# FR-107 交接日誌（append-only）

> **這個檔只追加，寫完的 block 永不修改。** 發現舊 block 有誤 → 在新 block 的
> 「推翻了什麼」欄寫明更正，不要回頭改。
>
> 現況看 [`fr107-STATE.md`](fr107-STATE.md)；本檔是決策軌跡與教訓的來源。

---

## Block 1 — 2026-09-16 首腦（討論稿 → design → 開卡 → .1 三張驗收）

### 這一棒做了什麼

從決策者對 FR-030 Drive 版分類線的盤點討論開始，寫討論稿、拿到 D1–D12 裁示、落成
design.md、開母子卡（29 張連號），並跟到 .1 前三張（T-1.1／T-1.2／T-1.3）驗收通過。

### commits

見 STATE 表 B。

### 關鍵決策（決策者拍板）

- **D1～D10**：批次檔另開新表；容器改甲案（宿主先拉檔）；舊 Drive 線並存一版；job 狀態
  DB 化做最小版；分類結果沿用舊表加欄；未設儲存只在上傳路徑擋；清批次實刪實體；批次權限
  manager 可寫、auditor 唯讀；不做跨來源去重；孤兒回填歸 ROOT。
- **D11**（本文與討論稿最大差異）：新功能全部進既有套件 `jedi-evidence-classification`，
  不開新套件；主專案只留宿主 adapter 與前端。理由：批次的狀態機、歸檔演算法是引擎自己
  的知識，開第二支套件等於兩處各長一半；被排除方案是新開 `jedi-evidence-batch`（違反插件
  互不相依）與主專案自己做（重蹈 FR-069 之前「一半在主專案」的病）。
- **D12**（寫作中追加）：支援多供應商 AI，金鑰進系統設定（租戶級，比照 `STORAGE_CONFIG`）
  而非只放環境變數——落地版客戶不會自己改伺服器 `.env`，改了也要重啟，無法自助切換供應商。
- 六項補裁：容器 image 進出貨包（T-6.4）；port 切法照 design 兩支分工；
  `classification_profiles` 不建 FK；profile CRUD 只給平台管理員；`job_evidences` 既存
  重複列先查不合併；舊 route／FE 常數並存期可接受、.6 一起清。

### 推翻了什麼

無（本棒為新案起點，尚無被推翻的判斷；.1 三張 runner 對卡面規格有三處更正，見下）。

### 教訓

**教訓 1｜開卡時寫的檔案落點會撞 `.gitignore`，三張卡同一個錯。**
T-1.1／T-1.2 卡上都寫 migration 落 `scripts/sql/packages/<套件>/`，但那個目錄是 build 時
從套件攤出來的產生物、在 `.gitignore` 內，放進去 commit 不了。正本該放套件自己的
`migrations/`，且不自帶 `INSERT schema_migrations`（build 腳本統一登記）。**開卡引用
既有卡當範本時，範本本身如果錯，會被原樣複製三次**——T-1.1 runner 第一個查出來，
T-1.2／T-1.3 開卡時首腦已知道要改但沒有回頭巡一次已開好的卡面文字，是 runner 自己在
「已完成修正」段裡各自更正一次。

**教訓 2｜`grep` SQL 關鍵字判「零寫入／零影響」是盲區，本案在派工 prompt 就先寫明。**
上一個 arc（FR-104／FR-105）已踩過 ORM `session.flush()` 不會被 SQL 關鍵字 grep 抓到，
本案 T-1.1 卡面「怎麼做②」已預先要求同時 grep `session.add`／`merge`／`flush`，算是把
教訓直接內化進卡片而非等 runner 撞到。

