Skip to content

ID-132 SPEC — G-OVERLAY-CV: client-overlay ontology path ({132.34})

ID-132 SPEC — G-OVERLAY-CV client-overlay ontology path

Section titled “ID-132 SPEC — G-OVERLAY-CV client-overlay ontology path”

Kind: Spec slice — a standalone, ratifiable PRODUCT-level slice that defines numbered, testable overlay invariants (OV-N) the Orchestrator can ratify and later dispatch implementation from. It does not implement; it specifies the client-CV-overlay path over the already-landed DR-027 base-ontology seam (bundle_writer.py:write_ontology_artefact). Authored: 11/07/2026 · Task Planner (fresh dispatch, {132.34} DESIGN-FIRST slice). Owner-ratified frame (DR-027 / S441 board R6): “Base system CVs shipped by the product, client-configured overlays in the client’s repo, both versioned and linted together.” Governing rulings to respect (cite, not re-litigate): DR-027 (ontology CV medium — effective ontology ships in the bundle; base source-of-truth in the platform repo; client overlay client-owned), DR-016 (client-owned bundle git repo; human edits are additive producer overrides, never destructive mutations), DR-019 (OKF superset posture — the closed validator-enforced taxonomy is spec-legal producer-side strictness), DR-020 (corpus two-tier admission; per-value client-widening of the allowed set is legitimate), DR-047 (pipeline degrade-to-re-extraction posture — scoped narrowly; contrasted at OV-5). This slice formalises; it does not re-open any of those.

Empirical-verification note. This slice introduces no new external-library symbol. The composition path uses Python stdlib only — json (already imported, bundle_writer.py:139), hashlib (sha256 provenance), pathlib (already imported). The one external-library call in the seam, localfs.declare_file, is unchanged and already carries an EXECUTOR-VERIFY empirical finding against the real cocoindex==1.0.7 engine (bundle_writer.py:34-62). All other symbols cited here are internal Canonical Platform surfaces (import-and-call check N/A). No ABSENT/SIGNATURE_DRIFT risk applies.

Code-intelligence orientation (verbatim; repo:'canonical'). Per the Planner code-intel binding rule; tool outputs quoted verbatim and cross-checked against direct file reads.

  • gitnexus_query({query:'ontology overlay bundle writer', repo:'canonical'})"processes": [], "process_symbols": []. definitions surfaced the exact seam this slice extends, verbatim: Function:…/bundle_writer.py:write_ontology_artefact (startLine 560, endLine 585, module "Producer"), Function:…/bundle_writer.py:write_bundle (615–716), and the two already-shipped overlay tests Function:…/test_producer_bundle_writer.py:test_write_ontology_artefact_base_only_when_no_overlay (537–545) + …:test_write_ontology_artefact_with_client_overlay (548–554). It also surfaced the linter-side tests …/test_producer_validator.py:test_ontology_lint_rejects_relationship_outside_closed_set (202–204) and …:test_ontology_has_exactly_12_entity_types_and_10_relationship_types (211–213). No overlay read path, format, or lint composition symbol exists — confirming this is net-new capability over an existing write seam.
  • gitnexus_context({name:'write_ontology_artefact', repo:'canonical'})"uid": "Function:…/bundle_writer.py:write_ontology_artefact", "incoming": { "calls": [ { "name": "write_bundle" } ] }, "outgoing": { "calls": [ { "name": "_base_ontology_snapshot" } ] }, "processes": []. The sole caller is write_bundle; the sole callee is _base_ontology_snapshot. So the overlay read/compose layer wires in at exactly one call site (write_bundle) and the base half is a single function (_base_ontology_snapshot).
  • ccc search fallback: not required — gitnexus + direct read both returned the real surface (a single write seam with two existing tests). No greenfield disclaimer applies.

