aontu 0.52.1 → 0.53.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 (272) hide show
  1. package/README.md +88 -0
  2. package/bin/aontu-mcp.js +4 -0
  3. package/dist/agentsmd.d.ts +16 -0
  4. package/dist/agentsmd.js +107 -0
  5. package/dist/agentsmd.js.map +1 -0
  6. package/dist/aontu.d.ts +14 -3
  7. package/dist/aontu.js +96 -4
  8. package/dist/aontu.js.map +1 -1
  9. package/dist/cli.d.ts +44 -1
  10. package/dist/cli.js +2401 -44
  11. package/dist/cli.js.map +1 -1
  12. package/dist/ctx.d.ts +13 -0
  13. package/dist/ctx.js +43 -0
  14. package/dist/ctx.js.map +1 -1
  15. package/dist/diff.d.ts +22 -0
  16. package/dist/diff.js +141 -0
  17. package/dist/diff.js.map +1 -0
  18. package/dist/err.d.ts +3 -1
  19. package/dist/err.js +38 -7
  20. package/dist/err.js.map +1 -1
  21. package/dist/graph.d.ts +16 -0
  22. package/dist/graph.js +73 -0
  23. package/dist/graph.js.map +1 -0
  24. package/dist/hcanon.d.ts +3 -0
  25. package/dist/hcanon.js +146 -0
  26. package/dist/hcanon.js.map +1 -0
  27. package/dist/hints.js +167 -4
  28. package/dist/hints.js.map +1 -1
  29. package/dist/jsonschema.d.ts +20 -0
  30. package/dist/jsonschema.js +391 -0
  31. package/dist/jsonschema.js.map +1 -0
  32. package/dist/lang.js +512 -69
  33. package/dist/lang.js.map +1 -1
  34. package/dist/lsp.d.ts +9 -2
  35. package/dist/lsp.js +262 -47
  36. package/dist/lsp.js.map +1 -1
  37. package/dist/mcp-server.d.ts +20 -0
  38. package/dist/mcp-server.js +147 -0
  39. package/dist/mcp-server.js.map +1 -0
  40. package/dist/mcp.d.ts +42 -0
  41. package/dist/mcp.js +814 -0
  42. package/dist/mcp.js.map +1 -0
  43. package/dist/mod-tool.d.ts +58 -0
  44. package/dist/mod-tool.js +498 -0
  45. package/dist/mod-tool.js.map +1 -0
  46. package/dist/mod.d.ts +31 -0
  47. package/dist/mod.js +250 -0
  48. package/dist/mod.js.map +1 -0
  49. package/dist/patch.d.ts +44 -0
  50. package/dist/patch.js +506 -0
  51. package/dist/patch.js.map +1 -0
  52. package/dist/provenance.d.ts +40 -0
  53. package/dist/provenance.js +335 -0
  54. package/dist/provenance.js.map +1 -0
  55. package/dist/query.d.ts +27 -0
  56. package/dist/query.js +294 -0
  57. package/dist/query.js.map +1 -0
  58. package/dist/reach.d.ts +14 -0
  59. package/dist/reach.js +140 -0
  60. package/dist/reach.js.map +1 -0
  61. package/dist/relation.d.ts +19 -0
  62. package/dist/relation.js +305 -0
  63. package/dist/relation.js.map +1 -0
  64. package/dist/report-sarif.d.ts +14 -0
  65. package/dist/report-sarif.js +102 -0
  66. package/dist/report-sarif.js.map +1 -0
  67. package/dist/site.d.ts +4 -0
  68. package/dist/site.js +31 -0
  69. package/dist/site.js.map +1 -1
  70. package/dist/std.d.ts +1 -0
  71. package/dist/std.js +73 -0
  72. package/dist/std.js.map +1 -0
  73. package/dist/subsume.d.ts +39 -0
  74. package/dist/subsume.js +526 -0
  75. package/dist/subsume.js.map +1 -0
  76. package/dist/trim.d.ts +19 -0
  77. package/dist/trim.js +155 -0
  78. package/dist/trim.js.map +1 -0
  79. package/dist/tsconfig.tsbuildinfo +1 -1
  80. package/dist/type.d.ts +17 -1
  81. package/dist/type.js.map +1 -1
  82. package/dist/unify.d.ts +2 -1
  83. package/dist/unify.js +253 -36
  84. package/dist/unify.js.map +1 -1
  85. package/dist/utility.d.ts +9 -1
  86. package/dist/utility.js +122 -1
  87. package/dist/utility.js.map +1 -1
  88. package/dist/val/AggFuncVal.d.ts +33 -0
  89. package/dist/val/AggFuncVal.js +202 -0
  90. package/dist/val/AggFuncVal.js.map +1 -0
  91. package/dist/val/ArithFuncVal.d.ts +31 -0
  92. package/dist/val/ArithFuncVal.js +62 -0
  93. package/dist/val/ArithFuncVal.js.map +1 -0
  94. package/dist/val/BagVal.d.ts +5 -0
  95. package/dist/val/BagVal.js +96 -5
  96. package/dist/val/BagVal.js.map +1 -1
  97. package/dist/val/CloseFuncVal.js +9 -1
  98. package/dist/val/CloseFuncVal.js.map +1 -1
  99. package/dist/val/ConjunctVal.d.ts +1 -1
  100. package/dist/val/ConjunctVal.js +19 -0
  101. package/dist/val/ConjunctVal.js.map +1 -1
  102. package/dist/val/ConstraintVal.d.ts +7 -1
  103. package/dist/val/ConstraintVal.js +490 -40
  104. package/dist/val/ConstraintVal.js.map +1 -1
  105. package/dist/val/CopyFuncVal.d.ts +1 -2
  106. package/dist/val/CopyFuncVal.js +7 -0
  107. package/dist/val/CopyFuncVal.js.map +1 -1
  108. package/dist/val/Decimal.d.ts +1 -0
  109. package/dist/val/Decimal.js +13 -0
  110. package/dist/val/Decimal.js.map +1 -1
  111. package/dist/val/DeprecateFuncVal.d.ts +11 -0
  112. package/dist/val/DeprecateFuncVal.js +47 -0
  113. package/dist/val/DeprecateFuncVal.js.map +1 -0
  114. package/dist/val/DisjunctVal.js +130 -21
  115. package/dist/val/DisjunctVal.js.map +1 -1
  116. package/dist/val/EachFuncVal.d.ts +15 -0
  117. package/dist/val/EachFuncVal.js +75 -0
  118. package/dist/val/EachFuncVal.js.map +1 -0
  119. package/dist/val/ExpectVal.js +27 -4
  120. package/dist/val/ExpectVal.js.map +1 -1
  121. package/dist/val/FeatureVal.js +1 -1
  122. package/dist/val/FeatureVal.js.map +1 -1
  123. package/dist/val/FilterFuncVal.d.ts +15 -0
  124. package/dist/val/FilterFuncVal.js +91 -0
  125. package/dist/val/FilterFuncVal.js.map +1 -0
  126. package/dist/val/FuncBaseVal.d.ts +6 -1
  127. package/dist/val/FuncBaseVal.js +171 -1
  128. package/dist/val/FuncBaseVal.js.map +1 -1
  129. package/dist/val/HideFuncVal.js.map +1 -1
  130. package/dist/val/IdFuncVal.d.ts +13 -0
  131. package/dist/val/IdFuncVal.js +54 -0
  132. package/dist/val/IdFuncVal.js.map +1 -0
  133. package/dist/val/JunctionVal.js +7 -1
  134. package/dist/val/JunctionVal.js.map +1 -1
  135. package/dist/val/KeyFuncVal.d.ts +1 -1
  136. package/dist/val/KeyFuncVal.js +38 -30
  137. package/dist/val/KeyFuncVal.js.map +1 -1
  138. package/dist/val/ListVal.js +101 -14
  139. package/dist/val/ListVal.js.map +1 -1
  140. package/dist/val/LowerFuncVal.js.map +1 -1
  141. package/dist/val/MapVal.js +93 -8
  142. package/dist/val/MapVal.js.map +1 -1
  143. package/dist/val/MatchFuncVal.d.ts +15 -0
  144. package/dist/val/MatchFuncVal.js +107 -0
  145. package/dist/val/MatchFuncVal.js.map +1 -0
  146. package/dist/val/MoveFuncVal.js.map +1 -1
  147. package/dist/val/NilVal.js +24 -0
  148. package/dist/val/NilVal.js.map +1 -1
  149. package/dist/val/OpBaseVal.d.ts +1 -1
  150. package/dist/val/OpBaseVal.js +19 -1
  151. package/dist/val/OpBaseVal.js.map +1 -1
  152. package/dist/val/OpenFuncVal.js +4 -1
  153. package/dist/val/OpenFuncVal.js.map +1 -1
  154. package/dist/val/PackFuncVal.d.ts +15 -0
  155. package/dist/val/PackFuncVal.js +108 -0
  156. package/dist/val/PackFuncVal.js.map +1 -0
  157. package/dist/val/PathFuncVal.js.map +1 -1
  158. package/dist/val/PlaceVal.d.ts +13 -0
  159. package/dist/val/PlaceVal.js +131 -0
  160. package/dist/val/PlaceVal.js.map +1 -0
  161. package/dist/val/PlusOpVal.js +11 -2
  162. package/dist/val/PlusOpVal.js.map +1 -1
  163. package/dist/val/PrefFuncVal.js.map +1 -1
  164. package/dist/val/PrefVal.d.ts +2 -2
  165. package/dist/val/PrefVal.js +78 -23
  166. package/dist/val/PrefVal.js.map +1 -1
  167. package/dist/val/RefVal.d.ts +1 -1
  168. package/dist/val/RefVal.js +89 -7
  169. package/dist/val/RefVal.js.map +1 -1
  170. package/dist/val/ReferFuncVal.d.ts +36 -0
  171. package/dist/val/ReferFuncVal.js +303 -0
  172. package/dist/val/ReferFuncVal.js.map +1 -0
  173. package/dist/val/ScalarKindVal.d.ts +1 -2
  174. package/dist/val/ScalarKindVal.js +0 -11
  175. package/dist/val/ScalarKindVal.js.map +1 -1
  176. package/dist/val/TopVal.js.map +1 -1
  177. package/dist/val/TypeFuncVal.js.map +1 -1
  178. package/dist/val/UpperFuncVal.js.map +1 -1
  179. package/dist/val/Val.d.ts +8 -1
  180. package/dist/val/Val.js +150 -1
  181. package/dist/val/Val.js.map +1 -1
  182. package/dist/val/VarVal.js.map +1 -1
  183. package/dist/val/arith.d.ts +6 -0
  184. package/dist/val/arith.js +170 -0
  185. package/dist/val/arith.js.map +1 -0
  186. package/dist/vet.d.ts +45 -0
  187. package/dist/vet.js +776 -0
  188. package/dist/vet.js.map +1 -0
  189. package/dist/walk.d.ts +2 -0
  190. package/dist/walk.js +91 -0
  191. package/dist/walk.js.map +1 -0
  192. package/grammar/aontu.gbnf +130 -0
  193. package/grammar/aontu.lark +113 -0
  194. package/package.json +21 -6
  195. package/skill/SKILL.md +37 -0
  196. package/skill/error-codes.md +62 -0
  197. package/skill/examples.md +99 -0
  198. package/skill/grammar-card.md +57 -0
  199. package/src/agentsmd.ts +135 -0
  200. package/src/aontu.ts +135 -4
  201. package/src/cli.ts +2858 -71
  202. package/src/ctx.ts +73 -1
  203. package/src/diff.ts +196 -0
  204. package/src/err.ts +42 -7
  205. package/src/graph.ts +135 -0
  206. package/src/hcanon.ts +169 -0
  207. package/src/hints.ts +208 -4
  208. package/src/jsonschema.ts +511 -0
  209. package/src/lang.ts +570 -70
  210. package/src/lsp.ts +281 -48
  211. package/src/mcp-server.ts +187 -0
  212. package/src/mcp.ts +993 -0
  213. package/src/mod-tool.ts +679 -0
  214. package/src/mod.ts +344 -0
  215. package/src/patch.ts +624 -0
  216. package/src/provenance.ts +430 -0
  217. package/src/query.ts +379 -0
  218. package/src/reach.ts +184 -0
  219. package/src/relation.ts +395 -0
  220. package/src/report-sarif.ts +137 -0
  221. package/src/site.ts +36 -1
  222. package/src/std.ts +73 -0
  223. package/src/subsume.ts +690 -0
  224. package/src/trim.ts +195 -0
  225. package/src/tsconfig.json +10 -4
  226. package/src/type.ts +51 -2
  227. package/src/unify.ts +274 -36
  228. package/src/utility.ts +139 -1
  229. package/src/val/AggFuncVal.ts +319 -0
  230. package/src/val/ArithFuncVal.ts +108 -0
  231. package/src/val/BagVal.ts +101 -4
  232. package/src/val/CloseFuncVal.ts +9 -1
  233. package/src/val/ConjunctVal.ts +20 -0
  234. package/src/val/ConstraintVal.ts +542 -43
  235. package/src/val/CopyFuncVal.ts +7 -1
  236. package/src/val/Decimal.ts +15 -0
  237. package/src/val/DeprecateFuncVal.ts +84 -0
  238. package/src/val/DisjunctVal.ts +139 -28
  239. package/src/val/EachFuncVal.ts +133 -0
  240. package/src/val/ExpectVal.ts +28 -6
  241. package/src/val/FeatureVal.ts +1 -1
  242. package/src/val/FilterFuncVal.ts +154 -0
  243. package/src/val/FuncBaseVal.ts +192 -1
  244. package/src/val/HideFuncVal.ts +0 -2
  245. package/src/val/IdFuncVal.ts +91 -0
  246. package/src/val/JunctionVal.ts +7 -1
  247. package/src/val/KeyFuncVal.ts +39 -35
  248. package/src/val/ListVal.ts +108 -15
  249. package/src/val/LowerFuncVal.ts +0 -1
  250. package/src/val/MapVal.ts +99 -8
  251. package/src/val/MatchFuncVal.ts +176 -0
  252. package/src/val/MoveFuncVal.ts +0 -2
  253. package/src/val/NilVal.ts +25 -0
  254. package/src/val/OpBaseVal.ts +20 -1
  255. package/src/val/OpenFuncVal.ts +4 -2
  256. package/src/val/PackFuncVal.ts +175 -0
  257. package/src/val/PathFuncVal.ts +0 -1
  258. package/src/val/PlaceVal.ts +193 -0
  259. package/src/val/PlusOpVal.ts +11 -2
  260. package/src/val/PrefFuncVal.ts +0 -1
  261. package/src/val/PrefVal.ts +79 -36
  262. package/src/val/RefVal.ts +91 -8
  263. package/src/val/ReferFuncVal.ts +387 -0
  264. package/src/val/ScalarKindVal.ts +0 -13
  265. package/src/val/TopVal.ts +0 -1
  266. package/src/val/TypeFuncVal.ts +0 -2
  267. package/src/val/UpperFuncVal.ts +0 -1
  268. package/src/val/Val.ts +205 -2
  269. package/src/val/VarVal.ts +0 -1
  270. package/src/val/arith.ts +316 -0
  271. package/src/vet.ts +992 -0
  272. package/src/walk.ts +99 -0
@@ -0,0 +1,319 @@
1
+ /* Copyright (c) 2025 Richard Rodger, MIT License */
2
+
3
+ // AGGREGATION AND PROJECTION: `sum(d)`, `least(d)`, `greatest(d)`
4
+ // and `pick(d, k)`.
5
+ //
6
+ // The review's finding I: "No aggregation. `length()` counts but
7
+ // nothing sums: an invoice total, a fleet-wide resource budget, a quota
8
+ // roll-up -- all inexpressible." Use case 10 had to write totals by
9
+ // hand and spot-check them with `must()`, which is a model asserting
10
+ // what it should be able to COMPUTE.
11
+ //
12
+ // total: sum($.lines) # an invoice total
13
+ // cheapest: least($.quotes) # ... and the extremes
14
+ // peak: greatest($.hourly)
15
+ //
16
+ // A FOLD OVER A FINITE, SETTLED BAG IS AS TOTAL AS `each`. There is no
17
+ // recursion here and no user-supplied step: the bag is the one the
18
+ // model already holds, the operation is fixed, and the walk visits each
19
+ // child exactly once. That is the whole reason these are built-ins
20
+ // rather than a `fold` combinator -- a fold takes a function, and a
21
+ // language with no user functions has none to take.
22
+ //
23
+ // THE NAMES ARE `least` AND `greatest`, NOT `min` AND `max`, because
24
+ // those two are already the constraint atoms for a LOWER and UPPER
25
+ // BOUND (`min(3)` means "at least 3"). An aggregate over a bag and a
26
+ // bound on a value are different things and must not share a spelling;
27
+ // least and greatest are the lattice's own words for the extremes of a
28
+ // set, which is exactly what these compute.
29
+ //
30
+ // `sum` FOLDS WITH `add`, so the number tower's whole law comes with it
31
+ // (arith.ts): the exact ladder, R5 contagion, the refusal to mix a
32
+ // binary float with an exact leaf, and the refusal to store a result
33
+ // that will not fit. A bag of integers sums to an integer; one integer
34
+ // float among them makes the total a float; `0d` operands keep it
35
+ // exact. And `sum([])` is `0` -- addition has an identity, which is
36
+ // what makes the empty case answerable.
37
+ //
38
+ // `least`/`greatest` have NO identity to answer an empty bag with: the
39
+ // least element of nothing is not a value, it is a question with no
40
+ // answer, so an empty bag is a located `aggregate_empty` rather than an
41
+ // invented zero or infinity. They compare with the tower's EXACT
42
+ // comparator (numcmp), so an integer and a bigdecimal in one bag order
43
+ // correctly rather than through binary64, and the winner is returned
44
+ // with its own kind intact.
45
+
46
+ import type {
47
+ Val,
48
+ ValSpec,
49
+ } from '../type'
50
+
51
+ import {
52
+ AontuContext,
53
+ } from '../ctx'
54
+
55
+ import { makeNilErr } from '../err'
56
+ import { IntegerVal } from './IntegerVal'
57
+ import { ListVal } from './ListVal'
58
+ import { FuncBaseVal } from './FuncBaseVal'
59
+ import { arith } from './arith'
60
+ import { cmpNumeric } from './numcmp'
61
+
62
+
63
+ type AggOp = 'sum' | 'least' | 'greatest'
64
+
65
+
66
+ // The children of a bag, in the order the aggregate sees them: source
67
+ // order for a list, sorted-key order for a map -- `each`'s order, and
68
+ // for the same reason (a map has no order of its own, so the language
69
+ // picks one and states it).
70
+ function bagChildren(data: any): Val[] | undefined {
71
+ if (true === data?.isList) {
72
+ return data.peg as Val[]
73
+ }
74
+ if (true === data?.isMap) {
75
+ return Object.keys(data.peg).sort().map((k: string) => data.peg[k])
76
+ }
77
+ return undefined
78
+ }
79
+
80
+
81
+ class AggFuncVal extends FuncBaseVal {
82
+ isAggFunc = true
83
+
84
+ // THE STAGING RULE (G8 phase 0). A total over a bag that is still
85
+ // being merged into is a total of the wrong bag -- the same reason
86
+ // `filter` and `each` wait.
87
+ staged = true
88
+
89
+ op: AggOp
90
+
91
+ constructor(
92
+ spec: ValSpec,
93
+ ctx: AontuContext | undefined,
94
+ op: AggOp
95
+ ) {
96
+ super(spec, ctx)
97
+ this.op = op
98
+ }
99
+
100
+
101
+ // NO `make` OVERRIDE, matching the other STAGED funcs (pack, each,
102
+ // filter, match): a staged call returns `residuate` before `unify`
103
+ // ever reaches the rebuild branch, so an override there would be
104
+ // unreachable code pretending to be a contract. The base's `make`
105
+ // raises `func:<name>` if that ever stops being true, which is the
106
+ // loud answer rather than a value that silently lost its operation.
107
+
108
+ funcname() {
109
+ return this.op
110
+ }
111
+
112
+
113
+ // The base does not drive the argument: `unify` drives it by hand,
114
+ // because a staged func must advance what it is waiting on every
115
+ // pass rather than only on the pass it fires.
116
+ prepare(_ctx: AontuContext, _args: Val[]) {
117
+ return null
118
+ }
119
+
120
+
121
+ unify(peer: Val, ctx: AontuContext): Val {
122
+ const ready = this.driveStagedArgs(ctx, 1)
123
+
124
+ if (!ready || !ctx.settle) {
125
+ return this.residuate(peer, ctx)
126
+ }
127
+
128
+ return super.unify(peer, ctx)
129
+ }
130
+
131
+
132
+ resolve(ctx: AontuContext, args: Val[]) {
133
+ const children = bagChildren(args?.[0])
134
+
135
+ if (undefined === children) {
136
+ return this.place(makeNilErr(ctx, 'aggregate_data', this, undefined,
137
+ this.op))
138
+ }
139
+
140
+ if ('sum' === this.op) {
141
+ // Zero is addition's identity, so an empty bag has an answer and
142
+ // it is an integer -- the narrowest kind, which the first real
143
+ // operand then widens under R5 exactly as `add(0, x)` would.
144
+ let total: Val = new IntegerVal({ peg: 0 })
145
+ for (const child of children) {
146
+ total = arith(ctx, 'add', this, total, child, this.op)
147
+ // A refusal inside the fold IS the answer: adding on past a
148
+ // non-numeric child or an overflow would report the wrong
149
+ // reason, or none.
150
+ if (true === (total as any).isNil) {
151
+ return this.place(total)
152
+ }
153
+ }
154
+ return this.place(total)
155
+ }
156
+
157
+ if (0 === children.length) {
158
+ return this.place(makeNilErr(ctx, 'aggregate_empty', this, undefined,
159
+ this.op))
160
+ }
161
+
162
+ const want = 'least' === this.op ? -1 : 1
163
+ let best: any = undefined
164
+ for (const child of children) {
165
+ const c: any = unpref(child)
166
+ if (!(c?.isVal && c.isScalar && 'string' !== typeof c.peg &&
167
+ 'boolean' !== typeof c.peg && !c.isNull)) {
168
+ return this.place(makeNilErr(ctx, 'invalid-arg', this, undefined,
169
+ this.op))
170
+ }
171
+ // The EXACT comparator (numcmp), never binary64: a bigdecimal and
172
+ // an integer in one bag must order by their values and not by
173
+ // whatever their float images happen to be.
174
+ if (undefined === best || want === cmpNumeric(c, best)) {
175
+ best = c
176
+ }
177
+ }
178
+ // The winner is returned as itself, so it keeps its own kind: the
179
+ // least of a bag of bigdecimals is a bigdecimal.
180
+ return this.place(best.clone(ctx))
181
+ }
182
+ }
183
+
184
+
185
+ // A pref child contributes its preferred value, and therefore that
186
+ // value's kind too -- the rule `+` and the arithmetic family apply to
187
+ // operands, applied here to bag members.
188
+ function unpref(v: any): any {
189
+ while (v?.isPref) {
190
+ v = v.peg
191
+ }
192
+ return v
193
+ }
194
+
195
+
196
+ // PROJECTION: `pick(d, k)` -- one element per child of `d`, being that
197
+ // child's `k`.
198
+ //
199
+ // The other half of the review's finding I: "`_.field` is unspellable,
200
+ // `filter` cannot see into lists, `unique()`-by-field is reserved but
201
+ // absent -- so 'no two services share a port' and 'unique event ids'
202
+ // cannot be said." Without it the aggregates above cannot reach the
203
+ // case that motivated them, because `sum` needs a bag of NUMBERS and a
204
+ // model holds a bag of RECORDS:
205
+ //
206
+ // total: sum(pick($.lines, amountCents))
207
+ //
208
+ // IT IS NOT `each` WITH A CLEVER TEMPLATE. `each(d, t)` MEETS each
209
+ // child with `t`, and a meet cannot select: `each($.lines, _.amount)`
210
+ // asks for a child that is simultaneously the whole record and one of
211
+ // its fields, which is why every spelling of it answers `no_path`.
212
+ // Selection is a different operation and gets its own verb.
213
+ //
214
+ // A CHILD MISSING THE KEY IS AN ERROR, not a silently shorter list.
215
+ // Skipping would make `sum(pick(...))` quietly total the wrong set of
216
+ // records -- the failure mode an aggregate exists to prevent -- so the
217
+ // refusal names the child (`pick_key`).
218
+ class PickFuncVal extends FuncBaseVal {
219
+ isPickFunc = true
220
+
221
+ // The bag must settle before it is projected, exactly as it must
222
+ // before it is folded.
223
+ staged = true
224
+
225
+ constructor(
226
+ spec: ValSpec,
227
+ ctx?: AontuContext
228
+ ) {
229
+ super(spec, ctx)
230
+ }
231
+
232
+
233
+ funcname() {
234
+ return 'pick'
235
+ }
236
+
237
+
238
+ // The base drives neither argument: the DATA is driven by hand below
239
+ // (a staged func must advance what it waits on every pass), and the
240
+ // KEY is a bare word, which the parser has already made a string.
241
+ prepare(_ctx: AontuContext, _args: Val[]) {
242
+ return null
243
+ }
244
+
245
+
246
+ unify(peer: Val, ctx: AontuContext): Val {
247
+ const ready = this.driveStagedArgs(ctx, 1)
248
+
249
+ if (!ready || !ctx.settle) {
250
+ return this.residuate(peer, ctx)
251
+ }
252
+
253
+ return super.unify(peer, ctx)
254
+ }
255
+
256
+
257
+ resolve(ctx: AontuContext, args: Val[]) {
258
+ const children = bagChildren(args?.[0])
259
+ const key: any = args?.[1]
260
+
261
+ if (undefined === children) {
262
+ return this.place(makeNilErr(ctx, 'aggregate_data', this, undefined,
263
+ 'pick'))
264
+ }
265
+
266
+ // The key is a STRING for a map child and the decimal spelling of an
267
+ // index for a list child -- the same rule a reference segment
268
+ // follows, so `pick(d, 0)` and `$.d.0.x` agree about what `0` names.
269
+ const name = null == key?.peg ? undefined :
270
+ 'string' === typeof key.peg ? key.peg :
271
+ 'number' === typeof key.peg && key.isInteger ? String(key.peg) :
272
+ undefined
273
+
274
+ if (undefined === name) {
275
+ return this.place(makeNilErr(ctx, 'invalid-arg', this, undefined,
276
+ 'pick'))
277
+ }
278
+
279
+ const peg: Val[] = []
280
+ for (const child of children) {
281
+ const c: any = child
282
+ const got =
283
+ true === c?.isMap ? c.peg[name] :
284
+ true === c?.isList ? c.peg[Number(name)] :
285
+ undefined
286
+ if (null == got) {
287
+ return this.place(makeNilErr(ctx, 'pick_key', this, undefined, 'pick',
288
+ { key: name }))
289
+ }
290
+ peg.push(got.clone(ctx.descend(String(peg.length))))
291
+ }
292
+ return this.place(new ListVal({ peg }, ctx))
293
+ }
294
+ }
295
+
296
+
297
+ // The three the registry names. Each is its operation and nothing else.
298
+ class SumFuncVal extends AggFuncVal {
299
+ constructor(spec: ValSpec, ctx?: AontuContext) { super(spec, ctx, 'sum') }
300
+ }
301
+
302
+ class LeastFuncVal extends AggFuncVal {
303
+ constructor(spec: ValSpec, ctx?: AontuContext) { super(spec, ctx, 'least') }
304
+ }
305
+
306
+ class GreatestFuncVal extends AggFuncVal {
307
+ constructor(spec: ValSpec, ctx?: AontuContext) {
308
+ super(spec, ctx, 'greatest')
309
+ }
310
+ } /* node:coverage ignore next 9 */
311
+
312
+
313
+ export {
314
+ AggFuncVal,
315
+ PickFuncVal,
316
+ SumFuncVal,
317
+ LeastFuncVal,
318
+ GreatestFuncVal,
319
+ }
@@ -0,0 +1,108 @@
1
+ /* Copyright (c) 2025 Richard Rodger, MIT License */
2
+
3
+
4
+ import type {
5
+ Val,
6
+ ValSpec,
7
+ } from '../type'
8
+
9
+ import {
10
+ AontuContext,
11
+ } from '../ctx'
12
+
13
+ import { FuncBaseVal } from './FuncBaseVal'
14
+ import { arith } from './arith'
15
+ import type { ArithOp } from './arith'
16
+
17
+
18
+ // ONE CLASS FOR SIX FUNCTIONS, because every rule they obey is a rule
19
+ // about arithmetic rather than about any one operation (see arith.ts).
20
+ // Six near-identical classes would be six places for the exact ladder,
21
+ // the zero divisor and the storage contract to drift apart, and the
22
+ // number tower's whole point is that they cannot.
23
+ //
24
+ // The op is carried on the instance and answered by `funcname()`, which
25
+ // is what canon renders and what an error names. The six one-line
26
+ // subclasses below exist only because the parser's registry constructs
27
+ // with `new funcval({peg: args})` and has nowhere to put a name; every
28
+ // line of behaviour is here.
29
+ class ArithFuncVal extends FuncBaseVal {
30
+ isArithFunc = true
31
+
32
+ // The operation this call performs. Carried on the instance because
33
+ // `make` rebuilds the value during residuation and must rebuild the
34
+ // SAME function.
35
+ op: ArithOp
36
+
37
+ constructor(
38
+ spec: ValSpec,
39
+ ctx: AontuContext | undefined,
40
+ op: ArithOp
41
+ ) {
42
+ super(spec, ctx)
43
+ this.op = op
44
+ }
45
+
46
+
47
+ // Rebuilt as its own class, carrying its own op: residuation must not
48
+ // turn a `sub` into a bare arithmetic call with no operation.
49
+ make(_ctx: AontuContext, spec: ValSpec): Val {
50
+ return new (this.constructor as any)(spec, undefined, this.op)
51
+ }
52
+
53
+ funcname() {
54
+ return this.op
55
+ }
56
+
57
+
58
+ resolve(ctx: AontuContext | undefined, args: Val[]) {
59
+ return this.place(arith(ctx, this.op, this, args?.[0], args?.[1]))
60
+ }
61
+
62
+
63
+ // NO superior() OVERRIDE, deliberately. An arithmetic call could only
64
+ // advertise a kind once both its operands were concrete scalars -- and
65
+ // at that point it has RESOLVED, so what `super()` sees is the result,
66
+ // whose own superior is already the right answer:
67
+ // `super(mul(2,3))` is `integer` and `super(mul(2,1.5))` is `float`,
68
+ // through the value rather than through a promise about it. The
69
+ // override was written and then removed as unreachable; the same is
70
+ // true of FuncVal.superior in the Go port.
71
+ }
72
+
73
+
74
+ // The six the registry names. Each is its operation and nothing else.
75
+ class AddFuncVal extends ArithFuncVal {
76
+ constructor(spec: ValSpec, ctx?: AontuContext) { super(spec, ctx, 'add') }
77
+ }
78
+
79
+ class SubFuncVal extends ArithFuncVal {
80
+ constructor(spec: ValSpec, ctx?: AontuContext) { super(spec, ctx, 'sub') }
81
+ }
82
+
83
+ class MulFuncVal extends ArithFuncVal {
84
+ constructor(spec: ValSpec, ctx?: AontuContext) { super(spec, ctx, 'mul') }
85
+ }
86
+
87
+ class DivFuncVal extends ArithFuncVal {
88
+ constructor(spec: ValSpec, ctx?: AontuContext) { super(spec, ctx, 'div') }
89
+ }
90
+
91
+ class ModFuncVal extends ArithFuncVal {
92
+ constructor(spec: ValSpec, ctx?: AontuContext) { super(spec, ctx, 'mod') }
93
+ }
94
+
95
+ class RemFuncVal extends ArithFuncVal {
96
+ constructor(spec: ValSpec, ctx?: AontuContext) { super(spec, ctx, 'rem') }
97
+ } /* node:coverage ignore next 11 */
98
+
99
+
100
+ export {
101
+ ArithFuncVal,
102
+ AddFuncVal,
103
+ SubFuncVal,
104
+ MulFuncVal,
105
+ DivFuncVal,
106
+ ModFuncVal,
107
+ RemFuncVal,
108
+ }
package/src/val/BagVal.ts CHANGED
@@ -49,20 +49,90 @@ abstract class BagVal extends FeatureVal {
49
49
 
50
50
 
51
51
  handleExpectedVal(key: string, val: Val, parent: Val, ctx: AontuContext): Val {
52
- if (val.isGenable) {
52
+ // A MARKED value is carried, never expected (ADR-005 era, BUGS.md
53
+ // §12's include form): a type()/hide()-marked child legitimately
54
+ // participates in unification without ever generating — that is
55
+ // the marks contract — so wrapping one as an expectation turned a
56
+ // schema field arriving through an include's map meet into a
57
+ // bogus `mapval_spread_required` naming a spread that exists in
58
+ // neither file. The bag's gen already skips marked children.
59
+ //
60
+ // An OPERATOR is carried too (BUGS.md §36): an expression is a
61
+ // computation that resolves by itself once its operands do — the
62
+ // bag's own-key loop drives it every pass — so `m:{y:.x+1}`
63
+ // arriving as a peer key must keep computing exactly as it does
64
+ // written inline. Wrapping it froze the op (an expectation only
65
+ // advances when a peer arrives) and the residue then reported the
66
+ // phantom `mapval_spread_required` naming a spread that exists
67
+ // nowhere. An op that truly never resolves is honest *_no_gen
68
+ // residue naming the expression itself.
69
+ if (val.isGenable || val.isOp || val.mark.type || val.mark.hide) {
53
70
  return val
54
71
  }
55
- const expectVal = new ExpectVal({ peg: val }, ctx)
72
+ // An expectation baked into a combined spread template (the
73
+ // 'map-self' meet of two unequal templates) is re-wrapped FRESH, so
74
+ // key/parent name THIS bag and the template's own node is never
75
+ // stored at a destination.
76
+ const expectVal = new ExpectVal({ peg: val.isExpect ? val.peg : val }, ctx)
56
77
  expectVal.key = key
57
78
  expectVal.parent = parent
58
79
  return expectVal
59
80
  }
60
81
 
61
82
 
83
+ // TWO BAGS ARE THE SAME VALUE WHEN THEY HAVE THE SAME SHAPE. Val.same
84
+ // falls back to object IDENTITY, which no two separately built maps
85
+ // share -- so `x:*{a:1}|{a:number}` met by `x:{a:2}` left
86
+ // `{"a":2}|{"a":2}`, a disjunction of one value spelled twice, past
87
+ // the DisjunctVal dedup. Generation's old member FOLD hid that
88
+ // (folding a value with itself is that value); ADR-007 does not, and
89
+ // a disjunction whose alternatives are all the SAME value is
90
+ // resolved, not ambiguous. Canon prints the collapse too, which is
91
+ // the more honest text.
92
+ //
93
+ // Structural, and deliberately strict: container kind, closedness,
94
+ // the marks, the optional keys and the key set must all agree before
95
+ // the children are compared pairwise. Recursion terminates because a
96
+ // reference is not a bag -- RefVal keeps the identity comparison.
97
+ same(peer: any): boolean {
98
+ if (this === peer) {
99
+ return true
100
+ }
101
+ if (null == peer || true !== peer.isBag) {
102
+ return false
103
+ }
104
+ if (this.isMap !== peer.isMap ||
105
+ this.closed !== peer.closed ||
106
+ this.mark.type !== peer.mark.type ||
107
+ this.mark.hide !== peer.mark.hide) {
108
+ return false
109
+ }
110
+
111
+ const keys = Object.keys(this.peg)
112
+ if (keys.length !== Object.keys(peer.peg).length) {
113
+ return false
114
+ }
115
+ if (this.optionalKeys.length !== peer.optionalKeys.length ||
116
+ this.optionalKeys.some((k) => !peer.optionalKeys.includes(k))) {
117
+ return false
118
+ }
119
+
120
+ for (const k of keys) {
121
+ const mine: any = (this.peg as any)[k]
122
+ const theirs: any = (peer.peg as any)[k]
123
+ if (null == mine || null == theirs || !mine.same(theirs)) {
124
+ return false
125
+ }
126
+ }
127
+
128
+ return true
129
+ }
130
+
131
+
62
132
  gen(ctx: AontuContext) {
63
133
  let out: any = this.isMap ? {} : []
64
134
 
65
- if (this.mark.type || this.mark.hide) {
135
+ if ((this.mark.type || this.mark.hide) && true !== ctx?.probe) {
66
136
  return undefined
67
137
  }
68
138
 
@@ -83,7 +153,7 @@ abstract class BagVal extends FeatureVal {
83
153
  const p = item[0]
84
154
  const child = item[1]
85
155
 
86
- if (child.mark.type || child.mark.hide) {
156
+ if ((child.mark.type || child.mark.hide) && true !== ctx?.probe) {
87
157
  continue
88
158
  }
89
159
 
@@ -114,6 +184,14 @@ abstract class BagVal extends FeatureVal {
114
184
  put(cval)
115
185
  }
116
186
 
187
+ // A CONJUNCT IS GENERABLE WHEN IT IS A SETTLED SIZING RESIDUE
188
+ // (the review's finding C, use-cases/BUGS.md §16). `length` and
189
+ // `unique` over a container keep the readings more members could
190
+ // still change, so `a: length(3) a:[1,2,3]` is a conjunct of the
191
+ // atom and the list right up to generation -- which is where the
192
+ // atom decides, in ConjunctVal.gen. Any OTHER conjunct is
193
+ // unresolved and falls through to the residue error below,
194
+ // exactly as before.
117
195
  else if (child.isScalar
118
196
  || child.isMap
119
197
  || child.isList
@@ -121,6 +199,7 @@ abstract class BagVal extends FeatureVal {
121
199
  || child.isRef
122
200
  || child.isDisjunct
123
201
  || child.isNil
202
+ || undefined !== sizingResidue(child)
124
203
  ) {
125
204
  // An optional child is generated in an isolated collect context so an
126
205
  // unresolved inner value (a bare type that survived unification, e.g.
@@ -184,3 +263,21 @@ abstract class BagVal extends FeatureVal {
184
263
  export {
185
264
  BagVal,
186
265
  }
266
+
267
+
268
+ // A conjunct of exactly one sizing constraint and one container: the
269
+ // shape ConstraintVal.admitContainer leaves when its reading is still
270
+ // provisional, and the one ConjunctVal.gen knows how to finish. Kept
271
+ // here rather than as a flag on the conjunct because it is a question
272
+ // about the TERMS, and they can change until the meet converges.
273
+ export function sizingResidue(v: any): { con: any, bag: any } | undefined {
274
+ if (true !== v?.isConjunct || 2 !== v.peg?.length) {
275
+ return undefined
276
+ }
277
+ const [a, b]: any[] = v.peg
278
+ const con = true === a?.isConstraint ? a :
279
+ true === b?.isConstraint ? b : undefined
280
+ const bag = true === a?.isConstraint ? b : a
281
+ return undefined !== con && (true === bag?.isMap || true === bag?.isList) ?
282
+ { con, bag } : undefined
283
+ }
@@ -39,13 +39,21 @@ class CloseFuncVal extends FuncBaseVal {
39
39
 
40
40
 
41
41
  resolve(ctx: AontuContext, args: Val[]) {
42
- const argval: any = args[0]
42
+ let argval: any = args[0]
43
43
 
44
44
  if (null == argval) {
45
45
  return makeNilErr(ctx, 'no_first_arg', this, undefined, 'close')
46
46
  }
47
47
 
48
48
  if (argval.isMap || argval.isList) {
49
+ // The in-place write is safe BECAUSE of the per-destination
50
+ // instantiation rule (ADR-005): everywhere a close() call is
51
+ // multiplied — a pack/each template, a spread constraint — the
52
+ // clone now owns its argument (`dup`), so `closed` lands on that
53
+ // instance alone. Cloning the bag here instead was tried and
54
+ // rejected: the re-path it implies corrupts the source
55
+ // attribution of children inside nested spread templates (the
56
+ // 06-k8s use case's env findings named the wrong path).
49
57
  (argval as BagVal).closed = true
50
58
  // console.log('CLOSED', argval.canon)
51
59
  }
@@ -22,6 +22,7 @@ import {
22
22
 
23
23
 
24
24
  import { JunctionVal } from './JunctionVal'
25
+ import { sizingResidue } from './BagVal'
25
26
 
26
27
  import {
27
28
  explainOpen,
@@ -215,6 +216,25 @@ class ConjunctVal extends JunctionVal {
215
216
 
216
217
 
217
218
  gen(ctx?: AontuContext) {
219
+ // A RESIDUATED SIZING ATOM DECIDES HERE (the review's finding C,
220
+ // use-cases/BUGS.md §16). `length`/`unique` over a container keep
221
+ // the readings that MORE MEMBERS COULD STILL CHANGE -- an upper
222
+ // bound satisfied, a lower bound violated, distinctness so far --
223
+ // rather than deciding against whatever the container held when it
224
+ // first settled (ConstraintVal.admitContainer). Generation is where
225
+ // no more members can arrive, so it is where the provisional
226
+ // reading becomes the verdict: the container generates if the atom
227
+ // is satisfied, and the atom's OWN refusal is raised if it is not.
228
+ //
229
+ // Without this the conjunct would report `conjunct` for a document
230
+ // whose only fault is a length -- the constraint's message is the
231
+ // one the author needs.
232
+ const residue = sizingResidue(this)
233
+ if (undefined !== residue) {
234
+ const settled: any = residue.con.settleContainer(residue.bag, ctx)
235
+ return true === settled?.isNil ? undefined : settled.gen(ctx)
236
+ }
237
+
218
238
  // Unresolved conjunct cannot be generated, so always an error.
219
239
  let nil = makeNilErr(
220
240
  ctx,