FR-067 檢測多 Agent 分派 — LOG(append-only)

只追加、不改寫既有 block。 每棒一個 block:commits/決策/教訓/推翻了什麼。 現況看同目錄 FR-067-STATE.md


§1

第一棒 — FR-067.1 地基(2026-08-23)

:子需求 CM-1351|T-1.1 CM-1357|T-1.2 CM-1358 commits9f83973b feat(FR-067.1): Agent 清單補能力與在線狀態+修好自動挑機 改動:13 檔(BE code 8+test 4+spec 1),未動 DB、未碰 STG/POC

決策

D-1.a 新增 get_dispatchable_agents(),而非改掉既有 get_agents_with_capability() 派工要濾離線、清單不能濾離線——同一支方法答不了兩個問題。若直接把在線條件加進既有 方法,清單頁會把離線機藏起來,使用者找不到機器又看不出原因(正是 memory feedback_verify_data_source_before_hiding_empty_display 講的失敗模式)。故拆兩支、 docstring 互相交代分工。既有方法保留原語意,零回歸。

D-1.b 修在 domain service,不在兩個 caller 各修一次 盤點發現 _pick_agenttest_connection 早已共用 get_agents_with_capability (FR-056.5 就抽好了)。卡片文字看起來像要改兩處,實際只需換掉共用的那一支,兩邊 自動對齊——這正是 D9③「測通的就是會執行的」最省的達成方式。沒有另寫第二套挑機邏輯。

D-1.c 排序鍵選 id uid 是隨機 UUID、name 使用者可改,兩者都不穩定;id 單調不變。穩定性有反向 斷言守(輸入順序反轉,輸出不變)。

D-1.d is_onlineutc_now 搬到 common/util/agent_auth/heartbeat.py domain 層要做在線判定,但原本這兩支住在 app 層,domain 不得反向依賴 app(DDD 層級 規範)。搬到 common 並在原處 re-export,既有 import(含 3 支測試檔)零改動。

D-1.e 在線欄位命名 is_online(清單)vs online(管理頁)並存 管理頁 RemoteAgentDetailResponse.online 是 FE RemoteAgentManage.vue 已在用的既有 契約,動它要連 FE 一起改且不屬本卡範圍;新欄位依 design.md §5.6 契約命名 is_online。 兩者語意與門檻相同,已在 serializer docstring 交代,避免後人以為是不一致。

教訓

① 「兩處各寫一套」的卡,先查是不是早就共用了 卡片描述兩個檔案座標,容易直覺開兩處修。實際 grep 後發現 FR-056.5 已收斂到 domain service,只需換一支。先 grep 行為再動手省掉一次重複造輪子(CLAUDE.md「新增 method 前先找既有能力」鐵則的正面案例)。

② 環境設定值可能與設計文件舉例不同,別把文件數字寫死 design.md 通篇寫「900s=300×3」,但 DEV .envAGENT_HEARTBEAT_INTERVAL_SEC=60 → 實際門檻 180s。卡片要求「沿用既有常數不自創」正是為此。驗收時若拿 900 去對會誤判。

③ 同目錄可能有平行 session,commit 前必須逐檔認領 本棒 commit 前 git status 出現 27 個我沒動過的檔(FR-067.2 的 group/assignment 表)。 用 ls -lT 比對修改時間發現是當下正在寫入的平行 session 產物。若當時用了 git add -A,會把對方未完成的半成品掃進我的 commit。判定方法:比對檔案 mtime 與自己的作業時間窗、隔一段時間再看 git status 行數是否還在變。已寫進 STATE 警示。

④ 改了共用 canonical,要順手改掉 mock 它的測試——否則測試綠得沒有意義 四支測試檔 mock 的是舊方法名。不改的話 MagicMock 會對新方法名回傳 Mock 物件, 測試可能仍「綠」但驗的是空氣(其中 test_no_available_agent_raises_bad_request 原本就會因此變成假通過)。

沒做什麼(避免後人誤以為漏了)

  • 未寫新 unit test:依 CLAUDE.md 測試政策,碎片卡預設不寫;卡上未明示要求。 驗證改走「跑既有相關測試(460 passed)+實跑 DEV API +反向/邊界斷言腳本」。
  • 未動 FE:任務設置的 Agent 下拉屬 FR-067.3 範圍,本棒只交付 BE 讀取面。
  • 管理頁仍不顯示 capabilities:屬 UI 範圍,SPEC 坑 10 已更新為「API 讀得到、 本頁 UI 未呈現」,未擅自擴大到改管理頁。

§2

第二棒 — 2026-08-23 — runner(FR-067.2 資料層:CM-1359 T-2.1 migration + CM-1360 T-2.2 DDD 四層)

與第一棒(.1 地基)平行進行,非接續。兩棒各自 append 自己的 block。

commits(BE,皆未 push):9bcd02f5(T-2.1 migration)/69e6906d(T-2.2 DDD 四層+彙總函式,28 檔) Notion: CM-1359/CM-1360 皆回寫「修正待驗證」+白話已完成修正段+commit hash;子需求卡 CM-1352 維持 In progress 待驗收。

做了什麼

  • T-2.1scripts/sql/2026-08-23-fr067-2-detection-multi-agent-tables.sql——兩張新表 +detection_executionsgroup_uidassignment_uid+補三支索引(缺口 11 還債)。 只套 DEVschema_migrationsmanifest.tsv 皆已登記,不做 backfill(D8)
  • T-2.2:兩張新表 DDD 四層全套+DI wiring;detection_executions 側同步擴欄並加 list_by_grouplist_latest_per_assignment;彙總計算獨立成模組級函式。
  • 驗證:30 個新單元測試全綠;彙總函式三輪突變測試;鄰近既有 44 測試不壞; DEV 原生 SQL 實跑(BEGIN…ROLLBACK)驗 CRUD/DISTINCT ON/唯一約束/軟刪加回/列鎖,無殘留; ddd-compliance-reviewer 掃過 0 findings。

決策(含被排除的)

  • 彙總函式擺哪:選模組級函式detection_group_status.py)而非 group domain service 私有方法。理由:收口、單組重跑、取消、列表呈現四個入口都要算同一件事,私有方法會誘使 各入口複製一份成第二套真相。group domain service 保留 compute_status() 薄轉呼叫當 domain 入口,不重寫邏輯。
  • reopen() 另立一支而不走 update(entity)repo.update() 是「value is not None 才寫」語意,closed_at=None 會被靜默 skip,舊收口時間清不掉——這是幽靈覆寫防護的反面 代價。故 repo 內另開 reopen() 直接操作 model 欄位。被排除:entity 加 clear_closed_at 布林旗標(把 repo 寫入語意漏進 entity,更難懂)。
  • assignment 唯一約束用 partial unique indexWHERE is_delete = FALSE):table-level UNIQUE 會讓軟刪列繼續佔 slot,「移除某台再加回同一台」會撞 UniqueViolation。這個坑 uq_jedt_job_active 當年踩過,其 migration 註解留有前例記載,直接沿用。
  • 兩張新表 RLS 抄哪一版:抄 2026-07-26-fr056-2-jedt-fix-rls-delimiter.sql(修正後: slash-delimiter + is_super_admin = 't'),不是 job_execution_detection_tools 原始建表 migration(逗號 delimiter + 'true',該版是壞的,當年 FE 手測才炸出來)。 新表照抄原始版就會複製同一個 bug。
  • 未知 status 的歸類compute_group_status 把任何非 succeededfailedcancelled 的值都當未終態(回 running),而不是丟進 partial_failed。理由:保守——寧可不收口讓 人工看,也不要誤判成 succeeded 而觸發 D5 auto 自動完成任務。

教訓

  • TenantScopedMixinModel 讓「獨立 script import model 做 DB smoke」行不通:該 mixin 只在 ENABLE_MULTI_TENANT=true 時才產出 tenant_idorg_unit_id,且帶 ForeignKey("tenants.id")——FK 目標表要整個 model registry 載入才解析得到,否則炸 NoReferencedTableError。資料層實跑驗證改用原生 SQL 打 DEV(包在 BEGIN…ROLLBACK 內) 反而更快更乾淨,且驗的正是與 repo 相同的 SQL 形狀(DISTINCT ON 那段是直接把 repo 產出的 SQL 抄過去跑)。
  • 平行 session 下差點覆蓋對方的交接檔:本棒寫 STATE/LOG 時,第一棒的同名檔案在 幾分鐘前才 commit(75a0a3f5),本棒是用 cat > 整檔覆寫——working tree 內對方版本 被蓋掉。發現後以 git checkout <commit> -- <path> 復原對方版本,再把自己的內容以 Edit 併入(STATE 併節、LOG append block)。教訓:交接雙檔在平行作業下要當共享檔對待, 一律先 git log --oneline -- <path> 看有無新 commit,再用 Edit 併入,不要 cat > 整檔寫。

推翻了什麼

無。D1–D9 皆照 design.md 實作,無偏離。

沒做什麼(避免後人誤以為漏了)

  • 未動 orchestrationstart_execution 展開、收口、重跑、取消):那是 .4 範圍, 本棒只交付資料層能力。detection_orchestration_service.py 一行未改。
  • 未動 API/serializer/FE:綁定設置的 agent_assignments 欄位屬 .3。
  • 未套 STG/POC:開發鐵律,等決策者放行。
  • 未做存量 backfill:D8 定案(開發驗證階段資料之後重置)。既有 detection_executionsgroup_uid 全為 NULL=legacy 筆,讀取端視為單筆獨立顯示。

§3

第四棒 — 2026-08-23 — runner(FR-067.4 執行展開與收口:CM-1363 T-4.1 / CM-1364 T-4.2 / CM-1365 T-4.3)

與 .3(綁定設置)平行進行。本棒範圍限 detection_orchestration_service.pyremote_agent_domain_service.py / detection_tools route+DI+error code / task_execution_query.py未碰 job_service.py 的 binding 寫入與 FE

commit(BE,未 push):e6283532(11 檔:BE code 7+test 4) Notion:CM-1363/CM-1364/CM-1365 皆回寫「修正待驗證」+白話段+commit hash; 子需求卡 CM-1354 同步「修正待驗證」。 DB:無異動(表在 .2 就建好)。

做了什麼

  • T-4.1start_execution 展開成 group+N 筆 execution+N 張工單;D3 逐台驗證 收集全部不合格一次 400;無 assignment 走 fallback 隱含單組(仍建 group); running group 守門與自動派工冪等上移。
  • T-4.2_close_group_if_terminal() 列鎖收口——彙總落地/auto gate(只認 succeeded)/通知 hook 點;complete_job 冪等防護;409 地雷解除。
  • T-4.3:三支新端點(單組重跑/單組取消/整組取消 best-effort)+既有 job 層 取消端點改成委派整組。
  • 驗證:新增 31 個單元測試全綠;核心斷言過六輪突變測試(放寬 auto gate/ 拿掉冪等/關掉 D3 擋下/取消改非 best-effort/拿掉列鎖/拿掉 job 層委派——每項 都有測試轉紅);相關 242 個既有測試全綠;BE 實際重啟成功(DI wiring 過)、三支 新端點打得到(401=有守門、bogus 路徑 404 證明比對正確);改過的冪等 SQL 在 DEV 實跑通過(唯讀);ddd-compliance-reviewer 掃過 PASS(唯一 Warning 是 FE error-code 未同步,屬 .5 範圍)。

決策(含被排除的)

  • D3 驗證另開 diagnose_dispatchability() 而非複用 get_dispatchable_agents(): 後者只答「有哪些可派」,答不出「這台為什麼不可派」。D3 要求逐台列原因(避免使用者 修一台再撞下一台),故條件逐項檢查而非一次過濾掉。被排除:用集合差集推斷原因 ——推不出「同時 disabled 又 offline」這種多重原因,訊息會失真。代價是兩支的資格 條件要同步維護,已在雙方 docstring 互相交代。
  • fallback 隱含單組也建 group:本可讓「沒設 assignment」走完全不建 group 的舊路徑, 但那會讓收口有兩套(有 group 的走新邏輯、沒有的走舊邏輯),auto 完成與通知都要 分岔判斷。統一建 group 後,assignment_uid=NULL 自成一條分組線(.2 已實跑確認 PG 的 DISTINCT ON 對 NULL 的語意正是要的),收口只有一套。
  • 通知本體不動、只留 hook:卡片明示彙總通知是 T-5.2。若在本棒順手改成收口一封, 會與 .5 撞範圍且 _notify_scan_result 的九項內容測試(TestScanNotificationContent 共 7 例)要一起改造。現階段仍是單筆各發一封(行為與 .4 前相同),收口只寫 log。
  • _auto_dispatch_detection_scans 冪等條件「加」而非「換」:卡片寫「改『該任務無 任何 group』」,但直接換掉會讓 D8 不 backfill 的舊資料(有 execution、無 group)被 判成「沒派過」而重派一輪。改成兩條並存(無 execution 無 group)。這是對 卡片指令的偏離,理由是照做會製造重複派工。
  • 既有 job 層取消端點改成委派整組(卡片未列):群組化後不改的話,FE 抽屜的取消鈕 只會取消 N 台裡的第一台,其餘幾台繼續掃、群組永遠 running 擋住下一輪執行——這是 群組化引入的迴歸,不是既有 bug,屬本棒責任範圍。
  • force 重派仍是「取消失敗即 409」,不採 best-effort:與 T-4.3 整組取消刻意不同。 那裡使用者意圖就是收掉這一輪、能收多少算多少;這裡是「確保沒有殘留執行中工作」的 前置,沒收乾淨就不該派新的(否則掃描器上會疊出並行工作)。

