Unreleased documentation. These pages follow the main branch and can change before the next release. For supported guidance, use v0.15.2.
Use this guide after registryctl build to turn the two unsigned product-lane inputs into one
ApprovedBaselineSetV1 that a deployment operator can consume.
When to use this
Section titled “When to use this”Use this procedure for the first governed approval of a project environment. For a later change, compare against the current approved set and follow Approve a changed source.
The public Relay and consultation Relay lanes keep separate anchors, signing keys, signed bundles, and approval decisions. The approved-set owner assembles verified lane outputs but does not sign on behalf of a lane.
Evidence Gateway and Registry Mint
are not registryctl-generated products and do not participate in this trust-anchor and
approved-set pipeline at all: each validates its own deployment inputs directly, with
evidence check and mint check. The current evidencectl new command starts an incomplete
OpenAPI authoring workspace; it does not create Evidence Gateway deployment inputs. For a complete
Evidence Gateway project, evidencectl fixtures run drives the offline fixture checks. See
Configure Evidence Gateway and Configure Registry Mint
for those procedures.
Before you start
Section titled “Before you start”Use this procedure only when the selected build binds an operator-managed source and reports
Next: registryctl trust anchor create --help.
Do not sign a build that contains a project-local file dataset. Bind the operator-managed source
in a separate governed environment, rerun its fixtures and review, and build that environment
first.
From the authored project, test, review, and build the target environment:
registryctl test --environment localregistryctl check --environment local --explainregistryctl review compare --environment localregistryctl build --environment localAn initial comparison identifies both lanes as requiring approval. The build creates one unsigned input under each of these directories:
.registry-stack/build/local/signing-inputs/relay-public.registry-stack/build/local/signing-inputs/relay-consultationThe optional local evaluation-key procedure requires OpenSSL with Ed25519 support and Python 3. Production trust owners use their approved custody tooling instead.
Prepare a fresh handoff directory.
Each lane trust owner also needs an approved public JSON Web Key (JWK) file and the matching
private key through an explicit file: or op:// locator.
mkdir operator-handoffDo not put a private signing key in the generated build or the handoff directory.
Generate evaluation-only lane keys
Section titled “Generate evaluation-only lane keys”The maintained HTTP starter declares only the local environment.
For a local evaluation, this procedure creates one independent Ed25519 key pair per lane using
OpenSSL and the Python 3 standard library:
This procedure writes exportable private keys to local files. Never use these keys for a production approval. In production, each lane trust owner must generate and retain its private key inside the organization’s approved external custody boundary, then provide only the public JWK and an approved signing locator.
umask 077python3 - <<'PY'from pathlib import Path
Path("evaluation-keys").mkdir(mode=0o700)PY
for lane in relay-public relay-consultation; do openssl genpkey \ -algorithm ED25519 \ -outform DER \ -out "evaluation-keys/$lane.private.der" openssl pkey \ -in "evaluation-keys/$lane.private.der" \ -inform DER \ -pubout \ -outform DER \ -out "evaluation-keys/$lane.public.der" LANE="$lane" python3 - <<'PY'import base64import jsonimport osfrom pathlib import Path
root = Path("evaluation-keys")lane = os.environ["LANE"]private_der_path = root / f"{lane}.private.der"public_der_path = root / f"{lane}.public.der"private_der = private_der_path.read_bytes()public_der = public_der_path.read_bytes()private_prefix = bytes.fromhex("302e020100300506032b657004220420")public_prefix = bytes.fromhex("302a300506032b6570032100")if ( len(private_der) != len(private_prefix) + 32 or not private_der.startswith(private_prefix) or len(public_der) != len(public_prefix) + 32 or not public_der.startswith(public_prefix)): raise SystemExit("openssl emitted an unexpected Ed25519 key encoding")
def encode(value): return base64.urlsafe_b64encode(value).rstrip(b"=").decode("ascii")
public = { "crv": "Ed25519", "kty": "OKP", "x": encode(public_der[-32:]),}private = { **public, "d": encode(private_der[-32:]),}for kind, value in (("private", private), ("public", public)): path = root / f"{lane}.{kind}.jwk" path.write_text( json.dumps(value, sort_keys=True, separators=(",", ":")) + "\n", encoding="utf-8", ) path.chmod(0o600)private_der_path.unlink()public_der_path.unlink()PYdoneThe procedure refuses to reuse an existing evaluation-keys directory.
It leaves each JWK owner-readable and owner-writable only.
Approve the public Relay lane
Section titled “Approve the public Relay lane”The public Relay trust owner creates the lane anchor, signs the exact build input, and verifies the result:
registryctl trust anchor create \ --lane relay-public \ --input .registry-stack/build/local/signing-inputs/relay-public \ --public-key evaluation-keys/relay-public.public.jwk \ --threshold 1 \ --output-file operator-handoff/relay-public-anchor.jsonregistryctl trust bundle sign \ --lane relay-public \ --input .registry-stack/build/local/signing-inputs/relay-public \ --anchor operator-handoff/relay-public-anchor.json \ --key file:evaluation-keys/relay-public.private.jwk \ --output-dir operator-handoff/relay-public-bundleregistryctl trust bundle verify \ --bundle-dir operator-handoff/relay-public-bundle \ --anchor operator-handoff/relay-public-bundle/anchor.jsonVerification must identify the signed input as the relay-public lane under that anchor.
Approve the consultation Relay lane
Section titled “Approve the consultation Relay lane”The consultation Relay trust owner repeats the procedure with its own key and anchor:
registryctl trust anchor create \ --lane relay-consultation \ --input .registry-stack/build/local/signing-inputs/relay-consultation \ --public-key evaluation-keys/relay-consultation.public.jwk \ --threshold 1 \ --output-file operator-handoff/relay-consultation-anchor.jsonregistryctl trust bundle sign \ --lane relay-consultation \ --input .registry-stack/build/local/signing-inputs/relay-consultation \ --anchor operator-handoff/relay-consultation-anchor.json \ --key file:evaluation-keys/relay-consultation.private.jwk \ --output-dir operator-handoff/relay-consultation-bundleregistryctl trust bundle verify \ --bundle-dir operator-handoff/relay-consultation-bundle \ --anchor operator-handoff/relay-consultation-bundle/anchor.jsonVerification must identify the signed input as the relay-consultation lane under that anchor.
Assemble the approved baseline set
Section titled “Assemble the approved baseline set”After both lane owners transfer their verified outputs, the approved-set owner assembles the initial set:
registryctl trust approved-set assemble \ --environment local \ --relay-public operator-handoff/relay-public-bundle \ --relay-consultation operator-handoff/relay-consultation-bundle \ --output-file operator-handoff/approved-set.v1.jsonRegistryctl verifies the two lane bundles and writes one approved-set file.
It reports the approved-set digest and the next registryctl deploy generate command.
The assembled document declares approved-set schema version 2.0.
Current registryctl refuses an approved set that declares schema version 1.0, carries a notary
lane, or binds cross-lane interface digests.
It reports why the document is refused and names re-approval as the remedy.
A refused set cannot be repaired by editing the file: the removed cross-lane interface digests are
no longer verified, so honoring the old approval would keep a signed integrity claim that nothing
enforces.
Re-run this procedure from the current lane bundles to issue a schema version 2.0 set.
Rotate an anchor after initial approval
Section titled “Rotate an anchor after initial approval”Do not create another version-1 anchor when an approved lane changes signer. Pin the current lane anchor and current approved set as the preceding trust state, then authorize the transition with the current threshold. For example, rotate the consultation Relay anchor and replace only that lane:
CURRENT_APPROVED_SET=operator-inputs/current-approved-set.v1.jsonCURRENT_ANCHOR=operator-handoff/relay-consultation-bundle/anchor.jsonROTATED_TRUST=operator-handoff/relay-consultation-anchor.v2NEXT_SIGNING_INPUT=.registry-stack/build/local/signing-inputs/relay-consultationNEXT_BUNDLE=operator-handoff/relay-consultation-bundle.v2
registryctl build \ --environment local \ --against "$CURRENT_APPROVED_SET" \ --rotate-anchor relay-consultationregistryctl trust anchor rotate \ --current-anchor "$CURRENT_ANCHOR" \ --next-public-key evaluation-keys/relay-consultation.public.jwk \ --next-public-key operator-inputs/relay-consultation-next.public.jwk \ --next-threshold 1 \ --key file:evaluation-keys/relay-consultation.private.jwk \ --output-dir "$ROTATED_TRUST"registryctl trust bundle sign \ --lane relay-consultation \ --input "$NEXT_SIGNING_INPUT" \ --anchor "$ROTATED_TRUST/anchor.json" \ --key file:operator-inputs/relay-consultation-next.private.jwk \ --against "$CURRENT_APPROVED_SET" \ --output-dir "$NEXT_BUNDLE"registryctl trust bundle verify \ --bundle-dir "$NEXT_BUNDLE" \ --anchor "$NEXT_BUNDLE/anchor.json"registryctl trust approved-set assemble \ --from "$CURRENT_APPROVED_SET" \ --relay-consultation "$NEXT_BUNDLE" \ --output-file operator-handoff/approved-set.v2.jsonregistryctl deploy generate \ --approved-set operator-handoff/approved-set.v2.json \ --output-dir operator-handoff/registry-stack.v2registryctl deploy verify --package operator-handoff/registry-stack.v2The explicit build selector emits the unchanged consultation input and binds the reviewed update
to that lane’s anchor rotation.
trust anchor rotate verifies that the transition is authorized by the pinned preceding anchor;
the repeated public-key options keep the current signer during the overlap.
trust bundle sign --against advances from the pinned preceding approved set, and assembly keeps
the unchanged public Relay lane from that set.
Deployment verification does not accept the rotated anchor into an existing product state.
For thresholds, overlap windows, and the required preview, stop, audited acceptance, exact
verification, and startup order, follow
Rotate credentials and trust.
Verify the handoff
Section titled “Verify the handoff”Inspect the result without printing key material:
test -f operator-handoff/approved-set.v1.jsonregistryctl deploy generate \ --approved-set operator-handoff/approved-set.v1.json \ --output-dir operator-handoff/registry-stackGENERATED_CLOSURE_SHA256="<independently-recorded-generated-closure-sha256>"registryctl deploy verify \ --package operator-handoff/registry-stack \ --expected-closure-sha256 "$GENERATED_CLOSURE_SHA256"Keep the approved-set digest and generated closure root outside the package before transfer. This check binds the generated package to that external record, but does not accept it for initialization. Continue with Run the generated single-node Compose package to supply and check operator files before one-time initialization and ordinary startup.
Troubleshooting
Section titled “Troubleshooting”| Symptom | Cause | Fix |
|---|---|---|
| Anchor creation rejects the public key | The key is not a supported public JWK or does not match the selected threshold. | Export the lane owner’s approved public JWK and retry into a fresh output path. |
| Bundle signing refuses the key | The locator is missing file: or op://, or the key does not satisfy the anchor. | Use an explicit locator for the matching private key. |
| Bundle verification fails | The bundle, lane, input, or anchor does not belong to the same approval. | Stop the handoff and return the exact input to that lane’s trust owner. |
| Approved-set assembly reports a missing lane | Initial assembly requires one verified public Relay bundle and one verified consultation Relay bundle. | Supply both verified lane directories. |