1 import Base 2 import ./change_tick.bend as Tick 3 import ./world.bend as W 4 import ./registration.bend as Reg 5 6 # Runner identity is a closed erased index; each registration owns its Local. 7 # Runner completion retaining World and the returned Local owner on both 8 # success and failure. Only success returns Out; failure does not automatically 9 # restore Local or recover body outputs. Runner defines World rollback behavior. 10 type Outcome is Type: 11 Succeeded{world: World,local: L,output: Out} 12 Failed{world: World,local: L,error: Error} 13 # Affine per-registration Local owner with runtime namespace and actual 14 # registration metadata. L may be arbitrary Type; no other registration shares 15 # it implicitly. Raw construction is trusted administration. 16 type Instance -> L -> Args -> Outcome,L,Error,Out>> is Type: 17 Instance{namespace: U32,id: U32,name: String,access: List<&2,String>,local: L} 18 # Registered returns World and the Local instance; RegistrationRejected 19 # returns World and the original Local owner without invoking the runner. 20 type Registered -> L -> Args -> Outcome,L,Error,Out>> is Type: 21 Registered{world: W.World,instance: Instance} 22 RegistrationRejected{world: W.World,local: L} 23 # Completed returns World, retained Local instance and success/error result. 24 # Refused returns those owners and original Args before runner execution. 25 # Runner failure retains its returned Local modifications, not an old copy. 26 type Run -> L -> Args -> Outcome,L,Error,Out>> is Type: 27 Completed{world: W.World,instance: Instance,result: Result<&2,&1,Error,Out>,tick:Tick.Tick} 28 Refused{world: W.World,instance: Instance,args: Args} 29 # Disposed returns World and output of the explicit disposeLocal callback. 30 # DisposalRejected returns World and original instance without invoking that 31 # callback; no implicit finalizer is invented by dropping a result variant. 32 type Disposal -> L -> Args -> Outcome,L,Error,Out>,Disposed: Type> is Type: 33 Disposed{world: W.World,output: Disposed} 34 DisposalRejected{world: W.World,instance: Instance} 35 36 # Rank-2 provider: callbacks get declared operations, not the concrete cell. 37 # Trusted implementation cell for opaque Local capability callbacks. 38 # Ordinary bodies receive abstract H and Ops, not this concrete owner. 39 type Cell is Type: 40 Cell{local: L} 41 # Opaque Local read/replacement operations. read returns detached Data V; 42 # replace consumes L and returns H without a displaced-owner receipt. 43 # Use explicit recovery APIs if replacement must return the displaced owner. 44 type Ops is Type: 45 Ops{read: H -> H & V,replace: H -> L -> H} 46 def projected(~L: Type,~V: Data,result: L & V) -> Cell & V: 47 (local,value) = result 48 (Cell{local},value) 49 def read_cell(~L: Type,~V: Data,~project: L -> L & V,cell: Cell) -> Cell & V: 50 match cell: 51 case Cell{local}: projected(~L,~V,project(local)) 52 53 def replace_cell(~L: Type,cell: Cell,local: L) -> Cell: 54 match cell: 55 case Cell{old}: Cell{local} 56 def modify_cell(~L: Type,cell: Cell,modify: L -> L) -> Cell: 57 match cell: 58 case Cell{local}: Cell{modify(local)} 59 def unwrapped(~L: Type,~Out: Type,result: Cell & Out) -> L & Out: 60 match result: 61 case (Cell{local},out): (local,out) 62 # Run a universally quantified Local body through read/replace capabilities. 63 # Returns the retained Local owner and Out. project must preserve its L owner; 64 # replacement does not invoke a finalizer or journal arbitrary mutations. 65 # Contract and accepted scenarios: docs/design/public-local-policy.md. 66 def provide(~L: Type,~V: Data,~Out: Type,~project: L -> L & V,~consume: @-H: Type -> Ops -> H -> H & Out,local: L) -> L & Out: 67 unwrapped(~L,~Out,consume(Cell,Ops{read_cell(~L,~V,~project),replace_cell(~L)},Cell{local})) 68 69 # Local capability invocation with affine Args and Out, returning retained 70 # L. As with provide, the opaque body gets no concrete cell and the caller 71 # must handle any operation-specific ownership/recovery it requires. 72 def provide_with(~L: Type,~V: Data,~Args: Type,~Out: Type,~project: L -> L & V,~consume: @-H: Type -> Ops -> H -> Args -> H & Out,local: L,args: Args) -> L & Out: 73 unwrapped(~L,~Out,consume(Cell,Ops{read_cell(~L,~V,~project),replace_cell(~L)},Cell{local},args)) 74 75 def register_result(~S: Data,~C: Type,~R: Type,~E: Data,~L: Type,~Args: Type,~Error: Data,~Out: Type,~runner: W.World -> L -> Args -> Outcome,L,Error,Out>,registered: W.World & W.Registration,+namespace: U32,+name: String,+access: List<&2,String>,local: L) -> Registered: 76 match registered: 77 case (world,W.Registered{id}): Registered{world,Instance{namespace,id,name,access,local}} 78 case (world,W.RegistrationRejected{_}): RegistrationRejected{world,local} 79 def register_named(~S: Data,~C: Type,~R: Type,~E: Data,~L: Type,~Args: Type,~Error: Data,~Out: Type,~runner: W.World -> L -> Args -> Outcome,L,Error,Out>,named: W.World & U32,+name: String,+access: List<&2,String>,local: L) -> Registered: 80 (world,+namespace) = named 81 register_result(~S,~C,~R,~E,~L,~Args,~Error,~Out,~runner,W.register(~S,~C,~R,~E,world,name,access),namespace,name,access,local) 82 # Register a closed runner and consume its initial Local owner. Returns 83 # instance on success, original Local on refusal, and World on both paths. 84 # Raw access strings are trusted metadata; ordinary wrappers derive them. 85 def register(~S: Data,~C: Type,~R: Type,~E: Data,~L: Type,~Args: Type,~Error: Data,~Out: Type,~runner: W.World -> L -> Args -> Outcome,L,Error,Out>,world: W.World,name: String,access: List<&2,String>,local: L) -> Registered: 86 register_named(~S,~C,~R,~E,~L,~Args,~Error,~Out,~runner,W.namespace(~S,~C,~R,~E,world),name,access,local) 87 88 def completed(~S: Data,~C: Type,~R: Type,~E: Data,~L: Type,~Args: Type,~Error: Data,~Out: Type,~runner: W.World -> L -> Args -> Outcome,L,Error,Out>,namespace: U32,id: U32,name: String,access: List<&2,String>,tick:Tick.Tick,outcome: Outcome,L,Error,Out>) -> Run: 89 match outcome: 90 case Succeeded{world,local,out}: Completed{W.leave_run(~S,~C,~R,~E,world),Instance{namespace,id,name,access,local},Done{out},tick} 91 case Failed{world,local,error}: Completed{W.leave_run(~S,~C,~R,~E,world),Instance{namespace,id,name,access,local},Fail{error},tick} 92 def entered(~S: Data,~C: Type,~R: Type,~E: Data,~L: Type,~Args: Type,~Error: Data,~Out: Type,~runner: W.World -> L -> Args -> Outcome,L,Error,Out>,namespace: U32,id: U32,name: String,access: List<&2,String>,local:L,args:Args,observed:W.World & Tick.Tick) -> Run: 93 match observed: 94 case (world,tick):completed(~S,~C,~R,~E,~L,~Args,~Error,~Out,~runner,namespace,id,name,access,tick,runner(world,local,args)) 95 def checked(~S: Data,~C: Type,~R: Type,~E: Data,~L: Type,~Args: Type,~Error: Data,~Out: Type,~runner: W.World -> L -> Args -> Outcome,L,Error,Out>,allowed: Bool,instance: Instance,world: W.World,args: Args) -> Run: 96 match allowed instance: 97 case False{} instance: Refused{world,instance,args} 98 case True{} Instance{namespace,id,name,access,local}: entered(~S,~C,~R,~E,~L,~Args,~Error,~Out,~runner,namespace,id,name,access,local,args,W.enter_run(~S,~C,~R,~E,world)) 99 # Validate namespace and live registration metadata before invoking runner. 100 # Refused returns original Args/instance/World. Completed retains the Local 101 # returned by the runner even on error; World rollback is the runner's job. 102 # This primitive has no query lifecycle cursor of its own. 103 def run(~S: Data,~C: Type,~R: Type,~E: Data,~L: Type,~Args: Type,~Error: Data,~Out: Type,~runner: W.World -> L -> Args -> Outcome,L,Error,Out>,instance: Instance,world: W.World,args: Args) -> Run: 104 match instance world: 105 case Instance{+namespace,+id,+name,+access,local} W.World{+actual,allocator,store,resource,events,pending,+registrations,nextSystemId,clock}: 106 checked(~S,~C,~R,~E,~L,~Args,~Error,~Out,~runner,Bool.and(U32.is_eq(namespace,actual),W.registration_list_matches(registrations,id,name,access)),Instance{namespace,id,name,access,local},W.World{actual,allocator,store,resource,events,pending,registrations,nextSystemId,clock},args) 107 # Return the same Local instance without invoking a body or modifying L. 108 def skip(~S: Data,~C: Type,~R: Type,~E: Data,~L: Type,~Args: Type,~Error: Data,~Out: Type,~runner: W.World -> L -> Args -> Outcome,L,Error,Out>,instance: Instance) -> Instance: 109 instance 110 111 # Return the retained instance and registration ID without running a body. 112 # An observed ID does not bypass namespace/live-registration validation. 113 def identity(~S: Data,~C: Type,~R: Type,~E: Data,~L: Type,~Args: Type,~Error: Data,~Out: Type,~runner: W.World -> L -> Args -> Outcome,L,Error,Out>,instance: Instance) -> Instance & U32: 114 match instance: 115 case Instance{namespace,+id,name,access,local}: (Instance{namespace,id,name,access,local},id) 116 def observed(~S: Data,~C: Type,~R: Type,~E: Data,~L: Type,~Args: Type,~Error: Data,~Out: Type,~runner: W.World -> L -> Args -> Outcome,L,Error,Out>,~V: Data,namespace: U32,id: U32,name: String,access: List<&2,String>,value: L & V) -> Instance & V: 117 (local,value) = value 118 (Instance{namespace,id,name,access,local},value) 119 # Return the instance with a detached Data projection of Local. project 120 # must return its L owner; this trusted callback is not a World transaction 121 # and grants no authority to execute a registration in another World. 122 def observe(~S: Data,~C: Type,~R: Type,~E: Data,~L: Type,~Args: Type,~Error: Data,~Out: Type,~runner: W.World -> L -> Args -> Outcome,L,Error,Out>,~V: Data,~project: L -> L & V,instance: Instance) -> Instance & V: 123 match instance: 124 case Instance{namespace,id,name,access,local}: observed(~S,~C,~R,~E,~L,~Args,~Error,~Out,~runner,~V,namespace,id,name,access,project(local)) 125 126 def disposal(~S: Data,~C: Type,~R: Type,~E: Data,~L: Type,~Args: Type,~Error: Data,~Out: Type,~runner: W.World -> L -> Args -> Outcome,L,Error,Out>,~Disposed: Type,~disposeLocal: L -> Disposed,instance: Instance,result: W.World & Bool) -> Disposal: 127 match instance result: 128 case instance, (world,False{}): DisposalRejected{world,instance} 129 case Instance{_,_,_,_,local}, (world,True{}): Disposed{world,disposeLocal(local)} 130 # Remove the matching live registration, then consume L once with the 131 # explicit disposeLocal callback and return its output. Refusal retains 132 # instance/World without calling disposeLocal. Arbitrary callback effects 133 # are outside ECS rollback; inspect the result instead of assuming disposal. 134 def dispose(~S: Data,~C: Type,~R: Type,~E: Data,~L: Type,~Args: Type,~Error: Data,~Out: Type,~runner: W.World -> L -> Args -> Outcome,L,Error,Out>,~Disposed: Type,~disposeLocal: L -> Disposed,instance: Instance,world: W.World) -> Disposal: 135 match instance: 136 case Instance{+namespace,+id,+name,+access,local}: disposal(~S,~C,~R,~E,~L,~Args,~Error,~Out,~runner,~Disposed,~disposeLocal,Instance{namespace,id,name,access,local},Reg.remove(~S,~C,~R,~E,world,namespace,id,name,access))