diff --git a/harness/DECISIONS.md b/harness/DECISIONS.md index 53f67dc1..152aa6e9 100644 --- a/harness/DECISIONS.md +++ b/harness/DECISIONS.md @@ -25,3 +25,4 @@ - Git is now explicitly authorized by the user. Establish a source baseline on codex/release1-engineering, then use focused commits at verified milestones. Use Git worktrees for independent writers when their scope can be partitioned; root integrates and owns native UI. Keep target/dist, recovery/store files and credentials outside version control. No remote publication is implied. - User's hierarchy/port correction supersedes generic graph defaults. Architecture/dataflow/capability content differs by engineering purpose, while containment/allocation and port anchors remain derived from semantic records. The exact correction and remaining port metamodel work are in release-diagram-port-correction.md. +- Adopt `release-port-contract.md`: function input/output ports declare items; Standard/Flow component ports have separate orientation, interface roles and explicit function-port allocations. Required functional endpoints are generated/reused by one atomic planner. Schema3 migration preserves original generic ports as explicit unresolved contracts and generates only previously missing FE endpoints. This avoids guessing component kind, interface role or splitting stable InOut identities during import. diff --git a/harness/release-diagram-port-correction.md b/harness/release-diagram-port-correction.md index 33001e7b..229d4f37 100644 --- a/harness/release-diagram-port-correction.md +++ b/harness/release-diagram-port-correction.md @@ -12,12 +12,14 @@ Installed 7.0.1 Help, `4.05. Diagram Editor`, says function ports appear automat The [FunctionalAnalysis metamodel](https://raw.githubusercontent.com/eclipse-capella/capella/master/m2/plugins/org.polarsys.capella.core.data.def/model/FunctionalAnalysis.ecore) distinguishes FunctionInputPort/FunctionOutputPort with incoming/outgoing item contracts; FE creation automatically creates those endpoints. ComponentPort has distinct Standard/Flow kind and orientation applicable to Flow, plus ComponentPortAllocation to allocated ports. These semantics must not be reduced to one bidirectional interface-bearing field. -Native Capella reopen attempted using the existing disposable workspace. Console launcher pid14556 has not exposed a targetable modeling window; graphical launcher also reported no targetable window. No new native port workflow has been observed yet. Existing native benchmark establishes nested OA activities and class/interface allocation rows but does not prove ports. +Native Capella eventually completed startup in the disposable workspace (console launcher pid14556/window1049956). Root reached the System Analysis workflow and System Architecture diagram list. No new native port workflow has been observed yet. Existing native benchmark establishes nested OA activities and class/interface allocation rows but does not prove ports. A later physical Escape stopped Computer Use; native work is paused while implementation continues. ## Ordered implementation 1. Correct shared projection + SVG + canvas: architecture shows participant containment and allocated work with meaningful exchange edges; dataflow shows work hierarchy; capabilities have a dedicated view. Keep an explicitly named all-relationships view for exploration. Do not change semantic objects just to remove clutter. Ports derive as small, selectable boundary anchors of visible owners, and exchanges terminate on the actual port. Computed links after collapse must not invent port identities. Group movement, Fit, SVG and hit testing use this same geometry. 2. Correct authoring/model contracts: distinguish functional In/Out contracts from component Standard/Flow ports; function-port exchange items; explicit component→function port allocation and direction/owner checks. Creating a functional exchange creates or reuses compatible ports atomically. OA interactions remain activity-to-activity without fake functional ports. Preserve legacy IDs/prose/interface references in the explicit schema3 migration, with reviewed resolution for incompatible old InOut work ports. Implement source/form/API/reference/transition parity with this change, not a canvas-only workaround. + + Exact adopted interface, invariants, planners and migration acceptance: `release-port-contract.md`. 3. Contextual insertion/allocation and grouped edge filters, orthogonal routes and labels. Default demo/worked examples must communicate these modeling distinctions immediately. Show allocation/containment explanations and derived-summary styling. Verify nested movement/collapse/reveal, creation, port selection/connection, exchange allocation, undo, save/reopen and SVG on actual native UI at default/minimum size. Keep R5 state editor's current code/tests; its native acceptance remains pending. R6 data contract and release audit must be amended around step2 before implementation. All prior first-release requirements remain active. diff --git a/harness/release-port-contract.md b/harness/release-port-contract.md new file mode 100644 index 00000000..088a2fd5 --- /dev/null +++ b/harness/release-port-contract.md @@ -0,0 +1,215 @@ +# Adopted port correction contract + +Status: adopted design, 2026-09-05. This document freezes the port milestone interface; it does not claim implementation, test results, or native acceptance. It supersedes the generic work-port/InOut rules in [the R6 data proposal](release-data-contract.md) and implements the semantic portion of [the diagram and port correction](release-diagram-port-correction.md). Item specialization follows the future R6 seam below; semantic review follows [the R3 transition contract](release-transition-contract.md). + +The official [FunctionalAnalysis metamodel](https://raw.githubusercontent.com/eclipse-capella/capella/master/m2/plugins/org.polarsys.capella.core.data.def/model/FunctionalAnalysis.ecore) distinguishes FunctionInputPort/FunctionOutputPort and their item declarations, ComponentPort Standard/Flow kind, Flow orientation, independently oriented component exchanges, and ComponentPortAllocation. Its function-port documentation also describes automatic endpoint creation when an FE is created. Provided/required interfaces are separate references in [Information.ecore](https://raw.githubusercontent.com/eclipse-capella/capella/master/m2/plugins/org.polarsys.capella.core.data.def/model/Information.ecore). The rules below are Studio decisions for OA/SA, not a claim of exhaustive Capella compatibility. + +## Persisted schema 3 interface + +Keep element/relation headers, identity, ownership, and the existing `Kind::Port` selection/query category. Replace the old payload, add `RelationKind::PortAllocation`, and retain the names of existing exchange and interface relations. Reference-bearing structs/enums use the project's existing serde derives and `deny_unknown_fields` convention. + +```rust +// Retained solely for preserved generic contracts. +enum PortDirection { In, Out, InOut } + +enum ComponentPortKind { Standard, Flow } +enum ComponentPortOrientation { Unset, In, Out, InOut } + +struct LegacyPortContract { + direction: PortDirection, + interface: Option, +} + +enum PortContract { + FunctionInput { incoming_items: Vec }, + FunctionOutput { outgoing_items: Vec }, + Component { + kind: ComponentPortKind, + orientation: ComponentPortOrientation, + }, + Legacy, +} + +SharedBody::Port { + contract: PortContract, + legacy: Option, +} + +RelationBody::FunctionalExchange { + source: Id, // SA Function + target: Id, // SA Function + source_port: Id, // mandatory, owned by source + target_port: Id, // mandatory, owned by target + items: Vec, +} + +RelationBody::BoundaryExchange { + source: Id, // component port, or preserved legacy participant port + target: Id, + oriented: bool, + items: Vec, +} + +RelationBody::PortAllocation { + source: Id, // SA Component port + target: Id, // SA function input/output port +} +``` + +`InterfaceProvision`, `InterfaceRequirement`, and `ExchangeAllocation` remain identified relations. Do not add duplicate writable provided/required interface lists, allocating-port lists, or allocated-port lists. Derive them from relations. + +`legacy` preserves the original generic direction/interface association through import and reviewed resolution. It is not a second active typed contract. New typed ports have `legacy: None`. `PortContract::Legacy` requires `legacy: Some(...)` and always produces a located compatibility warning. Legacy remains an explicit documented JSON/source constructor, rather than validation depending on the input channel or an unpersisted origin flag. Ordinary creation pickers never offer it. + +## Normative ownership and contract rules + +- Typed function ports belong directly to a same-layer SA `Function`. Typed Component ports belong directly to a same-layer SA `System` or `SystemActor`. No typed ports are admitted in OA. Port placement and visible allocation nesting do not change semantic ownership. +- OA `Interaction` stays activity-to-activity with its item list. Neither creating an interaction nor projecting/transiting one creates OA function ports. Preserve existing OA generic ports as Legacy; do not silently classify them as component ports. +- An FE source port must be FunctionOutput owned by its source; its target port must be FunctionInput owned by its target. No typed function InOut exists. Legacy FE endpoints receive their original owner/direction checks and an unresolved-contract warning. Normal creation/reuse never selects Legacy endpoints. +- Function port item declarations are distinct from incident FE item lists. Every FE item must satisfy the declaration at both endpoints. Empty declarations mean an empty contract, not a wildcard. Empty FE/port declarations are valid drafts with completeness diagnostics. Adding/removing an FE does not continually overwrite independent port declarations. +- Lists contain unique, same-layer ExchangeItem references. The shared compatibility helper is `item_satisfies(project, carried, declared)`: identity equality now; R6 will additionally permit a carried item specializing the declared item through explicit acyclic ancestry. The reverse is not compatible. No alias/name comparison or speculative subtype inference. Apply the same helper to interfaces, port contracts, and exchange allocations. +- Standard requires `orientation == Unset`. Flow admits Unset/In/Out/InOut. Contextual creation supplies Standard/Unset or Flow/Unset; function input/output actions select their respective variants directly. Unset Flow orientation is an incomplete draft, not InOut. +- Provided interfaces, required interfaces, function item contracts, component kind, and component orientation are separate facts. Keep provision/requirement roles visible and independently editable. Their union may describe potential carried items, but cannot prove role matching. New port-level provision/requirement relations use Component ports; participant-level relations retain their current meaning. Preserved relations on Legacy ports, or resolved function ports retaining `legacy`, remain visible compatibility declarations with warnings and do not replace function item contracts. Ordinary creation does not offer interface roles on function ports. +- For a Standard connection, stored endpoint order does not establish provider/consumer roles. A required interface is satisfied by a partner-provided identical interface; R6 may also use its explicitly compatible interface specialization. Unsatisfied requirements are located completeness warnings. Never infer that Out provides or In requires: Operation call/reply direction is independent of those roles. Participant interface declarations are not silently inherited by every owned port. +- At a component port with explicit provided/required interface declarations, carried items must fit their effective interface-item union using `item_satisfies`. Absence of declarations means unspecified compatibility and may warn; an explicitly empty interface does not accept arbitrary items. Do not manufacture interface relations to make a connection pass. Legacy uses its preserved interface association and the old item checks until resolved; after resolution that association is displayed as preserved/unclassified data and is not another active typed constraint. +- `BoundaryExchange.oriented` is independent of port kind/orientation. New Standard connections default false; directional Flow connections default true. For oriented Flow endpoints, source must permit Out and target In. A known contradictory direction is an error; Unset warns. For an unoriented connection between Flow ports, at least one communication direction must be possible: In-only/In-only or Out-only/Out-only conflicts. Standard has no Flow direction. Mixed Standard/Flow connections warn that transport compatibility is unspecified; they do not assert an adapter. +- A BoundaryExchange connects different participant owners. Typed component endpoints are SA-only. Preserved OA boundary exchanges retain Legacy participant endpoints and an explanatory compatibility warning. +- PortAllocation is same-layer Component-to-typed-FunctionPort, unique per pair. Its function must have an actual work Allocation to the component owning the allocating port. A missing/contradictory allocation is an error for this new relation; the authoring action must resolve that work allocation first. Do not use computed allocation-summary nesting as evidence. Flow In permits input, Out output, InOut either, and Unset warns. Standard may allocate either direction. Multiple allocations are allowed when these invariants hold; no unsupported one-to-one cardinality is imposed. +- A typed SA ExchangeAllocation requires both boundary endpoints to allocate the corresponding FE ports through explicit PortAllocation relations. An unoriented boundary can correspond in either stored order; an oriented boundary must correspond in its direction. The boundary item set must cover the allocated FE items using the same compatibility helper. Legacy exchange allocations retain prior owner/item checks and a warning that typed port allocation remains unresolved. + +## Atomic authoring interfaces + +Implement a bounded `ports.rs` helper module using existing commands and transactions, not another editing framework. The public request/planner interfaces are: + +```rust +enum PortChoice { Auto, Reuse(Id), Create } + +struct FunctionalExchangeRequest { + id: Id, + alias: String, + name: String, + description: String, + source: Id, + target: Id, + source_port: PortChoice, + target_port: PortChoice, + items: Vec, +} + +fn plan_functional_exchange( + project: &Project, + request: &FunctionalExchangeRequest, +) -> Result, ModelError>; + +enum ComponentExchangeChoice { + Create { + id: Id, + alias: String, + name: String, + description: String, + port_kind: ComponentPortKind, + oriented: bool, + source_port: PortChoice, + target_port: PortChoice, + }, + Reuse(Id), +} + +struct ComponentExchangeRequest { + functional_exchange: Id, + boundary: ComponentExchangeChoice, +} + +fn plan_component_exchange( + project: &Project, + request: &ComponentExchangeRequest, +) -> Result, ModelError>; + +struct LegacyPortResolution { + port: Id, + contract: PortContract, // must be a typed variant; reject Legacy +} + +fn plan_legacy_port_resolution( + project: &Project, + request: &LegacyPortResolution, +) -> Result, ModelError>; + +enum FunctionPortSide { Input, Output } + +struct LegacyWorkPortSplitRequest { + port: Id, + primary: FunctionPortSide, + incoming_items: Vec, + outgoing_items: Vec, +} + +fn plan_legacy_work_port_split( + project: &Project, + request: &LegacyWorkPortSplitRequest, +) -> Result, ModelError>; +``` + +Planners return concrete `CreateElement`, `EditElement`, `CreateRelation`, and `EditRelation` commands for preview. Execute through one existing Batch/candidate transaction, with one final validation and undo/revision entry. A failed plan or transaction changes neither the model nor disk/ETag state. A preview is bound to the existing project fingerprint/revision; regenerate it after intervening edits. No generic arbitrary-reference-edit field is added to these requests. + +### Functional exchanges and item synchronization + +Explicit Reuse must satisfy typed direction, owner, and the requested items. Auto reuses exactly one compatible typed port; if none or several qualify, it creates a fresh port. It never chooses among ambiguous identities, reuses Legacy, or silently broadens an existing declaration. Create always creates a new port. New ports receive the FE items as initial incoming/outgoing declarations. + +Use UUID v5 with the FE UUID namespace and stable names `function-source-port` and `function-target-port` for generated endpoints. Check collisions across every object category before mutation. A collision is a located error, never overwrite or accidental reuse. Generate names from exchange name plus endpoint role, and use the existing deterministic collision-free alias convention. Empty names use stable role labels. Repeating a failed/undone plan produces the same new IDs. + +`Command::Connect(FunctionalExchange, ...)` and GUI connect/form actions call the same planner. `CreateRelation` remains the exact low-level operation and requires explicit persisted FE port IDs. REST clients can submit the equivalent Batch; exposing the specialized requests as command variants is optional, but no channel may bypass core invariants. Schema 3 source contains explicit port aliases and FE endpoints; parsing must not secretly create ports. + +Provide a separate, previewed "Synchronize port items" action: its proposed declarations and affected exchanges must be visible, and contract shrinkage that invalidates a remaining exchange must be rejected or resolved in the same reviewed transaction. It is not implicit behavior of ordinary FE creation/deletion. + +### Component exchange generation + +The selected FE must have typed endpoints and actually allocated endpoint functions. Create determines participants from those actual work Allocations. Source/target PortChoice applies within the corresponding owner and requested `port_kind`; reuse additionally checks orientation and declared interface-item compatibility. Generated Flow ports use Out/In respectively; generated Standard ports use Unset. Explicit Reuse of a boundary checks its participants, orientation, and endpoint compatibility; reversed stored order is permitted only when the boundary is unoriented. + +The planner creates/reuses the component ports, both PortAllocation relations, the boundary exchange, and ExchangeAllocation together. When reusing a boundary, it visibly proposes the stable union of existing and FE items. Existing matching allocation relations are reused without modifying identity/prose. Derive relation IDs in the project UUID namespace using `port-allocation::` and `exchange-allocation::`. Generated component-port IDs use the boundary UUID namespace and `component-source-port`/`component-target-port`. Collision rules are the same as FE generation. + +The planner does not generate an interface, infer provided/required roles, change a reused port contract, or invent a missing work Allocation. Failure leaves none of the proposed ports/exchanges/allocations behind. Running generation again against the same selected existing boundary reuses existing relations and yields no duplicate facts. + +### Legacy resolution and split + +Resolution applies to Legacy with a preserved original record, checks the requested typed variant against its actual owner/layer and all uses, and retains the original ID, alias, prose, and `legacy`. Prefill SA directed work-port item choices from its original interface membership and actual incident FE usages, showing their origins; the engineer selects the effective contract. This does not assert a provided/required role. An OA legacy owner cannot receive a typed port: preserve it unresolved, navigate to an explicitly mapped SA counterpart, or delete it through ordinary reviewed impact. + +Split applies only to an SA Function-owned Legacy InOut port. Keep the original ID as the requested primary typed direction and its original legacy record. Create one opposite typed sibling with `legacy: None`; derive its ID with the original port UUID namespace and `split-opposite-port`. Derive a collision-free alias/name from the original plus opposite direction. Use the request's explicit incoming/outgoing declarations. Remap every FE source use to the output and every target use to the input. Validate all resulting contracts in the final candidate. + +The preview lists constraints, artifact attachments, interface declarations, traces, and other non-FE usages of the old ID. They remain on the original primary ID by default; do not duplicate or silently move their engineering meaning. Preserved legacy interface-role declarations follow the compatibility rule above. Any genuinely incompatible non-FE use requires an explicit separately reviewed edit in the same Batch, or resolution fails. Existing placement IDs remain with the primary port; automatic owner-boundary anchors display the sibling without requiring another stored placement. Undo restores the exact original port and references. + +## Explicit V2-to-schema-3 migration + +1. Add real version-2 DTO/readers before changing current serde shapes. `Some(2) => serde_json::from_slice::(...)` is no longer an adapter. Preserve original/staged V2 inputs and route V1 normalization through the same V2-to-3 stage. Canonical output is schema 3 / `arcadia 3`. +2. Preserve every original project, element, relation, diagram, node, trace, and artifact ID, alias, name, description, parent, and coordinate. Each generic port becomes `Port { contract: Legacy, legacy: Some(LegacyPortContract { direction: old.direction, interface: old.interface }) }` in its existing layer. Do not infer Component kind, interface role, or split an InOut identity. +3. Preserve all existing FE port IDs. For each missing V2 endpoint, create one typed output/input port under the already specified source/target function with that FE's item list. In the project UUID namespace use `v2-fe-port::source` or `v2-fe-port::target`, with collision-checked deterministic aliases. Filling previously missing endpoints is reported explicitly. Existing Legacy endpoints remain unresolved warnings rather than being replaced. +4. Preserve BoundaryExchange endpoints/items and set `oriented = true` to retain the old directed behavior. Preserve all provision/requirement/ExchangeAllocation relation IDs and bodies. Do not manufacture PortAllocation relations from visual nesting. OA ports and boundary links remain Legacy compatibility content; OA Interactions remain portless. +5. Return/surface a migration report listing unresolved preserved ports, generated endpoint IDs, orientation defaults, generated aliases, and required resolutions. Count generated objects/references against admission limits before construction. Validate old input with its own contract and migrated output with schema 3; report a located pathological collision instead of overwriting. Migration alone does not increase stored revision or silently save the file. +6. Keep original V2 golden fixtures byte-for-byte. Test deterministic migration and canonical second-save stability. Subsequent explicit resolution/split preserves original legacy data and primary identities as specified above; user-requested delete operations retain their ordinary impact semantics. + +## References, deletion, transitions, and drift + +Visit incoming/outgoing item IDs and `legacy.interface` in element reference traversal. Visit required FE endpoint IDs, PortAllocation endpoints, and existing role/allocation endpoints in relation traversal. All are live references for usage navigation, reidentify, remap, delete impact, and same-layer validation. Add `incoming_items`, `outgoing_items`, and nested `legacy.interface` to source reference awareness. Legacy metadata is preserved association data, not an immutable historical snapshot: explicitly deleting that interface clears the optional reference and shows that consequence. + +Deleting a port must include incident FEs in `deletion_impact`; it can no longer merely set FE endpoint fields to None. Existing cascades then include affected occurrences, ExchangeAllocations, trace links, and artifact links. Deleting an FE retains its independent ports. Deleting a PortAllocation retains both ports, and impact must also include any typed ExchangeAllocation losing its required endpoint correspondence, unless a replacement is supplied in the same transaction. Deleting a referenced item prunes declarations/carried items through the existing explicit cascade policy; deleting an interface does not delete its allocated items or ports. Cross-layer counterparts are never silently deleted. + +OA-to-SA Interaction creation invokes the same FE planner after mapping functions/items. SA-to-OA FE mapping emits a portless Interaction. Required closure copies the needed item/type/interface dependencies, but generic shared-body mapping must not copy typed SA ports or PortAllocation into OA. Also omit typed BoundaryExchanges and provision/requirement/ExchangeAllocation relations whose endpoints depend on those omitted objects; do not leave dangling target references or create substitute OA ports. Dictionary objects needed only by omitted objects are not required closure unless separately selected or needed by retained mappings. Preview explicitly lists SA-only omissions; original SA ports, contracts, artifacts, and traces stay intact. Existing FE reuse preserves endpoint identities unless a reviewed update explicitly reroutes them. Existing Legacy port traces retain their IDs/content; do not generate Item traces to invented OA ports. Explicitly requested legacy counterpart handling remains a reviewed legacy mapping, not an automatic typed conversion. + +R3 snapshots/drift must cover the port contract variant, item declarations, Component kind/orientation, preserved legacy association, FE endpoint IDs, boundary orientation, and the separate provision/requirement/PortAllocation facts. The current name/description-only `TracedProperties` is insufficient. Follow the R3 historical-reference rules for baseline values; they are distinct from the live references above. Baselines and cross-layer preview do not claim that SA port contracts have a port-level OA equivalent. + +## Channel and file ownership impact + +- Core: `model.rs`, bounded `ports.rs`, `lib.rs`, `validation.rs`, `references.rs`, `session.rs`, `persistence.rs`, `language.rs`, `transitions.rs`, and `demo.rs`. Do not keep generic `ElementBody::new(Kind::Port, ...)` inventing InOut; contextual creation must supply a typed variant and owner. +- Desktop: `forms.rs` for variant-specific fields and compatible selectors; `views.rs` for owner, item, interface-role, and allocation rows; authoring handlers for atomic generation/resolution/split. API/source have the same typed contracts and located errors. Update `docs/openapi.json`, language documentation, and worked examples. +- Projection/canvas/SVG: small selectable anchors come from port contract and owner. Anchor side is a visual property, not `PortDirection`; Standard/Unset is not bidirectional Flow. Exchange geometry terminates at the referenced actual port. Collapsed summary links do not create persisted/computed port identities. Preserve existing port placement IDs during migration. +- Amend R6's superseded port section and R3 coverage references during integration; do not implement future R6 specialization or the broader R3 engine under the guise of this isolated port change. + +## Required acceptance + +1. Create an SA FE from functions; observe correctly directed item-bearing endpoint ports, owner-boundary anchors, one undo entry, and required IDs in canonical source. Exercise unique reuse, ambiguous Auto creating new, explicit incompatible reuse rollback, and allocation of two functions to their actual components. +2. Generate a component exchange with both PortAllocations and ExchangeAllocation atomically. Repeat with an existing boundary, preserving all reused IDs and prose. Show Standard/Unset with independent unoriented connection, directional Flow, Unset warnings, and contradictory directions/owners rejected. +3. Show an Operation with separately visible provider/requirement roles and call/reply direction; no source-direction role inference. Exercise item contract mismatch, empty contracts, and the equality compatibility seam before R6 specialization is introduced. +4. Migrate original V2 fixtures and a fixture containing missing FE endpoints, directed work ports, InOut work ports, OA ports, old neutral interface links, provision/requirement links, artifacts, and traces. Check every original identity/content, reported generated IDs, admission limits, deterministic collision handling, and stable second save. +5. Resolve a directed Legacy port and split an InOut port with incoming/outgoing FE uses plus an artifact, constraint, and trace. Verify preserved original legacy content/primary identity, explicit reference routing, undo, save/reopen, and no duplicated engineering attachments. +6. Reidentify ports/items/interfaces and delete a port, FE, PortAllocation, interface, and carried item. Verify exact impact, no required endpoint becomes missing, no dangling references survive, and cross-layer counterparts remain. +7. Round-trip GUI save/load, canonical source, JSON, REST candidate/transaction/export/restart. Invalid batches leave revision/ETag/disk unchanged. OA-to-SA creates SA ports; SA-to-OA creates no OA typed ports and reports omissions. Required closure is bounded and reviewable. +8. R3 integration detects contract/endpoint/orientation/allocation changes. Native acceptance checks port selection/connection, allocation rows, grouped movement/collapse/reveal, undo, reload, and matching SVG geometry at default/minimum window sizes. Until observed, native acceptance remains pending.