e7a681a989
契約漂移修補:T3 的 strict Zod 鏡射舊 contract,ingest 照新 contract(ingest#1 升格)送向量化打標欄位會被 .strict() 擋成 422。方向 A:顯式加合法新欄位、保留 strict。 - 同步 contracts/ingest-candidate.json 副本到頂層單一真相源(mira-dissolve)。 - NodeSchema 加 id?/aliases?/embed?;EdgeSchema 加 predicate_embed?。strict() 保留 → bridge_score/clusters 等 graph 領域禁送欄位仍 422。 - 落地:predicate_embed 透傳進 triplet slot;node 打標(embed/gloss/aliases)存進 entity slot,供 base/KBDB embed 模組讀標執行(graph 不算向量,鐵律一致)。 - id 作 node 去重鍵:同卡多邊指到只存一筆 entity。 - persistNodes 拆成獨立 action(triplet-ingest.ts 回到 95 行,守樂高 100 行限制)。 - 測試 +4:帶向量化欄位通過、bridge_score/clusters 仍 422、同 id 去重。 vitest 23 passed。零 SQL / 無 D1·Vectorize·AI 綁定 / dry-run 乾淨。 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
contracts/ — graph 插件邊界契約
本目錄放 graph 插件對外的 JSON Schema 契約。兩份契約是不同方向的東西,別混。
兩份契約的分工
| 檔案 | 是什麼 | 方向 | 誰產 / 誰收 |
|---|---|---|---|
ingest-candidate.json |
輸入候選(envelope) | ingest 插件 → graph 插件 | ingest 萃取後送進來,graph 收下處理 |
triplet.json |
已存三元組(graph 內部存的單筆 S-P-O) | graph 內部 / 對查詢面 | graph 寫進基本盤 records 後的形態 |
核心區別:ingest-candidate 是「還沒進庫的一批原始萃取」,triplet 是「已經正規化、存好、可查的單筆」。
- ingest 只送原始
(subject, predicate, object) + confidence,禁送 graph 領域欄位(id/clusters/bridge_score/created_at/ triplet 上的*_entity_type)。送了 → graph 以 422 拒收(additionalProperties: false)。 - 正規化、clusters、bridge_score、embed、
status/superseded_by取代邏輯——全是 graph 領域,ingest 一無所知(純餵食器)。
ingest-candidate envelope 重點
- 一個 envelope = 一個來源檔(canonical MD)一次萃取的產物。
source.uri(穩定識別)+source.content_hash(快照鍵)共同決定 idempotency:- 同 uri + 同 hash → no-op(
{skipped:true})。 - 同 uri + 新 hash → deprecate-then-append(舊 active 翻
status=deprecated+superseded_by,append 新批 active)。
- 同 uri + 同 hash → no-op(
nodes[](選填)帶gloss/entity_type—— 節點屬性,不是邊屬性,故獨立於triplets[]。graph 用 gloss 去 embed(每節點一句,非裸詞)。
完整 schema 與欄位說明見 ingest-candidate.json 的 description / $comment。
本 repo 對此契約的實作(端點、取代邏輯、測試)見 docs/3-specs/ingest-contract/。