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