feat(km-wiki-ingest): 重做成跑在 cypher 上的 workflow(走 cypher binding,汰換 service-binding drainer,D28)

把知識庫 ingest 從 standalone drainer(誤用 Service Bindings)重表達為 cypher workflow:

- workflow.yaml(Phase 0 cron drain):watch_cron→load_cursor→list_cards→pick_card(code)
  →fetch_card→parse_card(code)→upsert_entry→save_cursor→post_envelopes→post_one_envelope。
  線性 pipe,跨-worker 全走 cypher binding(零件節點),零 service binding。
- workflow.delta.yaml(Gitea webhook 穩態):collect_changed(code)→foreach card→fetch/parse/upsert/foreach envelope。
- code/kbdb/graph 接法:code=canonical `code` 零件(arcrun-code);kbdb/graph=http_request 零件打
  /entries/ingest、/triplets/ingest(server 端冪等);Gitea=http_request。
- 平台端最小補丁(各需 gated 部署):
  1) cypher-executor component-loader:WASM_HTTP_RUNNER_IDS 加 'code'(canonical→arcrun-code,cypher binding 正解)。
  2) kbdb base:POST /entries/ingest(page_name+content_hash 冪等 upsert,對稱 graph /triplets/ingest;
     因 flow DSL 無資料條件分支,把 create/patch/skip 冪等推到 server 端)。
- drainer/DEPRECATED.md:標舊 standalone worker 退役計畫(新版穩定後 wrangler delete)。
- DEPLOY.md:部署順序、cron/webhook 掛法、subdomain 對齊、驗收與退役。

