aontu 0.52.0 → 0.53.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (273) hide show
  1. package/README.md +88 -0
  2. package/bin/aontu-mcp.js +4 -0
  3. package/dist/agentsmd.d.ts +16 -0
  4. package/dist/agentsmd.js +107 -0
  5. package/dist/agentsmd.js.map +1 -0
  6. package/dist/aontu.d.ts +14 -3
  7. package/dist/aontu.js +145 -4
  8. package/dist/aontu.js.map +1 -1
  9. package/dist/cli.d.ts +44 -1
  10. package/dist/cli.js +2401 -44
  11. package/dist/cli.js.map +1 -1
  12. package/dist/ctx.d.ts +16 -0
  13. package/dist/ctx.js +44 -0
  14. package/dist/ctx.js.map +1 -1
  15. package/dist/diff.d.ts +22 -0
  16. package/dist/diff.js +141 -0
  17. package/dist/diff.js.map +1 -0
  18. package/dist/err.d.ts +3 -1
  19. package/dist/err.js +48 -8
  20. package/dist/err.js.map +1 -1
  21. package/dist/graph.d.ts +16 -0
  22. package/dist/graph.js +73 -0
  23. package/dist/graph.js.map +1 -0
  24. package/dist/hcanon.d.ts +3 -0
  25. package/dist/hcanon.js +146 -0
  26. package/dist/hcanon.js.map +1 -0
  27. package/dist/hints.js +223 -5
  28. package/dist/hints.js.map +1 -1
  29. package/dist/jsonschema.d.ts +20 -0
  30. package/dist/jsonschema.js +391 -0
  31. package/dist/jsonschema.js.map +1 -0
  32. package/dist/lang.js +698 -35
  33. package/dist/lang.js.map +1 -1
  34. package/dist/lsp.d.ts +9 -2
  35. package/dist/lsp.js +262 -46
  36. package/dist/lsp.js.map +1 -1
  37. package/dist/mcp-server.d.ts +20 -0
  38. package/dist/mcp-server.js +147 -0
  39. package/dist/mcp-server.js.map +1 -0
  40. package/dist/mcp.d.ts +42 -0
  41. package/dist/mcp.js +814 -0
  42. package/dist/mcp.js.map +1 -0
  43. package/dist/mod-tool.d.ts +58 -0
  44. package/dist/mod-tool.js +498 -0
  45. package/dist/mod-tool.js.map +1 -0
  46. package/dist/mod.d.ts +31 -0
  47. package/dist/mod.js +250 -0
  48. package/dist/mod.js.map +1 -0
  49. package/dist/patch.d.ts +44 -0
  50. package/dist/patch.js +506 -0
  51. package/dist/patch.js.map +1 -0
  52. package/dist/provenance.d.ts +40 -0
  53. package/dist/provenance.js +335 -0
  54. package/dist/provenance.js.map +1 -0
  55. package/dist/query.d.ts +27 -0
  56. package/dist/query.js +294 -0
  57. package/dist/query.js.map +1 -0
  58. package/dist/reach.d.ts +14 -0
  59. package/dist/reach.js +140 -0
  60. package/dist/reach.js.map +1 -0
  61. package/dist/relation.d.ts +19 -0
  62. package/dist/relation.js +305 -0
  63. package/dist/relation.js.map +1 -0
  64. package/dist/report-sarif.d.ts +14 -0
  65. package/dist/report-sarif.js +102 -0
  66. package/dist/report-sarif.js.map +1 -0
  67. package/dist/site.d.ts +4 -0
  68. package/dist/site.js +31 -0
  69. package/dist/site.js.map +1 -1
  70. package/dist/std.d.ts +1 -0
  71. package/dist/std.js +73 -0
  72. package/dist/std.js.map +1 -0
  73. package/dist/subsume.d.ts +39 -0
  74. package/dist/subsume.js +526 -0
  75. package/dist/subsume.js.map +1 -0
  76. package/dist/trim.d.ts +19 -0
  77. package/dist/trim.js +155 -0
  78. package/dist/trim.js.map +1 -0
  79. package/dist/tsconfig.tsbuildinfo +1 -1
  80. package/dist/type.d.ts +17 -1
  81. package/dist/type.js.map +1 -1
  82. package/dist/unify.d.ts +3 -1
  83. package/dist/unify.js +287 -18
  84. package/dist/unify.js.map +1 -1
  85. package/dist/utility.d.ts +9 -1
  86. package/dist/utility.js +122 -1
  87. package/dist/utility.js.map +1 -1
  88. package/dist/val/AggFuncVal.d.ts +33 -0
  89. package/dist/val/AggFuncVal.js +202 -0
  90. package/dist/val/AggFuncVal.js.map +1 -0
  91. package/dist/val/ArithFuncVal.d.ts +31 -0
  92. package/dist/val/ArithFuncVal.js +62 -0
  93. package/dist/val/ArithFuncVal.js.map +1 -0
  94. package/dist/val/BagVal.d.ts +5 -0
  95. package/dist/val/BagVal.js +96 -5
  96. package/dist/val/BagVal.js.map +1 -1
  97. package/dist/val/CloseFuncVal.js +9 -1
  98. package/dist/val/CloseFuncVal.js.map +1 -1
  99. package/dist/val/ConjunctVal.d.ts +1 -1
  100. package/dist/val/ConjunctVal.js +19 -0
  101. package/dist/val/ConjunctVal.js.map +1 -1
  102. package/dist/val/ConstraintVal.d.ts +48 -1
  103. package/dist/val/ConstraintVal.js +1501 -110
  104. package/dist/val/ConstraintVal.js.map +1 -1
  105. package/dist/val/CopyFuncVal.d.ts +1 -2
  106. package/dist/val/CopyFuncVal.js +7 -0
  107. package/dist/val/CopyFuncVal.js.map +1 -1
  108. package/dist/val/Decimal.d.ts +1 -0
  109. package/dist/val/Decimal.js +13 -0
  110. package/dist/val/Decimal.js.map +1 -1
  111. package/dist/val/DeprecateFuncVal.d.ts +11 -0
  112. package/dist/val/DeprecateFuncVal.js +47 -0
  113. package/dist/val/DeprecateFuncVal.js.map +1 -0
  114. package/dist/val/DisjunctVal.js +130 -21
  115. package/dist/val/DisjunctVal.js.map +1 -1
  116. package/dist/val/EachFuncVal.d.ts +15 -0
  117. package/dist/val/EachFuncVal.js +75 -0
  118. package/dist/val/EachFuncVal.js.map +1 -0
  119. package/dist/val/ExpectVal.d.ts +1 -0
  120. package/dist/val/ExpectVal.js +41 -4
  121. package/dist/val/ExpectVal.js.map +1 -1
  122. package/dist/val/FeatureVal.js +1 -1
  123. package/dist/val/FeatureVal.js.map +1 -1
  124. package/dist/val/FilterFuncVal.d.ts +15 -0
  125. package/dist/val/FilterFuncVal.js +91 -0
  126. package/dist/val/FilterFuncVal.js.map +1 -0
  127. package/dist/val/FuncBaseVal.d.ts +6 -1
  128. package/dist/val/FuncBaseVal.js +178 -2
  129. package/dist/val/FuncBaseVal.js.map +1 -1
  130. package/dist/val/HideFuncVal.js.map +1 -1
  131. package/dist/val/IdFuncVal.d.ts +13 -0
  132. package/dist/val/IdFuncVal.js +54 -0
  133. package/dist/val/IdFuncVal.js.map +1 -0
  134. package/dist/val/JunctionVal.js +7 -1
  135. package/dist/val/JunctionVal.js.map +1 -1
  136. package/dist/val/KeyFuncVal.d.ts +1 -1
  137. package/dist/val/KeyFuncVal.js +38 -30
  138. package/dist/val/KeyFuncVal.js.map +1 -1
  139. package/dist/val/ListVal.js +117 -17
  140. package/dist/val/ListVal.js.map +1 -1
  141. package/dist/val/LowerFuncVal.js.map +1 -1
  142. package/dist/val/MapVal.js +102 -8
  143. package/dist/val/MapVal.js.map +1 -1
  144. package/dist/val/MatchFuncVal.d.ts +15 -0
  145. package/dist/val/MatchFuncVal.js +107 -0
  146. package/dist/val/MatchFuncVal.js.map +1 -0
  147. package/dist/val/MoveFuncVal.js.map +1 -1
  148. package/dist/val/NilVal.js +24 -0
  149. package/dist/val/NilVal.js.map +1 -1
  150. package/dist/val/OpBaseVal.d.ts +1 -1
  151. package/dist/val/OpBaseVal.js +24 -2
  152. package/dist/val/OpBaseVal.js.map +1 -1
  153. package/dist/val/OpenFuncVal.js +4 -1
  154. package/dist/val/OpenFuncVal.js.map +1 -1
  155. package/dist/val/PackFuncVal.d.ts +15 -0
  156. package/dist/val/PackFuncVal.js +108 -0
  157. package/dist/val/PackFuncVal.js.map +1 -0
  158. package/dist/val/PathFuncVal.js.map +1 -1
  159. package/dist/val/PlaceVal.d.ts +13 -0
  160. package/dist/val/PlaceVal.js +131 -0
  161. package/dist/val/PlaceVal.js.map +1 -0
  162. package/dist/val/PlusOpVal.js +11 -2
  163. package/dist/val/PlusOpVal.js.map +1 -1
  164. package/dist/val/PrefFuncVal.js.map +1 -1
  165. package/dist/val/PrefVal.d.ts +2 -2
  166. package/dist/val/PrefVal.js +78 -23
  167. package/dist/val/PrefVal.js.map +1 -1
  168. package/dist/val/RefVal.d.ts +1 -1
  169. package/dist/val/RefVal.js +158 -30
  170. package/dist/val/RefVal.js.map +1 -1
  171. package/dist/val/ReferFuncVal.d.ts +36 -0
  172. package/dist/val/ReferFuncVal.js +303 -0
  173. package/dist/val/ReferFuncVal.js.map +1 -0
  174. package/dist/val/ScalarKindVal.d.ts +1 -2
  175. package/dist/val/ScalarKindVal.js +0 -11
  176. package/dist/val/ScalarKindVal.js.map +1 -1
  177. package/dist/val/TopVal.js.map +1 -1
  178. package/dist/val/TypeFuncVal.js.map +1 -1
  179. package/dist/val/UpperFuncVal.js.map +1 -1
  180. package/dist/val/Val.d.ts +9 -2
  181. package/dist/val/Val.js +150 -4
  182. package/dist/val/Val.js.map +1 -1
  183. package/dist/val/VarVal.js.map +1 -1
  184. package/dist/val/arith.d.ts +6 -0
  185. package/dist/val/arith.js +170 -0
  186. package/dist/val/arith.js.map +1 -0
  187. package/dist/vet.d.ts +45 -0
  188. package/dist/vet.js +776 -0
  189. package/dist/vet.js.map +1 -0
  190. package/dist/walk.d.ts +2 -0
  191. package/dist/walk.js +91 -0
  192. package/dist/walk.js.map +1 -0
  193. package/grammar/aontu.gbnf +130 -0
  194. package/grammar/aontu.lark +113 -0
  195. package/package.json +30 -15
  196. package/skill/SKILL.md +37 -0
  197. package/skill/error-codes.md +62 -0
  198. package/skill/examples.md +99 -0
  199. package/skill/grammar-card.md +57 -0
  200. package/src/agentsmd.ts +135 -0
  201. package/src/aontu.ts +192 -4
  202. package/src/cli.ts +2858 -71
  203. package/src/ctx.ts +81 -0
  204. package/src/diff.ts +196 -0
  205. package/src/err.ts +52 -8
  206. package/src/graph.ts +135 -0
  207. package/src/hcanon.ts +169 -0
  208. package/src/hints.ts +271 -5
  209. package/src/jsonschema.ts +511 -0
  210. package/src/lang.ts +779 -37
  211. package/src/lsp.ts +281 -47
  212. package/src/mcp-server.ts +187 -0
  213. package/src/mcp.ts +993 -0
  214. package/src/mod-tool.ts +679 -0
  215. package/src/mod.ts +344 -0
  216. package/src/patch.ts +624 -0
  217. package/src/provenance.ts +430 -0
  218. package/src/query.ts +379 -0
  219. package/src/reach.ts +184 -0
  220. package/src/relation.ts +395 -0
  221. package/src/report-sarif.ts +137 -0
  222. package/src/site.ts +36 -1
  223. package/src/std.ts +73 -0
  224. package/src/subsume.ts +690 -0
  225. package/src/trim.ts +195 -0
  226. package/src/tsconfig.json +10 -4
  227. package/src/type.ts +51 -2
  228. package/src/unify.ts +311 -16
  229. package/src/utility.ts +139 -1
  230. package/src/val/AggFuncVal.ts +319 -0
  231. package/src/val/ArithFuncVal.ts +108 -0
  232. package/src/val/BagVal.ts +101 -4
  233. package/src/val/CloseFuncVal.ts +9 -1
  234. package/src/val/ConjunctVal.ts +20 -0
  235. package/src/val/ConstraintVal.ts +1699 -116
  236. package/src/val/CopyFuncVal.ts +7 -1
  237. package/src/val/Decimal.ts +15 -0
  238. package/src/val/DeprecateFuncVal.ts +84 -0
  239. package/src/val/DisjunctVal.ts +139 -28
  240. package/src/val/EachFuncVal.ts +133 -0
  241. package/src/val/ExpectVal.ts +43 -6
  242. package/src/val/FeatureVal.ts +1 -1
  243. package/src/val/FilterFuncVal.ts +154 -0
  244. package/src/val/FuncBaseVal.ts +200 -3
  245. package/src/val/HideFuncVal.ts +0 -2
  246. package/src/val/IdFuncVal.ts +91 -0
  247. package/src/val/JunctionVal.ts +7 -1
  248. package/src/val/KeyFuncVal.ts +39 -35
  249. package/src/val/ListVal.ts +125 -18
  250. package/src/val/LowerFuncVal.ts +0 -1
  251. package/src/val/MapVal.ts +110 -8
  252. package/src/val/MatchFuncVal.ts +176 -0
  253. package/src/val/MoveFuncVal.ts +0 -2
  254. package/src/val/NilVal.ts +25 -0
  255. package/src/val/OpBaseVal.ts +26 -3
  256. package/src/val/OpenFuncVal.ts +4 -2
  257. package/src/val/PackFuncVal.ts +175 -0
  258. package/src/val/PathFuncVal.ts +0 -1
  259. package/src/val/PlaceVal.ts +193 -0
  260. package/src/val/PlusOpVal.ts +11 -2
  261. package/src/val/PrefFuncVal.ts +0 -1
  262. package/src/val/PrefVal.ts +79 -36
  263. package/src/val/RefVal.ts +163 -30
  264. package/src/val/ReferFuncVal.ts +387 -0
  265. package/src/val/ScalarKindVal.ts +0 -13
  266. package/src/val/TopVal.ts +0 -1
  267. package/src/val/TypeFuncVal.ts +0 -2
  268. package/src/val/UpperFuncVal.ts +0 -1
  269. package/src/val/Val.ts +213 -3
  270. package/src/val/VarVal.ts +0 -1
  271. package/src/val/arith.ts +316 -0
  272. package/src/vet.ts +992 -0
  273. package/src/walk.ts +99 -0
