1 import Base 2 import ./schema_fragments.bend as SF 3 import ./schedule_provision.bend as P 4 5 # Recursive author metadata contains no payload owners or gameplay authority. 6 # Detached feature selection metadata with named dependencies and schema 7 # entries. A dependency reference does not automatically select/build it; 8 # selected features must include required names. Owns no live world or payload. 9 type Tree is Data: 10 Empty{} 11 Feature{name:String,entries:List<&2,SF.Entry>,dependencies:Tree} 12 Sequence{left:Tree,right:Tree} 13 # Feature composition refusal: duplicate selected feature, absent dependency 14 # name or schema descriptor conflict. No builder has run on validation refusal. 15 type Error is Data: 16 DuplicateFeature{name:String} 17 MissingDependency{name:String} 18 SchemaConflict{error:SF.Error} 19 # Pure feature validation result; owns no application payload or context. 20 type Validation is Data: 21 Valid{} 22 Invalid{error:Error} 23 # Validated composition outcome. Built owns build output; Rejected returns 24 # the original affine Owners with the validation error for recovery or retry. 25 type Composition is Type: 26 Built{output:Output} 27 Rejected{error:Error,owners:Owners} 28 # List selected feature names in authored sequence order, without recursively 29 # treating dependency references as additional selected features. 30 def names(~S:Data,tree:Tree) -> List<&2,String>: 31 match tree: 32 case Empty{}: [] 33 case Feature{name,_,_}: [name] 34 case Sequence{left,right}: List.append(&2,String,names(~S,left),names(~S,right)) 35 # List entries from selected features in authored sequence order. Dependency 36 # metadata does not implicitly add those features or their schema entries. 37 def entries(~S:Data,tree:Tree) -> List<&2,SF.Entry>: 38 match tree: 39 case Empty{}: [] 40 case Feature{_,entries,_}: entries 41 case Sequence{left,right}: List.append(&2,SF.Entry,entries(~S,left),entries(~S,right)) 42 # Collect own and referenced dependency entries recursively for visibility 43 # description. This does not select/build dependencies or validate composition. 44 def visibility(~S:Data,tree:Tree) -> List<&2,SF.Entry>: 45 match tree: 46 case Empty{}: [] 47 case Feature{_,own,dependencies}: List.append(&2,SF.Entry,own,visibility(~S,dependencies)) 48 case Sequence{left,right}: List.append(&2,SF.Entry,visibility(~S,left),visibility(~S,right)) 49 def contains(items:List<&2,String>,+name:String) -> Bool: 50 match items: 51 case []: False{} 52 case head <> rest: Bool.or(String.eq(head,name),contains(rest,name)) 53 def repeated(present:Bool,name:String,next:Unit -> Validation) -> Validation: 54 match present: 55 case True{}: Invalid{DuplicateFeature{name}} 56 case False{}: next(Unit{}) 57 def duplicates(items:List<&2,String>,+seen:List<&2,String>) -> Validation: 58 match items: 59 case []: Valid{} 60 case +name <> rest: repeated(contains(seen,name),name,_ => duplicates(rest,name <> seen)) 61 def missing(present:Bool,name:String,next:Unit -> Validation) -> Validation: 62 match present: 63 case False{}: Invalid{MissingDependency{name}} 64 case True{}: next(Unit{}) 65 def required(items:List<&2,String>,+selected:List<&2,String>) -> Validation: 66 match items: 67 case []: Valid{} 68 case +name <> rest: missing(contains(selected,name),name,_ => required(rest,selected)) 69 def continue_validation(result:Validation,next:Unit -> Validation) -> Validation: 70 match result: 71 case Invalid{error}: Invalid{error} 72 case Valid{}: next(Unit{}) 73 def dependencies(~S:Data,tree:Tree,+selected:List<&2,String>) -> Validation: 74 match tree: 75 case Empty{}: Valid{} 76 case Feature{_,_,deps}: required(names(~S,deps),selected) 77 case Sequence{left,right}: continue_validation(dependencies(~S,left,selected),_ => dependencies(~S,right,selected)) 78 def schema_validation(result:SF.Validation) -> Validation: 79 match result: 80 case SF.Valid{}: Valid{} 81 case SF.Invalid{error}: Invalid{SchemaConflict{error}} 82 # Check selected feature names, required dependency names and schema entries 83 # before any owned builder runs. Dependencies are name requirements, not an 84 # automatic dependency resolver or topological reorder of authored selection. 85 def validate(~S:Data,+tree:Tree) -> Validation: 86 continue_validation(duplicates(names(~S,tree),[]),_ => continue_validation(dependencies(~S,tree,names(~S,tree)),_ => schema_validation(SF.validate(~S,entries(~S,tree))))) 87 def compose_checked(~S:Data,~Owners:Type,~Output:Type,~build:Owners -> Output,result:Validation,owners:Owners) -> Composition: 88 match result: 89 case Invalid{error}: Rejected{error,owners} 90 case Valid{}: Built{build(owners)} 91 # Validate the metadata tree before passing affine Owners to the trusted 92 # build callback. Invalid returns Rejected with original owners; valid invokes 93 # build once. Does not make arbitrary builder effects transactional. 94 # Contract and accepted consumers: docs/parity/features.md. 95 def compose(~S:Data,~Owners:Type,~Output:Type,~build:Owners -> Output,tree:Tree,owners:Owners) -> Composition: 96 compose_checked(~S,~Owners,~Output,~build,validate(~S,tree),owners) 97 98 # Typed builder composition threads Context and recursive heterogeneous owners. 99 # Schedule plans retain authored nested requirements and selected order. 100 # Heterogeneous affine head/tail feature owners or outputs, preserving order. 101 # Nest this product for additional features; payloads need not be Data. 102 type Pack is Type: 103 Pack{head:Head,tail:Tail} 104 # Owned extra feature result plus bootstrap/update operational schedule plans. 105 # These plans describe later execution; constructing Output runs no schedule. 106 type Output is Type: 107 Output{extra:Extra,bootstrap:P.Plan,update:P.Plan} 108 def combine(~S:Data,~Ctx:Type,~HE:Type,~TE:Type,observed:Ctx & Output,head:Output) -> Ctx & Output>: 109 match observed head: 110 case (ctx,Output{te,tb,tu}) Output{he,hb,hu}: (ctx,Output{Pack{he,te},P.Sequence{hb,tb},P.Sequence{hu,tu}}) 111 def build_head_done(~S:Data,~Ctx:Type,~HO:Type,~TO:Type,~HE:Type,~TE:Type,~tail:Ctx -> TO -> Ctx & Output,owners:TO,observed:Ctx & Output) -> Ctx & Output>: 112 match observed: 113 case (ctx,output): combine(~S,~Ctx,~HE,~TE,tail(ctx,owners),output) 114 def build_head(~S:Data,~Ctx:Type,~HO:Type,~TO:Type,~HE:Type,~TE:Type,~head:Ctx -> HO -> Ctx & Output,~tail:Ctx -> TO -> Ctx & Output,ctx:Ctx,owners:Pack) -> Ctx & Output>: 115 match owners: 116 case Pack{ho,to}: build_head_done(~S,~Ctx,~HO,~TO,~HE,~TE,~tail,to,head(ctx,ho)) 117 def build_empty(~S:Data,~Ctx:Type,ctx:Ctx,owner:Unit) -> Ctx & Output: 118 (ctx,Output{owner,P.Empty{},P.Empty{}}) 119 120 # Trusted schema constructors pair each definition with its actual owned builder. 121 # Gameplay does not receive these raw constructors or callback fields. 122 # Trusted static feature definition: name, own schema entries and dependency 123 # references. It contains no live feature owners or gameplay authority. 124 type Definition is Data: 125 Definition{name:String,entries:List<&2,SF.Entry>,dependencies:Tree} 126 # Owned feature definition and trusted builder. build consumes/returns its 127 # context and consumes Owners to produce Extra plus plans; raw callbacks remain 128 # trusted setup rather than constrained system-body capabilities. 129 type Feature is Type: 130 FeatureOwner{definition:Definition,owners:Owners,build:Ctx -> Owners -> Ctx & Output} 131 # Selected feature metadata with actual heterogeneous owners and builder. 132 # Thread affinely into run_recipe; metadata validation never copies owners. 133 type Recipe is Type: 134 Recipe{selected:Tree,owners:Owners,build:Ctx -> Owners -> Ctx & Output} 135 # Completed returns context and built output; Refused returns context and 136 # the entire original recipe on metadata validation failure. No builder runs 137 # in Refused, so its owners can be retried or disposed by the caller. 138 type RecipeResult is Type: 139 Completed{ctx:Ctx,output:Output} 140 Refused{error:Error,ctx:Ctx,recipe:Recipe} 141 def definition_tree(~S:Data,definition:Definition) -> Tree: 142 match definition: 143 case Definition{name,entries,dependencies}: Feature{name,entries,dependencies} 144 def callbacks_done(~S:Data,~Ctx:Type,~HO:Type,~TO:Type,~HE:Type,~TE:Type,tail:Ctx -> TO -> Ctx & Output,owners:TO,observed:Ctx & Output) -> Ctx & Output>: 145 match observed: 146 case (ctx,output): combine(~S,~Ctx,~HE,~TE,tail(ctx,owners),output) 147 def callbacks(~S:Data,~Ctx:Type,~HO:Type,~TO:Type,~HE:Type,~TE:Type,head:Ctx -> HO -> Ctx & Output,tail:Ctx -> TO -> Ctx & Output,ctx:Ctx,owners:Pack) -> Ctx & Output>: 148 match owners: 149 case Pack{ho,to}: callbacks_done(~S,~Ctx,~HO,~TO,~HE,~TE,tail,to,head(ctx,ho)) 150 # Prepend an owned feature to a recipe, retaining head-before-tail build 151 # and bootstrap/update plan order. Does not validate, invoke builders or 152 # automatically add dependencies; run_recipe validates the completed selection. 153 def prepend(~S:Data,~Ctx:Type,~HO:Type,~TO:Type,~HE:Type,~TE:Type,head:Feature,tail:Recipe) -> Recipe,Pack>: 154 match head tail: 155 case FeatureOwner{definition,ho,head} Recipe{tree,to,tail}: Recipe{Sequence{definition_tree(~S,definition),tree},Pack{ho,to},ctx => owners => callbacks(~S,~Ctx,~HO,~TO,~HE,~TE,head,tail,ctx,owners)} 156 # Create the empty recipe with Unit owners and empty schedule plans. 157 # No context is consumed until the resulting builder is executed. 158 def recipe_empty(~S:Data,~Ctx:Type) -> Recipe: 159 Recipe{Empty{},Unit{},ctx => owners => build_empty(~S,~Ctx,ctx,owners)} 160 def recipe_completed(~S:Data,~Ctx:Type,~Owners:Type,~Extra:Type,observed:Ctx & Output) -> RecipeResult: 161 match observed: 162 case (ctx,output): Completed{ctx,output} 163 def recipe_checked(~S:Data,~Ctx:Type,~Owners:Type,~Extra:Type,result:Validation,ctx:Ctx,recipe:Recipe) -> RecipeResult: 164 match result recipe: 165 case Invalid{error} recipe: Refused{error,ctx,recipe} 166 case Valid{} Recipe{_,owners,build}: recipe_completed(~S,~Ctx,~Owners,~Extra,build(ctx,owners)) 167 # Validate the selected tree before executing its trusted owned builder. 168 # Refusal returns context and original recipe; success returns context plus 169 # built extras/plans. Authored order is preserved; no automatic rollback of 170 # arbitrary callback effects or host IO is provided. 171 def run_recipe(~S:Data,~Ctx:Type,~Owners:Type,~Extra:Type,ctx:Ctx,recipe:Recipe) -> RecipeResult: 172 match recipe: 173 case Recipe{+tree,owners,build}: recipe_checked(~S,~Ctx,~Owners,~Extra,validate(~S,tree),ctx,Recipe{tree,owners,build})