feat(evidence): scope the configuration revision per requirement - #657
Merged
Conversation
There was a problem hiding this comment.
💡 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".
`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
force-pushed
the
feat/evidence-per-requirement-revision
branch
from
August 6, 2026 11:14
c15bbb7 to
62878c2
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
configurationRevisionwas 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 genericVerificationError::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: auditbundle_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.EvidenceDefinitioninstead 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
registry.evidence.requirement-revision/v1\0) and length prefixed per path and per byte string. The projection rides in the closure underevidence.yaml#requirement, and artifact paths cannot contain#, so it cannot collide with a real file.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 readpublished["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: 188test 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) andnpm run check(full gate, including the evidence-tutorial drift check, 35973 links).npm ci,npm run build:debug,npm test(22 pass),npm run check:types,cmp LICENSE.python3 -m unittest discover -s tests/python(33 pass),cmp LICENSE.docs/site/src/content/docs/tutorials/request-evidence-from-an-application.mdxis not in the tutorial replay gate's registered set. Its pinning script was replayed against its own documented discovery sample and reproduces its documentedprocedure.jsonbyte for byte.#[ignore]d live gates incrates/registry-evidencectl/tests/production_handoff.rsnow 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.