FR-103 交接日誌(append-only)

這個檔只追加,寫完的 block 永不修改。 發現舊 block 有誤 → 在新 block 的 「推翻了什麼」欄寫明更正,不要回頭改。

現況看 fr103-STATE.md;本檔是決策軌跡與教訓的來源。


§1

Block 1 — 2026-09-16 首腦(FR-099 / FR-100 / FR-102 / FR-103 四案)

這一棒做了什麼

從決策者一句「為什麼我看還有很多 route 在主專案」開始,逐支盤點主專案 api/ 底下 20 個目錄,查出兩支漏網、一支死端點,接著清掉。過程中衍生四個 FR:

FR 是什麼 結果
FR-099 意見回饋與標籤併入 jedi-issue ✅ 三棒收完,套件 1.2.0 已發 Nexus
FR-100 主專案剩餘 route 逐支盤點 ✅ 記錄卡 + 兩棒(translate 退役、flow-engine 加警語)
FR-102 全模組現況地圖(26 模組) ✅ 一棒,數字已抽驗
FR-103 殘留清理 🟡 五棒收完、兩棒待派

commits(本棒首腦自己動手的部分)

repo hash 做了什麼
BE 5e065b87 jedi-issue pin 1.1.1→1.2.0(發版後)
BE 08d1267f 出貨基線重產(CM-1833 的 migration 收斂進 02-schema)
BE 90be71f6 重產 Postman collection + routes.json
BE 11773fde FR-102 地圖加失效警示
BE a2812d90 FR-100 更正 flow_engine 歸屬敘述
BE 96c6814a 使用者匯入範本「帳號」欄改必填
BE ce410ffb 刪 generate_complex_password 死複本
FE 3d35099 補清 cruising i18n 殘留(五檔 25 key)
套件 edc9118 帳號匯入改必填 + 修密碼產生器少一碼

runner 各棒的 commit 見 STATE 的表 B。

關鍵決策

D1|jedi-issue 發版(2026-09-16) — 決策者裁「等第 3 棒收完一起發」,理由是第 3 棒 可能還會動套件,分兩次發是白做。實際執行時決策者明示授權首腦執行 poetry publish。 ⚠️ 規範原文是「user 明確指示才發版;絕不自動推 Nexus」——「絕不自動」指的是不可未經 指示自行發版,不是「絕不由 Claude 做」。 首腦一度誤讀為後者、拒絕兩次才被糾正。

D2|report 整條線退役 — 決策者裁「這不需要了」。它服務的是 bin/report_downloader/ 那支 Selenium(登入 Wazuh 下載 11 種 Windows 資安報表),不經前端。

D3|12 個 enable=0 選單全數退役 — 決策者逐列確認後裁。但 FE 側因 views/workflow/ 不是孤兒而拆成乙案(CM-1835)與丙案(CM-1836),見下方「推翻了什麼」。

D4|COMMON_PASSWORD_TOO_SHORT 留著不刪 — BE 已零使用、FE 只剩兩份 i18n 翻譯, 表面可刪。但 jedi-common 有註解寫著「本碼是 FE 該 key 的唯一語意,不要再讓出」—— COMMON_400002 是 CM-1802 特地從 COMMON_400001 分出來的(原本兩邊共用同一號碼但語意 不同)。刪掉會讓號碼變空號被回收,重蹈 CM-1802 的覆轍。決策者裁留著。

D5|FR-102 地圖用「加失效警示」而非逐格改 — 那份文件的定位是「某一天的快照」, 逐格追改會讓它失去快照的意義,下一輪又要再改一次。

🔴 推翻了什麼(首腦三次判斷錯誤,都是 runner 或決策者查出來的)

① views/workflow/ 是孤兒 → 錯(CM-1833 runner 查出) 首腦開卡時只查「誰 import 這些檔案」(結果 0),漏了執行期跳轉。實際有五支活頁面 用 window.open/router.push 開它(TaskSetupView:524/ProjectPlanningView:1930/ ModuleFrameItems:103/ModuleFrameTemplateEditView:104/ModuleFrameDetail:150)。 關鍵原理:前端路由守衛只擋 requiresAdmin,沒有依 ui_routes 擋直入——「選單 enable=0」只是不顯示在側邊欄,程式照樣打得開。照原卡刪下去,那五支的按鈕會全壞。

② flow_engine 要 226 檔大改造 → 錯(決策者連問三次才查清) 226 檔是 flow_control 的數字。app/flow_engine 實際只有 3,041 行/6 支 service, 而且 2026-08-31 舊設計稿寫的 9,183 行也已失效(兩筆 commit 561922d4/83a5c47e 砍掉六成)。首腦三次憑印象回答、每次被追問才去查,答案每次都不同。

