# FR-169 下載檔加密 — LOG（append-only，每棒追加一 block）

## 2026-10-29 首腦：討論 → 討論稿 v1～v3 → design v1 → 開卡

- 決策者提問起點：「檔案下載後有人來我的電腦就可以直接開檔」；工研院要裝解密軟體太麻煩。
- 裁示序列：先不做撤銷（IRM／DRM 整條排除）→ 不要使用者自訂密碼（定案系統產、畫面顯示一次）→ Agent 離線寄不了信要搬通知模組？（確認密碼只走畫面不寄信，不搬）→ 加密函式放哪？（定案獨立套件，不放 jedi-file-upload、不塞 jedi-common）→ 看雛形「不要有未加密下載的選項」（D5 v2 拿掉使用者可選）→ 「要讓 user 選嗎？業界怎麼做？PM 意思都要加密」（v3 定案：使用者永遠不能選、管理員開關預設開、關閉三道警示）→ 「都照建議，落 design 開卡」。
- 推翻過的：v1 三選一含「使用者可選」→ v2 兩檔 → v3 一個開關預設開。首腦曾建議「寫死強制」後自己重算推翻（e2e／教育訓練、客戶 DLP、匯出餵別系統三種情境要正規出口）。
- 雛形 bug：對話框遮罩蓋住頂部切換橫幅，決策者點不到其他畫面；改遮罩只蓋畫面區、橫幅 sticky 置頂。
- 開卡陷阱：agent-workshop 的 `scripts/notion_case.py` 預設指向另一個「任務清單」（Agent Workshop 自己的，64 張卡、無 Repo 欄）；Guidant AI 的卡在 `23c346da-4cd0-8041-955e-000bb6976dd2`。本案 `patch_workshop_fields.py` 明確覆寫 `nc.DATASOURCE_ID`；建卡走 BE 本地 `scripts/notion_create_case.py`（它旁邊的 notion_case 指對庫）。
- 拆卡斷言抓到一處：D1 依賴 F3 卻同批，移批 5。
- Commits：529e801b1 analysis、196eb9f7c 討論稿 v1＋雛形、d4b2a2902 雛形修遮罩、6ae27ec48 v2、82bad11b0 v3、761c7fd24 design＋cards.py、（本次）開卡與卡號回填。
- Notion：母卡 CM-3223；子卡 CM-3224 P1 套件（批1）、CM-3225 B1（批2）、CM-3226 F1（批2）、CM-3227 B2（批3）、CM-3228 F2（批3）、CM-3229 A1（批3）、CM-3230 F3（批4）、CM-3231 D1（批5）。
- 下一步：決策者在工作坊狀態頁「新增工作坊」選 CM-3223 派批 1。

## 2026-10-29 首腦：母卡層總驗 → 三支套件發版 → 補批 6 兩張卡

