@bjornpagen/bumbledb 0.8.0 → 0.10.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bjornpagen/bumbledb",
3
- "version": "0.8.0",
3
+ "version": "0.10.0",
4
4
  "description": "Type-theoretic TypeScript SDK for the bumbledb embedded relational engine",
5
5
  "type": "module",
6
6
  "exports": {
@@ -40,9 +40,6 @@
40
40
  "dependencies": {
41
41
  "@superbuilders/errors": "^4.0.2"
42
42
  },
43
- "optionalDependencies": {
44
- "@bjornpagen/bumbledb-darwin-arm64": "0.8.0"
45
- },
46
43
  "devDependencies": {
47
44
  "@biomejs/biome": "^2.0.0-beta.5",
48
45
  "@types/node": "^26.1.0",
@@ -55,6 +52,9 @@
55
52
  "access": "public"
56
53
  },
57
54
  "private": false,
55
+ "optionalDependencies": {
56
+ "@bjornpagen/bumbledb-darwin-arm64": "0.10.0"
57
+ },
58
58
  "scripts": {
59
59
  "build": "node scripts/build.ts",
60
60
  "typecheck": "tsc --noEmit",
package/src/capacity.ts CHANGED
@@ -196,6 +196,20 @@ type UnitWindowBan<W extends CapacityWindow> = W["window"] extends {
196
196
  ? BannedWindow<"`{1..*}` on the unit instance says only what the bare containment says — write contained(source, target)">
197
197
  : unknown
198
198
 
199
+ /**
200
+ * The C18 dimension gate's ban row, unit instance (the engine's
201
+ * `CapacityDimensionMixing` twin, ruled 2026-07-24): a unit (count)
202
+ * window against a `duration()` bound counts facts against a span of
203
+ * time — a dimension error. Judged on the `capacity()` UNIT overload
204
+ * only: Duration weights pair with Duration-capable bounds, so the
205
+ * weighted overload takes the same window freely.
206
+ */
207
+ type UnitDimensionBan<W extends CapacityWindow> = W["window"] extends {
208
+ readonly hi: { readonly kind: "durationField" }
209
+ }
210
+ ? BannedWindow<"a count of facts bounded by a span of time mixes dimensions (C18) — weigh the source with weigh(duration(field)), or bound by a u64 field or literal">
211
+ : unknown
212
+
199
213
  /** The projected kind of one field of a face's source, read off the schema type. */
200
214
  type KindAt<S extends FaceSource, K extends string> =
201
215
  ProjectedShape<S, K> extends readonly [infer Kind, ...unknown[]] ? Kind : undefined
@@ -450,5 +464,14 @@ function duration<const F extends string>(field: F & PathBan<F>): DurationRef<F>
450
464
  return Object.freeze({ kind: "durationField", field: assertRowLocal(field, "Duration measure") }) as DurationRef<F>
451
465
  }
452
466
 
453
- export type { BoundsOnTarget, CapacityWeight, CapacityWindow, DurationRef, FieldRef, UnitWindowBan, WeightOnSource }
467
+ export type {
468
+ BoundsOnTarget,
469
+ CapacityWeight,
470
+ CapacityWindow,
471
+ DurationRef,
472
+ FieldRef,
473
+ UnitDimensionBan,
474
+ UnitWindowBan,
475
+ WeightOnSource
476
+ }
454
477
  export { duration, isCapacityWeight, isCapacityWindow, ref, unitWeight, weigh, within }
package/src/index.ts CHANGED
@@ -119,7 +119,7 @@ export type {
119
119
  Staleness,
120
120
  StatementKindTag
121
121
  } from "#native.ts"
122
- export type { Desc, SortKey } from "#order.ts"
122
+ export type { Desc, EngineOrderable, SortKey } from "#order.ts"
123
123
  export { by, desc } from "#order.ts"
124
124
 
125
125
  export type {
package/src/order.ts CHANGED
@@ -10,6 +10,16 @@
10
10
  * `by(...)` into one row-typed comparator. Cross-type cells cannot arise
11
11
  * within one column (one column, one domain), and the cell order is TOTAL
12
12
  * anyway (the type-rank wall), so the comparator never throws.
13
+ *
14
+ * ZERO keys is the identity key (ruled 2026-07-25): `by()` / `desc()` are
15
+ * the same two spellings over BARE scalar arrays (`bigint[]` of ids, map
16
+ * keys) — with no column to name, the value itself is the key, there is
17
+ * nothing to fold, and the comparator IS the result. One ordering
18
+ * vocabulary, no sibling names. The identity arms are typed to EXACTLY the
19
+ * engine-orderable roster ({@link EngineOrderable}) because a bare scalar
20
+ * carries no decoded-row provenance — the type wall must carry the
21
+ * orderability law itself, where the row arm's cells arrive under their
22
+ * columns' law-typed proof.
13
23
  */
14
24
 
15
25
  import type { FactValue } from "#native.ts"
@@ -23,6 +33,23 @@ interface Desc<K extends string> {
23
33
  /** One sort key: a bare column name (ascending — the punning spelling) or `desc(name)`. */
24
34
  type SortKey<K extends string> = K | Desc<K>
25
35
 
36
+ /**
37
+ * The engine-orderable scalar roster in the SDK's representation — `bigint`
38
+ * (U64/I64) and `boolean` (false < true — the strict 0/1 encoding IS the
39
+ * order), EXACTLY as `docs/architecture/10-data-model.md` § "Orderability,
40
+ * complete" admits it (ruled 2026-07-23, R3/R4), and nothing more:
41
+ * `string` is deliberately absent (intern ids are meaningless to order — a
42
+ * typed refusal, not a gap) and `number` is not an engine scalar at all.
43
+ * The identity comparators `by()`/`desc()` constrain on this alias, so
44
+ * sorting a `string[]` or `number[]` through them is a COMPILE error naming
45
+ * this roster. ONE OWNER for scalar ordering semantics: both identity arms
46
+ * route through the same cell order the row arm uses, whose bigint and
47
+ * boolean arms mirror the engine's U64/I64/Bool order exactly — a host
48
+ * sort over these values can never disagree with an engine order judgment
49
+ * (`Lt`-family, `Min`/`Max`) over the same column.
50
+ */
51
+ type EngineOrderable = bigint | boolean
52
+
26
53
  /**
27
54
  * The type-rank wall: boolean 0, bigint 1, string 2, bytes 3, interval 4.
28
55
  * One column carries one domain, so a mixed pair never arises from decoded
@@ -116,8 +143,38 @@ function cellCmp(left: FactValue, right: FactValue): number {
116
143
  return cellRank(left) - cellRank(right)
117
144
  }
118
145
 
119
- /** Marks one sort key DESCENDING — the single descending spelling (a bare name is already ascending). */
120
- function desc<const K extends string>(key: K): Desc<K> {
146
+ /**
147
+ * The ascending identity comparator `by()` returns — one value, minted
148
+ * once (`by() === by()`), {@link cellCmp} narrowed to the engine-orderable
149
+ * roster (the one owner of the cell order).
150
+ */
151
+ function identityAscending<T extends EngineOrderable>(left: T, right: T): number {
152
+ return cellCmp(left, right)
153
+ }
154
+
155
+ /** The descending identity comparator `desc()` returns — the same owner, sides flipped. */
156
+ function identityDescending<T extends EngineOrderable>(left: T, right: T): number {
157
+ return cellCmp(right, left)
158
+ }
159
+
160
+ /**
161
+ * The descending spelling — ONE name, both arities of one ordering
162
+ * vocabulary. `desc(name)` marks one sort key DESCENDING: plain data for
163
+ * {@link by} to fold (a bare name is already ascending). `desc()` — zero
164
+ * keys, the identity key (ruled 2026-07-25) — is the descending comparator
165
+ * over engine-orderable scalars themselves: a bare `bigint[]` or
166
+ * `boolean[]` has no column to name, so the value IS the key, there is
167
+ * nothing to fold, and the comparator is the result directly. The identity
168
+ * arm covers EXACTLY the {@link EngineOrderable} roster — `string` and
169
+ * `number` refuse at compile time, citing the orderability law
170
+ * (`10-data-model.md` § "Orderability, complete").
171
+ */
172
+ function desc(): <T extends EngineOrderable>(left: T, right: T) => number
173
+ function desc<const K extends string>(key: K): Desc<K>
174
+ function desc<const K extends string>(key?: K): Desc<K> | (<T extends EngineOrderable>(left: T, right: T) => number) {
175
+ if (key === undefined) {
176
+ return identityDescending
177
+ }
121
178
  const marker: Desc<K> = { key, desc: true }
122
179
  return Object.freeze(marker)
123
180
  }
@@ -130,12 +187,33 @@ function desc<const K extends string>(key: K): Desc<K> {
130
187
  * carries that proof here (parse-don't-validate). The generic RETURN is the
131
188
  * load-bearing trick: `rows.sort(by("rank"))` instantiates `Row` from the
132
189
  * array's own element type and checks the key set right there.
190
+ *
191
+ * ZERO keys is the identity key (ruled 2026-07-25): `by()` is the
192
+ * ascending comparator over engine-orderable scalars themselves —
193
+ * `ids.sort(by())` — for the bare arrays (`bigint[]`, map keys) the
194
+ * row-typed arm cannot reach because they have no column to name. One
195
+ * vocabulary, both arities. The identity arm is typed to EXACTLY the
196
+ * {@link EngineOrderable} roster: with no law-typed column standing proof
197
+ * over the values, the type wall carries the orderability law itself —
198
+ * `string` ordering is deliberately refused and `number` is not an engine
199
+ * scalar (`10-data-model.md` § "Orderability, complete"), so both refuse
200
+ * at compile time and a host sort can never disagree with an engine order
201
+ * judgment.
133
202
  */
203
+ function by(): <T extends EngineOrderable>(left: T, right: T) => number
134
204
  function by<const K extends string>(
135
205
  first: SortKey<K>,
136
206
  ...rest: ReadonlyArray<SortKey<K>>
137
- ): <Row extends Readonly<Record<K, FactValue>>>(left: Row, right: Row) => number {
138
- const entries = [first, ...rest].map(function normalizeKey(sortKey): { readonly key: K; readonly factor: 1 | -1 } {
207
+ ): <Row extends Readonly<Record<K, FactValue>>>(left: Row, right: Row) => number
208
+ function by<const K extends string>(
209
+ ...keys: ReadonlyArray<SortKey<K>>
210
+ ):
211
+ | (<T extends EngineOrderable>(left: T, right: T) => number)
212
+ | (<Row extends Readonly<Record<K, FactValue>>>(left: Row, right: Row) => number) {
213
+ if (keys.length === 0) {
214
+ return identityAscending
215
+ }
216
+ const entries = keys.map(function normalizeKey(sortKey): { readonly key: K; readonly factor: 1 | -1 } {
139
217
  if (typeof sortKey === "string") {
140
218
  return { key: sortKey, factor: 1 }
141
219
  }
@@ -152,5 +230,5 @@ function by<const K extends string>(
152
230
  }
153
231
  }
154
232
 
155
- export type { Desc, SortKey }
233
+ export type { Desc, EngineOrderable, SortKey }
156
234
  export { by, desc }
package/src/query/atom.ts CHANGED
@@ -46,7 +46,7 @@ import type {
46
46
  SetParam,
47
47
  ShapeOf
48
48
  } from "#query/scope.ts"
49
- import { inferred, isTerm } from "#query/scope.ts"
49
+ import { inferred, isTerm, term } from "#query/scope.ts"
50
50
  import type { FieldsShape } from "#relation.ts"
51
51
 
52
52
  /**
@@ -335,8 +335,8 @@ type EqRight = AnyVar | Param<string> | SetParam<string> | bigint | string | boo
335
335
  /** What `ne`'s right side accepts. */
336
336
  type NeRight = AnyVar | Param<string> | bigint | string | boolean | Uint8Array | IntervalValue
337
337
 
338
- /** One side of an order comparison: orderable terms only (the IR's comparison rules). */
339
- type OrderSide = AnyVar | Param<string> | Duration | bigint
338
+ /** One side of an order comparison: orderable terms only (the IR's comparison rules — bool orders, `false < true`, R3). */
339
+ type OrderSide = AnyVar | Param<string> | Duration | bigint | boolean
340
340
 
341
341
  /** The point side of `pointIn`. */
342
342
  type PointSide = AnyVar | Param<string> | bigint
@@ -350,14 +350,28 @@ function comparison<Op extends CmpKind, L, R, M>(op: Op, lhs: L, rhs: R, mask: M
350
350
  }
351
351
 
352
352
  /**
353
- * Rejects a comparison with no term side: it is constant-valued, the
354
- * engine's own validation refuses it, and the lowering has no anchored
355
- * position to type the literals by fail here with the same verdict.
353
+ * Whether a comparison side is a VARIABLE side in the engine's sense — a
354
+ * bound variable or the measure. A param is a constant at execution, so a
355
+ * comparison whose every side is a param or literal is constant-valued:
356
+ * the engine's `ConstantComparison` conviction
357
+ * (`bumbledb/crates/bumbledb/src/ir/validate/context.rs`,
358
+ * `comparison_shape`'s last arm).
359
+ */
360
+ function isVariableSide(value: unknown): boolean {
361
+ return isTerm(value) && (value[term] === "var" || value[term] === "duration")
362
+ }
363
+
364
+ /**
365
+ * Rejects a comparison with no variable side: it is constant-valued —
366
+ * params included, a param is a constant the moment it binds — the
367
+ * engine's own validation refuses it (`ConstantComparison`), and the
368
+ * lowering has no anchored position to type the literals by — fail here
369
+ * with the same verdict.
356
370
  */
357
371
  function assertTermSide(op: string, lhs: unknown, rhs: unknown): void {
358
- if (!isTerm(lhs) && !isTerm(rhs)) {
372
+ if (!isVariableSide(lhs) && !isVariableSide(rhs)) {
359
373
  throw errors.new(
360
- `${op}: a comparison without a variable or parameter side is constant-valued — write the query you mean`
374
+ `${op}: a comparison without a variable side is constant-valued (a parameter is a constant at execution) — write the query you mean`
361
375
  )
362
376
  }
363
377
  }
@@ -388,7 +402,7 @@ function order<Op extends "lt" | "le" | "gt" | "ge", const L extends OrderSide,
388
402
  return comparison(op, left, right, undefined)
389
403
  }
390
404
 
391
- /** Strict less-than (`ir::CmpOp::Lt`) — orderable sides only, never intervals/bytes/strings/bools. */
405
+ /** Strict less-than (`ir::CmpOp::Lt`) — orderable sides only (bool included: `false < true`, R3), never intervals/bytes/strings. */
392
406
  function lt<const L extends OrderSide, const R extends OrderSide>(left: L, right: R): Cmp<"lt", L, R> {
393
407
  return order("lt", left, right)
394
408
  }
@@ -531,34 +545,159 @@ function isRecTarget(value: MatchOwner | RecTarget): value is RecTarget {
531
545
  }
532
546
 
533
547
  /**
534
- * Whether a variable's OWN field is orderable (u64/i64). A CLOSED reference
548
+ * Whether a variable's OWN field is NUMERIC (u64/i64) the judgment the
549
+ * point side of `pointIn` and the `sum` input read: a point lives in the
550
+ * interval's element domain, and a quantifier is not an addition, so bool
551
+ * (orderable, never numeric) is exactly here refused. A CLOSED reference
535
552
  * is excluded even though its kind is `u64`: a vocabulary's declaration-id
536
553
  * order is an accident, not semantics (`docs/architecture/10-data-model.md`
537
554
  * § orderability), so every order-comparison and fold position refuses
538
- * closed-bound terms — this judgment is the one gate they all read, and the
539
- * construction-time validations in `#query/lower.ts` are its runtime twin.
555
+ * closed-bound terms — the construction-time validations in
556
+ * `#query/lower.ts` are that ban's runtime twin.
540
557
  */
541
- type OrderVarOk<V extends AnyVar> = V["field"] extends { readonly closed: ClosedRoster }
558
+ type NumericVarOk<V extends AnyVar> = V["field"] extends { readonly closed: ClosedRoster }
542
559
  ? false
543
560
  : V["field"]["kind"] extends "u64" | "i64"
544
561
  ? true
545
562
  : false
546
563
 
564
+ /**
565
+ * Whether a variable's OWN field is ORDERABLE (u64/i64/bool) — the one gate
566
+ * every order-comparison side, `min`/`max` input, and Arg key reads. Bool
567
+ * orders: `false < true`, the strict 0/1 encoding IS the order (ruled R3),
568
+ * exactly the engine's operand screen
569
+ * (`bumbledb/crates/bumbledb/src/ir/validate/context.rs`); bool has no
570
+ * closed variant, so the closed exclusion rides in {@link NumericVarOk}.
571
+ */
572
+ type OrderVarOk<V extends AnyVar> = V["field"]["kind"] extends "bool" ? true : NumericVarOk<V>
573
+
547
574
  /** Whether a variable's OWN field is interval-typed. */
548
575
  type IntervalVarOk<V extends AnyVar> = V["field"]["kind"] extends "interval" ? true : false
549
576
 
550
- /** One order-comparison side's judgment (off the term's own field). */
551
- type OrderSideOk<T> = T extends AnyVar
552
- ? OrderVarOk<T>
577
+ /**
578
+ * Whether a comparison has a VARIABLE side (a var or the measure) — the
579
+ * type-level twin of the engine's `ConstantComparison` rule: a param is a
580
+ * constant at execution, so param-vs-param and param-vs-literal are
581
+ * constant-valued whatever their domains ({@link assertTermSide} is the
582
+ * runtime wall behind this one).
583
+ */
584
+ type CmpVarSideOk<L, R> = L extends AnyVar | Duration ? true : R extends AnyVar | Duration ? true : false
585
+
586
+ /**
587
+ * One order side's DOMAIN — the type-level twin of the engine's resolved
588
+ * operand type (`ir/validate/context.rs`, `classify`'s `Ord*` arms): a
589
+ * variable names its own field kind, the measure is u64 by definition (and
590
+ * never meets another measure — `DurationBothSides`), a bigint literal
591
+ * spans the integer domains, a param is typed by its sibling (`"open"`).
592
+ * `never` marks a side that is itself unorderable: interval/bytes/string
593
+ * vars (the operand screen), closed-bound vars (R4), a measure over a
594
+ * non-interval var.
595
+ */
596
+ type OrderDomain<T> = T extends AnyVar
597
+ ? OrderVarOk<T> extends true
598
+ ? T["field"]["kind"]
599
+ : never
553
600
  : T extends Duration<infer V extends AnyVar>
554
- ? IntervalVarOk<V>
555
- : true
601
+ ? IntervalVarOk<V> extends true
602
+ ? "duration"
603
+ : never
604
+ : T extends Param<string>
605
+ ? "open"
606
+ : T extends bigint
607
+ ? "integer"
608
+ : T extends boolean
609
+ ? "bool"
610
+ : never
556
611
 
557
- /** One point side's judgment. */
558
- type PointSideOk<T> = T extends AnyVar ? OrderVarOk<T> : true
612
+ /**
613
+ * Whether two order domains may MEET under one operator — the engine's
614
+ * same-type rule judged pairwise, not per side: bool meets only bool (a
615
+ * bigint literal against a bool var is exactly `check_const`'s
616
+ * conviction; R3 made bool orderable, never cross-orderable), u64 and i64
617
+ * never meet, the measure's sibling must live in u64 (`OrdMeasureVar`:
618
+ * scalar ≠ U64 is `IllegalComparison`), two measures never meet, and an
619
+ * open side (param, integer literal) takes whatever its sibling names.
620
+ */
621
+ type OrderDomainsOk<A, B> = [A] extends [never]
622
+ ? false
623
+ : [B] extends [never]
624
+ ? false
625
+ : A extends "open"
626
+ ? true
627
+ : B extends "open"
628
+ ? true
629
+ : A extends "duration"
630
+ ? B extends "u64" | "integer"
631
+ ? true
632
+ : false
633
+ : B extends "duration"
634
+ ? A extends "u64" | "integer"
635
+ ? true
636
+ : false
637
+ : A extends "bool"
638
+ ? B extends "bool"
639
+ ? true
640
+ : false
641
+ : B extends "bool"
642
+ ? false
643
+ : A extends "integer"
644
+ ? true
645
+ : B extends "integer"
646
+ ? true
647
+ : A extends B
648
+ ? true
649
+ : false
650
+
651
+ /** The whole order comparison's judgment: a variable side exists, and the sides' domains meet. */
652
+ type OrderPairOk<L, R> = CmpVarSideOk<L, R> extends true ? OrderDomainsOk<OrderDomain<L>, OrderDomain<R>> : false
559
653
 
560
- /** One interval side's judgment. */
561
- type IntervalSideOk<T> = T extends AnyVar ? IntervalVarOk<T> : true
654
+ /**
655
+ * An interval side's ELEMENT domain: a variable names its field's element
656
+ * (`never` on a non-interval var), a param or bare interval literal is
657
+ * `"open"` — no type exists to name its element at this tier; the engine
658
+ * anchors it at prepare.
659
+ */
660
+ type IntervalElementDomain<T> = T extends AnyVar
661
+ ? T["field"] extends { readonly kind: "interval"; readonly element: infer E extends "u64" | "i64" }
662
+ ? E
663
+ : never
664
+ : "open"
665
+
666
+ /**
667
+ * The point side's domain — a point lives in the interval's element
668
+ * domain: a numeric var names its kind (never bool, never closed), a
669
+ * param or bigint literal is open.
670
+ */
671
+ type PointDomain<T> = T extends AnyVar ? (NumericVarOk<T> extends true ? T["field"]["kind"] : never) : "open"
672
+
673
+ /** Whether two element domains meet: both legal, and equal unless one is open. */
674
+ type ElementMeets<A, B> = [A] extends [never]
675
+ ? false
676
+ : [B] extends [never]
677
+ ? false
678
+ : A extends "open"
679
+ ? true
680
+ : B extends "open"
681
+ ? true
682
+ : A extends B
683
+ ? true
684
+ : false
685
+
686
+ /**
687
+ * The whole `pointIn` judgment (operands already sealed interval-left):
688
+ * the point's domain must BE the interval's element domain — `pointIn(t,
689
+ * w)` with an i64-typed `t` against `interval(u64)` is the engine's
690
+ * `IllegalComparison`, judged here first.
691
+ */
692
+ type PointInPairOk<L, R> =
693
+ CmpVarSideOk<L, R> extends true ? ElementMeets<IntervalElementDomain<L>, PointDomain<R>> : false
694
+
695
+ /**
696
+ * The whole `allen` judgment: two interval sides of ONE element domain
697
+ * (Q1 — widths meet freely, u64-vs-i64 stays illegal), judged pairwise.
698
+ */
699
+ type AllenPairOk<L, R> =
700
+ CmpVarSideOk<L, R> extends true ? ElementMeets<IntervalElementDomain<L>, IntervalElementDomain<R>> : false
562
701
 
563
702
  /** The `eq`/`ne` judgment: var-var joins by mint slot; var-literal is exact-typed by the var's own field. */
564
703
  type EqOk<Classes extends SchemaClasses, L, R> = L extends AnyVar
@@ -632,10 +771,13 @@ type NotIdbOk<Classes extends SchemaClasses, Head, B> =
632
771
 
633
772
  /**
634
773
  * One condition's judgment — the type-level twin of the engine's comparison
635
- * roster: class-equal joins (off the mint slots), orderable order sides (an
636
- * interval var under a non-`pointIn` op is exactly here refused),
637
- * kind-correct `pointIn`/`allen` sides, and negated-atom class safety. The
638
- * leading `[AnyTreeChild] extends [C]` arm is the recursion's base case.
774
+ * roster: class-equal joins (off the mint slots), PAIRWISE-judged order
775
+ * domains (an interval var under a non-`pointIn` op is refused per side;
776
+ * bool-vs-numeric, u64-vs-i64, and measure-vs-non-u64 are refused as
777
+ * pairs the engine's same-type rule), element-matched `pointIn`/`allen`
778
+ * pairs, the no-variable-side (constant comparison) rule, and
779
+ * negated-atom class safety. The leading `[AnyTreeChild] extends [C]` arm
780
+ * is the recursion's base case.
639
781
  */
640
782
  type CondOkBool<Classes extends SchemaClasses, C> = [AnyTreeChild] extends [C]
641
783
  ? true
@@ -643,17 +785,11 @@ type CondOkBool<Classes extends SchemaClasses, C> = [AnyTreeChild] extends [C]
643
785
  ? Op extends "eq" | "ne"
644
786
  ? EqOk<Classes, L, R>
645
787
  : Op extends "lt" | "le" | "gt" | "ge"
646
- ? [OrderSideOk<L>, OrderSideOk<R>] extends [true, true]
647
- ? true
648
- : false
788
+ ? OrderPairOk<L, R>
649
789
  : Op extends "pointIn"
650
- ? [IntervalSideOk<L>, PointSideOk<R>] extends [true, true]
651
- ? true
652
- : false
790
+ ? PointInPairOk<L, R>
653
791
  : Op extends "allen"
654
- ? [IntervalSideOk<L>, IntervalSideOk<R>] extends [true, true]
655
- ? true
656
- : false
792
+ ? AllenPairOk<L, R>
657
793
  : false
658
794
  : C extends Tree<infer Ch extends readonly AnyTreeChild[]>
659
795
  ? false extends CondOkBool<Classes, Ch[number]>
@@ -677,8 +813,18 @@ type EqParams<L, R> = L extends AnyVar
677
813
  : never
678
814
  : never
679
815
 
680
- /** An order side's params contribution (order params are always `bigint`). */
681
- type OrderSideParams<T> = T extends Param<infer P extends string> ? { readonly [Q in P]: bigint } : never
816
+ /**
817
+ * An order side's params contribution, typed by the SIBLING side the
818
+ * runtime anchors a param to its sibling variable's field, so a bool var
819
+ * orders against a `boolean` param; every var-less pairing stays `bigint`.
820
+ */
821
+ type OrderSideParams<T, Sib> =
822
+ T extends Param<infer P extends string>
823
+ ? { readonly [Q in P]: Sib extends AnyVar ? Infer<Sib["field"]> : bigint }
824
+ : never
825
+
826
+ /** The point side's params contribution (a point is always `bigint`). */
827
+ type PointParams<T> = T extends Param<infer P extends string> ? { readonly [Q in P]: bigint } : never
682
828
 
683
829
  /** An interval side's params contribution. */
684
830
  type IntervalSideParams<T> = T extends Param<infer P extends string> ? { readonly [Q in P]: IntervalValue } : never
@@ -697,9 +843,9 @@ type CondParams<C> = [AnyTreeChild] extends [C]
697
843
  ? Op extends "eq" | "ne"
698
844
  ? EqParams<L, R>
699
845
  : Op extends "lt" | "le" | "gt" | "ge"
700
- ? OrderSideParams<L> | OrderSideParams<R>
846
+ ? OrderSideParams<L, R> | OrderSideParams<R, L>
701
847
  : Op extends "pointIn"
702
- ? IntervalSideParams<L> | OrderSideParams<R>
848
+ ? IntervalSideParams<L> | PointParams<R>
703
849
  : Op extends "allen"
704
850
  ? IntervalSideParams<L> | IntervalSideParams<R> | MaskParams<M>
705
851
  : never
@@ -749,6 +895,7 @@ export type {
749
895
  MatchShape,
750
896
  NotAtom,
751
897
  NotIdbAtom,
898
+ NumericVarOk,
752
899
  OrderSide,
753
900
  OrderVarOk,
754
901
  ParamUse,
package/src/query/find.ts CHANGED
@@ -21,7 +21,7 @@
21
21
 
22
22
  import type { Infer } from "#fields.ts"
23
23
  import type { SchemaClasses } from "#law.ts"
24
- import type { IntervalVarOk, OrderVarOk } from "#query/atom.ts"
24
+ import type { IntervalVarOk, NumericVarOk, OrderVarOk } from "#query/atom.ts"
25
25
  import type { AnyVar, Duration, MintSlotOf } from "#query/scope.ts"
26
26
 
27
27
  /** One aggregate operator name of the find vocabulary. */
@@ -71,20 +71,21 @@ function countDistinct<const V extends AnyVar>(over: V): Agg<"countDistinct", V>
71
71
  }
72
72
 
73
73
  /**
74
- * Exact checked sum over an orderable (u64/i64) variable — wide
75
- * accumulator, one finalize range check; overflow is the engine's typed
76
- * runtime error — or over the measure (`r.sum(r.duration(w))`).
74
+ * Exact checked sum over a NUMERIC (u64/i64) variable — wide accumulator,
75
+ * one finalize range check; overflow is the engine's typed runtime error —
76
+ * or over the measure (`r.sum(r.duration(w))`). Bool stays refused: a
77
+ * quantifier is not an addition (R3).
77
78
  */
78
79
  function sum<const O extends AnyVar | Duration>(over: O): Agg<"sum", O> {
79
80
  return aggregate("sum", over, undefined)
80
81
  }
81
82
 
82
- /** Minimum over an orderable variable or the measure (orderable types only). */
83
+ /** Minimum over an orderable variable (u64/i64/bool — over bool, `Min` is the ALL quantifier; R3) or the measure. */
83
84
  function min<const O extends AnyVar | Duration>(over: O): Agg<"min", O> {
84
85
  return aggregate("min", over, undefined)
85
86
  }
86
87
 
87
- /** Maximum over an orderable variable or the measure (orderable types only). */
88
+ /** Maximum over an orderable variable (u64/i64/bool — over bool, `Max` is the ANY quantifier; R3) or the measure. */
88
89
  function max<const O extends AnyVar | Duration>(over: O): Agg<"max", O> {
89
90
  return aggregate("max", over, undefined)
90
91
  }
@@ -116,7 +117,22 @@ function pack<const V extends AnyVar>(over: V): Agg<"pack", V> {
116
117
  return aggregate("pack", over, undefined)
117
118
  }
118
119
 
119
- /** A fold input's judgment: an orderable variable, or the measure of an interval variable. */
120
+ /**
121
+ * A `sum` input's judgment: a NUMERIC variable — Sum over bool stays
122
+ * refused, a quantifier is not an addition (R3) — or the measure of an
123
+ * interval variable.
124
+ */
125
+ type SumOverOk<O> = O extends AnyVar
126
+ ? NumericVarOk<O>
127
+ : O extends Duration<infer V extends AnyVar>
128
+ ? IntervalVarOk<V>
129
+ : false
130
+
131
+ /**
132
+ * A `min`/`max` input's judgment: an ORDERABLE variable — bool folds:
133
+ * `Max` is Any and `Min` is All, the two quantifiers as the 0/1 encoding's
134
+ * extremes (R3) — or the measure of an interval variable.
135
+ */
120
136
  type FoldOverOk<O> = O extends AnyVar
121
137
  ? OrderVarOk<O>
122
138
  : O extends Duration<infer V extends AnyVar>
@@ -136,15 +152,17 @@ type FindEntryOk<E> = E extends AnyVar
136
152
  ? true
137
153
  : E extends Agg<"countDistinct", AnyVar>
138
154
  ? true
139
- : E extends Agg<"sum" | "min" | "max", infer O>
140
- ? FoldOverOk<O>
141
- : E extends Agg<"argMax" | "argMin", AnyVar, infer K extends AnyVar>
142
- ? OrderVarOk<K> extends true
143
- ? true
144
- : false
145
- : E extends Agg<"pack", infer V extends AnyVar>
146
- ? IntervalVarOk<V>
147
- : false
155
+ : E extends Agg<"sum", infer O>
156
+ ? SumOverOk<O>
157
+ : E extends Agg<"min" | "max", infer O>
158
+ ? FoldOverOk<O>
159
+ : E extends Agg<"argMax" | "argMin", AnyVar, infer K extends AnyVar>
160
+ ? OrderVarOk<K> extends true
161
+ ? true
162
+ : false
163
+ : E extends Agg<"pack", infer V extends AnyVar>
164
+ ? IntervalVarOk<V>
165
+ : false
148
166
 
149
167
  /** The validated find record (intersect with the inferred entries — errors land on the offending key). */
150
168
  type CheckFind<F extends FindShape> = {
@@ -1327,7 +1327,12 @@ function renderParamAnchor(roster: ClosedRoster | undefined): string {
1327
1327
  * Folds every rule's param uses (recs in declaration order first, output
1328
1328
  * rules last — exactly the lowering walk) into the query's registry: first
1329
1329
  * use mints the dense `ParamId`, the first FIELD-ANCHORED use types the
1330
- * wire, and one name keeps one shape AND one closedness.
1330
+ * wire, and one name keeps one shape AND one closedness. The orderable
1331
+ * ban needs no registry arm: an order use always anchors its SIBLING's
1332
+ * domain (the no-variable-side spelling is refused at the comparison
1333
+ * constructor), so a closed-anchored param under an order op dies at the
1334
+ * one-domain wall here — and an order use whose sibling is itself
1335
+ * closed-bound dies at the comparison's own var-side wall first.
1331
1336
  */
1332
1337
  function paramRegistryOf(recs: readonly RecData[], rules: readonly RuleData[]): readonly ParamEntry[] {
1333
1338
  const order: string[] = []
@@ -1338,7 +1343,6 @@ function paramRegistryOf(recs: readonly RecData[], rules: readonly RuleData[]):
1338
1343
  anchor: ParamEntry["anchor"]
1339
1344
  op: ParamEntry["op"]
1340
1345
  members: readonly string[] | undefined
1341
- orderOp: "lt" | "le" | "gt" | "ge" | "pointIn" | undefined
1342
1346
  }
1343
1347
  >()
1344
1348
  function fold(uses: readonly ParamUse[]): void {
@@ -1350,8 +1354,7 @@ function paramRegistryOf(recs: readonly RecData[], rules: readonly RuleData[]):
1350
1354
  shape: use.shape,
1351
1355
  anchor: use.anchor,
1352
1356
  op: use.op,
1353
- members: use.members,
1354
- orderOp: isOrderOp(use.op) ? use.op : undefined
1357
+ members: use.members
1355
1358
  })
1356
1359
  continue
1357
1360
  }
@@ -1378,9 +1381,6 @@ function paramRegistryOf(recs: readonly RecData[], rules: readonly RuleData[]):
1378
1381
  existing.anchor = use.anchor
1379
1382
  existing.op = use.op
1380
1383
  }
1381
- if (existing.orderOp === undefined && isOrderOp(use.op)) {
1382
- existing.orderOp = use.op
1383
- }
1384
1384
  }
1385
1385
  }
1386
1386
  for (const rec of recs) {
@@ -1397,10 +1397,6 @@ function paramRegistryOf(recs: readonly RecData[], rules: readonly RuleData[]):
1397
1397
  if (entry === undefined) {
1398
1398
  throw errors.new(`query param ${name} lost its registry entry`)
1399
1399
  }
1400
- const anchorRoster = anchorRosterOf(entry.anchor)
1401
- if (entry.orderOp !== undefined && anchorRoster !== undefined) {
1402
- throw closedOrderError(`query param ${name}`, `its ${entry.orderOp} use's anchor`, anchorRoster.name)
1403
- }
1404
1400
  /**
1405
1401
  * A membership array's handle names are program constants, so the
1406
1402
  * entry stores the resolved IMAGE: each name rides the one