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:
@@ -1,5 +1,9 @@
|
||||
// 零件提交:沙盒驗收 → 寫入 KBDB Block → 上傳 R2
|
||||
// 零件提交:沙盒驗收 → 寫入 SUBMISSIONS_KV → 上傳 R2
|
||||
// Requirements: 2.1, 2.2, 2.3
|
||||
//
|
||||
// arcrun registry 不依賴 KBDB(InkStone 內部服務)。
|
||||
// 零件元數據存入 SUBMISSIONS_KV(key = comp:{canonical_id}:{version})。
|
||||
// WASM 二進位存入 WASM_BUCKET R2(key = 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,
|
||||
|
||||
Reference in New Issue
Block a user