Direct reads (cross-check) — the seams this slice extends:

  • bundle_writer.py:545-558 _base_ontology_snapshot() — materialises DR-027’s pinned base snapshot from producer/validator.py’s ALLOWED_ENTITY_TYPES / ALLOWED_RELATIONSHIP_TYPES frozensets (the SAME closed 12-entity/10-relation register BI-13 gates every concept write against). Returns {"entity_types": sorted(...), "relationship_types": sorted(...)}. It does not currently carry the concept-type vocabulary (ALLOWED_CONCEPT_TYPES).
  • bundle_writer.py:560-585 write_ontology_artefact(bundle_dir, *, client_overlay=None)already accepts a client_overlay mapping and nests it under the overlay key; ships overlay: null as an explicit FLAGGED placeholder when none is supplied (:580) so a consumer can detect “no overlay shipped yet”.
  • The overlay pass-through is ALREADY landed one level up. write_bundle already carries a client_ontology_overlay keyword (bundle_writer.py:624) and forwards it verbatim — write_ontology_artefact(bundle_dir, client_overlay=client_ontology_overlay) (bundle_writer.py:714). The actual gaps are therefore not inside write_bundle: (a) the sole production caller producer/flow_def.py:379-385 invokes write_bundle(resolved_bundle_dir, drafts, reference_drafts, theme_config=…, timestamp=…) and never supplies client_ontology_overlay; and (b) nothing reads ontology-overlay.json from the bundle dir to produce that mapping. The whole overlay CHAIN below the read is wired; only the read + its wiring at the flow_def seam is missing (see OV-4, checklist item 2).
  • bundle_writer.py:156-163 _RESERVED_BUNDLE_FILENAMES = frozenset({"index.md", "log.md", "ontology.json"}) — the bundle-level filenames _existing_concept_paths’s rglob("*.md") previous-run keyset scan (:594-613) must never treat as a concept file. .json files are already glob-excluded; .md bundle-level files (README, index, log) must be listed here or they surface as false RunSummary.removed entries (this is Rider R1 below).
  • validator.py:177-207 the closed register (ALLOWED_ENTITY_TYPES 12, ALLOWED_RELATIONSHIP_TYPES 10); validator.py:105-107 ALLOWED_CONCEPT_TYPES (5: topic/product/company/certification/case_study). validator.py:386-416 lint_entity_relation_mentions reads the two frozensets directly (module constants, no injection point). validator.py:277-285 check_type_membership reads ALLOWED_CONCEPT_TYPES directly. validator.py:419-453 check_concept / validate_concept — the BI-13 gate; “No concept is written or published unless it passes this gate.”
  • The linter is a HARD-reject legality gate (themes/okf-platform/semantic-linter-gate.md, checker-verified 11/07/2026): closed vocabulary → HARD-reject; open dimension → SOFT-WARN. entity_type / relationship / concept type are all closed (HARD-reject). This is the load-bearing fact behind OV-5’s fail-loud posture.

Problem. DR-027 ratified a two-part effective ontology: a pinned base snapshot (shipped by the product, source-of-truth in the canonical repo — landed as _base_ontology_snapshot) plus a client-configured overlay (client-owned, in the client’s bundle repo). Today only the base half exists. The per-bundle ontology.json ships overlay: null as an explicit FLAGGED placeholder; the overlay pass-through through write_bundlewrite_ontology_ artefact is landed but fed nothing (the sole production caller flow_def.py:379-385 never supplies it); and no overlay source format, no producer read path, and no linter composition exist. This slice specifies all four legs — format+location, producer read/compose, linter composition, and platform-bundle behaviour — plus two same-module hygiene riders (R1/R2).

Non-goals.

  • Blocking {45.9} first client publication. A base-only effective ontology (overlay: null) is a complete, valid, publishable effective ontology per DR-027’s pinned-snapshot rule (see OV-12). The overlay path is additive capability, never a precondition for the first client to publish. This slice does not gate {45.9}.
  • Composing the overlay into the id-131 EXTRACTION gate. The extraction-side gate (extraction.py’s EntityMentionExtraction.entity_type / RelationshipExtraction. relationship Pydantic Literals) runs at INGEST, upstream of the producer, and does not have the bundle overlay in hand; per-run extension of Pydantic Literals is an id-131/id-133 design question. This slice wires only the producer-side concept-write gate (see OV-9, OQ-OV-2). extraction.py is unchanged.
  • Extending concept-type ROUTING / emission / TS mirror. Overlay-added concept types are composed into the concept-write GATE’s acceptance only (OV-8). The downstream consumers of concept typebundle_write_path routing (bundle_writer.py:64-108), frontmatter emission, the TS mirror lib/ontology/concept-schema.ts, and the OKF CONFORMANCE.md — are not taught overlay types by this slice (see OQ-OV-3).
  • Editing the base register. The overlay is a per-run additive layer; the base validator.py frozensets and their parity guard (validator.py ↔ extraction.py ↔ schemas.ts) are untouched (OV-13).
  • The accept/edit/reject review UI / human-override store. DR-016-adjacent, id-135 territory. Out of scope.