本輪不部署(待總管/leo 審架構)。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HJiLCRUU2o3aSpPEzVCt2o
This commit is contained in:
kbdb-cc
2026-07-06 12:59:13 +00:00
parent c6bd0d7899
commit c6932b0088
6 changed files with 765 additions and 69 deletions
+103
View File
@@ -0,0 +1,103 @@
# km-wiki-ingest — 部署(cypher workflow 版,走 cypher binding
> **架構(D28**:知識庫 ingest **跑在 cypher-executor 上的 workflow**,跨-worker 呼叫全走
> **cypher binding**(零件節點,由 component-loader 解析),**零 Service Bindings**。
> 汰換舊的 standalone `arcrun-km-wiki-drainer``drainer/`,已誤用 service binding → 見 `drainer/DEPRECATED.md`)。
>
> **本輪不部署**(等總管/leo 審架構)。以下是過閘後的部署順序。
## 0) 先決條件(兩個平台端小改,皆本分支已含,需各自 gated 部署)
這兩處是「讓 workflow 能純走 cypher binding」的最小平台補丁,**不是** ingest 專屬邏輯:
1. **cypher-executor`code` 零件進 loader 白名單**
- 檔:`cypher-executor/src/lib/component-loader.ts``WASM_HTTP_RUNNER_IDS``'code'`(本分支已加 1 行)。
- 效果:workflow 節點 `component: code` → 解析成 `arcrun-code.{WORKER_SUBDOMAIN}.workers.dev`cypher binding)。
- 部署:`cd cypher-executor && npx wrangler deploy`(要 leo 過閘;動到共用 executor)。
- **替代(不改 executor**:把兩個 workflow 裡 `component: code` 改成 code 的完整 URL
`component: "https://arcrun-code.<subdomain>.workers.dev"`(走 loader step 2 外部 URL)。省一次 executor 部署,
但少了「canonical 零件」的乾淨語義。建議用白名單版(這才是 D28 說的 cypher binding)。
2. **KBDB base`POST /entries/ingest`server 端冪等 upsert**
- 檔:`kbdb/src/routes/entries.ts`(本分支已加)。語義對稱 graph 的 `/triplets/ingest`
page_name 當鍵、`content_hash` 同 → skip、不同/無 → update、無則 create。
- 為何需要它:cypher 的 flow DSL **無資料條件分支**(只有 ON_SUCCESS/ON_FAIL/FOREACH),
無法在 workflow 內做「查→比對→create 或 patch」三岔。把冪等推到 server 端,workflow 只打一發。
- 部署:`cd kbdb && npx wrangler deploy`(要過閘)。
> ⚠️ **subdomain 對齊**:兩個 workflow 內的 URL 用 `uncle6-me`(官方 `WORKER_SUBDOMAIN`,見 `cypher-executor/wrangler.toml`)。
> 若你的 arcrun-kbdb / kbdb-graph-plugin / arcrun-code 部在別的 workers.dev subdomain(例 `leo21c`),
> 把兩檔的 URL 一起改。**舊 standalone drainer 誤用 `leo21c` 是坑之一。**
## 1) CLI 設定
```bash
# self-hostednamespace 明碼分區):
acr config set mode self-hosted
acr config set cypher_url https://cypher.arcrun.dev # 或你自架的 executor
echo "NAMESPACE=leo" >> .env
# standard(平台多租戶):acr init 取 api_key
```
## 2) 設 Gitea token(機密;節點以 {{credential.gitea_token}} 引用)
```bash
acr creds set gitea_token <你的 GITEA_TOKEN> # 讀 Leo/notes 用
```
> graph 的 `X-Arcrun-API-Key: leo` 是 namespace 字串(非機密),已內嵌節點,不需設 credential。
## 3) 部署 Phase 0 draincron 觸發)
```bash
acr push workflow.yaml
```
- `watch_cron``cron_expr: "*/2 * * * *"` 會被 `webhooks-named``extractCronExpr` 抓出,
寫進 `cron-idx:_all`cypher `scheduled()` 每分鐘比對觸發本 workflow**不需另設 CF cron**)。
- 游標**不需 seed**:首跑 `load_cursor` GET 回空 → `pick_card` 從頭;`save_cursor` 首 tick 自動 create。
- 手動補跑一 tick(驗收):`acr run km_wiki_ingest_drain`
## 4) 部署穩態 deltaGitea webhook 觸發)
```bash
acr push workflow.delta.yaml
```
- 取得 trigger URLself-hosted 例):
`https://cypher.arcrun.dev/webhooks/named/leo/km_wiki_ingest_delta/trigger`
- Gitea repo `Leo/notes`**Settings → Webhooks → Add Webhook → Gitea**
- Target URL:上面的 trigger URL
- Content-Type`application/json`
- TriggerPush events
- standard 模式改帶 `X-Arcrun-API-Key` headerself-hosted 免 headernamespace 在 path
- 這是 Gitea→Cloudflare(cypher)**非 GitHub Actions** → 不觸 GitHub flagD4/D20)。
## 5) 驗收
```bash
# entry 落地?
curl -s "https://arcrun-kbdb.uncle6-me.workers.dev/entries?owner_id=leo&entry_type=wiki_card&limit=5" | jq '.count'
# triplet 落地?
curl -s "https://kbdb-graph-plugin.uncle6-me.workers.dev/graph?owner_id=leo" | jq '.nodes|length'
# 游標?
curl -s "https://arcrun-kbdb.uncle6-me.workers.dev/entries?owner_id=leo&page_name=cursor:km_wiki_ingest_drain:Leo/notes" | jq '.entries[0].content'
```
冪等自證:連跑兩次 `acr run km_wiki_ingest_drain`,第二次同卡 entry `action:skipped`、graph per-source no-op。
## 6) 舊 standalone drainer 退役
- 全 drain 完 + delta webhook 穩定收斂後,退役 `arcrun-km-wiki-drainer``wrangler delete arcrun-km-wiki-drainer`
- 保留 `drainer/` 原始碼一個週期(含 `DEPRECATED.md`)供對照,確認新版逐卡輸出與舊版一致再刪。
-`drainer/DEPRECATED.md`
## 節點圖速覽
**draincron**`watch_cron(cron)``load_cursor(http GET kbdb)``list_cards(http GET Gitea tree)`
`pick_card(code)``fetch_card(http GET Gitea raw)``parse_card(code)`
`upsert_entry(http POST kbdb /entries/ingest)``save_cursor(http POST kbdb /entries/ingest)`
`post_envelopes(foreach)``post_one_envelope(http POST graph /triplets/ingest)`
**deltawebhook**`collect_changed(code)` →〔對每個 card〕→ `fetch_card_d``parse_card_d(code)`
`upsert_entry_d(http)``post_envelopes_d(foreach)``post_one_envelope_d(http)`
零件對應的 cypher binding`code`→arcrun-code`http_request`→arcrun-http-request(打 Gitea/kbdb/graph);
`cron`→觸發登記;`foreach_control`→平台 logic primitive(唯一允許的 service-binding 用法:零件等級,非本工作流編排)。
**全程無工作流等級的 service binding。**