@bjornpagen/bumbledb 0.4.0 → 0.5.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 (92) hide show
  1. package/COOKBOOK.md +181 -47
  2. package/README.md +4 -2
  3. package/dist/closed.d.ts +30 -2
  4. package/dist/closed.d.ts.map +1 -1
  5. package/dist/closed.js +66 -20
  6. package/dist/closed.js.map +1 -1
  7. package/dist/db.d.ts +33 -6
  8. package/dist/db.d.ts.map +1 -1
  9. package/dist/db.js +87 -70
  10. package/dist/db.js.map +1 -1
  11. package/dist/exhume.d.ts.map +1 -1
  12. package/dist/exhume.js +1 -14
  13. package/dist/exhume.js.map +1 -1
  14. package/dist/face.d.ts +1 -1
  15. package/dist/face.d.ts.map +1 -1
  16. package/dist/face.js +2 -1
  17. package/dist/face.js.map +1 -1
  18. package/dist/fields.d.ts +18 -1
  19. package/dist/fields.d.ts.map +1 -1
  20. package/dist/fields.js +44 -16
  21. package/dist/fields.js.map +1 -1
  22. package/dist/index.d.ts +11 -6
  23. package/dist/index.d.ts.map +1 -1
  24. package/dist/index.js +8 -4
  25. package/dist/index.js.map +1 -1
  26. package/dist/law.d.ts +2 -1
  27. package/dist/law.d.ts.map +1 -1
  28. package/dist/law.js +15 -14
  29. package/dist/law.js.map +1 -1
  30. package/dist/lower.d.ts.map +1 -1
  31. package/dist/lower.js +1 -7
  32. package/dist/lower.js.map +1 -1
  33. package/dist/marshal.d.ts.map +1 -1
  34. package/dist/marshal.js +15 -27
  35. package/dist/marshal.js.map +1 -1
  36. package/dist/native.d.ts +21 -2
  37. package/dist/native.d.ts.map +1 -1
  38. package/dist/native.js +20 -3
  39. package/dist/native.js.map +1 -1
  40. package/dist/order.d.ts +36 -0
  41. package/dist/order.d.ts.map +1 -0
  42. package/dist/order.js +135 -0
  43. package/dist/order.js.map +1 -0
  44. package/dist/query/atom.d.ts +18 -15
  45. package/dist/query/atom.d.ts.map +1 -1
  46. package/dist/query/atom.js +9 -16
  47. package/dist/query/atom.js.map +1 -1
  48. package/dist/query/lower.d.ts +5 -8
  49. package/dist/query/lower.d.ts.map +1 -1
  50. package/dist/query/lower.js +87 -76
  51. package/dist/query/lower.js.map +1 -1
  52. package/dist/query/predicate.d.ts.map +1 -1
  53. package/dist/query/predicate.js +34 -2
  54. package/dist/query/predicate.js.map +1 -1
  55. package/dist/query/run.d.ts.map +1 -1
  56. package/dist/query/run.js +1 -2
  57. package/dist/query/run.js.map +1 -1
  58. package/dist/query/scope.d.ts +2 -16
  59. package/dist/query/scope.d.ts.map +1 -1
  60. package/dist/query/scope.js +7 -47
  61. package/dist/query/scope.js.map +1 -1
  62. package/dist/relation.d.ts +9 -22
  63. package/dist/relation.d.ts.map +1 -1
  64. package/dist/relation.js +9 -28
  65. package/dist/relation.js.map +1 -1
  66. package/dist/schema.d.ts.map +1 -1
  67. package/dist/schema.js +7 -31
  68. package/dist/schema.js.map +1 -1
  69. package/dist/statements.d.ts +5 -2
  70. package/dist/statements.d.ts.map +1 -1
  71. package/dist/statements.js +30 -35
  72. package/dist/statements.js.map +1 -1
  73. package/package.json +2 -5
  74. package/src/closed.ts +73 -28
  75. package/src/db.ts +122 -84
  76. package/src/exhume.ts +1 -15
  77. package/src/face.ts +4 -3
  78. package/src/fields.ts +58 -16
  79. package/src/index.ts +10 -8
  80. package/src/law.ts +15 -14
  81. package/src/lower.ts +2 -9
  82. package/src/marshal.ts +15 -31
  83. package/src/native.ts +22 -4
  84. package/src/order.ts +156 -0
  85. package/src/query/atom.ts +13 -21
  86. package/src/query/lower.ts +90 -91
  87. package/src/query/predicate.ts +39 -4
  88. package/src/query/run.ts +1 -3
  89. package/src/query/scope.ts +7 -59
  90. package/src/relation.ts +9 -51
  91. package/src/schema.ts +7 -33
  92. package/src/statements.ts +33 -38
@@ -26,8 +26,9 @@
26
26
  */
