{ "adr_refs": [ "adr-037", "adr-038" ], "description": "Rotates ops-controller key, keeper key, or operator key. For ops-controller: generates new key, proposes delegation patch on state- signed by quorum, activates new key, verifies continuity. Keeper and operator key rotation follow the same quorum-sign pattern.", "emit_audit": true, "id": "rotate_keys", "name": "Key Rotation", "params": [ { "default_val": "", "description": "Target workspace (e.g. libre-wuji)", "name": "workspace", "required": true }, { "default_val": "", "description": "Which key to rotate: ops-controller | keeper | operator", "name": "key_type", "required": true }, { "default_val": "", "description": "Path to the new Ed25519 private key PEM", "name": "new_key_path", "required": true }, { "default_val": "", "description": "Path to the new Ed25519 public key PEM", "name": "new_pubkey_path", "required": true }, { "default_val": "", "description": "ops-vm SSH host (required for keeper rotation)", "name": "ops_vm_host", "required": false } ], "preconditions": [ "Identify which key type to rotate: ops-controller | keeper | operator", "Ensure quorum of existing signers is available to sign the delegation patch", "For ops-controller rotation: audit mirror must be paused or confirmed resilient to a 30-60s gap", "Generate the new key offline before running this playbook" ], "rollback_strategy": "manual", "steps": [ { "depends_on": [], "dry_run_arg": "--dry-run", "id": "validate_new_key", "name": "Validate new key is a valid Ed25519 keypair", "on_error": "Stop", "params": {}, "script": "steps/validate_new_key.nu" }, { "depends_on": [ "validate_new_key" ], "dry_run_arg": "--dry-run", "id": "propose_delegation_patch", "name": "Propose delegation patch on state- repo", "on_error": "Stop", "params": {}, "script": "steps/propose_delegation_patch.nu" }, { "depends_on": [ "propose_delegation_patch" ], "dry_run_arg": "--dry-run", "id": "sign_delegation_patch", "name": "Await quorum signatures on delegation patch", "on_error": "Stop", "params": {}, "script": "steps/sign_delegation_patch.nu" }, { "depends_on": [ "sign_delegation_patch" ], "dry_run_arg": "--dry-run", "id": "activate_new_key", "name": "Activate new key (deploy to target service)", "on_error": "Rollback", "params": {}, "script": "steps/activate_new_key.nu" }, { "depends_on": [ "activate_new_key" ], "dry_run_arg": "--dry-run", "id": "verify_continuity", "name": "Verify audit/signing continuity with new key", "on_error": "Continue", "params": {}, "script": "steps/verify_continuity.nu" }, { "depends_on": [ "verify_continuity" ], "dry_run_arg": "--dry-run", "id": "revoke_old_key", "name": "Revoke old key from delegation set", "on_error": "Continue", "params": {}, "script": "steps/revoke_old_key.nu" }, { "depends_on": [ "revoke_old_key" ], "dry_run_arg": "--dry-run", "id": "emit_audit", "name": "Emit key-rotation audit event", "on_error": "Continue", "params": { "event_type": "key_rotation" }, "script": "steps/emit_audit.nu" } ], "success_criteria": [ "New key is active and accepted by the target service", "Delegation patch is merged in state- Radicle repo", "Old key is removed from delegation set", "ops history shows key-rotation audit event", "No interruption in audit trail during rotation" ], "version": 1 }