Scope reality — read at ratification (honest framing). This slice ships a gate + a composed, self-describing artefact, pre-wired for follow-ons — it does not ship an end-to-end-useful custom-ontology capability today, for either dimension:

  • Concept types: the concept-write gate can be taught to accept an overlay-added type (OV-8), but the sole ConceptKey feeder (sources/l_records.py:list_concepts, six hard-coded per-type methods) never CONSTRUCTS one — so no published concept of a new type appears until a feeder is added (OQ-OV-3).
  • Entity / relationship types: the concept-write gate can accept overlay-added mentions, but the id-131 EXTRACTION side (Pydantic Literals) never EMITS them — so no concept carries a new mention until the extraction gate is composed too (OV-9, OQ-OV-2). What this slice DOES deliver and is worth ratifying now: the overlay source contract (format/location/additivity/failure semantics, OV-1..7), the self-describing composed ontology.json (OV-6/11 — portable, auditable, DR-027’s whole point), the gate injection point every follow-on needs (OV-8), and platform-bundle + non-blocking guarantees (OV-10/12). The follow-ons (a concept feeder; extraction-gate composition) are named, not hidden.

Each invariant carries an inline (per DR-NNN) tag where a ruling drives it, and a *Test:* clause the Checker uses as its acceptance criterion. OV-N numbers are append-only.

  1. OV-1 (reserved overlay file at bundle root — DR-016 / DR-027). The client-authored overlay source is a single JSON file at a reserved bundle-root path, recommended ontology-overlay.json (OQ-OV-1 ratifies the exact name), distinct from the producer-written ontology.json. It lives IN the client-owned bundle git repo (DR-016), is authored/committed by the client (never declare_file-written by the producer, so it is never orphan-deleted by cocoindex’s reconcile), and is added to _RESERVED_BUNDLE_FILENAMES so no previous-run keyset scan mistakes it for a concept file. Test: a fixture bundle repo with ontology-overlay.json at its root is read by the producer; the file is in _RESERVED_BUNDLE_FILENAMES; the producer never declares/deletes it; JSON is the accepted format.

  2. OV-2 (closed additive overlay schema). The overlay is a JSON object with up to two optional keysentity_types, relationship_types — each a list of strings (additional vocabulary terms). Missing keys default to empty (no extension for that dimension). The schema is closed: any unknown top-level key (including a singular typo like entity_type) or a non-list/non-string-list value is a validation failure handled per OV-5 (fail-loud), never silently ignored (OQ-OV-4). Test: an overlay with {"entity_types": ["widget"], "relationship_types": ["partners_with"]} parses; an overlay with an unknown key {"entity_type": ["widget"]} or a non-list value fails per OV-5.

    AMENDED 2026-08-10 (S550 owner ruling, executed at canonical 20b644468; recorded by id-427 {427.13}). This clause read three optional keys, concept_types first among them, from ratification until now. concept_types is RETIRED from the overlay. The ground is DR-054 meeting DR-141, not disuse: an overlay dimension exists so a client may WIDEN legality, and DR-141 withdrew the closed, validator-enforced concept-type taxonomy — so there is no legality left for this dimension to widen. DR-141’s own Consequences already name DR-054 as in scope for exactly this amendment. entity_types/relationship_types stay because they still widen a real gate (validator.EffectiveOntology composes them and the BI-13 lint enforces the result).

    “Nothing reads it” is NOT the ground and must not be re-derived as one — that is the claim DR-027’s S548 amendment exists to stop being re-derived, having been measured false on this same artefact family.

    Consequence for the producer. bundle_writer._validate_overlay_schema now REFUSES a concept_types key. Because this clause was still ratified and still handed to clients, the refusal shipped a targeted message naming DR-141 rather than the generic unknown-key text, so an author following this document would not be told their key was “unknown”. That contingency is now discharged by this amendment: the targeted branch (_RETIRED_OVERLAY_DIMENSIONS and its retired_keys arm) is retirable, and removing it is a behaviour change routed to a future subtask, not taken here.

  3. OV-3 (additive-only — no removal, no redefinition — DR-027 / DR-019 / DR-016). The overlay may only ADD terms to the effective ontology; it may NOT remove or redefine a base term. Justification: (a) the linter is a legality gate, and an additive-only overlay can only widen what is legal — so adding an overlay can never retroactively invalidate an already-published base concept; (b) removal would break the base parity guarantee (validator.py ↔ extraction.py ↔ schemas.ts) and could silently un-gate a term the extraction side still emits (DR-019); (c) it mirrors DR-016’s grain — client input layers additively over the base, never destructively mutates it. An overlay term that restates a base term is an idempotent union no-op (tolerated; MAY soft-warn as a signal of overlay confusion). An overlay that attempts removal (any mechanism — a remove/exclude key, a negation) is a validation failure per OV-5. Test: an overlay restating a base entity type composes to the same effective set (idempotent, no error); an overlay expressing removal fails loudly.

  1. OV-4 (read the overlay + supply it at the flow_def seam — DR-027). The overlay pass-through below the read is already landed (write_bundle’s client_ontology_overlay kwarg :624write_ontology_artefact :714); the net-new work is the read and its wiring at the producer flow seam. During a producer run, before ontology.json is written, the producer reads ontology-overlay.json from the resolved bundle dir, parses+validates it (OV-2/OV-3/OV-5), and supplies the resulting mapping so it reaches write_bundle(..., client_ontology_overlay=<mapping>) — closing the gap that the sole production caller producer/flow_def.py:379-385 currently leaves (it never passes the kwarg). The composed effective ontology is base ∪ overlay per dimension. When the file is absent, the mapping is None and the artefact is base-only (overlay: null) — not an error (OV-11, OV-12). Read-site decision (take a position, not an OQ): place the read in write_bundle itself — it already performs bundle-dir filesystem reads (_existing_ concept_paths’s rglob over bundle_dir, :594-613) and already owns _RESERVED_BUNDLE_ FILENAMES, so reading one more reserved bundle-root file is cohesive there and keeps flow_def.py a thin composition layer; the alternative (read in flow_def.py and pass the mapping down) needlessly re-plumbs the kwarg through the call site for no benefit. The base half (_base_ontology_snapshot) and the artefact writer keep their contracts. Test: with an overlay file in the bundle dir, write_bundle composes base+overlay into ontology.json; with it absent, ontology.json is base-only with overlay: null; flow_def.py’s producer run exercises the read end-to-end (not only a direct write_bundle unit call).

  2. OV-5 (parse/validation failure = FAIL-LOUD, contra DR-047). A present-but-invalid overlay file (invalid JSON, wrong shape, non-list values, unknown key per OV-2, or a removal attempt per OV-3) ABORTS the producer run for that bundle with a clear, actionable error; it never degrades to a base-only or partial ontology silently. Rationale — and the deliberate departure from DR-047: DR-047’s degrade-to-re-extraction is scoped narrowly to DeserializationError on memo HITs (stale LMDB cache) and explicitly keeps failure semantics for other errors; it is not a general “degrade on any error” posture. Ontology composition gates legality (HARD-reject gate, semantic-linter-gate.md): a silently-degraded overlay would either over-reject every overlay-dependent concept (base-only gate) or, if the gate fell open, admit illegal concepts — both are silent legality corruption of a published, client-owned artefact. Fail-loud mirrors resolve-bundle-root.ts’s fail-loud-when-unset posture and validate_concept’s raise-on-violation posture. An absent file is NOT a failure (OV-4). Test: a bundle with a malformed ontology-overlay.json aborts the run with a clear error and publishes no bundle; a bundle with no overlay file runs to a base-only bundle.

  3. OV-6 (composed-artefact provenance — DR-027 as amended). The materialised ontology.json is self-describing: (a) when an overlay is applied, the overlay key records the composed overlay plus provenance — the source filename and a content sha256 — so a consumer can tell base-only from composed and audit which overlay produced the effective set (recommended shape at OQ-OV-6); (b) overlay is null when no file is present. The artefact stays deterministic (json.dumps(..., indent=2, sort_keys=True), already in place). Test: with an overlay present, overlay carries the overlay terms + source + sha256; absent → null.

    AMENDED 2026-08-10 (recorded by id-427 {427.13}), in two steps that landed separately. This clause was titled “composed-artefact provenance + all-three-dimension base” and its limb (a) required _base_ontology_snapshot to be extended to carry all three CV dimensions, concept_types from ALLOWED_CONCEPT_TYPES alongside entity_types/relationship_types, “so every dimension is uniformly overlay-extensible”. Its Test required ontology.json’s base to carry all three.

    1. The base half is gone (id-427 {427.11}, executing DR-027’s S546 amendment). _base_ontology_snapshot is DELETED and ontology.json has no base key at all — the platform’s entity/relationship CVs stay in this repo, versioned with the linter that enforces them, and are no longer asserted to bundle consumers. The old limb (a) and the base clause of the Test are therefore unsatisfiable, not merely narrowed.
    2. The concept_types dimension is gone (S550 owner ruling). See OV-2’s amendment above for the ground.

    What survives is the provenance limb, renumbered above. overlay: null is preserved deliberately (OV-10, and lib/okf/bundle-graph.ts’s readBundleClassSignal reads the present-and-null key rather than falling through to 'unknown').

  4. OV-7 (effective ontology = deterministic sorted union). For each dimension the effective allowed set is sorted(set(base) | set(overlay_terms)) — a de-duplicated, deterministically ordered union — matching the existing sorted base snapshot so bundle-to-bundle diffs stay stable. Test: base ["a","b"] + overlay ["b","c"] composes to effective ["a","b","c"] (sorted, de-duplicated); re-running with the same inputs yields byte-identical ontology.json.

  1. OV-8 (concept-write gate lints against the effective set — DR-020). For a run that composed an overlay, the BI-13 concept-write gate lints against base ∪ overlay, not the bare base frozensets: check_concept / validate_concept accept an optional effective-ontology argument (an object carrying the run’s effective entity_types / relationship_types), threaded into lint_entity_relation_mentions (entity/relationship mentions). It defaults to base-only when no overlay is supplied, so every existing call site is unchanged. This is the mechanism by which DR-020’s “a client legitimately widens the allowed set” is realised for concept writes. Test: an overlay-added entity_type: "widget" passes the gate only when the run’s overlay includes it; the base-only gate (no overlay) rejects the same value. This is the core testStrategy assertion.

    AMENDED 2026-08-10 (recorded by id-427 {427.13}). This clause was NOT on {427.13}‘s brief; it was found by re-deriving rather than by walking the list, and it was false in two independent ways. As ratified it said the effective-ontology argument carries concept_types / entity_types / relationship_types and is “threaded into check_type_membership (concept type) and lint_entity_relation_mentions, and its Test offered “(or an overlay-added concept type)” as an alternative subject.

    • check_type_membership does not exist. id-427 {427.5} replaced it with validator.check_type_shape, which takes no effective ontology: a concept type is a shape-validated open label under DR-141, with no vocabulary to compose against. check_concept deliberately no longer threads the argument into the type check at all.
    • EffectiveOntology has no concept_types field — it carries exactly two dimensions.

    The mechanism OV-8 describes is intact for the two dimensions that still gate anything; only the concept-type limb is withdrawn. This matters beyond the document: the producer docstrings that cite OV-8 (validator.lint_entity_relation_mentions, bundle_writer.declare_concept, bundle_writer.write_bundle, producer/iri_projection.py’s module docstring) are accurate about what those functions do, and were pointing readers at a clause that was not.

  2. OV-9 (extraction gate out of scope — non-regression). This slice composes the overlay into the concept-write gate only. The id-131 extraction gate (extraction.py Pydantic Literals) is unchanged — its per-run extensibility is an id-131/id-133 coordination follow-on (OQ-OV-2), because extraction runs at ingest, upstream of the producer and the bundle overlay. Test: extraction.py’s entity/relationship Literals and its parity tests are untouched by this slice’s diff.

  1. OV-10 (platform bundle is overlay-less by construction — DR-027). The platform bundle ships overlay: null, base-only, always: it is the base authority (DR-027 — base source-of-truth in the canonical repo), never a client-overlay consumer. An ontology-overlay.json appearing in the platform bundle repo is a configuration error, recommended to fail loudly (OQ-OV-5). Test: a platform-bundle run produces ontology.json with overlay: null; no overlay is composed for the platform bundle.

  2. OV-11 (overlay: null vs empty-overlay are distinct observable states). overlay: null means no overlay file present (or platform bundle) → base-only. A present overlay file that adds nothing (empty object {} or all-empty lists) composes to a base-identical effective set but stamps provenance recording the overlay was present-and-empty (a non-null overlay object with empty term lists + source/sha256). The distinction lets the OKF landing / audit tell “no overlay authored” from “overlay authored, currently adds nothing”. Test: absent file → overlay: null; present-empty file → non-null overlay object with empty term lists and provenance, identical effective allowed-sets to base.

  1. OV-12 ({45.9} non-blocking — DR-027 pinned-snapshot rule). A base-only effective ontology is a complete, valid, publishable effective ontology. A client with no ontology-overlay.json publishes a base-only bundle (overlay: null), which fully satisfies DR-027. The overlay is additive capability layered on later; it is never a precondition for first client publication ({45.9}). Test: a bundle with no overlay file publishes successfully with a base-only ontology.json; {45.9} does not depend on this slice landing.

  2. OV-13 (base parity untouched). The overlay is a per-run additive layer; the base validator.py frozensets (ALLOWED_ENTITY_TYPES / ALLOWED_RELATIONSHIP_TYPES / ALLOWED_CONCEPT_TYPES) and their hand-mirrored parity guard against extraction.py and lib/validation/schemas.ts are unchanged. Overlay composition happens over the base at read/compose time, never by editing the base sets. Test: the base frozensets and the validator↔extraction↔schemas parity tests are untouched by this slice’s diff.

