Open concurrent processes with boundary traffic #
This file defines the semantic bridge between the closed-world concurrent
process core (ProcessOver, StepOver) and the open-world layer needed for
UC-style composition.
The key idea is simple: a closed concurrent step already records controller paths and local views at each node. An open concurrent step additionally records how each node may receive traffic from, or emit traffic to, an external boundary.
The design follows the layered approach from the UC design notes:
BoundaryAction Δ Xrecords, at one protocol node with move spaceX, whether the node is externally activated and what outbound packets the chosen move contributes.OpenNodeSemantics Party Δ Xextends the existingNodeSemantics Party Xby oneBoundaryActionfield.OpenStepContext Party Δis the resulting realized node context.OpenProcess Party ΔspecializesProcessOverto that open context.
The closed-world layer is recovered by the canonical forgetful projection
openStepContextForget, which drops the boundary action and retains only
the NodeSemantics. This means every OpenProcess can be viewed as a plain
closed Process by ProcessOver.mapContext.
Boundary actions are structurally mappable along PortBoundary.Hom via
BoundaryAction.mapBoundary. The isActivated flag is invariant under
boundary adaptation (only emit transforms), which ensures functoriality.
The query-level decoding of how input messages determine node moves belongs
in the runtime/execution layer, not the structural boundary action.
BoundaryAction Δ X records the boundary traffic associated with one
protocol node whose move space is X, against an open boundary Δ.
Fields:
isActivatedflags whether this node is driven by external boundary input (true) or by the internal protocol dynamics (false).emitmaps each chosen move to the list of outbound packets it contributes onΔ.Out.
The activation flag is a structural marker. The query-level information
about how an input message determines the node's move belongs in the
runtime/execution layer: charts (used by PortBoundary.Hom) can map
packets but cannot map queries, so the structural boundary action records
only the fact of activation, not the decoding.
The emit field records only the protocol's own direct output. Runtime-level
concerns (buffering, duplication, scheduling, delivery) belong in a separate
Runtime layer and are not encoded here.
- isActivated : Bool
Instances For
A purely internal node: not externally activated and no outbound packets.
Instances For
A boundary-activated node that emits no outbound packets.
Instances For
An internally driven node that emits outbound packets.
Instances For
Transform a boundary action along a boundary adaptation.
The activation flag is preserved (it does not depend on the boundary
presentation). The emitted packets are translated forward along
φ.onOut.
Instances For
Embed a boundary action on the left factor into the tensor boundary.
Emitted packets are injected into the left summand of the combined output interface. The activation flag is preserved.
Instances For
Embed a boundary action on the right factor into the tensor boundary.
Emitted packets are injected into the right summand of the combined output interface. The activation flag is preserved.
Instances For
Transform a boundary action on tensor Δ₁ Γ into one on tensor Δ₁ Δ₂
by keeping only the left-summand (Δ₁) packets and re-injecting them
into the left summand of the combined output. Right-summand (Γ) packets
are dropped (they become internal traffic handled by the runtime).
Instances For
Transform a boundary action on tensor (swap Γ) Δ₂ into one on
tensor Δ₁ Δ₂ by keeping only the right-summand (Δ₂) packets and
re-injecting them into the right summand of the combined output.
Left-summand (swap Γ) packets are dropped (internal traffic).
Instances For
Close a boundary action by dropping all external traffic.
The result lives on PortBoundary.empty and has no activation or emission.
This is used by plug to internalize all boundary interactions.
Instances For
OpenNodeSemantics Party Δ X extends NodeSemantics Party X with one
BoundaryAction Δ X recording the node's interaction with an external
boundary.
This is the per-node data that distinguishes an open process from a closed
one: the closed part (controllers, views) describes internal control and
observation, while boundary describes the node's interface with the outside
world.
- controllers : X → List Party
- views : Party → Multiparty.LocalView X
- boundary : BoundaryAction Δ X
Instances For
Build an OpenNodeSemantics from a closed NodeSemantics by marking the node
as purely internal (no boundary traffic).
Instances For
Transform the boundary action of an open node semantics along a boundary adaptation, preserving the closed-world node semantics.
Instances For
The open-world node context for processes with boundary Δ.
At a node with move space X, the context value is
OpenNodeSemantics Party Δ X: the usual controller-path and local-view data,
plus a BoundaryAction describing the node's external traffic.
Instances For
The forgetful map from the open-world context to the closed-world context.
This drops the BoundaryAction and retains only the NodeSemantics
(controllers and local views).
Instances For
The embedding from the closed-world context into the open-world context.
This marks every node as purely internal (no boundary traffic).
Instances For
The context hom induced by a boundary adaptation.
This transforms every node's boundary action along φ while preserving
the closed-world node semantics.
Instances For
Embed the left factor's open-world context into the tensor boundary context.
This injects emitted packets into the left summand of the combined output interface while preserving the closed-world node semantics.
Instances For
Embed the right factor's open-world context into the tensor boundary context.
This injects emitted packets into the right summand of the combined output interface while preserving the closed-world node semantics.
Instances For
Wire the left factor: transform OpenStepContext Party (tensor Δ₁ Γ) into
OpenStepContext Party (tensor Δ₁ Δ₂) by filtering out internal (Γ) packets
and keeping only external (Δ₁) packets.
Instances For
Wire the right factor: transform
OpenStepContext Party (tensor (swap Γ) Δ₂) into
OpenStepContext Party (tensor Δ₁ Δ₂) by filtering out internal
(swap Γ) packets and keeping only external (Δ₂) packets.
Instances For
Close the boundary: transform OpenStepContext Party Δ into
OpenStepContext Party empty by dropping all boundary traffic.
Used by plug to internalize all external interactions.
Instances For
The open-world specialization of StepOver.
Here the node context carries OpenNodeSemantics Party Δ, so every node
records both the usual controller/view data and its boundary traffic against
Δ.
Instances For
The open-world specialization of ProcessOver.
An OpenProcess Party Δ is a continuation-based process whose steps are
decorated by OpenNodeSemantics Party Δ. It exposes the directed boundary
Δ to an external context.
The closed-world Process Party is recovered by
ProcessOver.mapContext (OpenStepContext.forget Party Δ).
Instances For
Forget the boundary layer and view an open process as a plain closed-world process.
This is the canonical projection: it drops all BoundaryAction data from
every node while preserving the process structure, controller paths, and
local views.
Instances For
Embed a closed-world process as an open process with no boundary traffic.
Every node is marked as purely internal: isActivated = false and emit
produces no packets.
Instances For
Adapt the exposed boundary of an open process along a structural boundary morphism.
This transforms every node's boundary action along φ (translating emitted
packets, preserving activation flags) while leaving the process structure
and closed-world node semantics unchanged.
Instances For
OpenProcess.System augments an open process by the standard verification
predicates used throughout VCVio.
Instances For
A transcript path through a decorated open-process spec is silent when
every visited node is not externally activated (isActivated = false).
Checking only isActivated (rather than also requiring emit x = [])
ensures the predicate is invariant under all context morphisms, including
wireLeft / wireRight which filter shared-boundary packets via
List.filterMap.
Instances For
A complete step of an open process is silent when every node along the chosen transcript path has boundary-internal semantics.
Instances For
IsSilentDecoration is invariant under context morphisms that preserve
isActivated. All morphisms in the open-process framework (including
mapBoundary, embedInlTensor, embedInrTensor, wireLeft, wireRight,
and closed) preserve isActivated.
IsSilentStep is invariant under OpenProcess.mapBoundary: remapping
the boundary does not change which transcripts are silent, because all
boundary maps preserve isActivated.
Two open processes with the same boundary are weakly bisimilar when there exists a relation on their state types satisfying:
- Totality / surjectivity: every state on each side has a related partner.
- Silent forward/backward: a silent step can either be matched by some step on the other side (maintaining the relation), or absorbed (the other side stays put and the relation is maintained with the successor).
- Visible forward/backward: a visible (non-silent) step must be matched by a visible step on the other side that preserves the relation.
This is the appropriate equality notion for openTheory monoidal laws,
where the internal scheduler structure differs (e.g., left-nested vs.
right-nested interleaving) but the observable boundary traffic is the same.
The scheduler nodes introduced by ProcessOver.interleave are always silent,
so they can be absorbed by the weak bisimulation.
Instances For
Every open process is weakly bisimilar to itself.
Weak bisimilarity is symmetric.
Weak bisimilarity is transitive. The composite relation witnesses p₂ as
an intermediate: ∃ s₂, r₁₂ s₁ s₂ ∧ r₂₃ s₂ s₃. For silent steps, the
intermediate state can advance or stay, using Classical.em to case-split
on whether the intermediate step is itself silent.