What gets written to the safety audit
The screen loads the whole visible record with a single call:api.audit() returns a list of audit entries. The dependency array is
empty, so the screen fetches once on mount: what you see is a snapshot
taken when you opened the screen, not a live tail. Reload the screen to
pick up entries written since.
Each entry carries the fields the table renders:
kind and severity are independent axes. kind says what class of
thing happened; severity says how loudly the console should say it.
The four kinds: state, fault, reject, control
The header of the screen lists the four kinds, and the Kind column renders each entry’s kind as a chip whose text and border take the kind’s colour:
Note that
reject is deliberately the quietest chip. A reject means the
safety layer did its job and stopped something: it is a normal, healthy
outcome, and a wall of muted reject rows is not an incident by itself.
control is the loudest chip, because a control event changes who or what
is driving.
Severity and how an entry is attributed to a session
Severity is applied to the Event column only:severity === 'bad'→ the label is drawn in--bad.severity === 'warn'→ the label is drawn in--warn.- anything else (including absent) → default ink.
state entry with a bad label, or a fault entry with
an untinted one. Scan the Kind column to classify, scan the Event column’s
colour to triage.
Attribution works through the session field:
- Entry has a session id. The entry belongs to that robotics session. The id is rendered monospace so it can be copied and matched against the session elsewhere in the console.
- Entry shows
—. The entry has no session attached. Fleet- or device-level events that did not happen inside a session land here, and they cannot be traced to a session from this screen.
Reject code reference
Below the table, the screen renders a reference grid built fromREJECT_HINT in the robotics domain module. REJECT_HINT maps each reject
code to a one-line hint. The grid renders every key except NONE, with
the code in a fixed-width monospace column and the hint wrapping beside it.
Two consequences worth knowing:
NONEis a sentinel, not a rejection. It is the value the domain model uses for “no reject”, which is why the reference grid filters it out. If you seeNONEin an API payload, nothing was refused.- The grid is the authoritative list. The set of reject codes comes from the domain table that the console ships, so the screen always shows the current set and the current hint text. Read the hint next to the code rather than relying on a copy of the list elsewhere.
SCREAMING_SNAKE_CASE and name the condition that blocked
the command, not the command that was blocked. Match a reject row’s
Event value against the code column in the grid to get its hint.
PROTECTIVE_STOP_LATCHED
PROTECTIVE_STOP_LATCHED is the code you meet first, and it is worth
reading carefully because of the word latched. A latched protective stop
does not clear itself when the condition that triggered it goes away — it
stays asserted until something explicitly clears it. Every command that
arrives while the latch is held is refused, and each refusal writes its own
reject entry with this code.
That produces a characteristic shape in the log: one fault or state
entry marking the protective stop, then a run of muted
PROTECTIVE_STOP_LATCHED rejects for the same session as the controller
keeps retrying. The rejects are a symptom. The entry above them is the
incident.
Clearing the latch is not something this screen does. The audit log is a
read-only record of what happened; it has no clear, acknowledge, or reset
action.
Reconstructing an incident from the log
The table is flat and fleet-wide, so reconstruct an incident by narrowing along the two axes the record gives you:- Anchor on the session. Copy the session id out of the Session
column and collect every row that carries it. Rows showing
—are not part of that session’s story. - Read down to the first loud row. Entries are shown by age, so walk
from the newest reject back to the earliest
fault,control, or severity-tintedstateentry for that session. That entry, not the rejects that follow it, is the cause. - Use the Detail column.
detailcarries the per-entry context. A row with—in Detail has none; thelabelis all the record holds. - Translate every reject. Look each
rejectlabel up in the reject code grid. The hint tells you what condition the safety layer was enforcing when it refused the command. - Re-open the screen to extend the window. Because the fetch happens on mount, an incident still in progress will keep producing entries that your open table does not have.
Immutability and read-only access
The screen labels the logattributable · immutable, and it is built to be
exactly that. Its only interaction with the API is the single api.audit()
read; it exposes no create, edit, annotate, or delete affordance for an
entry, and there is no way from this screen to change an entry’s kind,
severity, session attribution, or detail text. The record is append-only as
far as the console is concerned.
That is the whole point of keeping this log separately from the
administrative audit: “every transition is logged and attributable” is a
claim ClutchCall makes about the robotics safety layer, and this is the
record that has to be able to back it up after the fact. Treat entries as
evidence, and export or copy what you need rather than expecting to curate
it in place.
This screen does not configure retention. Nothing on it sets, displays, or
bounds how long entries are kept, so do not infer a retention window from
the oldest row you happen to see — the list you get is whatever
api.audit() returned for your org at load time.
Related
- Telemetry — the metrics, trace, and CDR streams that sit alongside this record
- Authentication — the API key scope that gates control-plane reads