教訓

  • 「response 形狀改了」要主動 grep FE 有沒有讀start_execution{agent_task_uid, execution_uid} 改成群組形狀。grep JobExecutionDrawer.vue 確認 FE 只看呼叫成功與否、不讀 body,故無破壞。沒 grep 就改的話,破壞會等到 .5 串 FE 才浮現,屆時查起來像是 .5 自己弄壞的。
  • 群組化會「引入」新的迴歸,不只是「新增功能」:既有 job 層取消端點原本正確, 正是因為引入群組才變成半殘(只取消第一筆)。做結構性改動時要問「哪些原本對的東西 會因為這個改動變成錯的」,而不只是「我要新增什麼」。
  • 測試綠一次過時反而要更用力驗:31 個測試第一次跑就全綠,照突變測試紀律故意寫錯 六處確認每處都有測試轉紅。其中「拿掉列鎖」一輪第一次執行時因為 shell heredoc 的 字串替換沒生效(replace 沒命中),出現「看起來沒轉紅」的假象——改用檔案比對 確認替換真的發生後才看到正確結果。突變測試本身也要驗突變真的注入了。
  • DEV agent 全部離線,展開路徑的成功分支在 DEV 驗不了:D3 會正確擋下(那是對的 行為),但也意味著「兩組成功展開」這個主場景無法在 DEV 手測,只有單元測試守著。 已寫進 STATE 環境注意,避免驗收者以為是 bug。

推翻了什麼

  • 卡片 T-4.1 的「_auto_dispatch_detection_scans 冪等條件改『該任務無任何 group』」 ——改成「加一條」而非「換掉」(理由見上方決策段)。這是本棒唯一對派工單指令的偏離。

沒做什麼(避免後人誤以為漏了)

  • 未改 receive_result 不吞例外的既有行為(卡片明示不必改)。
  • 未動彙總通知本體_notify_scan_result 仍每筆一封)——T-5.2 範圍,收口路徑 ③ 已留 hook 點。
  • 未動 FE、未動 job_service.py binding 寫入(.3 平行 session 的範圍)。
  • 未同步 FE error-code.json(5 個新碼)——.4 範圍不含 FE,已寫進 STATE 下一步。
  • 未跑全量 pytest(依測試政策),只跑相關檔+新檔。
  • 未套 STG/POC(本棒根本無 DB 異動)。

§4

第三棒 — 2026-08-24 — runner(FR-067.3 綁定設置:CM-1361 T-3.1 BE diff-sync+enrich + CM-1362 T-3.2 FE Agent 分派 UI)

與第四棒(.4 執行展開與收口)平行進行。接手時 .4 已收工並 commit(e628353210d6b43b), 故本棒的交接雙檔改動是併入 .4 的版本(Edit 就地併節,未整檔覆寫)。

commits(皆未 push):BE f6e27786(11 檔)/FE 996ed22(3 檔)。DB 無異動(表是 .2 建好的)。 Notion:CM-1361/CM-1362 皆回寫「修正待驗證」+白話已完成修正段+commit hash+執行紀錄; 子需求卡 CM-1353 維持 In progress 待驗收。

做了什麼

  • T-3.1DetectionToolBindingSchemaagent_assignments;寫入端 diff-sync(增/改/軟刪) 掛在 _replace_detection_tool_bindingupdate 與 create 兩分支;讀取端每列 enrich agent_nameagent_online(app service 層批次查,不在 infra JOIN);新增兩個 error code。
  • T-3.2ProjectPlanningView 檢測工具區新增「Agent 分派」列表(agent 下拉+掃描目標輸入+ 增刪列),空列表顯示「自動分派」說明;離線 agent 標示且不可選;已被別列選走的一併反灰; i18n 中英雙語補齊。沿用既有 tokens 與全域 class(hint-barform-label),未新增自訂樣式。
  • 驗證:DEV 實打 API 九情境全過(見 STATE 待驗收段)+綁定 create 分支另驗;control-tree 端點實跑確認回傳分派與 enrich 名稱;npm run build:DEV 通過;ddd-compliance-reviewer 0 findings。 依測試政策未寫新單元測試(卡上未要求)。

決策(含被排除的)

  • agent_assignmentsNone vs [] 分開語意None=不動既有、[]=清空。理由:規劃頁 有「只改任務名稱」的 PUT 路徑,若把未帶欄位當清空,改個名字就會把分派設定洗掉。這與同一 schema 內 tool_paramscompletion_mode 的 None-skip 慣例一致。被排除:一律當清空 (簡單但會誤刪)、一律當不動(則永遠無法清空回 fallback)。
  • 配對規則加上 agent_uid 當第二鍵(原本只用 uid):見下方教訓①。被排除:要求 FE 必須 帶 uid——契約上做得到,但「前端漏帶就 500」是脆弱設計,且 agent_uid 本來就是 DB 唯一約束的 欄位,用它配對才與唯一性語意同源。
  • 重複 agent 擋在 app service 層回 400,而非讓它撞 DB 唯一約束:撞約束會丟 500 且訊息是 psycopg 的英文約束名,使用者看不懂。擋在寫入前也避免部分列已 flush 才炸。
  • FE 一律送出 agent_assignments(含空陣列):因為空陣列是有意義的值(清空),省略才是 「不動」。若 FE 用「有值才送」的常見寫法,使用者永遠刪不掉最後一組。

教訓

  • ① 配對只看 uid 會撞唯一約束(實跑抓到,非推測):第一版 diff-sync 用「帶 uid=更新、 不帶=新增」,手測第 2、3 情境立刻 409——因為測試請求模擬 FE 沒帶 uid 的送法,同一台 agent 走進 create 分支撞 uq_jedta_binding_agent_active這正是「批次驗證」紀律的價值:九個 情境一次跑完,兩個坑在同一輪暴露,修完再驗一輪就收斂;逐項驗會變成兩輪。
  • ② repo 的 addupdate 都立即 flush,故 diff-sync 的操作順序有語意:「先增後刪」在 「把 A 台換成 B 台」時會撞約束(A 還沒讓位)。軟刪必須排在新增之前。這類「同一交易內 多筆寫入互相踩」的坑,光看 code 想不出來,是靠情境 4(整台換掉)跑出來的。
  • ③ 綁定的讀取路徑有兩條,規劃頁走的是不顯眼的那條get_joblist_jobsGrcJobRepoImpl._batch_fetch_job_tools,但規劃頁任務面板 prefill 實際走 control-tree 的 raw SQLssp_control_implementation_servicessp_control_implementation_query)。 只補前者的話,存檔當下畫面正常、重新整理設定就消失——使用者會回報「存不進去」,實際 DB 有資料。任何加在綁定上的新欄位都要同時補這條。發現方式是實作 FE 時追 mapAoJobs 的資料來源,不是靠 grep 猜。

推翻了什麼

無。D2/D8 皆照 design.md 實作;卡片指定的檔案座標與 §5.6 契約皆未偏離。

