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.
Summary
Section titled “Summary”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.
Problem
Section titled “Problem”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.ts→registerSearchToolslines 76–819,findSimilarItemsImpl267–383; clusterTools).hybrid_searchRETURNS 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 MCPfindtool maps only 8 (search.ts:191–202). - Citations writers (gitnexus: flow
draft-stream→route.ts:start185–410; readereffectiveness/route.ts:GET35–187;lib/mcp/formatters/procurements.ts:CitationResult169–177). Verified writers still emitcited_kind: 'content_item'atlib/mcp/tools/procurement.ts:463andapp/api/procurement/[id]/responses/draft-stream/route.ts:330. - Citation contract (supabase):
cited_target_kindenum currently holds only{content_item, q_a_pair};citations_cited_one_of_chkenforces 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_ratederives outcome fromform_outcome_types(counts_toward_win_rate= true;cited_kind-keyed), whilehybrid_searchderives it fromworkspaces.domain_metadata(domain_metadata= true;cited_kind-keyed). Two sources, one concept.get_content_win_ratehas 2 live callers (effectiveness/route.ts:81,lib/mcp/tools/procurement.ts:544). - Record identity (supabase):
source_documentscarries only a primary key — NOUNIQUE(storage_path), NOUNIQUE(content_hash)— so thesd:{rel_path}seed IS the idempotency mechanism.q_a_pairs.id DEFAULT gen_random_uuid()(opaque master PK). - Embeddings inventory (supabase): 5
vectorcolumns (content_chunks.embedding,content_items.embedding,form_template_requirements.requirement_embedding,q_a_pairs.question_embedding,reference_items.embedding) +company_profiles.company_embeddingstored as TEXT +content_items.embedding_model(dies with the table). - Governance shape (supabase):
q_a_pairscarries ONLY the hot subset (publication_status, superseded_by, valid_from, valid_to) and none of the cold axis;reference_itemscarries no governance/lifecycle columns at all;source_documentscarries none of the classification or governance columns (the lean-21-col table). - Forms matching (gitnexus + grep):
lib/domains/procurement/form-templating/template-coverage.tsfetchContentForMatching(line 519) SELECTs FROMcontent_items; thresholdsSIMILARITY_STRONG_THRESHOLD = 0.55/SIMILARITY_PARTIAL_THRESHOLD = 0.35;matched_content_ids/source_content_ids(uuid[]) spanai/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→ POSTapp/api/items/route.tsinserts intocontent_itemswith acontent_type(incl.q_a_pair, populatinganswer_standardatroute.ts:249), pre-filled byhooks/use-content-templates.ts→lib/content/content-templates.tsskeletons.
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 / Non-goals
Section titled “Goals / Non-goals”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.
Behaviour
Section titled “Behaviour”A. Preconditions — full-replace, zero data migration
Section titled “A. Preconditions — full-replace, zero data migration”-
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_itemsrows into a new table. There is no step that copies the 33 debris rows anywhere. -
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
uuid5record 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.
B. Record identity & the seed contract
Section titled “B. Record identity & the seed contract”-
BI-3.
source_documentskeeps itssd:{rel_path}identity seed (uuid5(_KH_PIPELINE_DOC_NS, "sd:" + rel_path); URL-ingest branchsd:{url}). Because the table has only a primary key (noUNIQUEonstorage_pathorcontent_hash), the seed string is the sole idempotency mechanism: re-ingesting the same path mints the same id and upserts in place.content_hashremains a stored audit/diff column, not the identity seed. -
BI-4.
q_a_extractions(the raw tier) keeps itsqa:{rel_path}:{idx}identity seed (idempotency-in-place over the gated corpus). It is not replaced by any question-hash. -
BI-5.
q_a_pairs(the master/canonical tier) keeps its opaquegen_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. -
BI-6.
reference_itemskeeps itsri:{source_url}identity seed with the liveUNIQUE(source_url)constraint;source_urlremains a NOT NULL column onreference_items. -
BI-7 (SEED-CONTRACT). The record-identity seed contract is declared and frozen: the namespace constant
_KH_PIPELINE_DOC_NS = fbfaf1ff-1ee4-583c-9757-1674465b2ec1and the three citeable seed-string formatssd:{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 seedsci:{rel_path}(dies withcontent_items) andchunk:{rel_path}:{position}(retrieval grain) are explicitly OUT of the contract and are never bundle-cited. -
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_paircitation is DB-internal only (draft reuse, forms completion, win-rate). No bundle or concept artefact ever carries acanonical://...pointer to aq_a_pair; a concept needing Q&A data points atq_a_pairsas a table/query viaresource:, never per-row. Rationale (reframed 28/06/2026, D8): bundles are not record-based by design — aq_a_paircould be cited where it is the authoritative answer, but its provenance source (the originatingsource_document/reference_item) is the better, deterministic cite, and a bundle pointer to an opaque, re-mintingq_a_pairuuid would in any case orphan on the next rebuild. The anchors-only conclusion is unchanged.
C. content_items elimination (CI-DECOMP)
Section titled “C. content_items elimination (CI-DECOMP)”-
BI-9. After ID-131 the
content_itemstable does not exist. No code path reads from or writes tocontent_items, and no role it served is left without a typed home. -
BI-10. Every reference to
content_items.idis re-homed per the ratified inventory — 10 enforced FKs + 2 unenforced bare-uuid columns + the 3rd bare-uuid refq_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.
- Re-parent to
-
BI-11. Re-homing the classification family onto
source_documentsmeans 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_reasoningandsecondary_domain/secondary_subtopicARE persisted ontosource_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), soflow.pyis extended to write them;classification_modelis dropped (dead — 0 stored consumers).created_bymaps to SD’s existinguploaded_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_pairsalready has them).source_urlis dropped from the eliminated table but kept onsource_documents(URL-ingest provenance) andreference_items(NOT NULL).thumbnail_urlis net-new onreference_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. -
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. -
BI-13. The
quality_issues_pendingVIEW (defined overcontent_items, 0 rows) is dropped or rewritten together with the table — it does not survive as a dangling consumer.
D. Extraction & Q&A re-parenting
Section titled “D. Extraction & Q&A re-parenting”-
BI-14. Entities and relationships stay in the database (no graph migration). The pipeline re-parents
content_chunks,entity_mentions, andentity_relationshipsontosource_document_id, and the pipeline writer (flow.py) writessource_document_idfor these from day one of the full-replace re-ingest, becausecontent_itemswill not exist at that point. (No write-site may depend on acontent_item_idparent after BI-9.) -
BI-15.
q_a_extractionsis re-parented ontosource_document_id(itssource_content_item_idlink dies withcontent_items).q_a_pairsalready carriessource_document_id, so the master tier is unaffected. -
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__/*.mdfiles.q_a_pairs.source_document_idis 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.)
E. Embeddings store (EMB-STORE)
Section titled “E. Embeddings store (EMB-STORE)”- BI-17. A dedicated polymorphic embeddings store exists, keyed
(owner_kind, owner_id, model)with avectorcolumn, whereowner_kind ∈ {source_document, content_chunk, q_a_pair, reference_item, concept}. It absorbs the six scattered embedding columns (the fivevectorcolumns above and thecompany_profiles.company_embeddingTEXT inconsistency, normalised to a vector). Its pgvector index is declared via a Supabase migration (not cocoindexdeclare_vector_index). One store serves both L-recordshybrid_searchand later L-concept bundle traversal, and it is ratified within ID-131 so the sibling producer Task can rely on it.
F. Governance facet (GOV-FACET)
Section titled “F. Governance facet (GOV-FACET)”-
BI-18. Governance/freshness/lifecycle/ownership is promoted into a shared, polymorphic
record_lifecyclefacet keyed(owner_kind, owner_id)with a denormaliseddomainfor 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’sowner_kindis per-axis — the freshness/expiry axis and the review/governance axis carry different owner sets — RATIFIED 28/06/2026, D7.) -
BI-19 (reference_item governance exclusion — RESEARCH §5.2 calls this BI-16). On the governance/review axis only the facet’s
owner_kindset is{source_document, q_a_pair}and a CHECK constraint excludesreference_item; absence of a governance facet row means a record is governance/review-free —reference_itemsis deliberately governance/review-free (verified: it carries no governance columns today). Tracked-deferred (not built in ID-131, D7):reference_itemgains 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 noreference_itemfreshness/validity schema lands in ID-131. -
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 filterspublication_statuson every query;q_a_pairsalready 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_scoreandcitation_countare 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 bysource_documentonly — aq_a_paircarries no cold freshness/expiry row and relies on its inlinesuperseded_by+ opt-invalid_to; the review/governance cold fields still span{source_document, q_a_pair}. -
BI-21.
governance_configis unchanged (per-domain policy,UNIQUE(domain)). The facet’s denormaliseddomainis kept equal to the owner record’sprimary_domainby a write-time sync, so the existing policy→state join in the freshness/review/quality crons keeps working in one hop after reclassification. -
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 — aq_a_pairhas no freshness/expiry cron axis and drops its own freshness clock (reversing the earlier “own clock” recommendation). Aq_a_pair’s lifecycle is carried by supersession + dedup + an opt-in inlinevalid_toonly (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 JSONmetadata->>'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)”-
BI-23 (CITE-EXT). The citation contract is extended:
cited_target_kindgains{reference_item, source_document, concept}andq_a_pairis activated as a real target (today it is dormant). The exactly-one-of CHECK is rewritten to span all kinds, the matchingcited_*_idcolumns 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. -
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.) -
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), withhybrid_search.win_statsre-pointed offworkspaces.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 toq_a_pair, or the dual-source disagreement propagates into the rewrite. -
BI-26. Once BI-25 is resolved, both win-rate consumers (
get_content_win_rate— 2 live callers — andhybrid_search.win_stats) are re-anchored fromcited_kind = 'content_item'tocited_kind = 'q_a_pair'(the reusable canonical answer is what wins or loses a bid). Their return shapes are otherwise preserved (get_content_win_ratestill returnstotal_citations, winning_citations, losing_citations, pending_citations, win_rate).
H. Search-RPC result-shape contract
Section titled “H. Search-RPC result-shape contract”-
BI-27. The MCP
findvalue-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. -
BI-28. The search RPCs are reworked without breaking BI-27:
hybrid_searchbecomes a polymorphic UNION acrosssource_documents/content_chunks/q_a_pairs/reference_items(and itswin_statsCTE re-anchors per BI-26);find_related_itemsandfilter_by_keywordsare DROPPED ENTIRELY per {131.20} §9 (§7.4 related-records → an ontology-grounded backlog item; §7.5 keyword pre-filter → a futurehybrid_searchfacet param), superseding the earlier re-anchor / keep-the-live-variant framing. The dedup RPCs (find_duplicate_pairs,find_exact_duplicates) re-targetq_a_pairs(BI-32).
I. Forms
Section titled “I. Forms”-
BI-29 (RATIFIED 28/06/2026, D2). Forms matching is re-pointed off
content_itemsonto typed records:q_a_pairs(primary) +reference_items(optional).source_documentsis NOT a match target — it carries no embedding and no answer-grained content, so it is a provenance/citation-resolution target only (reached viacontent_chunksif document-body matching is ever wanted), never a direct vector-match source.fetchContentForMatching(template-coverage.ts) no longer SELECTs fromcontent_items, and theuuid[]arraysform_responses.source_content_ids/form_questions.matched_content_idscarry q_a_pair / reference_item ids. -
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 noq_a_pairs/source_documentscontent 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. -
BI-31. Forms matching quality must not regress: because the matching thresholds (
0.55/0.35) andcosineSimilaritywere tuned againstcontent_itemsembeddings, 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.
J. Dedup, manual authoring & drops
Section titled “J. Dedup, manual authoring & drops”-
BI-32 (dedup home — resolves RESEARCH §6(d)). Normalised-text dedup belongs to
q_a_pairsonly — 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_documentsdoes not carry acontent_text_hashcolumn; SD idempotency comes from therel_pathseed + thecontent_hashaudit column. The IMS dedup tooling is ported toq_a_pairs, not re-homed onto documents. -
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 manualq_a_pairauthoring shape (question +answer_standard+answer_advanced+scope_tag[], writing directly toq_a_pairswith 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 (theapp/item/newcreate path + thecomponents/qaTiptap editor + the/libraryviewer) is rebound fromcontent_itemsto typedq_a_pairs, and a new'manually_authored'origin_kindis added so hand-authored pairs are auditably distinct from the'curated_explicit'default and the'derived_from_form_response'path. Provenancesource_document_idis nullable — set only when the author cites an ingested document, otherwise NULL withorigin_kindcarrying the provenance (the form-response path, which already runs withsource_document_idNULL, is the precedent). The genericcontent_itemcreate form and thecontent_templatesskeletons 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). -
BI-34.
content_item_workspacesandcontent_templatesare dropped (both 0 rows, both ratified). Cross-workspace scope thatcontent_item_workspacesprovided is handled at query time viascope_tagoverlap; thecontent_templatesskeleton 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_chkidiom) vs(owner_kind, owner_id)+ a validating trigger — a referential-integrity-vs-ergonomics trade for TECH. (BI-22 is RATIFIED to per-axis — aq_a_pairdrops its own freshness clock — so the per-axis freshness-vs-review facet structure is a TECH consequence.) - (c)
feed_articlesre-parent target.reference_itemvssource_document— theflow.pyURL 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.