Skip to content

Audit runbook

This page is the canonical audit procedure reference. The audit list is an investigation view. It is not an integrity proof. Integrity comes from the implemented chain verifier and, when configured, the external-anchor verifier. Do not edit or delete audit rows while any procedure below is running.

Audit

Procedure gate

Gate Contract
Classification Read-only
Prerequisites An operator identity permitted to view audit data, or the direct store route and its configured store access; a protected output location.
Owner Authority operator or incident responder.
Approval None for listing or verification. Approval is required before changing audit keys, retention, or export destinations.
Backup point Preserve the store backup, external audit keys, and off-cluster export before any repair or restore.
Command or check gdsgate --config <authority-config> authority audit list --class admin-action --limit 20 for rows; gdsgate --config <authority-config> authority audit verify for integrity.
Expected status and exit Listing exits 0 and prints rows newest first. Verification exits 0 only when the chain and configured anchors verify; any integrity or store error is non-zero.
Stop condition Stop destructive maintenance on a broken chain, anchor mismatch, corrupt anchor row, missing key, or unexplained gap. Preserve the original store and exports.
Recovery Move to the off-band verification workstation, compare the last trusted export and checkpoint, and follow the chain-integrity procedure.
Rollback No rollback for a read. Restore only from an approved backup after incident authority decides which history is authoritative.
Escalation Escalate chain or anchor failures to security and the Authority owner; escalate an unavailable store to the database owner.

Authority appends defined security events to its hash-chained audit log, including issuance authorization decisions, node registration, administrative operations, and session activity. High-volume per-request paths do not all append an allow row; their documented request events and metrics are separate. Each chain record links to the previous record hash. Grant paths that require a durable decision record persist it before issuing access. Listing, exporting, and verification are separate operations with separate failure boundaries.

Chain integrity vs. the database

Procedure gate

Gate Contract
Classification Incident action
Prerequisites A read-only copy or controlled access to the store, the original external anchor key when configured, and the last trusted export.
Owner Security incident responder with the Authority owner.
Approval Incident authorization before restoring, clearing, or altering any store object. Verification needs no change approval.
Backup point Take or preserve an immutable store snapshot before any further write. Preserve the exact key and export files.
Command or check gdsgate --config <authority-config> authority audit verify; compare its result with the off-cluster export and anchor checkpoint.
Expected status and exit Exit 0 means the loaded chain links and configured anchors cross-check. A broken link, truncated or rewritten chain, wrong key, malformed anchor row, or store read error returns non-zero.
Stop condition Stop immediately on any verification error. A self-consistent database chain is not sufficient evidence if the external anchor or export disagrees.
Recovery Quarantine the suspect store, verify a read-only backup with the original key, and identify the last trusted serial.
Rollback Restore the last approved store and external material only after incident approval. Never rewrite rows to make a check pass.
Escalation Security owns suspected tampering; the database owner owns storage corruption; the Authority owner owns service recovery.

The in-database chain catches a changed row or broken link. It cannot detect an attacker with full write access who deletes the table and creates a new chain from genesis. When the audit keyring of [security.integrity] is configured (or the deprecated [audit].anchor_key_path), Authority seals each committed chain head with an HMAC key held outside the database and stores the checkpoint in audit_anchor. verify-audit first replays the chain, then cross-checks every anchor against the matching serial and key. The exported JSON-lines witness is a separate comparison source. verify-audit remains the verifier.

Configuring chain anchoring

Procedure gate

Gate Contract
Classification Planned change
Prerequisites Authority configuration access, a protected directory for the two keyrings, a store backup, and an approved key-custody plan.
Owner Authority security owner.
Approval Security and change approval before enabling, rotating, or replacing an anchor key.
Backup point Back up the existing store, keyrings, key files, anchor rows, and export before changing the setting.
Command or check New deployment: gdsgate authority init --profile <profile> creates both keyrings. Deployment with key files: gdsgate --config <authority-config> authority keys legacy migrate. Then run gdsgate --config <authority-config> authority audit verify.
Expected status and exit authority init exits 0 when it creates the keyrings and never replaces an existing one. Verification exits 0 only when the configured keys agree with all anchors.
Stop condition Stop if an existing keyring or anchor key is missing, the reference changes for an already-anchored store, or verification fails. Do not clear audit_anchor as routine repair.
Recovery Restore the original keyring at its configured path or restore the paired store and keyring backup. A fresh key is valid only for a fresh history after an approved incident decision.
Rollback Restore the previous [security.integrity] configuration and keyrings together; never replace a live keyring in place.
Escalation Escalate key loss or suspected exposure to security; escalate startup refusal to the Authority owner.

