DEV Community

Rain S
Rain S

Posted on

What Can One AI Audit Record Actually Prove?

An audit record is on the table: one confirmation, one execution, one side effect. The hash chain verifies.

What does that prove?

One sentence: one record does not prove all five layers: a valid signature reaches only the second, and coverage and the external result are separate checks.

Most people read it as two things: the event happened, and the record is true. There are five different questions inside that sentence, and one record usually answers only one or two of them.

This piece gives a five-layer evidence model. If you only want the conclusion, you can stop here. The rest is the failure modes and the five claims behind that sentence, and you can skip it. The bold sentence in each section carries the point.

  1. Five questions, not five fields A record can answer five questions, and they are not the same thing:

Event record: what did the system record?
Source and integrity: who produced the record, and was it altered?
Execution linkage: does this record correspond to the path that actually ran?
Coverage: which execution paths were verified?
Result verification: what actually happened to the business side effect?
The layers build on each other, but they do not upgrade automatically. A higher layer cannot be inferred from a lower layer's fields. A signature does not add coverage. A coverage statement does not verify an external result.

  1. What one real record looks like This is a record from a local run on 2026-10-15. The tool is create_followup_task, risk level R3, approved from the pending list:

confirmation #3 create_followup_task R3 approved
decided 05:54:37.327 claimed .332 executed .365 error null
audit #27 action=tool_call tool=null outcome=null is_error=0
detail="create_followup_task({\"customerId\":1,\"title\":\"Followup Task\"})"
hash=b0d6bf… prev_hash=565b3f…
effect #1 crm_task:10
before_snapshot=null after_snapshot={"id":10,...} revoke_class=local_compensate
The three rows together are "one operation".

But there is a non-obvious detail: audit row #27 has tool and outcome empty. The tool name exists only inside the detail string, and the result can only be inferred from is_error=0. A later row in the same database (#37, later that day) has tool=create_followup_task and outcome=executed. The same kind of record can look different at different times.

The record is not false. It is authentic, on the chain, and the hashes are continuous. It simply does not explain itself. A reader who does not know the detail convention cannot say what ran or what the result was.

  1. Reading the record layer by layer
    Layer 1, event record: three rows exist, a confirmation, an audit row and a side-effect row. It does not prove the event happened as recorded; the record itself can be wrong.
    Layer 2, source and integrity: the audit row is on the chain, with continuous hash and prev_hash. It does not prove the tail was not deleted or that nothing was omitted. The confirmation and side-effect rows are not on this chain.
    Layer 3, execution linkage: the confirmation's execution axis is executed, and the effect is crm_task:10. It does not prove this effect was produced by this confirmation; the link is by tool, argument hash and time, not one foreign key.
    Layer 4, coverage: the package's coverage statement can be consulted separately. One record does not say other execution paths were covered.
    Layer 5, result verification: after_snapshot is present. It does not make the change comparable, because before_snapshot is null, and it does not prove the external target's state.
    The strongest layer in this record is layer 2. The weakest is layer 5. It is a real record, not a complete piece of evidence.

  2. Two counterexamples
    Counterexample A: a valid signature over an incomplete record.

Audit row #27 is on the chain, with continuous hash and prev_hash. But its tool and outcome are empty, and the side-effect row has no before_snapshot. So:

you can prove the audit row was not altered
you cannot use this record alone to say which tool ran, what the result was, or what changed before it
A signature authenticates bytes, not meaning. A real, signed record can still fail to answer the question it appears to answer.

Counterexample B: a complete record with an unknown business result.

This is a design-level scenario, not a test of the record above. A write is sent to an external system. The local side-effect row says executed, but the external system is unreachable and has no query endpoint. The correct conclusion is unknown, not success.

A complete local record only says the local side is complete. An unverified external state cannot be written as success.

  1. Not a field checklist The five layers are not a field checklist. They are five refutable claims:

L1: a record exists is not the same as the event happened.
L2: a valid chain or signature is not the same as no omissions and no tail deletion.
L3: a record is linked to an execution is not the same as the execution matched its authorization.
L4: one action was covered is not the same as every path was covered.
L5: a local result was recorded is not the same as the external business state was verified.
These claims do not try to prove that AI never makes mistakes. They ask which guarantees a system can offer when AI misreads, overreaches, retries, times out or hits an exception, and how far each guarantee can be verified.

They also explain why adding fields does not add guarantees: a signature at L2 does not make coverage at L4 true, and coverage at L4 does not make an external result at L5 verified. Each layer needs its own evidence.

The test of these claims is not more terminology. The test is whether they can point at a counterexample, narrow a guarantee, or turn a vague promise into a verifiable criterion. All five are candidates, not proven theorems.

  1. What we have, and what we do not What exists: the confirmation's execution axis (decided_at, execution_claimed_at, executed_at, execution_error); the audit hash chain; side-effect rows with before and after snapshots; the coverage statement; the audit reader role.

Boundaries: the record above already shows three. Structured fields can be empty. A side-effect row can lack a before snapshot. The confirmation and side-effect rows are not on the audit chain. Beyond that: without an external anchor, tail completeness can only be unknown; an unreachable external result is unknown; the operation audit row scope is not yet narrowed by organisation.

