aontu 0.52.1 → 0.54.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (310) hide show
  1. package/README.md +88 -0
  2. package/bin/aontu-mcp.js +4 -0
  3. package/dist/agentsmd.d.ts +16 -0
  4. package/dist/agentsmd.js +107 -0
  5. package/dist/agentsmd.js.map +1 -0
  6. package/dist/aontu.d.ts +15 -3
  7. package/dist/aontu.js +126 -5
  8. package/dist/aontu.js.map +1 -1
  9. package/dist/cli.d.ts +45 -1
  10. package/dist/cli.js +2845 -69
  11. package/dist/cli.js.map +1 -1
  12. package/dist/ctx.d.ts +18 -0
  13. package/dist/ctx.js +44 -0
  14. package/dist/ctx.js.map +1 -1
  15. package/dist/diff.d.ts +22 -0
  16. package/dist/diff.js +141 -0
  17. package/dist/diff.js.map +1 -0
  18. package/dist/err.d.ts +3 -1
  19. package/dist/err.js +45 -8
  20. package/dist/err.js.map +1 -1
  21. package/dist/graph.d.ts +13 -0
  22. package/dist/graph.js +110 -0
  23. package/dist/graph.js.map +1 -0
  24. package/dist/hcanon.d.ts +3 -0
  25. package/dist/hcanon.js +145 -0
  26. package/dist/hcanon.js.map +1 -0
  27. package/dist/hints.js +235 -8
  28. package/dist/hints.js.map +1 -1
  29. package/dist/jsonschema.d.ts +20 -0
  30. package/dist/jsonschema.js +425 -0
  31. package/dist/jsonschema.js.map +1 -0
  32. package/dist/lang.js +955 -110
  33. package/dist/lang.js.map +1 -1
  34. package/dist/lsp.d.ts +9 -2
  35. package/dist/lsp.js +343 -48
  36. package/dist/lsp.js.map +1 -1
  37. package/dist/mcp-server.d.ts +20 -0
  38. package/dist/mcp-server.js +147 -0
  39. package/dist/mcp-server.js.map +1 -0
  40. package/dist/mcp.d.ts +42 -0
  41. package/dist/mcp.js +941 -0
  42. package/dist/mcp.js.map +1 -0
  43. package/dist/mod-tool.d.ts +58 -0
  44. package/dist/mod-tool.js +532 -0
  45. package/dist/mod-tool.js.map +1 -0
  46. package/dist/mod.d.ts +35 -0
  47. package/dist/mod.js +345 -0
  48. package/dist/mod.js.map +1 -0
  49. package/dist/patch.d.ts +49 -0
  50. package/dist/patch.js +506 -0
  51. package/dist/patch.js.map +1 -0
  52. package/dist/provenance.d.ts +41 -0
  53. package/dist/provenance.js +336 -0
  54. package/dist/provenance.js.map +1 -0
  55. package/dist/query.d.ts +27 -0
  56. package/dist/query.js +294 -0
  57. package/dist/query.js.map +1 -0
  58. package/dist/reach.d.ts +15 -0
  59. package/dist/reach.js +168 -0
  60. package/dist/reach.js.map +1 -0
  61. package/dist/relation.d.ts +23 -0
  62. package/dist/relation.js +230 -0
  63. package/dist/relation.js.map +1 -0
  64. package/dist/report-sarif.d.ts +14 -0
  65. package/dist/report-sarif.js +102 -0
  66. package/dist/report-sarif.js.map +1 -0
  67. package/dist/sig.d.ts +25 -0
  68. package/dist/sig.js +277 -0
  69. package/dist/sig.js.map +1 -0
  70. package/dist/sigdecl.d.ts +2 -0
  71. package/dist/sigdecl.js +11 -0
  72. package/dist/sigdecl.js.map +1 -0
  73. package/dist/siggate.d.ts +4 -0
  74. package/dist/siggate.js +90 -0
  75. package/dist/siggate.js.map +1 -0
  76. package/dist/site.d.ts +4 -0
  77. package/dist/site.js +31 -0
  78. package/dist/site.js.map +1 -1
  79. package/dist/std.d.ts +1 -0
  80. package/dist/std.js +129 -0
  81. package/dist/std.js.map +1 -0
  82. package/dist/subsume.d.ts +39 -0
  83. package/dist/subsume.js +573 -0
  84. package/dist/subsume.js.map +1 -0
  85. package/dist/trim.d.ts +19 -0
  86. package/dist/trim.js +155 -0
  87. package/dist/trim.js.map +1 -0
  88. package/dist/tsconfig.tsbuildinfo +1 -1
  89. package/dist/type.d.ts +17 -1
  90. package/dist/type.js.map +1 -1
  91. package/dist/unify.d.ts +2 -1
  92. package/dist/unify.js +234 -38
  93. package/dist/unify.js.map +1 -1
  94. package/dist/utility.d.ts +8 -1
  95. package/dist/utility.js +68 -1
  96. package/dist/utility.js.map +1 -1
  97. package/dist/val/AggFuncVal.d.ts +44 -0
  98. package/dist/val/AggFuncVal.js +364 -0
  99. package/dist/val/AggFuncVal.js.map +1 -0
  100. package/dist/val/ArithFuncVal.d.ts +31 -0
  101. package/dist/val/ArithFuncVal.js +62 -0
  102. package/dist/val/ArithFuncVal.js.map +1 -0
  103. package/dist/val/BagVal.d.ts +7 -0
  104. package/dist/val/BagVal.js +150 -11
  105. package/dist/val/BagVal.js.map +1 -1
  106. package/dist/val/CloseFuncVal.js +9 -1
  107. package/dist/val/CloseFuncVal.js.map +1 -1
  108. package/dist/val/ConjunctVal.d.ts +1 -1
  109. package/dist/val/ConjunctVal.js +19 -0
  110. package/dist/val/ConjunctVal.js.map +1 -1
  111. package/dist/val/ConstraintVal.d.ts +7 -1
  112. package/dist/val/ConstraintVal.js +523 -45
  113. package/dist/val/ConstraintVal.js.map +1 -1
  114. package/dist/val/ContainerKindVal.d.ts +35 -0
  115. package/dist/val/ContainerKindVal.js +99 -0
  116. package/dist/val/ContainerKindVal.js.map +1 -0
  117. package/dist/val/CopyFuncVal.d.ts +1 -2
  118. package/dist/val/CopyFuncVal.js.map +1 -1
  119. package/dist/val/Decimal.d.ts +1 -0
  120. package/dist/val/Decimal.js +13 -0
  121. package/dist/val/Decimal.js.map +1 -1
  122. package/dist/val/DeprecateFuncVal.d.ts +11 -0
  123. package/dist/val/DeprecateFuncVal.js +47 -0
  124. package/dist/val/DeprecateFuncVal.js.map +1 -0
  125. package/dist/val/DisjunctVal.d.ts +1 -2
  126. package/dist/val/DisjunctVal.js +269 -53
  127. package/dist/val/DisjunctVal.js.map +1 -1
  128. package/dist/val/EachFuncVal.d.ts +15 -0
  129. package/dist/val/EachFuncVal.js +75 -0
  130. package/dist/val/EachFuncVal.js.map +1 -0
  131. package/dist/val/ExpectVal.js +62 -4
  132. package/dist/val/ExpectVal.js.map +1 -1
  133. package/dist/val/FeatureVal.js +1 -1
  134. package/dist/val/FeatureVal.js.map +1 -1
  135. package/dist/val/FilterFuncVal.d.ts +15 -0
  136. package/dist/val/FilterFuncVal.js +91 -0
  137. package/dist/val/FilterFuncVal.js.map +1 -0
  138. package/dist/val/FuncBaseVal.d.ts +7 -1
  139. package/dist/val/FuncBaseVal.js +183 -2
  140. package/dist/val/FuncBaseVal.js.map +1 -1
  141. package/dist/val/GraphAtomVal.d.ts +39 -0
  142. package/dist/val/GraphAtomVal.js +184 -0
  143. package/dist/val/GraphAtomVal.js.map +1 -0
  144. package/dist/val/HideFuncVal.js.map +1 -1
  145. package/dist/val/JunctionVal.js +24 -1
  146. package/dist/val/JunctionVal.js.map +1 -1
  147. package/dist/val/KeyFuncVal.d.ts +1 -1
  148. package/dist/val/KeyFuncVal.js +38 -30
  149. package/dist/val/KeyFuncVal.js.map +1 -1
  150. package/dist/val/ListVal.js +94 -14
  151. package/dist/val/ListVal.js.map +1 -1
  152. package/dist/val/LowerFuncVal.js.map +1 -1
  153. package/dist/val/MapVal.d.ts +1 -0
  154. package/dist/val/MapVal.js +146 -14
  155. package/dist/val/MapVal.js.map +1 -1
  156. package/dist/val/MatchFuncVal.d.ts +15 -0
  157. package/dist/val/MatchFuncVal.js +107 -0
  158. package/dist/val/MatchFuncVal.js.map +1 -0
  159. package/dist/val/MoveFuncVal.js.map +1 -1
  160. package/dist/val/NilVal.js +24 -0
  161. package/dist/val/NilVal.js.map +1 -1
  162. package/dist/val/OpBaseVal.d.ts +1 -1
  163. package/dist/val/OpBaseVal.js +19 -1
  164. package/dist/val/OpBaseVal.js.map +1 -1
  165. package/dist/val/OpenFuncVal.js +4 -1
  166. package/dist/val/OpenFuncVal.js.map +1 -1
  167. package/dist/val/PackFuncVal.d.ts +15 -0
  168. package/dist/val/PackFuncVal.js +108 -0
  169. package/dist/val/PackFuncVal.js.map +1 -0
  170. package/dist/val/PathFuncVal.d.ts +2 -2
  171. package/dist/val/PathFuncVal.js +75 -16
  172. package/dist/val/PathFuncVal.js.map +1 -1
  173. package/dist/val/PathVal.d.ts +25 -0
  174. package/dist/val/PathVal.js +150 -0
  175. package/dist/val/PathVal.js.map +1 -0
  176. package/dist/val/PlaceVal.d.ts +13 -0
  177. package/dist/val/PlaceVal.js +131 -0
  178. package/dist/val/PlaceVal.js.map +1 -0
  179. package/dist/val/PlusOpVal.d.ts +2 -1
  180. package/dist/val/PlusOpVal.js +61 -37
  181. package/dist/val/PlusOpVal.js.map +1 -1
  182. package/dist/val/PrefFuncVal.js.map +1 -1
  183. package/dist/val/PrefVal.d.ts +4 -2
  184. package/dist/val/PrefVal.js +223 -41
  185. package/dist/val/PrefVal.js.map +1 -1
  186. package/dist/val/RecurseVal.d.ts +19 -0
  187. package/dist/val/RecurseVal.js +217 -0
  188. package/dist/val/RecurseVal.js.map +1 -0
  189. package/dist/val/RefVal.d.ts +3 -2
  190. package/dist/val/RefVal.js +181 -66
  191. package/dist/val/RefVal.js.map +1 -1
  192. package/dist/val/ReferFuncVal.d.ts +59 -0
  193. package/dist/val/ReferFuncVal.js +604 -0
  194. package/dist/val/ReferFuncVal.js.map +1 -0
  195. package/dist/val/ScalarKindVal.d.ts +4 -3
  196. package/dist/val/ScalarKindVal.js +12 -12
  197. package/dist/val/ScalarKindVal.js.map +1 -1
  198. package/dist/val/SuperFuncVal.d.ts +4 -2
  199. package/dist/val/SuperFuncVal.js +118 -14
  200. package/dist/val/SuperFuncVal.js.map +1 -1
  201. package/dist/val/TopVal.d.ts +1 -1
  202. package/dist/val/TopVal.js.map +1 -1
  203. package/dist/val/TypeFuncVal.js.map +1 -1
  204. package/dist/val/UpperFuncVal.js.map +1 -1
  205. package/dist/val/Val.d.ts +7 -1
  206. package/dist/val/Val.js +170 -2
  207. package/dist/val/Val.js.map +1 -1
  208. package/dist/val/VarVal.js.map +1 -1
  209. package/dist/val/arith.d.ts +6 -0
  210. package/dist/val/arith.js +173 -0
  211. package/dist/val/arith.js.map +1 -0
  212. package/dist/vet.d.ts +45 -0
  213. package/dist/vet.js +802 -0
  214. package/dist/vet.js.map +1 -0
  215. package/dist/view.d.ts +85 -0
  216. package/dist/view.js +1948 -0
  217. package/dist/view.js.map +1 -0
  218. package/dist/walk.d.ts +2 -0
  219. package/dist/walk.js +91 -0
  220. package/dist/walk.js.map +1 -0
  221. package/grammar/aontu.gbnf +138 -0
  222. package/grammar/aontu.lark +118 -0
  223. package/grammar/aontu.tmLanguage.json +184 -0
  224. package/package.json +30 -8
  225. package/skill/SKILL.md +37 -0
  226. package/skill/error-codes.md +62 -0
  227. package/skill/examples.md +99 -0
  228. package/skill/grammar-card.md +56 -0
  229. package/src/agentsmd.ts +135 -0
  230. package/src/aontu.ts +166 -5
  231. package/src/cli.ts +3324 -72
  232. package/src/ctx.ts +93 -1
  233. package/src/diff.ts +198 -0
  234. package/src/err.ts +50 -8
  235. package/src/graph.ts +185 -0
  236. package/src/hcanon.ts +167 -0
  237. package/src/hints.ts +291 -8
  238. package/src/jsonschema.ts +552 -0
  239. package/src/lang.ts +1049 -110
  240. package/src/lsp.ts +351 -49
  241. package/src/mcp-server.ts +187 -0
  242. package/src/mcp.ts +1130 -0
  243. package/src/mod-tool.ts +718 -0
  244. package/src/mod.ts +453 -0
  245. package/src/patch.ts +628 -0
  246. package/src/provenance.ts +437 -0
  247. package/src/query.ts +381 -0
  248. package/src/reach.ts +213 -0
  249. package/src/relation.ts +298 -0
  250. package/src/report-sarif.ts +137 -0
  251. package/src/sig.ts +345 -0
  252. package/src/sigdecl.ts +11 -0
  253. package/src/siggate.ts +144 -0
  254. package/src/site.ts +36 -1
  255. package/src/std.ts +131 -0
  256. package/src/subsume.ts +739 -0
  257. package/src/trim.ts +195 -0
  258. package/src/tsconfig.json +10 -4
  259. package/src/type.ts +51 -2
  260. package/src/unify.ts +253 -38
  261. package/src/utility.ts +79 -1
  262. package/src/val/AggFuncVal.ts +546 -0
  263. package/src/val/ArithFuncVal.ts +108 -0
  264. package/src/val/BagVal.ts +158 -12
  265. package/src/val/CloseFuncVal.ts +9 -1
  266. package/src/val/ConjunctVal.ts +20 -0
  267. package/src/val/ConstraintVal.ts +576 -48
  268. package/src/val/ContainerKindVal.ts +158 -0
  269. package/src/val/CopyFuncVal.ts +0 -1
  270. package/src/val/Decimal.ts +15 -0
  271. package/src/val/DeprecateFuncVal.ts +84 -0
  272. package/src/val/DisjunctVal.ts +282 -59
  273. package/src/val/EachFuncVal.ts +133 -0
  274. package/src/val/ExpectVal.ts +65 -8
  275. package/src/val/FeatureVal.ts +1 -1
  276. package/src/val/FilterFuncVal.ts +154 -0
  277. package/src/val/FuncBaseVal.ts +206 -2
  278. package/src/val/GraphAtomVal.ts +264 -0
  279. package/src/val/HideFuncVal.ts +0 -2
  280. package/src/val/JunctionVal.ts +24 -1
  281. package/src/val/KeyFuncVal.ts +39 -35
  282. package/src/val/ListVal.ts +101 -15
  283. package/src/val/LowerFuncVal.ts +0 -1
  284. package/src/val/MapVal.ts +153 -14
  285. package/src/val/MatchFuncVal.ts +176 -0
  286. package/src/val/MoveFuncVal.ts +0 -2
  287. package/src/val/NilVal.ts +25 -0
  288. package/src/val/OpBaseVal.ts +20 -1
  289. package/src/val/OpenFuncVal.ts +4 -2
  290. package/src/val/PackFuncVal.ts +175 -0
  291. package/src/val/PathFuncVal.ts +107 -20
  292. package/src/val/PathVal.ts +221 -0
  293. package/src/val/PlaceVal.ts +193 -0
  294. package/src/val/PlusOpVal.ts +67 -39
  295. package/src/val/PrefFuncVal.ts +0 -1
  296. package/src/val/PrefVal.ts +251 -60
  297. package/src/val/RecurseVal.ts +285 -0
  298. package/src/val/RefVal.ts +183 -78
  299. package/src/val/ReferFuncVal.ts +732 -0
  300. package/src/val/ScalarKindVal.ts +12 -13
  301. package/src/val/SuperFuncVal.ts +137 -13
  302. package/src/val/TopVal.ts +1 -2
  303. package/src/val/TypeFuncVal.ts +0 -2
  304. package/src/val/UpperFuncVal.ts +0 -1
  305. package/src/val/Val.ts +216 -4
  306. package/src/val/VarVal.ts +0 -1
  307. package/src/val/arith.ts +319 -0
  308. package/src/vet.ts +1025 -0
  309. package/src/view.ts +2563 -0
  310. package/src/walk.ts +99 -0
