@bjornpagen/bumbledb 0.2.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (94) hide show
  1. package/COOKBOOK.md +503 -427
  2. package/README.md +84 -36
  3. package/dist/closed.d.ts +111 -38
  4. package/dist/closed.d.ts.map +1 -1
  5. package/dist/closed.js +94 -99
  6. package/dist/closed.js.map +1 -1
  7. package/dist/db.d.ts +16 -2
  8. package/dist/db.d.ts.map +1 -1
  9. package/dist/db.js +46 -8
  10. package/dist/db.js.map +1 -1
  11. package/dist/face.d.ts +114 -69
  12. package/dist/face.d.ts.map +1 -1
  13. package/dist/face.js +38 -21
  14. package/dist/face.js.map +1 -1
  15. package/dist/fields.d.ts +72 -87
  16. package/dist/fields.d.ts.map +1 -1
  17. package/dist/fields.js +35 -67
  18. package/dist/fields.js.map +1 -1
  19. package/dist/index.d.ts +18 -13
  20. package/dist/index.d.ts.map +1 -1
  21. package/dist/index.js +11 -7
  22. package/dist/index.js.map +1 -1
  23. package/dist/law.d.ts +224 -0
  24. package/dist/law.d.ts.map +1 -0
  25. package/dist/law.js +224 -0
  26. package/dist/law.js.map +1 -0
  27. package/dist/lower.d.ts +17 -10
  28. package/dist/lower.d.ts.map +1 -1
  29. package/dist/lower.js +34 -23
  30. package/dist/lower.js.map +1 -1
  31. package/dist/marshal.d.ts +33 -6
  32. package/dist/marshal.d.ts.map +1 -1
  33. package/dist/marshal.js +67 -6
  34. package/dist/marshal.js.map +1 -1
  35. package/dist/native.d.ts +6 -2
  36. package/dist/native.d.ts.map +1 -1
  37. package/dist/native.js.map +1 -1
  38. package/dist/query/atom.d.ts +139 -56
  39. package/dist/query/atom.d.ts.map +1 -1
  40. package/dist/query/atom.js +7 -1
  41. package/dist/query/atom.js.map +1 -1
  42. package/dist/query/lower.d.ts +71 -56
  43. package/dist/query/lower.d.ts.map +1 -1
  44. package/dist/query/lower.js +341 -69
  45. package/dist/query/lower.js.map +1 -1
  46. package/dist/query/predicate.d.ts +10 -9
  47. package/dist/query/predicate.d.ts.map +1 -1
  48. package/dist/query/predicate.js +2 -2
  49. package/dist/query/predicate.js.map +1 -1
  50. package/dist/query/run.d.ts +15 -5
  51. package/dist/query/run.d.ts.map +1 -1
  52. package/dist/query/run.js +26 -6
  53. package/dist/query/run.js.map +1 -1
  54. package/dist/query/scope.d.ts +100 -43
  55. package/dist/query/scope.d.ts.map +1 -1
  56. package/dist/query/scope.js +89 -30
  57. package/dist/query/scope.js.map +1 -1
  58. package/dist/query/select.d.ts +5 -5
  59. package/dist/query/select.d.ts.map +1 -1
  60. package/dist/relation.d.ts +29 -15
  61. package/dist/relation.d.ts.map +1 -1
  62. package/dist/relation.js +45 -17
  63. package/dist/relation.js.map +1 -1
  64. package/dist/schema.d.ts +41 -3
  65. package/dist/schema.d.ts.map +1 -1
  66. package/dist/schema.js +16 -2
  67. package/dist/schema.js.map +1 -1
  68. package/dist/spec.d.ts +10 -8
  69. package/dist/spec.d.ts.map +1 -1
  70. package/dist/spec.js.map +1 -1
  71. package/dist/statements.d.ts +67 -31
  72. package/dist/statements.d.ts.map +1 -1
  73. package/dist/statements.js +97 -20
  74. package/dist/statements.js.map +1 -1
  75. package/package.json +2 -2
  76. package/src/closed.ts +214 -146
  77. package/src/db.ts +65 -10
  78. package/src/face.ts +169 -102
  79. package/src/fields.ts +97 -164
  80. package/src/index.ts +43 -18
  81. package/src/law.ts +519 -0
  82. package/src/lower.ts +36 -23
  83. package/src/marshal.ts +74 -7
  84. package/src/native.ts +6 -2
  85. package/src/query/atom.ts +155 -65
  86. package/src/query/lower.ts +572 -167
  87. package/src/query/predicate.ts +43 -33
  88. package/src/query/run.ts +26 -6
  89. package/src/query/scope.ts +161 -51
  90. package/src/query/select.ts +5 -5
  91. package/src/relation.ts +60 -26
  92. package/src/schema.ts +48 -7
  93. package/src/spec.ts +10 -8
  94. package/src/statements.ts +165 -46
