# Pending Changes（規格變更緩衝區）

> 規則來源：`SDD-LIFECYCLE.md` 第 3、4 條。
> 規格層變更（核心設計/方向改變）**只有這一條路**：CC 把 change proposal 寫進「待裁決」——
> 變更摘要與觸發原因＋影響分析（現行 SDD 哪些任務作廢/修改/不受影響/尚未完成）——然後**停止**，
> 等使用者明說「confirm」才依第 4 條開新 SDD；沒 confirm 就繼續依現行 SDD 工作。
> 多個 proposal 可並存，由人一次裁決。本檔不是 SDD，不掛 status。

## 待裁決

### P？｜認證儲存要不要搬回 D1/KV——resource-reuse 上線後，D61 想解的病可能已被更通用的機制解掉（2026-08-14）

**觸發**：`arcrun-rag#99`／`inkstone/Arcrun#119`——全新安裝卡在「建立第一個帳號」，leo 本人與封測者
都實撞。根因：D61（2026-08-10，`c4cee35`）把認證資料搬到 CF Workers per-script Secrets，需要 cypher
自己持有 `env.CF_SECRETS_API_TOKEN`；這把 token 從安裝那天起就沒被種過（07-29 已知缺口），於是**每
一台全新實例**永遠 `writable:false`。

**已做的止血只解掉一格，其餘留債**：先做的修法（安裝精靈把裝機當下自己還有效的 OAuth token 隨
`/console/setup`／`/portal/admin/bootstrap` 請求傳給 cypher，用這一次、不落地）讓「建立第一個帳號」
能通，但 `console-auth.ts:216`（`/console/setup/reset`）、`/portal/admin/users`、`POST /credentials`
（存 API 金鑰）、`/portal/password/change`——**這些路徑安裝精靈都不在場，永遠拿不到那個表頭**，裝完
之後這台實例對這些操作永遠 `writable:false`。leo 的判準是「別人不再出這個問題」且「後面不能留
債」，這不合格。

**排除掉的兩個止血選項（留給下一個人看，避免重新踩一次）**：
- **A. 用 OAuth 權限「代鑄」一把不過期的 CF API Token**（呼叫 `POST /user/tokens`）——需要新的
  OAuth scope（目前 `OAUTH_SCOPES` 六項裡沒有「管理 API Tokens」這項），會讓用戶授權畫面多一條
  同意項，是產品面決定，未經查證 CF 的 OAuth scope 目錄裡那個 scope 確切叫什麼。
- **B. 讓用戶自己去 CF Dashboard 建一把 Token 貼進安裝精靈**——不需要新 scope，但打破「一鍵安裝」，
  也是產品面決定。
- **（已明確排除）把 OAuth access_token 直接當長效 secret 存**——access_token 16 小時過期，
  refresh_token 是 rotation 制、單次有效，cypher 沒有能力自己做 OAuth refresh（那等於要 cypher 自己
  再長一份 OAuth client，且會跟安裝器搶同一條 refresh 鏈的競態）。存了會變成「`writable` 顯示
  `true`，但 16 小時後開始靜默 401，且沒有任何機制會發現」——比現在「明確顯示 `writable:false`」更
  糟，直接違反 D61 commit 自己引用的 #10「寧可明顯失敗，不要靜默錯置」。

**本提案（C）：查證結果——技術前提成立，有具體證據，不是推論**

D61 的病根是「重裝時 binding 被安裝器照名字重新指到新建的空資源」（console 帳密住 `SESSIONS_KV`、
portal_user 住 KBDB 的 D1）。**這個病根本身已經在 2026-08-13 被一個更早、更通用、範圍更廣的機制
解掉**——`shared/resource-rule`（`Arcrun#97`／`arcrun-rag#87`：「已部署的 worker 綁著什麼，那就是
事實 → 原封不動沿用，不管叫什麼名字」）。現在**每一次**安裝或更新都會先跑這條規則，涵蓋所有
`kv_namespace`／`d1` binding，不分是不是認證用的——不是為了本提案才要新造的機制，是已經在保護
workflows／recipes／總圖的既有基礎設施。

核實證據（讀碼＋讀已生成的成品，非猜測）：
- `.worker-builds/manifest.json`（本次為驗證這個提案，用 `scripts/build-worker-artifacts.mjs`
  重打的真實成品）：`arcrun-cypher-executor.requires.kv` 含 `"SESSIONS_KV"`；`requires.d1` =
  `{binding:"CREDENTIALS_DB", database_name:"arcrun-kbdb"}`——與 `arcrun-kbdb` worker 自己的 `DB`
  binding 指向同一個資料庫名（portal_user 的家）。
- `installer/oauth-prototype/worker.js:1762`：`runInstall` 真的呼叫
  `resolveResourcesByRule(token, accountId, manifestRequirements(manifest, baseName, true), mode)`，
  `mode ∈ {'init','update'}`——**每次安裝/更新都會走這條路，不是選配**。
- `manifestRequirements()`（`:818-843`）把 `requires.kv`／`requires.d1` 轉成規則輸入，
  `SESSIONS_KV`／`CREDENTIALS_DB` 均未被排除。
- `shared/resource-rule/rule.mjs` 對 `kv_namespace`／`d1` 一視同仁，不分 binding 名字；同
  `createName`（`arcrun-kbdb`）的 d1 需求會被 `shareSameResource` 收斂成同一顆。

**時序細節（誠實揭露，降低但不推翻結論）**：`Arcrun#97` 的觸發事故發生在 2026-08-12（比 D61 晚兩
天），當時症狀含「portal 登出」——代表 D61 上線後、resource-reuse 落地前，這類事故確實還發生過一
次。無法確認 leo21c 那次事故當下實際跑的是 D61 前還是後的 bundle（merge 進 main ≠ 部署到任何一台
實例，這個落差在別處也反覆出現）。不論是哪一種：**resource-reuse 落地之後（08-13 起）**，這個機制
對 `SESSIONS_KV`／KBDB D1 的保護，與 D61 對認證儲存的保護，範圍是重疊的——這件事在程式碼層級是確
定的，不是本節不確定的部分。

**建議做法**（confirm 後才動，這裡先寫方向不寫實作細節）：
1. 認證資料搬回 D1（走 KBDB base API，比照 D61 之前的舊機制）＋ console 帳密搬回 `SESSIONS_KV`——
   寫入走 Workers binding，不需要任何外部 CF token，零過期、零新 OAuth scope、零手動步驟。
2. D61 加的三個「明顯失敗」機制**保留**，只是底層儲存換回 binding：讀不到不算密碼錯且不計入鎖定
   （`auth_store_empty`）、`/console/setup` 遇既有帳號說清楚密碼沒被採用、`/health`／
   `/console/auth-status` 吐儲存狀態。
3. `portal-auth-store.ts` 整份（CF Secrets 分片機制）——需要人決定留著當可切換的備援路徑，還是直
   接移除單一化；本提案不預設答案。

**影響分析（依 SDD-LIFECYCLE 第 3 條格式）**：
- **現行 active SDD**：`system-dev/docs/3-specs/portal-auth/`——本提案不影響該 SDD 已完成的任務
  （P2 bootstrap／登入／role 閘等），只改「認證資料住哪」這一層的實作，功能行為（登入／bootstrap／
  改密碼的 API 契約）不變。
- **尚未完成、待搬移**：本提案本身沒有現成 tasks 可搬，confirm 後在新工作項目下處理。
- **既有相關**：`arcrun-rag#99`／`inkstone/Arcrun#119`（本次 P0 症狀單，confirm 後統一收斂修
  法）；D61（`Leo/arcrun-rag#55`）本身——confirm 即等於部分撤銷 D61 的儲存層決定（保留其明顯失敗
  語意），需要在 ADR 記一筆「D61 補充/取代」。
- **已裝好、正在跑 D61（CF Secrets 認證儲存）的實例怎麼辦**：需要一次性遷移（讀出 CF Secrets 裡的
  JSON → 寫回 D1／KV），且要處理「這把 CF Secrets 還留著沒人管」的收尾（刪除或忽略均可，留著不影
  響正確性只是佔用 secret 額度）。confirm 後另立遷移步驟，不在本提案本身寫死做法。

