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,552 @@
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
+ // A path value is its address string at the JSON boundary
113
+ // (docs/design/PATHS.0.md), so the projection says `string` -- the
114
+ // same lossy-projection rule the exact numeric leaves follow.
115
+ Path: 'string',
116
+ }
117
+
118
+
119
+ // The JSON value of a concrete scalar, for `const`, `enum` and
120
+ // `default`. An exact leaf renders through its own digits rather than
121
+ // through binary64 -- and is reported as a loss where it is emitted,
122
+ // because JSON's number has no exactness to receive it.
123
+ function scalarJson(v: any): any {
124
+ if (v.isBigInteger) {
125
+ return Number(v.peg)
126
+ }
127
+ if (v.isBigDecimal) {
128
+ return Number(v.peg.toString())
129
+ }
130
+ return v.peg
131
+ }
132
+
133
+
134
+ function scalarType(v: any): string {
135
+ if (v.isBigDecimal) {
136
+ return 'number'
137
+ }
138
+ if (v.isInteger || v.isBigInteger) {
139
+ return 'integer'
140
+ }
141
+ const t = typeof v.peg
142
+ return 'number' === t ? 'number' : 'boolean' === t ? 'boolean' : 'string'
143
+ }
144
+
145
+
146
+ // The constraint residual's atoms, onto JSON Schema's keywords. The
147
+ // three that map exactly (bounds, pattern, count) go across; the two
148
+ // that cannot (`must`, `unique(k)`) are reported.
149
+ function fromConstraint(ctx: Ctx, path: string[], c: any): any {
150
+ const out: any = {}
151
+
152
+ if (null != c.kind && null != KIND_TYPE[c.kind.name]) {
153
+ out.type = KIND_TYPE[c.kind.name]
154
+ }
155
+ else if ('string' === c.domain) {
156
+ out.type = 'string'
157
+ }
158
+ else if ('number' === c.domain) {
159
+ out.type = 'number'
160
+ }
161
+
162
+ // Bounds. An OPEN endpoint is JSON Schema's exclusive form, which is
163
+ // a keyword of its own in 2020-12 rather than the boolean flag draft-4
164
+ // used.
165
+ if (null != c.lo) {
166
+ out[c.lo.open ? 'exclusiveMinimum' : 'minimum'] = scalarJson(c.lo.v)
167
+ }
168
+ if (null != c.hi) {
169
+ out[c.hi.open ? 'exclusiveMaximum' : 'maximum'] = scalarJson(c.hi.v)
170
+ }
171
+
172
+ // Exclusions. `neq(1,2)` is "not one of these", which is exactly
173
+ // `not: {enum: [...]}`.
174
+ if (0 < c.neqs.length) {
175
+ out.not = { enum: c.neqs.map(scalarJson) }
176
+ }
177
+
178
+ // Patterns. Aontu DEFINES its portable subset (`re`'s abbreviations
179
+ // mean the same in both ports); every member of that subset is also
180
+ // an ECMA-262 regular expression, which is what JSON Schema's
181
+ // `pattern` is read as, so this crosses without translation. More
182
+ // than one pattern needs `allOf`: `pattern` is a single keyword.
183
+ if (1 === c.res.length) {
184
+ out.pattern = c.res[0].src
185
+ }
186
+ else if (1 < c.res.length) {
187
+ out.allOf = c.res.map((r: any) => ({ pattern: r.src }))
188
+ }
189
+
190
+ // A COUNT is a length or a size depending on what is counted, and the
191
+ // residual does not always know which. Where the domain says string,
192
+ // it is minLength/maxLength; otherwise the container keywords, since
193
+ // a count atom on a container is what `length` overwhelmingly means.
194
+ if (null != c.count) {
195
+ const lo = null == c.count.lo ? undefined : scalarJson(c.count.lo.v)
196
+ const hi = null == c.count.hi ? undefined : scalarJson(c.count.hi.v)
197
+ const str = 'string' === c.domain || 'string' === out.type
198
+ if (null != lo) {
199
+ out[str ? 'minLength' : 'minItems'] = lo
200
+ }
201
+ if (null != hi) {
202
+ out[str ? 'maxLength' : 'maxItems'] = hi
203
+ }
204
+ if (!str && null == c.domain) {
205
+ lose(ctx, path, 'length',
206
+ 'a count with no domain is exported as minItems/maxItems; ' +
207
+ 'JSON Schema has no keyword that counts a string OR a container')
208
+ }
209
+ }
210
+
211
+ if (c.uniq) {
212
+ out.uniqueItems = true
213
+ }
214
+
215
+ for (const key of c.uniqBy) {
216
+ lose(ctx, path, 'unique(' + key + ')',
217
+ 'JSON Schema has no uniqueness-by-property keyword; uniqueItems ' +
218
+ 'compares whole items, so this constraint is DROPPED and the ' +
219
+ 'schema admits records sharing a `' + key + '`')
220
+ }
221
+
222
+ if (0 < c.musts.length) {
223
+ lose(ctx, path, 'must',
224
+ 'an evaluate-only check is opaque by construction -- it carries ' +
225
+ 'the author\'s own message and the algebra never reasons about ' +
226
+ 'it -- so it is DROPPED and the schema admits values `vet` refuses')
227
+ }
228
+
229
+ return out
230
+ }
231
+
232
+
233
+ // The exporter proper. Every arm answers a schema; the ones that cannot
234
+ // answer honestly report a loss and fall back to `{}`, which admits
235
+ // anything -- the safe direction for a document that will be checked
236
+ // again by `vet`, and the direction the loss report exists to make
237
+ // visible.
238
+ // NO DEFENSIVE GUARD ON `v`. Every caller walks a bag's own children
239
+ // and a bag holds Vals, so a non-Val here is not a degenerate input,
240
+ // it is a bug in this file -- and an unreachable `if` is a branch arm
241
+ // the ADR-002 gate counts and no `node:coverage ignore` suppresses
242
+ // (the marker drops LINES, not branches). Go's twin keeps its `nil ==
243
+ // v` arm because a missing map key there yields a typed nil rather
244
+ // than an absent property.
245
+ // DEPRECATION IS AN ANNOTATION, and 2020-12 has one: `deprecated`.
246
+ // The export used to drop the whole record in SILENCE -- no keyword,
247
+ // and no loss line even under --strict -- which is the one thing the
248
+ // verb's own contract says it never does (use-cases/BUGS.md §56).
249
+ //
250
+ // The boolean crosses faithfully. The record's STRINGS (msg, use,
251
+ // since) have no home in 2020-12, so they are reported as a loss
252
+ // rather than invented into `description`: this exporter emits no
253
+ // `description` anywhere, and quietly making it mean "deprecation
254
+ // note" would be a mapping a consumer cannot undo.
255
+ function fromVal(ctx: Ctx, path: string[], v: any): any {
256
+ const out = fromValInner(ctx, path, v)
257
+
258
+ const dep = v?.deprecation
259
+ if (null != dep && null != out && 'object' === typeof out) {
260
+ const said = DEPRECATION_TEXT.filter((k) => null != dep[k])
261
+ if (0 < said.length) {
262
+ lose(ctx, path, 'deprecate',
263
+ 'JSON Schema 2020-12 has the `deprecated` flag and no field for ' +
264
+ 'what it SAYS, so ' + said.join('/') + ' cannot cross; the ' +
265
+ 'schema marks the property deprecated and a consumer must read ' +
266
+ 'the model for the reason')
267
+ }
268
+ return { ...out, deprecated: true }
269
+ }
270
+
271
+ return out
272
+ }
273
+
274
+
275
+ // The record's text keys -- the ones with nothing to carry them.
276
+ // `DEPRECATION_KEYS` in DeprecateFuncVal.ts is the authority on what a
277
+ // record may hold; this is that list, and a key added there without a
278
+ // JSON Schema home belongs here too.
279
+ const DEPRECATION_TEXT = ['msg', 'use', 'since']
280
+
281
+
282
+ function fromValInner(ctx: Ctx, path: string[], v: any): any {
283
+ // A preference is its inner value plus a DEFAULT. JSON Schema's
284
+ // `default` is annotation rather than constraint -- it does not
285
+ // validate -- which is exactly what a preference is when something
286
+ // else supplies the value.
287
+ if (true === v.isPref) {
288
+ const inner = fromVal(ctx, path, v.peg)
289
+ const gen = generated(v.peg)
290
+ return undefined === gen ? inner : { ...inner, default: gen }
291
+ }
292
+
293
+ if (true === v.isDisjunct && Array.isArray(v.peg)) {
294
+ return fromDisjunct(ctx, path, v)
295
+ }
296
+
297
+ if (true === v.isConstraint) {
298
+ return fromConstraint(ctx, path, v)
299
+ }
300
+
301
+ // A SIZING RESIDUE is a container and its own sizing atom, kept
302
+ // together because more members could still change the atom's reading
303
+ // (use-cases/BUGS.md §16). Both halves are exportable and both must
304
+ // be: the container gives the shape, the atom gives `uniqueItems` and
305
+ // the length keywords, and a walk that saw only "a conjunct" would
306
+ // report the whole field as unresolved and admit anything.
307
+ const residue = sizingResidue(v)
308
+ if (undefined !== residue) {
309
+ return {
310
+ ...fromVal(ctx, path, residue.bag),
311
+ ...fromConstraint(ctx, path, residue.con),
312
+ }
313
+ }
314
+
315
+ if (true === v.isMap) {
316
+ return fromMap(ctx, path, v)
317
+ }
318
+
319
+ if (true === v.isList) {
320
+ return fromList(ctx, path, v)
321
+ }
322
+
323
+ if (true === v.isScalarKind) {
324
+ const t = KIND_TYPE[v.peg?.name]
325
+ if ('BigInteger' === v.peg?.name || 'BigDecimal' === v.peg?.name) {
326
+ lose(ctx, path, v.peg.name.toLowerCase(),
327
+ 'JSON has one number type and it is binary64, so the EXACTNESS ' +
328
+ 'this leaf exists for cannot be carried; the schema says ' +
329
+ '"' + t + '" and a consumer may round')
330
+ }
331
+ // KIND_TYPE is TOTAL over the kinds a marker can carry, so there is
332
+ // no miss to fall back from -- and a fallback that cannot be taken
333
+ // is a branch arm the coverage gate counts. Go's twin keeps its
334
+ // empty-map arm, which its own ignore mechanism can carry.
335
+ return { type: t }
336
+ }
337
+
338
+ if (true === v.isNull) {
339
+ return { type: 'null' }
340
+ }
341
+
342
+ if (true === v.isTop) {
343
+ return {}
344
+ }
345
+
346
+ if (true === v.isScalar) {
347
+ if (v.isBigInteger || v.isBigDecimal) {
348
+ lose(ctx, path, 'exact literal',
349
+ 'JSON has one number type and it is binary64, so this exact ' +
350
+ 'value is emitted as the nearest JSON number')
351
+ }
352
+ return { const: scalarJson(v), type: scalarType(v) }
353
+ }
354
+
355
+ // Everything else is residue: a reference that did not resolve, a
356
+ // function or operator still waiting, a nil. None of them is a
357
+ // property constraint, and guessing one would be inventing a promise.
358
+ lose(ctx, path, residueName(v),
359
+ 'this is not a value yet, so there is nothing to constrain a ' +
360
+ 'consumer to; the schema admits anything here')
361
+ return {}
362
+ }
363
+
364
+
365
+ function residueName(v: any): string {
366
+ return true === v.isNil ? 'nil' :
367
+ true === v.isRef ? 'reference' :
368
+ true === v.isFunc ? v.funcname() :
369
+ 'unresolved'
370
+ }
371
+
372
+
373
+ // The generated JSON of a value, or undefined where it does not
374
+ // generate. Used for `default` and for `enum` members: both are VALUES
375
+ // in the schema, so a member that is itself a shape has none to give.
376
+ function generated(v: any): any {
377
+ // No try/catch: a collecting context RECORDS a failed generation on
378
+ // itself instead of throwing, which is the whole point of the mode.
379
+ // Neither `vet` nor the Go twin (`schemaGenerated`) wraps this
380
+ // either, and an untakeable catch is a branch arm the gate counts.
381
+ const a0 = new Aontu()
382
+ const ctx = a0.ctx({ collect: true })
383
+ const out = v.gen(ctx)
384
+ return 0 === ctx.err.length ? out : undefined
385
+ }
386
+
387
+
388
+ // A disjunction of CONCRETE members is an enum -- the shape a
389
+ // structured-output API constrains best. Anything else is `anyOf`. A
390
+ // preferred member contributes the `default` either way, which is how
391
+ // `*"a"|"b"|"c"` reaches a provider as a defaulted enum.
392
+ function fromDisjunct(ctx: Ctx, path: string[], v: any): any {
393
+ const members: any[] = v.peg
394
+ let def: any = undefined
395
+
396
+ for (const m of members) {
397
+ if (true === m?.isPref && undefined === def) {
398
+ def = generated(m.peg)
399
+ }
400
+ }
401
+
402
+ const bare = members.map((m: any) => true === m?.isPref ? m.peg : m)
403
+ const consts = bare.map((m: any) =>
404
+ true === m?.isScalar && true !== m?.isNil ? scalarJson(m) : undefined)
405
+
406
+ const out: any = consts.every((c: any) => undefined !== c) ?
407
+ { enum: consts } :
408
+ { anyOf: bare.map((m: any) => fromVal(ctx, path, m)) }
409
+
410
+ return undefined === def ? out : { ...out, default: def }
411
+ }
412
+
413
+
414
+ function fromMap(ctx: Ctx, path: string[], v: any): any {
415
+ const props: Record<string, any> = {}
416
+ const required: string[] = []
417
+ const optional: string[] = v.optionalKeys
418
+ let spread: any = undefined
419
+
420
+ for (const key of Object.keys(v.peg).sort()) {
421
+ const child: any = v.peg[key]
422
+
423
+ // A hidden child does not generate, so it is not part of the value
424
+ // a consumer produces -- and a schema that demanded it would refuse
425
+ // every correct document.
426
+ if (true === child?.mark?.hide) {
427
+ lose(ctx, [...path, key], 'hide',
428
+ 'a hidden entry is not generated, so it is omitted from the ' +
429
+ 'schema; a consumer is neither asked for it nor allowed to know ' +
430
+ 'about it')
431
+ continue
432
+ }
433
+
434
+ props[key] = fromVal(ctx, [...path, key], child)
435
+ if (!optional.includes(key)) {
436
+ required.push(key)
437
+ }
438
+ }
439
+
440
+ const spr: any = v.spread?.cj
441
+ if (null != spr) {
442
+ spread = fromVal(ctx, [...path, '&'], spr)
443
+ }
444
+
445
+ const out: any = { type: 'object', properties: props }
446
+ if (0 < required.length) {
447
+ out.required = required
448
+ }
449
+
450
+ // CLOSEDNESS IS THE ONE THING JSON SCHEMA SAYS EXACTLY AS AONTU DOES.
451
+ // A closed map is `additionalProperties: false`; an open one leaves
452
+ // the keyword off, since JSON Schema's default is already open.
453
+ if (true === v.closed) {
454
+ out.additionalProperties = false
455
+ if (null != spread) {
456
+ lose(ctx, path, '&:',
457
+ 'a spread on a CLOSED map constrains keys that cannot exist, ' +
458
+ 'so additionalProperties:false stands alone and the template ' +
459
+ 'is dropped')
460
+ }
461
+ }
462
+ else if (null != spread) {
463
+ // A spread IS additionalProperties-with-a-schema: every key the
464
+ // author did not name must still satisfy the template.
465
+ out.additionalProperties = spread
466
+ }
467
+
468
+ return out
469
+ }
470
+
471
+
472
+ function fromList(ctx: Ctx, path: string[], v: any): any {
473
+ const els: any[] = v.peg
474
+ const spr: any = v.spread?.cj
475
+
476
+ // A list with a spread template is homogeneous: every element, named
477
+ // or not, satisfies it. That is `items`.
478
+ if (null != spr) {
479
+ const out: any = {
480
+ type: 'array',
481
+ items: fromVal(ctx, [...path, '&'], spr),
482
+ }
483
+ if (0 < els.length) {
484
+ out.minItems = els.length
485
+ }
486
+ return out
487
+ }
488
+
489
+ // A written list literal is a TUPLE: position by position, and no
490
+ // more. 2020-12 spells that `prefixItems` plus `items: false`.
491
+ return {
492
+ type: 'array',
493
+ prefixItems: els.map((el: any, i: number) =>
494
+ fromVal(ctx, [...path, String(i)], el)),
495
+ items: false,
496
+ minItems: els.length,
497
+ }
498
+ }
499
+
500
+
501
+ // The verb. Evaluate, anchor, walk, report.
502
+ export function jsonSchema(src: string, options?: SchemaOptions): SchemaReport {
503
+ const opts = options ?? {}
504
+ const aontu = new Aontu(null == opts.trust ? {} : { trust: opts.trust })
505
+
506
+ // COLLECT MODE, so a syntax error arrives on the context rather than
507
+ // as a throw -- the same failure Go's `parseEntry` hands back as an
508
+ // error, reached by the branch below. No try/catch here for the same
509
+ // reason `vet` has none: the mode exists so the failure can be
510
+ // reported rather than escape, and a catch that cannot be entered is
511
+ // a branch arm the ADR-002 gate counts.
512
+ const actx = aontu.ctx({ collect: true })
513
+ const root: any = aontu.unify(src, { path: opts.path, collect: true }, actx)
514
+
515
+ // A nil root always arrives with its reason collected beside it, which
516
+ // is failureFinding's stated precondition (ts/src/vet.ts: "ctx.err is
517
+ // never empty at a call site").
518
+ if (0 < actx.err.length || true === root?.isNil) {
519
+ return {
520
+ verdict: 'error', schema: {}, lossy: [],
521
+ errors: [failureFinding(actx, opts.path, root)],
522
+ }
523
+ }
524
+
525
+ let node: any = root
526
+ const anchor: string[] = []
527
+ if (null != opts.at && '' !== opts.at) {
528
+ const found: any = anchorAt(root, opts.at)
529
+ if (null == found) {
530
+ // The anchor names nothing. Reported as a `no_path` nil through
531
+ // the same finding shape every other refusal here uses, so a
532
+ // caller reads one error format rather than two.
533
+ const nil: any = makeNilErr(actx, 'no_path', root, undefined, 'at')
534
+ actx.err.push(nil)
535
+ return {
536
+ verdict: 'error', schema: {}, lossy: [],
537
+ errors: [failureFinding(actx, opts.path, root)],
538
+ }
539
+ }
540
+ node = found
541
+ anchor.push(...opts.at.replace(/^\$/, '').split('.').filter((p) => '' !== p))
542
+ }
543
+
544
+ const ctx: Ctx = { lossy: [] }
545
+ const body = fromVal(ctx, anchor, node)
546
+
547
+ return {
548
+ verdict: 0 < ctx.lossy.length ? 'lossy' : 'ok',
549
+ schema: { $schema: DRAFT, ...body },
550
+ lossy: ctx.lossy,
551
+ }
552
+ }