Audit trail

Every answer, and every refusal.

Most access logs record what worked. This one records what did not, with equal care, because a pattern of refusals is the more interesting signal – somebody probing at the edge of their access looks exactly like nothing at all in a log that only writes successes.

audit · planning-testillustrative
TimePersonToolOutcomeReason
09:14:02a.morgandescribe_modelallow
09:14:38a.morganread_sliceallow
09:15:11a.morgancrm_read_alldenyskill_not_enabled
09:15:11t.walshdocs_searchallow
09:16:40t.walshread_slicedenyno_grant_on_project
09:22:03denycredential_rejected

The last row has no person on it on purpose. A credential rejected before it reached any permission check is still recorded, so somebody guessing at your connector is visible rather than silent.

The reason codes

Seventeen names, and a test that freezes them.

A refusal reason is a stable name, not a sentence someone rephrased. There are seventeen of them, and a test fails the build if one changes – because this is data somebody will read three years from now, and it has to still mean the same thing then.

Why not a message

A human-readable message is written for whoever is debugging that week. It gets reworded, it gets translated, and every rewording silently breaks the report somebody built on top of it.

Why a test rather than a convention

Conventions are followed until a deadline. The frozen set means renaming a code is a deliberate, visible act with a failing build attached, rather than a tidy-up in a pull request nobody reads closely.

What you do with them

Group by code. A rise in one specific refusal is a person hitting a boundary repeatedly, and it usually means a grant is wrong rather than that somebody is malicious.

The record

What a row contains, and what it deliberately does not.

On every row

  • When – to the second.
  • Who – the verified person, by their own identity rather than a shared credential.
  • Which environment – never inferred, always named.
  • Which tool was called.
  • The outcome – allowed or refused.
  • The reason – one of the seventeen names, on every refusal.

Never on a row

  • The figures that came back. The record says a read happened, not what it returned.
  • Document contents. Same reason.
  • Anything about another organization. Not a name, not an identifier, not a slug.
  • Free-text somebody can shape. A log line an attacker can write into is a log line you cannot trust.

The absence of figures is a deliberate trade. It means the audit trail cannot tell you what a person saw, only what they were permitted to ask for. Recording the values would make the log a second copy of your data, with all of the exposure and none of the controls.

Access to the record

It is yours, and reading it is itself governed.

QuestionAnswer
Who can read it Your own administrators, through the console, scoped to your organization. There is no cross-organization view, including for us.
Can you export it Yes. It is your record and it leaves in a form you can put in front of an auditor.
Can you edit it No. Neither can we, through the console.
Does it survive a revoked grant Yes. Removing somebody's access does not remove the history of what they reached, which is the point.

One honest caveat about permanence. The console offers no way to alter a row, and no feature exists to do it. That is an application-level guarantee, not a cryptographic one: it is a database, and a database administrator with direct access is a different threat model. If write-once storage is a requirement in your review, say so early rather than assuming it.

See what a month of it looks like.

The most useful thing in an evaluation is usually not the demo. It is a fortnight of real rows from your own environment, and the conversations that start when somebody reads them.