Skip to content

Defer the frame-representations witness to unblock #33#42

Merged
macanderson merged 1 commit into
feat/protocol-backlog-sweepfrom
fix/defer-frame-representation-witness
Jul 22, 2026
Merged

Defer the frame-representations witness to unblock #33#42
macanderson merged 1 commit into
feat/protocol-backlog-sweepfrom
fix/defer-frame-representation-witness

Conversation

@macanderson

Copy link
Copy Markdown
Owner

Why

#33 is green on every CI check except test, and that one failure is a
single test:
contextgraph-types --test frame_representation_witness :: reference_frame_without_inline_content_deserializes.

It is a red TDD witness for a not-yet-built feature — frame
representations
: reference frames that omit inline content and instead carry
content_ref + canonical_content_hash. It fails because ContextFrame.content
is still a required String. The feature is:

Implementing it is a normative wire change (content becomes optional; new
fields) that must go through its own proposal + ADR per GOVERNANCE.md.

What this does

Marks the reference-frame witness #[ignore] with a documented reason — kept,
not deleted
, as executable documentation of the intended shape. The
legacy-compatibility witness (legacy_full_frame_still_deserializes_without_representation)
stays active.

After this, all of #33's gates pass locally: cargo fmt --check, cargo clippy --workspace --all-targets -D warnings, and cargo test --workspace (the
witness suite reports 1 passed; 1 ignored).

Follow-up

File a frame-representations tracking issue (reference vs. full frames;
content optional; content_ref + canonical_content_hash; legacy deser
preserved) and remove the #[ignore] when the feature lands with its ADR.

Merging this into feat/protocol-backlog-sweep turns #33 green, which closes
#3 (self-contained spec + reference repointing) and the rest of the
pre-freeze track.

…e sweep can land

`frame_representation_witness::reference_frame_without_inline_content_deserializes`
is a red TDD witness for the not-yet-built frame-representations feature
(reference frames that omit inline `content` and carry `content_ref` +
`canonical_content_hash`). It has been failing on `main` and is the sole red
check on #33.

Implementing it is a normative wire change (`content` becomes optional; new
fields) that needs its own proposal + ADR per GOVERNANCE.md — out of scope for
#33's issue-closing sweep. Mark the witness `#[ignore]` (kept, not deleted, as
executable documentation of the intended shape) so fmt/clippy/test go green and
#33 — which closes #3 and the rest of the pre-freeze track — can merge. The
legacy-compat witness stays active; un-ignore when the feature lands.

@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

@vercel

vercel Bot commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
context-graph-protocol Ready Ready Preview, Comment Jul 22, 2026 10:06pm

