How must an instance handle identity rotation without losing audit continuity?
**Date of Record:** 2026 October 26 **Scope:** Applies when an agentic session's cryptographic identity (keypair, signing certificate, or SPIFFE ID) is rotated (scheduled rotation, compromise recovery, or key loss) and downstream consumers must maintain a continuous audit trail despite the identity change. Does not apply to strictly session-scoped identities that are never reused, or systems with no audit requirements.
Core Distinction
The separation between **identity replacement** (the old key is revoked, and a new key is issued, with no link between them) and **identity rotation with continuity** (the new identity is cryptographically bound to the old identity via a transition certificate, proving that the same principal is behind both). Failing to distinguish these creates the **Audit Continuity Break**: an auditor cannot link actions signed by the old key to actions signed by the new key, breaking the chain of accountability.
What Was Observed
In long-lived agent and infrastructure systems, failing to preserve audit continuity across identity rotation produces three primary failure modes:
- **Signature Discontinuity:** An agent signs records with key A for one year, then rotates to key B. Auditors see two separate "identities" with no proof they are the same agent, breaking the audit trail.
- **Repudiation Window:** An agent performs a questionable action with key A, then rotates to key B. The agent denies the action, and the auditor cannot prove the same principal is behind both keys.
- **Trust Chain Collapse:** Downstream services trust key A but not key B. Rotation breaks all integrations until each service is manually re-configured, creating a compliance gap.
Recorded Elements
- **`rotation_transition_certificate`:** A certificate signed by key A (before rotation) that binds key B to the same principal, establishing cryptographic continuity.
- **`revocation_receipt`:** The cryptographic proof that key A was revoked at a specific epoch, preventing any actions after that epoch from being attributed to the old identity.
- **`rotation_epoch_boundary`:** The exact epoch at which the identity transition occurred, allowing auditors to attribute actions before and after correctly.
- **`continuity_attestation_signature`:** An attestation from a trusted identity authority (CA, SPIFFE controller, or organization root) confirming the rotation was authorized and the transition certificate is valid.
Condition of Validity
* **Applies when:** The identity is long-lived across multiple sessions or rotations, downstream consumers must verify a continuous audit trail, and the identity authority can issue transition certificates. * **Does not apply when:** Identities are strictly per-session and never reused, or the audit requirements are satisfied by a centralized log aggregator that does not depend on per-action identity attribution.
Decision Boundary
- **Has the identity been rotated?** If no, standard identity verification applies. If yes, proceed.
- **Is a `rotation_transition_certificate` present, signed by the old key?** If no, the old and new identities are unlinked. The audit trail is `BROKEN` and downstream consumers must be notified.
- **Does the `continuity_attestation_signature` verify against the trusted identity authority?** If no, the rotation is unauthorized. Reject the new identity and treat the old identity as still authoritative.
- **Conversely, when can an auditor accept a rotated identity without a transition certificate?** Only when the rotation is anchored by an out-of-band attestation (e.g., a hardware-signed rotation receipt from a TPM), or the identity is explicitly scoped to short-lived sessions with no continuity requirement.
Operational Effect
- **Audit Continuity:** Preserves the chain of accountability across identity rotations by linking old and new keys cryptographically.
- **Repudiation Prevention:** Closes the repudiation window by proving the same principal is behind both identities.
- **Trust Propagation:** Allows downstream services to trust new identities automatically by verifying the transition certificate against the old key's chain.
from dataclasses import dataclass
from enum import Enum, auto
import hashlib
import json
class RotationContinuityVerdict(Enum):
CONTINUITY_VERIFIED = auto()
ROTATION_BROKEN = auto()
UNAUTHORIZED_ROTATION = auto()
NO_ROTATION = auto()
@dataclass(frozen=True)
class IdentityRotation:
old_identity_hash: str
new_identity_hash: str
rotation_epoch: float
transition_certificate: str | None
old_key_revocation_receipt: str | None
continuity_attestation_signature: str | None
@dataclass(frozen=True)
class ContinuityPolicy:
trusted_identity_authorities: frozenset[str]
require_revocation_receipt: bool
@dataclass(frozen=True)
class RotationManifest:
verdict: RotationContinuityVerdict
directive: str
manifest_digest: str
class IdentityContinuityGuard:
@staticmethod
def _hash(data: str) -> str:
return hashlib.sha256(data.encode("utf-8")).hexdigest()
@classmethod
def evaluate_rotation(
cls, rotation: IdentityRotation, policy: ContinuityPolicy
) -> RotationManifest:
# Gate 1: Rotation detected
if not rotation.transition_certificate:
return RotationManifest(
verdict=RotationContinuityVerdict.ROTATION_BROKEN,
directive="HALT: No transition certificate. Audit trail is broken.",
manifest_digest=cls._hash(f"BROKEN:{rotation.new_identity_hash}")
)
# Gate 2: Attestation trust
if not rotation.continuity_attestation_signature:
return RotationManifest(
verdict=RotationContinuityVerdict.UNAUTHORIZED_ROTATION,
directive="HALT: No continuity attestation. Rotation may be unauthorized.",
manifest_digest=cls._hash(f"NO_ATTEST:{rotation.new_identity_hash}")
)
if rotation.continuity_attestation_signature not in policy.trusted_identity_authorities:
return RotationManifest(
verdict=RotationContinuityVerdict.UNAUTHORIZED_ROTATION,
directive=f"HALT: Attestation {rotation.continuity_attestation_signature} not trusted.",
manifest_digest=cls._hash(f"UNTRUSTED_ATTEST:{rotation.new_identity_hash}")
)
# Gate 3: Revocation receipt
if policy.require_revocation_receipt and not rotation.old_key_revocation_receipt:
return RotationManifest(
verdict=RotationContinuityVerdict.ROTATION_BROKEN,
directive="HALT: Old key revocation receipt missing. Audit boundary undefined.",
manifest_digest=cls._hash(f"NO_REVOCATION:{rotation.old_identity_hash}")
)
# Gate 4: Verified
digest = cls._hash(json.dumps({
"old": rotation.old_identity_hash,
"new": rotation.new_identity_hash,
"epoch": str(rotation.rotation_epoch),
"transition": rotation.transition_certificate,
"attestation": rotation.continuity_attestation_signature
}, sort_keys=True))
return RotationManifest(
verdict=RotationContinuityVerdict.CONTINUITY_VERIFIED,
directive="PROCEED: Identity rotation verified with full continuity.",
manifest_digest=digest
)