1 import Base 2 import ./schema_fragments.bend as SF 3 import ./execution_composer_layout.bend as Layout 4 import ./execution_composer_schema.bend as Schema 5 import ./execution_registry.bend as Registry 6 import ./dependency_registration.bend as Reg 7 import ./dependency_executor.bend as Exec 8 import ./dependency_composer_program.bend as Scoped 9 import ./dependency_composer.bend as Compose 10 import ./dependency_plan.bend as Plan 11 12 # Recursive author metadata contains no payload owners or gameplay authority. 13 # Detached feature selection metadata with named dependencies and schema 14 # entries. A dependency reference does not automatically select/build it; 15 # selected features must include required names. Owns no live world or payload. 16 type Tree is Data: 17 Empty{} 18 Feature{name:String,entries:List<&2,SF.Entry>,dependencies:Tree} 19 Sequence{left:Tree,right:Tree} 20 # Feature composition refusal: duplicate selected feature, absent dependency 21 # name or schema descriptor conflict. No builder has run on validation refusal. 22 type Error is Data: 23 DuplicateFeature{name:String} 24 MissingDependency{name:String} 25 SchemaConflict{error:SF.Error} 26 # Pure feature validation result; owns no application payload or context. 27 type Validation is Data: 28 Valid{} 29 Invalid{error:Error} 30 # Validated composition outcome. Built owns build output; Rejected returns 31 # the original affine Owners with the validation error for recovery or retry. 32 type Composition is Type: 33 Built{output:Output} 34 Rejected{error:Error,owners:Owners} 35 # List selected feature names in authored sequence order, without recursively 36 # treating dependency references as additional selected features. 37 def names(~S:Data,tree:Tree) -> List<&2,String>: 38 match tree: 39 case Empty{}: [] 40 case Feature{name,_,_}: [name] 41 case Sequence{left,right}: List.append(&2,String,names(~S,left),names(~S,right)) 42 # List entries from selected features in authored sequence order. Dependency 43 # metadata does not implicitly add those features or their schema entries. 44 def entries(~S:Data,tree:Tree) -> List<&2,SF.Entry>: 45 match tree: 46 case Empty{}: [] 47 case Feature{_,entries,_}: entries 48 case Sequence{left,right}: List.append(&2,SF.Entry,entries(~S,left),entries(~S,right)) 49 # Collect own and referenced dependency entries recursively for visibility 50 # description. This does not select/build dependencies or validate composition. 51 def visibility(~S:Data,tree:Tree) -> List<&2,SF.Entry>: 52 match tree: 53 case Empty{}: [] 54 case Feature{_,own,dependencies}: List.append(&2,SF.Entry,own,visibility(~S,dependencies)) 55 case Sequence{left,right}: List.append(&2,SF.Entry,visibility(~S,left),visibility(~S,right)) 56 def contains(items:List<&2,String>,+name:String) -> Bool: 57 match items: 58 case []: False{} 59 case head <> rest: Bool.or(String.eq(head,name),contains(rest,name)) 60 def repeated(present:Bool,name:String,next:Unit -> Validation) -> Validation: 61 match present: 62 case True{}: Invalid{DuplicateFeature{name}} 63 case False{}: next(Unit{}) 64 def duplicates(items:List<&2,String>,+seen:List<&2,String>) -> Validation: 65 match items: 66 case []: Valid{} 67 case +name <> rest: repeated(contains(seen,name),name,_ => duplicates(rest,name <> seen)) 68 def missing(present:Bool,name:String,next:Unit -> Validation) -> Validation: 69 match present: 70 case False{}: Invalid{MissingDependency{name}} 71 case True{}: next(Unit{}) 72 def required(items:List<&2,String>,+selected:List<&2,String>) -> Validation: 73 match items: 74 case []: Valid{} 75 case +name <> rest: missing(contains(selected,name),name,_ => required(rest,selected)) 76 def continue_validation(result:Validation,next:Unit -> Validation) -> Validation: 77 match result: 78 case Invalid{error}: Invalid{error} 79 case Valid{}: next(Unit{}) 80 def dependencies(~S:Data,tree:Tree,+selected:List<&2,String>) -> Validation: 81 match tree: 82 case Empty{}: Valid{} 83 case Feature{_,_,deps}: required(names(~S,deps),selected) 84 case Sequence{left,right}: continue_validation(dependencies(~S,left,selected),_ => dependencies(~S,right,selected)) 85 def schema_validation(result:SF.Validation) -> Validation: 86 match result: 87 case SF.Valid{}: Valid{} 88 case SF.Invalid{error}: Invalid{SchemaConflict{error}} 89 # Check selected feature names, required dependency names and schema entries 90 # before any owned builder runs. Dependencies are name requirements, not an 91 # automatic dependency resolver or topological reorder of authored selection. 92 def validate(~S:Data,+tree:Tree) -> Validation: 93 continue_validation(duplicates(names(~S,tree),[]),_ => continue_validation(dependencies(~S,tree,names(~S,tree)),_ => schema_validation(SF.validate(~S,entries(~S,tree))))) 94 def compose_checked(~S:Data,~Owners:Type,~Output:Type,~build:Owners -> Output,result:Validation,owners:Owners) -> Composition: 95 match result: 96 case Invalid{error}: Rejected{error,owners} 97 case Valid{}: Built{build(owners)} 98 # Validate the metadata tree before passing affine Owners to the trusted 99 # build callback. Invalid returns Rejected with original owners; valid invokes 100 # build once. Does not make arbitrary builder effects transactional. 101 # Contract and accepted consumers: docs/parity/features.md. 102 def compose(~S:Data,~Owners:Type,~Output:Type,~build:Owners -> Output,tree:Tree,owners:Owners) -> Composition: 103 compose_checked(~S,~Owners,~Output,~build,validate(~S,tree),owners) 104 105 # Heterogeneous affine head/tail feature owners or outputs, preserving order. 106 # Nest this product for additional features; payloads need not be Data. 107 type Pack is Type: 108 Pack{head:Head,tail:Tail} 109 110 type Definition is Data: 111 Definition{name:String,entries:List<&2,SF.Entry>,dependencies:Tree} 112 def definition_tree(~S:Data,definition:Definition) -> Tree: 113 match definition: 114 case Definition{name,entries,dependencies}:Feature{name,entries,dependencies} 115 116 # Entries and node IDs come from the same accepted declarations that execute. 117 # Scope paths retain feature boundaries before dependency edges are flattened. 118 type Assembly<-S:Data,-ActualRegistry:Type> is Type: 119 Assembly{selected:Tree,facts:Registry.Facts,scope:Scoped.Scope} 120 type AssemblyError is Data: 121 InvalidFeatures{error:Error} 122 InvalidComposition{error:Compose.Error} 123 type Lowered<-ActualRegistry:Type> is Type: 124 Lowered{facts:Registry.Facts,edges:List<&2,Plan.Edge>,invalid:Maybe<&2,AssemblyError>} 125 def needs_key(nodes:List<&2,Plan.Node>,+key:String) -> Bool: 126 match nodes: 127 case []:False{} 128 case Plan.Node{_,needs} <> rest:Bool.or(contains(needs,key),needs_key(rest,key)) 129 def selected_entry(~S:Data,selected:Bool,entry:SF.Entry,rest:List<&2,SF.Entry>) -> List<&2,SF.Entry>: 130 match selected: 131 case True{}:entry <> rest 132 case False{}:rest 133 def own_entries(~S:Data,entries:List<&2,SF.Entry>,+nodes:List<&2,Plan.Node>) -> List<&2,SF.Entry>: 134 match entries: 135 case []:[] 136 case +entry <> rest:selected_entry(~S,needs_key(nodes,Compose.entry_key(~S,entry)),entry,own_entries(~S,rest,nodes)) 137 def from_facts(~S:Data,~components:Layout.Layout,~resources:Layout.Layout,~World:Type,~Nodes:Type,~Error:Data,~Out:Data,~dispatch:Nodes -> World -> U32 -> Nodes & (World & Exec.Invocation),name:String,dependencies:Tree,facts:Registry.Facts>) -> Assembly>: 138 match facts: 139 case Registry.Facts{Reg.Registry{nodes,+planNodes},deferred}:Assembly{Feature{name,own_entries(~S,Schema.inventory(~S,~components,~resources),planNodes),dependencies},Registry.Facts{Reg.Registry{nodes,planNodes},deferred},Scoped.Scope{Scoped.ids(planNodes),[]}} 140 def scope_members(scope:Scoped.Scope) -> List<&2,U32>: 141 match scope: 142 case Scoped.Scope{members,_}:members 143 def pair(~S:Data,~World:Type,~Left:Type,~Right:Type,~Error:Data,~Out:Data,~leftDispatch:Left -> World -> U32 -> Left & (World & Exec.Invocation),~rightDispatch:Right -> World -> U32 -> Right & (World & Exec.Invocation),left:Assembly>,right:Assembly>) -> Assembly,Error,Out,Reg.paired_dispatch(~World,~Left,~Right,~Error,~Out,~leftDispatch,~rightDispatch)>>: 144 match left right: 145 case Assembly{left,lf,+ls} Assembly{right,rf,+rs}:Assembly{Sequence{left,right},Registry.paired(~World,~Left,~Right,~Error,~Out,~leftDispatch,~rightDispatch,lf,rf),Scoped.Scope{List.append(&2,U32,scope_members(ls),scope_members(rs)),[ls,rs]}} 146 def feature_error(validation:Validation) -> Maybe<&2,AssemblyError>: 147 match validation: 148 case Valid{}:None{} 149 case Invalid{error}:Some{InvalidFeatures{error}} 150 def composition_error(error:Maybe<&2,Compose.Error>) -> Maybe<&2,AssemblyError>: 151 match error: 152 case None{}:None{} 153 case Some{error}:Some{InvalidComposition{error}} 154 def first_error(first:Maybe<&2,AssemblyError>,second:Maybe<&2,AssemblyError>) -> Maybe<&2,AssemblyError>: 155 match first: 156 case Some{error}:Some{error} 157 case None{}:second 158 def nesting_error(error:Maybe<&2,Scoped.NestError>) -> Maybe<&2,Compose.Error>: 159 match error: 160 case None{}:None{} 161 case Some{error}:Some{Compose.Nesting{error}} 162 def lower_nested(~S:Data,~components:Layout.Layout,~resources:Layout.Layout,~World:Type,~Nodes:Type,~Error:Data,~Out:Data,~dispatch:Nodes -> World -> U32 -> Nodes & (World & Exec.Invocation),selected:Tree,facts:Registry.Facts>,nested:Scoped.Nesting) -> Lowered>: 163 match facts nested: 164 case Registry.Facts{Reg.Registry{nodes,+planNodes},deferred} Scoped.Nesting{edges,invalid}:Lowered{Registry.Facts{Reg.Registry{nodes,planNodes},deferred},edges,first_error(feature_error(validate(~S,selected)),composition_error(Compose.first_error(nesting_error(invalid),Compose.first_error(Compose.schema_error(Schema.validate(~S,~components,~resources)),Compose.schedule_error(Compose.missing(~S,planNodes,Schema.inventory(~S,~components,~resources)))))))} 165 def lower(~S:Data,~components:Layout.Layout,~resources:Layout.Layout,~World:Type,~Nodes:Type,~Error:Data,~Out:Data,~dispatch:Nodes -> World -> U32 -> Nodes & (World & Exec.Invocation),groups:List<&2,Scoped.Nested>,assembly:Assembly>) -> Lowered>: 166 match assembly: 167 case Assembly{selected,facts,scope}:lower_nested(~S,~components,~resources,~World,~Nodes,~Error,~Out,~dispatch,selected,facts,Scoped.nested(groups,scope)) 168 169 type Application<-Root:Type,-Provisions:Type> is Type: 170 Application{root:Root,provisions:Provisions,invalid:Maybe<&2,AssemblyError>} 171 type Report is Data: 172 Executed{report:Out} 173 Refused{error:AssemblyError} 174 def returned(-Root:Type,-Provisions:Type,-Out:Data,provisions:Provisions,result:Root & Out) -> Application & Report: 175 match result: 176 case (root,report):(Application{root,provisions,None{}},Executed{report}) 177 def run(-Root:Type,-Provisions:Type,-Out:Data,invoke:Root -> Root & Out,application:Application) -> Application & Report: 178 match application: 179 case Application{root,provisions,Some{+error}}:(Application{root,provisions,Some{error}},Refused{error}) 180 case Application{root,provisions,None{}}:returned(Root,Provisions,Out,provisions,invoke(root))