aontu 0.52.1 → 0.54.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 (310) 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 +15 -3
  7. package/dist/aontu.js +126 -5
  8. package/dist/aontu.js.map +1 -1
  9. package/dist/cli.d.ts +45 -1
  10. package/dist/cli.js +2845 -69
  11. package/dist/cli.js.map +1 -1
  12. package/dist/ctx.d.ts +18 -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 +45 -8
  20. package/dist/err.js.map +1 -1
  21. package/dist/graph.d.ts +13 -0
  22. package/dist/graph.js +110 -0
  23. package/dist/graph.js.map +1 -0
  24. package/dist/hcanon.d.ts +3 -0
  25. package/dist/hcanon.js +145 -0
  26. package/dist/hcanon.js.map +1 -0
  27. package/dist/hints.js +235 -8
  28. package/dist/hints.js.map +1 -1
  29. package/dist/jsonschema.d.ts +20 -0
  30. package/dist/jsonschema.js +425 -0
  31. package/dist/jsonschema.js.map +1 -0
  32. package/dist/lang.js +955 -110
  33. package/dist/lang.js.map +1 -1
  34. package/dist/lsp.d.ts +9 -2
  35. package/dist/lsp.js +343 -48
  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 +941 -0
  42. package/dist/mcp.js.map +1 -0
  43. package/dist/mod-tool.d.ts +58 -0
  44. package/dist/mod-tool.js +532 -0
  45. package/dist/mod-tool.js.map +1 -0
  46. package/dist/mod.d.ts +35 -0
  47. package/dist/mod.js +345 -0
  48. package/dist/mod.js.map +1 -0
  49. package/dist/patch.d.ts +49 -0
  50. package/dist/patch.js +506 -0
  51. package/dist/patch.js.map +1 -0
  52. package/dist/provenance.d.ts +41 -0
  53. package/dist/provenance.js +336 -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 +15 -0
  59. package/dist/reach.js +168 -0
  60. package/dist/reach.js.map +1 -0
  61. package/dist/relation.d.ts +23 -0
  62. package/dist/relation.js +230 -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/sig.d.ts +25 -0
  68. package/dist/sig.js +277 -0
  69. package/dist/sig.js.map +1 -0
  70. package/dist/sigdecl.d.ts +2 -0
  71. package/dist/sigdecl.js +11 -0
  72. package/dist/sigdecl.js.map +1 -0
  73. package/dist/siggate.d.ts +4 -0
  74. package/dist/siggate.js +90 -0
  75. package/dist/siggate.js.map +1 -0
  76. package/dist/site.d.ts +4 -0
  77. package/dist/site.js +31 -0
  78. package/dist/site.js.map +1 -1
  79. package/dist/std.d.ts +1 -0
  80. package/dist/std.js +129 -0
  81. package/dist/std.js.map +1 -0
  82. package/dist/subsume.d.ts +39 -0
  83. package/dist/subsume.js +573 -0
  84. package/dist/subsume.js.map +1 -0
  85. package/dist/trim.d.ts +19 -0
  86. package/dist/trim.js +155 -0
  87. package/dist/trim.js.map +1 -0
  88. package/dist/tsconfig.tsbuildinfo +1 -1
  89. package/dist/type.d.ts +17 -1
  90. package/dist/type.js.map +1 -1
  91. package/dist/unify.d.ts +2 -1
  92. package/dist/unify.js +234 -38
  93. package/dist/unify.js.map +1 -1
  94. package/dist/utility.d.ts +8 -1
  95. package/dist/utility.js +68 -1
  96. package/dist/utility.js.map +1 -1
  97. package/dist/val/AggFuncVal.d.ts +44 -0
  98. package/dist/val/AggFuncVal.js +364 -0
  99. package/dist/val/AggFuncVal.js.map +1 -0
  100. package/dist/val/ArithFuncVal.d.ts +31 -0
  101. package/dist/val/ArithFuncVal.js +62 -0
  102. package/dist/val/ArithFuncVal.js.map +1 -0
  103. package/dist/val/BagVal.d.ts +7 -0
  104. package/dist/val/BagVal.js +150 -11
  105. package/dist/val/BagVal.js.map +1 -1
  106. package/dist/val/CloseFuncVal.js +9 -1
  107. package/dist/val/CloseFuncVal.js.map +1 -1
  108. package/dist/val/ConjunctVal.d.ts +1 -1
  109. package/dist/val/ConjunctVal.js +19 -0
  110. package/dist/val/ConjunctVal.js.map +1 -1
  111. package/dist/val/ConstraintVal.d.ts +7 -1
  112. package/dist/val/ConstraintVal.js +523 -45
  113. package/dist/val/ConstraintVal.js.map +1 -1
  114. package/dist/val/ContainerKindVal.d.ts +35 -0
  115. package/dist/val/ContainerKindVal.js +99 -0
  116. package/dist/val/ContainerKindVal.js.map +1 -0
  117. package/dist/val/CopyFuncVal.d.ts +1 -2
  118. package/dist/val/CopyFuncVal.js.map +1 -1
  119. package/dist/val/Decimal.d.ts +1 -0
  120. package/dist/val/Decimal.js +13 -0
  121. package/dist/val/Decimal.js.map +1 -1
  122. package/dist/val/DeprecateFuncVal.d.ts +11 -0
  123. package/dist/val/DeprecateFuncVal.js +47 -0
  124. package/dist/val/DeprecateFuncVal.js.map +1 -0
  125. package/dist/val/DisjunctVal.d.ts +1 -2
  126. package/dist/val/DisjunctVal.js +269 -53
  127. package/dist/val/DisjunctVal.js.map +1 -1
  128. package/dist/val/EachFuncVal.d.ts +15 -0
  129. package/dist/val/EachFuncVal.js +75 -0
  130. package/dist/val/EachFuncVal.js.map +1 -0
  131. package/dist/val/ExpectVal.js +62 -4
  132. package/dist/val/ExpectVal.js.map +1 -1
  133. package/dist/val/FeatureVal.js +1 -1
  134. package/dist/val/FeatureVal.js.map +1 -1
  135. package/dist/val/FilterFuncVal.d.ts +15 -0
  136. package/dist/val/FilterFuncVal.js +91 -0
  137. package/dist/val/FilterFuncVal.js.map +1 -0
  138. package/dist/val/FuncBaseVal.d.ts +7 -1
  139. package/dist/val/FuncBaseVal.js +183 -2
  140. package/dist/val/FuncBaseVal.js.map +1 -1
  141. package/dist/val/GraphAtomVal.d.ts +39 -0
  142. package/dist/val/GraphAtomVal.js +184 -0
  143. package/dist/val/GraphAtomVal.js.map +1 -0
  144. package/dist/val/HideFuncVal.js.map +1 -1
  145. package/dist/val/JunctionVal.js +24 -1
  146. package/dist/val/JunctionVal.js.map +1 -1
  147. package/dist/val/KeyFuncVal.d.ts +1 -1
  148. package/dist/val/KeyFuncVal.js +38 -30
  149. package/dist/val/KeyFuncVal.js.map +1 -1
  150. package/dist/val/ListVal.js +94 -14
  151. package/dist/val/ListVal.js.map +1 -1
  152. package/dist/val/LowerFuncVal.js.map +1 -1
  153. package/dist/val/MapVal.d.ts +1 -0
  154. package/dist/val/MapVal.js +146 -14
  155. package/dist/val/MapVal.js.map +1 -1
  156. package/dist/val/MatchFuncVal.d.ts +15 -0
  157. package/dist/val/MatchFuncVal.js +107 -0
  158. package/dist/val/MatchFuncVal.js.map +1 -0
  159. package/dist/val/MoveFuncVal.js.map +1 -1
  160. package/dist/val/NilVal.js +24 -0
  161. package/dist/val/NilVal.js.map +1 -1
  162. package/dist/val/OpBaseVal.d.ts +1 -1
  163. package/dist/val/OpBaseVal.js +19 -1
  164. package/dist/val/OpBaseVal.js.map +1 -1
  165. package/dist/val/OpenFuncVal.js +4 -1
  166. package/dist/val/OpenFuncVal.js.map +1 -1
  167. package/dist/val/PackFuncVal.d.ts +15 -0
  168. package/dist/val/PackFuncVal.js +108 -0
  169. package/dist/val/PackFuncVal.js.map +1 -0
  170. package/dist/val/PathFuncVal.d.ts +2 -2
  171. package/dist/val/PathFuncVal.js +75 -16
  172. package/dist/val/PathFuncVal.js.map +1 -1
  173. package/dist/val/PathVal.d.ts +25 -0
  174. package/dist/val/PathVal.js +150 -0
  175. package/dist/val/PathVal.js.map +1 -0
  176. package/dist/val/PlaceVal.d.ts +13 -0
  177. package/dist/val/PlaceVal.js +131 -0
  178. package/dist/val/PlaceVal.js.map +1 -0
  179. package/dist/val/PlusOpVal.d.ts +2 -1
  180. package/dist/val/PlusOpVal.js +61 -37
  181. package/dist/val/PlusOpVal.js.map +1 -1
  182. package/dist/val/PrefFuncVal.js.map +1 -1
  183. package/dist/val/PrefVal.d.ts +4 -2
  184. package/dist/val/PrefVal.js +223 -41
  185. package/dist/val/PrefVal.js.map +1 -1
  186. package/dist/val/RecurseVal.d.ts +19 -0
  187. package/dist/val/RecurseVal.js +217 -0
  188. package/dist/val/RecurseVal.js.map +1 -0
  189. package/dist/val/RefVal.d.ts +3 -2
  190. package/dist/val/RefVal.js +181 -66
  191. package/dist/val/RefVal.js.map +1 -1
  192. package/dist/val/ReferFuncVal.d.ts +59 -0
  193. package/dist/val/ReferFuncVal.js +604 -0
  194. package/dist/val/ReferFuncVal.js.map +1 -0
  195. package/dist/val/ScalarKindVal.d.ts +4 -3
  196. package/dist/val/ScalarKindVal.js +12 -12
  197. package/dist/val/ScalarKindVal.js.map +1 -1
  198. package/dist/val/SuperFuncVal.d.ts +4 -2
  199. package/dist/val/SuperFuncVal.js +118 -14
  200. package/dist/val/SuperFuncVal.js.map +1 -1
  201. package/dist/val/TopVal.d.ts +1 -1
  202. package/dist/val/TopVal.js.map +1 -1
  203. package/dist/val/TypeFuncVal.js.map +1 -1
  204. package/dist/val/UpperFuncVal.js.map +1 -1
  205. package/dist/val/Val.d.ts +7 -1
  206. package/dist/val/Val.js +170 -2
  207. package/dist/val/Val.js.map +1 -1
  208. package/dist/val/VarVal.js.map +1 -1
  209. package/dist/val/arith.d.ts +6 -0
  210. package/dist/val/arith.js +173 -0
  211. package/dist/val/arith.js.map +1 -0
  212. package/dist/vet.d.ts +45 -0
  213. package/dist/vet.js +802 -0
  214. package/dist/vet.js.map +1 -0
  215. package/dist/view.d.ts +85 -0
  216. package/dist/view.js +1948 -0
  217. package/dist/view.js.map +1 -0
  218. package/dist/walk.d.ts +2 -0
  219. package/dist/walk.js +91 -0
  220. package/dist/walk.js.map +1 -0
  221. package/grammar/aontu.gbnf +138 -0
  222. package/grammar/aontu.lark +118 -0
  223. package/grammar/aontu.tmLanguage.json +184 -0
  224. package/package.json +30 -8
  225. package/skill/SKILL.md +37 -0
  226. package/skill/error-codes.md +62 -0
  227. package/skill/examples.md +99 -0
  228. package/skill/grammar-card.md +56 -0
  229. package/src/agentsmd.ts +135 -0
  230. package/src/aontu.ts +166 -5
  231. package/src/cli.ts +3324 -72
  232. package/src/ctx.ts +93 -1
  233. package/src/diff.ts +198 -0
  234. package/src/err.ts +50 -8
  235. package/src/graph.ts +185 -0
  236. package/src/hcanon.ts +167 -0
  237. package/src/hints.ts +291 -8
  238. package/src/jsonschema.ts +552 -0
  239. package/src/lang.ts +1049 -110
  240. package/src/lsp.ts +351 -49
  241. package/src/mcp-server.ts +187 -0
  242. package/src/mcp.ts +1130 -0
  243. package/src/mod-tool.ts +718 -0
  244. package/src/mod.ts +453 -0
  245. package/src/patch.ts +628 -0
  246. package/src/provenance.ts +437 -0
  247. package/src/query.ts +381 -0
  248. package/src/reach.ts +213 -0
  249. package/src/relation.ts +298 -0
  250. package/src/report-sarif.ts +137 -0
  251. package/src/sig.ts +345 -0
  252. package/src/sigdecl.ts +11 -0
  253. package/src/siggate.ts +144 -0
  254. package/src/site.ts +36 -1
  255. package/src/std.ts +131 -0
  256. package/src/subsume.ts +739 -0
  257. package/src/trim.ts +195 -0
  258. package/src/tsconfig.json +10 -4
  259. package/src/type.ts +51 -2
  260. package/src/unify.ts +253 -38
  261. package/src/utility.ts +79 -1
  262. package/src/val/AggFuncVal.ts +546 -0
  263. package/src/val/ArithFuncVal.ts +108 -0
  264. package/src/val/BagVal.ts +158 -12
  265. package/src/val/CloseFuncVal.ts +9 -1
  266. package/src/val/ConjunctVal.ts +20 -0
  267. package/src/val/ConstraintVal.ts +576 -48
  268. package/src/val/ContainerKindVal.ts +158 -0
  269. package/src/val/CopyFuncVal.ts +0 -1
  270. package/src/val/Decimal.ts +15 -0
  271. package/src/val/DeprecateFuncVal.ts +84 -0
  272. package/src/val/DisjunctVal.ts +282 -59
  273. package/src/val/EachFuncVal.ts +133 -0
  274. package/src/val/ExpectVal.ts +65 -8
  275. package/src/val/FeatureVal.ts +1 -1
  276. package/src/val/FilterFuncVal.ts +154 -0
  277. package/src/val/FuncBaseVal.ts +206 -2
  278. package/src/val/GraphAtomVal.ts +264 -0
  279. package/src/val/HideFuncVal.ts +0 -2
  280. package/src/val/JunctionVal.ts +24 -1
  281. package/src/val/KeyFuncVal.ts +39 -35
  282. package/src/val/ListVal.ts +101 -15
  283. package/src/val/LowerFuncVal.ts +0 -1
  284. package/src/val/MapVal.ts +153 -14
  285. package/src/val/MatchFuncVal.ts +176 -0
  286. package/src/val/MoveFuncVal.ts +0 -2
  287. package/src/val/NilVal.ts +25 -0
  288. package/src/val/OpBaseVal.ts +20 -1
  289. package/src/val/OpenFuncVal.ts +4 -2
  290. package/src/val/PackFuncVal.ts +175 -0
  291. package/src/val/PathFuncVal.ts +107 -20
  292. package/src/val/PathVal.ts +221 -0
  293. package/src/val/PlaceVal.ts +193 -0
  294. package/src/val/PlusOpVal.ts +67 -39
  295. package/src/val/PrefFuncVal.ts +0 -1
  296. package/src/val/PrefVal.ts +251 -60
  297. package/src/val/RecurseVal.ts +285 -0
  298. package/src/val/RefVal.ts +183 -78
  299. package/src/val/ReferFuncVal.ts +732 -0
  300. package/src/val/ScalarKindVal.ts +12 -13
  301. package/src/val/SuperFuncVal.ts +137 -13
  302. package/src/val/TopVal.ts +1 -2
  303. package/src/val/TypeFuncVal.ts +0 -2
  304. package/src/val/UpperFuncVal.ts +0 -1
  305. package/src/val/Val.ts +216 -4
  306. package/src/val/VarVal.ts +0 -1
  307. package/src/val/arith.ts +319 -0
  308. package/src/vet.ts +1025 -0
  309. package/src/view.ts +2563 -0
  310. package/src/walk.ts +99 -0
