ID-132 SPEC — G-IRI-PROJECTION: JSON-LD @context / IRI projection over the OKF ontology layer ({132.43}/{132.44}, bl-457)
ID-132 SPEC — G-IRI-PROJECTION JSON-LD @context / IRI projection
Section titled “ID-132 SPEC — G-IRI-PROJECTION JSON-LD @context / IRI projection”Kind: TECH-lite implementation slice. Numbered, testable IRI-N invariants +
one-to-one Proposed-changes mapping + a 2-Subtask decomposition ({132.43}, {132.44}) the
Orchestrator can dispatch. It specifies the additive JSON-LD @context / IRI projection
layer over the already-landed DR-027 base-ontology seam and the {132.34} client-overlay
compose path; it does not rewrite the authoring format (markdown + plain YAML
frontmatter stays).
Authored: 16/07/2026 · Task Planner (fresh dispatch, bl-457 owner-ratified design).
Owner-ratified frame (bl-457, 2026-07-15 — BINDING, do not re-litigate): “DO the
JSON-LD @context/IRI projection — additive layer, NOT a rewrite. Authoring stays markdown +
plain YAML frontmatter; the producer bundle-writer + frontmatter emitter gain a
deterministic IRI-minting projection step. Ship the @context SELF-CONTAINED/inline in the
bundle (portable, client-owned, no phone-home) over hosted. [[wikilink]] emission was
considered and REJECTED. The base+overlay split extends to NAMESPACES: base terms →
platform-owned stable namespace (…/ontology/base#term); client/overlay terms → a per-client
namespace (…/ontology/client/reference/decision-register.md
on 16/07/2026 (grep hits: DR-079:48, DR-060:298, DR-054:386, DR-027:736).
Empirical-verification note. This slice introduces no new external-library symbol. The mint + projection path uses Python stdlib only —
json(already imported,bundle_writer.py:151region),re+unicodedata(slug normalisation),os.environ(client-id resolution, same pattern as the existingOKF_BUNDLE_DIRread atflow_def.py:202). The one external-library call in the seam,localfs.declare_file, is unchanged and already carries an EXECUTOR-VERIFY empirical finding against the realcocoindex==1.0.7engine (bundle_writer.py:34-62docstring). All other cited symbols are internal Canonical Platform surfaces (import-and-call check N/A per shared-discipline §Empirical verification — stdlib/internal only). NoABSENT/SIGNATURE_DRIFT/BEHAVIOUR_DRIFTrisk applies.
Context
Section titled “Context”Code-intelligence orientation (verbatim; repo:'canonical'). Per the Planner code-intel
binding rule; tool outputs quoted verbatim and cross-checked against direct file reads on
16/07/2026 (the OVERLAY-CV.md line numbers below have since drifted — this slice orients on
the CURRENT lines from direct reads, per grounding Part 3).
gitnexus_query({query:'ontology compose effective ontology bundle writer base overlay snapshot', repo:'canonical'})→"processes": [],"process_symbols": [].definitionssurfaced the exact seams this slice extends, verbatim:Method:…/producer/validator.py:EffectiveOntology.compose#1(startLine 249, endLine 271),Method:…:EffectiveOntology.base_only#0(238–246),Class:…:EffectiveOntology(226–271),Function:…/producer/bundle_writer.py:write_ontology_artefact(761–790),Function:…:write_bundle(820–957),Function:…:read_client_overlay(727–758),Function:…/producer/flow_def.py:run_producer_flow(329–494). No@context,jsonld, orIRI/irisymbol exists anywhere in the corpus — confirming net-new capability over an existing write seam.gitnexus_context({name:'write_ontology_artefact', repo:'canonical'})→"incoming": {"calls": [{"name": "write_bundle"}]},"outgoing": {"calls": [{"name": "_base_ontology_snapshot"}]},"processes": []. The sole caller iswrite_bundle; the sole callee is_base_ontology_snapshot. The new context artefact wires in at exactly one call site (write_bundle), a sibling of this existing ontology-artefact call.gitnexus_context({name:'write_bundle', repo:'canonical'})→"incoming": {"calls": [{"name": "run_producer_flow"}]};outgoingincludesEffectiveOntology.compose,read_client_overlay,write_ontology_artefact,append_log_entry(+ index/theme writers). Sowrite_bundlealready composes theEffectiveOntology(bundle_writer.py:880) and already writes the ontology artefact (:955) — the composed effective ontology this slice must project is already in hand at exactly the point the newcontext.jsonldwrite belongs.ccc searchfallback: not required — gitnexus + direct read both returned the real surface. No greenfield disclaimer applies.
Direct reads (cross-check) — the CURRENT seams this slice extends:
bundle_writer.py:660-677_base_ontology_snapshot()— DR-027’s pinned base snapshot. Returns{"concept_types": sorted(ALLOWED_CONCEPT_TYPES), "entity_types": sorted(ALLOWED_ENTITY_TYPES), "relationship_types": sorted(ALLOWED_RELATIONSHIP_TYPES)}. All three CV dimensions (overlay-extensible). Base terms are already URL-safe[a-z0-9_]slugs (case_study,topic, …).STALE 2026-08-10 (id-427 {427.13}): this seam no longer exists.
_base_ontology_snapshotandALLOWED_CONCEPT_TYPESare both DELETED — the former by {427.11} (DR-027’s S546 amendment;ontology.jsonhas nobasekey), the latter by {427.5} (DR-141). Left in place because this block is captioned as a point-in-time cross-check of “the CURRENT seams this slice extends”, and it correctly records what was current when the slice was designed.validator.py:226-272EffectiveOntology(frozen dataclass) +.compose(overlay)— OV-7 sorted-unionbase ∪ overlayper dimension.write_bundleholds the composedeffective_ontology(bundle_writer.py:880). This is the exact object to project.bundle_writer.py:762-791write_ontology_artefact(bundle_dir, *, client_overlay=None)— serialises{"base": _base_ontology_snapshot(), "overlay": …}toontology.jsonwithjson.dumps(payload, indent=2, sort_keys=True)(deterministic). The new context writer mirrors this serialisation contract exactly.bundle_writer.py:174-189_RESERVED_BUNDLE_FILENAMES— additive frozenset (index.md,log.md,ontology.json,README.md,CONFORMANCE.md,ontology-overlay.json)._existing_concept_paths(:799-818) diffs bundle.mdfiles against this set;.json/.jsonldare already glob-excluded (rglob("*.md")), so a newcontext.jsonldnever surfaces as a falseRunSummary.removed— but is listed here for intent/parity with the other bundle-level artefacts.bundle_writer.py:955write_bundletail:write_ontology_artefact(bundle_dir, client_overlay=overlay)thenappend_log_entry(...). The newwrite_context_artefact(...)call belongs immediately after the ontology write, before the log entry.flow_def.py:198-216_resolve_bundle_dirreadsOKF_BUNDLE_DIRfromos.environ. The client-id resolution mirrors this pattern (OKF_CLIENT_ID).flow_def.py:404-410run_producer_flowcallswrite_bundle(resolved_bundle_dir, drafts, reference_drafts, theme_config=…, timestamp=…)— the one production call site that must pass the resolved client-id.- External §9 validator (
okf_validate.py, scaccogatto pluginokf@0.3.4) walksbundle.rglob("*.md")ONLY (okf_validate.py:162); JSON/JSON-LD artefacts are invisible to it. Its only hard errors are §9.1 (parseable.mdfrontmatter) + §9.2 (non-emptytype). Addingcontext.jsonldis therefore trivially §9-conformant — the checker never inspects it. The internal producer BI-13 gate (validator.check_concept) gates.mdconcepts against the effective ontology, also untouched by a JSON artefact.
Problem. ontology.json today ships a plain-JSON vocabulary (base/overlay term
lists) with no IRIs and no @context — there is no stable, portable, machine-resolvable
identity for a concept type or ontology term. DR-079 makes cross-bundle identity depend on
base-namespace IRIs that do not yet exist; {132.39} (concept-graph UI) needs a
concept-type → IRI projection to render; and the base+overlay composition ({132.34},
DR-027/054) already produces the exact EffectiveOntology object to project, but nothing
mints IRIs from it. This slice adds the deterministic mint + a self-contained
context.jsonld artefact — additive, gating nothing.
Design decisions (Planner rulings on the delegated mechanics)
Section titled “Design decisions (Planner rulings on the delegated mechanics)”The owner delegated the projection mechanics to the Planner (“Exact projection mechanics: where @context lands … what gets projected … validator posture”). Rulings:
-
@context lands in a NEW reserved bundle file
context.jsonld(not embedded inontology.json). Rationale: (a) idiomatic JSON-LD (a@contextdocument is conventionally its own file — Vault-LDcontext.jsonldprecedent); (b) insulatesontology.json’s existingbase/overlayshape and every current consumer + the ontology-artefact tests from any change; (c) the external §9 validator ignores non-.mdfiles, so a sibling JSON-LD file is zero-risk. It is shipped INLINE in the bundle (self-contained, no phone-home) per the ratified frame — “inline in the bundle” is satisfied by a bundle-resident file, not only by an in-ontology.jsonkey. -
What gets projected: ontology TERMS only (all three CV dimensions), NOT per-concept frontmatter. The
@contextmaps every effective-ontology term (base ∪ overlay acrossconcept_types/entity_types/relationship_types) to its IRI. (Amended 2026-08-10, id-427 {427.13}: two dimensions, not three, and client-overlay terms only rather thanbase ∪ overlay— see IRI-5’s supersession note below for both steps and their grounds. The decision this paragraph actually makes — terms, not per-concept frontmatter — is UNAFFECTED and still holds.) Per-concept frontmatter gains noiri:key this wave. Rationale: (a) a per-concept key rewrites EVERY concept.mdfile → churns the {132.35} BI-18 delta-determinism re-proof this slice gates before; (b) it collides head-on with the bl-456/bl-477 frontmatter wave (sharedfrontmatter.py); (c) it is redundant — the concept’stypefrontmatter value IS the join key, so {132.39} resolves concept-type → IRI purely fromcontext.jsonld’s term→IRI map (IRI-11). The ratified “frontmatter emitter gains the projection step” is honoured by making the mint a SHARED pure module (iri_projection.py) importable byfrontmatter.pylater, without forcing per-concept churn now (deferral recorded as OQ-2). -
Client-id source = a new
OKF_CLIENT_IDenv var (mirrorsOKF_BUNDLE_DIR), threaded as an explicitclient_id: str | None = Nonekwarg onwrite_bundle(mirrors the existingclient_ontology_overlaykwarg idiom). The producer carries no client identity today (verified: noclient_id/client_slug/config surface inproducer/), so this is net-new. Overlay-term IRIs are minted ONLY whenOKF_CLIENT_IDis explicitly set; absent,context.jsonldships base-only and overlay terms are recorded as un-projected (advisory, run NOT aborted — IRI-6). Rationale: published IRIs are irreversible, so we never mint an overlay IRI under a GUESSED/derived client-id that a later real client-id would contradict permanently. -
Namespace authority is a SINGLE config constant (
IRI_BASE_NAMESPACE), its concrete value = OQ-1 (owner ratification). Code lands with a loud# OQ-1 UNRATIFIED PLACEHOLDERmarker; the FIRST real client-bundle mint is gated on OQ-1. Recommendation in OQ-1 below.
Invariants (IRI-N — testable; the Checker verifies acceptance against these)
Section titled “Invariants (IRI-N — testable; the Checker verifies acceptance against these)”-
IRI-1 — Deterministic mint. For a term
Tand scopeS,iri(T,S) = namespace(S) + "#" + slug(T). A pure function of(T,S): identical inputs → byte-identical IRI on every run, with no clock/UUID/ordering nondeterminism. -
IRI-2 — Base/overlay namespace split. Base terms mint under
{IRI_BASE_NAMESPACE}/base; overlay terms under{IRI_BASE_NAMESPACE}/client/{client_id}. An overlay term is never minted under the base namespace, and a base term is never minted under a client namespace. -
IRI-3 — Versionless / stable base IRIs. A base IRI carries no version segment (the IRI identifies the concept; version lives in bundle metadata /
log.mdper DR-060). The mint is meaning-stable: the projection never mutates an existing base IRI (an incompatible meaning change is a governance act producing a NEW IRI + deprecate — out of this slice’s runtime scope). -
IRI-4 — Self-contained
@context. Every bundle shipscontext.jsonldcarrying the full term→IRI mapping inline; a consumer resolves any term without a network fetch. -
IRI-5 — All three dimensions projected.
concept_types,entity_types,relationship_typesare each fully projected from the composedEffectiveOntology(base ∪ overlay); no dimension is dropped.SUPERSEDED 2026-08-10 (recorded by id-427 {427.13}). Both halves of this invariant have fallen, in two separate steps, and neither is recoverable by narrowing it:
- The
concept_typesdimension is gone (id-427 {427.5}, DR-141).EffectiveOntologycarries two fields, not three. The classifier that made the projection meaningful wasALLOWED_CONCEPT_TYPES— the register that said which terms were base. With it gone there is no base/overlay distinction for that dimension, and minting an IRI for an organically-minted label would assert exactly the registration DR-027-as-amended retired. Carried as TQ-2 inspecs/id-427-producer-inversion/TECH.md§6, ruled S546: no live requirement; defer, documenting the door. - The BASE half of the remaining two is gone (id-427 {427.14}, executing TQ-1’s S546
ruling as amended S548).
project_contextprojects client-overlay terms only; a bundle composing no overlay emits{"@context": {}}. The base registers became an EXCLUSION filter rather than a mint list.
IRI-6’s “base-only” fallback goes with it — there is no base-only emission left for a missing client-id to fall back to.
Not settled, and deliberately not settled here: TQ-1a is still OPEN (carried verbatim in
tasks/id-427.md{427.14} and in §6 of id-427’s TECH). {427.14} shipped reading (b) — artefact always written, carrying only overlay terms — as a REVERSIBLE DEFAULT, not as an answer, because no requirement with a current source discriminates it from reading (a), the artefact not being written at all on a no-overlay run. - The
-
IRI-6 — Client-id gate on overlay minting (non-gating fallback). Overlay-term IRIs are minted iff an explicit client-id is present. Absent →
context.jsonldis emitted base-only, overlay terms recorded in an advisory diagnostic (logged; not a fatal error); the producer run is not aborted. -
IRI-7 — Slug determinism + collision posture.
slug(T)is deterministic and idempotent on the existing snake_case base terms (case_study→case_study). A within-(scope,dimension) slug collision between two distinct source terms is resolved deterministically (sorted-order first-wins) and recorded as an advisory diagnostic — never gates a run. -
IRI-8 — Promotion alias affordance. The projection structure can carry a
sameAs/alias for a term whose IRI changed via overlay→base promotion, so already- published client IRIs never dangle. The promotion MECHANISM is out-of-scope this wave (no term promoted yet); the emission shape must simply be able to express the alias. -
IRI-9 — Additive, non-gating validator posture.
context.jsonldis additive: the external §9 validator (walks.mdonly) is unaffected; the internal BI-13 gate is untouched; no projection outcome ever aborts a bundle run. -
IRI-10 — Single namespace-authority config point. The base namespace is exactly one constant; changing the authority is a one-line change. Its value is OQ-1 (unratified) and the first real client-bundle mint is gated on OQ-1 ratification.
-
IRI-11 — Concept-type → IRI resolution surface ({132.39}). A consumer resolves a concept’s
typefrontmatter value to its IRI purely viacontext.jsonld’s term→IRI map — no per-concept frontmatter change is required. -
IRI-12 — BI-18 delta-determinism preserved. With unchanged inputs (same effective ontology, same client-id),
context.jsonldis byte-identical run-to-run (sort_keys=Trueserialisation) → no spurious churn in the {132.35} BI-18 re-proof.
Proposed changes per invariant (one-to-one map)
Section titled “Proposed changes per invariant (one-to-one map)”| Invariant | Proposed change | Landing Subtask |
|---|---|---|
| IRI-1, IRI-2, IRI-3, IRI-7, IRI-8 | New pure module producer/iri_projection.py: IRI_BASE_NAMESPACE constant, _base_namespace()/_client_namespace(client_id), slug(term), mint_iri(term, *, scope), project_context(effective_ontology, *, client_id) -> dict (builds the @context dict + advisory diagnostics), deterministic collision guard. No I/O. | {132.43} |
| IRI-4, IRI-5, IRI-6, IRI-9, IRI-10, IRI-12 | bundle_writer.py: CONTEXT_FILENAME = "context.jsonld" added to _RESERVED_BUNDLE_FILENAMES; new write_context_artefact(bundle_dir, effective_ontology, *, client_id) serialising project_context(...) with json.dumps(..., indent=2, sort_keys=True); write_bundle gains client_id kwarg and calls write_context_artefact(...) immediately after write_ontology_artefact (:955). | {132.44} |
| IRI-6, IRI-10 | flow_def.py: resolve OKF_CLIENT_ID from os.environ (mirroring _resolve_bundle_dir) and pass to the one production write_bundle(...) call (:404-410). | {132.44} |
| IRI-11 | No code change — verified by an acceptance test that reads context.jsonld and resolves a known concept type to its base IRI. | {132.44} |
Projection mechanics (concrete)
Section titled “Projection mechanics (concrete)”context.jsonld shape (self-describing; full absolute IRIs so no JSON-LD processor is
required by {132.39}, with prefix definitions for humans/expansion):
{ "@context": { "base": "{IRI_BASE_NAMESPACE}/base#", "client": "{IRI_BASE_NAMESPACE}/client/{client_id}#", "case_study": "{IRI_BASE_NAMESPACE}/base#case_study", "topic": "{IRI_BASE_NAMESPACE}/base#topic", "<overlay_term>": "{IRI_BASE_NAMESPACE}/client/{client_id}#<slug(overlay_term)>" }}clientprefix +client/...term entries are omitted when no client-id is set (IRI-6). Keys are emittedsort_keys=Truefor byte-determinism (IRI-12).- Slug rule (IRI-7):
slug(T)= NFKD-normalise → lowercase → replace every run of characters not in[a-z0-9_-]with a single-→ collapse repeated-→ strip leading/trailing-. This preserves existing snake_case base terms verbatim (they contain only[a-z_]) and normalises arbitrary runtime overlay strings. - Collision guard (IRI-7): iterate terms in sorted order per (scope,dimension); if two
distinct source terms slug to the same fragment, keep the first and append the loser to a
_projection_diagnostics.collisionslist (logged at WARNING); do not raise. - Alias affordance (IRI-8): reserve an optional expanded term form
(
{"@id": "...", "sameAs": "..."}) the projector can emit later; not populated this wave.
Namespace authority — OQ-1 (owner ratification required)
Section titled “Namespace authority — OQ-1 (owner ratification required)”The IRI is an identifier, not a runtime-resolved URL (self-contained @context, no phone-home), so permanence — not live resolution — is the governing property. Options weighed:
w3id.org(RECOMMENDED) — e.g.https://w3id.org/canonical/ontology(→…/base#,…/client/<id>#). W3C Permanent Identifier CG redirection service; the de-facto standard for stable semantic-web IRIs; permanence is decoupled from aisolutionhub’s own DNS tenure (directly addresses “citation-pin-class irreversible”). Cost: a one-time w3id GitHub PR to register/canonical/, and confirming that path is free before the first mint.- aisolutionhub-owned purl-style domain — e.g.
https://ontology.canonical.<tld>— full control, simplest, but IRI permanence rests entirely on the org perpetually holding the domain (a lapse dangles every published IRI). Viable fallback if w3id/canonical/is taken or the org prefers self-hosting. - Public canonical GitHub repo raw URL — REJECTED: not a persistent-identifier authority (repo rename/move breaks every IRI); wrong tool for irreversible identifiers.
- purl.org — viable but functionally dominated by w3id.org for this use.
Recommendation: IRI_BASE_NAMESPACE = "https://w3id.org/canonical/ontology". Code lands
with this as a marked placeholder; the first real client-bundle mint is gated on owner
ratification of OQ-1.
Open questions
Section titled “Open questions”- OQ-1 (namespace authority — BLOCKS first real mint, not implementation). Adopt
https://w3id.org/canonical/ontology(w3id) asIRI_BASE_NAMESPACE? Recommend YES (permanence decoupled from org DNS; semantic-web standard). Fallback: aisolutionhub-owned purl-style domain. Implementation of {132.43}/{132.44} proceeds with a placeholder + loud marker; only the first published bundle’s mint is gated on this. - OQ-2 (per-concept frontmatter IRI — deferral). Confirm we do NOT add an
iri:frontmatter key this wave. Recommend YES, defer (per-concept churn perturbs BI-18 + collides with the bl-456/bl-477 frontmatter wave; thecontext.jsonldterm→IRI map already serves {132.39}). Revisit as a follow-on backlog item after the frontmatter wave settles, only if a per-file IRI proves needed. - OQ-3 (client-id source). Confirm
OKF_CLIENT_IDenv var (mirroringOKF_BUNDLE_DIR) as the client-id source, resolved inflow_def.py. Recommend YES (consistent with the existing env-driven bundle-dir pattern; no overlay-schema change — the OV-2 closed schema stays intact). Alternative (a bundle-root manifest field) is heavier and unneeded now.
File ownership + overlap report (for Orchestrator sequencing)
Section titled “File ownership + overlap report (for Orchestrator sequencing)”{132.43} owns (zero overlap — safe to run in parallel with anything):
scripts/cocoindex_pipeline/producer/iri_projection.py(NEW)scripts/tests/test_producer_iri_projection.py(NEW)
{132.44} owns:
scripts/cocoindex_pipeline/producer/bundle_writer.py(constants region:174-189; newwrite_context_artefactbesidewrite_ontology_artefact:762-791;write_bundlesignature + tail:820-957)scripts/cocoindex_pipeline/producer/flow_def.py(OKF_CLIENT_IDresolution + the onewrite_bundle(...)call:404-410)scripts/tests/test_producer_bundle_writer.py(additive tests)scripts/tests/test_producer_flow_def.py(additive tests)
Overlap vs the bl-456/bl-477 frontmatter wave (SHARED files flagged):
bundle_writer.pyis the one shared file. The frontmatter wave touchesfrontmatter.pybundle_writer.py’s per-conceptdeclare_concept/render_concept_frontmattercall path (:339-353region). {132.44} touches DIFFERENT regions (reserved-file constants:174-189, the ontology/context-artefact writers:762-791, andwrite_bundle’s tail:955) — but it is the same file, so a same-wave parallel dispatch risks a merge conflict at_RESERVED_BUNDLE_FILENAMES(both waves may add reserved filenames) and atwrite_bundle. Recommendation: serialise thebundle_writer.pyedits — dispatch {132.44} and the frontmatter wave’sbundle_writer.pyslice in different waves (cherry-pick order does not matter provided they are not concurrent). {132.43} (new file) has no overlap and may run concurrently with either.
frontmatter.pyis NOT touched by this slice (Design decision 2) — the primary de-risking of the overlap.flow_def.pyis not expected to be a frontmatter-wave file (low risk).- Sequencing gate (bl-457): this slice gates BEFORE {132.35} BI-18 re-proof (in_progress).
IRI-12 keeps
context.jsonlddelta-deterministic so it does not perturb that re-proof.
Subtask decomposition (2 Subtasks — records returned to the Orchestrator)
Section titled “Subtask decomposition (2 Subtasks — records returned to the Orchestrator)”- {132.43} — pure mint module
iri_projection.py+ unit tests. No I/O, no bundle_writer change. Depends on nothing. - {132.44} —
context.jsonldemission inbundle_writer.py+OKF_CLIENT_IDthreading inflow_def.py+ reserved-file + integration/determinism tests. Depends on {132.43}.
Sibling-only dependency ({132.44} → {132.43}, both under Task 132) — no cross-Task dep, no escalation. Estimated total effort ~2–3h (mint module small; integration + determinism + §9-still-passes tests the bulk). Both are additive; neither regenerates Supabase types.