AAC Docs

Operate and troubleshoot the sidecar

AAC Sidecar v0.4.4MarkdownDocs 42797a46037e

Start the AAC journey · All references

This reference is for a deployment or integration you have chosen to configure. For the first authenticated reservation, use the packaged Compose journey above.

Use the actual paths printed by your setup and run. The executable offline audit recipes below refer to the optional protocol fixture and its AAC_DEMO_DIR; the reservation demo instead prints its retained agent state paths and saved mint response. Do not guess a tenant's local file path.

Diagnostics and support

Work in this order: exact installed version → configuration/file permissions → /healthz and /readyz → trust publication and certificate validity → workload projection → pairing → workflow outcome → local and central evidence. A 401 at an agent callback means pairing failed before business execution. A trust or recipient rejection should leave the agent untouched. A healthy process with missing trust can remain unable to accept an authenticated workflow.

Contact support@cascadeauth.com with the version, image/bundle digest, platform, timestamp/time zone, request/root identifier if appropriate, stable error code and sanitized reproduction. Beta support is best-effort without a 24/7 SLA. For a suspected compromise, stop affected admission, preserve local evidence and contact the same support address; your operator owns credential revocation and recovery. License questions go to legal@cascadeauth.com.

Optional central trace forwarding

The sidecar always follows its configured local telemetry sink. To also send eligible chain metadata to AAC, add this block using the supplied data-plane URL and a private tenant API-key file:

sidecar:
  telemetry:
    sink: "/var/lib/aac/telemetry.jsonl"
    central_forward:
      control_plane_url: "https://api.stage.cascadeauth.dev"
      api_key_file: "/etc/aac/keys/tenant-api-key"

The parent directory must exist. The sidecar exchanges that API key for a short-lived telemetry-ingest session and sends only the allowed central metadata. Business payloads, task references, human claims, raw AAC/DPoP credentials and terminal attestations remain local. A2A protocol diagnostics without chain identifiers also remain local. Local sink: "none" may be used with central forwarding when local retention is intentionally disabled.

Forwarding uses a bounded background queue and never waits on the authorization path. Outages, queue saturation or shutdown can lose central events; retain local audit data according to your needs. Coarse local warnings identify exchange/forward failures without printing credentials or response bodies. The API-key file is reread on each token exchange, so an atomic replacement takes effect without a restart; already-issued sessions retain their own expiry.

After a real workflow, use aac chain --help and the returned root token identifier to inspect its central trace. Allow for asynchronous delivery; successful local events alone do not prove central delivery.

Audit your workflows

Configure a tenant-local JSONL sink and retain it alongside your application's business audit. Each line is one JSON object; fields not applicable to an event are omitted. Start with root_token_id from the client, then follow token IDs, hop indices, workload identities, decisions and failures across your sidecars. Wall-clock arrival order across hosts is not a causal guarantee.

Event Interpretation
mint Root/initial authority creation or its failure
receive Authority admission and local delivery, or a verification/callback failure
dispatch Outbound delegation/result, including downstream failure
respond Local terminal response; a successful settlement includes the compact JWS
composite_mint A supported composite authority was created from multiple arrivals
a2a_ingress Protocol/body/admission diagnostics for a unary A2A request
a2a_egress Paired-agent A2A dispatch outcome and retained-state pressure

Native events use result: success or failure; A2A diagnostics may use accepted, rejected or failed. Event availability depends on how far the request progressed. A request rejected before authority can be decoded may have no root/token ID. A bad pairing signature at the application is an application HTTP 401, not proof of a chain-verification event at the sidecar.

Local event fields

Fields Meaning
timestamp_unix_seconds, event_type, result Event time as Unix seconds (possibly fractional), category and outcome
root_token_id, token_id, parent_token_id, hop_index Chain correlation and parent/hop relationships
creator_svid_hash, audience_hash, caveat_audience Creator/audience binding and the readable audience where available
tenant_id, actor_spiffe_id, presenter_spiffe_id Local tenant/actor and verified presenting workload
caveat_predicates, destination, task_ref, originator_issuer Local restrictions, destination profile and task/originator context; may contain business-sensitive values
parent_token_ids_list, parent_root_token_ids, parent_tenant_ids, inbound_token_id Multi-parent/composite and inbound correlation
http_status, response_size_bytes, latency_ms Observed response and operation measurements when emitted
terminal_attestation, terminal_attestation_verification Compact terminal JWS and the downstream verifier's recorded result, where available
failure_code, failure_detail, agent_decision_action Stable failure category, diagnostic text (bounded), and agent decision
agent_id, agent_descriptor_hash Optional agent/descriptor correlation
method, protocol_version, result_kind, error_category Optional A2A protocol diagnostics
request_body_bytes, configured_body_limit_bytes Request size and configured A2A body bound
egress_entries, egress_reserved_cached_bytes, egress_cached_response_bytes Current local retained dispatch state and byte use
egress_expired_reclaimed_total, egress_saturation_rejections_total, egress_oversize_rejections_total Cumulative reclamation/capacity/response-bound counters
egress_conflicts_total, egress_in_progress_hits_total, egress_cached_hits_total Cumulative changed-body conflicts, in-progress hits and cached-result reuse

Not every supported field is populated by every current path. Treat optional absence as unknown, not a successful verification or a zero measurement. Ordinary access/application logs are separate from this JSONL schema.

These abbreviated, synthetic examples show the shape, not complete captured events. Ellipses indicate omitted values and must not be fed into a verifier:

{"timestamp_unix_seconds":1700000000,"event_type":"mint","result":"success","root_token_id":"...","agent_decision_action":"forward"}
{"timestamp_unix_seconds":1700000001,"event_type":"receive","result":"failure","failure_code":"ERR_DPOP_REPLAY"}
{"timestamp_unix_seconds":1700000002,"event_type":"receive","result":"failure","failure_code":"ERR_RECIPIENT_NOT_AUTHORIZED"}
{"timestamp_unix_seconds":1700000003,"event_type":"respond","result":"success","root_token_id":"...","agent_decision_action":"settle","terminal_attestation":"..."}

Investigate three common failures

Symptom What to inspect Next step
Replayed proof Receiver HTTP 403 with ERR_DPOP_REPLAY; local receive failure, possibly without a root ID Do not resend a captured AAC/DPoP request. Use the originator/paired-agent API to produce a fresh proof; for a business retry preserve the original A2A dispatch ID and body. Never disable replay checks to retry.
Wrong recipient Receiver ERR_RECIPIENT_NOT_AUTHORIZED; sender may record ERR_DOWNSTREAM_REJECTED Compare destinations.*.audience_pattern to the receiver's exact agent.spiffe_id. A wildcard holder audience does not turn a different final recipient into the intended workload.
Bad pairing Agent HTTP 401 before its business handler; library reports a missing/malformed header, timestamp skew or signature mismatch. Sidecar delivery can report ERR_AGENT_REJECTED Compare the two secret files without printing them, check clocks, method/path, covered headers and exact body bytes. Sign once after serialization and transmit those bytes unchanged.

Keep access logs or request correlation when a pre-verification failure has no root ID. Do not invent a root association from unverified caller headers. After a successful two-agent flow, correlate sender dispatch and receiver receive/respond, then verify the terminal JWS below. An HTTP 200 alone is insufficient evidence of the intended recipient or settlement.

Retain and inspect safely

The sidecar writes file sinks with mode 0600 and fails startup for an unusable path. Restrict access, encrypt backups, define a retention period, and forward to an append-only/tamper-evident store if your audit requirements need it. JSONL itself is not a signed or tamper-proof ledger. Preserve clock/source metadata and test your collector's loss detection. For file rotation, coordinate stop/reopen/restart; renaming an open file does not make the process reopen a new path. Do not rely on log retention as a substitute for retained A2A state.

Central forwarding sends a smaller metadata envelope asynchronously. Payloads, task references, human claims, raw authority/DPoP and terminal attestations stay local. Central outages can lose events without stopping authorization. Use:

aac chain show --help
aac chain show --profile "$AAC_PROFILE" --token-id '<root_token_id>' --output json

Only participating tenants can inspect a trace. A missing central trace can mean delayed/lost forwarding, an unknown ID, or no access; investigate local evidence and coarse forwarding warnings before changing credentials.

Verify terminal evidence offline

Save the local terminal_attestation string from a respond event into terminal.jws. Verify it using your already trusted CA certificates for that workload's SPIFFE domain and the expected tenant, workload, root and task from your own registration/request records. Never derive those expectations or trust anchors from the unverified JWS itself.

Install python -m pip install --upgrade cryptography in an isolated verifier environment. Save the following as verify_terminal.py. It implements the supported direct-CA, single-leaf Ed25519/P-256 profile, a 64-KiB input bound and a fixed 30-second certificate clock tolerance. It checks the signature and identity/correlation bindings; it does not reconstruct the entire AAC chain or prove the business action happened. Pair it with the workflow and application audit records.