@@ -0,0 +1,285 @@
1
+ /* Copyright (c) 2025 Richard Rodger, MIT License */
2
+
3
+ // THE RECURSIVE RESIDUAL (docs/design/RECURSION.0.md): where RefVal's
4
+ // prefix test used to answer `path_cycle` for a self-reference --
5
+ // `$.Node` written anywhere inside `Node` -- it now answers this
6
+ // value: a deferred reference carrying the target path, exactly as a
7
+ // constraint atom carries its bound. No new syntax: the reference the
8
+ // author wrote simply MEANS the fixpoint.
9
+ //
10
+ // The three moments of every residual:
11
+ //
12
+ // - At unification, EXPAND ONE LEVEL PER MEET WITH STRUCTURE: met by
13
+ // a map, list, or scalar, the residual clones the schema body one
14
+ // level -- per destination, under ADR-005's clone discipline -- and
15
+ // unifies the clone with the peer. The clone's own self-reference
16
+ // is again this residual, so each expansion consumes one level of
17
+ // concrete data; data is finite, so expansion terminates
18
+ // (structural descent), with the depth budget as the backstop
19
+ // (recursion_budget).
20
+ // - In canon and the aon1- hash, SYMBOLIC: the residual prints as the
21
+ // reference the author wrote, so a recursive schema's canon is
22
+ // finite and round-trips, and the hash pins the mu-form rather than
23
+ // any unrolling.
24
+ // - At generation, an unexpanded residual REFUSES with
25
+ // recursion_unexpanded. Guardedness is therefore EMERGENT: under
26
+ // `next?:` the refusal is isolated and the optional key drops;
27
+ // under `*null | $.Node` the preference generates; a REQUIRED
28
+ // recursive field with no data refuses at the instance.
29
+
30
+ import type {
31
+ Val,
32
+ ValSpec,
33
+ } from '../type'
34
+
35
+ import {
36
+ DONE,
37
+ } from '../type'
38
+
39
+ import {
40
+ AontuContext,
41
+ } from '../ctx'
42
+
43
+ import { makeNilErr } from '../err'
44
+
45
+ import { FeatureVal } from './FeatureVal'
46
+ import { ConjunctVal } from './ConjunctVal'
47
+ import { unite } from '../unify'
48
+ import { propagateMarks, walk } from '../utility'
49
+
50
+
51
+ class RecurseVal extends FeatureVal {
52
+ isRecurse = true
53
+ isGenable = true
54
+ // LAST in a conjunct fold, after even the graph atoms: the residual
55
+ // wants to see the assembled concrete structure it expands against.
56
+ cjo = 47000
57
+
58
+ // The target path, absolute from the root, as the reference spelled
59
+ // it.
60
+ target: string[]
61
+ // Expansion depth so far along this chain, charged against the
62
+ // depth budget (the T-1 backstop).
63
+ xc: number
64
+
65
+ constructor(spec: ValSpec, ctx?: AontuContext) {
66
+ super(spec, ctx)
67
+ this.target = (spec as any).target ?? []
68
+ this.xc = (spec as any).xc ?? 0
69
+ // A settled residual: a type() body carrying one must settle, and
70
+ // an unmet recursion is its own value until data arrives.
71
+ this.dc = DONE
72
+ }
73
+
74
+ clone(ctx: AontuContext, spec?: ValSpec): Val {
75
+ const out: any = super.clone(ctx, spec)
76
+ out.target = this.target
77
+ out.xc = this.xc
78
+ return out
79
+ }
80
+
81
+ // The schema body the target names, from the ROOT: the fixpoint is
82
+ // over the finished definition, and the definition's own residual
83
+ // keeps it finite. When the root does not contain the target -- the
84
+ // residual was LIFTED out of its defining tree, as vet's anchored
85
+ // meet does -- fall back to ctx._fixroot, the settled tree the
86
+ // lifter kept for exactly this walk.
87
+ private body(ctx: AontuContext): Val | undefined {
88
+ return walkTarget(ctx.root, this.target)
89
+ ?? walkTarget((ctx as any)._fixroot, this.target)
90
+ }
91
+
92
+ unify(peer: Val, ctx: AontuContext): Val {
93
+ const p: any = peer
94
+
95
+ // The self-drive: nothing to advance -- the residual waits for
96
+ // structure. (A null/nil peer never arrives; unite's ladder
97
+ // absorbs both.)
98
+ if (null == peer || true === p.isTop) {
99
+ return this
100
+ }
101
+
102
+ // The same fixpoint twice is one fixpoint.
103
+ if (true === p.isRecurse) {
104
+ if (this.target.length === p.target.length
105
+ && this.target.every((s, i) => s === p.target[i])) {
106
+ return this
107
+ }
108
+ // Different targets -- mutual recursion meeting -- are BOTH
109
+ // held: each expands as data arrives, through the fold that
110
+ // keeps a conjunct's members separate.
111
+ const out = new ConjunctVal({ peg: [this, peer] }, ctx)
112
+ propagateMarks(this, out)
113
+ out.path = this.path
114
+ return out
115
+ }
116
+
117
+ // CONCRETE STRUCTURE: expand one level against it.
118
+ if (true === p.isMap || true === p.isList || true === p.isScalar) {
119
+ if (ctx.budget.depth <= this.xc) {
120
+ return makeNilErr(ctx, 'recursion_budget', this, peer, 'recurse',
121
+ { target: '$.' + this.target.join('.') })
122
+ }
123
+ const body = this.body(ctx)
124
+ if (undefined === body) {
125
+ // The definition has not assembled yet (an early pass): hold
126
+ // the peer beside the residual and try again when it has.
127
+ const out = new ConjunctVal({ peg: [this, peer] }, ctx)
128
+ propagateMarks(this, out)
129
+ out.path = this.path
130
+ return out
131
+ }
132
+ // ADR-005's clone discipline: the expansion is a per-destination
133
+ // instantiation, so the definition itself is never written
134
+ // into. The clone's type/hide marks are CLEARED at every depth,
135
+ // exactly as a plain reference copy clears them (concreteFlow's
136
+ // rule): the schema is hidden, the instances it expands into
137
+ // are the output.
138
+ // The clone is REBASED to the DRIVE path explicitly (spec.path;
139
+ // the Go twin passes cp(r.path), whose stored paths its clone
140
+ // arms keep clean): the default rebase slices the definition's
141
+ // path by the destination's length, which leaks definition
142
+ // segments into instance paths whenever the destination is
143
+ // shallower -- a residual carried inside a copied definition
144
+ // body (`chain: $.spec.Step` inside Policy, copied to a slot
145
+ // shallower than the definition) reported
146
+ // `$.payments_policy.Policy.chain.then.approver` for the
147
+ // finding Go placed at `$.payments_policy.chain.then.approver`.
148
+ // The drive path is also right under vet's anchored meet, which
149
+ // drives AT the anchor's own path (see vet.ts), so anchored
150
+ // findings land in the schema's namespace in both ports.
151
+ const level: any = body.clone(ctx, {
152
+ dup: true, path: [...ctx.path],
153
+ } as any)
154
+ walk(level, (_key: string | number | undefined, v: Val) => {
155
+ v.mark.type = false
156
+ v.mark.hide = false
157
+ return v
158
+ })
159
+ bumpRecurse(level, this.xc + 1)
160
+ return unite(ctx, level, peer, 'recurse-expand')
161
+ }
162
+
163
+ // Anything else -- a func still resolving, a reference, a
164
+ // constraint -- waits beside the residual.
165
+ const out = new ConjunctVal({ peg: [this, peer] }, ctx)
166
+ propagateMarks(this, out)
167
+ out.path = this.path
168
+ return out
169
+ }
170
+
171
+ get canon(): string {
172
+ return '$.' + this.target.join('.')
173
+ }
174
+
175
+ gen(ctx: AontuContext) {
176
+ // An unexpanded residual in a demanded position refuses;
177
+ // guardedness is emergent -- under an optional key the bag's
178
+ // isolated context swallows this and drops the key.
179
+ makeNilErr(ctx, 'recursion_unexpanded', this, undefined, 'recurse',
180
+ { target: '$.' + this.target.join('.') })
181
+ return undefined
182
+ }
183
+ }
184
+
185
+
186
+ // walkTarget descends a tree by the residual's absolute target path,
187
+ // answering the definition node or undefined.
188
+ function walkTarget(root: any, target: string[]): Val | undefined {
189
+ let node: any = root
190
+ for (const seg of target) {
191
+ node = node?.peg?.[seg]
192
+ }
193
+ return null != node && true === node.isVal ? node : undefined
194
+ }
195
+
196
+
197
+ // bumpRecurse stamps the expansion depth onto every residual inside a
198
+ // freshly cloned level, so descent is charged along the chain.
199
+ function bumpRecurse(v: any, xc: number): void {
200
+ if (null == v || true !== v.isVal) {
201
+ return
202
+ }
203
+ if (true === v.isRecurse) {
204
+ v.xc = Math.max(v.xc, xc)
205
+ return
206
+ }
207
+ const peg: any = v.peg
208
+ if (true === v.isMap && null != peg) {
209
+ for (const k of Object.keys(peg)) {
210
+ bumpRecurse(peg[k], xc)
211
+ }
212
+ }
213
+ else if (true === v.isList && Array.isArray(peg)) {
214
+ for (const e of peg) {
215
+ bumpRecurse(e, xc)
216
+ }
217
+ }
218
+ else if (true === v.isConjunct && Array.isArray(peg)) {
219
+ for (const e of peg) {
220
+ bumpRecurse(e, xc)
221
+ }
222
+ }
223
+ if (null != v.spread?.cj) {
224
+ bumpRecurse(v.spread.cj, xc)
225
+ }
226
+ }
227
+
228
+
229
+ // containsRecurseOf answers whether a definition holds a residual of
230
+ // the given target -- i.e. the definition is (transitively) the
231
+ // fixpoint that target names. A reference RESOLVING to such a
232
+ // definition must itself answer the residual: cloned instead, every
233
+ // reparse of a canon unrolled the schema one more level and canon
234
+ // never converged.
235
+ function containsRecurseOf(v: any, target: string[], depth?: number): boolean {
236
+ const d = depth ?? 0
237
+ if (null == v || true !== v.isVal || 8 < d) {
238
+ return false
239
+ }
240
+ if (true === v.isRecurse) {
241
+ return v.target.length === target.length
242
+ && v.target.every((s: string, i: number) => s === target[i])
243
+ }
244
+ // A RAW REFERENCE to the target IS the recursion, minted or not:
245
+ // the answer must not depend on whether the definition's own
246
+ // prefix position has been visited yet. Without this arm the
247
+ // answer was ORDER-DEPENDENT -- reparsing a generated canon puts
248
+ // the instance before the definition, its trailing `$.spec.Step`
249
+ // leaves resolved before `Step.then` had minted, and each resolve
250
+ // cloned one more unrolled level until the unify depth guard
251
+ // (unify_cycle) killed the document.
252
+ if (true === v.isRef && Array.isArray(v.peg)) {
253
+ if (v.peg.length === target.length
254
+ && v.peg.every((s: any, i: number) => s === target[i])) {
255
+ return true
256
+ }
257
+ }
258
+ const peg: any = v.peg
259
+ if (true === v.isMap && null != peg) {
260
+ for (const k of Object.keys(peg)) {
261
+ if (containsRecurseOf(peg[k], target, d + 1)) {
262
+ return true
263
+ }
264
+ }
265
+ }
266
+ else if ((true === v.isList || true === v.isConjunct || true === v.isDisjunct)
267
+ && Array.isArray(peg)) {
268
+ for (const e of peg) {
269
+ if (containsRecurseOf(e, target, d + 1)) {
270
+ return true
271
+ }
272
+ }
273
+ }
274
+ if (null != v.spread?.cj && containsRecurseOf(v.spread.cj, target, d + 1)) {
275
+ return true
276
+ }
277
+ return false
278
+ } /* node:coverage ignore next 7 */
279
+
280
+
281
+ export {
282
+ RecurseVal,
283
+ bumpRecurse,
284
+ containsRecurseOf,
285
+ }
package/src/val/RefVal.ts CHANGED
@@ -1,7 +1,6 @@
1
1
  /* Copyright (c) 2021-2025 Richard Rodger, MIT License */
