fix(channels): offer only session trust on channel approval cards (#4421) - #5202
Conversation
Opus 4.8 Review — ✅ no blocking findingsReviewed Review detailsThe change is a well-contained, subtractive UI gate: No findings. [OPUS-REVIEWED] 81c5d94 Verdict parsed from the review's SHA-scoped output markers for commit False positive or not applicable? A repository writer can comment: |
Design Review (Fable 5) — ✅ PASSDesign-level review of Design-Verdict: PASS Correctly scopes trust tiers to what the channel backend accepts, with an explicit opt-out prop and the root cause (no structured command in the message schema) named for the eventual fix. [DESIGN-REVIEWED] 81c5d94 |
GPT 5.6 Review — ✅ no blocking findingsGPT 5.6 completed its review of This comment is updated in place on each push. Review detailsNo findings. False positive or not applicable? A repository writer can comment: |
UX Review (Fable 5) — ✅ PASSUX-level review of UX-Verdict: PASS Removing the lying "Trust "Researcher"" tier fixes a label that silently rejected what it promised to grant; what remains is truthful and backend-honored. Suggestions
[UX-REVIEWED] 81c5d94 |
First Principles Review (Fable 5) — ✅ PASSPremise-level review of All evidence gathered; I verified the backend's accepted decision set in First-Principles-Verdict: PASS A reported silent-reject defect (#4421) is removed by deleting the two menu tiers that caused it; the one new prop has a counted consumer. What this change shipsIntent: stop the channel approval card from offering trust tiers that mislabel an agent role as a command and silently reject the tool call. This is a FIX.
Counts run: non-test [FIRST-PRINCIPLES-REVIEWED] 81c5d94 |
|
🤖 Kiro Crew Auto-Pipeline [operator: bolichen97#bb3ad1ca] CI failure attribution for this PR's first run (all four failures are external to this frontend-only diff):
Will rerun failed jobs once the run completes; the backend pair stays red until #5212 (or an equivalent) merges. |
|
🤖 Kiro Crew Auto-Pipeline [operator: bolichen97#bb3ad1ca] Re: First Principles CONCERNS (cause #3 — the silent-confirm mechanism): agreed it deserved a named line, and it now has one — the PR body's out-of-scope list explicitly covers the optimistic Why not close it here: the swallow spans every ApprovalCard consumer and changes the shared card contract ( |
) A channel approval card mounted TrustDropdown with the agent's ROLE as its command: the "Trust this command" tier described a role string, and clicking it emitted trust_command — a decision the channel approve endpoint rejects (HTTP 400) and channel.py's timeout path resolves as "rejected", so the card showed "Trusted" while the tool call was silently refused. The channel approval message schema carries no tool command, so take the narrow direction: give ApprovalCard and TrustDropdown an explicit hasCommand prop (defaulting to true) and pass hasCommand={false} from ChannelPage. The channels surface now offers only the session-scoped `trust` action — which the backend accepts — and the command-scoped tiers (trust_command / trust_base) render exactly as before on the chat surface, whose call sites omit the prop. Deliberately out of scope: threading a real command through the channel approval message schema (direction (a) in the issue), any change to the chat surface tiers, and channel.py's fail-closed decision coercion (unknown decisions still coerce to "rejected"). Tests pin both surfaces: the channels surface offers the plain trust action and neither command tier, and the chat surface keeps all three. Closes #4421
46d9b02 to
81c5d94
Compare
|
🤖 Kiro Crew Auto-Pipeline [operator: bolichen97#bb3ad1ca] Head refreshed |
Open PR relationship auditThis is a consolidated, point-in-time code-level audit note. It compares complete merge-base diffs and current/merged code; it does not treat a shared topic as duplication or partial coverage as completion. Relationship findings
No PR, Issue, label, branch, or review state was changed by the relationship-note portion of this audit. |
Problem / Motivation
On a channel approval (the Channels page, not the chat surface), the approval card offered command-scoped trust tiers that were both mislabeled and destructive:
ChannelPage.tsxmountsApprovalCardwithtitle={msg.fromRole}— an agent role string, not a command.TrustDropdownderived itsfullCommand/basePatternfrom that title, so the "Trust this command" tier literally read Trust "Researcher".TrustDropdownrendered thetrust_commandaction unconditionally; onlytrust_basewas gated on the title looking like a shell command.trust_command— a decision outside the channel backend's accepted set (approved/rejected/trust): the approve endpoint rejects it with HTTP 400,ChannelPage's.catch(() => {})swallows the error, and the card optimistically flips to "Trusted — auto-approving future calls" while the approval future stays pending untilchannel.py's 1-hour timeout resolves it rejected.Why it matters
A user who clicks "Trust this command" on a channel approval silently rejects the tool call they meant to allow — the UI confirms a grant that never happened, and the tier they clicked was describing an agent role rather than any command. That is a trust-UI integrity bug on an approval surface.
What changed (motivation → approach → change)
The channel approval message schema has no structured command field, so this takes the narrow direction from #4421: stop offering command-scoped tiers on a surface that has no command to scope them to.
TrustDropdowngains an explicithasCommandprop (defaulttrue). Whenfalse, the command-scoped tiers (trust_command,trust_base) are not rendered; the session-scoped plaintrustaction remains — the one decision in this family the channel backend accepts.ApprovalCardforwards the prop.ChannelPagepasseshasCommand={false}at its singleApprovalCardmount (all threeMessageBubblecall sites, including the thread panel, route through it).ChatInput.tsx,ActivityViewer.tsx), which omit the prop, keep all three tiers exactly as today.ChannelPage'sonApprovesignature still drops thepatternargument — moot for the removed tiers; no synthetic pattern is threaded through.Explicitly out of scope (this PR does NOT close the schema gap):
channel.py's decision coercion (unknown →rejected) is deliberately untouched: fail-closed is the right default.setDecided+.catch(() => {})that lets a failed decision POST still flip the card to "Trusted"/"Approved" — is what made the removed tiers destructive, and this PR eliminates the only guaranteed-failure path through it. The mechanism itself (still reachable via a stale card after the 1-hour timeout, a decision raced from another tab, or a 404 after gateway restart) spans all ApprovalCard consumers and is tracked as ApprovalCard optimistically shows 'Trusted/Approved' even when the decision POST fails #5204 with a concrete fix shape (makeonApprovereturn the request Promise; roll backdecidedand render failure on rejection).hasCommandstays optional-with-default-true by design (the narrow direction leaves command-bearing call sites untouched); a future command-less surface must passfalseexplicitly.Tests
TrustDropdown.test.tsx: withhasCommand={false}— only the plain session-trust item renders (even when the title looks like a shell command); it emitstrustwith no pattern; default keeps all 3 tiers (chat-surface regression guard).ApprovalCard.test.tsx:hasCommand={false}offers only the plain trust action and emitstrust(a backend-accepted decision); shell-looking titles stay suppressed; default keeps 3 tiers.ChannelPageCoverage.test.tsx: integration — a channel approval message offers exactly one menu item ("Trust all tools"), no tier describes the role string, and the posted decision is always withinapproved/rejected/trust.ChatInput.approval.test.tsx: the chat surface offers all three tiers for a shell command (regression guard so a future change cannot silently strip them).Local gates: backend pytest 61259 passed;
tsc -bclean; vitest 22919 passed (the single failure is the pre-existing main-red #5188 —capture/thinking-block-align.tsxboots the English-only i18n entry, reproduced on cleanmain, fix in flight in #5166); isort/flake8/mypy/black/brand/i18n/jscpd all green.Manual verification
Captured against a live dev server with mocked channel data (screenshots below): before — the dropdown offers Trust "Researcher" (the agent role rendered as a trustable command); after — only "Trust all tools".
Screenshots / video
Before — command tier mislabels the agent role as a command:
After — only the session-scoped trust action, which the backend accepts:
Full-card context (before / after)
Related Issues
Closes #4421
Checklist
feat|fix|docs|refactor|perf|test|chore|ci|build|revert: ...)