schema_fragments.bend
bendvy-ecs-core@0.1.0.1 · 187 lines · 0 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})