ID-138 {138.15} SEED-CONTRACT — Corpus-bucket object-key & uuid5 seed freeze
SEED-CONTRACT — Corpus-bucket object-key & uuid5 seed freeze
Section titled “SEED-CONTRACT — Corpus-bucket object-key & uuid5 seed freeze”Status: FROZEN at first bundle publication (id-132 BI-20/21). Authored under {138.15} (TECH.md §2.1 R(a), §4). This document is the single source of truth for the corpus-bucket object-key scheme and the uuid5 seed formulas — any change to a formula below after freeze is a BREAKING change that orphans the citation graph and blocks bundle publication (TECH.md §6 CRITICAL risk).
Formulas below are cited byte-for-byte against
scripts/cocoindex_pipeline/flow.pyand the id-138 migrations as they stand after {138.14} (commit4d39a4ed, the base this Subtask worked from). Re-verify citations against the working tree before relying on a line number if further id-138 subtasks land.
1. The object-key ruling (R(a))
Section titled “1. The object-key ruling (R(a))”The corpus-bucket object_key ≡ source_documents.storage_path — the admission-time
rel_path, stored VERBATIM, no transform (no re-encode, no case-fold, no prefix). Example:
markdown/Phew-Bid-Library-2026-v4_4.md.
This is what makes the corpus→bucket lift identity-neutral: existing rows’
storage_path values become the bucket object keys verbatim, so every uuid5 seed derived
from that key (directly or transitively) is unchanged — zero identity churn (s440 §5.1,
DR-023). See scripts/tests/test_cocoindex_seed_contract_lift.py for the behaviour proof,
including a disproof-by-construction that a NON-identity lift (a transformed key) would
churn identity.
Interplay with R(id) (admission-minted identity, DR-024 clause i): the frozen object
key is the admission-time key, used for the ONE-TIME deterministic mint only. Post-mint,
source_documents.id is authoritative and never re-derived. A client-facing rename
updates the mutable logical_path attribute only (§2.2, TECH.md) and does not move
the bucket object or re-mint identity. The object key therefore records provenance
(“admitted as X”), not the current display path.
2. The frozen namespace
Section titled “2. The frozen namespace”_KH_PIPELINE_DOC_NS = uuid.UUID("fbfaf1ff-1ee4-583c-9757-1674465b2ec1")scripts/cocoindex_pipeline/flow.py:1708. This is the SAME namespace the SQL-side
precedent public.reference_ingest already used
(20260619130100_id112_reference_ingest_derive_method.sql:29-31,
20260617130000_squash_baseline.sql:4564-4565) via
extensions.uuid_generate_v5(namespace, ...) — uuid_generate_v5 (uuid-ossp, RFC 4122
v5/SHA-1) is byte-identical to Python’s uuid.uuid5 given the same namespace + name
string, so the Python walk and the SQL-side admission resolver mint the SAME id for the
same key. A namespace divergence here would silently orphan every citation at first
bundle publication (id-132 BI-20/21).
3. Seed formulas (frozen)
Section titled “3. Seed formulas (frozen)”| Seed | Formula | Keyed on | Minted where | Cite |
|---|---|---|---|---|
sd: (register identity) | uuid5(NS, "sd:"+rel_path) | rel_path (= storage_path at admission) | ONE-TIME at admission only — SQL resolver public.resolve_or_mint_source_identity | flow.py:1994 (provisional log-correlation id, NOT the stored identity post-{138.10} — see §4); 20260703160100_id138_admission_identity_fn.sql:70-71 (the TRUE mint); called from Python via _resolve_source_identity (flow.py:2877-2919) at flow.py:2162 (content branch) and flow.py:3443 (ingest-once one-shot branch) |
sd: (URL/reference route) | uuid5(NS, "sd:"+item.url) | normalised URL | _ingest_url_body | flow.py:3869 |
ci: (content_items) | uuid5(NS, "ci:"+source_document_id) | stored source_document_id (registry-keyed since {138.10}) | content branch | flow.py:2174 |
chunk: (content_chunks) | uuid5(NS, "chunk:"+source_document_id+":"+position) | stored source_document_id (registry-keyed since {138.10}) | content branch; ingest-once one-shot branch | flow.py:2348-2350; flow.py:3502-3504 |
qa: (q_a_extractions) | uuid5(NS, "qa:"+source_document_id+":"+idx) | stored source_document_id (registry-keyed since {138.10}) | content branch; ingest-once one-shot branch | flow.py:2394-2396; flow.py:3528-3530 |
ri: (reference_items) | uuid5(NS, "ri:"+item.url) | normalised URL | _ingest_url_body | flow.py:3870 |
Post-mint, source_document_id is authoritative and never re-derived from rel_path
(R(id), DR-024 clause i) — the M2 resolver (public.resolve_or_mint_source_identity,
20260703160100_id138_admission_identity_fn.sql) resolves by content_hash FIRST; same
bytes at a new rel_path resolve to the STORED id and update only the mutable
logical_path column; only a genuinely new content_hash mints a new identity. The
sd:{rel_path} formula therefore fires exactly ONCE per document, at first admission —
never again, including across renames. (Note: flow.py:1994 still computes the
sd:{rel_path} form eagerly on every walk entry, but only as a provisional
log-correlation handle for the pre-fork failure paths, which write zero rows — the walk
itself resolves the STORED identity post-fork via the M2 resolver, per the {138.10}
comment at flow.py:1985-1993. It is not the mint.)
4. Known exception — em:/er: remain rel_path-keyed in ONE branch (F4 gap, accepted)
Section titled “4. Known exception — em:/er: remain rel_path-keyed in ONE branch (F4 gap, accepted)”The entity_mentions (em:) / entity_relationships (er:) seeds in the
engine-declared content branch (_ingest_content_branch, the keep-and-watch /
orphan-cleaned route) are STILL rel_path-keyed, not registry-keyed:
em:—uuid5(NS, "em:"+rel_path+":"+per_doc_canonical+":"+entity_type)—flow.py:2505-2508er:—uuid5(NS, "er:"+rel_path+":"+source_c+":"+predicate+":"+target_c)—flow.py:2579-2582
This is a real gap (F4): a rename of a keep-and-watch document would re-mint these two
row classes (new ids at the new rel_path), producing transient duplicates until the
engine’s orphan-cleanup reaps the old rows on the next walk. It is accepted and tracked
rather than fixed in this Subtask because: (a) entity_mentions/entity_relationships in
this branch are engine-declared, orphan-cleaned, and rebuildable from a re-walk — a rename
churn here is self-healing, not data loss (contrast the ingest-once class below, which
would lose data permanently without registry-keying); (b) fixing it is R(e)/§3.2 scope
already tracked in TECH.md, not a bucket-lift concern.
Contrast — the ingest-once one-shot branch’s em:/er: are ALREADY registry-keyed
(landed under {138.11} P4, R(e)): uuid5(NS, "em:"+source_document_id+":"+per_doc_canonical+":"+entity_type)
(flow.py:3572-3574) and uuid5(NS, "er:"+source_document_id+":"+source_c+":"+predicate+":"+target_c)
(flow.py:3614-3617) — required there because ingest-once derived rows must survive a
rename/re-derivation with zero data loss (R(e), §2.5).
Why this exception does not break the bucket-lift identity-neutrality claim (R(a)): a
corpus-bucket lift is, by definition, never a rename — object_key = storage_path
verbatim means the lift touches no key at all. The F4 gap is a rename-tolerance
concern (R(id)), exercised only when a client mutates the logical path, which is a
distinct operation from the lift. scripts/tests/test_cocoindex_identity_core.py covers
rename-tolerance; scripts/tests/test_cocoindex_seed_contract_lift.py (this Subtask)
covers the lift.
5. Freeze semantics
Section titled “5. Freeze semantics”- Frozen at first bundle publication (id-132 BI-20/21) — no seed formula above may change after the first OKF bundle is published; a post-freeze formula change silently orphans the citation graph (TECH.md §6 CRITICAL risk).
- Renames update
logical_pathonly — neverstorage_path(the frozen admission key) and neversource_documents.id. This severs the R(id) identity problem from the R(a) bucket-key freeze cleanly (TECH.md §2.1). - One-time pre-launch
full_reprocesstransition note (DR-023): seeds already minted under the OLDrel_pathformula (pre-{138.10}, i.e.ci:/chunk:/qa:seeded directly onrel_pathrather than on the storedsource_document_id) are absorbed via a one-timefull_reprocess(a pre-launch tool run) — these are engine-owned derived row classes, already rebuildable, and no bundle has published yet, so there is no live citation graph to orphan at the time of this transition.q_a_pairs(the promoted/ curated class) is unaffected: its PK is independently minted at promotion, never derived from theqa:seed (TECH.md §2.2).
6. Migration status (M1–M5 + api-regen applied at S445 GO#2; only the lease migration is pending)
Section titled “6. Migration status (M1–M5 + api-regen applied at S445 GO#2; only the lease migration is pending)”The id-138 migration serial implementing the M2 resolver was applied to BOTH staging
(rbwqewalexrzgxtvcqrh) AND prod (zjqbrdctesqvouboziae) at S445 GO#2;
database.types.ts was regenerated at commit 51767eef. On-disk filenames (as seen in
this worktree at commit 4d39a4ed):
20260703160050_id138_sd_source_binding_cols.sql(M1 — source-binding + lifecycle columns) — APPLIED (S445 GO#2)20260703160100_id138_admission_identity_fn.sql(M2 —resolve_or_mint_source_identity, the SEED-CONTRACT mint fn) — APPLIED (S445 GO#2)20260703160200_id138_erasure_cascade_fn.sql(M3 — GDPR tombstone cascade) — APPLIED (S445 GO#2)20260703160300_id138_orphan_reaper_fn.sql(M4 — orphan reaper) — APPLIED (S445 GO#2)20260703160400_id138_writer_fence.sql(M5 — superseded writer-fence primitive) — APPLIED (S445 GO#2)20260703200000_id138_sd_source_binding_api_regen.sql(api-schema regen) — APPLIED (S445 GO#2)20260703210000_id138_api_rpc_wrappers.sql(RPC wrappers) — APPLIED (S445 GO#2)20260704120000_id138_writer_fence_lease.sql(the pooling-safe writer-fence lease that REPLACES the session-scoped advisory lock, {138.9}, authored this session) — PENDING, the SOLE unapplied migration, awaiting the parent’s owner-gated coordinated GO. Adatabase.types.tsregen follows immediately after that apply.
(In-repo header comments on these files reading “authored, NOT applied” / “owner-gated coordinated GO” predate S445 GO#2 and are stale — do not trust them over this status; the orchestrator’s dispatch state is ground truth here.)
Applying the lease migration is an owner-gated coordinated GO — see the dry-run checklist (companion to this document, delivered alongside {138.15}) for the sequencing.
7. References
Section titled “7. References”- TECH.md §2.1 R(a) (bucket object-key/path scheme freeze × SEED-CONTRACT)
- TECH.md §2.2 R(id) (admission-minted identity, rename-tolerant, DR-024 clause i)
- TECH.md §2.5 R(e) (survives-the-engine per-record-class contract)
- TECH.md §4 (testing and validation — R(a) identity-neutral migration test row)
- DR-023 (source-binding model), DR-024 clause i (admission-minted identity), DR-025 (corpus reframe)
- id-132 BI-20/21 (first bundle publication blocks on R(id)/R(a))
scripts/tests/test_cocoindex_identity_core.py({138.10} — rename-tolerance proof)scripts/tests/test_cocoindex_seed_contract_lift.py({138.15} — this Subtask’s identity-neutral lift proof)