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.
Files changed (310) hide show
  1. package/README.md +88 -0
  2. package/bin/aontu-mcp.js +4 -0
  3. package/dist/agentsmd.d.ts +16 -0
  4. package/dist/agentsmd.js +107 -0
  5. package/dist/agentsmd.js.map +1 -0
  6. package/dist/aontu.d.ts +15 -3
  7. package/dist/aontu.js +126 -5
  8. package/dist/aontu.js.map +1 -1
  9. package/dist/cli.d.ts +45 -1
  10. package/dist/cli.js +2845 -69
  11. package/dist/cli.js.map +1 -1
  12. package/dist/ctx.d.ts +18 -0
  13. package/dist/ctx.js +44 -0
  14. package/dist/ctx.js.map +1 -1
  15. package/dist/diff.d.ts +22 -0
  16. package/dist/diff.js +141 -0
  17. package/dist/diff.js.map +1 -0
  18. package/dist/err.d.ts +3 -1
  19. package/dist/err.js +45 -8
  20. package/dist/err.js.map +1 -1
  21. package/dist/graph.d.ts +13 -0
  22. package/dist/graph.js +110 -0
  23. package/dist/graph.js.map +1 -0
  24. package/dist/hcanon.d.ts +3 -0
  25. package/dist/hcanon.js +145 -0
  26. package/dist/hcanon.js.map +1 -0
  27. package/dist/hints.js +235 -8
  28. package/dist/hints.js.map +1 -1
  29. package/dist/jsonschema.d.ts +20 -0
  30. package/dist/jsonschema.js +425 -0
  31. package/dist/jsonschema.js.map +1 -0
  32. package/dist/lang.js +955 -110
  33. package/dist/lang.js.map +1 -1
  34. package/dist/lsp.d.ts +9 -2
  35. package/dist/lsp.js +343 -48
  36. package/dist/lsp.js.map +1 -1
  37. package/dist/mcp-server.d.ts +20 -0
  38. package/dist/mcp-server.js +147 -0
  39. package/dist/mcp-server.js.map +1 -0
  40. package/dist/mcp.d.ts +42 -0
  41. package/dist/mcp.js +941 -0
  42. package/dist/mcp.js.map +1 -0
  43. package/dist/mod-tool.d.ts +58 -0
  44. package/dist/mod-tool.js +532 -0
  45. package/dist/mod-tool.js.map +1 -0
  46. package/dist/mod.d.ts +35 -0
  47. package/dist/mod.js +345 -0
  48. package/dist/mod.js.map +1 -0
  49. package/dist/patch.d.ts +49 -0
  50. package/dist/patch.js +506 -0
  51. package/dist/patch.js.map +1 -0
  52. package/dist/provenance.d.ts +41 -0
  53. package/dist/provenance.js +336 -0
  54. package/dist/provenance.js.map +1 -0
  55. package/dist/query.d.ts +27 -0
  56. package/dist/query.js +294 -0
  57. package/dist/query.js.map +1 -0
  58. package/dist/reach.d.ts +15 -0
  59. package/dist/reach.js +168 -0
  60. package/dist/reach.js.map +1 -0
  61. package/dist/relation.d.ts +23 -0
  62. package/dist/relation.js +230 -0
  63. package/dist/relation.js.map +1 -0
  64. package/dist/report-sarif.d.ts +14 -0
  65. package/dist/report-sarif.js +102 -0
  66. package/dist/report-sarif.js.map +1 -0
  67. package/dist/sig.d.ts +25 -0
  68. package/dist/sig.js +277 -0
  69. package/dist/sig.js.map +1 -0
  70. package/dist/sigdecl.d.ts +2 -0
  71. package/dist/sigdecl.js +11 -0
  72. package/dist/sigdecl.js.map +1 -0
  73. package/dist/siggate.d.ts +4 -0
  74. package/dist/siggate.js +90 -0
  75. package/dist/siggate.js.map +1 -0
  76. package/dist/site.d.ts +4 -0
  77. package/dist/site.js +31 -0
  78. package/dist/site.js.map +1 -1
  79. package/dist/std.d.ts +1 -0
  80. package/dist/std.js +129 -0
  81. package/dist/std.js.map +1 -0
  82. package/dist/subsume.d.ts +39 -0
  83. package/dist/subsume.js +573 -0
  84. package/dist/subsume.js.map +1 -0
  85. package/dist/trim.d.ts +19 -0
  86. package/dist/trim.js +155 -0
  87. package/dist/trim.js.map +1 -0
  88. package/dist/tsconfig.tsbuildinfo +1 -1
  89. package/dist/type.d.ts +17 -1
  90. package/dist/type.js.map +1 -1
  91. package/dist/unify.d.ts +2 -1
  92. package/dist/unify.js +234 -38
  93. package/dist/unify.js.map +1 -1
  94. package/dist/utility.d.ts +8 -1
  95. package/dist/utility.js +68 -1
  96. package/dist/utility.js.map +1 -1
  97. package/dist/val/AggFuncVal.d.ts +44 -0
  98. package/dist/val/AggFuncVal.js +364 -0
  99. package/dist/val/AggFuncVal.js.map +1 -0
  100. package/dist/val/ArithFuncVal.d.ts +31 -0
  101. package/dist/val/ArithFuncVal.js +62 -0
  102. package/dist/val/ArithFuncVal.js.map +1 -0
  103. package/dist/val/BagVal.d.ts +7 -0
  104. package/dist/val/BagVal.js +150 -11
  105. package/dist/val/BagVal.js.map +1 -1
  106. package/dist/val/CloseFuncVal.js +9 -1
  107. package/dist/val/CloseFuncVal.js.map +1 -1
  108. package/dist/val/ConjunctVal.d.ts +1 -1
  109. package/dist/val/ConjunctVal.js +19 -0
  110. package/dist/val/ConjunctVal.js.map +1 -1
  111. package/dist/val/ConstraintVal.d.ts +7 -1
  112. package/dist/val/ConstraintVal.js +523 -45
  113. package/dist/val/ConstraintVal.js.map +1 -1
  114. package/dist/val/ContainerKindVal.d.ts +35 -0
  115. package/dist/val/ContainerKindVal.js +99 -0
  116. package/dist/val/ContainerKindVal.js.map +1 -0
  117. package/dist/val/CopyFuncVal.d.ts +1 -2
  118. package/dist/val/CopyFuncVal.js.map +1 -1
  119. package/dist/val/Decimal.d.ts +1 -0
  120. package/dist/val/Decimal.js +13 -0
  121. package/dist/val/Decimal.js.map +1 -1
  122. package/dist/val/DeprecateFuncVal.d.ts +11 -0
  123. package/dist/val/DeprecateFuncVal.js +47 -0
  124. package/dist/val/DeprecateFuncVal.js.map +1 -0
  125. package/dist/val/DisjunctVal.d.ts +1 -2
  126. package/dist/val/DisjunctVal.js +269 -53
  127. package/dist/val/DisjunctVal.js.map +1 -1
  128. package/dist/val/EachFuncVal.d.ts +15 -0
  129. package/dist/val/EachFuncVal.js +75 -0
  130. package/dist/val/EachFuncVal.js.map +1 -0
  131. package/dist/val/ExpectVal.js +62 -4
  132. package/dist/val/ExpectVal.js.map +1 -1
  133. package/dist/val/FeatureVal.js +1 -1
  134. package/dist/val/FeatureVal.js.map +1 -1
  135. package/dist/val/FilterFuncVal.d.ts +15 -0
  136. package/dist/val/FilterFuncVal.js +91 -0
  137. package/dist/val/FilterFuncVal.js.map +1 -0
  138. package/dist/val/FuncBaseVal.d.ts +7 -1
  139. package/dist/val/FuncBaseVal.js +183 -2
  140. package/dist/val/FuncBaseVal.js.map +1 -1
  141. package/dist/val/GraphAtomVal.d.ts +39 -0
  142. package/dist/val/GraphAtomVal.js +184 -0
  143. package/dist/val/GraphAtomVal.js.map +1 -0
  144. package/dist/val/HideFuncVal.js.map +1 -1
  145. package/dist/val/JunctionVal.js +24 -1
  146. package/dist/val/JunctionVal.js.map +1 -1
  147. package/dist/val/KeyFuncVal.d.ts +1 -1
  148. package/dist/val/KeyFuncVal.js +38 -30
  149. package/dist/val/KeyFuncVal.js.map +1 -1
  150. package/dist/val/ListVal.js +94 -14
  151. package/dist/val/ListVal.js.map +1 -1
  152. package/dist/val/LowerFuncVal.js.map +1 -1
  153. package/dist/val/MapVal.d.ts +1 -0
  154. package/dist/val/MapVal.js +146 -14
  155. package/dist/val/MapVal.js.map +1 -1
  156. package/dist/val/MatchFuncVal.d.ts +15 -0
  157. package/dist/val/MatchFuncVal.js +107 -0
  158. package/dist/val/MatchFuncVal.js.map +1 -0
  159. package/dist/val/MoveFuncVal.js.map +1 -1
  160. package/dist/val/NilVal.js +24 -0
  161. package/dist/val/NilVal.js.map +1 -1
  162. package/dist/val/OpBaseVal.d.ts +1 -1
  163. package/dist/val/OpBaseVal.js +19 -1
  164. package/dist/val/OpBaseVal.js.map +1 -1
  165. package/dist/val/OpenFuncVal.js +4 -1
  166. package/dist/val/OpenFuncVal.js.map +1 -1
  167. package/dist/val/PackFuncVal.d.ts +15 -0
  168. package/dist/val/PackFuncVal.js +108 -0
  169. package/dist/val/PackFuncVal.js.map +1 -0
  170. package/dist/val/PathFuncVal.d.ts +2 -2
  171. package/dist/val/PathFuncVal.js +75 -16
  172. package/dist/val/PathFuncVal.js.map +1 -1
  173. package/dist/val/PathVal.d.ts +25 -0
  174. package/dist/val/PathVal.js +150 -0
  175. package/dist/val/PathVal.js.map +1 -0
  176. package/dist/val/PlaceVal.d.ts +13 -0
  177. package/dist/val/PlaceVal.js +131 -0
  178. package/dist/val/PlaceVal.js.map +1 -0
  179. package/dist/val/PlusOpVal.d.ts +2 -1
  180. package/dist/val/PlusOpVal.js +61 -37
  181. package/dist/val/PlusOpVal.js.map +1 -1
  182. package/dist/val/PrefFuncVal.js.map +1 -1
  183. package/dist/val/PrefVal.d.ts +4 -2
  184. package/dist/val/PrefVal.js +223 -41
  185. package/dist/val/PrefVal.js.map +1 -1
  186. package/dist/val/RecurseVal.d.ts +19 -0
  187. package/dist/val/RecurseVal.js +217 -0
  188. package/dist/val/RecurseVal.js.map +1 -0
  189. package/dist/val/RefVal.d.ts +3 -2
  190. package/dist/val/RefVal.js +181 -66
  191. package/dist/val/RefVal.js.map +1 -1
  192. package/dist/val/ReferFuncVal.d.ts +59 -0
  193. package/dist/val/ReferFuncVal.js +604 -0
  194. package/dist/val/ReferFuncVal.js.map +1 -0
  195. package/dist/val/ScalarKindVal.d.ts +4 -3
  196. package/dist/val/ScalarKindVal.js +12 -12
  197. package/dist/val/ScalarKindVal.js.map +1 -1
  198. package/dist/val/SuperFuncVal.d.ts +4 -2
  199. package/dist/val/SuperFuncVal.js +118 -14
  200. package/dist/val/SuperFuncVal.js.map +1 -1
  201. package/dist/val/TopVal.d.ts +1 -1
  202. package/dist/val/TopVal.js.map +1 -1
  203. package/dist/val/TypeFuncVal.js.map +1 -1
  204. package/dist/val/UpperFuncVal.js.map +1 -1
  205. package/dist/val/Val.d.ts +7 -1
  206. package/dist/val/Val.js +170 -2
  207. package/dist/val/Val.js.map +1 -1
  208. package/dist/val/VarVal.js.map +1 -1
  209. package/dist/val/arith.d.ts +6 -0
  210. package/dist/val/arith.js +173 -0
  211. package/dist/val/arith.js.map +1 -0
  212. package/dist/vet.d.ts +45 -0
  213. package/dist/vet.js +802 -0
  214. package/dist/vet.js.map +1 -0
  215. package/dist/view.d.ts +85 -0
  216. package/dist/view.js +1948 -0
  217. package/dist/view.js.map +1 -0
  218. package/dist/walk.d.ts +2 -0
  219. package/dist/walk.js +91 -0
  220. package/dist/walk.js.map +1 -0
  221. package/grammar/aontu.gbnf +138 -0
  222. package/grammar/aontu.lark +118 -0
  223. package/grammar/aontu.tmLanguage.json +184 -0
  224. package/package.json +30 -8
  225. package/skill/SKILL.md +37 -0
  226. package/skill/error-codes.md +62 -0
  227. package/skill/examples.md +99 -0
  228. package/skill/grammar-card.md +56 -0
  229. package/src/agentsmd.ts +135 -0
  230. package/src/aontu.ts +166 -5
  231. package/src/cli.ts +3324 -72
  232. package/src/ctx.ts +93 -1
  233. package/src/diff.ts +198 -0
  234. package/src/err.ts +50 -8
  235. package/src/graph.ts +185 -0
  236. package/src/hcanon.ts +167 -0
  237. package/src/hints.ts +291 -8
  238. package/src/jsonschema.ts +552 -0
  239. package/src/lang.ts +1049 -110
  240. package/src/lsp.ts +351 -49
  241. package/src/mcp-server.ts +187 -0
  242. package/src/mcp.ts +1130 -0
  243. package/src/mod-tool.ts +718 -0
  244. package/src/mod.ts +453 -0
  245. package/src/patch.ts +628 -0
  246. package/src/provenance.ts +437 -0
  247. package/src/query.ts +381 -0
  248. package/src/reach.ts +213 -0
  249. package/src/relation.ts +298 -0
  250. package/src/report-sarif.ts +137 -0
  251. package/src/sig.ts +345 -0
  252. package/src/sigdecl.ts +11 -0
  253. package/src/siggate.ts +144 -0
  254. package/src/site.ts +36 -1
  255. package/src/std.ts +131 -0
  256. package/src/subsume.ts +739 -0
  257. package/src/trim.ts +195 -0
  258. package/src/tsconfig.json +10 -4
  259. package/src/type.ts +51 -2
  260. package/src/unify.ts +253 -38
  261. package/src/utility.ts +79 -1
  262. package/src/val/AggFuncVal.ts +546 -0
  263. package/src/val/ArithFuncVal.ts +108 -0
  264. package/src/val/BagVal.ts +158 -12
  265. package/src/val/CloseFuncVal.ts +9 -1
  266. package/src/val/ConjunctVal.ts +20 -0
  267. package/src/val/ConstraintVal.ts +576 -48
  268. package/src/val/ContainerKindVal.ts +158 -0
  269. package/src/val/CopyFuncVal.ts +0 -1
  270. package/src/val/Decimal.ts +15 -0
  271. package/src/val/DeprecateFuncVal.ts +84 -0
  272. package/src/val/DisjunctVal.ts +282 -59
  273. package/src/val/EachFuncVal.ts +133 -0
  274. package/src/val/ExpectVal.ts +65 -8
  275. package/src/val/FeatureVal.ts +1 -1
  276. package/src/val/FilterFuncVal.ts +154 -0
  277. package/src/val/FuncBaseVal.ts +206 -2
  278. package/src/val/GraphAtomVal.ts +264 -0
  279. package/src/val/HideFuncVal.ts +0 -2
  280. package/src/val/JunctionVal.ts +24 -1
  281. package/src/val/KeyFuncVal.ts +39 -35
  282. package/src/val/ListVal.ts +101 -15
  283. package/src/val/LowerFuncVal.ts +0 -1
  284. package/src/val/MapVal.ts +153 -14
  285. package/src/val/MatchFuncVal.ts +176 -0
  286. package/src/val/MoveFuncVal.ts +0 -2
  287. package/src/val/NilVal.ts +25 -0
  288. package/src/val/OpBaseVal.ts +20 -1
  289. package/src/val/OpenFuncVal.ts +4 -2
  290. package/src/val/PackFuncVal.ts +175 -0
  291. package/src/val/PathFuncVal.ts +107 -20
  292. package/src/val/PathVal.ts +221 -0
  293. package/src/val/PlaceVal.ts +193 -0
  294. package/src/val/PlusOpVal.ts +67 -39
  295. package/src/val/PrefFuncVal.ts +0 -1
  296. package/src/val/PrefVal.ts +251 -60
  297. package/src/val/RecurseVal.ts +285 -0
  298. package/src/val/RefVal.ts +183 -78
  299. package/src/val/ReferFuncVal.ts +732 -0
  300. package/src/val/ScalarKindVal.ts +12 -13
  301. package/src/val/SuperFuncVal.ts +137 -13
  302. package/src/val/TopVal.ts +1 -2
  303. package/src/val/TypeFuncVal.ts +0 -2
  304. package/src/val/UpperFuncVal.ts +0 -1
  305. package/src/val/Val.ts +216 -4
  306. package/src/val/VarVal.ts +0 -1
  307. package/src/val/arith.ts +319 -0
  308. package/src/vet.ts +1025 -0
  309. package/src/view.ts +2563 -0
  310. package/src/walk.ts +99 -0
@@ -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
+ }