schema_fragments.bend

bendvy-ecs-core@0.1.0.3 · 187 lines · 40 definitions

1import Base
2
3# Metadata is kind-local, as in the pinned Schema merge. Declaration packs stay affine.
4# Descriptor namespace: component/resource/event/relation/service.
5# Keys and names collide within one kind, not across distinct kinds.
6type RegistryKind is Data:
7  Component{}
8  Resource{}
9  Event{}
10  Relation{}
11  Service{}
12# Schema/token/payload-indexed descriptor metadata without a live payload.
13# Raw construction is trusted schema setup and grants no operational access.
14type Descriptor<S: Data,Token: Data,Payload: Type> is Data:
15  Descriptor{kind: RegistryKind,key: String,name: String}
16# Authored persistence classification for descriptor metadata; does not
17# implement a codec, validate raw input or export a snapshot by itself.
18type PersistenceKind is Data:
19  Plain{}
20  Transient{}
21  Constructed{}
22# Detached descriptor entry, optionally classified by persistence policy.
23# Both variants retain the same kind/key/name collision rules.
24type Entry<S: Data> is Data:
25  Entry{kind: RegistryKind,key: String,name: String}
26  Classified{kind: RegistryKind,key: String,name: String,persistence:PersistenceKind}
27# Descriptor entries and their actual affine declaration owners. Metadata
28# is Data; Owners may contain arbitrary Type payloads and are never duplicated.
29type Fragment<S: Data,Owners: Type> is Type:
30  Fragment{entries: List<&2,Entry<S>>,owners: Owners}
31# Schema refusal identifies duplicate kind-local key/name or a descriptor
32# required by a declared initializer but absent from the fragment.
33type Error is Data:
34  DuplicateKey{kind: RegistryKind,key: String}
35  DuplicateName{kind: RegistryKind,name: String}
36  UndeclaredDescriptor{kind: RegistryKind,key: String,name: String}
37# Pure metadata-validation result, without consuming declaration owners.
38type Validation is Data:
39  Valid{}
40  Invalid{error: Error}
41# Merged owns the combined fragment; Rejected returns both original affine
42# fragments with the conflict. Refusal does not execute an initializer.
43type Merge<S: Data,L: Type,R: Type> is Type:
44  Merged{fragment: Fragment<S,L & R>}
45  Rejected{error: Error,left: Fragment<S,L>,right: Fragment<S,R>}
46# Built owns the initializer output; BindRejected returns the original
47# fragment and metadata error, allowing caller recovery without copied owners.
48type Bound<S: Data,Owners: Type,Output: Type> is Type:
49  Built{output: Output}
50  BindRejected{error: Error,fragment: Fragment<S,Owners>}
51
52# Only the operational extension builders choose a classification. Raw schema construction is trusted.
53def bare_entry(~S:Data,value:Entry<S>) -> Entry<S>:
54  match value:
55    case Entry{kind,key,name}:Entry{kind,key,name}
56    case Classified{kind,key,name,_}:Entry{kind,key,name}
57def bare_entries(~S:Data,values:List<&2,Entry<S>>) -> List<&2,Entry<S>>:
58  match values:
59    case []:[]
60    case head <> rest:bare_entry(~S,head) <> bare_entries(~S,rest)
61def same_kind(a: RegistryKind,b: RegistryKind) -> Bool:
62  match a b:
63    case Component{} Component{}: True{}
64    case Resource{} Resource{}: True{}
65    case Event{} Event{}: True{}
66    case Relation{} Relation{}: True{}
67    case Service{} Service{}: True{}
68    case _ _: False{}
69# Project a typed descriptor to its kind/key/name metadata entry. Does not
70# read payloads or grant access to the descriptor's runtime storage.
71def entry(~S: Data,~Token: Data,~Payload: Type,descriptor: Descriptor<S,Token,Payload>) -> Entry<S>:
72  match descriptor:
73    case Descriptor{kind,key,name}: Entry{kind,key,name}
74# Create an empty fragment with a Unit owner and no metadata entries.
75# Use fragment when supplying arbitrary affine declaration owners.
76def empty(~S: Data,owners: Unit) -> Fragment<S,Unit>:
77  Fragment{[],owners}
78# Trusted setup constructor pairing authored entries with actual owners.
79# Does not verify their semantic relationship or reject duplicate entries;
80# validate/merge/bind perform their stated metadata checks later.
81def fragment(~S: Data,~Owners: Type,entries: List<&2,Entry<S>>,owners: Owners) -> Fragment<S,Owners>:
82  Fragment{entries,owners}
83def key_exists_bare(~S: Data,xs: List<&2,Entry<S>>,+kind: RegistryKind,+key: String) -> Bool:
84  match xs:
85    case Nil{}: False{}
86    case Con{Entry{other,otherKey,_},rest}: Bool.or(Bool.and(same_kind(kind,other),String.eq(key,otherKey)),key_exists_bare(~S,rest,kind,key))
87    case Con{Classified{other,otherKey,_,_},rest}: Bool.or(Bool.and(same_kind(kind,other),String.eq(key,otherKey)),key_exists_bare(~S,rest,kind,key))
88def key_exists(~S:Data,xs:List<&2,Entry<S>>,kind:RegistryKind,key:String) -> Bool:key_exists_bare(~S,bare_entries(~S,xs),kind,key)
89def name_exists_bare(~S: Data,xs: List<&2,Entry<S>>,+kind: RegistryKind,+name: String) -> Bool:
90  match xs:
91    case Nil{}: False{}
92    case Con{Entry{other,_,otherName},rest}: Bool.or(Bool.and(same_kind(kind,other),String.eq(name,otherName)),name_exists_bare(~S,rest,kind,name))
93    case Con{Classified{other,_,otherName,_},rest}: Bool.or(Bool.and(same_kind(kind,other),String.eq(name,otherName)),name_exists_bare(~S,rest,kind,name))
94def name_exists(~S:Data,xs:List<&2,Entry<S>>,kind:RegistryKind,name:String) -> Bool:name_exists_bare(~S,bare_entries(~S,xs),kind,name)
95def check_entry(keyConflict: Bool,nameConflict: Bool,kind: RegistryKind,key: String,name: String) -> Validation:
96  match keyConflict nameConflict:
97    case True{} _: Invalid{DuplicateKey{kind,key}}
98    case False{} True{}: Invalid{DuplicateName{kind,name}}
99    case False{} False{}: Valid{}
100def check_one_bare(~S: Data,+seen: List<&2,Entry<S>>,item: Entry<S>) -> Validation:
101  match item:
102    case Entry{+kind,+key,+name}: check_entry(key_exists(~S,seen,kind,key),name_exists(~S,seen,kind,name),kind,key,name)
103    case Classified{+kind,+key,+name,_}: check_entry(key_exists(~S,seen,kind,key),name_exists(~S,seen,kind,name),kind,key,name)
104def check_one(~S:Data,seen:List<&2,Entry<S>>,item:Entry<S>) -> Validation:check_one_bare(~S,bare_entries(~S,seen),bare_entry(~S,item))
105def validation_continue(~S: Data,result: Validation,next: Unit -> Validation) -> Validation:
106  match result:
107    case Invalid{error}: Invalid{error}
108    case Valid{}: next(Unit{})
109def validate_loop(~S: Data,xs: List<&2,Entry<S>>,+seen: List<&2,Entry<S>>) -> Validation:
110  match xs:
111    case Nil{}: Valid{}
112    case Con{+item,rest}: validation_continue(~S,check_one(~S,seen,item),_ => validate_loop(~S,rest,item <> seen))
113def filter_kind_done(~S: Data,selected: Bool,item: Entry<S>,rest: List<&2,Entry<S>>) -> List<&2,Entry<S>>:
114  match selected:
115    case True{}: item <> rest
116    case False{}: rest
117def filter_kind(~S:Data,xs:List<&2,Entry<S>>,+kind:RegistryKind) -> List<&2,Entry<S>>:
118  match xs:
119    case []:[]
120    case Entry{+itemKind,+key,+name} <> rest:filter_kind_done(~S,same_kind(kind,itemKind),Entry{itemKind,key,name},filter_kind(~S,rest,kind))
121    case Classified{+itemKind,+key,+name,+persistence} <> rest:filter_kind_done(~S,same_kind(kind,itemKind),Classified{itemKind,key,name,persistence},filter_kind(~S,rest,kind))
122def first_failure(first: Validation,second: Validation) -> Validation:
123  match first:
124    case Invalid{error}: Invalid{error}
125    case Valid{}: second
126# Check descriptor key/name collisions within each registry kind.
127# Preserves kind-specific error order; this is metadata validation, not proof
128# of lens laws, actual payload values or correctness of arbitrary adapters.
129def validate(~S: Data,+entries: List<&2,Entry<S>>) -> Validation:
130  first_failure(validate_loop(~S,filter_kind(~S,entries,Component{}),[]),first_failure(validate_loop(~S,filter_kind(~S,entries,Resource{}),[]),first_failure(validate_loop(~S,filter_kind(~S,entries,Event{}),[]),first_failure(validate_loop(~S,filter_kind(~S,entries,Relation{}),[]),validate_loop(~S,filter_kind(~S,entries,Service{}),[])))))
131def merge_checked(~S: Data,~L: Type,~R: Type,result: Validation,+entries: List<&2,Entry<S>>,left: Fragment<S,L>,right: Fragment<S,R>) -> Merge<S,L,R>:
132  match result left right:
133    case Invalid{error} left right: Rejected{error,left,right}
134    case Valid{} Fragment{_,l} Fragment{_,r}: Merged{Fragment{entries,(l,r)}}
135# Validate both fragments and their combined entries before combining owners.
136# Success retains left-then-right entries and (L & R) owners; refusal returns
137# both original fragments. No initializer or gameplay callback is invoked.
138def merge(~S: Data,~L: Type,~R: Type,left: Fragment<S,L>,right: Fragment<S,R>) -> Merge<S,L,R>:
139  match left right:
140    case Fragment{+le,l} Fragment{+re,r}: merge_checked(~S,~L,~R,first_failure(validate(~S,le),first_failure(validate(~S,re),validate(~S,List.append(&2,Entry<S>,le,re)))),List.append(&2,Entry<S>,le,re),Fragment{le,l},Fragment{re,r})
141def bind_checked(~S: Data,~Owners: Type,~Output: Type,~build: Owners -> Output,result: Validation,fragment: Fragment<S,Owners>) -> Bound<S,Owners,Output>:
142  match result fragment:
143    case Invalid{error} fragment: BindRejected{error,fragment}
144    case Valid{} Fragment{_,owners}: Built{build(owners)}
145# Validate fragment metadata before consuming Owners with a trusted builder.
146# BindRejected returns the original fragment; Built contains builder output.
147# For a builder with explicit descriptor prerequisites use bind_declared.
148def bind(~S: Data,~Owners: Type,~Output: Type,~build: Owners -> Output,fragment: Fragment<S,Owners>) -> Bound<S,Owners,Output>:
149  match fragment:
150    case Fragment{+entries,owners}: bind_checked(~S,~Owners,~Output,~build,validate(~S,entries),Fragment{entries,owners})
151
152# A closed initializer states its required descriptors before receiving any owners.
153# As with Family lenses, caller-authored adapters remain trusted provisioning.
154def descriptor_equal(~S:Data,a:Entry<S>,b:Entry<S>) -> Bool:
155  match a b:
156    case Entry{ak,ax,an} Entry{bk,bx,bn}:Bool.and(same_kind(ak,bk),Bool.and(String.eq(ax,bx),String.eq(an,bn)))
157    case _ _:False{}
158def descriptor_present(~S:Data,xs:List<&2,Entry<S>>,+item:Entry<S>) -> Bool:
159  match xs:
160    case []:False{}
161    case head <> rest:Bool.or(descriptor_equal(~S,bare_entry(~S,head),bare_entry(~S,item)),descriptor_present(~S,rest,item))
162def required_continue(result: Validation,next: Unit -> Validation) -> Validation:
163  match result:
164    case Invalid{error}: Invalid{error}
165    case Valid{}: next(Unit{})
166def required_check_bare(~S: Data,present: Bool,item: Entry<S>) -> Validation:
167  match present item:
168    case True{} _: Valid{}
169    case False{} Entry{kind,key,name}: Invalid{UndeclaredDescriptor{kind,key,name}}
170    case False{} Classified{kind,key,name,_}: Invalid{UndeclaredDescriptor{kind,key,name}}
171def required_check(~S:Data,present:Bool,item:Entry<S>) -> Validation:required_check_bare(~S,present,bare_entry(~S,item))
172def requirements(~S: Data,required: List<&2,Entry<S>>,+entries: List<&2,Entry<S>>) -> Validation:
173  match required:
174    case []: Valid{}
175    case +item <> rest: required_continue(required_check(~S,descriptor_present(~S,entries,item),item),_ => requirements(~S,rest,entries))
176def bind_requirements(~S: Data,~Owners: Type,~Output: Type,~build: Owners -> Output,result: Validation,required: List<&2,Entry<S>>,fragment: Fragment<S,Owners>) -> Bound<S,Owners,Output>:
177  match result fragment:
178    case Invalid{error} fragment: BindRejected{error,fragment}
179    case Valid{} Fragment{+entries,owners}: bind_checked(~S,~Owners,~Output,~build,requirements(~S,required,entries),Fragment{entries,owners})
180# Validate fragment collisions and every explicitly required descriptor
181# before invoking the closed builder. Missing kind/key/name yields
182# BindRejected with the original fragment, without executing build. Trusted
183# builders must still obey their declared requirements; arbitrary callbacks
184# are not runtime-confined by metadata alone.
185def bind_declared(~S: Data,~Owners: Type,~Output: Type,~build: Owners -> Output,fragment: Fragment<S,Owners>,required: List<&2,Entry<S>>) -> Bound<S,Owners,Output>:
186  match fragment:
187    case Fragment{+entries,owners}: bind_requirements(~S,~Owners,~Output,~build,validate(~S,entries),required,Fragment{entries,owners})