feat(schema): name the schemas' identity on a branded, family-versioned URL - #109
Merged
Conversation
There was a problem hiding this comment.
Sorry @macanderson, you've used your own review budget of 250,000 diff characters for the last 7 days.
You can request another review in 16 hours and 7 minutes by commenting @sourcery-ai review. Upgrade to get a review now.
Reviewer's GuideThe PR gives both JSON Schemas a stable, branded Sequence diagram for post-publish schema identity verificationsequenceDiagram
participant Workflow as publish-spec.yml
participant CDN as contextgraphprotocol.org
participant Schema as Published schema JSON
Workflow->>CDN: sync schema/*.json to /schema/v1/
Workflow->>CDN: invalidate CDN
Workflow->>CDN: dereference schema $id URL
CDN-->>Workflow: served schema body
Workflow->>Schema: read $id
alt served $id matches requested identity
Workflow-->>Workflow: publish succeeds
else identity mismatch
Workflow-->>Workflow: publish fails
end
Flow diagram for schema identity validation and URL hygieneflowchart TD
Start[Schema validation]
Offline[validate-examples.py]
Pin[Compare schema $id with PUBLISHED_SCHEMA_BASE]
Hygiene[check-deploy-hygiene.py]
Prefix[Match longest served URL prefix]
Deploy[Published deployment]
Live[Dereference each $id and compare served body $id]
Pass[Checks pass]
Start --> Offline
Offline --> Pin
Pin --> Hygiene
Hygiene --> Prefix
Prefix --> Deploy
Deploy --> Live
Live --> Pass
File-Level Changes
Assessment against linked issues
Possibly linked issues
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
This was referenced Aug 30, 2026
macanderson
force-pushed
the
fix/schema-id-brand-b726a4bc
branch
from
August 30, 2026 04:13
afb6c0f to
85bc531
Compare
…ed URL Both schemas' `$id` moves from `raw.githubusercontent.com/macanderson/context-graph-protocol/main/schema/…` to `https://contextgraphprotocol.org/schema/v1/…`. `$id` is the identity a validator resolves and a third party quotes, so this is protocol-visible rather than a deployment detail. The old URL had three defects: a code-hosting domain with no stable-content guarantee, not the project's own name, and a `main` pin — a git branch, so a `1.x` additive minor silently changed what a cached resolver saw. ADR 0013 records the decision and why the version segment is the **major family** rather than the minor. Within `contextgraph/1` evolution is additive-only, so a consumer holding an older copy is never wrong, only less complete; per-minor paths would mint a new identity every minor and buy nothing. `v1` is `contextgraph/1`, not the crate version, which is already 2.x against that same wire. ADR 0008's rule — advertise only on a host this repo serves — is what permits the move rather than resisting it. #78 made the apex such a host for `/schema/`; only 0008's conclusion for the schemas expires, and it is amended in place rather than left to contradict what ships. Nothing is asked of implementers. The bytes are identical, the raw URL keeps returning 200 (guaranteed by never moving `schema/*.schema.json`), and every `$ref` in both schemas is a same-document pointer with no cross-schema reference, so resolution is unchanged offline and online. Identity is checked in two halves, neither weakened for the other: `validate-examples.py` pins the string and stays offline, because it runs on every PR and fork against commits whose publish has not happened; `publish-spec.yml` publishes the identity path, then dereferences it and fails unless the served body reports that same `$id`. A 200 alone is not accepted — a static site answers its 404 page with one. Fixes a live gap in `check-deploy-hygiene.py` found on the way: its URL pattern required the filename to sit directly under `schema/`, so it matched no versioned URL at all and would have gone silently blind to the two `$id`s it exists to police. Its prefix map also needed longest-match, since `/schema/` and `/schema/v1/` now nest and resolve to different repo paths. Also repairs three docs that had come to state the opposite of what ships: CONTRIBUTING.md still said a `contextgraphprotocol.org/schema/…` URL 404s (stale since #78), `validate-examples.py`'s docstring claimed a byte-identical served-copy fetch it never performed, and ADR 0008 cited a `SERVED_HOSTS` that The ADR is numbered 0013 rather than 0012: PR #106 was opened first and holds 0012, and because the two filenames differ git would have merged both cleanly into a tree with two ADR 0012s and no CI able to see it. Numbers are being allocated centrally until a guard exists — that guard, and the GUIDE's ADR index skipping 0009-0011, are #129. Closes #79, Closes #58 Signed-off-by: macanderson <mac@oxagen.sh>
macanderson
force-pushed
the
fix/schema-id-brand-b726a4bc
branch
from
August 30, 2026 04:30
85bc531 to
282fa52
Compare
macanderson
added a commit
that referenced
this pull request
Aug 30, 2026
Two doc conflicts, both in tables this branch and main each added a row to. docs/GUIDE.md — main's #109 added ADR 0013 plus a comment saying 0009, 0010 and 0011 were missing from the table and tracked in #129. This branch fills that gap, so the merged table carries 0009 through 0013 and 0017, and the comment goes with the gap it described (#129). docs/profiles/context-exchange-provider.md §9 — LF1 and LF3 are this branch's, describing the vectors it publishes and what the suite now checks; LF2 is main's, where #109 moved the schema $id from a GitHub-raw URL to the protocol's own branded, family-versioned one. Signed-off-by: macanderson <mac@oxagen.sh>
This was referenced Aug 30, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Pull request
Summary
Both JSON Schemas'
$idmoves fromraw.githubusercontent.com/…/main/schema/…to
https://contextgraphprotocol.org/schema/v1/…— the protocol's own domain,versioned by major protocol family.
$idis what validators resolve andthird parties quote, so this is protocol-visible; nothing is asked of
implementers.
Closes #79, Closes #58
The decision, and why
#79 asks for one call to be made and recorded: is the branded
$idversioned,or does it track
mainthe way the current one does? Versioned, by majorfamily. ADR 0013
carries the reasoning:
mainis a git branch — unbounded change under a stable name. A1.xadditive minor already changes what a cached resolver sees. The protocol has
no concept of
main; that segment names where the bytes happen to live./schema/1.2/) are the wrong granularity and arerejected. Within
contextgraph/1evolution is additive-only (GOVERNANCE.md,SPEC §13 U1–U4), so a consumer holding an older copy is never wrong, only
less complete — the staleness a per-minor path guards against cannot hurt.
Against that it would cost a new published path and a fresh
$ideveryminor, with every pin going stale by design.
axis the protocol already versions on — the URL now agrees with
versions_compatible, which compares exactly this prefix.v1meanscontextgraph/1, not the crate version — which is already2.xagainst that same wire (docs/stability.md). The ADR says so explicitly, because
that is the misreading waiting to happen.
contextgraph/2would be/schema/v2/, and/schema/v1/would keep answering.ADR 0008 permits this rather than resisting it
Its rule (1) — advertise only on a host this repo serves — is unchanged. #78
made the apex such a host for
/schema/. Only 0008's conclusion for theschemas ("GitHub-raw is the only host it can honestly advertise", called
permanent) expires, and it is amended in place rather than left to contradict
what ships.
What changed
schema/*.schema.json— the two$idvalues. The files do not move;that is what keeps the raw URL resolving.
.github/workflows/publish-spec.yml— publishes/schema/v1/, and itspost-publish step now dereferences each
$idand asserts the served bodyreports that same
$id. The old step accepted any 200 whose body had a.title, which would happily accept a stale object at an alias path. Headercomment rewritten: it said the publisher deliberately does not move identity.
schema/validate-examples.py— pins the new$ids via one sharedconstant. Stays offline; see the ordering note below.
.github/scripts/check-deploy-hygiene.py—/schema/v1/prefix row,longest-prefix matching, and a URL pattern that admits a version segment.
SPEC.md§13.1 (new),PUBLISHING.md,MIGRATION.md§6,CHANGELOG.md,CONTRIBUTING.md,docs/GUIDE.md, and the lifecycle profile's normative LF2.Two guard bugs found on the way
check-deploy-hygiene.pywas about to go silently blind. Its URLpattern required the filename to sit directly under
schema/, so it matchedno versioned URL — the two
$ids it exists to police would have producedno offender check and no missing-artifact check, and a green run. Proven by
diffing old and new match sets across every tracked file: 0 lost, +2
gained, the two
$ids./schema/and/schema/v1/nest and map to different repo paths, so the old order would have resolved
the versioned URL against the wrong row and looked for a
schema/v1/thatdeliberately does not exist.
Three docs that stated the opposite of what ships
CONTRIBUTING.mdstill said acontextgraphprotocol.org/schema/…URL 404s(stale since #78);
validate-examples.py's docstring claimed it checked "abyte-identical served copy" when the code is a pure offline string compare, no
network anywhere; ADR 0008 cited a
SERVED_HOSTSthat #78 replaced with aprefix map. All three fixed here.
The ordering hazard, stated plainly
https://contextgraphprotocol.org/schema/v1/…404s right now — this PRcreates it. The fix is the publisher, not a weakened validator:
validatejob (the only one that runs on a PR) runsvalidate-examples.py, which is offline and passes.publishjob isif: github.event_name != 'pull_request'. On merge itsyncs
/schema/v1/, invalidates the CDN, then dereferences. Order isenforced by step order.
So the identity cannot be live before the merge, and cannot be dead after one
without failing the job. The offline validator was deliberately not made to
fetch: it runs on every PR and fork, against commits whose publish has not
happened, so a fetch there trades a real guarantee for a flaky one.
Evidence
Witness (fails on the old value, passes on the new): reverting only the two
$idstrings makesvalidate-examples.pyreport2 failure(s)—FAIL $id is https://contextgraphprotocol.org/schema/v1/…for both schemas; restoring them returns rc=0.
Negative test of the hygiene guard: a probe line carrying
…/schema/v1/does-not-exist.schema.jsonandhttps://cgp.oxagen.sh/schema/v9/…makes both halves fail (rc=1) — so theguard sees versioned URLs now, rather than passing vacuously.
$refcoherence:jqover both schemas returns only#/$defs/…— every$refis a same-document pointer and neither schema references the other,so
$idhas no bearing on resolution. Both validate fully offline. No Rust,Python, TS or Go source in this repo reads
$idor the old URL (checked).#58 sweep:
rg -n 'contextgraphprotocol\.io|context-graph-protocol\.org'returns two hits, both prose citations of the dead host in the records that
explain why it was wrong (
validate-examples.py's comment, ADR 0013's Context).Not zero, and deliberately so — rewriting them would make the history lie about
itself, the same reasoning
check-deploy-hygiene.pyalready encodes in itsEXEMPTlist. No live URL remains.Cargo.tomlhomepageis correct(
https://contextgraphprotocol.org, fixed in #45); SDK metadata carries nostale host.
ADR numbering
The ADR is 0013, not 0012. PR #106 was opened first and holds 0012
(
0012-sdk-version-pins-share-a-major.md). Because the two filenames differ,git merges both cleanly and the tree ends up with two ADR 0012s — neither PR's
CI can see it. Numbers are being allocated centrally until a guard exists;
that guard, and the GUIDE's ADR index skipping 0009–0011, are #129.
Which issue closes, and why both
Both. They are one problem seen twice, and each contributes something the other
does not, so closing either alone would strand work.
(versioned vs
main), the ordering constraint, and the "old URL must keepresolving" requirement.
Cargo.tomlhalf landed in feat(trace): host execution trace + replay oracles (contextgraph-trace, sketch) #45,and its premise that the branded URL 404s was retired by ci: publish the schemas and spec to contextgraphprotocol.org on merge #78. What survives
is its stale-host sweep and its open question, "pick one so the
$idisdereferenceable", which is exactly what ADR 0013 settles. That question was
the reason it stayed open, so it closes here.
Checklist
git commit -s, DCO)CHANGELOG.mdupdated under[Unreleased]On
fmt/clippy/test: this change touches no Rust — the diff is twoJSON string values, two Python scripts, one workflow and prose. Per SCR-001 the
Rust suite is CI's job and nothing here could move it. The checks that can
judge this change were run and are quoted above.
On the witness:
validate-examples.py's$idpin is the witness, and thefail→pass flip is demonstrated above rather than asserted. The guard change has
its own negative test and a match-set diff.
Registry submission
Protocol-stability impact
contextgraph/1Nothing on the wire changes: no field, no type, no
PROTOCOL_VERSION. Whatchanges is the schemas' published identity, which GOVERNANCE.md reaches
through the conformance-requirement clause — the lifecycle profile's normative
LF2 pins that identity, and is updated here. GOVERNANCE.md's process for
such a change is followed:
CHANGELOG.mdunder[Unreleased], and a witness.Old and new URLs both serve identical bytes and
$refresolution is unchanged,so a
contextgraph/1.0-draftprovider, a pinned local copy, and a cachedfetch of the old URL all keep working untouched.
License
By submitting this pull request, I agree to dual-license this contribution
under MIT OR Apache-2.0, as certified by my DCO sign-off.
Summary by Sourcery
Adopt branded, major-family-versioned URLs as the canonical identities for both JSON Schemas while preserving backward-compatible access to existing URLs.
Bug Fixes:
$id.Enhancements:
contextgraphprotocol.org/schema/v1/while retaining the existing aliases and raw GitHub URLs.CI:
Deployment:
/schema/v1/as the canonical path while preserving existing schema locations.Documentation:
Tests:
$idvalues.