**教訓 3｜「剩下的孤兒歸 ROOT」字面執行會傷到真實使用者。**
卡上 D10 寫「查不到歸屬的孤兒歸 ROOT」，T-1.1 runner 若照字面只做參照鏈回填，會有
1804 筆被掃進 ROOT——這些檔案的真正擁有者從此看不到自己的檔（RLS 擋掉）且無錯誤訊息。
runner 多加一層「退回上傳者所屬公司」並與參照鏈結果交叉比對（711 筆一致、0 衝突）驗證
後才用，孤兒降到 21 筆。**寫回填規則時，「找不到就歸預設值」這句話本身要包含「先窮舉所有
可能的間接判定依據」，不能只給一條鏈路就當作規則完整。**

### 未驗證的假設

- T-1.4（FE 上傳到批次頁）與 .2（儲存 port／容器改造）尚未驗收，港灣式風險：T-1.3 已驗證
  後端六條 API 全部實打過，但前端串接與容器實際掛目錄跑分類的行為本棒未觸及。

### 環境異動紀錄

- **DEV DB**（`localhost:5432/guidant_ai_dev`）：套了 T-1.1（套件 jedi-file-upload
  `004-upload-files-tenant-owner-rls.sql`）、T-1.2（套件 jedi-evidence-classification
  `003-evidence-batches.sql`）兩支 migration。
- **STG／POC**：完全沒碰。

---

## Block 2 — 2026-09-17 首腦（T-1.4 退回兩項修正驗收、.2 三張夜跑核驗、T-1.4 UI 整頁退回）

### 這一棒做了什麼

復查 T-1.1 回填的兩項待修正、對 .2 三張（T-2.1／T-2.2／T-2.3）做夜跑核驗，並處理決策者
對 T-1.4（CM-1853）前端 UI 的驗收——整頁不接受，開新卡 T-1.4m 先出 HTML mockup 過目再
重做。

### commits

見 STATE 表 B（本棒未新增 commit，僅驗收與開卡）。

### 關鍵決策（決策者拍板）

- **T-1.4 UI 整頁退回重做**：決策者原話「最好用流程引導的方式，需要有一個歷史清單，
  新建批次與上傳分開，功能應該叫『自動分類證據』不是『批次上傳證據』，上傳的檔案一律
  要能預覽，檔案數量一多會一直往下長，整個重新設計，參考業界設計」。根因是上一棒跳過
  `guidant-design-system` skill 明寫的「新頁面先出 HTML mockup 過目再實作」規定，直接刻
  Vue，落成畫面後才被全盤打回。已定案：兩頁結構（歷史清單頁＋四步流程頁）、第③步重用
  既有審閱頁（換資料來源屬 T-3.4）、命名「自動分類證據」路由 `auto-classify`。開
  T-1.4m（CM-1878）先出 mockup，過目後才重派 T-1.4。

### 推翻了什麼

- **首腦誤判時區差 8 小時**：復查 T-1.1 回填結果時，首腦一度認為 DB 記的時間與 API 回應
  的時間對不上、疑似資料不一致，實際上 DB 存的是 `+08` 時區、API 回的是 `GMT`，是同一個
  時刻的兩種表示法。runner 原本的復查是對的，首腦第一次判斷錯誤，重新核對後撤回。
- **T-1.4 跳過 mockup 直接實作**：違反 `guidant-design-system` skill 的明文規定，是本棒
  被推翻的實作方向，見上「關鍵決策」。

### 教訓

**教訓 1｜新頁面必須先出 mockup 過目，開實作卡時要把「mockup 已過目」列為前置條件。**
T-1.4 卡面沒有把「先出 mockup 給決策者看過」寫成前置步驟，只當作 skill 背景知識期待
runner 自己記得，結果整頁做完才被打回，浪費一次實作與一次驗收的成本。之後開任何新頁面
實作卡，前置條件要明寫「mockup 已過目通過」而不是假設 runner 會自己觸發那個 skill。

**教訓 2｜runner 驗收打折要當場講明，.2 的 AI 401 標得好。**
.2 三張夜跑核驗時，其中一張的驗收條件因為外部 AI 服務 401（金鑰過期或額度問題）沒辦法
用真實呼叫驗證，runner 在回報裡把「這一項用模擬／替代方式驗證」寫得很清楚，讓首腦知道
這是打折的驗收而不是無條件通過，符合「驗收環境打折要當場講明」的規則，值得延續。