- 八張卡（CM-3224～3231）工作坊首腦已逐卡驗到「驗收通過待確認」；本棒做母卡層抽查：四個 repo 工作區 `ws-CM-3223` 都乾淨、commit 在；BE helper 測試 117 過（兩支 `capabilities_are_seeded` 紅是工作區沒掛 seed、主資料夾同測試綠，非本案）；Agent 工作區全量 1674 過；套件 jedi-file-protect 38 過、jedi-masking 25 過；FE 兩支 spec 綠、新增 `border-left` 為 0；DEV 已有 28 筆 FILE_DOWNLOADED 稽核、`log/app.log` grep `X-File-Password` 為 0。
- **沒驗到、留給決策者**：GUI 開檔（Word／Acrobat／7-Zip 跳密碼框）這台機器沒有 GUI 軟體；Agent 真機下載（Agent 要註冊才起得來，小弟與首腦都只用 test client 打路由）。
- **總驗抓到三個缺口，開批 6 補卡**：①主系統收 Agent 稽核事件的白名單 `_agent_console_audit._FIELDS` 沒有 `encrypted`，Agent 那邊是加密還是明文下載主系統稽核頁看不到（A1 首腦復核已記、沒人開卡）；②D5 的「總覽常駐警示」沒做（只有安全政策頁自己的警示）；③D5 的「稽核頁每筆下載標未加密」沒做（i18n 預留了 key 沒人用）。D1 第 2 輪為了「文件只寫現況」把②③從 SPEC 與手冊拿掉了，B3 做完 F4 再補回。→ CM-3280 B3（BE，sonnet/low）、CM-3281 F4（FE，sonnet/medium），同批。
- **套件發版**（決策者「可以的話就發套件版本」）：jedi-file-protect 0.1.0 首發、jedi-iam 1.9.0→1.10.0（新鍵是功能，進中版號）、jedi-masking 0.1.0→0.1.1（jedi-log 與 Agent 都釘 `<0.2`，不能進 0.2）。build 到 scratchpad 乾淨資料夾、開包 grep 驗三處改動都在、再 publish；Nexus 查到三版都上架。套件 repo 工作區 commit `ae95ad61`。
- **釘版**：BE 主資料夾 `pyproject.toml` jedi-iam 1.10.0、加 jedi-file-protect 0.1.0、dev path 註解區補一行，`poetry update` 三支，驗 import 在 site-packages、無 `.pth`；commit `34206718e`。Agent 工作區 `poetry add jedi-file-protect`、升 jedi-masking 0.1.1；commit `4df889e`。四個 repo 的 `workshop.md` v4 各自 commit。
- **踩到**：BE session 的 shell 帶 `VIRTUAL_ENV`，到套件工作區跑 `poetry install` 把 editable 套件裝進 BE venv；拔掉後重做。記憶 `feedback_poetry_in_worktree_respects_virtual_env_of_session`。
- **jedi-iam 測試紅燈是基線既有**：`test_plugin_layout::test_frozen_urls_match_actual_mount` 與五支 collection error 在 feature/FR-139 主資料夾同樣紅，與本案無關，沒動。
- **未完，等決策者**：工作坊「合回」四個 repo 的 `ws-CM-3223` 到 `feature/FR-139`（合回後重啟 8000 才吃到新程式與新套件）；派批 6；母卡 Done；push；STG／POC 上版。

## 2026-10-29 首腦：四 repo 合回 feature/FR-139 ＋ 重啟驗證

- 決策者「你把它合回來呀」：四個 repo 主資料夾 `git merge --no-ff ws-CM-3223`。套件 94841065、Agent 0d30cf5 無衝突；BE 5ae53b390 衝突一處（`agent-console.md` 變更紀錄表 FR-166.1 與 FR-169 同位置各加一行，兩行都留）；FE 0aeefd06 衝突一處（`console/views/Files.vue` import 區，FR-166.1 F2 的 `canDownload`／`downloadOnly`／`ReportPreviewDialog` 與 FR-169 F3 的 `useProtectedDownload`、拿掉 `saveBlob`，兩邊都留，檔內各符號皆仍在用）。合回後四 repo `ws-CM-3223` 領先 0。
- 重啟 8000（只殺核過的 57425 與其 multiprocessing 子行程 57505；worker 41809 不動）。release 字串帶合回 commit。實打：`GET /system/file-download-protection` 回 enabled true；`POST /governance/risks/export` 無 header 回 400 GRC_400190；帶 `X-File-Password` 回 200、xlsx `is_encrypted` True、用同密碼解回 PK 開頭 21,344 bytes、header `Cache-Control: no-store`／`X-File-Protected: 1`／`X-File-Needs-7zip: 0`；稽核多一筆 encrypted=True；`log/app.log` grep 密碼 0。守衛測試＋helper 128 過。
- 工作區 `.worktrees/CM-3223` 保留：決策者會派批 6（CM-3280、CM-3281），工作坊會疊在 `ws-CM-3223` 上做，做完再按一次「合回」。
- **等決策者**：派批 6；母卡 Done；push 四 repo；STG／POC 上版。

## 2026-10-29 首腦：批 6 驗收 → 合回 → 十張卡收齊

