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
package/src/subsume.ts ADDED
@@ -0,0 +1,739 @@
1
+ /* Copyright (c) 2025 Richard Rodger, MIT License */
2
+
3
+ // Subsumption as a first-class query (G3 phases 1-2,
4
+ // docs/capability-review/g3-subsumption-evolution.md): does the GENERAL
5
+ // value admit every instance the SPECIFIC value admits?
6
+ //
7
+ // The recursion is a dedicated structural walk over EVALUATED values —
8
+ // design option B. It never mutates its inputs (it runs after
9
+ // evaluation, on finished trees), carries no fixpoint, and returns a
10
+ // THREE-VALUED verdict: `subsumes`, `does_not_subsume` (with the
11
+ // failing path and both sides' canons as the witness), or `undecided`
12
+ // (with a reason code, never silently). Where a rule cannot decide the
13
+ // answer folds toward "not subsumed" or "undecided" — the safe
14
+ // directions: a compatibility gate that wrongly reports "breaking"
15
+ // costs a second look, one that wrongly reports "compatible" ships the
16
+ // break (docs/reference-language.md, "Subsumption").
17
+ //
18
+ // Findings reuse G2's report vocabulary (the vet finding object), with
19
+ // the G3 codes appended to the shared registry: `compat_narrowed`,
20
+ // `compat_required_added`, `compat_default_changed`,
21
+ // `compat_marks_changed`, and the `sub_*` undecided reasons — all
22
+ // class `compat`.
23
+
24
+
25
+ import type { TrustOptions } from './type'
26
+ import { Aontu } from './aontu'
27
+ import { anchorAt } from './vet'
28
+ import { hcanon } from './hcanon'
29
+ import type { VetFinding, VetSite } from './vet'
30
+ import {
31
+ constraintSubsumesConstraint,
32
+ constraintAdmitsScalar,
33
+ } from './val/ConstraintVal'
34
+ import { kindSubsumes } from './val/ScalarKindVal'
35
+ import { prefixMeet } from './val/PathVal'
36
+ import { prefInnerPeg } from './val/PrefVal'
37
+
38
+
39
+ export type SubsumeVerdict =
40
+ | 'subsumes' | 'does_not_subsume' | 'undecided' | 'error'
41
+
42
+ export type SubsumeProfile = 'values' | 'defaults' | 'gen'
43
+
44
+ export type SubsumeOptions = {
45
+ profile?: SubsumeProfile
46
+ at?: string
47
+ generalUrl?: string // provenance label for general sites
48
+ specificUrl?: string // provenance label for specific sites
49
+ // Where each document CAME FROM, so a relative `@"file"` load inside
50
+ // it resolves from its own directory — vet's schemaPath/dataPath
51
+ // precedent, one per document because they need not live together.
52
+ generalPath?: string
53
+ specificPath?: string
54
+ // The include capability both documents evaluate under (G5,
55
+ // docs/trust.md). vet's precedent: the verb passes the profile the
56
+ // caller asked for, and an absent option means today's default.
57
+ trust?: TrustOptions
58
+ }
59
+
60
+ export type SubsumeReport = {
61
+ verdict: SubsumeVerdict
62
+ findings: VetFinding[]
63
+ }
64
+
65
+
66
+ const DEFAULT_GENERAL_URL = 'general'
67
+ const DEFAULT_SPECIFIC_URL = 'specific'
68
+
69
+
70
+ // One comparison's running state: the finding list, the two provenance
71
+ // labels, and the profile.
72
+ type SubState = {
73
+ profile: SubsumeProfile
74
+ // Set inside a distribution trial: see trialSubsume.
75
+ distributing?: boolean
76
+ findings: VetFinding[]
77
+ generalUrl: string
78
+ specificUrl: string
79
+ }
80
+
81
+ // The per-node answer. Aggregation: any `no` makes the verdict
82
+ // does_not_subsume; otherwise any `undecided` makes it undecided.
83
+ type Tri = 'yes' | 'no' | 'undecided'
84
+
85
+
86
+ function pathText(path: string[]): string {
87
+ return '$' + (0 < path.length ? '.' + path.join('.') : '')
88
+ }
89
+
90
+
91
+ // Every recorded operand is a real evaluated Val or the sited topLike
92
+ // stand-in, so no null guards: a Val's site is always an object, and a
93
+ // value parsed without a usable frame (a disjunct, say) already carries
94
+ // the -1 coordinates in it.
95
+ function siteOf(v: any, role: string, url: string): VetSite {
96
+ return {
97
+ file: url,
98
+ row: v.site.row,
99
+ col: v.site.col,
100
+ len: v.site.len,
101
+ role: role as any,
102
+ src: v.site.src,
103
+ value: v.canon,
104
+ }
105
+ }
106
+
107
+
108
+ // One finding: general and specific sites (general first — it is the
109
+ // contract being weakened or broken), canons as expected/actual.
110
+ function record(
111
+ state: SubState, code: string, path: string[],
112
+ g: any, s: any, message: string,
113
+ ): void {
114
+ state.findings.push({
115
+ code,
116
+ class: 'compat',
117
+ severity: 'error',
118
+ path: pathText(path),
119
+ message,
120
+ sites: [
121
+ siteOf(g, 'general', state.generalUrl),
122
+ siteOf(s, 'specific', state.specificUrl),
123
+ ],
124
+ expected: g.canon,
125
+ actual: s.canon,
126
+ })
127
+ }
128
+
129
+
130
+ // The admission view of a value under a profile: a BARE preference
131
+ // admits what its superior admits (the engine's own PrefVal.superpeg
132
+ // semantics, and the docs' "a bare preference is gated by kind"); the
133
+ // default itself is compared separately, by the `defaults` and `gen`
134
+ // profiles.
135
+ function admission(v: any): any {
136
+ return true === v?.isPref ? v.superpeg : v
137
+ }
138
+
139
+
140
+ // A PREFERRED BRANCH CONTRIBUTES EXACTLY ITS OWN VALUE (ADR-004). The
141
+ // admission gate made that the engine's rule -- `*'auto'|'literal'|'data'`
142
+ // admits those three strings and nothing else -- and the subsumption
143
+ // walk kept comparing a pref MEMBER by its kind superior, the
144
+ // pre-ADR-004 reading. So a disjunction with a default did not subsume
145
+ // ITSELF: every member of the specific side widened to `string`, which
146
+ // no general member admits, and the walk answered the distribution
147
+ // case. `aontu_policy: hide({compat: *backward|forward|full|none})` --
148
+ // the verbatim idiom from reference-api.md -- failed self-subsumption
149
+ // under --profile gen (use-cases/BUGS.md §29).
150
+ //
151
+ // Only for a member of a disjunction: a bare `*x` standing alone is
152
+ // still gated by kind, which is the rule above and the documented one.
153
+ function memberAdmission(v: any): any {
154
+ return true === v?.isPref ? prefInnerPeg(v) : v
155
+ }
156
+
157
+
158
+ // The effective default of a value, or undefined when it has none, or
159
+ // 'indeterminate' when equal-rank preferences disagree (which the
160
+ // engine itself refuses only at generation).
161
+ // Exported (with subsumeNode) for the default-validity lint in
162
+ // ts/src/vet.ts (G3 phase 5): the lint asks exactly this walk's two
163
+ // questions — what is the effective default, and does a member admit
164
+ // it.
165
+ export function effectiveDefault(v: any): any {
166
+ // EVERY pref layer is unwrapped (prefInnerPeg), not just one: a
167
+ // ranked default's effective value is the innermost peg — `**member`
168
+ // generates "member" exactly as `*member` does (the rank-uniform
169
+ // meet, ADR-004). The one-layer unwrap left a rank-2 default wearing
170
+ // a `*`-wrapper no plain alternative subsumes, which is the
171
+ // pref_not_instance lint's ranked false positive of
172
+ // use-cases/BUGS.md §4 (`**member|member|admin|owner` warned while
173
+ // generating a value its own branch admits).
174
+ if (true === v?.isPref) {
175
+ return prefInnerPeg(v)
176
+ }
177
+ if (true === v?.isDisjunct && Array.isArray(v.peg)) {
178
+ const prefs = v.peg.filter((m: any) => true === m?.isPref)
179
+ if (0 === prefs.length) {
180
+ return undefined
181
+ }
182
+ // Generation picks the LOWEST rank (`a:**1|*2` generates 2 —
183
+ // test/spec/edge.tsv), so the effective default does too.
184
+ const minRank = Math.min(...prefs.map((p: any) => p.rank))
185
+ const top = prefs.filter((p: any) => p.rank === minRank)
186
+ const first: any = prefInnerPeg(top[0])
187
+ for (const p of top.slice(1)) {
188
+ if (!first.same?.(prefInnerPeg(p))) {
189
+ return 'indeterminate'
190
+ }
191
+ }
192
+ return first
193
+ }
194
+ return undefined
195
+ }
196
+
197
+
198
+ // Is this evaluated value concrete enough to serve as a witness — a
199
+ // value that certainly IS an instance of the specific side?
200
+ function isConcrete(v: any): boolean {
201
+ if (true === v?.isScalar) {
202
+ return true
203
+ }
204
+ if ((true === v?.isMap || true === v?.isList) && null != v.peg) {
205
+ const children = Array.isArray(v.peg) ? v.peg : Object.values(v.peg)
206
+ return children.every((c: any) => isConcrete(c))
207
+ }
208
+ return false
209
+ }
210
+
211
+
212
+ // Unresolved residue makes the relation undecidable at this node:
213
+ // references, variables, unreduced conjuncts and functions have no
214
+ // admitted set to compare.
215
+ function unresolved(v: any): boolean {
216
+ return true === v?.isRef || true === v?.isVar ||
217
+ true === v?.isConjunct || true === v?.isExpect ||
218
+ (true === v?.isFunc && true !== v?.isConstraint)
219
+ }
220
+
221
+
222
+ // Exported for the defensive-arm unit test (ADR-002,
223
+ // ts/test/coverage3.test.ts): the no-rule fold at the walk's tail is
224
+ // unreachable through subsume() today — the ladder is total for every
225
+ // evaluated former — and the walk is otherwise private.
226
+ export function subsumeNode(
227
+ state: SubState, path: string[], g0: any, s0: any): Tri {
228
+
229
+ const g = admission(g0)
230
+ const s = admission(s0)
231
+
232
+ // Marks change the OUTPUT shape, not the admitted set: only the `gen`
233
+ // profile reports them, and only when they differ on corresponding
234
+ // nodes.
235
+ if ('gen' === state.profile && true !== state.distributing &&
236
+ (!!g?.mark?.type !== !!s?.mark?.type ||
237
+ !!g?.mark?.hide !== !!s?.mark?.hide)) {
238
+ record(state, 'compat_marks_changed', path, g, s,
239
+ 'marks differ: general ' + JSON.stringify(g?.mark) +
240
+ ', specific ' + JSON.stringify(s?.mark))
241
+ return 'no'
242
+ }
243
+
244
+ // TOP admits everything. There is no nil rule: an error-free
245
+ // evaluated document carries no nil (failing disjunct members are
246
+ // discarded, every other nil collects an error, and subsume() answers
247
+ // `error` for a source that does not stand alone), so a nil handed to
248
+ // the walk by a future caller falls to the no-rule fold below —
249
+ // `undecided`, the safe direction.
250
+ if (true === g?.isTop) {
251
+ return 'yes'
252
+ }
253
+
254
+ if (unresolved(g) || unresolved(s)) {
255
+ // REFLEXIVITY IS A LAW, not a rule the ladder gets to skip. Every
256
+ // value admits itself, residue included: the set admitted by
257
+ // `integer & min(0)` is exactly the set admitted by
258
+ // `integer & min(0)`. Without this, a constraint inside a spread
259
+ // template made a contract non-SELF-subsumable -- expected and
260
+ // actual byte-identical, verdict `undecided` -- so `breaking` on
261
+ // the documented close-per-entry idiom hard-failed reflexivity and
262
+ // had to run --allow-undecided, which then masks the genuine
263
+ // undecideds it exists to surface (use-cases/BUGS.md §28).
264
+ //
265
+ // Identity is the HASH FORM, not the canon: canon drops closedness
266
+ // and the marks, so `close({a:1})` and `{a:1}` share a canon while
267
+ // admitting different sets. Computed only on this branch, where
268
+ // the answer would otherwise be undecided, so the hot path is
269
+ // untouched.
270
+ if (hcanon(g) === hcanon(s)) {
271
+ return 'yes'
272
+ }
273
+ record(state, 'sub_unresolved', path, g, s,
274
+ 'unresolved residue: the admitted set is not comparable')
275
+ return 'undecided'
276
+ }
277
+
278
+ // Disjunctions. General side first: a specific member must be
279
+ // subsumed by SOME general member; member-wise failure is not proof
280
+ // (the distribution case), so a concrete failing member becomes the
281
+ // witness and anything else is honestly undecided.
282
+ if (true === s?.isDisjunct) {
283
+ let out: Tri = 'yes'
284
+ for (const raw of s.peg as any[]) {
285
+ const member = memberAdmission(raw)
286
+ const trial = trialSubsume(state, path, g, member)
287
+ if ('yes' !== trial) {
288
+ if (isConcrete(admission(member))) {
289
+ record(state, 'compat_narrowed', path, g, member,
290
+ 'a specific alternative is not admitted by the general value')
291
+ return 'no'
292
+ }
293
+ record(state, 'sub_disjunct_distribution', path, g, member,
294
+ 'a specific alternative is not admitted member-wise, and no' +
295
+ ' concrete counterexample settles the distribution case')
296
+ out = 'undecided'
297
+ }
298
+ }
299
+ return out
300
+ }
301
+ if (true === g?.isDisjunct) {
302
+ for (const raw of g.peg as any[]) {
303
+ if ('yes' === trialSubsume(state, path, memberAdmission(raw), s)) {
304
+ return 'yes'
305
+ }
306
+ }
307
+ if (isConcrete(s)) {
308
+ record(state, 'compat_narrowed', path, g, s,
309
+ 'no general alternative admits the specific value')
310
+ return 'no'
311
+ }
312
+ record(state, 'sub_disjunct_distribution', path, g, s,
313
+ 'no general alternative admits the specific value member-wise,' +
314
+ ' and no concrete counterexample settles the distribution case')
315
+ return 'undecided'
316
+ }
317
+
318
+ // Scalar kinds: a kind subsumes its scalars, narrower kinds, and the
319
+ // constraint residuals of its domain.
320
+ if (true === g?.isScalarKind) {
321
+ if (true === s?.isScalarKind) {
322
+ if (g.peg === s.peg || kindSubsumes(g.peg, s.peg)) {
323
+ return 'yes'
324
+ }
325
+ record(state, 'compat_narrowed', path, g, s,
326
+ 'the general kind does not admit the specific kind')
327
+ return 'no'
328
+ }
329
+ if (true === s?.isScalar) {
330
+ const leaf = s.superior?.()
331
+ if (true === leaf?.isScalarKind &&
332
+ (g.peg === leaf.peg || kindSubsumes(g.peg, leaf.peg))) {
333
+ return 'yes'
334
+ }
335
+ record(state, 'compat_narrowed', path, g, s,
336
+ 'the general kind does not admit the specific scalar')
337
+ return 'no'
338
+ }
339
+ if (true === s?.isConstraint) {
340
+ // A kind covers a residual of its own domain: `number` admits any
341
+ // numeric residual (the supertype admits every leaf a bound can
342
+ // pin), a numeric LEAF kind admits only a residual pinned to that
343
+ // leaf, and `string` admits any pattern residual. A residual with
344
+ // no domain (sizing-only) admits containers no kind covers.
345
+ const dom = (s as any).domain
346
+ const skind = (s as any).kind
347
+ if ('number' === dom &&
348
+ (Number === g.peg ||
349
+ (null != skind && kindSubsumes(g.peg, skind)))) {
350
+ return 'yes'
351
+ }
352
+ if ('string' === dom && String === g.peg) {
353
+ return 'yes'
354
+ }
355
+ record(state, 'compat_narrowed', path, g, s,
356
+ 'the general kind does not cover the specific residual')
357
+ return 'no'
358
+ }
359
+ record(state, 'compat_narrowed', path, g, s,
360
+ 'the general kind admits no such value')
361
+ return 'no'
362
+ }
363
+
364
+ // Container kinds (docs/design/PATHS.0.md): `map()` subsumes every
365
+ // map and itself, `list()` every list. The unit literals (`{}`,
366
+ // `[]`) already subsume through the container rules; only the kind
367
+ // former needs an arm.
368
+ if (true === (g as any)?.isContainerKind) {
369
+ const ga: any = g
370
+ const sa: any = s
371
+ if ((true === ga.isMapKind && (true === sa?.isMap || true === sa?.isMapKind))
372
+ || (true === ga.isListKind && (true === sa?.isList || true === sa?.isListKind))) {
373
+ return 'yes'
374
+ }
375
+ record(state, 'compat_narrowed', path, g, s,
376
+ 'the general container kind admits no such value')
377
+ return 'no'
378
+ }
379
+
380
+ // Constraint residuals.
381
+ if (true === g?.isConstraint) {
382
+ if (true === s?.isConstraint) {
383
+ const r = constraintSubsumesConstraint(g, s)
384
+ if (true === r) {
385
+ return 'yes'
386
+ }
387
+ if ('undecided' === r) {
388
+ record(state, 'sub_evaluate_only', path, g, s,
389
+ 'an evaluate-only check (must) makes the admitted set opaque')
390
+ return 'undecided'
391
+ }
392
+ record(state, 'compat_narrowed', path, g, s,
393
+ 'the general residual does not contain the specific residual')
394
+ return 'no'
395
+ }
396
+ if (true === s?.isScalar) {
397
+ const r = constraintAdmitsScalar(g, s)
398
+ if (true === r) {
399
+ return 'yes'
400
+ }
401
+ if ('undecided' === r) {
402
+ record(state, 'sub_evaluate_only', path, g, s,
403
+ 'an evaluate-only check (must) makes the admitted set opaque')
404
+ return 'undecided'
405
+ }
406
+ record(state, 'compat_narrowed', path, g, s,
407
+ 'the general residual does not admit the specific scalar')
408
+ return 'no'
409
+ }
410
+ record(state, 'compat_narrowed', path, g, s,
411
+ 'the general residual constrains a domain the specific value is not in')
412
+ return 'no'
413
+ }
414
+
415
+ // Concrete scalars subsume only themselves (identity compares kind
416
+ // as well as value) -- except paths, whose meet is the prefix rule
417
+ // (ADR-016): a prefix admits every extension of itself, so it
418
+ // subsumes one, exactly as the meet answers the longer.
419
+ if (true === g?.isScalar) {
420
+ if (true === g.isPath && true === (s as any)?.isPath &&
421
+ prefixMeet(g.peg, (s as any).peg) === (s as any).peg) {
422
+ return 'yes'
423
+ }
424
+ if (true === s?.isScalar && true === g.same?.(s)) {
425
+ return 'yes'
426
+ }
427
+ record(state, 'compat_narrowed', path, g, s,
428
+ 'a concrete value subsumes only itself')
429
+ return 'no'
430
+ }
431
+
432
+ // Maps.
433
+ if (true === g?.isMap) {
434
+ if (true !== s?.isMap) {
435
+ record(state, 'compat_narrowed', path, g, s,
436
+ 'the general value is a map and the specific value is not')
437
+ return 'no'
438
+ }
439
+ return subsumeBag(state, path, g, s, Object.keys(g.peg),
440
+ Object.keys(s.peg), (v: any, k: string) => v.peg[k])
441
+ }
442
+
443
+ // Lists: element-wise by position; the same required/optional shape
444
+ // as maps, with positions as keys.
445
+ if (true === g?.isList) {
446
+ if (true !== s?.isList) {
447
+ record(state, 'compat_narrowed', path, g, s,
448
+ 'the general value is a list and the specific value is not')
449
+ return 'no'
450
+ }
451
+ const gk = (g.peg as any[]).map((_: any, i: number) => '' + i)
452
+ const sk = (s.peg as any[]).map((_: any, i: number) => '' + i)
453
+ return subsumeBag(state, path, g, s, gk, sk,
454
+ (v: any, k: string) => v.peg[Number(k)])
455
+ }
456
+
457
+ // THE LADDER IS NOT TOTAL, and the formers that fall past it are the
458
+ // ones the evaluator MEANS to leave standing: a recursion, a
459
+ // relation and its graph atom, a `refer()` target constraint. None
460
+ // of them describes a set of values a structural walk can compare,
461
+ // so `undecided` is the honest answer for two DIFFERENT ones.
462
+ //
463
+ // For two IDENTICAL ones it is not. REFLEXIVITY IS A LAW -- every
464
+ // value admits itself -- and identity is the HASH FORM, the same
465
+ // rule the unresolved branch above applies and for the same reason:
466
+ // it costs nothing, because it runs only where the answer would
467
+ // otherwise be `undecided`. Without it a document that declares a
468
+ // relation, shares a template by reference or alias, or recurses did
469
+ // not subsume ITSELF, so `breaking` on the idiom the language exists
470
+ // for hard-failed and had to run --allow-undecided, which masks the
471
+ // genuine undecideds it exists to surface (use-cases/BUGS.md 64,
472
+ // and 28 before it).
473
+ //
474
+ // A NIL is the exception, and the reason the law is spelled here
475
+ // rather than in `unresolved`: a nil is not a value, so it admits
476
+ // nothing, itself included.
477
+ if (true !== g?.isNil && true !== s?.isNil && hcanon(g) === hcanon(s)) {
478
+ return 'yes'
479
+ }
480
+ record(state, 'sub_unresolved', path, g, s,
481
+ 'no subsumption rule covers this pair of value formers')
482
+ return 'undecided'
483
+ }
484
+
485
+
486
+ // Maps and lists share one shape: required keys of the general side
487
+ // must be required in the specific side and subsume; optional keys
488
+ // compare when present; closedness bounds the specific key set; spread
489
+ // templates govern the specific side's surplus.
490
+ function subsumeBag(
491
+ state: SubState, path: string[], g: any, s: any,
492
+ gKeys: string[], sKeys: string[],
493
+ child: (v: any, k: string) => any,
494
+ ): Tri {
495
+ let out: Tri = 'yes'
496
+ const worse = (r: Tri) => {
497
+ if ('no' === r || ('undecided' === r && 'no' !== out)) {
498
+ out = 'no' === r ? 'no' : 'undecided'
499
+ }
500
+ }
501
+
502
+ // Always arrays on an evaluated bag (BagVal initialises them).
503
+ const gOptional: string[] = g.optionalKeys
504
+ const sOptional: string[] = s.optionalKeys
505
+
506
+ for (const k of gKeys) {
507
+ const gChild = child(g, k)
508
+ const has = sKeys.includes(k)
509
+ const optional = gOptional.includes(k)
510
+ if (!has) {
511
+ if (optional) {
512
+ continue
513
+ }
514
+ record(state, 'compat_required_added', [...path, k], gChild, s,
515
+ 'the general value requires this key; the specific value admits' +
516
+ ' instances without it')
517
+ worse('no')
518
+ continue
519
+ }
520
+ if (!optional && sOptional.includes(k)) {
521
+ record(state, 'compat_required_added', [...path, k], gChild,
522
+ child(s, k),
523
+ 'the general value requires this key; the specific value makes' +
524
+ ' it optional, so instances without it are admitted')
525
+ worse('no')
526
+ continue
527
+ }
528
+ worse(subsumeNode(state, [...path, k], gChild, child(s, k)))
529
+ }
530
+
531
+ // Closedness: a closed general bag admits only key sets it declares,
532
+ // so the specific side must be closed and inside it.
533
+ if (true === g.closed) {
534
+ if (true !== s.closed) {
535
+ record(state, 'compat_narrowed', path, g, s,
536
+ 'the general value is closed; the open specific value admits' +
537
+ ' surplus keys')
538
+ worse('no')
539
+ }
540
+ else {
541
+ for (const k of sKeys) {
542
+ if (!gKeys.includes(k)) {
543
+ record(state, 'compat_narrowed', [...path, k], g, child(s, k),
544
+ 'the closed general value does not declare this key')
545
+ worse('no')
546
+ }
547
+ }
548
+ }
549
+ }
550
+
551
+ // Spread templates: a path-dependent template's meaning depends on
552
+ // where it lands, which no structural comparison can decide -- UNLESS
553
+ // the two templates are the same template. REFLEXIVITY IS A LAW and
554
+ // identity is the HASH FORM (the same rule the unresolved branch of
555
+ // subsumeNode applies): two byte-identical templates admit the same
556
+ // set wherever they land, so a document with a reference- or
557
+ // alias-valued template subsumes itself, and the comparison either
558
+ // side of the template is decided on its own merits rather than
559
+ // dragged to `undecided` (use-cases/BUGS.md 64). Where they are NOT
560
+ // identical, nothing structural can decide them and the fold stands.
561
+ const gcj = g.spread?.cj
562
+ const scj = s.spread?.cj
563
+ if (null != gcj || null != scj) {
564
+ const sameTemplate = null != gcj && null != scj && hcanon(gcj) === hcanon(scj)
565
+ if (!sameTemplate &&
566
+ (true === gcj?.isPathDependent || true === scj?.isPathDependent)) {
567
+ record(state, 'sub_path_dependent_spread', path, gcj ?? g, scj ?? s,
568
+ 'a path-dependent spread template cannot be compared structurally')
569
+ worse('undecided')
570
+ }
571
+ else if (null != gcj) {
572
+ // The general template governs the specific side's surplus keys
573
+ // and its template.
574
+ for (const k of sKeys) {
575
+ if (!gKeys.includes(k)) {
576
+ worse(subsumeNode(state, [...path, k], gcj, child(s, k)))
577
+ }
578
+ }
579
+ worse(subsumeNode(state, [...path, '&'], gcj, scj ?? topLike()))
580
+ }
581
+ // A specific-only template narrows the specific side; nothing for
582
+ // the (unconstrained) general side to refuse.
583
+ }
584
+
585
+ return out
586
+ }
587
+
588
+
589
+ // A stand-in TOP for "the specific side leaves this unconstrained".
590
+ let topVal: any
591
+ function topLike(): any {
592
+ if (null == topVal) {
593
+ // A full site, not a partial one: `len` and `src` are as much part
594
+ // of the shape as row and col, and leaving them undefined dropped
595
+ // both keys from the emitted report — so this stand-in was the one
596
+ // site in either port with no `len` at all, and the Go twin (which
597
+ // has no undefined) disagreed. It occupies no source, so the values
598
+ // are the "unknown" ones.
599
+ topVal = {
600
+ isTop: true, canon: 'top',
601
+ site: { row: -1, col: -1, len: -1, src: '' },
602
+ }
603
+ }
604
+ return topVal
605
+ }
606
+
607
+
608
+ // A trial comparison whose findings are DISCARDED: disjunct
609
+ // member-matching asks many "would this member do?" questions, and only
610
+ // the aggregated outcome is a finding.
611
+ // A DISTRIBUTION TRIAL IS NOT A NODE CORRESPONDENCE. It asks whether
612
+ // one ALTERNATIVE of one side admits one alternative of the other,
613
+ // which is a question about admitted sets; the two values it compares
614
+ // are not the same node of the two documents. The `gen` profile's mark
615
+ // rule is a correspondence question -- did a field that used to be
616
+ // generated become hidden -- and firing it here compared a whole
617
+ // disjunction (carrying its enclosing bag's mark) against a member
618
+ // extracted out of one (which does not), so `hide({c: *a|b})` stopped
619
+ // subsuming ITSELF under --profile gen (use-cases/BUGS.md §29). The
620
+ // enclosing node's marks are compared where they correspond: at that
621
+ // node, by the ordinary walk.
622
+ function trialSubsume(
623
+ state: SubState, path: string[], g: any, s: any): Tri {
624
+ const trial: SubState = { ...state, findings: [], distributing: true }
625
+ return subsumeNode(trial, path, g, s)
626
+ }
627
+
628
+
629
+ // The defaults comparison (the `defaults` and `gen` profiles): the
630
+ // specific side's effective default must survive into the general side
631
+ // unchanged. Adding a default where none existed is compatible;
632
+ // changing or removing one is not (removal turns previously generable
633
+ // documents incomplete).
634
+ function subsumeDefaults(
635
+ state: SubState, path: string[], g: any, s: any): Tri {
636
+ const sd = effectiveDefault(s)
637
+ if (undefined === sd) {
638
+ return 'yes'
639
+ }
640
+ const gd = effectiveDefault(g)
641
+ if ('indeterminate' === sd || 'indeterminate' === gd) {
642
+ record(state, 'sub_default_indeterminate', path, g, s,
643
+ 'equal-rank preferences disagree, so the effective default is' +
644
+ ' not a single value')
645
+ return 'undecided'
646
+ }
647
+ if (undefined === gd || true !== gd.same?.(sd)) {
648
+ record(state, 'compat_default_changed', path, g, s,
649
+ 'the effective default changed: previously generable documents' +
650
+ ' materialise differently or become incomplete')
651
+ return 'no'
652
+ }
653
+ return 'yes'
654
+ }
655
+
656
+
657
+ // Walk both trees for default agreement wherever the specific side has
658
+ // one: at the node itself and inside every corresponding bag child.
659
+ function subsumeDefaultsWalk(
660
+ state: SubState, path: string[], g: any, s: any): Tri {
661
+ let out: Tri = subsumeDefaults(state, path, g, s)
662
+
663
+ const gm = true === g?.isMap ? g.peg : undefined
664
+ const sm = true === s?.isMap ? s.peg : undefined
665
+ if (null != gm && null != sm) {
666
+ for (const k of Object.keys(sm)) {
667
+ if (null != gm[k]) {
668
+ const r = subsumeDefaultsWalk(state, [...path, k], gm[k], sm[k])
669
+ if ('no' === r || ('undecided' === r && 'no' !== out)) {
670
+ out = 'no' === r ? 'no' : 'undecided'
671
+ }
672
+ }
673
+ }
674
+ }
675
+ return out
676
+ }
677
+
678
+
679
+ /**
680
+ * Does `generalSrc` subsume `specificSrc` — is every instance the
681
+ * specific admits admitted by the general too?
682
+ *
683
+ * Both sources are evaluated fresh (single-use trees make this
684
+ * mandatory), and the recursion runs on the finished values. The
685
+ * verdict is three-valued plus `error` (a source that does not stand up
686
+ * on its own, mirroring vet's schema-error verdict); findings reuse
687
+ * G2's object with class `compat`.
688
+ */
689
+ export function subsume(
690
+ generalSrc: string, specificSrc: string, opts?: SubsumeOptions,
691
+ ): SubsumeReport {
692
+ const options = opts ?? {}
693
+ const profile: SubsumeProfile = options.profile ?? 'defaults'
694
+ const state: SubState = {
695
+ profile,
696
+ findings: [],
697
+ generalUrl: options.generalUrl ?? DEFAULT_GENERAL_URL,
698
+ specificUrl: options.specificUrl ?? DEFAULT_SPECIFIC_URL,
699
+ }
700
+
701
+ const load = (src: string, path?: string): any => {
702
+ const aontu = new Aontu(
703
+ null == options.trust ? undefined : { trust: options.trust })
704
+ const ctx = aontu.ctx({ collect: true })
705
+ const v: any = aontu.unify(
706
+ src, null == path ? undefined : { path }, ctx)
707
+ if (0 < ctx.err.length || true === v?.isNil) {
708
+ return undefined
709
+ }
710
+ return v
711
+ }
712
+
713
+ let g = load(generalSrc, options.generalPath)
714
+ let s = load(specificSrc, options.specificPath)
715
+ if (null == g || null == s) {
716
+ return { verdict: 'error', findings: [] }
717
+ }
718
+
719
+ if (null != options.at) {
720
+ g = anchorAt(g, options.at)
721
+ s = anchorAt(s, options.at)
722
+ if (null == g || null == s) {
723
+ return { verdict: 'error', findings: [] }
724
+ }
725
+ }
726
+
727
+ let out: Tri = subsumeNode(state, [], g, s)
728
+ if ('values' !== profile) {
729
+ const d = subsumeDefaultsWalk(state, [], g, s)
730
+ if ('no' === d || ('undecided' === d && 'no' !== out)) {
731
+ out = 'no' === d ? 'no' : 'undecided'
732
+ }
733
+ }
734
+
735
+ const verdict: SubsumeVerdict =
736
+ 'no' === out ? 'does_not_subsume' :
737
+ 'undecided' === out ? 'undecided' : 'subsumes'
738
+ return { verdict, findings: state.findings }
739
+ }