LearnNewsExamplesServices
Frontmatter
id16700
titleCross-zone executor: the post-dwell activeCandidate read is unguarded
stateClosed
labels
bugai
assigneesneo-kimi-iris
createdAtAug 8, 2026, 3:43 PM
updatedAtAug 8, 2026, 5:03 PM
githubUrlhttps://github.com/neomjs/neo/issues/16700
authorneo-kimi-iris
commentsCount0
parentIssue16620
subIssues[]
subIssuesCompleted0
subIssuesTotal0
contentTrust
projected
quarantined0
signals[]
blockedBy[]
blocking[]
closedAtAug 8, 2026, 5:03 PM

Cross-zone executor: the post-dwell activeCandidate read is unguarded

neo-kimi-iris
neo-kimi-iris commented on Aug 8, 2026, 3:43 PM

Context

Leg 1 of #16620 (ambient gesture arming/preview failures under suite load), split per the pull-request workflow's one-PR-one-leaf rule: the parent stays open for the battery/classification legs (its AC-2/3/4). The parent's Fix step 1 is this leaf: convert the unguarded post-dwell read into a gate-named executor failure — both the robustness repair and the attribution instrument the battery needs.

Live latest-open sweep: checked latest 20 open issues at 2026-08-08 ~13:44Z; no equivalent (parent #16620 only). A2A herd-window sweep: mailbox current through 13:27Z; no competing claim.

The Problem

apps/workstation/view/Workspace.mjs executeCrossZoneShowcaseStep verifies the active candidate (waitUntil on indicators.activeCandidate?.preview?.previewId), waits dwellDelay, then reads indicators.activeCandidate.preview unguarded. A candidate lost during the dwell — the human-pause-dwell claim-TTL-expiry family pinned on #16497 (600-900ms dwells outlive the gesture claim's arbitration TTL) — turns the read into an unattributed Cannot read properties of null (reading 'preview') that kills the gesture chain mid-flight. Under suite load this face cost ~10 attribution runs on the #16499 table alone (the parent's origin evidence).

The Architectural Reality

  • The executor's own catch converts any throw into the returned {applied: false, errors: [message]} receipt — so a thrown, gate-named error IS the receipts channel; no new error path is added.
  • The re-verify discriminates the two loss modes in the message: active=null (expiry) vs active='<other-id>' (preemption), plus dwell position — the classification data leg 2 needs.
  • The file's other activeCandidate reads are all optional-chained (verified by grep at dev 5792ec4d46); this is the only unguarded read of the class in the file.

The Fix

Optional-chain the read and throw a gate-named executor error on loss: gate=dwell-reverify, expected vs live candidate id, dwell index/position. Witness: prototype-call spec (the file's established idiom) whose fake host retires the active candidate inside the dwell — red against the unpatched read (the unattributed TypeError message), green with the guard.

Acceptance Criteria

  • The post-dwell read is re-verified; a loss returns an attributed gate=dwell-reverify step receipt (no unattributed null-read remains reachable from this seam).
  • Unit witness red-proven against the unpatched read and green with the guard; full apps/workstation/Workspace spec file green.

Out of Scope

  • The parent's suite battery (≥5 runs), face classification, and any product-side repair of residual expiry paths — legs 2-4 live on #16620.
  • The silent-degradation note from the #16688 review (diverged-shell fallback) — already owned by the #16500 oracle leg.

Avoided Traps

  • Repairing the product behavior in this leg (e.g. re-arming the claim mid-dwell): rejected — classification first, per the parent; the guard is the instrument, not the verdict.
  • A new error channel: rejected — the executor's existing catch-to-receipt contract is the channel.

Decision Record impact

none — consumes the executor's existing receipt contract; no ADR surface.

Related

Parent: #16620 (legs 2-4 live there) · Refs #16497 (claim-TTL family mechanism) · Refs #16500 (oracle instrument) · Refs #16499 (origin suite table)

Origin Session ID: f62a4ece-0cb8-43eb-9311-e684aaf9cbc5

Retrieval Hint: query_raw_memories("cross-zone dwell activeCandidate claim TTL expiry gate-named receipt")

Structure-map gate: N/A — app-view + spec surface, no ai//Agent OS/MCP/skills touch.