### 未驗證的假設

- T-2.1／T-2.2／T-2.3 為首腦實查後「建議通過」，尚未經決策者本人確認，狀態先標「修正待
  驗證」等最終蓋章。
- T-1.4m mockup 尚未產出，本棒只完成開卡與 CM-1853 的退回說明，實際兩頁 HTML 稿件由
  下一棒（另開 session）做。

### 環境異動紀錄

無（本棒只做驗收復查與 Notion／文件作業，未動 DB、未動程式碼）。

---

## Block 3 — 2026-09-17 首腦（下午，T-3.2／T-5.1／T-5.4 驗收、D13 原廠 AI 金鑰裁定）

### 這一棒做了什麼

驗收 T-3.2、T-5.1、T-5.4 三張卡；T-5.4 抓到三處需修正、退回重做；就「AI 金鑰在落地版怎麼
發給客戶」跟決策者討論，拿到 D13 裁示並落成 design.md、開 T-5.5（CM-1879）。

### commits

見 STATE 表 B（本棒為驗收與文件／開卡作業，無新增程式碼 commit）。

### 關鍵決策（決策者拍板，D13）

原廠給客戶一組 AI 金鑰，放進 ROOT 租戶（tenant_id=1）的 `AI_PROVIDER_CONFIG`（加密存
DB，不寫 `guidant.env`）；AI 小幫手／AI Dashboard／證據分類三功能統一改成「租戶自己的設定
→ ROOT 原廠鑰 → 環境變數」三層解析；第一版不開放租戶自填（畫面鎖 ROOT 平台管理員）；
`install.sh` 裝機時把原廠鑰寫進 ROOT，`--upgrade` 不覆寫既有值。被排除：明文存 DB、加密鑰
與密文放同位置、「原廠代理」完全藏鑰（列為獨立 FR 候選，未裁）。風險天花板與 Drive OAuth
token 同級——防意外與 DB 外洩，不防客戶自己的 root 有心人（解密鑰仍在同一台機器上）。

### 推翻了什麼

- **裁示 4「profile CRUD 只給平台管理員」修正為「放寬到租戶管理員」**：實查發現四層解析鏈
  裡租戶那兩層本來就只有租戶管理員自己建得出來，只鎖平台管理員的話那兩層永遠沒人用得到，
  形同虛設。已在 design.md 裁示 4 旁加註 2026-09-17 修正說明，AI 供應商金鑰設定頁仍維持鎖
  ROOT 平台管理員（D13，跟 profile 守門是不同軸，不要混為一談）。
- **首腦誤把 T-5.2 當「進行中」**：復核時發現 T-5.2 其實從未真正被派出去，只是先前某棒給過
  一次建議 prompt，首腦誤把「給過 prompt」當「已派工」。**派工狀態要看卡片實際狀態欄位，
  不能靠印象推斷**——之後回報一律先查卡片再說派出或待派。
  - **首腦誤判 T-5.4 金鑰頁該開放給租戶**：討論初期一度覺得「技術上租戶各自能填金鑰比較
  合理」，但決策者裁示第一版就是要鎖 ROOT、統一用原廠那把，不開放客戶自填——「怎麼做技術
  上合理」不能取代「決策者的商業意圖」，這類「誰能設定什麼」的問題要在設計階段直接問決策
  者，不能只靠技術推理自己下判斷。

### 教訓

**教訓 1｜「給過 prompt」不等於「已派工」，回報前先查卡片狀態。**
首腦手上有派工 prompt 草稿不代表工作已經發出去，只有 Notion 卡片狀態真的挪動了才算派出；
下一棒接手看到「進行中」字樣，要先去卡片核對而不是照單全收。