package/src/mod.ts ADDED
@@ -0,0 +1,453 @@
1
+ /* Copyright (c) 2025 Richard Rodger, MIT License */
2
+
3
+ // MODULE IDENTITY AND LOCAL RESOLUTION (G6 phase 2,
4
+ // docs/capability-review/g6-distribution.md).
5
+ //
6
+ // An import is still just `@"…"`; the string's SHAPE routes it, so the
7
+ // grammar is untouched and every existing include keeps its exact
8
+ // behaviour:
9
+ //
10
+ // service: @"corp.example/schemas/service@1"
11
+ // frozen: @"corp.example/schemas/service@1#aon1-4vJemVYtWFR2mQeN…"
12
+ // local: @"./fragment.aon" <- unchanged, not a module
13
+ //
14
+ // EVALUATION NEVER TOUCHES THE NETWORK. Resolution reads local stores
15
+ // only: `aon_vendor/` beside the project's `mod.aon`, then a
16
+ // content-addressed user cache keyed by canon-hash. Fetching is a
17
+ // separate, explicit tool step, and a module that is in neither store
18
+ // is an evaluation error that says so.
19
+ //
20
+ // TWO PINS, TWO ROLES. The lockfile's `oci` digest certifies that these
21
+ // are the bytes the registry served; the `canon` hash certifies that
22
+ // this is the MEANING that was reviewed. Only the second can be checked
23
+ // locally without the registry, and it is the one this file checks: the
24
+ // module is unified standalone and its canon-hash compared with the
25
+ // pin. An inline `#aon1-…` fragment is the same check without a
26
+ // lockfile — the degenerate mode for single-file and agent-sandbox use.
27
+
28
+ import { join as pathJoin, dirname as pathDirname } from 'node:path'
29
+
30
+
31
+ // A module import, as the string spells it.
32
+ export type ModuleRef = {
33
+ // The module path WITHOUT the major: `corp.example/schemas/service`.
34
+ path: string
35
+ // The major version, from the `@N` suffix.
36
+ major: number
37
+ // The inline canon-hash pin, if the import froze one.
38
+ hash?: string
39
+ }
40
+
41
+
42
+ // A file store the resolver can read. The engine passes its own `fs`
43
+ // when the host injected one, so a sandboxed evaluation stays in the
44
+ // filesystem the host gave it.
45
+ export type ModuleFs = {
46
+ existsSync: (p: string) => boolean
47
+ readFileSync: (p: string, enc: string) => string
48
+ }
49
+
50
+
51
+ // A module path is DOMAIN-SHAPED — the first segment carries a dot,
52
+ // which is what tells it apart from `./local.aon`, `pkg-name` and every
53
+ // other spelling already in use — and carries the major version in the
54
+ // path, CUE/Go-style, so two majors are two modules.
55
+ //
56
+ // The pattern is deliberately narrow: anything it does not match falls
57
+ // through to the existing resolver chain unchanged, so no document that
58
+ // worked before this phase can be routed somewhere new by it.
59
+ const MODULE_RE =
60
+ /^([a-z0-9][a-z0-9-]*(?:\.[a-z0-9][a-z0-9-]*)+(?:\/[A-Za-z0-9._-]+)*)@(\d+)(?:#(aon1-[A-Za-z0-9_-]+))?$/
61
+
62
+
63
+ export function parseModuleRef(spec: string): ModuleRef | undefined {
64
+ const m = MODULE_RE.exec(spec)
65
+ if (null == m) {
66
+ return undefined
67
+ }
68
+ return {
69
+ path: m[1],
70
+ major: +m[2],
71
+ ...(null == m[3] ? {} : { hash: m[3] }),
72
+ }
73
+ }
74
+
75
+
76
+ // SHAPE IS NOT VALIDITY, and the gap between them was a hole. MODULE_RE
77
+ // answers "does this string route to the module resolver" -- a
78
+ // ROUTING predicate, and it must stay one, because anything it rejects
79
+ // falls through to the file leg and a stricter pattern would silently
80
+ // re-route documents that work today. But its element class
81
+ // `[A-Za-z0-9._-]` admits `..`, and `moduleDir` joins elements with
82
+ // pathJoin, which CLEANS `..` rather than refusing it:
83
+ //
84
+ // moduleDir('/store/aon_vendor', 'corp.example/../../etc/passwd@1')
85
+ // -> /store/etc/passwd@1
86
+ //
87
+ // `mod vendor` then copied a tree THERE, outside the project entirely,
88
+ // and reported `verdict: ok`. So validity is a separate question asked
89
+ // separately, after the shape matched, and asked at every site that
90
+ // turns a module path into a directory.
91
+ //
92
+ // The rules are Go's (golang.org/x/mod/module.CheckPath), for the
93
+ // reason Go has them: a module path becomes a real directory on every
94
+ // platform the toolchain runs on, so it must be a legal one everywhere.
95
+ export const MODULE_MAX_PATH = 512
96
+ export const MODULE_MAX_ELEMS = 32
97
+
98
+
99
+ // Windows refuses these as file names whatever the extension, so a
100
+ // module path containing one cannot be materialised there at all. The
101
+ // check is on the element up to its first dot, which is where Windows
102
+ // stops looking too.
103
+ const RESERVED_ELEMS = new Set([
104
+ 'con', 'prn', 'aux', 'nul',
105
+ 'com1', 'com2', 'com3', 'com4', 'com5', 'com6', 'com7', 'com8', 'com9',
106
+ 'lpt1', 'lpt2', 'lpt3', 'lpt4', 'lpt5', 'lpt6', 'lpt7', 'lpt8', 'lpt9',
107
+ ])
108
+
109
+
110
+ // Why a module path may not be used as a directory, or undefined when
111
+ // it may. The reason is user-facing: it goes in the refusal, because a
112
+ // path refused without saying which rule it broke is a puzzle.
113
+ export function validateModulePath(path: string): string | undefined {
114
+ if (MODULE_MAX_PATH < path.length) {
115
+ return 'longer than ' + MODULE_MAX_PATH + ' characters'
116
+ }
117
+
118
+ const elems = path.split('/')
119
+ if (MODULE_MAX_ELEMS < elems.length) {
120
+ return 'more than ' + MODULE_MAX_ELEMS + ' elements'
121
+ }
122
+
123
+ for (const elem of elems) {
124
+ if ('' === elem) {
125
+ return 'an element is empty'
126
+ }
127
+ // This one rule kills `.` and `..` -- the traversal -- along with
128
+ // `.hidden` and `trailing.`, exactly as Go's does. Stating it as
129
+ // the rule rather than as "no `..`" is deliberate: a check that
130
+ // named the two dangerous spellings would miss the next one.
131
+ if (elem.startsWith('.') || elem.endsWith('.')) {
132
+ return 'an element begins or ends with "."'
133
+ }
134
+ if (RESERVED_ELEMS.has(elem.split('.')[0].toLowerCase())) {
135
+ return 'an element is a reserved device name'
136
+ }
137
+ }
138
+
139
+ return undefined
140
+ }
141
+
142
+
143
+ // An element as it is spelled ON DISK. Uppercase is escaped to
144
+ // `!`+lowercase, Go's rule (go.dev/ref/mod, module proxy protocol) and
145
+ // for Go's reason: `github.com/Alice/Widgets` and
146
+ // `github.com/alice/widgets` are two module identities and, on macOS
147
+ // and Windows, ONE directory -- so without this the second module
148
+ // fetched silently clobbers the first, and an unpinned import resolves
149
+ // to whichever won.
150
+ //
151
+ // The WRITTEN path stays the identity; only the directory is escaped.
152
+ function escapeElem(elem: string): string {
153
+ return elem.replace(/[A-Z]/g, (c) => '!' + c.toLowerCase())
154
+ }
155
+
156
+
157
+ // The directory a module's files live in, under a store root.
158
+ //
159
+ // Callers must have validated the path (validateModulePath); this
160
+ // function cannot refuse, because it answers a location rather than a
161
+ // question, and every caller has a refusal shape of its own.
162
+ export function moduleDir(store: string, ref: ModuleRef): string {
163
+ return pathJoin(store, ...ref.path.split('/').map(escapeElem)) + '@' + ref.major
164
+ }
165
+
166
+
167
+ // EVERY project root at or above `from`, innermost first — a project
168
+ // root being a directory holding a `mod.aon`. This used to answer with
169
+ // the NEAREST one alone, and the plural is the fix, because a
170
+ // VENDORED MODULE IS A PROJECT INSIDE A PROJECT. A module in
171
+ // `aon_vendor/` carries its own `mod.aon`, which stopped the upward
172
+ // walk there, so a nested import resolved against the vendored
173
+ // module's own directory: a tree with no `aon_vendor/` of its own, and
174
+ // therefore a `module not fetched` for a dependency sitting flat
175
+ // beside it in the CONSUMER's vendor tree — the only layout `mod
176
+ // vendor` produces (use-cases/BUGS.md §31).
177
+ //
178
+ // The consumer's stores are searched after the module's own, so a
179
+ // module that vendors its dependencies nested still wins for its own
180
+ // tree, and one that does not falls through to the consumer that
181
+ // vendored it. The last element is `from` itself when nothing above it
182
+ // declares a module, which is the single-file inline-pin mode.
183
+ export function projectRoots(from: string, fs: ModuleFs): string[] {
184
+ const roots: string[] = []
185
+ let dir = from
186
+ for (; ;) {
187
+ if (fs.existsSync(pathJoin(dir, 'mod.aon'))) {
188
+ roots.push(dir)
189
+ }
190
+ const up = pathDirname(dir)
191
+ if (up === dir) {
192
+ return 0 < roots.length ? roots : [from]
193
+ }
194
+ dir = up
195
+ }
196
+ }
197
+
198
+
199
+ // The lockfile's pin for one import, or undefined.
200
+ //
201
+ // `mod-lock.aon` is machine-written CANONICAL Aontu, and canonical
202
+ // Aontu whose leaves are scalars IS JSON — which is why reading it here
203
+ // needs no evaluator, and why a hand-edited lockfile that is no longer
204
+ // canonical simply does not parse. It is generated; the file says so.
205
+ // The lockfile's JSON: its canonical line, with the generated-file
206
+ // header stripped. The file is AONTU, so it may carry `#` comments —
207
+ // and the header `aontu mod tidy` writes says not to edit it, which is
208
+ // worth more than the two lines it costs to skip. Everything below the
209
+ // comments is the canonical map, and canonical Aontu whose leaves are
210
+ // scalars is JSON.
211
+ export function lockJson(text: string): string {
212
+ return text
213
+ .split('\n')
214
+ .filter((line) => !line.trimStart().startsWith('#'))
215
+ .join('\n')
216
+ }
217
+
218
+
219
+ // The user cache: `$XDG_CACHE_HOME/aontu/mod` unless the host names
220
+ // another, else the platform's own cache location. A host with nowhere
221
+ // to put one has no cache, which is a miss rather than a failure. One
222
+ // rule, in one place: the resolver reads this cache during evaluation
223
+ // and `aontu mod` writes into it, and two spellings of "where the cache
224
+ // is" is one bug.
225
+ export function modCacheDir(): string | undefined {
226
+ return modCacheDirFor(process.platform, process.env)
227
+ }
228
+
229
+
230
+ // That rule with the platform and the environment PASSED IN, so the
231
+ // Windows arm can be exercised off Windows — the only way a rule about
232
+ // a platform nobody here runs gets tested at all. The Go port splits
233
+ // the same way (modCacheDirFor, go/aontu.go).
234
+ //
235
+ // THE ORDER IS EXPLICIT BEFORE IMPLICIT, and LOCALAPPDATA is LAST.
236
+ // XDG_CACHE_HOME is the override and wins everywhere, Windows included:
237
+ // a caller who names a cache directory means it. HOME comes next and is
238
+ // also honoured on Windows, where it is not standard but IS set by Git
239
+ // Bash and by most development shells — a user who has one expects
240
+ // their tools to agree about where home is.
241
+ //
242
+ // LOCALAPPDATA is the PLATFORM DEFAULT beneath both, which is the whole
243
+ // addition: Windows sets neither XDG_CACHE_HOME nor HOME by default —
244
+ // it supplies USERPROFILE and LOCALAPPDATA, and LOCALAPPDATA is what a
245
+ // cache directory means there — so a rule that knew only the first two
246
+ // left every Windows user with NO cache. Putting it ABOVE HOME was the
247
+ // first attempt and was wrong: it made an explicitly set HOME
248
+ // unreachable on Windows, which broke the existing fallback test the
249
+ // moment CI ran it. A platform default that overrides what the
250
+ // environment was told is not a default.
251
+ export function modCacheDirFor(
252
+ platform: string,
253
+ env: Record<string, string | undefined>,
254
+ ): string | undefined {
255
+ const xdg = env.XDG_CACHE_HOME
256
+ if ('string' === typeof xdg && '' !== xdg) {
257
+ return pathJoin(xdg, 'aontu', 'mod')
258
+ }
259
+ const home = env.HOME
260
+ if ('string' === typeof home && '' !== home) {
261
+ return pathJoin(home, '.cache', 'aontu', 'mod')
262
+ }
263
+ if ('win32' === platform) {
264
+ const local = env.LOCALAPPDATA
265
+ if ('string' === typeof local && '' !== local) {
266
+ return pathJoin(local, 'aontu', 'mod')
267
+ }
268
+ }
269
+ return undefined
270
+ }
271
+
272
+
273
+ export function lockHash(root: string, ref: ModuleRef, fs: ModuleFs):
274
+ string | undefined {
275
+ const file = pathJoin(root, 'mod-lock.aon')
276
+ if (!fs.existsSync(file)) {
277
+ return undefined
278
+ }
279
+
280
+ let lock: any
281
+ try {
282
+ lock = JSON.parse(lockJson(fs.readFileSync(file, 'utf8')))
283
+ }
284
+ catch {
285
+ return undefined
286
+ }
287
+
288
+ const entry = lock?.lock?.[ref.path + '@' + ref.major]
289
+ return 'string' === typeof entry?.canon ? entry.canon : undefined
290
+ }
291
+
292
+
293
+ // What a module resolution needs from the engine: evaluate a source
294
+ // standalone and answer both what it MEANS (the generated value, for
295
+ // reading a module file's own metadata) and what its meaning HASHES to
296
+ // (for the integrity check). Injected rather than imported, because
297
+ // this is EVALUATION — the very thing this file is called from the
298
+ // middle of — and a module resolver that imported the evaluator would
299
+ // close a cycle around the whole language.
300
+ export type ModuleEval =
301
+ (src: string, path: string) => { gen: any, hash: string }
302
+
303
+
304
+ // How deep module verification may nest before it is refused. A module
305
+ // is verified by EVALUATING it, and that evaluation resolves the
306
+ // module's own imports -- so a vendor tree that leads back to itself
307
+ // (a symlink is enough) would recurse until the host's stack gave out,
308
+ // and a verdict that depends on the host's stack size is exactly what
309
+ // the determinism clause forbids (docs/trust.md, and the same argument
310
+ // unify_cycle rests on). Sixteen is far above any real vendor nesting.
311
+ export const MODULE_MAX_DEPTH = 16
312
+
313
+
314
+ export type ModuleOptions = {
315
+ // The content-addressed user cache, keyed by canon-hash. Consulted
316
+ // only when the expected hash is known, which is what "content
317
+ // addressed" means: without a pin there is no address.
318
+ cache?: string
319
+ // The standalone evaluator, for reading module files and for the
320
+ // integrity check. Always present: Aontu injects it (ts/src/aontu.ts)
321
+ // because only the class that evaluates can answer what a module
322
+ // MEANS, and the resolver runs inside a parse that class started.
323
+ eval: ModuleEval
324
+ // How many module verifications deep this evaluation already is.
325
+ depth?: number
326
+ }
327
+
328
+
329
+ export type ModuleFound = {
330
+ // The module's main file, as an absolute path.
331
+ full: string
332
+ src: string
333
+ }
334
+
335
+
336
+ // EVERY code `resolveModule` can refuse with. The list lives HERE,
337
+ // beside the refusals themselves, because the parse layer has to
338
+ // recognise them to turn the throw into a parse-stage nil
339
+ // (ts/src/lang.ts) -- and when that list was written out longhand
340
+ // there, adding a fourth code left it unhandled and the refusal
341
+ // surfaced as `unexpected error` instead of the message it carries.
342
+ // The Go port has no such list (recordModErr takes any code), which is
343
+ // why only this side could drift.
344
+ export const MODULE_REFUSAL_CODES: ReadonlySet<string> = new Set([
345
+ 'module_path', 'module_missing', 'module_integrity', 'module_depth',
346
+ ])
347
+
348
+
349
+ // A refusal that carries its code to the parse layer, exactly as a
350
+ // denied include does (makeModelResolver's `deny`): the resolver
351
+ // THROWS, so a bare-member module import cannot vanish in the merge and
352
+ // leave a plausible, silently-partial document.
353
+ function refuse(code: string, message: string): never {
354
+ const err: any = new Error(message)
355
+ err.code = code
356
+ throw err
357
+ }
358
+
359
+
360
+ // Resolve one module import against the local stores.
361
+ export function resolveModule(
362
+ ref: ModuleRef,
363
+ fromDir: string,
364
+ fs: ModuleFs,
365
+ options: ModuleOptions,
366
+ ): ModuleFound {
367
+ // THE PATH IS CHECKED BEFORE ANYTHING IS BUILT FROM IT. This is
368
+ // first because it is a question about the REQUEST, not about the
369
+ // state of the machine: a path that cannot legally be a directory is
370
+ // refused identically whether or not the module is present, and
371
+ // whether or not the depth bound is near.
372
+ const badpath = validateModulePath(ref.path)
373
+ if (undefined !== badpath) {
374
+ refuse('module_path',
375
+ 'module path: ' + ref.path + '@' + ref.major + ' (' + badpath + ')')
376
+ }
377
+
378
+ if (MODULE_MAX_DEPTH <= (options.depth ?? 0)) {
379
+ refuse('module_depth',
380
+ 'module depth: ' + ref.path + '@' + ref.major +
381
+ ' (verification nested past ' + MODULE_MAX_DEPTH + ')')
382
+ }
383
+
384
+ // EVERY enclosing project, innermost first (see projectRoots): a
385
+ // vendored module is a project inside a project, and its nested
386
+ // imports have to reach the tree the consumer vendored them into.
387
+ const roots = projectRoots(fromDir, fs)
388
+ // The PIN comes from the first lockfile that names this import. A
389
+ // vendored module usually ships none, so that is the consumer's --
390
+ // which is right: the consumer's lock is what its build is pinned to.
391
+ const expect = ref.hash ??
392
+ roots.map((r) => lockHash(r, ref, fs)).find((h) => null != h)
393
+
394
+ const stores: string[] =
395
+ roots.map((r) => moduleDir(pathJoin(r, 'aon_vendor'), ref))
396
+ if (null != options.cache && null != expect) {
397
+ // Content-addressed: the cache is keyed by the hash, so a cache hit
398
+ // is already the right MEANING before anything is read from it.
399
+ stores.push(pathJoin(options.cache, expect))
400
+ }
401
+
402
+ const dir = stores.find((d) => fs.existsSync(pathJoin(d, 'mod.aon')))
403
+ if (undefined === dir) {
404
+ // The wording is the contract (docs/capability-review/
405
+ // g6-distribution.md): it names the module AND the step that fixes
406
+ // it, because an agent reading this error is the audience.
407
+ refuse('module_missing',
408
+ 'module not fetched: ' + ref.path + '@' + ref.major +
409
+ ' (run: aontu mod get)')
410
+ }
411
+
412
+ // The module's own `mod.aon` names its entry file. Read with the
413
+ // evaluator rather than a regexp: a module file is ordinary Aontu,
414
+ // and the language reading its own metadata is the point.
415
+ const main = moduleMain(pathJoin(dir, 'mod.aon'), fs, options)
416
+ const full = pathJoin(dir, main)
417
+
418
+ if (!fs.existsSync(full)) {
419
+ refuse('module_missing',
420
+ 'module not fetched: ' + ref.path + '@' + ref.major +
421
+ ' (run: aontu mod get)')
422
+ }
423
+
424
+ const src = fs.readFileSync(full, 'utf8')
425
+
426
+ if (null != expect) {
427
+ // VERIFICATION IS ALWAYS LOCAL. The registry's annotation is
428
+ // advisory; what decides is the hash of the module as it is on this
429
+ // machine, recomputed now.
430
+ const got = options.eval(src, full).hash
431
+ if (got !== expect) {
432
+ refuse('module_integrity',
433
+ 'module integrity: ' + ref.path + '@' + ref.major +
434
+ ' expected ' + expect + ' got ' + got)
435
+ }
436
+ }
437
+
438
+ return { full, src }
439
+ }
440
+
441
+
442
+ // The `mod.main` a module file declares, or the default entry name.
443
+ // The module file is ORDINARY AONTU, read by the language itself — the
444
+ // toolchain dogfooding its own evaluator rather than pattern-matching
445
+ // its own syntax with a regexp.
446
+ function moduleMain(file: string, fs: ModuleFs, options: ModuleOptions): string {
447
+ const gen: any = options.eval(fs.readFileSync(file, 'utf8'), file).gen
448
+ const main = gen?.mod?.main
449
+ return 'string' === typeof main && '' !== main ? main : DEFAULT_MAIN
450
+ }
451
+
452
+
453
+ const DEFAULT_MAIN = 'main.aon'