27
27
 
28
28
  import * as errors from "@superbuilders/errors"
29
+ import { sealedFieldsOf } from "#closed.ts"
29
30
  import type { AnyField, ClosedRoster } from "#fields.ts"
30
- import { assertDeclarationOrderKey } from "#fields.ts"
31
+ import { assertDeclarationOrderKey, isIntervalValue, literalShapeError, rosterOf } from "#fields.ts"
31
32
  import type { ClassRecordOf, SchemaClasses } from "#law.ts"
32
33
  import type {
33
34
  AtomIr,
@@ -68,7 +69,7 @@ import type {
68
69
  SelectEntryData,
69
70
  TreeData
70
71
  } from "#query/atom.ts"
71
- import { allen, and, covers, eq, ge, gt, le, lt, ne, not, or, pointIn } from "#query/atom.ts"
72
+ import { allen, and, eq, ge, gt, le, lt, ne, not, or, pointIn } from "#query/atom.ts"
72
73
  import type {
73
74
  ClassedField,
74
75
  EnvShape,
@@ -88,13 +89,12 @@ import {
88
89
  makeParam,
89
90
  makeSetParam,
90
91
  makeVar,
91
- makeVars,
92
92
  renderFieldKind,
93
93
  term
94
94
  } from "#query/scope.ts"
95
95
  import type { CheckNameSelect, CheckSelect, RowOfNameSelect, RowOfSelect, SelectEntry } from "#query/select.ts"
96
96
  import { argMax, argMin, count, countDistinct, max, min, pack, sum } from "#query/select.ts"
97
- import type { FieldsShape, RelationField } from "#relation.ts"
97
+ import type { FieldsShape } from "#relation.ts"
98
98
  import type { AnySchema, Schema, SchemaRelations } from "#schema.ts"
99
99
 
100
100
  /**
@@ -198,8 +198,6 @@ type CheckIdbVars<Env extends EnvShape, V, Head extends HeadShape = undefined> =
198
198
  interface TermOps {
199
199
  /** Declares/names one variable: typed by the field it first binds; reuse joins. */
200
200
  readonly var: typeof makeVar
201
- /** Mints several variables at once — `const { service, w } = r.vars("service", "w")`: each name typed exactly; duplicates refuse. */
202
- readonly vars: typeof makeVars
203
201
  /** Names one scalar parameter: typed by its use; the key of the execute params object. */
204
202
  readonly param: typeof makeParam
205
203
  /** Names one ∈-set parameter (the IR's `ParamSet`): bound to a readonly array at execution. */
@@ -215,7 +213,6 @@ interface TermOps {
215
213
  readonly gt: typeof gt
216
214
  readonly ge: typeof ge
217
215
  readonly pointIn: typeof pointIn
218
- readonly covers: typeof covers
219
216
  readonly allen: typeof allen
220
217
  readonly and: typeof and
221
218
  readonly or: typeof or
@@ -431,7 +428,6 @@ interface QueryStart<Rels extends SchemaRelations, Classes extends SchemaClasses
431
428
  /** The frozen constructor vocabulary every rule builder spreads. */
432
429
  const termOps: TermOps = Object.freeze({
433
430
  var: makeVar,
434
- vars: makeVars,
435
431
  param: makeParam,
436
432
  inSet: makeSetParam,
437
433
  maskParam: makeMaskParam,
@@ -443,7 +439,6 @@ const termOps: TermOps = Object.freeze({
443
439
  gt,
444
440
  ge,
445
441
  pointIn,
446
- covers,
447
442
  allen,
448
443
  and,
449
444
  or,
@@ -479,22 +474,6 @@ interface ResolvedBindings {
479
474
  readonly uses: readonly ParamUse[]
480
475
  }
481
476
 
482
- /**
483
- * The ordered matchable fields of an atom owner — the runtime twin of the
484
- * type tier's `MatchFields`: a relation's declared fields; a closed
485
- * relation's SEALED shape, the synthetic `id` (the value's own
486
- * roster-carrying descriptor, by identity) at ordinal 0 and the declared
487
- * payload columns at declared index + 1 (the sealed shift, mirroring
488
- * `spec.rs`'s resolver — a `ClosedColumn` is structurally a
489
- * {@link RelationField}). The lowering golden pins this mapping.
490
- */
491
- function matchFieldsOf(owner: MatchOwner): readonly RelationField[] {
492
- if ("axioms" in owner) {
493
- return [Object.freeze({ name: "id", field: owner.id }), ...owner.data.columns]
494
- }
495
- return owner.data.fields
496
- }
497
-
498
477
  /**
499
478
  * Judges one membership ARRAY at a binding position — legal exactly at a
500
479
  * CLOSED-reference field (the owner ruling: ordinary u64/str membership is
@@ -515,7 +494,8 @@ function membershipSet(
515
494
  field: AnyField,
516
495
  value: readonly unknown[]
517
496
  ): { readonly name: string; readonly members: readonly string[] } {
518
- if (!("closed" in field)) {
497
+ const roster = rosterOf(field)
498
+ if (roster === undefined) {
519
499
  throw errors.new(
520
500
  `${context}: a membership array is the closed-reference spelling — ordinary field membership is a bound ∈-set param (r.inSet)`
521
501
  )
@@ -531,7 +511,7 @@ function membershipSet(
531
511
  const seen = new Set<string>()
532
512
  const members = value.map(function memberName(member) {
533
513
  if (typeof member !== "string") {
534
- throw literalShapeError(context, `a ${field.closed.name} handle name (string)`, member)
514
+ throw literalShapeError(context, `a ${roster.name} handle name (string)`, member)
535
515
  }
536
516
  if (seen.has(member)) {
537
517
  throw errors.new(
@@ -542,7 +522,7 @@ function membershipSet(
542
522
  return member
543
523
  })
544
524
  const key = [...members].sort()
545
- return { name: `∈ ${field.closed.name} ${JSON.stringify(key)}`, members: Object.freeze(members) }
525
+ return { name: `∈ ${roster.name} ${JSON.stringify(key)}`, members: Object.freeze(members) }
546
526
  }
547
527
 
548
528
  /**
@@ -564,7 +544,7 @@ function resolveBindings(
564
544
  const vars: Array<{ readonly name: string; readonly slot: ClassedField }> = []
565
545
  const uses: ParamUse[] = []
566
546
  const relationClasses = classes[relation.name]
567
- const ordered = matchFieldsOf(relation)
547
+ const ordered = sealedFieldsOf(relation)
568
548
  for (const [fieldName, value] of Object.entries(bindings)) {
569
549
  if (value === undefined) {
570
550
  continue
@@ -922,7 +902,7 @@ function closedOrderError(context: string, position: string, vocabulary: string)
922
902
  )
923
903
  }
924
904
 
925
- /** The comparison ops the orderable ban covers (order roster + point membership — every order-comparison position). */
905
+ /** The comparison ops under the orderable ban (order roster + point membership — every order-comparison position). */
926
906
  function isOrderOp(op: CmpKind | "binding"): op is "lt" | "le" | "gt" | "ge" | "pointIn" {
927
907
  return op === "lt" || op === "le" || op === "gt" || op === "ge" || op === "pointIn"
928
908
  }
@@ -960,8 +940,9 @@ function validateCond(context: string, varFields: Readonly<Record<string, Classe
960
940
  for (const side of [cond.lhs, cond.rhs]) {
961
941
  if (side.kind === "var") {
962
942
  const slot = assertBound(context, varFields, side.name)
963
- if (isOrderOp(cond.op) && "closed" in slot.field) {
964
- throw closedOrderError(context, `the ${cond.op} side ${side.name}`, slot.field.closed.name)
943
+ const roster = rosterOf(slot.field)
944
+ if (isOrderOp(cond.op) && roster !== undefined) {
945
+ throw closedOrderError(context, `the ${cond.op} side ${side.name}`, roster.name)
965
946
  }
966
947
  }
967
948
  if (side.kind === "measure") {
@@ -1009,12 +990,9 @@ function validateColumn(
1009
990
  case "fold": {
1010
991
  if (typeof agg.over === "string") {
1011
992
  const slot = assertBound(`${context} select ${column.name}`, varFields, agg.over)
1012
- if ("closed" in slot.field) {
1013
- throw closedOrderError(
1014
- `${context} select ${column.name}`,
1015
- `the ${agg.fold} input ${agg.over}`,
1016
- slot.field.closed.name
1017
- )
993
+ const roster = rosterOf(slot.field)
994
+ if (roster !== undefined) {
995
+ throw closedOrderError(`${context} select ${column.name}`, `the ${agg.fold} input ${agg.over}`, roster.name)
1018
996
  }
1019
997
  return
1020
998
  }
@@ -1024,11 +1002,12 @@ function validateColumn(
1024
1002
  case "arg": {
1025
1003
  assertBound(`${context} select ${column.name}`, varFields, agg.over)
1026
1004
  const key = assertBound(`${context} select ${column.name}`, varFields, agg.key)
1027
- if ("closed" in key.field) {
1005
+ const keyRoster = rosterOf(key.field)
1006
+ if (keyRoster !== undefined) {
1028
1007
  throw closedOrderError(
1029
1008
  `${context} select ${column.name}`,
1030
1009
  `the ${agg.direction} key ${agg.key}`,
1031
- key.field.closed.name
1010
+ keyRoster.name
1032
1011
  )
1033
1012
  }
1034
1013
  return
@@ -1064,11 +1043,7 @@ function selectClosedOf(
1064
1043
  if (over === undefined) {
1065
1044
  return undefined
1066
1045
  }
1067
- const field = varFields[over]?.field
1068
- if (field !== undefined && "closed" in field) {
1069
- return field.closed
1070
- }
1071
- return undefined
1046
+ return rosterOf(varFields[over]?.field)
1072
1047
  }
1073
1048
 
1074
1049
  /**
@@ -1267,10 +1242,12 @@ function makeRawScope(context: ChainContext): RawScope {
1267
1242
  }
1268
1243
 
1269
1244
  /**
1270
- * The one trusted admission seam of the rule builders (the `relation()`
1271
- * `refsComplete` pattern): the raw builder is one runtime shape for every
1272
- * context, and this guard verifies the checkable fact — the builder verbs
1273
- * exist before the value is admitted at its TYPED face. The type-level
1245
+ * The rule builders' trusted admission seam THE home of the
1246
+ * trusted-admission-seam pattern the other mint guards cite (the face,
1247
+ * class-map, axiom-readback, rec-handle, and query-value seams): the raw
1248
+ * builder is one runtime shape for every context, and this guard verifies
1249
+ * the checkable fact — the builder verbs exist — before the value is
1250
+ * admitted at its TYPED face. The type-level
1274
1251
  * judgments (domain-equal joins, boundness, the recursion cut) live in the
1275
1252
  * interfaces themselves; the runtime twin of every one of them is a
1276
1253
  * construction-time validation in this module.
@@ -1329,17 +1306,30 @@ function headSignature(column: SelectColumn): string {
1329
1306
  return `${column.name}:${agg.op}`
1330
1307
  }
1331
1308
 
1332
- /** The roster a param anchor carries: present exactly on a closed-reference field anchor. */
1333
- function anchorRosterOf(anchor: AnyField | "measure" | undefined): ClosedRoster | undefined {
1334
- if (anchor === undefined || anchor === "measure") {
1335
- return undefined
1309
+ /**
1310
+ * The classed slot one answer column's VALUES flow from, resolved through
1311
+ * the rule's own binding environment: a projected var's first-binding slot,
1312
+ * or an Arg-carried payload's (`argMax`/`argMin` carry `over` verbatim —
1313
+ * the same two shapes the closed slice lifts). Counts, folds, `pack` and
1314
+ * the measure derive numbers/intervals rather than carrying a slot's ids,
1315
+ * so they resolve no slot (`undefined`).
1316
+ */
1317
+ function headSlotOf(rule: RuleData, column: SelectColumn): ClassedField | undefined {
1318
+ const entry = column.entry
1319
+ if (entry.kind === "var") {
1320
+ return rule.varFields[entry.over]
1336
1321
  }
1337
- if ("closed" in anchor) {
1338
- return anchor.closed
1322
+ if (entry.kind === "aggregate" && entry.agg.op === "arg") {
1323
+ return rule.varFields[entry.agg.over]
1339
1324
  }
1340
1325
  return undefined
1341
1326
  }
1342
1327
 
1328
+ /** The roster a param anchor carries: present exactly on a closed-reference field anchor (rides THE one `rosterOf` reader). */
1329
+ function anchorRosterOf(anchor: AnyField | "measure" | undefined): ClosedRoster | undefined {
1330
+ return anchor === "measure" ? undefined : rosterOf(anchor)
1331
+ }
1332
+
1343
1333
  /** Renders one param anchor's closedness for the registry's coherence diagnostics. */
1344
1334
  function renderParamAnchor(roster: ClosedRoster | undefined): string {
1345
1335
  return roster === undefined ? "a non-closed position" : `a ${roster.name} reference`
@@ -1430,13 +1420,9 @@ function paramRegistryOf(recs: readonly RecData[], rules: readonly RuleData[]):
1430
1420
  if (entry === undefined) {
1431
1421
  throw errors.new(`query param ${name} lost its registry entry`)
1432
1422
  }
1433
- if (
1434
- entry.orderOp !== undefined &&
1435
- entry.anchor !== undefined &&
1436
- entry.anchor !== "measure" &&
1437
- "closed" in entry.anchor
1438
- ) {
1439
- throw closedOrderError(`query param ${name}`, `its ${entry.orderOp} use's anchor`, entry.anchor.closed.name)
1423
+ const anchorRoster = anchorRosterOf(entry.anchor)
1424
+ if (entry.orderOp !== undefined && anchorRoster !== undefined) {
1425
+ throw closedOrderError(`query param ${name}`, `its ${entry.orderOp} use's anchor`, anchorRoster.name)
1440
1426
  }
1441
1427
  return Object.freeze({ name, shape: entry.shape, anchor: entry.anchor, op: entry.op, members: entry.members })
1442
1428
  })
@@ -1481,6 +1467,23 @@ function makeRawQuery(theory: AnySchema, recs: readonly RecData[], rules: readon
1481
1467
  `every rule of a query derives the same head — the answer column ${lead.name} is ${renderClosedSlice(lead.closed)} in rule 0 but ${renderClosedSlice(column.closed)} in rule ${index} (one column decodes through one roster)`
1482
1468
  )
1483
1469
  }
1470
+ // The law-class wall on the union head: one answer column is one
1471
+ // value space, so the classed slot each rule binds the column at
1472
+ // must join across rules — the SAME fieldJoins judgment every
1473
+ // join/eq/negated-atom position enforces. The SDK holds this wall
1474
+ // because the wire IR carries no domains: the engine cannot
1475
+ // backstop it, and without it a union mixes (say) Holder ids and
1476
+ // Account ids in one column the consumer reads as one id space.
1477
+ if (lead === undefined) {
1478
+ return
1479
+ }
1480
+ const leadSlot = headSlotOf(first, lead)
1481
+ const slot = headSlotOf(rule, column)
1482
+ if (leadSlot !== undefined && slot !== undefined && !fieldJoins(leadSlot, slot)) {
1483
+ throw errors.new(
1484
+ `every rule of a query derives the same head — the answer column ${lead.name} unions domain-unequal fields: bound at ${renderFieldKind(leadSlot)} in rule 0 but at ${renderFieldKind(slot)} in rule ${index} (a union column joins only class-equal slots; bare pairs only with bare)`
1485
+ )
1486
+ }
1484
1487
  })
1485
1488
  })
1486
1489
  const data: QueryData = Object.freeze({
@@ -1502,7 +1505,7 @@ function makeRawQuery(theory: AnySchema, recs: readonly RecData[], rules: readon
1502
1505
  }
1503
1506
 
1504
1507
  /**
1505
- * The query values' trusted admission seam (the `refsComplete` pattern):
1508
+ * The query values' trusted admission seam (the {@link isTypedScope} pattern):
1506
1509
  * the checkable fact — the value was assembled over the identical theory —
1507
1510
  * is verified before the raw value is admitted at its typed face.
1508
1511
  */
@@ -1548,23 +1551,6 @@ function query<Rels extends SchemaRelations, Classes extends SchemaClasses>(
1548
1551
  return start
1549
1552
  }
1550
1553
 
1551
- /** The typed shape refusal of the literal tagger — a genuine failure, never data. */
1552
- function literalShapeError(context: string, expected: string, value: unknown): Error {
1553
- return errors.new(`${context}: expected ${expected}, got ${typeof value}`)
1554
- }
1555
-
1556
- /** Narrows an interval-shaped literal (a plain `{ start, end }` bigint pair). */
1557
- function isIntervalShaped(value: unknown): value is { readonly start: bigint; readonly end: bigint } {
1558
- return (
1559
- typeof value === "object" &&
1560
- value !== null &&
1561
- "start" in value &&
1562
- "end" in value &&
1563
- typeof value.start === "bigint" &&
1564
- typeof value.end === "bigint"
1565
- )
1566
- }
1567
-
1568
1554
  /**
1569
1555
  * Tags one closed-reference literal: the handle NAME, verified against the
1570
1556
  * roster (the belt the wide fallback type cannot provide — structural
@@ -1604,7 +1590,7 @@ function taggedAtElementDomain(context: string, element: "u64" | "i64", value: u
1604
1590
  }
1605
1591
  return { kind: "i64", value }
1606
1592
  }
1607
- if (isIntervalShaped(value)) {
1593
+ if (isIntervalValue(value)) {
1608
1594
  if (element === "u64") {
1609
1595
  return { kind: "intervalU64", start: value.start, end: value.end }
1610
1596
  }
@@ -1622,8 +1608,9 @@ function taggedAtElementDomain(context: string, element: "u64" | "i64", value: u
1622
1608
  * tagged u64 after a roster verification.
1623
1609
  */
1624
1610
  function taggedLiteral(context: string, field: AnyField, value: unknown): TaggedValue {
1625
- if ("closed" in field) {
1626
- return taggedHandleId(context, field.closed, value)
1611
+ const roster = rosterOf(field)
1612
+ if (roster !== undefined) {
1613
+ return taggedHandleId(context, roster, value)
1627
1614
  }
1628
1615
  switch (field.kind) {
1629
1616
  case "bool": {
@@ -1648,6 +1635,18 @@ function taggedLiteral(context: string, field: AnyField, value: unknown): Tagged
1648
1635
  if (typeof value !== "string") {
1649
1636
  throw literalShapeError(context, "string", value)
1650
1637
  }
1638
+ /**
1639
+ * The marshal's bijection law at the query seam (`marshal.ts`
1640
+ * cellOf): a lone surrogate would be lossily replaced with
1641
+ * U+FFFD at the bridge's UTF-8 crossing and silently match a
1642
+ * fact the typed write surface can never store — distinct JS
1643
+ * strings collapsing to one wire query. This is the single
1644
+ * seam every query string literal, string param
1645
+ * (`taggedCmpLiteral`), and membership member lowers through.
1646
+ */
1647
+ if (!value.isWellFormed()) {
1648
+ throw literalShapeError(context, "well-formed string", value)
1649
+ }
1651
1650
  return { kind: "string", value }
1652
1651
  }
1653
1652
  case "bytes": {
@@ -1665,11 +1664,11 @@ function taggedLiteral(context: string, field: AnyField, value: unknown): Tagged
1665
1664
  * Tags one host literal at a COMPARISON or PARAM position, where the
1666
1665
  * SIBLING anchors the type: a measure sibling is u64, an interval-field
1667
1666
  * sibling contributes its element domain (so both a point literal in
1668
- * `covers` and a `span` literal in `allen` tag correctly), a scalar
1667
+ * `pointIn` and a `span` literal in `allen` tag correctly), a scalar
1669
1668
  * sibling its own type. At `pointIn` the operand order is interval-left,
1670
1669
  * point-right (`ir::CmpOp::PointIn`), so an interval-shaped literal
1671
- * beside a scalar element-typed sibling is the LEGAL lhs of
1672
- * `covers(span(...), t)` and tags as the interval of the sibling's
1670
+ * beside a scalar element-typed sibling is the LEGAL interval operand of
1671
+ * `pointIn(t, span(...))` and tags as the interval of the sibling's
1673
1672
  * element domain; under every other operator an interval shape against a
1674
1673
  * scalar sibling stays refused (the engine's IllegalComparison — the
1675
1674
  * bug-hunt fix, preserved op-aware).
@@ -1686,14 +1685,14 @@ function taggedCmpLiteral(
1686
1685
  }
1687
1686
  return { kind: "u64", value }
1688
1687
  }
1689
- if (!("closed" in sibling) && sibling.kind === "interval") {
1688
+ if (rosterOf(sibling) === undefined && sibling.kind === "interval") {
1690
1689
  return taggedAtElementDomain(context, sibling.element, value)
1691
1690
  }
1692
1691
  if (
1693
1692
  op === "pointIn" &&
1694
- !("closed" in sibling) &&
1693
+ rosterOf(sibling) === undefined &&
1695
1694
  (sibling.kind === "u64" || sibling.kind === "i64") &&
1696
- isIntervalShaped(value)
1695
+ isIntervalValue(value)
1697
1696
  ) {
1698
1697
  return taggedAtElementDomain(context, sibling.kind, value)
1699
1698
  }
@@ -1743,7 +1742,7 @@ function paramIdOf(ctx: LowerContext, name: string): number {
1743
1742
  * Lowers one EDB atom (either polarity). A CLOSED owner lowers through the
1744
1743
  * same edb source — its ordinal is its record-declaration slot exactly like
1745
1744
  * an ordinary relation's — with field ordinals over the SEALED shape: `id`
1746
- * at 0, each payload column at its declared index + 1 (`matchFieldsOf`
1745
+ * at 0, each payload column at its declared index + 1 (`sealedFieldsOf`
1747
1746
  * carries the shift; the lowering golden pins it).
1748
1747
  */
1749
1748
  function lowerAtom(ctx: LowerContext, atom: AtomData, ids: VarIds): AtomIr {
@@ -1757,7 +1756,7 @@ function lowerAtom(ctx: LowerContext, atom: AtomData, ids: VarIds): AtomIr {
1757
1756
  if (relationId === undefined) {
1758
1757
  throw errors.new(`query lowering: relation ${atom.relation.name} has no ordinal`)
1759
1758
  }
1760
- const ordered = matchFieldsOf(atom.relation)
1759
+ const ordered = sealedFieldsOf(atom.relation)
1761
1760
  const bindings: Array<readonly [number, TermIr]> = atom.bindings.map(function lowerBinding(binding) {
1762
1761
  const ordinal = ordered.findIndex(function byName(candidate) {
1763
1762
  return candidate.name === binding.field
@@ -29,7 +29,7 @@
29
29
 
30
30
  import * as errors from "@superbuilders/errors"
31
31
  import type { SchemaClasses } from "#law.ts"
32
- import type { RecData } from "#query/atom.ts"
32
+ import type { RecData, RuleData, SelectColumn } from "#query/atom.ts"
33
33
  import type {
34
34
  AnyRuleValue,
35
35
  HeadOf,
@@ -45,8 +45,8 @@ import type {
45
45
  RuleValue
46
46
  } from "#query/lower.ts"
47
47
  import { makeOutputRuleScope, makeQuery, makeRawScope } from "#query/lower.ts"
48
- import type { Flatten, ParamsRecord, ShapeOf } from "#query/scope.ts"
49
- import { inferred } from "#query/scope.ts"
48
+ import type { ClassedField, Flatten, ParamsRecord, ShapeOf } from "#query/scope.ts"
49
+ import { fieldJoins, inferred, renderFieldKind } from "#query/scope.ts"
50
50
  import type { Schema, SchemaRelations } from "#schema.ts"
51
51
 
52
52
  /**
@@ -112,6 +112,20 @@ interface RawRec<Name extends string> {
112
112
  rule(build: (r: RawScope) => RuleValue<never, never>): RawRec<Name>
113
113
  }
114
114
 
115
+ /**
116
+ * The classed slot one rec head column binds, through the rule's own
117
+ * environment: a rec head projects bound variable NAMES only (the strata
118
+ * roster's unwritability), so every column is a projected var and its
119
+ * slot is the var's first positive binding.
120
+ */
121
+ function recHeadSlotOf(rule: RuleData, column: SelectColumn): ClassedField | undefined {
122
+ const entry = column.entry
123
+ if (entry.kind === "var") {
124
+ return rule.varFields[entry.over]
125
+ }
126
+ return undefined
127
+ }
128
+
115
129
  /** Builds the runtime rec handle over shared rec data. */
116
130
  function makeRawRec<Name extends string>(state: ProgramState, name: Name, data: RecData): RawRec<Name> {
117
131
  const rec: RawRec<Name> = {
@@ -137,6 +151,26 @@ function makeRawRec<Name extends string>(state: ProgramState, name: Name, data:
137
151
  `rec ${name}: every rule derives the same head — rule 0 projects (${declared.join(", ")}), this rule projects (${candidate.join(", ")})`
138
152
  )
139
153
  }
154
+ // The law-class wall on the sealed head: names alone do not
155
+ // align value spaces. Every rule must bind each head column
156
+ // at a slot that JOINS rule 0's (the sealing rule — the one
157
+ // slot every downstream idb pairing class-checks against),
158
+ // under the same fieldJoins judgment every reuse site
159
+ // enforces; otherwise a later rule feeds (say) bare weights
160
+ // into a column the idb joins as Node ids.
161
+ built.rule.select.forEach(function verifyHeadSlot(column, position) {
162
+ const lead = head.select[position]
163
+ if (lead === undefined) {
164
+ return
165
+ }
166
+ const leadSlot = recHeadSlotOf(head, lead)
167
+ const slot = recHeadSlotOf(built.rule, column)
168
+ if (leadSlot !== undefined && slot !== undefined && !fieldJoins(leadSlot, slot)) {
169
+ throw errors.new(
170
+ `rec ${name}: every rule derives the same head — the head column ${lead.name} is bound at ${renderFieldKind(leadSlot)} in rule 0 but at ${renderFieldKind(slot)} in this rule (a head column joins only class-equal slots; bare pairs only with bare)`
171
+ )
172
+ }
173
+ })
140
174
  }
141
175
  data.rules.push(built.rule)
142
176
  return makeRawRec<Name>(state, name, data)
@@ -147,7 +181,8 @@ function makeRawRec<Name extends string>(state: ProgramState, name: Name, data:
147
181
  }
148
182
 
149
183
  /**
150
- * The rec handles' trusted admission seam (the `refsComplete` pattern):
184
+ * The rec handles' trusted admission seam (the pattern's home is
185
+ * `isTypedScope` in query/lower.ts):
151
186
  * the checkable fact — the handle owns exactly the rec data it names — is
152
187
  * verified before the raw handle is admitted at its typed face.
153
188
  */
package/src/query/run.ts CHANGED
@@ -20,12 +20,10 @@ import * as errors from "@superbuilders/errors"
20
20
  import { handleOf } from "#marshal.ts"
21
21
  import type { FactValue, QueryParam, TaggedValue } from "#native.ts"
22
22
  import type { SelectColumn } from "#query/atom.ts"
23
+ import { ALLEN_ALL_BITS } from "#query/atom.ts"
23
24
  import { taggedCmpLiteral } from "#query/lower.ts"
24
25
  import type { ParamEntry } from "#query/scope.ts"
25
26
 
26
- /** The 13-bit Allen mask ceiling (`bumbledb/crates/bumbledb/src/allen.rs`: bits above the low 13 are unrepresentable). */
27
- const ALLEN_ALL_BITS = (1 << 13) - 1
28
-
29
27
  /** Tags one supplied mask-param value. */
30
28
  function wireMask(name: string, value: unknown): TaggedValue {
31
29
  if (typeof value !== "number" || !Number.isInteger(value) || value < 0 || value > ALLEN_ALL_BITS) {
@@ -19,8 +19,8 @@
19
19
  * inference ride.
20
20
  */
21
21
 
22
- import * as errors from "@superbuilders/errors"
23
22
  import type { AnyField, Infer } from "#fields.ts"
23
+ import { rosterOf } from "#fields.ts"
24
24
 
25
25
  /**
26
26
  * The runtime discriminant of query term values. Host literals (bigints,
@@ -110,57 +110,6 @@ function makeVar<const Name extends string>(name: Name): Var<Name> {
110
110
  return Object.freeze(value)
111
111
  }
112
112
 
113
- /** The record `makeVars` mints: one own frozen `Var<Name>` per requested name, each typed exactly. */
114
- type VarsRecord<Names extends string> = { readonly [N in Names]: Var<N> }
115
-
116
- /**
117
- * The trusted seam of the vars mint: every requested name reads back as an
118
- * own var term of exactly that name — verified before the record is
119
- * admitted at the {@link VarsRecord} type (a name riding the
120
- * object-protocol accessor instead of an own definition would fail exactly
121
- * this check).
122
- */
123
- function varsMinted<Names extends string>(
124
- record: Readonly<Record<string, unknown>>,
125
- names: readonly Names[]
126
- ): record is Readonly<Record<string, unknown>> & VarsRecord<Names> {
127
- return names.every(function varMinted(name) {
128
- if (!Object.hasOwn(record, name)) {
129
- return false
130
- }
131
- const value = record[name]
132
- return isTerm(value) && value[term] === "var" && value.name === name
133
- })
134
- }
135
-
136
- /**
137
- * Mints several variables at once — `const { service, w } = r.vars("service",
138
- * "w")`: tuple-to-object, each name typed exactly (`Var<"service">`),
139
- * inference identical to the one-at-a-time `r.var` spelling (one lowering,
140
- * two entry flavors). Each key is defined as an OWN property (a name like
141
- * `"__proto__"` is a record key like any other, never a prototype write),
142
- * and a duplicate name in one call is a construction error: each name mints
143
- * one variable — write it once and reuse the binding.
144
- */
145
- function makeVars<const Names extends readonly string[]>(...names: Names): VarsRecord<Names[number]> {
146
- const out: Record<string, unknown> = {}
147
- const seen = new Set<string>()
148
- for (const name of names) {
149
- if (seen.has(name)) {
150
- throw errors.new(
151
- `vars: duplicate name ${name} — each name mints one variable; write it once and reuse the binding`
152
- )
153
- }
154
- seen.add(name)
155
- Object.defineProperty(out, name, { value: makeVar(name), enumerable: true })
156
- }
157
- Object.freeze(out)
158
- if (!varsMinted<Names[number]>(out, names)) {
159
- throw errors.new("vars: variable minting incomplete")
160
- }
161
- return out
162
- }
163
-
164
113
  /** Builds one scalar-parameter term. */
165
114
  function makeParam<const Name extends string>(name: Name): Param<Name> {
166
115
  const value: Param<Name> = { [term]: "param", name }
@@ -279,8 +228,8 @@ function fieldJoins(a: ClassedField, b: ClassedField): boolean {
279
228
  const widthB = "width" in b.field ? b.field.width : undefined
280
229
  const elementA = "element" in a.field ? a.field.element : undefined
281
230
  const elementB = "element" in b.field ? b.field.element : undefined
282
- const rosterA = "closed" in a.field ? a.field.closed : undefined
283
- const rosterB = "closed" in b.field ? b.field.closed : undefined
231
+ const rosterA = rosterOf(a.field)
232
+ const rosterB = rosterOf(b.field)
284
233
  return (
285
234
  a.field.kind === b.field.kind &&
286
235
  a.class === b.class &&
@@ -300,8 +249,9 @@ function fieldJoins(a: ClassedField, b: ClassedField): boolean {
300
249
  function renderFieldKind(slot: ClassedField): string {
301
250
  const field = slot.field
302
251
  let base: string = field.kind
303
- if ("closed" in field) {
304
- base = `u64 referencing ${field.closed.name}`
252
+ const roster = rosterOf(field)
253
+ if (roster !== undefined) {
254
+ base = `u64 referencing ${roster.name}`
305
255
  }
306
256
  if (field.kind === "bytes") {
307
257
  base = `bytes<${field.width}>`
@@ -363,8 +313,7 @@ export type {
363
313
  SetParam,
364
314
  ShapeOf,
365
315
  UnionToIntersection,
366
- Var,
367
- VarsRecord
316
+ Var
368
317
  }
369
318
  export {
370
319
  fieldJoins,
@@ -375,7 +324,6 @@ export {
375
324
  makeParam,
376
325
  makeSetParam,
377
326
  makeVar,
378
- makeVars,
379
327
  renderFieldKind,
380
328
  term
381
329
  }