Skip to content

Commit b2befc8

Browse files
committed
docs(render): state the seal's guarantees, residuals and operator steps after review
- docs(render): state how the bounded reader's guarantee is established SECURITY-MATRIX row 27 now says the reader's agreement with hayro-syntax 0.7.2 rests on a reading of that version's source, with one regression test for each difference found, rather than exhaustive or fuzzed proof, and that the pin makes any new version repeat that reading. The signature check is said to see every object the cross-reference table lists, which is what the stream refusals give. - docs(render): state the seal's outside calls and the serving clients in the concept The removed Definition of Done carried two rules the concept did not: the seal route's calls beyond OIDC (the Transit signing proxy and the timestamp authority, from the serving process only), and the serving clients, which call the HTTP routes and hold no rendering code. The concept now states both, and no longer lists a client crate as outside version 1. The example bundles' PDF/A note now names the bundles it covers, since the seal fixture's veraPDF runs are recorded in EVIDENCE.md. - docs(render): state the seal's remaining accepted residuals in the matrix The security matrix is Render's security document, so each accepted residual the seal work leaves is stated there: a worker with code execution can signal the serving process; reading a local-jwk key file leaves two copies of key text in dependencies that Render does not erase; and /ready is unauthenticated, so any caller who reaches the listener reads seal readiness and can make a not-ready Transit seal probe once at a time. The timestamp authority's unpinned identity was already stated. - docs(render): say what the serve suite and the two-OS golden job establish Both named the removed Definition of Done as their purpose. The serve suite's module comment now lists what it covers, and the golden record says what identical hashes on macOS and Linux prove. - docs(render): close the operator gaps a real OpenBao seal run found An end-to-end run against OpenBao 2.7.2 passed every step, but each of these needed knowledge the operate page did not give: - Where the Transit key, its policy and the proxy come from: the Evidence Transit tutorial's, since Render makes the same key read and prehashed sha2-256 sign with the pinned key_version. - How Render finds the issuer's keys: discovery, or jwksSource as a URI or a static key set held as a secret. - What turns the test seal's signing block into a working seal: the authentication block, `seal: ministry` in the manifest and a new package, and a caller token with render:seal from an allowed client. - Why a secret file is refused: a file secret must be a regular file the serving user owns, mode 0400 or 0600, with one link. - What readiness reads for Transit: the signer's last known state, so a seal turns signer-not-ready only once a sign call fails. Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
1 parent c6a8160 commit b2befc8

6 files changed

Lines changed: 62 additions & 22 deletions

File tree

‎crates/registry-render/tests/serve.rs‎

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
//! End-to-end serve tests: the real `registry-render` binary serving real
22
//! requests, spawning real supervised workers, writing a real audit file
3-
//! through the platform audit writer. These tests are the DoD's serve-mode
4-
//! acceptance coverage.
3+
//! through the platform audit writer. They cover the render route end to end:
4+
//! authentication, the PDF and JSON answers, the paired audit entries,
5+
//! package verification at startup and per render, body limits, bounded
6+
//! problems and pathological renders.
57
68
use std::collections::{BTreeMap, BTreeSet};
79
use std::fs::File;

