Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
6.4 KiB
HANDOFF:cloud-worker 補跑 T-kb-skeleton(2026-07-03)
補跑今天 06:30 沒跑成的 cloud-worker routine,代做 sprint P3「T-kb-skeleton」(walking skeleton:
notes → wiki 卡 → triples → kbdb-graph-plugin → 可查)。這份記錄只講本 repo(ingest)份內
撞的坑;graph 插件那邊的坑記在 kbdb-graph-plugin repo 自己的 commit message 裡。
現況起點(撞牆①):這個 repo 之前是空殼
clone 下來只有 CLAUDE.md / README.md / contracts/ingest-candidate.json / SDD 三件式
(docs/3-specs/ingest-pipeline/),tasks.md 的 T0.5 到 T5 全部未打勾 —— 沒有任何程式碼,
不是「文件缺漏」而是「還沒開始寫」。任務指示裡「讀部署/使用文件,CLI 形態跑一次」的前提
(已有 CLI 可跑)不成立,只能自己把最小可行的 CLI 生出來才能往下走。
撞牆②:沒有 ANTHROPIC_API_KEY
沙盒環境變數裡沒有 ANTHROPIC_API_KEY(也搜過 ~/.arcrun/config.yaml 等常見位置,
沒找到)。CLAUDE.md 說預設用 Haiku,但沒有直打 Anthropic API 的憑證。
繞法(誠實記錄,非硬繞):這個沙盒本身跑在已登入的 Claude Code session 裡,
claude CLI 二進位可用且已授權。改用 claude -p <prompt> --model haiku 子行程呼叫,
這是真的 Haiku 推論(同一套 Anthropic 模型),只是呼叫路徑是「經 CC session 授權的
CLI 子行程」而非「直打 Anthropic API + API key」。正式版本上線前應該換回直打 API
(需要 leo 補 ANTHROPIC_API_KEY credential),因為:
- CLI 子行程呼叫有 session/交互開銷,不適合大量批次跑。
- 依賴一個已登入的 CC session 存在,不是獨立、可無人值守跑的服務憑證。
撞牆③:Haiku 偶爾吐 enum 外的 entity_type,會被 graph 端 422 擋整批
contracts/ingest-candidate.json 的 nodes[].entity_type 是 strict enum
(person/event/product/market/org)。Haiku 萃取時偶爾猜出 enum 外的值(例如 "skill"),
graph 端 Zod strict() 驗證會直接拒收,422 打回整個 envelope(不是只丟該欄位)。
scripts/ingest-cli.mjs 加了送出前的過濾:非白名單值直接刪掉該欄位(寧可欄位缺,不要
整批被拒)。這是 ingest 端的責任(契約寫的很清楚「entity_type 沒把握就不要填」,
但沒堵住模型亂填的可能)。
撞牆④(比較重要):POST /triplets/ingest 不是原子的,client 中斷會留半殘資料
第一輪批次跑(3 張卡)用 timeout 180 包整支 CLI,結果卡 2(Prompt能力即拆解自己邏輯的能力.md)
萃取完 7 個 triplets 後,POST 到一半整支 node 行程被 timeout 砍掉 —— 但 graph 端已經
把其中 2 個 triplet 寫進去了(因為 ingestEnvelope 是 for-loop 逐條 createTriplet,
不是一次性交易)。事後查 GET /triplets 證實:這張卡的 source_uri 底下只有 2/7 條,
不是 0 條也不是 7 條,卡在中間。
更麻烦的是幂等性设计跟这个情境对不上:ingestEnvelope 的 dedup 邏輯是
「同 source.uri 下若已有 content_hash 相同的 active 記錄 → 整批 skip」。
因為檔案內容沒變(同一份卡片重跑),content_hash 一定相同 —— 意味著這 2/7 的半殘狀態
會被後續重跑永久當成「已處理過」直接跳過,不會自動補完。目前唯一的修復方式是人工介入
(改內容強制 hash 變化,或直接呼叫 graph 的單條 POST /triplets 補寫缺的 5 條)。
這次沒有為了掩蓋而重送假造一致的資料——是老實留著,另外用沒有 timeout 限制重新跑了 第 3 張卡(乾淨的 5/5),第 2 張的半殘狀態原樣留在 base 裡當作真實證據,可用下面的 curl 驗證。
建議記入正式設計(不是這次補跑範圍,留給 leo/graph 端評估):
POST /triplets/ingest對於「同 hash 但實際 triplet 數量對不上已寫入數量」的情況, 應該要能偵測並允許補完,而不是無條件 skip。- 或者 ingest 端自己在 POST 前後做一次「數量核對」,不一致就重試/告警,而不是默默放過。
這次做了什麼(scripts/ingest-cli.mjs)
最小可行 CLI,只走「路徑 A 簡化版」:不是拉「已存在的裸三元組」(notes 裡沒有這種東西),
而是拉已經被之前一輪 cloud-worker 精耕過的 wiki 卡(Leo/notes 的
system-dev/wiki/cards/notes/*.md,2026-07-02 產出的 3 張),對每張卡用 Haiku 萃取
triples + node gloss,組 contracts/ingest-candidate.json 規定的 envelope,
POST 給 graph 插件的 /triplets/ingest。
source.uri 格式從契約範例的 github:<owner>/<repo>@<path> 改成
gitea:Leo/notes@<path>(因為整個堆疊都在 Gitea 不是 GitHub,契約本身沒有嚴格要求
一定要 github: 前綴,只要求非空字串 + 穩定識別)。
未做(老實列出,不是這次範圍)
- T1 SourceAdapter 自動化(GitHub/Gitea API 拉 repo + per-file content-hash 自動判斷變動)— 這次是手動 clone + 手動指路徑。
- T2.2 cherry-pick
polaris/mira/tools/_kbdb_client.py— 沒做,ingest-cli.mjs是重新寫的最小版本,不是 cherry-pick 移植。 - T3.1/3.4 完整 extract SOP(JSON-fail 升級 deep tier 等)— 這次沒做失敗重試/升級邏輯,Haiku JSON 解析失敗就直接跳過該卡。
- T4 跨 repo 織網 — 完全沒碰,這次只餵了
Leo/notes一庫。 - 沒建
package.json/wrangler.toml(T0.5)—ingest-cli.mjs是純 Node script,不是 Worker,跟原規劃的「插件也是 CF Worker」形態不同,值得之後討論薄 CLI 到底要不要是 Worker。
驗證用 curl(供複驗)
BASE=https://kbdb-graph-plugin.leo21c.workers.dev
# 完整批次總覽
curl -sS "$BASE/triplets/stats" | jq .
# 卡1(Gitea 卡)— 應該 7/7 乾淨
curl -sS "$BASE/triplets?subject=Gitea" | jq '.count'
# 卡2(Prompt能力卡)— 應該只有 2/7(半殘證據,見撞牆④)
curl -sS "$BASE/triplets?limit=100" | jq '[.triplets[] | select(.source_uri == "gitea:Leo/notes@system-dev/wiki/cards/notes/Prompt能力即拆解自己邏輯的能力.md")] | length'
# 卡3(程式化邏輯卡)— 應該 5/5 乾淨(第二輪無 timeout 補跑)
curl -sS "$BASE/triplets?limit=100" | jq '[.triplets[] | select(.source_uri == "gitea:Leo/notes@system-dev/wiki/cards/notes/程式化邏輯可圖解任何主題不限AI.md")] | length'