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/relation.ts
ADDED
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
/* Copyright (c) 2025 Richard Rodger, MIT License */
|
|
2
|
+
|
|
3
|
+
// RELATION GRAPH VERDICTS (RELATIONS.0.md §3.3, replacing the G4
|
|
4
|
+
// phase 5 magic-key pass): acyclicity and inverse consistency over
|
|
5
|
+
// the edge set, DECLARED by the graph atoms -- `acyclic()` and
|
|
6
|
+
// `inverse(name)` conjoined at the field whose key is the predicate
|
|
7
|
+
// -- and decided AFTER unification, never by it.
|
|
8
|
+
//
|
|
9
|
+
// Why not in the lattice. Both properties are GLOBAL and NON-MONOTONE:
|
|
10
|
+
// an acyclic graph becomes cyclic when one more edge unifies in, and an
|
|
11
|
+
// inverse that is present becomes absent when the far side is narrowed.
|
|
12
|
+
// The lattice guarantee is that more information never falsifies what
|
|
13
|
+
// has been observed, so a constraint that could be true and then false
|
|
14
|
+
// is not a constraint the lattice may hold. These are facts about the
|
|
15
|
+
// finished model, so the atoms only REGISTER during unification
|
|
16
|
+
// (GraphAtomVal.register, onto ctx._reldecls), and the verdict lands
|
|
17
|
+
// at GENERATION -- the sizing atoms' model -- where no more
|
|
18
|
+
// information can arrive. The `relations` verb reports the same
|
|
19
|
+
// verdict from the same declarations: one decision, two surfaces.
|
|
20
|
+
//
|
|
21
|
+
// The old declaration channel -- a `relations:` key at the document
|
|
22
|
+
// root, read by name -- is GONE, discharging ADR-010's grandfather
|
|
23
|
+
// clause: the engine no longer knows any spellable key. The target
|
|
24
|
+
// half of the old declaration is `rel(t)`'s flow at the site, which
|
|
25
|
+
// checks by unifying rather than by a report-layer probe.
|
|
26
|
+
|
|
27
|
+
import { Aontu } from './aontu'
|
|
28
|
+
import { failureFinding } from './vet'
|
|
29
|
+
import type { VetFinding } from './vet'
|
|
30
|
+
import type { TrustOptions } from './type'
|
|
31
|
+
import { graphOf } from './graph'
|
|
32
|
+
import type { Edge, Graph } from './graph'
|
|
33
|
+
import { cmpCodePoint } from './keyorder'
|
|
34
|
+
import { makeNilErr } from './err'
|
|
35
|
+
import type { RelDecl } from './val/GraphAtomVal'
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
export type RelationVerdict = 'pass' | 'fail' | 'error'
|
|
39
|
+
|
|
40
|
+
export type RelationFinding = {
|
|
41
|
+
code: string
|
|
42
|
+
// The relation the finding is about.
|
|
43
|
+
relation: string
|
|
44
|
+
// Where the offending edge is written, as a `$.dotted.path`.
|
|
45
|
+
at: string
|
|
46
|
+
// For a cycle, the node paths it runs through, in the order the walk
|
|
47
|
+
// found them, closing back on the first. For a missing inverse, the
|
|
48
|
+
// two ends and the relation that should have mirrored it.
|
|
49
|
+
detail: string[]
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export type RelationReport = {
|
|
53
|
+
verdict: RelationVerdict
|
|
54
|
+
findings: RelationFinding[]
|
|
55
|
+
|
|
56
|
+
// WHY the graph could not be looked at, in the same finding shape
|
|
57
|
+
// vet reports in (the review's finding F). `findings` is about the
|
|
58
|
+
// GRAPH and stays that way; a document that does not stand up has no
|
|
59
|
+
// graph to have findings about, and an `error` verdict used to
|
|
60
|
+
// arrive with an empty list -- something is wrong, and nothing about
|
|
61
|
+
// what. Present ONLY on an `error` verdict.
|
|
62
|
+
errors?: VetFinding[]
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export type RelationOptions = {
|
|
66
|
+
// Where the document CAME FROM, so a relative `@"file"` load inside
|
|
67
|
+
// it resolves from its own directory (trimCheck's precedent).
|
|
68
|
+
path?: string
|
|
69
|
+
// The include capability this document evaluates under (G5,
|
|
70
|
+
// docs/trust.md). vet's precedent: the verb passes the profile the
|
|
71
|
+
// caller asked for, and an absent option means today's default.
|
|
72
|
+
trust?: TrustOptions
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
// The first cycle reachable from `start`, as the node paths it runs
|
|
77
|
+
// through, or undefined. Depth-first with the path as the stack, and
|
|
78
|
+
// the successors visited in sorted order, so the cycle a report names
|
|
79
|
+
// is the same one in both ports.
|
|
80
|
+
function findCycle(
|
|
81
|
+
start: string,
|
|
82
|
+
succ: Map<string, string[]>,
|
|
83
|
+
done: Set<string>,
|
|
84
|
+
): string[] | undefined {
|
|
85
|
+
const stack: string[] = []
|
|
86
|
+
const onStack = new Set<string>()
|
|
87
|
+
|
|
88
|
+
const walk = (node: string): string[] | undefined => {
|
|
89
|
+
if (onStack.has(node)) {
|
|
90
|
+
return [...stack.slice(stack.indexOf(node)), node]
|
|
91
|
+
}
|
|
92
|
+
if (done.has(node)) {
|
|
93
|
+
return undefined
|
|
94
|
+
}
|
|
95
|
+
done.add(node)
|
|
96
|
+
stack.push(node)
|
|
97
|
+
onStack.add(node)
|
|
98
|
+
for (const next of succ.get(node) ?? []) {
|
|
99
|
+
const found = walk(next)
|
|
100
|
+
if (undefined !== found) {
|
|
101
|
+
return found
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
stack.pop()
|
|
105
|
+
onStack.delete(node)
|
|
106
|
+
return undefined
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return walk(start)
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
// The verdict itself, pure over what the evaluation produced: the
|
|
114
|
+
// registered declarations and the edge set. Shared by the generation
|
|
115
|
+
// hook (relationErrors) and the `relations` verb, so the two surfaces
|
|
116
|
+
// cannot disagree.
|
|
117
|
+
export function relationFindings(
|
|
118
|
+
decls: Map<string, RelDecl>, graph: Graph
|
|
119
|
+
): RelationFinding[] {
|
|
120
|
+
const findings: RelationFinding[] = []
|
|
121
|
+
|
|
122
|
+
// The edge set, indexed the two ways the checks read it.
|
|
123
|
+
const byRelation = new Map<string, Edge[]>()
|
|
124
|
+
const pairs = new Set<string>()
|
|
125
|
+
for (const e of graph.edges) {
|
|
126
|
+
const list = byRelation.get(e.key)
|
|
127
|
+
if (undefined === list) {
|
|
128
|
+
byRelation.set(e.key, [e])
|
|
129
|
+
}
|
|
130
|
+
else {
|
|
131
|
+
list.push(e)
|
|
132
|
+
}
|
|
133
|
+
pairs.add(e.key + ' ' + e.from + ' ' + e.to)
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Predicates in sorted order, so the findings arrive the same way
|
|
137
|
+
// in both ports (the registry is insertion-ordered here, random in
|
|
138
|
+
// Go).
|
|
139
|
+
const names = [...decls.keys()].sort(cmpCodePoint)
|
|
140
|
+
for (const name of names) {
|
|
141
|
+
const decl = decls.get(name) as RelDecl
|
|
142
|
+
const mine = byRelation.get(name) ?? []
|
|
143
|
+
|
|
144
|
+
if (true === decl.acyclic) {
|
|
145
|
+
const succ = new Map<string, string[]>()
|
|
146
|
+
for (const e of mine) {
|
|
147
|
+
const list = succ.get(e.from)
|
|
148
|
+
const to = e.to
|
|
149
|
+
if (undefined === list) {
|
|
150
|
+
succ.set(e.from, [to])
|
|
151
|
+
}
|
|
152
|
+
else {
|
|
153
|
+
list.push(to)
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
for (const list of succ.values()) {
|
|
157
|
+
list.sort(cmpCodePoint)
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// The roots are visited in sorted order, and a node already
|
|
161
|
+
// settled is not revisited, so one cycle is reported once and the
|
|
162
|
+
// SAME one in both ports.
|
|
163
|
+
const done = new Set<string>()
|
|
164
|
+
const roots = [...succ.keys()].sort(cmpCodePoint)
|
|
165
|
+
for (const from of roots) {
|
|
166
|
+
const cycle = findCycle(from, succ, done)
|
|
167
|
+
if (undefined !== cycle) {
|
|
168
|
+
// The cycle's first node is a key of `succ`, and every key of
|
|
169
|
+
// `succ` came from an edge's `from`, so the edge is there.
|
|
170
|
+
const at = mine.find((e) => e.from === cycle[0]) as Edge
|
|
171
|
+
findings.push({
|
|
172
|
+
code: 'relation_cycle',
|
|
173
|
+
relation: name,
|
|
174
|
+
at: at.at,
|
|
175
|
+
detail: cycle,
|
|
176
|
+
})
|
|
177
|
+
break
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const inverses = [...decl.inverses].sort(cmpCodePoint)
|
|
183
|
+
for (const inv of inverses) {
|
|
184
|
+
for (const e of mine) {
|
|
185
|
+
const to = e.to
|
|
186
|
+
if (!pairs.has(inv + ' ' + to + ' ' + e.from)) {
|
|
187
|
+
findings.push({
|
|
188
|
+
code: 'relation_inverse_missing',
|
|
189
|
+
relation: name,
|
|
190
|
+
at: e.at,
|
|
191
|
+
detail: [e.from, to, inv],
|
|
192
|
+
})
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// SORTED, because a report is read by a machine that diffs it: by the
|
|
199
|
+
// position the offending edge is written at, then by code, then by
|
|
200
|
+
// the detail (two inverse declarations on one predicate can flag one
|
|
201
|
+
// edge twice). The sort is STABLE and the predicates were iterated
|
|
202
|
+
// in sorted order, so what order remains is fixed anyway.
|
|
203
|
+
findings.sort((a, b) =>
|
|
204
|
+
cmpCodePoint(a.at, b.at) || cmpCodePoint(a.code, b.code)
|
|
205
|
+
|| cmpCodePoint(a.detail.join(' '), b.detail.join(' ')))
|
|
206
|
+
|
|
207
|
+
return findings
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
// The generation hook (Aontu.generate, between unification success
|
|
212
|
+
// and value generation): each finding becomes a LOCATED evaluation
|
|
213
|
+
// error at the offending edge, exactly as an unmet sizing atom
|
|
214
|
+
// refuses at generation. Findings name node paths and positions the
|
|
215
|
+
// document spelled, so the walk to the site cannot miss.
|
|
216
|
+
export function relationErrors(ctx: any, root: any): void {
|
|
217
|
+
const decls: Map<string, RelDecl> = ctx._reldecls
|
|
218
|
+
if (0 === decls.size) {
|
|
219
|
+
return
|
|
220
|
+
}
|
|
221
|
+
const findings = relationFindings(decls, graphOf(root))
|
|
222
|
+
for (const f of findings) {
|
|
223
|
+
let node: any = root
|
|
224
|
+
for (const seg of f.at.slice(2).split('.')) {
|
|
225
|
+
// Graph atoms hold the field's value -- possibly nested, one
|
|
226
|
+
// atom carrying another -- and the path steps through them
|
|
227
|
+
// exactly as the graph walk does.
|
|
228
|
+
while (true === node?.isGraphAtom) {
|
|
229
|
+
node = node.held
|
|
230
|
+
}
|
|
231
|
+
node = node?.peg?.[seg]
|
|
232
|
+
}
|
|
233
|
+
// No unwrap AFTER the walk: a finding's `at` names an edge
|
|
234
|
+
// element, and an edge's element is a string -- an atom-wrapped
|
|
235
|
+
// element mints no edge in the first place (the graph visit
|
|
236
|
+
// descends atoms only at field values), so the walk cannot end on
|
|
237
|
+
// an atom.
|
|
238
|
+
ctx.adderr(makeNilErr(ctx, f.code, node, undefined, 'relate', {
|
|
239
|
+
relation: f.relation,
|
|
240
|
+
detail: f.detail.join(' -> '),
|
|
241
|
+
}))
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
// The relation checks for one document: evaluate, then report the
|
|
247
|
+
// same verdict generation enforces.
|
|
248
|
+
export function relationCheck(
|
|
249
|
+
src: string, opts?: RelationOptions): RelationReport {
|
|
250
|
+
const options = opts ?? {}
|
|
251
|
+
const aontu = new Aontu(
|
|
252
|
+
null == options.trust ? undefined : { trust: options.trust })
|
|
253
|
+
const ctx = aontu.ctx({ collect: true })
|
|
254
|
+
const parseOpts = null == options.path ? undefined : { path: options.path }
|
|
255
|
+
const root: any = aontu.unify(src, parseOpts, ctx)
|
|
256
|
+
|
|
257
|
+
// A document that does not stand up is not a document with a bad
|
|
258
|
+
// graph: the errors it already has are the answer, and blaming its
|
|
259
|
+
// relations on top would be noise.
|
|
260
|
+
if (0 < ctx.err.length || true === root?.isNil) {
|
|
261
|
+
return {
|
|
262
|
+
verdict: 'error',
|
|
263
|
+
findings: [],
|
|
264
|
+
errors: [failureFinding(ctx, options.path, root)],
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
const decls: Map<string, RelDecl> = (ctx as any)._reldecls
|
|
269
|
+
if (0 === decls.size) {
|
|
270
|
+
return { verdict: 'pass', findings: [] }
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// NOR IS A DOCUMENT THAT CANNOT BE GENERATED. Unification can
|
|
274
|
+
// succeed over a tree that still holds an unsettled disjunction --
|
|
275
|
+
// more than one alternative admitted, which `disjunct_no_gen`
|
|
276
|
+
// refuses at generation -- and the graph walk cannot read inside
|
|
277
|
+
// one: the links in every alternative are invisible, so a mirror
|
|
278
|
+
// the document plainly writes is missing from the edge set and
|
|
279
|
+
// `inverse(n)` calls it missing. Generation is therefore asked
|
|
280
|
+
// FIRST, on a collecting context of its own, and what it says is
|
|
281
|
+
// the answer. Generation is what the atoms' verdict lands at, so
|
|
282
|
+
// this is the same order the engine itself now runs in.
|
|
283
|
+
const gctx = ctx.clone({ err: [], collect: true })
|
|
284
|
+
root.gen(gctx)
|
|
285
|
+
if (0 < gctx.err.length) {
|
|
286
|
+
return {
|
|
287
|
+
verdict: 'error',
|
|
288
|
+
findings: [],
|
|
289
|
+
errors: [failureFinding(gctx, options.path, root)],
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
const findings = relationFindings(decls, graphOf(root))
|
|
294
|
+
return {
|
|
295
|
+
verdict: 0 === findings.length ? 'pass' : 'fail',
|
|
296
|
+
findings,
|
|
297
|
+
}
|
|
298
|
+
}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
/* Copyright (c) 2025 Richard Rodger, MIT License */
|
|
2
|
+
|
|
3
|
+
// SARIF rendering for a vet report (G2 phase 5,
|
|
4
|
+
// docs/capability-review/g2-validation-verb.md).
|
|
5
|
+
//
|
|
6
|
+
// A MINIMAL SARIF 2.1.0 profile, and deliberately nothing more: one
|
|
7
|
+
// run, one `result` per finding, the finding's first site as the
|
|
8
|
+
// primary location, its remaining sites under `relatedLocations`, and
|
|
9
|
+
// the whole finding object embedded in `properties` so a SARIF consumer
|
|
10
|
+
// still holds the native contract. No fixes, no code flows, no
|
|
11
|
+
// baselines — the JSON report is the native contract, SARIF is the
|
|
12
|
+
// interchange skin CI systems already ingest (GitHub code scanning
|
|
13
|
+
// uploads, PR annotation).
|
|
14
|
+
//
|
|
15
|
+
// This is library API, not CLI plumbing, for the same reason the vet
|
|
16
|
+
// engine is: an embedder (and G7's MCP server later) must be able to
|
|
17
|
+
// emit the interchange form without shelling out. The Go twin is
|
|
18
|
+
// go/report_sarif.go, and the two are held to byte parity over the
|
|
19
|
+
// shared fixture pair in test/spec/files/vet-sarif/ — with the message
|
|
20
|
+
// text and producer version redacted, exactly as test/spec/vet.tsv
|
|
21
|
+
// carves the message out of its goldens, because prose and the two
|
|
22
|
+
// independent version series are deliberately not in cross-port parity.
|
|
23
|
+
|
|
24
|
+
import type { VetReport, VetFinding, VetSite } from './vet'
|
|
25
|
+
|
|
26
|
+
import { exactJSON } from './exactjson'
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
// SARIF levels are error/warning/note; the report's severities are
|
|
30
|
+
// error/warning/info. Only `info` needs translating, but the map spells
|
|
31
|
+
// out all three so a new severity fails loudly here rather than
|
|
32
|
+
// silently emitting itself.
|
|
33
|
+
const SARIF_LEVEL: Record<string, string> = {
|
|
34
|
+
error: 'error',
|
|
35
|
+
warning: 'warning',
|
|
36
|
+
info: 'note',
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
// A site's file is a filesystem path, and a SARIF artifactLocation.uri
|
|
41
|
+
// is a URI reference: `#`, `%`, spaces and every other URI-significant
|
|
42
|
+
// character must be percent-encoded or a consumer parses the path as
|
|
43
|
+
// something else (text after `#` becomes a fragment). Encoded BY BYTE
|
|
44
|
+
// over UTF-8, with RFC 3986's unreserved and path characters kept
|
|
45
|
+
// literal, so the Go twin produces identical bytes from an identical
|
|
46
|
+
// loop (go/report_sarif.go sarifURI).
|
|
47
|
+
function sarifUri(path: string): string {
|
|
48
|
+
const bytes = Buffer.from(path, 'utf8')
|
|
49
|
+
let out = ''
|
|
50
|
+
for (const b of bytes) {
|
|
51
|
+
const c = String.fromCharCode(b)
|
|
52
|
+
if (b < 128 && /[A-Za-z0-9\-._~/!$&'()*+,;=:@]/.test(c)) {
|
|
53
|
+
out += c
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
out += '%' + b.toString(16).toUpperCase().padStart(2, '0')
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return out
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
function sarifLocation(site: VetSite): any {
|
|
64
|
+
const physical: any = {
|
|
65
|
+
artifactLocation: { uri: sarifUri(site.file) },
|
|
66
|
+
}
|
|
67
|
+
// SARIF regions are 1-based. A finding with no position — a parse
|
|
68
|
+
// failure reports at -1:-1 — gets a location with no region rather
|
|
69
|
+
// than an invented one.
|
|
70
|
+
if (1 <= site.row) {
|
|
71
|
+
physical.region = { startColumn: site.col, startLine: site.row }
|
|
72
|
+
}
|
|
73
|
+
return { physicalLocation: physical }
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
function sarifResult(finding: VetFinding): any {
|
|
78
|
+
// The engine orders sites data-first (the thing to fix), so the first
|
|
79
|
+
// site is the primary location and the rest are related — which for a
|
|
80
|
+
// two-site conflict puts the schema's declaration under
|
|
81
|
+
// `relatedLocations`, exactly where a code-scanning UI shows "the
|
|
82
|
+
// other side".
|
|
83
|
+
const result: any = {
|
|
84
|
+
level: SARIF_LEVEL[finding.severity],
|
|
85
|
+
locations: [sarifLocation(finding.sites[0])],
|
|
86
|
+
message: { text: finding.message },
|
|
87
|
+
properties: finding,
|
|
88
|
+
ruleId: 'aontu/' + finding.code,
|
|
89
|
+
}
|
|
90
|
+
const related = finding.sites.slice(1)
|
|
91
|
+
if (0 < related.length) {
|
|
92
|
+
result.relatedLocations = related.map(sarifLocation)
|
|
93
|
+
}
|
|
94
|
+
return result
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Render a vet report as SARIF 2.1.0 text (a minimal profile: one run,
|
|
100
|
+
* one result per finding, the finding embedded in `properties`).
|
|
101
|
+
*
|
|
102
|
+
* @param report A report from `vet()`.
|
|
103
|
+
* @param version The producer version for `tool.driver.version` —
|
|
104
|
+
* the CLI passes its package version; the two ports'
|
|
105
|
+
* version series are independent by design.
|
|
106
|
+
* @returns The SARIF JSON text, indented two spaces, keys in
|
|
107
|
+
* the canonical emitter's sorted order.
|
|
108
|
+
*/
|
|
109
|
+
function sarifReport(report: VetReport, version: string): string {
|
|
110
|
+
return exactJSON({
|
|
111
|
+
$schema: 'https://json.schemastore.org/sarif-2.1.0.json',
|
|
112
|
+
runs: [{
|
|
113
|
+
// An `error` verdict means the run could not be set up (an
|
|
114
|
+
// unusable schema): zero findings from a FAILED run must not
|
|
115
|
+
// read like zero findings from a clean one, so the failure is
|
|
116
|
+
// carried in SARIF's own invocation metadata rather than by an
|
|
117
|
+
// indistinguishable empty result list.
|
|
118
|
+
invocations: [{
|
|
119
|
+
executionSuccessful: 'error' !== report.verdict,
|
|
120
|
+
}],
|
|
121
|
+
results: report.findings.map(sarifResult),
|
|
122
|
+
tool: {
|
|
123
|
+
driver: {
|
|
124
|
+
informationUri: 'https://github.com/rjrodger/aontu',
|
|
125
|
+
name: 'aontu',
|
|
126
|
+
version,
|
|
127
|
+
},
|
|
128
|
+
},
|
|
129
|
+
}],
|
|
130
|
+
version: '2.1.0',
|
|
131
|
+
}, 2)
|
|
132
|
+
} /* node:coverage ignore next 6 */
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
export {
|
|
136
|
+
sarifReport,
|
|
137
|
+
}
|