**信心水位（誠實標，不假裝已驗證）**：上面「技術前提成立」的證據全部來自**靜態推論**——讀原始
碼、讀本機重打出的 `.worker-builds/manifest.json`、讀呼叫鏈——**沒有跑過一次真的部署＋動態驗
證**。要把信心水位補到「實測過」，得先過 D20（部署 stage）才做得到，而部署本身要 leo 開閘 ⇒
不能把它當成「先驗證才准寫提案」的前置，否則變成「等閘才能寫提案、寫了提案才要得到閘」的死結
（總管裁決：先寫提案，把這段誠實揭露）。

**怎麼驗（confirm 後、真正動工前必跑，依 D84 之二三段式，不能只驗 update）**：
1. **測 update**：已裝好、正在跑 D61 的實例，走過一次性遷移後，`/console/auth-status` 回
   `writable:true`，既有帳密登入不受影響。
2. **uninstall 拆掉後從乾淨狀態模擬第一次安裝**：全新帳號走一次安裝精靈，`/console/setup` 建帳密
   成功、`/portal/admin/bootstrap` 建第一個 admin 成功，**且不需要安裝精靈傳任何表頭**（因為寫入
   走 binding，不再依賴 CF_SECRETS_API_TOKEN 這條路）。
3. **③ 目前做不到，如實標記**：D84 之二要求「除了測 update 還要用 uninstaller 刪除後再模擬第一次
   安裝」——**uninstaller 現在還不存在**（另一條 subagent 正在做，`Arcrun#120` 家族）。本提案的驗
   收要等 uninstaller 落地才補得齊第 2 步的「先真的拆過一次」那個誠實版本；在那之前，第 2 步只能
   用「全新 CF 帳號」代替「先 uninstall 再裝」，兩者不完全等價（後者才驗得到「舊資源殘留時是否誤
   沿用」這個 resource-rule 專門處理的情境）。

**⏸ 等 leo／總管 confirm 才動**（依 SDD 生命週期鐵律第 3 條）。confirm 前，止血用的表頭修法
（`fix/install-bootstrap-cf-token-99`／`fix/installer-pass-install-token-99`，已 push 分支未合併）
繼續作為短期止血候選，兩者不衝突——表頭修法讓「建第一個帳號」現在能用，即使本提案要花時間走完整
個遷移流程也不擋這條路先出。

### P2｜fan-out 並行執行（一個節點的多條出邊目前是循序跑）— 2026-08-03

**觸發**：leo 08-03 原話——「這是在測試中的計畫，**希望體驗很好**，我發現用 gemma4 的反應非常慢。」
把 rag_chat 的生成端換成 Workers AI 之後（16.87 s → 2.2 s），一量才發現
**慢的大頭根本不是 LLM**。

**實測（1.4.4 實例，逐段疊加、每段取兩次的較快值）**
```
① prep（code）                    208 ms
② +kw_search                    1,383 ms   (+1,175)
③ +sem_search                   3,240 ms   (+1,857)
④ +fetch_triplets               5,198 ms   (+1,958)
⑤ +fetch_blocks a/b/c           7,793 ms   (+2,595)
⑥ +assemble（code）              8,421 ms   (+628)
   +ask_llm（Workers AI）        ≈10,400 ms (+2,000)
```
⇒ **6 個 KBDB 檢索節點合計約 7.6 s，佔全鏈 73%；LLM 只佔 20%。**
而這 6 個節點**彼此完全獨立**（kw／sem／triplets／blocks×3 誰都不吃誰的輸出），
只是因為 flow 被寫成一條鏈才一個接一個跑。

**根因在引擎，不在 workflow**（`cypher-executor/src/graph-executor.ts`）：
- 起點節點**已經是並行**的（`:85` `Promise.all(startNodes.map(...))`）
- fan-in **已經支援**（`:78-84` 入度 >1 的節點等所有上游到齊）
- 但**出邊是循序的**（`:427` `for (const edge of outEdges)`，迴圈內 `await executeNode`）
  ⇒ 一個節點分岔出 6 條 `ON_SUCCESS`，是一條跑完才跑下一條。
⇒ **改 workflow 解不了**：把 6 個節點都掛在 prep 底下，只是把「鏈」變成「6 條循序出邊」，一樣慢。

**提案（擇一，我建議 A）**

- **A. 出邊並行 ＋ 沿用既有 fan-in**：把 `:427` 那個迴圈改成
  「同型別、無條件分支的出邊」用 `Promise.all` 併發，其餘（`ON_TRUE`／`ON_FALSE`／`ON_BRANCH`／`ON_FAIL`）
  維持原樣。rag_chat 的 6 個檢索節點改掛在 prep 底下、匯流進 assemble（入度 6，fan-in 現成）
  ⇒ 預估 **7.6 s → 約 2 s**，全鏈約 **10.4 s → 4.5 s**。
  ⚠️ **這是引擎核心、風險最高**（照本 repo 自己的規矩：先寫測試再改）。
  需要先講清楚的語意：多條邊並行時 `result` 怎麼合併（現行是「後一條覆蓋前一條」的隱含語意）、
  任一條失敗時的行為、trace 的順序。**既有 workflow 行為必須零變化，且要跑一次證明。**

- **B. 只砍節點數（完全不動引擎）**：`kw_search`／`sem_search` 在 v2 計分裡只是小加分
  （kw 命中 +2、sem 前十 +0.4，主導權在 IDF 字面重疊）⇒ 拿掉這兩個節點可省約 3 s。
  代價是**檢索品質**（少了兩個佐證訊號），屬產品取捨，不是我能裁的。

- **C. 不做**：接受目前的 10 s。

**影響分析**
- 現行 active SDD `workflow-discovery`：A 屬引擎能力，與 3.9（`ON_TRUE`／`ON_FALSE` 走邊）同一塊，
  是**新增任務**，不作廢任何既有任務。
- B 只動 `arcrun-rag/workflows/rag-chat.local.yaml`，不碰本 repo。
- 三者都**不影響**已完成的 3.12（recipe 三層）與本次 Workers AI 換源。

**⏸ 停在這裡等 leo 裁**：回「A」我就先寫測試再改引擎；回「B」我只改 workflow；回「C」就記著不做。

### P1｜host fn 二進位通道（解開「零件無法處理非文字檔」的框架級限制）— 2026-07-27

**觸發**：leo 07-27 原話（arcrun-rag t73 收尾時）——
> 「**解析各種檔案的零件還是要開發的，要記下來**，雖然跟現在無關，
> 是因為**這次我用他本機來跑閃掉了**。」

**現況＝框架級硬牆（2026-07-27 實測，非推論）**：
Arcrun 的 host function 邊界是 **UTF-8 文字通道 ＋ 64 KB 上限**
（`outBuf` 64KB／`res.text()`／host fn 內 `TextDecoder`）。後果：
1. **零件拿不到二進位** ⇒ PDF/docx/圖片這類檔案，**做不出解析零件**。
2. 雲端 workflow 的 `fetch_raw` 走同一顆 `http_request` ⇒ 同樣拿不到。
3. 連純文字都受 64KB 限：15 頁 PDF 抽出約 40KB 尚可，**25 頁以上會爆**
   ＝「轉好了卻進不去」的靜默失敗。

**arcrun-rag 目前怎麼過關的（要講清楚，免得被誤會已解決）**：
把轉檔搬到 **daemon 本機**（`collector/convert.go` 調度層＋PDFium-via-wazero）——
**這是繞過（work around），不是修好**。leo 的原話「用他本機來跑閃掉了」講的就是這件事。

**為什麼仍要做（不擋當前工作，但別遺忘）**：
- **能力分裂**：現在只有「裝了 daemon 的人」有轉檔能力；**純雲端用戶永遠沒有**。
- **地端/雲端兩版格式支援會分岔**，長期維護兩套。
- 64KB 上限是**既有**限制，`.md` 今天就會撞到，PDF 只是讓它更快浮現。

**變更範圍（提案，待 leo confirm 才動）**：
① host fn 加二進位/base64 通道（或 ArrayBuffer 傳遞）
② 放大或分塊化 `outBuf`（串流/分頁讀取，避免單次 64KB 天花板）
③ 之上才談「各格式解析零件」

**影響分析**：
- **不受影響**：現行 active SDD `portal-auth` 的所有任務（不同層）。
- **不擋**：arcrun-rag t73 本機轉檔層照做（已 commit `74d18ff`）。
- **牽動**：`http_request` 零件、code 零件的 host fn 契約 ⇒ 屬**全人共用框架**改動，
  影響所有既有零件，**必須 leo 拍板**（跨專案結構決策）。

**現在不做的理由**：leo 明說「跟現在無關」＋客戶在等 daemon；本條僅**立案留底**，
避免「本機能跑了」被後人誤讀成「這個限制已解決」。

## 已裁決

### ✅ confirmed 2026-07-24｜CP2-F cypher 二～四刀拆分（leo 授權總管技術裁決）

> **裁決依據**：leo 2026-07-24 原話「不影響現在 CP 的話就排下一個，這個你是需要判斷的，不能是我，完全技術問題」。
> **總管裁決**：confirm。排程＝A4 全通＋t21/t24 合併重部之後（與在途不撞）。
> **四題**：①開工時點＝同上 ②刀④驗證＝**手寫**（瘦身工程不再引依賴；兩鏈手寫+測試）③新 worker 命名＝**arcrun-api** ④CP2-F 記載更正已由總管代辦。
> 開工時照 D35 ④ 先開新 SDD 搬任務再寫 code。

### P-2026-07-24：CP2-F 第二～四刀——執行引擎獨立成精簡 worker（cypher 瘦身收官）

> 提案人：總管交辦之 arcrun 偵察 subagent（唯讀分析，未動 code、未部署）。
> 觸發：頂層 CRITICAL-PATH.md CP2-F（w=9，P3）——「免費起步」承諾不成立；
> leo 定調：workflow 執行引擎獨立成精簡 worker（只做調度），console/portal/credentials/webhooks 拆出去。
> **依 D35：現行 active SDD＝portal-auth，與本題不同卷，故只寫 proposal 停下等 confirm，不動 code。**

#### 0. 先更正頂層 CP2-F 的過時記載（本次實測核實）

| 項目 | CP2-F 記載 | 2026-07-24 實測 | 出入原因 |
|---|---|---|---|
| 原始碼行數 | 15,446 行 | **11,050 行**（`find src -name '*.ts' \| xargs wc -l`） | 第一刀已砍 |
| bundle | 748KB | **528.1KB**（540,742 bytes，`wrangler deploy --dry-run --outdir` 實跑） | 同上 |
| 「ingest 必爆」 | 隱含現在式 | **已穩定**（第一刀驗收：連灌 10 張全過、同卡 6 連跑全過） | 5a16484 已修 |
| /health 5-7ms、9ms/節點 | 現在式 | **拆分前舊數，第一刀後未重測**——本提案每刀驗法含重測 | 未更新 |

第一刀＝commit `5a16484`（07-21）：console.ts(1623行)＋portal-ui.ts(1390行) 搬去 `console-ui/` CF Pages
（arcrun-console-ui.pages.dev），748→528KB（-29%）。07-22 `ad367e4` 補 console-ui build 修復＋
`deploy.targets.json` 具名部署目標（personal/enterprise 兩 profile，帳號＋apiBase＋專案名收一檔）。
→ **CP2-F 條目該由總管在頂層更新為「第一刀 ✅、二～四刀待裁」**，狀態從 ❌ 斷改 ◐ 半通。

#### 1. Bundle 實測解剖（528.1KB，sourcemap byte 級歸因，非推測）

方法：`npx wrangler deploy --dry-run --outdir=<scratch>` 產 index.js＋index.js.map →
自寫 VLQ 解碼器把每個 byte 歸因到原始檔（mapped 507.5KB＋bundler glue 20.5KB）。

| 模組 | KB | 佔比 | 主要檔案（行數） | 去向提議 |
|---|---|---|---|---|
| **dep: zod** | 130.6 | 24.7% | 只被 `lib/schemas.ts`＋`lib/prompt-recipe-schema.ts` import | 刀④逐出引擎 |
| **dep: hono** | 76.9 | 14.6% | 框架 | 兩邊都要，留 |
| **執行引擎核心** | 74.3 | 14.1% | graph-executor(702)、component-loader(370)、wasi-shim(674)、execute/executions/resume/cypher routes | **留（引擎本體）** |
| Credentials/Auth | 48.0 | 9.1% | auth.ts(467)、credentials.ts(322)、auth-dispatcher(301)、auth-recipe-seeds(748行/21.9KB) | CRUD＋seeds 搬；dispatcher 讀路徑留 |
| Portal（RAG 多人） | 42.3 | 8.0% | portal.ts(783)、portal-data.ts(457)、portal-auth、portal-seeds | 刀②搬 |
| Console API | 31.8 | 6.0% | console-dashboard(526)、console-auth(165)、兩 model(616) | 刀②搬 |
| dep: unenv/polyfill | 29.1 | 5.5% | nodejs_compat 代價 | 留（兩邊皆有） |
| Webhooks CRUD | 25.6 | 4.8% | webhooks-named(521)含觸發＋管理混一檔 | **拆檔**：trigger/query 留、CRUD 搬 |
| Recipes CRUD/seeds | 24.1 | 4.6% | recipes.ts(563)、api-recipe-seeds、init-seed | CRUD/seeds 搬；`resolveRecipe` 讀路徑留 |
| Docs/OpenAPI | 12.5 | 2.4% | openapi.ts(306) | 刀③搬 |
| KBDB proxy | 8.4 | 1.6% | kbdb-proxy.ts(272) | 刀②搬 |
| scheduled/cron | 3.7 | 0.7% | scheduled.ts、cron-* | 留（觸發面） |

**三個關鍵事實**：
- zod 一家＝24.7%，卻只服務「入口驗證」兩條鏈（execute→schemas、recipe-loader→prompt-recipe-schema）。
- 引擎真身（核心+hono+unenv）只要 ~180KB；其餘 ~350KB 全是管理/前端 API 面。
- 耦合點：`graph-executor.ts:6` import `resolveRecipe` from `routes/recipes.ts:406`——
  **讀路徑長在 CRUD 路由檔裡**，拆分前要先抽到 lib（刀②-0 前置小步）。

#### 2. 拆分方案（Option A：引擎留原地，管理面搬出——推薦）

**為什麼引擎留在 cypher-executor 原 worker 不搬家**：webhook 觸發 URL
（`/webhooks/:token/trigger`、`/webhooks/named/:ns/:name/trigger`、`/q/:ns/:name`）
被外部 caller（Telegram webhook、Routine、CLI、MCP）焊死；引擎留原地＝**外部零改動**。
反向（引擎搬新 worker）要全網改 URL，風險大得多，不採。

**佈局**：既有 25 顆（22 零件＋cypher＋kbdb＋mcp）→ **26 顆**：新增 1 顆 `arcrun-api`（管理/控制面 TS worker）。
（arcrun-api 是框架自身的 API 面、非業務零件，與零件須 WASM 的鐵律不衝突——cypher 本身同理。）

| 端點群 | 去向 |
|---|---|
| `/execute`、`/cypher/*`、`/validate`（刀④前）、`/resume`、`/executions/*`、`/workflows/:name/executions`、webhook **trigger/query**、`/health`、scheduled cron | **cypher-executor（引擎）留** |
| `/portal/*`、`/portal/data/*`、`/console/*`、`/kbdb/*`（proxy）、`/credentials/*`、`/auth/*` CRUD、`/recipes/*` CRUD、`/webhooks` CRUD＋named 管理（backfill/migrate/delete/list）、`/init-seed`、`/docs`＋OpenAPI | **arcrun-api（新）搬** |

**Service binding 過渡（13 顆 SVC_\* 懶載現況）**：零風險——13 個 binding 服務的是
`component-loader.ts:73-85` LOGIC_BINDING_MAP 的邏輯零件調度，**全屬引擎**；引擎不搬家＝
wrangler.toml `[[services]]` 原封不動。arcrun-api 完全不需要 SVC binding。
未配置時 fallback 公網 workers.dev 的既有機制（component-loader.ts:249-272）也不動。
self-hosted 的 deploy.ts 注入機制（D1 database_id 等）需對 arcrun-api 的 toml 複用同款注入——既有機制，非新開發。

**與 07-22 第一刀銜接**：console-ui Pages 的 `ARCRUN_API_BASE`（build 期參數）刀②時改指
arcrun-api；`deploy.targets.json` 兩 profile 各加一欄，部署命令不變。

**刀法（分三刀，每刀獨立可回退）**：
- **刀②（先抽 resolveRecipe 到 lib）**：Portal＋Console API＋kbdb-proxy 搬 arcrun-api。
  預估 -82.5KB → 引擎 ~446KB。console-ui apiBase 同步切。
- **刀③**：credentials/auth CRUD＋webhooks 管理面（先把 webhooks-named.ts 拆成 trigger/管理兩檔）＋
  recipes CRUD＋init-seed（含兩包 seeds 30KB）＋docs/openapi 搬過去。預估 -90KB → 引擎 ~355KB。
- **刀④（zod 逐出引擎）**：`/validate` 搬 arcrun-api（zod 隨行）；`/execute` 入口驗證改手寫窄驗證
  （graph shape 檢查 ~50 行）或換 valibot（~10KB）；prompt-recipe-schema 同款處理。
  預估 -130KB → **引擎 ~200-225KB（對 528KB 減 ~60%）**。

**目標與誠實邊界**：/health <2ms 主要靠 bundle 縮（冷啟 parse＋常駐面縮）可期；
但**單節點 10ms 內不保證光靠瘦身達標**——9ms/節點若主要來自 per-node KV put
（graph-executor.ts:352 每節點 `kvSetNodeOutput`）＋JSON 序列化，則需**刀④.5（備案）**：
node output 改「僅斷點/暫停時寫 KV、其餘記憶體傳遞」。是否需要動，以刀②後的 cpuTime 實測決定，不預先動。

#### 3. 每刀驗法（免費層 cpuTime 實測法）

每刀收工四件、缺一不算完成（守 CP 使用規則 6/7）：
1. `wrangler deploy --dry-run` bundle size 對照表（貼實跑輸出，驗預估）。
2. vitest 全綠＋tsc 0（現基線：cypher 169/170，唯一失敗＝executor「不存在的零件」pre-existing）。
3. **免費層 cpuTime 實測**：部署後 `npx wrangler tail arcrun-cypher-executor --format=json` 抓
   `cpuTime`，三個探針各打 10 次取 P50：/health、單節點 workflow（http_request×1）、
   km_wiki_ingest（6 節點）。記進 CP2-F 條目當證據。
4. 端到端頭尾驗：console-ui 兩 profile 登入＋搜尋（打 arcrun-api）、portal 登入、
   `acr push`＋`acr run` 走引擎、**webhook trigger 原 URL 打通**（外部 caller 不改的承諾）。

**回退**：每刀＝一個 PR；arcrun-api 是加法（新 worker），引擎側只刪路由掛載——
回退＝revert PR＋重部引擎（arcrun-api 留著不礙事）。KV/D1 零 schema 變更，無資料遷移，無不可逆步驟。

#### 4. 影響分析（D35 第 3 條）

- **portal-auth（現行 active）**：P1-P4 已完成待部署排練。刀②把 `/portal/*` 路由搬 arcrun-api＝
  **只搬家不改行為**，其測試（portal 三套 56/56）隨檔案搬移後應原樣全綠；
  但其「部署清單」要加一行（portal 部署目標從 cypher 變 arcrun-api）。**不作廢任何任務**。
- **第一刀遺留（5a16484「未做（第二刀）」清單）**：console-dashboard/portal/portal-data＝本提案刀②，正式立案銷帳。
- **library-map（draft）**：M5 console 首頁區塊打 `/kbdb/map`——kbdb-proxy 刀②搬家後 console-ui apiBase 同步切，不改功能。
- **P-2026-07-21 registry proposal 的 B1**（「在 cypher-executor 開 GET /search」）：若兩案都 confirm，
  `/search` 統一面該開在 **arcrun-api** 而非引擎（搜尋是管理面不是執行面）——兩案不衝突，落點修一字。
- **不受影響**：22 顆零件、kbdb worker、mcp、CLI（`acr` 打的執行端點全留原 URL）、
  webhook 外部 caller、13 個 service binding、artifact-sharing（confirmed）各 Phase。
- **新增維護面（誠實記）**：多一顆 worker 要部署（官方＋self-hosted 各一次）；
  deploy 腳本/文件要加 arcrun-api；兩 worker 共用 KV/D1 binding（同一批 id，讀寫面分離）。

#### 5. 待 leo 拍板點

1. **開工排序**：CP 排 P3（P1=CP3-A、P1↑=A4 OAuth 在前）。本提案只求 confirm 方案，開工時點照 CP 排序走——除非 leo 要提前。
2. **刀④驗證庫選擇**：手寫窄驗證（零依賴、-130KB 全拿）vs valibot（保留 schema 風格、-120KB）——品味題。
3. **arcrun-api 命名**：`arcrun-api`？或併入既有規劃中的其他 worker？（跨 repo 佈局＝總管/leo 層決策）
4. **頂層 CP2-F 條目更正**（§0 表）由總管執行——本 repo 只能在此記載，不碰頂層文件。



> 提案人：總管交辦之 arcrun subagent。
> 觸發：leo 2026-07-21 —「一旦推進一個零件，就自動進 registry；recipe、workflow、app 都應該可以 registry，
> 不然搜不到。一邊是寫進去，另一邊是搜到，當然要有。」
> 判準：「Arcrun ＝ 讓 AI 輕易建立程式碼」「AI 要覺得 Arcrun 比 Python 還簡單，絕不可迷路」。
> **依 D35：本任務找不到對應的 active SDD（現行 active＝portal-auth，與本題無關），
> 故不動 code、不自建 SDD，寫本 proposal 後停止等 leo confirm。**

#### 一句話

registry 的寫入機制**存在且可用**，但它唯一的自動觸發點長在 GitHub Actions 上；
Actions 因防 flag 鐵律被整個刪除後（commit `037cf9b`），**寫入端失去觸發者、庫從此是空的**。
這不是「沒有機制」，是「機制的手被砍掉、沒補上替代觸發點」。

#### 根因（file:line 級）

| 事實 | 證據 |
|---|---|
| 寫入端點存在 | `registry/src/routes/components.ts:112` `POST /components/index-only`（metadata-only 索引，冪等） |
| 寫入實作存在 | `registry/src/actions/indexOnlyComponent.ts:44` 寫 `comp:{hash}:{ver}` + `idx:{canonical_id}` 兩個 KV key |
| 批次灌注腳本存在 | `registry/scripts/backfill-index.mjs`（掃 22 個 contract.yaml → POST index-only） |
| 單顆註冊腳本存在 | `registry/scripts/register-component.sh`（註解自稱「本地+CI 共用 SSOT」） |
| **唯一自動觸發點已不存在** | `git show 037cf9b^:.github/workflows/deploy.yml` 第 269-275 行有 `Register component in registry` step 呼叫上述 sh；`037cf9b` 刪除整個 `.github/workflows/`，**該 step 隨之消失，無替代品** |
| 從未跑過的實證 | 對 leo21c 跑 `REGISTRY_URL=... node scripts/backfill-index.mjs --dry-run` → 22 顆待灌；線上 `/components/search?q=http` 回 `count:0` |

→ **答案：是「有機制但從沒跑過」**（且失去觸發者），不是「沒有寫入機制」。
→ 附帶事實：`registry/wrangler.toml:26` 的 `[[routes]]` 寫死 `registry.arcrun.dev`，
  self-hosted（leo21c）只能靠 workers.dev URL，backfill 腳本預設 `REGISTRY_URL` 也是官方域 —— 需帶環境變數才會打到自己的庫。

#### 重要更正：leo 要的東西**有一半已經存在，只是不在 registry 上**

