// 零件提交:沙盒驗收 → 派發 hash id → 寫入 SUBMISSIONS_KV → 上傳 R2 // Requirements: 2.1, 2.2, 2.3 // // KV key 設計: // comp:{hash_id}:{version} → 零件元數據 JSON // idx:{canonical_id} → hash_id 反查索引(canonical_id → hash_id) // // hash_id 派發規則: // hash_id = 'cmp_' + sha256(canonical_id).slice(0, 8) // 相同 canonical_id 永遠得到相同 hash_id(冪等) // 不同 canonical_id 的 hash_id 碰撞機率極低(2^32 空間) import { runSandboxAcceptance } from './sandboxAcceptance'; import type { ComponentContract, SandboxResult, Bindings } from '../types'; // ── hash id 生成 ───────────────────────────────────────────────────────────── async function deriveHashId(canonicalId: string): Promise { const encoder = new TextEncoder(); const data = encoder.encode(canonicalId); const hashBuffer = await crypto.subtle.digest('SHA-256', data); const hashArray = Array.from(new Uint8Array(hashBuffer)); const hex = hashArray.map(b => b.toString(16).padStart(2, '0')).join(''); return 'cmp_' + hex.slice(0, 8); } // ── 主流程 ──────────────────────────────────────────────────────────────────── export async function submitComponent( wasmBytes: Uint8Array, contract: ComponentContract, env: Bindings, ): Promise { // 1. 沙盒驗收 const sandboxResult = runSandboxAcceptance(wasmBytes, contract); if (!sandboxResult.success) { return sandboxResult; } // 2. 派發 hash id(canonical_id 的確定性 hash,相同輸入永遠得到相同 id) const hashId = await deriveHashId(contract.canonical_id); const kvKey = `comp:${hashId}:${contract.version}`; const r2Key = `components/${hashId}/${contract.version}.wasm`; // 3. 冪等:若已存在相同 (hash_id, version) 直接回傳 const existing = await env.SUBMISSIONS_KV.get(kvKey); if (existing) { return { success: true, component_hash_id: hashId, canonical_id: contract.canonical_id, version: contract.version, wasm_r2_key: r2Key, }; } // 4. 上傳 .wasm 至 R2 await env.WASM_BUCKET.put(r2Key, wasmBytes, { httpMetadata: { contentType: 'application/wasm' }, }); // 5. 寫入 SUBMISSIONS_KV(元數據 + 初始統計) const record = { component_hash_id: hashId, canonical_id: contract.canonical_id, display_name: contract.display_name, category: contract.category, version: contract.version, wasi_target: contract.wasi_target, stability: contract.stability, runtime_compat: contract.runtime_compat, component_type: contract.component_type ?? 'wasm', 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 ?? '', aliases: contract.aliases ?? [], 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, }; await env.SUBMISSIONS_KV.put(kvKey, JSON.stringify(record)); // 6. 寫入 canonical_id → hash_id 反查索引 // 同一個 canonical_id 的所有版本共用同一個 hash_id,索引只需存一份 await env.SUBMISSIONS_KV.put(`idx:${contract.canonical_id}`, hashId); return { success: true, component_hash_id: hashId, canonical_id: contract.canonical_id, version: contract.version, wasm_r2_key: r2Key, }; }