TLS Operations
This runbook covers the implemented hovel pki operator surface.
The commands talk to the daemon that owns the selected workspace; they do
not edit the PKI database or key files directly. Read-only commands may be
used interactively or with --json. Durable mutations that expose
--yes prompt for the literal answer yes when that flag
is omitted. Signing-lease unlock is different: it requires
--approve. The idempotent lock command requires neither prompt nor
confirmation flag.
hovel pki command. Recovery and master-key rotation are internal
infrastructure capabilities, not operator commands. Do not invent flags or
replace these missing front ends with direct database or secret-file edits.
- preflight
- initialize custody
- create root
- unlock lease
- issue leaf
- activate trust
- activate assignment
- inspect
- lock lease
Operator contract
| Surface | Implemented behavior | Operator consequence |
|---|---|---|
hovel pki |
Daemon-backed commands in this runbook. | Use these commands rather than internal Go packages or workspace files. |
| Daemon API | Typed PKI routes, including additional operation, rollover, bundle-export, and reconciliation methods. | An API route is not automatically a CLI command. Consult the daemon contract. |
| Go, Python, and Rust SDKs | Optional credential-delivery descriptors and secret-aware provider calls. | The SDKs let a provider consume an active assignment; they do not administer authorities or certificates. |
| Current crypto runtime | The daemon-local factory registers builtin-x509. |
Always check pki backend list. Do not assume HSM, cloud KMS, or module-provided backend support. |
Preflight and initialize custody
Start with an existing Hovel workspace and a running daemon. Every PKI
command accepts --workspace and --json. If the
daemon is not managed by a service, hovel daemon serve runs it
in the foreground and should be supervised in a separate terminal.
WORKSPACE="/absolute/path/to/hovel-workspace"
hovel daemon status --workspace "$WORKSPACE" --json
hovel pki status --workspace "$WORKSPACE" --json
# Only when status reports initialized=false:
hovel pki init --workspace "$WORKSPACE" --yes --json
hovel pki status --workspace "$WORKSPACE" --json
pki init creates workspace master-key custody when it is absent
and opens existing custody when it is already present. Repeating it does not
request a new master-key version. Status intentionally reports only
initialized, activeKeyVersion, the version count,
and a generic availability error; it does not disclose key bytes or secret
paths.
Discover profiles and backends
hovel pki backend list --workspace "$WORKSPACE"
hovel pki backend list --workspace "$WORKSPACE" --json
hovel pki profile list --workspace "$WORKSPACE"
hovel pki profile list --workspace "$WORKSPACE" --json
| Profile | Purpose | Peer trust on an assignment |
|---|---|---|
root-modern | Self-signed root authority | Not an assignment profile |
tls-server | Server authentication | Not required |
tls-client | Client authentication | Required |
mtls-server | Mutual-TLS server authentication | Required |
mtls-client | Mutual-TLS client authentication | Required |
dual-role-mtls | Server and client authentication | Required |
Other built-in profiles are visible in profile list. Select
only a profile and backend reported by the current daemon. Omitting
--backend uses the backend fixed by the selected profile; the
examples name builtin-x509 explicitly for audit clarity.
Create a root and capture generated identifiers
Authority creation accepts a stable retry key, but the current CLI has no
--id, --generation-id, or --key-id
option for this command. Hovel generates those identifiers. Capture the
JSON response instead of guessing them.
ROOT_JSON="$(
hovel pki authority create "Mesh Lab Root" \
--workspace "$WORKSPACE" \
--role root \
--profile root-modern \
--backend builtin-x509 \
--idempotency-key mesh-lab-root-v1 \
--yes \
--json
)"
printf '%s\n' "$ROOT_JSON"
ROOT_AUTHORITY_ID="$(printf '%s\n' "$ROOT_JSON" | jq -r '.authority.id')"
ROOT_GENERATION_ID="$(printf '%s\n' "$ROOT_JSON" | jq -r '.generation.id')"
jq is optional and is not invoked by Hovel.
Without it, redirect --json to a file and copy
authority.id and generation.id. Keep the same
--idempotency-key for an exact retry: a completed replay returns
the same generated identifiers. A changed request under the same key is an
idempotency conflict.
hovel pki authority list --workspace "$WORKSPACE"
hovel pki authority inspect "$ROOT_AUTHORITY_ID" \
--workspace "$WORKSPACE" \
--json
A root is created with persistent authority state locked. Root
creation is self-signed and does not need a pre-existing lease. Subsequent
certificate and CRL signatures do.
Open and close a signing lease
hovel pki authority unlock "$ROOT_AUTHORITY_ID" \
--workspace "$WORKSPACE" \
--duration 10m \
--approve \
--json
--approve is mandatory. The default duration is five minutes;
valid durations range from one second through fifteen minutes. The lease is
in-memory and scoped to the CLI actor hovel-cli and operation
pki. A fresh correlation ID distinguishes each call while that
stable actor/operation scope lets later PKI CLI calls use the lease. Daemon
restart or expiry removes the lease. Unlocking does not rewrite the
authority's persistent locked state.
hovel pki authority lock "$ROOT_AUTHORITY_ID" \
--workspace "$WORKSPACE" \
--json
Lock as soon as the signing window is complete. Repeating lock for the same caller is safe. Do not leave a long lease open merely to avoid another explicit approval.
Issue server, client, and mutual-TLS leaves
Keep the authority lease open while issuing. The positional name becomes the subject common name. For server, mutual-TLS server, and dual-role profiles, the default template also uses that name as its DNS subject alternative name. The CLI has no custom SAN, IP SAN, template, certificate-ID, generation-ID, or key-ID options for initial issuance.
# TLS server
hovel pki certificate issue "api.lab.example" \
--workspace "$WORKSPACE" --issuer "$ROOT_AUTHORITY_ID" \
--profile tls-server --backend builtin-x509 \
--idempotency-key issue-api-server-v1 --yes --json
# TLS client
hovel pki certificate issue "operator-01" \
--workspace "$WORKSPACE" --issuer "$ROOT_AUTHORITY_ID" \
--profile tls-client --backend builtin-x509 \
--idempotency-key issue-operator-client-v1 --yes --json
# Mutual-TLS server
hovel pki certificate issue "listener.lab.example" \
--workspace "$WORKSPACE" --issuer "$ROOT_AUTHORITY_ID" \
--profile mtls-server --backend builtin-x509 \
--idempotency-key issue-listener-mtls-server-v1 --yes --json
# Mutual-TLS client
hovel pki certificate issue "mesh-peer-01" \
--workspace "$WORKSPACE" --issuer "$ROOT_AUTHORITY_ID" \
--profile mtls-client --backend builtin-x509 \
--idempotency-key issue-peer-mtls-client-v1 --yes --json
# One certificate valid for both endpoint roles, when the consumer requires it
hovel pki certificate issue "mesh-dual-01.lab.example" \
--workspace "$WORKSPACE" --issuer "$ROOT_AUTHORITY_ID" \
--profile dual-role-mtls --backend builtin-x509 \
--idempotency-key issue-dual-role-mtls-v1 --yes --json
Initial issue returns a certificate-generation object directly. Capture
.id, .certificateId, and .keyId from
--json. List and inspect operations return certificate and
public-key material, provenance, compatibility, validity, and state, but no
private key.
MTLS_SERVER_JSON="$(
hovel pki certificate issue "listener.lab.example" \
--workspace "$WORKSPACE" --issuer "$ROOT_AUTHORITY_ID" \
--profile mtls-server --backend builtin-x509 \
--idempotency-key issue-listener-mtls-server-v1 --yes --json
)"
MTLS_SERVER_GENERATION_ID="$(printf '%s\n' "$MTLS_SERVER_JSON" | jq -r '.id')"
hovel pki certificate list --workspace "$WORKSPACE"
hovel pki certificate inspect "$MTLS_SERVER_GENERATION_ID" \
--workspace "$WORKSPACE" --json
Create, stage, and activate a trust set
A trust set is a mutable pointer to immutable trust generations. Creation
starts at revision 1 in pending. Stage and activate each require
the exact current revision and increment it. At least one CA certificate
generation is required as an anchor.
TRUST_SET_ID="trust-mesh-listener-clients"
TRUST_GENERATION_ID="trustgen-mesh-listener-clients-v1"
TRUST_JSON="$(
hovel pki trust create "Mesh listener client roots" \
--workspace "$WORKSPACE" \
--id "$TRUST_SET_ID" \
--idempotency-key create-mesh-listener-trust-v1 \
--yes --json
)"
TRUST_REVISION="$(printf '%s\n' "$TRUST_JSON" | jq -r '.revision')"
TRUST_JSON="$(
hovel pki trust stage "$TRUST_SET_ID" \
--workspace "$WORKSPACE" \
--revision "$TRUST_REVISION" \
--generation-id "$TRUST_GENERATION_ID" \
--anchors "$ROOT_GENERATION_ID" \
--idempotency-key stage-mesh-listener-trust-v1 \
--yes --json
)"
TRUST_REVISION="$(printf '%s\n' "$TRUST_JSON" | jq -r '.trustSet.revision')"
TRUST_JSON="$(
hovel pki trust activate "$TRUST_SET_ID" \
--workspace "$WORKSPACE" \
--revision "$TRUST_REVISION" \
--idempotency-key activate-mesh-listener-trust-v1 \
--yes --json
)"
TRUST_REVISION="$(printf '%s\n' "$TRUST_JSON" | jq -r '.trustSet.revision')"
TRUST_GENERATION_ID="$(printf '%s\n' "$TRUST_JSON" | jq -r '.trustSet.activeGenerationId')"
hovel pki trust inspect "$TRUST_SET_ID" \
--workspace "$WORKSPACE" --json
--anchors, --intermediates, and
--crls accept comma-separated generation IDs. A staged CRL must
be fresh and must be signed by an issuer present in that trust generation.
Activation changes the trust set's active generation; it does not silently
change assignments that already pin an older trust generation.
Bind, stage, and activate an assignment
Binding creates a revision-1 pending assignment. Staging checks
that the certificate purpose and profile match and, for a peer-trust
purpose, automatically pins the trust set's currently active generation.
There is no CLI option to choose a different trust generation. Activation
promotes both staged pins and normally produces revision 3 and state
active.
ASSIGNMENT_ID="assignment-mesh-listener-edge"
MESH_PROVIDER_NAME="replace-with-provider-name"
LISTENER_ID="replace-with-listener-id"
CONSUMER_ID="${MESH_PROVIDER_NAME}/${LISTENER_ID}"
ASSIGNMENT_JSON="$(
hovel pki assignment bind "$CONSUMER_ID" \
--workspace "$WORKSPACE" \
--id "$ASSIGNMENT_ID" \
--consumer-type mesh-listener \
--purpose mtls-server \
--profile mtls-server \
--trust-set "$TRUST_SET_ID" \
--idempotency-key bind-mesh-listener-edge-v1 \
--yes --json
)"
ASSIGNMENT_REVISION="$(printf '%s\n' "$ASSIGNMENT_JSON" | jq -r '.revision')"
ASSIGNMENT_JSON="$(
hovel pki assignment stage "$ASSIGNMENT_ID" "$MTLS_SERVER_GENERATION_ID" \
--workspace "$WORKSPACE" \
--revision "$ASSIGNMENT_REVISION" \
--idempotency-key stage-mesh-listener-edge-v1 \
--yes --json
)"
ASSIGNMENT_REVISION="$(printf '%s\n' "$ASSIGNMENT_JSON" | jq -r '.assignment.revision')"
ASSIGNMENT_JSON="$(
hovel pki assignment activate "$ASSIGNMENT_ID" \
--workspace "$WORKSPACE" \
--revision "$ASSIGNMENT_REVISION" \
--idempotency-key activate-mesh-listener-edge-v1 \
--yes --json
)"
ASSIGNMENT_REVISION="$(printf '%s\n' "$ASSIGNMENT_JSON" | jq -r '.assignment.revision')"
hovel pki assignment inspect "$ASSIGNMENT_ID" \
--workspace "$WORKSPACE" --json
--rotation-policy.
The flag is present on assignment bind, but the application
rejects every non-empty rotation-policy ID because durable rotation-policy
inventory is not implemented. Rotation remains an explicit operator
workflow.
Worked example: Mesh listener PKI from start to finish
The following is the complete PKI side of a mutual-TLS Mesh listener. The provider name is the canonical name reported by the selected Mesh provider, without an installed-version suffix. The listener ID must be the same ID supplied to the daemon's Mesh listener-start request. Replace the two placeholder values before use.
WORKSPACE="/absolute/path/to/hovel-workspace"
MESH_PROVIDER_NAME="replace-with-provider-name"
LISTENER_ID="replace-with-listener-id"
CONSUMER_ID="${MESH_PROVIDER_NAME}/${LISTENER_ID}"
TRUST_SET_ID="trust-${LISTENER_ID}-clients"
TRUST_GENERATION_ID="trustgen-${LISTENER_ID}-clients-v1"
ASSIGNMENT_ID="assignment-${LISTENER_ID}-server"
# 1. Preflight and custody.
hovel daemon status --workspace "$WORKSPACE" --json
hovel pki init --workspace "$WORKSPACE" --yes --json
hovel pki backend list --workspace "$WORKSPACE" --json
hovel pki profile list --workspace "$WORKSPACE" --json
# 2. Root. IDs are generated; the retry key makes an exact replay stable.
ROOT_JSON="$(
hovel pki authority create "Mesh listener root" \
--workspace "$WORKSPACE" --role root \
--profile root-modern --backend builtin-x509 \
--idempotency-key mesh-listener-root-v1 --yes --json
)"
ROOT_AUTHORITY_ID="$(printf '%s\n' "$ROOT_JSON" | jq -r '.authority.id')"
ROOT_GENERATION_ID="$(printf '%s\n' "$ROOT_JSON" | jq -r '.generation.id')"
# 3. Short signing lease and listener leaf.
hovel pki authority unlock "$ROOT_AUTHORITY_ID" \
--workspace "$WORKSPACE" --duration 10m --approve --json
SERVER_JSON="$(
hovel pki certificate issue "listener.lab.example" \
--workspace "$WORKSPACE" --issuer "$ROOT_AUTHORITY_ID" \
--profile mtls-server --backend builtin-x509 \
--idempotency-key issue-listener-server-v1 --yes --json
)"
SERVER_GENERATION_ID="$(printf '%s\n' "$SERVER_JSON" | jq -r '.id')"
# Optional peer certificate for a controlled lab client.
PEER_JSON="$(
hovel pki certificate issue "mesh-peer-01" \
--workspace "$WORKSPACE" --issuer "$ROOT_AUTHORITY_ID" \
--profile mtls-client --backend builtin-x509 \
--idempotency-key issue-listener-peer-v1 --yes --json
)"
PEER_GENERATION_ID="$(printf '%s\n' "$PEER_JSON" | jq -r '.id')"
# 4. Active trust for client certificates issued by this root.
TRUST_JSON="$(
hovel pki trust create "${LISTENER_ID} client roots" \
--workspace "$WORKSPACE" --id "$TRUST_SET_ID" \
--idempotency-key create-listener-trust-v1 --yes --json
)"
TRUST_REVISION="$(printf '%s\n' "$TRUST_JSON" | jq -r '.revision')"
TRUST_JSON="$(
hovel pki trust stage "$TRUST_SET_ID" \
--workspace "$WORKSPACE" --revision "$TRUST_REVISION" \
--generation-id "$TRUST_GENERATION_ID" \
--anchors "$ROOT_GENERATION_ID" \
--idempotency-key stage-listener-trust-v1 --yes --json
)"
TRUST_REVISION="$(printf '%s\n' "$TRUST_JSON" | jq -r '.trustSet.revision')"
TRUST_JSON="$(
hovel pki trust activate "$TRUST_SET_ID" \
--workspace "$WORKSPACE" --revision "$TRUST_REVISION" \
--idempotency-key activate-listener-trust-v1 --yes --json
)"
# 5. Bind the exact provider/listener consumer, then promote with CAS revisions.
ASSIGNMENT_JSON="$(
hovel pki assignment bind "$CONSUMER_ID" \
--workspace "$WORKSPACE" --id "$ASSIGNMENT_ID" \
--consumer-type mesh-listener --purpose mtls-server \
--profile mtls-server --trust-set "$TRUST_SET_ID" \
--idempotency-key bind-listener-server-v1 --yes --json
)"
ASSIGNMENT_REVISION="$(printf '%s\n' "$ASSIGNMENT_JSON" | jq -r '.revision')"
ASSIGNMENT_JSON="$(
hovel pki assignment stage "$ASSIGNMENT_ID" "$SERVER_GENERATION_ID" \
--workspace "$WORKSPACE" --revision "$ASSIGNMENT_REVISION" \
--idempotency-key stage-listener-server-v1 --yes --json
)"
ASSIGNMENT_REVISION="$(printf '%s\n' "$ASSIGNMENT_JSON" | jq -r '.assignment.revision')"
ASSIGNMENT_JSON="$(
hovel pki assignment activate "$ASSIGNMENT_ID" \
--workspace "$WORKSPACE" --revision "$ASSIGNMENT_REVISION" \
--idempotency-key activate-listener-server-v1 --yes --json
)"
# 6. Verify all immutable pins, then close the signing window.
hovel pki authority inspect "$ROOT_AUTHORITY_ID" --workspace "$WORKSPACE" --json
hovel pki certificate inspect "$SERVER_GENERATION_ID" --workspace "$WORKSPACE" --json
hovel pki certificate inspect "$PEER_GENERATION_ID" --workspace "$WORKSPACE" --json
hovel pki trust inspect "$TRUST_SET_ID" --workspace "$WORKSPACE" --json
hovel pki assignment inspect "$ASSIGNMENT_ID" --workspace "$WORKSPACE" --json
hovel pki authority lock "$ROOT_AUTHORITY_ID" --workspace "$WORKSPACE" --json
Success here means the PKI assignment is active and ready for a compatible
provider descriptor. It does not start a listener. When an
authorized daemon Mesh request supplies a credential selection for this
assignment, Hovel verifies that the assignment matches
mesh-listener and the exact
<provider-name>/<listener-id> consumer, resolves a supported
short-lived projection, and invokes credential delivery before the
consuming provider call in the same process. See Mesh,
Mesh Development, and
Providers and Artifacts.
Inspection checklist
hovel pki status --workspace "$WORKSPACE" --json
hovel pki authority list --workspace "$WORKSPACE" --json
hovel pki authority inspect "$ROOT_AUTHORITY_ID" --workspace "$WORKSPACE" --json
hovel pki certificate list --workspace "$WORKSPACE" --json
hovel pki certificate inspect "$SERVER_GENERATION_ID" --workspace "$WORKSPACE" --json
hovel pki trust list --workspace "$WORKSPACE" --json
hovel pki trust inspect "$TRUST_SET_ID" --workspace "$WORKSPACE" --json
hovel pki assignment list --workspace "$WORKSPACE" --json
hovel pki assignment inspect "$ASSIGNMENT_ID" --workspace "$WORKSPACE" --json
| Object | Verify before activation |
|---|---|
| Authority | Expected ID, role, active generation, profile, backend commitment, and validity. |
| Certificate | Expected profile and purpose, issuer IDs, DNS name where applicable, state active, key ID, and expiry. |
| Trust set | Expected revision, staged or active generation ID, anchors, intermediates, and fresh CRLs. |
| Assignment | Exact consumer type/ID, profile/purpose, revision, certificate pin, trust-generation pin, and state. |
Renew versus rotate
| Operation | Key behavior | Use when |
|---|---|---|
certificate renew |
Creates a new immutable generation with the same key and backend; JSON reports keyReused: true. |
The key remains trusted and only validity/serial rollover is needed. |
certificate rotate |
Creates a new immutable generation with a distinct key; JSON reports keyReused: false. |
Routine key hygiene, backend migration, or suspected key exposure. |
# Renew with a stable new generation ID.
RENEW_JSON="$(
hovel pki certificate renew "$SERVER_GENERATION_ID" \
--workspace "$WORKSPACE" \
--generation-id certgen-listener-renewed-v2 \
--idempotency-key renew-listener-v2 \
--yes --json
)"
RENEWED_GENERATION_ID="$(printf '%s\n' "$RENEW_JSON" | jq -r '.generation.id')"
# Rotate with stable generation and key IDs.
ROTATE_JSON="$(
hovel pki certificate rotate "$SERVER_GENERATION_ID" \
--workspace "$WORKSPACE" \
--generation-id certgen-listener-rotated-v2 \
--key-id key-listener-rotated-v2 \
--backend builtin-x509 \
--idempotency-key rotate-listener-v2 \
--yes --json
)"
ROTATED_GENERATION_ID="$(printf '%s\n' "$ROTATE_JSON" | jq -r '.generation.id')"
Both operations require an active source leaf and an active signing lease. They preserve the certificate lineage, profile, purpose, subject policy, compatibility target, and issuer. They do not update an assignment. Stage the selected new generation on the assignment using its current revision, inspect the staged certificate and trust pins, then activate with the new revision. Authority generations reject both commands and require the authority-rollover workflow.
Revoke a generation and publish a full CRL
REVOKE_JSON="$(
hovel pki certificate revoke "$SERVER_GENERATION_ID" \
--workspace "$WORKSPACE" \
--reason superseded \
--idempotency-key revoke-listener-v1 \
--yes --json
)"
REVOCATION_ID="$(printf '%s\n' "$REVOKE_JSON" | jq -r '.revocation.id')"
hovel pki revocation inspect "$REVOCATION_ID" \
--workspace "$WORKSPACE" --json
hovel pki revocation generation "$SERVER_GENERATION_ID" \
--workspace "$WORKSPACE" --json
hovel pki revocation list "$ROOT_AUTHORITY_ID" \
--workspace "$WORKSPACE" --json
# CRL publication signs, so open a lease if the earlier lease is closed or expired.
hovel pki authority unlock "$ROOT_AUTHORITY_ID" \
--workspace "$WORKSPACE" --duration 5m --approve --json
CRL_JSON="$(
hovel pki crl publish "$ROOT_AUTHORITY_ID" \
--workspace "$WORKSPACE" \
--valid-for 24h \
--idempotency-key publish-root-crl-v1 \
--yes --json
)"
CRL_GENERATION_ID="$(printf '%s\n' "$CRL_JSON" | jq -r '.generation.id')"
hovel pki crl inspect "$CRL_GENERATION_ID" \
--workspace "$WORKSPACE" --json
hovel pki crl list "$ROOT_AUTHORITY_ID" \
--workspace "$WORKSPACE" --json
hovel pki authority lock "$ROOT_AUTHORITY_ID" \
--workspace "$WORKSPACE" --json
Supported reasons are unspecified,
key-compromise, ca-compromise,
affiliation-changed, superseded,
cessation-of-operation, certificate-hold,
privilege-withdrawn, and aa-compromise. Omitting
--effective-at uses the current time. An explicit value must be
RFC 3339 and cannot schedule a future revocation or predate certificate
validity.
Revocation atomically marks the generation revoked, degrades assignments actively serving it, clears staged-only references to it, and returns the complete affected-assignment set. A self-signed root cannot be revoked; it must be removed from trust through a controlled rollover. CRL publication creates an immutable, full CRL snapshot. It does not modify a trust set or an assignment automatically.
To publish the new CRL through an existing trust set, stage another trust
generation with the same required anchors/intermediates plus
--crls "$CRL_GENERATION_ID", then activate it with revision
fencing. Existing assignments retain their pinned trust generation. A
normal assignment update therefore stages a new leaf generation and pins
the trust set generation that is active at that moment; there is no
trust-only assignment command.
Safety table
| Action | Guard | Required post-check |
|---|---|---|
| Initialize custody | Interactive confirmation or --yes. | pki status --json reports initialized custody. |
| Create root | Confirmation, selected profile/backend, and stable retry key. | Inspect authority and root generation before distributing trust. |
| Use an authority key | Explicit --approve lease bounded to at most 15 minutes. | Lock after the signing window. |
| Activate trust | Immutable staged generation, material validation, confirmation, and expected revision. | Inspect active anchors, intermediates, CRLs, and revision. |
| Activate assignment | Exact consumer/profile/purpose checks, pinned trust, confirmation, and expected revision. | Inspect active certificate/trust pins and consumer identity. |
| Revoke | Typed reason, confirmation, atomic assignment impact, and durable retry result. | Inspect the revocation and every affected assignment; publish a CRL. |
| Read inventory | List/inspect responses omit private key bytes. | Do not treat public JSON as a private-key export path. |
Idempotency and revision fencing
| Command class | Retry contract |
|---|---|
| Status, list, inspect | Read-only; rerun without an idempotency key. |
pki init | Converges on existing custody and has no --idempotency-key option. |
| Authority create, issue, renew, rotate | Use one stable key for one exact request. Completed issuance replays the same generated result. |
| Trust and assignment mutations | The state, audit event, and public result commit atomically. Replay the same key, payload, and expected revision. |
| Revoke and CRL publish | Use stable keys. Revocation preserves its affected-assignment result; CRL publication preserves its immutable generation result. |
| Unlock and lock | No idempotency-key option. Unlock replaces the time-bounded lease after explicit approval; lock removes the caller's lease. |
Explicit retry keys are scoped by mutation kind and authenticated actor. Reusing a key with a changed request fails closed. Without an explicit key, each CLI call has a fresh correlation ID, so a transport retry is a new operation. Revision-fenced commands require a positive decimal revision. The server rejects stale revisions without applying the requested state transition.
Failure and retry table
| Failure | State assumption | Safe response |
|---|---|---|
| Daemon unavailable | The CLI did not receive a result; the request may or may not have reached the daemon. | Restore the same workspace daemon, run status/inspect, then replay the exact command with the same explicit key. |
| Signing locked or lease expired | No signature was authorized for the failed attempt. | Unlock with --approve, then replay the exact command and key. |
| Revision conflict | Another mutation changed the object, or an earlier response was lost. | Inspect first. If replaying the exact prior request, retain its key and revision. For a newly reviewed request, use the current revision and a new key. |
| Idempotency conflict | The key already identifies a different normalized request. | Do not force reuse. Compare the prior operation with the intended one and choose a new key only for an intentionally new request. |
| Issuance already pending | A durable issuance plan owns the generated IDs and may still be reconciling. | Do not fan out retries under new keys. Preserve the key, inspect inventory and daemon audit state, and escalate if it remains pending. |
| Prior issuance failed | The durable plan preserves the failure and will not silently become a new issuance. | Inspect inventory, correct the cause, then use a new key only after confirming that no successful generation exists. |
| Trust material rejected | The staged trust mutation did not commit. | Inspect each CA/CRL generation; correct role, state, issuer membership, or freshness, then retry the same reviewed request. |
| CRL publication fails after revocation | The revocation remains committed; CRL publication is a separate operation. | Do not revoke again. Repair the lease/backend issue and retry CRL publication with its same key. |
Recovery and rollover boundaries
| Need | Current implementation boundary | Do not substitute |
|---|---|---|
| Leaf renewal or key rotation | Implemented by pki certificate renew and pki certificate rotate. |
Do not use these commands for a CA generation. |
| Authority rollover | The domain, persistence, daemon RPC, and OpenAPI implement start, overlap acknowledgements, overlap activation, leaf rotation checks, final-trust acknowledgements, completion, and early cancellation. | There is no hovel pki authority rollover command. Do not approximate rollover with leaf rotation or manual trust edits. |
| Recovery export/restore | Passphrase-protected recovery-envelope code exists in the file master-key infrastructure. | No CLI or OpenAPI recovery route exists. Do not copy, rewrite, or merge secrets/pki-master-keys.json as an operator workflow. |
| Master-key rotation/rewrap | An internal coordinator can rotate and converge encrypted key records. | No operator CLI or daemon route exposes it. Do not rotate files independently of protected records. |
| Provider credential delivery | Daemon Mesh calls and the language SDKs implement typed, descriptor-checked runtime delivery for supported projections. | An active assignment is not proof that a chosen provider advertises the required slot, profile, projection, or material form. |
The rollover API is a revision-fenced state machine, not a single
certificate replacement. Its implemented phases are
awaiting-overlap-acknowledgements,
awaiting-leaf-rotation,
awaiting-final-acknowledgements, and completed.
Use only an authorized client generated or implemented against the
current OpenAPI contract;
do not infer request fields from this operational summary.
Related references
- Daemon RPC Contract — local transport, typed errors, and PKI method inventory.
- Daemon OpenAPI — exact request and response schemas.
- Providers and Artifacts — assignment resolution and credential delivery.
- Mesh — listener, task, stream, and bridge control-plane behavior.
- Mesh Development — provider descriptor and SDK contracts.
- Module Protocol — module-process JSON-RPC and optional credential methods.
- Operator Workflows — daemon-owned operator state and JSON conventions.
- Safety — confirmation and audit expectations.