Riders (same-module hygiene — S464, fold into the implementation checklist)

Section titled “Riders (same-module hygiene — S464, fold into the implementation checklist)”

Two same-module (bundle_writer.py scope) hygiene riders were declared for this subtask’s dispatch ({132.34} journal, S464, from {132.33} findings). They are empirically durable regardless of the overlay work but ride this slice because they touch the same file/namespace:

  • Rider R1 (README.md_RESERVED_BUNDLE_FILENAMES). Add "README.md" to _RESERVED_BUNDLE_FILENAMES (bundle_writer.py:163) so the committed bundle README stops appearing in RunSummary.removed / the false "Removed: README.md" log.md line each run. README.md is a .md file, so — unlike ontology.json — it IS scanned by _existing_concept_paths’s rglob("*.md") and MUST be reserved to be excluded (log hygiene + boundary tightening). Fold the new ontology-overlay.json (OV-1) into the same reserved set in the same edit for consistency (it is .json, already glob-excluded, but belongs in the reserved set semantically). Test: a bundle run with a committed README.md reports no Removed: README.md in log.md; README.md and ontology-overlay.json are both in _RESERVED_BUNDLE_FILENAMES.

  • Rider R2 (re-pin ALL stale flow.py:1665 citations → flow.py:1708). The _KH_PIPELINE_DOC_NS namespace constant now lives at flow.py:1708 (verified: grep -n _KH_PIPELINE_DOC_NS scripts/cocoindex_pipeline/flow.py1708:_KH_PIPELINE_DOC_NS = uuid.UUID("fbfaf1ff-1ee4-583c-9757-1674465b2ec1")). The stale flow.py:1665 citation is not a pair — a grep -rn "flow.py:1665" sweep finds at least five live citations, so this rider closes on the grep, not a hand-enumerated list. Known live sites (illustrative, not exhaustive): (i) producer/resource_uri.py:37 docstring; (ii) producer/resource_uri.py:74 (_seed_contract_namespace docstring); (iii) scripts/tests/test_producer_resource_uri.py:6; (iv) __tests__/integration/id138-erasure-cascade.integration.test.ts:112 (comment); (v) docs-site specs/id-138-corpus-durable-home/SEED-CONTRACT.md §2 (line 44). Re-pin every one to flow.py:1708. Explicitly OUT of scope (do NOT rewrite): the ledger provenance records that legitimately narrate the fix history — ledgers/task-list.json + ledgers/tasks/ID-132.md (they record “flow.py 1665->1708” as what WAS fixed) — and this spec’s own R2 text. Test: grep -rn "flow.py:1665" over scripts/, __tests__/, lib/, and the docs-site specs/ tree returns ZERO hits (excluding the ledger provenance records and this rider’s own text); every re-pinned site now reads flow.py:1708.

