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})