沒做什麼(避免後人誤以為漏了)

  • 未碰 detection_orchestration_service.pydetection_result_handler.py(.4 平行 session 範圍, 一行未改)。
  • 未同步 FE error-code.jsonGRC_400107GRC_400108)——與 .4 的 5 個一併留給 .5 串 FE 時補。
  • 未寫新單元測試(依測試政策,卡上未要求),驗證走實打 API + FE build。
  • FE 頁面實際操作未手測——FE dev server 依慣例由使用者啟動,手測步驟已列進 STATE 待驗收段。
  • 未套 STG/POC(本棒無 DB 異動)。
  • 未 push(等 user 明示)。

§5

第五棒 — 2026-08-24 — runner(FR-067.5 BE:CM-1366 T-5.1 執行紀錄 group 化 + CM-1367 T-5.2 彙總通知;FR-067.7 BE:CM-1373 T-7.1 排程)

commit(未 push):0dc088d9(44 檔,含 2 支新測試檔)。DB 有異動:只套 DEV2026-08-24-fr067-7-per-assignment-scheduling.sql,已入 schema_migrationsmanifest.tsv)。 Notion:CM-1366/CM-1367/CM-1373 皆回寫「修正待驗證」;子需求卡 CM-1355/CM-1372 維持待驗收。

三張卡合一個 commit:orchestration service 同時承載執行紀錄列表、收口通知與展開/取消鏈, 拆開 commit 會留下中間跑不起來的狀態。

做了什麼

  • T-5.1list_executions 改回兩層形狀(group → executions),每筆補 agent_uidagent_nameassignment_uidscan_targetsis_latest;legacy 筆與「群組列查無」 一律走「單筆自成一組」合成分支(is_legacy: true)。順修兩處既有 N+1(缺口 11): scan_params 與 report_file_uid 各補批次版,剝除邏輯抽成 module-level 純函式共用。
  • T-5.2:收口第 ③ 步接上 _notify_group_result(整組一封,每 assignment 一列: agent/目標/狀態/發現數/時間/失敗原因),三通道同步;單筆通知只留給 legacy 無 group 的筆_dispatch_notificationerror_message 參數改成 failed_tone 布林。
  • T-7.1:migration 三處加欄(assignment 子表/agent_tasksscheduled_atdetection_executions.statusscheduled 語意)+領單部分索引;領單查詢加時間過濾; 綁定 API 讀寫帶 scheduled_at;展開時快照進工單與 execution 狀態;取消排程組短路; 「立即開始」端點;ack 時 scheduled→running。
  • 驗證:322 例相關測試全綠(新增 22+12 例,順修 .3 遺留三支紅燈 fixture);六個關鍵 斷言過突變測試;DEV 實跑執行紀錄 API/排程寫入讀回清除/領單過濾/兩條讀取路徑格式一致; 彙總通知渲染肉眼確認(純文字+Email HTML 兩版);ddd-compliance-reviewer 0 findings。

決策(含被排除的)

  • 時區契約獨立成 common/util/scheduled_time.py(naive 視為 UTC)scheduled_at 落 TIMESTAMPTZ,但 jedi-common 的 db_mw.DATETIME_FIELDS 只涵蓋 created_at/updated_at,本欄 不會被自動轉時區。FE 若送不帶 offset 的本地時間,psycopg 會以 session TZ(三環境皆 Etc/UTC)解讀 → 設的凌晨兩點變成早上十點才開掃,且畫面完全正常(存什麼讀什麼)。 被排除:靠 DB session TZ(隱式、換環境就漂)、在 BE 轉成台北時間(跨時區使用者看到錯的 牆鐘時間)。
  • 領單查詢另開 list_dispatchable_for_agent,不改 list_for_agent_by_status:後者是 FR-058.7(D25)下載授權判定用的,要看 pending/dispatched/running 三態,不該被排程時間 過濾(已領走的單本來就過了時間,套上去 agent 取檔會被自己人擋掉)。兩支分家有測試守著。 被排除:在既有那支加參數——共用一支就得在每個 caller 判斷要不要過濾,是漏判的溫床。
  • execution 的 scheduled 狀態,group 層不另設 scheduled 態:group 彙總仍為 running (compute_group_status 視 scheduled 為未終態)。列表的「2 組:1 執行中、1 排程於…」文案 由 FE 依明細組成。被排除:group 加第五個狀態——會讓 D4 彙總表從四態變五態,而收口判定 只關心「終態了沒」,多一態是純負擔。
  • 取消短路是「新增一條分流」而非改既有語意:既有「可達才標記 cancelled」(best-effort 的 可達性要求)對已派出的單完全不變;只有「scheduled +工單仍 pending」才短路。判定條件兩個 都要看——只看 execution 狀態的話,排程時間到、agent 已領走但尚未回報的瞬間會誤短路, 掃描還在對方機器上跑卻被標成已取消。
  • 發現數空值顯示「—」而非 0:各 connector 的 summary 鍵不同(OpenSCAP 系 fail、Nmap 系 findings),取不到時填 0 會被讀成「掃了但什麼都沒發現」,與「這個工具不回報這個數字」是 完全不同的意思。
  • 通知明細壓成單一列的多行值,而非另立表格:三通道共用同一份資料,Telegram/Discord 是 純文字沒有表格可畫,強行雙結構會讓兩版必然漂移(這正是既有 list of tuple 設計的理由)。

教訓

  • _is_unclaimed_scheduled 用直接取屬性會炸既有測試(實跑抓到):測試 fixture 是薄的 SimpleNamespace,沒有 status 欄位。改 getattr(..., None) 時順手想清楚了保守方向該往 哪倒——判不出來就走「打 agent 取消」(多打一次頂多拿到「查無此任務」),而不是短路 (該打沒打=掃描還在跑卻標成已取消)。容錯的預設值本身是設計決策,不是隨手填。
  • ② control-tree 那條 raw SQL 直出 datetime 會變 RFC 格式Mon, 24 Aug 2026 18:00:00 GMT vs grc job 路徑的 ISO 2026-08-24T18:00:00+00:00——同一個欄位兩種格式,FE 得寫兩套解析。 這條路徑不經 marshmallow schema(本檔 reviewed_at 早有 .isoformat() 的既有慣例,只是 沒人在加新欄位時想到要跟)。加欄位到 control-tree 時,格式一致性要跟資料存在性一起驗。
  • ③ 突變測試抓到一個真漏網is_latest 與批次化的斷言都有牙齒,但「多筆 legacy 各自成組」 一開始沒鎖住——把分組鍵從 f"legacy:{e.uid}" 改成常數 "legacy"(=所有舊筆擠成一組) 測試照樣全綠。補了一條測試才抓住。「跑綠了」不等於「守住了」,突變測試是唯一的檢驗方式。
  • ④ 驗證時改到了 DEV 的真實資料(任務名稱):為驗「只改任務名不動分派」的語意,PUT 了 {"name": "排程測試任務"},把一個真任務改名了。從 api_logs.response 撈回原名 (GCB Scan use upload profile)並走 API 還原(不是 raw UPDATE——名稱會同步回寫 BPMN 模板 XML,直接改表會讓兩邊不一致)。教訓是驗證用的寫入要挑自己建的資料,或先記下原值。

