# 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：巢狀容器的事件冒泡行為
