Skip to content

Pre-freeze round 2: close the enforcement gaps, and the ones found while closing them - #63

Merged
macanderson merged 9 commits into
mainfrom
pre-freeze/enforcement-and-integrity
Jul 26, 2026
Merged

Pre-freeze round 2: close the enforcement gaps, and the ones found while closing them#63
macanderson merged 9 commits into
mainfrom
pre-freeze/enforcement-and-integrity

Conversation

@macanderson

@macanderson macanderson commented Jul 26, 2026

Copy link
Copy Markdown
Owner

Builds ten issues. The theme is the same one PR #60 started: a guarantee nobody can falsify is not a guarantee. Tightening the checks kept finding surfaces that only looked enforced.

The finds

Three defects surfaced from making an existing check stricter, not from reading the issues:

§H4 correlation was unenforced. The drop-correlation-id misbehave mode claimed to trip a correlation check that did not exist. It "went red" only because dropping the id desynchronizes the host's demultiplexer and breaks everything downstream. Any of the three SDKs could have declared correlation: true, never echoed an id, and passed the suite.

§Q1 kinds was a filter nothing honored. sample_query sends kinds: , so every provider was only ever asked the unfiltered question — and not one of the four reference implementations honored the filter. They declared capabilities.query.kinds and returned whatever they had. ADR 0004's dead-capability surface in its subtler form: not an unreachable field, a reachable one that silently does nothing.

R3 was defeatable in one line. compose_context interpolated untrusted content into the <frame> fence with no escaping, so a frame containing </frame> ended its own quoted block and everything after it was read at the host's own level. SPEC §11.1 already listed this; it is now closed.

What landed

Issue
#54 ContextQuery.kinds/.anchors were schema-required but omitted by skip_serializing_if — the unfiltered query was un-representable in conformant JSON. Fixed, plus schema/reference-vectors.ndjson: every envelope serialized from the reference types in minimal and maximal form, drift-gated in Rust and schema-validated in CI.
#51 The red script now requires each misbehave mode to be caught by the check it declares it trips. Added the correlation check, §Q1 + kinds-filter, granted_at stamping (with a dependency-free F4 formatter), and the docs-site witness that had never run in CI.
#15 Fence escaping. Chosen over an unguessable delimiter deliberately: composition's contract is a byte-stable prompt prefix, so a per-turn nonce would forfeit the prompt cache to buy what escaping already provides.
#7 §G4 gives "anchored" a decidable predicate (zero hops via uri, one hop via relations.target_uri). The fixture is now genuinely graph-capable — it had graph: false and no relations at all, so G1/G2 passed vacuously.
#31 Attribution: selected/rendered/cited against the existing FrameId, paired with UsageReport for value-per-token.
#53, #55 Stack traces as a first-class ingest kind, CSV/aligned tables with currency+percent typing, F4 log-timestamp normalization, duplicate-line collapse; plus a docs page and a runnable example.
#58, #21, #56 $id pointed at an unregistered host; now canonical, served from the site, and drift-gated. Rename residue. Removed a completed plan doc.

Deliberate non-additions

#31 ships no context/feedback method and no Capabilities.feedback. Adding a negotiated capability with no provider consuming it and no check able to witness it would recreate the exact defect ADR 0004 removed and §Q1 had to be written to repair — and the asymmetry favors waiting: adding it later is family-safe, removing a dead one is not. The vocabulary ships (it must be shared for scores to compare); the transport is sketched with its open questions in docs/sketches/attribution-feedback.md.

Also worth knowing

Several issues I first selected turned out already satisfied by the prior sweep — #12 (digest verification: contextgraph_host::verify + the F5-bytes host check exist), #11 (E1 fingerprint enforcement), #4/#5/#6/#9 (correlation ids, upsert/subscribe dropped, error codes), and #52's fixture half (goldens already cover representations and satisfy B3). They can be closed on evidence. I swapped in issues with real residue.

Verification

Every gate run locally, green:

  • cargo fmt --check, cargo clippy --workspace --all-targets -D warnings, 22 test suites
  • conformance 12/12 green; every misbehave mode caught by its declared check
  • host-conformance, schema validation, docs-site witness
  • all three SDK example providers (TypeScript, Python, Go) still 12/12 conformant — checked, because two new checks could have broken them
  • the ingest example was run, not just compiled

Two changes were verified red-then-green rather than assumed: restoring the old required list fails vector line 4 while every hand-authored example still passes (which is why that bug lived so long), and mutating the served schema copy trips the drift guard.

