Skip to content

audit verify: what it checks

The checks on records and seals, the flags and the output. The auditor's procedure is verify the trail.

audit verify --profile security --from 2026-09-01 --to 2026-09-17 \
  --bucket <name> --prefix audit/<application> [--json]

The range is of ingest time: from the hour --from is in, up to but not including the hour --to is in. Give a date, an hour (2026-09-17T10) or a full timestamp. The command needs only the archive and read-only credentials.

Name an application's --prefix to verify only its records. Verify one profile per run.

Record checks

For every object under records/<profile>/ in the range, in key order, against the bucket contract:

Check Passes when
Key It has the contract's grammar: profile, tenant, ingest hour, ULID
Metadata format is 1; sha256 and count are present and well formed
Bytes The SHA-256 of the stored bytes is the object's sha256
Records The object decompresses, has count lines, and each hash is the SHA-256 of its record's canonical encoding
Lock Given the deployment: an object without retention is unlocked under a profile that demands no lock and INVALID under one that does (0056)

These checks cannot show that nothing was removed or added beside the objects. Seals show that (0061); --root checks them.

Seal checks

audit verify --profile security --from 2026-09-01 --to 2026-09-17 \
  --bucket <name> --prefix audit/<application> \
  --root <thumbprint>[,<thumbprint>...]

With --root the command checks the seals of every tenant of the profile. The thumbprints are the only keys it trusts: get them from the operator out of band (audit key public --thumbprint). Nothing in the bucket, keys/roots.jwks included, can add one.

Check Passes when
Exists Each due hour has a seal. An hour is due once it has ended and the settle window (--settle, default 10m) and grace (--grace, default 1h) have passed. Quiet hours have a seal too, from the tenant's first object on. A missing seal finds a removed seal or a stopped notary
Signed By a pinned root, or a key it delegated to for this profile and tenant, within the delegation window (at most 25 hours), not revoked as of the seal. The bucket's write time is checked beside sealed_at
Chains prev is the SHA-256 of the previous hour's seal. A tenant's first seal has none
Current Count, first and last key and Merkle root, recomputed from the bucket, match. An added, removed or changed object breaks the seal, including one whose metadata was made to agree
Lock Given the deployment: the seal is locked as long as the records it covers

A range in the middle of a chain checks its first seal against the seal before the range.

Finding Meaning
seal.signature, seal.chain, seal.root, seal.missing, seal.key, seal.payload, seal.jws, seal.lock, keys.roots, keys.statement The rule broken. Each finding also names the seal's key and the tenant

Output

Item Value
Lines One per object and per seal: valid, unlocked or INVALID: <reason>
Summary Objects and records checked
Exit code Non-zero on any invalid entry. unlocked is information
flag what
--profile <p> required; the profile to check
--from, --to the range of ingest time, --to not included
--last 24h the objects ingested in the last this long, ending at the hour that has closed; instead of --from and --to
--prefix <p> the installation's prefix in the bucket, as archive.prefix of the chart's configuration names it; omit only where the installation is at the bucket's root
--root <thumbprints> also check the seals, trusting only the root keys with these thumbprints, comma separated; --settle and --grace set when a seal is due
--sink <writer> record what was checked through the writer. The scheduled job does; an auditor's run by hand should not
--deployment <file> the profile configuration, so the check knows what lock the profile demands. The scheduled job passes it; an auditor without it still gets keys, bytes and hashes checked, with nothing said about locks
--json print the report as JSON
--endpoint, --path-style an S3-compatible store that is not AWS, as put the archive on an S3-compatible store has it

Scheduled job

The nightly in-cluster run is audit verify --config; see configuration: the jobs. When it checks seals (seals.roots), it records audit.seal.verified or audit.seal.failed per ingest hour through the writer. The target is records/<profile>/<yyyy>/<mm>/<dd>/<hh>. A run that checked no seal records none.

The archive written before the v1 layout is not read. Verify it with the previous release's CLI (v0.6.x).

For a failed verification, see investigate a failed verification.