HOVEL // chapter 13
Chapter 13 Part 03 / Operator Experience

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.

Keep the boundary clear. The CLI implements initialization, inventory, issuance, leaf lifecycle, revocation, full CRL publication, trust sets, assignments, and signing leases. Authority rollover exists in the daemon API but has no 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.
Normal workspace PKI flow
  1. preflight
  2. initialize custody
  3. create root
  4. unlock lease
  5. issue leaf
  6. activate trust
  7. activate assignment
  8. inspect
  9. lock lease

Operator contract

SurfaceImplemented behaviorOperator 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
ProfilePurposePeer trust on an assignment
root-modernSelf-signed root authorityNot an assignment profile
tls-serverServer authenticationNot required
tls-clientClient authenticationRequired
mtls-serverMutual-TLS server authenticationRequired
mtls-clientMutual-TLS client authenticationRequired
dual-role-mtlsServer and client authenticationRequired

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
Omit --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
ObjectVerify before activation
AuthorityExpected ID, role, active generation, profile, backend commitment, and validity.
CertificateExpected profile and purpose, issuer IDs, DNS name where applicable, state active, key ID, and expiry.
Trust setExpected revision, staged or active generation ID, anchors, intermediates, and fresh CRLs.
AssignmentExact consumer type/ID, profile/purpose, revision, certificate pin, trust-generation pin, and state.

Renew versus rotate

OperationKey behaviorUse 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

ActionGuardRequired post-check
Initialize custodyInteractive confirmation or --yes.pki status --json reports initialized custody.
Create rootConfirmation, selected profile/backend, and stable retry key.Inspect authority and root generation before distributing trust.
Use an authority keyExplicit --approve lease bounded to at most 15 minutes.Lock after the signing window.
Activate trustImmutable staged generation, material validation, confirmation, and expected revision.Inspect active anchors, intermediates, CRLs, and revision.
Activate assignmentExact consumer/profile/purpose checks, pinned trust, confirmation, and expected revision.Inspect active certificate/trust pins and consumer identity.
RevokeTyped reason, confirmation, atomic assignment impact, and durable retry result.Inspect the revocation and every affected assignment; publish a CRL.
Read inventoryList/inspect responses omit private key bytes.Do not treat public JSON as a private-key export path.

Idempotency and revision fencing

Command classRetry contract
Status, list, inspectRead-only; rerun without an idempotency key.
pki initConverges on existing custody and has no --idempotency-key option.
Authority create, issue, renew, rotateUse one stable key for one exact request. Completed issuance replays the same generated result.
Trust and assignment mutationsThe state, audit event, and public result commit atomically. Replay the same key, payload, and expected revision.
Revoke and CRL publishUse stable keys. Revocation preserves its affected-assignment result; CRL publication preserves its immutable generation result.
Unlock and lockNo 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

FailureState assumptionSafe response
Daemon unavailableThe 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 expiredNo signature was authorized for the failed attempt.Unlock with --approve, then replay the exact command and key.
Revision conflictAnother 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 conflictThe 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 pendingA 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 failedThe 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 rejectedThe 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 revocationThe 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

NeedCurrent implementation boundaryDo 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