③ 匯入三欄名稱沒解析成 id → 錯(決策者重新走一次流程後證實正常,CM-1837 已作廢) 首腦只讀了 check_excel_user_data 一段就下結論「套件完全沒有名稱→id 的解析」, 沒有把三條端點追完(/user/upload//user/validate//user/batch-upload)。 grep 在單一檔案零命中不等於整條鏈路都沒有。

教訓

教訓 1|查一半就下結論,三次都栽在同一件事上。 ①只查 import 漏了執行期跳轉、②憑印象轉述舊文件數字、③單檔 grep 當成全鏈路結論。 共同形狀是**「我查到的範圍」被當成「全部的範圍」**。判準:下結論前先問「我這個查法 會漏掉哪一種情況?」——import 之外還有跳轉、grep 之外還有動態組字串、單檔之外還有 別條路徑。

教訓 2|盤點文件會過期,而且過期得比想像快。 2026-08-31 的 flow-boundary-design.md 每個數字都失效了,半個月無人察覺。本棒產出的 FR-102 地圖因此檔頭就標「引用前先重驗」+當時 HEAD commit,並在同日下午被自己的 FR-103 改動推翻四項(已加失效警示)。引用任何盤點文件前先確認它的日期與 HEAD。

教訓 3|「改必填」這種小改動會改變執行路徑,引爆既有的沉睡 bug。 帳號改必填之後,匯入從「留空走 email prefix」改走正常建帳號路徑,撞上 generate_complex_password(12) 產出 11 碼的既有缺陷(符號那行被註解掉時減數沒跟著 從 4 改成 3),而預設政策要求 ≥12——實測修正前 200 次全不合規。 錯誤訊息還會誤導:「密碼不符政策要求」指向使用者輸入,但範本根本沒有密碼欄。 改動前多問一句「這會讓哪條路徑改變」。

教訓 4|policy=None 不是「不驗」是「用預設政策驗」。 user_batch_import_service.py 的意圖是「系統自產的密碼不驗政策」,寫成 policy=policy if user_supplied_password else None,但 validate_password_policy 收到 None 會 policy = policy or PasswordPolicy()。那個意圖從來沒生效過。 修完密碼長度後不影響功能,但語意仍不一致——未改,留給後人。

未驗證的假設(下一棒要當心)

  • 單筆新增使用者(不填密碼)那條路徑走 user_service.py:157 的自產密碼,修完應該 沒事,但首腦沒實測過。
  • CM-1834 的問卷前端四頁與共編 socket 未手測(runner 無瀏覽器環境)。後端 37 條 route 首腦已驗證全在(實 dump url_map、拔掉測試 37→0)。
  • 兩次無法重現的前端現象:租戶下拉只顯示 1 條(DB 實查應為 6 條,RLS 模擬也回 6 條, 重整後恢復)、匯入預覽三欄空白(重新走流程後正常)。兩者都指向前端狀態/載入時機, 但沒有證據。若再出現,第一步是在 DevTools Network 看 /tenants/menu 等三支 實際回幾筆——回滿但畫面不對是前端問題,回不滿才是後端 scope 問題。

環境異動紀錄

  • DEV DB(localhost:5432/guidant_ai_dev):套了 CM-1833 的 migration(12 選單 +16 能力點退役)
  • 188 出貨基線庫(guidant_ai):套了同一支 migration 並重產 02-schema.sql/ 99-stamp.sql。備份在 ~/backup/guidant_ai_baseline_20260916_1552_before_CM1833.dump
  • STG/POC:完全沒碰

§2

Block 2 — 2026-09-16 首腦(CM-1835 驗收 + 交接收尾)

做了什麼

CM-1835(乙案)驗收通過並收交接檔。本 block 只補這一棒,其餘見 Block 1。

CM-1835 驗收結果(FE 0dc93e1,六檔/1,434 行刪除)

驗收項 實查
🔴 /workflow/workflow-setup 未被誤傷 ✅ 路由在(router/index.js:356)、WorkflowSetupForm.vue 與 55KB Editor 都在
🔴 五支活頁面跳轉全在 ✅ TaskSetup/ProjectPlanning/ModuleFrameItems/ModuleFrameTemplateEdit/ModuleFrameDetail 五處齊全(另 2 處是註解引用,非跳轉)
三條路由移除 ✅ 各 0 條
i18n 四份語法 ✅ 全通過 json.load
殘留 grep ✅ 零命中

runner 做得好的一點:刪掉 WorkflowStepViewer.vue 後,順手把 WorkflowSetupEditor.vue 裡指向該已刪檔案的過期註解改掉(改成說明「新建流程時 route.query?.id 本就是 undefined」)。純註解、零邏輯改動——這正是「動到哪個檔順手收掉那個檔的過期脈絡」的做法。

推翻了什麼

無。Block 1 的三個誤判在本 block 沒有新增或更正。

教訓

教訓 5|交接檔的 §1 盤點會在寫的當下就過期。 本次派 subagent 盤點時 CM-1835 尚未 commit,subagent 如實記錄「FE 工作區有未 commit 檔」, 但寫完交接檔之前那棒就收了。首腦已更新該格並在檔頭標註哪一格被改過。 做法:盤點結果要標時間,併入時逐格確認還成不成立——不要因為「是 subagent 查的」 就假設它還是最新。


§3

Block 3 — 2026-09-16 接手首腦(CM-1836 收尾 + FR-104/105/106 五棒)

這一棒做了什麼

接手 Block 2 之後的交接,收掉 FR-103 最後一棒,再往下跑五棒並全部驗收通過:

棒 卡 做什麼 commit
FR-103 第 7 棒 CM-1836 丙案:新舊兩代 BPMN 編輯器整併評估——決策者裁不遷、加註記 FE 3d35229/BE 98d756bb
FR-104 CM-1823 flow_engine+flow_control 114 檔重做歸屬判定,取代舊設計稿 aac31fb1
FR-105 第 1 棒 CM-1839 module_frame 81 檔分類 84b8426a
FR-105 第 2 棒 CM-1840 oscal 163 檔分類 d6f066e6
FR-105 第 3 棒 CM-1841 兩模組清理:不可達端點/死序列化器/FE 殘骸,Excel 欄位契約去重 BE 22c65991/7b017144/b1a80773,FE ea74482
FR-106 未開卡 SSP 對應層反轉設計稿——決策者裁先不動,留作紀錄 d4953579

另:三棒各自跑過 render_index.py 但都漏 add index.html,首腦一次收進(99f35b90)。 jedi-iam 發 1.2.0(套件 3cc966f)、主專案 pin 升到 1.2.0、path dependency 還原;三 repo 皆已 push。 主專案對外 route 從 195 減到 189。CM-1837(Excel 匯入三欄未解析)作廢——決策者重走流程證實正常。

關鍵決策(決策者 2026-09-16 裁)

  1. workflow_template_snapshot_service 歸稽核套件——四個月只有一個用戶且用途是稽核輪次凍結;將來有第二個 caller 再抽上引擎(FR-104 待裁①)。
  2. FR-104 D-7 原裁示(review_service 算通用)正式撤銷,以歸稽核為準(FR-104 待裁②)。
  3. 六支殼的空值哨兵記債不動,只補檔頭陷阱註解(已做)。
  4. module_frame 與 oscal 依賴方向維持現狀不反轉;FR-106 反轉稿留存暫不動——「與主專案牽連太多,後續釐清再處理,紀錄留著」。
  5. 三支殼與業務混住的千行大檔不拆。
  6. Excel 欄位契約去重落點在 module_frame 側(CM-1841 已做)。

推翻了什麼

  1. 開卡時卡上寫 FR-101,撞號改 FR-104。 CM-1823 卡片標題與 URL 仍帶 FR-101,文件一律以 FR-104 為準。
  2. 首腦初判 workflow_template_snapshot_service 可立即搬,被 runner 推翻。 依賴面只有兩行 jedi_flow_engine import,但唯一業務消費者是稽核套件,改列待裁(後由決策者裁歸稽核)。
  3. FR-104 runner 判三支「純讀」可搬 readmodel,首腦驗收時推翻兩支。 flow_control_project_repo_impl.py:698-715 的 soft_delete_project 用 ORM session.flush() 寫入、task_execution_query.py:136 有 UPDATE compliance.workflow_executions——runner 的「零寫入」是用 SQL 關鍵字 grep 得出,ORM 寫入是盲區。只有 job_export_query.py 真純讀。 另:CM-1840 runner 判資源庫「發布」端點不可收(公版治理唯一途徑),首腦驗收推翻——公版建立時就已 enable=1,發布端點無人走。

教訓

教訓 6|grep SQL 關鍵字判「零寫入」會漏 ORM flush。 判一支檔讀寫,除了 INSERT/UPDATE/DELETE 字串,還要查 session.add/merge/flush/delete(obj)。兩種寫法在同一個 repo 裡並存,只查一種就是半份真相。

教訓 7|產生物要寫進卡的 add 清單。 三棒 runner 都照卡跑了 render_index.py,也都漏 add index.html——因為卡上的「要 add 的檔」只列了 md 與該站的 html,沒列總入口。凡是腳本產出的檔,開卡時就把完整產物清單寫進去,runner 不必猜。

環境異動紀錄

無。全程只動 DEV 之外零環境異動;DB 無 migration、無 seed。