Skip to content

Commit 66f4f98

Browse files
authored
chore: retire Registry Notary and make Evidence the documented product (#644)
* style(evidence): expand remaining inline YAML in the reference bundle Finishes the reformatting the reference deployment bundle already started: the last flow-style mappings become block mappings, so selector fields, fixed headers, and selector alternatives are edited and diffed a line at a time. Verified value-identical: the parsed document is unchanged. Signed-off-by: Jeremi Joslin <jeremi@joslin.fr> * perf(evidence): commit audit appends in groups to sustain 1000 req/s Measured audit throughput was about 270 appends/second and flat from 1 to 128 concurrent appenders: every append took one fsync under a held mutex, at 3.70 ms each. At two audit records per request that capped the service near 135 requests/second regardless of hardware or concurrency. Security review note, required for a change to audit integrity. What changed Chain-head ownership moves from registry_platform_audit::ChainState into Evidence's DurableJsonlSink. ChainState::append held its mutex across sink.write(), which serialized every append whatever the sink did internally. The on-disk chain is byte-identical: same keyed hasher, same envelope. A two-lock split replaces the single mutex. A short state lock claims a chain position and advances the head, performing no I/O; a separate flush lock is held across the blocking fsync. Appends arriving during a write form the next batch. Group commit is leader-follower with no timer and no configured window: the first caller to arrive while no write is in flight writes everything queued so far, and the others wait on the flush lock to find their records already durable. Batch size is 1 at idle and grows by itself under load. Segments rotate at auditStorage.maximumFileBytes with chain continuity across the seam, and startup verification is now bounded to the active segment, so restart time no longer grows with retained history. Properties preserved Chain positions are handed out in enqueue order under one lock, so concurrent appends extend one chain rather than forking it. Nothing is reported durable before its own bytes are synced. A failed durable write leaves the in-memory head ahead of the disk, so the sink poisons itself and refuses every later append for the life of the process rather than chaining onto a record the disk never received; concurrent waiters on a poisoned batch each receive the failure instead of hanging. A batch crossing a segment bound flushes the buffered run into the outgoing segment before sealing. Pinned-writer identity is still validated before the append and again after the sync, for both the segment and the lock file. Defect found and fixed during review The first version of the split read the recorded fingerprint under the state lock but compared it after releasing the lock, and acquired that lock with a non-blocking try_lock. Both were wrong under load: the service's own appends changed the file between snapshot and comparison, so readiness read its own traffic as external mutation, and try_lock lost to writer contention, which is busyness rather than ill health. Measured 195 of 200 probes unready while the service wrote its own audit records, which would have taken a healthy service out of a load balancer rotation under sustained load. The fingerprint is now compared under the same lock the writer advances it under, and only while the sink is quiescent. Tamper detection is unchanged. Accepted risk Startup verification is bounded to the active segment, so tampering inside an already sealed segment is not caught at boot. The new `evidence verify-audit` command is the out-of-band counterpart that does catch it. It reads the audit path and hash secret from the deployment's own runtime document and takes no path or secret flags, so it can neither be aimed at a foreign chain nor take a secret on a command line. It reports a missing sealed segment as archived history rather than as corruption, so deliberate archival stays distinguishable from tampering. Cross-crate change AuditEnvelope::new_with_hasher in registry-platform-audit becomes public. Visibility only, no behavior change. The alternative was a second implementation of the chain hash inside Evidence, and two implementations of a security-critical hash chain diverge eventually. Notary shares the crate and is unaffected; its tests pass unchanged. Result Audit appends, same host, before and after: 270/s to 322/s at 1 concurrent appender, ~270/s to 1,253/s at 8, to 4,245/s at 32, and to 12,864/s at 128. End to end over real sockets through the whole request path, including token verification, rate limiting, Rhai preparation, one source call, Rhai extraction, evidence construction, Ed25519 signing, and both audit appends: 6,976 to 7,057 requests/second across two runs at 128 in flight, p50 17.8 ms, zero non-2xx. The in-process source's own ceiling is measured in the same run and the check reports the run inconclusive below a 5x margin; it measured 20.8x. The request path also gains structured request logging, a correlation header, and an opt-in metrics listener, and the audit and rate-limiter capacity gauges that make the ceilings above observable. They land here rather than separately because the gauges read storage state this change introduces. The traceability checker now accepts #[tokio::test(flavor = "multi_thread")] as a test item. A concurrency invariant cannot be proven by a single-threaded test, so the stricter form meant leaving these invariants untraced. Signed-off-by: Jeremi Joslin <jeremi@joslin.fr> * fix(evidence): validate deployment secret material in check evidence check accepted a deployment whose mounted secrets the server refuses at startup, so a signing key whose kid does not match the bundle's signing.activeKeyId passed check and then failed every start. Check now resolves and validates the audit, subject-binding, and signing material exactly as startup does, without opening the audit chain; source credentials stay unresolved because readiness owns them. Security review notes: startup validation is extracted into validate_secret_material, not altered; check gains read-only secret resolution with the same fixed value-free operator messages; no secret bytes reach output; startup error-class precedence now reports subject-binding and signing faults before audit chain faults when both are broken. Signed-off-by: Jeremi Joslin <jeremi@joslin.fr> * feat(evidence): add a compose deployment for the adopter images A static reference docker-compose for the evidence service and the optional mint pairing: static private addresses on a user-defined network because the listener refuses wildcard binds, the deployment project mounted read-only with a container-shaped runtime file overlaid on the project's host-shaped one, and the audit chain in a named volume. The files are adopter-owned after copying; there is no generation contract. Verified end to end against the locally built images: check, both health endpoints, and audit persistence across a restart. Signed-off-by: Jeremi Joslin <jeremi@joslin.fr> * docs(evidence): route compose traffic on readiness, not liveness Signed-off-by: Jeremi Joslin <jeremi@joslin.fr> * feat: add distroless adopter and development images for mint and evidence One multi-stage Dockerfile built from the repository root, with a cargo-chef recipe per binary so dependency compilation is a cacheable layer and an unrelated workspace manifest change invalidates neither image. Both images run as the distroless nonroot user with no shell. These are not release evidence; released images keep following the release/docker reproducible-build path. Signed-off-by: Jeremi Joslin <jeremi@joslin.fr> * feat(evidence): declare source response shapes and add get_path Every source now carries a required responseSchema: a closed JSON Schema for the projected response, validated in Rust after projection and before the response reaches an extraction script. A response outside the shape its adapter was reviewed against is a source-protocol failure and no script runs, so hand-written protocol checking is no longer the only thing between a malformed response and fact construction. The response role relaxes two rules the fact and adapter-parameter roles keep, because the projected tree is not the wire response. It may require fewer members than it declares properties, since projection drops a leaf the record did not carry and an ambiguous page is decided before any record is read. A node may also write its type as the pair [T, "null"], because a source that reports an explicit null has that null carried through projection verbatim; it reaches the script as the same unit marker is_missing already reads. That pair is the only union the subset admits and only in this role. get_path resolves one bounded RFC 6901 pointer and answers with the missing marker, replacing chains of contains and type_of in the migrated adapters. A pointer that is not resolvable syntax, exceeds 256 bytes, or exceeds 16 segments is a script fault and fails the invocation rather than answering missing. Security-sensitive: this narrows what reaches bounded script execution and adds one enforcement point before extraction. It changes no authentication, authorization, signing, audit, or disclosure behaviour, and every source protocol failure stays publicly indistinguishable. Tracked as the source_response_shape invariant with a bound negative test. Signed-off-by: Jeremi Joslin <jeremi@joslin.fr> * docs: add Notary retirement and Evidence onboarding plan, DoD, and /goal Track the approved consolidation plan as a repo-level artifact: retire Notary, keep Relay, land Evidence onboarding with CI-verified tutorials, and prove the Evidence-over-Relay composition. The /goal command drives execution one DoD item at a time and keeps the plan's checklist and status log current. Narrow the .claude/ ignore so only the shared project commands directory is tracked. Signed-off-by: Jeremi Joslin <jeremi@joslin.fr> * feat(evidence): add a curl | bash installer for the Evidence toolset evidencectl gains an install.sh mirroring the registryctl installer conventions: it installs evidence, evidencectl, and mint together or not at all, verifies every release asset against SHA256SUMS before anything reaches the install directory, refuses unpinned or non-canonical release tags, and supports an offline EVIDENCECTL_ASSET_DIR mode for higher-assurance installs after release/VERIFY.md. Eleven offline bash tests drive the script through fake curl and uname binaries; shellcheck and shfmt clean. Ticks F1 in plans/notary-retirement-and-evidence-onboarding.md. Review notes: distribution tooling that verifies release artifacts. It performs integrity verification only (SHA256SUMS) and states so; authenticity verification remains the documented release/VERIFY.md path. Failed or interrupted installs roll back to the previous toolset. No production code changes. Signed-off-by: Jeremi Joslin <jeremi@joslin.fr> * feat(release): ship the Evidence toolset through the release channel The reproducible builder now builds evidence, evidencectl, and mint and checksums them into dist/bin like the existing binaries. The candidate workflow stages evidencectl-<tag>-install.sh beside the registryctl installer. The registry-release artifact inventory accepts the three binaries and the installer as optional artifacts, with per-platform optional backfill names and an installer filename guard; two unit tests cover acceptance and continued rejection of unknown artifacts. Ticks F2 in plans/notary-retirement-and-evidence-onboarding.md. Review notes: release provenance change. The new artifacts are optional in the inventory so every historical manifest still validates (registry-stack-beta-26.yaml re-validated); they flip to required at a minimum version only after the first release ships them end to end (plan item F3), the way registryctl-installer did at v0.14.0. The builder image, cargo flags, and checksum flow are unchanged; the new binaries follow the exact existing pattern. Verified: full test_registry_release run (only pre-existing rust-result shard failure, flagged separately), test_release_candidate, test_release_workflow_ structure, test_release_workflow_guard, test_check_release_source_model, manifest validation, source-model check, bash -n, shellcheck. Signed-off-by: Jeremi Joslin <jeremi@joslin.fr> * docs(evidence): document how adopters install the Evidence toolset The product README explains the release-asset installer (pinned tag, three binaries together or not at all, SHA256SUMS integrity check, the release/VERIFY.md path plus EVIDENCECTL_ASSET_DIR for authenticity), its environment variables, and the source build alternative. Signed-off-by: Jeremi Joslin <jeremi@joslin.fr> * docs: record that root CI already drift-checks the Evidence OpenAPI Ticks B2 in the plan: the evidence-contracts CI job regenerates every Evidence contract, the OpenAPI document included, and byte-diffs the committed products/evidence/generated/ artifacts, triggered for crate and product-path changes alike. No new gate needed. Signed-off-by: Jeremi Joslin <jeremi@joslin.fr> * fix(evidence): stop response schemas requiring leaves projection may drop A response schema that required a projected leaf of every record turned an ordinary ambiguous page into a source-protocol failure: the page is refused from the pager before any record is read, so a record on it need not be complete. Relax the record shapes to require nothing, move the presence checks a single matched record must pass into the extract scripts, and add fixture cases covering both sides. Projection guarantees intermediate containers and only drops terminal leaves, so requiring an intermediate is redundant and guarding one in script is unreachable; state that rule in the source contract's response_shape. Reported by the tutorial writer as finding 23. Signed-off-by: Jeremi Joslin <jeremi@joslin.fr> * feat(release): build the Evidence toolset for the optional platforms The candidate workflow's native build-platforms matrix (macos-arm64, linux-arm64) now builds evidence, evidencectl, and mint beside registryctl and stages them into the platform payload. The assemble step and the artifact inventory accept the per-platform names generically, so no other step changes. Ticks F4 in plans/notary-retirement-and-evidence-onboarding.md: linux-amd64 stays the required reproducible baseline; linux-arm64 and macos-arm64 are optional, matching registryctl. Review notes: release provenance change scoped to the existing native matrix job; toolchain pin, targets, and upload flow unchanged. The registryctl version-verification step stays registryctl-only. Verified: test_release_candidate, test_release_workflow_structure, test_release_workflow_guard (63 tests), YAML parse. Signed-off-by: Jeremi Joslin <jeremi@joslin.fr> * docs(evidence): record group commit as implemented in PERFORMANCE.md The file still described group commit as deferred work and presented the pre-batching 122-161 rps runs as the current baseline, while audit.rs commits appends in groups and OPERATOR-CONTRACT.md records the sustained 7057 requests/second measurement. The baseline rows stay as the labeled before-figure; the group-commit section now states the implemented behavior and its preserved properties, and points at the OPERATOR-CONTRACT measurement. The Linux re-measure caveat stays. Signed-off-by: Jeremi Joslin <jeremi@joslin.fr> * docs(site): add the Evidence onboarding content surface New pages: the RS-PR-EVIDENCE protocol profile (56 requirements, each citing its frozen contract file), the first-assertion tutorial (E1, commands and outputs verified against the built binaries), Configure Evidence, Evidence security model (every invariant-matrix id covered and traced to named tests), Evaluate Evidence, the Evidence problems reference (all nine public problem types), and Registry Mint configure and reference pages. The when-to-use page gains the Evidence door and the four adopter personas; the glossary disambiguates Evidence (the product), Notary evidence credentials, the Evidence toolset, and Registry Mint. Signed-off-by: Jeremi Joslin <jeremi@joslin.fr> * docs(evidence): quote the fixture placeholder in the operator contract The docs site's shell-fence gate parses every sh fence with /bin/sh -n once the operator contract is mirrored as a product page; the bare <path> placeholder reads as a redirection there. Quoting it keeps the placeholder convention and parses. Signed-off-by: Jeremi Joslin <jeremi@joslin.fr> * docs(site): wire the Registry Evidence product into the site plumbing repo-docs.yaml mirrors seven Evidence product pages from products/evidence at the current ref, the latest docset registers the product, and the OpenAPI pipeline pulls the generated registry-evidence.openapi.json (drift-checked in root CI) into a Redoc operations section beside a new narrative API page. The sidebar gains the Answer with Evidence flow, the Evidence security, problems, Mint, evaluate, and RS-PR-EVIDENCE entries, and a Registry Evidence product group. Archived docsets predate Evidence, so sync and fetch now filter docset-excluded docs before requiring a docset product pin, a repo absent from an archived docset keeps its current ref and rides the current shell (verified against v0.15.2: the Evidence throw is gone; that docset's remaining sync failure is a pre-existing Notary allowlist gap on main, flagged separately), and the Evidence product group splices optionally while generate-sidebar.test.mjs pins its presence for the current docset. Site suite: 267 tests, 0 failures; redocly lint clean for the new spec. Signed-off-by: Jeremi Joslin <jeremi@joslin.fr> * docs: tick B1, B3, B7, F6, F7 and record the docs-wave status The plan's status log records the archived-docset design decision, the partial B5/B8/F5 state, the pre-existing failures flagged out of scope, and the next unblocked items (B4 gate, E6, E2-E5). Signed-off-by: Jeremi Joslin <jeremi@joslin.fr> * docs(evidence): use straight quotes in the operator contract lead The Vale gate on the mirrored product page rejects typographic quotes. Signed-off-by: Jeremi Joslin <jeremi@joslin.fr> * fix(evidence): remove eight adopter frictions found writing the tutorial A tutorial writer took the Mint and Evidence scaffold end to end and recorded where the tooling misled or under-informed them. Each fix below closes one of those: - Bundle immutability errors name the failing path, not just the rule. - `evidence serve` announces a start only once both listeners are held, and a bind failure names the address and the cause. - A rejected source response shape logs which member failed which schema rule. - The bounded-integer rejection states the whole rule it enforces. - `evidencectl new --port` chooses the Evidence listener port, and refuses the port a paired `--with-mint` deployment binds. - `--with-mint` keeps the example caller's key at project root: the caller is a different party from the issuer that registers it. - `evidencectl fixtures run` reports the cases evaluated beside the artifact counts, which are not a coverage figure. - New `evidencectl keygen token` writes a printable, header-safe bearer token. `keygen secret` makes HMAC key material whose raw bytes an HTTP header rejects; its help and the scaffold's printed steps now say so. Security review notes: - `keygen token` writes 32 bytes of `getrandom` entropy as base64url without padding, owner-only (0600), with no trailing newline because the runtime reads the file whole. The value is never printed, and a test asserts it reaches neither stream. - The response-shape rejection log is built from the violation's instance and schema pointers only. The library's own message embeds the offending value and is deliberately unused; a test plants a secret in a rejected response and asserts it never appears. - The bind failure message carries the configured address and the operating system's reason, both already operator-supplied or operator-visible. No secret reaches it. Signed-off-by: Jeremi Joslin <jeremi@joslin.fr> * feat(docs): add the Evidence tutorial gate check-evidence-tutorials.sh extracts the first-assertion tutorial's shell fences, drift-checks the fence count and required command and output literals, then replays the on-machine journey (scaffold, key generation, the immutability freeze, the fixtures run, cleanup) against toolset binaries built from source or supplied via EVIDENCE_BIN and EVIDENCECTL_BIN. The release-download fences wait for a release that ships the toolset (plan item F3). The dry-run drift check joins npm run check; three node tests cover the pass case and both drift failures. Verified end to end locally: the tutorial executes verbatim and reports 2 passed, 0 failed. Signed-off-by: Jeremi Joslin <jeremi@joslin.fr> * feat(ci): run the Evidence tutorials from a clean container A path-gated evidence-tutorials job tests the gate helpers, checks command drift, builds the toolset with the ci profile, and executes the tutorial inside the repository's pinned builder-image digest with the repo mounted read-only and the prebuilt binaries injected, so the run sees only a clean Debian userland plus the toolset. The classifier gains an evidence_tutorial output firing on the gate's inputs and on registry-evidence and registry-evidencectl changes, with routing tests, and the required-results job counts the new job. actionlint clean; classifier suite 29 tests green. Signed-off-by: Jeremi Joslin <jeremi@joslin.fr> * docs(site): add the move-Evidence-to-production-signing tutorial Tutorial E6: production key generation with the kid-must-match rule, JWKS assembly with the retired-key retention window, offline validation with evidence check and fixture evaluation, serve and readiness confirmation, the startup-only rotation procedure, the limits of what a signature proves, and troubleshooting. Every rule restates products/evidence/OPERATOR-CONTRACT.md; commands verified against the evidencectl CLI. Site suite 270 tests green; Vale clean. Signed-off-by: Jeremi Joslin <jeremi@joslin.fr> * docs: tick B4 and B6 and record the tutorial-gate design The status log records the gate's container caveat, the F3 dependency for the release-download fences, and that extending the executable gate to further tutorials belongs to B5. Signed-off-by: Jeremi Joslin <jeremi@joslin.fr> * docs: record the A1 token-topology outcome Relay embeds no token issuance and verifies inbound callers through configured OIDC with EdDSA support; Mint issues EdDSA tokens and serves a JWKS, so IdP-less Evidence-over-Relay is Relay verifier configuration pointed at Mint's issuer, and the reserved Mint-for-Relay code branch is not triggered. registry-platform-sts turns out to have zero consumers and joins the C4 orphan candidates. The Mint claim-set question moves to A2 and D. Signed-off-by: Jeremi Joslin <jeremi@joslin.fr> * fix(evidencectl): keep zero bytes out of test-provisioned secrets The scaffold test wrote 32 raw random bytes per secret file, and the runtime rejects secret material containing a NUL byte, so each file failed ~12% of the time and a full run roughly 40%. Regenerate until no byte is zero, exactly as keygen secret does. Signed-off-by: Jeremi Joslin <jeremi@joslin.fr> * feat(evidencectl): suggest source configuration from an OpenAPI document evidencectl source suggest turns one OpenAPI operation into draft Evidence source artifacts: the projection allowlist, a closed-subset response schema, a get_path extract skeleton, a facts stub, and a pasteable sources block. An interactive front-end (operation picker, leaf multi-select, bound prompts with provenance-labelled defaults) and the flag-driven front-end share one deterministic core, and every run ends by printing the equivalent fully-flagged command. The tool derives what the document can state, observes optional bounds from a local sample response, and never invents a bound: an unresolved decision is omitted plus a TODO comment, so evidence check rejects the draft until the operator supplies it. The runtime stays the only validator of the closed subset. Security review notes: the drafted acquisition posture is the weakest claim (record-transformed) with an explicit upgrade TODO, never field-projected on the operator's behalf; sample files are read once, size-capped, and only derived numeric bounds ever reach an artifact, pinned by canary tests; no network access anywhere, external $refs rejected; source ids are sanitized so generated file paths cannot escape the project bundle directory; existing files are never overwritten. Signed-off-by: Jeremi Joslin <jeremi@joslin.fr> * test(evidence): prove a signed assertion over a Relay-shaped source A wiremock mirrors the Relay OpenAPI's protected household-record read by hand (path, bearer, Data-Purpose, entity response shape) so Evidence proves the blessed Evidence-over-Relay composition without depending on Relay code. The test drives OAuth client credentials through the frozen runtime's public API to a signed flattened JWS, verifies it against the deployment JWKS under the full relying policy, and pins minimum disclosure with canaries: the record id, the untouched field, and the raw region code never reach the assertion payload. Zero production-code changes were needed; every composition component already exists in the frozen V1 runtime. Ticks A2 in plans/notary-retirement-and-evidence-onboarding.md. Signed-off-by: Jeremi Joslin <jeremi@joslin.fr> * wip(evidence): commit in-flight work set aside for the worktree merge Committed on request so the evidencectl/source-suggest worktree branch can merge into a clean main. Contents are the main-worktree WIP that the merge automation stashed as source-suggest-merge-setaside: the evidencectl suggest module (OpenAPI ingestion, flattening, narrowing, emit, interactive mode, with fixtures and five test files), evidence crate follow-ups to the source response-schema work (bundle, runtime, main, cli and source-contract tests), boundary-day fixture cases for both reference deployment projects, scaffold template and test updates, and wording refinements to the two Evidence tutorials. Not reviewed or verified as a unit; committed as WIP for review and squash. Restored verbatim from the setaside stash tree. Signed-off-by: Jeremi Joslin <jeremi@joslin.fr> * Revert "wip(evidence): commit in-flight work set aside for the worktree merge" This reverts commit 1f2390fb901b4593a9445951303cf3d63a0d8c11. Signed-off-by: Jeremi Joslin <jeremi@joslin.fr> * docs(site): state the exact immutability refusal wording in tutorials The runtime prints the refused input after the error prefix, and the secret provider root has its own ownership refusal; both tutorials now quote the real messages. Tutorial gate dry-run still passes. Signed-off-by: Jeremi Joslin <jeremi@joslin.fr> * fix(evidencectl): make the evidence binary resolver crate-visible suggest::emit calls fixtures::resolve_evidence_binary, which was private to its own module. The binary target does not declare mod suggest yet, so the error only surfaced under --all-targets, where the suggest_emit test target includes both modules and fails to compile. Signed-off-by: Jeremi Joslin <jeremi@joslin.fr> * fix(evidencectl): dispatch the source suggest command from the binary The suggest feature merged with its modules, fixtures and five test files but without `mod suggest`, without the `Source` subcommand, and without the `inquire` dependency its interactive front-end imports, so the crate did not build and the command was unreachable. Four of the five suggest test files pull modules in with `#[path = "../src/suggest/X.rs"] mod X;` because the crate has only a `[[bin]]` target. That compiles a module whether or not `main.rs` declares it, which is how a fully tested command shipped unreachable. `tests/cli_surface.rs` drives the real binary over the whole top-level subcommand set, asserting each one is both listed and dispatchable, so the same omission fails next time. Verified red at db20eab0: `--help` lists four commands, not five. `inquire` joins the workspace dependency table with its default features, matching every other dependency in the crate. Default features are kept because dropping them swaps the fuzzy filter behind `Select`/`MultiSelect` for substring matching, which is a behaviour change rather than a wiring fix. Signed-off-by: Jeremi Joslin <jeremi@joslin.fr> * fix(evidencectl): correct the bounds and quoting the suggest draft emits Review of the merged `source suggest` pipeline found seven defects that each let a draft claim more than the evidence behind it supports. Page-size detection matched any parameter name containing "page", "size" or "limit", so a `page` index beside a `pageSize` ceiling, and unrelated names like `fileSizeCeiling`, both fed the array bound. The comparison is now against a closed list of whole normalized names. When several genuine size parameters are advertised, the fallback takes the smallest rather than the largest: only the smallest ceiling is one a response can actually reach, and bounding above it points away from minimum disclosure. The emitted artifacts escaped too little. The reproduce line now shell-quotes any path outside a safe character set, the extract script emits a real Rhai string literal for the pointer, and drafted YAML quotes on any control character rather than newline alone. A root-level array carried no bound annotation at all, and TODO paths named the template directory rather than the scaffolded project's, so neither told the operator where to act. `evidence check` classification keyed on a substring, so a bundle rejection whose message merely mentioned a runtime stage was reported as unprovisioned secrets. It now matches the runtime's fixed stage messages at the start of stderr. An unnamed root pointer renders as "(response root)", the wording narrow.rs already uses. The OpenAPI loader gained the size ceiling the sampler already enforces. Every fix carries a test proved failing first. Signed-off-by: Jeremi Joslin <jeremi@joslin.fr> * feat(evidence): add a protected-read reference deployment project The two existing reference projects both read a registry product's own API and neither covers residence region. This one reads a protected, scoped, read-only registry API of the shape Registry Relay presents, and signs residence region as a coarse controlled code mapped from the register's own code, so all four coequal acceptance definitions now have a deployment-shaped worked example. It earns its place on lookup shape rather than branding: the envelope reports no total, only a page of records and a further-pages flag, so uniqueness is decided from both signals together and a single record on a page claiming further pages is ambiguous. The bundle names no registry product and depends on none. Eighteen fixture cases run through the production ABI offline. No Evidence production code changes; the only Rust edits extend the three existing #[cfg(test)] reference-project lists. Signed-off-by: Jeremi Joslin <jeremi@joslin.fr> * docs(site): correct the first-assertion tutorial against the shipped scaffold The tutorial described a residence-region acceptance definition the scaffold does not ship, listed a codelists directory it does not create, and printed a fixtures-run output that predates the case counts evidencectl now reports. A reader following it saw different output than documented on their first command. The gate did not catch the output drift because it substring-matched the count-free prefix, so pin the full documented lines instead. The gate also failed confusingly, mid-journey, when handed a relative binary path; refuse that up front with a message that names the cause. Signed-off-by: Jeremi Joslin <jeremi@joslin.fr> * fix(evidencectl): read real-world OpenAPI documents in source suggest Trialling `source suggest` against two published descriptions, a DHIS2 2.42 document and the OpenCRVS Events 2.0.0 document, showed the tool refusing schemas the closed subset already admits, and refusing whole operations over constructs it could bound instead. - A `$ref` cycle no longer fails the operation. The repeat is cut with a marker that declares no type, so the flattener skips it and never offers it as a leaf, and a whole-subtree selection over it is rejected by name. A cycle is not missing information, it is an expansion with no end; cutting it keeps the rest of the operation selectable. - An `anyOf`/`oneOf` of exactly two members where one is `{"type": "null"}` collapses into the kept member carrying `[T, "null"]`. This is how a generated 3.1 document spells nullability, and the subset already admits the result. - A `["null", T]` pair is reordered to `[T, "null"]` at resolution time. Flattening accepted either order and narrowing required one, so a document writing the pair the other way passed one stage and failed the next. - A node stating no `type` but stating `properties` or `items` is read as `object` or `array`, with a note naming the reading. - A bound clamped to the subset ceiling is now credited to the ceiling, not to the document. A specification writing `maxItems: 2147483647` is declining to bound the array; carrying its name onto the 256 the draft ends up with would read as a promise the source never made. - A run with no terminal now lists the operations, or the leaves of the chosen operation, alongside the flags it needs, and a URL passed to `--openapi` is refused with the command that fetches it. Measured on the two documents: the DHIS2 sweep of 154 GET operations goes from 87 yielding nothing to 1, and that one describes a schema with no declared properties. On the OpenCRVS document `GET /config` goes from failing outright to 32 leaves, `POST /events/search` from 10 leaves to 31, and `GET /locations` from 2 to 6. No source-product name enters production code, dependencies, features, configuration schemas, routes, or CLI options; the new fixtures are neutral. Signed-off-by: Jeremi Joslin <jeremi@joslin.fr> * docs(site): run the Evidence tutorial gate over a registry of tutorials The gate replayed one hard-coded tutorial. The onboarding path is a series of them, so the drift knobs move into a per-tutorial spec: the sh fence count, the ordered reader journey, the literals the text must keep, and the lines the transcript must contain. Each tutorial replays in a reader directory of its own. A journey step is either a range of sh fences to execute or a documented before/after fence pair applied to a file the reader edits, which is what a tutorial that modifies the scaffolded bundle needs. Files a reader creates outright stay inside the sh fences that create them, so the text a reader copies is the text CI runs. Registering a tutorial is now a slug plus a spec branch. Signed-off-by: Jeremi Joslin <jeremi@joslin.fr> * ci: keep Evidence tutorial routing in step with the gate's registry The Evidence tutorial job watched one tutorial page by name. The onboarding track is a series of them, so the next tutorial added would not have triggered the job that replays it, and could have broken with no pull request noticing. The watched inputs become a named constant, and a test reads the gate's own tutorial registry and asserts every registered slug is watched. Adding a tutorial to the gate without wiring its page here now fails, naming the path that is missing. Signed-off-by: Jeremi Joslin <jeremi@joslin.fr> * feat(evidencectl): fetch the OpenAPI document for source suggest from a URL Registry APIs publish their description at a well-known URL far more often than they ship it as a file, so `--openapi` now takes either. Nothing about the draft changes: it is still reviewed by a human and still carries a TODO wherever a bound could not be derived. Security review note. The description decides which leaves the operator is offered, and the projection they pick from it is the data-minimization boundary of the finished source, so a tampered document could quietly widen what a deployment reads. The rule applied is the one the runtime already enforces for the source URLs it will itself call (`validate_source_url`): https anywhere, plain http only to a numeric loopback host, never a credential in the authority. The credential refusal does not echo the URL back, so a password cannot reach a terminal or a scrollback buffer. No request carries authentication; a description behind a token is fetched by the operator and passed as a file. Redirects are followed but bounded, and the URL the response came from is re-checked under the same rule. The body is capped while reading rather than from Content-Length. Fetched text reaches only the draft the operator sees. Transport is ureq, already a workspace dependency used by registryctl, so no crate enters the graph and cargo-deny is unaffected. Tests are hermetic: policy is a pure function, and transport runs against a throwaway server on a loopback port, which is also the one case where plain http is permitted. Signed-off-by: Jeremi Joslin <jeremi@joslin.fr> * docs(evidence): note the URL form of source suggest --openapi States the fetch rule where the flag is documented, so an operator reading the README learns the constraint before hitting the refusal. Signed-off-by: Jeremi Joslin <jeremi@joslin.fr> * fix(evidence): name why the audit boundary refused to start Startup printed one line, "runtime audit initialization failed", for four unrelated faults with four unrelated remedies: a configuration bound out of range, an unusable hash secret, a file or lock that is not owner-only, a second writer holding the sink lock, and a chain that no longer verifies. From outside the process they are indistinguishable, and the wrong guess sends an operator hunting for tampering in what is a permission bit. Classify the fault at the boundary and append its cause to the same fixed message, in the shape bundle immutability failures already use. Security review notes: - Each cause is a fixed &'static str chosen from the fault class. No path, no secret, no chain content, and no operating-system message reaches it; the audit path an operator needs is already in their runtime file. - The classification is reporting only. Every fault stays fail-closed and the service still refuses to start on any of them. Signed-off-by: Jeremi Joslin <jeremi@joslin.fr> * feat(evidencectl): report every artifact mode the runtime will refuse Evidence and Mint refuse a deployment artifact whose permissions or ownership are wrong, and each refusal names one artifact. An operator who has run `chmod -R` over a project therefore discovers a project-wide mistake one restart at a time, and the audit chain and the caller keys are refused by paths that are not part of the freeze the generated README documents. `evidencectl doctor --project <dir>` walks the project once and reports all of them: the bundle tree and `runtime.yaml` under the immutability rule, the secret root and every secret the bundle references, the audit chain and its lock companion once they exist, and the key files of a paired Mint. It starts nothing and needs no `evidence` binary, so it is usable before the deployment is complete. Secrets are discovered from the bundle's own `secret:file/` references rather than by listing the secret directory, because `keygen signing` writes the public half of a key into that directory at 0644 by design and a directory walk would report a project the runtime accepts. Security review notes: this is advisory and read-only. It stats artifacts and reads only the two YAML configuration documents; it never opens a secret, a key or the audit chain, and it reports modes, link counts and uids but no file contents. It relaxes nothing: `evidence check` and startup remain the only authority on what a deployment may run with, and doctor compares ownership against the user running it rather than the user the service runs as, which the module doc and the generated README both state. Signed-off-by: Jeremi Joslin <jeremi@joslin.fr> * fix(evidence): name an unreachable access-token issuer and keep serving through its outage A deployment whose `jwksUri` could not be retrieved answered `/ready` 200 and then rejected every request with the same closed `401` a bad token receives, with nothing in the log naming the issuer, the URI, or the transport error underneath. Ready, 401, silence. Underneath both symptoms was a third problem: `key_for_kid` propagated the refresh transport error from its stale-cache arm, and `refresh` only writes keys on success. Past `cache_ttl`, an issuer that had merely gone quiet caused total rejection using a valid key still sitting in the cache. The verifier now keeps serving an already-retrieved key set for `cache_ttl` plus a bounded `outage_tolerance`, and only while the issuer cannot be reached. Startup attempts the key set once and names a `jwksUri` it cannot use. Readiness asks on every check and reports what comes back without letting the answer decide readiness. Two rate-limited WARNs name an unretrievable key set and a key set being served without being confirmed. Security review notes: - Authentication surface. No change to what is accepted: signature, issuer, audience, algorithm, token type and claim extraction are untouched, and the caller-facing rejection stays the same closed `401` with no added detail. The new material is operator-facing logging and cache lifetime. - Bounded outage tolerance. A key the issuer has withdrawn stays trusted for at most `cache_ttl` (600s) plus `outage_tolerance` (900s), and only while the issuer is unreachable; any successful refresh clears it immediately. The window is bounded rather than open-ended precisely because an attacker who can keep the issuer unreachable would otherwise hold it open indefinitely. This trades a bounded revocation delay during an outage against total rejection during one; the outage is reported for its whole duration rather than degrading silently to a cliff. - Log content. The WARNs carry the operator-configured `jwksUri`, an outage duration, and a cause chain bounded to three causes and 512 bytes. No token, claim, key material, or caller-supplied value reaches them, and they are rate-limited to one line per minute so an issuer outage cannot drive log volume. - Readiness. Deliberately not a gate on the issuer. It is a shared dependency no replica owns, so failing readiness on it would remove every replica from rotation at once for a cause rotation cannot address, while the verifier was still serving requests. This follows the same conclusion Envoy reaches with its `fast_listener` escape hatch and Kubernetes guidance names as a cascading-failure anti-pattern. Signed-off-by: Jeremi Joslin <jeremi@joslin.fr> * docs(evidence): add the acceptance-definition authoring tutorial The first tutorial gets a reader to one passing assertion but leaves the scaffold's abstract boolean in place. This second one has them author a coequal acceptance definition end to end: a narrower selector profile, a second source, a codelist that maps a register's fine-grained code to a coarse disclosed one, a derivation, and its own fixture file. Registered in the tutorial gate, which replays all thirteen command blocks and applies the four documented before/after edits to bundle/evidence.yaml. Signed-off-by: Jeremi Joslin <jeremi@joslin.fr> * docs(evidence): add the institution-source connection tutorial The first two tutorials work against the scaffold's stand-in source. This third one connects a real institution read API: draft the source from its OpenAPI document with evidencectl source suggest, review the draft rather than trust it, write the prepare and extract scripts, and prove the adapter offline from sanitized fixture cases before any live request. It teaches the two runtime rules that are easiest to get wrong: a response schema describes the response after projection, not what the API returns, and an absent key needs get_path rather than direct indexing. Registered in the tutorial gate, which replays all eleven command blocks and applies the three documented before/after edits. Signed-off-by: Jeremi Joslin <jeremi@joslin.fr> * ci: route the two new Evidence tutorials to the gate that replays them A tutorial the change filter does not watch is a tutorial that can break without any pull request noticing. The parity test already refuses a registered tutorial missing from this set. Signed-off-by: Jeremi Joslin <jeremi@joslin.fr> * fix(docs): restore the docs site check for the Evidence surface The Evidence onboarding surface landed with three defects the site check catches and nothing else does, so `npm run check` has been red since. `reference/mint.mdx` wrote byte and entry ceilings as bare `<=` inside table cells, which MDX reads as the start of a JSX tag and refuses to parse, taking the whole build down. State the ceilings in words instead. The Evidence API reference was registered with starlight-openapi but not added to the list of generated bases, which three separate consumers each kept their own copy of. Its pages therefore advertised a Markdown twin the build never generates, failing both the coverage check and the built link check. Move the list to one module, read it everywhere, and pin it to the bases astro.config.mjs actually registers so the next API reference cannot repeat this. `security/evidence.mdx` linked to `report-a-vulnerability/` relative to a page one level deeper than the one it was copied from. Signed-off-by: Jeremi Joslin <jeremi@joslin.fr> * docs(evidence): point tutorial readers at evidencectl doctor Both tutorials explain that Evidence names one refused artifact per restart, which is exactly the friction doctor was added to remove. Name it where the reader is already stuck, and say plainly that the missing source bearer token it also reports is expected in a project that only replays fixtures offline. Signed-off-by: Jeremi Joslin <jeremi@joslin.fr> * docs(evidence): teach the real response-schema defect in the source tutorial The tutorial claimed projection drops an intermediate container when its only selected leaf is absent, and built a repair step on that claim. It does not: a key is omitted only when it is absent and its projection node is terminal, and an absent intermediate is a projection violation. The repair step was a no-op and the lesson contradicted the source contract. Teach the defect the contract names instead. Requiring a projected leaf of every record turns an ordinary incomplete record into a source-protocol failure, so the requester is told the institution is unavailable rather than that evidence is not available; the check belongs in the extract script, which this project already does. The step is now load-bearing: without it the fixtures fail. Two other facts had gone stale against the merged source-suggest work: one verbatim line of the drafting report, and the claim that --openapi never fetches. Pin that report line in the gate so the next rewording fails CI instead of leaving the quoted transcript wrong. Signed-off-by: Jeremi Joslin <jeremi@joslin.fr> * docs(evidence): cover $ref cycles in the source connection tutorial A schema that refers to itself is common in institution documents: offices nest, so an office names the office above it. evidencectl cuts the repeat at its first repetition and reports it on standard error, and a reader whose needed leaf sits below such a cut has to select members individually rather than the subtree above it. The tutorial's synthetic document had no cycle, so the page never showed this. Adding one costs nothing downstream: the drafted schema, the derived-bounds report and the source block are byte-identical with the cycle present, and the fence count is unchanged. Pin one line of each quoted transcript in the gate so a rewording fails CI rather than leaving the page stale. Signed-off-by: Jeremi Joslin <jeremi@joslin.fr> * docs(site): label every tutorial with whose it is The four deployment roles start/when-to-use.mdx defines were prose only, so nothing tied a tutorial to a reader. They are now a checked vocabulary: a `persona:` key required on every doc_type: tutorial page, validated against src/lib/doc-personas.mjs, rendered under the title as a link back to the definitions. A unit test holds the list and the page in step in both directions, so adding a role to one without the other fails rather than drifting. This is a different axis from `audience`, which names who reads a specification (RS-TERMS Section 6). The two vocabularies stay apart. No tutorial claims consumer or verifier yet: that page is E5, still unwritten. Signed-off-by: Jeremi Joslin <jeremi@joslin.fr> * ci: bind the adopter image to the Debian 13 boundary docker/Dockerfile was reviewed and committed but no check held it to the same base-image contract as the release and per-product images, so the digest pins could drift silently. It does not fit the release shape: one file builds two binaries as two targets, so there is no single stage named `runtime`, and there is no HEALTHCHECK because distroless has no shell and neither binary has a healthcheck subcommand. Rather than bend the file to a shape it should not have, it is checked under its own: pinned frontend first, every upstream base digest-pinned, the pinned Rust builder, a nonroot-owned runtime root, and no shell, package manager, or fetch tooling in any distroless stage. Distroless stages are found by their base rather than by a stage name, and an unpinned base still counts as a stage, so an unpinned base is reported instead of dropping its stage out of the scan. Comments are scanned out so a stage can say in prose why it has no shell or curl. The nightly security workflow now retriggers on docker/ changes and scans the adopter image for baked-in key or credential material beside the release images. Signed-off-by: Jeremi Joslin <jeremi@joslin.fr> * docs(evidence): add the served-assertion tutorial The first tutorial ends offline, and the next thing a reader has to do is run the product as a service. Nothing walked them through it, and the two things that make it non-obvious are both real constraints rather than oversights, so the page teaches them instead of routing around them. Evidence requires https for the token issuer and its key set with no loopback exception, and outboundTls.trustProfiles governs only the calls Evidence makes to sources, so a development certificate authority reaches the JWKS client through SSL_CERT_FILE on the evidence process alone. That puts a TLS terminator in front of Mint in the journey, which is what a real deployment's ingress already is. Sources are exempt from https only for a numeric loopback host, which is the one place the page can point at a plain http stand-in, and it says so rather than leaving the asymmetry unexplained. The gate pins a negative control. Restarting Evidence without the trust anchor returns HTTP 401 and logs UnknownIssuer, so a passing run proves the token is checked rather than proving a request succeeded. No token is echoed or passed on a command line: mint token writes to a file created under umask 077 and curl reads it with --header @file. Mint changes now route to the Evidence tutorial job, because the gate builds and runs mint. Signed-off-by: Jeremi Joslin <jeremi@joslin.fr> * docs(evidence): add the consumer verification tutorial E5 completes the five-tutorial Evidence track and gives a tutorial the consumer or verifier persona for the first time. It is the only page that needs no deployment and no network: evidence verify opens no socket, resolves no metadata, and fetches no key, so the reader is handed a stored response and a pinned key set and writes the verification policy from state their own service retained. Both inputs are embedded rather than generated. A consumer can mint neither, and evaluate --fixture signs with an ephemeral in-memory key that never reaches disk, so there is no cheaper honest way to hand the reader bytes they did not produce. The gate carries the regeneration recipe and the warning that the policy values and the --at instants must be refreshed together. Five negative controls are pinned, so a passing run proves the checks refuse rather than proving one good response verifies. A replayed nonce and a different subject both collapse to the single policy class, because a refusal naming the failing comparison would be an oracle over exactly the retained state the check protects. An incomplete policy refuses as malformed with no authenticity verdict at all. A tampered payload byte refuses as signature, and an unpinned key set refuses as key one stage earlier. Signed-off-by: Jeremi Joslin <jeremi@joslin.fr> * docs(mint): diagram the client, Mint, and Evidence round trip The two Mint pages described each side of the token exchange but never the whole path, so a reader had to assemble the round trip from the token endpoint contract and the Evidence verification section themselves. Add a sequence diagram covering it end to end, and point at it from the how-to. Labels stay short and every wire shape they abbreviate is linked to the section that gives it in full, so the diagram summarises the contracts on the page rather than becoming a second source for them. Signed-off-by: Jeremi Joslin <jeremi@joslin.fr> * docs(site): make the Evidence tutorial track traversable No sequence of in-page Next clicks reached all six Evidence tutorials: one page dead-ended with no tutorial link, one pointed backward, and the consumer verification page never mentioned production signing. Align every first Next bullet with the sidebar order, and fix the production signing prerequisite to ask for any scaffolded project rather than one no intervening page tells the reader to keep. Signed-off-by: Jeremi Joslin <jeremi@joslin.fr> * docs(site): present two doors on when-to-use The decision table still offered three, asking a newcomer to tell a bounded answer from a minimum-disclosure answer before knowing what either meant. Reduce it to reader intent: read specific data goes to Registry Relay, learn only a fact goes to Evidence. Notary keeps one unlinked sentence saying it is being retired; the retirement page is not published yet. Replace the Relay-plus-Notary composition paragraph with the Evidence-over-Relay pattern, and name Registry Mint once as the token issuer for deployments with no identity provider. Signed-off-by: Jeremi Joslin <jeremi@joslin.fr> * docs(mint): show how to request a token from application code Add a how-to with worked Python and TypeScript examples that build the private_key_jwt assertion and exchange it, for callers integrating a registered client rather than running `mint token` at a terminal. Both examples were run against a real Mint deployment before being written down. Also correct the reference errors table: a missing `grant_type` returns `invalid_request`, not `unsupported_grant_type`, which only covers a `grant_type` that is present and wrong (`server.rs`). Signed-off-by: Jeremi Joslin <jeremi@joslin.fr> * docs(evidence): say what each generated key is for The keygen step listed three commands and their output without saying what the files do. Name each one against the bundle field that references it, and explain the two symmetric secrets: the audit key both chains the log and derives its pseudonyms, and the subject-binding key is scoped so two audiences cannot correlate assertions about the same person. Signed-off-by: Jeremi Joslin <jeremi@joslin.fr> * docs(site): give Evidence a front door and a quickstart Both front doors led with the retiring Notary path. Rebalance them on the two-door split when-to-use already uses, and add the Evidence overview route every other section has. The new start/evidence-quickstart maps the six tutorials, their times, and their outcomes, names the four coequal acceptance definitions without privileging one, and states that the track ends at production signing, not production approval. It carries no commands, so it does not compete with the first-assertion tutorial. Three IA assertions guard the result: the overview-route rule now covers Evidence, and each front door must open an Evidence lane ahead of the Notary tutorial. Nothing is deleted; removing a Notary lane belongs to the retirement workstream. Signed-off-by: Jeremi Joslin <jeremi@joslin.fr> * docs: record Notary retirement decision Signed-off-by: Jeremi Joslin <jeremi@joslin.fr> * docs(plan): record D1, the composed Relay, Mint, and Evidence demo Signed-off-by: Jeremi Joslin <jeremi@joslin.fr> * refactor(registryctl): remove Notary tooling Signed-off-by: Jeremi Joslin <jeremi@joslin.fr> * fix(ci): expect Evidence contracts in Rust result Signed-off-by: Jeremi Joslin <jeremi@joslin.fr> * docs(evidence): rewrite the Solmara Lab first run against the composed demo The tutorial ended at `just smoke`, which proved the lab was alive and nothing else. A reader finished it without ever seeing the thing the lab now exists to show: a registry answering a question about a person without handing over the record. It now runs the Evidence overlay and closes on one hand-made call. Reading two fields of one civil-register row through Relay, Evidence returns a signed `true`, and the page reads the decoded payload back to name what is not in it. The last Verify command fails unless the answer is `true` and carries neither the subject reference nor the date of birth, so the claim is checked rather than asserted. The gate had drifted well past the Evidence work: it still expected `redis` and three shared Notary instances the lab dropped some time ago. It now names the 17 services the page names and asserts the running total of 41 separately, so the count printed on the page is the count the gate checks. Two prerequisites stay documented rather than hidden. No release ships Evidence or Mint, so the reader names a clean Registry Stack checkout; and `just generate` rotates the Postgres password, so a second run against kept volumes dies in bootstrap with a message naming no cause. That failure now has a troubleshooting row, and the gate refuses to start against a checkout still holding volumes rather than deleting someone else's data. Signed-off-by: Jeremi Joslin <jeremi@joslin.fr> * refactor(release): retire Notary release surface Signed-off-by: Jeremi Joslin <jeremi@joslin.fr> * docs: present Relay and Evidence as maintained products Signed-off-by: Jeremi Joslin <jeremi@joslin.fr> * refactor(notary): remove retired product Signed-off-by: Jeremi Joslin <jeremi@joslin.fr> * docs(evidence): end the onboarding spine on Evidence over a Relay API The quickstart chooser sent readers down one door and never showed the two working together. Publish the composed Solmara Lab run, drop its redirect back to the chooser, and close the quickstart on it, so both doors reach the one place Evidence answers over a Relay-protected API rather than a local fixture. Mark the Notary sense of evidence credential as retired in the glossary. Signed-off-by: Jeremi Joslin <jeremi@joslin.fr> * ci: retire Notary gates Signed-off-by: Jeremi Joslin <jeremi@joslin.fr> * refactor(platform): remove orphaned Notary primitives Signed-off-by: Jeremi Joslin <jeremi@joslin.fr> * docs(evidence): rewrite the heavy pages around Relay, Evidence, and Mint The 21 pages that carried most of the Notary product surface now describe the stack an adopter can actually start on. A new gate, check-notary-surface.mjs, holds the line: a published page may name Registry Notary only in a block that also says it is retired. Four inherited claims were false and are corrected rather than reworded. Purpose-bound authorization does not narrow what a requirement discloses, only whether it may be invoked. Evidence has no caller-selectable disclosure modes, because its request body is closed on four members plus an optional holder key. The shared platform error section described a helper only Notary ever called. Solmara Lab builds Evidence and Mint from source, since no release ships those binaries. Sections that existed only to describe machinery Evidence deliberately does not have are deleted, not given an invented equivalent: federation trust, credential issuance and lifecycle, wallets, delegated subject access, the Notary approval lane, and the Notary posture producer. Signed-off-by: Jeremi Joslin <jeremi@joslin.fr> * docs: remove retired Notary product surface Signed-off-by: Jeremi Joslin <jeremi@joslin.fr> * docs(evidence): redraw the two diagrams E1 left orphaned The heavy-page rewrite removed both figures rather than replacing them, leaving two SVGs required by the accessibility gate but referenced by no page, and still drawing retired surfaces. Trust boundaries now draws the request path Mint to caller to Evidence to Relay to sources, with the seven boundary sections the page documents and a lane for operator key material and the published key set. There is no Mint to Evidence edge: Evidence holds an issuer URL and a key set, so an edge would draw a runtime dependency the page denies. The country mesh now draws authorities keeping custody and exposing bounded outputs through Relay and Evidence, with consumers receiving answers rather than becoming raw-record readers. The selectable-topology story is dropped because registryctl cannot express an Evidence topology. Both figures are restored with alt text restating every label. Signed-off-by: Jeremi Joslin <jeremi@joslin.fr> * chore(release): drop the external integration retirement guard The guard asserted that a deleted Notary runner and its evidence packet stay absent, that a Relay script file still exists, and that CI never names either path again. None of that is a statement about behaviour, so a green run reported coverage the release does not have, and the absence assertions would fail CI for anyone restoring source-read conformance coverage under the same names. A retirement is better proven by the absence of dangling references, which the surrounding checks already enforce. The Relay-only successor this leaves owed is tracked in the plan. Signed-off-by: Jeremi Joslin <jeremi@joslin.fr> * docs(plan): record the post-merge review decisions and open C9 and C10 Decisions 9 and 10 settle where the retirement line falls (products and runtimes, not the layers that must still parse what shipped before it) and what happens to previously issued approved baseline sets (rejected, with re-approval as the remedy). C9 tracks the source-read conformance the retired integration runner took with it. C10 tracks the manifest schema's federation and PDP identifiers, which need a version decision rather than a mechanical deletion. Signed-off-by: Jeremi Joslin <jeremi@joslin.fr> * chore(ci): drop dangling Notary coverage flags and secret-scan allowlists The three notary-* codecov flags carried coverage forward from paths that no longer exist, which reads as coverage rather than as its absence. The notary-postgres status entries move to relay-postgres, the postgres flag the nightly job actually uploads, so postgres coverage keeps a reporting surface; both stay informational, so no gate changes strength. The removed gitleaks allowlist entries each waived a scan for a file or line that is gone: the Notary holder-proof fixtures, the Notary OpenAPI example identifiers, and three oid4vci and Notary test lines. A dead allowlist entry is a standing exemption nobody is checking. Verified with the CI-pinned gitleaks 8.30.1 over a clean export of the tracked tree: identical findings before and after, so nothing new is exposed. Signed-off-by: Jeremi Joslin <jeremi@joslin.fr> * docs(release): retire the Notary release evidence and repin its permalinks The negative-path coverage map is marked, not trimmed: eight rows whose every anchor is retired become "Retired evidence", six mixed rows keep only their live anchors, and NP-20 and NP-21 drop from Covered to Partial because the product-surface half of their evidence lived on the removed credentials route. Deleting the rows would have erased the record of what is now unproven; Partial is the stricter reading, and the Release Decision now refuses to count a retired row as coverage. Nine links to deleted Notary documents are repinned at v0.16.3, where each target is confirmed to exist. The proof-level selector drops three sensitive-path globs under products/notary, and the error-registry check says which product set it means. Signed-off-by: Jeremi Joslin <jeremi@joslin.fr> * test(relay): restore real-loader coverage for the maintained examples The Notary retirement deleted live_consultation_journeys.rs, and with it the only test that staged each maintained operator example the way an operator deploys it and then drove the real config loader over the result. config_schema.rs schema-validates the examples in place and opencrvs_profile.rs loads one profile from the tree; neither proves the pinned artifact closure still resolves after the example is copied into …
1 parent b8ed78f commit 66f4f98

1,281 files changed

Lines changed: 190861 additions & 389724 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎.claude/commands/goal.md‎

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
---
2+
description: Execute the next step of the Notary retirement and Evidence onboarding plan
3+
argument-hint: [DoD item id, e.g. B5, or a workstream letter; empty picks the next item]
4+
---
5+
6+
Work the tracked plan at `plans/notary-retirement-and-evidence-onboarding.md`.
7+
8+
1. Read the plan in full: decisions, constraints, DoD checklist, dependency
9+
order, status log. The decisions are settled; do not relitigate them.
10+
2. Select work. If `$ARGUMENTS` names a DoD item or workstream, target it.
11+
Otherwise pick the first unchecked item whose dependencies are satisfied,
12+
preferring workstream B (onboarding is the standing priority).
13+
3. Announce the selected item and its intended shape in one short message,
14+
then execute. Only stop for input when the item is security-sensitive,
15+
requires a decision the plan reserves for Jeremi (A1's Mint-for-Relay
16+
branch, G4 re-approval), or turns out to conflict with a constraint.
17+
4. For code: TDD, failing test first. The frozen Evidence V1 rules in
18+
`AGENTS.md` and `products/evidence/AGENTS.md` apply; composition work
19+
must not touch Evidence production code.
20+
5. Verify with the gates listed in the plan's Verification section for every
21+
area touched. All must pass; paste the evidence in the report.
22+
6. Update the plan file in the same commit as the work: tick the DoD
23+
checkbox, append a dated status log line (absolute dates).
24+
7. Commit with `git commit -s` and a conventional prefix. Stage only files
25+
belonging to this item; never sweep unrelated worktree changes.
26+
8. Report: what changed, verification evidence, and the next unblocked item.
27+
28+
Never: modify frozen Evidence V1 contracts, hand-edit generated artifacts,
29+
scrub Notary from history pages, or log secrets.

‎.dockerignore‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
.git
2+
.cargo-home
3+
target
4+
scratch
5+
docs/site/node_modules

‎.github/dependabot.yml‎

Lines changed: 0 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -30,21 +30,6 @@ updates:
3030
- minor
3131
- patch
3232

33-
# The conformance helper lock is a pip-compile requirements file. Its direct
34-
# input remains byte-bound to the reviewed upstream suite requirements.
35-
- package-ecosystem: pip
36-
directory: "/release/conformance/openid"
37-
schedule:
38-
interval: weekly
39-
day: wednesday
40-
time: "08:30"
41-
timezone: Etc/UTC
42-
open-pull-requests-limit: 1
43-
groups:
44-
conformance-python:
45-
patterns:
46-
- "*"
47-
4833
# Pinned GitHub Actions used by the workflows.
4934
- package-ecosystem: github-actions
5035
directory: "/"
@@ -64,9 +49,7 @@ updates:
6449
- package-ecosystem: docker
6550
directories:
6651
- "/release/docker"
67-
- "/release/conformance/openid"
6852
- "/crates/registry-relay"
69-
- "/products/notary"
7053
schedule:
7154
interval: weekly
7255
day: wednesday
@@ -77,18 +60,3 @@ updates:
7760
docker-images:
7861
patterns:
7962
- "*"
80-
81-
# Docker Compose image pins use a distinct Dependabot ecosystem from
82-
# Dockerfiles. Both override filenames match Dependabot's Compose matcher.
83-
- package-ecosystem: docker-compose
84-
directory: "/release/conformance/openid"
85-
schedule:
86-
interval: weekly
87-
day: wednesday
88-
time: "11:30"
89-
timezone: Etc/UTC
90-
open-pull-requests-limit: 1
91-
groups:
92-
docker-compose-images:
93-
patterns:
94-
- "*"

‎.github/scripts/ci_changes.py‎

Lines changed: 40 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -15,49 +15,66 @@
1515
"platform": (
1616
"registry-platform-audit",
1717
"registry-platform-authcommon",
18-
"registry-platform-cache",
1918
"registry-platform-canonical-json",
2019
"registry-platform-config",
2120
"registry-platform-crypto",
2221
"registry-platform-httpsec",
2322
"registry-platform-httputil",
24-
"registry-platform-oid4vci",
2523
"registry-platform-oidc",
2624
"registry-platform-ops",
2725
"registry-platform-pdp",
28-
"registry-platform-replay",
2926
"registry-platform-sdjwt",
3027
"registry-platform-testing",
3128
),
3229
"manifest": (
3330
"registry-manifest-cli",
3431
"registry-manifest-core",
3532
),
36-
"notary": (
37-
"registry-notary",
38-
"registry-notary-client",
39-
"registry-notary-core",
40-
"registry-notary-server",
41-
"registry-notary-worker-harness",
42-
"xtask",
43-
),
4433
"relay": ("registry-relay",),
34+
"evidence": ("registry-evidence", "registry-evidencectl"),
35+
"mint": ("registry-mint",),
4536
"developer-tools": (
4637
"registry-config-report",
4738
"registry-language-server",
4839
),
4940
"registryctl": ("registryctl",),
5041
}
5142

