Files
Arcrun/registry/components/code/DESIGN.md

91 lines
7.0 KiB
Markdown
Raw Permalink 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.
# `code` 零件 —— 沙箱設計小結(Arcrun#10)
> 狀態:**Workers 就緒(裁定 A,可部署)**Node/vitest 12/12 綠燈,**未部署 leo21c**。live 前需總管與 leo 過寫入/部署閘。封裝=Aquickjs-emscripten singlefile variant);B(自建 QuickJS+wasi-sdk)列後續技術債。
## 0. arcrun 零件 runtime 真相(先摸清再設計)
- 每個 logic 零件 = **一顆 Worker**`{name}.arcrun.dev`),POST JSON → 內部跑 wasm → JSON 回傳。
- 零件實作 = **Go`//go:build tinygo`)→ TinyGo 編成 WASI-preview1 wasm**build 時靜態 bundle 進 Worker`wrangler.toml [[wasm_modules]]`)。
- host`component-worker-template/src/index.ts`**用 TS 自己實作一份 WASI-preview1 shim**`fd_read` 餵 stdin= POST body 的 JSON)、`fd_write` 收 stdout= 回傳 JSON)。
- `no_network` / `no_filesystem` **不是靠 wasm 自律,是 host 根本不提供那些 import**`sock_*` / `path_*` 全回 `ENOSYS(76)``u6u.http_request` no-op。→ 零件對外「無 ambient 能力」是**由 host 建構保證**的。
- runtime 是 **workerd(=cf-workers**contract 的 `wazero` 只是相容標記(本機/CLI 測試路徑),生產不經 wazero。
**關鍵結論**host 只認 WASI-preview1 + stdin/stdout JSON**與 guest 語言無關**。所以載入「QuickJS 編成的 wasm」與載入 TinyGo wasm 在 runtime 層是同一件事 —— **QuickJS-wasm 可行,且完全合現有零件模型**。差別只在 guest 從「TinyGo」換成「QuickJS」,且 user 的 JS 是「跑時餵進去的資料」而非「build 時編進去的程式」。
## 1. 沙箱機制
user JS 跑在 **QuickJS context** 裡,該 context 三層封裝、逐層無逃逸:
```
Cloudflare Worker isolateV8
└─ QuickJS wasm module(線性記憶體沙箱;無 WASI 網路/檔案 import
└─ QuickJS JS contextglobal 只有純 ECMAScript 內建)
└─ user code:讀 input、return 值
```
- **無 ambient 能力(by construction**QuickJS context 起始 global 只有 `Object/Array/JSON/Math/Date/String/RegExp…`**沒有** `fetch/process/require/WebAssembly/XMLHttpRequest/globalThis.env`。要給的能力必須 host 明確、逐一注入。
- **唯一注入的 curated builtin**`sha256(str)`(純、決定性、零能力 —— 不能碰網路/檔案/secret)。card→envelope 的 content_hash 需要它。任何新 builtin 都必須維持「純函式、無 ambient 能力」這條線。
- **input 穿越邊界**:以 JSON 字串 marshal,沙箱內 `JSON.parse` —— host 與 guest **不共享物件圖**,杜絕 prototype/引用逃逸。
- **user code 形狀**:當「函式體」跑(可含 `const`/`function` 宣告、以 `return` 回值),綁定唯一入參 `input`。回值 `JSON.stringify` 後交回 host。
## 2. 生產路徑(裁定 A,已就緒)
`sandbox.mjs`**runtime-agnostic 核心**Node 測試與 CF Worker **共用同一份**
- **封裝**`@jitl/quickjs-singlefile-mjs-release-sync` variantwasm 內嵌 base64、同步載入)
—— CF Workers 相容 loading 路徑(不靠 fetch/fs 取 .wasm),bundler 友善、無需 `[[wasm_modules]]`
- **`sha256` curated builtin**:以**純 JS SHA-256 字串 prelude** 注入沙箱(不呼叫 host、不用 async
Web Crypto、不需 `nodejs_compat`),Node/Worker 皆決定性;與 Node crypto sha256 逐字等價
(測試 ⑤ 對 card 全文比對 content_hash 相同)。「curated builtin = 純演算法字串」定為往後標準。
- **Worker host**`index.ts`HonoPOST /→`runCode`),自足 Worker,不走 TinyGo 模板流程。
- **測試**`sandbox.mjs` + `test/`**Node/vitest 12/12 全綠**(含 card→envelope 全等)。
- **與 B 的差**B(自建 QuickJS+wasi-sdk→preview1 wasm,跑現有 WASI host shim)最同構,但需
wasi-sdk 工具鏈(本環境無 sysroot)+維護 C harness,列後續技術債。
## 3. 資源限制(防跑飛)
| 限制 | 機制 | 預設 |
|---|---|---|
| 執行 timeout | QuickJS runtime `setInterruptHandler`(逐指令檢查 wall-clock deadline | 1000 ms |
| 記憶體 | `setMemoryLimit`QuickJS runtime 硬上限) | 16 MiB |
| 堆疊 | `setMaxStackSize`(防深遞迴) | 512 KiB |
| 輸出大小 | host 量測 stdout JSON bytes,超限即 `ResourceError` | 1 MiB |
| code 大小 | host 量測 user code bytes,超限即 `ResourceError` | 256 KiB |
節點 config 可覆蓋(但不得放寬過契約 `sandbox_limits` 硬上限 —— 由零件在讀 config 時 clamp)。
## 4. 錯誤處理(Worker 絕不掛)
一律回結構化 envelope`error_type` 分類:
- `UserCodeError`user code 拋錯 / 語法錯誤。
- `TimeoutError`:超時被 interrupt。
- `ResourceError`:記憶體 / 輸出 / code 超限。
- `ContractError`:輸入形狀不合(如 code 非字串)。
- `SandboxError`:其餘沙箱層例外。
## 5. 安全性質(總結)
1. user code **無網路**:沒有 fetch/XHRQuickJS wasm 也沒有 WASI socket import。
2. user code **無檔案**:沒有 fsWASI path_* 一律 ENOSYS。
3. user code **無 env/secret**:沒有 processWorker 的 `env`bindings/secret)不進 QuickJS context。
4. user code **碰不到 Worker 物件圖**:獨立 wasm 線性記憶體 + 獨立 QuickJS heap + JSON 邊界。
5. **可終止**:timeout/記憶體/輸出上限,跑飛也不拖垮 Worker。
6. **決定性**(除 `Date`/`Math.random`):curated builtin 全是純函式。
## 6. 設計岔路(給總管/leo 裁)
沙箱主機制 = **QuickJS-wasm**(本 PoC 已證可行且合模型)。封裝方式有兩條,取捨如下:
- **A. QuickJS-emscripten 直接在零件 Worker 用(PoC 走這條,推薦先行)**
優點:本環境即可跑、npm 現成、限制 API 齊(timeout/mem/stack)、已在 Workers 驗證過可用。
缺點:不經現有 TinyGo-wasm 的 `[[wasm_modules]]` + 自建 WASI shim 路徑,是零件家族裡的「特例封裝」。
- **B. 自建 QuickJS+C-harness → wasi-sdk 編成 preview1 wasm,跑在現有 host shim(最「同構」)**
優點:與其他零件同一條 runtime(同 WASI shim),`wasi_target: preview1` 名副其實,「無 ambient 能力」最純。
缺點:需要 wasi-sdk 工具鏈(**本環境無 sysroot,無法即刻 build**)、要維護一段 C harness。
**建議**:先以 A live(快、已驗證),把 B 列為後續「收斂到同構 runtime」的技術債。若總管要求所有零件單一 runtime,則走 B,但需先補 wasi-sdk build 基礎設施。**此為安全敏感原語,機制選定請總管拍板再 live。**
## 7. 首個消費者(Arcrun#8
`km_wiki_ingest_drain` 的 card→envelope 解析,把 `card-to-envelope.mjs``parseCard`/`planEnvelopes`/`planCard` 當作 `code` 節點的 inline JS(去掉 `import 'node:crypto'``export`,改用注入的 `sha256`)。PoC 測試 ⑤ 已證:**沙箱輸出與原始模組 `planCard` 逐欄全等**(含 content_hash)。→ 可丟掉 domain 零件 `km_wiki_card_parse`