chore: D22 落地——docs/SDD/wiki/CLAUDE.md 進 repo(Gitea private 預設全 push)

頂層 D22 決策(leo 2026-07-03 拍板):推什麼由開發環境歸屬決定,
Gitea private=除機敏值/build 產物/.github 外全 push。
解 T1.5 卡點:雲端工人 clone 拿得到 credential-store-migration.md,可就地改寫 SDD。
機敏掃描兩輪通過(新增 189 檔約 2.1MB,node_modules/dist/wasm 照舊排除)。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
uncle6me-web
2026-07-03 07:13:15 +08:00
parent c830150da1
commit 5d00e71275
190 changed files with 39486 additions and 14 deletions
@@ -0,0 +1,189 @@
# 階段一:檔案分類清單
> 共 101 個 .md 檔案,按建議位置分類。
> 信心度:確定/不確定 | 原因
---
## 📌 根目錄 — 保留(核心配置)
| 檔案 | 判定 | 原因 |
|------|------|------|
| CLAUDE.md | ✅ 保留 | 項目規範入口,cc 必讀 |
| README.md | ✅ 保留 | 對外項目說明 |
| DECISIONS.md | ⚠️ → docs/2-architecture/decisions/ | 架構決策歷史,應歸檔 |
| BACKLOG.md | ⚠️ → docs/3-specs/ | 需求清單,隨 SDD 更新 |
| BETA_TEST.md | ⚠️ → docs/5-records/test-reports/ | 測試記錄 |
| RELEASE-CHECKLIST.md | ✅ → docs/4-guides/ | 操作手冊 |
| CONTRIBUTING.md | ✅ → docs/6-user/ | 對外開發指南 |
| AGENTS.md | ⚠️ → docs/4-guides/ | Agent 用法指南 |
---
## 📦 .agents/specs/ → docs/3-specs/
(高優先,直接搬家,保留目錄結構)
### arcrun-core-mvp/
- requirements.md ✅
- design.md ✅
- tasks.md ✅
### arcrun-platform-evolution/
- requirements.md ✅
- design.md ✅
- tasks.md ✅
### arcrun/ (主線)
- arcrun.md ✅
- auth-recipe.md ✅
### arcrun/credential-primitives-wasm/
- design.md ✅
- tasks.md ✅
### arcrun/frontend-redesign/
- requirements.md ✅
- design.md ✅
- tasks.md ✅
- design-source/SOURCE_README.md ✅
- design-source/design-chat.md ✅
### arcrun/kbdb-base/
- design.md ✅
- tasks.md ✅
### arcrun/landing-page.md ✅
### arcrun/sdk-and-website/
- design.md ✅
- requirements.md ✅
- tasks.md ✅
- config-layering.md ✅
- mcp-account-source.md ✅
- self-hosted-init.md ✅
### component-gatekeeping/
- requirements.md ✅
- design.md ✅
- tasks.md ✅
- recipe-push-gatekeeping.md ✅
### component-registry-canon/
- design.md ✅
- tasks.md ✅
### data-exfil-warning/
- requirements.md ✅
- design.md ✅
- tasks.md ✅
### llm-interface/
- requirements.md ✅
- design.md ✅
- tasks.md ✅
### recipe-system/
- design.md ✅
- tasks.md ✅
### resumable-workflow/
- design.md ✅
- tasks.md ✅
### user-cc-harness/
- design.md ✅
- tasks.md ✅
---
## 🏗️ .claude/rules/ → docs/2-architecture/
(高優先,技術棧 + 架構規範)
| 檔案 | 目標位置 | 說明 |
|------|---------|------|
| 00-sdd-protocol.md | docs/2-architecture/ | SDD 協議(流程規範) |
| 01-tech-stack.md | docs/2-architecture/ | 技術棧硬限制(三層語言) |
| 02-forbidden.md | docs/2-architecture/ | 禁止行為(hook 強制) |
| 03-component-architecture.md | docs/2-architecture/ | 零件架構定義(R2/binding/URL |
| 04-current-progress.md | docs/2-architecture/ | 當前進度(動態,常更新) |
| 05-deploy-convention.md | docs/4-guides/deploy/ | 部署慣例(操作手冊) |
| 06-mindset.md | docs/2-architecture/ | 設計哲學(為什麼層) |
| 07-thin-shell.md | docs/2-architecture/ | 薄殼原則鐵律 |
---
## 📋 docs/ 現有 → 重新分類
### docs/incidents/ → docs/5-records/incidents/
- 2026-05-13-cypher-outbound-522.md ✅
- 2026-05-13-chain-ctx-propagation.md ✅
- 2026-05-13-nested-foreach-iterable.md ✅
- 2026-05-29-encryption-key-drift.md ✅
- README.md ✅
### docs/ 根目錄
| 檔案 | 新位置 | 說明 |
|------|--------|------|
| pre-customer-checklist-2026-06-07.md | docs/5-records/test-reports/ | 測試檢查清單 |
| 壓測-recipe-library-2026-06-07.md | docs/5-records/test-reports/ | 壓測報告 |
### docs/user_requirements/ → docs/6-user/ 或 docs/3-specs/
(需求源,可適度歸檔,但常引用)
- credential_parts.md → docs/3-specs/
- wishlist.md → docs/6-user/
- u6u-plan.md → docs/3-specs/ (歷史規劃)
- u6u_design.md → docs/3-specs/
- u6u_system_spec.md → docs/3-specs/
- 其他 ADR → docs/2-architecture/decisions/
---
## 🛠️ 專案子目錄內的文件
### cli/
- CHANGELOG.md → docs/5-records/(版本歷史)
- harness/ → 保留(bundled into npm package
### landing/
- CLAUDE.md → 保留(子項目規範)
- README.md → 保留(對外說明)
- AGENTS.md → docs/4-guides/ agent 文件)
### mcp/
- README.md → 保留(對外說明)
- GUIDE.md → docs/4-guides/mcp-setup.md
- dev/review.md → docs/4-guides/mcp-development.md
### registry/
- examples/README.md → docs/4-guides/workflow-examples.md
- examples/*/description.md → docs/4-guides/examples/ (搬進)
- skills/README.md → docs/4-guides/
- skills/*.md → docs/4-guides/skills/ (搬進)
- components/*/README.md → docs/4-guides/components/ (有名字的元件文件)
### tests/
- TEST_CASES.md → docs/5-records/test-reports/
---
## 📊 統計
| 類別 | 件數 | 狀態 |
|------|------|------|
| docs/1-vision/ | 0(待補) | 待建 |
| docs/2-architecture/ | ~20 | 從 .claude/rules + DECISIONS |
| docs/3-specs/ | ~40 | 從 .agents/specs + user_requirements |
| docs/4-guides/ | ~15 | 從 registry/skills + README 類 |
| docs/5-records/ | ~10 | 從 incidents + test-reports |
| docs/6-user/ | ~5 | 從 CONTRIBUTING + user_requirements |
| .claude/wiki/ | 4+N | 待建 |
| 保留根目錄 | 3 | CLAUDE.md, README.md, .claude/ |
---
## ⏭️ 下一步
確認上述分類無誤後,進行階段二(逐一讀文件,建 wiki)。
@@ -0,0 +1,144 @@
# 官方 KBDB 誤寫清理 SOPissue #3 待辦 1
> **狀態**runbook 已備妥,**待官方運營方(leo)親自執行**。
> **為什麼不由 CC 直接跑**:對官方 prod D1 執行不可逆 `DELETE`,需官方憑證 + 人類明示確認
> (mindset §7「絕不代替人類做有風險的確認」;rule 06)。CC 只備妥可審、防誤刪的腳本,DELETE 由人按下。
> **來源**issue #3leo 2026-06-24 拍板,14-E 遷移善後)。根因 bug 已修(issue #2commit 9c4333d)。
---
## 背景
14-E 遷移期間(issue #2`KBDB_BASE_URL` fallback bug 修好**之前**),mira 的 `_kbdb_client.py`
**~11 萬筆 `owner_id='leo'`** 的資料誤寫進**官方 prod kbdb**`arcrun-kbdb`,非 leo21c self-hosted)。
- **歸屬**:官方 SaaS 庫的清理 = arcrun 官方運營方的事,不是 mira(用戶)。讓用戶拿官方憑證 DELETE 官方 prod 本身違反隔離。
- **重要性**:SaaS 尚未營運,這批誤寫資料不重要 → 可刪。但**官方 prod DELETE 不可逆 → 必須防誤刪**。
---
## 目標庫(精確座標)
| 項目 | 值 |
|------|-----|
| Worker / DB name | `arcrun-kbdb` |
| D1 database_id | `0c580910-e00b-4f8e-9c57-ac54ea52242f`(官方 prod,見 `kbdb/wrangler.toml:13` |
| 官方 CF account | `58309bb9…`(記憶 [[cf-account-official-vs-loadtest]] |
| 誤寫標記 | `entries.owner_id = 'leo'` |
⚠️ **帳號對齊**:執行前確認本機 wrangler 對的是**官方帳號**(不是 leo21c)。
`wrangler whoami` 應顯示官方 uncle6.me account。誤寫在官方庫,所以這次**就是要對官方帳號**操作
(與 self-hosted 部署相反,那邊要避開官方——見記憶 [[selfhosted-deploy-account-override-trap]])。
---
## 表關係(決定刪除範圍)
base 三表(`kbdb/migrations/0001_base.sql`):
- `entries`:主表,誤寫資料在這(`owner_id='leo'`)。
- `entry_values`slot-link`entry_id REFERENCES entries(id)`。若 leo 資料含 record(用 template 組的結構化資料),
其 slot 連結在這。**刪 entries 會留下孤兒 entry_values** → 要一併清。
- `templates``created_by` 可能是 `'leo'`。**先確認** leo 有沒有建 template(待辦 2 步驟會查),
template 較可能是共享/誤建,刪前單獨核實。
---
## SOP(逐步,每步有 gate,防誤刪)
> 全程用 `wrangler d1 execute arcrun-kbdb --remote --command "..."`。**`--remote` 不可漏**(漏了打本地空庫,假綠)。
### 步驟 0:帳號 + 庫核實(gate)
```bash
wrangler whoami # 確認 = 官方 uncle6.me account58309bb9
wrangler d1 info arcrun-kbdb # 確認 database_id = 0c580910...
```
### 步驟 1:備份(整庫導出,防誤刪的底氣)
```bash
wrangler d1 export arcrun-kbdb --remote --output backup-before-cleanup-2026-06-24.sql
ls -lh backup-before-cleanup-2026-06-24.sql # 確認檔案非空、大小合理
```
### 步驟 2:標記 / 核實刪除範圍(**最關鍵的 gate**)
```bash
# 2a. 誤寫總數(應 ~11 萬)
wrangler d1 execute arcrun-kbdb --remote --command \
"SELECT count(*) AS leo_entries FROM entries WHERE owner_id='leo';"
# 2b. 關鍵確認:owner_id='leo' 是否只有這批誤寫,有沒有別的 leo 真資料混入
# 看 entry_type 分布 + 時間範圍(誤寫應集中在 14-E 遷移那段時間)
wrangler d1 execute arcrun-kbdb --remote --command \
"SELECT entry_type, count(*) AS n, min(created_at) AS first_at, max(created_at) AS last_at \
FROM entries WHERE owner_id='leo' GROUP BY entry_type ORDER BY n DESC;"
# 2c. 孤兒 entry_values(會被 entries 刪除留下的)
wrangler d1 execute arcrun-kbdb --remote --command \
"SELECT count(*) AS leo_entry_values FROM entry_values \
WHERE entry_id IN (SELECT id FROM entries WHERE owner_id='leo');"
# 2d. leo 建的 template(單獨核實,勿盲刪——可能是共享/系統 template 誤標)
wrangler d1 execute arcrun-kbdb --remote --command \
"SELECT id, name, created_by FROM templates WHERE created_by='leo';"
```
**Gate 判定**(人類看數字決定):
- 2a count ≈ 11 萬、2b 時間集中在遷移期 → 範圍乾淨,可進步驟 3。
- 若 2b 出現非遷移期、或 entry_type 異常 → **停手**,逐筆核實,別整批刪。
- 2d 若有 template → 個別判斷是否該刪(template 通常想保留,除非確認是誤建)。
### 步驟 3:刪除(確認範圍乾淨後)
```bash
# 3a. 先刪孤兒 entry_values(外鍵指向即將被刪的 entries)
wrangler d1 execute arcrun-kbdb --remote --command \
"DELETE FROM entry_values WHERE entry_id IN (SELECT id FROM entries WHERE owner_id='leo');"
# 3b. 再刪 entries
wrangler d1 execute arcrun-kbdb --remote --command \
"DELETE FROM entries WHERE owner_id='leo';"
# 3c.(可選,僅當步驟 2d 確認某 template 是誤建才刪)
# wrangler d1 execute arcrun-kbdb --remote --command \
# "DELETE FROM templates WHERE created_by='leo' AND id='<確認過的 id>';"
```
### 步驟 4:驗證(刪後核實)
```bash
# 4a. leo 誤寫歸零
wrangler d1 execute arcrun-kbdb --remote --command \
"SELECT count(*) AS remaining_leo FROM entries WHERE owner_id='leo';" # 應 = 0
# 4b. 孤兒 entry_values 歸零
wrangler d1 execute arcrun-kbdb --remote --command \
"SELECT count(*) AS orphan_ev FROM entry_values \
WHERE entry_id NOT IN (SELECT id FROM entries);" # 應 = 0
# 4c. 官方庫其餘資料不受影響(總 entries 數應 = 刪除前總數 - 11 萬)
wrangler d1 execute arcrun-kbdb --remote --command \
"SELECT owner_id, count(*) AS n FROM entries GROUP BY owner_id ORDER BY n DESC;"
```
### 步驟 5:收尾
- 4a/4b/4c 符合預期 → 清理完成。
- 備份檔(`backup-before-cleanup-2026-06-24.sql`)可棄(SaaS 未營運,無長期保留必要)。
- 在 issue #3 comment 回報:刪除數量 + 驗證 count + 確認官方庫其餘不受影響。
---
## 為什麼這樣設計(防誤刪三道閘)
1. **備份先行**(步驟 1):DELETE 不可逆,先有 d1 export 的整庫快照當底氣。
2. **核實再刪**(步驟 2 gate):不盲信「~11 萬都是誤寫」,看 entry_type + 時間分布確認範圍乾淨,
排除別的 leo 真資料混入(issue #3 待辦 1 步驟 2 的「關鍵確認」)。
3. **驗證歸零**(步驟 4):刪後客觀證據(count=0 + 孤兒=0 + 其餘不受影響),不靠「跑完了」口頭宣布
(mindset §7 完成=客觀證據)。
## 待辦 2(願景)落點
`acr migrate` 一等公民雙向遷移已記頂層 `docs/1-vision/product-wishlist.md` C7 + 本 repo backlog,不急做。
詳見 issue #3 待辦 2。
+32
View File
@@ -0,0 +1,32 @@
# 5. Records — 日誌 + 驗收報告
> 線上事件復盤、測試報告、決策軌跡。
## 線上事件
| 檔案 | 日期 | 內容 |
|------|------|------|
| **incidents/2026-05-13-cypher-outbound-522.md** | 2026-05-13 | 同 zone 自循環死鎖(22 分鐘故障) |
| **incidents/2026-05-13-chain-ctx-propagation.md** | 2026-05-13 | workflow chain context 無法向下傳播 |
| **incidents/2026-05-13-nested-foreach-iterable.md** | 2026-05-13 | 巢狀 foreach iterable 爆解析 |
| **incidents/2026-05-29-encryption-key-drift.md** | 2026-05-29 | 多 Worker 的 ENCRYPTION_KEY 不一致 |
## 測試報告
| 檔案 | 日期 | 內容 |
|------|------|------|
| **test-reports/壓測-recipe-library-2026-06-07.md** | 2026-06-07 | kbdb-base §7.5 上線驗收(16 項通過) |
| **test-reports/pre-customer-checklist-2026-06-07.md** | 2026-06-07 | 對客前檢查清單 |
| **test-reports/BETA_TEST.md** | — | 封測計畫(推遲) |
---
## 常見用途
- **線上問題復現** → 查 incidents/
- **功能驗收標準** → 查 test-reports/
- **決策背景** → 查 `.claude/wiki/decisions-summary.md`
---
更新時間:2026-06-08
@@ -0,0 +1,116 @@
# 2026-05-13 cypher-executor multi-node chain context propagation 漏失
> **總耗時**:約 20 分鐘 debug(找 P0 #9 過程中順帶找到)
> **根因**4 個 edge type 沒 spread baseCtx 給下游節點,原始 context 從第 2 節點開始消失
> **修法**ON_SUCCESS / ON_FAIL / IF / ON_CLICK 套用 PIPE / FOREACH 同模式 `{...baseCtx, ...result}`
> **影響**:任何 chain workflow 從第 2 節點開始 interpolate context key 都失敗
---
## 症狀
mira `acr run wiki_synthesis`7 節點 workflow)回 `Unauthorized`。trace 顯示:
- `load_schema`(節點 1)✓ 200input 含 `api_key=ak_xxx`
- `load_skill`(節點 2)✗ 401 Unauthorizedinput 含 `api_key="{{api_key}}"`**模板原文未替換**
## 推測 → 驗證
**對照組**2 節點 chain 看 input keys
| 節點 | input keys |
|---|---|
| n1 | `api_key, b1, b2, block_id` ✓ 全 context 在 |
| n2 | `blocks, count, success, api_key, block_id`**`b1, b2` 不見**`api_key` 是原文 `"{{api_key}}"` |
n2 的 ctx **只有 n1 output spread**,原始 context`b1`, `b2`, ...)全消失。
## 根因
`graph-executor.ts` 在 outEdges 處理時,PIPE 跟 FOREACH 已有 baseCtx merge
```typescript
// PIPE (line 381-384)
const pipeContext = {
...(context as Record<string, unknown>),
...baseResult,
};
// FOREACH (line 438-444)
const baseCtx = ...;
const itemContext = {
...baseCtx,
...(result as Record<string, unknown>),
[iteratorKey]: item,
};
```
但 ON_SUCCESS / ON_FAIL / IF / ON_CLICK 直接傳 `result`
```typescript
case 'ON_SUCCESS':
result = await this.executeNode(nextNode, graph, result, ...); // ← bug
```
`result` 是上游節點的 output**沒有原始 context**。下游 interpolate 找不到原始 key 就原文留下。
## 歷史脈絡
2026-05-07 commit `e8fca33`"FOREACH preserves outer context")已意識到問題並修了 FOREACH。但**沒同步處理另外 4 個 edge type**。今天才被 mira 7 節點 workflow 踩到。
→ 教訓:架構級修法要全 edge type 一致掃過,不只修當下踩到的。
## 修法
`cypher-executor/src/graph-executor.ts` 4 個 edge case 補:
```typescript
// 改前
result = await this.executeNode(nextNode, graph, result, ...);
// 改後
const baseCtx = (typeof context === 'object' && context !== null) ? context as Record<string, unknown> : {};
const baseResult = (typeof result === 'object' && result !== null) ? result as Record<string, unknown> : {};
const mergedCtx = { ...baseCtx, ...baseResult };
result = await this.executeNode(nextNode, graph, mergedCtx, ...);
```
套用在 line 407 (ON_SUCCESS) / 415 (ON_FAIL) / 423 (IF) / 472 (ON_CLICK)。
## 驗證
`acr run wiki_synthesis` 7 節點 workflow 端對端跑通:
| 節點 | 修前 input | 修後 input |
|---|---|---|
| n2 (load_skill) | `api_key={{api_key}}` ✗ | `api_key=ak_xxx` ✓ |
| n7 (emit_result) | 上游 spread only | baseCtx + 各上游 spread ✓ |
整條 16.2 秒(含 claude_api 真實 Claude 呼叫),結果 `{success: true, data: {...}}`
## Known Limitation
`interpolateData()` 只展開 `node.data` top-level string values**不遞迴 nested object**
```yaml
# emit_result 的 values 內 {{...}} 不會展開
emit_result:
component: set
values:
text: "{{classify.data.text}}" # ← 原文傳,不展開
```
非阻擋 P1SDD 待開 `interpolate-nested-config`。當前 workaround:直接看上游節點的 trace output。
## 未來怎麼避免
1. **新 edge type 加進來時必須走 baseCtx merge 模式**——可以抽出 helper `mergeCtxForDownstream(context, result)` 強制所有 caller 用,避免漏
2. **interpolation 邊界要有測試**:寫一個 2 節點 chain 用 `{{api_key}}` 引用原始 context 的 e2e testCI 跑過
3. **trace output 要顯示 input** —— 沒看 trace 內 `input` 欄位很難看出 interpolation 失敗(目前 acr CLI 不顯示 input,只顯示 result)。CLI 應加 `--verbose` 顯示每節點 input
## Reference
- 對應 SDD`matrix/arcrun/.agents/specs/arcrun/arcrun.md` P0 #10
- 相關 commit`e8fca33` 2026-05-07FOREACH 同類修法)
- 受影響 SDD`polaris/mira/.agents/specs/mira-app/tasks.md` 7B.3c
- 同日另一個 incident[2026-05-13-cypher-outbound-522.md](./2026-05-13-cypher-outbound-522.md)
@@ -0,0 +1,143 @@
# 2026-05-13 cypher-executor outbound fetch 全失效(CF 同 zone 自循環死鎖)
> **總耗時**:約一整天 debug
> **根因**Cloudflare Workers 對「綁 custom domain 的 Worker fetch 同 zone 另一個 custom domain Worker」會撞 zone routing 死鎖 → 回 522
> **修法**cypher-executor fetch component worker 改走 `*.workers.dev` 子域(不同路由系統,繞過 zone proxy)
> **影響**mira 7B.3c 阻擋一整天;封測 P0 從「全綠」revert 成 #9 阻擋
---
## 症狀
mira 跑 `acr run wiki_synthesis`5 節點 workflow,含 kbdb_get / claude_api)回:
```
n1 → {"success": false, "status": 522, "error": "error code: 522"}
```
每節點精準 ~1000ms timeout 後 522。
進一步測試發現**所有 outbound HTTP fetch from cypher-executor 都 522**,無論目標是:
- 同 zone`kbdb-get.arcrun.dev``claude-api.arcrun.dev``cypher.arcrun.dev/health`self
- 外部:`httpbin.org``github.com``google.com`
**service binding 路徑(SVC_STRING_OPS 等 15 個邏輯零件)完全正常**
## 觀察矩陣
| 路徑 | 結果 |
|---|---|
| 本機 curl → kbdb-get.arcrun.dev | 200 ✓ |
| cypher-executor → kbdb-get.arcrun.dev (HTTP fetch) | **522** |
| cypher-executor → claude-api.arcrun.dev (HTTP fetch) | **522** |
| cypher-executor → httpbin.org (HTTP fetch) | **522** |
| cypher-executor → string_ops (Service Binding) | 200 ✓ |
| `acr run hello`(純 SB 1 節點)| 200 ✓ |
| `acr run`5 節點純 SB chainwallTime 2.2s| 200 ✓ |
| **wrangler dev(本機跑 cypher-executor)→ httpbin / kbdb-get** | **200 ✓** |
| 同 src 部署成 `arcrun-cypher-executor-probe`(純 workers.dev,無 routes 綁定)→ httpbin / kbdb-get / claude-api | **200 ✓** |
最關鍵兩條:
1. **本機 wrangler dev 跑同 srcfetch 全通** → 不是 code bug,是 prod 環境問題
2. **probe worker(同 src、不同 name、走 workers.dev、無 cypher.arcrun.dev route)→ 全通** → 是 `cypher.arcrun.dev/*` route 觸發的環境問題
## 誤判路徑(重要 — 避免重犯)
| 假設 | 為什麼錯 |
|---|---|
| **A. CF Free Tier 10ms CPU cap**(首先猜的)| 5 節點 SB chain 跑 2.2 秒卻通過,CPU cap 假設立刻被推翻。但前期 debug 走了不少彎路 |
| **B. 用戶從 Paid 掉到 Free** | 用戶確實掉到 Free,當下繳費恢復 Paid,**重測仍全 522**,徹底排除付費假設 |
| **C. compatibility_date 太舊(2025-02-19**| 沒嘗試 bump,但本機 dev 跟 prod 同個 compatibility_date 一通一不通,**不是這個** |
| **D. 5/8-5/9 manual deploy 推了壞掉的 unpushed commits** | git diff 本機 vs origin/main 確實有 3 個 unpushed commits,但檢查 prod bundle 內 `makeHttpRunner` 跟本機 src 一字不差。**code 沒問題** |
| **E. Bot Fight Mode / WAF 攔截 outbound fetch** | 用戶截圖 zone Security 設定,Bot Fight Mode 未開。**不是這個** |
| **F. 帳號層 outbound network policy 限制** | dashboard worker bindings 完全乾淨(無 outbound worker / tail consumer / Hyperdrive 等),**不是這個** |
| **G. CF Worker subrequest quota** | wrangler tail 顯示 `wallTime: 497ms, cpuTime: 2ms, outcome: ok` —— cypher-executor 自己沒撞任何 quota,是 fetch 出去就被攔 |
| **H. u6u-mcp service trigger 攔截** | 那是 reverse directionu6u-mcp 把 cypher-executor 當 service binding 呼叫),跟 outbound fetch 無關 |
**共通教訓**:522 來自「fetch 出去就被攔截,cypher-executor 不知情把 522 包成 component output 回 client」。wrangler tail 看 cypher-executor outcome 是 `ok` —— **不要被 worker 自己的 `ok` 騙了**,要看 trace 內 component output 才是真相。
## 真相
CF Workers 對「綁 custom domain Worker A 用 `fetch()` 打同 zone 另一個 custom domain Worker B」會撞 **zone reverse proxy 路由死鎖**。具體機制(推測):
1. cypher.arcrun.dev/* route 觸發 → cypher-executor 收到請求
2. cypher-executor 內部 `fetch("https://kbdb-get.arcrun.dev/")` 出去
3. CF edge 看「目標 hostname 是同 zone (`arcrun.dev`),有 worker route」 → 走 zone reverse proxy
4. 同 zone re-entry 觸發 CF 自循環防護 → fetch 不能完成
5. 對 cypher-executor 表現為「fetch 等到 timeout,得到一個假 522 response」
6. cypher-executor 不知情,把 522 當作 component 真實回應傳回 client
httpbin.org 也 522 不是 same-zone 問題,可能是「進入 outbound fetch code path 就被同樣機制 abort」的副作用(這部分仍不完全清楚,但實證 workers.dev 路徑無此問題)。
**`*.workers.dev` 不走 zone reverse proxy**,是 CF 內部的另一條路由(worker-to-worker internal routing),不撞死鎖。**Service Binding 同理**(不走網路,process 內呼叫)。
## 為什麼以前能跑
4/18 arcrun.md 記錄「httpbin_post 端對端驗證通過」,當時 cypher-executor 也是綁 cypher.arcrun.dev/*,也是 fetch *.arcrun.dev**卻能通**。推測 CF 之後某次平台更新收緊了 same-zone fetch 防護,但確切時間不可考。
**教訓**CF 平台行為會悄悄變,依賴 `fetch(*.same-zone.dev)` 是脆弱設計,從此架構上不該再依賴。
## 修法
**改 4 個檔案**commit see git log around 2026-05-13):
1. `cypher-executor/src/lib/component-loader.ts`
- `wasmWorkerUrl(canonicalId, subdomain)` 簽名加 `subdomain` 參數
- URL pattern 從 `https://${kebab}.arcrun.dev` 改為 `https://arcrun-${kebab}.${subdomain}.workers.dev`
- 兩個 callerline 128 / line 192 fallback)同步改
2. `cypher-executor/src/actions/auth-dispatcher.ts`
- `wasmWorkerUrl(...)` 呼叫同步加 `subdomain` 參數,從 `env.WORKER_SUBDOMAIN`
3. `cypher-executor/src/types.ts`
- `Bindings``WORKER_SUBDOMAIN: string`
4. `cypher-executor/wrangler.toml`
- `[vars]``WORKER_SUBDOMAIN = "uncle6-me"`
**Dashboard 一次性手動操作**5 個 P0 component workerkbdb-get / kbdb-ingest / kbdb-create-block / kbdb-patch-block / claude-api)→ Settings → Domains & Routes → workers.dev → **Enable**
**未來新增 component worker 時必須**dashboard 啟用 workers.dev URLrule 03 已加入步驟 5
## 驗證
修復後重測同樣 3 個 trigger:
| Trigger | 修復前 | 修復後 |
|---|---|---|
| cypher → kbdb-get | 522 (1064ms) | **200 (2027ms)** 拿回真實 block 內容 |
| cypher → claude-api | 522 (1014ms) | **200 (6226ms)** Claude 真實回應 |
| cypher → httpbin | 522 (1023ms) | 404 error 1042(不是 522 死鎖了;外部 fetch 限制是另一個問題,不影響 mira)|
mira `acr run wiki_synthesis` 5 節點 workflow 跑通前 3 節點(load_schema / load_skill / load_entities),後續節點失敗是 mira 業務邏輯問題(Unauthorized 在某個 block id),不是 cypher-executor 平台問題。
## 影響範圍與善後
**影響**
- 任何用 cypher-executor 跑外部 fetch 的 workflow 都壞(封測 Step 2/5/6 / mira / 自架用戶)
- arcrun 「每個零件 = 公開 URL」承諾在純 `*.arcrun.dev` 體系下無法跟「cypher-executor HTTP fetch」並存
**善後(已做)**
- ✅ rule 01-tech-stack.mdURL 慣例改為「對內 workers.dev / 對外 arcrun.dev」二元
- ✅ rule 03-component-architecture.md:第一核心概念改為「每個零件 = 兩個 URL」,部署步驟加 dashboard workers.dev enable
- ✅ arcrun.md P0 #9 標 resolved 並 reference 本 incident
- ✅ probe worker 已刪
- ⏳ mira tasks.md 7B.3c 解除阻擋(即將)
## 未來怎麼避免
1. **新 component worker 部署 checklist 強制包含 dashboard workers.dev enable**(rule 03 已加,但實務上靠 dashboard 容易忘,未來可考慮寫 deploy 後驗證 script
2. **不要再加 outbound HTTP fetch 對同 zone hostname** —— cypher-executor 對任何 `*.arcrun.dev` 的 fetch 都該走 workers.dev URL
3. **wrangler dev 是診斷神器** —— 本機跑 prod 同份 src 是區分「環境問題 vs code 問題」最快方法,未來 prod 出怪異行為先跑這個
4. **wrangler tail outcome:ok 不代表沒問題** —— 要看 component trace output 才是真相
5. **Self-hosted fork 文件要明寫**:必須改 `WORKER_SUBDOMAIN` + 所有 component worker dashboard 啟用 workers.devrule 03 已加,BETA_TEST.md 待 onboarding 章節加)
## Reference
- 對應 SDD`matrix/arcrun/.agents/specs/arcrun/arcrun.md` P0 #9
- 規範更新:`matrix/arcrun/.claude/rules/01-tech-stack.md``rules/03-component-architecture.md`
- 受影響 SDD`polaris/mira/.agents/specs/mira-app/tasks.md` 7B.3c(阻擋一整天)
@@ -0,0 +1,113 @@
# 2026-05-13 cypher-executor 巢狀 FOREACH 內層找不到 iterable
> **總耗時**:約 30 分鐘
> **根因**`getIterableFromContext()` 只看當前節點 result + 只看 top-level,巢狀 FOREACH 內層拿不到外層注入的 nested array
> **修法**fallback 找 context;掃 ctx 內 object 取 nested key
> **影響**:任何想做「FOREACH X → 每個 X 內再 FOREACH Y」結構的 workflow 都壞
---
## 症狀
mira `wiki_synthesis` 想做三層樹寫入:
```yaml
flow:
- "classify >> ON_SUCCESS >> create_wiki_page"
- "create_wiki_page >> 對每個 paragraph >> create_paragraph"
- "create_paragraph >> 對每個 triplet >> create_triplet"
```
classify 回 `{ entity, paragraphs: [{ facet, content, triplets: [...] }, ...] }`
- 外層 FOREACH(對每個 paragraph):✓ 正常跑 N 次
- 內層 FOREACH(對每個 triplet):✗ 跑 0 次
KBDB 內結果:N 個 wiki-paragraph 建好,0 個 triplet。
## 兩個獨立根因
### 根因 A`result` only, no fallback to context
外層 FOREACH 跑時:
- `create_paragraph` output: `{ data: { id, ... }, success: true }`
- FOREACH 處理 `>> 對每個 triplet >> create_triplet`iteratorKey = `triplet`
- `getIterableFromContext(result, 'triplet')``result.triplet` / `result.triplets` → 都沒,回 `[]`
`paragraph.triplets` 早就在 ctx(外層 FOREACH 注入了 `paragraph` 物件)。
→ FOREACH 該 fallback 找 context。
### 根因 B:只看 top-level,不看 nested
即使 fallback 找 context`getIterableFromContext(ctx, 'triplet')``ctx.triplet` / `ctx.triplets`**找 top-level**。
但 triplets 在 `ctx.paragraph.triplets`(外層 FOREACH 把 paragraph 整個物件注入 ctx 的 `paragraph` key)。
`getIterableFromContext` 該掃一層 nested。
## 修法
`cypher-executor/src/graph-executor.ts`
```typescript
// FOREACH case
let items = getIterableFromContext(result, iteratorKey);
if (items.length === 0) {
items = getIterableFromContext(context, iteratorKey); // ← A. fallback
}
// getIterableFromContext
function getIterableFromContext(context: unknown, key: string): unknown[] {
if (!context || typeof context !== 'object') return [];
const plural = key + 's';
const obj = context as Record<string, unknown>;
let items = obj[plural] ?? obj[key];
if (!Array.isArray(items)) {
for (const v of Object.values(obj)) { // ← B. 掃 nested
if (v !== null && typeof v === 'object' && !Array.isArray(v)) {
const nested = (v as Record<string, unknown>)[plural] ?? (v as Record<string, unknown>)[key];
if (Array.isArray(nested)) {
items = nested;
break;
}
}
}
}
return Array.isArray(items) ? items : [];
}
```
## 驗證
mira wiki_synthesis 跑「物理 AI」raw → KBDB 內出現:
```
wiki-page "物理 AI" (ef644ec3)
├─ paragraph 00d8f819
│ ├─ triplet: 物理 AI >> 對立於 >> 純數位空間的 AI
│ └─ triplet: 物理 AI >> 提出者 >> Andrej Karpathy
└─ paragraph 9a5b11b7
├─ triplet: leo >> 支持 >> 物理 AI
└─ triplet: 物理 AI >> 需要 >> 傳感器與機器人協同
```
4 個 triplet 都正確接到對應 paragraph parent_id。
## 為什麼這個沒早被踩到
cypher binding 之前的用例都是「一層 FOREACH」(commit e8fca33 wiki workflow 範例就是單層)。mira V2 wiki 結構是首個真正用巢狀 FOREACH 的,所以才剛踩到。
## 未來避免
1. **設計 FOREACH 時假設 iterable 可能在 ctx 任何深度** —— 這次的「掃一層 nested」其實還不夠通用,未來如果有三層 FOREACH(A → B → C → D),可能要遞迴掃。MVP 先一層,需要時擴
2. **驗證 yaml 應該支援巢狀 FOREACH 測試** —— CLI validator 沒擋住巢狀,但「能 parse」不等於「能跑」。未來加 e2e 測試
3. **`對每個 X` 命名建議用單數** —— iteratorKey 自動補 `s` 找 plural`paragraph` → 找 `paragraphs`)。如果 ctx 內的 array 用單數命名(如 `items`),會找不到。MVP 階段建議 yaml 內 array 用複數,FOREACH 用單數,符合慣例
## Reference
- 對應 SDD`matrix/arcrun/.agents/specs/arcrun/arcrun.md` P0 #10 補完 C 段
- 同日先解:
- [2026-05-13-cypher-outbound-522.md](./2026-05-13-cypher-outbound-522.md)
- [2026-05-13-chain-ctx-propagation.md](./2026-05-13-chain-ctx-propagation.md)
- 受影響 SDD`polaris/mira/.agents/specs/mira-app/tasks.md` 7B.3e (V2 樹狀結構)
@@ -0,0 +1,87 @@
# 2026-05-29 credential 解密失敗(兩個 Worker 的 ENCRYPTION_KEY 漂移)
> **症狀**`acr recipe test kbdb`credential 注入)回 HTTP 500`auth_static_key` 回 `credential kbdb_api_key 解密失敗`
> **根因(主)**`arcrun-auth-static-key` Worker 的 `ENCRYPTION_KEY` secret 跟正本(cypher-executor / CLI 用的那把)值不同、格式也不同(44-char base64 vs 64-char hex)。AES-GCM 用錯 key 必然解密失敗。
> **根因(附)**`component-loader.ts` 用 `res.json().catch(() => res.text())` 讀 response body → body 被讀兩次 → `Body has already been used`。
> **修法**(1) `wrangler secret put ENCRYPTION_KEY` 把 auth-static-key 對齊正本 64-hex(2) 新增 `readBodyOnce()` 先取 text 再 parse JSON。
> **影響**BACKLOG 步驟 2credential 注入鏈路)阻擋;Phase 3 降級假零件成 recipe 的前置。
---
## 症狀
`acr recipe test kbdb` 端到端打不到 2xx。直接 probe `auth_static_key`
```
POST https://auth-static-key.arcrun.dev/ {action:"authenticate", api_key:"ak_…", service:"kbdb"}
→ {"success":false, "error":"credential kbdb_api_key 解密失敗", ...}
```
前置都綠(排除誤判方向):
- `auth_recipe:kbdb` 存在、`primitive=static_key`kv_get 命中 410 bytes
- `kbdb_api_key` credential 存在 KVkv_get 命中 108 bytes 的 `{encrypted, iv}`
- 失敗精準落在「解密」這一步
## 定位(key-fingerprint 診斷,只印 SHA-256 前綴,不印 key/明文)
`aesGcmDecrypt``wasi-shim.ts`)暫加:
```
console.error(`[decrypt] ENCRYPTION_KEY sha256_prefix=${fpHex} keyLen=${len}`)
```
deploy auth-static-key + `wrangler tail` 抓到:
| 來源 | keyLen | sha256 前綴 | 格式 |
|---|---|---|---|
| 加密端(CLI `~/.arcrun/config.yaml``encryption_key` | 64 | `fa84f2ce9027` | hex(→32 bytes)✓ |
| 解密端(`arcrun-auth-static-key``ENCRYPTION_KEY` secret | **44** | **`ff219b123c89`** | base64 ✗ |
**兩個 mismatch 同時存在**:值不同 + 格式不同。`hexToUint8Array` 套在 44-char base64 上會解成垃圾 bytesAES-GCM 必失敗。
漂移源頭:`arcrun/.env` 裡的 `ENCRYPTION_KEY` 就是那把錯的 base64`ff219b123c89`),有人拿它去 `wrangler secret put` 設進 auth-static-key。
## 為什麼正本是 64-hex
`/register`register.ts:42)把 `encryption_key: c.env.ENCRYPTION_KEY` 原樣回給用戶 —— 即 **cypher-executor 的** `ENCRYPTION_KEY`。用戶 config 是 64-hex`fa84f2ce9027`),所以正本 = cypher-executor 那把 64-hex。CLI 加密 credential 也用這把。auth-static-key 必須跟它一致才能解開。
診斷用完即移除(`wasi-shim.ts` 還原,git diff 為空)。
## 附帶 bugBody has already been used
修對 key 後,`/execute` 端到端從 500 變成「Node n1 failed: Body has already been used」。
`component-loader.ts``makeRecipeRunner` / `makeAuthRecipeRunner`
```ts
const data = await res.json().catch(() => res.text()); // ✗ res.json() 失敗時 body 已消費
```
KBDB `/health` 回非 JSON(純文字)→ `res.json()` throw → `.catch(() => res.text())` 第二次讀 body → throw。
修法 — 讀一次:
```ts
async function readBodyOnce(res: Response): Promise<unknown> {
const text = await res.text();
try { return JSON.parse(text); } catch { return text; }
}
```
## 修法步驟
1. `cd .component-builds/auth_static_key && wrangler secret put ENCRYPTION_KEY`,貼正本 64-hex= `~/.arcrun/config.yaml``encryption_key`)。**richblack 手動**rule 05runtime secret 不進 CI、CC 不碰)。
2. `component-loader.ts``readBodyOnce()`,兩處 `res.json().catch(...)` 換掉。`tsc --noEmit` 綠,deploy cypher-executor。
3. 修正源頭文件 `arcrun/.env``ENCRYPTION_KEY` 改成 64-hex(避免下次再設錯)。
## 驗證證據
- 直接 probe auth-static-key**HTTP 200**, `success:true`, 產出 `Authorization: Bearer …`
- 端到端 `/execute`**HTTP 200**, trace 乾淨
- auth 確證:直接 curl KBDB `/blocks` 不帶 token → `401 {"error":"Missing token"}`;經 cypher-executor(注入 token)→ 過 auth,進 KBDB handler 回 ZodError(缺 `content`)。**無 401 = token 被接受**。
## 教訓
- **同一把 key 出現在 ≥2 個 Worker 的 secret = 漂移風險**。auth-static-key / auth_service_account / cypher-executor 都讀 `ENCRYPTION_KEY`,靠人各設一次必漂。長期應有單一發放來源或部署時自動同步。
- **debug 加密問題,先比 key 指紋(SHA-256 前綴),不要碰 key 明文**。一個 fingerprint log 就分辨出「值錯」vs「格式錯」vs「資料壞」。
- **`res.json().catch(() => res.text())` 是反模式** —— body 只能讀一次。永遠先 `res.text()``JSON.parse`
@@ -0,0 +1,33 @@
# Incidents
平台/架構級事件 post-mortem。每份檔案 = 一個事件。
不同於 SDD`.agents/specs/`)的「我們要怎麼設計」,incident 記錄「我們實際撞過什麼雷、怎麼診斷、怎麼修、未來怎麼避免」。
## 命名慣例
`YYYY-MM-DD-{short-slug}.md`
例:`2026-05-13-cypher-outbound-522.md`
## 寫的時機
- 花了顯著時間(> 1 小時)才查清根因的問題
- 平台層 / CF / 架構層問題(不是普通 code bug)
- 需要修架構決策或 URL 慣例的問題
- 「以為解決了結果不是」的反覆事件
普通 bug fix 不需要寫 incident。
## 檔案結構建議
每份 incident 至少含:
- **症狀**:怎麼觀察到的
- **誤判路徑**:走過哪些錯方向(含為什麼錯)
- **真相**:根因
- **修法**:實際改了什麼
- **驗證**:怎麼確認解了
- **未來避免**:下次怎麼提早識別
最重要的是**誤判路徑**——這是未來自己(或其他 dev)會踩同樣假設的最好預防。
@@ -0,0 +1,286 @@
# arcrun 封測指南
感謝你參與 arcrun 的封測。
arcrun 是一個讓 AI 和人都能直接讀寫、執行的 workflow 工具。
你的任務是測試核心功能,並記錄任何不符合預期的地方。
---
## 環境安裝(5 分鐘)
```bash
npm install -g arcrun
acr --version # 應顯示 1.1.0 或以上
```
---
## 模式選擇
arcrun 有兩種使用模式:
### Local 模式(不需要帳號,快速試用)
```bash
mkdir my-workflows && cd my-workflows
acr init --local
```
建立 `~/.arcrun/config.yaml`local 模式)和一個 `hello.yaml` 範例。
```bash
acr validate hello.yaml --offline
acr run hello --input input="Hello, arcrun!"
```
預期看到:`"result": "HELLO, ARCRUN!"`
### Standard 模式(需要 API Key,支援 Webhook 部署)
```bash
acr init
```
互動式設定,輸入 email 後自動取得 API Key,存入 `~/.arcrun/config.yaml`
---
## 零件清單
執行以下指令查看所有可用零件:
```bash
acr parts
```
取得單一零件的 config 範本:
```bash
acr parts scaffold string_ops
acr parts scaffold http_request
acr parts scaffold gmail # 含 credentials.yaml 範本
```
---
## 可用零件(21 個,不需要帳號)
### 字串操作 — `string_ops`
```yaml
config:
my_node:
component: string_ops
operation: upper # upper / lower / trim / length / replace / split / join
```
### 數字運算 — `number_ops`
```yaml
config:
my_node:
component: number_ops
operation: add
b: 10 # 加上 10
```
支援:`add` / `sub` / `mul` / `div` / `round` / `floor` / `ceil` / `abs`
### HTTP 請求 — `http_request`
```yaml
config:
my_node:
component: http_request
method: GET # GET / POST / PUT / DELETE
```
```bash
acr run notify --input url="https://httpbin.org/get"
```
### 其他零件
```
if_control 條件分支(ON_SUCCESS / ON_FAIL 路由)
switch 多分支條件
foreach_control 迭代陣列
filter 過濾陣列
set 設定固定值到 context
array_ops 陣列操作(push / pop / slice
date_ops 日期操作(now / format / diff
validate_json 驗證 JSON Schema
ai_transform_compile / ai_transform_run AI 自然語言轉換
```
---
## 動態參數 `{{variable}}`
config 裡的字串欄位支援 `{{variable}}`,從 `--input` 取值:
```yaml
# flexible.yaml
name: flexible
flow:
- "input >> ON_SUCCESS >> process"
config:
process:
component: string_ops
operation: "{{op}}"
```
```bash
acr run flexible --input input="hello" --input op=upper # → HELLO
acr run flexible --input input="HELLO" --input op=lower # → hello
```
---
## 錯誤路由(ON_FAIL
```yaml
# safe-fetch.yaml
name: safe-fetch
flow:
- "input >> ON_SUCCESS >> fetch"
- "fetch >> ON_FAIL >> fallback"
config:
fetch:
component: http_request
method: GET
fallback:
component: string_ops
operation: upper
```
```bash
# 故意讓 fetch 失敗,觸發 fallback
acr run safe-fetch \
--input url="https://invalid.domain.xyz" \
--input input="fallback triggered"
```
---
## 中文語意
flow 支援中文關係詞:
```yaml
flow:
- "輸入 >> 完成後 >> 轉換"
- "轉換 >> 失敗時 >> 錯誤處理"
```
---
## Webhook 部署(Standard 模式)
讓外部網頁或服務能觸發你的 workflow:
```bash
# 部署 workflow
acr push my-workflow.yaml
```
輸出範例:
```
✓ "my-workflow" 已部署
Webhook URLhttps://cypher.arcrun.dev/webhooks/named/my-workflow/trigger
需帶 HeaderX-Arcrun-API-Key: ak_...
curl 觸發範例:
curl -X POST https://cypher.arcrun.dev/webhooks/named/my-workflow/trigger \
-H 'X-Arcrun-API-Key: ak_your-key' \
-H 'Content-Type: application/json' \
-d '{"message": "hello"}'
```
---
## API Recipe(整合外部服務)
不需要 deploy Worker,只要上傳 recipe YAML
```bash
acr recipe push my-recipe.yaml
acr recipe list
acr recipe delete rec_xxxxxxxx
```
Recipe 上傳後會得到 `rec_xxxxxxxx` hash,可直接在 workflow config 的 `component` 欄位使用。
---
## Credential 管理(Standard 模式)
需要帶 token 的零件(gmail、telegram、notion 等)可以提前上傳 credential,執行 workflow 時自動注入。
**加密金鑰在 `acr init` 時已自動取得並存入 `~/.arcrun/config.yaml`,不需要手動設定。**
**步驟一:查看某服務需要哪些 credential**
```bash
acr auth-recipe scaffold notion # 輸出 credentials.yaml 範本 + workflow 使用範例
acr auth-recipe list # 列出所有支援的服務(20 個)
```
**步驟二:建立 credentials.yaml**(參考 scaffold 的輸出):
```yaml
# 範例:Notion
notion_token: "secret_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
# 範例:Telegram Bot
telegram_bot_token: "123456789:your-bot-token"
```
**步驟三:上傳**
```bash
acr creds push credentials.yaml
```
上傳後執行 workflow 時,tokens 自動注入,不需要在 `--input` 手動帶。
### 支援的第三方服務(20 個)
```bash
acr auth-recipe list
```
輸出:Notion、Slack、GitHub、OpenAI、Anthropic、Airtable、Discord、Stripe、Twilio、SendGrid、HubSpot、Linear、Shopify、Resend、Supabase、Typeform、Jira、Google SheetsService Account)、GmailService Account)、Google DriveService Account
---
## 回饋格式
請把你的觀察記錄在 `FEEDBACK.md`,格式不限,但希望包含:
1. **成功的地方** — 哪些功能符合預期?
2. **失敗的地方** — 錯誤訊息是什麼?步驟是?
3. **困惑的地方** — 不知道怎麼用、文件不清楚的地方
4. **想要的功能** — 你覺得少了什麼
---
## 已知限制
- `number_ops` 的數字參數(`a``b`)若從 `--input` 帶入為字串,需要零件自行做型別轉換(目前已支援)
- `ON_FAIL` 觸發時,fallback 節點收到的 context 包含上游的錯誤物件(`{success: false, ...}`
- 多節點串連時,context 為 flat merge,上游的 `data.result` 會直接合併到頂層
- `if_control` 條件為 false 時,不執行任何下游節點(沒有明確的 else 分支)
---
## 有問題?
遇到任何問題直接問。你的 API Key 是確定性的,只要用同一個 email 呼叫 `/register` 就能拿回來:
```bash
curl -X POST https://cypher.arcrun.dev/register \
-H "Content-Type: application/json" \
-d '{"email":"your@email.com"}'
```
@@ -0,0 +1,72 @@
# 交付前自測 Checklistpre-customer)— 2026-06-07
> 給 **你(人)** 在交給客戶前跑一次的精簡清單,不是給 Haiku 操盤的詳細壓測(那份在
> 壓測-recipe-library-2026-06-07.md)。順序照客戶真實旅程。**任一項 ❌ = 不能交付。**
> 過關標準都是客觀證據(HTTP 2xx / D1 數字 / 檔案存在),禁口頭過關。
---
## 1. 雲端後端活著(最快,先確認 deploy 沒掛)
- [ ] `curl https://arcrun-kbdb.uncle6-me.workers.dev/health``{"ok":true}`
- [ ] `curl https://cypher.arcrun.dev/public-recipes?q=gmail``{"found":true,...}`
- [ ] `curl https://cypher.arcrun.dev/health`(或任一既有端點)→ 200
- [ ] npm 上有最新 CLI`npm view arcrun version` → 1.3.2(或更新)
## 2. 冷啟動:空專案能裝起來(客戶第一步)
- [ ] 開**空目錄**`npm i -g arcrun``acr --version` 出版本
- [ ] `acr init`(local 模式)→ 不報錯、寫出 config
- [ ] 跟著它印的「下一步」跑 hello workflow → `acr run hello` 有輸出
- [ ] self-hosted 路徑)`acr init --self-hosted` 無 wrangler 時 → 錯訊清楚教裝 wrangler
## 3. 環境設定(self-hosted,客戶要用自己 CF
- [ ] `acr init --self-hosted --account-id X --api-token Y` → 建 KV + D1 + deploy 成功
- [ ] 印出的「下一步①②」可照抄:.envNAMESPACE+ENCRYPTION_KEY+ wrangler secret put
- [ ] D1 建出來且套了 migration`/recipe-stats/x` 回 stat 結構)
- [ ] 免綁卡(全程沒被要信用卡)
## 4. Recipe 公庫/私庫(本次新功能,重點測)
- [ ] `acr recipe search gmail` → 列出 recipe 含 author/market_stat
- [ ] `acr recipe search 不存在的xyz` → found:false + 創作引導 hint
- [ ] `acr recipe pull gmail_send` → 拉進私庫;`acr recipe list` 看得到
- [ ] 自製 recipe `acr recipe push` → 私庫有
- [ ] `acr recipe submit-p <id>`**跳暴露同意**(未同意/非互動擋住),同意後投稿成功
- [ ] 同 canonical 不同作者 submit-p 兩次 → 公庫並存兩筆(非覆蓋)
## 5. 做一件真實的事(端到端,客戶的目的)
- [ ] 建一個真實 workflow(如 webhook → http_request → 輸出),`acr push` + `acr run` 跑通回 2xx
- [ ] 用到 recipe 的 workflow 跑完 → `curl kbdb/recipe-stats/{uuid}` success_count +1
- [ ] 缺 credential 時 → 誠實標「未驗收:缺 X」,不假裝成功(401/403 不當 bug
## 6. 兩介面一致(CLI + MCPAI 客戶會用 MCP
- [ ] MCP 已裝(`acr mcp-setup` 或 init 自動)→ `.mcp.json` 存在
- [ ] MCP `arcrun_recipe_search` 回的 = CLI `acr recipe search` 回的(同一組)
- [ ] CLI pull 後 MCP `arcrun_recipe_list` 看得到(同一私庫帳號)
⚠️ 若 self-hosted 的 MCP 連到平台而非自己 cypher → 記下(§5.2 已知違反待修)
## 7. 回歸(沒把舊功能弄壞)
- [ ] 既有種子 recipegmail/telegram)的 workflow 跑通(UUID 重構沒破執行鏈)
- [ ] `acr recipe list` 無重複項(同 canonical 舊 key+uuid 兩筆)
- [ ] 既有 workflow push/list/run 全正常
## 8. 誠實性 / 安全界線(交付前必守)
- [ ] 暴露動作(submit-p / push webhook)都有人類明示同意關卡
- [ ] 非互動環境(你直跑)→ 需確認處會停下,不自己偽造同意
- [ ] 任何「未實作/缺 credential」誠實回 success:false 或標未驗收,無假綠
---
## 交付判定
- 全 ✅ → 可交付。
- 任一 ❌ → 修掉再交(記下是哪項)。
- ⚠️(已知違反,如 §5.2 MCP account-source)→ 寫進交付說明的「已知限制」,不假裝沒有。
**禁假綠**:沒實際看到證據的項目標「未測」,不要勾 ✅。
@@ -0,0 +1,162 @@
# 壓測 Test Case — Recipe 公庫/私庫機制 + UUID2026-06-07 deploy 後)
> 對象:本次上線的 kbdb-base §7.5(公庫/私庫雙向、UUID 身份、市場數據)+ 回歸。
>
> **操盤模型:全程 Haiku。Haiku 能搞定是「設計目標」不只是壓測手段(richblack 2026-06-07)。**
> 理由:arcrun 價值=比直接開發容易→用戶省 token+省時間+可重複用。若只有 Sonnet 能驅動 arcrun
> 「省」就不成立(Sonnet 貴)。**Haiku 就能搞定才證明 arcrun 真降低門檻**。前瞻:未來要接 Gemini /
> 更弱模型門檻只會更高,現在用最弱的 Haiku 把介面磨到夠白痴化,未來接別的模型才不會更難。
>
> **∴ Haiku 撞牆 = 設計缺陷訊號(不是「換 Sonnet 解決」),撞牆點就是要修介面的地方。**
> 只有真的撞牆才**暫時**升 Sonnet 跑同一 case,用來判別「是介面問題(Haiku/Sonnet 都該過但 Haiku 過不了→修介面)
> vs 模型能力本質差異」。修完介面再用 Haiku 重測該 case。
> arcrun 是 AI 呼叫的工具 → 壓測打 floor 不打 ceiling。
>
> 介面:兩條都要測(CLI `acr` + MCP tools),驗薄殼一致性(rule 07 §5)。
> 判定原則(mindset §7):完成=客觀證據(HTTP status / D1 數據 / 2xx),不是口頭宣布。
---
## Cold. 冷啟動:空專案 → 能跑(真實第一次體驗,最容易撞牆)— Haiku
> 真實起點:用戶在 VSCode 開一個**空白專案**,叫 Haiku「幫我做 X 工作」。
> 此時 **arcrun 沒裝、環境沒建、CF 沒設**。Haiku 要從零把環境建起來才談得上做事。
> 這是 self-hosted-init.md 流程的 dogfood。**裝不起來後面全白搭 → 這組是壓測 floor 的 floor。**
>
> 測法:給 Haiku 一句話需求(如「幫我做一個每天抓 RSS 存到 Google Sheet 的工作流」),
> **不給任何安裝指示**,看它能否自己摸出完整環境建置。觀察它卡在哪 = 介面要磨白痴化的地方。
| # | 觀察點 | 預期(Haiku 自己走通) | 判定(撞牆=介面缺陷) |
|---|--------|------|------|
| Cold.1 | Haiku 是否知道「要先裝 arcrun」 | 自己找到 `npm i -g arcrun` 或從 MCP/README 得知 | 卡 → 入口可發現性不足 |
| Cold.2 | 選模式:local / standard / self-hosted | Haiku 能依需求選對(要存 credential→standard/self-hosted;純試→local | 卡 → `acr init` 模式說明不夠白痴 |
| Cold.3 | self-hosted 前置:wrangler 未裝時 | 錯訊「npm i -g wrangler 後重跑」→ Haiku 照做 | 卡 → 前置提示不可自癒 |
| Cold.4 | `acr init --self-hosted` 非互動參數 | Haiku 知道要帶 --account-id/--api-token(或被引導) | 卡 → 非互動路徑不明 |
| Cold.5 | 建 .envNAMESPACE + ENCRYPTION_KEY | Haiku 照「下一步①」生成 key 並寫 .env | 卡 → key 生成指令是否現成可抄 |
| Cold.6 | wrangler secret put ENCRYPTION_KEY | Haiku 照「下一步②」對各 worker 設 secret | 卡 → 多 worker 共用 key 是否講清楚([[encryption-key-drift-trap]] |
| Cold.7 | MCP / harness 安裝(acr mcp-setup / install-harness | 自動或被引導補上 | 卡 → 補裝路徑是否被提示 |
| Cold.8 | 環境就緒後,Haiku 能接著做原始需求 | 不卡在環境、進入實際 workflow 建置 | **冷啟動到能做事的端到端卡點數** |
> **核心觀察**:Haiku 從「一句話需求 + 空專案」到「環境就緒能做事」,**全程靠 CLI 輸出 + 錯誤訊息 + MCP 工具描述自我引導**,
> 不靠人類補指示、不靠強模型腦補。每個卡點記下 = arcrun onboarding 要磨白痴化的清單。
> 非互動雷區:mindset §7「非 TTY 直跑就拒絕、不自塞 flag 假裝人類同意」——Haiku 遇到需人類確認處(建 CF 資源、暴露)
> 應停下請用戶確認,不自己偽造同意。測這個界線有沒有守住。
---
## 0. 前置 / 環境健康(回歸,每輪先跑)
| # | 操作 | 預期 | 判定 |
|---|------|------|------|
| 0.1 | `curl https://arcrun-kbdb.uncle6-me.workers.dev/health` | `{"ok":true}` | KBDB 活著 |
| 0.2 | `curl https://cypher.arcrun.dev/public-recipes?q=gmail` | `{found:true, recipes:[...]}` | 公庫端點上線 |
| 0.3 | `curl https://arcrun-kbdb.uncle6-me.workers.dev/recipe-stats/x` | `{success:true, stat:{...0}}` | D1 三表通 |
---
## A. 公庫搜尋 + 落空創作引導(§7.5.6)— Haiku
測「AI 找 recipe,公庫沒有時是否被正確引導去自己做」。
| # | 操作(CLI / MCP) | 預期 | 判定(暴露什麼) |
|---|------|------|------|
| A.1 | `acr recipe search gmail` / `arcrun_recipe_search{query:"gmail"}` | found:true,列 gmail_send 等,各帶 author/market_stat | 搜尋可用 |
| A.2 | 搜一個一定不存在的:`acr recipe search zzz_nonexistent_xyz` | **found:false + hint「可自己做一個投稿成為作者」** | **落空引導是否讓 AI 知道下一步**(不是回空陣列卡住) |
| A.3 | 接 A.2:操盤 AI 讀到 hint 後,**是否自己提議「那我做一個 recipe」** | AI 主動走向 push→submit-p(非停手說「找不到」) | **§7.5.6 閉環是否被 AI 接住**(這是核心壓測點) |
> A.3 是最關鍵的 Haiku 測點:弱模型若能靠 hint 自己走向創作,代表引導設計成功。
---
## B. 公→私 pull(§7.5.3 流1)— Haiku
| # | 操作 | 預期 | 判定 |
|---|------|------|------|
| B.1 | `acr recipe pull gmail_send` / `arcrun_recipe_pull{canonical_id:"gmail_send"}` | ✓ 拉進私庫,提示可用 component: gmail_send | pull 寫進私庫成功 |
| B.2 | `acr recipe list`(私庫)→ 應出現 gmail_send | 私庫有這筆 | pull 確實落地(不是只回成功訊息) |
| B.3 | pull 一個不存在的 `acr recipe pull zzz_nonexistent` | found:false + 創作引導 | pull 落空也引導(不報模糊錯誤) |
| B.4 | 指定作者 `acr recipe pull gmail_send --author=system` | 取 system 版本 | author 參數生效 |
---
## C. 私→公 submit-p + UUID 多作者並存(§7.5.5 app-store)— Haiku(撞牆才升 Sonnet 判別)
測 app-store 模型:同 canonical 多作者並存、submit=新增不覆蓋。
| # | 操作 | 預期 | 判定(暴露什麼) |
|---|------|------|------|
| C.1 | 建一個自製 recipe push 私庫(如 `my_test_api`endpoint 指 httpbin.org/post | ✓ 私庫有 | push 可用 |
| C.2 | `acr recipe submit-p my_test_api`(**需暴露同意**) | 提示暴露警示 → 同意後投稿公庫、領新 uuid | **暴露同意是否擋住**(mindset §6);非互動/未同意是否拒絕 |
| C.3 | 第二次用**不同作者**再 submit-p 同 canonical(模擬 John 版) | 公庫**並存兩筆**同 canonical 不同 uuid/author(非覆蓋) | **app-store 模型驗證**:覆蓋 vs 新增 |
| C.4 | `acr recipe search my_test_api` | 回**多筆**同名不同作者,各帶 market_stat | 多作者並存可被搜到 |
---
## D. 市場數據 per-uuid(§7.5.h)— Haiku(要跑工作流;撞牆才升 Sonnet)
測「跑工作流 → recipe 成功/失敗記到 KBDB per-uuid → 影響市場選擇」。
| # | 操作 | 預期 | 判定 |
|---|------|------|------|
| D.1 | pull 一個能實打通的 recipe(或用 httpbin 自製),建 workflow`acr run` 跑成功 | workflow 回 2xx | 工作流能跑 |
| D.2 | 跑完後 `curl kbdb/recipe-stats/{該 recipe uuid}` | success_count +1 | **5.1 成功記錄落 D1**per-uuid 非 canonical |
| D.3 | 故意讓 recipe 打不通(壞 endpoint)再跑,查 stat | failure_count +1 | 失敗也記、且區分 |
| D.4 | 同 canonical 兩作者版本各跑幾次成功率不同 → `recipe search` | market_stat 區分兩 uuid(不是同一份) | **§7.5.h per-uuid 真正生效**Leo/John 可區分) |
---
## E. 回歸 — 既有能力沒被 UUID 改動破壞(§7.5.f 向後相容)— Haiku
UUID key 重構最大風險 = 破執行鏈。必測既有 recipe 執行不掛。
| # | 操作 | 預期 | 判定 |
|---|------|------|------|
| E.1 | 用一個既有種子 recipegmail/telegram)建 workflow `acr run` | 正常執行(resolveRecipe 向後相容) | **執行鏈沒被 key 重構破壞** |
| E.2 | `acr recipe list` 不出現重複(同 canonical 舊 key + uuid 兩筆) | dedup 正確 | GET dedup 生效 |
| E.3 | `acr recipe delete {某 recipe}` 後再 list | 該 recipe 消失、索引清乾淨 | DELETE 清 uuid+索引 |
| E.4 | 既有 workflow push/list/run(與 recipe 無關) | 全正常 | 沒波及無關功能 |
---
## F. 薄殼一致性(rule 07 §5)— Haiku
同一能力 CLI 和 MCP 走出**同樣結果**(驗薄殼不漂移)。
| # | 操作 | 預期 | 判定 |
|---|------|------|------|
| F.1 | `acr recipe search gmail` vs `arcrun_recipe_search{query:"gmail"}` | 兩者回**同一組** recipe | CLI/MCP 不漂移 |
| F.2 | CLI pull 後,MCP `arcrun_recipe_list` 看得到(反之亦然) | 同一私庫、同一帳號 | **帳號來源統一**(§5.3self-hosted 帳號是否一致) |
| F.3 | MCP submit-p 的 exposure_consent 把關 = CLI 的暴露同意 | 兩者都擋未同意 | 暴露把關一致 |
> F.2 會踩到已知違反(§5.2 MCP account-source):若 self-hosted 用 MCP 連到平台 cypher 而非自己的,
> CLI 和 MCP 會看到不同私庫 → **這是預期會暴露的問題**,記下不算 bug 是待修項。
---
## G. 邊界 / 異常(誠實性測試,mindset §7)— Haiku
| # | 操作 | 預期 | 判定 |
|---|------|------|------|
| G.1 | submit-p 缺 endpoint / 缺 canonical_id | 400 明確錯 | 不假綠、錯訊清楚 |
| G.2 | pull 後私庫改該 recipe 再 submit-p | author 變自己、不冒原作者(derived_from 溯源) | **不冒名**(§7.5.5 |
| G.3 | 缺 credential 的 recipe 跑 workflow | 誠實標「未驗收:缺 X」、401/403 不當 arcrun bug | 不 mock 假綠(mindset §3/§7 |
| G.4 | migrate-uuid 重跑一次 | skipped 全部、migrated 0(冪等) | 重跑安全 |
---
## 判定總表(壓測完填)
每個 case 標:✅ 通過(附證據:HTTP status / D1 數字 / 截圖)/ ⚠️ 暴露問題(描述)/ ❌ 失敗。
**禁假綠**:沒實際拿到 2xx/數據就標「未驗收:缺 X」,不口頭宣布通過。
**重點觀察(全程 Haiku**
0. **Cold.1-8 冷啟動:Haiku 從空專案能否自己把環境裝起來**?← floor 的 floor,裝不起來後面全白搭
1. A.3 落空→Haiku 是否自己走向創作(§7.5.6 閉環被接住)?← 設計目標核心
2. 錯誤訊息的 next_actions 是否讓 Haiku 自癒(不靠更強模型腦補)?
3. F.2 CLI/MCP 帳號是否一致(§5.2 已知違反會在此暴露)?
4. D.4 多作者市場數據是否真正區分?
5. 複雜多步(pull→改→submit-p→跑→看市場)Haiku 能否一氣呵成?
**撞牆處理**Haiku 過不了某 case → 先判「是介面缺陷還是模型本質限制」:
暫時升 Sonnet 跑同一 case。Sonnet 過、Haiku 不過 → **介面缺陷**(修介面後 Haiku 重測)。
兩者都不過 → 功能 bug。記錄撞牆點 = arcrun 要磨白痴化的地方(前瞻 Gemini/更弱模型)。