**教訓 2｜「誰能設定什麼」屬商業決策，不能用技術合理性推論，要在設計時直接問決策者。**
T-5.4 金鑰頁該不該開放租戶自填，從純技術角度兩種做法都說得通，但答案其實是決策者的商業
判斷（第一版鎖定原廠統一供應、避免客戶到處填錯或洩漏）。遇到這類「權限開放範圍」的題目，
不要自己推理出一個「看起來合理」的答案就往下做，要先問。

### 未驗證的假設

- T-5.5（CM-1879）尚未派工，D13 的三功能統一解析器實作與 install.sh 寫入行為完全未觸碰，
  現況仍是三處各自讀法（AI 小幫手／Dashboard 直讀 env，證據分類讀 DB 但容器注入仍讀 env）。
- T-5.4 退回的三處修正尚未重做，需等下一棒依修正意見重新實作再驗。

### 環境異動紀錄

無（本棒只做驗收復查、design.md／STATE／LOG 文件更新、開 Notion 卡，未動 DB、未動程式碼）。

---

## Block 4 — 2026-09-17 首腦（下午～交接，T-5.2/5.4/5.5 收尾、T-3.3+5.3a 斷線接手、三張新派、交接雙檔重核）

### 這一棒做了什麼

T-5.2（catalog／報表）三輪修正後驗收、T-5.4（金鑰頁）驗收退回後重做三輪過關、T-5.5
（D13 三功能統一金鑰＋install.sh）開卡直接做完；接手一個中斷的 session（T-3.3＋T-5.3a
併做），拆成兩支獨立 commit 落地；新派三張卡（T-3.4／T-4.1／T-5.3b）；本 block 收尾時
重新逐張核對 Notion 卡片真實狀態與四個 repo 實際 git log，據此重寫 STATE 表 A／表 B。

### commits

見 STATE §4／§5（表 A 逐卡 commit、表 B 統整）。

### 關鍵決策（決策者拍板）

延續 D13；T-5.5 落地細節：`AI_PROVIDER_CONFIG` 三功能（AI 小幫手／AI Dashboard／證據
分類）共用同一支解析器，`install.sh` 在裝機階段寫入 ROOT 原廠鑰、`--upgrade` 路徑不覆寫
既有值（避免升級洗掉客戶已設定的租戶鑰或已輪替的原廠鑰）。

### 推翻了什麼

- **本棒交接前的自我查核，推翻了「派工 prompt 本身」的多項事實**：撰寫這份 STATE／LOG
  更新前，逐張重查 Notion 與逐一重跑四個 repo 的 `git log`／`git status`，發現交接指令裡
  當作「已實查」的下列事實其實是**舊資訊，未反映最新進度**：
  ①「T-3.4／T-4.1／T-5.3b 派出中」——正確，但需標明是**同一天下午晚些才派**、尚未有任何
  runner 回報；②「T-1.4 待決策者手測」——已經是 Done，不需要再排驗收；③「T-5.2／T-5.4／
  T-5.5 待派或退回中」——三張其實都已經 Done；④「T-1.1／T-1.2／T-1.3 標『✅ 驗收通過』」
  ——Notion 實際狀態是「修正待驗證」，比「Done」低一階，尚未真正蓋章。**「派工 prompt 裡
  的『已實查』事實」本身也需要被下一棒當作「聲稱」而非「保證」來對待**——這是 Block 3
  教訓 1（「給過 prompt 不等於已派工」）的鏡像版本：這次踩到的是「首腦盤點過的事實，寫進
  prompt 後也會在傳遞過程中變舊」。
- **T-5.4 三輪修正**：第一輪驗收退回，具體三處修正意見與退回原因見 CM-1867 卡面歷史，
  本 block 不重複抄（卡片是唯一真相來源，文件只記結論）。

### 教訓

