Audit trails
Steelmoth keeps nine separate records of what happened. This page puts them side by side: what each one writes down, who can change it afterwards, and how hard it is to tamper with.
The trails answer different questions. “What did the agent try?” is one record. “Who approved it?” is another. “Who changed the rules of the watchdog itself?” is a third. No single log answers everything — together they do.
The nine trails
Section titled “The nine trails”| Trail | What it records | Who can write | Edit / delete? |
|---|---|---|---|
1. violations.jsonl host file, /var/log/operator-authority/ |
Every block, every protected-path hit, every startup problem (policy failed to load, shim failed to init, hook got no input). One JSON line per event: time, agent, tool, target, rule, outcome. | Appended under a lock by the enforcement code. It is the single file outside the allowed write roots that Landlock permits — so even a fully sandboxed agent can still be logged. | Nothing in the agent’s world edits it; everything around it is read-only to agents. This file is the canonical record. |
2. Violations mirror DB, operator_authority.violations |
The same events, mirrored into the database so the dashboard can show them. | The runtime role holds INSERT and SELECT only. |
No — no UPDATE, no DELETE grant exists. Rows can only ever be added. |
3. Hash-chained ledger authz audit.py |
Every governed product action: who, what, which capability, the decision, the risk, in order. | The pipeline appends; each record carries a sequence number and the fingerprint (hash) of the record before it, starting from a fixed genesis value. | Effectively no — change, drop, reorder, or forge any record and re-verification fails for everything after it. A signing layer (RSA over each record’s hash) is built on top, awaiting key custody wiring. |
4. Governance decisions DB, steelmoth.governance_decisions |
The pipeline’s verdicts persisted per tenant/workspace/agent. | The runtime role, but only within its row scope: the table has row-level security with FORCE on, so even the app role can’t read or write across tenants. | Constrained by the same forced row scope. |
5. Settings audit DB, steelmoth.governance_settings_audit |
Who flipped which governance switch, old value to new, when — and whether they had passed a second factor (a field that is recorded today, not yet enforced). | SELECT + INSERT only, FORCE RLS. |
No — append-only by grant, same as the violations mirror. |
6. Action audits DB, write_action_audit, morpheus_run_audit |
Product write-actions and Morpheus runs, per scope. | Runtime role within forced row scope. | Constrained by FORCE RLS. |
7. Sessions DB, operator_authority.sessions |
Which agent was active and when (24-hour last-seen window), with its kind locked to four allowed values by a database CHECK. | Opened and closed by the shims, best-effort. | Close is idempotent; the kind constraint can’t be bypassed by the writer. |
| 8. Approval records DB, approvals + task plans | Every request that waited for a human, and what the human said. | Created by the gate’s client; decided from the dashboard. | Decided exactly once — the decision update only applies where no decision exists yet, so a second click (or a race) can’t overwrite the first answer. |
9. The paper trail repo: CHANGELOG.md, ADRs, RFCs, audits |
Why things are the way they are: every change (Hard Rule 8), every decision record, the forensic write-ups of past incidents. | Anyone can propose; merging needs the meta scope and a CODEOWNERS review. |
Editable like any code — but only through reviewed pull requests, with git history underneath. |
Ranked by tamper resistance
Section titled “Ranked by tamper resistance” hardest to tamper with │ │ 1. hash-chained ledger math — any change breaks every later link │ 2. append-only DB tables grants — UPDATE/DELETE simply don't exist │ 3. FORCE-RLS audit tables row scope the app role cannot turn off │ 4. violations.jsonl file on a host agents can't write to (almost) anywhere │ 5. reviewed markdown git history + required human review │ ▼ easiest (still not easy)
