feat(code): Workers 就緒化(裁定 A)—— singlefile variant + 純 JS SHA-256 prelude

- 沙箱改用 quickjs-emscripten singlefile variant(wasm 內嵌 base64、同步載入),
  CF Workers 相容;sandbox.mjs 成 Node/Worker 共用 runtime-agnostic 核心。
- sha256 curated builtin 改「純 JS SHA-256 prelude 字串注入」,去掉 node:crypto /
  async Web Crypto host-call,Node/Worker 皆決定性(card content_hash 逐字等價)。
- index.ts 成可部署 Worker host(Hono,POST /→runCode),自足不走 TinyGo 模板流程。
- 補 wrangler.toml(arcrun-code / code.arcrun.dev)、tsconfig、DEPLOY.md。
- contract stability 修為 floating(過 registry zod schema 驗證)。
- 單測 12/12 全綠(含 card→envelope 與原模組 planCard 逐欄全等)。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HJiLCRUU2o3aSpPEzVCt2o
This commit is contained in:
Leo
2026-07-06 04:39:28 +00:00
parent 60f5f10ba5
commit efa0b0578c
8 changed files with 226 additions and 60 deletions
+27 -24
View File
@@ -1,24 +1,25 @@
/**
* arcrun `code` 零件 —— Worker host骨架,尚未部署驗證
* arcrun `code` 零件 —— Worker host可部署
*
* POST / → { code, input, limits? } → QuickJS-wasm 沙箱 → { success, data } | { success:false, error, error_type }
* POST / → { code, input?, limits? }
* → QuickJS-wasm 沙箱(./sandbox.mjs 的 runCode
* → { success:true, data } | { success:false, error, error_type }
*
* 與其他 logic 零件的差異:
* - 其他零件 = 一顆「arcrun 自建 TinyGo→wasm」,靜態 bundle 進 Worker[[wasm_modules]]),
* 跑在 component-worker-template 的 WASI-preview1 shim 上。
* - `code` 零件 = 載入「QuickJSJS 直譯器)編成的 wasm」,把 user 的 inline JS 當「資料」
* 餵進去跑。QuickJS 的 wasm 由 quickjs-emscripten 提供(有 Cloudflare Workers 相容 variant)。
* 封裝=Aquickjs-emscripten singlefile variant):wasm 內嵌為 base64、同步載入,
* bundler 友善、無需 [[wasm_modules]] 綁定、無需 nodejs_compatsandbox 用 TextEncoder 計 bytes)。
*
* ⚠️ 此檔為設計骨架。PoC 的可執行 + 受測實作在 ./sandbox.mjsNode/vitest 綠燈)。
* live 化差異見 DESIGN.md「② 生產路徑」與「curated builtinssha256)」
* 與其他 logic 零件不同:`code` 是自足 Worker(自帶 index.ts + sandbox.mjs + quickjs variant),
* 不走 component-worker-template 的 TinyGo-wasm bundling 流程。部署見 DEPLOY.md
*/
import { Hono } from 'hono';
import { cors } from 'hono/cors';
import { getQuickJS } from 'quickjs-emscripten';
// @ts-expect-error —— sandbox.mjs 為 runtime-agnostic JS 核心(Node 測試與 Worker 共用同一份)
import { runCode } from './sandbox.mjs';
const app = new Hono();
app.use('*', cors());
app.get('/', (c) => c.json({ ok: true, component: 'code' }));
app.post('/', async (c) => {
@@ -28,20 +29,22 @@ app.post('/', async (c) => {
} catch {
return c.json({ success: false, error: 'request body must be JSON', error_type: 'ContractError' }, 400);
}
// runCode 語義與簽章同 ./sandbox.mjsPoC 已受測)。生產版把 sha256 curated builtin
// 換成「純 JS SHA-256 prelude」(無 host call、Node/Worker 皆決定性),見 DESIGN.md。
const result = await runCodeInWorker(String(body.code ?? ''), body.input, body.limits);
return c.json(result);
if (typeof body.code !== 'string') {
return c.json({ success: false, error: 'code (string) is required', error_type: 'ContractError' }, 400);
}
try {
const result = await runCode(body.code, body.input, { limits: body.limits });
// sandbox 永遠回結構化 envelopesuccess=false 仍以 200 帶 error_type 回(零件語義層錯,非 HTTP 錯)
return c.json(result);
} catch (e) {
// 理論上 runCode 自己 try/catch;這層是最後保險,Worker 絕不掛。
return c.json(
{ success: false, error: e instanceof Error ? e.message : String(e), error_type: 'SandboxError' },
500,
);
}
});
export default app;
// 生產實作(待補:把 sandbox.mjs 的 runCode 移植成 Worker 版)。
declare function runCodeInWorker(
code: string,
input: unknown,
limits?: Record<string, number>,
): Promise<unknown>;
// 保留 import 以標示相依(bundler 不 tree-shake 掉):
void getQuickJS;