"""FR-166.1 拆卡底表：design.md §6、Notion spec、工作坊四欄都從這張表產，改一處三處一致。

每張卡：id／主題／批／repo／標題／reuse-extend-new／改哪些檔／新開哪些檔／依賴／驗收／model／effort／理由。
批（Stage）＝能同時跑的那一組：不改同一檔、不依賴對方產出、不搶同一外部資源（登記型檔：manifest.tsv、
event_code.py、remote_agent.py adapters、console/routes.py、enroll.py、Shell.vue 各自只讓一張卡碰）。
repo 值：main（BE）、fe、jedi-packages、agent。
"""

TOPIC_NAME = {"T1": "下載權限點", "T2": "離線剩餘天數", "T3": "profile 同步", "T4": "代設工具帳密", "T5": "Agent 日誌", "T6": "語言切換", "T7": "文件"}
REPO_NAME = {"main": "BE", "fe": "FE", "jedi-packages": "jedi 套件", "agent": "evidence-agent"}

CARDS = [
# ───────── 批 1：套件、Agent 日誌管線、FE 殼層小改 ─────────
dict(id="A1", topic="T1", stage=1, repo="jedi-packages", model="opus", effort="medium",
  title="jedi-file-upload：簽票分 preview／download 兩種用途、下載端點只收 download 票、宿主可注入下載守門",
  ren="extend：`UploadFileAccessTokenRoute` 加 `?action=preview|download`（預設 preview，舊前端不壞）；`FileUploadAdapters` 加一欄 `download_guard`（宿主注入，None＝不擋）；`UploadFileDownloadRoute` 驗票時只收 action=download 的票；不另開第二條下載端點",
  edit=["jedi_file_upload/api/routes/upload_file_route.py 141（簽票）、160（下載）", "jedi_file_upload/plugin/contract.py 125（FileUploadAdapters）", "jedi_file_upload/api/guards.py 42（file_access_guard 傳 action）"],
  new=[], deps=[], why="改守門鏈要看清楚 st 的 scope 與 action 怎麼編進票；範圍小但錯了全系統下載壞",
  accept="套件 tests：簽 preview 票打 download 端點 403；簽 download 票先過 `download_guard`（注入一支會拒的 guard → 簽票 403）；沒注入 guard 行為與今天相同；pdf-preview 端點不受影響"),
dict(id="A2", topic="T1", stage=1, repo="jedi-packages", model="sonnet", effort="medium",
  title="jedi-remote-agent：主控台票新範圍 download-files ↔ 能力點 file.download；AgentEventCode 補本機事件碼；console_event 收 event_code",
  ren="extend：`plugin/contract.py` 的 `CONSOLE_SCOPE_CAPABILITIES` 與 `OFFLINE_OPERATOR_SCOPE_CAPABILITIES` 各加一列；`common/code.py` `AgentEventCode` 從 6207 起加碼；`heartbeat_console.ingest_console_events` 收的事件多一個 `event_code` 鍵（沒帶仍照 action 對照，舊 Agent 不壞）",
  edit=["jedi_remote_agent/plugin/contract.py 44、52", "jedi_remote_agent/common/code.py 89", "jedi_remote_agent/app/service/heartbeat_console.py 87"],
  new=[], deps=[], why="登記型改動、形狀照既有；三處各加幾行",
  accept="tests：持有 `file.download` 的票帶 `download-files`；離線操作者範圍含它；`ingest_console_events` 帶 event_code 的事件原碼寫入、只帶 action 的照舊；新碼 6207～ 不與主專案 `event_code.py` 撞號（開卡已查最大 6206）"),
dict(id="A4", topic="T3", stage=1, repo="jedi-packages", model="opus", effort="medium",
  title="jedi-detection：Agent×profile 取得狀態表、心跳附加欄位 provider、代設帳密的 handover 單（overwrite）",
  ren="extend：新表 `agent_profile_states(agent_id, profile_version_id, sha256, synced_at, error)` 照套件既有 migration 形狀；`credential_handover_service.start_for_agent` 加 `tools=`／`overwrite=` 參數（代設走同一支，不另開 service）；心跳附加欄位 `profiles` 的 provider 放套件（主專案接線）",
  edit=["jedi_detection/app/service/credential_handover_service.py 91、116", "jedi_detection/migrations/（新號）"],
  new=["jedi_detection/domain/detection_tools/entity/agent_profile_state_entity.py", "jedi_detection/infra/detection_tools/model+repo（agent_profile_state）", "jedi_detection/app/service/agent_profile_sync_service.py（current_profiles_for_heartbeat()、record_agent_state()）"],
  deps=[], why="一個新 aggregate＋既有 service 加參數；RLS 與唯一鍵一次定",
  accept="DEV 套套件 migration 後 `\\d` 新表有 RLS 四指令與 `(agent_id, profile_version_id)` 唯一鍵；`current_profiles_for_heartbeat()` 只回 `is_current` 版本的 `{uid, version, sha256, file_uid}`；`start_for_agent(tools=[x], overwrite=True)` 建的單 params 帶 overwrite；既有 handover tests 綠"),
dict(id="A3", topic="T5", stage=1, repo="agent", model="opus", effort="high",
  title="Agent 稽核改走 logging 管線：system_logs（SQLite）＋QueueHandler＋操作者過濾器＋DB handler 過渡子類；41 個呼叫點改用套件 audit()",
  ren="reuse：入口用 jedi-remote-agent `common/audit.py::audit(logger, event_name, event_code, **fields)`；表／DTO／service 用 jedi-common 0.0.31 `logger/db_log/system_log`；標準庫 `QueueHandler`＋`QueueListener`。new（過渡層，升 jedi-common 即刪）：`DBLogHandler` 薄子類（只放行帶 event_code、缺欄補 None）。new：`ConsoleActorFilter`（從 `flask.g.console_session` 補 user_uid／user_nickname／login_method／ip，背景執行緒補 system）。刪：`audit.py` 的 jsonl 寫入、ISO 游標、`local_rows`、`REPORTED_ACTIONS`／`_MASKED_KEYS`",
  edit=["core/console/audit.py 155（整支改寫：record→委派 audit()；pending／ack 改讀表、游標改自增 id）", "core/local_db/migrations.py（v3：system_logs 表，欄位照 jedi-common model）", "core/app_factory.py 24（logging 接線一處）", "core/console/heartbeat.py 51", "core/console/{routes,ops_routes,scan_routes,netdiag_routes,tools,files,runs_routes,ldap_routes,local_auth,local_auth_routes}.py、core/{console_allow_cidr,local_state_cli,ldap_source}.py（41 處呼叫改簽名、拿掉 actor 參數）"],
  new=["core/audit_pipeline.py（filter、queue、handler 子類、install()）"],
  deps=[], why="跨 15 個檔的簽名統一＋執行緒模型；要實查 @transaction 在 listener 執行緒能否自開 scope；做錯稽核整批靜默消失",
  accept="登入／設帳密／手動掃描後 `sqlite3 data/agent.db 'select event_code,user_name,message from system_logs'` 有列且密碼欄為 `***`；心跳 `console_events` 帶 `event_code`、ack 後游標前進（重啟不重送）；背景執行緒事件 user_name=system；把 filter 拿掉 → 測試紅（突變）；`grep -rn 'console-audit.jsonl' core/` 為 0；寫表失敗只進 app log、請求不失敗；180 天清理在心跳跑一次／天"),
dict(id="F1", topic="T6", stage=1, repo="fe", model="sonnet", effort="medium",
  title="主控台殼層：登入後語言切換、總覽「離線登入剩餘天數」卡、選單登記「日誌」分頁（先指向佔位）",
  ren="extend：`Shell.vue` 頂欄把 `Login.vue` 既有的 `toggleLocale` 搬成共用（`state.js` 或新 `useLocale.js`，localStorage 記住，照 `theme.js` 形狀）；`Overview.vue` 的 banner 既有 `offline_days`／`offline_login_days` 算剩餘天數加一張卡（照 `overviewHealth.js` 既有卡形狀）；**登記型檔集中**：`Shell.vue` 選單本卡一次加「日誌」項並佔 i18n key，F3 只新開 view",
  edit=["console/Shell.vue 201", "console/Login.vue 200（toggleLocale 改呼叫共用）", "console/views/Overview.vue 141", "console/router.js（logs 路由指向 Placeholder.vue）", "i18n zh-tw/en agent-console.json"],
  new=["console/useLocale.js"],
  deps=[], why="純 FE 小改，規格明確",
  accept="登入後右上角有地球鈕，切換後重整仍是該語言；總覽有「離線登入還能用 N 天」卡：N=上限−已離線天數、≤0 紅字並寫「只剩本機管理員與 AD 可登」、在線時卡不顯示；側欄多「日誌」項（點進去是佔位）；`grep border-left console/` 為 0"),

# ───────── 批 2：主系統接線、Agent 四張 ─────────
dict(id="B1", topic="T1", stage=2, repo="main", model="opus", effort="medium",
  title="主系統：能力點 file.download（出貨無任何角色持有）、簽票注入能力點守門、Agent 執行紀錄報告下載改看它、EventCode 鏡像、console sink 收 event_code",
  ren="extend：migration 照 `2026-10-27-fr166-agent-standalone-capabilities.sql` 形狀但**不綁預設角色**；`core/plugins/file_upload.py` adapters 多注入 `download_guard=require_capability('file.download')`；`core/plugins/remote_agent.py` `_ADMIN_CAPABILITIES` 的 `RemoteAgentLocalRunReportRoute.get` 改 `file.download`；`common/enum/event_code.py` 鏡像 A2 新碼；`core/plugins/_agent_console_audit.py` 收 event_code；跑 `scripts/deliverables/gen_permission_matrix.py` 重產 FE `capabilities.js`（FE 檔由 F2 commit）",
  edit=["core/plugins/file_upload.py 351", "core/plugins/remote_agent.py 186", "common/enum/event_code.py 198", "core/plugins/_agent_console_audit.py 25", "scripts/sql/manifest.tsv"],
  new=["scripts/sql/2026-10-29-fr166-1-file-download-capability.sql"],
  deps=["A1", "A2"], why="守門接線＋一支 migration；要實打三條端點驗 403／200",
  accept="DEV 套 migration：`select count(*) from role_capabilities rc join capabilities c on c.id=rc.capability_id where c.name='file.download'` 為 0（Administrator 靠 `*.*.*`）；無此能力點的帳號 `GET /file/access-token/<uid>?action=download` 403、`?action=preview` 200；Agent 執行紀錄報告下載端點同樣 403／200；BE 重啟實打；守衛測試綠；回寫明講「出貨基線待重產」"),
dict(id="A5", topic="T1", stage=2, repo="agent", model="sonnet", effort="medium",
  title="Agent 端：檔案與報告下載只認 download-files 範圍、檔案頁補預覽端點、不能 inline 的格式不給看",
  ren="extend：`files.py` 的 `files_download` 改 `require_session('download-files')`、新增 `files_preview` 走既有 `inline_policy`（照 `runs_routes` 預覽端點形狀，不複製）；`netdiag_routes` 報告下載同樣改範圍；`local_login.py` 認得新範圍（票上帶不認得的範圍整張拒收，先認再用）",
  edit=["core/console/files.py 169", "core/console/netdiag_routes.py（report 下載）", "core/console/runs_routes.py（報告下載）", "core/console/local_login.py（範圍白名單）"],
  new=[], deps=["A2", "A3"], why="改守門值與加一支預覽端點，形狀都有",
  accept="curl 8444：票無 `download-files` 時 `GET /console/files/<uid>/download` 403、`/preview` 200 且 HTML 回應帶 `Content-Security-Policy: sandbox`；svg 預覽 415；主系統票帶 `download-files` 時 200；tests 含突變（把範圍檢查拿掉要紅）"),
dict(id="A6", topic="T5", stage=2, repo="agent", model="sonnet", effort="medium",
  title="Agent 日誌查詢 API：依人／事件碼／日期分頁讀本機 system_logs",
  ren="reuse：jedi-common `system_log_service.get_system_log_list(curr_page, page_size, **filters)` 已有分頁與篩選，端點只做參數解析與序列化；守門 `require_session(('configure','files','run-local-scan','net-diag'))` 唯讀；**登記型檔**：`routes.py` 只加一行掛 blueprint",
  edit=["core/console/routes.py 212（掛一行）"],
  new=["core/console/log_routes.py"],
  deps=["A3"], why="薄端點，service 現成",
  accept="`GET /console/logs?page=1&size=50&user=local-admin&event_code=6093&from=…&to=…` 回 `{items, total}`，欄位 act_time／user_name／event_code／message／method；無 session 401；message 裡沒有密碼（A3 已遮）"),
dict(id="A7", topic="T3", stage=2, repo="agent", model="opus", effort="medium",
  title="Agent profile 同步：心跳收 profiles 清單比對本機快取、背景補拉、回報每支狀態；主控台「立即檢查」",
  ren="reuse：取檔與快取用既有 `core/profile_cache.py`（`_populate_cache`、`is_cache_valid`），**不另寫下載**；流程照 `core/vulndb_sync.py` 形狀（observe→比對→背景執行緒→回報→冷卻），`request_check()` 同款；**登記型檔**：`enroll.py` 掛一個 observer＋payload 加一欄",
  edit=["core/enroll.py 287（payload 加 `profiles`）、362（observer 一行）", "core/console/ops_routes.py（`POST /console/profiles/check` 照 vulndb/check 形狀）"],
  new=["core/profile_sync.py"],
  deps=["A4"], why="要抽 profile_cache 的兩支內部函式成可呼叫介面、不複製；背景執行緒與冷卻照 vulndb",
  accept="心跳回應帶 `profiles:[{uid,version,sha256,file_uid}]` 時，本機沒有的在背景拉、拉完 marker sha256 一致；下次心跳 payload `profiles:[{uid,version,sha256,state:ok|error,error}]`；`POST /console/profiles/check` 清冷卻；拉失敗回報 error 不重試到死（冷卻同 vulndb）；稽核 `AGENT_PROFILE_UPDATED` 一筆"),
dict(id="A9", topic="T4", stage=2, repo="agent", model="sonnet", effort="low",
  title="Agent 收代設帳密：handover 單帶 overwrite 時蓋掉本機既有值",
  ren="extend：`credential_handover.store` 讀 `task.get('overwrite')`，True 走 `credential_store.put`（既有）而非 `put_if_absent`；回報 summary 多 `overwritten`",
  edit=["core/credential_handover.py 20"],
  new=[], deps=["A4"], why="十行改動",
  accept="tests：overwrite=False 既有不蓋（stored/skipped 同今天）；overwrite=True 蓋掉並回 overwritten；log 只帶工具代號；稽核 `AGENT_CREDENTIAL_SET` method=handover"),

# ───────── 批 3 ─────────
dict(id="B2", topic="T3", stage=3, repo="main", model="opus", effort="medium",
  title="主系統 profile 同步接線：心跳附加 profiles、心跳回報入表、profile 頁查每台 Agent 取得狀態、補更新端點",
  ren="extend：`core/plugins/remote_agent.py` `heartbeat_extras` 加 A4 的 provider（照 `_agent_vulndb.py` 形狀新開 `_agent_profile_sync.py`）；心跳 payload `profiles` 入表走套件 service；新端點 `GET /detection-profiles/<uid>/agents`（每台 Agent：version、sha256、synced_at、error）與 `POST /remote-agents/<uid>/profiles/check`（經心跳要求 Agent 立即檢查，照 restart 旗標形狀）；守門 `detection.profile.read`／`detection.remote-agent.configure`",
  edit=["core/plugins/remote_agent.py 319（heartbeat_extras 加一項）、186（兩端點登記）"],
  new=["core/plugins/_agent_profile_sync.py", "api/remote_agent/routes/agent_profile_sync_route.py"],
  deps=["A4", "A7"], why="接線兩端＋兩支端點；要實跑一輪心跳驗狀態落表",
  accept="DEV：上傳新版 profile → 123 Agent 下次心跳拉到 → `agent_profile_states` 有列、`GET …/agents` 看得到 ok；按補更新 → 心跳回應帶旗標 → Agent 清冷卻；BE 重啟實打；守衛測試綠"),
dict(id="F2", topic="T1", stage=3, repo="fe", model="opus", effort="high",
  title="全系統下載鈕看 file.download：主站 10 處＋Agent 執行紀錄頁＋主控台檔案頁改預覽、執行紀錄對話框下載鈕；commit 重產的 capabilities.js",
  ren="extend：主站 `signedDownloadUtil.fileDownloadUrl` 簽票帶 `action=download`、`filePreviewUrl` 帶 preview；10 處下載鈕統一 `v-can=\"CAP.FILE_DOWNLOAD\"`（PrimeVue Menu 項用既有 `useMenuItemsCan`）；主控台 `Files.vue` 下載鈕改看 `me.scope` 含 `download-files`、加「預覽」鈕 reuse `ReportPreviewDialog`（svg／xhtml 不給看、顯示「此格式僅能下載」）；`Runs.vue` 對話框下載鈕同範圍；`runs.js canDownload` 改認 `download-files`",
  edit=["src/utils/signedDownloadUtil.js 44", "src/components/grc/JobExecutionDrawer.vue 1434（超標 3179 行：只改一行加 v-can）", "src/components/grc/evidence-view/RoundEvidenceView.vue 80", "src/views/feedback/system/SystemFeedbackView.vue 60", "src/views/feedback/system/SystemFeedbackForm.vue 240", "src/views/evidence-classification/components/EvidenceFilePreviewDialog.vue 135", "src/composables/useSbomViewer.js 26", "src/composables/useScanFileDownload.js 19", "src/views/remote-agent/AgentLocalRunsView.vue 213", "src/config/capabilities.js（B1 重產，本卡 commit）", "console/views/Files.vue 90", "console/views/Runs.vue 185", "console/runs.js 82"],
  new=[], deps=["B1", "A5"], why="13 個檔逐一核對守門方向，漏一處就是權限洞；量大但每處小",
  accept="無 `file.download` 的角色：主站 10 處下載鈕全部不見、預覽照常；主控台檔案頁只有預覽、報告對話框無下載；有能力點的角色全部回來；`grep -rn 'DOWNLOAD_FILE\\|openFileDownload\\|fileDownloadUrl' src/ | grep -v v-can` 逐處說明；`npm run build` 與 `build:CONSOLE` 過；`grep border-left` 為 0"),
dict(id="F3", topic="T5", stage=3, repo="fe", model="sonnet", effort="medium",
  title="主控台「日誌」分頁：依人／事件／日期篩，欄位比照主站使用者日誌頁",
  ren="reuse：表格與篩選列照主站 `src/views/action-log/UserLogs.vue` 的欄位與寫法，分段用 SegmentButtons；資料來源 A6 端點；事件碼顯示用既有 i18n `lang.event_code.*`（grep 確認主站有）；只新開 view，不碰 `Shell.vue`（F1 已登記）",
  edit=["console/router.js（logs 指向新 view）"],
  new=["console/views/Logs.vue", "console/logsApi.js"],
  deps=["A6", "F1"], why="照範本刻一頁",
  accept="分頁對 123 實際有列；篩人／事件／日期區間有效；message 不顯示密碼；`grep border-left` 為 0；表格一格一值"),

# ───────── 批 4 ─────────
dict(id="B3", topic="T4", stage=4, repo="main", model="opus", effort="medium",
  title="主系統代設 Agent 工具帳密端點：在線才收、建 overwrite handover 單、主系統不落地；檢測工具頁 route 翻回並改名",
  ren="extend：新端點 `POST /remote-agents/<uid>/tool-credentials {tool, fields}` 走 A4 `start_for_agent(tools=[tool], overwrite=True)`，fields 只進派工單 payload（既有 `build_payload` 從雲端密文讀 → 本卡加「一次性 fields 直接進單」分支，不存 `tenant_detection_tool_configs`）；Agent 離線（`is_online` 為 false）回 409 `GRC_409xxx`；守門 `detection.remote-agent.configure`；稽核 `AGENT_CREDENTIAL_SET` method=remote；migration 把 `tool-plugin-manage` 翻回 enable=1、名稱改「Agent 工具帳密代設」、綁 configure 能力點",
  edit=["core/plugins/remote_agent.py 186（端點登記）", "common/code/grc_error_code.py", "scripts/sql/manifest.tsv"],
  new=["api/remote_agent/routes/agent_tool_credentials_route.py", "scripts/sql/2026-10-29-fr166-1-tool-page-agent-credentials.sql"],
  deps=["A4", "A9"], why="帳密只能在記憶體過一手、不落地；要核派工單 payload 加密路徑",
  accept="在線 Agent：POST 後 123 下次心跳收到單、本機值被蓋、回執 overwritten；離線 Agent 409；`select * from tenant_detection_tool_configs` 沒新增列；`system_logs` 有 6086 method=remote 且 message 無帳密；選單翻回且改名；守衛測試綠；回寫明講「出貨基線待重產」"),
dict(id="F4", topic="T3", stage=4, repo="fe", model="sonnet", effort="medium",
  title="主站 profile 頁：每個版本展開「Agent 取得狀態」表＋「補更新」鈕",
  ren="extend：`ScanToolResourcesView.vue`（82 行）加一個子元件掛點；新元件 `ProfileAgentStates.vue` 讀 B2 端點，表格一格一值（Agent 名、版本、同步時間、狀態 Tag），補更新鈕 `v-can=\"CAP.DETECTION_REMOTE_AGENT_CONFIGURE\"`",
  edit=["src/views/plugin/ScanToolResourcesView.vue 82", "src/config/api/api.js", "i18n"],
  new=["src/views/plugin/components/ProfileAgentStates.vue"],
  deps=["B2"], why="一個表格元件",
  accept="上傳新版後表格顯示 123 從「待更新」變「已取得」；按補更新有 toast、Agent 下次心跳拉；離線 Agent 顯示「離線」不報錯；`grep border-left` 為 0"),

# ───────── 批 5、6 ─────────
dict(id="F5", topic="T4", stage=5, repo="fe", model="opus", effort="medium",
  title="主站「Agent 工具帳密代設」頁：選 Agent、選工具、填帳密、送出；離線直接說連不上",
  ren="extend：`ToolPluginManage.vue` 766 行**超標**：只留殼與路由，新邏輯進新元件 `AgentCredentialSetter.vue`；工具表單 reuse 既有 `tool_schemas`／`config_field_schema` 的表單渲染（grep 主控台 `SecretForm.vue` 與主站既有工具表單，二擇一抽共用、不第三份）；送出後顯示「已送出，等 Agent 下次心跳（最多 N 秒）」、輪詢 handover 進度（既有 `credential_handover` 欄）",
  edit=["src/views/plugin/ToolPluginManage.vue 766（淨減）", "src/config/api/api.js", "i18n", "src/router（頁名）"],
  new=["src/views/plugin/components/AgentCredentialSetter.vue"],
  deps=["B3", "F2"], why="舊頁重用途、超標檔要拆",
  accept="選離線 Agent 送出 → 409 toast「這台現在連不上」；在線送出 → 幾十秒內狀態變「Agent 已收」；主系統 DB 無帳密；`wc -l ToolPluginManage.vue` 比 766 小；表單不自刻第三份；`grep border-left` 為 0"),
dict(id="D1", topic="T7", stage=6, repo="main", model="sonnet", effort="medium",
  title="SPEC 與手冊補到現況：下載權限點、profile 同步、代設帳密、Agent 日誌、剩餘天數、語言切換；FE 角色矩陣多一顆",
  ren="extend：`docs/spec-site/current/evidence/agent-console.md`、`remote-agent-manage.md`、`scan-tool-resources`（profile 頁）對應頁各加段＋檔頭變更紀錄一行；手冊 `docs/user-manual/agent/13-standalone-operation.md` 13.x 補操作；`docs/claude/reference_permission_system_sop` 若列能力點清單補 `file.download`",
  edit=["docs/spec-site/current/evidence/agent-console.md", "docs/spec-site/current/evidence/remote-agent-manage.md", "docs/user-manual/agent/13-standalone-operation.md"],
  new=[], deps=["F2", "F3", "F4", "F5"], why="量產文件",
  accept="六個主題各有一段；`grep -n '原本\\|第 [0-9] 棒\\|這次'` 命中 0；SPEC 頁 build 過"),
]

def check():
    by_stage = {}
    ids = {c["id"] for c in CARDS}
    for c in CARDS:
        assert all(d in ids for d in c["deps"]), c["id"]
        by_stage.setdefault(c["stage"], []).append(c)
    stage_of = {c["id"]: c["stage"] for c in CARDS}
    for c in CARDS:
        for d in c["deps"]:
            assert stage_of[d] < c["stage"], (c["id"], d)
    for st, cs in by_stage.items():
        seen = {}
        for c in cs:
            for e in c["edit"]:
                key = (c["repo"], e.split(" ")[0].split("（")[0])
                assert key not in seen, (st, key, seen.get(key), c["id"])
                seen[key] = c["id"]
    return True

if __name__ == "__main__":
    check(); print("ok", len(CARDS))
