aontu 0.61.0 → 0.63.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 +4 -4
- package/dist/agentsmd.d.ts +1 -0
- package/dist/agentsmd.js +7 -28
- package/dist/agentsmd.js.map +1 -1
- package/dist/alias.js.map +1 -1
- package/dist/allow.d.ts +23 -0
- package/dist/allow.js +138 -0
- package/dist/allow.js.map +1 -0
- package/dist/aontu.d.ts +4 -2
- package/dist/aontu.js +4 -80
- package/dist/aontu.js.map +1 -1
- package/dist/aontumodel.d.ts +4 -0
- package/dist/aontumodel.js +33 -0
- package/dist/aontumodel.js.map +1 -0
- package/dist/cli.d.ts +10 -1
- package/dist/cli.js +877 -479
- package/dist/cli.js.map +1 -1
- package/dist/ctx.js +0 -48
- package/dist/ctx.js.map +1 -1
- package/dist/diff.js +0 -32
- package/dist/diff.js.map +1 -1
- package/dist/err.js +0 -40
- package/dist/err.js.map +1 -1
- package/dist/escape.js +0 -45
- package/dist/escape.js.map +1 -1
- package/dist/exactjson.d.ts +0 -35
- package/dist/exactjson.js +0 -131
- package/dist/exactjson.js.map +1 -1
- package/dist/format.js +55 -189
- package/dist/format.js.map +1 -1
- package/dist/grammar.d.ts +9 -0
- package/dist/grammar.js +54 -0
- package/dist/grammar.js.map +1 -0
- package/dist/graph.js +0 -26
- package/dist/graph.js.map +1 -1
- package/dist/hcanon.js +0 -82
- package/dist/hcanon.js.map +1 -1
- package/dist/helpdoc.d.ts +16 -0
- package/dist/helpdoc.js +59 -0
- package/dist/helpdoc.js.map +1 -0
- package/dist/hints.d.ts +0 -6
- package/dist/hints.js +59 -47
- package/dist/hints.js.map +1 -1
- package/dist/jsonschema.js +0 -114
- package/dist/jsonschema.js.map +1 -1
- package/dist/keyorder.d.ts +0 -7
- package/dist/keyorder.js +0 -41
- package/dist/keyorder.js.map +1 -1
- package/dist/lang.js +32 -877
- package/dist/lang.js.map +1 -1
- package/dist/lower.d.ts +3 -0
- package/dist/lower.js +14 -61
- package/dist/lower.js.map +1 -1
- package/dist/lsp-server.js +0 -16
- package/dist/lsp-server.js.map +1 -1
- package/dist/lsp.d.ts +1 -1
- package/dist/lsp.js +12 -159
- package/dist/lsp.js.map +1 -1
- package/dist/mcp-server.js +0 -26
- package/dist/mcp-server.js.map +1 -1
- package/dist/mcp.js +0 -113
- package/dist/mcp.js.map +1 -1
- package/dist/mod-tool.js +0 -130
- package/dist/mod-tool.js.map +1 -1
- package/dist/mod.js +0 -162
- package/dist/mod.js.map +1 -1
- package/dist/patch.js +0 -217
- package/dist/patch.js.map +1 -1
- package/dist/provenance.js +0 -140
- package/dist/provenance.js.map +1 -1
- package/dist/query.js +0 -75
- package/dist/query.js.map +1 -1
- package/dist/reach.js +0 -43
- package/dist/reach.js.map +1 -1
- package/dist/relation.d.ts +2 -0
- package/dist/relation.js +3 -58
- package/dist/relation.js.map +1 -1
- package/dist/render.js +33 -143
- package/dist/render.js.map +1 -1
- package/dist/report-sarif.d.ts +0 -11
- package/dist/report-sarif.js +0 -28
- package/dist/report-sarif.js.map +1 -1
- package/dist/sig.js +0 -35
- package/dist/sig.js.map +1 -1
- package/dist/sigdecl.js +1 -1
- package/dist/sigdecl.js.map +1 -1
- package/dist/siggate.js +0 -4
- package/dist/siggate.js.map +1 -1
- package/dist/site.js +3 -29
- package/dist/site.js.map +1 -1
- package/dist/subsume.d.ts +0 -10
- package/dist/subsume.js +0 -137
- package/dist/subsume.js.map +1 -1
- package/dist/template.d.ts +2 -1
- package/dist/template.js +58 -138
- package/dist/template.js.map +1 -1
- package/dist/trim.js +0 -41
- package/dist/trim.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/type.js.map +1 -1
- package/dist/unify.js +12 -242
- package/dist/unify.js.map +1 -1
- package/dist/utility.js +0 -22
- package/dist/utility.js.map +1 -1
- package/dist/val/AbnfFuncVal.d.ts +18 -0
- package/dist/val/AbnfFuncVal.js +132 -0
- package/dist/val/AbnfFuncVal.js.map +1 -0
- package/dist/val/AbsentVal.d.ts +11 -0
- package/dist/val/AbsentVal.js +30 -0
- package/dist/val/AbsentVal.js.map +1 -0
- package/dist/val/AggFuncVal.d.ts +10 -1
- package/dist/val/AggFuncVal.js +104 -116
- package/dist/val/AggFuncVal.js.map +1 -1
- package/dist/val/ArithFuncVal.js +0 -12
- package/dist/val/ArithFuncVal.js.map +1 -1
- package/dist/val/BagVal.js +1 -78
- package/dist/val/BagVal.js.map +1 -1
- package/dist/val/BigDecimalVal.js +0 -16
- package/dist/val/BigDecimalVal.js.map +1 -1
- package/dist/val/BigIntegerVal.js +0 -16
- package/dist/val/BigIntegerVal.js.map +1 -1
- package/dist/val/CloseFuncVal.js +0 -9
- package/dist/val/CloseFuncVal.js.map +1 -1
- package/dist/val/CmpFuncVal.d.ts +20 -0
- package/dist/val/CmpFuncVal.js +188 -0
- package/dist/val/CmpFuncVal.js.map +1 -0
- package/dist/val/ConjunctVal.js +0 -29
- package/dist/val/ConjunctVal.js.map +1 -1
- package/dist/val/ConstraintVal.js +0 -500
- package/dist/val/ConstraintVal.js.map +1 -1
- package/dist/val/ContainerKindVal.js +0 -2
- package/dist/val/ContainerKindVal.js.map +1 -1
- package/dist/val/CopyFuncVal.js +0 -3
- package/dist/val/CopyFuncVal.js.map +1 -1
- package/dist/val/Decimal.js +0 -179
- package/dist/val/Decimal.js.map +1 -1
- package/dist/val/DeprecateFuncVal.js.map +1 -1
- package/dist/val/DisjunctVal.js +0 -152
- package/dist/val/DisjunctVal.js.map +1 -1
- package/dist/val/EachFuncVal.d.ts +1 -2
- package/dist/val/EachFuncVal.js +12 -29
- package/dist/val/EachFuncVal.js.map +1 -1
- package/dist/val/EmitFuncVal.d.ts +1 -1
- package/dist/val/EmitFuncVal.js +6 -119
- package/dist/val/EmitFuncVal.js.map +1 -1
- package/dist/val/ExpectVal.js +0 -62
- package/dist/val/ExpectVal.js.map +1 -1
- package/dist/val/FilterFuncVal.js +0 -25
- package/dist/val/FilterFuncVal.js.map +1 -1
- package/dist/val/FuncBaseVal.d.ts +1 -0
- package/dist/val/FuncBaseVal.js +7 -127
- package/dist/val/FuncBaseVal.js.map +1 -1
- package/dist/val/GraphAtomVal.js +0 -15
- package/dist/val/GraphAtomVal.js.map +1 -1
- package/dist/val/HideFuncVal.js +0 -13
- package/dist/val/HideFuncVal.js.map +1 -1
- package/dist/val/IntegerVal.js +0 -61
- package/dist/val/IntegerVal.js.map +1 -1
- package/dist/val/JunctionVal.js +0 -20
- package/dist/val/JunctionVal.js.map +1 -1
- package/dist/val/KeyFuncVal.js +0 -46
- package/dist/val/KeyFuncVal.js.map +1 -1
- package/dist/val/ListVal.js +0 -57
- package/dist/val/ListVal.js.map +1 -1
- package/dist/val/LowerFuncVal.js +11 -6
- package/dist/val/LowerFuncVal.js.map +1 -1
- package/dist/val/MapVal.js +0 -151
- package/dist/val/MapVal.js.map +1 -1
- package/dist/val/MatchFuncVal.js +0 -27
- package/dist/val/MatchFuncVal.js.map +1 -1
- package/dist/val/{FormFuncVal.d.ts → MaybeFuncVal.d.ts} +5 -5
- package/dist/val/MaybeFuncVal.js +50 -0
- package/dist/val/MaybeFuncVal.js.map +1 -0
- package/dist/val/MoveFuncVal.js +0 -18
- package/dist/val/MoveFuncVal.js.map +1 -1
- package/dist/val/NilVal.js +2 -36
- package/dist/val/NilVal.js.map +1 -1
- package/dist/val/NomFuncVal.d.ts +12 -0
- package/dist/val/NomFuncVal.js +153 -0
- package/dist/val/NomFuncVal.js.map +1 -0
- package/dist/val/NumberVal.js +0 -15
- package/dist/val/NumberVal.js.map +1 -1
- package/dist/val/OpBaseVal.d.ts +1 -0
- package/dist/val/OpBaseVal.js +3 -15
- package/dist/val/OpBaseVal.js.map +1 -1
- package/dist/val/PackFuncVal.js +0 -34
- package/dist/val/PackFuncVal.js.map +1 -1
- package/dist/val/PathFuncVal.js +0 -6
- package/dist/val/PathFuncVal.js.map +1 -1
- package/dist/val/PathVal.js +0 -41
- package/dist/val/PathVal.js.map +1 -1
- package/dist/val/PlaceVal.js +0 -25
- package/dist/val/PlaceVal.js.map +1 -1
- package/dist/val/PlusOpVal.d.ts +1 -7
- package/dist/val/PlusOpVal.js +13 -74
- package/dist/val/PlusOpVal.js.map +1 -1
- package/dist/val/PrefFuncVal.js +0 -1
- package/dist/val/PrefFuncVal.js.map +1 -1
- package/dist/val/PrefVal.js +0 -167
- package/dist/val/PrefVal.js.map +1 -1
- package/dist/val/RecurseVal.js +0 -55
- package/dist/val/RecurseVal.js.map +1 -1
- package/dist/val/RefVal.js +0 -282
- package/dist/val/RefVal.js.map +1 -1
- package/dist/val/ReferFuncVal.js +0 -232
- package/dist/val/ReferFuncVal.js.map +1 -1
- package/dist/val/ScalarKindVal.js +0 -49
- package/dist/val/ScalarKindVal.js.map +1 -1
- package/dist/val/ScalarVal.js +0 -11
- package/dist/val/ScalarVal.js.map +1 -1
- package/dist/val/StrFuncVal.js +0 -18
- package/dist/val/StrFuncVal.js.map +1 -1
- package/dist/val/SuperFuncVal.js +0 -32
- package/dist/val/SuperFuncVal.js.map +1 -1
- package/dist/val/TopVal.js +0 -1
- package/dist/val/TopVal.js.map +1 -1
- package/dist/val/TranslateFuncVal.d.ts +12 -0
- package/dist/val/TranslateFuncVal.js +99 -0
- package/dist/val/TranslateFuncVal.js.map +1 -0
- package/dist/val/UpperFuncVal.js +11 -6
- package/dist/val/UpperFuncVal.js.map +1 -1
- package/dist/val/Val.d.ts +1 -0
- package/dist/val/Val.js +2 -133
- package/dist/val/Val.js.map +1 -1
- package/dist/val/VarVal.js +0 -12
- package/dist/val/VarVal.js.map +1 -1
- package/dist/val/arith.js +0 -37
- package/dist/val/arith.js.map +1 -1
- package/dist/val/caserange.d.ts +3 -0
- package/dist/val/caserange.js +49 -0
- package/dist/val/caserange.js.map +1 -0
- package/dist/val/members.js +0 -6
- package/dist/val/members.js.map +1 -1
- package/dist/val/numcmp.js +0 -11
- package/dist/val/numcmp.js.map +1 -1
- package/dist/val/numkind.js +0 -145
- package/dist/val/numkind.js.map +1 -1
- package/dist/val/valutil.js +0 -16
- package/dist/val/valutil.js.map +1 -1
- package/dist/vet.d.ts +12 -0
- package/dist/vet.js +159 -412
- package/dist/vet.js.map +1 -1
- package/dist/view.js +0 -414
- package/dist/view.js.map +1 -1
- package/dist/walk.js +0 -41
- package/dist/walk.js.map +1 -1
- package/grammar/aontu.abnf +9 -7
- package/grammar/aontu.gbnf +5 -5
- package/grammar/aontu.lark +5 -5
- package/grammar/aontu.tmLanguage.json +1 -1
- package/package.json +4 -2
- package/skill/SKILL.md +8 -0
- package/skill/init/check.sh +28 -0
- package/skill/init/data.aon +12 -0
- package/skill/init/model.aon +19 -0
- package/skill/tasks.md +151 -0
- package/src/agentsmd.ts +8 -32
- package/src/alias.ts +0 -39
- package/src/allow.ts +221 -0
- package/src/aontu.ts +10 -108
- package/src/aontumodel.ts +32 -0
- package/src/cli.ts +1009 -540
- package/src/ctx.ts +0 -103
- package/src/diff.ts +0 -40
- package/src/err.ts +0 -40
- package/src/escape.ts +0 -46
- package/src/exactjson.ts +0 -131
- package/src/format.ts +63 -234
- package/src/grammar.ts +72 -0
- package/src/graph.ts +0 -61
- package/src/hcanon.ts +0 -82
- package/src/helpdoc.ts +77 -0
- package/src/hints.ts +72 -49
- package/src/jsonschema.ts +0 -123
- package/src/keyorder.ts +0 -42
- package/src/lang.ts +39 -895
- package/src/lower.ts +15 -65
- package/src/lsp-server.ts +0 -16
- package/src/lsp.ts +12 -180
- package/src/mcp-server.ts +0 -31
- package/src/mcp.ts +0 -130
- package/src/mod-tool.ts +0 -158
- package/src/mod.ts +0 -178
- package/src/patch.ts +0 -232
- package/src/provenance.ts +0 -183
- package/src/query.ts +0 -84
- package/src/reach.ts +0 -53
- package/src/relation.ts +7 -71
- package/src/render.ts +33 -180
- package/src/report-sarif.ts +0 -48
- package/src/sig.ts +0 -35
- package/src/sigdecl.ts +1 -1
- package/src/siggate.ts +0 -30
- package/src/site.ts +3 -29
- package/src/subsume.ts +1 -161
- package/src/template.ts +69 -140
- package/src/trim.ts +0 -53
- package/src/type.ts +2 -45
- package/src/unify.ts +13 -251
- package/src/utility.ts +0 -31
- package/src/val/AbnfFuncVal.ts +181 -0
- package/src/val/AbsentVal.ts +54 -0
- package/src/val/AggFuncVal.ts +152 -188
- package/src/val/ArithFuncVal.ts +0 -20
- package/src/val/BagVal.ts +1 -78
- package/src/val/BigDecimalVal.ts +0 -16
- package/src/val/BigIntegerVal.ts +0 -16
- package/src/val/CloseFuncVal.ts +0 -9
- package/src/val/CmpFuncVal.ts +249 -0
- package/src/val/ConjunctVal.ts +0 -33
- package/src/val/ConstraintVal.ts +2 -537
- package/src/val/ContainerKindVal.ts +0 -18
- package/src/val/CopyFuncVal.ts +0 -5
- package/src/val/Decimal.ts +1 -185
- package/src/val/DeprecateFuncVal.ts +0 -10
- package/src/val/DisjunctVal.ts +0 -157
- package/src/val/EachFuncVal.ts +12 -53
- package/src/val/EmitFuncVal.ts +8 -208
- package/src/val/ExpectVal.ts +0 -62
- package/src/val/FilterFuncVal.ts +0 -55
- package/src/val/FuncBaseVal.ts +9 -130
- package/src/val/GraphAtomVal.ts +0 -42
- package/src/val/HideFuncVal.ts +0 -15
- package/src/val/IntegerVal.ts +0 -61
- package/src/val/JunctionVal.ts +0 -20
- package/src/val/KeyFuncVal.ts +0 -48
- package/src/val/ListVal.ts +0 -59
- package/src/val/LowerFuncVal.ts +12 -7
- package/src/val/MapVal.ts +0 -151
- package/src/val/MatchFuncVal.ts +0 -59
- package/src/val/MaybeFuncVal.ts +86 -0
- package/src/val/MoveFuncVal.ts +0 -20
- package/src/val/NilVal.ts +2 -36
- package/src/val/NomFuncVal.ts +200 -0
- package/src/val/NumberVal.ts +0 -16
- package/src/val/OpBaseVal.ts +4 -17
- package/src/val/PackFuncVal.ts +0 -63
- package/src/val/PathFuncVal.ts +0 -32
- package/src/val/PathVal.ts +0 -66
- package/src/val/PlaceVal.ts +0 -45
- package/src/val/PlusOpVal.ts +18 -75
- package/src/val/PrefFuncVal.ts +0 -1
- package/src/val/PrefVal.ts +0 -179
- package/src/val/RecurseVal.ts +0 -81
- package/src/val/RefVal.ts +1 -285
- package/src/val/ReferFuncVal.ts +0 -255
- package/src/val/ScalarKindVal.ts +0 -50
- package/src/val/ScalarVal.ts +0 -12
- package/src/val/StrFuncVal.ts +0 -44
- package/src/val/SuperFuncVal.ts +0 -42
- package/src/val/TopVal.ts +0 -1
- package/src/val/TranslateFuncVal.ts +132 -0
- package/src/val/UpperFuncVal.ts +12 -7
- package/src/val/Val.ts +3 -192
- package/src/val/VarVal.ts +0 -15
- package/src/val/arith.ts +0 -92
- package/src/val/caserange.ts +53 -0
- package/src/val/members.ts +0 -23
- package/src/val/numcmp.ts +1 -27
- package/src/val/numkind.ts +0 -149
- package/src/val/valutil.ts +0 -16
- package/src/vet.ts +209 -504
- package/src/view.ts +0 -507
- package/src/walk.ts +0 -41
- package/dist/std.d.ts +0 -3
- package/dist/std.js +0 -637
- package/dist/std.js.map +0 -1
- package/dist/val/FormFuncVal.js +0 -55
- package/dist/val/FormFuncVal.js.map +0 -1
- package/src/std.ts +0 -648
- package/src/val/FormFuncVal.ts +0 -119
package/src/vet.ts
CHANGED
|
@@ -1,28 +1,5 @@
|
|
|
1
1
|
/* Copyright (c) 2025 Richard Rodger, MIT License */
|
|
2
2
|
|
|
3
|
-
// The validation verb: check a data document against a schema document
|
|
4
|
-
// and return a MACHINE-READABLE report (G2 phase 2,
|
|
5
|
-
// docs/capability-review/g2-validation-verb.md).
|
|
6
|
-
//
|
|
7
|
-
// This module is the engine only. The CLI verb, the JSON/SARIF
|
|
8
|
-
// renderers and `--watch` are phase 3 and 5; the Go port is phase 4.
|
|
9
|
-
// Keeping the engine transport-free is the split lsp.ts already uses,
|
|
10
|
-
// and for the same reason: it is the part that has to be unit-testable
|
|
11
|
-
// and embeddable.
|
|
12
|
-
//
|
|
13
|
-
// Two properties of the engine shape everything here, and both were
|
|
14
|
-
// probed rather than assumed:
|
|
15
|
-
//
|
|
16
|
-
// - A CONTRADICTION surfaces as a NilVal in the unified tree, so
|
|
17
|
-
// conflict findings come from a tree walk (the `walkNils` pattern
|
|
18
|
-
// lsp.ts already uses, generalised).
|
|
19
|
-
// - INCOMPLETENESS does not. `{name:"auth"}` against
|
|
20
|
-
// `{name:string, port:integer}` unifies cleanly and leaves
|
|
21
|
-
// `port:integer` standing — no nil, no error. It surfaces only when
|
|
22
|
-
// something tries to GENERATE, which is why vet runs a generate
|
|
23
|
-
// check in an isolated collect context and reads the
|
|
24
|
-
// `incomplete`-class errors out of it. The two verdicts the report
|
|
25
|
-
// distinguishes therefore come from two different mechanisms.
|
|
26
3
|
|
|
27
4
|
import type { TrustOptions, Val } from './type'
|
|
28
5
|
|
|
@@ -40,12 +17,8 @@ import { collectDeprecations, walkBagVals, deprecationMessage,
|
|
|
40
17
|
includeOpts,
|
|
41
18
|
} from './utility'
|
|
42
19
|
import { subsumeNode, effectiveDefault } from './subsume'
|
|
43
|
-
// The `--at` refusal is the SAME refusal `get` and `why` give for a
|
|
44
|
-
// path that names nothing, down to the "did you mean" -- so it is that
|
|
45
|
-
// one, not a second spelling of it. A cycle in the module graph
|
|
46
|
-
// (query imports anchorAt from here), and a benign one: both sides
|
|
47
|
-
// use the other only from inside a function body, never at load time.
|
|
48
20
|
import { noPathFinding } from './query'
|
|
21
|
+
import { cmpCodePoint } from './keyorder'
|
|
49
22
|
|
|
50
23
|
|
|
51
24
|
export type VetVerdict = 'valid' | 'invalid' | 'incomplete' | 'error'
|
|
@@ -56,34 +29,8 @@ export type VetSite = {
|
|
|
56
29
|
file: string
|
|
57
30
|
row: number
|
|
58
31
|
col: number
|
|
59
|
-
// The extent in UTF-16 code units, or -1 when unknown — the same
|
|
60
|
-
// "unknown" row and col already use.
|
|
61
|
-
//
|
|
62
|
-
// THIS IS WHAT MAKES A FINDING REPAIRABLE. `value` is the CANON, not
|
|
63
|
-
// the source text: `port: 0x1F` reports canon `31` at column 7, so a
|
|
64
|
-
// consumer replacing `(col, value.length)` writes `port: 5x1F` and
|
|
65
|
-
// corrupts the document. With `len` the span is `(col, 4)` and the
|
|
66
|
-
// replacement is exact.
|
|
67
|
-
//
|
|
68
|
-
// NEVER GUESSED. Where the span is unknown this is -1 and a consumer
|
|
69
|
-
// must not edit — unlike the LSP, which falls back to canon because a
|
|
70
|
-
// wonky highlight is cosmetic while a wrong edit is a lost file.
|
|
71
|
-
// See ts/src/site.ts for what the extent covers.
|
|
72
32
|
len: number
|
|
73
33
|
role: VetRole
|
|
74
|
-
// The SOURCE TEXT the span covers, or '' when unknown.
|
|
75
|
-
//
|
|
76
|
-
// This is what makes the span SELF-VERIFYING, and it is not the same
|
|
77
|
-
// as `value`. For a scalar the two differ by normalisation — `0x1F`
|
|
78
|
-
// has src `0x1F` and value (canon) `31`. For a COMPOUND the span
|
|
79
|
-
// names the opening token only, exactly as row and col always have:
|
|
80
|
-
// a constraint `min(1)` reports src `min`, and a reference `$.b`
|
|
81
|
-
// reports src `$`.
|
|
82
|
-
//
|
|
83
|
-
// So a consumer must read the document at (row, col, len), compare it
|
|
84
|
-
// to `src`, and REFUSE when they differ — and, seeing `min` where it
|
|
85
|
-
// expected `min(1)`, refuse rather than replace the name and orphan
|
|
86
|
-
// the arguments. Without this field that mistake is undetectable.
|
|
87
34
|
src?: string
|
|
88
35
|
value?: string
|
|
89
36
|
}
|
|
@@ -95,21 +42,6 @@ export type VetFinding = {
|
|
|
95
42
|
path: string
|
|
96
43
|
message: string
|
|
97
44
|
|
|
98
|
-
// THE REPAIR, not just the diagnosis. `message` is one line by
|
|
99
|
-
// design -- it is the headline, and the frames under it are for a
|
|
100
|
-
// human at a terminal -- but for several codes the part that says
|
|
101
|
-
// what to DO about the failure lived only in those frames, so a
|
|
102
|
-
// machine reader (an agent, a CI annotation, an editor) got the
|
|
103
|
-
// complaint and none of the cure. `0d` is the clearest case: the
|
|
104
|
-
// engine refuses a lossy integer literal and the hint names the
|
|
105
|
-
// exact-decimal escape that fixes it. Populated from the shared
|
|
106
|
-
// hints table (ts/src/hints.ts, mirrored by go/hints.go) whenever
|
|
107
|
-
// the code has one; absent when it does not (the report-layer
|
|
108
|
-
// compat_* codes, notably, carry no hint text).
|
|
109
|
-
//
|
|
110
|
-
// Excluded from spec goldens for the same reason `message` is: it is
|
|
111
|
-
// prose, it is long, and the two ports hold it to the byte through
|
|
112
|
-
// the message tests instead of through every vet row.
|
|
113
45
|
hint?: string
|
|
114
46
|
|
|
115
47
|
sites: VetSite[]
|
|
@@ -118,10 +50,27 @@ export type VetFinding = {
|
|
|
118
50
|
note?: string
|
|
119
51
|
}
|
|
120
52
|
|
|
53
|
+
export type VetCoverage = {
|
|
54
|
+
checked: number
|
|
55
|
+
declared: number
|
|
56
|
+
// Data leaves in all, under `coverageAt` when it is given. `checked`
|
|
57
|
+
// over this is the ratio a reader wants.
|
|
58
|
+
leaves: number
|
|
59
|
+
unchecked: string[]
|
|
60
|
+
// The SHALLOWEST declarations no data path met.
|
|
61
|
+
unused: string[]
|
|
62
|
+
// NO data leaf was constrained, over a document that has leaves.
|
|
63
|
+
// The exact condition of the `"*"` failure, and what
|
|
64
|
+
// `--strict-coverage` exits 1 on.
|
|
65
|
+
vacuous: boolean
|
|
66
|
+
}
|
|
67
|
+
|
|
121
68
|
export type VetReport = {
|
|
122
69
|
verdict: VetVerdict
|
|
123
70
|
truncated: boolean
|
|
124
71
|
findings: VetFinding[]
|
|
72
|
+
// Absent unless the run asked for it.
|
|
73
|
+
coverage?: VetCoverage
|
|
125
74
|
}
|
|
126
75
|
|
|
127
76
|
export type VetOptions = {
|
|
@@ -129,29 +78,18 @@ export type VetOptions = {
|
|
|
129
78
|
closed?: boolean // close() the anchor for this run
|
|
130
79
|
partial?: boolean // residue is not a failure
|
|
131
80
|
maxErrors?: number // cap the finding list (default 20)
|
|
132
|
-
|
|
81
|
+
|
|
82
|
+
// Account for what the check examined (G11 phase 5). Off by default:
|
|
83
|
+
// the report gains a `coverage` object only when this is set, so no
|
|
84
|
+
// existing caller's report changes shape.
|
|
85
|
+
coverage?: boolean
|
|
86
|
+
coverageAt?: string
|
|
87
|
+
schemaUrl?: string
|
|
133
88
|
dataUrl?: string // provenance label for data sites
|
|
134
89
|
|
|
135
|
-
// Where each document CAME FROM, used to resolve its relative
|
|
136
|
-
// `@"file"` loads -- the file path, as `Aontu({path})` takes it, not
|
|
137
|
-
// the directory. Vet takes two documents from its caller rather than
|
|
138
|
-
// from the filesystem, so it cannot know this: without it a modular
|
|
139
|
-
// schema resolved its includes against the process working
|
|
140
|
-
// directory, which fails outside that directory and, worse, silently
|
|
141
|
-
// reads a same-named file that happens to sit there. The two
|
|
142
|
-
// documents get their OWN bases, because they need not live in the
|
|
143
|
-
// same place.
|
|
144
90
|
schemaPath?: string
|
|
145
91
|
dataPath?: string
|
|
146
92
|
|
|
147
|
-
// The trust profile this run evaluates under (G5, docs/trust.md).
|
|
148
|
-
// Vet's whole job is to evaluate a document its caller did not
|
|
149
|
-
// write, so the caller must be able to say what that document is
|
|
150
|
-
// allowed to reach: without this the include chain is the default
|
|
151
|
-
// one, and `@"../../etc/passwd.aon"` in hostile data reads whatever
|
|
152
|
-
// the validating process can. (`@"x.js"` no longer executes --
|
|
153
|
-
// ADR-012 refuses the extension -- but reading is enough.) A server
|
|
154
|
-
// passes `{include:'none'}`.
|
|
155
93
|
trust?: TrustOptions
|
|
156
94
|
|
|
157
95
|
// Extensions additionally read as text (the CLI's `--text-ext`).
|
|
@@ -170,37 +108,10 @@ const DEFAULT_SCHEMA_URL = 'schema'
|
|
|
170
108
|
const DEFAULT_DATA_URL = 'data'
|
|
171
109
|
|
|
172
110
|
|
|
173
|
-
// Every site in a freshly parsed tree carries the same url, and for a
|
|
174
|
-
// bare parse that url is the empty string: `site.url` is only populated
|
|
175
|
-
// by the multisource loader (ts/src/lang.ts). Vet takes two documents
|
|
176
|
-
// from its CALLER, not from the filesystem, so it stamps provenance
|
|
177
|
-
// itself — which is what lets the report assign site ROLES by
|
|
178
|
-
// provenance rather than by NilVal's source-order heuristic, exactly as
|
|
179
|
-
// the design requires.
|
|
180
|
-
// EVERY SITE NAMES THE FILE WHOSE TEXT IT EXCERPTS (the review's
|
|
181
|
-
// finding F, use-cases/BUGS.md §25). The parser already names the file
|
|
182
|
-
// each value was read from -- a value loaded through `@"lib/types.aon"`
|
|
183
|
-
// carries that path, with that file's row and column -- and this walk
|
|
184
|
-
// used to OVERWRITE every url with the entry document's name. The
|
|
185
|
-
// coordinates stayed the included file's, so a finding cited
|
|
186
|
-
// `entry.aon:3:7` for text that lives three files away, at a line the
|
|
187
|
-
// entry may not even have. A repair agent that follows the site edits
|
|
188
|
-
// the wrong file.
|
|
189
|
-
//
|
|
190
|
-
// Only the values that carry no name of their own are stamped: those
|
|
191
|
-
// are the ones the engine minted rather than read, and the entry is
|
|
192
|
-
// the honest name for them. The urls actually seen are collected, so
|
|
193
|
-
// the report can still tell WHICH DOCUMENT a site belongs to without
|
|
194
|
-
// pretending they all came from one file -- see roleOf.
|
|
195
111
|
function stampUrl(v: any, url: string, seen?: Set<string>): Set<string> {
|
|
196
112
|
const urls = seen ?? new Set<string>()
|
|
197
113
|
urls.add(url)
|
|
198
114
|
walkVals(v, (n: any) => {
|
|
199
|
-
// UNDEFINED counts as unstamped, not just empty: the parser leaves
|
|
200
|
-
// the url undefined on one of its two paths (ts/src/lang.ts), and
|
|
201
|
-
// treating that as a name would put `undefined` in the url set --
|
|
202
|
-
// where the OTHER document's unstamped values would then match it,
|
|
203
|
-
// and every site would read `data`.
|
|
204
115
|
if (null == n.site.url || '' === n.site.url) {
|
|
205
116
|
n.site.url = url
|
|
206
117
|
}
|
|
@@ -211,10 +122,6 @@ function stampUrl(v: any, url: string, seen?: Set<string>): Set<string> {
|
|
|
211
122
|
}
|
|
212
123
|
|
|
213
124
|
|
|
214
|
-
// The provenance a report projects its sites through: which urls
|
|
215
|
-
// belong to the DATA document, and how the caller reached each of the
|
|
216
|
-
// two documents (which is what says how to NAME a third file either of
|
|
217
|
-
// them included).
|
|
218
125
|
type Prov = {
|
|
219
126
|
// The urls the data walk reached. Roles are decided by membership,
|
|
220
127
|
// on the RAW url -- never by a name comparison.
|
|
@@ -226,20 +133,6 @@ type Prov = {
|
|
|
226
133
|
}
|
|
227
134
|
|
|
228
135
|
|
|
229
|
-
// A FILE THE READER CAN OPEN. The parser resolves an include to an
|
|
230
|
-
// absolute path, which is the right identity (two files loading the
|
|
231
|
-
// same library by different relative spellings must be one file) and
|
|
232
|
-
// the wrong NAME: a report whose entry reads `contract.aon` and whose
|
|
233
|
-
// included site reads `/home/someone/checkout/types.aon` is a report
|
|
234
|
-
// that cannot be uploaded as SARIF, diffed between machines, or read
|
|
235
|
-
// beside the command that produced it.
|
|
236
|
-
//
|
|
237
|
-
// So an included file is named as the ENTRY'S OWN NAME reaches it:
|
|
238
|
-
// relative to the entry's directory, then re-anchored on however the
|
|
239
|
-
// caller spelled the entry. `vet contract.aon` names `types.aon`;
|
|
240
|
-
// `vet a/b/contract.aon` names `a/b/types.aon`; an absolute entry
|
|
241
|
-
// keeps absolute includes. A caller who passed no path at all has no
|
|
242
|
-
// base to relativise against and gets the url unchanged.
|
|
243
136
|
export function displayFile(
|
|
244
137
|
url: string, label: string, path?: string): string {
|
|
245
138
|
if (url === label || null == path || '' === url || !isAbsolute(url)) {
|
|
@@ -251,12 +144,6 @@ export function displayFile(
|
|
|
251
144
|
}
|
|
252
145
|
|
|
253
146
|
|
|
254
|
-
// The name for one site, taken from the document the site BELONGS to
|
|
255
|
-
// -- which is the role, already decided by url-set membership. Doing
|
|
256
|
-
// it here rather than from a map built at stamping time is not a
|
|
257
|
-
// shortcut: a nil's operands are off the tree by the time the report
|
|
258
|
-
// is built, so a value first seen during the MEET (the commonest
|
|
259
|
-
// schema site there is) would be missing from any such map.
|
|
260
147
|
function displayOf(file: string, role: VetRole, prov: Prov): string {
|
|
261
148
|
return 'data' === role
|
|
262
149
|
? displayFile(file, prov.dataUrl ?? file, prov.dataPath)
|
|
@@ -264,50 +151,21 @@ function displayOf(file: string, role: VetRole, prov: Prov): string {
|
|
|
264
151
|
}
|
|
265
152
|
|
|
266
153
|
|
|
267
|
-
// The ROLE of a site: which of the two documents it belongs to. Not a
|
|
268
|
-
// name comparison -- a data document may itself include another file,
|
|
269
|
-
// and that file's values are still data. Membership of the url set the
|
|
270
|
-
// stamping walk collected is the question, and the answer is `schema`
|
|
271
|
-
// for anything the data walk never reached (an engine-minted value
|
|
272
|
-
// stamped with the schema entry, say).
|
|
273
154
|
function roleOf(file: string, prov: Prov): VetRole {
|
|
274
155
|
return prov.data.has(file) ? 'data' : 'schema'
|
|
275
156
|
}
|
|
276
157
|
|
|
277
158
|
|
|
278
|
-
// `$.a.b`, and `$` for the root. Deliberately NOT delimiter-escaped: a
|
|
279
|
-
// map key may contain any character, including every separator a
|
|
280
|
-
// compact summary might pick, so the path is carried as a JSON string
|
|
281
|
-
// and never parsed back out of a larger token.
|
|
282
159
|
function pathText(path?: string[]): string {
|
|
283
160
|
return '$' + (null != path && 0 < path.length ? '.' + path.join('.') : '')
|
|
284
161
|
}
|
|
285
162
|
|
|
286
163
|
|
|
287
|
-
// `secondary` is the only operand that can be absent — a `closed` or an
|
|
288
|
-
// incomplete finding has one side, a two-site conflict has both — so
|
|
289
|
-
// this is the one nullable input, and every Val that does arrive
|
|
290
|
-
// carries a site and a canon.
|
|
291
164
|
function siteOf(v: any, prov: Prov): VetSite | undefined {
|
|
292
165
|
if (null == v) {
|
|
293
166
|
return undefined
|
|
294
167
|
}
|
|
295
|
-
// The report's `file` is whatever the site carries, and by the time a
|
|
296
|
-
// site reaches a report that is always a stamped name: vet walks both
|
|
297
|
-
// documents before they meet, and the walk reaches the off-peg values
|
|
298
|
-
// a finding can name (ts/src/walk.ts). A consumer therefore reads
|
|
299
|
-
// `file` without a presence check, and the Go port -- whose field is
|
|
300
|
-
// a plain string -- writes the same key.
|
|
301
|
-
//
|
|
302
|
-
// NOT coalesced. The parser leaves the url undefined on one of its
|
|
303
|
-
// two paths (ts/src/lang.ts), and a `?? ''` here would be dead code
|
|
304
|
-
// that hides it: if a value ever reaches a report unstamped, the two
|
|
305
|
-
// ports should disagree loudly rather than quietly agree on an empty
|
|
306
|
-
// name that neither of them meant.
|
|
307
168
|
const file = v.site.url
|
|
308
|
-
// NAMED for the reader, ROLED by the raw url: the two questions are
|
|
309
|
-
// different, and only the first is about how the file is spelled
|
|
310
|
-
// (see displayFile).
|
|
311
169
|
const role = roleOf(file, prov)
|
|
312
170
|
return {
|
|
313
171
|
file: displayOf(file, role, prov),
|
|
@@ -325,13 +183,6 @@ function siteOf(v: any, prov: Prov): VetSite | undefined {
|
|
|
325
183
|
// The underlying NilVal fields are untouched: this is a report-layer
|
|
326
184
|
// projection, so the existing error.tsv assertions do not move.
|
|
327
185
|
function sitesOf(nil: any, prov: Prov): VetSite[] {
|
|
328
|
-
// `?? nil`: a failure raised about a CONSTRUCT rather than about a
|
|
329
|
-
// failed meet -- a lossy integer literal, say -- carries no operands
|
|
330
|
-
// at all, and reporting it about ITSELF is what ctx.adderr already
|
|
331
|
-
// does for the same reason. Without the fallback the report built a
|
|
332
|
-
// site out of `undefined` and threw while partitioning it, which is
|
|
333
|
-
// the one thing vet promises not to do: a bad value in the data is
|
|
334
|
-
// DATA, and the caller gets a report.
|
|
335
186
|
const sites: VetSite[] = [siteOf(nil.primary ?? nil, prov) as VetSite]
|
|
336
187
|
|
|
337
188
|
const secondary = siteOf(nil.secondary, prov)
|
|
@@ -339,9 +190,6 @@ function sitesOf(nil: any, prov: Prov): VetSite[] {
|
|
|
339
190
|
sites.push(secondary)
|
|
340
191
|
}
|
|
341
192
|
|
|
342
|
-
// Partitioned rather than sorted: which of the two NilVal operands is
|
|
343
|
-
// `primary` follows source order within one document, which says
|
|
344
|
-
// nothing useful when one side is a schema and the other is data.
|
|
345
193
|
return [
|
|
346
194
|
...sites.filter((s) => 'data' === s.role),
|
|
347
195
|
...sites.filter((s) => 'schema' === s.role),
|
|
@@ -349,12 +197,6 @@ function sitesOf(nil: any, prov: Prov): VetSite[] {
|
|
|
349
197
|
}
|
|
350
198
|
|
|
351
199
|
|
|
352
|
-
// The message text is MATERIALISED on demand, exactly as handleErrors
|
|
353
|
-
// materialises one before a caller sees it: makeNilErr defers it
|
|
354
|
-
// because most NilVals are transient and never rendered, and only the
|
|
355
|
-
// throwing path asks for it. Without this a finding could carry an
|
|
356
|
-
// empty `message` -- which is what the incomplete half of every report
|
|
357
|
-
// did, and what any nil built during the PARSE of a document did.
|
|
358
200
|
function materialise(nil: any, ctx: any): void {
|
|
359
201
|
if (null == nil.msg || '' === nil.msg) {
|
|
360
202
|
descErr(nil, ctx)
|
|
@@ -379,32 +221,15 @@ function findingOf(nil: any, prov: Prov): VetFinding {
|
|
|
379
221
|
class: nil.class,
|
|
380
222
|
severity: 'error',
|
|
381
223
|
path: pathText(nil.path),
|
|
382
|
-
// The HEADLINE only, WITHOUT ANSI: the frames below it are for a
|
|
383
|
-
// human reading a terminal, and the first line is the part the two
|
|
384
|
-
// ports hold to byte parity. Materialised before this runs, so it
|
|
385
|
-
// is always there (see materialise above). The escapes matter for
|
|
386
|
-
// one family only -- a parse failure's text comes from the parser,
|
|
387
|
-
// which colours its marker -- and a machine-readable report is no
|
|
388
|
-
// place for terminal control codes.
|
|
389
224
|
message: stripAnsi(nil.msg.split('\n')[0]),
|
|
390
225
|
sites: sitesOf(nil, prov),
|
|
391
226
|
}
|
|
392
227
|
|
|
393
|
-
// The hint, whole, with its detail placeholders filled in exactly as
|
|
394
|
-
// the terminal frame fills them. Trailing whitespace is dropped
|
|
395
|
-
// because it is spacing for the frame that used to follow it, not
|
|
396
|
-
// part of the text; the deliberate blank lines INSIDE a hint are
|
|
397
|
-
// `\n \n` and survive.
|
|
398
228
|
const hint = getHint(nil.why, nil.details)
|
|
399
229
|
if (null != hint && '' !== hint) {
|
|
400
230
|
finding.hint = stripAnsi(hint).replace(/\s+$/, '')
|
|
401
231
|
}
|
|
402
232
|
|
|
403
|
-
// `expected`/`actual` are the admissible-alternatives contract, and
|
|
404
|
-
// the constraint algebra already produces them: G1's atoms attach the
|
|
405
|
-
// normalised residual and the offending value, and `must` attaches
|
|
406
|
-
// the author's message. Read them where they are rather than
|
|
407
|
-
// re-deriving them here.
|
|
408
233
|
if ('string' === typeof details.expected) {
|
|
409
234
|
finding.expected = details.expected
|
|
410
235
|
}
|
|
@@ -419,19 +244,6 @@ function findingOf(nil: any, prov: Prov): VetFinding {
|
|
|
419
244
|
}
|
|
420
245
|
|
|
421
246
|
|
|
422
|
-
// Findings are sorted BY VET, not by the walk. The underlying walk
|
|
423
|
-
// iterates raw object keys and the two hosts disagree about their order
|
|
424
|
-
// — `10:… 9:…` yields ["9","10"] in JavaScript, which hoists
|
|
425
|
-
// integer-like keys, against Go's insertion order (ts/src/keyorder.ts
|
|
426
|
-
// exists for exactly this) — so an unsorted report could never be in
|
|
427
|
-
// cross-port parity.
|
|
428
|
-
//
|
|
429
|
-
// The order is by data site (file, row, column), then code, then path.
|
|
430
|
-
// It is carried in ONE key string rather than a cascade of comparisons:
|
|
431
|
-
// the row and column are zero-padded so lexicographic order is numeric
|
|
432
|
-
// order, and NUL joins the fields because no field can contain one.
|
|
433
|
-
// A cascade would need a test per tie-breaker to stay honest; a key
|
|
434
|
-
// needs none, and cannot disagree with itself.
|
|
435
247
|
const ORDER_PAD = 9
|
|
436
248
|
|
|
437
249
|
|
|
@@ -440,12 +252,6 @@ function pad(n: number): string {
|
|
|
440
252
|
}
|
|
441
253
|
|
|
442
254
|
|
|
443
|
-
// The walk index is the LAST field, which makes every key unique and
|
|
444
|
-
// the sort below total: two findings can otherwise share everything the
|
|
445
|
-
// key carries — same data site, same code, same path — and a comparator
|
|
446
|
-
// that has to answer "equal" is one more thing to get right in two
|
|
447
|
-
// languages. With the index appended, ties simply keep walk order, in
|
|
448
|
-
// both ports, by construction rather than by the sort's promises.
|
|
449
255
|
function orderKey(f: VetFinding, index: number): string {
|
|
450
256
|
const site = f.sites[0]
|
|
451
257
|
return [
|
|
@@ -459,44 +265,11 @@ function orderKey(f: VetFinding, index: number): string {
|
|
|
459
265
|
}
|
|
460
266
|
|
|
461
267
|
|
|
462
|
-
// A DOCUMENT THAT DOES NOT STAND UP, in the finding shape (the
|
|
463
|
-
// review's finding F). `trim` and `relations` answered an unusable
|
|
464
|
-
// document with `verdict: error` and an EMPTY list: the caller learned
|
|
465
|
-
// that something was wrong and nothing about what, which is the one
|
|
466
|
-
// thing a repair loop cannot work with. Both verbs take ONE document,
|
|
467
|
-
// so there is no role to decide -- the document is the thing being
|
|
468
|
-
// checked and the thing to edit, which is what `data` means here.
|
|
469
|
-
//
|
|
470
|
-
// The engine's own first error IS the finding: these verbs add nothing
|
|
471
|
-
// to a diagnosis the evaluator already made, and the FIRST is enough
|
|
472
|
-
// because everything after it is a consequence.
|
|
473
|
-
//
|
|
474
|
-
// The Go twin is failureFinding in go/vet.go.
|
|
475
268
|
export function failureFinding(
|
|
476
269
|
ctx: any, url?: string, failed?: any): VetFinding {
|
|
477
|
-
// ctx.err IS SOMETIMES EMPTY, and the comment that used to stand here
|
|
478
|
-
// said otherwise (use-cases/BUGS.md §43). `&: id(root)` fails with a
|
|
479
|
-
// NIL ROOT and NO COLLECTED ERROR -- the id-spread refusal is the
|
|
480
|
-
// root itself -- and every verb that reports "this document does not
|
|
481
|
-
// stand up" then read `ctx.err[0]` as undefined and died: a TypeError
|
|
482
|
-
// out of `relations`, `reaches` and `jsonschema` in TypeScript, a
|
|
483
|
-
// panic in Go. The one shape where finding F's own invariant, that a
|
|
484
|
-
// document which does not stand up SAYS SO in the finding shape, was
|
|
485
|
-
// answered with a stack trace.
|
|
486
|
-
//
|
|
487
|
-
// `failed` is the caller's own root -- every caller has it, and its
|
|
488
|
-
// condition is `0 < ctx.err.length || root.isNil`, so when the first
|
|
489
|
-
// half is false the second holds and the root IS the reason.
|
|
490
270
|
const nil: any = ctx.err[0] ?? failed
|
|
491
271
|
materialise(nil, ctx)
|
|
492
272
|
|
|
493
|
-
// STAMPED, as vet stamps both documents before they meet: siteOf does
|
|
494
|
-
// not coalesce a missing name (deliberately -- see there), so a site
|
|
495
|
-
// that reached the report unstamped would carry `file: undefined`.
|
|
496
|
-
// The three Vals a finding can name are the nil and its two operands,
|
|
497
|
-
// and the url set collects whatever name each already had, so a value
|
|
498
|
-
// read from an included file keeps that file's name and still counts
|
|
499
|
-
// as part of the one document being checked.
|
|
500
273
|
const at = url ?? ''
|
|
501
274
|
const urls = new Set([at])
|
|
502
275
|
for (const v of [nil, nil.primary, nil.secondary]) {
|
|
@@ -522,28 +295,8 @@ export function anchorAt(root: any, at: string): Val | undefined {
|
|
|
522
295
|
|
|
523
296
|
let node: any = root
|
|
524
297
|
for (const part of parts) {
|
|
525
|
-
// A SIZING RESIDUE IS ITS CONTAINER, plus a note about what the
|
|
526
|
-
// container must still satisfy (use-cases/BUGS.md §16). The path
|
|
527
|
-
// steps through it: `$.a.ports.0.port` names the same node whether
|
|
528
|
-
// or not `ports` still carries a `unique()`, and an anchor that
|
|
529
|
-
// stopped here would report `no_path` for a key the document
|
|
530
|
-
// plainly has.
|
|
531
298
|
node = throughResidue(node)
|
|
532
299
|
|
|
533
|
-
// TYPE-DIRECTED, not a property lookup on whatever `peg` happens to
|
|
534
|
-
// be. An anchor is a STRUCTURAL path into the schema — the same
|
|
535
|
-
// thing a reference means by `$.a.b` — so it walks map keys and
|
|
536
|
-
// list indices, and stops at anything else.
|
|
537
|
-
//
|
|
538
|
-
// Indexing the peg generically walked much further than that: into
|
|
539
|
-
// a junction's branches (`a:1|2` with `--at $.a.0` validated
|
|
540
|
-
// against ONE branch), into a constraint's atom arguments (so
|
|
541
|
-
// `min(2)` with `--at $.a.0` reported the bound's own argument as
|
|
542
|
-
// the truth), into a pref's wrapped value through the literal key
|
|
543
|
-
// `peg`, and into an array's `length` — that last one handing back
|
|
544
|
-
// a JavaScript NUMBER as the anchor, after which every document
|
|
545
|
-
// whatsoever came back valid. The Go port has always been
|
|
546
|
-
// type-directed here; this is the canonical side moving to it.
|
|
547
300
|
if (true === node?.isMap) {
|
|
548
301
|
const peg = node.peg
|
|
549
302
|
if (null == peg || !Object.prototype.hasOwnProperty.call(peg, part)) {
|
|
@@ -568,34 +321,183 @@ export function anchorAt(root: any, at: string): Val | undefined {
|
|
|
568
321
|
}
|
|
569
322
|
}
|
|
570
323
|
|
|
571
|
-
// THE ANCHOR KEEPS ITS ATOM. Stepping THROUGH a residue is right --
|
|
572
|
-
// `$.x.a` names a key of the container whatever the container still
|
|
573
|
-
// has to satisfy -- but ARRIVING at one and handing back the bare
|
|
574
|
-
// container drops a constraint the author wrote, so `--at $.x` vetted
|
|
575
|
-
// clean against a `length` the evaluator enforces. The residue is the
|
|
576
|
-
// honest schema for the node: the meet drives it, and generation
|
|
577
|
-
// settles it, exactly as it does without an anchor.
|
|
578
324
|
return node
|
|
579
325
|
}
|
|
580
326
|
|
|
581
327
|
|
|
582
|
-
// The container inside a settled sizing residue, or the value itself.
|
|
583
|
-
// EXPORTED for the `doc` figure, which walks the same shape the anchor
|
|
584
|
-
// does: a list still carrying a `unique()` is a list, and a drawing
|
|
585
|
-
// that stopped at the residue would omit keys the document plainly
|
|
586
|
-
// has.
|
|
587
328
|
export function throughResidue(v: any): any {
|
|
588
329
|
return sizingResidue(v)?.bag ?? v
|
|
589
330
|
}
|
|
590
331
|
|
|
591
332
|
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
//
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
333
|
+
const COVER_TEMPLATE = '&'
|
|
334
|
+
|
|
335
|
+
|
|
336
|
+
// Is this value a bag with children to walk?
|
|
337
|
+
function coverKids(v: any): { key: string, val: any }[] {
|
|
338
|
+
const out: { key: string, val: any }[] = []
|
|
339
|
+
if (true === v.isMap && null != v.peg) {
|
|
340
|
+
for (const k of Object.keys(v.peg).sort(cmpCodePoint)) {
|
|
341
|
+
out.push({ key: k, val: v.peg[k] })
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
else if (true === v.isList && Array.isArray(v.peg)) {
|
|
345
|
+
v.peg.forEach((m: any, i: number) => out.push({ key: String(i), val: m }))
|
|
346
|
+
}
|
|
347
|
+
return out
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
|
|
351
|
+
function coverTemplate(v: any): any {
|
|
352
|
+
const cj = v?.spread?.cj
|
|
353
|
+
return null != cj && true === cj.isVal && true !== cj.isTop ? cj : undefined
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
|
|
357
|
+
function coverDeclare(
|
|
358
|
+
v: any, path: string[], out: Map<string, any>): void {
|
|
359
|
+
const tpl = coverTemplate(v)
|
|
360
|
+
if (null != tpl) {
|
|
361
|
+
const at = [...path, COVER_TEMPLATE]
|
|
362
|
+
out.set(pathText(at), tpl)
|
|
363
|
+
coverDeclare(tpl, at, out)
|
|
364
|
+
}
|
|
365
|
+
for (const { key, val } of coverKids(v)) {
|
|
366
|
+
const at = [...path, key]
|
|
367
|
+
out.set(pathText(at), val)
|
|
368
|
+
coverDeclare(val, at, out)
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
|
|
373
|
+
// Every path in the data, and whether it is a LEAF -- a node with no
|
|
374
|
+
// children, which is where a value lives.
|
|
375
|
+
function coverDataPaths(
|
|
376
|
+
v: any, path: string[],
|
|
377
|
+
out: { path: string, leaf: boolean }[]): void {
|
|
378
|
+
const kids = coverKids(v)
|
|
379
|
+
if (0 < path.length) {
|
|
380
|
+
out.push({ path: pathText(path), leaf: 0 === kids.length })
|
|
381
|
+
}
|
|
382
|
+
for (const { key, val } of kids) {
|
|
383
|
+
coverDataPaths(val, [...path, key], out)
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
|
|
388
|
+
// Walk one data path down the schema, naming the declaration that
|
|
389
|
+
// constrains it -- the exact key where the schema has one, else the
|
|
390
|
+
// covering template. Undefined when the schema declares nothing there.
|
|
391
|
+
function coverMatch(
|
|
392
|
+
anchor: any, segs: string[]): string | undefined {
|
|
393
|
+
let at: any = anchor
|
|
394
|
+
let decl = ''
|
|
395
|
+
for (const seg of segs) {
|
|
396
|
+
const named = true === at.isMap && null != at.peg ? at.peg[seg]
|
|
397
|
+
: true === at.isList && Array.isArray(at.peg) ? at.peg[Number(seg)]
|
|
398
|
+
: undefined
|
|
399
|
+
if (null != named && true === named.isVal) {
|
|
400
|
+
decl = '' === decl ? seg : decl + '.' + seg
|
|
401
|
+
at = named
|
|
402
|
+
continue
|
|
403
|
+
}
|
|
404
|
+
const tpl = coverTemplate(at)
|
|
405
|
+
if (null == tpl) {
|
|
406
|
+
return undefined
|
|
407
|
+
}
|
|
408
|
+
decl = '' === decl ? COVER_TEMPLATE : decl + '.' + COVER_TEMPLATE
|
|
409
|
+
at = tpl
|
|
410
|
+
}
|
|
411
|
+
return '$.' + decl
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
|
|
415
|
+
// The SHALLOWEST members of a set of paths: one whose parent is also in
|
|
416
|
+
// the set is covered by naming the parent, and naming both is noise.
|
|
417
|
+
// `render --coverage`'s `dead` is built on the same rule.
|
|
418
|
+
function coverShallowest(paths: string[]): string[] {
|
|
419
|
+
const held = new Set(paths)
|
|
420
|
+
return paths.filter((p) => {
|
|
421
|
+
for (let at = p; -1 !== at.lastIndexOf('.');) {
|
|
422
|
+
at = at.slice(0, at.lastIndexOf('.'))
|
|
423
|
+
if (held.has(at)) {
|
|
424
|
+
return false
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
return true
|
|
428
|
+
}).sort(cmpCodePoint)
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
|
|
432
|
+
// The accounting itself: what the schema declared, what the data holds,
|
|
433
|
+
// and which of each the other met.
|
|
434
|
+
export function vetCoverage(
|
|
435
|
+
anchor: any, dataVal: any, coverageAt?: string): VetCoverage {
|
|
436
|
+
const declarations = new Map<string, any>()
|
|
437
|
+
coverDeclare(anchor, [], declarations)
|
|
438
|
+
|
|
439
|
+
const dataPaths: { path: string, leaf: boolean }[] = []
|
|
440
|
+
coverDataPaths(dataVal, [], dataPaths)
|
|
441
|
+
|
|
442
|
+
// `--coverage-at` narrows the DATA side, which is the side a caller
|
|
443
|
+
// gating one subtree is asking about. The schema side follows from
|
|
444
|
+
// it: a declaration is unused only among the data actually measured.
|
|
445
|
+
const under = null == coverageAt ? undefined
|
|
446
|
+
: coverageAt.replace(/^\$\.?/, '')
|
|
447
|
+
const inScope = (p: string): boolean => {
|
|
448
|
+
if (null == under || '' === under) {
|
|
449
|
+
return true
|
|
450
|
+
}
|
|
451
|
+
const want = '$.' + under
|
|
452
|
+
return p === want || p.startsWith(want + '.')
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
const used = new Set<string>()
|
|
456
|
+
const unchecked: string[] = []
|
|
457
|
+
let checked = 0
|
|
458
|
+
let leaves = 0
|
|
459
|
+
for (const { path, leaf } of dataPaths) {
|
|
460
|
+
if (!inScope(path)) {
|
|
461
|
+
continue
|
|
462
|
+
}
|
|
463
|
+
const segs = path.replace(/^\$\.?/, '').split('.').filter((x) => '' !== x)
|
|
464
|
+
const decl = coverMatch(anchor, segs)
|
|
465
|
+
if (leaf) {
|
|
466
|
+
leaves++
|
|
467
|
+
}
|
|
468
|
+
if (null == decl) {
|
|
469
|
+
unchecked.push(path)
|
|
470
|
+
continue
|
|
471
|
+
}
|
|
472
|
+
used.add(decl)
|
|
473
|
+
if (leaf) {
|
|
474
|
+
checked++
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
// A declaration is met when it constrained a data path, or when a
|
|
479
|
+
// declaration BENEATH it was: `$.a` is used by `$.a.b` meeting
|
|
480
|
+
// `$.a.b`, and reporting the parent as unused would be false.
|
|
481
|
+
const unused: string[] = []
|
|
482
|
+
for (const decl of declarations.keys()) {
|
|
483
|
+
const covered = used.has(decl) ||
|
|
484
|
+
[...used].some((u) => u.startsWith(decl + '.'))
|
|
485
|
+
if (!covered) {
|
|
486
|
+
unused.push(decl)
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
return {
|
|
491
|
+
checked,
|
|
492
|
+
declared: declarations.size,
|
|
493
|
+
leaves,
|
|
494
|
+
unchecked: coverShallowest(unchecked),
|
|
495
|
+
unused: coverShallowest(unused),
|
|
496
|
+
vacuous: 0 === checked && 0 < leaves,
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
|
|
599
501
|
export function vet(
|
|
600
502
|
schemaSrc: string, dataSrc: string, opts?: VetOptions): VetReport {
|
|
601
503
|
const options = opts ?? {}
|
|
@@ -603,9 +505,6 @@ export function vet(
|
|
|
603
505
|
const dataUrl = options.dataUrl ?? DEFAULT_DATA_URL
|
|
604
506
|
const maxErrors = options.maxErrors ?? VET_MAX_ERRORS
|
|
605
507
|
|
|
606
|
-
// ONE instance, two bases: the path rides on each CALL rather than on
|
|
607
|
-
// the constructor, because the schema and the data may live in
|
|
608
|
-
// different directories (Lang.parse takes `opts.path` per parse).
|
|
609
508
|
const aontu = new Aontu(includeOpts(options))
|
|
610
509
|
const schemaOpts = null == options.schemaPath ?
|
|
611
510
|
undefined : { path: options.schemaPath }
|
|
@@ -617,34 +516,8 @@ export function vet(
|
|
|
617
516
|
const schemaCtx = aontu.ctx({ collect: true })
|
|
618
517
|
const schemaVal: any = aontu.unify(schemaSrc, schemaOpts, schemaCtx)
|
|
619
518
|
if (0 < schemaCtx.err.length || true === schemaVal?.isNil) {
|
|
620
|
-
// A broken schema REPORTS, exactly as broken data does. It used to
|
|
621
|
-
// answer `findings: []` with exit 4 and nothing else, in both
|
|
622
|
-
// ports: the engine had collected the fault and vet threw it away,
|
|
623
|
-
// so an agent -- or a person -- was told the schema was broken and
|
|
624
|
-
// not what or where. The verdict stays `error` (the fault is in
|
|
625
|
-
// the truth, not in the data, and that distinction is the whole
|
|
626
|
-
// point of the class), but the finding travels with it.
|
|
627
|
-
//
|
|
628
|
-
// The FIRST error only, and the data path's reasoning applies
|
|
629
|
-
// unchanged: later errors in a document that does not stand up are
|
|
630
|
-
// consequences of the first rather than separate things to fix.
|
|
631
|
-
//
|
|
632
|
-
// ONE OF THE TWO IS ALWAYS THERE, and both are nils: the branch
|
|
633
|
-
// condition admits a collected error or a nil root, and every
|
|
634
|
-
// value on `schemaCtx.err` is a NilVal. There is no third case, so
|
|
635
|
-
// there is no guard here -- a guard that cannot fire is dead code,
|
|
636
|
-
// and dead code is what ADR-002 exists to keep out. (One stood
|
|
637
|
-
// here and the TypeScript line report called it covered; the Go
|
|
638
|
-
// gate, which measures blocks, refused the twin.)
|
|
639
519
|
const failure: any =
|
|
640
520
|
0 < schemaCtx.err.length ? schemaCtx.err[0] : schemaVal
|
|
641
|
-
// The normal path stamps both documents before they meet
|
|
642
|
-
// (stampUrl(anchor...) below), and this early return never reaches
|
|
643
|
-
// it, so it stamps what it is about to report: the unified root,
|
|
644
|
-
// and the failure itself -- a COLLECTED error is minted during
|
|
645
|
-
// unification and hangs off no tree, so nothing else would name
|
|
646
|
-
// it. The walk reaches a failure's operands (ts/src/walk.ts),
|
|
647
|
-
// which is what makes the sites say which file.
|
|
648
521
|
stampUrl(schemaVal, schemaUrl)
|
|
649
522
|
stampUrl(failure, schemaUrl)
|
|
650
523
|
materialise(failure, schemaCtx)
|
|
@@ -662,11 +535,6 @@ export function vet(
|
|
|
662
535
|
if (null != options.at) {
|
|
663
536
|
anchor = anchorAt(schemaVal, options.at)
|
|
664
537
|
if (null == anchor) {
|
|
665
|
-
// AND IT SAYS WHICH SEGMENT. `--at` naming nothing is an error
|
|
666
|
-
// verdict for the same reason a broken schema is -- the run
|
|
667
|
-
// could not be set up from the truth's side -- and it reports
|
|
668
|
-
// for the same reason too: a caller handed exit 4 and an empty
|
|
669
|
-
// list has nothing to act on.
|
|
670
538
|
return {
|
|
671
539
|
verdict: 'error',
|
|
672
540
|
truncated: false,
|
|
@@ -680,22 +548,6 @@ export function vet(
|
|
|
680
548
|
const dataCtx = aontu.ctx({ collect: true })
|
|
681
549
|
const dataVal: any = aontu.parse(dataSrc, dataOpts, dataCtx)
|
|
682
550
|
if (0 < dataCtx.err.length || null == dataVal) {
|
|
683
|
-
// A DATA DOCUMENT THAT WILL NOT PARSE IS THE DATA'S FAULT, and the
|
|
684
|
-
// report says so: verdict `invalid`, with a finding carrying the
|
|
685
|
-
// parser's own code and a site in the data. `error` is left to mean
|
|
686
|
-
// what the exit table says it means -- the run could not be set up
|
|
687
|
-
// from the SCHEMA side.
|
|
688
|
-
//
|
|
689
|
-
// The engine already answered it this way one character earlier: a
|
|
690
|
-
// refused CONSTRUCT (`a: 9007199254740993`) reaches the tree as an
|
|
691
|
-
// ordinary nil and is reported as an invalid data finding. A stray
|
|
692
|
-
// `]` took the throwing path instead and came back as a broken
|
|
693
|
-
// SCHEMA -- the same fault, classified two opposite ways by which
|
|
694
|
-
// branch the parser happened to take.
|
|
695
|
-
//
|
|
696
|
-
// The FIRST error only: the parser stops at the first syntax error,
|
|
697
|
-
// so a second entry would be a consequence of the first rather than
|
|
698
|
-
// a separate thing to fix.
|
|
699
551
|
const failure = dataCtx.err[0]
|
|
700
552
|
if (null == failure) {
|
|
701
553
|
return { verdict: 'error', truncated: false, findings: [] }
|
|
@@ -708,17 +560,6 @@ export function vet(
|
|
|
708
560
|
findings: [findingOf(failure, { data: new Set([dataUrl]) })],
|
|
709
561
|
}
|
|
710
562
|
}
|
|
711
|
-
// STAMP THE WHOLE SETTLED SCHEMA, not just the lifted anchor.
|
|
712
|
-
// Without `--at` these are the same tree. With it, the anchor is a
|
|
713
|
-
// subtree and the rest of the schema is still REACHABLE from inside
|
|
714
|
-
// it -- a `%alias` declaration (`[&: %U]`, target `$.%U`) or a
|
|
715
|
-
// recursive residual's `$.spec.Step`, both of which the meet
|
|
716
|
-
// resolves through _fixroot (RefVal.find, RecurseVal.body). A node
|
|
717
|
-
// reached that way but never stamped carries no url, so its site
|
|
718
|
-
// named no file while excerpting the schema's text -- against the
|
|
719
|
-
// invariant that every site names the file whose text it shows
|
|
720
|
-
// (finding F, §25). stampUrl only fills a url that is EMPTY, so
|
|
721
|
-
// stamping the superset never renames a value read from an include.
|
|
722
563
|
stampUrl(schemaVal, schemaUrl)
|
|
723
564
|
const dataUrls = stampUrl(dataVal, dataUrl)
|
|
724
565
|
// The projection every site in this report goes through: roles by
|
|
@@ -729,31 +570,18 @@ export function vet(
|
|
|
729
570
|
dataUrl, dataPath: options.dataPath,
|
|
730
571
|
}
|
|
731
572
|
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
// "invalid against its own disjunct" and the enum-with-default idiom
|
|
745
|
-
// (`*'auto'|'literal'|'data'`) is sound as written. The lint is KEPT,
|
|
746
|
-
// as an advisory: a default admitted only because it is the default
|
|
747
|
-
// is also the exact shape of a typo'd default
|
|
748
|
-
// (`level:*wran|info|warn|debug` — the intended `*warn` would be
|
|
749
|
-
// silent), and nothing at meet time can catch that. The
|
|
750
|
-
// repeated-branch spelling (`*warn|warn|...`) states "the default is
|
|
751
|
-
// a first-class member", silences the lint, and — unlike before the
|
|
752
|
-
// gate — enforces exactly the same admitted set. The message names
|
|
753
|
-
// the REMAINING alternatives because that is what was scanned: the
|
|
754
|
-
// preferred branch itself always admits its own default, so the old
|
|
755
|
-
// wording ("any alternative of *5|string") read as false on its face
|
|
756
|
-
// (use-cases/BUGS.md §4).
|
|
573
|
+
let coverage: VetCoverage | undefined
|
|
574
|
+
if (true === options.coverage) {
|
|
575
|
+
const coverCtx = aontu.ctx({ collect: true })
|
|
576
|
+
const settledData: any = aontu.unify(dataSrc, dataOpts, coverCtx)
|
|
577
|
+
// A data document that does not stand alone is already reported by
|
|
578
|
+
// the meet below; here it falls back to what was parsed, which is
|
|
579
|
+
// the same paths minus whatever an include would have added.
|
|
580
|
+
const measured = 0 === coverCtx.err.length && true !== settledData?.isNil
|
|
581
|
+
? settledData : dataVal
|
|
582
|
+
coverage = vetCoverage(anchor, measured, options.coverageAt)
|
|
583
|
+
}
|
|
584
|
+
|
|
757
585
|
const lintFindings: VetFinding[] = []
|
|
758
586
|
walkBagVals(anchor, (v: any, path: string[]): void => {
|
|
759
587
|
if (true === v.isDisjunct && Array.isArray(v.peg)) {
|
|
@@ -790,45 +618,10 @@ export function vet(
|
|
|
790
618
|
}
|
|
791
619
|
})
|
|
792
620
|
|
|
793
|
-
// `--closed` sets the flag `close()` itself sets, rather than wrapping
|
|
794
|
-
// the anchor in a CloseFuncVal: the anchor is an already-evaluated
|
|
795
|
-
// tree, and a func value would have to resolve again to have any
|
|
796
|
-
// effect. A scalar anchor has no keys to close, so the flag is only
|
|
797
|
-
// meaningful on a bag.
|
|
798
621
|
if (true === options.closed && (true === anchor.isMap || true === anchor.isList)) {
|
|
799
622
|
anchor.closed = true
|
|
800
623
|
}
|
|
801
624
|
|
|
802
|
-
// THE MEET IS FROM A FRESH PARSE, NOT THE SETTLED SCHEMA (the
|
|
803
|
-
// review's finding C, use-cases/BUGS.md §15).
|
|
804
|
-
//
|
|
805
|
-
// Step 1 evaluated the schema ALONE, to decide whether it stands up
|
|
806
|
-
// before any data is blamed for it. That answer is a diagnosis, and
|
|
807
|
-
// it was also being used as the left side of the meet -- so every
|
|
808
|
-
// reference in the schema had already RESOLVED against the schema's
|
|
809
|
-
// own values and been replaced by them. `a:integer b:$.a` settled to
|
|
810
|
-
// `a:integer b:integer`, and data `{a:3,b:4}` then vetted VALID,
|
|
811
|
-
// while the same four lines as one document refuse with
|
|
812
|
-
// scalar_value. A reference is a statement about the FINAL model, and
|
|
813
|
-
// vet is asking about a model the data is part of.
|
|
814
|
-
//
|
|
815
|
-
// Parsing again is what makes `vet(S,D)` and `eval(S ∪ D)` the same
|
|
816
|
-
// question: the meet runs the fixpoint once, over both documents, so
|
|
817
|
-
// references, spreads and generators all see the data. Parsed trees
|
|
818
|
-
// are single-use, hence a second parse rather than a reuse of step
|
|
819
|
-
// 1's. The lint above still reads the SETTLED tree, where
|
|
820
|
-
// disjunctions are ranked and normalised.
|
|
821
|
-
//
|
|
822
|
-
// ONLY WHEN THERE IS NO `--at`. An anchor is a SUBTREE lifted out of
|
|
823
|
-
// the schema, and an absolute reference inside it (`$.OrderPlaced`,
|
|
824
|
-
// the discriminated-union idiom) names a sibling of the document
|
|
825
|
-
// root -- which the lifted subtree no longer has. The settled tree is
|
|
826
|
-
// where those references have already been resolved and substituted,
|
|
827
|
-
// so an anchored run keeps meeting that, exactly as it always has.
|
|
828
|
-
// Making the rule explicit rather than leaving it to whether
|
|
829
|
-
// anchorAt happens to find the path in an unresolved tree: the two
|
|
830
|
-
// ports answered that differently, which is an ADR-001 divergence
|
|
831
|
-
// waiting to happen.
|
|
832
625
|
const ctx = aontu.ctx({ collect: true })
|
|
833
626
|
let meetAnchor: any = anchor
|
|
834
627
|
if (null == options.at) {
|
|
@@ -844,39 +637,13 @@ export function vet(
|
|
|
844
637
|
}
|
|
845
638
|
}
|
|
846
639
|
else {
|
|
847
|
-
// A RECURSIVE residual inside the lifted anchor still names its
|
|
848
|
-
// definition by absolute path (`then?: $.spec.Step` -- the
|
|
849
|
-
// fixpoint, RECURSION.0.md), and the meet's root is the anchored
|
|
850
|
-
// subtree, which does not contain `$.spec`. Without a tree to
|
|
851
|
-
// walk, the residual held its peer forever and everything under a
|
|
852
|
-
// recursive field vetted VALID unchecked. The settled schema root
|
|
853
|
-
// is kept on the meet context for exactly that walk
|
|
854
|
-
// (AontuContext._fixroot; RecurseVal.body and RefVal.find fall
|
|
855
|
-
// back to it).
|
|
856
640
|
; (ctx as any)._fixroot = schemaVal
|
|
857
|
-
// And the meet DRIVES AT the anchor's own path, so every finding
|
|
858
|
-
// minted live during it sits in the schema's namespace
|
|
859
|
-
// ($.spec.Step.then.approver), exactly where the findings carried
|
|
860
|
-
// on the settled anchor's stored paths already sit -- the Go port
|
|
861
|
-
// gets this for free from its clone discipline, and the shared
|
|
862
|
-
// anchored-vet rows pin the agreement.
|
|
863
641
|
; (ctx as any).path = options.at.replace(/^\$\.?/, '')
|
|
864
642
|
.split('.').filter((s: string) => '' !== s)
|
|
865
643
|
}
|
|
866
644
|
const pair = new ConjunctVal({ peg: [meetAnchor, dataVal] }, ctx)
|
|
867
645
|
const unified: any = aontu.unify(pair, undefined, ctx)
|
|
868
646
|
|
|
869
|
-
// 4. Contradictions: every NilVal standing in the result, PLUS the
|
|
870
|
-
// ones that never made it into the tree.
|
|
871
|
-
//
|
|
872
|
-
// The second half is not belt-and-braces. When a parent collapses to
|
|
873
|
-
// a nil the whole subtree goes with it, so `service: close({...})`
|
|
874
|
-
// meeting a typo AND a kind conflict leaves ONE nil in the tree and
|
|
875
|
-
// reports the other only on the context — the vet verb's own
|
|
876
|
-
// motivating example, reporting half of what it found. The language
|
|
877
|
-
// server already walks both for this reason; vet dedups by identity
|
|
878
|
-
// the same way, and skips the transient disjunct-trial sentinel,
|
|
879
|
-
// which is bookkeeping rather than a finding.
|
|
880
647
|
const seen = new Set<any>()
|
|
881
648
|
const nils: any[] = collectNils(unified, seen)
|
|
882
649
|
for (const err of ctx.err) {
|
|
@@ -891,45 +658,17 @@ export function vet(
|
|
|
891
658
|
return findingOf(n, prov)
|
|
892
659
|
})
|
|
893
660
|
|
|
894
|
-
// 5. Incompleteness: what is left standing that cannot generate. The
|
|
895
|
-
// generate check runs in its own collect context so nothing it
|
|
896
|
-
// raises reaches the caller's error list, and so a schema that is
|
|
897
|
-
// merely unsatisfied does not look like one that is contradicted.
|
|
898
|
-
// No try/catch: in collect mode `gen` records its reasons on the
|
|
899
|
-
// context instead of throwing, which is the whole point of the mode.
|
|
900
661
|
const genCtx: any = aontu.ctx({ collect: true })
|
|
901
662
|
genCtx.root = unified
|
|
902
|
-
// Under `--at` the probe descends through the OUTPUT marks: the
|
|
903
|
-
// caller named this node as the truth to validate against, so a
|
|
904
|
-
// `type()` or `hide()` on it (or propagated into it) is not a reason
|
|
905
|
-
// to check nothing. See AontuContext.probe.
|
|
906
663
|
genCtx.probe = null != options.at
|
|
907
664
|
unified.gen(genCtx)
|
|
908
665
|
for (const err of genCtx.err) {
|
|
909
|
-
// A CONFLICT RAISED AT GENERATION COUNTS TOO (the review's finding
|
|
910
|
-
// C, use-cases/BUGS.md §16). The filter used to keep the
|
|
911
|
-
// `incomplete` class alone, on the reading that step 4 had already
|
|
912
|
-
// found every contradiction -- true while every conflict was
|
|
913
|
-
// decided during the meet, and untrue since a sizing atom or a
|
|
914
|
-
// container `must` may hold a PROVISIONAL reading until generation,
|
|
915
|
-
// which is where no more members can arrive. Dropping those left
|
|
916
|
-
// `vet` answering `valid` for data the evaluator refuses, which is
|
|
917
|
-
// the one disagreement the vet-equals-eval harness exists to catch
|
|
918
|
-
// -- and did.
|
|
919
|
-
//
|
|
920
|
-
// Deduped against step 4 by the same cause key the loop below uses,
|
|
921
|
-
// so a contradiction seen twice is still reported once.
|
|
922
666
|
if ('incomplete' === err.class || 'conflict' === err.class) {
|
|
923
667
|
materialise(err, genCtx)
|
|
924
668
|
findings.push(findingOf(err, prov))
|
|
925
669
|
}
|
|
926
670
|
}
|
|
927
671
|
|
|
928
|
-
// 5b. Deprecation warnings (G3 phase 4): a value that carries the
|
|
929
|
-
// deprecate() record after the meet was USED — the data met a
|
|
930
|
-
// deprecated schema value, or the schema's own default will
|
|
931
|
-
// generate one. Severity `warning` (the slot G2 reserved for
|
|
932
|
-
// exactly this mark), and warnings never touch the verdict below.
|
|
933
672
|
findings.push(...lintFindings)
|
|
934
673
|
for (const { val, path } of collectDeprecations(unified)) {
|
|
935
674
|
const v: any = val
|
|
@@ -959,27 +698,6 @@ export function vet(
|
|
|
959
698
|
keyed.sort((a, b) => a.key < b.key ? -1 : 1)
|
|
960
699
|
let ordered = keyed.map((k) => k.finding)
|
|
961
700
|
|
|
962
|
-
// ONE CAUSE, ONE FINDING. A reference resolves by CLONING its target,
|
|
963
|
-
// so a target that later fails can fail once per referrer — same
|
|
964
|
-
// code, same two source sites, a different path each time. Multi-pass
|
|
965
|
-
// collection (G2 phase 6) made this reachable: the pass loop now
|
|
966
|
-
// continues past the erroring pass, so the clones' own folds run too.
|
|
967
|
-
// The dedup key is the CODE plus the SITES (file, row, col, value,
|
|
968
|
-
// role): two findings that name the same meet of the same two source
|
|
969
|
-
// positions are one contradiction observed from two paths. The key is
|
|
970
|
-
// NOT (code, path) — the design's sketch — because the paths are
|
|
971
|
-
// exactly what differ. Sorted order makes the kept finding the first
|
|
972
|
-
// by data site then path, deterministically in both ports.
|
|
973
|
-
//
|
|
974
|
-
// THE KEPT PATH IS THE DEEPEST one (use-cases/BUGS.md §41). A meet
|
|
975
|
-
// that fails inside a REFERENCED map is recorded twice: once at the
|
|
976
|
-
// key that actually conflicts, and once at the enclosing map, which
|
|
977
|
-
// collapsed as a consequence and carries the child's two sites. Both
|
|
978
|
-
// are the same cause; only the deeper one names the field an author
|
|
979
|
-
// or an agent has to edit, and `$.q` for a conflict in `$.q.a` sent a
|
|
980
|
-
// repair loop to rewrite the whole record -- twice over, identically,
|
|
981
|
-
// when two of its fields conflicted. Depth first, then the sort order
|
|
982
|
-
// above, so the choice stays deterministic in both ports.
|
|
983
701
|
const causeKey = (f: VetFinding): string =>
|
|
984
702
|
f.code + '\u0000' + f.sites.map((s) =>
|
|
985
703
|
[s.file, s.row, s.col, s.role, s.value].join('\u0000')).join('\u0000')
|
|
@@ -1005,22 +723,6 @@ export function vet(
|
|
|
1005
723
|
const truncated = maxErrors < ordered.length
|
|
1006
724
|
const kept = truncated ? ordered.slice(0, maxErrors) : ordered
|
|
1007
725
|
|
|
1008
|
-
// 6. The verdict derives from finding CLASSES, never from codes, so a
|
|
1009
|
-
// new code can never change exit behaviour.
|
|
1010
|
-
//
|
|
1011
|
-
// BY CLASS, NOT BY STAGE. The split used to be positional -- whatever
|
|
1012
|
-
// step 4 found counted as contradiction and whatever step 5 added
|
|
1013
|
-
// counted as incompleteness -- which stopped being true when a sizing
|
|
1014
|
-
// atom or a container `must` began holding a provisional reading
|
|
1015
|
-
// until generation (the review's finding C, use-cases/BUGS.md §16). A
|
|
1016
|
-
// CONTRADICTION found at generation is still a contradiction: reading
|
|
1017
|
-
// it as mere incompleteness answered `incomplete` where the evaluator
|
|
1018
|
-
// refuses, and `vet` and `eval` have to agree.
|
|
1019
|
-
//
|
|
1020
|
-
// So: an error-severity finding that is not INCOMPLETENESS makes the
|
|
1021
|
-
// document invalid, wherever it was found -- a contradiction, a parse
|
|
1022
|
-
// refusal, an unresolvable reference alike. Warnings (the `compat`
|
|
1023
|
-
// class: lint and deprecation) never touch the verdict.
|
|
1024
726
|
let verdict: VetVerdict = 'valid'
|
|
1025
727
|
const errors = ordered.filter((f) => 'error' === f.severity)
|
|
1026
728
|
const unmet = errors.filter((f) => 'incomplete' === f.class).length
|
|
@@ -1031,5 +733,8 @@ export function vet(
|
|
|
1031
733
|
verdict = 'incomplete'
|
|
1032
734
|
}
|
|
1033
735
|
|
|
1034
|
-
return {
|
|
736
|
+
return {
|
|
737
|
+
verdict, truncated, findings: kept,
|
|
738
|
+
...(null == coverage ? {} : { coverage }),
|
|
739
|
+
}
|
|
1035
740
|
}
|