922a57fe34
Self-hosted 開源:WASM 零件 + recipe + cypher-executor,跑在你自己的 Cloudflare。 此為重建的乾淨歷史起點(移除曾誤 commit 的 GCP SA 金鑰,舊歷史保留在 richblack/arcrun 與本地 backup 分支)。含: - acr init --self-hosted installer(建 KV/R2 + codeload 拉預編譯 wasm + wrangler deploy + seed recipe) - recipe push 把關(資料外流提醒 + 打通檢查) - 19 個正當零件預編譯 wasm(claude_api/km_writer/kbdb_upsert_block 排除:違反 DECISIONS §1) - CLI / cypher-executor / registry / 完整 SDD Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
823 lines
31 KiB
Markdown
823 lines
31 KiB
Markdown
# Design Document: u6u Platform Evolution
|
||
|
||
## Overview
|
||
|
||
u6u 平台演進的核心目標是將現有的「HTTP endpoint 零件 + 單一 Cloudflare 部署」架構,演進為「WASM 零件模型 + 三層物理部署 + 雙面翻轉畫布」的完整平台。
|
||
|
||
設計的最高原則是 **Dogfooding**:每一層都是下一層的第一個用戶。底層先建立最小可運行的能力,再用自己的方式往上蓋。這確保每個設計決策都被真實使用場景驗證,而非紙上談兵。
|
||
|
||
### Bootstrap 順序(不可跳過)
|
||
|
||
```
|
||
Phase 0:最小 WASM 執行核心
|
||
→ Component Dispatcher 能在 CF Workers 執行一個 .wasm(stdin/stdout JSON)
|
||
→ validate_json.wasm 作為第一個真實零件(TinyGo,< 50KB,驗證整個 pipeline)
|
||
→ Component Registry API(/guide、/validate-contract、/components)
|
||
|
||
Phase 1:遷移現有零件
|
||
→ 將 u6u-builtins 的 20 個 HTTP endpoint 逐一遷移為 .wasm
|
||
→ 遷移期間 Component Dispatcher 雙模式並存(HTTP fallback)
|
||
→ 每個零件附帶 component.contract.yaml
|
||
|
||
Phase 2:Cypher 語意擴展 + Multi-Tier Dispatcher
|
||
→ 支援 IS_A、ON_SUCCESS、ON_FAIL、CALLS_SUBFLOW、ON_CLICK
|
||
→ Component Dispatcher 路由層(Tier 1 CF / Tier 2 workerd / Tier 3 Wazero)
|
||
|
||
Phase 3:前端畫布(用自己的 Web Components 開發)
|
||
→ 先建立 Web Components 零件庫(u6u-btn、u6u-card 等)
|
||
→ 畫布本身用這些 Web Components 組裝
|
||
→ 雙面翻轉介面
|
||
```
|
||
|
||
### 關鍵設計約束
|
||
|
||
- **KBDB 不變量**:永遠只有三張表(blocks / templates / slots),不新增表
|
||
- **API-First 鐵律**:所有跨服務通訊只透過 HTTP API,禁止相對路徑引用
|
||
- **零件 I/O 不變量**:唯一合法的 I/O 模型是 `stdin_stdout_json`
|
||
- **Tier 3 約束**:無 V8、無 Node.js、無網路,所有零件必須在 Wazero 上跑
|
||
|
||
---
|
||
|
||
## Architecture
|
||
|
||
### 系統全景圖
|
||
|
||
```mermaid
|
||
graph TB
|
||
subgraph "Tier 1 — Cloudflare Workers(雲端)"
|
||
CE[Cypher Executor<br/>GraphExecutor]
|
||
CD[Component Dispatcher<br/>路由層]
|
||
CR[Component Registry<br/>KBDB HTTP API]
|
||
KBDB[(KBDB<br/>blocks/templates/slots<br/>+ Vectorize)]
|
||
R2[(R2<br/>.wasm 二進位)]
|
||
CE --> CD
|
||
CD --> CR
|
||
CR --> KBDB
|
||
CR --> R2
|
||
end
|
||
|
||
subgraph "Tier 2 — workerd self-hosted(企業地端)"
|
||
T2D[Tier 2 Dispatcher<br/>同 wasi-shim,不同部署]
|
||
T2R[本地 Registry 快取]
|
||
T2D --> T2R
|
||
end
|
||
|
||
subgraph "Tier 3 — 邊緣載具"
|
||
T3E[Go 排程引擎]
|
||
Wazero[Wazero Runtime]
|
||
SQLite[(SQLite<br/>本地 KBDB)]
|
||
DTN[DTN 佇列]
|
||
T3E --> Wazero
|
||
T3E --> SQLite
|
||
T3E --> DTN
|
||
end
|
||
|
||
subgraph "前端"
|
||
Canvas[雙面翻轉畫布<br/>React 19 + Web Components]
|
||
WC[Web Components 零件庫<br/>u6u-btn / u6u-card / ...]
|
||
Canvas --> WC
|
||
end
|
||
|
||
CD -->|WASM 執行| Tier1WASM[.wasm 執行]
|
||
CD -->|Cypher binding| ExtSvc[外部服務<br/>MCP / n8n / 任意 URL]
|
||
CD -->|HTTP| T2D
|
||
T2D -->|Wazero IPC| Wazero
|
||
DTN -->|Burst 傳輸| T2D
|
||
Canvas -->|u6u:trigger event| CE
|
||
```
|
||
|
||
### Cypher Binding 的正確定義
|
||
|
||
**Cypher binding** 是 u6u 的核心執行機制,指「用 Cypher 三元組語法把零件串接成工作流,串接關係儲存在 KBDB,不寫死在程式碼裡」。
|
||
|
||
這個概念相對於 Cloudflare Workers 原生的 **Service Binding**(需要 deploy、串接關係寫死在 wrangler.toml)。
|
||
|
||
`cypher-executor` 就是執行 Cypher binding 的引擎。
|
||
|
||
**零件本身只有兩種 component_type:**
|
||
|
||
| component_type | 說明 | 需要 deploy? |
|
||
|---|---|---|
|
||
| `wasm` | 所有後端零件(內建或用戶自建),本地 WASM 執行 | 否 |
|
||
| `service_binding` | 多個零件預組合成單一高頻零件的效能最佳化(如 OAuth + GSheets 常用組合) | 是 |
|
||
|
||
> **重要:`cypher_binding` 不是 component_type。** 它是整個執行引擎的名字,描述「零件如何被串接」,而不是「零件如何被執行」。所有零件(不管是內建還是用戶自建、不管是打外部 API 還是純邏輯)都是 `.wasm`,透過 Cypher 三元組串接。
|
||
|
||
> **所有後端零件都是 `.wasm`。** 需要呼叫外部 HTTP API 的零件(如 google-sheets、http-request),透過 WASI shim 注入的 **host function** 發出網路請求,不在 .wasm 內部直接呼叫網路 syscall。
|
||
|
||
### Component Dispatcher 路由決策樹
|
||
|
||
```mermaid
|
||
flowchart TD
|
||
A[Cypher Executor 呼叫零件 id] --> B{查 Component Registry<br/>取得合約}
|
||
B --> C{component_type?}
|
||
C -->|wasm| E{當前 Tier?}
|
||
C -->|service_binding| SB{有 CF Service Binding?}
|
||
SB -->|是| SBExec[CF Service Binding 執行<br/>需 deploy,效能最佳]
|
||
SB -->|否| SBErr[回傳錯誤:binding 未宣告]
|
||
E -->|Tier 1 / Tier 2| I[workerd WASM<br/>WebAssembly.instantiate<br/>+ WASI shim(兩者相同)]
|
||
E -->|Tier 3| L[Wazero IPC<br/>stdin/stdout,完全離線]
|
||
I --> RC{runtime_compat<br/>包含 cf-workers?}
|
||
RC -->|否| J[回傳 RUNTIME_INCOMPATIBLE 錯誤]
|
||
RC -->|是| Exec[執行 .wasm]
|
||
```
|
||
|
||
### KBDB 資料模型(tpl-component)
|
||
|
||
```mermaid
|
||
erDiagram
|
||
TEMPLATES {
|
||
string template_id "tpl-component"
|
||
string name
|
||
string description
|
||
}
|
||
BLOCKS {
|
||
string block_id "comp-{id}-{version}"
|
||
string template_id
|
||
string user_id
|
||
string page_name
|
||
}
|
||
SLOTS {
|
||
string slot_id
|
||
string block_id
|
||
string key
|
||
string value
|
||
}
|
||
TEMPLATES ||--o{ BLOCKS : "defines"
|
||
BLOCKS ||--o{ SLOTS : "has"
|
||
```
|
||
|
||
---
|
||
|
||
## Components and Interfaces
|
||
|
||
### 1. Component Registry(`u6u-core/registry/`)
|
||
|
||
Component Registry 是 KBDB 的薄包裝層,透過 HTTP API 管理零件合約。
|
||
|
||
#### 零件命名機制
|
||
|
||
零件有兩個名稱,職責完全不同:
|
||
|
||
| 欄位 | 由誰決定 | 用途 | 範例 |
|
||
|---|---|---|---|
|
||
| `display_name` | 建立者自由取 | 顯示用,不影響任何邏輯 | `宇宙無敵 GSheets 超級寫入器` |
|
||
| `canonical_id` | Registry AI 正規化後確認 | 搜尋、版本控制、Cypher 引用的唯一鍵 | `gsheets_create_table` |
|
||
|
||
**canonical_id 正規化流程:**
|
||
|
||
```
|
||
提交者輸入 display_name
|
||
↓
|
||
Registry 用 Workers AI 建議 canonical_id
|
||
(格式:{service}_{verb}_{object},全小寫底線)
|
||
↓
|
||
同時搜尋 Vectorize,若相似度 > 0.9 的 canonical_id 已存在
|
||
→ 提示「可能與 gsheets_create_table 重複,是否作為新版本提交?」
|
||
↓
|
||
提交者確認或修改 canonical_id
|
||
↓
|
||
上架,canonical_id 永久不變
|
||
```
|
||
|
||
#### 零件分類機制
|
||
|
||
採用「強制 category + 自由 tags」雙層分類:
|
||
|
||
- **`category`**:強制填,有限集合,定義前後端邊界
|
||
- `logic`:後端邏輯零件(.wasm,純計算/轉換)
|
||
- `api`:後端 API 零件(.wasm + cypher_binding,呼叫外部服務)
|
||
- `ui`:前端 UI 元件(Web Component,瀏覽器執行)
|
||
- `style`:前端樣式零件(CSS tokens)
|
||
- `anim`:前端動畫零件
|
||
|
||
- **`tags`**:自由增加,跨零件共享語意
|
||
- 例:`gsheets_create_table` 有 `["google", "sheets", "spreadsheet", "storage", "write"]`
|
||
- 例:`excel_write_row` 有 `["microsoft", "excel", "spreadsheet", "storage", "write"]`
|
||
- 搜尋「外部存儲」時,兩個都能透過 Vectorize 語意搜尋找到
|
||
|
||
**HTTP 端點:**
|
||
|
||
```
|
||
GET /components/guide → 機器可讀開發指引(Markdown)
|
||
POST /components/validate-contract → 驗證 component.contract.yaml 格式
|
||
POST /components → 提交零件(.wasm + contract)觸發沙盒驗收
|
||
GET /components/:id → 取得零件合約(最優版本)
|
||
GET /components/:id/versions → 取得所有版本清單(含評分)
|
||
GET /components/search?q=... → 語意搜尋零件
|
||
```
|
||
|
||
**KBDB 整合:**
|
||
- 每個零件版本 = 一個 Block,`block_id = comp-{id}-{version}`
|
||
- Template = `tpl-component`(預先建立,不新增表)
|
||
- `.wasm` 二進位存 R2,KBDB slot 只存 `wasm_r2_key`
|
||
- `description` + `tags` 欄位寫入 Vectorize 索引,支援語意搜尋
|
||
|
||
**Slot 欄位對應(tpl-component):**
|
||
|
||
| Slot key | 說明 | 範例值 |
|
||
|---|---|---|
|
||
| `canonical_id` | 正規化功能名稱(永久不變,搜尋/版本控制用) | `gsheets_create_table` |
|
||
| `display_name` | 建立者自取的顯示名稱 | `宇宙無敵 GSheets 超級寫入器` |
|
||
| `category` | 零件分類(有限集合) | `logic` / `api` / `ui` / `style` / `anim` |
|
||
| `version` | 實作版本 | `v1` |
|
||
| `wasi_target` | WASM 目標 | `preview1` |
|
||
| `stability` | 穩定性標籤 | `floating` |
|
||
| `runtime_compat` | 相容 runtime(JSON 陣列) | `["cf-workers","wazero"]` |
|
||
| `component_type` | 零件類型 | `wasm` / `service_binding` |
|
||
| `max_size_kb` | 體積上限 | `2048` |
|
||
| `max_cold_start_ms` | 冷啟動上限 | `50` |
|
||
| `no_network_syscall` | 禁止網路 syscall | `true` |
|
||
| `input_schema` | JSON Schema(JSON 字串) | `{"type":"object",...}` |
|
||
| `output_schema` | JSON Schema(JSON 字串) | `{"type":"object",...}` |
|
||
| `gherkin_tests` | 測試案例(JSON 字串) | `[{"scenario":"..."}]` |
|
||
| `wasm_r2_key` | R2 物件鍵(wasm 模式) | `components/validate_json/v1.wasm` |
|
||
| `service_binding_key` | CF binding key(service_binding 模式) | `CLINIC_GSHEETS` |
|
||
| `description` | 自然語言描述(寫入 Vectorize) | `在 Google Sheets 建立新工作表` |
|
||
| `tags` | 自由標籤(JSON 陣列,跨零件共享語意) | `["google","sheets","storage","write"]` |
|
||
| `success_rate` | 成功率(0-1) | `0.98` |
|
||
| `avg_duration_ms` | 平均執行時間 | `12` |
|
||
| `call_count` | 被調用次數 | `1024` |
|
||
| `status` | 狀態 | `active` / `deprecated` / `tombstone` |
|
||
| `deprecated_at` | 棄用時間戳記 | `1700000000000` |
|
||
|
||
### 2. Component Dispatcher(`cypher-executor/src/lib/component-loader.ts` 擴展)
|
||
|
||
Component Dispatcher 是 `createComponentLoader` 的升級版,新增 WASM 執行路徑。
|
||
|
||
**介面定義:**
|
||
|
||
```typescript
|
||
// 零件類型(只有兩種)
|
||
type ComponentType =
|
||
| 'wasm' // 所有後端零件,透過 Cypher binding 串接,本地 WASM 執行
|
||
| 'service_binding'; // 效能最佳化:CF Service Binding,需 deploy,用於高頻預組合零件
|
||
|
||
// 新版 ComponentDescriptor
|
||
type ComponentDescriptor = {
|
||
component_type: ComponentType;
|
||
// WASM 模式
|
||
wasm_r2_key?: string;
|
||
runtime_compat?: string[];
|
||
max_cold_start_ms?: number;
|
||
// Service Binding 模式(CF Worker 間高效呼叫,需 deploy)
|
||
binding?: string; // wrangler.toml 中宣告的 binding key
|
||
path?: string;
|
||
};
|
||
```
|
||
|
||
**Tier 1 WASM 執行(CF Workers 原生):**
|
||
|
||
Cloudflare Workers 原生支援 `WebAssembly.instantiate`,但 WASI preview1 需要手動實作 WASI imports。設計採用輕量 WASI shim 方案:
|
||
|
||
```typescript
|
||
// WASI preview1 shim(只實作 stdin/stdout/stderr,其餘 syscall 回傳 ENOSYS)
|
||
function createWasiImports(stdin: Uint8Array): {
|
||
imports: WebAssembly.Imports;
|
||
getStdout: () => Uint8Array;
|
||
} {
|
||
const stdoutChunks: Uint8Array[] = [];
|
||
let stdinOffset = 0;
|
||
|
||
return {
|
||
imports: {
|
||
wasi_snapshot_preview1: {
|
||
fd_write: (fd: number, iovs: number, iovs_len: number, nwritten: number) => { /* ... */ },
|
||
fd_read: (fd: number, iovs: number, iovs_len: number, nread: number) => { /* ... */ },
|
||
proc_exit: (code: number) => { throw new Error(`wasm exit: ${code}`); },
|
||
// 其餘 syscall 回傳 ENOSYS(76)
|
||
fd_seek: () => 76,
|
||
fd_close: () => 0,
|
||
environ_get: () => 0,
|
||
environ_sizes_get: () => 0,
|
||
args_get: () => 0,
|
||
args_sizes_get: () => 0,
|
||
clock_time_get: () => 0,
|
||
random_get: (buf: number, buf_len: number) => { /* crypto.getRandomValues */ return 0; },
|
||
},
|
||
},
|
||
getStdout: () => { /* 合併 stdoutChunks */ },
|
||
};
|
||
}
|
||
```
|
||
|
||
> **設計決策**:不使用 `@cloudflare/workers-wasi`(已停止維護)。改用自製輕量 WASI shim,只實作 `fd_read`/`fd_write`/`proc_exit`/`random_get`,其餘 syscall 回傳 `ENOSYS`。這足以支援 TinyGo/Rust/AssemblyScript 的 stdin/stdout 零件,且不引入外部依賴。
|
||
|
||
**執行流程:**
|
||
|
||
```
|
||
1. 從 R2 取得 .wasm 二進位(ArrayBuffer)
|
||
2. WebAssembly.compile(buffer) → WebAssembly.Module
|
||
3. 建立 WASI imports shim(注入 stdin = JSON.stringify(input))
|
||
4. WebAssembly.instantiate(module, imports)
|
||
5. 呼叫 _start() 或 main()
|
||
6. 從 stdout buffer 讀取輸出
|
||
7. JSON.parse(stdout) → output
|
||
```
|
||
|
||
**R2 快取策略:**
|
||
- 第一次呼叫:從 R2 fetch `.wasm`,`WebAssembly.compile` 後快取 `WebAssembly.Module`(Worker 記憶體,跨請求共享)
|
||
- 後續呼叫:直接用快取的 Module,只重新 instantiate(避免重複編譯)
|
||
|
||
### 3. Cypher Triplet Parser 擴展(`cypher-executor/src/actions/triplet-parser.ts`)
|
||
|
||
現有 parser 只支援 `PIPE / IF / FOREACH / CONTINUE`。需擴展支援新語意關係。
|
||
|
||
**新增 EdgeType:**
|
||
|
||
```typescript
|
||
export type EdgeType =
|
||
| 'PIPE' | 'IF' | 'FOREACH' | 'CONTINUE' // 現有
|
||
| 'IS_A' | 'ON_SUCCESS' | 'ON_FAIL' // 新增:執行語意
|
||
| 'ON_CLICK' | 'CALLS_SUBFLOW' // 新增:觸發語意
|
||
| 'CONTAINS' | 'HAS_STYLE' | 'HAS_BEHAVIOR'; // 新增:結構語意
|
||
```
|
||
|
||
**URI 協議解析:**
|
||
|
||
```typescript
|
||
// 節點 componentId 解析
|
||
function resolveComponentId(uri: string): {
|
||
type: 'component' | 'workflow' | 'ui' | 'style';
|
||
canonicalId: string;
|
||
stability: 'floating' | 'stable' | 'pinned';
|
||
pinnedVersion?: string;
|
||
} {
|
||
// component://validate_json@stable → { type: 'component', canonicalId: 'validate_json', stability: 'stable' }
|
||
// component://validate_json@pinned:v1 → { type: 'component', canonicalId: 'validate_json', stability: 'pinned', pinnedVersion: 'v1' }
|
||
// workflow://wf_save_to_db → { type: 'workflow', canonicalId: 'wf_save_to_db', stability: 'floating' }
|
||
// ui://u6u-btn → { type: 'ui', canonicalId: 'u6u-btn', stability: 'floating' }
|
||
}
|
||
```
|
||
|
||
**ON_SUCCESS / ON_FAIL 執行語意:**
|
||
|
||
GraphExecutor 需要區分「節點執行成功」vs「節點執行失敗」,而非依賴 context 欄位:
|
||
|
||
```typescript
|
||
// 在 executeNode 中,捕捉 try/catch 後分別走 ON_SUCCESS / ON_FAIL 邊
|
||
case 'ON_SUCCESS':
|
||
// 只在上游節點成功時執行
|
||
if (!nodeError) {
|
||
result = await this.executeNode(nextNode, ...);
|
||
}
|
||
break;
|
||
case 'ON_FAIL':
|
||
// 只在上游節點失敗時執行(接收 error context)
|
||
if (nodeError) {
|
||
result = await this.executeNode(nextNode, graph, { ...context, error: nodeError }, ...);
|
||
}
|
||
break;
|
||
```
|
||
|
||
**CALLS_SUBFLOW 執行語意:**
|
||
|
||
```typescript
|
||
case 'CALLS_SUBFLOW': {
|
||
// 從 KBDB 載入子 Workflow 定義
|
||
const subWorkflowId = nextNode.componentId!.replace('workflow://', '');
|
||
const subGraph = await loadWorkflowFromKBDB(subWorkflowId, env);
|
||
const subExecutor = new GraphExecutor(loader);
|
||
const subResult = await subExecutor.execute(subGraph, result as Record<string, unknown>, kvNamespace);
|
||
result = { ...(result as Record<string, unknown>), ...subResult.data as Record<string, unknown> };
|
||
break;
|
||
}
|
||
```
|
||
|
||
### 4. Web Components 零件庫(`u6u-core/web-components/`)
|
||
|
||
Web Components 以原生 Custom Elements API 實作,不依賴任何框架。
|
||
|
||
**`<u6u-btn>` 介面:**
|
||
|
||
```typescript
|
||
// HTML attributes
|
||
interface U6uBtnAttributes {
|
||
label: string; // 顯示文字
|
||
color?: string; // 主題色(CSS custom property)
|
||
tooltip?: string; // 滑鼠懸停提示(純靜態)
|
||
workflow?: string; // workflow://id
|
||
disabled?: boolean;
|
||
}
|
||
|
||
// 發出的自訂事件
|
||
interface U6uTriggerEvent extends CustomEvent {
|
||
detail: {
|
||
workflowId: string;
|
||
payload: Record<string, unknown>;
|
||
};
|
||
}
|
||
```
|
||
|
||
**`<u6u-card>` Smart Container 邏輯:**
|
||
|
||
```typescript
|
||
// u6u-card 攔截子元件的 u6u:trigger 事件
|
||
// 收集同容器內所有 u6u-text-input / u6u-text-field 的值
|
||
// 合併至 payload 後再向上冒泡
|
||
connectedCallback() {
|
||
this.addEventListener('u6u:trigger', (e: Event) => {
|
||
const trigger = e as CustomEvent;
|
||
e.stopPropagation();
|
||
|
||
const inputs = this.querySelectorAll('u6u-text-input, u6u-text-field');
|
||
const collected: Record<string, unknown> = {};
|
||
inputs.forEach(input => {
|
||
const name = input.getAttribute('name');
|
||
const value = (input as any).value;
|
||
if (name) collected[name] = value;
|
||
});
|
||
|
||
this.dispatchEvent(new CustomEvent('u6u:trigger', {
|
||
bubbles: true,
|
||
composed: true,
|
||
detail: {
|
||
...trigger.detail,
|
||
payload: { ...trigger.detail.payload, ...collected },
|
||
},
|
||
}));
|
||
});
|
||
}
|
||
```
|
||
|
||
### 5. 雙面翻轉畫布(`inkstone-admin/frontend/web/`)
|
||
|
||
畫布本身用 React 19 + Web Components 組裝,體現 dogfooding 原則。
|
||
|
||
**翻轉狀態機:**
|
||
|
||
```mermaid
|
||
stateDiagram-v2
|
||
[*] --> UIView: 初始狀態
|
||
UIView --> LogicView: 點擊翻面按鈕
|
||
LogicView --> UIView: 點擊翻面按鈕
|
||
LogicView --> Editing: 修改三元組
|
||
Editing --> Saving: 確認儲存
|
||
Saving --> LogicView: KBDB 寫入成功
|
||
Saving --> LogicView: KBDB 寫入失敗(顯示錯誤)
|
||
```
|
||
|
||
---
|
||
|
||
## Data Models
|
||
|
||
### Component Contract YAML(完整規格)
|
||
|
||
```yaml
|
||
canonical_id: "validate_json" # 正規化功能名稱(永久不變,Registry AI 正規化後確認)
|
||
display_name: "JSON 格式驗證器" # 建立者自取,顯示用
|
||
category: "logic" # logic | api | ui | style | anim
|
||
version: "v1" # 實作版本
|
||
wasi_target: "preview1" # WASM 目標格式
|
||
stability: "floating" # floating | stable | pinned
|
||
|
||
runtime_compat:
|
||
- "cf-workers"
|
||
- "workerd"
|
||
- "wazero"
|
||
|
||
constraints:
|
||
max_size_kb: 2048
|
||
max_cold_start_ms: 50
|
||
no_network_syscall: true
|
||
no_filesystem_syscall: true
|
||
io_model: "stdin_stdout_json" # 唯一合法值
|
||
|
||
input_schema:
|
||
type: object
|
||
required: ["json_string"]
|
||
properties:
|
||
json_string:
|
||
type: string
|
||
description: "待驗證的 JSON 字串"
|
||
|
||
output_schema:
|
||
type: object
|
||
properties:
|
||
valid:
|
||
type: boolean
|
||
error:
|
||
type: string
|
||
description: "驗證失敗時的錯誤訊息"
|
||
|
||
gherkin_tests:
|
||
- scenario: "合法 JSON 通過驗證"
|
||
given: '{"json_string":"{\"key\":\"value\"}"}'
|
||
then_contains: '{"valid":true}'
|
||
- scenario: "非法 JSON 回傳錯誤"
|
||
given: '{"json_string":"not-json"}'
|
||
then_contains: '{"valid":false,"error":'
|
||
|
||
tags: ["validation", "json", "utility"]
|
||
description: "驗證輸入字串是否為合法 JSON 格式"
|
||
```
|
||
|
||
### 零件開發語言決策
|
||
|
||
**內建零件使用 TinyGo**(純邏輯零件)和 TinyGo + `json.RawMessage`(需要任意 HTTP body 的零件)。不引入 Rust 作為內建零件語言。
|
||
|
||
**用戶自建零件支援三種語言,按難度分層:**
|
||
|
||
| 語言 | 目標用戶 | JSON 能力 | 備註 |
|
||
|---|---|---|---|
|
||
| **AssemblyScript** | 一般用戶(TS 背景) | 社群套件 `assemblyscript-json`,支援動態 JSON | 語法最接近 TS,門檻最低;靜默錯誤風險,沙盒驗收必須通過 |
|
||
| **TinyGo** | 技術較強用戶(Go 背景) | 靜態 struct 完整支援;`json.RawMessage` 處理任意 body | 編譯期報錯,AI 生成安全性較高 |
|
||
| **Rust** | 進階用戶 | `serde_json::Value` 完整動態 JSON | 生態最成熟,體積最小;學習曲線陡 |
|
||
|
||
**`/components/guide` 端點提供三份語言範例**,用戶根據自身背景選擇。
|
||
|
||
**內建零件 JSON 策略(TinyGo):**
|
||
|
||
```go
|
||
// 固定 schema 零件(google-sheets、gmail 等)→ 靜態 struct
|
||
type Input struct {
|
||
SpreadsheetId string `json:"spreadsheet_id"`
|
||
Range string `json:"range"`
|
||
AccessToken string `json:"access_token"`
|
||
}
|
||
|
||
// 任意 body 零件(http-request)→ json.RawMessage 傳遞 raw bytes,不解析
|
||
type Input struct {
|
||
URL string `json:"url"`
|
||
Method string `json:"method"`
|
||
Body json.RawMessage `json:"body"` // 任意 JSON,不解析
|
||
}
|
||
```
|
||
|
||
### Workflow Cypher 三元組(完整語法)
|
||
|
||
```yaml
|
||
kind: Workflow
|
||
id: wf_submit_form
|
||
|
||
triplets:
|
||
# 節點類型宣告
|
||
- "btn_submit >> IS_A >> ui://u6u-btn"
|
||
- "step_validate >> IS_A >> component://validate_json"
|
||
- "step_save >> IS_A >> component://kbdb_write"
|
||
|
||
# 前端觸發後端
|
||
- "btn_submit >> ON_CLICK >> step_validate"
|
||
|
||
# 成功/失敗分支
|
||
- "step_validate >> ON_SUCCESS >> step_save"
|
||
- "step_validate >> ON_FAIL >> step_notify_error"
|
||
|
||
# 子流程呼叫
|
||
- "step_save >> ON_SUCCESS >> CALLS_SUBFLOW >> workflow://wf_notify_user"
|
||
|
||
# 容器結構
|
||
- "card_main >> CONTAINS >> btn_submit"
|
||
- "card_main >> CONTAINS >> input_name"
|
||
```
|
||
|
||
### Evaluation Block(KBDB tpl-evaluation)
|
||
|
||
每次 Workflow 執行後,Evaluator Agent 寫入一個 Evaluation Block:
|
||
|
||
| Slot key | 說明 |
|
||
|---|---|
|
||
| `run_id` | 執行唯一 ID |
|
||
| `workflow_id` | Workflow ID |
|
||
| `component_id` | 被評價的零件 ID |
|
||
| `verdict` | `success` / `failed` / `timeout` |
|
||
| `duration_ms` | 執行時間 |
|
||
| `error_message` | 失敗訊息(可選) |
|
||
| `evaluated_at` | 評價時間戳記 |
|
||
|
||
### Pitfall Block(KBDB tpl-pitfall)
|
||
|
||
| Slot key | 說明 |
|
||
|---|---|
|
||
| `component_id` | 問題零件 ID |
|
||
| `failure_pattern` | 失敗模式描述 |
|
||
| `first_seen_at` | 首次發現時間戳記 |
|
||
| `occurrence_count` | 發生次數 |
|
||
|
||
---
|
||
|
||
## Correctness Properties
|
||
|
||
*A property is a characteristic or behavior that should hold true across all valid executions of a system — essentially, a formal statement about what the system should do. Properties serve as the bridge between human-readable specifications and machine-verifiable correctness guarantees.*
|
||
|
||
|
||
### Property Reflection(去重分析)
|
||
|
||
在寫出最終屬性前,先做冗餘分析:
|
||
|
||
- **1.1 + 1.2**:都是合約欄位完整性驗證,合併為 Property 1「合約格式完整性」
|
||
- **2.1 + 2.2**:驗收流程執行 + 失敗回報,合併為 Property 2「沙盒驗收流程正確性」
|
||
- **2.3 + 2.4**:提交後可讀取 + 冪等提交,合併為 Property 3「零件提交冪等性與持久性」
|
||
- **3.1 + 3.5**:WASM 執行 + 雙模式路由,合併為 Property 4「Component Dispatcher 路由正確性」
|
||
- **3.4 + 6.4**:不相容 Tier 回傳錯誤 + 結構化錯誤,合併為 Property 5「Dispatcher 錯誤結構完整性」
|
||
- **4.1**:URI 解析 round-trip,獨立為 Property 6
|
||
- **4.2 + 4.4**:版本選擇算法(floating 最高分 + pinned 固定版本),合併為 Property 7「版本選擇策略正確性」
|
||
- **4.5**:版本保留不變量,獨立為 Property 8
|
||
- **5.7**:Confluence 屬性,獨立為 Property 9
|
||
- **8.3 + 8.4 + 8.5**:Web Components 事件與渲染,合併為 Property 10「Web Components 事件與渲染冪等性」
|
||
- **10.6 + 12.5**:評價冪等性 + 查詢冪等性,合併為 Property 11「系統操作冪等性」
|
||
|
||
最終保留 11 個屬性,每個提供獨立驗證價值。
|
||
|
||
---
|
||
|
||
### Property 1: 合約格式完整性
|
||
|
||
*For any* component contract object,若缺少任何必填欄位(`id`、`version`、`wasi_target`、`stability`、`runtime_compat`、`constraints.max_size_kb`、`constraints.max_cold_start_ms`、`constraints.no_network_syscall`、`constraints.io_model`、`input_schema`、`output_schema`、`gherkin_tests`),合約驗證器 SHALL 拒絕該合約並回傳包含缺失欄位名稱的錯誤;反之,包含所有必填欄位的合約 SHALL 通過格式驗證。
|
||
|
||
**Validates: Requirements 1.1, 1.2, 1.4**
|
||
|
||
---
|
||
|
||
### Property 2: 沙盒驗收流程正確性
|
||
|
||
*For any* 提交的零件(.wasm + contract),若零件在驗收步驟 N 失敗,Component_Registry 的回應 SHALL 包含步驟 N 的名稱與具體失敗原因,且不執行步驟 N+1 之後的步驟。
|
||
|
||
**Validates: Requirements 2.1, 2.2**
|
||
|
||
---
|
||
|
||
### Property 3: 零件提交冪等性與持久性
|
||
|
||
*For any* 通過驗收的零件(id, version),提交後從 Component_Registry 讀取該零件的合約,所有欄位值 SHALL 與提交時的合約完全一致(序列化 round-trip);對相同 (id, version) 重複提交 N 次,KBDB 中 SHALL 只存在一個對應的 Block。
|
||
|
||
**Validates: Requirements 2.3, 2.4**
|
||
|
||
---
|
||
|
||
### Property 4: Component Dispatcher 路由正確性
|
||
|
||
*For any* 零件合約,若 `io_model = "stdin_stdout_json"`,Component_Dispatcher SHALL 使用 WASM 執行路徑,將 input JSON 寫入 stdin,從 stdout 讀取 output JSON;若 `io_model = "http_endpoint"`,SHALL 使用 HTTP 路徑。對任意合法 JSON input,WASM 執行路徑的輸出 SHALL 與 HTTP 執行路徑的輸出語意等效。
|
||
|
||
**Validates: Requirements 3.1, 3.5, 3.6**
|
||
|
||
---
|
||
|
||
### Property 5: Dispatcher 錯誤結構完整性
|
||
|
||
*For any* (component_id, tier) 組合,若該零件的 `runtime_compat` 不包含當前 tier,Component_Dispatcher 的錯誤回應 SHALL 同時包含:零件 id、當前 tier 名稱、已嘗試的呼叫路徑清單,三個欄位缺一不可。
|
||
|
||
**Validates: Requirements 3.4, 6.4**
|
||
|
||
---
|
||
|
||
### Property 6: 零件 URI 解析 Round-Trip
|
||
|
||
*For any* 合法的零件 URI 字串(格式為 `component://id`、`component://id@stable`、`component://id@pinned:vN`),解析後再重新序列化 SHALL 產生與原始 URI 語意等效的字串;解析出的 `id`、`stability`、`pinnedVersion` 欄位 SHALL 與原始 URI 中的對應部分完全一致。
|
||
|
||
**Validates: Requirements 4.1**
|
||
|
||
---
|
||
|
||
### Property 7: 版本選擇策略正確性
|
||
|
||
*For any* 零件 id 下的版本集合(每個版本有 success_rate、avg_duration_ms、call_count 評分),當 stability = `floating` 時,Component_Dispatcher SHALL 選取「成功率 × 速度評分 × 被調用次數」最高的版本;當 stability = `pinned:vN` 時,無論版本集合中其他版本的評分如何,SHALL 永遠選取版本 vN。
|
||
|
||
**Validates: Requirements 4.2, 4.4**
|
||
|
||
---
|
||
|
||
### Property 8: 歷史版本永久保留不變量
|
||
|
||
*For any* 已上架的零件版本(id, version),無論該版本後來被標記為 `deprecated` 或 `tombstone`,其 `.wasm` 二進位 SHALL 永遠可從 R2 讀取,且 `pinned` 引用 SHALL 永遠能透過 Component_Dispatcher 執行該版本。
|
||
|
||
**Validates: Requirements 4.5, 10.5**
|
||
|
||
---
|
||
|
||
### Property 9: Cypher 三元組解析 Confluence(順序無關性)
|
||
|
||
*For any* 合法的 Cypher 三元組集合,無論三元組在輸入陣列中的排列順序如何,`parseTriplets` 產生的執行圖(節點集合、邊集合、拓撲結構)SHALL 語意等效——即相同的節點 id 集合、相同的 (from, to, type) 邊集合。
|
||
|
||
**Validates: Requirements 5.7**
|
||
|
||
---
|
||
|
||
### Property 10: Web Components 事件與渲染冪等性
|
||
|
||
*For any* workflow URI 設定於 `<u6u-btn>` 的 `workflow` attribute,使用者點擊後發出的 `u6u:trigger` 事件 detail 中的 `workflowId` SHALL 與 URI 中的 id 完全一致;*For any* 一組具名 `<u6u-text-input>` 元件置於 `<u6u-card>` 內,觸發事件後收集到的 payload SHALL 包含所有輸入元件的 name-value 對;*For any* attribute 值,對同一 Web Component 設定相同 attribute 值 N 次,渲染結果 SHALL 與設定一次相同(冪等渲染)。
|
||
|
||
**Validates: Requirements 8.3, 8.4, 8.5**
|
||
|
||
---
|
||
|
||
### Property 11: 系統操作冪等性
|
||
|
||
*For any* Workflow 執行日誌(run_id),Evaluator_Agent 對相同 run_id 處理 N 次,KBDB 中 SHALL 只存在一個對應的 Evaluation Block,不產生重複記錄;*For any* Component_Registry 讀取操作的查詢參數,在 KBDB 資料不變的前提下,對相同參數呼叫 N 次 SHALL 回傳完全相同的結果。
|
||
|
||
**Validates: Requirements 10.6, 12.5**
|
||
|
||
---
|
||
|
||
## Error Handling
|
||
|
||
### Component Dispatcher 錯誤分類
|
||
|
||
| 錯誤類型 | 觸發條件 | 回應格式 |
|
||
|---|---|---|
|
||
| `COMPONENT_NOT_FOUND` | KBDB 中找不到零件 | `{ error: "COMPONENT_NOT_FOUND", component_id, tier }` |
|
||
| `RUNTIME_INCOMPATIBLE` | runtime_compat 不含當前 Tier | `{ error: "RUNTIME_INCOMPATIBLE", component_id, tier, attempted_paths: [] }` |
|
||
| `WASM_EXECUTION_TIMEOUT` | 超過 max_cold_start_ms | `{ error: "WASM_EXECUTION_TIMEOUT", component_id, timeout_ms }` |
|
||
| `WASM_INVALID_OUTPUT` | stdout 不是合法 JSON | `{ error: "WASM_INVALID_OUTPUT", component_id, raw_output }` |
|
||
| `WASM_SYSCALL_VIOLATION` | .wasm 嘗試網路/檔案 syscall | `{ error: "WASM_SYSCALL_VIOLATION", component_id, syscall_name }` |
|
||
| `CONTRACT_VALIDATION_FAILED` | 合約格式不合規 | `{ error: "CONTRACT_VALIDATION_FAILED", missing_fields: [] }` |
|
||
|
||
### 沙盒驗收失敗回應格式
|
||
|
||
```json
|
||
{
|
||
"success": false,
|
||
"failed_step": "syscall_scan",
|
||
"reason": "發現禁止的 syscall:sock_connect",
|
||
"guide_anchor": "#syscall-constraints",
|
||
"component_id": "my_component",
|
||
"version": "v1"
|
||
}
|
||
```
|
||
|
||
### Tier 3 離線錯誤處理
|
||
|
||
Tier 3 在離線環境中,所有無法執行的操作都寫入 DTN 佇列,不拋出錯誤:
|
||
|
||
```go
|
||
// Go 排程引擎的錯誤處理策略
|
||
type DTNEntry struct {
|
||
Type string // "missing_component" | "sync_log" | "request_wasm"
|
||
Payload json.RawMessage
|
||
CreatedAt time.Time
|
||
RetryCount int
|
||
}
|
||
```
|
||
|
||
### Web Components 錯誤邊界
|
||
|
||
`<u6u-btn>` 在 `workflow` attribute 未設定時,點擊不發出事件,僅在 console 輸出警告:
|
||
|
||
```
|
||
[u6u-btn] workflow attribute is not set, click event ignored
|
||
```
|
||
|
||
---
|
||
|
||
## Testing Strategy
|
||
|
||
### 雙軌測試策略
|
||
|
||
本功能採用「單元測試 + 屬性測試」雙軌策略:
|
||
|
||
- **單元測試(Vitest)**:驗證具體範例、邊界條件、錯誤情境
|
||
- **屬性測試(fast-check)**:驗證上述 11 個 Correctness Properties,每個屬性最少執行 100 次迭代
|
||
|
||
### 屬性測試配置
|
||
|
||
使用 `fast-check`(已在 tech stack 中),每個屬性測試標記格式:
|
||
|
||
```typescript
|
||
// Feature: arcrun-platform-evolution, Property N: {property_text}
|
||
it.prop([fc.record({ id: fc.string(), version: fc.string(), ... })])(
|
||
'Property 1: 合約格式完整性',
|
||
(contract) => {
|
||
// ...
|
||
},
|
||
{ numRuns: 100 }
|
||
);
|
||
```
|
||
|
||
### 各 Phase 測試重點
|
||
|
||
**Phase 0(WASM 執行核心):**
|
||
- Property 4:WASM 執行路徑 round-trip(`validate_json.wasm` 作為 ground truth)
|
||
- Property 1:合約格式驗證
|
||
- 單元測試:WASI shim 的 `fd_read`/`fd_write` 正確性
|
||
|
||
**Phase 1(零件遷移):**
|
||
- Property 3:提交冪等性(20 個零件各提交兩次,驗證無重複)
|
||
- Property 2:沙盒驗收流程(各步驟失敗案例)
|
||
- Property 8:歷史版本保留(deprecate 後仍可讀取)
|
||
|
||
**Phase 2(Cypher 擴展):**
|
||
- Property 9:Confluence(三元組順序無關性,fast-check shuffle)
|
||
- Property 6:URI 解析 round-trip
|
||
- Property 7:版本選擇策略(floating 最高分、pinned 固定版本)
|
||
- Property 5:錯誤結構完整性
|
||
|
||
**Phase 3(前端畫布):**
|
||
- Property 10:Web Components 事件與渲染冪等性(`@testing-library/react` + fast-check)
|
||
- Property 11:評價冪等性(Evaluator Agent 重複處理)
|
||
|
||
### 整合測試
|
||
|
||
以下場景使用整合測試(1-3 個具體範例,不用 PBT):
|
||
|
||
- Tier 1 CF Workers 環境中實際執行 `validate_json.wasm`(驗證 WASM 在 Workers 環境可運行)
|
||
- KBDB tpl-component Template 建立與 Slot 讀寫(驗證 KBDB 整合)
|
||
- R2 `.wasm` 上傳與讀取(驗證 R2 整合)
|
||
- Vectorize 語意搜尋(驗證「查詢 Google Sheets 資料」能找到 `gsheets_get_entries`)
|
||
|
||
### 單元測試重點(非 PBT)
|
||
|
||
- WASI shim:`fd_read` 正確讀取 stdin、`fd_write` 正確寫入 stdout
|
||
- `evaluateCondition`:現有條件評估函數的邊界案例
|
||
- `resolveComponentId`:URI 解析的邊界案例(空字串、特殊字元)
|
||
- `<u6u-card>` Smart Container:巢狀容器的事件冒泡行為
|