package/dist/lang.js CHANGED
@@ -3,6 +3,12 @@
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");
7
13
  const debug_1 = require("@tabnas/debug");
8
14
  const multisource_1 = require("@tabnas/multisource");
@@ -10,6 +16,8 @@ const multisource_1 = require("@tabnas/multisource");
10
16
  const file_1 = require("@tabnas/multisource/resolver/file");
11
17
  const pkg_1 = require("@tabnas/multisource/resolver/pkg");
12
18
  const mem_1 = require("@tabnas/multisource/resolver/mem");
19
+ const std_1 = require("./std");
20
+ const mod_1 = require("./mod");
13
21
  const expr_1 = require("@tabnas/expr");
14
22
  const path_1 = require("@tabnas/path");
15
23
  const type_1 = require("./type");
@@ -41,6 +49,16 @@ const CopyFuncVal_1 = require("./val/CopyFuncVal");
41
49
  const KeyFuncVal_1 = require("./val/KeyFuncVal");
42
50
  const TypeFuncVal_1 = require("./val/TypeFuncVal");
43
51
  const HideFuncVal_1 = require("./val/HideFuncVal");
52
+ const DeprecateFuncVal_1 = require("./val/DeprecateFuncVal");
53
+ const IdFuncVal_1 = require("./val/IdFuncVal");
54
+ const ReferFuncVal_1 = require("./val/ReferFuncVal");
55
+ const PackFuncVal_1 = require("./val/PackFuncVal");
56
+ const EachFuncVal_1 = require("./val/EachFuncVal");
57
+ const FilterFuncVal_1 = require("./val/FilterFuncVal");
58
+ const MatchFuncVal_1 = require("./val/MatchFuncVal");
59
+ const ArithFuncVal_1 = require("./val/ArithFuncVal");
60
+ const AggFuncVal_1 = require("./val/AggFuncVal");
61
+ const PlaceVal_1 = require("./val/PlaceVal");
44
62
  const MoveFuncVal_1 = require("./val/MoveFuncVal");
45
63
  const PathFuncVal_1 = require("./val/PathFuncVal");
46
64
  const PrefFuncVal_1 = require("./val/PrefFuncVal");
@@ -201,6 +219,16 @@ let AontuJsonic = function AontuLang(jsonic) {
201
219
  v.site.row = null == r.o0 ? -1 : r.o0.rI;
202
220
  v.site.col = null == r.o0 ? -1 : r.o0.cI;
203
221
  v.site.url = ctx.meta.multisource ? ctx.meta.multisource.path : '';
222
+ // The source text, from the SAME token the row and column above
223
+ // come from. jsonic has carried it all along; not reading it is
224
+ // what left a site uneditable (ts/src/site.ts).
225
+ //
226
+ // A TOKEN WITH NO TEXT IS NO SPAN, in both ports, and the extent is
227
+ // DERIVED from the text rather than read from the token's own `len`
228
+ // — so the Go twin, whose token has no len field, computes the
229
+ // identical number with utf16Len and nothing has to be kept in step.
230
+ v.site.src = null == r.o0 ? '' : r.o0.src;
231
+ v.site.len = '' === v.site.src ? -1 : v.site.src.length;
204
232
  // A keyed rule always carries a path array; a keyless one has none.
205
233
  v.path = r.k ? [...r.k.path] : [];
206
234
  return v;
@@ -264,6 +292,12 @@ help isolate the syntax error.`,
264
292
  },
265
293
  // TODO: FIX: need a TOP instance to hold path
266
294
  'top': { val: () => (0, top_1.top)() },
295
+ // G8 phase 3: the placeholder. A BARE `_` is the hole; `"_"`
296
+ // quoted, and any longer bare word containing it, stay text.
297
+ // Reserving it is a breaking change, pinned by place.tsv.
298
+ '_': {
299
+ val: (r, ctx) => addsite(new PlaceVal_1.PlaceVal({}), r, ctx)
300
+ },
267
301
  }
268
302
  },
269
303
  map: {
@@ -316,6 +350,71 @@ help isolate the syntax error.`,
316
350
  above: ConstraintVal_1.AboveConstraintVal,
317
351
  below: ConstraintVal_1.BelowConstraintVal,
318
352
  neq: ConstraintVal_1.NeqConstraintVal,
353
+ // G1 phase 2: pattern membership, over the portable subset both
354
+ // host regex engines agree on (nonPortableRe in ConstraintVal.ts).
355
+ re: ConstraintVal_1.ReConstraintVal,
356
+ // G1 phase 3: the sizing atoms. Both are properties of a CONTAINER
357
+ // (or, for length, of a string) rather than comparisons against a
358
+ // value, which is why `unique` is the one built-in taking no
359
+ // argument at all.
360
+ length: ConstraintVal_1.LengthConstraintVal,
361
+ unique: ConstraintVal_1.UniqueConstraintVal,
362
+ // G1 phase 5: Band B. `must` is the one atom the algebra does not
363
+ // reason about -- it is checked against the finished value and
364
+ // reported with the author's own message, never simplified and
365
+ // never consulted for emptiness or subsumption.
366
+ must: ConstraintVal_1.MustConstraintVal,
367
+ // G3 phase 4: the deprecation mark. Unification-transparent; the
368
+ // record rides the result (Val.deprecation) and canon renders the
369
+ // call back (canonRiders).
370
+ deprecate: DeprecateFuncVal_1.DeprecateFuncVal,
371
+ // G4 phase 1: the identity mark. Written as a conjunct
372
+ // (`id(svc/auth) & {…}`), it resolves to the unit carrying the
373
+ // name, and every node in one evaluation with that name is
374
+ // unified with every other.
375
+ id: IdFuncVal_1.IdFuncVal,
376
+ // G4 phase 2: the checked, typed, LINK-shaped reference. A
377
+ // constraint on a string field: the string must be an entity
378
+ // address, the address must resolve, and the optional argument
379
+ // flows INTO the target. The field keeps the string.
380
+ refer: ReferFuncVal_1.ReferFuncVal,
381
+ // G8 phase 1: the generation combinators. `pack` makes one keyed
382
+ // child per child of its data, `each` one list element; both clone
383
+ // their template per destination exactly as a spread does, and both
384
+ // wait for the model to settle before they fire (the staging rule,
385
+ // G8 phase 0).
386
+ pack: PackFuncVal_1.PackFuncVal,
387
+ each: EachFuncVal_1.EachFuncVal,
388
+ // G8 phase 2: selection. `filter` keeps the children of a bag that
389
+ // unify with a condition; `match` picks the first arm whose
390
+ // pattern the scrutinee unifies with. Both select by
391
+ // UNIFIABILITY, tried in trial mode, so neither adds a predicate
392
+ // language to the one the lattice already is.
393
+ filter: FilterFuncVal_1.FilterFuncVal,
394
+ match: MatchFuncVal_1.MatchFuncVal,
395
+ // The arithmetic family (the review's finding I). Maths beyond `+`
396
+ // arrives as FUNCTIONS — `-` `*` `/` `%` stay reserved — and the
397
+ // family is numeric where the operator is polymorphic, which is
398
+ // what makes `add` more than a second spelling of `+`: it refuses
399
+ // the string concatenation that silently answers `"500m" + "500m"`.
400
+ // Every rule they obey is in ts/src/val/arith.ts.
401
+ add: ArithFuncVal_1.AddFuncVal,
402
+ sub: ArithFuncVal_1.SubFuncVal,
403
+ mul: ArithFuncVal_1.MulFuncVal,
404
+ div: ArithFuncVal_1.DivFuncVal,
405
+ mod: ArithFuncVal_1.ModFuncVal,
406
+ rem: ArithFuncVal_1.RemFuncVal,
407
+ // Aggregation over a finite, settled bag (the review's finding I).
408
+ // `least` and `greatest` rather than min and max, which are already
409
+ // the atoms for a lower and an upper BOUND -- an aggregate over a
410
+ // set and a bound on a value must not share a spelling.
411
+ sum: AggFuncVal_1.SumFuncVal,
412
+ least: AggFuncVal_1.LeastFuncVal,
413
+ greatest: AggFuncVal_1.GreatestFuncVal,
414
+ // Projection, which is what lets the aggregates reach a bag of
415
+ // RECORDS: `sum(pick($.lines, amountCents))`. Not a clever `each`
416
+ // template -- `each` MEETS each child, and a meet cannot select.
417
+ pick: AggFuncVal_1.PickFuncVal,
319
418
  };
320
419
  // A dangling operator (`a:1|`, `a:$`, `a:*` at end of input) leaves
321
420
  // null/undefined unfilled terms. Junction ops drop them (so `a:1&`
