FR-172 · 設計文件 · 2026-11-01 定稿
決策者 2026-11-01 裁 D1~D12 後的定案;拆卡表由 handoff/cards.py 產,19 張 10 批;P3/P4 等 FR-171 批 3
🔴 這份是設計決策,不是現況。 內文停在 2026-11-01 定稿當時,現況看 FINAL-SPEC.md(結案時寫)。本案前提:出貨前無客戶,不做舊客戶過渡、不補登 1.4.8、更新站 product 必填;Agent 不連外網,包只從主系統拿、離線靠主控台上傳;出包/CI/簽章金庫屬 FR-173,本案只定包長什麼樣、更新站怎麼存、主系統與 Agent 怎麼用。
📑 本頁是 v1.0(定稿)。 190 實測後有設計異動(推翻 D9 兩步改一步,補定維護模式、同版重套等六條),見 設計 v1.1。本頁內容保留定稿原樣,不回頭改;現況看 FINAL-SPEC.md。
整案一句話:Agent 的「貨」(安裝包、版本線、下載、推送畫面)從主系統身上拆下來自己走;「線」(叫升級、回報進度)用 FR-171 的通道。
| 階段 | 做什麼 | 卡 | 完成怎麼判定(決策者檢查法) |
|---|---|---|---|
| P1 出貨分家 | 更新站多 Agent 版本線;Agent 的章另一型別;Agent 包帶清單與公鑰;主系統包不帶 agent/ |
U-1 U-2 A-0 B-0 | 更新站後台有「主系統」「Agent」兩分頁;主系統包解開沒有 agent/;Agent 包有 release.json 與 release-keys/ |
| P2 主系統 Agent 區 | 自己查 Agent 新版、預先下載、相容兩向、推選定的台(雛形①②⑤) | H-1 H-2 H-3 H-4 H-5 | 主系統不升版,Agent 區按「立即檢查」看到 Agent 新版、按「預先下載」、按「更新選取的台」 |
| P3 進度即時 | Agent 每步當下送事件;八格跟著走;失敗看得到主機原因與全量 log(雛形③④) | E-3 E-4 E-5 | 推一台 3 秒內八格開始走;塞滿磁碟推一次「看原因」出現 no space left on device;「升級紀錄」下載到 journal |
| P4 主控台更新頁 | 看版本、請主系統更新這一台、離線上傳(雛形⑥~⑨) | G-1 G-2 G-3 | 登入 Agent 主控台按更新,主系統更新中心同時出現這台進度;拔掉主系統上傳離線包,Agent 自己升上去 |
| P5 收尾與併修 | 待辦 58;189 Demo 的 CSP 與 CINC 報告;結案出 1.28.0/Agent 1.5.0 | S-1 S-2 S-3 S-4 | 189 重裝新包後上傳型掃描不被 CSP 擋、CINC 報告看得到 |
依賴一句:P1、P2、S-1~S-3 不等 FR-171;P3、P4 的 Stage 從 10 起算,等 FR-171 批 3(A-1 A-2 E-1 合回)。
| # | 題目 | 定案 | 被排除的 | 理由 |
|---|---|---|---|---|
| 已裁 | Agent 連更新站 | 不;包只從主系統拿、離線靠主控台上傳 | Agent 直連 | 客戶內網掃描機不出網是賣點也是資安前提 |
| 已裁 | 主控台「發動更新」 | 主系統建一張只含這台的單並推送 | 跳轉連結 | 主控台頁自足 |
| D1 | 更新站存法 | product_releases 加 product 欄,主鍵 (product, version),Agent 列多 min_host_version;product 必填不預設 |
另開表 | 另開表讓發布、撤回、保留、下載紀錄每支寫兩份 |
| D2 | Agent 的章 | payload_type=agent_release_manifest,同一把章 |
同型別加欄 | 兩種型別各驗各的,拿 Agent 清單餵主系統第一道門就拒 |
| D3 | 相容兩向 | 只擋「Agent 新版 min_host_version > 主系統現行版」(灰鈕+原因);主系統新版 min_agent_version > 某台只標「必須更新」不擋主系統升級 |
雙向都擋 | Agent 比主系統新是允許的 |
| D4 | Agent CI | 屬 FR-173 | — | — |
| D5 | 離線上傳驗章 | Agent 包隨包帶 release-keys/(與主系統同一批);驗章永遠用現行版的公鑰,不用上傳包裡的;換版後新包的公鑰才生效;相容檢查離線時只提示不擋;上傳需 update 權限並寫稽核 |
註冊時主系統傳公鑰、Agent 專屬章 | 自己驗自己是洞;離線場景人在現場 |
| D6 | 主控台權限 | 新能力點 update,local-admin 預設有 |
沿用 restart |
換版不可逆,授權面不同 |
| D7 | 本機 Agent 包目錄 | upgrade/agent-bundles/<Agent 版>/,保留「各台現行版中最舊」與「最新」兩份 |
沿用 bundles/<主系統版>/agent/ |
解掉 FR-167 待辦 42 |
| D8 | 失敗原因 | 兩層:每步 stderr 最後 20 行(≤4 KB、過濾不可列印字元)進事件表給畫面;全量升級 journal 打包上傳主系統當更新單附件,紀錄頁「升級紀錄」下載 | 只給一句 | 60.167 磁碟滿只拿到七個字 |
| D9 | Agent 區步驟 | 對齊成「預先下載 → 更新」兩步 | 一步推送 | 1.8 GB 誰按何時下載要有紀錄 |
| D10 | 舊客戶過渡 | 不做 | 搬包+補登 1.4.8 | 出貨前無客戶 |
| D11 | FR-167 搬入待辦 | 併 63(後台三欄)、58(PDF 快取)、61(CINC 網址型 Profile,開 H-5);60、42 由新機制涵蓋;不併 62 | — | 62 屬主系統自升級 |
| D12 | 批次 | P1/P2/S 立刻;P3/P4 等 171 批 3 | — | 見 §6 |
| 另案併修 | 189 Demo | CSP 擋 8444(S-3)、CINC 報告格式與 HTML 線上看(S-2+S-3)併進本案一起出包 | 另開案 | 不然做完又要再 build 一次 |
| 主系統 | Agent | |
|---|---|---|
| 版號 | 1.28.0 | 1.5.0 |
| 包 | guidant-ai-<版>.tar.gz(不含 agent/) |
guidant-agent-compose-<版>.tar.gz(含 release.json 未簽、release-keys/、notes.json) |
| R2 | v<主版>/<版>/ |
agent/v<主版>/<版>/ |
| 更新站列 | product=host、min_agent_version |
product=agent、min_host_version |
| 章 | release_manifest |
agent_release_manifest |
| 誰驗 | 主系統(現行版 release-keys/) |
主系統代下載時驗一次;Agent 離線上傳時用現行版 Agent 包內的 release-keys/ 再驗一次 |
| 離線檔 | guidant-ai-<版>.offline.tar.gz |
guidant-agent-<版>.offline.tar.gz(U-2 的 publish --product agent 產) |
| 版本說明 | release note 檔尾 3~5 條 | RELEASE_NOTES.md 檔尾 3~5 條(A-0 讀進 notes.json) |
| 最低相容 | scripts/build/min_agent_version |
scripts/build/min_host_version |
%%{init: {'theme':'base','themeVariables':{'primaryColor':'#E2F0F1','primaryTextColor':'#14201F','primaryBorderColor':'#0E7C86','secondaryColor':'#EEF2F3','secondaryTextColor':'#14201F','tertiaryColor':'#FBFCFC','tertiaryTextColor':'#14201F','lineColor':'#4A5A5C','textColor':'#14201F','mainBkg':'#E2F0F1','nodeBorder':'#0E7C86','nodeTextColor':'#14201F','edgeLabelBackground':'#FBFCFC','titleColor':'#14201F','clusterBkg':'#FBFCFC','clusterBorder':'#E4EAEB','actorBkg':'#E2F0F1','actorTextColor':'#14201F','actorBorder':'#0E7C86','actorLineColor':'#C7D1D2','signalColor':'#4A5A5C','signalTextColor':'#14201F','labelBoxBkgColor':'#EEF2F3','labelBoxBorderColor':'#C7D1D2','labelTextColor':'#14201F','loopTextColor':'#14201F','noteBkgColor':'#F6EBD5','noteTextColor':'#14201F','noteBorderColor':'#9C6B12','activationBkgColor':'#EEF2F3','activationBorderColor':'#C7D1D2','sequenceNumberColor':'#FFFFFF'}}}%%
sequenceDiagram
autonumber
participant U as 更新站
participant M as 主系統(Agent 區)
participant A as Agent
participant H as Agent 主機 systemd
M->>U: 每 6 小時/立即檢查 GET /v1/check?product=agent
U-->>M: 1.5.0、min_host_version、sha256、說明
Note over M: 相容:min_host_version ≤ 主系統現行版 才能下載
M->>U: 預先下載 GET /v1/download-url?product=agent
M->>M: 下到 agent-bundles/1.5.0/,驗 agent_release_manifest 章+sha256,寫稽核
M->>A: 推送:寫 rollout → 直推 POST /update {target, sha256, size}(FR-171 A)
A->>M: GET /agents/update/bundle(Range 續傳)
A-->>M: 事件 update_download 每 5%(FR-171 E)
A->>A: 驗 sha256、寫升級單
H->>H: path 單元 → 預檢→停工作→換版→健檢→清理,每步寫進度檔(失敗帶 stderr 末 20 行)
A-->>M: 事件 update_step 每步
H->>H: 新容器起來
A-->>M: 事件 update_result + 上傳升級 journal
M->>M: rollout 列 succeeded/failed → 下一批或暫停
M->>U: POST /v1/report target=agent
core/scheduler_update.py 的 6 小時檢查與「立即檢查」同時問 product=host 與 product=agent。agent_release_service.download() → agent-bundles/<版>/,驗章用 package_verifier 加 expected_payload_type 參數(不另寫一支),寫稽核 AGENT_BUNDLE_DOWNLOAD 含下載人。host_compatible(min_host_version, host_version) 取代 host_allows;overview 回 compatible 與 reason 給畫面。agent_update_rollouts(先 1 台、再其餘、任一失敗其餘暫停、暫停/繼續/稍後提醒/只重推這一台);兩處換掉:包來源、下發改 FR-171 command('/update')。host_version 欄改 agent_bundle_version;主系統升級第 7 步不再保留 bundles/<版>/agent/;_PROGRESS_CACHE、GET /update/progress、agent_update_notify_client 刪。agent-bundles/ 已有相容新版就自動建列」。| kind | 誰送、何時 | payload | 主系統做什麼 |
|---|---|---|---|
update_download |
Agent 容器,每 5% 或每 10 秒 | {job_id, bytes_done, bytes_total} |
upsert agent_update_events 該列的 download 欄 |
update_step |
Agent 容器監看進度檔變動 | {job_id, n, name, status, started, ended, limit_s, detail≤4KB} |
追加該列 steps |
update_result |
新容器啟動讀升級單結果(容器換掉那幾步由新容器補送) | {job_id, status, version, error, log_ref} |
寫 rollout 終態、觸發 _advance、清 download |
| 升級 log | 新容器啟動 POST /agents/update/log(≤10 MB) |
journal tar | 存成更新單附件,紀錄頁「升級紀錄」鈕 |
送不到進 FR-171 outbox(kind event),重連補送、順序照 created_at;主系統按 (agent_uid, job_id, kind, at) 冪等。畫面 3 秒輪詢只讀 agent_update_events,不再去拉 Agent。
| 元素 | 資料 | 動作 |
|---|---|---|
| 現行版、上次更新 | 本機升級單結果檔 | — |
| 「主系統上有 Agent 新版 X」/「已是最新」/「要先更新主系統」 | GET /agents/update/available(通行證+DPoP) |
「請主系統更新這一台」→ POST /agents/update/request → 主系統 push(chosen=[this], actor='agent-console:<操作者>') → 走 §2 圖 |
| 八格、失敗原因 | 本機進度檔(與送主系統的同一份) | — |
| 離線上傳 | 上傳 .offline.tar.gz → 用現行版 release-keys/ 驗 agent_release_manifest → 驗 sha256 → 版本比現行新 → 磁碟夠 → 寫升級單 |
相容只提示;需 update scope;console_events 記一筆 |
由 handoff/cards.py 產。登記型檔 owner:update-center releases.ts/migrations/ U-1;Agent CONSOLE_SCOPES/console blueprint 掛載 G-1;BE product_update_route.py/app_modules.py H-1;FE api.js H-3、console router.js G-3(與 FR-171 K-6 不同批)。
| 批 | 卡 | 階段 | repo | 做什麼 | reuse/extend/new | 改既有檔 | 新開檔 | 依賴 | 驗收(小弟本機) | model/effort |
|---|---|---|---|---|---|---|---|---|---|---|
| 1 | U-1 | 出貨分家 | update-center | 更新站多一條 Agent 版本線:product_releases 加 product 欄(主鍵 product+version)、Agent 列多 min_host_version;r2Key 對 agent 拼 agent/v<主版>/<版>/;三個客戶端端點 product 必填;後台 Agent 分頁與三欄改比 Agent 版本線 | extend:releases.ts 全部查詢加 product 條件、r2Key(product, major, version, filename);registerDraft 收 product/min_host_version;releaseAdoption 的 agent 三欄改 WHERE product='agent' 對 agent_version_reports(FR-167 待辦 63);routes/client.ts 三支 product 缺就 400;後台 routes/admin.ts 版本頁分兩個分頁。登記型檔 owner:src/releases.ts、migrations/ |
src/releases.tssrc/routes/client.tssrc/routes/admin.tssrc/instances.tstest/ |
migrations/0005_product_column.sqltest/releases_agent.spec.ts |
— | vitest:r2Key('agent',1,'1.5.0','x.tar.gz')=agent/v1/1.5.0/x.tar.gz、r2Key('host',…) 不變;/v1/check 不帶 product 400;帶 product=agent 只回 agent 列且帶 min_host_version;後台 Agent 分頁三欄用 agent 回報算(突變:拿掉 product 條件 → 紅)。wrangler dev 套 0005 後本機 D1 既有 10 列 product=host |
opus/high |
| 1 | A-0 | 出貨分家 | evidence-agent | Agent 包分家:build_compose_bundle.sh 產 release.json(未簽,product=agent、min_host_version 讀 scripts/build/min_host_version 一行檔、sha256、size)、包內帶 release-keys/(與主系統同一批公鑰與 PROD_KIDS)、版本說明改讀 RELEASE_NOTES.md 檔尾 3~5 條 | extend:build_compose_bundle.sh 收尾段加 release.json 與 release-keys;notes.json 產法不變但來源改 RELEASE_NOTES.md;min_host_version 檔不存在=不限 |
scripts/build/build_compose_bundle.shscripts/build/README.mdRELEASE_NOTES.md(加「版本說明怎麼寫」段) |
scripts/build/min_host_versionscripts/build/agent_release_json.shtest/shell/agent_release_json.bats |
— | bats:對假 bundle 目錄跑 → release.json 欄位齊、sha256 與 .sha256 一致、min_host_version 檔有值就帶沒有就不放鍵;包內 release-keys/PROD_KIDS 與 BE scripts/build/release-keys/ 同內容(守衛:diff 為空)。shellcheck 綠。不在 188 出包 |
opus/medium |
| 1 | B-0 | 出貨分家 | BE | 主系統包不再帶 Agent:build_bundle.sh 拿掉 ⑧ 收 Agent 包與「正式包必須帶 Agent」守門;--skip-prod-key-check 不再管 Agent;bundle-info 拿掉 Agent 列 | extend:刪段 | scripts/build/build_bundle.sh 337–369、463–469、518scripts/build/README.mddocs/features/FR-167-2610-online-update-channel/release-runbook.md 2.3 段 |
— | — | bats/shellcheck 綠;對假目錄跑 build_bundle 不找 .build/compose/;bundle-info 無 Agent 列;grep -n AGENT_COMPOSE_BUNDLE scripts/build/ 為 0 |
sonnet/low |
| 1 | S-1 | 收尾與 189 Demo 併修 | evidence-agent | 待辦 58:Agent 主控台檔案頁三個查詢加 ref_id IS NULL(PDF 預覽快取不列成證據) | extend | guidant_agent/files/adapters/customer_files_repo.pytest/files/ |
— | — | pytest:有 ref_id 的列不出現在清單、數量、佔用 | sonnet/low |
| 1 | S-2 | 收尾與 189 Demo 併修 | BE | 189 Demo 併修(BE):CINC 報告 report_ref 同時接受 upload_uids 格式、有報告就有「看報告」;HTML 報告由主系統代取後以 sandbox 內容回給 FE(Content-Type text/html、CSP sandbox header、不跑腳本) | extend:_agent_local_runs.py:50 _report_file_uid 加 upload_uids 分支;新 route agent-local-runs/<uid>/report/view reuse RemoteAgentAdapter.fetch_agent_blob 與證據預覽的 sandbox header 做法;FR-166 design.md report_file_uid 改對 |
core/plugins/_agent_local_runs.pyapi/remote_agent/routes/agent_local_run_route.pydocs/features/FR-166-2610-agent-standalone-console/design.md |
test/test_agent_local_run_report.py |
— | pytest:upload_uids 格式 has_report=True;view 端點回 Content-Security-Policy: sandbox 且 X-Content-Type-Options: nosniff;FR-166 design 字串改對 |
opus/medium |
| 2 | U-2 | 出貨分家 | update-center | 蓋章 CLI:guidant-release 加 --product host | agent;Agent 清單 payload_type=agent_release_manifest、含 min_host_version;publish --product agent 順帶產離線單一檔 guidant-agent-<版>.offline.tar.gz;驗章函式按型別各驗各的 | extend:cli/envelope.ts signRelease 加 payloadType 參數;cli/commands.ts 三個子命令加 --product;verifyRelease 的型別檢查改「等於呼叫端指定的型別」不寫死 |
cli/envelope.tscli/commands.tscli/site.tscli/README.mdtest/cli/ |
test/cli/agent_manifest.spec.ts |
U-1 | vitest:--product agent 簽出的 canonical payload_type=agent_release_manifest 且有 min_host_version;用 host 驗證器驗 agent 清單 → bad_signature(突變:驗證器不看型別 → 紅);離線檔內含包+簽好的 release.json+.sha256。用測試章實簽一份存 test/fixtures/agent-release-1.5.0.json 給 BE/Agent 卡當 fixture |
| 2 | H-3 | 主系統側 Agent 區 | FE | FE 登記型檔佔位:api.js 加 agents/check、agents/download、agents/events 三個常數;i18n 鍵占位 | extend | src/config/api/api.jssrc/locales/*/product-update.json |
— | — | vite build 0 |
sonnet/low |
| 2 | S-3 | 收尾與 189 Demo 併修 | FE | 189 Demo 併修(FE):前門 CSP connect-src 動態帶入登記的 Agent 位址(api 啟動產 include 檔、fe 容器 entrypoint 讀取、Agent 新增或改位址時重載);Agent 執行紀錄頁「看報告」用 sandbox iframe | extend:nginx.onprem.conf 的 CSP 行改 include /etc/nginx/conf.d/agent-origins.conf;entrypoint 從 api GET /internal/agent-origins 產檔並 nginx -s reload;AgentLocalRunsView.vue 加「看報告」開 <iframe sandbox>(用 .attr,見 memory iframe_sandbox_attr) |
nginx.onprem.confdocker/entrypoint.shsrc/views/remote-agent/AgentLocalRunsView.vuesrc/views/remote-agent/__tests__/AgentLocalRunsView.spec.js |
docker/agent-origins.sh |
S-2 | 本機 docker 跑 fe image:有兩台 Agent 時 CSP header 含兩個 https 來源;新增一台後 reload 含三個;iframe 有 sandbox 屬性;vite build 0 |
opus/medium |
| 3 | H-1 | 主系統側 Agent 區 | BE | 主系統 Agent 版本線:更新站 client 帶 product=agent 檢查、Agent 包下載到 upgrade/agent-bundles/<Agent 版>/ 並驗 agent_release_manifest 章與 sha256、相容兩向判定、保留兩份清理、host_version 欄退役改 agent_bundle_version、升級第 7 步不再保留 bundles/<版>/agent/ | extend:update_site_client.py check()/download_url() 加 product;package_downloader.py/package_verifier.py 加 expected_payload_type 參數(不另寫一支);agent_rollout_service.find_agent_bundle 改掃 agent-bundles/;host_allows 改 host_compatible(min_host_version, host_version);migration 改欄名。登記型檔 owner:product_update_route.py、config/app_modules.py(一次掛 H-2 的端點空殼) |
app/product_update/service/update_site_client.pyapp/product_update/service/package_downloader.pyapp/product_update/service/package_verifier.pyapp/product_update/service/agent_rollout_service.py 205–272app/product_update/service/product_update_flow.py(第 7 步)infra/product_update/agent_update_rollout_model.pyapi/product_update/routes/product_update_route.pyconfig/app_modules.pytest/test_fr167_agent_rollout.py |
app/product_update/service/agent_release_service.py(檢查/下載/驗/保留)scripts/sql/2026-11-xx-fr172-agent-bundle-version.sqltest/test_agent_release_service.py |
U-2 | pytest:用 U-2 fixture 驗章過、把型別改 host 的 fixture 驗不過(突變);min_host_version 高於現行 → compatible=False 且原因字串;下載後目錄 agent-bundles/1.5.0/ 有包+清單;保留規則:三版只留最舊現行與最新。migration 只套 DEV、回報基線待重產。實打 8003 GET /product-update/agents 看到 agent_release 區塊 |
opus/high |
| 3 | H-5 | 主系統側 Agent 區 | BE | 待辦 61:網址型 Profile 匯入時順便存檔,之後當檔案型同步給 Agent(CINC 下拉才有東西) | extend:profile_import 網址型解析完 file_id 補寫(存進既有 upload_files);_agent_profile_sync.py 同步條件不變(有 file_id 就同步) |
app/detection_profile/(網址型解析 service)test/ |
— | — | pytest:網址型匯入後 file_id 非空;DEV 重新匯入 dev-sec 兩支後 121 本機 Agent 拉 config 看到 CINC 兩筆 |
opus/low |
| 4 | H-2 | 主系統側 Agent 區 | BE | 更新中心 Agent 區端點:POST agents/check(問更新站 Agent 新版)、POST agents/download(預先下載,寫稽核下載人)、overview 回 Agent 版本線狀態與相容原因;每 6 小時排程同時問 host 與 agent | extend:填 H-1 掛好的空殼;core/scheduler_update.py 的 6 小時檢查加一次 agent |
app/product_update/service/product_update_app_service.pycore/scheduler_update.pyapp/product_update/service/agent_rollout_service.py(overview 組裝) |
test/test_agent_release_endpoints.py |
H-1 | 實打 8003:check 回 {current, latest, compatible, reason, size, notes};download 寫稽核 AGENT_BUNDLE_DOWNLOAD 含下載人;overview bundle_ready 由 agent-bundles 判;排程 log 看到兩次 check |
opus/medium |
| 5 | H-4 | 主系統側 Agent 區 | FE | 更新中心 Agent 區 v3(雛形①②⑤):Agent 自己的版本線、立即檢查、預先下載、相容提示、要先升主系統的灰鈕、更新確認視窗第 4 條文案;更新紀錄來源欄加「主系統下載/離線上傳(Agent 主控台)」 | extend:AgentRolloutPanel.vue+useAgentRollout.js 改讀新 overview;主系統卡元件不動 |
src/views/product-update/AgentRolloutPanel.vuesrc/composables/useAgentRollout.jssrc/views/product-update/agentRolloutState.jssrc/views/product-update/__tests__/ |
— | H-2, H-3 | vitest:compatible=false 時預先下載鈕 disabled 且顯示 reason;已下載未推 → 顯示「已下載,可更新」;接 8003 手點一輪;grep border-left 0;vite build 0 |
opus/high |
| 10 | E-3 | 進度與結果即時(等 FR-171 批 3) | evidence-agent | Agent 升級事件:下載每 5%/10 秒、主機每步開始結束、最終結果,用 FR-171 send_event 當下送;新容器啟動補送;upgrade_order.sh 每步失敗把 stderr 最後 20 行寫進度檔 detail(≤4 KB);升級完把全量 journal 打包上傳主系統當附件 | extend:update_sync.py 的 _fetch 下載迴圈與 current_progress 改成呼叫 `event_sender.send_event('update_download' |
'update_step' | 'update_result', …);upgrade_order.sh的uo*失敗路徑收 stderr;新sync/app/update_log_upload.py用既有 blob 上傳路徑POST /agents/update/log;拔掉心跳 upgrade_result與GET /update/progress` |
guidant_agent/sync/app/update_sync.py 449scripts/install/lib/upgrade_order.shscripts/install/lib/progress.shscripts/install/lib/upgrade_runner.sh(收尾打包 journal)test/shell/upgrade_order.batstest/sync/test_update_sync.py |
guidant_agent/sync/app/update_events.pyguidant_agent/sync/app/update_log_upload.py |
— |
| 10 | E-4 | 進度與結果即時(等 FR-171 批 3) | BE | 主系統收升級事件:agent_update_events 表(每台每 job 一列 upsert,存最後進度與全部步驟與 detail)、三個 kind 的 handler 掛進 FR-171 事件分派表、升級 log 附件端點、rollout 終態由 result 事件寫、拔掉拉進度與 _PROGRESS_CACHE | extend:往 FR-171 E-1 的 AgentEventDispatcher 掛 update_download/update_step/update_result 三個 handler;agent_rollout_service.record_result 改由事件呼叫;刪 _PROGRESS_CACHE、agent_update_notify_client 殘留;log 附件 reuse 證據上傳的儲存路徑存成更新單附件 |
app/product_update/service/agent_rollout_service.py 65–67、410、633–682core/plugins/_agent_update.pytest/test_fr167_agent_rollout.py |
infra/product_update/agent_update_event_model.pydomain/product_update/agent_update_event_entity.pyapp/product_update/service/agent_update_event_handlers.pyapi/remote_agent/routes/agent_update_log_route.pyscripts/sql/2026-11-xx-fr172-agent-update-events.sqltest/test_agent_update_event_handlers.py |
H-1 | pytest:step 事件 upsert 同列 steps 長度遞增;result succeeded → rollout 列 succeeded 並觸發 _advance;重送同 (job_id, kind, at) 冪等;grep 無 _PROGRESS_CACHE、無 update/progress。migration 只套 DEV。本機 Agent 連 8003 推一台,events 表 3 秒內有列 |
opus/high |
| 11 | E-5 | 進度與結果即時(等 FR-171 批 3) | FE | Agent 區八格改讀事件表(雛形③④):每格顯示時間與耗時、下載量、失敗那格標紅、「看原因」展開 detail、「升級紀錄」下載鈕與診斷包並排、失敗後「只重推這一台」 | extend:agentRolloutState.js agentStepRows 改吃 agent.update_event;AgentRolloutPanel.vue 展開區加 detail 與下載鈕;「只重推這一台」reuse 既有 push 選單 |
src/views/product-update/agentRolloutState.jssrc/views/product-update/AgentRolloutPanel.vuesrc/views/product-update/__tests__/ |
— | E-4, H-4 | vitest:給 7 步 steps 第 8 格 pending;failed 於第 3 步 → 第 3 格 bad 其餘空;detail 預設收起點開顯示;grep border-left 0 |
opus/medium |
| 11 | G-1 | Agent 主控台更新頁(等 FR-171 批 3) | evidence-agent | Agent 主控台更新 API:能力點 update;GET /console/update/status(現行版、上次更新、向主系統問 available);POST /console/update/request(請主系統更新這一台);POST /console/update/offline(上傳離線檔→用現行版 release-keys 驗 agent_release_manifest 章→驗 sha256→寫升級單);console_events 記誰按了什麼 | extend:shared/constants.py CONSOLE_SCOPES 加 update;新 console/api/update_routes.py 掛 blueprint;驗章 reuse Agent 既有 verify-tree 的 Ed25519 驗簽 helper 加 payload_type 參數;寫升級單 reuse update_sync._write_order。登記型檔 owner:shared/constants.py、console/api/ 掛載 |
guidant_agent/shared/constants.pyguidant_agent/console/api/(掛載)guidant_agent/sync/app/update_sync.py(_write_order 開放給 console 用)test/console/ |
guidant_agent/console/api/update_routes.pyguidant_agent/console/app/update_console.pytest/console/test_update_routes.py |
A-0, E-3 | pytest:無 update scope 403;離線檔用上傳包內的公鑰換掉後驗不過(突變:改用上傳包內公鑰 → 紅);sha256 不符拒;成功寫升級單且 console_events 有一筆;status 在主系統連不上時回 main_unreachable 不炸 |
opus/high |
| 12 | G-2 | Agent 主控台更新頁(等 FR-171 批 3) | BE | 主系統給 Agent 主控台的兩支:GET /agents/update/available(這台相容的最新 Agent 版與說明)、POST /agents/update/request(建一張只含這台的 manual rollout,按更新的人記 agent-console:<操作者>);Agent 管理頁勾權限清單多 update 一格 | extend:route 用 agent_pass_required;request reuse agent_rollout_service.push(chosen=[this]);離線操作者權限清單在 _agent_offline_operators.py 加一項 |
app/product_update/service/agent_rollout_service.py(push 的 actor 參數)core/plugins/_agent_offline_operators.pytest/ |
api/remote_agent/routes/agent_update_request_route.pytest/test_agent_update_request.py |
H-2, G-1 | 實打:available 回相容最新版或 requires_host;request 建 rollout 一列 actor=agent-console:local-admin,並經 FR-171 command('/update') 直推這台;權限清單多一格 |
opus/medium |
| 13 | G-3 | Agent 主控台更新頁(等 FR-171 批 3) | FE | Agent 主控台「更新」頁(雛形⑥⑦⑧⑨):維運群組加一項(scope update)、四種狀態、請主系統更新確認框、離線上傳拖放與五項檢查清單、更新中八格 | extend:console/router.js 加一項;Update.vue 照 Ops.vue 版面;八格元件與主系統 FE 的 UpgradeStepList 不共用(兩個 build 分開)但 rows 形狀一致 |
console/router.jsconsole/api.js |
console/views/Update.vueconsole/update.jsconsole/updateApi.js |
G-1, G-2 | npm run build:console 0;四狀態切換;上傳假檔顯示五項檢查;grep border-left 0 |
opus/medium |
| 14 | S-4 | 收尾與 189 Demo 併修 | BE | 結案:版號 BE 1.28.0/Agent 1.5.0、本機新裝+升級驗、188 出包(主系統包不含 agent/;Agent 包含 release.json 與 release-keys/)、開包 grep;FR-167 CM-3378 一併收 | reuse:P12-5(CM-3378)流程,少掉 Agent 進主系統包那段 | pyproject.tomldocs/features/FR-167-2610-online-update-channel/handoff/fr167-STATE.md(CM-3378 收) |
— | S-1, S-2, S-3, E-5, G-3 | 主系統包解開無 agent/、小 ≥1.5 GB;Agent 包有 release.json(payload 未簽)與 release-keys/PROD_KIDS;本機新裝 0 錯;回寫兩包路徑與 sha256 |
opus/high |
共 19 張、10 批;update-center 2、evidence-agent 4、BE 8、FE 5。
跨案依賴:E-3 用 FR-171 E-2 的 send_event;E-4 往 FR-171 E-1 的分派器掛 handler;G-2 用 FR-171 A-1 的 command('/update')。批 10 起由首腦在 FR-171 批 3 合回後才按「開始」。
agent-bundles/1.5.0/ 有包與清單、稽核有下載人。no space left on device、「升級紀錄」下載到 journal、其餘暫停;「只重推這一台」成功。release-keys/ 的假包上傳 → 驗章失敗。agent/;/v1/check 不帶 product 回 400。Agent 直連更新站、Agent 差異包、Agent 自動裝、主系統版本線改名或搬 R2、第二把發布私鑰、舊客戶過渡、心跳備援、FR-167 待辦 62。