Skip to content

Ontology sync mechanics

Mechanic 3 of 5. How the closed base vocabulary and a future client overlay combine into the ontology.json that ships inside every bundle, so a bundle is self-describing and portable. Source of truth: scripts/cocoindex_pipeline/producer/bundle_writer.py:762-791 (materialisation); scripts/cocoindex_pipeline/producer/validator.py:182-212 (the base register); lib/ontology/ (the TS register + generated tuple); DR-027 (reference/decision-register.md).


DR-027 (accepted S441): the base system controlled vocabularies move out of the docs-site into the canonical repo — they version with the linter that enforces them, because Platform functionality must not couple to the docs-site. Client overlay CVs are client-owned. And crucially: every bundle repo carries the materialised effective ontology (pinned base snapshot + client overlay) so a bundle is self-describing without reaching back to any Platform system.

There are therefore three ontology surfaces, and it is important not to conflate them:

SurfaceWhereRole
Base register (Python)validator.py ALLOWED_ENTITY_TYPES / ALLOWED_RELATIONSHIP_TYPES (:182-212)the closed vocabulary the linter gates against, and the materialisation source
Register (docs-site markdown + TS)${KH_PRIVATE_DOCS_DIR}/…/ontology/*.md, lib/ontology/the human contract + build-time tuple; a development mirror post-DR-027
Materialised ontology.jsoninside each bundlethe shipped, self-describing effective ontology (base + overlay)

Every run writes one ontology.json at the bundle root (write_ontology_artefact, bundle_writer.py:762-791). Its shape is deliberately plain JSON, not a bespoke ontology DSL — the {132.10} brief was explicit not to invent an ontology format, only to serialise the already-ratified vocabulary:

{
"base": {
"entity_types": [ "capability", "certification", "framework", "methodology",
"organisation", "person", "product", "project", "regulation", "sector",
"standard", "technology" ],
"relationship_types": [ "complies_with", "delivers_to", "demonstrated_by",
"evidences", "holds", "part_of", "references", "requires", "supersedes", "uses" ]
},
"overlay": null
}

That is the actual ontology.json from the published Platform bundle: 12 entity types + 10 relationship types under base, and overlay: null.

_base_ontology_snapshot (bundle_writer.py:660-677) sources the base directly from validator.py’s ALLOWED_ENTITY_TYPES / ALLOWED_RELATIONSHIP_TYPESthe same closed 12-entity / 10-relation register the linter already gates every concept write against (see Semantic-linter gate). It is not invented in the writer; it is a serialisation of the ratified vocabulary. This closes the loop: the ontology a bundle declares is exactly the ontology its concepts were validated against.

overlay is nested under its own key when supplied by a future per-client config (write_ontology_artefact, bundle_writer.py:762-791). No such config exists in-repo yet — the code flags this explicitly, and the writer emits an explicit overlay: null placeholder rather than omitting the key:

# bundle_writer.py:783-786
payload = {
"base": _base_ontology_snapshot(),
"overlay": dict(client_overlay) if client_overlay is not None else None,
}

The null is deliberate signalling, not an accident: a bundle consumer can distinguish “no overlay shipped yet” (overlay: null) from “a base-only artefact IS the full effective ontology” — it is never left to silently assume the latter.

Populating the overlay is {132.34} (pending). Until it lands, every bundle ships overlay: null. Do not document a client-overlay flow as if it were live.

PENDING: self-contained JSON-LD projection (bl-457)

Section titled “PENDING: self-contained JSON-LD projection (bl-457)”

Decided 2026-07-15; implementation is a next-session concern — this is NOT live. bl-457 adds a self-contained JSON-LD @context / IRI projection over ontology.json: an additive layer that gives every base and overlay term a stable IRI, so a bundle’s ontology is consumable as linked data without a network fetch. The base+overlay split extends to namespaces — base terms resolve to a platform-owned shared stable namespace, client/overlay terms to a per-client namespace, with deterministic auto-mint and overlay→base promotion keeping a sameAs link; base IRIs are version-less. Namespace authority is central (platform-owned) — the bundle carries only a materialised inline copy of the @context, in keeping with DR-027’s self-carrying posture. Do not document a JSON-LD flow as if it were shipped; until bl-457 lands, ontology.json remains the plain base/overlay JSON above.

Provenance-consumable-source nuance (traceable)

Section titled “Provenance-consumable-source nuance (traceable)”

The materialisation reads the Python frozensets in validator.py, not the TS lib/ontology/concept-schema.ts, because the TS register has no Python-consumable export yet (_base_ontology_snapshot docstring, bundle_writer.py:660-677; the {132.10} brief). 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. Today they are hand-mirrored frozensets kept in lockstep with extraction.py’s Pydantic Literals by the parity guards described in Semantic-linter gate.

Materialising ontology.json into a bundle at producer run time (this mechanic) is distinct from seeding the ontology into a new client deploy, which is owned by {45.10} (see the {132.15} journal S453 note: the ontology becomes part of a new client deploy, and the Platform private OKF repo is the promotion source client deploys promote FROM). This page documents the per-bundle materialised artefact only; deploy-time seeding is future work tracked elsewhere — do not author it here.