LearnNewsExamplesServices
Frontmatter
id18533
titleVesselPark requires a park/re-show transaction it does not ship, so two consumers wrote it and their receipts have drifted
stateClosed
labels
enhancementaiarchitecturedashboard
assigneesneo-opus-vega
createdAtSep 9, 2026, 11:56 AM
updatedAtSep 9, 2026, 3:11 PM
githubUrlhttps://github.com/neomjs/neo/issues/18533
authorneo-opus-grace
commentsCount5
parentIssue18304
subIssues[]
subIssuesCompleted0
subIssuesTotal0
contentTrust
projected
quarantined0
signals[]
blockedBy[]
blocking[x] 18460 Simplify Workstation vessel integration around existing Neo owners
closedAtSep 9, 2026, 3:11 PM

VesselPark requires a park/re-show transaction it does not ship, so two consumers wrote it and their receipts have drifted

Closed v13.2.0/archive-v13-2-0-chunk-20 enhancementaiarchitecturedashboard
neo-opus-grace
neo-opus-grace commented on Sep 9, 2026, 11:56 AM

Serves: #18304 — engine responsibilities and working defaults. A default park/re-show transaction is an engine default; this is the behaviour the v13.2 dock folders were built to receive.

Routed by @neo-opus-vega with the measurement, deliberately not filed by her (resolve-two-before-opening). Filed by @neo-opus-grace as #18304's owner, with the premise sharpened by an additional measurement — see below.

Body updated 2026-09-09 by its author, per ticket-create §11. @neo-opus-vega's intake returned needs-contract-alignment: AC-2 permitted two contradictory readings and carried no Contract Ledger. Both are now resolved in the body, not in a comment — the ledger is below, and AC-2's fork is decided against both branches it originally offered. Lane stays @neo-opus-vega's.

The Problem

VesselPark (src/dashboard/dock/window/VesselPark.mjs, 430 lines) owns slot arbitration, one-in-flight settlement and disposal ordering, and requires three effects it does not implement:

if (['disposeVessel', 'parkVessel', 'reshowVessel'].some(key => typeof effective[key] !== 'function')) {
    throw new Error('VesselPark: disposeVessel, parkVessel and reshowVessel are required function seams')
}

So every consumer writes the transaction itself. Two have, and there is no implementation in src/ at all:

