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/unify.ts CHANGED
@@ -10,6 +10,7 @@ import { DONE } from './type'
10
10
  import { makeNilErr } from './err'
11
11
 
12
12
  import { NilVal } from './val/NilVal'
13
+ import { hasPlace } from './val/PlaceVal'
13
14
 
14
15
  import {
15
16
  Lang
@@ -26,27 +27,27 @@ import {
26
27
  } from './val/top'
27
28
 
28
29
 
29
- // Per-pass revisit bound: how many times one (Val, path) pair may be
30
- // re-unified within a single fixpoint pass before the evaluator calls it
31
- // non-convergence (`unify_cycle`). The old false positive here -- a
32
- // legal model with more than MAXCYCLE sibling conjunct terms at one
33
- // path, each re-running the TOP self-unify -- is fixed by the per-pass
34
- // memo below (_tcc/_tpi); test/spec/budget.tsv drives 1200 sibling
35
- // terms through both engines as the regression guard.
36
- const MAXCYCLE = 999
37
-
38
- // Structural recursion budget: how deep `unite` may nest before the
39
- // evaluator reports `unify_cycle`. SHARED LANGUAGE SURFACE -- Go's
40
- // maxUniteDepth (go/unify.go) carries the same number, and
41
- // test/spec/budget.tsv pins the boundary in both, so changing it is a
42
- // spec-visible change in both ports at once.
30
+ // The evaluation budgets live on the context (ctx.budget: passes,
31
+ // revisits, depth), defaulted there to the shared spec-visible
32
+ // constants test/spec/budget.tsv pins in both ports (9 / 999 / 1000)
33
+ // and configurable through the trust profile (G5, docs/trust.md) —
34
+ // deterministically: a budget is an integer count of engine events,
35
+ // never wall-clock.
43
36
  //
44
- // Why 1000: the whole shared suite peaks at 603 (the deliberately
45
- // extreme 1200-sibling-term fixture; ordinary documents are two orders
46
- // below), and V8 exhausts its call stack somewhere past depth ~1500 in
47
- // this evaluator. 1000 sits above every real document and below the
48
- // host limit, so the budget -- not the host -- decides the verdict.
49
- const MAXDEPTH = 1000
37
+ // Why the revisit default is 999: how many times one (Val, path) pair
38
+ // may be re-unified within a single fixpoint pass before the evaluator
39
+ // calls it non-convergence (`unify_cycle`). The old false positive here
40
+ // -- a legal model with many sibling conjunct terms at one path, each
41
+ // re-running the TOP self-unify -- is fixed by the per-pass memo below
42
+ // (_tcc/_tpi); test/spec/budget.tsv drives 1200 sibling terms through
43
+ // both engines as the regression guard.
44
+ //
45
+ // Why the depth default is 1000: the whole shared suite peaks at 603
46
+ // (the deliberately extreme 1200-sibling-term fixture; ordinary
47
+ // documents are two orders below), and V8 exhausts its call stack
48
+ // somewhere past depth ~1500 in this evaluator. 1000 sits above every
49
+ // real document and below the host limit, so the budget -- not the
50
+ // host -- decides the verdict.
50
51
 
51
52
  // Charge a DIRECT `Val.unify` recursion to the same depth budget that
52
53
  // `unite` enforces. Function and operator arguments evaluate through
@@ -59,7 +60,7 @@ const MAXDEPTH = 1000
59
60
  const withDepth = (
60
61
  ctx: AontuContext, a: any, b: any, run: () => any
61
62
  ): any => {
62
- if (MAXDEPTH <= ctx._depth.n) {
63
+ if (ctx.budget.depth <= ctx._depth.n) {
63
64
  return makeNilErr(ctx, 'unify_cycle', a, b)
64
65
  }
65
66
  ctx._depth.n++
@@ -89,14 +90,39 @@ const unite = (ctx: AontuContext, a: any, b: any, whence: string) => {
89
90
  if (a === b) {
90
91
  if (a.done) return a
91
92
  }
92
- else if (b !== undefined && b !== null) {
93
+ // ... and NOT on an instrumented run (G7 provenance). Both arms
94
+ // below answer with one operand and never reach the recorder at
95
+ // the tail, so an equal pair — two positions of one entity that
96
+ // agree, a clone meeting its source — contributed silently and
97
+ // `why` named one site where the Go port, whose recorder wraps the
98
+ // whole dispatcher, named both. Instrumented runs pay the slow
99
+ // path knowingly; uninstrumented ones pay one undefined check.
100
+ else if (b !== undefined && b !== null && undefined === ctx.prov) {
93
101
  if (a.done && b.done) {
94
- if (a.id === b.id) return a
102
+ if (a.id === b.id) {
103
+ // The deprecation record survives the fast path (G3).
104
+ if (null == a.deprecation && null != b.deprecation) {
105
+ a.deprecation = b.deprecation
106
+ }
107
+ return a
108
+ }
95
109
  if (a.constructor === b.constructor && a.peg === b.peg
96
110
  && !a.isNil && !b.isNil
97
111
  && !a.isMap && !a.isList
98
112
  && !a.isConjunct && !a.isDisjunct
99
- && !a.isRef && !a.isPref && !a.isFunc && !a.isExpect) {
113
+ && !a.isRef && !a.isPref && !a.isFunc && !a.isExpect
114
+ // NOT two TOPs (G4 phase 1): every top has the same
115
+ // (absent) peg, so this path treated any two as the same
116
+ // value — true of the unit itself, false of a unit
117
+ // CARRYING AN IDENTITY, and `id(x) & id(y)` is two of
118
+ // those. The slow path answers the same thing for two
119
+ // plain tops, and refuses the pair for two named ones.
120
+ && !a.isTop && !b.isTop) {
121
+ // The deprecation record survives the fast path too (G3):
122
+ // `deprecate(5) & 5` short-circuits here.
123
+ if (null == a.deprecation && null != b.deprecation) {
124
+ a.deprecation = b.deprecation
125
+ }
100
126
  return a
101
127
  }
102
128
  }
@@ -119,7 +145,7 @@ const unite = (ctx: AontuContext, a: any, b: any, whence: string) => {
119
145
  // NOTE: if this error occurs "unreasonably", attemp to avoid unnecesary unification
120
146
  // See for example PrefVal peg.id equality inspection.
121
147
  const sawCount = ctx.seen[saw] ?? 0
122
- if (MAXDEPTH <= ctx._depth.n) {
148
+ if (ctx.budget.depth <= ctx._depth.n) {
123
149
  // Structural recursion budget. Without it, deep nesting exhausts the
124
150
  // V8 call stack and the catch-all below reports a RangeError as
125
151
  // `internal` — a verdict that depends on the host's stack size
@@ -128,7 +154,7 @@ const unite = (ctx: AontuContext, a: any, b: any, whence: string) => {
128
154
  // makes it a stated budget error, like the pass budget.
129
155
  out = makeNilErr(ctx, 'unify_cycle', a, b)
130
156
  }
131
- else if (MAXCYCLE < sawCount) {
157
+ else if (ctx.budget.revisits < sawCount) {
132
158
  // console.log('SAW', sawCount, saw, a?.id, a?.canon, b?.id, b?.canon, ctx.cc)
133
159
  out = makeNilErr(ctx, 'unify_cycle', a, b)
134
160
  }
@@ -184,6 +210,18 @@ const unite = (ctx: AontuContext, a: any, b: any, whence: string) => {
184
210
  || b.isVar
185
211
  || b.isFunc
186
212
  || b.isExpect
213
+ // The refer residual (G4 phase 2) DRIVES, like the other
214
+ // residuals here: its peer is a plain string, which knows
215
+ // nothing about entity addresses, so letting the string drive
216
+ // dropped the address and left the constraint standing.
217
+ || b.isRefer
218
+ // An operator holding a HOLE (G8 phase 3) drives for the same
219
+ // reason: its peer is what FILLS it, and a scalar asked to
220
+ // unify with `_ + 2` sees an operator rather than a hole and
221
+ // refuses it on kind. Narrow to placeheld operators on
222
+ // purpose -- every other operator meets its peer the way it
223
+ // always has, through the conjunct fold that drives it.
224
+ || (b.isOp && hasPlace(b))
187
225
  ) {
188
226
  out = b.unify(a, te ? ctx.clone({ explain: ec(te, 'BW') }) : ctx)
189
227
  unified = true
@@ -251,6 +289,47 @@ const unite = (ctx: AontuContext, a: any, b: any, whence: string) => {
251
289
 
252
290
  ctx.explain && explainClose(te, out)
253
291
 
292
+ // The provenance record (G7 phase 3), at the one place every meet
293
+ // passes through — the same reason the deprecation rider below lives
294
+ // here. Off by default: an uninstrumented run pays this one property
295
+ // load, and an instrumented one pays site materialisation knowingly.
296
+ if (undefined !== ctx.prov) {
297
+ ctx.prov.record(ctx.path, a, b, out)
298
+ }
299
+
300
+ // The IDENTITY survives every meet (G4 phase 1), by the same
301
+ // channel and for the same reason as the deprecation record below.
302
+ // TWO DIFFERENT NAMES on one node is a contradiction, not a merge:
303
+ // one node cannot be two entities, and the error names both sites.
304
+ if (null != out && true === (out as any).isVal && !out.isNil) {
305
+ const ae = null != a ? a.entity : undefined
306
+ const be = null != b ? b.entity : undefined
307
+ if (null != ae && null != be && ae !== be) {
308
+ out = makeNilErr(ctx, 'id_conflict', a, b)
309
+ }
310
+ else if (!out.isTop) {
311
+ const e = ae ?? be
312
+ if (null != e) {
313
+ out.entity = e
314
+ }
315
+ }
316
+ }
317
+
318
+ // The deprecation record survives EVERY meet (G3 phase 4): the
319
+ // boolean marks have their own sweeps (ConjunctVal, the bag walks),
320
+ // but a record lost in one meet shape is a use the tooling never
321
+ // warns about, so it rides here, at the one place all meets pass
322
+ // through. First record wins; TOP and nil stay clean (TOP is the
323
+ // unit, and an error needs no deprecation).
324
+ if (null != out && true === (out as any).isVal &&
325
+ !out.isTop && !out.isNil && null == out.deprecation) {
326
+ const dep = (null != a ? a.deprecation : undefined) ??
327
+ (null != b ? b.deprecation : undefined)
328
+ if (null != dep) {
329
+ out.deprecation = dep
330
+ }
331
+ }
332
+
254
333
  return out
255
334
  }
256
335
 
@@ -285,6 +364,102 @@ function residuePaths(v: Val, max: number): string[] {
285
364
  }
286
365
 
287
366
 
367
+ // IDENTITY-MERGE (G4 phase 1): every node in one evaluation carrying
368
+ // the same id is unified with every other. Declaring two nodes the
369
+ // same entity MEANS unifying them, so this is not a lookup table —
370
+ // it is a meet, and a contradiction between two declarations is an
371
+ // ordinary conflict naming both sites.
372
+ //
373
+ // Run once per fixpoint pass, after the pass's own unification: a
374
+ // position picks up the representative, the representative picks up
375
+ // the position, and the two converge across passes exactly as chained
376
+ // references do, inside the same `maxcc` bound.
377
+ //
378
+ // The tree stays a TREE. Every declared position holds the merged
379
+ // value and generation emits it at each path — duplication, as
380
+ // references generate today. Identity adds addressing, not a new
381
+ // shape.
382
+ // The ctx DESCENDS with the walk, so the merge's meet happens at the
383
+ // position's own path: a contribution `$.b.k` picked up from `$.a.k`
384
+ // is recorded against `$.b.k`, which is where a reader asking `why`
385
+ // stands. Merging under the root ctx instead filed every contribution
386
+ // at the top and left the positions themselves with an empty record —
387
+ // and the Go port, whose bag loops derive the base from the value's
388
+ // own path, already answered the useful way.
389
+ function mergeEntities(ctx: AontuContext, root: Val): Val {
390
+ const reg: Map<string, Val> = (ctx as any).entities
391
+
392
+ // COLLECT, then APPLY — the same walk twice, not two walks. A single
393
+ // pass merges each position into the representative as it meets it,
394
+ // which leaves the positions it already passed holding the pre-merge
395
+ // value: `a: id(x) & {k:1}` kept `{k:1}` while `b: id(x) & {j:2}`
396
+ // became `{j:2,k:1}`, and the two sites disagreed about what the one
397
+ // entity is. The representative is therefore settled over the WHOLE
398
+ // tree before any position is written.
399
+ //
400
+ // `write` is which half is running. One function rather than two
401
+ // because the two halves differ in three lines and agree in the walk
402
+ // — and a walk written twice is a walk that drifts.
403
+ const walk = (node: any, seen: Set<any>, nctx: AontuContext,
404
+ write: boolean): any => {
405
+ if (null == node || true !== node.isVal) {
406
+ return node
407
+ }
408
+
409
+ const name = (node as any).entity
410
+ if (null != name) {
411
+ if (write) {
412
+ // The SUBSTITUTION happens before the seen-guard, not after.
413
+ // Two positions of one entity hold the SAME object once a pass
414
+ // has merged them, so a guard that ran first would visit the
415
+ // first position, replace it with a newer representative, and
416
+ // then skip the second as already-seen — leaving it on the
417
+ // older value. That is exactly what a `refer(t)` flow
418
+ // produces: it writes a new representative mid-pass, and every
419
+ // position must take it.
420
+ const rep: any = reg.get(name)
421
+ if (null != rep && rep !== node) {
422
+ node = rep
423
+ }
424
+ }
425
+ else {
426
+ const rep = reg.get(name)
427
+ reg.set(name, null == rep || rep === node ? node :
428
+ unite(nctx, node, rep, 'entity'))
429
+ }
430
+ }
431
+
432
+ // The guard bounds the DESCENT, which is all it was ever for: a
433
+ // unified tree is a graph, and a subtree is worth walking once.
434
+ if (seen.has(node)) {
435
+ return node
436
+ }
437
+ seen.add(node)
438
+
439
+ if ((true === node.isMap || true === node.isList) && null != node.peg) {
440
+ for (const k of Object.keys(node.peg)) {
441
+ const out = walk(node.peg[k], seen, nctx.descend(k), write)
442
+ if (write) {
443
+ node.peg[k] = out
444
+ }
445
+ }
446
+ }
447
+ return node
448
+ }
449
+
450
+ walk(root, new Set(), ctx, false)
451
+
452
+ // NOTHING TO APPLY. The collect half is also the "does this document
453
+ // use identity at all?" answer, so a document that never says `id()`
454
+ // pays for one walk per pass rather than two — and the writing half
455
+ // never runs over a tree it cannot change.
456
+ if (0 === reg.size) {
457
+ return root
458
+ }
459
+ return walk(root, new Set(), ctx, true)
460
+ }
461
+
462
+
288
463
  class Unify {
289
464
  root: Val
290
465
  res: Val
@@ -339,27 +514,89 @@ class Unify {
339
514
  // keyed by ref canon + source site, shared across all passes.
340
515
  ; (uctx as any).snapmap = new Map()
341
516
 
517
+ // The identity registry (G4 phase 1): id -> the representative
518
+ // value every position with that id has been merged into. Same
519
+ // lifetime and placement as the ref-spread snapshot map above —
520
+ // one evaluation, one set of entities.
521
+ ; (uctx as any).entities = new Map()
522
+
342
523
  const explain = null == ctx?.explain ? undefined : ctx?.explain
343
524
  const te = explain && explainOpen(uctx, explain, 'root', res)
344
525
 
345
526
  // NOTE: if true === res.done already, then this loop never needs to run.
346
- let maxcc = 9 // 99
527
+ let maxcc = uctx.budget.passes
347
528
  let prevCanon: string | undefined = undefined
529
+ let lastCanon: string | undefined = undefined
530
+ let settle = false
348
531
  for (; this.cc < maxcc && DONE !== res.dc; this.cc++) {
349
532
  // console.log('CC', this.cc, res.canon)
350
533
  uctx.cc = this.cc
351
534
  uctx.seen = {}
352
- res = unite(te ? uctx.clone({ explain: ec(te, 'run') }) : uctx, res, top(), 'unify')
353
535
 
354
- if (0 < uctx.err.length) {
355
- break
536
+ // THE STAGING RULE (G8 phase 0,
537
+ // docs/capability-review/g8-generation.md), stated once, here,
538
+ // for every value whose answer depends on WHERE IT IS. Such a
539
+ // value residuates while the model is still moving and fires on
540
+ // the first pass whose input is IDENTICAL to the previous
541
+ // pass's input: nothing moved, so nothing will move it again,
542
+ // and the position it reports is the position it ends at.
543
+ //
544
+ // Why the whole model and not the value's own path. A spread, a
545
+ // reference or a `move` can place a value under a path it has
546
+ // already been driven at and THEN change what encloses it --
547
+ // `move` hides its source one pass AFTER it copies it, and a
548
+ // `key()` that answered on the strength of its path alone would
549
+ // answer for the ghost. Stability of the model is the only
550
+ // signal that says every such rearrangement is finished.
551
+ uctx.settle = settle
552
+
553
+ // Snapshot BEFORE the final pass (the loop condition has
554
+ // already established the tree is not done), so exhaustion can
555
+ // tell "still refining" from "stable residue" below. Taken at
556
+ // the final pass's ENTRY rather than the previous pass's exit
557
+ // — the same value when the budget allows two passes, and the
558
+ // only possible value when the trust profile sets passes to 1,
559
+ // where the old placement (cc === maxcc - 2, never true) made
560
+ // exhaustion silent, exactly the truncation docs/trust.md
561
+ // forbids. `lastCanon` IS that entry canon whenever a previous
562
+ // pass rendered one, so this costs nothing extra.
563
+ if (this.cc === maxcc - 1) {
564
+ prevCanon = lastCanon ?? res.canon
356
565
  }
357
566
 
358
- // Snapshot the second-to-last pass's result, so exhaustion can
359
- // tell "still refining" from "stable residue" below. Only paid
360
- // by models still unresolved this late.
361
- if (this.cc === maxcc - 2 && DONE !== res.dc) {
362
- prevCanon = res.canon
567
+ res = unite(te ? uctx.clone({ explain: ec(te, 'run') }) : uctx, res, top(), 'unify')
568
+
569
+ // MULTI-ERROR COLLECTION (G2 phase 6): the pass loop CONTINUES
570
+ // past an erroring pass, so independent failures a later pass
571
+ // would reach are collected in the same run — the break that
572
+ // stood here made every multi-error report truncated at the
573
+ // first erroring pass.
574
+ //
575
+ // What controls the cascade the design feared: a nil is
576
+ // ABSORBING (unite's isNil arms return the existing nil, no new
577
+ // error), so one failure stays ONE NilVal however many later
578
+ // meets touch it — a reference resolving to a failed target
579
+ // takes the same nil identity, which is exactly what lets the
580
+ // report layer dedup by identity. The probes that established
581
+ // this (fan-in refs, spread templates, disjunct trials, nested
582
+ // conjuncts) are pinned as vet.tsv's multi-* rows in both
583
+ // ports.
584
+
585
+ // The identity merge, after the pass's own unification: the
586
+ // positions this pass produced are what there is to merge.
587
+ res = mergeEntities(uctx, res)
588
+
589
+ // The staging signal for the NEXT pass, rendered here rather
590
+ // than at the top of the loop so a model that is FINISHED is
591
+ // never rendered at all: canon walks references, and the only
592
+ // trees that close a cycle are hand-built ones (the pass loop
593
+ // is what a test drives them through), which converge in one
594
+ // pass and must not be walked to decide a question that no
595
+ // longer arises.
596
+ if (DONE !== res.dc) {
597
+ const nowCanon = res.canon
598
+ settle = undefined !== lastCanon && lastCanon === nowCanon
599
+ lastCanon = nowCanon
363
600
  }
364
601
 
365
602
  uctx = uctx.clone({ root: res })
@@ -389,7 +626,7 @@ class Unify {
389
626
 
390
627
  this.res = res
391
628
  }
392
- } /* node:coverage ignore next 9 */
629
+ } /* node:coverage ignore next 10 */
393
630
 
394
631
 
395
632
 
@@ -398,4 +635,5 @@ export {
398
635
  Unify,
399
636
  unite,
400
637
  withDepth,
638
+ mergeEntities,
401
639
  }
package/src/utility.ts CHANGED
@@ -25,6 +25,139 @@ function propagateMarks(source: Val, target: Val): void {
25
25
  }
26
26
 
27
27
 
28
+ // Collect every value in the tree carrying the deprecation record (G3
29
+ // phase 4), with its path — the one walk behind vet's `deprecated`
30
+ // warnings and the LSP's Deprecated tags. The record travels on meets
31
+ // (the unite rider) and clones, so this sees the declaration and every
32
+ // use resolving through it. The non-Val guard is for a bag's raw peg
33
+ // entries, which degenerate parses can leave behind.
34
+ function collectDeprecations(
35
+ root: Val): Array<{ val: Val, path: string[] }> {
36
+ const out: Array<{ val: Val, path: string[] }> = []
37
+ walkBagVals(root, (v: any, path) => {
38
+ if (null != v.deprecation) {
39
+ out.push({ val: v, path })
40
+ }
41
+ })
42
+ return out
43
+ }
44
+
45
+
46
+ // Visit every Val reachable through bag children, with its path — the
47
+ // walk under collectDeprecations and vet's default-validity lint. The
48
+ // non-Val guard is for a bag's raw peg entries, which degenerate
49
+ // parses can leave behind (pinned by the collect-deprecations direct
50
+ // test, ts/test/coverage3.test.ts).
51
+ function walkBagVals(
52
+ root: Val, fn: (v: Val, path: string[]) => void): void {
53
+ const walk = (v: any, path: string[]): void => {
54
+ if (null == v || true !== v.isVal) {
55
+ return
56
+ }
57
+ fn(v, path)
58
+ if ((true === v.isMap || true === v.isList) && null != v.peg) {
59
+ for (const k of Object.keys(v.peg)) {
60
+ walk(v.peg[k], [...path, k])
61
+ }
62
+ }
63
+ }
64
+ walk(root, [])
65
+ }
66
+
67
+
68
+ // The one-line prose for a deprecation record, shared by vet's warning
69
+ // findings and the LSP's tagged diagnostics.
70
+ function deprecationMessage(d: Record<string, string>): string {
71
+ const msg = 'string' === typeof d.msg ? d.msg : ''
72
+ return 'deprecated' + ('' === msg ? '' : ': ' + msg) +
73
+ ('string' === typeof d.use ? ' (use ' + d.use + ')' : '') +
74
+ ('string' === typeof d.since ? ' (since ' + d.since + ')' : '')
75
+ }
76
+
77
+
78
+ // SPREAD TEMPLATES MAY NOT STAMP ONE ID ONTO EVERY CHILD (G4 phase
79
+ // 1, clearing rule 3). `&: id(svc/thing) & {…}` says that every child
80
+ // of the bag IS the entity `svc/thing`, and the identity merge then
81
+ // unifies all of them into one another: an author who wrote a
82
+ // per-child template would get a single merged blob, and any two
83
+ // children that disagreed about a field would fail at a site that
84
+ // explains nothing.
85
+ //
86
+ // A PATH-DEPENDENT argument is allowed, and is how the author says
87
+ // what they meant: `&: id(key()) & {…}` names each child distinctly,
88
+ // resolved per destination by the existing spreadClone machinery.
89
+ // Duck-typed on the `isIdFunc` flag rather than imported: this file
90
+ // is below the Val classes, and the identity function sits above
91
+ // them.
92
+ function constantIdFunc(v: any, seen?: Set<any>): any {
93
+ if (null == v || true !== v.isVal) {
94
+ return undefined
95
+ }
96
+ const s = seen ?? new Set()
97
+ if (s.has(v)) {
98
+ return undefined
99
+ }
100
+ s.add(v)
101
+
102
+ if (true === v.isIdFunc && true !== v.isPathDependent) {
103
+ return v
104
+ }
105
+
106
+ const peg = v.peg
107
+ if (null != peg && 'object' === typeof peg) {
108
+ for (const k of Object.keys(peg)) {
109
+ const found = constantIdFunc(peg[k], s)
110
+ if (undefined !== found) {
111
+ return found
112
+ }
113
+ }
114
+ }
115
+ return constantIdFunc(v.spread?.cj, s)
116
+ }
117
+
118
+
119
+ // The IDENTITY wrapper (G4 phase 1): `id("svc/auth")&{…}`, written
120
+ // as the conjunct an author writes, so canon reparses to the same
121
+ // entity. This deliberately differs from the type/hide MARKS, which
122
+ // canon drops (test/spec/marks.tsv, row `type-canon`): identity is
123
+ // semantic content, and G6's canon-hash must see it — two documents
124
+ // that disagree about which entity a node IS do not mean the same
125
+ // thing and must not hash alike.
126
+ //
127
+ // The name is JSON-quoted whatever it spells: `-` is not a bare-text
128
+ // character (test/spec/op-chars.tsv pins `a:6-2` as a parse error), so
129
+ // an unquoted `id(team-pay)` would not reparse.
130
+ function canonEntity(v: Val): string {
131
+ const c = v.canon
132
+ const e = v.entity
133
+ return null == e ? c : 'id(' + JSON.stringify(e) + ')&' + c
134
+ }
135
+
136
+
137
+ // The canonical form of a value, wrapped in the RIDERS it carries —
138
+ // the identity (G4 phase 1) and the deprecation record (G3 phase 4) —
139
+ // reparseably, so `id(name) & x` and `deprecate(x, m)` survive canon.
140
+ // Bags render their children through this (MapVal/ListVal canon),
141
+ // which is where a marked FIELD — the realistic case — lives.
142
+ //
143
+ // The riders render HERE and not in the value's own `canon` for the
144
+ // same reason the guard at the MapVal call site tests the isVal flag:
145
+ // a bag's canon recursion visits each child once, and a child that
146
+ // wrapped itself as well would render its subtree twice per level —
147
+ // 2^depth on a nested document.
148
+ function canonRiders(v: Val): string {
149
+ const c = canonEntity(v)
150
+ const d = v.deprecation
151
+ if (null == d) {
152
+ return c
153
+ }
154
+ const keys = Object.keys(d).sort()
155
+ const rec = keys.map((k) =>
156
+ JSON.stringify(k) + ':' + JSON.stringify(d[k])).join(',')
157
+ return 'deprecate(' + c + ('' === rec ? '' : ',{' + rec + '}') + ')'
158
+ }
159
+
160
+
28
161
  function formatPath(path: Val | string[], absolute?: boolean) {
29
162
  let parts: string[]
30
163
  if (Array.isArray(path)) {
@@ -188,12 +321,17 @@ function items(o: any) {
188
321
  else {
189
322
  return []
190
323
  }
191
- } /* node:coverage ignore next 15 */
324
+ } /* node:coverage ignore next 18 */
192
325
 
193
326
 
194
327
  export {
195
328
  items,
196
329
  propagateMarks,
330
+ constantIdFunc,
331
+ canonRiders,
332
+ collectDeprecations,
333
+ walkBagVals,
334
+ deprecationMessage,
197
335
  formatPath,
198
336
  walk,
199
337
  WalkApply,