FR-191 Agent 安裝包自助下載:交接紀錄(append-only)

每棒追加一個 block,既有 block 不改。發現舊 block 有誤,在新 block 的「推翻了什麼」寫明。


§1

首腦棒(2026-11-09~11-10,session「[R3]Agent下載點」,Fable 5.1)

做了什麼

  • 探脈:Agent 包在主系統 agent-bundles/<版>/(更新中心預先下載後落地,旁有 .sha256 與簽章清單)但人拿不到;更新站預簽網址寫死 15 分鐘、客戶主機多無外網;既有短效簽章票(軸⑤)ttl 是驗證參數;FE navigator.clipboard 在 http+IP 不存在。
  • design.md D1~D11、FR 登記、FR-172 README 標「新需求另開」。
  • Notion 母卡 CM-3761、子卡 CM-3762(BE/批 1/opus high)、CM-3763(FE/批 2/opus high/Repo fe)、CM-3764(文件/批 3/sonnet medium),四欄齊。
  • 決策者問「要不要擋 user 一直點下載」→ 補 D11(FE 10 秒鎖+先探測;BE 同票 2 條、全站 3 條、發票每分鐘 10 次),append 進兩張子卡。
  • 決策者問「可以跟 FR-188 一起跑嗎」→ 逐張撈 FR-188 十九張 main/fe 卡的改檔清單,撞四處;BE 卡改成完全不碰那六個檔(三端點全在新檔、時限改獨立端點、稽核 reuse 事件碼 6251)、FE 卡加「CM-3741 合回才動 AgentRolloutPanel」前置。design 加 §6。
  • 工作坊 53 跑完三棒後抽查驗收:三個 commit 均在 feature/FR-172 HEAD、禁改檔零異動、BE 141 條與 FE 136 條測試綠;DEV 放測試包 1.5.0 實打十四項全過(細節在 FINAL-SPEC §4.2)。
  • 重啟 DEV BE(kill 舊 PID 86499 後起新的),FE vite 熱更新未重啟。
  • 預先收尾:FINAL-SPEC、LOG、README、STATE。

commits(BE,feature/FR-172)

  • 79e58a387 design D1~D10、README、STATE、FR 登記、FR-172 補註
  • 1d1c9b105 design D11 與 §6 避撞;STATE 同步
  • 本棒收尾 commit(FINAL-SPEC、LOG、README、STATE)

關鍵決策

  • 包從主系統本機供檔,不給更新站 R2 連結(寫死 15 分、客戶主機多無外網)。
  • 兩個入口共用一個元件;產生連結只要讀權限(下載不改系統狀態);時限參數放更新中心。
  • 票綁 <版>:<kind>,時限不寫進票、由驗票當下設定決定(調整時限立刻影響已發連結,接受這個副作用,換來不用另立 token 表)。
  • 併發限「同時幾條」不限「總共幾次」,續傷不受影響;Redis 連不上放行(防佔線程,不是授權)。
  • 不動前門 nginx、不做 X-Accel 直送(頻寬真被吃滿再議)。
  • 編號原登記 FR-190,與同日另一 session 的「Agent 主控台權限能力點化」撞號,改 FR-191。

推翻了什麼

  • 卡主文「agent_overview() 加 bundle_link_ttl_minutes」與「新增事件碼 6252」被 FR-188 避撞段推翻,改獨立端點與 reuse 6251。
  • 卡主文「AgentRolloutPanel.vue 只加兩行」實作為三行(動作列包在 canWrite && !updating 的 v-if 內,唯讀管理員會看不到鈕,另加一行 v-else);首腦接受。
  • 卡主文「時限欄位放 Agent 區說明旁」實作改放進「取得安裝包」對話框內(Panel 接近 800 行上限,且改時限與拿連結是同一件事);首腦接受。

踩到的坑

  • Flask send_file 預設 direct_passthrough,call_on_close 不會被叫,名額放不回去;開發伺服器在客戶端中途斷線時也跳過 close。修法是串流包一層 generator 在 finally 釋放,並補「中途斷線」測試。單元測試原本假綠。
  • PrimeVue 3.53 InputNumber 先 emit blur 才寫回 v-model,blur 當下讀到上一個值,要 await nextTick();也不能設 min/max,會在 blur 時把 0 夾成 1 默默送出。
  • 前端到期判斷不能直接比伺服器 expires_at,使用者電腦時鐘快了就一拿到就過期。
  • 登入端點欄位是 username 不是 login_name;FE dev server 在 5180 不是 5173。
  • Redis 密碼含 !,zsh 在 -a 參數會吃掉,用 REDISCLI_AUTH 環境變數。
  • 驗收開始時 repo 正處於另一 session 的 merge 衝突中(docs/features/index.html),我的 design 改動退出暫存區等它收完再 commit。

DEV 環境留下的東西

  • .env 末尾新增 GUIDANT_UPGRADE_DIR=~/guidant-dev/upgrade、GUIDANT_RELEASE_KEYS_DIR=~/guidant-dev/release-keys(路徑,非憑證),備份 .env.bak-20261110102535。
  • ~/guidant-dev/upgrade/agent-bundles/1.5.0/ 三個檔與 ~/guidant-dev/release-keys/eb5eb520d9c608cb.pub,來源 test/fixtures/agent_release/。要清就連目錄刪掉並把 .env 兩行拿掉。
  • runner 測 token 對話框時建的 4 把註冊 token 已撤銷;時限已回 30。

下一棒接手要知道

  • 母卡 CM-3761 是否收 Done 等決策者。
  • 手冊截圖等真包;決策者已裁無圖版先收。
  • 真包三項驗證(FINAL-SPEC §4.2 打折處)等 STG/POC 預先下載後跑。

§2

首腦棒續(2026-11-10,同 session,Fable 5.1 → Opus 5.5)

發生什麼

  • 決策者實測 v1.0 畫面,指出三件事:時限欄位不該在下載對話框、不該讓客戶改;大小 1 KB 不對(那是首腦放的測試包);連結應該直接給 Cloudflare R2。
  • 首腦先回「兩條都給」的折衷,決策者澄清情境:「客戶還沒裝 Agent 的時候,要有地方可以下載安裝包」,裁直接給 R2。
  • 決策者:「你重新開卡跟改文件,差太多了」。

推翻了什麼

  • v1.0 D1(主系統本機供檔):誤判情境。以為是「主系統已有包、給人拿」,實際是「還沒裝 Agent、主系統通常也沒包」。v1.0 要先預先下載 1.7 GB 才有東西給,最需要時最不好用。首腦 11-09 排除 R2 的兩個理由(寫死 15 分鐘、客戶主機沒外網)都不成立:時限可以改更新站;下載者本來就是管理員的電腦,不是客戶 Agent 主機。
  • v1.0 D4(主系統可調時限):時限該由網址簽發方(原廠)控,不是客戶。
  • v1.0 D11 的併發上限與前端探測:下載不經主系統後不需要。
  • 首腦先前的「兩條都給」折衷:多一套來源與時限,客戶看到兩組網址不知道選哪個。

做了什麼

  • 實查:更新站 /v1/download-url 只認 artifact=ops 且 Agent 不准帶;預簽時限常數 15 分鐘;R2 上 Agent 1.7.4 的包、.sha256、release.json 都在;更新站後台有 Access 保護、無設定表;主系統 update_site_client.download_url() 有 R2 主機白名單;AgentReleaseService.check() 會寫 agent_last_check。
  • 寫 design-v2.0.md(D1~D10+與 v1.0 的異動表),design.md 加「已被推翻」提示。
  • 開 CM-3800~3803(批 4~7),母卡 append 改判說明。
  • FINAL-SPEC 改成「v2.0 實作中」短版(v1.0 版的定版內容已失效,不能留著當現況);README 三段、STATE 改寫。
  • 移除 DEV 為 v1.0 驗收加的 .env 兩行與 ~/guidant-dev/ 測試包,重啟 BE。

教訓

  • 需求一句話裡的「下載點」要先問「誰、在什麼時候、手上有什麼」。首腦用「主系統已經有包」當前提,整套設計跟著歪。
  • 「可以調整的參數」要問「誰可以調」。首腦預設是客戶管理員。
  • 首腦用「沒外網」排除 R2 時,把下載者想成 Agent 主機;實際下載者是管理員的電腦。
  • 「產品管理員」在專案沒有對應角色,首腦假設是原廠。待決策者確認。

§3

v2 第 4 棒 文件棒(2026-11-10,CM-3803,Sonnet 5.5)

做了什麼

  • 手冊:快速版第 3 步與 03 章 3.2 取包句換成標準句(R2 預簽網址、下載電腦要能連外網、時限由原廠設定、未連線更新站向原廠索取);失敗句改「回主系統重新產生連結下載」(03/10/11、快速版三處);07 開頭、README 的「從主系統下載」改成「取得」;04 章提示補「原廠」。FR-189 README 取包來源一行同步。
  • FINAL-SPEC 整份改寫成 v2.0 現況;README 進度、STATE 更新。

推翻了什麼

  • 沒有推翻前棒結論。FINAL-SPEC「v2.0 實作中」短版被定版取代。

坑/打折

  • 手冊截圖不放:CM-3802 的兩張是模擬環境(示意 R2 主機、icon 字型缺),決策者已裁先不做。
  • 驗證都沒有對真 R2 與真授權照;FINAL-SPEC §4、§5 明寫。
  • grep 剩一處「從主系統下載」在 07 章 7.8(Agent 升級包由主系統供應),與取包無關,保留。

§4

首腦總驗與結案(2026-11-10,Opus 5.5)

做了什麼

  • v2 四張卡(CM-3800~3803)工作坊跑完。首腦核:commit 只在三個 repo 的 ws-CM-3761、未合回;試合併三 repo 無衝突;舊 v1 程式拿乾淨;BE 376/FE 145/更新站 187+101 條測試綠;DEV agent_last_check 未被動到、稽核 6251 有一筆 source=r2。端到端實打採信 CM-3801 回寫(同環境,未重做)。
  • 決策者裁合回、push、母卡 Done。合回前確認三 repo 無進行中 merge、別人未提交的改動與合回檔不重疊。三 repo merge --no-ff ws-CM-3761 無衝突;重啟 DEV 後端,實打新端點回 412001(DEV 沒設更新站,預期)、兩支舊端點 404。
  • push 前 fetch 三 repo,遠端無他人新 commit。BE push 時多帶一個別的 session 在合回後提交的 a8583fe29(重產出貨基線補 FR-190 能力點,決策者裁示),確認 FR-191 合回 commit 在內。
  • 母卡 CM-3761 改 Done(四張 v2 子卡原本已 Done)。

查到的事

  • 正式更新站最後部署 2026-11-04,FR-191 的後台「設定」分頁與 artifact=sha256 都還沒上線。決策者裁不另部署 dev Worker、隨下一次上 PRD。
  • 守衛 test_module_boundaries.py 的紅與 FR-191 無關:主線兩支是 FR-190 能力點未進出貨基線(已被 a8583fe29 補上),工作坊分支多一支是落後主線、對不上新釘的 jedi-agent-protocol 0.6.0。

上 PRD 的陷阱

  • 正式 D1 要先套 0007_site_settings.sql 再部署 Worker,否則讀設定時表不存在,所有下載網址(含主系統升級包)都會 500。

§5

正式更新站部署與上線缺陷修正(2026-11-10,Opus 5.5)

做了什麼

  • 決策者裁「部署上去」。照 docs/system-design/update-center/04-作業程序.md:正式 D1 先套 0007_site_settings.sql,再 npm run deploy。部署前用真 R2 金鑰簽帶 response-content-disposition 的網址實打 R2,確認接受、續傳正常(這是唯一會影響主系統升級包下載的改動)。
  • 部署第一次被 predeploy 擋下:vitest 主設定自訂 exclude 蓋掉預設排除,掃進工作坊 .worktrees/ 各自的 node_modules,434 檔 6006 條、72 條失敗。update-center 9ed50a1 加排除 .worktrees/** 後 187+101 條綠,重新部署成功(版本 80ccaecb)。
  • 用 190 主機授權身分(從 190 DB 唯讀取授權原文、容器 machine-id 算指紋,190 本身零改動)實打正式站:主系統 check、升級包網址、真 R2 下載與續傳都正常;Agent 線 0.0.0 回 none。
  • 決策者裁「開卡、直接修」:開 CM-3819(批 8),BE 3dd1547de 抽常數 NO_AGENT_BASE = "1.0.0" 三處共用,正式站用主系統真程式碼實打通過,push,DEV 重啟,卡改修正待驗證。

踩到的坑

  • Agent 線所有版本 min_base_version 由更新站補成 <主版>.0.0;零 Agent 時報 0.0.0 會被 planPath 濾光。DEV fixture 的 min_base 是 0.0.0,四棒驗收都假綠。教訓:版本路徑類邏輯要拿正式站真資料驗一次。
  • Cloudflare 機器人防護擋 Python urllib 預設 User-Agent(1010)。主系統用 httpx 不受影響,手寫驗證腳本要自帶 UA。
  • update-center 主設定 exclude 會蓋掉 vitest 預設排除清單;工作坊在 repo 內開工作區後才會踩到。

§6

本機驗收環境與結案(2026-11-10,Opus 5.5)

  • 決策者要在本機看畫面。DEV 授權與身分票都綁別台機器(f2ef9354…),本機是 67ac366f…,正式站也不認 DEV 金鑰。走方案 A:本機起 wrangler dev 模擬更新站(灌正式站 Agent 1.7.4 的真清單與簽章、模擬 R2 放真 .sha256 與佔位包);用本機 DEV 授權中心私鑰簽一張綁本機的身分票(不寫 LC 資料庫),只替換 DEV 授權列的 update_ticket 一欄;.env 加 UPDATE_SITE_URL 與 GUIDANT_RELEASE_KEYS_DIR(後者指 repo 內附公鑰,.env.sample 明載開發機這樣設)。後端 API 與無頭瀏覽器都打通。
  • 決策者「不用了,直接合併回去標 Done」。還原:原票寫回(md5 核對)、.env 兩行拿掉(與加入前逐字相同)、wrangler 程序強制結束(一般 kill 沒收掉 npm exec 父程序)、暫存票檔刪除、後端重啟。CM-3819 與母卡 Done。
  • 推送時發現本地多 48/21/4 筆是另一個 session 的 FR-188 合回,未代推。

踩到的坑

  • DEV .env 根本沒有 UPDATE_SITE_URL 行,不是空值;改 .env 前的檢查要分「缺行」與「空值」。
  • DEV 授權的 update_ticket 是 JSON 信封,payload 為 zlib 壓縮的 base64,不是 armor 格式。
  • 前端登入後立刻切路由會被守衛導回首頁,要等 /users/me/capabilities 回來;整頁 reload 會掉登入態。
  • npm exec wrangler dev 的父程序不吃 SIGTERM,要對確切 PID 補 SIGKILL。

§7

文件收尾(2026-11-10,Opus 5.5)

決策者「要收尾了,文件都有做了嗎」。逐類開檔核對,補了五處:

  • FINAL-SPEC:CM-3803 在正式站部署前寫的,四處過時(真 R2 待補、驗證打折、沒做完三項)。改寫為上線後現況,驗證改列正式站實打七項。
  • design-v2.1:v2.0 的 D3、現況、流程圖寫零 Agent 送 0.0.0,正是 CM-3819 修掉的錯。定稿不回改,開 v2.1(完整可獨讀+「與 v2.0 的異動」),v2.0/v1.0 頂部指向 v2.1。
  • 頁面規格(spec-site):更新中心與 Agent 管理兩頁原本完全沒寫這功能。更新中心補功能總覽 #13、UC-PU-05、權限、API、資料對應、檔案地圖、外部依賴、兩條已知坑;Agent 管理補功能 #3a、檔案地圖。mkdocs build --strict 通過。
  • 更新站系統說明:原本寫死 900 秒、artifact 只有 ops、五個分頁、18 條路由、migration 到 0006。對照實作補 05/06/07/08 四章,04 章新增「調整下載網址有效時間」程序,02/03/12/README 同步數字。
  • README:「先看哪一份」補頁面規格、客戶手冊、原廠維運三類文件位置。

核對過不用改的:手冊 03 章 3.2、04 章 4.1、快速版第 3 步已是 v2 說法;07 章 7.8「Agent 只從主系統下載」講的是 Agent 升級,屬事實。

踩到的坑

  • 錨點腳本 fix_anchor_ids.py 會順手改別人的頁(這次在 FR-190 的 agent-console.md 補一個 id,因為被本案跨頁連結到);spec-site site/ 產物有其他 session 改 md 沒 build 的殘留,只收本案頁面的產物。
  • git 對中文檔名預設八進位轉義,批次 git add 要用 status --porcelain -z。