---
status: paused
superseded_by: ""
---

# Design: Component Gatekeeping（零件投稿真把關）

> 2026-05-29。實作 requirements.md 的 R1-R6。

---

## ⚠️ 方向修正（richblack 2026-05-30）：投稿走 GitHub PR，廢 registry self-service

**零件投稿管道 = GitHub PR，不是 registry submit API。** 理由與影響見下；以下 §0-§9 的 registry submit 設計，
凡屬「self-service 投稿管道」者**作廢**，凡屬「把關邏輯」者**搬到 CI（PR check）跑**。

**為何改：** primitive 極少、未來絕大部分是 recipe → 新增零件是稀有低頻事件，不需 self-service 自動化管道。
PR 天然滿足每道閘門：

| 設計的閘門 | PR 怎麼天然滿足 |
|---|---|
| G0 人類閘門 | PR 必須有人 merge（richblack approve）；AI 偽造不了 GitHub approve |
| 舉證「為何不是工作流」 | PR description，review 時看 |
| G1 假零件 / G3 純WASI / G4 Gherkin / 覆蓋檢查 / 黃金向量 | **CI（PR check）跑** —— CI 有 tinygo + 能 runtime 跑 wasm，**繞開 CF Workers 不能 runtime 編譯 wasm 的 venue 牆** |

**§8 衝突釐清：** DECISIONS §8「不依賴 GitHub Actions」指**執行鏈路**（init/push/run/recipe，常態高頻，
用戶機器+CF）。**零件投稿是稀有低頻、該由 PR 治理**，用 PR/CI 不違反 §8——反而更對（CI 能跑 wasm，
registry Worker 不能）。需在 DECISIONS 補這個區別（待 richblack 確認改穩定文件）。

**哪些作廢 / 哪些保留：**
- ❌ 作廢：registry submit API 當主投稿管道、四路（CLI/MCP/py/js）self-service 投稿、平台端 sandbox 重跑、`acr parts publish` 加人類閘門（投稿不走 CLI 了）。
- ✅ 保留並搬 CI：G1 假零件偵測邏輯（detectFakeComponent.ts）、G3 純WASI（wasmImports.ts）、G4 Gherkin 真跑（CI 能跑 wasm）、B 覆蓋檢查、黃金向量人工核對。
- ✅ 已 commit 的 registry G0/G1/G3 程式碼**保留不刪**（無害，且 G1/G3 邏輯被 CI 複用），但 registry submit 不再是主管道。
- ✅ R5 本機 hook（擋 CC 直接造零件目錄）仍要 —— 它擋的是「繞過 PR 直接改 repo」，與 PR 管道互補。

**以下 §0-§9 為原 design，閱讀時套用上述修正。**

---

## 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 parts publish`（會互動式問你），
     並說明為何工作流做不到。預設假設工作流能做——先試工作流 / recipe。"
  記錄 reason_why_not_workflow 進 KV metadata（軌跡可審）
```

### 1.3 四路收斂（CLI / MCP / Python / JS）
- 它們建零件都呼叫 registry submit endpoint → G0 在 endpoint，自動四路通管。
- **CLI `acr parts publish`**：強制互動式 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 指令**（既有指令；「本地或公共都是投稿」）：
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 清單。

## 5.5 黃金向量：人工核對 + B 覆蓋檢查（richblack 2026-05-30 定）

Claude.ai 建議用「黃金向量 + 把關自己重跑」自動驗收，防放水的 Gherkin。**價值保留，實作降級**，理由：

- **primitive 極少、未來絕大部分是 recipe**（人類閘門 + 工作流優先把零件擋在源頭）。現役 17 白名單 + cron/platform_crypto，未來極少新增。
- 「把關自動重跑向量」是為「零件大量增加」做的規模化基建——但零件不會大量增加 → 為不存在的規模做自動化 = 過度工程（DECISIONS 附錄「會不會累積成債」判準）。
- 且「把關自己重跑 wasm」撞 venue 牆（CF 不能 runtime 編譯 wasm，同 §4.0）→ 需平台端 sandbox（第一期沒有）。

**降級後做法：**
- **A 黃金向量 conformance → 人工核對**：黃金向量當「人類閘門時用 CC 核對 primitive 的對照表」。新增 primitive（極稀有）時，人類閘門已要你親自確認，那一刻用 CC 本地跑向量核對（本地有 tinygo + 能跑 wasm，繞開 venue 牆）、人工凍結。**不做機器自動重跑。**
- **B 覆蓋檢查 hook → 現在做（純靜態、不可造假、成本低、價值與零件數無關）**：靜態 parse contract：`input_schema` 每個 required 欄位至少出現在一個 Gherkin given、`output_schema` 每個欄位至少被一個 then_contains 斷言。缺 → exit 2 指出漏哪個欄位。擋「只測 happy path、不碰宣告過的行為面」。
- **初始向量來源（信任根：寫向量≠寫實作）**：另起 session 從 contract 語義寫、不看實作原碼（primitive 語義客觀如 add(2,2)=4）。人工核對用，不急、新增 primitive 時逐個補，不必 21 個一次到位。
- 殘留（誠實）：向量/覆蓋檢查擋不住「列出的 case 全對、沒列到的 case 錯」。交給「用」——出 bug 補進向量，永不 regress。是會長大的網，非設一次完美。

## 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）。
- **B 覆蓋檢查**（5.5）：可放 registry submit 的靜態驗收（不需跑 wasm，CF 可跑）或 pre-write hook，擋宣告過的欄位沒被 Gherkin 測到。

## 7. 範圍邊界

- **動 registry TS**（sandboxAcceptance / submitComponent / routes / types）+ **CLI**（acr parts publish，既有指令）+ **hook**。
- 不動 cypher-executor 執行路徑、不動既有零件 wasm。
- backfill 路徑（skip_acceptance）保持可用，不被新閘門擋（存量零件不需人類閘門）。
- CLI/MCP/Python/JS 四路：本期至少做 CLI `acr parts publish` + 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。
兩道閘門邏輯自洽。