- 批 6 兩張卡工作坊首腦各驗兩輪通過。本棒母卡層抽查：B3 `_FIELDS` 加 `encrypted`、`_clean` 只對該欄放行 bool 轉字串；sink 測試 17 過、突變（拿掉該欄）1 紅、還原。三處文件 grep「稽核事件頁每筆」0、兩份手冊一致。F4 `FileProtectionOffBanner.vue` 掛 `AppLayout`、條件 store 值為 false 且持安全政策讀取能力點；用 SSR 渲染字串驗三種狀態（false 顯示／true 不顯示／null 不顯示）通過；`isUnprotectedDownload` 殘留 0；新檔 `border-left` 0。
- **D5 第三道「畫面上標未加密」決策者裁示不做**（F4 復核時）：系統沒有稽核事件頁，稽核事件只出到診斷包與 log 轉發；UserLogs 讀的是 HTTP 請求流水表 `api_logs`，比對不到 `system_logs` 的稽核。開卡時把 UserLogs 當稽核事件頁是首腦寫錯。
- **FE 工作區 vitest 那條 i18n 守衛紅燈是工作區落後 feature**：五句都是 CM-3279 在 feature 修的、ws 沒碰那五檔；合回後 FE 全跑 367 全綠。不是本案問題，記下免得下次又追。
- 合回：BE 9a909e711、FE e9a521e5，零衝突；兩 repo ws 領先 0。BE 測試 145 過；8000 重啟帶 9a909e711。
- **還沒驗到**：真機 Agent 下載後主系統稽核看到 `encrypted=true`——DEV 唯一一台 Agent（1126d080）今天 18:58 送的事件還沒這欄（當時主系統沒收、Agent 也未必是 A1 版）；要等 Agent 以合回後的程式部署再看。GUI 開檔與橫幅實看同前。
- **等決策者**：母卡 Done；四 repo push；Agent image 出包（A1 回寫記的 Nuitka 收 py7zr／msoffcrypto／reportlab 檢查）；STG／POC 上版；出包前基線重產（seed 守衛兩顆能力點缺 04-seed-core.sql，FR-166.1 遺留）。工作區 `.worktrees/CM-3223` 可由工作坊「結案」移除。

## 2026-10-29 首腦：決策者實測證據附件沒加密 → 直修 → 全站出口盤點補卡

- 決策者截圖證據區塊按下載直接開。根因：開卡時範圍定成「主系統 16 支匯出 route」，D1 把 jedi-file-upload 列「不動」，套件自帶的 `GET /file/download/<uid>`（證據、程序書、模組框架文件池、前置資料、問卷附件、意見回饋附件）整條漏接；FE 走 `signedDownloadUtil.openFileDownload` 換 `?st=` 開新分頁，帶不了密碼 header。十張卡驗收與合回都沒抓到，因為驗的都是匯出端點。
- **直修**（決策者「你這邊直接修」）：jedi-file-upload 1.4.0→1.5.0 加選填 `FileUploadAdapters.download_responder`、`runtime.send_download`、下載 route 改走它（未注入照舊 send_file；拿掉 `?attachment=`）；contract 測試＋突變。BE `core/plugins/_file_upload_download.py` responder 把路徑／BytesIO 讀成 bytes 交 `send_protected(source="upload_file")`，`file_upload.build_adapters` 注入；helper 測試三條。FE `useProtectedDownload.downloadFile(fileUid, name)`，12 處改接，`fileDownloadUrl`／`openFileDownload` 刪除，意見回饋兩頁的手刻 axios blob 一併收掉。
- 驗證：套件 25 過（突變紅）；BE 131 過；FE 367 過、build 過；DEV 實打截圖那兩個檔：docx 無密碼 400、帶密碼加密可解；png 包 header 加密 7z；稽核 source=upload_file；log 無密碼。發版 1.5.0 推 Nexus、BE 釘版重啟再打仍對。commits：套件 13e0de4a、FE cfd311d9、BE 84a23bf41。
- **全站出口盤點**（grep 主專案＋已裝 jedi 套件的 send_file／Content-Disposition、FE blob／window.open／DOWNLOAD_FILE）：還漏三支人用機敏匯出——jedi-api-log 操作日誌、jedi-issue 意見回饋匯出、jedi-task-platform 任務批次 Excel。不接（理由）：jedi-iam 匯入範本與 TOTP QR、jedi-asset 裝置範本、jedi-evidence-classification inline 預覽、jedi-detection 報告（主系統 Agent 報告端點已接）、BE `agent_file`／`agent_vulndb`（Agent 機器對機器）、OSCAL 框架 JSON（公開）。→ 批 7 P2 CM-3286（套件掛點）、批 8 B4 CM-3287（主系統注入＋FE 三處）。
- 教訓進 memory `feedback_download_exits_enumerate_all_routes_incl_packages_before_scoping`：橫切需求開卡前固定三條 grep，每條出口都要在「接」或「不接（理由）」其中一邊。
- **等決策者**：派批 7；母卡 Done；四 repo push；Agent 出包；STG／POC。