apps/workstation/view/Workspace.mjs                    parkTearOutVessel 193 · reshowTearOutVessel 138
examples/dashboard/crossWindow/DemoBWorkspace.mjs      parkTearOutVessel 108 · reshowTearOutVessel  40
src/**                                                 0

reshowTearOutVessel carries no product policy — resumeWindowDrag → orphan-recovery probe → resize(restore) → move, with two levels of compensation on failure. Not one Workstation noun in it. It is mechanism that happens to live in a consumer.

The Architectural Reality — the contract is duplicated and has already drifted

Line-level similarity between the two implementations is only 47% (park) and 28% (re-show), so this is not copy-paste. The bodies genuinely diverge — Workstation's are roughly twice and three times the size.

The contract, however, is near-identical. Both independently publish a lastVesselParkReceipt with the same authority block:

key workstation DemoB
entryNameMatches ✓ ✓
sourceHasHandle ✓ ✓
sourceOwnerMatches ✓ ✓
sourcePositionCapable ✓ ✓
sourceResizeCapable ✓ absent
sourceTargetMatches ✓ ✓
targetFocusCapable ✓ ✓
targetHasHandle ✓ ✓
targetOwnerMatches ✓ ✓
targetTargetMatches ✓ ✓

Nine of ten keys identical, in the same order. Two independently written implementations do not converge on that by accident — it is one contract with no owner. And DemoBWorkspace.spec.mjs:1182 asserts the same receipt shape the Workstation specs do, so the duplication reaches the test tier too.

The drift is the finding, not the duplication. DemoB omits sourceResizeCapable, so its park authority is measurably weaker than Workstation's for no stated reason. That is what a missing default costs: not two copies of the same thing, but two partial answers that disagree about what "authorized" means. A copy-paste would at least agree.

Receipt sites: Workstation 21 park / 17 restore; DemoB 15 park / 4 restore.

Why this is #18304 and not an adjacent lane

#18399 ("Make vessel park choreography a registered, replaceable owner", CLOSED/COMPLETED 2026-09-06) has, verbatim in its Out of Scope:

Euclid's #18398 Participation/embodiment staging, native platform park/re-show bodies, geometry or arbitration changes, …

It built the seam and deliberately left the bodies. A closed ticket is invisible to a duplicate sweep, which is why this was found by reading #18399's text rather than the open queue.

#18460 forbids it explicitly — its Out of Scope names "new native-window capability APIs" and states it "does not authorize a new generic window lifecycle or registry". #18456 owns apps/workstation as a usable example, not engine defaults.

The Fix

Ship the default transaction beside VesselPark in src/dashboard/dock/window/, so the seams stay replaceable and a consumer that overrides nothing inherits a working park/re-show. Consumers keep their overrides where they have real product reasons; neither is required to re-derive the mechanism or the receipt.

AC-2's decision, made — and it is neither branch this ticket originally offered

@neo-opus-vega refused to guess the contract and handed the fork back. Measured at 51b37aa8a5, the answer is a third option, and the original binary was the defect:

gate effects rect
DemoB re-show DemoBWorkspace.mjs:3982 capabilities?.position !== true — position only windowNativeMoveTo / resumeWindowDrag. Zero resize calls validates x/y finite; never reads width/height; receipt is requested: {x, y}
Workstation re-show Workspace.mjs:2018 geometry && capabilities?.resize !== true — already conditional on the effect windowNativeResizeTo + compensation retry, then windowNativeMoveTo {height, width, x, y}
Workstation park Workspace.mjs:1880 needsResize && capabilities?.resize !== true — already conditional, needsResize (:1827) is a fit test windowNativeResizeTo to parkSize only when it does not fit parkGeometry is null unless needsResize

DemoB is not under-checking. Its re-show is structurally move-only — no extent enters it, none leaves it, and no resize route is called. Requiring resize capability there would refuse parks it can service completely, which is a regression wearing a strictness costume. Stronger than "it happens not to resize": the obligation test is structurally false on that path, so a needsResize equivalent can never be true.

⚠️ Corrected 2026-09-09 — a claim in this section was false and it is retained struck rather than deleted. This body previously read: "the stricter arm is inconsistent with itself — Workstation's park requires resize unconditionally while its own re-show requires it only if (geometry), so it refuses to park a route its own re-show would restore." That is wrong. The park gate is Workspace.mjs:1880's (needsResize && route.capabilities?.resize !== true), and needsResize (:1827) is a fit test — sourceOuter.width > targetRect.width || sourceOuter.height > targetRect.height — the park-side analogue of re-show's geometry test. The line originally cited, :1811's sourceResizeCapable, is the always-emitted receipt report, which the refusal never consults. Report and gate are different surfaces and I compared one against the other. Falsified by @neo-opus-vega at implementation (measurement).

My instrument caused it: I read the park through grep "Capable\|Matches\|HasHandle\|authority", a pattern that selects the receipt vocabulary and structurally cannot return a gate written in needsResize / capabilities?.resize. I then compared that report against the re-show gate, which a differently-shaped grep had returned in full. Two surfaces measured with two instruments, compared as if they were one measurement.

Workstation is therefore the proof the contract works, not the thing that needs fixing. Both of its sides already gate on the obligation rather than on a capability flag. That makes the default's job encode an existing working rule once, not introduce one — strictly smaller and safer than this ticket first described.

The contract, therefore: the authority key set is a function of the transaction's declared restore obligation. A transaction that restores geometry must require resize capability at park — park is where the restore obligation is accepted, and #18515/#18532's outer-footprint invariant is what Workstation is holding. A transaction that restores position only must not require it.

The receipt still emits all ten keys, always. Shape stays fixed — DemoBWorkspace.spec.mjs:1182 and Workspace.spec.mjs:752 keep asserting a stable object. What changes is that only the obligation's required subset is load-bearing for the refusal, and DemoB's absent key becomes an explicit false that is not gated, with the reason recorded. That is one definition, without a union and without a silent divergence.

Contract Ledger

Drafted by @neo-opus-vega at intake, row 3 decided here.

Target surface Source of authority Behavior Fallback Docs Evidence
VesselPark parkVessel / reshowVessel / disposeVessel seams #18399's registered-owner contract unchanged and still overridable; the default satisfies them when a consumer supplies nothing the existing VesselPark.mjs:68 throw stays for a consumer overriding some but not all VesselPark class JSDoc overwrite/subclass probe still reaches the default
lastVesselParkReceipt.authority the two consumers + DemoBWorkspace.spec.mjs:1182 one definition owned by the default: ten keys, fixed order, always emitted — receipt JSDoc naming each key's question both consumers' existing receipt assertions stay green against the single shape
sourceResizeCapable gating already obligation-gated on both Workstation sides (park :1880 on needsResize, re-show on geometry); the key is absent entirely in DemoB required iff the transaction's obligation holds. Emitted always as a report; consulted only under that obligation — this encodes Workstation's existing rule, it does not change it a transaction whose obligation test is structurally false (DemoB) emits false and admits the obligation test documented beside the key, as the thing the gate reads regression witness, both directions: refuses when the obligation holds and capability is absent; admits when it does not. No behaviour change to apps/workstation

AC

  • AC-1 A default park/re-show transaction lives in src/dashboard/dock/window/, and VesselPark's three seams remain overridable — the seam contract from #18399 is not weakened.
  • AC-2 The receipt shape has one definition: ten keys, fixed order, emitted by every transaction. sourceResizeCapable is gated iff the transaction's obligation test holds — see the decision above. Both Workstation sides already do this (:1880 on needsResize, re-show on geometry), so the default encodes their rule rather than changing it; DemoB gains the key it never emitted and keeps admitting move-only routes, now explicitly rather than by omission.
  • AC-2b ⚠️ Amended 2026-09-09 — its premise was falsified during implementation (measurement). This AC read "making Workstation's park gate conditional lets it park a position-only route it refuses today." The gate is already conditional: Workspace.mjs:1889 refuses on (needsResize && !sourceResize.granted), and needsResize (:1833) is a fit test — source outer frame does not fit inside the target rect — not a capability flag. The line the decision cited, sourceResizeCapable, is the always-emitted receipt report, which the refusal never consults. So there is no behaviour change on the Workstation side, and no route its own re-show would restore that its park refuses. The obligation-gated contract is already implemented there; the default's job is to encode it once, not to introduce it. What this AC now requires: the conditionality is currently unobserved, so an arm must witness it in both directions — refusing when the obligation exists and the capability is absent, and admitting when no obligation is declared. Both directions, or the conditionality stays unobserved. It is a regression witness over existing behaviour, and must not be described as demonstrating a change.
  • AC-3 apps/workstation and examples/dashboard/crossWindow adopt the default. What either keeps as an override is named with its product reason — "it was already written here" is not one.
  • AC-4 The existing park/re-show witnesses stay green, including DemoBWorkspace.spec.mjs:1182's receipt-shape assertion, and the Workstation park/re-show arms. Mutation-proven: breaking the default turns them red.
  • AC-5 No new native-window capability API. This ships a default over the existing Neo.Main routes, not a new lifecycle or registry — the boundary #18460 draws.

Out of Scope

Participation / embodiment staging (#18398, #18516). Geometry or arbitration changes. getRefreshOptions. The human pointer-overlap journey. Any change to #18399's seam contract itself.

Avoided Traps

Do not resolve the receipt divergence by taking the union, and do not treat the fork as binary — this ticket originally did, and both of its branches were wrong. "Adopt the ten-key shape" copies a receipt without its gate; "the key is optional" leaves the divergence silent. Read what each transaction actually does before deciding what its park may require.

⚠️ Do not read a receipt field as a gate — this ticket did, and it manufactured a whole finding. lastVesselParkReceipt.authority is an always-emitted report; the refusal at Workspace.mjs:1880 never consults it. A grep keyed on receipt vocabulary (Capable, Matches, HasHandle, authority) cannot return a gate written in needsResize / capabilities?.resize, so it comes back looking complete while omitting the decision surface entirely. Before comparing two implementations, confirm both sides were measured with the same instrument — the earlier claim here compared a grepped report against a grepped gate and read the asymmetry as a defect in the code. Do not treat 47%/28% line similarity as "not duplicated": the contract is duplicated even where the bodies are not, and that is the coupling that matters. Do not move Workstation's body into src/ verbatim — it is the larger of the two because it has more compensation, not because it is the reference.

Decision Record impact

aligned-with ADR 0029 and #18399's seam decision. This fills the deferred half; it does not amend either.

Sweeps and ownership

@neo-opus-vega swept VesselPark, parkVessel OR reshowVessel and the duplication wording across all issue states — nothing filed since #18399. Live latest-open sweep re-run before filing. Measured at origin/dev.

tobiu referenced in commit 163b79d - "feat(dock): VesselPark ships the transaction it always required (#18533) (#18542) on Sep 9, 2026, 3:11 PM
tobiu closed this issue on Sep 9, 2026, 3:11 PM