diff --git a/docs/site/public/images/registry-trust-boundaries.svg b/docs/site/public/images/registry-trust-boundaries.svg index 7a1a57b34..dea15073d 100644 --- a/docs/site/public/images/registry-trust-boundaries.svg +++ b/docs/site/public/images/registry-trust-boundaries.svg @@ -2,7 +2,7 @@ font-family="'Public Sans', system-ui, -apple-system, BlinkMacSystemFont, sans-serif" text-rendering="geometricPrecision"> Registry Stack trust boundaries - One request path runs downward from Registry Mint, which issues a short-lived access token, to the caller, to Evidence Gateway, and to Evidence Gateway's configured authoritative HTTP source. Registry Relay has a separate protected-read path to its own registry sources. Dashed rules mark authenticated service edges. Caller to Evidence Gateway carries a bearer token, a declared purpose, a named requirement revision, and selector values, and returns the answer rather than the record. Evidence Gateway source access is fixed by trusted configuration and executed only by the core, one request per evaluation. Registry Mint to Evidence Gateway runs one way: Evidence Gateway verifies tokens as an ordinary resource server and does not depend on Registry Mint. A separate lane runs operator to signing key material to Evidence Gateway to the published key set to the verifier, which pins that key set out of band. The footnote lists the unauthenticated operational and discovery routes at the Evidence Gateway edge. + One request path runs downward from Registry Mint, which issues a short-lived access token, to the caller, to Evidence Gateway, and to Evidence Gateway's configured authoritative source, which it reaches over a fixed HTTP request or a read-only SQLite extract. Registry Relay has a separate protected-read path to its own registry sources. Dashed rules mark authenticated service edges. Caller to Evidence Gateway carries a bearer token, a declared purpose, a named requirement revision, and selector values, and returns the answer rather than the record. Evidence Gateway source access is one fixed source request per acquisition stage, fixed by trusted configuration and executed only by the core: an HTTP source fixes origin, method, path, headers, authentication, TLS, projection, and redirect and proxy denial, and a statement source fixes one reviewed SQL statement over a local read-only extract with no origin, no credential, and no network hop. Lookup collapses to match, no match, or ambiguous, and raw responses are never logged. Registry Mint to Evidence Gateway runs one way: Evidence Gateway verifies tokens as an ordinary resource server and does not depend on Registry Mint. A separate lane runs operator to signing key material to Evidence Gateway to the published key set to the verifier, which pins that key set out of band. The footnote lists the unauthenticated operational and discovery routes at the Evidence Gateway edge. TRUST BOUNDARIES @@ -33,7 +33,8 @@ the client asking the question one bounded question - fixed HTTP source + fixed HTTP request or + read-only SQLite extract @@ -60,8 +61,8 @@ short-lived access token bearer token, purpose, revision, selectors - one fixed HTTP request - per evaluation + one fixed source request + per acquisition stage separate product path @@ -88,15 +89,16 @@ responding. Evidence Gateway runs one OIDC bearer profile, one trusted issuer, one principal claim, and no trust in proxy identity headers. - Origin, method, path, headers, credential, TLS, projection, redirect - and proxy denial, and one request per evaluation are fixed by trusted - configuration and executed only by the core. Lookup collapses to - match, no match, or ambiguous; raw responses are never logged. + Trusted configuration fixes each source and only the core executes it. An HTTP + source fixes origin, method, path, headers, authentication, TLS, projection, and + redirect and proxy denial. A statement source fixes one reviewed SQL statement + over a local read-only extract: no origin, no credential, no network hop. Lookup + collapses to match, no match, or ambiguous; raw responses are never logged. Evidence Gateway and Relay use independently configured sources. Neither product calls or authorizes the other. Each keeps its own authorization decisions, audit trail, and readiness checks; only - Evidence Gateway holds a source credential. + Evidence Gateway ever holds a source credential. Relay owns its own source boundary: one local SQLite file opened read-only, with no origin, no credential, and no outbound request. diff --git a/docs/site/src/content/docs/configure/evidence.mdx b/docs/site/src/content/docs/configure/evidence.mdx index ecbad4e56..cab49482e 100644 --- a/docs/site/src/content/docs/configure/evidence.mdx +++ b/docs/site/src/content/docs/configure/evidence.mdx @@ -5,7 +5,7 @@ status: current owner: registry-docs source_repos: - registry-stack -last_reviewed: "2026-08-07" +last_reviewed: "2026-08-21" doc_type: how-to locale: en standards_referenced: [] @@ -106,7 +106,7 @@ atomic revision (`products/evidence/contracts/bundle.schema.yaml`). Its top-leve | `signing` | Fixed ES256 signing, governed active and published public JWK files, revoked service key identifiers, the fixed JWKS path, and assertion validity and clock-skew bounds. | | `responseFormats` (optional) | The bundle-wide ceiling on releasable serializations; defaults to signed JWS alone and must always include it. | | `selectorProfiles` | Named, bounded scalar field sets a subject role may be looked up by. | -| `sources` | Named fixed HTTP JSON sources: transport, base URL, acquisition posture, authentication, the fixed request shape, and the response, extraction, and fact schemas bound to it. | +| `sources` | Named sources: a closed transport tag selects HTTP JSON (base URL, authentication, the fixed request shape) or SQLite extract (extract profile, the fixed statement), plus acquisition posture and the response, extraction, and fact schemas bound to each. | | `authorityProfiles` | Named requester-tag gates: which grants (requirement, purpose, audience, subject roles, selector profiles, value origins) a caller carrying those tags holds. | | `requirements` | The assertion definitions themselves: id, closed acquisition, purposes, subject roles, reference frameworks, evidence type, validity, derivation script and parameters, output concepts, fixtures, and disclosure-guard family. | diff --git a/docs/site/src/content/docs/explanation/integration-patterns.mdx b/docs/site/src/content/docs/explanation/integration-patterns.mdx index d57b664fd..dd7158daf 100644 --- a/docs/site/src/content/docs/explanation/integration-patterns.mdx +++ b/docs/site/src/content/docs/explanation/integration-patterns.mdx @@ -9,7 +9,7 @@ source_repos: - registry-relay - registry-evidence - registry-mint -last_reviewed: "2026-08-11" +last_reviewed: "2026-08-21" doc_type: explanation locale: en standards_referenced: @@ -57,15 +57,23 @@ Each authority keeps custody of its own data and exposes a bounded output; consu as a social-protection management information system receive answers and do not become raw-record readers. -Which surface fits depends on what the authority holds and how it holds it: - -- Where the authoritative data already sits in a SQLite database, or the authority can produce a - SQLite extract from it, **Registry Relay** serves it as a governed read-only API. Relay opens - that file in place, read-only, and holds no upstream credential because it makes no upstream - call. -- Where the authoritative data lives inside a domain platform with its own API, such as a FHIR - server or a DHIS2 instance, **Evidence Gateway** reaches it through its own fixed HTTP source - contract and returns only the values a reviewed requirement declares. +Which surface fits depends first on what the caller needs back, and then on what the authority +holds: + +- Where the caller needs a governed read over the records themselves, **Registry Relay** serves + them as a read-only API, which requires the authoritative data to already sit in a SQLite + database or in a SQLite extract the authority can produce. Relay opens that file in place, + read-only, and holds no upstream credential because it makes no upstream call. +- Where the caller needs one signed answer rather than the records, **Evidence Gateway** returns + only the values a reviewed requirement declares, and reaches its source over either of two + coequal transports. A domain platform with its own API, such as a FHIR server or a DHIS2 + instance, is read over a fixed HTTP JSON request to a fixed origin. A SQLite extract the + authority publishes is read directly, by one reviewed SQL statement against a read-only file + mounted beside the process, with no origin, credential, or network hop involved. + {/* Evidence: "A fixed source request reaches its source over one of two coequal transports", + products/evidence/CONCEPT.md; the statement transport has no origin, no credential, and no + network path, `transport_absences` in + products/evidence/contracts/sqlite-extract-source-contract.yaml (frozen). */} Civil registration, social protection, agriculture, and education systems each keep their own authoritative store either way; neither surface copies the record into a central store. @@ -146,10 +154,10 @@ Deployment-local facts, including where the database file actually is, stay in ` never enter the reviewed contract, so the same contract can be reviewed once and deployed in staging and production without editing it. -{/* Evidence: crates/registry-relay-v2/src/contract.rs:323-325 declares SourceProfile with the two - variants Snapshot and LiveReadOnly; the RelayRuntime schema at :1060-1300 carries sources, - package path, listener, issuer, audit sink, limits, and quotas, and nothing that can add or - widen a resource, operation, access profile, or disclosure decision. */} +{/* Evidence: the SourceProfile enum declares the two variants Snapshot and LiveReadOnly, and the + RelayRuntime struct carries sources, package path, listener, issuer, audit sink, limits, and + quotas, and nothing that can add or widen a resource, operation, access profile, or disclosure + decision, both in crates/registry-relay-v2/src/contract.rs. */} Wire Registry Stack in alongside a domain platform when: @@ -192,9 +200,10 @@ subject evaluation and emits one credential per presented holder key. A lookup still resolves to exactly one match, no match, or ambiguity, and Evidence Gateway never surfaces or chooses between candidates. -{/* Evidence: crates/registry-relay-v2/src/contract.rs:1010-1027 declares LookupRequestBody and - SelectorDefinition; crates/registry-relay-v2/src/api.rs:2292 refuses a body whose selector - count differs from the declared set; crates/registry-relay-v2/src/identification.rs:832-855 +{/* Evidence: the LookupRequestBody and SelectorDefinition structs, + crates/registry-relay-v2/src/contract.rs; parse_selectors() refuses a body whose selector + count differs from the declared set, crates/registry-relay-v2/src/api.rs; + crates/registry-relay-v2/src/identification.rs:832-855 raises classification.context.selector_more_restrictive_than_disclosure as a contextual review finding, which is a review signal rather than a hard compile refusal; products/evidence/contracts/request.schema.yaml, @@ -230,25 +239,50 @@ If you are reading an integration guide that describes source scripts, same-orig API-key placement for Relay, it describes the retired runtime. See [Known limitations](../known-limitations/) for the full list of what went with it. -{/* Evidence: crates/registry-relay-v2/src/contract.rs:323-325 declares SourceProfile as exactly - Snapshot and LiveReadOnly; the crate holds no HTTP source client, and the runtime schema at - :1060-1300 accepts a source only as a path. Read-only opening is enforced in - crates/registry-platform-sqlite (SQLITE_OPEN_READ_ONLY | SQLITE_OPEN_URI | - SQLITE_OPEN_NO_MUTEX in schema.rs:131-133 and statement.rs:494-496). */} - -Evidence Gateway keeps its own source model, and it is a different one: fixed HTTP sources with -compiled credentials and bounded reviewed Rhai. +{/* Evidence: the SourceProfile enum declares exactly Snapshot and LiveReadOnly, and the + RelayRuntime struct's RuntimeSource accepts a source only as a path, both in + crates/registry-relay-v2/src/contract.rs; the crate holds no HTTP source client. Read-only + opening is enforced in crates/registry-platform-sqlite (SQLITE_OPEN_READ_ONLY | + SQLITE_OPEN_URI | SQLITE_OPEN_NO_MUTEX in schema.rs:131-133 and statement.rs:494-496). */} + +Evidence Gateway keeps its own source model, and it is a different one: one fixed source request +reached over either of two coequal transports, with bounded reviewed Rhai around it. +An HTTP JSON source states a fixed origin and resolves a compiled credential for it, except +under the `none` authentication kind, which only a `local` assurance bundle may declare and +only at a numeric-loopback origin. +A statement source states no origin and resolves no credential at all: it runs one reviewed SQL +statement against a read-only SQLite extract file the runtime mounts beside the process. The two products do not share that machinery, and Evidence does not inherit Relay's authorization model. -When an institution's authoritative data is not in SQLite and cannot reasonably be extracted into -it, Evidence Gateway is the surface that can reach it. +The distinction between the two products is not which storage each can reach, because both can read +a SQLite extract. It is what each does with the source: Relay serves the properties a compiled +disclosure profile declares, unsigned, and Evidence Gateway returns a signed assertion carrying only +the values a reviewed requirement declares. + +{/* Evidence: "A fixed source request reaches its source over one of two coequal transports", + products/evidence/CONCEPT.md; Evidence holds no database credential for the statement + transport, and there is no connection string, secret reference, or authentication kind, + `transport_absences` in products/evidence/contracts/sqlite-extract-source-contract.yaml + (frozen). */} ## Evidence Gateway with a fixed authoritative source Use this pattern when an institution wants a minimum-disclosure answer over data in an -authoritative system. Evidence Gateway calls that system through its own fixed HTTP source -contract. The reviewed bundle fixes the origin, path authority, method, authentication, -projection, schemas, and bounded scripts. The caller cannot select or redirect the source. +authoritative system. Evidence Gateway reaches that system over one of its two coequal source +transports, and the reviewed bundle fixes the whole request either way. +For an HTTP JSON source it fixes the origin, which a production bundle must state as HTTPS, plus +the path authority, method, authentication, projection, schemas, and bounded scripts. +For a statement source it fixes the one reviewed SQL statement, the result columns that statement +declares, the parameter bindings it declares, the schemas, and the bounded scripts, and there is no +origin, credential, or redirect policy to fix because the transport opens one local extract file. +The caller cannot select or redirect the source either way. + +{/* Evidence: the closed `transport` tag selecting http-json or sqlite-extract, the baseUrl pattern + pair admitting an http numeric-loopback origin only when a source's authentication kind is + none, and the statement source's one bundle-relative statement file with its declared result + columns and parameter bindings, products/evidence/contracts/bundle.schema.yaml (frozen); the + statement transport's absent origin, credential, and redirect policy, `transport_absences` in + products/evidence/contracts/sqlite-extract-source-contract.yaml (frozen). */} Request batches use the same closed sources. Sequential item evaluation is available without extra source configuration. An operator can enable the optional `source-batch` capability in both the @@ -262,8 +296,13 @@ The caller's access token and the source credential serve different boundaries: - The caller holds a short-lived access token for Evidence Gateway, from the deployment's identity provider or from Registry Mint when there is none. See the [Registry Mint reference](../../reference/mint/). -- Evidence Gateway holds its own compiled credential for the authoritative source. The caller never - sees it and cannot influence which credential is resolved. +- Where the authoritative source is an HTTP JSON one, Evidence Gateway holds its own compiled + credential for it, in every bundle a production or evidence-grade deployment may load. The caller + never sees it and cannot influence which credential is resolved. A statement source holds no + credential at all, because it opens a local read-only extract file and reaches no origin. + {/* Evidence: Evidence holds no database credential for the statement transport, and there is no + connection string, secret reference, or authentication kind, `transport_absences` in + products/evidence/contracts/sqlite-extract-source-contract.yaml (frozen). */} Registry Relay is not part of this request path. If the institution also operates Relay, it is a separate protected-read product with its own source, callers, authorization, and audit trail. diff --git a/docs/site/src/content/docs/explanation/records-stay-home.mdx b/docs/site/src/content/docs/explanation/records-stay-home.mdx index 030a38587..1a3ee681c 100644 --- a/docs/site/src/content/docs/explanation/records-stay-home.mdx +++ b/docs/site/src/content/docs/explanation/records-stay-home.mdx @@ -7,7 +7,7 @@ source_repos: - registry-stack - registry-evidence - registry-relay -last_reviewed: "2026-08-11" +last_reviewed: "2026-08-21" doc_type: explanation locale: en standards_referenced: @@ -57,7 +57,7 @@ answer is one boolean. The register's list of that child's parents never travels flowchart LR subgraph inst["Institution: data stays here"] relaySrc[("Relay source\nread-only SQLite file")] - evidenceSrc[("Evidence Gateway source\nauthoritative HTTP API")] + evidenceSrc[("Evidence Gateway source\nauthoritative HTTP API\nor read-only SQLite extract")] relay["Registry Relay\nprotected read API"] evidence["Evidence Gateway\nevaluate · minimize · sign"] key>"Signing key\n(private half never leaves)"] @@ -87,10 +87,18 @@ bounded by the access profile that guards the operation and the disclosure profi the response. Evidence Gateway answers one predefined requirement about one set of subjects and returns a signed assertion; it is the only component that evaluates a requirement, minimizes the result, and -signs it. It contacts its own configured authoritative HTTP source rather than calling Relay. +signs it. It contacts its own configured authoritative source rather than calling Relay, over one +of two coequal transports: a fixed HTTP JSON request to a fixed origin, or one reviewed SQL +statement against a read-only SQLite extract file mounted beside the process. Evidence Gateway is the stronger minimization surface. Relay reads are compiled, authorized, and audited, not open data. +{/* Evidence: "A fixed source request reaches its source over one of two coequal transports", + products/evidence/CONCEPT.md; contracts registry.evidence.fixed-http-json-source/v1, + products/evidence/contracts/source-contract.yaml, and + registry.evidence.fixed-sqlite-extract-source/v1, + products/evidence/contracts/sqlite-extract-source-contract.yaml (both frozen). */} + The policy boundary remains separate across the two products and their consumers. Relay owns its compiled contract and disclosure policy. Evidence Gateway owns evidence authorization and the declared disclosure of each requirement. @@ -164,7 +172,7 @@ partial string reveal (`***`) and a date reduced to year or year-month. Relay si responses carry no assertion. {/* Evidence: AccessRule is Public | Protected{scope, purpose, authorityRowBinding} and AuthorityRowBinding has a claim variant and a principal variant, - crates/registry-relay-v2/src/contract.rs:947-976; row authority is injected as a bound + crates/registry-relay-v2/src/contract.rs; row authority is injected as a bound parameter with an exact-equality COLLATE BINARY predicate, never string concatenation, crates/registry-relay-v2/src/sqlite_runtime.rs add_row_authority(). */} Evidence Gateway returns the values a requirement declares rather than the source row; keeping that diff --git a/docs/site/src/content/docs/explanation/threat-model.mdx b/docs/site/src/content/docs/explanation/threat-model.mdx index c0fafe07a..4f3db6785 100644 --- a/docs/site/src/content/docs/explanation/threat-model.mdx +++ b/docs/site/src/content/docs/explanation/threat-model.mdx @@ -9,7 +9,7 @@ source_repos: - registry-evidence - registry-mint - solmara-lab -last_reviewed: "2026-08-11" +last_reviewed: "2026-08-21" doc_type: explanation locale: en standards_referenced: @@ -87,17 +87,21 @@ auditing separately. The Registry Stack trust boundaries in one map. One request path runs from Registry Mint,
             which issues a short-lived access token, to the caller, to Evidence Gateway, and then to
-            Evidence Gateway's configured authoritative HTTP source. Registry Relay has a separate
+            Evidence Gateway's configured authoritative source, which it reaches over a fixed HTTP
+            request or a read-only SQLite extract. Registry Relay has a separate
             protected-read path to its own sources. Dashed rules mark authenticated service edges.
             Evidence Gateway
             runs one OIDC bearer profile with one trusted issuer, one principal claim, and no trust
             in proxy identity headers. Caller to Evidence Gateway carries a bearer token, a declared
             purpose, a named requirement revision, and selector values; the answer, not the record,
             comes back, only predefined revisions are evaluable, one authorization decision binds
-            the whole tuple, and possession is never authority. Evidence Gateway to its fixed sources fixes
-            origin, method, path, headers, credential, TLS, projection, redirect and proxy denial,
-            and one request per configured acquisition stage by trusted configuration executed only by the core, with
-            lookup collapsing to match, no match, or ambiguous and raw responses never logged. Evidence
+            the whole tuple, and possession is never authority. Evidence Gateway to its fixed sources
+            runs one fixed source request per acquisition stage, fixed by trusted configuration and
+            executed only by the core: an HTTP source fixes origin, method, path, headers,
+            authentication, TLS, projection, and redirect and proxy denial, and a statement source fixes
+            one reviewed SQL statement over a local read-only extract, with no origin, no credential,
+            and no network hop. Lookup collapses to match, no match, or ambiguous, and raw responses
+            are never logged. Evidence
             Gateway and Registry Relay keep separate sources, credentials, authorization decisions, and
             audit trails. Registry Mint to Evidence Gateway
             runs one way: Evidence Gateway verifies the token as an ordinary resource server that knows
@@ -111,6 +115,15 @@ auditing separately.
             /.well-known/jwt-vc-issuer are unauthenticated at the Evidence Gateway edge. +{/* Evidence: the diagram's two source transports are the closed http-json and sqlite-extract + variants under `source` in products/evidence/contracts/bundle.schema.yaml (frozen), governed by + contracts registry.evidence.fixed-http-json-source/v1, + products/evidence/contracts/source-contract.yaml, and + registry.evidence.fixed-sqlite-extract-source/v1, + products/evidence/contracts/sqlite-extract-source-contract.yaml (both frozen). The statement + transport's absent origin, credential, and network path are stated in the latter under + evidence_data_request.transport_absences. */} + **The service edge (authentication).** Authentication is the trust boundary at the service edge, and each service authenticates every record- or assertion-bearing route before responding. Registry Relay has exactly one authentication mode, OIDC bearer, and it is @@ -120,10 +133,10 @@ not. A Relay contract with any protected operation refuses to start without a co so a deployment cannot leave a protected operation unauthenticated by omission. There is no static-credential mode and no API-key mode; the runtime reads no credential from configuration. {/* Evidence: AuthenticationRuntime carries only an optional issuer, - crates/registry-relay-v2/src/contract.rs:1189; validate_runtime_contract requires an issuer - when the contract has protected access, crates/registry-relay-v2/src/startup.rs:469-492; + crates/registry-relay-v2/src/contract.rs; validate_runtime_contract requires an issuer + when the contract has protected access, crates/registry-relay-v2/src/startup.rs; RelayAuthenticator::authorize() admits Public unconditionally and requires a scoped - principal for Protected, crates/registry-relay-v2/src/auth.rs:205-257. */} + principal for Protected, crates/registry-relay-v2/src/auth.rs. */} Evidence Gateway runs one reviewed OIDC bearer profile with exactly one trusted issuer and exact audience, token type, and algorithm allowlists, and one configured principal claim with no `client_id`, `azp`, header, or request fallback: missing data denies rather than @@ -161,13 +174,17 @@ granted set rather than an identity-provider attestation, unless the operator ha distinct requester tag per purpose. **Evidence Gateway to its fixed sources.** Evidence Gateway's outbound edge is deliberately not -general-purpose. Source calls are fixed by trusted bundle configuration and executed only by -the core, which owns the closed single or search-then-fetch sequence, origin, method, fixed or tagged -selector or prior-fact-bound path, fixed headers, authentication, TLS, projection, redirect denial, -proxy denial, limits, and a one-request-per-stage ceiling; this is what closes SSRF, credential +general-purpose. A source reaches its data over one of two coequal transports, a fixed HTTP JSON +request or one reviewed SQL statement against a read-only SQLite extract file mounted beside the +process, and the core owns both. Source calls are fixed by trusted bundle configuration and +executed only by the core, which owns the closed single or search-then-fetch sequence, the limits, +and the one-request-per-stage ceiling on either transport, and on the HTTP transport also the +origin, method, fixed or tagged selector or prior-fact-bound path, fixed headers, authentication, +TLS, projection, redirect denial, and proxy denial; this is what closes SSRF, credential forwarding, script-directed networking, and response-led routing (`V1-I09`, `V1-I40`). A preparation -script renders only bounded query pairs and one JSON body, and Rust -validates that output before a credential is resolved. Every source declares a response schema +script renders only ordered query pairs and at most one JSON body on the HTTP transport, or a +bounded map of scalar parameters on the statement transport, and Rust validates that output +before a credential is resolved or a row is read. Every source declares a response schema that Rust validates the projected response against before any extraction script runs, so a response outside its reviewed shape is a source-protocol failure rather than an input to fact construction. Lookup collapses to `match`, `no_match`, or `ambiguous`: Evidence Gateway never fetches @@ -175,10 +192,36 @@ broad candidate sets, follows pages, scores candidates, or exposes counts (`V1-I source responses are never persisted or logged (`V1-I14`). Hostname and fixed-origin verification stay mandatory, there is no trust-all mode, and ambient `HTTP_PROXY`-family variables are ignored. +The statement transport has none of those fields to fix: a statement source has no origin, scheme, +host, port, method, path, request or response media type, redirect policy, or header, and Evidence +Gateway holds no database credential for it. +What trusted configuration fixes there is the one reviewed SQL statement with its declared result +columns and parameter bindings, the read-only extract file the runtime binds by logical profile, +and the row, cell, statement-step, timeout, response-byte, and concurrency limits. + +{/* Evidence: the two coequal transports, products/evidence/CONCEPT.md, "A fixed source request + reaches its source over one of two coequal transports". The statement transport's absences, + contract registry.evidence.fixed-sqlite-extract-source/v1, + products/evidence/contracts/sqlite-extract-source-contract.yaml (frozen), keys + evidence_data_request.transport_absences.statement and + evidence_data_request.transport_absences.credentials. Preparation output shapes: that + contract's preparation_channel.output and preparation_channel.validation_order, "Rust + validates the exact map shape, entry count, value kinds, and sizes ... before any row is + read", beside ownership.scripts.prepare, "Renders only ordered query pairs and at most one + JSON body", in products/evidence/contracts/source-contract.yaml (frozen). The governed + statement, declared result columns, parameter bindings, logical extractProfile name, and the + row, cell, statement-step, timeout, response-byte, and concurrency limits, the same statement + contract's ownership.governed_bundle; the runtime binds each logical name to a process-local + path under sourceExtracts, products/evidence/contracts/runtime.schema.yaml (frozen). */} **Registry Relay and Evidence Gateway remain separate.** A deployment may operate both products, -and neither inherits the other's decisions. Evidence Gateway contacts its own configured -authoritative HTTP sources; Relay reads its own local SQLite file and contacts nothing. Each +and neither inherits the other's decisions. Evidence Gateway reaches its own configured +authoritative sources over either transport: a fixed HTTP JSON request, which does cross a network +boundary, or one reviewed SQL statement against a read-only SQLite extract file mounted beside the +process, which does not. Relay reaches no source over the network at all, and reads its own local +SQLite file. The two local reads are still not the same thing: Relay compiles a whole registry +contract into the fixed route set it serves over its source, while an Evidence Gateway statement +source runs one reviewed statement and returns one assertion. Each product owns its own credentials, authorization decision, audit trail, and readiness checks. One composition is supported, and it runs one way only: a Relay-served API is an ordinary protected HTTP endpoint, so an Evidence bundle may name one as a fixed HTTP source. That is a deployment @@ -186,6 +229,14 @@ choice, not a feature either product declares. Relay does not know it is being r Gateway still makes its own authorization and disclosure decision. A reviewer should reject a deployment description that treats Relay authorization as Evidence Gateway authorization. +{/* Evidence: the statement transport's absent network path, contract + registry.evidence.fixed-sqlite-extract-source/v1, + products/evidence/contracts/sqlite-extract-source-contract.yaml (frozen), key + evidence_data_request.transport_absences.network, "There is no network path from Evidence into + a registry data tier. The transport opens one local file."; Relay re-derives its whole + compiled contract and serves the fixed route set from it, crates/registry-relay-v2/src/package.rs + and router() in crates/registry-relay-v2/src/server.rs. */} + **Registry Mint to Evidence Gateway.** Registry Mint issues access tokens; Evidence Gateway verifies them as an ordinary resource server. The dependency runs one way, and Evidence Gateway's only knowledge of Registry Mint is an issuer URL and a key set: it reads every claim by the name in its own @@ -258,7 +309,7 @@ the reviewed contract. Four properties hold it: problem cannot leak a row, a path, or a statement into a response. {/* Evidence: crates/registry-platform-sqlite/src/schema.rs:131-133 and src/statement.rs:494-496 - (open flags); src/capture.rs:11 (SNAPSHOT_SIDECARS), :34-36 (symlink refusal), :42-45 + (open flags); src/capture.rs:11 (SNAPSHOT_SIDECARS), :34-38 (symlink refusal), :42-45 (read-only filesystem or non-writable mode), :285-296 (same_file: dev/ino/len/mode/mtime/ctime), :305-309 (same_live_file: dev+ino); src/statement.rs:800-851 (positional parameters rejected), :918-930 (named parameters bound by ordinal); @@ -299,14 +350,34 @@ through a governed read, and privacy regressions that expose raw subject identif optional cursor integrity key, and both are named indirectly as `secret:env/` or `secret:file/` rather than written inline. The file provider accepts only a single flat filename under the runtime root, refuses traversal and nesting, opens with `O_NOFOLLOW`, and - requires a regular file owned by the effective user with mode exactly `0600` and one hard link. - {/* Evidence: valid_secret_reference(), crates/registry-relay-v2/src/contract.rs:1144-1164; + requires a regular file owned by the effective user, with mode `0400` or `0600`, and exactly one + hard link. + An Evidence Gateway statement source resolves no source credential either: that transport has no + connection string, no secret reference, and no authentication kind, so it is credential-free by + construction rather than by profile, and a credential that does not exist cannot leak, expire + unnoticed, or be widened. An Evidence Gateway HTTP source is the case where a source credential + does exist, and the bundle names it by logical secret reference rather than writing it inline. The + one HTTP source that resolves none is the `none` authentication kind, and it is credential-free by + allowance rather than by construction: only a `local` assurance bundle may declare it, only at a + canonical numeric-loopback origin, and production and evidence-grade bundles reject that kind. + {/* Evidence: valid_secret_reference(), crates/registry-relay-v2/src/contract.rs; parse_reference(), validate_file_metadata(), and the tests references_use_only_the_two_exact_contract_grammars, - file_secret_uses_open_file_owner_and_exact_mode_checks, and + file_secret_accepts_only_owner_read_and_optional_owner_write_modes, and file_secret_rejects_every_name_for_a_hard_link in crates/registry-platform-config/src/secrets.rs. Note the non-Unix branch of - read_secret_file() checks only symlink-and-regular-file, with no owner or mode check. */} + read_secret_file() checks only symlink-and-regular-file, with no owner or mode check. The + statement transport's absent database credential, contract + registry.evidence.fixed-sqlite-extract-source/v1, + products/evidence/contracts/sqlite-extract-source-contract.yaml (frozen), key + evidence_data_request.transport_absences.credentials, with the reading it carries under + evidence_data_request.transport_absences.reading; the HTTP transport's logical secret + references, contract registry.evidence.fixed-http-json-source/v1, + products/evidence/contracts/source-contract.yaml (frozen), key + ownership.governed_bundle; `none` "sends no credential and is admitted only by a `local` + assurance bundle at a canonical numeric-loopback origin; production and evidence-grade + bundles reject that kind", under `sources.*.authentication.kind` in + products/evidence/contracts/bundle.schema.yaml (frozen). */} - Token forgery or acceptance of untrusted tokens: A token is trusted only after signature verification against the configured issuer JWKS, plus issuer, audience, token type, and algorithm checks. The service still owns its route scopes and grants regardless of what the @@ -347,7 +418,8 @@ through a governed read, and privacy regressions that expose raw subject identif and values violating type, codelist, cardinality, precision, or size bounds are rejected before evidence construction (`V1-I12`). - Payload substitution and unsigned success masquerading as verified evidence: The signed - flattened JWS is mandatory, available to every authorized grant, and the default response. + flattened JWS is mandatory, available to every authorized grant issuing under the + audience-scoped subject binding, and the default response. Unsigned output exists only through its own exact media type, and only when both the immutable bundle and the one matched grant permit it; requesting a format creates no permission of its own (`V1-I21`, `V1-I28`). The unsigned envelope is self-identifying and is @@ -355,9 +427,9 @@ through a governed read, and privacy regressions that expose raw subject identif evidence. - A release with no accountability record: The final immutable response bytes exist before the disclosure-release audit is durably accepted, and those exact bytes are what is released - afterward. Every native audit event records the closed response-protection mode, with a - signing key identity present exactly for a cryptographically protected release (`V1-I29`, - `V1-I30`). + afterward. Every authorized-material native audit event records the closed response-protection + mode, with a signing key identity present exactly for a cryptographically protected release, + while the minimal authorization-refusal event records neither field (`V1-I29`, `V1-I30`). - Unreviewed hot mutation of policy: Configuration is immutable for the life of the serving process. There is no runtime override, reload, merge, or fallback path, and the closed runtime file cannot reach any governed field (`V1-I18`). @@ -372,17 +444,36 @@ through a governed read, and privacy regressions that expose raw subject identif `/.well-known/openid-configuration`. The health-check subcommand refuses a URL carrying userinfo, a password, a query, or a fragment, disables proxies, and follows no redirect. Evidence Gateway's fixed executor closes the same class for its own HTTP sources: origin, - method, path authority, headers, credential, TLS, projection, redirect denial, proxy denial, + method, path authority, headers, authentication, TLS, projection, redirect denial, proxy denial, and a one-request-per-stage ceiling, with scripts controlling request flow only inside that - host-enforced authority. - {/* Evidence: IssuerRuntime::profile() discovery-URL constraints, - crates/registry-relay-v2/src/contract.rs:1215-1255; healthcheck() rejects userinfo, + host-enforced authority. Evidence Gateway's statement transport closes the class the way Relay + does, by construction rather than by policy: that transport opens one local extract file and has + no origin, no scheme, no host, no port, and no network path into a registry data tier, so there + is nothing for a URL security rule or a redirect denial to govern and no destination an attacker + could steer. + {/* Evidence: IssuerRuntime::profile() and canonical_issuer_transport_url() discovery-URL + constraints, crates/registry-relay-v2/src/contract.rs; healthcheck() rejects userinfo, password, query, and fragment and uses .no_proxy() with redirect::Policy::none(), - crates/registry-relay-v2/src/startup.rs. */} + crates/registry-relay-v2/src/startup.rs. The statement transport's absences, contract + registry.evidence.fixed-sqlite-extract-source/v1, + products/evidence/contracts/sqlite-extract-source-contract.yaml (frozen), keys + evidence_data_request.transport_absences.statement, "There is nothing for a fixed-header + allowlist, a URL security rule, or a redirect denial to govern, so this contract states + none.", and evidence_data_request.transport_absences.network, "There is no network path from + Evidence into a registry data tier. The transport opens one local file." */} - A hostile or defective reviewed script: Evidence Gateway's script engine has no filesystem, environment, network, process, or module access, no ambient clock or randomness, and a normative operation ceiling that terminates a runaway invocation with a closed, value-free - error. + error. On the statement transport a script additionally never receives the statement text, a SQL + fragment or constructed SQL, the extract path, the file handle, or the database connection, and + Rust binds every value into the prepared statement by index rather than rendering it into + statement text, so a statement's shape is identical for every request it serves. Statement + parameters are named only, and a positional parameter is refused. + {/* Evidence: contract registry.evidence.fixed-sqlite-extract-source/v1, + products/evidence/contracts/sqlite-extract-source-contract.yaml (frozen), keys + ownership.scripts.prohibited, parameters.binding_owner, "Rust binds every value into the + prepared statement by index. No value is ever rendered into statement text, so a statement's + shape is identical for every request it serves.", and parameters.named_only. */} - A caller widening its own read (Relay side): a Relay caller supplies only the named parameters the compiled operation declares, and cannot add a filter, a projection, a source column, or a statement. Where an operation declares an authority row binding, every row it returns is pinned @@ -480,7 +571,7 @@ These are the risks the design does *not* close: across replicas, so running N instances multiplies every budget by N and a restart resets each bucket. Treat them as an availability control, not an enumeration defense. {/* Evidence: QuotaLimiter is an in-memory per-operation token bucket, - crates/registry-relay-v2/src/server.rs:359-401. */} + crates/registry-relay-v2/src/server.rs. */} - A Relay package is not authenticated: `packageRevision` is a SHA-256 integrity digest that any holder of the package can recompute. Startup detects drift and tampering by re-deriving every artifact, but it cannot tell a legitimate package from a well-formed forgery. Package diff --git a/docs/site/src/content/docs/explanation/trusted-context-constraints.mdx b/docs/site/src/content/docs/explanation/trusted-context-constraints.mdx index ae6313979..ca10ba209 100644 --- a/docs/site/src/content/docs/explanation/trusted-context-constraints.mdx +++ b/docs/site/src/content/docs/explanation/trusted-context-constraints.mdx @@ -7,7 +7,7 @@ source_repos: - registry-stack - registry-relay - registry-evidence -last_reviewed: "2026-08-11" +last_reviewed: "2026-08-21" doc_type: explanation locale: en standards_referenced: @@ -53,9 +53,9 @@ is set by `metadataVisibility.processing`, which is public, operation-bound, or {/* Evidence: ProcessingDescription { id, operationRefs, purpose, recipientClass, legalBasisRef, dpvProfileRef, safeguards } with deny_unknown_fields, - crates/registry-relay-v2/src/contract.rs:1030-1040; MetadataVisibility carries a processing + crates/registry-relay-v2/src/contract.rs; MetadataVisibility carries a processing field over the closed Visibility enum Public | OperationBound | OperatorOnly, - crates/registry-relay-v2/src/contract.rs:1042-1060; the compiler requires every + crates/registry-relay-v2/src/contract.rs; the compiler requires every legalBasisRef and dpvProfileRef to resolve to a governed file it seals, crates/registry-relay-v2/src/compiler.rs. */} @@ -68,7 +68,7 @@ the value that pins every returned row comes from a token claim or from the toke identifier, never from a request field. {/* Evidence: PurposeConstraint { claim, allowed }, - crates/registry-relay-v2/src/contract.rs:966-969; RelayAuthenticator::authorize() reads the + crates/registry-relay-v2/src/contract.rs; RelayAuthenticator::authorize() reads the purpose claim from the verified principal and denies a value outside the allowed list, crates/registry-relay-v2/src/auth.rs:204-253. */} @@ -115,20 +115,51 @@ authorization is most often assumed rather than enforced: entitlement (invariant `V1-I07`). Evidence Gateway has no notion of a Relay operation or a compiled evidence mode selecting one. It -declares a closed single or search-then-fetch acquisition over HTTP sources with fixed origins, -methods, fixed or tagged selector or prior-fact-bound paths, fixed non-secret headers, denied -redirects, and client-side response projection. The core, not a script or response, executes the -sequence and enforces one request per configured stage (invariants `V1-I09` and `V1-I40`). The source +declares a closed single or search-then-fetch acquisition, and the acquisition kind places no +constraint on which of the two coequal transports a stage uses. +An HTTP JSON stage is fixed by its origin, which a production bundle must state as HTTPS and only a +local unauthenticated source may state as a numeric loopback address, plus its method, its fixed or +tagged selector or prior-fact-bound path, its fixed non-secret headers, its denied redirects, and +its client-side response projection. +A statement stage is fixed instead by one reviewed SQL statement held in the bundle, the result +columns that statement declares in result order, the parameter bindings it declares, and the +read-only SQLite extract the runtime binds to the logical profile the bundle names. It reaches no +origin, resolves no credential, and opens one local file. +The core, not a script or response, executes the sequence and enforces one request per configured +stage (invariants `V1-I09` and `V1-I40`). The source boundary belongs entirely to Evidence Gateway and its authoritative sources. -## Freshness has no counterpart in either product +{/* Evidence: the closed `transport` tag selecting http-json or sqlite-extract, the baseUrl pattern + pair admitting an http numeric-loopback origin only when a source's authentication kind is + none, and the statement source's `extractProfile` logical name, declared result columns, and + declared parameter bindings, products/evidence/contracts/bundle.schema.yaml (frozen); the + runtime binds each logical name to a process-local path under `sourceExtracts`, + products/evidence/contracts/runtime.schema.yaml (frozen); the statement transport reaches no + origin and holds no credential, `transport_absences` in + products/evidence/contracts/sqlite-extract-source-contract.yaml (frozen). */} + +## Record freshness has no counterpart in either product An assertion carries `observedAt`, which is when Evidence Gateway evaluated the requirement. It is -not a source-declared observation time, and Evidence Gateway Version 1 has no source freshness -family: there is no maximum accepted observation age, no freshness field, and no configuration for -one. The request nonce is uninterpreted correlation data and is explicitly not a freshness proof +not a source-declared observation time, and Version 1 has no general source freshness family: no +freshness field on an assertion, and no accepted-observation-age setting on an HTTP JSON source. +One narrower bound does exist, and only on the statement transport. A statement source must declare +`maximumExtractAgeSeconds`, and the runtime compares it against the `publishedAt` instant the +extract itself publishes, before a single row is read, so an extract past its declared age fails as +an unavailable dependency rather than yielding a confidently signed assertion. That bounds how +stale the extract file may be, not how stale the record inside it is. +The request nonce is uninterpreted correlation data and is explicitly not a freshness proof (`products/evidence/contracts/cccev-field-mapping.yaml`). +{/* Evidence: `maximumExtractAgeSeconds` is a required key on a sqlite-extract source, 1 through + 2,592,000 seconds, products/evidence/contracts/bundle.schema.yaml (frozen); + extract_age_within_bound() compares it against the ExtractMetadata publication instant, which + is read from the extract's reserved metadata table and never from the file's modification + time, and validate_extract_age() runs before any row is read, both in + crates/registry-evidence/src/source_sqlite.rs; source_failure_problem() answers every source + failure with ProblemCode::DependencyUnavailable, + crates/registry-evidence/src/runtime.rs. */} + Registry Relay has no freshness family either. A `snapshot` source is pinned to the exact bytes captured at startup, so its answers are as current as the file an operator placed there and no more; a `live-read-only` source reflects the file as other processes commit to it. Neither profile diff --git a/docs/site/src/content/docs/spec/rs-arc-g.mdx b/docs/site/src/content/docs/spec/rs-arc-g.mdx index 67c010c5a..47fece766 100644 --- a/docs/site/src/content/docs/spec/rs-arc-g.mdx +++ b/docs/site/src/content/docs/spec/rs-arc-g.mdx @@ -11,7 +11,7 @@ source_repos: - registry-evidence - registry-mint - solmara-lab -last_reviewed: "2026-08-11" +last_reviewed: "2026-08-21" doc_type: specification doc_id: RS-ARC-G category: normative @@ -149,7 +149,7 @@ Relay reads adopter data through the shared bounded read-only SQLite boundary, o ### Evidence Gateway -Evidence Gateway (`registry-evidence`) is a single-crate Rust service and the `evidence` binary, depending on the portable `registry-evidence-verifier` library for the response formats, the Evidence payload contract, and relying-party verification, so client tooling can verify a signed response without the service. Each evaluation answers one bounded question about one complete subject-role set with one signed assertion that carries the answer and not the record. Its request-batch route groups up to sixteen independent audience-scoped evaluations under one common requirement and purpose, while preserving one independently signed assertion per available item. It loads one reviewed governed bundle and one closed operator runtime file at startup, treats both as immutable for the process lifetime, and holds no application database: it persists no selector, no source payload, no evidence value, and no response body. It authenticates callers with strict OIDC bearer tokens against exactly one trusted issuer, matches exactly one authority path per evaluation, calls the fixed bounded HTTP JSON sources its bundle declares, and releases an assertion as a flattened JWS JSON document by default. Where the bundle and the matched grant both enable it, the same assertion can also be serialized as an SD-JWT VC (`application/dc+sd-jwt`) or as a visibly unsigned envelope for local diagnosis. +Evidence Gateway (`registry-evidence`) is a single-crate Rust service and the `evidence` binary, depending on the portable `registry-evidence-verifier` library for the response formats, the Evidence payload contract, and relying-party verification, so client tooling can verify a signed response without the service. Each evaluation answers one bounded question about one complete subject-role set with one signed assertion that carries the answer and not the record. Its request-batch route groups up to sixteen independent audience-scoped evaluations under one common requirement and purpose, while preserving one independently signed assertion per available item. It loads one reviewed governed bundle and one closed operator runtime file at startup, treats both as immutable for the process lifetime, and holds no application database: it persists no selector, no source payload, no evidence value, and no response body. It authenticates callers with strict OIDC bearer tokens against exactly one trusted issuer, matches exactly one authority path per evaluation, calls the fixed bounded HTTP JSON or SQLite extract sources its bundle declares, and releases an assertion as a flattened JWS JSON document by default. Where the bundle and the matched grant both enable it, the same assertion can also be serialized as an SD-JWT VC (`application/dc+sd-jwt`) or as a visibly unsigned envelope for local diagnosis. Evidence Gateway Version 1 supports its declared holder-bound subject-binding mode and bounded issuance envelope, but has no credential lifecycle, no interactive holder-binding ceremony or issuance session, no status list, no revocation, no delegated or federated evaluation between peers, no policy decision point, no replay subsystem, and no worker or document subsystem. It does not depend on Relay, does not consult it, and does not read a metadata manifest. Evidence Gateway may consume a Relay-protected API through its ordinary fixed HTTP source contract, and that consumption grants it none of Relay's authorization model. @@ -293,6 +293,11 @@ This specification is `verified`: it is distilled from published artifacts a rea deny-by-default statement authorizer. */} {/* Evidence: crates/registry-evidence-oid4vci/src/lib.rs states the three non-negotiable properties of the wallet delivery front end. */} +{/* Evidence: an Evidence Gateway source request reaches its source over one of two coequal + transports, contract registry.evidence.fixed-http-json-source/v1 in + products/evidence/contracts/source-contract.yaml and contract + registry.evidence.fixed-sqlite-extract-source/v1 in + products/evidence/contracts/sqlite-extract-source-contract.yaml (both frozen). */} - The [boundary map](../../map/boundaries-and-map/) records each component's boundaries with their source citations. It is a distillation source for the components (Section 3) and the invariants (Section 5). - The [architecture overview](../../explanation/architecture/) gives the narrative data and contract flow that Section 4 makes precise. diff --git a/docs/site/src/content/docs/start/evidence-quickstart.mdx b/docs/site/src/content/docs/start/evidence-quickstart.mdx index 2713644a9..351ece8eb 100644 --- a/docs/site/src/content/docs/start/evidence-quickstart.mdx +++ b/docs/site/src/content/docs/start/evidence-quickstart.mdx @@ -5,7 +5,7 @@ status: current owner: registry-docs source_repos: - registry-stack -last_reviewed: "2026-08-04" +last_reviewed: "2026-08-21" doc_type: explanation locale: en standards_referenced: [] @@ -105,10 +105,15 @@ signed the exact assertion bytes and whether the assertion matches the retained expectations. Evidence Gateway is separate from Registry Relay. Registry Relay exposes protected, selected records. -Evidence Gateway contacts its own configured authoritative HTTP sources and retains its own authorization, +Evidence Gateway contacts its own configured authoritative sources and retains its own authorization, derivation, signing, verification, and audit boundaries. Registry Mint is optional supporting infrastructure that supplies short-lived caller tokens when a deployment has no identity provider. +{/* Evidence: a fixed source request reaches its source over one of two coequal transports, a fixed + HTTP JSON request or one reviewed SQL statement against a read-only SQLite extract, + products/evidence/CONCEPT.md; the closed http-json and sqlite-extract source variants under + `source` in products/evidence/contracts/bundle.schema.yaml (frozen). */} + ## Next - [Get your first Evidence Gateway assertion](../../tutorials/first-evidence-assertion/)