Skip to content

ID-131 {131.2} PRODUCT — OKF L-records refactor (content_items elimination)

PRODUCT — OKF L-records refactor (content_items elimination)

Section titled “PRODUCT — OKF L-records refactor (content_items elimination)”

Status: DRAFT — 28/06/2026. Behaviour invariants for the FOUNDATION Task of the four-Task OKF re-architecture. Authored by a fresh Planner (Q-PLANNER-2) from the ratified {131.1} RESEARCH.md, re-verified independently against current head + the live platform DB (zjqbrdctesqvouboziae).

Spec chain: {131.1} RESEARCH → {131.2} PRODUCT (this doc) → {131.3} TECH → {131.4} PLAN.

How to read this: every numbered Behaviour invariant (BI-N) is a testable statement the owner ratifies and the Checker verifies. Invariants tagged RATIFY: need the owner’s explicit call at the ratification gate before {131.3} TECH proceeds — they are the genuine product decisions the ratified model deliberately left open. Everything else LOCKS a decision already ratified in RESEARCH.md (do not re-litigate; if you believe one is wrong, flag it).

De-identification: the real first ingest corpus is referred to throughout as “the first client”. The client’s name is deliberately omitted.


content_items — a 67-column polymorphic god-table inherited from a personal information-management fork — is fully eliminated and its roles re-homed onto purpose-built typed records (source_documents, q_a_pairs, reference_items, content_chunks), a shared governance facet (record_lifecycle), a dedicated polymorphic embeddings store, and a polymorphic citation contract. The change is a pre-launch code/schema refactor with zero production data migration: the L-records database is rebuilt deterministically by full-replace re-ingest. ID-131 also declares a frozen seed contract so that, when a later Task publishes the client-owned OKF concept bundle, its canonical://<table>/<uuid> pointers stay valid across every subsequent rebuild.

content_items forces every consumer (search RPCs, citations, governance crons, the MCP find tool, forms matching) to reason over a generic blob instead of a typed record. The platform’s core value is high-quality structured data accessible by AI; a god-table is the opposite of structured. The cost is concentrated in a small number of behaviour-bearing surfaces, all re-verified at current head below.

Code-intelligence orientation (verbatim — Checker: confirm this ran). Tools: gitnexus_query / gitnexus_context, supabase execute_sql (project zjqbrdctesqvouboziae, 28/06/2026), grep over TS callers.

  • Search value path (gitnexus: lib/mcp/tools/search.tsregisterSearchTools lines 76–819, findSimilarItemsImpl 267–383; cluster Tools). hybrid_search RETURNS exactly 21 columns (pg_get_function_result: id, title, suggested_title, summary, primary_domain, primary_subtopic, content_type, platform, author_name, source_domain, thumbnail_url, captured_date, ai_keywords, classification_confidence, priority, metadata, similarity, snippet, created_by, verified_at, verified_by). The MCP find tool maps only 8 (search.ts:191–202).
  • Citations writers (gitnexus: flow draft-streamroute.ts:start 185–410; reader effectiveness/route.ts:GET 35–187; lib/mcp/formatters/procurements.ts:CitationResult 169–177). Verified writers still emit cited_kind: 'content_item' at lib/mcp/tools/procurement.ts:463 and app/api/procurement/[id]/responses/draft-stream/route.ts:330.
  • Citation contract (supabase): cited_target_kind enum currently holds only {content_item, q_a_pair}; citations_cited_one_of_chk enforces exactly-one-of those two via per-kind nullable FKs (cited_content_item_id / cited_q_a_pair_id); citations = 0 rows.
  • Dual outcome-source bug (REAL at head) (supabase): get_content_win_rate derives outcome from form_outcome_types (counts_toward_win_rate = true; cited_kind-keyed), while hybrid_search derives it from workspaces.domain_metadata (domain_metadata = true; cited_kind-keyed). Two sources, one concept. get_content_win_rate has 2 live callers (effectiveness/route.ts:81, lib/mcp/tools/procurement.ts:544).
  • Record identity (supabase): source_documents carries only a primary key — NO UNIQUE(storage_path), NO UNIQUE(content_hash) — so the sd:{rel_path} seed IS the idempotency mechanism. q_a_pairs.id DEFAULT gen_random_uuid() (opaque master PK).
  • Embeddings inventory (supabase): 5 vector columns (content_chunks.embedding, content_items.embedding, form_template_requirements.requirement_embedding, q_a_pairs.question_embedding, reference_items.embedding) + company_profiles.company_embedding stored as TEXT + content_items.embedding_model (dies with the table).
  • Governance shape (supabase): q_a_pairs carries ONLY the hot subset (publication_status, superseded_by, valid_from, valid_to) and none of the cold axis; reference_items carries no governance/lifecycle columns at all; source_documents carries none of the classification or governance columns (the lean-21-col table).
  • Forms matching (gitnexus + grep): lib/domains/procurement/form-templating/template-coverage.ts fetchContentForMatching (line 519) SELECTs FROM content_items; thresholds SIMILARITY_STRONG_THRESHOLD = 0.55 / SIMILARITY_PARTIAL_THRESHOLD = 0.35; matched_content_ids / source_content_ids (uuid[]) span ai/draft.ts, draft-response.ts, item-provenance.ts, queue handlers, [rId]/route.ts.
  • Manual authoring path SURVIVES (grep): app/item/new/create-content-client.tsx → POST app/api/items/route.ts inserts into content_items with a content_type (incl. q_a_pair, populating answer_standard at route.ts:249), pre-filled by hooks/use-content-templates.tslib/content/content-templates.ts skeletons.