`acr search <term>`（`cli/src/commands/search.ts`）已經是 leo 描述的「意圖驅動、跨類一次搜」——
它 fan-out 四個來源（component 靜態清單 / `/recipes` / `/auth-recipes` / `/webhooks/named`）。
**實測（2026-07-21，leo21c，真實輸出見交辦回報）**：
- `acr search http` → 命中零件 `http_request`（驗收劇本 1 ✅）
- `acr search notify` → 命中 recipe `line_notify_send` + auth-recipe `line_notify`（劇本 2 部分達成）
- `acr search github` → 命中 auth-recipe `github`（驗收劇本 3 ✅）
- `acr search telegram` → 命中 recipe `telegram_send` + auth-recipe `telegram`

→ **能力在 CLI 有、在 registry/MCP 沒有**。這正是薄殼原則（rule 07）被違反的典型：
  同一個「跨類搜尋」能力長在介面層（CLI）而非 API，於是另一個薄殼（MCP）享受不到。
  **修法的方向不是在 registry 重造一套搜尋，而是把 `acr search` 的 fan-out 能力下沉成 API 端點，CLI/MCP 同吃。**

#### 提案內容（四件，全部需 confirm 後才動）

**A. 寫入端：補回失去的觸發點（不復活 Actions）**
- A1. `acr push` / `acr deploy` 成功後自動呼叫 `POST /components/index-only`（本機發起、低頻、單 repo，守 D20 讀寫界線與防 flag 鐵律）。
- A2. `acr init` / `acr update` 部署完 22 顆零件後跑一次 backfill（讓新裝的人開箱即有索引）。
- A3. **recipe / workflow 不需要新的 registry 寫入路徑**——它們本來就活在 store（KV），
  `acr recipe push` / `acr push` 當下就已「寫進去」。缺的只是**搜尋端讀得到**（見 B）。
  → leo 說的「recipe、workflow、app 都應該可以 registry」，正解是**統一搜尋面**，不是把它們搬進 registry KV 再存一份（那會製造第二份真相源）。
- A4. `app`（bundle）已有 P-2026-07-19 artifact-sharing 卷涵蓋，本卷不重複立案，只在搜尋面預留類別。

**B. 搜尋端：能力下沉 + 語意**
- B1. 在 **cypher-executor** 開 `GET /search?q=`（不是 registry——因為 recipe/workflow 的真相源在 cypher 的 KV），
  server 端做 `acr search` 現有的四類 fan-out，回統一結果（含 `type` 欄位）。
- B2. `acr search` 與 MCP 新 tool `arcrun_search`（或改造 `arcrun_search_components`）雙雙改為呼叫 B1，介面層不再自行 fan-out（回歸薄殼）。
- B3. 語意搜尋：KBDB 那條路已驗證可用（Vectorize），把四類的 description 灌進去，讓「我要打一個 HTTP API」這種自然語言句命中 `http_request`。
  現行 `registry/src/actions/queryComponents.ts:104` 明載「這是 Phase 0 的純文字比對版本，Phase 2 接入 Vectorize」——本項即補完該 Phase 2。
- B4. 搜不到時的文案改為「導向 recipe / 既有 workflow」，**移除「可以用 arcrun_publish_component 提交新零件」的建議**（見 C）。

**C. 斷掉「推人去寫零件」的路**
- C1. `mcp/src/tools/arcrun_search_components.ts:50` 搜不到時明文建議 `arcrun_publish_component` —— 違反 leo 既定規矩（零件走 PR、專業等級）。改為導向 recipe/workflow。
- C2. `arcrun_get_component_guide`（回 TinyGo 寫 WASM 教學）與 `arcrun_publish_component` 對一般使用者是誤導 → 建議降級為「專業模式」才暴露（預設不掛載），或在描述首句明寫「99% 情況你不需要這個，先用 recipe」。
- C3. **查證結果：零件相關「已拆到另一個 repo」未獲證實**——`registry/components/` 22 顆零件仍在本 repo，
  wiki 與 git 均查無拆分紀錄。leo 的印象可能來自 `.github-public/` 對外鏡像（`GitHub mirror 發佈模型`）。**此點需 leo 確認**。

**D. 過時項**
- D1. MCP 仍要求已廢的 `api_key` 參數者共 **15 處**：`arcrun_introspection.ts`（4）、`arcrun_recipe.ts`（6）、`arcrun_workflow_crud.ts`（5）。
  2026-07-20 已改 namespace 明碼 → 這些應改為由 server 端 namespace 推導，不再要 AI 傳。
- D2. **1042 的真正解法已確認並已落地**（本項無需修 code，只需更正記載）：
  `global_fetch_strictly_public` compatibility flag，已實裝於 `cypher-executor/wrangler.toml:13`
  與 `.component-builds/http_request/wrangler.toml:8`。
  leo 說的「開了一個什麼，把所有呼叫都視為外部」＝**這個 flag**（讓 same-zone fetch 走公網前門）。
  wiki 已有正確記載：`system-dev/wiki/cards/decisions/same-zone-1042用flag解不用binding.md`、
  `mistakes.md:101`、`decisions-summary.md:84`。
  → 「graph_neighbors 改用 recipe 繞過」的過時說法**在本 repo wiki 查無此記載**（status.md:50-52 只寫 MCP tool PR），
    該過時記載可能在頂層 InkStoneCo wiki 或 MEMORY.md，需由總管在該層更正。

#### 影響分析（D35 第 3 條要求）

- **現行 active SDD（portal-auth）**：完全不受影響，本提案不碰其任務。
- **`component-registry-canon`（status: paused，2026-05-07 建）**：**本題其實早有此卷**，
  其 §1.2 診斷的根因與今日實測完全一致（「registry 活著但 index 空的，AI 找不到零件就會繞回 Python」），
  尚有 **29 個未完成任務**。→ **建議：不新開 SDD，改為把此卷 resume 成 active**（並把 A/B/C/D 的新項增補進其 tasks），
  這比另立新卷更符合 D35 第 4 條「先搬移未完成任務」的精神，也避免第二份重疊規格。
  ⚠️ 但這需要先把 portal-auth 收尾或轉 paused（單一活性鐵律），**此為 leo 的排序決策，不是 CC 能裁的**。
- **`workflow-discovery`（paused）／`library-map`（draft）**：與 B1 統一搜尋面高度重疊，resume 時應一併檢視是否合併。

#### 待 leo 拍板的四點

1. **排序**：要不要把 `portal-auth` 讓位、resume `component-registry-canon` 來做這件事？（單一活性鐵律強制二選一）
2. **C2**：`get_component_guide` / `publish_component` 要「預設不掛載」還是「留著但改描述」？（品味/方向）
3. **C3**：零件是否真的已拆到另一個 repo？（leo 記憶待證實）
4. **B3 語意搜尋**：四類描述灌進 Vectorize 會產生 embedding 呼叫成本，確認可行？（花錢）
### P-2026-07-19：artifact-sharing 增補「App（bundle）＋實例譜系＋訂閱更新＋多源分享」

> 狀態：**confirmed 2026-07-19**（leo「好的可走」）。四個拍板點總管採建議值（leo 可翻案）：①排序＝Phase 1.5 緊接 Phase 1、在 KV 退休（#16/#17）前後皆可並行 ②訂閱預設 policy＝notify ③側載標記第一波＝列表標記即可 ④P6 第一波＝官方源＋直連一個外源，org 私區留第二波。tasks 增補見 artifact-sharing/tasks.md「Phase 1.5」段。

> 提案人：雲端總管（leo 2026-07-19 對話三輪收斂後令「寫」）。
> 目標 SDD：`Leo/Arcrun` `system-dev/docs/3-specs/arcrun/artifact-sharing/`（#27＋#31）。
> 依 D35：本檔為規格層 proposal，**寫入 pending-changes.md 後停下等 leo confirm**，confirm 前不動 tasks、不寫 code。

## 一句話

workflow/recipe/template 的實體在平台（CF/KBDB），不在 YAML——YAML 是隨叫隨到的視圖（搖桿）。
因此版本控制、譜系、更新通知必須是**平台內建能力**，git 降級為：引擎程式碼的家＋人審 diff 面＋災備快照。
本提案把 #27 公庫從「單品發布/拉取」補全成「App 商店＋訂閱更新＋實例譜系」。