Implementation checklist (dispatch order for {132.34+})

Section titled “Implementation checklist (dispatch order for {132.34+})”
  1. Overlay reader + validator (new, producer/ — likely bundle_writer.py or a small sibling): read ontology-overlay.json from bundle root; parse; enforce OV-2 (closed schema)
    • OV-3 (additive-only) + OV-5 (fail-loud on invalid, absent = None); stamp OV-6 provenance (source + sha256). Returns the overlay mapping or None.
  2. Base snapshot + artefact + the actual wiring gap: extend _base_ontology_snapshot to add concept_types (OV-6). Note the write_bundlewrite_ontology_artefact pass-through is already landed (client_ontology_overlay kwarg :624:714) — do not re-plumb it. The net-new wiring is (a) calling the step-1 reader (recommended read-site: inside write_bundle, which already reads bundle_dir, per OV-4) and (b) ensuring the composed overlay reaches the artefact for a real producer run — i.e. producer/flow_def.py:379-385’s write_bundle(...) call now exercises a supplied overlay (whether via the in-write_bundle read or an explicit kwarg). Keep write_ontology_artefact’s echo contract (existing test evolves per OQ-OV-6).
  3. Concept-write gate composition (validator.py): thread an optional effective-ontology arg through check_concept/validate_conceptcheck_type_membership / lint_entity_relation_mentions; default base-only (OV-8). Producer passes the run’s effective set to the gate.
  4. Rider R1: README.md + ontology-overlay.json_RESERVED_BUNDLE_FILENAMES.
  5. Rider R2: re-pin every live flow.py:1665flow.py:1708 (≥5 sites: resource_uri.py:37
    • :74, test_producer_resource_uri.py:6, id138-erasure-cascade.integration.test.ts:112, SEED-CONTRACT.md §2); close on grep -rn "flow.py:1665" = ZERO (excluding ledger provenance
    • this spec).
  6. Tests (behaviour-first, test-philosophy.md): the testStrategy fixture (below) + OV-5 fail-loud + OV-11 null-vs-empty + OV-10 platform-overlay-less + OV-13 parity-untouched.