2
2
 
3
3
 
4
- import Util from 'node:util'
5
4
 
6
5
  import {
7
6
  walk,
@@ -23,6 +22,7 @@ import {
23
22
  import { AontuContext } from '../ctx'
24
23
 
25
24
  import { makeNilErr } from '../err'
25
+ import { RecurseVal, containsRecurseOf } from './RecurseVal'
26
26
  import { unite } from '../unify'
27
27
 
28
28
 
@@ -49,6 +49,30 @@ import { BigDecimalVal } from './BigDecimalVal'
49
49
  const UNSPELLABLE_SEGMENT = '\u0000unspellable'
50
50
 
51
51
 
52
+ // Is this value an unresolved type()/hide() call — or a conjunct still
53
+ // carrying one? A reference that lands on one must defer rather than
54
+ // clone it (see the call site in `find`): the marks such a call will
55
+ // stamp belong to the field it was WRITTEN at, and a clone resolving
56
+ // at the reference's site re-applies them after the reference's
57
+ // mark-clearing walk has already run. Only the two mark wrappers
58
+ // qualify — every other pending call resolves to an unmarked value,
59
+ // and the existing early-clone behaviour for those is pinned
60
+ // (move()/copy() ghost rows, hole-filling conjuncts).
61
+ function pendingMarkWrapper(v: any): boolean {
62
+ if (true === v.isTypeFunc || true === v.isHideFunc) {
63
+ return !v.done
64
+ }
65
+ if (true === v.isConjunct && Array.isArray(v.peg)) {
66
+ for (const t of v.peg) {
67
+ if (pendingMarkWrapper(t)) {
68
+ return true
69
+ }
70
+ }
71
+ }
72
+ return false
73
+ }
74
+
75
+
52
76
  class RefVal extends FeatureVal {
53
77
  isRef = true
54
78
  isGenable = true
@@ -175,7 +199,6 @@ class RefVal extends FeatureVal {
175
199
 
176
200
  const te = ctx.explain && explainOpen(ctx, ctx.explain, 'Ref', this, peer)
177
201
  let out: Val = this
178
- let why = 'id'
179
202
 
180
203
  if (this.id !== peer.id) {
181
204
 
@@ -191,29 +214,24 @@ class RefVal extends FeatureVal {
191
214
  if (resolved instanceof RefVal) {
192
215
  if (peer.isTop) {
193
216
  out = this
194
- why = 'pt'
195
217
  }
196
218
  else if (peer.isNil) {
197
219
  out = makeNilErr(ctx, 'ref[' + this.peg + ']', this, peer)
198
- why = 'pn'
199
220
  }
200
221
 
201
222
  // same path
202
223
  else if (this.canon === peer.canon) {
203
224
  out = this
204
- why = 'pp'
205
225
  }
206
226
 
207
227
  else {
208
228
  // Ensure RefVal done is incremented
209
229
  this.dc = DONE === this.dc ? DONE : this.dc + 1
210
230
  out = new ConjunctVal({ peg: [this, peer] }, ctx)
211
- why = 'cj'
212
231
  }
213
232
  }
214
233
  else {
215
234
  out = unite(te ? ctx.clone({ explain: ec(te, 'RES') }) : ctx, resolved, peer, 'ref')
216
- why = 'u'
217
235
  }
218
236
 
219
237
  out.dc = DONE === out.dc ? DONE : this.dc + 1
@@ -226,7 +244,15 @@ class RefVal extends FeatureVal {
226
244
  }
227
245
 
228
246
 
229
- find(ctx: AontuContext) {
247
+ // `snap` is set by snapshotRefSpread (MapVal): a SPREAD snapshot
248
+ // wants the target's pre-resolution STRUCTURE — key()/path() still
249
+ // unresolved, to be re-resolved per destination — so the
250
+ // pending-mark-wrapper defer below must not apply to it. Deferring
251
+ // there made the snapshot wait until the target's own key() had
252
+ // resolved at the target, and the literal leaked into every
253
+ // destination (the exact failure the snapshot exists to prevent —
254
+ // test/spec/spread-type.tsv, spread-type-key-ref).
255
+ find(ctx: AontuContext, snap?: boolean) {
230
256
  let out: Val | undefined = undefined
231
257
 
232
258
  // Check if self.path starts with peg (cycle detection).
@@ -254,50 +280,56 @@ class RefVal extends FeatureVal {
254
280
  // let descent = ''
255
281
 
256
282
  if (isprefixpath) {
257
- // console.log('SELFPATH', selfpath, 'PEGPATH', pegpath)
258
- out = makeNilErr(ctx, 'path_cycle', this)
283
+ // THE DETECTOR'S ANSWER IS A RESIDUAL (RECURSION.0.md): a
284
+ // self-reference under a guarded shape is the fixpoint the
285
+ // author wrote, so the prefix hit mints the recursive residual
286
+ // instead of refusing -- except the degenerate all-empty
287
+ // spelling (path("")), which names nothing and keeps its
288
+ // path_cycle. Only all-string paths recurse; anything else
289
+ // keeps the conservative refusal.
290
+ let degenerate = 0 === this.path.length
291
+ let target: string[] = []
292
+ for (let i = 0; i < this.peg.length && !degenerate; i++) {
293
+ if ('string' !== typeof this.peg[i] || '' === this.peg[i]) {
294
+ degenerate = true
295
+ break
296
+ }
297
+ target.push(this.peg[i] as string)
298
+ }
299
+ if (degenerate) {
300
+ out = makeNilErr(ctx, 'path_cycle', this)
301
+ }
302
+ else {
303
+ const rec: any = new RecurseVal({ target } as any, ctx)
304
+ rec.site = this.site
305
+ rec.path = [...this.path]
306
+ out = rec
307
+ }
259
308
  }
260
309
  else {
261
310
 
262
311
  let parts: string[] = []
263
312
 
264
- let modes: string[] = []
265
-
266
313
  for (let pI = 0; pI < this.peg.length; pI++) {
267
314
  let part = this.peg[pI]
315
+ // An unspellable segment MISSES BEFORE ANY LOOKUP. The marker
316
+ // is NUL-prefixed because no spelling produces one, but a
317
+ // document can still hold a key spelled with an escaped NUL
318
+ // (`a:{"unspellable":7}`), and matching it would turn the
319
+ // silent path-shortening this marker exists to prevent into a
320
+ // different silent wrong value. The marker is a marker, never a
321
+ // lookup key.
322
+ if (UNSPELLABLE_SEGMENT === part) {
323
+ return makeNilErr(ctx, 'no_path', this)
324
+ }
268
325
  if (part instanceof VarVal) {
269
- let strval = (part as VarVal).peg
270
- let name = strval ? '' + strval.peg : ''
271
-
272
- if ('KEY' === name) {
273
- if (pI === this.peg.length - 1) {
274
- modes.push(name)
275
- }
276
- else {
277
- // TODO: return a Nil explaining error
278
- return
279
- }
280
- }
281
-
282
- if ('SELF' === name) {
283
- if (pI === 0) {
284
- modes.push(name)
285
- }
286
- else {
287
- // TODO: return a Nil explaining error
288
- return
289
- }
290
- }
291
- else if ('PARENT' === name) {
292
- if (pI === 0) {
293
- modes.push(name)
294
- }
295
- else {
296
- // TODO: return a Nil explaining error
297
- return
298
- }
299
- }
300
- else if (0 === modes.length) {
326
+ // EVERY `$name` IN A PATH IS AN ORDINARY VARIABLE. `$KEY`,
327
+ // `$SELF` and `$PARENT` used to be intercepted here by name;
328
+ // they are gone (ADR-009). `key()` is the replacement for
329
+ // `$KEY` and answers where a value LANDS rather than where it
330
+ // was written, `$SELF.x` was only ever `$.x`, and `$PARENT.x`
331
+ // was only ever `.x`.
332
+ {
301
333
  part = (part as VarVal).unify(top(), ctx)
302
334
  if (part.isNil) {
303
335
  // TODO: var not found, so can't find path
@@ -332,15 +364,9 @@ class RefVal extends FeatureVal {
332
364
  refpath = parts
333
365
  }
334
366
  else {
335
- // TODO: deprecate $KEY, etc
336
- refpath = this.path.slice(
337
- 0,
338
- (
339
- modes.includes('SELF') ? 0 :
340
- modes.includes('PARENT') ? -1 :
341
- -1 // siblings
342
- )
343
- ).concat(parts)
367
+ // A relative reference reads from the SIBLING scope: drop this
368
+ // node's own key and append the written segments.
369
+ refpath = this.path.slice(0, -1).concat(parts)
344
370
  }
345
371
 
346
372
  let sep = '.'
@@ -348,29 +374,6 @@ class RefVal extends FeatureVal {
348
374
  .reduce(((a: string[], p: string) =>
349
375
  (p === sep ? a.length = a.length - 1 : a.push(p), a)), [])
350
376
 
351
- if (modes.includes('KEY')) {
352
- // STRINGIFY. A LIST index arrives here as a JS NUMBER (jsonic puts
353
- // it in the path as one, and lang.ts copies the path wholesale),
354
- // so `.$KEY` inside a list built a StringVal whose peg was the
355
- // number 0 -- an ill-formed value, not a design choice: it canoned
356
- // as a bare 0, generated a JSON number, satisfied `number` and
357
- // failed `string`. Go stringifies, and key() already agreed with
358
- // Go, so this port disagreed with itself.
359
- //
360
- // Coerced HERE, at the consumption site, rather than by
361
- // normalising Val.path: the numeric segment originates in jsonic's
362
- // own r.k.path and every other path consumer (find's descent,
363
- // key(), the clone/spread machinery) already handles it.
364
- let key = this.path[this.path.length - 2]
365
- let sv = new StringVal({ peg: null == key ? '' : '' + key }, ctx)
366
-
367
- // TODO: other props?
368
- sv.dc = DONE
369
- sv.path = this.path
370
-
371
- return sv
372
- }
373
-
374
377
  let node = ctx.root as Val
375
378
 
376
379
  let nopath = false
@@ -388,6 +391,21 @@ class RefVal extends FeatureVal {
388
391
  else if (node.isList) {
389
392
  node = node.peg[part]
390
393
  }
394
+ // A PENDING MARK WRAPPER IS TRANSPARENT TO THE WALK: hide()
395
+ // and type() only mark, and their argument is the structure
396
+ // the path names. Without this, two sibling schemas in one
397
+ // hide() bag deadlock -- the wrapper waits for its argument,
398
+ // the argument's members wait for references that walk into
399
+ // the unresolved wrapper (BUGS.md §53's family; the
400
+ // recursive Policy/Step pair found it again). The found node
401
+ // is cloned and mark-cleared exactly as any reference target
402
+ // is, and the wrapper still marks its own field.
403
+ else if (true === (node as any).isFunc
404
+ && ((node as any).isHideFunc || (node as any).isTypeFunc)
405
+ && (true === (node as any).peg?.[0]?.isMap
406
+ || true === (node as any).peg?.[0]?.isList)) {
407
+ node = (node as any).peg[0].peg[part]
408
+ }
391
409
  else if (node.done) {
392
410
  nopath = true
393
411
  break;
@@ -404,8 +422,41 @@ class RefVal extends FeatureVal {
404
422
  }
405
423
  }
406
424
 
407
- // console.log('REFPATH', ctx.cc, pI, refpath, nopath, ctx.root, node)
408
-
425
+ // THE ANCHORED-MEET FALLBACK (vet --at): an absolute reference
426
+ // inside a lifted subtree names the SCHEMA's namespace, and the
427
+ // meet's root does not contain it. The settled schema root the
428
+ // lifter kept (AontuContext._fixroot; see RecurseVal.body) is
429
+ // the tree such a reference means. Without this, a recursion
430
+ // re-entering through a list spread died as no_path at the
431
+ // first element under vet --at, while the map-tail form (whose
432
+ // residual was minted during schema settling) worked; the Go
433
+ // port answers the anchored meet from settled structures and
434
+ // never sees the gap.
435
+ const fixroot: any = (ctx as any)._fixroot
436
+ if (this.absolute && null != fixroot
437
+ && (nopath || pI !== refpath.length)) {
438
+ nopath = false
439
+ pI = 0
440
+ let fnode: any = fixroot
441
+ for (; pI < refpath.length; pI++) {
442
+ const part = refpath[pI]
443
+ if (true === fnode.isMap || true === fnode.isList) {
444
+ fnode = fnode.peg[part]
445
+ }
446
+ else {
447
+ break
448
+ }
449
+ if (null == fnode) {
450
+ break
451
+ }
452
+ }
453
+ if (null != fnode && pI === refpath.length) {
454
+ node = fnode
455
+ }
456
+ else {
457
+ nopath = true
458
+ }
459
+ }
409
460
 
410
461
  if (nopath) {
411
462
  out = makeNilErr(ctx, 'no_path', this)
@@ -424,6 +475,53 @@ class RefVal extends FeatureVal {
424
475
  this.detectRefCycle(ctx)) {
425
476
  out = makeNilErr(ctx, 'path_cycle', this)
426
477
  }
478
+ // A PENDING MARK WRAPPER IS NOT YET A VALUE TO COPY (ADR-005).
479
+ // A type()/hide() call still waiting for its argument — an
480
+ // alias reference inside a type() body, a generator inside a
481
+ // hide() — would be cloned here as the CALL, and the clone
482
+ // then resolves at the REFERENCE's site, stamping marks that
483
+ // the mark-clearing walk below has already run too early to
484
+ // clear. That is how a type-marked alias silently suppressed
485
+ // the referring field's emission (use-cases/BUGS.md §12), how
486
+ // `hide(pack(...))` leaked its mark onto downstream packs
487
+ // (§11), and how hide() around a computed field swallowed the
488
+ // value into a silent [] (§35b). Defer instead: the reference
489
+ // residuates until the wrapper has resolved at its OWN field,
490
+ // and the ordinary marked-value path below then clears the
491
+ // marks on the clone as documented. The move() reference
492
+ // (`_hide_found`) is exempt: a move TRANSPLANTS the pending
493
+ // call, and the ghost rows (test/spec/func.tsv) pin that its
494
+ // innards resolve at the destination.
495
+ else if (null != out && !snap && !this.mark._hide_found &&
496
+ pendingMarkWrapper(out)) {
497
+ out = undefined
498
+ }
499
+ // A STAGED ARGUMENT SNAPSHOTS A SETTLED SOURCE (the argsnap
500
+ // flag, set by driveStagedArgs). A generator's data argument is
501
+ // a copy OUTSIDE the tree, so anything in the target that still
502
+ // resolves against its own tree location — a spread-injected
503
+ // relative reference, a pending template — must finish there
504
+ // BEFORE the copy is taken: cloned earlier, the copy's rebased
505
+ // relative refs dangle under the generator and the model dies
506
+ // as *_no_gen with the generator never firing. Deferring here
507
+ // is exactly the documented staging rule: the generator waits
508
+ // for the source, then snapshots it whole.
509
+ else if (null != out && !snap && true === (ctx as any).argsnap &&
510
+ !out.done) {
511
+ out = undefined
512
+ }
513
+ // A REFERENCE TO A RECURSIVE DEFINITION IS THE FIXPOINT
514
+ // REFERENCE (RECURSION.0.md): resolving it to a clone
515
+ // unrolled the schema one level, and every reparse of a
516
+ // canon then unrolled one more -- canon never converged. The
517
+ // residual is the resolved form, exactly as at the prefix
518
+ // positions inside the definition.
519
+ else if (null != out && !snap && containsRecurseOf(out, this.peg as any)) {
520
+ const rec: any = new RecurseVal({ target: [...this.peg] } as any, ctx)
521
+ rec.site = this.site
522
+ rec.path = [...this.path]
523
+ out = rec
524
+ }
427
525
  // Types and hidden values are cloned and made concrete
428
526
  else if (null != out) { // && (out.mark.type || out.mark.hide)) {
429
527
 
@@ -452,6 +550,12 @@ class RefVal extends FeatureVal {
452
550
  walk(out, (_key: string | number | undefined, val: Val) => {
453
551
  val.mark.type = false
454
552
  val.mark.hide = false
553
+ // THE LINK IS NOT CLEARED (G4 phase 3): a link says what a
554
+ // value POINTS AT, and a copy of a link points at the same
555
+ // thing. An ABSOLUTE address still names the same node from
556
+ // the copy; a RELATIVE one is read from the copy's own
557
+ // position, which is what makes a referenced model resolve
558
+ // its internal links inside the copy (ADR-014).
455
559
  return val
456
560
  })
457
561
  //}
@@ -636,5 +740,6 @@ class RefVal extends FeatureVal {
636
740
 
637
741
 
638
742
  export {
743
+ pendingMarkWrapper,
639
744
  RefVal,
640
745
  }