## 背景（為什麼現在）

- 實證痛點：arcrun-rag（企業）與 Mira（個人）跑同族管線的兩份變體（rag_ingest v2 vs km_wiki_ingest），
  「哪邊同步了沒」靠人腦；claude.ai 07-19 實測抓到的三個引擎缺口再證「修一次、兩實例都該自動受益」的通道不存在。
- T-pack-v2「repo 即真相源」是**過渡義肢**：因為平台缺版本層（KV 讀不回、無版本、無譜系），才用 git 代位。
  義肢照 D15 前例：平台長出器官後拆。
- 地端版（workerd）將成第三個實例形態——在複雜度上升前把「App×實例×版本」模型立好。

## 提案內容（五件）

### P1. bundle 第四型（App）
- `public_artifact` 新增 `type=bundle`。portable_body＝成員清單：
  `[{type: workflow|recipe|template, canonical_id, uuid(鎖版) 或 version_policy(track-latest)}]`
  ＋`install_params_schema`（安裝參數：多人開關、Gitea 端點、LLM provider…）＋`conformance`（見 P5）。
- 「裝一個 App」＝pull 一個 bundle，成員經既有 dependency_manifest 機制解析；
  個人版與企業版＝**同一個 bundle、不同 install params**。
- arcrun-rag 為第一個 bundle（現行 install.sh 即其手寫前身，落地時翻譯成 manifest）。

### P2. 訂閱與更新通知（可拒、可 pin）
- install/pull 時建立訂閱關係：新 entry `artifact_subscription`
  （subscriber namespace、canonical_id、目前安裝 uuid、policy: notify|auto|pin）。
  （`artifact_pull_event` 保持純事件帳本不改；訂閱是關係、事件是史料，分開存。）
- 新版 submit-p 後，訂閱者「得知」的通道**查詢式優先**：
  `GET /subscriptions/updates`（實例/AI/console 隨時問「我有沒有落後」）；
  推播（notify workflow）為選配、且**禁止因發布事件自動 fan-out 到多實例**（D4 紅線——通知生成可以批次/排程，不掛事件觸發鏈）。
- 更新永遠是訂閱端的**顯式動作**：可更、可拒、可 pin 死版本。

### P3. installed_from 譜系＋側載標記（隱形工作流可見化）
- 實例內每個 materialized artifact（workflow/recipe/template）落籍記
  `{installed_from_uuid, installed_at, content_hash}`（entry metadata，不建表）。
- 平台即可回答四態：`up_to_date / behind / diverged（本地改過）/ sideloaded（無譜系，API 直建）`。
- list/console/MCP 的 workflow 列表帶狀態標記。**側載不禁止、只標記**（Android sideload 哲學）；
  「沒有 YAML 就不准」的 policy 不採納——對 AI 是降頻，可見性由本條提供。
- 既有 git 外掛式 drift 稽核（guard 對 hash）為過渡措施，本條落地後退役。

### P4. 更新與分岔語意
- update＝拉新版重新 materialize（installed_from 換新 uuid）。
- `diverged` 者不自動覆蓋：提示二選一——fork（submit-p 推回公庫成自己的作者版本，derived_from 記溯源）或捨棄本地改動收新版。
- 對應 leo 模型：「Mira 和 Arcrun RAG 都 clone 了 rag app，都可以推回去存成新版本，收到更新通知但自己決定要不要更」。

### P5. conformance self-check 隨 bundle（驗收矩陣自動化）
- bundle 附驗收清單：介面×能力的機械檢查（例：portal 登入 200、MCP tools/list 含 kbdb_*＋graph、
  keyword/semantic/graph 三模式各一題 golden question 命中）。
- install/update 完自動跑、產報告；解「企業 GUI 驗了但企業 MCP／個人 GUI／個人 MCP 不知道、檢查到沒完沒了」——
  可用性從人肉巡邏變安裝產物。地端（workerd）實例＝同一張體檢單多跑一列。

### P6. registry＝源（source）非地方：多源與點對點分享（leo 2026-07-19 追加）
- 公庫端點長在 cypher-executor＝**每個實例都內建 registry 能力**；官方公開市場只是「預設源」。
- 實例可加多個源：公開市場／org 內部源／**另一個實例直連**（A 做了 App，B 把 A 加為源直接拉，CF-to-CF，不經公開市場——Homebrew tap 模式）。
- artifact 加 `visibility: public | org | unlisted`；外源拉取走既有 token 認證（portal-auth／MCP OAuth 機制，A 發「可拉取」token 給 B）。
- P3 落籍擴充：`installed_from` 記「源＋uuid」；P2 訂閱跨源成立（B 訂閱「A 源上的 canonical_id」）。
- **信譽記分邊界**：私下/org 內分享的 pull 事件留在該源帳本，**不進公開信譽**；日後同 canonical 發布公開市場，溯源保留、私史不折算。
- 信任判斷仍在 import 端（#27 原則）：來源是誰，列表標記說清楚。

### P6 鐵律：拉取＝落地複本，runtime 零外部依賴（leo 2026-07-19 Drive 之問定形）
- pull/install＝把 artifact（含 dependency_manifest 解析出的全部依賴，跨源亦然）**materialize 進本實例 KBDB**——複本式分發（npm/App Store），非引用式分享（Google Drive）。
- 源斷線/停止分享＝只失去更新，**已裝的照跑**；狀態標 `source_lost`。互相參照在安裝時全落地，runtime 永不回頭找源；只有「查更新」動作碰源。
- 多源不亂的機制：裝完面對的是一張本地清單（每項帶源＋五態標記）；同 canonical_id 跨源衝突＝安裝時顯式選源，不靜默合併；bundle 鎖版 uuid＝lockfile 對應物。

### 排序定調：分享先行、投稿後行（leo 2026-07-19 收斂）
- **官方的本質＝第一個策展人**（leo 定調）：官方源不是特權角色，就是「把很多資源收集起來分享給大家的人」；公開市場＝分享機制＋官方獎勵制度（信譽層）疊上去，作為分享落地後的下一件事。
- **先做**：P6 最小版（官方源＋直連外源）＋P1＋P3 → 這就是產品自己的部署通道（leo 官方源→客戶實例拉 arcrun-rag bundle；第一組 A/B＝leo 與第一個客戶、以及 Mira）；再 P2 訂閱更新。
- **後開**：公開投稿＋信譽榜——空市場掛榜＝鬼城，等 self-hosted 用戶密度夠再開（社群玩法）。
- **零代價保證**：#31「事件是真相、統計是視圖」——pull/submission 事件 day-one append，信譽晚開也能從頭算，先分享不欠投稿任何債。

### 附：獎勵/信譽（確認既有設計，非新增）
- 「貢獻 recipe 得 reputation」＝#31 作者信譽經濟已設計（作者頁/聚合信譽/選版複合分/獎勵層預留、author 綁認證身份）；事件 day-one append 保證信譽可事後計算。
- 本提案只補一句：**bundle（App）作者同樣累積信譽**——組裝 App 與寫 recipe 同屬貢獻。

## 影響分析

- **不建表**：全部走 entry_type＋metadata_json（D6 鐵律）；`artifact_subscription`／落籍 metadata 皆 template/slot 層。
- **與 Phase 1 關係**：不改既有 1.1–1.6 任務；本提案為 Phase 1.5（bundle/lineage/subscription 建在單品公庫之上）。
  1.5 的 recipe KV 過渡（#16）不受影響。
- **與 D4 flag 鐵律**：通知採查詢式優先、禁事件 fan-out（見 P2），無 GitHub 流量、無 Actions。
- **與現行 installer**：過渡期 install.sh 續用；第一步可先讓 installer **代寫 installed_from 落籍**（義肢幫器官接生），
  公庫端點好了再切換 pull 路徑——遷移平滑、無一次性大爆改。
- **受益者**：Mira 跟上企業版＝「訂閱同一個 bundle 按更新」；未來 N 個客戶實例同此通道；地端版=第三個實例非第三套系統。
- **風險**：scope 擴張（#27 尚未動工就加型）——緩解：P1/P3 是最小核（bundle 形狀＋落籍欄位），
  P2 通知與 P5 self-check 可各自獨立分批落地；全部 compute-on-read 起步，物化快照沿用 #31 判準。