52-
NOTARY_PACKAGES = frozenset(SHARDS["notary"])
43+
EVIDENCE_PACKAGES = frozenset(SHARDS["evidence"])
5344
PLATFORM_PACKAGES = frozenset(SHARDS["platform"])
5445
MANIFEST_PACKAGES = frozenset(SHARDS["manifest"])
5546
TUTORIAL_PACKAGES = frozenset(
5647
package
57-
for shard in ("platform", "manifest", "notary", "relay", "registryctl")
48+
for shard in ("platform", "manifest", "relay", "registryctl")
5849
for package in SHARDS[shard]
5950
) | {"registry-config-report"}
6051

52+
# Every input the Evidence tutorial gate replays or is built from. The tutorial
53+
# pages and helper scripts here must stay in step with the gate's own registry
54+
# and the helpers it invokes, which test_ci_changes.py enforces: a tutorial or
55+
# helper CI does not watch is one that rots silently.
56+
EVIDENCE_TUTORIAL_INPUTS = frozenset(
57+
{
58+
"Cargo.lock",
59+
"Cargo.toml",
60+
"docs/site/package-lock.json",
61+
"docs/site/package.json",
62+
"docs/site/scripts/check-evidence-tutorials.sh",
63+
"docs/site/scripts/check-evidence-tutorials.test.mjs",
64+
"docs/site/scripts/evidence-tutorial-fence.sh",
65+
"docs/site/scripts/registryctl-tutorial.mjs",
66+
"docs/site/src/content/docs/tutorials/assert-a-role-bound-relationship.mdx",
67+
"docs/site/src/content/docs/tutorials/first-evidence-assertion.mdx",
68+
"docs/site/src/content/docs/tutorials/refuse-unsafe-evidence-requests.mdx",
69+
"docs/site/src/content/docs/tutorials/return-a-governed-value.mdx",
70+
"docs/site/src/content/docs/tutorials/verify-an-assertion-as-a-consumer.mdx",
71+
}
72+
)
73+
74+
# The gate also builds and runs `mint`, because one tutorial serves assertions
75+
# to a caller holding a real Mint-issued token.
76+
EVIDENCE_TUTORIAL_PACKAGES = EVIDENCE_PACKAGES | frozenset(SHARDS["mint"])
77+
6178
ROOT_RUST_INPUTS = {
6279
"Cargo.lock",
6380
"Cargo.toml",
@@ -307,8 +324,8 @@ def classify(
307324
if package is not None:
308325
seeds.add(package)
309326
continue
310-
if path.startswith("products/notary/"):
311-
seeds.update(NOTARY_PACKAGES)
327+
if path.startswith("products/evidence/"):
328+
seeds.update(EVIDENCE_PACKAGES)
312329
elif path.startswith("products/manifest/"):
313330
seeds.update(MANIFEST_PACKAGES)
314331
elif path.startswith("products/platform/"):
@@ -387,7 +404,6 @@ def classify(
387404
"crates/registry-relay/openapi/*",
388405
"crates/registry-relay/src/api/openapi.rs",
389406
"crates/registryctl/assets/project-starters/*",
390-
"crates/registry-notary-server/src/standalone/activation.rs",
391407
"crates/registry-platform-ops/src/lib.rs",
392408
"crates/registry-relay/src/consultation/*",
393409
"crates/registryctl/schemas/project-reports/*",
@@ -396,8 +412,6 @@ def classify(
396412
"crates/registryctl/tests/fixtures/project-reports/*",
397413
"docs/site/*",
398414
"products/manifest/docs/*",
399-
"products/notary/docs/*",
400-
"products/notary/openapi/*",
401415
*AUTHORING_REFERENCE_PATTERNS,
402416
)
403417
or path
@@ -472,18 +486,13 @@ def classify(
472486
"docs/site/src/content/docs/tutorials/use-your-spreadsheet.mdx",
473487
"docs/site/src/content/docs/tutorials/verify-claim-registry-api.mdx",
474488
"docs/site/src/content/docs/tutorials/verify-opencrvs-claims.mdx",
475-
"release/docker/Dockerfile.registry-notary",
476489
"release/docker/Dockerfile.registry-relay",
477490
}
478-
or path.startswith("products/notary/")
479491
for path in paths
480492
)
481493
tutorial_source_under_test = any(
482494
matches(
483495
path,
484-
"crates/registry-notary/src/*",
485-
"crates/registry-notary-core/src/*",
486-
"crates/registry-notary-server/src/*",
487496
"crates/registryctl/src/templates/*",
488497
)
489498
or path
@@ -503,6 +512,12 @@ def classify(
503512
or bool(affected & TUTORIAL_PACKAGES)
504513
)
505514

515+
evidence_tutorial = (
516+
complete
517+
or any(path in EVIDENCE_TUTORIAL_INPUTS for path in paths)
518+
or bool(affected & EVIDENCE_TUTORIAL_PACKAGES)
519+
)
520+
506521
matrix = []
507522
for shard_name, shard_packages in SHARDS.items():
508523
selected = sorted(affected.intersection(shard_packages))
@@ -521,15 +536,16 @@ def classify(
521536
"rust_packages": sorted(affected),
522537
"platform": platform,
523538
"platform_hygiene": platform_hygiene,
524-
"notary_contracts": bool(affected & NOTARY_PACKAGES),
525539
"relay_contracts": "registry-relay" in affected,
540+
"evidence_contracts": bool(affected & EVIDENCE_PACKAGES),
526541
"project_authoring": "registryctl" in affected,
527542
"release_tool": release_tool,
528543
"release_source_proof": release_source_proof,
529544
"docs": docs,
530545
"docs_archives": docs_archives,
531546
"editors": editors,
532547
"registryctl_tutorial": registryctl_tutorial,
548+
"evidence_tutorial": evidence_tutorial,
533549
}
534550

535551

0 commit comments

Comments
 (0)