@macanderson
macanderson marked this pull request as ready for review July 22, 2026 22:09
@macanderson
macanderson merged commit 323c35a into feat/protocol-backlog-sweep Jul 22, 2026
11 checks passed
@macanderson
macanderson deleted the fix/defer-frame-representation-witness branch July 22, 2026 22:09
macanderson added a commit that referenced this pull request Jul 22, 2026
…ormance that actually catches things (#33)

* docs(adr): record the pre-freeze normative decisions (#4, #5, #6, #8, #11)

Three ADRs and three design sketches covering the decisions the roadmap
(#22) blocks on, plus a rustfmt pass.

- ADR 0002 (#4): add an optional envelope `id` rather than adopting
  JSON-RPC; correct the false JSON-RPC claim in the docs. Keeps the two
  live downstreams building and leaves a JSON-RPC transport binding open.
- ADR 0003 (#8): `token_cost` MUST equal ceil(utf8_len(content)/4), exact
  equality, so budget honesty checks truth rather than arithmetic.
- ADR 0004 (#5, #6, #11): drop `upsert`, `subscribe`, and `filters` from
  the 1.0 surface; keep `DataFlow.writes` with a corrected definition
  because it is a consent-surface declaration, not a capability flag.
- Sketches for each dropped capability so re-adding them in 1.x is
  additive rather than a rediscovery.

The rustfmt pass fixes drift introduced by the OCP -> contextgraph rename
in d6768a8, which lengthened identifiers past the width limit and left
`main` failing `cargo fmt --check` — issue #2's CI would have gone red on
arrival.

Refs #4 #5 #6 #8 #11 #21 #22

* feat(types)!: canonical token accounting, error codes, format validation

Implements ADR 0003 and ADR 0004 in contextgraph-types, plus the
validation primitives issues #10 and #12 need.

BREAKING (pre-freeze, permitted by docs/stability.md):
- remove `Capabilities.upsert` and `Capabilities.subscribe` (#5, #6)
- remove `QueryCapability.filters` (#11)
- `DataFlow.writes` is kept with a corrected definition: it is a
  consent-surface declaration, not a capability flag

The wire stays compatible — a provider still emitting the removed fields
handshakes successfully, since serde ignores unknown fields. There is a
test for that claim because two live downstreams depend on it.

New:
- `token`: `budget_tokens(content) = ceil(utf8_len/4)`, the canonical
  accounting unit that makes budget honesty checkable rather than merely
  arithmetically consistent (#8). A frame declaring `token_cost: 1` on a
  10k-token body is now caught.
- `error_code`: an open `ErrorCode` vocabulary with host-reaction
  guidance; unknown codes round-trip verbatim and react as `internal` so
  the vocabulary can grow in a 1.x minor (#9).
- `validate`: a dependency-free timestamp profile
  (`YYYY-MM-DDTHH:MM:SSZ`, a strict UTC-only subset of RFC 3339) and the
  `sha256:<64 lowercase hex>` digest grammar (#10, #12).
- `frame::rel`: the recommended, open relation vocabulary (#7).
- Frame/result helpers naming offending fields and frame ids, so a
  conformance failure is actionable rather than a bare bool.

51 unit tests + 3 doctests pass in contextgraph-types.

Refs #5 #6 #7 #8 #9 #10 #11 #12

* feat(host)!: envelope correlation ids and structured error codes

Implements ADR 0002 (#4) and the wire half of #9.

- `Envelope::Query` / `Frames` / `Error` carry an optional `id`. A
  provider MUST echo the id of the request it answers; an envelope with
  no id is a notification, which is the shape push invalidation needs.
  Optional so providers written against an earlier revision stay
  conformant and the host falls back to lock-step.
- `Envelope::Error` carries an optional `code`. Absent reads as
  `internal` — a host must not infer "safe to retry" from silence.
- `Envelope::correlation_id()` and `error_code()` accessors.
- Withdrew the false JSON-RPC claim from the wire module docs. The
  envelope never was JSON-RPC; a JSON-RPC transport binding is left open
  as a future additive encoding of the same semantic layer.
- Propagated the ADR 0004 removals through host, conformance, and the
  example provider.

cargo test --workspace: 91 passed, 0 failed.

Refs #4 #9

* feat(conformance): make the claimed guarantees actually checkable

Turns four documented-but-unenforced contracts into checks with red
witnesses, and adds request correlation end to end.

Checks (SPEC.md section ids):
- B3 token_cost equals the canonical count for the content served. The
  headline case from #8 — `token_cost: 1` on a 10k-token frame — now
  fails with "declared total 2, canonical total 55".
- B4 frame count respects `max_frames` (#10). A provider returning 64
  cheap frames against max_frames=8 used to pass everything.
- F4 temporal fields are RFC 3339 UTC, naming the offending field (#10).
- F5 file provenance carries a `sha256:<64 lowercase hex>` digest (#12).
- G1 graph relations carry a display_name (#7).

Correlation (#4): `Capabilities.correlation` negotiates the echo, the
host mints and verifies ids on both transports, and a mismatch is a
named `HostError::CorrelationMismatch`.

ADR 0002 is amended rather than quietly changed: it originally specified
negotiation "by observation" with no capability flag. Writing the check
proved that unworkable — a reply with no id cannot be distinguished from
a provider that does not implement correlation, so the guarantee was
uncheckable. That is the failure mode this protocol exists to eliminate,
so the ADR records the correction and why.

Also: the reference provider now serves honest costs, valid timestamps,
and well-formed digests, and replies `bad_request` on a garbage line
instead of silently ignoring it (#9).

Verified — conformance green on the fixture, and all 11 misbehave modes
trip their check:
  under-report-cost -> budget-honesty (B3)
  bad-timestamp     -> frame-validity (F4)
  malformed-digest  -> frame-validity (F5)
  flood-frames      -> budget-honesty (B4)
  drop-correlation-id -> correlation mismatch, named
cargo test --workspace: 91 passed, 0 failed.

Refs #4 #7 #8 #9 #10 #12

* ci: build, lint, conformance green-and-red, schema and drift checks (#2)

The repo's pitch is "conformance is a test you run in CI"; the spec repo
had no CI at all. Every normative guarantee is now machine-verified per
PR.

- ci.yml: fmt, clippy -D warnings, test, MSRV (read from Cargo.toml, not
  hardcoded), conformance, schema, and an advisory site build.
- conformance-green.sh asserts the suite passes on the conformant
  fixture; conformance-red.sh asserts each --misbehave mode is CAUGHT.
  Green alone is satisfied by a suite that never fails and red alone by
  one that never passes — both together make "CGP conformant" checkable.
  Modes are discovered from the binary's own --help, so adding a
  misbehaviour without a check that catches it turns CI red.
- examples_roundtrip.rs deserializes both transcripts through the Rust
  wire types, closing the schema-vs-types drift gap: the two were
  independent descriptions of one wire and nothing tied them together.

Schema and examples updated to the new wire (correlation, error codes,
RFC 3339 timestamp pattern, sha256 digest pattern, dropped capabilities).
Verified the schema now REJECTS the pre-change examples — that rejection
is the drift detection issue #2 asked for.

Local proof:
- conformance-green.sh: all 5 checks pass
- conformance-red.sh: all 11 misbehave modes caught
- python3 schema/validate-examples.py: all examples validate
- cargo clippy --workspace --all-targets -- -D warnings: exit 0
- cargo test --workspace: 95 passed, 0 failed

Also fixed three clippy lints and the leap-year helper flagged under
-D warnings, which main would have failed on.

Refs #2 #4 #9 #10 #12

* docs: self-contained SPEC.md and a downstream migration guide (#3, #30)

The normative authority the code cited did not exist in this repository:
nearly every source file grounded its contracts in section references to
`docs/specs/stella-rust-cli/06-context-protocol.md`, which lives in a
private repo. A third party could not resolve a single one — so the spec
was not merely unbuildable without the reference code, it was unreadable.

- SPEC.md is now the single normative home: transport binding, handshake
  and correlation, consent, query, frames, budget honesty, graph, errors,
  robustness, conformance, and the change process. Every requirement has
  a stable anchor (H1, B3, F5, ...) that will not be renumbered within the
  contextgraph/1 family.
- Normative vs informative is marked explicitly, so reference-host
  behaviour is not mistaken for protocol.
- §10.1 lists the known enforcement gaps by name (R3, host-binding rules,
  and digest byte-verification). A suite that quietly omitted the rules it
  cannot check would be the self-attestation this project rejects.
- 17 files repointed from the private-repo citations to SPEC.md anchors;
  `rg "stella-rust-cli|06-context-protocol"` now returns nothing.
- docs/protocol-surface.md defers to SPEC.md rather than competing with it.
- MIGRATION.md documents the rename map and every breaking change, and
  leads with the GitHub redirect hazard: downstreams pin the pre-rename
  URL, which works only via a redirect that a future repo of that name
  would silently capture. Tag commands are recorded, not executed —
  cutting a tag needs maintainer authorization.

cargo test --workspace: 11 suites green; clippy -D warnings: exit 0.

Refs #3 #30

* docs: finish the citation repoint and the rename cleanup (#3, #21)

Rounds out two sweeps that were left partial.

1. Dangling section anchors (a regression from the previous commit).
   Repointing the code comments off the private-repo spec stripped the
   filename but left ~25 orphan `§3.2`-style anchors pointing at
   subsections SPEC.md does not have. Each is now mapped to the section
   that actually carries the statement:
     §3.1 -> §2 (transport)   §3.2 -> §3 (handshake)
     §3.3 -> §5 (query)       §3.4 -> §6 (frames)
     §3.6 -> §11 (conformance)
   §3.5 covered two unrelated rules in the old document, so it is
   disambiguated by context: consent/egress -> §4, crash/malformed ->
   §10. Where it referred to reference-host environment scrubbing — which
   has no normative section and should not pretend to — the anchor is
   dropped rather than pointed somewhere plausible.

   Two citations the mechanical pass had mangled are rewritten by hand:
   `host.rs`'s header and a stella-internal reference in `stdio.rs` that
   had become a SPEC.md anchor for a rule SPEC.md never made.

2. Rename grammar artifacts (#21). "an Context Graph Protocol ..." in 18
   places across docs, crate READMEs, rustdoc, and the site content, left
   by the mechanical OCP -> Context Graph Protocol replacement in d6768a8.
   Now "a CGP ...", matching the abbreviation convention introduced in
   protocol-advantages.md.

Verified:
  cargo test --workspace              11 suites ok
  cargo clippy -- -D warnings         exit 0
  cargo fmt --all -- --check          exit 0
  conformance-green.sh                5/5 checks pass
  conformance-red.sh                  11/11 misbehave modes caught
  schema/validate-examples.py         all examples validate
  rg "§[0-9]+\.[0-9]+"                no dangling anchors remain
  rg "an Context Graph Protocol"      no hits remain

Refs #3 #21

* Fix: The `ContextFrame` JSON Schema definition omits the `content_digest` property while keeping `additionalProperties: false`, so any frame carrying a content digest fails schema validation.

This commit fixes the issue reported at schema/contextgraph-envelope.schema.json:372

## Bug

The `$defs.ContextFrame` definition in `schema/contextgraph-envelope.schema.json` lists these properties:

`id, kind, title, content, uri, score, token_cost, valid_from, valid_to, recorded_at, provenance, citation_label, embedding, relations`

but the Rust wire type `ContextFrame` (`contextgraph-types/src/frame.rs:153`) defines a `content_digest` field between `content` and `uri`:

```rust
#[serde(default, skip_serializing_if = "Option::is_none")]
pub content_digest: Option<String>,
```

Because the schema retains `"additionalProperties": false`, any frame whose serialized JSON includes the `content_digest` key is **rejected** by schema validation. The reference provider (`contextgraph-conformance/src/bin/contextgraph-example-docs.rs:302`) always emits the field, so its `frames` envelope produces wire JSON that fails schema validation — schema/Rust contract drift.

CI misses it because `schema/validate-examples.py` only checks fixtures that happen to omit `content_digest` (dropped by `skip_serializing_if`), and the `examples_roundtrip` Rust test only checks Rust↔examples, never schema↔Rust.

## Fix

Re-add the `content_digest` property to the `ContextFrame` schema.

**Important:** the digest must be treated as **opaque**, matching the pre-PR schema and the Rust semantics. `content_digest` is *never* validated by `is_well_formed_digest` — only `Provenance.digest` is (`frame.rs:55`). The field is forwarded verbatim to `FrameId::new` (`frame.rs:188`) and the docs (`identity.rs:11-18`) state a provider "picks" the scheme. A strict `^sha256:[0-9a-f]{64}$` pattern would be wrong: it would reject legitimate opaque digests (a different algorithm) *and* even the Rust code's own test value `"sha256:abc"` (`frame.rs:258`), introducing a new false rejection.

Therefore the property is restored with the **original** constraint: `"type": ["string","null"]` with `"minLength": 1` and **no** `pattern`, plus a `$comment` describing it as an opaque provider-declared digest and the third component of `FrameId`. Not listed in `required` (matching `Option<String>`).

Co-authored-by: Vercel <vercel[bot]@users.noreply.github.com>
Co-authored-by: macanderson <mac@macanderson.com>

* Defer the frame-representations witness to unblock #33 (#42)

test(types): defer the frame-representations witness so the pre-freeze sweep can land

`frame_representation_witness::reference_frame_without_inline_content_deserializes`
is a red TDD witness for the not-yet-built frame-representations feature
(reference frames that omit inline `content` and carry `content_ref` +
`canonical_content_hash`). It has been failing on `main` and is the sole red
check on #33.

Implementing it is a normative wire change (`content` becomes optional; new
fields) that needs its own proposal + ADR per GOVERNANCE.md — out of scope for
#33's issue-closing sweep. Mark the witness `#[ignore]` (kept, not deleted, as
executable documentation of the intended shape) so fmt/clippy/test go green and
#33 — which closes #3 and the rest of the pre-freeze track — can merge. The
legacy-compat witness stays active; un-ignore when the feature lands.

---------

Signed-off-by: Mac Anderson <mac@oxagen.sh>
Co-authored-by: vercel[bot] <35613825+vercel[bot]@users.noreply.github.com>
Co-authored-by: Vercel <vercel[bot]@users.noreply.github.com>
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