Say Blocked about a refusal the audit page had been calling Allowed - #302
Merged
davidmckayv merged 3 commits intoAug 31, 2026
Merged
Conversation
The audit page decides what a row was twice: once for the label and the colour, and once for the `eventType` list behind the Blocked and Did not happen saved views. Both were hand-written lists, and the fallback for anything neither list recognises is "Allowed". Six event types were on neither. A hop one Bot was not allowed to make, an endpoint this deployment refused to dial, a rotation the vault turned down and a sign-in it turned away were each drawn as Allowed, in the muted colour every ordinary row uses, on the screen an administrator opens to find out what this deployment refused. Clicking Blocked showed none of them. `agent.handoff_failed` and `agent.escalation_failed` read as Allowed too, where they are "Did not happen": nothing was refused, and nothing came of it either. The comment beside the predicate already says why this matters — "the fallback below calls anything it does not recognise Allowed, which for a refusal is the one wrong answer" — and the trail's own notes say `agent.handoff_refused` is the more important of its pair, because a hop that happened is visible in the transcript and one that was refused is invisible everywhere else. It was invisible here, and worse than invisible: affirmatively wrong. Two lists in one module now, and the page derives both the row and the saved view from them, so the next refusal is added in one place or in none. Nothing else changes: the types already recognised keep their labels, `mcp.call_failed`, `component.function_failed` and `bot.declined` keep the accurate labels they already had, and the fallback stays open so a row type this build has never heard of is still not called a refusal. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
kevin9327
requested review from
MikeRyanDev,
davidmckayv,
guidovizoso and
tylerslaton
as code owners
August 31, 2026 11:21
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
9 tasks
# Conflicts: # CHANGELOG.md
davidmckayv
previously approved these changes
Aug 31, 2026
davidmckayv
left a comment
Contributor
There was a problem hiding this comment.
Verified + CI verify green; template-appropriate correctness fix.
# Conflicts: # CHANGELOG.md
davidmckayv
approved these changes
Aug 31, 2026
davidmckayv
left a comment
Contributor
There was a problem hiding this comment.
CI verify green pre-rebase; CHANGELOG-only rebase; template-appropriate.
davidmckayv
added a commit
that referenced
this pull request
Aug 31, 2026
…one that landed (#306) The Audit screen's Bot column reads `payload.bot` and nothing else, and renders a dash without it. `agent.handoff_offered` says so in its own comment, added when the accepted pair was fixed: without this the two handoff rows are the only Bot actions on a screen headed "Every action a Bot took" that name no Bot. The pair got the key. The four rows either side of it did not: `agent.handoff_refused`, both `agent.handoff_retried` rows, and `agent.handoff_failed` carry `from` and no `bot`, so every one of them draws a dash. Those are the rows the trail exists for. `server/src/audit.ts` says it outright of the first one — "a hop that happened is visible in the transcript anyway; a hop that was refused is invisible everywhere else" — and the same holds for one that ran out of attempts, which is where a person's unanswered question ends. So the screen named the Bot on the outcome you could already see, and drew a dash on the three you could not. `agent.escalated` and `agent.escalation_failed` one file over share a single payload and get this right on both outcomes, which is the shape being restored here. Pairs with #302, which stops that same screen calling `agent.handoff_refused` "Allowed". Together a refused hop reads as a refusal and says whose it was; either alone leaves half the row wrong. Co-authored-by: kevin9327 <kevin9327@users.noreply.github.com> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> Co-authored-by: David McKay <davidmckayv@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What this changes
The audit page asks "what kind of row is this" twice, from two hand-written lists:
app/src/routes/_authed/admin/audit.tsx:146-158and:168— therefused/failedpredicate,behind the label at
:276-278and the colour at:269-274.app/src/routes/_authed/admin/audit.tsx:36-59— theeventTypequery behind the Blocked andDid not happen saved views.
Anything on neither list falls through to Allowed. Six event types are on neither, and all six
are written by reachable code today:
agent.handoff_refusedserver/src/agents/handoff.ts:108agent.dial_refusedserver/src/index.ts:585credential.rotation_refusedserver/src/credentials.ts:567session.refusedserver/src/auth/index.ts:227,:269agent.handoff_failedserver/src/agents/handoff-runner.ts:403agent.escalation_failedserver/src/agents/escalation.ts:123The comment beside the predicate already states the rule this breaks:
And
server/src/audit.ts:341-347says of the first row in that table:It was invisible on the one screen built to show it, and affirmatively labelled Allowed. Same shape
for
session.refused, whichaudit.ts:306-308calls "the one somebody investigating actuallyreaches for", and
credential.rotation_refused, of whichaudit.ts:41-46says "the refusals arethe interesting ones".
routines.dispatch_refused— the most recently added refusal — was wired into both places, whichis the pattern this follows and the reason I read the omission as an oversight rather than a choice.
The fix
The two lists become one module,
app/src/lib/audit/outcome.ts, and the page derives both the rowand the saved view from it. That is the part worth more than the six additions: a refusal on one list
and not the other is either a row drawn as Allowed or a view that is not empty, just short — and the
second is the harder one to notice.
Deliberately unchanged:
mcp.call_failed("The server did not answer"),component.function_failed("Could not be read") and
bot.declined("The Bot declined") already carry accurate labels throughDECISIONS, so only their colour is muted; that is a judgement about emphasis rather than a wrongsentence, and I left it to you. The fallback also stays open, so a row type a build has never heard
of is still not called a refusal.
Where it runs
lookup; no component state, no cache, no store.
row it was handed; the classification is a constant compiled into the bundle.
?eventType=query stringagainst the existing read endpoint, now generated from the list instead of retyped.
Boundary and audit
is the read surface for rows the server already writes.
correctly.
Changelog
CHANGELOG.md, underUnreleased.Proof
Before.
main's predicate andBlockedlist, lifted verbatim and run over the six types:After.
app/tests/audit-outcome.test.ts, following thesilenceOf/audit-silence.test.tspattern — the pure part extracted and tested, no
renderHook. It pins each of the six, keeps thesix that were already right, keeps the ordinary rows "allowed", holds the fallback open for an
unknown type, and asserts that each saved view filters by exactly the family it draws (which is the
drift the module exists to stop).