Documentation
¶
Overview ¶
Package exec holds the public node-execution contracts (ADR-012 v.1): the node executor a model element implements, the synchronizing-join variant, and the data-binding consumer/producer + Frame surface. The implementations (the instance's runtime environment, the data-plane Frame) live in internal/*; pkg/model depends only on these interfaces.
Index ¶
- type ActivationJoin
- type CallOutcome
- type CallableRef
- type CallableResolver
- type CallableResolverFunc
- type ChildProcess
- type Decision
- type DefaultCallableResolver
- type FlowChecker
- type Frame
- type GuardEval
- type NodeDataConsumer
- type NodeDataProducer
- type NodeExecutor
- type ProcessCall
- type ProcessInvoker
- type ReachabilityJoin
- type SynchronizingJoin
- type WaitHolders
- type WaitKind
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type ActivationJoin ¶
type ActivationJoin interface {
NodeExecutor
// Record registers arrivingTrackID's arrival on incomingFlowID and reports
// whether the gateway has already fired — in which case the arrival is a trailing
// token to be consumed (a discriminator / partial join ignores the arrivals after
// the activating one). It makes no activation decision.
Record(incomingFlowID, arrivingTrackID string) (firedAlready bool)
// Recheck decides the join's fate using eval for data guards and fc for
// reachability. Called only from the instance loop, so fc's live-token view is
// consistent. Fires (Survivor + Merged), aborts (the rule is unsatisfiable), or
// neither (wait).
Recheck(eval GuardEval, fc FlowChecker) (Decision, error)
}
ActivationJoin is a converging gateway whose completion is an activation rule over per-triple data guards, arrival counts, and required gates (ADR-005 v.3 §2.11). It reuses the reachability machinery (FlowChecker) but, unlike a ReachabilityJoin, a token death makes it ABORT (the arrival count is monotonic, so a death can only make a triple unsatisfiable) rather than fire.
The arriving track only Records (reachability and guards are not read off the track goroutine — the live-token set the loop owns must not be raced); the loop owns the whole fire/abort decision via Recheck.
type CallOutcome ¶ added in v0.9.0
type CallOutcome struct {
foundation.BaseElement
// contains filtered or unexported fields
}
CallOutcome is the synthetic completion the instance loop delivers to a parked Call Activity track when its child instance ends (the job/scope completion idiom). It carries the terminal fault (nil on a normal completion — the declared outputs are already committed into the caller scope by the loop before delivery); a non-nil err is returned by CallActivity.Exec so the caller track faults and the §2.6 error chain catches it at the node (SRD-050 FR-7/FR-8). Implements flow.EventDefinition so it rides the parked track's event channel exactly like a WorkerOutcome.
func NewCallOutcome ¶ added in v0.9.0
func NewCallOutcome(err error) *CallOutcome
NewCallOutcome mints a call-completion carrying err (nil = a normal completion, the caller track then just selects its outgoing flows).
func (*CallOutcome) Err ¶ added in v0.9.0
func (o *CallOutcome) Err() error
Err returns the child's terminal fault, or nil on a normal completion.
func (*CallOutcome) GetItemsList ¶ added in v0.9.0
func (o *CallOutcome) GetItemsList() []*data.ItemDefinition
GetItemsList returns no payload — the outputs are committed into the caller scope loop-side before delivery, so the outcome itself carries only the fault.
func (*CallOutcome) Type ¶ added in v0.9.0
func (o *CallOutcome) Type() flow.EventTrigger
Type returns the internal call-completion trigger.
type CallableRef ¶ added in v0.12.0
type CallableRef struct {
// Namespace is the target document's namespace URI, empty for a
// reference inside the calling document.
Namespace string
// Key is the callable's local name — the process key for an unqualified
// reference.
Key string
}
CallableRef is what a Call Activity names: a key, optionally qualified by the namespace of the definitions document that declared the callable.
BPMN types `calledElement` as a plain String and fixes no naming convention for it, and a reference into another document is meaningful only through that document's <import>. So the engine owns no convention either: an UNQUALIFIED reference (Namespace == "") names a registry key directly, and a qualified one names a callable the host must map, because only the host knows what it registered the other document's definitions under.
type CallableResolver ¶ added in v0.12.0
type CallableResolver interface {
// ResolveCallable answers the registry key ref maps onto. Returning an
// error fails the CALL — the caller's activity faults and the error chain
// catches it at the Call Activity node — never the engine.
ResolveCallable(ctx context.Context, ref CallableRef) (string, error)
}
CallableResolver turns a callable reference into the key the engine's process registry serves.
It is a HOST contract, supplied once per engine, and it is consulted at call time for every call — including unqualified ones, so a host that maps keys (a tenant prefix, a naming convention) sees them all and a resolver can be reasoned about from its own code. The engine calls it outside every engine lock, because it is host code and may do anything, including calling back into the engine.
type CallableResolverFunc ¶ added in v0.12.0
type CallableResolverFunc func(context.Context, CallableRef) (string, error)
CallableResolverFunc adapts a plain function to CallableResolver, so a host with a one-line mapping writes no type (the http.HandlerFunc idiom).
func (CallableResolverFunc) ResolveCallable ¶ added in v0.12.0
func (f CallableResolverFunc) ResolveCallable( ctx context.Context, ref CallableRef, ) (string, error)
ResolveCallable calls f, refusing a nil function rather than panicking on it. CallableResolverFunc(nil) is a NON-nil CallableResolver — the interface check just below this file relies on exactly that — so it passes thresher.WithCallableResolver's nil guard and would otherwise surface as a panic inside the engine at the first call, arbitrarily far from the mistake.
type ChildProcess ¶ added in v0.9.0
type ChildProcess interface {
// ID returns the child instance id.
ID() string
// Key returns the RESOLVED registry key the call bound — what the host's
// CallableResolver answered, which is not the reference the document
// wrote when a namespace qualified it. The caller records it on the call
// fact beside the version, so an audit shows which registration actually
// ran rather than which one was asked for (ADR-023 v.5 §2.7, §6).
Key() string
// Version returns the RESOLVED 1-based version the call bound — the pinned
// version, or the concrete latest-at-launch version for a version-0 call.
// The caller records it on the call fact (the audit point, ADR-023 §6).
Version() int
// Done is closed when the child reaches a terminal state (Completed or
// Terminated) — a guaranteed, never-dropped signal.
Done() <-chan struct{}
// Failed reports the child's terminal fault: the terminal error if the
// child ended abnormally, else nil (a normal completion, or a plain
// cancellation that carried no fault). Read after Done is closed.
Failed() error
// Outputs reads the named data after completion — the declared Output
// parameters of the Call Activity, the call contract's return values —
// in the order of names. A child with a declared contract serves its
// collected result, and a name it declared optional but never produced
// comes back as a nil slot (nothing flows); a contract-less child reads
// its root scope, where a missing name is a classified error.
Outputs(names []string) ([]data.Data, error)
// Terminate ends the child (the cancel cascade). Idempotent.
Terminate()
}
ChildProcess is the caller loop's watch handle onto a launched child instance. The loop starts one watcher goroutine that blocks on Done, then reads Outputs (on normal completion) or Failed (on an abnormal terminal) and resumes the parked caller track. Terminate ends the child when the call's episode dies (the cancel cascade, ADR-023 §2.7).
type Decision ¶
Decision is the outcome of an ActivationJoin step: the gateway either fired (with a promoted survivor and the absorbed merged track ids), aborted (its activation rule can no longer be satisfied — the instance must fail), or neither (the arrival parks).
type DefaultCallableResolver ¶ added in v0.12.0
type DefaultCallableResolver struct{}
DefaultCallableResolver is the resolver the engine uses when the host supplies none: the unqualified case is exact — the reference IS the key — and a qualified one is refused by name.
Refusing is the only honest answer available to it. Taking the local part would silently call whatever the host happens to have registered under a name that merely coincides, and inventing a composite key would be a naming convention the standard declines to give. A host that never references another document therefore configures nothing, and one that does is told exactly which namespace it must teach the engine about.
func (DefaultCallableResolver) ResolveCallable ¶ added in v0.12.0
func (DefaultCallableResolver) ResolveCallable( _ context.Context, ref CallableRef, ) (string, error)
ResolveCallable answers ref.Key for an unqualified reference and refuses a qualified one, naming the namespace and the key.
type FlowChecker ¶
type FlowChecker interface {
// CheckFlows returns the subset of flows still reachable for node — those
// with a live token somewhere on a backward path from the flow's source to
// the start. Reachability is structural (condition-ignoring) and
// cycle-guarded.
CheckFlows(node flow.Node, flows []*flow.SequenceFlow) ([]*flow.SequenceFlow, error)
}
FlowChecker answers reachability for a synchronizing join. It is implemented by the instance (which owns the static node graph and the live track positions) and is consulted only from the instance loop, so the live-token set it reads is consistent (ADR-005 v.2 §2.10, SRD-022 §6).
type Frame ¶
type Frame interface {
// InstantiateInputs creates the node's input instances in the frame.
InstantiateInputs(defs []*data.Parameter) error
// InstantiateOutputs creates the node's output instances in the frame.
InstantiateOutputs(defs []*data.Parameter) error
// LoadProperties creates the node's property instances in the frame.
LoadProperties(defs []*data.Property) error
// Inputs returns the node's instantiated input parameters.
Inputs() []*data.Parameter
// Outputs returns the node's instantiated output parameters.
Outputs() []*data.Parameter
// GetDataByID returns the data whose ItemDefinition id is id, resolving
// frame-first then walking the container scopes.
GetDataByID(id string) (data.Data, error)
// GetData returns the data resolved by name, frame-first then walking the
// container scopes (SRD-063 FR-5 resolves a DataObject by its scope name).
GetData(name string) (data.Data, error)
// DataStores returns the engine-global Data Store registry a
// DataStoreReference association resolves its store from (SRD-068 FR-4). It
// is shared across instances; a nil registry means none is wired for this
// frame (a transient evaluation frame), and a store-backed association fails
// fast rather than dereferencing it.
DataStores() datastore.Registry
// ExpressionEngine returns the engine a data association's transformation
// or assignment evaluates through (ADR-011 §2.4, ADR-002). Like
// DataStores it is engine-global and shared across instances, and nil
// means none is wired for this frame (a transient evaluation frame) — an
// association carrying an expression then fails fast rather than
// dereferencing it, while a plain copy is unaffected.
ExpressionEngine() expression.Engine
// RecordDataMovement notes a Data Object / Data Store read or write for
// observability (SRD-063 / SRD-068): the reroute records each movement and
// the track reports them as KindDataObject / KindDataStore facts after the
// node's data phases. engineStore selects Data Store vs Data Object, write
// selects outbound vs inbound; storeRef is empty for a Data Object.
RecordDataMovement(engineStore, write bool, name, storeRef string)
// SetReceived stages the payload item captured from THIS delivery's
// fired event definition (ADR-006 v.5 §2.9.1, SRD-085 FR-1): the
// receiving execution captures at delivery, the frame carries, and a
// catch node's UploadData binds from Received — never from node
// state, so N concurrent deliveries can never observe each other's
// payloads. nil clears.
SetReceived(item *data.ItemDefinition)
// Received returns the payload item staged by SetReceived, nil when
// this execution carries no delivery payload.
Received() *data.ItemDefinition
}
Frame is the per-execution data-binding surface a node's LoadData/UploadData operate on. It is the narrow public view of the data-plane frame (the concrete implementation lives in internal/scope); a node sees only what it needs to instantiate and read its own data.
type GuardEval ¶
type GuardEval func(cond data.FormalExpression) (bool, error)
GuardEval evaluates a Complex gateway's data guard against process-level data. It is supplied by the caller (the instance, built over its root data scope + the expression engine) so the gateway can test a triple's condition at a point — Activate / Recheck — that has no per-node execution frame. A nil cond is true (ADR-005 v.3 §2.11).
type NodeDataConsumer ¶
type NodeDataConsumer interface {
flow.Node
// LoadData loads the node's data into the execution Frame.
LoadData(context.Context, Frame) error
}
NodeDataConsumer is implemented by nodes that consume data: LoadData instantiates the node's inputs and properties in the execution Frame and fills the inputs from the node's incoming data associations. The track calls it before the node executes.
type NodeDataProducer ¶
type NodeDataProducer interface {
flow.Node
// UploadData yields the node's outputs through the execution Frame.
UploadData(context.Context, Frame) error
}
NodeDataProducer is implemented by nodes that produce data: UploadData fills the node's output instances in the execution Frame and pushes the outgoing data associations. The track calls it after a successful node execution, right before the Frame commit.
type NodeExecutor ¶
type NodeExecutor interface {
Exec(
ctx context.Context,
re renv.RuntimeEnvironment,
) ([]*flow.SequenceFlow, error)
}
NodeExecutor runs a single node and returns its valid outgoing sequence flows on success or an error on failure.
type ProcessCall ¶ added in v0.9.0
type ProcessCall struct {
Key string
// Namespace qualifies Key with the document that declared the callable,
// empty for a reference inside the calling document. The invoker hands
// the pair to the host's CallableResolver, which answers the registry key
// to serve (ADR-023 v.5 §2.7) — so what reaches the registry is the
// RESOLVED key, never this one.
Namespace string
ParentInstanceID string
CallNodeID string
Inputs []data.Data
// Outputs are the caller's declared output names — what it will read
// back from the child. A callee declaring a contract validates them at
// launch (ADR-040 §2.4, SRD-093 FR-10); a contract-less callee ignores
// them here and serves whatever its root scope holds at completion.
Outputs []string
Version int
}
ProcessCall is the resolved request the loop hands the invoker for one Call Activity execution: which process to launch (Key + Version), the inputs to seed into its root scope (already resolved and cloned at the caller's scope — the isolation contract), and the linkage that stitches the child's trace back to the caller (ParentInstanceID + CallNodeID).
type ProcessInvoker ¶ added in v0.9.0
type ProcessInvoker interface {
// InvokeProcess resolves the callable (Version 0 = latest-at-launch, else
// the pinned 1-based version), binds the call's Inputs into the child's
// root scope, launches the child, and returns its watch handle. A missing
// key/version is a classified error that fails the CALL, not the engine.
InvokeProcess(ctx context.Context, call ProcessCall) (ChildProcess, error)
}
ProcessInvoker launches a registered process as a CHILD instance on behalf of a Call Activity (ADR-023 v.1 §2.7): the reuse boundary. It is implemented by the engine (the thresher) and consumed by the instance loop — never by node code, so a node cannot launch a process outside the loop's call protocol (§4.1). An Instance carries a nil invoker for a library embedder without a thresher; a call then fails fast with a classified no-invoker error.
type ReachabilityJoin ¶
type ReachabilityJoin interface {
SynchronizingJoin
// Recheck re-prunes the join's now-unreachable incoming flows via fc and
// reports completion without a new arrival. On completion it returns the
// promoted survivor track id and the absorbed (merged) track ids.
Recheck(fc FlowChecker) (complete bool, survivor string, merged []string)
// IsTrailing reports whether arrivingTrackID reached the join after it had
// already fired without being recorded — a late arrival (a reachability fire
// can precede a branch that was deemed unreachable) that must be consumed, not
// parked. Atomic, so the answer is consistent with this track's own Arrive.
IsTrailing(arrivingTrackID string) bool
}
ReachabilityJoin is a SynchronizingJoin whose completion is non-local: it fires only when no live token can still reach an un-marked incoming flow. The owning loop supplies reachability through a FlowChecker and re-checks the join when a token parks at it and on every token death (ADR-005 v.2 §2.10).
type SynchronizingJoin ¶
type SynchronizingJoin interface {
NodeExecutor
// Arrive records the arrival of a token on incomingFlowID from
// arrivingTrackID, reporting whether the join is now complete and which
// flow ids merged into this completion.
Arrive(incomingFlowID, arrivingTrackID string) (complete bool, merged []string)
}
SynchronizingJoin is a NodeExecutor that also synchronizes multiple incoming flows before it executes (a converging parallel/inclusive gateway).
type WaitHolders ¶ added in v0.10.0
type WaitHolders interface {
// HoldTimer registers a timer wait's ABSOLUTE deadline with the engine
// timer service, keyed to (instanceID, trackID). At the deadline the
// service wakes the instance, passing eDef as the trigger that fires the
// woken timer node through. cycles carries a repeating timer's remaining
// count. An error means the hold was NOT taken — the caller must keep the
// wait resident (fall back to the in-hub waiter) rather than lose the timer.
// kind says whether the deadline belongs to the parked node or to a
// boundary guarding it, which decides how the wake delivers it (WaitKind).
HoldTimer(
instanceID, trackID string,
eDef flow.EventDefinition,
deadline time.Time,
cycles int,
kind WaitKind,
) error
// HoldSubscription registers a message/signal wait's hub subscription
// against the ENGINE rather than the instance, keyed to
// (instanceID, trackID): the subscription outlives the instance's
// goroutines, so a trigger arriving at a released instance reaches the
// holder and wakes it.
//
// convKeys are the instance's conversation key VALUES at arm time — what
// the instance's own registration would have contributed, so the holder
// subscribes to exactly the same conversation (ADR-016). A foreign
// conversation is therefore filtered by the broker and never wakes the
// instance; an empty slice subscribes wildcard, as an un-keyed wait does.
//
// An error means the hold was NOT taken and the caller must keep the wait
// resident (registering its own subscription) rather than lose the trigger.
HoldSubscription(
instanceID, trackID string,
eDef flow.EventDefinition,
convKeys []string,
kind WaitKind,
) error
// HoldTask registers a parked human task against the ENGINE, keyed to
// (instanceID, trackID). Unlike a timer or a subscription there is nothing
// to subscribe: the task already lives in the distributor's inbox
// independent of the instance's residency (ADR-020). The hold only records
// WHICH track the task belongs to, so a Take/Complete on it can wake a
// released instance. An error means the hold was NOT taken and the caller
// must keep the wait resident.
HoldTask(instanceID, trackID, taskID string) error
// ReleaseWaits withdraws EVERY hold taken for a track — its deadline, its
// subscriptions, or the whole set an Event-Based Gateway armed. Called when
// the wait fires, when the instance tears down, and (the EBG case) when one
// arm wins and its siblings must be released. Idempotent: withdrawing
// unknown holds is a no-op.
ReleaseWaits(instanceID, trackID string)
}
WaitHolders is the engine-level DURABLE holder registry a dehydratable wait registers with at ARM time (ADR-007 v.2 §2.4, SRD-071 FR-3): the trigger source that outlives the instance's goroutines and wakes it on fire. It is implemented by the engine (the thresher) and consumed by the instance loop — a released instance never receives a trigger at its own (vanished) loop; the holder does, and it drives the wake.
An Instance carries a nil WaitHolders for a library embedder without a thresher, or when checkpointing is off: every wait then stays resident (no wait releases without a holder that can wake it — the ADR §2.4 safety). M3 covers the timer kind; the message/signal and human-task holders (M4/M6) extend this seam.
type WaitKind ¶ added in v0.10.0
type WaitKind uint8
WaitKind names WHAT a held wait belongs to. The set is closed by the BPMN object model — a trigger either belongs to the wait a token is parked on, or to a boundary event guarding the activity it sits in — so this is a named constant rather than a boolean flag: `WaitBoundary` says what it means at the call site, `true` does not, and no invalid combination can be expressed.
It matters because the two wake DIFFERENTLY. A node wait's trigger fires through the parked node itself, so the wake carries it as a pending trigger and the woken track continues from there. A boundary's trigger belongs to the boundary, not to the guarded node: the wake is trigger-ABSENT, the restored boundary re-arms at its recorded deadline, and the fire that follows is a fork at the boundary event with the guarded track as its parent — interrupting cancels that parent, non-interrupting leaves it running — which is precisely what the loop's fireBoundary produces.