@bjornpagen/bumbledb 0.20.2 → 1.0.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 (192) hide show
  1. package/COOKBOOK.md +283 -1611
  2. package/README.md +156 -118
  3. package/dist/capacity.d.ts +16 -12
  4. package/dist/capacity.d.ts.map +1 -1
  5. package/dist/capacity.js +16 -48
  6. package/dist/capacity.js.map +1 -1
  7. package/dist/changes.d.ts +54 -0
  8. package/dist/changes.d.ts.map +1 -0
  9. package/dist/changes.js +245 -0
  10. package/dist/changes.js.map +1 -0
  11. package/dist/close.d.ts +24 -0
  12. package/dist/close.d.ts.map +1 -0
  13. package/dist/close.js +34 -0
  14. package/dist/close.js.map +1 -0
  15. package/dist/closed.d.ts +1 -3
  16. package/dist/closed.d.ts.map +1 -1
  17. package/dist/closed.js +24 -12
  18. package/dist/closed.js.map +1 -1
  19. package/dist/codec.d.ts +56 -0
  20. package/dist/codec.d.ts.map +1 -0
  21. package/dist/codec.js +289 -0
  22. package/dist/codec.js.map +1 -0
  23. package/dist/compile.d.ts +68 -0
  24. package/dist/compile.d.ts.map +1 -0
  25. package/dist/compile.js +75 -0
  26. package/dist/compile.js.map +1 -0
  27. package/dist/db-native.d.ts +170 -0
  28. package/dist/db-native.d.ts.map +1 -0
  29. package/dist/db-native.js +5 -0
  30. package/dist/db-native.js.map +1 -0
  31. package/dist/db.d.ts +101 -233
  32. package/dist/db.d.ts.map +1 -1
  33. package/dist/db.js +232 -1084
  34. package/dist/db.js.map +1 -1
  35. package/dist/errors.d.ts +57 -0
  36. package/dist/errors.d.ts.map +1 -0
  37. package/dist/errors.js +32 -0
  38. package/dist/errors.js.map +1 -0
  39. package/dist/face.d.ts.map +1 -1
  40. package/dist/face.js +2 -2
  41. package/dist/face.js.map +1 -1
  42. package/dist/fields.d.ts +65 -24
  43. package/dist/fields.d.ts.map +1 -1
  44. package/dist/fields.js +94 -24
  45. package/dist/fields.js.map +1 -1
  46. package/dist/index.d.ts +37 -32
  47. package/dist/index.d.ts.map +1 -1
  48. package/dist/index.js +17 -21
  49. package/dist/index.js.map +1 -1
  50. package/dist/internal/log.d.ts +24 -0
  51. package/dist/internal/log.d.ts.map +1 -0
  52. package/dist/internal/log.js +10 -0
  53. package/dist/internal/log.js.map +1 -0
  54. package/dist/law.d.ts +8 -13
  55. package/dist/law.d.ts.map +1 -1
  56. package/dist/law.js +6 -59
  57. package/dist/law.js.map +1 -1
  58. package/dist/lower.d.ts +0 -13
  59. package/dist/lower.d.ts.map +1 -1
  60. package/dist/lower.js +9 -17
  61. package/dist/lower.js.map +1 -1
  62. package/dist/migration.d.ts +33 -0
  63. package/dist/migration.d.ts.map +1 -0
  64. package/dist/migration.js +48 -0
  65. package/dist/migration.js.map +1 -0
  66. package/dist/native.d.ts +256 -301
  67. package/dist/native.d.ts.map +1 -1
  68. package/dist/native.js +65 -108
  69. package/dist/native.js.map +1 -1
  70. package/dist/query/atom.d.ts +24 -13
  71. package/dist/query/atom.d.ts.map +1 -1
  72. package/dist/query/atom.js +7 -3
  73. package/dist/query/atom.js.map +1 -1
  74. package/dist/query/compute.d.ts +68 -0
  75. package/dist/query/compute.d.ts.map +1 -0
  76. package/dist/query/compute.js +141 -0
  77. package/dist/query/compute.js.map +1 -0
  78. package/dist/query/find.d.ts +15 -8
  79. package/dist/query/find.d.ts.map +1 -1
  80. package/dist/query/find.js +6 -2
  81. package/dist/query/find.js.map +1 -1
  82. package/dist/query/lower.d.ts +23 -2
  83. package/dist/query/lower.d.ts.map +1 -1
  84. package/dist/query/lower.js +433 -80
  85. package/dist/query/lower.js.map +1 -1
  86. package/dist/query/parse-ir.d.ts.map +1 -1
  87. package/dist/query/parse-ir.js +17 -8
  88. package/dist/query/parse-ir.js.map +1 -1
  89. package/dist/query/run.d.ts +12 -2
  90. package/dist/query/run.d.ts.map +1 -1
  91. package/dist/query/run.js +26 -9
  92. package/dist/query/run.js.map +1 -1
  93. package/dist/query/scope.d.ts +47 -3
  94. package/dist/query/scope.d.ts.map +1 -1
  95. package/dist/query/scope.js +83 -19
  96. package/dist/query/scope.js.map +1 -1
  97. package/dist/relation.d.ts +3 -8
  98. package/dist/relation.d.ts.map +1 -1
  99. package/dist/relation.js +16 -10
  100. package/dist/relation.js.map +1 -1
  101. package/dist/result.d.ts +47 -0
  102. package/dist/result.d.ts.map +1 -0
  103. package/dist/result.js +50 -0
  104. package/dist/result.js.map +1 -0
  105. package/dist/rows.d.ts +69 -0
  106. package/dist/rows.d.ts.map +1 -0
  107. package/dist/rows.js +328 -0
  108. package/dist/rows.js.map +1 -0
  109. package/dist/runtime-codes.d.ts +2 -0
  110. package/dist/runtime-codes.d.ts.map +1 -0
  111. package/dist/runtime-codes.js +19 -0
  112. package/dist/runtime-codes.js.map +1 -0
  113. package/dist/runtime-errors.d.ts +106 -0
  114. package/dist/runtime-errors.d.ts.map +1 -0
  115. package/dist/runtime-errors.js +62 -0
  116. package/dist/runtime-errors.js.map +1 -0
  117. package/dist/runtime-native.d.ts +171 -0
  118. package/dist/runtime-native.d.ts.map +1 -0
  119. package/dist/runtime-native.js +5 -0
  120. package/dist/runtime-native.js.map +1 -0
  121. package/dist/runtime.d.ts +73 -0
  122. package/dist/runtime.d.ts.map +1 -0
  123. package/dist/runtime.js +293 -0
  124. package/dist/runtime.js.map +1 -0
  125. package/dist/scalar.d.ts +165 -0
  126. package/dist/scalar.d.ts.map +1 -0
  127. package/dist/scalar.js +344 -0
  128. package/dist/scalar.js.map +1 -0
  129. package/dist/schema.d.ts +1 -1
  130. package/dist/schema.d.ts.map +1 -1
  131. package/dist/schema.js +34 -37
  132. package/dist/schema.js.map +1 -1
  133. package/dist/shape.d.ts +28 -0
  134. package/dist/shape.d.ts.map +1 -0
  135. package/dist/shape.js +2 -0
  136. package/dist/shape.js.map +1 -0
  137. package/dist/spec.d.ts +27 -3
  138. package/dist/spec.d.ts.map +1 -1
  139. package/dist/spec.js +22 -1
  140. package/dist/spec.js.map +1 -1
  141. package/dist/statements.d.ts +16 -3
  142. package/dist/statements.d.ts.map +1 -1
  143. package/dist/statements.js +54 -28
  144. package/dist/statements.js.map +1 -1
  145. package/dist/uuid.d.ts +23 -0
  146. package/dist/uuid.d.ts.map +1 -0
  147. package/dist/uuid.js +54 -0
  148. package/dist/uuid.js.map +1 -0
  149. package/pack-provenance.json +7 -0
  150. package/package.json +12 -16
  151. package/src/capacity.ts +34 -54
  152. package/src/changes.ts +361 -0
  153. package/src/close.ts +53 -0
  154. package/src/closed.ts +25 -19
  155. package/src/codec.ts +379 -0
  156. package/src/compile.ts +149 -0
  157. package/src/db-native.ts +247 -0
  158. package/src/db.ts +461 -1632
  159. package/src/errors.ts +50 -0
  160. package/src/face.ts +2 -2
  161. package/src/fields.ts +182 -55
  162. package/src/index.ts +62 -126
  163. package/src/internal/log.ts +51 -0
  164. package/src/law.ts +13 -28
  165. package/src/lower.ts +9 -4
  166. package/src/migration.ts +69 -0
  167. package/src/native.ts +326 -487
  168. package/src/query/atom.ts +61 -25
  169. package/src/query/compute.ts +237 -0
  170. package/src/query/find.ts +31 -12
  171. package/src/query/lower.ts +531 -143
  172. package/src/query/parse-ir.ts +18 -13
  173. package/src/query/run.ts +32 -16
  174. package/src/query/scope.ts +140 -8
  175. package/src/relation.ts +17 -21
  176. package/src/result.ts +130 -0
  177. package/src/rows.ts +404 -0
  178. package/src/runtime-codes.ts +18 -0
  179. package/src/runtime-errors.ts +69 -0
  180. package/src/runtime-native.ts +242 -0
  181. package/src/runtime.ts +440 -0
  182. package/src/scalar.ts +560 -0
  183. package/src/schema.ts +35 -37
  184. package/src/shape.ts +31 -0
  185. package/src/spec.ts +33 -3
  186. package/src/statements.ts +75 -68
  187. package/src/uuid.ts +69 -0
  188. package/dist/marshal.d.ts +0 -44
  189. package/dist/marshal.d.ts.map +0 -1
  190. package/dist/marshal.js +0 -165
  191. package/dist/marshal.js.map +0 -1
  192. package/src/marshal.ts +0 -200
@@ -1,10 +1,13 @@
1
- import * as errors from "@superbuilders/errors";
2
1
  import { sealedFieldsOf } from "#closed.ts";
3
- import { assertDeclarationOrderKey, isIntervalValue, literalShapeError, rosterOf } from "#fields.ts";
2
+ import { AuthoringError, SdkInvariantError } from "#errors.ts";
3
+ import { assertDeclarationOrderKey, f64 as f64Field, isFloatIntervalValue, isIntervalValue, literalShapeError, rosterOf, u64 as u64Field } from "#fields.ts";
4
4
  import { allen, and, eq, ge, gt, le, lt, ne, not, or, pointIn } from "#query/atom.ts";
5
- import { count, max, min, pack, sum } from "#query/find.ts";
5
+ import { computeFieldOf, computeVarsOf, isComputeExpr, MAX_COMPUTE_DEPTH } from "#query/compute.ts";
6
+ import { count, max, mean, min, pack, sum } from "#query/find.ts";
6
7
  import { parseQueryIr } from "#query/parse-ir.ts";
7
- import { fieldAntiJoins, fieldJoins, headFieldJoins, inferred, isTerm, makeParam, makeSetParam, renderFieldKind, term } from "#query/scope.ts";
8
+ import { fieldAntiJoins, fieldJoins, headFieldJoins, importFacadeOf, importFacadeOfOwner, inferred, isImportedSource, isTerm, labelImport, makeParam, makeSetParam, renderFieldKind, term } from "#query/scope.ts";
9
+ import { literalWireOf } from "#scalar.ts";
10
+ import { Uuid } from "#uuid.ts";
8
11
  const termOps = Object.freeze({
9
12
  param: makeParam,
10
13
  inSet: makeSetParam,
@@ -21,6 +24,7 @@ const termOps = Object.freeze({
21
24
  not,
22
25
  count,
23
26
  sum,
27
+ mean,
24
28
  min,
25
29
  max,
26
30
  pack
@@ -40,21 +44,42 @@ const EMPTY_RULE = Object.freeze({
40
44
  * cross-binding joins mutually class-equal by transitivity.
41
45
  */
42
46
  function mintSlotOf(context, ref) {
47
+ const facade = importFacadeOfOwner(ref.owner);
48
+ if (facade !== undefined) {
49
+ // An import var's mint slot is the imported head column's slot:
50
+ // the descriptor it projected and the carrier class that survived
51
+ // the projection (aggregate outputs are bare derived scalars).
52
+ const column = facade.columns.get(ref.column);
53
+ if (column === undefined || facade.source.schema !== context.theory) {
54
+ throw new AuthoringError({
55
+ message: `the variable ${ref.label} was minted from a query template schema ${context.theory.name} does not own`
56
+ });
57
+ }
58
+ return column.slot ?? { field: ref.field, class: undefined };
59
+ }
43
60
  if (context.theory.relations[ref.owner.name] !== ref.owner) {
44
- throw errors.new(`the variable ${ref.label} was minted from a relation schema ${context.theory.name} does not declare — mint variables with v() from the schema's own relations`);
61
+ throw new AuthoringError({
62
+ message: `the variable ${ref.label} was minted from a relation schema ${context.theory.name} does not declare — mint variables with v() from the schema's own relations`
63
+ });
45
64
  }
46
65
  return { field: ref.field, class: context.classes[ref.owner.name]?.[ref.column] };
47
66
  }
48
67
  function membershipSet(context, field, value) {
49
68
  const roster = rosterOf(field);
50
69
  if (roster === undefined) {
51
- throw errors.new(`${context}: a membership array is the closed-reference spelling — ordinary field membership is a bound ∈-set param (r.inSet)`);
70
+ throw new AuthoringError({
71
+ message: `${context}: a membership array is the closed-reference spelling — ordinary field membership is a bound ∈-set param (r.inSet)`
72
+ });
52
73
  }
53
74
  if (value.length === 0) {
54
- throw errors.new(`${context}: an empty membership array selects nothing — write the query you mean`);
75
+ throw new AuthoringError({
76
+ message: `${context}: an empty membership array selects nothing — write the query you mean`
77
+ });
55
78
  }
56
79
  if (value.length === 1) {
57
- throw errors.new(`${context}: a one-element membership array is the bare literal respelled — write the literal (the canonical-utterance law: one meaning, one spelling)`);
80
+ throw new AuthoringError({
81
+ message: `${context}: a one-element membership array is the bare literal respelled — write the literal (the canonical-utterance law: one meaning, one spelling)`
82
+ });
58
83
  }
59
84
  const seen = new Set();
60
85
  const members = value.map(function memberName(member) {
@@ -62,7 +87,9 @@ function membershipSet(context, field, value) {
62
87
  throw literalShapeError(context, `a ${roster.name} handle name (string)`, member);
63
88
  }
64
89
  if (seen.has(member)) {
65
- throw errors.new(`${context}: the membership array spells ${member} twice — write it once (the canonical-utterance law: one meaning, one spelling)`);
90
+ throw new AuthoringError({
91
+ message: `${context}: the membership array spells ${member} twice — write it once (the canonical-utterance law: one meaning, one spelling)`
92
+ });
66
93
  }
67
94
  seen.add(member);
68
95
  return member;
@@ -84,7 +111,7 @@ function resolveBindings(context, label, relation, bindings, joins = fieldJoins)
84
111
  return candidate.name === fieldName;
85
112
  });
86
113
  if (declared === undefined) {
87
- throw errors.new(`${label} has no field ${fieldName}`);
114
+ throw new AuthoringError({ message: `${label} has no field ${fieldName}` });
88
115
  }
89
116
  const fieldClass = relationClasses?.[fieldName];
90
117
  let bound;
@@ -95,7 +122,9 @@ function resolveBindings(context, label, relation, bindings, joins = fieldJoins)
95
122
  const mint = mintSlotOf(context, ref);
96
123
  const positionSlot = { field: declared.field, class: fieldClass };
97
124
  if (!joins(mint, positionSlot)) {
98
- throw errors.new(`${label}: the variable ${ref.label} joins domain-unequal fields — minted at ${renderFieldKind(mint)}, reused at ${renderFieldKind(positionSlot)} (a var joins only class-equal slots; bare pairs only with bare)`);
125
+ throw new AuthoringError({
126
+ message: `${label}: the variable ${ref.label} joins domain-unequal fields — minted at ${renderFieldKind(mint)}, reused at ${renderFieldKind(positionSlot)} (a var joins only class-equal slots; bare pairs only with bare)`
127
+ });
99
128
  }
100
129
  bound = Object.freeze({ kind: "var", ref });
101
130
  vars.push(ref);
@@ -190,7 +219,9 @@ function condDataOf(cond, uses) {
190
219
  if (cond.op === "allen") {
191
220
  const maskValue = cond.mask;
192
221
  if (typeof maskValue !== "number") {
193
- throw errors.new("allen: the mask position takes a 13-bit mask number built from the ALLEN constants");
222
+ throw new AuthoringError({
223
+ message: "allen: the mask position takes a 13-bit mask number built from the ALLEN constants"
224
+ });
194
225
  }
195
226
  return Object.freeze({
196
227
  kind: "cmp",
@@ -207,11 +238,13 @@ function condDataOf(cond, uses) {
207
238
  });
208
239
  return Object.freeze({ kind: "tree", op: cond.op, children: Object.freeze(children) });
209
240
  }
210
- throw errors.new("a negated atom is not a condition-tree node — pass not(...) to where() directly, never inside and()/or()");
241
+ throw new AuthoringError({
242
+ message: "a negated atom is not a condition-tree node — pass not(...) to where() directly, never inside and()/or()"
243
+ });
211
244
  }
212
245
  function advanceWhere(context, state, cond) {
213
246
  if (typeof cond !== "object" || cond === null || !("cond" in cond)) {
214
- throw errors.new("where() takes a comparison, an and()/or() tree, or a negated atom");
247
+ throw new AuthoringError({ message: "where() takes a comparison, an and()/or() tree, or a negated atom" });
215
248
  }
216
249
  if (cond.cond === "notInterior") {
217
250
  const bindings = Object.fromEntries(Object.entries(cond.bindings ?? {}).filter(function defined([, value]) {
@@ -246,7 +279,9 @@ function advanceInterior(state, target, bindings, kind) {
246
279
  continue;
247
280
  }
248
281
  if (!isTerm(value) || value[term] !== "var") {
249
- throw errors.new(`interior ${target.name}: position ${key} takes a variable — bind literals and params through where()/match()`);
282
+ throw new AuthoringError({
283
+ message: `interior ${target.name}: position ${key} takes a variable — bind literals and params through where()/match()`
284
+ });
250
285
  }
251
286
  resolved.push(Object.freeze({ key, ref: value }));
252
287
  }
@@ -270,7 +305,7 @@ function asVarTerm(context, value) {
270
305
  if (isTerm(value) && value[term] === "var") {
271
306
  return value;
272
307
  }
273
- throw errors.new(`${context}: expected a variable`);
308
+ throw new AuthoringError({ message: `${context}: expected a variable` });
274
309
  }
275
310
  function aggDataOf(name, entry) {
276
311
  if (entry.agg === "count") {
@@ -279,17 +314,18 @@ function aggDataOf(name, entry) {
279
314
  const over = entry.over;
280
315
  switch (entry.agg) {
281
316
  case "sum":
317
+ case "mean":
282
318
  case "min":
283
319
  case "max": {
284
320
  if (isTerm(over) && over[term] === "var") {
285
321
  return Object.freeze({ op: "fold", fold: entry.agg, over });
286
322
  }
287
- throw errors.new(`find ${name} (${entry.agg}): takes a variable`);
323
+ throw new AuthoringError({ message: `find ${name} (${entry.agg}): takes a variable` });
288
324
  }
289
325
  case "pack":
290
326
  return Object.freeze({ op: "pack", over: asVarTerm(`find ${name} (pack)`, over) });
291
327
  default:
292
- throw errors.new(`find ${name}: unknown aggregate ${entry.agg}`);
328
+ throw new AuthoringError({ message: `find ${name}: unknown aggregate ${entry.agg}` });
293
329
  }
294
330
  }
295
331
  function findColumnOf(name, entry) {
@@ -302,7 +338,17 @@ function findColumnOf(name, entry) {
302
338
  slot: undefined
303
339
  });
304
340
  }
305
- throw errors.new(`find ${name}: a ${entry[term]} is not projectable — find takes variables or aggregates`);
341
+ throw new AuthoringError({
342
+ message: `find ${name}: a ${entry[term]} is not projectable — find takes variables, aggregates or Compute expressions`
343
+ });
344
+ }
345
+ if (isComputeExpr(entry)) {
346
+ return Object.freeze({
347
+ name,
348
+ entry: Object.freeze({ kind: "compute", expr: entry, result: entry.result }),
349
+ closed: undefined,
350
+ slot: undefined
351
+ });
306
352
  }
307
353
  if (isAggregateEntry(entry)) {
308
354
  return Object.freeze({
@@ -312,26 +358,34 @@ function findColumnOf(name, entry) {
312
358
  slot: undefined
313
359
  });
314
360
  }
315
- throw errors.new(`find ${name}: not a find entry — find takes variables or aggregates`);
361
+ throw new AuthoringError({
362
+ message: `find ${name}: not a find entry — find takes variables, aggregates or Compute expressions`
363
+ });
316
364
  }
317
365
  /**
318
366
  * The orderable ban's pointed refusal
319
367
  * § orderability): a closed reference is equality-and-membership only.
320
368
  */
321
369
  function closedOrderError(context, position, vocabulary) {
322
- return errors.new(`${context}: ${position} is a ${vocabulary} reference — declaration order is an accident, not semantics: vocabularies do not order (equality, membership, and counting remain)`);
370
+ return new AuthoringError({
371
+ message: `${context}: ${position} is a ${vocabulary} reference — declaration order is an accident, not semantics: vocabularies do not order (equality, membership, and counting remain)`
372
+ });
323
373
  }
324
374
  function isOrderOp(op) {
325
375
  return op === "lt" || op === "le" || op === "gt" || op === "ge" || op === "pointIn";
326
376
  }
327
377
  function assertBound(where, bound, ref) {
328
378
  if (!bound.has(ref)) {
329
- throw errors.new(`${where}: the variable ${ref.label} is not bound by a relation atom of the rule`);
379
+ throw new AuthoringError({
380
+ message: `${where}: the variable ${ref.label} is not bound by a relation atom of the rule`
381
+ });
330
382
  }
331
383
  }
332
384
  function assertInterval(where, ref) {
333
385
  if (ref.field.kind !== "interval") {
334
- throw errors.new(`${where}: ${ref.label} is not interval-typed — the measure is defined over interval-typed variables only`);
386
+ throw new AuthoringError({
387
+ message: `${where}: ${ref.label} is not interval-typed — the measure is defined over interval-typed variables only`
388
+ });
335
389
  }
336
390
  }
337
391
  function assertNotClosed(where, position, ref) {
@@ -341,16 +395,40 @@ function assertNotClosed(where, position, ref) {
341
395
  }
342
396
  }
343
397
  function assertNumeric(where, position, ref) {
344
- if (ref.field.kind !== "u64" && ref.field.kind !== "i64") {
345
- throw errors.new(`${where}: ${position} ${ref.label} is ${ref.field.kind}, not numeric — a fold reads u64/i64 only`);
398
+ if (ref.field.kind !== "u64" && ref.field.kind !== "i64" && ref.field.kind !== "f64") {
399
+ throw new AuthoringError({
400
+ message: `${where}: ${position} ${ref.label} is ${ref.field.kind}, not numeric — a fold reads u64/i64/f64 only`
401
+ });
346
402
  }
347
403
  }
404
+ /**
405
+ * The head column's slot: a projected variable keeps its mint slot; an
406
+ * aggregate output is a NEW derived scalar — its descriptor follows the
407
+ * aggregate's typing (count is u64, mean is f64, sum/min/max keep the
408
+ * input's scalar kind, pack keeps the interval kind) and its class is
409
+ * BARE: a derived value is not the carrier the class law tracked.
410
+ */
348
411
  function findColumnSlotOf(context, column) {
349
412
  const entry = column.entry;
350
413
  if (entry.kind === "var") {
351
414
  return mintSlotOf(context, entry.over);
352
415
  }
353
- return undefined;
416
+ if (entry.kind === "compute") {
417
+ return { field: computeFieldOf(entry.result), class: undefined };
418
+ }
419
+ const agg = entry.agg;
420
+ switch (agg.op) {
421
+ case "count":
422
+ return { field: u64Field, class: undefined };
423
+ case "fold": {
424
+ if (agg.fold === "mean") {
425
+ return { field: f64Field, class: undefined };
426
+ }
427
+ return { field: agg.over.field, class: undefined };
428
+ }
429
+ case "pack":
430
+ return { field: agg.over.field, class: undefined };
431
+ }
354
432
  }
355
433
  function validateColumn(context, bound, column) {
356
434
  const where = `${contextLabel(context)} find ${column.name}`;
@@ -359,6 +437,15 @@ function validateColumn(context, bound, column) {
359
437
  assertBound(where, bound, entry.over);
360
438
  return;
361
439
  }
440
+ if (entry.kind === "compute") {
441
+ // Kind agreement, closed-reference and depth walls hold at
442
+ // construction (#query/compute.ts); boundness is a RULE property,
443
+ // judged here like every other term position.
444
+ for (const ref of computeVarsOf(entry.expr)) {
445
+ assertBound(where, bound, ref);
446
+ }
447
+ return;
448
+ }
362
449
  const agg = entry.agg;
363
450
  switch (agg.op) {
364
451
  case "count":
@@ -367,6 +454,9 @@ function validateColumn(context, bound, column) {
367
454
  assertBound(where, bound, agg.over);
368
455
  assertNotClosed(where, `the ${agg.fold} input`, agg.over);
369
456
  assertNumeric(where, `the ${agg.fold} input`, agg.over);
457
+ if (agg.fold === "mean" && agg.over.field.kind !== "f64") {
458
+ throw new AuthoringError({ message: `${where}: mean requires an f64 input; cast integers explicitly` });
459
+ }
370
460
  return;
371
461
  }
372
462
  case "pack":
@@ -393,7 +483,9 @@ function validateCond(context, bound, cond) {
393
483
  const lhs = mintSlotOf(context, cond.lhs.ref);
394
484
  const rhs = mintSlotOf(context, cond.rhs.ref);
395
485
  if (!fieldJoins(lhs, rhs)) {
396
- throw errors.new(`${label}: ${cond.op.kind}(${cond.lhs.ref.label}, ${cond.rhs.ref.label}) unifies domain-unequal fields — ${cond.lhs.ref.label} bound at ${renderFieldKind(lhs)}, ${cond.rhs.ref.label} at ${renderFieldKind(rhs)} (a var joins only class-equal slots; bare pairs only with bare)`);
486
+ throw new AuthoringError({
487
+ message: `${label}: ${cond.op.kind}(${cond.lhs.ref.label}, ${cond.rhs.ref.label}) unifies domain-unequal fields — ${cond.lhs.ref.label} bound at ${renderFieldKind(lhs)}, ${cond.rhs.ref.label} at ${renderFieldKind(rhs)} (a var joins only class-equal slots; bare pairs only with bare)`
488
+ });
397
489
  }
398
490
  }
399
491
  return;
@@ -413,17 +505,23 @@ function validateInterior(context, bound, item, columns) {
413
505
  });
414
506
  for (const key of keys) {
415
507
  if (!headNames.includes(key)) {
416
- throw errors.new(`${label}: interior ${item.target.name} binds ${key}, not a head column of ${item.target.name} (head columns: ${headNames.join(", ")})`);
508
+ throw new AuthoringError({
509
+ message: `${label}: interior ${item.target.name} binds ${key}, not a head column of ${item.target.name} (head columns: ${headNames.join(", ")})`
510
+ });
417
511
  }
418
512
  }
419
513
  for (const name of headNames) {
420
514
  if (!keys.includes(name)) {
421
- throw errors.new(`${label}: interior ${item.target.name} omits the head column ${name} — an interior join binds every head column of ${item.target.name}`);
515
+ throw new AuthoringError({
516
+ message: `${label}: interior ${item.target.name} omits the head column ${name} — an interior join binds every head column of ${item.target.name}`
517
+ });
422
518
  }
423
519
  }
424
520
  for (const binding of item.bindings) {
425
521
  if (item.kind === "negatedInterior" && !bound.has(binding.ref)) {
426
- throw errors.new(`${label}: negated interior ${item.target.name} names the variable ${binding.ref.label}, but no positive atom of the rule binds it — a negated atom binds nothing, only rejects (the safety rule)`);
522
+ throw new AuthoringError({
523
+ message: `${label}: negated interior ${item.target.name} names the variable ${binding.ref.label}, but no positive atom of the rule binds it — a negated atom binds nothing, only rejects (the safety rule)`
524
+ });
427
525
  }
428
526
  const headColumn = headColumns.find(function byName(column) {
429
527
  return column.name === binding.key;
@@ -433,14 +531,16 @@ function validateInterior(context, bound, item, columns) {
433
531
  }
434
532
  const mint = mintSlotOf(context, binding.ref);
435
533
  if (!fieldJoins(headColumn.slot, mint)) {
436
- throw errors.new(`${label}: interior ${item.target.name} joins the variable ${binding.ref.label} (${renderFieldKind(mint)}) at head column ${binding.key} (${renderFieldKind(headColumn.slot)}) — a var joins only class-equal slots; bare pairs only with bare`);
534
+ throw new AuthoringError({
535
+ message: `${label}: interior ${item.target.name} joins the variable ${binding.ref.label} (${renderFieldKind(mint)}) at head column ${binding.key} (${renderFieldKind(headColumn.slot)}) — a var joins only class-equal slots; bare pairs only with bare`
536
+ });
437
537
  }
438
538
  }
439
539
  }
440
540
  function completeRule(context, state, rawColumns) {
441
541
  const label = contextLabel(context);
442
542
  if (rawColumns.length === 0) {
443
- throw errors.new(`${label}: a find needs at least one entry`);
543
+ throw new AuthoringError({ message: `${label}: a find needs at least one entry` });
444
544
  }
445
545
  const columns = rawColumns.map(function enrichColumn(column) {
446
546
  assertDeclarationOrderKey(`${label} find column`, column.name);
@@ -452,7 +552,9 @@ function completeRule(context, state, rawColumns) {
452
552
  if (item.kind === "negated") {
453
553
  for (const binding of item.atom.bindings) {
454
554
  if (binding.term.kind === "var" && !state.bound.has(binding.term.ref)) {
455
- throw errors.new(`${label}: negated ${item.atom.relation.name} atom binds the variable ${binding.term.ref.label} at position ${binding.field}, but no positive atom of the rule binds it — a negated atom binds nothing, only rejects (the safety rule)`);
555
+ throw new AuthoringError({
556
+ message: `${label}: negated ${item.atom.relation.name} atom binds the variable ${binding.term.ref.label} at position ${binding.field}, but no positive atom of the rule binds it — a negated atom binds nothing, only rejects (the safety rule)`
557
+ });
456
558
  }
457
559
  }
458
560
  }
@@ -489,44 +591,177 @@ function lookupDerived(context, name) {
489
591
  });
490
592
  if (interior !== undefined) {
491
593
  if (context.kind === "interior" && name === context.self) {
492
- throw errors.new(`interior ${name}: an interior does not read itself — declaration order is topological (a self-read is InteriorNotPrior)`);
594
+ throw new AuthoringError({
595
+ message: `interior ${name}: an interior does not read itself — declaration order is topological (a self-read is InteriorNotPrior)`
596
+ });
493
597
  }
494
598
  return interior;
495
599
  }
496
600
  const rec = "rec" in context ? context.rec : undefined;
497
601
  if (rec !== undefined && rec.name === name) {
498
602
  if (context.kind === "interior") {
499
- throw errors.new(`interior ${context.self}: interiors cannot read the rec — this cut's interiors are a prefix`);
603
+ throw new AuthoringError({
604
+ message: `interior ${context.self}: interiors cannot read the rec — this cut's interiors are a prefix`
605
+ });
500
606
  }
501
607
  if (context.kind === "rec-base") {
502
- throw errors.new(`rec ${rec.name}: a base arm does not read the rec — self-atoms belong on rec arms`);
608
+ throw new AuthoringError({
609
+ message: `rec ${rec.name}: a base arm does not read the rec — self-atoms belong on rec arms`
610
+ });
503
611
  }
504
612
  if (!isRecHead(rec)) {
505
- throw errors.new(`rec ${rec.name}: rec arms resolve the rec head after base arms seal it`);
613
+ throw new AuthoringError({ message: `rec ${rec.name}: rec arms resolve the rec head after base arms seal it` });
506
614
  }
507
615
  return rec;
508
616
  }
509
- throw errors.new(`${contextLabel(context)}: no derived table named ${name} is in scope`);
617
+ throw new AuthoringError({ message: `${contextLabel(context)}: no derived table named ${name} is in scope` });
510
618
  }
511
619
  function interiorAdvance(context, state, name, bindings) {
512
620
  return advanceInterior(state, lookupDerived(context, name), bindings, "interior");
513
621
  }
622
+ const importEntries = new WeakMap();
623
+ const importTables = new WeakSet();
624
+ function importEntryOf(context, imported) {
625
+ const cached = importEntries.get(imported);
626
+ if (cached !== undefined) {
627
+ if (imported.schema !== context.theory) {
628
+ throw new AuthoringError({
629
+ message: `${contextLabel(context)}: the imported query belongs to schema ${imported.schema.name}, not ${context.theory.name} — templates compose within one schema`
630
+ });
631
+ }
632
+ return cached;
633
+ }
634
+ if (imported.schema !== context.theory) {
635
+ throw new AuthoringError({
636
+ message: `${contextLabel(context)}: the imported query belongs to schema ${imported.schema.name}, not ${context.theory.name} — templates compose within one schema`
637
+ });
638
+ }
639
+ if (imported.data.kind === "reach") {
640
+ throw new AuthoringError({
641
+ message: `${contextLabel(context)}: a recursive query is not importable as a relation expression yet — declare the recursion on the consuming query (C05 boundary, recorded)`
642
+ });
643
+ }
644
+ const userParams = imported.data.params.filter(function userSupplied(entry) {
645
+ return entry.membership === undefined;
646
+ });
647
+ if (userParams.length > 0) {
648
+ throw new AuthoringError({
649
+ message: `${contextLabel(context)}: the imported query takes parameters (${userParams
650
+ .map((entry) => entry.name)
651
+ .join(", ")}) — bind an import's meaning at declaration; parameterized imports are not supported`
652
+ });
653
+ }
654
+ const facade = importFacadeOf(imported);
655
+ const table = Object.freeze({
656
+ name: facade.owner.name,
657
+ finds: imported.data.finds,
658
+ rules: imported.data.rules
659
+ });
660
+ const entry = Object.freeze({ table, inner: imported.data.interiors });
661
+ importEntries.set(imported, entry);
662
+ importTables.add(table);
663
+ importInner.set(table, entry.inner);
664
+ return entry;
665
+ }
666
+ const importInner = new WeakMap();
667
+ /**
668
+ * Every import table the assembled query references, in first-reference
669
+ * order (declared interiors first, then rec arms, then main rules — the
670
+ * lowering walk), deduplicated by table identity.
671
+ */
672
+ function referencedImports(interiors, rec, rules) {
673
+ const seen = new Set();
674
+ const out = [];
675
+ function walk(ruleList) {
676
+ for (const rule of ruleList) {
677
+ for (const item of rule.items) {
678
+ if (item.kind !== "interior" && item.kind !== "negatedInterior") {
679
+ continue;
680
+ }
681
+ const target = item.target;
682
+ if (importTables.has(target) && !seen.has(target)) {
683
+ seen.add(target);
684
+ out.push(target);
685
+ }
686
+ }
687
+ }
688
+ }
689
+ for (const interior of interiors) {
690
+ walk(interior.rules);
691
+ }
692
+ if (rec !== undefined) {
693
+ walk(rec.base);
694
+ walk(rec.rec);
695
+ }
696
+ walk(rules);
697
+ return out;
698
+ }
699
+ /**
700
+ * Splices referenced imports ahead of the declared interiors: each import's
701
+ * inner interiors first (its rules read them), then the import's own table.
702
+ * Interior prior-ness is preserved — declared interiors never read imports
703
+ * they could not have named, and main/rec rules may read anything earlier.
704
+ * Duplicate names across the final roster refuse.
705
+ */
706
+ function spliceImports(interiors, rec, rules) {
707
+ const imports = referencedImports(interiors, rec, rules);
708
+ if (imports.length === 0) {
709
+ return interiors;
710
+ }
711
+ const spliced = [];
712
+ const names = new Set();
713
+ const added = new Set();
714
+ function add(table) {
715
+ if (added.has(table)) {
716
+ return;
717
+ }
718
+ if (names.has(table.name)) {
719
+ throw new AuthoringError({
720
+ message: `query: the derived-table name ${table.name} appears twice after import splicing — rename the colliding interior (names are unique)`
721
+ });
722
+ }
723
+ added.add(table);
724
+ names.add(table.name);
725
+ spliced.push(table);
726
+ }
727
+ for (const table of imports) {
728
+ for (const inner of importInner.get(table) ?? []) {
729
+ add(inner);
730
+ }
731
+ add(table);
732
+ }
733
+ for (const declared of interiors) {
734
+ add(declared);
735
+ }
736
+ return Object.freeze(spliced);
737
+ }
738
+ function importAdvance(context, state, imported, bindings) {
739
+ const entry = importEntryOf(context, imported);
740
+ return advanceInterior(state, entry.table, bindings, "interior");
741
+ }
514
742
  function notInteriorAdvance(context, state, name, bindings) {
515
743
  if (context.kind === "rec-base" || context.kind === "rec-arm") {
516
- throw errors.new(`rec ${context.self.name}: a rec rule negates no table — self-negation is negation through the cycle (a finished set is what keeps the operator monotone), and a finished table's fold belongs in the main rules`);
744
+ throw new AuthoringError({
745
+ message: `rec ${context.self.name}: a rec rule negates no table — self-negation is negation through the cycle (a finished set is what keeps the operator monotone), and a finished table's fold belongs in the main rules`
746
+ });
517
747
  }
518
748
  return advanceInterior(state, lookupDerived(context, name), bindings, "negatedInterior");
519
749
  }
520
750
  function findColumns(context, entries) {
521
751
  const columns = [];
522
- const derivedHead = context.kind !== "query";
752
+ // Nonrecursive derived stages (interiors) may emit aggregate/computed
753
+ // outputs (C05: "do not leave Interior.rules projection only"); ONLY the
754
+ // recursive feedback cycle stays projection-only — no aggregate,
755
+ // arithmetic-created value or negation flows through it.
756
+ const recName = context.kind === "rec-base" || context.kind === "rec-arm" ? context.self.name : undefined;
523
757
  for (const [name, entry] of Object.entries(entries)) {
524
758
  if (entry === undefined) {
525
759
  continue;
526
760
  }
527
- if (derivedHead && !(isTerm(entry) && entry[term] === "var")) {
528
- const who = context.kind === "interior" ? `interior ${context.self}` : `rec ${context.self.name}`;
529
- throw errors.new(`${who}: a rec head projects bound variables only — aggregates and the measure read finished sets (unwritable here)`);
761
+ if (recName !== undefined && !(isTerm(entry) && entry[term] === "var")) {
762
+ throw new AuthoringError({
763
+ message: `rec ${recName}: a rec head projects bound variables only — no aggregate or arithmetic-created value flows through the feedback cycle (compute over the finished set in the main rules)`
764
+ });
530
765
  }
531
766
  columns.push(findColumnOf(name, entry));
532
767
  }
@@ -535,6 +770,9 @@ function findColumns(context, entries) {
535
770
  function makeRawChain(context, state) {
536
771
  const chain = {
537
772
  match(relation, bindings) {
773
+ if (isImportedSource(relation)) {
774
+ return makeRawChain(context, importAdvance(context, state, relation, bindings));
775
+ }
538
776
  return makeRawChain(context, advanceMatch(context, state, relation, bindings));
539
777
  },
540
778
  where(cond) {
@@ -554,6 +792,9 @@ function makeRawScope(context) {
554
792
  const scope = {
555
793
  ...termOps,
556
794
  match(relation, bindings) {
795
+ if (isImportedSource(relation)) {
796
+ return makeRawChain(context, importAdvance(context, EMPTY_RULE, relation, bindings));
797
+ }
557
798
  return makeRawChain(context, advanceMatch(context, EMPTY_RULE, relation, bindings));
558
799
  },
559
800
  interior(name, bindings) {
@@ -578,14 +819,14 @@ function isTypedScope(scope) {
578
819
  function makeQueryRuleScope(theory, env) {
579
820
  const raw = makeRawScope({ kind: "query", classes: theory.classes, theory, ...env });
580
821
  if (!isTypedScope(raw)) {
581
- throw errors.new("query rule builder construction incomplete");
822
+ throw new SdkInvariantError({ message: "query rule builder construction incomplete" });
582
823
  }
583
824
  return raw;
584
825
  }
585
826
  function makeInteriorRuleScope(theory, env, self) {
586
827
  const raw = makeRawScope({ kind: "interior", self, classes: theory.classes, theory, ...env });
587
828
  if (!isTypedScope(raw)) {
588
- throw errors.new("interior rule builder construction incomplete");
829
+ throw new SdkInvariantError({ message: "interior rule builder construction incomplete" });
589
830
  }
590
831
  return raw;
591
832
  }
@@ -606,7 +847,7 @@ function makeRecRuleScope(theory, env, self, kind) {
606
847
  ...env
607
848
  });
608
849
  if (!isTypedScope(raw)) {
609
- throw errors.new("rec rule builder construction incomplete");
850
+ throw new SdkInvariantError({ message: "rec rule builder construction incomplete" });
610
851
  }
611
852
  return raw;
612
853
  }
@@ -618,6 +859,9 @@ function headSignature(column) {
618
859
  if (entry.kind === "var") {
619
860
  return `${column.name}:var`;
620
861
  }
862
+ if (entry.kind === "compute") {
863
+ return `${column.name}:compute`;
864
+ }
621
865
  const agg = entry.agg;
622
866
  if (agg.op === "fold") {
623
867
  return `${column.name}:${agg.fold}`;
@@ -656,16 +900,22 @@ function paramRegistryOf(interiors, rec, rules) {
656
900
  continue;
657
901
  }
658
902
  if ((existing.members === undefined) !== (use.members === undefined)) {
659
- throw errors.new(`query param ${use.name} collides with a membership array's registry entry — name the param differently`);
903
+ throw new AuthoringError({
904
+ message: `query param ${use.name} collides with a membership array's registry entry — name the param differently`
905
+ });
660
906
  }
661
907
  if (existing.shape !== use.shape) {
662
- throw errors.new(`query param ${use.name} is used both as a ${existing.shape} param and a ${use.shape} param — one name, one shape`);
908
+ throw new AuthoringError({
909
+ message: `query param ${use.name} is used both as a ${existing.shape} param and a ${use.shape} param — one name, one shape`
910
+ });
663
911
  }
664
912
  if (existing.anchor !== undefined && use.anchor !== undefined) {
665
913
  const registered = rosterOf(existing.anchor);
666
914
  const anchored = rosterOf(use.anchor);
667
915
  if (registered !== anchored) {
668
- throw errors.new(`query param ${use.name} is anchored at ${renderParamAnchor(registered)} and at ${renderParamAnchor(anchored)} — a closed-anchored param translates handle names through ONE roster (one name, one domain); name the params differently`);
916
+ throw new AuthoringError({
917
+ message: `query param ${use.name} is anchored at ${renderParamAnchor(registered)} and at ${renderParamAnchor(anchored)} — a closed-anchored param translates handle names through ONE roster (one name, one domain); name the params differently`
918
+ });
669
919
  }
670
920
  }
671
921
  if (existing.anchor === undefined && use.anchor !== undefined) {
@@ -693,13 +943,13 @@ function paramRegistryOf(interiors, rec, rules) {
693
943
  return Object.freeze(order.map(function entryOf(name) {
694
944
  const entry = byName.get(name);
695
945
  if (entry === undefined) {
696
- throw errors.new(`query param ${name} lost its registry entry`);
946
+ throw new AuthoringError({ message: `query param ${name} lost its registry entry` });
697
947
  }
698
948
  let membership;
699
949
  if (entry.members !== undefined) {
700
950
  const anchor = entry.anchor;
701
951
  if (anchor === undefined) {
702
- throw errors.new(`query param ${name} lost its membership anchor`);
952
+ throw new AuthoringError({ message: `query param ${name} lost its membership anchor` });
703
953
  }
704
954
  membership = Object.freeze({
705
955
  kind: "set",
@@ -722,24 +972,30 @@ function paramRegistryOf(interiors, rec, rules) {
722
972
  function alignedHeadOf(label, rules) {
723
973
  const first = rules[0];
724
974
  if (first === undefined) {
725
- throw errors.new(`${label} needs at least one rule`);
975
+ throw new AuthoringError({ message: `${label} needs at least one rule` });
726
976
  }
727
977
  const signature = first.finds.map(headSignature).join(", ");
728
978
  rules.forEach(function verifyHead(rule, index) {
729
979
  const candidate = rule.finds.map(headSignature).join(", ");
730
980
  if (candidate !== signature) {
731
- throw errors.new(`every rule of ${label} derives the same head — rule 0 finds (${signature}), rule ${index} finds (${candidate})`);
981
+ throw new AuthoringError({
982
+ message: `every rule of ${label} derives the same head — rule 0 finds (${signature}), rule ${index} finds (${candidate})`
983
+ });
732
984
  }
733
985
  rule.finds.forEach(function verifyClosedSlice(column, position) {
734
986
  const lead = first.finds[position];
735
987
  if (lead !== undefined && column.closed !== lead.closed) {
736
- throw errors.new(`every rule of ${label} derives the same head — the head column ${lead.name} is ${renderClosedSlice(lead.closed)} in rule 0 but ${renderClosedSlice(column.closed)} in rule ${index} (one column decodes through one roster)`);
988
+ throw new AuthoringError({
989
+ message: `every rule of ${label} derives the same head — the head column ${lead.name} is ${renderClosedSlice(lead.closed)} in rule 0 but ${renderClosedSlice(column.closed)} in rule ${index} (one column decodes through one roster)`
990
+ });
737
991
  }
738
992
  if (lead === undefined) {
739
993
  return;
740
994
  }
741
995
  if (lead.slot !== undefined && column.slot !== undefined && !headFieldJoins(lead.slot, column.slot)) {
742
- throw errors.new(`every rule of ${label} derives the same head — the head column ${lead.name} is bound at ${renderFieldKind(lead.slot)} in rule 0 but at ${renderFieldKind(column.slot)} in rule ${index} (a head column joins class-equal slots; same-wire u64 admits a bare side, and the merged head is bare)`);
996
+ throw new AuthoringError({
997
+ message: `every rule of ${label} derives the same head — the head column ${lead.name} is bound at ${renderFieldKind(lead.slot)} in rule 0 but at ${renderFieldKind(column.slot)} in rule ${index} (a head column joins class-equal slots; same-wire u64 admits a bare side, and the merged head is bare)`
998
+ });
743
999
  }
744
1000
  });
745
1001
  });
@@ -765,18 +1021,21 @@ function alignedHeadOf(label, rules) {
765
1021
  return Object.freeze(merged);
766
1022
  }
767
1023
  function afterMainError(what) {
768
- return errors.new(`query: ${what} after a main rule is unwritable — declaration order is interiors, then rec, then main`);
1024
+ return new AuthoringError({
1025
+ message: `query: ${what} after a main rule is unwritable — declaration order is interiors, then rec, then main`
1026
+ });
769
1027
  }
770
1028
  function makeRawQuery(theory, interiors, rec, rules) {
771
1029
  const mergedFinds = alignedHeadOf("a query", rules);
772
1030
  const first = rules[0];
773
1031
  if (first === undefined) {
774
- throw errors.new("a query needs at least one rule");
1032
+ throw new AuthoringError({ message: "a query needs at least one rule" });
775
1033
  }
776
1034
  const env = rec === undefined ? { interiors } : { interiors, rec };
777
- const frozenInteriors = Object.freeze([...interiors]);
1035
+ const allInteriors = spliceImports(interiors, rec, rules);
1036
+ const frozenInteriors = Object.freeze([...allInteriors]);
778
1037
  const frozenRules = Object.freeze([...rules]);
779
- const params = paramRegistryOf(interiors, rec, rules);
1038
+ const params = paramRegistryOf(allInteriors, rec, rules);
780
1039
  const data = rec === undefined
781
1040
  ? Object.freeze({
782
1041
  kind: "cq",
@@ -800,6 +1059,12 @@ function makeRawQuery(theory, interiors, rec, rules) {
800
1059
  const built = build(makeRawScope({ kind: "query", classes: theory.classes, theory, ...env }));
801
1060
  return makeRawQuery(theory, interiors, rec, [...rules, built.rule]);
802
1061
  },
1062
+ named(label) {
1063
+ // A diagnostic name for composition/tracing — not a schema
1064
+ // relation, a persistent view or a separate CTE type.
1065
+ labelImport(value, label);
1066
+ return value;
1067
+ },
803
1068
  interior() {
804
1069
  throw afterMainError("interior");
805
1070
  },
@@ -815,7 +1080,7 @@ function makeQuery(theory, interiors, rec, rules) {
815
1080
  }
816
1081
  function collectInterior(theory, env, name, builds) {
817
1082
  if (builds.length === 0) {
818
- throw errors.new(`query: interior ${name} needs at least one rule`);
1083
+ throw new AuthoringError({ message: `query: interior ${name} needs at least one rule` });
819
1084
  }
820
1085
  const rules = builds.map(function buildRule(buildOne) {
821
1086
  return buildOne(makeInteriorRuleScope(theory, env, name)).rule;
@@ -825,10 +1090,10 @@ function collectInterior(theory, env, name, builds) {
825
1090
  }
826
1091
  function collectRec(theory, interiors, name, baseBuilds, recBuilds) {
827
1092
  if (baseBuilds.length === 0) {
828
- throw errors.new(`query: rec ${name} has no base arms`);
1093
+ throw new AuthoringError({ message: `query: rec ${name} has no base arms` });
829
1094
  }
830
1095
  if (recBuilds.length === 0) {
831
- throw errors.new(`query: rec ${name} has no rec arms`);
1096
+ throw new AuthoringError({ message: `query: rec ${name} has no rec arms` });
832
1097
  }
833
1098
  const handle = Object.freeze({ name });
834
1099
  const baseEnv = { interiors, rec: handle };
@@ -838,11 +1103,11 @@ function collectRec(theory, interiors, name, baseBuilds, recBuilds) {
838
1103
  const baseFinds = alignedHeadOf(`rec ${name}`, base);
839
1104
  const first = base[0];
840
1105
  if (first === undefined) {
841
- throw errors.new(`query: rec ${name} has no base arms`);
1106
+ throw new AuthoringError({ message: `query: rec ${name} has no base arms` });
842
1107
  }
843
1108
  const firstFind = baseFinds[0];
844
1109
  if (firstFind === undefined) {
845
- throw errors.new(`query: rec ${name} has no head`);
1110
+ throw new AuthoringError({ message: `query: rec ${name} has no head` });
846
1111
  }
847
1112
  const finds = [firstFind, ...baseFinds.slice(1)];
848
1113
  const head = Object.freeze({ name, finds });
@@ -853,12 +1118,12 @@ function collectRec(theory, interiors, name, baseBuilds, recBuilds) {
853
1118
  const mergedAll = alignedHeadOf(`rec ${name}`, [...base, ...rec]);
854
1119
  const mergedFirst = mergedAll[0];
855
1120
  if (mergedFirst === undefined) {
856
- throw errors.new(`query: rec ${name} has no head`);
1121
+ throw new AuthoringError({ message: `query: rec ${name} has no head` });
857
1122
  }
858
1123
  const sealedFinds = [mergedFirst, ...mergedAll.slice(1)];
859
1124
  const firstRec = rec[0];
860
1125
  if (firstRec === undefined) {
861
- throw errors.new(`query: rec ${name} has no rec arms`);
1126
+ throw new AuthoringError({ message: `query: rec ${name} has no rec arms` });
862
1127
  }
863
1128
  const sealedBase = [first, ...base.slice(1)];
864
1129
  const sealedRec = [firstRec, ...rec.slice(1)];
@@ -877,10 +1142,10 @@ function makeQueryStart(theory, interiors) {
877
1142
  if (interiors.some(function sameName(interior) {
878
1143
  return interior.name === name;
879
1144
  })) {
880
- throw errors.new(`query: interior ${name} is already declared — names are unique`);
1145
+ throw new AuthoringError({ message: `query: interior ${name} is already declared — names are unique` });
881
1146
  }
882
1147
  if (name.length === 0) {
883
- throw errors.new("query: an interior needs a name");
1148
+ throw new AuthoringError({ message: "query: an interior needs a name" });
884
1149
  }
885
1150
  const data = collectInterior(theory, env, name, builds);
886
1151
  return makeQueryStart(theory, [...interiors, data]);
@@ -889,10 +1154,10 @@ function makeQueryStart(theory, interiors) {
889
1154
  if (interiors.some(function sameName(interior) {
890
1155
  return interior.name === name;
891
1156
  })) {
892
- throw errors.new(`query: interior and rec share the name ${name}`);
1157
+ throw new AuthoringError({ message: `query: interior and rec share the name ${name}` });
893
1158
  }
894
1159
  if (name.length === 0) {
895
- throw errors.new("query: reach needs a name");
1160
+ throw new AuthoringError({ message: "query: reach needs a name" });
896
1161
  }
897
1162
  const data = collectRec(theory, interiors, name, arms.base, arms.rec);
898
1163
  return makeQueryReachStart(theory, interiors, data);
@@ -925,11 +1190,29 @@ function taggedHandleId(context, closed, value) {
925
1190
  }
926
1191
  const id = closed.handles.indexOf(value);
927
1192
  if (id < 0) {
928
- throw errors.new(`${context}: "${value}" is not a handle of ${closed.name} — the roster is ${closed.handles.join(", ")}`);
1193
+ throw new AuthoringError({
1194
+ message: `${context}: "${value}" is not a handle of ${closed.name} — the roster is ${closed.handles.join(", ")}`
1195
+ });
929
1196
  }
930
1197
  return { kind: "u64", value: BigInt(id) };
931
1198
  }
932
1199
  function taggedAtElementDomain(context, element, value) {
1200
+ if (element === "f64") {
1201
+ if (typeof value === "number") {
1202
+ return { kind: "f64", value };
1203
+ }
1204
+ if (isFloatIntervalValue(value)) {
1205
+ if (Number.isNaN(value.start) || Number.isNaN(value.end) || !(value.start < value.end)) {
1206
+ throw literalShapeError(context, "a nonempty float interval with non-NaN endpoints", value);
1207
+ }
1208
+ return {
1209
+ kind: "intervalF64",
1210
+ start: Object.is(value.start, -0) ? 0 : value.start,
1211
+ end: Object.is(value.end, -0) ? 0 : value.end
1212
+ };
1213
+ }
1214
+ throw literalShapeError(context, "number (point) or { start, end } numbers (interval)", value);
1215
+ }
933
1216
  if (typeof value === "bigint") {
934
1217
  if (element === "u64") {
935
1218
  return { kind: "u64", value };
@@ -968,6 +1251,18 @@ function taggedLiteral(context, field, value) {
968
1251
  }
969
1252
  return { kind: "i64", value };
970
1253
  }
1254
+ case "f64": {
1255
+ if (typeof value !== "number") {
1256
+ throw literalShapeError(context, "number", value);
1257
+ }
1258
+ return { kind: "f64", value };
1259
+ }
1260
+ case "uuid": {
1261
+ if (!Uuid.isUuid(value)) {
1262
+ throw literalShapeError(context, "a UUID (canonical UUID text)", value);
1263
+ }
1264
+ return { kind: "uuid", value };
1265
+ }
971
1266
  case "str": {
972
1267
  if (typeof value !== "string") {
973
1268
  throw literalShapeError(context, "string", value);
@@ -1006,6 +1301,9 @@ function taggedCmpLiteral(context, sibling, value, op) {
1006
1301
  isIntervalValue(value)) {
1007
1302
  return taggedAtElementDomain(context, sibling.kind, value);
1008
1303
  }
1304
+ if (op === "pointIn" && rosterOf(sibling) === undefined && sibling.kind === "f64" && isFloatIntervalValue(value)) {
1305
+ return taggedAtElementDomain(context, "f64", value);
1306
+ }
1009
1307
  return taggedLiteral(context, sibling, value);
1010
1308
  }
1011
1309
  function freshVarIds() {
@@ -1025,18 +1323,20 @@ function freshVarIds() {
1025
1323
  function paramIdOf(ctx, name) {
1026
1324
  const id = ctx.paramIds.get(name);
1027
1325
  if (id === undefined) {
1028
- throw errors.new(`query lowering: param ${name} is not in the query's registry`);
1326
+ throw new AuthoringError({ message: `query lowering: param ${name} is not in the query's registry` });
1029
1327
  }
1030
1328
  return id;
1031
1329
  }
1032
1330
  function lowerAtom(ctx, atom, ids) {
1033
1331
  const member = ctx.theory.relations[atom.relation.name];
1034
1332
  if (member !== atom.relation) {
1035
- throw errors.new(`query lowering: relation ${atom.relation.name} is not the relation value schema ${ctx.theory.name} declares`);
1333
+ throw new AuthoringError({
1334
+ message: `query lowering: relation ${atom.relation.name} is not the relation value schema ${ctx.theory.name} declares`
1335
+ });
1036
1336
  }
1037
1337
  const relationId = ctx.relationIds.get(atom.relation.name);
1038
1338
  if (relationId === undefined) {
1039
- throw errors.new(`query lowering: relation ${atom.relation.name} has no ordinal`);
1339
+ throw new AuthoringError({ message: `query lowering: relation ${atom.relation.name} has no ordinal` });
1040
1340
  }
1041
1341
  const ordered = sealedFieldsOf(atom.relation);
1042
1342
  const bindings = atom.bindings.map(function lowerBinding(binding) {
@@ -1044,7 +1344,9 @@ function lowerAtom(ctx, atom, ids) {
1044
1344
  return candidate.name === binding.field;
1045
1345
  });
1046
1346
  if (ordinal < 0) {
1047
- throw errors.new(`query lowering: relation ${atom.relation.name} has no field ${binding.field}`);
1347
+ throw new AuthoringError({
1348
+ message: `query lowering: relation ${atom.relation.name} has no field ${binding.field}`
1349
+ });
1048
1350
  }
1049
1351
  return [ordinal, lowerBindingTerm(ctx, `${atom.relation.name}.${binding.field}`, binding, ids)];
1050
1352
  });
@@ -1074,17 +1376,17 @@ function lowerBindingTerm(ctx, context, binding, ids) {
1074
1376
  function lowerInteriorAtom(ctx, target, bindings, ids) {
1075
1377
  const interior = ctx.interiorIds.get(target.name);
1076
1378
  if (interior === undefined) {
1077
- throw errors.new(`query lowering: derived table ${target.name} was not declared on this query`);
1379
+ throw new AuthoringError({ message: `query lowering: derived table ${target.name} was not declared on this query` });
1078
1380
  }
1079
1381
  if (target.finds.length === 0) {
1080
- throw errors.new(`query lowering: derived table ${target.name} has no head`);
1382
+ throw new AuthoringError({ message: `query lowering: derived table ${target.name} has no head` });
1081
1383
  }
1082
1384
  const irBindings = target.finds.map(function lowerPosition(column, position) {
1083
1385
  const binding = bindings.find(function byKey(candidate) {
1084
1386
  return candidate.key === column.name;
1085
1387
  });
1086
1388
  if (binding === undefined) {
1087
- throw errors.new(`query lowering: interior ${target.name} omits head column ${column.name}`);
1389
+ throw new AuthoringError({ message: `query lowering: interior ${target.name} omits head column ${column.name}` });
1088
1390
  }
1089
1391
  return [position, { kind: "var", var: ids.of(binding.ref) }];
1090
1392
  });
@@ -1101,7 +1403,9 @@ function lowerCmpTerm(ctx, side, sibling, ids, op) {
1101
1403
  case "literal": {
1102
1404
  const anchor = cmpAnchorOf(ctx, sibling);
1103
1405
  if (anchor === undefined) {
1104
- throw errors.new("query lowering: a comparison literal needs a bound-variable or anchored-param sibling to type it");
1406
+ throw new AuthoringError({
1407
+ message: "query lowering: a comparison literal needs a bound-variable or anchored-param sibling to type it"
1408
+ });
1105
1409
  }
1106
1410
  return { kind: "literal", value: taggedCmpLiteral("comparison literal", anchor, side.value, op) };
1107
1411
  }
@@ -1141,10 +1445,54 @@ function lowerCondition(ctx, cond, ids) {
1141
1445
  })
1142
1446
  };
1143
1447
  }
1448
+ /**
1449
+ * Lowers one computed scalar node to the C05 wire (`#native.ts`'s
1450
+ * `ScalarExprIr`): the shared grammar arms pass through 1:1. Cached
1451
+ * construction depth is the bound; this walk emits wire only and does
1452
+ * not re-judge kinds (L14/native compile owns binding).
1453
+ */
1454
+ function lowerComputeNode(node, ids) {
1455
+ if (node.depth > MAX_COMPUTE_DEPTH) {
1456
+ throw new AuthoringError({
1457
+ message: `query lowering: a compute expression is deeper than ${MAX_COMPUTE_DEPTH} nodes (the engine's scalar depth bound)`
1458
+ });
1459
+ }
1460
+ return lowerComputeGrammar(node, ids);
1461
+ }
1462
+ function lowerComputeGrammar(node, ids) {
1463
+ switch (node.kind) {
1464
+ case "var":
1465
+ return { kind: "var", var: ids.of(node.ref) };
1466
+ case "literal":
1467
+ return { kind: "literal", value: literalWireOf(node.value) };
1468
+ case "negate":
1469
+ case "isNaN":
1470
+ case "isFinite":
1471
+ return { kind: node.kind, expr: lowerComputeGrammar(node.expr, ids) };
1472
+ case "cast":
1473
+ return { kind: "cast", cast: node.cast, expr: lowerComputeGrammar(node.expr, ids) };
1474
+ case "add":
1475
+ case "subtract":
1476
+ case "multiply":
1477
+ case "divide":
1478
+ return {
1479
+ kind: node.kind,
1480
+ left: lowerComputeGrammar(node.left, ids),
1481
+ right: lowerComputeGrammar(node.right, ids)
1482
+ };
1483
+ default:
1484
+ throw new AuthoringError({
1485
+ message: "query lowering: a query-var tree cannot carry a source-field leaf — use Compute over bound variables"
1486
+ });
1487
+ }
1488
+ }
1144
1489
  function lowerFind(entry, ids) {
1145
1490
  if (entry.kind === "var") {
1146
1491
  return { kind: "var", var: ids.of(entry.over) };
1147
1492
  }
1493
+ if (entry.kind === "compute") {
1494
+ return { kind: "compute", expr: lowerComputeNode(entry.expr, ids) };
1495
+ }
1148
1496
  const agg = entry.agg;
1149
1497
  switch (agg.op) {
1150
1498
  case "count":
@@ -1170,6 +1518,9 @@ function headTermOf(column) {
1170
1518
  if (entry.kind === "var") {
1171
1519
  return { kind: "var" };
1172
1520
  }
1521
+ if (entry.kind === "compute") {
1522
+ return { kind: "compute" };
1523
+ }
1173
1524
  return { kind: "aggregate", op: headOpOf(entry.agg) };
1174
1525
  }
1175
1526
  function lowerRule(ctx, rule) {
@@ -1227,7 +1578,9 @@ function lowerQuery(q) {
1227
1578
  const params = new Map();
1228
1579
  q.data.params.forEach(function assignParamId(entry, index) {
1229
1580
  if (entry.anchor === undefined) {
1230
- throw errors.new(`query param ${entry.name} has no field-anchored use — bind it in an atom or compare it against a bound variable`);
1581
+ throw new AuthoringError({
1582
+ message: `query param ${entry.name} has no field-anchored use — bind it in an atom or compare it against a bound variable`
1583
+ });
1231
1584
  }
1232
1585
  paramIds.set(entry.name, index);
1233
1586
  params.set(entry.name, entry);