feat(registry): component_hash_id — stable id system for workflow references

Problem: canonical_id is readable but mutable; if a component is renamed,
all workflows referencing it by canonical_id break.

Solution: dual-id system
- component_hash_id: cmp_{sha256(canonical_id).slice(0,8)}, derived deterministically,
  never changes, safe for workflow references
- canonical_id: human-readable name, used for search and display
- idx:{canonical_id} KV key: reverse-lookup index for resolving canonical_id → hash_id

Changes:
- types.ts: SandboxResult.component_id → component_hash_id + canonical_id,
  added 'data' to category enum
- submitComponent.ts: deriveHashId(), writes idx: reverse-lookup on submit
- queryComponents.ts: full rewrite — removed KBDB dependency, uses SUBMISSIONS_KV;
  supports both cmp_* and canonical_id as query id; Phase 0 keyword search
  with note to upgrade to Vectorize in Phase 2
- sandboxAcceptance.ts: updated field names, fixed TextDecoder TS type
- ensureTemplate.ts: removed KBDB dependency, now a KV health check
- tests: updated component_id → canonical_id
- CONTRIBUTING.md: explain hash_id derivation and dual-id workflow reference syntax

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-16 14:41:22 +08:00
parent d8028eabe0
commit 8e2c32e466
7 changed files with 177 additions and 178 deletions
+12 -4
View File
@@ -7,8 +7,11 @@ import { z } from 'zod';
export type Bindings = {
WASM_BUCKET: R2Bucket;
AI: Ai;
SUBMISSIONS_KV: KVNamespace; // 零件元數據 + 可見性狀態(key = comp:{id}:{version}
ANALYTICS_KV: KVNamespace; // 執行統計匯總(key = stats:{id}:{version}
// KV key 格式:
// comp:{hash_id}:{version} → 零件元數據(hash_id = cmp_ + sha256 前 8 碼
// idx:{canonical_id} → canonical_id → hash_id 反查索引
SUBMISSIONS_KV: KVNamespace;
ANALYTICS_KV: KVNamespace; // 執行統計匯總(key = stats:{hash_id}:{version}
ENVIRONMENT: string;
};
@@ -28,9 +31,12 @@ export const GherkinTestSchema = z.object({
});
export const ComponentContractSchema = z.object({
// canonical_id:提交者填寫的可讀名稱(小寫底線),用於搜尋與 workflow 引用
// component_hash_id:由 Registry 在提交時派發,格式 cmp_{8碼hex}workflow 引用此 id 才能保證永久不壞
// 兩者都可以在 workflow 中引用,Registry 會互相解析
canonical_id: z.string().min(1).regex(/^[a-z][a-z0-9_]*$/, 'canonical_id 必須為小寫底線格式'),
display_name: z.string().min(1),
category: z.enum(['logic', 'api', 'ui', 'style', 'anim']),
category: z.enum(['logic', 'api', 'ui', 'style', 'anim', 'data']),
version: z.string().min(1).regex(/^v\d+$/, 'version 格式必須為 vN'),
wasi_target: z.literal('preview1'),
stability: z.enum(['floating', 'stable', 'pinned']),
@@ -64,7 +70,9 @@ export interface SandboxResult {
failed_step?: SandboxStep;
reason?: string;
guide_anchor?: string;
component_id: string;
// 驗收通過後回傳兩個 id
component_hash_id: string; // cmp_{8碼hex}workflow 引用用,永久不變
canonical_id: string; // 可讀名稱,搜尋用
version: string;
}