Skip to content

S528 census — Lane 3: ontology CV register rot

Lane 3 — The ontology controlled-vocabulary register and its context rot

Section titled “Lane 3 — The ontology controlled-vocabulary register and its context rot”

Census only. No file was edited. Scope: the 37-CV register + README at $KH_PRIVATE_DOCS_DIR/src/content/docs/ontology/, its derived artefacts in the canonical repo, and the CV → DB → OKF chain. Lane 1 owns the classification code path and the DB columns; Lane 2 owns the seeding/propagation scripts; Lane 4 owns test debt.

Measured against Platform staging rbwqewalexrzgxtvcqrh and canonical HEAD c3286753f.


Round-2 revision note — what I got wrong

Section titled “Round-2 revision note — what I got wrong”

My round-1 lead recommendation (“invert the authority: generate baseline_values from the live DB”) is withdrawn. The owner’s objection is correct and my own report contained the refutation — §2 documented the ratified flow as register markdown → parse flow → DB → linter, register upstream and normative, and I then recommended reversing it without saying I was doing so. A schema-generated ontology can only describe what the DB happens to contain; it cannot say a value is wrong, and it makes the OKF linter circular.

A recall pass (GROUNDING §0.6) also corrected a load-bearing fact. I reported the register→DB gate as “never built”. That is wrong for the half that matters:

