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/dist/lang.js CHANGED
@@ -3,13 +3,39 @@
3
3
  Object.defineProperty(exports, "__esModule", { value: true });
4
4
  exports.Site = exports.Lang = void 0;
5
5
  // import { performance } from 'node:perf_hooks'
6
+ // Named imports, not `import * as`: the namespace form makes tsc emit
7
+ // the __importStar downlevel helper, whose branches no supported Node
8
+ // takes (the same rule cli.ts records). Aliased because `Path` is
9
+ // already the @tabnas/path plugin below.
10
+ const node_fs_1 = require("node:fs");
11
+ const node_path_1 = require("node:path");
6
12
  const jsonic_1 = require("@tabnas/jsonic");
13
+ // THE CONFIG-FORMAT READERS (ADR-012). Each is a jsonic plugin for one
14
+ // format, so an included `.toml` or `.yaml` is parsed by a real parser
15
+ // for that format rather than guessed at by this one. `@tabnas/json` is
16
+ // the strict RFC 8259 reader, used for `.json` and `.jsonld`.
17
+ const sig_1 = require("./sig");
18
+ const json_1 = require("@tabnas/json");
19
+ const toml_1 = require("@tabnas/toml");
20
+ const jsonc_1 = require("@tabnas/jsonc");
21
+ const json5_1 = require("@tabnas/json5");
22
+ const yaml_1 = require("@tabnas/yaml");
23
+ const ini_1 = require("@tabnas/ini");
7
24
  const debug_1 = require("@tabnas/debug");
8
25
  const multisource_1 = require("@tabnas/multisource");
9
26
  // TODO: @tabnas/multisource should support virtual fs
10
27
  const file_1 = require("@tabnas/multisource/resolver/file");
11
28
  const pkg_1 = require("@tabnas/multisource/resolver/pkg");
12
29
  const mem_1 = require("@tabnas/multisource/resolver/mem");
30
+ // The Aontu-source processor, TAKEN RATHER THAN ALIASED. The obvious
31
+ // spelling is the alias `aon: 'jsonic'`, which multisource resolves
32
+ // through its own processor map -- but `jsonic` is a FORMAT NAME in
33
+ // the include table now, so that alias resolved to the plain-jsonic
34
+ // DATA reader and every `.aon` include was suddenly parsed without the
35
+ // language in it. Naming the function leaves nothing to collide with.
36
+ const jsonic_2 = require("@tabnas/multisource/processor/jsonic");
37
+ const std_1 = require("./std");
38
+ const mod_1 = require("./mod");
13
39
  const expr_1 = require("@tabnas/expr");
14
40
  const path_1 = require("@tabnas/path");
15
41
  const type_1 = require("./type");
@@ -41,8 +67,19 @@ const CopyFuncVal_1 = require("./val/CopyFuncVal");
41
67
  const KeyFuncVal_1 = require("./val/KeyFuncVal");
42
68
  const TypeFuncVal_1 = require("./val/TypeFuncVal");
43
69
  const HideFuncVal_1 = require("./val/HideFuncVal");
70
+ const DeprecateFuncVal_1 = require("./val/DeprecateFuncVal");
71
+ const ReferFuncVal_1 = require("./val/ReferFuncVal");
72
+ const GraphAtomVal_1 = require("./val/GraphAtomVal");
73
+ const PackFuncVal_1 = require("./val/PackFuncVal");
74
+ const EachFuncVal_1 = require("./val/EachFuncVal");
75
+ const FilterFuncVal_1 = require("./val/FilterFuncVal");
76
+ const MatchFuncVal_1 = require("./val/MatchFuncVal");
77
+ const ArithFuncVal_1 = require("./val/ArithFuncVal");
78
+ const AggFuncVal_1 = require("./val/AggFuncVal");
79
+ const PlaceVal_1 = require("./val/PlaceVal");
44
80
  const MoveFuncVal_1 = require("./val/MoveFuncVal");
45
81
  const PathFuncVal_1 = require("./val/PathFuncVal");
82
+ const ContainerKindVal_1 = require("./val/ContainerKindVal");
46
83
  const PrefFuncVal_1 = require("./val/PrefFuncVal");
47
84
  const CloseFuncVal_1 = require("./val/CloseFuncVal");
48
85
  const OpenFuncVal_1 = require("./val/OpenFuncVal");