**教訓 1｜「已實查」的事實會在交接鏈路上變舊，接手方仍要自己重驗一次高頻變動欄位
（卡片狀態、commit hash 清單），不能把「盤點過」直接當「當下為真」。**
本案的教訓 3（Block 3）講的是「給過 prompt 不等於已派工」，這次是同一顆藥丸的另一面：
「盤點過」也不等於「維持到你讀到這份文件的當下還是真的」——尤其是卡片狀態與 commit 清單
這種每小時都可能變的欄位。**寫交接文件的規則要更新為：卡片狀態與 commit 清單這兩類欄位，
永遠在動筆前用即時查詢重新核對一次，不可以沿用上一份文件裡抄來的版本**，即使那份文件
自稱「皆已實查」。

**教訓 2｜表 B commits 清單這種會員爆炸成長的內容，不該逐條抄進 STATE，改成「去對應
卡片查」的索引式寫法。**
本案 commits 數量已經到 BE 33 筆／套件 14 筆／FE 9 筆，且還在持續增加，逐條列在 STATE
會讓這個「≤200 行」的檔案被 commits 清單吃掉大半篇幅，且每棒都要重新核對每一條 hash 是否
還對得上（容易出現複製貼上時的手誤）。改成「commit 清單以 Notion 卡片『已完成修正』段
為準，STATE 表 B 只給逐卡一行 hash」大幅降低維護成本與出錯面。

### 未驗證的假設

- T-3.4／T-4.1／T-5.3b 三張卡截至本 block 收尾時，Notion 狀態仍讀「Not started」（T-4.1
  在 BE repo 有對應的未 commit 程式碼改動，疑似已有 runner 在動但尚未回寫卡片與 commit——
  下一棒接手前**先看這三張卡片是否已更新**，不要假設仍是「剛派出、什麼都沒開始」）。
- T-3.4 完成後尚未實測「舊 Drive 分類路由是否仍打得開」（design.md D3 舊線並存的驗收項）。

### 環境異動紀錄

- **DEV DB**：套了套件 `jedi-evidence-classification` 004／005（T-5.1／T-5.2 對應
  migration）與主線 `scripts/sql/2026-09-17-fr107-ai-service-config-menu.sql`（T-5.5，
  新增 main-line 選單項）。**ROOT 租戶（tenant_id=1）的 `AI_PROVIDER_CONFIG` 已寫入三家
  供應商加密金鑰**（Anthropic 可用，OpenAI／Google 額度或金鑰無效，見 STATE §6）。
- **STG／POC**：完全沒碰。

---

## Block 5 — 2026-09-17 首腦（第五棒：冷接→驗收十四張→手測修正→push→開發收口）

### 這一棒做了什麼

冷接後驗三張跑中卡（T-3.4／T-4.1／T-5.3b）；開七張新卡（CM-1883 升級路徑補原廠鑰、CM-1884 批次詳情補欄、CM-1885 碼表換代＋思考深度、CM-1886 e2e 拆出、CM-1887 小幫手／Dashboard 換代、CM-1888 手測第一批八項＋五追加、CM-1889 基線重產）；依序派出並驗收 T-4.2、CM-1884、T-4.3、T-6.1、T-6.2（發 1.2.0）、T-6.3（拆卡）、CM-1885、CM-1887、CM-1888、CM-1889 共十四張改 Done；決策者親手測整條線；三 repo push；決策者裁「先不發版」，出貨兩卡暫停。

### commits

見 STATE §4／§5。

### 關鍵決策（決策者拍板）

- OpenAI 鑰無額度不擋 T-5.3b：先 Done、儲值後補驗（後已儲值，CM-1885 真跑四支型號全過）。
- 升級路徑也要補寫原廠 AI 金鑰（缺補、有不動）。
- 模型碼表換代：Anthropic Sonnet 5（預設）／Opus 5；OpenAI 預設 `gpt-5.6-terra`、可選 `gpt-5.6-luna`（不放 sol／astra）；小幫手 Haiku 4.5；Dashboard 三檔皆 Sonnet 5；新增「思考深度」三檔預設 medium。
- 金鑰讀法不改：三功能已統一走 DB resolver（首腦清空 env 實驗證過）。
- T-6.3 拆卡：SPEC 這半做，e2e 移 CM-1886 等 image。
- 「歸檔方式」選項從未接線，整組拿掉；「刪除整批」UI 有就要做，硬刪不軟刪。
- 先不發版，多 FR 一起發；模型設定驅動先不做；Drive 當儲存後端先不立案。
- 授權 push 三 repo；CM-1889 放行寫 188 基線庫。

