LearnNewsExamplesServices
Frontmatter
id17688
titleThe claim resolver's decisions are only observable as a post-hoc reconstruction
stateClosed
labels
bugdeveloper-experiencetestingcore
assigneesneo-opus-grace
createdAtAug 24, 2026, 10:27 AM
updatedAtAug 24, 2026, 6:40 PM
githubUrlhttps://github.com/neomjs/neo/issues/17688
authorneo-opus-grace
commentsCount0
parentIssuenull
subIssues[]
subIssuesCompleted0
subIssuesTotal0
contentTrust
projected
quarantined0
signals[]
blockedBy[]
blocking[]
closedAtAug 24, 2026, 6:40 PM

The claim resolver's decisions are only observable as a post-hoc reconstruction

Closed Backlog/active-chunk-19 bugdeveloper-experiencetestingcore
neo-opus-grace
neo-opus-grace commented on Aug 24, 2026, 10:27 AM

Context

Leaf of #17578. That ticket spent five refuted hypotheses on DragCoordinator.resolveClaimedTarget, and a sixth (mine — an absent sort group) could not be settled by reading at all. The reason is not the difficulty of the bug: it is that the resolver's decisions were never observable.

The only evidence available was candidateDiagnostics, built by the workspace inside its own bail branch (if (!remoteSnapshot.ready)), iterating the sort group and calling zone.acceptsRemoteDrag() itself. That is a reconstruction of what the answer would have been, computed once readiness has already failed.

The Problem

The reconstruction cannot answer three questions that decide the search:

  1. Did the collection loop run at all? resolveClaimedTarget returns at a !group guard above the loop. #17578 concluded the loop had run because pointerGestureToken was set — but that token is minted at DragCoordinator.mjs:688, one line above the resolver call at :689. Loop-never-iterated and token-present are consistent, so that branch was closed on a witness that could not carry it.
  2. Which conjunct failed? The gate is compound and && short-circuits:
       inner?.intersects({…}) && zone.acceptsRemoteDrag(screenX - inner.x, screenY - inner.y)
    A nullish inner means acceptsRemoteDrag is never called. candidateDiagnostics records only that second conjunct, so "the zone refused" and "the zone was never asked" are the same observation.
  3. Was the source correctly skipped? The reconstruction reported sameAsSource: false for both windows, including the source — it does not model the resolver's own skip rule.

The cost is measured, not hypothetical: six hypotheses, at least two of them formed and published against a reconstruction that structurally could not refute them.

The Fix

resolveClaimedTarget records what it actually did, on a bounded ring surfaced through the route that already reaches a spec (toJSONRuntimeService.getDragState):

  • each conjunct separatelyinnerResolved, intersects, accepts — so a refusal is distinguishable from a zone never asked;
  • skipped candidates with their reason (source-or-excluded, no-stable-identity);
  • groupSize and a distinct group-absent outcome, so the early return is visible rather than indistinguishable from an ordinary no-claim;
  • bounded to 40 entries — one gesture emits one per pointer move, so the ring covers the tail of a single drag and cannot accumulate across a session.

Not a console.log. Neo.manager.DragCoordinator runs in the App Worker and neither fixtures.mjs nor the spec forwards browser console to Playwright's stdout, so a probe there is structurally invisible — #17578 documents this, and I re-derived the same negative before re-reading it.

Contract Ledger

Target surface Source of authority Proposed behavior Fallback / edge case Docs Evidence
DragCoordinator.claimTrace the resolver's own decisions bounded ring of per-resolution observations, newest last ring never exceeds claimTraceLimit; a gesture cannot grow it without bound member JSDoc unit arm over claimTraceLimit + 25 moves
Per-candidate record the compound gate's two conjuncts innerResolved / intersects / accepts recorded independently a zone never asked reports accepts: null, never false method JSDoc unit arm where the zone refuses and all three are asserted
Early return !group guard above the loop its own outcome: 'group-absent' with groupSize: null never collapses into no-claim inline comment at the guard unit arm asserting the distinct outcome
Debug route toJSONgetDragState the trace rides the existing drag-state payload no new transport, no new tool the cross-window spec prints it on failure

Acceptance Criteria

  • A refusing zone and a zone that was never asked produce different records — asserted on the returned trace, not on a log line.
  • An absent sort group is recorded as its own outcome with groupSize: null, and never as an ordinary no-claim.
  • The ring is bounded: driving more moves than claimTraceLimit leaves exactly claimTraceLimit entries.
  • The trace reaches a spec through the existing drag-state route, and the cross-window spec prints it beside the workspace payload when the gesture fails.
  • Red-proof: collapsing group-absent into no-claim turns exactly one arm red, so the distinction is load-bearing rather than decorative.

Out of Scope

  • Fixing the cross-window drag defect itself — that stays on #17578, whose five ACs all describe the fix and which this leaf deliberately does not close.
  • The vessel-conversion admission seam (dockVesselConversionIn), which the trace has now narrowed to but which is a separate measurement.
  • Any new transport, MCP tool, or Neural Link surface.
tobiu referenced in commit 5dfe942 - "feat(manager): the claim resolver records its own decisions (#17688) (#17689) on Aug 24, 2026, 6:40 PM
tobiu closed this issue on Aug 24, 2026, 6:40 PM