aontu 0.52.0 → 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 (273) 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 +145 -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 +16 -0
  13. package/dist/ctx.js +44 -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 +48 -8
  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 +223 -5
  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 +698 -35
  33. package/dist/lang.js.map +1 -1
  34. package/dist/lsp.d.ts +9 -2
  35. package/dist/lsp.js +262 -46
  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 +3 -1
  83. package/dist/unify.js +287 -18
  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 +48 -1
  103. package/dist/val/ConstraintVal.js +1501 -110
  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.d.ts +1 -0
  120. package/dist/val/ExpectVal.js +41 -4
  121. package/dist/val/ExpectVal.js.map +1 -1
  122. package/dist/val/FeatureVal.js +1 -1
  123. package/dist/val/FeatureVal.js.map +1 -1
  124. package/dist/val/FilterFuncVal.d.ts +15 -0
  125. package/dist/val/FilterFuncVal.js +91 -0
  126. package/dist/val/FilterFuncVal.js.map +1 -0
  127. package/dist/val/FuncBaseVal.d.ts +6 -1
  128. package/dist/val/FuncBaseVal.js +178 -2
  129. package/dist/val/FuncBaseVal.js.map +1 -1
  130. package/dist/val/HideFuncVal.js.map +1 -1
  131. package/dist/val/IdFuncVal.d.ts +13 -0
  132. package/dist/val/IdFuncVal.js +54 -0
  133. package/dist/val/IdFuncVal.js.map +1 -0
  134. package/dist/val/JunctionVal.js +7 -1
  135. package/dist/val/JunctionVal.js.map +1 -1
  136. package/dist/val/KeyFuncVal.d.ts +1 -1
  137. package/dist/val/KeyFuncVal.js +38 -30
  138. package/dist/val/KeyFuncVal.js.map +1 -1
  139. package/dist/val/ListVal.js +117 -17
  140. package/dist/val/ListVal.js.map +1 -1
  141. package/dist/val/LowerFuncVal.js.map +1 -1
  142. package/dist/val/MapVal.js +102 -8
  143. package/dist/val/MapVal.js.map +1 -1
  144. package/dist/val/MatchFuncVal.d.ts +15 -0
  145. package/dist/val/MatchFuncVal.js +107 -0
  146. package/dist/val/MatchFuncVal.js.map +1 -0
  147. package/dist/val/MoveFuncVal.js.map +1 -1
  148. package/dist/val/NilVal.js +24 -0
  149. package/dist/val/NilVal.js.map +1 -1
  150. package/dist/val/OpBaseVal.d.ts +1 -1
  151. package/dist/val/OpBaseVal.js +24 -2
  152. package/dist/val/OpBaseVal.js.map +1 -1
  153. package/dist/val/OpenFuncVal.js +4 -1
  154. package/dist/val/OpenFuncVal.js.map +1 -1
  155. package/dist/val/PackFuncVal.d.ts +15 -0
  156. package/dist/val/PackFuncVal.js +108 -0
  157. package/dist/val/PackFuncVal.js.map +1 -0
  158. package/dist/val/PathFuncVal.js.map +1 -1
  159. package/dist/val/PlaceVal.d.ts +13 -0
  160. package/dist/val/PlaceVal.js +131 -0
  161. package/dist/val/PlaceVal.js.map +1 -0
  162. package/dist/val/PlusOpVal.js +11 -2
  163. package/dist/val/PlusOpVal.js.map +1 -1
  164. package/dist/val/PrefFuncVal.js.map +1 -1
  165. package/dist/val/PrefVal.d.ts +2 -2
  166. package/dist/val/PrefVal.js +78 -23
  167. package/dist/val/PrefVal.js.map +1 -1
  168. package/dist/val/RefVal.d.ts +1 -1
  169. package/dist/val/RefVal.js +158 -30
  170. package/dist/val/RefVal.js.map +1 -1
  171. package/dist/val/ReferFuncVal.d.ts +36 -0
  172. package/dist/val/ReferFuncVal.js +303 -0
  173. package/dist/val/ReferFuncVal.js.map +1 -0
  174. package/dist/val/ScalarKindVal.d.ts +1 -2
  175. package/dist/val/ScalarKindVal.js +0 -11
  176. package/dist/val/ScalarKindVal.js.map +1 -1
  177. package/dist/val/TopVal.js.map +1 -1
  178. package/dist/val/TypeFuncVal.js.map +1 -1
  179. package/dist/val/UpperFuncVal.js.map +1 -1
  180. package/dist/val/Val.d.ts +9 -2
  181. package/dist/val/Val.js +150 -4
  182. package/dist/val/Val.js.map +1 -1
  183. package/dist/val/VarVal.js.map +1 -1
  184. package/dist/val/arith.d.ts +6 -0
  185. package/dist/val/arith.js +170 -0
  186. package/dist/val/arith.js.map +1 -0
  187. package/dist/vet.d.ts +45 -0
  188. package/dist/vet.js +776 -0
  189. package/dist/vet.js.map +1 -0
  190. package/dist/walk.d.ts +2 -0
  191. package/dist/walk.js +91 -0
  192. package/dist/walk.js.map +1 -0
  193. package/grammar/aontu.gbnf +130 -0
  194. package/grammar/aontu.lark +113 -0
  195. package/package.json +30 -15
  196. package/skill/SKILL.md +37 -0
  197. package/skill/error-codes.md +62 -0
  198. package/skill/examples.md +99 -0
  199. package/skill/grammar-card.md +57 -0
  200. package/src/agentsmd.ts +135 -0
  201. package/src/aontu.ts +192 -4
  202. package/src/cli.ts +2858 -71
  203. package/src/ctx.ts +81 -0
  204. package/src/diff.ts +196 -0
  205. package/src/err.ts +52 -8
  206. package/src/graph.ts +135 -0
  207. package/src/hcanon.ts +169 -0
  208. package/src/hints.ts +271 -5
  209. package/src/jsonschema.ts +511 -0
  210. package/src/lang.ts +779 -37
  211. package/src/lsp.ts +281 -47
  212. package/src/mcp-server.ts +187 -0
  213. package/src/mcp.ts +993 -0
  214. package/src/mod-tool.ts +679 -0
  215. package/src/mod.ts +344 -0
  216. package/src/patch.ts +624 -0
  217. package/src/provenance.ts +430 -0
  218. package/src/query.ts +379 -0
  219. package/src/reach.ts +184 -0
  220. package/src/relation.ts +395 -0
  221. package/src/report-sarif.ts +137 -0
  222. package/src/site.ts +36 -1
  223. package/src/std.ts +73 -0
  224. package/src/subsume.ts +690 -0
  225. package/src/trim.ts +195 -0
  226. package/src/tsconfig.json +10 -4
  227. package/src/type.ts +51 -2
  228. package/src/unify.ts +311 -16
  229. package/src/utility.ts +139 -1
  230. package/src/val/AggFuncVal.ts +319 -0
  231. package/src/val/ArithFuncVal.ts +108 -0
  232. package/src/val/BagVal.ts +101 -4
  233. package/src/val/CloseFuncVal.ts +9 -1
  234. package/src/val/ConjunctVal.ts +20 -0
  235. package/src/val/ConstraintVal.ts +1699 -116
  236. package/src/val/CopyFuncVal.ts +7 -1
  237. package/src/val/Decimal.ts +15 -0
  238. package/src/val/DeprecateFuncVal.ts +84 -0
  239. package/src/val/DisjunctVal.ts +139 -28
  240. package/src/val/EachFuncVal.ts +133 -0
  241. package/src/val/ExpectVal.ts +43 -6
  242. package/src/val/FeatureVal.ts +1 -1
  243. package/src/val/FilterFuncVal.ts +154 -0
  244. package/src/val/FuncBaseVal.ts +200 -3
  245. package/src/val/HideFuncVal.ts +0 -2
  246. package/src/val/IdFuncVal.ts +91 -0
  247. package/src/val/JunctionVal.ts +7 -1
  248. package/src/val/KeyFuncVal.ts +39 -35
  249. package/src/val/ListVal.ts +125 -18
  250. package/src/val/LowerFuncVal.ts +0 -1
  251. package/src/val/MapVal.ts +110 -8
  252. package/src/val/MatchFuncVal.ts +176 -0
  253. package/src/val/MoveFuncVal.ts +0 -2
  254. package/src/val/NilVal.ts +25 -0
  255. package/src/val/OpBaseVal.ts +26 -3
  256. package/src/val/OpenFuncVal.ts +4 -2
  257. package/src/val/PackFuncVal.ts +175 -0
  258. package/src/val/PathFuncVal.ts +0 -1
  259. package/src/val/PlaceVal.ts +193 -0
  260. package/src/val/PlusOpVal.ts +11 -2
  261. package/src/val/PrefFuncVal.ts +0 -1
  262. package/src/val/PrefVal.ts +79 -36
  263. package/src/val/RefVal.ts +163 -30
  264. package/src/val/ReferFuncVal.ts +387 -0
  265. package/src/val/ScalarKindVal.ts +0 -13
  266. package/src/val/TopVal.ts +0 -1
  267. package/src/val/TypeFuncVal.ts +0 -2
  268. package/src/val/UpperFuncVal.ts +0 -1
  269. package/src/val/Val.ts +213 -3
  270. package/src/val/VarVal.ts +0 -1
  271. package/src/val/arith.ts +316 -0
  272. package/src/vet.ts +992 -0
  273. 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
  }
