1 import Base 2 import ./change_tick.bend as Tick 3 import ./trace_facts.bend as Facts 4 5 # Detached copied facts. No World, registration, cursor or stream owner is held. 6 # Copied system identity for presentation: namespace/id and optional name. 7 # It is metadata, not a registration owner or capability for invoking that system. 8 type Identity is Data: 9 Identity{namespace:U32,id:U32,name:Maybe<&2,String>} 10 # Completion status of one executed system, retaining its detached Data error. 11 type Status is Data: 12 SystemOk{} 13 SystemError{error:Error} 14 # Schedule-level completion distinguishes failure, admission rejection and missing 15 # execution from success; it is not a replacement for the retained App run result. 16 type Completion is Data: 17 EndOk{} 18 EndFailed{error:Error} 19 EndRejected{} 20 EndMissing{} 21 # Ordered copied execution facts: schedule/phase boundaries, systems, operations 22 # and admission outcomes. Facts describe the actual run; they grant no mutable 23 # World access and do not imply that a failed transaction committed its operations. 24 type TraceEvent is Data: 25 ScheduleStarted{namespace:U32,name:String} 26 PhaseEntered{name:String} 27 SystemCompleted{identity:Identity,before:Tick.Tick,after:Tick.Tick,status:Status} 28 OperationObserved{identity:Identity,fact:Facts.Fact} 29 BarrierOperationObserved{fact:Facts.Fact} 30 ExecutionOperationObserved{fact:Facts.Fact} 31 SystemSkipped{id:U32} 32 DeferredApplied{} 33 ScheduleEnded{outcome:Completion} 34 ScheduleAdmissionRefused{namespace:U32,name:String,outcome:Completion} 35 # Detached delivery value returned after execution. Disabled differs from an 36 # enabled Ready batch with no events. No live reader or stream retention is held; 37 # keeping or dropping a copied batch does not acknowledge an ECS event reader. 38 type Batch is Data: 39 Disabled{} 40 Ready{events:List<&2,TraceEvent>} 41 42 # Each subscription is its actual affine state owner, not a copied callback. 43 # The erased reusable step is supplied as a top-level definition. 44 type Subscriber TraceEvent -> State> is Type: 45 Subscriber{state:State} 46 # Wrap the supplied actual State owner with a reusable erased step definition. 47 # This creates a local subscriber, not an ECS registration or automatic callback. 48 def subscribe(~State:Type,~Error:Data,~step:State -> TraceEvent -> State,state:State) -> Subscriber: 49 Subscriber{state} 50 # Consume the subscriber and return its actual State for reuse or explicit 51 # disposal. No implicit user cleanup/destructor callback is invoked. 52 def unsubscribe(~State:Type,~Error:Data,~step:State -> TraceEvent -> State,value:Subscriber) -> State: 53 match value: 54 case Subscriber{state}:state 55 # Replace subscription state and return the displaced actual owner separately. 56 # Neither owner is copied or silently restored into ECS storage. 57 def reset(~State:Type,~Error:Data,~step:State -> TraceEvent -> State,value:Subscriber,incoming:State) -> Subscriber & State: 58 match value: 59 case Subscriber{state}:(Subscriber{incoming},state) 60 def delivered(~State:Type,~Error:Data,~step:State -> TraceEvent -> State,events:List<&2,TraceEvent>,state:State) -> State: 61 match events: 62 case []:state 63 case event <> rest:delivered(~State,~Error,~step,rest,step(state,event)) 64 # Thread subscriber State through Ready events in list order; Disabled returns 65 # the subscriber unchanged. Delivery is explicitly called after the ECS run. 66 # Any external printing/file IO performed by a consumer is outside ECS rollback, 67 # not an in-transaction observer. See examples/scheduled-health/application.bend. 68 def deliver(~State:Type,~Error:Data,~step:State -> TraceEvent -> State,value:Subscriber,batch:Batch) -> Subscriber: 69 match value batch: 70 case subscriber Disabled{}:subscriber 71 case Subscriber{state} Ready{events}:Subscriber{delivered(~State,~Error,~step,events,state)} 72 73 # Generic owner composition has no arity or subscriber-count bound. Order is 74 # left then right; each reusable delivery function can itself be another pair. 75 # Deliver the same detached batch to two actual owners, left then right, returning 76 # both results. Compose this adapter recursively for additional subscribers; this 77 # is owner composition, not a global subscription registry. 78 def both(~Left:Type,~Right:Type,~Error:Data,~left:Left -> Batch -> Left,~right:Right -> Batch -> Right,value:Left & Right,+batch:Batch) -> Left & Right: 79 match value: 80 case (first,second):(left(first,batch),right(second,batch))