推翻了什麼

無。D6/D10 皆照 design.md §5.6/§5.7/§5.9 實作;T-4.2 留的收口 hook 點 ③ 依約接上。

沒做什麼(避免後人誤以為漏了)

  • 未做 T-5.3/T-7.2(FE)——本棒是 BE 面,FE 抽屜 group 化與排程 chip UX 是同一棒的另一半, 待辦契約已寫進 STATE「FE 待辦契約」節。
  • 未同步 FE error-code.json——本棒新增 DETECTION_TOOLS_409015,連同 .3/.4 遺留的六個 共七個,一併列在 STATE 的 FE 待辦契約節。
  • 未套 STG/POC(開發期只動 DEV,等放行)。
  • 未 push(等 user 明示)。
  • 未跑全量 pytest(依測試政策,只跑改動模組的相關檔)。
  • manifest.tsv 有兩支既有未登記檔2026-08-18-cm1283-*2026-08-20-cm1317-*,非本棒 產生,commit 早於本棒)——check_migration_manifest.sh 因此仍 FAIL,本棒新增的那支已登記

§6

第六棒(.5 FE + .7 FE)— 2026-08-24

交付JobExecutionDrawer 群組卡片 + 排程 UX 三端 + 十支 error code i18n。 FE repo feature/FR-067cb458a6(群組卡片,CM-1368)/055d9c2(設定端排程 chip, CM-1374)/80e1c29(error code + App.vue 明細併陳)。未 push。BE repo 無程式異動 (僅本 STATE/LOG)。

T-7.2 的執行紀錄那一端(排程中徽章/立即開始/確認框)落在 cb458a6 裡——與 T-5.3 同一個 檔案同一段模板,硬拆成兩支 commit 反而讓任一支單獨看不成立。

決策

  • executions 從 ref 改成 computed(攤平 executionGroups,而不是把既有以「單筆」為 單位的邏輯全部改寫。sticky 源碼檔追溯、執行中判定這些都只關心「有哪些筆」,不關心分組; 留一個攤平視圖讓它們原封不動,改動面從整個檔縮到只有渲染層。
  • 徽章看明細組成,不看 group.status:BE 視 scheduled 為未終態,群組層在排程等待期間 仍是 running。但使用者的心智是「機器正在掃」vs「時間還沒到」,兩者同一個顏色會讓人 以為卡住。故 groupStatusKind() 在 group 為 running 時往下看明細:全 scheduled → 中性 「排程中」,混合(有一台真的在跑)→ 仍算「執行中」(已經有機器在動了),彙總列補講 「1 執行中、1 排程於 8/25 02:00」。
  • 當前列與歷史列共用同一段模板groupRenderRows() 合成單一清單,只差 history 旗標) ——兩者內容完全相同,只是暗一階且無動作鈕。分成兩個 v-for 會變成同一段七十行模板維護兩份。
  • ACTIVE_EXEC_STATUSES = ['running','scheduled'] 在 FE 也集中成一個常數,對齊 BE 的 _ACTIVE_EXECUTION_STATUSES。前一棒 LOG 已警告「別在各處另寫 == 'running' 字面值」, FE 同樣適用——漏掉 scheduled 會讓排程中的組取消鈕不出現。
  • scheduled_at 一律送出(含 null):它是「每次依送來的值覆寫」語意,與整包 agent_assignments 的「未帶欄位=不動」是兩個層級。移除 chip 要真的清掉排程。

教訓

  • ① 實跑真實資料抓到 BE 的 is_latest 缺陷——這是靠「跑一次真的 API」才看見的。 單看契約文件與模擬 payload,is_latest 的語意完全合理;但 DEV 既有 10 筆 legacy 跑下去, 9 筆變成「歷史」而在預設收合下消失。分組鍵 (group_uid, assignment_uid) 對 legacy 筆 全是 (NULL, NULL),卻又讓每筆各自成組——兩個決策各自合理,湊在一起就矛盾。 作法上的收穫:把 FE 的實際 helper 函式抽出來(esbuild 去型別)餵真實 API 回應跑, 比讀程式碼想像有效得多,成本也只有幾分鐘。
  • ② 補 i18n 反而會弄壞錯誤訊息DETECTION_TOOLS_400017 的 BE msg 尾端帶逐台原因, 而 t(code, {default: msg}) 有翻譯時整個蓋掉 default。交接文件已標「需保留 msg 內容」 卻沒說怎麼保留——照字面補翻譯就會把明細吞掉,只剩一句空泛的「有 Agent 無法派工」。 「補上翻譯」不是無害動作:對「訊息本身帶動態明細」的碼,補翻譯等於降低資訊量。
  • ③ 交接清單會漏:STATE 列七(實為八)支 error code,實打四支新端點驗路由時發現 DETECTION_EXECUTION_404002 是四顆新按鈕都會撞到的碼卻不在清單上。清單是前一棒的 記憶產物,端點實打才是真相——花兩分鐘打一輪 404 就補到了兩支。
  • ④ best-effort 的成功回應不等於成功:整組取消時部分 agent 打不到,BE 回 200 並把它們 放在 failed。照 200 報「已取消」是謊——那幾台的掃描還在對方機器上跑,只是本地紀錄被 標成失敗。凡是 best-effort 端點,FE 都要讀回應內容而不是只看 HTTP 狀態碼。

推翻了什麼

無。§5.9.4 UX 三端與 STATE「FE 待辦契約」皆照做。唯一與交接文件不符處是那節寫「七個 error code」(實為八支,另補兩支漏列),已在 STATE 就地更正。

補做:修掉 BE 的卡片分組鍵(b205e07a,同棒稍後,user 指示)

FE 發現的缺陷改在源頭修掉,不只靠 FE 保底。修法上的重點是「不要選一支鍵去改」—— _group_executions()(分卡)與 _latest_execution_uids()(判最新)本來就是同一個分組概念 的兩面,兩處各寫一份鍵才是病根;抽成 _execution_card_key() 共用,並在兩支 docstring 互相標註「必須同鍵」的義務,才是讓它不再漂移的作法。真群組行為零變化,僅 legacy 路徑受影響。

回歸測試斷言刻意寫成性質(「每張卡至少有一列 is_latest」)而非特定 uid 的期望值—— 分卡規則日後再變,這條仍然守得住。依 [[feedback_mutation_test_proves_assertion_has_teeth]] 做了突變測試:鍵改回舊版該測試立刻紅、還原後綠。

修後重啟 BE、以 DEV 真實資料重打 API 確認 10 張卡各有 1 列當前(修前 1 有 9 無),並回頭用 FE 實際 helper 跑修好的 payload,確認 FE 保底不會因此重複計算(10 列渲染 10 列、無重複)。

