From 55280ab2cc5be07f40ae3af166c0a684eff0f400 Mon Sep 17 00:00:00 2001 From: Jeremi Joslin Date: Thu, 20 Aug 2026 19:49:52 +0200 Subject: [PATCH 1/2] chore: pin Registry Stack v0.23.0 v0.23.0 upgrades `h2` to 0.4.17, past RUSTSEC-2026-0258, where a peer could queue empty DATA frames without limit on the HTTP/2 path that Relay and Evidence both serve over. That is the reason to move. The bump is not only a re-pin. The Evidence deployment contract gained three required fields, so the six cell bundles are authored too: - `service.publicOrigin`, the one routed origin RFC 9728 discovery answers on, set to each cell's own public host. - `requirements[].handle` and `requirements[].concepts[].handle`, the stable keys a client reads a result under, each authored to the last segment of the identifier it names. Because the bundle content changed, a deployment's already-staged runtime no longer matches what this version stages: locally `just reset` before `just up`, and a hosted rollout cannot reuse the existing Evidence and Relay runtime volumes. Signed-off-by: Jeremi Joslin --- README.md | 2 +- docs/changelog.md | 24 +++++++++++++++++++ docs/hosted-deployment.md | 2 +- evidence/cells/cra/bundle/evidence.yaml | 17 +++++++------ .../cells/mosd-programme/bundle/evidence.yaml | 7 +++--- evidence/cells/nagdi/bundle/evidence.yaml | 12 ++++++---- evidence/cells/nia/bundle/evidence.yaml | 12 ++++++---- evidence/cells/sipf/bundle/evidence.yaml | 12 ++++++---- evidence/cells/sro/bundle/evidence.yaml | 7 +++--- evidence/tests/test_cells.py | 19 +++++++++++++++ home/src/lib/server/data.test.ts | 4 ++-- justfile | 2 +- scripts/check-registry-stack-release-pin.py | 2 +- scripts/test_build_registry_stack_runtime.py | 10 ++++---- scripts/test_hosted_image_manifest.py | 2 +- scripts/test_registry_stack_release_pin.py | 16 ++++++------- versions.env | 22 ++++++++--------- 17 files changed, 113 insertions(+), 59 deletions(-) diff --git a/README.md b/README.md index 89c5d21..914ee3f 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ Evidence runtime cannot classify Relay's concealed `consultation.unresolved` response as a data-free unresolved consultation. Registry Stack v0.21.0 is the first coherent release that contains both capabilities and publishes official Relay, Evidence, and Mint runtime images. Solmara tracks that release line and -currently pins v0.22.0, recording its release tag and source commit, those +currently pins v0.23.0, recording its release tag and source commit, those official OCI references by digest, and the `relayctl` binary checksum in `versions.env`. The release handoff and every hosted deployment consume those same full references without reconstructing them from a second deployment diff --git a/docs/changelog.md b/docs/changelog.md index cbae640..12869ef 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -3,6 +3,30 @@ A small dated log of what changed in the visitor center and the lab topology. Newest entry first. +## 2026-08-20 Registry Stack v0.23.0 + +Solmara pins Registry Stack v0.23.0 across `versions.env`, the release-pin +gate, and the home release-identity surface. The Relay, Evidence, and Mint OCI +references move to the v0.23.0 digests, and the Relayctl authoring helper is +built from the v0.23.0 `linux-amd64` release asset and its checksum. The +topology is unchanged. The reason to move is security: v0.23.0 upgrades `h2` +to 0.4.17, past RUSTSEC-2026-0258, where a peer could queue unlimited empty +DATA frames on the HTTP/2 path that Relay and Evidence both serve over. + +The Evidence deployment contract gained three required fields, so the six cell +bundles are authored rather than only re-pinned. `service.publicOrigin` names +the one routed origin RFC 9728 discovery answers on, which is each cell's own +`-evidence.solmara.registrystack.org`. Every requirement and every +concept now carries a `handle`, the stable key a client reads a result under; +each is authored to the last segment of the identifier it names. v0.23.0 also +adds Registry Discovery and published Discovery client packages, neither of +which the lab uses yet. + +Because the Evidence bundle content changed, the staged runtime a deployment +already holds no longer matches the one this version stages. A local run needs +`just reset` before `just up`. A hosted rollout cannot reuse the existing +Evidence and Relay runtime volumes. + ## 2026-08-19 Registry Stack v0.22.0 Solmara pins Registry Stack v0.22.0 across `versions.env`, the release-pin diff --git a/docs/hosted-deployment.md b/docs/hosted-deployment.md index 64c5fa3..26656a7 100644 --- a/docs/hosted-deployment.md +++ b/docs/hosted-deployment.md @@ -17,7 +17,7 @@ Registry Stack v0.20.0 remains immutable and does not contain the Evidence capability required by this lab. v0.20.1 contains the required runtime capabilities but does not publish official Evidence and Mint OCI images. Registry Stack v0.21.0 is the first coherent release with all three official -runtime images; the lab currently pins v0.22.0 from that line. Do not move a +runtime images; the lab currently pins v0.23.0 from that line. Do not move a release, substitute a floating source reference, or recreate those runtime images in Solmara. diff --git a/evidence/cells/cra/bundle/evidence.yaml b/evidence/cells/cra/bundle/evidence.yaml index 487e7b9..26bdd66 100644 --- a/evidence/cells/cra/bundle/evidence.yaml +++ b/evidence/cells/cra/bundle/evidence.yaml @@ -1,6 +1,6 @@ version: 1 assuranceProfile: production -service: { providerId: https://id.registrystack.org/solmara/evidence/cra, trustDomain: https://id.registrystack.org/solmara/trust-domain/lab } +service: { providerId: https://id.registrystack.org/solmara/evidence/cra, trustDomain: https://id.registrystack.org/solmara/trust-domain/lab, publicOrigin: https://cra-evidence.solmara.registrystack.org } issuer: { id: did:web:id.registrystack.org:solmara:authority:cra } authentication: { kind: oidc-access-token, issuer: https://mint.solmara.registrystack.org, audiences: [solmara-runtime], tokenTypes: [at+jwt], algorithms: [ES256], jwksUri: https://mint.solmara.registrystack.org/.well-known/jwks.json, principalClaim: sub, requesterTagsClaim: evidence_tags, evidenceAudienceClaim: evidence_audience, grantIdClaim: evidence_grant_id, grantAuthorityClaim: evidence_authority, maximumTokenLifetimeSeconds: 300, revokedKeyIds: [] } audit: { format: keyed-jsonl, hashSecretRef: secret:file/audit-hmac-key, hashKeyVersion: 1, failClosed: true } @@ -86,7 +86,8 @@ authorityProfiles: - { requirement: https://id.registrystack.org/solmara/requirement/cra-pension-death/v1, purpose: pension-payment-review, audienceFrom: authenticated-requester, responseFormats: [signed-jws], subjects: [{ role: subject, selectorProfile: solmara-uin-v1, valueOrigin: request }] } - { requirement: https://id.registrystack.org/solmara/requirement/cra-citizen-record/v1, purpose: citizen-self-service, audienceFrom: authenticated-requester, responseFormats: [signed-jws], subjects: [{ role: subject, selectorProfile: solmara-uin-v1, valueOrigin: request }] } requirements: - - id: https://id.registrystack.org/solmara/requirement/cra-child-benefit/v1 + - handle: cra-child-benefit + id: https://id.registrystack.org/solmara/requirement/cra-child-benefit/v1 kind: information-requirement acquisition: { kind: single, source: cra-child-benefit } purposes: [child-benefit-review] @@ -96,11 +97,12 @@ requirements: observationTimezone: Asia/Bangkok validitySeconds: 3600 derivation: { script: derivations/child-benefit.rhai, parameters: { as_of_date: '2026-07-14' } } - concepts: [{ id: https://id.registrystack.org/solmara/concept/birth-is-registered, form: boolean, required: true, constraints: {} }, { id: https://id.registrystack.org/solmara/concept/child-age-under-5, form: boolean, required: true, constraints: {} }] + concepts: [{ handle: birth-is-registered, id: https://id.registrystack.org/solmara/concept/birth-is-registered, form: boolean, required: true, constraints: {} }, { handle: child-age-under-5, id: https://id.registrystack.org/solmara/concept/child-age-under-5, form: boolean, required: true, constraints: {} }] fixtures: fixtures/child-benefit.yaml disclosureGuard: { families: [https://id.registrystack.org/solmara/disclosure-family/cra-child-benefit] } existenceDisclosure: collapse-unresolved - - id: https://id.registrystack.org/solmara/requirement/cra-pension-death/v1 + - handle: cra-pension-death + id: https://id.registrystack.org/solmara/requirement/cra-pension-death/v1 kind: information-requirement acquisition: { kind: single, source: cra-pension } purposes: [pension-payment-review] @@ -109,11 +111,12 @@ requirements: evidenceType: https://id.registrystack.org/solmara/evidence-type/cra-death-status/v1 validitySeconds: 300 derivation: { script: derivations/deceased.rhai, parameters: {} } - concepts: [{ id: https://id.registrystack.org/solmara/concept/person-is-deceased, form: boolean, required: true, constraints: {} }] + concepts: [{ handle: person-is-deceased, id: https://id.registrystack.org/solmara/concept/person-is-deceased, form: boolean, required: true, constraints: {} }] fixtures: fixtures/pension.yaml disclosureGuard: { families: [https://id.registrystack.org/solmara/disclosure-family/cra-pension] } existenceDisclosure: collapse-unresolved - - id: https://id.registrystack.org/solmara/requirement/cra-citizen-record/v1 + - handle: cra-citizen-record + id: https://id.registrystack.org/solmara/requirement/cra-citizen-record/v1 kind: information-requirement acquisition: { kind: single, source: cra-citizen } purposes: [citizen-self-service] @@ -122,7 +125,7 @@ requirements: evidenceType: https://id.registrystack.org/solmara/evidence-type/civil-record-linked/v1 validitySeconds: 300 derivation: { script: derivations/linked.rhai, parameters: {} } - concepts: [{ id: https://id.registrystack.org/solmara/concept/civil-record-linked, form: boolean, required: true, constraints: {} }] + concepts: [{ handle: civil-record-linked, id: https://id.registrystack.org/solmara/concept/civil-record-linked, form: boolean, required: true, constraints: {} }] fixtures: fixtures/citizen.yaml disclosureGuard: { families: [https://id.registrystack.org/solmara/disclosure-family/cra-citizen] } existenceDisclosure: collapse-unresolved diff --git a/evidence/cells/mosd-programme/bundle/evidence.yaml b/evidence/cells/mosd-programme/bundle/evidence.yaml index 84a2e8b..ae651c6 100644 --- a/evidence/cells/mosd-programme/bundle/evidence.yaml +++ b/evidence/cells/mosd-programme/bundle/evidence.yaml @@ -1,6 +1,6 @@ version: 1 assuranceProfile: production -service: { providerId: https://id.registrystack.org/solmara/evidence/mosd-programme, trustDomain: https://id.registrystack.org/solmara/trust-domain/lab } +service: { providerId: https://id.registrystack.org/solmara/evidence/mosd-programme, trustDomain: https://id.registrystack.org/solmara/trust-domain/lab, publicOrigin: https://mosd-programme-evidence.solmara.registrystack.org } issuer: { id: did:web:id.registrystack.org:solmara:authority:mosd-programme-mis } authentication: { kind: oidc-access-token, issuer: https://mint.solmara.registrystack.org, audiences: [solmara-runtime], tokenTypes: [at+jwt], algorithms: [ES256], jwksUri: https://mint.solmara.registrystack.org/.well-known/jwks.json, principalClaim: sub, requesterTagsClaim: evidence_tags, evidenceAudienceClaim: evidence_audience, grantIdClaim: evidence_grant_id, grantAuthorityClaim: evidence_authority, maximumTokenLifetimeSeconds: 300, revokedKeyIds: [] } audit: { format: keyed-jsonl, hashSecretRef: secret:file/audit-hmac-key, hashKeyVersion: 1, failClosed: true } @@ -39,7 +39,8 @@ authorityProfiles: requesterTags: [solmara-demo] grants: [{ requirement: https://id.registrystack.org/solmara/requirement/mosd-child-benefit/v1, purpose: child-benefit-review, audienceFrom: authenticated-requester, responseFormats: [signed-jws], subjects: [{ role: subject, selectorProfile: solmara-uin-v1, valueOrigin: request }] }] requirements: - - id: https://id.registrystack.org/solmara/requirement/mosd-child-benefit/v1 + - handle: mosd-child-benefit + id: https://id.registrystack.org/solmara/requirement/mosd-child-benefit/v1 kind: information-requirement acquisition: { kind: single, source: mosd-child-benefit } purposes: [child-benefit-review] @@ -48,7 +49,7 @@ requirements: evidenceType: https://id.registrystack.org/solmara/evidence-type/not-enrolled/v1 validitySeconds: 300 derivation: { script: derivations/not-enrolled.rhai, parameters: {} } - concepts: [{ id: https://id.registrystack.org/solmara/concept/not-already-enrolled, form: boolean, required: true, constraints: {} }] + concepts: [{ handle: not-already-enrolled, id: https://id.registrystack.org/solmara/concept/not-already-enrolled, form: boolean, required: true, constraints: {} }] fixtures: fixtures/child-benefit.yaml disclosureGuard: { families: [https://id.registrystack.org/solmara/disclosure-family/mosd-child-benefit] } existenceDisclosure: collapse-unresolved diff --git a/evidence/cells/nagdi/bundle/evidence.yaml b/evidence/cells/nagdi/bundle/evidence.yaml index 5838edb..903e5b7 100644 --- a/evidence/cells/nagdi/bundle/evidence.yaml +++ b/evidence/cells/nagdi/bundle/evidence.yaml @@ -1,6 +1,6 @@ version: 1 assuranceProfile: production -service: { providerId: https://id.registrystack.org/solmara/evidence/nagdi, trustDomain: https://id.registrystack.org/solmara/trust-domain/lab } +service: { providerId: https://id.registrystack.org/solmara/evidence/nagdi, trustDomain: https://id.registrystack.org/solmara/trust-domain/lab, publicOrigin: https://nagdi-evidence.solmara.registrystack.org } issuer: { id: did:web:id.registrystack.org:solmara:authority:nagdi } authentication: { kind: oidc-access-token, issuer: https://mint.solmara.registrystack.org, audiences: [solmara-runtime], tokenTypes: [at+jwt], algorithms: [ES256], jwksUri: https://mint.solmara.registrystack.org/.well-known/jwks.json, principalClaim: sub, requesterTagsClaim: evidence_tags, evidenceAudienceClaim: evidence_audience, grantIdClaim: evidence_grant_id, grantAuthorityClaim: evidence_authority, maximumTokenLifetimeSeconds: 300, revokedKeyIds: [] } audit: { format: keyed-jsonl, hashSecretRef: secret:file/audit-hmac-key, hashKeyVersion: 1, failClosed: true } @@ -64,7 +64,8 @@ authorityProfiles: - { requirement: https://id.registrystack.org/solmara/requirement/nagdi-voucher/v1, purpose: voucher-eligibility-review, audienceFrom: authenticated-requester, responseFormats: [signed-jws], subjects: [{ role: subject, selectorProfile: farmer-reference-v1, valueOrigin: request }] } - { requirement: https://id.registrystack.org/solmara/requirement/nagdi-livestock/v1, purpose: livestock-movement-control, audienceFrom: authenticated-requester, responseFormats: [signed-jws], subjects: [{ role: subject, selectorProfile: farmer-reference-v1, valueOrigin: request }] } requirements: - - id: https://id.registrystack.org/solmara/requirement/nagdi-voucher/v1 + - handle: nagdi-voucher + id: https://id.registrystack.org/solmara/requirement/nagdi-voucher/v1 kind: information-requirement acquisition: { kind: single, source: nagdi-voucher } purposes: [voucher-eligibility-review] @@ -73,11 +74,12 @@ requirements: evidenceType: https://id.registrystack.org/solmara/evidence-type/climate-smart-voucher/v1 validitySeconds: 300 derivation: { script: derivations/voucher.rhai, parameters: {} } - concepts: [{ id: https://id.registrystack.org/solmara/concept/farmer-registered, form: boolean, required: true, constraints: {} }, { id: https://id.registrystack.org/solmara/concept/data-use-authorized-for-purpose, form: boolean, required: true, constraints: {} }, { id: https://id.registrystack.org/solmara/concept/eligible-for-climate-smart-input-voucher, form: boolean, required: true, constraints: {} }] + concepts: [{ handle: farmer-registered, id: https://id.registrystack.org/solmara/concept/farmer-registered, form: boolean, required: true, constraints: {} }, { handle: data-use-authorized-for-purpose, id: https://id.registrystack.org/solmara/concept/data-use-authorized-for-purpose, form: boolean, required: true, constraints: {} }, { handle: eligible-for-climate-smart-input-voucher, id: https://id.registrystack.org/solmara/concept/eligible-for-climate-smart-input-voucher, form: boolean, required: true, constraints: {} }] fixtures: fixtures/voucher.yaml disclosureGuard: { families: [https://id.registrystack.org/solmara/disclosure-family/nagdi-voucher] } existenceDisclosure: collapse-unresolved - - id: https://id.registrystack.org/solmara/requirement/nagdi-livestock/v1 + - handle: nagdi-livestock + id: https://id.registrystack.org/solmara/requirement/nagdi-livestock/v1 kind: information-requirement acquisition: { kind: single, source: nagdi-livestock } purposes: [livestock-movement-control] @@ -86,7 +88,7 @@ requirements: evidenceType: https://id.registrystack.org/solmara/evidence-type/livestock-movement/v1 validitySeconds: 300 derivation: { script: derivations/livestock.rhai, parameters: {} } - concepts: [{ id: https://id.registrystack.org/solmara/concept/registered-herd, form: boolean, required: true, constraints: {} }, { id: https://id.registrystack.org/solmara/concept/origin-district-not-quarantined-for-species, form: boolean, required: true, constraints: {} }, { id: https://id.registrystack.org/solmara/concept/eligible-for-livestock-movement-permit, form: boolean, required: true, constraints: {} }] + concepts: [{ handle: registered-herd, id: https://id.registrystack.org/solmara/concept/registered-herd, form: boolean, required: true, constraints: {} }, { handle: origin-district-not-quarantined-for-species, id: https://id.registrystack.org/solmara/concept/origin-district-not-quarantined-for-species, form: boolean, required: true, constraints: {} }, { handle: eligible-for-livestock-movement-permit, id: https://id.registrystack.org/solmara/concept/eligible-for-livestock-movement-permit, form: boolean, required: true, constraints: {} }] fixtures: fixtures/livestock.yaml disclosureGuard: { families: [https://id.registrystack.org/solmara/disclosure-family/nagdi-livestock] } existenceDisclosure: collapse-unresolved diff --git a/evidence/cells/nia/bundle/evidence.yaml b/evidence/cells/nia/bundle/evidence.yaml index 275d41d..f5fb936 100644 --- a/evidence/cells/nia/bundle/evidence.yaml +++ b/evidence/cells/nia/bundle/evidence.yaml @@ -1,6 +1,6 @@ version: 1 assuranceProfile: production -service: { providerId: https://id.registrystack.org/solmara/evidence/nia, trustDomain: https://id.registrystack.org/solmara/trust-domain/lab } +service: { providerId: https://id.registrystack.org/solmara/evidence/nia, trustDomain: https://id.registrystack.org/solmara/trust-domain/lab, publicOrigin: https://nia-evidence.solmara.registrystack.org } issuer: { id: did:web:id.registrystack.org:solmara:authority:nia } authentication: { kind: oidc-access-token, issuer: https://mint.solmara.registrystack.org, audiences: [solmara-runtime], tokenTypes: [at+jwt], algorithms: [ES256], jwksUri: https://mint.solmara.registrystack.org/.well-known/jwks.json, principalClaim: sub, requesterTagsClaim: evidence_tags, evidenceAudienceClaim: evidence_audience, grantIdClaim: evidence_grant_id, grantAuthorityClaim: evidence_authority, maximumTokenLifetimeSeconds: 300, revokedKeyIds: [] } audit: { format: keyed-jsonl, hashSecretRef: secret:file/audit-hmac-key, hashKeyVersion: 1, failClosed: true } @@ -39,7 +39,8 @@ authorityProfiles: - { requirement: https://id.registrystack.org/solmara/requirement/nia-child-benefit/v1, purpose: child-benefit-review, audienceFrom: authenticated-requester, responseFormats: [signed-jws], subjects: [{ role: subject, selectorProfile: solmara-uin-v1, valueOrigin: request }] } - { requirement: https://id.registrystack.org/solmara/requirement/nia-citizen-status/v1, purpose: citizen-self-service, audienceFrom: authenticated-requester, responseFormats: [signed-jws], subjects: [{ role: subject, selectorProfile: solmara-uin-v1, valueOrigin: request }] } requirements: - - id: https://id.registrystack.org/solmara/requirement/nia-child-benefit/v1 + - handle: nia-child-benefit + id: https://id.registrystack.org/solmara/requirement/nia-child-benefit/v1 kind: information-requirement acquisition: { kind: single, source: population-extract } purposes: [child-benefit-review] @@ -48,11 +49,12 @@ requirements: evidenceType: https://id.registrystack.org/solmara/evidence-type/population-active/v1 validitySeconds: 3600 derivation: { script: derivations/population-active.rhai, parameters: { concept_id: https://id.registrystack.org/solmara/concept/population-record-active } } - concepts: [{ id: https://id.registrystack.org/solmara/concept/population-record-active, form: boolean, required: true, constraints: {} }] + concepts: [{ handle: population-record-active, id: https://id.registrystack.org/solmara/concept/population-record-active, form: boolean, required: true, constraints: {} }] fixtures: fixtures/child-benefit.yaml disclosureGuard: { families: [https://id.registrystack.org/solmara/disclosure-family/nia-child-benefit] } existenceDisclosure: collapse-unresolved - - id: https://id.registrystack.org/solmara/requirement/nia-citizen-status/v1 + - handle: nia-citizen-status + id: https://id.registrystack.org/solmara/requirement/nia-citizen-status/v1 kind: information-requirement acquisition: { kind: single, source: population-extract } purposes: [citizen-self-service] @@ -61,7 +63,7 @@ requirements: evidenceType: https://id.registrystack.org/solmara/evidence-type/citizen-population-active/v1 validitySeconds: 3600 derivation: { script: derivations/population-active.rhai, parameters: { concept_id: https://id.registrystack.org/solmara/concept/citizen-population-record-active } } - concepts: [{ id: https://id.registrystack.org/solmara/concept/citizen-population-record-active, form: boolean, required: true, constraints: {} }] + concepts: [{ handle: citizen-population-record-active, id: https://id.registrystack.org/solmara/concept/citizen-population-record-active, form: boolean, required: true, constraints: {} }] fixtures: fixtures/citizen.yaml disclosureGuard: { families: [https://id.registrystack.org/solmara/disclosure-family/nia-citizen] } existenceDisclosure: collapse-unresolved diff --git a/evidence/cells/sipf/bundle/evidence.yaml b/evidence/cells/sipf/bundle/evidence.yaml index 2a2dfc6..73ee7d3 100644 --- a/evidence/cells/sipf/bundle/evidence.yaml +++ b/evidence/cells/sipf/bundle/evidence.yaml @@ -1,6 +1,6 @@ version: 1 assuranceProfile: production -service: { providerId: https://id.registrystack.org/solmara/evidence/sipf, trustDomain: https://id.registrystack.org/solmara/trust-domain/lab } +service: { providerId: https://id.registrystack.org/solmara/evidence/sipf, trustDomain: https://id.registrystack.org/solmara/trust-domain/lab, publicOrigin: https://sipf-evidence.solmara.registrystack.org } issuer: { id: did:web:id.registrystack.org:solmara:authority:sipf } authentication: { kind: oidc-access-token, issuer: https://mint.solmara.registrystack.org, audiences: [solmara-runtime], tokenTypes: [at+jwt], algorithms: [ES256], jwksUri: https://mint.solmara.registrystack.org/.well-known/jwks.json, principalClaim: sub, requesterTagsClaim: evidence_tags, evidenceAudienceClaim: evidence_audience, grantIdClaim: evidence_grant_id, grantAuthorityClaim: evidence_authority, maximumTokenLifetimeSeconds: 300, revokedKeyIds: [] } audit: { format: keyed-jsonl, hashSecretRef: secret:file/audit-hmac-key, hashKeyVersion: 1, failClosed: true } @@ -64,7 +64,8 @@ authorityProfiles: - { requirement: https://id.registrystack.org/solmara/requirement/sipf-pension-payment/v1, purpose: pension-payment-review, audienceFrom: authenticated-requester, responseFormats: [signed-jws], subjects: [{ role: subject, selectorProfile: solmara-uin-v1, valueOrigin: request }] } - { requirement: https://id.registrystack.org/solmara/requirement/sipf-survivor-benefit/v1, purpose: survivor-benefit-determination, audienceFrom: authenticated-requester, responseFormats: [signed-jws], subjects: [{ role: subject, selectorProfile: solmara-uin-v1, valueOrigin: request }] } requirements: - - id: https://id.registrystack.org/solmara/requirement/sipf-pension-payment/v1 + - handle: sipf-pension-payment + id: https://id.registrystack.org/solmara/requirement/sipf-pension-payment/v1 kind: information-requirement acquisition: { kind: single, source: sipf-pension } purposes: [pension-payment-review] @@ -73,11 +74,12 @@ requirements: evidenceType: https://id.registrystack.org/solmara/evidence-type/pension-payment-active/v1 validitySeconds: 300 derivation: { script: derivations/pension-active.rhai, parameters: {} } - concepts: [{ id: https://id.registrystack.org/solmara/concept/pension-payment-active, form: boolean, required: true, constraints: {} }] + concepts: [{ handle: pension-payment-active, id: https://id.registrystack.org/solmara/concept/pension-payment-active, form: boolean, required: true, constraints: {} }] fixtures: fixtures/pension.yaml disclosureGuard: { families: [https://id.registrystack.org/solmara/disclosure-family/sipf-pension] } existenceDisclosure: collapse-unresolved - - id: https://id.registrystack.org/solmara/requirement/sipf-survivor-benefit/v1 + - handle: sipf-survivor-benefit + id: https://id.registrystack.org/solmara/requirement/sipf-survivor-benefit/v1 kind: information-requirement acquisition: { kind: single, source: sipf-survivor } purposes: [survivor-benefit-determination] @@ -86,7 +88,7 @@ requirements: evidenceType: https://id.registrystack.org/solmara/evidence-type/survivor-benefit/v1 validitySeconds: 300 derivation: { script: derivations/survivor-eligible.rhai, parameters: {} } - concepts: [{ id: https://id.registrystack.org/solmara/concept/survivor-is-eligible, form: boolean, required: true, constraints: {} }] + concepts: [{ handle: survivor-is-eligible, id: https://id.registrystack.org/solmara/concept/survivor-is-eligible, form: boolean, required: true, constraints: {} }] fixtures: fixtures/survivor.yaml disclosureGuard: { families: [https://id.registrystack.org/solmara/disclosure-family/sipf-survivor] } existenceDisclosure: collapse-unresolved diff --git a/evidence/cells/sro/bundle/evidence.yaml b/evidence/cells/sro/bundle/evidence.yaml index 6da0298..f38bb51 100644 --- a/evidence/cells/sro/bundle/evidence.yaml +++ b/evidence/cells/sro/bundle/evidence.yaml @@ -1,6 +1,6 @@ version: 1 assuranceProfile: production -service: { providerId: https://id.registrystack.org/solmara/evidence/sro, trustDomain: https://id.registrystack.org/solmara/trust-domain/lab } +service: { providerId: https://id.registrystack.org/solmara/evidence/sro, trustDomain: https://id.registrystack.org/solmara/trust-domain/lab, publicOrigin: https://sro-evidence.solmara.registrystack.org } issuer: { id: did:web:id.registrystack.org:solmara:authority:sro } authentication: { kind: oidc-access-token, issuer: https://mint.solmara.registrystack.org, audiences: [solmara-runtime], tokenTypes: [at+jwt], algorithms: [ES256], jwksUri: https://mint.solmara.registrystack.org/.well-known/jwks.json, principalClaim: sub, requesterTagsClaim: evidence_tags, evidenceAudienceClaim: evidence_audience, grantIdClaim: evidence_grant_id, grantAuthorityClaim: evidence_authority, maximumTokenLifetimeSeconds: 300, revokedKeyIds: [] } audit: { format: keyed-jsonl, hashSecretRef: secret:file/audit-hmac-key, hashKeyVersion: 1, failClosed: true } @@ -37,7 +37,8 @@ authorityProfiles: requesterTags: [solmara-demo] grants: [{ requirement: https://id.registrystack.org/solmara/requirement/sro-child-benefit/v1, purpose: child-benefit-review, audienceFrom: authenticated-requester, responseFormats: [signed-jws], subjects: [{ role: subject, selectorProfile: solmara-uin-v1, valueOrigin: request }] }] requirements: - - id: https://id.registrystack.org/solmara/requirement/sro-child-benefit/v1 + - handle: sro-child-benefit + id: https://id.registrystack.org/solmara/requirement/sro-child-benefit/v1 kind: information-requirement acquisition: { kind: single, source: poverty-extract } purposes: [child-benefit-review] @@ -46,7 +47,7 @@ requirements: evidenceType: https://id.registrystack.org/solmara/evidence-type/poverty-priority/v1 validitySeconds: 3600 derivation: { script: derivations/poverty-priority.rhai, parameters: {} } - concepts: [{ id: https://id.registrystack.org/solmara/concept/household-below-poverty-threshold, form: boolean, required: true, constraints: {} }] + concepts: [{ handle: household-below-poverty-threshold, id: https://id.registrystack.org/solmara/concept/household-below-poverty-threshold, form: boolean, required: true, constraints: {} }] fixtures: fixtures/child-benefit.yaml disclosureGuard: { families: [https://id.registrystack.org/solmara/disclosure-family/sro-child-benefit] } existenceDisclosure: collapse-unresolved diff --git a/evidence/tests/test_cells.py b/evidence/tests/test_cells.py index 067340d..879da41 100644 --- a/evidence/tests/test_cells.py +++ b/evidence/tests/test_cells.py @@ -84,6 +84,9 @@ def test_six_independent_authority_identities_and_runtime_boundaries(self): config = configs[cell] runtime = load_yaml(ROOT / "evidence" / "cells" / cell / "runtime.yaml") self.assertEqual(config["service"]["providerId"], f"https://id.registrystack.org/solmara/evidence/{cell}") + # RFC 9728 discovery answers on the cell's own routed origin, which is + # the one origin a relying party may treat as this resource's identity. + self.assertEqual(config["service"]["publicOrigin"], f"https://{cell}-evidence.solmara.registrystack.org") self.assertEqual(config["issuer"]["id"], ISSUERS[cell]) self.assertEqual(config["signing"]["algorithm"], "ES256") self.assertEqual(config["signing"]["activePublicJwkFile"], f"public-keys/{cell}.jwk.json") @@ -92,6 +95,22 @@ def test_six_independent_authority_identities_and_runtime_boundaries(self): self.assertEqual(runtime["signer"]["keyName"], f"solmara-evidence-{cell}") self.assertNotEqual(runtime["auditStorage"]["path"], "/var/lib/registry-evidence/audit/evidence.jsonl") + def test_every_requirement_and_concept_carries_its_own_stable_handle(self): + """A handle is the key a client reads a result under, so it is authored to + the last segment of the identifier it names rather than invented.""" + for cell, config in self.configs().items(): + handles = [] + for requirement in config["requirements"]: + with self.subTest(cell=cell, requirement=requirement["id"]): + self.assertEqual(requirement["handle"], requirement["id"].rsplit("/", 2)[-2]) + handles.append(requirement["handle"]) + concepts = [concept["handle"] for concept in requirement["concepts"]] + for concept in requirement["concepts"]: + with self.subTest(cell=cell, concept=concept["id"]): + self.assertEqual(concept["handle"], concept["id"].rsplit("/", 1)[-1]) + self.assertCountEqual(concepts, set(concepts)) + self.assertCountEqual(handles, set(handles)) + def test_exact_requirement_evidence_type_purpose_and_validity_set(self): actual = {} for config in self.configs().values(): diff --git a/home/src/lib/server/data.test.ts b/home/src/lib/server/data.test.ts index ef28a42..1b52c3e 100644 --- a/home/src/lib/server/data.test.ts +++ b/home/src/lib/server/data.test.ts @@ -18,8 +18,8 @@ describe('home data adapters', () => { it('publishes the exact Registry Stack release identity', async () => { const versions = await readVersions(); - expect(versions.REGISTRY_STACK_REQUIRED_VERSION).toBe('0.22.0'); - expect(versions.REGISTRY_STACK_SOURCE_REF).toBe('v0.22.0'); + expect(versions.REGISTRY_STACK_REQUIRED_VERSION).toBe('0.23.0'); + expect(versions.REGISTRY_STACK_SOURCE_REF).toBe('v0.23.0'); expect(versions.REGISTRY_STACK_SOURCE_COMMIT).toMatch(/^[0-9a-f]{40}$/); expect(versions.REGISTRY_RELAY_IMAGE).toMatch(/@sha256:[0-9a-f]{64}$/); expect(versions.SOLMARA_EVIDENCE_IMAGE).toMatch(/^ghcr\.io\/registrystack\/evidence@sha256:[0-9a-f]{64}$/); diff --git a/justfile b/justfile index a2f38cd..280417d 100644 --- a/justfile +++ b/justfile @@ -66,7 +66,7 @@ compose: @scripts/check-hosted-compose.sh @scripts/check-coolify-compose.sh -# Verify that every Registry Stack v0.22.0 release reference is public and immutable. +# Verify that every Registry Stack v0.23.0 release reference is public and immutable. hosted-pin-check: build-runtime-images scripts/check-image-pins.py scripts/check-registry-stack-release-pin.py --require-public diff --git a/scripts/check-registry-stack-release-pin.py b/scripts/check-registry-stack-release-pin.py index c20ae0d..9b4dfcd 100755 --- a/scripts/check-registry-stack-release-pin.py +++ b/scripts/check-registry-stack-release-pin.py @@ -10,7 +10,7 @@ ROOT = Path(__file__).resolve().parents[1] DIGEST = re.compile(r"^[0-9a-f]{64}$") HTTPS = re.compile(r"^https://[^\s]+$") -REQUIRED_VERSION = "0.22.0" +REQUIRED_VERSION = "0.23.0" def read_versions(path: Path) -> dict[str, str]: diff --git a/scripts/test_build_registry_stack_runtime.py b/scripts/test_build_registry_stack_runtime.py index c66b838..3c0405e 100644 --- a/scripts/test_build_registry_stack_runtime.py +++ b/scripts/test_build_registry_stack_runtime.py @@ -36,13 +36,13 @@ def setUp(self) -> None: self.relayctl_asset.write_bytes(b"published relayctl fixture") relayctl_sha256 = hashlib.sha256(self.relayctl_asset.read_bytes()).hexdigest() (self.root / "versions.env").write_text( - "REGISTRY_STACK_REQUIRED_VERSION=0.22.0\n" + "REGISTRY_STACK_REQUIRED_VERSION=0.23.0\n" "REGISTRY_STACK_SOURCE_COMMIT=" + "a" * 40 + "\n" f"REGISTRY_STACK_RELEASE_RELAY_DIGEST={self.digests['RELAY']}\n" f"REGISTRY_RELAY_IMAGE=ghcr.io/registrystack/relay@sha256:{self.digests['RELAY']}\n" f"SOLMARA_EVIDENCE_IMAGE=ghcr.io/registrystack/evidence@sha256:{self.digests['EVIDENCE']}\n" f"SOLMARA_MINT_IMAGE=ghcr.io/registrystack/mint@sha256:{self.digests['MINT']}\n" - "REGISTRY_RELAYCTL_IMAGE=solmara-lab-relayctl:v0.22.0\n" + "REGISTRY_RELAYCTL_IMAGE=solmara-lab-relayctl:v0.23.0\n" "REGISTRY_STACK_RELEASE_RELAYCTL_ASSET_URL=https://example.invalid/relayctl\n" f"REGISTRY_STACK_RELEASE_RELAYCTL_ASSET_SHA256={relayctl_sha256}\n", encoding="utf-8", @@ -72,7 +72,7 @@ def setUp(self) -> None: elif "org.opencontainers.image.revision" in output_format: print("a" * 40) elif "org.opencontainers.image.version" in output_format: - print("0.22.0") + print("0.23.0") elif "org.opencontainers.image.source" in output_format: print("https://github.com/registrystack/registry-stack") elif arguments[0] == "pull": @@ -134,7 +134,7 @@ def test_all_official_version_tags_match_their_pinned_digests(self) -> None: calls = self.log.read_text(encoding="utf-8") for component in ("relay", "evidence", "mint"): self.assertIn( - f"buildx imagetools inspect ghcr.io/registrystack/{component}:v0.22.0", + f"buildx imagetools inspect ghcr.io/registrystack/{component}:v0.23.0", calls, ) @@ -149,7 +149,7 @@ def test_mismatched_official_tag_digest_fails_closed(self) -> None: result.stderr, ) calls = self.log.read_text(encoding="utf-8") - self.assertNotIn("ghcr.io/registrystack/mint:v0.22.0", calls) + self.assertNotIn("ghcr.io/registrystack/mint:v0.23.0", calls) def test_relayctl_is_verified_on_the_host_before_the_minimal_image_build(self) -> None: self.environment["FORCE_RELAYCTL_BUILD"] = "1" diff --git a/scripts/test_hosted_image_manifest.py b/scripts/test_hosted_image_manifest.py index 047e58d..8da7b08 100644 --- a/scripts/test_hosted_image_manifest.py +++ b/scripts/test_hosted_image_manifest.py @@ -130,7 +130,7 @@ def test_every_image_must_use_its_exact_ghcr_repository_and_digest(self) -> None def test_relay_must_use_the_canonical_official_reference(self) -> None: invalid = ( - "ghcr.io/registrystack/relay:v0.22.0", + "ghcr.io/registrystack/relay:v0.23.0", f"ghcr.io/registrystack/solmara-lab-relay@sha256:{'1' * 64}", f"ghcr.io/registrystack/relay@sha256:{'A' * 64}", ) diff --git a/scripts/test_registry_stack_release_pin.py b/scripts/test_registry_stack_release_pin.py index 0a99b69..7aa2489 100644 --- a/scripts/test_registry_stack_release_pin.py +++ b/scripts/test_registry_stack_release_pin.py @@ -25,12 +25,12 @@ def authenticator_values() -> dict[str, str]: class ReleasePinTests(unittest.TestCase): def test_older_release_is_rejected_in_favour_of_coherent_release(self) -> None: values = {"REGISTRY_STACK_REQUIRED_VERSION": "0.21.0", **authenticator_values()} - self.assertIn("must be 0.22.0", MODULE.validate(values, require_public=False)[0]) + self.assertIn("must be 0.23.0", MODULE.validate(values, require_public=False)[0]) def test_missing_public_digest_is_an_explicit_blocker(self) -> None: values = { - "REGISTRY_STACK_REQUIRED_VERSION": "0.22.0", - "REGISTRY_STACK_SOURCE_REF": "v0.22.0", + "REGISTRY_STACK_REQUIRED_VERSION": "0.23.0", + "REGISTRY_STACK_SOURCE_REF": "v0.23.0", **authenticator_values(), } self.assertEqual(MODULE.validate(values, require_public=False), []) @@ -38,11 +38,11 @@ def test_missing_public_digest_is_an_explicit_blocker(self) -> None: def test_digest_must_be_exact(self) -> None: values = { - "REGISTRY_STACK_REQUIRED_VERSION": "0.22.0", - "REGISTRY_STACK_SOURCE_REF": "v0.22.0", + "REGISTRY_STACK_REQUIRED_VERSION": "0.23.0", + "REGISTRY_STACK_SOURCE_REF": "v0.23.0", "REGISTRY_STACK_SOURCE_COMMIT": "f" * 40, "REGISTRY_STACK_RELEASE_RELAY_DIGEST": "a" * 64, - "REGISTRY_STACK_RELEASE_RELAYCTL_ASSET_URL": "https://github.com/registrystack/registry-stack/releases/download/v0.22.0/relayctl-v0.22.0-linux-amd64", + "REGISTRY_STACK_RELEASE_RELAYCTL_ASSET_URL": "https://github.com/registrystack/registry-stack/releases/download/v0.23.0/relayctl-v0.23.0-linux-amd64", "REGISTRY_STACK_RELEASE_RELAYCTL_ASSET_SHA256": "d" * 64, **authenticator_values(), } @@ -52,12 +52,12 @@ def test_digest_must_be_exact(self) -> None: def test_public_source_ref_must_bind_the_release_tag(self) -> None: values = { - "REGISTRY_STACK_REQUIRED_VERSION": "0.22.0", + "REGISTRY_STACK_REQUIRED_VERSION": "0.23.0", "REGISTRY_STACK_SOURCE_REF": "main", **authenticator_values(), } self.assertIn( - "REGISTRY_STACK_SOURCE_REF must be v0.22.0", + "REGISTRY_STACK_SOURCE_REF must be v0.23.0", MODULE.validate(values, require_public=False), ) diff --git a/versions.env b/versions.env index fd381c2..268d166 100644 --- a/versions.env +++ b/versions.env @@ -1,19 +1,19 @@ # Registry Stack v0.21.0 is the earliest release that publishes the Relay, # Evidence, and Mint images required by the authority-cell reset. Solmara -# pins v0.22.0, the current release on that line. -REGISTRY_STACK_REQUIRED_VERSION=0.22.0 -REGISTRY_STACK_RELEASE_RELAY_DIGEST=6d58ea62b78db451409e77a96ed45c7f82a72cc17ebc9d4eb65230319fe13d83 -REGISTRY_STACK_RELEASE_RELAYCTL_ASSET_URL=https://github.com/registrystack/registry-stack/releases/download/v0.22.0/relayctl-v0.22.0-linux-amd64 -REGISTRY_STACK_RELEASE_RELAYCTL_ASSET_SHA256=1354946ffca568d0a95e32de37fb7b45805c3756f21217aca44d1d34babbaebc +# pins v0.23.0, the current release on that line. +REGISTRY_STACK_REQUIRED_VERSION=0.23.0 +REGISTRY_STACK_RELEASE_RELAY_DIGEST=734d89fa6dd11a7fe788d09b0cc2bec381de9f19b8522026158428bea93aaaa0 +REGISTRY_STACK_RELEASE_RELAYCTL_ASSET_URL=https://github.com/registrystack/registry-stack/releases/download/v0.23.0/relayctl-v0.23.0-linux-amd64 +REGISTRY_STACK_RELEASE_RELAYCTL_ASSET_SHA256=d72edcc4913a93195621435e4d99596c2b99b8ad46954a22c0a580c115c8cce1 # Exact release source identity and local thin-image tags. Every Registry Stack # executable is fetched from the published release or the digest-pinned OCI. -REGISTRY_STACK_SOURCE_REF=v0.22.0 -REGISTRY_STACK_SOURCE_COMMIT=d9a278a55fa3cd275e391cdf2d94dae342c65edc -REGISTRY_RELAY_IMAGE=ghcr.io/registrystack/relay@sha256:6d58ea62b78db451409e77a96ed45c7f82a72cc17ebc9d4eb65230319fe13d83 -REGISTRY_RELAYCTL_IMAGE=solmara-lab-relayctl:v0.22.0 -SOLMARA_EVIDENCE_IMAGE=ghcr.io/registrystack/evidence@sha256:175cb81e12125bbb17425bdc2df34b109b0da95b3b1df2d6cb2bfa3223f2c577 -SOLMARA_MINT_IMAGE=ghcr.io/registrystack/mint@sha256:a4aaf418493ce2d14994082b93fb1ccb32b1351d71b3850a694045ad00174e54 +REGISTRY_STACK_SOURCE_REF=v0.23.0 +REGISTRY_STACK_SOURCE_COMMIT=6be85c74e6902bd931ea9b143feff6ab7877c52c +REGISTRY_RELAY_IMAGE=ghcr.io/registrystack/relay@sha256:734d89fa6dd11a7fe788d09b0cc2bec381de9f19b8522026158428bea93aaaa0 +REGISTRY_RELAYCTL_IMAGE=solmara-lab-relayctl:v0.23.0 +SOLMARA_EVIDENCE_IMAGE=ghcr.io/registrystack/evidence@sha256:fb0c486be9605c7752f19d2e60452c7000f275e87d1bda6462197932a2de2154 +SOLMARA_MINT_IMAGE=ghcr.io/registrystack/mint@sha256:5d8f01f3207d5f5b02f3c55886f1a79b71228f91e2f06acdf3d5528b442524fc # Third-party runtime images are immutable. Refresh intentionally, never by tag. PYTHON_STATIC_IMAGE=python@sha256:6d43704baacd1bfbe7c295d7f13079d5d8104ed33568873133f8fc69980419df From 5cc9dff22a232ef0c9b18279df530e844b876428 Mon Sep 17 00:00:00 2001 From: Jeremi Joslin Date: Thu, 20 Aug 2026 21:13:06 +0200 Subject: [PATCH 2/2] docs(hosted): record that a cell's route is also its discovery identity v0.23.0 makes service.publicOrigin the resource identity RFC 9728 discovery answers with, and the route override variables move a route without moving it. A test ties every bundle origin to the canonical route table. Signed-off-by: Jeremi Joslin --- docs/hosted-deployment.md | 8 ++++++++ scripts/test_hosted_evidence_routes.py | 15 +++++++++++++++ 2 files changed, 23 insertions(+) diff --git a/docs/hosted-deployment.md b/docs/hosted-deployment.md index 26656a7..8123506 100644 --- a/docs/hosted-deployment.md +++ b/docs/hosted-deployment.md @@ -53,6 +53,14 @@ Evidence hosts are: - `sipf-evidence.solmara.registrystack.org` - `nagdi-evidence.solmara.registrystack.org` +Each host is also the cell's own identity. The signed bundle carries it as +`service.publicOrigin`, and the cell answers with it as the `resource` and the +`jwks_uri` prefix of its RFC 9728 protected-resource metadata and in the +`WWW-Authenticate` challenge it returns. `SOLMARA__EVIDENCE_PUBLIC_HOST` +moves the route without moving that identity, so a deployment that sets one +still publishes discovery documents naming the host above. Leave those +overrides unset until the bundle origin is deployment-set as well. + Shared authority state lives at fixed absolute host paths of the form `/data/solmara-authority-cells//` rather than in named volumes. Coolify rewrites every named volume reference to `{app_uuid}_{key}` and does not diff --git a/scripts/test_hosted_evidence_routes.py b/scripts/test_hosted_evidence_routes.py index d81a2e9..e9fb1ec 100644 --- a/scripts/test_hosted_evidence_routes.py +++ b/scripts/test_hosted_evidence_routes.py @@ -179,5 +179,20 @@ def test_every_routed_service_carries_a_coolify_resolvable_name(self) -> None: self.assertNotIn("-", name) +class EvidenceIdentityTests(unittest.TestCase): + def test_every_cell_bundle_names_the_origin_it_is_routed_on(self) -> None: + """`service.publicOrigin` is the resource identity RFC 9728 discovery + answers with, so a bundle that names anything other than the cell's own + canonical route hands a relying party the wrong resource.""" + for service, (_, _, canonical_host) in EVIDENCE_ROUTES.items(): + cell = service.removesuffix("_evidence").replace("_", "-") + bundle = ROOT / "evidence" / "cells" / cell / "bundle" / "evidence.yaml" + with self.subTest(cell=cell): + config = yaml.safe_load(bundle.read_text(encoding="utf-8")) + self.assertEqual( + config["service"]["publicOrigin"], f"https://{canonical_host}" + ) + + if __name__ == "__main__": unittest.main()