Skip to content

fix(cloud-agent-sdk): validate message time and permission scope at the event boundary - #8872

Open
iscekic wants to merge 7 commits into
mainfrom
drain/cloud-agent-sdk-event-validation
Open

iscekic wants to merge 7 commits into
mainfrom
drain/cloud-agent-sdk-event-validation

Conversation

@iscekic

@iscekic iscekic commented Oct 24, 2026 •

Copy link
Copy Markdown
Collaborator

Evidence

Evidence: demonstrated — on the local stack I relayed session events and observed both surfaces render as designed: a request the app presents as a notice appears in the web client and on the Android dev client, a request it presents as a consent card still renders with its patterns and command, and answering the notice's request clears it.

Change

packages/cloud-agent-sdk now has a place for a permission request that the app cannot present as a decision, and both clients show it as a notice.

  • The permission.asked contract is unchanged for a request that parses: it still becomes the consent card with the scope it declared.
  • Such a request becomes a permission.unreadable event carrying its request id; the transport groups it with the other pending-interaction events while the connected snapshot is pending.
  • Session state holds one notice per request id, clears it on that request's permission.replied or on reconnect, and clears it when a presentable request for the same id arrives. The session manager exposes the list as atoms.unreadablePermissions.
  • Web renders PermissionUnreadableNotice above the consent-card slot; mobile renders the same notice above the blocking-card region, with its copy in all 87 catalogs. Both name the situation and point the reader at the app or the CLI. The composer stays usable.
  • The message.updated and replay handling from the earlier commits is unchanged.

Tests: normalizer.test.ts for the boundary, service-state.test.ts for tracking and clearing, session-manager.test.ts for the atom, PermissionUnreadableNotice.test.ts for the web render, permission-unreadable-notice.mounted.test.tsx and session-detail-content.test.ts for mobile.

End-to-end verification

Run on the drain work VM, 2026-10-24, at head 9afd18244f5c5a1300ced80a1b33b9d79cb9490f.

Stack, all local: web on 3000 (pnpm dev:start nextjs cloudflare-session-ingest), Postgres from dev/docker-compose.yml, pnpm drizzle migrate, and Metro on 8081 for the Android dev client.

Fixture, on an owned test account created only in the local database: a session row written through the worker's own ingest endpoint, plus a local stand-in for a remote CLI that opens the same relay websocket, heartbeats the session, and emits the session's events. The browser's real user-web connection, the relay Durable Object, the shipped client bundles, and the real session state all run unchanged; only the agent end is local. Like the real CLI, the stand-in replays the requests it is still waiting on after a subscribe.

Probes:

probe web client Android dev client
request presented as a notice notice renders above the card slot notice renders above the card slot
request presented as a consent card, relayed beside it card renders with its patterns and command card renders with its patterns and command
answer the notice's request notice disappears, the card stays not exercised on the device
answer the card's request card disappears, nothing remains not exercised on the device
composer while the notice shows editable editable (Message, attachment and stop controls present)

Limits: the answer transition was observed in the web client; mobile shares the same session state and atoms, and its clearing is covered by the state tests and the screen-level test. The device run observed the notice and the consent card together after a resubscribe, which is the path the relay takes for a still-pending request.

Production prerequisites

None.

Proxy answers

