-module(flow_dispatch). -export([start/4, guard_passes/3]). %% Bridge from "an activity matched a trigger" to "a flow started with %% that activity as input" (fed-sx-triggers Phase 3). A NATIVE call into %% next/flow (flow_store) — the engine is Erlang-on-SX too, so there is %% no cross-guest FFI: the kernel and the workflow engine share one %% runtime. %% %% start(Spec, Activity, ActorState, Cfg) %% -> {ok, FlowId, {ActivityCid, TriggerCid, FlowId}} (audit triple) %% | {error, Reason} %% %% The flow named in Spec is started with the activity bound into its %% input environment, so flow steps can read the activity, the actor id, %% and the trigger cid (the audit chain). Flow-start failures — an %% unknown flow name, or a crashing first step (flow_store isolates the %% raise) — come back as {error, Reason}, never raised, so the fan-out %% caller is insulated from one flow's failure. start(Spec, Activity, ActorState, _Cfg) -> FlowName = trigger_registry:spec_flow_name(Spec), TriggerCid = trigger_registry:spec_cid(Spec), ActivityCid = activity_cid(Activity), Input = [{activity, Activity}, {actor, actor_id_of(ActorState, Activity)}, {trigger_cid, TriggerCid}], case flow_store:start(FlowName, Input) of {ok, FlowId, _Result} -> {ok, FlowId, {ActivityCid, TriggerCid, FlowId}}; {error, Reason} -> {error, Reason} end. %% guard_passes(Spec, Activity, ActorState) — a spec fires when its %% actor-scope admits the activity's actor AND its guard (if any) %% returns true. An `any` scope and an `undefined` guard always pass; %% the guard lets one activity-type bind multiple flows with %% discriminators. guard_passes(Spec, Activity, ActorState) -> scope_ok(trigger_registry:spec_actor_scope(Spec), Activity) andalso guard_ok(trigger_registry:spec_guard(Spec), Activity, ActorState). scope_ok(any, _Activity) -> true; scope_ok(Scope, Activity) -> case envelope:get_field(actor, Activity) of {ok, Scope} -> true; _ -> false end. guard_ok(undefined, _Activity, _ActorState) -> true; guard_ok(Guard, Activity, ActorState) when is_function(Guard, 2) -> Guard(Activity, ActorState); guard_ok(_, _, _) -> false. %% ── helpers ───────────────────────────────────────────────────── activity_cid(Activity) -> case envelope:get_field(id, Activity) of {ok, Cid} -> Cid; _ -> undefined end. %% actor_id_of/2 — prefer the receiving actor's id (ActorState carries %% {actor_id, _}); fall back to the activity's :actor. Reading %% ActorState as a proplist keeps this decoupled from actor_state's %% internal shape and testable with a plain [{actor_id, _}] stand-in. actor_id_of(ActorState, Activity) -> case envelope:get_field(actor_id, ActorState) of {ok, Id} -> Id; _ -> case envelope:get_field(actor, Activity) of {ok, A} -> A; _ -> undefined end end.