Skip to content

Three types named FrameAttestation, and two wire places to put the same attestation #161

Description

@macanderson

Problem

There are three types called FrameAttestation in this workspace, all modelling "a provenance attestation bound to a frame", and the wire now has two places to put the same data.

Definition Shape Came from
contextgraph-types/src/attest.rs frame: FrameId, attestation: Option<..>, inclusion_proof: Option<..> #90 / PR #138 — the canonical wire type
contextgraph-host/src/trust.rs frame_id: String, attestation: ProvenanceAttestation #88 / PR #140
contextgraph-host/src/wire.rs frame_id: String, attestation: ProvenanceAttestation PR #154 (open)

contextgraph-host/src/lib.rs re-exports two of them under one name, which is an E0252 build error the moment PR #154 rebases onto current main.

The wire-level duplication is the serious half

Envelope::Frames is { id, result: ContextQueryResult, attestations: Vec<FrameAttestation> }, and ContextQueryResult already carries frame_attestations and result_attestation (added by PR #138 for #90). So a signed answer can be expressed two ways in one envelope, and nothing says which wins when they disagree. For a protocol whose pitch is "enforced by contract, not convention", two encodings of one fact with no tie-breaking rule is exactly the defect it exists to prevent.

ContextQueryResult is also the right home on the merits, and ADR 0014 says why: an in-process provider that never builds an Envelope must still be able to sign what it serves.

attester_keys needs a decision, not a merge

PR #154 also adds attester_keys: Vec<AttesterKey> to Envelope::HandshakeAck. ADR 0016 (merged in PR #140) decided the opposite: a host learns a provider's signing key from the operator, ssh known_hosts-style, and the protocol specifies no PKI. A key self-asserted in handshake_ack proves continuity, not identity — which is precisely why #130 was filed as "no trust-on-first-use tier: handshake_ack carries no public key, so a host cannot pin one", as an open question rather than a shipped field.

So attester_keys is not a merge conflict to resolve. It is a protocol addition that contradicts a just-merged ADR, and it needs either its own ADR arguing the TOFU tier, or removal.

Why this happened

Three agents worked #88, #90 and #89 in parallel off the same base. Each needed somewhere to put an attestation, and none could see the others' answer. #140's own doc comment predicted it:

This shape lives here for now. ... Carrying attestations on the wire is issue #90; when that lands, this becomes the host-side view of a wire field rather than the only source of one.

#90 has landed. The prediction came true and the collapse never happened.

What to do

  1. One type. contextgraph_types::FrameAttestation is canonical. Delete wire.rs's and trust.rs's, re-export the canonical one from contextgraph-host, and update call sites — trust.rs moves from frame_id: String to frame: FrameId and must handle attestation: Option<..>, since a frame attested only through a Merkle inclusion proof carries no per-frame signature.
  2. One wire location. Remove Envelope::Frames.attestations; the carriage is result.frame_attestations / result.result_attestation.
  3. Decide attester_keys separately under No trust-on-first-use tier: handshake_ack carries no public key, so a host cannot pin one #130. Do not land it as a side effect of a conformance PR.

Files

Verify

  • command grep -rn "pub struct FrameAttestation" contextgraph-*/src/ returns exactly one hit.
  • cargo clippy -p contextgraph-host --all-targets --all-features -- -D warnings is clean (today it is E0252).
  • An attested frames envelope round-trips with the attestations in exactly one place, and schema/validate-examples.py passes.

Done when

One FrameAttestation, one wire location for an attestation, and attester_keys either justified by an ADR or absent.

Blocks PR #154 (#89), whose conformance work is otherwise complete and green — the five --misbehave modes and the F9 test all pass.

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