Skip to content

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.py and the id-138 migrations as they stand after {138.14} (commit 4d39a4ed, 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.

The corpus-bucket object_keysource_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.

_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).

SeedFormulaKeyed onMinted whereCite
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_identityflow.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_bodyflow.py:3869
ci: (content_items)uuid5(NS, "ci:"+source_document_id)stored source_document_id (registry-keyed since {138.10})content branchflow.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 branchflow.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 branchflow.py:2394-2396; flow.py:3528-3530
ri: (reference_items)uuid5(NS, "ri:"+item.url)normalised URL_ingest_url_bodyflow.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-2508
  • er: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.

  • 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_path only — never storage_path (the frozen admission key) and never source_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_reprocess transition note (DR-023): seeds already minted under the OLD rel_path formula (pre-{138.10}, i.e. ci:/chunk:/qa: seeded directly on rel_path rather than on the stored source_document_id) are absorbed via a one-time full_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 the qa: 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. A database.types.ts regen 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.

  • 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)