‎docs/site/src/content/docs/operate/registry-render.mdx‎

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,10 @@ These rules are not in the tables:
117117
- `secret:file/<name>` resolves under `secretProviders.file.root`. `secret:env/<NAME>` works only
118118
when `secretProviders.environment: {}` is declared. A reference to an undeclared provider is
119119
refused.
120+
- A file a `secret:file/` reference names (the API key, an OIDC key set, a seal key, a CA bundle)
121+
is a regular file owned by the user serve runs as, with mode `0400` or `0600` and a single link.
122+
Serve follows no symbolic link to one, and refuses any other file at startup with
123+
`runtime-invalid` and the message `the referenced secret file is unsafe`.
120124
- A string value may come from the environment as `${VAR}`, `${VAR:-default}`, or
121125
`${VAR:?message}`, except in `apiKeyRef` and under `secretProviders`.
122126
- `audit.destination: stdout` hands entries to your collector and takes no `path`.
@@ -266,7 +270,14 @@ certificate, its issuer, and how you hold the key.
266270
You need an OIDC deployment (a seal never works with the legacy API key), a seal certificate chain
267271
in a PEM file with the seal certificate first, and the seal key in Vault or OpenBao Transit: an
268272
`ecdsa-p256` key created non-exportable, reached through a Transit signing proxy on a Unix socket
269-
on the Render host. The proxy holds the Transit token; Render sends none. With the proxy running,
273+
on the Render host. The proxy holds the Transit token; Render sends none. Create the key and its
274+
least-privilege policy as
275+
[Configure Transit signing for Registry Evidence](../../tutorials/move-evidence-to-production-signing/#create-the-transit-key-and-its-policy)
276+
does, and run the proxy
277+
[with a forced token](../../tutorials/move-evidence-to-production-signing/#run-the-proxy-with-a-forced-token),
278+
its socket group admitting the Render service. Render makes the same calls, a read of the key and a
279+
prehashed `sha2-256` sign with the pinned `key_version`, so that policy works with your key name and
280+
version in place of Evidence's. With the proxy running,
270281
`registry-render keygen --csr <file> --common-name <seal name> --transit-unix-socket-path
271282
<socket> --transit-mount <mount> --transit-key-name <key> --transit-key-version <version>` reads
272283
the key version's public key and has Transit sign the certificate request your certificate
@@ -301,6 +312,11 @@ signing:
301312
trust: production
302313
```
303314

315+
Render reads the issuer's signing keys from its OpenID Connect discovery document, or from
316+
`authentication.oidc.jwksSource`: a key set URL (`type: uri`) or a key set held as a secret
317+
(`type: static` with `documentRef`, such as `secret:file/jwks.json`).
318+
[Connect an OIDC issuer](../advanced/connect-an-oidc-issuer/) covers each choice.
319+
304320
- `trust` is required. `production` marks an official seal, and it signs through `transit` only:
305321
Render refuses a production seal with a key file
306322
(`render.runtime.seal-signer-assurance-mismatch`). `test-only` marks a test seal; see
@@ -442,8 +458,12 @@ signing:
442458
trust: test-only
443459
```
444460

445-
Then run `check --environment` and `serve` as above. `test-root.key` can issue certificates: keep
446-
it out of every deployment, and never trust the test root outside development.
461+
Put this `signing` block in a runtime file that has the `authentication` block from
462+
[Enable a seal](#enable-a-seal). Name the seal in the document type's `manifest.yaml` entry with
463+
`seal: ministry`, and run `registry-render package` again. Then run `check --environment` and
464+
`serve` as above. Callers need a token from your issuer that carries `render:seal`, issued to a
465+
client the seal's `allowedClients` names. `test-root.key` can issue certificates: keep it out of
466+
every deployment, and never trust the test root outside development.
447467

448468
### Renew the certificate
449469

@@ -467,8 +487,10 @@ When seals are configured, `/ready` adds a `signing` member with one entry per s
467487
`certificate-expired`, `certificate-not-yet-valid`, or `signer-not-ready`. A seal never changes
468488
the status code: Render stays ready for plain rendering while a seal cannot sign, and a seal
469489
request answers `signing-unavailable` or `seal-certificate-invalid` (503) until it can. Readiness
470-
reads the certificate validity and the key provider; it never calls the timestamp authority, so a
471-
ready seal can still fail with `timestamp-failed`.
490+
reads the certificate validity and the signer's last known state; it never calls the timestamp
491+
authority, so a ready seal can still fail with `timestamp-failed`. A Transit seal turns
492+
`signer-not-ready` only after a sign call fails, so after its proxy stops, `/ready` reports it
493+
ready until a seal request fails.
472494

473495
A Transit seal whose proxy stops answering fails the seal request in progress with
474496
`signing-unavailable` and turns `signer-not-ready`. Each `/ready` call and each seal request then

‎products/render/CONCEPT.md‎

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,10 @@ APIs. It is a pure rendering function (bundle + validated data in,
1111
byte-stable PDF + hashes out), a governed template package as its
1212
content unit, and a value-free audit log as its issuance record.
1313
No database or rendering-path network calls, and no engine changes in any other
14-
product. Only the optional HTTP adapter uses OIDC discovery/JWKS networking.
14+
product. Only the optional HTTP adapter makes outside calls: OIDC discovery and
15+
JWKS, and for an optional seal, the Transit signing proxy on a host Unix socket
16+
and the timestamp authority, both from the serving process, never the render
17+
worker.
1518

1619
## Document families
1720

@@ -31,9 +34,9 @@ record through an app-owned public check route and an Evidence-backed relying
3134
service. The app owns the bounded answer and its disclosure policy; Render
3235
mints nothing and does nothing at scan time.
3336

34-
Version 1 has no batch or multi-document request, client crate, TLS
35-
termination, per-tenant rate limiting, metrics route, or hot reload or runtime
36-
bundle editing, and no document-type-specific behavior in production code.
37+
Version 1 has no batch or multi-document request, TLS termination, per-tenant
38+
rate limiting, metrics route, or hot reload or runtime bundle editing, and no
39+
document-type-specific behavior in production code.
3740

3841
## Sealing
3942

@@ -111,7 +114,10 @@ house product anatomy: `init`/`check`/`validate`/`package`/`compile`/`serve`/
111114
runtime configuration loader) for deployment-local bindings; the
112115
`registry-platform-*` primitives (config loader and secrets, httpsec layers and
113116
problems, the shared audit writer, authcommon key handling, buildinfo, canonical
114-
JSON) reused rather than reinvented.
117+
JSON) reused rather than reinvented. The serving clients,
118+
`registry-render-client` in Rust with its Node and Python bindings
119+
(`registry-render-client-node`, `registry-render-client-py`), call the HTTP
120+
routes and hold no rendering code.
115121

116122
The rendering engine is Typst in library mode, pinned at 0.15.1 with a
117123
lockfile matched to the Typst release — including the deflate stack,

‎products/render/EVIDENCE.md‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -294,8 +294,8 @@ cargo test -p registry-render --test golden # fails with the drift first
294294
```
295295

296296
The two-OS CI job (see `.github/workflows/render-golden.yml`) runs the
297-
golden test on macOS and Linux; identical hashes across both are the
298-
cross-machine byte-stability proof the DoD requires.
297+
golden test on macOS and Linux; identical hashes across both prove that
298+
the same inputs give the same bytes on both operating systems.
299299

300300
**Regenerated 2026-09-17, example corpus made generic.** The three example
301301
bundles named one real programme, its institution, its jurisdiction and its

‎products/render/README.md‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,9 +50,9 @@ Latin font set. Scripts beyond Latin (Arabic, Hebrew, …) need a bundle font
5050
the golden-hash record.
5151
- `bundles/` — three immutable packaged examples: a bilingual RTL receipt, a
5252
PDF/A-4 certificate, and an ID-1 duplex card with a photo. Formal PDF/A
53-
conformance validation (for example veraPDF) is not run; the evidence
54-
records only that the `pdfaid:part` marker is present. Use them with
55-
`check`, `compile`, or `serve`; start editable source with
53+
conformance validation (for example veraPDF) is not run on these bundles;
54+
the evidence records only that the `pdfaid:part` marker is present. Use
55+
them with `check`, `compile`, or `serve`; start editable source with
5656
`registry-render init`.
5757
- `integrations/` — the OpenFn job and App Kit wiring sketches.
5858
- `schemas/`: JSON Schemas for `manifest.yaml`, `labels/<locale>.yaml`, and

0 commit comments

Comments
 (0)