@@ -324,6 +423,106 @@ help isolate the syntax error.`,
324
423
  // resolve value" error (the Go port mirrors this in lang.go).
325
424
  const dropUnfilled = (terms) => terms.filter((t) => null != t);
326
425
  const incompleteNil = (r, ctx) => addsite(new NilVal_1.NilVal({ why: 'incomplete_expression' }), r, ctx);
426
+ // Build a call from a NAME and the argument terms as the author
427
+ // wrote them. Shared by the `func(...)` handler and by the pipe,
428
+ // which is the same call with one more argument on the front — so
429
+ // the arity check, the comma-group rule and the raw-value conversion
430
+ // are stated once and both spellings get all three.
431
+ const buildCall = (r, ctx, fname, argterms) => {
432
+ const funcval = funcMap[fname];
433
+ // Arity is known for every built-in, so a surplus or missing
434
+ // argument is a mistake in the SOURCE, refused here where the
435
+ // author can see it (issue #51). It was previously left to each
436
+ // function to notice or not: the two ports disagreed on `upper()`
437
+ // and on `close()`, and `min(1,2)` noticed nothing at all -- it
438
+ // built a constraint that merely refused to generate later, with a
439
+ // message about the map rather than about the call.
440
+ //
441
+ // Counted BEFORE the rawToVal pass below, which is what makes the
442
+ // count possible: a comma group arrives as a RAW array and a
443
+ // written list literal as a ListVal, and rawToVal turns the first
444
+ // into the second.
445
+ const arity = funcArity[fname];
446
+ if (null != arity) {
447
+ const got = writtenArgCount(argterms);
448
+ if (got < arity[0] || (-1 !== arity[1] && got > arity[1])) {
449
+ // details is assigned AFTER construction: the NilVal
450
+ // constructor does not read it from its spec (only NilVal.make
451
+ // does), so passing it in the spec left the hint's
452
+ // {func}/{want}/{got} placeholders un-injected and printed
453
+ // literally.
454
+ const nil = new NilVal_1.NilVal({ why: 'func_arity' });
455
+ nil.details = {
456
+ func: fname,
457
+ want: arityText(arity[0], arity[1]),
458
+ got: '' + got,
459
+ };
460
+ // The CALL AS WRITTEN rides the refusal (G8 phase 4): a pipe
461
+ // rebuilds `x |> upper()` as `upper(x)`, and the arity it fails
462
+ // on here is the arity of a call one argument short of the one
463
+ // the author actually wrote.
464
+ nil._callname = fname;
465
+ nil._callterms = argterms;
466
+ return addsite(nil, r, ctx);
467
+ }
468
+ }
469
+ // rawToVal EVERY argument. A degenerate expression can hand this
470
+ // handler raw parse values rather than Vals -- `pref(1-3)` arrives
471
+ // as the plain numbers 1 and -3 -- and a func's peg is unified
472
+ // element by element, so a raw one reached `arg.unify(...)` and
473
+ // threw. The unifier's catch-all turned that into an `internal`
474
+ // verdict: a crash reported as a unification result (issue #49).
475
+ // The Go port has always converted here (asVal in evaluate).
476
+ // A comma group is ONE raw-array term (see writtenArgCount).
477
+ // For a function whose arguments are distinct POSITIONS —
478
+ // deprecate's value and record, pack's and each's data and template
479
+ // — the group is expanded back into them here, while a written list
480
+ // literal, already a ListVal, stays one argument. The constraint
481
+ // atoms make the same move in their own constructor (atomArgs,
482
+ // ConstraintVal.ts), which is why they are not in this set:
483
+ // `neq(1,2)` is one argument LIST, not two positions, and expanding
484
+ // it here would take the list away from the code that reads it.
485
+ let terms = argterms;
486
+ if (true === POSITIONAL_ARG_FUNCS[fname] && 1 === terms.length &&
487
+ Array.isArray(terms[0])) {
488
+ terms = terms[0];
489
+ }
490
+ const args = terms.map(rawToVal);
491
+ const val = null == funcval ?
492
+ new NilVal_1.NilVal({ why: 'unknown_function' }) :
493
+ new funcval({ peg: args });
494
+ // The call as written, for the pipe to rebuild from. Parse-time
495
+ // only: nothing downstream reads it, and a clone does not carry it.
496
+ //
497
+ // NOT on a constraint atom that BUILT: an atom with its argument
498
+ // list complete is a residual, not a call waiting for a subject,
499
+ // and `1 |> neq(2,3)` is asking for `1 & neq(2,3)` -- which is
500
+ // what `&` is for. (An atom the arity check REFUSED still carries
501
+ // it, on the nil above: `1 |> min()` is `min(1)`, and that is a
502
+ // call waiting for a subject.) The Go port cannot rebuild a built
503
+ // atom at all -- its residual keeps no atom name -- so this is
504
+ // also what keeps the two ports answering the same thing.
505
+ if (true !== val.isConstraint) {
506
+ val._callname = fname;
507
+ val._callterms = argterms;
508
+ }
509
+ return val;
510
+ };
511
+ // The argument terms `f(...)` would have been written with, had the
512
+ // piped value been written into it. A comma group is one raw-array
513
+ // term: for a POSITIONAL function the group is separate arguments,
514
+ // so the piped value joins them; for a constraint atom the group IS
515
+ // the argument list, so the piped value joins the list instead.
516
+ const pipeTerms = (call, val) => {
517
+ const written = call._callterms;
518
+ const group = 1 === written.length && Array.isArray(written[0]) ?
519
+ written[0] : written;
520
+ if (0 === group.length) {
521
+ return [val];
522
+ }
523
+ return true === POSITIONAL_ARG_FUNCS[call._callname] ?
524
+ [val, ...group] : [[val, ...group]];
525
+ };
327
526
  let opmap = {
328
527
  'conjunct-infix': (r, ctx, _op, terms) => addsite(new ConjunctVal_1.ConjunctVal({ peg: dropUnfilled(terms) }), r, ctx),
329
528
  'disjunct-infix': (r, ctx, _op, terms) => addsite(new DisjunctVal_1.DisjunctVal({ peg: dropUnfilled(terms) }), r, ctx),
@@ -394,22 +593,51 @@ help isolate the syntax error.`,
394
593
  return incompleteNil(r, ctx);
395
594
  return addsite(val, r, ctx);
396
595
  },
596
+ // THE PIPE `|>` (G8 phase 4): parse-time sugar and nothing else.
597
+ // `x |> f(a)` IS `f(x, a)` -- the piped value goes in as the FIRST
598
+ // argument, Elixir-style, because every Aontu call is data-first
599
+ // already (`close(x)`, `pack(data, tmpl)`) and a pipe must read the
600
+ // way the calls it replaces read. It never reaches a Val: by the
601
+ // time the tree exists the call is an ordinary call, which is why
602
+ // canon can never emit the token and the two ports' canon stay
603
+ // byte-identical without either knowing about it.
604
+ 'pipe-infix': (r, ctx, _op, terms) => {
605
+ const val = terms[0];
606
+ const call = terms[1];
607
+ if (null == val || null == call)
608
+ return incompleteNil(r, ctx);
609
+ // The right-hand side is a CALL: either one the func handler
610
+ // already built, or one it refused for an arity the pipe is about
611
+ // to satisfy. Both carry what they were written as.
612
+ if (null != call._callname) {
613
+ return buildCall(r, ctx, call._callname, pipeTerms(call, val));
614
+ }
615
+ // ... or a bare NAME, which is the whole point of the short
616
+ // spelling: `x |> upper` is `upper(x)`. A bare word has already
617
+ // become a string VALUE by the time an infix operator sees it, so
618
+ // this is where a string becomes a call.
619
+ if (true === call?.isScalar && 'string' === typeof call.peg &&
620
+ null != funcMap[call.peg]) {
621
+ return buildCall(r, ctx, call.peg, [val]);
622
+ }
623
+ // Anything else is not a call, and a pipe into a non-call is a
624
+ // mistake in the source rather than a value.
625
+ return addsite(new NilVal_1.NilVal({ why: 'pipe_target' }), r, ctx);
626
+ },
397
627
  'func-paren': (r, ctx, _op, terms) => {
398
628
  let val = terms[1];
399
629
  const fname = terms[0];
400
630
  if ('' !== fname) {
401
- const funcval = funcMap[fname];
402
- const args = terms.slice(1);
403
- val = null == funcval ?
404
- new NilVal_1.NilVal({ why: 'unknown_function' }) :
405
- new funcval({
406
- peg: args
407
- });
631
+ val = buildCall(r, ctx, fname, terms.slice(1));
408
632
  }
409
633
  // `a:()` — grouping parens with nothing inside.
410
634
  if (null == val)
411
635
  return incompleteNil(r, ctx);
412
- const out = addsite(val, r, ctx);
636
+ // ... and the same for a GROUPING paren, whose value is passed
637
+ // straight through: `(([]%))` yielded a raw array, and addsite went
638
+ // on to write a site onto it, throwing a TypeError that escaped the
639
+ // unifier entirely ("Cannot set properties of undefined").
640
+ const out = addsite(rawToVal(val), r, ctx);
413
641
  return out;
414
642
  },