Name the keyrings in [security.integrity]. The audit keyring seals the anchors; the control keyring holds the approval, revocation, delegation and inventory keys. The two are not interchangeable. Protect each keyring file and its parent directory using the deployment's filesystem controls. A keyring is held outside the database; keep an approved custody copy separate from the live Authority host. The five [audit].*_key_path parameters are deprecated; see Operations → [audit].

Continuous verification

Procedure gate

Gate Contract
Classification Planned change
Prerequisites A configured anchor key, a working alert destination, and an agreed verify_interval_secs value.
Owner Authority operator and security monitoring owner.
Approval Change approval before enabling or changing the periodic verifier.
Backup point Preserve the prior [audit] settings and current store/export backup.
Command or check Set [audit].verify_interval_secs to the approved positive interval, restart Authority, then watch the audit.tamper target and run authority audit verify once off-band.
Expected status and exit The long-running Authority stays active and the background verifier runs at the configured cadence. A startup failure exits non-zero. Any verification failure is logged at audit.tamper, and the off-band command returns non-zero.
Stop condition Stop maintenance and traffic changes on an audit.tamper error. Do not suppress the target or increase the interval to hide failures.
Recovery Preserve logs and the store, run off-band verification with the original key, and follow the chain-integrity procedure.
Rollback Restore the prior interval or set it to 0 only as an approved temporary containment measure while an off-band verifier remains in use.
Escalation Security receives every integrity failure; the Authority owner handles service configuration.

The implemented periodic verifier calls the same durable chain path as the off-band command. With an anchor key it also verifies the audit_anchor table; without one it verifies only the chain. This page does not promise a metric for chain depth or CA rotation. Use the emitted logs, the verifier exit status, and the health metrics documented in health.

Queued rows

Procedure gate

Gate Contract
Classification Incident action
Prerequisites The same audit keyring on every Authority instance, read access to the store, and the Authority logs.
Owner Security incident responder with the Authority owner.
Approval Incident authorization before deleting a held row.
Backup point Copy audit_outbox before deleting anything from it.
Command or check gdsgate --config <authority-config> doctor for state.audit.outbox_held; the metrics gdsgate_audit_outbox_held, gdsgate_audit_outbox_held_total and gdsgate_audit_outbox_missing_total; the log target audit.tamper; authority audit list for the codes AUD-0301 and AUD-0302.
Expected status and exit state.audit.outbox_held is ok, both counters stay at 0, and no AUD-0301 or AUD-0302 row appears.
Stop condition A held row, an AUD-0302 row, or a verify-audit failure that names a replayed queued row.
Recovery Compare each held row and each missing reference with the logs of the instance its origin names, then follow the chain-integrity procedure.
Rollback N/A. A held row is never sealed. Deleting a row that an AUD-0301 row names by held_outbox removes the evidence and changes nothing else. Deleting any other row with a valid seal is reported as AUD-0302.
Escalation Security owns every held or missing row.

A row a follower writes, and a row that goes with a change to the catalogue or to an agent host, is queued in audit_outbox and sealed into the chain by the write leader afterwards. While it waits it is a row in the database.

The Authority instance that queues a row seals it with an HMAC under a key derived from the active anchor key of the audit keyring. The seal covers the event, the instance name, and the row's reference <epoch>:<seq>: a random name for the process and a count within it. The reference is also written into the event's detail under outbox. With no audit keyring, rows are queued without a seal, as the chain is then unanchored.

Only Authority writes outbox. SubmitAuditEvents refuses a batch with PermissionDenied when the detail of any of its events has an outbox member, in any spelling of the name, and stores none of the batch. verify-audit and the watch below read outbox only on rows whose submitter is gdsgate-authority.

The leader checks the seal before it seals the event into the chain, and holds the row when the check fails:

held_reason The row
unsigned Carries no seal while the audit keyring is configured. A row queued by a build that did not seal is unsigned too.
unknown_key Names a key the audit keyring does not hold.
forged Carries a seal that does not match it.
replayed Repeats a reference this leader has already sealed.

