canonical:// resolution
canonical:// resolution
Section titled “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/.
What a canonical:// pointer is
Section titled “What a canonical:// pointer is”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
# Citationsbody 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.
The citable-target set
Section titled “The citable-target set”Only three things are citable, and each has its own form:
| Target | Citation form | Builder | Notes |
|---|---|---|---|
source_documents row | canonical://source_documents/<uuid> | build_source_document_uri (resource_uri.py:132) | per-row anchor |
reference_items row | canonical://reference_items/<uuid> | build_reference_item_uri (resource_uri.py:137) | per-row anchor |
| another concept | the 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.)
Why q_a_pairs stays DB-internal
Section titled “Why q_a_pairs stays DB-internal”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.
derive_source_document_id(rel_path) # uuid5(_KH_PIPELINE_DOC_NS, "sd:" + rel_path) :85-93derive_reference_item_id(source_url) # uuid5(_KH_PIPELINE_DOC_NS, "ri:" + source_url) :96-105The 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.
How a reader resolves a citation
Section titled “How a reader resolves a citation”canonical://source_documents/<uuid>orcanonical://reference_items/<uuid>— look the row up by primary key in the Platform DB (source_documents/reference_items). The uuid is stable across rebuilds.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.- 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.
Validator parity (TS ↔ Python)
Section titled “Validator parity (TS ↔ Python)”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-8q_a_pairsquery 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.