SDD kbdb-base §8.3/§8.4 P2+P3。總管裁定 2026-07-06: ① upsert 走 KBDB base PUT /entries(by owner_id+page_name+entry_type) ② cron 掃 KBDB、不保留 KV cron-idx ③ 一 workflow=一筆 entry_type=workflow;content=description(保 embed)、 graph+config+cron_expr 進 metadata_json KBDB: - entry-crud.ts 加 upsertEntry(read-then-write,無 UNIQUE、表不變) - entries.ts 加 PUT /entries(upsert,回 created 旗標,content 變動時 embed-on-write) cypher-executor: - 新增 lib/workflow-store.ts(走 kbdbBase,禁直連 D1) - webhooks-named register/trigger/list/delete 切雙軌(讀先 KBDB miss fallback KV、 寫 KBDB 為主+暫雙寫 KV);writeWorkflowSearchEntry 上收進 putWorkflow(修重複 entry bug); backfill 升級為 KV→KBDB 遷移入口;移除 migrate-cron-index 端點 - scheduled 改掃 KBDB(listCronWorkflows);刪除 lib/cron-index.ts - component-loader(trigger_workflow)、executions 擁有權檢查切雙軌讀 tsc 0 error;kbdb vitest 6/6;cypher vitest 41/42(1 失敗為 pre-existing,與本 PR 無關)。 不 merge、不部署、不動 production KV workflow。搬遷步驟見 MIGRATION.md。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HJiLCRUU2o3aSpPEzVCt2o
6.7 KiB
MIGRATION — workflow 真相源 WEBHOOKS KV → KBDB API(entry_type=workflow)
分支:
feat/workflow-store-kbdbSDD:system-dev/docs/3-specs/arcrun/kbdb-base/design.md§8.3/§8.4 總管裁定(2026-07-06):① upsert 走 KBDB basePUT /entries;② cron 掃 KBDB、不保留 KV cron-idx; ③ 一 workflow = 一筆entry_type=workflow,content=description(保 embed),graph+config+cron_expr 進metadata_json。本 PR 不 merge、不部署、不動 production 那 7 筆 KV workflow。 data 搬遷是下一階段(leo 寫入閘)。
1. 這個 PR 改了什麼
KBDB(新增能力)
kbdb/src/actions/entry-crud.ts:新增upsertEntry(db, input)— by(owner_id, page_name, entry_type)read-then-write(無 UNIQUE 約束、表不變鐵律;命中最舊一筆更新,重跑收斂)。kbdb/src/routes/entries.ts:新增PUT /entries(upsert,回{ success, entry, created };content 變動時 embed-on-write)。
cypher-executor(workflow store + 六路 + 兩路切雙軌)
- 新檔
cypher-executor/src/lib/workflow-store.ts:getWorkflow / listWorkflows / putWorkflow / deleteWorkflow / listCronWorkflows,全走 KBDB base API(kbdbBase= KBDB_BASE_URL + 選用 KBDB_INTERNAL_TOKEN),owner_id=namespace 租戶隔離。禁直連 D1/SQL。 routes/webhooks-named.ts:- register:寫
putWorkflow(KBDB 為主)+ 暫雙寫 KV;移除writeWorkflowSearchEntry(上收進 putWorkflow, 修掉每次 POST 堆重複 entry 的 bug);移除 KV cron-idx 維護。 - trigger / list / delete:讀先 KBDB、miss fallback KV;list = KBDB ∪ KV(同名 KBDB 為準);delete 兩邊都刪。
backfill-search-entries:升級為 KV→KBDB 完整遷移入口(putWorkflow upsert,冪等可重跑)。- 移除
migrate-cron-index端點(KV cron-idx 已退場)。
- register:寫
scheduled.ts:cron 掃描改listCronWorkflows(掃 KBDB、記憶體濾 cron_expr);不再讀 KV cron-idx。lib/component-loader.ts(trigger_workflow 子流程)、routes/executions.ts(擁有權檢查):切雙軌讀。- 刪除
lib/cron-index.ts(KV cron-idx 模型退場)。
綠燈
kbdbtsc 0 error、vitest 6/6 綠。cypher-executortsc 0 error、vitest 41/42 綠(1 個executor.test.ts > 不存在的零件失敗為 pre-existing,main 上同樣失敗,與本 PR 無關——component-not-found 錯誤訊息措辭斷言,非 workflow 路徑)。
2. 部署後驗證計畫(對真端點 curl,禁 miniflare 假綠)
前提:部署到 leo21c(auto 模式本輪不做;下述為部署後的驗收步驟)。 端點:cypher
https://arcrun-cypher-executor.leo21c.workers.dev、KBDBhttps://arcrun-kbdb.leo21c.workers.dev。 全程只用測試 namespace(如ak_wfmigtest),不碰現有 7 筆 production workflow。
設 NS=ak_wfmigtest、CY=https://arcrun-cypher-executor.leo21c.workers.dev、KB=https://arcrun-kbdb.leo21c.workers.dev。
- KBDB upsert 端點本身
curl -XPUT $KB/entries -H 'content-type: application/json' \ -d '{"entry_type":"workflow","owner_id":"'$NS'","page_name":"t1","content":"desc A","metadata_json":"{\"graph\":{\"id\":\"g\"}}"}' # → { success:true, created:true } # 再打一次(改 content:"desc B")→ { success:true, created:false }(同一 entry 更新,非新增) curl "$KB/entries?entry_type=workflow&owner_id=$NS&page_name=t1" # → 恰 1 筆、content="desc B" - register(部署)→ 恰一筆、redeploy 不重複
curl -XPOST $CY/webhooks/named -H "X-Arcrun-API-Key: $NS" -H 'content-type: application/json' \ -d '{"name":"wf_a","description":"測試工作流","graph":{"id":"wf_a","nodes":[]}}' curl "$KB/entries?entry_type=workflow&owner_id=$NS&page_name=wf_a" # → 恰 1 筆 # 同名再 POST 一次(改 description)→ 仍恰 1 筆、content 更新(驗 upsert 冪等,無重複 entry bug) - list / trigger / delete 走 KBDB
curl $CY/webhooks/named -H "X-Arcrun-API-Key: $NS" # → 含 wf_a,欄位齊 curl -XPOST $CY/webhooks/named/wf_a/trigger -H "X-Arcrun-API-Key: $NS" -d '{}' # → 執行(讀到 KBDB graph) curl -XDELETE $CY/webhooks/named/wf_a -H "X-Arcrun-API-Key: $NS" # → {deleted:true} curl "$KB/entries?entry_type=workflow&owner_id=$NS&page_name=wf_a" # → 0 筆 - cron 掃 KBDB:部署一個首節點為 cron 的 workflow(cron_expr 每分鐘),等 1–2 分鐘看
wrangler tail[scheduled] scanned N KBDB cron workflows, k triggered有觸發。 - 雙軌 fallback(不寫 KBDB 的舊 KV workflow 仍可 trigger):手動塞一筆
{NS}:wf:legacy進 WEBHOOKS KV (只 KV、不進 KBDB),打/webhooks/named/legacy/trigger→ 應 fallback KV 成功執行;GET /webhooks/named應同時列出 KBDB 的與這筆 KV-only 的(union)。
驗收標準:上述每步 HTTP status + 回傳體符合預期;特別是「redeploy 同名恆為 1 筆」(upsert 生效) 與「cron 從 KBDB 觸發」。缺任一即不算綠,回報而非假綠(mindset §7)。
3. 剩餘 data 搬遷步驟(下一階段,leo 寫入閘,本輪別做)
現有 7 筆 production workflow 仍只在 WEBHOOKS KV。雙軌讀讓它們仍可 trigger,但:
- cron 掃描只看 KBDB → 若這 7 筆裡有 cron workflow,未搬遷前它們的排程會停止觸發(⚠️ 高優先)。
- semantic 搜尋也只看 KBDB entry。
搬遷程序(冪等、可重跑):
- 逐 namespace 呼叫
POST /workflows/backfill-search-entries(帶該租戶X-Arcrun-API-Key)。 它 list KV 的{ns}:wf:*、對每筆putWorkflow(upsert)遷進 KBDB。回傳backfilled/needs_description。 - 驗證:
GET $KB/entries?entry_type=workflow&owner_id={ns}每筆都有對應 entry(graph 在 metadata)。 needs_description清單交操盤 CC re-deploy 時據實補描述(不自動編造)。- 全部驗穩後,才由 leo 決定拆掉 register 的 KV 雙寫 + trigger/list/delete 的 KV fallback(另一次閘)。 拆除前 KV 是安全網,不要急著拿掉。
誰有幾筆、哪些是 cron,需先盤點(
WEBHOOKS.list或 console)再排搬遷順序(cron 的先搬)。
4. 回滾
本 PR 不動 production 資料,且 register 仍雙寫 KV、讀有 KV fallback → 直接 revert 分支即可回到「KV 為真相源」。
KBDB 端只新增 PUT /entries(不改既有端點行為),revert cypher 側即停用;KBDB 側可留(無副作用)。
唯一不可逆的取捨:本 PR 刪了 cron-index.ts 與 migrate-cron-index 端點(KV cron-idx 退場);
如需回滾 cron 到 KV,從 git 歷史取回即可。