沒做什麼(避免後人誤以為漏了)

  • 未做任何 live 行為驗證——DEV agent 全離線,多台展開/重跑/取消/排程領單/立即開始 都只驗到 build 與邏輯層。逐項打折清單見 STATE「FE 已交付與未驗清單」表。此為 user 當次 裁示(不等 agent 上線,統一留給端到端驗收棒)。
  • 未手測頁面實際操作(FE dev server 依慣例由 user 啟動)。
  • 未 push(等 user 明示)。
  • FE 側未寫新測試(依測試政策,UI 卡不強制;FE 無對應 test file)——但BE 補修有寫test_every_card_has_at_least_one_current_row,屬核心共用邏輯的回歸,符合政策條件②)。
  • 未動 .6 收尾(e2e/SPEC 三頁)。

§7

協調者棒(中繼交接)— 2026-08-24

本棒角色:協調者——逐棒驗收 .1–.5+.7 全部六棒(全數通過)、陪跑端到端驗收、 拍板三個驗收回饋修項的修法。無程式 commit(僅本 STATE/LOG)。

進度快照

  • .1–.5+.7 六棒完成且協調者逐棒驗收通過,全部 commit 未 push
  • 剩:端到端驗收(進行中)+ 三修項開卡派 runner + .6 收尾棒(未派)。
  • DEV 環境重大變化:user 刪掉 123 舊 agent,改裝 0.2.28 交付包(systemd 原生形態, 非 compose)——設定檔 /etc/guidant-agent/agent.env、程式 /opt/guidant-agent/versions/0.2.28(current symlink)。已註冊成功: remote_agents id=17 ubuntu-lab-03https://192.168.50.123:8443[file_storage, detection_scan]、在線。user 正在跑單台端到端場景 (基本掃描/排程/立即開始/取消/失敗重跑)。

裝機踩坑(實錄)

enroll token 頁回的 endpoint 是 AGENT_CLOUD_ENDPOINT 原值 http://10.8.0.6:8000/api/1.0(10.8.0.6=user Mac 的 VPN IP,BE 跑本機),agent 端會 自己再補 /api/1.0 → 打成 /api/1.0/api/1.0/agents/register 404。當場修法=agent.env 的 CLOUD_ENDPOINT 拿掉路徑後重啟即註冊成功;根治留給修項 2。

三個驗收回饋修項(已拍板修法,待開卡派 runner,皆規模 S)

  1. ZAP/SonarQube 型工具分派列目標欄多餘(user 發現+協調者分析):hosts 鍵對 ZAP(只讀 target_url)/SonarQube(只讀 repo_url)無效,UI 卻照樣渲染目標欄。 修法:按 param_schema 是否含 hosts 切——hosts 型(openvas/openscap/inspec/gcb/nmap) 照現狀,單一目標型分派列只剩 agent 下拉(用 param_schema 判斷,不寫死工具清單); BE 展開時對非 hosts 型忽略 scan_targets 防禦。
  2. enroll token endpoint 顯示 bugagent_enroll_token_service.py:62 直接回含 /api/1.0 的原值,使用者照抄必 404。修法二擇一(下一棒定):BE 顯示時剝路徑, 或 agent 端 enroll 正規化容忍帶路徑。
  3. BE legacy is_latest 語意:legacy 筆(assignment_uid 全 null)被算成同一條 latest 線,10 筆只有 1 筆 true(FE 保底已擋住消失問題)。該修成 legacy 每筆自成一組 各自 true。影響低(D8 資料會重置)。

ZAP 項卡已開(卡號見 Notion FR-067 母案 CM-1350 下最新子卡);後兩項可併一張 「驗收回饋修正」卡或掛進 .6。

教訓

  • ① agent 交付包有兩種形態(systemd 原生/compose)——排錯前先 systemctl list-units 確認形態,別假設 compose。假設錯形態會去找不存在的容器與 compose 檔,全程撲空。
  • ② endpoint 類設定「含不含路徑」的契約要兩端對齊:token 頁顯示什麼、使用者就抄 什麼——顯示端回原值+接收端自動補路徑,兩個「各自合理」的行為湊在一起就是 404。
  • ③ FE 用真實資料實跑抓到 BE is_latest 語意問題——「單元測試綠」不等於 「真資料形狀對」。真實存量資料(legacy NULL 欄位)是契約文件照不到的角落。

推翻了什麼

無。前六棒交付與決策皆維持;b205e07a 修的卡片分組鍵與修項 3 的 is_latest 語意是 同一區塊的兩個獨立問題(前者已修、後者待開卡),不是推翻。

沒做什麼(避免後人誤以為漏了)

  • 未開修項 2/3 的卡(等下一棒協調者,見 STATE「下一步」)。
  • 未派 .6 收尾棒、未做全案收尾(母卡收 Done/SPEC/手冊/analysis 等 user 下令)。
  • 未 push(BE/FE 皆是,等 user 明示)。

§8

協調者棒(端到端驗收第一輪)— 2026-08-24

本棒角色:協調者——收 user 端到端驗收回饋、開六張卡、派四棒 runner(皆開 case 給新 session)、逐棒抽查、user 驗後收 Done。本體 commit 僅 STATE/LOG。

進度快照

  • 端到端驗收第一輪(雙 agent 雙網段)完成,六張回饋卡全清:
    • CM-1375(Done):分派列 UX 累積五波——①ZAP/SonarQube 不渲染目標欄(FE 9809d29 +BE c408814f)②掃描目標單一入口+Agent 分派上移(8633ed6)③預設備妥一組 (fe10773)④profile 純下拉(BE 5ec85bee)⑤重跑歷史收合 v-show 被 PrimeFlex !important 壓過(3a9a213)+排程小日曆 minDate(d10ab5a)。第⑤波實際收合行為 留待多列場景複驗。
    • CM-1377(Done):測試連線可指定 agent(BE 06e98031/FE 4081e58)—— agents[0] 固定取首台在多 agent 下與實際執行脫鉤。
    • CM-1378(Done):stderr 帶出(_exec_with_cancel 原「排空即丟」)+Rocky 檔名 推 rl(evidence-agent 6fdd541,bump 0.2.29 ae9f4c9)。runner 順抓上層彙整 「從頭截 300 字」會切掉尾端原因的連帶坑。0.2.29 尚未部署到兩台 agent。
    • CM-1379(Done):分派列覆寫 profile+同 agent 多列(BE 8c24e212/FE 83248dc +migration 拿 uq_jedta_binding_agent_active,DEV+188 stack 已套)。
    • CM-1380(Done):188 驗收 stack 重布最新版+補 migration。
  • 開而未派:CM-1376(re-enroll 指令,user 裁示 FR 測完再做)。
  • 188 環境:容器 agent(guidant-ai-agent)token 失效 401 循環,經 user 指示已 docker stop(unless-stopped,不自啟);在線 agent=id=9(60.166)+id=11(50.123)。