@@ -76,6 +99,16 @@ class ListVal extends BagVal {
76
99
  const TOP = top()
77
100
  peer = peer ?? TOP
78
101
 
102
+ // A sizing residual (`length`, `unique`) sorts AFTER containers in a
103
+ // conjunct so that it counts the MERGED list rather than the first
104
+ // fragment (SIZING_CJO in ConstraintVal.ts). That makes the list the
105
+ // accumulator and the constraint its peer, the reverse of the usual
106
+ // order — and the reading belongs to the constraint either way, so
107
+ // hand it straight back.
108
+ if (true === (peer as any).isConstraint) {
109
+ return peer.unify(this, ctx)
110
+ }
111
+
79
112
  const te = ctx.explain && explainOpen(ctx, ctx.explain, 'List', this, peer)
80
113
  let done: boolean = true
81
114
  let exit = false
@@ -111,24 +144,81 @@ class ListVal extends BagVal {
111
144
 
112
145
  let spread_cj = out.spread.cj || TOP
113
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
+
114
162
  // Always unify children first
115
163
  for (let key in this.peg) {
116
164
  const keyctx = ctx.descend(key)
117
- const key_spread_cj = spread_cj.spreadClone(keyctx)
118
165
  const child = this.peg[key]
119
166
 
120
167
  propagateMarks(this, child)
121
168
 
122
- // child is non-nullish: propagateMarks above dereferences it.
123
- out.peg[key] =
124
- child.isNil ? child :
125
- key_spread_cj.isNil ? key_spread_cj :
126
- key_spread_cj.isTop && child.done ? child :
127
- child.isTop && key_spread_cj.done ? key_spread_cj :
128
- unite(te ? keyctx.clone({ explain: ec(te, 'PEG:' + key) }) : keyctx,
129
- 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
130
220
 
131
- done = (done && DONE === out.peg[key].dc)
221
+ done = (done && DONE === oval.dc)
132
222
  }
133
223
 
134
224
  const allowedKeys: string[] = this.closed ? Object.keys(this.peg) : []
@@ -161,6 +251,9 @@ class ListVal extends BagVal {
161
251
 
162
252
  if (this.spread.cj) {
163
253
  let key_spread_cj = spread_cj.spreadClone(peerctx)
254
+ if (undefined !== peerctx.prov) {
255
+ markSpread(key_spread_cj)
256
+ }
164
257
 
165
258
  oval = out.peg[peerkey] =
166
259
  unite(te ? peerctx.clone({ explain: ec(te, 'PSP:' + peerkey) }) : peerctx,
@@ -216,7 +309,11 @@ class ListVal extends BagVal {
216
309
  }
217
310
 
218
311
  if (!allScalarKind) {
219
- 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
220
317
  }
221
318
 
222
319
  let out = (super.clone(ctx) as ListVal)
@@ -239,12 +336,16 @@ class ListVal extends BagVal {
239
336
 
240
337
  clone(ctx: AontuContext, spec?: ValSpec): Val {
241
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 } : {}
242
343
  for (let entry of Object.entries(this.peg)) {
243
344
  out.peg[entry[0]] =
244
- (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]
245
346
  }
246
347
  if (this.spread.cj) {
247
- out.spread.cj = this.spread.cj.clone(ctx, spec?.mark ? { mark: spec.mark } : {})
348
+ out.spread.cj = this.spread.cj.clone(ctx, childspec)
248
349
  }
249
350
 
250
351
  out.closed = this.closed
@@ -263,10 +364,16 @@ class ListVal extends BagVal {
263
364
  '[' +
264
365
  (this.spread.cj ? '&:' + this.spread.cj.canon +
265
366
  (0 < keys.length ? ',' : '') : '') +
266
- keys
267
- .map(k => this.optionalKeys.includes(k) ?
268
- k + '?:' + this.peg[k].canon :
269
- this.peg[k].canon).join(',') +
367
+ // No optional-element rendering. A list HAS no optional elements to
368
+ // render: a key:value pair in list position contributes no element
369
+ // at all, in either spelling and whatever its key (issue #40), so
370
+ // nothing a source can write reaches this method with an optional
371
+ // key. The Go port's ListVal.Canon has no such arm either, and the
372
+ // two canons must agree -- a canon is round-trippable, and a marker
373
+ // on an element the grammar cannot produce would not reparse.
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(',') +
270
377
  ']'
271
378
  }
272
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
 
@@ -125,6 +152,17 @@ class MapVal extends BagVal {
125
152
 
126
153
  const TOP = top()
127
154
  peer = peer ?? TOP
155
+
156
+ // A sizing residual (`length`, `unique`) sorts AFTER containers in a
157
+ // conjunct so that it counts the MERGED map rather than the first
158
+ // fragment (SIZING_CJO in ConstraintVal.ts). That makes the map the
159
+ // accumulator and the constraint its peer, the reverse of the usual
160
+ // order — and the reading belongs to the constraint either way, so
161
+ // hand it straight back.
162
+ if (true === (peer as any).isConstraint) {
163
+ return peer.unify(this, ctx)
164
+ }
165
+
128
166
  const te = ctx.explain && explainOpen(ctx, ctx.explain, 'Map', this, peer)
129
167
 
130
168
  let done: boolean = true
@@ -173,6 +211,13 @@ class MapVal extends BagVal {
173
211
  // combined here). unite resolves key()/path() at each destination via
174
212
  // spreadClone below, so nested + sibling key() cases stay correct
175
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.
176
221
  out.spread.cj = null == out.spread.cj ? peer.spread.cj : (
177
222
  null == peer.spread.cj ? out.spread.cj :
178
223
  out.spread.cj.canon === peer.spread.cj.canon ? out.spread.cj :
@@ -193,6 +238,21 @@ class MapVal extends BagVal {
193
238
 
194
239
  let spread_cj = out.spread.cj ?? TOP
195
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
+
196
256
  // Snapshot a path-dependent *ref* spread to its structural target
197
257
  // once (while inner key()/path() funcs are still unresolved), so
198
258
  // later fixpoint passes don't re-resolve the ref against the mutated
@@ -241,11 +301,25 @@ class MapVal extends BagVal {
241
301
  else {
242
302
  const key_spread_cj = spread_cj.spreadClone(keyctx)
243
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
+
244
312
  // child is non-nullish: propagateMarks above dereferences it.
245
313
  oval =
246
314
  child.isNil ? child :
247
315
  key_spread_cj.isNil ? key_spread_cj :
248
- 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 :
249
323
  child.isTop && key_spread_cj.done ? key_spread_cj :
250
324
  unite(te ? keyctx.clone({ explain: ec(te, 'KEY:' + key) }) : keyctx,
251
325
  child, key_spread_cj, 'map-own')
@@ -257,6 +331,7 @@ class MapVal extends BagVal {
257
331
 
258
332
  out.peg[key] = oval
259
333
 
334
+
260
335
  done = (done && DONE === oval.dc)
261
336
  }
262
337
 
@@ -285,7 +360,19 @@ class MapVal extends BagVal {
285
360
  const peerctx = ctx.descend(peerkey)
286
361
 
287
362
  let oval = out.peg[peerkey] =
288
- 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)) :
289
376
  child.isTop && peerchild.done ? peerchild :
290
377
  child.isNil ? child :
291
378
  peerchild.isNil ? peerchild :
@@ -371,7 +458,11 @@ class MapVal extends BagVal {
371
458
  }
372
459
 
373
460
  if (!allScalarKind) {
374
- 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
375
466
  }
376
467
 
377
468
  let out = (super.clone(ctx) as MapVal)
@@ -403,12 +494,17 @@ class MapVal extends BagVal {
403
494
  // (entry[1] as Val).clone(ctx, spec?.mark ? { mark: spec.mark } : {}) :
404
495
  (entry[1] as Val).clone(ctx, {
405
496
  mark: spec?.mark ?? {},
406
- 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,
407
501
  }) :
408
502
  entry[1]
409
503
  }
410
504
  if (this.spread.cj) {
411
- 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 } : {})
412
508
  }
413
509
 
414
510
  out.closed = this.closed
@@ -439,7 +535,13 @@ class MapVal extends BagVal {
439
535
  JSON.stringify(k) +
440
536
  (this.optionalKeys.includes(k) ? '?' : '') +
441
537
  ':' +
442
- (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])
443
545
  ])
444
546
  .join(',') +
445
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
  }