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
@@ -17,6 +17,8 @@ import { unite } from '../unify'
17
17
 
18
18
  import {
19
19
  propagateMarks,
20
+ canonRiders,
21
+ constantIdFunc,
20
22
  explainOpen,
21
23
  ec,
22
24
  explainClose,
@@ -32,6 +34,8 @@ import {
32
34
  import { ConjunctVal } from './ConjunctVal'
33
35
  import { NilVal } from './NilVal'
34
36
  import { BagVal } from './BagVal'
37
+ import { repathInstance } from './Val'
38
+ import { markSpread } from '../provenance'
35
39
 
36
40
 
37
41
  class ListVal extends BagVal {
@@ -55,7 +59,10 @@ class ListVal extends BagVal {
55
59
  if (spread) {
56
60
  if ('&' === spread.o) {
57
61
 
58
- // TODO: handle existing spread!
62
+ // Multiple same-level spreads conjoin; an unequal spread from
63
+ // another statement meets this one in unify's combination
64
+ // below (see the MapVal constructor note — the combined
65
+ // template is stateless, BUGS.md §6-§7).
59
66
  this.spread.cj =
60
67
  Array.isArray(spread.v) ?
61
68
  1 < spread.v.length ?
@@ -63,6 +70,22 @@ class ListVal extends BagVal {
63
70
  spread.v[0] :
64
71
  spread.v
65
72
 
73
+ // Clearing rule 3 (G4 phase 1): a CONSTANT id in the template
74
+ // would declare every child to be one entity. The refusal
75
+ // replaces the template, so it reaches every child and the
76
+ // bag itself (see the isNil arm where the spread is applied)
77
+ // as ONE nil identity — made here, once, rather than per
78
+ // pass, so the report names it once.
79
+ const idfn: any = constantIdFunc(this.spread.cj)
80
+ if (undefined !== idfn) {
81
+ const nil: any = new NilVal({ why: 'id_spread' }, ctx)
82
+ nil.site.row = idfn.site.row
83
+ nil.site.col = idfn.site.col
84
+ nil.site.url = idfn.site.url
85
+ nil.primary = idfn
86
+ this.spread.cj = nil
87
+ }
88
+
66
89
  // let tmv = Array.isArray(spread.v) ? spread.v : [spread.v]
67
90
  // this.spread.cj = new ConjunctVal({ peg: tmv }, ctx)
68
91
  }
@@ -121,24 +144,81 @@ class ListVal extends BagVal {
121
144
 
122
145
  let spread_cj = out.spread.cj || TOP
123
146
 
147
+ // The template REFUSED at construction (clearing rule 3, G4
148
+ // phase 1): the bag itself is that refusal. Returning the nil
149
+ // here rather than only letting it reach the children is what
150
+ // makes an EMPTY bag with a bad template an error too — there
151
+ // are no children to carry it.
152
+ //
153
+ // Narrow to THIS code on purpose. A nil spread from any other
154
+ // cause keeps its existing behaviour of driving every key
155
+ // (coverage3 `nil-spread-drives-every-key`): a template that has
156
+ // merely not resolved yet must not permanently kill the bag that
157
+ // holds it.
158
+ if ('id_spread' === (spread_cj as any).why) {
159
+ return spread_cj
160
+ }
161
+
124
162
  // Always unify children first
125
163
  for (let key in this.peg) {
126
164
  const keyctx = ctx.descend(key)
127
- const key_spread_cj = spread_cj.spreadClone(keyctx)
128
165
  const child = this.peg[key]
129
166
 
130
167
  propagateMarks(this, child)
131
168
 
132
- // child is non-nullish: propagateMarks above dereferences it.
133
- out.peg[key] =
134
- child.isNil ? child :
135
- key_spread_cj.isNil ? key_spread_cj :
136
- key_spread_cj.isTop && child.done ? child :
137
- child.isTop && key_spread_cj.done ? key_spread_cj :
138
- unite(te ? keyctx.clone({ explain: ec(te, 'PEG:' + key) }) : keyctx,
139
- child, key_spread_cj, 'list-own')
169
+ // APPLIED ONCE PER ELEMENT, the guard MapVal has carried since
170
+ // the spread was written: an element that already holds this
171
+ // template's contribution is progressed by self-unification
172
+ // instead of having the template met into it a second time.
173
+ // Re-applying is the identity for a template that has already
174
+ // RESOLVED, which is why the missing guard went unnoticed here
175
+ // — but a template that residuates (`&: id(key(1))`, G8 phase
176
+ // 0) is not yet a value to be idempotent about, so each pass
177
+ // conjoined another copy and the element's canon DOUBLED per
178
+ // pass. The old `ctx.cc < 3` key delay hid it by ending the
179
+ // growth at three passes; the staging rule waits for the model
180
+ // to settle, and a model whose canon doubles every pass never
181
+ // does.
182
+ let oval: Val
183
+ if (!spread_cj.isTop
184
+ && (child as any)._spr === (spread_cj as any).id) {
185
+ oval = child.done ? child :
186
+ unite(te ? keyctx.clone({ explain: ec(te, 'PEG:' + key) }) : keyctx,
187
+ child, TOP, 'list-own')
188
+ ; (oval as any)._spr = (spread_cj as any).id
189
+ }
190
+ else {
191
+ const key_spread_cj = spread_cj.spreadClone(keyctx)
192
+ // The spread mark the provenance recorder reads (G7 phase 3),
193
+ // as in MapVal: this is where a template becomes a per-element
194
+ // contribution. Instrumented runs only.
195
+ if (undefined !== keyctx.prov) {
196
+ markSpread(key_spread_cj)
197
+ }
198
+
199
+ // child is non-nullish: propagateMarks above dereferences it.
200
+ oval =
201
+ child.isNil ? child :
202
+ key_spread_cj.isNil ? key_spread_cj :
203
+ // The no-op meet is SKIPPED on the normal path (it is the
204
+ // identity) but TAKEN while recording: a value written once
205
+ // and never met is still a contribution the author wants
206
+ // pointed at, and the Go port's unite sees that meet (G7
207
+ // phase 4). Instrumented runs pay knowingly.
208
+ key_spread_cj.isTop && child.done && undefined === keyctx.prov
209
+ ? child :
210
+ child.isTop && key_spread_cj.done ? key_spread_cj :
211
+ unite(te ? keyctx.clone({ explain: ec(te, 'PEG:' + key) }) : keyctx,
212
+ child, key_spread_cj, 'list-own')
213
+
214
+ if (!spread_cj.isTop && !oval.isNil) {
215
+ ; (oval as any)._spr = (spread_cj as any).id
216
+ }
217
+ }
218
+
219
+ out.peg[key] = oval
140
220
 
141
- done = (done && DONE === out.peg[key].dc)
221
+ done = (done && DONE === oval.dc)
142
222
  }
143
223
 
144
224
  const allowedKeys: string[] = this.closed ? Object.keys(this.peg) : []
@@ -171,6 +251,9 @@ class ListVal extends BagVal {
171
251
 
172
252
  if (this.spread.cj) {
173
253
  let key_spread_cj = spread_cj.spreadClone(peerctx)
254
+ if (undefined !== peerctx.prov) {
255
+ markSpread(key_spread_cj)
256
+ }
174
257
 
175
258
  oval = out.peg[peerkey] =
176
259
  unite(te ? peerctx.clone({ explain: ec(te, 'PSP:' + peerkey) }) : peerctx,
@@ -226,7 +309,11 @@ class ListVal extends BagVal {
226
309
  }
227
310
 
228
311
  if (!allScalarKind) {
229
- return this.clone(ctx)
312
+ // A full instance (`dup`, ADR-005), paths normalised to the
313
+ // destination: see Val.spreadClone and repathInstance.
314
+ const out = this.clone(ctx, { dup: true })
315
+ repathInstance(out, out.path)
316
+ return out
230
317
  }
231
318
 
232
319
  let out = (super.clone(ctx) as ListVal)
@@ -249,12 +336,16 @@ class ListVal extends BagVal {
249
336
 
250
337
  clone(ctx: AontuContext, spec?: ValSpec): Val {
251
338
  let out = (super.clone(ctx, spec) as ListVal)
339
+ // The instantiation flag descends with the mark (ADR-005): a
340
+ // template's elements are part of the instance.
341
+ const childspec = spec?.mark || spec?.dup ?
342
+ { mark: spec?.mark, dup: spec?.dup } : {}
252
343
  for (let entry of Object.entries(this.peg)) {
253
344
  out.peg[entry[0]] =
254
- (entry[1] as any)?.isVal ? (entry[1] as Val).clone(ctx, spec?.mark ? { mark: spec.mark } : {}) : entry[1]
345
+ (entry[1] as any)?.isVal ? (entry[1] as Val).clone(ctx, childspec) : entry[1]
255
346
  }
256
347
  if (this.spread.cj) {
257
- out.spread.cj = this.spread.cj.clone(ctx, spec?.mark ? { mark: spec.mark } : {})
348
+ out.spread.cj = this.spread.cj.clone(ctx, childspec)
258
349
  }
259
350
 
260
351
  out.closed = this.closed
@@ -280,7 +371,9 @@ class ListVal extends BagVal {
280
371
  // key. The Go port's ListVal.Canon has no such arm either, and the
281
372
  // two canons must agree -- a canon is round-trippable, and a marker
282
373
  // on an element the grammar cannot produce would not reparse.
283
- keys.map(k => this.peg[k].canon).join(',') +
374
+ // canonRiders, not .canon: a deprecated element renders
375
+ // back as its `deprecate(x, m)` call, reparseably (G3).
376
+ keys.map(k => canonRiders(this.peg[k])).join(',') +
284
377
  ']'
285
378
  }
286
379
  } /* node:coverage ignore next 8 */
@@ -13,7 +13,6 @@ import {
13
13
  import { makeNilErr } from '../err'
14
14
 
15
15
 
16
- import { NilVal } from '../val/NilVal'
17
16
  import { ScalarKindVal } from '../val/ScalarKindVal'
18
17
  import { makeScalarLike } from '../val/valutil'
19
18
  import { Decimal } from '../val/Decimal'
package/src/val/MapVal.ts CHANGED
@@ -16,6 +16,8 @@ import { unite } from '../unify'
16
16
 
17
17
  import {
18
18
  propagateMarks,
19
+ canonRiders,
20
+ constantIdFunc,
19
21
  walk,
20
22
  explainOpen,
21
23
  ec,
@@ -32,7 +34,9 @@ import {
32
34
  import { ConjunctVal } from './ConjunctVal'
33
35
  import { NilVal } from './NilVal'
34
36
  import { BagVal } from './BagVal'
37
+ import { repathInstance } from './Val'
35
38
  import { cmpCodePoint } from '../keyorder'
39
+ import { markSpread } from '../provenance'
36
40
 
37
41
 
38
42
  // Structural snapshots of ref spreads (see MapVal.unify), keyed by the
@@ -60,7 +64,9 @@ function snapshotRefSpread(cj: any, ctx: AontuContext): Val | undefined {
60
64
  const sk = spreadSnapKey(cj)
61
65
  let snap: Val | undefined = snapmap.get(sk)
62
66
  if (undefined === snap) {
63
- let tgt: Val | undefined = cj.find(ctx)
67
+ // snap mode: the pending-mark-wrapper defer in find must not
68
+ // apply here — the snapshot WANTS the pre-resolution structure.
69
+ let tgt: Val | undefined = cj.find(ctx, true)
64
70
  // A ref to a type() resolves to its inner template — snapshot that,
65
71
  // so a type-wrapped ref behaves like a plain-map ref spread.
66
72
  if (tgt && (tgt as any).isTypeFunc) tgt = (tgt as any).peg?.[0]
@@ -104,13 +110,34 @@ class MapVal extends BagVal {
104
110
 
105
111
  if (spread) {
106
112
  if ('&' === spread.o) {
107
- // TODO: handle existing spread!
113
+ // Multiple same-level spreads arrive as an array and conjoin;
114
+ // an unequal spread arriving from ANOTHER statement meets this
115
+ // one in unify's spread combination below — sound since the
116
+ // combined template became stateless (pure ExpectVal, BUGS.md
117
+ // §6-§7): each child meets the combined constraint
118
+ // independently and children never meet each other's data.
108
119
  this.spread.cj =
109
120
  Array.isArray(spread.v) ?
110
121
  1 < spread.v.length ?
111
122
  new ConjunctVal({ peg: spread.v }, ctx) :
112
123
  spread.v[0] :
113
124
  spread.v
125
+
126
+ // Clearing rule 3 (G4 phase 1): a CONSTANT id in the template
127
+ // would declare every child to be one entity. The refusal
128
+ // replaces the template, so it reaches every child and the
129
+ // bag itself (see the isNil arm where the spread is applied)
130
+ // as ONE nil identity — made here, once, rather than per
131
+ // pass, so the report names it once.
132
+ const idfn: any = constantIdFunc(this.spread.cj)
133
+ if (undefined !== idfn) {
134
+ const nil: any = new NilVal({ why: 'id_spread' }, ctx)
135
+ nil.site.row = idfn.site.row
136
+ nil.site.col = idfn.site.col
137
+ nil.site.url = idfn.site.url
138
+ nil.primary = idfn
139
+ this.spread.cj = nil
140
+ }
114
141
  }
115
142
  }
116
143
 
@@ -184,6 +211,13 @@ class MapVal extends BagVal {
184
211
  // combined here). unite resolves key()/path() at each destination via
185
212
  // spreadClone below, so nested + sibling key() cases stay correct
186
213
  // (test/spec/spread-nested-key, spread-key-all).
214
+ //
215
+ // The combined template must stay STATELESS: this meet wraps a
216
+ // key present in only one side as an ExpectVal, the combined
217
+ // map is shared across destinations when path-independent
218
+ // (spreadClone tier 1), and a stateful expect accumulated the
219
+ // first sibling's data and met it into the next (BUGS.md
220
+ // §6-§7). ExpectVal.unify is pure for exactly this reason.
187
221
  out.spread.cj = null == out.spread.cj ? peer.spread.cj : (
188
222
  null == peer.spread.cj ? out.spread.cj :
189
223
  out.spread.cj.canon === peer.spread.cj.canon ? out.spread.cj :
@@ -204,6 +238,21 @@ class MapVal extends BagVal {
204
238
 
205
239
  let spread_cj = out.spread.cj ?? TOP
206
240
 
241
+ // The template REFUSED at construction (clearing rule 3, G4
242
+ // phase 1): the bag itself is that refusal. Returning the nil
243
+ // here rather than only letting it reach the children is what
244
+ // makes an EMPTY bag with a bad template an error too — there
245
+ // are no children to carry it.
246
+ //
247
+ // Narrow to THIS code on purpose. A nil spread from any other
248
+ // cause keeps its existing behaviour of driving every key
249
+ // (coverage3 `nil-spread-drives-every-key`): a template that has
250
+ // merely not resolved yet must not permanently kill the bag that
251
+ // holds it.
252
+ if ('id_spread' === (spread_cj as any).why) {
253
+ return spread_cj
254
+ }
255
+
207
256
  // Snapshot a path-dependent *ref* spread to its structural target
208
257
  // once (while inner key()/path() funcs are still unresolved), so
209
258
  // later fixpoint passes don't re-resolve the ref against the mutated
@@ -252,11 +301,25 @@ class MapVal extends BagVal {
252
301
  else {
253
302
  const key_spread_cj = spread_cj.spreadClone(keyctx)
254
303
 
304
+ // The one place a spread is APPLIED, so the one place that
305
+ // knows a contribution came from a template rather than
306
+ // from the key itself (G7 phase 3). Only when someone is
307
+ // recording: the walk is O(template) per key per pass.
308
+ if (undefined !== keyctx.prov) {
309
+ markSpread(key_spread_cj)
310
+ }
311
+
255
312
  // child is non-nullish: propagateMarks above dereferences it.
256
313
  oval =
257
314
  child.isNil ? child :
258
315
  key_spread_cj.isNil ? key_spread_cj :
259
- key_spread_cj.isTop && child.done ? child :
316
+ // The no-op meet is SKIPPED on the normal path (it is the
317
+ // identity) but TAKEN while recording: a value written once
318
+ // and never met is still a contribution the author wants
319
+ // pointed at, and the Go port's unite sees that meet (G7
320
+ // phase 4). Instrumented runs pay knowingly.
321
+ key_spread_cj.isTop && child.done && undefined === keyctx.prov
322
+ ? child :
260
323
  child.isTop && key_spread_cj.done ? key_spread_cj :
261
324
  unite(te ? keyctx.clone({ explain: ec(te, 'KEY:' + key) }) : keyctx,
262
325
  child, key_spread_cj, 'map-own')
@@ -268,6 +331,7 @@ class MapVal extends BagVal {
268
331
 
269
332
  out.peg[key] = oval
270
333
 
334
+
271
335
  done = (done && DONE === oval.dc)
272
336
  }
273
337
 
@@ -296,7 +360,19 @@ class MapVal extends BagVal {
296
360
  const peerctx = ctx.descend(peerkey)
297
361
 
298
362
  let oval = out.peg[peerkey] =
299
- undefined === child ? this.handleExpectedVal(peerkey, peerchild, this, ctx) :
363
+ // A peer-only key is CARRIED, not met — except on an
364
+ // instrumented run, where the identity meet is taken so
365
+ // the recorder sees where the value came from. The Go port
366
+ // unites a genable peer-only child with TOP unconditionally
367
+ // (go/mapval.go), and the difference was invisible until
368
+ // G4's identity merge brought a peer whose children the
369
+ // recorder counts as WRITTEN: `why $.b.k` on two positions
370
+ // of one entity named the site in Go and answered "nothing
371
+ // met here" in TypeScript.
372
+ undefined === child
373
+ ? (undefined !== peerctx.prov && peerchild.isGenable
374
+ ? unite(peerctx, peerchild, TOP, 'map-peer-only')
375
+ : this.handleExpectedVal(peerkey, peerchild, this, ctx)) :
300
376
  child.isTop && peerchild.done ? peerchild :
301
377
  child.isNil ? child :
302
378
  peerchild.isNil ? peerchild :
@@ -382,7 +458,11 @@ class MapVal extends BagVal {
382
458
  }
383
459
 
384
460
  if (!allScalarKind) {
385
- return this.clone(ctx)
461
+ // A full instance (`dup`, ADR-005), paths normalised to the
462
+ // destination: see Val.spreadClone and repathInstance.
463
+ const out = this.clone(ctx, { dup: true })
464
+ repathInstance(out, out.path)
465
+ return out
386
466
  }
387
467
 
388
468
  let out = (super.clone(ctx) as MapVal)
@@ -414,12 +494,17 @@ class MapVal extends BagVal {
414
494
  // (entry[1] as Val).clone(ctx, spec?.mark ? { mark: spec.mark } : {}) :
415
495
  (entry[1] as Val).clone(ctx, {
416
496
  mark: spec?.mark ?? {},
417
- path: [...out.path, entry[0]]
497
+ path: [...out.path, entry[0]],
498
+ // The instantiation flag descends (ADR-005): a template's
499
+ // children are part of the instance.
500
+ dup: spec?.dup,
418
501
  }) :
419
502
  entry[1]
420
503
  }
421
504
  if (this.spread.cj) {
422
- out.spread.cj = this.spread.cj.clone(ctx, spec?.mark ? { mark: spec.mark } : {})
505
+ out.spread.cj = this.spread.cj.clone(ctx,
506
+ spec?.mark || spec?.dup ?
507
+ { mark: spec?.mark, dup: spec?.dup } : {})
423
508
  }
424
509
 
425
510
  out.closed = this.closed
@@ -450,7 +535,13 @@ class MapVal extends BagVal {
450
535
  JSON.stringify(k) +
451
536
  (this.optionalKeys.includes(k) ? '?' : '') +
452
537
  ':' +
453
- (this.peg[k]?.canon ?? this.peg[k])
538
+ // canonRiders, not .canon: a deprecated field renders
539
+ // back as its `deprecate(x, m)` call, reparseably (G3). The
540
+ // guard is the isVal FLAG, never the canon getter: computing
541
+ // canon in the guard and again in the render doubles the
542
+ // recursion per level, which is 2^depth on a nested document.
543
+ (true === this.peg[k]?.isVal
544
+ ? canonRiders(this.peg[k]) : this.peg[k])
454
545
  ])
455
546
  .join(',') +
456
547
  '}' // + '<' + (this.mark.hide ? 'H' : '') + '>'
@@ -0,0 +1,176 @@
1
+ /* Copyright (c) 2025 Richard Rodger, MIT License */
2
+
3
+ // GENERATION: `match(v, p1, r1, p2, r2, …, d?)` (G8 phase 2,
4
+ // docs/capability-review/g8-generation.md). A BOUNDED conditional:
5
+ // alternating pattern/result arguments and an optional trailing
6
+ // default. The first pattern IN ARGUMENT ORDER that `v` unifies with
7
+ // SELECTS its result, and the result is the answer.
8
+ //
9
+ // The design said the answer was `v & p & r` -- the scrutinee narrowed
10
+ // by the arm rather than replaced by it. That cannot be what a match
11
+ // is for: it makes every arm whose result is not already a `v` a
12
+ // contradiction, and the design's own example
13
+ // (`match($.tier, small, {cpu:1}, …)`, a string scrutinee and map
14
+ // results) cannot be evaluated at all under it. A match MAPS a value
15
+ // to another value; a document that wants the scrutinee kept can say
16
+ // so, because the scrutinee is a value it can name.
17
+ //
18
+ // size: match($.tier, small, {cpu:1}, large, {cpu:8}, {cpu:2})
19
+ //
20
+ // WHAT KEEPS IT FROM BECOMING A CONDITIONAL LANGUAGE. The scrutinee is
21
+ // matched by UNIFIABILITY only: no boolean guards, no comparisons
22
+ // beyond what the constraint atoms already are, no fallthrough (first
23
+ // match wins, in a spec-pinned order), and no way to write a pattern
24
+ // that is not an ordinary Aontu value. The whole form is total.
25
+ //
26
+ // NO MATCH AND NO DEFAULT IS AN ERROR, not an empty answer, and the
27
+ // report names the patterns that were tried -- the admissible-
28
+ // alternatives shape (G2's error contract). A default is how a
29
+ // document says it meant to allow the rest.
30
+ //
31
+ // A RESULT IS NOT EVALUATED UNTIL IT IS SELECTED. Only the scrutinee
32
+ // and the patterns are driven; an unselected result never runs, so a
33
+ // broken arm nobody takes is not an error the document has to carry
34
+ // (which multi-error collection, G2 phase 6, would otherwise report).
35
+
36
+ import type {
37
+ Val,
38
+ ValSpec,
39
+ } from '../type'
40
+
41
+ import {
42
+ AontuContext,
43
+ } from '../ctx'
44
+
45
+ import { makeNilErr } from '../err'
46
+ import { top } from './top'
47
+ import { prefInnerPeg } from './PrefVal'
48
+ import { FuncBaseVal, trialUnify } from './FuncBaseVal'
49
+
50
+
51
+ // THE DEFAULTED-SCRUTINEE RULE (ADR-004, use-cases/BUGS.md §5). The
52
+ // generation-effective view of a settled scrutinee: a preference — or
53
+ // a disjunction carrying one — means "this value unless something
54
+ // overrides it", and by resolve time the model has SETTLED (staging
55
+ // rule), so nothing will. The value generation is about to emit is
56
+ // therefore the value the patterns must be tested against. Testing
57
+ // against the still-open preference instead let a pattern SELECT an
58
+ // arm by overriding the default: `side_effect:*readonly|write|
59
+ // destructive` beside `match(.side_effect, destructive, true, false)`
60
+ // answered `true` while generating "readonly" next to it — a derived
61
+ // value contradicting the very value it derives from, exit 0.
62
+ // A pref-free scrutinee (open disjunction included) is untouched:
63
+ // matching by unifiability is its documented meaning.
64
+ // Exported for the multi-pref unit test (ADR-002, the subsumeNode
65
+ // precedent): rankPrefs leaves a settled disjunct at most one pref, so
66
+ // the min-rank scan below cannot be reached through a document.
67
+ export function effectiveScrutinee(v: Val): Val {
68
+ let out: any = v
69
+ if (true === out?.isDisjunct && Array.isArray(out.peg)) {
70
+ const prefs = out.peg.filter((m: any) => true === m?.isPref)
71
+ if (0 === prefs.length) {
72
+ return v
73
+ }
74
+ // Generation picks the LOWEST rank (effectiveDefault in
75
+ // subsume.ts; `a:**1|*2` generates 2). rankPrefs leaves at most
76
+ // one pref standing in a settled disjunct, so the scan is
77
+ // defensive.
78
+ out = prefs.reduce((a: any, b: any) => b.rank < a.rank ? b : a)
79
+ }
80
+ return prefInnerPeg(out)
81
+ }
82
+
83
+
84
+ class MatchFuncVal extends FuncBaseVal {
85
+ isMatchFunc = true
86
+
87
+ // THE STAGING RULE (G8 phase 0). A scrutinee that is still being
88
+ // narrowed can match an EARLIER pattern than the one it will end up
89
+ // matching, and the arm a match takes is not a thing to guess at.
90
+ staged = true
91
+
92
+ constructor(
93
+ spec: ValSpec,
94
+ ctx?: AontuContext
95
+ ) {
96
+ super(spec, ctx)
97
+ }
98
+
99
+
100
+ funcname() {
101
+ return 'match'
102
+ }
103
+
104
+
105
+ prepare(_ctx: AontuContext, _args: Val[]) {
106
+ return null
107
+ }
108
+
109
+
110
+ // The scrutinee is argument 0 and the patterns are the odd
111
+ // arguments; the results are the even ones after 0, and the last
112
+ // argument is a DEFAULT when the count is even. Written once, read
113
+ // by both the driver below and resolve.
114
+ hasDefault() {
115
+ return 0 === this.peg.length % 2
116
+ }
117
+
118
+
119
+ unify(peer: Val, ctx: AontuContext): Val {
120
+ // The scrutinee and the PATTERNS are driven; the results are not
121
+ // (see the header). driveStagedArgs takes a prefix, so the odd
122
+ // positions are driven one at a time.
123
+ let ready = this.driveStagedArgs(ctx, 1)
124
+ const last = this.peg.length - (this.hasDefault() ? 1 : 0)
125
+ for (let i = 1; i < last; i += 2) {
126
+ const arg: Val = this.peg[i]
127
+ if (!arg.done) {
128
+ this.peg[i] = arg.unify(top(), ctx)
129
+ }
130
+ ready = ready && true === this.peg[i].done
131
+ }
132
+
133
+ if (!ready || !ctx.settle) {
134
+ return this.residuate(peer, ctx)
135
+ }
136
+
137
+ return super.unify(peer, ctx)
138
+ }
139
+
140
+
141
+ resolve(ctx: AontuContext, args: Val[]) {
142
+ const scrutinee: Val = effectiveScrutinee(args[0])
143
+ const dflt: Val | undefined = this.hasDefault() ?
144
+ args[args.length - 1] : undefined
145
+ const last = args.length - (undefined === dflt ? 0 : 1)
146
+
147
+ const tried: string[] = []
148
+
149
+ for (let i = 1; i < last; i += 2) {
150
+ const pattern: Val = args[i]
151
+ tried.push(pattern.canon)
152
+
153
+ // The trial is against CLONES: `unite` refines a bag in place
154
+ // against a TOP peer, and a pattern that failed must be
155
+ // untouched for the next document that reads its canon.
156
+ if (undefined !== trialUnify(ctx, scrutinee.clone(ctx), pattern.clone(ctx))) {
157
+ return args[i + 1].clone(ctx)
158
+ }
159
+ }
160
+
161
+ if (undefined !== dflt) {
162
+ return dflt.clone(ctx)
163
+ }
164
+
165
+ return makeNilErr(ctx, 'match_none', this, undefined, 'resolve', {
166
+ value: scrutinee.canon,
167
+ tried: tried.join(' '),
168
+ })
169
+ }
170
+
171
+ } /* node:coverage ignore next 5 */
172
+
173
+
174
+ export {
175
+ MatchFuncVal,
176
+ }
@@ -12,7 +12,6 @@ import {
12
12
 
13
13
  import { makeNilErr } from '../err'
14
14
 
15
- import { NilVal } from '../val/NilVal'
16
15
 
17
16
  import {
18
17
  walk
@@ -21,7 +20,6 @@ import {
21
20
 
22
21
 
23
22
  import { FuncBaseVal } from './FuncBaseVal'
24
- import { CopyFuncVal } from './CopyFuncVal'
25
23
  import { PrefFuncVal } from './PrefFuncVal'
26
24
 
27
25
 
package/src/val/NilVal.ts CHANGED
@@ -79,6 +79,31 @@ class NilVal extends Val {
79
79
  }
80
80
  }
81
81
 
82
+ // THE PATH IS WHERE THE MEET IS, NOT WHERE THE OPERAND WAS WRITTEN
83
+ // (use-cases/BUGS.md §41). The operand path above decides the SITE
84
+ // correctly and the path only by accident: a value that arrives by
85
+ // REFERENCE is re-pathed to the referring field, and its children
86
+ // are re-pathed to that same field rather than rebased under it, so
87
+ // every conflict inside `q: $.M` reported `$.q` — the enclosing
88
+ // record, not the key to edit, and the SAME path for every one of
89
+ // its fields. The context has descended to the child by the time
90
+ // the meet fails, and Go reads the path off the tree position for
91
+ // exactly this reason.
92
+ //
93
+ // Only EXTENDS, never redirects: the context path is taken when the
94
+ // operand's path is a PREFIX of it, so a nil minted away from the
95
+ // descent (a func resolving an argument, a generator's own probe,
96
+ // an anchored `--at` run whose operand is already correctly placed)
97
+ // keeps the path its operand carries. Taking the context path
98
+ // unconditionally was tried and reverted -- it moved the closed-key,
99
+ // spread-template and every `--at` finding to the driving location,
100
+ // which is not where those belong.
101
+ if (null != ctx?.path && null != nil.path &&
102
+ nil.path.length < ctx.path.length &&
103
+ nil.path.every((p, i) => p === ctx.path[i])) {
104
+ nil.path = [...ctx.path]
105
+ }
106
+
82
107
  if (ctx) {
83
108
  ctx.adderr(nil)
84
109
  }
@@ -31,6 +31,7 @@ import {
31
31
 
32
32
  import { ConjunctVal } from './ConjunctVal'
33
33
  import { FeatureVal } from './FeatureVal'
34
+ import { hasPlace, fillPlace } from './PlaceVal'
34
35
 
35
36
 
36
37
 
@@ -66,6 +67,17 @@ class OpBaseVal extends FeatureVal {
66
67
 
67
68
 
68
69
  unify(peer: Val, ctx: AontuContext): Val {
70
+
71
+ // THE PLACEHOLDER (G8 phase 3, see PlaceVal), on the operator side:
72
+ // `x: {&: {m: _ + 2}}` meeting `1` is `3`. Same rule as
73
+ // FuncBaseVal's -- the peer fills the hole and the operation is
74
+ // what answers.
75
+ if (!peer.isTop && !peer.isNil && this.id !== peer.id && hasPlace(this)) {
76
+ if (hasPlace(peer)) {
77
+ return makeNilErr(ctx, 'place_pair', this, peer)
78
+ }
79
+ return fillPlace(this, peer, ctx).unify(top(), ctx)
80
+ }
69
81
  const te = ctx.explain && explainOpen(ctx, ctx.explain, 'Op:' + this.opname(), this, peer)
70
82
  // Declared without an initial value: every arm below assigns it, and
71
83
  // seeding it with `this` made the two arms that stand the op read as
@@ -165,10 +177,17 @@ class OpBaseVal extends FeatureVal {
165
177
  }
166
178
 
167
179
 
168
- clone(ctx: AontuContext, _spec?: ValSpec): Val {
180
+ clone(ctx: AontuContext, spec?: ValSpec): Val {
169
181
  let out = (super.clone(ctx, {
170
182
  peg: this.peg,
171
183
  }) as OpBaseVal)
184
+ // THE PER-DESTINATION INSTANTIATION RULE (ADR-005): an operation
185
+ // inside a template clones its operands as instances — the
186
+ // default clone shares them (see FuncBaseVal.clone). Every
187
+ // operand is a Val by construction, as in the Go twin's []Val.
188
+ if (true === spec?.dup) {
189
+ out.peg = this.peg.map((a: Val) => a.clone(ctx, { dup: true }))
190
+ }
172
191
  return out
173
192
  }
174
193