aontu 0.52.0 → 0.53.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 (273) 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 +14 -3
  7. package/dist/aontu.js +145 -4
  8. package/dist/aontu.js.map +1 -1
  9. package/dist/cli.d.ts +44 -1
  10. package/dist/cli.js +2401 -44
  11. package/dist/cli.js.map +1 -1
  12. package/dist/ctx.d.ts +16 -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 +48 -8
  20. package/dist/err.js.map +1 -1
  21. package/dist/graph.d.ts +16 -0
  22. package/dist/graph.js +73 -0
  23. package/dist/graph.js.map +1 -0
  24. package/dist/hcanon.d.ts +3 -0
  25. package/dist/hcanon.js +146 -0
  26. package/dist/hcanon.js.map +1 -0
  27. package/dist/hints.js +223 -5
  28. package/dist/hints.js.map +1 -1
  29. package/dist/jsonschema.d.ts +20 -0
  30. package/dist/jsonschema.js +391 -0
  31. package/dist/jsonschema.js.map +1 -0
  32. package/dist/lang.js +698 -35
  33. package/dist/lang.js.map +1 -1
  34. package/dist/lsp.d.ts +9 -2
  35. package/dist/lsp.js +262 -46
  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 +814 -0
  42. package/dist/mcp.js.map +1 -0
  43. package/dist/mod-tool.d.ts +58 -0
  44. package/dist/mod-tool.js +498 -0
  45. package/dist/mod-tool.js.map +1 -0
  46. package/dist/mod.d.ts +31 -0
  47. package/dist/mod.js +250 -0
  48. package/dist/mod.js.map +1 -0
  49. package/dist/patch.d.ts +44 -0
  50. package/dist/patch.js +506 -0
  51. package/dist/patch.js.map +1 -0
  52. package/dist/provenance.d.ts +40 -0
  53. package/dist/provenance.js +335 -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 +14 -0
  59. package/dist/reach.js +140 -0
  60. package/dist/reach.js.map +1 -0
  61. package/dist/relation.d.ts +19 -0
  62. package/dist/relation.js +305 -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/site.d.ts +4 -0
  68. package/dist/site.js +31 -0
  69. package/dist/site.js.map +1 -1
  70. package/dist/std.d.ts +1 -0
  71. package/dist/std.js +73 -0
  72. package/dist/std.js.map +1 -0
  73. package/dist/subsume.d.ts +39 -0
  74. package/dist/subsume.js +526 -0
  75. package/dist/subsume.js.map +1 -0
  76. package/dist/trim.d.ts +19 -0
  77. package/dist/trim.js +155 -0
  78. package/dist/trim.js.map +1 -0
  79. package/dist/tsconfig.tsbuildinfo +1 -1
  80. package/dist/type.d.ts +17 -1
  81. package/dist/type.js.map +1 -1
  82. package/dist/unify.d.ts +3 -1
  83. package/dist/unify.js +287 -18
  84. package/dist/unify.js.map +1 -1
  85. package/dist/utility.d.ts +9 -1
  86. package/dist/utility.js +122 -1
  87. package/dist/utility.js.map +1 -1
  88. package/dist/val/AggFuncVal.d.ts +33 -0
  89. package/dist/val/AggFuncVal.js +202 -0
  90. package/dist/val/AggFuncVal.js.map +1 -0
  91. package/dist/val/ArithFuncVal.d.ts +31 -0
  92. package/dist/val/ArithFuncVal.js +62 -0
  93. package/dist/val/ArithFuncVal.js.map +1 -0
  94. package/dist/val/BagVal.d.ts +5 -0
  95. package/dist/val/BagVal.js +96 -5
  96. package/dist/val/BagVal.js.map +1 -1
  97. package/dist/val/CloseFuncVal.js +9 -1
  98. package/dist/val/CloseFuncVal.js.map +1 -1
  99. package/dist/val/ConjunctVal.d.ts +1 -1
  100. package/dist/val/ConjunctVal.js +19 -0
  101. package/dist/val/ConjunctVal.js.map +1 -1
  102. package/dist/val/ConstraintVal.d.ts +48 -1
  103. package/dist/val/ConstraintVal.js +1501 -110
  104. package/dist/val/ConstraintVal.js.map +1 -1
  105. package/dist/val/CopyFuncVal.d.ts +1 -2
  106. package/dist/val/CopyFuncVal.js +7 -0
  107. package/dist/val/CopyFuncVal.js.map +1 -1
  108. package/dist/val/Decimal.d.ts +1 -0
  109. package/dist/val/Decimal.js +13 -0
  110. package/dist/val/Decimal.js.map +1 -1
  111. package/dist/val/DeprecateFuncVal.d.ts +11 -0
  112. package/dist/val/DeprecateFuncVal.js +47 -0
  113. package/dist/val/DeprecateFuncVal.js.map +1 -0
  114. package/dist/val/DisjunctVal.js +130 -21
  115. package/dist/val/DisjunctVal.js.map +1 -1
  116. package/dist/val/EachFuncVal.d.ts +15 -0
  117. package/dist/val/EachFuncVal.js +75 -0
  118. package/dist/val/EachFuncVal.js.map +1 -0
  119. package/dist/val/ExpectVal.d.ts +1 -0
  120. package/dist/val/ExpectVal.js +41 -4
  121. package/dist/val/ExpectVal.js.map +1 -1
  122. package/dist/val/FeatureVal.js +1 -1
  123. package/dist/val/FeatureVal.js.map +1 -1
  124. package/dist/val/FilterFuncVal.d.ts +15 -0
  125. package/dist/val/FilterFuncVal.js +91 -0
  126. package/dist/val/FilterFuncVal.js.map +1 -0
  127. package/dist/val/FuncBaseVal.d.ts +6 -1
  128. package/dist/val/FuncBaseVal.js +178 -2
  129. package/dist/val/FuncBaseVal.js.map +1 -1
  130. package/dist/val/HideFuncVal.js.map +1 -1
  131. package/dist/val/IdFuncVal.d.ts +13 -0
  132. package/dist/val/IdFuncVal.js +54 -0
  133. package/dist/val/IdFuncVal.js.map +1 -0
  134. package/dist/val/JunctionVal.js +7 -1
  135. package/dist/val/JunctionVal.js.map +1 -1
  136. package/dist/val/KeyFuncVal.d.ts +1 -1
  137. package/dist/val/KeyFuncVal.js +38 -30
  138. package/dist/val/KeyFuncVal.js.map +1 -1
  139. package/dist/val/ListVal.js +117 -17
  140. package/dist/val/ListVal.js.map +1 -1
  141. package/dist/val/LowerFuncVal.js.map +1 -1
  142. package/dist/val/MapVal.js +102 -8
  143. package/dist/val/MapVal.js.map +1 -1
  144. package/dist/val/MatchFuncVal.d.ts +15 -0
  145. package/dist/val/MatchFuncVal.js +107 -0
  146. package/dist/val/MatchFuncVal.js.map +1 -0
  147. package/dist/val/MoveFuncVal.js.map +1 -1
  148. package/dist/val/NilVal.js +24 -0
  149. package/dist/val/NilVal.js.map +1 -1
  150. package/dist/val/OpBaseVal.d.ts +1 -1
  151. package/dist/val/OpBaseVal.js +24 -2
  152. package/dist/val/OpBaseVal.js.map +1 -1
  153. package/dist/val/OpenFuncVal.js +4 -1
  154. package/dist/val/OpenFuncVal.js.map +1 -1
  155. package/dist/val/PackFuncVal.d.ts +15 -0
  156. package/dist/val/PackFuncVal.js +108 -0
  157. package/dist/val/PackFuncVal.js.map +1 -0
  158. package/dist/val/PathFuncVal.js.map +1 -1
  159. package/dist/val/PlaceVal.d.ts +13 -0
  160. package/dist/val/PlaceVal.js +131 -0
  161. package/dist/val/PlaceVal.js.map +1 -0
  162. package/dist/val/PlusOpVal.js +11 -2
  163. package/dist/val/PlusOpVal.js.map +1 -1
  164. package/dist/val/PrefFuncVal.js.map +1 -1
  165. package/dist/val/PrefVal.d.ts +2 -2
  166. package/dist/val/PrefVal.js +78 -23
  167. package/dist/val/PrefVal.js.map +1 -1
  168. package/dist/val/RefVal.d.ts +1 -1
  169. package/dist/val/RefVal.js +158 -30
  170. package/dist/val/RefVal.js.map +1 -1
  171. package/dist/val/ReferFuncVal.d.ts +36 -0
  172. package/dist/val/ReferFuncVal.js +303 -0
  173. package/dist/val/ReferFuncVal.js.map +1 -0
  174. package/dist/val/ScalarKindVal.d.ts +1 -2
  175. package/dist/val/ScalarKindVal.js +0 -11
  176. package/dist/val/ScalarKindVal.js.map +1 -1
  177. package/dist/val/TopVal.js.map +1 -1
  178. package/dist/val/TypeFuncVal.js.map +1 -1
  179. package/dist/val/UpperFuncVal.js.map +1 -1
  180. package/dist/val/Val.d.ts +9 -2
  181. package/dist/val/Val.js +150 -4
  182. package/dist/val/Val.js.map +1 -1
  183. package/dist/val/VarVal.js.map +1 -1
  184. package/dist/val/arith.d.ts +6 -0
  185. package/dist/val/arith.js +170 -0
  186. package/dist/val/arith.js.map +1 -0
  187. package/dist/vet.d.ts +45 -0
  188. package/dist/vet.js +776 -0
  189. package/dist/vet.js.map +1 -0
  190. package/dist/walk.d.ts +2 -0
  191. package/dist/walk.js +91 -0
  192. package/dist/walk.js.map +1 -0
  193. package/grammar/aontu.gbnf +130 -0
  194. package/grammar/aontu.lark +113 -0
  195. package/package.json +30 -15
  196. package/skill/SKILL.md +37 -0
  197. package/skill/error-codes.md +62 -0
  198. package/skill/examples.md +99 -0
  199. package/skill/grammar-card.md +57 -0
  200. package/src/agentsmd.ts +135 -0
  201. package/src/aontu.ts +192 -4
  202. package/src/cli.ts +2858 -71
  203. package/src/ctx.ts +81 -0
  204. package/src/diff.ts +196 -0
  205. package/src/err.ts +52 -8
  206. package/src/graph.ts +135 -0
  207. package/src/hcanon.ts +169 -0
  208. package/src/hints.ts +271 -5
  209. package/src/jsonschema.ts +511 -0
  210. package/src/lang.ts +779 -37
  211. package/src/lsp.ts +281 -47
  212. package/src/mcp-server.ts +187 -0
  213. package/src/mcp.ts +993 -0
  214. package/src/mod-tool.ts +679 -0
  215. package/src/mod.ts +344 -0
  216. package/src/patch.ts +624 -0
  217. package/src/provenance.ts +430 -0
  218. package/src/query.ts +379 -0
  219. package/src/reach.ts +184 -0
  220. package/src/relation.ts +395 -0
  221. package/src/report-sarif.ts +137 -0
  222. package/src/site.ts +36 -1
  223. package/src/std.ts +73 -0
  224. package/src/subsume.ts +690 -0
  225. package/src/trim.ts +195 -0
  226. package/src/tsconfig.json +10 -4
  227. package/src/type.ts +51 -2
  228. package/src/unify.ts +311 -16
  229. package/src/utility.ts +139 -1
  230. package/src/val/AggFuncVal.ts +319 -0
  231. package/src/val/ArithFuncVal.ts +108 -0
  232. package/src/val/BagVal.ts +101 -4
  233. package/src/val/CloseFuncVal.ts +9 -1
  234. package/src/val/ConjunctVal.ts +20 -0
  235. package/src/val/ConstraintVal.ts +1699 -116
  236. package/src/val/CopyFuncVal.ts +7 -1
  237. package/src/val/Decimal.ts +15 -0
  238. package/src/val/DeprecateFuncVal.ts +84 -0
  239. package/src/val/DisjunctVal.ts +139 -28
  240. package/src/val/EachFuncVal.ts +133 -0
  241. package/src/val/ExpectVal.ts +43 -6
  242. package/src/val/FeatureVal.ts +1 -1
  243. package/src/val/FilterFuncVal.ts +154 -0
  244. package/src/val/FuncBaseVal.ts +200 -3
  245. package/src/val/HideFuncVal.ts +0 -2
  246. package/src/val/IdFuncVal.ts +91 -0
  247. package/src/val/JunctionVal.ts +7 -1
  248. package/src/val/KeyFuncVal.ts +39 -35
  249. package/src/val/ListVal.ts +125 -18
  250. package/src/val/LowerFuncVal.ts +0 -1
  251. package/src/val/MapVal.ts +110 -8
  252. package/src/val/MatchFuncVal.ts +176 -0
  253. package/src/val/MoveFuncVal.ts +0 -2
  254. package/src/val/NilVal.ts +25 -0
  255. package/src/val/OpBaseVal.ts +26 -3
  256. package/src/val/OpenFuncVal.ts +4 -2
  257. package/src/val/PackFuncVal.ts +175 -0
  258. package/src/val/PathFuncVal.ts +0 -1
  259. package/src/val/PlaceVal.ts +193 -0
  260. package/src/val/PlusOpVal.ts +11 -2
  261. package/src/val/PrefFuncVal.ts +0 -1
  262. package/src/val/PrefVal.ts +79 -36
  263. package/src/val/RefVal.ts +163 -30
  264. package/src/val/ReferFuncVal.ts +387 -0
  265. package/src/val/ScalarKindVal.ts +0 -13
  266. package/src/val/TopVal.ts +0 -1
  267. package/src/val/TypeFuncVal.ts +0 -2
  268. package/src/val/UpperFuncVal.ts +0 -1
  269. package/src/val/Val.ts +213 -3
  270. package/src/val/VarVal.ts +0 -1
  271. package/src/val/arith.ts +316 -0
  272. package/src/vet.ts +992 -0
  273. package/src/walk.ts +99 -0
@@ -0,0 +1,511 @@
1
+ /* Copyright (c) 2025 Richard Rodger, MIT License */
2
+
3
+ // JSON SCHEMA EXPORT (the review's finding I / SUPPORT.md act 2).
4
+ //
5
+ // "JSON Schema's decisive 2026 advantage is not its validator ecosystem
6
+ // -- it is that every major LLM provider's structured-output API
7
+ // natively constrains generation to JSON Schema." Without an export,
8
+ // Aontu cannot ride that path and then apply `vet` as the semantic gate
9
+ // -- the hybrid an enterprise would actually deploy -- and an MCP tool's
10
+ // `inputSchema`, which the protocol REQUIRES to be JSON Schema, cannot
11
+ // be derived from an Aontu tool model at all (use case 09's interop
12
+ // wall).
13
+ //
14
+ // THE MAPPING IS LOSSY, AND SAYS SO PER CONSTRUCT. That is the whole
15
+ // design. Aontu's constraint algebra maps cleanly onto JSON Schema's
16
+ // core -- bounds, patterns, lengths, enums, required and closed -- and
17
+ // then stops: an evaluate-only `must()` is opaque by construction, an
18
+ // exact `0d` leaf has no JSON type that preserves it, a cross-field
19
+ // reference is not a property constraint at all, and `unique(k)` has no
20
+ // JSON Schema spelling. A converter that dropped those silently would
21
+ // hand its caller a schema that ADMITS MORE than the model does, which
22
+ // is the failure mode this whole language exists to refuse. So every
23
+ // loss is reported with its path, its construct and its reason, and
24
+ // `--strict` turns the report into a refusal.
25
+ //
26
+ // It exports the UNIFIED value, not the parse: what a document MEANS is
27
+ // what a consumer should be constrained to, and the meaning is what the
28
+ // fixpoint produced.
29
+ //
30
+ // Draft 2020-12, because that is what the structured-output APIs read.
31
+
32
+ import { Aontu } from './aontu'
33
+ import { makeNilErr } from './err'
34
+ import { sizingResidue } from './val/BagVal'
35
+ import { failureFinding } from './vet'
36
+ import type { VetFinding } from './vet'
37
+ import type { TrustOptions } from './type'
38
+ import { anchorAt } from './vet'
39
+
40
+
41
+ export type SchemaLoss = {
42
+ // Where in the document, in the same `$.a.b` spelling every other
43
+ // report uses.
44
+ path: string
45
+ // WHAT could not be carried: the Aontu construct's own name, so a
46
+ // reader can grep their source for it.
47
+ construct: string
48
+ // One sentence: why JSON Schema cannot say it, and what the schema
49
+ // says instead.
50
+ reason: string
51
+ }
52
+
53
+ export type SchemaVerdict = 'ok' | 'lossy' | 'error'
54
+
55
+ export type SchemaReport = {
56
+ // `ok` everything carried; `lossy` the schema is a WEAKER statement
57
+ // than the model; `error` the document does not stand up and there is
58
+ // nothing to export.
59
+ verdict: SchemaVerdict
60
+ // The JSON Schema document. Empty object on `error`.
61
+ schema: any
62
+ // Every construct that could not be carried, in document order.
63
+ lossy: SchemaLoss[]
64
+ // WHY the run could not be made, in vet's finding shape. Present only
65
+ // on `error`, exactly as trim's is.
66
+ errors?: VetFinding[]
67
+ }
68
+
69
+ export type SchemaOptions = {
70
+ // The subtree to export, as a path -- the same anchor `vet --at`
71
+ // takes, and parsed by the same walk, so `--at spec` means what it
72
+ // means everywhere else.
73
+ at?: string
74
+ // Where the document came from, so a relative `@"file"` resolves from
75
+ // its own directory.
76
+ path?: string
77
+ trust?: TrustOptions
78
+ }
79
+
80
+
81
+ const DRAFT = 'https://json-schema.org/draft/2020-12/schema'
82
+
83
+
84
+ function pathText(path: string[]): string {
85
+ return '$' + (0 < path.length ? '.' + path.join('.') : '')
86
+ }
87
+
88
+
89
+ // The exporter's running state: the losses collected so far, in the
90
+ // order the walk meets them.
91
+ type Ctx = { lossy: SchemaLoss[] }
92
+
93
+
94
+ function lose(ctx: Ctx, path: string[], construct: string, reason: string) {
95
+ ctx.lossy.push({ path: pathText(path), construct, reason })
96
+ }
97
+
98
+
99
+ // The JSON type for a kind marker, by the marker CLASS's name -- the
100
+ // same load-bearing name canon renders (ScalarKindVal). `number` and
101
+ // its four leaves all become JSON's two numeric types; the exactness of
102
+ // `biginteger`/`bigdecimal` has no JSON type at all, which is a loss the
103
+ // caller is told about rather than a silent widening.
104
+ const KIND_TYPE: Record<string, string> = {
105
+ String: 'string',
106
+ Boolean: 'boolean',
107
+ Integer: 'integer',
108
+ BigInteger: 'integer',
109
+ Float: 'number',
110
+ BigDecimal: 'number',
111
+ Number: 'number',
112
+ }
113
+
114
+
115
+ // The JSON value of a concrete scalar, for `const`, `enum` and
116
+ // `default`. An exact leaf renders through its own digits rather than
117
+ // through binary64 -- and is reported as a loss where it is emitted,
118
+ // because JSON's number has no exactness to receive it.
119
+ function scalarJson(v: any): any {
120
+ if (v.isBigInteger) {
121
+ return Number(v.peg)
122
+ }
123
+ if (v.isBigDecimal) {
124
+ return Number(v.peg.toString())
125
+ }
126
+ return v.peg
127
+ }
128
+
129
+
130
+ function scalarType(v: any): string {
131
+ if (v.isBigDecimal) {
132
+ return 'number'
133
+ }
134
+ if (v.isInteger || v.isBigInteger) {
135
+ return 'integer'
136
+ }
137
+ const t = typeof v.peg
138
+ return 'number' === t ? 'number' : 'boolean' === t ? 'boolean' : 'string'
139
+ }
140
+
141
+
142
+ // The constraint residual's atoms, onto JSON Schema's keywords. The
143
+ // three that map exactly (bounds, pattern, count) go across; the two
144
+ // that cannot (`must`, `unique(k)`) are reported.
145
+ function fromConstraint(ctx: Ctx, path: string[], c: any): any {
146
+ const out: any = {}
147
+
148
+ if (null != c.kind && null != KIND_TYPE[c.kind.name]) {
149
+ out.type = KIND_TYPE[c.kind.name]
150
+ }
151
+ else if ('string' === c.domain) {
152
+ out.type = 'string'
153
+ }
154
+ else if ('number' === c.domain) {
155
+ out.type = 'number'
156
+ }
157
+
158
+ // Bounds. An OPEN endpoint is JSON Schema's exclusive form, which is
159
+ // a keyword of its own in 2020-12 rather than the boolean flag draft-4
160
+ // used.
161
+ if (null != c.lo) {
162
+ out[c.lo.open ? 'exclusiveMinimum' : 'minimum'] = scalarJson(c.lo.v)
163
+ }
164
+ if (null != c.hi) {
165
+ out[c.hi.open ? 'exclusiveMaximum' : 'maximum'] = scalarJson(c.hi.v)
166
+ }
167
+
168
+ // Exclusions. `neq(1,2)` is "not one of these", which is exactly
169
+ // `not: {enum: [...]}`.
170
+ if (0 < c.neqs.length) {
171
+ out.not = { enum: c.neqs.map(scalarJson) }
172
+ }
173
+
174
+ // Patterns. Aontu DEFINES its portable subset (`re`'s abbreviations
175
+ // mean the same in both ports); every member of that subset is also
176
+ // an ECMA-262 regular expression, which is what JSON Schema's
177
+ // `pattern` is read as, so this crosses without translation. More
178
+ // than one pattern needs `allOf`: `pattern` is a single keyword.
179
+ if (1 === c.res.length) {
180
+ out.pattern = c.res[0].src
181
+ }
182
+ else if (1 < c.res.length) {
183
+ out.allOf = c.res.map((r: any) => ({ pattern: r.src }))
184
+ }
185
+
186
+ // A COUNT is a length or a size depending on what is counted, and the
187
+ // residual does not always know which. Where the domain says string,
188
+ // it is minLength/maxLength; otherwise the container keywords, since
189
+ // a count atom on a container is what `length` overwhelmingly means.
190
+ if (null != c.count) {
191
+ const lo = null == c.count.lo ? undefined : scalarJson(c.count.lo.v)
192
+ const hi = null == c.count.hi ? undefined : scalarJson(c.count.hi.v)
193
+ const str = 'string' === c.domain || 'string' === out.type
194
+ if (null != lo) {
195
+ out[str ? 'minLength' : 'minItems'] = lo
196
+ }
197
+ if (null != hi) {
198
+ out[str ? 'maxLength' : 'maxItems'] = hi
199
+ }
200
+ if (!str && null == c.domain) {
201
+ lose(ctx, path, 'length',
202
+ 'a count with no domain is exported as minItems/maxItems; ' +
203
+ 'JSON Schema has no keyword that counts a string OR a container')
204
+ }
205
+ }
206
+
207
+ if (c.uniq) {
208
+ out.uniqueItems = true
209
+ }
210
+
211
+ for (const key of c.uniqBy) {
212
+ lose(ctx, path, 'unique(' + key + ')',
213
+ 'JSON Schema has no uniqueness-by-property keyword; uniqueItems ' +
214
+ 'compares whole items, so this constraint is DROPPED and the ' +
215
+ 'schema admits records sharing a `' + key + '`')
216
+ }
217
+
218
+ if (0 < c.musts.length) {
219
+ lose(ctx, path, 'must',
220
+ 'an evaluate-only check is opaque by construction -- it carries ' +
221
+ 'the author\'s own message and the algebra never reasons about ' +
222
+ 'it -- so it is DROPPED and the schema admits values `vet` refuses')
223
+ }
224
+
225
+ return out
226
+ }
227
+
228
+
229
+ // The exporter proper. Every arm answers a schema; the ones that cannot
230
+ // answer honestly report a loss and fall back to `{}`, which admits
231
+ // anything -- the safe direction for a document that will be checked
232
+ // again by `vet`, and the direction the loss report exists to make
233
+ // visible.
234
+ // NO DEFENSIVE GUARD ON `v`. Every caller walks a bag's own children
235
+ // and a bag holds Vals, so a non-Val here is not a degenerate input,
236
+ // it is a bug in this file -- and an unreachable `if` is a branch arm
237
+ // the ADR-002 gate counts and no `node:coverage ignore` suppresses
238
+ // (the marker drops LINES, not branches). Go's twin keeps its `nil ==
239
+ // v` arm because a missing map key there yields a typed nil rather
240
+ // than an absent property.
241
+ function fromVal(ctx: Ctx, path: string[], v: any): any {
242
+ // A preference is its inner value plus a DEFAULT. JSON Schema's
243
+ // `default` is annotation rather than constraint -- it does not
244
+ // validate -- which is exactly what a preference is when something
245
+ // else supplies the value.
246
+ if (true === v.isPref) {
247
+ const inner = fromVal(ctx, path, v.peg)
248
+ const gen = generated(v.peg)
249
+ return undefined === gen ? inner : { ...inner, default: gen }
250
+ }
251
+
252
+ if (true === v.isDisjunct && Array.isArray(v.peg)) {
253
+ return fromDisjunct(ctx, path, v)
254
+ }
255
+
256
+ if (true === v.isConstraint) {
257
+ return fromConstraint(ctx, path, v)
258
+ }
259
+
260
+ // A SIZING RESIDUE is a container and its own sizing atom, kept
261
+ // together because more members could still change the atom's reading
262
+ // (use-cases/BUGS.md §16). Both halves are exportable and both must
263
+ // be: the container gives the shape, the atom gives `uniqueItems` and
264
+ // the length keywords, and a walk that saw only "a conjunct" would
265
+ // report the whole field as unresolved and admit anything.
266
+ const residue = sizingResidue(v)
267
+ if (undefined !== residue) {
268
+ return {
269
+ ...fromVal(ctx, path, residue.bag),
270
+ ...fromConstraint(ctx, path, residue.con),
271
+ }
272
+ }
273
+
274
+ if (true === v.isMap) {
275
+ return fromMap(ctx, path, v)
276
+ }
277
+
278
+ if (true === v.isList) {
279
+ return fromList(ctx, path, v)
280
+ }
281
+
282
+ if (true === v.isScalarKind) {
283
+ const t = KIND_TYPE[v.peg?.name]
284
+ if ('BigInteger' === v.peg?.name || 'BigDecimal' === v.peg?.name) {
285
+ lose(ctx, path, v.peg.name.toLowerCase(),
286
+ 'JSON has one number type and it is binary64, so the EXACTNESS ' +
287
+ 'this leaf exists for cannot be carried; the schema says ' +
288
+ '"' + t + '" and a consumer may round')
289
+ }
290
+ // KIND_TYPE is TOTAL over the kinds a marker can carry, so there is
291
+ // no miss to fall back from -- and a fallback that cannot be taken
292
+ // is a branch arm the coverage gate counts. Go's twin keeps its
293
+ // empty-map arm, which its own ignore mechanism can carry.
294
+ return { type: t }
295
+ }
296
+
297
+ if (true === v.isNull) {
298
+ return { type: 'null' }
299
+ }
300
+
301
+ if (true === v.isTop) {
302
+ return {}
303
+ }
304
+
305
+ if (true === v.isScalar) {
306
+ if (v.isBigInteger || v.isBigDecimal) {
307
+ lose(ctx, path, 'exact literal',
308
+ 'JSON has one number type and it is binary64, so this exact ' +
309
+ 'value is emitted as the nearest JSON number')
310
+ }
311
+ return { const: scalarJson(v), type: scalarType(v) }
312
+ }
313
+
314
+ // Everything else is residue: a reference that did not resolve, a
315
+ // function or operator still waiting, a nil. None of them is a
316
+ // property constraint, and guessing one would be inventing a promise.
317
+ lose(ctx, path, residueName(v),
318
+ 'this is not a value yet, so there is nothing to constrain a ' +
319
+ 'consumer to; the schema admits anything here')
320
+ return {}
321
+ }
322
+
323
+
324
+ function residueName(v: any): string {
325
+ return true === v.isNil ? 'nil' :
326
+ true === v.isRef ? 'reference' :
327
+ true === v.isFunc ? v.funcname() :
328
+ 'unresolved'
329
+ }
330
+
331
+
332
+ // The generated JSON of a value, or undefined where it does not
333
+ // generate. Used for `default` and for `enum` members: both are VALUES
334
+ // in the schema, so a member that is itself a shape has none to give.
335
+ function generated(v: any): any {
336
+ // No try/catch: a collecting context RECORDS a failed generation on
337
+ // itself instead of throwing, which is the whole point of the mode.
338
+ // Neither `vet` nor the Go twin (`schemaGenerated`) wraps this
339
+ // either, and an untakeable catch is a branch arm the gate counts.
340
+ const a0 = new Aontu()
341
+ const ctx = a0.ctx({ collect: true })
342
+ const out = v.gen(ctx)
343
+ return 0 === ctx.err.length ? out : undefined
344
+ }
345
+
346
+
347
+ // A disjunction of CONCRETE members is an enum -- the shape a
348
+ // structured-output API constrains best. Anything else is `anyOf`. A
349
+ // preferred member contributes the `default` either way, which is how
350
+ // `*"a"|"b"|"c"` reaches a provider as a defaulted enum.
351
+ function fromDisjunct(ctx: Ctx, path: string[], v: any): any {
352
+ const members: any[] = v.peg
353
+ let def: any = undefined
354
+
355
+ for (const m of members) {
356
+ if (true === m?.isPref && undefined === def) {
357
+ def = generated(m.peg)
358
+ }
359
+ }
360
+
361
+ const bare = members.map((m: any) => true === m?.isPref ? m.peg : m)
362
+ const consts = bare.map((m: any) =>
363
+ true === m?.isScalar && true !== m?.isNil ? scalarJson(m) : undefined)
364
+
365
+ const out: any = consts.every((c: any) => undefined !== c) ?
366
+ { enum: consts } :
367
+ { anyOf: bare.map((m: any) => fromVal(ctx, path, m)) }
368
+
369
+ return undefined === def ? out : { ...out, default: def }
370
+ }
371
+
372
+
373
+ function fromMap(ctx: Ctx, path: string[], v: any): any {
374
+ const props: Record<string, any> = {}
375
+ const required: string[] = []
376
+ const optional: string[] = v.optionalKeys
377
+ let spread: any = undefined
378
+
379
+ for (const key of Object.keys(v.peg).sort()) {
380
+ const child: any = v.peg[key]
381
+
382
+ // A hidden child does not generate, so it is not part of the value
383
+ // a consumer produces -- and a schema that demanded it would refuse
384
+ // every correct document.
385
+ if (true === child?.mark?.hide) {
386
+ lose(ctx, [...path, key], 'hide',
387
+ 'a hidden entry is not generated, so it is omitted from the ' +
388
+ 'schema; a consumer is neither asked for it nor allowed to know ' +
389
+ 'about it')
390
+ continue
391
+ }
392
+
393
+ props[key] = fromVal(ctx, [...path, key], child)
394
+ if (!optional.includes(key)) {
395
+ required.push(key)
396
+ }
397
+ }
398
+
399
+ const spr: any = v.spread?.cj
400
+ if (null != spr) {
401
+ spread = fromVal(ctx, [...path, '&'], spr)
402
+ }
403
+
404
+ const out: any = { type: 'object', properties: props }
405
+ if (0 < required.length) {
406
+ out.required = required
407
+ }
408
+
409
+ // CLOSEDNESS IS THE ONE THING JSON SCHEMA SAYS EXACTLY AS AONTU DOES.
410
+ // A closed map is `additionalProperties: false`; an open one leaves
411
+ // the keyword off, since JSON Schema's default is already open.
412
+ if (true === v.closed) {
413
+ out.additionalProperties = false
414
+ if (null != spread) {
415
+ lose(ctx, path, '&:',
416
+ 'a spread on a CLOSED map constrains keys that cannot exist, ' +
417
+ 'so additionalProperties:false stands alone and the template ' +
418
+ 'is dropped')
419
+ }
420
+ }
421
+ else if (null != spread) {
422
+ // A spread IS additionalProperties-with-a-schema: every key the
423
+ // author did not name must still satisfy the template.
424
+ out.additionalProperties = spread
425
+ }
426
+
427
+ return out
428
+ }
429
+
430
+
431
+ function fromList(ctx: Ctx, path: string[], v: any): any {
432
+ const els: any[] = v.peg
433
+ const spr: any = v.spread?.cj
434
+
435
+ // A list with a spread template is homogeneous: every element, named
436
+ // or not, satisfies it. That is `items`.
437
+ if (null != spr) {
438
+ const out: any = {
439
+ type: 'array',
440
+ items: fromVal(ctx, [...path, '&'], spr),
441
+ }
442
+ if (0 < els.length) {
443
+ out.minItems = els.length
444
+ }
445
+ return out
446
+ }
447
+
448
+ // A written list literal is a TUPLE: position by position, and no
449
+ // more. 2020-12 spells that `prefixItems` plus `items: false`.
450
+ return {
451
+ type: 'array',
452
+ prefixItems: els.map((el: any, i: number) =>
453
+ fromVal(ctx, [...path, String(i)], el)),
454
+ items: false,
455
+ minItems: els.length,
456
+ }
457
+ }
458
+
459
+
460
+ // The verb. Evaluate, anchor, walk, report.
461
+ export function jsonSchema(src: string, options?: SchemaOptions): SchemaReport {
462
+ const opts = options ?? {}
463
+ const aontu = new Aontu(null == opts.trust ? {} : { trust: opts.trust })
464
+
465
+ // COLLECT MODE, so a syntax error arrives on the context rather than
466
+ // as a throw -- the same failure Go's `parseEntry` hands back as an
467
+ // error, reached by the branch below. No try/catch here for the same
468
+ // reason `vet` has none: the mode exists so the failure can be
469
+ // reported rather than escape, and a catch that cannot be entered is
470
+ // a branch arm the ADR-002 gate counts.
471
+ const actx = aontu.ctx({ collect: true })
472
+ const root: any = aontu.unify(src, { path: opts.path, collect: true }, actx)
473
+
474
+ // A nil root always arrives with its reason collected beside it, which
475
+ // is failureFinding's stated precondition (ts/src/vet.ts: "ctx.err is
476
+ // never empty at a call site").
477
+ if (0 < actx.err.length || true === root?.isNil) {
478
+ return {
479
+ verdict: 'error', schema: {}, lossy: [],
480
+ errors: [failureFinding(actx, opts.path, root)],
481
+ }
482
+ }
483
+
484
+ let node: any = root
485
+ const anchor: string[] = []
486
+ if (null != opts.at && '' !== opts.at) {
487
+ const found: any = anchorAt(root, opts.at)
488
+ if (null == found) {
489
+ // The anchor names nothing. Reported as a `no_path` nil through
490
+ // the same finding shape every other refusal here uses, so a
491
+ // caller reads one error format rather than two.
492
+ const nil: any = makeNilErr(actx, 'no_path', root, undefined, 'at')
493
+ actx.err.push(nil)
494
+ return {
495
+ verdict: 'error', schema: {}, lossy: [],
496
+ errors: [failureFinding(actx, opts.path, root)],
497
+ }
498
+ }
499
+ node = found
500
+ anchor.push(...opts.at.replace(/^\$/, '').split('.').filter((p) => '' !== p))
501
+ }
502
+
503
+ const ctx: Ctx = { lossy: [] }
504
+ const body = fromVal(ctx, anchor, node)
505
+
506
+ return {
507
+ verdict: 0 < ctx.lossy.length ? 'lossy' : 'ok',
508
+ schema: { $schema: DRAFT, ...body },
509
+ lossy: ctx.lossy,
510
+ }
511
+ }