@@ -84,6 +121,12 @@ function bigVal(res) {
84
121
  const CC_0 = 48;
85
122
  const CC_d = 100;
86
123
  const CC_D = 68;
124
+ // THE ALIAS SIGIL. `%` is part of an alias's name, so the name is one
125
+ // lexeme wherever it appears and its meaning is decided by position:
126
+ // a BINDING in key position (`%uint8: …` declares), a USE in value
127
+ // position (`listen: %uint8` refers). docs/design/ALIASES.0.md §4.
128
+ const CC_PCT = 37;
129
+ const ALIAS_RE = /^%[A-Za-z_][A-Za-z0-9_]*/;
87
130
  let AontuJsonic = function AontuLang(jsonic) {
88
131
  jsonic.use(asPlugin(path_1.Path));
89
132
  // Only # line comments are valid Aontu syntax (see
@@ -94,6 +137,35 @@ let AontuJsonic = function AontuLang(jsonic) {
94
137
  terms = dropUnfilled(terms);
95
138
  if (0 === terms.length)
96
139
  return incompleteNil(r, ctx);
140
+ // AN ALIAS IS NOT A PATH SEGMENT. `$.%foo` is refused: the alias
141
+ // namespace and the path namespace are disjoint, and an alias is
142
+ // reached by writing `%foo` and only that.
143
+ //
144
+ // The engine spells an alias reference AS a root reference to the
145
+ // declaration -- which is what gives it order independence and a
146
+ // cycle check shared with paths -- but that is an implementation of
147
+ // the name, not a second way to write it. Left writable, the two
148
+ // spellings would drift apart the moment aliases stop being
149
+ // file-shaped, and `$.%b` inside an included file would reach the
150
+ // INCLUDER's `%b` rather than its own, which is exactly the
151
+ // cross-file capture the sigil exists to prevent.
152
+ // `%foo` lexes to the reference itself, so in `$.%foo` it arrives
153
+ // as a TERM rather than as a string segment -- both shapes are
154
+ // checked, since a quoted `$."%foo"` would arrive as the string.
155
+ // Terms here are always Vals -- dropUnfilled has removed the
156
+ // nulls, and the dot rules never hand over a raw string -- so the
157
+ // shapes are exactly three: a RefVal (peg is the segment array), a
158
+ // StringVal (peg is the segment), and anything else (a numeric or
159
+ // exact segment, which cannot be an alias name).
160
+ for (const t of terms) {
161
+ const segs = Array.isArray(t.peg) ? t.peg :
162
+ ('string' === typeof t.peg ? [t.peg] : []);
163
+ for (const seg of segs) {
164
+ if ('string' === typeof seg && ALIAS_RE.test(seg)) {
165
+ return addsite(new NilVal_1.NilVal({ why: 'alias_in_path' }), r, ctx);
166
+ }
167
+ }
168
+ }
97
169
  return addsite(new RefVal_1.RefVal({ peg: terms, prefix }), r, ctx);
98
170
  };
99
171
  jsonic.options({ comment: { def: null } });
@@ -165,9 +237,41 @@ let AontuJsonic = function AontuLang(jsonic) {
165
237
  check: (lex) => {
166
238
  // Guard first, on char codes: this hook runs at every text
167
239
  // position, and the common case (any run that cannot be a `0d`
168
- // literal) must cost two char reads and no allocation.
240
+ // literal or an alias) must cost two char reads and no
241
+ // allocation.
169
242
  const pnt = lex.pnt;
170
243
  const src = lex.src;
244
+ // AN ALIAS NAME IS CLAIMED WHOLE, for the same reason the `0d`
245
+ // run below is: the text matcher's ender regexp would otherwise
246
+ // carve `%uint8` at the `%` and emit the sigil as its own token,
247
+ // leaving a bare `uint8` behind -- which is exactly the capture
248
+ // the sigil exists to prevent. Claiming it here, before that
249
+ // ender runs, keeps the name one lexeme.
250
+ //
251
+ // The token's SOURCE is the whole `%name`, which is what makes
252
+ // the same lexeme work in both positions: jsonic keys a pair by
253
+ // the token's source text (`0d1: 5` yields the key `0d1`), so a
254
+ // declaration reads as the key `%uint8`, while a value position
255
+ // calls the function below and gets the reference.
256
+ if (CC_PCT === src.charCodeAt(pnt.sI)) {
257
+ const ares = ALIAS_RE.exec(lex.refwd());
258
+ if (null == ares) {
259
+ return undefined;
260
+ }
261
+ const asrc = ares[0];
262
+ const atkn = lex.token('#VL',
263
+ // AN ALIAS REFERENCE IS A PATH REFERENCE. `%uint8` is
264
+ // `$.%uint8`: root-absolute, one segment, spelled with the
265
+ // sigil the declaration is spelled with. Everything the
266
+ // design asks of it -- order independence, alias-of-alias,
267
+ // redeclaration unifying, cycle refusal spanning both
268
+ // namespaces -- is then the reference machinery already in
269
+ // the language, not a second resolver beside it.
270
+ (r, ctx) => addsite(new RefVal_1.RefVal({ peg: [asrc], absolute: true }), r, ctx), asrc, pnt);
271
+ pnt.sI += asrc.length;
272
+ pnt.cI += asrc.length;
273
+ return { done: true, token: atkn };
274
+ }
171
275
  if (CC_0 !== src.charCodeAt(pnt.sI)) {
172
276
  return undefined;
173
277
  }
@@ -201,6 +305,16 @@ let AontuJsonic = function AontuLang(jsonic) {
201
305
  v.site.row = null == r.o0 ? -1 : r.o0.rI;
202
306
  v.site.col = null == r.o0 ? -1 : r.o0.cI;
203
307
  v.site.url = ctx.meta.multisource ? ctx.meta.multisource.path : '';
308
+ // The source text, from the SAME token the row and column above
309
+ // come from. jsonic has carried it all along; not reading it is
310
+ // what left a site uneditable (ts/src/site.ts).
311
+ //
312
+ // A TOKEN WITH NO TEXT IS NO SPAN, in both ports, and the extent is
313
+ // DERIVED from the text rather than read from the token's own `len`
314
+ // — so the Go twin, whose token has no len field, computes the
315
+ // identical number with utf16Len and nothing has to be kept in step.
316
+ v.site.src = null == r.o0 ? '' : r.o0.src;
317
+ v.site.len = '' === v.site.src ? -1 : v.site.src.length;
204
318
  // A keyed rule always carries a path array; a keyless one has none.
205
319
  v.path = r.k ? [...r.k.path] : [];
206
320
  return v;
@@ -264,6 +378,12 @@ help isolate the syntax error.`,
264
378
  },
265
379
  // TODO: FIX: need a TOP instance to hold path
266
380
  'top': { val: () => (0, top_1.top)() },
381
+ // G8 phase 3: the placeholder. A BARE `_` is the hole; `"_"`
382
+ // quoted, and any longer bare word containing it, stay text.
383
+ // Reserving it is a breaking change, pinned by place.tsv.
384
+ '_': {
385
+ val: (r, ctx) => addsite(new PlaceVal_1.PlaceVal({}), r, ctx)
386
+ },
267
387
  }
268
388
  },
269
389
  map: {
@@ -304,6 +424,13 @@ help isolate the syntax error.`,
304
424
  move: MoveFuncVal_1.MoveFuncVal,
305
425
  path: PathFuncVal_1.PathFuncVal,
306
426
  pref: PrefFuncVal_1.PrefFuncVal,
427
+ // First-class paths and the container kinds
428
+ // (docs/design/PATHS.0.md). `path(p)` CAPTURES a path as a value;
429
+ // `path()`, `map()` and `list()` are kinds -- the vacuous
430
+ // constructor call admits its values and defaults to nothing,
431
+ // where the container LITERALS `{}`/`[]` default to empty.
432
+ map: ContainerKindVal_1.MapFuncVal,
433
+ list: ContainerKindVal_1.ListFuncVal,
307
434
  close: CloseFuncVal_1.CloseFuncVal,
308
435
  open: OpenFuncVal_1.OpenFuncVal,
309
436
  super: SuperFuncVal_1.SuperFuncVal,
@@ -330,6 +457,63 @@ help isolate the syntax error.`,
330
457
  // reported with the author's own message, never simplified and
331
458
  // never consulted for emptiness or subsumption.
332
459
  must: ConstraintVal_1.MustConstraintVal,
460
+ // G3 phase 4: the deprecation mark. Unification-transparent; the
461
+ // record rides the result (Val.deprecation) and canon renders the
462
+ // call back (canonRiders).
463
+ deprecate: DeprecateFuncVal_1.DeprecateFuncVal,
464
+ // G4 phase 2: the checked, typed, LINK-shaped reference. A
465
+ // constraint on a string field: the string must be a TREE ADDRESS
466
+ // (`$.a.b` or `.b`), the address must resolve, and the optional
467
+ // argument flows INTO the target. The field keeps the string.
468
+ refer: ReferFuncVal_1.ReferFuncVal,
469
+ rel: ReferFuncVal_1.RelFuncVal,
470
+ // RELATIONS P2 (docs/design/RELATIONS.0.md §3.3): the graph
471
+ // atoms, conjoined at the field whose key is the predicate they
472
+ // govern. Lattice-inert; the verdict lands at generation.
473
+ acyclic: GraphAtomVal_1.AcyclicFuncVal,
474
+ inverse: GraphAtomVal_1.InverseFuncVal,
475
+ // G8 phase 1: the generation combinators. `pack` makes one keyed
476
+ // child per child of its data, `each` one list element; both clone
477
+ // their template per destination exactly as a spread does, and both
478
+ // wait for the model to settle before they fire (the staging rule,
479
+ // G8 phase 0).
480
+ pack: PackFuncVal_1.PackFuncVal,
481
+ each: EachFuncVal_1.EachFuncVal,
482
+ // G8 phase 2: selection. `filter` keeps the children of a bag that
483
+ // unify with a condition; `match` picks the first arm whose
484
+ // pattern the scrutinee unifies with. Both select by
485
+ // UNIFIABILITY, tried in trial mode, so neither adds a predicate
486
+ // language to the one the lattice already is.
487
+ filter: FilterFuncVal_1.FilterFuncVal,
488
+ match: MatchFuncVal_1.MatchFuncVal,
489
+ // The arithmetic family (the review's finding I). Maths beyond `+`
490
+ // arrives as FUNCTIONS — `-` `*` `/` `%` stay reserved — and the
491
+ // family is numeric where the operator is polymorphic, which is
492
+ // what makes `add` more than a second spelling of `+`: it refuses
493
+ // the string concatenation that silently answers `"500m" + "500m"`.
494
+ // Every rule they obey is in ts/src/val/arith.ts.
495
+ add: ArithFuncVal_1.AddFuncVal,
496
+ sub: ArithFuncVal_1.SubFuncVal,
497
+ mul: ArithFuncVal_1.MulFuncVal,
498
+ div: ArithFuncVal_1.DivFuncVal,
499
+ mod: ArithFuncVal_1.ModFuncVal,
500
+ rem: ArithFuncVal_1.RemFuncVal,
501
+ // Aggregation over a finite, settled bag (the review's finding I).
502
+ // `least` and `greatest` rather than min and max, which are already
503
+ // the atoms for a lower and an upper BOUND -- an aggregate over a
504
+ // set and a bound on a value must not share a spelling.
505
+ sum: AggFuncVal_1.SumFuncVal,
506
+ least: AggFuncVal_1.LeastFuncVal,
507
+ greatest: AggFuncVal_1.GreatestFuncVal,
508
+ // Projection, which is what lets the aggregates reach a bag of
509
+ // RECORDS: `sum(pick($.lines, amountCents))`. Not a clever `each`
510
+ // template -- `each` MEETS each child, and a meet cannot select.
511
+ pick: AggFuncVal_1.PickFuncVal,
512
+ // G9 phase 2: the fold to a STRING. `sum` folds with `add`; this
513
+ // folds with `+`, so it inherits the one number-to-text rule and
514
+ // the language does not grow a second. It is the primitive that
515
+ // turns a bag of computed lines into a file.
516
+ join: AggFuncVal_1.JoinFuncVal,
333
517
  };
334
518
  // A dangling operator (`a:1|`, `a:$`, `a:*` at end of input) leaves
335
519
  // null/undefined unfilled terms. Junction ops drop them (so `a:1&`
@@ -338,6 +522,68 @@ help isolate the syntax error.`,
338
522
  // resolve value" error (the Go port mirrors this in lang.go).
339
523
  const dropUnfilled = (terms) => terms.filter((t) => null != t);
340
524
  const incompleteNil = (r, ctx) => addsite(new NilVal_1.NilVal({ why: 'incomplete_expression' }), r, ctx);
525
+ // Build a call from a NAME and the argument terms as the author
526
+ // wrote them: the arity check, the comma-group rule and the
527
+ // raw-value conversion, stated once.
528
+ const buildCall = (r, ctx, fname, argterms) => {
529
+ const funcval = funcMap[fname];
530
+ // Arity is known for every built-in, so a surplus or missing
531
+ // argument is a mistake in the SOURCE, refused here where the
532
+ // author can see it (issue #51). It was previously left to each
533
+ // function to notice or not: the two ports disagreed on `upper()`
534
+ // and on `close()`, and `min(1,2)` noticed nothing at all -- it
535
+ // built a constraint that merely refused to generate later, with a
536
+ // message about the map rather than about the call.
537
+ //
538
+ // Counted BEFORE the rawToVal pass below, which is what makes the
539
+ // count possible: a comma group arrives as a RAW array and a
540
+ // written list literal as a ListVal, and rawToVal turns the first
541
+ // into the second.
542
+ const arity = funcArity[fname];
543
+ if (null != arity) {
544
+ const got = writtenArgCount(argterms);
545
+ if (got < arity[0] || (-1 !== arity[1] && got > arity[1])) {
546
+ // details is assigned AFTER construction: the NilVal
547
+ // constructor does not read it from its spec (only NilVal.make
548
+ // does), so passing it in the spec left the hint's
549
+ // {func}/{want}/{got} placeholders un-injected and printed
550
+ // literally.
551
+ const nil = new NilVal_1.NilVal({ why: 'func_arity' });
552
+ nil.details = {
553
+ func: fname,
554
+ want: arityText(arity[0], arity[1]),
555
+ got: '' + got,
556
+ };
557
+ return addsite(nil, r, ctx);
558
+ }
559
+ }
560
+ // rawToVal EVERY argument. A degenerate expression can hand this
561
+ // handler raw parse values rather than Vals -- `pref(1-3)` arrives
562
+ // as the plain numbers 1 and -3 -- and a func's peg is unified
563
+ // element by element, so a raw one reached `arg.unify(...)` and
564
+ // threw. The unifier's catch-all turned that into an `internal`
565
+ // verdict: a crash reported as a unification result (issue #49).
566
+ // The Go port has always converted here (asVal in evaluate).
567
+ // A comma group is ONE raw-array term (see writtenArgCount).
568
+ // For a function whose arguments are distinct POSITIONS —
569
+ // deprecate's value and record, pack's and each's data and template
570
+ // — the group is expanded back into them here, while a written list
571
+ // literal, already a ListVal, stays one argument. The constraint
572
+ // atoms make the same move in their own constructor (atomArgs,
573
+ // ConstraintVal.ts), which is why they are not in this set:
574
+ // `neq(1,2)` is one argument LIST, not two positions, and expanding
575
+ // it here would take the list away from the code that reads it.
576
+ let terms = argterms;
577
+ if (true === POSITIONAL_ARG_FUNCS[fname] && 1 === terms.length &&
578
+ Array.isArray(terms[0])) {
579
+ terms = terms[0];
580
+ }
581
+ const args = terms.map(rawToVal);
582
+ const val = null == funcval ?
583
+ new NilVal_1.NilVal({ why: 'unknown_function' }) :
584
+ new funcval({ peg: args });
585
+ return val;
586
+ };
341
587
  let opmap = {
342
588
  'conjunct-infix': (r, ctx, _op, terms) => addsite(new ConjunctVal_1.ConjunctVal({ peg: dropUnfilled(terms) }), r, ctx),
343
589
  'disjunct-infix': (r, ctx, _op, terms) => addsite(new DisjunctVal_1.DisjunctVal({ peg: dropUnfilled(terms) }), r, ctx),
@@ -349,11 +595,47 @@ help isolate the syntax error.`,
349
595
  'star-prefix': (r, ctx, _op, terms) => {
350
596
  if (null == terms[0])
351
597
  return incompleteNil(r, ctx);
598
+ // A PREFERENCE MARKS A VALUE, AND A BARE KEY IS NOT ONE.
599
+ // `*a: 1` has no braces, so the prefix took the whole IMPLICIT
600
+ // map as its operand and the document silently became
601
+ // `*{"a":1}` -- `*a: 1, b: 2` became a one-element LIST, losing
602
+ // `b` outright. Neither is anything the author wrote.
603
+ //
604
+ // The accident is confined to the first position of the implicit
605
+ // top-level map, which is the only place no brace has yet
606
+ // committed the rule to a map: `{*a: 1}` and `a: 1, *b: 2` are
607
+ // ALREADY parse errors. This makes the third spelling agree with
608
+ // them rather than inventing a meaning for it.
609
+ //
610
+ // A BRACED operand is untouched, and that is the whole of the
611
+ // distinction: `*{x:1}` and `*[1]` are the real spelling, they
612
+ // are what `*{x:1} | *{y:2}` needs, and the shared spec pins them
613
+ // (11 rows). The open token's own source text is what separates
614
+ // the two -- `{` or `[` for a braced bag, the first key or
615
+ // element for an implicit one.
616
+ const bag = terms[0];
617
+ if ((bag.isMap && '{' !== bag.site.src) ||
618
+ (bag.isList && '[' !== bag.site.src)) {
619
+ return addsite(new NilVal_1.NilVal({ why: 'pref_implicit_bag' }), r, ctx);
620
+ }
352
621
  return addsite(new PrefVal_1.PrefVal({ peg: terms[0] }), r, ctx);
353
622
  },
354
623
  'dollar-prefix': (r, ctx, _op, terms) => {
355
624
  if (null == terms[0])
356
625
  return incompleteNil(r, ctx);
626
+ // A refusal from the dot rule below (an alias used as a path
627
+ // segment) rides straight through: wrapping it in a VarVal would
628
+ // replace `alias_in_path` with a var whose peg is a nil.
629
+ if (terms[0]?.isNil) {
630
+ return terms[0];
631
+ }
632
+ // `$%foo` -- the sigil directly after the root -- reaches here
633
+ // as the alias reference rather than through the dot rule, and
634
+ // is refused for the same reason.
635
+ if (terms[0] instanceof RefVal_1.RefVal &&
636
+ terms[0].peg.some((seg) => 'string' === typeof seg && ALIAS_RE.test(seg))) {
637
+ return addsite(new NilVal_1.NilVal({ why: 'alias_in_path' }), r, ctx);
638
+ }
357
639
  // $.a.b absolute path
358
640
  if (terms[0] instanceof RefVal_1.RefVal) {
359
641
  terms[0].absolute = true;
@@ -412,50 +694,7 @@ help isolate the syntax error.`,
412
694
  let val = terms[1];
413
695
  const fname = terms[0];
414
696
  if ('' !== fname) {
415
- const funcval = funcMap[fname];
416
- // Arity is known for every built-in, so a surplus or missing
417
- // argument is a mistake in the SOURCE, refused here where the
418
- // author can see it (issue #51). It was previously left to each
419
- // function to notice or not: the two ports disagreed on `upper()`
420
- // and on `close()`, and `min(1,2)` noticed nothing at all -- it
421
- // built a constraint that merely refused to generate later, with
422
- // a message about the map rather than about the call.
423
- //
424
- // Counted BEFORE the rawToVal pass below, which is what makes the
425
- // count possible: a comma group arrives as a RAW array and a
426
- // written list literal as a ListVal, and rawToVal turns the first
427
- // into the second.
428
- const arity = funcArity[fname];
429
- if (null != arity) {
430
- const got = writtenArgCount(terms.slice(1));
431
- if (got < arity[0] || (-1 !== arity[1] && got > arity[1])) {
432
- // details is assigned AFTER construction: the NilVal
433
- // constructor does not read it from its spec (only
434
- // NilVal.make does), so passing it in the spec left the
435
- // hint's {func}/{want}/{got} placeholders un-injected and
436
- // printed literally.
437
- const nil = new NilVal_1.NilVal({ why: 'func_arity' });
438
- nil.details = {
439
- func: fname,
440
- want: arityText(arity[0], arity[1]),
441
- got: '' + got,
442
- };
443
- return addsite(nil, r, ctx);
444
- }
445
- }
446
- // rawToVal EVERY argument. A degenerate expression can hand this
447
- // handler raw parse values rather than Vals -- `pref(1-3)` arrives
448
- // as the plain numbers 1 and -3 -- and a func's peg is unified
449
- // element by element, so a raw one reached `arg.unify(...)` and
450
- // threw. The unifier's catch-all turned that into an `internal`
451
- // verdict: a crash reported as a unification result (issue #49).
452
- // The Go port has always converted here (asVal in evaluate).
453
- const args = terms.slice(1).map(rawToVal);
454
- val = null == funcval ?
455
- new NilVal_1.NilVal({ why: 'unknown_function' }) :
456
- new funcval({
457
- peg: args
458
- });
697
+ val = buildCall(r, ctx, fname, terms.slice(1));
459
698
  }
460
699
  // `a:()` — grouping parens with nothing inside.
461
700
  if (null == val)
@@ -560,6 +799,7 @@ help isolate the syntax error.`,
560
799
  const TX = jsonic.token.TX;
561
800
  const NR = jsonic.token.NR;
562
801
  const QM = jsonic.token.QM;
802
+ const VL = jsonic.token.VL;
563
803
  const OPTKEY = [TX, ST, NR];
564
804
  jsonic.rule('expr', (rs) => {
565
805
  rs.close([
@@ -658,6 +898,13 @@ help isolate the syntax error.`,
658
898
  valnode.site.row = st.rI;
659
899
  valnode.site.col = st.cI;
660
900
  valnode.site.url = ctx.meta.multisource && ctx.meta.multisource.path;
901
+ // No `?? ''` and no empty-text arm here: this branch runs only
902
+ // for a rule that HAS an open token, and a token that opens a
903
+ // value always carries text — the coverage gate refuses both
904
+ // guards as dead. The unset case is the one above, where r.o0
905
+ // itself can be absent.
906
+ valnode.site.src = st.src;
907
+ valnode.site.len = st.src.length;
661
908
  }
662
909
  // else { ERROR? }
663
910
  r.node = valnode;
@@ -674,6 +921,7 @@ help isolate the syntax error.`,
674
921
  ])
675
922
  .bc((r, ctx) => {
676
923
  const optionalKeys = r.u.aontu_optional_keys ?? [];
924
+ const aliasKeys = r.u.aontu_alias_keys ?? [];
677
925
  let mo = r.node;
678
926
  // An elided value (`a:`) leaves a raw null/undefined that never
679
927
  // passed through the val rule. It is REFUSED rather than made a
@@ -733,12 +981,14 @@ help isolate the syntax error.`,
733
981
  // TODO: needs addpath?
734
982
  let mopv = new MapVal_1.MapVal({ peg: mop });
735
983
  mopv.optionalKeys = optionalKeys;
984
+ mopv.aliasKeys = aliasKeys;
736
985
  r.node =
737
986
  addsite(new ConjunctVal_1.ConjunctVal({ peg: [mopv, ...mo.___merge] }), r, ctx);
738
987
  }
739
988
  else {
740
989
  r.node = addsite(new MapVal_1.MapVal({ peg: mo }), r, ctx);
741
990
  r.node.optionalKeys = optionalKeys;
991
+ r.node.aliasKeys = aliasKeys;
742
992
  }
743
993
  return undefined;
744
994
  })
@@ -860,6 +1110,31 @@ help isolate the syntax error.`,
860
1110
  })
861
1111
  .bc((rule) => {
862
1112
  // TRAVERSE PARENTS TO GET PATH
1113
+ // A DECLARATION IS A PAIR WHOSE KEY IS AN ALIAS NAME. The lexer
1114
+ // claims `%name` whole and hands it over as a #VL token whose
1115
+ // SOURCE is the name, so the key TEXT alone cannot be the test:
1116
+ // a quoted `"%a": 1` is an ordinary key that merely starts with
1117
+ // the sigil, and erasing that would be wrong. The token is what
1118
+ // separates them.
1119
+ //
1120
+ // Recorded on the enclosing map, never on the value, and that is
1121
+ // the point: a reference COPIES the value it resolves to, so a
1122
+ // mark riding the value would erase the referring field too.
1123
+ // Being a property of the map is also what carries it through a
1124
+ // meet, the way optional keys are carried.
1125
+ const ktkn = rule.o0;
1126
+ if (null != ktkn && VL === ktkn.tin && ALIAS_RE.test('' + ktkn.src)) {
1127
+ const holder = rule.parent;
1128
+ const aname = '' + ktkn.src;
1129
+ // Always recorded here; whether the map is ALLOWED to carry
1130
+ // declarations is decided on the VALUE (MapVal.unify), not at
1131
+ // the parse. The parse cannot see it: an INCLUDED file's
1132
+ // declarations are at the root of their own text, and only
1133
+ // once the loaded map is placed does it become apparent that
1134
+ // root is not the document's.
1135
+ holder.u.aontu_alias_keys = (holder.u.aontu_alias_keys || []);
1136
+ holder.u.aontu_alias_keys.push(aname);
1137
+ }
863
1138
  if (rule.u.spread) {
864
1139
  rule.node[type_1.SPREAD] =
865
1140
  (rule.node[type_1.SPREAD] || { o: rule.o0.src, v: [] });
@@ -896,7 +1171,10 @@ help isolate the syntax error.`,
896
1171
  s: [QM, CL],
897
1172
  c: (r) => r.prev.u.aontu_optional,
898
1173
  p: 'val',
899
- u: { spread: true, done: true, list: true, pair: true },
1174
+ u: {
1175
+ spread: true, done: true, list: true, pair: true,
1176
+ aontu_optional_elem: true,
1177
+ },
900
1178
  a: (r) => {
901
1179
  pairkey(r.prev);
902
1180
  r.u.key = r.prev.u.key;
@@ -904,18 +1182,17 @@ help isolate the syntax error.`,
904
1182
  },
905
1183
  g: 'aontu-optional-elem'
906
1184
  },
907
- // A PLAIN pair in list position, `[k:v]`. It contributes no
908
- // element either -- a key:value pair is simply not a list element,
909
- // which is the rule the optional form above already followed, and
910
- // the two spellings must not disagree (issue #40).
911
- //
912
- // It needed an alt of its own because only a NON-NUMERIC key was
913
- // already inert: jsonic writes the pair at `node[key]`, and the
914
- // node is an array, so `[x:1]` set a property that never showed up
915
- // (`length` stays 0) while `[0:1]` set an INDEX and became an
916
- // element -- `[1:2]` even filling the gap with a null. That is the
917
- // shape of a JavaScript array, not a decision about the language,
918
- // and it made the two ports disagree on generate as well as canon.
1185
+ // A PLAIN pair in list position IS A SINGLE-KEY MAP ELEMENT:
1186
+ // `[a:1, b:2]` is `[{a:1}, {b:2}]` (the rule @tabnas/jsonic
1187
+ // spells as `list.pair`). This REVERSES issue #40's "a pair is
1188
+ // not an element": that rule was chosen because jsonic wrote
1189
+ // the pair at `node[key]` -- an array PROPERTY that never
1190
+ // showed up for a text key and an INDEX for a numeric one --
1191
+ // and inert beat that incoherence. But inert was itself a
1192
+ // silent drop: `x: [a:1, b:2]` evaluated to `x: []`, the
1193
+ // author's data gone at exit 0. The element is built in the
1194
+ // bc below, where the value is already a Val; the snapshot
1195
+ // still neutralises jsonic's raw slot write first.
919
1196
  {
920
1197
  s: [OPTKEY, CL], p: 'val',
921
1198
  u: { spread: true, done: true, list: true, pair: true },
@@ -926,32 +1203,316 @@ help isolate the syntax error.`,
926
1203
  g: 'aontu-plain-pair-elem'
927
1204
  }
928
1205
  ])
929
- .bc((rule) => {
1206
+ // NOTE: manually adjust path - the twin of the `pair` rule's hook
1207
+ // above, and for the same reason, one layer down.
1208
+ //
1209
+ // Every alt above contributes NO element: a `&:` spread is a
1210
+ // constraint on the elements, and a `k:v` pair in list position is
1211
+ // simply not one (the `aontu-plain-pair-elem` note above). The array
1212
+ // slot they briefly occupy is already given back by
1213
+ // restorePairSlot. The PATH index was not: @tabnas/path's
1214
+ // `@elem-ao` increments `r.k.index` for every elem rule it sees, so
1215
+ // each of these stole an index and every later element's path was
1216
+ // one too high — `[&: integer, 10, 20, "bad"]` reported the bad
1217
+ // value at `$.l.3` while `aontu get $.l.2` returned it, and on a
1218
+ // one-element list the path pointed off the end. Generation was
1219
+ // never wrong, which is why nothing caught it: the array is right
1220
+ // and only the labels on it were shifted (BUGS.md 44).
1221
+ //
1222
+ // Rewinding here rather than in the plugin keeps the plugin's rule
1223
+ // ("in an array, the path property is the element index") true —
1224
+ // these alts are the aontu-specific exceptions to what counts as an
1225
+ // element, so the correction belongs with the grammar that
1226
+ // introduces them. The child is re-pathed because the plugin has
1227
+ // already stamped it with the index being given back: a spread
1228
+ // takes the `'&'` segment its map twin takes, and a pair takes its
1229
+ // key, as a map entry would.
1230
+ .ao((r) => {
1231
+ // A pair IS an element now, so it keeps the index @tabnas/path
1232
+ // gave it, and its VALUE is pathed through both the index and
1233
+ // the key (`[a: $.nope]` fails at $.l.0.a). Only the `&:`
1234
+ // spread still contributes no element and gives its index back
1235
+ // (BUGS.md 44).
1236
+ if (0 < r.d && r.u.spread && !r.u.pair) {
1237
+ r.k.index = r.k.index - 1;
1238
+ const seg = '&';
1239
+ r.child.k.path = [...r.k.path, seg];
1240
+ r.child.k.key = seg;
1241
+ }
1242
+ else if (0 < r.d && r.u.pair) {
1243
+ // The element's index is the array length: everything before
1244
+ // it is already pushed, and the pair's own map is pushed at
1245
+ // close. `r.k.index` is not usable here -- the path plugin
1246
+ // counts only the elements it pushes itself, and this one is
1247
+ // aontu's.
1248
+ const seg = '' + r.u.key;
1249
+ r.child.k.path =
1250
+ [...r.k.path, '' + (r.node?.length ?? 0), seg];
1251
+ r.child.k.key = seg;
1252
+ }
1253
+ })
1254
+ .bc((rule, ctx) => {
930
1255
  // TRAVERSE PARENTS TO GET PATH
931
- if (rule.u.spread) {
1256
+ // Only the `&:` alternative is a SPREAD. All four alts above set
1257
+ // `spread: true` -- it is what marks them as contributing no
1258
+ // element -- so this guard needs the narrower test, and `pair`
1259
+ // is what distinguishes a `k:v` in list position from a spread.
1260
+ //
1261
+ // Without it a pair BUILT the spread record, with `o` taken from
1262
+ // its own key rather than '&': `[x:1, &:integer, "bad"]` left
1263
+ // `{o:'x', v:[1, integer]}`, and ListVal's `'&' === spread.o`
1264
+ // then discarded the real constraint -- so the element spread
1265
+ // was silently dropped and the bad value generated (BUGS.md 46).
1266
+ // A pair alone did it too: `[x:1, 10]` produced a spread record
1267
+ // out of nothing.
1268
+ if (rule.u.spread && !rule.u.pair) {
932
1269
  rule.node[type_1.SPREAD] =
933
1270
  (rule.node[type_1.SPREAD] || { o: rule.o0.src, v: [] });
934
1271
  rule.node[type_1.SPREAD].v.push(rule.child.node);
935
1272
  }
1273
+ // The slot is given back BEFORE the element is added: the
1274
+ // restore undoes jsonic's raw write (a property for a text
1275
+ // key, an INDEX for a numeric one -- restoring length is what
1276
+ // keeps `[1:2]` from padding with a null), and the push then
1277
+ // appends cleanly after it.
936
1278
  restorePairSlot(rule);
1279
+ // THE SINGLE-KEY MAP ELEMENT, for both pair spellings. The
1280
+ // value is a Val already (`p: 'val'`), so the map is built
1281
+ // exactly as the map rule builds one -- and an elided value
1282
+ // (`[a:]`) is refused exactly as the map rule refuses one
1283
+ // (issue #48): a key with nothing after the colon is a
1284
+ // mistake, not an empty value.
1285
+ if (true === rule.u.pair) {
1286
+ const key = '' + rule.u.key;
1287
+ let v = rule.child.node;
1288
+ if (null == v) {
1289
+ v = addsite(new NilVal_1.NilVal({ why: 'elided_value' }), rule, ctx);
1290
+ v.path = [...(rule.k?.path ?? []),
1291
+ '' + rule.node.length, key];
1292
+ }
1293
+ const mv = addsite(new MapVal_1.MapVal({ peg: { [key]: v } }), rule, ctx);
1294
+ // `[a?: 1]` is `[{a?: 1}]`: the key is optional IN the
1295
+ // element, so the two spellings stay one rule apart rather
1296
+ // than two behaviours apart.
1297
+ if (true === rule.u.aontu_optional_elem) {
1298
+ mv.optionalKeys = [key];
1299
+ }
1300
+ rule.node.push(mv);
1301
+ }
937
1302
  return undefined;
938
1303
  })
939
1304
  .close([{ s: [CJ, CL], r: 'elem', b: 2, g: 'spread,json,more' }]);
940
1305
  return rs;
941
1306
  });
942
1307
  };
943
- // SECURITY: the default resolver reads any file/package the process can
944
- // reach — @"path" follows relative paths (`@"../../etc/passwd"`) and
945
- // symlinks with no containment check, and @"pkg" can require() arbitrary
946
- // installed modules. This is intentional for the CLI, but it means a
947
- // `.aon` source can read referenced files; the LSP uses this same
948
- // resolver, so treat opening an untrusted source as running it. Pass a
949
- // confined `options.resolver` to restrict reads in less-trusted contexts.
1308
+ // INCLUDE_KINDS IS THE RULE FOR WHAT AN INCLUDE MEANS (ADR-012,
1309
+ // use-cases/BUGS.md §49). An extension is on this list or it is not
1310
+ // read at all, and its entry says WHICH OF TWO THINGS the file is.
1311
+ //
1312
+ // `source` — Aontu, with everything the language has: types, defaults,
1313
+ // references, constraints, its own includes. Two extensions, and they
1314
+ // are the ones this project owns.
1315
+ //
1316
+ // A FORMAT NAME — configuration DATA, parsed by that format's own
1317
+ // parser into the JSON value it denotes, which then becomes Aontu
1318
+ // values like any other data. Every one of these formats maps onto
1319
+ // JSON, which is why one word covers them: a `.toml` file is a map of
1320
+ // scalars, lists and maps, and so is the `.aon` file that unifies with
1321
+ // it. What the format does NOT get is the language — a `&` in a YAML
1322
+ // file is a YAML anchor, not a spread key, because the YAML parser
1323
+ // reads it, not this one.
1324
+ //
1325
+ // The parsers are @tabnas's, one per format, and the Go port uses the
1326
+ // same ones (ADR-001): the two implementations agree because they are
1327
+ // running the same grammar, not because two hand-written readers were
1328
+ // kept in step.
1329
+ //
1330
+ // This table and go/source.go's includeKinds are the same table.
1331
+ const INCLUDE_KINDS = {
1332
+ aon: 'source',
1333
+ aontu: 'source',
1334
+ json: 'json',
1335
+ // JSON-LD is JSON: a `@context` is a key like any other here, and
1336
+ // what it MEANS is the vocabulary's business, not the reader's.
1337
+ jsonld: 'json',
1338
+ jsonc: 'jsonc',
1339
+ json5: 'json5',
1340
+ jsonic: 'jsonic',
1341
+ jsc: 'jsonic',
1342
+ toml: 'toml',
1343
+ yaml: 'yaml',
1344
+ yml: 'yaml',
1345
+ ini: 'ini',
1346
+ };
1347
+ // `.csv` IS DELIBERATELY ABSENT, and the reason is ADR-001 rather than
1348
+ // taste. The two ports' CSV parsers disagree about what a CSV file IS:
1349
+ // one answers header-keyed records with string fields, the other raw
1350
+ // rows including the header, with numbers parsed. Admitting it would
1351
+ // admit a divergence into the one thing this project refuses to have
1352
+ // one in. Recorded in ADR-012 and pinned by file.tsv's load-ext-csv.
1353
+ // The multisource kind of a path: the LAST segment's extension, without
1354
+ // its dot, lowercased -- `''` for a name that has none. The rule is
1355
+ // @tabnas/multisource's own extKind (and Go's filepath.Ext), copied
1356
+ // rather than imported because it decides what a source IS: a dot in a
1357
+ // parent folder (`/my.app/conf`) must not read as an extension.
1358
+ function extKindOf(full) {
1359
+ const seg = full.match(/[^\\/]*$/)[0];
1360
+ return (seg.match(/\.([^.]*)$/) || ['', ''])[1].toLowerCase();
1361
+ }
1362
+ // The refusal message, naming the extension -- because the extension is
1363
+ // the whole reason, and a reader told only "not readable" has to guess
1364
+ // which part of the path the engine objected to. Byte-identical to Go's
1365
+ // extensionMsg.
1366
+ function extensionMsg(path, ext) {
1367
+ const which = '' === ext ? 'no extension' : 'extension: .' + ext;
1368
+ return 'include not readable: ' + path + ' (' + which + ')';
1369
+ }
1370
+ // THE RULE ALSO HOLDS FOR A RESOLVER THIS ENGINE DID NOT WRITE.
1371
+ // gateExtension refuses an unlisted extension inside makeModelResolver,
1372
+ // which is the default; a HOST may supply its own through
1373
+ // `AontuOptions.resolver`, and that one has never heard of
1374
+ // INCLUDE_KINDS. Without this the host's resolution would fall to
1375
+ // multisource's own default for an unnamed kind, which hands the file
1376
+ // back as TEXT — or, for `.js`, EXECUTES it. So the two roads end in
1377
+ // one place: whatever chose the source, an extension off the list is
1378
+ // refused with the same code and the same message.
1379
+ const refuseProcessor = (res) => {
1380
+ // `full` is the one part a host resolution may leave out -- it is the
1381
+ // path the resolver CHOSE, and a resolver that answers from something
1382
+ // other than a filesystem need not have one. The written path always
1383
+ // reaches here, so it is the fallback.
1384
+ const err = new Error(extensionMsg(res.path, extKindOf(res.full ?? res.path)));
1385
+ err.code = 'include_extension';
1386
+ throw err;
1387
+ };
1388
+ // ONE READER PER FORMAT, BUILT ONCE. These are stateless parsers and
1389
+ // building a jsonic instance is not free, so they are made at module
1390
+ // load rather than per include. The file name is passed through so a
1391
+ // syntax error inside an included `.toml` names the `.toml`.
1392
+ const DATA_READERS = (() => {
1393
+ const viaPlugin = (plugin) => {
1394
+ const jsonic = jsonic_1.Jsonic.make().use(plugin);
1395
+ return (src, fileName) => jsonic(src, { fileName });
1396
+ };
1397
+ const toml = viaPlugin(toml_1.Toml);
1398
+ // The strict RFC 8259 reader is its own parser rather than a
1399
+ // plugin, and it is `make().parse` rather than the module's bare
1400
+ // `parse`: only the instance carries the meta bag, and without it
1401
+ // a syntax error in an included `.json` says `<no-file>`.
1402
+ const json = (0, json_1.make)();
1403
+ return {
1404
+ json: (src, fileName) => json.parse(src, { fileName }),
1405
+ jsonc: viaPlugin(jsonc_1.Jsonc),
1406
+ json5: viaPlugin(json5_1.Json5),
1407
+ // Plain jsonic needs no plugin: it IS the base parser.
1408
+ jsonic: (src, fileName) => (0, jsonic_1.Jsonic)(src, { fileName }),
1409
+ toml: (src, fileName) => tomlDates(toml(src, fileName)),
1410
+ yaml: viaPlugin(yaml_1.Yaml),
1411
+ ini: viaPlugin(ini_1.Ini),
1412
+ };
1413
+ })();
1414
+ /**
1415
+ * A TOML document with its dates as the TEXT they were written as.
1416
+ *
1417
+ * TOML HAS DATES AND JSON DOES NOT, so the reader cannot hand one over
1418
+ * as itself: it answers with a marker object carrying the kind and the
1419
+ * source text. The value that reaches a document is that TEXT, which is
1420
+ * what a JSON document carries for a date anyway — and it is what the
1421
+ * Go port produces too, from a `*TomlTime` holding those same two
1422
+ * fields (`dataToValDepth`, go/source.go). Without this the same file
1423
+ * is a nested map in one port and a string in the other, which is the
1424
+ * class of divergence ADR-012 exists to stop.
1425
+ *
1426
+ * The guard is exact — one key, `__toml__`, holding a `kind` and a
1427
+ * `src` string — so a document whose own data happens to use the name
1428
+ * passes through untouched.
1429
+ */
1430
+ function tomlDates(node) {
1431
+ if (Array.isArray(node)) {
1432
+ return node.map(tomlDates);
1433
+ }
1434
+ if (null === node || 'object' !== typeof node) {
1435
+ return node;
1436
+ }
1437
+ const keys = Object.keys(node);
1438
+ const mark = node.__toml__;
1439
+ if (1 === keys.length && '__toml__' === keys[0] && null != mark &&
1440
+ 'string' === typeof mark.kind && 'string' === typeof mark.src) {
1441
+ return mark.src;
1442
+ }
1443
+ const out = {};
1444
+ for (const k of keys) {
1445
+ out[k] = tomlDates(node[k]);
1446
+ }
1447
+ return out;
1448
+ }
1449
+ /**
1450
+ * Read one included file as DATA in the named format.
1451
+ *
1452
+ * The parser hands back the JSON value the file denotes — plain maps,
1453
+ * lists and scalars — and rawToVal turns that into Vals. THE
1454
+ * CONVERSION HAPPENS HERE, not at the top level, because an include is
1455
+ * usually not at the top level: `a: @"conf.toml"` puts the value under
1456
+ * a key, where a raw JavaScript object is something the tree cannot
1457
+ * unify with (the crash that was BUGS §49b).
1458
+ */
1459
+ const dataProcessor = (format) => (res) => {
1460
+ res.val = rawToVal(DATA_READERS[format](res.src, res.path));
1461
+ };
1462
+ /**
1463
+ * The multisource processor map, built FROM the include table so the
1464
+ * two cannot drift: every extension the table names gets the reader
1465
+ * the table names for it, and the two kinds that are not in the table
1466
+ * refuse.
1467
+ */
1468
+ function includeProcessors() {
1469
+ const map = {
1470
+ // multisource's fallback for an extension no entry names, so it is
1471
+ // the one that catches whatever the resolver's gate did not.
1472
+ '': refuseProcessor,
1473
+ // ... and the one upstream default that would EXECUTE the file.
1474
+ js: refuseProcessor,
1475
+ };
1476
+ const source = (0, jsonic_2.makeJsonicProcessor)();
1477
+ for (const kind of Object.keys(INCLUDE_KINDS)) {
1478
+ const format = INCLUDE_KINDS[kind];
1479
+ map[kind] = 'source' === format ? source : dataProcessor(format);
1480
+ }
1481
+ return map;
1482
+ }
1483
+ // SECURITY: under the DEFAULT ('system') include capability this
1484
+ // resolver reads any file the process can reach — @"path" follows
1485
+ // relative paths (`@"../../etc/passwd.aon"`) and symlinks — so treat
1486
+ // opening an untrusted source as reading your disk. It no longer RUNS
1487
+ // one: @"pkg" could require() an arbitrary installed module until
1488
+ // ADR-012, which refuses a `.js` entry point by the same rule that
1489
+ // refuses `.txt`. The trust profile (G5, docs/trust.md) is the
1490
+ // confinement surface: `trust.include` of
1491
+ // 'none', `{ mem }` or `{ root }` restricts what `@"..."` may resolve,
1492
+ // and a denied resolution is a deterministic parse-stage
1493
+ // `include_denied` error.
950
1494
  function makeModelResolver(options) {
951
1495
  const useRequire = options.require || require;
952
- let memResolver = (0, mem_1.makeMemResolver)({
953
- ...(options.resolver?.mem || {})
954
- });
1496
+ const capability = options.trust?.include ?? 'system';
1497
+ const memCapability = 'object' === typeof capability && null != capability.mem;
1498
+ const rootDir = 'object' === typeof capability &&
1499
+ 'string' === typeof capability.root
1500
+ ? (0, node_path_1.resolve)(capability.root) : undefined;
1501
+ // Under the mem capability the CAPABILITY's file set is the whole
1502
+ // world; otherwise the host-injected `options.resolver.mem` entries
1503
+ // remain available under every capability but 'none' — they are
1504
+ // host-provided, not document-requested, so confining them would
1505
+ // confine the host against itself.
1506
+ // THE BUNDLED VOCABULARY (G4 phase 4, ts/src/std.ts) rides the
1507
+ // memory leg: served from the engine itself, so it needs neither the
1508
+ // filesystem nor package resolution and is available under every
1509
+ // capability but `none` — which denies every include outright, that
1510
+ // being what `none` means. Host entries and the capability's own set
1511
+ // WIN over it: a caller that supplies its own `std/system` gets the
1512
+ // one it supplied.
1513
+ let memResolver = (0, mem_1.makeMemResolver)(memCapability
1514
+ ? { ...capability.mem }
1515
+ : { ...(options.resolver?.mem || {}) });
955
1516
  // TODO: make this consistent with other resolvers
956
1517
  let fileResolver = (0, file_1.makeFileResolver)((spec) => {
957
1518
  return 'string' === typeof spec ? spec : spec?.peg;
@@ -960,6 +1521,110 @@ function makeModelResolver(options) {
960
1521
  require: useRequire,
961
1522
  ...(options.resolver?.pkg || {})
962
1523
  });
1524
+ // Confinement is realpath-then-prefix-check (docs/trust.md): the
1525
+ // RESOLVED file's real path must sit below the root's real path, so a
1526
+ // symlink inside the root pointing outside it is an escape, not a
1527
+ // loophole. A path realpath cannot resolve falls back to the lexical
1528
+ // form — the comparison is then against what the resolver actually
1529
+ // read.
1530
+ // Real fs, deliberately: `options.fs` is not a sandbox (it feeds
1531
+ // parse text; the file leg reads through its own channel), so the
1532
+ // containment check must see the same filesystem that leg read from.
1533
+ // A path that does not (fully) exist cannot be realpath'd whole, and
1534
+ // falling back to the LEXICAL form compares apples to oranges when
1535
+ // the root itself sits behind a symlink -- on macOS a root under
1536
+ // /var realpaths to /private/var, so a merely-missing file inside it
1537
+ // reads as an escape. Realpath the deepest EXISTING ancestor and
1538
+ // re-attach the rest, so both sides of the check are in real
1539
+ // coordinates. (The MCP server's own confinement carries the twin of
1540
+ // this rule; its CI failure is what found the shape.)
1541
+ const realpath = (p) => {
1542
+ try {
1543
+ return (0, node_fs_1.realpathSync)(p);
1544
+ }
1545
+ catch {
1546
+ const parent = (0, node_path_1.dirname)(p);
1547
+ if (parent === p) {
1548
+ return p;
1549
+ }
1550
+ return (0, node_path_1.join)(realpath(parent), (0, node_path_1.basename)(p));
1551
+ }
1552
+ };
1553
+ const outsideRoot = (root, full) => {
1554
+ const rootReal = realpath(root);
1555
+ const fullReal = realpath(full);
1556
+ return fullReal !== rootReal && !fullReal.startsWith(rootReal + node_path_1.sep);
1557
+ };
1558
+ // A denial THROWS with the code; Lang.parse converts it to the
1559
+ // parse-stage `include_denied` nil (the same shape a syntax failure
1560
+ // takes). Raising beats injecting a nil value: a bare-member include
1561
+ // (`@"denied.aon"` at the top of a file) MERGES into the enclosing
1562
+ // map, and a nil contributes no keys, so an injected denial would
1563
+ // vanish and leave a plausible, silently-partial document.
1564
+ const deny = (path) => {
1565
+ // Only 'none' and 'root' can deny: the mem capability's misses are
1566
+ // not-found (its set is the whole world), so there is no third arm.
1567
+ const capname = 'none' === capability ? 'none' : 'root:' + rootDir;
1568
+ const err = new Error('include denied: ' + path + ' (capability: ' + capname + ')');
1569
+ err.code = 'include_denied';
1570
+ throw err;
1571
+ };
1572
+ // AN UNREADABLE EXTENSION THROWS, exactly as a denial does, and for
1573
+ // the same reason: a bare-member include (`@"notes.txt"` at the top
1574
+ // of a file) MERGES into the enclosing map, and a nil contributes no
1575
+ // keys, so an injected refusal would vanish and leave a plausible,
1576
+ // silently-partial document. Lang.parse turns the throw into the
1577
+ // parse-stage `include_extension` nil.
1578
+ const refuseExtension = (path, full) => {
1579
+ const err = new Error(extensionMsg(path, extKindOf(full)));
1580
+ err.code = 'include_extension';
1581
+ throw err;
1582
+ };
1583
+ // The gate every leg that RESOLVES A NAME passes through. The std and
1584
+ // module legs do not: both state `kind: 'aon'` because what they
1585
+ // serve is Aontu source by construction, not by its spelling.
1586
+ const gateExtension = (path, full) => {
1587
+ if (undefined === INCLUDE_KINDS[extKindOf(full)]) {
1588
+ refuseExtension(path, full);
1589
+ }
1590
+ };
1591
+ // The user cache: whatever the host named, else the platform rule
1592
+ // (`modCacheDir`, ts/src/mod.ts) the tooling writes by.
1593
+ const modCache = (opts) => {
1594
+ const named = opts.mod?.cache;
1595
+ return 'string' === typeof named ? named : (0, mod_1.modCacheDir)();
1596
+ };
1597
+ // The directory an include is being resolved FROM: the source that
1598
+ // holds it, or the entry path when the source is a string. Same base
1599
+ // the file leg computes (resolvePathSpec in @tabnas/multisource).
1600
+ const dirOf = (p) => null == p || '' === p ? (0, node_path_1.resolve)('.') : (0, node_path_1.dirname)((0, node_path_1.resolve)(p));
1601
+ // The module store reader: the host's filesystem when one was
1602
+ // injected, so a sandboxed evaluation stays in the filesystem the
1603
+ // host gave it.
1604
+ const modFs = (ctx) => {
1605
+ const hostfs = ctx?.meta?.fs;
1606
+ return null == hostfs ? { existsSync: node_fs_1.existsSync, readFileSync: node_fs_1.readFileSync } : {
1607
+ existsSync: (p) => {
1608
+ try {
1609
+ hostfs.statSync(p);
1610
+ return true;
1611
+ }
1612
+ catch {
1613
+ return false;
1614
+ }
1615
+ },
1616
+ readFileSync: (p, enc) => hostfs.readFileSync(p, enc),
1617
+ };
1618
+ };
1619
+ // The manifest sink rides the parse meta (Lang.parse seeds it, the
1620
+ // multisource plugin's child-meta spread carries it to every nested
1621
+ // include), so the recorded closure covers the whole include tree.
1622
+ const record = (ctx, path, cap) => {
1623
+ const manifest = ctx?.meta?.aontu?.manifest;
1624
+ if (Array.isArray(manifest)) {
1625
+ manifest.push({ path, capability: cap });
1626
+ }
1627
+ };
963
1628
  return function ModelResolver(spec, popts, rule, ctx, jsonic) {
964
1629
  // The aontu val rule's ac has already wrapped every raw string node
965
1630
  // as a StringVal, so spec is a Val here (or a raw object from a
@@ -970,50 +1635,169 @@ function makeModelResolver(options) {
970
1635
  if (null == path || '' === path) {
971
1636
  return { found: false, path: '' + (path ?? ''), search: [] };
972
1637
  }
1638
+ if ('none' === capability) {
1639
+ deny(path);
1640
+ }
1641
+ // THE BUNDLED VOCABULARY (G4 phase 4, ts/src/std.ts): served from
1642
+ // the engine itself, so it needs neither the filesystem nor package
1643
+ // resolution and is available under every capability but `none` —
1644
+ // checked just above, that being what `none` means. Matched against
1645
+ // the name the author WROTE, before the memory leg, so the kind is
1646
+ // stated rather than guessed from an extension the bare name does
1647
+ // not have.
1648
+ const std = std_1.STD_SOURCES[path];
1649
+ if (null != std) {
1650
+ record(ctx, path, 'std');
1651
+ return { found: true, path, full: path, kind: 'aon', src: std, search: [] };
1652
+ }
973
1653
  let search = [];
974
1654
  let res = memResolver(path, popts, rule, ctx, jsonic);
975
1655
  res.path = path;
976
1656
  if (res.found) {
1657
+ // THE EXTENSION DECIDES HERE TOO. A virtual file set is still a
1658
+ // file set: its keys carry extensions, and the same rule has to
1659
+ // read them, or the mem capability becomes a way to include what
1660
+ // the filesystem would refuse.
1661
+ gateExtension(path, res.full ?? path);
1662
+ record(ctx, res.full ?? path, 'mem');
1663
+ return res;
1664
+ }
1665
+ // THE MODULE LEG (G6 phase 2, ts/src/mod.ts): memory -> MODULE ->
1666
+ // filesystem -> package. Memory stays FIRST so a sandbox and the
1667
+ // spec suite can stub a module path without touching disk; a path
1668
+ // that is not module-shaped falls straight through, so no existing
1669
+ // include can be routed somewhere new by this.
1670
+ const modref = memCapability ? undefined : (0, mod_1.parseModuleRef)(path);
1671
+ if (null != modref) {
1672
+ const msmeta = ctx?.meta?.multisource;
1673
+ const from = dirOf(null != msmeta?.path ? msmeta.path : popts?.path);
1674
+ const found = (0, mod_1.resolveModule)(modref, from, modFs(ctx), {
1675
+ // The user cache lives outside any confinement root, so it is
1676
+ // consulted only when nothing confines this evaluation. A
1677
+ // rooted profile sees the project's own `aon_vendor/` and
1678
+ // nothing else, which is what `root` means.
1679
+ ...(null == rootDir ? { cache: modCache(options) } : {}),
1680
+ eval: options.mod?.eval,
1681
+ depth: options.mod?.depth,
1682
+ });
1683
+ if (null != rootDir && outsideRoot(rootDir, found.full)) {
1684
+ deny(path);
1685
+ }
1686
+ record(ctx, found.full, 'mod');
1687
+ return {
1688
+ found: true, path, full: found.full,
1689
+ kind: 'aon', src: found.src, search: [],
1690
+ };
1691
+ }
1692
+ if (memCapability) {
1693
+ // A miss in the declared virtual set is NOT-FOUND, not denial:
1694
+ // the allowed mechanism ran and missed. Denial is reserved for a
1695
+ // capability refusing a mechanism outright.
1696
+ res.search = search.concat(res.search);
977
1697
  return res;
978
1698
  }
979
1699
  search = search.concat(res.search);
980
1700
  res = fileResolver(path, popts, rule, ctx, jsonic);
981
1701
  res.path = path;
982
1702
  if (res.found) {
1703
+ // `res.full` asserted non-null: a FOUND file resolution always
1704
+ // carries the absolute path it read (and the pkg leg below is the
1705
+ // same), so a runtime fallback arm would be dead code.
1706
+ const full = res.full;
1707
+ if (null != rootDir && outsideRoot(rootDir, full)) {
1708
+ deny(path);
1709
+ }
1710
+ // After the trust check, not before: a file outside the
1711
+ // confinement root is denied whatever it is called, and answering
1712
+ // "extension" there would say the file exists.
1713
+ gateExtension(path, full);
1714
+ // The warning window for the staged default flip (G5 phase 6):
1715
+ // under 'system', the CLI supplies trustWarn and the entry root,
1716
+ // and every resolution escaping that root names the flag a future
1717
+ // default will require.
1718
+ if (null == rootDir && null != options.trustWarn &&
1719
+ null != options.trustWarnRoot &&
1720
+ outsideRoot(options.trustWarnRoot, full)) {
1721
+ options.trustWarn('escape', full);
1722
+ }
1723
+ record(ctx, full, 'file');
983
1724
  return res;
984
1725
  }
985
1726
  search = search.concat(res.search);
1727
+ if (null != rootDir) {
1728
+ // Package resolution is not part of the root capability; the
1729
+ // miss stands as not-found with the searched paths listed.
1730
+ res.search = search;
1731
+ return res;
1732
+ }
986
1733
  res = pkgResolver(path, popts, rule, ctx, jsonic);
987
1734
  res.path = path;
988
1735
  if (res.found) {
1736
+ gateExtension(path, res.full);
1737
+ if (null != options.trustWarn) {
1738
+ options.trustWarn('pkg', res.full);
1739
+ }
1740
+ record(ctx, res.full, 'pkg');
989
1741
  return res;
990
1742
  }
991
1743
  res.search = search.concat(res.search);
992
1744
  return res;
993
1745
  };
994
1746
  }
995
- // funcArity is the permitted WRITTEN argument count of each built-in, as
1747
+ // THE SIGNATURE REGISTRY (docs/design/SIGNATURES.0.md). The call
1748
+ // surface is DECLARED in test/spec/signature.tsv and parsed by the
1749
+ // signature grammar (ts/src/sig.ts) from the build-time-inlined copy;
1750
+ // the arity table and the positional set below are DERIVED from the
1751
+ // parsed registry (funcSig, ts/src/sig.ts), so the declaration is the
1752
+ // one source. go/func.go derives the same two tables from the same
1753
+ // text.
1754
+ // The functions whose comma-separated arguments are distinct POSITIONS
1755
+ // rather than one argument list. See the func-paren handler above: this
1756
+ // is the set whose comma group is expanded back into separate `peg`
1757
+ // entries. Derived: two or more declared argument slots, excluding the
1758
+ // residual producers (`constraint` results) -- the constraint atoms
1759
+ // make the same expansion in their own constructor (`atomArgs`,
1760
+ // ConstraintVal.ts, deliberately before the settled check), which is
1761
+ // why they are not in this set; `must` is the load-bearing example.
1762
+ // Arithmetic is here because `sub` is not commutative: `sub(a, b)`
1763
+ // reaching the engine as one two-element list would lose which is
1764
+ // which.
1765
+ const POSITIONAL_ARG_FUNCS = {};
1766
+ for (const name in sig_1.funcSig) {
1767
+ if (2 <= sig_1.funcSig[name].args.length && 'constraint' !== sig_1.funcSig[name].out) {
1768
+ POSITIONAL_ARG_FUNCS[name] = true;
1769
+ }
1770
+ }
996
1771
  // [min, max]; a max of -1 is unbounded. Every name in funcMap has an
997
1772
  // entry, and the arity is a property of the language rather than of
998
- // either port -- go/func.go carries the same table.
999
- //
1000
- // Nearly everything takes exactly one. The four exceptions earn their
1001
- // place: key() names how many levels UP the path to read, defaulting to
1002
- // the parent when omitted, neq takes a whole set of exclusions,
1003
- // unique() is a property of the container rather than a comparison
1004
- // against anything, so there is nothing for it to take, and must()
1005
- // takes a check AND the author's message for when it fails.
1006
- const funcArity = {
1007
- upper: [1, 1], lower: [1, 1], copy: [1, 1], pref: [1, 1],
1008
- super: [1, 1], type: [1, 1], hide: [1, 1], close: [1, 1],
1009
- open: [1, 1], move: [1, 1], path: [1, 1],
1010
- min: [1, 1], max: [1, 1], above: [1, 1], below: [1, 1], re: [1, 1],
1011
- length: [1, 1],
1012
- key: [0, 1],
1013
- unique: [0, 0],
1014
- neq: [1, -1],
1015
- must: [2, 2],
1016
- };
1773
+ // either port -- go/func.go derives the same table. A required slot
1774
+ // counts toward the minimum; a rest slot makes the maximum unbounded
1775
+ // and counts its group size (one, for a plain rest type) toward the
1776
+ // minimum, which is what gives `match` its floor of three and `neq`
1777
+ // its floor of one.
1778
+ function sigArity(sig) {
1779
+ let min = 0;
1780
+ let max = 0;
1781
+ for (const a of sig.args) {
1782
+ if (true === a.rest) {
1783
+ min += undefined === a.group ? 1 : a.group.length;
1784
+ max = -1;
1785
+ }
1786
+ else {
1787
+ if (true !== a.opt) {
1788
+ min++;
1789
+ }
1790
+ if (-1 !== max) {
1791
+ max++;
1792
+ }
1793
+ }
1794
+ }
1795
+ return [min, max];
1796
+ }
1797
+ const funcArity = {};
1798
+ for (const name in sig_1.funcSig) {
1799
+ funcArity[name] = sigArity(sig_1.funcSig[name]);
1800
+ }
1017
1801
  // writtenArgCount counts the arguments as the AUTHOR wrote them.
1018
1802
  //
1019
1803
  // It cannot simply be terms.length: a comma group reaches the func-paren
@@ -1043,8 +1827,10 @@ function arityText(lo, hi) {
1043
1827
  return 'one or more arguments';
1044
1828
  }
1045
1829
  if (lo !== hi) {
1046
- return 'no arguments or one';
1830
+ return 0 === lo ? 'no arguments or one' : 'one argument or two';
1047
1831
  }
1832
+ // The {0,0} arm returned with the container kinds and acyclic()
1833
+ // (ADR-015): `map(1)` must not claim map takes exactly one.
1048
1834
  if (0 === hi) {
1049
1835
  return 'no arguments';
1050
1836
  }
@@ -1085,15 +1871,21 @@ function opCharHint(src) {
1085
1871
  return '';
1086
1872
  }
1087
1873
  function rawToVal(n) {
1088
- if (null == n) {
1089
- return new NullVal_1.NullVal({ peg: null });
1090
- }
1091
- if (true === n.isVal) {
1874
+ if (true === n?.isVal) {
1092
1875
  return n;
1093
1876
  }
1094
1877
  if (Array.isArray(n)) {
1095
1878
  return new ListVal_1.ListVal({ peg: n.map(rawToVal) });
1096
1879
  }
1880
+ // THE SCALAR ARMS ARE WHERE A CONFIG FILE BECOMES VALUES. Every
1881
+ // format on the include table is read by its own parser into plain
1882
+ // JavaScript -- a string, a number, a map -- and this is the walk
1883
+ // that turns that into Vals (dataProcessor, ADR-012). The two arms
1884
+ // above are the other caller: a raw expression TERM, which the
1885
+ // expression grammar hands over already built.
1886
+ if (null == n) {
1887
+ return new NullVal_1.NullVal({ peg: null });
1888
+ }
1097
1889
  const t = typeof n;
1098
1890
  if ('string' === t) {
1099
1891
  return new StringVal_1.StringVal({ peg: n });
@@ -1108,14 +1900,20 @@ function rawToVal(n) {
1108
1900
  if ('boolean' === t) {
1109
1901
  return new BooleanVal_1.BooleanVal({ peg: n });
1110
1902
  }
1111
- if ('object' === t) {
1112
- const peg = {};
1113
- for (const k in n) {
1114
- peg[k] = rawToVal(n[k]);
1115
- }
1116
- return new MapVal_1.MapVal({ peg });
1903
+ // AND EVERYTHING ELSE IS A MAP, with no arm after it because there is
1904
+ // nothing after it. Every reader on the include table answers with
1905
+ // the JSON kinds and no others -- probed, including the two that
1906
+ // could plausibly escape them: a big integer comes back a `number`,
1907
+ // and a TOML date is normalised to its text before it gets here. The
1908
+ // one include that could hand over a function was `.js`, which
1909
+ // ADR-012 refuses. `parse_unknown` lived here for that case and has
1910
+ // no producer left in this port; the Go twin keeps its own, where the
1911
+ // type switch really can be handed something unaccounted for.
1912
+ const peg = {};
1913
+ for (const k in n) {
1914
+ peg[k] = rawToVal(n[k]);
1117
1915
  }
1118
- return new NilVal_1.NilVal({ why: 'parse_unknown' });
1916
+ return new MapVal_1.MapVal({ peg });
1119
1917
  }
1120
1918
  class Lang {
1121
1919
  constructor(options) {
@@ -1135,11 +1933,22 @@ class Lang {
1135
1933
  // works. `.jsonic` is retired (no longer auto-resolved); the
1136
1934
  // default `['jsonic','jsc','json','js']` is overridden here.
1137
1935
  // (Upstream option name is the misspelled `implictExt`.)
1936
+ //
1937
+ // Only these two are SEARCHED for a bare `@"name"`; `.json` and
1938
+ // `.jsonld` are read when NAMED, which is how a vendored
1939
+ // vocabulary is always written.
1138
1940
  implictExt: ['aon', 'aontu'],
1139
- processor: {
1140
- aontu: 'jsonic',
1141
- aon: 'jsonic',
1142
- }
1941
+ // ONE ENTRY PER EXTENSION THE TABLE NAMES, built from it (see
1942
+ // includeProcessors) so the rule and its wiring cannot drift.
1943
+ //
1944
+ // The upstream defaults are REPLACED, not extended. Its `json`
1945
+ // entry is what made that extension the one that crashed: it
1946
+ // hands back a raw JS object where the aontu grammar produces
1947
+ // Vals, and the tree then met a value it could not convert
1948
+ // (BUGS §49b). Its `js` entry EXECUTES the file, which is not
1949
+ // something an extension should be able to ask for. And its
1950
+ // fallback hands any other file back as TEXT.
1951
+ processor: includeProcessors()
1143
1952
  })
1144
1953
  .use(AontuJsonic);
1145
1954
  }
@@ -1152,7 +1961,13 @@ class Lang {
1152
1961
  multisource: {
1153
1962
  path: opts?.path ?? this.opts.path,
1154
1963
  deps: (opts && opts.deps) || undefined
1155
- }
1964
+ },
1965
+ // The include-manifest sink (G5, docs/trust.md): the resolver
1966
+ // records every resolved include here, and the plugin's
1967
+ // child-meta spread carries the same array to nested includes.
1968
+ aontu: {
1969
+ manifest: opts?.manifest,
1970
+ },
1156
1971
  };
1157
1972
  if (null != opts?.idcount) {
1158
1973
  this.idcount = opts.idcount;
@@ -1174,16 +1989,46 @@ class Lang {
1174
1989
  }
1175
1990
  }
1176
1991
  catch (e) {
1177
- if (e instanceof jsonic_1.JsonicError || 'JsonicError' === e.constructor.name) {
1992
+ if ('include_denied' === e?.code || 'include_extension' === e?.code ||
1993
+ mod_1.MODULE_REFUSAL_CODES.has(e?.code)) {
1994
+ // A denied include (G5), an include whose extension is not read
1995
+ // as Aontu source (ADR-012, INCLUDE_KINDS), and a module that is
1996
+ // missing, fails its pin, or names a path that escapes its store
1997
+ // (G6 phase 2) are refused the same way, for the same reason: the
1998
+ // resolver THROWS so a bare-member include cannot vanish in the
1999
+ // merge, and the code survives here as the parse-stage nil the
2000
+ // registry pins (errcodes.tsv).
1178
2001
  val = new NilVal_1.NilVal({
1179
2002
  why: 'parse',
1180
2003
  err: new NilVal_1.NilVal({
1181
- why: 'syntax',
1182
- msg: e.message + opCharHint(src),
2004
+ why: e.code,
2005
+ msg: e.message,
1183
2006
  err: e,
1184
2007
  })
1185
2008
  });
1186
2009
  }
2010
+ else if (e instanceof jsonic_1.JsonicError || 'JsonicError' === e.constructor.name) {
2011
+ const syntax = new NilVal_1.NilVal({
2012
+ why: 'syntax',
2013
+ msg: e.message + opCharHint(src),
2014
+ err: e,
2015
+ });
2016
+ // THE POSITION TRAVELS WITH IT. The parser knows exactly where
2017
+ // it stopped -- it draws a caret there -- and the rendered
2018
+ // message carried the only copy, so `vet --format json`
2019
+ // reported row -1, col -1 for a document whose fault the human
2020
+ // renderer located to the character. A machine-readable report
2021
+ // that says "somewhere in this file" is the one a repair loop
2022
+ // can do nothing with. Both numbers are already 1-based here,
2023
+ // which is the base a site uses (go/lang.go does the same).
2024
+ if ('number' === typeof e.lineNumber) {
2025
+ syntax.site.row = e.lineNumber;
2026
+ }
2027
+ if ('number' === typeof e.columnNumber) {
2028
+ syntax.site.col = e.columnNumber;
2029
+ }
2030
+ val = new NilVal_1.NilVal({ why: 'parse', err: syntax });
2031
+ }
1187
2032
  else {
1188
2033
  throw e;
1189
2034
  }