A held row stays in audit_outbox with held_at_ms and held_reason and is never sealed. The leader logs it under audit.tamper, counts it in gdsgate_audit_outbox_held_total, and writes audit.outbox_held (AUD-0301) to the chain with outbox_id and reason in the detail. When the row reads as a queued row, the detail also has held_outbox, the reference the row names, and held_row_sha256, the SHA-256 of the row. gdsgate doctor fails state.audit.outbox_held while one remains. A copy of a row that an earlier leader sealed reaches the chain; verify-audit then fails and names both serials.

Every Authority instance, leader or not, watches the queued rows it knows: the rows it queued, from the commit, and every row with a valid seal it has seen in the queue. Every 5 seconds it looks for each one on the chain and in the queue. The watch of a row ends on one of two chain rows, submitted by gdsgate-authority:

Chain row Matches when
The sealed event Every field but the serial equals the event the queued row carries.
audit.outbox_held held_outbox is the row's reference and held_row_sha256 is the SHA-256 of the row.

With the audit keyring, the chain row counts only when the first anchor at its serial or after it verifies and the rows between hash to the anchored hash. Without it, the chain row counts when it hashes to its own hash and links to the rows on either side, and a row the database writes onto the chain with the same event ends the watch.

held_at_ms does not end the watch. A row with a valid seal is in the queue whether it is marked held or not. A row found neither on the chain nor in the queue is looked for once over the whole chain, then reported in gdsgate_audit_outbox_missing_total, under audit.tamper, and as an audit.outbox_missing row (AUD-0302) with missing_outbox and origin in the detail.

Removed while Reported
The instance that queued it runs At that instance's next pass, within 5 s.
That instance has stopped and another runs By the other instance, if its pass saw the row before the removal. Each instance sees every waiting row within 5 s of its commit.
Every instance has restarted since the row was queued By each instance whose first pass after its start saw the row.
No instance runs Not reported. The watch is held in memory.

Upgrade every instance of a cluster together. A row an older instance queues is held as unsigned, and an older leader stops at the first sealed row.

Off-cluster export

Procedure gate

Gate Contract
Classification Planned change
Prerequisites Protected off-cluster storage, an approved SIEM route, a store backup, and access to the external audit key only where verification requires it.
Owner Authority operator and security logging owner.
Approval Security approval before enabling an export path or changing its reader set.
Backup point Preserve the current export, anchor key custody record, and store backup before changing the destination.
Command or check Set [audit].export_path to the approved append-only JSON-lines path, restart Authority, then parse a copied line with jq -e . < <export-file>.
Expected status and exit Each committed audit batch and its closing anchor are appended as independently parseable records. jq -e . exits 0 for a valid copied line. An export failure logs a warning but does not undo an already durable chain commit.
Stop condition Stop export if the destination is writable by untrusted users, contains key material, leaks credentials, or loses ordering. Do not treat an export outage as permission to edit the chain.
Recovery Protect or replace the destination, retain the gap window, and reconcile it with the store and later export batches.
Rollback Restore the previous export path or unset it, retaining the store chain as the durable source.
Escalation Escalate data-loss or disclosure to security and the logging owner; escalate durable grant failures to Authority.

The file lane is best effort. Durable grant decisions fail closed if their audit row cannot be persisted, while ordinary listing and sign-in records remain best effort. Never place key material beside an unencrypted export.

Audit event classes

Procedure gate

Gate Contract
Classification Read-only
Prerequisites A row listing or export and the event-class vocabulary below.
Owner Incident responder or audit analyst.
Approval None for filtering. Approval is required before sharing rows outside the incident or audit boundary.
Backup point Preserve the unfiltered source before filtering.
Command or check gdsgate --config <authority-config> authority audit list --class <class> --limit <n>.
Expected status and exit Exit 0 prints matching rows; an invalid class or inaccessible route is non-zero. A class is not an outcome.
Stop condition Stop analysis if the source is incomplete or the row's version predates a field relied on by the query.
Recovery Re-run against the complete store/export and account for older row schemas.
Rollback N/A. Restore the unfiltered source if an analyst overwrote a working copy.
Escalation Escalate classification ambiguity to the audit owner.

The source event classes are authentication, authorization, ca-operation, session-activity, admin-action, capability-grant, client-assertion, and resource-integrity:

