Skip to content

fix: hold the spec to its own schema, and its own conformance claims - #64

Merged
macanderson merged 2 commits into
mainfrom
worktree-spec-schema-conformance
Jul 26, 2026
Merged

fix: hold the spec to its own schema, and its own conformance claims#64
macanderson merged 2 commits into
mainfrom
worktree-spec-schema-conformance

Conversation

@macanderson

Copy link
Copy Markdown
Owner

Three reported defects, all the same shape — a normative claim nothing checked — plus a fourth found by generalizing the third.

1. SPEC.md §9's verify example failed the schema SPEC.md ships

Both envelopes carried "id": "v1". But §3.2 grants an id only to query/frames/error, the reference Envelope::Verify/Verified have no such field, and the schema is additionalProperties: false. The spec's own example was invalid against the spec's own definition.

Removed. verify correlates by full frame identity, not by envelope id — which §9's prose already explains.

Root cause, and the actual fix: schema/validate-examples.py validated examples/ but never SPEC.md. The one example surface with no machine check was the one that drifted. It now validates every fenced jsonc block in SPEC.md// comments stripped (string-aware), documented placeholders (sha256:<64 hex>, "…") normalized, then structure checked against the root schema or $defs/ContextFrame as appropriate. Substituting a placeholder never invents a member, so it cannot mask an additionalProperties violation.

CI's existing schema job now covers this. Confirmed by re-introducing the id:

FAIL  SPEC.md:442 [1/2] (envelope verify)
FAIL  SPEC.md:442 [2/2] (envelope verified)
2 failure(s)

2. An ordinary ContextQuery was un-representable in conformant JSON

kinds and anchors were listed as required while both are skip_serializing_if = "Vec::is_empty". An unfiltered, unanchored query — what a host sends when it wants anything relevant — serializes without them and failed validation. Precisely the bug PR #44 fixed for ContextFrame, one type over.

Fixed the schema to require only what the reference serializer always emits. A new test asserts an ordinary query satisfies the schema's own required array (it reads the schema, so it can't drift into a stale snapshot).

I also re-ran the audit that missed this, mechanically cross-referencing every skip_serializing_if field against every schema required list: ContextQuery was the only remaining mismatch across all 16 shared types. The audit flags it pre-fix and is clean post-fix.

3. §G2 claimed an enforcement that did not exist

target_uri appeared nowhere in the conformance or validation code. Implemented rather than downgraded — the schema has carried minLength: 1 from the start and §G1 immediately beside it is enforced, so making the claim true beats weakening it.

4. §D1 was unenforced too (found by generalizing #3)

Auditing the other ten rules citing frame-validity turned up a second instance: the frame's own content_digest was never format-checked. check_frames accepted sha256:abc — the very placeholder validate::tests::rejects_the_placeholder_digest_the_repo_used_in_examples exists to reject — while its evidence string claimed "well-formed digests." The digest anchoring deterministic composition, usage reports, and context/verify was held to a looser standard than the §F5 provenance digests directly below it.

Fixed. The remaining nine frame-validity rules (F1–F5, P1–P3, G1) were confirmed genuinely enforced.

Both new checks are mutation-tested — disabling either makes its test fail, so neither is vacuous:

a relation with target_uri "" passed frame-validity — §G2 is unenforced again

Also

contextgraph-host::wire documented correlation as "negotiated by observation, not by a capability flag" — contradicting §3.2 and the shipped Capabilities::correlation, and inverting a MUST NOT. An implementer following it would send an id to a provider that never declared correlation. Corrected.

Verification

Gate Result
cargo test --workspace 243 passed, 0 failed
cargo fmt --all --check clean
cargo clippy --workspace --all-targets -- -D warnings clean
python3 schema/validate-examples.py OK — all examples validate
conformance-green.sh / conformance-red.sh / host-conformance.sh pass

The red suite matters here: it asserts every --misbehave mode is still caught, so the two new checks don't mask existing detections.

Review notes

Three defects, all the same shape: a normative claim nothing checked.

1. SPEC.md §9's `verify` example was invalid against the schema SPEC.md
   ships. Both envelopes carried `"id": "v1"`, but §3.2 grants an `id` only
   to `query`/`frames`/`error`, `Envelope::Verify`/`Verified` have no such
   field, and the schema is `additionalProperties: false`. Removed — verify
   correlates by full frame identity, not by envelope id.

   Root cause: validate-examples.py checked `examples/` but never SPEC.md,
   so the one example surface with no machine check was the one that
   drifted. It now validates every fenced jsonc block in SPEC.md (comments
   and documented placeholders normalized; structure checked), so CI's
   existing `schema` job catches this class. Verified by re-introducing the
   `id`: the harness fails both envelopes and exits non-zero.

2. An ordinary ContextQuery was un-representable in conformant JSON.
   `kinds` and `anchors` were `required` while both are
   skip_serializing_if = "Vec::is_empty", so an unfiltered, unanchored
   query failed validation — the bug PR #44 fixed for ContextFrame, one
   type over. A test now asserts an ordinary query satisfies the schema's
   own `required` array (reads the schema, so it cannot drift), and a
   cross-audit of all 16 shared types confirms no other type demands a
   field its serializer elides.

3. §G2 claimed "Verified by frame-validity" while `target_uri` appeared
   nowhere in the conformance or validation code — the self-attestation
   §11.1 exists to reject. Implemented rather than downgraded: check_frames
   now rejects an edge with an empty target_uri.

   Auditing the other ten rules citing frame-validity found §D1 unenforced
   too: the frame's own content_digest was never format-checked, and the
   evidence string claimed "well-formed digests" while accepting
   `sha256:abc` — the very placeholder validate.rs exists to reject. Also
   fixed; the remaining nine were confirmed enforced.

Both new checks are mutation-tested: disabling either makes its test fail.

Also corrects contextgraph-host::wire, which said correlation is
"negotiated by observation, not by a capability flag" — contradicting
§3.2 and the shipped Capabilities::correlation, and inverting a MUST NOT.

cargo test --workspace: 243 passed, 0 failed. fmt, clippy -D warnings,
validate-examples.py, and conformance-{green,red}/host all pass.

@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, you have reached your weekly rate limit of 500000 diff characters.

Please try again later or upgrade to continue using Sourcery

@macanderson
macanderson marked this pull request as ready for review July 26, 2026 18:53
Signed-off-by: Mac Anderson <mac@oxagen.sh>
@macanderson
macanderson merged commit 8533416 into main Jul 26, 2026
10 checks passed
@macanderson
macanderson deleted the worktree-spec-schema-conformance branch July 26, 2026 18:56

@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, you have reached your weekly rate limit of 500000 diff characters.

Please try again later or upgrade to continue using Sourcery

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
* 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 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