aontu 0.52.1 → 0.54.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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 +15 -3
- package/dist/aontu.js +126 -5
- package/dist/aontu.js.map +1 -1
- package/dist/cli.d.ts +45 -1
- package/dist/cli.js +2845 -69
- package/dist/cli.js.map +1 -1
- package/dist/ctx.d.ts +18 -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 +45 -8
- package/dist/err.js.map +1 -1
- package/dist/graph.d.ts +13 -0
- package/dist/graph.js +110 -0
- package/dist/graph.js.map +1 -0
- package/dist/hcanon.d.ts +3 -0
- package/dist/hcanon.js +145 -0
- package/dist/hcanon.js.map +1 -0
- package/dist/hints.js +235 -8
- package/dist/hints.js.map +1 -1
- package/dist/jsonschema.d.ts +20 -0
- package/dist/jsonschema.js +425 -0
- package/dist/jsonschema.js.map +1 -0
- package/dist/lang.js +955 -110
- package/dist/lang.js.map +1 -1
- package/dist/lsp.d.ts +9 -2
- package/dist/lsp.js +343 -48
- 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 +941 -0
- package/dist/mcp.js.map +1 -0
- package/dist/mod-tool.d.ts +58 -0
- package/dist/mod-tool.js +532 -0
- package/dist/mod-tool.js.map +1 -0
- package/dist/mod.d.ts +35 -0
- package/dist/mod.js +345 -0
- package/dist/mod.js.map +1 -0
- package/dist/patch.d.ts +49 -0
- package/dist/patch.js +506 -0
- package/dist/patch.js.map +1 -0
- package/dist/provenance.d.ts +41 -0
- package/dist/provenance.js +336 -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 +15 -0
- package/dist/reach.js +168 -0
- package/dist/reach.js.map +1 -0
- package/dist/relation.d.ts +23 -0
- package/dist/relation.js +230 -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/sig.d.ts +25 -0
- package/dist/sig.js +277 -0
- package/dist/sig.js.map +1 -0
- package/dist/sigdecl.d.ts +2 -0
- package/dist/sigdecl.js +11 -0
- package/dist/sigdecl.js.map +1 -0
- package/dist/siggate.d.ts +4 -0
- package/dist/siggate.js +90 -0
- package/dist/siggate.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 +129 -0
- package/dist/std.js.map +1 -0
- package/dist/subsume.d.ts +39 -0
- package/dist/subsume.js +573 -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 +234 -38
- package/dist/unify.js.map +1 -1
- package/dist/utility.d.ts +8 -1
- package/dist/utility.js +68 -1
- package/dist/utility.js.map +1 -1
- package/dist/val/AggFuncVal.d.ts +44 -0
- package/dist/val/AggFuncVal.js +364 -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 +7 -0
- package/dist/val/BagVal.js +150 -11
- 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 +523 -45
- package/dist/val/ConstraintVal.js.map +1 -1
- package/dist/val/ContainerKindVal.d.ts +35 -0
- package/dist/val/ContainerKindVal.js +99 -0
- package/dist/val/ContainerKindVal.js.map +1 -0
- package/dist/val/CopyFuncVal.d.ts +1 -2
- 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.d.ts +1 -2
- package/dist/val/DisjunctVal.js +269 -53
- 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 +62 -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 +7 -1
- package/dist/val/FuncBaseVal.js +183 -2
- package/dist/val/FuncBaseVal.js.map +1 -1
- package/dist/val/GraphAtomVal.d.ts +39 -0
- package/dist/val/GraphAtomVal.js +184 -0
- package/dist/val/GraphAtomVal.js.map +1 -0
- package/dist/val/HideFuncVal.js.map +1 -1
- package/dist/val/JunctionVal.js +24 -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 +94 -14
- package/dist/val/ListVal.js.map +1 -1
- package/dist/val/LowerFuncVal.js.map +1 -1
- package/dist/val/MapVal.d.ts +1 -0
- package/dist/val/MapVal.js +146 -14
- 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.d.ts +2 -2
- package/dist/val/PathFuncVal.js +75 -16
- package/dist/val/PathFuncVal.js.map +1 -1
- package/dist/val/PathVal.d.ts +25 -0
- package/dist/val/PathVal.js +150 -0
- package/dist/val/PathVal.js.map +1 -0
- 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.d.ts +2 -1
- package/dist/val/PlusOpVal.js +61 -37
- package/dist/val/PlusOpVal.js.map +1 -1
- package/dist/val/PrefFuncVal.js.map +1 -1
- package/dist/val/PrefVal.d.ts +4 -2
- package/dist/val/PrefVal.js +223 -41
- package/dist/val/PrefVal.js.map +1 -1
- package/dist/val/RecurseVal.d.ts +19 -0
- package/dist/val/RecurseVal.js +217 -0
- package/dist/val/RecurseVal.js.map +1 -0
- package/dist/val/RefVal.d.ts +3 -2
- package/dist/val/RefVal.js +181 -66
- package/dist/val/RefVal.js.map +1 -1
- package/dist/val/ReferFuncVal.d.ts +59 -0
- package/dist/val/ReferFuncVal.js +604 -0
- package/dist/val/ReferFuncVal.js.map +1 -0
- package/dist/val/ScalarKindVal.d.ts +4 -3
- package/dist/val/ScalarKindVal.js +12 -12
- package/dist/val/ScalarKindVal.js.map +1 -1
- package/dist/val/SuperFuncVal.d.ts +4 -2
- package/dist/val/SuperFuncVal.js +118 -14
- package/dist/val/SuperFuncVal.js.map +1 -1
- package/dist/val/TopVal.d.ts +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 +7 -1
- package/dist/val/Val.js +170 -2
- 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 +173 -0
- package/dist/val/arith.js.map +1 -0
- package/dist/vet.d.ts +45 -0
- package/dist/vet.js +802 -0
- package/dist/vet.js.map +1 -0
- package/dist/view.d.ts +85 -0
- package/dist/view.js +1948 -0
- package/dist/view.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 +138 -0
- package/grammar/aontu.lark +118 -0
- package/grammar/aontu.tmLanguage.json +184 -0
- package/package.json +30 -8
- package/skill/SKILL.md +37 -0
- package/skill/error-codes.md +62 -0
- package/skill/examples.md +99 -0
- package/skill/grammar-card.md +56 -0
- package/src/agentsmd.ts +135 -0
- package/src/aontu.ts +166 -5
- package/src/cli.ts +3324 -72
- package/src/ctx.ts +93 -1
- package/src/diff.ts +198 -0
- package/src/err.ts +50 -8
- package/src/graph.ts +185 -0
- package/src/hcanon.ts +167 -0
- package/src/hints.ts +291 -8
- package/src/jsonschema.ts +552 -0
- package/src/lang.ts +1049 -110
- package/src/lsp.ts +351 -49
- package/src/mcp-server.ts +187 -0
- package/src/mcp.ts +1130 -0
- package/src/mod-tool.ts +718 -0
- package/src/mod.ts +453 -0
- package/src/patch.ts +628 -0
- package/src/provenance.ts +437 -0
- package/src/query.ts +381 -0
- package/src/reach.ts +213 -0
- package/src/relation.ts +298 -0
- package/src/report-sarif.ts +137 -0
- package/src/sig.ts +345 -0
- package/src/sigdecl.ts +11 -0
- package/src/siggate.ts +144 -0
- package/src/site.ts +36 -1
- package/src/std.ts +131 -0
- package/src/subsume.ts +739 -0
- package/src/trim.ts +195 -0
- package/src/tsconfig.json +10 -4
- package/src/type.ts +51 -2
- package/src/unify.ts +253 -38
- package/src/utility.ts +79 -1
- package/src/val/AggFuncVal.ts +546 -0
- package/src/val/ArithFuncVal.ts +108 -0
- package/src/val/BagVal.ts +158 -12
- package/src/val/CloseFuncVal.ts +9 -1
- package/src/val/ConjunctVal.ts +20 -0
- package/src/val/ConstraintVal.ts +576 -48
- package/src/val/ContainerKindVal.ts +158 -0
- package/src/val/CopyFuncVal.ts +0 -1
- package/src/val/Decimal.ts +15 -0
- package/src/val/DeprecateFuncVal.ts +84 -0
- package/src/val/DisjunctVal.ts +282 -59
- package/src/val/EachFuncVal.ts +133 -0
- package/src/val/ExpectVal.ts +65 -8
- package/src/val/FeatureVal.ts +1 -1
- package/src/val/FilterFuncVal.ts +154 -0
- package/src/val/FuncBaseVal.ts +206 -2
- package/src/val/GraphAtomVal.ts +264 -0
- package/src/val/HideFuncVal.ts +0 -2
- package/src/val/JunctionVal.ts +24 -1
- package/src/val/KeyFuncVal.ts +39 -35
- package/src/val/ListVal.ts +101 -15
- package/src/val/LowerFuncVal.ts +0 -1
- package/src/val/MapVal.ts +153 -14
- 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 +107 -20
- package/src/val/PathVal.ts +221 -0
- package/src/val/PlaceVal.ts +193 -0
- package/src/val/PlusOpVal.ts +67 -39
- package/src/val/PrefFuncVal.ts +0 -1
- package/src/val/PrefVal.ts +251 -60
- package/src/val/RecurseVal.ts +285 -0
- package/src/val/RefVal.ts +183 -78
- package/src/val/ReferFuncVal.ts +732 -0
- package/src/val/ScalarKindVal.ts +12 -13
- package/src/val/SuperFuncVal.ts +137 -13
- package/src/val/TopVal.ts +1 -2
- package/src/val/TypeFuncVal.ts +0 -2
- package/src/val/UpperFuncVal.ts +0 -1
- package/src/val/Val.ts +216 -4
- package/src/val/VarVal.ts +0 -1
- package/src/val/arith.ts +319 -0
- package/src/vet.ts +1025 -0
- package/src/view.ts +2563 -0
- package/src/walk.ts +99 -0
package/src/ctx.ts
CHANGED
|
@@ -21,6 +21,11 @@ type AontuContextConfig = {
|
|
|
21
21
|
cc?: number
|
|
22
22
|
err?: any[] // Omit<NilVal[], "push">
|
|
23
23
|
explain?: any[] | boolean | null
|
|
24
|
+
|
|
25
|
+
// The provenance recorder (G7 phase 3), or absent for an
|
|
26
|
+
// uninstrumented run. Shared by reference down every descend, as the
|
|
27
|
+
// error list is, so one run has one record.
|
|
28
|
+
prov?: any
|
|
24
29
|
fs?: any
|
|
25
30
|
path?: string[]
|
|
26
31
|
root?: Val
|
|
@@ -41,6 +46,26 @@ class AontuContext {
|
|
|
41
46
|
path: string[] // Path to current Val.
|
|
42
47
|
vc: number // Val counter to create unique val ids.
|
|
43
48
|
cc: number = -1
|
|
49
|
+
|
|
50
|
+
// THE STAGING RULE (G8 phase 0,
|
|
51
|
+
// docs/capability-review/g8-generation.md). A value whose answer
|
|
52
|
+
// depends on WHERE IT IS -- `key()` today, the generation
|
|
53
|
+
// combinators next -- must not answer while anything is still
|
|
54
|
+
// moving it: resolved early it reports the position it was WRITTEN
|
|
55
|
+
// at rather than the one it ends up at. Such a value RESIDUATES
|
|
56
|
+
// while this is false, and fires exactly once on the pass where it
|
|
57
|
+
// is true.
|
|
58
|
+
//
|
|
59
|
+
// The pass loop (ts/src/unify.ts) sets it on the first pass whose
|
|
60
|
+
// input tree is IDENTICAL to the previous pass's: everything that
|
|
61
|
+
// was going to move has moved, and what is left is the staged
|
|
62
|
+
// values themselves, which is precisely the moment they may answer.
|
|
63
|
+
// It replaces a `ctx.cc < 3` pass count in KeyFuncVal -- a magic
|
|
64
|
+
// number, right for the documents it was tuned on and silently
|
|
65
|
+
// wrong for anything that took a fourth pass to place a value. The
|
|
66
|
+
// comment it replaces said as much: "this delay makes keys in
|
|
67
|
+
// spreads and refs work, but it is a hack - find a better way".
|
|
68
|
+
settle: boolean = false
|
|
44
69
|
vars: Record<string, Val> = {}
|
|
45
70
|
src?: string
|
|
46
71
|
fs?: FST
|
|
@@ -50,6 +75,25 @@ class AontuContext {
|
|
|
50
75
|
|
|
51
76
|
collect: boolean
|
|
52
77
|
|
|
78
|
+
// THE COMPLETENESS PROBE (the review's finding C). vet detects
|
|
79
|
+
// residue by GENERATING the anchored meet and keeping the
|
|
80
|
+
// incomplete-class failures. Generation honours the OUTPUT marks --
|
|
81
|
+
// `type()` and `hide()` say "do not emit this" -- so a `--at` anchor
|
|
82
|
+
// sitting under a mark generated nothing at all, reported nothing,
|
|
83
|
+
// and vetted VALID for data missing a required field, while the same
|
|
84
|
+
// anchor without the mark answered incomplete (use-cases/BUGS.md
|
|
85
|
+
// §14). A mark is a decision about OUTPUT; it is not a statement
|
|
86
|
+
// about what the data must satisfy, and `--at` names the truth to
|
|
87
|
+
// validate against explicitly. Under this flag the generation walk
|
|
88
|
+
// descends through marked values; nothing else changes, and no
|
|
89
|
+
// output is produced from a probe run -- only its findings are read.
|
|
90
|
+
probe: boolean = false
|
|
91
|
+
|
|
92
|
+
// The provenance recorder (G7 phase 3), or undefined for an
|
|
93
|
+
// uninstrumented run. Inherited by every descended and cloned
|
|
94
|
+
// context through the prototype chain, so one run has one record.
|
|
95
|
+
prov?: any
|
|
96
|
+
|
|
53
97
|
// errlist: Omit<NilVal[], "push"> // Nil error log of current unify.
|
|
54
98
|
err: any[]
|
|
55
99
|
explain: any[] | null
|
|
@@ -71,7 +115,7 @@ class AontuContext {
|
|
|
71
115
|
_pathTrie: Map<number, Map<string, { idx: number, path: string[] }>>
|
|
72
116
|
_pathidxNext: { n: number }
|
|
73
117
|
|
|
74
|
-
// Current `unite` recursion depth, checked against
|
|
118
|
+
// Current `unite` recursion depth, checked against the depth budget
|
|
75
119
|
// (ts/src/unify.ts). Held in a shared mutable box, like _pathidxNext,
|
|
76
120
|
// because clone() uses Object.create: the box is inherited by
|
|
77
121
|
// reference, so a nested clone's increments are visible to the frame
|
|
@@ -79,6 +123,40 @@ class AontuContext {
|
|
|
79
123
|
// directly on its Ctx pointer (go/unify.go, maxUniteDepth).
|
|
80
124
|
_depth: { n: number }
|
|
81
125
|
|
|
126
|
+
// The relation declarations this evaluation accumulates (RELATIONS
|
|
127
|
+
// P2): predicate -> what its graph atoms said. One Map per
|
|
128
|
+
// evaluation, created here and inherited by reference through
|
|
129
|
+
// clone()'s prototype chain, exactly as _depth's box is -- a clone
|
|
130
|
+
// made before the first registration must still share the registry.
|
|
131
|
+
_reldecls: Map<string, { acyclic?: boolean, inverses: Set<string> }>
|
|
132
|
+
|
|
133
|
+
// The tree a recursive residual's target resolves against when the
|
|
134
|
+
// meet's own root does not contain it (RECURSION.0.md). Normally
|
|
135
|
+
// undefined: a residual expands by walking ctx.root, and the root
|
|
136
|
+
// holds the definition. An ANCHORED vet run meets a subtree LIFTED
|
|
137
|
+
// out of the settled schema, so `$.spec.Step` names nothing in the
|
|
138
|
+
// meet's root -- the residual held its peer forever and the data
|
|
139
|
+
// under it vetted VALID unchecked. Vet sets this to the settled
|
|
140
|
+
// schema root for anchored runs; RecurseVal.body falls back to it
|
|
141
|
+
// only when the root walk finds nothing. Inherited by clone()
|
|
142
|
+
// through the prototype chain.
|
|
143
|
+
_fixroot: any
|
|
144
|
+
|
|
145
|
+
// The evaluation budgets (G5 trust profile, docs/trust.md): integer
|
|
146
|
+
// counts of engine events, never wall-clock. Always present, defaults
|
|
147
|
+
// from the shared spec-visible constants (test/spec/budget.tsv), so
|
|
148
|
+
// the hot-path reads in unify.ts are plain property loads. Inherited
|
|
149
|
+
// by clone() through the prototype chain. `revisits` is NOT profile
|
|
150
|
+
// surface (the Go port has no revisit counter to configure — see
|
|
151
|
+
// TrustBudget in type.ts); it is carried here so unify.ts reads one
|
|
152
|
+
// budget object, at its fixed spec constant.
|
|
153
|
+
budget: { passes: number, revisits: number, depth: number }
|
|
154
|
+
|
|
155
|
+
// The include manifest sink (G5, docs/trust.md): every include the
|
|
156
|
+
// resolver reads is recorded here as { path, capability }, and
|
|
157
|
+
// Aontu.parse() sorts and dedups it onto the result's `deps`.
|
|
158
|
+
manifest: { path: string, capability: string }[]
|
|
159
|
+
|
|
82
160
|
// Trial mode: set by DisjunctVal.unify while each member is tried
|
|
83
161
|
// against the peer. When true, makeNilErr returns the shared
|
|
84
162
|
// TRIAL_NIL sentinel instead of allocating a fresh NilVal, and
|
|
@@ -102,6 +180,7 @@ class AontuContext {
|
|
|
102
180
|
this.src = cfg.src
|
|
103
181
|
|
|
104
182
|
this.collect = cfg.collect ?? null != cfg.err
|
|
183
|
+
this.prov = cfg.prov
|
|
105
184
|
|
|
106
185
|
this.err = cfg.err ?? []
|
|
107
186
|
this.explain = Array.isArray(cfg.explain) ? cfg.explain : null
|
|
@@ -125,10 +204,23 @@ class AontuContext {
|
|
|
125
204
|
this._pathTrie = new Map()
|
|
126
205
|
this._pathidxNext = { n: 1 } // 0 reserved for the root path
|
|
127
206
|
this._depth = { n: 0 }
|
|
207
|
+
this._reldecls = new Map()
|
|
128
208
|
this._pathidx = 0
|
|
129
209
|
|
|
210
|
+
this.manifest = []
|
|
211
|
+
|
|
130
212
|
this.opts = DEFAULT_OPTS()
|
|
131
213
|
this.addopts(cfg.opts)
|
|
214
|
+
|
|
215
|
+
// Budget defaults are the shared spec-visible constants
|
|
216
|
+
// (test/spec/budget.tsv pins the boundaries in both ports); the
|
|
217
|
+
// trust profile may lower or raise them, deterministically.
|
|
218
|
+
const budget = (this.opts as any).trust?.budget ?? {}
|
|
219
|
+
this.budget = {
|
|
220
|
+
passes: budget.passes ?? 9,
|
|
221
|
+
revisits: 999,
|
|
222
|
+
depth: budget.depth ?? 1000,
|
|
223
|
+
}
|
|
132
224
|
}
|
|
133
225
|
|
|
134
226
|
|
package/src/diff.ts
ADDED
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
/* Copyright (c) 2025 Richard Rodger, MIT License */
|
|
2
|
+
|
|
3
|
+
// PATH-ADDRESSED DIFF (G7 phase 6,
|
|
4
|
+
// docs/capability-review/g7-machine-access.md): what changed, at which
|
|
5
|
+
// paths, between two documents — the dyff-style answer, which
|
|
6
|
+
// deterministic canon makes possible without phantom noise. Two
|
|
7
|
+
// documents that mean the same thing canon the same way, so a diff of
|
|
8
|
+
// canons reports semantic change and not reformatting.
|
|
9
|
+
//
|
|
10
|
+
// The text compared is the HASH FORM (G6's `hcanon`), not the plain
|
|
11
|
+
// canon, for the reason G6 gives: canon drops closedness and the
|
|
12
|
+
// type/hide marks, so a canon diff calls `close({a:1})` and `{a:1}`
|
|
13
|
+
// identical. A false "changed" costs a needless read; a false
|
|
14
|
+
// "unchanged" is a change nobody reviewed, which is the one direction
|
|
15
|
+
// that must not happen.
|
|
16
|
+
//
|
|
17
|
+
// WHETHER a change is BREAKING is a different question, and it belongs
|
|
18
|
+
// to G3: `subsume` and `breaking` answer it with the lattice's own
|
|
19
|
+
// rules. This verb answers "what moved", which is what a reviewer
|
|
20
|
+
// reads first and what an agent needs before it can ask the other
|
|
21
|
+
// question at all.
|
|
22
|
+
|
|
23
|
+
import { Aontu } from './aontu'
|
|
24
|
+
import type { TrustOptions } from './type'
|
|
25
|
+
import { anchorAt } from './vet'
|
|
26
|
+
import type { VetFinding } from './vet'
|
|
27
|
+
import { cmpCodePoint } from './keyorder'
|
|
28
|
+
import { hcanon } from './hcanon'
|
|
29
|
+
import { evalFailure } from './query'
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
export type DiffKind = 'added' | 'removed' | 'changed'
|
|
33
|
+
|
|
34
|
+
export type DiffChange = {
|
|
35
|
+
kind: DiffKind
|
|
36
|
+
// The canon on each side, absent where there is no value.
|
|
37
|
+
left?: string
|
|
38
|
+
path: string
|
|
39
|
+
right?: string
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export type DiffReport = {
|
|
43
|
+
changes: DiffChange[]
|
|
44
|
+
findings: VetFinding[]
|
|
45
|
+
ok: boolean
|
|
46
|
+
// True when nothing moved: the two documents mean the same thing.
|
|
47
|
+
same: boolean
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export type DiffOptions = {
|
|
51
|
+
leftPath?: string
|
|
52
|
+
rightPath?: string
|
|
53
|
+
// Compare at this path of both documents, rather than at the root.
|
|
54
|
+
at?: string
|
|
55
|
+
|
|
56
|
+
// The trust profile this run evaluates under (G5, docs/trust.md).
|
|
57
|
+
// The source arrives from a caller, so the caller must be able to
|
|
58
|
+
// say what it may reach: without this the include chain is the
|
|
59
|
+
// default one, and `@"../../etc/passwd.aon"` reads whatever the
|
|
60
|
+
// process can. (`@"x.js"` no longer executes -- ADR-012 refuses the
|
|
61
|
+
// extension -- but reading is enough.) A server passes
|
|
62
|
+
// `{include:'none'}`.
|
|
63
|
+
trust?: TrustOptions
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
function pathText(parts: string[]): string {
|
|
68
|
+
return '$' + (0 < parts.length ? '.' + parts.join('.') : '')
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
// Both sides of one node — never both absent: keys come from the
|
|
73
|
+
// union of the two bags, and list indices run to the longer side, so
|
|
74
|
+
// every walk has at least one value. Bags of the SAME kind recurse,
|
|
75
|
+
// which is what makes the report path-addressed rather than one line
|
|
76
|
+
// saying the whole document changed; everything else compares text.
|
|
77
|
+
function walk(
|
|
78
|
+
left: any, right: any, parts: string[], out: DiffChange[]): void {
|
|
79
|
+
if (null == left) {
|
|
80
|
+
out.push({ kind: 'added', path: pathText(parts), right: hcanon(right) })
|
|
81
|
+
return
|
|
82
|
+
}
|
|
83
|
+
if (null == right) {
|
|
84
|
+
out.push({ kind: 'removed', left: hcanon(left), path: pathText(parts) })
|
|
85
|
+
return
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const bothMaps = true === left.isMap && true === right.isMap
|
|
89
|
+
const bothLists = true === left.isList && true === right.isList
|
|
90
|
+
if (bothMaps || bothLists) {
|
|
91
|
+
// The bag's OWN attributes, at pseudo-keys under it: a recursing
|
|
92
|
+
// bag never compares its own text, so what the children do not
|
|
93
|
+
// carry has to be compared here. The spread is part of what a bag
|
|
94
|
+
// MEANS; so are closedness and the marks, which is exactly why the
|
|
95
|
+
// hash form spells them (G6).
|
|
96
|
+
const lc = null == left.spread.cj ? undefined : hcanon(left.spread.cj)
|
|
97
|
+
const rc = null == right.spread.cj ? undefined : hcanon(right.spread.cj)
|
|
98
|
+
if (lc !== rc) {
|
|
99
|
+
out.push({
|
|
100
|
+
kind: null == lc ? 'added' : null == rc ? 'removed' : 'changed',
|
|
101
|
+
...(null == lc ? {} : { left: lc }),
|
|
102
|
+
path: pathText(parts.concat('&')),
|
|
103
|
+
...(null == rc ? {} : { right: rc }),
|
|
104
|
+
})
|
|
105
|
+
}
|
|
106
|
+
flag(left.closed, right.closed, parts, 'closed', out)
|
|
107
|
+
flag(left.mark?.type, right.mark?.type, parts, 'type', out)
|
|
108
|
+
flag(left.mark?.hide, right.mark?.hide, parts, 'hide', out)
|
|
109
|
+
|
|
110
|
+
const keys = bothMaps
|
|
111
|
+
? [...new Set([
|
|
112
|
+
...Object.keys(left.peg), ...Object.keys(right.peg),
|
|
113
|
+
])].sort(cmpCodePoint)
|
|
114
|
+
: [...new Set([
|
|
115
|
+
...Object.keys(left.peg), ...Object.keys(right.peg),
|
|
116
|
+
])].sort((a, b) => Number(a) - Number(b))
|
|
117
|
+
for (const k of keys) {
|
|
118
|
+
walk(left.peg[k], right.peg[k], parts.concat(k), out)
|
|
119
|
+
}
|
|
120
|
+
return
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const lh = hcanon(left)
|
|
124
|
+
const rh = hcanon(right)
|
|
125
|
+
if (lh !== rh) {
|
|
126
|
+
out.push({ kind: 'changed', left: lh, path: pathText(parts), right: rh })
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
// One boolean attribute of a bag, as a pseudo-key: `$.a.&closed` says
|
|
132
|
+
// the map at `$.a` was closed (or opened) without saying anything
|
|
133
|
+
// about its keys.
|
|
134
|
+
function flag(
|
|
135
|
+
left: any, right: any, parts: string[], name: string,
|
|
136
|
+
out: DiffChange[]): void {
|
|
137
|
+
const l = true === left
|
|
138
|
+
const r = true === right
|
|
139
|
+
if (l !== r) {
|
|
140
|
+
out.push({
|
|
141
|
+
kind: 'changed',
|
|
142
|
+
left: String(l),
|
|
143
|
+
path: pathText(parts.concat('&' + name)),
|
|
144
|
+
right: String(r),
|
|
145
|
+
})
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
function evalSide(
|
|
151
|
+
aontu: Aontu, src: string, path: string | undefined, at: string | undefined,
|
|
152
|
+
): { node?: any, finding?: VetFinding } {
|
|
153
|
+
const ctx = aontu.ctx({ collect: true })
|
|
154
|
+
const parseOpts = null == path ? undefined : { path }
|
|
155
|
+
const root: any = aontu.unify(src, parseOpts, ctx)
|
|
156
|
+
if (0 < ctx.err.length) {
|
|
157
|
+
// The query surface's own fold: a document that does not stand up
|
|
158
|
+
// has no meaning to compare, and the engine's diagnosis is the
|
|
159
|
+
// report.
|
|
160
|
+
return { finding: evalFailure(ctx) }
|
|
161
|
+
}
|
|
162
|
+
const node: any = null == at ? root : anchorAt(root, at)
|
|
163
|
+
if (null == node) {
|
|
164
|
+
return {
|
|
165
|
+
finding: {
|
|
166
|
+
code: 'no_path',
|
|
167
|
+
class: 'reference',
|
|
168
|
+
severity: 'error',
|
|
169
|
+
path: at as string,
|
|
170
|
+
message: `The path ${at} names nothing in this document.`,
|
|
171
|
+
sites: [],
|
|
172
|
+
},
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
return { node }
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
// Diff two documents. Each is evaluated on its own — a document that
|
|
180
|
+
// does not stand up has no meaning to compare, and the report says so
|
|
181
|
+
// rather than diffing a wreck.
|
|
182
|
+
export function diff(
|
|
183
|
+
leftSrc: string, rightSrc: string, opts?: DiffOptions): DiffReport {
|
|
184
|
+
const options = opts ?? {}
|
|
185
|
+
const aontu = new Aontu(
|
|
186
|
+
null == options.trust ? undefined : { trust: options.trust })
|
|
187
|
+
|
|
188
|
+
const l = evalSide(aontu, leftSrc, options.leftPath, options.at)
|
|
189
|
+
const r = evalSide(aontu, rightSrc, options.rightPath, options.at)
|
|
190
|
+
const findings = [l.finding, r.finding].filter(Boolean) as VetFinding[]
|
|
191
|
+
if (0 < findings.length) {
|
|
192
|
+
return { changes: [], findings, ok: false, same: false }
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
const changes: DiffChange[] = []
|
|
196
|
+
walk(l.node, r.node, [], changes)
|
|
197
|
+
return { changes, findings: [], ok: true, same: 0 === changes.length }
|
|
198
|
+
}
|
package/src/err.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
/* Copyright (c) 2021-2025 Richard Rodger, MIT License */
|
|
2
2
|
|
|
3
3
|
|
|
4
|
+
import { sep } from 'node:path'
|
|
5
|
+
|
|
4
6
|
import { util } from '@tabnas/jsonic'
|
|
5
7
|
|
|
6
8
|
import { Val, ErrContext } from './type'
|
|
@@ -15,6 +17,39 @@ import { hints } from './hints'
|
|
|
15
17
|
const { errmsg, strinject } = util
|
|
16
18
|
|
|
17
19
|
|
|
20
|
+
// COLOUR IS A DECISION ABOUT THE DESTINATION, not about the message.
|
|
21
|
+
// Every error frame hardcoded the ANSI escapes, so a piped report and
|
|
22
|
+
// a `--jsonl` answer carried terminal control codes into whatever read
|
|
23
|
+
// them -- a log file, a CI annotation, an agent's parser (the review's
|
|
24
|
+
// finding F).
|
|
25
|
+
//
|
|
26
|
+
// NO_COLOR (no-color.org: set, to anything, means no colour) turns them
|
|
27
|
+
// off everywhere, library callers included. The CLI additionally turns
|
|
28
|
+
// them off when its stderr is not a terminal, through setColor: a
|
|
29
|
+
// library cannot see the destination, and a caller who has one is the
|
|
30
|
+
// only one who can say.
|
|
31
|
+
let COLOR: boolean | undefined
|
|
32
|
+
|
|
33
|
+
function setColor(on: boolean | undefined): void {
|
|
34
|
+
COLOR = on
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function colorActive(): boolean {
|
|
38
|
+
if (null != COLOR) {
|
|
39
|
+
return COLOR
|
|
40
|
+
}
|
|
41
|
+
// `globalThis` always exists; `process` need not (this library runs
|
|
42
|
+
// in a browser too), so the optional chain starts at the part that
|
|
43
|
+
// can actually be missing -- and stays a CHAIN rather than becoming
|
|
44
|
+
// an `if`, because the browser arm is unreachable from any test this
|
|
45
|
+
// suite can run and ADR-002 does not accept an arm nothing takes.
|
|
46
|
+
// Set-but-EMPTY is the documented exception and does not disable
|
|
47
|
+
// colour (no-color.org).
|
|
48
|
+
const no = (globalThis as any).process?.env?.NO_COLOR
|
|
49
|
+
return null == no || '' === no
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
|
|
18
53
|
function getHint(why: any, details?: Record<string, any>): string | undefined {
|
|
19
54
|
if (hints[why]) {
|
|
20
55
|
let txt = hints[why]
|
|
@@ -61,8 +96,8 @@ function descErr<NILS extends NilVal | NilVal[]>(
|
|
|
61
96
|
let v1: any = err.primary
|
|
62
97
|
let v2: any = err.secondary
|
|
63
98
|
|
|
64
|
-
let v1src = resolveSrc(v1, errctx
|
|
65
|
-
let v2src = resolveSrc(v2, errctx
|
|
99
|
+
let v1src = resolveSrc(v1, errctx)
|
|
100
|
+
let v2src = resolveSrc(v2, errctx)
|
|
66
101
|
|
|
67
102
|
// STRICT `!==` against the empty string. The loose `!=` here dropped
|
|
68
103
|
// the list index 0, because `'' != 0` is FALSE in JavaScript ('' and
|
|
@@ -83,7 +118,7 @@ function descErr<NILS extends NilVal | NilVal[]>(
|
|
|
83
118
|
|
|
84
119
|
err.msg = [
|
|
85
120
|
errmsg({
|
|
86
|
-
color: { active:
|
|
121
|
+
color: { active: colorActive() },
|
|
87
122
|
name: 'aontu',
|
|
88
123
|
code: err.why,
|
|
89
124
|
txts: {
|
|
@@ -99,7 +134,7 @@ function descErr<NILS extends NilVal | NilVal[]>(
|
|
|
99
134
|
|
|
100
135
|
(null != v1 && errmsg({
|
|
101
136
|
// TODO: color should come from jsonic config
|
|
102
|
-
color: { active:
|
|
137
|
+
color: { active: colorActive(), line: '\x1b[34m' },
|
|
103
138
|
txts: {
|
|
104
139
|
msg: 'Cannot ' + attempt + ' value: ' + v1.canon +
|
|
105
140
|
(null == v2 ? '' : ' with value: ' + v2.canon), // + ' #' + err.id,
|
|
@@ -115,7 +150,7 @@ function descErr<NILS extends NilVal | NilVal[]>(
|
|
|
115
150
|
|
|
116
151
|
(null != v2 && errmsg({
|
|
117
152
|
// TODO: color should come from jsonic config
|
|
118
|
-
color: { active:
|
|
153
|
+
color: { active: colorActive(), line: '\x1b[34m' },
|
|
119
154
|
txts: {
|
|
120
155
|
msg: 'Cannot ' + attempt + ' value: ' + v2.canon +
|
|
121
156
|
' with value: ' + v1.canon, // + ' #' + err.id,
|
|
@@ -147,13 +182,18 @@ function descErr<NILS extends NilVal | NilVal[]>(
|
|
|
147
182
|
|
|
148
183
|
function resolveFile(url: string | undefined) {
|
|
149
184
|
const cwd = process.cwd()
|
|
150
|
-
|
|
185
|
+
// The PLATFORM'S separator, not '/': a hardcoded slash never matched
|
|
186
|
+
// a Windows cwd, so every Windows report named the absolute path
|
|
187
|
+
// where the POSIX report (and the Go CLI, which prints the entry as
|
|
188
|
+
// typed) named `clash.aon` -- caught by the docs transcript for
|
|
189
|
+
// reading a conflict error, which pins the relative spelling.
|
|
190
|
+
let out = url?.replace(cwd + sep, '') ?? '<no-file>'
|
|
151
191
|
out = out === cwd || '' === out ? '<no-file>' : out
|
|
152
192
|
return out
|
|
153
193
|
}
|
|
154
194
|
|
|
155
195
|
|
|
156
|
-
function resolveSrc(v: Val, errctx: ErrContext | undefined
|
|
196
|
+
function resolveSrc(v: Val, errctx: ErrContext | undefined) {
|
|
157
197
|
let src: string | undefined = undefined
|
|
158
198
|
const url = v?.site.url
|
|
159
199
|
|
|
@@ -210,7 +250,7 @@ class AontuError extends Error {
|
|
|
210
250
|
}
|
|
211
251
|
|
|
212
252
|
errs: () => NilVal[]
|
|
213
|
-
} /* node:coverage ignore next
|
|
253
|
+
} /* node:coverage ignore next 11 */
|
|
214
254
|
|
|
215
255
|
|
|
216
256
|
export {
|
|
@@ -218,4 +258,6 @@ export {
|
|
|
218
258
|
makeNilErr,
|
|
219
259
|
descErr,
|
|
220
260
|
AontuError,
|
|
261
|
+
setColor,
|
|
262
|
+
colorActive,
|
|
221
263
|
}
|
package/src/graph.ts
ADDED
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
/* Copyright (c) 2025 Richard Rodger, MIT License */
|
|
2
|
+
|
|
3
|
+
// THE DERIVED STRUCTURE (G4 phase 3,
|
|
4
|
+
// docs/capability-review/g4-identity-relations.md): an evaluated
|
|
5
|
+
// document has, besides its value, a GRAPH — the set of checked links,
|
|
6
|
+
// each from one tree position to another.
|
|
7
|
+
//
|
|
8
|
+
// The graph is PATH-NATIVE (ADR-014). There is no second namespace to
|
|
9
|
+
// index: a node's address is its path, so the entity index the first
|
|
10
|
+
// design carried is exactly the set of paths already in the edges, and
|
|
11
|
+
// the node a link starts at is derived from where the link sits rather
|
|
12
|
+
// than declared by a mark.
|
|
13
|
+
//
|
|
14
|
+
// G4's deliverable is that this exists and is DETERMINISTIC. What is
|
|
15
|
+
// built on it — impact analysis ("what reaches $.services.auth?"),
|
|
16
|
+
// reachability, context-window-sized slices — is a traversal, and its
|
|
17
|
+
// exposure as verbs and projections belongs to G7. Relation properties
|
|
18
|
+
// (acyclicity, inverse consistency) are G4 phase 5's, and consume
|
|
19
|
+
// exactly this edge set.
|
|
20
|
+
|
|
21
|
+
import type { Val } from './type'
|
|
22
|
+
|
|
23
|
+
import { cmpCodePoint } from './keyorder'
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
export type Edge = {
|
|
27
|
+
// The node the link starts at, as a `$.dotted.path`: the link's own
|
|
28
|
+
// position with the relation key and any list indices stripped. `$`
|
|
29
|
+
// when the link sits at the top of the document.
|
|
30
|
+
from: string
|
|
31
|
+
// The RELATION: the key the link hangs under, so a link inside a
|
|
32
|
+
// list (`dependsOn: [refer() & "$.a"]`) is an edge under `dependsOn`
|
|
33
|
+
// rather than under `0`. A rel()-minted link carries its predicate
|
|
34
|
+
// declared rather than inferred.
|
|
35
|
+
key: string
|
|
36
|
+
// The address, as the link spells it.
|
|
37
|
+
to: string
|
|
38
|
+
// Where the link is, as a `$.dotted.path`, so a report can point at
|
|
39
|
+
// it.
|
|
40
|
+
at: string
|
|
41
|
+
// Set when the link sits inside a `hide()`-marked subtree. The link
|
|
42
|
+
// is still checked and still an edge, but a figure that draws it
|
|
43
|
+
// DISCLOSES what the document hides, so the view extractors skip it
|
|
44
|
+
// and report it as `hidden_contribution`.
|
|
45
|
+
hidden?: true
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export type Graph = {
|
|
49
|
+
edges: Edge[]
|
|
50
|
+
// The positions of links written under an UNRESOLVED DISJUNCTION.
|
|
51
|
+
// ADR-007: an unresolved disjunction is not a value, so a link
|
|
52
|
+
// beneath one of its arms is not a fact and is not an edge -- but it
|
|
53
|
+
// is not nothing either, and a figure that silently dropped it would
|
|
54
|
+
// be the failure the views exist to avoid. Absent when there are
|
|
55
|
+
// none, so a graph of a decided document is the shape it always was.
|
|
56
|
+
disjunct?: string[]
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
const formatPath = (path: string[]): string =>
|
|
61
|
+
0 === path.length ? '$' : '$.' + path.join('.')
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
// Digits-only segments are list indices, which is exactly how the rest
|
|
65
|
+
// of the engine spells them.
|
|
66
|
+
const isIndex = (seg: string): boolean => /^[0-9]+$/.test(seg)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
// The node a link starts at and the relation it hangs under, derived
|
|
70
|
+
// from the link's own position.
|
|
71
|
+
//
|
|
72
|
+
// A DECLARED predicate (rel()-minted) is authoritative: the link is cut
|
|
73
|
+
// at the key the rel() sat on, wherever that is on the way down, which
|
|
74
|
+
// is what makes a MAP-valued relation report the relation rather than
|
|
75
|
+
// the inner label. Without one the relation is INFERRED: strip the list
|
|
76
|
+
// indices, and the first real key above the link is it.
|
|
77
|
+
const cut = (
|
|
78
|
+
at: string[], relkey: string | undefined
|
|
79
|
+
): { from: string, key: string } => {
|
|
80
|
+
if (undefined !== relkey) {
|
|
81
|
+
for (let i = at.length - 1; 0 <= i; i--) {
|
|
82
|
+
if (at[i] === relkey) {
|
|
83
|
+
return { from: formatPath(at.slice(0, i)), key: relkey }
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
let i = at.length - 1
|
|
88
|
+
for (; 0 <= i && isIndex(at[i]); i--) { }
|
|
89
|
+
return 0 > i
|
|
90
|
+
? { from: formatPath([]), key: relkey ?? '' }
|
|
91
|
+
: { from: formatPath(at.slice(0, i)), key: relkey ?? at[i] }
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
// The graph of an evaluated tree. Walks POSITIONS, not values: a
|
|
96
|
+
// reference or a spread can put one value object at several positions,
|
|
97
|
+
// and a walk guarded by object identity would find the first and miss
|
|
98
|
+
// every other place it is reached. The guard is therefore the ancestor
|
|
99
|
+
// chain — which is what a cycle actually is.
|
|
100
|
+
export function graphOf(root: Val): Graph {
|
|
101
|
+
const edges: Edge[] = []
|
|
102
|
+
const disjunct: string[] = []
|
|
103
|
+
|
|
104
|
+
// ONE WALK, with `undecided` saying which side of ADR-007 it is on:
|
|
105
|
+
// below an unresolved disjunction every link is a position the
|
|
106
|
+
// document has not decided, and nothing there is an edge.
|
|
107
|
+
const visit = (
|
|
108
|
+
node: any, path: string[], ancestors: Set<any>, hidden: boolean,
|
|
109
|
+
undecided: boolean
|
|
110
|
+
): void => {
|
|
111
|
+
if (null == node || true !== node.isVal || ancestors.has(node)) {
|
|
112
|
+
return
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
hidden = hidden || true === node.mark?.hide
|
|
116
|
+
|
|
117
|
+
const link = node.link
|
|
118
|
+
if (null != link) {
|
|
119
|
+
if (undecided) {
|
|
120
|
+
disjunct.push(formatPath(path))
|
|
121
|
+
}
|
|
122
|
+
else {
|
|
123
|
+
const { from, key } = cut(path, node.relkey as string | undefined)
|
|
124
|
+
const edge: Edge = { from, key, to: link, at: formatPath(path) }
|
|
125
|
+
if (hidden) {
|
|
126
|
+
edge.hidden = true
|
|
127
|
+
}
|
|
128
|
+
edges.push(edge)
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// A graph atom is TRANSPARENT here (RELATIONS P2): it carries the
|
|
133
|
+
// field's value at the field's own position, and the graph is about
|
|
134
|
+
// the value.
|
|
135
|
+
if (true === node.isGraphAtom && undefined !== node.held) {
|
|
136
|
+
visit(node.held, path, ancestors, hidden, undecided)
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// An unresolved conjunction (a link waiting on a peer that never
|
|
140
|
+
// came, a constraint still open) holds its terms at the SAME
|
|
141
|
+
// position; every link among them is written there, and is an
|
|
142
|
+
// edge. This is what a match-selected branch or a deferred refer()
|
|
143
|
+
// looks like after evaluation.
|
|
144
|
+
if (true === node.isConjunct && Array.isArray(node.peg)) {
|
|
145
|
+
ancestors.add(node)
|
|
146
|
+
for (const term of node.peg) {
|
|
147
|
+
visit(term, path, ancestors, hidden, undecided)
|
|
148
|
+
}
|
|
149
|
+
ancestors.delete(node)
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// AN UNRESOLVED DISJUNCTION IS NOT A VALUE (ADR-007), so a link
|
|
153
|
+
// under one of its arms is not an edge. Its POSITION is collected
|
|
154
|
+
// instead, so a figure can report what the document leaves
|
|
155
|
+
// undecided rather than drawing it or dropping it in silence.
|
|
156
|
+
if (true === node.isDisjunct && Array.isArray(node.peg)) {
|
|
157
|
+
ancestors.add(node)
|
|
158
|
+
for (const arm of node.peg) {
|
|
159
|
+
visit(arm, path, ancestors, hidden, true)
|
|
160
|
+
}
|
|
161
|
+
ancestors.delete(node)
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
if ((true === node.isMap || true === node.isList) && null != node.peg) {
|
|
165
|
+
ancestors.add(node)
|
|
166
|
+
for (const k of Object.keys(node.peg)) {
|
|
167
|
+
visit(node.peg[k], [...path, k], ancestors, hidden, undecided)
|
|
168
|
+
}
|
|
169
|
+
ancestors.delete(node)
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
visit(root, [], new Set(), false, false)
|
|
174
|
+
|
|
175
|
+
// DETERMINISTIC by construction, not by luck: edges by the position
|
|
176
|
+
// they are written at, which is unique — one link, one place. That
|
|
177
|
+
// holds through a conjunction too: its terms share the position, and
|
|
178
|
+
// two links there would have had to unify into one.
|
|
179
|
+
edges.sort((a, b) => cmpCodePoint(a.at, b.at))
|
|
180
|
+
|
|
181
|
+
if (0 < disjunct.length) {
|
|
182
|
+
return { edges, disjunct: [...new Set(disjunct)].sort(cmpCodePoint) }
|
|
183
|
+
}
|
|
184
|
+
return { edges }
|
|
185
|
+
}
|