Skip to content

keelgate.audit

Stability of each symbol (stable or provisional) is listed in the API contract. Narrative and semantics are in the integration contract.

Tamper-evident, hash-chained audit log and its verifier.

AuditError

Bases: Exception

The audit log could not be written or read correctly.

AuditLog

Append-only, hash-chained, per-tenant audit log.

Records every tool call, policy decision, approval and loop transition. Writing is synchronous and durable before the call returns: a side effect must never happen without its audit record having been committed first.

verify_chain

verify_chain(
    tenant_id: str,
    *,
    expected_head: ChainHead | None = None,
) -> ChainVerification

Verify this tenant's chain as stored. See :func:verify_chain.

AuditRecord

Bases: BaseModel

One hash-chained entry in a tenant's audit log: sequence, time, event type, actor, payload and the hashes that link it to the one before.

AuditStore

Bases: Protocol

Where audit records are persisted (SQLite and Postgres ship).

Implement this to store the chain elsewhere. Appends must be atomic per tenant, and stored records must not be editable in place.

append

append(tenant_id: str, build: BuildRecord) -> AuditRecord

Atomically build and persist the tenant's next record.

build(next_seq, prev_hash, prev_timestamp) runs inside the critical section, so the record is stamped in the same order it is chained.

ChainHead dataclass

The newest record of a tenant's chain. Anchor this outside the writer.

ChainVerification dataclass

The result of verifying one tenant's audit chain.

Whether it is intact, how many records were checked, the head, and where it broke if it did. Truthy when the chain is intact.

EventType

Bases: StrEnum

Event kinds Keelgate itself emits. Stored as plain strings, so a chain containing a newer event kind still verifies under an older verifier.

PostgresAuditStore

Shared audit store for multi-process deployments.

Payloads are TEXT, not JSONB: JSONB normalises key order and number formatting, which would break the byte-exact hash. Appends serialise per tenant with a transaction-scoped advisory lock.

close

close() -> None

Connections are per-operation; nothing is held open.

SqliteAuditStore

Local, single-file audit store. Use ":memory:" for tests.

canonical_json

canonical_json(value: object) -> str

Deterministic JSON: sorted keys, no whitespace, ASCII, no NaN or infinity.

genesis_hash

genesis_hash(tenant_id: str) -> str

The prev_hash of a tenant's first record.

verify_chain

verify_chain(
    records: Iterable[AuditRecord],
    *,
    tenant_id: str,
    expected_head: ChainHead | None = None,
) -> ChainVerification

Verify one tenant's records, in order, from genesis.

Pure and storage-independent: feed it records exported from anywhere. It checks tenant, contiguous sequence numbers, each prev_hash link, every recomputed hash and timestamp ordering. With expected_head it also detects truncation and rollback of the tail.