決策

  • CM-1379 判準:重複擋「(agent, scan_targets 內容) 完全相同」由 BE 驗證層做,不做 JSONB DB 唯一(鍵序正規化成本高於收益,重複屬輸入錯誤非完整性風險)。
  • content_path 也開放覆寫,收進階切換、已填自動展開(兩列外觀一樣掃不同基準是雷)。
  • 1378/1379 平行派(不同 repo 零交集);agent build 與 BE image build 不同主機 即可並行(派工 prompt 均帶 pgrep 前置檢查)。
  • OpenSCAP content 平台派發定調為正解方向(沿 FR-059 file 型派發擴到 openscap), 逐台手放僅過渡——50 台客戶場景逐台維運不成立。

教訓

  • ① 我在卡上寫的「BE 展開零改動」被 runner 推翻——profile 參照展開 (profile:<uid>_profile 凍結快照)原在合併,分派列覆寫後第二列會拿 A 基準檔跑 B 設定且被自家下載授權閘擋,靜默掃錯基準不報錯。runner 查證後改為 「先合併再展開」並加不變量測試。協調者拍板的「零改動」假設也要 runner 驗——寫卡時 該標「假設,實作前查證」。
  • ② OpenSCAP 的 content 生態比設計假設碎:D3「apt 裝得到」只對 RHEL 系成立—— Ubuntu 24.04 apt 倉 content 停在 0.1.71(無 2404)、26.04 只有上游 nightly、Rocky 官方檔名前綴是 rl 不是 os-release 的 rocky。一輪驗收四台目標三種情況。
  • ③ stderr 丟棄讓故障診斷成本爆炸:60.162 兩次失敗(未裝 oscap/content 檔不存在) UI 全是空訊息,都要上目標機手動重現才定位。「錯誤訊息帶 stderr」不是 nice-to-have。
  • ④ 檢測工具「測試連線」的單 agent 假設:FR-067.1 修的「測通的那台=實際執行的 那台」在多 agent 下需要「使用者指定哪台去測」才閉環——同類單 agent 假設值得在 .6 收尾時整體掃一輪。

推翻了什麼

  • 卡 CM-1379 的「BE 展開零改動」假設(見教訓①,runner 修正)。
  • STATE 原「三修項皆規模 S」——修項 1 實際長成 CM-1375 五波+CM-1379 M 級功能。

沒做什麼(避免後人誤以為漏了)

  • 未 push(BE/FE/evidence-agent 皆是,等 user 明示)。
  • agent 0.2.29 未部署到 60.166/50.123(build 完等 user 指示)。
  • 未開卡:enroll endpoint 顯示/legacy is_latest/content 平台派發。
  • 未派:CM-1376、.6 收尾棒。
  • 兩台 U26.04 目標機未配置(engine 可 apt、content 需 nightly)。

補記(同棒收尾前,2026-08-24 晚):Windows 無域憑證議題入列

user 提出:Linux 共用帳號模式在 Windows 只對「有 AD」環境成立;無域(workgroup) 機器同名帳號互不相干、逐台建帳+密碼同步不可維運,且有 UAC token filter 限制。 協調者分析與三層建議(短期手冊+前置腳本/中期 per-分派列憑證組覆寫復用 1379 地基/ 長期 PAM)已寫進 STATE 待辦第 5 項,下個 session 首先討論。此為分析記錄非定案。


§9

協調者棒(驗收續輪+.8 每列自足)— 2026-08-25

本棒角色:協調者——收續輪回饋開卡派工抽查、主導 FR-067.8 設計拍板與三棒落地、 FR-068 設計定案、環境救火(ZAP)。本體 commit 僅 docs(含一次誤掃事故,見教訓)。

進度快照

  • 驗收續輪四卡:CM-1376(re-enroll,0.2.30)/1381(文件一輪)/1382(transport per 列+摘要展開卡)/1383(IP CIDR/range,依欄位語意分流展開)——皆抽查過、 修正待驗證。部署 CM-1384(188+兩台 agent 0.2.30)/1394(含 .8 再重布)已 Done。
  • FR-067.8 每列自足模型(user 拍板:廢兩層覆寫改每列完整獨立設定;secret 跟列走; 零列 fallback 廢掉):design.md §5.10+CM-1385~1388 四卡,三棒(BE 列 params 完整化 5ca869e7 內+FE 整份 param_schema da33373+SonarQube upload per 列 fda3921b/ a4c5ab4)全完成抽查通過。文件二輪同步 CM-1393(a4f063bb)。
  • FR-068 系統 Log 轉發設計定案(D1–D6:syslog+GELF 雙協定/app log+稽核事件 兩流/系統層預留租戶欄/Queue 旁路/遮罩預留/熱生效)+四卡 CM-1389~1392 就緒, user 裁示等 FR-067 收完再派
  • 在飛 CM-1395/1396(顯示回饋);CM-1397(重跑放寬)已開卡等 1396 收。

決策(user 拍板)

  • push 授權放寬(僅限本 arc):協調者抽查通過即可 push(runner 或協調者皆可), main/STG/POC 照舊等令。memory feedback_fr067_push_delegated_after_review
  • 不進版:v1.15.0 已於 08-19 發過(FR-063/064/065);FR-067 掛 1.15 滾動演進, v1.16.0 等 PM 確認(屆時含 FR-066 27 commits)。CM-1394 原進版指令作廢。
  • .8 設計:每列自足+複製列+新列帶第一列預設值;「重用靠 profile 庫引用」對齊 業界(policy/scanner/job 三分);不做跨任務參數組庫(反悔條件:客戶抱怨重填)。
  • 後移:憑證多組化(含 Windows 無域)/掃描設定下放指派人員(等 PM)/ZAP 排隊。

教訓

  • ① 協調者 commit 掃走平行 runner 的 staged 檔5ca869e7):顯式 add 自己的檔 擋不住「別人已 stage 的一起進 commit」——同目錄有 runner 時,commit 前必看 git status 的 staged 區,有別人的東西就停。歸屬靠 Notion 卡標注收拾,不 rebase (force push 會弄壞部署 runner 的 checkout)。
  • ② 協調者的技術斷言連續兩次被 runner 實查推翻:1383 卡寫「Nmap hosts 透傳」 實為執行主機要展開(照卡做整段變一筆連線失敗);1394 卡寫「bump 1.15.0」實則已 發過(照卡做會覆寫凍結快照)。開卡時的環境/版本斷言要先查現況,或明標 「假設,實作前查證」——runner 的煞車(停下回報而非硬做)是這兩次的救命機制。
  • ③ 省 context 派 sonnet subagent 又陣亡一次(CM-1381 文件棒 prompt too long) ——[[feedback_subagent_use_1m_context_model]] 再驗證;文件同步棒材料量必超小 model。
  • ④ 派工 prompt 重工(連四卡把卡片內容重抄進 prompt):user 糾正後回歸薄 prompt =卡號+讀序+紀律;任務知識寫卡不寫 prompt。skill Step 5 已補紅字。
  • ⑤ ZAP 拿去掃 SPA 會自噬:ajaxSpider 爬 Next.js 站灌爆佇列,PassiveScan 掃一個 CSS 要 30 秒、API 逾時、整台 load 30 連 OpenVAS 陪葬。已重啟+上 6g/2cpu 容器限制 (docker update,持久)。ajaxSpider 全域互斥=同 daemon 掃描必序列,並發直接 失敗不排隊。8091 是 proxy 口——健檢要用 -x proxy 姿勢打,直接 GET 永遠 refused。
  • ⑥ runner 鐵律生效前的空窗:T-8.2 做完停在 working tree 沒 commit 沒回卡—— 「做完就 commit+回寫」入 CLAUDE.md 後(cb9601a7)後續棒未再發生。

