1 import Base 2 import ./column.bend as Col 3 import ./component.bend as Cmp 4 import ./persistence_declaration.bend as Recipe 5 import ./ordinary_declaration.bend as Identity 6 import ./declared_family.bend as Family 7 import ./snapshot_leaf.bend as Leaf 8 import ./decode_data.bend as D 9 import ./compose.bend as Q 10 import ./capabilities.bend as Cap 11 import ./world.bend as W 12 13 # One constructor owns both operational grant/matcher and detached syntax. 14 # Metadata is library-produced; the application supplies no metadata argument. 15 # Query clause kind used by declaration-derived inspection and access metadata. 16 type Mode is Data: 17 Read{} 18 Write{} 19 Optional{} 20 With{} 21 Without{} 22 Added{} 23 Changed{} 24 # Detached schema-scoped description of one component query requirement. 25 type Clause is Data: 26 Clause{name:String,mode:Mode} 27 # Typed query declaration: grants, row selection and debug clauses share one source. 28 # Build with read/write/filter constructors and pair; raw construction is a trusted 29 # adapter boundary. H is threaded affinely; G determines available operations. 30 type Declaration<-S:Data,H:Type,G:Type> is Type: 31 Declaration{grant:G,select:H -> H & Bool,clauses:List<&2,Clause>} 32 # Return the declaration owner together with its detached debug clauses. 33 # Does not inspect a world, execute a query or advance a system cursor. 34 def observe(~S:Data,~H:Type,~G:Type,declaration:Declaration) -> Declaration & List<&2,Clause>: 35 match declaration: 36 case Declaration{grant,select,+clauses}:(Declaration{grant,select,clauses},clauses) 37 # Consume the declaration and extract its typed operations for a system body. 38 # This supplies only G; a read declaration cannot confer write operations. 39 def grant(~S:Data,~H:Type,~G:Type,declaration:Declaration) -> G: 40 match declaration: 41 case Declaration{grant,_,_}:grant 42 43 # Constructed-component counterpart of read, using the declaration-bound save recipe. 44 # take/put must preserve the matching Leaf.Component and all remaining storage. 45 # Requires presence and returns a detached V while retaining the affine payload. 46 # Reads return Found, ComponentAbsent or MissingEntity (including stale/foreign). 47 def constructed_read(~S:Data,~P:Type,~Store:Type,~R:Type,~E:Data,~Rest:Type,~V:Data,~Token:Data,~identity:Identity.Declaration,~save:P -> P & D.Raw,~view:P -> P & V,~take:Store -> Rest & Leaf.Component,~put:Rest -> Leaf.Component -> Store) -> Declaration,Cap.Read,V>>: 48 Declaration{Q.read_family(~S,~Store,~R,~E,~(Rest & Recipe.Constructed),~P,~V,~Token,~Family.take_column(~S,~P,~Store,~Rest,~identity,~save,~take),~Family.put_column(~S,~P,~Store,~Rest,~identity,~save,~put),~view,~Family.family(~S,~P,~Store,~Rest,~V,~Token,~identity,~save,~view,~take,~put)),Q.family_match(~S,~Store,~R,~E,~(Rest & Recipe.Constructed),~P,~V,~Token,~Family.take_column(~S,~P,~Store,~Rest,~identity,~save,~take),~Family.put_column(~S,~P,~Store,~Rest,~identity,~save,~put),~view,~Family.family(~S,~P,~Store,~Rest,~V,~Token,~identity,~save,~view,~take,~put),~Q.Required{}),[Clause{Identity.key(~S,~P,identity),Read{}}]} 49 50 # Constructed-component counterpart of write, using the declaration-bound save recipe. 51 # take/put must preserve the matching Leaf.Component and all remaining storage. 52 # Requires presence and grants transactional payload replacement, including rollback. 53 # Failed sets retain an error in Frame; Compose reports AccessError and rolls back. 54 # The set grant consumes P and does not return rejected input to the caller. 55 def constructed_write(~S:Data,~P:Type,~Store:Type,~R:Type,~E:Data,~Rest:Type,~V:Data,~Token:Data,~identity:Identity.Declaration,~save:P -> P & D.Raw,~view:P -> P & V,~take:Store -> Rest & Leaf.Component,~put:Rest -> Leaf.Component -> Store) -> Declaration,Cap.Write,P,V>>: 56 Declaration{Q.write_family(~S,~Store,~R,~E,~(Rest & Recipe.Constructed),~P,~V,~Token,~Family.take_column(~S,~P,~Store,~Rest,~identity,~save,~take),~Family.put_column(~S,~P,~Store,~Rest,~identity,~save,~put),~view,~Family.family(~S,~P,~Store,~Rest,~V,~Token,~identity,~save,~view,~take,~put)),Q.family_match(~S,~Store,~R,~E,~(Rest & Recipe.Constructed),~P,~V,~Token,~Family.take_column(~S,~P,~Store,~Rest,~identity,~save,~take),~Family.put_column(~S,~P,~Store,~Rest,~identity,~save,~put),~view,~Family.family(~S,~P,~Store,~Rest,~V,~Token,~identity,~save,~view,~take,~put),~Q.Required{}),[Clause{Identity.key(~S,~P,identity),Write{}}]} 57 58 # Constructed-component counterpart of optional, using the declaration-bound save recipe. 59 # take/put must preserve the matching Leaf.Component and all remaining storage. 60 # Allows absence; reads return Found, ComponentAbsent or MissingEntity. 61 # Stale/foreign handles yield MissingEntity, never an optional matching row. 62 def constructed_optional(~S:Data,~P:Type,~Store:Type,~R:Type,~E:Data,~Rest:Type,~V:Data,~Token:Data,~identity:Identity.Declaration,~save:P -> P & D.Raw,~view:P -> P & V,~take:Store -> Rest & Leaf.Component,~put:Rest -> Leaf.Component -> Store) -> Declaration,Cap.Read,V>>: 63 Declaration{Q.read_family(~S,~Store,~R,~E,~(Rest & Recipe.Constructed),~P,~V,~Token,~Family.take_column(~S,~P,~Store,~Rest,~identity,~save,~take),~Family.put_column(~S,~P,~Store,~Rest,~identity,~save,~put),~view,~Family.family(~S,~P,~Store,~Rest,~V,~Token,~identity,~save,~view,~take,~put)),Q.family_match(~S,~Store,~R,~E,~(Rest & Recipe.Constructed),~P,~V,~Token,~Family.take_column(~S,~P,~Store,~Rest,~identity,~save,~take),~Family.put_column(~S,~P,~Store,~Rest,~identity,~save,~put),~view,~Family.family(~S,~P,~Store,~Rest,~V,~Token,~identity,~save,~view,~take,~put),~Q.Optional{}),[Clause{Identity.key(~S,~P,identity),Optional{}}]} 64 65 # Constructed-component counterpart of with_component, using the declaration-bound save recipe. 66 # take/put must preserve the matching Leaf.Component and all remaining storage. 67 # Requires presence without granting payload access. 68 def constructed_with_component(~S:Data,~P:Type,~Store:Type,~R:Type,~E:Data,~Rest:Type,~V:Data,~Token:Data,~identity:Identity.Declaration,~save:P -> P & D.Raw,~view:P -> P & V,~take:Store -> Rest & Leaf.Component,~put:Rest -> Leaf.Component -> Store) -> Declaration,Unit>: 69 Declaration{Unit{},Q.family_match(~S,~Store,~R,~E,~(Rest & Recipe.Constructed),~P,~V,~Token,~Family.take_column(~S,~P,~Store,~Rest,~identity,~save,~take),~Family.put_column(~S,~P,~Store,~Rest,~identity,~save,~put),~view,~Family.family(~S,~P,~Store,~Rest,~V,~Token,~identity,~save,~view,~take,~put),~Q.Required{}),[Clause{Identity.key(~S,~P,identity),With{}}]} 70 71 # Constructed-component counterpart of without_component, using the declaration-bound save recipe. 72 # take/put must preserve the matching Leaf.Component and all remaining storage. 73 # Requires absence without granting payload access. 74 def constructed_without_component(~S:Data,~P:Type,~Store:Type,~R:Type,~E:Data,~Rest:Type,~V:Data,~Token:Data,~identity:Identity.Declaration,~save:P -> P & D.Raw,~view:P -> P & V,~take:Store -> Rest & Leaf.Component,~put:Rest -> Leaf.Component -> Store) -> Declaration,Unit>: 75 Declaration{Unit{},Q.family_match(~S,~Store,~R,~E,~(Rest & Recipe.Constructed),~P,~V,~Token,~Family.take_column(~S,~P,~Store,~Rest,~identity,~save,~take),~Family.put_column(~S,~P,~Store,~Rest,~identity,~save,~put),~view,~Family.family(~S,~P,~Store,~Rest,~V,~Token,~identity,~save,~view,~take,~put),~Q.Absent{}),[Clause{Identity.key(~S,~P,identity),Without{}}]} 76 77 # Constructed-component counterpart of added, using the declaration-bound save recipe. 78 # take/put must preserve the matching Leaf.Component and all remaining storage. 79 # Matches added stamps relative to the executing system cursor; grants no access. 80 def constructed_added(~S:Data,~P:Type,~Store:Type,~R:Type,~E:Data,~Rest:Type,~V:Data,~Token:Data,~identity:Identity.Declaration,~save:P -> P & D.Raw,~view:P -> P & V,~take:Store -> Rest & Leaf.Component,~put:Rest -> Leaf.Component -> Store) -> Declaration,Unit>: 81 Declaration{Unit{},Q.lifecycle_match(~S,~Store,~R,~E,~(Rest & Recipe.Constructed),~P,~V,~Token,~Family.take_column(~S,~P,~Store,~Rest,~identity,~save,~take),~Family.put_column(~S,~P,~Store,~Rest,~identity,~save,~put),~view,~Family.family(~S,~P,~Store,~Rest,~V,~Token,~identity,~save,~view,~take,~put),~Q.Added{}),[Clause{Identity.key(~S,~P,identity),Added{}}]} 82 83 # Constructed-component counterpart of changed, using the declaration-bound save recipe. 84 # take/put must preserve the matching Leaf.Component and all remaining storage. 85 # Matches changed stamps relative to the executing system cursor; grants no access. 86 def constructed_changed(~S:Data,~P:Type,~Store:Type,~R:Type,~E:Data,~Rest:Type,~V:Data,~Token:Data,~identity:Identity.Declaration,~save:P -> P & D.Raw,~view:P -> P & V,~take:Store -> Rest & Leaf.Component,~put:Rest -> Leaf.Component -> Store) -> Declaration,Unit>: 87 Declaration{Unit{},Q.lifecycle_match(~S,~Store,~R,~E,~(Rest & Recipe.Constructed),~P,~V,~Token,~Family.take_column(~S,~P,~Store,~Rest,~identity,~save,~take),~Family.put_column(~S,~P,~Store,~Rest,~identity,~save,~put),~view,~Family.family(~S,~P,~Store,~Rest,~V,~Token,~identity,~save,~view,~take,~put),~Q.Changed{}),[Clause{Identity.key(~S,~P,identity),Changed{}}]} 88 89 # Read the selected component through a read declaration, returning owner H 90 # with Access. The detached Data view does not copy the affine payload. 91 # Results are Found, ComponentAbsent or MissingEntity; stale/foreign handles 92 # produce MissingEntity. No payload owner is transferred to the reader. 93 def execute_read(~S:Data,~H:Type,~V:Data,~declaration:Declaration>,owner:H) -> H & Cmp.Access: 94 Cap.read(~H,~V,~grant(~S,~H,~Cap.Read,declaration),owner) 95 96 # Consume the declaration to obtain its owner-threading row predicate. 97 # Selection grants no additional component access and does not enumerate rows. 98 def selector(~S:Data,~H:Type,~G:Type,declaration:Declaration) -> H -> H & Bool: 99 match declaration: 100 case Declaration{_,select,_}:select 101 # Consume the declaration and return detached schema-scoped query metadata. 102 # Prefer observe when the caller also needs to retain the declaration. 103 def clauses(~S:Data,~H:Type,~G:Type,declaration:Declaration) -> List<&2,Clause>: 104 match declaration: 105 case Declaration{_,_,clauses}:clauses 106 def selected_right(~H:Type,selected:Bool,observed:H & Bool) -> H & Bool: 107 match observed: 108 case (owner,right):(owner,Bool.and(selected,right)) 109 def selected_left(~H:Type,~right:H -> H & Bool,observed:H & Bool) -> H & Bool: 110 match observed: 111 case (owner,False{}):(owner,False{}) 112 case (owner,True{}):right(owner) 113 def both(~H:Type,~left:H -> H & Bool,~right:H -> H & Bool,owner:H) -> H & Bool: 114 selected_left(~H,~right,left(owner)) 115 def pair_selected(~H:Type,right:H -> H & Bool,observed:H & Bool) -> H & Bool: 116 match observed: 117 case (owner,False{}):(owner,False{}) 118 case (owner,True{}):right(owner) 119 def pair_select(~H:Type,left:H -> H & Bool,right:H -> H & Bool,owner:H) -> H & Bool: 120 pair_selected(~H,right,left(owner)) 121 def pair_parts(~S:Data,~H:Type,~L:Type,~R:Type,left:Declaration,right:Declaration) -> Declaration: 122 match left right: 123 case Declaration{leftGrant,leftSelect,leftClauses} Declaration{rightGrant,rightSelect,rightClauses}:Declaration{(leftGrant,rightGrant),owner=>pair_select(~H,leftSelect,rightSelect,owner),List.append(&2,Clause,leftClauses,rightClauses)} 124 # Compose two declarations for the same schema and owner into one query. 125 # Selection is conjunction, evaluated left then right with short circuiting; 126 # grants retain the nested (L & R) shape and clauses retain left-to-right order. 127 # Nest pair to combine more than two selections; it does not impose a row order. 128 # Example: examples/scheduled-health/registration.bend defines query(). 129 def pair(~S:Data,~H:Type,~L:Type,~R:Type,~left:Declaration,~right:Declaration) -> Declaration: 130 pair_parts(~S,~H,~L,~R,left,right) 131 132 # Require this component and grant read access to its detached Data view. 133 # identity and the storage binding must describe the same schema component. 134 # view consumes and returns P, including affine payloads; it must not discard 135 # the payload. This declaration neither visits rows nor changes the world. 136 # Reads return Found, ComponentAbsent or MissingEntity (including stale/foreign). 137 # Example bindings and query(): examples/scheduled-health/registration.bend. 138 def read(~S:Data,~P:Type,~Store:Type,~R:Type,~E:Data,~Rest:Type,~V:Data,~Token:Data,~identity:Identity.Declaration,~view:P -> P & V,~take:Store -> Rest & Col.Column,~put:Rest -> Col.Column -> Store) -> Declaration,Cap.Read,V>>: 139 Declaration{Q.read_family(~S,~Store,~R,~E,~Rest,~P,~V,~Token,~take,~put,~view,~Cmp.Family{Identity.key(~S,~P,identity)}),Q.family_match(~S,~Store,~R,~E,~Rest,~P,~V,~Token,~take,~put,~view,~Cmp.Family{Identity.key(~S,~P,identity)},~Q.Required{}),[Clause{Identity.key(~S,~P,identity),Read{}}]} 140 141 # Require this component and grant typed read/write operations to the body. 142 # The set operation consumes replacement P and returns the affine owner. 143 # Failed sets retain an error in Frame; Compose reports AccessError and rolls back. 144 # Rejected replacement payloads are not returned through this grant. Reads expose V. 145 # Writes participate in the enclosing system transaction and its rollback; 146 # declaring access performs no write. See scheduled-health/registration.bend. 147 def write(~S:Data,~P:Type,~Store:Type,~R:Type,~E:Data,~Rest:Type,~V:Data,~Token:Data,~identity:Identity.Declaration,~view:P -> P & V,~take:Store -> Rest & Col.Column,~put:Rest -> Col.Column -> Store) -> Declaration,Cap.Write,P,V>>: 148 Declaration{Q.write_family(~S,~Store,~R,~E,~Rest,~P,~V,~Token,~take,~put,~view,~Cmp.Family{Identity.key(~S,~P,identity)}),Q.family_match(~S,~Store,~R,~E,~Rest,~P,~V,~Token,~take,~put,~view,~Cmp.Family{Identity.key(~S,~P,identity)},~Q.Required{}),[Clause{Identity.key(~S,~P,identity),Write{}}]} 149 150 # Grant read access without excluding rows where the component is absent. 151 # The body handles Found/ComponentAbsent/MissingEntity; optional fabricates no V. 152 # Stale/foreign handles yield MissingEntity and never match as live rows. 153 # Payload projection consumes and returns P, preserving affine ownership. 154 def optional(~S:Data,~P:Type,~Store:Type,~R:Type,~E:Data,~Rest:Type,~V:Data,~Token:Data,~identity:Identity.Declaration,~view:P -> P & V,~take:Store -> Rest & Col.Column,~put:Rest -> Col.Column -> Store) -> Declaration,Cap.Read,V>>: 155 Declaration{Q.read_family(~S,~Store,~R,~E,~Rest,~P,~V,~Token,~take,~put,~view,~Cmp.Family{Identity.key(~S,~P,identity)}),Q.family_match(~S,~Store,~R,~E,~Rest,~P,~V,~Token,~take,~put,~view,~Cmp.Family{Identity.key(~S,~P,identity)},~Q.Optional{}),[Clause{Identity.key(~S,~P,identity),Optional{}}]} 156 157 # Filter to entities with this component without granting its payload access. 158 # Produces Unit grants; combine with pair to add independently declared access. 159 def with_component(~S:Data,~P:Type,~Store:Type,~R:Type,~E:Data,~Rest:Type,~V:Data,~Token:Data,~identity:Identity.Declaration,~view:P -> P & V,~take:Store -> Rest & Col.Column,~put:Rest -> Col.Column -> Store) -> Declaration,Unit>: 160 Declaration{Unit{},Q.family_match(~S,~Store,~R,~E,~Rest,~P,~V,~Token,~take,~put,~view,~Cmp.Family{Identity.key(~S,~P,identity)},~Q.Present{}),[Clause{Identity.key(~S,~P,identity),With{}}]} 161 162 # Filter to entities without this component, granting no payload access. 163 # Membership follows the executing query and explicit structural barriers. 164 def without_component(~S:Data,~P:Type,~Store:Type,~R:Type,~E:Data,~Rest:Type,~V:Data,~Token:Data,~identity:Identity.Declaration,~view:P -> P & V,~take:Store -> Rest & Col.Column,~put:Rest -> Col.Column -> Store) -> Declaration,Unit>: 165 Declaration{Unit{},Q.family_match(~S,~Store,~R,~E,~Rest,~P,~V,~Token,~take,~put,~view,~Cmp.Family{Identity.key(~S,~P,identity)},~Q.Absent{}),[Clause{Identity.key(~S,~P,identity),Without{}}]} 166 167 # Filter by this component being added since the executing system cursor. 168 # Grants no payload access; combine with read/write to access matching values. 169 # Declaration creation and detached inspection do not consume lifecycle visibility. 170 def added(~S:Data,~P:Type,~Store:Type,~R:Type,~E:Data,~Rest:Type,~V:Data,~Token:Data,~identity:Identity.Declaration,~view:P -> P & V,~take:Store -> Rest & Col.Column,~put:Rest -> Col.Column -> Store) -> Declaration,Unit>: 171 Declaration{Unit{},Q.lifecycle_match(~S,~Store,~R,~E,~Rest,~P,~V,~Token,~take,~put,~view,~Cmp.Family{Identity.key(~S,~P,identity)},~Q.Added{}),[Clause{Identity.key(~S,~P,identity),Added{}}]} 172 173 # Filter by this component changing since the executing system cursor. 174 # Grants no payload access; cursor advancement follows system success/skip/failure 175 # rules, not creation or inspection of this declaration. 176 def changed(~S:Data,~P:Type,~Store:Type,~R:Type,~E:Data,~Rest:Type,~V:Data,~Token:Data,~identity:Identity.Declaration,~view:P -> P & V,~take:Store -> Rest & Col.Column,~put:Rest -> Col.Column -> Store) -> Declaration,Unit>: 177 Declaration{Unit{},Q.lifecycle_match(~S,~Store,~R,~E,~Rest,~P,~V,~Token,~take,~put,~view,~Cmp.Family{Identity.key(~S,~P,identity)},~Q.Changed{}),[Clause{Identity.key(~S,~P,identity),Changed{}}]} 178 179 # Declare required write access and a display function for its Data view. 180 # display formats detached V for enabled debug output; it never owns P. 181 # Failed sets retain an error in Frame; Compose reports AccessError and rolls back. 182 # The set grant consumes P without returning rejected input; see write. 183 def write_displayed(~S:Data,~P:Type,~Store:Type,~R:Type,~E:Data,~Rest:Type,~V:Data,~Token:Data,~identity:Identity.Declaration,~view:P -> P & V,~display:V -> String,~take:Store -> Rest & Col.Column,~put:Rest -> Col.Column -> Store) -> Declaration,Cap.Write,P,V>>: 184 Declaration{Q.write_family_displayed(~S,~Store,~R,~E,~Rest,~P,~V,~Token,~take,~put,~view,~Cmp.Family{Identity.key(~S,~P,identity)},~display),Q.family_match(~S,~Store,~R,~E,~Rest,~P,~V,~Token,~take,~put,~view,~Cmp.Family{Identity.key(~S,~P,identity)},~Q.Required{}),[Clause{Identity.key(~S,~P,identity),Write{}}]}