LearnNewsExamplesServices
Frontmatter
id14983
titleReject dock candidates whose split axis contradicts direction
stateClosed
labels
bugaitestingcore
assigneesneo-gpt
createdAtJul 10, 2026, 9:23 PM
updatedAtJul 10, 2026, 11:59 PM
githubUrlhttps://github.com/neomjs/neo/issues/14983
authorneo-gpt
commentsCount0
parentIssuenull
subIssues[]
subIssuesCompleted0
subIssuesTotal0
contentTrust
projected
quarantined0
signals[]
blockedBy[]
blocking[]
closedAtJul 10, 2026, 11:59 PM

Reject dock candidates whose split axis contradicts direction

Closed Backlog/active-chunk-5 bugaitestingcore
neo-gpt
neo-gpt commented on Jul 10, 2026, 9:23 PM

Context

PR #14974 review cycle 2 hardened neo.harness.dockCandidates.v1 after the original fail-closed review. The exact-head re-review at 2dddcd5e8e909ceb8ebe19e0e6c35a25bcf81ed2 independently confirmed the real producer path and all binding gesture paths, then ran one malformed-payload falsifier against the new validator.

The falsifier mutates an otherwise valid top candidate to {kind: 'split-before', orientation: 'horizontal'}. isValidCandidateSet() returns true, even though a top/bottom sibling insertion is valid only on the vertical axis. The real producer does not emit this mismatch, so this is a narrow contract-hardening successor rather than a blocker for PR #14974.

The Problem

crossKindMatchesPosition(position, kind) verifies leading/trailing direction but does not receive or validate placement.orientation. Consequently:

  • top/bottom split-before|split-after candidates can claim horizontal;
  • left/right split-before|split-after candidates can claim vertical;
  • the indicator can visually advertise one direction while previewToOperation() commits a perpendicular split.

That contradicts the contract's fail-closed promise for malformed or lying menus. The bug is not reachable through the current DockPreviewProducer.produceCandidates() implementation, which derives kind and orientation together, but it remains reachable through any malformed consumed payload.

Empirical reproducer at PR #14974 head:

const top = set.cross.find(candidate => candidate.position === 'top');
top.preview.placement = {kind: 'split-before', orientation: 'horizontal'};
isValidCandidateSet(set); // true; expected false

The Architectural Reality

The ownership is already correct:

  • src/dashboard/dockPreviewContract.mjs owns structural validation for neo.harness.dockCandidates.v1.
  • src/dashboard/DockPreviewProducer.mjs owns grammar-correct candidate production and needs no behavioral change.
  • previewToOperation() remains the semantic conversion path and must not compensate for an invalid candidate set.
  • DockDropIndicators remains a commit-free renderer/selector.

The validator should express the same axis rule that the producer already implements: top/bottom sibling insertion requires vertical; left/right sibling insertion requires horizontal.

The Fix

Extend the candidate-set validation helper in src/dashboard/dockPreviewContract.mjs so split candidates validate direction, kind, and orientation as one semantic tuple.

Add focused negative tests beside the existing malformed-menu probes in test/playwright/unit/dashboard/DockPreviewProducer.spec.mjs. Preserve the producer, renderer, reducer, and public schema version.

Contract Ledger

Target Surface Source of Authority Proposed Behavior Fallback Docs Evidence
isValidCandidateSet() split-candidate tuple Dock choreography §06 grammar; ADR 0029 §2.3; #14959 Contract Ledger Accept top/bottom split-* only with vertical; accept left/right split-* only with horizontal Contradictory tuple returns false; indicator layer clears Tighten helper JSDoc to name the axis invariant Four negative axis probes plus producer-emission positive control

Acceptance Criteria

  • Top/bottom split-before|split-after candidates are rejected unless placement.orientation === 'vertical'.
  • Left/right split-before|split-after candidates are rejected unless placement.orientation === 'horizontal'.
  • Existing valid producer emissions continue to pass unchanged.
  • Focused unit coverage includes every contradictory direction/axis class.
  • No producer, renderer, reducer, schema-version, or persisted-model change.

Out of Scope

Changing indicator geometry, the real pointer journey, Escape delivery (#14980), or the previewToOperation() mapping.

Decision Record impact

aligned-with ADR 0029 — this closes a fail-closed validation edge inside the existing presentation/runtime contract.

Related

#14959 · PR #14974 · #14980 · parent #13158.

Live latest-open sweep: checked the latest 20 open issues immediately before creation on 2026-07-10; no equivalent found. A2A herd-window sweep: checked the latest 30 all-state messages; no competing claim or in-flight ticket on this validator edge.

Origin Session ID: 019f484c-662f-7f31-969a-cbde373efd4a

Retrieval Hint: "dockCandidates split orientation direction tuple fail closed top horizontal"

tobiu referenced in commit 293bebe - "fix(dashboard): validate dock candidate split axes (#14983) (#14990)" on Jul 10, 2026, 11:59 PM
tobiu closed this issue on Jul 10, 2026, 11:59 PM