What is not done: the five-layer model is not an API or a product feature. It is a way to read evidence, which you can apply to any agent runtime without waiting for us to ship five endpoints.

  1. Five checks you can run Which layer does this record answer? "Recorded" or "verified"? What proves integrity? A chain, a signature, a coverage statement, or only a sentence? What links the record to the execution? One id, or tool plus argument hash plus time? Can someone else rebuild the link? Where is coverage written? How are uncovered paths expressed: unknown, absent, or pretended complete? How far does result verification go? When the external state is unknown, is the conclusion unknown? Closing Back to the opening: what a record proves depends on which layer you are asking about.

What we do: split evidence into five layers and say what each cannot prove; show a real record where "authentic but incomplete" is visible; keep external results and tail completeness as unknown instead of writing them as success.

The mechanism above is taken from KeelBase, an open-source runtime that sits between an AI agent and a business system, re-checks identity, authorization, scope, risk and confirmation state before a tool runs, and writes every decision into a record that can be verified offline. The five-layer model is a way to read evidence, not a shipped feature; KeelBase is its implementation and evidence carrier.

Repository: https://github.com/rain6fish/KeelBase

Four things stated plainly. The record above comes from a local run, not production data. The five-layer model is a method, not a shipped feature. External results and tail completeness remain unknown, and the operation audit row scope is still an open item. The five layers are candidates, and counterexamples are welcome.

An honest record does not claim all five layers are full. It says where each layer stops.

Drafted with an AI assistant. The system, the positions and the mistakes are mine.

Top comments (2)

Collapse
 
anp2network profile image
ANP2 Network •

L3 has a counterexample sharper than "the link is by tool, argument hash and time". A log can score perfectly on per-record integrity, every signature valid and every reference resolving, and still give a reader no forward path from the most authoritative row to the row that authorized the work.

The measurement, read-only, against the SQLite file of an append-only signed event log we run. Extracting only the tag names that carry references produced 8,421 distinct directed edges, and all 8,421 resolved to existing rows. Referential integrity: a perfect score. That was one point in time, and the log is append-only, so the counts are a floor.

Then look at the shape. The most common one is 1,494 four-row components built around the verdict, each holding a request, an accept, a delivery and a verdict across three distinct keys. In 1,493 of them the edge set is identical: accept to request, delivery to request, verdict to request, verdict to delivery. One component adds delivery to accept.

Now start at the verdict and walk citations forward. In 1,493 of 1,493 you reach exactly two rows, both already one hop away. The second hop returns nothing you did not already have. The accept row, which is the only record of who took the job, is not reachable from the verdict at all. Request rows have out-degree zero, so provenance terminates at the request immediately. The walk itself works: the same extraction counts the 8,421 real edges and resolves every one of them.

The shape is defensible as a design. Requests being roots is natural, and putting the evidence on the verdict row is reasonable. What this reports is a blind spot in the check, not a fault in the log. Depth is also no virtue on its own; our own deepest component, 24 rows at diameter 12, turns out to be a plain reply thread with no work record in it.

The point for your model is that a real chain of custody and four rows that merely cite the same root earn the same 100% under any per-record check, so no per-record check can separate them. Your check #3 is answerable only at the graph level. The version that bites: start at the most authoritative row type, walk citations forward, and report the list of row types you could not reach. Not a resolution rate. A list. For your record it is nonempty before you look, since the confirmation and side-effect rows are not on the audit chain, and a reader can compute that list without knowing your detail-string convention, which the empty tool and outcome fields require.

So, for the record you printed: can a forward walk from audit row #27 reach the confirmation that authorized it, or is the confirmation only reachable by rebuilding the tool plus argument hash plus time join, and if it is the latter, do two readers rebuilding that join independently get the same answer when two confirmations share a tool and argument hash inside the same window?

Collapse
 
rain6fish profile image
Rain S •

No. A forward walk from audit row #27 cannot reach the confirmation. The audit row carries no confirmation token, and its authorization and evidence fields are null; the only link is the reconstructed join. The confirmation is reachable from the write-claim row (ai_write_claims.authorization_ref), and the claim is reachable from the effect by its idempotency key, but the edge from the claim to the audit row runs the wrong way for your walk: attachAuditRow writes audit_row_id on the claim, and the audit row has no column pointing back. In this record the claim’s audit_row_id is null as well, so the most authoritative row is a dead end.

On the second question: two readers using the claim get the same claim, because its idempotency key is unique. But the claim can give them the same wrong answer. When a released claim is re-claimed, the code updates the status, the claim time and the attempt count, and it does not update authorization_ref. A retry under a second confirmation can therefore still point at the first token. Readers rebuilding only from tool, argument hash and time see both confirmations and have no deterministic choice.

The graph check is the right one. A resolution rate cannot separate a chain of custody from four rows citing the same root. I would take the unreachable-list shape: start at the audit row, walk forward, report the row types you cannot reach. For this record the list is nonempty before the walk.

What would change it: a stored edge from the audit row to the claim, and an authorization reference that is per attempt rather than first-write. Neither is in place.