### 推翻了什麼

- 首腦給的 GPT 型號與價格（gpt-5／gpt-5-mini）是第三方舊資料，決策者拿官方計費表糾正為 gpt-5.6 系列；CM-1885 卡面作廢改補充。
- CM-1885 runner 說「DEV OpenAI 鑰是空字串」——它讀 `.env`，鑰在 DB；首腦解密實查三把都在。
- T-6.3 runner 查出 190 沒有任何 image 含 FR-107 端點，原卡「stub 同名取代即可」前提不成立 → 拆卡。
- CM-1888 ⑥ 比卡上寫的重：控制樹 jobs 根本沒 `evidences` 欄位，手動上傳與 Drive 同步從以前就沒算過。
- 四支選單 migration 全漏登記 manifest（含前一棒 CM-1867），客戶端 migrate 會靜默不套。
- 出貨基線待重產清單從「六支」修正為主線五支（套件四支走攤平不進基線）。
- Block 4 說「三張跑中卡 Not started」——冷接時三張都已回寫「修正待驗證」，STATE 高頻欄位又一次過期（同 Block 4 教訓 1）。

### 教訓

1. **價格與型號 id 這種外部事實，不能拿第三方搜尋直接給決策者**——對官方頁或請決策者提供，否則整張卡型號名重寫。
2. **「鑰在哪」要寫進派工卡**：三功能統一走 DB resolver 後，runner 憑直覺讀 env 就得出「鑰是空的」並給三個都不對的選項；卡上一句「取鑰走 `resolve_api_key`，不讀 env」省一輪。
3. **換模型世代不是換字串**：5 系列 thinking 預設開，`content[0].text` 會炸、`max_tokens` 要放大、`budget_tokens` 回 400；每次換代把「回應形狀變了沒」當必查項。
4. **彙總讀模型「有沒有算某型資料」要用 DB 真相對照 API 回應驗**，光看 code 會低估缺口。
5. **migration 寫完必查 manifest 登記**：漏登記＝客戶端靜默不套，比忘記套 DEV 更難發現。
6. **首腦驗收自己起 BE、自己建批次真跑**（含 OpenAI）比讀回寫快，抓到「FE 沒送 provider」這種只有真打才出現的 400。
7. **文件更新 subagent 讀大檔會 autocompact 爆掉**：STATE／LOG 這種要整檔重寫的，首腦直接寫比派 sonnet 穩。

### 未驗證的假設

- `gemini-3.5-flash` 型號 id 存在（runner 查官方文件，DEV Google 鑰無效未實打）。
- 落地版 BE 容器能 `docker run` 分類容器——compose 沒掛 docker.sock，高度懷疑不能，T-6.4 要補。

### 環境異動紀錄

- **DEV DB**：套件 006（effort 兩欄）、主線四支選單 migration（icon-fix／system-menu-regroup／cloud-integrations-regroup）＋ job-evidences（T-4.1）；驗收建的批次 19（run 35 歸檔進 round 78 的 14 張任務 21 筆證據）、579e00fd（run 34）、4aaaf23f（run 36／37）；本機 `evidence-classifier:dev` 重 build 含 `--effort`。
- **188 基線庫 `guidant_ai`**（決策者放行）：CM-1889 套五支主線 migration，備份 `~/backup/guidant_ai_baseline_20260917_1836_before_CM-1889.dump`。
- 三 repo push 到 `origin/feature/review`。**STG／POC 零碰。**
