Skip to content
Tracon

IAuditLog

Namespace Tracon · Assembly Tracon.Abstractions.dll

The audit trail log.

public interface IAuditLog

Writing happens in the store decorators, not in the endpoint layer: writing in the endpoint layer would miss a change made to the same store from another code path. The decorator is the single gate.

A write failure does not stop the operation — with eight named exceptions. For every audit write but those eight, a failed IAuditLog.WriteAsync is logged and the operation continues: observability does not break behaviour. The record can therefore be missing, which is what an audit trail proves and does not prove.

The eight FAIL-CLOSED operations write their entry BEFORE the change and do not apply the change if the write fails: an approval decision; granting or revoking a skill script; running a skill script; saving or deleting an inbound trigger; an automatic canary rollback; a data subject erasure; and answering a workflow’s pending human request (workflow.respond), which is written before the answer starts a run; and answering a tool’s request for user input (tool_input.answer), which is written before the suspended tool call runs again. Each of these is irreversible or security-bearing enough that a missing record is worse than a refused operation, so a failed write surfaces to the caller as an error instead of a warning in a log.

That split is a published guarantee, not an implementation detail: an implementation of this interface that swallows its own write errors turns eight fail-closed operations into best-effort ones without anything else changing. Throw on a write that did not happen. Both paths count a failure on the tracon.audit.write_failures counter, tagged swallowed or refused, so the difference stays visible in production.

There is a read endpoint only; there is no delete or edit endpoint, and there will not be one.

DI lifetime. Registered as a singleton with TryAdd; a consumer’s own registration wins. An implementation must be safe under concurrent calls and must not capture or depend on a scoped service.

Tenant behavior — EXPECTED tenant, with an AMBIENT fallback. IAuditLog.WriteAsync is scoped by the entry’s own AuditEntry.TenantId field — an explicit, EXPECTED tenant that is never inferred. IAuditLog.QueryAsync and IAuditLog.VerifyChainAsync accept an explicit TenantId override (AuditQuery.TenantId, AuditChainQuery.TenantId) and fall back to the AMBIENT ITenantContext.TenantId ONLY when that override is null or empty. A null TenantId is a contract, not a convenience: it MUST resolve to the single ambient tenant, never to “every tenant”. An implementation that treats a missing filter as “no filter” returns every tenant’s records to whoever leaves the field unset — this is exactly the shape AuditLogContract’s ambient-fallback scenarios (BL-046) guard against; the built-in InMemoryAuditLog and SqlAuditLog both resolve the fallback through an injected ITenantContext, the same pattern IRunStore’s AMBIENT methods use.

QueryAsync(AuditQuery, CancellationToken)

Section titled “ QueryAsync(AuditQuery, CancellationToken)”

Reads the records through a filter.

ValueTask<IReadOnlyList<AuditEntry>> QueryAsync(AuditQuery query, CancellationToken cancellationToken = default)

query AuditQuery

The filter.

cancellationToken CancellationToken

The cancellation token.

ValueTask<IReadOnlyList<AuditEntry>>

The records, newest first.

VerifyChainAsync(AuditChainQuery, CancellationToken)

Section titled “ VerifyChainAsync(AuditChainQuery, CancellationToken)”

Walks a tenant’s hash chain and reports whether it is intact.

ValueTask<AuditChainVerification> VerifyChainAsync(AuditChainQuery query, CancellationToken cancellationToken = default)

query AuditChainQuery

The scope: tenant and, optionally, a date range.

cancellationToken CancellationToken

The cancellation token.

ValueTask<AuditChainVerification>

The verification result.

A date range narrows which entries are walked; it does not weaken the check within that range. Because the entry immediately before the range’s start is not read, a break at the range’s own boundary cannot be judged and is not reported — an unbounded query is the only way to check a tenant’s whole history.

WriteAsync(AuditEntry, CancellationToken)

Section titled “ WriteAsync(AuditEntry, CancellationToken)”

Writes an audit record.

ValueTask WriteAsync(AuditEntry entry, CancellationToken cancellationToken = default)

entry AuditEntry

The record to write.

cancellationToken CancellationToken

The cancellation token.

ValueTask