## 2026-10-29 首腦：zip 炸彈修正 → D3 改判 7z→AES-256 zip

- 自動資安檢視指出上傳檔走 `send_protected` 會被自動拆 zip（原前提是只有自產 zip 會進來）→ 加 `unzip` 旗標，responder 傳 False；測試＋突變。commit fa95445f1。
- 決策者「不要用 7z 請用 zip，我剛剛下載不能解壓」：講明 AES zip 檔名外露、內建解壓一樣開不了、ZipCrypto 等於沒鎖；決策者選 AES-256 zip。
- 改動：套件 archive 換 pyzipper（WZ_AES 256、ZIP_DEFLATED、禁 ZipCrypto）、副檔名 .zip、`needs_7zip` 欄位名與 `X-File-Needs-7zip` header 沿用（語意改「要裝第三方解壓工具」），0.2.0 發版（e1c18c23）；BE mimetype application/zip、測試改 pyzipper 驗、釘版 0.2.0；Agent mimetype 與測試、相依上限放寬到 <0.3（6673c97、lock commit）；FE 對話框文字與 fallback 副檔名（37a04cce）；SPEC 七頁與手冊兩份文字；design D3 改判、README 結論。
- 驗證：套件 37 過（突變：拿掉 WZ_AES 紅、256→128 紅）；BE 132 過；Agent 19 過（真裝 0.2.0）；FE 367 綠、build 過；DEV 實打 png 下載回 application/zip、`zipfile` 列得出檔名、無密碼讀內容 RuntimeError、帶密碼解出 PNG；log 無密碼。
- 已知代價寫進文件：zip 檔名清單明文（檔名不要放機密）；Windows／macOS 內建解壓打不開 AES zip，對話框與手冊都改成提醒裝 7-Zip／WinZip／Keka／The Unarchiver。
- 決策者問「檔名外露是什麼意思」，解釋（zip 檔名清單不用密碼看得到、內容要密碼）後裁示：「畫面上都有了，有檔名也不能幹嘛，用原檔名.zip 就好」。首腦提的兩案（zip 內檔名改 `file-<時間戳>-<序號>.<副檔名>`＋manifest 對照；只改外層檔名）都不做。現況即終局：外層 `原檔名.zip`、裡面原檔名、內容 AES-256。
- 決策者點名「故意不思考的狀況很嚴重」：這一輪三次設計（7z→zip、檔名中性化、副檔名）都是先動手再被問出盲點。教訓：改容器格式這種對外可見的決定，動手前先把「使用者拿到會看到什麼、哪一層被鎖、哪一層沒鎖」三句講清楚給決策者裁，再改。
- 決策者「壓縮檔類不要顯示副檔名」：包 zip 的單檔外層檔名去掉原副檔名（`截圖.zip`，不是 `截圖.png.zip`），裡面保留原檔名；Word／Excel／PDF 照原檔名。jedi-file-protect 0.2.1 發版（a5c23e37）、BE 釘版、Agent 升版與測試（7119048）。DEV 實打：X-Filename `…截圖.zip`、裡面 `…截圖.png`、AES strength 3（256）、密碼可解。
- 踩到：突變測試後用 `cp` 還原 `archive.py`，mtime 沒變、舊 pyc（128 位元版）繼續被 import，一度量到 AES-128 以為套件寫錯；清 `__pycache__` 後 256。已發版 wheel 與 BE venv 實量都是 256，線上無影響。以後突變測試用 `git checkout -- <檔>` 還原，不用 cp。
- 對話框：決策者看畫面「上下說明留一個或合併」→ zip 情況合併成一個橘框，刪 `hint_7z`（FE f48b57f0）。
- 決策者「還是有問題，給我 prompt 我請下個首腦繼續修」：沒說是什麼問題，本棒結束。STATE §2 寫明下一棒先問清楚再動。

