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.
- package/README.md +88 -0
- package/bin/aontu-mcp.js +4 -0
- package/dist/agentsmd.d.ts +16 -0
- package/dist/agentsmd.js +107 -0
- package/dist/agentsmd.js.map +1 -0
- package/dist/aontu.d.ts +14 -3
- package/dist/aontu.js +145 -4
- package/dist/aontu.js.map +1 -1
- package/dist/cli.d.ts +44 -1
- package/dist/cli.js +2401 -44
- package/dist/cli.js.map +1 -1
- package/dist/ctx.d.ts +16 -0
- package/dist/ctx.js +44 -0
- package/dist/ctx.js.map +1 -1
- package/dist/diff.d.ts +22 -0
- package/dist/diff.js +141 -0
- package/dist/diff.js.map +1 -0
- package/dist/err.d.ts +3 -1
- package/dist/err.js +48 -8
- package/dist/err.js.map +1 -1
- package/dist/graph.d.ts +16 -0
- package/dist/graph.js +73 -0
- package/dist/graph.js.map +1 -0
- package/dist/hcanon.d.ts +3 -0
- package/dist/hcanon.js +146 -0
- package/dist/hcanon.js.map +1 -0
- package/dist/hints.js +223 -5
- package/dist/hints.js.map +1 -1
- package/dist/jsonschema.d.ts +20 -0
- package/dist/jsonschema.js +391 -0
- package/dist/jsonschema.js.map +1 -0
- package/dist/lang.js +698 -35
- package/dist/lang.js.map +1 -1
- package/dist/lsp.d.ts +9 -2
- package/dist/lsp.js +262 -46
- package/dist/lsp.js.map +1 -1
- package/dist/mcp-server.d.ts +20 -0
- package/dist/mcp-server.js +147 -0
- package/dist/mcp-server.js.map +1 -0
- package/dist/mcp.d.ts +42 -0
- package/dist/mcp.js +814 -0
- package/dist/mcp.js.map +1 -0
- package/dist/mod-tool.d.ts +58 -0
- package/dist/mod-tool.js +498 -0
- package/dist/mod-tool.js.map +1 -0
- package/dist/mod.d.ts +31 -0
- package/dist/mod.js +250 -0
- package/dist/mod.js.map +1 -0
- package/dist/patch.d.ts +44 -0
- package/dist/patch.js +506 -0
- package/dist/patch.js.map +1 -0
- package/dist/provenance.d.ts +40 -0
- package/dist/provenance.js +335 -0
- package/dist/provenance.js.map +1 -0
- package/dist/query.d.ts +27 -0
- package/dist/query.js +294 -0
- package/dist/query.js.map +1 -0
- package/dist/reach.d.ts +14 -0
- package/dist/reach.js +140 -0
- package/dist/reach.js.map +1 -0
- package/dist/relation.d.ts +19 -0
- package/dist/relation.js +305 -0
- package/dist/relation.js.map +1 -0
- package/dist/report-sarif.d.ts +14 -0
- package/dist/report-sarif.js +102 -0
- package/dist/report-sarif.js.map +1 -0
- package/dist/site.d.ts +4 -0
- package/dist/site.js +31 -0
- package/dist/site.js.map +1 -1
- package/dist/std.d.ts +1 -0
- package/dist/std.js +73 -0
- package/dist/std.js.map +1 -0
- package/dist/subsume.d.ts +39 -0
- package/dist/subsume.js +526 -0
- package/dist/subsume.js.map +1 -0
- package/dist/trim.d.ts +19 -0
- package/dist/trim.js +155 -0
- package/dist/trim.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/type.d.ts +17 -1
- package/dist/type.js.map +1 -1
- package/dist/unify.d.ts +3 -1
- package/dist/unify.js +287 -18
- package/dist/unify.js.map +1 -1
- package/dist/utility.d.ts +9 -1
- package/dist/utility.js +122 -1
- package/dist/utility.js.map +1 -1
- package/dist/val/AggFuncVal.d.ts +33 -0
- package/dist/val/AggFuncVal.js +202 -0
- package/dist/val/AggFuncVal.js.map +1 -0
- package/dist/val/ArithFuncVal.d.ts +31 -0
- package/dist/val/ArithFuncVal.js +62 -0
- package/dist/val/ArithFuncVal.js.map +1 -0
- package/dist/val/BagVal.d.ts +5 -0
- package/dist/val/BagVal.js +96 -5
- package/dist/val/BagVal.js.map +1 -1
- package/dist/val/CloseFuncVal.js +9 -1
- package/dist/val/CloseFuncVal.js.map +1 -1
- package/dist/val/ConjunctVal.d.ts +1 -1
- package/dist/val/ConjunctVal.js +19 -0
- package/dist/val/ConjunctVal.js.map +1 -1
- package/dist/val/ConstraintVal.d.ts +48 -1
- package/dist/val/ConstraintVal.js +1501 -110
- package/dist/val/ConstraintVal.js.map +1 -1
- package/dist/val/CopyFuncVal.d.ts +1 -2
- package/dist/val/CopyFuncVal.js +7 -0
- package/dist/val/CopyFuncVal.js.map +1 -1
- package/dist/val/Decimal.d.ts +1 -0
- package/dist/val/Decimal.js +13 -0
- package/dist/val/Decimal.js.map +1 -1
- package/dist/val/DeprecateFuncVal.d.ts +11 -0
- package/dist/val/DeprecateFuncVal.js +47 -0
- package/dist/val/DeprecateFuncVal.js.map +1 -0
- package/dist/val/DisjunctVal.js +130 -21
- package/dist/val/DisjunctVal.js.map +1 -1
- package/dist/val/EachFuncVal.d.ts +15 -0
- package/dist/val/EachFuncVal.js +75 -0
- package/dist/val/EachFuncVal.js.map +1 -0
- package/dist/val/ExpectVal.d.ts +1 -0
- package/dist/val/ExpectVal.js +41 -4
- package/dist/val/ExpectVal.js.map +1 -1
- package/dist/val/FeatureVal.js +1 -1
- package/dist/val/FeatureVal.js.map +1 -1
- package/dist/val/FilterFuncVal.d.ts +15 -0
- package/dist/val/FilterFuncVal.js +91 -0
- package/dist/val/FilterFuncVal.js.map +1 -0
- package/dist/val/FuncBaseVal.d.ts +6 -1
- package/dist/val/FuncBaseVal.js +178 -2
- package/dist/val/FuncBaseVal.js.map +1 -1
- package/dist/val/HideFuncVal.js.map +1 -1
- package/dist/val/IdFuncVal.d.ts +13 -0
- package/dist/val/IdFuncVal.js +54 -0
- package/dist/val/IdFuncVal.js.map +1 -0
- package/dist/val/JunctionVal.js +7 -1
- package/dist/val/JunctionVal.js.map +1 -1
- package/dist/val/KeyFuncVal.d.ts +1 -1
- package/dist/val/KeyFuncVal.js +38 -30
- package/dist/val/KeyFuncVal.js.map +1 -1
- package/dist/val/ListVal.js +117 -17
- package/dist/val/ListVal.js.map +1 -1
- package/dist/val/LowerFuncVal.js.map +1 -1
- package/dist/val/MapVal.js +102 -8
- package/dist/val/MapVal.js.map +1 -1
- package/dist/val/MatchFuncVal.d.ts +15 -0
- package/dist/val/MatchFuncVal.js +107 -0
- package/dist/val/MatchFuncVal.js.map +1 -0
- package/dist/val/MoveFuncVal.js.map +1 -1
- package/dist/val/NilVal.js +24 -0
- package/dist/val/NilVal.js.map +1 -1
- package/dist/val/OpBaseVal.d.ts +1 -1
- package/dist/val/OpBaseVal.js +24 -2
- package/dist/val/OpBaseVal.js.map +1 -1
- package/dist/val/OpenFuncVal.js +4 -1
- package/dist/val/OpenFuncVal.js.map +1 -1
- package/dist/val/PackFuncVal.d.ts +15 -0
- package/dist/val/PackFuncVal.js +108 -0
- package/dist/val/PackFuncVal.js.map +1 -0
- package/dist/val/PathFuncVal.js.map +1 -1
- package/dist/val/PlaceVal.d.ts +13 -0
- package/dist/val/PlaceVal.js +131 -0
- package/dist/val/PlaceVal.js.map +1 -0
- package/dist/val/PlusOpVal.js +11 -2
- package/dist/val/PlusOpVal.js.map +1 -1
- package/dist/val/PrefFuncVal.js.map +1 -1
- package/dist/val/PrefVal.d.ts +2 -2
- package/dist/val/PrefVal.js +78 -23
- package/dist/val/PrefVal.js.map +1 -1
- package/dist/val/RefVal.d.ts +1 -1
- package/dist/val/RefVal.js +158 -30
- package/dist/val/RefVal.js.map +1 -1
- package/dist/val/ReferFuncVal.d.ts +36 -0
- package/dist/val/ReferFuncVal.js +303 -0
- package/dist/val/ReferFuncVal.js.map +1 -0
- package/dist/val/ScalarKindVal.d.ts +1 -2
- package/dist/val/ScalarKindVal.js +0 -11
- package/dist/val/ScalarKindVal.js.map +1 -1
- package/dist/val/TopVal.js.map +1 -1
- package/dist/val/TypeFuncVal.js.map +1 -1
- package/dist/val/UpperFuncVal.js.map +1 -1
- package/dist/val/Val.d.ts +9 -2
- package/dist/val/Val.js +150 -4
- package/dist/val/Val.js.map +1 -1
- package/dist/val/VarVal.js.map +1 -1
- package/dist/val/arith.d.ts +6 -0
- package/dist/val/arith.js +170 -0
- package/dist/val/arith.js.map +1 -0
- package/dist/vet.d.ts +45 -0
- package/dist/vet.js +776 -0
- package/dist/vet.js.map +1 -0
- package/dist/walk.d.ts +2 -0
- package/dist/walk.js +91 -0
- package/dist/walk.js.map +1 -0
- package/grammar/aontu.gbnf +130 -0
- package/grammar/aontu.lark +113 -0
- package/package.json +30 -15
- package/skill/SKILL.md +37 -0
- package/skill/error-codes.md +62 -0
- package/skill/examples.md +99 -0
- package/skill/grammar-card.md +57 -0
- package/src/agentsmd.ts +135 -0
- package/src/aontu.ts +192 -4
- package/src/cli.ts +2858 -71
- package/src/ctx.ts +81 -0
- package/src/diff.ts +196 -0
- package/src/err.ts +52 -8
- package/src/graph.ts +135 -0
- package/src/hcanon.ts +169 -0
- package/src/hints.ts +271 -5
- package/src/jsonschema.ts +511 -0
- package/src/lang.ts +779 -37
- package/src/lsp.ts +281 -47
- package/src/mcp-server.ts +187 -0
- package/src/mcp.ts +993 -0
- package/src/mod-tool.ts +679 -0
- package/src/mod.ts +344 -0
- package/src/patch.ts +624 -0
- package/src/provenance.ts +430 -0
- package/src/query.ts +379 -0
- package/src/reach.ts +184 -0
- package/src/relation.ts +395 -0
- package/src/report-sarif.ts +137 -0
- package/src/site.ts +36 -1
- package/src/std.ts +73 -0
- package/src/subsume.ts +690 -0
- package/src/trim.ts +195 -0
- package/src/tsconfig.json +10 -4
- package/src/type.ts +51 -2
- package/src/unify.ts +311 -16
- package/src/utility.ts +139 -1
- package/src/val/AggFuncVal.ts +319 -0
- package/src/val/ArithFuncVal.ts +108 -0
- package/src/val/BagVal.ts +101 -4
- package/src/val/CloseFuncVal.ts +9 -1
- package/src/val/ConjunctVal.ts +20 -0
- package/src/val/ConstraintVal.ts +1699 -116
- package/src/val/CopyFuncVal.ts +7 -1
- package/src/val/Decimal.ts +15 -0
- package/src/val/DeprecateFuncVal.ts +84 -0
- package/src/val/DisjunctVal.ts +139 -28
- package/src/val/EachFuncVal.ts +133 -0
- package/src/val/ExpectVal.ts +43 -6
- package/src/val/FeatureVal.ts +1 -1
- package/src/val/FilterFuncVal.ts +154 -0
- package/src/val/FuncBaseVal.ts +200 -3
- package/src/val/HideFuncVal.ts +0 -2
- package/src/val/IdFuncVal.ts +91 -0
- package/src/val/JunctionVal.ts +7 -1
- package/src/val/KeyFuncVal.ts +39 -35
- package/src/val/ListVal.ts +125 -18
- package/src/val/LowerFuncVal.ts +0 -1
- package/src/val/MapVal.ts +110 -8
- package/src/val/MatchFuncVal.ts +176 -0
- package/src/val/MoveFuncVal.ts +0 -2
- package/src/val/NilVal.ts +25 -0
- package/src/val/OpBaseVal.ts +26 -3
- package/src/val/OpenFuncVal.ts +4 -2
- package/src/val/PackFuncVal.ts +175 -0
- package/src/val/PathFuncVal.ts +0 -1
- package/src/val/PlaceVal.ts +193 -0
- package/src/val/PlusOpVal.ts +11 -2
- package/src/val/PrefFuncVal.ts +0 -1
- package/src/val/PrefVal.ts +79 -36
- package/src/val/RefVal.ts +163 -30
- package/src/val/ReferFuncVal.ts +387 -0
- package/src/val/ScalarKindVal.ts +0 -13
- package/src/val/TopVal.ts +0 -1
- package/src/val/TypeFuncVal.ts +0 -2
- package/src/val/UpperFuncVal.ts +0 -1
- package/src/val/Val.ts +213 -3
- package/src/val/VarVal.ts +0 -1
- package/src/val/arith.ts +316 -0
- package/src/vet.ts +992 -0
- 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
|
+
}
|