import argparse
import base64
import hashlib
import json
import re
import time
from pathlib import Path
from cryptography import x509
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric import ec, ed25519
from cryptography.hazmat.primitives.asymmetric.utils import encode_dss_signature
from cryptography.x509.oid import SignatureAlgorithmOID

ORDER = int("FFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC632551", 16)


def require(ok, message):
    if not ok:
        raise ValueError(message)


def unb64(value):
    require(bool(re.fullmatch(r"[A-Za-z0-9_-]+", value)), "Invalid base64url")
    return base64.urlsafe_b64decode(value + "=" * (-len(value) % 4))


def verify(compact, ca_pem, tenant, spiffe, root, task, at):
    require(len(compact) <= 65536, "Attestation too large")
    parts = compact.split(".")
    require(len(parts) == 3, "Expected compact JWS")
    header, claims = [json.loads(unb64(p)) for p in parts[:2]]
    require(isinstance(header, dict) and isinstance(claims, dict), "Expected JSON objects")
    require(
        header.get("typ") == "aac-terminal-attestation+jwt" and "crit" not in header,
        "Unsupported protected header",
    )
    require(header.get("alg") in ("EdDSA", "ES256"), "Unsupported algorithm")
    chain = header.get("x5c")
    require(isinstance(chain, list) and len(chain) == 1, "Expected one x5c leaf")
    leaf = x509.load_der_x509_certificate(base64.b64decode(chain[0], validate=True))
    public = leaf.public_key()
    spki = public.public_bytes(
        serialization.Encoding.DER, serialization.PublicFormat.SubjectPublicKeyInfo
    )
    kid = base64.urlsafe_b64encode(hashlib.sha256(spki).digest()).rstrip(b"=").decode()
    require(header.get("kid") == kid, "Leaf-key fingerprint mismatch")
    uris = leaf.extensions.get_extension_for_class(
        x509.SubjectAlternativeName
    ).value.get_values_for_type(x509.UniformResourceIdentifier)
    require(
        [u for u in uris if u.startswith("spiffe://")] == [spiffe], "Unexpected workload identity"
    )

    def valid(cert):
        return (
            cert.not_valid_before_utc.timestamp() - 30
            <= at
            <= cert.not_valid_after_utc.timestamp() + 30
        )

    require(valid(leaf), "Leaf outside certificate validity")
    trusted = False
    for ca in x509.load_pem_x509_certificates(ca_pem):
        if not valid(ca):
            continue
        key = ca.public_key()
        try:
            if (
                isinstance(key, ed25519.Ed25519PublicKey)
                and leaf.signature_algorithm_oid == SignatureAlgorithmOID.ED25519
            ):
                key.verify(leaf.signature, leaf.tbs_certificate_bytes)
            elif (
                isinstance(key, ec.EllipticCurvePublicKey)
                and isinstance(key.curve, ec.SECP256R1)
                and leaf.signature_algorithm_oid == SignatureAlgorithmOID.ECDSA_WITH_SHA256
            ):
                key.verify(leaf.signature, leaf.tbs_certificate_bytes, ec.ECDSA(hashes.SHA256()))
            else:
                continue
            trusted = True
            break
        except Exception:
            continue
    require(trusted, "Leaf not signed by a currently valid trusted CA")
    signature = unb64(parts[2])
    data = ".".join(parts[:2]).encode("ascii")
    require(len(signature) == 64, "Expected 64-byte signature")
    if header["alg"] == "EdDSA":
        require(isinstance(public, ed25519.Ed25519PublicKey), "Algorithm/key mismatch")
        public.verify(signature, data)
    else:
        require(
            isinstance(public, ec.EllipticCurvePublicKey)
            and isinstance(public.curve, ec.SECP256R1),
            "Algorithm/key mismatch",
        )
        r, s = int.from_bytes(signature[:32], "big"), int.from_bytes(signature[32:], "big")
        require(0 < r < ORDER and 0 < s <= ORDER // 2, "Invalid or noncanonical ES256 signature")
        public.verify(encode_dss_signature(r, s), data, ec.ECDSA(hashes.SHA256()))
    expected = {
        "iss": tenant,
        "terminal_agent_svid": spiffe,
        "root_token_id": root,
        "task_ref": task,
    }
    require(isinstance(root, str) and bool(root), "Expected root must be nonempty")
    for name, value in expected.items():
        require(claims.get(name) == value, "Claim mismatch: " + name)
    require(
        bool(re.fullmatch(r"tnt-[0-9a-f]{8}(?:-[0-9a-f]{4}){3}-[0-9a-f]{12}", tenant)),
        "Invalid expected tenant",
    )
    require(
        type(claims.get("iat")) is int and -(2**63) <= claims["iat"] < 2**63,
        "Invalid issued-at time",
    )
    require(
        isinstance(claims.get("settlement_id"), str) and bool(claims["settlement_id"]),
        "Missing settlement ID",
    )
    require(
        "action_summary" not in claims or isinstance(claims["action_summary"], str),
        "Invalid action summary",
    )
    return {"verified": True, **expected, "settlement_id": claims["settlement_id"]}


def read_compact(path):
    with Path(path).open("rb") as stream:
        raw = stream.read(65537)
    require(len(raw) <= 65536, "Attestation file exceeds 64 KiB")
    return raw.decode("ascii").strip()


if __name__ == "__main__":
    p = argparse.ArgumentParser()
    for flag in ("jws", "ca", "tenant", "spiffe", "root", "task"):
        p.add_argument("--" + flag, required=True)
    p.add_argument("--at", type=int, default=None)
    a = p.parse_args()
    result = verify(
        read_compact(a.jws),
        Path(a.ca).read_bytes(),
        a.tenant,
        a.spiffe,
        a.root,
        a.task,
        int(time.time()) if a.at is None else a.at,
    )
    print(json.dumps(result))
python verify_terminal.py --jws terminal.jws --ca "$AAC_DEMO_DIR/pki/dev-ca.crt" \
  --tenant "$AAC_TENANT_ID" --spiffe "spiffe://${AAC_TRUST_DOMAIN}/demo/agent" \
  --root '<root_token_id returned by your client>' --task '<task_ref from your request>'

For the normal current-time check, omit --at. For historical analysis, use an independently trusted observation time and archived trusted CA material; never use an unverified iat to choose the certificate time. Historical verification does not establish that a key is trusted or a credential active now. The attestation has no expiry claim; iat is signed metadata, not a replay defense. Check it against your independently recorded workflow timeline. For a result from the two-agent example's peer, use its registered spiffe://<trust-domain>/demo/peer identity instead of /demo/agent.

Sidecar error reference

Application errors use this envelope; detail may be null and diagnostic wording may change. Use code for automation and request_id for support. The HTTP status and code together identify the response; the same code can appear at different operation boundaries.

{"error":{"code":"ERR_RECIPIENT_NOT_AUTHORIZED","message":"Recipient verification failed","detail":null,"request_id":"req-aac-sdk-example"}}
Code Meaning and next action
ERR_A2A_AUTHORITY_REGISTRATION_FAILED Verified inbound continuation could not be retained; retry only after the local authority/state problem is resolved.
ERR_A2A_DISPATCH_FAILED Outbound A2A operation failed; inspect local logs and endpoint/TLS configuration before retrying.
ERR_A2A_DISPATCH_TIMEOUT A2A operation exceeded its deadline; reconcile the outcome and preserve the dispatch ID/body.
ERR_A2A_DISPATCH_UNCERTAIN Delivery outcome is unknown; do not create a new dispatch ID to repeat a possible business action.
ERR_A2A_OVERLOADED A2A admission capacity is full; apply backoff and check configured concurrency/headroom.
ERR_A2A_REMOTE_REJECTED The peer rejected A2A delivery; inspect its authorized error response and recipient/profile expectations.
ERR_AGENT_REJECTED The local agent returned a rejecting HTTP response; check pairing first, then its application policy/logs.
ERR_AGENT_TIMEOUT Local agent response exceeded its budget; inspect the handler and deadlines.
ERR_AGENT_UNREACHABLE Local agent could not be reached; check process, loopback namespace, port and TLS settings.
ERR_CHAIN_INVALID Chain structure or restrictions are invalid; inspect the submitted chain and attenuation rules.
ERR_CLASS_OF_ACTION_NOT_FOUND Mint/originate named an unknown class; use a configured classes_of_action name.
ERR_CONFIG_ERROR The requested operation lacks valid configuration; inspect signer/material/duration settings and startup logs.
ERR_CONTINUATION_AUTHORITY_UNAVAILABLE Inbound authority is missing or expired for this pair/task/presenter; obtain a new authorized arrival rather than fabricating continuation.
ERR_DESTINATION_NOT_FOUND A decision/envelope named no configured destination; correct its profile name.
ERR_DOWNSTREAM_REJECTED Native downstream delivery was rejected; inspect the peer's response and expected identity/restrictions.
ERR_DOWNSTREAM_TIMEOUT Downstream transport failed or timed out; check TLS, reachability and budgets, and reconcile before retrying business work.
ERR_DPOP_ATH_MISMATCH Proof does not bind the submitted authority credential; transmit the matching chain and proof together.
ERR_DPOP_CHAIN Proof certificate/trust validation failed; check CA publication, algorithms and certificate validity.
ERR_DPOP_HTM Proof HTTP method differs from the request; sign for the actual method.
ERR_DPOP_HTU Proof URL differs from the request; check final scheme/host/port/path and proxy routing.
ERR_DPOP_REPLAY Proof has already been claimed; obtain fresh authorization/proof through supported APIs, preserving business idempotency.
ERR_DPOP_SIG Proof signature or protected-header profile is invalid; check signer, algorithm and exact signed bytes.
ERR_DPOP_TIME Proof time is outside the accepted window; synchronize clocks and generate a fresh proof.
ERR_DPOP_TTL_TOO_LONG Proof lifetime exceeds the supported bound; use the sidecar's supported issuance path.
ERR_DUPLICATE_CONVERGENCE The composite task is already converging; do not trigger a second concurrent convergence.
ERR_EGRESS_DISPATCH_IN_PROGRESS The same A2A operation is still running; back off and retry the same ID/body.
ERR_EGRESS_IDEMPOTENCY_CONFLICT An existing dispatch ID was reused with different authenticated bytes; preserve the original request or start a genuinely new operation.
ERR_EGRESS_IDEMPOTENCY_SATURATED Retained dispatch capacity is exhausted; check retention and qualified entry/byte budgets.
ERR_EGRESS_RESPONSE_TOO_LARGE Response exceeded the retained-result bound; reconcile the operation and review the configured response limit.
ERR_HMAC_CHAIN_MISMATCH Chain authentication failed; reject the chain and check its serialized/delegated form.
ERR_INTERNAL_ERROR An internal operation failed; retain the request ID and sanitized evidence for support.
ERR_INTERNAL_VERIFIER_ERROR Verification failed internally; stop relying on that attempt and report the request ID.
ERR_INVALID_AGENT_DECISION The agent returned an invalid/unsupported decision or used it at the wrong workflow stage; fix the handler response.
ERR_INVALID_MINT_INPUT Native authority input is invalid or conflicts: check predicate names, canonical integers, byte limits and task_ref consistency; remove request valid_until and use class valid_for.
ERR_PAIRING_AUTH_FAILED Native chain-start pairing failed (401): sign the exact POST alias/raw body with this pair's secret, use fresh canonical headers once each, and check clock skew.
ERR_INVALID_AGENT_RESPONSE Agent/peer response does not match the supported response schema; validate the integration contract.
ERR_INVALID_OIDC_ISSUER Originator issuer is invalid for this request; use the verified application's correct issuer.
ERR_INVALID_REQUEST Request shape, headers, pairing or protocol metadata are invalid; inspect status/detail and the endpoint's request schema.
ERR_INVALID_REQUEST_ENCODING Request text/encoding is invalid; send the supported UTF-8 representation.
ERR_MISSING_AAC_MACAROON No required AAC authority credential was supplied; use the supported sending sidecar.
ERR_MISSING_DPOP_PROOF No required presenting proof was supplied; use the supported sending sidecar.
ERR_MISSING_REQUIRED_HEADER A required operation header is absent; supply the documented value.
ERR_PRESENTER_NOT_PREVIOUS_HOLDER Presenting workload is not the previous authorized holder; correct the sender/chain binding.
ERR_RECIPIENT_NOT_AUTHORIZED Receiving workload is not the exact final recipient; correct the destination audience.
ERR_REPLAY_AUTHORITY_INCOMPATIBLE Replay service/profile is incompatible; restore the supported authenticated retained-write-safe configuration.
ERR_REPLAY_AUTHORITY_QUARANTINED Replay authority is in its safety quarantine; keep admission closed until it is ready.
ERR_REPLAY_AUTHORITY_SATURATED Replay storage has reached its safe bound; Basic admits new claims as old records expire. Size for all new replay claims, including requests rejected later. Never evict unexpired records to make room.
ERR_REPLAY_AUTHORITY_TIMEOUT Replay decision timed out; check service health/latency rather than bypassing it.
ERR_REPLAY_AUTHORITY_UNAVAILABLE Replay authority is unavailable; restore it before admitting requests.
ERR_REQUEST_TOO_LARGE Body/header/operation limit was exceeded; reduce the request or qualify an appropriate configured limit.
ERR_ROOT_SIGNATURE_INVALID Root signature is invalid; check root key ID, trust publication and signed content.
ERR_ROUTE_NOT_FOUND Method/path is unsupported on that listener; check endpoint and port.
ERR_STATE_STORE_UNAVAILABLE Local workflow arrival state is unavailable; repair the state service before retrying.
ERR_T0_ON_WIRE A root-only token was sent where a delegated chain is required; use the sidecar's normal dispatch path.
ERR_T1_ON_WIRE Initial holder authority was sent without a peer delegation; use the normal dispatch path.
ERR_TOKEN_EXPIRED Authority has expired; obtain a new authorized task/chain.
ERR_TOKEN_FORMAT Serialized authority cannot be decoded as the supported token format; do not alter or hand-construct it.
ERR_TOKEN_NOT_FOUND Required buffered arrival/token is absent; check task/token correlation and lifecycle.
ERR_UNKNOWN_PREDICATE A restriction name is unsupported; use the published predicate vocabulary.
ERR_UNKNOWN_TENANT_KEY Referenced tenant/root key is not in the trusted set; check tenant ID, key ID, publication and revocation.
ERR_UNSUPPORTED_MEDIA_TYPE Content type is unsupported; use the endpoint's documented JSON media type.

Authenticated A2A protocol errors can instead use JSON-RPC errors under HTTP 200: -32700 parse error, -32600 invalid request, -32601 unknown method, -32602 invalid parameters, -32004 unsupported operation and -32009 unsupported version. Inspect the JSON body even when HTTP transport succeeded. Admission/authentication/body-size errors may use non-200 HTTP responses.

CLI/control-plane errors are a separate surface. For example, ERR_API_KEY_LAST_ACTIVE belongs to API-key retirement, not sidecar receiving; its documented recovery is to issue/migrate a second active key first.

Upgrade, rollback, and uninstall

Developer-binary uninstall:

rm "${HOME}/.local/bin/aac-sidecar"
rm -rf "${HOME}/.local/lib/aac-sidecar/releases/v0.4.4"

Do not use those commands for an operator-owned production directory or state volume. Follow the tenant's change, retention, and secure-deletion procedures.

Rotate or revoke credentials deliberately

A binary upgrade does not rotate tenant credentials. Before revocation, identify all clients of the exact key and distinguish planned rotation from compromise:

aac tenant api-key list --profile "$AAC_PROFILE" --output table
aac tenant api-key issue --help
aac tenant api-key retire --help
aac tenant reissue-api-key --help
aac trust-anchor list --profile "$AAC_PROFILE" --output table
aac trust-anchor revoke --help

For planned API-key rotation, issue the second active key, migrate all clients, verify them, then retire the exact old key. The control plane refuses to retire the last ACTIVE API key (ERR_API_KEY_LAST_ACTIVE); issue and migrate a second key first. For a lost/compromised key, follow the recovery command's separate semantics. The telemetry API-key file is reread on token exchange; existing sessions keep their own expiry.

Root-key rotation uses a new key ID, publishes its public key, waits for trust visibility, moves the workload to it, verifies a real workflow, then revokes the old key. Revoking the last root-signing key is permitted for compromise or teardown; the API-key last-ACTIVE protection does not apply to root keys. Revoked roots leave the public trust set, so verifiers lose trust in chains rooted at that key when their trust view refreshes. Healthy control-plane-backed caches poll every five minutes by default; outage stale-serve behavior remains, and there is no instantaneous global revocation guarantee. Stop affected workflows and coordinate trust refresh/replacement with peers. Do not assume removing a public root erases a workload's locally held signing key.

Replace workload/terminal keys with matching valid certificates and update trust as required. Replace a compromised pairing secret in both paired processes and restart them together. For remote signers, disable/revoke the exact key version and verify failure without provider/file fallback.

An unsafe beta is removed from recommendations and replaced by a newly reviewed immutable version; its old digest is never overwritten. Pause affected traffic and preserve evidence while deciding whether rollback is safe.

Local uninstall removes software, not a tenant account. For reusable testing, stop the processes and retain tenant registration, credentials in your secret manager, and necessary private state. Remove local credential copies only after verifying custody. Permanent workload/credential retirement is a separate operator action; this guide does not promise a tenant-account deletion API or a complete tenant-offboarding procedure.