415
643
  };
@@ -423,6 +651,13 @@ help isolate the syntax error.`,
423
651
  'disjunct': {
424
652
  infix: true, src: '|', left: 14_000_000, right: 15_000_000
425
653
  },
654
+ // G8 phase 4: the pipe. LOOSEST of all the infix operators, so
655
+ // `a & b |> f` pipes the whole meet and not just `b` -- a pipe
656
+ // reads as "and then", which is a statement about everything to
657
+ // its left. Kept in lock-step with the op table in go/lang.go.
658
+ 'pipe-infix': {
659
+ infix: true, src: '|>', left: 12_000_000, right: 13_000_000
660
+ },
426
661
  'plus-infix': {
427
662
  src: '+',
428
663
  infix: true,
@@ -603,6 +838,13 @@ help isolate the syntax error.`,
603
838
  valnode.site.row = st.rI;
604
839
  valnode.site.col = st.cI;
605
840
  valnode.site.url = ctx.meta.multisource && ctx.meta.multisource.path;
841
+ // No `?? ''` and no empty-text arm here: this branch runs only
842
+ // for a rule that HAS an open token, and a token that opens a
843
+ // value always carries text — the coverage gate refuses both
844
+ // guards as dead. The unset case is the one above, where r.o0
845
+ // itself can be absent.
846
+ valnode.site.src = st.src;
847
+ valnode.site.len = st.src.length;
606
848
  }
607
849
  // else { ERROR? }
608
850
  r.node = valnode;
@@ -621,12 +863,56 @@ help isolate the syntax error.`,
621
863
  const optionalKeys = r.u.aontu_optional_keys ?? [];
622
864
  let mo = r.node;
623
865
  // An elided value (`a:`) leaves a raw null/undefined that never
624
- // passed through the val rule; make it an explicit NullVal.
866
+ // passed through the val rule. It is REFUSED rather than made a
867
+ // null (issue #48): a key with nothing after the colon is a
868
+ // mistake in the source, and turning it into a value made that
869
+ // mistake indistinguishable from a deliberate `a:null`.
870
+ //
871
+ // A colon chain (`a: b:1`) is not an elision -- the value is the
872
+ // nested pair, which the val rule does produce -- and neither is
873
+ // a trailing comma.
625
874
  for (const k in mo) {
626
875
  if (null == mo[k] && '___merge' !== k) {
627
- mo[k] = addsite(new NullVal_1.NullVal({ peg: null }), r, ctx);
876
+ // Pathed at the KEY, not at the enclosing map. addsite takes
877
+ // the rule's path, which here is the map's, so the error
878
+ // would otherwise name the container and leave the reader to
879
+ // work out which key was elided.
880
+ const en = addsite(new NilVal_1.NilVal({ why: 'elided_value' }), r, ctx);
881
+ en.path = [...(r.k?.path ?? []), k];
882
+ mo[k] = en;
883
+ // An elided value under an OPTIONAL key stops being optional.
884
+ // Optionality is about a value that may be absent at
885
+ // GENERATE; it does not excuse a source that stops after the
886
+ // colon. Left optional, the refusal was dropped with the key
887
+ // and `a?:` generated `{}` -- a silent nothing, which is
888
+ // worse than either the old null or the error.
889
+ const oi = optionalKeys.indexOf(k);
890
+ if (-1 !== oi) {
891
+ optionalKeys.splice(oi, 1);
892
+ }
628
893
  }
629
894
  }
895
+ // ... and the OPTIONAL spelling, `a?:`, which does not leave a
896
+ // null behind to be found: its value never reaches the node at
897
+ // all, so the key is simply absent and the map generated without
898
+ // it. A key recorded as optional but missing from the node was
899
+ // written with nothing after its colon.
900
+ for (const k of optionalKeys) {
901
+ if (!(k in mo)) {
902
+ mo[k] = addsite(new NilVal_1.NilVal({ why: 'elided_value' }), r, ctx);
903
+ }
904
+ }
905
+ // An elided SPREAD (`x:$obj&:` with nothing after the colon)
906
+ // refuses the whole map, not a key (issue #48). A spread is not a
907
+ // child, so a refusal stored in its place has nothing to attach
908
+ // to: `x:&:` has no children for the spread to apply to, and the
909
+ // map would generate as `{}` with the mistake silently gone.
910
+ // Refusing the container is what makes it visible at all.
911
+ const sp = mo[type_1.SPREAD];
912
+ if (sp && sp.v.some((sv) => null == sv)) {
913
+ r.node = addsite(new NilVal_1.NilVal({ why: 'elided_value' }), r, ctx);
914
+ return undefined;
915
+ }
630
916
  // Handle defered conjuncts, e.g. `{x:1 @"foo"}`
631
917
  if (mo.___merge) {
632
918
  let mop = { ...mo };
@@ -652,11 +938,15 @@ help isolate the syntax error.`,
652
938
  .bc((r, ctx) => {
653
939
  const optionalKeys = r.u.aontu_optional_keys ?? [];
654
940
  let ao = r.node;
655
- // An elided element (`[,]`) is a raw null that never passed
656
- // through the val rule; make it an explicit NullVal.
941
+ // An elided ELEMENT (`[,]`, `[1,,2]`) is refused for the same
942
+ // reason as an elided map value (issue #48). A trailing comma
943
+ // (`[1,]`) is not an elision and never reaches here.
657
944
  for (let i = 0; i < ao.length; i++) {
658
945
  if (null == ao[i]) {
659
- ao[i] = addsite(new NullVal_1.NullVal({ peg: null }), r, ctx);
946
+ // Pathed at the INDEX, for the same reason as the map case.
947
+ const en = addsite(new NilVal_1.NilVal({ why: 'elided_value' }), r, ctx);
948
+ en.path = [...(r.k?.path ?? []), '' + i];
949
+ ao[i] = en;
660
950
  }
661
951
  }
662
952
  // No ___merge arm here: the deferred map.merge that writes it
@@ -681,6 +971,47 @@ help isolate the syntax error.`,
681
971
  : key_token.src; // Was number, use original text
682
972
  r.u.key = key;
683
973
  };
974
+ // A pair in LIST position writes its value at `node[key]` like any
975
+ // other pair, and the enclosing list's node is an ARRAY -- so a numeric
976
+ // key lands on an index and becomes an element, and may land on an
977
+ // index a real element already holds (`[5,0:1]`). Since the pair must
978
+ // contribute nothing (issue #40), the slot is photographed before the
979
+ // value is parsed and put back afterwards. Restoring beats deleting for
980
+ // exactly the overwrite case: deleting `[5,0:1]`'s index 0 would take
981
+ // the 5 with it, where restoring gives back the list the pair was
982
+ // never part of.
983
+ //
984
+ // `length` is saved too: writing past the end grows an array, and
985
+ // `[5,1?:9]` must be [5] again and not [5, <hole>].
986
+ // Typed as a string bag deliberately: the slot may be named by a
987
+ // non-numeric key (`[x:1]`), which the array type would refuse. Both
988
+ // helpers run only from the elem rule, whose node is the enclosing
989
+ // list, so neither guards against a non-array node — a guard there
990
+ // proved unreachable and dead code is worse than none.
991
+ const asSlots = (r) => r.node;
992
+ const snapshotPairSlot = (r, key) => {
993
+ const node = asSlots(r);
994
+ r.u.aontu_pair_slot = {
995
+ key,
996
+ had: Object.prototype.hasOwnProperty.call(node, key),
997
+ was: node[key],
998
+ len: node.length,
999
+ };
1000
+ };
1001
+ const restorePairSlot = (r) => {
1002
+ const slot = r.u.aontu_pair_slot;
1003
+ if (null == slot) {
1004
+ return;
1005
+ }
1006
+ const node = asSlots(r);
1007
+ if (slot.had) {
1008
+ node[slot.key] = slot.was;
1009
+ }
1010
+ else {
1011
+ delete node[slot.key];
1012
+ }
1013
+ node.length = slot.len;
1014
+ };
684
1015
  jsonic.rule('pair', (rs) => {
685
1016
  rs
686
1017
  .open([
@@ -756,10 +1087,30 @@ help isolate the syntax error.`,
756
1087
  a: (r) => {
757
1088
  pairkey(r.prev);
758
1089
  r.u.key = r.prev.u.key;
759
- r.parent.u.aontu_optional_keys = (r.parent.u.aontu_optional_keys || []);
760
- r.parent.u.aontu_optional_keys.push('' + r.u.key);
1090
+ snapshotPairSlot(r, '' + r.u.key);
761
1091
  },
762
1092
  g: 'aontu-optional-elem'
1093
+ },
1094
+ // A PLAIN pair in list position, `[k:v]`. It contributes no
1095
+ // element either -- a key:value pair is simply not a list element,
1096
+ // which is the rule the optional form above already followed, and
1097
+ // the two spellings must not disagree (issue #40).
1098
+ //
1099
+ // It needed an alt of its own because only a NON-NUMERIC key was
1100
+ // already inert: jsonic writes the pair at `node[key]`, and the
1101
+ // node is an array, so `[x:1]` set a property that never showed up
1102
+ // (`length` stays 0) while `[0:1]` set an INDEX and became an
1103
+ // element -- `[1:2]` even filling the gap with a null. That is the
1104
+ // shape of a JavaScript array, not a decision about the language,
1105
+ // and it made the two ports disagree on generate as well as canon.
1106
+ {
1107
+ s: [OPTKEY, CL], p: 'val',
1108
+ u: { spread: true, done: true, list: true, pair: true },
1109
+ a: (r) => {
1110
+ pairkey(r);
1111
+ snapshotPairSlot(r, '' + r.u.key);
1112
+ },
1113
+ g: 'aontu-plain-pair-elem'
763
1114
  }
764
1115
  ])