## 待 leo 拍板點

1. Phase 1.5 排序：在 Portal（#24/#25 已收官）之後、KV 退休（#16/#17）之前或之後？
2. 訂閱 policy 預設值：notify（通知不動手）？
3. 側載標記的呈現強度：只列表標記，或 console 加「無譜系 workflow」提醒區塊？
4. P6 多源第一波範圍：只做「官方源＋直連一個外源」？org 私區（visibility=org）是否留第二波？

---

## [proposal] 兩個真缺口要進 SDD（2026-07-30，總管提，等 leo confirm）

**來源**：頂層 CP `critical-paths/arcrun-usable.md`（leo confirm 開工）逐步查證後，
發現兩件事**SDD 完全沒有**，而它們是「AI 不必寫 code」的前提。

leo 的判準（2026-07-30）：
> 「如果在這裡編任務，不就是廢掉了原本的 SDD，那到底要照 SDD 做事還是照 CP？」
⇒ **照 SDD 做事、照 CP 排順序**。CP 不得自帶任務 ⇒ 這兩件必須進 SDD 才能做。

### 缺口 ①：引擎缺條件邊（`ON_TRUE`／`ON_FALSE`）→ 建議進 `arcrun-core-mvp`

- **實測**：`if_control` 的 output 是 `{result: boolean, branch: "true"|"false"}`，
  但 `cypher-executor/src` 全域 grep `ON_TRUE|ON_FALSE` = **0**（圖的邊只有 `ON_SUCCESS`）
- **後果**：AI 照規矩用 `if_control` 也只拿到布林值，**還是得寫 code 判斷該走哪裡**
  ⇒ 這是「為什麼 workflow 全變成 code」的根（8 個 code 節點含 if×61）
- **佐證**：`kbdb_upsert_block` 契約自述「解 arcrun workflow **缺 IF/branch 能力**的缺口
  （arcrun.md P1 #1）」；Gitea **Arcrun#5**「if_control 無法真擋分支，filter+FOREACH 才是真閘」
  於 **2026-07-04** 就發現，至今未修
- **SDD 查證**：`grep "ON_TRUE|ON_FALSE|條件邊|分支"` 於全部 SDD → 7 命中**全是別義**
  （「部署分支」「git 分支」），**條件邊本身完全沒設計過**
- **影響面**：改圖 schema ＋ `graph-executor` 走邊邏輯＝引擎核心，風險最高
  ⇒ 建議排在「查詢誠實」「節點替換」驗過之後

### 缺口 ②：recipe 缺 payload 與回應處理層 → 建議進 `recipe-system`

- **實測**：recipe schema 只有 `{canonical_id, endpoint, method, auth_service}`
  （線上 `GET /recipes/telegram_send` 實查）
- **後果**：`telegram_send` 的 description 明寫「body 帶 chat_id+text」但**schema 存不住 body**
  ⇒ 任何要帶 body 的 API 都只能繞過 recipe、把 URL/header/body 整包寫進 workflow
  ⇒ Gemini 就是這樣寫死的（`ask_llm` 三層全塌進節點）
- **要補三層**
  - `body_template`（payload）
  - `response_map`（回應正規化：取值路徑／思考型模型旗標／淨化規則）
    ——現在這段是 workflow 裡 2786 字元的 code（`finalize`）
  - `auth` 加第四型 `binding`（免金鑰；一次打開 `env.AI`／`VECTORIZE`／`BROWSER`／`QUEUE`）
- **SDD 查證**：`grep "body_template|response_map|payload"` → 17 命中**全是別的 payload**
- **不做的話**：LLM 換源永遠要改 workflow（而非換 recipe），違反「LLM 可換源」原則

### 誤標修正（誠實記錄）
總管原本在 CP 標了 **5 個新缺口**，逐條查 SDD 後**只有上述 2 個是真的**：
- `success_rate` 回寫 → **`arcrun-core-mvp/design.md:499` 早就設計**
  （`success_rate = success_runs / total_runs * 100`、排序規則、UI 顯示），只是沒實作
- 8 個壞範例、意圖節點替換 → 相關設計散在既有 SDD，已改為引用

### ⏸ 等 leo confirm
- confirm 後依 D35：兩份 SDD 目前皆 `paused`，要動需先處理單一活性
  （現行 active＝`workflow-discovery`）


## 提案：workflow export/import 一等公民化（2026-07-31，總管代 leo 口頭定調立案）

- **來源**：leo 07-31 原話（見 workflow-discovery tasks.md 3.9 引文）——分享 workflow
  給同事的正路＝export 檔→import，不是「用 search 湊」。
- **變更面**：① cypher `GET /webhooks/named/:name/definition`（已實作，staging 試點中，
  零新資料模型／同 list 認證）② `acr workflow export <name>`／`acr workflow import <file>`
  新 CLI 命令（已寫未接線）③ 安裝器 workflows.json＝export 檔同形狀（graph 預編，已上 prod）。
- **不動的牆**：KBDB 零接觸（export 讀 WEBHOOKS KV workflow record；import 打既有
  POST /webhooks/named）；「部署≠發現」（import 不驗零件存在，執行時現形）；
  description 必填閘照舊。
- **影響分析**：新公開端點 1 個（租戶 key 認證，吐的是該租戶自己部署的 workflow 定義；
  無跨租戶讀）；CLI 新命令 2 個（薄殼，能力全在既有 API）；無 schema migration、無新金鑰面。
- **狀態**：⏳ 待 leo confirm 後 3.9b 接線。


## 提案：Arcrun App ↔ Portal 掛載協定 v0（2026-08-09，Leo/Arcrun#82）

- **設計全文住在票上，本檔只留指針**（leo 08-09 立：設計寫在 issue 上，別讓下一個人再翻一次源碼）
  → `Leo/Arcrun#82` 的「設計提案：Arcrun App ↔ Portal 掛載協定 v0」留言。
- **觸發**：leo 08-09——「要變成是一個 Arcrun App，含前端、template、slots 的設置，任何人可以
  安裝後他的 Arcrun 就跳出筆記功能」＋「Mira 的那些功能萃取都要變成可安裝」＋追加指示
  「等你搞清楚 Mira 的功能時直接改成 App 模式，**不要分兩段**，不然開發好了又要一次遷移」。
- **病根（實查）**：Portal 是單檔 HTML，「有哪些頁」在 `console-ui/public/portal/index.html`
  裡寫了四遍（側欄 :253-259／tab :510-516／VIEWS :718／allowedViews :721-729）；出貨時整包
  內嵌成單檔 worker（`build-ui-bundle.mjs:154`），安裝器只會「整顆換掉」、無任何掛載點概念。
  ⇒ 加一個能力＝改核心＋重出 release。`Leo/mira#2` 那批能力若照現況搬，就是逐個焊死。
- **變更面**：① KBDB seed 一列 `arcrun_app` template（零新表，加進 `lib/portal-seeds.ts:21`）
  ② `/portal/session`（`routes/portal.ts:495-515`）多回唯讀 `apps`
  ③ 新增泛用端點 `POST /portal/apps/:id/actions/:name`（server 側代打既有 named webhook，
  金鑰不外流，形狀同既有 `/portal/data/upload`，但**泛用**）
  ④ Portal 一次性泛用 loader（約 60 行）⑤ `acr app build|install|remove|list`
  ⑥ `arcrun-app.yaml` 的 JSON Schema（repo 目前無「可安裝單位」schema，這是第一份）。
- **不動的牆**：KBDB 零 SQL、永不加表；App 前端不進 `arcrun-rag-ui` bundle、不進
  `bundle-components.mjs`（否則回到「改核心才能加能力」）；App 拿不到 session token
  與租戶字串，資料一律走既有 `/portal/data/*` 的 owner_id＋library enforce；
  App 自帶 workflow 必須自帶預編圖（冷實例即時編圖 25.7s ＞ 15s timeout 的既有教訓）。