## 2026-10-29 首腦（第二棒）：「還是有問題」＝兩件事——Agent 本機登入（不歸本案）、PDF／Office 預覽哭臉（FR-166 回歸）

- 決策者說明「還是有問題」是：①本機 Agent 主控台登不進去 ②docx 無法預覽，接著補「直接預覽 PDF、Excel、docx 都一樣」並附 Chrome 哭臉截圖。
- ①Agent 登入：決策者裁示「不要查，我請另一個 session」，本棒停手不追。查到一半的線索留一行：Agent log 顯示 `console login rejected: ticket scope [...] is empty or unknown`，PID 18931 是 10-28 21:28 起的，`download-files` 那個 scope 是 10-29 02:29 才加進 `CONSOLE_SCOPES`，跑的程式比票舊——只是線索不是結論，給另一棒自己驗。
- ②預覽：後端實打 `/file/pdf-preview/` 三個檔（netdiag PDF、CRA docx、control-mapping xlsx）都回 200 `application/pdf` inline、未加密，LibreOffice 轉檔正常，**不是 FR-169 弄壞的**。根因在 FE `FilePreviewPane.vue`：44f2e5f7（FR-166 CM-3198，10-28 晚）加的 `:sandbox="sandboxed ? '' : undefined"`——真瀏覽器 `iframe.sandbox` 是 DOM 屬性，Vue 走 `el.sandbox = undefined` 被字串化成 `sandbox="undefined"`，等於全禁 sandbox，Chrome PDF 檢視器不畫。
- 為什麼十張卡＋昨晚實機都沒抓到：jsdom 的 iframe 沒實作 `sandbox` 屬性，Vue 在測試裡改走 setAttribute、undefined 正確移除，「PDF iframe 不帶 sandbox」那條斷言一直假綠；實機只開過 HTML（sandboxed=true）沒開 PDF。**教訓：DOM 屬性與 attribute 的分歧 jsdom 測不出來，涉及 iframe／media 這類有專屬 DOM 屬性的元素，要在真 Chrome 開一次。**
- 修法：改 `:sandbox.attr`（強制 setAttribute）。HTML 報告預覽仍帶 `sandbox=""`、後端 CSP sandbox 不動，PDF 回到 10-28 前狀態、沒放寬。Pane spec 補瀏覽器形狀的 `HTMLIFrameElement.prototype.sandbox` stub＋兩條斷言；突變（拿掉 .attr）紅；vitest 375 綠、build 過。FE commit `72ed484c`。決策者手測預覽 OK。
- 決策者指示「不用做太多測試，我會手動測試」——後續只做修正與必要守衛測試。
- 踩到：突變後 `git checkout -- <檔>` 把自己未 commit 的修正也一起還原了（還原的是整個檔不是只有突變那行），重套一次。要突變未 commit 的改動，先 commit 再突變，或用 sed 反向改回。

## 2026-10-29 首腦（第二棒）：PDF 預覽藏瀏覽器工具列 → 文件收尾

