202a5ab8d6
新 SDD .agents/specs/component-gatekeeping/(richblack 確認,含 venue 修訂 + 信任模型)。 registry 端靜態把關(CF Worker 可跑,不執行 wasm): - G1 detectFakeComponent: 外部 URL/domain + http_request 子集偵測,硬擋退稿指回 recipe - G3 wasmImports: 解析 wasm import section,只准 wasi_snapshot_preview1 + u6u 白名單 - G5/G6: unimplemented_steps 明列 gherkin/cold_start/runtime_compat,不假綠(§3c/§7) - gherkin_evidence 一致性驗證(投稿者本地跑,registry 不重跑——CF 禁 runtime 編譯 wasm) 把關範圍:公共庫 + self-hosted 私人庫同一套(design §0.0)。 信任模型(design §4.5):Gherkin 全綠≠安全;純 WASI 沙箱框死能力才是發佈底氣; 第一期 evidence 可造假(誠實標明),平台重跑列未來。 hook: pre-write-guard 白名單加 component-gatekeeping / component-registry-canon SDD 目錄。 測試: sandboxAcceptance.test.ts 4 綠(含 G1 假零件被擋)。 待續(同 SDD): G4 CLI 投稿指令本地跑 Gherkin、G0 人類閘門、R5 白名單+本機 hook。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
176 lines
13 KiB
Markdown
176 lines
13 KiB
Markdown
# Design: Component Gatekeeping(零件投稿真把關)
|
||
|
||
> 2026-05-29。實作 requirements.md 的 R1-R6。**本 design 需 richblack 確認後才動 registry code。**
|
||
|
||
---
|
||
|
||
## 0. 架構總覽
|
||
|
||
### 0.0 範圍:把關跨公共庫 + self-hosted 私人庫(richblack 2026-05-29)
|
||
|
||
把關**不是公共庫專屬**。每個 self-hosted 部署有自己的零件庫(自己的 registry Worker)。
|
||
**加入公共庫或任何 self-hosted 私人庫,都跑同一套把關鏈(G0-G6 + 本機 hook)。**
|
||
- 實作上天然成立:把關邏輯在 registry Worker code 裡,self-hosted 跑同一份 registry → 把關跟著走,不需公私庫分兩套。
|
||
- G0 人類閘門在 self-hosted 下,「人類」= 部署擁有者本人(防的是「他的 AI 自作主張把東西做成零件」,不是防他本人;他自己確認 + 舉證即可過)。
|
||
- 理由:self-hosted 一樣有「自用服務沒驗證就變零件」的風險,且私人庫零件之後可能貢獻回公共。
|
||
|
||
### 0.1 把關鏈
|
||
|
||
投稿零件的唯一入口是 registry Worker 的 submit。把關鏈(依序,任一失敗即退稿):
|
||
|
||
```
|
||
submit 請求(帶 wasm + contract + 人類確認憑證 + 舉證)
|
||
│
|
||
├─ G0 人類閘門(R4) ← 最先擋:沒人類確認 + 舉證 → 403
|
||
├─ G1 假零件偵測(R2) ← contract/原碼有外部 URL 或 http 子集 → 退稿指回正路
|
||
├─ G2 size_check(已有)
|
||
├─ G3 syscall_scan + 純WASI(R3)← 擴充:只准 WASI preview1 + u6u host func 白名單
|
||
├─ G4 gherkin_tests(R1) ← 真跑 WASM,given→stdin→比對 then_contains
|
||
├─ G5 cold_start(mock,標未實作)
|
||
└─ G6 runtime_compat(mock,標未實作)
|
||
→ 全過 → 派 hash → 寫 KV
|
||
```
|
||
|
||
另一道獨立防線:**本機 hook**(R5),擋 CC 繞過 API 直接在 repo 造零件目錄。
|
||
|
||
---
|
||
|
||
## 1. G0 人類閘門(R4)— registry submit endpoint
|
||
|
||
### 1.1 請求格式增欄
|
||
submit 請求 body 增兩個欄位:
|
||
```ts
|
||
interface SubmitRequest {
|
||
wasm_base64: string;
|
||
contract: ComponentContract;
|
||
human_confirmation?: {
|
||
confirmed_by_human: true; // 必須為 literal true
|
||
reason_why_not_workflow: string; // 非空,AI 舉證
|
||
confirmed_at: string; // ISO timestamp
|
||
};
|
||
skip_acceptance?: boolean; // 既有:backfill 用(仍保留)
|
||
}
|
||
```
|
||
|
||
### 1.2 閘門邏輯
|
||
```
|
||
若 skip_acceptance(backfill 既有零件)→ 跳過 G0(這些是已驗、已部署的存量,不是新投稿)
|
||
否則(新投稿):
|
||
若無 human_confirmation 或 reason_why_not_workflow 空 → 403:
|
||
"建零件需人類確認。請用 `acr component create`(會互動式問你),
|
||
並說明為何工作流做不到。預設假設工作流能做——先試工作流 / recipe。"
|
||
記錄 reason_why_not_workflow 進 KV metadata(軌跡可審)
|
||
```
|
||
|
||
### 1.3 四路收斂(CLI / MCP / Python / JS)
|
||
- 它們建零件都呼叫 registry submit endpoint → G0 在 endpoint,自動四路通管。
|
||
- **CLI `acr component create`**:強制互動式 prompt 問人類「(1) 工作流為何做不到?(2) 確認要建零件?」,把答案組成 `human_confirmation` 送出。非互動環境(AI 直跑)`acr` 偵測 stdin 非 TTY → 拒絕並提示「需人類互動」。
|
||
- MCP / Python / JS lib:傳 `human_confirmation` 才能成功;它們的 SDK 文件註明此欄位需人類提供。
|
||
- **誠實限制**(寫進 mindset + 文件):AI 技術上能偽造 `confirmed_by_human:true`。靠 reason 留記錄 + mindset 明示「絕不代替人類確認建零件」+ 軌跡可審,讓偽造成明確越界,不聲稱不可能繞過。
|
||
|
||
## 2. G1 假零件偵測(R2)
|
||
|
||
新增 `detectFakeComponent(contract, wasmBytes): string | null`:
|
||
- (a) **外部 URL/domain**:掃 contract 的 description / input_schema / output_schema 文字,及 wasm binary 文字,比對 URL pattern(`https?://`、常見 domain)。命中 → 退稿。
|
||
- (b) **http_request 子集**:若 contract 宣告只做「打固定 endpoint」(heuristic:description 含「打/呼叫 ... API/endpoint」且 input 有 url-like 欄位且無實質邏輯運算),標記疑似。
|
||
- 退稿訊息:「偵測到疑似假零件(寫死 endpoint / http 子集)。這該是 API recipe(http_request + 固定設定)或工作流,不是零件。見 DECISIONS §1。」
|
||
- 排除:`auth_*` primitive(credential 後端,DECISIONS §3b 不適用假零件判準)、`http_request` 自己。
|
||
|
||
## 3. G3 純 WASI 把關(R3)
|
||
|
||
擴充現有 `scanSyscalls`:
|
||
- 現況:掃 `FORBIDDEN_SYSCALLS` 黑名單。
|
||
- 擴充:改為「import 白名單」——解析 wasm import section,確認所有 import module 只屬 `wasi_snapshot_preview1` + `u6u`(host functions)。出現其他 module → 退稿(runtime 鎖定風險)。
|
||
- 實作:簡易 wasm import section 解析(不需完整 wasm parser,掃 import 段的 module name 字串)。
|
||
|
||
## 4. G4 Gherkin 真實作(R1)— **修訂(2026-05-29,richblack review)**
|
||
|
||
### 4.0 為何不能在 registry Worker 跑(原設計作廢)
|
||
原設計假設 registry Worker instantiate 投稿 wasm 跑 Gherkin。**此假設錯誤**:
|
||
- **Cloudflare Workers 禁止 request-time 編譯 WASM**(`new WebAssembly.Module(bytes)` / `WebAssembly.compile()` 只能 startup 用 bundle 的 module;workers-types 把 `Module` 標 abstract 正反映此限制)。registry 收到的是 runtime 投稿 bytes → 跑不了。
|
||
- DECISIONS §8:第一期**不依賴 GitHub Actions** → 也不能靠 CI 跑 Gherkin。
|
||
- 剩下唯一一致 venue = **投稿者本地機器**(有 tinygo + 能跑 wasm,與現有 build 流程同環境)。
|
||
|
||
### 4.1 正確設計:Gherkin 在投稿指令本地跑
|
||
零件投稿走一個**獨立 CLI 指令**(暫名 `acr component submit`;「本地或公共都是投稿」):
|
||
1. 本地 `tinygo build`(或讀已 build 的 .wasm)。
|
||
2. **本地跑 Gherkin**:對每個 `gherkin_tests[]`,用 Node 的 WebAssembly + 同一份 wasi-shim instantiate wasm,given→stdin→run→比對 then_contains。Node 環境能 runtime 編譯 wasm(不像 CF Workers)。
|
||
3. 任一 scenario 失敗 → 投稿指令本地就擋下,不送出。
|
||
4. 通過 → 把**測試結果隨投稿上傳**(見 4.2)。
|
||
|
||
`runGherkin.ts`(已寫,用 createWasiShim)邏輯正確,只是**執行 venue 從 registry Worker 改成 CLI(Node)**。registry 端不再跑 Gherkin。
|
||
|
||
### 4.2 「平台看得到測試結果」(呼應 §3c:執行者不能驗證自己)
|
||
投稿 payload 帶 `gherkin_evidence`:每個 scenario 的 `{scenario, given, actual_stdout, passed}`。
|
||
- registry 存進 KV metadata(軌跡可審)。
|
||
- 平台看得到**原始 stdout**,不是只看投稿者宣稱的「passed」。
|
||
- **誠實限制**(同人類閘門):本地跑 + 自報結果,AI 技術上能偽造 actual_stdout。靠軌跡可審 + mindset 明示 + 未來 §3c 的 test/relay(投稿走 relay 讓平台當下親跑,第一期後)補強。第一期是「本地跑 + 上傳證據 + 可審」,不聲稱不可繞過。
|
||
|
||
### 4.3 公私庫分流(投稿指令旗標)
|
||
- 預設投稿 → **私人庫**(self-hosted 自己的 registry)。
|
||
- `-p` / `--public` → 推**公共庫**。
|
||
- 兩者都跑同一套把關(§0.0:跨公私庫同一套)。差別只在目標 registry。
|
||
|
||
### 4.4 registry 端對應
|
||
- registry submit 仍跑 G1(假零件)、G3(純WASI)——這兩個是**靜態掃描,不需執行 wasm,CF Worker 可跑**。
|
||
- G4 Gherkin 的執行移到 CLI;registry 收 `gherkin_evidence` 存證、可選做輕量一致性檢查(evidence 的 scenario 數與 contract.gherkin_tests 對得上、每個 passed=true),但**不重跑**(跑不了)。
|
||
- G5/G6(cold_start/runtime_compat)維持 unimplemented_steps 標記。
|
||
|
||
## 4.5 信任模型與發佈風險(richblack 2026-05-29 定,重要——勿誤讀為零風險)
|
||
|
||
**Gherkin 全綠 ≠ 零件安全可發佈。** 把關能驗到什麼、不能驗到什麼,要說清楚:
|
||
|
||
### 把關各層各防什麼
|
||
| 層 | 防得了 | 防不了 |
|
||
|---|---|---|
|
||
| G4 Gherkin | 「零件對宣稱的 given 不 work」 | 「Gherkin 寫得避重就輕、只測會過的 case」——投稿者可寫不完整的測試,沒測到的 input 仍可能出錯/惡意 |
|
||
| G1 假零件 / G3 純WASI | 連外 / 非 WASI / 寫死 endpoint | 沙箱內的邏輯錯誤 |
|
||
| **純 WASI 沙箱(真正的發佈底氣)** | **框死零件能做的事**:只能 stdin→stdout,無網路 syscall、無檔案系統、無 host 能力(除白名單 host func)→ 就算邏輯有問題或惡意,傷害被沙箱框死 | 沙箱允許範圍內的錯誤輸出 |
|
||
| 市場機制(§1) | 長尾:別人用了發現不對 → 回報 → 修 | 投稿當下測不到的 |
|
||
|
||
**結論:發佈非零風險,是風險可控。** 主要安全保證是**純 WASI 沙箱框死能力**(不是 Gherkin)。Gherkin 驗行為契約、沙箱框死破壞力、市場補長尾,三者合起來讓「發佈陌生人的零件」風險可控到能接受。
|
||
|
||
### 信任層級(誰跑 Gherkin 決定可信度,§3c:執行者不能驗證自己)
|
||
- **第一期**:投稿者**本地跑 + evidence 上傳可審**。AI 技術上能偽造 actual_stdout(它控制本地環境)。**誠實標明:第一期 evidence 可造假**,靠純 WASI 沙箱 + 軌跡可審 + mindset 撐著。
|
||
- **未來**:平台**自己重編源碼 + 重跑 Gherkin**(投稿者無法造假「跑的結果」)。需要能 runtime 跑 wasm 的 venue——**非 CF Worker(不能 runtime 編譯)、非 CI(§8 不依賴)**——可能是公共庫專屬的 sandbox 服務。列未來,可能擋公共庫「完全可信發佈」。
|
||
- 再往後:§3c 的 test/relay(投稿走 relay 讓平台當下親跑)。
|
||
|
||
## 5. G5/G6 mock 標未實作(R3 誠實)
|
||
|
||
- cold_start / runtime_compat 保留 mock,但 **SandboxResult 增 `unimplemented_steps: string[]`**,回傳時明列 `["cold_start","runtime_compat"]`,submit 回應與文件明示「這兩步未實作、未真正驗證」。不回 `success:true` 假裝全綠——回 success 但附 unimplemented 清單。
|
||
|
||
## 6. R5 白名單 + 本機 hook
|
||
|
||
- `registry/MVP_COMPONENTS.txt`:一行一個白名單 canonical_id(現役 22 個)。
|
||
- `pre-write-guard.sh` 增規則:寫入 `registry/components/{name}/...` 且 `{name}` 不在 MVP_COMPONENTS.txt → exit 2,訊息「新增零件需走 submit API 人類閘門,不可直接造 repo 目錄」。
|
||
- `pre-bash-guard.sh` 增規則:`mkdir .../registry/components/{白名單外}` → exit 2。
|
||
- `.ts` 偵測現有 hook 已做(rule 1.1)。
|
||
|
||
## 7. 範圍邊界
|
||
|
||
- **動 registry TS**(sandboxAcceptance / submitComponent / routes / types)+ **CLI**(acr component create)+ **hook**。
|
||
- 不動 cypher-executor 執行路徑、不動既有零件 wasm。
|
||
- backfill 路徑(skip_acceptance)保持可用,不被新閘門擋(存量零件不需人類閘門)。
|
||
- CLI/MCP/Python/JS 四路:本期至少做 CLI `acr component create` + registry endpoint 強制;MCP/Python/JS 補 `human_confirmation` 欄位支援(薄)。
|
||
|
||
## 8. 驗收標準
|
||
|
||
- 投一個寫死 endpoint 的假零件 → G1 退稿(終端輸出)。
|
||
- 投一個 `.ts` 進 registry/components → hook exit 2。
|
||
- 投一個白名單外的新零件目錄(本機造)→ hook exit 2。
|
||
- 無 human_confirmation 的 submit → 403。
|
||
- 帶 human_confirmation + 過 Gherkin 的真零件 → 通過、寫 KV、reason 留 metadata。
|
||
- Gherkin given/then 對真零件跑綠;故意改壞 then_contains → 退稿。
|
||
- cold_start/runtime_compat 在回應裡列入 unimplemented_steps(不假綠)。
|
||
|
||
## 9. 決議(richblack 2026-05-29 design review 定)
|
||
|
||
- **Q1 → 消解**:Gherkin 測的零件**永遠是封閉邏輯(框架),不連外**。任何要加外部 URL 的東西按定義就是 recipe,不是零件——這種「連外零件」根本不該存在(會被 G1 假零件偵測擋下、降成 recipe)。所以 G4 Gherkin 只跑不需 host function 的封閉邏輯零件,**不需要 mock host func、不需要 skip 機制**。零件用 `u6u.http_request` 連外 = G1 直接退稿。
|
||
- **Q2 → 兩者都硬擋**:(a) contract/原碼有具體外部 URL/domain → 硬退稿;(b) 宣告能力是 http_request 子集 → 也硬退稿。理由:與 Q1 一致——零件不該連外,這兩個 pattern 都是「該是 recipe 的東西偽裝成零件」,硬擋無誤殺顧慮(真的要連外就去做 recipe)。
|
||
- **Q3 → submit 過閘門後自動 append**:人類閘門通過 + 驗收綠的零件,submit 成功時自動把 canonical_id append 進 `MVP_COMPONENTS.txt`。白名單反映「已正當投稿的零件」,不需手動維護。本機 hook 讀此檔擋「白名單外的直接造目錄」。
|
||
|
||
### Q1 連帶結論(強化 G1)
|
||
既然「零件不連外、連外即 recipe」是硬規則,G1 假零件偵測 = G4 Gherkin 的前置守門:
|
||
G1 擋掉所有連外/http 子集的投稿 → 能進到 G4 的必然是封閉邏輯零件 → Gherkin 必然不需 host func。
|
||
兩道閘門邏輯自洽。
|