testStrategy (the slice must make this satisfiable): A client repo overlay fixture composes into ontology.json (base + overlay) and extends the linter register for that run; the platform bundle is unchanged; the frontmatter validator accepts overlay-added concept types only when the overlay is present. — satisfied by OV-4/OV-6/OV-7 (compose), OV-8 (register extension + present-only acceptance), OV-10 (platform unchanged).

Coordination notes (implementation-time, not owner questions)

Section titled “Coordination notes (implementation-time, not owner questions)”
  • C-1 (OKF landing / LI-16 machine-facing treatment). ontology-overlay.json is machine-facing JSON, like ontology.json. The OKF landing file explorer (OKF-LANDING.md LI-16: render-excluded, may-be-listed) should give it the same treatment — listable in the tree, never rendered as human/markdown content. Non-blocking; {132.32} owns the landing.
  • C-2 (existing tests evolve). test_write_ontology_artefact_with_client_overlay (test_producer_bundle_writer.py:548-554) asserts payload["overlay"] == overlay for a raw dict; if OQ-OV-6 provenance-wrapping is ratified, that assertion evolves. The base-only test (:537-545, asserts overlay is None) stays green. Flag at dispatch.
  • C-3 (git-sync ordering). The overlay is a client-authored file that predates the producer run — the git-sync flow ({132.12}) checks out the client bundle repo (carrying the client’s overlay commit), the producer reads it, composes, writes ontology.json, commits its output. The producer never authors the overlay. DR-016-coherent.