Class Examples and boundary
Authentication auth.signin records a verified identity observation. It carries no token, factor, group list, or source address.
Authorization policy.decision records the principal, resource, decision, and the Cedar action in cedar_action. A route miss is session data because no policy was consulted.
CA and capability Node registration, renewal, CA operations, and JIT approvals. A refused renewal records its reason.
Administration Policy, catalog, session, audit, recording, and CA administration. A list operation does not verify integrity.
Session activity Open/close metadata and lane records for SSH, database, Kubernetes, MCP, TCP, web, and model sessions. Recording bytes are referenced by a chain-sealed digest.
Client assertion sandbox.claim records a delegated run's own assertion and does not make it a verified fact.
Resource integrity MCP descriptor pin observations, including baseline, changed, reverted, repinned, unsealed, ignored, and unreported outcomes.

A policy push, vote and rejection write their policy.edit or policy.edit_approve row in the store transaction that changes the policy, and a break-glass restore writes policy.edit with operation = break_glass_restore in the transaction that lands the file. A policy version never becomes active without its row. The row waits in the audit queue (queued rows) until the leader seals it; while the chain cannot be written it stays there and gdsgate_audit_outbox_depth counts it.

Credential type: presented

Procedure gate

Gate Contract
Classification Read-only
Prerequisites Session or decision rows and the policy context vocabulary.
Owner Audit analyst.
Approval None for analysis; approval is required before exporting identity-bearing rows.
Backup point Preserve the original rows before adding analyst annotations.
Command or check List the relevant rows and inspect presented; compare it with the session's signed credential evidence.
Expected status and exit presented=key means possession of the certificate-bound key; presented=cookie means the browser session cookie path. Absence is not silently converted to key.
Stop condition Stop if a query treats absence as a credential type or joins rows from before the field existed without version handling.
Recovery Re-run with the row-version caveat and distinguish no presentation from a legacy row.
Rollback N/A. Remove only analyst-derived copies. Source rows stay.
Escalation Escalate unexplained credential attribution to security and the identity owner.

presented is written on session open and close, inner rows, and policy decisions, including denials. key means possession of the certificate-bound key. cookie means the clientless browser entry bound the session certificate to a cookie digest. A row before the field existed or a path with nothing presented has no presented field.

Outcome values

Procedure gate

Gate Contract
Classification Read-only
Prerequisites Audit rows with their outcome, action, and error context.
Owner Audit analyst or incident responder.
Approval None for interpretation; approval is required before using a row as a customer-facing incident statement.
Backup point Preserve the original row set.
Command or check Compare success, failure, allow, and deny with the actor and gateway status fields.
Expected status and exit success means the gateway action completed; failure means it did not without a policy refusal; allow or deny records a policy decision. HTTP status is a separate field.
Stop condition Stop if a backend's own 403 is described as a gateway policy deny or if a pre-v0.8.23 connector row is compared without its historical outcome rule.
Recovery Re-read the action and event code, then include both the gateway outcome and backend status in the report.
Rollback N/A. Correct the analyst report. Audit rows are immutable.
Escalation Escalate a policy decision mismatch to policy/security and a backend response mismatch to the connector owner.

An application, apiserver, or model provider returning 403 is a backend failure from the gateway's point of view. A gateway policy denial is deny. Older connector rows may encode some refusals as failure; preserve that historical meaning when querying across versions.

Database observations and gateway decisions

Procedure gate

Gate Contract
Classification Incident action
Prerequisites The session and audit rows, database engine metadata, the gateway classifier version, and a read-only database observation path.
Owner Database and connector incident owners.
Approval Incident authorization before changing database permissions, classifier rules, or stored rows.
Backup point Preserve the relevant audit rows, statement evidence, and database snapshot before remediation.
Command or check Query the audit rows for db.write_in_read_only_session, db.write_refused, and db.write_misclassified; correlate session_uid, engine, and kind.
Expected status and exit The query completes without changing the store. The first two outcomes are incident signals; the third is a classifier defect report.
Stop condition Stop writes or customer traffic when a write passed a read-only gateway session or the server contradicts the gateway category.
Recovery Preserve the statement and server command tag, contain the session, and reproduce against a controlled database.
Rollback Roll back the customer-side change or database transaction only under the database owner's procedure. Never rewrite the audit observation.
Escalation Escalate read-only bypasses to security and database owners; escalate classification defects to the connector owner.

The connector classifies statements before forwarding and also reads the answer stream. It records these distinct observations:

Event Outcome Meaning
db.write_in_read_only_session failure The session had no write capability but the server reported a write.
db.write_refused deny The server refused a write with SQLSTATE 25006; the gateway had forwarded it and the read-only database mode stopped it.
db.write_misclassified failure The server reported a write for a statement classified as a read in a write-capable session.

