TECH — OKF ontology three-layer pass + semantic linter (allowed_types/allowed_relations register)
TECH — OKF ontology three-layer pass + semantic linter
Section titled “TECH — OKF ontology three-layer pass + semantic linter”Task: ID-133 (Task D in the OKF v3 programme). Tier = TECH+PLAN — reports/okf-record-model-lineage/okf-record-model-v3.md §6
is the settled product intent (owner round-3 ratified 28/06/2026); this artefact is the technical
design; {133.2} PLAN decomposes it.
Depends (coordination, not hard-blocking): ID-131 (Task B, L-records refactor) for the DB homes the
column CVs re-home onto. ID-133’s doc-side items unblock ID-132 (Task A, producer).
Gates: the rewritten register + the two new semantic linters gate BOTH ID-131 extraction writes (the
Pydantic gate) AND ID-132 concept writes (the new concept-frontmatter validator).
Audience: Product owner (non-developer) + Executors. Plain-English first; file:line evidence inline.
UK English throughout.
Context
Section titled “Context”What is being built
Section titled “What is being built”The KH ontology register (33 controlled-vocabulary markdown files + a Zod schema + a runtime Pydantic
gate + parity guards) deeply encodes the content_items god-table that ID-131 eliminates. ID-133
re-aligns the ontology to the OKF three-layer model (L-raw / L-records / L-concepts, v3 §1) and promotes
the closed entity/relationship vocabularies into a first-class allowed_types / allowed_relations
register with per-value core-vs-client provenance — the artefact the owner asked for, which today does
not exist. It is a nine-item pass (v3 §6). The pass changes what is gated and where the extraction
stamp points, NOT the gate’s enforcement semantics: HARD-reject for closed enums (content_type and
successors), SOFT-WARN for open dimensions (domain/subtopic) is preserved exactly.
Code-intelligence orientation (verbatim — Checker: confirm this ran)
Section titled “Code-intelligence orientation (verbatim — Checker: confirm this ran)”Per the binding rule (.gitnexus/CLAUDE.md, .ast-dataflow/CLAUDE.md), outputs are cited verbatim, not
paraphrased.
gitnexus_query({query: 'ontology controlled vocabulary loader parity guard entity_type relationship', repo:'canonical'})returned (definitions, abridged to the load-bearing hits):Function:scripts/cocoindex_pipeline/extraction.py:extract_relationships(1116–1144)Class:scripts/cocoindex_pipeline/extraction.py:RelationshipExtraction(398–435)Class:scripts/tests/test_cocoindex_extraction.py:TestEntityTypeParity(880–908) +Method:…TestEntityTypeParity.test_python_literal_matches_ts_constant(883–908)Method:scripts/tests/test_cocoindex_prompts.py:TestPromptsEnumeratesEnums.test_entity_mention_enumerates_12_entity_types(235–254)…test_relationship_enumerates_10_relationship_types(256–274)
Function:scripts/cocoindex_pipeline/extraction.py:_load_canonical_form_types(99–129)
gitnexus_context({name: 'loadOntologyCVs', repo:'canonical'})→Function:lib/ontology/loader.ts:loadOntologyCVs(42–75);incoming: {}(no production caller — only the parity test imports it);outgoing.accesses: [issues];processes: []. Confirms the loader is a leaf with no runtime consumer (item 7 evidence).gitnexus_context({name: 'OntologyCVSchema', repo:'canonical'})→ ambiguous:Function:andConst:both atlib/ontology/schemas.ts:104(thesuperRefinewrapper + its exported const). The symbol the spec extends.gitnexus_impact({target: '_ExtractionStamp', direction:'upstream', repo:'canonical', includeTests:true})→ risk: HIGH,impactedCount: 46,summary.direct: 16,byDepthCounts {1:16, 2:19, 3:11},affected_modules: [{name:'Cocoindex_pipeline', hits:3, impact:'direct'}]. This is the blast radius behind item 8; the CODE re-parent is owned by ID-131 (see below), ID-133 owns only the ontology-doc side.
ccc fallback was not required — gitnexus resolved every symbol the spec mandates. Not a greenfield surface.
Current state — the four artefacts and the repo-split (the drift hazard)
Section titled “Current state — the four artefacts and the repo-split (the drift hazard)”The register is NOT a single source of truth today; it is one human source + three derived artefacts + a dead bridge:
- Human source of truth — the register markdown (PRIVATE, since ID-68.27):
${KH_PRIVATE_DOCS_DIR}/src/content/docs/ontology/*.md(33 CV files +README.md+index.md). The boundary rule (README.md:74–87, the Q1/Q1a/Q1b/Q2/Q3/Q4 decision procedure) routes client-canonical knowledge →content_items(README.md:76) and forbids newcontent_typevalues for routing (README.md:87).04-content-type.md:63defines content_type as “the shapes acontent_itemsrow can take” (15 values;capabilityalready ratified-S237 to retire to 14).03-layer-vocabulary.mdis the audience axis (sales_brief/bid_detail/company_reference/research), explicitly NOT the OKF layer model (READMEprovenance rollup: 22 core / 9 hybrid / 2 client). - Runtime gate — the Pydantic semantic linter in
scripts/cocoindex_pipeline/extraction.py(the LIVE linter, NOT the markdown register):- HARD-reject
content_typevia_validate_content_typefield-validator (470–481), reading_VALID_CONTENT_TYPES(97) loaded fromtaxonomy_snapshot.json(_load_canonical_content_types, 84–94). - HARD-reject
form_type(_validate_form_type, 304–315) +form_formatLiteral (285) + theentity_typeLiteral (378–391, 12 values) + therelationshipLiteral (423–434, 10 values). - SOFT-WARN
primary_domain/primary_subtopic/secondary_classificationsvia_surface_out_of_taxonomy_classificationmodel-validator (483–532): bumps a counter + logs, never raises, always returns self (Inv-6/7). This is the enforcement-semantics line item 8/9 must not move.
- HARD-reject
- DB-derived runtime list —
scripts/tests/fixtures/taxonomy_snapshot.json(regenerated from the live DB bybun run sync:taxonomy); holdscontent_types(15),form_types(8),domains,subtopics. - Public lockstep fixture + codegen —
__tests__/fixtures/ontology/ontology-cv-baselines.json(the frozen 4-CV baseline: content_type, platform, requirement_type, form_type).scripts/generate-content-type-values.tsreads the fixture (NOT the now-removeddocs/ontology/) and writeslib/ontology/content-type-values.generated.ts;lib/ontology/content-type-registry.tsre-exports it; wiredpredev/prebuild(package.json:13–18).lib/validation/schemas.tsre-exports the tuple asVALID_CONTENT_TYPES. - The dead bridge —
lib/ontology/loader.ts:29ONTOLOGY_DIR = join(REPO_ROOT, 'docs', 'ontology')points at a directory that no longer exists;loadOntologyCVs(43–76) fail-louds if it is absent (46–51). The parity test__tests__/lib/ontology/markdown-parity.test.tsinverts this into a privacy tripwire: it assertsexistsSync(ONTOLOGY_DIR) === falseANDloadOntologyCVs()throws. So the loader has no production caller (confirmed:loadOntologyCVsincoming: {}) and exists only as a tripwire.
Parity guards (the lockstep mechanism):
__tests__/lib/ontology/markdown-parity.test.ts— asserts the public fixture, the generated tuple, andtaxonomy_snapshot.jsonagree (the dead-loader tripwire lives here too).scripts/tests/test_cocoindex_extraction.py:881–909TestEntityTypeParity.test_python_literal_matches_ts_constant— asserts the Pythonentity_typeLiteral == TSVALID_ENTITY_TYPES(lib/validation/schemas.ts, extracted by_extract_ts_string_array, 861). There is NO equivalent register source in this guard, and NO Python↔TS parity test forrelationship— relationship parity is only indirectly covered by the prompt-enumeration test (test_cocoindex_prompts.py:256–274). The TS relationship vocabulary lives in theExtractedRelationship/ExtractedEntityunions inlib/ai/classify.ts:653–666.
The stale wp6 spec: ${KH_PRIVATE_DOCS_DIR}/src/content/docs/specs/wp6-ontology-harness/TECH.md §3
(29–69) still describes “29 CV files”, the live docs/ontology/*.md register, and a live runtime loader —
all three superseded (33 CVs; private register; dead loader).
The Layer-5 framing to correct: …/phase-0-investigation/phase-b-prerequisite-1-onthology-pipeline-feedback-investigation.md §5
(481–482) records BOTH source_documents AND content_items as “Layer 5 ontology class”, with
content_items = the “Knowledge artefact”. Eliminating content_items retires the artefact but the
Layer-5 framing must move to the typed records + L-concepts.
What ID-131 owns (NOT ID-133 — the boundary)
Section titled “What ID-131 owns (NOT ID-133 — the boundary)”The DB column moves and the extraction.py CODE re-parent are ID-131’s; ID-133 documents them in the
register and adjusts the CVs to match the new homes. From id-131/TECH.md:
- M3
id131_sd_classification_cols(290): ADD ~25–30 classification + hot cols tosource_documents(content_type,primary_domain,primary_subtopic,secondary_domain,secondary_subtopic,ai_keywords text[],summary,suggested_title,classified_at,classification_confidence,classification_reasoning,classification_model,captured_date,summary_data jsonb,updated_by,updated_at,publication_status NOT NULL DEFAULT 'published'INLINE). - M1a
id131_record_lifecycle_facet(287) + GOV-FACET hot/cold split: the freshness/lifecycle/ governance family → therecord_lifecyclefacet (NOTsource_documents);publication_status/valid_*/superseded_bystay INLINE. - M2
id131_extract_reparent(289): re-parentcontent_chunks/entity_mentions/entity_relationships/classification_disputes/q_a_extractionscontent_item_id→source_document_id, in lockstep with the extraction stampextraction.py:261–264(_ExtractionStamp, HIGH). This is the code re-parent for item 8. ID-133 does not touch extraction.py:261–264; it documents it. - M4b CITE-EXT —
citationsextended to{q_a_pair, reference_item, source_document, concept}.
The two cross-cutting decisions (made here)
Section titled “The two cross-cutting decisions (made here)”Decision A — single source of truth + lockstep (item 7, resolves v3 risk 8)
Section titled “Decision A — single source of truth + lockstep (item 7, resolves v3 risk 8)”DECISION: the private docs-site register is THE single human source of truth; every other artefact is a DERIVED mirror kept in lockstep by parity guards. Retire the dead loader; expand the public fixture into the complete public mirror of the runtime-relevant CVs; document the derive-and-lockstep chain.
Rationale + rejected alternative:
-
The owner-flagged drift hazard is the triple (dead loader + partial 4-CV fixture +
taxonomy_snapshot.json) with no declared authority. The fix is to name ONE authority and make everything else derived, not to add a fourth reader. -
REJECTED: repoint
loader.tsat the docs-site register viaKH_PRIVATE_DOCS_DIR. The codegen (generate-content-type-values.ts) already solved the client-bundlenode:fsproblem by reading the public frozen fixture instead of the loader; the loader has zero production callers (gitnexusincoming: {}). Repointing it would (a) reintroduce a private-siblingnode:fsread into the public repo’s server/test runtime, throwing locally wheneverKH_PRIVATE_DOCS_DIRis unset, and (b) not change any runtime behaviour because nothing consumes the loader. It adds a reader without removing drift. -
CHOSEN: retire
loadOntologyCVs+ONTOLOGY_DIR(lib/ontology/loader.ts). Replace the dead-loader tripwire inmarkdown-parity.test.tswith a same-intent privacy tripwire that does not import the loader (assert the publicdocs/ontology/dir is absent directly). The authority chain becomes explicit and is documented inREADME.md+ the wp6 TECH refresh (item 9):Role Artefact Kept in lockstep by Human source of truth ${KH_PRIVATE_DOCS_DIR}/…/ontology/*.md(authored by hand; docubot lane) Public mirror (CI-visible) __tests__/fixtures/ontology/ontology-cv-baselines.json(EXPANDED: + entity_type, + relationship)docs-site “parity-guard twin” (ID-68.27 follow-up) DB-derived runtime list scripts/tests/fixtures/taxonomy_snapshot.jsonbun run sync:taxonomyBuild-time client tuple lib/ontology/content-type-values.generated.tsgenerate:content-type-values(predev/prebuild)Code Literals (KG CVs) extraction.pyLiterals +schemas.ts/classify.tsconststhe parity guards below Drift in any derived artefact fails a parity guard at build/CI. The register markdown is never read at runtime (correct — it is the human contract; the machine reads the snapshot/fixture/Literals).
This decision is a prerequisite for item 5 (promoting the KG vocabularies to the register adds a third source for entity_type/relationship — the fixture is where that third source lives publicly, and the parity guard is extended to bind all three).
Reframe — markdown register vs DB-backed register, for THESE CVs (owner round-4 pushback, E11/D6): the
owner asked whether the register should instead be DB-backed (core) + client rather than retired. The
answer is that a DB-backed core+client register ALREADY EXISTS and is authoritative for the genuinely
client-extensible CVs — taxonomy_domains / taxonomy_subtopics / layer_vocabulary are DB tables
carrying a provenance column + a recommend-then-accept workflow (recommended_by / accepted_at), with
taxonomy_snapshot.json derived FROM them (the DB is the source of truth for the open dimensions). The
markdown register is deliberately reserved for the closed/core CVs that are HARD-rejected and product-closed
at v1 (content_type, entity_type, relationship). For BI-5’s named targets — entity_type (12) +
relationship (10) — a DB register buys nothing now: they are closed at v1 (Decision B
client_extensible: false) AND prompt-coupled (hardcoded Pydantic Literals mirrored into the extraction
prompts), so a provenance-bearing table would only add a further lockstep source — the exact drift hazard
Decision A collapses. Forward bridge: IF the product later makes them client-extensible (the Decision B
research-deferred call), promote them to the SAME DB-backed provenance pattern as taxonomy_domains
(ratified S235 Q-OQR1-11), NOT to the markdown register — Decision B’s optional per-value keys
(provenance_model / client_extensible / editable_via) are exactly the columns that future table would
carry, so they are the forward-compat bridge.
id-63 lineage (F3 — faithful continuation, not a fresh start): ID-133 BUILDS ON the original id-63
ontology-pipeline plans (phase-b-prerequisite-1-onthology-pipeline*.md). Decision A (markdown register
authoritative) IS the original Q-OQR1-12 “auto-fresh register” vision; Decision B (per-value provenance)
extends Q-OQR1-11; the entity_type / relationship CORE-closed posture and the HARD/SOFT enforcement
split are id-63’s contributions carried forward unchanged. The ONE superseded thread is id-63’s original
“auto-fresh register / ID-9 → Astro auto-sync” mechanism: it is replaced here by the
hand-authored-contract + parity-guard + docubot lane model — the docubot lane (docs upkeep automated IN
the docs-site repo) is the SUCCESSOR to the auto-fresh intent, and the id-63 → ID-9/Astro auto-sync linkage
is explicitly SUPERSEDED/CLOSED. Recording this keeps the lineage readable as a continuation, not an
abandonment.
Decision B — the allowed_types / allowed_relations register schema (item 5, the highest-value artefact)
Section titled “Decision B — the allowed_types / allowed_relations register schema (item 5, the highest-value artefact)”DECISION: promote entity_type (12) and relationship (10) into two NEW register CVs —
34-entity-type.md (Layer 5, Ontology) and 35-relationship.md (Layer 6, Knowledge Graph) — with
per-value provenance, by EXTENDING BaselineValueSchema (lib/ontology/schemas.ts:33–49) with three
OPTIONAL per-value keys.
The register already carries CV-level provenance (provenance_model core/client/hybrid,
client_extensible, editable_via) and per-value provenance (core/client/recommended). The owner’s ask
(“allowed_types/allowed_relations with per-value core-vs-client provenance”) needs the core-vs-client
distinction per value so a future client-defined entity type can be marked extensible while the 12
shipped types stay immutable. Concrete schema extension (backward-compatible — all keys optional, so the
existing 33 CVs are unaffected and continue to validate):
// lib/ontology/schemas.ts — BaselineValueSchema (extended)const BaselineValueSchema = z.object({ key: z.string().min(1).regex(/^([a-z][a-z0-9_-]*|TBD)$/, '…'), label: z.string().min(1), provenance: z.enum(PROVENANCE_VALUES), // existing: core | client | recommended definition: z.string().min(1).optional(), // existing // NEW (ID-133) — per-value provenance for KG-ontology CVs. Optional ⇒ existing CVs unaffected. provenance_model: z.enum(PROVENANCE_MODEL_VALUES).optional(), // core | client | hybrid client_extensible: z.boolean().optional(), editable_via: z.enum(EDITABLE_VIA_VALUES).optional(),});34-entity-type.md frontmatter: layer: 5, provenance_model: hybrid, client_extensible: true,
editable_via: database_migration, status: active, baseline_values: = the 12 types each with
provenance: core, provenance_model: core, client_extensible: false, editable_via: database_migration
(closed at v1, client-extension research-deferred — mirrors content_type’s “closed for v1” posture).
35-relationship.md frontmatter: layer: 6, otherwise identical, baseline_values: = the 10 predicates.
Schema note: the existing OntologyCVSchema.superRefine (105–137) requires baseline_values for every
non-Layer-5 CV and allows (does not require) them on Layer 5. 34-entity-type.md (Layer 5) carries
baseline_values (allowed) and 35-relationship.md (Layer 6) carries them (required) — both validate
under the current rule with no superRefine change. Only BaselineValueSchema is extended.
Per-value provenance is the spine of the §G extraction and the contract ID-132’s concept producer and ID-131’s extraction gate both read.
Proposed changes — one section per v3 §6 item (BI-1 … BI-9)
Section titled “Proposed changes — one section per v3 §6 item (BI-1 … BI-9)”Items are numbered BI-1…BI-9 to match v3 §6’s nine-item pass, giving {133.2} PLAN and the Checker a
one-to-one mapping. Doc-side items (BI-1, BI-2) author in parallel — they unblock ID-132. Homes-dependent
items (BI-3, BI-4, BI-8) gate on ID-131’s M2/M3 decisions.
BI-1 — rewrite the README boundary rule (doc-side; unblocks ID-132)
Section titled “BI-1 — rewrite the README boundary rule (doc-side; unblocks ID-132)”File: ontology/README.md:70–88 (“Where does new data live?”). Rewrite the Q1/Q1a/Q1b/Q2/Q3/Q4
procedure so client-canonical knowledge routes to {source_documents, q_a_pairs, reference_items} +
L-concepts, NOT content_items + content_item_workspaces:
- Q1a (client-canonical knowledge): no longer ”→
content_items”. Split by record type: a unit of usable knowledge is either a canonical answer (q_a_pairs), a provenance-bearing document body (source_documents+content_chunksretrieval grain), or — when it is a distilled concept, not a record — an L-concept in the OKF bundle. Drop thecontent_item_workspacesassociation sentence (M2M dropped, ID-131); workspace scoping is query-timescope_tagoverlap. - Q1b (external evidence): unchanged →
reference_items(33-reference-item.md). - Q2 (raw original): unchanged →
source_documents. - Corollary (1) (
README.md:87): keep “never solve routing with a newcontent_typevalue”, but reword socontent_typeis “editorial shape within asource_documentsclassification”, not “within Q1a”. - Add a one-line pointer to the new
36-three-layer-model.md(BI-2) and clarify L-concepts are client-owned (NOT a DB table). - Update the README provenance/file-index tables (130–223) to add rows 34/35/36 and adjust the rollups
(core/hybrid counts shift;
content_items-keyed “Where to look first” entries re-point tosource_documents).
BI-2 — NEW 3-layer-model CV, distinct from the audience axis (doc-side; unblocks ID-132)
Section titled “BI-2 — NEW 3-layer-model CV, distinct from the audience axis (doc-side; unblocks ID-132)”NEW file: ontology/36-three-layer-model.md. Layer 1 CV, provenance_model: core, client_extensible: false,
editable_via: database_migration, baseline_values: = l_raw / l_records / l_concepts (3 values,
each provenance: core). Body: the v3 §1 definitions (L-raw = client raw sources, client-owned; L-records
= Canonical DB; L-concepts = client OKF bundle) + the legitimate cross-layer pointers (L-records→L-raw
provenance; L-concepts→L-records canonical://<table>/<uuid> citation).
Anti-conflation guard (v3 Lens E): the CV body MUST state in a ## Notes bullet that this is the OKF
storage-layer axis and is ORTHOGONAL to 03-layer-vocabulary (the audience/depth axis,
sales_brief/bid_detail/…). Add a reciprocal note to 03-layer-vocabulary.md:74 (“Three orthogonal
axes”) naming the new fourth, storage-layer axis so prompts/classifiers reading …layer are never
confused. Do NOT rename 03-layer-vocabulary or its layer_vocabulary cv_name.
BI-3 — decompose 04-content-type across three destinations (homes-dependent; coordinate ID-131 §3 + ID-132 concept-type set)
Section titled “BI-3 — decompose 04-content-type across three destinations (homes-dependent; coordinate ID-131 §3 + ID-132 concept-type set)”File: ontology/04-content-type.md. The 15-value closed enum splits three ways (a PRODUCT call — mis-splitting
hardens at first bundle publication, v3 risk 5; drive from the id-71 strawman + the client’s hand-built
topic index):
- Record classifications → stay as
source_documents.content_type(re-homed offcontent_itemsby ID-131 M3):article,blog,pdf,note,research,document,other. These remain the closedcontent_typeenum, now describing asource_documentsrow’s editorial shape. - q_a_pair → migration-OUT:
q_a_pairleavescontent_typeentirely (q_a_pairs is a peer Layer-5 class,32-q-a-pair.md; it carries nocontent_typecolumn). Record the migration-out in04’s Notes (the04-content-type.md:71“onceq_a_pairsis promoted… historic rows migrate” note becomes “done”). - L-concept type discriminators → move to the concept-frontmatter ontology (BI-6):
case_study,policy,compliance,methodology,certification,capability,product_descriptionbecome the L-concepttypeset, NOTcontent_typevalues. (capabilityis already ratified-S237 to retire — fold the retirement into the concept-type set decision.) - Coordination: the resulting
source_documents.content_typevalue set MUST equal ID-131 M3’scontent_typecolumn CHECK and thetaxonomy_snapshot.json:content_typesregenerated after the DB change; the L-concepttypeset MUST equal ID-132’s concepttypediscriminators. The split is a joint ratification across ID-131 §3 / ID-133 BI-3 / ID-132 — flag for the owner before the fixture + snapshot are frozen. - Keep the HARD-reject: the trimmed
source_documents.content_typestays a closed CHECK enforced by the Pydantic_validate_content_type(470–481) reading the regenerated snapshot.
BI-4 — re-home the content_items-coupled column CVs onto their ID-131 homes (homes-dependent)
Section titled “BI-4 — re-home the content_items-coupled column CVs onto their ID-131 homes (homes-dependent)”The brief’s “~7 column CVs onto source_documents” is, precisely, ~11 CVs across FOUR homes (cite ID-131
M3/M1a/GOV-FACET). For each CV, update the ## Relationships + ## Consumers sections to point at the new
home and the database.types.ts projected column:
| CV file | content_items column(s) | New home (ID-131) |
|---|---|---|
01-taxonomy-domains.md, 02-taxonomy-subtopics.md | primary_domain/primary_subtopic/secondary_* | source_documents.* (M3); q_a_pairs has none — note Finding 2 (win-rate derives QA domain via record_lifecycle.domain) |
10-publication-status.md | publication_status | source_documents.publication_status INLINE (M3); already on q_a_pairs |
13-ingest-source.md | ingest_source/ingestion_source | source_documents.ingest_source |
29-cocoindex-source-kind.md | cocoindex_source_kind | source_documents.* (planned col → SD) |
06-lifecycle-type.md, 08-freshness.md, 09-governance-review-status.md | lifecycle_type, freshness/previous_freshness/expiry_date, governance_review_status | record_lifecycle facet COLD fields (M1a) — NOT source_documents |
07-dedup-status.md | dedup_status/content_text_hash | q_a_pairs (dedup is QA-valuable, v3 §3.5); content_text_hash DROPS (no SD home) |
21-scope-tag.md | scope_tag/anti_scope_tag | q_a_pairs + query-time overlap (application-surfacing axis) |
05-platform.md | platform | DROP (IMS vestige, no writer, v3 §3.2) — mark the CV status: planned→deprecation note |
03-layer-vocabulary.md | layer | RESOLVED (owner round-4, D5): NO source_documents.layer column — layer is retired from the SD/QA homes (the cocoindex SD/QA pipeline never writes content_items.layer, flow.py:2515). But KEEP the 03-layer-vocabulary CV — do NOT deprecate/retire it. Re-frame its OWNERSHIP to the Guides application type: it is the Guides audience/depth axis, matched against guide_sections.expected_layer. Update the CV’s ## Relationships/## Consumers to name the Guides home, not source_documents. |
16-extraction-method.md, 20-chunk-kind.md, 25-application-type.md, 19-engineering-types.md need
only prose touch-ups (BI-9), not re-homing.
BI-5 — promote entity_type + relationship into the allowed_types / allowed_relations register (the highest-value artefact)
Section titled “BI-5 — promote entity_type + relationship into the allowed_types / allowed_relations register (the highest-value artefact)”Per Decision B. Two NEW CV files + a schema extension + a parity-guard extension:
- NEW
ontology/34-entity-type.md(Layer 5) andontology/35-relationship.md(Layer 6) with the 12 + 10 baseline values, each carrying per-valueprovenance/provenance_model/client_extensible/editable_via(allcore/falseat v1). Source the values verbatim from the Python Literals (extraction.py:378–391,423–434) so the register == Literal at authoring time. - Extend
BaselineValueSchema(lib/ontology/schemas.ts:33–49) with the three optional per-value keys (Decision B code block). This is a PUBLIC-repo change — rungitnexus_impactonBaselineValueSchemabefore editing (low risk: consumed only byOntologyCVSchema+ the loader being retired). No barrel re-export; direct import. - EXTEND the parity guard (do NOT add a third unguarded source): add the entity_type + relationship
baseline_values to
__tests__/fixtures/ontology/ontology-cv-baselines.json(Decision A), and EXTENDscripts/tests/test_cocoindex_extraction.py:TestEntityTypeParityto assert Python Literal == TS const == fixture baseline_values (currently it asserts only Python == TS, 905). Add a NEWTestRelationshipParityclass (none exists today) asserting the same triple forrelationshipagainstlib/ai/classify.ts:653–666(theExtractedRelationshipunion) — extract the TS union with the existing_extract_ts_string_arrayhelper (861) or a sibling for the union shape. - The docs-site “parity-guard twin” (ID-68.27 follow-up) keeps the fixture == register markdown in the private repo. The lockstep is: register markdown (human) → fixture (public CI) → Python Literal + TS const (runtime), all three bound by the extended guard.
BI-6 — OKF concept-frontmatter ontology + validator (gates L-concept writes; ID-132 consumes)
Section titled “BI-6 — OKF concept-frontmatter ontology + validator (gates L-concept writes; ID-132 consumes)”NEW register CV + NEW validator module. This is the L-concept analogue of the Pydantic extraction gate:
it gates concept writes exactly as _validate_content_type gates extraction writes.
- NEW
ontology/37-concept-type.md(Layer 5) — the L-concepttypeset (the BI-3 discriminators:case_study/policy/compliance/methodology/certification/product_description+ any id-71-derived additions),provenance_model: hybrid, per-value provenance as in BI-5. Plus a## Notesdefinition of the required concept frontmatter keys (type,title,description,timestamp), theresource:URI scheme (canonical://<table>/<uuid>, owned by ID-132), and thetagsconvention. - NEW validator: a Zod schema
ConceptFrontmatterSchemainlib/ontology/concept-schema.ts(sibling toschemas.ts; direct import, no barrel) validating:type ∈the concept-type set, required keys present,resource:matches thecanonical://<table>/<uuid>pattern,tags: string[]. Reusegray-matter(^4.0.3, already used atloader.ts:61) to parse concept.mdfrontmatter, exactly as the register loader does. Evaluate lifting the Google okf-skills validator rather than building from zero — but the KH idiom is gray-matter + Zod (mirrorslib/ontology/schemas.ts), so the recommendation is to author a thin KH-native Zod schema and borrow only the okf-skills rule set (required keys, URI shape), not its runtime. Record the lift-vs-build call in the CV Notes. - ID-132 wires
ConceptFrontmatterSchemainto the producer’sdeclare_filewrite path (the gate). ID-133 owns the CV + the schema module; ID-132 owns the call site. HARD-reject semantics (a malformed concept fails the write) mirror the content_type gate.
BI-7 — fix the loader repo-split (single source of truth)
Section titled “BI-7 — fix the loader repo-split (single source of truth)”Per Decision A. Concretely:
- Retire
lib/ontology/loader.ts(loadOntologyCVs+ONTOLOGY_DIR) — gitnexus confirms no production caller (incoming: {}). Rungitnexus_detect_changesbefore commit to confirm the only affected consumer is the parity test. - Update
markdown-parity.test.ts: replace theloadOntologyCVs-based tripwire (the case “the public docs/ontology/ register is gone and the loader fails loudly”) with a loader-free privacy tripwire that asserts the publicdocs/ontology/directory is absent directly (preserve the privacy-regression intent; drop the fail-loud-loader half, which no longer has a loader to assert against). Keep the fixture ↔ snapshot ↔ generated-tuple parity cases unchanged. - The codegen (
generate-content-type-values.ts) already reads the fixture — no change beyond the fixture expansion in BI-5. - Document the Decision-A authority chain in
README.md(replace the stale “Cross-links (will exist after Wave D / Ratifier)” + “Docs-site auto-update” loader references, 89–125) and in the wp6 TECH refresh (BI-9).
BI-8 — extraction-stamp re-parent: ontology-doc side (code re-parent owned by ID-131)
Section titled “BI-8 — extraction-stamp re-parent: ontology-doc side (code re-parent owned by ID-131)”The CODE change content_items_id → source_document_id at extraction.py:261–264 (_ExtractionStamp)
is ID-131’s M2 / G-PIPELINE (id-131/TECH BI-14/BI-15), lockstep with flow.py:2245/2381/2456/2271.
gitnexus_impact confirms it is HIGH (16 direct, 46 total) — ID-131’s Executor re-runs impact at edit time
and updates the 3 stamped subclasses + the ~4–5 flow_context/test_cocoindex_* fixtures in lockstep.
ID-133 owns only the ontology-doc reflection:
- Update any register CV / README prose that asserts the extraction stamp FKs
content_items_id(the phase-b-feedback §5 framing, swept in BI-9) to saysource_document_id. - Add a
## Notesbullet to34-entity-type.md+35-relationship.md(the KG CVs whose mentions/ relationships are stamped) recording that the stamp parent issource_document_idpost-ID-131, and that the entity/relationship write-sites stay in the DB (entities do NOT leave the DB, v3 §7.1 / id-131 BI-14). - Cross-reference ID-131 M2 so the Checker can confirm the lockstep, not duplicate the migration.
BI-9 — prose sweep + refresh the stale wp6 TECH.md
Section titled “BI-9 — prose sweep + refresh the stale wp6 TECH.md”- Prose sweep across the ~14 register files that reference
content_items/content_item_workspaces(live counts:13-ingest-source.md×15,33-reference-item.md×9,32-q-a-pair.md×6,README.md×5,21-scope-tag.md×5,04-content-type.md×4,03-layer-vocabulary.md×4,02×4,06/01×3,29/25/16/10/09/08/07/05×2,20/19×1). Re-point eachcontent_items/content_items.<col>mention to the BI-4 home; preserve historical-context mentions where they explain provenance (mark them “(pre-ID-131)”). - Correct the Layer-5 framing:
phase-b-prerequisite-1-onthology-pipeline-feedback-investigation.md§5 (481–482) —content_items“Knowledge artefact / Layer 5 ontology class” row is retired; the Layer-5 classes are nowsource_documents(+ classification) andq_a_pairs, with L-concepts the distilled map. - Refresh
specs/wp6-ontology-harness/TECH.md§3 (29–69): “29 CVs” → “37 CVs (33 + entity_type + relationship + three-layer-model + concept-type)”; “livedocs/ontology/*.mdregister / runtime loader” → the Decision-A authority chain (private register + public fixture + snapshot + retired loader); the §5.2 loader section → “retired (ID-133)”. 04-content-type.md:111+README.md“(planned)” cross-link rows that referencemarkdown-parity.test.tsasserting “15 values” → update to the post-BI-3 trimmed set.
Enforcement-semantics invariant (applies across BI-3, BI-5, BI-6, BI-8)
Section titled “Enforcement-semantics invariant (applies across BI-3, BI-5, BI-6, BI-8)”The pass changes WHAT is gated and WHERE the stamp points, NOT the gate’s enforcement semantics. Preserve:
- HARD-reject for closed enums:
content_type(trimmed, BI-3),form_type,form_format,entity_type(BI-5),relationship(BI-5), concepttype(BI-6) — a violation RAISES (_validate_content_typepattern;ConceptFrontmatterSchemarejects). - SOFT-WARN for open dimensions:
primary_domain/primary_subtopic/secondary_classifications— the_surface_out_of_taxonomy_classificationmodel-validator (483–532) is UNTOUCHED (it must keep always returning self, never raising). Do NOT add a field_validator to these dimensions.
Testing and validation
Section titled “Testing and validation”Behaviour-first (test-philosophy.md); bun run test (never bun test) for TS, python3 -m pytest scripts/tests/ for Python. Per-item mapping:
- BI-1/BI-2/BI-9 (doc-side):
markdown-parity.test.ts(post-BI-7) validates every CV frontmatter againstOntologyCVSchema; the new36-three-layer-model.md+ the rewrittenREADMEmust parse + validate. Add a fixture case asserting36’sbaseline_valueskeys =[l_raw, l_records, l_concepts]. A grep-based test (CI) asserting zero un-annotatedcontent_itemsreferences survive the sweep in the register dir (allow(pre-ID-131)-tagged historical mentions). - BI-3: parity — after ID-131 trims the DB CHECK +
bun run sync:taxonomy,markdown-parity.test.tsasserts04-content-type.mdbaseline (via the fixture) ==taxonomy_snapshot.json:content_types== generated tuple. PythonTestContentTypeParity(991+) asserts the runtime_VALID_CONTENT_TYPES== the trimmed snapshot. A test assertingq_a_pairis ABSENT from the trimmed set. - BI-4: schema-touch validation only at the doc layer (the DB moves are ID-131-tested). Assert each re-homed CV’s frontmatter still validates; the grep sweep (BI-9) covers the re-point.
- BI-5 (highest-value): the EXTENDED
TestEntityTypeParity(triple-bind Python == TS == fixture) + the NEWTestRelationshipParity; both fail on any drift between the Literal, the TS const, and the register fixture. A Zod test asserting34/35validate under the extendedBaselineValueSchemaand that per-valueprovenance_model/client_extensible/editable_viaround-trip. A backward-compat test asserting the existing 33 CVs still validate (optional keys absent). - BI-6: unit tests for
ConceptFrontmatterSchema: accepts a well-formed concept (typein set, required keys,resource: canonical://q_a_pairs/<uuid>shape,tags); REJECTS a badtype, a missing required key, and a malformedresource:URI (HARD-reject parity with the content_type gate). - BI-7:
markdown-parity.test.tspost-edit: the loader-free privacy tripwire asserts the publicdocs/ontology/dir is absent;bun run build+bun run testpass withloader.tsremoved (gitnexus_detect_changesconfirms only the test consumed it). - BI-8: no ID-133 code test (ID-131 owns the stamp tests, id-131/TECH §Testing — 4 stale fixtures
updated,
flow_contexttests). ID-133’s Checker confirms the register prose namessource_document_idand cross-references ID-131 M2. - Enforcement-semantics invariant: a Python test asserting a junk
entity_type/relationshipstill RAISES (invalid_enum) and a junkprimary_domainstill SOFT-WARNs (row written, counter bumped) — proves the pass did not move the HARD/SOFT line.
Regression gate: full bun run test + python3 -m pytest scripts/tests/ after the public-repo edits
(schema + parity guards + loader retirement) land.
Risks and mitigations
Section titled “Risks and mitigations”- content_type / concept-type mis-split hardens at first bundle publication (MEDIUM — v3 risk 5). BI-3
- BI-6 partition one enum across three destinations; the L-concept half pins into ID-132’s bundle. Mit: joint owner ratification across ID-131 §3 / BI-3 / ID-132 BEFORE the fixture + snapshot are frozen; drive from the id-71 strawman + the client’s hand-built topic index.
- Promoting the KG vocabularies adds a third source (MEDIUM — v3 risk 8). Mit: Decision A + the EXTENDED parity guard bind register-fixture == Python Literal == TS const; CI fails on drift. Do NOT add the register as an unguarded fourth reader.
- Stamp re-parent is HIGH-risk and cross-Task (MEDIUM). gitnexus_impact
_ExtractionStamp= HIGH (16 direct). Mit: the code change is ID-131-owned (M2/G-PIPELINE); ID-133 touches only doc prose. The Checker confirms the cross-reference, not a duplicate migration. ID-133 must not edit extraction.py:261–264. 03-layer-vocabulary↔36-three-layer-modelconflation (MEDIUM — v3 Lens E). Two “layer” axes. Mit: BI-2 reciprocal anti-conflation Notes on both CVs; do NOT renamelayer_vocabulary; the storage axis is a NEW CV, the audience axis is untouched.layercolumn has no declared ID-131 home (RESOLVED — D5). Mit: resolved by the owner (round-4): nosource_documents.layercolumn;layeris retired from the SD/QA homes (the pipeline never writescontent_items.layer,flow.py:2515). The03-layer-vocabularyCV is KEPT and re-framed to the Guides application type (audience/depth axis, matched againstguide_sections.expected_layer) — see BI-4.- Loader retirement breaks an unseen consumer (LOW). Mit: gitnexus
incoming: {}+ a mandatorygitnexus_detect_changespre-commit;bun run build(client-bundle) + full test gate.
Empirical-verification note (OQ-3 — Q-EX2)
Section titled “Empirical-verification note (OQ-3 — Q-EX2)”No NEW external-library symbols are introduced by this pass. Verified 28/06/2026 against pinned versions:
gray-matter@^4.0.3(package.json) —matter()already import-and-called atlib/ontology/loader.ts:61; BI-6 reuses the same call inconcept-schema.ts. PRESENT (existing call site).zod@^4.4.3(package.json) —z.object/z.enum/z.boolean/.optional()/.superRefinealready in use atlib/ontology/schemas.ts; BI-5 extendsBaselineValueSchema, BI-6 addsConceptFrontmatterSchemawith the same surface. PRESENT (existing call site).pydantic==2.12.5(requirements.txt) —field_validator/model_validator/Literalalready in use atscripts/cocoindex_pipeline/extraction.py; ID-133 does NOT edit the Pydantic gate (BI-8 is ID-131’s). PRESENT (existing call site). The Google okf-skills validator (BI-6) is referenced for its RULE SET only (required keys, URI shape), not imported as a runtime dependency — the recommendation is a KH-native gray-matter+Zod validator, so no new third-party symbol is taken. Scope per shared-discipline §Empirical verification: external-library symbols only; no new ones here.
Quality bars (inherited by every Executor)
Section titled “Quality bars (inherited by every Executor)”Semantic design tokens only (no raw UI in this Task, but applies to any surface); UK English + DD/MM/YYYY;
auth.success + authFailureResponse(auth) for any new route (none expected); sb()/tryQuery() for any
Supabase access (none expected — ID-133 is register + schema + validators, no feature DB calls); no barrel
re-exports (direct imports for concept-schema.ts, the extended schemas.ts); TanStack Query only for any
fetch; bun run test (never bun test); behaviour-first tests (test-philosophy.md) for the parity-guard
and validator Subtasks. gitnexus discipline: gitnexus_impact before editing BaselineValueSchema /
removing loader.ts; gitnexus_detect_changes before commit. NEVER edit supabase/types/database.types.ts
by hand (BI-3/BI-4 column homes are ID-131’s regenerated types).
Follow-ups
Section titled “Follow-ups”- The docs-site “parity-guard twin” (ID-68.27 follow-up) must add entity_type + relationship + concept-type to its private-side fixture lockstep when BI-5/BI-6 land.
layerre-home is RESOLVED (D5): nosource_documents.layercolumn — retired from the SD/QA homes; the03-layer-vocabularyCV is kept and re-framed to the Guides application type (guide_sections.expected_layer). Not an ID-131 column-add.- Client-extensible entity/relationship types (per-value
client_extensible: true) are research-deferred to v2, mirroringcontent_type’s “closed for v1” posture — the schema supports it now; the admin path is future.