feat(km-wiki-ingest): 新增無人值守 drainer runtime(cursor + cron + Gitea webhook)

Phase b:把 ingest 從手動直驅升級成真無人值守。drainer/ 是 workflow
km_wiki_ingest_drain 的可部署執行體(Cloudflare Worker),復用已 live 的
arcrun-code/kbdb/graph、不改 cypher-executor。

- 兩觸發共用 processCard 核心:(A) cron drain(Gitea tree 列卡→游標後取一小批→
  逐卡處理→進游標→到底回捲;撐 5,083 檔);(B) Gitea push webhook→只處理 delta。
- 游標存 KBDB 一個 ingest_cursor entry(走 API、零建表)。冪等:未改卡 entry
  (page_name+content_hash)/triplet(uri+hash) 皆 skip,只有新/改卡真寫。
- src/usercode.ts 由 workflow.yaml parse_card.code 抽出(單一真相源 card-to-envelope.mjs)。
- 維持非-auto:wrangler.toml 的 cron trigger 預設註解(不自走),drain 走 POST /drain。
  scheduled handler 已在 code,leo 過閘後取消註解即開自走。README 記部署/secret/webhook 設定。

已部署 leo21c:arcrun-km-wiki-drainer.leo21c.workers.dev。以 wrangler dev(本地 miniflare
+ .dev.vars token,非 secret 寫入)對 live 服務驗證:cron drain 冪等全 skip、游標前進+回捲、
webhook delta 只寫變更卡、三模式命中新卡。GITEA_TOKEN secret 需 leo 於互動模式設(auto 擋 secret 寫入)。

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 09:13:03 +00:00
parent befc63cfe0
commit 88b7fbf409
9 changed files with 1948 additions and 1 deletions
@@ -0,0 +1,70 @@
# arcrun-km-wiki-drainer —— km_wiki_ingest_drain 的無人值守 runtime
`workflow.yaml` 是宣告式 spec;**本 Worker 是它可真部署、有游標/觸發的執行體**。
復用已 live 的零件與服務,**不改 cypher-executor**
```
Gitea 抓卡 md → arcrun-code 零件(sandbox inline JS 解析)→ /entries 冪等 upsertembed:true
→ graph /triplets/ingestserver 側 per-source 冪等)
```
- Live`https://arcrun-km-wiki-drainer.leo21c.workers.dev`
- 解析 JS`src/usercode.ts`(由 `workflow.yaml``parse_card.code` 抽出,單一真相源 `lib/card-to-envelope.mjs`)。
## 兩觸發模式(共用同一 `processCard` 核心)
| 模式 | 觸發 | 行為 |
|---|---|---|
| **Phase 0 cron drain** | cron tick(見「開自走」)| `drainBatch(BATCH_SIZE)`Gitea tree 列卡 → 游標之後取一小批 → 逐卡 `processCard` → 進游標(到底**回捲**重掃)。壓 CF subrequest 下、撐得住 5,083 檔(每 tick 小批)。 |
| **穩態 webhook** | Gitea push webhook → `POST /webhook` | 只處理本次 commit 動到的 `system-dev/wiki/cards/**.md`delta),不重掃全庫。**Gitea→Cloudflare,非 GitHub Actions(不觸 D20**。 |
**冪等**:未改卡的 entry`page_name`+`content_hash`)與 triplet`source.uri`+`content_hash`)皆 skip;只有新/改卡真寫。游標存 KBDB 一個 `ingest_cursor` entry(走 API、零建表)。
## HTTP 路由
| 方法 | 路徑 | 用途 |
|---|---|---|
| GET | `/` | 健康 |
| GET | `/cursor` | 看游標狀態 |
| POST | `/drain?batch=N` | 手動觸發一批 drain= 一個 cron tick;驗收/補跑用) |
| POST | `/webhook` | Gitea push webhook 入口(delta |
| POST | `/cursor/reset` | 游標歸零(重新全 drain) |
## ⚠️ 非-auto(預設)
`wrangler.toml``[triggers] crons` **預設註解掉**——不自走排程、不無人值守亂寫。
drain 以 `POST /drain` 手動觸發(與 cron `scheduled` handler 同核心)。
要開自走 Phase 0 backfillleo 過閘後取消 `wrangler.toml` 那兩行註解再 deploy。
## 部署(wrangler 直推 leo21c,禁 acr update
```bash
cd registry/examples/km-wiki-ingest/drainer
npm install
npx wrangler deploy
# ★ 必須:設 Gitea 讀取權(Leo/notes 為 private repo)——此步需 leo 在互動模式跑(secret 寫入)
printf '%s' "$GITEA_TOKEN" | npx wrangler secret put GITEA_TOKEN
# 選填:webhook 簽章驗證
# printf '%s' "$WEBHOOK_SECRET" | npx wrangler secret put GITEA_WEBHOOK_SECRET
```
## Gitea webhook 設定(穩態 delta
`Leo/notes` → Settings → Webhooks → Add Webhook → Gitea
- Target URL`https://arcrun-km-wiki-drainer.leo21c.workers.dev/webhook`
- HTTP MethodPOSTContent-Type`application/json`
- TriggerPush events(可只限 `main`
- Secret:若設,同步 `GITEA_WEBHOOK_SECRET` secret(本 Worker 驗 `X-Gitea-Signature` HMAC-SHA256
- Gitea 的 ping event → Worker 回 `{ok:true,pong:true}`(測連通)
## configwrangler.toml [vars]
`REPO=Leo/notes REF=main OWNER=leo CARDS_ROOT=system-dev/wiki/cards BATCH_SIZE=2`
`CODE_WORKER_URL / KBDB_URL / GRAPH_URL / GRAPH_KEY`(皆 leo21c live 服務)。
## 規模備註(5,083 檔)
- 列卡走 Gitea `git/trees?recursive=true`(一次全樹);若 `truncated`,回應標記 `truncated:true`
屆時換 tree 分頁列法(現況 notes 卡數小、不截斷)。
- 每 tick 只 `BATCH_SIZE` 張,游標續傳;到底回捲 → 全庫變更靠冪等於下一輪捕捉;
即時變更走 webhook。