Skip to content

feat(evidence): scope the configuration revision per requirement - #657

Merged
jeremi merged 2 commits into
mainfrom
feat/evidence-per-requirement-revision
Aug 6, 2026
Merged

feat(evidence): scope the configuration revision per requirement#657
jeremi merged 2 commits into
mainfrom
feat/evidence-per-requirement-revision

Conversation

@jeremi

@jeremi jeremi commented Aug 6, 2026

Copy link
Copy Markdown
Member

Why

configurationRevision was one SHA-256 over every file in the acceptance bundle. A relying party pins it, verification compares it by exact equality, and every policy mismatch collapses to one generic VerificationError::Policy. So any byte change anywhere in the bundle invalidated every relying party's pinned value at once, and each of them saw an opaque failure until they had re-reviewed and redeployed.

What changes

An assertion now carries a digest over the configuration and artifact closure of the one requirement it answers.

  • Bundle::revision() stays the deployment's own identity: audit bundle_revision, /status, evidence check, operator diagnostics.
  • Bundle::configuration_revision(requirement_id) is what an assertion carries. Assertion construction, discovery, local verification, and the fixture CLI path all use it. Every call site now declares which of the two it means.
  • Discovery publishes it per EvidenceDefinition instead of once per document, so a relying party pins the requirements it consumes rather than the deployment.

A requirement's closure is its own configuration plus its source, selector profiles, authority grants, and the scripts, schemas, codelists, and fixtures those reach. Retired public verification keys stay in every closure, so key-rollover coverage is unchanged.

The canonical projection starts from serde_json::to_value(&EvidenceConfig) and narrows only the four per-requirement members, so a configuration member added later is covered by default rather than silently omitted. Canonicalization is the shared RFC 8785 implementation (registry_platform_crypto::canonicalize_json), not a bespoke serializer.

Security review notes

  • Assertion construction and verification policy comparison change value, not shape. What the signature covers is unchanged.
  • Tripwire narrowing is intended and is the point of the change: an edit outside a requirement's closure no longer invalidates its pinned revision. Retired public verification keys stay in every closure, so key rollover still moves every revision.
  • The projection is faithful because the configuration types deny unknown fields, so no authored member can sit outside the serialized value it starts from.
  • The digest is domain separated (registry.evidence.requirement-revision/v1\0) and length prefixed per path and per byte string. The projection rides in the closure under evidence.yaml#requirement, and artifact paths cannot contain #, so it cannot collide with a real file.
  • Every currently pinned revision changes once. Relying parties must re-pin from authenticated discovery.
  • Moving the discovery field from the document to each definition is a breaking wire change. Both client SDK types deny unknown fields, so a deployment still publishing it at the document level is refused rather than silently ignored, and a relying party cannot pin a value no assertion carries.

Area boundary

This spans three owning areas on purpose: the frozen-contract amendment (products/), its implementation (crates/), and the reader-facing prose and commands (docs/site/). The application tutorial's pinning script read published["configurationRevision"] at the document level, so leaving it would have documented a procedure that no longer verifies.

Verification

  • cargo fmt --check, cargo clippy --offline --workspace --all-targets -- -D warnings: clean.
  • cargo test --offline --workspace: 188 test result: ok, zero failures.
  • products/evidence/scripts/check-contracts.sh, check-source-neutrality.sh, check-verifier-portability.sh: all pass. Generated artifacts were regenerated by their documented generator, not hand-edited.
  • docs/site: npm test (316 pass) and npm run check (full gate, including the evidence-tutorial drift check, 35973 links).
  • Node binding: npm ci, npm run build:debug, npm test (22 pass), npm run check:types, cmp LICENSE.
  • Python binding: extension-module build, python3 -m unittest discover -s tests/python (33 pass), cmp LICENSE.
  • docs/site/src/content/docs/tutorials/request-evidence-from-an-application.mdx is not in the tutorial replay gate's registered set. Its pinning script was replayed against its own documented discovery sample and reproduces its documented procedure.json byte for byte.
  • The two #[ignore]d live gates in crates/registry-evidencectl/tests/production_handoff.rs now read the expected revision from authenticated discovery, so the assertion check stays independent of the signed payload it is compared against, and assert it differs from the bundle revision.

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: c15bbb7b1c

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread crates/registry-evidence/src/bundle.rs
Comment thread crates/registry-evidence/src/bundle.rs Outdated
Comment thread crates/registry-evidence/src/kernel.rs
Comment thread crates/registry-evidence/src/bundle.rs
jeremi added 2 commits August 6, 2026 18:05
`configurationRevision` was one digest over the whole acceptance bundle, so any
byte change anywhere invalidated every relying party's pinned value at once.
Verification compares it by exact equality and reports one generic policy
mismatch, so an unrelated edit broke every consumer with an opaque failure
until each of them re-reviewed and redeployed.

An assertion now carries a digest over the configuration and artifact closure
of the one requirement it answers. The deployment keeps its own bundle revision
for audit, status, `evidence check`, and operator diagnostics; the two are now
distinct at every call site. Discovery publishes the value per definition
instead of per document, so a relying party pins the requirements it consumes.

The projection starts from the whole serialized configuration and narrows only
the four per-requirement members, so a configuration member added later is
covered by default rather than silently omitted. Canonicalization is the
shared RFC 8785 implementation.

Security review notes:

- Assertion construction and verification policy comparison change value, not
  shape. What the signature covers is unchanged.
- Tripwire narrowing is intended and is the point of the change: an edit
  outside a requirement's closure no longer invalidates its pinned revision.
  Retired public verification keys stay in every requirement's closure, so key
  rollover still moves every revision.
- The projection is faithful because the configuration types deny unknown
  fields, so no authored member can sit outside the serialized value it starts
  from.
- The digest is domain separated and length prefixed per path and per byte
  string. The projection rides in the closure under `evidence.yaml#requirement`
  and artifact paths cannot contain `#`, so it cannot collide with a real file.
- Every currently pinned revision changes once. Relying parties must re-pin
  from authenticated discovery.
- Moving the discovery field from the document to each definition is a breaking
  wire change. Both types deny unknown fields, so a deployment still publishing
  it at the document level is refused rather than silently ignored, and a
  relying party cannot pin a value no assertion carries.

This change spans `crates/`, `products/`, and `docs/site/`: a frozen-contract
amendment, its implementation, and the reader-facing prose and commands that
would otherwise document the wrong scope. The application tutorial's pinning
script read the document-level field, so it would have produced a procedure
that no longer verifies.

Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
@jeremi
jeremi force-pushed the feat/evidence-per-requirement-revision branch from c15bbb7 to 62878c2 Compare August 6, 2026 11:14
@jeremi
jeremi merged commit 2bd9d61 into main Aug 6, 2026
31 checks passed
@jeremi
jeremi deleted the feat/evidence-per-requirement-revision branch August 6, 2026 11:25
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