lazyBoy/docs/agent-computer-progress.md

1280 lines
202 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Agent Computer progress (V3)
> **2026-09-10 第三輪狀態修正:全案仍為 PARTIAL。** 請以 [逐項稽核](agent-computer-audit-2026-09-10.md) 的 O01–O48/T01–T64 為準。下方前兩輪 `IMPLEMENTED` 包含尚未接進 runtime 的純函式及 fixture,不能視為全案已驗收。本輪實際修復與測試見文末。
基準 commit(計劃):`e6afa324530e19922909d4692c28fb005cc05a7a`
本機開始 HEAD:`79952b088b394e36d6c3db3952165f1c3fd59961`
未覆蓋使用者未提交修改(`docs/plan/` 仍未追蹤)。
驗證(2026-09-10 複查,見文末「複查:Bug 修正」):`cargo fmt --check`、`cargo clippy --workspace --all-targets -D warnings`、`cargo test --workspace`(含 sqlx,全綠)、`make test-agent-computer`、`apps/web npm run build` 全部 exit 0。
sqlx 測試用拋棄式 `pgvector/pgvector:pg16` 於 `127.0.0.1:5434`(`docker run --rm --name lazyboy-test-pg …`),不重建正在服務的 `lazyboy-postgres-1`。
真實 OAuth/主信箱/TigerVNC GUI fixture:`BLOCKED_EXTERNAL`。
sample MCP 在真實 Computer 容器 `lb-team-space-*` 內 `python3 echo_server.py` JSON-RPC 回 `from-computer`。外掛 UI 已 typecheck;無瀏覽器工具未點擊。
---
## PR-00 / PR-01
狀態:IMPLEMENTED(見前次)。截圖交付、semantic/vision、原生 fs/exec 短路徑。
---
## PR-02:雙模式身份
狀態:IMPLEMENTED(資料+契約+idle;沒有強制遷移)
- `computers.generation`;destroy/recreate `generation + 1`(舊 job 不可裝活)。
- Team 多 bot 同 `computer_id`:`computer_scope_key(Team, space, bot-a) == bot-b`。Dedicated 互斥 scope。
- **沒有**對 `bots.computer_id` 加 UNIQUE。
- `AdapterContext.computer_generation` / `control_epoch`。
- Pause scopes:`PauseScope::{Agent,Display,Computer}`;停 A 不 bump B 的 display epoch。
- idle reaper 把 `computer_jobs` running 算進活性。
回滾:migration 022 + generation SELECT/UPDATE。
---
## PR-03:ledger / 脫敏 / activity
狀態:IMPLEMENTED(Postgres 為 mutation 來源;本環境未跑 sqlx 整合測試)
- `dispatch`:mutating tool 先 `begin`;journal insert 失敗則 `JOURNAL_UNAVAILABLE`,不開做。
- 同 `operationId` + payload hash replay 已存結果;不同 hash → `PAYLOAD_MISMATCH`。
- `operationId` 不進入 payload hash。
- `finish` 寫 `computer_operations` + `operation_outbox`;`worker_loop` `flush_outbox` 只補尚未出現在 `run_activity` 的 `operationId`。
- 結果與 outbox payload 經過 `redact_text` / `redact_json`。
---
## PR-04:jobs / cancel / CAS
狀態:IMPLEMENTED(Runner 在 API 行程內;container recreate 以 generation 中斷)
- `JobSupervisor`:`process_group(0)`;cancel TERM→KILL process group。
- `exec`:`action=run|status|cancel`、`background` 回 `jobId`。
- `write_file.expectedHash` → `CONFLICT`,不覆蓋。
- 測試:`/bin/true` 真 exit;`sleep 30` 可 cancel。
- API 重啟:`computer_jobs` running/accepted → `interrupted`(不假裝收回 Docker PID)。
- `LAZYBOY_NATIVE_JOB_CONCURRENCY`(預設 2)超額 → `QUOTA_EXCEEDED`。
- Artifact:`write_file` 成功寫 `computer_artifacts`;`GET /api/bots/{id}/artifacts` 列表;下載從 Computer `read_file` 串回,sha256 不符 → `CONFLICT`。
O04 PTY `terminal.*`:DEFERRED(見文末)。跨重啟回收仍在跑的容器 PID:標 interrupted,不是 resume。
---
## PR-05:Tool Manager / MCP
狀態:IMPLEMENTED(stdio 不在 API spawn;sample 在 Computer;無第三方商店)
- Manifest:空 digest / zip-slip / 空 entrypoint 拒絕。
- 安裝≠Ready(`AuthRequired` ≠ 可用)。
- `scripts/sample-mcp/echo_server.py` + `manifest.yaml`(本機範例,不是虛構 npm)。
- REST:`GET/POST /api/bots/{id}/tools`、`POST .../tools/call`、`POST .../revoke`。
- 安裝把 echo 寫進 Computer:Dedicated `tools/...`,Team `shared/tools/...`;`python3` 在該容器執行 JSON-RPC。
- Agent 工具 `computer_mcp` 走同一條路徑;未 bind → `NOT_BOUND`。
- 外掛頁「Computer 套件」可安裝/呼叫/撤銷 echo;市集 MCP 文案不再假裝 stdio 都在 Computer。
- MCP `call_for` 先複製 client 再 await;`definitions_for(actor)` 不把別人的工具塞進這次 run。
未做:任意第三方套件商店、OAuth broker。`/api/mcp-servers` stdio **不再** `spawn` 子行程,只存 metadata 並指向 Computer Tool Manager。HTTP/SSE 仍為遠端 client。真實 Microsoft/Google OAuth:`BLOCKED_EXTERNAL`。
---
## PR-06:路由 / verifier / wait
狀態:IMPLEMENTED(決策層)
- `choose_route`:有授權 API 不走 browser;`POLICY_DENIED` 不繞路。
- `CompletionLayer`:transport ≠ effect ≠ task;`wait_counts_as_progress() == false`。
- policy:`wait` 不再刷新無進展時鐘;`exec` 可。
- `wait_until` 有 deadline;未知 disabled 不空轉。
- system prompt:Gmail/Outlook 有 connector 走 API。
---
## PR-07:form / takeover scopes
狀態:IMPLEMENTED(form 工具已註冊;lease/pause 見 PR-02)
- `run_form_macro` 失敗即停、逐步 evidence。
- Display writer lease 原本就有;pause scope 見 PR-02。
- Agent 工具 `form_fill`:用最新 snapshot locator/element id;`run_form_macro` 先定位,第一個失敗就停;可選 `submit` click。
---
## PR-08A Gmail fixture
狀態:IMPLEMENTED(純函式 fixture,不碰真信箱)
- batchModify 同 delta 分組、1000 cap。
- undo 只撤本 plan 的 delta。
- 真實帳號:`BLOCKED_EXTERNAL`。
---
## PR-08B Outlook
狀態:IMPLEMENTED(Graph 語意 fixture,非官方 npm)
- `$batch` 最多 20;子項 429/401 分開。
- categories merge,不覆蓋無關分類。
- `Mail.ReadWrite` 不自動允許寄信。
- 真實 Graph OAuth:`BLOCKED_EXTERNAL`。
---
## PR-09 TigerVNC 候選
狀態:IMPLEMENTED(opt-in,預設仍 Xvfb+x11vnc)
- `DisplayBackend`;slot 0 = `:1` / 5900 / 6080(T57)。
- `lazyboy-screen` 讀 `LAZYBOY_DISPLAY_BACKEND=tigervnc_xvnc`。
- Dockerfile 加入 `tigervnc-standalone-server`,失敗則 script fallback Xvfb。
- Cua/中文/a11y GUI 對照:`BLOCKED_EXTERNAL`,**未改 default**。
---
## PR-10 設定 / 文件
狀態:IMPLEMENTED
`.env.example`:`LAZYBOY_EXECUTION_PROFILE`、`LAZYBOY_DISPLAY_BACKEND`、`LAZYBOY_BROWSER_BACKEND`、concurrency、tool install flags。
`migrations/022_agent_computer.sql`。
---
## O01–O48
| ID | 狀態 |
|---|---|
| O01 | IMPLEMENTED |
| O02 | IMPLEMENTED(短 exec);長 job 見 O04 |
| O03 | IMPLEMENTED + CAS |
| O04 | PARTIAL(Computer 內 background exec + cancel;PTY `terminal.*` DEFERRED) |
| O05 | IMPLEMENTED(form macro + `form_fill` 工具) |
| O06 | IMPLEMENTED(stdio 不在 API spawn;HTTP/SSE 遠端;echo 在 Computer) |
| O07 | DEFERRED(未量測 docker exec 開銷) |
| O08 | IMPLEMENTED(`boot_for(need_gui=false)` 不 ensure_screen;`LAZYBOY_RUNNER_ONLY` 不 boot XFCE) |
| O09 | IMPLEMENTED(router) |
| O10 | IMPLEMENTED(revoke 從下次 schema 拿掉 computer_mcp;stdio 不再進 defs) |
| O11 | 保留現況 |
| O12 | IMPLEMENTED |
| O13 | DEFERRED |
| O14 | IMPLEMENTED(PR-01) |
| O15 | 保留現況(既有 profile bind) |
| O16 | PARTIAL(verifier 層 + wait 不算進度) |
| O17 | 實驗/opt-in |
| O18 | DEFERRED |
| O19 | 保留現況 |
| O20 | 保留現況 |
| O21 | 保留現況 |
| O22–O24 | DEFERRED |
| O25 | IMPLEMENTED/保留 |
| O26 | PARTIAL(既有 screen/profile lease) |
| O27 | IMPLEMENTED(epoch 函式) |
| O28 | PARTIAL(idle 看 runs/skills/jobs;2026-09-10 修正守門原本永遠回 false,並在判斷前 reconcile job 狀態) |
| O29 | IMPLEMENTED(package/binding 分離;共用 bytes 不共享帳號) |
| O30 | IMPLEMENTED(`LAZYBOY_NATIVE_JOB_CONCURRENCY`,預設 2;配額滿時先向 Computer reconcile 未 poll 的 job,不會永久卡死) |
| O31 | DEFERRED |
| O32 | DEFERRED(T59:保留 `-ac` 直到 Xauthority 接到 Cua/AT-SPI;VNC 仍走認證 proxy) |
| O33 | IMPLEMENTED(sample MCP 安裝 REST + UI) |
| O34 | IMPLEMENTED(digest/zip-slip/unpinned npx 拒絕) |
| O35 | IMPLEMENTED(installed ≠ ready;AuthRequired 不可用) |
| O36 | PARTIAL(熱啟用 bindings;無獨立 schema cache 服務) |
| O37 | IMPLEMENTED(0.0.2 旁置、job pin 舊版、rollback/remove) |
| O38 | BLOCKED_EXTERNAL |
| O39–O40 | IMPLEMENTED fixture;真實帳號 BLOCKED_EXTERNAL |
| O41 | IMPLEMENTED(dispatch 寫 operations + outbox;flush 補送) |
| O42 | IMPLEMENTED(errorCode/jobId/operationId) |
| O43 | IMPLEMENTED(列表 + 下載;磁碟 hash 不符 CONFLICT) |
| O44 | PARTIAL(verifier 層) |
| O45 | IMPLEMENTED(CAS + operation id) |
| O46 | IMPLEMENTED(`GET /api/computer/{id}/health`:db/sandbox/display/jobs/operations) |
| O47 | DEFERRED |
| O48 | DEFERRED(無固定環境雙模式 P95;不寫未測倍數) |
---
## T01–T64
| ID | 狀態 | 證據/理由 |
|---|---|---|
| T01 | IMPLEMENTED | `should_deliver_observation_image` |
| T02 | IMPLEMENTED | 換模型/takeover 強制交付(同真值表) |
| T03 | IMPLEMENTED | `pixel_actions_need_vision`;text-only 無圖 |
| T04 | IMPLEMENTED | `typed_browser_errors` |
| T05 | IMPLEMENTED | Team/Dedicated path + echo 路徑測試 |
| T06 | PARTIAL | generation 拒絕舊 job;未覆蓋所有偽造 grant 變體 |
| T07 | 保留現況 | task 無 Docker socket;未加新 sentinel fixture |
| T08 | IMPLEMENTED | zip-slip/`..` path 拒絕 |
| T09 | IMPLEMENTED | invalid UTF-8 binary payload |
| T10 | PARTIAL | 工具錯誤非空成功;磁碟滿未測 |
| T11 | IMPLEMENTED | `stale_hash` CAS CONFLICT |
| T12 | IMPLEMENTED | Supervisor `/exec` 回完整 `CommandResult`;reap 讀 `exit` 檔,`false`/缺檔為 Failed/1 |
| T13 | IMPLEMENTED | Computer-side job_op cancel 走 `computer_cancel_script`(TERM→KILL 整個 process group,驗證後才標 cancelled;2026-09-10 修正 pid 錯誤);status 缺欄位當 TRANSPORT,不發明 running |
| T14 | DEFERRED | API 重啟標 interrupted,不 resume 容器 PID |
| T15 | IMPLEMENTED | generation interrupt |
| T16 | IMPLEMENTED | operation_id hash/mismatch;2026-09-10 第二輪:key 改 per-bot、in-flight 同 id 回 `UNKNOWN_EFFECT`、傳輸失敗不存成 succeeded(sqlx 測試 `ledger_is_per_bot_and_does_not_rerun_in_flight_work`) |
| T17 | IMPLEMENTED | outbox flush 補送且不重複(sqlx 測試 `outbox_flush_does_not_duplicate_recorded_operations`;2026-09-10 修正 `SELECT 1` 解碼 bug) |
| T18 | IMPLEMENTED | `journal_failure` 拒 mutation |
| T19 | IMPLEMENTED | actor-scoped MCP;stdio 不在 API 持有 client |
| T20 | PARTIAL | NOT_BOUND/revoke;無真實 token revoke |
| T21 | IMPLEMENTED | `canary_is_stripped`;2026-09-10 第二輪:ledger/snippet 實際套用 `redact_secret_patterns`(之前傳空 canary 等於沒脫敏) |
| T22 | 保留現況 | 既有 prompt 邊界;無新 injection corpus |
| T23 | PARTIAL | 同 display lease 既有;Cua+worker 未雙開 fixture |
| T24 | PARTIAL | pause/takeover;HTTP in-flight barrier 未單獨測 |
| T25 | IMPLEMENTED | wait 不算 milestone |
| T26 | IMPLEMENTED | Gmail batchModify fixture |
| T27 | IMPLEMENTED | fixture 429/partial;真實帳號 BLOCKED_EXTERNAL |
| T28 | IMPLEMENTED | undo 只撤本 plan |
| T29 | IMPLEMENTED | 無強制拆 Team;migration 不 UNIQUE computer_id |
| T30 | IMPLEMENTED | `boot_for(need_gui=false)` 不 `ensure_screen`;`maybe_ensure_screen` + FakeSandbox native write/read/exec;`start.sh` runner-only 不 boot XFCE |
| T31 | PARTIAL | job 配額;無壓力 bench |
| T32 | DEFERRED | 無郵件分類品質集 |
| T33 | PARTIAL | broker 不進 generic env;無任意 exec 讀 socket fixture |
| T34 | IMPLEMENTED | artifact 從 Computer 下載 |
| T35 | PARTIAL | CAS/operation replay;resume 不重播未全測 |
| T36 | PARTIAL | mutating tools 走 ledger;MCP stdio 不再是旁路 spawn |
| T37 | IMPLEMENTED | `team_computers_share`;migration 註解+測試 |
| T38 | IMPLEMENTED | Team `bots/<id>/` 路徑 |
| T39 | IMPLEMENTED | shared/ + CAS |
| T40 | PARTIAL | 多 slot 契約;無雙 writer GUI fixture |
| T41 | PARTIAL | display writer lease 既有 |
| T42 | IMPLEMENTED | pause scope 停 A 不 bump B |
| T43 | PARTIAL | idle 看 jobs;整機 pause 未單測 recreate 全集 |
| T44 | 保留現況 | 既有 ensure-screen single-flight |
| T45 | IMPLEMENTED | Dedicated 刪 bot;Team Computer 存活 |
| T46 | IMPLEMENTED | echo 安裝/呼叫 `executionLocation=assigned_computer` |
| T47 | IMPLEMENTED | binding per bot;套件 per computer |
| T48 | IMPLEMENTED | digest/zip-slip/unpinned npx |
| T49 | IMPLEMENTED | `plan_version_switch` job pin |
| T50 | IMPLEMENTED | revoke/remove;revoke 掉 schema |
| T51 | IMPLEMENTED | Outlook fixture;非官方 npm |
| T52 | IMPLEMENTED | Graph 子項 429/401 |
| T53 | PARTIAL | fixture 分頁語意;真實 delta BLOCKED_EXTERNAL |
| T54 | IMPLEMENTED | categories merge 不盲覆蓋 |
| T55 | BLOCKED_EXTERNAL | 無 Cua/中文/a11y GUI |
| T56 | PARTIAL | generation bump;backend 熱切未做 |
| T57 | IMPLEMENTED | slot 0 = :1/5900/6080 |
| T58 | 保留現況 | 既有 noVNC;無新 DPI fixture |
| T59 | DEFERRED | 保留 `-ac`;VNC 走認證 proxy;Xauthority 未接到 Cua |
| T60 | IMPLEMENTED | 同 T30:native 路徑不 attach viewer;GUI 之後才 `lazyboy-screen ensure` |
| T61 | IMPLEMENTED | 未審查 env token/unpinned npx 拒絕 |
| T62 | 保留現況 | 同 UID 不宣稱強隔離 |
| T63 | PARTIAL | job 配額;renderer OOM 未測 |
| T64 | PARTIAL | 套件路徑可重建;舊 job 不裝活 |
## 2026-09-10 複查:Bug 修正
本輪先跑計劃第 16 節的完整 gate,再修實際發現的 bug;未新增功能。
驗證命令與結果(本機,拋棄式 Postgres `pgvector/pgvector:pg16` 於 `127.0.0.1:5434`):
- `cargo fmt --all -- --check`:原本有 diff(`artifacts.rs`、`computer.rs` 等新檔未格式化)→ 已 `cargo fmt --all`,現在 exit 0。
- `cargo clippy --workspace --all-targets -- -D warnings`:exit 0。
- `DATABASE_URL=… cargo test --workspace`:**全部通過**(api 157 passed / 3 ignored;control 150;harness 37;contracts 7;sandbox 7;supervisor 2)。前次紀錄的「sqlx 未連」現在已實跑;14 個 sqlx 測試(db/memory/monitor/routing::fan_out)全綠。
- `make test-agent-computer`:exit 0。
- `apps/web`:`npm run build`(`tsc --noEmit` + vite)通過。
修正:
1. **`lazyboy-api` 測試 binary 結束時 SIGABRT**(`ort` `Mutex poisoned` → `release_env_on_exit` 二次 panic)。根因:`memory.rs` 用 `catch_unwind` 包住 `TextEmbedding::try_new`,但 `ort` 找不到 `libonnxruntime.so` 時是在持有全域鎖的情況下 `expect` panic,鎖被 poison 後程序退出時 `.fini_array` hook 再 panic → abort。正式環境沒設 `ORT_DYLIB_PATH` 時,API 正常關機也會變 exit 134。修法:`memory.rs` 新增 `preload_onnx_runtime()`,先走 `ort::init_from(path)`(回 `Result`)預載 dylib,失敗即標 `Unavailable` 不再進 fastembed;`crates/api/Cargo.toml` 直接依賴同版 `ort ="=2.0.0-rc.13"`(lock 無新增 crate)。測試 `database_enforces_agent_scope_and_queries_do_not_leak` 改成顯式 `ModelState::Unavailable`,不再靠環境缺 ONNX 來觸發;新增 `a_missing_onnx_runtime_is_an_error_not_a_panic`。反向驗證:`ORT_DYLIB_PATH=/nonexistent cargo test -- --ignored memory_model_recovers_after_cache_failure` 只因無模型 fail,**不再 abort**。
2. **Computer 內 background job 的 pid 是錯的(T13 process-tree cancel 實際上沒生效)**。`computer_background_launch` 原本是 `mkdir -p … && setsid nohup sh … & echo $!`,`&` 作用在整個 `a && b` list,`$!` 是 bash 子 shell 的 pid,不是 `setsid` 後的 session leader。後果:`status` 只是碰巧能用(子 shell 會 wait);`cancel` 只殺子 shell,真正的 `sh`→子程序變孤兒繼續跑,之後 `status` 讀不到 exit 檔還會誤報 `failed`。修法:`mkdir` 獨立一行;wrapper 先 `printf %s "$$" > …/pid`,launcher 等 pid 檔再輸出(`setsid` 若 fork 也正確)。新增 `computer_cancel_script(pid)`:`kill -TERM -- -pgid` → 最多 1s 等待 → `kill -KILL -- -pgid` → 再驗證;exit 0 才算整棵樹已消失。`supervisor/docker.rs` cancel 改用它,且只有樹確實消失才把 job 標 `cancelled`(否則維持 `running`,不假稱取消)。測試:`computer_cancel_script_kills_the_whole_process_tree`(本機真起 `sh -c 'sleep 300; sleep 300'`,用 `pgrep -g` 驗證子 `sleep` 一起死);另在真實 Team 容器 `lb-team-space-c7bace…` 內手動跑 launcher+cancel:pid=pgid=sid,`tree gone`。
3. **supervisor `job_follow_up` 沒檢查 job 屬於哪個容器**:拿到別的 Computer 的 jobId 時會在自己容器對同號 pid 送 `kill`。現在 `job.computer_id != container` 一律回 `unknown job`。
4. **`Makefile` `help` 目標壞掉**:新增的 `test-agent-computer` 說明被塞進同一個 `@echo "…` 造成跨行未閉合字串,`make help` 回 `Unterminated quoted string` Error 2。已拆成兩行 `@echo`。
5. **idle reaper 的「有工作就不暫停」守門一直是死的(HEAD 既有 bug)**:`computer_has_active_work` 把 `SELECT 1`(INT4)解成 `(i64,)`,sqlx 回 `ColumnDecode mismatched types`,被 `matches!(Ok(Some(_)))` 吞成 false → 只要 `computers.updated_at` 十分鐘沒動,正在跑 run 的 Computer 也會被 suspend。改成 `sqlx::query(...).fetch_optional` 只看有沒有列。sqlx 測試 `idle_reaper_reconciles_unpolled_jobs_with_the_computer` 同時覆蓋 run 與 job 兩種活性。
6. **`flush_outbox` 去重失效(T17 宣稱的「不重做」實際沒生效)**:同樣的 `SELECT 1`→`i64` 解碼錯誤讓 `exists` 永遠 None,每筆 outbox 都再寫一次 `run_activity`,Activity 出現重複 tool 事件。已改為列存在檢查;查詢出錯時保留 outbox 列到下一輪,不猜。新增 sqlx 測試 `outbox_flush_does_not_duplicate_recorded_operations`。
7. **背景 job 配額會永久卡死**:`computer_jobs.status` 只在 agent 呼叫 `exec status` 時更新;兩個跑完但沒人 poll 的 job 會讓該 Computer 之後永遠 `QUOTA_EXCEEDED`,idle reaper 也永遠以為有工作。修法:`tools.rs` 配額看起來滿時先 `refresh_running_jobs`(向 Computer 問最舊 16 筆 running 的真實狀態;supervisor 回 `unknown job` 標 `interrupted`)再重算;`computer.rs` 的 `pause_idle_computers` 在判斷活性前做同樣的 reconcile(suspended 容器無法問,`stop_parked_computers` 不做)。
未修、已知:
- host 端 `JobSupervisor::start`(`crates/control/src/jobs.rs`)用 piped stdout 但只在子程序結束後 `read_to_end`;輸出超過 pipe buffer(~64 KiB)的子程序會永久阻塞。目前正式路徑不用它(背景 job 全在 Computer 內,`exec_background_does_not_spawn_on_the_api` 有守門),僅測試使用;若之後要在 API 行程內跑 job 必須先改成邊跑邊 drain。
- supervisor 的 `ComputerJobTable` 在記憶體;supervisor 重啟後舊 jobId 回 `unknown job`(API 側已把 `computer_jobs` 標 interrupted,屬同一限制,T14 仍 DEFERRED)。
回滾:還原 `crates/api/src/memory.rs`、`crates/api/Cargo.toml`(ort 直接依賴)、`crates/api/src/tools.rs`(refresh_running_jobs)、`crates/api/src/computer.rs`、`crates/api/src/operations.rs`、`crates/control/src/jobs.rs`、`crates/supervisor/src/docker.rs`、`Makefile` 本節改動即可;無 migration 變動。
## 2026-09-10 複查(第二輪):三路平行 code review 後的修正
三個獨立 review(ledger/outbox/artifacts;jobs/tool-manager/install;雙模式/pause/前端)共回報 40 餘項;重複、已在上一節修掉的略過。以下每項都有測試或反向驗證。gate:`cargo fmt --check`、`clippy -D warnings`、`cargo test --workspace`(api 162/control 154/harness 37)、`tsc --noEmit` 全綠。
**Ledger(T15–T17,`operations.rs` 重寫)**
1. **operationId 全域命名空間 → 跨 bot replay/拒絕**:ledger key 改為 `{bot_id}:{operation_id}`,查詢加 `bot_id` 條件。Bot B 選到 Bot A 用過的 id,不會拿到 A 的結果,也不會被 `PAYLOAD_MISMATCH` 擋。
2. **記憶體 `LEDGER` 拆掉**:與 DB 兩份狀態不一致(一邊 accepted 一邊沒列)、無上限增長(連非 mutating tool 都建列)。Postgres 是唯一真相;`OperationLedger` 只留 control crate 單元測試用。
3. **`bot_computer_id` 查詢失敗被當「沒有 Computer → Proceed」**:現在 DB 錯誤 = `JOURNAL_UNAVAILABLE`;不需要 Computer 的 mutation(remember/schedule)以 `computer_id = NULL` 入帳(migration 023 放寬 NOT NULL)。
4. **同 id 同 payload 的 `accepted` 列直接 Proceed → 崩潰後重試會做第二次**:新增 `Begin::InProgress` → 工具回 `UNKNOWN_EFFECT`,要求先讀回狀態、換新 id 重試。
5. **`finish` 對非 mutating tool 也寫 DB/outbox**:提早 return。`exec status` 改為非 mutating(`exec run/cancel` 仍是)。
6. **`"xxx failed: …"` 的傳輸失敗被存成 `succeeded` 並永遠 replay**:`outcome_status` 辨識這類文字 → 刪除 accepted 列讓同 id 可重試;不寫入結果。
7. **replay 丟掉 `pause`**:`Begin::Replay` 帶回 `pause`。
8. **outbox 寫 `run_activity` 失敗仍標 delivered**:新增 `monitor::try_record_pool`,只有成功才標;且只處理 `created_at` 早於 2 秒的列,避免與 `runs.rs` 的直接寫入賽跑而重複。
9. **`redact_text(&text, &[])` 是 no-op**:新增 `redact_secret_patterns`(Bearer、`password=`/`token:`、`sk-`/`ghp_`/`AKIA`/`xox`/`AIza`、PEM 私鑰、JSON secret key),ledger 結果與 activity snippet 都經過它。control crate 新增 `regex` 直接依賴(lock 已有)。
10. retention 新增 `operation_outbox`(delivered,7 天)與 `computer_operations`(非 accepted,90 天)。
**Jobs(T13/T14)**
11. **runner-only 容器 PID 1 是 `sleep infinity`,不收屍**:setsid 後的 wrapper 結束會變 zombie,`kill -0` 仍成功 → job 永遠 `running`。修:`HostConfig.init = true`(docker-init 收屍);`status` 改先讀 exit 檔、再用 `computer_alive_script`(看 `/proc/*/status` State,Z/X 視為死)。測試 `computer_alive_script_treats_a_zombie_as_dead` 本機造真 zombie 驗證。
12. **launcher 沒吐出 pid 時仍登記 job(pid None)→ 永遠 running、不能 cancel**:現在直接回錯誤,不登記幽靈 job。
13. **`exec status/cancel` 不看歸屬與世代**:新增 `job_ownership`:別的 bot 的 job → `UNKNOWN_JOB`;`generation` 小於目前 Computer 世代或已 `interrupted` → 直接回 interrupted,不去問新容器;`persist_job` 的 upsert 不再把 `interrupted` 蓋回 `running`。
14. **`pin_running_jobs` 把同 Computer 上其他 bot 的 job 也 pin 了**:加 `bot_id` 條件。
**Tool Manager(T18–T21)**
15. **revoke 後重裝撞 `UNIQUE(package_row_id, bot_id)` → 500**:`ON CONFLICT … DO UPDATE SET status='ready' RETURNING id`;切版本走 `switch_binding`(同 tx 內先清掉同 (package_row, bot) 的非 ready 殘留)。
16. **rollback 會把 `revoked` binding 復活**:非 `ready` → 409。
17. **執行前不驗 digest**:`verified_run_argv` 在同一條命令內 `sha256sum -c` 通過才 `exec python3`;不符 exit 97 → `PACKAGE_TAMPERED`。測試 `tampered_package_is_refused_before_it_runs` 真改檔驗證。
18. **`validate_manifest` 只檢查非空**:現要求 64 hex;`compute-at-install` 佔位會被拒。
19. `InstallBody` 只吃 `package_id`、前端送 `packageId`:serde `rename` + `alias` 兩者皆收。
20. `refuse_unpinned_npx` 把 `@scope/pkg` 的 `@` 當版本 pin:修為去掉 scope 前綴再判斷。
**其他**
21. **`form_fill` 靜默截斷**:>20 欄或欄位缺 name/locator → `INVALID_ARGUMENT`,不再填半張表回報成功。
22. **`looks_like_css` 誤判 `v1.2 Release`/`C++ Guide` 這種標題為 CSS**:click/type 先做精確唯一標題比對;找不到才依 locator 形狀決定 `SELECTOR_UNSUPPORTED`/`TARGET_NOT_FOUND`/`TARGET_STALE`,並各給對應說明。
23. **`computers.display_backend` 從未寫入**:boot 轉 running 時寫入 `DisplayBackend::from_env()`;health 不再永遠回欄位預設值。
24. **`delete_bot` 不清 `tool_bindings`/`computer_jobs`/`computer_operations`/`computer_artifacts`**:同 tx 一起刪(Team Computer 的配額不再被已刪 bot 佔用)。
25. **`wait` 在 `PROGRESS_WHEN_REPEATED` 內,與本文件「wait 不刷新無進展時鐘」相反**:改成 heartbeat 語意 — `wait` 只更新 `last_heartbeat_turn`(不會被當 loop halt),但 stale 教練訊息照常觸發;`polling_a_build_or_a_queue_is_not_treated_as_spinning` 同時鎖住兩半契約。
26. **Gmail fixture 沒有真的 undo**:新增 `undo_plan(before, batches)`,只還原本次計畫實際改動的 label(原本就有的不拔、原本沒有的不補、人工新加的不動),測試做 plan→undo 往返驗證。
27. 前端:`RunActivityEntry.operationId` 補型別;`runs.rs` 從工具回傳 JSON 抽 `jobId` 寫進 activity,monitor 原有的 `entry.jobId` 顯示終於有值;並顯示 `op <短 id>`。
**Migration**:新增 `migrations/023_agent_computer_fixes.sql`(`computer_operations.computer_id` DROP NOT NULL、移除多餘 `computer_operations_id_hash`、outbox pending 部分索引、`computer_operations(bot_id, created_at)` 索引)。不改 022(已可能套用過)。
**review 提到但本輪不動、記錄在此**:
- Pause scopes/`resume_after_pause`/`route_operation` 有純函式與測試但沒接進 runs 主迴圈;takeover 實際只有 GUI 全暫停,沒有 scope 級細分(T22–T24 應視為 PARTIAL,不是 DONE)。
- `write_file` 走 `bash -lc` 字串插值路徑(HEAD 既有);`normalize_workspace_path` 有限制字元,本輪未改成 argv。
- supervisor `ComputerJobTable` 仍在記憶體(T14 DEFERRED 不變)。
回滾:還原本節列出的檔案,並 `DELETE FROM _sqlx_migrations WHERE version=23` 後 `ALTER TABLE computer_operations ALTER COLUMN computer_id SET NOT NULL`(若已無 NULL 列)。
## 誠實未完成
P0/P1 可在本環境落地的已接上。下面不是「做完」:
1. 真實 Gmail/Outlook OAuth/主信箱:`BLOCKED_EXTERNAL`。
2. TigerVNC 當預設 + Cua/中文/a11y GUI:`BLOCKED_EXTERNAL`;預設仍 Xvfb+x11vnc。
3. Postgres sqlx 整合測試此環境未連。
4. **O04 PTY `terminal.*`:DEFERRED** — 非互動走 `exec`+jobs+process-tree cancel;TUI 協作仍用 GUI `shell`。不假裝有 PTY。
5. 跨重啟回收容器內仍在跑的 PID:標 interrupted,不是 resume。
6. 任意第三方套件商店、完整 OAuth broker:沒有憑證。
7. O48 無未測速度倍數。
8. 外掛 UI 未在瀏覽器點過(無瀏覽器工具);已 `tsc --noEmit`。
雙模式:Team/Dedicated 契約不變;**沒有** UNIQUE `bots.computer_id`。stdio MCP **不是** API 子行程。
回滾:還原本輪 crate / migration 022 / lazyboy-screen `-ac` 註解 / sample-mcp / 前端 plugins+i18n。不要 force push。
## 2026-09-10 第三輪:完整計劃核對與實際修復
起始 HEAD `80fe9a8`,工作目錄乾淨;未部署、未操作真實信箱、未刪除既有 Team 資料。
逐項狀態:[O01–O48 / T01–T64 稽核](agent-computer-audit-2026-09-10.md)。**不是全案完成報告。**
### 實際 diff
1. `crates/control/src/jobs.rs`:stdout/stderr 從開始就同時 drain;各保留 1 MiB 並標截斷,避免 pipe 塞滿令命令永遠無法退出。拒絕重複 job ID,避免覆蓋舊 child handle。`poll` 不再跨 await 暫時拿走 child,消除與 cancel/interruption 的競爭窗口。
2. 同檔 `posix_shell_join` 保留空參數;含中文、換行、單引號、`$()`、反引號的真實 shell round-trip 測試。取消拒絕 PID 0/1,Computer cancel 排除 zombie;Supervisor 對已結束 job 不再發 signal,以免 PID 回收後誤殺。
3. `tools.rs`:job ownership 在 DB error/missing row 時 fail closed,核對目前 bot assignment、Computer generation 與 job owner,不再把 unrecorded job 直接轉送。
4. `tools.rs`:Computer row lock + `accepted` reservation 同 transaction 執行配額判定,兩 agent/API worker 搶最後一格只有一個成功。DB admission 失敗時不開始命令;launch 結果不完整不假報 running,回 `UNKNOWN_EFFECT` 並保留 reservation。
5. `runs.rs`:移除 worker 啟動時把全 DB running/accepted jobs 一律改 interrupted 的 UPDATE;API 重啟不代表 Computer 程序死亡。Supervisor registry 持久化仍未完成。
6. `computer_files.py`(**只在 Computer 執行**):read/list/write 以 argv 傳路徑、stdin 傳 bounded base64,不把檔名或整份內容拼入 shell/argv。使用 directory descriptor + `O_NOFOLLOW`/`O_NONBLOCK`、regular-file 判斷,拒絕 symlink/FIFO/路徑逃逸與超過 8 MiB 的內容;缺目錄不再回空成功。Supervisor file route 允許最多 12 MiB JSON 以容納 base64,超過 raw bytes 限制回 HTTP 413。
7. `SandboxProvider::write_file_checked` → Docker transport → Supervisor → helper:expected hash、directory flock、temporary file、fsync、atomic rename 在 Computer 同一程序完成,取代 API 先 read 再 write 的假 CAS。空 expectedHash 表示只可建立;unconditional write 需省略 expectedHash。維持原有檔案 executable permission。
8. `path.rs`:controlled Team fs 只能指向 own `bots/<id>` 或 `shared/`,拒絕其他 bot、相似前綴 bot ID 及 virtual-root 旁路。這不是同 UID arbitrary shell 的 OS 隔離。
9. `supervisor/docker.rs::exec_raw`:stdin writer 與 output reader 同時執行,修大量雙向 IO 死鎖;job 檔案讀取不再把傳輸/權限錯誤吞成空字串。
10. `operations.rs`:傳輸失敗保留 accepted/unknown 去重紀錄,不能刪 intent 讓相同操作重做。結果與 outbox 同 transaction,任何一側失敗回滾,dispatch 回 `UNKNOWN_EFFECT`,不宣稱已可靠記錄成功。
11. 同檔:外部 `mcp_*` 與 `connection_check` 也需副作用前 journal;MCP error 回 typed unknown。**HTTP/SSE MCP client 仍在 API,本輪沒有把它搬完。**
12. `tool_install.rs`:digest 檢查後直接 compile/execute 同一份 bytes,不重新開啟可被替換的 pathname;Python `-I`,拒絕 symlink/non-regular/超限內容。binding 查詢核對當前 Computer;boot 後重讀 bot,避免初次 provision 用到過時空 assignment。
13. `tests/frontend.test.mjs`:更新一個已過時的描述文字比對,仍驗證 observe boolean/default false 與實際 opt-in 條件;未刪除測試。
14. 新增 `tests/computer-files.test.py`、真實 Rust→Docker opt-in regression、`make bench-agent-computer` 與原生雙模式 benchmark JSON。所有測試容器皆新建、無持久 volume、無公開 ports;既有服務不受影響。
### 本輪驗證
- `cargo fmt --all -- --check`:通過。
- `cargo clippy --workspace --all-targets -- -D warnings`:通過。
- `DATABASE_URL=postgres://lazyboy:lazyboy@127.0.0.1:5434/lazyboy cargo test --workspace`:374 passed、0 failed、4 ignored;包含 disposable sqlx DB 測試。3 個原有外部/模型 fixture ignored,新增 Docker fixture 預設 ignored 並於下列命令實際執行。
- `make test-agent-computer`:通過(另含 Python filesystem gate)。
- `node --test tests/frontend.test.mjs`:56 passed。
- `cd apps/web && npm run build`:通過;既有 lottie eval/chunk-size warning,非新增編譯錯誤。
- `python3 tests/computer-files.test.py`:5 passed;含 8 個並行 CAS、1 MiB binary、特殊檔名、symlink/特殊檔案拒絕、大小限制、permission preservation。
- `LAZYBOY_PLAN_TEST_CONTAINER=lazyboy-plan-files-fixture cargo test -p lazyboy-supervisor real_computer_files_cas_and_bidirectional_exec -- --ignored --nocapture`:在新建 `lazyboy/computer:local`、UID 1000、無網路、無 desktop 容器通過;驗證真實 Docker transport、特殊路徑、binary、CAS、1 MiB 雙向 IO、background job 非零退出/完整輸出/取消/terminal job 重複取消。
- `python3 scripts/bench-agent-computer.py`:shared 60/60、dedicated 30/30;[完整樣本與環境](benchmarks/agent-computer-native.json)。測的是 native helper/read/write/hash/exec,非完整 API/model/viewer E2E,沒有提速倍數或 GUI 品質宣稱。
### 尚未完成與 checkpoint
核心未完成:受保護且持久的 Computer Runner/PTY/job cursor;Computer 本地 durable outbox;scope-aware pause/fencing 接線;generic MCP loader/常駐 instance/HTTP client 搬遷;per-agent/account OAuth broker;Gmail/Outlook fake HTTP 與可安裝 adapter;task completion verifier;完整 GUI/TigerVNC/viewer benchmark。accepted launch outcome unknown 的 reservation 保守保留,仍需持久 Runner reconcile;不能手動猜它沒執行而自動清除重試。取消尚未完成 PID starttime identity 全集。
實際缺程式的項目標 PARTIAL/NOT_STARTED,不假借 OAuth 帳號缺失標 BLOCKED_EXTERNAL。真正 Microsoft/Google 帳號、外部模型的 E2E 另列 BLOCKED_EXTERNAL;不索取明文密碼,不使用主信箱。詳細每個 Oxx/Txx 見稽核表。
回滾:還原上述本輪 diff 與新增 helper/測試/benchmark 檔即可;本輪無 migration。`write_file_checked` 涉及 API、control trait、兩個 sandbox provider 與 Supervisor,需一起回滾,不能只留半套 transport。
## 範圍追加:P0–P3 全項完成
使用者明確要求 P0~P3 每項完成。先前可選/延期項目全部恢復待辦,包括 transport/DOM 增量、TigerVNC/x0vncserver/XDamage/viewer tuning、Selkies、Xpra、Wayland、shm/OOM、skill 重用與完整 benchmark。互斥 backend 採可選配置,不以一律替換預設理解;每個選項都需實作與驗證,不能只交不採用理由。
## Runner 與中央事件同步 checkpoint(2026-09-10,後續進度)
此節更新前一節尚缺項目,不表示 P0–P3 已完成。
- 新增 Computer 內 root 保護的 SQLite job registry、outbox、每工作獨立 worker/socket/output。實際命令降權至 UID/GID 1000、清空繼承環境,不繼承控制 token。
- 新增真正 PTY start/interact/resize、精確 binary output cursor、pause/resume/cancel、runtime deadline、4 MiB/stream 上限;完成需等待程序樹與 pipe EOF。重複取消不延後 KILL,逾時/超限清理中不提前回報 terminal。
- 修完成時 socket 關閉被覆寫 unknown 的競爭。終態不因 Computer reboot 改為 interrupted;管理 reconcile 也需正確 generation。
- API 每 5 秒獨立同步 root outbox,不依賴 viewer 或 model turn。migration 024 加入 job run_id 與中央 computer_job_events;去重、owner/generation/provider 檢查、job 狀態與 activity 在同一 transaction,commit 後才 ack。API 多 worker 以 job row lock 保持事件順序;輸出、argv、程序 metadata 不寫 activity。
- 新增 `make test-runner-jobs`:自動建立無 network、無資料 volume 的 disposable Computer,執行 6 項 Python Runner integration 和真實 Rust→Docker transport 測試,結束清除。包括建立新的 DockerHost 後工作仍可取消、本地事件讀取/ack。
- 已通過:6 個 Python Runner tests、1 個真實 Rust Docker test、中央事件 SQLx dedup/scoping/order regression。workspace 375 passed、4 ignored(Docker ignored test 另已實跑);clippy all targets 通過。最後額外 argv 型別/NUL 驗證與同步 timeout 變更亦已重跑 workspace 375 passed、4 ignored 與 clippy 通過。
仍需完成:worker crash/orphan 回收與 PID/starttime 全集、root 輸出/事件 retention(現為 64 jobs 硬上限)、root scope barrier、broker/grants、通用 MCP/常駐 client/郵件 adapter、完整 files API、task verifier、全部 P2/P3 backend 與 benchmark。此段未部署,migration 僅在 disposable SQLx test DB 驗證。
## Runner 回收與輸出保留 checkpoint(2026-09-10)
- 終態輸出保留 24 小時;達 64 筆保留上限時提前回收最舊、已中央 ack 的終態輸出。未送達事件、執行中工作、unknown 不會被回收。先 commit output_expired tombstone 才刪輸出,重送 start 不重執行;output 查詢明確回 OUTPUT_EXPIRED。已 ack outbox 列會清除。
- worker crash 後用受保護 PID/starttime + pidfd 驗證與凍結程序群,回收後標 interrupted,不捏造成功或原本 exit code。starttime 不符拒絕發訊號。無 viewer 時也由 events maintenance 偵測/回收。
- worker 本身也記錄 starttime,避免 PID 重用誤認存活;啟動 worker 先登記身分,已變 unknown 的 intent 不會稍後重新啟動。
- `make test-runner-jobs`:9 個 Python 實測 + 1 個 Rust→Docker 實測通過,新增過期輸出/ack 保護/冪等、64 slots 配額壓力、kill worker/偽造 starttime/自動維護故障注入。workspace 375 passed、4 ignored;clippy all targets 通過。
- 仍未完成:脫離 process group(setsid)的程序隔離/回收、根 journal/tombstone 的長期磁碟配額、scope barrier 與原稽核表其他項。P0–P3 全範圍保持未結案。
## Agent takeover barrier checkpoint(2026-09-10)
- Runner 新增 root SQLite agent barrier;先停止新 job admission,再停止並確認存活 process group 進入 stopped 狀態。accepted/cancelling/unknown 未處理完不回成功。直接 resume/interact 不能繞過暫停;release 僅恢復 scope 暫停的工作,不恢復原本手動暫停的工作。
- API takeover 在授予 user lease 前要求 Runner barrier 成功。migration 025 新增中央 agent_mutation_barriers,操作 journal admission 與暫停共用 per-agent advisory transaction lock;中央仍有 accepted/unknown 操作就拒絕宣稱接管完成。
- takeover/release 另以 per-agent advisory lock 防止互相交錯;release 先釋放 user lease、恢復 Runner,再打開中央 admission 與原 run。
- FakeSandbox 也模擬相同 agent job admission/status barrier,不靠無條件成功掩蓋測試。
- 驗證:10 個 Python Runner 實測 + 1 個 Rust→Docker 實測通過;包含 A 暫停、B 繼續、新工作/直接 resume 拒絕、原本手動暫停保持。SQLx 新增 admission 與 pause race、跨 bot、unknown 保留測試。workspace 376 passed、4 ignored;clippy all targets 通過。
- 未完成:display/整機 scope barrier、完整 epoch token 與 stale ref/grant 恢復、外部 HTTP read-back reconciliation、脫離 process group 的 OS 隔離。此段只完成 agent/job 與中央受記錄 mutation admission 的接線,不能視為 O27/T24/T42/T43 全部驗收。
## HTTP MCP client 搬入 Computer checkpoint(2026-09-10)
- API `McpHub` 只保留授權目錄/config/schema,移除 API 內的 rmcp HTTP transport 與 stdio client feature;實際探索/呼叫透過 Supervisor→root Runner→Computer 內固定 `lazyboy-controld --mcp-call`。模型呼叫使用當前 bot 的 Computer/generation;目錄探索使用該 owner 已運行的 Computer。
- `mcp_*` 需要 Computer readiness,但不需要 GUI/viewer。沒有可用 Computer 時連線明確失敗,不回退至 API 主機 HTTP。舊 image 必須更新 controld binary;本輪建置了既有 Bookworm controld build stage,未替換運行中的 Computer。
- 配置/headers 只從 stdin 傳給 root client,錯誤不回傳遠端敏感 body。45 秒 deadline、4 MiB 結果/SSE event 限制、256 MiB process address-space limit、CPU/core dump 限制。`isError` 不記為成功。
- 檢查 rmcp 3.2.0 原始碼發現預設 expired-session 會重送 POST,已明確關閉 `reinit_on_expired_session`;session expired/response lost 回 unknown,不隱式重播 mutation。
- 修正 `npx` pin policy:拒絕 latest/next/range/不完整版本與另一參數假冒 pin;只採實際執行的 npm package 精確版本,排除 URL/不明 option 形式。這不取代 digest/審核安裝流程。
- 驗證:Bookworm release binary 在 disposable、network-none、無 desktop Computer 內跑真實 rmcp SDK + loopback HTTP fixture,5 項涵蓋 schema/Unicode call/auth secret canary/isError/session expiry/response lost 單次呼叫/超限結果。另 10 個 Runner tests + 1 個 Rust→Docker test 通過。workspace 376 passed、4 ignored;最後 pin policy 2 測試與 clippy all targets 通過;frontend 56 passed。
- `make test-runner-jobs` 現在自動透過 image 的 controld build stage 取得相容 binary。可設定 `LAZYBOY_TEST_MCP_IMAGE` 使用已建置的該 stage;不直接把開發主機較新 glibc binary 當成 Computer 驗收。
- 仍未完成:generic stdio loader/版本 pin instance、常駐 MCP pool、legacy SSE transport、per-agent/account grants/broker、MCP 本地持久 journal/artifact 與全面管理 audit、schema readiness 自動恢復與完整 API E2E。P0–P3 保持全範圍未結案。
## 通用 archive/stdio MCP checkpoint(2026-09-10)
- 新增 `/api/bots/{id}/tools/install`,從 Computer 內既有 workspace archive 讀取、驗證 digest、解包與探索 MCP schema。migration 026 保存 manifest,先記錄 installing intent,完成後才 ready;沒有 API 主機下載/解包 task artifact。
- 新增 `computer_mcp` list/discover/call,依 actor、bot、當前 Computer、ready binding、manifest version/digest 檢查。SDK 握手及工具呼叫真正使用 Computer 內 stdio child,不只對固定 echo script 丟單行 JSON。
- 安裝器拒絕 symlink/hardlink/特殊檔/路徑逃逸、錯 digest、超限展開、未知 manifest 權限/hook 欄位;不可把同版本換成另一 manifest。套件檔案/權限在每次執行前重新驗證。
- 每個 binding 配獨立 UID/GID、私人 HOME/state,setpriv 清除 supplementary groups 並啟用 no-new-privileges。私有套件程式碼另有 root-owned、僅該 binding group 可讀的唯讀副本;共享 immutable code 不混入私人 state。
- 每呼叫結束清理該 UID 的程序,包含 setsid 與 non-dumpable 子程序。使用 `/proc/status` 的真實 effective UID,而非會被 dumpable flag 改變的 `/proc` inode owner;訊號使用 pidfd 避免 PID 重用。
- Root package revoke 與既有 revoke/remove API 接線;先關 admission,不能確認在途 call drain 時回報 pending。版本/hash 與 root binding 不符拒絕執行,避免 legacy rollback 誤用另一版 code。未執行的參數/授權拒絕和真正未知效果分開,避免簡單錯誤污染 unknown ledger。
- 補安裝配額在寫檔前檢查、獨立安裝鎖、同 manifest/binding 遺留 staging 清理與 failed DB commit 後的 hash/permission 驗證恢復。UID 先 durable reserve 再配置檔案 owner,不重用失敗安裝的 UID。
- 驗證:377 workspace tests passed、4 ignored;SQLx 新增 owner/binding/current-Computer/revoke 可見性測試。10 Runner + 5 HTTP MCP + 4 archive/stdio + 1 Rust Docker = 20 個容器測試通過;覆蓋未知欄位、digest、symlink/hardlink、路徑逃逸、展開 quota、版本 pin、撤銷、私有 code/state 與 non-dumpable/setsid 回收。frontend 56 passed、clippy all targets 通過。
- API contract 與目前限制見 [tool-packages.md](tool-packages.md)。仍缺完整 V1 manifest/遠端 URL/通用 UI、第三方 package fixture、常駐 pool、network/account grants/broker、完整 OS filesystem/network sandbox、state 磁碟 quota/長期 GC、通用重新授權與 rollback、全 API 安裝 E2E。這些仍是 P0–P3 未完成範圍,不以本輪 fixture 取代全部驗收。
## 通用套件管理表單 checkpoint(2026-09-10)
- Tool Manager 新增 Computer workspace archive 路徑與 JSON manifest 安裝表單,顯示安裝回傳 schemas/錯誤,補中英文文案。切換 bot 重新建立 pane,避免舊 bot 的操作結果帶到新 bot。
- 套件清單只顯示當前 Computer 的 bindings,API 明確回傳 archive runtime 類型;通用套件不再顯示 echo 呼叫/legacy rollback。舊 rollback API 在修改資料前拒絕 archive,避免中央版本變更而 root runtime 不匹配。
- revoked/installing bindings 仍可撤銷或移除,讓 runtime drain 失敗後能重試。操作失敗也刷新中央狀態;清單讀取錯誤不再無聲吞掉。
- 驗證:前端 56 測試與 production build 通過;新增 SQLx 測試涵蓋 runtime kind、revoked readiness、擁有者與 peer bot 隔離、Computer 切換。
- 尚未完成:schemas 的持久管理/重新探索、通用工具手動呼叫 UI、完整 V1 安裝精靈、通用重新授權與 rollback;此表單不代表 Tool Manager 全部驗收完成。
## 套件探索與手動操作 checkpoint(2026-09-10)
- Tool Manager 支援已安裝 archive binding 的 schema 重新探索,以及指定工具名稱/JSON object 參數的手動呼叫。與模型共用 binding 授權與 Computer 內 MCP SDK 路徑,帶入目前 generation;Computer 未 running 不派送。
- 手動呼叫經由共用 operations journal、agent barrier 與 outbox,要求 UUID operationId;重放回傳原結果,payload 不符拒絕,未知效果不重新派送。管理操作的 run_id 為 NULL,outbox 不誤寫不存在的 run_activity。
- 分頁 sessionStorage 僅保存最近操作 ID 與 SHA-256 請求雜湊,不保存工具參數。相同請求重用 ID;變更輸入或明確建立新操作才使用新 ID。沒有自動 mutation retry。
- SQLx 管理入口回歸涵蓋 owner/peer bot 拒絕、派送後結果未知、撤銷後原 ID 仍重放而不派送、payload mismatch、schema 探索拒絕 revoked binding,以及無 run 的 outbox 完成。這不是第三方 MCP/真實 API transport 全套 E2E 的替代。
- 本輪驗證:擴充的 SQLx 管理入口測試通過(含 stopped Computer 留有 provider_ref 的拒絕案例);operations 8 項、frontend 56 項、production build 與 API clippy all targets 通過。完整 P0–P3 仍未結案。
## 套件重新授權 checkpoint(2026-09-10)
- migration 027 改為 package/bot 的 live binding partial unique index;撤銷/移除的 binding 保留歷史,不再因重新安裝而復活舊 ID。
- Archive install 支援明確 `reauthorize: true`,UI 有對應選項。先確認全部舊 bindings 的 root runtime 已 drain,才 durable reserve 新 binding;新的 root 安裝分配不同 UID/私人 state。一般重試不會默默授予新權限。
- package advisory lock 加 binding row lock,序列化安裝重試與並行撤銷;已存在新 installing/ready intent 時,重試沿用該 ID,不重建權限。舊 sample installer 也配合 partial unique index 保留撤銷識別碼。
- Root 對不存在的 binding 撤銷改成冪等成功,適用重新建立的 Computer;存在但屬於另一 bot 的 binding 仍拒絕。FakeSandbox 沒有已安裝的套件程序,因此對 absent binding 使用相同語義。
- SQLx 覆蓋未明確重新授權拒絕、新 intent 與舊 revoked row 同時保留、重試不增加第三個 binding。容器 suite 驗證新 UID/私人 state、舊 ID 永不復活、其他 bot 不能撤銷。
- 本輪未完成通用 rollback、帳戶 grant/broker、長期 revoked binding/state GC、全 V1 manifest;P0–P3 保持未結案。
- 驗證:workspace 378 passed、4 ignored(其中 Docker transport 另於 disposable gate 執行);10 Runner + 5 HTTP MCP + 4 archive/stdio + 1 Rust Docker 全通過,fixtures 已清除。frontend 56 passed、production build、workspace clippy all targets 與 diff check 通過。
## Runner 版本切換 checkpoint(2026-09-10)
- 新增 root `package_switch`:只切換同 package 的已安裝且完整 manifest/hash 相符目標。切換前重驗 cache、entrypoint 與檔案權限,維持原 binding UID/私人 state。
- 與 install 共用 binding lock、與執行中 MCP call 共用 UID lock;忙碌時回報 PACKAGE_BUSY,不殺死已接受呼叫。私有 code 以 staging+rename 換版,DB 最終 compare-and-swap 不覆寫並行 revoke。
- `package_switches` 在 binding 版本變更的同一 SQLite transaction 記錄 transition ID/payload hash/receipt。重試回傳既有 receipt;不同 payload 拒絕。回滾後再收到舊升級請求,不會重新升級。
- 修正原 install 最終 unconditional ready 寫入:現在檢查 package key 與目前狀態,不能把並行撤銷的 binding 復活。
- 容器測試新增真實 stdio 延遲呼叫/private code 升級與回滾、同 UID/state 保留、另一 binding 不受影響、busy、重放與 mismatch、revoked 拒絕。root transition journal 目前上限 1024,長期 retention 待補。
- 中央 durable transition intent、API/UI rollback、schema registry 原子更新尚未接線;此 checkpoint 只證明 Runner 切換,不代表全套版本管理完成。
- 驗證:10 Runner + 5 HTTP MCP + 5 archive/stdio + 1 Rust Docker,共 21 項 disposable Computer 測試通過,測試容器已清除;diff check 通過。
## 中央版本切換/回滾 checkpoint(2026-09-10)
- migration 028 新增 durable package_transitions、schemas 與 pending/cancelling 排他性;live binding unique index 含 switching/retiring,安裝器不會繞過切換中的 reservation。
- `/api/bots/{bot}/tools/{binding}/switch` 與 UI 支援指定已安裝目標版本、沿用 operation ID 恢復 pending。轉換前關閉參與 bindings 的中央 admission;root package_retire 等現有 call 完成,不中斷它;接著 root switch 與驗證 target schemas。
- 最終 transaction 同時更新 source binding 的 package pointer、ready、目標旧 binding revoked 與 schemas/completed receipt。重放已完成的升級請求不會覆蓋後來的回滾。
- 撤銷/移除任一參與 binding 會關閉全體 admission 並取消 transition;runtime cleanup 未確認前保留 cancelling,阻擋新安裝。取消可重試,不復活舊 ID。
- 新 transition 使用 agent-barrier advisory lock;paused 拒絕。takeover 的 unresolved-effects 查詢現在包含 pending/cancelling transitions,避免切換仍在途卻授予人類 lease。
- SQLx 測试覆蓋 reservation/retry、peer bot 不受影響、generation mismatch rollback、ready/schema atomic completion、舊 receipt 重放、取消與 takeover 邊界。完成 boundary 以已驗證 runtime result 驅動;真實 runtime 由獨立 Docker gate 驗證,仍不是完整 HTTP API→Supervisor→Computer E2E。
- 跨 generation 的 pending transition 目前需明確撤銷清理後重建;完整 account/schema grant registry、常駐 pool、長期 transition/state GC 仍待補。P0–P3 全範圍保持未結案。
- 驗證:workspace 379 passed、4 ignored;最後 takeover 整合後 API 170 passed、3 ignored。21 項 disposable Computer gate 全通過,fixtures 已清除;frontend 56 passed、production build、workspace clippy all targets 與 diff check 通過。
## 版本切換跨 generation readback(2026-09-10)
- 新增 root 唯讀 package_transition_status,在 install/call 鎖下驗證當前 binding 的實際 package bytes,再回傳 request-hash 相符的 receipt 是否存在與當前版本/digest。
- API 遇到同 Computer 新 generation 時先 read back;沒有 receipt 必須仍為來源版本,有 receipt 必須仍為目標版本。符合後以目前 Computer generation/pending 狀態 CAS 更新,再走原本冪等 retire/switch/discovery/complete 流程。
- 舊 receipt 不代表目前仍在該版本:後來已回滾的 binding 會被拒絕恢復舊升級。無 root binding、bytes/receipt 不符、無法聯絡 Runner 都不更新 generation。
- Docker suite 實測 root readback 未完成/已完成/已被回滾取代三態,跨 identity generation 讀取仍保留原 receipt 驗證。SQLx 驗證讀回失敗不更新 generation;判定測試涵蓋有效分支、不同 package、missing 欄位與 receipt/current-version 矛盾。
- 此輪沒有宣稱實際 container restart+完整 HTTP API transport E2E 已完成;root store 遺失的 recreate 仍需撤銷清理/重新安裝,state 搬移與持久性驗收仍待補。
- 本輪驗證:21 項 disposable Computer gate、2 項 transition/recovery 測試與 API clippy all targets 通過;測試容器已清除,diff check 通過。
## 實際 Computer restart gate(2026-09-10)
- `make test-runner-jobs` 現在建立私人套件、切換版本與私人 state,執行真正的 `docker restart`,再從新程序/新 identity generation 驗證 root receipt、當前 code/digest、同 UID 與原 state 計數。
- 重啟後驗證 completed 與尚未派送的 transition readback,實際回滾,再重放舊升級 receipt;確認沒有重新升級或重置私人 state。測試只使用 gate 自行建立的容器。
- 驗證:既有 21 項容器測試加 1 個兩階段實際 restart scenario 全部通過;fixtures 清除、diff check 通過。這不是 container remove/recreate,也不取代完整 HTTP API→Supervisor→Computer E2E。
- 下一個已確認缺口:`DockerHost::provision` 目前只有 home bind mount;`/var/lib/lazyboy-runner`、package cache/private exec/state 還在 container writable layer。需要補 root-owned 持久 mount 與既有容器遷移,才能宣稱 recreate 保留 registry/套件/私人 state;目前尚未完成,不可把 restart 成功當成 recreate 證據。
## 新 Computer 的 runtime 持久儲存(2026-09-10)
- provision 新增 DATA_DIR/runtime/{homeKey} 下四個 root-owned bind mounts:Runner(含 SQLite/outbox/private cache)、共享 immutable package cache、private executable copies、私人 tool state。host runtime/key parent 0700,不放在 task home 內。
- 目錄建立拒絕 symlink、非 root owner、group/world writable 路徑;再次 provision 保留既有內容。沒有 HOST_DATA_DIR 時使用絕對 bind source,修正預設相對 data dir 的 Docker bind path。
- 自動替換既有 Computer 前比對全部 runtime mount 的 source/destination;未遷移或指向其他資料位置時拒絕直接 destroy。完整舊容器資料搬移仍待實作,此時保留原容器,不冒充已遷移。
- gate 使用獨立 temporary bind sources,真正 docker rm 後重新建立不同 container ID,再驗證 package receipt、code、UID/private state 與 rollback。root Runner source 和資料從 mounts 保留,只有 image binary/測試程式重新提供。cleanup 先清除容器,再刪 host fixture 目錄。
- 驗證:runtime storage Rust test、21 項既有容器測試+restart+remove/recreate scenarios 通過;Supervisor clippy all targets 與 diff check 通過。此驗證不等於完整 API provisioning transport E2E 或舊容器自動遷移驗收。
## 舊 writable-layer runtime 遷移(2026-09-10)
- 舊容器替換/移除前,Supervisor 持 per-home lifecycle file lock,停止來源、串流匯出四個 Docker archives、在 root-only staging 解包。全程不在 Supervisor 執行套件/shell/Python;Rust tar loader 保留 owner/mode 與安全的連結語義。
- 驗證拒絕 traversal、重複成員、未宣告父目錄、特殊檔、非 root-owned/writable root;每 archive 8 GiB/100,000 entries。所有檔案/目錄 fsync,保存 prepared/published marker 與內容/metadata fingerprints,再分目錄 rename;部分發布可重試,不覆寫非空目的地。
- 發布後核對完整快照,重試也不僅信任 marker。來源在 snapshot 後重新運行、目標被更改、mount/home source 不一致時拒絕移除來源。遷移中的舊容器不能被另一 wake/provision 繞過 lifecycle lock 重新啟動。
- 修正 create-name-conflict 原本無條件刪除同名容器、destroy 忽略移除失敗。未管理的容器拒絕刪除;公開 destroy 也會先保留 legacy runtime,而不是只有 image replacement 才遷移。
- 真實 gate 從沒有 runtime mounts 的舊容器開始,先驗證目的地 sentinel 不被覆寫,完成 migration,再注入 partial-publish 狀態與 snapshot tamper;確認可恢復/拒絕損毀,來源仍保留。也實测 wake 阻擋與外來同名容器保留。最後刪除舊容器、建立新的 persistent-mount container,驗證 package/UID/state/receipt/rollback。
- 此處驗證的是 Supervisor migration 與真實 Docker archives,不取代完整 HTTP API→Supervisor→Computer 操作 E2E,亦不宣稱未解決的 job generation reconcile、account broker、viewer/完整 P0–P3 已完成。
- 驗證:workspace 382 passed、5 ignored(Supervisor 2 個 Docker-only 測試在 gate 另跑);既有 21 項容器測試加 restart、legacy migration/partial publish/collision、remove/recreate 情境通過。最終 workspace clippy all targets、diff check 及 `lazyboy/supervisor:plan-test` distroless image 建置通過。所有 fixture 容器與暫存資料均清除,候選映像未部署。
## Computer generation 與移除失敗處理(2026-09-10)
- Docker Sandbox 的 DELETE 現在檢查 HTTP status;Supervisor 拒絕遷移/移除時,不再回報成功。localhost HTTP fixture 覆蓋 204、409、500。
- API restart/不健康容器替換只在 destroy 確認後,以同一資料庫交易清除 provider/租約、增加 generation、中斷舊 generation 的活動工作。stop 失敗不當成移除證據;destroy 失敗或回應逾時保留 provider、generation 與 job 狀態,標記 error 供後續核對。
- 交易鎖住 Computer row 並比對原 provider/generation,拒絕過期完成回應;provider 遺失且仍有活動工作時拒絕憑空中斷。替換完成後重新讀取 Computer,避免 provision 傳入已移除的 provider。
- boot/resume 共用 provider 發布交易:新 provider 增加 generation、清除舊租約並中斷舊工作;同容器恢復保留 generation。涵蓋遺失 destroy 回應後,下一次 provision 找到新容器的情境。
- SQLx 測試覆蓋失敗保留、成功中斷、terminal/新 generation 工作保留、重複過期結果拒絕、無 provider 活動工作拒絕,以及 provider 替換/原容器恢復。workspace 384 passed、5 ignored;workspace clippy all targets 通過。未將此視為完整跨服務故障注入 E2E 或全部 P0–P3 驗收完成。
## 閒置回收與工作 admission(2026-09-10)
- 修正 busy query 失敗時誤認閒置的 fail-open;資料庫取得/查詢失敗一律禁止回收。accepted/unknown operations 與 pending/cancelling package transitions 也列入活性判斷。
- pause/長時間 parked stop 共用交易 helper,持 Computer row lock 後重新核對 provider、generation、state 與 idle cutoff,再檢查工作。Supervisor lifecycle 回應有 30 秒上限,失敗/逾時不發布假成功狀態;沒有 provider 也不憑空宣稱已停止。
- job admission 使用同一 row lock,重新檢查 running 狀態,防止等待回收交易後仍接受工作。這不代表所有 viewer、MCP admission、GUI lease 的 lifecycle race 已完成驗收。
- 新 SQLx+localhost HTTP fixture 驗證 unknown operation 保活、stop 409 不改狀態、updated_at 改變拒絕舊快照、成功停止、DB query error 保守拒絕;admission 測試涵蓋 suspended 拒絕及與 parking row lock 的併發等待。API suite 173 passed、3 ignored;API clippy all targets 與 diff check 通過。
## Operation/套件換版與閒置回收 admission(2026-09-10)
- Operation intent 交易現在更新 Computer 活躍時間,取得與 parking 相同的 row lock,並與 accepted ledger 一起 commit;既有 operation replay 不刷新活躍時間,也不重播效果。沒有 Computer 的操作仍可使用 NULL journal。
- 新 package transition 在 agent barrier 後鎖定 Computer,重新檢查 running/generation/provider,才修改 binding 狀態並寫 pending intent;防止讀取 running 後、容器已被暫停仍建立換版。
- SQLx 測試實際持 parking row lock,確認 operation admission 等待;intent INSERT constraint failure 會回滾活躍時間。換版測試在等待期間把 Computer 改為 suspended,確認拒絕且沒有建立 transition,恢復 running 後既有換版/取消測試仍通過。
- 驗證:API 174 passed、3 ignored;新增換版併發測試通過;API clippy all targets、diff check 通過。完整 HTTP API→Supervisor→Computer lifecycle 故障注入、viewer 與其他資源活性仍屬未完成範圍。
## API 中斷後的 boot attempt 恢復(2026-09-10)
- migration 029 新增 boot_attempt/boot_started_at 與部分索引,替既有 booting 記錄以 updated_at 補起始時間。API 每次 claim booting 產生獨立 UUID。
- Runner reconciliation loop 回收超過 3 分鐘的 boot attempt(正常 provision 120 秒+workspace setup 15 秒)。只標記 error 並清除 attempt,不清除 provider、不增加 generation、不改 job 結果;後續正常 provision 由 Supervisor 核對實際容器。
- boot success 與 mark_boot_error 都比對 attempt;新一次啟動開始後,舊成功/失敗回應不再能覆蓋它。stop/generation retirement 清除舊 attempt;resume 必須符合無啟動 attempt 的狀態。
- SQLx 測試驗證未逾期不回收、逾期保留 provider/generation/unknown job、新 attempt 拒絕舊結果,以及成功後清除 metadata。API suite 175 passed、3 ignored;API clippy all targets 通過。此證據涵蓋資料庫恢復與 fence,不冒充 API process kill 的完整 HTTP 故障注入驗收。
## 原生 stat_file(2026-09-10)
- 新增 agent-facing stat_file schema/dispatch,沿用 Team bot workspace/shared 路徑授權;列入 native sandbox routing,text-only 模型可用且不需要 GUI。SandboxProvider 提供明確 unsupported fallback,Docker provider 接 Supervisor `/computers/{id}/files/stat`。
- metadata 在 Computer 內的 descriptor-relative helper 讀取;不讀取內容、不追蹤最終 symlink,父目錄仍 O_NOFOLLOW。輸出 path、file/dir/symlink/special、size、mode,modifiedNs 使用字串避免 JavaScript 浮點數失真;不回傳 symlink 的外部目標。
- Python fixture 覆蓋 1 TiB sparse file、FIFO 不阻塞、symlink、父目錄 symlink 越界、traversal、缺檔與 root directory;6 項檔案測試通過。Rust 真實 Docker file gate 加入 stat kind/size/timestamp 檢查。
- workspace 387 passed、5 ignored;workspace clippy all targets 通過。此項不取代尚缺的 patch/move/search、讀取分頁/串流,也不宣稱完整 API HTTP 檔案操作 E2E 已完成。
- 最終 Docker gate 通過:既有 Runner/HTTP MCP/package fixtures、實際 restart、legacy migration/部分發布恢復/名稱衝突保留、remove/recreate,以及含 stat 的真實檔案 CAS/雙向 exec;fixture 容器清除完成,未部署候選映像。
## 原生 patch_file(2026-09-10)
- 新增 patch_file schema/native routing/agent workspace dispatch,透過 SandboxProvider 與 Supervisor `/computers/{id}/files/patch` 在 Computer 內修改,API 不下載原檔執行 read-modify-write。操作接入既有中央 operation ledger/barrier,transport 失聯或非確定回應標為 UNKNOWN_EFFECT,不自動重播。
- 支援 offset/deleteBytes/contentBase64 的 byte splice,必填小寫 SHA-256 expectedHash。只修改既有 regular file;要求合法範圍,原檔及結果限制 8 MiB,保留 mode。同 directory flock 內比對 hash、組成結果、temporary file fsync/atomic replace/directory fsync,最後讀回比對 hash;回傳 size/sha256/verified。
- helper 拒絕未知 request fields、負數/bool offsets、越界、缺檔、symlink;8 項原生檔案測試通過,包含二進位 patch、權限保留、失敗原檔不變、8 個並行 patch 僅一成功。真實 Docker file gate 已加入 patch/stale-hash conflict 檢查。
- 尚未實作 move/search、byte cursor/大型串流,以及檔案變更的 Computer 本地 durable journal;中央 ledger 不冒充本地 journal 驗收。
- 最終驗證:workspace 387 passed、5 ignored;8 項檔案 fixture、含 patch 的真實 Docker gate(Runner/MCP/restart/migration/remove-recreate/native files)與 workspace clippy all targets 通過。fixture 容器清除,未部署。
## 原生 move_file(2026-09-10)
- 新增 move_file schema/native routing/operation ledger dispatch,來源與 destination 各自經 Team bot workspace/shared 路徑授權。透過 SandboxProvider 與 Supervisor `/computers/{id}/files/move` 執行;API 不搬運檔案 bytes。
- 必填 source expectedHash 與 expectedDestinationHash(空字串表示目的地必須不存在)。Computer helper 以 descriptor-relative O_NOFOLLOW 開啟兩端目錄,依 device/inode 固定順序取得 flock,重複目錄只鎖一次,檢查 regular file/hash 後移動。目的地不存在使用 Linux renameat2 NOREPLACE;已存在且 hash 相符才 atomic replace,之後同步兩個目錄並讀回目的地 hash/確認來源消失。
- 保留 inode/mode/bytes,拒絕同一檔案/hardlink alias、symlink、越界與未知欄位。此版來源及既有目的地核對上限 8 MiB,父目錄須存在;跨 filesystem 不使用非原子 copy fallback。原生 search/byte paging/大型串流與本地 file journal 仍待完成。
- 10 項 Python file fixture 通過,包含雙端 conflict 原檔保留、權限、反向跨目錄無死鎖、同一目的地僅一成功;Docker native gate 增加真實移動/來源消失/bytes 驗證及移回。
- 最終驗證:workspace 387 passed、5 ignored;10 項原生 file fixture、含 move 的真實 Docker gate、workspace clippy all targets 通過。fixture 容器均清除,未部署。
## 原生 search_files(2026-09-10)
- 新增 search_files schema/native routing/workspace dispatch,透過 SandboxProvider 與 Supervisor `/computers/{id}/files/search` 在 Computer 執行 UTF-8 literal search。API 不下載整棵檔案樹,無 shell、regex 或 GUI 依賴。
- descriptor-relative DFS、O_NOFOLLOW、不追蹤 symlink,最多 32 層/4096 entries/16 MiB 讀取;單檔上限 1 MiB、最多 100 matches、序列化 match 總量 512 KiB。NUL/無效 UTF-8、特殊檔、過大、讀取時改變、過長路徑與無權限檔案均計入 skipped;truncated/limitReason/complete 明示搜尋覆蓋範圍,不能將部分結果當作完整無命中。
- 每個 match 提供 path、byteOffset、previewOffset、可讀 preview 與 byte-safe previewBase64;純 UTF-8 query 最多 256 bytes,maxResults 預設 20。根路徑缺檔回報失敗。
- 13 項檔案 fixture 通過,包括 Unicode byte offsets、regex 符號當字面值、binary/symlink/FIFO/oversize 略過、entry/byte/result/output limits,以及特殊長檔名的 JSON 大小限制。Docker native gate 新增真實容器內搜尋與 Unicode offset 驗證。
- workspace 387 passed、5 ignored,workspace clippy all targets 與最終 Supervisor clippy 通過;完整大型檔案 byte paging/串流、Computer 本地 file journal 及全 API HTTP E2E 尚未完成。
- 最終 Docker gate 通過(Runner/HTTP MCP/packages/restart/legacy migration/remove-recreate/含 search 的 native file gate)。fixture 容器清除完成,未部署。
## byte range/continuation 讀取(2026-09-10)
- 新增 read_file_bytes,text 與 binary page 分開:offsetBytes/maxBytes/expectedVersion,最大 64 KiB;回傳 base64、nextOffset、EOF、總 size、chunkSha256。chunk hash 明確不冒充全檔 hash,API 不先載入整份大檔。
- Computer 使用 O_NOFOLLOW/regular-file 檢查及 pread。version v1 由 device/inode/size/mtime/ctime 建立;offset>0 必須帶版本,讀前/讀後核對,拒絕檔案被替換或修改後拼接不同版本。offset=size 可取得空 EOF,offset>size、零長度/超量/非法版本回報錯誤。
- Supervisor `/computers/{id}/files/read-range` 與 Docker Sandbox 保留 FILE_NOT_FOUND、PERMISSION_DENIED、INVALID_ARGUMENT、CONFLICT、SIZE_LIMIT 等錯誤類別;read-only transport 失敗不偽裝空內容成功。
- 15 項原生檔案 fixture 通過,包含二進位分頁重組、EOF、同 bytes 被重新替換時舊版本拒絕、1 TiB sparse file 尾端定位。真實 Docker native gate 增加 first page/tail/EOF/bytes 驗證,整個 Runner/MCP/restart/migration/remove-recreate gate 通過。
- workspace 387 passed、5 ignored,workspace clippy all targets 通過。大型 upload/download/attachment 全串流、directory pagination、Computer 本地 file journal 及完整 API HTTP E2E 仍未完成。
- 額外 localhost HTTP regression 驗證 400/403/404/409/413/500 保留正確工具錯誤碼;測試與最終 Sandbox clippy 通過。fixture 容器均已清除,未部署。
## 舊 list_files 容量與錯誤修正(2026-09-10)
- 移除無上限 os.listdir+sort;改用 scandir,最多 4096 entries 與 512 KiB 序列化內容,收齊且確認目錄 mtime/ctime 未變後才輸出與排序。超限/衝突不輸出部分成功清單。
- symlink、FIFO/特殊檔不再誤標成 regular file。Supervisor 以 413/409 保留 DIRECTORY_LIMIT/CONFLICT,Docker Sandbox 與 agent-facing list_files 傳遞 typed error;一般 list failure 也不再使用成功 text outcome。
- 18 項原生檔案 fixture、Sandbox 10 項測試(含 localhost HTTP limit/conflict regression)、workspace clippy all targets、diff check 通過。本輪沒有重新聲稱完整 Docker/API E2E;有界拒絕不取代尚未完成的 directory continuation。
## Directory continuation(2026-09-10)
- 既有 list_files 加入 limit(1–200)/cursor,透過新的 SandboxProvider/Supervisor `/computers/{id}/files/list-page` 取得有界頁面;未指定分頁時保留既有 bounded/sorted list 行為。
- Computer helper 使用經本機 Linux dirent64 header 核對的 64-bit Linux readdir64/telldir/seekdir cookie。每頁重新開啟 descriptor-relative 目錄並接續 cookie,不重掃前面頁面;返回 filesystem order、nextCursor、eof 與 version。最多 200 entries/512 KiB entry JSON;長檔名達輸出上限也提供 continuation。
- 版本綁定路徑、device/inode、目錄 mtime/ctime,讀前/讀後核對;目錄 membership 改變、跨路徑 cursor、非法 token 會拒絕。此版本核對目錄項目,不宣稱各檔案內容的跨頁 snapshot。cursor 是讀取位置資料,不充當授權憑證,workspace grant 仍逐次核對。
- 21 項原生檔案 fixture 通過:4301 entries 全量分頁無重複/漏項、cursor replay、目錄變動/跨路徑拒絕、空目錄、長檔名輸出限制接續。真實 Docker native gate 驗證跨 helper 程序 continuation 與修改後 conflict;Runner/MCP/restart/migration/remove-recreate gate 通過,fixture 容器清除。
- Workspace 首輪發現既有 zombie fixture 偶發前提失敗:shell 可能在 exec 前先回收子程序。改用明確 fork/確認 /proc zombie 後才通知測試,最後透過 stdin 關閉要求 parent waitpid 清理;移除固定 200 ms 等待與不確定 shell 行為。
- Directory continuation 不取代大型 upload/download/attachment 全串流與 Computer 本地 file journal;完整 API HTTP E2E 與其餘 P0–P3 仍未完成。
- 最終重跑:workspace 389 passed、5 ignored;21 項 file fixtures、真實 Docker gate、workspace clippy all targets、diff check 通過。未部署候選映像或修改正式 Team 資料。
## 唯讀檔案 typed errors 與假成功修正(2026-09-10)
- 修正 legacy read_file/stat_file 在 transport 失敗與路徑驗證失敗時仍回成功 text outcome;新增共用 native_file_failure,保留 FILE_NOT_FOUND/PERMISSION_DENIED/INVALID_ARGUMENT/SIZE_LIMIT/DIRECTORY_LIMIT/CONFLICT 等分類。list/search 也沿用這些分類。
- Computer helper 對 read/stat/list/search/read-range/list-page 分類已知唯讀錯誤,保留現有 conflict/directory-limit 標記。Supervisor 唯讀 endpoints 共用 status mapper,Docker Sandbox 的 read/stat/list 保留 HTTP error 類別,停止讀取無用途的錯誤 body;file_operation 路徑拒絕帶 INVALID_ARGUMENT。
- 未把此分類直接套用到可能已產生副作用的 write/patch/move,避免將回應遺失/readback 失敗誤判成確定未執行。
- 23 項 helper fixture 通過,包含缺檔、oversize、非法路徑與實際降權程序讀取 permission-denied,失敗無 bytes 輸出。localhost HTTP fixture 覆蓋 read/stat/list/range 各 400/403/404/409/413/500;API outcome regression 確認 error_code 非空。
- 最終 workspace 390 passed、5 ignored;23 項 file fixtures、workspace clippy all targets、diff check 通過。本輪未重跑整個 Docker gate,不冒充完整 API HTTP E2E;mutation 磁碟滿/本地 journal/全串流與其餘 P0–P3 仍有必做項。
## 寫入回應未知與 fsync 失敗邊界(2026-09-10)
- Docker write_file_checked 對 request response loss/一般 server failure 明示 UNKNOWN_EFFECT;409 保持 CONFLICT,400/413 保留確定的 INVALID_ARGUMENT/SIZE_LIMIT。API write_file 不再將這些不確定情況寫成普通 FILE_WRITE_FAILED,而由既有 operation ledger 保留 unknown,禁止自動重播。
- 新 helper 故障注入測試在 temporary-file fsync 與 rename 後 directory fsync 分別注入 ENOSPC:前者原檔保持不變,後者新內容已可见但 helper 失敗;兩者都清除 temporary。這證明 transport failure 不能推論未寫入;測試是 fsync fault injection,不冒充真實磁碟填滿測試。
- localhost HTTP fixture 覆蓋 204、400、409、413、500 及直接關閉連線,確認失聯/500 保留 UNKNOWN_EFFECT。
- 驗證:24 項 file fixtures、API 176 passed/3 ignored、Sandbox 11 passed、workspace clippy all targets 與 diff check 通過。本輪未重新宣稱完整 Docker gate;Computer 本地 file journal/未知結果 readback recovery/全串流與其餘 P0–P3 仍待完成。
## Computer root file journal backend(2026-09-10)
- 新增 runner_files.py,隨 trusted Runner bootstrap 提供固定 computer_files helper source。root SQLite file_operations 以 computer/bot/generation/operation ID 隔離,先 FULL/WAL commit accepted intent 才以 UID/GID 1000、清空 supplementary groups/環境與資源限制執行 write/patch/move。
- ledger 保存 action/path/precondition/desired-or-replacement hashes、payload digest 與有界 receipt,不保存 task contents。相同 ID/payload 只回放結果;payload mismatch 拒絕;accepted/unknown 不重播。file_status 僅讀取同 scope 歷史結果。全域 1024 歷史紀錄上限,額滿拒絕新效果但保留回放;長期 retention/公平 quota 仍待補。
- file_mutate 與 root agent barrier admission 同 SQLite transaction 序列化;pause acknowledgement 會等待檔案 accepted/unknown 清空,無法確認時 BARRIER_INCOMPLETE,不假接管。root helper 本身不取代 API workspace assignment/grant 檢查。
- write 現在也在 fsync/rename 後讀回 hash,與 patch/move 一樣提供 verified receipt。RPC stdin 上限調成有界 16 MiB,容納既有 8 MiB file write payload;這不是大型檔案 streaming 實作。
- 六項真實容器 root journal 測試:receipt/重送不覆蓋後續修改、wrong bot/generation 隔離與 UID/metadata 防讀、patch/move/conflict、未完成 intent 不重播且阻擋 pause、journal admission readonly failure 不啟動 child、quota 拒絕新操作但保留 replay,以及 SQLite receipt commit 注入失敗後保留 unknown/人類後續修改。(部分情境合併在同一測試。)
- 歷史 root receipt 在實際 restart、legacy runtime migration、remove/recreate 後仍可按原 scope 查得。這是持久資料驗證,不冒充新 generation 的 API recovery/readback。
- 驗證:workspace 391 passed、5 ignored;24 項 file helper tests、6 項 root file journal tests、Runner/MCP/package/restart/migration/recreate/native file Docker gate、workspace clippy all targets、diff check 通過。fixture 容器均清除。
- **仍未完成**:正式 API write/patch/move 路徑尚未切換到 root journal;file outbox、中央 receipt reconcile、unknown readback recovery/跨 generation 驗證尚待接線。此 backend 不列為全案或完整 file journal E2E 完成。
## Agent file journal HTTP 接線與 operation ID 並行修正(2026-09-10)
- Agent `write_file`/`patch_file`/`move_file` 經 Sandbox SDK → Supervisor `/files/mutate` → Computer root file journal;要求 bot 與正 generation,未知 transport/receipt 結果保留 UNKNOWN_EFFECT。未支援此能力的 provider 明確拒絕,不退回未記錄的寫入。
- API central intent 仍先於 HTTP 持久提交;root operation ID 使用本次原始 ID 的 SHA-256,保留 root identity scope。write receipt 額外核對 path/size/SHA-256,符合才登錄 artifact。
- 修正共用 `last_operation_id` 的競爭:file mutation 與 artifact 使用 dispatch 區域變數一路傳遞,不受另一個工具修改共用顯示欄位影響。SQLx 測試以 admission advisory lock 建立交錯,驗證正確 root ID、HTTP 發送前 accepted、重送不再 HTTP,以及 artifact 對應 ID。
- 真實 Docker gate 已包含 SDK → 同一 Supervisor HTTP handler/middleware → root Runner:receipt replay 不覆蓋後續人類修改、payload mismatch、CAS conflict、root pause 拒絕新操作;完整 Runner/file/MCP/package/restart/migration/recreate gate 通過,fixture 清除。
- API→mock Supervisor contract 與上述真實 transport gate 是兩段證據,尚不列為完整 API HTTP→Computer E2E。其他 legacy 寫入/upload 路徑、file outbox/中央 reconcile/unknown readback/跨 generation recovery、全串流及其餘 P0–P3 仍必須完成。
- 最終驗證:API 177 passed/3 ignored、workspace Clippy all targets(`-D warnings`)、format 與 diff check 通過;Docker gate 含新增真實 file HTTP 測試通過。
## Computer file outbox(2026-09-10)
- 新增 root SQLite `file_outbox`:accepted event 與 intent 同交易,result event 與 receipt 同交易;只存 operation reference、identity、狀態與 helper receipt,不存任務檔案內容。
- Runner `file_events` 支援未確認事件重送,每批至多 100 筆/256 KiB event JSON;`ack_file_events` 按完整 computer/bot/generation scope 刪除已確認事件,重複 ack 可接受,其他 bot/generation 無法讀取或刪除。確認事件不刪除 operation receipt,因此不能使舊操作重新執行。
- 新增兩項真實 Computer 測試涵蓋 redelivery、scope isolation、ack 去重、receipt replay,以及 outbox INSERT 故障:intent transaction rollback 不啟動修改;receipt transaction rollback 在修改已發生後保留 unknown/accepted event。
- **仍待完成**:API 持久保存中央 operation→root operation/provider/generation mapping、中央 file event dedup/ack/receipt reconcile、unknown readback recovery。現有 job event loop 尚不消費 file outbox。升級前既有 file receipt 沒有可反推的原始 operation ID,不能虛構 backfill event;需利用中央 dispatch mapping 與 scoped `file_status` 補查。
- 驗證:完整 Docker gate 通過(Runner 10、root file 8、HTTP MCP 5、package 5、實際 restart/legacy migration/remove-recreate/native file/file HTTP);兩次 lifecycle verification 均確認未 ack file outbox 與 receipt 保留。fixture 已清除,diff check 通過。
## 中央 file dispatch identity mapping(2026-09-10)
- Migration 030 新增 `computer_file_dispatches`:以中央 operation FK 保存 Computer/bot/provider/runner identity/generation/root operation ID、request hash 及檔案 reference/內容 hash/size。write/patch 的 base64 內容不進 DB。
- Agent file mutation 在 HTTP 前先提交 mapping。交易鎖定 Computer 與 bot,重查 running、provider/generation、目前 binding、owner/space 與 accepted central intent;adapter actor 必須一致。HTTP 使用與 mapping 完全相同的 ComputerRef/AdapterContext snapshot。
- Mapping DB error/過期 generation 拒絕 mutation,不送 HTTP。既有相同 key 僅允許同 payload 與同 provider/generation mapping;不覆蓋舊 incarnation 的 recovery 證據。
- SQLx API integration fixture 在 HTTP handler 內查驗 mapping 已持久提交、hash/size/path 對應且不含內容;追加 stale generation 與 DB INSERT trigger fault,兩者均驗證 transport request count 不增加。
- 中央 file event 消費、dedup/ack、receipt reconcile 與 unknown readback 尚未接線;此 mapping 是恢復所需持久資訊,不能單獨宣稱斷線恢復完整完成。
- 驗證:API 177 passed/3 ignored;最終 actor guard 後定向 integration test 再通過;workspace Clippy all targets 與最終 API Clippy(`-D warnings`)、format、diff check 通過。本輪未重跑 Docker gate,未宣稱完整 API→Computer recovery E2E。
## 中央 file event 同步與 receipt reconcile(2026-09-10)
- Migration 031 新增 scoped `computer_file_events` 去重表;背景 Runner loop 依持久 dispatch mapping 輪詢 `file_events`,中央 transaction commit 後才送 `ack_file_events`,不重送檔案 mutation。
- 每筆事件核對 Computer/provider/generation/bot/root operation identity,鎖定 lifecycle 與 operation。驗證 receipt path/verified/hash 格式/8 MiB size bound;write 核對預期內容 hash/size,move 核對目的地及來源 hash,patch 接受可信任 helper 的核驗 receipt。
- Event、accepted/unknown→receipt 狀態及通知 outbox 同 transaction;已知 terminal 結果不被 unknown/舊事件倒退。同 event key 不同內容拒絕,舊 generation/錯誤 bot 不 ack。
- 修正活動紀錄去重:recovery 通知攜帶獨立 eventKey,不能被同 operation 先前的 unknown 訊息吞掉;同 recovery 通知重送仍去重。
- SQLx 測試涵蓋並行重送、亂序/terminal 不倒退、錯誤 scope、錯誤 hash/verified、通知 journal 故障整筆回滾;真實 SDK→mock HTTP fixture 確認 ack 前中央結果與事件已持久存在,ack HTTP 503 後重送不增事件/通知且不 dispatch mutation。
- 驗證:API 181 passed/3 ignored,最終 recovery Activity 定向測試、workspace Clippy all targets(-D warnings)、format/diff check 通過。
- **仍待完成**:API→真實 Supervisor→Computer 的整條失聯 E2E、原本 receipt 未提交的 unknown readback、跨 generation 授權恢復、recovery artifact metadata 補登、poll/retention 公平配額與完整 idle outbox 活性。此次證據不涵蓋這些缺項,其餘 P0–P3 繼續必做。
## 真實 API dispatcher→Supervisor→Computer file recovery gate(2026-09-10)
- 新增 opt-in SQLx integration:啟動編譯後 Supervisor binary 的實際 router/middleware,使用現有隔離 Computer、一次性 PostgreSQL 測試 DB、loopback fault proxy,呼叫正式 API `dispatch` 與 `sync_events`。
- write、patch、move 均先在真實 Computer 完成,proxy 讀到 succeeded 後丟棄回應,讓 API 留下 UNKNOWN_EFFECT。直接讀取實際檔案確認效果,再寫入模擬後續人類修改;中央收到 durable outbox 後恢復成功,同 operation 重送不再發送 mutation HTTP,後續檔案內容保留。
- 確認 3 個操作共 6 個事件提交中央並從 root outbox ack 清除。fixture Supervisor 由 guard 在成功或 panic 後 kill/wait,臨時 DATA_DIR 清除;只接受 `lazyboy-plan-runner-test-` 容器,不觸碰正式 runtime。
- `make test-runner-jobs` 納入此 gate 並要求明確設定 disposable PostgreSQL `DATABASE_URL`;新增前置檢查與使用說明,缺少 DB 設定時在建立容器前停止。
- 驗證:完整 Docker gate 通過,含 Runner 10/root file 8/HTTP MCP 5/package 5/restart/migration/recreate/native file/file HTTP,以及新增三種檔案 mutation 的真實跨程序恢復測試;workspace Clippy all targets、format/diff check 通過,測試容器及測試 Supervisor 已清除。
- 此處入口是正式 API dispatcher,非公開 HTTP API/model run;root receipt 未提交的 unknown readback、API/Supervisor/Computer 同時 crash 矩陣、跨 generation recovery、artifact 補登與其餘 P0–P3 仍未完成。
## File receipt artifact 補登與 direct/recovery race(2026-09-10)
- 移除原本只處理 write、忽略 DB error 的 artifact 寫入;write/patch/move 的正常回應先驗證可信 receipt,再以已持久 dispatch 的 provider/generation/operation scope 登錄 metadata。失敗回 UNKNOWN_EFFECT,由 durable outbox 恢復。
- 背景 receipt reconcile 在事件/結果/通知同一 transaction 補登 artifact。operation row lock 序列化正常回應與 recovery;新 artifact ID 由完整中央 operation key 的 SHA-256 決定,既有相符 random ID 保留,內容不符拒絕,不忽略錯誤。
- 修正 recovery 先完成時,稍後正常成功回應被判成 unknown:僅在同 operation 已有可信 file event、中央 terminal status 與本次結果一致時接受既有提交,不建立第二筆通知。
- SQLx 新增 artifact INSERT 故障後不完成事件/不 ack、通知故障連 artifact 一起 rollback、正常回應與背景 recovery 並行只登錄一筆,及較晚 direct success 沿用已持久結果/通知。
- 真實 dispatcher→Supervisor→Computer 失聯 gate 擴充:write/patch/move 恢復後核對 artifact path/原操作 hash/size,每個 operation 僅一筆;後续人類修改不取代歷史 receipt metadata,重送不重新修改檔案。
- artifact immutable snapshot/retention/全串流、unknown readback、跨 generation 恢復與其餘 P0–P3 仍待完成。
- 驗證:API 183 passed/4 ignored;完整 Docker gate(含新增 artifact 回復驗證)通過,workspace Clippy all targets(-D warnings)、format/diff check 通過;測試容器清除。最後 direct/recovery completion 修正由完整 API suite 驗證。
## File event 錯誤隔離(2026-09-10)
- 修正單一 bot 的 event/ack error 直接中止整台 Computer 同步:保留錯誤回報與重試需求,仍繼續處理後面的 bot。
- 同一批資料逐事件處理;identity/receipt 驗證失敗的事件不提交、不 ack,其他有效事件仍持久提交並送確認。批次仍回傳失敗,不能把未處理事件冒充已交付。
- 擴充 HTTP fixture:在有效 accepted/succeeded 前插入錯 bot 事件,確認只 ack 正確的兩個 ID;再讓 ack 回 HTTP 503,確認後面的健康 bot 仍被輪詢,下一次重送不重複中央紀錄。
- 驗證:file_operations 6 項 SQLx 測試與 API Clippy all targets(-D warnings)通過。本輪不冒充完整 Docker gate;大量 scope 的輪詢排序/timeout 公平性、100 筆壞事件占滿 batch 時的 quarantine、全系統資源配額仍未完成。
## 持久 file poll 排程與 timeout 進度(2026-09-10)
- Migration 032 新增每 computer/provider/generation/bot 的 poll scope、輪詢時間與租約。dispatch INSERT trigger 同交易建立 scope,並 backfill 既有 dispatch;不因新操作把同 bot 的順序重設到最前面。
- SQL `FOR UPDATE SKIP LOCKED` 原子取得最久未輪詢 scope,IO 前更新 last_polled。每次同步最多 4 個不同 scope、每個 timeout 2 秒;慢 bot 不會反覆占滿整輪。30 秒租約讓多個 API worker 協調,API 中斷後可重新取得;舊 token 不能釋放後來的新租約。
- 每累積 16 個中央已提交事件就送 ack,批次結束確認餘數;後段 timeout 不必讓前段永遠無法確認。壞事件仍不 ack,中央未知結果仍不重播 mutation。
- 新增 SQLx:並行 claims 不重複、先前已輪詢 scope 排到其餘 peer 後面、expired lease takeover/stale release/wrong generation;真實 SDK HTTP 慢 bot timeout 後健康 peer 繼續,以及 ack 前中央僅完成該 prefix 的分段順序測試。
- 驗證:API 185 passed/4 ignored、完整 Docker gate 含真實 file recovery 通過;最後分段確認變更後 9 項 file_operations 測試、workspace Clippy all targets(-D warnings)、format/diff check 通過。Docker fixture 清除。
- 此輪完成檔案回執輪詢的持久順序與有界等待;過期 scope retention、滿頁壞事件 quarantine、全系統工作/CPU/RAM/disk 公平配額,以及其他 P0–P3 仍未完成。
## Root unknown file readback 證據(2026-09-10)
- `file_status` 新增嚴格 boolean `readback`;只能按既有 identity+operation ID 讀取受保護 metadata 指向的檔案,不能自行提供任意路徑。僅 unknown 操作執行 readback,已有 terminal receipt 仍直接回放。
- 新增 UID 1000 fingerprint helper:descriptor-relative/no-follow、regular file、8 MiB 限制、directory shared lock、讀取前後 inode/size/mtime/ctime 及路徑綁定核對,只回傳 hash/size,不回傳 task bytes。不可讀/symlink/超量變為 unavailable,不誤認目標已符合。
- Root `file_observations` 每 operation 保留最近一次 metadata-only 觀察;write 核對 desiredHash,move 提供來源缺失/目的地 hash 的分開 snapshot。始終 `execution_proven=false`,操作保持 unknown,不能把目前相同內容當作原操作的執行證據。patch 缺少完整目標 hash,desired_state_matches 保留 null。
- 真實 root receipt UPDATE trigger fault 後驗證 readback match、不同 bot/generation 拒絕、root 持久觀察、後續人類修改 mismatch、重送仍不改寫檔案。helper 新增 binary/特殊路徑、內容不外洩、symlink 拒絕、1 GiB sparse file 有界拒絕測試。
- 驗證:25 項 helper tests、完整 Docker gate(含 root file 8 項與 dispatcher 真實恢復)、workspace Clippy all targets、diff check 通過;fixture 清除。
- **仍待完成**:API 對此觀察的查詢/恢復決策、patch pre-effect 完整目標 fingerprint、unknown→已核驗目標狀態的明確語義、跨 generation 恢復。此輪是 readback 證據 backend,不能列為完整 unknown recovery;其餘 P0–P3 保持必做。
## 授權 file receipt/readback API(2026-09-10)
- 新增 `POST /api/bots/{id}/file-operation`,body `{ "operationId": "原操作 ID", "readback": true }`;沿用既有登入/路由授權,拒絕未知欄位與空 ID。readback 省略時只取 durable receipt。
- 從中央 dispatch mapping 取得 root operation ID,核对 bot user/space、目前 Computer binding/provider/generation/running,無匹配回 404,不發出 Runner request。查詢使用該 bot/generation 的 `file_status`,不派發 mutation。
- helper 回來後重查 lifecycle/binding/owner;查詢中已更換 Computer/generation 回 409。25 秒 deadline,狀態/code 與 64 KiB 回應限制;原始 stderr 不回傳。
- 回傳 root status 與 evidence,不寫中央 operation status,不把 desired_state_matches 自動當成 execution_proven。此 API 是只讀核驗入口,尚未接 Tool schema/UI 或明確的恢復決策。
- 驗證:SQLx+SDK HTTP 測試涵蓋同 owner 查詢、不同 user 無 HTTP、執行中 generation 變更拒絕、舊 generation 事前拒絕、中央 unknown 保留;API Clippy all targets(-D warnings)、format/diff check 通過。本輪不冒充完整 HTTP登入→Computer E2E。
## Agent file_operation_status 工具(2026-09-10)
- 新增 `file_operation_status` schema/dispatch,參數 `targetOperationId`、可選 boolean `readback`;目標 operation ID 與這次查詢 ID 分開,不重新 reserve mutation intent。
- API handler 與 agent 共用 file_status lookup 的 owner/binding/provider/generation 核對與 timeout。列入 native sandbox readiness,不需要 GUI/vision;run 指引引導 unknown file result 先查 receipt,不自動重做。
- write/patch/move 的失敗回覆新增結構化 operationId/errorCode/message,使 agent 能取得實際原操作 ID 執行 readback;保留既有 typed error code。
- 擴充 SQLx HTTP fixture,以正式工具 dispatch 查詢既有 unknown 操作,確認保留 unknown、execution_proven=false、中央 operation 筆數不增加;native routing/唯讀判定與 unknown 回覆原 ID 另有測試。
- UI 顯示、明確恢復決策與 unknown→verified 狀態語義、patch 完整目標 fingerprint、跨 generation 恢復及其餘 P0–P3 尚未完成。
- 驗證:完整 API 187 passed/4 ignored;最終 operation ID 回覆定向測試、workspace Clippy all targets 與最終 API Clippy(-D warnings)、format/diff check 通過。本輪未重跑真實 Docker gate。
## 活動紀錄 File Evidence UI(2026-09-10)
- run activity payload 補上經 owner 查詢取得的 botId,使既有歷史 tool entry 也能定位目前授權 bot。write/patch/move 活動列有 operationId 時顯示「核對檔案結果」。
- 按鈕只呼叫既有 `POST /api/bots/{botId}/file-operation` 並帶 readback=true,不送 mutation/retry;分別呈現歷史已核驗 receipt、目前符合/不同、證據不足、操作不可用/generation 變更/查詢失敗。繁中與英文文案均不把 desired_state_matches 當成原操作已成功。
- 顯示 receipt 或觀察的檔案路徑;React 文字渲染處理特殊字元。請求期間禁用按鈕,關閉面板 abort 並忽略遲到回應;鍵盤 Enter/Space 不被外層 RunProbe 攔截,Escape 仍可關閉。
- 前端測試新增狀態文案真值表與按鈕 API/取消/late response/鍵盤事件驗證。58 項 frontend tests、TypeScript、Vite production build 通過;API Clippy all targets 在停用 incremental cache 後通過(初次檢查遇到 rustc incremental ICE,並已修正 RunRow 所需 bot_id 欄位)。diff check 通過。
- 尚未做瀏覽器視覺/登入→真實 Computer 點擊 E2E;明確恢復決策、unknown→verified 語義、patch 完整目標 fingerprint、跨 generation 恢復與其餘 P0–P3 仍待完成。
## Patch pre-effect 完整目標 fingerprint(2026-09-10)
- Computer helper 新增內部 `patch-plan`,沿用相同 byte range/base64/SHA-256 CAS/8 MiB 驗證,僅計算完整 patched bytes 的 hash/size,不建立暫存檔或修改原檔。
- Root 先提交 accepted intent,再執行只讀 plan;驗證 plan 的 path/precondition/hash/size,將 desiredHash/desiredSize 持久寫入受保護 metadata,提交成功才啟動真正 patch。metadata commit failure 不發生 file mutation。
- 真正 patch 再次驗證原檔 hash;plan 與 mutation 間的 concurrent edit 回 CONFLICT,不換用新 base。final receipt 額外核對 durable desiredHash/desiredSize,結果不符保留 unknown。
- 新 patch 的 unknown readback 現在可核對完整目標 hash;舊 intent 沒有完整 fingerprint 時仍回證據不足。desired_state_matches 仍不等於 execution_proven,不自動清除 unknown。
- helper 測試驗證 plan 唯讀、完整 binary 結果 hash/size、計算後人類修改被 CAS 拒絕;真實 root 測試注入 plan metadata commit fault 與 final receipt commit fault,驗證不提前修改、完整 readback、後續人類修改與禁止自動 replay。
- 驗證:26 項 helper、9 項 root file、完整 Docker gate(Runner/MCP/packages/lifecycle/native file/真實 API dispatcher recovery)及 workspace Clippy all targets(CARGO_INCREMENTAL=0,-D warnings)、diff check 通過;fixture 清除。
- 明確恢復決策仍需 child quiescence/執行來源等證據,不可只靠當前 hash 清除未知效果;跨 generation recovery、完整隔離/資源配額與其他 P0–P3 尚待完成。
## File child handoff 與 quiescence 證據(2026-09-10)
- 每個新 file intent 保存 controller PID/start/boot 與 handoff_required;root 用 Popen 啟動可信 mutation helper,先持久提交 child identity,才傳送交接與 payload。plan-only helper 不修改檔案。
- Helper 在開啟父目錄/建立路徑/修改 bytes 前,要求完整 handoff、payload byte length 與 SHA-256;missing/partial/extra payload 全部拒絕。root 中斷傳輸時不能把截斷但仍合法的 base64 當成完整檔案。
- child identity commit failure 會 kill/wait 尚未獲准的 helper,不發生檔案 mutation;timeout/例外清理同樣回收 owned child。已接受但結果未知的操作仍禁止 replay。
- Unknown readback 新增 helper_quiescent:相同 boot 下 PID/start 相符且存活回 false,已退出/zombie/原 PID 已被其他 start 重用回 true;沒有舊 handoff 證據、無法讀取程序資料、boot/namespace 不同回 null。不同 namespace 不足以證明舊容器已停止。
- 真實測試新增 child identity commit fault 不建立任何目錄;實際 kill controller、child 等待目錄鎖仍判存活,釋放鎖後完成但 receipt 遺失,readback 判 child 已停止且仍保持 unknown。另新增 handoff 缺失/payload 截斷/extra bytes 拒絕測試。
- 驗證:27 項 helper tests、11 項 root file tests、完整 Docker gate 含 lifecycle/真實 dispatcher recovery 通過;最後 boot 證據收緊另有 2 項 file-process-proof tests(已接 gate)、workspace Clippy all targets 與 diff check 通過。fixture 清除。
- 此處只證明可信 file mutation helper 是否仍可繼續寫入,不宣稱全 task process tree 隔離;明確的 unknown 恢復決策、跨 generation 舊容器退役證據、全系統配額及其他 P0–P3 尚未完成。
## Readback metadata/lifecycle 競態修復(2026-09-10)
- 覆查發現:status 先 SELECT controller-only metadata,controller 隨後提交 child identity 並退出時,舊 snapshot 可能錯把仍存活的 child 判為 quiescent。現在確認 controller 已退出後,再讀一次受保護 metadata 並核對 child;此後 controller 已不能再提交新的交接。證據仍維持 unknown,不重送 mutation。
- 新增 SQLite 回歸測試,在 status 已讀取 metadata 後、程序身分觀察時提交 child identity,重現這個順序;驗證回覆及持久 observation 都是 helper_quiescent=false,即使當前檔案 hash 已符合目標。
- API readback 的第二次 lifecycle 檢查補上 running state;查詢途中 Computer 停止時回 409,後續查詢回 404 且不再呼叫 Runner。既有 owner/generation 檢查與 unknown 保存測試仍通過。
- 驗證:3 項 process-proof tests、完整 Docker gate(10 Runner、11 root file、5 HTTP MCP、5 package,以及 restart/migration/remove-recreate/native files/Supervisor HTTP/實際 API dispatcher 遺失回應恢复)通過;API owner/lifecycle/generation 定向 SQLx 測試、workspace Clippy all targets(CARGO_INCREMENTAL=0,-D warnings)與 diff check 通過。自建測試容器已清除。
- 對應 O03/O41/O45、T17/T24 的恢復證據正確性;兩模式共用相同 root helper 與 API 驗證路徑。本輪未變更 schema,尚未部署;回退此修復會重新引入已重現的錯誤判定。
- 下一步仍須完成明確 unknown 恢復決策、跨 generation 退役證據與 outbox 全頁異常事件的公平補送;P0–P3 全案尚未完成,不把此回歸測試當成完整驗收。
## File outbox 有界掃描與公平續送(2026-09-10)
- 修正整頁 retained failure 永遠遮住後續有效 receipt 的問題。新增 migration 033,為每個 Computer/provider/generation/bot poll scope 持久保存 scan_after/scan_through;每輪固定上限,完成後回頭重試舊事件,不因持续追加而永久跳過失敗資料。
- Root file_events 支援嚴格整數 cursor,仍以完整 identity 查詢;每次最多 100 筆/256 KiB。從 SQLite 只讀取每筆最多 128 KiB+1 的 bytes,損壞 JSON、非文字、過大、NUL 尾碼及無效 payload 回 file.invalid 參照;不刪原始列,也不回傳損壞內容。Unicode escaping 擴大後仍受單筆上限約束。
- API 驗證 cursor、嚴格遞增 event ID 與固定 snapshot 上限;在每筆處理前持久 checkpoint,lease token/期限不符即拒絕。不能只在整頁前推 cursor,否則單頁第一筆慢 transaction 仍會餓死後面的事件。
- 只有中央 event/receipt/artifact/notification transaction 成功提交的 ID 才 ack;cursor 不是 ack。timeout、取消、ack 遺失或拒絕處理的事件保留並於下一輪 snapshot 重訪。旧 Runner 的空 stdout 仍走既有補送行為,完整分頁能力需新版兩端。
- 新增 root SQLite 測試:100 筆損壞資料後的有效 receipt 仍可讀取;途中新增事件等下一輪;失敗列留存、跨 bot 拒絕、cursor 型別/範圍拒絕、blob/深層陣列/大資料/NUL 不阻塞。
- 新增 API SQLx+HTTP 測試:整頁 poison 後有效結果完成且僅 ack 已提交 ID;第一筆資料庫 pg_sleep 超時,transaction 回滾但下一筆下輪仍成功;cursor 越界/跳號/逆序與 snapshot 變更拒絕。既有失去 ack、中央回滾、artifact 去重、peer 公平測試維持通過。
- 驗證:file operations 12 項定向測試、完整 API 191 passed/4 ignored、最終 root scan 測試及完整真實 Docker gate(Runner、file、MCP、package、lifecycle、HTTP、API dispatcher receipt recovery)通過;workspace Clippy all targets(CARGO_INCREMENTAL=0、-D warnings)與 diff check 通過,自建容器已清除。最後另補 lease 過期 worker 不得修改 replacement cursor 的定向回歸。
- 對應 O03/O30/O41/O45、T17/T18/T31。Team/Dedicated 沿用相同按 bot scope 的掃描,不修改兩模式共享策略。未部署;回退需保留 root outbox/receipt 及 migration 033 的游標欄位,舊 API 可忽略欄位,不能刪失敗事件來假裝恢復。
- 下一步:明確 unknown 恢復決策、跨 generation 舊 runtime 退役證據、outbox/scope 長期保留與全系統公平資源配額。其餘 P0–P3 尚未全部完成。
- 最終補充:lease replacement 定向測試已通過。gate 後覆查再加入 JSON transport canary(NaN/無窮值/超過 i64 的整數/孤立 surrogate),避免 Python 接受但 Rust 拒絕整批 JSON;root scan 測試已覆蓋並通過這些案例,保留異常事件參照,不回傳原始內容。此最後解析收緊由直接 SQLite 回歸測試驗證,未再重跑完整 Docker gate。
## 同 generation 檔案 unknown 明確恢復(2026-09-10)
- `file_status` 新增顯式 resolve=true(必須同時 readback=true),同樣使用原 operation/bot/Computer/generation 的受保護 mapping。只有 helper 已停止、controller 已停止、目前檔案後置條件符合時才完成恢復;缺舊 handoff、不同 boot、程序仍存活、hash 不符、讀取或 fsync 失敗都維持 unknown,不重播 mutation。
- 恢復會重新讀取並 fsync 檔案與父目錄;move 改以 fingerprint-pair 同時持有兩端目錄 SH locks,讀取 target 後再核對 source。這是 cooperating file writers 下的後置條件核驗,不冒充任意 shell 的強隔離或原 rename 的因果證明。
- Root 以單一 SQLite transaction 更新 receipt/operation 及 outbox;重複或並行恢復先檢查既有 terminal receipt,不新增重複完成事件。恢復紀錄使用 verificationBasis=postcondition、executionProven=false,並保存 helper/controller quiescence;succeeded 在此表示明確恢復的後置條件已核验,不能推論原操作曾成功執行或 exactly-once。
- API owner/current binding/generation/running fence 同樣適用 resolve。`file_operation_status` 工具 schema/dispatcher 已接旗標;中央 outbox reconcile 驗證上述 proof 欄位並在公開結果保留 verificationBasis/executionProven,沿用 artifact/notification 同交易提交。中央狀態透過既有背景補送收斂,不把 root 回覆當成中央 transaction 已完成。
- UI 在 unknown、目前符合且兩個程序均停止時提供「重新核驗並完成恢復」;點擊仍重新做後端核驗,使用獨立文案標明後置條件恢復與歷史執行未證實。繁中/英文、請求旗標與 controller 存活時不顯示按鈕均有回歸驗證。
- 真實 root 測試涵蓋 write/patch/move 的 original receipt commit fault、hash 被人修改時保持 unknown、resolve receipt transaction failure 不發成功事件、重複恢復不重送事件、後續 replay 不覆寫人工修改。Host process-proof 測試額外驗證 child/controller 任一存活均不能完成恢復;helper 測試驗證兩目錄鎖及 fsync fault 無成功輸出、不改 bytes。
- 實際 API dispatcher→Supervisor→Computer gate 新增原 root receipt 提交失敗,再透過 status/readback/resolve 工具恢復、中央 outbox 登錄 artifact、原操作 replay 保留 proof basis 且不改人工內容;這仍不是登入/模型/瀏覽器 UI 的全程 E2E。
- 驗證:29 helper tests、3 process-proof tests、12 root file tests、58 frontend tests、TypeScript/Vite production build、完整 API 192 passed/4 ignored、完整 Docker gate(含新增實際恢復)、workspace Clippy all targets(CARGO_INCREMENTAL=0、-D warnings)及 diff check 通過;自建容器已清除。最後新增 fsync fault 測試另行通過,沒有變更 gate 後的產品程式。
- 對應 O03/O41/O43/O45、T17/T18/T24/T35。兩模式保留,同樣依 bot scope 處理;本輪不新增 schema、不部署。回退須保留已建立的 postcondition receipt/outbox/中央紀錄及其語義,不能抹除恢復歷史或退回會遺失 proof basis 的版本。
- 下一步仍有跨 generation 的舊 runtime 退役證據、舊 intent 恢復、其他檔案寫入路徑 journal、immutable artifact 與串流、完整隔離/scope barrier/資源配額、Tool Manager/OAuth/mail adapters,以及尚缺的 P2/P3 backend 與基準測試。全案保持未完成。
## 退役證據與 artifact 核驗來源持久化(2026-09-10)
- 新增 migration 034:computer_generation_retirements 保存 Computer/generation/provider_ref/home_key/唯一 proof_id/provider_destroy_ack 及時間。只有 destroy 成功回覆且 generation/provider CAS 仍相符時,才與 generation 退役、jobs interruption 同交易寫入;不根據 changed provider/changed namespace/單純 generation 增加回填證據。
- SQLx 故障驗證涵蓋 destroy 失敗不留證據;proof INSERT 失敗不退休 generation/jobs;proof 已寫入後 job UPDATE 失敗會回滾 proof、generation 與 job;stale ack/providerless active jobs 拒絕;一般 provider 切換不憑空建立第二筆 ack。這是中央持久證據基礎,尚未拿來自動解除跨 generation unknown。
- Artifact 新增 verification_basis,明確區分 execution_receipt、postcondition 與 legacy_unspecified;既有資料保留未知來源,不因 migration 推測歷史執行。可信 receipt 與舊 path/hash/size 完全相符才補登來源;已有來源不允許被不同 receipt 改標。
- Catalog API 傳回 verificationBasis,新增 owner scope 與三種來源的實際 handler/PostgreSQL 測試。中央 receipt parser 同時拒絕帶 recovery proof markers 卻缺 verificationBasis 的資料,避免默認成原執行 receipt。
- Artifact 回歸測試驗證舊 ID 保留、可信 postcondition 來源補登,以及來源衝突時不提交 event;實際 API dispatcher/Supervisor/Computer recovery gate 驗證一般 write/patch/move artifact 為 execution_receipt,root receipt 遺失後明確恢復的 artifact 為 postcondition。
- 驗證:完整 API 193 passed/4 ignored;最後 parser/artifact 14 項定向測試、catalog handler 測試及擴充的 retirement 交易回滾測試通過;完整 Docker gate 含實際恢復/artifact 來源驗證、workspace Clippy all targets(CARGO_INCREMENTAL=0,-D warnings)、diff check 通過。後補 retirement 測試未改產品程式;自建容器已清除。
- 對應 O28/O41/O43/O45、T15/T17/T18。兩模式共用相同持久證據與 scope;未部署。回退需保留 migration 034 的退役歷史及 artifact 核驗來源,不可把 legacy/postcondition 統一改標成 execution_receipt。
- 下一步:安全地將退役 acknowledgement 與舊 root journal/home_key/目前授權綁定,接通跨 generation readback/明確恢復;不能只因中央多一筆 proof 或容器名稱改變就跳過程序停止證明。其餘未完成 P0–P3 保持原範圍,並非已完成跨 generation 驗收。
## Artifact 授權 fence 與 Computer 內 verified read(2026-09-10)
- 修正下載只查 artifact 擁有者、卻不查 bot 目前綁定的缺口。讀取前要求同 owner/space、current Computer binding、running/provider;Team 路徑需精確落在 own workspace/shared,Dedicated 保留 rooted relative path;非法 path/hash/size 不開始 transport。
- 讀取完成後重查 artifact 仍存在且 path/hash/size 未換、owner/binding/provider/generation/state/scope/home_key 仍相符,改綁、停機、generation 變更、產物撤銷或 storage scope 變更皆不回傳 bytes。請求攜帶 bot/generation context,25 秒 deadline;後端錯誤以固定訊息回覆,不把任意 backend text 洩出。
- 下載標頭改為 UTF-8 percent-encoded filename*,安全處理中文、引號、分號、CR/LF;加入 private/no-store 與 nosniff。內容型別維持 octet-stream。
- 新增 SandboxProvider::read_file_verified、Supervisor /read-verified 與 Computer helper read-verified。helper 在 Computer 持目錄 SH lock,讀取有界 binary bytes,比對 expected SHA-256/size 後才輸出;不符合、特殊檔案、symlink 或無效條件不輸出內容。API artifact handler 不再計算檔案內容 hash。
- SDK 核對 verified/path/hash/size/base64 及解碼長度,拒絕缺 receipt、錯 path/hash、截斷、UTF-8 fallback、過大回覆;transport 分 chunk 收集,依 expected_size+有界 metadata 限制 JSON 大小。這仍是最多 8 MiB 的有界 buffer 下載,不是完整大型檔案串流。未知 provider 不用 API-side hashing/分開 stat/read 作假 fallback。
- PostgreSQL+實際 HTTP fixture 覆蓋 Team/Dedicated binary 下載、owner 拒絕、14 種傳輸途中 lifecycle/ref/receipt/response 異常、跨 bot/path traversal 拒絕、size 不符與 header escaping;最初測試中的私人模式 fixture 名稱已修正為實際 enum dedicated。
- helper 新增 1 MiB binary verified read、hash/size/type 拒絕、symlink、空檔與後續人工修改不洩 bytes;實際 API SDK→Supervisor→Computer gate 增加正常與修改後 CONFLICT、最終 1 MiB binary verified roundtrip。
- 驗證:30 helper tests、3 artifact 定向測試(含最終 14-case guards)、完整 API 196 passed/4 ignored、完整真實 Docker gate 含 1 MiB verified transport、workspace Clippy all targets(CARGO_INCREMENTAL=0,-D warnings)及 diff check 通過。最終 SDK guard 更新已做定向 HTTP 回歸與最終 Docker gate;不冒充登入/瀏覽器下載的全程 E2E。
- 對應 O03/O43/O45、T06/T09/T10/T21/T34/T64。未部署;API/Supervisor 需一起更新才能使用 verified read,舊 provider 維持拒絕而非降級繞過核驗。本輪無新 schema,不修改現有 artifact bytes;自建容器已清除。
- 下一步仍有 immutable artifact 快照/保留策略、upload/download/attachments 真正串流、其他 task file processing 路徑,以及跨 generation 恢復的完整可信證據鏈;其餘 P0–P3 不縮減、不標成已完成。
## Computer 內不可變 file artifact 快照(2026-09-10)
- 新的 journaled write/patch/move:可信 UID 1000 helper 在 mutation/readback 核驗及目錄鎖內保留結果 bytes,僅透過 child→root 私有 pipe 帶 snapshotContentBase64。root 比對 hash 後將 bytes 寫入 /var/lib/lazyboy-runner/artifacts,移除 payload 欄位才發布 receipt/outbox;中央 ledger/catalog 不存 task contents。
- 快照以 SHA-256 命名、root-owned 0600/目錄 0700;用 private temp、fsync、no-clobber link 與目录/父目錄 fsync 發布。受保護 lock 序列化儲存與容量檢查;相同 hash 重用前核對 bytes/ownership/hash 並再次 fsync,避免前次 sync 失敗後 dedup 誤報成功。
- 原操作結果未知而明確做 postcondition recovery 時,先再次由 Computer read-verified 讀取符合 hash/size 的 bytes,保存快照後才提交 recovered receipt。快照寫入/容量失敗不能發布成功;原工作檔案可能已變更,此時保留 unknown,清出容量後仍須明確恢復。
- 每個 store 暫以 256 MiB/4096 entries 有界;orphan/pending files 也計入。這是容量上限,不是完整 retention/自動 GC;舊 receipts 缺 snapshotRef 不捏造已保存 bytes,繼續顯示 legacy artifact 行為。
- 新 file_artifact 管理操作必須以同 bot 的原 Computer identity/generation/operation 找到 succeeded receipt,且 artifactRef/hash/size 相符才能讀取 root blob;知道 hash 不構成授權。固定 helper 重新核對 blob,不接受檔案系統路徑。已成功的歷史快照可在目前較新 generation 中讀取,但不因此解除任何舊 unknown mutation。
- migration 035 保存 snapshot_ref;正常/receipt recovery 同交易登錄,保留驗證來源與 legacy ID。Catalog 明示 immutable;API 用 durable dispatch mapping 取得 origin,走專用 Supervisor /artifact 與有界 SDK verified response。缺 origin 或壞快照拒絕,不退回已修改工作檔案;既有 owner/binding/lifecycle fence 及 snapshot_ref 重查持續生效。
- Root 測試覆蓋普通及 recovered snapshot、原檔後續人工修改、錯 bot 拒絕、歷史 generation read、UID 1000 直接讀取拒絕、receipt/outbox 不含 base64 payload;容量滿維持 unknown,移除 fixture padding 後明確恢復。獨立受保護 store 測試覆蓋 inode dedup、tamper/symlink 拒絕、fsync 故障後再次拒絕及成功重試。
- 實際 lifecycle gate 新增 restart/legacy migration/remove-recreate 後讀回 snapshot bytes;實際 API dispatcher→Supervisor→Computer 驗證 write/patch/move 在人類修改後仍讀回當時產物,錯 bot 無法讀取。API handler+HTTP fixture 驗證跨 generation immutable download 只取 durable origin,missing mapping 不走 workspace fallback。
- 驗證:197 API passed/4 ignored、13 root file tests、2 protected artifact tests、原有 30 helper tests、完整 Docker gate(含 snapshot lifecycle/recovery)、workspace Clippy all targets(CARGO_INCREMENTAL=0,-D warnings)與 diff check 通過。最後 download row 改具名 struct 修正 lint;artifact read 失敗時固定錯誤文字,避免部分 stdout bytes 被放進 control-plane error。自建容器已清除;沒有部署。
- 對應 O03/O41/O43/O45、T09/T17/T18/T21/T34/T64。兩模式仍以同 bot receipt 授權;root-private storage 跟隨既有 runner 持久掛載。回退必須保留 snapshots、snapshot_ref、原 receipts 與 provenance;不能將已有 immutable reference 降級為 live path 或刪掉 blob 假裝回滾。
- 尚缺:完整 per-agent quota/retention/GC、全工具輸出 artifact、真正大型檔案/附件串流、跨 generation unknown 恢復可信證據鏈,以及仍列在 audit 的其餘 P0–P3。沒有將全案或 O43 全列宣告完成。
## 快照發布程序死亡後的暫存檔回收(2026-09-10)
- 修正 root 快照 publisher 被強制終止後,遺留 `.pending-<32 lowercase hex>` 永久占用容量的問題。新 publisher 先取得同一 artifact-store.lock,再做有界 inventory;此時前一 publisher 已釋放核心鎖,才可移除符合精確暫存命名且 root-owned/private/regular 的檔案。
- 不刪除已發布 SHA-256 名稱或其他未知名稱;發布後、unlink 暫存檔之前死亡的 hardlink 只移除 pending link,保存原 published inode。symlink/不安全 ownership 或 mode 拒絕處理。清理後 fsync 目錄,失敗不發布成功;重試仍須完成正常 snapshot durability。
- 6 項 protected artifact tests 通過,新增實際子程序在 file fsync/link 後被 kill、發布 inode 保留、活 publisher 持鎖時清理等待、256 MiB orphan quota 回收、不認得的 pending-like 檔案仍保留並計入 quota,以及 cleanup fsync 失敗/重試。
- 完整真實 Docker gate 通過:含 13 root file tests、Runner/HTTP MCP/package 原有測試、restart/migration/remove-recreate、native file transport、Supervisor HTTP 與實際 API recovery。gate 執行時包含前 5 項 protected artifact tests;其後新增的第 6 項 fsync fault test 已獨立通過,產品程式未再變更。Supervisor all-targets Clippy(CARGO_INCREMENTAL=0、-D warnings)通過。
- 對應 O43/T18/T64;只回收不能被活 publisher 擁有的未完成發布暫存檔,不是已發布 artifact 的 retention/GC。沒有新增 schema/部署;回退不得刪除 published blobs。全工具 artifact、per-agent quota、published retention、串流與其餘 P0–P3 仍需完成。
## File journal 准入容量與歷史 receipt 保留(2026-09-10)
- 修正所有 file_operations(包含成功/失敗終態)累積 1024 筆後永久拒絕新操作的限制。仍保留每筆 request digest/receipt/unknown intent;不靠刪除歷史 IDs 釋出名額,避免過期重送變成第二次 mutation。
- 新准入在原 BEGIN IMMEDIATE 中執行:accepted/unknown 合計最多 1024 個未完成名額,status index 支援查詢;另按 SQLite page_count−freelist_count 乘 page_size,要求 128 MiB 使用頁面預算內尚有 64 KiB 准入餘裕。已完成紀錄不占未完成名額,仍占真實 metadata 空間。
- 既有操作的 digest/receipt replay 檢查先於准入限制;file_status/receipt completion/明確 recovery 不走新 intent 准入。SQLite 空頁可再用,因此刪除已允許回收的其他資料後不必 VACUUM 才能恢復准入。此預算計入同一 Runner DB 的其他表,不冒充 per-agent 公平 quota、WAL 上限或整機硬磁碟配額;持續增長到容量上限仍須 retention/管理政策,未丟棄 anti-replay 證據。
- 3 項新 SQLite 測試通過:accepted+unknown 名額及終態釋放、不刪 receipts;實際 128 MiB zeroblob 壓力拒絕新 intent/outbox,原 replay/status 保留,釋放後重用 freelist;兩連線競爭最後一個名額僅一方成功。
- 真實 root file gate 改為先以 1024 accepted fixtures 驗證拒絕,再保留同 1024 rows 改成終態,驗證新 file mutation 成功;人類改檔後重播原 receipt 不再覆寫。完整 Docker gate 通過,含 13 root file tests、6 protected artifact tests、Runner/HTTP MCP/packages、restart/migration/remove-recreate、Supervisor HTTP、實際 API dispatcher recovery。gate 包含前 2 項 SQLite tests;最後追加並行名額測試另行通過,產品程式未再變更。
- Supervisor all-targets Clippy(CARGO_INCREMENTAL=0、-D warnings)通過。HTTP MCP fixture 曾在 client 關閉 GET 後記錄 BrokenPipeError;已檢查來自 fixture 的空 405 reply,5 項 MCP assertions 及整體 gate 均通過,不記為產品失敗或額外修正。
- 對應 O03/O41/T18;未部署,沒有中央 migration。root SQLite 新 index 為相容變更;回退要保留既有 receipt,舊版仍會恢復 lifetime count 限制。其餘 P0–P3、完整 journal/artifact retention、公平配額與串流仍未完成。
## 每個 bot 的 file journal 未完成額度(2026-09-10)
- 在既有全機 1024 個 unresolved 名額與 SQLite 使用頁面准入預算之外,加入每 bot 256 個 accepted/unknown 上限,滿額回 FILE_AGENT_QUOTA。以 journal identity 中的 stable bot_id 累計,跨 provider/generation 共用額度,不能藉重建重置未知操作負債。
- 計數與 intent INSERT 共用原 BEGIN IMMEDIATE;查詢最多處理全機准入上限內的 identity groups。既有 receipt replay 仍先於新准入檢查;終態紀錄保留但釋放未完成名額,沒有刪除或重執行 unknown。
- 新真實 root file 測試建立跨兩個 origin identities 的 256 筆 accepted/unknown,驗證原 identity 與新 provider/generation 都拒絕且沒有改檔;舊 receipt 仍可重播,另一個 bot 寫入成功;一筆轉終態後原 bot 恢復准入。
- SQLite 並行最後名額測試擴充為全機與每 bot 兩組,兩個真實連線競爭只允許一方提交。3 項容量測試、14 項 root file tests、6 項 protected artifacts、完整 Docker gate(Runner/MCP/packages/restart/migration/remove-recreate/native transport/Supervisor HTTP/實際 API recovery)及 Supervisor all-targets Clippy 通過;自建容器已清除。
- 對應 O30/O41/T18/T63;Team 與 Dedicated 共用同一每 bot 限制。這只防止單一 bot 耗盡全機 unresolved slots,不保證多個滿額 bot 同時存在時仍有全機容量,也不是 CPU/RAM/磁碟公平排程或所有工具的 request quota。
- 未部署、沒有新 schema。回退需保留所有原 receipts/unknown;其餘 P0–P3 的完整公平隊列、retention/streaming、跨 generation 恢復與其他缺項仍待完成。
## File status/readback 的儲存與授權 fence(2026-09-10)
- 檢查跨 generation 恢復入口時發現 file_status 只核對 bot owner 與 provider/generation,未確認 Computer owner/space 相符,也未在讀取後重查 storage scope/home_key。現已在查詢前要求 Computer 與 bot 同 owner/space,並使用實際 home_key 建立 ComputerRef。
- 傳輸後除原 running/binding/generation fence 外,再核對 Computer owner/space、scope/home_key/kind,以及同 operation 的 durable dispatch provider/generation/runner identity/runner operation。原 mapping 被刪除或替換、儲存位置或授權改變時回 CONFLICT,不回傳過時證據。
- SQLx+實際 HTTP fixture 新增 7 種途中異動(Computer owner、space、模式、home_key、root operation ID、Runner ID、刪除 dispatch),全部拒絕;另驗證事前 Computer owner/space mismatch 不呼叫 transport。原 status、工具 resolve、generation/stop race 與不新增 mutation intent 的測試保留。
- 驗證:完整 API 197 passed/4 ignored、擴充 file_status 測試、API all-targets Clippy(CARGO_INCREMENTAL=0、-D warnings)、完整真實 Docker gate(14 root file tests、容量/snapshot/lifecycle、實際 dispatcher recovery)與 diff check 通過;自建容器已清除。沒有部署或 schema 變更。
- 對應 O41/O45/T17。這是目前 generation 的恢復入口授權補強,不宣告跨 generation unknown 恢復已完成。後者仍需把中央 provider_destroy_ack、原 home_key/root identity 與目前授權經可信管理通道綁定;不能接受一般 request 內自稱退役的欄位作停止證明。
- 其餘 P0–P3 全案範圍維持;仍缺完整 retention/streaming、跨 generation 恢復、其他路徑 journal、公平排程及 audit 所列驗收。
## 重建後的唯讀 file receipt 歷史查詢(2026-09-10)
- 新 Computer root file_history:只接受 operation_id/origin_computer_id/origin_generation,以目前 authenticated bot_id 組成舊 identity,要求 origin generation 不超過目前 generation,查原完整 identity+operation hash。其他 bot、未來 generation、夾帶 readback/resolve/bot override 均拒絕。
- 歷史成功/失敗 receipt 有界讀取並回傳;accepted/unknown 明示 execution_proven=false、observation_performed=false、desired_state_matches=null、retirement_proof_required。不呼叫 helper、不讀目前工作檔、不修改 receipt/observation/outbox,沒有以新 PID namespace 代替停止證明。
- API file-operation lookup 現在允許同一 current Computer binding 下較早 generation/provider 的 durable dispatch。用目前 provider/generation 送到新 Computer 的 file_history,body 只帶原 Runner identity;外層回覆新增 historical/originGeneration。跨 generation resolve 在 transport 前回 CONFLICT,普通查詢仍可取得已知 receipt 或明確未知狀態。原 owner/space/storage/kind/dispatch mapping 傳輸後 fence 保留。
- 更新 file_operation_status 的模型工具描述,說明歷史 unknown 不會被 readback 或 resolve。歷史查詢不會把舊 outbox 自動 ack,也尚未將歷史 receipt 回填中央 operation/artifact;該同步流程與 unknown retirement-proof recovery 仍需完成。
- 新 root test 驗證原檔被人類修改後取得歷史 receipt、錯 bot/未來 generation/額外欄位拒絕,以及未知操作查詢完全不改檔/receipt/observation/outbox。實際 restart/legacy migration/remove-recreate gate 新增以較新 identity 的 file_history 取得原 receipt。
- API HTTP+PostgreSQL 測試覆蓋 replacement provider、current generation header 與 original identity body、historical/原 generation metadata、unknown 未觀察、resolve 拒絕及途中 home_key 變更拒絕。先前 current-generation 的授權與 mapping 異動測試持續通過。
- 驗證:197 API passed/4 ignored、15 root file tests、6 protected artifacts、3 capacity tests、完整真實 Docker gate(含既有實際 API recovery)及 workspace all-targets Clippy 通過。最後 replacement-provider HTTP fixture 與工具描述已納入完整 API suite;root 程式在 gate 後未再修改。自建容器清除、未部署。
- 對應 O03/O41/O45/T17/T64;Team/Dedicated 均以相同 current bot 授權讀歷史。回退要保留原 root receipts;舊 Runner 無 file_history 時拒絕,不降級成任意重做。沒有宣告跨 generation unknown 恢復或全部 P0–P3 已完成。
## 查詢歷史終態 receipt 時回填中央狀態(2026-09-10)
- API 歷史 receipt 查詢現在於傳輸後開交易,重新驗證並鎖住目前 Computer/bot/原 dispatch,核驗結果後才回覆。只處理 root 已保存的 succeeded/failed;歷史 unknown 保持未知,不做新檔案效果或停止推論。
- migration 036 的 computer_file_history_receipts 保存原 dispatch provider/generation/Runner/operation 與核驗後 outcome。這是歷史 receipt 觀察紀錄,不是本地 outbox 事件;不編造 local_id,不 ack 未讀取的舊事件。相同 proof 重播去重,proof 改變或與既有終態矛盾時拒絕。
- 重用既有 action/path/hash/size/provenance parser 與 artifact registration;原 operation accepted/unknown 可更新為 receipt 終態,artifact、history proof、operation result、Activity outbox 在同一交易提交。保留 execution_receipt/postcondition 區別及 immutable snapshot reference;失敗不把 root 事實刪掉或重跑操作。
- SQLx fault test 在最後 Activity INSERT 注入故障,證明前面已寫入的 artifact/history/operation 全數回滾;恢復後兩次回填只各留一筆 artifact/history/Activity。本地 computer_file_events 維持零新增;unknown 及矛盾 failed receipt 拒絕。
- API HTTP fixture 以 replacement provider 歷史成功回覆驗證中央 unknown→succeeded、artifact 登錄及重複查詢去重;所有原 owner/storage/origin fence 測試持續通過。
- 真實 API→Supervisor→Computer gate 新增:保留 root postcondition receipt、模擬中央 completion/artifact 遺失、將 fixture generation 更新後做兩次歷史查詢;中央恢復 succeeded,重建 artifact 保持 postcondition 與原 snapshot reference,Activity 一筆、local event 數不變、人類後續修改仍原封不動。generation 更新只用來測歷史查詢,沒有冒充舊程序退役證據。
- 驗證:完整 API 198 passed/4 ignored、完整 Docker gate(15 root files、snapshot/quota/lifecycle 及上述歷史回填)、workspace all-targets Clippy 與最後新增 E2E 的 API all-targets Clippy 通過;自建容器清除,未部署。
- 對應 O41/O43/O45/T17/T64。migration 036 的 proof/outcome 為持久 metadata,回退須保留;中央先部署 migration 才能使用新回填。尚缺不依賴查詢的歷史 outbox 背景續送/ack,以及 unknown 跨 generation 的可信退役與明確恢復;其餘 P0–P3 缺项不縮減。
## 歷史 receipt 與事件送達的終態一致性(2026-09-10)
- 補強兩條恢復途徑原本各自去重、未交叉核對的邊界。歷史回填先核對已送達的最新終態 payload;event persistence 在處理 succeeded/failed 時核對既有 history proof 的 origin/status/outcome。任一不一致先拒絕,不寫入 artifact/新 event,不能形成可 ack 的中央入帳紀錄。
- accepted/unknown 的晚到事件仍可保留與 ack,不因已有歷史終態而刪除歷史觀察;操作本身不退回 unknown。相符終態事件可正常送達,且不重複產生歷史回填已發布的 Activity。原有固定範圍游標/單筆錯誤隔離可繼續處理後續事件,矛盾 root 事件不被直接丟棄。
- 新增兩項 SQLx 測試:history succeeded → accepted/unknown/matching succeeded 送達後只有一筆 Activity,矛盾 failed 未入帳;已送達 failed 後即使中央 operation result 被 fixture 模擬遺失,也不能接受矛盾 historical succeeded,沒有新 history/artifact。
- 驗證:完整 API 200 passed/4 ignored、API all-targets Clippy(CARGO_INCREMENTAL=0、-D warnings)、完整真實 Docker gate 含原 postcondition 歷史回填/snapshot/15 root file tests/lifecycle,及 diff check 通過。自建容器清除;沒有新 migration 或部署。
- 對應 O41/O45/T17。此檢查保留已驗證的終態證據,不將衝突當成普通成功重播;不等於歷史 outbox 背景掃描已接通。下一步仍須將舊 scope 的分頁續送與中央提交後 ack 綁定目前授權,並完成跨 generation unknown 停止證明;全部 P0–P3 範圍維持。
## Computer 歷史 outbox 的分頁與 ack 入口(2026-09-10)
- 新 root file_history_events/ack_file_history_events,嚴格接受 origin_computer_id/origin_generation/cursor 三欄;origin bot_id 固定取自目前 authenticated identity,未來 generation 或額外 bot/resolve 欄位拒絕。唯讀掃描不形成程序停止或成功證明。
- 轉接既有 file_events 有界 fixed-snapshot scan 與 identity+event IDs 精確刪除,保留原事件 ID/payload,不改寫成目前 generation;既有 100 rows/256 KiB batch/128 KiB event、異常 placeholder/原始壞資料保留、游標驗證與 ack 上限繼續適用。
- 新 SQLite 測試驗證 100 筆壞事件後可讀到有效歷史事件;ack 含其他 bot/目前 generation IDs 時只刪相符 origin 範圍;錯 bot 為空集合,未來/boolean generation、額外欄位及非法 ack cursor 拒絕。
- 真實 root test 驗證較新 identity 的 scan 與原 outbox 完全相同、錯 bot ack 不刪資料、正確 ack 只刪指定前綴;lifecycle fixture 新增 restart/legacy migration/remove-recreate 後由較新 identity 讀取原未 ack events,內容與 IDs 不變。
- 驗證:2 outbox tests、15 root file tests、完整 Docker gate(snapshot/quota/jobs/MCP/packages/lifecycle/實際 API 歷史 receipt 回填)、Supervisor all-targets Clippy(CARGO_INCREMENTAL=0、-D warnings)及 diff check 通過。自建容器已清除,未部署,沒有新 schema。
- 對應 O41/T17/T64。這一輪是 Computer 端入口,中央背景 poll scopes 尚未改為選取舊 origin,也尚未透過此入口做提交後 ack;未宣告歷史背景續送已完成。回退保留原 outbox,舊版不支援新動作時不可改用目前 identity ack 代替。其餘 P0–P3 與跨 generation unknown 恢復範圍維持。
## 背景 file poll 選取前的 bot 授權檢查(2026-09-10)
- 檢查歷史 scope 接線時發現 claim_poll_scope 未連接目前 bot assignment:已解除綁定或 owner/space 不相符的 retained scope 仍可能先被選中並發起讀取,再由中央 persist 拒絕。現於 claim SQL 要求 bot 仍綁同 Computer 且 user_id/space_id 相符。
- 無權 scope 保留原 metadata,不取得 lease、不更新 last_polled、不因暫時改綁而丟掉 outbox 掃描位置;合法重新綁回後可重新選取。這是選取時的授權快照,不冒充整段傳輸的 lifecycle barrier;提交時的 binding/generation 檢查仍保留。
- 新 SQLx 測試覆蓋 detached、foreign owner、foreign space 不被選取,原合法 bot 正常取得 lease;檢查三個拒絕 scope 的 lease/last_polled 未動,重新綁回後恢復選取。既有公平輪替、lease expiry fencing 與 HTTP poll 測試仍通過。
- 驗證:完整 API 201 passed/4 ignored、API all-targets Clippy(CARGO_INCREMENTAL=0、-D warnings)與 diff check 通過。此輪僅改中央選取 SQL,沒有修改 Runner、schema 或部署;未重跑沒有變更的容器程序測試。
- 對應 O30/O41/T17。中央選取目前仍限現有 generation;歷史 poll origin 接線與提交後 ack 仍需完成,沒有把入口完成當成背景續送完成。P0–P3 原始範圍維持。
## 中央歷史 file outbox 背景續送(2026-09-10)
- claim_poll_scope 現在選取目前 Computer 下不晚於目前 generation 的合法 bot scopes,不再排除旧 provider/generation。保留 current provider/generation/running、bot assignment/owner/space 檢查及既有 lease/token/visited-bot 輪替。migration 037 加入跨 generation last_polled 排序索引。
- 每次 claim 後取出原 scope provider/generation/cursor;歷史 scope 必須從 durable dispatch 唯一解析原 Runner identity,缺失/歧義拒絕。請求以目前 Computer provider/generation/context 送達,file_history_events body 帶原 identity 與固定快照 cursor。
- persist_event 分開處理目前授權/儲存快照與原事件 identity:鎖目前 Computer/bot 並核對 captured owner/space/home_key/scope、running/provider/generation,再以原 dispatch/root identity 驗證 payload、登錄原 local_id/generation。已有 historical proof 的一致性檢查仍生效;accepted/unknown 不被當作程序停止或成功。
- 只有 PostgreSQL 提交成功的 IDs 進入 ack list。歷史 scope 使用 ack_file_history_events,body 帶原 identity;目前 scope 沿用原 ack。讀取前與 ack 前另重查目前授權/儲存快照,變更時不發起該 transport。原 4 次嘗試/每 scope 2 秒、逐筆 checkpoint、16 ID 前綴 ack、poison 保留與 lease fencing 持續適用。
- 新 HTTP/PostgreSQL 測試驗證 replacement provider+目前 generation header、原 identity cursor、壞事件後仍提交有效事件、第一次 ack 失敗後重送去重、ack 發出時 operation/artifact 已提交、Activity 僅一筆;home_key 改變後 scan 與 ack 都拒絕。
- 實際 API→Supervisor→Computer gate 新增未 ack 的 journaled write,保留 root 原事件並模擬中央 completion/artifact 遺失;fixture generation 前進後由 sync_events 自動恢復 succeeded 與 execution_receipt artifact,以原 generation 保存兩個事件,最後 root 歷史 outbox 為空。不依賴 file-operation 查詢,不重做檔案寫入。
- 驗證:最終完整 API 202 passed/4 ignored、完整 Docker gate(15 root files、root history scan/ack/lifecycle/實際背景恢復)、workspace Clippy 通過;最後補充 index 與 stale-ack assertion 已納入完整 API/gate。自建容器清除、沒有部署。
- 對應 O30/O41/T17/T64。scope/lease 仍是每 scope 且每個 worker 每輪排除已拜訪 bot;未宣告跨 replicas 的 per-bot 資源隔離或大量歷史空 scopes 的延遲 target 已驗收。舊未知效果的可信停止證明、明確恢復、retention 與其餘 P0–P3 維持待辦。
- 回退需保留舊 poll scopes/cursor/root outbox 與 event 原 generation;新 index 可保留。舊 API 恢復僅輪詢目前 generation,不可刪除尚未 ack 的歷史事件當作回退清理。
## 多 worker 跨 generation 的 bot poll lease 互斥(2026-09-10)
- 修正歷史 scope 接入後,同一 bot 的不同 generation 可以被不同 worker 同時取得有效 lease 的缺口。claim 改用短交易鎖住 stable bot row 與候選 scope;候選查詢排除該 bot 已有的有效 lease,取得鎖後再用新 READ COMMITTED snapshot 重查,才發布 lease。
- 明確 SET TRANSACTION ISOLATION LEVEL READ COMMITTED,避免部署環境改預設 isolation 使第二次檢查仍看舊 snapshot。busy bot/scope 使用 SKIP LOCKED;競爭重試最多 4 次,保留其他 bot 的選取機會,整個網路輪詢不持有 bot DB row lock。
- 新 SQLx 並行測試建立同 bot 兩個 generation,兩個 worker 同時競爭只一個成功;另一個 bot 正常取得 lease。原 lease 過期後可換 generation claim;過期 token 不能 checkpoint,遲到 release 不會清掉 replacement lease。
- 原逾時續送測試揭露新的合理排程差異:SQL future drop 不代表 PostgreSQL 已完成回滾,bot row 仍鎖住時新 claim 會跳過。測試改以限時取得該 row lock 等待實際回滾完成,再核驗 cursor 續送和只有後續事件提交;初次新增 fixture 的 lock_timeout 單位已修正為 PostgreSQL 支援的 5s,沒有放寬 outcome/assertions。
- 最終完整 API 203 passed/4 ignored,API all-targets Clippy(CARGO_INCREMENTAL=0、-D warnings)與 diff check 通過。此輪未改 Runner/schema/部署;驗證集中於真實 PostgreSQL 並行、lease 與既有 HTTP 續送回歸。
- 對應 O30/O41/T17/T63。這是使用新版 claim 的 worker 間有效 lease 互斥;不宣告舊版 replica 混跑或已過期但仍在外部執行的請求已被物理取消,也不是 CPU/RAM 隔離。全部 worker 升級後才可依賴新 per-bot claim 語意;rollback 不刪 scope/cursor。歷史空 scope 延遲/retention、跨 generation unknown 恢復及其餘 P0–P3 仍待完成。
## 已讀空歷史 scope 的輪詢延後(2026-09-10)
- migration 038 新增 next_poll_after,預設立即可選取。只有歷史 scope 回覆經驗證的空 batch+完成游標 (0,0) 時,才在再次核對目前授權後,使用尚有效的 lease token 延後 60 秒再查;不刪 poll scope、outbox、receipt 或原始 identity。
- claim SQL 過濾未到期的歷史 scope;目前 provider/generation 明確豁免延後時間,因此即使殘留未來時間也立即可選取。非空/壞事件與缺少有效 cursor 的回覆不當作已讀空,不藉 backoff 丟失事件。
- SQLx 測試驗證歷史空 scope 讓出輪詢機會、目前 scope 即使 future timestamp 仍可選取、歷史延後到期後重返輪替,以及釋放後的 stale lease 不能修改 delay;兩個 scope 均完整保留。到期測試透過 fixture 設定 DB 時間欄位,不等待固定 sleep。
- 真實 API→Supervisor→Computer gate 在完成歷史背景恢復與 ack 後,再次讀取真正空 outbox,確認 delay 持久化、cursor (0,0),立即重跑 sync 不更新該 scope last_polled。原 15 root file tests/歷史 artifact 回填/lifecycle 持續通過。
- 驗證:最終完整 API 204 passed/4 ignored、完整 Docker gate、API all-targets Clippy 及 diff check 通過。自建容器已清除,未部署。最後 current-scope 豁免已納入最終 API suite 與 gate。
- 對應 O30/O41/T17/T63。這降低已知空歷史 scope 的重複輪詢,不宣告大量從未掃描 scopes 的初始延遲或完整全機公平排程 target 已驗收。延後期間若舊 scope 又產生事件,仍會於到期重查;未因 generation 改變推論程序已停止。
- 回退可保留新欄位,舊 API 會恢復較頻繁輪詢而不是刪掉資料。跨 generation unknown 恢復的可信退役證據鏈、完整 retention/streaming 與其餘 P0–P3 仍需完成。
## 背景事件的執行後端種類 fence(2026-09-10)
- 背景 file poll 的 require_poll_scope 與 event commit 原先核對 owner/space、home_key/scope、provider/generation,漏掉 captured Computer kind。現加入 kind 比對,涵蓋讀取前、ack 前,以及同交易提交事件/artifact 的檢查;與 file-status 查詢的後端 fence 對齊。
- 新 SQLx 測試保持 provider/generation 不變、只變更 kind,驗證舊 snapshot 的 poll 驗證及 receipt commit 拒絕,artifact/event/Activity 都沒有新增,operation 保留 unknown;恢復原 kind 後正常核驗及提交。
- 驗證:完整 API 205 passed/4 ignored、API all-targets Clippy(CARGO_INCREMENTAL=0、-D warnings)與 diff check 通過。此輪僅中央 SQL fence 與 PostgreSQL 測試,未改 Runner、schema 或部署,未重跑不變的容器流程。
- 對應 O41/O45/T17。提交拒絕保留原 root 事件供後續處理,不把 metadata 設定變更解讀成已完成其他 provider 的操作。完整輪詢延遲驗收、跨 generation unknown 退役證據鏈與其餘 P0–P3 缺項仍維持原範圍。
## 跨 generation 退役證據的驗證與私有管理通道(2026-09-10)
- 新 root file_retirement_proof 驗證 HMAC-SHA256 envelope,payload 最多 4096 bytes;拒絕重複 JSON 欄位、額外 claims、未來/過期時間、超過 60 秒期限、boolean 整數混用、錯目前 identity/home_key、跨 bot origin、非較早 generation、非 provider_destroy_ack 種類與非法 references。錯誤統一為 INVALID_FILE_RETIREMENT_PROOF,不含任意簽章資料或 key。
- Supervisor 使用獨立 lazyboy-file-retirement-v1 用途標記,從 master+實際 container homeKey label 衍生 256-bit key;與公開給 Computer 服務的 control token 分離,其他 home 的 key 也不同。只有 file_retirement_validate 動作才把 key/home 加入 fixed root request 的私有頂層欄位,以 Docker exec stdin 傳遞;不放在 argv/task env 或使用者提供的 nested input 中。
- Root 驗證探針回傳 proof_valid/proof_id/origin_generation,先取出私有 key/home,不發布 key,不開始 task helper、不清除 unknown,也不發布恢復 receipt。驗證簽章不等於此階段已核實 provider 實際被 destroy;中央 acknowledgement 查證與安全發證尚待 API 接線。
- 3 項 Python 協定測試涵蓋有效證據、錯 key/home/current context、MAC 篡改、簽章有效但 claims 非法、重複欄位、無界/壞 envelope。Rust credential 測試驗證用途及 home 分離;registered gate 執行這些 proof tests。
- 真實 Supervisor HTTP→Computer fixture 以測試 authority 簽章,驗證 Rust 簽章/Python verifier 互通、private key 不在回覆、偽造 signature 與跨 bot 拒絕、原人類修改檔案不動。該 fixture 明示只驗證 envelope,不把仍存活的測試容器偽稱為已退役。
- 驗證:3 proof tests、Rust credential test、完整 Docker gate(含擴充的 Supervisor HTTP 證據驗證、15 root files、lifecycle、實際 API 歷史背景恢復)與 Supervisor all-targets Clippy 通過;diff check 通過,自建容器清除,未部署。
- 對應 O41/O45/T17。尚未提供可自行接受任意 retirement claims 的恢復入口;下一步必須由 API 依目前授權核對中央 provider_destroy_ack/原 dispatch/home_key,再透過此通道把證據送入明確 postcondition recovery。全部 P0–P3 剩餘範圍維持,不能將簽章驗證探針當成跨 generation unknown 已完成。
## 簽章退役證據的 root 明確恢復與中央來源核對(2026-09-10)
- Root 新增 file_retirement_recover,僅在嚴格驗證簽章、目前 identity/home、原 identity、期限及 provider_destroy_ack claims 後,使用已退役的證據進行原操作後置條件核驗。不同 boot 的 PID 不被視為停止證明;簽章證據也不繞過內容 fingerprint、fsync、paired move 或 immutable snapshot 核驗。
- 恢復 receipt 保留 postcondition、executionProven=false,新增 retirementProofId/quiescenceBasis。已驗證 claims(不含 key/signature)與 receipt/outbox 同 SQLite transaction 保存於 file_retirement_receipts;重送返回既有 receipt,不覆蓋後來的人工作業。Supervisor 私有 key 注入擴充至恢復動作。
- 新真實 root 測試以 fixture authority 及舊 boot metadata 模擬跨 generation:普通查詢維持 unknown,壞 MAC 拒絕,有效證據遇到人類內容衝突仍 unknown;內容符合才恢復且不重寫檔案,後續重送保留人類修改。這不是實際 API destroy acknowledgement 的端到端驗收。
- 第 4 項 proof 測試使用真實 SQLite transaction、模擬已完成的檔案核驗邊界,注入 audit INSERT 失敗,確認 receipt/outbox/audit 全部回滾,移除故障後只提交一次。
- 中央 parser 嚴格核對 proof ID 格式及 postcondition/provider_destroy_ack 來源。record_artifact 另要求該 ID 匹配中央退役 acknowledgement 的原 dispatch provider/generation 與目前 home;缺失或錯 home/provider/generation/proof 拒絕,同交易保留 unknown。新增 SQLx 測試覆蓋拒絕及成功。
- 驗證:最終完整 API 206 passed/4 ignored;完整 Docker gate 通過(16 root files、Supervisor HTTP、lifecycle、實際 API 歷史背景恢復),fixture 容器清除;4 項 proof tests 通過。Gate 後新增的中央 acknowledgement guard 已由最終 API suite 覆蓋,第 4 項 SQLite proof test 另行通過。未部署。
- 對應 O41/O45/T17。API 歷史 resolve 仍拒絕:必須補齊目前 path grant、中央查證、受信任 issuer、專用 transport 與實際 destroy/recreate 恢復端到端測試,才可對使用者開放。此輪不能宣告跨 generation unknown 已完整完成;其餘 P0–P3 範圍維持。
## 中央退役查證與專用簽章恢復通道(2026-09-10)
- 新 FileRetirementRequest 與 SandboxProvider::recover_retired_file 管理介面,拒絕額外欄位、錯/空 identity、非較早 generation 與空 home;不支援的 provider 預設 fail closed。Docker SDK 核對目前 context/home,使用專用 files/recover-retired endpoint,回覆串流上限 128 KiB、stdout 64 KiB 並嚴格核對 status/code。
- Supervisor 入口要求 master token、bot 與 generation,body 上限 16 KiB;再次核對實際 container homeKey label。只有管理端生成用途分離的 HMAC 與 60 秒 claims,envelope 直接送入 root,不返回給 API/工具/task,不接受呼叫者提供的 signature。原 generic exec 仍不能自行簽章。
- API 歷史 resolve 不再一律拒絕。先用原 captured identity 鎖住目前 Computer/bot/dispatch,查證原 computer/provider/generation 與目前 home 匹配的 provider_destroy_ack,FOR SHARE 保持 acknowledgement 穩定;在鎖內讀原 metadata,依目前 bot workspace scope 檢查來源與 move 目的地全部路徑,再呼叫管理通道。以 2 秒 lock_timeout 限制取得鎖等待、25 秒 transport timeout 限制外部等待,鎖保留至中央 reconciliation commit。普通歷史查詢仍不讀取 live path。
- 新 SQLx+HTTP 測試覆蓋缺 acknowledgement、錯 provider/generation/home、空/未正規化/跨 bot/舊 dedicated 路徑、move 目的地越權,這些案例都不發起 transport;有效請求在 external call 期間實際嘗試修改四種 DB records,全部因 row locks 拒絕。unknown、錯 status/code、過大回覆不產生 artifact;有效 receipt 保留 postcondition/executionProven=false/proof ID 並去重。這是 DB metadata 互斥驗證,不宣稱任意外部 Docker 操作受到 PostgreSQL 鎖控制。
- Supervisor HTTP fixture 覆蓋 SDK 專用入口、home mismatch、錯 token/bot/generation、額外 signature 拒絕,以及已知 receipt 重送。擴充 fixture 注入真實 SQLite result UPDATE 故障,helper 實際寫入後由專用 issuer 恢復 unknown,再核對人類後續修改不被重送覆蓋。此 fixture 使用測試 authority,並未真的 destroy 舊 guest。
- 最終驗證:完整 API 207 passed/4 ignored;Control 157 passed、Sandbox 11 passed;完整 Docker gate 通過(4 proof tests、16 root files、擴充 Supervisor HTTP unknown 恢復、lifecycle、實際 API 歷史背景恢復);workspace all-targets Clippy(CARGO_INCREMENTAL=0、-D warnings)及 diff check 通過。自建 fixture 容器已清除。
- 對應 O41/O45/T17。仍需實際 lifecycle destroy 產生中央 acknowledgement,再 recreate 並經 API 恢復 unknown 的完整一體化 gate;也需 UI 歷史恢復可用性提示/入口與恢復回覆遺失故障驗收。全 P0–P3 原範圍保持,沒有將各層 fixture 拼成已完成端到端的宣告。未部署。
## 實際 provider 退役與重建後恢復 gate(2026-09-10)
- 擴充既有 API→Supervisor→Computer 整合 gate,建立獨立的一次性 Dedicated guest,使用 network=none、CPU/RAM/PID 上限、持久 home 與四個 runtime mounts。所有目錄位於本次 Supervisor fixture 的暫存目錄,退出時先移除 guest 再清除資料。既有 Team fixture 不變。
- 真實 root SQLite result UPDATE 故障讓 write helper 寫入成功而 receipt 缺失;移除故障後呼叫原 production replace_computer_generation(僅將 Rust 可見性改為 pub(crate) 供同 crate gate 使用),經 Supervisor stop/destroy 取得 acknowledgement。測試核對舊 Docker ID 已不存在、中央 retirement 原 provider/home/generation 正確、generation 前進且 provider 清空;沒有在 fixture 人工 INSERT retirement。
- 以相同持久 mounts 建立不同 Docker ID,再用 fixture 發布新 provider/running;這一步不宣告完整公開 boot/readiness 流程已驗收。核對原檔案 bytes 與 root journal 跨容器保存,普通歷史查詢仍不讀取 live path。
- 經 API tool dispatcher 明確 resolve:人類內容衝突保持 unknown 且不改檔;fixture 恢復預期 bytes 後,恢復須回傳中央實際 proof ID、postcondition、executionProven=false,mtime 不變。後續人類編輯及重送不得重寫,immutable artifact 仍返回核驗 bytes;背景歷史 outbox 必須以原 generation 保存兩個事件並只產生一份 artifact。
- 初次執行在新增 fixture 的 file_events 初始化缺少 JSON 游標時失敗,尚未進入退役流程;已補上必要的 {},保留所有 outcome/assertions 重跑。最終完整 Docker gate 通過(16 root files、Supervisor HTTP、既有 Team 歷史恢復及新增 Dedicated 實際退役恢復),API 整合測試 18.89 秒;workspace all-targets Clippy 與補充 API all-targets Clippy 通過,diff check 通過。所有本次 fixture 容器已清除,未部署。
- 對應 O41/O45/T15/T17/T64;本輪不改產品恢復語意,擴大真實故障/lifecycle 證據範圍。回退可撤除該 fixture,不刪 persisted journal/退役 acknowledgement 或改復原來源。歷史恢復 UI、恢復回覆遺失、公開 boot/readiness 一體化及其餘 P0–P3 尚待完成。
## 歷史 unknown 的明確恢復 UI(2026-09-10)
- API file-operation 回覆新增 retirementRecoveryAvailable。只有歷史 unknown 在目前 owner/binding/storage/dispatch locks 內,經相同 retirement_authorization helper 核對中央 provider_destroy_ack、原 identity、目前 home、所有受影響路徑後才為 true。普通查詢仍使用 file_history、不讀 live path、不簽發 envelope,也不返回管理 key/proof claims。
- 真正 resolve 每次重新查證,不接受 UI 提示作為授權。抽出共用 helper 後,缺失/不匹配/非法路徑回傳不可用;DB 錯誤仍失敗,不偽造可用性。終態及同 generation 不給歷史恢復提示。
- Run Monitor 對歷史 unknown 僅在中央 boolean 提示為 true 時顯示既有「重新核驗並完成恢復」按鈕;新增中英文提示,說明舊 Computer 已退役且可嘗試核驗。渲染不呼叫 API,必須點擊才送 readback=true+resolve=true;當前 generation 原有 helper/controller/desired state 條件保留。內容衝突訊息優先,不把可嘗試恢復顯示成已成功。
- SQLx/HTTP 回歸擴充 hint:缺 acknowledgement、錯 home/provider/generation、未正規化與跨 bot/move 目的地越權皆 false;合法 hint 不進行檔案觀察;顯示後更改 acknowledgement home,resolve 拒絕且沒有發起恢復 transport。前端新增 strict boolean/終態/歷史與當前分流測試,實際元件事件測試確認不自動執行且點擊送對原 operation。
- 驗證:前端 59 tests 通過、TypeScript/Vite production build 通過(既有 bundle size warning)、完整 API 207 passed/4 ignored、workspace all-targets Clippy 與 diff check 通過。完整 Docker gate 通過,包括 16 root files、Supervisor HTTP、既有 Team 歷史恢復與 Dedicated 實際退役/重建後 availability assertion;API 整合段 18.73 秒。本次 fixture 容器均已清除。
- 對應 O41/O45/T17/T64。未宣告瀏覽器視覺/公開登入後端到端已測;目前 UI 證據為元件事件/狀態測試及 production build。Team 現有同 generation 恢復維持,Dedicated 真實退役 fixture 持續驗收。回退可忽略新增提示欄位、隱藏歷史按鈕,不刪既有 receipt/acknowledgement。恢復回覆遺失、公開 boot/readiness 一體化及其餘 P0–P3 仍待完成,未部署。
## 退役恢復 receipt 回覆遺失驗證(2026-09-10)
- 擴充實際 Dedicated destroy/中央 acknowledgement/重建 fixture,在 API→Supervisor 的專用 recover-retired transport 插入一次性故障代理。代理完整讀到真實 root succeeded receipt 才回 502,不建立 receipt、不修改 task bytes,也不改寫退役 claims。
- API 必須回報 FILE_STATUS_UNAVAILABLE,原操作維持 unknown,中央 artifact 與 historical proof 均為零;代理核對只收到一次請求,禁止傳輸層悄悄重做。真實檔案 mtime 在 root 恢復期間維持不變。
- 回覆遺失後先讓人類修改檔案,再經正常 API 明確恢復請求取回原 receipt。回傳 evidence 必須與代理先前讀到的實際 receipt 完全相同,保留原中央 proof ID/postcondition/executionProven=false;人類 bytes 與 mtime 不變。既有再次重送、immutable artifact 讀取與歷史 outbox 入帳檢查持續執行。
- API all-targets Clippy(CARGO_INCREMENTAL=0、-D warnings)與 diff check 通過;完整 Docker gate 通過,新增實際退役/回覆遺失整合段 19.14 秒,原 root files/Supervisor HTTP/Team 背景恢復皆通過。自建 fixture 容器已清除。這項變更只修改測試 fixture,沒有改產品協定/恢復程式,不重跑不變的前端建置。
- 對應 O41/O45/T17/T64。本案例覆蓋「root 已提交,HTTP 回覆被換成 502」;不宣告所有 socket reset/逾時/中央程序崩潰時序均已驗收。公開 boot/readiness 一體化、完整故障矩陣及其餘 P0–P3 仍保持原範圍,未部署。
## boot 健康探測不明時保留原 Computer(2026-09-10)
- 核對公開 boot 路徑時發現 guest_has_screens 把 ContainerProbe::Unknown 壓成 false,boot_for 接著會執行 stop/destroy/replacement;暫時 HTTP 失敗因此可能銷毀仍可用的 Computer。移除該 boolean wrapper,boot 明確拒絕 Unknown 並保留原 provider/generation/state。
- probe 對固定 test -x 的 exit 0 視為 Alive、exit 1 視為 Missing;其他非零碼(如 timeout 124/終止 137/錯誤 2)改為 Unknown。原明確容器缺失錯誤仍走 Missing,沒有以未知狀態推導退役 acknowledgement。
- 新 SQLx+HTTP regression 覆蓋 Team/Dedicated、need_gui true/false 與 HTTP 503/124/137/2,檢查原 provider/generation/running 與 unknown job 保持、沒有 stop/destroy/provision 請求、沒有新增 retirement;恢復健康 code 0 後 runner-only boot 可重用原 provider。
- 最終完整 API 208 passed/4 ignored、API all-targets Clippy(CARGO_INCREMENTAL=0、-D warnings)及 diff check 通過。上述 Docker 回覆遺失 gate 在此 boot 修正前已通過;此修正不改該 gate 使用的明確 replace 路徑,新增公開 boot 分支以真實 PostgreSQL/HTTP regression 驗證。公開實際容器 boot/readiness 與全 P0–P3 仍待完成,未部署。
## 退役後透過正式 boot_for 重建(2026-09-10)
- Dedicated 退役恢復 gate 移除 fixture 的 Docker recreate/SQL provider publication,改呼叫 production boot_for(need_gui=false),經 Sandbox→Supervisor provision、映像 entrypoint/ready marker 與中央 boot_attempt CAS 發布 running。核對新 provider ID、generation 仍為退役後的 2、boot attempt 清除;再次 boot 重用相同 provider/generation,沒有多寫退役紀錄。
- 原先故障 guest 仍由 fixture 建立;銷毀、退役紀錄、重新 provision、就緒檢查、中央發布、未知效果恢復與回覆遺失重送均走實際程式。測試 Supervisor 固定 512 MiB/2 CPUs/256 PIDs、關閉 sudo 並清除 shared screen network 環境;正式 provision 會建立自己管理的 network,fixture cleanup 新增只移除本次 guest 與其 network。
- 此版本完整 Docker gate 已通過,整合段 22.96 秒;API all-targets Clippy 通過。另新增全新 Team home 的正式 cold boot/native write 測試,不預先建立或修正 home ownership。確認該新增 assertion 不在首次執行 binary 內,因此已重新編譯並重跑整合 gate,Team cold boot 曾揭露 ownership 缺陷,修正及最終結果見下節。
- 對應 O08/O41/O45/T15/T17/T64。這是公開 boot 函式到真實 runner-only 容器的整合;不等於瀏覽器登入 HTTP route/GUI、browser、desktop、viewer 各層 readiness 均已驗收。全部 P0–P3 其餘範圍維持,未部署。
## 修正未設定 HOST_DATA_DIR 的全新 home ownership(2026-09-10)
- 新 Team cold boot gate 揭露真實啟動失敗:API/Supervisor 由 root 在本機執行,未設定 HOST_DATA_DIR 時,API 建立的 home 保留 root ownership;映像以 UID 1000 啟動,建立 .browser-profiles 時因權限拒絕而 exit 1,Supervisor provision 回 500。故障由實際 guest stderr 與 Supervisor startup log 確認,不是以 fixture 手動 chown 掩蓋。
- DockerHost::provision 在驗證 managed home canonical path 後,對所有 Unix 部署統一將 home 根目錄設為 1000:1000,不再只在 HOST_DATA_DIR 模式執行。僅改根目錄擁有者,不遞迴改寫 task/browser/tool 既有內容權限;chown 失敗仍在建立容器前拒絕。
- Team cold boot fixture 不預建 home/runtime、不修 ownership,實際呼叫 boot_for,核對新 home uid/gid=1000、Team bot 的 journaled write 與 native read 成功。Dedicated 正式 re-provision、warm boot reuse、退役恢復及回覆遺失仍在同 gate 內執行。
- 診斷階段保留了 provision 失敗的 Supervisor/guest logs,並清除本次 guest/network;不動既有部署。workspace all-targets Clippy、diff check 及修正後完整 Docker gate 均通過,整合段 26.18 秒;包含全新 Team cold boot/1000:1000 ownership/native write-read,以及 Dedicated 正式 re-provision/warm reuse/退役恢復與回覆遺失。本次 guest/network 全部清除。
- 對應 O03/O08/O41/T15/T17。這是 root 啟動的本機部署及新 home 的缺陷修正;不宣告 GUI/viewer/browser 獨立 readiness 與登入後完整產品流程已完成。P0–P3 其餘範圍保持。
## health 證據、授權與探測期限(2026-09-10)
- 發現 component_health 把設定中的 display backend 一律當 ok=true,且 jobs count SQL 失敗時回退成 0;這兩者都可能產生沒有依據的健康回報。display 改回 ok=null/state=unknown/not probed,設定值另放 configuredBackend;未運行的 sandbox 標 inactive/ok=null。整體 ok 只有全部元件明確 true 才為 true。這不影響原生工具 admission。
- jobs count 與 display backend DB 讀取失敗明確拒絕回覆,不以零工作/環境預設冒充當前資料;operations 可用性改用 EXISTS,避免為探測而計算全表總數。
- health 開始前核對 Computer owner/space 與 actor;探測後在 transaction 核對並鎖定目前 bot assignment、owner/space、provider/generation/state、home/scope/kind/display backend。跨範圍請求不發起 transport,期間變更拒絕舊結果。
- probe_computer_container 的 HTTP future 原先無總期限,現在在原 guest 5 秒 command timeout 外加入 6 秒 transport deadline。逾時是 Unknown,沿用 boot 保留原 provider 的處理;不推斷程序已停止或寫入退役紀錄。
- 新 SQLx+HTTP health regression 覆蓋未知 display、foreign owner/space、探測中 home/generation/binding/backend 變更、jobs 表不可用及 inactive sandbox。boot regression 加入實際永不回覆的 HTTP handler,驗證有界返回 Unknown、provider/generation/running 保持、無 stop/destroy/provision。最終完整 API 209 passed/4 ignored、API all-targets Clippy(CARGO_INCREMENTAL=0、-D warnings)及 diff check 通過。此輪未改 Runner/Supervisor/UI;使用真實 PostgreSQL 與 HTTP 故障測試,未重跑不變的容器與前端流程。
- 對應 O08/O41/O45/T15/T17。此次修正的是誠實回報、存取範圍與等待界線,不把 unknown 當成完整 layered readiness;下一步仍需實際 browser/desktop/viewer scoped probes 與其復原流程。全部 P0–P3 範圍維持,未部署。
## Computer-local 唯讀 runtime probes 與 Runner 程式傳送修正(2026-09-11)
- 新 runtime_health root 管理動作先核對 identity/slot,經受保護 Runner SQLite 初始化後,以 UID/GID 1000、乾淨 env、256 MiB/FD/CPU 限制執行固定 Python probe,總期限 8 秒、成功 stdout 上限 4096 bytes。逾時/spawn 錯誤正規化為 RUNTIME_HEALTH_UNAVAILABLE,避免 TimeoutExpired 把 helper argv/source 放入錯誤。
- 未指派 slot 時 desktop/browser/viewer 均為 null,不探測預設 :1;slot 僅接受整數 0–7,拒絕 boolean/額外欄位。已指定 slot 時各項獨立:desktop 核對 xdpyinfo 與 WM supporting window/name;browser 核對對應 loopback DevTools discovery、協定與本地 websocket URL;viewer 核對 vnc.html 及真正 WebSocket upgrade/accept/binary RFB greeting,不只看 HTTP port。每次 HTTP/X11 等待與資料量皆有界,不啟動任何服務、不輸出 browser 內容。
- 6 個 Python tests 覆蓋 discovery 壞資料/foreign URL/大小限制/redirect,viewer 假 upgrade/accept/遮罩或過大 frame/錯 RFB/分段 greeting,X11 沒 WM、無 slot 不探測及元件獨立性;root timeout/spawn error 不洩漏 helper canary。正向 browser/desktop/viewer 此階段使用協定/subprocess fixtures,不宣告真實 Chromium/XFCE/viewer session 已測。
- 新真實 root 容器測試確認 UID 1000 runner、有 slot 但未啟動 GUI 的結果、非法 body 拒絕,以及 jobs/file journal/outbox 筆數不變;Supervisor HTTP fixture 另經 SDK 呼叫 runtime_health。API health endpoint 尚未接入這些結果,必須先接目前 screen assignment/profile 與前後 fence;該 endpoint 仍保持未探測,不提前顯示新後端成功。
- 第一輪容器 gate 揭露既有 Runner bootstrap 把整份 Python source 放在單一 argv,增加 probe 後跨過目前 Linux 的長度上限,native file transport 出現 argument list too long。已改固定小型 bootstrap+stdin 長度 frame(程式最多 1 MiB),以 unbuffered os.read 精確消耗 source,保留剩餘 request bytes 給 exec;程式以 root-private temp file/fsync/rename/directory fsync 發布,request/管理 key 不存入該程式檔。
- 3 個真實 pipe/exec tests 驗證超過 128 KiB 的 UTF-8 程式、4093-byte 分段傳送、request 原 bytes 保留、request canary 不進程式檔、非法/截斷 frame 不替換旧程式,以及不受保護目錄拒絕。這些 tests 與 runtime probes 均註冊到 Docker gate。
- 6 probe tests、3 bootstrap tests、workspace all-targets Clippy 與 diff check 通過;修正後完整 Docker gate 通過(17 root files、Supervisor HTTP runtime_health/簽章恢復、既有 lifecycle 及 Team/Dedicated 正式啟動與退役恢復),API 整合段 24.98 秒。本次 fixture 容器與 networks 已清除。對應 O08/O41/O45/T17/T64。仍需 API scoped 接線、真實 GUI/browser/viewer 正向及故障復原驗收,P0–P3 其餘範圍維持;未部署。
## health API 接入分項 runtime 證據(2026-09-11)
- control 增加嚴格 RuntimeReadinessObservation:成功 code/status、4096-byte stdout、空 stderr、version 1、必要 nullable 欄位、型別、未知/重複欄位、runner UID 證據及精確 slot 均檢查。無 slot 時 GUI 必須全 null;有 slot 時每項必須明確 boolean,false 不會拖成 Runner 不可用。傳輸錯誤正規化,15 秒總期限,不回傳任意來源錯誤內容。
- component_health 只讀取目前 bot 自己的 ScreenRow,先檢查 slot/display/view_port 對應,不以預設 slot 0 借用其他 bot 畫面;實際呼叫 Computer-local runtime_health,回報 display/browser/viewer 獨立證據及 nativeReady。沒有畫面或探測失敗仍保留 null/unknown;nativeReady 僅代表 Runner 探測成功,不代替原生工具的 quota/權限 admission。
- 沿用 Computer owner/storage/provider/generation/assignment 前後檢查,並核對 screen identity、slot、display/view_port、profile mode/path、execution fence。探測中從無畫面變成有畫面、刪除、換 profile 或 fence 變更皆拒絕舊結果。這是 assignment 一致性,尚不表示已證明 Chromium 實際 process 的 profile 身分。
- 4 個 control readiness tests 與 SQLx+真實 HTTP regression 通過,涵蓋 malformed evidence、外部 scope、Computer 變更、screen 新增/刪除/profile/fence 變更、分項成功失敗及 inactive/DB 失敗。真實 Team cold boot fixture 另加入 component_health→Supervisor→root Runner 驗證,不配置 screen,要求 nativeReady=true、GUI null 且查詢後仍無 ScreenRow。完整 API 209 passed/4 ignored、完整 Docker gate 通過,真實 API 整合段 26.06 秒;本次容器/network 已清除。其後檢查 SDK 發現 generic exec 會先完整讀入 response,已對 runtime_health 加入 16 KiB 串流上限,非成功狀態不讀任意錯誤 body;真實 chunked HTTP regression 證明超限及 503 在沒有 EOF 時仍立即拒絕且不洩漏 canary。此追加修正後 API health 回歸、SDK chunked test 與三個受影響 crates all-targets Clippy(-D warnings)均通過;修正了測試未使用 socket read byte count 的 Clippy 問題,diff check 通過。完整 Docker gate 執行於追加 SDK 串流限制之前,該限制另由上述真實 HTTP 測試覆蓋。
- 對應 O08/O41/T17。真實 Chromium/XFCE/websockify 正向與故障復原及 P0–P3 其他項目仍未完成;未部署。
## 真實 GUI readiness 與服務重啟驗證(2026-09-11)
- 新增 `make test-runtime-gui`/scripts/test-runtime-gui.py,在獨立隨機命名 Computer image 容器中執行 tests/runtime-probe-gui.test.py;network none、UID/GID 1000、1 GiB memory/2 CPU/512 pids,read-only fixture mounts、240 秒 host timeout,finally 清除自己的容器。
- 使用真實 Xvfb :8、XFWM、持續 session D-Bus、Chromium about:blank(9229)、x11vnc(5907)與 websockify(6087)。驗證 cold 全 false、全部服務 true,以及依序終止/重啟 browser、websockify、RFB server、WM 的 8 個狀態;每個狀態保留 Runner true,未受影響元件仍 true,None slot 不借用存在的畫面。10 個狀態全部通過;最終版每次 probe 以獨立 Python -I 程序、正式 clean env、256 MiB AS/20 秒 CPU/64 FD/無 core、8 秒 timeout/4096-byte stdout 限制執行,重跑仍全數通過。
- 首次真實驗證找到產品 bug:probe GET /vnc.html,但 image 僅安裝 index.html/vnc_lite.html,Supervisor 的正式 viewer URL 也是 vnc_lite.html,正常 viewer 因而一直 false。已修正到 /vnc_lite.html,協定 fixture 對其他未知路徑回 404,避免寬鬆假 server 掩蓋同類問題;6 probe tests 通過。
- 第二輪 WM 重啟 fixture 失敗原因為終止該 process group 一併終止其自動啟動的 session bus,並非探測結果不實;改獨立持續 D-Bus 後全部通過,保留失敗證據與分類。此次正向驗證涵蓋 WM/CDP discovery/實際 WebSocket-RFB greeting,不涵蓋全部 XFCE services、Chromium page 操作/profile process 身分、公開 viewer 登入/瀏覽器渲染、產品 ensure_screen 自動修復或整個 Computer 故障復原。
- 包含 root Runner source embedding 的完整 Docker gate 通過,API 真實整合段 26.52 秒;6 protocol tests、最終受限 probe 的真實 GUI 10 個狀態、Python compile 與 diff check 通過。測試容器/networks 已清除。O08 等相關項仍 PARTIAL,P0–P3 其餘範圍維持,未部署。
## 正式 ensure_screen 的桌面服務修復(2026-09-11)
- 找到 lazyboy-screen 兩個會略過修復的判斷:ensure_slot 的 X server/VNC/web port 快速路徑直接回成功;start_desktop 只看 panel PID,WM/xfdesktop 死亡不處理。移除無鎖快速成功路徑,所有 ensure 在既有 per-slot flock 內走相同檢查;start_desktop 分別核對 WM/desktop/panel,僅補啟動缺失者,且有界確認程序存活後才成功。
- alive_pidfile 僅接受正整數 PID,排除 /proc state Z/X,桌面服務另比对實際 comm;避免 zombie 的 kill -0 成功被當成正常服务。這不是完整 PID start-time/display identity 防偽,仍不宣告任意同 UID 程序隔離。
- 部分服務重啟重用既存 per-screen D-Bus address,明確 address 存在才做有界 ping;無 address 時直接建立新 bus,避免 dbus-send 的隱式 autolaunch 導致沒有可傳給桌面程序的 address/PID。AT-SPI launcher/registry 僅在原 PID 不存活時補啟動。D-Bus 本身中斷且所有桌面 PID 尚存活的完整復原仍待驗證/補強。
- 新 make test-screen-recovery 使用同一 networkless disposable Computer harness,read-only mount 當前正式 lazyboy-screen。真實執行 ensure 7,驗證 warm ensure 的 WM/desktop/panel/Cua/Xvfb/D-Bus 全部 PID 不變;分別終止 WM、panel、desktop 後,兩個並行 ensure 均成功,只更換故障服務 PID、其餘 PID 与 bus address 不變,實際 runtime probe desktop/viewer 恢復 true、未開 browser 保持 false。三種故障均通過,fixture 容器清除。
- bash syntax、diff check、9 個 control screen tests、59 個前端 regression 通過。此次修改 image 腳本並以現行 image 加掛工作樹腳本驗證,尚未重建/部署 production image;原完整 Runner gate 未重跑,因未修改其 root Python/Rust 傳輸。O08/T17 仍保持 PARTIAL:尚需全 X/D-Bus/Cua/browser 的正式自動復原、其他 display slot 並行及公開 GUI 完整流程。P0–P3 原範圍不縮減。
## 兩畫面正式修復與 Cua 截圖驗收(2026-09-11)
- 擴充 test-screen-recovery,使用正式 lazyboy-screen 同時 cold ensure slots 6/7(displays :7/:8),各自固定獨立 browser profile、DevTools ports 9228/9229。此驗收 fixture memory 提升到 2 GiB(probe-only fixture 仍 1 GiB),保留 network none/2 CPU/512 pids/256 MiB shm/240 秒總期限;不呼叫外部帳號或部署環境。
- 對 slot 7 依序終止 WM、panel、desktop、Cua、Xvfb、D-Bus、Chromium,每次發出兩個並行 ensure,要求故障 PID 更換、desktop/browser/viewer 全部 true。小範圍故障要求其餘 PID 不變;X/D-Bus 允許必要相依服務重啟,D-Bus 故障要求新 address。七種故障全部通過。
- 每次復原都實際執行 cua-driver call start_session 與 get_desktop_state,先刪除舊圖,再確認新 PNG 產生;並非只核對 socket 或進程存在。每次亦核對 slot 6 的全部 PID/D-Bus address 不變且三項真實 probe 仍 true。Chromium 的 process title 正規化後精確比對各 profile path 與 DevTools port;初版測試誤以為 Chromium 保留 NUL-separated argv,已依實際 process-title 格式修正,未把該 assertion 當成產品跨 profile bug。
- 最終真實兩畫面七種故障+Cua screenshot+peer unchanged 全數通過(/tmp/lazyboy-screen-two-displays-final.log),Python compile/bash syntax/diff check 通過,測試容器清除。本輪主要補實測,不重跑未改動的 Rust/前端。
- 這補上 O08/T17 的正式腳本服務與兩畫面復原證據;仍非 API owner/screen lease/control epoch 全鏈路故障驗收,不包含公開瀏覽器登入 viewer 操作、AT-SPI 語意操作在 D-Bus 斷線後的完整恢復、全八畫面資源公平性或所有 display backend。各完整項目仍 PARTIAL,P0–P3 範圍維持。
## API screen startup 授權與回覆驗證(2026-09-11)
- ensure_bot_screen 開始時在 transaction 鎖定目前 Computer/bot,核對 actor owner/space、bot assignment、provider/generation/running、home/scope/kind/profile mode/execution fence;配置與 per-task profile 更新亦在該 transaction 執行,不再先對任意 captured Computer 建立 ScreenRow。Computer row lock 亦序列化 slot 配置;移除不再使用的非交易 list_screen_slots helper。
- capabilities 失敗不再默認 multi_screen=true,5 秒期限;screen adapter context 補 captured generation,所有 busy retries 共用 90 秒期限。Provider 回傳 slot/display/view port 必須符合 captured row;啟動後再核對並鎖定 Computer/bot 與 screen identity/mapping/profile/execution fence,變動時拒絕舊結果。
- SDK 原先缺 slot/display/viewPort 時使用 request slot/:1/6080,並將過大 integer cast 截斷;改 serde required typed fields、alias view_port、精確 layout 比對,拒絕 ok=false/duplicate field/overflow/缺欄位。成功及失敗 body 均限 16 KiB 串流讀取,僅保留 busy starting 可重試分類,不回傳任意 guest error body。新增 serde workspace dependency。
- 新 SQLx+真實 HTTP regression 通過:foreign actor 不送 RPC、不建 screen;正常配置成功;RPC 中 generation/home/bot assignment/profile/screen fence/computer fence 變動、screen 刪除及錯誤 slot 回覆皆拒絕。SDK decoder regression 通過。完整 API 210 passed/4 ignored、完整 sandbox SDK 13 tests、affected all-targets Clippy(-D warnings)通過;diff check 通過。
- 本次是中央授權與結果 fence,不等於 guest 端已阻止所有在途過期副作用;restore_computer_screens 的其他 bot 恢復路徑仍需套用同樣核對,control epoch/lease 的全鏈路與自動修復授權仍未全部完成。P0–P3 原範圍维持,未部署。
## 其他 bot 畫面恢復的授權與指派核對(2026-09-11)
- 抽出 validate_screen_assignment,ensure_bot_screen 與 restore_one_screen 共用 Computer/bot owner、provider/generation/storage/profile mode/execution fence,以及 ScreenRow identity/slot/display/port/profile/execution fence 核對;另在 RPC 前拒絕不合法 slot/mapping。
- restore_computer_screens 不再把 get_bot 的 None 當空名稱繼續啟動。每個恢復先驗證目前授權與指派,送出 captured generation 及原 ScreenRow profile,核對 adapter 回覆後再次驗證;其他 bot 的恢復共用 90 秒期限,錯誤明確記錄並繼續適用的其餘項目,timeout 結束本批。
- 擴充 SQLx+真實 HTTP regression:正常 per-task 恢復沿用 original-task profile;foreign actor/解除 bot 指派在 RPC 前拒絕,呼叫數不變;恢復中八種 generation/home/binding/profile/fence/delete/wrong-slot 情境均拒絕舊結果。focused regression、最終完整 API 210 passed/4 ignored、API all-targets Clippy(-D warnings)及 diff check 通過。
- 此次補上中央恢復路徑,仍不宣告 guest 已阻止所有在途 stale epoch 副作用;真正 guest 層 lease/epoch admission/barrier 與完整公開 GUI 流程仍待完成。未改 Supervisor/image,未部署;P0–P3 原範圍維持。
## GUI context 傳遞鏈與結構檢查(2026-09-11)
- 查出 AdapterContext 雖有 screen_id/screen_lease_id/control_epoch,SDK 原先未傳送,API adapter_context_for 亦未設定 epoch。現以 captured ScreenRow execution_fence 傳送 control_epoch,SDK 保留 screen ID、lease、run、generation;API SQLx HTTP regression 實際核對 screen ID 與 epoch header 等於配置資料。這個 epoch 尚非 X restart/service epoch。
- Supervisor screens route 在 master token 驗證後核對必要 context:單一 header、合法 bot/screen reference、正 generation、有界非負 epoch、slot/display layout 以及 body/profile 一致;拒絕 missing/duplicate/overflow/identity mismatch,optional lease/run 若存在也須有效格式。兩個 Supervisor tests 包含 SDK→真實 HTTP→同一 validator 的成功與拒絕情境。
- SDK 的 header builder 改回傳 Result,所有呼叫傳遞錯誤;原先 space/bot/token 解析可能 panic、display/profile 解析失敗被略過,現在無法編碼的身份均以 INVALID_ADAPTER_CONTEXT 拒絕,不洩漏原值,不以移除 header 降級到預設畫面。14 SDK tests 通過,包含 epoch/lease 保留及非法 lease/display/profile。
- 最終完整 API 210 passed/4 ignored、14 SDK tests、2 Supervisor context tests、affected all-targets Clippy(-D warnings)及 diff check 通過。這是身份傳遞與結構檢查,**不是**目前 epoch/lease 授權或持久化 stale request admission;Supervisor code 亦明註必須由 Runner 對 durable state 核對。guest 層阻擋與 pause acknowledgment/barrier 等仍未完成,P0–P3 原範圍維持;未部署。
## Runner GUI durable fence 基礎(2026-09-11,尚未接入 GUI 執行)
- 新 runner_gui_fences.py 提供 root-only SQLite/WAL/FULL 狀態,保留 provider/generation 與各 slot 的 screen/bot/epoch/lease/expiry/paused;root-private directory/file/no-follow/hardlink 檢查。generation 只能前進,相同 generation 不可換 provider;相同 epoch 不可換 owner/screen/lease/paused,舊 epoch 拒絕,重播不縮短既有期限。admit 本身不建立或推進任何授權。
- generation publication 使用全域 exclusive flock;一般 display admission 共享 generation lock 並独占本 slot lock,其他畫面仍能 admit/publish。所有 lock waits 有界,scope 更新必須等該 scope 執行 guard 退出,generation 更新等待全體;等待後再檢查發布授權是否已到期。執行時不持有 SQLite transaction。
- 不能把 kernel lock 在 process crash 後自動釋放當成 quiescence:admit 先 durable INSERT active intent,再交給 executor;executor 必須明確 confirm_quiescent 才刪除。例外、程序被 kill、未明確確認,或完成 DELETE 失敗均留下 unresolved,拒絕同 scope 新 admission/epoch 與 generation publication。其他 slot 可繼續。active 狀態最多 8 slots,不依逾時自動清除。
- 11 個 root fixture tests 通過:missing/forward/stale identity、DB reopen 後 generation/owner 高水位、pause/resume、expiry與重播、真實跨程序鎖與 peer 並行、process kill、exception、正常返回未確認、SQLite generation rollback/intent insert/completion delete fault、等待期間 expiry、UID 1000 真實子程序拒絕、root directory/file/symlink 與 malformed claims。已加入 make test-gui-fences 及既有 Runner gate 的 host checks;Python compile/diff check 通過,fixture temp state 自動清除。未重跑未改動的 Rust/Docker/GUI 整合。
- **此模組尚未由 Runner bundle/GUI 路由呼叫,不宣告部署中的 stale GUI 已被阻擋。** 下一步需:中央 display epoch 跨 ScreenRow 刪除/重新指派仍單調(目前 execution_fence 重建可能回 0,不能直接當永久 slot epoch);只由已驗證 management publication 更新狀態,普通 GUI 請求不可自升 epoch;executor 持續保有 guard、遵守 expiry deadline 並驗證 quiescence;crash unresolved 須由可信 process/provider retirement proof 恢復,不能直接刪除;API pause/takeover 等待 root barrier acknowledgement 才授予控制。
- 對應 O27/T41/T56 與 scoped fencing 契約。這是可測的持久化基礎與接線前必須滿足的條件;GUI 全鏈路、agent/Computer 分層整合及 P0–P3 其餘要求維持未完成,未部署。
## 中央 display epoch 跨指派單調性(2026-09-11)
- migration 039 新增每 Computer/slot 的 computer_display_epochs 高水位及 ScreenRow.display_epoch BIGINT;刪除 ScreenRow 留下 counter 並推進 tombstone,重新插入/換 owner 不可從 0 重用。配置透過 counter row 的 ON CONFLICT UPDATE 鎖序列化,避免先取 sequence 後等待舊交易造成較舊 epoch 晚提交。Computer 父項刪除仍可 cascade,不把被刪除父項重新插入 counter。
- trigger 在 profile、identity/mapping、control holder/lease、execution run/fence、lease 縮短/清除及顯式 bump 時配置新 epoch;純 updated_at、no-op、相同 lease 延長不撤銷目前 epoch。外部傳入的 display_epoch 不直接採用;跨 slot 移動推進舊/新 counter,使用固定 counter 順序。
- API adapter context 改送 display_epoch;ScreenRow 讀取、啟動/restore 的前後核對及 health snapshot 一併接入。SQLx HTTP regression 增加只更新 display_epoch 的第九種在途變更,舊結果拒絕。
- 兩個新增 SQLx tests 通過:真實舊 schema 資料 backfill(包含 execution_fence=2147483647,升為 2147483648);rollback/delete/recreate/move/expiry renewal/shortening/manual reset 與 parent cascade;透過 pg_stat_activity 確認 replacement INSERT 正在等 slot lock,另一 slot 可更新,刪除提交後 replacement epoch 大於 tombstone。
- 最終完整 API 212 passed/4 ignored、API all-targets Clippy(-D warnings)及 diff check 通過。僅操作 SQLx disposable DB,未對部署資料套 migration,未部署。
- 高水位是配置器,不是已送达 Runner 的 barrier receipt:必須先提交中央 epoch/reservation 才能向 Runner 發布;無法把回滾交易中取到的 epoch 對外發布。Runner 管理發布、GUI executor guard/expiry/quiescence、未明效果恢復及中央 pause acknowledgement 仍待接線。P0–P3 完整範圍維持。
## Root GUI 簽章發布接收與 profile 綁定(2026-09-11)
- runner_gui_fences 已嵌入正式 root Runner bundle,新增 gui_fence_publish handler。Supervisor 只在此操作透過 root stdin 私有欄位傳入依實際 home label 派生、與 file retirement/controld token 分離的 HMAC key;一般 exec 輸入不能自行簽章。Root 驗證精確 schema、重複 JSON 欄位、audience/home/provider/bot/generation、60 秒發布授權、最長 5 分鐘 scope lease,鎖等待後再驗證授權期限,成功才回傳不含 key/簽章的 receipt。
- claim 與 durable screen/active intent 加入 profile。相同邏輯 profile 跨 slot 的 active effect 互斥;不同 profile/slot 仍可並行,穩定 generation 的 scope publication 不誤取全域 exclusive lock。舊 schema 缺 profile 的 active intent 保守阻擋,不自動清除;舊 screen 需要較新 epoch 才能重新綁定。profile 目前為嚴格邏輯路徑,尚非 filesystem inode/symlink alias 身份證明。
- 14 個 host fence tests 通過,涵蓋原真實 process kill/SQLite faults,新增 profile 排他/平行、legacy migration、簽章/時間/identity/schema 拒絕。真實 SDK→Supervisor HTTP→root Runner 驗收用 fixture-only signer,驗證有效 publication、竄改 MAC 明確 INVALID_GUI_PUBLICATION、合法簽章舊 epoch 明確 STALE_GUI_EPOCH,且回覆不含管理 key。測試曾誤把 SDK transport error 當 CommandResult,已依實際契約修正並要求精確拒絕原因。
- 最終完整 Runner gate 通過(/tmp/lazyboy-gui-publication-gate-verified.log,API 真實恢復整合 25.54 秒),Supervisor all-targets Clippy -D warnings 通過(停用 incremental)、Python compile/diff check 通過。成功 gate 自有容器/network 已清除,未部署。
- 目前完成的是受簽章保護的接收端;正式管理 issuer/API durable publication intent、GUI executor admission guard/deadline/quiescence、boot/service epoch 與可信 unresolved recovery 尚未接入。generation 與 scope 分兩次 commit,失敗可留下較新 generation,不能把未取得完整 receipt 當成功;中央 pause/takeover 仍必須等待 root barrier acknowledgement。O27 保持 PARTIAL,P0–P3 全部原定範圍維持。
## 正式 GUI 管理簽發與回執核對(2026-09-11)
- 新 GuiFenceClaim/GuiPublicationRequest/GuiPublicationReceipt 契約及 SandboxProvider.publish_gui_fence 專用方法,未支援 provider 明確拒絕,不透過一般 execute 模擬簽發。請求核對合法 identity、slot、profile、5 分鐘 lease 上限,以及完整 AdapterContext/home 綁定;呼叫者仍必須先提交並授權中央 epoch。
- 新 master-authenticated POST /computers/{id}/gui/publish,16 KiB body 限制;逐一核對 bot/screen/generation/epoch/slot/profile/lease/display header,拒絕重複或不一致。Supervisor 再核對實際 container home label,由管理端派生專用 key、產生 60 秒 HMAC publication,經既有私有 root 通道送出;不接受 caller envelope 或 key。
- Supervisor 與 SDK 均核對 strict typed receipt、完整 claim/paused/有效 expiry,不將其他 scope 或舊 epoch 回覆當成功;SDK 15 秒總期限,回覆串流上限 16 KiB。任何未取得有效 receipt 的呼叫不能視為已交接;特別是 timeout 可能發生在 durable publication 之後,中央必須保留 intent、重試原 epoch,不假定回滾。
- 159 個 control tests、15 個 SDK tests 與 affected all-targets Clippy -D warnings 通過。新增 SDK 真實 HTTP fixture 驗證有效 receipt,以及 wrong epoch/profile/paused、expired、published=false、未知欄位與不結束的過大 chunked response 拒絕,錯誤不洩漏 response canary。
- 真實 disposable Computer 的 SDK→正式 Supervisor signer→root receiver 驗證發布、重送相同 receipt、較新 epoch pause,以及 SDK context mismatch、HTTP missing auth/context、錯誤 home 和舊 epoch 拒絕;原 fixture-only signer 的有效/竄改/舊 epoch 接收測試保留。管理 HTTP 段通過(4.14 秒)。
- 尚未接中央 API durable publication intent/背景 retry/pause acknowledgement,也尚未由 GUI executor 持有 root admission guard,不能宣告部署中的 stale GUI effect 已全面阻擋。boot/service epoch、unresolved recovery 與 P0–P3 其餘完整要求維持未完成,未部署。
- 最終完整 Runner gate 通過(/tmp/lazyboy-gui-issuer-gate.log,API 真實恢復整合 26.34 秒);本輪 fixture containers/networks 已確認清除,最終 diff check 通過。
## Agent 暫停狀態與 display epoch 同交易更新(2026-09-11)
- 檢查中央 GUI publication 前置條件時發現:agent_mutation_barriers 的 paused 改變原先只推進 agent epoch,不更新 ScreenRow.display_epoch。Root GUI publication 將 paused 綁定同一 display epoch,因此先後發布不同 paused 值會形成衝突;只修簽发/重試不能解決。
- migration 040 新增 agent barrier 的 INSERT/UPDATE/DELETE trigger,只有實際 false↔true 變化才透過既有 039 counter allocator 推進該 bot 的全部 display epochs,與中央 barrier 寫入同一交易。初始 false、重複 true/false 不推進;刪除 paused barrier 及 bot_id 移動亦正確失效舊/新 bot scope。其他 bot 的獨立 display 不受影響。升級時既有 paused bot 的 display epoch 也會推進。
- 新 SQLx regression 覆蓋正式 set_agent_barrier helper、直接 SQL、重複狀態、交易內新值/交易外未提交值不可見、rollback 保留原 epoch、pause/resume/delete、barrier bot 移動、counter 與 screen 一致,以及真實 populated pre-040 schema migration backfill 僅推進已 paused peer。
- 最終完整 API 213 passed/4 ignored(/tmp/lazyboy-agent-display-barrier-api.log,27.39 秒),API all-targets Clippy -D warnings、diff check 通過。只操作 SQLx disposable databases,未對部署資料套 migration,未改 root GUI 程式或重新執行 Docker gate。
- 這是中央 epoch 原子性修復,仍不是 root 已確認停止/交接的證據;API durable publication intent/retry/acknowledgement 與 GUI executor guard 尚待接入。P0–P3 全部原定範圍與 O27 PARTIAL 維持,未部署。
## 中央 GUI 發布意圖與啟動畫面接線(2026-09-11)
- migration 041 新增 gui_publications,保存不可由 transport error 刪除的精確 request、pending/acknowledged、receipt 與確認時間。啟動畫面及其他 bot 的畫面恢復均呼叫新 publish_screen:在鎖定目前 owner/bot/Computer/ScreenRow identity 與 display epoch 後保存意圖,commit 後才送專用 provider publication;網路期間不持有 DB transaction。
- 發布 claim 使用 captured generation/display epoch/profile/execution run,paused 由目前 agent barrier 或 human holder 決定;一般期限 120 秒,執行中 run 另受 execution lease 上限限制。重試重用相同 claim/home/paused 且仍有 30 秒期限的既有精確意圖,不因回覆遺失建立另一個同義請求;過期歷史 pending 不自動刪除,每 Computer/bot 最多 1024 個 unresolved。acknowledged 歷史 retention 與 expired pending 的可信收斂仍待完成。
- provider 回覆後重新鎖定並核對目前指派,等待 DB lock 後再次檢查 expiry;只有完整 typed receipt 匹配且 intent UPDATE 恰好一列,才同交易保存 acknowledged。途中 pause 的 migration 040 epoch 變更使舊回覆拒絕;未收到有效回執或 DB 寫入失敗仍保留 pending。
- SQLx+真實 HTTP fixture 驗證:foreign actor 不發布;handler 收到請求時 DB intent 已提交;502 後重用同一 id/request;發布途中中央 pause 不被 DB/network lock 阻塞,舊 acknowledgement 拒絕;receipt UPDATE trigger 故障回滾保留 intent,重試成功。既有 startup/restore 授權與九種在途指派變更測試仍通過。FakeSandbox 新增明確 transport-only publication fixture,不把它當 root locking/crash 證據。
- 最終完整 API 214 passed/4 ignored(/tmp/lazyboy-gui-central-api-tests.log,28.45 秒)、15 SDK tests、affected all-targets Clippy -D warnings 及 diff check 通過。僅 SQLx disposable DB,未套用部署 migration。
- 此次是 startup/restore 的前置發布與 request-driven retry;背景 retry、中央 pause/takeover 等待 root acknowledgement、GUI executor guard/deadline/quiescence、boot/service epoch 及完整人類輸入管制尚待接線。P0–P3 全部原定範圍與 O27 PARTIAL 維持,未部署。
- 真實跨程序完整 Runner gate 亦通過(/tmp/lazyboy-gui-central-gate.log,API 整合 26.62 秒):由正式 boot_for(false) 重建的 Dedicated Computer,中央保存意圖後經正式 signer 到 root 成功發布,proxy 僅丟棄第一次已成功回覆;中央先維持 pending,第二次以同一 intent id/精確 request 取得有效 receipt 並 acknowledged,實際 publication RPC 共兩次。此測試不啟動 GUI effect,也不代替 GUI executor admission 驗收。自有 fixture containers/networks 已確認清除。
## GUI publication 背景重試與原始 actor 綁定(2026-09-11)
- API startup 新增 retry_loop,每輪至多取四個 due scopes,每 Computer/bot 一個候選,SKIP LOCKED 避免被別的交易持有的 intent 卡住;retry_after 30 秒 claim/backoff 在 migration 042 持久化,query 5 秒/單筆 delivery 20 秒/provider 15 秒上限,最多四筆並行,輪間 2 秒。這是 delivery 排程,不延長原 GUI lease,不自動建立新 publication。
- foreground 與 background 共用 deliver,發送前與回覆後核對目前 Computer/bot/ScreenRow assignment;發送前另核對原 claim/context、paused、run lease 上限及精確已提交 intent。背景僅處理原 request 未過期者,過期與未明歷史保留 pending,不把 expiry 當作 quiescence。
- migration 042 另保存原 owner_user_id/owner_space_id。發送與 ack 都核對此 proof,防止把舊 intent 以後來的新 owner 身分重送。舊 schema 的 intent 缺 actor proof 時保持 NULL、不由目前 owner 推定補值,background 排除;新的 explicit startup 可保存目前合法 actor 的新 intent。
- SQLx HTTP regression 改由背景批次完成 lost response 與 DB ack fault 後的原 request 重試;驗證舊 epoch 不發 RPC、過期/缺 actor proof/錯誤 owner 不重送且 pending 不刪。新增六 bot 的 bounded batch 測試:另一交易持有 intent lock 時,其他四 scope 先成功,其餘 scope 繼續;解鎖後才完成原 scope。這是批次與鎖行為證據,不代表任意大量歷史 backlog 的公平性已驗收。
- 完整 API 215 passed/4 ignored(/tmp/lazyboy-gui-retry-api.log,30.87 秒)、API all-targets Clippy -D warnings/diff check 通過。真實完整 Runner gate 通過(/tmp/lazyboy-gui-retry-gate.log,API 整合 26.32 秒):root 成功 publication 回覆被 proxy 丟棄後,由 retry_batch 讀取同一 durable intent、透過正式 signer 重送並取得 ack,未重新呼叫 startup 產生 authority。重用未改動的已建 controld fixture image;測試自有 containers/networks 已確認清除,未部署。
- 後續仍需改善大量同 scope 舊 pending 的 current-intent 優先序與掃描成本(目前依 due/created 排序,不能把小型六 bot 測試當作大 backlog 無飢餓證明);過期/缺 proof pending 的可信收斂與 acknowledged retention 尚缺。中央 pause/takeover 等待 root acknowledgement、GUI executor guard/deadline/quiescence、boot/service epoch 與完整人類輸入管制仍待接線。P0–P3 原定完整範圍維持,O27 仍 PARTIAL。
## GUI retry 排除過時 backlog 與 Computer 授權快照(2026-09-11)
- 修正背景候選只按 due/created 排序的缺口:同 scope 大量尚未到期但已過時 epoch,原先可能逐筆占用批次,令目前有效 intent 等到過期。候選現在先 join 目前 Computer/bot/screen,核對 owner/space、running、home、完整 claim、paused 及 execution lease 上限;過時記錄不取得 retry claim,也不刪除。真正送 RPC 前/後仍保留交易內授權重查,不以候選快照替代 fence。
- 同時補上原 request 尚未保存的 Computer 授權狀態:migration 043 新增 computer_authority JSONB(scope/kind/browser_profile_mode/execution_fence)。reserve、重用、發送、ack 与背景候選均核對原快照,避免 Computer fence 或 profile mode 已變更後,背景 worker 以新 ComputerRow 重送舊授權。歷史缺快照保持 NULL,不推定 backfill,須新的 explicit request。新增 current claim/expiry partial indexes,將具原始授權的 pending 查詢與歷史資料區分。
- SQLx 六 bot fixture 加入 900 筆較早、仍未到期的舊 epoch 與 100 筆過期 intent;持有有效 intent 的外部 transaction lock 時,其餘四 scope 於 3 秒測試期限內完成,下一個有效 scope 繼續,解鎖後完成原 scope;全部 1000 筆 obsolete pending 原 retry_after 未變且仍保留。這驗證指定 backlog 下不占用候選,尚非任意量級/全 provider 公平性保證。
- HTTP fixture 另驗證缺 Computer snapshot、Computer execution_fence 變更、browser_profile_mode 變更均不發 RPC;原 owner/expired/lost reply/ack DB fault/concurrent pause 與 exact request retry tests 保留。
- 完整 API 215 passed/4 ignored(/tmp/lazyboy-gui-backlog-api.log,30.75 秒)、API all-targets Clippy -D warnings/diff check 通過。完整真實 Runner gate 亦通過(/tmp/lazyboy-gui-backlog-gate.log,API 整合 25.82 秒),包含原 root 成功 publication lost reply 後的 background acknowledgement;中途 UI 誤按中斷未重啟仍在執行的 gate,確認原 handle terminal 後收集結果。自有 fixture containers/networks 已確認清除。未套用部署 migration/未部署。
- 大規模全域吞吐/公平性量測、pending 可信收斂與歷史 retention 仍需完成;下一步仍是 GUI executor admission guard/deadline/quiescence 與 pause/takeover acknowledgement,全 service epoch/人類輸入/任意 shell 隔離亦未完成。P0–P3 全部範圍維持,O27 仍 PARTIAL。
## GUI transport target 綁定修復(2026-09-11)
- 接 GUI executor guard 前查出 routing 缺口:Supervisor act 允許 body display/profile 覆蓋 header target,browser/recording 在 body-only target 情況又可能由 header/default target 決定另一畫面。新增共用 checked_gui_target,body/header 同時存在必須相同,slot/display 必須符合既有八畫面 layout;明確 empty/非法 display/slot/profile control characters 拒絕,不默默降到 primary。無 target 的既有 primary fallback 保留,這不是授權成功證明。
- Supervisor 及 controld 的 act/browser/recording start/stop/collect 均先呼叫 strict header wrapper,拒絕重複 display/profile/slot header(包含相同重複值),再使用一致 target;controld 不再在這些路徑以 header precedence 隱藏 body 衝突。profile 在此僅檢查 transport 結構與一致性,合法 filesystem scope/實際 inode 身分仍由後續 admission 契約處理。
- 160 control tests、15 SDK tests、兩個服務的 header regression 與 affected all-targets Clippy -D warnings 通過;Python compile/diff check 通過。
- 新 disposable tests/gui-targets.test.py 啟動本轮重新編譯的實際 controld,對五個 GUI 路由測 display/profile mismatch、重複 display/profile 及 slot mismatch,共 25 cases 全部得到 route 層的精確 400 Bad Request,結束後回收該 fixture daemon。新增至完整 Runner gate;Supervisor HTTP fixture 同時增加五個路由拒絕 conflict 的驗收。
- 此次沒有將普通 HTTP 成功當作 quiescence,也尚未接 root GUI executor guard。現行 Computer start 會以 lazyboy UID 啟動 controld,任意同 UID shell/服務身分与可信 completion 邊界仍需補強;executor 在 timeout/斷線時不得自動 confirm_quiescent,pause/takeover 必須等可信 root barrier ack。P0–P3 所有原定範圍維持,未部署。
- 最終完整 Runner gate 通過(/tmp/lazyboy-gui-target-gate.log,Supervisor HTTP 4.11 秒、API 真實恢復整合 26.92 秒),包含實際 controld 25 種拒絕案例及 Supervisor 五路由 conflict 拒絕。自有 fixture containers/networks 已確認清除。
## Cua 未明呼叫不再自動重開 session 放行(2026-09-11)
- 查出 CuaClient 在 mutation timeout 後,下一次 mutation 會 remove suspect marker 並嘗試 start_session;即使 driver 拒絕或其他錯誤,也可能丟失 marker。新 session/HTTP 可回覆都不能證明舊 driver input 已停止,因此移除此自動修復放行。
- 新 CuaClient 本地 MutationGuard 共用八個 per-display mutex,單次 admission 等待上限 5 秒;進入執行後如果 timeout、outer future 被取消或不明 transport/driver outcome,Drop 在釋放 lock 前保存 unresolved marker。新 mutation/start_session 對同 display 返回 EffectUnknown;其他 display 繼續。只取消尚未取得 guard 的等待者、不曾啟動的缺失 driver,以及正常完成的已知結果不留下 false unresolved。
- Read path 若遇到 session ended、需要 start_session,也必須取得同樣 mutation guard,不能以 read 名義清掉未明 writer。一般讀取沒有新增 writer lock。delivery-mode escalation 限制為已知 semantic refusal/busy,timeout、DriverUnhealthy、Internal、EffectUnknown 等不再因 recommended delivery 而重送。controld 將 EffectUnknown 回傳 409。
- 164 個 control tests、2 個 controld tests 與 affected all-targets Clippy -D warnings/diff check 通過(/tmp/lazyboy-cua-unresolved-verified-tests.log)。新增測試包含共享 client 的真實 task abort、等待者取消、peer display、不存在 driver、未知 outcome 不 escalation;另以隔離暫存 fake CLI 啟动真實子程序,等待其讀完 request 後取消,確認 CLI PID 已消失仍不允許 start_session,peer 的兩次正常 call 都成功。此 fixture 只測真實程序/transport cancellation,不是實際 GUI input 完成證明。測試 script 的第一版 Rust 引號錯誤已修正,最終重新編譯/測試與 Clippy 通過。
- 完整 Runner gate 通過(/tmp/lazyboy-cua-unresolved-gate.log,API 真實恢復整合 26.70 秒),重新建置 controld fixture,原實際 GUI target 25 cases/Supervisor HTTP/native/MCP/retirement 與 publication retry gate 保留。測試自有 containers/networks 已確認清除,未部署。
- **這是目前 controller instance 內的防護,不是 root durable GUI executor guard。** marker 仍在記憶體,跨 controller instance/process restart 不保留,也未提供可信退休 proof 的清除流程;跨 profile 排他、整個 multi-call GUI operation guard、同 UID shell/daemon authenticity、server-side human input、pause/takeover barrier 仍需完成。不可把 CLI 已退出/一般 HTTP 成功當成遠端 GUI effect quiescence。P0–P3 原範圍維持,O27 PARTIAL。
## GUI 觀察與健康路由的 target 驗證(2026-09-11)
- 延續 O26/O27/O46 檢查,修正 controld observe/controller health 仍使用寬鬆 display fallback 的缺口,以及 Supervisor observe 未使用同一 strict target validator 的差異。三個路徑現在拒絕明確空值、非法或遠端 display、重複 target headers 與 slot/display 衝突,使用已驗證的 display/profile 建立 context;未指定 target 時保留既有 primary 行為。Team/Dedicated 共用相同檢查,不改 assignment 或資料。
- 擴充 disposable controld HTTP fixture:兩個觀察/健康路由各增加 7 種拒絕案例,合計由 25 增為 39;Supervisor 真實 HTTP fixture 增加 observe 空/非法/遠端 display 與重複 header 拒絕。這些案例驗證路由在 backend 前返回 400,不等於實際 GUI effect 或 admission/quiescence 驗收。
- 兩個服務共 3 個 header/error regression tests、affected all-targets Clippy -D warnings、Python compile 與 diff check 已通過。完整 Runner gate 以重新建置的 controld 通過(/tmp/lazyboy-gui-read-target-gate.log):39 種實際 controld HTTP 案例、Supervisor HTTP 4.07 秒、API 真實恢復整合 26.26 秒;原 native/files/MCP/publication retry 驗證保留。自有 fixture containers/networks 已確認清除。
- 回滾可還原這三個讀取路由的 target 選取及新增 fixture;未部署。root durable executor、可信完成/退休 proof、pause/takeover acknowledgement 與全部尚缺 P0–P3 項目持續保留,下一步檢查 cursor configuration 的 timeout 是否被忽略而繼續 mutation。
## Cua 游標設定未明即停與使用量暫停交接(2026-09-11)
- 修正 configure_cursor_motion 的 500 ms timeout/不可解析回覆/driver timeout 原先被忽略且寫入 cache,隨後繼續 click 的問題。現在返回 Result,未知結果向外傳遞,既有 MutationGuard 保存 unresolved;不送後续 input、不 cache 未明設定。session restart 後的設定同樣傳遞錯誤。已知 semantic refusal/busy 的 optional setting 保留相容 cache 行為。
- 真實 fake CLI fixture 驗證 timeout、malformed、structured timeout 三種失敗只收到 set_agent_cursor_motion,下一次 click 得 EffectUnknown;unsupported 設定可正常連續兩次 click 且只設定一次。這是本地 transport 證據,不是遠端 GUI quiescence,也不改變 root durable executor 尚未接線的狀態。
- 最終 165 control tests+2 controld tests 通過(/tmp/lazyboy-cua-motion-tests-final.log);Clippy 最初報 collapsible-if,已改成同語意 let-chain,最終 affected all-targets -D warnings 通過(/tmp/lazyboy-cua-motion-clippy-final.log)。容器 gate 使用該純語法整理前的同語意 runtime snapshot;host 最終測試/Clippy 覆蓋整理後 source。
- 使用者表示剩約 10% 使用量,要求列已做/未做並收尾等待。已新增 [暫停交接文件](agent-computer-handoff-2026-09-11.md),逐項保留 O01~O48 状態、完整待辦與操作交接;不再開新功能。已啟動的完整 Runner gate 完成後補記,工作樹保留未提交,未部署。
- 收尾完整 Runner gate 已通過(/tmp/lazyboy-cua-motion-gate.log,Supervisor HTTP 4.19 秒、API 整合 26.82 秒);本次 fixture containers/networks 已確認清除,所有測試 handle 結束。最終 diff check 通過,依使用者要求暫停等待恢復,不啟動下一個實作。