- **影響分析**：現行 active SDD＝`workflow-discovery`，本提案**不作廢它任何任務**，屬新增一卷；
  新公開端點 1 個（portal session 認證）；新 template 1 列；無 schema migration、無新金鑰面；
  既有 `/portal/data/upload` 可事後收編成一個 App，不必第一天做。
- **⏸ 等 leo 裁的兩題（票上 §七）**：① App 前端跑 Portal origin（ES module）還是 iframe
  ——短期 App 是否都由我們自己寫？② v0 只開 `nav` 一個掛載點夠不夠？
- **狀態**：⏳ 待 confirm。沒 confirm 前照現行 SDD 走，不開新 SDD、不動功能程式碼。

---

## 提案：安裝器的狀態判斷一律看帳號，中心帳本降級成純優化資料（leo 2026-08-14）

- **leo 原話（本提案的來源，也是判準）**：
  > 「你吃飯了沒？是由我來判斷的嗎？我看到你沒有盤子，應該問你吃了嗎？
  > 但我沒問，堅持說你吃過了，**因為我這裡登記你吃了，結果讓你不能買飯**。」
  > 「我有**檢修孔**，需要去問問就好了，東西有沒有**一看就知道**，
  > **根本不應該由中心判斷**。」

- **病根（同一天三次撞牆，同一個形狀）**：
  安裝器用**自己的帳本**（`INSTALLER_KV` 的 `deployed:<accountId>:<subdomain>`）
  決定「這台裝過了沒」，而不是去看**帳號上現在有什麼**——
  但它手上一直握著那把 CF token，一次 API 就問得到（＝leo 說的檢修孔）。

  | 撞牆 | 帳本說 | 現場 | 結果 |
  |---|---|---|---|
  | leo（`#120`） | 裝過了 → `update` | leo21c 已被清空，0 顆 worker | `rule.mjs:257` 停手「找不到任何要更新的 worker」，**畫面上沒有出口** |
  | 封測者（`#123`） | 沒人綁這個名字 → 可以建 | 名字已被上次半殘安裝佔走 | CF 回 `title already exists`，**帳號永久卡死** |
  | 08-14 早上的處置 | — | — | **人工去 uncle6 刪掉那筆紀錄**才能繼續＝把帳本改回跟現場一致。**那是繞過，不是修法** |

- **要達成什麼**：**帳本壞掉、被清掉、或根本不存在時，安裝器照樣裝得起來。**

- **變更面（規格層，不是實作指定）**：
  1. **`mode`（init/update）的來源改成帳號現況**——「這個帳號上有沒有我們的 worker」。
     `worker.js:1750-1756` 現在讀兩條通道的 KV 前綴決定，改成問帳號。
  2. **`rule.mjs:257` 那道「說是更新卻一顆 worker 都找不到 → 停手」不再是錯誤**。
     在新判準下這個組合根本不會出現：沒有 worker ⇒ `mode` 本來就會是 `init`。
     （該閘原本是 `#97` 的另一道門，其防護目的由「2b 已部署綁定絕對優先」承接。）
  3. **那張紙搬到用戶自己的帳號上，中心不再保管**（leo 2026-08-14 第三次收斂，比「降級」更徹底）：

     > 「**我覺得根本不用我 keep**，這次裝的 sha **記在一張紙放你家**，
     > 下次又要裝我就把那張紙拿出來看你上次裝了什麼版本，
     > **如果不符，以實際有幾顆為準**。」

     ⇒ 紀錄（每顆 worker 的 `sha256`、`bundleBase`）**寫在使用者自己的 Cloudflare 帳號上**，
     不再寫進我們的 `INSTALLER_KV`。

     🔑 **為什麼這個比「降級」更好——它讓那個 bug 變成不可能發生，而不是被處理掉**：
     紙跟東西放在同一個地方 ⇒ **他把帳號清空時，紙跟著消失**
     ⇒ 結構上不可能出現「東西沒了、而我們手上還留著一張說他裝過的紙」。
     `#120` 因此不是「被修好」，是**沒有地方可以發生**。

     附帶好處：
     · 跨通道互蓋（t157）不必再讀「對方通道的 KV」——紙就在帳號上，兩條通道看的是同一張
     · 我們不再保管一份「誰裝了什麼」的中央登記簿（少一個要維護、會漂、會外洩的東西）
     · 帳本壞掉／被清掉／從沒有過，安裝器照樣 work

     🔗 **必須一併處理的勾連：辨識碼的「更新者免碼」**（leo 2026-08-14 問「辨識碼是用來找那張紙的嗎」，
     查證後方向是**反的**）：
     · 辨識碼＝**邀請閘**（`verifyInviteCode` 打 landing 的 `/api/verify-code`），與帳本無關；
       真正認出使用者的是 **email**（`slugFromEmail` 決定所有資源名字），辨識碼不參與。
     · 但 `worker.js:3246`（t154）**用那張紙免掉辨識碼**：碼留空 → 放行進 OAuth →
       查此帳號有無部署紀錄 → 有＝視同已核可免碼；無＝退回要碼。
     ⇒ 紙搬到帳號上之後，「免碼」的判準自然從**查中心登記簿**變成**去帳號上看一眼**。
     ⇒ 附帶讓 `#120` 那個情境變**誠實**：帳號被清空 ⇒ 沒有 worker、沒有紙 ⇒ 你本來就是新裝，
       **「請填辨識碼」是正確答案**；對照今天是「免碼放行 → 進去才說找不到 worker → 停手、沒有出口」。
       **同一個情境，一個是請你填個碼，一個是把你關在門外。**
     ⚠️ 辨識碼**不可拿掉**——那是封測資格閘，與本提案無關。

     ⏸ **要 leo 一併裁的兩題**（實作前必須有答案，但**做法由收工方判斷**）：
     ① 那張紙**貼在帳號的什麼位置**才會「跟著東西一起消失」——
        要能被拆除工具連帶清掉，且不需要額外權限面
     ② `hasDeployRecordForToken`（t154：用一把 token 查「這些帳號有沒有裝過」）
        現在靠中心前綴掃描，搬走後要改成逐帳號去看——**這是預期內的成本，不是阻礙**
  4. 🔴 **鐵律：帳本永遠不得產生「停手」。** 帳本與現場不合時，
     **以現場為準並順手把帳本改對**，不是把使用者擋在門外。

     > leo 補充（2026-08-14，這句定義了帳本的身分）：
     > 「**我只是記錄上次的，這次去看看**，本來要記錄 sha，
     > **一看東西都沒了，也不能硬說你裝過**。」
     >
     > ⇒ **帳本記的是「上次」，不是「現在」。** 它是歷史紀錄，不是狀態宣告。
     > 每一趟都要去看現場；**看到的與帳本衝突時，看到的才算數**。
     > ⇒ 這也是為什麼「帳本不得停手」不是一條例外處理，而是它本來就沒有那個權限——
     > 一份講過去的紀錄，本來就不該否決現在觀察到的事實。

- **不動的牆**：
  · 2b「已部署 worker 的綁定＝事實，絕對優先」不得放寬（`#97` 的災情來源）
  · 接管同名資源仍須呼叫端聲明 `createNameIsOurs`，預設 fail-closed（`#123` 的反向災情）
  · 判斷仍只有**一份**，在 `shared/resource-rule/`；安裝器不得自建第二套

- **影響分析**：
  · 動的是 `installer/oauth-prototype/worker.js` 的 mode 決定點 ＋ `shared/resource-rule/rule.mjs`
    的 update-mode 前置閘；**鏡像兩份要同批**（`MIRROR.json` 守著）
  · 多一次帳號查詢（`listAllScripts` 本來就會查，可共用，預期零額外成本）
  · 不動 KBDB、無 schema 異動、無新金鑰面、無新公開端點
  · 會讓 `#120` 從「要人工清中心紀錄」變成不需要人介入

- **牽涉的票**：`inkstone/Arcrun#120`（重裝死結，本提案的主治對象）
  ｜`inkstone/Arcrun#123`（已併 `9ad95ee`，是同一個病在資源層的那一半）
  ｜`inkstone/arcrun-rag#78`（用戶版 uninstaller，受益但不因此結案）

- **狀態**：⏳ **待 leo confirm 範圍**。方向已由 leo 當面給定；未 confirm 前不動功能程式碼。
