aontu 0.52.0 → 0.53.0

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