Row counts re-verified (28/06/2026): content_items 33 (E2E debris), content_chunks 0, q_a_pairs 0, q_a_extractions 0, citations 0, governance_config 0, content_templates 0, content_item_workspaces 0, reference_items 3, form_template_requirements 96. No ratified claim in RESEARCH.md was contradicted at head.

Figma: none — backend/data-model refactor. The only user-visible UI change is the removal of the deprecated IMS browse/item surface (BI-12); no new UI is designed in ID-131.

Goals. Fully eliminate content_items; re-home its roles onto typed records + the facet + the embeddings store + the extended citation contract; declare the frozen seed contract; resolve the four open sub-decisions below into ratified behaviour.

Non-goals (sibling Tasks — referenced for sequencing only). The L-concepts producer (the OKF bundle, the two-pass enrichment agent, the resource: URI scheme, the git knowledge-sync writer); the ontology pass (the three-layer re-alignment + the allowed_types/allowed_relations register); the {127.4} promotion-confidence E2E corpus. ID-131 re-parents the three extraction write-sites but does NOT touch the producer, the ontology register, or the synthetic corpus. Version-threading of source_documents (version/parent_id) is out of scope — same-path re-ingest overwrites in place, as today.


A. Preconditions — full-replace, zero data migration

Section titled “A. Preconditions — full-replace, zero data migration”
  1. BI-1. ID-131 performs no data migration. Every “re-parent” is a schema change plus a pipeline-writer change, never a backfill of existing content_items rows into a new table. There is no step that copies the 33 debris rows anywhere.

  2. BI-2. The L-records database is rebuilt by full-replace re-ingest on a new, empty preview branch. Re-ingesting the same gated corpus with the same recipe reproduces the same deterministic uuid5 record ids, so the database is disposable and reproducible and may be re-seeded any number of times pre-launch. No behaviour in ID-131 assumes a stable opaque master uuid survives a rebuild (see BI-5). This also covers the first-client production DB (D11 dissolved, 28/06/2026): it is not live (no real users), and the id-45 full-replace re-ingest (a cocoindex full walk) wipes and rebuilds it from the gated corpus — its current rows are pre-OKF debris, not data to migrate. The L-records DB is disposable in both environments; nothing is migrated, everything is regenerated from L-raw.

  1. BI-3. source_documents keeps its sd:{rel_path} identity seed (uuid5(_KH_PIPELINE_DOC_NS, "sd:" + rel_path); URL-ingest branch sd:{url}). Because the table has only a primary key (no UNIQUE on storage_path or content_hash), the seed string is the sole idempotency mechanism: re-ingesting the same path mints the same id and upserts in place. content_hash remains a stored audit/diff column, not the identity seed.

  2. BI-4. q_a_extractions (the raw tier) keeps its qa:{rel_path}:{idx} identity seed (idempotency-in-place over the gated corpus). It is not replaced by any question-hash.

  3. BI-5. q_a_pairs (the master/canonical tier) keeps its opaque gen_random_uuid() primary key (ratify-as-built — there is nothing to switch). Opacity is intentional: full-replace re-mints it, the many-raw→one-master merge is re-established by re-pointing extractions (promoted_to_pair_id), and nothing external pins a master uuid.

  4. BI-6. reference_items keeps its ri:{source_url} identity seed with the live UNIQUE(source_url) constraint; source_url remains a NOT NULL column on reference_items.

  5. BI-7 (SEED-CONTRACT). The record-identity seed contract is declared and frozen: the namespace constant _KH_PIPELINE_DOC_NS = fbfaf1ff-1ee4-583c-9757-1674465b2ec1 and the three citeable seed-string formats sd:{rel_path}, ri:{source_url}, qa:{rel_path}:{idx}. A CI test (an ID-131 deliverable, with no schema dependency so it can land early) asserts the namespace value and the three formats; any change to them fails CI. The two internal seeds ci:{rel_path} (dies with content_items) and chunk:{rel_path}:{position} (retrieval grain) are explicitly OUT of the contract and are never bundle-cited.

  6. BI-8 (anchors-only bundle citation — RATIFIED 28/06/2026, locked). The client-owned OKF bundle cites only deterministic anchors: {source_document, reference_item, concept}. q_a_pair citation is DB-internal only (draft reuse, forms completion, win-rate). No bundle or concept artefact ever carries a canonical://... pointer to a q_a_pair; a concept needing Q&A data points at q_a_pairs as a table/query via resource:, never per-row. Rationale (reframed 28/06/2026, D8): bundles are not record-based by design — a q_a_pair could be cited where it is the authoritative answer, but its provenance source (the originating source_document / reference_item) is the better, deterministic cite, and a bundle pointer to an opaque, re-minting q_a_pair uuid would in any case orphan on the next rebuild. The anchors-only conclusion is unchanged.

  1. BI-9. After ID-131 the content_items table does not exist. No code path reads from or writes to content_items, and no role it served is left without a typed home.

  2. BI-10. Every reference to content_items.id is re-homed per the ratified inventory — 10 enforced FKs + 2 unenforced bare-uuid columns + the 3rd bare-uuid ref q_a_extractions.source_content_item_id:

    • Re-parent to source_document_id: content_chunks.content_item_id (add the column — CC has none today), entity_mentions.content_item_id (add the column), entity_relationships.source_item_id, classification_disputes.content_item_id, q_a_extractions.source_content_item_id.
    • Re-parent to the facet owner / SD: ingestion_quality_log.content_item_id, verification_history.content_item_id (moves with governance).
    • Re-parent to a typed record (target deferred to TECH): feed_articles.content_item_id (see “Deferred to {131.3}”).
    • Drop: read_marks.content_item_id (IMS reading-tracker), content_item_workspaces (whole table), content_items.superseded_by (self), content_history.content_item_id (IMS history).
    • Extend the contract: citations.cited_content_item_id → BI-23.
  3. BI-11. Re-homing the classification family onto source_documents means adding ~25–30 net-new columns (primary_domain, primary_subtopic, secondary_domain, secondary_subtopic, ai_keywords, summary, classification_confidence, classification_reasoning, classified_at, updated_by, updated_at, …) because SD carries none today. classification_confidence, classification_reasoning and secondary_domain/secondary_subtopic ARE persisted onto source_documents (RATIFIED 28/06/2026, D1 — all are live-consumed: review-queue ordering + quality-score + MCP for confidence; provenance/audit for reasoning; review-card display + forms matching for the secondary pair), so flow.py is extended to write them; classification_model is dropped (dead — 0 stored consumers). created_by maps to SD’s existing uploaded_by. The IMS-vestige columns (author_name, platform, source_domain, starred, user_tags, priority, brief, detail, reference, file_path, source_file) are dropped, not re-homed. The Q&A duplicate columns (answer_standard, answer_advanced) are dropped (q_a_pairs already has them). source_url is dropped from the eliminated table but kept on source_documents (URL-ingest provenance) and reference_items (NOT NULL). thumbnail_url is net-new on reference_items (RATIFIED 28/06/2026, D4): it is added as an empty nullable column now, backfilled later. The ~55 first-client og:image values are wiped by the full-replace re-ingest (not migrated — the og:image writer was retired at ID-110); an optional follow-up may re-wire og:image capture into the new reference ingest so reference cards get thumbnails going forward.

  4. BI-12. The legacy IMS browse/item UI is deleted, not rewritten — including components/content/content-card.tsx, content-row.tsx, item-detail/metadata-sidebar.tsx, editor-view.tsx, browse/filter-panel.tsx, app/item/[id]/item-detail-client.tsx. These bind dropped IMS columns; their removal is scoped as deletion of a deprecated surface.

  5. BI-13. The quality_issues_pending VIEW (defined over content_items, 0 rows) is dropped or rewritten together with the table — it does not survive as a dangling consumer.

  1. BI-14. Entities and relationships stay in the database (no graph migration). The pipeline re-parents content_chunks, entity_mentions, and entity_relationships onto source_document_id, and the pipeline writer (flow.py) writes source_document_id for these from day one of the full-replace re-ingest, because content_items will not exist at that point. (No write-site may depend on a content_item_id parent after BI-9.)

  2. BI-15. q_a_extractions is re-parented onto source_document_id (its source_content_item_id link dies with content_items). q_a_pairs already carries source_document_id, so the master tier is unaffected.

  3. BI-16 (QA-DBONLY). The {59.x} Q&A sidecar round-trip is retired: the promoter no longer materialises promoted pairs back to the corpus as __qa__/*.md files. q_a_pairs.source_document_id is kept as pure-DB provenance. (A Q&A pair is a record, never a concept; writing it as a bundle file contradicts the concept≠record split.)

  1. BI-17. A dedicated polymorphic embeddings store exists, keyed (owner_kind, owner_id, model) with a vector column, where owner_kind ∈ {source_document, content_chunk, q_a_pair, reference_item, concept}. It absorbs the six scattered embedding columns (the five vector columns above and the company_profiles.company_embedding TEXT inconsistency, normalised to a vector). Its pgvector index is declared via a Supabase migration (not cocoindex declare_vector_index). One store serves both L-records hybrid_search and later L-concept bundle traversal, and it is ratified within ID-131 so the sibling producer Task can rely on it.
  1. BI-18. Governance/freshness/lifecycle/ownership is promoted into a shared, polymorphic record_lifecycle facet keyed (owner_kind, owner_id) with a denormalised domain for the policy join. The cross-type expiry sweep / review queue / cadence become set-based operations over the facet rather than per-type logic. (Per BI-22 the facet’s owner_kind is per-axis — the freshness/expiry axis and the review/governance axis carry different owner sets — RATIFIED 28/06/2026, D7.)

  2. BI-19 (reference_item governance exclusion — RESEARCH §5.2 calls this BI-16). On the governance/review axis only the facet’s owner_kind set is {source_document, q_a_pair} and a CHECK constraint excludes reference_item; absence of a governance facet row means a record is governance/review-free — reference_items is deliberately governance/review-free (verified: it carries no governance columns today). Tracked-deferred (not built in ID-131, D7): reference_item gains its own freshness / validity / supersession / contradiction awareness on the Intelligence-domain track (after id-130) — that lifecycle facet is recorded here as deferred so it is not lost, but no reference_item freshness/validity schema lands in ID-131.

  3. BI-20 (hot/cold split). The read-path “hot” lifecycle fields stay inline on the typed records — publication_status, superseded_by, valid_from, valid_to (q_a_search filters publication_status on every query; q_a_pairs already carries these NOT NULL). The cron/queue-driven “cold” fields move to the facet — freshness, freshness_checked_at, previous_freshness, lifecycle_type, expiry_date, next_review_date, review_cadence_days, governance_review_status, governance_review_due, governance_reviewer_id, verified_at, verified_by, content_owner_id, domain. quality_score and citation_count are derived (cron-computed / rollup), not stored. Per-axis caveat (BI-22, D7): the cold freshness/expiry subset (freshness, freshness_checked_at, previous_freshness, lifecycle_type, expiry_date) is owned by source_document only — a q_a_pair carries no cold freshness/expiry row and relies on its inline superseded_by + opt-in valid_to; the review/governance cold fields still span {source_document, q_a_pair}.

  4. BI-21. governance_config is unchanged (per-domain policy, UNIQUE(domain)). The facet’s denormalised domain is kept equal to the owner record’s primary_domain by a write-time sync, so the existing policy→state join in the freshness/review/quality crons keeps working in one hop after reclassification.

  5. BI-22 — Q&A freshness clock RATIFIED 28/06/2026 (per-axis; Q&A drops freshness, D7). The governance facet uses per-axis owner_kind. The freshness/expiry axis owner set is {source_document} only — a q_a_pair has no freshness/expiry cron axis and drops its own freshness clock (reversing the earlier “own clock” recommendation). A q_a_pair’s lifecycle is carried by supersession + dedup + an opt-in inline valid_to only (0 net-new facet columns for Q&A). The review/publication axis owner set remains {source_document, q_a_pair}. TECH implements this as a per-axis split (freshness axis vs review/governance axis), not a single uniform facet. Sub-clause: entity_mentions (which carries a JSON metadata->>'expiry_date' swept by freshness-transitions) stays on the metadata path and does not join the facet (entities are not first-class governed records).

G. Citations & win-rate (CITE-EXT + WINRATE-FIX)

Section titled “G. Citations & win-rate (CITE-EXT + WINRATE-FIX)”
  1. BI-23 (CITE-EXT). The citation contract is extended: cited_target_kind gains {reference_item, source_document, concept} and q_a_pair is activated as a real target (today it is dormant). The exactly-one-of CHECK is rewritten to span all kinds, the matching cited_*_id columns exist, and all writers (procurement.ts:463, draft-stream:330) and readers (items/route.ts, effectiveness/route.ts) source from the extended contract. DB-record targets use the §B record uuid; concept targets use the concept path.

  2. BI-24. CITE-EXT and WINRATE-FIX (BI-25/BI-26) land together as one atomic change. No intermediate state ships where the enum, the CHECK, the writers, the readers, and both win-rate consumers disagree about how a citation is anchored. (Safe pre-launch: citations = 0 rows, so no live citation can orphan; but a split landing would break every citation path.)

  3. BI-25 — RATIFY: single canonical win-rate outcome source. Win-rate has exactly one canonical outcome source. Recommended: form_outcome_types.counts_toward_win_rate (the id-130 purpose-built, typed outcome taxonomy), with hybrid_search.win_stats re-pointed off workspaces.domain_metadata->>'outcome' onto it. Owner’s call because it defines “what counts as a win”. This is resolved before either function is re-anchored to q_a_pair, or the dual-source disagreement propagates into the rewrite.

  4. BI-26. Once BI-25 is resolved, both win-rate consumers (get_content_win_rate — 2 live callers — and hybrid_search.win_stats) are re-anchored from cited_kind = 'content_item' to cited_kind = 'q_a_pair' (the reusable canonical answer is what wins or loses a bid). Their return shapes are otherwise preserved (get_content_win_rate still returns total_citations, winning_citations, losing_citations, pending_citations, win_rate).

  1. BI-27. The MCP find value-path output contract is preserved: the tool still returns the same 8 fields with the same semantics — id, title, suggested_title, content_type, primary_domain, primary_subtopic, summary, similarity. If the polymorphic rewrite changes any of these 8 (name, type, or meaning), the change is made explicit in TECH and surfaced to the owner — it is not allowed to drift silently.

  2. BI-28. The search RPCs are reworked without breaking BI-27: hybrid_search becomes a polymorphic UNION across source_documents / content_chunks / q_a_pairs / reference_items (and its win_stats CTE re-anchors per BI-26); find_related_items and filter_by_keywords are DROPPED ENTIRELY per {131.20} §9 (§7.4 related-records → an ontology-grounded backlog item; §7.5 keyword pre-filter → a future hybrid_search facet param), superseding the earlier re-anchor / keep-the-live-variant framing. The dedup RPCs (find_duplicate_pairs, find_exact_duplicates) re-target q_a_pairs (BI-32).

  1. BI-29 (RATIFIED 28/06/2026, D2). Forms matching is re-pointed off content_items onto typed records: q_a_pairs (primary) + reference_items (optional). source_documents is NOT a match target — it carries no embedding and no answer-grained content, so it is a provenance/citation-resolution target only (reached via content_chunks if document-body matching is ever wanted), never a direct vector-match source. fetchContentForMatching (template-coverage.ts) no longer SELECTs from content_items, and the uuid[] arrays form_responses.source_content_ids / form_questions.matched_content_ids carry q_a_pair / reference_item ids.

  2. BI-30. The blank-vs-answered fork (id-80) is preserved: an answered form’s Q&As promote to q_a_pairs (Path-A knowledge); a blank form instrument produces zero content rows (Path-B deterministic extractor). The refactor must not collapse this distinction — ingesting a blank form still yields no q_a_pairs/source_documents content rows.

    S438 {131.25} reconcile: DR-014/ID-136 retire the forms corpus-INGEST route — forms now enter via manual-upload → form_template_requirements. The blank-vs-answered distinction {131.16} preserves is the matching behaviour (search consumer), not the retired ingest fork; the “ingesting a blank form” framing above refers to that now-retired path.

  3. BI-31. Forms matching quality must not regress: because the matching thresholds (0.55 / 0.35) and cosineSimilarity were tuned against content_items embeddings, they are re-calibrated against the EMB-STORE vector space (BI-17). A silent degradation of match distribution is a regression, not an acceptable outcome.

  1. BI-32 (dedup home — resolves RESEARCH §6(d)). Normalised-text dedup belongs to q_a_pairs only — it is the corpus’s own “masters from many Q&As” merge, carried as a normalised-question hash matching the existing two-tier merge model. source_documents does not carry a content_text_hash column; SD idempotency comes from the rel_path seed + the content_hash audit column. The IMS dedup tooling is ported to q_a_pairs, not re-homed onto documents.

  2. BI-33 — manual q_a_pair authoring IN SCOPE (RATIFIED 28/06/2026, D3; resolves RESEARCH §6(e)). After ID-131 each record type is seeded by its natural path: source_document by file ingest (sd:{rel_path}, no manual seed), reference_item by URL (ri:{source_url}), q_a_pair by pipeline promotion (opaque PK + DB provenance) or by manual authoring (this BI). A manual q_a_pair authoring shape (question + answer_standard + answer_advanced + scope_tag[], writing directly to q_a_pairs with an opaque PK + recorded provenance origin) ships as an ID-131 sub-task — and it is reuse-and-repoint, not greenfield: the existing, shipped (S198) Q&A editor surface (the app/item/new create path + the components/qa Tiptap editor + the /library viewer) is rebound from content_items to typed q_a_pairs, and a new 'manually_authored' origin_kind is added so hand-authored pairs are auditably distinct from the 'curated_explicit' default and the 'derived_from_form_response' path. Provenance source_document_id is nullable — set only when the author cites an ingested document, otherwise NULL with origin_kind carrying the provenance (the form-response path, which already runs with source_document_id NULL, is the precedent). The generic content_item create form and the content_templates skeletons are still removed (the generic blob path dies; see BI-34) — the Q&A editor is repointed, not deleted; and no sidecar file is written for manually-authored pairs (BI-16 QA-DBONLY applies).

  3. BI-34. content_item_workspaces and content_templates are dropped (both 0 rows, both ratified). Cross-workspace scope that content_item_workspaces provided is handled at query time via scope_tag overlap; the content_templates skeleton idea, if wanted later, is re-homed as OKF concept-type templates owned by the sibling producer Task — not re-templated here.


Deferred to {131.3} TECH (not resolved in PRODUCT)

Section titled “Deferred to {131.3} TECH (not resolved in PRODUCT)”
  • (b) Facet FK idiom. Per-kind nullable FKs + an exactly-one CHECK (mirroring the verified citations_cited_one_of_chk idiom) vs (owner_kind, owner_id) + a validating trigger — a referential-integrity-vs-ergonomics trade for TECH. (BI-22 is RATIFIED to per-axis — a q_a_pair drops its own freshness clock — so the per-axis freshness-vs-review facet structure is a TECH consequence.)
  • (c) feed_articles re-parent target. reference_item vs source_document — the flow.py URL branch mints both an SD and an RI for a feed URL, so TECH verifies the feed writers before fixing the target.

Sequencing constraints carried into TECH/PLAN

Section titled “Sequencing constraints carried into TECH/PLAN”

Net-new schema first (the facet + EMB-STORE land before any re-parent or drop, so cron/queue and embedding consumers have a destination) → re-parents next (add source_document_id columns AND the flow.py write-sites in lockstep) → CITE-EXT + WINRATE-FIX as one atomic slice (after BI-25) → search-RPC bodies (after the typed columns exist) → drops last (content_item_workspaces, content_templates, content_items, the quality_issues_pending view, the IMS UI). The SEED-CONTRACT test (BI-7) has no schema dependency and can land early.

Quality bars (inherited by {131.3} TECH and every Executor)

Section titled “Quality bars (inherited by {131.3} TECH and every Executor)”

Semantic design tokens only; UK English (DD/MM/YYYY, “colour”, “organisation”); auth.success + authFailureResponse(auth); getAuthorisedClient() / sb() / tryQuery() Supabase safety; no barrel re-exports (direct file imports); TanStack Query only; bun run test (never bun test); behaviour-first tests (test-philosophy.md). Tool-discipline for the implementation wave: gitnexus impact({direction:'upstream'}) before modifying any symbol (warn on HIGH/CRITICAL), gitnexus detect_changes() before committing, gitnexus rename for renames; ast-dataflow column-reads/column-writes to pin every TS consumer site, grep for the Python pipeline + SQL migrations. High-blast-radius symbols: hybrid_search, get_content_win_rate, _ExtractionStamp, the governance freshness fns, fetchContentForMatching.