- 決策者看到 docx 轉 PDF 的預覽上方有 Chrome 內建檢視器的下載／列印鈕，問能不能擋。這是 FR-169 的真缺口：有預覽權的人按那顆鈕拿到的是沒密碼的 PDF，繞過加密也繞過 FR-166.1 F2「預覽歸預覽、下載看能力點」。
- 先講三句再動：藏工具列一行（擋順手點，擋不住右鍵／Ctrl+S，Firefox／Safari 不理）；pdf.js 自繪一張卡（全擋畫面操作，擋不住開發者工具）；後端轉圖片不建議（截圖本來就擋不住）。決策者「先加那一行」。
- 做法：主站 `FilePreviewDialog` 與主控台 `console/components/ReportPreviewDialog.vue` 的 PDF blob 網址加 `#toolbar=0`；共用 `FilePreviewPane` 加 `@media print { .fp-body { display:none } }`。HTML 預覽不加片段。第一版把判斷放在 Pane 猜網址，把主站 HTML 的 st 網址也加上了，改成由呼叫端決定。三條既有 PDF src 斷言跟著改；vitest 375 綠、build 過。FE commit `03e6acfb`。
- 決策者「Agent 你沒改到，還是因為沒重啟」：主控台走 `dist-console` 靜態包（Agent 從 `AGENT_CONSOLE_ASSETS_DIR` 直接讀目錄、不入版控），停在 20:45 的舊包，sandbox 修正與工具列都沒進去。`npx vite build -c vite.console.config.js` 重 build 後決策者實看 OK，Agent 不用重啟。**教訓：改到 `src/components/common/file-preview/` 或 `console/` 的東西，主站重整就吃到、主控台要重 build dist-console；回報時要分開講。**
- 文件：共用檔案預覽視窗 SPEC（變更紀錄＋規則 3、4）、agent-console SPEC 執行紀錄一句、兩份手冊加「線上預覽不用密碼但拿不到檔」一節、design 變更紀錄兩列（D3 補 0.2.1、D7 補）＋D7 定案改寫＋驗收第 9 條、README status／結論／需求討論紀錄三筆、STATE。
- 決策者「準備收尾」：收尾類動作（母卡 Done、push、SUMMARY、memory）等令。

## 2026-10-29 首腦（第二棒）：批 7～8 首腦直做 → 三支套件發版 → 收尾

- 決策者問批 7、8 是什麼、要不要做。首腦實查三個出口仍 `send_file` 明文，判斷要做（操作日誌是全站 API 請求紀錄，最敏感）。決策者「你這邊直接做」、「發吧，做完就發，然後這邊可以收了」、「最後再 push 一下」。
- 套件（`aad916f4`、`5230800d`、`9cda814e`）：jedi-log 操作日誌、jedi-issue 意見回饋、jedi-task-platform 任務批次匯出照 jedi-file-upload 1.5.0 形狀加 `download_responder`＋`send_download`；jedi-log route 事後補的 `X-Filename`／expose 改 `setdefault`（直接指派會蓋掉宿主設的加密後檔名與 `X-File-Protected`）。發版 jedi-log 1.4.0、jedi-issue 1.5.0、jedi-task-platform 1.5.0，開包驗過再 publish。
- jedi-log 1.4.0 會連帶發出源碼裡本來就沒發過的 1.3.7（jedi-masking 薄殼化），相依 jedi-masking 0.1.1（早已上架）；jedi-log 自己的跨套件 import 守衛因此早就紅，白名單補 `jedi_masking`。BE `test_module_boundaries` 也登記 jedi_masking（層 1.5、能力點豁免、頂層單檔白名單）。
- BE（`b16f053a5`、`c9bbd014f`）：`_file_upload_download.responder_for(source, unzip=False)`，api_log `api_log_export`（unzip=True，自產 zip 重包成一個 AES zip，裡面 xlsx 不雙重上鎖）、issue `feedback_export`、task `job_export`；釘三支新版。FE 不用改：三處 F2 就走 `useProtectedDownload`，只是後端沒收。
- DEV 實打（先 pip -e、釘版後再打一次）：三支都是無密碼 400 GRC_400190、帶密碼 200 加密檔、稽核 source 正確、log 無密碼；pytest 133 過。
- 踩到：BE working tree 有另一個 session（FR-166.1 首腦）未 commit 的 jedi-detection path 改動在 pyproject／lock。釘版時先存 patch＋備份、`git stash` 那兩檔、在乾淨 HEAD 上 update＋commit、再 pop；pop 撞 lock 的 content-hash 衝突，手解後 `poetry lock` 重算、`git restore --staged` 讓兩檔回到對方未 commit 狀態、jedi-detection path 重裝回 venv；stash@{0} 保留未 drop。對方事後來訊說那個 path 已不需要、可以還原到 HEAD 並 drop stash——交給對方自己收或決策者裁。
- 文件：三頁 SPEC（任務批次、意見回饋、操作日誌；操作日誌那列順手改掉過期的「🔴 無認證」）、README、design、STATE。

