Skip to content

Wire attestation verification into host composition #88

Description

@macanderson

PR #87 adds contextgraph_types::attest (SPEC.md §6.5) but nothing in contextgraph-host consumes it: a frame arriving with a valid ProvenanceAttestation is treated exactly like one arriving with none.

Needed:

  • Verify attestations during query_provider / fan-out, against a host-held trust store.
  • Surface the outcome in CompositionAudit / VerificationState so a composed prompt can distinguish attested from unattested evidence.
  • Honour F9: an unverifiable attestation degrades a frame to unattested, it never disqualifies it. A host that dropped such frames would hand any peer a DoS primitive — attach a malformed attestation, watch the evidence vanish.

Blocked on the trust-root question (separate issue): the host needs some way to learn a provider public key before it can verify anything.

Definition of done

  • contextgraph-host verifies ProvenanceAttestations on the fan-out path (query_all, query_all_budgeted) and on the single-provider door (query_provider_attested).
  • Verification is against a host-held trust store defined by the ADR from Key distribution and trust roots for provenance attestation #91contextgraph_host::TrustStore, keyed by (provider_id, key_id), populated only by the operator.
  • The outcome reaches CompositionAudit, so a composed prompt can distinguish attested from unattested evidence — AuditEntry::attestation, present on every entry, included and excluded alike.
  • The surfaced state distinguishes "no key known for this provider" from "key known, signature bad"; "no check performed", "nothing offered", "unrecognised algorithm" and each malformed case are named separately too.
  • F9: a test proves a frame carrying a garbage attestation is still served and still composed into the prompt, marked unattested — a_frame_carrying_a_garbage_attestation_is_still_served_marked_unattested.
  • A test covers each outcome through the real fan-out: verified, no-key-known, bad-signature, malformed, unknown-algorithm — every_verification_outcome_reaches_the_audit_with_its_own_name.
  • A witness test fails without the change and passes with it, checked by stashing the change and re-running.
  • A malformed attestation cannot panic, allocate unboundedly, or cost more than a bounded amount of CPU per frame — length checks precede every hex decode, at most one attestation is verified per frame, and no more than frames.len() offers are scanned. Tests cover an oversized signature and an oversized identifier.
  • Verification changes neither which frames are selected nor their order — verification_changes_neither_selection_nor_order compares the composed bytes and the audit order with and without a trust store.
  • cargo fmt --check, cargo clippy --all-targets -D warnings and the crate's tests are green in CI.
  • CHANGELOG.md records the change under [Unreleased], including the Rust-semver break, and docs/composing-frames-into-a-prompt.md documents the new audit field.

Metadata

Metadata

Assignees

No one assigned

    Labels

    triageAwaiting the triage agent: sizing + priority (SCR-005)

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions