The problem
The pages a provider author reads before writing any code describe a wire
contract we do not have. Four separate errors, all of which would make someone
build the wrong thing. Two of them would make them build something
non-conformant.
1. FrameKind is shown as a closed set of seven. It is open.
README.md and docs/overview.md both print:
pub enum FrameKind { Snippet, Symbol, Fact, Doc, Memory, Episode, Graph }
docs/protocol-surface.md prints the same seven variants inside its "Every type
below lives in that crate" section, and docs/GUIDE.md says
"FrameKind — the 7 kinds of frame".
contextgraph-types/src/frame.rs has an eighth variant:
/// A kind this revision does not define — most likely one introduced by a
/// later minor version of the same major family. The original wire string
/// is retained verbatim so the frame round-trips unchanged.
Unknown(String),
and FrameKind::from_wire never fails on an unrecognised kind.
This is not a cosmetic omission. Opening the vocabulary is the whole subject of
ADR 0011, which ships in this repository, and it is the breaking change that
drove the crate to 2.0.0. SPEC.md §13 U2 makes it a MUST:
A host that receives an unrecognised kind MUST treat the frame as opaque
evidence — it MAY decline to specialise its handling, but MUST NOT fail
to deserialise, reject, or crash
An implementer who ports the enum as printed writes a receiver that rejects a
kind added in a 1.x minor. That is precisely the flag day the open vocabulary
exists to prevent. The schema file itself carries the warning that the docs do
not — schema/contextgraph-envelope.schema.json's FrameKind $comment says
"AUTHORING LINT, NOT THE INTEROP CONTRACT … On the wire the vocabulary is OPEN".
2. ContextFrame.content is shown as String. It is Option<String>.
README.md and docs/overview.md:
pub content: String, // untrusted data, host quotes it, never executes it
contextgraph-types/src/frame.rs:
pub content: Option<String>,
It has to be optional — a reference frame must omit content entirely, which
ContextFrame::representation_invariants enforces and SPEC.md §P3 requires.
Anyone porting the struct as printed cannot represent a reference frame.
docs/protocol-surface.md gets this one right.
3. The ContextProvider trait reproduced in the provider guide is missing verify
docs/implementing-a-provider.md says "implement the one trait every source
implements" and prints a five-method trait: id, info, capabilities,
query, shutdown.
contextgraph-host/src/provider.rs has a sixth:
async fn verify(&self, request: &VerifyRequest) -> Result<VerifyResponse, HostError> {
with a default unknown-for-everything body. The same page tells you sixty
lines later to implement verify, so the page contradicts itself.
4. The same guide calls the envelope "externally-tagged", and quotes the attribute that makes it internally-tagged
docs/implementing-a-provider.md:
contextgraph-host::wire::Envelope (a serde externally-tagged enum,
#[serde(tag = "type", rename_all = "snake_case")])
#[serde(tag = "…")] is serde's internally-tagged representation — the tag
sits alongside the payload fields, which is exactly what every transcript in
examples/ shows. examples/README.md says "an internally-tagged enum" and is
correct. The sentence refutes its own adjective inside its own parentheses.
Externally-tagged would mean {"query": {…}}. An implementer who believes the
adjective over the example writes the wrong framing.
5. Bonus, and the reason #3 and #4 are worth fixing at the source
The guide's stale capabilities sentence —
Capabilities: which frame kinds and filters this provider serves, whether it
upserts, does graph, is an embedder, or supports subscriptions.
— is copied verbatim from contextgraph-host/src/provider.rs's own doc comment
on capabilities(). Capabilities has exactly seven fields: query,
correlation, graph, embeddings_fingerprint, verify, representations,
resolve. There is no upsert, no subscribe, no filters — ADR 0004 removed
them, and capability.rs says so in a comment a few lines above the struct.
So the code's own doc comment documents a type the code does not have, and the
guide inherited it. Fix the doc comment and the guide's copy together.
docs/protocol-advantages.md §5 carries a third copy of the same stale idea:
pub writes: bool, // persists context/upsert writes. There is no
context/upsert in 1.0; docs/protocol-surface.md states plainly that writes
is "NOT a callable write method — none exists in 1.0 (ADR 0004)".
What I verified
All of it, by reading both sides, on origin/main at a01ca64:
FrameKind's Unknown(String) variant and from_wire in
contextgraph-types/src/frame.rs; the seven-variant listings in README.md,
docs/overview.md, docs/protocol-surface.md and docs/GUIDE.md.
pub content: Option<String> in frame.rs against pub content: String in
the two docs.
async fn verify on ContextProvider in contextgraph-host/src/provider.rs
against the five-method block in docs/implementing-a-provider.md.
#[serde(tag = "type", rename_all = "snake_case")] on Envelope in
contextgraph-host/src/wire.rs.
- The seven
Capabilities fields in contextgraph-types/src/capability.rs, and
the upsert/subscriptions sentence appearing in both provider.rs and the
guide.
Nothing here is inferred.
What "done" looks like
- The
FrameKind block in all four documents shows Unknown(String), or says
in one sentence beside the list that the vocabulary is open and a receiver
must not reject an unknown kind. The second is better — the point is the rule,
not the variant.
ContextFrame.content is Option<String> wherever it is printed, with the
one-line reason (reference frames carry none).
- The guide's trait block includes
verify, and says "externally-tagged" is
wrong — "internally-tagged", matching examples/README.md.
contextgraph-host/src/provider.rs's capabilities() doc comment names the
fields Capabilities actually has. So does the guide's copy, and
docs/protocol-advantages.md's writes comment.
- Also in the guide, while you are in the file: it calls
contextgraph-example-docs.rs "a real, runnable ~150-line stdio provider".
It is 663 lines. Either recount or drop the number — a count in prose is a
promise nobody keeps.
Constraints
- These type blocks are duplicated across
README.md, docs/overview.md,
docs/protocol-surface.md and docs/GUIDE.md, which is why they drifted
apart. If a way to generate or check them against contextgraph-types is
cheap, it is worth more than this round of fixes; #108's pin-checking guard
is the nearest existing pattern.
docs/protocol-surface.md is already correct on content, so use it as the
reference when reconciling.
- Do not change the schema's closed
FrameKind enum. Its $comment explains
why it is deliberately an authoring lint, and that reasoning is sound.
The problem
The pages a provider author reads before writing any code describe a wire
contract we do not have. Four separate errors, all of which would make someone
build the wrong thing. Two of them would make them build something
non-conformant.
1.
FrameKindis shown as a closed set of seven. It is open.README.mdanddocs/overview.mdboth print:docs/protocol-surface.mdprints the same seven variants inside its "Every typebelow lives in that crate" section, and
docs/GUIDE.mdsays"
FrameKind— the 7 kinds of frame".contextgraph-types/src/frame.rshas an eighth variant:and
FrameKind::from_wirenever fails on an unrecognised kind.This is not a cosmetic omission. Opening the vocabulary is the whole subject of
ADR 0011, which ships in this repository, and it is the breaking change that
drove the crate to
2.0.0.SPEC.md§13 U2 makes it a MUST:An implementer who ports the enum as printed writes a receiver that rejects a
kind added in a
1.xminor. That is precisely the flag day the open vocabularyexists to prevent. The schema file itself carries the warning that the docs do
not —
schema/contextgraph-envelope.schema.json'sFrameKind$commentsays"AUTHORING LINT, NOT THE INTEROP CONTRACT … On the wire the vocabulary is OPEN".
2.
ContextFrame.contentis shown asString. It isOption<String>.README.mdanddocs/overview.md:contextgraph-types/src/frame.rs:It has to be optional — a
referenceframe must omitcontententirely, whichContextFrame::representation_invariantsenforces andSPEC.md§P3 requires.Anyone porting the struct as printed cannot represent a
referenceframe.docs/protocol-surface.mdgets this one right.3. The
ContextProvidertrait reproduced in the provider guide is missingverifydocs/implementing-a-provider.mdsays "implement the one trait every sourceimplements" and prints a five-method trait:
id,info,capabilities,query,shutdown.contextgraph-host/src/provider.rshas a sixth:with a default
unknown-for-everything body. The same page tells you sixtylines later to implement verify, so the page contradicts itself.
4. The same guide calls the envelope "externally-tagged", and quotes the attribute that makes it internally-tagged
docs/implementing-a-provider.md:#[serde(tag = "…")]is serde's internally-tagged representation — the tagsits alongside the payload fields, which is exactly what every transcript in
examples/shows.examples/README.mdsays "an internally-tagged enum" and iscorrect. The sentence refutes its own adjective inside its own parentheses.
Externally-tagged would mean
{"query": {…}}. An implementer who believes theadjective over the example writes the wrong framing.
5. Bonus, and the reason #3 and #4 are worth fixing at the source
The guide's stale capabilities sentence —
— is copied verbatim from
contextgraph-host/src/provider.rs's own doc commenton
capabilities().Capabilitieshas exactly seven fields:query,correlation,graph,embeddings_fingerprint,verify,representations,resolve. There is noupsert, nosubscribe, nofilters— ADR 0004 removedthem, and
capability.rssays so in a comment a few lines above the struct.So the code's own doc comment documents a type the code does not have, and the
guide inherited it. Fix the doc comment and the guide's copy together.
docs/protocol-advantages.md§5 carries a third copy of the same stale idea:pub writes: bool, // persists context/upsert writes. There is nocontext/upsertin 1.0;docs/protocol-surface.mdstates plainly thatwritesis "NOT a callable write method — none exists in 1.0 (ADR 0004)".
What I verified
All of it, by reading both sides, on
origin/mainat a01ca64:FrameKind'sUnknown(String)variant andfrom_wireincontextgraph-types/src/frame.rs; the seven-variant listings inREADME.md,docs/overview.md,docs/protocol-surface.mdanddocs/GUIDE.md.pub content: Option<String>inframe.rsagainstpub content: Stringinthe two docs.
async fn verifyonContextProviderincontextgraph-host/src/provider.rsagainst the five-method block in
docs/implementing-a-provider.md.#[serde(tag = "type", rename_all = "snake_case")]onEnvelopeincontextgraph-host/src/wire.rs.Capabilitiesfields incontextgraph-types/src/capability.rs, andthe
upsert/subscriptionssentence appearing in bothprovider.rsand theguide.
Nothing here is inferred.
What "done" looks like
FrameKindblock in all four documents showsUnknown(String), or saysin one sentence beside the list that the vocabulary is open and a receiver
must not reject an unknown kind. The second is better — the point is the rule,
not the variant.
ContextFrame.contentisOption<String>wherever it is printed, with theone-line reason (
referenceframes carry none).verify, and says "externally-tagged" iswrong — "internally-tagged", matching
examples/README.md.contextgraph-host/src/provider.rs'scapabilities()doc comment names thefields
Capabilitiesactually has. So does the guide's copy, anddocs/protocol-advantages.md'swritescomment.contextgraph-example-docs.rs"a real, runnable ~150-line stdio provider".It is 663 lines. Either recount or drop the number — a count in prose is a
promise nobody keeps.
Constraints
README.md,docs/overview.md,docs/protocol-surface.mdanddocs/GUIDE.md, which is why they driftedapart. If a way to generate or check them against
contextgraph-typesischeap, it is worth more than this round of fixes;
#108's pin-checking guardis the nearest existing pattern.
docs/protocol-surface.mdis already correct oncontent, so use it as thereference when reconciling.
FrameKindenum. Its$commentexplainswhy it is deliberately an authoring lint, and that reasoning is sound.