1 import Base 2 import ./lifetime_scope.bend as Sc 3 import ./lifetime_scope_world_spawn.bend as Spawn 4 import ./world.bend as W 5 import ./ordinary_schema.bend as Schema 6 import ./ordinary_schema_provision.bend as Provision 7 import ./relation_commands.bend as A 8 import ./relation_types.bend as G 9 import ./relation_failure_reader.bend as Rel 10 import ./ordinary_app_linked_cleanup.bend as LC 11 import ./ordinary_app_linked_cleanup_system.bend as LCS 12 import ./ordinary_component_clear_declaration.bend as Clear 13 import ./column.bend as Col 14 import ./ordinary_component_binding.bend as Binding 15 import ./capabilities.bend as Cap 16 import ./transaction.bend as T 17 import ./commands.bend as Cmd 18 import ./system.bend as Sys 19 20 # Scope metadata does not add a component/schema entry. Original owners survive. 21 # Trusted schema adapter: build an empty component store with scope membership 22 # and relation-graph wrappers. Component payload C may contain affine owners; 23 # empty constructs C once. Scope membership adds no component schema entry. 24 def empty_store(~S:Data,~C:Type,~empty:Unit -> C,unit:Unit) -> A.Store>: 25 A.empty(~S,~Sc.Store,Sc.empty(~S,~C,empty(unit))) 26 def counted(~S:Data,~C:Type,members:List<&2,Sc.Member>,graph:G.Graph,seen:C & List<&2,Maybe<&2,U32>>) -> A.Store> & List<&2,Maybe<&2,U32>>: 27 match seen: 28 case (components,counts):(A.Store{Sc.Store{components,members},graph},counts) 29 # Trusted provisioning adapter: run the original component counter through the 30 # scope/relation wrappers. Returns the wrapped store with its membership and 31 # graph owners threaded back; counting does not assign scopes or delete entities. 32 def count_store(~S:Data,~C:Type,~count:C -> List<&2,W.Handle> -> C & List<&2,Maybe<&2,U32>>,store:A.Store>,handles:List<&2,W.Handle>) -> A.Store> & List<&2,Maybe<&2,U32>>: 33 match store: 34 case A.Store{Sc.Store{components,members},graph}:counted(~S,~C,members,graph,count(components,handles)) 35 # Lift an ordinary schema to the scope/relation-wrapped Store type. 36 # Preserves the existing entries and resource schema; no synthetic scope component 37 # is registered. Use this during setup, not to change a live World's schema. 38 def schema(~S:Data,~C:Type,~R:Type,value:Schema.Schema) -> Schema.Schema>,R>: 39 match value: 40 case Schema.Schema{entries}:Schema.Schema{entries} 41 # Lift the matching schema provision with its empty/count adapters. 42 # The component/resource entries remain unchanged while the Store gains scope 43 # membership and relation state. Pass the result to ordinary-lifetime-scope-io.create. 44 # Concrete setup: test-fixtures/public-scopes/feature-timing-v1/fixture.bend: 45 # provision, create and scoped_binding; main.bend exercises deferred cleanup. 46 def provision(~S:Data,~C:Type,~R:Type,~empty:Unit -> C,~count:C -> List<&2,W.Handle> -> C & List<&2,Maybe<&2,U32>>,value:Provision.Provision) -> Provision.Provision>,R,empty_store(~S,~C,~empty),count_store(~S,~C,~count)>: 47 match value: 48 case Provision.Provision{value}:Provision.Provision{schema(~S,~C,~R,value)} 49 # Reuse the canonical Type-result request; rejected payloads are not forced to Data. 50 # Capabilities a lifetime-scope system body receives for payload type P. 51 # spawn consumes a scope/payload request and returns an owning SpawnResult; 52 # cleanup queues scope cleanup and returns QueueStatus. The opaque owner H must 53 # be threaded through both. P may be affine Type; a scope is metadata, not an 54 # entity owner or evidence that any member is currently live. 55 type Ops<-H:Type,-S:Data,-P:Type> is Type: 56 Ops{spawn:Cap.OwnedRequest & P,Sc.SpawnResult>,cleanup:Cap.Request,A.QueueStatus>} 57 # Advanced capability projection: consume Ops and obtain its owned spawn 58 # request operation. Normal system bodies use spawn to thread their opaque owner. 59 def spawn_cap(~H:Type,~S:Data,~P:Type,ops:Ops) -> Cap.OwnedRequest & P,Sc.SpawnResult>: 60 match ops: 61 case Ops{spawn,_}:spawn 62 # Advanced capability projection: consume Ops and obtain its cleanup request. 63 # Normal system bodies use cleanup; projection alone queues no work. 64 def cleanup_cap(~H:Type,~S:Data,~P:Type,ops:Ops) -> Cap.Request,A.QueueStatus>: 65 match ops: 66 case Ops{_,cleanup}:cleanup 67 # Request a deferred spawn in a scope from this World's namespace. 68 # Returns the updated opaque owner and either Spawned{handle}, a reserved handle 69 # not yet live, or SpawnRejected{payload,error}, returning the incoming affine P. 70 # A foreign-world scope is refused; reservation exhaustion also returns payload. 71 # Accepted requests retain P in deferred work. At an explicit command barrier, 72 # activation/populate precede membership assignment; the body does not see a live 73 # entity merely because this request succeeded. Only immediate refusal promises 74 # that payload result: a later failed system discards its Out and staged work, 75 # so placing rejected P inside Out does not make it a failed-system recovery API. 76 def spawn(~H:Type,~S:Data,~P:Type,~ops:Ops,owner:H,scope:Sc.Scope,payload:P) -> H & Sc.SpawnResult: 77 Cap.owned_request(~H,~(Sc.Scope & P),~Sc.SpawnResult,~spawn_cap(~H,~S,~P,ops),owner,(scope,payload)) 78 # Queue cleanup of the given scope, threading the opaque owner back. 79 # Queued means accepted deferred work, not completed deletion; a foreign scope 80 # returns MissingEntity without queuing cleanup. Membership is observed when the 81 # command is applied at an explicit barrier, so same-world/name scopes refer to 82 # the same group rather than distinct owners. 83 # Cleanup uses the supplied linked-relation descriptors and component clear route. 84 # An unscoped persistent entity survives only outside the linked deletion closure; 85 # an unscoped linked descendant can still be deleted, while ordinary links from 86 # survivors to removed targets are cleared. Queuing cleanup does not flush commands. 87 # See docs/parity/scopes.md for the qualified survivor and relation boundary. 88 def cleanup(~H:Type,~S:Data,~P:Type,~ops:Ops,owner:H,scope:Sc.Scope) -> H & A.QueueStatus: 89 Cap.request(~H,~Sc.Scope,~A.QueueStatus,~cleanup_cap(~H,~S,~P,ops),owner,scope) 90 def spawn_request(~S:Data,~C:Type,~R:Type,~E:Data,~P:Type,~populate:W.World>,R,A.Notice> -> W.Handle -> P -> W.World>,R,A.Notice>,owner:T.Tx>,R,A.Notice>,A.Notice>,input:Sc.Scope & P) -> T.Tx>,R,A.Notice>,A.Notice> & Sc.SpawnResult: 91 match input: 92 case (scope,payload):Spawn.tx_spawn_in(~S,~C,~R,~E,~P,~populate,owner,scope,payload) 93 def cleanup_request(~S:Data,~C:Type,~R:Type,~E:Data,~clear:C -> W.Handle -> C & List<&2,A.Notice>,~record:W.Handle -> A.Notice,~declarations:List<&2,Rel.Declaration>,owner:T.Tx>,R,A.Notice>,A.Notice>,scope:Sc.Scope) -> T.Tx>,R,A.Notice>,A.Notice> & A.QueueStatus: 94 Sc.tx_cleanup(~S,~C,~R,~E,~clear,~record,~LC.descriptors(~S,declarations),owner,scope) 95 # Trusted system-construction adapter for the canonical transaction owner. 96 # Supplies spawn/cleanup operations using the closed populate, clear, record and 97 # relation declarations. populate receives the full World at deferred application; 98 # clear must match its Clear declaration, and relation declarations must cover the 99 # links cleanup is expected to maintain. These callbacks are schema/engine setup, 100 # not extra runtime authority exposed to the polymorphic gameplay body. 101 def provide(~S:Data,~C:Type,~R:Type,~E:Data,~P:Type,~populate:W.World>,R,A.Notice> -> W.Handle -> P -> W.World>,R,A.Notice>,~clear:C -> W.Handle -> C & List<&2,A.Notice>,~record:W.Handle -> A.Notice,~declarations:List<&2,Rel.Declaration>) -> Ops>,R,A.Notice>,A.Notice>,S,P>: 102 Ops{Cap.OwnedRequest{spawn_request(~S,~C,~R,~E,~P,~populate)},Cap.Request{cleanup_request(~S,~C,~R,~E,~clear,~record,~declarations)}} 103 # Sys failure retains World/error only; body output is discarded by that existing 104 # contract. Request-level rejected payload recovery does not imply failed-Out recovery. 105 def returned(~S:Data,~C:Type,~R:Type,~E:Data,~Error:Data,~Out:Type,output:Out,result:T.Returned>,R,A.Notice>,Error>) -> Sys.Outcome>,R,A.Notice>,Error,Out>: 106 match result: 107 case T.Returned{world,T.Success{}}:Sys.Succeeded{world,output} 108 case T.Returned{world,T.Failure{error}}:Sys.Failed{world,error} 109 def invoked(~S:Data,~C:Type,~R:Type,~E:Data,~Error:Data,~Out:Type,seen:T.Tx>,R,A.Notice>,A.Notice> & (T.Outcome & Out)) -> Sys.Outcome>,R,A.Notice>,Error,Out>: 110 match seen: 111 case (tx,(outcome,output)):returned(~S,~C,~R,~E,~Error,~Out,output,Cmd.tx_finish(~S,~A.Store>,~R,~A.Notice,~Error,tx,outcome)) 112 # Execute one body with lifetime capabilities inside an ordinary transaction. 113 # Threads World through transaction finish: success returns Out; failure returns 114 # World/error and discards Out, including any affine payload stored only there. 115 # Failed staged spawns/cleanup are not published. Successful completion still 116 # leaves structural work pending until the caller applies an explicit barrier. 117 # Host IO and arbitrary trusted callback effects are not made reversible here. 118 def runner(~S:Data,~C:Type,~R:Type,~E:Data,~P:Type,~populate:W.World>,R,A.Notice> -> W.Handle -> P -> W.World>,R,A.Notice>,~clear:C -> W.Handle -> C & List<&2,A.Notice>,~record:W.Handle -> A.Notice,~declarations:List<&2,Rel.Declaration>,~Args:Type,~Error:Data,~Out:Type,~body:@-H:Type -> @-ops:Ops -> Args -> H -> H & (T.Outcome & Out),world:W.World>,R,A.Notice>,args:Args) -> Sys.Outcome>,R,A.Notice>,Error,Out>: 119 invoked(~S,~C,~R,~E,~Error,~Out,body(T.Tx>,R,A.Notice>,A.Notice>,provide(~S,~C,~R,~E,~P,~populate,~clear,~record,~declarations),args,T.begin(~W.World>,R,A.Notice>,~A.Notice,world))) 120 # Register a scope system with access derived from the component-clear 121 # certificate, relation declarations and LifetimeScope:write. Returns the ordinary 122 # Registry registration result with the returned World on success or refusal. 123 # Registration runs no body and applies no spawn/cleanup. Supply the matching 124 # trusted populate/clear routes, then retain the actual returned Registry for run. 125 def register(~S:Data,~C:Type,~R:Type,~E:Data,~P:Type,~populate:W.World>,R,A.Notice> -> W.Handle -> P -> W.World>,R,A.Notice>,~clear:C -> W.Handle -> C & List<&2,A.Notice>,~record:W.Handle -> A.Notice,~declarations:List<&2,Rel.Declaration>,~Args:Type,~Error:Data,~Out:Type,~body:@-H:Type -> @-ops:Ops -> Args -> H -> H & (T.Outcome & Out),~clearDeclaration:Clear.Declaration,clear>,world:W.World>,R,A.Notice>,name:String) -> Sys.RegistrationResult>,R,A.Notice,Args,Error,Out,runner(~S,~C,~R,~E,~P,~populate,~clear,~record,~declarations,~Args,~Error,~Out,~body)>: 126 Sys.register(~S,~A.Store>,~R,~A.Notice,~Args,~Error,~Out,~runner(~S,~C,~R,~E,~P,~populate,~clear,~record,~declarations,~Args,~Error,~Out,~body),world,name,List.append(&2,String,LCS.writes(Clear.keys(~S,~C,~A.Notice,~clear,clearDeclaration)),List.append(&2,String,LCS.relation_accesses(~S,declarations),["LifetimeScope:write"]))) 127 # Run the registered scope system, consuming its Registry, World and Args. 128 # Returns the updated Registry/World and success output or typed system failure; 129 # registration refusal returns the ordinary rejected-run Args. Failure discards 130 # body Out rather than returning affine contents separately. Earlier committed 131 # systems remain, and successful queued work requires a later explicit barrier. 132 # Current consumer: test-fixtures/public-scopes/feature-timing-v1/main.bend. 133 def run(~S:Data,~C:Type,~R:Type,~E:Data,~P:Type,~populate:W.World>,R,A.Notice> -> W.Handle -> P -> W.World>,R,A.Notice>,~clear:C -> W.Handle -> C & List<&2,A.Notice>,~record:W.Handle -> A.Notice,~declarations:List<&2,Rel.Declaration>,~Args:Type,~Error:Data,~Out:Type,~body:@-H:Type -> @-ops:Ops -> Args -> H -> H & (T.Outcome & Out),owner:Sys.Registry>,R,A.Notice,Args,Error,Out,runner(~S,~C,~R,~E,~P,~populate,~clear,~record,~declarations,~Args,~Error,~Out,~body)>,world:W.World>,R,A.Notice>,args:Args) -> Sys.RunResult>,R,A.Notice,Args,Error,Out,runner(~S,~C,~R,~E,~P,~populate,~clear,~record,~declarations,~Args,~Error,~Out,~body)>: 134 Sys.run(~S,~A.Store>,~R,~A.Notice,~Args,~Error,~Out,~runner(~S,~C,~R,~E,~P,~populate,~clear,~record,~declarations,~Args,~Error,~Out,~body),owner,world,args) 135 136 # Project ordinary families through the metadata wrapper without changing owners. 137 # Trusted storage-adapter remainder for a projected component column. 138 # Retains the original component rest, scope membership and relation graph while 139 # the selected column is out. Gameplay should use the derived binding instead 140 # of constructing or unpacking this storage wrapper. 141 type ComponentRest is Type: 142 ComponentRest{rest:Rest,members:List<&2,Sc.Member>,graph:G.Graph} 143 def taken_column(~S:Data,~C:Type,~Rest:Type,~P:Type,members:List<&2,Sc.Member>,graph:G.Graph,seen:Rest & Col.Column) -> ComponentRest & Col.Column: 144 match seen: 145 case (rest,column):(ComponentRest{rest,members,graph},column) 146 # Trusted schema projection: take one component column through scope/relation 147 # wrappers, returning ComponentRest with every sibling and metadata owner. 148 # Pair it with put_column from the same original store projection. 149 def take_column(~S:Data,~C:Type,~Rest:Type,~P:Type,~take:C -> Rest & Col.Column,store:A.Store>) -> ComponentRest & Col.Column: 150 match store: 151 case A.Store{Sc.Store{components,members},graph}:taken_column(~S,~C,~Rest,~P,members,graph,take(components)) 152 # Trusted schema reconstruction: consume the matching ComponentRest and column 153 # to rebuild the wrapped Store, preserving membership and relation graph. 154 # It does not itself change scope membership or apply structural commands. 155 def put_column(~S:Data,~C:Type,~Rest:Type,~P:Type,~put:Rest -> Col.Column -> C,rest:ComponentRest,column:Col.Column) -> A.Store>: 156 match rest: 157 case ComponentRest{rest,members,graph}:A.Store{Sc.Store{put(rest,column),members},graph} 158 # Derive a component binding for the wrapped Store from its original binding. 159 # Retains the schema identity, view and typed column path while adding scope and 160 # relation owners to the remainder. This adapts schema setup; it grants no new 161 # access merely from a component name. See fixture.bend:scoped_binding in the 162 # public-scopes/feature-timing-v1 consumer. 163 def component_binding(~S:Data,~C:Type,~Rest:Type,~P:Type,~V:Data,~Token:Data,~take:C -> Rest & Col.Column,~put:Rest -> Col.Column -> C,~view:P -> P & V,binding:Binding.Binding) -> Binding.Binding>,ComponentRest,P,V,Token,take_column(~S,~C,~Rest,~P,~take),put_column(~S,~C,~Rest,~P,~put),view>: 164 Binding.bind(~S,~A.Store>,~ComponentRest,~P,~V,~Token,~take_column(~S,~C,~Rest,~P,~take),~put_column(~S,~C,~Rest,~P,~put),~view,Binding.identity(~S,~C,~Rest,~P,~V,~Token,~take,~put,~view,binding))