765
1116
  .bc((rule) => {
@@ -769,24 +1120,44 @@ help isolate the syntax error.`,
769
1120
  (rule.node[type_1.SPREAD] || { o: rule.o0.src, v: [] });
770
1121
  rule.node[type_1.SPREAD].v.push(rule.child.node);
771
1122
  }
1123
+ restorePairSlot(rule);
772
1124
  return undefined;
773
1125
  })
774
1126
  .close([{ s: [CJ, CL], r: 'elem', b: 2, g: 'spread,json,more' }]);
775
1127
  return rs;
776
1128
  });
777
1129
  };
778
- // SECURITY: the default resolver reads any file/package the process can
779
- // reach — @"path" follows relative paths (`@"../../etc/passwd"`) and
780
- // symlinks with no containment check, and @"pkg" can require() arbitrary
781
- // installed modules. This is intentional for the CLI, but it means a
782
- // `.aon` source can read referenced files; the LSP uses this same
783
- // resolver, so treat opening an untrusted source as running it. Pass a
784
- // confined `options.resolver` to restrict reads in less-trusted contexts.
1130
+ // SECURITY: under the DEFAULT ('system') include capability this
1131
+ // resolver reads any file/package the process can reach — @"path"
1132
+ // follows relative paths (`@"../../etc/passwd"`) and symlinks, and
1133
+ // @"pkg" can require() arbitrary installed modules — so treat opening
1134
+ // an untrusted source as running it. The trust profile (G5,
1135
+ // docs/trust.md) is the confinement surface: `trust.include` of
1136
+ // 'none', `{ mem }` or `{ root }` restricts what `@"..."` may resolve,
1137
+ // and a denied resolution is a deterministic parse-stage
1138
+ // `include_denied` error.
785
1139
  function makeModelResolver(options) {
786
1140
  const useRequire = options.require || require;
787
- let memResolver = (0, mem_1.makeMemResolver)({
788
- ...(options.resolver?.mem || {})
789
- });
1141
+ const capability = options.trust?.include ?? 'system';
1142
+ const memCapability = 'object' === typeof capability && null != capability.mem;
1143
+ const rootDir = 'object' === typeof capability &&
1144
+ 'string' === typeof capability.root
1145
+ ? (0, node_path_1.resolve)(capability.root) : undefined;
1146
+ // Under the mem capability the CAPABILITY's file set is the whole
1147
+ // world; otherwise the host-injected `options.resolver.mem` entries
1148
+ // remain available under every capability but 'none' — they are
1149
+ // host-provided, not document-requested, so confining them would
1150
+ // confine the host against itself.
1151
+ // THE BUNDLED VOCABULARY (G4 phase 4, ts/src/std.ts) rides the
1152
+ // memory leg: served from the engine itself, so it needs neither the
1153
+ // filesystem nor package resolution and is available under every
1154
+ // capability but `none` — which denies every include outright, that
1155
+ // being what `none` means. Host entries and the capability's own set
1156
+ // WIN over it: a caller that supplies its own `std/system` gets the
1157
+ // one it supplied.
1158
+ let memResolver = (0, mem_1.makeMemResolver)(memCapability
1159
+ ? { ...capability.mem }
1160
+ : { ...(options.resolver?.mem || {}) });
790
1161
  // TODO: make this consistent with other resolvers
791
1162
  let fileResolver = (0, file_1.makeFileResolver)((spec) => {
792
1163
  return 'string' === typeof spec ? spec : spec?.peg;
@@ -795,6 +1166,91 @@ function makeModelResolver(options) {
795
1166
  require: useRequire,
796
1167
  ...(options.resolver?.pkg || {})
797
1168
  });
1169
+ // Confinement is realpath-then-prefix-check (docs/trust.md): the
1170
+ // RESOLVED file's real path must sit below the root's real path, so a
1171
+ // symlink inside the root pointing outside it is an escape, not a
1172
+ // loophole. A path realpath cannot resolve falls back to the lexical
1173
+ // form — the comparison is then against what the resolver actually
1174
+ // read.
1175
+ // Real fs, deliberately: `options.fs` is not a sandbox (it feeds
1176
+ // parse text; the file leg reads through its own channel), so the
1177
+ // containment check must see the same filesystem that leg read from.
1178
+ // A path that does not (fully) exist cannot be realpath'd whole, and
1179
+ // falling back to the LEXICAL form compares apples to oranges when
1180
+ // the root itself sits behind a symlink -- on macOS a root under
1181
+ // /var realpaths to /private/var, so a merely-missing file inside it
1182
+ // reads as an escape. Realpath the deepest EXISTING ancestor and
1183
+ // re-attach the rest, so both sides of the check are in real
1184
+ // coordinates. (The MCP server's own confinement carries the twin of
1185
+ // this rule; its CI failure is what found the shape.)
1186
+ const realpath = (p) => {
1187
+ try {
1188
+ return (0, node_fs_1.realpathSync)(p);
1189
+ }
1190
+ catch {
1191
+ const parent = (0, node_path_1.dirname)(p);
1192
+ if (parent === p) {
1193
+ return p;
1194
+ }
1195
+ return (0, node_path_1.join)(realpath(parent), (0, node_path_1.basename)(p));
1196
+ }
1197
+ };
1198
+ const outsideRoot = (root, full) => {
1199
+ const rootReal = realpath(root);
1200
+ const fullReal = realpath(full);
1201
+ return fullReal !== rootReal && !fullReal.startsWith(rootReal + node_path_1.sep);
1202
+ };
1203
+ // A denial THROWS with the code; Lang.parse converts it to the
1204
+ // parse-stage `include_denied` nil (the same shape a syntax failure
1205
+ // takes). Raising beats injecting a nil value: a bare-member include
1206
+ // (`@"denied.aon"` at the top of a file) MERGES into the enclosing
1207
+ // map, and a nil contributes no keys, so an injected denial would
1208
+ // vanish and leave a plausible, silently-partial document.
1209
+ const deny = (path) => {
1210
+ // Only 'none' and 'root' can deny: the mem capability's misses are
1211
+ // not-found (its set is the whole world), so there is no third arm.
1212
+ const capname = 'none' === capability ? 'none' : 'root:' + rootDir;
1213
+ const err = new Error('include denied: ' + path + ' (capability: ' + capname + ')');
1214
+ err.code = 'include_denied';
1215
+ throw err;
1216
+ };
1217
+ // The user cache: whatever the host named, else the platform rule
1218
+ // (`modCacheDir`, ts/src/mod.ts) the tooling writes by.
1219
+ const modCache = (opts) => {
1220
+ const named = opts.mod?.cache;
1221
+ return 'string' === typeof named ? named : (0, mod_1.modCacheDir)();
1222
+ };
1223
+ // The directory an include is being resolved FROM: the source that
1224
+ // holds it, or the entry path when the source is a string. Same base
1225
+ // the file leg computes (resolvePathSpec in @tabnas/multisource).
1226
+ const dirOf = (p) => null == p || '' === p ? (0, node_path_1.resolve)('.') : (0, node_path_1.dirname)((0, node_path_1.resolve)(p));
1227
+ // The module store reader: the host's filesystem when one was
1228
+ // injected, so a sandboxed evaluation stays in the filesystem the
1229
+ // host gave it.
1230
+ const modFs = (ctx) => {
1231
+ const hostfs = ctx?.meta?.fs;
1232
+ return null == hostfs ? { existsSync: node_fs_1.existsSync, readFileSync: node_fs_1.readFileSync } : {
1233
+ existsSync: (p) => {
1234
+ try {
1235
+ hostfs.statSync(p);
1236
+ return true;
1237
+ }
1238
+ catch {
1239
+ return false;
1240
+ }
1241
+ },
1242
+ readFileSync: (p, enc) => hostfs.readFileSync(p, enc),
1243
+ };
1244
+ };
1245
+ // The manifest sink rides the parse meta (Lang.parse seeds it, the
1246
+ // multisource plugin's child-meta spread carries it to every nested
1247
+ // include), so the recorded closure covers the whole include tree.
1248
+ const record = (ctx, path, cap) => {
1249
+ const manifest = ctx?.meta?.aontu?.manifest;
1250
+ if (Array.isArray(manifest)) {
1251
+ manifest.push({ path, capability: cap });
1252
+ }
1253
+ };
798
1254
  return function ModelResolver(spec, popts, rule, ctx, jsonic) {
799
1255
  // The aontu val rule's ac has already wrapped every raw string node
800
1256
  // as a StringVal, so spec is a Val here (or a raw object from a
@@ -805,28 +1261,202 @@ function makeModelResolver(options) {
805
1261
  if (null == path || '' === path) {
806
1262
  return { found: false, path: '' + (path ?? ''), search: [] };
807
1263
  }
1264
+ if ('none' === capability) {
1265
+ deny(path);
1266
+ }
1267
+ // THE BUNDLED VOCABULARY (G4 phase 4, ts/src/std.ts): served from
1268
+ // the engine itself, so it needs neither the filesystem nor package
1269
+ // resolution and is available under every capability but `none` —
1270
+ // checked just above, that being what `none` means. Matched against
1271
+ // the name the author WROTE, before the memory leg, so the kind is
1272
+ // stated rather than guessed from an extension the bare name does
1273
+ // not have.
1274
+ const std = std_1.STD_SOURCES[path];
1275
+ if (null != std) {
1276
+ record(ctx, path, 'std');
1277
+ return { found: true, path, full: path, kind: 'aon', src: std, search: [] };
1278
+ }
808
1279
  let search = [];
809
1280
  let res = memResolver(path, popts, rule, ctx, jsonic);
810
1281
  res.path = path;
811
1282
  if (res.found) {
1283
+ record(ctx, res.full ?? path, 'mem');
1284
+ return res;
1285
+ }
1286
+ // THE MODULE LEG (G6 phase 2, ts/src/mod.ts): memory -> MODULE ->
1287
+ // filesystem -> package. Memory stays FIRST so a sandbox and the
1288
+ // spec suite can stub a module path without touching disk; a path
1289
+ // that is not module-shaped falls straight through, so no existing
1290
+ // include can be routed somewhere new by this.
1291
+ const modref = memCapability ? undefined : (0, mod_1.parseModuleRef)(path);
1292
+ if (null != modref) {
1293
+ const msmeta = ctx?.meta?.multisource;
1294
+ const from = dirOf(null != msmeta?.path ? msmeta.path : popts?.path);
1295
+ const found = (0, mod_1.resolveModule)(modref, from, modFs(ctx), {
1296
+ // The user cache lives outside any confinement root, so it is
1297
+ // consulted only when nothing confines this evaluation. A
1298
+ // rooted profile sees the project's own `aon_vendor/` and
1299
+ // nothing else, which is what `root` means.
1300
+ ...(null == rootDir ? { cache: modCache(options) } : {}),
1301
+ eval: options.mod?.eval,
1302
+ depth: options.mod?.depth,
1303
+ });
1304
+ if (null != rootDir && outsideRoot(rootDir, found.full)) {
1305
+ deny(path);
1306
+ }
1307
+ record(ctx, found.full, 'mod');
1308
+ return {
1309
+ found: true, path, full: found.full,
1310
+ kind: 'aon', src: found.src, search: [],
1311
+ };
1312
+ }
1313
+ if (memCapability) {
1314
+ // A miss in the declared virtual set is NOT-FOUND, not denial:
1315
+ // the allowed mechanism ran and missed. Denial is reserved for a
1316
+ // capability refusing a mechanism outright.
1317
+ res.search = search.concat(res.search);
812
1318
  return res;
813
1319
  }
814
1320
  search = search.concat(res.search);
815
1321
  res = fileResolver(path, popts, rule, ctx, jsonic);
816
1322
  res.path = path;
817
1323
  if (res.found) {
1324
+ // `res.full` asserted non-null: a FOUND file resolution always
1325
+ // carries the absolute path it read (and the pkg leg below is the
1326
+ // same), so a runtime fallback arm would be dead code.
1327
+ const full = res.full;
1328
+ if (null != rootDir && outsideRoot(rootDir, full)) {
1329
+ deny(path);
1330
+ }
1331
+ // The warning window for the staged default flip (G5 phase 6):
1332
+ // under 'system', the CLI supplies trustWarn and the entry root,
1333
+ // and every resolution escaping that root names the flag a future
1334
+ // default will require.
1335
+ if (null == rootDir && null != options.trustWarn &&
1336
+ null != options.trustWarnRoot &&
1337
+ outsideRoot(options.trustWarnRoot, full)) {
1338
+ options.trustWarn('escape', full);
1339
+ }
1340
+ record(ctx, full, 'file');
818
1341
  return res;
819
1342
  }
820
1343
  search = search.concat(res.search);
1344
+ if (null != rootDir) {
1345
+ // Package resolution is not part of the root capability; the
1346
+ // miss stands as not-found with the searched paths listed.
1347
+ res.search = search;
1348
+ return res;
1349
+ }
821
1350
  res = pkgResolver(path, popts, rule, ctx, jsonic);
822
1351
  res.path = path;
823
1352
  if (res.found) {
1353
+ if (null != options.trustWarn) {
1354
+ options.trustWarn('pkg', res.full);
1355
+ }
1356
+ record(ctx, res.full, 'pkg');
824
1357
  return res;
825
1358
  }
826
1359
  res.search = search.concat(res.search);
827
1360
  return res;
828
1361
  };
829
1362
  }
1363
+ // funcArity is the permitted WRITTEN argument count of each built-in, as
1364
+ // The functions whose comma-separated arguments are distinct POSITIONS
1365
+ // rather than one argument list. See the func-paren handler above: this
1366
+ // is the set whose comma group is expanded back into separate `peg`
1367
+ // entries.
1368
+ const POSITIONAL_ARG_FUNCS = {
1369
+ deprecate: true, pack: true, each: true, filter: true, match: true,
1370
+ // Arithmetic takes two OPERANDS, and an operand is a position: `sub`
1371
+ // is not commutative, so `sub(a, b)` reaching the engine as one
1372
+ // two-element list would lose which is which.
1373
+ add: true, sub: true, mul: true, div: true, mod: true, rem: true,
1374
+ // The bag and the key are distinct positions.
1375
+ pick: true,
1376
+ };
1377
+ // [min, max]; a max of -1 is unbounded. Every name in funcMap has an
1378
+ // entry, and the arity is a property of the language rather than of
1379
+ // either port -- go/func.go carries the same table.
1380
+ //
1381
+ // Nearly everything takes exactly one. The exceptions earn their place:
1382
+ // key() names how many levels UP the path to read, defaulting to the
1383
+ // parent when omitted, neq takes a whole set of exclusions, unique()
1384
+ // takes none (a property of the container) or a PROJECTOR key, must()
1385
+ // takes a check AND the author's message for when it fails, and the
1386
+ // arithmetic family takes two operands.
1387
+ const funcArity = {
1388
+ upper: [1, 1], lower: [1, 1], copy: [1, 1], pref: [1, 1],
1389
+ super: [1, 1], type: [1, 1], hide: [1, 1], close: [1, 1],
1390
+ open: [1, 1], move: [1, 1], path: [1, 1],
1391
+ min: [1, 1], max: [1, 1], above: [1, 1], below: [1, 1], re: [1, 1],
1392
+ length: [1, 1],
1393
+ key: [0, 1],
1394
+ // THE ONE ARGUMENT IS A PROJECTOR: `unique(port)` says no two
1395
+ // members share a `port`. The arity was reserved for it (finding I).
1396
+ unique: [0, 1],
1397
+ neq: [1, -1],
1398
+ must: [2, 2],
1399
+ deprecate: [1, 2],
1400
+ id: [1, 1],
1401
+ refer: [0, 1],
1402
+ pack: [2, 2],
1403
+ each: [1, 2],
1404
+ filter: [2, 2],
1405
+ // The scrutinee, then pattern/result pairs, then an optional
1406
+ // default: three arguments at least, and any number above that.
1407
+ match: [3, -1],
1408
+ // Two operands, always. Arithmetic has no variadic reading that is
1409
+ // not a fold, and a fold is the recursion this language refuses.
1410
+ add: [2, 2], sub: [2, 2], mul: [2, 2],
1411
+ div: [2, 2], mod: [2, 2], rem: [2, 2],
1412
+ // One bag. The operation is fixed, so there is nothing else to pass:
1413
+ // a fold's second argument is a FUNCTION, and this language has none
1414
+ // to give it.
1415
+ sum: [1, 1], least: [1, 1], greatest: [1, 1],
1416
+ // The bag, and the key to take from each of its children.
1417
+ pick: [2, 2],
1418
+ };
1419
+ // writtenArgCount counts the arguments as the AUTHOR wrote them.
1420
+ //
1421
+ // It cannot simply be terms.length: a comma group reaches the func-paren
1422
+ // handler as ONE term holding a raw array, so `upper("a","b")` and
1423
+ // `upper(["a","b"])` both arrive as a single argument. They are still
1424
+ // distinguishable, and that is what makes an arity check possible at
1425
+ // all -- the comma group is a RAW array, while a written list literal
1426
+ // has already been built into a ListVal by the list rule.
1427
+ function writtenArgCount(terms) {
1428
+ if (1 === terms.length) {
1429
+ // `terms[0]` is re-read rather than reusing a narrowed local:
1430
+ // Array.isArray narrows an `any` to `any[]`, which then has no
1431
+ // `isVal` to test.
1432
+ const t = terms[0];
1433
+ if (Array.isArray(t) && true !== terms[0].isVal) {
1434
+ return t.length;
1435
+ }
1436
+ }
1437
+ return terms.length;
1438
+ }
1439
+ // arityText renders a built-in's permitted count for the error message.
1440
+ // The fixed-arity case says "one" outright rather than counting: every
1441
+ // fixed arity in the table IS one, and a phrasing for a count no entry
1442
+ // carries would be untested prose pretending to be tested.
1443
+ function arityText(lo, hi) {
1444
+ if (-1 === hi) {
1445
+ return 'one or more arguments';
1446
+ }
1447
+ if (lo !== hi) {
1448
+ return 0 === lo ? 'no arguments or one' : 'one argument or two';
1449
+ }
1450
+ // NO {0,0} ARM. `unique` was the only built-in taking none, and its
1451
+ // one argument is now the projector the arity was reserved for
1452
+ // (finding I), so a phrasing for a count no entry carries would be
1453
+ // untested prose pretending to be tested -- the rule this function's
1454
+ // header states. The arm returns with the table, if one ever does.
1455
+ if (2 === hi) {
1456
+ return 'exactly two arguments';
1457
+ }
1458
+ return 'exactly one argument';
1459
+ }
830
1460
  // rawToVal converts a raw parse node (or raw elements inside one) into
831
1461
  // the matching Val. Used for implicit top-level lists, whose nodes skip
832
1462
  // the aontu val rule conversions (mirrors asVal in go/lang.go; like
@@ -883,12 +1513,6 @@ function rawToVal(n) {
883
1513
  return new BooleanVal_1.BooleanVal({ peg: n });
884
1514
  }
885
1515
  if ('object' === t) {
886
- // An expr-plugin internal (operator descriptor) leaking through a
887
- // degenerate parse (`k2.b K:1`) is not data — reject it rather
888
- // than emitting raw internals in generated output.
889
- if (undefined !== n.OP_MARK) {
890
- return new NilVal_1.NilVal({ why: 'parse_unknown' });
891
- }
892
1516
  const peg = {};
893
1517
  for (const k in n) {
894
1518
  peg[k] = rawToVal(n[k]);
@@ -932,7 +1556,13 @@ class Lang {
932
1556
  multisource: {
933
1557
  path: opts?.path ?? this.opts.path,
934
1558
  deps: (opts && opts.deps) || undefined
935
- }
1559
+ },
1560
+ // The include-manifest sink (G5, docs/trust.md): the resolver
1561
+ // records every resolved include here, and the plugin's
1562
+ // child-meta spread carries the same array to nested includes.
1563
+ aontu: {
1564
+ manifest: opts?.manifest,
1565
+ },
936
1566
  };
937
1567
  if (null != opts?.idcount) {
938
1568
  this.idcount = opts.idcount;
@@ -954,16 +1584,49 @@ class Lang {
954
1584
  }
955
1585
  }
956
1586
  catch (e) {
957
- if (e instanceof jsonic_1.JsonicError || 'JsonicError' === e.constructor.name) {
1587
+ if ('include_denied' === e?.code ||
1588
+ 'module_missing' === e?.code || 'module_integrity' === e?.code ||
1589
+ 'module_depth' === e?.code) {
1590
+ // A denied include (trust profile, G5): the resolver throws so
1591
+ // a bare-member include cannot vanish in the merge, and the
1592
+ // code survives here as the parse-stage nil the registry
1593
+ // pins (errcodes.tsv: include_denied, class parse).
1594
+ // A denied include (G5) and a module that is missing or fails
1595
+ // its pin (G6 phase 2) are refused the same way, for the same
1596
+ // reason: the resolver THROWS so a bare-member include cannot
1597
+ // vanish in the merge, and the code survives here as the
1598
+ // parse-stage nil the registry pins (errcodes.tsv).
958
1599
  val = new NilVal_1.NilVal({
959
1600
  why: 'parse',
960
1601
  err: new NilVal_1.NilVal({
961
- why: 'syntax',
962
- msg: e.message + opCharHint(src),
1602
+ why: e.code,
1603
+ msg: e.message,
963
1604
  err: e,
964
1605
  })
965
1606
  });
966
1607
  }
1608
+ else if (e instanceof jsonic_1.JsonicError || 'JsonicError' === e.constructor.name) {
1609
+ const syntax = new NilVal_1.NilVal({
1610
+ why: 'syntax',
1611
+ msg: e.message + opCharHint(src),
1612
+ err: e,
1613
+ });
1614
+ // THE POSITION TRAVELS WITH IT. The parser knows exactly where
1615
+ // it stopped -- it draws a caret there -- and the rendered
1616
+ // message carried the only copy, so `vet --format json`
1617
+ // reported row -1, col -1 for a document whose fault the human
1618
+ // renderer located to the character. A machine-readable report
1619
+ // that says "somewhere in this file" is the one a repair loop
1620
+ // can do nothing with. Both numbers are already 1-based here,
1621
+ // which is the base a site uses (go/lang.go does the same).
1622
+ if ('number' === typeof e.lineNumber) {
1623
+ syntax.site.row = e.lineNumber;
1624
+ }
1625
+ if ('number' === typeof e.columnNumber) {
1626
+ syntax.site.col = e.columnNumber;
1627
+ }
1628
+ val = new NilVal_1.NilVal({ why: 'parse', err: syntax });
1629
+ }
967
1630
  else {
968
1631
  throw e;
969
1632
  }