Open questions (OQ-OV-N) — recommendations; owner ratifies

Section titled “Open questions (OQ-OV-N) — recommendations; owner ratifies”
  • OQ-OV-1 (reserved overlay filename). Recommend ontology-overlay.json (pairs with ontology.json, flat bundle root, self-documenting). Alternatives: overlay.json, .okf/overlay.json. Recommendation: ontology-overlay.json.
  • OQ-OV-2 (extraction-gate composition / end-to-end usefulness). Overlay entity/relationship types are only end-to-end useful if the id-131 extraction side can EMIT them (its Pydantic Literals) — until then a gate that ACCEPTS entity_type: "widget" never sees a widget mention, so the entity/relation dimension is inert end-to-end today, exactly as the concept-type dimension is (OQ-OV-3). Recommend deferring extraction-gate composition to an id-131/id-133 coordination follow-on; {132.34} wires the concept-write gate + ontology.json only (OV-9). Recommendation: defer; concept-write-gate + artefact composition ships now, extraction-gate extension tracked separately (see Scope reality note).
  • OQ-OV-3 (concept-type overlay blast radius + end-to-end inertness). Extending concept type (vs entity/relation) also touches bundle_write_path routing, frontmatter emission, concept-schema.ts (TS mirror), OKF CONFORMANCE.md, and the OKF landing render. It is also inert end-to-end today for a reason the owner must weigh: the sole ConceptKey feeder is sources/l_records.py LRecordsSource.list_concepts() (:452), which fans out to six hard-coded per-type methods (_list_topic_concepts :466, _list_product_concepts :489, _list_company_concepts :500, _list_certification_concepts :508, _list_case_study_ concepts :521, _list_won_bid_case_study_concepts :534). No path constructs an overlay-added 6th type, regardless of what check_type_membership is taught to accept — so gate-composing concept types produces zero observable published concepts of a new type until a feeder emits one. Recommend {132.34} composes concept-type extension into the concept-write gate only (OV-8, per testStrategy); routing/emission/TS-mirror and a feeder for overlay concept types is a follow-on. Recommendation: gate-only for concept types this slice, with the inertness stated plainly (see Scope reality note).
  • OQ-OV-4 (unknown-key / removal handling = fail-loud). Recommend reject (fail-loud) on unknown top-level keys (guards silent entity_type-vs-entity_types typos) and any removal/redefinition attempt (OV-3/OV-5), over lenient-ignore. Recommendation: fail-loud closed schema.
  • OQ-OV-5 (overlay in the platform bundle repo). Recommend hard-reject: the platform bundle is the base authority (DR-027) and must not self-overlay; an ontology-overlay.json in the platform repo is a config error → fail loudly (OV-10). Recommendation: reject.
  • OQ-OV-6 (provenance stamp shape). Recommend overlay: { "source": "ontology-overlay.json", "sha256": "…", "concept_types": [...], "entity_types": [...], "relationship_types": [...] } when present; null when absent (OV-6/OV-11). Recommendation: provenance-wrapped overlay object; evolve the one affected existing test (C-2).

DR-intent (for the Orchestrator, on main — not written in-branch)

Section titled “DR-intent (for the Orchestrator, on main — not written in-branch)”

If the owner ratifies OQ-OV-1/3/4/5/6 as recommended, a new binding ruling is warranted to record the client-CV-overlay contract: reserved bundle-root JSON source; additive-only (no removal/redefinition); fail-loud composition (contra DR-047’s narrowly-scoped degrade); provenance-stamped composed artefact; concept-write-gate composition with extraction-gate deferred. This refines DR-027 (which named the medium but not the format/failure/additivity semantics). Surfaced as intent only — the Orchestrator writes it on main per the decision-register discipline.