WP6 specs/wp6-ontology-harness/TECH.md §1, Goals, verbatim: “Make the markdown ontology register under docs/ontology/*.md the single source-of-truth artefact for KH controlled vocabularies, by wrapping it in a Zod-validated loader plus a parity test that fails the build when markdown and DB CHECK constraints disagree.”

Read as intent-at-S236, not as a warrant (§0.5b — see VC-1 below): what this establishes is that the mechanism the owner is now asking for was built and did run (S236–S238). That is a measured fact about the repo, and it is all I claim from it. lib/ontology/loader.ts read the register with gray-matter, validated every file against OntologyCVSchema, and markdown-parity.test.ts asserted markdown ↔ generated tuple ↔ live DB CHECK with bidirectional set equality. Recall confirms it: “asserts: markdown ↔ generated tuple ↔ live DB CHECK — 3-way lockstep”.

It was dismantled in two steps, both recorded as amendments in the spec that specified it:

  1. ID-68.27 branch (b) relocated the register into the private docs-site for privacy. The public test could no longer see the markdown, so it was repointed at a frozen fixture, and the register-side replacement (the “parity-guard twin”) was booked as a follow-up. It was never built — the phrase appears in 5 documents and 0 lines of code.
  2. ID-133 Decision A retired lib/ontology/loader.ts — the only component that read the register — reasoning “zero production callers” and that repointing “would add a reader without removing drift”.

So the correct diagnosis is not “the ontology never had a gate”. It is: the gate existed in the ratified direction, and a privacy relocation plus a decision taken inside the frame removed its two ends, leaving fixture↔fixture parity that cannot fail. That makes rebuilding materially cheaper than round 1 implied — the Zod schema, the codegen, the DB-side oracle and the loader’s own specification all still exist.

Per GROUNDING §2a I flag rather than close: ID-133 Decision A is a ruling taken within the inherited frame. Its stated subject was whether closed CVs should be promoted to DB tables (recall: “a DB row can’t gate LLM extraction without code regen, so a DB table buys nothing now and adds drift”) and it affirmed “docs-site register authoritative”. Its effect was to delete the mechanism that made the register authoritative over anything. That gap between what it ruled and what it did is worth the owner’s attention; I am not treating it as settled.

No verdict in this report rests on row counts or “never run” (GROUNDING §0.1 Trap 2). Where a count appears it measures prose staleness (the CV cites a figure that was never true or is no longer), never liveness.


Round-3 pass — applying §0.5b (a task is evidence of intent, never of correctness)

Section titled “Round-3 pass — applying §0.5b (a task is evidence of intent, never of correctness)”

I audited my own round-2 report against the new rule. It caught my lead finding’s warrant and changed four verdicts. Listed here so the owner can see exactly what moved and why.

VC-1 — my lead recommendation was warranted by a task spec. Re-warranted.

Section titled “VC-1 — my lead recommendation was warranted by a task spec. Re-warranted.”

Round 2 opened by quoting WP6 specs/wp6-ontology-harness/TECH.md §1 as establishing that register → DB is the right design. That is a task spec, and under §0.5b it is intent at S236, not proof of correctness. Worse, WP6 §1’s second goal names the first downstream consumer to wire as content_items.content_type — a table dropped at ID-131 M6. The spec I was leaning on was itself authored inside the frame this census is investigating.

Re-warranted, and the recommendation is unchanged:

  • The direction is owner-ruled, this session — GROUNDING §0.4: “The ontology direction is register → DB, NOT DB → register”, with the reasoning that a schema-generated ontology loses the power to say a value is wrong. That is the authority, and it is the only authority I now claim for the direction.
  • WP6 is demoted to two legitimate uses, neither of which is a correctness warrant: (a) measured history — the mechanism was once built and ran, which is a fact about the repo, not a claim in a doc; (b) a cost basis — §5.2 specifies the loader’s behaviour in five numbered steps, so P2 is a re-implementation rather than a design exercise.
  • The empirical argument stands on its own and does not need WP6 at all: the 16-extraction-method case below shows the register carrying a ruling the DB never received. That is measured current behaviour (§0.2 tier 2), and it is the strongest evidence in this lane.

VC-2 — P5 (CHECK emission) is sequenced behind the concept rulings. Changed.

Section titled “VC-2 — P5 (CHECK emission) is sequenced behind the concept rulings. Changed.”

Round 2 proposed P5 as ready-to-go for seven CVs. Withdrawn as an immediate action. P5 takes the register’s declared value set and hardens it into a DB constraint. For 04-content-type that set is ID-133 BI-3’s 7-value stay-set — a task decision, and content_type is a Layer-1 concept, which GROUNDING §2b rules already known to be incorrect.

Emitting a CHECK over a vocabulary whose correctness is under question is the exact “forced to fit” move this census exists to end — it would convert an unresolved concept into schema.

Revised: P5 stays in the design (it is what makes the register normative rather than advisory) but each CV opts in only after its value set is settled. 34-entity-type / 35-relationship / 37-concept-type are ready today — they already agree with their live CHECK or gate and their sets have been exercised. 04-content-type waits on Lane 1 and the owner. The lifecycle/governance four (06, 08, 09, 10) are less obviously IMS-inherited but are still Layer-1 concepts; do not batch them.

VC-3 — 03-layer-vocabulary: a task directive and a requirement point different ways. Surfaced, not resolved by deference.

Section titled “VC-3 — 03-layer-vocabulary: a task directive and a requirement point different ways. Surfaced, not resolved by deference.”

This is the clean instance of the conflict the rule exists to expose:

SourceWhat it says about layerClass of evidence
ID-133 BI-4 (task decision, S451)layer is the Guides audience/depth axis, matched against guide_sections.expected_layer; and “The CV is KEPT, not deprecated”task directive → intent at that time (§0.5b)
Gap tracker B.1 (recorded “Liam decision: Option (ii)”)“currently layers are metadata tags used for filtering, not for UI hierarchy” → layer becomes a UI nesting rule§0.2 requirement evidence — an owner decision in a requirements doc, not a task directive

They are not compatible: BI-4 re-homed and blessed the filter mechanism; B.1 rules that mechanism replaced. Per §0.2’s precedence the requirement wins, and my round-2 REWRITE verdict stands — but the reason is now the requirement, not “the CV is out of date”. BI-4 was a reasonable re-home at the time; it simply did not consult B.1.

VC-4 — Inv 30 downgraded from constraint to revisable task invariant. Changed.

Section titled “VC-4 — Inv 30 downgraded from constraint to revisable task invariant. Changed.”

Round 2 treated “no PR-blocking CI job may use this action (Inv 30)” as a hard boundary and built the export-artefact bridge around it. Inv 30 is an ID-68 task invariant, not a DR. Under §0.5b it is intent at that time. It may well still be right — coupling blocking CI to a private-repo checkout is a real fragility — but it is a choice the owner can revisit, and I should not have designed around it as if it were fixed. The three options in §0 stay, with the relax-Inv-30 option now on equal footing rather than presented as a concession.

VC-5 — 33-reference-item’s source_document_id — same shape, now named.

Section titled “VC-5 — 33-reference-item’s source_document_id — same shape, now named.”

ID-75 BI-15 (task decision) specifies reference_items.source_document_id as NOT NULL ON DELETE RESTRICT, “provenance chain integrity”. The owner ruled at the round-1 board (§0.5, id-364 F1/C5) that “a reference item does NOT need a source_document — different concepts, “source documents are walked, reference items are not”. Owner ruling outranks the task decision; the CV documents the task decision as though settled. Flagged for Lane 1, who owns the column.

  • §2’s eleven-CV delete list. Every row rests on to_regclass / information_schema returning nothing — measured non-existence, not a task saying so. Where a task claim and the measurement disagree (CVs 30/31 read APPLIED-S246; the tables do not exist) the measurement wins, which is the rule working in the intended direction.
  • The content_items / dropped-table findings. ID-131 M6 and ID-145 W1 are cited as events verified in the schema, never as authority for a design.
  • B.1 and B.5. Both are owner decisions recorded in a requirements doc — §0.2 evidence, the top tier. Not downgraded.
  • The DR treatment. Unchanged from round 2: each DR is cited only for what it explicitly ruled, and DR-027 is noted as explicitly excluding this register from its scope.

Section titled “0. Recommended approach — re-derived in the ratified direction”

Restore the register → DB gate that ID-68.27/ID-133 dismantled, and extend it from “detect drift” to “the register can require the DB to change”. Keep the register upstream and normative. Delete the ~21 CVs whose subject does not exist, because a normative register that contains 21 statements about nothing cannot be enforced.

Authority for the direction: the owner’s §0.4 ruling, this session. WP6 is cited below only as measured history (the mechanism ran) and as a cost basis (§5.2 specifies the loader) — never as evidence the design is correct. See VC-1.

Three layers, in dependency order:

LayerWhat it isStatus today
1. The register is contractedOntologyCVSchema validates every register fileBroken by one key. .strict() + Starlight’s title:37/37 files FAIL; 37/37 PASS once title is admitted. The contract cannot parse its own subject
2. The register is exportedregister frontmatter → a machine-readable artefactHand-maintained. The artefact (ontology-cv-baselines.json) exists but is updated by a prose instruction in its own _meta.update_protocol
3. The DB must conform to the exportCI fails when a DB CHECK disagrees with the registerGone. Replaced by fixture↔stale-snapshot comparison, which is a tautology for 3 of 4 CVs

Layer 1 is a one-line fix. Layer 2 is the retired loader plus a JSON write. Layer 3 is the WP6 parity test with its DB oracle changed from a stale snapshot to live introspection.

Why this direction is the right one — a worked example that proves it

Section titled “Why this direction is the right one — a worked example that proves it”

This is not a doctrinal preference; the difference is observable in the current drift.

16-extraction-method.md declares pullmd_trafilatura, pullmd_playwright, pullmd_readability, pullmd_cloudflare, pullmd_reddit. The live source_documents_extraction_method_check contains trafilatura, playwright, unpdf, mcp_create and none of the pullmd_* values.

  • Under my withdrawn DB→register proposal, generation would have silently overwritten the register with the DB’s names. The disagreement disappears without anyone deciding anything.
  • Under register→DB, the gate fails and forces the question: which naming is correct? And the register already holds a ruling on it — its S237 note records that Docling adoption retires the trafilatura/pdfplumber paths. The register is carrying a decision the DB never received. That is the ontology doing the one job a generated register cannot do.

Same shape on 22-origin-kind: DB says derived_from_form_response, register says derived_from_bid_response. The DB is probably right (the platform renamed bid→form throughout — form_instances, form_responses), but the point stands: the gate makes it a decision instead of a silent adoption.

And the settled bid form_type ruling is the same mechanism running correctly: the owner has ruled bid out of the vocabulary everywhere, the migration reclassified biditt under a guarded DELETE, and the DB is now 7 values. The register and fixture still carry 8. Under the ratified direction the remedy is unambiguous — the register is wrong and must be corrected to match a ruling, not to match a table.

1. What would the register → DB sync take?

Section titled “1. What would the register → DB sync take?”

Critical scoping distinction: the gate the owner wants is a test, not a pipeline. parse-flow.py was specified (§5.5) as a write path — parse the register into a DB target table — and it was deferred for two stated reasons: it needs (a) a DB target table for parsed CVs and (b) a localfs.walk_dir source. Neither is needed to make the DB conform to the register. Do not build the cocoindex flow to get the gate.

Costed, smallest first:

PieceWhat it isCostPrecedent it reuses
P1 — admit titletitle: z.string().optional() (+ kh_docubot_owned) on OntologyCVBaseSchema; add retired to STATUS_VALUES~10 minverified: 37/37 pass once title is excluded
P2 — re-instate the readerreaddirSyncgray-matterOntologyCVSchema.parse → emit JSON. This is the retired loader.ts, whose behaviour is still specified verbatim at WP6 §5.2 (5 numbered steps), plus a file write~80 lines, half a dayscripts/generate-content-type-values.ts already does exactly this for one CV
P3 — publish the exportdocs-site CI writes ontology-cv-baselines.json into the canonical repo, replacing the hand-update protocol~1 workflowthe docubot lane already opens cross-repo PRs with a GitHub App token covering both repos
P4 — the conformance gateVitest case: for every CV with a live target, introspect pg_constraint and assert set-equality against the export. Fail with the missing keys in both directions~120 lines, half a daythis is WP6 §5.4 as originally written; blocking ci.yml already carries SUPABASE_SERVICE_ROLE_KEY, POSTGRES_PASSWORD and SUPABASE_URL
P5 — CHECK emission (per-CV opt-in)For a CV the register declares closed where the DB has no CHECK, emit ALTER TABLE … ADD CONSTRAINT … CHECK (col = ANY (ARRAY[…])) for owner review. Emit for review; never auto-apply~60 lines

Total for a working gate (P1–P4): roughly one to one-and-a-half days. That is less than hand-stripping 37 files, and unlike hand-stripping it does not re-rot.

P5 is what makes the register normative rather than advisory — and it is the one piece that must NOT be applied in bulk (VC-2). Seven CVs declare a closed vocabulary the DB does not enforce at all: content_type (source_documents), lifecycle_type, freshness, governance_review_status, publication_status (source_documents half), and origin_type. The ID-131 re-home carried the columns across but not their CHECK constraints — that gap is measured and real.

But the value sets those CHECKs would freeze are task-era decisions (ID-133 BI-3 for content_type; a never-landed S236 “ADD CHECK” ruling for lifecycle_type), and §2b rules the Layer-1 concepts already known to be incorrect. Hardening an unresolved vocabulary into a schema constraint is the “forced to fit” move this census exists to end. So P5 is per-CV opt-in after the set is settled: 34-entity-type, 35-relationship and 37-concept-type qualify today; 04-content-type waits on Lane 1 and the owner; the lifecycle/governance four are not batched.

Under the withdrawn design drift was impossible by construction, which was its hidden cost — it could never report a problem. Under the ratified direction the DB can diverge and CI must fail. Three cases, and the gate treats them differently:

CaseCVsAssertionMeaning of a failure
A — live CHECK exists9: entity_type, relationship, requirement_type, origin_kind, extractor_kind, publication_status (q_a_pairs), extraction_method (×2 tables), ingestion_source (reference_items), plus the form_types tableset-equality register ↔ CHECK, both directions, keys listed on failuresomeone changed one side. Default remedy is a migration, because the register is normative — the reverse remedy needs an explicit ruling
B — register says closed, DB does not enforce7 (listed above)the gate reports the unenforced set; P5 emits the CHECK for reviewthe register is asserting something the DB cannot honour. This is the class the register has flagged three times and never closed
C — no DB targetthe ~21 in §2 + the genuinely planned onesnothing to assertthis is why the deletion is a prerequisite, not a separate task. A normative register cannot contain 21 unenforceable statements and still be a contract

Where it runs. .github/actions/resolve-private-docs/ exists and would let canonical CI read the real register, but its own header states: “Opt-in lanes only: no PR-blocking CI job may use this action (Inv 30).” Only token-parity.yml and supabase-advisors.yml use it, both side workflows.

Inv 30 is an ID-68 task invariant, not a DR (VC-4) — intent at that time, and revisable. Its reasoning is sound on its face (coupling blocking CI to a private-repo checkout is a real fragility), but it is the owner’s to keep or drop, and I have stopped treating it as a boundary. P3’s export artefact is the option that respects Inv 30: the artefact is derived from the register, so the arrow stays register → export → DB gate and the register never stops being upstream. Its cost is one publish hop plus the discipline that the artefact is generated and never hand-edited — which is exactly the discipline that lapsed and produced today’s state.

Two alternatives, now on equal footing rather than presented as concessions: relax Inv 30 for this one gate and read the register directly in blocking CI; or run the gate in docs-site CI where the register is local — conceptually cleanest, since the repo that owns the artefact is the one that fails, but it needs read-only DB credentials in a repo whose ci.yml is explicitly built with “NO environment / secrets”. All three are owner calls; none is a technical blocker.

3. Where does validator.py get its values?

Section titled “3. Where does validator.py get its values?”

This is the OKF-linter leg and it stays broken under any design, so it is the concrete piece of the “biggest disconnect”.

Today scripts/cocoindex_pipeline/producer/validator.py:189-217 declares ALLOWED_ENTITY_TYPES and ALLOWED_RELATIONSHIP_TYPES as hand-written Python frozensets; bundle_writer.py:660-677 serialises them into every bundle’s ontology.json. The intended fix is already written down — ontology-sync.md:103-112: “When id-133 promotes the register to a first-class allowed_types / allowed_relations register with a Python-consumable export, the two constants are meant to be swapped for a load from that export with no call-site change.” id-133 shipped without doing it.

Cost: ~10 lines. P2’s export already contains entity_type (12 values) and relationship (10) with per-value provenance. validator.py replaces the two frozenset literals with a json.load of the export and a frozenset(...) over the keys. _base_ontology_snapshot is unchanged, so the bundle’s ontology.json becomes register-derived end to end — closing register → export → linter → bundle. The existing TestEntityTypeParity / TestRelationshipParity guards keep passing and become meaningful rather than tautological.

One caveat, stated because it is a real design tension: extraction.py’s Pydantic Literals cannot be loaded from JSON at runtime — a Literal is a static type. So the extraction gate stays code-generated or hand-mirrored, and the parity test remains its enforcement. Recall records the same conclusion behind Decision A: “a DB row can’t gate LLM extraction without code regen”. The honest position is that validator.py and the bundle can be register-driven; extraction.py needs codegen (the content-type-values.generated.ts pattern, in Python) or stays guarded by parity. Either is fine — but it should be a stated decision, not the current silence.

4. generate-taxonomy-snapshot.ts — the ask, restated in the ratified direction

Section titled “4. generate-taxonomy-snapshot.ts — the ask, restated in the ratified direction”

Round 1 asked Lane 2 to make the hardcodes “read from the DB”. Wrong arrow — withdrawn.

scripts/generate-taxonomy-snapshot.ts:91-127 hardcodes content_types, platforms and requirement_type as literal arrays. Under the ratified direction the fix is that those three read from the register export, not from the DB and not from a hardcode. The snapshot is a consumer artefact for the Python pipeline, so it should be downstream of the register like every other consumer.

The two keys that are legitimately DB-read — domains, subtopics — are the ones whose values are genuinely client-extensible via admin UI, and form_types is the third. Those stay DB-read, and the register’s role for them is to declare the shape and provenance model, not the row list. That distinction (closed core vocabulary vs client-extensible instance table) is already carried by provenance_model in the frontmatter and by the provenance column on the six tables that have one.

Also relevant to Lane 2: taxonomy-sync.yml fires only on repository_dispatch from /api/admin/taxonomy-sync after an admin mutation. That is why taxonomy_snapshot.json is dated 2026-07-07 — nobody has mutated taxonomy through the admin UI since. Any gate that uses the snapshot as its DB oracle is reading a four-week-old cache. The gate must introspect the live DB, not the snapshot.

Is any of this impractical? One thing, stated plainly

Section titled “Is any of this impractical? One thing, stated plainly”

Inv 30 blocks the direct route. The private-docs checkout action is forbidden in PR-blocking CI, so blocking CI cannot read the register itself. Everything else is straightforward — the schema exists, the loader’s behaviour is specified, blocking CI already holds DB credentials, and the cross-repo App token already exists. The export-artefact bridge (P3) works and preserves the arrow, but it is a genuine extra moving part, and it only holds if the artefact is generated and never hand-edited. Today’s failure is exactly that discipline lapsing: the artefact exists, and its update protocol is a sentence asking a human to remember.

If the owner would rather not add a publish hop, the alternative is to relax Inv 30 for this one gate or to run it in docs-site CI with read-only DB credentials. That is a policy decision I am flagging, not making.

Only the taxonomy rows. taxonomy_domains / taxonomy_subtopics are case-A CVs with a live admin-editable instance table; if taxonomy retires they leave the register with their tables, and if it survives as an ontology layer they are re-derived against the new model rather than patched. Everything else here holds either way, marked per-row in §3. Note also that CVs 01 and 02 both cite classification_disputes in §Relationships, which the owner has ruled retire — those lines go regardless of the taxonomy verdict.


1. The client requirement B.1 — and what it does to my CV 03 verdict

Section titled “1. The client requirement B.1 — and what it does to my CV 03 verdict”

GROUNDING §0.2 directs me to the requirement, not the proposed solution. Reading B.1 that way changes my round-1 Group F handling of 03-layer-vocabulary.md, and it is the clearest instance in this lane of a CV documenting a superseded mechanism for a live requirement.

The requirement (gap tracker B.1, recorded “Liam decision: Option (ii)”): “currently layers are metadata tags used for filtering, not for UI hierarchy” → layer becomes a UI nesting rule: within each topic section, Sales Brief at top, Bid Detail nested beneath, Reference collapsible. Originating case: Advanced Audits §13 “Security & Compliance” exists as one bid_detail section; the intent was Security (Sales Brief) with Security (Bid Detail) nested under it.

What the CV says: 03-layer-vocabulary.md reframes layer as “the Guides audience/depth axis, matched against guide_sections.expected_layer, a query-time filter“Free-text column matched at query time against a record’s classified layer value”. That is exactly the mechanism B.1 rules is being replaced. The CV’s own §Notes acknowledges the nesting ruling and calls the spec “Deferred-S238+”, but its Definition and Relationships still define the superseded semantics as current. Reconciled verdict: REWRITE, not KEEP.

Three concrete consequences, all verified against staging:

  1. The CV’s shape cannot express what the requirement needs. B.1 Phase 1 asks whether nesting uses guide_sections.parent_section_id or a new layer_priority_order column. Both the ordering signals already exist in the DBguide_sections.parent_section_id ✓ and layer_vocabulary.display_order ✓ (the CV itself notes the four rows carry 10/20/30/40). But OntologyCVSchema’s BaselineValueSchema has no ordering field at allkey, label, provenance, definition, and three optional provenance keys. A register that is normative over an ordered vocabulary cannot state the order. That is a real gap in the contract, and it is precisely the kind of thing the register→DB direction surfaces and the DB→register direction would have papered over by inheriting display_order silently.
  2. B.1’s implementation target is currently broken. Phase 2 requires extending get_guide_content to return layer-grouped buckets. That RPC still LEFT JOINs the dropped content_items table and does flat matching (expected_layer = ci.layer), and app/api/guides/[slug]/route.ts:64 still calls it. This is no longer a stray docs-file annotation — it blocks a recorded owner decision. guides and guide_sections are populated (4 and 9 rows).
  3. The value set does not match the requirement’s vocabulary. B.1 names Sales Brief / Bid Detail / Reference; the CV’s keys are sales_brief / bid_detail / company_reference / research. Whether “Reference” means company_reference, research, or both is unresolved, and the nesting order for research is unspecified. UNDECIDABLE — owner input needed, and it is a one-line answer.

On B.5, for contrast — a requirement that landed. B.5 asked for next_review_date + review_cadence_days + a cadence cron. Both columns exist on record_lifecycle today, re-homed off content_items. CVs 06/08/09 describe that facet correctly. So the register is not uniformly behind the requirements — it is behind on the ones where the mechanism changed, which is the pattern worth acting on.


1a. scope_tag — settled valid; its stakes are higher than the register reflects

Section titled “1a. scope_tag — settled valid; its stakes are higher than the register reflects”

Not re-opened. Reporting encoding only, per §0.5, and raising its stakes per the client brief.

The requirement is a safety requirement, not a tagging nicety. Client Item 3: content correctly tagged Internal IT (Bitdefender EDR) surfaced as the answer for production-infrastructure questions — “Production infrastructure doesn’t run Bitdefender, so we’d have given LBBD a false claim.” Closing line: “A high-confidence match in the wrong scope is much more dangerous than a low-confidence match in the right scope.” The client also asks for anti-tags explicitly (“the Bitdefender entry should carry an explicit ‘does not apply to: production infrastructure’ flag”) — which is anti_scope_tag, and it is implemented.

Register encoding: 21-scope-tag.md, status: active, provenance_model: client, client_extensible: true, editable_via: admin_ui. Its baseline_values holds exactly one row — key: client-defined — which is a schema artefact: .superRefine requires ≥1 baseline value for every non-Layer-5 CV, so an open client-defined vocabulary must invent a placeholder to be representable at all. Under a normative register that is a defect worth fixing (allow an empty baseline_values for provenance_model: client), because the placeholder is the kind of not-really-a-value that a conformance gate would have to special-case.

Schema encoding (verified): q_a_pairs.scope_tag and .anti_scope_tag, both text[] NOT NULL DEFAULT ARRAY[]::text[]; mirrored on q_a_pair_history; and the separate q_a_extractions.scope_tags (plural, different table) which the CV correctly warns against conflating. CV 21 is substantively accurate, including its correction that workspaces.scope_tag[] does not exist (verified) and that filtering is caller-side via the q_a_search RPC parameter.

Raised stakes: because Item 3 is a safety requirement, the fact that the scope vocabulary is provenance_model: client with no platform baseline and no enforcement anywhere means a misspelled or missing tag fails open — the pair is simply not excluded. That is worth the owner’s attention independently of this lane, and it is not something the register can fix by itself.

Lane 1 overlap, stated not assumed: Lane 1 is working the same term from the schema side. If they find a read/write asymmetry, that is consistent with the register rather than drift against it — CV 21 records the admin UI as “deferred to v1.1”, and 01-vision.md §3.2 places “taxonomy / tag / scope_tag administration” in the admin surface with v1.1 extending to client-managed vocabularies. That admin surface is the missing writer, if there is one.


2. Delete these now — zero ambiguity (unchanged; stands on its own)

Section titled “2. Delete these now — zero ambiguity (unchanged; stands on its own)”

Eleven CV files whose entire declared subject is a table or column that does not exist on any environment. Verified by to_regclass / information_schema.columns against Platform staging.

This section rests on non-existence, never on emptiness. No row count is used as evidence anywhere in it. No migration is implicated, because there is nothing to migrate. None of it depends on Lane 1’s taxonomy verdict. Under the ratified direction these deletions are a prerequisite for the gate, not an independent tidy-up: a normative register cannot contain statements the DB has no way to satisfy.

#FileWhat the CV declares its subject to beStaging reality (verified)
0505-platform.mdcontent_items.platformtable dropped ID-131 M6; no platform column on any table
0707-dedup-status.mdcontent_items.dedup_statusdropped. CV’s own text: “the owner ruled (b) DROP”
1111-change-type.mdcontent_history.change_typecontent_history does not exist
1313-ingest-source.mdcontent_items.ingestion_sourcedropped. CV’s own text: “permanently historic”
1414-bid-states.mdtypes/bid.ts BID_STATESfile absent at HEAD; lib/bid/ absent; no bids or bid_questions table
1515-workspaces-type.mdworkspaces.typecolumn gone — replaced by application_type_id
1717-diff-mode.mdsource_document_diffs.diff_modesource_document_diffs does not exist
1919-engineering-types.mdthe file listing of types/*.ts6 of its 13 values name deleted files; 4 live files absent from it
2727-form-format.mdform_templates.form_formatform_templates does not exist; the column was never built
3030-procurement-vehicle.mdprocurement_vehicles tabledoes not exist — while README status reads APPLIED-S246
3131-procurement-vehicle-instance.mdprocurement_vehicle_instances tabledoes not exist — likewise APPLIED-S246

Two corrections to carry with the deletions, both of which are live facts wrongly stated:

  • 13-ingest-source.md claims two “live remnants”. One is right (reference_items_ingestion_source_check = rss_feed|url_import — verified). The other is wrong on both table and values: form_templates.ingest_source does not exist; the live column is form_instances.ingest_source with app_upload|minted, not pipeline|app_upload. Re-home the correct fact; drop the incorrect one.
  • 15-workspaces-type.md §“Relationship to tenant & application” is correct and load-bearing (workspace ≠ tenant; one Supabase project per client; no tenant_id column). Move it to reference/entity-glossary.md before deleting the file.

Delete alongside them:

TargetWhy
$KH_PRIVATE_DOCS_DIR/__tests__/ontology-content-items-annotation.test.ts105 lines whose function is to preserve 127 mentions of a table dropped at ID-131 M6, by requiring a marker word, with a 14-line baseline of exemptions. The previous strip attempt produced a guard that institutionalises the rot
the platform entries in ontology-cv-baselines.json, taxonomy_snapshot.json, generate-taxonomy-snapshot.ts:127, and markdown-parity.test.ts:71-74CI currently asserts parity for a CV whose own first paragraph says “DEPRECATED … do NOT re-home”
the dropped-table narrative in the survivorscontent_items 127 / 20 files, plus template_requirements 20, form_templates 12, kb_section 10, content_history 8, procurement_vehicles 7, digests 6, content_citations 4, content_item_workspaces 3, bids 2 — none of which the annotation guard’s literal content_items match catches

scripts/ontology-sync/parse-flow.py — verdict revised. Round 1 said delete. Revised: HOLD, and decide against the gate design. It is the stub for the write path (register → DB target table), which P1–P4 do not need. If the owner ever wants the register materialised into a queryable DB table, this is the specified starting point and one of its stated blockers has cleared — WP6 §5.5 defers on “the DB target table lands alongside the application_types migration in Q-OQR1-16”, and application_types has since landed with 6 rows. That is a status observation, not an argument that the write path is needed (§0.5b — WP6 §5.5 is intent at S236). If the answer is that the export artefact plus the CI gate is sufficient — which I believe it is — then delete it, and delete the scripts/ontology-sync/ directory with it. Do not delete it before that decision; it is the only surviving design record for that path.


Per GROUNDING §6. Grouped by concept. L1? = conditional on Lane 1’s taxonomy verdict.

Group A — subject does not exist; delete now

Section titled “Group A — subject does not exist; delete now”

See §2 for the full evidence table. Every row: RETIRE / high / verified non-existence via to_regclass + information_schema / blast radius = README rows and cross-refs only / L1: no.

05-platform.md · 07-dedup-status.md · 11-change-type.md · 13-ingest-source.md · 14-bid-states.md · 15-workspaces-type.md · 17-diff-mode.md · 19-engineering-types.md · 27-form-format.md · 30-procurement-vehicle.md · 31-procurement-vehicle-instance.md

Group B — case A: live CHECK exists and the register disagrees

Section titled “Group B — case A: live CHECK exists and the register disagrees”

Under the ratified direction the register is normative, so the default remedy for each is a migration — except where an owner ruling already says the register is the wrong side.

TargetVerdictConf.EvidenceBlast radiusOwner actionL1?
16-extraction-method.mdREWRITEhighLive source_documents_extraction_method_check = rss_content, fetch, jina_reader, firecrawl, summary_fallback, docling, trafilatura, playwright, unpdf, mcp_create. Register declares 11 of which 5 pullmd_* values exist in no CHECK; 4 live values absent from the register. The register also carries an S237 ruling (Docling adoption retires trafilatura/pdfplumber) the DB never receivedfeed_articles CHECK (9 values) toothe worked example for the whole direction. Settle which naming is correct, then migrate the DB to itno
22-origin-kind.mdREWRITEhighRegister status: planned, “column not yet in production schema” — but q_a_pairs_origin_kind_check is live with extracted_from_corpus, curated_explicit, derived_from_form_response, imported_legacy, manually_authored. Register says derived_from_bid_**response** and omits manually_authored. Also cites q_a_pairs.source_workspace_id, dropped at ID-145 W132-q-a-pair.md §2.1 transcribes the same wrong CHECKstatus: active; adopt form_response (the platform-wide bid→form rename) and manually_authored; fix CV 32no
23-extractor-kind.mdREWRITEhighRegister status: planned; q_a_extractions_extractor_kind_check is live with prior_bid_response, llm_extraction, yaml_frontmatter_v1, markdown_heading_v1. Register declares mempalace_miner, cocoindex_pattern_a, cocoindex_pattern_b — none exist; 2 live values absentnonestatus: active; reconcile — the three register-only values look like abandoned design, not a ruling the DB missedno
26-form-type.md + ontology-cv-baselines.json + taxonomy_snapshot.jsonREWRITEhighLive form_types = 7 rows. Register, fixture and snapshot all carry 8, incl. bid. Owner has ruled bid out of the vocabulary everywhere and the migration reclassified bidittform-type-parity.test.ts is green because it compares fixture↔snapshot and the snapshot is dated 2026-07-07drop bid from register + fixture; regenerate the snapshot. This is the register being wrong against a ruling — correct it, do not adopt the tableno
12-requirement-type.mdREWRITEmedValues correct (7, matching form_template_requirements_requirement_type_check), but the CV names the table template_requirements with a “planned rename” note; the live table is form_requirement_templates. Its cited per-value counts describe a prod state that no longer applies — a prose-staleness signal, not a liveness argumentnonecorrect the table name; delete the counts and the Q-OQR1-16 noteno

Group C — case B: register declares closed, DB does not enforce

Section titled “Group C — case B: register declares closed, DB does not enforce”

The ID-131 re-home carried these columns across and left their CHECK constraints behind. The missing CHECK is measured; the value set that would replace it is a task-era decision. Per VC-2 the gate reports each of these as unenforced from day one, but P5 emission is per-CV opt-in after the set is settled — never a batch.

TargetVerdictConf.EvidenceOwner actionL1?
04-content-type.mdsource_documents.content_typeKEEP; P5 BLOCKEDhighNo DB CHECK. Enforcement is extraction.py::_validate_content_type reading the snapshot — a Python validator standing in for a constraint. But the 7-value set is ID-133 BI-3, a task decision, and content_type is a Layer-1 concept that §2b rules already known to be incorrectgate reports it unenforced. Do not emit the CHECK until Lane 1 and the owner settle the set — freezing an unresolved vocabulary into schema is the failure mode this census exists to endyes
06-lifecycle-type.mdrecord_lifecycle.lifecycle_typeKEEP; P5 deferredhighCV’s own Notes: “Liam ruling S236’s ‘ADD CHECK’ migration never landed against content_items and the ratification is unresolved in the new home too” — i.e. the set was never ratified in its current homereport unenforced; settle the set before emittingno
08-freshness.mdrecord_lifecycle.freshnessKEEP; P5 deferredhighCV: the pre-M1a content_items_previous_freshness_check “did not carry over”; both columns soft in the new homereport unenforced; do not batch with 06/09no
09-governance-review-status.mdrecord_lifecycle.governance_review_statusKEEP; P5 deferredhighCV: content_items_governance_review_status_check enforced this 5-value set; record_lifecycle has no equivalent CHECK”report unenforcedno
10-publication-status.mdKEEP (partial)highq_a_pairs.publication_status has a real 4-value CHECK matching the register (case A, passing) — the set is exercised, so the source_documents half is the strongest P5 candidate of this groupP5 for the source_documents half once the owner confirms the two columns should share one vocabularyno
29-cocoindex-source-kind.mdsource_documents.origin_typeKEEPmedColumn live, populated, soft. The CV explicitly leaves the coarse-vs-fine CHECK shape as an open owner decision — correctly, in my viewleave open; the gate reports unenforced rather than failingno

Group D — status contradicts reality (case C or mis-stated)

Section titled “Group D — status contradicts reality (case C or mis-stated)”
TargetVerdictConf.EvidenceOwner actionL1?
25-application-type.mdREWRITEhighstatus: needed, “table does not yet exist”. application_types exists with exactly the 6 declared values plus a provenance columnstatus: active; delete the Q-OQR1-16 notes. Values already correctno
18-entity-aliases.mdREWRITEhighCV: “category rename to provenance is pending… not yet shipped”. Live columns: id, alias, canonical, **provenance**, is_active, created_atlanded. The CV describes the v1.1 client-vocabulary seam as not yet existingdelete the rename-pending notes and the row-count proseno
28-change-report-category.mdREWRITEmedstatus: planned; “change_reports (renamed from digests… rename pending)”. change_reports exists; no category columnplanned is right for the column; the rename framing landedno
24-citation-kind.mdUNDECIDABLEmedstatus: planned; “planned citations table, distinct from existing content_citations”. Inverted: citations exists (with citation_type); content_citations does notone question settles it: is citation_kind a planned column on the live citations table, or superseded by citation_type? If superseded → deleteno
20-chunk-kind.mdKEEPhighstatus: planned; content_chunks has no *kind* column — accurate. A genuinely planned vocabulary, not rotkeep; the gate asserts nothing until the column landsno
21-scope-tag.mdKEEPhighAccurate (see §1a). Settled valid, and a safety requirementkeep. Fix the schema so provenance_model: client may carry an empty baseline_valuesno

Group E — the chain machinery (re-derived in the ratified direction)

Section titled “Group E — the chain machinery (re-derived in the ratified direction)”
TargetVerdictConf.EvidenceOwner actionL1?
lib/ontology/schemas.ts:113 .strict()REWRITEhighReal OntologyCVSchema executed over all 37 register files: 37 FAIL on unrecognised key title; 37 PASS once excluded. The docstring calls itself “Zod contract for every docs/ontology/*.md file’s YAML frontmatter”P1 — admit title + kh_docubot_owned. Prerequisite for everything elseno
lib/ontology/schemas.ts:31 STATUS_VALUESREWRITEhigh['active','planned','needed']. CVs 05/07/13 each carry a paragraph explaining the enum “has no literal deprecated value, so this paragraph is the deprecation marker”P1 — add retired. A normative register must be able to state retirementno
BaselineValueSchema has no ordering fieldREWRITEhighlayer_vocabulary.display_order exists in the DB (10/20/30/40); B.1 needs layer ordering; the register cannot express itadd an optional display_order — driven by B.1, not by tidinessno
lib/ontology/loader.tsretired at ID-133 Decision AREINSTATE (as an exporter)highThis was the register→code leg. WP6 §5.2 still specifies its behaviour in 5 numbered steps. Decision A’s stated subject was DB-table promotion; its effect was to delete the register’s only readerP2. Re-instate as a build-time exporter, not a runtime reader — that answers Decision A’s actual objection (“adds a reader without removing drift”) because the export removes the hand-update protocolno
ontology-cv-baselines.json _meta.update_protocolREWRITEhighThe register→artefact link is a prose instruction: “When a baseline changes in the private CV register, update the matching frontmatter block here”P3 — make the artefact generated; delete the instructionno
markdown-parity.test.ts:207REWRITEhighAsserts “The snapshot is DB-derived, so fixture == snapshot == live DB CHECK holds transitively”. False for 3 of its 4 keysgenerate-taxonomy-snapshot.ts:91-127 hardcodes content_types, platforms, requirement_type. For the fourth (form_types) the snapshot is 4 weeks stale and the DB has divergedP4 — change the oracle from the snapshot to live pg_constraint introspection. Blocking ci.yml already carries SUPABASE_SERVICE_ROLE_KEY / POSTGRES_PASSWORD / SUPABASE_URLpartly
validator.py:189-217 frozensetsREWRITEhighHand-written ALLOWED_ENTITY_TYPES / ALLOWED_RELATIONSHIP_TYPES, serialised into every bundle by bundle_writer.py:660-677. ontology-sync.md:103-112 already specifies the swap “with no call-site change”~10 lines — load from the P2 export. Closes register → export → linter → bundleno
extraction.py Pydantic LiteralsKEEP (guarded)highA Literal is a static type and cannot be loaded from JSONstate the decision explicitly: codegen (the content-type-values.generated.ts pattern in Python) or stay parity-guarded. Currently neither is statedno
34-entity-type.md:127, 35-relationship.md:113 “parity-guard twin … Drift in any of the three fails CIREWRITEhighgrep -rn 'parity-guard twin'5 documents, 0 codeP2/P3 make the claim true. Until then, correct it — two CVs assert CI enforcement that does not existno
lib/ontology/concept-schema.ts:130 vs 37-concept-type.mdREWRITEhighCV claims register == TS type enum lockstep. The schema types type as a plain non-empty string, not z.enum(CONCEPT_TYPE_VALUES); the const is exported and unused. No test binds concept_typegate the TS schema on the enum (matching validator.py:116 ALLOWED_CONCEPT_TYPES, which does gate) or correct the CVno
Inv 30 (.github/actions/resolve-private-docs/action.yml)UNDECIDABLE — owner policyhigh”Opt-in lanes only: no PR-blocking CI job may use this action.” An ID-68 task invariant, not a DR — intent at that time, revisable (VC-4). Its reasoning (don’t couple blocking CI to a private-repo checkout) is sound on its face but is the owner’s to keep or dropthree equal options: (a) export-artefact bridge — P3, my recommendation, because it respects Inv 30 and preserves the arrow; (b) relax Inv 30 for this one gate; (c) run the gate in docs-site CI with read-only DB credentialsno

Group F — keep the substance; re-home or correct

Section titled “Group F — keep the substance; re-home or correct”
TargetVerdictConf.EvidenceOwner actionL1?
03-layer-vocabulary.mdREWRITE (was KEEP)highTask-directive vs requirement conflict (VC-3). ID-133 BI-4 (task decision) re-homed layer as the Guides filter axis and declared the CV “KEPT, not deprecated”. Gap tracker B.1 — an owner decision in a requirements doc, §0.2 top-tier evidence — rules that same filter mechanism replaced by a UI nesting rule. The requirement outranks the task directiverewrite against B.1’s nesting semantics; add display_order to BaselineValueSchema; resolve which key “Reference” denotes. BI-4 was a reasonable re-home at the time; it simply did not consult B.1no
get_guide_content / get_guide_coverage + app/api/guides/[slug]/route.ts:64KEEP — raise as a task NOWhighBoth RPCs still LEFT JOIN content_items (dropped ID-131 M6) and do flat expected_layer = ci.layer matching; the route still calls the first. B.1 Phase 2 requires extending exactly this RPCthis blocks a recorded owner decision, not just a docs annotation. Raise before the surrounding prose is deletedno
README §“Where does new data live?”KEEP — re-homehighThe Q1a/Q1b/Q2/Q3/Q4 decision procedure. Post-ID-131/133; routes correctly to q_a_pairs / source_documents / L-concepts; carries the Q1b→Q1a adoption edge and the “never solve placement with a new content_type value” corollarythe most valuable artefact in the directory. Move to reference/; do not lose it with the READMEno
36-three-layer-model.mdKEEP — re-homehigh65 lines, zero content_items, zero dead paths, 4 provenance markers. Carries the anti-conflation guardmove to reference/; it is a storage model, not a CVno
34-entity-type.md / 35-relationship.mdKEEPhigh12 and 10 values, exact match to entity_mentions_entity_type_check / entity_relationships_relationship_type_check and to validator.py:189/205. The two case-A CVs that are fully correctthe model for what the gate should protectno
37-concept-type.mdKEEPhigh5 values == validator.py:116 ALLOWED_CONCEPT_TYPEScorrect the false TS-lockstep claimno
32-q-a-pair.mdREWRITEmedMost actively maintained (kh_docubot_owned, last_updated: 29/07/2026) and still documents source_workspace_id, dropped at ID-145 W1, plus a 4-value origin_kind CHECK that has 5. Its §2 duplicates database.types.ts — the part that rotskeep §1 Mission; drop the column tables. Its drift is the proof that hand-maintenance fails even under active ownershipno
33-reference-item.mdREWRITEmedreference_items live and shape broadly matches; 71 provenance markers, the heaviest accretion in the register. Second task-directive vs owner-ruling conflict (VC-5): the CV documents source_document_id as NOT NULL ON DELETE RESTRICT per ID-75 BI-15 (“provenance chain integrity”) — a task decision. The owner ruled at the round-1 board (id-364 F1/C5) that a reference item does NOT need a source_document“source documents are walked, reference items are not”keep §1 Mission; drop §2. The owner ruling outranks BI-15; the CV presents BI-15 as settled. Column is Lane 1’s — flagged, not resolved hereno
01-taxonomy-domains.md, 02-taxonomy-subtopics.mdUNDECIDABLEhighCase A with a live admin-editable instance table (provenance column present). Values correct (7 domains exact; 34 subtopics vs 33 declared). Existence is Lane 1’s callhold for Lane 1. Either way: the classification_disputes Relationships lines go (owner ruled retire), and “15 active domains / 57 active subtopics” is wrong todayyes

Group G — register-wide prose rot (unchanged)

Section titled “Group G — register-wide prose rot (unchanged)”
TargetVerdictConf.EvidenceOwner action
Dead code-path citationsREWRITEhighScripted check of every backtick-quoted repo-rooted path across the 39 files: 74 of 190 (38%) do not exist at HEAD. README §File index’s “Where to look first” column — the register’s navigation surface — is 9-for-26 deadafter the §2 deletions, extend the docs-site link-validator.test.ts to code paths
Stale countsREWRITEhighREADME “33 controlled vocabularies”, “Output: 29 .md files… Current wave (S236 WP6 A1)”; index.md37”; schemas.ts:51 “the existing 33 CVs”. Live: 37the export makes the count derivable; stop stating it
Provenance-archaeology accretionREWRITEhigh561 markers (S###, {N.M}, ID-N, Q-OQR1-N, BI-N, INV-N, OQ-*, DR-NNN) over 3,474 lines — ~1 per 6. Worst: 33 71, 13 60, 29 49, 04 46, 37 44confine provenance to one ## Provenance footer per file. A normative register states the rule, not how it was reached
26-form-type.md inline revision historyREWRITEhighFour ~~struck-through~~ passages (:483, :486, :503, :508) recording what the CV used to say, plus a §Relationships bullet duplicated verbatim in §Notesdelete
README §Multi-agent update workflowRETIREmedA Drafter/Verifier/Editor/Ratifier wave described as “Current wave (S236 WP6 A1)” — ~290 sessions stale — instructing “do NOT collapse these stages”delete; the export + gate supersede it
Client names in proseREWRITEmedA named client appears in CVs 01, 18, 21, 32 as example data. Private repo, so not a leak — but it makes 01/18 read as tenant records rather than platform baselinesstrip; the provenance column already separates core from client rows

  1. The register→DB gate was built, not merely designed. markdown-parity.test.ts once asserted markdown ↔ tuple ↔ live DB CHECK, via a loader that read the register. My round-1 “never built” was wrong for the gate; it is right only for parse-flow.py’s write path. This makes the fix cheaper — but it is a precedent for feasibility, not for correctness (VC-1). WP6 §1 states the same goal the owner now states, which is corroboration, not authority; and WP6’s own second goal names content_items.content_type as the consumer to wire, so the spec was authored inside the frame under investigation.

  2. The gate is not dead — it is alive, green, and blind. bunx vitest run __tests__/lib/ontology/39 tests pass. It compares a frozen fixture to a four-week-old snapshot whose three most-cited keys are hardcoded in the generator. The one assertion capable of catching a real drift (form_types) is currently wrong and passing.

  3. The layer conflation is genuinely fixed36-three-layer-model.md:62 and 03-layer-vocabulary.md:90 carry reciprocal anti-conflation guards. But 03’s filter semantics is superseded by B.1’s nesting ruling, which is a different and live problem. Fixing the conflation did not make the CV current.

  4. The register’s frontmatter is sound; its prose is not. All 37 files pass the real OntologyCVSchema once title is admitted. Only five CVs have materially wrong values. This is why the exporter is cheap — the register is already machine-readable, which is also why generating from it (P2) is available and generating into it was never necessary.

  5. content_items was addressed, but wrongly and too narrowly. The definitional rot was swept; the remedy was annotate-and-baseline and it matched only the literal string, so content_history, content_citations, content_item_workspaces, form_templates, template_requirements, procurement_vehicles, digests and kb_section survived untouched. 11-change-type.md is the clearest case: 96 lines about content_history, status: active, flagged by nothing.

  6. 01-vision.md §1.1 corroborates the rot rather than settling anything. Both mechanisms it names — “markdown-as-source-of-truth scaffold” and “the parity-tested registry” — were dismantled or never completed after §1.1 was written. Per §2a it is an input with stated provenance, and Lane 1’s diff of the 28/07/2026 commit (15 insertions / 15 deletions, all path re-points and DR-038 noun swaps, §1.1 untouched) confirms the stamp never covered the claim.


5. Coverage — and what I could not cover

Section titled “5. Coverage — and what I could not cover”

Recall pass (GROUNDING §0.6) run before deriving, using the corrected FTS recipe (each term double-quoted). Queries: ontology/register/sync; parse-flow; register source-of-truth; markdown source-of-truth + CHECK; loader + Decision A + retire; wp6 + ontology + harness; 3-way lockstep; layer + nesting + Sales Brief. Recall and code agreed, and recall supplied the fact code alone would not have: the WP6 architecture diagram showing the 3-way lockstep, and Decision A’s stated rationale. Recorded above rather than silently preferred.

Covered in full: all 37 CV files + README + index; every backtick-quoted repo-rooted code path checked for existence (190 paths, scripted, 74 missing); every CV’s declared DB target checked against staging (to_regclass, information_schema.columns, pg_constraint, provenance-column sweep); the real OntologyCVSchema executed against all 37 files; the fixture diffed against register frontmatter for all 6 guarded CVs; taxonomy_snapshot.json diffed against the live DB; the ontology parity suite executed (39 pass); git history of the register (19 commits; 12 of 37 files unmodified since the 2026-06-07 relocation); wp6-ontology-harness/TECH.md §1–§5.5; validator.py, concept-schema.ts, generate-taxonomy-snapshot.ts, markdown-parity.test.ts, form-type-parity.test.ts, ontology-content-items-annotation.test.ts, content.config.ts, parse-flow.py; .github/actions/resolve-private-docs/action.yml, ci.yml, schema-parity.yml, taxonomy-sync.yml; DR-016/019/027/079/082; ontology-sync.md, semantic-linter-gate.md; 01-vision.md §1.1/§2/§3.2; kh-client-feedback.md in full; gap tracker B.1 and B.5; reference/entity-glossary.md.

Not covered, and why:

  • Gap tracker B.2, B.4, B.6 — named by the owner but read only via recall summaries, not opened. B.2 (Product Guide section naming) and B.4 (Advanced Audits dual-tier) both sit on guide_sections, so they likely bear on 03-layer-vocabulary the same way B.1 does. This is the largest gap in my coverage and I would open them next.
  • phase-b-prerequisite-1-onthology-pipeline.md (65 KB) + -feedback-investigation.md (79 KB) — where “Layer 1 = Controlled Vocabulary” and the Talisman 1–6 framing were decided, and the source of the frontmatter shape. Not audited. Given §2b’s ruling that the Layer-1 concepts are already known to be incorrect, these two are the natural follow-on: no DR I read re-derived that framing, and the register’s whole layer: axis rests on it.
  • Whether the values are right as vocabulary (is evergreen the correct lifecycle set?). This census establishes whether each value binds something and whether register and binding agree — not whether the vocabulary is well designed.
  • 06-mcp-tooling.md (cited by CV 31) — could not resolve in either repo.

Round-3 self-audit (§0.5b). I re-read my round-2 report for the “task directive read as correctness” shape. Five instances found, all recorded as VC-1..VC-5 above. The one I would most want a second pair of eyes on is VC-2: I have blocked P5 for content_type on the grounds that ID-133 BI-3’s 7-value set is a task decision over a Layer-1 concept §2b rules incorrect. If Lane 1 concludes content_type survives with that exact set, P5 unblocks immediately and the delay costs nothing; if it does not, emitting the CHECK first would have written the wrong vocabulary into the schema. The asymmetry is why I blocked rather than deferred to the task.

What I need from the owner (per §0.6 — cheaper than a wrong verdict):

  1. B.1’s “Reference” layer — does it mean company_reference, research, or both? And where does research sit in the nesting order? One line settles 03-layer-vocabulary.
  2. 24-citation-kind — is citation_kind planned on the live citations table, or superseded by citation_type? One line settles delete-vs-rewrite.
  3. The Inv 30 choice — export-artefact bridge, relax Inv 30 for this gate, or DB credentials in docs-site CI.
  4. 16-extraction-methodpullmd_* or the DB’s bare names? This is the first real exercise of the register’s authority and it should be the owner’s call, not an agent’s.
  5. P5 sequencing (VC-2) — is ID-133 BI-3’s 7-value content_type set still the intended vocabulary, or does it go back to Lane 1 with the rest of the Layer-1 concepts? Until that is answered I will not propose emitting a CHECK over it. Same question, lower stakes, for lifecycle_type / freshness / governance_review_status.