package/src/marshal.ts CHANGED
@@ -13,10 +13,22 @@
13
13
  * the engine admitted IS a legal fact of its relation (the same trust
14
14
  * direction as Rust's typed readback). Shape mismatches here are genuine
15
15
  * failures and THROW typed; they are never domain data.
16
+ *
17
+ * THE CLOSED BIJECTION (0.4.0): a closed-referencing cell crosses this
18
+ * boundary as its handle NAME — the write side lowers name → u64 row id
19
+ * (declaration order = row ids, the sealed roster's own law, ≤ 256 rows),
20
+ * the read side lifts id → name, and both directions are total and static
21
+ * over the roster. An unknown name is a pointed THROW at the write seam —
22
+ * a deliberate UPGRADE over 0.3.0, where any bigint sailed through the
23
+ * marshal to a commit-time containment violation; the wrong spelling now
24
+ * dies here, before the engine ever sees the row. An out-of-roster id on
25
+ * the read side (reachable only in a store whose closed-typed column was
26
+ * never pinned by its containment law) is equally pointed — never a
27
+ * silent fallback, never `undefined`.
16
28
  */
17
29
 
18
30
  import * as errors from "@superbuilders/errors"
19
- import type { AnyField } from "#fields.ts"
31
+ import type { AnyField, ClosedRoster } from "#fields.ts"
20
32
  import type { FactValue } from "#native.ts"
21
33
  import type { AnyRelation, Fact, FreshKeys, RelationData } from "#relation.ts"
22
34
 
@@ -80,14 +92,64 @@ function recordOf(fact: object): Record<string, unknown> {
80
92
  return Object.fromEntries(Object.entries(fact))
81
93
  }
82
94
 
95
+ /**
96
+ * The write half of the closed bijection: one handle NAME to its u64 row
97
+ * id (declaration order = row ids — the engine's own minting of the
98
+ * sealed extension). An unknown name is a pointed refusal naming the
99
+ * vocabulary and its roster — the 0.4.0 upgrade over any-bigint-compiles:
100
+ * the wrong spelling dies at the marshal, never as a commit-time
101
+ * violation. `indexOf` is the whole machine (the roster is ≤ 256 rows,
102
+ * engine law — no map is warranted).
103
+ */
104
+ function closedCellOf(context: string, closed: ClosedRoster, name: string): FactValue {
105
+ const id = closed.handles.indexOf(name)
106
+ if (id === -1) {
107
+ throw errors.new(
108
+ `${context}: "${name}" is not a handle of ${closed.name} — the roster is ${closed.handles.join(", ")}`
109
+ )
110
+ }
111
+ return BigInt(id)
112
+ }
113
+
114
+ /**
115
+ * The read half of the closed bijection: one u64 row id back to its handle
116
+ * NAME (`Number(cell)` is safe — the sealed extension holds at most 256
117
+ * rows, engine law). An id outside the roster THROWS pointed, never a
118
+ * silent fallback and never `undefined`: the state is reachable only in a
119
+ * store whose closed-typed column was never pinned by its containment law,
120
+ * and the error names that missing piece.
121
+ */
122
+ function handleOf(context: string, closed: ClosedRoster, cell: FactValue): string {
123
+ if (typeof cell !== "bigint") {
124
+ throw cellShapeError(context, `a ${closed.name} handle id (bigint)`, cell)
125
+ }
126
+ const handle = closed.handles[Number(cell)]
127
+ if (handle === undefined) {
128
+ throw errors.new(
129
+ `${context}: id ${cell} is outside the ${closed.name} roster (${closed.handles.join(", ")}) — the column types ${closed.name} but no law pins it — a containment statement is the missing piece`
130
+ )
131
+ }
132
+ return handle
133
+ }
134
+
83
135
  /**
84
136
  * Marshals one host cell at its field position to the natural wire value,
85
137
  * schema-directed by the field descriptor's structural kind (never
86
- * guessed). Values are bare, so the runtime values ARE the wire's natural
87
- * JS values; widths and domain labels are the engine's own judgment at the
88
- * write boundary.
138
+ * guessed). A closed-referencing cell arrives as its handle NAME and
139
+ * lowers through {@link closedCellOf} the arm precedes the switch
140
+ * because a closed reference is structurally a u64 descriptor plus the
141
+ * roster (the same precedence `Infer` pins at the type level). Everything
142
+ * else is bare, so the runtime values ARE the wire's natural JS values;
143
+ * widths and domain labels are the engine's own judgment at the write
144
+ * boundary.
89
145
  */
90
146
  function cellOf(context: string, field: AnyField, value: unknown): FactValue {
147
+ if ("closed" in field) {
148
+ if (typeof value !== "string") {
149
+ throw cellShapeError(context, `a ${field.closed.name} handle name (string)`, value)
150
+ }
151
+ return closedCellOf(context, field.closed, value)
152
+ }
91
153
  switch (field.kind) {
92
154
  case "bool": {
93
155
  if (typeof value !== "boolean") {
@@ -210,7 +272,9 @@ function isMintedFresh<R extends AnyRelation>(
210
272
  /**
211
273
  * Unmarshals one positional row to the relation's named, frozen fact object
212
274
  * of bare structural values — the inverse of {@link rowOf},
213
- * ordinal-directed by the same declaration order.
275
+ * ordinal-directed by the same declaration order. Closed-referencing cells
276
+ * lift id → handle NAME through {@link handleOf} (the read half of the
277
+ * bijection), so every fact a user sees speaks the roster's vocabulary.
214
278
  */
215
279
  function factOf<R extends AnyRelation>(relation: R, row: readonly FactValue[]): Fact<R> {
216
280
  const data = relation.data
@@ -225,7 +289,10 @@ function factOf<R extends AnyRelation>(relation: R, row: readonly FactValue[]):
225
289
  if (cell === undefined) {
226
290
  throw errors.new(`relation ${data.name}: row cell ${ordinal} (${declared.name}) is absent`)
227
291
  }
228
- decoded[declared.name] = cell
292
+ decoded[declared.name] =
293
+ "closed" in declared.field
294
+ ? handleOf(`relation ${data.name} field ${declared.name}`, declared.field.closed, cell)
295
+ : cell
229
296
  })
230
297
  Object.freeze(decoded)
231
298
  if (!isCompleteFact(relation, decoded)) {
@@ -235,4 +302,4 @@ function factOf<R extends AnyRelation>(relation: R, row: readonly FactValue[]):
235
302
  }
236
303
 
237
304
  export type { KeyFact, Minted }
238
- export { cellOf, factOf, isFreshField, isMintedFresh, keyRowOf, recordOf, rowOf }
305
+ export { cellOf, factOf, handleOf, isFreshField, isMintedFresh, keyRowOf, recordOf, rowOf }
package/src/native.ts CHANGED
@@ -244,13 +244,17 @@ interface Violation {
244
244
  * `dbCreate`/`dbOpen`'s domain outcome. `schemaError` covers both spec
245
245
  * resolution (unresolvable names, banned spellings — every issue in one
246
246
  * message) and schema validation at the declaration boundary;
247
- * `fingerprintMismatch` is `dbOpen`'s stored-theory refusal.
247
+ * `newtypeMismatch` is the coherence wall's own kind — a spec whose
248
+ * statement pairs faces with disagreeing newtype labels (the engine twin
249
+ * of the schema-level class wall; unreachable through the typed builder,
250
+ * which computes every label from the laws, so only a raw spec can reach
251
+ * it); `fingerprintMismatch` is `dbOpen`'s stored-theory refusal.
248
252
  */
249
253
  type DbOpenResult =
250
254
  | { readonly ok: true; readonly db: DbHandle }
251
255
  | {
252
256
  readonly ok: false
253
- readonly kind: "schemaError" | "fingerprintMismatch"
257
+ readonly kind: "schemaError" | "newtypeMismatch" | "fingerprintMismatch"
254
258
  readonly message: string
255
259
  }
256
260
 
package/src/query/atom.ts CHANGED
@@ -4,6 +4,9 @@
4
4
  * (`bumbledb/crates/bumbledb/src/ir.rs`, the bijection target;
5
5
  * `docs/architecture/20-query-ir.md` normative). A `match` binding record
6
6
  * binds fields to vars, params, ∈-set params, or bare structural literals
7
+ * — a closed-reference field's literal is its handle NAME, and a plain
8
+ * ARRAY of names there is membership, folded into the program (closed-only
9
+ * by owner ruling; see {@link BindingInput})
7
10
  * (unmentioned fields ARE the wildcard — no wildcard value exists);
8
11
  * `not(Rel, {...})` is negation-as-position (anti-join); `eq`/`ne` and the
9
12
  * order roster, `pointIn`/`covers` (both spellings of `ir::CmpOp::PointIn`,
@@ -25,8 +28,11 @@
25
28
  */
26
29
 
27
30
  import * as errors from "@superbuilders/errors"
28
- import type { AnyField, Infer, IntervalValue } from "#fields.ts"
31
+ import type { AnyClosed } from "#closed.ts"
32
+ import type { AnyField, ClosedIdField, ClosedRoster, Infer, IntervalValue } from "#fields.ts"
33
+ import type { ClassLookup, ClassRecordOf, SchemaClasses } from "#law.ts"
29
34
  import type {
35
+ ClassedField,
30
36
  Duration,
31
37
  EnvShape,
32
38
  JoinOk,
@@ -38,25 +44,59 @@ import type {
38
44
  Var
39
45
  } from "#query/scope.ts"
40
46
  import { isTerm } from "#query/scope.ts"
41
- import type { AnyRelation, FieldsShape, Relation } from "#relation.ts"
47
+ import type { AnyRelation, FieldsShape, RelationFields } from "#relation.ts"
42
48
 
43
- /** One atom-binding position as runtime data. */
49
+ /**
50
+ * What a query atom matches over: an ordinary relation or a CLOSED
51
+ * vocabulary (ψ query atoms — the engine folds a resolvable closed atom
52
+ * into a plan-constant member set at prepare, or joins the L1-resident
53
+ * virtual image when the shape does not fold; the SDK never pre-folds and
54
+ * never knows which — transparency is the contract).
55
+ */
56
+ type MatchOwner = AnyRelation | AnyClosed
57
+
58
+ /**
59
+ * The matchable field block of an atom owner: a relation's declared
60
+ * fields; a closed relation's SEALED shape — the synthetic `id` (the
61
+ * value's OWN roster-carrying descriptor, at its precise type: the handle
62
+ * union rides into ψ id bindings and joins exactly as it does on a
63
+ * referencing column) first, then the declared payload columns read
64
+ * through the typed `columns` carrier (the one source of payload typing —
65
+ * no parallel column table exists). The runtime twin is `matchFieldsOf` in
66
+ * `#query/lower.ts`; the id-first ordinal shift the two tiers share is
67
+ * pinned by the lowering golden.
68
+ */
69
+ type MatchFields<R extends MatchOwner> = R extends AnyClosed
70
+ ? { readonly id: R["id"] } & R["columns"]
71
+ : R extends AnyRelation
72
+ ? RelationFields<R>
73
+ : never
74
+
75
+ /**
76
+ * One atom-binding position as runtime data. `literalSet` is a membership
77
+ * ARRAY at a closed-reference field, folded into the program: `name` is
78
+ * the content-addressed registry key its dense `ParamId` is minted under
79
+ * (the lowering rides the existing param-set term; the SDK itself supplies
80
+ * the translated member set at every execute — never the host).
81
+ */
44
82
  type BindingTermData =
45
83
  | { readonly kind: "var"; readonly name: string }
46
84
  | { readonly kind: "param"; readonly name: string }
47
85
  | { readonly kind: "setParam"; readonly name: string }
86
+ | { readonly kind: "literalSet"; readonly name: string; readonly members: readonly string[] }
48
87
  | { readonly kind: "literal"; readonly value: unknown }
49
88
 
50
- /** One resolved binding: the field's name, its descriptor, and the term. */
89
+ /** One resolved binding: the field's name, its descriptor, its law-computed class, and the term. */
51
90
  interface BindingEntry {
52
91
  readonly field: string
53
92
  readonly data: AnyField
93
+ readonly class: string | undefined
54
94
  readonly term: BindingTermData
55
95
  }
56
96
 
57
- /** One EDB atom as runtime data (either polarity — polarity is the rule item's). */
97
+ /** One EDB atom as runtime data (either polarity — polarity is the rule item's; a closed owner is a ψ atom). */
58
98
  interface AtomData {
59
- readonly relation: AnyRelation
99
+ readonly relation: MatchOwner
60
100
  readonly bindings: readonly BindingEntry[]
61
101
  }
62
102
 
@@ -107,10 +147,18 @@ type SelectEntryData =
107
147
  | { readonly kind: "measure"; readonly over: string }
108
148
  | { readonly kind: "aggregate"; readonly agg: AggData }
109
149
 
110
- /** One answer column: its name (the row object key) and its entry. */
150
+ /**
151
+ * One answer column: its name (the row object key), its entry, and — when
152
+ * the column's value is a closed reference (a projected var or an
153
+ * Arg-carried payload bound at a closed-referencing field) — the roster the
154
+ * decode lifts row ids back to handle NAMES through (the read half of the
155
+ * marshal bijection; `undefined` on every bare column). The slice is
156
+ * SDK-side marshaling data only: the wire `ProgramIr` never carries it.
157
+ */
111
158
  interface SelectColumn {
112
159
  readonly name: string
113
160
  readonly entry: SelectEntryData
161
+ readonly closed: ClosedRoster | undefined
114
162
  }
115
163
 
116
164
  /** One body item of a rule, in written order. */
@@ -123,21 +171,25 @@ type RuleItem =
123
171
  /**
124
172
  * One use of a parameter inside a rule, in written order: the census the
125
173
  * query-level registry folds (first use mints the dense `ParamId`, first
126
- * FIELD-ANCHORED use types the wire).
174
+ * FIELD-ANCHORED use types the wire). `members` is present exactly on a
175
+ * membership-array use (a literal set folded into the program): the handle
176
+ * names the SDK itself translates and supplies at execute — the entry
177
+ * never appears in the host's params object.
127
178
  */
128
179
  interface ParamUse {
129
180
  readonly name: string
130
181
  readonly shape: "value" | "set" | "mask"
131
182
  readonly anchor: AnyField | "measure" | undefined
132
183
  readonly op: "binding" | CmpKind
184
+ readonly members: readonly string[] | undefined
133
185
  }
134
186
 
135
187
  /** One complete rule as runtime data. */
136
188
  interface RuleData {
137
189
  readonly items: readonly RuleItem[]
138
190
  readonly select: readonly SelectColumn[]
139
- /** Variable name → the field descriptor its FIRST positive binding carries (the runtime env). */
140
- readonly varFields: Readonly<Record<string, AnyField>>
191
+ /** Variable name → the classed slot its FIRST positive binding carries (the runtime env — descriptor + class). */
192
+ readonly varFields: Readonly<Record<string, ClassedField>>
141
193
  readonly paramUses: readonly ParamUse[]
142
194
  }
143
195
 
@@ -156,10 +208,18 @@ interface RecData {
156
208
  * of the field's value type, a var/param/∈-set-param term — and, when the
157
209
  * field is interval-typed, a bare point literal (the IR's membership
158
210
  * typing rule: an element-typed term at an interval field is point
159
- * membership; an interval-typed term is value equality).
211
+ * membership; an interval-typed term is value equality). A
212
+ * CLOSED-reference field additionally takes a plain ARRAY of handle names
213
+ * read as membership — `kind: ["Practice", "Review"]` (the drizzle law:
214
+ * set membership is an array, never an operator). Arrays are CLOSED-ONLY
215
+ * in this packet by owner ruling: ordinary u64/str membership already has
216
+ * its spelling through `r.inSet` params; widening literal arrays to every
217
+ * literal-capable kind is a separate future taste call — deliberately not
218
+ * done here.
160
219
  */
161
220
  type BindingInput<F extends AnyField> =
162
221
  | Infer<F>
222
+ | (F extends ClosedIdField ? readonly Infer<F>[] : never)
163
223
  | (F extends { readonly kind: "interval" } ? bigint : never)
164
224
  | Var<string>
165
225
  | Param<string>
@@ -174,50 +234,61 @@ type MatchShape<F extends FieldsShape> = {
174
234
  readonly [K in keyof F]?: BindingInput<F[K]>
175
235
  }
176
236
 
237
+ /**
238
+ * One field position of a bindings record as a classed slot: the declared
239
+ * descriptor plus the slot's law-computed class, read off the relation's
240
+ * class record (`CR` — the schema class map's entry for the atom's
241
+ * relation). The one shape every join judgment compares.
242
+ */
243
+ type SlotAt<F extends FieldsShape, CR, K> = {
244
+ readonly field: F[K & keyof F]
245
+ readonly class: ClassLookup<CR, K>
246
+ }
247
+
177
248
  /**
178
249
  * The var binding's judgment against the incoming rule environment: a name
179
- * already bound must land on a domain-equal field.
250
+ * already bound must land on a class-equal slot (bare pairs only with bare).
180
251
  */
181
- type EnvJoinOk<Env extends EnvShape, F extends FieldsShape, K, N extends string> = N extends keyof Env
182
- ? JoinOk<Env[N], F[K & keyof F]>
252
+ type EnvJoinOk<Env extends EnvShape, F extends FieldsShape, CR, K, N extends string> = N extends keyof Env
253
+ ? JoinOk<Env[N], SlotAt<F, CR, K>>
183
254
  : true
184
255
 
185
256
  /**
186
257
  * The var binding's judgment against its OWN record's siblings: two
187
258
  * bindings of one var name inside a single bindings record are the same
188
259
  * join the environment check judges across atoms, so every same-named
189
- * sibling must be domain-equal too. Without this arm two FIRST occurrences
260
+ * sibling must be class-equal too. Without this arm two FIRST occurrences
190
261
  * of one name (a record the environment has not seen yet) would meet no
191
- * check at all — the intra-atom join would silently cross domains.
262
+ * check at all — the intra-atom join would silently cross classes.
192
263
  */
193
- type SiblingJoinOk<F extends FieldsShape, B, K extends keyof B, N extends string> = false extends {
194
- [K2 in Exclude<keyof B & keyof F, K>]: B[K2] extends Var<N> ? JoinOk<F[K2], F[K & keyof F]> : true
264
+ type SiblingJoinOk<F extends FieldsShape, CR, B, K extends keyof B, N extends string> = false extends {
265
+ [K2 in Exclude<keyof B & keyof F, K>]: B[K2] extends Var<N> ? JoinOk<SlotAt<F, CR, K2>, SlotAt<F, CR, K>> : true
195
266
  }[Exclude<keyof B & keyof F, K>]
196
267
  ? false
197
268
  : true
198
269
 
199
270
  /**
200
271
  * The per-property join judgment of a bindings record: a var binding must
201
- * be domain-equal to the rule environment's binding of the name AND to
202
- * every same-named sibling of its own record (a cross-domain reuse maps
272
+ * be class-equal to the rule environment's binding of the name AND to
273
+ * every same-named sibling of its own record (a cross-class reuse maps
203
274
  * the property to `never` — the compile error the old value brand carried,
204
- * now structural).
275
+ * now law-born off the schema type's class map).
205
276
  */
206
- type BindingOk<Env extends EnvShape, F extends FieldsShape, B, K extends keyof B> =
277
+ type BindingOk<Env extends EnvShape, F extends FieldsShape, CR, B, K extends keyof B> =
207
278
  B[K] extends Var<infer N extends string>
208
- ? [EnvJoinOk<Env, F, K, N>, SiblingJoinOk<F, B, K, N>] extends [true, true]
279
+ ? [EnvJoinOk<Env, F, CR, K, N>, SiblingJoinOk<F, CR, B, K, N>] extends [true, true]
209
280
  ? true
210
281
  : false
211
282
  : true
212
283
 
213
284
  /** The validated bindings record (intersect with the inferred `B` — errors land on the offending property). */
214
- type CheckBindings<Env extends EnvShape, F extends FieldsShape, B> = {
215
- readonly [K in keyof B]: K extends keyof F ? (BindingOk<Env, F, B, K> extends true ? B[K] : never) : never
285
+ type CheckBindings<Env extends EnvShape, F extends FieldsShape, CR, B> = {
286
+ readonly [K in keyof B]: K extends keyof F ? (BindingOk<Env, F, CR, B, K> extends true ? B[K] : never) : never
216
287
  }
217
288
 
218
- /** The environment a bindings record contributes: var name → the bound field's descriptor. */
219
- type BindEnv<F extends FieldsShape, B> = {
220
- readonly [K in keyof B & keyof F as B[K] extends Var<infer N extends string> ? N : never]: F[K]
289
+ /** The environment a bindings record contributes: var name → the bound slot (descriptor + class). */
290
+ type BindEnv<F extends FieldsShape, CR, B> = {
291
+ readonly [K in keyof B & keyof F as B[K] extends Var<infer N extends string> ? N : never]: SlotAt<F, CR, K>
221
292
  }
222
293
 
223
294
  /** The params-object fragments a bindings record contributes (one union member per param use). */
@@ -257,9 +328,9 @@ interface Tree<Ch extends readonly AnyTreeChild[]> {
257
328
  * membership at `.where` IS the safety rule, a compile error before it is
258
329
  * the engine's refusal.
259
330
  */
260
- interface NotAtom<F extends FieldsShape, B> {
331
+ interface NotAtom<R extends MatchOwner, B> {
261
332
  readonly cond: "not"
262
- readonly relation: Relation<string, F>
333
+ readonly relation: R
263
334
  readonly bindings: B
264
335
  }
265
336
 
@@ -270,7 +341,7 @@ type AnyCmp = Cmp<CmpKind, unknown, unknown, unknown>
270
341
  type AnyTreeChild = AnyCmp | Tree<readonly AnyTreeChild[]>
271
342
 
272
343
  /** Any negated-atom value. */
273
- type AnyNotAtom = NotAtom<FieldsShape, unknown>
344
+ type AnyNotAtom = NotAtom<MatchOwner, unknown>
274
345
 
275
346
  /** Any `.where` input: a comparison, a condition tree, or a negated atom. */
276
347
  type AnyCond = AnyCmp | Tree<readonly AnyTreeChild[]> | AnyNotAtom
@@ -464,26 +535,40 @@ function or<const C extends readonly AnyTreeChild[]>(...children: C): Tree<C> {
464
535
  * rejects every binding some matching fact extends. A negated atom binds
465
536
  * nothing, only rejects: every variable it names must be positively bound
466
537
  * in the rule, which `.where`'s environment check makes a COMPILE error
467
- * (the engine's safety refusal stands behind it).
538
+ * (the engine's safety refusal stands behind it). A CLOSED owner is legal
539
+ * here too — the engine folds a resolvable negated closed atom to the
540
+ * COMPLEMENT of its member set (domain-witness guarded), and the SDK's
541
+ * negation rules apply to it unchanged.
468
542
  */
469
- function not<Name extends string, F extends FieldsShape, const B extends MatchShape<F>>(
470
- relation: Relation<Name, F>,
543
+ function not<R extends MatchOwner, const B extends MatchShape<MatchFields<R>>>(
544
+ relation: R,
471
545
  bindings: B
472
- ): NotAtom<F, B> {
473
- const value: NotAtom<F, B> = { cond: "not", relation, bindings }
546
+ ): NotAtom<R, B> {
547
+ const value: NotAtom<R, B> = { cond: "not", relation, bindings }
474
548
  return Object.freeze(value)
475
549
  }
476
550
 
477
- /** Whether a var name is bound in the environment at an orderable (u64/i64) field. */
551
+ /**
552
+ * Whether a var name is bound in the environment at an orderable (u64/i64)
553
+ * field. A CLOSED reference is excluded even though its kind is `u64`: a
554
+ * vocabulary's declaration-id order is an accident, not semantics
555
+ * (`docs/architecture/10-data-model.md` § orderability — order on it is
556
+ * refused exactly as the enum's ordinal order was), so every
557
+ * order-comparison and fold position refuses closed-bound terms — this
558
+ * judgment is the one gate they all read, and the construction-time
559
+ * validations in `#query/lower.ts` are its runtime twin.
560
+ */
478
561
  type OrderVarOk<Env extends EnvShape, N extends string> = N extends keyof Env
479
- ? Env[N]["kind"] extends "u64" | "i64"
480
- ? true
481
- : false
562
+ ? Env[N]["field"] extends { readonly closed: ClosedRoster }
563
+ ? false
564
+ : Env[N]["field"]["kind"] extends "u64" | "i64"
565
+ ? true
566
+ : false
482
567
  : false
483
568
 
484
569
  /** Whether a var name is bound at an interval field. */
485
570
  type IntervalVarOk<Env extends EnvShape, N extends string> = N extends keyof Env
486
- ? Env[N]["kind"] extends "interval"
571
+ ? Env[N]["field"]["kind"] extends "interval"
487
572
  ? true
488
573
  : false
489
574
  : false
@@ -502,7 +587,7 @@ type PointSideOk<Env extends EnvShape, T> = T extends Var<infer N extends string
502
587
  /** One interval side's judgment. */
503
588
  type IntervalSideOk<Env extends EnvShape, T> = T extends Var<infer N extends string> ? IntervalVarOk<Env, N> : true
504
589
 
505
- /** The `eq`/`ne` judgment: left var bound; right joins it (domain-equal var, param, or an exact-type literal). */
590
+ /** The `eq`/`ne` judgment: left var bound; right joins it (class-equal var, param, or an exact-type literal). */
506
591
  type EqOk<Env extends EnvShape, L, R> =
507
592
  L extends Var<infer N extends string>
508
593
  ? N extends keyof Env
@@ -512,34 +597,36 @@ type EqOk<Env extends EnvShape, L, R> =
512
597
  : false
513
598
  : R extends Param<string> | SetParam<string>
514
599
  ? true
515
- : [R] extends [Infer<Env[N]>]
600
+ : [R] extends [Infer<Env[N]["field"]>]
516
601
  ? true
517
602
  : false
518
603
  : false
519
604
  : false
520
605
 
521
- /** One negated-atom binding's judgment: a var must be positively bound (safety) AND domain-equal. */
522
- type NotBindingOk<Env extends EnvShape, F extends AnyField, T> =
523
- T extends Var<infer N extends string> ? (N extends keyof Env ? JoinOk<Env[N], F> : false) : true
606
+ /** One negated-atom binding's judgment: a var must be positively bound (safety) AND class-equal. */
607
+ type NotBindingOk<Env extends EnvShape, S extends ClassedField, T> =
608
+ T extends Var<infer N extends string> ? (N extends keyof Env ? JoinOk<Env[N], S> : false) : true
524
609
 
525
- /** The whole negated atom's judgment. */
526
- type NotOk<Env extends EnvShape, F extends FieldsShape, B> = false extends {
527
- [K in keyof B]: NotBindingOk<Env, K extends keyof F ? F[K] : never, B[K]>
610
+ /** The whole negated atom's judgment (`CR` — the negated relation's class record off the schema class map). */
611
+ type NotOk<Env extends EnvShape, F extends FieldsShape, CR, B> = false extends {
612
+ [K in keyof B]: NotBindingOk<Env, SlotAt<F, CR, K>, B[K]>
528
613
  }[keyof B]
529
614
  ? false
530
615
  : true
531
616
 
532
617
  /**
533
618
  * One condition's judgment against the rule environment — the type-level
534
- * twin of the engine's comparison roster: domain-equal joins, orderable
535
- * order sides (an interval var under a non-`pointIn` op is exactly here
536
- * refused), kind-correct `pointIn`/`covers`/`allen` sides, and negated-atom
537
- * safety. The leading `[AnyTreeChild] extends [C]` arm is the recursion's
538
- * base case: at an UNRESOLVED constraint (the whole condition union — or a
539
- * tree's child union, which is the union itself) the judgment is vacuously
540
- * true without it the constraint instantiation recurses into itself.
619
+ * twin of the engine's comparison roster: class-equal joins (off the
620
+ * schema type's class map), orderable order sides (an interval var under a
621
+ * non-`pointIn` op is exactly here refused), kind-correct
622
+ * `pointIn`/`covers`/`allen` sides, and negated-atom safety (the negated
623
+ * relation's class record is resolved through `Classes` by its name). The
624
+ * leading `[AnyTreeChild] extends [C]` arm is the recursion's base case:
625
+ * at an UNRESOLVED constraint (the whole condition union or a tree's
626
+ * child union, which is the union itself) the judgment is vacuously true —
627
+ * without it the constraint instantiation recurses into itself.
541
628
  */
542
- type CondOkBool<Env extends EnvShape, C> = [AnyTreeChild] extends [C]
629
+ type CondOkBool<Env extends EnvShape, Classes extends SchemaClasses, C> = [AnyTreeChild] extends [C]
543
630
  ? true
544
631
  : C extends Cmp<infer Op, infer L, infer R, unknown>
545
632
  ? Op extends "eq" | "ne"
@@ -558,23 +645,24 @@ type CondOkBool<Env extends EnvShape, C> = [AnyTreeChild] extends [C]
558
645
  : false
559
646
  : false
560
647
  : C extends Tree<infer Ch extends readonly AnyTreeChild[]>
561
- ? false extends CondOkBool<Env, Ch[number]>
648
+ ? false extends CondOkBool<Env, Classes, Ch[number]>
562
649
  ? false
563
650
  : true
564
- : C extends NotAtom<infer F extends FieldsShape, infer B>
565
- ? NotOk<Env, F, B>
651
+ : C extends NotAtom<infer R extends MatchOwner, infer B>
652
+ ? NotOk<Env, MatchFields<R>, ClassRecordOf<Classes, R["name"]>, B>
566
653
  : false
567
654
 
568
655
  /** The validated `.where` argument (intersect with the inferred condition type). */
569
- type CheckCond<Env extends EnvShape, C> = CondOkBool<Env, C> extends true ? C : never
656
+ type CheckCond<Env extends EnvShape, Classes extends SchemaClasses, C> =
657
+ CondOkBool<Env, Classes, C> extends true ? C : never
570
658
 
571
659
  /** The `eq`/`ne` params contribution: the param typed by the left variable's field. */
572
660
  type EqParams<Env extends EnvShape, L, R> =
573
661
  L extends Var<infer N extends string>
574
662
  ? R extends Param<infer P extends string>
575
- ? { readonly [Q in P]: Infer<Env[N & keyof Env]> }
663
+ ? { readonly [Q in P]: Infer<Env[N & keyof Env]["field"]> }
576
664
  : R extends SetParam<infer P extends string>
577
- ? { readonly [Q in P]: readonly Infer<Env[N & keyof Env]>[] }
665
+ ? { readonly [Q in P]: readonly Infer<Env[N & keyof Env]["field"]>[] }
578
666
  : never
579
667
  : never
580
668
 
@@ -607,8 +695,8 @@ type CondParams<Env extends EnvShape, C> = [AnyTreeChild] extends [C]
607
695
  : never
608
696
  : C extends Tree<infer Ch extends readonly AnyTreeChild[]>
609
697
  ? CondParams<Env, Ch[number]>
610
- : C extends NotAtom<infer F extends FieldsShape, infer B>
611
- ? BindParams<F, B>
698
+ : C extends NotAtom<infer R extends MatchOwner, infer B>
699
+ ? BindParams<MatchFields<R>, B>
612
700
  : never
613
701
 
614
702
  /** The flattened params record one bindings record contributes. */
@@ -643,6 +731,8 @@ export type {
643
731
  IntervalSide,
644
732
  IntervalVarOk,
645
733
  MaskData,
734
+ MatchFields,
735
+ MatchOwner,
646
736
  MatchShape,
647
737
  NotAtom,
648
738
  OrderSide,