Skip to content

The audit record

Sparkyard keeps an append-only record of what happened in your workspace, and an operator can export it as a file with a date range on it. It exists because “your code runs on your hardware and your inference goes to your account” are claims somebody will eventually ask you to evidence, and the answer should be a file rather than a paragraph.

kindwhat it says
turna turn finished — whose machine, who it was for, which agent, which endpoint actually answered, what it cost
setting-changeda workspace setting was changed, by whom, to what
routing-changedwhere a vendor’s inference is sent was changed, by whom
workbench-openedsomebody asked for a browser terminal or IDE on a machine
link-shareda preview or workbench link was widened, by whom, to how many people

turn is the row that state alone could not answer. The cost ledger keeps a running total and only the last endpoint a thread used, which cannot tell you where the seventh turn went. The audit row can.

  • No credentials, ever. A sealed secret appears as its name and nothing else; a routing change records the target, never the values. A denylist of key names is redacted on the way in as a second line of defence.
  • No prompts or replies. The recent conversation lives with the thread and dies with it. Copying it here would put the most sensitive content into the artifact most likely to leave the building.

In the operator dashboard, /admin:

  • …/admin/api/audit?team=<workspace>&from=<ms>&to=<ms> — JSON
  • …/admin/api/audit.csv?team=<workspace>&from=<ms>&to=<ms> — a CSV download
  • …/admin/api/audit-verify?team=<workspace> — checks the chain

Every export reports writeFailures, the number of appends that have failed since the gateway last started. It is reported even when it is zero, because “0” is information and its absence would read as a promise of completeness that nobody made.

Each row carries a hash of the row before it, and its own hash covers that link. So:

A passing verification means no row was altered, and none was lifted out from between two others, since it was written. An edit or a deletion breaks every row after it, and the check names the first seq where it broke. The hash travels in the export, so a file can be checked away from the server that produced it.

Two things, and they are stated here rather than buried because a tamper-evidence claim that oversells is worse than making none.

It does not prove nothing is missing. A row that was never written leaves no gap — the sequence stays contiguous — so the chain attests to the integrity of what is present, never the completeness of it. That is what the writeFailures count is for, and it only covers failures this process saw.

It does not bind whoever operates the database. On hardware you run, an administrator with write access can rewrite every row and recompute every hash, and the chain will verify. It protects against an edit after the fact, not against the operator. Making it bind the operator needs the chain head published somewhere they do not control, which Sparkyard does not do today.

If your requirement is the second one, say so — it is a real feature and this is honestly not it.