## 2026-10-29 首腦（第二棒）：系統回饋頁白畫面——cfd311d9 漏 import

- 決策者「公告功能整個壞掉」，貼系統回饋表單網址與主控台截圖：`ReferenceError: useProtectedDownload is not defined`（SystemFeedbackForm.vue:25）。後端端點全部 200，問題在 FE。
- 根因：cfd311d9（證據附件補洞，12 處改接）在系統回饋表單與檢視兩頁加了 `useProtectedDownload()` 呼叫卻沒 import。vite build 不檢查未宣告識別字、vitest 沒掛這兩頁，所以 build 與全量測試都過，打開頁面才炸。全站掃過只有這兩頁漏。
- 修：兩頁補 import；新增守衛 `src/__tests__/protected-download-import-guard.spec.js`（有呼叫卻沒 import 就紅，略過註解行），突變拿掉 import 會紅並指名檔案。FE `257b52dc`，已 push。
- **教訓：大批改接呼叫端（一次改十幾個檔）時，build 綠不代表每頁都開得起來；改完要 grep「有呼叫卻沒 import」，或靠這支守衛。**

## 2026-10-29 首腦（第二棒）：收尾

- 決策者問目前可設定的下載能力點有哪些，首腦列出 7 顆（任務證據、安全計畫佐證、稽核匯入原稿、AI 分類暫存、檢測報告、意見回饋附件、Agent 下載檔案）與各自用在哪些畫面；指出「稽核匯入原稿」前端沒有畫面引用，按鈕不會依它隱藏、只靠後端 403。只回報，未改。
- 決策者「這邊可以收 Done 了」：Notion CM-3286、CM-3287、母卡 CM-3223 改 Done（前十張早已 Done）。README／design／STATE 改已收尾；STATE §3 依決策者裁示三項未驗當作驗過，§4 只留上版事項。

## 2026-10-29 首腦（第二棒）：收 Done 後的三個回報

- **SBOM 頁一開就跳缺少密碼**：決策者以為是匯出報表，實為頁面載入時 SBOM 檢視器打 `/files/sbom` 拿 JSON 解析顯示，B2 把這支整條改加密。講三句、決策者同意後：後端 `inline=1` 且 `kind=sbom` 回純 JSON（inline、nosniff、不給檔名），其他 kind 帶 inline 照舊加密；前端檢視器帶 inline。BE `9d0d5f380`、FE `759f8af6`。自動資安檢視指出「client 旗標決定不加密、不記稽核」——同證據預覽的既有取捨、決策者已知，未改。
- **責任分工 Excel 也不要加密**：首腦查出匯入端全專案沒有能解密加密 xlsx 的，七支「匯出再匯回」樣板都有同一問題；提「類別設定、證據強制」方案。決策者裁示：**責任分工設定、控制項對照、任務批次先不加密，設定功能先不做**。新增 `send_unprotected`（照樣記 FILE_DOWNLOADED encrypted=false），三類改走它（控制項對照 4 個出口共用 `_download` 一起改）；前端三處 `protect: () => false`。BE `32d3c42b1`、FE `22748f7a`。DEV 實打三支不帶密碼 200 明文、稽核 encrypted=false、沒登入 401。
- **決策者問「是不是只有前端擋、知道路徑就能下載」**：不是，加密在後端，直接打網址不帶密碼 400、帶了拿到的是加密檔。真正知道路徑就拿得到明文的只有預覽端點（證據 docx 預覽回未加密 PDF），這是 FR-169 一開始就有的取捨；要堵的兩個方向（預覽也要下載權限／預覽改圖片或 pdf.js 自繪）已回報，等決策者裁。
