Repository navigation
feat(evidence): add a public certificate check and map where each rule lives - #2606
Merged
Merged
Conversation
Codex Review Summary: ✅ CompletedThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 5a7aecc005
ℹ️ 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".
jeremi
enabled auto-merge
November 8, 2026 03:08
…ing model Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
…l on activation Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
…plan cannot read stored records Also rewraps the BReg action-plan constraint to the file's width. Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
…oute Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
…is never styled valid Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
…e authority A printed certificate carries a document number and a check code. The runtime now answers one such pair from the active target authority: it selects the record through the activated lookup, compares the stored code in constant time, and returns only the activated status and printed fields. Every miss, whatever its cause, collapses to one not-found outcome. Each check writes an access attempt before the source read and a disclosure release before the answer, under the registry.evidence.audit.public-check/v1 entry. The entry records no presented value, no caller, and no requester pseudonym. Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
POST /v1/public-check is the first unauthenticated route that reads registry data, so it is registered only when the active target authority activates a check. Without one the path answers exactly like any unknown route. The route reads one request header, Content-Type, and a bounded body. It mints its own trace instead of continuing an inbound traceparent, shares the evaluation slots and request timeout of the relying routes, and leaves rate limiting to the fronting proxy. Any origin may read its responses, the preflight allows only POST with Content-Type, and no other route carries a CORS header. Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
The request and answer schemas are hand-written YAML contracts, served unchanged and generated into JSON. The OpenAPI document describes the check and its preflight as experimental and unauthenticated, with every miss as one 404, no-store on every response, and a trace the service mints rather than reads. Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
The route tests run a compiled certificate authority against a mock Source. They prove an unactivated check answers like an unknown route, every miss is one identical 404, a refused request reaches neither the Source nor the audit, the audit brackets the Source read and the answer, an audit or Source failure is a 503 and never a miss, only this route is readable cross-origin, and no request header but Content-Type reaches the logs or the audit. Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
…dit record Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
…te claims Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
…inition Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
…ath cannot carry Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
…ppears in the audit Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
…eta is missing Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
…ommand and a real binary Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
…angelog entries Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
…a keys Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
…e route cannot serve A value the Source inputs schema refuses is a malformed request on the public route. Startup refuses a status codelist outside the four statuses and a check code fact that is not a string fact of the acquiring Source or that a Source input binds, and the answer refuses any other status. Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
…author rule Describe the two selector refusals, the before-comparison existence residual and the startup checks in the contracts, the concept, the operator contract and the site. Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
… a record exists The facts schema declares types only, the lookup treats a non-matching record as no match, and the derivation checks record content after the code comparison. Repackage the reference and test more misses. Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
The page removes the number and code from the address bar and history, ignores answers to superseded requests, and has its own message for a rate limit. Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
The evidence-tutorials job runs the page tests, and a change under the example path triggers it. Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
…ilures before the code comparison Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
jeremi
force-pushed
the
feat/evidence-public-check
branch
from
November 8, 2026 03:26
2cbfa2e to
9c1267e
Compare
…present as text Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
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.
Adds an experimental public certificate check to Evidence, and publishes an explanation of where each kind of rule lives across the products.
Public certificate check
A holder, or anyone the holder shows a paper certificate to, scans a QR code and sees whether that certificate is currently valid, suspended, revoked or expired, with the fields printed on it. No account is needed.
Authoring and activation
publicCheck(checkCodeFact,statusOutput,fieldsOutput).publicCheck.definition. Without an activation the route does not exist and answers like any unknown path.evidencectl checkand package loading refuse:integerorboolean, since the route presents the number as text (startup refuses this too);existenceDisclosureother thancollapse-unresolved;valid,suspended,revokedandexpired, and a check code fact that is not a string fact of the acquiring Source or that a Source input binds.Route
POST /v1/public-checktakes{documentNumber, checkCode}. The code is 24 characters of base32; the server uppercases it before comparing.Authorizationheader and no request header exceptContent-Type. The trace id is minted by the server.registry.evidence.public-check/v1alpha1. The answer is unsigned, and its schema enumerates the four statuses; the answer refuses any other status with503 service.unavailable.404 resource.not-found.400before any audit entry or registry read. That includes a number the selector profile accepts but the Source's inputs schema refuses, which answers exactly as a profile refusal.503 source.unavailable.*. The route carries no cookie or token, so the wildcard grants nothing a plain HTTP client lacks.Audit
registry.evidence.audit.public-check/v1. Neither holds the document number or the check code.evidencectl audit showand the local audit view list these entries.Operator duties (documented, not enforced)
POSTandOPTIONSon/v1/public-checkthrough a path-restricted proxy, limits each client and the whole path with a429answer, keeps no client address in the access or error log for that location, and terminates TLS on the printed check origin.Writing a pack for a public check
503reveal that a record exists for that number, though neither its status nor its fields. A register that answers an error status for one record but not for an unknown number has the same effect.400(not in the expected format) rather than503(unavailable), and to bind a number that can contain/to a query parameter.Example and deployment reference
products/evidence/examples/public-check/: project, check-only target, authority, derive function, printed-fields schema, a synthetic Adapter pack, fixtures for each status plus a record issued before check codes existed and a record with an unknown status, a dependency-free static page, and a code generation recipe.429.products/evidence/reference/request-adapter/deployment-projects/public-check-evidence/is the packaged form of the example. A test fails if it drifts from the example.evidence-tutorialsjob, which the example path now triggers.Contract correction: selector values a Source cannot carry
The problem contract, the security invariant matrix (V1-I56) and the source contract describe two ways the runtime refuses a selector value that passes its profile but that a Source cannot carry. Neither reads a credential or sends a source request.
400 request.selector-invalidon the public check route, and503 service.unavailable(categorysource-inputs) on the relying routes.503 source.unavailable, audited as a dependency failure. On the relying routes it does not count against the caller's selector-failure budget.Adapter pack routes compute path values in route scripts, which Evidence cannot see during selector resolution, so the second refusal happens inside source execution. The text previously described a selector-time refusal.
Where each rule lives
docs/site: a new explanation page maps each kind of rule (field shape, Action, eligibility, evidence question, workflow, rendering, scheduling, and so on) to the product and file that owns it, with examples. BReg docs now name the Action and hook ABIs the code accepts.Render
The Render docs and the App Kit integration guidance now point printed QR checks at the Evidence route instead of an app-owned route.
Checks
Run on macOS against this branch, rebased on current
main:cargo fmt --checkfor the touched crates: clean.cargo clippy --locked -p registry-evidence -p registry-evidencectl -p registry-evidence-authoring --all-targets -- -D warnings: clean.cargo test --locked:registry-evidence1221 passed, 8 ignored;registry-evidencectl1000 passed, 15 ignored;registry-evidence-authoring226 passed.public_checkwithEVIDENCE_BINand--include-ignored: 2 passed.check-contracts.sh(includingsecurity_contract_traceabilityand generated-contract reproduction),check-authoring-schema.sh,check-authoring-no-io.sh,check-config-key-paths.sh,check-source-neutrality.sh: all pass.check-config-conventions.py --base origin/main: 0 errors, 0 unrecorded findings.products/identifiers/scripts/check.sh: the catalog is complete and reproducible.node --teston the page: 20 of 20 pass.docs/site:npm test949 of 949 pass;npm run checkpasses (llms check, SEO and the built link check).429message, a form miss, malformed input, and a missing endpoint meta (unavailable message, no request sent).What the end-to-end tests exercise
evidence evaluateafterevidence authority activate, including the unknown-status case, which fails closed.evidencectl, activates the authority and startsevidence serve. A loopback thread stands in for the register and the issuer key endpoint.traceId.evidencectl audit showend to end. It reads only a stoppeddev startsession, which needs an issuer container and registered clients that a check-only target does not have. Its rendering of these entries is unit-tested.Known limits
404would make a genuine certificate read as unknown during a register fault, so the runtime keeps them as503.503 service.unavailable(source-inputs); only the public route treats it as a malformed request.evidencectl testresolves each fixture through an access profile and has no fallback foraccessProfiles: {}. The example authority therefore carries one relying-service profile. A deployed check-only instance that runs no fixtures can useaccessProfiles: {}.localassurance profile with loopback hosts, because it is the example's own package. A deployment packages its own target with a deployable profile and HTTPS hosts.v1alpha1).