@bjornpagen/bumbledb 0.7.0 → 0.8.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 (53) hide show
  1. package/COOKBOOK.md +105 -3
  2. package/README.md +3 -3
  3. package/dist/capacity.d.ts +310 -0
  4. package/dist/capacity.d.ts.map +1 -0
  5. package/dist/capacity.js +138 -0
  6. package/dist/capacity.js.map +1 -0
  7. package/dist/db.d.ts +5 -3
  8. package/dist/db.d.ts.map +1 -1
  9. package/dist/db.js +8 -8
  10. package/dist/db.js.map +1 -1
  11. package/dist/face.d.ts +1 -1
  12. package/dist/face.d.ts.map +1 -1
  13. package/dist/face.js.map +1 -1
  14. package/dist/index.d.ts +7 -7
  15. package/dist/index.d.ts.map +1 -1
  16. package/dist/index.js +3 -3
  17. package/dist/index.js.map +1 -1
  18. package/dist/law.d.ts +3 -2
  19. package/dist/law.d.ts.map +1 -1
  20. package/dist/law.js +1 -1
  21. package/dist/law.js.map +1 -1
  22. package/dist/lower.d.ts.map +1 -1
  23. package/dist/lower.js +3 -2
  24. package/dist/lower.js.map +1 -1
  25. package/dist/native.d.ts +6 -3
  26. package/dist/native.d.ts.map +1 -1
  27. package/dist/native.js.map +1 -1
  28. package/dist/schema.js +1 -1
  29. package/dist/schema.js.map +1 -1
  30. package/dist/spec.d.ts +76 -23
  31. package/dist/spec.d.ts.map +1 -1
  32. package/dist/spec.js +39 -7
  33. package/dist/spec.js.map +1 -1
  34. package/dist/statements.d.ts +42 -25
  35. package/dist/statements.d.ts.map +1 -1
  36. package/dist/statements.js +81 -19
  37. package/dist/statements.js.map +1 -1
  38. package/package.json +2 -2
  39. package/src/capacity.ts +454 -0
  40. package/src/db.ts +13 -11
  41. package/src/face.ts +1 -0
  42. package/src/index.ts +19 -8
  43. package/src/law.ts +3 -2
  44. package/src/lower.ts +3 -2
  45. package/src/native.ts +6 -3
  46. package/src/schema.ts +1 -1
  47. package/src/spec.ts +91 -25
  48. package/src/statements.ts +160 -34
  49. package/dist/count.d.ts +0 -102
  50. package/dist/count.d.ts.map +0 -1
  51. package/dist/count.js +0 -115
  52. package/dist/count.js.map +0 -1
  53. package/src/count.ts +0 -211
package/src/db.ts CHANGED
@@ -117,8 +117,10 @@ interface OffendingFact<Rels extends SchemaRelations> {
117
117
  * orientation (identical strings; the engine's `render.rs` renders each
118
118
  * partner of a mirrored pair as the `==` spelling, never a bare `<=`
119
119
  * direction). `direction` (`sourceUnsatisfied` | `targetRequired`) and
120
- * `count` are the containment/window form payloads, passed through from
121
- * the engine VERBATIM `direction` is relative to the violated SLOT's
120
+ * `measure` are the containment/capacity form payloads, passed through
121
+ * from the engine VERBATIM (`measure` the capacity form's witnessed group
122
+ * total — u128-wide, crossing whole as bigint, C3) — `direction` is
123
+ * relative to the violated SLOT's
122
124
  * own orientation, so for a `mirrors` statement it alone cannot say which
123
125
  * side of the `==` was violated: the slot identity is carried by
124
126
  * `orientation`, present exactly for `mirrors` slots — `written` is the
@@ -131,7 +133,7 @@ interface Violation<Rels extends SchemaRelations> {
131
133
  readonly canonical: string
132
134
  readonly direction?: "sourceUnsatisfied" | "targetRequired"
133
135
  readonly orientation?: "written" | "mirrored"
134
- readonly count?: bigint
136
+ readonly measure?: bigint
135
137
  readonly facts: readonly OffendingFact<Rels>[]
136
138
  }
137
139
 
@@ -536,10 +538,10 @@ function impliedKeyEntries(theory: AnySchema): StatementEntry[] {
536
538
  }
537
539
 
538
540
  /**
539
- * One declared statement's materialized slots: a key or window occupies
540
- * one, a `mirrors` occupies two adjacent slots (the engine lowers `==` to
541
- * two containments, `source <= target` first), both owned by the one SDK
542
- * value.
541
+ * One declared statement's materialized slots: a key or capacity statement
542
+ * occupies one, a `mirrors` occupies two adjacent slots (the engine lowers
543
+ * `==` to two containments, `source <= target` first), both owned by the
544
+ * one SDK value.
543
545
  */
544
546
  function declaredEntries(statement: Statement): StatementEntry[] {
545
547
  const data = statement.data
@@ -562,8 +564,8 @@ function declaredEntries(statement: Statement): StatementEntry[] {
562
564
  }
563
565
  return [{ kind: "containment", statement, key: undefined }]
564
566
  }
565
- case "window": {
566
- return [{ kind: "cardinality", statement, key: undefined }]
567
+ case "capacity": {
568
+ return [{ kind: "capacity", statement, key: undefined }]
567
569
  }
568
570
  }
569
571
  }