engine identifies the database engine and kind identifies the wire lane. CockroachDB is reached over the PostgreSQL wire, so those values can differ.

Sessions a connector never accounted for

Procedure gate

Gate Contract
Classification Read-only
Prerequisites Rows for the affected session ids, and the enrolled-node id of the connector whose tunnel ended.
Owner Incident responder, with the connector owner for the node.
Approval None for reading. Approval is required before presenting an observation as the account of what a session did.
Backup point Preserve the rows before correlating them with what the connector still holds on disk.
Command or check gdsgate --config <authority-config> authority audit list --code SES-0104 --limit <n>, then --code SYS-0101 for the departures that produced them.
Expected status and exit Exit 0 prints matching rows. An empty result is not evidence that no connector departed: a Proxy that stopped while holding notices reported none of them.
Stop condition Stop if a session id carries both an observation and a session.close, and read the close. An observation is not a close and never becomes one.
Recovery Run gdsgate doctor on the connector before concluding a recording is lost; a cast it still holds is delivered by its next pass.
Rollback N/A. Never rewrite an observation.
Escalation Escalate a node whose departures repeat to the connector owner, and a rising gdsgate_abandoned_session_notices_dropped_total to the Proxy owner.

A connector that is killed writes nothing on its way out, so the session it was relaying leaves an open row with nothing after it. The Proxy that carried the tunnel reports what was on it after [proxy].abandoned_session_grace_secs, and Authority writes these rows:

Code Action Written by What one row says
SES-0104 session.abandoned Authority One session whose connector went away while the chain held an open for it and no close.
SYS-0101 connector.tunnel_lost Authority One departure, at the head of the observations that report produced.
REC-0201 recording.deliver the connector's delivery pass One cast that reached the central store on a later pass than the session it belongs to.
SES-0105 session.claim_refused Authority A node named a session it did not open.

session.abandoned takes its actor, its target and the session kind from the session's own session.open row. The Proxy names the sessions and nothing about who ran them. The detail carries session_uid, kind, node for the connector that left, reason for how the tunnel ended, and observed_at for the Proxy's own clock reading, which is a claim and not a checked fact. The submitter is gdsgate-authority and the outcome is failure.

The row does not spell session.close, and that is deliberate. The reader that decides whether a recording may be delivered finds a close by the name of the action, so an observation opens nothing, closes nothing, and admits no bytes. A Proxy naming sessions it did not serve leaves false observations on the chain and moves nothing. It carries no digest either, because it is written without the bytes.

connector.tunnel_lost is one row per departure and answers what became of the node, where the observations beside it answer what became of a session. The node is the row's actor and its target both: nothing acted on it, and the Proxy that saw the departure cannot be named, since every Proxy in a cluster presents the same common name. Its detail is reason, count, and observed_at. It is written only where at least one observation was, so a tunnel whose sessions the chain already holds closes for produces neither.

A connector that comes back accounts for itself instead. It closes the recordings its last run left half-written, and for a session the chain holds no close for it asks Authority to write the one it owed: an ordinary session.close row, sealed by Authority, with reason = late_close, the outcome failure, and the actor and target of the open row. The cast is uploaded after that row is on the chain. Its arrival is recording.deliver.

recording.deliver is written by that delivery pass and by nothing else. An upload that goes through at session close is already described by the close row beside it, and a second row there would repeat its neighbour. The row carries the length, the digest, and how long the cast waited on the connector, because what follows it is the removal of the only local copy and the store's own created_at is not sealed.

session.claim_refused names one fact: a node named a session it did not open. Two calls can find it, the upload of a recording and the late close of a session, and both write this code, because the fact is the same either way. Which call was refused is the operation key in the detail, recording_upload for the upload and late_close for the late close. The actor and the target are the open row's either way, so nothing the refused caller said about the session is written down as fact.

The row is sealed once per session, call, and caller. Attempts beyond the first add nothing: a delivery pass that offers the same refused cast again on a later sweep finds its own earlier row already on the chain and adds none beside it. A different caller, or a refusal by the other call for the same session, each leaves its own row.

The upload's detail carries session_uid, calling_node, and session_node for the node the session.open row was handed in by. The late close's detail carries session_uid, kind from the open row, and calling_submitter for the common name on the call; session_submitter and session_node carry what the open row named, where it named them, and calling_node carries the caller's registry id where one resolved. reason names which of three findings it made: submitter_mismatch when the calling name disagrees with the open row's, no_open_submitter when the open row names no submitter to compare against, and node_mismatch when the names agree but the machines do not.

