feature.bend
bendvy-ecs-core@0.1.0.1 · 173 lines · 0 definitions
1import Base
2import bendvy-ecs-core@0.1.0.1/schema_fragments.bend as SF
3import bendvy-ecs-core@0.1.0.1/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.
9type Tree<S:Data> is Data:
10 Empty{}
11 Feature{name:String,entries:List<&2,SF.Entry<S>>,dependencies:Tree<S>}
12 Sequence{left:Tree<S>,right:Tree<S>}
13# Feature composition refusal: duplicate selected feature, absent dependency
14# name or schema descriptor conflict. No builder has run on validation refusal.
15type 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.
20type 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.
25type Composition<S:Data,Owners:Type,Output:Type> 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.
30def names(~S:Data,tree:Tree<S>) -> 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.
37def entries(~S:Data,tree:Tree<S>) -> List<&2,SF.Entry<S>>:
38 match tree:
39 case Empty{}: []
40 case Feature{_,entries,_}: entries
41 case Sequence{left,right}: List.append(&2,SF.Entry<S>,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.
44def visibility(~S:Data,tree:Tree<S>) -> List<&2,SF.Entry<S>>:
45 match tree:
46 case Empty{}: []
47 case Feature{_,own,dependencies}: List.append(&2,SF.Entry<S>,own,visibility(~S,dependencies))
48 case Sequence{left,right}: List.append(&2,SF.Entry<S>,visibility(~S,left),visibility(~S,right))
49def 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))
53def repeated(present:Bool,name:String,next:Unit -> Validation) -> Validation:
54 match present:
55 case True{}: Invalid{DuplicateFeature{name}}
56 case False{}: next(Unit{})
57def 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))
61def missing(present:Bool,name:String,next:Unit -> Validation) -> Validation:
62 match present:
63 case False{}: Invalid{MissingDependency{name}}
64 case True{}: next(Unit{})
65def 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))
69def continue_validation(result:Validation,next:Unit -> Validation) -> Validation:
70 match result:
71 case Invalid{error}: Invalid{error}
72 case Valid{}: next(Unit{})
73def dependencies(~S:Data,tree:Tree<S>,+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))
78def 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.
85def validate(~S:Data,+tree:Tree<S>) -> Validation:
86 continue_validation(duplicates(names(~S,tree),[]),_ => continue_validation(dependencies(~S,tree,names(~S,tree)),_ => schema_validation(SF.validate(~S,entries(~S,tree)))))
87def compose_checked(~S:Data,~Owners:Type,~Output:Type,~build:Owners -> Output,result:Validation,owners:Owners) -> Composition<S,Owners,Output>:
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.
95def compose(~S:Data,~Owners:Type,~Output:Type,~build:Owners -> Output,tree:Tree<S>,owners:Owners) -> Composition<S,Owners,Output>:
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.
102type Pack<Head:Type,Tail:Type> 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.
106type Output<S:Data,Extra:Type> is Type:
107 Output{extra:Extra,bootstrap:P.Plan<S>,update:P.Plan<S>}
108def combine(~S:Data,~Ctx:Type,~HE:Type,~TE:Type,observed:Ctx & Output<S,TE>,head:Output<S,HE>) -> Ctx & Output<S,Pack<HE,TE>>:
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}})
111def build_head_done(~S:Data,~Ctx:Type,~HO:Type,~TO:Type,~HE:Type,~TE:Type,~tail:Ctx -> TO -> Ctx & Output<S,TE>,owners:TO,observed:Ctx & Output<S,HE>) -> Ctx & Output<S,Pack<HE,TE>>:
112 match observed:
113 case (ctx,output): combine(~S,~Ctx,~HE,~TE,tail(ctx,owners),output)
114def build_head(~S:Data,~Ctx:Type,~HO:Type,~TO:Type,~HE:Type,~TE:Type,~head:Ctx -> HO -> Ctx & Output<S,HE>,~tail:Ctx -> TO -> Ctx & Output<S,TE>,ctx:Ctx,owners:Pack<HO,TO>) -> Ctx & Output<S,Pack<HE,TE>>:
115 match owners:
116 case Pack{ho,to}: build_head_done(~S,~Ctx,~HO,~TO,~HE,~TE,~tail,to,head(ctx,ho))
117def build_empty(~S:Data,~Ctx:Type,ctx:Ctx,owner:Unit) -> Ctx & Output<S,Unit>:
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.
124type Definition<S:Data> is Data:
125 Definition{name:String,entries:List<&2,SF.Entry<S>>,dependencies:Tree<S>}
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.
129type Feature<S:Data,Ctx:Type,Owners:Type,Extra:Type> is Type:
130 FeatureOwner{definition:Definition<S>,owners:Owners,build:Ctx -> Owners -> Ctx & Output<S,Extra>}
131# Selected feature metadata with actual heterogeneous owners and builder.
132# Thread affinely into run_recipe; metadata validation never copies owners.
133type Recipe<S:Data,Ctx:Type,Owners:Type,Extra:Type> is Type:
134 Recipe{selected:Tree<S>,owners:Owners,build:Ctx -> Owners -> Ctx & Output<S,Extra>}
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.
138type RecipeResult<S:Data,Ctx:Type,Owners:Type,Extra:Type> is Type:
139 Completed{ctx:Ctx,output:Output<S,Extra>}
140 Refused{error:Error,ctx:Ctx,recipe:Recipe<S,Ctx,Owners,Extra>}
141def definition_tree(~S:Data,definition:Definition<S>) -> Tree<S>:
142 match definition:
143 case Definition{name,entries,dependencies}: Feature{name,entries,dependencies}
144def callbacks_done(~S:Data,~Ctx:Type,~HO:Type,~TO:Type,~HE:Type,~TE:Type,tail:Ctx -> TO -> Ctx & Output<S,TE>,owners:TO,observed:Ctx & Output<S,HE>) -> Ctx & Output<S,Pack<HE,TE>>:
145 match observed:
146 case (ctx,output): combine(~S,~Ctx,~HE,~TE,tail(ctx,owners),output)
147def callbacks(~S:Data,~Ctx:Type,~HO:Type,~TO:Type,~HE:Type,~TE:Type,head:Ctx -> HO -> Ctx & Output<S,HE>,tail:Ctx -> TO -> Ctx & Output<S,TE>,ctx:Ctx,owners:Pack<HO,TO>) -> Ctx & Output<S,Pack<HE,TE>>:
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.
153def prepend(~S:Data,~Ctx:Type,~HO:Type,~TO:Type,~HE:Type,~TE:Type,head:Feature<S,Ctx,HO,HE>,tail:Recipe<S,Ctx,TO,TE>) -> Recipe<S,Ctx,Pack<HO,TO>,Pack<HE,TE>>:
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.
158def recipe_empty(~S:Data,~Ctx:Type) -> Recipe<S,Ctx,Unit,Unit>:
159 Recipe{Empty{},Unit{},ctx => owners => build_empty(~S,~Ctx,ctx,owners)}
160def recipe_completed(~S:Data,~Ctx:Type,~Owners:Type,~Extra:Type,observed:Ctx & Output<S,Extra>) -> RecipeResult<S,Ctx,Owners,Extra>:
161 match observed:
162 case (ctx,output): Completed{ctx,output}
163def recipe_checked(~S:Data,~Ctx:Type,~Owners:Type,~Extra:Type,result:Validation,ctx:Ctx,recipe:Recipe<S,Ctx,Owners,Extra>) -> RecipeResult<S,Ctx,Owners,Extra>:
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.
171def run_recipe(~S:Data,~Ctx:Type,~Owners:Type,~Extra:Type,ctx:Ctx,recipe:Recipe<S,Ctx,Owners,Extra>) -> RecipeResult<S,Ctx,Owners,Extra>:
172 match recipe:
173 case Recipe{+tree,owners,build}: recipe_checked(~S,~Ctx,~Owners,~Extra,validate(~S,tree),ctx,Recipe{tree,owners,build})