The owner's proxy gave these answers in the owner's place.

  1. Question: Message events: How strict should the message.updated boundary be? The CLI's own wire union requires role plus time.created on both message kinds, and the SDK's readers dereference time (the streaming check reads time.completed; both app transcripts read time.created). Option A rejects an event whose role is missing, or whose time.created is not a number, and rejects a non-numeric time.completed. Option B only requires time for a role-unknown-or-assistant message and leaves role and user time unvalidated, which is narrower but leaves a message with no readable created reaching the renderers.
    Answer: Match the CLI wire contract
    Confidence: 0.65 · Precedent: Fix the root cause and the class of the issue: session 00eb2a1d3e4b, "if anything repeats the same mistake over and over again, fix the class". Every reader of time and role is covered at the single boundary, which matches the CLI contract.
  2. Question: Permission scope: How should the permission.asked boundary treat the scope fields? The CLI contract declares patterns (string array), metadata (object) and always (string array) as required. Option A requires all three: a request whose field is the wrong type or missing is dropped, so no approval is ever offered with a scope the app could not read. Option B requires the fields only when present, so a payload that omits them keeps today's empty-scope request (backwards compatible with an older CLI that sends no scope, at the cost of offering approval without a visible scope).
    Answer: Require all three (fail closed)
    Confidence: 0.60 · Precedent: Accept real security fixes even when they cost some behavior: session fd4be8a3d43b, where the token-scheme and attachment-reaper fixes were approved. The brief also asks to report unparseable scope as malformed instead of forwarding empty values.
  3. Question: Dropped request: When a permission request is rejected as malformed, what should the user see? The event simply never reaches the consent surface today, so the CLI keeps waiting for an answer the app cannot offer.
    Answer: Drop silently
    Confidence: 0.60 · Precedent: Reject changes that add new product policy, and keep the change to the brief's scope: session fd4be8a3d43b and PR fix(payments): refine callback response contracts #8666, "limit the change". Malformed events are handled the same way as every other malformed event, and any existing diagnostic logging for them stays in place.
  4. Question: Replay snapshot: The live part schema is mirrored into sessionSnapshotSchema (the replay/history shape), and that schema currently validates only message id. Should the new message info schema be mirrored there too? Rejecting a row drops the whole stored message and its parts from that parse; leaving it out keeps today's behaviour and relies on the new reader guard. No code in the repository parses a snapshot through that schema today, so this is about the contract it states.
    Answer: Mirror into the replay schema
    Confidence: 0.60 · Precedent: Fix the class on every affected surface: session 75f8833fe8c1, "fix this for all shared services model pickers on all surfaces"; the part schema is already mirrored there, so keep one shape model.
  5. Question: Composer: While an unreadable permission request is waiting, what should the message composer do?
    Answer: Stay usable (Recommended)
    Confidence: 0.70 · Precedent: Choose the simplest design that reaches the goal: session a30dc8dc131a, owner typed "Simplest please"
  6. Question: No id: If the event cannot yield even a request id (nothing to key the notice to, and no permission.replied could ever clear it), what should the boundary do?
    Answer: Keep dropping it (Recommended)
    Confidence: 0.65 · Precedent: Choose the simplest design that reaches the goal: session 1f0202ee8c71, "What's the simplest solution that gets us to our goal?"
  7. Question: Sidebar: Should a session with only an unreadable request report itself as 'permission' in the sidebar/session list status?
    Answer: No, transcript notice only (Recommended)
    Confidence: 0.70 · Precedent: Do the least work possible with no drop in quality: session 7f3470e0ba6f, "do the least amount of work possible, with no drop in quality"
  8. Question: Unreadable ask: Earlier this pull request recorded 'drop silently' for a permission request whose scope cannot be parsed. The owner has since requested the opposite. Which stands for the final change?
    Answer: Surface a notice (Recommended)
    Confidence: 0.85 · Precedent: Treat human PR actions as authoritative (session fd4be8a3d43b: owner's PR actions override earlier automated decisions); the owner's changes-requested comment supersedes the earlier 'drop silently' answer

…he event boundary

The message.updated boundary now validates the identity, role and time the SDK
reads, and the session's streaming check reads the assistant completion time
defensively so a stored row without it cannot throw inside the transcript atoms.

A permission request keeps patterns, metadata and always only when they parse,
so a request whose scope cannot be read is not forwarded. Tests cover both
events, the mirrored replay schema, and a stored assistant row with no readable
time.
@iscekic iscekic added drain Work item from the findings drain janitor:trust Janitor finding: trust boundary hardening labels Oct 24, 2026
@iscekic

iscekic commented Oct 24, 2026

Copy link
Copy Markdown
Collaborator Author

bot: Local web run at head 1fac735, Chromium against the dev stack with a session fixture serving the probe sessions' live events. Both captures come from the app with a seeded fake account on this VM.

  • The transcript session renders each served row in order.
  • A permission request that carries its scope renders the consent card with its Patterns and Command sections.

Coverage limit: captured on the web client; the mobile consent card shares this SDK event path and was not captured.

Transcript with every served row rendered

Consent card showing the request scope Patterns and Command

Comment thread packages/cloud-agent-sdk/src/schemas.ts
Comment thread packages/cloud-agent-sdk/src/schemas.ts
Comment thread packages/cloud-agent-sdk/src/normalizer.test.ts
Comment thread packages/cloud-agent-sdk/src/schemas.ts Outdated
Comment thread packages/cloud-agent-sdk/src/schemas.ts
@kilo-code-bot

kilo-code-bot Bot commented Oct 24, 2026 •

Copy link
Copy Markdown
Contributor

Code Review Summary

Status: 1 Issue Found | Recommendation: Merge

Executive Summary

The three previous suggestions are addressed - the notice now announces itself, the atom-identity test returns fresh arrays, and the composer test mounts an answerable session - but the new unconditional announcement duplicates the Android live region, so TalkBack speaks the notice twice.

Assumptions: [INFERENCE] Tests, typecheck and lint were not run in this sandbox; findings rest on reading the changed code and the repository's documented a11y contract.

Overview

Severity Count
CRITICAL 0
WARNING 0
SUGGESTION 1
Issue Details (click to expand)

SUGGESTION

File Line Issue
apps/mobile/src/components/agents/permission-unreadable-notice.tsx 23 The notice announces imperatively on every platform while it also carries an Android polite live region, so TalkBack speaks it twice; the shared contract (status-announcement.ts) announces only on iOS and leaves Android to the live region
Files Reviewed (4 changed files)
  • apps/mobile/src/components/agents/permission-unreadable-notice.tsx - 1 suggestion (announcement channel duplicated on Android)
  • apps/mobile/src/components/agents/permission-unreadable-notice.mounted.test.tsx - no issues (asserts the polite live region)
  • apps/mobile/src/components/agents/session-detail-content.test.ts - no issues (writable session mounted; wrapper hidden/accessibilityElementsHidden cover the composer-gating regression)
  • packages/cloud-agent-sdk/src/session-manager.test.ts - no issues (fresh array with the same ids now exercises the id comparison in sameRequestIds)

Fix these issues in Kilo Cloud

Previous Review Summaries (5 snapshots, latest commit 65c842f)

Current summary above is authoritative. Previous snapshots are kept for context only.

Previous review (commit 65c842f)

Status: 3 Issues Found | Recommendation: Merge

Executive Summary

The unreadable-permission path is correct end to end - normalizer event, per-id tracking, clearing on reply/reconnect/supersede, and rendering on web and mobile - and the only new items are an accessibility gap in the mobile notice and two tests that do not exercise the behavior they claim.

Assumptions: [INFERENCE] Tests, typecheck and lint were not run in this sandbox; findings rest on reading the changed code and its call paths. The previous carried suggestion about a silently dropped malformed permission request is resolved: such a request now produces the notice on both surfaces.

Overview

Severity Count
CRITICAL 0
WARNING 0
SUGGESTION 3
Issue Details (click to expand)

SUGGESTION

File Line Issue
apps/mobile/src/components/agents/permission-unreadable-notice.tsx 18 The notice relies on accessibilityRole="alert" only, so it is not announced on iOS and has no live region on Android, unlike PermissionCard/QuestionCard which use useStatusAnnouncement plus requestA11yFocus
packages/cloud-agent-sdk/src/session-manager.test.ts 6691 The atom-identity assertion passes trivially because the mock returns the same array reference, so sameRequestIds' id comparison is never exercised
apps/mobile/src/components/agents/session-detail-content.test.ts 2377 The "keeps the composer usable" test mounts a read-only session, so the composer is disabled regardless; only the wrapper's hidden class is asserted
Files Reviewed (108 files)
  • packages/cloud-agent-sdk/src/normalizer.ts - no issues (malformed scope becomes permission.unreadable keyed by id)
  • packages/cloud-agent-sdk/src/schemas.ts - no issues (identity schema requires a non-empty id; full scope schema unchanged)
  • packages/cloud-agent-sdk/src/cloud-agent-transport.ts - no issues (unreadable event grouped with pending interactions only until connected; pending replay follows)
  • packages/cloud-agent-sdk/src/service-state.ts - no issues (one notice per id, superseded by a readable ask, cleared on reply/reconnect/reset)
  • packages/cloud-agent-sdk/src/session-manager.ts - no issues (atom mirrored with a reference-stability guard)
  • packages/cloud-agent-sdk/src/types.ts - no issues
  • packages/cloud-agent-sdk/src/index.ts - no issues
  • packages/cloud-agent-sdk/src/service-state.test.ts - no issues (tracking, dedupe, clearing, supersede cases)
  • packages/cloud-agent-sdk/src/normalizer.test.ts - no issues
  • packages/cloud-agent-sdk/src/session-manager.test.ts - 1 suggestion (identity assertion cannot fail)
  • packages/cloud-agent-sdk/src/status-copy.test.ts - no issues (mock updated)
  • apps/web/src/components/cloud-agent-next/PermissionUnreadableNotice.tsx - no issues
  • apps/web/src/components/cloud-agent-next/CloudChatPage.tsx - no issues (rendered in the interaction slot, suppressed while opening, composer gated only by question/permission)
  • apps/web/src/components/cloud-agent-next/PermissionUnreadableNotice.test.ts - no issues
  • apps/web/src/components/cloud-agent-next/terminal-tabs.test.ts - no issues (fixture field added)
  • apps/web/src/components/cloud-agent-next/transcript-phase.mounted.test.ts - no issues (fixture field added)
  • apps/mobile/src/components/agents/permission-unreadable-notice.tsx - 1 suggestion (no announcement channel)
  • apps/mobile/src/components/agents/permission-unreadable-notice.mounted.test.tsx - no issues
  • apps/mobile/src/components/agents/session-detail-content.tsx - no issues (notice outside the blocking/composer-hidden wrapper)
  • apps/mobile/src/components/agents/session-detail-content.test.ts - 1 suggestion (composer claim not covered)
  • apps/mobile/src/components/agents/session-detail-queue.test.ts - no issues (mock field added)
  • 87 apps/mobile/src/i18n/locales/*.json catalogs - no issues (both keys present at agentChat.permissionCard, valid JSON, no placeholders)

Previous review (commit b863b86)

Status: 1 Issue Found | Recommendation: Merge

Executive Summary

The replay seam now rejects stored rows the live boundary would reject, so the unreadable-time render crash flagged in the previous review is closed where a stored row enters the transcript; the only remaining item is the owner-accepted fail-closed drop of an unparsable permission scope.

Assumptions: [INFERENCE] Tests, typecheck and lint were not run in this sandbox; findings rest on reading the changed code and its call paths.

Overview

Severity Count
CRITICAL 0
WARNING 0
SUGGESTION 1
Issue Details (click to expand)

SUGGESTION

File Line Issue
packages/cloud-agent-sdk/src/schemas.ts 927 Carried from the prior review: a permission.asked whose scope fails the now-required patterns/metadata/always shape still returns null with no card and no error, so a CLI contract skew stalls the run invisibly (owner accepted fail-closed; a diagnostic or CLI-side auto-reject would remove the silent stall).
Files Reviewed (7 files)
  • packages/cloud-agent-sdk/src/snapshot-page.ts - no issues (per-row validation added at the replay seam)
  • packages/cloud-agent-sdk/src/snapshot-page.test.ts - no issues
  • packages/cloud-agent-sdk/src/session-manager.ts - no issues (prior renderer-throw finding resolved; all message.updated writes flow through the validating normalizer or replay)
  • packages/cloud-agent-sdk/src/session-manager.test.ts - no issues
  • packages/cloud-agent-sdk/src/schemas.ts - 1 carried suggestion
  • packages/cloud-agent-sdk/src/normalizer.ts - no issues (unchanged; targeted verification)
  • packages/cloud-agent-sdk/src/normalizer.test.ts - no issues (role cases now carry time)

Fix these issues in Kilo Cloud

Previous review (commit d86442f)

Status: 4 Issues Found | Recommendation: Address before merge

Executive Summary

The new completion-time fallback keeps a stored row from throwing inside the SDK atom but still hands it to transcript renderers that read time unconditionally, so the crash the PR targets remains on the replay path.

Assumptions: [INFERENCE] No pinned CLI payload was found in this repository, so whether any CLI version emits a permission scope that fails the newly required patterns/metadata/always shapes is unverified; the owner states the CLI contract declares them required. The web duplicate of the streaming check (apps/web/src/components/cloud-agent-next/types.ts:225) was not changed by this PR and still reads time.completed. Tests and typecheck were not run in this sandbox.

Overview

Severity Count
CRITICAL 0
WARNING 2
SUGGESTION 2
Issue Details (click to expand)

WARNING

File Line Issue
packages/cloud-agent-sdk/src/session-manager.ts 761 Treating an unreadable time as settled moves the row into staticMessages, but ConversationMessages.tsx:57, types.ts:225 and mobile session-transcript.ts:200 still dereference info.time, so the replayed row throws during render instead
packages/cloud-agent-sdk/src/schemas.ts 927 Required permission scope fields make a malformed request vanish with no prompt, no error, and no way for the user to answer the waiting CLI (owner states this is the intended fail-closed contract)

SUGGESTION

File Line Issue
packages/cloud-agent-sdk/src/schemas.ts 723 A live payload failing only on time drops the whole message row, and parts cannot recreate it
packages/cloud-agent-sdk/src/schemas.ts 1193 sessionSnapshotSchema still has no importers, so the replay mirror validates nothing at runtime
Files Reviewed (7 files)
  • packages/cloud-agent-sdk/src/schemas.ts - 3 issues
  • packages/cloud-agent-sdk/src/session-manager.ts - 1 issue
  • packages/cloud-agent-sdk/src/normalizer.ts - no issues
  • packages/cloud-agent-sdk/src/normalizer.test.ts - no issues (role cases now carry time, prior finding resolved)
  • packages/cloud-agent-sdk/src/session-manager.test.ts - no issues
  • packages/cloud-agent-sdk/src/fixture-runner.test.ts - no issues
  • packages/cloud-agent-sdk/src/cloud-agent-transport.test.ts - no issues

Previous review

Status: 3 Issues Found | Recommendation: Address before merge

Executive Summary

A malformed permission scope still disappears silently, leaving the CLI waiting with no consent card, and the replay mirror added by this PR still has no runtime consumer.

Assumptions: [INFERENCE] The incremental diff since 9df061243 contains only test-isolation changes in normalizer.test.ts and removal of the stale role comment in schemas.ts; no new issues were raised in it, and the two prior findings about role-test isolation and the "no reader branches on role" claim are resolved. The web renderer still dereferences info.time.created for stored rows (apps/web/src/components/cloud-agent-next/ConversationMessages.tsx, types.ts), which is outside this diff and was not commented inline. Tests and typecheck were not run in this sandbox.

Overview

Severity Count
CRITICAL 0
WARNING 1
SUGGESTION 2
Issue Details (click to expand)

WARNING

File Line Issue
packages/cloud-agent-sdk/src/schemas.ts 927 Required permission scope fields make a malformed request vanish with no prompt, no error, and no way for the user to answer the waiting CLI

SUGGESTION

File Line Issue
packages/cloud-agent-sdk/src/schemas.ts 723 A payload failing only on time drops the whole live message row, and parts cannot recreate it
packages/cloud-agent-sdk/src/schemas.ts 1193 sessionSnapshotSchema still has no importers, so the replay mirror validates nothing
Files Reviewed (7 files)
  • packages/cloud-agent-sdk/src/schemas.ts - 3 issues
  • packages/cloud-agent-sdk/src/normalizer.test.ts - no issues
  • packages/cloud-agent-sdk/src/normalizer.ts - no issues
  • packages/cloud-agent-sdk/src/session-manager.ts - no issues
  • packages/cloud-agent-sdk/src/session-manager.test.ts - no issues
  • packages/cloud-agent-sdk/src/fixture-runner.test.ts - no issues
  • packages/cloud-agent-sdk/src/cloud-agent-transport.test.ts - no issues

Fix these issues in Kilo Cloud

Previous review (commit 9df0612)

Status: 5 Issues Found | Recommendation: Address before merge

Executive Summary

The event boundary correctly tightens message and permission parsing, but a malformed permission scope now disappears silently while the CLI waits, and one bad message timestamp drops the entire transcript row.

Assumptions: [INFERENCE] No real CLI payload that omits permission scope or message time was found in the repository, so the silent-drop paths depend on a wire-shape change or a legacy sender not observed here. The SDK atoms no longer throw for a stored assistant row without time, but the web renderer still dereferences info.time.created (apps/web/src/components/cloud-agent-next/ConversationMessages.tsx:57, apps/web/src/components/cloud-agent-next/types.ts:227), so the end-to-end no-throw guarantee for that row is unproven; those lines are outside this diff and were not commented inline. Tests and typecheck were not run in this sandbox.

Overview

Severity Count
CRITICAL 0
WARNING 1
SUGGESTION 4
Issue Details (click to expand)

WARNING

File Line Issue
packages/cloud-agent-sdk/src/schemas.ts 933 Required permission scope fields make a malformed request vanish with no prompt, no error, and no way for the user to answer the waiting CLI

SUGGESTION

File Line Issue
packages/cloud-agent-sdk/src/schemas.ts 729 A payload failing only on time drops the whole message row, and parts cannot recreate it
packages/cloud-agent-sdk/src/normalizer.test.ts 110 Role tests omit time, so they pass for the wrong reason and never exercise role validation
packages/cloud-agent-sdk/src/schemas.ts 720 Comment claims no reader branches on role, but consumers treat every non-user role as assistant
packages/cloud-agent-sdk/src/schemas.ts 1199 sessionSnapshotSchema has no importers, so the replay mirror validates nothing
Files Reviewed (7 files)
  • packages/cloud-agent-sdk/src/schemas.ts - 4 issues
  • packages/cloud-agent-sdk/src/normalizer.test.ts - 1 issue
  • packages/cloud-agent-sdk/src/normalizer.ts - no issues
  • packages/cloud-agent-sdk/src/session-manager.ts - no issues
  • packages/cloud-agent-sdk/src/session-manager.test.ts - no issues
  • packages/cloud-agent-sdk/src/fixture-runner.test.ts - no issues
  • packages/cloud-agent-sdk/src/cloud-agent-transport.test.ts - no issues

Fix these issues in Kilo Cloud


Reviewed by deepseek-flash-latest · Input: 45.6K · Output: 10.4K · Cached: 654.5K

Review guidance: REVIEW.md from base branch main

@iscekic iscekic added merge:blocked:kilobot-not-ready Review bot has not recommended merge at this head merge:blocked:insufficient-e2e Verification does not cover the change merge:human Substantive functional risk requires human review. labels Oct 24, 2026
The role cases now include a valid time so each one exercises the role field.
@iscekic iscekic added merge:blocked:insufficient-e2e Verification does not cover the change merge:blocked:kilobot-not-ready Review bot has not recommended merge at this head merge:human Substantive functional risk requires human review. and removed merge:human Substantive functional risk requires human review. merge:blocked:insufficient-e2e Verification does not cover the change merge:blocked:kilobot-not-ready Review bot has not recommended merge at this head labels Oct 24, 2026
@iscekic

iscekic commented Oct 24, 2026

Copy link
Copy Markdown
Collaborator Author

bot: Re-ran the same local Chromium probe at head d86442f09c (the commits after the run above touch only tests and a comment): the transcript session renders its three rows with no client exception, the request carrying its scope renders its Patterns and Command sections, and the request whose scope fields do not parse renders no card.

@iscekic iscekic removed the merge:blocked:insufficient-e2e Verification does not cover the change label Oct 24, 2026
Comment thread packages/cloud-agent-sdk/src/session-manager.ts Outdated
… schema

Replay hands a stored page straight to the chat processor, so every replay
path now checks each row against the same message schema the live wire uses.
A row that does not satisfy it is skipped together with its parts, and the rest
of the page is still emitted.

The streaming check reads the completion stamp directly: every row that enters
storage satisfies the message contract.
@iscekic iscekic added merge:blocked:kilobot-not-ready Review bot has not recommended merge at this head merge:human Substantive functional risk requires human review. merge:blocked:insufficient-e2e Verification does not cover the change and removed merge:blocked:kilobot-not-ready Review bot has not recommended merge at this head merge:human Substantive functional risk requires human review. labels Oct 24, 2026
@iscekic

iscekic commented Oct 24, 2026

Copy link
Copy Markdown
Collaborator Author

bot: Screenshots from the local end-to-end run at the current head.

  • transcript-live-row.png: the session transcript after a conforming message event relayed over the real local relay was accepted. The seeded prompt and answer render from the session read, and the relayed row renders with its text.
  • consent-scope.png: the consent card for a conforming permission request relayed over the same path, showing the requested permission, its patterns and command, and the allow/deny actions. A request whose scope was incomplete left no card, and the card from the conforming request was unchanged.

Both captures are scoped to the conversation pane of the local web app, signed in as an owned test account on the local stack; no navigation or account chrome is included.

Session transcript with a relayed message row

Permission consent card showing the requested scope

@iscekic iscekic removed the merge:blocked:insufficient-e2e Verification does not cover the change label Oct 24, 2026
@iscekic

iscekic commented Oct 24, 2026

Copy link
Copy Markdown
Collaborator Author

Changes requested: do not drop an unreadable permission request silently.

  • With the .catch defaults removed from permissionAskedDataSchema, a permission.asked event that fails to parse makes the normalizer (normalizer.ts) return null. The user sees no card and no error, while the CLI keeps waiting for an answer.
  • Emit a distinct normalized event for this case instead of null, and render it in web and mobile as a notice: the permission request could not be read, and the user must update the app or the CLI, or answer it in the CLI.
  • Keep the stricter schema for well-formed requests.
  • Add tests for the normalizer output and for the rendered notice.

@iscekic iscekic removed the merge:human Substantive functional risk requires human review. label Oct 24, 2026
@iscekic

iscekic commented Oct 24, 2026

Copy link
Copy Markdown
Collaborator Author

bot: Added at head 9afd18244, as requested.

  • A request whose scope cannot be read now reaches the session as its own normalized event, keyed by its request id. A request that parses keeps the strict schema and the existing consent card.
  • Session state tracks these notices per request id: one clears on that request's permission.replied and on reconnect, and drops when a readable request for the same id arrives.
  • Web renders the notice above the consent-card slot; mobile renders the same notice above the blocking-card region. Both tell the reader the request could not be read and to update the app or the CLI, or answer the request in the CLI. The composer stays usable in both.
  • Tests: boundary cases for a request that cannot be read, state tracking/clearing/superseding, the manager atom, the web notice render, and the mobile notice plus a screen-level render.

Local end-to-end on this VM: a relayed request that cannot be read shows the notice with no card for it, a readable request beside it still renders its card, and answering the unreadable request clears the notice while the card stays. The same sequence was repeated on the Android dev client against the same stack.

@iscekic

iscekic commented Oct 24, 2026

Copy link
Copy Markdown
Collaborator Author

bot: Captures from the local end-to-end run at head 9afd18244, on this VM with an owned test account.

  • Web conversation pane: the notice and the consent card shown together while both requests are waiting.
  • Web conversation pane after the notice's request is answered: the notice is gone and the card remains.
  • Android dev client on the emulator, same session: the notice above the consent card, composer editable.

Web conversation pane showing the notice and the consent card

Web conversation pane after the request is answered, card remaining

Android dev client showing the notice and the consent card

Comment thread apps/mobile/src/components/agents/permission-unreadable-notice.tsx
Comment thread packages/cloud-agent-sdk/src/session-manager.test.ts
Comment thread apps/mobile/src/components/agents/session-detail-content.test.ts
@iscekic iscekic added merge:blocked:kilobot-not-ready Review bot has not recommended merge at this head merge:blocked:insufficient-e2e Verification does not cover the change merge:human Substantive functional risk requires human review. and removed merge:blocked:kilobot-not-ready Review bot has not recommended merge at this head labels Oct 25, 2026
@iscekic iscekic removed merge:blocked:insufficient-e2e Verification does not cover the change merge:human Substantive functional risk requires human review. labels Oct 25, 2026
const announcement = t('agentChat.permissionCard.unreadableBody');

useEffect(() => {
announceForA11y(announcement);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SUGGESTION: Android now announces this notice twice

The notice sets accessibilityLiveRegion="polite" (line 30) and also announces imperatively here, on every platform. On Android both channels fire: TalkBack speaks the polite live-region text when the node is added and then speaks the same text again from announceForAccessibility. The repository's shared contract avoids exactly this by using one channel per platform - status-announcement.ts:6-11 documents "One channel per platform prevents double announcements" and useStatusAnnouncement only calls announceForA11y when Platform.OS === 'ios' (status-announcement.ts:50). Gate this call the same way (keep the live region for Android, announce only on iOS).

@iscekic iscekic added merge:blocked:insufficient-e2e Verification does not cover the change merge:blocked:kilobot-not-ready Review bot has not recommended merge at this head merge:human Substantive functional risk requires human review. and removed merge:blocked:kilobot-not-ready Review bot has not recommended merge at this head labels Oct 25, 2026

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

drain Work item from the findings drain janitor:trust Janitor finding: trust boundary hardening merge:blocked:insufficient-e2e Verification does not cover the change merge:human Substantive functional risk requires human review.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant