2707fca32b
Phase 1-5 complete per .agents/specs/u6u-core-mvp/: **Phase 1 — Cherry-pick & cleanup** - Create arcrun/ from cypher-executor, credentials, builtins, registry - Remove 9 InkStone Service Bindings (KBDB, REGISTRY, CLINIC_*, AICEO, MINI_ME) - Rewrite component-loader: 3-layer (builtin → WASM_BUCKET R2 → error) - Remove autoPublishMissing.ts, proxy.ts (AICEO), execution-logger.ts (KBDB) - Clean all KV namespace IDs and InkStone internal URLs from config files **Phase 2 — contract.yaml completeness** - Add credentials_required to gmail, google_sheets, telegram, line_notify - Add config_example to all 21 components with annotated field descriptions **Phase 3 — Credential injection** - Add credential-injector.ts: AES-GCM decrypt from CREDENTIALS_KV - Integrate into GraphExecutor before WASM execution - Structured errors with repair instructions when credential missing **Phase 4 — CLI (acr)** - cli/package.json: arcrun package, bin: acr, deps: commander/js-yaml/chalk/ora - 8 commands: init, creds push, push, run, validate, parts, list, logs - Standard mode: writes directly to user's CF KV via CF REST API - acr init: interactive setup with arcrun.dev API Key registration **Phase 5 — Open source release prep** - README.md: 5-minute quickstart, component table, workflow YAML syntax - CONTRIBUTING.md: TinyGo dev env, component scaffolding, submission flow - Security audit: no InkStone internal URLs/IDs in committed files - .gitignore: exclude credentials.yaml, .wrangler, *.wasm https://claude.ai/code/session_01BnCdSLVH8tUed9VrrPavgT
97 lines
3.7 KiB
TypeScript
97 lines
3.7 KiB
TypeScript
// 沙盒驗收流程:五個步驟依序執行
|
||
// Requirements: 2.1, 2.2, 2.3
|
||
|
||
import { FORBIDDEN_SYSCALLS } from '../types';
|
||
import type { ComponentContract, SandboxResult, SandboxStep } from '../types';
|
||
|
||
// ── 步驟 (a):體積檢查 ────────────────────────────────────────────────────────
|
||
|
||
function checkSize(wasmBytes: Uint8Array, contract: ComponentContract): string | null {
|
||
const maxSizeKb = contract.constraints.max_size_kb;
|
||
const actualKb = wasmBytes.byteLength / 1024;
|
||
if (actualKb > maxSizeKb) {
|
||
return `體積 ${actualKb.toFixed(1)}KB 超過上限 ${maxSizeKb}KB`;
|
||
}
|
||
return null;
|
||
}
|
||
|
||
// ── 步驟 (b):冷啟動時間(Phase 0 mock 0ms)────────────────────────────────────
|
||
|
||
function checkColdStart(_wasmBytes: Uint8Array, _contract: ComponentContract): string | null {
|
||
// Phase 0:mock 通過,記錄 0ms
|
||
// Phase 2 再實作真實測量
|
||
return null;
|
||
}
|
||
|
||
// ── 步驟 (c):syscall 掃描 ────────────────────────────────────────────────────
|
||
|
||
function scanSyscalls(wasmBytes: Uint8Array): string | null {
|
||
// 將 .wasm binary 轉為文字,搜尋禁止的 import 字串
|
||
// WASM binary 中 import section 的函數名稱以 UTF-8 字串形式存在
|
||
const text = new TextDecoder('utf-8', { fatal: false }).decode(wasmBytes);
|
||
|
||
for (const syscall of FORBIDDEN_SYSCALLS) {
|
||
if (text.includes(syscall)) {
|
||
return `發現禁止的 syscall:${syscall}`;
|
||
}
|
||
}
|
||
return null;
|
||
}
|
||
|
||
// ── 步驟 (d):Gherkin 測試(Phase 0 mock 通過)────────────────────────────────
|
||
|
||
function runGherkinTests(_wasmBytes: Uint8Array, _contract: ComponentContract): string | null {
|
||
// Phase 0:mock 通過
|
||
// Phase 1 再實作真實 Gherkin 執行
|
||
return null;
|
||
}
|
||
|
||
// ── 步驟 (e):runtime 相容測試(Phase 0 mock 通過)────────────────────────────
|
||
|
||
function checkRuntimeCompat(_wasmBytes: Uint8Array, _contract: ComponentContract): string | null {
|
||
// Phase 0:mock 通過
|
||
// Phase 2 再實作真實多 runtime 測試
|
||
return null;
|
||
}
|
||
|
||
// ── 主流程 ────────────────────────────────────────────────────────────────────
|
||
|
||
interface StepDef {
|
||
name: SandboxStep;
|
||
run: (wasmBytes: Uint8Array, contract: ComponentContract) => string | null;
|
||
guideAnchor: string;
|
||
}
|
||
|
||
const STEPS: StepDef[] = [
|
||
{ name: 'size_check', run: checkSize, guideAnchor: '#common-errors' },
|
||
{ name: 'cold_start', run: checkColdStart, guideAnchor: '#common-errors' },
|
||
{ name: 'syscall_scan', run: scanSyscalls, guideAnchor: '#syscall-constraints' },
|
||
{ name: 'gherkin_tests', run: runGherkinTests, guideAnchor: '#local-testing' },
|
||
{ name: 'runtime_compat', run: checkRuntimeCompat, guideAnchor: '#contract-example' },
|
||
];
|
||
|
||
export function runSandboxAcceptance(
|
||
wasmBytes: Uint8Array,
|
||
contract: ComponentContract,
|
||
): SandboxResult {
|
||
for (const step of STEPS) {
|
||
const error = step.run(wasmBytes, contract);
|
||
if (error !== null) {
|
||
return {
|
||
success: false,
|
||
failed_step: step.name,
|
||
reason: error,
|
||
guide_anchor: step.guideAnchor,
|
||
component_id: contract.canonical_id,
|
||
version: contract.version,
|
||
};
|
||
}
|
||
}
|
||
|
||
return {
|
||
success: true,
|
||
component_id: contract.canonical_id,
|
||
version: contract.version,
|
||
};
|
||
}
|