…e it (#54)

`ContextQuery.kinds` and `.anchors` were globally `required` in the schema but
`skip_serializing_if = "Vec::is_empty"` in the reference type. An unfiltered,
unanchored query — the most common query there is, and precisely the one this
suite's own `sample_query()` sends — therefore serialized to JSON the schema
rejected. Same class of bug ADR 0006 hit on `ContextFrame`; fixing that one
frame left the class alive.

It survived because schema validation only ever ran over hand-authored
transcripts. Curated examples prove the schema accepts what a human wrote; they
can never catch the schema disagreeing with the serializer.

The structural fix:

- `schema/reference-vectors.ndjson` — every envelope variant serialized from
  the reference Rust types, in both minimal form (empty vecs, None options,
  where omission bugs live) and maximal form.
- `tests/reference_vectors.rs` generates them, fails on drift naming the
  drifted vector (regenerate: REGENERATE_VECTORS=1), asserts each round-trips
  through the wire decoder, and asserts every frame is *conformant* — B3 costs,
  F5 digests, representation invariants — so the file doubles as an attested
  vector set downstreams can diff against (#52).
- `schema/validate-examples.py` validates every vector, so CI's existing schema
  job covers serialized reference output, not only curated fixtures.

Verified red-then-green: restoring the old `required` list fails vector line 4
(query/minimal-unfiltered) while every hand-authored example still passes —
which is exactly why the bug lived this long.
… rename (#58, #21, #56)

#58 — the schema's `$id` was `https://context-graph-protocol.org/...`, a
hyphenated host that was never registered: it returned a DNS failure, so every
third party that tried to resolve the schema's public identity got nothing.
Repointed at the live `contextgraphprotocol.org`, and `workspace.homepage` now
names the site rather than the repo.

An `$id` that 404s is barely better than one that never resolves, so the site
actually serves the file from `site/public/schema/`. A served copy can drift
from the source of truth — worse than a 404, because a stale schema that still
resolves silently validates the wrong thing — so `validate-examples.py` asserts
the copy is byte-identical and that `$id` matches the URL it is served from.
Verified red-then-green by mutating the copy.

#21 — residue the mechanical OCP→Context Graph Protocol rename left behind:
"An Context Graph Protocol host" (twice) and "Context Graph Protocol protocol
version" (three places, the doubled noun the old acronym hid). MIGRATION.md's
`ocp-*` references are deliberately kept: naming the old crates is the entire
job of a migration guide.

#56 — removed docs/future/context-receipt-impact-trace-plan.md. Verified before
deleting: it is a completed agentic implementation plan (embedded
"REQUIRED SUB-SKILL" directive, `- [ ]` task tracking), it names a stack this
repo does not use (Vinext/Vite, Cloudflare Workers vs. the actual
Next.js/Fumadocs site), the feature it plans already shipped
(site/src/app/_components/ContextReceiptDemo.tsx + site/tests/context-receipt.test.mjs),
and nothing links to it. Its sibling *design* doc is referenced from
docs/sketches/host-trace.md and stays.
)

Requiring each `--misbehave` mode to be caught by the check it *declares* it
trips — instead of by any check at all — immediately found a hole, which was
the point of tightening it.

**§H4 correlation was unenforced.** `drop-correlation-id` claimed to trip a
`correlation` check that did not exist. It "went red" only because dropping the
id desynchronizes the host's demultiplexer and breaks every other check
downstream. So an external implementation — each of the three SDKs — could
declare `correlation: true`, never echo an id, and pass the suite. Added a
raw-stdio `correlation` probe that asserts the echo verbatim.

**§Q1 `kinds` was a filter nothing honored.** `sample_query()` sends
`kinds: []`, so every provider was only ever asked the unfiltered question — and
not one of the four reference implementations honored the filter at all. They
declared `capabilities.query.kinds` and returned whatever they had. That is
ADR 0004's dead-capability surface in its subtler form: not an unreachable
field, but a reachable one that silently does nothing.

Specified rather than dropped (SPEC §5 Q1 + §5.1), because `unsupported_kind`
already exists to answer "you asked for kinds I don't serve", which presupposes
the filter binds. A host that narrows to `["snippet"]` and silently receives
`doc` frames has had its budget spent on content it explicitly excluded.
Reference provider now filters; new `kinds-filter` check and `ignore-kinds`
misbehave mode. The fixture's second frame became a `snippet` on purpose — with
every frame one kind the check passed vacuously, which is the decorative-check
sin this round is removing, not adding.

**`granted_at` was never stamped.** Every consent decision entered the audit
ledger with no time on it, because the host had no way to spell the current
instant. Added `format_protocol_timestamp` to contextgraph-types —
dependency-free Gregorian arithmetic, since every implementer in every language
ports this crate and "pull in chrono" is a cost paid by the whole ecosystem.
`ConsentStore::record` stamps on insertion; a caller-supplied instant is
preserved (replaying a persisted decision must not rewrite the trail to "now"),
and a non-F4 instant is refused rather than stored.

**The docs-site witness never ran.** It passed locally and executed nowhere,
making it decoration rather than a gate. Now a step in the `site` job.

Verified: suite 11/11 green; every misbehave mode caught by its declared check;
all three SDK example providers (TS, Python, Go) still 11/11 conformant.
… quotes it (#15)

R3 says a host MUST treat frame `content` as untrusted data, delimited as
quoted material and never executed as instructions. `compose_context` performed
the concatenation that defeats it: `content`, `id`, and the citation label were
interpolated into the `<frame …>` fence with no escaping.

So a provider could serve content containing `</frame>` and every byte after it
landed *outside* the quoted block, read at the host's own level — retrieved text
promoted to instruction, which is the exact failure R3 exists to prevent.
A `"` in a citation label was the same breakout through a field nobody thinks of
as content. SPEC §11.1 already listed this as a known gap; this closes it.

Escaping rather than an unguessable fence, deliberately. A random
per-composition delimiter is the other standard answer and it is wrong *here*:
this module's entire purpose is a byte-stable prompt prefix, so a nonce that
changed per turn would bust the provider prompt cache it exists to protect —
paying a real, measured cost for a guarantee escaping already provides.

Only the delimiter is touched. Escaping `<`/`>` wholesale would mangle the code
and markup frame content most often *is*, degrading every honest frame to harden
against a rare one. Content is neutralized, never dropped: `<\/frame>` stays
legible to whoever reads the prompt.

Tests cover the closing-fence breakout, a forged sibling frame, an attribute
breakout via a quoted citation label, that ordinary markup and generics survive
untouched, and that escaping stays deterministic.
The protocol is named for the graph, and the graph was its least exercised
surface. The reference fixture declared `graph: false` and served every frame
with `relations: vec![]`, so G1 (labelled edges) and G2 (non-empty target URIs)
passed vacuously — there were no edges to validate — and G3's anchor boost was
never witnessed at all.

G3 also never said what an anchor is compared *against*. Two conformant
providers could reasonably match anchors against the frame `uri`, against
`relations[].target_uri`, or against neither, and no test could tell graph
traversal apart from ignoring `anchors` entirely.

- **SPEC §G4 + §8.2**: a frame is *anchored* when its own `uri` equals an anchor
  (zero hops) or any `relations[].target_uri` does (one hop). A graph-declaring
  provider given anchors MUST return an anchored frame when it has one, and
  SHOULD rank anchored first. A floor on what must be found, not a ceiling on
  how hard a provider may look — deeper traversal stays provider-private.
- **Fixture**: now genuinely graph-capable — real `doc.documents` edges, and it
  honors anchors by stable-partitioning anchored frames first (stable, so
  composition stays deterministic).
- **`anchor-relevance` check**: asks an unanchored question first to discover a
  URI the provider actually serves, then re-asks anchored on it. Deriving the
  anchor from the provider's own output is what keeps it fair — the suite never
  invents a URI and demands the provider know it. It prefers a *relation
  target*, so passing requires traversing an edge rather than string-matching a
  frame's own uri. G3's ranking half is a SHOULD, so it is reported, not
  enforced.
- **`ignore-anchors`** misbehave mode, caught by the matching check.
- **R3 host check** now asserts breakout-resistance rather than carrying a note
  that compose_context lacked it — that note went stale when #15 landed.

Suite is 12/12 green; every misbehave mode caught by its declared check. The
three SDK example providers declare `graph: false`, so the check skips for them
and they remain conformant.
The Context Frame spec's sixth question — "why was each item included, and can
its effect be evaluated later?" — had provenance for the first half and nothing
for the second. A host could say a frame cost 42 tokens and came from
retry-policy.md, and nothing about whether including it helped. Not theoretical:
a host in this ecosystem already A/B-suppresses recall to measure whether
retrieval earns its budget, entirely outside the protocol, because the protocol
gave it no vocabulary to say so.

**The id is not new (A1).** `FrameId` is already what composition, dedup, usage
reports, and `verify` key on. A second attribution id would be free to disagree
with the first, and a disagreement between "the frame that was billed" and "the
frame that was cited" is the confusion attribution exists to remove.

**Three booleans, not a score (A2).** `selected` / `rendered` / `cited` — ADR
0007's `context_use` vocabulary — because they are separately observable and
collapse badly. The case that matters is `rendered` but not `cited`: tokens
paid, content read, nothing changed. A used/unused flag cannot express it and a
0–1 usefulness score would invent precision nobody measured. `cited` means the
output referred to the frame — observable — never that the frame *influenced*
it, which is not.

**Reconcilable (A3).** Records must be coherent and name a frame the paired
usage report actually billed; attribution that cannot be walked back to the bill
is not auditable, and is the shape a mis-keyed identity takes.

`AttributionReport` pairs the outcome records with `UsageReport`, so cost and
outcome finally sit together — value-per-token, which #31 and #8 each supply
half of.

**Deliberately not on the wire.** No `context/feedback`, no
`Capabilities.feedback`. Shipping a negotiated method with no provider consuming
it and no check able to witness it would recreate exactly the dead capability
surface ADR 0004 removed and §Q1 had to be written to repair — days before a
freeze, when the asymmetry runs the other way: adding the method later is
family-safe, removing a dead one is not. The vocabulary ships now because it
must be shared for scores to compare; the transport is sketched in
docs/sketches/attribution-feedback.md with its open questions (privacy of
`cited` for an egress provider, batching, what a conformance check could even
assert).
#53, #55)

Follow-ups to #44 / ADR 0006. All of this is *provider policy* — the wire, the
frame shapes, and the conformance surface are untouched; only what the
prompt-ingest provider chooses to inline changes.

**#53 — segmentation and distillation**

- Stack traces are a first-class segment kind with their own distiller, instead
  of folding into the generic log heuristic. Keeping the exception line plus the
  top N frames and eliding the tail beats the log distiller for the single most
  common paste there is.
- Tabular ingestion handles CSV and whitespace-aligned columns, not just pipe
  and tab, and `infer_column_type` recognizes currency, percent, and nullable
  columns — so a distilled table says what its columns *are*.
- Common log timestamps (`2026-07-20 18:00:01,250`, syslog, bracketed) are
  parsed and normalized to the F4 profile, so the temporal window actually
  populates. Guarded: a shape that cannot be normalized yields no timestamp
  rather than a non-F4 string.
- Runs of identical log lines collapse before salient-line selection, so a
  retry storm cannot crowd out the rest of the log.
- Classification-precedence edge cases documented as known limits.

**#55 — docs and a runnable example**

- `docs/prompt-ingestion.md`: paste → intent + anchors + content-addressed
  evidence, compact-by-default with `[full]` on demand, and how it composes
  byte-stably.
- `contextgraph-host/examples/ingest_paste.rs`: a real end-to-end run. On a
  2288-token paste it segments five attachments, serves four frames for 610
  tokens (27% of raw), re-composes byte-identically, and pulls `[full]` on the
  top-ranked frame with a matching content digest.

Verified: fmt, clippy -D warnings, and all 22 test suites green; conformance
12/12; every misbehave mode caught; host-conformance, schema, and the docs-site
witness green. The example was run, not just compiled.

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry @macanderson, your pull request is larger than the review limit of 150000 diff characters

…e ingest page

Two follow-ups.

`cargo clippy --workspace --all-targets -- -D warnings` was failing on
`manual_contains` in the §G4 anchor predicate I added in a9a5348. My earlier
"clippy clean" reading was a **cached** result — the crate had not been re-linted
after the edit, so clippy emitted no diagnostics and I misread silence as
success. Re-verified here from a clean build: exit 0.

Also links docs/prompt-ingestion.md from the docs index. It is deliberately not
added to site/content/docs/meta.json: the site mirrors a hand-maintained subset
of docs/ (context-reuse.md is likewise absent), and listing a page with no mdx
copy would fail the docs-site build, which is now a hard gate.
Adding the `anchor-relevance` check broke all three SDK CI jobs, and the way it
broke them is worth recording.

`conformance-external.sh` asserts "every check green, **none skipped**" — a
deliberately stricter bar than SPEC §12's "green for your declared capability
set", because it is how the GOVERNANCE freeze criterion "≥2 independent
implementations pass conformance" is machine-verified. A skip there means the
external implementation did not exercise that surface at all.

The new check correctly *skips* for a `graph: false` provider, and all three
examples declared `graph: false` with `relations: []` — exactly the vacuum the
Rust fixture had. So the honest fix is to make them graph-capable rather than to
relax the gate: weakening "none skipped" would let an SDK silently stop
declaring a capability and still pass.

Each example now declares `graph: true`, carries a labelled `doc.documents`
edge, and ranks anchored frames first (§G4) — Go via `sort.SliceStable`, so
equally-anchored frames keep their relative order and composition stays
deterministic.

All three verified 12/12 with no skips: `go vet` clean, `tsc` builds, and each
runs green under conformance-external.sh.
@macanderson

macanderson commented Jul 26, 2026

Copy link
Copy Markdown
Owner Author

Follow-up after opening: two real regressions caught by re-running the gates

Both were mine, and both were hidden by a false-clean reading. Recording them because the second is a trap anyone touching the conformance surface will hit.

1. clippy --workspace was failing, and I reported it clean. My check ran cargo clippy … | rg -c '^error', but cargo had cached the previous lint result and emitted no diagnostics — I read silence as success. There was a genuine manual_contains in the §G4 anchor predicate I had just written. Fixed in bb326be; re-verified from a clean build by exit code, not by grepping output.

2. The new anchor-relevance check would have turned all three SDK CI jobs red. conformance-external.sh asserts "every check green, none skipped" — deliberately stricter than SPEC §12's "green for your declared capability set", because it is how the GOVERNANCE freeze criterion "≥2 independent implementations pass conformance" is machine-verified. The new check correctly skips for a graph: false provider, and all three examples declared graph: false with relations: .

I made the SDKs graph-capable rather than relaxing the gate (f61d293). Weakening "none skipped" would let an SDK silently stop declaring a capability and keep passing — and the examples having no edges at all was the same vacuum that made G1/G2 pass vacuously in the Rust fixture, which is what #7 is about. Each example now declares graph: true, carries a doc.documents edge, and ranks anchored frames first (Go via sort.SliceStable, so composition stays deterministic).

Final state — every gate by exit code, from a clean build

fmt: 0          conformance-green: 0     schema: 0
clippy: 0       conformance-red:   0     docs-witness: 0
test: 0         host-conformance:  0

Plus all three SDK example providers at 12/12 with no skips (conformance-external.sh exit 0 for Python, TypeScript, and Go), go vet clean, and the ingest example run rather than merely compiled.

@macanderson
macanderson marked this pull request as ready for review July 26, 2026 18:53
@macanderson
macanderson merged commit 01990f6 into main Jul 26, 2026
10 checks passed
@macanderson
macanderson deleted the pre-freeze/enforcement-and-integrity branch July 26, 2026 18:53

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry @macanderson, your pull request is larger than the review limit of 150000 diff characters

macanderson added a commit that referenced this pull request Jul 27, 2026
)

#64's merge with main kept its own `schema/validate-examples.py` wholesale,
silently discarding both checks #63 had added to that file:

  * the reference-SERIALIZED vector validation (issue #54) — the half that
    catches schema/serializer disagreement, which curated examples cannot;
  * the `$id` + served-copy check, asserting `$id` names the live domain and
    `site/public/schema/` stays byte-identical to the source.

Neither loss was visible in CI. Both are additive validations, so deleting
them removes coverage without failing anything: #64 was green with them
gone, and main is green now.

The second loss is not theoretical — main is currently serving a stale
schema. #64 rewrote the ContextQuery `$comment`, so the copy under
`site/public/schema/` no longer matches the source. That is the exact
"stale schema that still resolves is worse than a 404" case #63's comment
describes, and the check that would have caught it went out in the same
commit that caused it.

Resolves the drift by taking #63's `$comment` wording over #64's: the two
fixed the same bug independently, and #63's is the better text (it names
the conformance suite's own sample_query() and records that absence and []
mean the same thing). `schema/contextgraph-envelope.schema.json` is
byte-identical to the served copy again.

CHANGELOG: reworded #64's ContextQuery bullet, which claimed a schema fix
that had already landed in #63, to claim only the regression test and the
cross-audit it actually contributed.

Section numbering reconciled: 1-2 examples/, 3 reference vectors, 4 SPEC.md,
5 schema identity.

Both restored checks are mutation-tested rather than assumed: the $id check
fails on main's current drift, and the vector check fails when ContextQuery's
`required` is reverted to the pre-#63 list.

validate-examples.py: 39 checks (37 on main — the two dropped ones back).
cargo test --workspace: 292 passed, 0 failed. fmt and clippy -D warnings clean.
macanderson added a commit that referenced this pull request Jul 29, 2026
The graph itself is already real and witnessed — §8 specifies graph frames, the
open `rel` vocabulary, and the G1/G2/G3/G4 checks (G4's anchored predicate and
its `anchor-relevance` check landed in #63/#64). The one remaining #7 acceptance
box was the design sketch for multi-hop traversal.

Adds docs/sketches/context-neighbors.md (a `context/neighbors { uri, rels, depth }`
envelope pair as a post-1.0 additive minor, defined so `depth: 1` ≡ the G4
anchored set) following the docs/sketches/resolve.md template, and a §8.3
forward-reference in SPEC.md mirroring the §6.4.1 deferral pattern. No wire
change — traversal beyond one hop is explicitly out of scope for the 1.0 freeze.

Closes #7

Claude-Session: https://claude.ai/code/session_01Co9faUWdYC1SPqrof7njyD
macanderson added a commit that referenced this pull request Jul 29, 2026
* feat: backlog sweep wave 1 — registry, release/SDK prep, canary, schema $id

Five file-disjoint backlog issues, all additive (no wire/Rust-logic change):

- #20 Conformance registry page + reproducible-report seed + badge + PR
  submission checklist. Seed report is a verified 12/12 capture of
  `contextgraph-inspect stdio --json` against the bundled example provider.
- #16 Tag-triggered, environment-gated crates.io release.yml + a credential-free
  `publish-dry-run` CI job + crates.io/docs.rs badges. Version cut and the
  crates-io environment/secret remain the owner's decision.
- #59 sdk/PUBLISHING.md + tag-gated publish-sdks.yml; PyPI/Go publishes and the
  Go tag remain human-only. npm already live via #46.
- #29 downstream-canary.yml builds stella's contextgraph-* consumers against
  HEAD (advisory); oxagen-canary activates once OXAGEN_PLATFORM_TOKEN is wired.
- #58 schema $id repointed to the GitHub-raw URL that resolves today (interim
  until #57's Vercel relink); schema validate-examples.py green, mirror
  byte-identical.

Closes #20, #29, #58
Refs #16, #59 (publish/tag/secret steps are human-only)

Claude-Session: https://claude.ai/code/session_01Co9faUWdYC1SPqrof7njyD

* docs(spec): add normative Usage reports section, fix tokenizer_ref comment (#49)

Closes the two remaining #49 "survivors":

- SPEC.md gains a normative §7.3 "Usage reports" (UR1): a host MUST be able to
  produce a usage report whose budget_consumed equals the summed token_cost of
  served frames, referencing them by FrameId — backed by the existing, tested
  contextgraph-host::FanOut::usage_report. Resolves the "U1" anchor collision
  with §13's ignore-unknown-members rule by labelling this UR1 across SPEC.md,
  docs/context-reuse.md, and docs/protocol-surface.md, and repointing §14's A1
  cross-reference at §7.3.
- Reword the schema canonical_token_cost $comment so tokenizer_ref pairs only
  with canonical_token_cost (the exact-count companion), never the byte-formula
  token_cost (§B3/§7.2) — resolving #50's tokenizer residual. Source and site
  schema copies stay byte-identical.

schema/validate-examples.py green.

Closes #49
Refs #50

Claude-Session: https://claude.ai/code/session_01Co9faUWdYC1SPqrof7njyD

* docs(spec): sketch the deferred context/neighbors 1.x operation (#7)

The graph itself is already real and witnessed — §8 specifies graph frames, the
open `rel` vocabulary, and the G1/G2/G3/G4 checks (G4's anchored predicate and
its `anchor-relevance` check landed in #63/#64). The one remaining #7 acceptance
box was the design sketch for multi-hop traversal.

Adds docs/sketches/context-neighbors.md (a `context/neighbors { uri, rels, depth }`
envelope pair as a post-1.0 additive minor, defined so `depth: 1` ≡ the G4
anchored set) following the docs/sketches/resolve.md template, and a §8.3
forward-reference in SPEC.md mirroring the §6.4.1 deferral pattern. No wire
change — traversal beyond one hop is explicitly out of scope for the 1.0 freeze.

Closes #7

Claude-Session: https://claude.ai/code/session_01Co9faUWdYC1SPqrof7njyD

* feat(host): carry structured error codes across the transport boundary (#9)

The wire already carried `code: Option<ErrorCode>`; nothing read it. This plumbs
it end to end and tightens the conformance floor:

- ErrorCode gains `unsupported_representation` (§P5) and `incompatible_version`
  (§H3), wired through as_str/From<&str>/reaction(). incompatible_version is
  permanent — a new HostReaction::DropProvider (the request is fine, the provider
  is unusable; distinct from DoNotRetry/Respawn/ReportAndCount).
- HostError::Provider now carries `code`; the four http.rs/stdio.rs error arms
  pass it through instead of discarding it, so FanOut::failures() surfaces it.
- The malformed-input-tolerance conformance check now passes only on a
  `bad_request` code (was: any Envelope::Error), per SPEC.md R1. A new
  `--misbehave mislabel-malformed` mode (answers `internal`) exercises the
  tightened check in conformance-red.sh, with a matching suite test.

Gate green: fmt, clippy -D warnings, test --workspace, conformance-green (12/12),
conformance-red (all misbehave modes caught).

Closes #9

Claude-Session: https://claude.ai/code/session_01Co9faUWdYC1SPqrof7njyD

* feat(host): enforce C7/C8 in the reference HTTP transport (#13)

C7/C8 were specified (§4.2) but listed as a live enforcement gap (§11.1). This
implements them in the reference host:

- C7 (TLS for non-loopback): HttpProvider refuses a plaintext http:// target to
  any non-loopback host with HostError::InsecureTransport, BEFORE the client is
  built or DNS resolves. Loopback (localhost / 127.0.0.0/8 / [::1]) stays exempt
  so the wiremock suite keeps working.
- C8 (credentials never logged): a new Credential type whose Debug AND Display
  both render only "Credential(<redacted>)" (secret reachable only via a
  crate-private expose()); attached via reqwest bearer_auth, never a format
  string. A redaction test asserts no HostError/format string leaks the secret.
- connect_with_auth / Host::add_http take an optional Credential (connect stays
  as a back-compat None wrapper); a 401 surfaces as HostError::Unauthorized.
- SPEC.md §11.1 updated: C7/C8 now enforced + unit-tested at the
  transport-refusal/redaction level; full live-TLS-peer conformance remains the
  stated next increment (unchanged).

Gate green: fmt, clippy -D warnings, test (119 host + 4 new), conformance
green/red, schema validate. wiremock was already a dev-dep.

Closes #13

Claude-Session: https://claude.ai/code/session_01Co9faUWdYC1SPqrof7njyD
macanderson added a commit that referenced this pull request Jul 30, 2026
#15)

Layered on compose_context's byte-stability floor (the injection-escaping half
shipped in #63); this delivers the rest of #15:

- Host::query_all_budgeted splits a global token budget into per-provider shares
  before fan-out, so N honest legs sum to <= the whole budget instead of N x it.
- compose::dedup_cross_provider collapses the same evidence from two providers
  (content_digest match, then uri+range provenance overlap), keeping the
  higher-scored frame and merging provenance.
- order_by_value places the highest-value frames at the top/bottom edges
  (Lost in the Middle, Liu et al. 2024), byte-stable for a fixed set.
- compose_for_prompt returns an injection-resistant fenced prompt with an
  "evidence, not instructions" preamble, a citation map (label -> frame id +
  provenance), and a CompositionAudit that explains every included/excluded frame.
- New host-conformance check host-composition-audit (host suite now 9),
  red-then-green mutation-tested; a property test bounds composed tokens <=
  budget; an injection-corpus test proves no instruction-shaped payload escapes
  the fence. SPEC.md R3 now cites the host checks + the new reference doc.

Gate green: fmt, clippy -D warnings, test --workspace (+property +injection),
conformance green/red, host-conformance (9/9), schema validate.

Closes #15

Claude-Session: https://claude.ai/code/session_01Co9faUWdYC1SPqrof7njyD
macanderson added a commit that referenced this pull request Jul 30, 2026
* feat: backlog sweep wave 1 — registry, release/SDK prep, canary, schema $id

Five file-disjoint backlog issues, all additive (no wire/Rust-logic change):

- #20 Conformance registry page + reproducible-report seed + badge + PR
  submission checklist. Seed report is a verified 12/12 capture of
  `contextgraph-inspect stdio --json` against the bundled example provider.
- #16 Tag-triggered, environment-gated crates.io release.yml + a credential-free
  `publish-dry-run` CI job + crates.io/docs.rs badges. Version cut and the
  crates-io environment/secret remain the owner's decision.
- #59 sdk/PUBLISHING.md + tag-gated publish-sdks.yml; PyPI/Go publishes and the
  Go tag remain human-only. npm already live via #46.
- #29 downstream-canary.yml builds stella's contextgraph-* consumers against
  HEAD (advisory); oxagen-canary activates once OXAGEN_PLATFORM_TOKEN is wired.
- #58 schema $id repointed to the GitHub-raw URL that resolves today (interim
  until #57's Vercel relink); schema validate-examples.py green, mirror
  byte-identical.

Closes #20, #29, #58
Refs #16, #59 (publish/tag/secret steps are human-only)

Claude-Session: https://claude.ai/code/session_01Co9faUWdYC1SPqrof7njyD

* docs(spec): add normative Usage reports section, fix tokenizer_ref comment (#49)

Closes the two remaining #49 "survivors":

- SPEC.md gains a normative §7.3 "Usage reports" (UR1): a host MUST be able to
  produce a usage report whose budget_consumed equals the summed token_cost of
  served frames, referencing them by FrameId — backed by the existing, tested
  contextgraph-host::FanOut::usage_report. Resolves the "U1" anchor collision
  with §13's ignore-unknown-members rule by labelling this UR1 across SPEC.md,
  docs/context-reuse.md, and docs/protocol-surface.md, and repointing §14's A1
  cross-reference at §7.3.
- Reword the schema canonical_token_cost $comment so tokenizer_ref pairs only
  with canonical_token_cost (the exact-count companion), never the byte-formula
  token_cost (§B3/§7.2) — resolving #50's tokenizer residual. Source and site
  schema copies stay byte-identical.

schema/validate-examples.py green.

Closes #49
Refs #50

Claude-Session: https://claude.ai/code/session_01Co9faUWdYC1SPqrof7njyD

* docs(spec): sketch the deferred context/neighbors 1.x operation (#7)

The graph itself is already real and witnessed — §8 specifies graph frames, the
open `rel` vocabulary, and the G1/G2/G3/G4 checks (G4's anchored predicate and
its `anchor-relevance` check landed in #63/#64). The one remaining #7 acceptance
box was the design sketch for multi-hop traversal.

Adds docs/sketches/context-neighbors.md (a `context/neighbors { uri, rels, depth }`
envelope pair as a post-1.0 additive minor, defined so `depth: 1` ≡ the G4
anchored set) following the docs/sketches/resolve.md template, and a §8.3
forward-reference in SPEC.md mirroring the §6.4.1 deferral pattern. No wire
change — traversal beyond one hop is explicitly out of scope for the 1.0 freeze.

Closes #7

Claude-Session: https://claude.ai/code/session_01Co9faUWdYC1SPqrof7njyD

* feat(host): carry structured error codes across the transport boundary (#9)

The wire already carried `code: Option<ErrorCode>`; nothing read it. This plumbs
it end to end and tightens the conformance floor:

- ErrorCode gains `unsupported_representation` (§P5) and `incompatible_version`
  (§H3), wired through as_str/From<&str>/reaction(). incompatible_version is
  permanent — a new HostReaction::DropProvider (the request is fine, the provider
  is unusable; distinct from DoNotRetry/Respawn/ReportAndCount).
- HostError::Provider now carries `code`; the four http.rs/stdio.rs error arms
  pass it through instead of discarding it, so FanOut::failures() surfaces it.
- The malformed-input-tolerance conformance check now passes only on a
  `bad_request` code (was: any Envelope::Error), per SPEC.md R1. A new
  `--misbehave mislabel-malformed` mode (answers `internal`) exercises the
  tightened check in conformance-red.sh, with a matching suite test.

Gate green: fmt, clippy -D warnings, test --workspace, conformance-green (12/12),
conformance-red (all misbehave modes caught).

Closes #9

Claude-Session: https://claude.ai/code/session_01Co9faUWdYC1SPqrof7njyD

* feat(host): enforce C7/C8 in the reference HTTP transport (#13)

C7/C8 were specified (§4.2) but listed as a live enforcement gap (§11.1). This
implements them in the reference host:

- C7 (TLS for non-loopback): HttpProvider refuses a plaintext http:// target to
  any non-loopback host with HostError::InsecureTransport, BEFORE the client is
  built or DNS resolves. Loopback (localhost / 127.0.0.0/8 / [::1]) stays exempt
  so the wiremock suite keeps working.
- C8 (credentials never logged): a new Credential type whose Debug AND Display
  both render only "Credential(<redacted>)" (secret reachable only via a
  crate-private expose()); attached via reqwest bearer_auth, never a format
  string. A redaction test asserts no HostError/format string leaks the secret.
- connect_with_auth / Host::add_http take an optional Credential (connect stays
  as a back-compat None wrapper); a 401 surfaces as HostError::Unauthorized.
- SPEC.md §11.1 updated: C7/C8 now enforced + unit-tested at the
  transport-refusal/redaction level; full live-TLS-peer conformance remains the
  stated next increment (unchanged).

Gate green: fmt, clippy -D warnings, test (119 host + 4 new), conformance
green/red, schema validate. wiremock was already a dev-dep.

Closes #13

Claude-Session: https://claude.ai/code/session_01Co9faUWdYC1SPqrof7njyD

* feat(conformance): host-side H3 version-rejection + crash-isolation scenarios (#14)

The host-conformance harness gained the two adversarial transport scenarios it
was missing (the primitives already existed in contextgraph-host; this wires
them in as witnessed checks). run_host_conformance now exposes 8 checks:

- host-version-reject (§3 H3, host-side): drives the reference host's handshake
  at a fixture declaring contextgraph/2.0 (mismatched major family), under an
  explicit tokio timeout so "never a hang" is a load-bearing assertion, and
  asserts HostError::VersionMismatch. Distinct from §3's provider-facing
  handshake check (both now named in the H3 "Verified by" cell).
- host-crash-isolation (§11): a query_all fan-out where one provider dies
  mid-query (ProviderCrashed via the BrokenPipe/EOF path) while a healthy peer
  is queried concurrently; asserts the fan-out still completes with the healthy
  frames and the crash is reported + excluded, never poisoning the query.

Each keeps the adversarial+well-behaved-counterpart discrimination pattern, and
both were red-then-green mutation-tested (invert the fixture → check fails).
SPEC.md §11.1 updated to name both host-side scenarios (added to #13's C7/C8
text, not reverting it).

Gate green: fmt, clippy -D warnings, test, host-conformance (8/8),
conformance green/red, schema validate.

Closes #14

Claude-Session: https://claude.ai/code/session_01Co9faUWdYC1SPqrof7njyD

* feat(sdk): HTTP adapters + create-contextgraph-provider scaffold + quick-starts (#17)

Fills the provider-SDK residue (skip Java; publishing is #59):

- HTTP adapter per SDK, mirroring the stdio provider loop as a single-endpoint
  POST handler: createHttpHandler (TypeScript), make_wsgi_app (Python),
  Handler (Go). Each ships a runnable example-docs-http provider that goes green
  under `contextgraph-inspect http` (9 passed / 3 skipped — the 3 skips are the
  harness's stdio-only wire probes, unavoidable over HTTP).
- create-contextgraph-provider: a zero-dep Node CLI with TypeScript + Python
  templates that scaffold a provider wired to both transports PLUS a bundled
  GitHub Actions workflow running contextgraph-inspect against the generated
  provider in its OWN CI from the first commit (the literal acceptance criterion).
- Quick-starts: TS + Python quick-starts, an HTTP-transport section, and a
  scaffold section appended to docs/implementing-a-provider.md and the docs-site
  mirror; HTTP APIs documented in each SDK README.

Validated via the pre-built contextgraph-inspect: TS/Python/Go HTTP all green,
existing stdio conformance still 12/12, both scaffolded templates conformant.
The CI jobs (sdk-*-http, sdk-scaffold) are applied to ci.yml separately.

Closes #17

Claude-Session: https://claude.ai/code/session_01Co9faUWdYC1SPqrof7njyD

* ci+docs: wire sdk-http/scaffold CI jobs, record the host+sdk wave in CHANGELOG

- ci.yml: add sdk-typescript-http, sdk-python-http, sdk-go-http (start each
  example server, run `contextgraph-inspect http` against it) and sdk-scaffold
  (generate a provider from create-contextgraph-provider and assert its own
  conformance check passes) for #17. actionlint clean.
- CHANGELOG [Unreleased]: record #9, #13, #14, #17.

Refs #9, #13, #14, #17

Claude-Session: https://claude.ai/code/session_01Co9faUWdYC1SPqrof7njyD

* feat(conformance): stale-digest misbehave mode + fixture self-consistency check (#12)

The reference fixture now verifies its own digests end to end, closing the
"stdio fixture" survivor of #12 (the digest grammar + host verify API were
already done):

- The example-docs fixture gains real on-disk backing files
  (fixtures/example-docs/{getting-started,configuration}.md); fixture_digest now
  computes a genuine sha256 over those bytes at runtime and frames carry file://
  provenance, so verify_file_provenance can re-read and re-hash them.
- New provider check `provenance-fixture-consistency`: re-reads each frame's file
  provenance and re-hashes it against the bytes on disk (Verified→pass,
  Mismatch→fail, Unreadable→host-local skip). The suite is now 13 checks.
- New `--misbehave stale-digest` mode emits a WELL-FORMED sha256 (one hex digit
  flipped) that passes F5 grammar and verify-honesty but does not match the real
  bytes — provenance forgery only the new check catches. conformance-red.sh
  auto-discovers it (no script edit).
- sha2 moved from a conformance dev-dep to the workspace 0.10 normal dep (matches
  the host verifier); verify_wire.rs now computes real digests from the files.

Gate green: fmt, clippy -D warnings, test, conformance-green (13/13),
conformance-red (all modes incl. stale-digest), schema validate.

Closes #12

Claude-Session: https://claude.ai/code/session_01Co9faUWdYC1SPqrof7njyD

* docs(registry): regenerate seed report at 13 checks after #12

#12 added the provenance-fixture-consistency check (suite 12→13). Regenerate the
bundled contextgraph-example-docs conformance report from
`contextgraph-inspect stdio --json` and update the registry table to 13/13 so the
listed attestation stays a faithful capture, not a stale claim.

Refs #20, #12

Claude-Session: https://claude.ai/code/session_01Co9faUWdYC1SPqrof7njyD

* feat(host): pipeline the stdio transport — demux on id, shrink the mutex (#4)

Delivers the demux/pipelining half of ADR 0002 (the correlation-id decision half
already shipped). StdioProvider previously held one mutex across the whole query
round-trip, so concurrent queries serialized even when the provider negotiated
capabilities.correlation.

- The connection is split after handshake into a write-half (stdin mutex), a
  dedicated reader task, and a control handle (StdioControl) that reproduces the
  SHUTDOWN_GRACE + kill_group semantics exactly. RawStdioConnection::into_parts
  moves the fields out without running Drop (ManuallyDrop + one ptr::read per
  field — sound: each read once, destructor suppressed).
- A `pending: HashMap<id, oneshot::Sender>` demuxes replies. query() (correlated)
  registers its oneshot before sending, holds the stdin mutex only for the write,
  then awaits its reply with no lock held — so two queries interleave. Reader
  drains every waiter on EOF/decode/transport error, so a crash fails in-flight
  queries instead of hanging them.
- Non-correlating providers and verify() keep the strict lock-step path
  (exchange_lockstep), provably unchanged. RawStdioConnection's public raw
  send/recv API is byte-for-byte unchanged, so the conformance crate's wire
  probes compile and pass untouched.
- Witness test (ADR 0002): a fixture that reads both queries before answering
  either, then replies to the second FIRST — deadlocks a lock-step transport,
  demuxes correctly here. Ran 15x, no flakes.

Gate green: fmt, clippy -D warnings, test --workspace (+witness), conformance
green (13/13)/red/host, schema validate.

Closes #4

Claude-Session: https://claude.ai/code/session_01Co9faUWdYC1SPqrof7njyD

* docs(changelog): record #4 (stdio pipelining) and #12 (stale-digest)

Refs #4, #12

Claude-Session: https://claude.ai/code/session_01Co9faUWdYC1SPqrof7njyD

* feat(host): reference prompt-composition module — budget, dedup, audit (#15)

Layered on compose_context's byte-stability floor (the injection-escaping half
shipped in #63); this delivers the rest of #15:

- Host::query_all_budgeted splits a global token budget into per-provider shares
  before fan-out, so N honest legs sum to <= the whole budget instead of N x it.
- compose::dedup_cross_provider collapses the same evidence from two providers
  (content_digest match, then uri+range provenance overlap), keeping the
  higher-scored frame and merging provenance.
- order_by_value places the highest-value frames at the top/bottom edges
  (Lost in the Middle, Liu et al. 2024), byte-stable for a fixed set.
- compose_for_prompt returns an injection-resistant fenced prompt with an
  "evidence, not instructions" preamble, a citation map (label -> frame id +
  provenance), and a CompositionAudit that explains every included/excluded frame.
- New host-conformance check host-composition-audit (host suite now 9),
  red-then-green mutation-tested; a property test bounds composed tokens <=
  budget; an injection-corpus test proves no instruction-shaped payload escapes
  the fence. SPEC.md R3 now cites the host checks + the new reference doc.

Gate green: fmt, clippy -D warnings, test --workspace (+property +injection),
conformance green/red, host-conformance (9/9), schema validate.

Closes #15

Claude-Session: https://claude.ai/code/session_01Co9faUWdYC1SPqrof7njyD

* feat: reference provider crates + MCP interop bridges (#18, #19)

Five new publish=false workspace crates, each conformance-green:

#18 — reference providers:
- contextgraph-ripgrep: Snippet frames from a ripgrep/built-in content search
  with real, re-verifiable file provenance.
- contextgraph-treesitter: Symbol + Graph frames (code.defines/calls/imports),
  via a self-contained pure-Rust symbol extractor (no tree-sitter C toolchain).
- contextgraph-refprov: the shared stdio-provider kit both binaries reuse.
Both providers pass all 13 provider checks under conformance-external.sh; git
history episodes deferred as the sanctioned stretch. See docs/reference-providers.md.

#19 — MCP interop, a bridge in each direction:
- contextgraph-mcp-bridge: wraps any MCP resource server as a budgeted, cited,
  consent-gated CGP provider (MCP resources -> Doc/Snippet frames with
  mcp-resource provenance; local file:// resources get a byte-verifiable digest).
  Goes fully conformance-green against a hermetic in-repo MCP fixture — no
  network, no npx.
- contextgraph-mcp-server: exposes a CGP host's fan-out as an MCP
  query_context(goal, budget, kinds) tool returning frames, provenance,
  citations, and a budget audit as structured content. See docs/composition-walkthrough.md.

CI gains reference-provider-ripgrep, reference-provider-treesitter, and mcp-bridge
jobs. No new external dependencies. Full workspace gate green: fmt, clippy
-D warnings, test, conformance green (13/13)/red/host, the three external-provider
suites, and schema validate.

Closes #18
Closes #19

Claude-Session: https://claude.ai/code/session_01Co9faUWdYC1SPqrof7njyD

---------

Signed-off-by: Mac Anderson <mac@oxagen.sh>
macanderson added a commit that referenced this pull request Jul 30, 2026
The graph itself is already real and witnessed — §8 specifies graph frames, the
open `rel` vocabulary, and the G1/G2/G3/G4 checks (G4's anchored predicate and
its `anchor-relevance` check landed in #63/#64). The one remaining #7 acceptance
box was the design sketch for multi-hop traversal.

Adds docs/sketches/context-neighbors.md (a `context/neighbors { uri, rels, depth }`
envelope pair as a post-1.0 additive minor, defined so `depth: 1` ≡ the G4
anchored set) following the docs/sketches/resolve.md template, and a §8.3
forward-reference in SPEC.md mirroring the §6.4.1 deferral pattern. No wire
change — traversal beyond one hop is explicitly out of scope for the 1.0 freeze.

Closes #7
macanderson added a commit that referenced this pull request Jul 30, 2026
#15)

Layered on compose_context's byte-stability floor (the injection-escaping half
shipped in #63); this delivers the rest of #15:

- Host::query_all_budgeted splits a global token budget into per-provider shares
  before fan-out, so N honest legs sum to <= the whole budget instead of N x it.
- compose::dedup_cross_provider collapses the same evidence from two providers
  (content_digest match, then uri+range provenance overlap), keeping the
  higher-scored frame and merging provenance.
- order_by_value places the highest-value frames at the top/bottom edges
  (Lost in the Middle, Liu et al. 2024), byte-stable for a fixed set.
- compose_for_prompt returns an injection-resistant fenced prompt with an
  "evidence, not instructions" preamble, a citation map (label -> frame id +
  provenance), and a CompositionAudit that explains every included/excluded frame.
- New host-conformance check host-composition-audit (host suite now 9),
  red-then-green mutation-tested; a property test bounds composed tokens <=
  budget; an injection-corpus test proves no instruction-shaped payload escapes
  the fence. SPEC.md R3 now cites the host checks + the new reference doc.

Gate green: fmt, clippy -D warnings, test --workspace (+property +injection),
conformance green/red, host-conformance (9/9), schema validate.

Closes #15
macanderson added a commit that referenced this pull request Jul 30, 2026
Resolving the conflict with #63 kept this branch's version of
`schema/validate-examples.py` wholesale, silently discarding both checks
#63 had added to it on main:

  * the reference-SERIALIZED vector validation (issue #54) — the half that
    catches schema/serializer disagreement, which curated examples cannot;
  * the `$id` + served-copy check, asserting `$id` names the live domain
    and `site/public/schema/` is byte-identical to the source.

Neither loss was visible in CI: both are additive validations, so deleting
them removes coverage without failing anything. The PR was green with the
checks gone.

The second loss was not merely theoretical. This branch had edited the
ContextQuery `$comment`, so the served copy under `site/public/schema/`
had already drifted from the source — the exact "stale schema that still
resolves" failure #63's comment warns about. The merge introduced the
drift and deleted the detector in one move. Restoring the check fails
immediately on it, which is how the drift was found.

Also drops this branch's ContextQuery `required` edit in favour of main's.
#63 fixed that bug independently and identically; its `$comment` is the
better of the two (it names the conformance suite's own sample_query and
records that absence and  mean the same thing). `schema/` is now
byte-identical to main, and the CHANGELOG entry is reworded to claim only
what this branch still contributes: the regression test and the
cross-audit, not the schema change.

Section numbering reconciled: 1-2 examples/, 3 reference vectors,
4 SPEC.md, 5 schema identity.

validate-examples.py: 39 checks pass (24 before the merge, 0 dropped).
cargo test --workspace: 292 passed, 0 failed. fmt, clippy -D warnings, and
conformance-{green,red}/host all pass.
macanderson added a commit that referenced this pull request Aug 1, 2026
* feat: backlog sweep wave 1 — registry, release/SDK prep, canary, schema $id

Five file-disjoint backlog issues, all additive (no wire/Rust-logic change):

- #20 Conformance registry page + reproducible-report seed + badge + PR
  submission checklist. Seed report is a verified 12/12 capture of
  `contextgraph-inspect stdio --json` against the bundled example provider.
- #16 Tag-triggered, environment-gated crates.io release.yml + a credential-free
  `publish-dry-run` CI job + crates.io/docs.rs badges. Version cut and the
  crates-io environment/secret remain the owner's decision.
- #59 sdk/PUBLISHING.md + tag-gated publish-sdks.yml; PyPI/Go publishes and the
  Go tag remain human-only. npm already live via #46.
- #29 downstream-canary.yml builds stella's contextgraph-* consumers against
  HEAD (advisory); oxagen-canary activates once OXAGEN_PLATFORM_TOKEN is wired.
- #58 schema $id repointed to the GitHub-raw URL that resolves today (interim
  until #57's Vercel relink); schema validate-examples.py green, mirror
  byte-identical.

Closes #20, #29, #58
Refs #16, #59 (publish/tag/secret steps are human-only)

* docs(spec): add normative Usage reports section, fix tokenizer_ref comment (#49)

Closes the two remaining #49 "survivors":

- SPEC.md gains a normative §7.3 "Usage reports" (UR1): a host MUST be able to
  produce a usage report whose budget_consumed equals the summed token_cost of
  served frames, referencing them by FrameId — backed by the existing, tested
  contextgraph-host::FanOut::usage_report. Resolves the "U1" anchor collision
  with §13's ignore-unknown-members rule by labelling this UR1 across SPEC.md,
  docs/context-reuse.md, and docs/protocol-surface.md, and repointing §14's A1
  cross-reference at §7.3.
- Reword the schema canonical_token_cost $comment so tokenizer_ref pairs only
  with canonical_token_cost (the exact-count companion), never the byte-formula
  token_cost (§B3/§7.2) — resolving #50's tokenizer residual. Source and site
  schema copies stay byte-identical.

schema/validate-examples.py green.

Closes #49
Refs #50

* docs(spec): sketch the deferred context/neighbors 1.x operation (#7)

The graph itself is already real and witnessed — §8 specifies graph frames, the
open `rel` vocabulary, and the G1/G2/G3/G4 checks (G4's anchored predicate and
its `anchor-relevance` check landed in #63/#64). The one remaining #7 acceptance
box was the design sketch for multi-hop traversal.

Adds docs/sketches/context-neighbors.md (a `context/neighbors { uri, rels, depth }`
envelope pair as a post-1.0 additive minor, defined so `depth: 1` ≡ the G4
anchored set) following the docs/sketches/resolve.md template, and a §8.3
forward-reference in SPEC.md mirroring the §6.4.1 deferral pattern. No wire
change — traversal beyond one hop is explicitly out of scope for the 1.0 freeze.

Closes #7

* feat(host): carry structured error codes across the transport boundary (#9)

The wire already carried `code: Option<ErrorCode>`; nothing read it. This plumbs
it end to end and tightens the conformance floor:

- ErrorCode gains `unsupported_representation` (§P5) and `incompatible_version`
  (§H3), wired through as_str/From<&str>/reaction. incompatible_version is
  permanent — a new HostReaction::DropProvider (the request is fine, the provider
  is unusable; distinct from DoNotRetry/Respawn/ReportAndCount).
- HostError::Provider now carries `code`; the four http.rs/stdio.rs error arms
  pass it through instead of discarding it, so FanOut::failures surfaces it.
- The malformed-input-tolerance conformance check now passes only on a
  `bad_request` code (was: any Envelope::Error), per SPEC.md R1. A new
  `--misbehave mislabel-malformed` mode (answers `internal`) exercises the
  tightened check in conformance-red.sh, with a matching suite test.

Gate green: fmt, clippy -D warnings, test --workspace, conformance-green (12/12),
conformance-red (all misbehave modes caught).

Closes #9

* feat(host): enforce C7/C8 in the reference HTTP transport (#13)

C7/C8 were specified (§4.2) but listed as a live enforcement gap (§11.1). This
implements them in the reference host:

- C7 (TLS for non-loopback): HttpProvider refuses a plaintext http:// target to
  any non-loopback host with HostError::InsecureTransport, BEFORE the client is
  built or DNS resolves. Loopback (localhost / 127.0.0.0/8 / [::1]) stays exempt
  so the wiremock suite keeps working.
- C8 (credentials never logged): a new Credential type whose Debug AND Display
  both render only "Credential(<redacted>)" (secret reachable only via a
  crate-private expose); attached via reqwest bearer_auth, never a format
  string. A redaction test asserts no HostError/format string leaks the secret.
- connect_with_auth / Host::add_http take an optional Credential (connect stays
  as a back-compat None wrapper); a 401 surfaces as HostError::Unauthorized.
- SPEC.md §11.1 updated: C7/C8 now enforced + unit-tested at the
  transport-refusal/redaction level; full live-TLS-peer conformance remains the
  stated next increment (unchanged).

Gate green: fmt, clippy -D warnings, test (119 host + 4 new), conformance
green/red, schema validate. wiremock was already a dev-dep.

Closes #13

* feat(conformance): host-side H3 version-rejection + crash-isolation scenarios (#14)

The host-conformance harness gained the two adversarial transport scenarios it
was missing (the primitives already existed in contextgraph-host; this wires
them in as witnessed checks). run_host_conformance now exposes 8 checks:

- host-version-reject (§3 H3, host-side): drives the reference host's handshake
  at a fixture declaring contextgraph/2.0 (mismatched major family), under an
  explicit tokio timeout so "never a hang" is a load-bearing assertion, and
  asserts HostError::VersionMismatch. Distinct from §3's provider-facing
  handshake check (both now named in the H3 "Verified by" cell).
- host-crash-isolation (§11): a query_all fan-out where one provider dies
  mid-query (ProviderCrashed via the BrokenPipe/EOF path) while a healthy peer
  is queried concurrently; asserts the fan-out still completes with the healthy
  frames and the crash is reported + excluded, never poisoning the query.

Each keeps the adversarial+well-behaved-counterpart discrimination pattern, and
both were red-then-green mutation-tested (invert the fixture → check fails).
SPEC.md §11.1 updated to name both host-side scenarios (added to #13's C7/C8
text, not reverting it).

Gate green: fmt, clippy -D warnings, test, host-conformance (8/8),
conformance green/red, schema validate.

Closes #14

* feat(sdk): HTTP adapters + create-contextgraph-provider scaffold + quick-starts (#17)

Fills the provider-SDK residue (skip Java; publishing is #59):

- HTTP adapter per SDK, mirroring the stdio provider loop as a single-endpoint
  POST handler: createHttpHandler (TypeScript), make_wsgi_app (Python),
  Handler (Go). Each ships a runnable example-docs-http provider that goes green
  under `contextgraph-inspect http` (9 passed / 3 skipped — the 3 skips are the
  harness's stdio-only wire probes, unavoidable over HTTP).
- create-contextgraph-provider: a zero-dep Node CLI with TypeScript + Python
  templates that scaffold a provider wired to both transports PLUS a bundled
  GitHub Actions workflow running contextgraph-inspect against the generated
  provider in its OWN CI from the first commit (the literal acceptance criterion).
- Quick-starts: TS + Python quick-starts, an HTTP-transport section, and a
  scaffold section appended to docs/implementing-a-provider.md and the docs-site
  mirror; HTTP APIs documented in each SDK README.

Validated via the pre-built contextgraph-inspect: TS/Python/Go HTTP all green,
existing stdio conformance still 12/12, both scaffolded templates conformant.
The CI jobs (sdk-*-http, sdk-scaffold) are applied to ci.yml separately.

Closes #17

* ci+docs: wire sdk-http/scaffold CI jobs, record the host+sdk wave in CHANGELOG

- ci.yml: add sdk-typescript-http, sdk-python-http, sdk-go-http (start each
  example server, run `contextgraph-inspect http` against it) and sdk-scaffold
  (generate a provider from create-contextgraph-provider and assert its own
  conformance check passes) for #17. actionlint clean.
- CHANGELOG [Unreleased]: record #9, #13, #14, #17.

Refs #9, #13, #14, #17

* feat(conformance): stale-digest misbehave mode + fixture self-consistency check (#12)

The reference fixture now verifies its own digests end to end, closing the
"stdio fixture" survivor of #12 (the digest grammar + host verify API were
already done):

- The example-docs fixture gains real on-disk backing files
  (fixtures/example-docs/{getting-started,configuration}.md); fixture_digest now
  computes a genuine sha256 over those bytes at runtime and frames carry file://
  provenance, so verify_file_provenance can re-read and re-hash them.
- New provider check `provenance-fixture-consistency`: re-reads each frame's file
  provenance and re-hashes it against the bytes on disk (Verified→pass,
  Mismatch→fail, Unreadable→host-local skip). The suite is now 13 checks.
- New `--misbehave stale-digest` mode emits a WELL-FORMED sha256 (one hex digit
  flipped) that passes F5 grammar and verify-honesty but does not match the real
  bytes — provenance forgery only the new check catches. conformance-red.sh
  auto-discovers it (no script edit).
- sha2 moved from a conformance dev-dep to the workspace 0.10 normal dep (matches
  the host verifier); verify_wire.rs now computes real digests from the files.

Gate green: fmt, clippy -D warnings, test, conformance-green (13/13),
conformance-red (all modes incl. stale-digest), schema validate.

Closes #12

* docs(registry): regenerate seed report at 13 checks after #12

#12 added the provenance-fixture-consistency check (suite 12→13). Regenerate the
bundled contextgraph-example-docs conformance report from
`contextgraph-inspect stdio --json` and update the registry table to 13/13 so the
listed attestation stays a faithful capture, not a stale claim.

Refs #20, #12

* feat(host): pipeline the stdio transport — demux on id, shrink the mutex (#4)

Delivers the demux/pipelining half of ADR 0002 (the correlation-id decision half
already shipped). StdioProvider previously held one mutex across the whole query
round-trip, so concurrent queries serialized even when the provider negotiated
capabilities.correlation.

- The connection is split after handshake into a write-half (stdin mutex), a
  dedicated reader task, and a control handle (StdioControl) that reproduces the
  SHUTDOWN_GRACE + kill_group semantics exactly. RawStdioConnection::into_parts
  moves the fields out without running Drop (ManuallyDrop + one ptr::read per
  field — sound: each read once, destructor suppressed).
- A `pending: HashMap<id, oneshot::Sender>` demuxes replies. query (correlated)
  registers its oneshot before sending, holds the stdin mutex only for the write,
  then awaits its reply with no lock held — so two queries interleave. Reader
  drains every waiter on EOF/decode/transport error, so a crash fails in-flight
  queries instead of hanging them.
- Non-correlating providers and verify keep the strict lock-step path
  (exchange_lockstep), provably unchanged. RawStdioConnection's public raw
  send/recv API is byte-for-byte unchanged, so the conformance crate's wire
  probes compile and pass untouched.
- Witness test (ADR 0002): a fixture that reads both queries before answering
  either, then replies to the second FIRST — deadlocks a lock-step transport,
  demuxes correctly here. Ran 15x, no flakes.

Gate green: fmt, clippy -D warnings, test --workspace (+witness), conformance
green (13/13)/red/host, schema validate.

Closes #4

* docs(changelog): record #4 (stdio pipelining) and #12 (stale-digest)

Refs #4, #12

* feat(host): reference prompt-composition module — budget, dedup, audit (#15)

Layered on compose_context's byte-stability floor (the injection-escaping half
shipped in #63); this delivers the rest of #15:

- Host::query_all_budgeted splits a global token budget into per-provider shares
  before fan-out, so N honest legs sum to <= the whole budget instead of N x it.
- compose::dedup_cross_provider collapses the same evidence from two providers
  (content_digest match, then uri+range provenance overlap), keeping the
  higher-scored frame and merging provenance.
- order_by_value places the highest-value frames at the top/bottom edges
  (Lost in the Middle, Liu et al. 2024), byte-stable for a fixed set.
- compose_for_prompt returns an injection-resistant fenced prompt with an
  "evidence, not instructions" preamble, a citation map (label -> frame id +
  provenance), and a CompositionAudit that explains every included/excluded frame.
- New host-conformance check host-composition-audit (host suite now 9),
  red-then-green mutation-tested; a property test bounds composed tokens <=
  budget; an injection-corpus test proves no instruction-shaped payload escapes
  the fence. SPEC.md R3 now cites the host checks + the new reference doc.

Gate green: fmt, clippy -D warnings, test --workspace (+property +injection),
conformance green/red, host-conformance (9/9), schema validate.

Closes #15

* feat: reference provider crates + MCP interop bridges (#18, #19)

Five new publish=false workspace crates, each conformance-green:

#18 — reference providers:
- contextgraph-ripgrep: Snippet frames from a ripgrep/built-in content search
  with real, re-verifiable file provenance.
- contextgraph-treesitter: Symbol + Graph frames (code.defines/calls/imports),
  via a self-contained pure-Rust symbol extractor (no tree-sitter C toolchain).
- contextgraph-refprov: the shared stdio-provider kit both binaries reuse.
Both providers pass all 13 provider checks under conformance-external.sh; git
history episodes deferred as the sanctioned stretch. See docs/reference-providers.md.

#19 — MCP interop, a bridge in each direction:
- contextgraph-mcp-bridge: wraps any MCP resource server as a budgeted, cited,
  consent-gated CGP provider (MCP resources -> Doc/Snippet frames with
  mcp-resource provenance; local file:// resources get a byte-verifiable digest).
  Goes fully conformance-green against a hermetic in-repo MCP fixture — no
  network, no npx.
- contextgraph-mcp-server: exposes a CGP host's fan-out as an MCP
  query_context(goal, budget, kinds) tool returning frames, provenance,
  citations, and a budget audit as structured content. See docs/composition-walkthrough.md.

CI gains reference-provider-ripgrep, reference-provider-treesitter, and mcp-bridge
jobs. No new external dependencies. Full workspace gate green: fmt, clippy
-D warnings, test, conformance green (13/13)/red/host, the three external-provider
suites, and schema validate.

Closes #18
Closes #19

* feat(types): ratify the Context Exchange Provider lifecycle profile (#28)

Turns docs/profiles/context-exchange-provider.md from a draft skeleton into a
normative profile (contextgraph/lifecycle/1.0-draft) with RFC-2119 rows + stable
anchors and every [OPEN] resolved from ADR 0007 / the reconciliation doc:

- schema/contextgraph-lifecycle-record.schema.json (+ byte-identical site mirror):
  the discriminated ContextRecord union — common envelope + 12 record kinds
  (observation, knowledge, memory, directive, record_proposal, evidence,
  artifact_contract, contract_validation, outcome_assessment, promotion_event,
  context_use, context_use_feedback), closed via unevaluatedProperties:false.
- contextgraph-types::record: serde wire types for the envelope + kinds +
  detached RecordAttestation + envelope_invariants; the crate stays
  zero-runtime-dep beyond serde.
- tests/fixtures/: one golden fixture per record kind + an attestation + a README
  documenting the canonical fixture home and a worked RFC 8785 JCS -> sha256
  record_hash example (Python and Rust canonicalizers agree byte-for-byte).
- contextgraph-conformance/tests/lifecycle_profile_examples.rs: round-trip,
  envelope-invariant, and JCS hash-recomputation tests over the fixtures.
- Resolutions: context/resolve is profile-scoped (SPEC §6.4.1 reservation);
  E3 7-key scope (tenant_id/project_id dropped, schema rejects them); B5 3-value
  record_status; D6/D7 schema-vs-execution split; C5 provenance + detached
  attestation. Reconciliation rows D1/D4/D5/D6/D7/B3/B5/C5/E3 marked resolved;
  SPEC §6.4.1/§13 cross-linked (non-normative pointers).

Owner judgment calls flagged in the issue: origin-enum vs provenance boundary,
open `sensitivity` string, string-valued `extensions`, capability types doc-only.

Gate green: fmt, clippy -D warnings, test --workspace, schema validate (new
schema + fixtures + byte-identical mirror), build, conformance-green (13/13).

Closes #28

* docs: apply the CGP abbreviation convention + README CI badge (#21, #2 partial)

- README, CONTRIBUTING, docs/, and the site/content/docs mirrors now expand
  "Context Graph Protocol (CGP)" on first mention and use "CGP" for subsequent
  body-prose mentions — matching the already-conventional SPEC.md / ADR 0002 /
  reconciliation doc. Titles, markdown link text, version strings
  (contextgraph/1.0-draft), crate names, and code fences left intact.
- Reconciled a stale OCP→full-name rename artifact in the protocol-advantages
  site mirror ("Open Context Protocol (Context Graph Protocol)" / doubled bold).
- Fixed the bug-report template grammar ("in an" → "in a").
- Added a CI status badge to README (the buildable half of #2; branch protection
  itself stays owner-only).

Closes #21
Refs #2 (branch-protection rule remains owner-only)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant