# IAuditLog

> Tracon documentation. Tracon is a .NET package family that adds a control
> plane on Microsoft Agent Framework, published as NuGet packages that run
> inside your own ASP.NET Core application.
>
> Page: https://tracon.dev/api/tracon.iauditlog/
> Summary: IAuditLog: The audit trail log.

<div class="api-provenance">Namespace Tracon · Assembly <code>Tracon.Abstractions.dll</code></div>

The audit trail log.

```csharp
public interface IAuditLog
```

## Remarks

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](/api/tracon.iauditlog/#Tracon_IAuditLog_WriteAsync_Tracon_AuditEntry_System_Threading_CancellationToken_) 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](/api/tracon.iauditlog/#Tracon_IAuditLog_WriteAsync_Tracon_AuditEntry_System_Threading_CancellationToken_) is scoped by the entry's own `AuditEntry.TenantId`
field — an explicit, EXPECTED tenant that is never inferred. [IAuditLog.QueryAsync](/api/tracon.iauditlog/#Tracon_IAuditLog_QueryAsync_Tracon_AuditQuery_System_Threading_CancellationToken_)
and [IAuditLog.VerifyChainAsync](/api/tracon.iauditlog/#Tracon_IAuditLog_VerifyChainAsync_Tracon_AuditChainQuery_System_Threading_CancellationToken_) accept an explicit `TenantId` override
([AuditQuery.TenantId](/api/tracon.auditquery/#Tracon_AuditQuery_TenantId), [AuditChainQuery.TenantId](/api/tracon.auditchainquery/#Tracon_AuditChainQuery_TenantId)) and fall
back to the AMBIENT [ITenantContext.TenantId](/api/tracon.itenantcontext/#Tracon_ITenantContext_TenantId) ONLY when that override is
[null](https://learn.microsoft.com/dotnet/csharp/language-reference/keywords/null) or empty. A [null](https://learn.microsoft.com/dotnet/csharp/language-reference/keywords/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](/api/tracon.itenantcontext/), the same pattern
`IRunStore`'s AMBIENT methods use.

## Methods

### <a id="Tracon_IAuditLog_QueryAsync_Tracon_AuditQuery_System_Threading_CancellationToken_"></a> QueryAsync\(AuditQuery, CancellationToken\)

Reads the records through a filter.

```csharp
ValueTask<IReadOnlyList<AuditEntry>> QueryAsync(AuditQuery query, CancellationToken cancellationToken = default)
```

#### Parameters

`query` [AuditQuery](/api/tracon.auditquery/)

The filter.

`cancellationToken` [CancellationToken](https://learn.microsoft.com/dotnet/api/system.threading.cancellationtoken)

The cancellation token.

#### Returns

 [ValueTask](https://learn.microsoft.com/dotnet/api/system.threading.tasks.valuetask\-1)<[IReadOnlyList](https://learn.microsoft.com/dotnet/api/system.collections.generic.ireadonlylist\-1)<[AuditEntry](/api/tracon.auditentry/)\>\>

The records, newest first.

### <a id="Tracon_IAuditLog_VerifyChainAsync_Tracon_AuditChainQuery_System_Threading_CancellationToken_"></a> VerifyChainAsync\(AuditChainQuery, CancellationToken\)

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

```csharp
ValueTask<AuditChainVerification> VerifyChainAsync(AuditChainQuery query, CancellationToken cancellationToken = default)
```

#### Parameters

`query` [AuditChainQuery](/api/tracon.auditchainquery/)

The scope: tenant and, optionally, a date range.

`cancellationToken` [CancellationToken](https://learn.microsoft.com/dotnet/api/system.threading.cancellationtoken)

The cancellation token.

#### Returns

 [ValueTask](https://learn.microsoft.com/dotnet/api/system.threading.tasks.valuetask\-1)<[AuditChainVerification](/api/tracon.auditchainverification/)\>

The verification result.

#### Remarks

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.

### <a id="Tracon_IAuditLog_WriteAsync_Tracon_AuditEntry_System_Threading_CancellationToken_"></a> WriteAsync\(AuditEntry, CancellationToken\)

Writes an audit record.

```csharp
ValueTask WriteAsync(AuditEntry entry, CancellationToken cancellationToken = default)
```

#### Parameters

`entry` [AuditEntry](/api/tracon.auditentry/)

The record to write.

`cancellationToken` [CancellationToken](https://learn.microsoft.com/dotnet/api/system.threading.cancellationtoken)

The cancellation token.

#### Returns

 [ValueTask](https://learn.microsoft.com/dotnet/api/system.threading.tasks.valuetask)
