fix(arcrun): address PR #2 review findings

Security:
- init.ts: remove cf_api_token from POST /register (only email sent to arcrun.dev)
- cf-api.ts: remove base64 fallback in encryptCredential, throw clear error if key missing

Correctness:
- submitComponent.ts: replace KBDB dependency with SUBMISSIONS_KV + R2 (standalone)
- registry/types.ts: remove KBDB_URL/KBDB_INTERNAL_TOKEN, add SUBMISSIONS_KV/ANALYTICS_KV
- webhooks.ts: add waitUntil(writeExecutionVerdict) for fire-and-forget analytics
- execution-logger.ts: create missing module (was imported but didn't exist)
- cypher-executor/types.ts + wrangler.toml: add ANALYTICS_KV binding
- gmail/telegram/google_sheets/line_notify/http_request: no_network_syscall false (api category)
- init.ts: replace require() with await import() for ES module compatibility

Cleanup:
- Remove arcrun/builtins/ (dead code — initComponents used old HTTP endpoint model,
  all 21 components now in TinyGo WASM under registry/components/)

Docs:
- tasks.md: update to reflect completed work and remaining items

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-16 13:07:28 +08:00
parent 2707fca32b
commit e630fca2df
20 changed files with 123 additions and 392 deletions
+37 -49
View File
@@ -1,5 +1,9 @@
// 零件提交:沙盒驗收 → 寫入 KBDB Block → 上傳 R2
// 零件提交:沙盒驗收 → 寫入 SUBMISSIONS_KV → 上傳 R2
// Requirements: 2.1, 2.2, 2.3
//
// arcrun registry 不依賴 KBDBInkStone 內部服務)。
// 零件元數據存入 SUBMISSIONS_KVkey = comp:{canonical_id}:{version})。
// WASM 二進位存入 WASM_BUCKET R2key = components/{id}/{version}.wasm)。
import { runSandboxAcceptance } from './sandboxAcceptance';
import type { ComponentContract, SandboxResult, Bindings } from '../types';
@@ -15,71 +19,55 @@ export async function submitComponent(
return sandboxResult;
}
const blockId = `comp-${contract.canonical_id}-${contract.version}`;
const kvKey = `comp:${contract.canonical_id}:${contract.version}`;
const r2Key = `components/${contract.canonical_id}/${contract.version}.wasm`;
// 2. 上傳 .wasm 至 R2
// 2. 冪等:若已存在相同 (id, version) 直接回傳
const existing = await env.SUBMISSIONS_KV.get(kvKey);
if (existing) {
return {
success: true,
component_id: contract.canonical_id,
version: contract.version,
wasm_r2_key: r2Key,
};
}
// 3. 上傳 .wasm 至 R2
await env.WASM_BUCKET.put(r2Key, wasmBytes, {
httpMetadata: { contentType: 'application/wasm' },
});
// 3. 寫入 KBDB Block(冪等:先嘗試取得,存在則更新,不存在則建立
const kbdbUrl = env.KBDB_URL || 'https://kbdb.finally.click';
const headers = {
'Content-Type': 'application/json',
'Authorization': `Bearer ${env.KBDB_INTERNAL_TOKEN}`,
};
const slots: Record<string, string> = {
// 4. 寫入 SUBMISSIONS_KV(元數據 + 初始統計
const record = {
canonical_id: contract.canonical_id,
display_name: contract.display_name,
category: contract.category,
version: contract.version,
author: contract.author ?? '',
wasi_target: contract.wasi_target,
stability: contract.stability,
runtime_compat: JSON.stringify(contract.runtime_compat),
runtime_compat: contract.runtime_compat,
component_type: contract.component_type ?? 'wasm',
max_size_kb: String(contract.constraints.max_size_kb),
max_cold_start_ms: String(contract.constraints.max_cold_start_ms),
no_network_syscall: String(contract.constraints.no_network_syscall),
input_schema: JSON.stringify(contract.input_schema),
output_schema: JSON.stringify(contract.output_schema),
gherkin_tests: JSON.stringify(contract.gherkin_tests),
constraints: contract.constraints,
input_schema: contract.input_schema,
output_schema: contract.output_schema,
gherkin_tests: contract.gherkin_tests,
wasm_r2_key: r2Key,
description: contract.description ?? '',
tags: JSON.stringify(contract.tags ?? []),
success_rate: '1',
avg_duration_ms: '0',
call_count: '0',
status: 'active',
deprecated_at: '',
tags: contract.tags ?? [],
// 初始統計
success_rate: 1,
avg_duration_ms: 0,
call_count: 0,
// 可見性:預設 author_only,人工審核通過後改為 public
visibility: 'author_only' as const,
status: 'active' as const,
submitted_at: new Date().toISOString(),
deprecated_at: null,
};
if (contract.cypher_binding_url) slots.cypher_binding_url = contract.cypher_binding_url;
if (contract.service_binding_key) slots.service_binding_key = contract.service_binding_key;
// 冪等:先查是否存在
const existRes = await fetch(`${kbdbUrl}/records/${blockId}`, { headers });
if (existRes.ok) {
// 已存在:更新 slots
await fetch(`${kbdbUrl}/records/${blockId}`, {
method: 'PUT',
headers,
body: JSON.stringify({ values: slots }),
});
} else {
// 不存在:建立新 Block
await fetch(`${kbdbUrl}/records`, {
method: 'POST',
headers,
body: JSON.stringify({
record_id: blockId,
template_id: 'tpl-component',
values: slots,
}),
});
}
await env.SUBMISSIONS_KV.put(kvKey, JSON.stringify(record));
return {
success: true,