@@ -889,7 +891,7 @@ function openDb<Rels extends SchemaRelations>(handle: DbHandle, theory: Schema<R
889
891
  canonical: wire.canonical,
890
892
  direction: wire.direction,
891
893
  orientation: orientationOf(entry.reversed),
892
- count: wire.count,
894
+ measure: wire.measure,
893
895
  facts: Object.freeze(wire.facts.map(offendingFactOf))
894
896
  })
895
897
  }
@@ -912,7 +914,7 @@ function openDb<Rels extends SchemaRelations>(handle: DbHandle, theory: Schema<R
912
914
  )
913
915
  }
914
916
  if (entry.kind !== "functionality" || entry.key === undefined) {
915
- throw errors.new("keyed get takes a key() statement — containments and windows key nothing")
917
+ throw errors.new("keyed get takes a key() statement — containments and capacity statements key nothing")
916
918
  }
917
919
  if (entry.key.owner !== relation.name) {
918
920
  throw errors.new(
package/src/face.ts CHANGED
@@ -322,6 +322,7 @@ export type {
322
322
  FaceShapes,
323
323
  FaceSource,
324
324
  OwnerOf,
325
+ ProjectedShape,
325
326
  SameArity,
326
327
  SameShapes
327
328
  }
package/src/index.ts CHANGED
@@ -25,6 +25,16 @@
25
25
  * `.slice`). The raw native bridge is not exported.
26
26
  */
27
27
 
28
+ export type {
29
+ BoundsOnTarget,
30
+ CapacityWeight,
31
+ CapacityWindow,
32
+ DurationRef,
33
+ FieldRef,
34
+ UnitWindowBan,
35
+ WeightOnSource
36
+ } from "#capacity.ts"
37
+ export { duration, ref, weigh, within } from "#capacity.ts"
28
38
  export type {
29
39
  AnyClosed,
30
40
  AnySelectedClosed,
@@ -40,8 +50,6 @@ export type {
40
50
  SelectedClosed
41
51
  } from "#closed.ts"
42
52
  export { closed } from "#closed.ts"
43
- export type { Count } from "#count.ts"
44
- export { atLeast, atMost, between, exactly, none } from "#count.ts"
45
53
  export type {
46
54
  Abandon,
47
55
  AbandonedArm,
@@ -81,6 +89,7 @@ export type {
81
89
  FaceShapes,
82
90
  FaceSource,
83
91
  OwnerOf,
92
+ ProjectedShape,
84
93
  SameArity,
85
94
  SameShapes
86
95
  } from "#face.ts"
@@ -181,6 +190,8 @@ export { relation } from "#relation.ts"
181
190
  export type { AnySchema, Schema, SchemaRelation, SchemaRelations } from "#schema.ts"
182
191
  export { schema } from "#schema.ts"
183
192
  export type {
193
+ CapacityBoundSpec,
194
+ CapacityWindowSpec,
184
195
  FieldSpec,
185
196
  LiteralSetSpec,
186
197
  LiteralSpec,
@@ -191,17 +202,17 @@ export type {
191
202
  StatementSpec,
192
203
  ValueSpec,
193
204
  ValueTypeSpec,
194
- WindowSpec
205
+ WeightSpec
195
206
  } from "#spec.ts"
196
- export { renderLiteral, renderLiteralSet, renderWindow } from "#spec.ts"
207
+ export { renderCapacityBound, renderCapacityWindow, renderLiteral, renderLiteralSet, renderWeight } from "#spec.ts"
197
208
  export type {
209
+ CapacityData,
210
+ CapacityStatement,
198
211
  ContainedStatement,
199
212
  ContainmentData,
200
213
  KeyData,
201
214
  KeyStatement,
202
215
  Statement,
203
- StatementData,
204
- WindowData,
205
- WindowStatement
216
+ StatementData
206
217
  } from "#statements.ts"
207
- export { contained, key, mirrors, renderStatement, window } from "#statements.ts"
218
+ export { capacity, contained, key, mirrors, renderStatement } from "#statements.ts"
package/src/law.ts CHANGED
@@ -33,7 +33,7 @@
33
33
  *
34
34
  * Every paired face of the statement tuple unions its positionwise field
35
35
  * slots: containment (ψ-selected targets included — a selection changes
36
- * pairing not at all), the `==` bijection, and window source/target pairs.
36
+ * pairing not at all), the `==` bijection, and capacity source/target pairs.
37
37
  * `key()` statements pair nothing (an FD constrains one relation's own
38
38
  * rows; it identifies no carriers).
39
39
  *
@@ -141,7 +141,8 @@ type ZipCoords<
141
141
 
142
142
  /**
143
143
  * One statement's slot pairs: containments (bidirectional included — pair
144
- * unions are symmetric) and windows pair their two faces positionwise;
144
+ * unions are symmetric) and capacity statements pair their two faces
145
+ * positionwise;
145
146
  * `key()` pairs nothing. A widened face (owner name or projection no
146
147
  * longer literal) contributes nothing — the runtime map stays complete.
147
148
  */
package/src/lower.ts CHANGED
@@ -98,10 +98,11 @@ function lowerStatement(statement: Statement): StatementSpec {
98
98
  target: lowerFace(data.target),
99
99
  bidirectional: data.bidirectional
100
100
  }
101
- case "window":
101
+ case "capacity":
102
102
  return {
103
- kind: "cardinality",
103
+ kind: "capacity",
104
104
  target: lowerFace(data.target),
105
+ weight: data.weight,
105
106
  window: data.window,
106
107
  source: lowerFace(data.source)
107
108
  }
package/src/native.ts CHANGED
@@ -175,7 +175,7 @@ type ConditionTreeIr =
175
175
  | { readonly kind: "or"; readonly children: readonly ConditionTreeIr[] }
176
176
 
177
177
  /** A statement's form tag. */
178
- type StatementKindTag = "functionality" | "containment" | "cardinality"
178
+ type StatementKindTag = "functionality" | "containment" | "capacity"
179
179
 
180
180
  /** One field's name, dense id, and structural type. */
181
181
  interface ManifestField {
@@ -231,14 +231,17 @@ interface ViolationFact {
231
231
  * One violated statement of a rejected commit, rendered to plain data: the
232
232
  * statement id (materialized order), form tag, CANONICAL spelling (the
233
233
  * engine's one renderer — a bijection on legal statements, paste-back-able),
234
- * the form's direction/count payloads, and the decoded offending facts.
234
+ * the form's direction/measure payloads, and the decoded offending facts.
235
+ * `measure` is the capacity form's witnessed group total — the engine
236
+ * accumulates in u128 and the value crosses WHOLE as bigint (C3:
237
+ * truncation is unrepresentable).
235
238
  */
236
239
  interface Violation {
237
240
  readonly statementId: number
238
241
  readonly kind: StatementKindTag
239
242
  readonly canonical: string
240
243
  readonly direction?: "sourceUnsatisfied" | "targetRequired"
241
- readonly count?: bigint
244
+ readonly measure?: bigint
242
245
  readonly facts: readonly ViolationFact[]
243
246
  }
244
247
 
package/src/schema.ts CHANGED
@@ -306,7 +306,7 @@ function schema<const Rels extends SchemaRelations, const Stmts extends readonly
306
306
  */
307
307
  if (!isStatement(statement)) {
308
308
  throw errors.new(
309
- `schema ${name}: a statement is minted only by key/contained/mirrors/window — a structural literal skips the construction-time arity and roster walls`
309
+ `schema ${name}: a statement is minted only by key/contained/mirrors/capacity — a structural literal skips the construction-time arity and roster walls`
310
310
  )
311
311
  }
312
312
  const rendered = renderStatement(statement)
package/src/spec.ts CHANGED
@@ -72,10 +72,10 @@ type LiteralSetSpec =
72
72
  | { readonly kind: "many"; readonly literals: readonly LiteralSpec[] }
73
73
 
74
74
  /**
75
- * One side of a containment or window: `R(fields… | field == literal…)`,
76
- * all names. `projection` is π in the statement's written order (positional
77
- * pairing with the other side); `selection` is σ as (field, literal-or-set)
78
- * pairs, read conjunctively.
75
+ * One side of a containment or capacity statement:
76
+ * `R(fields… | field == literal…)`, all names. `projection` is π in the
77
+ * statement's written order (positional pairing with the other side);
78
+ * `selection` is σ as (field, literal-or-set) pairs, read conjunctively.
79
79
  */
80
80
  interface SideSpec {
81
81
  readonly relation: string
@@ -84,16 +84,43 @@ interface SideSpec {
84
84
  }
85
85
 
86
86
  /**
87
- * A cardinality window's bounds the canonical-utterance law's surviving
88
- * spellings only, since the SDK's `Count` constructors make every banned
89
- * spelling unwritable or a construction error: `exact` is `{n}` (`{0}` the
90
- * exclusion), `range` is `{lo..hi}` with lo < hi, `floor` is `{lo..*}` with
91
- * lo 2.
87
+ * One capacity bound: a non-negative literal, a u64 field of the TARGET
88
+ * row (the dependent bound per-group capacity read at judge time), or
89
+ * the interval-measure of a TARGET-row field (`Duration(span)`). Names,
90
+ * not ids the spec is the name-level wire; the engine resolves bound
91
+ * names against the target's FULL roster (C1), never the projection.
92
92
  */
93
- type WindowSpec =
94
- | { readonly kind: "exact"; readonly n: bigint }
95
- | { readonly kind: "range"; readonly lo: bigint; readonly hi: bigint }
96
- | { readonly kind: "floor"; readonly lo: bigint }
93
+ type CapacityBoundSpec =
94
+ | { readonly kind: "lit"; readonly value: bigint }
95
+ | { readonly kind: "field"; readonly field: string }
96
+ | { readonly kind: "durationField"; readonly field: string }
97
+
98
+ /**
99
+ * A capacity statement's weight — a TOTAL sum (C4: `unit` is a case, not
100
+ * an absence): the count instance (`unit`), a u64 field of the SOURCE row
101
+ * (`field`), or a SOURCE-row interval's measure (`durationField`). The
102
+ * wire always carries it — a unit statement crosses as `{ kind: "unit" }`,
103
+ * never by omission.
104
+ */
105
+ type WeightSpec =
106
+ | { readonly kind: "unit" }
107
+ | { readonly kind: "field"; readonly field: string }
108
+ | { readonly kind: "durationField"; readonly field: string }
109
+
110
+ /**
111
+ * A capacity statement's window — the canonical-utterance law's surviving
112
+ * spellings, per-aggregate where weight-sensitive (design § 6), since the
113
+ * SDK's `within()` mint makes every banned spelling unwritable or a
114
+ * construction error: `exact` is `{n}` (`{0}` the exclusion on the unit
115
+ * instance, "total is zero" on a weighted one), `range` is `{lo..hi}` with
116
+ * lo < hi (`{0..hi}` the canonical ceiling; the hi slot admits a dependent
117
+ * bound — C6: hi only), `floor` is `{lo..*}` (`{1..*}` legal on weighted
118
+ * statements only).
119
+ */
120
+ type CapacityWindowSpec =
121
+ | { readonly kind: "exact"; readonly n: CapacityBoundSpec }
122
+ | { readonly kind: "range"; readonly lo: CapacityBoundSpec; readonly hi: CapacityBoundSpec }
123
+ | { readonly kind: "floor"; readonly lo: CapacityBoundSpec }
97
124
 
98
125
  /**
99
126
  * One field: name, structural type, host newtype name — the field's
@@ -148,8 +175,10 @@ interface RelationSpec {
148
175
  * One dependency statement, tagged by form. `==` is not a variant: a
149
176
  * bidirectional containment is `containment` with `bidirectional: true`,
150
177
  * lowered by the engine to the two adjacent containments (`source <=
151
- * target` first). `cardinality` is B-family, target-left: the target is the
152
- * per-group parent, the source is counted.
178
+ * target` first). `capacity` reads as the operator does (C2 — target,
179
+ * weight, window, source): the target is the per-group parent, the source
180
+ * is the weighed side, and the weight is ALWAYS present (`unit` the count
181
+ * instance).
153
182
  */
154
183
  type StatementSpec =
155
184
  | { readonly kind: "fd"; readonly relation: string; readonly projection: readonly string[] }
@@ -160,9 +189,10 @@ type StatementSpec =
160
189
  readonly bidirectional: boolean
161
190
  }
162
191
  | {
163
- readonly kind: "cardinality"
192
+ readonly kind: "capacity"
164
193
  readonly target: SideSpec
165
- readonly window: WindowSpec
194
+ readonly weight: WeightSpec
195
+ readonly window: CapacityWindowSpec
166
196
  readonly source: SideSpec
167
197
  }
168
198
 
@@ -308,22 +338,58 @@ function renderLiteralSet(set: LiteralSetSpec): string {
308
338
  }
309
339
 
310
340
  /**
311
- * Renders window bounds in their one canonical spelling: `{n}` exact
341
+ * Renders one capacity bound in its one canonical spelling: a literal as
342
+ * digits, a dependent bound bare by field name, an interval-measure bound
343
+ * as `Duration(field)` — the spellings the engine's renderer emits.
344
+ */
345
+ function renderCapacityBound(bound: CapacityBoundSpec): string {
346
+ switch (bound.kind) {
347
+ case "lit":
348
+ return bound.value.toString()
349
+ case "field":
350
+ return bound.field
351
+ case "durationField":
352
+ return `Duration(${bound.field})`
353
+ }
354
+ }
355
+
356
+ /**
357
+ * Renders a capacity window in its one canonical spelling: `{n}` exact
312
358
  * (`{0}` the exclusion), `{lo..hi}`, `{lo..*}` — the spelling set the
313
- * engine's renderer emits for sealed statements.
359
+ * engine's renderer emits for sealed statements, bounds through
360
+ * {@link renderCapacityBound}.
314
361
  */
315
- function renderWindow(window: WindowSpec): string {
362
+ function renderCapacityWindow(window: CapacityWindowSpec): string {
316
363
  switch (window.kind) {
317
364
  case "exact":
318
- return `{${window.n}}`
365
+ return `{${renderCapacityBound(window.n)}}`
319
366
  case "range":
320
- return `{${window.lo}..${window.hi}}`
367
+ return `{${renderCapacityBound(window.lo)}..${renderCapacityBound(window.hi)}}`
321
368
  case "floor":
322
- return `{${window.lo}..*}`
369
+ return `{${renderCapacityBound(window.lo)}..*}`
370
+ }
371
+ }
372
+
373
+ /**
374
+ * Renders a capacity weight as the operator's bracket: the unit weight
375
+ * renders NOTHING — the count utterance `<={lo..hi}` falls out of the one
376
+ * printer, never a second "legacy" arm — a field weight as `[field]`, an
377
+ * interval measure as `[Duration(field)]`.
378
+ */
379
+ function renderWeight(weight: WeightSpec): string {
380
+ switch (weight.kind) {
381
+ case "unit":
382
+ return ""
383
+ case "field":
384
+ return `[${weight.field}]`
385
+ case "durationField":
386
+ return `[Duration(${weight.field})]`
323
387
  }
324
388
  }
325
389
 
326
390
  export type {
391
+ CapacityBoundSpec,
392
+ CapacityWindowSpec,
327
393
  ClosedSpec,
328
394
  FieldSpec,
329
395
  LiteralSetSpec,
@@ -335,6 +401,6 @@ export type {
335
401
  StatementSpec,
336
402
  ValueSpec,
337
403
  ValueTypeSpec,
338
- WindowSpec
404
+ WeightSpec
339
405
  }
340
- export { renderLiteral, renderLiteralSet, renderWindow }
406
+ export { renderCapacityBound, renderCapacityWindow, renderLiteral, renderLiteralSet, renderWeight }
package/src/statements.ts CHANGED
@@ -2,7 +2,7 @@
2
2
  * Dependency statements as typed values (`docs/architecture/30-dependencies.md`
3
3
  * owns the semantics; `docs/architecture/70-api.md` the surface): the FD key
4
4
  * form, conditional containment, the bidirectional `==` abbreviation, and
5
- * the cardinality window. A statement value is opaque and inert — no
5
+ * the capacity statement. A statement value is opaque and inert — no
6
6
  * methods, no fluent continuation: a fact about the theory, not a builder.
7
7
  *
8
8
  * Every field reference is checked against the relation it names in the
@@ -35,12 +35,21 @@
35
35
  */
36
36
 
37
37
  import * as errors from "@superbuilders/errors"
38
+ import {
39
+ type BoundsOnTarget,
40
+ type CapacityWeight,
41
+ type CapacityWindow,
42
+ isCapacityWeight,
43
+ isCapacityWindow,
44
+ type UnitWindowBan,
45
+ unitWeight,
46
+ type WeightOnSource
47
+ } from "#capacity.ts"
38
48
  import { isClosedMember, sealedFieldOf } from "#closed.ts"
39
- import type { Count } from "#count.ts"
40
49
  import { type AnyFace, type FaceData, renderFace, type SameArity, type SameShapes } from "#face.ts"
41
50
  import { type ClosedRoster, rosterOf } from "#fields.ts"
42
51
  import type { AnyRelation, RelationFields } from "#relation.ts"
43
- import { renderWindow, type WindowSpec } from "#spec.ts"
52
+ import { type CapacityWindowSpec, renderCapacityWindow, renderWeight, type WeightSpec } from "#spec.ts"
44
53
 
45
54
  /** A `key()` statement's runtime description — owner and projection carried at exact types. */
46
55
  interface KeyData<R extends AnyRelation, Projection extends readonly string[]> {
@@ -63,20 +72,26 @@ interface ContainmentData<Src extends FaceData = FaceData, Tgt extends FaceData
63
72
  readonly bidirectional: boolean
64
73
  }
65
74
 
66
- /** A window statement's runtime description — target-left, faces at exact types like {@link ContainmentData}. */
67
- interface WindowData<Tgt extends FaceData = FaceData, Src extends FaceData = FaceData> {
68
- readonly kind: "window"
75
+ /**
76
+ * A capacity statement's runtime description target-left, in the
77
+ * operator's own order (C2: target, weight, window, source), faces at
78
+ * exact types like {@link ContainmentData}. The weight is ALWAYS present
79
+ * (C4 — `unit` is a case, not an absence).
80
+ */
81
+ interface CapacityData<Tgt extends FaceData = FaceData, Src extends FaceData = FaceData> {
82
+ readonly kind: "capacity"
69
83
  readonly target: Tgt
70
- readonly window: WindowSpec
84
+ readonly weight: WeightSpec
85
+ readonly window: CapacityWindowSpec
71
86
  readonly source: Src
72
87
  }
73
88
 
74
89
  /** One statement's runtime description, tagged by form. */
75
- type StatementData = KeyData<AnyRelation, readonly string[]> | ContainmentData | WindowData
90
+ type StatementData = KeyData<AnyRelation, readonly string[]> | ContainmentData | CapacityData
76
91
 
77
92
  /**
78
93
  * The admission brand — a module-private symbol, deliberately unexported
79
- * (the `count.ts` pattern): `Statement` is a public structural type, so
94
+ * (the `capacity.ts` pattern): `Statement` is a public structural type, so
80
95
  * without this brand a forged plain object of the right shape would walk
81
96
  * past the construction-time arity and roster walls into `schema()` — and
82
97
  * the roster wall is the one the engine cannot backstop (the wire carries
@@ -115,9 +130,9 @@ interface ContainedStatement<Src extends FaceData, Tgt extends FaceData> extends
115
130
  readonly data: ContainmentData<Src, Tgt>
116
131
  }
117
132
 
118
- /** A window statement as a TYPED value — the {@link ContainedStatement} of the window form. */
119
- interface WindowStatement<Tgt extends FaceData, Src extends FaceData> extends Statement {
120
- readonly data: WindowData<Tgt, Src>
133
+ /** A capacity statement as a TYPED value — the {@link ContainedStatement} of the capacity form. */
134
+ interface CapacityStatement<Tgt extends FaceData, Src extends FaceData> extends Statement {
135
+ readonly data: CapacityData<Tgt, Src>
121
136
  }
122
137
 
123
138
  /**
@@ -266,29 +281,138 @@ function mirrors<A extends AnyFace, B extends AnyFace>(
266
281
  }
267
282
 
268
283
  /**
269
- * `B(Y|ψ) <={window} A(X|φ)` the cardinality window. READ CAREFULLY: the
270
- * LEFT face is the window's TARGET, the per-group parent (B-family,
271
- * target-left macro parity), and the RIGHT face is the counted source.
272
- * `window(on(Holder, "id"), atMost(3n), on(Account, "holder"))` says: each
273
- * Holder id groups at most three Account rows by holder. The two faces
274
- * pair by arity AND structural shape ({@link SameShapes}), exactly as
275
- * containment — the grouping join reads the same positionwise field
276
- * pairing.
284
+ * The runtime twin of the weight source wall ({@link WeightOnSource}): the
285
+ * weighed field must be a u64-encoded position of the SOURCE's own row
286
+ * (a signed weight would break the polarity scheduler the illegal weight
287
+ * is unrepresentable, not checked), an interval position for the
288
+ * `Duration(...)` form. Judged at CONSTRUCTION for untyped callers; the
289
+ * engine's `validate_capacity` stays the final authority.
290
+ */
291
+ function assertWeightOnSource(weight: WeightSpec, source: FaceData, statement: Statement): void {
292
+ if (weight.kind === "unit") {
293
+ return
294
+ }
295
+ const field = sealedFieldOf(source.owner, weight.field)
296
+ if (field === undefined) {
297
+ throw errors.new(
298
+ `${source.owner.name} has no field ${weight.field} — a weight names a field of the SOURCE's own row (the weight vocabulary is closed at the row) — ${renderStatement(statement)}`
299
+ )
300
+ }
301
+ if (weight.kind === "field" && field.kind !== "u64") {
302
+ throw errors.new(
303
+ `${source.owner.name}.${weight.field} is ${field.kind}, not u64 — a weight is u64-encoded (a signed weight would break the polarity scheduler: an insert could lower a sum) — ${renderStatement(statement)}`
304
+ )
305
+ }
306
+ if (weight.kind === "durationField" && field.kind !== "interval") {
307
+ throw errors.new(
308
+ `${source.owner.name}.${weight.field} is ${field.kind}, not an interval — Duration(...) weighs an interval field's measure — ${renderStatement(statement)}`
309
+ )
310
+ }
311
+ }
312
+
313
+ /**
314
+ * The runtime twin of the dependent-bound target wall
315
+ * ({@link BoundsOnTarget}): a `ref()` bound must name a u64 field of the
316
+ * TARGET's own row, a `duration()` bound an interval field — bound names
317
+ * resolve against the target's FULL roster (C1), never the projection.
318
+ * `within()` mints dependent bounds in the hi slot only (C6), but the walk
319
+ * here is total over the window's bound slots.
277
320
  */
278
- function window<B extends AnyFace, A extends AnyFace>(
321
+ function assertBoundsOnTarget(window: CapacityWindowSpec, target: FaceData, statement: Statement): void {
322
+ const bounds = window.kind === "range" ? [window.lo, window.hi] : [window.kind === "exact" ? window.n : window.lo]
323
+ for (const bound of bounds) {
324
+ if (bound.kind === "lit") {
325
+ continue
326
+ }
327
+ const field = sealedFieldOf(target.owner, bound.field)
328
+ if (field === undefined) {
329
+ throw errors.new(
330
+ `${target.owner.name} has no field ${bound.field} — a dependent bound names a field of the TARGET's own row (bound names resolve against the target's full roster) — ${renderStatement(statement)}`
331
+ )
332
+ }
333
+ if (bound.kind === "field" && field.kind !== "u64") {
334
+ throw errors.new(
335
+ `${target.owner.name}.${bound.field} is ${field.kind}, not u64 — a dependent bound reads a u64 field of the TARGET row (Duration(...) is the interval-measure spelling) — ${renderStatement(statement)}`
336
+ )
337
+ }
338
+ if (bound.kind === "durationField" && field.kind !== "interval") {
339
+ throw errors.new(
340
+ `${target.owner.name}.${bound.field} is ${field.kind}, not an interval — Duration(...) bounds by an interval field's measure — ${renderStatement(statement)}`
341
+ )
342
+ }
343
+ }
344
+ }
345
+
346
+ /**
347
+ * `B(Y|ψ) <=[w]{window} A(X|φ)` — the capacity statement, the one
348
+ * extension form: per ψ-selected target fact, the group of φ-selected
349
+ * source facts sharing its key tuple must have its MEASURE (Σ weight; the
350
+ * unit weight IS the count instance) inside the window. READ CAREFULLY:
351
+ * the LEFT face is the TARGET, the per-group parent (B-family, target-left
352
+ * — macro parity), and the RIGHT face is the weighed source. Two
353
+ * overloads mirror the operator positionally (target, weight?, window,
354
+ * source): `capacity(on(Holder, "id"), within(0n, 3n), on(Account,
355
+ * "holder"))` says each Holder id groups at most three Account rows;
356
+ * `capacity(on(Pool, "id"), weigh("watts"), within(0n, ref("supply")),
357
+ * on(Device, "pool"))` bounds each pool's summed draw by the pool's own
358
+ * row. The two faces pair by arity AND structural shape
359
+ * ({@link SameShapes}), exactly as containment — the grouping join reads
360
+ * the same positionwise field pairing. The weight-sensitive `{1..*}` ban
361
+ * rides the UNIT overload only ({@link UnitWindowBan} — on a weighted
362
+ * statement "positive total" is a different, weaker law than containment).
363
+ */
364
+ function capacity<B extends AnyFace, W extends CapacityWindow, A extends AnyFace>(
365
+ target: B,
366
+ window: W & UnitWindowBan<W> & BoundsOnTarget<W, B>,
367
+ source: A & SameArity<B, A> & SameShapes<B, A>
368
+ ): CapacityStatement<B["data"], A["data"]>
369
+ function capacity<B extends AnyFace, M extends CapacityWeight, W extends CapacityWindow, A extends AnyFace>(
279
370
  target: B,
280
- count: Count,
371
+ weight: M & WeightOnSource<M, A>,
372
+ window: W & BoundsOnTarget<W, B>,
281
373
  source: A & SameArity<B, A> & SameShapes<B, A>
282
- ): WindowStatement<B["data"], A["data"]> {
283
- const data: WindowData<B["data"], A["data"]> = Object.freeze({
284
- kind: "window",
374
+ ): CapacityStatement<B["data"], A["data"]>
375
+ function capacity(
376
+ target: AnyFace,
377
+ second: unknown,
378
+ third: unknown,
379
+ fourth?: AnyFace
380
+ ): CapacityStatement<FaceData, FaceData> {
381
+ const weighted = fourth !== undefined
382
+ const windowValue = weighted ? third : second
383
+ const source = weighted ? fourth : (third as AnyFace)
384
+ if (!isCapacityWindow(windowValue)) {
385
+ throw errors.new(
386
+ "a capacity window is minted only by within() — a structural literal skips the ban table (the canonical-utterance law)"
387
+ )
388
+ }
389
+ let weight: WeightSpec = unitWeight
390
+ if (weighted) {
391
+ if (!isCapacityWeight(second)) {
392
+ throw errors.new(
393
+ "a capacity weight is minted only by weigh() — a structural literal skips the row-local weight wall"
394
+ )
395
+ }
396
+ weight = second.weight
397
+ }
398
+ const window = windowValue.window
399
+ if (weight.kind === "unit" && window.kind === "floor" && window.lo.kind === "lit" && window.lo.value === 1n) {
400
+ throw errors.new(
401
+ "`{1..*}` on the unit instance says only what the bare containment says — drop the annotation and write the containment: contained(source, target)"
402
+ )
403
+ }
404
+ const data: CapacityData = Object.freeze({
405
+ kind: "capacity",
285
406
  target: target.data,
286
- window: count.window,
407
+ weight,
408
+ window,
287
409
  source: source.data
288
410
  })
289
411
  const statement = Object.freeze({ data, [admitted]: true as const })
290
412
  assertArityAgreement(data.source, data.target, statement)
291
413
  assertRosterAgreement(data.source, data.target, statement)
414
+ assertWeightOnSource(weight, data.source, statement)
415
+ assertBoundsOnTarget(window, data.target, statement)
292
416
  return statement
293
417
  }
294
418
 
@@ -298,9 +422,11 @@ function window<B extends AnyFace, A extends AnyFace>(
298
422
  * same shapes for violations) — `Account(id) -> Account`,
299
423
  * `Account(holder) <= Holder(id)`,
300
424
  * `Account(id | kind == Savings) == SavingsTerms(account)`,
301
- * `Holder(id) <={0..3} Account(holder)` — so TS-side errors and
425
+ * `Holder(id) <={0..3} Account(holder)`,
426
+ * `Pool(id) <=[watts]{0..supply} Device(pool)` — so TS-side errors and
302
427
  * engine-side diagnostics read identically. A renderer, never a parser:
303
- * strings are output-only.
428
+ * strings are output-only. The unit weight renders nothing — the count
429
+ * utterance falls out of the one printer.
304
430
  */
305
431
  function renderStatement(statement: Statement): string {
306
432
  const data = statement.data
@@ -311,19 +437,19 @@ function renderStatement(statement: Statement): string {
311
437
  const operator = data.bidirectional ? "==" : "<="
312
438
  return `${renderFace(data.source)} ${operator} ${renderFace(data.target)}`
313
439
  }
314
- case "window":
315
- return `${renderFace(data.target)} <=${renderWindow(data.window)} ${renderFace(data.source)}`
440
+ case "capacity":
441
+ return `${renderFace(data.target)} <=${renderWeight(data.weight)}${renderCapacityWindow(data.window)} ${renderFace(data.source)}`
316
442
  }
317
443
  }
318
444
 
319
445
  export type {
446
+ CapacityData,
447
+ CapacityStatement,
320
448
  ContainedStatement,
321
449
  ContainmentData,
322
450
  KeyData,
323
451
  KeyStatement,
324
452
  Statement,
325
- StatementData,
326
- WindowData,
327
- WindowStatement
453
+ StatementData
328
454
  }
329
- export { contained, isStatement, key, mirrors, renderStatement, window }
455
+ export { capacity, contained, isStatement, key, mirrors, renderStatement }