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,437 @@
1
+ /* Copyright (c) 2025 Richard Rodger, MIT License */
2
+
3
+ // THE PROVENANCE RECORDER (G7 phase 3,
4
+ // docs/capability-review/g7-machine-access.md): what CONTRIBUTED to
5
+ // the value at a path, in order, with the site each contribution was
6
+ // written at. `why` is the positive twin of G2's error report — errors
7
+ // explain what failed to unify, this explains what did.
8
+ //
9
+ // The recorder rides the CONTEXT and is off by default: `unite` pays
10
+ // one property load on the normal path, and an instrumented run pays
11
+ // site materialisation knowingly. It records at `unite` and nowhere
12
+ // else, because that is the one place every meet passes through — the
13
+ // same reason G3's deprecation rider lives there. A meet is where
14
+ // information currently vanishes, so a meet is what to record.
15
+ //
16
+ // Contributions are the operands that were NOT produced by an earlier
17
+ // meet at the same path: a value that a previous meet made is an
18
+ // intermediate, not a source. Deduplicated by (path, val id) — ids are
19
+ // unique per run — so fixpoint revisits across up to `maxcc` passes do
20
+ // not multiply the record.
21
+ //
22
+ // Roles come from the operand itself, with no further instrumentation:
23
+ // a reference is still a RefVal when it meets its peer (its canon is
24
+ // the path it names), a preference is still a PrefVal (its canon is
25
+ // the `*` form), and a spread-applied template is marked once where
26
+ // the spread is applied. Precedence is spread > ref > pref > literal:
27
+ // a preference INSIDE a spread template is a spread contribution,
28
+ // which is what the author needs to be told.
29
+
30
+
31
+
32
+ export type WhyRole = 'literal' | 'spread' | 'ref' | 'pref'
33
+
34
+ // The G2 site object, minus its data/schema role: a contribution's
35
+ // role is its own (above), and a `why` run has one document.
36
+ export type WhySite = {
37
+ col: number
38
+ file: string
39
+ // The extent in UTF-16 code units, or -1 when unknown. The same
40
+ // field, and the same meaning, as VetSite.len (ts/src/vet.ts).
41
+ len: number
42
+ row: number
43
+ }
44
+
45
+ export type WhyConjunct = {
46
+ canon: string
47
+ role: WhyRole
48
+ site: WhySite
49
+ // The SOURCE TEXT this contribution was written as.
50
+ //
51
+ // `canon` is the value; `src` is the spelling. They are not the same
52
+ // thing, and the difference is the whole reason this record exists:
53
+ // `port: 0x1F` contributes canon `31` from source `0x1F`, so a reader
54
+ // told only the canon cannot find, verify or replace what was
55
+ // actually written. Empty when the contribution occupies no source —
56
+ // a value unification minted rather than a document wrote.
57
+ src: string
58
+ // The PREFERENCE RANK, 0-based, when the contribution is one: `*x`
59
+ // is 0, `**x` is 1. Absent for anything else. The engine's own
60
+ // number (PrefVal.rank), so a reader arbitrating between ranked
61
+ // contributions -- the meet ladder -- need not count stars in a
62
+ // canon string.
63
+ rank?: number
64
+ }
65
+
66
+ export type WhyRecord = {
67
+ conjuncts: WhyConjunct[]
68
+ path: string
69
+ value: string
70
+ }
71
+
72
+
73
+ // Set on a spread template's per-key clone, at the one place a spread
74
+ // is applied (MapVal/ListVal.unify). Nothing else reads it.
75
+ export const FROM_SPREAD = '_fromSpread'
76
+
77
+
78
+ // THE AUTHORED MARK, and it is a property of the VALUE rather than a
79
+ // set of ids held beside it (the review's finding E). A contribution
80
+ // is a value the author wrote, and the recorder used to decide that by
81
+ // looking the operand's id up in a set stamped over the parsed tree --
82
+ // which is true of the parsed tree and of nothing derived from it. So
83
+ // every value that reached a path through a CLONE was dark: a default
84
+ // flowing into a `pack()`-generated child, a shape carried by a `$ref`,
85
+ // a value a spread template stamped. `why` answered "(no contributions:
86
+ // nothing met at this path)" over a value it had just printed, with
87
+ // exit 0 -- a false statement, and the one an audit surface may not
88
+ // make (use-cases/BUGS.md §23, §24).
89
+ //
90
+ // A clone of a written value IS that written value, re-instantiated
91
+ // somewhere else: it carries the author's site, so it can be pointed
92
+ // at. Val.clone therefore carries this mark, exactly as it carries the
93
+ // site -- provenance is part of the clone contract, not a recorder
94
+ // bolted beside it. Values the engine MINTS (a kind lifted while a
95
+ // disjunct trials its members, a fold's intermediate) are constructed
96
+ // rather than cloned and stay unmarked, which is what keeps the record
97
+ // to what the author can edit.
98
+ //
99
+ // The mark is only ever SET by an instrumented run (`why` calls
100
+ // writtenFrom; nothing else does), so an ordinary evaluation pays one
101
+ // undefined property read per clone.
102
+ export const WRITTEN = '_written'
103
+
104
+
105
+ // THE CONTAINER A VALUE IS PART OF, when the two stand at the SAME
106
+ // path: a junction's members, a preference's inner value, a function's
107
+ // arguments, an operator's operands. `*1|integer` is one thing the
108
+ // author wrote, and `*1` is not a second thing beside it.
109
+ //
110
+ // The recorder already knew that where the container itself met
111
+ // something -- contributing it marked its members `inside` and the
112
+ // filter dropped them. But the container only appears as an operand
113
+ // when the fixpoint happens to meet it whole; where it resolves
114
+ // member-wise instead (a later sibling of a spread, a default reaching
115
+ // a generated child) the members arrived alone and the record split
116
+ // one written value into two contributions at two columns. Which
117
+ // happened was decided by evaluation order, so `why` answered
118
+ // differently for identical statements (use-cases/BUGS.md §22).
119
+ //
120
+ // So the relation is recorded as a fact about the DOCUMENT, at
121
+ // stamping time, and an operand is reported as the outermost written
122
+ // value it is part of. NOT set for a bag's children (they stand at
123
+ // their own, deeper paths) nor for a conjunct's terms (a conjunct is
124
+ // the statement that several things must all hold, and each term is
125
+ // one of them -- which is exactly what the author needs shown).
126
+ export const INNER_OF = '_innerOf'
127
+
128
+
129
+ // Mark a spread clone and everything inside it, so a contribution
130
+ // several levels down a template is still known to have come from the
131
+ // template. ONLY on an instrumented run: the walk is O(template) per
132
+ // key per pass, which is real money on a large model and buys nothing
133
+ // when no one is recording.
134
+ //
135
+ // THE GUARD IS A CYCLE GUARD, NOT A "DONE" FLAG, and that distinction
136
+ // is the whole of the review's finding E for sibling position. A
137
+ // template is applied once per destination, and the fixpoint advances
138
+ // values IN PLACE between those applications (AGENTS.md, the mutation
139
+ // caveat): by the time the second key is spread, the template's
140
+ // `replicas` child is no longer the disjunction the first key saw but
141
+ // the value that meet produced. Skipping the walk because the
142
+ // CONTAINER was already marked left every one of those replacements
143
+ // unmarked, so `why` at the first sibling reported the written
144
+ // `*1|integer` as one contribution and at the second reported `*1` and
145
+ // `integer` as two -- identical statements, different answers, decided
146
+ // by which key the fixpoint reached first (use-cases/BUGS.md §22).
147
+ // Marking is idempotent, so re-walking costs a pass and changes
148
+ // nothing where nothing moved.
149
+ export function markSpread(v: any, seen?: Set<any>): void {
150
+ const marked = seen ?? new Set<any>()
151
+ if (null == v || true !== v.isVal || marked.has(v)) {
152
+ return
153
+ }
154
+ marked.add(v)
155
+ v[FROM_SPREAD] = true
156
+ if (true === v.isMap && null != v.peg) {
157
+ for (const k of Object.keys(v.peg)) {
158
+ markSpread(v.peg[k], marked)
159
+ }
160
+ }
161
+ else if (true === v.isList && null != v.peg) {
162
+ for (const k of Object.keys(v.peg)) {
163
+ markSpread(v.peg[k], marked)
164
+ }
165
+ }
166
+ else if (Array.isArray(v.peg)) {
167
+ // A junction, a func's arguments, an op's operands: every one of
168
+ // them can hold the value that reaches the destination.
169
+ for (const m of v.peg) {
170
+ markSpread(m, marked)
171
+ }
172
+ }
173
+ else if (true === v.isPref) {
174
+ markSpread(v.peg, marked)
175
+ }
176
+ }
177
+
178
+
179
+ // The children that stand at the SAME path as v. See INNER_OF.
180
+ function samePathKids(v: any): any[] {
181
+ if (true === v.isMap || true === v.isList || true === v.isConjunct) {
182
+ return []
183
+ }
184
+ if (true === v.isPref) {
185
+ return null != v.peg && true === v.peg.isVal ? [v.peg] : []
186
+ }
187
+ return Array.isArray(v.peg)
188
+ ? v.peg.filter((k: any) => null != k && true === k.isVal) : []
189
+ }
190
+
191
+
192
+ // Order contributions the way the document reads: by file, then row,
193
+ // then column, with the canon as the last tiebreak so the order is
194
+ // total even for two values written at the same position (which a
195
+ // merged duplicate key can produce).
196
+ function cmpSite(a: Contribution, b: Contribution): number {
197
+ return a.site.file.localeCompare(b.site.file) ||
198
+ a.site.row - b.site.row ||
199
+ a.site.col - b.site.col ||
200
+ a.canon.localeCompare(b.canon)
201
+ }
202
+
203
+
204
+ function roleOf(v: any): WhyRole {
205
+ if (true === v[FROM_SPREAD]) {
206
+ return 'spread'
207
+ }
208
+ if (true === v.isRef) {
209
+ return 'ref'
210
+ }
211
+ if (true === v.isPref) {
212
+ return 'pref'
213
+ }
214
+ return 'literal'
215
+ }
216
+
217
+
218
+ type Contribution = WhyConjunct & {
219
+ id: number
220
+ }
221
+
222
+ type PathRecord = {
223
+ conjuncts: Contribution[]
224
+ // Ids of values PRODUCED by a meet at this path: an operand among
225
+ // them is an intermediate result, not a source contribution.
226
+ made: Set<number>
227
+ seen: Set<number>
228
+ }
229
+
230
+
231
+ export class Provenance {
232
+ paths: Map<string, PathRecord> = new Map()
233
+
234
+ // id -> the written container it names, for INNER_OF. The mark on a
235
+ // value is the container's ID rather than the container itself: a
236
+ // Val holding another Val as an own property is a reference cycle
237
+ // through the tree, and enough of the engine walks a value's own
238
+ // properties that one is not safe to introduce.
239
+ containers: Map<number, any> = new Map()
240
+
241
+ // Stamp the parsed tree with the AUTHORED mark: everything the
242
+ // author wrote, before unification starts. A value minted during
243
+ // unification — a kind lifted from a leaf while a disjunct trials
244
+ // its members, a fold's intermediate — is the engine's own work, not
245
+ // a contribution the author can be pointed at. A CLONE of a marked
246
+ // value keeps the mark (see WRITTEN above), because it is the same
247
+ // written value somewhere else. Called once, before unify, by `why`.
248
+ writtenFrom(v: any): void {
249
+ if (null == v || true !== v.isVal || true === v[WRITTEN]) {
250
+ return
251
+ }
252
+ v[WRITTEN] = true
253
+ const kids: any[] =
254
+ (true === v.isMap || true === v.isList) && null != v.peg
255
+ ? Object.keys(v.peg).map((k) => v.peg[k])
256
+ : Array.isArray(v.peg) ? v.peg
257
+ : null != v.peg && true === v.peg.isVal ? [v.peg]
258
+ : []
259
+ for (const k of kids) {
260
+ this.writtenFrom(k)
261
+ }
262
+ // OUTERMOST WINS: the walk is top-down, so a value already pointed
263
+ // at a container is inside that one and this one, and the answer
264
+ // the author wants is the whole written statement.
265
+ for (const k of samePathKids(v)) {
266
+ if (null == k[INNER_OF]) {
267
+ k[INNER_OF] = v.id
268
+ this.containers.set(v.id, v)
269
+ }
270
+ }
271
+ if (null != v.spread?.cj) {
272
+ this.writtenFrom(v.spread.cj)
273
+ }
274
+ }
275
+
276
+ // One meet. Both operands are candidate contributions; the result is
277
+ // remembered so a later meet does not mistake it for a source.
278
+ record(path: string[], a: any, b: any, out: any): void {
279
+ const key = path.join('.')
280
+ let rec = this.paths.get(key)
281
+ if (null == rec) {
282
+ rec = {
283
+ conjuncts: [], made: new Set(), seen: new Set(),
284
+ }
285
+ this.paths.set(key, rec)
286
+ }
287
+
288
+ this.contribute(rec, a)
289
+ this.contribute(rec, b)
290
+
291
+ if (null != out && true === out.isVal && out !== a && out !== b) {
292
+ rec.made.add(out.id)
293
+ }
294
+ }
295
+
296
+ private contribute(rec: PathRecord, v: any): void {
297
+ // TOP is the unit element and a nil is a failure, neither of which
298
+ // is information the author wrote. A value an earlier meet MADE is
299
+ // an intermediate; the source that made it is already recorded.
300
+ if (null == v || true !== v.isVal || true === v.isTop || true === v.isNil ||
301
+ rec.made.has(v.id) || rec.seen.has(v.id)) {
302
+ return
303
+ }
304
+
305
+ // PART OF a written value is not a value beside it: report the
306
+ // whole statement the author wrote, whichever piece of it the
307
+ // fixpoint happened to meet here. See INNER_OF.
308
+ let outer: any = v
309
+ for (let up = this.containers.get(outer[INNER_OF]);
310
+ null != up; up = this.containers.get(outer[INNER_OF])) {
311
+ outer = up
312
+ }
313
+ if (outer !== v) {
314
+ this.contribute(rec, outer)
315
+ return
316
+ }
317
+ // Not the author's: see WRITTEN.
318
+ if (true !== v[WRITTEN] && true !== v[FROM_SPREAD]) {
319
+ return
320
+ }
321
+ // A CONJUNCT is not one contribution, it is the statement that
322
+ // several must all hold — duplicate keys merged at parse, an
323
+ // explicit `a & b`. Its own site is nowhere (the merge has no
324
+ // source position), while its terms each have one, which is what
325
+ // the author needs to be shown.
326
+ if (true === v.isConjunct && Array.isArray(v.peg)) {
327
+ rec.seen.add(v.id)
328
+ for (const term of v.peg) {
329
+ this.contribute(rec, term)
330
+ }
331
+ return
332
+ }
333
+ rec.seen.add(v.id)
334
+ rec.conjuncts.push({
335
+ canon: v.canon,
336
+ id: v.id,
337
+ role: roleOf(v),
338
+ // COALESCED, unlike vet's siteOf: a `why` run reads whatever
339
+ // source it was handed, and an inline document (a spec row, a
340
+ // piped stdin) has no file name to stamp. The Go port answers
341
+ // the empty string for the same value, so the two agree.
342
+ site: {
343
+ col: v.site.col, file: v.site.url ?? '', len: v.site.len,
344
+ row: v.site.row,
345
+ },
346
+ src: v.site.src,
347
+ ...(true === v.isPref ? { rank: v.rank } : {}),
348
+ })
349
+ }
350
+
351
+ // THE VALUE THAT STANDS at a path is a contribution when nothing met
352
+ // there and the author wrote it. A meet is where information
353
+ // vanishes, so a meet is what the recorder watches -- but a
354
+ // generator PLACES a value without meeting anything, and `why` then
355
+ // answered "(no contributions: nothing met at this path)" over a
356
+ // value it had just printed. That is literally true and practically
357
+ // false: the author is asking where the value came from, and it came
358
+ // from somewhere they can be shown (use-cases/BUGS.md §23).
359
+ //
360
+ // Only when the record is otherwise EMPTY. Where something did meet,
361
+ // the standing value is that meet's result -- an intermediate, and
362
+ // the recorder's oldest rule is that a result is not a source.
363
+ stands(path: string[], v: any): void {
364
+ const key = path.join('.')
365
+ const rec = this.paths.get(key)
366
+ if (null != rec && 0 < rec.conjuncts.length) {
367
+ return
368
+ }
369
+ this.record(path, v, undefined, undefined)
370
+ }
371
+
372
+ // The record at one path. Empty when nothing met there and nothing
373
+ // the author wrote stands there either — which is a true and useful
374
+ // answer rather than an error.
375
+ //
376
+ // ONLY WHOLE WRITTEN VALUES are contributions. A Val's own unify
377
+ // re-enters `unite` at the same path — a disjunct trials each member
378
+ // there, a constraint meets its atoms there — and those members are
379
+ // PARTS OF one written value, not further values beside it. That is
380
+ // settled BEFORE a member is ever pushed, by the INNER_OF fact
381
+ // stamped over the document (see `contribute`), which is why no
382
+ // filter runs here: a per-path "inside" set used to do it, and it
383
+ // could only work where the container itself happened to meet
384
+ // something at the same path — the order-dependence finding E
385
+ // records.
386
+ at(path: string[]): WhyConjunct[] {
387
+ const rec = this.paths.get(path.join('.'))
388
+ if (null == rec) {
389
+ return []
390
+ }
391
+ // SOURCE ORDER, not meet order: the two are the same in simple
392
+ // cases and diverge with the fixpoint's fold order, which is an
393
+ // engine detail and a parity risk. Sites are parse data, identical
394
+ // in both ports, so ordering by them makes the record read as the
395
+ // document reads and pins it across implementations.
396
+ // ONE WRITTEN TOKEN IS ONE CONTRIBUTION, and the SITE is what
397
+ // identifies it -- not the val id, and not the canon.
398
+ //
399
+ // Not the id, because provenance travels through clones now: a
400
+ // written value and a clone of it are the same statement in the
401
+ // same place, and a path that met both would list it twice.
402
+ //
403
+ // Not the canon, because the same written value reaches a path at
404
+ // different stages of narrowing -- `3|(1|2)` as the author wrote
405
+ // it and `3|1|2` after a fold -- and both name one token.
406
+ //
407
+ // Not the role either: the role says how the value REACHED this
408
+ // path, not which value it is, and one written value can reach a
409
+ // path both ways (a template applied to a key whose value is also
410
+ // written there). Keeping the literal would throw away the more
411
+ // informative half, so the roles have a precedence.
412
+ //
413
+ // ONLY WHERE THE SITE IS REAL. An unsited contribution (row -1)
414
+ // cannot be told apart from another unsited one, so those are kept
415
+ // as they come rather than collapsed into whichever arrived first.
416
+ const shown = new Map<string, Contribution>()
417
+ const order = ['spread', 'ref', 'pref', 'literal']
418
+ const out: Contribution[] = []
419
+ for (const c of rec.conjuncts.slice().sort(cmpSite)) {
420
+ if (c.site.row < 0) {
421
+ out.push(c)
422
+ continue
423
+ }
424
+ const key = [c.src, c.site.file,
425
+ c.site.row, c.site.col, c.site.len].join('\u0000')
426
+ const had = shown.get(key)
427
+ if (null == had) {
428
+ shown.set(key, c)
429
+ out.push(c)
430
+ }
431
+ else if (order.indexOf(c.role) < order.indexOf(had.role)) {
432
+ had.role = c.role
433
+ }
434
+ }
435
+ return out.map(({ id, ...rest }) => rest)
436
+ }
437
+ }