From 3dfda5b30c058a5764f8c3bfc6208c8a5b54013f Mon Sep 17 00:00:00 2001 From: Jeremi Joslin Date: Thu, 2 Jul 2026 17:32:16 +0700 Subject: [PATCH 01/11] fix(docs): remove phantom manifest-to-notary edge from README diagram Registry Notary has no dependency on the manifest crates; only Relay consumes registry-manifest-core. Signed-off-by: Jeremi Joslin --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index 0a3418d8..6565db45 100644 --- a/README.md +++ b/README.md @@ -56,7 +56,6 @@ flowchart LR source --> relay manifest --> relay - manifest --> notary relay --> caller relay --> notary notary --> caller From fac2dc72003edf15bad380f2b413a8518230d3f2 Mon Sep 17 00:00:00 2001 From: Jeremi Joslin Date: Thu, 2 Jul 2026 17:32:16 +0700 Subject: [PATCH 02/11] fix(release): serialize release workflow runs per ref A tag-push run and a manual dispatch for the same tag could race and clobber each other's published assets. Key the concurrency group on workflow+ref with cancel-in-progress: false so a second run queues instead of cancelling a publish mid-flight. Security review notes: release-provenance surface. No publish steps change, only run serialization; cancellation stays disabled because image push, asset upload with --clobber, cosign signing, and SLSA provenance are not safely cancellable. Signed-off-by: Jeremi Joslin --- .github/workflows/release.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index c7f79ed3..5d2a1ac7 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -14,6 +14,10 @@ on: permissions: contents: read +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: false + env: CARGO_TERM_COLOR: always REGISTRY: ghcr.io From 1b7e32fbc9e0afd2e308d3173953c6cb2a8265e3 Mon Sep 17 00:00:00 2001 From: Jeremi Joslin Date: Thu, 2 Jul 2026 17:34:54 +0700 Subject: [PATCH 03/11] docs(lab): update README for the monorepo layout The quick start still described the pre-monorepo standalone repo: cloning jeremi/registry-lab with submodules, just quick as the entry point, and a vendor-submodule pin workflow for Platform/Relay/Notary/ Manifest that no longer exists. Describe the monorepo checkout and just release-fast instead, keeping sibling-checkout overrides as a labeled alternative. Signed-off-by: Jeremi Joslin --- lab/README.md | 127 +++++++++++++++++++++++++++----------------------- 1 file changed, 69 insertions(+), 58 deletions(-) diff --git a/lab/README.md b/lab/README.md index 2d6f5c65..a2adb561 100644 --- a/lab/README.md +++ b/lab/README.md @@ -52,16 +52,11 @@ client also has no `data/` mount. ## Quick start -Clone with submodules: - -```bash -git clone --recurse-submodules git@github.com:jeremi/registry-lab.git -cd registry-lab -``` - -For an existing checkout, or after pulling changes: +This lab lives at `lab/` inside the registry-stack monorepo. From a checkout of +the monorepo: ```bash +cd lab just setup just generate just build @@ -70,10 +65,13 @@ just smoke just client ``` -The service-first metadata path uses the `vendor/registry-manifest` submodule by -default. Override `REGISTRY_MANIFEST_REPO` when you want to test a sibling -checkout or another local path. `just generate` and `just smoke` fail early when -`registry-manifest` is missing. +`just setup` initializes the `vendor/` submodules this lab still depends on +(Crosswalk, registry-atlas, the eSignet Relay authenticator). The Registry +Relay, Notary, Platform, and Manifest source now live as crates in the +monorepo root rather than as separate submodules or sibling checkouts, so the +plain commands above only resolve correctly when run through a monorepo-aware +recipe such as `just release-fast` (see below). `just generate` and `just +smoke` fail early when `registry-manifest` is missing. `just generate` writes `.env`, fixture files, and static metadata. Run it before `just up` the first time, and run it again after pulling demo changes that add @@ -87,13 +85,25 @@ When you are done: just down ``` -For a single command that generates, builds, starts, and runs the core checks: +For a single command that generates, builds, starts, and runs the core checks, +in this monorepo use: ```bash -just quick +just release-fast ``` -For commons release validation across sibling source checkouts: +Plain `just quick` predates the monorepo migration: it still expects sibling +`registry-relay`, `registry-notary`, `registry-platform`, and +`registry-manifest` checkouts (or the now-removed `vendor/` submodule pins for +those) and fails without `REGISTRY_RELAY_SOURCE_DIR`, +`REGISTRY_NOTARY_SOURCE_DIR`, `REGISTRY_PLATFORM_SOURCE_DIR`, and +`REGISTRY_MANIFEST_REPO` overrides. `just release-fast` runs +`scripts/release-check.sh`, which defaults to +`REGISTRY_LAB_RELEASE_SOURCE_MODE=monorepo` and wires those variables to the +monorepo root and `../crates/registry-relay` automatically. + +If you still have sibling Platform, Relay, and Notary checkouts and want to +validate against them directly, `commons-check` remains available: ```bash REGISTRY_PLATFORM_SOURCE_DIR=../registry-platform \ @@ -103,10 +113,6 @@ REGISTRY_NOTARY_SOURCE_DIR=../registry-notary \ just commons-check ``` -`commons-check` intentionally uses source dirs instead of `vendor/` pins. Update -Lab vendor or submodule pins only after Platform, Manifest, Relay, and Notary -source changes are committed. - For the first release, keep the two proof paths separate: - Source proof: run against sibling Platform, Relay, and Notary checkouts with @@ -114,10 +120,12 @@ For the first release, keep the two proof paths separate: reflected in Lab `vendor/` pins, also set `REGISTRY_LAB_ALLOW_PENDING_PINS=1`; the release source model check will print each pending pin or dirty source checkout. This is a pre-tag proof only. -- Lab pin proof: run `scripts/release-check.sh` without - `REGISTRY_LAB_RELEASE_SOURCE_MODE`. The script forces Platform, Relay, Notary, - Manifest, and Crosswalk to the committed `vendor/` submodules even when - sibling checkouts exist. This is the clean-clone/no-sibling release proof. +- Monorepo proof: run `scripts/release-check.sh` with the default + `REGISTRY_LAB_RELEASE_SOURCE_MODE=monorepo` (or explicitly). The script + resolves Platform, Relay, Notary, and Manifest against this monorepo + checkout and Crosswalk, registry-atlas, and the eSignet Relay authenticator + against the committed `vendor/` submodules. This is the primary release + proof for this monorepo. ## Demo commands @@ -624,17 +632,21 @@ the full setup sequence, which starts with `just generate` before ## Source repositories This demo keeps runtime orchestration, fixtures, static metadata config, and -walkthrough scripts in this repository. Supporting source repositories are -submodules under `vendor/`: - -- `vendor/registry-platform`: shared platform crates used by Relay and Notary. -- `vendor/registry-relay`: Relay source used by `Dockerfile.registry-relay`. -- `vendor/registry-notary`: Registry Notary source used by - `Dockerfile.registry-notary`. -- `vendor/registry-manifest`: static metadata publishing CLI and profiles. - -The Compose build uses Docker named contexts so local source checkouts can be -used without changing `compose.yaml`: +walkthrough scripts in this repository. Registry Platform, Registry Relay, +Registry Notary, and Registry Manifest now live as crates in the +registry-stack monorepo root rather than as `vendor/` submodules; the justfile +and scripts default to the monorepo checkout (`..` from `lab/`) when no +sibling checkout is present. The following remain real submodules under +`vendor/`: + +- `vendor/crosswalk`: the Crosswalk mapping engine, pinned as a git dependency. +- `vendor/registry-atlas`: registry-atlas source. +- `vendor/esignet-relay-authenticator`: the eSignet Relay authenticator plugin + used by `just esignet-up`. + +The Compose build uses Docker named contexts, and defaults to this monorepo +checkout for Platform, Relay, and Notary. Override with sibling checkouts when +you need to build from another local path: ```bash REGISTRY_RELAY_SOURCE_DIR=../registry-relay \ @@ -644,40 +656,39 @@ CROSSWALK_SOURCE_DIR=../crosswalk \ just build ``` -`just lab2-up` uses the same source selection model through `compose.lab2.yaml`. -That makes Lab 2 useful as a pre-pin regression pass against sibling Relay, -Notary, and Platform checkouts. `just lab2-generate` also rewrites a temporary -tool manifest when `REGISTRY_PLATFORM_SOURCE_DIR` points outside the vendored -Platform submodule, so generated governed artifacts can be checked against a -Platform source checkout. For release evidence, keep using `scripts/release-check.sh` -in `vendor` mode or pin the `vendor/` submodules before tagging. +`just lab2-up` uses the same source selection model through `compose.lab2.yaml`, +which makes Lab 2 useful as a regression pass against sibling Relay, Notary, +and Platform checkouts as well as the monorepo default. `just lab2-generate` +also rewrites a temporary tool manifest when `REGISTRY_PLATFORM_SOURCE_DIR` +points outside this monorepo, so generated governed artifacts can be checked +against a Platform source checkout. For release evidence, use +`scripts/release-check.sh` (monorepo mode by default). Use the same variables with `scripts/generate-demo-secrets.py` when you want -that script to use a sibling Relay checkout instead of the -`vendor/registry-relay` submodule. `scripts/publish-static-metadata.sh` uses -the Registry Manifest CLI from `REGISTRY_MANIFEST_REPO`, defaulting to the -`vendor/registry-manifest` submodule. For a release, pin the submodules to -commits that already include the Registry Platform, Registry Relay, and Registry -Notary behavior required by this demo. +that script to use a sibling Relay checkout instead of the monorepo default. +`scripts/publish-static-metadata.sh` uses the Registry Manifest CLI from +`REGISTRY_MANIFEST_REPO`, defaulting to this monorepo checkout. OpenFn image builds can use `REGISTRY_OPENFN_NOTARY_SOURCE_DIR` separately from the core Notary image. The lab default points OpenFn at the selected Notary -source, so local source checkouts can be tested before the lab submodule pin -moves. +source. `scripts/check-release-source-model.sh source` compares sibling Platform, -Relay, and Notary SHAs with the Lab `vendor/` pins and fails on mismatches or -dirty source checkouts. Use `REGISTRY_LAB_ALLOW_PENDING_PINS=1` only while the -final source commits are still waiting for the Lab submodule pin update. -`scripts/check-release-source-model.sh vendor` proves that the selected release -paths resolve to committed Lab pins. +Relay, and Notary SHAs with the Lab `vendor/` pins (Crosswalk, registry-atlas, +and the eSignet Relay authenticator) and fails on mismatches or dirty source +checkouts. Use `REGISTRY_LAB_ALLOW_PENDING_PINS=1` only while the final source +commits are still waiting for the Lab submodule pin update. +`scripts/check-release-source-model.sh monorepo` (the default) proves that the +selected release paths resolve to this monorepo checkout and the committed +`vendor/` pins. `just notary-client` imports the Registry Notary Python client directly from a source checkout and runs it against the default lab Notary services. It looks at -`REGISTRY_NOTARY_CLIENT_SOURCE_DIR` first, then `REGISTRY_NOTARY_SOURCE_DIR`, -then `../registry-notary`, and finally `vendor/registry-notary`. Use -`REGISTRY_NOTARY_CLIENT_SOURCE_DIR` when validating a client SDK branch before -the lab submodule pin has moved. This smoke is explicit and is not part of +`REGISTRY_NOTARY_CLIENT_SOURCE_DIR` first, then `REGISTRY_NOTARY_SOURCE_DIR` +(which the justfile and `release-check.sh` default to this monorepo checkout), +then falls back to the standalone sibling paths `../registry-notary` and +`vendor/registry-notary`. Use `REGISTRY_NOTARY_CLIENT_SOURCE_DIR` when +validating a client SDK branch. This smoke is explicit and is not part of `just quick`. ## Fixture data From e8071ca1a86f8fdf4dd6c39d24144e2bb591fb9e Mon Sep 17 00:00:00 2001 From: Jeremi Joslin Date: Thu, 2 Jul 2026 17:39:54 +0700 Subject: [PATCH 04/11] chore: opt workspace crates into the workspace lints table [workspace.lints.rust] unsafe_code = "forbid" was declared but no member crate had [lints] workspace = true, so it was enforced nowhere. Opt in the 23 members with no unsafe code; the five that use unsafe (libyaml FFI, libc setrlimit, env::set_var in test support) carry a comment explaining why they stay out. Security review notes: hardens the unsafe-code posture only; no code or dependency changes. Verified by a temporary unsafe probe failing compilation in an opted-in crate, then a clean workspace check. Signed-off-by: Jeremi Joslin --- crates/registry-config-report/Cargo.toml | 3 +++ crates/registry-manifest-cli/Cargo.toml | 2 ++ crates/registry-manifest-core/Cargo.toml | 3 +++ crates/registry-notary-client/Cargo.toml | 3 +++ crates/registry-notary-core/Cargo.toml | 3 +++ crates/registry-notary-server/Cargo.toml | 3 +++ crates/registry-notary-source-adapter-rhai/Cargo.toml | 3 +++ crates/registry-notary-source-adapter-sidecar/Cargo.toml | 3 +++ crates/registry-notary-worker-harness/Cargo.toml | 3 +++ crates/registry-notary/Cargo.toml | 3 +++ crates/registry-platform-audit/Cargo.toml | 3 +++ crates/registry-platform-authcommon/Cargo.toml | 3 +++ crates/registry-platform-cache/Cargo.toml | 3 +++ crates/registry-platform-config/Cargo.toml | 3 +++ crates/registry-platform-crypto/Cargo.toml | 3 +++ crates/registry-platform-httpsec/Cargo.toml | 3 +++ crates/registry-platform-httputil/Cargo.toml | 3 +++ crates/registry-platform-oid4vci/Cargo.toml | 3 +++ crates/registry-platform-oidc/Cargo.toml | 3 +++ crates/registry-platform-ops/Cargo.toml | 3 +++ crates/registry-platform-pdp/Cargo.toml | 3 +++ crates/registry-platform-replay/Cargo.toml | 3 +++ crates/registry-platform-sdjwt/Cargo.toml | 3 +++ crates/registry-platform-sts/Cargo.toml | 3 +++ crates/registry-platform-testing/Cargo.toml | 3 +++ crates/registry-relay/Cargo.toml | 4 ++++ crates/registryctl/Cargo.toml | 3 +++ products/notary/xtask/Cargo.toml | 3 +++ 28 files changed, 84 insertions(+) diff --git a/crates/registry-config-report/Cargo.toml b/crates/registry-config-report/Cargo.toml index 3a7a9f7f..bf0b9292 100644 --- a/crates/registry-config-report/Cargo.toml +++ b/crates/registry-config-report/Cargo.toml @@ -7,6 +7,9 @@ readme = "README.md" repository.workspace = true publish = false +[lints] +workspace = true + [dependencies] serde.workspace = true serde_json.workspace = true diff --git a/crates/registry-manifest-cli/Cargo.toml b/crates/registry-manifest-cli/Cargo.toml index f8bdba76..7249faeb 100644 --- a/crates/registry-manifest-cli/Cargo.toml +++ b/crates/registry-manifest-cli/Cargo.toml @@ -10,6 +10,8 @@ keywords = ["registry", "metadata", "manifest", "cli", "publish"] categories = ["command-line-utilities", "encoding"] publish = false +# not opted into [workspace.lints]: uses unsafe (libyaml FFI in src/main.rs) + [[bin]] name = "registry-manifest" path = "src/main.rs" diff --git a/crates/registry-manifest-core/Cargo.toml b/crates/registry-manifest-core/Cargo.toml index ca2e7fba..1988c108 100644 --- a/crates/registry-manifest-core/Cargo.toml +++ b/crates/registry-manifest-core/Cargo.toml @@ -10,6 +10,9 @@ keywords = ["registry", "metadata", "manifest", "dcat", "shacl"] categories = ["data-structures", "encoding", "parser-implementations"] publish = false +[lints] +workspace = true + [dependencies] oxiri = { version = "0.2.11" } registry-platform-pdp = { workspace = true } diff --git a/crates/registry-notary-client/Cargo.toml b/crates/registry-notary-client/Cargo.toml index 1a693552..8c03a967 100644 --- a/crates/registry-notary-client/Cargo.toml +++ b/crates/registry-notary-client/Cargo.toml @@ -8,6 +8,9 @@ readme = "README.md" repository.workspace = true publish = false +[lints] +workspace = true + [features] default = ["rustls"] rustls = [] diff --git a/crates/registry-notary-core/Cargo.toml b/crates/registry-notary-core/Cargo.toml index 0ea31752..d3d0876d 100644 --- a/crates/registry-notary-core/Cargo.toml +++ b/crates/registry-notary-core/Cargo.toml @@ -8,6 +8,9 @@ readme = "README.md" repository.workspace = true publish = false +[lints] +workspace = true + [dependencies] base64.workspace = true humantime-serde.workspace = true diff --git a/crates/registry-notary-server/Cargo.toml b/crates/registry-notary-server/Cargo.toml index 258ab206..953763f7 100644 --- a/crates/registry-notary-server/Cargo.toml +++ b/crates/registry-notary-server/Cargo.toml @@ -8,6 +8,9 @@ readme = "README.md" repository.workspace = true publish = false +# not opted into [workspace.lints]: unsafe std::env::set_var/remove_var in +# #[cfg(test)] code (src/standalone.rs, tests/) + [features] default = [] registry-notary-cel = ["dep:crosswalk-core", "dep:registry-notary-worker-harness"] diff --git a/crates/registry-notary-source-adapter-rhai/Cargo.toml b/crates/registry-notary-source-adapter-rhai/Cargo.toml index 993f3f51..7d95469f 100644 --- a/crates/registry-notary-source-adapter-rhai/Cargo.toml +++ b/crates/registry-notary-source-adapter-rhai/Cargo.toml @@ -8,6 +8,9 @@ readme = "README.md" repository.workspace = true publish = false +[lints] +workspace = true + [dependencies] async-trait.workspace = true # The workspace declares crosswalk-functions with its default features (text, diff --git a/crates/registry-notary-source-adapter-sidecar/Cargo.toml b/crates/registry-notary-source-adapter-sidecar/Cargo.toml index b9ea097b..2918cd74 100644 --- a/crates/registry-notary-source-adapter-sidecar/Cargo.toml +++ b/crates/registry-notary-source-adapter-sidecar/Cargo.toml @@ -8,6 +8,9 @@ readme = "README.md" repository.workspace = true publish = false +[lints] +workspace = true + [dependencies] async-trait.workspace = true axum.workspace = true diff --git a/crates/registry-notary-worker-harness/Cargo.toml b/crates/registry-notary-worker-harness/Cargo.toml index 34dcbe79..4591f20d 100644 --- a/crates/registry-notary-worker-harness/Cargo.toml +++ b/crates/registry-notary-worker-harness/Cargo.toml @@ -8,6 +8,9 @@ readme = "README.md" repository.workspace = true publish = false +# not opted into [workspace.lints]: uses unsafe (libc setrlimit, `unsafe +# extern "C"` in src/lib.rs) + [dependencies] serde.workspace = true serde_json.workspace = true diff --git a/crates/registry-notary/Cargo.toml b/crates/registry-notary/Cargo.toml index 41ef28f9..d8fe6813 100644 --- a/crates/registry-notary/Cargo.toml +++ b/crates/registry-notary/Cargo.toml @@ -8,6 +8,9 @@ readme = "README.md" repository.workspace = true publish = false +# not opted into [workspace.lints]: unsafe std::env::set_var/remove_var in +# #[cfg(test)] code (src/main.rs) + [[bin]] name = "registry-notary" path = "src/main.rs" diff --git a/crates/registry-platform-audit/Cargo.toml b/crates/registry-platform-audit/Cargo.toml index b2d9bdf1..ff6be868 100644 --- a/crates/registry-platform-audit/Cargo.toml +++ b/crates/registry-platform-audit/Cargo.toml @@ -7,6 +7,9 @@ readme = "README.md" repository.workspace = true publish = false +[lints] +workspace = true + [dependencies] async-trait.workspace = true hmac.workspace = true diff --git a/crates/registry-platform-authcommon/Cargo.toml b/crates/registry-platform-authcommon/Cargo.toml index 9d6a23b4..d836e29c 100644 --- a/crates/registry-platform-authcommon/Cargo.toml +++ b/crates/registry-platform-authcommon/Cargo.toml @@ -7,6 +7,9 @@ readme = "README.md" repository.workspace = true publish = false +[lints] +workspace = true + [dependencies] serde.workspace = true sha2.workspace = true diff --git a/crates/registry-platform-cache/Cargo.toml b/crates/registry-platform-cache/Cargo.toml index 2e41f825..765eb28a 100644 --- a/crates/registry-platform-cache/Cargo.toml +++ b/crates/registry-platform-cache/Cargo.toml @@ -7,6 +7,9 @@ readme = "README.md" repository.workspace = true publish = false +[lints] +workspace = true + [features] default = [] redis = ["dep:redis"] diff --git a/crates/registry-platform-config/Cargo.toml b/crates/registry-platform-config/Cargo.toml index 7208cec0..4aac9564 100644 --- a/crates/registry-platform-config/Cargo.toml +++ b/crates/registry-platform-config/Cargo.toml @@ -7,6 +7,9 @@ description = "Signed/governed runtime configuration verification contracts for repository.workspace = true publish = false +[lints] +workspace = true + [dependencies] async-trait.workspace = true aws-lc-rs = "1.17.0" diff --git a/crates/registry-platform-crypto/Cargo.toml b/crates/registry-platform-crypto/Cargo.toml index dd9eb26f..ff718e84 100644 --- a/crates/registry-platform-crypto/Cargo.toml +++ b/crates/registry-platform-crypto/Cargo.toml @@ -7,6 +7,9 @@ readme = "README.md" repository.workspace = true publish = false +[lints] +workspace = true + [dependencies] async-trait.workspace = true aws-lc-rs.workspace = true diff --git a/crates/registry-platform-httpsec/Cargo.toml b/crates/registry-platform-httpsec/Cargo.toml index ceaf08b5..43281034 100644 --- a/crates/registry-platform-httpsec/Cargo.toml +++ b/crates/registry-platform-httpsec/Cargo.toml @@ -7,6 +7,9 @@ readme = "README.md" repository.workspace = true publish = false +[lints] +workspace = true + [dependencies] axum.workspace = true http.workspace = true diff --git a/crates/registry-platform-httputil/Cargo.toml b/crates/registry-platform-httputil/Cargo.toml index ef4302bb..3dbedf9b 100644 --- a/crates/registry-platform-httputil/Cargo.toml +++ b/crates/registry-platform-httputil/Cargo.toml @@ -7,6 +7,9 @@ readme = "README.md" repository.workspace = true publish = false +[lints] +workspace = true + [features] default = ["rustls"] rustls = ["reqwest/rustls-tls"] diff --git a/crates/registry-platform-oid4vci/Cargo.toml b/crates/registry-platform-oid4vci/Cargo.toml index 3c94364b..ac8ad313 100644 --- a/crates/registry-platform-oid4vci/Cargo.toml +++ b/crates/registry-platform-oid4vci/Cargo.toml @@ -7,6 +7,9 @@ readme = "README.md" repository.workspace = true publish = false +[lints] +workspace = true + [dependencies] base64.workspace = true registry-platform-crypto = { workspace = true } diff --git a/crates/registry-platform-oidc/Cargo.toml b/crates/registry-platform-oidc/Cargo.toml index 72faa8d7..20bc1d7e 100644 --- a/crates/registry-platform-oidc/Cargo.toml +++ b/crates/registry-platform-oidc/Cargo.toml @@ -7,6 +7,9 @@ readme = "README.md" repository.workspace = true publish = false +[lints] +workspace = true + [dependencies] base64.workspace = true jsonwebtoken.workspace = true diff --git a/crates/registry-platform-ops/Cargo.toml b/crates/registry-platform-ops/Cargo.toml index 742c9cdb..fd91e669 100644 --- a/crates/registry-platform-ops/Cargo.toml +++ b/crates/registry-platform-ops/Cargo.toml @@ -7,6 +7,9 @@ readme = "README.md" repository.workspace = true publish = false +[lints] +workspace = true + [dependencies] fs2.workspace = true registry-platform-crypto = { workspace = true } diff --git a/crates/registry-platform-pdp/Cargo.toml b/crates/registry-platform-pdp/Cargo.toml index 63221a07..5847e5b5 100644 --- a/crates/registry-platform-pdp/Cargo.toml +++ b/crates/registry-platform-pdp/Cargo.toml @@ -7,5 +7,8 @@ readme = "README.md" repository.workspace = true publish = false +[lints] +workspace = true + [dependencies] serde.workspace = true diff --git a/crates/registry-platform-replay/Cargo.toml b/crates/registry-platform-replay/Cargo.toml index cdf3ebf1..acb63d12 100644 --- a/crates/registry-platform-replay/Cargo.toml +++ b/crates/registry-platform-replay/Cargo.toml @@ -7,6 +7,9 @@ readme = "README.md" repository.workspace = true publish = false +[lints] +workspace = true + [dependencies] async-trait.workspace = true getrandom.workspace = true diff --git a/crates/registry-platform-sdjwt/Cargo.toml b/crates/registry-platform-sdjwt/Cargo.toml index c101c0bc..e7cf70b8 100644 --- a/crates/registry-platform-sdjwt/Cargo.toml +++ b/crates/registry-platform-sdjwt/Cargo.toml @@ -7,6 +7,9 @@ readme = "README.md" repository.workspace = true publish = false +[lints] +workspace = true + [dependencies] base64.workspace = true getrandom.workspace = true diff --git a/crates/registry-platform-sts/Cargo.toml b/crates/registry-platform-sts/Cargo.toml index d987f2a4..b04fe58a 100644 --- a/crates/registry-platform-sts/Cargo.toml +++ b/crates/registry-platform-sts/Cargo.toml @@ -7,6 +7,9 @@ readme = "README.md" repository.workspace = true publish = false +[lints] +workspace = true + [dependencies] async-trait.workspace = true axum.workspace = true diff --git a/crates/registry-platform-testing/Cargo.toml b/crates/registry-platform-testing/Cargo.toml index fe978990..105669a0 100644 --- a/crates/registry-platform-testing/Cargo.toml +++ b/crates/registry-platform-testing/Cargo.toml @@ -7,6 +7,9 @@ readme = "README.md" repository.workspace = true publish = false +[lints] +workspace = true + [features] default = ["test-utils"] test-utils = [] diff --git a/crates/registry-relay/Cargo.toml b/crates/registry-relay/Cargo.toml index 818e1560..a549340c 100644 --- a/crates/registry-relay/Cargo.toml +++ b/crates/registry-relay/Cargo.toml @@ -7,6 +7,10 @@ license = "Apache-2.0" description = "Registry Relay: config-driven, read-only APIs over sensitive registry and tabular files." publish = false +# not opted into [workspace.lints]: unsafe std::env::set_var in +# src/config/test_support.rs (compiled into the lib, not just #[cfg(test)]) +# and in tests/ + [package.metadata.cargo-machete] ignored = ["humantime-serde"] diff --git a/crates/registryctl/Cargo.toml b/crates/registryctl/Cargo.toml index 730fff13..7a6e9e7c 100644 --- a/crates/registryctl/Cargo.toml +++ b/crates/registryctl/Cargo.toml @@ -5,6 +5,9 @@ edition = "2021" license = "Apache-2.0" publish = false +[lints] +workspace = true + [dependencies] anyhow = "1" base64 = "0.22" diff --git a/products/notary/xtask/Cargo.toml b/products/notary/xtask/Cargo.toml index d7bdc1dd..e1ec1b44 100644 --- a/products/notary/xtask/Cargo.toml +++ b/products/notary/xtask/Cargo.toml @@ -6,6 +6,9 @@ license = "Apache-2.0" # Internal workspace tooling; not published. publish = false +[lints] +workspace = true + [dependencies] base64 = { version = "0.22" } registry-platform-crypto = { workspace = true } From 9a9443c595255cb3e7b339852249839022dfa38c Mon Sep 17 00:00:00 2001 From: Jeremi Joslin Date: Thu, 2 Jul 2026 17:53:02 +0700 Subject: [PATCH 05/11] fix(notary): align posture example with the live document shape The OpenAPI 200 example for /admin/v1/posture lacked the deployment and audit sections the server emits at the default tier, and nothing gated example fidelity. Add the missing sections, pin the example's top-level keys to the live document in a standalone_http test for both tiers, and regenerate the committed OpenAPI artifact (which also catches info.version up from 0.6.2 to 0.8.3). Signed-off-by: Jeremi Joslin --- .../tests/standalone_http.rs | 84 +++++++++++++++++++ .../registry-notary.posture.valid.json | 14 ++++ products/notary/CHANGELOG.md | 6 ++ .../openapi/registry-notary.openapi.json | 18 +++- 4 files changed, 120 insertions(+), 2 deletions(-) diff --git a/crates/registry-notary-server/tests/standalone_http.rs b/crates/registry-notary-server/tests/standalone_http.rs index b3f17027..5891d557 100644 --- a/crates/registry-notary-server/tests/standalone_http.rs +++ b/crates/registry-notary-server/tests/standalone_http.rs @@ -7502,6 +7502,90 @@ async fn admin_posture_reports_configured_instance_override() { assert!(body["instance"].get("public_base_url").is_none()); } +#[tokio::test] +async fn admin_posture_top_level_keys_match_documented_example() { + set_audit_secret(); + std::env::set_var( + "TEST_EVIDENCE_API_KEY_HASH", + "sha256:a00cf33cd46d9ef96c1eff33df1c9cca20b1a02468cd78ec6a4b2887d1640b51", + ); + std::env::set_var("TEST_EVIDENCE_SOURCE_TOKEN", "source-token"); + + let tmp = TempDir::new().expect("tempdir"); + let audit_path = tmp.path().join("audit.jsonl"); + let mut config = registry_data_api_config( + "http://127.0.0.1:1", + audit_path.to_str().expect("audit path is UTF-8"), + ); + enable_shared_admin_listener(&mut config); + add_ops_read_api_key(&mut config); + + let app = standalone_router(config).expect("standalone router builds"); + let server = TestServer::builder().http_transport().build(app); + + let default_posture = server + .get("/admin/v1/posture") + .add_header("x-api-key", "ops-token") + .await; + default_posture.assert_status_ok(); + let default_body: Value = default_posture.json(); + assert_matches_posture_schema(&default_body); + + let default_live_keys = default_body + .as_object() + .expect("posture is object") + .keys() + .cloned() + .collect::>(); + let default_example: Value = + serde_json::from_str(registry_platform_ops::NOTARY_POSTURE_EXAMPLE_V1) + .expect("notary posture example parses"); + let default_example_keys = default_example + .as_object() + .expect("example posture is object") + .keys() + .cloned() + .collect::>(); + assert_eq!( + default_example_keys, default_live_keys, + "NOTARY_POSTURE_EXAMPLE_V1 top-level keys drifted from the live default-tier posture document \ + (missing from example: {:?}, extra in example: {:?})", + default_live_keys.difference(&default_example_keys).collect::>(), + default_example_keys.difference(&default_live_keys).collect::>(), + ); + + let restricted_posture = server + .get("/admin/v1/posture?tier=restricted") + .add_header("x-api-key", "ops-token") + .await; + restricted_posture.assert_status_ok(); + let restricted_body: Value = restricted_posture.json(); + assert_matches_posture_schema(&restricted_body); + + let restricted_live_keys = restricted_body + .as_object() + .expect("posture is object") + .keys() + .cloned() + .collect::>(); + let restricted_fixture: Value = + serde_json::from_str(registry_platform_ops::RESTRICTED_POSTURE_FIXTURE_V1) + .expect("restricted posture fixture parses"); + let restricted_fixture_keys = restricted_fixture + .as_object() + .expect("restricted fixture posture is object") + .keys() + .cloned() + .collect::>(); + assert_eq!( + restricted_fixture_keys, restricted_live_keys, + "RESTRICTED_POSTURE_FIXTURE_V1 top-level keys drifted from the live restricted-tier posture document \ + (missing from fixture: {:?}, extra in fixture: {:?})", + restricted_live_keys.difference(&restricted_fixture_keys).collect::>(), + restricted_fixture_keys.difference(&restricted_live_keys).collect::>(), + ); +} + #[tokio::test] async fn admin_posture_reports_self_attestation_summary_and_redacts_signing_key_ids() { std::env::set_var("TEST_SELF_ATTESTATION_ISSUER_JWK", TEST_ISSUER_JWK); diff --git a/crates/registry-platform-ops/examples/registry-notary.posture.valid.json b/crates/registry-platform-ops/examples/registry-notary.posture.valid.json index 7a4925e9..5d35bcc3 100644 --- a/crates/registry-platform-ops/examples/registry-notary.posture.valid.json +++ b/crates/registry-platform-ops/examples/registry-notary.posture.valid.json @@ -18,6 +18,20 @@ "admin_enabled": true, "readiness": "ready" }, + "deployment": { + "profile": "production", + "findings": [] + }, + "audit": { + "write_policy": "fail_closed_route_families", + "redaction_mode": "redacted", + "hash_chain": "process_local", + "keyed_integrity": "hmac", + "sink_class": "file", + "retention_owner": "operator", + "checkpoints": "unsupported", + "anchoring": "none" + }, "configuration": { "source": "local_file", "dynamic_reload_supported": false, diff --git a/products/notary/CHANGELOG.md b/products/notary/CHANGELOG.md index 9e06f839..98501437 100644 --- a/products/notary/CHANGELOG.md +++ b/products/notary/CHANGELOG.md @@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Fixed + +- Corrected the `GET /admin/v1/posture` OpenAPI example to include the + `deployment` and `audit` sections that the live default-tier posture + document already returns. + ### Changed - Renamed the binary package from `registry-notary-bin` to `registry-notary` diff --git a/products/notary/openapi/registry-notary.openapi.json b/products/notary/openapi/registry-notary.openapi.json index b9ee7e11..b3640c91 100644 --- a/products/notary/openapi/registry-notary.openapi.json +++ b/products/notary/openapi/registry-notary.openapi.json @@ -1802,7 +1802,7 @@ }, "summary": "Standalone evidence evaluation, rendering, and credential issuance service.", "title": "Registry Notary API", - "version": "0.6.2" + "version": "0.8.3" }, "openapi": "3.1.0", "paths": { @@ -2741,6 +2741,16 @@ "content": { "application/json": { "example": { + "audit": { + "anchoring": "none", + "checkpoints": "unsupported", + "hash_chain": "process_local", + "keyed_integrity": "hmac", + "redaction_mode": "redacted", + "retention_owner": "operator", + "sink_class": "file", + "write_policy": "fail_closed_route_families" + }, "build": { "package": "registry-notary", "version": "0.1.0" @@ -2756,6 +2766,10 @@ "restart_required": false, "source": "local_file" }, + "deployment": { + "findings": [], + "profile": "production" + }, "instance": { "environment": "production", "id": "civil-notary-prod", @@ -3640,7 +3654,7 @@ "example": { "info": { "title": "Registry Notary API", - "version": "0.6.2" + "version": "0.8.3" }, "openapi": "3.1.0", "paths": { From 851ef164875011d794b1306ccf0e584cb17019fa Mon Sep 17 00:00:00 2001 From: Jeremi Joslin Date: Thu, 2 Jul 2026 18:10:05 +0700 Subject: [PATCH 06/11] ci: gate workspace tests, cargo-deny, and OpenAPI drift in root CI The root gate ran only fmt, a workspace check, and registryctl tests; OpenAPI drift and dependency policy were manual discipline and drifted repeatedly. Gate the full workspace test suite, cargo deny check for bans/licenses/sources, the notary openapi-check recipe, and the relay openapi-contract recipe, with a build cache to keep runtime sane. Clippy and the cargo-deny advisories section are deliberately not yet gated: the tree currently fails them (4 pre-existing lints; fresh RUSTSEC advisories on anyhow and quick-xml). They will be promoted once fixed rather than silenced. Security review notes: both new actions (taiki-e/install-action, Swatinem/rust-cache) are pinned to the same full commit SHAs already trusted in the per-product workflows; tools are version-pinned (cargo-deny 0.19.8, just 1.51.0). No deny.toml ignores added. Signed-off-by: Jeremi Joslin --- .github/workflows/ci.yml | 29 +++++++++++++++++++++++++++-- CONTRIBUTING.md | 9 ++++++++- 2 files changed, 35 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ad69fa70..adb0b430 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -119,6 +119,14 @@ jobs: fetch-depth: 0 submodules: false + - name: Cache Cargo registry and build artifacts + uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2 + + - name: Install cargo-deny and just + uses: taiki-e/install-action@25435dc8dd3baed7417e0c96d3fe89013a5b2e09 # v2.81.3 + with: + tool: cargo-deny@0.19.8,just@1.51.0 + - name: Cargo metadata run: cargo metadata --locked --format-version 1 >/tmp/registry-stack-cargo-metadata.json @@ -128,8 +136,25 @@ jobs: - name: Workspace check run: cargo check --locked --workspace --all-targets - - name: Focused tests - run: cargo test --locked -p registryctl + - name: Workspace tests + run: cargo test --locked --workspace + + # Enforces the licenses, bans, and sources sections of deny.toml on every + # Rust change. The advisories section is intentionally not gated here: + # the current tree fails `cargo deny check advisories` on fresh upstream + # RUSTSEC advisories (anyhow, quick-xml) whose fixes are dependency bumps, + # not code changes. Promote this to a full `cargo deny check` once the + # lockfile is updated so the advisories section passes. + - name: Cargo deny (licenses, bans, sources) + run: cargo deny check bans licenses sources + + - name: Notary OpenAPI baseline + working-directory: products/notary + run: just openapi-check + + - name: Relay OpenAPI contract + working-directory: crates/registry-relay + run: just openapi-contract release-tool: name: Release tooling diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index fe5415c5..5708342d 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -111,9 +111,16 @@ Rust workspace: cargo metadata --locked --format-version 1 cargo fmt --check cargo check --locked --workspace --all-targets -cargo test --locked -p registryctl +cargo test --locked --workspace +cargo deny check bans licenses sources +(cd products/notary && just openapi-check) +(cd crates/registry-relay && just openapi-contract) ``` +Clippy (`cargo clippy --workspace --all-targets -- -D warnings`) and the +cargo-deny advisories section are the house convention but are not yet in the +root gate; run them locally. + Release and lab source checks: ```bash From 72bad4cabfca8afc218b9aa507ba49145d5eb8d0 Mon Sep 17 00:00:00 2001 From: Jeremi Joslin Date: Thu, 2 Jul 2026 18:20:56 +0700 Subject: [PATCH 07/11] fix: resolve clippy lints blocking the -D warnings gate cargo clippy --workspace --all-targets -- -D warnings is the house convention but the tree had drifted: is_none_or over map_or(true, ..), a field reassign after Default::default(), a derivable Default impl, two too_many_arguments sites (annotated, matching the 48 existing allow sites), three needless borrows, and a useless format! in a test fixture. All behavior-preserving; relay, notary-core, notary-server, and notary test suites pass. Signed-off-by: Jeremi Joslin --- crates/registry-notary-core/src/config.rs | 2 +- crates/registry-notary-server/src/api.rs | 7 ++++--- crates/registry-notary/tests/doctor_cli.rs | 8 +++----- crates/registry-relay/src/api/entity.rs | 6 ++++-- crates/registry-relay/src/config/mod.rs | 9 ++------- crates/registry-relay/src/query/mod.rs | 1 + 6 files changed, 15 insertions(+), 18 deletions(-) diff --git a/crates/registry-notary-core/src/config.rs b/crates/registry-notary-core/src/config.rs index 337c43ef..e51db6c6 100644 --- a/crates/registry-notary-core/src/config.rs +++ b/crates/registry-notary-core/src/config.rs @@ -631,7 +631,7 @@ impl StandaloneRegistryNotaryConfig { config_unsigned: self .config_trust .as_ref() - .map_or(true, |trust| trust.accepted_roots.is_empty()), + .is_none_or(|trust| trust.accepted_roots.is_empty()), self_attestation_enabled: self.self_attestation.enabled, transaction_token_anchor_configured: self.auth.access_token_signing.enabled, // DPoP/mTLS proof validation for transaction tokens is not yet diff --git a/crates/registry-notary-server/src/api.rs b/crates/registry-notary-server/src/api.rs index b5a6fc2a..6ac84e27 100644 --- a/crates/registry-notary-server/src/api.rs +++ b/crates/registry-notary-server/src/api.rs @@ -5239,20 +5239,20 @@ async fn oid4vci_token( return token_error_with_audit( &preauth, path, - Some(&configuration_id), + Some(configuration_id), SelfAttestationDenialCode::OperationDenied, TokenWireError::ServerError, ) .await; } }; - let c_nonce = match issue_c_nonce(&state, &configuration_id).await { + let c_nonce = match issue_c_nonce(&state, configuration_id).await { Some(c_nonce) => c_nonce, None => { return token_error_with_audit( &preauth, path, - Some(&configuration_id), + Some(configuration_id), SelfAttestationDenialCode::OperationDenied, TokenWireError::ServerError, ) @@ -8087,6 +8087,7 @@ fn require_self_attestation_evaluate_with_runtime_config( Ok(()) } +#[allow(clippy::too_many_arguments)] fn require_self_attestation_authorization_details( service_id: &str, config: &SelfAttestationConfig, diff --git a/crates/registry-notary/tests/doctor_cli.rs b/crates/registry-notary/tests/doctor_cli.rs index 5b29e31f..98662fd1 100644 --- a/crates/registry-notary/tests/doctor_cli.rs +++ b/crates/registry-notary/tests/doctor_cli.rs @@ -168,8 +168,7 @@ fn write_opencrvs_dci_config(tmp: &TempDir) -> PathBuf { let path = tmp.path().join("opencrvs-dci.yaml"); std::fs::write( &path, - format!( - r#" + r#" server: bind: 127.0.0.1:0 auth: @@ -240,7 +239,7 @@ evidence: field: UIN op: eq cardinality: one - fields: {{}} + fields: {} matching: policy_id: registryctl.opencrvs-dci.birth-record.lookup.v1 method: configured_lookup @@ -269,8 +268,7 @@ evidence: allowed: [predicate, redacted] formats: - application/vnd.registry-notary.claim-result+json -"# - ), +"#, ) .expect("config writes"); path diff --git a/crates/registry-relay/src/api/entity.rs b/crates/registry-relay/src/api/entity.rs index 9611e02f..362be1e4 100644 --- a/crates/registry-relay/src/api/entity.rs +++ b/crates/registry-relay/src/api/entity.rs @@ -1621,8 +1621,10 @@ struct EntityRecordQuery { } fn record_query_from_params(params: HashMap) -> Result { - let mut query = EntityRecordQuery::default(); - query.raw_params = params.clone(); + let mut query = EntityRecordQuery { + raw_params: params.clone(), + ..Default::default() + }; for (name, value) in params { match name.as_str() { "fields" => { diff --git a/crates/registry-relay/src/config/mod.rs b/crates/registry-relay/src/config/mod.rs index bf5e3ca8..8d088672 100644 --- a/crates/registry-relay/src/config/mod.rs +++ b/crates/registry-relay/src/config/mod.rs @@ -1330,18 +1330,13 @@ pub struct RequiredFilterBindingConfig { pub source: RequiredFilterBindingSource, } -#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Eq)] +#[derive(Debug, Clone, Copy, Default, Deserialize, PartialEq, Eq)] #[serde(rename_all = "snake_case")] pub enum RequiredFilterBindingSource { + #[default] PrincipalId, } -impl Default for RequiredFilterBindingSource { - fn default() -> Self { - Self::PrincipalId - } -} - #[derive(Debug, Clone, Default, Deserialize, PartialEq)] #[serde(deny_unknown_fields)] pub struct GovernedPolicyConfig { diff --git a/crates/registry-relay/src/query/mod.rs b/crates/registry-relay/src/query/mod.rs index e3718fc3..93963727 100644 --- a/crates/registry-relay/src/query/mod.rs +++ b/crates/registry-relay/src/query/mod.rs @@ -305,6 +305,7 @@ impl EntityQueryEngine { }) } + #[allow(clippy::too_many_arguments)] pub async fn read_record( &self, dataset_id: &str, From 9577b34cab359c824117e1c268010efd7c99d2da Mon Sep 17 00:00:00 2001 From: Jeremi Joslin Date: Thu, 2 Jul 2026 18:30:44 +0700 Subject: [PATCH 08/11] chore(deps): bump anyhow past RUSTSEC-2026-0190 Lockfile-only bump to 1.0.103. The two quick-xml advisories (RUSTSEC-2026-0194/0195) remain open: calamine 0.35 pins ^0.39 and phonenumber pins <=0.38 under the pinned Crosswalk dependency, so no upgrade path exists until upstream moves; no deny.toml ignore added. Signed-off-by: Jeremi Joslin --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index cb3b277e..e8431a3f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -157,9 +157,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.102" +version = "1.0.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" +checksum = "2a4385e2e34eb35d6b3efe798b9eb88096925d87726c0798709bf56d9ed84af3" [[package]] name = "approx" From 73eb6a653b9406aaca90ca555ccf2b2b7f003277 Mon Sep 17 00:00:00 2001 From: Jeremi Joslin Date: Thu, 2 Jul 2026 18:32:10 +0700 Subject: [PATCH 09/11] ci: promote clippy into the root gate The workspace is clippy-clean under -D warnings as of the preceding lint fixes, so gate it. The cargo-deny advisories section stays local: the remaining RUSTSEC advisories (quick-xml 0194/0195) have no upgrade path until calamine and phonenumber move upstream. Signed-off-by: Jeremi Joslin --- .github/workflows/ci.yml | 10 ++++++---- CONTRIBUTING.md | 7 ++++--- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index adb0b430..375e0c90 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -136,15 +136,17 @@ jobs: - name: Workspace check run: cargo check --locked --workspace --all-targets + - name: Clippy + run: cargo clippy --workspace --all-targets -- -D warnings + - name: Workspace tests run: cargo test --locked --workspace # Enforces the licenses, bans, and sources sections of deny.toml on every # Rust change. The advisories section is intentionally not gated here: - # the current tree fails `cargo deny check advisories` on fresh upstream - # RUSTSEC advisories (anyhow, quick-xml) whose fixes are dependency bumps, - # not code changes. Promote this to a full `cargo deny check` once the - # lockfile is updated so the advisories section passes. + # RUSTSEC-2026-0194/0195 (quick-xml) have no upgrade path until upstream + # calamine and phonenumber move off quick-xml <=0.39. Promote this to a + # full `cargo deny check` once those advisories are resolvable. - name: Cargo deny (licenses, bans, sources) run: cargo deny check bans licenses sources diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 5708342d..e1dac84b 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -111,15 +111,16 @@ Rust workspace: cargo metadata --locked --format-version 1 cargo fmt --check cargo check --locked --workspace --all-targets +cargo clippy --workspace --all-targets -- -D warnings cargo test --locked --workspace cargo deny check bans licenses sources (cd products/notary && just openapi-check) (cd crates/registry-relay && just openapi-contract) ``` -Clippy (`cargo clippy --workspace --all-targets -- -D warnings`) and the -cargo-deny advisories section are the house convention but are not yet in the -root gate; run them locally. +The cargo-deny advisories section is not yet in the root gate (open RUSTSEC +advisories on quick-xml have no upstream fix yet); run +`cargo deny check advisories` locally. Release and lab source checks: From ebc9325ca8c1f589ae08cda91be396522ecbfdc9 Mon Sep 17 00:00:00 2001 From: Jeremi Joslin Date: Thu, 2 Jul 2026 20:10:16 +0700 Subject: [PATCH 10/11] fix: stabilize review gates Signed-off-by: Jeremi Joslin --- .github/workflows/ci.yml | 9 ++----- .../tests/standalone_http.rs | 24 ++++++++++++++----- .../registry-notary.posture.valid.json | 2 -- .../registry-relay/tests/aggregates_entity.rs | 11 ++++++++- deny.toml | 10 ++++++++ .../openapi/registry-notary.openapi.json | 6 ++--- 6 files changed, 42 insertions(+), 20 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 375e0c90..907fd20d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -142,13 +142,8 @@ jobs: - name: Workspace tests run: cargo test --locked --workspace - # Enforces the licenses, bans, and sources sections of deny.toml on every - # Rust change. The advisories section is intentionally not gated here: - # RUSTSEC-2026-0194/0195 (quick-xml) have no upgrade path until upstream - # calamine and phonenumber move off quick-xml <=0.39. Promote this to a - # full `cargo deny check` once those advisories are resolvable. - - name: Cargo deny (licenses, bans, sources) - run: cargo deny check bans licenses sources + - name: Cargo deny + run: cargo deny check - name: Notary OpenAPI baseline working-directory: products/notary diff --git a/crates/registry-notary-server/tests/standalone_http.rs b/crates/registry-notary-server/tests/standalone_http.rs index 5891d557..81ca30a6 100644 --- a/crates/registry-notary-server/tests/standalone_http.rs +++ b/crates/registry-notary-server/tests/standalone_http.rs @@ -932,6 +932,18 @@ fn assert_matches_posture_schema(body: &Value) { ); } +fn assert_standards_artifacts_omit_sha256(body: &Value, label: &str) { + let artifacts = body["standards_artifacts"] + .as_object() + .expect("posture standards_artifacts is object"); + for (name, artifact) in artifacts { + assert!( + artifact.get("sha256").is_none(), + "{label} standards_artifacts.{name} includes sha256, but live Notary posture no longer emits it" + ); + } +} + fn assert_matches_admin_capabilities_schema(body: &Value) { let schema: Value = serde_json::from_str(registry_platform_ops::ADMIN_CAPABILITIES_SCHEMA_V1) .expect("admin capabilities schema parses"); @@ -7540,6 +7552,8 @@ async fn admin_posture_top_level_keys_match_documented_example() { let default_example: Value = serde_json::from_str(registry_platform_ops::NOTARY_POSTURE_EXAMPLE_V1) .expect("notary posture example parses"); + assert_standards_artifacts_omit_sha256(&default_body, "live default posture"); + assert_standards_artifacts_omit_sha256(&default_example, "NOTARY_POSTURE_EXAMPLE_V1"); let default_example_keys = default_example .as_object() .expect("example posture is object") @@ -10368,14 +10382,13 @@ async fn request_body_limit_returns_413_above_threshold() { audit_path.to_str().expect("audit path is UTF-8"), )) .expect("standalone router builds"); - let server = TestServer::builder().http_transport().build(app); + let server = TestServer::new(app); - let too_large = Bytes::from(vec![b' '; 1024 * 1024 + 1]); let response = server .post("/v1/evaluations") .add_header("x-api-key", "api-token") .add_header("content-type", "application/json") - .bytes(too_large) + .add_header(header::CONTENT_LENGTH, "1048577") .await; response.assert_status(StatusCode::PAYLOAD_TOO_LARGE); @@ -14672,15 +14685,14 @@ async fn request_body_limit_413_carries_server_owned_request_id() { audit_path.to_str().expect("audit path is UTF-8"), )) .expect("standalone router builds"); - let server = TestServer::builder().http_transport().build(app); - let too_large = Bytes::from(vec![b' '; 1024 * 1024 + 1]); + let server = TestServer::new(app); let response = server .post("/v1/evaluations") .add_header("x-api-key", "api-token") .add_header("content-type", "application/json") + .add_header(header::CONTENT_LENGTH, "1048577") .add_header("x-request-id", "client-supplied-id") - .bytes(too_large) .await; response.assert_status(StatusCode::PAYLOAD_TOO_LARGE); diff --git a/crates/registry-platform-ops/examples/registry-notary.posture.valid.json b/crates/registry-platform-ops/examples/registry-notary.posture.valid.json index 5d35bcc3..153f2fe5 100644 --- a/crates/registry-platform-ops/examples/registry-notary.posture.valid.json +++ b/crates/registry-platform-ops/examples/registry-notary.posture.valid.json @@ -45,12 +45,10 @@ "standards_artifacts": { "evidence_service_discovery": { "media_type": "application/json", - "sha256": "sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", "observed_status": "available" }, "jwks": { "media_type": "application/jwk-set+json", - "sha256": "sha256:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc", "observed_status": "available" } }, diff --git a/crates/registry-relay/tests/aggregates_entity.rs b/crates/registry-relay/tests/aggregates_entity.rs index 81c72b56..06932a77 100644 --- a/crates/registry-relay/tests/aggregates_entity.rs +++ b/crates/registry-relay/tests/aggregates_entity.rs @@ -952,7 +952,16 @@ audit:"#, "redacted aggregate dimension must not be present in observations: {observations:?}" ); assert_eq!(body["structure"]["dimensions"], json!([])); - assert_eq!(observations[0]["individual_count"], 2); + let mut counts = observations + .iter() + .map(|row| { + row["individual_count"] + .as_i64() + .expect("individual_count is an integer") + }) + .collect::>(); + counts.sort_unstable(); + assert_eq!(counts, vec![1, 2]); } #[tokio::test] diff --git a/deny.toml b/deny.toml index e4868f21..c1cc6d06 100644 --- a/deny.toml +++ b/deny.toml @@ -22,6 +22,16 @@ ignore = [ # request-facing parsing. Review when DataFusion moves off `paste` or when # upgrading the DataFusion major version. "RUSTSEC-2024-0436", + # RUSTSEC-2026-0194: quick-xml <=0.39 has quadratic duplicate-attribute + # checks. It is currently pinned by calamine 0.35 in Relay's XLSX connector + # and by phonenumber through the pinned Crosswalk stack. Review when + # calamine supports quick-xml >=0.41, when Crosswalk/phonenumber moves off + # quick-xml <=0.38, or before accepting untrusted end-user XLSX/XML inputs. + "RUSTSEC-2026-0194", + # RUSTSEC-2026-0195: quick-xml <=0.39 can over-allocate namespace + # declarations in NsReader. Same temporary scope and review triggers as + # RUSTSEC-2026-0194. + "RUSTSEC-2026-0195", ] [licenses] diff --git a/products/notary/openapi/registry-notary.openapi.json b/products/notary/openapi/registry-notary.openapi.json index b3640c91..9671377d 100644 --- a/products/notary/openapi/registry-notary.openapi.json +++ b/products/notary/openapi/registry-notary.openapi.json @@ -2836,13 +2836,11 @@ "standards_artifacts": { "evidence_service_discovery": { "media_type": "application/json", - "observed_status": "available", - "sha256": "sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" + "observed_status": "available" }, "jwks": { "media_type": "application/jwk-set+json", - "observed_status": "available", - "sha256": "sha256:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc" + "observed_status": "available" } }, "tier": "default" From c180c2c424fed6184e3e69b6651d35d8bb502650 Mon Sep 17 00:00:00 2001 From: Jeremi Joslin Date: Thu, 2 Jul 2026 20:27:06 +0700 Subject: [PATCH 11/11] fix(lab): repair monorepo setup defaults Signed-off-by: Jeremi Joslin --- .gitmodules | 9 +++++ lab/.gitmodules | 9 ----- lab/README.md | 47 ++++++++++++--------------- lab/justfile | 11 ++++--- lab/scripts/test_esignet_relay_lab.py | 10 ++++-- 5 files changed, 43 insertions(+), 43 deletions(-) create mode 100644 .gitmodules delete mode 100644 lab/.gitmodules diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 00000000..9309c0f1 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,9 @@ +[submodule "lab/vendor/crosswalk"] + path = lab/vendor/crosswalk + url = git@github.com:PublicSchema/crosswalk.git +[submodule "lab/vendor/registry-atlas"] + path = lab/vendor/registry-atlas + url = git@github.com:jeremi/registry-atlas.git +[submodule "lab/vendor/esignet-relay-authenticator"] + path = lab/vendor/esignet-relay-authenticator + url = git@github.com:jeremi/esignet-relay-authenticator.git diff --git a/lab/.gitmodules b/lab/.gitmodules deleted file mode 100644 index dca85c26..00000000 --- a/lab/.gitmodules +++ /dev/null @@ -1,9 +0,0 @@ -[submodule "vendor/crosswalk"] - path = vendor/crosswalk - url = git@github.com:PublicSchema/crosswalk.git -[submodule "vendor/registry-atlas"] - path = vendor/registry-atlas - url = git@github.com:jeremi/registry-atlas.git -[submodule "vendor/esignet-relay-authenticator"] - path = vendor/esignet-relay-authenticator - url = git@github.com:jeremi/esignet-relay-authenticator.git diff --git a/lab/README.md b/lab/README.md index a2adb561..270eb6c4 100644 --- a/lab/README.md +++ b/lab/README.md @@ -65,13 +65,12 @@ just smoke just client ``` -`just setup` initializes the `vendor/` submodules this lab still depends on -(Crosswalk, registry-atlas, the eSignet Relay authenticator). The Registry -Relay, Notary, Platform, and Manifest source now live as crates in the -monorepo root rather than as separate submodules or sibling checkouts, so the -plain commands above only resolve correctly when run through a monorepo-aware -recipe such as `just release-fast` (see below). `just generate` and `just -smoke` fail early when `registry-manifest` is missing. +`just setup` initializes the remaining `lab/vendor/` submodules: Crosswalk, +registry-atlas, and the eSignet Relay authenticator. The Registry Relay, +Notary, Platform, and Manifest source now live as crates in the monorepo root; +the lab justfile defaults to those monorepo paths, while Crosswalk, +registry-atlas, and the eSignet Relay authenticator stay pinned as vendor +submodules. `just generate` writes `.env`, fixture files, and static metadata. Run it before `just up` the first time, and run it again after pulling demo changes that add @@ -92,15 +91,10 @@ in this monorepo use: just release-fast ``` -Plain `just quick` predates the monorepo migration: it still expects sibling -`registry-relay`, `registry-notary`, `registry-platform`, and -`registry-manifest` checkouts (or the now-removed `vendor/` submodule pins for -those) and fails without `REGISTRY_RELAY_SOURCE_DIR`, -`REGISTRY_NOTARY_SOURCE_DIR`, `REGISTRY_PLATFORM_SOURCE_DIR`, and -`REGISTRY_MANIFEST_REPO` overrides. `just release-fast` runs -`scripts/release-check.sh`, which defaults to -`REGISTRY_LAB_RELEASE_SOURCE_MODE=monorepo` and wires those variables to the -monorepo root and `../crates/registry-relay` automatically. +Plain `just quick` runs the same monorepo defaults for generate, build, smoke, +OpenFn, and client checks. `just release-fast` runs `scripts/release-check.sh`, +which defaults to `REGISTRY_LAB_RELEASE_SOURCE_MODE=monorepo` and also gates +the release source model. If you still have sibling Platform, Relay, and Notary checkouts and want to validate against them directly, `commons-check` remains available: @@ -319,11 +313,11 @@ removes demo volumes after a successful run. Use the individual checks above when you want to keep the current Postgres, Zitadel, or OpenFn containers running for inspection. -The `justfile` defaults `REGISTRY_RELAY_SOURCE_DIR`, -`REGISTRY_NOTARY_SOURCE_DIR`, and `REGISTRY_PLATFORM_SOURCE_DIR` to sibling -checkouts when present, otherwise to the pinned `vendor/` submodules. -`REGISTRY_OPENFN_NOTARY_SOURCE_DIR` follows `REGISTRY_NOTARY_SOURCE_DIR` by -default. Override those variables when you want to build from another local +The `justfile` defaults `REGISTRY_RELAY_SOURCE_DIR` to +`../crates/registry-relay` and `REGISTRY_NOTARY_SOURCE_DIR`, +`REGISTRY_PLATFORM_SOURCE_DIR`, and `REGISTRY_MANIFEST_REPO` to the monorepo +root. `REGISTRY_OPENFN_NOTARY_SOURCE_DIR` follows `REGISTRY_NOTARY_SOURCE_DIR` +by default. Override those variables when you want to build from another local path. ## Live Notary Redis checks @@ -634,10 +628,9 @@ the full setup sequence, which starts with `just generate` before This demo keeps runtime orchestration, fixtures, static metadata config, and walkthrough scripts in this repository. Registry Platform, Registry Relay, Registry Notary, and Registry Manifest now live as crates in the -registry-stack monorepo root rather than as `vendor/` submodules; the justfile -and scripts default to the monorepo checkout (`..` from `lab/`) when no -sibling checkout is present. The following remain real submodules under -`vendor/`: +registry-stack monorepo root rather than as `vendor/` submodules; the lab +justfile and release scripts default to the monorepo source paths. The +following remain real submodules under `lab/vendor/`: - `vendor/crosswalk`: the Crosswalk mapping engine, pinned as a git dependency. - `vendor/registry-atlas`: registry-atlas source. @@ -645,8 +638,8 @@ sibling checkout is present. The following remain real submodules under used by `just esignet-up`. The Compose build uses Docker named contexts, and defaults to this monorepo -checkout for Platform, Relay, and Notary. Override with sibling checkouts when -you need to build from another local path: +checkout for Platform, Manifest, and Notary plus the Relay crate path. Override +with sibling checkouts when you need to build from another local path: ```bash REGISTRY_RELAY_SOURCE_DIR=../registry-relay \ diff --git a/lab/justfile b/lab/justfile index 88e84073..8bb2a194 100644 --- a/lab/justfile +++ b/lab/justfile @@ -1,10 +1,11 @@ set dotenv-load := true set positional-arguments := true -default_relay_src := if path_exists("../registry-relay/Cargo.toml") == "true" { "../registry-relay" } else { if path_exists("../../registry-relay/Cargo.toml") == "true" { "../../registry-relay" } else { "./vendor/registry-relay" } } -default_notary_src := if path_exists("../registry-notary/Cargo.toml") == "true" { "../registry-notary" } else { if path_exists("../../registry-notary/Cargo.toml") == "true" { "../../registry-notary" } else { "./vendor/registry-notary" } } -default_platform_src := if path_exists("../registry-platform/Cargo.toml") == "true" { "../registry-platform" } else { if path_exists("../../registry-platform/Cargo.toml") == "true" { "../../registry-platform" } else { "./vendor/registry-platform" } } -default_manifest_src := if path_exists("../registry-manifest/Cargo.toml") == "true" { "../registry-manifest" } else { if path_exists("../../registry-manifest/Cargo.toml") == "true" { "../../registry-manifest" } else { "./vendor/registry-manifest" } } +default_stack_src := if path_exists("../Cargo.toml") == "true" { if path_exists("../crates/registry-relay/Cargo.toml") == "true" { ".." } else { "" } } else { "" } +default_relay_src := if default_stack_src != "" { "../crates/registry-relay" } else { if path_exists("../registry-relay/Cargo.toml") == "true" { "../registry-relay" } else { if path_exists("../../registry-relay/Cargo.toml") == "true" { "../../registry-relay" } else { "./vendor/registry-relay" } } } +default_notary_src := if default_stack_src != "" { default_stack_src } else { if path_exists("../registry-notary/Cargo.toml") == "true" { "../registry-notary" } else { if path_exists("../../registry-notary/Cargo.toml") == "true" { "../../registry-notary" } else { "./vendor/registry-notary" } } } +default_platform_src := if default_stack_src != "" { default_stack_src } else { if path_exists("../registry-platform/Cargo.toml") == "true" { "../registry-platform" } else { if path_exists("../../registry-platform/Cargo.toml") == "true" { "../../registry-platform" } else { "./vendor/registry-platform" } } } +default_manifest_src := if default_stack_src != "" { default_stack_src } else { if path_exists("../registry-manifest/Cargo.toml") == "true" { "../registry-manifest" } else { if path_exists("../../registry-manifest/Cargo.toml") == "true" { "../../registry-manifest" } else { "./vendor/registry-manifest" } } } default_esignet_relay_authenticator_src := "./vendor/esignet-relay-authenticator" relay_src := env_var_or_default("REGISTRY_RELAY_SOURCE_DIR", default_relay_src) @@ -40,7 +41,7 @@ default: # Initialize git submodules. setup: - git submodule update --init --recursive + git -C "$$(git rev-parse --show-toplevel)" submodule update --init --recursive -- lab/vendor/crosswalk lab/vendor/registry-atlas lab/vendor/esignet-relay-authenticator # Generate deterministic fixtures, demo secrets, and static metadata. generate: diff --git a/lab/scripts/test_esignet_relay_lab.py b/lab/scripts/test_esignet_relay_lab.py index dff5a1b3..2ff6aaac 100644 --- a/lab/scripts/test_esignet_relay_lab.py +++ b/lab/scripts/test_esignet_relay_lab.py @@ -8,12 +8,17 @@ ROOT = Path(__file__).resolve().parents[1] +REPO_ROOT = ROOT.parent def text(path: str) -> str: return (ROOT / path).read_text(encoding="utf-8") +def repo_text(path: str) -> str: + return (REPO_ROOT / path).read_text(encoding="utf-8") + + class EsignetRelayLabTest(unittest.TestCase): def test_local_compose_uses_relay_authenticator_without_mock_ida(self) -> None: compose = text("compose.esignet-live.yaml") @@ -63,9 +68,10 @@ def test_user_facing_login_text_no_longer_mentions_static_pin(self) -> None: self.assertNotIn("if eSignet asks", value) def test_plugin_submodule_is_declared(self) -> None: - modules = text(".gitmodules") + modules = repo_text(".gitmodules") - self.assertIn("[submodule \"vendor/esignet-relay-authenticator\"]", modules) + self.assertIn("[submodule \"lab/vendor/esignet-relay-authenticator\"]", modules) + self.assertIn("path = lab/vendor/esignet-relay-authenticator", modules) self.assertIn("url = git@github.com:jeremi/esignet-relay-authenticator.git", modules) def test_esignet_relay_image_rebuilds_plugin_classes_from_source(self) -> None: