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
package/src/val/Val.ts CHANGED
@@ -8,6 +8,8 @@ import {
8
8
  Site
9
9
  } from '../site'
10
10
 
11
+ import { INNER_OF, WRITTEN } from '../provenance'
12
+
11
13
 
12
14
  type ValMark = {
13
15
  type: boolean,
@@ -22,6 +24,21 @@ type ValSpec = {
22
24
  mark?: Partial<ValMark>,
23
25
  kind?: any,
24
26
 
27
+ // THE PER-DESTINATION INSTANTIATION FLAG (ADR-005). A generator's
28
+ // template is cloned once per destination, and every clone must be
29
+ // a full instance: nothing path-dependent may be shared between two
30
+ // destinations, or the first destination's resolution answers for
31
+ // them all. The default clone shares inner structure deliberately
32
+ // (`peg: this.peg` below — the move()/copy() ghost rows in
33
+ // test/spec/func.tsv pin that sharing), so instantiation asks for
34
+ // depth explicitly: `dup: true` makes FuncBaseVal, PrefVal and
35
+ // OpBaseVal clone their inner Vals too, and the bag/junction clones
36
+ // carry the flag down. Set by pack/each template instantiation,
37
+ // filter condition testing, and spread application (MapVal/
38
+ // ListVal.spreadClone) — never by the residuation or ref-resolution
39
+ // clones, whose sharing is pinned behaviour.
40
+ dup?: boolean,
41
+
25
42
 
26
43
  row?: number,
27
44
  col?: number,
@@ -63,6 +80,17 @@ const EMPTY_ERR: any[] = Object.freeze([]) as unknown as any[]
63
80
  let ID = 1000
64
81
 
65
82
 
83
+ // A fresh Val id, for the one carrier that cannot take the one its
84
+ // class fixes: TopVal pins `id = 0` (there is only one top), and the
85
+ // identity mark (G4 phase 1) resolves to a top that must NOT collide
86
+ // with it — the fast path in `unite` returns early on two done Vals
87
+ // with the same id, which would drop an identity before the rider
88
+ // could carry it.
89
+ export function nextValId(): number {
90
+ return ID++
91
+ }
92
+
93
+
66
94
  abstract class Val {
67
95
  // Type-discriminator flags: defaults live on Val.prototype (see
68
96
  // bottom of this file). Each subclass overrides only its own
@@ -134,6 +162,37 @@ abstract class Val {
134
162
  hide: false,
135
163
  }
136
164
 
165
+ // The deprecation record (G3 phase 4, `deprecate(x, m)`): boolean
166
+ // marks cannot hold a message, a replacement path and a version, so
167
+ // the Val carries one optional record. Keys are the three the
168
+ // builtin defines (msg, use, since), all optional, values strings;
169
+ // `use` is a path spelled as a STRING — a live reference would
170
+ // resolve and unify, which is not wanted. Propagated through meets
171
+ // by propagateMarks and carried by clone, exactly as the boolean
172
+ // marks are.
173
+ deprecation?: Record<string, string>
174
+
175
+ // The IDENTITY (G4 phase 1, `id(name)`): the entity this value IS.
176
+ // A separate slot for the same reason the deprecation record has
177
+ // one — a boolean ValMark cannot hold a name — and carried through
178
+ // meets by the same rider in `unite`. Unlike the marks, canon
179
+ // RENDERS it: identity is semantic content, and G6's hash must see
180
+ // it.
181
+ entity?: string
182
+
183
+ // The LINK (G4 phase 2/3): the entity address a `refer` resolved to,
184
+ // stamped on the string it answers. The string IS the value — a
185
+ // link, not an embedding — so nothing downstream could otherwise
186
+ // tell a checked link from a literal that happens to look like one,
187
+ // and the edge set (ts/src/graph.ts) is exactly the set of these.
188
+ link?: string
189
+
190
+ // The GRAPH of an evaluated document (G4 phase 3): the entity index
191
+ // and the edge set, stamped on the result by Aontu.unify the way the
192
+ // include manifest is. Absent on every Val that is not a unify
193
+ // result.
194
+ graph?: any
195
+
137
196
  // Actual native value.
138
197
  peg: any = undefined
139
198
 
@@ -225,11 +284,71 @@ abstract class Val {
225
284
  out.site.row = spec?.row ?? this.site.row
226
285
  out.site.col = spec?.col ?? this.site.col
227
286
  out.site.url = spec?.url ?? this.site.url
287
+ // THE SPAN TRAVELS WITH THE POSITION. Copying row and col but not
288
+ // the extent would leave a site that names a place and denies it has
289
+ // any width — internally inconsistent, and it made the two ports
290
+ // disagree on every derived value (the shared subsume rows caught
291
+ // it). Safe because the span is VERIFIABLE: a consumer reads the
292
+ // document at (row, col, len) and refuses when it does not match
293
+ // `src`, so a span that has stopped describing its value is
294
+ // detectable rather than believed. See ts/src/site.ts.
295
+ //
296
+ // Read from `this.site`, never from the spec: ValSpec.src is a
297
+ // DIFFERENT field — ScalarVal's literal spelling, kept so `$.a.0x0`
298
+ // addresses the key `0x0` — and reading it here would put a path
299
+ // segment where a source span belongs.
300
+ //
301
+ // UNCONDITIONAL, as the Go twin is (clonePath, go/clone.go). A
302
+ // guard dropping the span when the spec relocates the value was
303
+ // written first and the coverage gate refused it as dead: nothing
304
+ // clones to a new row or column. Should a relocating caller ever
305
+ // appear it must drop both fields — an extent belongs to a place,
306
+ // and the text at a new one is not this value's to claim.
307
+ out.site.len = this.site.len
308
+ out.site.src = this.site.src
228
309
 
229
310
  out.mark = Object.assign({}, this.mark, fullspec.mark ?? {})
230
311
  out.mark.type = this.mark.type && (fullspec.mark?.type ?? true)
231
312
  out.mark.hide = this.mark.hide && (fullspec.mark?.hide ?? true)
232
313
 
314
+ // The two IDENTITY riders travel together, under one test: the
315
+ // entity a value IS, and the address a resolved link POINTS AT.
316
+ // One guard rather than two because the pair is what a clone
317
+ // either carries or does not — and because a second test for the
318
+ // link alone would be a branch no document takes, the reference
319
+ // clone catching the pending residual before it ever resolves.
320
+ if (null != this.entity || null != this.link) {
321
+ out.entity = this.entity
322
+ out.link = this.link
323
+ }
324
+ if (null != this.deprecation) {
325
+ out.deprecation = this.deprecation
326
+ }
327
+
328
+ // PROVENANCE TRAVELS WITH THE CLONE, exactly as the site does, and
329
+ // for the same reason: a clone of a value the author wrote IS that
330
+ // written value somewhere else, and it carries the author's site,
331
+ // so it can be pointed at. Without this a default reaching a
332
+ // `pack()`-generated child, or a shape carried by a `$ref`, was
333
+ // invisible to `why` -- which answered "nothing met at this path"
334
+ // over a value it had just printed (the review's finding E). See
335
+ // WRITTEN in ts/src/provenance.ts; the mark is only ever set by an
336
+ // instrumented run, so this is one undefined read otherwise.
337
+ if (true === (this as any)[WRITTEN]) {
338
+ (out as any)[WRITTEN] = true
339
+ }
340
+ // AND SO DOES BEING PART OF SOMETHING. A clone of a disjunction's
341
+ // member is still that member of that written disjunction, and the
342
+ // whole statement is what the author needs shown -- otherwise a
343
+ // default reaching a generated child reports `*"info"` and
344
+ // `string` as two contributions at two columns, where the author
345
+ // wrote `*info | string` once. A number, deliberately: a Val
346
+ // holding another Val as an own property is a cycle through the
347
+ // tree. See INNER_OF in ts/src/provenance.ts.
348
+ if (null != (this as any)[INNER_OF]) {
349
+ (out as any)[INNER_OF] = (this as any)[INNER_OF]
350
+ }
351
+
233
352
  return out
234
353
  }
235
354
 
@@ -237,8 +356,17 @@ abstract class Val {
237
356
  // Shallow clone for spread constraints: creates a new Val with the
238
357
  // correct path context but shares non-path-dependent children.
239
358
  // Override in MapVal/ListVal to avoid deep-cloning simple children.
359
+ //
360
+ // A FULL INSTANCE (`dup`, ADR-005): a spread constraint is applied
361
+ // once per destination child, and each application must own its
362
+ // path-dependent innards — a bare clone shared a call's arguments
363
+ // and a preference's inner value across destinations, so a spread
364
+ // like `&: id(key(0)) & $.schema.C` resolved its one shared key()
365
+ // at the first child it met (use-cases/BUGS.md §12's id_name form).
240
366
  spreadClone(ctx: AontuContext): Val {
241
- return this.clone(ctx)
367
+ const out = this.clone(ctx, { dup: true })
368
+ repathInstance(out, out.path)
369
+ return out
242
370
  }
243
371
 
244
372
 
@@ -278,10 +406,26 @@ abstract class Val {
278
406
  }
279
407
 
280
408
 
409
+ // PUT A MINTED VALUE WHERE THIS ONE STANDS: the site travels, and so
410
+ // does provenance, because the two answer one question. A narrowed
411
+ // disjunction, a lifted kind, a resolved reference -- each is a
412
+ // value the engine built from a value the author wrote, standing
413
+ // where that one stood. Carrying the site and withholding the mark
414
+ // would let `why` print a value, know the line it came from, and
415
+ // still answer "nothing met at this path" (the review's finding E).
416
+ // See WRITTEN and INNER_OF in ts/src/provenance.ts.
281
417
  place(v: Val) {
282
418
  v.site.row = this.site.row
283
419
  v.site.col = this.site.col
284
420
  v.site.url = this.site.url
421
+ v.site.len = this.site.len
422
+ v.site.src = this.site.src
423
+ if (true === (this as any)[WRITTEN]) {
424
+ (v as any)[WRITTEN] = true
425
+ }
426
+ if (null != (this as any)[INNER_OF]) {
427
+ (v as any)[INNER_OF] = (this as any)[INNER_OF]
428
+ }
285
429
  return v
286
430
  }
287
431
 
@@ -431,6 +575,64 @@ Object.assign(Val.prototype, {
431
575
  })
432
576
 
433
577
 
578
+ // THE INSTANCE PATH NORMALISATION (ADR-005), the TS mirror of the Go
579
+ // port's setPaths (go/clone.go): assign every value in a freshly
580
+ // instantiated template the path the PARSER would have given it at the
581
+ // instance's destination. A deep instance clone (`dup`) copies values
582
+ // whose stored parse paths are argument-shaped — a func argument has
583
+ // no key of its own, a spread template lives under a '&' segment — and
584
+ // Val.clone's ctx-cut cannot rebase those: it derives the child path
585
+ // from the driving ctx alone and drops the segments in between, which
586
+ // is how a nested list spread inside a close()d template lost its
587
+ // parent key and every finding under it named the wrong path (the
588
+ // 06-k8s use case's env findings). One canonical walk instead:
589
+ // bag children descend by key (numeric for a list element, as the
590
+ // parser records them), a spread constraint sits under '&' with its
591
+ // content at the bag's own path, and junction members, operator
592
+ // operands, function arguments and a preference's value all sit AT
593
+ // their holder's path — exactly the parse-time shape.
594
+ function repathInstance(v: any, path: string[]): void {
595
+ if (true !== v?.isVal) {
596
+ return
597
+ }
598
+ v.path = path
599
+
600
+ const peg = v.peg
601
+
602
+ if (true === v.isBag) {
603
+ const spread = v.spread?.cj
604
+ if (null != spread && true === spread.isVal) {
605
+ // The spread's CONTENT is pathed at the bag (its fields land on
606
+ // the bag's children); only its ROOT carries the '&' segment —
607
+ // the same two steps as the Go twin's setPaths.
608
+ repathInstance(spread, path)
609
+ spread.path = [...path, '&']
610
+ }
611
+ if (true === v.isList) {
612
+ for (let i = 0; i < peg.length; i++) {
613
+ // Numeric, as the parser records list positions: a numeric
614
+ // segment is what tells key() an element is not a keyed
615
+ // position (KeyFuncVal.resolve, `positioned`).
616
+ repathInstance(peg[i], [...path, i as unknown as string])
617
+ }
618
+ }
619
+ else {
620
+ for (const k of Object.keys(peg)) {
621
+ repathInstance(peg[k], [...path, k])
622
+ }
623
+ }
624
+ }
625
+ else if (Array.isArray(peg)) {
626
+ for (const t of peg) {
627
+ repathInstance(t, path)
628
+ }
629
+ }
630
+ else if (true === peg?.isVal) {
631
+ repathInstance(peg, path)
632
+ }
633
+ }
634
+
635
+
434
636
  function inspectpeg(peg: any, d: number) {
435
637
  const indent = ' '.repeat(d)
436
638
  return pretty(Array.isArray(peg) ?
@@ -474,5 +676,6 @@ export {
474
676
  DONE,
475
677
  SPREAD,
476
678
  EMPTY_ERR,
477
- empty
679
+ empty,
680
+ repathInstance,
478
681
  }
package/src/val/VarVal.ts CHANGED
@@ -24,7 +24,6 @@ import {
24
24
 
25
25
 
26
26
  import { StringVal } from './StringVal'
27
- import { NilVal } from './NilVal'
28
27
  import { FeatureVal } from './FeatureVal'
29
28
  import { NullVal } from './NullVal'
30
29
  import { BooleanVal } from './BooleanVal'
@@ -0,0 +1,316 @@
1
+ /* Copyright (c) 2025 Richard Rodger, MIT License */
2
+
3
+
4
+ // THE ARITHMETIC FAMILY -- add, sub, mul, div, mod, rem.
5
+ //
6
+ // The review's finding I: "Arithmetic stops at `+`", so "prod gets
7
+ // double the replicas" is inexpressible and Kubernetes quantity strings
8
+ // silently CONCATENATE ("500m" + "500m" is "500m500m"). The design
9
+ // pre-registered the semantics these functions must have
10
+ // (docs/capability-review/g8-generation.md, "Arithmetic semantics,
11
+ // pre-registered") and the boundary that keeps `-` `*` `/` `%`
12
+ // reserved: maths arrives as functions or not at all.
13
+ //
14
+ // THE FAMILY IS NUMERIC, WHICH IS WHAT MAKES `add` MORE THAN A SYNONYM
15
+ // FOR `+`. The operator is polymorphic -- concatenation for strings,
16
+ // disjunction for booleans, addition for numbers -- and that is why the
17
+ // quantity strings above concatenate instead of failing. `add("500m",
18
+ // "500m")` is a located error, because a function named for a numeric
19
+ // operation has no business inventing a string. So the two spellings
20
+ // mean different things, and both are kept.
21
+ //
22
+ // Everything else here is the number tower's existing law, applied to
23
+ // five more operations (docs/design/number-model.md):
24
+ //
25
+ // R5 CONTAGION no operation introduces a kind narrower than its
26
+ // operands, so an integer result needs integer
27
+ // operands.
28
+ // D6 EXACT LADDER integer < biginteger < bigdecimal. A mixed exact
29
+ // operation promotes to the WIDEST operand, is
30
+ // computed exactly, and never demotes.
31
+ // FLOAT IS OFF IT binary64 mixed with either big leaf is a hard
32
+ // error in both operand orders, because promotion
33
+ // either way throws away exactness the document
34
+ // asked for by writing `0d`.
35
+ // NO ROUNDING an exact result that will not store is refused,
36
+ // never rounded to fit.
37
+ //
38
+ // and three refusals this file adds, which the pre-registration named:
39
+ //
40
+ // div/mod/rem by ZERO is a hard error in every kind. A ground-truth
41
+ // language has no business manufacturing infinity, and Aontu cannot
42
+ // even write one down -- an overflowing literal is a `not_number`
43
+ // error nil -- so propagating one would invent a value no generated
44
+ // JSON could carry.
45
+ //
46
+ // A NON-FINITE FLOAT RESULT is a located error. This one was already
47
+ // reachable through `+` and reported as neither: `1.0e308+1.0e308`
48
+ // crashed TypeScript with `[aontu/internal]` and leaked Go's raw
49
+ // `json: unsupported value: +Inf` with no code at all (use-cases/
50
+ // BUGS.md 39). PlusOpVal and its Go twin now go through the same
51
+ // check.
52
+ //
53
+ // DIVISION IS NOT CLOSED OVER THE DECIMAL LEAF, so div/mod/rem refuse
54
+ // a bigdecimal operand rather than rounding one third to fit. See
55
+ // `inexact_divide`'s hint: scale to integers, which is the money wire
56
+ // convention anyway, or accept a float.
57
+
58
+
59
+ import type { Val } from '../type'
60
+
61
+ import { AontuContext } from '../ctx'
62
+ import { makeNilErr } from '../err'
63
+
64
+ import { IntegerVal } from './IntegerVal'
65
+ import { NumberVal } from './NumberVal'
66
+ import { BigIntegerVal } from './BigIntegerVal'
67
+ import { BigDecimalVal } from './BigDecimalVal'
68
+ import { Decimal, decimalOverBudget } from './Decimal'
69
+ import { isIntegerStorable } from './numkind'
70
+
71
+
72
+ type ArithOp = 'add' | 'sub' | 'mul' | 'div' | 'mod' | 'rem'
73
+
74
+ // The three that divide, and therefore the three that can be handed a
75
+ // zero divisor and cannot answer over the decimal leaf.
76
+ function divides(op: ArithOp): boolean {
77
+ return 'div' === op || 'mod' === op || 'rem' === op
78
+ }
79
+
80
+
81
+ // The numeric leaves, told apart by their own flags rather than by the
82
+ // JavaScript type of the peg: `integer` and `float` share `number`.
83
+ type ArithKind = 'integer' | 'float' | 'biginteger' | 'bigdecimal'
84
+
85
+ const EXACT_RANK: Record<string, number> = {
86
+ integer: 1,
87
+ biginteger: 2,
88
+ bigdecimal: 3,
89
+ }
90
+
91
+
92
+ function isBig(k: ArithKind): boolean {
93
+ return 'biginteger' === k || 'bigdecimal' === k
94
+ }
95
+
96
+
97
+ // A pref operand contributes its preferred value, and therefore that
98
+ // value's kind too -- the same rule `+` applies.
99
+ function unpref(v: any): any {
100
+ while (v?.isPref) {
101
+ v = v.peg
102
+ }
103
+ return v
104
+ }
105
+
106
+
107
+ function arithKind(v: any): ArithKind | undefined {
108
+ if (!(v?.isVal && v.isScalar)) {
109
+ return undefined
110
+ }
111
+ if (v.isBigInteger) {
112
+ return 'biginteger'
113
+ }
114
+ if (v.isBigDecimal) {
115
+ return 'bigdecimal'
116
+ }
117
+ if (v.isInteger) {
118
+ return 'integer'
119
+ }
120
+ return 'number' === typeof v.peg ? 'float' : undefined
121
+ }
122
+
123
+
124
+ // An exact-ladder operand as an exact integer. Only reached for the two
125
+ // integral leaves; an `integer` peg is integral by construction.
126
+ function asInteger(v: any, k: ArithKind): bigint {
127
+ return 'biginteger' === k ? v.peg : BigInt(v.peg)
128
+ }
129
+
130
+
131
+ function asDecimal(v: any, k: ArithKind): Decimal {
132
+ return 'bigdecimal' === k ? v.peg : new Decimal(asInteger(v, k), 0)
133
+ }
134
+
135
+
136
+ // The whole family, in one function, because every rule above is a rule
137
+ // about ARITHMETIC and not about any one operation. `node` is the value
138
+ // the error is located at -- the call, or the `+` op.
139
+ // `attempt` is the name the ERROR reports, which is the operation
140
+ // except when a fold borrows one: `sum` adds, but a bad member is the
141
+ // author's `sum` call and must say so.
142
+ function arith(
143
+ ctx: AontuContext | undefined,
144
+ op: ArithOp,
145
+ node: Val,
146
+ a: Val,
147
+ b: Val,
148
+ attempt?: string
149
+ ): Val {
150
+ const name = attempt ?? op
151
+ const av: any = unpref(a)
152
+ const bv: any = unpref(b)
153
+ const ak = arithKind(av)
154
+ const bk = arithKind(bv)
155
+
156
+ // A non-numeric operand is not something to wait for: `resolve` is
157
+ // only reached once every argument has settled, so a kind, a map, a
158
+ // string or a boolean here is the author's mistake and is named as
159
+ // one. (`+` differs, and must: it has answers for strings and
160
+ // booleans.)
161
+ if (undefined === ak || undefined === bk) {
162
+ return makeNilErr(ctx, 'invalid-arg', node, undefined, name)
163
+ }
164
+
165
+ // A big leaf never silently becomes a binary float, in EITHER operand
166
+ // order. The error names both leaves in operand order.
167
+ if (('float' === ak && isBig(bk)) || (isBig(ak) && 'float' === bk)) {
168
+ return makeNilErr(ctx, 'exact_float_mix', node, undefined, name,
169
+ { left: ak, right: bk })
170
+ }
171
+
172
+ if ('float' === ak || 'float' === bk) {
173
+ return floatArith(ctx, op, name, node, av.peg, bv.peg)
174
+ }
175
+
176
+ const rank = EXACT_RANK[bk] < EXACT_RANK[ak] ? EXACT_RANK[ak] : EXACT_RANK[bk]
177
+
178
+ if (EXACT_RANK.bigdecimal === rank) {
179
+ return decimalArith(ctx, op, name, node, asDecimal(av, ak), asDecimal(bv, bk))
180
+ }
181
+
182
+ return integerArith(ctx, op, name, node, asInteger(av, ak),
183
+ asInteger(bv, bk), EXACT_RANK.biginteger === rank)
184
+ }
185
+
186
+
187
+ // IEEE-754 binary64, with the JSON-superset constraint still biting: an
188
+ // infinite or NaN result is a located error rather than a value, because
189
+ // there is no way to write one down and no JSON that could carry it.
190
+ function floatArith(
191
+ ctx: AontuContext | undefined,
192
+ op: ArithOp,
193
+ name: string,
194
+ node: Val,
195
+ x: number,
196
+ y: number
197
+ ): Val {
198
+ if (divides(op) && 0 === y) {
199
+ return makeNilErr(ctx, 'divide_by_zero', node, undefined, name)
200
+ }
201
+
202
+ const out =
203
+ 'add' === op ? x + y :
204
+ 'sub' === op ? x - y :
205
+ 'mul' === op ? x * y :
206
+ 'div' === op ? x / y :
207
+ // Truncated remainder, sign following the DIVIDEND, which is
208
+ // what JavaScript's `%` and Go's math.Mod both give...
209
+ 'rem' === op ? x % y :
210
+ // ...and the floored modulus, sign following the DIVISOR,
211
+ // built from it. Adding the divisor back moves a remainder
212
+ // whose sign disagrees into agreement, and leaves an exact
213
+ // zero alone.
214
+ flooredMod(x % y, y)
215
+
216
+ return Number.isFinite(out) ?
217
+ new NumberVal({ peg: out }) :
218
+ makeNilErr(ctx, 'float_overflow', node, undefined, name)
219
+ }
220
+
221
+
222
+ function flooredMod(rem: number, y: number): number {
223
+ return 0 !== rem && (rem < 0) !== (y < 0) ? rem + y : rem
224
+ }
225
+
226
+
227
+ // The exact integral leaves. Both compute in bigint, so nothing passes
228
+ // through binary64 and nothing rounds; only the storage test at the end
229
+ // differs, because `biginteger` is unbounded and `integer` is not.
230
+ function integerArith(
231
+ ctx: AontuContext | undefined,
232
+ op: ArithOp,
233
+ name: string,
234
+ node: Val,
235
+ x: bigint,
236
+ y: bigint,
237
+ big: boolean
238
+ ): Val {
239
+ if (divides(op) && 0n === y) {
240
+ return makeNilErr(ctx, 'divide_by_zero', node, undefined, name)
241
+ }
242
+
243
+ const out =
244
+ 'add' === op ? x + y :
245
+ 'sub' === op ? x - y :
246
+ 'mul' === op ? x * y :
247
+ // TRUNCATION TOWARD ZERO, stated once here rather than left to
248
+ // whichever host `/` each port happens to call: div(-7, 2) is
249
+ // -3, not -4. BigInt division truncates, and so does Go's
250
+ // big.Int.Quo (its Div floors, which is why the Go twin must
251
+ // not use it).
252
+ 'div' === op ? x / y :
253
+ 'rem' === op ? x % y :
254
+ flooredModBig(x % y, y)
255
+
256
+ if (big) {
257
+ // Unbounded and exact: nothing to check, and no demotion to
258
+ // `integer` however small the result.
259
+ return new BigIntegerVal({ peg: out })
260
+ }
261
+
262
+ // The result faces the SAME storage contract R1 puts on a literal --
263
+ // integral, inside the int64 window, and exactly representable in
264
+ // binary64 -- because Go's int64 holds results TypeScript's double
265
+ // cannot, and without a shared test a document would resolve in one
266
+ // port and round in the other.
267
+ return isIntegerStorable(out) ?
268
+ new IntegerVal({ peg: Number(out) }) :
269
+ makeNilErr(ctx, 'inexact_integer_sum', node, undefined, name,
270
+ { sum: out.toString() })
271
+ }
272
+
273
+
274
+ function flooredModBig(rem: bigint, y: bigint): bigint {
275
+ return 0n !== rem && (rem < 0n) !== (y < 0n) ? rem + y : rem
276
+ }
277
+
278
+
279
+ // The decimal leaf. Addition, subtraction and multiplication are exact
280
+ // coefficient arithmetic and land here; division does not, and says so.
281
+ function decimalArith(
282
+ ctx: AontuContext | undefined,
283
+ op: ArithOp,
284
+ name: string,
285
+ node: Val,
286
+ x: Decimal,
287
+ y: Decimal
288
+ ): Val {
289
+ if (divides(op)) {
290
+ // EXACT DECIMAL DIVISION IS NOT CLOSED: one third has no finite
291
+ // decimal form, so a `div` over this leaf either rounds -- the one
292
+ // thing the leaf exists to refuse -- or refuses. It refuses, and the
293
+ // hint names both ways out.
294
+ return makeNilErr(ctx, 'inexact_divide', node, undefined, name)
295
+ }
296
+
297
+ const out =
298
+ 'add' === op ? x.add(y) :
299
+ 'sub' === op ? x.add(y.negate()) :
300
+ x.multiply(y)
301
+
302
+ // The budget applies to RESULTS as well as literals: an exact answer
303
+ // too wide to hold is refused, never rounded to fit.
304
+ return decimalOverBudget(out) ?
305
+ makeNilErr(ctx, 'decimal_budget', node, undefined, name) :
306
+ new BigDecimalVal({ peg: out })
307
+ } /* node:coverage ignore next 9 */
308
+
309
+
310
+ export type {
311
+ ArithOp,
312
+ }
313
+
314
+ export {
315
+ arith,
316
+ }