The page rendered a map of eleven event types to labels and seven to colours.
The server emits forty-seven. Everything unmapped fell through to its raw
string, so "Key Assigned" in green sat above "workflow.schedule_updated" in
grey — the same kind of fact in two formats, which made the column look like it
carried a meaning it did not.
Presentation is now derived rather than enumerated. Event types are named
<category>.<action> by every call site, so the category becomes a chip, the
action is humanised, and the tone comes from the verb. A type added to the
server tomorrow gets a sensible label and colour with no second list to update;
the override table holds only the dozen the rule reads badly for. Every row is
one treatment, and colour never carries meaning alone — the sentence beside it
says the same thing in words.
Paging and filtering are server-side, unlike the fleet lists that answer with
everything and slice in the browser. audit_retention_days is a licensed
entitlement measured in months, and this log is read to answer questions about
the past, so a browser filtering the most recent page would report "no results"
for events that exist. GET /api/audit now takes q, category, limit and skip and
answers {events, total} — a short page is not evidence of the end of the log,
which is why the total is counted rather than inferred.
audit_logs had no indexes at all: every read was a collection scan with an
in-memory sort over an append-only collection. Adds (instance_id, created_at)
and warns rather than failing, matching EnsureSecretIndexes.
Two bugs found by running the deriver over all forty-seven real types rather
than eyeballing it: the tone rules matched only past-tense verbs, leaving
auth_provider.delete drawn as neutral beside key.deleted in red; and
"unaccepted" matched "accepted", so withdrawing an acceptance read as the same
caution as granting one.
The last of the inline two-step deletes, and the one with the most reach: a
key delete revokes it from every server at once, and for a generated key the
stored private half goes with it. That copy is the only one Vantage holds, so
unlike an uploaded key this cannot be undone by pasting the public half back.
Body names how many servers lose access, and says the private key is destroyed
only when there is one to destroy.
The delete error moves out of the toast and into the dialog, which stays open
on failure, matching the server and monitor deletes.
Destructive actions were confirmed by a second danger button rendered where
the first one had been, so a double click on Remove deleted the thing without
the operator ever reading which thing it was. Servers, monitors, secret keys
and sign-in providers now go through ConfirmDialog with requireTyped, matching
the secret-group delete that already worked this way. Notification channels and
vulnerability alert rules get an untyped dialog: both are a name and a URL and
are rebuilt in a minute, but neither had any confirmation at all, and both
silently stop alerts that nobody misses until an incident goes unannounced.
Mutations otherwise succeeded in silence, or reported into whatever inline
banner the page happened to own. Two failure modes came of that: a modal that
closed on error left the message nowhere to land, and a save that was rejected
left the old values on screen looking exactly like a save that worked
(/settings had no error path at all). Every mutation now reports through the
existing toast context.
Errors stay inline where the surface that raised them is still on screen and
the message is a correction to make in it: form validation, the cron field,
the tag rows, a rejected licence blob, and the workflow designer's autosave,
which is a standing condition rather than an event. Everything else toasts.
Ad-hoc feedback removed in favour of it: the "Sent!" button labels on the
server maintenance tab, the settings "Saved!" flag, the channel test line, and
the steps page's notice/error pair.