# 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)。 - `nodes[]`(選填)帶 `gloss` / `entity_type` —— **節點屬性**,不是邊屬性,故獨立於 `triplets[]`。graph 用 gloss 去 embed(每節點一句,非裸詞)。 完整 schema 與欄位說明見 `ingest-candidate.json` 的 `description` / `$comment`。 本 repo 對此契約的實作(端點、取代邏輯、測試)見 `docs/3-specs/ingest-contract/`。