Files
ARCADIA-Studio/harness/release-activity-allocation-drag-contract-v2-20260911.md
T

123 lines
21 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Allocation drag contract v2 — 2026-09-11
Codex-956atf `/root/capella_process_gap`. Implementation-ready proposal for root ratification of [issue 3](http://217.177.34.108:3000/sorokin.oleg.edu/ARCADIA-Studio/issues/3). The [original design](release-activity-allocation-drag-design-20260911.md) and [independent review](release-activity-allocation-drag-review-20260911.md) remain immutable. Root accepted all five review corrections; this document resolves them without claiming implementation, issue closure or full-goal completion.
Source inspected at **ff0175488ac57a92d401b00a0e4542582a82d8e9** on `codex/behavior-authoring-20260910`. [Peer issue 4 update 446](http://217.177.34.108:3000/sorokin.oleg.edu/ARCADIA-Studio/issues/4#issuecomment-446) now transfers both Allocation and RoleAssignment identities to the first child; that remains peer-owned. This task creates only this report. No production/test/Git/index/compiler/native/Linux/WSL/VPN changes or agents.
## Decisions from the review
| Finding | Binding v2 resolution | Revisit criterion |
| --- | --- | --- |
| 1. Same-performer role-only drag could create Allocation | Ordinary drop onto the unique current effective performer is pure layout, for leaves and uniformly assigned composites, regardless of direct/role provenance. Explicit Assign is a separate operation. Unsupported role exits refuse without moving. | Add a deliberate role/assignment editing workflow with an exact preview; never reinterpret ordinary arrangement. |
| 2. Deleted-ID checks miss surviving-record changes | Compare the entire prepared candidate against an expected Project containing only intended allocation edits and permitted presentation changes. Constraint cleanup, trace/artifact edits and cascading mappings fail this gate. | A specific dependency-repair workflow defines and displays its additional edits. |
| 3. Press selection can auto-apply dirty properties | New clean-specific work selection and gesture admission; no `SelectNode`, `select()`, `apply()` or legacy MoveNode fallthrough. A dirty ordinary click on eligible work preserves current selection/forms and explains refusal. | Any broader selection redesign must explicitly preserve draft identity and may not silently apply it. |
| 4. Mixed composite geometry is not solved by translation/reflow | Distinct semantic work closure and active-diagram placement closure; first slice translates a coherent uniform subtree as one block. Distributed/mixed convergence and unreadable candidates refuse. No invented placements or automatic sibling packing. | A bounded placement algorithm preserves identity, shows its exact changes and passes the mixed P/x/y counterexample. |
| 5. Target rectangles and semantic preview can disagree | Frozen, visibly outlined target hit regions with semantic ancestor precedence and explicit occlusion. Candidate Project uses its own ViewIndex; cache includes destination/semantic plan identity. | Relax only with evidence that displayed targets, hit resolution, candidate grouping and final state remain identical. |
The primary choice remains ordinary context-sensitive work dragging in Allocation views. A dedicated assignment tool is easier to isolate but does not fix the user's natural gesture. Adopt ordinary dragging with a transient Arrange only alternative. Likewise, immediate mixed-subtree packing would broaden this slice; a specific visible refusal is preferable until a real placement algorithm exists. These are staged implementation boundaries, not reduced OA/SA requirements.
## Admission, selection and lifetime
Eligible pointer bodies are Activity/Function placements in `ProjectionKind::Allocation`. Ports, edges and controls retain their own gesture priority. Every eligible work-body press enters this contract, including stationary clicks and Arrange only; refusal must never fall back to legacy movement.
A **clean owner** means the current Diagram page and active allocation diagram, accepted document, no dirty property/source/behavior draft, no architecture creation/connection session, and no modal/menu/chooser/file/save/hosted operation owning input. App supplies a render-time capability but rechecks it when receiving press, preview and release events.
- **Clean stationary press:** select the represented work and exact placement through `select_work_clean`. It checks the capability, sets selection, and may rebuild a clean property display. It does not call any applying selection helper, change source/history/revision, run Fit or invalidate this drag's lifetime.
- **Dirty stationary press or attempted drag:** preserve selection, draft contents, cursor/focus and source exactly. Do not arm movement; show “Finish or cancel the current edit before selecting or moving work.” Clicking the already selected work also preserves its dirty form. Within the Allocation canvas, intercept dirty left-button object presses before legacy node/port selection, so a nearby port hit cannot auto-apply the same draft. This is a local pointer-selection policy; explorer/text navigation is outside this change. An input-owning overlay keeps its own focus.
- **Clean drag:** capture a fresh gesture token, document instance/revision, diagram/projection, input epoch, exact canvas bounds/pan/zoom, work/placement IDs, original scope/provenance and frozen hit geometry. App registers the token on the clean press event. Selection alone must not retire it.
- Owner/document/revision/projection, source/property/behavior dirtiness, modal/busy ownership, tool, bounds, camera, window focus loss or Escape retires the token. Even a Fit/tool action that returns to the same numeric values retires it. No stale packet becomes valid again when a modal closes or Undo restores prior values.
Retain ordered native pointer positions and the existing 3-screen-pixel threshold. A fast press→motion→release batch must work without waiting for a Task/round-trip acknowledgement: App processes the stamped press before its stamped release and verifies their common token. Outside/hidden release cancels. There is at most one final command per token.
**Arrange only:** a visible “Arrange next drag” control arms one gesture and displays an armed indicator. Keyboard activation is supported; Escape, cancellation, completion or owner/lifetime change clears it. Capture the mode at press and consume it then; it cannot remain sticky after a gesture or switch interpretation midway through one. It changes presentation only.
## Scope and semantic destination
For selected work `w`, let **S** contain w and all recursive same-kind semantic work children; **L** is the nonempty subset with no such child. Traverse accepted semantic parents, not rendered groups, and include folded/unplaced leaves. Detect stale/missing/cyclic scope before planning. Let **Q** be every existing placement in the active diagram whose element is in S, including hidden placements. The selected placement belongs to Q. Do not create placements, move other diagrams' nodes, or include unrelated work merely because it is geometrically enclosed.
Classify each leaf by exact direct Allocation identity (zero/one), RoleAssignment/RoleRealization references, and current `effective_performer`. The supported uniform cases are:
- **Assigned(P):** every leaf has the same unique effective performer P, including role-only and mixed direct/role provenance.
- **Unassigned:** every leaf has neither direct Allocation nor RoleAssignment.
- Otherwise **MixedOrUnresolved**. This is a valid modeling state where admitted by core. Composite group dragging, including Arrange only, is a named `MixedLayoutRequired` refusal in this slice; select a specific leaf instead. A leaf with unresolved role responsibility may still be arranged or explicitly dropped onto B: B is chosen by the user, not inferred from its roles. Its outward role exit remains refused below.
Ordinary destination means `Performer(P)` or `OutsidePerformers`; Arrange only means `LayoutOnly` independent of the pointed performer.
| Operation | Intended semantic change |
| --- | --- |
| LayoutOnly, or Assigned(P) dropped onto P | None. In particular, create no direct Allocation for a role-only leaf. |
| Unassigned, Assigned(A) dropped onto a different B, or a single leaf without a unique performer dropped onto B | For each leaf: edit an existing Allocation's source only, preserving complete header/identity/target; otherwise create one B → leaf relation with an identity allocated once per gesture/leaf. No parent Allocation. Preserve all role relationships; existing advisory role warnings stay advisory. If roles were unresolved, label the explicit assignment and retained role responsibility. |
| Assigned(A) dropped outside; every leaf has one direct allocation and removing them leaves no role responsibility | Delete exactly those Allocation IDs. Keep work decomposition and work/port identities. Label “Remove assignment — decomposition retained.” |
| Outside drop with role-only responsibility, or removal revealing retained/ambiguous role responsibility | `RoleExitRequired`: unchanged model **and placement**. Name the retained role/performer. If direct A would reveal B, say “Removing direct assignment would place work in B through its role”; do not call it Unallocated. |
| Unassigned dropped outside | Layout only; no relation added/deleted. |
Explicitly assigning direct Allocation to an already effective performer remains an existing editor/API action, not ordinary dragging. For a semantic child, “outside performer” does not promise source-root or diagram-root placement: its work parent remains intact, and resulting ancestor groups must be shown.
## Stable target resolution
Freeze **visible accepted** performer rectangles, header/body bounds, semantic performer ancestry, card occluders and viewport clip at press. During preview render the active frozen target outline above the ghost, distinctly from the candidate's enclosure; retain all candidate-group feedback. Hidden performers are not targets. A collapsed performer is targetable only by its visible header; successful semantic assignment may unfold that target placement in the same Batch.
Use the ordered release pointer in the frozen transform:
1. Outside the captured viewport or on an overlay/control/port/edge handle is invalid; it is never an outgoing drop.
2. Exclude both Q's accepted rectangles and its moving ghost from occluders. A different visible work or other non-performer card blocks target selection in its own painted body, including an underlying performer; it never implies reparenting. This rule requires aiming at an exposed performer header/body. Blank areas between its child cards remain targetable.
3. Collect compatible performer regions containing the pointer. Use half-open rectangles (left/top included, right/bottom excluded) for exact boundary behavior. If all candidates form one chain in **semantic parent ancestry**, choose its deepest candidate. Geometric rectangle containment does not establish this relationship.
4. Two incomparable candidates mean `AmbiguousTarget`, even when one rectangle geometrically contains the other. Highlight both and ask the user to use an exposed region; do not choose by paint order, size or UUID.
5. No performer hit and no blocking card means OutsidePerformers. Current-performer body remains an ordinary same-effective layout destination.
Camera/bounds change invalidates rather than rebasing frozen targets. Candidate enclosure changes never change this resolver. Active target, status wording and committed destination must match, including when the pointer coordinates remain constant.
## Placement and readable candidate rule
First-slice geometry is **one uniform translation**, not packing:
- Compute the requested delta from the selected placement's original position, press offset and current ordered pointer. Snap that root position to the existing 10-world-unit grid, then apply the identical delta to every Q position, including hidden children. Retain each node's size, anchors and fold state except the explicitly shown target unfold.
- Before permitting a composite semantic move, require its currently placed work closure to form one projected subtree under the selected root when that root's folded subtree is expanded in a read-only geometry projection. A distributed mixed closure is not made coherent merely by applying a common delta.
- Use `translated_routes` with that **captured Q delta map**, not a new descendant walk after changing allocation. This translates an active-diagram route only under the existing both-endpoint/same-delta rule; one-ended pins and label offsets retain their current contract. Do not move target contents or unrelated siblings.
- Project the complete candidate, including ancestors whose allocation summaries change. All coordinates/dimensions must pass current core bounds. The moved expanded subtree's internal relative positions and footprint remain unchanged by translation; unplaced leaves do not add ghost cards.
- Reject `LayoutRequired` if the result introduces or enlarges positive-area overlap between unrelated projected card interiors (compare the same pair before/after; ancestor/descendant enclosure is exempt). Check the read-only expanded moved subtree as well as the visible scene, so folding cannot hide a newly introduced collision. No automatic `insertion_adjustments` packing is promised here.
- The selected work header and, when targeting a performer, its active frozen target label must be visible in the viewport at the actual drag zoom. Outside/layout-only status remains visible beside the ghost or in the fixed canvas status. If the candidate hides the required header/label, refuse with “Make room or adjust the view, then drag again.” Keep the camera fixed; do not silently Fit an unreadable result. Larger preexisting diagrams are allowed; this does not require the whole diagram to fit.
These are deterministic first-slice gates, not evidence that all large-model arrangements are usable. Native review must verify that they allow the common empty/incoming/between/outgoing workflows. If normal target contents make refusal frequent, implement a bounded **visible** block-placement algorithm and revisit finding 4 before broadening acceptance.
## Exact candidate/diff gate
Build an **expected Project** from the accepted one by applying only the selected allocation additions/source edits/removals directly to a scratch copy and the enumerated permitted presentation edits. Separately obtain the actual candidate by `prepare_command`/`execute_batch` using ordinary commands, including their deferred cleanup and full core validation. Require equality of all Project fields/maps with the expected Project.
Permitted presentation changes are only Q node positions; the active target's displayed unfold; exact active-diagram route changes from the captured delta map; and removal of routes whose `edge` is an intentionally deleted Allocation ID. **Own-route cleanup is allowed in any diagram** because the route cannot survive its deleted relation; all other inactive-diagram geometry remains identical. Do not whitelist general view-option cleanup or other saved-view changes.
Consequently all element bodies/parents, non-target relation records, project headers/schema, artifacts, traces/baselines and untouched diagram fields must be identical. Check surviving records as well as missing IDs. A Constraint losing its `constrained` reference, dependent PortAllocation/ExchangeAllocation deletion, or trace/artifact mutation yields `CollateralChangeRequired` with named changed records and no accepted movement. This is a scoped equality gate, not a new generic impact framework.
Core errors produce `InvalidCandidate`; permitted warnings remain visible and do not become new errors. Source compilation must succeed before commit. Recheck the exact gate at release; no preview grants mutation authority. Execute the same stable-ID Batch through `Document::execute` once. Source refusal preserves accepted Project/SourceBundle/history and displays a retryable reason; retry recomputes against the live revision. A snapped no-op emits no semantic edits and no history entry.
## Concrete interfaces and preview/cache ownership
New names are proposed seams, not existing APIs:
| Boundary | Contract |
| --- | --- |
| `capture(project, diagram, work, placement)` | Pure Scope: S/L/Q IDs, exact original records/provenance, uniform class; no model mutation or GUI selection. |
| `plan_semantics(project, scope, destination, allocation_ids)` | Pure `SemanticPlan` or typed Refusal. Stable intended edits, label, complete semantic candidate Project and candidate `ViewIndex::new(candidate)`. IDs are allocated once outside the pure function and reused when revisiting a target. |
| `place(plan, snapshot, delta)` | Candidate Diagram + moved-route edits, projection, readability result and final Batch. It reuses the semantic candidate/index; it does not revalidate/clone all semantics per pointer pixel. |
| `WorkDragEvent::PressClean(stamp, token, node)` | App rechecks clean owner, registers token/scope and performs clean-specific selection. Never translates to legacy SelectNode. |
| `WorkDragEvent::Preview(stamp, token, destination, point)` | App checks token, resolves/cross-checks destination against frozen geometry, and returns/displays the keyed plan/placement or refusal. Ignore old responses. |
| `WorkDragEvent::Release(stamp, token, destination, point)` | Recheck clean owner, token, captured facts and current target; recompute final geometry/diff; execute one Batch or visible refusal. Consumes token. No fallback MoveNode/apply. |
| `Cancel(token)`, `Refused(stamp, reason)`, `ArrangeNext` | Presentation only. Refusal never changes selection/forms; cancel retires token and one-shot mode. |
Keep gesture-local ordered input/ghost state in `diagram.rs`; keep command admission and clean selection in a narrow root-owned App handler; use a small `allocation_drag.rs` planner. No edits to peer CreateContextElement logic, allocation validation, source grammar/schema or a new generic draft framework.
Cache semantic work by **(document/revision, gesture token, captured scope/provenance identity, mode, semantic destination)**. A destination change rebuilds candidate semantics/index even if snapped position is unchanged. Within one plan, geometric cache keys additionally include snapped delta, target unfold and preview geometry identity. Draw, ports, route eligibility, routing, hit-related candidate feedback and export-style projection must all use the same candidate Project/index. The accepted semantic index may be reused only for a plan with identical semantics. All caches are transient and invalidated with the token/lifetime.
## Required nontrivial verification
No tests were run or added by this task. Before claiming this slice:
- **Role counterexamples:** same-effective role-only leaf and uniform composite arrangement leave Allocation empty. Direct/role mixture under A stays byte-identical semantically on ordinary drop in A. Different B creates/retargets exact intended leaf records and retains advisory warnings. Role-only outward and A→role-B fallback produce truthful refusal with unchanged placement.
- **Hidden semantic edits:** delete Allocation referenced by a surviving Constraint; candidate remains core-valid but must fail exact diff. Repeat artifacts/traces and SA mapping cascades. Conversely a deleted Allocation's own stored routes can disappear across diagrams while every other saved-view field stays exact. OA communication incompleteness that is warning-only remains warning-only.
- **Mounted admission:** dirty property form before actual press→selection→move→release; dirty source/behavior and modal/save ownership; no apply/history/selection change. Clean stationary click selects correctly without revision change; full same-batch clean drag commits once. Replay captured press/preview/release after return from modal, Fit/resize or revision change and verify rejection.
- **Scope/layout:** uniform first/second/grandchild trees, folded children, unplaced leaf, other diagram with different placement; move exactly Q while editing every intended L allocation. Peer first-child transfer of Allocation **and RoleAssignment** invalidates old scope. Mixed P with x under A and y under B refuses rather than stretching C. Check active ancestor regrouping and introduced overlaps.
- **Targets/cache:** nested semantic performers versus overlapping/included peers, exact shared borders, work-card occlusion, collapsed headers, outgoing blank region and outside viewport. Same snapped point with destination A→B rebuilds semantic preview/index; rendered target and committed target agree. Repeated motion within B reuses its semantic plan.
- **End-to-end OA/SA:** ordinary leaf in→between→out and uniform composite move at 1100×700 and wider; full Project/SourceBundle Save/reopen and exact one-step Undo/Redo, including relation metadata, hidden placements, routes and source comments. Escape, unfocus, snapped no-op and source refusal leave no partial result. Verify actual packaged Windows target visibility/readability; compilation or planner passes are insufficient.
Mixed-layout convergence, deliberate role exit and collateral dependency repair remain required named continuations with explicit current refusals. The full OA/SA and cross-platform goals remain unchanged; Linux/WSL remains user-paused.