Reading the audit chain

Procedure gate

Gate Contract
Classification Read-only
Prerequisites A route permitted to list rows, and the direct store plus anchor key for integrity verification.
Owner Audit analyst or incident responder.
Approval None to list or verify; approval is required before sharing output.
Backup point Preserve the command output and source snapshot.
Command or check gdsgate --config <authority-config> authority audit list --class admin-action --limit 20 and, separately, gdsgate --config <authority-config> authority audit verify.
Expected status and exit audit list prints newest-first rows and exits 0. It does not prove chain or anchor integrity. verify-audit performs that distinct proof and exits non-zero on failure.
Stop condition Stop using list output as evidence of integrity if verification has not passed.
Recovery Run the off-band verifier and compare the trusted export.
Rollback N/A.
Escalation Escalate verification failures to security and the Authority owner.

The list command opens a routed view or the direct store and prints rows. The implemented proof path is verify_durable_chain: it loads the entire chain, calls the chain verifier, and, when a key is supplied, cross-checks all audit_anchor rows. The periodic verifier calls the same path. There is no separate claim that listing rows proves depth, continuity, or anchor integrity.

Actor attribution

Procedure gate

Gate Contract
Classification Read-only
Prerequisites Rows containing actor, submitter, owner, submitter_node, and optional claimed_operator.
Owner Audit analyst.
Approval None for internal analysis; approval is required before treating a claimed name as verified identity.
Backup point Preserve the original row and export.
Command or check Compare actor, submitter, owner, and submitter_node with certificate or verified-token evidence; keep claimed_operator separate.
Expected status and exit The row remains unchanged. actor, submitter, owner, and submitter_node are cluster-derived; claimed_operator is explicitly unverified.
Stop condition Stop if a claimed name is used as a Cedar principal, request context, or verified actor.
Recovery Reissue the report with the verified fields and label the claim as unverified.
Rollback N/A.
Escalation Escalate identity disagreement to security and the identity provider owner.

actor identifies who performed the action, submitter who delivered the record, and owner on whose behalf a workload acts. claimed_operator is a caller-supplied label accepted only from the control-plane identity and never used as a decision principal, filter, or context field.

submitter_node names the machine behind submitter. submitter names the role a node enrolled under, one common name shared by every connector in a cluster; submitter_node names which of them delivered this row, resolved by Authority from the public key on the connection carrying the batch, never from a value in the request. It is empty for two distinct reasons, and neither is evidence of tampering: on a connector's row, the row was sealed before the column existed; on a row Authority sealed for itself, submitter_node is absent because Authority holds no row in the node registry: there is no node role for it to enroll under.

The id is the one authority nodes list prints and authority nodes revoke takes, so a row traced to a machine during an investigation is acted on directly. See Per-node revocation. Revocation cuts the node's identity along with its access: a node that is revoked and re-enrolls gets a new registry row and a new id, and its later rows will not match its earlier ones under --node. A batch from a connector holding no registry row at all is refused whole, with nothing from it reaching the chain; the fix is authority nodes import or a fresh enrollment.

submitter_node enters the row's signed content the way owner, claimed_operator, and code already do: appended only when the writer had a value. A chain sealed before the column existed carries nothing extra there and verifies with the same result as before; verify-audit needs no field-specific check for it.

Authentication identity

Procedure gate

Gate Contract
Classification Read-only
Prerequisites Authentication event rows and the identity provider's separate audit record when sign-in timing must be proved.
Owner Identity or audit analyst.
Approval None for internal analysis; approval is required before exporting identity-bearing rows.
Backup point Preserve the cluster row and provider event id or time window.
Command or check Count distinct actor over authentication / success rows for the time window and compare with the provider record.
Expected status and exit The result is an analysis count; no service exit applies. A token or fragment is never present in the cluster row.
Stop condition Stop if a report treats every decision row as a sign-in or expects the cluster to prove the provider's full authentication ceremony.
Recovery Use the provider's audit for login ceremony and the cluster row only for the first verified request here.
Rollback N/A.
Escalation Escalate identity-provider discrepancies to the provider owner and cluster attribution discrepancies to security.

The cluster records the first request carrying a verified resulting token, at most once per person per day on a running Authority instance. A restart or HA failover can re-arm local deduplication, so distinct actors are safer than row counts for a period. The row contains actor, issuer, write time, and optional auth_time, but no token, factor, groups, email, or source address.