Skip to main content

Signer Lifecycle Reference

Signer lifecycle administration is a Pro capability. It manages KMS-backed signer rotation, inventory, allowance evidence, retirement gates, and the forensic history of those actions. The shared schema, signer adapters, and movement-time signer safety remain Free.

API

All mutation endpoints require the admin role. Rotation history is readable by admin and operator. The shared HTTP client signs each request; configure QTG_KEY_ID, QTG_HMAC_SECRET_FILE (or QTG_HMAC_SECRET), and QTG_API_URL before using the CLI or the expected-identity request below. Rotation accepts the replacement signer and reason. --drill returns outcome: "drilled", persists drill evidence, and does not change signer bindings; a state-changing rotation requires the impact acknowledgement. Inventory persists evidence by default, and --no-persist is the explicitly read-only form. The allowances command persists audit/snapshot evidence. Retirement requires the signer to be deprecated, balances at or below the configured token-unit dust ceiling, no nonterminal node reference, and an allowance acknowledgement only when non-zero allowances deliberately remain. There is no force flag. A retirement gate denial is HTTP 409 with outcome: "denied" in the error body; the CLI raises QtgHttpError, exits nonzero, and does not print a successful 200 response.

CLI

The qtg signer group is the HTTP-backed CLI for the lifecycle endpoints:
Use --ack-allowances only for a documented exceptional residual allowance. dust_threshold_usd is currently a raw token-unit ceiling, not a USD conversion; drain to zero in production.

Enrollment and identity gate

Add replacement KMS signers through MG_EXTRA_SIGNERS_JSON, then restart the Pro application so its runtime registration and sync_registry_to_db() materialization run. Never enroll a runtime signer with direct SQL. Verify the active replacement through GET /v3/signers, then use the admin-only PUT /v3/signers/{signer_key}/expected-identity endpoint to pin the identity. For every drill and live rotation, the server automatically resolves the replacement signer’s fresh runtime identity and compares its canonical value with the persisted expected_signer_address. This signer I/O occurs outside the lifecycle database transaction. The locked lifecycle path then revalidates the operator pin, runtime signer continuity, and canonical identity equality before impact, audit, rotation-event, metadata, or authority-remap work. observed_signer_address is diagnostic and non-authoritative. It and identity_state: "match" remain useful roster and health-refresh evidence, but neither can authorize a drill or live rotation. The fresh manual probe in the runbook remains defense in depth; the CLI does not compare identities locally. Identity denials are HTTP 409 responses containing only a stable error code, without addresses or backend exception text:

Compact request and response contracts

All bodies below require an admin HMAC key. Malformed bodies receive FastAPI 422 validation responses; the endpoint-specific errors below are the common current contracts. FastAPI serializes each HTTPException body beneath the top-level detail key, and QtgHttpError.body sees that full response JSON.

Rotate

With drill: true, success returns outcome: "drilled", an impact report, audit_event_id, and signer_rotation_event_id; it persists drill evidence without changing signer bindings. With drill: false and ack_impact: true, success returns outcome: "changed" with the same evidence identifiers and performs the atomic rotation. A non-drill request without acknowledgement returns 400 with {"detail": {"error": "ack_impact_required"}}; failed preconditions return 409 with {"detail": {"error": "validation_failed", "cause": "<DETAIL>"}}. A replacement health failure returns 409 with {"detail": {"error": "new_signer_health_check_failed"}}. Identity failures return one of the following code-only bodies:

Inventory

Success returns signer/address/check time, balances, rotation state, and audit_event_id plus signer_rotation_event_id when persistence is enabled. persist: false is the read-only form and leaves those evidence IDs null; an unknown signer returns 404 with {"detail": {"error": "signer_not_found", "signer_key": "<SIGNER_KEY>"}}.

Allowances

Success returns only non-zero allowances and always persists its audit/snapshot evidence, including audit_event_id and signer_rotation_event_id. unsupported_spender_not_checked signals a partial scan where configured spender coverage was unavailable. An unknown signer returns 404 with {"detail": {"error": "signer_not_found", "signer_key": "<SIGNER_KEY>"}}.

Retire

Success returns outcome: "retired" with audit_event_id and signer_rotation_event_id. A hard gate denial returns HTTP 409 with {"detail": {"outcome": "denied", "failed_gate": "<GATE>"}}, plus the denial reason and nonterminal nodes when relevant; an unknown signer returns 404 with {"detail": {"error": "signer_not_found", "signer_key": "<SIGNER_KEY>"}}.

Secret-safe evidence

Record signer keys, addresses, outcomes, audit IDs, and sanitized snapshots as needed. Do not record KMS credentials, HMAC secrets, MG_EXTRA_SIGNERS_JSON, raw provider responses, or secret-bearing URLs. For the complete operator procedure, evidence expectations, exception handling, and post-cutover AWS KMS deletion, use the KMS signer rotation runbook.