aontu 0.52.1 → 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 (272) 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 +96 -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 +13 -0
  13. package/dist/ctx.js +43 -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 +38 -7
  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 +167 -4
  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 +512 -69
  33. package/dist/lang.js.map +1 -1
  34. package/dist/lsp.d.ts +9 -2
  35. package/dist/lsp.js +262 -47
  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 +2 -1
  83. package/dist/unify.js +253 -36
  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 +7 -1
  103. package/dist/val/ConstraintVal.js +490 -40
  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.js +27 -4
  120. package/dist/val/ExpectVal.js.map +1 -1
  121. package/dist/val/FeatureVal.js +1 -1
  122. package/dist/val/FeatureVal.js.map +1 -1
  123. package/dist/val/FilterFuncVal.d.ts +15 -0
  124. package/dist/val/FilterFuncVal.js +91 -0
  125. package/dist/val/FilterFuncVal.js.map +1 -0
  126. package/dist/val/FuncBaseVal.d.ts +6 -1
  127. package/dist/val/FuncBaseVal.js +171 -1
  128. package/dist/val/FuncBaseVal.js.map +1 -1
  129. package/dist/val/HideFuncVal.js.map +1 -1
  130. package/dist/val/IdFuncVal.d.ts +13 -0
  131. package/dist/val/IdFuncVal.js +54 -0
  132. package/dist/val/IdFuncVal.js.map +1 -0
  133. package/dist/val/JunctionVal.js +7 -1
  134. package/dist/val/JunctionVal.js.map +1 -1
  135. package/dist/val/KeyFuncVal.d.ts +1 -1
  136. package/dist/val/KeyFuncVal.js +38 -30
  137. package/dist/val/KeyFuncVal.js.map +1 -1
  138. package/dist/val/ListVal.js +101 -14
  139. package/dist/val/ListVal.js.map +1 -1
  140. package/dist/val/LowerFuncVal.js.map +1 -1
  141. package/dist/val/MapVal.js +93 -8
  142. package/dist/val/MapVal.js.map +1 -1
  143. package/dist/val/MatchFuncVal.d.ts +15 -0
  144. package/dist/val/MatchFuncVal.js +107 -0
  145. package/dist/val/MatchFuncVal.js.map +1 -0
  146. package/dist/val/MoveFuncVal.js.map +1 -1
  147. package/dist/val/NilVal.js +24 -0
  148. package/dist/val/NilVal.js.map +1 -1
  149. package/dist/val/OpBaseVal.d.ts +1 -1
  150. package/dist/val/OpBaseVal.js +19 -1
  151. package/dist/val/OpBaseVal.js.map +1 -1
  152. package/dist/val/OpenFuncVal.js +4 -1
  153. package/dist/val/OpenFuncVal.js.map +1 -1
  154. package/dist/val/PackFuncVal.d.ts +15 -0
  155. package/dist/val/PackFuncVal.js +108 -0
  156. package/dist/val/PackFuncVal.js.map +1 -0
  157. package/dist/val/PathFuncVal.js.map +1 -1
  158. package/dist/val/PlaceVal.d.ts +13 -0
  159. package/dist/val/PlaceVal.js +131 -0
  160. package/dist/val/PlaceVal.js.map +1 -0
  161. package/dist/val/PlusOpVal.js +11 -2
  162. package/dist/val/PlusOpVal.js.map +1 -1
  163. package/dist/val/PrefFuncVal.js.map +1 -1
  164. package/dist/val/PrefVal.d.ts +2 -2
  165. package/dist/val/PrefVal.js +78 -23
  166. package/dist/val/PrefVal.js.map +1 -1
  167. package/dist/val/RefVal.d.ts +1 -1
  168. package/dist/val/RefVal.js +89 -7
  169. package/dist/val/RefVal.js.map +1 -1
  170. package/dist/val/ReferFuncVal.d.ts +36 -0
  171. package/dist/val/ReferFuncVal.js +303 -0
  172. package/dist/val/ReferFuncVal.js.map +1 -0
  173. package/dist/val/ScalarKindVal.d.ts +1 -2
  174. package/dist/val/ScalarKindVal.js +0 -11
  175. package/dist/val/ScalarKindVal.js.map +1 -1
  176. package/dist/val/TopVal.js.map +1 -1
  177. package/dist/val/TypeFuncVal.js.map +1 -1
  178. package/dist/val/UpperFuncVal.js.map +1 -1
  179. package/dist/val/Val.d.ts +8 -1
  180. package/dist/val/Val.js +150 -1
  181. package/dist/val/Val.js.map +1 -1
  182. package/dist/val/VarVal.js.map +1 -1
  183. package/dist/val/arith.d.ts +6 -0
  184. package/dist/val/arith.js +170 -0
  185. package/dist/val/arith.js.map +1 -0
  186. package/dist/vet.d.ts +45 -0
  187. package/dist/vet.js +776 -0
  188. package/dist/vet.js.map +1 -0
  189. package/dist/walk.d.ts +2 -0
  190. package/dist/walk.js +91 -0
  191. package/dist/walk.js.map +1 -0
  192. package/grammar/aontu.gbnf +130 -0
  193. package/grammar/aontu.lark +113 -0
  194. package/package.json +21 -6
  195. package/skill/SKILL.md +37 -0
  196. package/skill/error-codes.md +62 -0
  197. package/skill/examples.md +99 -0
  198. package/skill/grammar-card.md +57 -0
  199. package/src/agentsmd.ts +135 -0
  200. package/src/aontu.ts +135 -4
  201. package/src/cli.ts +2858 -71
  202. package/src/ctx.ts +73 -1
  203. package/src/diff.ts +196 -0
  204. package/src/err.ts +42 -7
  205. package/src/graph.ts +135 -0
  206. package/src/hcanon.ts +169 -0
  207. package/src/hints.ts +208 -4
  208. package/src/jsonschema.ts +511 -0
  209. package/src/lang.ts +570 -70
  210. package/src/lsp.ts +281 -48
  211. package/src/mcp-server.ts +187 -0
  212. package/src/mcp.ts +993 -0
  213. package/src/mod-tool.ts +679 -0
  214. package/src/mod.ts +344 -0
  215. package/src/patch.ts +624 -0
  216. package/src/provenance.ts +430 -0
  217. package/src/query.ts +379 -0
  218. package/src/reach.ts +184 -0
  219. package/src/relation.ts +395 -0
  220. package/src/report-sarif.ts +137 -0
  221. package/src/site.ts +36 -1
  222. package/src/std.ts +73 -0
  223. package/src/subsume.ts +690 -0
  224. package/src/trim.ts +195 -0
  225. package/src/tsconfig.json +10 -4
  226. package/src/type.ts +51 -2
  227. package/src/unify.ts +274 -36
  228. package/src/utility.ts +139 -1
  229. package/src/val/AggFuncVal.ts +319 -0
  230. package/src/val/ArithFuncVal.ts +108 -0
  231. package/src/val/BagVal.ts +101 -4
  232. package/src/val/CloseFuncVal.ts +9 -1
  233. package/src/val/ConjunctVal.ts +20 -0
  234. package/src/val/ConstraintVal.ts +542 -43
  235. package/src/val/CopyFuncVal.ts +7 -1
  236. package/src/val/Decimal.ts +15 -0
  237. package/src/val/DeprecateFuncVal.ts +84 -0
  238. package/src/val/DisjunctVal.ts +139 -28
  239. package/src/val/EachFuncVal.ts +133 -0
  240. package/src/val/ExpectVal.ts +28 -6
  241. package/src/val/FeatureVal.ts +1 -1
  242. package/src/val/FilterFuncVal.ts +154 -0
  243. package/src/val/FuncBaseVal.ts +192 -1
  244. package/src/val/HideFuncVal.ts +0 -2
  245. package/src/val/IdFuncVal.ts +91 -0
  246. package/src/val/JunctionVal.ts +7 -1
  247. package/src/val/KeyFuncVal.ts +39 -35
  248. package/src/val/ListVal.ts +108 -15
  249. package/src/val/LowerFuncVal.ts +0 -1
  250. package/src/val/MapVal.ts +99 -8
  251. package/src/val/MatchFuncVal.ts +176 -0
  252. package/src/val/MoveFuncVal.ts +0 -2
  253. package/src/val/NilVal.ts +25 -0
  254. package/src/val/OpBaseVal.ts +20 -1
  255. package/src/val/OpenFuncVal.ts +4 -2
  256. package/src/val/PackFuncVal.ts +175 -0
  257. package/src/val/PathFuncVal.ts +0 -1
  258. package/src/val/PlaceVal.ts +193 -0
  259. package/src/val/PlusOpVal.ts +11 -2
  260. package/src/val/PrefFuncVal.ts +0 -1
  261. package/src/val/PrefVal.ts +79 -36
  262. package/src/val/RefVal.ts +91 -8
  263. package/src/val/ReferFuncVal.ts +387 -0
  264. package/src/val/ScalarKindVal.ts +0 -13
  265. package/src/val/TopVal.ts +0 -1
  266. package/src/val/TypeFuncVal.ts +0 -2
  267. package/src/val/UpperFuncVal.ts +0 -1
  268. package/src/val/Val.ts +205 -2
  269. package/src/val/VarVal.ts +0 -1
  270. package/src/val/arith.ts +316 -0
  271. package/src/vet.ts +992 -0
  272. package/src/walk.ts +99 -0
package/src/lang.ts CHANGED
@@ -3,6 +3,19 @@
3
3
 
4
4
  // import { performance } from 'node:perf_hooks'
5
5
 
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
+ import { existsSync, readFileSync, realpathSync } from 'node:fs'
11
+ import {
12
+ basename as pathBasename,
13
+ dirname as pathDirname,
14
+ join as pathJoin,
15
+ resolve as pathResolve,
16
+ sep as pathSep,
17
+ } from 'node:path'
18
+
6
19
  import {
7
20
  Jsonic,
8
21
  Tabnas,
@@ -34,6 +47,9 @@ import {
34
47
  makeMemResolver
35
48
  } from '@tabnas/multisource/resolver/mem'
36
49
 
50
+ import { STD_SOURCES } from './std'
51
+ import { parseModuleRef, resolveModule, modCacheDir } from './mod'
52
+
37
53
  import {
38
54
  Expr,
39
55
  Op,
@@ -96,6 +112,20 @@ import { CopyFuncVal } from './val/CopyFuncVal'
96
112
  import { KeyFuncVal } from './val/KeyFuncVal'
97
113
  import { TypeFuncVal } from './val/TypeFuncVal'
98
114
  import { HideFuncVal } from './val/HideFuncVal'
115
+ import { DeprecateFuncVal } from './val/DeprecateFuncVal'
116
+ import { IdFuncVal } from './val/IdFuncVal'
117
+ import { ReferFuncVal } from './val/ReferFuncVal'
118
+ import { PackFuncVal } from './val/PackFuncVal'
119
+ import { EachFuncVal } from './val/EachFuncVal'
120
+ import { FilterFuncVal } from './val/FilterFuncVal'
121
+ import { MatchFuncVal } from './val/MatchFuncVal'
122
+ import {
123
+ AddFuncVal, SubFuncVal, MulFuncVal, DivFuncVal, ModFuncVal, RemFuncVal,
124
+ } from './val/ArithFuncVal'
125
+ import {
126
+ SumFuncVal, LeastFuncVal, GreatestFuncVal, PickFuncVal,
127
+ } from './val/AggFuncVal'
128
+ import { PlaceVal } from './val/PlaceVal'
99
129
  import { MoveFuncVal } from './val/MoveFuncVal'
100
130
  import { PathFuncVal } from './val/PathFuncVal'
101
131
  import { PrefFuncVal } from './val/PrefFuncVal'
@@ -287,6 +317,16 @@ let AontuJsonic: Plugin = function AontuLang(jsonic: Jsonic) {
287
317
  v.site.row = null == r.o0 ? -1 : r.o0.rI
288
318
  v.site.col = null == r.o0 ? -1 : r.o0.cI
289
319
  v.site.url = ctx.meta.multisource ? ctx.meta.multisource.path : ''
320
+ // The source text, from the SAME token the row and column above
321
+ // come from. jsonic has carried it all along; not reading it is
322
+ // what left a site uneditable (ts/src/site.ts).
323
+ //
324
+ // A TOKEN WITH NO TEXT IS NO SPAN, in both ports, and the extent is
325
+ // DERIVED from the text rather than read from the token's own `len`
326
+ // — so the Go twin, whose token has no len field, computes the
327
+ // identical number with utf16Len and nothing has to be kept in step.
328
+ v.site.src = null == r.o0 ? '' : r.o0.src
329
+ v.site.len = '' === v.site.src ? -1 : v.site.src.length
290
330
  // A keyed rule always carries a path array; a keyless one has none.
291
331
  v.path = r.k ? [...r.k.path] : []
292
332
 
@@ -365,6 +405,14 @@ help isolate the syntax error.`,
365
405
 
366
406
  // TODO: FIX: need a TOP instance to hold path
367
407
  'top': { val: () => top() },
408
+
409
+ // G8 phase 3: the placeholder. A BARE `_` is the hole; `"_"`
410
+ // quoted, and any longer bare word containing it, stay text.
411
+ // Reserving it is a breaking change, pinned by place.tsv.
412
+ '_': {
413
+ val: (r: Rule, ctx: JsonicContext) =>
414
+ addsite(new PlaceVal({}), r, ctx)
415
+ },
368
416
  }
369
417
  },
370
418
 
@@ -441,6 +489,65 @@ help isolate the syntax error.`,
441
489
  // reported with the author's own message, never simplified and
442
490
  // never consulted for emptiness or subsumption.
443
491
  must: MustConstraintVal,
492
+
493
+ // G3 phase 4: the deprecation mark. Unification-transparent; the
494
+ // record rides the result (Val.deprecation) and canon renders the
495
+ // call back (canonRiders).
496
+ deprecate: DeprecateFuncVal,
497
+
498
+ // G4 phase 1: the identity mark. Written as a conjunct
499
+ // (`id(svc/auth) & {…}`), it resolves to the unit carrying the
500
+ // name, and every node in one evaluation with that name is
501
+ // unified with every other.
502
+ id: IdFuncVal,
503
+
504
+ // G4 phase 2: the checked, typed, LINK-shaped reference. A
505
+ // constraint on a string field: the string must be an entity
506
+ // address, the address must resolve, and the optional argument
507
+ // flows INTO the target. The field keeps the string.
508
+ refer: ReferFuncVal,
509
+
510
+ // G8 phase 1: the generation combinators. `pack` makes one keyed
511
+ // child per child of its data, `each` one list element; both clone
512
+ // their template per destination exactly as a spread does, and both
513
+ // wait for the model to settle before they fire (the staging rule,
514
+ // G8 phase 0).
515
+ pack: PackFuncVal,
516
+ each: EachFuncVal,
517
+
518
+ // G8 phase 2: selection. `filter` keeps the children of a bag that
519
+ // unify with a condition; `match` picks the first arm whose
520
+ // pattern the scrutinee unifies with. Both select by
521
+ // UNIFIABILITY, tried in trial mode, so neither adds a predicate
522
+ // language to the one the lattice already is.
523
+ filter: FilterFuncVal,
524
+ match: MatchFuncVal,
525
+
526
+ // The arithmetic family (the review's finding I). Maths beyond `+`
527
+ // arrives as FUNCTIONS — `-` `*` `/` `%` stay reserved — and the
528
+ // family is numeric where the operator is polymorphic, which is
529
+ // what makes `add` more than a second spelling of `+`: it refuses
530
+ // the string concatenation that silently answers `"500m" + "500m"`.
531
+ // Every rule they obey is in ts/src/val/arith.ts.
532
+ add: AddFuncVal,
533
+ sub: SubFuncVal,
534
+ mul: MulFuncVal,
535
+ div: DivFuncVal,
536
+ mod: ModFuncVal,
537
+ rem: RemFuncVal,
538
+
539
+ // Aggregation over a finite, settled bag (the review's finding I).
540
+ // `least` and `greatest` rather than min and max, which are already
541
+ // the atoms for a lower and an upper BOUND -- an aggregate over a
542
+ // set and a bound on a value must not share a spelling.
543
+ sum: SumFuncVal,
544
+ least: LeastFuncVal,
545
+ greatest: GreatestFuncVal,
546
+
547
+ // Projection, which is what lets the aggregates reach a bag of
548
+ // RECORDS: `sum(pick($.lines, amountCents))`. Not a clever `each`
549
+ // template -- `each` MEETS each child, and a meet cannot select.
550
+ pick: PickFuncVal,
444
551
  }
445
552
 
446
553
 
@@ -454,6 +561,117 @@ help isolate the syntax error.`,
454
561
  const incompleteNil = (r: Rule, ctx: JsonicContext) =>
455
562
  addsite(new NilVal({ why: 'incomplete_expression' }), r, ctx)
456
563
 
564
+ // Build a call from a NAME and the argument terms as the author
565
+ // wrote them. Shared by the `func(...)` handler and by the pipe,
566
+ // which is the same call with one more argument on the front — so
567
+ // the arity check, the comma-group rule and the raw-value conversion
568
+ // are stated once and both spellings get all three.
569
+ const buildCall = (r: Rule, ctx: JsonicContext,
570
+ fname: string, argterms: any[]): any => {
571
+ const funcval = funcMap[fname]
572
+
573
+ // Arity is known for every built-in, so a surplus or missing
574
+ // argument is a mistake in the SOURCE, refused here where the
575
+ // author can see it (issue #51). It was previously left to each
576
+ // function to notice or not: the two ports disagreed on `upper()`
577
+ // and on `close()`, and `min(1,2)` noticed nothing at all -- it
578
+ // built a constraint that merely refused to generate later, with a
579
+ // message about the map rather than about the call.
580
+ //
581
+ // Counted BEFORE the rawToVal pass below, which is what makes the
582
+ // count possible: a comma group arrives as a RAW array and a
583
+ // written list literal as a ListVal, and rawToVal turns the first
584
+ // into the second.
585
+ const arity = funcArity[fname]
586
+ if (null != arity) {
587
+ const got = writtenArgCount(argterms)
588
+ if (got < arity[0] || (-1 !== arity[1] && got > arity[1])) {
589
+ // details is assigned AFTER construction: the NilVal
590
+ // constructor does not read it from its spec (only NilVal.make
591
+ // does), so passing it in the spec left the hint's
592
+ // {func}/{want}/{got} placeholders un-injected and printed
593
+ // literally.
594
+ const nil: any = new NilVal({ why: 'func_arity' })
595
+ nil.details = {
596
+ func: fname,
597
+ want: arityText(arity[0], arity[1]),
598
+ got: '' + got,
599
+ }
600
+ // The CALL AS WRITTEN rides the refusal (G8 phase 4): a pipe
601
+ // rebuilds `x |> upper()` as `upper(x)`, and the arity it fails
602
+ // on here is the arity of a call one argument short of the one
603
+ // the author actually wrote.
604
+ nil._callname = fname
605
+ nil._callterms = argterms
606
+ return addsite(nil, r, ctx)
607
+ }
608
+ }
609
+
610
+ // rawToVal EVERY argument. A degenerate expression can hand this
611
+ // handler raw parse values rather than Vals -- `pref(1-3)` arrives
612
+ // as the plain numbers 1 and -3 -- and a func's peg is unified
613
+ // element by element, so a raw one reached `arg.unify(...)` and
614
+ // threw. The unifier's catch-all turned that into an `internal`
615
+ // verdict: a crash reported as a unification result (issue #49).
616
+ // The Go port has always converted here (asVal in evaluate).
617
+ // A comma group is ONE raw-array term (see writtenArgCount).
618
+ // For a function whose arguments are distinct POSITIONS —
619
+ // deprecate's value and record, pack's and each's data and template
620
+ // — the group is expanded back into them here, while a written list
621
+ // literal, already a ListVal, stays one argument. The constraint
622
+ // atoms make the same move in their own constructor (atomArgs,
623
+ // ConstraintVal.ts), which is why they are not in this set:
624
+ // `neq(1,2)` is one argument LIST, not two positions, and expanding
625
+ // it here would take the list away from the code that reads it.
626
+ let terms = argterms
627
+ if (true === POSITIONAL_ARG_FUNCS[fname] && 1 === terms.length &&
628
+ Array.isArray(terms[0])) {
629
+ terms = terms[0]
630
+ }
631
+ const args = terms.map(rawToVal)
632
+ const val: any = null == funcval ?
633
+ new NilVal({ why: 'unknown_function' }) :
634
+ new funcval({ peg: args })
635
+
636
+ // The call as written, for the pipe to rebuild from. Parse-time
637
+ // only: nothing downstream reads it, and a clone does not carry it.
638
+ //
639
+ // NOT on a constraint atom that BUILT: an atom with its argument
640
+ // list complete is a residual, not a call waiting for a subject,
641
+ // and `1 |> neq(2,3)` is asking for `1 & neq(2,3)` -- which is
642
+ // what `&` is for. (An atom the arity check REFUSED still carries
643
+ // it, on the nil above: `1 |> min()` is `min(1)`, and that is a
644
+ // call waiting for a subject.) The Go port cannot rebuild a built
645
+ // atom at all -- its residual keeps no atom name -- so this is
646
+ // also what keeps the two ports answering the same thing.
647
+ if (true !== val.isConstraint) {
648
+ val._callname = fname
649
+ val._callterms = argterms
650
+ }
651
+
652
+ return val
653
+ }
654
+
655
+
656
+ // The argument terms `f(...)` would have been written with, had the
657
+ // piped value been written into it. A comma group is one raw-array
658
+ // term: for a POSITIONAL function the group is separate arguments,
659
+ // so the piped value joins them; for a constraint atom the group IS
660
+ // the argument list, so the piped value joins the list instead.
661
+ const pipeTerms = (call: any, val: any): any[] => {
662
+ const written: any[] = call._callterms
663
+ const group = 1 === written.length && Array.isArray(written[0]) ?
664
+ written[0] : written
665
+
666
+ if (0 === group.length) {
667
+ return [val]
668
+ }
669
+
670
+ return true === POSITIONAL_ARG_FUNCS[call._callname] ?
671
+ [val, ...group] : [[val, ...group]]
672
+ }
673
+
674
+
457
675
  let opmap: any = {
458
676
  'conjunct-infix': (r: Rule, ctx: JsonicContext, _op: Op, terms: any) =>
459
677
  addsite(new ConjunctVal({ peg: dropUnfilled(terms) }), r, ctx),
@@ -533,55 +751,47 @@ help isolate the syntax error.`,
533
751
  return addsite(val, r, ctx)
534
752
  },
535
753
 
754
+ // THE PIPE `|>` (G8 phase 4): parse-time sugar and nothing else.
755
+ // `x |> f(a)` IS `f(x, a)` -- the piped value goes in as the FIRST
756
+ // argument, Elixir-style, because every Aontu call is data-first
757
+ // already (`close(x)`, `pack(data, tmpl)`) and a pipe must read the
758
+ // way the calls it replaces read. It never reaches a Val: by the
759
+ // time the tree exists the call is an ordinary call, which is why
760
+ // canon can never emit the token and the two ports' canon stay
761
+ // byte-identical without either knowing about it.
762
+ 'pipe-infix': (r: Rule, ctx: JsonicContext, _op: Op, terms: any) => {
763
+ const val = terms[0]
764
+ const call: any = terms[1]
765
+
766
+ if (null == val || null == call) return incompleteNil(r, ctx)
767
+
768
+ // The right-hand side is a CALL: either one the func handler
769
+ // already built, or one it refused for an arity the pipe is about
770
+ // to satisfy. Both carry what they were written as.
771
+ if (null != call._callname) {
772
+ return buildCall(r, ctx, call._callname, pipeTerms(call, val))
773
+ }
774
+
775
+ // ... or a bare NAME, which is the whole point of the short
776
+ // spelling: `x |> upper` is `upper(x)`. A bare word has already
777
+ // become a string VALUE by the time an infix operator sees it, so
778
+ // this is where a string becomes a call.
779
+ if (true === call?.isScalar && 'string' === typeof call.peg &&
780
+ null != funcMap[call.peg]) {
781
+ return buildCall(r, ctx, call.peg, [val])
782
+ }
783
+
784
+ // Anything else is not a call, and a pipe into a non-call is a
785
+ // mistake in the source rather than a value.
786
+ return addsite(new NilVal({ why: 'pipe_target' }), r, ctx)
787
+ },
788
+
536
789
  'func-paren': (r: Rule, ctx: JsonicContext, _op: Op, terms: any) => {
537
790
  let val = terms[1]
538
791
  const fname = terms[0]
539
792
 
540
793
  if ('' !== fname) {
541
- const funcval = funcMap[fname]
542
- // Arity is known for every built-in, so a surplus or missing
543
- // argument is a mistake in the SOURCE, refused here where the
544
- // author can see it (issue #51). It was previously left to each
545
- // function to notice or not: the two ports disagreed on `upper()`
546
- // and on `close()`, and `min(1,2)` noticed nothing at all -- it
547
- // built a constraint that merely refused to generate later, with
548
- // a message about the map rather than about the call.
549
- //
550
- // Counted BEFORE the rawToVal pass below, which is what makes the
551
- // count possible: a comma group arrives as a RAW array and a
552
- // written list literal as a ListVal, and rawToVal turns the first
553
- // into the second.
554
- const arity = funcArity[fname]
555
- if (null != arity) {
556
- const got = writtenArgCount(terms.slice(1))
557
- if (got < arity[0] || (-1 !== arity[1] && got > arity[1])) {
558
- // details is assigned AFTER construction: the NilVal
559
- // constructor does not read it from its spec (only
560
- // NilVal.make does), so passing it in the spec left the
561
- // hint's {func}/{want}/{got} placeholders un-injected and
562
- // printed literally.
563
- const nil: any = new NilVal({ why: 'func_arity' })
564
- nil.details = {
565
- func: fname,
566
- want: arityText(arity[0], arity[1]),
567
- got: '' + got,
568
- }
569
- return addsite(nil, r, ctx)
570
- }
571
- }
572
- // rawToVal EVERY argument. A degenerate expression can hand this
573
- // handler raw parse values rather than Vals -- `pref(1-3)` arrives
574
- // as the plain numbers 1 and -3 -- and a func's peg is unified
575
- // element by element, so a raw one reached `arg.unify(...)` and
576
- // threw. The unifier's catch-all turned that into an `internal`
577
- // verdict: a crash reported as a unification result (issue #49).
578
- // The Go port has always converted here (asVal in evaluate).
579
- const args = terms.slice(1).map(rawToVal)
580
- val = null == funcval ?
581
- new NilVal({ why: 'unknown_function' }) :
582
- new funcval({
583
- peg: args
584
- })
794
+ val = buildCall(r, ctx, fname, terms.slice(1))
585
795
  }
586
796
  // `a:()` — grouping parens with nothing inside.
587
797
  if (null == val) return incompleteNil(r, ctx)
@@ -607,6 +817,14 @@ help isolate the syntax error.`,
607
817
  infix: true, src: '|', left: 14_000_000, right: 15_000_000
608
818
  },
609
819
 
820
+ // G8 phase 4: the pipe. LOOSEST of all the infix operators, so
821
+ // `a & b |> f` pipes the whole meet and not just `b` -- a pipe
822
+ // reads as "and then", which is a statement about everything to
823
+ // its left. Kept in lock-step with the op table in go/lang.go.
824
+ 'pipe-infix': {
825
+ infix: true, src: '|>', left: 12_000_000, right: 13_000_000
826
+ },
827
+
610
828
  'plus-infix': {
611
829
  src: '+',
612
830
  infix: true,
@@ -818,6 +1036,13 @@ help isolate the syntax error.`,
818
1036
  valnode.site.row = st.rI
819
1037
  valnode.site.col = st.cI
820
1038
  valnode.site.url = ctx.meta.multisource && ctx.meta.multisource.path
1039
+ // No `?? ''` and no empty-text arm here: this branch runs only
1040
+ // for a rule that HAS an open token, and a token that opens a
1041
+ // value always carries text — the coverage gate refuses both
1042
+ // guards as dead. The unset case is the one above, where r.o0
1043
+ // itself can be absent.
1044
+ valnode.site.src = st.src
1045
+ valnode.site.len = st.src.length
821
1046
  }
822
1047
  // else { ERROR? }
823
1048
 
@@ -1166,19 +1391,41 @@ help isolate the syntax error.`,
1166
1391
 
1167
1392
 
1168
1393
 
1169
- // SECURITY: the default resolver reads any file/package the process can
1170
- // reach — @"path" follows relative paths (`@"../../etc/passwd"`) and
1171
- // symlinks with no containment check, and @"pkg" can require() arbitrary
1172
- // installed modules. This is intentional for the CLI, but it means a
1173
- // `.aon` source can read referenced files; the LSP uses this same
1174
- // resolver, so treat opening an untrusted source as running it. Pass a
1175
- // confined `options.resolver` to restrict reads in less-trusted contexts.
1394
+ // SECURITY: under the DEFAULT ('system') include capability this
1395
+ // resolver reads any file/package the process can reach — @"path"
1396
+ // follows relative paths (`@"../../etc/passwd"`) and symlinks, and
1397
+ // @"pkg" can require() arbitrary installed modules — so treat opening
1398
+ // an untrusted source as running it. The trust profile (G5,
1399
+ // docs/trust.md) is the confinement surface: `trust.include` of
1400
+ // 'none', `{ mem }` or `{ root }` restricts what `@"..."` may resolve,
1401
+ // and a denied resolution is a deterministic parse-stage
1402
+ // `include_denied` error.
1176
1403
  function makeModelResolver(options: any) {
1177
1404
  const useRequire = options.require || require
1178
-
1179
- let memResolver = makeMemResolver({
1180
- ...(options.resolver?.mem || {})
1181
- })
1405
+ const capability = options.trust?.include ?? 'system'
1406
+
1407
+ const memCapability =
1408
+ 'object' === typeof capability && null != (capability as any).mem
1409
+ const rootDir: string | undefined =
1410
+ 'object' === typeof capability &&
1411
+ 'string' === typeof (capability as any).root
1412
+ ? pathResolve((capability as any).root) : undefined
1413
+
1414
+ // Under the mem capability the CAPABILITY's file set is the whole
1415
+ // world; otherwise the host-injected `options.resolver.mem` entries
1416
+ // remain available under every capability but 'none' — they are
1417
+ // host-provided, not document-requested, so confining them would
1418
+ // confine the host against itself.
1419
+ // THE BUNDLED VOCABULARY (G4 phase 4, ts/src/std.ts) rides the
1420
+ // memory leg: served from the engine itself, so it needs neither the
1421
+ // filesystem nor package resolution and is available under every
1422
+ // capability but `none` — which denies every include outright, that
1423
+ // being what `none` means. Host entries and the capability's own set
1424
+ // WIN over it: a caller that supplies its own `std/system` gets the
1425
+ // one it supplied.
1426
+ let memResolver = makeMemResolver(memCapability
1427
+ ? { ...(capability as any).mem }
1428
+ : { ...(options.resolver?.mem || {}) })
1182
1429
 
1183
1430
  // TODO: make this consistent with other resolvers
1184
1431
  let fileResolver = makeFileResolver((spec: any) => {
@@ -1190,6 +1437,100 @@ function makeModelResolver(options: any) {
1190
1437
  ...(options.resolver?.pkg || {})
1191
1438
  })
1192
1439
 
1440
+ // Confinement is realpath-then-prefix-check (docs/trust.md): the
1441
+ // RESOLVED file's real path must sit below the root's real path, so a
1442
+ // symlink inside the root pointing outside it is an escape, not a
1443
+ // loophole. A path realpath cannot resolve falls back to the lexical
1444
+ // form — the comparison is then against what the resolver actually
1445
+ // read.
1446
+ // Real fs, deliberately: `options.fs` is not a sandbox (it feeds
1447
+ // parse text; the file leg reads through its own channel), so the
1448
+ // containment check must see the same filesystem that leg read from.
1449
+ // A path that does not (fully) exist cannot be realpath'd whole, and
1450
+ // falling back to the LEXICAL form compares apples to oranges when
1451
+ // the root itself sits behind a symlink -- on macOS a root under
1452
+ // /var realpaths to /private/var, so a merely-missing file inside it
1453
+ // reads as an escape. Realpath the deepest EXISTING ancestor and
1454
+ // re-attach the rest, so both sides of the check are in real
1455
+ // coordinates. (The MCP server's own confinement carries the twin of
1456
+ // this rule; its CI failure is what found the shape.)
1457
+ const realpath = (p: string): string => {
1458
+ try {
1459
+ return realpathSync(p)
1460
+ }
1461
+ catch {
1462
+ const parent = pathDirname(p)
1463
+ if (parent === p) {
1464
+ return p
1465
+ }
1466
+ return pathJoin(realpath(parent), pathBasename(p))
1467
+ }
1468
+ }
1469
+
1470
+ const outsideRoot = (root: string, full: string): boolean => {
1471
+ const rootReal = realpath(root)
1472
+ const fullReal = realpath(full)
1473
+ return fullReal !== rootReal && !fullReal.startsWith(rootReal + pathSep)
1474
+ }
1475
+
1476
+ // A denial THROWS with the code; Lang.parse converts it to the
1477
+ // parse-stage `include_denied` nil (the same shape a syntax failure
1478
+ // takes). Raising beats injecting a nil value: a bare-member include
1479
+ // (`@"denied.aon"` at the top of a file) MERGES into the enclosing
1480
+ // map, and a nil contributes no keys, so an injected denial would
1481
+ // vanish and leave a plausible, silently-partial document.
1482
+ const deny = (path: string): never => {
1483
+ // Only 'none' and 'root' can deny: the mem capability's misses are
1484
+ // not-found (its set is the whole world), so there is no third arm.
1485
+ const capname = 'none' === capability ? 'none' : 'root:' + rootDir
1486
+ const err: any = new Error(
1487
+ 'include denied: ' + path + ' (capability: ' + capname + ')')
1488
+ err.code = 'include_denied'
1489
+ throw err
1490
+ }
1491
+
1492
+ // The user cache: whatever the host named, else the platform rule
1493
+ // (`modCacheDir`, ts/src/mod.ts) the tooling writes by.
1494
+ const modCache = (opts: any): string | undefined => {
1495
+ const named = opts.mod?.cache
1496
+ return 'string' === typeof named ? named : modCacheDir()
1497
+ }
1498
+
1499
+ // The directory an include is being resolved FROM: the source that
1500
+ // holds it, or the entry path when the source is a string. Same base
1501
+ // the file leg computes (resolvePathSpec in @tabnas/multisource).
1502
+ const dirOf = (p: string | undefined): string =>
1503
+ null == p || '' === p ? pathResolve('.') : pathDirname(pathResolve(p))
1504
+
1505
+ // The module store reader: the host's filesystem when one was
1506
+ // injected, so a sandboxed evaluation stays in the filesystem the
1507
+ // host gave it.
1508
+ const modFs = (ctx: any): any => {
1509
+ const hostfs = ctx?.meta?.fs
1510
+ return null == hostfs ? { existsSync, readFileSync } : {
1511
+ existsSync: (p: string) => {
1512
+ try {
1513
+ hostfs.statSync(p)
1514
+ return true
1515
+ }
1516
+ catch {
1517
+ return false
1518
+ }
1519
+ },
1520
+ readFileSync: (p: string, enc: string) => hostfs.readFileSync(p, enc),
1521
+ }
1522
+ }
1523
+
1524
+ // The manifest sink rides the parse meta (Lang.parse seeds it, the
1525
+ // multisource plugin's child-meta spread carries it to every nested
1526
+ // include), so the recorded closure covers the whole include tree.
1527
+ const record = (ctx: any, path: string, cap: string) => {
1528
+ const manifest = ctx?.meta?.aontu?.manifest
1529
+ if (Array.isArray(manifest)) {
1530
+ manifest.push({ path, capability: cap })
1531
+ }
1532
+ }
1533
+
1193
1534
  return function ModelResolver(
1194
1535
  spec: any,
1195
1536
  popts: any,
@@ -1209,10 +1550,64 @@ function makeModelResolver(options: any) {
1209
1550
  return { found: false, path: '' + (path ?? ''), search: [] }
1210
1551
  }
1211
1552
 
1553
+ if ('none' === capability) {
1554
+ deny(path)
1555
+ }
1556
+
1557
+ // THE BUNDLED VOCABULARY (G4 phase 4, ts/src/std.ts): served from
1558
+ // the engine itself, so it needs neither the filesystem nor package
1559
+ // resolution and is available under every capability but `none` —
1560
+ // checked just above, that being what `none` means. Matched against
1561
+ // the name the author WROTE, before the memory leg, so the kind is
1562
+ // stated rather than guessed from an extension the bare name does
1563
+ // not have.
1564
+ const std = STD_SOURCES[path]
1565
+ if (null != std) {
1566
+ record(ctx, path, 'std')
1567
+ return { found: true, path, full: path, kind: 'aon', src: std, search: [] }
1568
+ }
1569
+
1212
1570
  let search: any = []
1213
1571
  let res = memResolver(path, popts, rule, ctx, jsonic)
1214
1572
  res.path = path
1215
1573
  if (res.found) {
1574
+ record(ctx, res.full ?? path, 'mem')
1575
+ return res
1576
+ }
1577
+
1578
+ // THE MODULE LEG (G6 phase 2, ts/src/mod.ts): memory -> MODULE ->
1579
+ // filesystem -> package. Memory stays FIRST so a sandbox and the
1580
+ // spec suite can stub a module path without touching disk; a path
1581
+ // that is not module-shaped falls straight through, so no existing
1582
+ // include can be routed somewhere new by this.
1583
+ const modref = memCapability ? undefined : parseModuleRef(path)
1584
+ if (null != modref) {
1585
+ const msmeta = (ctx as any)?.meta?.multisource
1586
+ const from = dirOf(null != msmeta?.path ? msmeta.path : popts?.path)
1587
+ const found = resolveModule(modref, from, modFs(ctx), {
1588
+ // The user cache lives outside any confinement root, so it is
1589
+ // consulted only when nothing confines this evaluation. A
1590
+ // rooted profile sees the project's own `aon_vendor/` and
1591
+ // nothing else, which is what `root` means.
1592
+ ...(null == rootDir ? { cache: modCache(options) } : {}),
1593
+ eval: options.mod?.eval,
1594
+ depth: options.mod?.depth,
1595
+ })
1596
+ if (null != rootDir && outsideRoot(rootDir, found.full)) {
1597
+ deny(path)
1598
+ }
1599
+ record(ctx, found.full, 'mod')
1600
+ return {
1601
+ found: true, path, full: found.full,
1602
+ kind: 'aon', src: found.src, search: [],
1603
+ }
1604
+ }
1605
+
1606
+ if (memCapability) {
1607
+ // A miss in the declared virtual set is NOT-FOUND, not denial:
1608
+ // the allowed mechanism ran and missed. Denial is reserved for a
1609
+ // capability refusing a mechanism outright.
1610
+ res.search = search.concat(res.search)
1216
1611
  return res
1217
1612
  }
1218
1613
 
@@ -1221,14 +1616,42 @@ function makeModelResolver(options: any) {
1221
1616
  res = fileResolver(path, popts, rule, ctx, jsonic)
1222
1617
  res.path = path
1223
1618
  if (res.found) {
1619
+ // `res.full` asserted non-null: a FOUND file resolution always
1620
+ // carries the absolute path it read (and the pkg leg below is the
1621
+ // same), so a runtime fallback arm would be dead code.
1622
+ const full = res.full as string
1623
+ if (null != rootDir && outsideRoot(rootDir, full)) {
1624
+ deny(path)
1625
+ }
1626
+ // The warning window for the staged default flip (G5 phase 6):
1627
+ // under 'system', the CLI supplies trustWarn and the entry root,
1628
+ // and every resolution escaping that root names the flag a future
1629
+ // default will require.
1630
+ if (null == rootDir && null != options.trustWarn &&
1631
+ null != options.trustWarnRoot &&
1632
+ outsideRoot(options.trustWarnRoot, full)) {
1633
+ options.trustWarn('escape', full)
1634
+ }
1635
+ record(ctx, full, 'file')
1224
1636
  return res
1225
1637
  }
1226
1638
 
1227
1639
  search = search.concat(res.search)
1228
1640
 
1641
+ if (null != rootDir) {
1642
+ // Package resolution is not part of the root capability; the
1643
+ // miss stands as not-found with the searched paths listed.
1644
+ res.search = search
1645
+ return res
1646
+ }
1647
+
1229
1648
  res = pkgResolver(path, popts, rule, ctx, jsonic)
1230
1649
  res.path = path
1231
1650
  if (res.found) {
1651
+ if (null != options.trustWarn) {
1652
+ options.trustWarn('pkg', res.full as string)
1653
+ }
1654
+ record(ctx, res.full as string, 'pkg')
1232
1655
  return res
1233
1656
  }
1234
1657
 
@@ -1239,16 +1662,31 @@ function makeModelResolver(options: any) {
1239
1662
 
1240
1663
 
1241
1664
  // funcArity is the permitted WRITTEN argument count of each built-in, as
1665
+ // The functions whose comma-separated arguments are distinct POSITIONS
1666
+ // rather than one argument list. See the func-paren handler above: this
1667
+ // is the set whose comma group is expanded back into separate `peg`
1668
+ // entries.
1669
+ const POSITIONAL_ARG_FUNCS: Record<string, boolean> = {
1670
+ deprecate: true, pack: true, each: true, filter: true, match: true,
1671
+ // Arithmetic takes two OPERANDS, and an operand is a position: `sub`
1672
+ // is not commutative, so `sub(a, b)` reaching the engine as one
1673
+ // two-element list would lose which is which.
1674
+ add: true, sub: true, mul: true, div: true, mod: true, rem: true,
1675
+ // The bag and the key are distinct positions.
1676
+ pick: true,
1677
+ }
1678
+
1679
+
1242
1680
  // [min, max]; a max of -1 is unbounded. Every name in funcMap has an
1243
1681
  // entry, and the arity is a property of the language rather than of
1244
1682
  // either port -- go/func.go carries the same table.
1245
1683
  //
1246
- // Nearly everything takes exactly one. The four exceptions earn their
1247
- // place: key() names how many levels UP the path to read, defaulting to
1248
- // the parent when omitted, neq takes a whole set of exclusions,
1249
- // unique() is a property of the container rather than a comparison
1250
- // against anything, so there is nothing for it to take, and must()
1251
- // takes a check AND the author's message for when it fails.
1684
+ // Nearly everything takes exactly one. The exceptions earn their place:
1685
+ // key() names how many levels UP the path to read, defaulting to the
1686
+ // parent when omitted, neq takes a whole set of exclusions, unique()
1687
+ // takes none (a property of the container) or a PROJECTOR key, must()
1688
+ // takes a check AND the author's message for when it fails, and the
1689
+ // arithmetic family takes two operands.
1252
1690
  const funcArity: Record<string, [number, number]> = {
1253
1691
  upper: [1, 1], lower: [1, 1], copy: [1, 1], pref: [1, 1],
1254
1692
  super: [1, 1], type: [1, 1], hide: [1, 1], close: [1, 1],
@@ -1256,9 +1694,30 @@ const funcArity: Record<string, [number, number]> = {
1256
1694
  min: [1, 1], max: [1, 1], above: [1, 1], below: [1, 1], re: [1, 1],
1257
1695
  length: [1, 1],
1258
1696
  key: [0, 1],
1259
- unique: [0, 0],
1697
+ // THE ONE ARGUMENT IS A PROJECTOR: `unique(port)` says no two
1698
+ // members share a `port`. The arity was reserved for it (finding I).
1699
+ unique: [0, 1],
1260
1700
  neq: [1, -1],
1261
1701
  must: [2, 2],
1702
+ deprecate: [1, 2],
1703
+ id: [1, 1],
1704
+ refer: [0, 1],
1705
+ pack: [2, 2],
1706
+ each: [1, 2],
1707
+ filter: [2, 2],
1708
+ // The scrutinee, then pattern/result pairs, then an optional
1709
+ // default: three arguments at least, and any number above that.
1710
+ match: [3, -1],
1711
+ // Two operands, always. Arithmetic has no variadic reading that is
1712
+ // not a fold, and a fold is the recursion this language refuses.
1713
+ add: [2, 2], sub: [2, 2], mul: [2, 2],
1714
+ div: [2, 2], mod: [2, 2], rem: [2, 2],
1715
+ // One bag. The operation is fixed, so there is nothing else to pass:
1716
+ // a fold's second argument is a FUNCTION, and this language has none
1717
+ // to give it.
1718
+ sum: [1, 1], least: [1, 1], greatest: [1, 1],
1719
+ // The bag, and the key to take from each of its children.
1720
+ pick: [2, 2],
1262
1721
  }
1263
1722
 
1264
1723
 
@@ -1293,11 +1752,13 @@ function arityText(lo: number, hi: number): string {
1293
1752
  return 'one or more arguments'
1294
1753
  }
1295
1754
  if (lo !== hi) {
1296
- return 'no arguments or one'
1297
- }
1298
- if (0 === hi) {
1299
- return 'no arguments'
1755
+ return 0 === lo ? 'no arguments or one' : 'one argument or two'
1300
1756
  }
1757
+ // NO {0,0} ARM. `unique` was the only built-in taking none, and its
1758
+ // one argument is now the projector the arity was reserved for
1759
+ // (finding I), so a phrasing for a count no entry carries would be
1760
+ // untested prose pretending to be tested -- the rule this function's
1761
+ // header states. The arm returns with the table, if one ever does.
1301
1762
  if (2 === hi) {
1302
1763
  return 'exactly two arguments'
1303
1764
  }
@@ -1421,7 +1882,13 @@ class Lang {
1421
1882
  multisource: {
1422
1883
  path: opts?.path ?? this.opts.path,
1423
1884
  deps: (opts && opts.deps) || undefined
1424
- }
1885
+ },
1886
+ // The include-manifest sink (G5, docs/trust.md): the resolver
1887
+ // records every resolved include here, and the plugin's
1888
+ // child-meta spread carries the same array to nested includes.
1889
+ aontu: {
1890
+ manifest: (opts as any)?.manifest,
1891
+ },
1425
1892
  }
1426
1893
 
1427
1894
  if (null != opts?.idcount) {
@@ -1449,16 +1916,49 @@ class Lang {
1449
1916
  }
1450
1917
  }
1451
1918
  catch (e: any) {
1452
- if (e instanceof JsonicError || 'JsonicError' === e.constructor.name) {
1919
+ if ('include_denied' === e?.code ||
1920
+ 'module_missing' === e?.code || 'module_integrity' === e?.code ||
1921
+ 'module_depth' === e?.code) {
1922
+ // A denied include (trust profile, G5): the resolver throws so
1923
+ // a bare-member include cannot vanish in the merge, and the
1924
+ // code survives here as the parse-stage nil the registry
1925
+ // pins (errcodes.tsv: include_denied, class parse).
1926
+ // A denied include (G5) and a module that is missing or fails
1927
+ // its pin (G6 phase 2) are refused the same way, for the same
1928
+ // reason: the resolver THROWS so a bare-member include cannot
1929
+ // vanish in the merge, and the code survives here as the
1930
+ // parse-stage nil the registry pins (errcodes.tsv).
1453
1931
  val = new NilVal({
1454
1932
  why: 'parse',
1455
1933
  err: new NilVal({
1456
- why: 'syntax',
1457
- msg: e.message + opCharHint(src),
1934
+ why: e.code,
1935
+ msg: e.message,
1458
1936
  err: e,
1459
1937
  })
1460
1938
  })
1461
1939
  }
1940
+ else if (e instanceof JsonicError || 'JsonicError' === e.constructor.name) {
1941
+ const syntax: any = new NilVal({
1942
+ why: 'syntax',
1943
+ msg: e.message + opCharHint(src),
1944
+ err: e,
1945
+ })
1946
+ // THE POSITION TRAVELS WITH IT. The parser knows exactly where
1947
+ // it stopped -- it draws a caret there -- and the rendered
1948
+ // message carried the only copy, so `vet --format json`
1949
+ // reported row -1, col -1 for a document whose fault the human
1950
+ // renderer located to the character. A machine-readable report
1951
+ // that says "somewhere in this file" is the one a repair loop
1952
+ // can do nothing with. Both numbers are already 1-based here,
1953
+ // which is the base a site uses (go/lang.go does the same).
1954
+ if ('number' === typeof e.lineNumber) {
1955
+ syntax.site.row = e.lineNumber
1956
+ }
1957
+ if ('number' === typeof e.columnNumber) {
1958
+ syntax.site.col = e.columnNumber
1959
+ }
1960
+ val = new NilVal({ why: 'parse', err: syntax })
1961
+ }
1462
1962
  else {
1463
1963
  throw e
1464
1964
  }