推翻了什麼

  • CM-1394 卡的進版指令(協調者自己開錯,runner 查證推翻,已改純部署卡)。
  • 1383 卡的 Nmap 分流假設(runner 改依欄位語意分流)。
  • T-8.1 的「內部欄位任務層永遠贏」規則被 T-8.3 改為「列上有就列上贏」(已記 design.md §5.10,防後人改回去)。

沒做什麼(避免後人誤以為漏了)

  • CM-1395/1396 未收(在飛);1397 未派(等 1396)。
  • 571b5b9e(1395 的 BE 半)未抽查未 push。
  • Windows 無域憑證討論(交接指定首議項)整棒沒開議——user 裁示「憑證往後放, 先共用帳號模式」,短中期方案材料仍在待做第 5 項。
  • .6 收尾棒未派;FR-068 未派(user 裁示排 FR-067 後);全案收尾未動。
  • working tree 留兩個未 commit 檔:STATE(本次交接改)+big-feature-workflow skill (薄 prompt 紅字)——隨本交接 commit。

§10

協調者第四棒(v1.16.0 定版上 POC+安裝包 P0)— 2026-08-26 ~ 08-27

本棒角色:協調者——收 1395/96/97 抽查 push、驗收 11 卡收 Done、主導 v1.16.0 進版全鏈(定版→出包→189 全新安裝)、.6 收尾棒派工、安裝包 P0 事故止血與開卡。

進度快照

  • 1395/96/97 收官:三卡批次抽查(開檔驗鎖序/theme token/突變測試紀錄)全過, push 後 user 手測收 Done。1397 併發鎖序(lock_for_close 在建紀錄前)是重點驗項。
  • v1.16.0 定版:CM-1398 進版三件套(RN/bump/快照+manifest 補登 cm1283/cm1317 兩支)→ user merge main → tag 三 repo(BE/FE v1.16.0、agent v0.2.30)推遠端。 發版 gate user 明示豁免。順帶補推 v1.15.0 tag(上次漏推);v1.10~1.14 遠端原本 就有(先前「整段空白」是本地視角誤判)。
  • 出包+上 POC:CM-1399 出包(bundle 891M 烙印 6d4ea5ee/agent onepack 1.3G 含 seaweedfs/Harbor 六 tag digest 驗過);CM-1400 189 主產品 user 親裝、CM-1401 189 LC 更新 user 親做(協調者事後唯讀實機驗證後補記收卡)。
  • .6 收尾棒:CM-1402 文件半完成(9ae28f68 在 main 未 push);CM-1403 E2E 半 runner 額度中斷留半成品。
  • 🔴 安裝包 P0(CM-1404 開卡未派):189 裝出來缺 8 支 migration,agent 心跳 500→capabilities 寫不進→檢測功能全殘。已止血(8 支補套+BE 重啟)。

決策(user 拍板)

  • v1.16.0 範圍=FR-066+FR-067(FR-067 branch 已含 FR-066 全部,merge 一次帶齊); FR-068 不進。gate 豁免。簽章照 188 前例用現有鑰(--skip-prod-key-check)。
  • 189 全新安裝不遷舊資料(舊庫遷移涉上傳檔案搬家+license 重簽,不值);舊 postgres 25432 原地封存續跑(license_center_poc 仍用它)。
  • 189 LC 更新到 main(原考慮「共用 188 LC 的照」被 user 否決——LC 太久沒同步, 要更新)。
  • PROD LC:正式出版時找一台設備架,現階段不動(懸最久待裁項正式結案)。
  • 後續優化池六件全數定調不開卡(enroll 顯示/legacy is_latest/content 派發/ Windows 憑證/ZAP 排隊/設定下放)。
  • 1402 branch 裁定:user merge 後停在 main,main 即 current branch,docs commit 合規;main push 仍留 user。

教訓

  • ① 出包驗證驗了烙印沒驗 schema 到 head:CM-1399 驗 bundle 烙印 commit、Harbor digest、tar 內容——全過,但 init image 的 02-schema/99-stamp 是 8/18 前 generate 的 stale 產物,裝出來 DB 缺 8 支 migration。「產物 generate 後 commit 進 repo」 的機制天生會漂移,build 無 gate 抓不到。修法(gate+裝機驗 schema)在 CM-1404。
  • ② 補套 migration 後 BE 必須重啟:psycopg cached plan 讓補完欄位後第一次心跳 仍炸 UndefinedColumn,症狀與「沒套進去」一模一樣——差點誤判補套失敗。
  • ③ 查 DB 先確認查的是哪套 PG:188 有老 25432 與 docker guidant-db 兩套, 一開始對老 STG 庫算出「缺 12 支」的過期清單,user 糾正後才對到 docker stack。 「安裝包形態的環境,DB 實況以容器內為準」。
  • ④ 檢測工具測試連線 agent 下拉的 >1 顯示條件會讓「單台/零台租戶」以為功能 不存在(189 心跳壞掉時 user 即誤判「FE 沒包到」)——設計本身合理,但排錯時 這是第一個要想到的分支。
  • ⑤ zsh 的 for pair in "a b" 不自動斷詞——批次驗證腳本靜默做錯事(七個 tag 全對到同一 commit),改用 ${pair%%:*} 拆。

推翻了什麼

  • 「STG 缺 12 支 migration」的第三棒遺留清單(對老 25432 STG 庫算的,docker stack 實際已全套;該清單對未來無意義)。
  • 「v1.10~1.15 遠端 tag 整段空白」的協調者誤判(僅 v1.15.0 真漏推)。
  • 本地 v1.12.0 tag 曾誤打在 6b8023be,與遠端 bed69767 不符,已對齊遠端。

沒做什麼(避免後人誤以為漏了)

  • CM-1404 未派(P0,接手首件事);CM-1403 半成品未收;CM-1402 的 main push 未做 (等 user)。
  • 189 使用面未動:.121 agent 服務 inactive、license 未簽新照、租戶未建。
  • FR-067 全案收尾未動(母卡/子需求卡/SUMMARY/手冊/memory);FR-068 未派。
  • 188 基線庫(25432 guidant_ai)是否含 cm1283/t52 未查(CM-1404 卡上待查點)。