Skip to content

canonical:// resolution

Mechanic 2 of 5. The citation scheme that lets a concept in a client-owned bundle point back at an authoritative Platform record — and the rules that keep those pointers stable, opaque, and provably real. Source of truth: scripts/cocoindex_pipeline/producer/resource_uri.py (the builder — the only sanctioned way to mint a pointer); lib/ontology/concept-schema.ts (the TS validator mirror); specs/id-138-corpus-durable-home/SEED-CONTRACT.md (the frozen seed formulas); BI-6…BI-10 in specs/id-132-okf-concept-producer/.


canonical://<table>/<uuid> is a stable, opaque, vendor-namespaced pointer value — never an identity key, never a resolvable public URL (BI-6, resource_uri.py:8-13). It appears in a concept in exactly two places, and nowhere else (BI-10):

  • the resource: frontmatter field (the concept’s primary record anchor, when one exists);
  • the # Citations body section.

A guard (contains_record_pointer, resource_uri.py:267-278) proves no other frontmatter key or body prose smuggles a pointer in — the semantic linter reuses the same guard (validator.py:426-457) so the two modules cannot diverge on what counts as a stray pointer.

Only three things are citable, and each has its own form:

TargetCitation formBuilderNotes
source_documents rowcanonical://source_documents/<uuid>build_source_document_uri (resource_uri.py:132)per-row anchor
reference_items rowcanonical://reference_items/<uuid>build_reference_item_uri (resource_uri.py:137)per-row anchor
another conceptthe target’s bundle rel_path (e.g. topics/social-value.md)concept_citation_path (resource_uri.py:185)never a uuid, never a canonical:// uri (BI-9)

The per-row anchor form is restricted to {source_documents, reference_items} (_PER_ROW_ANCHOR_TABLES, resource_uri.py:59). build_per_row_uri raises ValueError for any other table (resource_uri.py:116-122) — this is a structural guarantee, not a convention: there is no argument you can pass that emits a per-row pointer for any other table.

The on-disk # Citations trailer form (SPEC §8)

Section titled “The on-disk # Citations trailer form (SPEC §8)”

Since the 15/07/2026 conformance wave every # Citations entry is a numbered, REAL markdown link [n] [label](target) (SPEC §8), normalised deterministically at write time by normalise_citations_section (validator.py:635-663, delegating to the single trailer renderer render_citations_trailer, validator.py:594-632):

# Citations
[1] [canonical://source_documents/9c56fcc6-…](canonical://source_documents/9c56fcc6-…)
[2] [ISO 9001:2015 — Quality Management Certification](/certifications/iso-9001.md)

A record anchor keeps its canonical:// URI as both label and target; a concept cross-link renders as the SPEC §5.1 bundle-absolute path (/certifications/iso-9001.md) with the target concept’s title as the label. The LEGACY bare-path form (a - <target> bullet, the pre-conformance shipped format) still parses — every consumer normalises an entry to its TARGET via citation_target (resource_uri.py:258-264, delegating to parse_citation_entry, resource_uri.py:230-255) before comparing, so a legacy bundle and a conformant one never falsely diverge on the same citation. (The published Platform bundle’s concept trailers keep the legacy form until the {132.35} re-proof regenerates them.)

The Q&A corpus is the one class you might expect to be citable per-row but deliberately is not. The ruling is LOCKED (id-131 PRODUCT BI-8 + id-132 PRODUCT BI-9, owner-ratified 28/06/2026).

Primary rationale — provenance direction (the D8 design reframe). Provenance flows source_document → concept → q_a_pair: a Q&A answer is a downstream derivation of the source documents a concept already distils. A concept citing an individual downstream Q&A answer would point provenance backwards, against that flow. And a bundle is a map over knowledge, not a record index — it cites the authoritative upstream evidence (the source_documents/reference_items rows a run actually read), never every derived Q&A row. On this rationale the ruling holds regardless of database mechanics.

Secondary point (setup-phase-specific, NOT load-bearing). The q_a_pairs primary key is also an opaque, re-minting gen_random_uuid() master that would orphan on a full-replace rebuild during setup (BI-7, resource_uri.py:14-21). This reinforces the ruling, but the ruling does not depend on it: it holds even where the rows are fully stable — e.g. a live, protected client DB, once the platform is live and re-minting has stopped. The design rationale above survives the mechanical one evaporating.

Structural guarantee. There is no function in the builder whose signature accepts a q_a_pairs row id — the DB-internal posture is enforced by the code shape, not left to convention.

Instead the Q&A corpus is referenced only via a table/query form (BI-8, build_q_a_pairs_query_uri, resource_uri.py:154):

canonical://q_a_pairs?scope_tag=<tag>
canonical://q_a_pairs?domain=<domain>&subtopic=<subtopic>

There is deliberately no record_id parameter on that function, so a q_a_pairs row uuid cannot be emitted even by caller error.

Owner-ratified (S463): where a concept’s only record evidence is q_a_pairs rows (which carry no legal per-row anchor form), the concept cites that evidence via concept cross-links, never a bare id or a qa::<uuid> string. This was a live failure on the first Platform producer run — the won-bid case study tried to push its evidence QA into a cross-link as qa::<uuid> and was refused; the fix was guidance in the Pass-1 prompt, not a new pointer form. See Producer operations → BI-17 provenance minting.

Deterministic identity — the seed contract

Section titled “Deterministic identity — the seed contract”

A source_documents / reference_items uuid is not random: it is a deterministic uuid5 derived from a frozen namespace and the admission-time key. This is what makes a citation survive a full-replace rebuild.

scripts/cocoindex_pipeline/producer/resource_uri.py
derive_source_document_id(rel_path) # uuid5(_KH_PIPELINE_DOC_NS, "sd:" + rel_path) :85-93
derive_reference_item_id(source_url) # uuid5(_KH_PIPELINE_DOC_NS, "ri:" + source_url) :96-105

The namespace is frozen: _KH_PIPELINE_DOC_NS = fbfaf1ff-1ee4-583c-9757-1674465b2ec1 (flow.py:1708, reused via a lazy import so the pure builder stays cocoindex-free). The same namespace is used SQL-side (uuid_generate_v5), so the Python walk and the SQL admission resolver mint the same id for the same key — a namespace divergence would silently orphan every citation at first bundle publication.

The seed formulas are FROZEN at first bundle publication (BI-20/21). The single source of truth is specs/id-138-corpus-durable-home/SEED-CONTRACT.md — any change to a formula after freeze is a breaking change that orphans the citation graph. Post-mint, source_documents.id is authoritative and never re-derived from rel_path: the SQL resolver resolves by content_hash first, so the same bytes at a new path resolve to the stored id (a rename updates only the mutable logical_path, never the identity). Read the SEED-CONTRACT before touching any sd: / ri: / qa: / chunk: formula.

  1. canonical://source_documents/<uuid> or canonical://reference_items/<uuid> — look the row up by primary key in the Platform DB (source_documents / reference_items). The uuid is stable across rebuilds.
  2. canonical://q_a_pairs?scope_tag=… / ?domain=…&subtopic=… — run the query; the Q&A corpus is answered by set, never by a single opaque row id.
  3. a bundle rel_path (e.g. certifications/iso-9001.md) — open that concept file within the same bundle. This is a concept→concept cross-link.

lib/ontology/concept-schema.ts mirrors the Python allowlist so the app-side schema and the pipeline validator enforce the same per-row table set:

  • CANONICAL_RESOURCE_URI_PATTERN (concept-schema.ts:74) matches only the per-row form ^canonical://(?:source_documents|reference_items)/….
  • CANONICAL_QUERY_RESOURCE_URI_PATTERN (concept-schema.ts:82) matches the BI-8 q_a_pairs query form.

Known nuance (traceable, not a bug): the TS per-row regex matches only the per-row form, so the q_a_pairs query form is validated by the second TS pattern — the two are ORed in isValidConceptResourceUri (concept-schema.ts:87-92). resource: is optional in both the TS schema (concept-schema.ts:99-105) and the landed Python validator (which excludes resource from its hard-required key set) — PRODUCT.md BI-12 requires it only “where one exists”. TECH.md’s terser BI-table row still lists it as unconditionally required; the landed code treats it as optional. This tension is flagged in the {132.7} report and recorded in the schema module’s own docstring (concept-schema.ts:34-47) — cite the code, not the table row.