Skip to content

Our own docs would lead a provider author to build a non-conformant provider #151

Description

@macanderson

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    P1This cycle

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions