@intentius/chant 0.124.13 → 0.124.15

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 (991) hide show
  1. package/bin/chant +11 -0
  2. package/dist/agents/checks.js +326 -0
  3. package/dist/agents/discover.js +885 -0
  4. package/dist/agents/importer.js +17 -0
  5. package/dist/agents/index.js +12 -0
  6. package/dist/agents/types.js +19 -0
  7. package/dist/apply.js +151 -0
  8. package/dist/attrref.js +70 -0
  9. package/dist/audit/catalog.js +350 -0
  10. package/dist/audit/core.js +252 -0
  11. package/dist/audit/discover.js +633 -0
  12. package/dist/audit/fetch.js +666 -0
  13. package/dist/audit/lineage.js +91 -0
  14. package/dist/audit/nginx.js +366 -0
  15. package/dist/audit/prior-art.js +132 -0
  16. package/dist/audit/proof.js +321 -0
  17. package/dist/audit/report-html.js +194 -0
  18. package/dist/audit/report-model.js +198 -0
  19. package/dist/audit/report.js +102 -0
  20. package/dist/audit/rules-doc.js +144 -0
  21. package/dist/audit/secrets.js +405 -0
  22. package/dist/audit/terraform-state.js +140 -0
  23. package/dist/audit/wrangler.js +262 -0
  24. package/dist/behaviour-delta.js +401 -0
  25. package/dist/behaviour-engine.js +357 -0
  26. package/dist/behaviour-graph.js +83 -0
  27. package/dist/behaviour-http.js +188 -0
  28. package/dist/behaviour-kinds.js +172 -0
  29. package/dist/behaviour-overlay.js +50 -0
  30. package/dist/behaviour-predict.js +188 -0
  31. package/dist/behaviour-request.js +194 -0
  32. package/dist/behaviour.js +1325 -0
  33. package/dist/build-params.js +108 -0
  34. package/dist/build.d.ts +6 -0
  35. package/dist/build.d.ts.map +1 -1
  36. package/dist/build.js +669 -0
  37. package/dist/builder.js +44 -0
  38. package/dist/cdk/advise.js +30 -0
  39. package/dist/cdk/assembly.js +122 -0
  40. package/dist/cdk/graph.js +444 -0
  41. package/dist/cdk/tier-map.js +64 -0
  42. package/dist/cdk/types.js +24 -0
  43. package/dist/change-set.d.ts +24 -2
  44. package/dist/change-set.d.ts.map +1 -1
  45. package/dist/change-set.js +361 -0
  46. package/dist/child-project.js +21 -0
  47. package/dist/ci-jobs.d.ts +227 -0
  48. package/dist/ci-jobs.d.ts.map +1 -0
  49. package/dist/ci-jobs.js +448 -0
  50. package/dist/claimed-fields.js +135 -0
  51. package/dist/cli/build-options.js +70 -0
  52. package/dist/cli/build-params-cli.js +139 -0
  53. package/dist/cli/command-group.js +123 -0
  54. package/dist/cli/commands/audit-agents.js +326 -0
  55. package/dist/cli/commands/audit.js +571 -0
  56. package/dist/cli/commands/build.js +920 -0
  57. package/dist/cli/commands/carve-apply.js +153 -0
  58. package/dist/cli/commands/carve-bridge.js +201 -0
  59. package/dist/cli/commands/carve-emit.js +332 -0
  60. package/dist/cli/commands/carve-status.js +119 -0
  61. package/dist/cli/commands/carve.js +204 -0
  62. package/dist/cli/commands/check-lexicon-docs.js +79 -0
  63. package/dist/cli/commands/check-lexicon-examples.js +177 -0
  64. package/dist/cli/commands/check-lexicon-intrinsics.js +291 -0
  65. package/dist/cli/commands/check-lexicon-mcp.js +82 -0
  66. package/dist/cli/commands/check-lexicon-plugin.d.ts.map +1 -1
  67. package/dist/cli/commands/check-lexicon-plugin.js +104 -0
  68. package/dist/cli/commands/check-lexicon.js +766 -0
  69. package/dist/cli/commands/describe.js +85 -0
  70. package/dist/cli/commands/diff.js +175 -0
  71. package/dist/cli/commands/doctor.js +350 -0
  72. package/dist/cli/commands/import-agents.js +155 -0
  73. package/dist/cli/commands/import.js +670 -0
  74. package/dist/cli/commands/init-lexicon/templates/codegen.js +194 -0
  75. package/dist/cli/commands/init-lexicon/templates/docs.js +100 -0
  76. package/dist/cli/commands/init-lexicon/templates/examples.js +29 -0
  77. package/dist/cli/commands/init-lexicon/templates/lint.js +34 -0
  78. package/dist/cli/commands/init-lexicon/templates/lsp.js +37 -0
  79. package/dist/cli/commands/init-lexicon/templates/plugin.js +108 -0
  80. package/dist/cli/commands/init-lexicon/templates/project.js +240 -0
  81. package/dist/cli/commands/init-lexicon/templates/spec.js +56 -0
  82. package/dist/cli/commands/init-lexicon/templates/tests.js +66 -0
  83. package/dist/cli/commands/init-lexicon.js +172 -0
  84. package/dist/cli/commands/init.js +381 -0
  85. package/dist/cli/commands/lexicon-rolling-upgrade.js +145 -0
  86. package/dist/cli/commands/lexicon-surface-diff.js +145 -0
  87. package/dist/cli/commands/licenses.js +125 -0
  88. package/dist/cli/commands/lint.d.ts.map +1 -1
  89. package/dist/cli/commands/lint.js +948 -0
  90. package/dist/cli/commands/list.js +63 -0
  91. package/dist/cli/commands/migrate.d.ts.map +1 -1
  92. package/dist/cli/commands/migrate.js +423 -0
  93. package/dist/cli/commands/onboard.js +403 -0
  94. package/dist/cli/commands/pinned-upgrade.js +191 -0
  95. package/dist/cli/commands/update.js +194 -0
  96. package/dist/cli/commands/vendor.js +217 -0
  97. package/dist/cli/conflict-check.js +104 -0
  98. package/dist/cli/debug.js +8 -0
  99. package/dist/cli/format.js +104 -0
  100. package/dist/cli/handlers/build.js +267 -0
  101. package/dist/cli/handlers/carve-apply.js +30 -0
  102. package/dist/cli/handlers/carve-bridge.js +28 -0
  103. package/dist/cli/handlers/carve-emit.js +50 -0
  104. package/dist/cli/handlers/carve-status.js +26 -0
  105. package/dist/cli/handlers/carve.js +40 -0
  106. package/dist/cli/handlers/change-set.js +46 -0
  107. package/dist/cli/handlers/components.js +730 -0
  108. package/dist/cli/handlers/dev.js +113 -0
  109. package/dist/cli/handlers/emulator.js +233 -0
  110. package/dist/cli/handlers/explain.js +54 -0
  111. package/dist/cli/handlers/fan-out.js +422 -0
  112. package/dist/cli/handlers/gate-resume.js +275 -0
  113. package/dist/cli/handlers/generate.js +84 -0
  114. package/dist/cli/handlers/graph.js +969 -0
  115. package/dist/cli/handlers/init.js +102 -0
  116. package/dist/cli/handlers/lifecycle.js +1727 -0
  117. package/dist/cli/handlers/lint.js +62 -0
  118. package/dist/cli/handlers/migrate.js +48 -0
  119. package/dist/cli/handlers/misc.js +391 -0
  120. package/dist/cli/handlers/operator.js +1162 -0
  121. package/dist/cli/handlers/override-approve.js +102 -0
  122. package/dist/cli/handlers/pr.js +503 -0
  123. package/dist/cli/handlers/promote.js +342 -0
  124. package/dist/cli/handlers/run-generate.js +206 -0
  125. package/dist/cli/handlers/run-wave.js +66 -0
  126. package/dist/cli/handlers/run.js +1191 -0
  127. package/dist/cli/handlers/scenario.js +325 -0
  128. package/dist/cli/handlers/search.js +924 -0
  129. package/dist/cli/handlers/serve.js +32 -0
  130. package/dist/cli/handlers/vendor.js +154 -0
  131. package/dist/cli/index.js +3 -0
  132. package/dist/cli/is-entry-point.js +23 -0
  133. package/dist/cli/lsp/capabilities.js +28 -0
  134. package/dist/cli/lsp/diagnostics.js +34 -0
  135. package/dist/cli/lsp/server.js +329 -0
  136. package/dist/cli/main.d.ts.map +1 -1
  137. package/dist/cli/main.js +2540 -0
  138. package/dist/cli/mcp/lifecycle-tools.js +80 -0
  139. package/dist/cli/mcp/op-tools.js +306 -0
  140. package/dist/cli/mcp/resource-handlers.js +258 -0
  141. package/dist/cli/mcp/resources/context.js +85 -0
  142. package/dist/cli/mcp/server.js +353 -0
  143. package/dist/cli/mcp/tools/build.js +111 -0
  144. package/dist/cli/mcp/tools/composites.js +95 -0
  145. package/dist/cli/mcp/tools/explain.js +166 -0
  146. package/dist/cli/mcp/tools/import.js +49 -0
  147. package/dist/cli/mcp/tools/lint.js +41 -0
  148. package/dist/cli/mcp/tools/scaffold.js +111 -0
  149. package/dist/cli/mcp/tools/search.js +112 -0
  150. package/dist/cli/mcp/types.js +1 -0
  151. package/dist/cli/mcp/workspace-plugins.js +91 -0
  152. package/dist/cli/mcp/workspace-tools.d.ts +1 -1
  153. package/dist/cli/mcp/workspace-tools.js +564 -0
  154. package/dist/cli/mcp-config.js +70 -0
  155. package/dist/cli/plugins.js +313 -0
  156. package/dist/cli/registry.js +23 -0
  157. package/dist/cli/reporters/stylish.js +329 -0
  158. package/dist/cli/version.js +15 -0
  159. package/dist/cli/watch.js +79 -0
  160. package/dist/codegen/bound-property-types.js +82 -0
  161. package/dist/codegen/coverage.js +119 -0
  162. package/dist/codegen/docs-file-markers.js +60 -0
  163. package/dist/codegen/docs-pages.js +115 -0
  164. package/dist/codegen/docs-rule-scanning.js +164 -0
  165. package/dist/codegen/docs-rules.js +76 -0
  166. package/dist/codegen/docs-sections.js +117 -0
  167. package/dist/codegen/docs-sidebar.js +65 -0
  168. package/dist/codegen/docs-types.js +4 -0
  169. package/dist/codegen/docs.js +356 -0
  170. package/dist/codegen/fetch.js +340 -0
  171. package/dist/codegen/generate-post-synth-barrel.js +63 -0
  172. package/dist/codegen/generate-registry.js +71 -0
  173. package/dist/codegen/generate-runtime-index.js +56 -0
  174. package/dist/codegen/generate-typescript.js +119 -0
  175. package/dist/codegen/generate.js +115 -0
  176. package/dist/codegen/json-patch.js +141 -0
  177. package/dist/codegen/json-schema-to-ts.js +344 -0
  178. package/dist/codegen/json-schema.js +246 -0
  179. package/dist/codegen/lexicon-regen.js +412 -0
  180. package/dist/codegen/naming.js +217 -0
  181. package/dist/codegen/okf-lexicon.js +245 -0
  182. package/dist/codegen/package.js +150 -0
  183. package/dist/codegen/pinned-upgrade.d.ts.map +1 -1
  184. package/dist/codegen/pinned-upgrade.js +494 -0
  185. package/dist/codegen/registry.js +60 -0
  186. package/dist/codegen/rehype-base-url.mjs +68 -0
  187. package/dist/codegen/rolling-upgrade.js +217 -0
  188. package/dist/codegen/surface-snapshot.js +537 -0
  189. package/dist/codegen/testdata/postgres-tags.json +242 -0
  190. package/dist/codegen/topo-sort.js +36 -0
  191. package/dist/codegen/typecheck.js +115 -0
  192. package/dist/codegen/validate.js +200 -0
  193. package/dist/codegen/version-bump.js +35 -0
  194. package/dist/components/auto-release.js +143 -0
  195. package/dist/components/builders.js +47 -0
  196. package/dist/components/capability-plugin-loader.js +175 -0
  197. package/dist/components/capability-plugin-schema.js +46 -0
  198. package/dist/components/capability-plugin.js +99 -0
  199. package/dist/components/capability.js +60 -0
  200. package/dist/components/changed-files.js +72 -0
  201. package/dist/components/cli-support.js +427 -0
  202. package/dist/components/component.js +120 -0
  203. package/dist/components/component.schema.json +271 -0
  204. package/dist/components/config-defaults.js +130 -0
  205. package/dist/components/deploy-units.js +60 -0
  206. package/dist/components/discover.d.ts.map +1 -1
  207. package/dist/components/discover.js +189 -0
  208. package/dist/components/driver-output.js +86 -0
  209. package/dist/components/driver.js +728 -0
  210. package/dist/components/fan-out-output.js +165 -0
  211. package/dist/components/fan-out-record.js +94 -0
  212. package/dist/components/fan-out-run.js +373 -0
  213. package/dist/components/fan-out-support.js +60 -0
  214. package/dist/components/fan-out.js +273 -0
  215. package/dist/components/gate-plan.js +41 -0
  216. package/dist/components/gated-wave-pipeline.js +46 -0
  217. package/dist/components/index.js +24 -0
  218. package/dist/components/layers.js +28 -0
  219. package/dist/components/manifest-persistence.js +108 -0
  220. package/dist/components/pilots/alb-ecs.pilot.js +59 -0
  221. package/dist/components/pilots/dynamodb.pilot.js +51 -0
  222. package/dist/components/pilots/jar-emr.pilot.js +65 -0
  223. package/dist/components/pilots/lambda.pilot.js +52 -0
  224. package/dist/components/pilots/neo4j-fanout.pilot.js +68 -0
  225. package/dist/components/pilots/project.js +12 -0
  226. package/dist/components/pr-pipeline.d.ts.map +1 -1
  227. package/dist/components/pr-pipeline.js +135 -0
  228. package/dist/components/pr-run.js +363 -0
  229. package/dist/components/presets/ecs-fargate.js +72 -0
  230. package/dist/components/presets/index.js +28 -0
  231. package/dist/components/presets/lambda.js +34 -0
  232. package/dist/components/presets/single-host-compose.js +41 -0
  233. package/dist/components/promote.js +549 -0
  234. package/dist/components/registry.js +34 -0
  235. package/dist/components/run-progress.js +33 -0
  236. package/dist/components/sandbox/driver.d.ts.map +1 -1
  237. package/dist/components/sandbox/driver.js +93 -0
  238. package/dist/components/sandbox/run.js +115 -0
  239. package/dist/components/starter-plugin.js +104 -0
  240. package/dist/components/verbs/__tests__/mock-cloud-executor.js +68 -0
  241. package/dist/components/verbs/__tests__/mock-process-runner.js +45 -0
  242. package/dist/components/verbs/__tests__/mock-sbom-generator.js +56 -0
  243. package/dist/components/verbs/bom-writer.js +287 -0
  244. package/dist/components/verbs/build-archive.js +188 -0
  245. package/dist/components/verbs/build.js +142 -0
  246. package/dist/components/verbs/cloud-executor.js +110 -0
  247. package/dist/components/verbs/component-bom.js +125 -0
  248. package/dist/components/verbs/ensure-secret.js +50 -0
  249. package/dist/components/verbs/index.js +61 -0
  250. package/dist/components/verbs/license-policy.js +75 -0
  251. package/dist/components/verbs/lockfile-sbom-generator.js +191 -0
  252. package/dist/components/verbs/process-runner.js +78 -0
  253. package/dist/components/verbs/r2-sync.js +93 -0
  254. package/dist/components/verbs/reproducibility.js +62 -0
  255. package/dist/components/verbs/run-agent.js +577 -0
  256. package/dist/components/verbs/sbom-generator.js +119 -0
  257. package/dist/components/verbs/sbom.js +97 -0
  258. package/dist/components/verbs/shell.js +32 -0
  259. package/dist/components/verbs/sign.js +221 -0
  260. package/dist/components/verbs/stub.js +28 -0
  261. package/dist/components/verbs/tool-sbom-generator.js +154 -0
  262. package/dist/components/verbs/verify.js +112 -0
  263. package/dist/components/verbs/vex.js +131 -0
  264. package/dist/components/verbs/vuln-gate.js +152 -0
  265. package/dist/components/verbs/vuln-scan.js +288 -0
  266. package/dist/components/verbs/wait-verify.js +106 -0
  267. package/dist/components/verbs/wrangler.js +179 -0
  268. package/dist/components/wave-plan.js +102 -0
  269. package/dist/composite.d.ts +10 -2
  270. package/dist/composite.d.ts.map +1 -1
  271. package/dist/composite.js +313 -0
  272. package/dist/config-import.d.ts.map +1 -1
  273. package/dist/config-import.js +22 -0
  274. package/dist/config-sandbox.js +122 -0
  275. package/dist/config-static.js +206 -0
  276. package/dist/config.js +605 -0
  277. package/dist/content-digest.js +23 -0
  278. package/dist/declarable.js +114 -0
  279. package/dist/deep-observation.js +437 -0
  280. package/dist/detectLexicon.js +54 -0
  281. package/dist/discovery/cache.js +70 -0
  282. package/dist/discovery/collect.d.ts +2 -1
  283. package/dist/discovery/collect.d.ts.map +1 -1
  284. package/dist/discovery/collect.js +223 -0
  285. package/dist/discovery/cycles.js +95 -0
  286. package/dist/discovery/entity-wire-codec.js +391 -0
  287. package/dist/discovery/entity-wire.js +42 -0
  288. package/dist/discovery/files.js +121 -0
  289. package/dist/discovery/fold-import.d.ts.map +1 -1
  290. package/dist/discovery/fold-import.js +4073 -0
  291. package/dist/discovery/fold-rank.js +292 -0
  292. package/dist/discovery/graph.js +115 -0
  293. package/dist/discovery/host-composite.d.ts +35 -0
  294. package/dist/discovery/host-composite.d.ts.map +1 -0
  295. package/dist/discovery/host-composite.js +93 -0
  296. package/dist/discovery/import.d.ts.map +1 -1
  297. package/dist/discovery/import.js +65 -0
  298. package/dist/discovery/index.d.ts +9 -0
  299. package/dist/discovery/index.d.ts.map +1 -1
  300. package/dist/discovery/index.js +333 -0
  301. package/dist/discovery/param-deps.js +487 -0
  302. package/dist/discovery/resolve.js +39 -0
  303. package/dist/discovery/sandbox/bundle.js +214 -0
  304. package/dist/discovery/sandbox/child-errors.js +26 -0
  305. package/dist/discovery/sandbox/config-run.js +100 -0
  306. package/dist/discovery/sandbox/config-wire.js +183 -0
  307. package/dist/discovery/sandbox/driver.d.ts.map +1 -1
  308. package/dist/discovery/sandbox/driver.js +333 -0
  309. package/dist/discovery/sandbox/fork.js +229 -0
  310. package/dist/discovery/sandbox/policy-run.js +142 -0
  311. package/dist/discovery/sandbox/policy-wire.js +152 -0
  312. package/dist/discovery/sandbox/run.js +77 -0
  313. package/dist/discovery/specifier-resolve.js +320 -0
  314. package/dist/discovery/walk.js +284 -0
  315. package/dist/effect-receipt.js +340 -0
  316. package/dist/env.js +55 -0
  317. package/dist/errors.js +62 -0
  318. package/dist/fold/fold.js +1434 -0
  319. package/dist/fold/foldable-helpers.js +229 -0
  320. package/dist/fold/subset.js +696 -0
  321. package/dist/fold-provenance.js +414 -0
  322. package/dist/gate-decision.d.ts +20 -9
  323. package/dist/gate-decision.d.ts.map +1 -1
  324. package/dist/gate-decision.js +313 -0
  325. package/dist/gate-resume-run.js +154 -0
  326. package/dist/gated-waves.js +165 -0
  327. package/dist/governance.js +43 -0
  328. package/dist/graph-declared.js +45 -0
  329. package/dist/graph-detail.js +207 -0
  330. package/dist/graph-dot.js +54 -0
  331. package/dist/graph-effective.js +107 -0
  332. package/dist/graph-ir.js +623 -0
  333. package/dist/graph-layout.js +184 -0
  334. package/dist/graph-lens.js +105 -0
  335. package/dist/graph-mermaid.js +83 -0
  336. package/dist/graph-ops.js +39 -0
  337. package/dist/graph-refs.js +156 -0
  338. package/dist/held-elsewhere.js +87 -0
  339. package/dist/identity.js +251 -0
  340. package/dist/import/base-parser.js +34 -0
  341. package/dist/import/embedded.js +255 -0
  342. package/dist/import/generator.js +1 -0
  343. package/dist/import/ir-utils.js +85 -0
  344. package/dist/import/parser-options.js +45 -0
  345. package/dist/import/parser.js +1 -0
  346. package/dist/index.js +118 -0
  347. package/dist/intrinsic-interpolation.js +73 -0
  348. package/dist/intrinsic.js +31 -0
  349. package/dist/join-key.js +35 -0
  350. package/dist/kubectl-context.js +150 -0
  351. package/dist/lexicon-config.js +58 -0
  352. package/dist/lexicon-integrity.js +47 -0
  353. package/dist/lexicon-manifest.js +70 -0
  354. package/dist/lexicon-module.d.ts.map +1 -1
  355. package/dist/lexicon-module.js +241 -0
  356. package/dist/lexicon-output.js +218 -0
  357. package/dist/lexicon-plugin-helpers.js +104 -0
  358. package/dist/lexicon-schema.js +136 -0
  359. package/dist/lexicon.js +130 -0
  360. package/dist/lifecycle/affected.js +224 -0
  361. package/dist/lifecycle/assert-live.js +110 -0
  362. package/dist/lifecycle/build-ledger-store.js +160 -0
  363. package/dist/lifecycle/build-ledger.js +155 -0
  364. package/dist/lifecycle/change-set.js +356 -0
  365. package/dist/lifecycle/converge-ledger.js +161 -0
  366. package/dist/lifecycle/deep-diff.js +233 -0
  367. package/dist/lifecycle/deep-observe.js +147 -0
  368. package/dist/lifecycle/digest.js +143 -0
  369. package/dist/lifecycle/disruption.js +135 -0
  370. package/dist/lifecycle/gate-applied.js +113 -0
  371. package/dist/lifecycle/gate-audit.d.ts +69 -2
  372. package/dist/lifecycle/gate-audit.d.ts.map +1 -1
  373. package/dist/lifecycle/gate-audit.js +415 -0
  374. package/dist/lifecycle/gate-ledger-select.js +89 -0
  375. package/dist/lifecycle/gate-ledger.d.ts +24 -0
  376. package/dist/lifecycle/gate-ledger.d.ts.map +1 -1
  377. package/dist/lifecycle/gate-ledger.js +201 -0
  378. package/dist/lifecycle/gate-origin.js +117 -0
  379. package/dist/lifecycle/gate-pending.js +58 -0
  380. package/dist/lifecycle/git.js +1182 -0
  381. package/dist/lifecycle/identity.js +58 -0
  382. package/dist/lifecycle/index.js +26 -0
  383. package/dist/lifecycle/lease.js +283 -0
  384. package/dist/lifecycle/ledger-port.js +1 -0
  385. package/dist/lifecycle/legacy-digest.js +91 -0
  386. package/dist/lifecycle/live-diff.js +274 -0
  387. package/dist/lifecycle/local-note.js +15 -0
  388. package/dist/lifecycle/member-ledger.js +122 -0
  389. package/dist/lifecycle/observation-baseline.js +155 -0
  390. package/dist/lifecycle/observe.js +375 -0
  391. package/dist/lifecycle/oras-referrer-lookup.js +82 -0
  392. package/dist/lifecycle/override-ledger.js +54 -0
  393. package/dist/lifecycle/plan-digest.js +110 -0
  394. package/dist/lifecycle/plan-ledger.js +129 -0
  395. package/dist/lifecycle/receipt-plan.js +192 -0
  396. package/dist/lifecycle/release-ledger.js +155 -0
  397. package/dist/lifecycle/release-read.js +64 -0
  398. package/dist/lifecycle/replay.js +193 -0
  399. package/dist/lifecycle/rollback.js +102 -0
  400. package/dist/lifecycle/run-ledger.js +183 -0
  401. package/dist/lifecycle/scenario-eval.js +204 -0
  402. package/dist/lifecycle/scenario.js +214 -0
  403. package/dist/lifecycle/snapshot.js +239 -0
  404. package/dist/lifecycle/status.js +447 -0
  405. package/dist/lifecycle/symptoms.js +117 -0
  406. package/dist/lifecycle/teardown.js +255 -0
  407. package/dist/lifecycle/types.js +1 -0
  408. package/dist/lifecycle/unobserved-gate.js +33 -0
  409. package/dist/lifecycle/wave-plans.js +68 -0
  410. package/dist/lifecycle/work-item-id.js +36 -0
  411. package/dist/lifecycle/work-lease.js +393 -0
  412. package/dist/lint/component-checks.js +87 -0
  413. package/dist/lint/config.js +483 -0
  414. package/dist/lint/declarative.js +122 -0
  415. package/dist/lint/discover.d.ts.map +1 -1
  416. package/dist/lint/discover.js +138 -0
  417. package/dist/lint/engine.js +246 -0
  418. package/dist/lint/knowledge-checks.js +64 -0
  419. package/dist/lint/named-checks.js +22 -0
  420. package/dist/lint/output-checks.js +87 -0
  421. package/dist/lint/output-docs.js +163 -0
  422. package/dist/lint/parser.js +33 -0
  423. package/dist/lint/pipeline-change-gate.js +70 -0
  424. package/dist/lint/policy-import.d.ts +1 -1
  425. package/dist/lint/policy-import.d.ts.map +1 -1
  426. package/dist/lint/policy-import.js +62 -0
  427. package/dist/lint/policy-sandbox.js +86 -0
  428. package/dist/lint/policy.js +122 -0
  429. package/dist/lint/post-synth.js +87 -0
  430. package/dist/lint/presets/relaxed.json +26 -0
  431. package/dist/lint/presets/strict.json +24 -24
  432. package/dist/lint/receipt-checks.js +83 -0
  433. package/dist/lint/rule-loader.d.ts.map +1 -1
  434. package/dist/lint/rule-loader.js +89 -0
  435. package/dist/lint/rule-registry.js +86 -0
  436. package/dist/lint/rule.js +1 -0
  437. package/dist/lint/rules/__fixtures__/comp/comp000-discovery-error/broken-a.component.js +7 -0
  438. package/dist/lint/rules/__fixtures__/comp/comp000-discovery-error/broken-b.component.js +7 -0
  439. package/dist/lint/rules/__fixtures__/comp/comp001/fail/orphan-lib.component.js +9 -0
  440. package/dist/lint/rules/__fixtures__/comp/comp001/pass/emr-job.component.js +11 -0
  441. package/dist/lint/rules/__fixtures__/comp/comp001/pass/jar-lib.component.js +9 -0
  442. package/dist/lint/rules/__fixtures__/comp/comp002/fail/search-service.component.js +25 -0
  443. package/dist/lint/rules/__fixtures__/comp/comp002/pass/jar-lib.component.js +8 -0
  444. package/dist/lint/rules/__fixtures__/comp/comp002/pass/search-service.component.js +21 -0
  445. package/dist/lint/rules/__fixtures__/comp/comp003/fail/migrator.component.js +8 -0
  446. package/dist/lint/rules/__fixtures__/comp/comp003/pass/agent-turn.component.js +24 -0
  447. package/dist/lint/rules/__fixtures__/comp/comp003/pass/migrator.component.js +17 -0
  448. package/dist/lint/rules/__fixtures__/comp/comp003/pass/orders-table.component.js +8 -0
  449. package/dist/lint/rules/__fixtures__/comp/comp004/fail/neo4j-cluster.component.js +18 -0
  450. package/dist/lint/rules/__fixtures__/comp/comp004/pass/neo4j-cluster.component.js +20 -0
  451. package/dist/lint/rules/__fixtures__/comp/comp004/pass/orders-table.component.js +8 -0
  452. package/dist/lint/rules/__fixtures__/comp/comp005/fail/search-service.component.js +12 -0
  453. package/dist/lint/rules/__fixtures__/comp/comp005/pass/search-service.component.js +16 -0
  454. package/dist/lint/rules/__fixtures__/comp/comp006/fail/legacy-tool.component.js +8 -0
  455. package/dist/lint/rules/__fixtures__/comp/comp006/pass/legacy-tool.component.js +12 -0
  456. package/dist/lint/rules/__fixtures__/comp/comp007/fail/inventory-table.component.js +11 -0
  457. package/dist/lint/rules/__fixtures__/comp/comp007/fail/orders-table.component.js +11 -0
  458. package/dist/lint/rules/__fixtures__/comp/comp007/pass/orders-table.component.js +10 -0
  459. package/dist/lint/rules/__fixtures__/comp/comp007/pass/search-service.component.js +16 -0
  460. package/dist/lint/rules/comp/comp001-publishes-never-applies.js +69 -0
  461. package/dist/lint/rules/comp/comp002-dangling-wiring-ref.js +84 -0
  462. package/dist/lint/rules/comp/comp003-mutating-no-rollback.js +103 -0
  463. package/dist/lint/rules/comp/comp004-gate-needs-durable-runtime.js +70 -0
  464. package/dist/lint/rules/comp/comp005-capability-kind-is-noun.js +109 -0
  465. package/dist/lint/rules/comp/comp006-shell-needs-reason.js +49 -0
  466. package/dist/lint/rules/comp/comp007-composition-sprawl.js +64 -0
  467. package/dist/lint/rules/comp/index.js +33 -0
  468. package/dist/lint/rules/comp/support.js +129 -0
  469. package/dist/lint/rules/composite-scope.js +51 -0
  470. package/dist/lint/rules/cor017-composite-name-match.js +93 -0
  471. package/dist/lint/rules/cor018-composite-prefer-lexicon-type.js +136 -0
  472. package/dist/lint/rules/cor021-env-literal-name.js +100 -0
  473. package/dist/lint/rules/cor022-receipt-leaf.js +118 -0
  474. package/dist/lint/rules/cor024-receipt-secret-pointer.js +199 -0
  475. package/dist/lint/rules/declarable-naming-convention.js +62 -0
  476. package/dist/lint/rules/evl001-non-literal-expression.js +66 -0
  477. package/dist/lint/rules/evl002-control-flow-resource.js +69 -0
  478. package/dist/lint/rules/evl003-dynamic-property-access.js +44 -0
  479. package/dist/lint/rules/evl004-spread-non-const.js +94 -0
  480. package/dist/lint/rules/evl005-resource-block-body.js +41 -0
  481. package/dist/lint/rules/evl007-invalid-siblings.js +105 -0
  482. package/dist/lint/rules/evl009-composite-no-constant.js +158 -0
  483. package/dist/lint/rules/evl010-composite-no-transform.js +59 -0
  484. package/dist/lint/rules/evl011-symbolic-in-template.js +106 -0
  485. package/dist/lint/rules/export-required.js +115 -0
  486. package/dist/lint/rules/file-declarable-limit.js +88 -0
  487. package/dist/lint/rules/flat-declarations.js +62 -0
  488. package/dist/lint/rules/index.js +90 -0
  489. package/dist/lint/rules/no-cyclic-declarable-ref.js +138 -0
  490. package/dist/lint/rules/no-redundant-type-import.js +76 -0
  491. package/dist/lint/rules/no-redundant-value-cast.js +41 -0
  492. package/dist/lint/rules/no-string-ref.js +59 -0
  493. package/dist/lint/rules/no-unused-declarable-import.js +75 -0
  494. package/dist/lint/rules/no-unused-declarable.js +142 -0
  495. package/dist/lint/rules/op/index.js +28 -0
  496. package/dist/lint/rules/op/ops012-activity-contract.js +54 -0
  497. package/dist/lint/rules/op/ops013-step-output-ref.js +88 -0
  498. package/dist/lint/rules/op/ops014-converge-rule-refusals.js +114 -0
  499. package/dist/lint/rules/op/ops015-gate-approval.js +45 -0
  500. package/dist/lint/rules/op/support.js +11 -0
  501. package/dist/lint/rules/op/sys010-key-over-plain-http.js +100 -0
  502. package/dist/lint/rules/property-kind.js +56 -0
  503. package/dist/lint/rules/single-concern-file.js +71 -0
  504. package/dist/lint/rules/sys001-no-literal-key.js +60 -0
  505. package/dist/lint/selectors.js +168 -0
  506. package/dist/lint/suppressions.js +129 -0
  507. package/dist/lint/typescript.js +17 -0
  508. package/dist/live-endpoint.js +113 -0
  509. package/dist/live-read-session.js +76 -0
  510. package/dist/lsp/lexicon-providers.js +163 -0
  511. package/dist/lsp/types.js +5 -0
  512. package/dist/managed-fields.js +293 -0
  513. package/dist/mcp/types.js +4 -0
  514. package/dist/notify-verify.js +0 -1
  515. package/dist/notify.js +0 -1
  516. package/dist/notify.schema.json +72 -0
  517. package/dist/observation.js +266 -0
  518. package/dist/okf-read.js +134 -0
  519. package/dist/okf.js +275 -0
  520. package/dist/op/__fixtures__/decide-stub-backend.js +125 -0
  521. package/dist/op/activities/activity-contracts.d.ts +1 -1
  522. package/dist/op/activities/activity-contracts.js +313 -0
  523. package/dist/op/activities/apply.js +582 -0
  524. package/dist/op/activities/build.js +18 -0
  525. package/dist/op/activities/change-coverage.js +40 -0
  526. package/dist/op/activities/change-set.d.ts +8 -3
  527. package/dist/op/activities/change-set.d.ts.map +1 -1
  528. package/dist/op/activities/change-set.js +67 -0
  529. package/dist/op/activities/converge.js +492 -0
  530. package/dist/op/activities/decide.js +211 -0
  531. package/dist/op/activities/env-teardown.js +110 -0
  532. package/dist/op/activities/factory.js +591 -0
  533. package/dist/op/activities/forge-pull-request.js +82 -0
  534. package/dist/op/activities/gh-host.js +78 -0
  535. package/dist/op/activities/guard-validate.js +165 -0
  536. package/dist/op/activities/http-check.js +40 -0
  537. package/dist/op/activities/index.js +59 -0
  538. package/dist/op/activities/lexicon-upgrade.js +629 -0
  539. package/dist/op/activities/lifecycle.js +109 -0
  540. package/dist/op/activities/pipeline-audit.js +147 -0
  541. package/dist/op/activities/policy.js +26 -0
  542. package/dist/op/activities/predict-behaviour.js +341 -0
  543. package/dist/op/activities/propose-upgrade.js +165 -0
  544. package/dist/op/activities/reconcile.d.ts +28 -16
  545. package/dist/op/activities/reconcile.d.ts.map +1 -1
  546. package/dist/op/activities/reconcile.js +1011 -0
  547. package/dist/op/activities/shell.js +118 -0
  548. package/dist/op/activities/source-release.js +97 -0
  549. package/dist/op/activities/source-rollback.js +165 -0
  550. package/dist/op/activities/teardown.js +17 -0
  551. package/dist/op/activities/util.js +6 -0
  552. package/dist/op/activities/wait.js +51 -0
  553. package/dist/op/activities/work-evidence.js +35 -0
  554. package/dist/op/activities/workflow-audit.js +133 -0
  555. package/dist/op/activity-contract-registry.js +106 -0
  556. package/dist/op/activity-contract.js +232 -0
  557. package/dist/op/activity-failure.js +31 -0
  558. package/dist/op/activity-profiles.js +129 -0
  559. package/dist/op/activity-registry.js +105 -0
  560. package/dist/op/activity-runtime.js +28 -0
  561. package/dist/op/builders.d.ts +1 -1
  562. package/dist/op/builders.d.ts.map +1 -1
  563. package/dist/op/builders.js +801 -0
  564. package/dist/op/change-signal.js +118 -0
  565. package/dist/op/composites/apply-op.js +151 -0
  566. package/dist/op/composites/behaviour-op.js +65 -0
  567. package/dist/op/composites/converge-op.js +141 -0
  568. package/dist/op/composites/index.js +19 -0
  569. package/dist/op/composites/lexicon-upgrade-op.js +107 -0
  570. package/dist/op/composites/pipeline-audit-op.js +50 -0
  571. package/dist/op/composites/reconcile-op.js +84 -0
  572. package/dist/op/composites/watch-op.js +69 -0
  573. package/dist/op/composites/workflow-audit-op.js +57 -0
  574. package/dist/op/converge-rule.js +205 -0
  575. package/dist/op/cron.js +146 -0
  576. package/dist/op/decide-backend.js +181 -0
  577. package/dist/op/decide-config.js +68 -0
  578. package/dist/op/decide-read-inputs.js +141 -0
  579. package/dist/op/discover.d.ts.map +1 -1
  580. package/dist/op/discover.js +213 -0
  581. package/dist/op/duration.js +21 -0
  582. package/dist/op/emulator-freshness.js +80 -0
  583. package/dist/op/emulator-lifecycle.js +269 -0
  584. package/dist/op/factory.js +83 -0
  585. package/dist/op/gate-approval.js +172 -0
  586. package/dist/op/gate-name.js +86 -0
  587. package/dist/op/gate-plan-context.js +167 -0
  588. package/dist/op/gate-point-run.js +67 -0
  589. package/dist/op/gate-point.js +111 -0
  590. package/dist/op/gate-resume-plan.js +155 -0
  591. package/dist/op/gate-resume.js +31 -0
  592. package/dist/op/gate-review.js +290 -0
  593. package/dist/op/gate-run-locator.js +85 -0
  594. package/dist/op/gate-summary.js +100 -0
  595. package/dist/op/gate-wait.js +56 -0
  596. package/dist/op/gate.js +449 -0
  597. package/dist/op/generate-pipeline.js +124 -0
  598. package/dist/op/index.js +51 -0
  599. package/dist/op/lifecycle-receipt-store.js +45 -0
  600. package/dist/op/local-executor.js +1164 -0
  601. package/dist/op/local-output.js +140 -0
  602. package/dist/op/op-ir.js +317 -0
  603. package/dist/op/op-verb-class.js +131 -0
  604. package/dist/op/op-waves-notify.js +136 -0
  605. package/dist/op/op-waves-run.d.ts +1 -1
  606. package/dist/op/op-waves-run.d.ts.map +1 -1
  607. package/dist/op/op-waves-run.js +1657 -0
  608. package/dist/op/op-waves.js +409 -0
  609. package/dist/op/operator.js +743 -0
  610. package/dist/op/receipt-store.js +94 -0
  611. package/dist/op/resource.js +23 -0
  612. package/dist/op/run-context.js +28 -0
  613. package/dist/op/run-live.js +256 -0
  614. package/dist/op/runtime.js +21 -0
  615. package/dist/op/runtimes/local.js +195 -0
  616. package/dist/op/source-archive.js +131 -0
  617. package/dist/op/step-output-ref.js +397 -0
  618. package/dist/op/steward-beside.js +169 -0
  619. package/dist/op/steward-points.js +138 -0
  620. package/dist/op/steward-turn.js +59 -0
  621. package/dist/op/steward.js +317 -0
  622. package/dist/op/types.js +25 -0
  623. package/dist/op/work-lease-decl.js +81 -0
  624. package/dist/op/work-lease-run.js +393 -0
  625. package/dist/override-gate.js +284 -0
  626. package/dist/owner-chain.js +74 -0
  627. package/dist/ownership.js +92 -0
  628. package/dist/params.js +61 -0
  629. package/dist/plan-summary.js +711 -0
  630. package/dist/point-decision.js +211 -0
  631. package/dist/pr-forge.js +810 -0
  632. package/dist/pr-loop.js +203 -0
  633. package/dist/project-codegen.js +191 -0
  634. package/dist/project-root.js +74 -0
  635. package/dist/project-validation.js +50 -0
  636. package/dist/provenance.js +157 -0
  637. package/dist/pseudo-parameter.js +40 -0
  638. package/dist/reconcile.js +450 -0
  639. package/dist/resource-attributes.js +33 -0
  640. package/dist/runtime-adapter.js +77 -0
  641. package/dist/runtime.js +66 -0
  642. package/dist/secret-materialization.js +153 -0
  643. package/dist/secret-provenance.js +250 -0
  644. package/dist/serializer-walker.js +112 -0
  645. package/dist/serializer.js +1 -0
  646. package/dist/sort.js +45 -0
  647. package/dist/stack-output.js +115 -0
  648. package/dist/telemetry-attribution.js +149 -0
  649. package/dist/terraform/__fixtures__/build-graph.js +63 -0
  650. package/dist/terraform/adopt-state.js +40 -0
  651. package/dist/terraform/aws-resources.js +384 -0
  652. package/dist/terraform/bridge.js +204 -0
  653. package/dist/terraform/carve-provider.js +155 -0
  654. package/dist/terraform/carve.js +138 -0
  655. package/dist/terraform/data-source-shape.js +57 -0
  656. package/dist/terraform/emit-source.js +36 -0
  657. package/dist/terraform/excise.js +123 -0
  658. package/dist/terraform/graduate.js +128 -0
  659. package/dist/terraform/graph.js +435 -0
  660. package/dist/terraform/hcl2json.js +60 -0
  661. package/dist/terraform/manifest.js +149 -0
  662. package/dist/terraform/parse.js +81 -0
  663. package/dist/terraform/providers/aws.js +146 -0
  664. package/dist/terraform/providers/gcp.js +294 -0
  665. package/dist/terraform/providers/index.js +17 -0
  666. package/dist/terraform/providers/kubernetes.js +253 -0
  667. package/dist/terraform/score.js +151 -0
  668. package/dist/terraform/state.js +67 -0
  669. package/dist/terraform/tier-map.js +74 -0
  670. package/dist/terraform/types.js +13 -0
  671. package/dist/terraform/unified-diff.js +132 -0
  672. package/dist/testing.js +295 -0
  673. package/dist/toml-emit.js +153 -0
  674. package/dist/toml-parse.js +355 -0
  675. package/dist/toml-utils.js +60 -0
  676. package/dist/toml.js +428 -0
  677. package/dist/ts-loader.d.ts +46 -0
  678. package/dist/ts-loader.d.ts.map +1 -0
  679. package/dist/ts-loader.js +103 -0
  680. package/dist/types.js +1 -0
  681. package/dist/utils.js +61 -0
  682. package/dist/validation.js +20 -0
  683. package/dist/vendor/js-yaml-load.js +1662 -0
  684. package/dist/workspace/__fixtures__/contract-repo.d.ts +7 -0
  685. package/dist/workspace/__fixtures__/contract-repo.d.ts.map +1 -1
  686. package/dist/workspace/__fixtures__/contract-repo.js +119 -0
  687. package/dist/workspace/__fixtures__/decide-workspace.js +112 -0
  688. package/dist/workspace/__fixtures__/sessions.d.ts +1 -1
  689. package/dist/workspace/__fixtures__/sessions.d.ts.map +1 -1
  690. package/dist/workspace/__fixtures__/sessions.js +91 -0
  691. package/dist/workspace/__fixtures__/two-member-workspace.js +34 -0
  692. package/dist/workspace/agent-cli.js +99 -0
  693. package/dist/workspace/agent.schema.json +390 -0
  694. package/dist/workspace/answers-ledger.js +89 -0
  695. package/dist/workspace/box-cli.js +142 -0
  696. package/dist/workspace/box-factory-write.js +196 -0
  697. package/dist/workspace/box-factory-write.schema.json +342 -0
  698. package/dist/workspace/box-factory.js +88 -0
  699. package/dist/workspace/box-intent.js +126 -0
  700. package/dist/workspace/box-isolation.js +169 -0
  701. package/dist/workspace/box-listing-write.schema.json +204 -0
  702. package/dist/workspace/box-listing.js +298 -0
  703. package/dist/workspace/box-publish.js +352 -0
  704. package/dist/workspace/box-publish.schema.json +498 -0
  705. package/dist/workspace/box-services.js +39 -0
  706. package/dist/workspace/broker-protocol.js +359 -0
  707. package/dist/workspace/broker-protocol.schema.json +228 -0
  708. package/dist/workspace/change-set.schema.json +462 -0
  709. package/dist/workspace/changes-cli.js +56 -0
  710. package/dist/workspace/changes.js +446 -0
  711. package/dist/workspace/changes.schema.json +642 -0
  712. package/dist/workspace/chant-migrations/chud-lexicon-exit-fly-site.js +198 -0
  713. package/dist/workspace/chant-migrations/chud-lexicon-exit-live-names.js +155 -0
  714. package/dist/workspace/chant-migrations/chud-lexicon-exit-rollback.js +214 -0
  715. package/dist/workspace/chant-migrations/chud-lexicon-exit-ship-inputs.js +180 -0
  716. package/dist/workspace/chant-migrations/chud-lexicon-exit-ship-policy.js +99 -0
  717. package/dist/workspace/chant-migrations/chud-lexicon-exit.js +847 -0
  718. package/dist/workspace/chant-migrations.js +113 -0
  719. package/dist/workspace/check.schema.json +433 -0
  720. package/dist/workspace/checks/box-isolation.js +62 -0
  721. package/dist/workspace/checks/boxes.js +491 -0
  722. package/dist/workspace/checks/diagrams.js +104 -0
  723. package/dist/workspace/checks/generated.js +419 -0
  724. package/dist/workspace/checks/ledgers.js +212 -0
  725. package/dist/workspace/checks/links.js +191 -0
  726. package/dist/workspace/checks/live.js +100 -0
  727. package/dist/workspace/checks/pipelines.js +188 -0
  728. package/dist/workspace/checks/records.js +169 -0
  729. package/dist/workspace/checks.js +426 -0
  730. package/dist/workspace/ci-green-cli.js +125 -0
  731. package/dist/workspace/ci-green-forge.js +83 -0
  732. package/dist/workspace/ci-green-workflow.d.ts +3 -1
  733. package/dist/workspace/ci-green-workflow.d.ts.map +1 -1
  734. package/dist/workspace/ci-green-workflow.js +202 -0
  735. package/dist/workspace/ci-green.js +325 -0
  736. package/dist/workspace/ci-last-green.schema.json +158 -0
  737. package/dist/workspace/class-data.d.ts +84 -0
  738. package/dist/workspace/class-data.d.ts.map +1 -0
  739. package/dist/workspace/class-data.js +238 -0
  740. package/dist/workspace/compose-graph.js +235 -0
  741. package/dist/workspace/compose-reports.js +146 -0
  742. package/dist/workspace/composites.js +264 -0
  743. package/dist/workspace/composites.schema.json +617 -0
  744. package/dist/workspace/conformance/__fixture__/app/package.json +7 -0
  745. package/dist/workspace/conformance/__fixture__/app/src/server.mjs +29 -0
  746. package/dist/workspace/conformance/__fixture__/decisions/decision.kind.mjs +54 -0
  747. package/dist/workspace/conformance/__fixture__/decisions/decision.schema.json +512 -0
  748. package/dist/workspace/conformance/__fixture__/decisions/fix-001-how-the-app-is-deployed.md +40 -0
  749. package/dist/workspace/conformance/__fixture__/delivery/chant.config.ts +7 -0
  750. package/dist/workspace/conformance/__fixture__/delivery/lexicon/index.ts +26 -0
  751. package/dist/workspace/conformance/__fixture__/delivery/package.json +7 -0
  752. package/dist/workspace/conformance/__fixture__/delivery/src/app.component.ts +14 -0
  753. package/dist/workspace/conformance/__fixture__/delivery/src/app.ts +4 -0
  754. package/dist/workspace/conformance/__fixtures__/reference-broker.js +205 -0
  755. package/dist/workspace/conformance/__writer_fixture__/answers/answer.kind.mjs +32 -0
  756. package/dist/workspace/conformance/__writer_fixture__/answers/answer.schema.json +135 -0
  757. package/dist/workspace/conformance/__writer_fixture__/contracts/contract.kind.mjs +37 -0
  758. package/dist/workspace/conformance/__writer_fixture__/contracts/contract.schema.json +213 -0
  759. package/dist/workspace/conformance/__writer_fixture__/decisions/points.json +228 -0
  760. package/dist/workspace/conformance/__writer_fixture__/drivers/driver.kind.mjs +15 -0
  761. package/dist/workspace/conformance/__writer_fixture__/drivers/driver.schema.json +38 -0
  762. package/dist/workspace/conformance/__writer_fixture__/sessions/session.kind.mjs +54 -0
  763. package/dist/workspace/conformance/__writer_fixture__/sessions/session.schema.json +289 -0
  764. package/dist/workspace/conformance/__writer_fixture__/work/W-001-the-writer-suite-works-this-item.md +25 -0
  765. package/dist/workspace/conformance/__writer_fixture__/work/work.kind.mjs +60 -0
  766. package/dist/workspace/conformance/__writer_fixture__/work/work.schema.json +603 -0
  767. package/dist/workspace/conformance/broker.js +736 -0
  768. package/dist/workspace/conformance/index.js +673 -0
  769. package/dist/workspace/conformance/vitest.js +108 -0
  770. package/dist/workspace/conformance/writer.js +1080 -0
  771. package/dist/workspace/decide.js +879 -0
  772. package/dist/workspace/decision-points.schema.json +135 -135
  773. package/dist/workspace/declaration-write-cli.js +74 -0
  774. package/dist/workspace/declaration-write.js +238 -0
  775. package/dist/workspace/declaration.js +938 -0
  776. package/dist/workspace/declaration.schema.json +1413 -1413
  777. package/dist/workspace/declared-kinds.js +106 -0
  778. package/dist/workspace/environments.js +164 -0
  779. package/dist/workspace/evidence.schema.json +279 -0
  780. package/dist/workspace/export-cli.js +185 -0
  781. package/dist/workspace/export.js +499 -0
  782. package/dist/workspace/factory-rules.js +175 -0
  783. package/dist/workspace/generated-files.js +92 -0
  784. package/dist/workspace/glob.js +45 -0
  785. package/dist/workspace/graph-cache.js +352 -0
  786. package/dist/workspace/graph-cli.js +348 -0
  787. package/dist/workspace/graph.schema.json +1338 -0
  788. package/dist/workspace/identity-gate-rule.js +75 -0
  789. package/dist/workspace/identity.js +126 -0
  790. package/dist/workspace/import.js +283 -0
  791. package/dist/workspace/init.js +620 -0
  792. package/dist/workspace/intent-cli.js +263 -0
  793. package/dist/workspace/intent-joins.js +201 -0
  794. package/dist/workspace/intent-record.js +352 -0
  795. package/dist/workspace/intent-record.schema.json +864 -0
  796. package/dist/workspace/intent.js +1582 -0
  797. package/dist/workspace/intent.schema.json +2420 -0
  798. package/dist/workspace/json-edit.js +221 -0
  799. package/dist/workspace/jsonc.js +235 -0
  800. package/dist/workspace/kind-readers.js +103 -0
  801. package/dist/workspace/kinds.d.ts +3 -7
  802. package/dist/workspace/kinds.d.ts.map +1 -1
  803. package/dist/workspace/kinds.js +553 -0
  804. package/dist/workspace/lexicon-symbols.js +112 -0
  805. package/dist/workspace/lineage-adopt-cli.js +102 -0
  806. package/dist/workspace/lineage-adopt.js +360 -0
  807. package/dist/workspace/lineage-check.js +282 -0
  808. package/dist/workspace/lineage-cli.js +121 -0
  809. package/dist/workspace/lineage-hash-index.js +284 -0
  810. package/dist/workspace/lineage-init.js +507 -0
  811. package/dist/workspace/lineage-lock.js +387 -0
  812. package/dist/workspace/lineage-merge.js +45 -0
  813. package/dist/workspace/lineage-migrations.d.ts.map +1 -1
  814. package/dist/workspace/lineage-migrations.js +329 -0
  815. package/dist/workspace/lineage-provenance.js +36 -0
  816. package/dist/workspace/lineage-update.js +138 -0
  817. package/dist/workspace/lineage-upgrade-cli.js +131 -0
  818. package/dist/workspace/lineage-upgrade.js +889 -0
  819. package/dist/workspace/lineage-vendor.js +171 -0
  820. package/dist/workspace/lineage-version.js +128 -0
  821. package/dist/workspace/lineage-versions.js +275 -0
  822. package/dist/workspace/links.js +330 -0
  823. package/dist/workspace/ls-generated.js +95 -0
  824. package/dist/workspace/ls.js +277 -0
  825. package/dist/workspace/ls.schema.json +333 -0
  826. package/dist/workspace/member-commands.js +508 -0
  827. package/dist/workspace/member-handles.js +216 -0
  828. package/dist/workspace/member-pipeline.js +258 -0
  829. package/dist/workspace/member-run.js +117 -0
  830. package/dist/workspace/member-write.schema.json +193 -0
  831. package/dist/workspace/nested-graph.js +156 -0
  832. package/dist/workspace/nesting.js +38 -0
  833. package/dist/workspace/patch.js +301 -0
  834. package/dist/workspace/patch.schema.json +321 -0
  835. package/dist/workspace/pin-cli.js +55 -0
  836. package/dist/workspace/pin-integrity.js +100 -0
  837. package/dist/workspace/plan-summary.schema.json +155 -0
  838. package/dist/workspace/point-answer.schema.json +129 -129
  839. package/dist/workspace/points-cli.js +291 -0
  840. package/dist/workspace/points-write.schema.json +660 -0
  841. package/dist/workspace/points.js +521 -0
  842. package/dist/workspace/points.schema.json +916 -0
  843. package/dist/workspace/pr-report.schema.json +108 -0
  844. package/dist/workspace/principal-classes.d.ts +4 -11
  845. package/dist/workspace/principal-classes.d.ts.map +1 -1
  846. package/dist/workspace/principal-classes.js +162 -0
  847. package/dist/workspace/reason-codes.js +326 -0
  848. package/dist/workspace/record-assets.js +151 -0
  849. package/dist/workspace/record-decided.js +105 -0
  850. package/dist/workspace/record-sessions.js +96 -0
  851. package/dist/workspace/record-source.js +114 -0
  852. package/dist/workspace/records-amend.schema.json +284 -0
  853. package/dist/workspace/records-checkout.js +76 -0
  854. package/dist/workspace/records-cli.js +707 -0
  855. package/dist/workspace/records-close.js +116 -0
  856. package/dist/workspace/records-close.schema.json +278 -0
  857. package/dist/workspace/records-new.schema.json +202 -0
  858. package/dist/workspace/records-review.schema.json +367 -0
  859. package/dist/workspace/records-since.js +317 -0
  860. package/dist/workspace/records-since.schema.json +218 -0
  861. package/dist/workspace/records-write.js +1229 -0
  862. package/dist/workspace/records.js +1345 -0
  863. package/dist/workspace/records.schema.json +604 -0
  864. package/dist/workspace/reference-kinds/app/package.json +10 -0
  865. package/dist/workspace/reference-kinds/app/workspace-kinds.json +45 -0
  866. package/dist/workspace/returns.js +303 -0
  867. package/dist/workspace/root-refusal.js +75 -0
  868. package/dist/workspace/run-statement.schema.json +622 -0
  869. package/dist/workspace/runs-cli.js +584 -0
  870. package/dist/workspace/runs-write.schema.json +994 -0
  871. package/dist/workspace/runs.js +708 -0
  872. package/dist/workspace/runs.schema.json +1257 -0
  873. package/dist/workspace/runtimes.js +76 -0
  874. package/dist/workspace/session-kinds.js +83 -0
  875. package/dist/workspace/signers.schema.json +206 -0
  876. package/dist/workspace/source-block.js +125 -0
  877. package/dist/workspace/squash.js +177 -0
  878. package/dist/workspace/status-gates.js +162 -0
  879. package/dist/workspace/status-stewards.js +330 -0
  880. package/dist/workspace/status.js +514 -0
  881. package/dist/workspace/status.schema.json +998 -0
  882. package/dist/workspace/steward-index.js +191 -0
  883. package/dist/workspace/symbols.js +188 -0
  884. package/dist/workspace/template-manifest.js +227 -0
  885. package/dist/workspace/template-pins.js +115 -0
  886. package/dist/workspace/trailer-joins.js +65 -0
  887. package/dist/workspace/trailers.js +111 -0
  888. package/dist/workspace/tree.js +136 -0
  889. package/dist/workspace/trust/attestor.js +63 -0
  890. package/dist/workspace/trust/dsse.js +108 -0
  891. package/dist/workspace/trust/evidence-cli.js +175 -0
  892. package/dist/workspace/trust/evidence.js +161 -0
  893. package/dist/workspace/trust/gate-seal.js +226 -0
  894. package/dist/workspace/trust/policy.js +190 -0
  895. package/dist/workspace/trust/provenance.js +180 -0
  896. package/dist/workspace/trust/rotation.js +298 -0
  897. package/dist/workspace/trust/run-commits.js +101 -0
  898. package/dist/workspace/trust/run-statement.js +182 -0
  899. package/dist/workspace/trust/seal.js +272 -0
  900. package/dist/workspace/trust/signers-cli.js +152 -0
  901. package/dist/workspace/trust/ssh-commit.js +178 -0
  902. package/dist/workspace/trust/test-repo.js +118 -0
  903. package/dist/workspace/trust/trust-files.js +193 -0
  904. package/dist/workspace/trust/verify-cli.js +63 -0
  905. package/dist/workspace/trust/verify.js +192 -0
  906. package/dist/workspace/which-chant.js +141 -0
  907. package/dist/workspace/wip-cli.js +95 -0
  908. package/dist/workspace/wip-write.schema.json +504 -0
  909. package/dist/workspace/wip.js +631 -0
  910. package/dist/workspace/wip.schema.json +334 -0
  911. package/dist/workspace/work-cli.js +448 -0
  912. package/dist/workspace/work-evidence.js +122 -0
  913. package/dist/workspace/work-evidence.schema.json +222 -0
  914. package/dist/workspace/work-history.schema.json +421 -0
  915. package/dist/workspace/work-lease.schema.json +246 -0
  916. package/dist/workspace/work-size.js +77 -0
  917. package/dist/workspace/work.js +283 -0
  918. package/dist/workspace/workspace-kinds.schema.json +259 -259
  919. package/dist/workspace/workspace-principals.schema.json +53 -53
  920. package/dist/workspace/write-lock-cli.js +105 -0
  921. package/dist/workspace/write-lock.js +424 -0
  922. package/dist/workspace/write-lock.schema.json +172 -0
  923. package/dist/workspace/write-scope.js +484 -0
  924. package/dist/yaml.js +459 -0
  925. package/package.json +2 -2
  926. package/src/build.ts +8 -0
  927. package/src/change-set.test.ts +48 -0
  928. package/src/change-set.ts +52 -2
  929. package/src/ci-jobs.test.ts +49 -0
  930. package/src/ci-jobs.ts +515 -0
  931. package/src/cli/commands/check-lexicon-plugin.ts +2 -0
  932. package/src/cli/commands/lint.ts +3 -0
  933. package/src/cli/commands/migrate.ts +2 -0
  934. package/src/cli/compiled-cli.e2e.test.ts +165 -0
  935. package/src/cli/main.ts +121 -27
  936. package/src/cli/mcp/workspace-tools.ts +1 -1
  937. package/src/codegen/pinned-upgrade.ts +2 -0
  938. package/src/components/discover.ts +2 -0
  939. package/src/components/pr-pipeline.ts +3 -2
  940. package/src/components/sandbox/driver.ts +3 -1
  941. package/src/composite.ts +15 -3
  942. package/src/config-import.ts +3 -0
  943. package/src/discovery/collect.ts +15 -1
  944. package/src/discovery/fold-build-parity.test.ts +71 -0
  945. package/src/discovery/fold-import.ts +25 -127
  946. package/src/discovery/fold-project-provenance.test.ts +21 -4
  947. package/src/discovery/host-composite.ts +101 -0
  948. package/src/discovery/import.ts +2 -0
  949. package/src/discovery/index.ts +11 -1
  950. package/src/discovery/sandbox/driver.ts +3 -1
  951. package/src/gate-decision-bundle.test.ts +5 -4
  952. package/src/gate-decision-identity.test.ts +59 -6
  953. package/src/gate-decision.ts +81 -21
  954. package/src/lexicon-module.ts +3 -0
  955. package/src/lifecycle/gate-audit.test.ts +55 -1
  956. package/src/lifecycle/gate-audit.ts +116 -3
  957. package/src/lifecycle/gate-ledger.test.ts +20 -1
  958. package/src/lifecycle/gate-ledger.ts +33 -1
  959. package/src/lint/config.ts +1 -1
  960. package/src/lint/discover.ts +28 -8
  961. package/src/lint/policy-import.ts +4 -1
  962. package/src/lint/rule-loader.ts +2 -0
  963. package/src/op/activities/activity-contracts.ts +1 -1
  964. package/src/op/activities/change-set.ts +11 -3
  965. package/src/op/activities/lexicon-upgrade.test.ts +2 -0
  966. package/src/op/activities/reconcile.test.ts +62 -2
  967. package/src/op/activities/reconcile.ts +46 -22
  968. package/src/op/builders.ts +1 -1
  969. package/src/op/discover.ts +3 -0
  970. package/src/op/op-waves-run.ts +4 -3
  971. package/src/op/op-waves.test.ts +2 -0
  972. package/src/op/work-lease-run.test.ts +12 -4
  973. package/src/ts-loader.test.ts +23 -0
  974. package/src/ts-loader.ts +122 -0
  975. package/src/workspace/__fixtures__/contract-repo.ts +17 -1
  976. package/src/workspace/__fixtures__/sessions.ts +3 -3
  977. package/src/workspace/change-set.schema.json +5 -3
  978. package/src/workspace/chant-migrations/chud-lexicon-exit.ts +1 -1
  979. package/src/workspace/ci-green-workflow.test.ts +4 -4
  980. package/src/workspace/ci-green-workflow.ts +5 -2
  981. package/src/workspace/class-data.test.ts +64 -0
  982. package/src/workspace/class-data.ts +236 -0
  983. package/src/workspace/declaration.ts +1 -1
  984. package/src/workspace/kinds.ts +7 -36
  985. package/src/workspace/lineage-migrations.ts +2 -0
  986. package/src/workspace/points.ts +1 -1
  987. package/src/workspace/principal-classes.ts +12 -95
  988. package/src/workspace/records-formats.test.ts +7 -2
  989. package/src/workspace/records-sessions-write.test.ts +3 -3
  990. package/src/workspace/status-contract.e2e.test.ts +37 -0
  991. package/src/workspace/status-contract.test.ts +23 -21
@@ -0,0 +1,4073 @@
1
+ import * as ts from "typescript";
2
+ import { readFile } from "node:fs/promises";
3
+ import { existsSync, readFileSync, realpathSync } from "node:fs";
4
+ import { dirname, basename, join, sep, isAbsolute, resolve as resolvePath } from "node:path";
5
+ import { fileURLToPath } from "node:url";
6
+ import { createRequire } from "node:module";
7
+ import { isDeclarable, isResourceDeclarable } from "../declarable.js";
8
+ import { Composite, isCompositeInstance } from "../composite.js";
9
+ import { isAttrRefLike } from "../utils.js";
10
+ import { isIntrinsic } from "../intrinsic.js";
11
+ import { collectConsts, foldResource, fold, propName, FoldError, isFoldableFunction, locate, collectSameFileFunctions, constResolvesToResource, } from "../fold/fold.js";
12
+ import { isChantOwnedSpecifier, isFoldableHelperName } from "../fold/foldable-helpers.js";
13
+ import { lexiconModulePath, lexiconModuleRoot } from "../lexicon-module.js";
14
+ import { isFile, packageExportsTarget, probeModuleFile, splitBareSpecifier, tsconfigPathsMatch, } from "./specifier-resolve.js";
15
+ import { briefNodeText, callExpressionMessage, computedPropertyNameMessage, isLiteralElementKey, unsupportedBinaryMessage, unsupportedExpressionMessage, UNSUPPORTED_OBJECT_MEMBER_MESSAGE, UNSUPPORTED_UNARY_MESSAGE, SUPPORTED_BINARY_OPERATORS, SUPPORTED_UNARY_OPERATORS, } from "../fold/subset.js";
16
+ import { importModule } from "./import.js";
17
+ import { collectCompositeOrigins, collectParamDependencies, collectTagOrigins, sourceLocationOf, } from "./param-deps.js";
18
+ import { getInterpolationFields, getProvenance, setPathProvenance, setProvenance } from "../provenance.js";
19
+ import { foldProvenanceOfEntities } from "../fold-provenance.js";
20
+ import { collectEntities } from "./collect.js";
21
+ import { hostCompositeName, markHostCompositeInstance } from "./host-composite.js";
22
+ import { intrinsicCallFoldsEagerly, intrinsicTagFolds } from "../lexicon.js";
23
+ /**
24
+ * chant #1063 — the package specifier a lexicon NAME (`"aws"`, `"gitlab"`)
25
+ * is installed under. The one naming convention the whole CLI already
26
+ * depends on: `loadPlugin(name)` imports exactly this
27
+ * (../cli/plugins.ts), `detectLexicons` scans source for exactly this
28
+ * (../detectLexicon.ts), and `chant init` writes exactly this into
29
+ * package.json.
30
+ */
31
+ export function lexiconPackageName(lexiconName) {
32
+ return `@intentius/chant-lexicon-${lexiconName}`;
33
+ }
34
+ /**
35
+ * Create a fresh, empty {@link FoldSession}.
36
+ *
37
+ * @param lexicons - chant #1063: the lexicon NAMES active for this build
38
+ * (`["aws", "k8s"]`). Converted to package specifiers via
39
+ * {@link lexiconPackageName}; see {@link FoldSession.lexiconPackages}.
40
+ * @param sandbox - chant #1093: this build asked for the #1045 sandbox, so
41
+ * fold may not import or invoke anything outside the trusted allowlist —
42
+ * see {@link FoldSession.sandbox}.
43
+ */
44
+ export function createFoldSession(intrinsics = [], buildParams, lexicons = [], sandbox = false,
45
+ /**
46
+ * chant#2438 — package specifiers to allow verbatim, alongside the ones
47
+ * derived from `lexicons`.
48
+ *
49
+ * A chant build's active packages are always `@intentius/chant-lexicon-*`,
50
+ * so naming lexicons is the whole story there. A caller embedding the fold
51
+ * path against a host that is not a chant lexicon has packages the naming
52
+ * convention cannot express: the specification's conformance harness supplies
53
+ * `@tsad/shapes`, which is its host's own specifier. The boundary is
54
+ * unchanged, an explicitly named package of this build, only now it can be
55
+ * named as a specifier rather than only as a lexicon.
56
+ */
57
+ lexiconPackages = [],
58
+ /** chant#2455 — `ι = executing`. Mutually exclusive with `sandbox`. */
59
+ executing = false,
60
+ /**
61
+ * chant#2590 — the build's own source files. A path lexicon's trusted
62
+ * directory that holds any of them is dropped (see {@link FoldSession.lexiconRoots}).
63
+ */
64
+ sourceFiles = []) {
65
+ // chant#2577 — a lexicon declared by path is that module and no package, so
66
+ // its name contributes the module path and not `@intentius/chant-lexicon-<name>`.
67
+ const packages = new Set(lexiconPackages);
68
+ const modules = new Set();
69
+ const roots = [];
70
+ for (const name of lexicons) {
71
+ const modulePath = lexiconModulePath(name);
72
+ if (modulePath === undefined)
73
+ packages.add(lexiconPackageName(name));
74
+ else
75
+ modules.add(resolveModulePath(modulePath, modulePath));
76
+ const root = lexiconModuleRoot(name);
77
+ if (root !== undefined && !sourceFiles.some((file) => isUnderDirectory(resolvePath(file), root)))
78
+ roots.push(root);
79
+ }
80
+ return {
81
+ intrinsics,
82
+ cache: new Map(),
83
+ stack: [],
84
+ importCache: new Map(),
85
+ resolvePathCache: new Map(),
86
+ buildParams,
87
+ lexiconPackages: packages,
88
+ lexiconModules: modules,
89
+ lexiconRoots: roots,
90
+ sandbox,
91
+ executing,
92
+ factoryModules: new Map(),
93
+ };
94
+ }
95
+ /**
96
+ * chant #1020 hang fix — memo for {@link resolveModulePath}'s BARE
97
+ * (package) specifier branch, deliberately PROCESS-WIDE rather than
98
+ * session-scoped (contrast {@link FoldSession.resolvePathCache}, used for
99
+ * relative specifiers). A bare specifier like `@intentius/chant-lexicon-aws`
100
+ * resolves via `createRequire(fromFile).resolve(specifier)` — which package
101
+ * a name refers to cannot change mid-process (Node's own module cache
102
+ * already assumes this: nothing invalidates `require.cache`/the dynamic
103
+ * `import()` cache either if `node_modules` changes under a running
104
+ * process), so caching the answer for the process's lifetime, across every
105
+ * `discover()` call/`FoldSession`, is exactly as safe as Node's own
106
+ * assumptions — unlike a relative specifier (a project file `chant build
107
+ * --watch` can legitimately add/remove between rebuilds), never unsafe to
108
+ * reuse.
109
+ *
110
+ * This is what actually fixes the hang, not just reduces it: profiling (a
111
+ * real `sample` during the observed multi-minute stall) traced the cost to
112
+ * `createRequire(fromFile).resolve(specifier)` ITSELF taking upwards of a
113
+ * minute — measured on this exact call, in this exact spot, nowhere else —
114
+ * specifically for the FIRST bare-specifier resolution inside a
115
+ * `FoldSession` created right after `vitest`'s `vi.resetModules()`
116
+ * (`sandbox-differential.test.ts`'s own retry path, pre-existing and
117
+ * unrelated to #1020 — confirmed on `main`, which hits the identical retry
118
+ * for the identical reason and stays fast). Two narrower versions of this
119
+ * fix (session-scoped; then process-wide but still keyed by directory) each
120
+ * still paid that cost at least once more: directory-scoping alone pays it
121
+ * once per NEW directory a corpus entry introduces, and #1020's cross-file
122
+ * resolution reaches a real constructor/composite-factory/intrinsic
123
+ * resolution — hence a real bare-specifier resolve — from strictly more
124
+ * directories across a 98-entry corpus than the pre-#1020 single-file fold
125
+ * ever did. Keyed by specifier ALONE (see {@link resolveModulePathMemoized}'s
126
+ * doc for the directory-independence assumption this relies on), the answer
127
+ * computed the first time ANY directory needed a given package is there for
128
+ * every directory after it, `vi.resetModules()` retry or not — the slow call
129
+ * never has to happen a second time in the same process, for any package.
130
+ */
131
+ const bareSpecifierPathCache = new Map();
132
+ /**
133
+ * Memoized {@link resolveModulePath}. A relative/absolute specifier goes
134
+ * through the session-scoped `resolvePathCache`, keyed by DIRECTORY (not the
135
+ * full file path: `resolveModulePath` only ever consults `dirname(fromFile)`
136
+ * — the relative branch resolves against `dirname(fromFile)`, so the
137
+ * specific FILE within a directory never affects the answer) — a project
138
+ * file can legitimately be added/removed between builds (`chant build
139
+ * --watch`), so this cache is thrown away with the rest of the session.
140
+ *
141
+ * A bare (package) specifier goes through {@link bareSpecifierPathCache}
142
+ * instead, keyed by the specifier ALONE — process-wide, no directory
143
+ * component. `createRequire(fromFile).resolve(specifier)` technically CAN
144
+ * answer differently for the same specifier from two directories (nested
145
+ * `node_modules` with a version override), but `discover()`'s real usage is
146
+ * one `srcDir` per build, whose files overwhelmingly share one effective
147
+ * `node_modules` resolution — the same assumption bundlers (webpack,
148
+ * esbuild, vite) already make for their own module resolution caches. See
149
+ * {@link bareSpecifierPathCache}'s own doc for why process-wide (not just
150
+ * directory-scoped) is what actually fixes the hang: `examples/foo`'s
151
+ * directory and `lexicons/aws/examples/bar`'s directory both need
152
+ * `@intentius/chant-lexicon-aws`, and directory-scoping alone still pays the
153
+ * pathological cost once per NEW directory, not just once per package.
154
+ */
155
+ function resolveModulePathMemoized(specifier, fromFile, resolvePathCache) {
156
+ const isBare = !specifier.startsWith(".") && !isAbsolute(specifier);
157
+ // chant#3090 — a tsconfig `paths` answer depends on the importing file's
158
+ // directory and can name a project file that comes and goes between
159
+ // builds, so it is memoized per session and directory, like a relative one.
160
+ const key = `${dirname(fromFile)}\0${specifier}`;
161
+ if (isBare) {
162
+ const sessionCached = resolvePathCache.get(key);
163
+ if (sessionCached !== undefined)
164
+ return sessionCached;
165
+ const mapped = pathsMappedTarget(specifier, fromFile);
166
+ if (mapped !== undefined) {
167
+ resolvePathCache.set(key, mapped);
168
+ return mapped;
169
+ }
170
+ const cached = bareSpecifierPathCache.get(specifier);
171
+ if (cached !== undefined)
172
+ return cached;
173
+ const resolved = resolveModulePath(specifier, fromFile);
174
+ bareSpecifierPathCache.set(specifier, resolved);
175
+ return resolved;
176
+ }
177
+ const cached = resolvePathCache.get(key);
178
+ if (cached !== undefined)
179
+ return cached;
180
+ const resolved = resolveModulePath(specifier, fromFile);
181
+ resolvePathCache.set(key, resolved);
182
+ return resolved;
183
+ }
184
+ /**
185
+ * chant #1020 hang fix — construct an `Error` for a routine, EXPECTED
186
+ * cross-file resolution failure (an unresolvable import, a non-function
187
+ * export, a shape this module doesn't support) WITHOUT V8's normal eager
188
+ * stack-frame capture. Every one of these is thrown, then caught a few
189
+ * frames up and reduced to `.message` — `tryFoldFileCore`'s own top-level
190
+ * catch, ultimately, same as {@link FoldError} (see its own doc in
191
+ * ../fold/fold.ts for the full mechanism and profiling evidence): `.stack` is
192
+ * never read on this path. Cheap for a shallow call stack, but this module's
193
+ * cross-file recursion (`foldFileMemoized` -> `buildExternals` ->
194
+ * `tryFoldFileCore` -> `resolveDeclaratorValue`/`resolveLiveValue` ->
195
+ * `resolveCallExpression`/`resolveImportedExport`, sometimes several files
196
+ * deep) makes the live stack deep enough, and hot enough for V8 to inline
197
+ * aggressively across a 98-entry corpus, that eager capture becomes the
198
+ * dominant cost — confirmed via `sample` during the observed multi-minute
199
+ * stall. Every throw site in this module that isn't a {@link FoldError}
200
+ * itself should use this instead of `new Error(...)`.
201
+ */
202
+ function cheapError(message) {
203
+ const prevStackTraceLimit = Error.stackTraceLimit;
204
+ Error.stackTraceLimit = 0;
205
+ try {
206
+ return new Error(message);
207
+ }
208
+ finally {
209
+ Error.stackTraceLimit = prevStackTraceLimit;
210
+ }
211
+ }
212
+ /**
213
+ * Memoized {@link importModule} — see {@link FoldSession.importCache}'s doc
214
+ * for why this exists. Keyed by `modulePath` exactly as callers already pass
215
+ * it (the output of {@link resolveModulePath}, always an absolute path), so
216
+ * every caller resolving the same module shares the identical in-flight/
217
+ * settled promise instead of issuing its own `import()`.
218
+ */
219
+ function importModuleMemoized(modulePath, importCache) {
220
+ const cached = importCache.get(modulePath);
221
+ if (cached)
222
+ return cached;
223
+ const promise = importModule(modulePath);
224
+ importCache.set(modulePath, promise);
225
+ return promise;
226
+ }
227
+ /** True when `node` carries the `export` modifier. */
228
+ function hasExportModifier(node) {
229
+ return node.modifiers?.some((m) => m.kind === ts.SyntaxKind.ExportKeyword) ?? false;
230
+ }
231
+ const executionCounts = {
232
+ factoryInvocations: 0,
233
+ projectFactoryInvocations: 0,
234
+ factoryInterpretations: 0,
235
+ };
236
+ /** A snapshot of {@link FoldExecutionCounts}. Process-wide and monotonic — a caller wanting a per-build figure calls {@link resetFoldExecutionCounts} first. */
237
+ export function foldExecutionCounts() {
238
+ return { ...executionCounts };
239
+ }
240
+ /** Zero {@link foldExecutionCounts}, for a caller measuring one build. */
241
+ export function resetFoldExecutionCounts() {
242
+ executionCounts.factoryInvocations = 0;
243
+ executionCounts.projectFactoryInvocations = 0;
244
+ executionCounts.factoryInterpretations = 0;
245
+ }
246
+ /**
247
+ * True for any non-null, non-array object — a real `CompositeInstance`, a
248
+ * real `Declarable`, or (chant #1020) the synthetic plain object a
249
+ * namespace import's cross-file resolution builds from another file's own
250
+ * `exportedValues`. Property/member access on any of these is just a plain
251
+ * bracket index, so {@link resolveLiveValue} doesn't need to special-case
252
+ * which kind it is.
253
+ */
254
+ function isIndexableObject(value) {
255
+ return typeof value === "object" && value !== null && !Array.isArray(value);
256
+ }
257
+ /** Extract a foldable object-binding-pattern element's source property key, or `undefined` if the shape isn't supported (rest element, nested pattern, default value, computed name). */
258
+ function bindingElementPropKey(el) {
259
+ if (el.dotDotDotToken || el.initializer || !ts.isIdentifier(el.name))
260
+ return undefined;
261
+ if (!el.propertyName)
262
+ return el.name.text;
263
+ return ts.isIdentifier(el.propertyName) ? el.propertyName.text : undefined;
264
+ }
265
+ /**
266
+ * Scan a source file's top-level statements for the exported shapes this
267
+ * module can fold: `export const X = new Type(...)` (resources, #1022),
268
+ * `export const X = <expr>` / `export const { a, b } = <expr>` (composite
269
+ * calls and member access on them, #1023), `export { a, b }` (a local
270
+ * named-export list), and `export { a, b } from "./other"` (a re-export
271
+ * chain, #1020), and `export function name(...) {...}` (a foldable-function
272
+ * export, #1373). Any OTHER export construct (`export default`, `export *
273
+ * from`, an exported class, `let`/`var`, a destructured export with
274
+ * a rest/nested/defaulted element) makes the whole file ineligible: the
275
+ * module must run so that construct is actually evaluated. This mirrors the
276
+ * epic's hybrid design — fallback is per-module, not per-declaration,
277
+ * because an unfoldable export can itself reference or be referenced by a
278
+ * foldable one in ways only running proves safe.
279
+ */
280
+ function scanExports(sourceFile) {
281
+ const declarators = [];
282
+ for (const statement of sourceFile.statements) {
283
+ if (ts.isExportAssignment(statement)) {
284
+ return { declarators, unfoldableReason: "`export default` is not foldable" };
285
+ }
286
+ if (ts.isExportDeclaration(statement)) {
287
+ if (statement.isTypeOnly)
288
+ continue; // `export type { ... }` — erased, no runtime value to fold.
289
+ if (statement.moduleSpecifier) {
290
+ // chant #1020 — a genuine re-export (`export { a } from "./other"`).
291
+ // `export * from "./other"` has no `exportClause` at all — no
292
+ // enumerable element list to resolve one-by-one — and still
293
+ // disqualifies the file, same as before #1020.
294
+ if (!ts.isStringLiteral(statement.moduleSpecifier)) {
295
+ return { declarators, unfoldableReason: "re-export declaration is not foldable" };
296
+ }
297
+ if (!statement.exportClause || !ts.isNamedExports(statement.exportClause)) {
298
+ return { declarators, unfoldableReason: "re-export declaration is not foldable" };
299
+ }
300
+ const elements = [];
301
+ for (const el of statement.exportClause.elements) {
302
+ if (el.isTypeOnly)
303
+ continue;
304
+ const importedNameNode = el.propertyName ?? el.name;
305
+ if (!ts.isIdentifier(importedNameNode)) {
306
+ return { declarators, unfoldableReason: "re-export declaration is not foldable" };
307
+ }
308
+ elements.push({ imported: importedNameNode.text, exportedName: el.name.text });
309
+ }
310
+ declarators.push({
311
+ kind: "re-export",
312
+ specifier: statement.moduleSpecifier.text,
313
+ specifierNode: statement.moduleSpecifier,
314
+ elements,
315
+ });
316
+ continue;
317
+ }
318
+ if (!statement.exportClause || !ts.isNamedExports(statement.exportClause)) {
319
+ return { declarators, unfoldableReason: "export declaration is not foldable" };
320
+ }
321
+ const elements = [];
322
+ for (const el of statement.exportClause.elements) {
323
+ if (el.isTypeOnly)
324
+ continue;
325
+ const localNameNode = el.propertyName ?? el.name;
326
+ // `export { "string name" as foo }` (TS 4.5+ module-export-name
327
+ // syntax) isn't a plain identifier reference — not seen in practice
328
+ // for local (non-re-export) lists; fall back rather than guess.
329
+ if (!ts.isIdentifier(localNameNode)) {
330
+ return { declarators, unfoldableReason: "export declaration is not foldable" };
331
+ }
332
+ elements.push({ localNameNode, exportedName: el.name.text });
333
+ }
334
+ declarators.push({ kind: "named-export", elements });
335
+ continue;
336
+ }
337
+ if (ts.isFunctionDeclaration(statement) && hasExportModifier(statement)) {
338
+ // chant #1373 — a named, bodied exported function is a foldable-function
339
+ // export (see {@link FunctionDeclarator}). An overload signature has no
340
+ // body and no value of its own — skipped, the implementation that
341
+ // follows it is the export. `export default function` has no name an
342
+ // importer could bind and stays a disqualifier, like `export default`.
343
+ if (statement.modifiers?.some((m) => m.kind === ts.SyntaxKind.DefaultKeyword) || !statement.name) {
344
+ return { declarators, unfoldableReason: "`export default` is not foldable" };
345
+ }
346
+ if (!statement.body)
347
+ continue;
348
+ declarators.push({ kind: "function", name: statement.name.text });
349
+ continue;
350
+ }
351
+ if (ts.isClassDeclaration(statement) && hasExportModifier(statement)) {
352
+ return {
353
+ declarators,
354
+ unfoldableReason: `exported class declaration "${statement.name?.text ?? "<anonymous>"}" is not foldable`,
355
+ };
356
+ }
357
+ if (!ts.isVariableStatement(statement) || !hasExportModifier(statement))
358
+ continue;
359
+ if ((statement.declarationList.flags & ts.NodeFlags.Const) === 0) {
360
+ return { declarators, unfoldableReason: "exported `let`/`var` declaration is not foldable" };
361
+ }
362
+ for (const decl of statement.declarationList.declarations) {
363
+ if (!decl.initializer) {
364
+ return { declarators, unfoldableReason: "exported destructured or uninitialized declaration is not foldable" };
365
+ }
366
+ if (ts.isIdentifier(decl.name)) {
367
+ if (ts.isNewExpression(decl.initializer)) {
368
+ declarators.push({ kind: "resource", name: decl.name.text, node: decl.initializer });
369
+ }
370
+ else {
371
+ declarators.push({ kind: "single", name: decl.name.text, node: decl.initializer });
372
+ }
373
+ continue;
374
+ }
375
+ if (ts.isObjectBindingPattern(decl.name)) {
376
+ const elements = [];
377
+ let allSupported = true;
378
+ for (const el of decl.name.elements) {
379
+ const propKey = bindingElementPropKey(el);
380
+ if (propKey === undefined) {
381
+ allSupported = false;
382
+ break;
383
+ }
384
+ elements.push({ propKey, bindingName: el.name.getText() });
385
+ }
386
+ if (!allSupported) {
387
+ return { declarators, unfoldableReason: "exported destructured or uninitialized declaration is not foldable" };
388
+ }
389
+ declarators.push({ kind: "destructure", node: decl.initializer, elements });
390
+ continue;
391
+ }
392
+ // ArrayBindingPattern — not supported.
393
+ return { declarators, unfoldableReason: "exported destructured or uninitialized declaration is not foldable" };
394
+ }
395
+ }
396
+ return { declarators };
397
+ }
398
+ function collectLocalBindings(sourceFile) {
399
+ const bindings = new Map();
400
+ for (const statement of sourceFile.statements) {
401
+ if (!ts.isVariableStatement(statement))
402
+ continue;
403
+ if ((statement.declarationList.flags & ts.NodeFlags.Const) === 0)
404
+ continue;
405
+ for (const decl of statement.declarationList.declarations) {
406
+ if (!decl.initializer)
407
+ continue;
408
+ if (ts.isIdentifier(decl.name)) {
409
+ bindings.set(decl.name.text, { source: decl.initializer });
410
+ }
411
+ else if (ts.isObjectBindingPattern(decl.name)) {
412
+ for (const el of decl.name.elements) {
413
+ const propKey = bindingElementPropKey(el);
414
+ if (propKey === undefined)
415
+ continue;
416
+ bindings.set(el.name.getText(), { source: decl.initializer, propKey });
417
+ }
418
+ }
419
+ // ArrayBindingPattern locals: not indexed — a later reference to one
420
+ // of its names simply won't be found below, which correctly falls
421
+ // back (same as referencing any other unresolved identifier).
422
+ }
423
+ }
424
+ return bindings;
425
+ }
426
+ /** `(x) => …` / `function (x) {…}`, possibly wrapped in parens/`as`/`satisfies` — the initializer shapes that make a `const` a function binding. */
427
+ /**
428
+ * chant #1373 — every top-level function a file declares, exported or not, as
429
+ * a {@link FoldableFunction}: `function f(...) {...}` and `const f = (...) =>
430
+ * …`. Placed in the file's own `externals` (so a same-file call folds, and a
431
+ * sibling function can call it) and exported under its name (so an importer's
432
+ * `buildExternals` can). The marker carries the file's scope BY REFERENCE:
433
+ * `ctx.externals` is still being filled (pre-built resources land in it after
434
+ * this runs) and a body reads it only when called.
435
+ *
436
+ * The marker's `consts` drops every `new`-bound const (transitively, through
437
+ * aliases) — see {@link FoldableFunction.consts} for why a body must reach a
438
+ * module-level resource through `externals` only.
439
+ */
440
+ function collectLocalFunctions(sourceFile, ctx) {
441
+ // chant#2436 — the walk itself, and the `new`-bound stripping that makes it
442
+ // correct, live in ../fold/fold.ts so a unit-level fold with no externals of
443
+ // its own binds exactly what a build binds.
444
+ return collectSameFileFunctions(sourceFile, ctx.consts, ctx.file, ctx.externals, ctx.crossFileFailures);
445
+ }
446
+ /** Map every top-level `import`-bound local identifier to its source module + export name. */
447
+ function collectImports(sourceFile) {
448
+ const named = new Map();
449
+ const namespaces = new Map();
450
+ for (const statement of sourceFile.statements) {
451
+ if (!ts.isImportDeclaration(statement))
452
+ continue;
453
+ if (!ts.isStringLiteral(statement.moduleSpecifier))
454
+ continue;
455
+ const clause = statement.importClause;
456
+ if (!clause)
457
+ continue;
458
+ // A whole-statement `import type { ... } from "x"` is erased at runtime
459
+ // and never appears in expression position — indexing it would only
460
+ // ever manufacture a false cross-file dependency edge (taint, or worse,
461
+ // a phantom import-cycle) between files that share no real value
462
+ // dependency. Per-specifier `import { type Foo } from "x"` is filtered
463
+ // below, at the named-element level.
464
+ if (clause.isTypeOnly)
465
+ continue;
466
+ const specifier = statement.moduleSpecifier.text;
467
+ const specifierNode = statement.moduleSpecifier;
468
+ if (clause.name) {
469
+ named.set(clause.name.text, { specifier, imported: "default", specifierNode });
470
+ }
471
+ if (clause.namedBindings) {
472
+ if (ts.isNamedImports(clause.namedBindings)) {
473
+ for (const element of clause.namedBindings.elements) {
474
+ if (element.isTypeOnly)
475
+ continue;
476
+ const imported = element.propertyName?.text ?? element.name.text;
477
+ named.set(element.name.text, { specifier, imported, specifierNode });
478
+ }
479
+ }
480
+ else if (ts.isNamespaceImport(clause.namedBindings)) {
481
+ namespaces.set(clause.namedBindings.name.text, { specifier, specifierNode });
482
+ }
483
+ }
484
+ }
485
+ return { named, namespaces };
486
+ }
487
+ /**
488
+ * chant #1020 hang fix — best-effort fast path for resolving a BARE
489
+ * specifier by walking `node_modules` directly with the same primitives
490
+ * (`statSync`/`readFileSync`) the relative-specifier branch below already
491
+ * uses, reading `package.json`'s "exports"/"main" field by hand instead of
492
+ * calling `createRequire(fromFile).resolve(specifier)`. Returns `undefined`
493
+ * — never throws — when the package is not found, its `exports` map does not
494
+ * export the subpath under the `import`/`node`/`default` conditions, or a
495
+ * subpath of a package without `exports` has no `package.json` of its own:
496
+ * the caller then falls back to the slow, authoritative
497
+ * `createRequire().resolve()` path, so such a specifier is still resolved
498
+ * correctly, just not quickly.
499
+ *
500
+ * chant#3090 — subpaths (`pkg/sub/x`) and `exports` pattern keys
501
+ * (`"./sub/*"`) resolve here too, with condition objects read in key order
502
+ * as an ESM import reads them ({@link packageExportsTarget}). Before, a
503
+ * subpath always reached the `createRequire` fallback, whose CommonJS
504
+ * conditions miss an `import`-only target.
505
+ *
506
+ * Why this exists at all: profiling traced the hang to
507
+ * `createRequire(fromFile).resolve(specifier)` ITSELF taking upwards of a
508
+ * minute — and, worse, growing (measured 60s, then 69s, then 361s for
509
+ * successive NEW packages later in the same run) — inside a vitest worker,
510
+ * specifically for the FIRST resolution of a given bare specifier in the
511
+ * process (session-level and even process-wide-by-specifier caching, see
512
+ * {@link bareSpecifierPathCache}, only avoid paying that cost a SECOND
513
+ * time). The relative-specifier branch just below, using these exact same
514
+ * file-system primitives, never showed this slowdown anywhere
515
+ * in the same profiling — the cost is specific to Node's own
516
+ * `Module._resolveFilename` machinery, not to file-system access in
517
+ * general, so replacing just that one call with plain `statSync`/
518
+ * `readFileSync` sidesteps it entirely for the common case every
519
+ * `@intentius/chant*` package (and most well-formed npm packages) ships.
520
+ */
521
+ function fastResolveBareSpecifier(specifier, fromFile) {
522
+ const parts = splitBareSpecifier(specifier);
523
+ if (parts === undefined)
524
+ return undefined;
525
+ let dir = dirname(fromFile);
526
+ for (;;) {
527
+ const packageDir = join(dir, "node_modules", parts.name);
528
+ if (existsSync(packageDir)) {
529
+ const resolved = fastResolvePackageEntry(packageDir, parts.subpath);
530
+ if (resolved === undefined || !isFile(resolved))
531
+ return undefined;
532
+ try {
533
+ return realpathSync(resolved);
534
+ }
535
+ catch {
536
+ return undefined;
537
+ }
538
+ }
539
+ const parent = dirname(dir);
540
+ if (parent === dir)
541
+ return undefined; // reached the filesystem root, unresolved
542
+ dir = parent;
543
+ }
544
+ }
545
+ /**
546
+ * The absolute file `<packageDir>/package.json` names for `subpath` (`"."` or
547
+ * `"./x"`), or `undefined` — see {@link fastResolveBareSpecifier}'s doc for
548
+ * which shapes this recognizes. With an `exports` field, that map decides.
549
+ * Without one, the root is `main` (or `index.js`), and a subpath is read as a
550
+ * nested package directory with its own `package.json`.
551
+ */
552
+ function fastResolvePackageEntry(packageDir, subpath) {
553
+ const pkgJsonPath = join(packageDir, "package.json");
554
+ if (!existsSync(pkgJsonPath))
555
+ return undefined;
556
+ let pkg;
557
+ try {
558
+ pkg = JSON.parse(readFileSync(pkgJsonPath, "utf-8"));
559
+ }
560
+ catch {
561
+ return undefined;
562
+ }
563
+ if (typeof pkg !== "object" || pkg === null)
564
+ return undefined;
565
+ const exportsField = pkg.exports;
566
+ if (exportsField !== undefined) {
567
+ const target = packageExportsTarget(exportsField, subpath);
568
+ return target === undefined ? undefined : resolvePath(packageDir, target);
569
+ }
570
+ if (subpath !== ".")
571
+ return fastResolvePackageEntry(join(packageDir, subpath), ".");
572
+ const main = pkg.main;
573
+ if (main !== undefined)
574
+ return typeof main === "string" ? resolvePath(packageDir, main) : undefined;
575
+ return resolvePath(packageDir, "index.js");
576
+ }
577
+ /**
578
+ * chant#3090 — the file a tsconfig `paths` entry maps a bare `specifier` to,
579
+ * from the nearest `tsconfig.json` above `fromFile` ({@link tsconfigPathsTarget}),
580
+ * or `undefined`.
581
+ *
582
+ * chant's own package specifiers ({@link isChantOwnedSpecifier}) are never
583
+ * mapped. They resolve through `node_modules` to the modules the CLI has
584
+ * already loaded, which is what {@link isTrustedExecutableBinding} relies on,
585
+ * and a project's `paths` cannot point them at its own files. This repository's
586
+ * root tsconfig maps every chant package to its source, and those entries
587
+ * name the same files `node_modules` does.
588
+ *
589
+ * A specifier this maps is a project import ({@link isProjectImport}): it is
590
+ * folded as a project file and its trust is decided on the resolved path,
591
+ * even when its text names an active lexicon package.
592
+ */
593
+ function pathsMappedTarget(specifier, fromFile) {
594
+ return pathsMatch(specifier, fromFile)?.target;
595
+ }
596
+ /** The tsconfig `paths` match behind {@link pathsMappedTarget}, including a match whose targets name no file. */
597
+ function pathsMatch(specifier, fromFile) {
598
+ if (specifier.startsWith(".") || isAbsolute(specifier) || isChantOwnedSpecifier(specifier))
599
+ return undefined;
600
+ return tsconfigPathsMatch(specifier, fromFile);
601
+ }
602
+ /**
603
+ * Resolve an import specifier to an absolute module path, the way the
604
+ * declaring file's own `import` would — without depending on a TS-aware
605
+ * loader being active. Relative/absolute specifiers are probed against real
606
+ * TS/JS candidate files on disk ({@link probeModuleFile}, which maps `./x.js`
607
+ * to `./x.ts` as `tsc` does); a bare specifier a tsconfig `paths` entry maps
608
+ * resolves to that file ({@link pathsMappedTarget}); other bare package
609
+ * specifiers try {@link fastResolveBareSpecifier} first, falling back to
610
+ * Node's own CJS algorithm from the declaring file's location whenever that
611
+ * returns `undefined`.
612
+ */
613
+ function resolveModulePath(specifier, fromFile) {
614
+ if (specifier.startsWith(".") || isAbsolute(specifier)) {
615
+ const base = specifier.startsWith(".") ? resolvePath(dirname(fromFile), specifier) : specifier;
616
+ // Nothing found on disk under any probed extension — hand back the bare
617
+ // base and let `import()` fail with its own, more specific error.
618
+ return probeModuleFile(base) ?? base;
619
+ }
620
+ const mapped = pathsMappedTarget(specifier, fromFile);
621
+ if (mapped !== undefined)
622
+ return mapped;
623
+ const fast = fastResolveBareSpecifier(specifier, fromFile);
624
+ if (fast !== undefined)
625
+ return fast;
626
+ return createRequire(fromFile).resolve(specifier);
627
+ }
628
+ /**
629
+ * chant #1064 — the one real published subpath a project imports chant's
630
+ * build-time-parameters module from. {@link buildExternals} matches a bare
631
+ * specifier against this by TEXT ONLY, never by resolving it — see that
632
+ * function's own comment for why resolving an arbitrary bare specifier here
633
+ * would reintroduce the exact pathological cold-resolution cost chant#1020
634
+ * already fixed once (this module's other comments measure it at up to
635
+ * ~361s for the first resolution of a genuinely new bare specifier).
636
+ */
637
+ const PARAMS_BARE_SPECIFIER = "@intentius/chant/params";
638
+ /**
639
+ * chant #1064 — the absolute path of chant-core's OWN build-time-parameters
640
+ * runtime module (../params.ts), resolved lazily on first use, from THIS
641
+ * file's own location, via the exact same relative-specifier resolution
642
+ * {@link resolveModulePath} already applies to project files — cheap
643
+ * (`existsSync`/`statSync` candidate probing only, never Node's package
644
+ * resolution). Used by {@link buildExternals} to recognize a RELATIVE/
645
+ * ABSOLUTE import of the params module (this module's own test fixtures use
646
+ * an absolute path, matching the rest of this file's test convention) — a
647
+ * real project's bare `@intentius/chant/params` import is instead matched by
648
+ * {@link PARAMS_BARE_SPECIFIER}'s text alone, never through this path.
649
+ *
650
+ * Lazy and failure-safe, NOT a module-scope constant: this module is also
651
+ * bundled into the #1045 sandbox child, where module-init code runs inside
652
+ * `--permission` with a read allowlist. There `import.meta.url` is the
653
+ * bundle's temp-dir path, so an init-time probe reaches for `<tmp>/params` —
654
+ * outside the allowlist — and the `existsSync` throws `ERR_ACCESS_DENIED`,
655
+ * killing the child before it reports (a real, observed sandbox-vs-run error
656
+ * drift across six corpus entries). The child runs only the run path and
657
+ * never calls {@link buildExternals}, so deferring the probe to first fold
658
+ * use keeps it out of the sandbox entirely; if probing still fails there,
659
+ * `null` just disables the relative-path recognition rather than erroring.
660
+ */
661
+ let paramsModulePathMemo;
662
+ function paramsModulePath() {
663
+ if (paramsModulePathMemo === undefined) {
664
+ try {
665
+ paramsModulePathMemo = resolveModulePath("../params", fileURLToPath(import.meta.url));
666
+ }
667
+ catch {
668
+ paramsModulePathMemo = null;
669
+ }
670
+ }
671
+ return paramsModulePathMemo;
672
+ }
673
+ /**
674
+ * chant #1063 — is `specifier` a bare import of a package that is an ACTIVE
675
+ * LEXICON of this build? Returns the specifier itself when so, `undefined`
676
+ * otherwise.
677
+ *
678
+ * Three deliberate restrictions, each of which is the point rather than an
679
+ * omission:
680
+ *
681
+ * - **Text only, no resolution.** The answer is a `Set.has` on a set built
682
+ * from the lexicon names the build ALREADY resolved before discovery ran
683
+ * (`resolveProjectLexicons` -> `loadPlugins`). Nothing is probed, read, or
684
+ * resolved to decide whether a specifier is in scope — so an import of
685
+ * some unrelated package costs a string comparison and is then left alone,
686
+ * never resolved "just to find out what it is". That matters here more
687
+ * than anywhere: `resolveModulePath`'s bare branch can fall through to
688
+ * `createRequire(fromFile).resolve(specifier)`, measured at up to ~361s
689
+ * for the first resolution of a genuinely new bare specifier in a process
690
+ * (see {@link bareSpecifierPathCache}/{@link fastResolveBareSpecifier} —
691
+ * this exact class of cost regressed twice during chant#1020).
692
+ *
693
+ * - **Exact package specifier, no subpaths.** `@intentius/chant-lexicon-aws`
694
+ * matches; `@intentius/chant-lexicon-aws/actions` does not. Every lexicon
695
+ * re-exports its whole public surface from its barrel and every example
696
+ * imports it that way, so subpaths buy nothing — and they would cost
697
+ * something real: {@link fastResolveBareSpecifier} only recognizes a
698
+ * package ROOT (it looks for `<node_modules>/<specifier>/package.json`),
699
+ * so a subpath specifier falls through to exactly the slow
700
+ * `createRequire().resolve()` path this restriction exists to avoid.
701
+ *
702
+ * - **Lexicons of THIS build only.** Not "any `@intentius/chant-lexicon-*`
703
+ * package on disk", and emphatically not "any bare specifier". A lexicon
704
+ * the build did not load is as out of scope as `node:fs`.
705
+ *
706
+ * chant #1995 — the "no subpaths" restriction above stays as documented for
707
+ * THIS function's caller ({@link resolveActiveLexiconExport}, #1063's
708
+ * data-export resolution): every lexicon's public data surface is reachable
709
+ * from the barrel, so a subpath buys that caller nothing and would cost the
710
+ * same cold-resolution risk. {@link isTrustedExecutableBinding} (#1093) asks a
711
+ * different question — not "does this specifier name a reachable data
712
+ * export" but "has the CLI already loaded this code" — and a project file
713
+ * reaching a lexicon's generated module by subpath instead of through the
714
+ * barrel is code the CLI already imported and executed either way, so that
715
+ * caller resolves a subpath's package root separately, via
716
+ * {@link bareSpecifierPackageRoot}, rather than widening this function.
717
+ */
718
+ function activeLexiconPackage(specifier, lexiconPackages) {
719
+ return lexiconPackages.has(specifier) ? specifier : undefined;
720
+ }
721
+ /**
722
+ * chant#3090 — {@link activeLexiconPackage} for a specifier imported by
723
+ * `fromFile`: `undefined` as well when a tsconfig `paths` entry maps the
724
+ * specifier ({@link pathsMappedTarget}), because the import then names a
725
+ * project file, not the package the build loaded. Only a specifier whose text
726
+ * already matches pays the tsconfig lookup.
727
+ */
728
+ function activeLexiconPackageImport(specifier, fromFile, lexiconPackages) {
729
+ if (activeLexiconPackage(specifier, lexiconPackages) === undefined)
730
+ return undefined;
731
+ return pathsMappedTarget(specifier, fromFile) === undefined ? specifier : undefined;
732
+ }
733
+ /**
734
+ * chant#2577 — the path-declared counterpart of {@link activeLexiconPackage}:
735
+ * the resolved module path when a relative or absolute `specifier` reaches
736
+ * one of this build's {@link FoldSession.lexiconModules}, `undefined`
737
+ * otherwise. A bare specifier is never resolved here, and a build with no
738
+ * path-declared lexicon resolves nothing.
739
+ */
740
+ function activeLexiconModule(specifier, fromFile, lexiconModules, resolvePathCache) {
741
+ if (lexiconModules.size === 0 || !isProjectImport(specifier, fromFile))
742
+ return undefined;
743
+ let modulePath;
744
+ try {
745
+ modulePath = resolveModulePathMemoized(specifier, fromFile, resolvePathCache);
746
+ }
747
+ catch {
748
+ return undefined;
749
+ }
750
+ return lexiconModules.has(modulePath) ? modulePath : undefined;
751
+ }
752
+ /** chant#2590 — true when absolute `file` lies under absolute directory `dir`. */
753
+ function isUnderDirectory(file, dir) {
754
+ return file.startsWith(dir.endsWith(sep) ? dir : dir + sep);
755
+ }
756
+ /** chant#2590 — true when `path` lies under one of the build's trusted path-lexicon directories. */
757
+ function inPathLexiconRoot(path, roots) {
758
+ return roots.some((root) => isUnderDirectory(path, root));
759
+ }
760
+ /**
761
+ * chant #1995 — the package-ROOT portion of a bare subpath specifier
762
+ * (`@intentius/chant-lexicon-azure/generated/index` ->
763
+ * `@intentius/chant-lexicon-azure`), or `undefined` when `specifier` is a
764
+ * relative/absolute path, is already a bare root with no subpath, or is
765
+ * malformed (a lone `@scope` with nothing after it).
766
+ *
767
+ * Text only, no resolution — the same discipline {@link activeLexiconPackage}
768
+ * documents and for the identical reason: resolving an arbitrary bare
769
+ * specifier to find out what package it belongs to is the pathological cold
770
+ * `require.resolve` chant#1020 measured. A scoped package (`@scope/name`)
771
+ * reserves its first two `/`-separated segments for the root; an unscoped
772
+ * package (`name`) reserves one.
773
+ */
774
+ function bareSpecifierPackageRoot(specifier) {
775
+ if (specifier.startsWith(".") || isAbsolute(specifier))
776
+ return undefined;
777
+ const segments = specifier.split("/");
778
+ if (specifier.startsWith("@")) {
779
+ return segments.length > 2 ? `${segments[0]}/${segments[1]}` : undefined;
780
+ }
781
+ return segments.length > 1 ? segments[0] : undefined;
782
+ }
783
+ /**
784
+ * chant #1063 — resolve one named import binding against an active lexicon
785
+ * package's REAL exports, for {@link buildExternals}.
786
+ *
787
+ * The lexicon module is imported (through the session-wide
788
+ * {@link FoldSession.importCache}, so at most one real `import()` per package
789
+ * per build) and the requested export read straight off it. That is the same
790
+ * module object the run path gets — `importModule` is a plain dynamic
791
+ * `import()` with no cache-busting — so what fold captures here is not a
792
+ * reconstruction of the lexicon's data but the identical value, down to
793
+ * object identity for `Azure.ResourceGroupLocation`-style singletons. It is
794
+ * also the same two-step (resolve path, then import) that
795
+ * `resolveImportedExport` has always used to reach a lexicon's constructors
796
+ * and intrinsic functions; the only thing new is that a plain DATA export is
797
+ * now reachable too.
798
+ *
799
+ * Callable exports are otherwise excluded. A lexicon's functions — its
800
+ * resource classes, composite factories, intrinsic implementations — already
801
+ * have dedicated resolution paths that know how to INVOKE them
802
+ * (`resolveResourceEntity`, `resolveCallExpression`, `reviveFoldedValue`), and
803
+ * binding an arbitrary one as a plain identifier value here would widen what
804
+ * folds in unmeasured ways. The one exception (chant #1966) is a function its
805
+ * lexicon registered with {@link IntrinsicDef.foldsEagerly} — the same
806
+ * per-name, lexicon-declared opt-in `foldsAsCall` is for an intrinsic's
807
+ * envelope-and-revive form, admitting this one because `fold()`
808
+ * (../fold/fold.ts) needs to CALL it eagerly — synchronously, before
809
+ * revival — to support a lexicon's own string-building helpers used inline
810
+ * (`matrix("os").toString()`, `` `${inputs("x")}` ``), where an envelope
811
+ * would still be a plain object when the enclosing template folds.
812
+ *
813
+ * Returns `undefined` — never throws — for a package that isn't an active
814
+ * lexicon, an export the package doesn't have, a non-admitted callable
815
+ * export, or an import that fails. The binding is then simply absent from
816
+ * `externals`, exactly as before, and `fold()`'s ordinary "unresolved
817
+ * identifier" failure still fires if the name is actually referenced.
818
+ */
819
+ async function resolveActiveLexiconExport(binding, fromFile, session) {
820
+ let modulePath;
821
+ if (activeLexiconPackageImport(binding.specifier, fromFile, session.lexiconPackages)) {
822
+ try {
823
+ modulePath = resolveModulePathMemoized(binding.specifier, fromFile, session.resolvePathCache);
824
+ }
825
+ catch {
826
+ return undefined;
827
+ }
828
+ }
829
+ else {
830
+ // chant#2577 — a lexicon declared by path, imported by its path.
831
+ modulePath = activeLexiconModule(binding.specifier, fromFile, session.lexiconModules, session.resolvePathCache);
832
+ if (modulePath === undefined)
833
+ return undefined;
834
+ }
835
+ let mod;
836
+ try {
837
+ mod = await importModuleMemoized(modulePath, session.importCache);
838
+ }
839
+ catch {
840
+ return undefined;
841
+ }
842
+ if (!(binding.imported in mod))
843
+ return undefined;
844
+ const value = mod[binding.imported];
845
+ if (typeof value === "function") {
846
+ const eager = session.intrinsics.some((i) => i.name === binding.imported && intrinsicCallFoldsEagerly(i));
847
+ if (!eager)
848
+ return undefined;
849
+ }
850
+ return { value };
851
+ }
852
+ /**
853
+ * chant#3247 — a plain data export of a lexicon package's own module, read off
854
+ * the real module (see the package-root branch of {@link buildExternals}).
855
+ * `undefined` for a missing export, a callable one, or a module that does not
856
+ * import.
857
+ */
858
+ async function packageDataExport(modulePath, name, session) {
859
+ let mod;
860
+ try {
861
+ mod = await importModuleMemoized(modulePath, session.importCache);
862
+ }
863
+ catch {
864
+ return undefined;
865
+ }
866
+ if (!(name in mod) || typeof mod[name] === "function")
867
+ return undefined;
868
+ return { value: mod[name] };
869
+ }
870
+ function resolveMemoized(node, ctx) {
871
+ const cached = ctx.memo.get(node);
872
+ if (cached)
873
+ return cached;
874
+ const promise = resolveLiveValue(node, ctx);
875
+ ctx.memo.set(node, promise);
876
+ return promise;
877
+ }
878
+ /**
879
+ * Attempt to resolve `node` to the real runtime value it would have if the
880
+ * file were executed. Understands the "spine" of composite consumption: a
881
+ * bare call (a composite factory, or a wrapper like `propagate()` — resolved
882
+ * through this file's imports, invoked for real with statically-folded
883
+ * arguments), a reference to an earlier top-level `const` bound to one of
884
+ * those (memoized — see {@link ResolveCtx.memo}), and dotted property access
885
+ * or `!` non-null assertion on the result (`web.deployment`, `web.pdb!`).
886
+ *
887
+ * Throws when the shape IS recognized (a call, a member access) but
888
+ * resolution genuinely fails — unresolved import, non-function import,
889
+ * folding a nested argument failed, member access on a non-composite value,
890
+ * the call itself threw. That failure should fall back the whole file to
891
+ * run, same as any other fold gap.
892
+ */
893
+ async function resolveLiveValue(node, ctx) {
894
+ if (ts.isParenthesizedExpression(node) ||
895
+ ts.isNonNullExpression(node) ||
896
+ ts.isAsExpression(node) ||
897
+ ts.isSatisfiesExpression(node)) {
898
+ return resolveLiveValue(node.expression, ctx);
899
+ }
900
+ if (ts.isIdentifier(node)) {
901
+ const binding = ctx.locals.get(node.text);
902
+ if (!binding) {
903
+ // chant #1020 — not a same-file local; try a cross-file resolution
904
+ // (a plain value, or the real live Declarable/CompositeInstance a
905
+ // sibling file's own fold produced, or a namespace import's synthetic
906
+ // exports object). Absent from `externals` falls through to
907
+ // `undefined`, exactly the pre-#1020 behavior for any unbound name.
908
+ if (ctx.externals.has(node.text)) {
909
+ const external = ctx.externals.get(node.text);
910
+ // chant #1373 — a function marker is callable, never a value. The
911
+ // export-by-name cases (`export const g = f`, `export { f }`) are
912
+ // handled by the declarator loop before it gets here.
913
+ if (isFoldableFunction(external)) {
914
+ throw cheapError(`function "${node.text}" used as a value is not foldable`);
915
+ }
916
+ return { value: external };
917
+ }
918
+ return undefined;
919
+ }
920
+ const resolvedSource = await resolveMemoized(binding.source, ctx);
921
+ if (resolvedSource === undefined)
922
+ return undefined;
923
+ if (binding.propKey === undefined)
924
+ return resolvedSource;
925
+ if (!isIndexableObject(resolvedSource.value)) {
926
+ throw cheapError(`destructured member "${binding.propKey}" is not on a composite value`);
927
+ }
928
+ return { value: resolvedSource.value[binding.propKey] };
929
+ }
930
+ if (ts.isCallExpression(node)) {
931
+ return { value: await resolveCallExpression(node, ctx) };
932
+ }
933
+ if (ts.isPropertyAccessExpression(node)) {
934
+ const base = await resolveLiveValue(node.expression, ctx);
935
+ if (base === undefined)
936
+ return undefined;
937
+ const key = node.name.text;
938
+ if (!isIndexableObject(base.value)) {
939
+ throw cheapError(`property access ".${key}" on a non-composite value is not foldable`);
940
+ }
941
+ return { value: base.value[key] };
942
+ }
943
+ return undefined;
944
+ }
945
+ /**
946
+ * Resolve a bare call expression — a composite factory call
947
+ * (`SomeComposite({...})`) or a wrapper that takes a composite instance and
948
+ * returns one (`propagate(SomeComposite({...}), {...})`). The callee must be
949
+ * a plain identifier bound by this file's own `import` (a namespace-import
950
+ * call like `ns.Foo(...)`, or a call to a function/composite DEFINED in this
951
+ * same file, can't be resolved without running the file — falls back, same
952
+ * as an unresolvable resource constructor).
953
+ *
954
+ * Two ways to get the value, tried in that order:
955
+ *
956
+ * 1. **Interpretation** (chant #1023) — the callee is an interpretable
957
+ * registered `Composite` defined in a PROJECT file, so its body is
958
+ * evaluated statically and nothing is imported or run. See
959
+ * {@link resolveInterpretableFactory} for the exact admissible subset.
960
+ * 2. **Invocation** (chant #1022) — everything else. Each argument is
961
+ * resolved (recursively, for a nested composite-call/member-access
962
+ * argument like `propagate`'s first one) or folded (for a plain props
963
+ * object literal via {@link fold}), the real function is imported and
964
+ * invoked, and the RESULT is what matters to the caller —
965
+ * {@link resolveLiveValue}'s callers decide what shape they need (a
966
+ * `CompositeInstance` for member access, an
967
+ * `isDeclarable`/`isCompositeInstance` value for a top-level export).
968
+ * No pre-check verifies the resolved callee is "really" a composite.
969
+ *
970
+ * Arm 2 is untouched by #1023, including for a factory arm 1 STARTED and then
971
+ * declined on: interpretation is all-or-nothing per call, and a decline is not
972
+ * a fold failure — it lands exactly where the code landed before this existed.
973
+ */
974
+ async function resolveCallExpression(node, ctx) {
975
+ // chant #1054 — reuses ../fold/subset's `callExpressionMessage` (the SAME
976
+ // builder `fold()` throws with for a call used as a prop value) rather
977
+ // than a hand-written "call expression as a value" copy: the two used to
978
+ // say different things for the identical rejection, which silently broke
979
+ // any tooling grouping fallback reasons by text.
980
+ if (!ts.isIdentifier(node.expression)) {
981
+ throw cheapError(callExpressionMessage(node));
982
+ }
983
+ const calleeName = node.expression.text;
984
+ const binding = ctx.imports.get(calleeName);
985
+ // chant #1373 — a project-local function with a foldable body (declared in
986
+ // this file, or imported from a sibling project file) is evaluated
987
+ // statically, through the same `fold()` branch a NESTED call to it takes.
988
+ // When its body turns out not to fold, an IMPORTED callee still has the
989
+ // pre-#1373 path below (interpret as a composite, else import and invoke) —
990
+ // a helper that wraps a composite call, say, keeps folding by invocation
991
+ // exactly as it did. A same-file callee has nothing to invoke without
992
+ // running the file, so the fold diagnostic is the verdict.
993
+ const local = ctx.externals.get(calleeName);
994
+ if (isFoldableFunction(local)) {
995
+ let foldFailure;
996
+ try {
997
+ const folded = fold(node, ctx.consts, ctx.intrinsics, ctx.externals);
998
+ return await reviveFoldedValue(folded, ctx, false);
999
+ }
1000
+ catch (err) {
1001
+ if (!(err instanceof Error))
1002
+ throw err;
1003
+ foldFailure = err;
1004
+ }
1005
+ // chant#2453 — a DECLARED function is judged by its body, and that is the
1006
+ // whole answer. There is no invocation arm here any more.
1007
+ //
1008
+ // There used to be: when the body did not fold, an imported callee was
1009
+ // imported and invoked instead. chant#2441 narrowed that to stop it
1010
+ // rescuing a depth refusal. The external corpus then showed what the rest
1011
+ // of it does, in a project nobody here maintains
1012
+ // (jhgaylor/infisical-chant, INTENTIUS/typescript-as-data#129):
1013
+ //
1014
+ // export const namingParams = namingParamsFromEnv();
1015
+ //
1016
+ // whose body reads `process.env`. `F-Eval-Ident` step 4 rejects `process`,
1017
+ // so the body does not fold — and chant imported the module and ran it,
1018
+ // folding the file to whatever the FOLDING PROCESS's environment held. The
1019
+ // file reported `fold`, which reads as "determined statically". Two people
1020
+ // folding the same source got different output and nothing said so.
1021
+ //
1022
+ // That is wrong in chant's own terms before it is wrong in J1's: `--fold`
1023
+ // is the value you would get by running, WITHOUT running, and this ran. So
1024
+ // the rejection propagates and the file runs, carrying the reason
1025
+ // `F-Eval-CallLocal` gives.
1026
+ //
1027
+ // `F-Call` step 6's invocation is for a binding that is neither a declared
1028
+ // function nor an interpretable composite, and it still happens — below,
1029
+ // in the arm this one is not. A composite factory is not a
1030
+ // `FoldableFunction`, so `resolveImportedCall` still interprets it and
1031
+ // still invokes it when interpretation declines.
1032
+ //
1033
+ // chant#2455 — unless the caller asked for `ι = executing` (spec 1.8),
1034
+ // which is this behaviour as a MODE rather than as a silent default. The
1035
+ // value is then what a run would compute in this process, environment and
1036
+ // all, which is exactly why it cannot be implicit.
1037
+ if (ctx.executing && binding) {
1038
+ try {
1039
+ return await resolveImportedCall(node, calleeName, binding, ctx);
1040
+ }
1041
+ catch (err) {
1042
+ throw cheapError(`${foldFailure.message}; invoking it instead failed: ${err instanceof Error ? err.message : String(err)}`);
1043
+ }
1044
+ }
1045
+ // Name the mode that would have folded it, so the reason is actionable
1046
+ // rather than only correct. Not under `sandbox`, where `executing` is not
1047
+ // an option the caller could take.
1048
+ if (binding && !ctx.sandbox) {
1049
+ throw cheapError(`${foldFailure.message} (the default mode does not invoke a declared project function; ` +
1050
+ "`executing` would run it and fold what it returns)");
1051
+ }
1052
+ throw foldFailure;
1053
+ }
1054
+ if (!binding) {
1055
+ throw cheapError(callExpressionMessage(node));
1056
+ }
1057
+ return resolveImportedCall(node, calleeName, binding, ctx);
1058
+ }
1059
+ /** The pre-#1373 two-arm resolution of an IMPORTED callee — see {@link resolveCallExpression}. */
1060
+ async function resolveImportedCall(node, calleeName, binding, ctx) {
1061
+ // chant #1023 — is this callee a composite whose body can be INTERPRETED
1062
+ // rather than run? Purely a static question (does the defining module parse,
1063
+ // does it declare this export as `Composite(<fn>, "<name>")`, is `<fn>`'s
1064
+ // body inside the admissible subset) — no argument has been evaluated yet
1065
+ // and nothing has been imported, so a `undefined` here has cost nothing and
1066
+ // changed nothing.
1067
+ const factory = await resolveInterpretableFactory(binding, ctx);
1068
+ if (factory) {
1069
+ const args = await resolveCallArguments(node, calleeName, ctx);
1070
+ const interpreted = await interpretCompositeFactory(factory, args, ctx, { file: ctx.file, node });
1071
+ if (interpreted)
1072
+ return interpreted.value;
1073
+ // Declined — the body's shape passed but something in it did not resolve
1074
+ // (an identifier the module's own imports don't reach, a constructor
1075
+ // --sandbox refuses). Fall through to arm 2 with the arguments ALREADY
1076
+ // evaluated, so a nested composite-call argument is not built twice.
1077
+ return invokeImportedCallee(node, calleeName, binding, ctx, args);
1078
+ }
1079
+ return invokeImportedCallee(node, calleeName, binding, ctx);
1080
+ }
1081
+ /**
1082
+ * Arm 2 — import the module the callee came from and call it, in this
1083
+ * process, given ALREADY-RESOLVED argument values. Factored out of
1084
+ * {@link invokeImportedCallee} (chant #1174) so a caller with no raw
1085
+ * `ts.CallExpression` node — {@link resolveCompositeCall}, the nested-value
1086
+ * counterpart to {@link resolveCallExpression} — can reach the identical
1087
+ * refusal/resolve/import/callable-check/call sequence #1022 established,
1088
+ * without inventing a second copy of it.
1089
+ */
1090
+ async function invokeResolvedCallee(calleeName, binding, ctx, args) {
1091
+ // chant #1093 — THE gap this check exists for. Invoking the callee runs
1092
+ // project code (the factory body, and its whole module's top level) in the
1093
+ // CLI's own process; under --sandbox that has to happen in the child
1094
+ // instead, so refuse here and let the file fall back to the sandboxed run
1095
+ // path. See {@link sandboxedExecutionRefusal}.
1096
+ const refusal = sandboxedExecutionRefusal(binding, ctx, calleeName, "composite factory");
1097
+ if (refusal)
1098
+ throw cheapError(refusal);
1099
+ // spec 2.0, `F-Call` step 5 — a callee that resolves to a PROJECT FILE is
1100
+ // not imported and invoked here. Invoking it runs the factory body, and its
1101
+ // whole module's top level, in the CLI's own process, and that is the
1102
+ // project's own code rather than a package's.
1103
+ //
1104
+ // Step 2's project-owned invocation moved behind `ι = executing` at v0.72.3
1105
+ // (chant#2455). Step 6 is the same path and had not moved, which is the
1106
+ // whole of this change. The specification took the decision first: its
1107
+ // rationale says of `executing` that "it is never the default", and
1108
+ // `verdict.md` calls `open` the strict default.
1109
+ //
1110
+ // A package factory still runs here, which is what `F-Host-Admission`
1111
+ // governs. A project-file callee that a `Composite` registered is
1112
+ // INTERPRETED at step 4 and never reaches this line; only one that
1113
+ // interpretation declined does, and for that the verdict is `run`. Since
1114
+ // chant#3247 a package's registered composite is interpreted at step 4 too,
1115
+ // and reaches this line only when interpretation declined; it is a
1116
+ // package's code, so it is still invoked.
1117
+ // "Project file" is the specifier's ORIGIN, not its syntax.
1118
+ // {@link isProjectFileSpecifier} is true of any absolute path, and chant's
1119
+ // own modules are imported by absolute path throughout the tests, so the
1120
+ // shape test alone refuses `propagate` and every other host function chant
1121
+ // publishes. {@link isTrustedExecutableBinding} draws the line the rule
1122
+ // means: an active lexicon package or chant-core's own tree is a package's
1123
+ // code, and everything else reached by a relative or absolute specifier is
1124
+ // the project's.
1125
+ if (!ctx.executing && isProjectImport(binding.specifier, ctx.file) && !isTrustedExecutableBinding(binding, ctx)) {
1126
+ throw cheapError(`"${calleeName}" is imported from "${binding.specifier}", which is a project file; ` +
1127
+ "the default mode does not invoke project-owned code (`executing` would run it and fold what it returns)");
1128
+ }
1129
+ let modulePath;
1130
+ try {
1131
+ modulePath = resolveModulePathMemoized(binding.specifier, ctx.file, ctx.resolvePathCache);
1132
+ }
1133
+ catch (err) {
1134
+ throw cheapError(`could not resolve import "${binding.specifier}" for "${calleeName}": ${err instanceof Error ? err.message : String(err)}`);
1135
+ }
1136
+ // Reuses the SAME import mechanism the run path uses ({@link importModule},
1137
+ // ../discovery/import.ts — a plain-path `import()`) rather than a
1138
+ // `pathToFileURL(...)`-wrapped one. For a resource constructor (a
1139
+ // stateless lexicon class) the two would be interchangeable, but a
1140
+ // composite-call resolution (#1023) can import a SIBLING PROJECT FILE
1141
+ // (e.g. `import { network } from "./network"`, not just a lexicon
1142
+ // package) that `discover()`'s own per-file loop ALSO independently
1143
+ // processes — if that file falls back to run and imports the same
1144
+ // sibling for a live value (`network.vpc.VpcId`), the two import calls
1145
+ // MUST resolve to the identical cached module instance, or the run side
1146
+ // ends up with a second, distinct `network`/`vpc` object whose AttrRefs
1147
+ // can never be matched back to the entity fold already registered
1148
+ // (`Cannot serialize AttrRef ...: logical name not set`).
1149
+ let mod;
1150
+ try {
1151
+ mod = await importModuleMemoized(modulePath, ctx.importCache);
1152
+ }
1153
+ catch (err) {
1154
+ throw cheapError(`could not import "${binding.specifier}" to resolve "${calleeName}": ${err instanceof Error ? err.message : String(err)}`);
1155
+ }
1156
+ const Fn = mod[binding.imported];
1157
+ if (typeof Fn !== "function") {
1158
+ throw cheapError(`"${binding.imported}" from "${binding.specifier}" is not a function`);
1159
+ }
1160
+ executionCounts.factoryInvocations += 1;
1161
+ // chant#3247 — a relative import inside a lexicon package's own composite is
1162
+ // the package's code, not the project's.
1163
+ const packageInternal = ctx.packageRoot !== undefined && isUnderDirectory(modulePath, ctx.packageRoot);
1164
+ if (isProjectImport(binding.specifier, ctx.file) && !packageInternal)
1165
+ executionCounts.projectFactoryInvocations += 1;
1166
+ const result = Fn(...args);
1167
+ // chant#3608 — a host composite invoked rather than interpreted (one the
1168
+ // host publishes, or one whose body interpretation declined) returns the
1169
+ // host's own instance. Mark it as the interpreted form is marked, so an
1170
+ // instance exported whole is expanded and its members, whole or
1171
+ // destructured, are known to come from a composite.
1172
+ const compositeName = Fn.compositeName;
1173
+ if (typeof compositeName === "string" && isIndexableObject(result) && !isCompositeInstance(result)) {
1174
+ markHostCompositeInstance(result, compositeName);
1175
+ }
1176
+ return result;
1177
+ }
1178
+ /**
1179
+ * Arm 2's original entry point — a raw call-expression node in hand. Resolves
1180
+ * arguments (unless the caller already has them) and delegates to
1181
+ * {@link invokeResolvedCallee}. Unchanged from #1022 in both behavior and
1182
+ * ORDER (refusal, resolve, import, callable check, arguments, call); #1023
1183
+ * factored the body out once already, #1174 a second time.
1184
+ *
1185
+ * @param args - Already-evaluated arguments, when the caller has them.
1186
+ */
1187
+ async function invokeImportedCallee(node, calleeName, binding, ctx, args) {
1188
+ const callArgs = args ?? (await resolveCallArguments(node, calleeName, ctx));
1189
+ return invokeResolvedCallee(calleeName, binding, ctx, callArgs);
1190
+ }
1191
+ /**
1192
+ * Resolve a composite-factory call used as a NESTED value — chant #1174's
1193
+ * `<Identifier>(...).step` idiom ({@link FoldedCompositeStepCall}) — given
1194
+ * its callee NAME and already-REVIVED argument values. The nested-value
1195
+ * counterpart to {@link resolveCallExpression}, which the top-level
1196
+ * declarator path (`export const x = Checkout({...})`) already used: the
1197
+ * raw `ts.CallExpression` node isn't needed here because
1198
+ * {@link resolveInterpretableFactory} only needs the import `binding`, and
1199
+ * {@link interpretCompositeFactory}/{@link invokeResolvedCallee} only need
1200
+ * the resolved argument values — which `fold()`'s envelope already folded
1201
+ * and {@link reviveFoldedValue} has already revived by the time this runs.
1202
+ *
1203
+ * Same two arms, same order, as the top-level path: interpret a registered
1204
+ * `Composite` (chant #1023; a lexicon package's own since chant#3247) when its
1205
+ * body qualifies, otherwise import and invoke for real — the arm a
1206
+ * lexicon-package composite whose body leaves the subset (`Checkout`,
1207
+ * `SetupNode`, …) takes, exactly as it does for a top-level
1208
+ * `export const x = Checkout({...})`. An unbound
1209
+ * callee name throws the same "function call as a value" message `fold()`
1210
+ * itself would have, for a caller with no `FoldedCompositeStepCall` special
1211
+ * case to fall into.
1212
+ */
1213
+ async function resolveCompositeCall(calleeName, args, ctx) {
1214
+ const binding = ctx.imports.get(calleeName);
1215
+ if (!binding)
1216
+ throw cheapError(`unresolved identifier: ${calleeName}`);
1217
+ const factory = await resolveInterpretableFactory(binding, ctx);
1218
+ if (factory) {
1219
+ const interpreted = await interpretCompositeFactory(factory, args, ctx);
1220
+ if (interpreted)
1221
+ return interpreted.value;
1222
+ // Declined — falls through to arm 2 with the SAME arguments, so a nested
1223
+ // composite-call argument (or a live cross-file reference) is not
1224
+ // resolved a second time. Identical discipline to `resolveImportedCall`.
1225
+ }
1226
+ return invokeResolvedCallee(calleeName, binding, ctx, args);
1227
+ }
1228
+ /**
1229
+ * Resolve a call's arguments to the real values the callee would receive —
1230
+ * factored out of {@link resolveCallExpression} (chant #1023) so the
1231
+ * interpretation and invocation arms consume ONE evaluation of them. Anything
1232
+ * else would evaluate a nested composite-call argument twice and hand the
1233
+ * callee the second instance while the first was already wired into an
1234
+ * `AttrRef`.
1235
+ *
1236
+ * chant #1112 — ONE rule for a registered authoring helper's arguments,
1237
+ * applied at BOTH sites that can invoke one. `reviveHelperCall` (the
1238
+ * nested-value site, #1082) already revives a helper's arguments with
1239
+ * `requireLiveRefs`, because a helper reads THROUGH its ref (`output()`
1240
+ * derefs the `WeakRef` parent) and a look-alike `{__attrRef}` envelope
1241
+ * makes it produce a wrong result rather than none. This site — a
1242
+ * top-level `export const oArn = output(bucket.Arn, "oArn")` — took the
1243
+ * composite-factory rule (`false`) instead, which is right for a factory
1244
+ * (its props keep the envelope, and the serializer's own walker resolves
1245
+ * it) and wrong for a helper. It went unnoticed while the resulting
1246
+ * `LexiconOutput` was being discarded anyway; with the export namespace
1247
+ * now collected in full, a same-file `output(...)` would reach the
1248
+ * serializer holding an inert envelope where the run path has a real
1249
+ * reference. Rejected here instead, which falls the file back to run —
1250
+ * absent output, never a wrong one.
1251
+ */
1252
+ async function resolveCallArguments(node, calleeName, ctx) {
1253
+ const helperArgs = isFoldableHelperName(calleeName);
1254
+ const args = [];
1255
+ for (const argNode of node.arguments) {
1256
+ const live = await resolveLiveValue(argNode, ctx);
1257
+ if (live !== undefined) {
1258
+ args.push(live.value);
1259
+ continue;
1260
+ }
1261
+ // chant#3610 — the bindings hold for this argument only, so a read of the
1262
+ // same name outside any argument still refuses, whatever the declarator order.
1263
+ const bound = await resolveSameFileCallReferences(argNode, ctx);
1264
+ try {
1265
+ // chant #1039 — a folded (non-live) argument may itself contain a
1266
+ // registered intrinsic tagged template; revive it into the real value
1267
+ // before the composite factory actually runs on it (see the "Intrinsic
1268
+ // revival" section below `resolveResourceEntity` uses the same way).
1269
+ args.push(await reviveFoldedValue(fold(argNode, ctx.consts, ctx.intrinsics, ctx.externals), ctx, helperArgs));
1270
+ }
1271
+ finally {
1272
+ for (const name of bound)
1273
+ ctx.externals.delete(name);
1274
+ }
1275
+ }
1276
+ return args;
1277
+ }
1278
+ /**
1279
+ * chant#3325 — a composite call's props can name a same-file `const` bound to
1280
+ * another call (`left: notes.table`, with `export const notes =
1281
+ * TenantTable({...})` earlier in the file). `fold()` cannot answer that name:
1282
+ * it is synchronous, and the value is a composite instance only the live spine
1283
+ * builds. So before the argument folds, each such name it mentions is resolved
1284
+ * through {@link resolveMemoized}, the same promise the declarator and every
1285
+ * other reference to it share, and the instance goes into `externals`, where
1286
+ * `fold()`'s identifier branch reads it as it reads a pre-built `new` or tag
1287
+ * const (#1169, #3196).
1288
+ *
1289
+ * Only a call `fold()` has no case for qualifies: an imported callee that is
1290
+ * neither a registered authoring helper nor a registered intrinsic. For
1291
+ * anything else the name is left alone and folds exactly as before. A
1292
+ * resolution that fails, or gives anything but a Declarable or a composite
1293
+ * instance, is left alone too, so the reference rejects with the message it
1294
+ * always had. Inside a factory body `locals` is empty, so nothing here applies.
1295
+ *
1296
+ * chant#3610 — returns the names it bound, which the caller removes from
1297
+ * `externals` once the argument has folded. A host composite's instance
1298
+ * counts as a composite instance here, as it does everywhere else.
1299
+ */
1300
+ async function resolveSameFileCallReferences(node, ctx) {
1301
+ const names = new Set();
1302
+ const visit = (n) => {
1303
+ if (ts.isIdentifier(n)) {
1304
+ if (isUnfoldableSameFileCall(n.text, ctx))
1305
+ names.add(n.text);
1306
+ return;
1307
+ }
1308
+ if (ts.isFunctionLike(n) || ts.isTypeNode(n))
1309
+ return;
1310
+ if (ts.isPropertyAccessExpression(n))
1311
+ return visit(n.expression);
1312
+ if (ts.isPropertyAssignment(n)) {
1313
+ if (ts.isComputedPropertyName(n.name))
1314
+ visit(n.name);
1315
+ return visit(n.initializer);
1316
+ }
1317
+ ts.forEachChild(n, visit);
1318
+ };
1319
+ visit(node);
1320
+ const bound = [];
1321
+ for (const name of names) {
1322
+ let resolved;
1323
+ try {
1324
+ resolved = await resolveMemoized(ctx.locals.get(name).source, ctx);
1325
+ }
1326
+ catch {
1327
+ continue;
1328
+ }
1329
+ if (resolved &&
1330
+ (isDeclarable(resolved.value) ||
1331
+ isCompositeInstance(resolved.value) ||
1332
+ hostCompositeName(resolved.value) !== undefined)) {
1333
+ ctx.externals.set(name, resolved.value);
1334
+ bound.push(name);
1335
+ }
1336
+ }
1337
+ return bound;
1338
+ }
1339
+ /** A same-file `const name = Callee(...)` that `fold()` would refuse as a function call used as a value. */
1340
+ function isUnfoldableSameFileCall(name, ctx) {
1341
+ if (ctx.externals.has(name))
1342
+ return false;
1343
+ const binding = ctx.locals.get(name);
1344
+ if (!binding || binding.propKey !== undefined || ctx.consts.get(name) !== binding.source)
1345
+ return false;
1346
+ const source = binding.source;
1347
+ if (!ts.isCallExpression(source) || !ts.isIdentifier(source.expression))
1348
+ return false;
1349
+ const callee = source.expression.text;
1350
+ return (ctx.imports.has(callee) &&
1351
+ !isFoldableHelperName(callee) &&
1352
+ !ctx.intrinsics.some((i) => i.name === callee) &&
1353
+ !isFoldableFunction(ctx.externals.get(callee)));
1354
+ }
1355
+ // ─────────────────────────────────────────────────────────────────────────
1356
+ // Composite factory interpretation (chant #1023, epic #1019 Phase 5).
1357
+ //
1358
+ // THE CONTRACT — the admissible factory subset, in ../fold/subset.ts's style.
1359
+ //
1360
+ // A composite is a factory function, and #1022 got its value the only way it
1361
+ // could: by importing the defining module and CALLING it. That is the last
1362
+ // place `chant build --fold` executes project-authored code in the CLI's own
1363
+ // process (chant #1093), and the reason `--sandbox` has to demote such a file
1364
+ // to the sandboxed child instead of folding it (chant #1111). This section
1365
+ // removes the call for the factories whose bodies can be evaluated instead.
1366
+ //
1367
+ // A call is interpreted when ALL of the following hold. Every one of them is
1368
+ // checked BEFORE any argument is evaluated or anything is constructed, so a
1369
+ // factory outside the subset costs a parse and nothing else:
1370
+ //
1371
+ // 1. **The callee is bound by an `import` from a PROJECT FILE** (a
1372
+ // relative/absolute specifier, or one a tsconfig `paths` entry maps), **or
1373
+ // from an ACTIVE LEXICON PACKAGE of this build whose module is TypeScript
1374
+ // source** (chant#3247). The package case is matched by text first — the
1375
+ // specifier, or its package root for a subpath, against
1376
+ // {@link FoldSession.lexiconPackages}, exactly as
1377
+ // {@link isTrustedExecutableBinding}'s arm 1 matches it — and only then
1378
+ // resolved; a module that resolves to compiled JavaScript or a declaration
1379
+ // file is not interpreted, so a package that ships only `dist` keeps
1380
+ // invoking. Every chant lexicon routes its exports at `./src/*.ts`
1381
+ // (check-lexicon tier 1), so in practice the source is always there.
1382
+ // Interpreting buys provenance: the run path cannot say which parameter
1383
+ // fed a field, and interpretation can (#2161, #3246).
1384
+ //
1385
+ // The trust boundary does not move. A package's composite is interpreted
1386
+ // with that PACKAGE's directory ({@link FactoryModuleScope.packageRoot})
1387
+ // as a trusted lexicon root for the length of its body and nothing else:
1388
+ // the package's own relative imports are trusted under `--sandbox` the way
1389
+ // a subpath of the same package already is, and they are never folded as
1390
+ // project files. The package was imported and run by `loadPlugins` before
1391
+ // discovery began, and every module of it is reachable as a subpath, so
1392
+ // reading its source adds no trust. What it may NOT do is reach outside
1393
+ // that directory: a relative import that leaves the package root is not
1394
+ // followed. A lexicon this build did not load stays as out of scope as
1395
+ // any other bare specifier.
1396
+ // 2. **The defining module declares that export as `Composite(<fn>, "<name>")`**
1397
+ // — a top-level `export const <name> = Composite(...)`, where `Composite`
1398
+ // is bound in THAT module to an import of chant's own
1399
+ // ({@link isChantOwnedHelperBinding}, the same provenance question #1082
1400
+ // asks of an authoring helper, and the same answer). This is #1023's
1401
+ // "recognize the callee is a registered Composite": a bare call to
1402
+ // anything else — a project helper function, a `withDefaults(...)`
1403
+ // wrapper, an `import`-then-`export` alias — is not recognized and is not
1404
+ // interpreted. The defining module may sit behind BARREL re-exports
1405
+ // (chant#3247): `export { X } from "./x"`, `export { Y as X } from "./x"`
1406
+ // and `export * from "./x"` are followed, relative specifiers only, at most
1407
+ // {@link MAX_REEXPORT_HOPS} deep, and never out of a package's root. A
1408
+ // module that declares the name itself is answered by that declaration.
1409
+ // 3. **`<fn>` is an arrow/function expression taking at most one parameter**,
1410
+ // bound as a plain identifier or a simple object binding pattern (no
1411
+ // default, rest, or nested pattern).
1412
+ // 4. **Its body is a single expression, or a block of `const` declarations
1413
+ // followed by one `return`** — and nothing else. No `if`, no `throw`, no
1414
+ // loop, no `let`/`var`, no nested function declaration, no bare expression
1415
+ // statement. This is the line a project's hand-rolled composite modules
1416
+ // fall outside (module-level `buildXxx()` helpers with `if`/`throw` and
1417
+ // `.map()`), and they are meant to: they keep invoking, exactly as before.
1418
+ // 5. **Every expression in it is inside the fold subset, extended with the
1419
+ // two things a factory body exists to do**: `new Type(...)` in ANY value
1420
+ // position (a member, a nested property object, an array element), and a
1421
+ // call through a bare identifier. Both are shape-admissible here and
1422
+ // resolution-checked at evaluation, the same permissive-shape /
1423
+ // strict-evaluation split ../fold/subset.ts documents for identifier
1424
+ // binding and helper provenance.
1425
+ //
1426
+ // Everything else is out: a computed key, a method call (`naming.name(...)` —
1427
+ // the callee is a property access, not an identifier), an array method, an
1428
+ // operator `fold()` doesn't implement, `await`, a class expression, a template
1429
+ // with a computed tag.
1430
+ //
1431
+ // WHY THIS IS NOT IN ../fold/subset.ts. That module is the shared, shape-only
1432
+ // predicate `fold()` and EVL both read, and its own contract is that it may
1433
+ // only ever be PERMISSIVE relative to `fold()`, never stricter. Admissibility
1434
+ // here is not a shape question at all: rules 1 and 2 need the module graph —
1435
+ // which file a name came from, and what that file declares — exactly like
1436
+ // #1082's authoring-helper provenance, which subset.ts documents (point 2b)
1437
+ // as deliberately out for the same reason. Putting rules 3-5 there alone would
1438
+ // describe a subset no caller could act on without also answering 1 and 2, and
1439
+ // putting all five there would mean teaching a syntax-only lint rule to
1440
+ // resolve imports. The shape half stays checkable by anything that wants it —
1441
+ // {@link findFactorySubsetViolation} takes a lone `ts` node — but the shared
1442
+ // predicate is not made stricter, or wider, by this issue.
1443
+ //
1444
+ // WHAT IS PRESERVED. Interpretation is not a second construction path:
1445
+ //
1446
+ // - **The instances are real, and made by the lexicon's own constructors.**
1447
+ // A `new Role({...})` in the body resolves `Role` through the DEFINING
1448
+ // module's imports and calls that class, the same class the run path would
1449
+ // have called from the same resolved module path.
1450
+ // - **Sibling references are live, not symbolic.** `role.Arn` reads the
1451
+ // attribute off the `Role` instance this interpretation just built, so it
1452
+ // is a genuine `AttrRef` wired to a genuine parent — identical to running
1453
+ // the factory, and strictly better than a top-level fold's `{__attrRef}`
1454
+ // envelope (an intrinsic that inspects its argument, `Sub`/`Ref`, gets what
1455
+ // it expects here).
1456
+ // - **The instance is assembled by `Composite()` itself.** The members go
1457
+ // back through chant's own {@link Composite} (../composite.ts) rather than
1458
+ // through a hand-built look-alike, so member validation, the non-enumerable
1459
+ // `members`/`_definition` layout `propagate()` and `expandComposite()`
1460
+ // depend on, and provenance stamping are the run path's, by construction
1461
+ // and not by resemblance.
1462
+ // - **`propagate()` still works, untouched.** It is a chant-owned import, so
1463
+ // `propagate(SomeComposite({...}), {...})` invokes the real `propagate` on
1464
+ // the interpreted instance and mutates it in place exactly as it does a run
1465
+ // one — which is why instance identity has to be a real `CompositeInstance`
1466
+ // and not a copy (chant #1097).
1467
+ // ─────────────────────────────────────────────────────────────────────────
1468
+ /**
1469
+ * A backstop for a composite that calls itself, directly or through a member.
1470
+ * {@link FoldSession.stack} cannot see it — the recursion happens entirely
1471
+ * inside one module's source, crossing no file boundary — so this is what
1472
+ * terminates it. Deliberately small: real composite nesting is 2-3 deep, and a
1473
+ * chain past this is a bug, not a design.
1474
+ *
1475
+ * Exhausting it falls the file back to run, naming this bound (#2370). That is
1476
+ * the second half of the specification's `F-Depth`: an implementation states
1477
+ * its bounds and, on exhaustion, falls back rather than fails. The other two —
1478
+ * {@link MAX_RESOLUTION_DEPTH} and `fold()`'s `MAX_FUNCTION_CALL_DEPTH` —
1479
+ * already did.
1480
+ *
1481
+ * It previously returned `undefined` here, which
1482
+ * {@link resolveInterpretableFactory}'s contract defines as "not
1483
+ * interpretable", so the caller invoked the factory instead and the file was
1484
+ * still reported as folded. That conflated two different facts: a factory
1485
+ * whose shape is outside the subset (invoking it is correct) and a factory we
1486
+ * stopped looking at (invoking it is a different evaluation than the one being
1487
+ * reported). The first is a verdict; the second was a silent degradation, and
1488
+ * a reader comparing chant's output against the reference implementation's
1489
+ * "fell back at depth 16" had nothing to explain the difference.
1490
+ */
1491
+ const MAX_INTERPRETATION_DEPTH = 16;
1492
+ /**
1493
+ * Exhaustion of {@link MAX_INTERPRETATION_DEPTH}, as its own type so it can
1494
+ * pass through {@link interpretCompositeFactory}'s decline.
1495
+ *
1496
+ * That decline — a blanket `catch { return undefined }` — is right for every
1497
+ * other failure inside a factory body: the body turned out not to interpret,
1498
+ * the caller invokes it instead, and the file still folds. It is wrong for
1499
+ * this one, because giving up at a depth bound is not a statement about the
1500
+ * body. Without a type to test, the depth error was caught by the *enclosing*
1501
+ * interpretation, declined, and the factory invoked — which is how the bound
1502
+ * came to be silent (#2370).
1503
+ */
1504
+ class InterpretationDepthError extends FoldError {
1505
+ }
1506
+ /**
1507
+ * Parse a composite-defining module into its static scope, memoized per build.
1508
+ * Returns `undefined` when the file can't be read or parsed — a decline, never
1509
+ * a throw: the caller falls through to the invocation arm, which will produce
1510
+ * its own (identical, pre-#1023) error if the module is genuinely broken.
1511
+ *
1512
+ * Reads and parses only. Nothing is imported, nothing is executed, and the
1513
+ * module's own imports are not resolved yet — see
1514
+ * {@link FactoryModuleScope.resolved}.
1515
+ */
1516
+ function readFactoryModule(modulePath, packageRoot, session) {
1517
+ // chant#3247 — keyed by the package root as well: the same file read as a
1518
+ // project file and as package code resolves its imports differently.
1519
+ const key = packageRoot === undefined ? modulePath : `${packageRoot}\0${modulePath}`;
1520
+ const cached = session.factoryModules.get(key);
1521
+ if (cached)
1522
+ return cached;
1523
+ const promise = readFactoryModuleCore(modulePath, packageRoot);
1524
+ session.factoryModules.set(key, promise);
1525
+ return promise;
1526
+ }
1527
+ async function readFactoryModuleCore(modulePath, packageRoot) {
1528
+ let sourceFile;
1529
+ try {
1530
+ const source = await readFile(modulePath, "utf-8");
1531
+ sourceFile = ts.createSourceFile(modulePath, source, ts.ScriptTarget.Latest, /* setParentNodes */ true);
1532
+ }
1533
+ catch {
1534
+ // Not a readable TypeScript source — an installed package's compiled
1535
+ // entry point reached through a path specifier, a missing file. Decline.
1536
+ return undefined;
1537
+ }
1538
+ const collected = collectImports(sourceFile);
1539
+ const consts = collectConsts(sourceFile);
1540
+ for (const [name] of [...consts]) {
1541
+ if (constResolvesToResource(consts, name, new Set()))
1542
+ consts.delete(name);
1543
+ }
1544
+ return {
1545
+ file: modulePath,
1546
+ sourceFile,
1547
+ packageRoot,
1548
+ consts,
1549
+ imports: collected.named,
1550
+ namespaceImports: collected.namespaces,
1551
+ };
1552
+ }
1553
+ /**
1554
+ * Resolve a composite-defining module's own imports, once per module per
1555
+ * build. Guarded by {@link FoldSession.stack}, exactly like
1556
+ * {@link foldFileMemoized}: a module already being resolved further up the
1557
+ * same chain is a genuine cycle, and gets an empty scope (every identifier
1558
+ * then reads as unresolved, which declines the interpretation) rather than a
1559
+ * re-entry.
1560
+ */
1561
+ function factoryModuleScopeResolved(scope, session) {
1562
+ if (scope.resolved)
1563
+ return scope.resolved;
1564
+ if (session.stack.includes(scope.file) || session.stack.length >= MAX_RESOLUTION_DEPTH) {
1565
+ return Promise.resolve({ externals: new Map(), failures: new Map() });
1566
+ }
1567
+ session.stack.push(scope.file);
1568
+ const roots = scope.packageRoot === undefined ? [] : [scope.packageRoot];
1569
+ scope.resolved = buildExternals(scope.file, scope.imports, scope.namespaceImports, session, roots).finally(() => {
1570
+ const idx = session.stack.lastIndexOf(scope.file);
1571
+ if (idx !== -1)
1572
+ session.stack.splice(idx, 1);
1573
+ });
1574
+ return scope.resolved;
1575
+ }
1576
+ /**
1577
+ * Find `export const <exportName> = Composite(<fn>, "<name>")` at the top level
1578
+ * of `scope`'s module, verifying that `Composite` really is chant's own in that
1579
+ * module. Returns `undefined` for every other shape — see rule 2 of the
1580
+ * contract above.
1581
+ */
1582
+ function findCompositeDefinition(scope, exportName, ctx) {
1583
+ for (const statement of scope.sourceFile.statements) {
1584
+ if (!ts.isVariableStatement(statement))
1585
+ continue;
1586
+ if (!hasExportModifier(statement))
1587
+ continue;
1588
+ if ((statement.declarationList.flags & ts.NodeFlags.Const) === 0)
1589
+ continue;
1590
+ for (const decl of statement.declarationList.declarations) {
1591
+ if (!ts.isIdentifier(decl.name) || decl.name.text !== exportName)
1592
+ continue;
1593
+ const init = decl.initializer;
1594
+ if (!init || !ts.isCallExpression(init) || !ts.isIdentifier(init.expression))
1595
+ return undefined;
1596
+ // `Composite` must be bound, in THIS module, to an import of chant's
1597
+ // own — the identical provenance question #1082 asks of an authoring
1598
+ // helper, answered by the identical predicate. A project-local
1599
+ // `function Composite(...)` shadowing the name is not chant's, and a
1600
+ // call to it is not a registered composite.
1601
+ const compositeBinding = scope.imports.get(init.expression.text);
1602
+ if (!compositeBinding || compositeBinding.imported !== "Composite")
1603
+ return undefined;
1604
+ // chant's own is the answer for every chant project, and #1082's
1605
+ // provenance question is why: a project-local `function Composite(...)`
1606
+ // shadowing the name is not chant's. chant#2442 adds the case chant had
1607
+ // never had to consider, a HOST supplying the form (`F-Host-Composite`),
1608
+ // which only a caller that named its own host packages can reach. For a
1609
+ // chant build the set holds only `@intentius/chant-lexicon-*`, and no
1610
+ // lexicon exports a `Composite`, so nothing about a chant project moves.
1611
+ const chantOwned = isChantOwnedHelperBinding(compositeBinding, { ...ctx, file: scope.file });
1612
+ const hostOwned = activeLexiconPackageImport(compositeBinding.specifier, scope.file, ctx.lexiconPackages) !== undefined ||
1613
+ activeLexiconModule(compositeBinding.specifier, scope.file, ctx.lexiconModules, ctx.resolvePathCache) !==
1614
+ undefined;
1615
+ if (!chantOwned && !hostOwned)
1616
+ return undefined;
1617
+ const [fnArg, nameArg] = init.arguments;
1618
+ if (!fnArg || (!ts.isArrowFunction(fnArg) && !ts.isFunctionExpression(fnArg)))
1619
+ return undefined;
1620
+ // `Composite()`'s own default when the name is omitted (../composite.ts).
1621
+ // COR017 requires the literal in practice; anything that is not a plain
1622
+ // string literal is not something to guess at.
1623
+ if (nameArg !== undefined && !ts.isStringLiteral(nameArg))
1624
+ return undefined;
1625
+ return { fn: fnArg, compositeName: nameArg ? nameArg.text : "anonymous", hostForm: !chantOwned };
1626
+ }
1627
+ }
1628
+ return undefined;
1629
+ }
1630
+ /**
1631
+ * chant#3247 — how many barrel re-exports {@link findFactoryDefinition}
1632
+ * follows from the imported module to the one that defines the composite. A
1633
+ * lexicon's barrel reaches its composites in two (`src/index.ts` ->
1634
+ * `src/composites/index.ts` -> the file), a dialect subpath in three; past
1635
+ * this the lookup declines, and the composite is invoked as before.
1636
+ */
1637
+ const MAX_REEXPORT_HOPS = 8;
1638
+ /** A TypeScript source file, not compiled JavaScript and not a declaration file. */
1639
+ function isTypeScriptSource(path) {
1640
+ return /\.[cm]?tsx?$/.test(path) && !/\.d\.[cm]?ts$/.test(path);
1641
+ }
1642
+ /** Process-wide memo for {@link lexiconPackageDirectory}, keyed by package name and directory. */
1643
+ const lexiconPackageDirectoryCache = new Map();
1644
+ /**
1645
+ * chant#3247 — the directory of package `packageName` that holds the resolved
1646
+ * `modulePath`: the nearest ancestor whose `package.json` names that package.
1647
+ * `undefined` when none does before a `node_modules` directory or the
1648
+ * filesystem root, which declines interpretation. Reads `package.json` files
1649
+ * only; nothing is resolved or imported.
1650
+ */
1651
+ function lexiconPackageDirectory(modulePath, packageName) {
1652
+ const start = dirname(modulePath);
1653
+ const key = `${packageName}\0${start}`;
1654
+ if (lexiconPackageDirectoryCache.has(key))
1655
+ return lexiconPackageDirectoryCache.get(key);
1656
+ let found;
1657
+ for (let dir = start; basename(dir) !== "node_modules";) {
1658
+ const pkgJson = join(dir, "package.json");
1659
+ if (existsSync(pkgJson)) {
1660
+ try {
1661
+ if (JSON.parse(readFileSync(pkgJson, "utf-8")).name === packageName) {
1662
+ found = dir;
1663
+ break;
1664
+ }
1665
+ }
1666
+ catch {
1667
+ // An unreadable package.json is not the package's; keep walking.
1668
+ }
1669
+ }
1670
+ const parent = dirname(dir);
1671
+ if (parent === dir)
1672
+ break;
1673
+ dir = parent;
1674
+ }
1675
+ lexiconPackageDirectoryCache.set(key, found);
1676
+ return found;
1677
+ }
1678
+ /**
1679
+ * Rule 1 of the contract: where an import of a composite leads, or `undefined`
1680
+ * when the import is not one interpretation follows. No module is imported.
1681
+ *
1682
+ * - A project import (relative, absolute, or `paths`-mapped) resolves to its
1683
+ * file. Inside a package composite's body ({@link ResolveCtx.packageRoot})
1684
+ * it must stay inside that package.
1685
+ * - A bare specifier naming an active lexicon package, root or subpath, by
1686
+ * text, resolves through `node_modules` to a TypeScript source file inside
1687
+ * that package.
1688
+ * - Anything else is not followed.
1689
+ */
1690
+ function factoryOrigin(specifier, fromFile, ctx) {
1691
+ if (isProjectImport(specifier, fromFile)) {
1692
+ let modulePath;
1693
+ try {
1694
+ modulePath = resolveModulePathMemoized(specifier, fromFile, ctx.resolvePathCache);
1695
+ }
1696
+ catch {
1697
+ return undefined;
1698
+ }
1699
+ if (ctx.packageRoot === undefined)
1700
+ return { modulePath };
1701
+ return isUnderDirectory(modulePath, ctx.packageRoot) ? { modulePath, packageRoot: ctx.packageRoot } : undefined;
1702
+ }
1703
+ // Text first, as {@link isTrustedExecutableBinding}'s arm 1 does: no bare
1704
+ // specifier outside the build's own lexicons is ever resolved here.
1705
+ const packageName = activeLexiconPackage(specifier, ctx.lexiconPackages) ??
1706
+ activeLexiconPackage(bareSpecifierPackageRoot(specifier) ?? "", ctx.lexiconPackages);
1707
+ if (packageName === undefined)
1708
+ return undefined;
1709
+ let modulePath;
1710
+ try {
1711
+ modulePath = resolveModulePathMemoized(specifier, fromFile, ctx.resolvePathCache);
1712
+ }
1713
+ catch {
1714
+ return undefined;
1715
+ }
1716
+ if (!isTypeScriptSource(modulePath))
1717
+ return undefined;
1718
+ const packageRoot = lexiconPackageDirectory(modulePath, packageName);
1719
+ return packageRoot === undefined ? undefined : { modulePath, packageRoot };
1720
+ }
1721
+ /**
1722
+ * chant#3247 — the re-exports of `exportName` that `sourceFile` forwards, in the order they are worth
1723
+ * trying. Empty when the module declares the name itself (its own
1724
+ * declaration is the answer, interpretable or not) or forwards it from nowhere
1725
+ * this follows. An explicit `export { ... } from` wins over `export *`, as it
1726
+ * does in the language; a type-only re-export, an `export * as ns`, and a bare
1727
+ * `export { X }` of an imported binding are not followed.
1728
+ */
1729
+ function reExportsOf(sourceFile, exportName) {
1730
+ const stars = [];
1731
+ for (const statement of sourceFile.statements) {
1732
+ if (ts.isExportDeclaration(statement)) {
1733
+ if (statement.isTypeOnly)
1734
+ continue;
1735
+ const spec = statement.moduleSpecifier && ts.isStringLiteral(statement.moduleSpecifier)
1736
+ ? statement.moduleSpecifier.text
1737
+ : undefined;
1738
+ const clause = statement.exportClause;
1739
+ if (clause === undefined) {
1740
+ if (spec !== undefined)
1741
+ stars.push({ specifier: spec, imported: exportName, star: true });
1742
+ continue;
1743
+ }
1744
+ if (!ts.isNamedExports(clause)) {
1745
+ if (clause.name.text === exportName)
1746
+ return [];
1747
+ continue;
1748
+ }
1749
+ for (const el of clause.elements) {
1750
+ if (el.name.text !== exportName)
1751
+ continue;
1752
+ if (spec === undefined || el.isTypeOnly)
1753
+ return [];
1754
+ return [{ specifier: spec, imported: (el.propertyName ?? el.name).text, star: false }];
1755
+ }
1756
+ continue;
1757
+ }
1758
+ if (!hasExportModifier(statement))
1759
+ continue;
1760
+ if (ts.isVariableStatement(statement)) {
1761
+ for (const decl of statement.declarationList.declarations) {
1762
+ if (ts.isIdentifier(decl.name) && decl.name.text === exportName)
1763
+ return [];
1764
+ }
1765
+ }
1766
+ else if ((ts.isFunctionDeclaration(statement) || ts.isClassDeclaration(statement) || ts.isEnumDeclaration(statement)) &&
1767
+ statement.name?.text === exportName) {
1768
+ return [];
1769
+ }
1770
+ }
1771
+ return stars;
1772
+ }
1773
+ /** A text pre-check for an `export *` target: false only when the file cannot export `name`. */
1774
+ async function mayProvideExport(file, name) {
1775
+ try {
1776
+ const text = await readFile(file, "utf-8");
1777
+ return text.includes(name) || /export\s*\*\s*from/.test(text);
1778
+ }
1779
+ catch {
1780
+ return false;
1781
+ }
1782
+ }
1783
+ /**
1784
+ * Rule 2, through barrels: find `exportName`'s `Composite(...)` declaration
1785
+ * starting at `origin` and following {@link reExportsOf} — relative
1786
+ * specifiers only, confined to `origin.packageRoot` when there is one, cycle
1787
+ * safe, and at most {@link MAX_REEXPORT_HOPS} deep. Reads and parses; imports
1788
+ * nothing.
1789
+ */
1790
+ async function findFactoryDefinition(origin, exportName, ctx, seen = new Set(), hops = 0) {
1791
+ seen.add(origin.modulePath);
1792
+ const scope = await readFactoryModule(origin.modulePath, origin.packageRoot, ctx.session);
1793
+ if (!scope)
1794
+ return undefined;
1795
+ const definition = findCompositeDefinition(scope, exportName, ctx);
1796
+ if (definition)
1797
+ return { scope, definition };
1798
+ if (hops >= MAX_REEXPORT_HOPS)
1799
+ return undefined;
1800
+ for (const { specifier, imported, star } of reExportsOf(scope.sourceFile, exportName)) {
1801
+ if (!isProjectFileSpecifier(specifier))
1802
+ continue;
1803
+ let target;
1804
+ try {
1805
+ target = resolveModulePathMemoized(specifier, scope.file, ctx.resolvePathCache);
1806
+ }
1807
+ catch {
1808
+ continue;
1809
+ }
1810
+ if (seen.has(target) || !isTypeScriptSource(target))
1811
+ continue;
1812
+ if (origin.packageRoot !== undefined && !isUnderDirectory(target, origin.packageRoot))
1813
+ continue;
1814
+ // An `export *` target that neither names the export nor forwards a star
1815
+ // of its own cannot provide it, so it is not parsed. That keeps a
1816
+ // lexicon's generated barrel (megabytes, behind `export *` in most
1817
+ // lexicons) from being parsed for every call through the package root.
1818
+ if (star && !(await mayProvideExport(target, imported)))
1819
+ continue;
1820
+ const found = await findFactoryDefinition({ modulePath: target, packageRoot: origin.packageRoot }, imported, ctx, seen, hops + 1);
1821
+ if (found)
1822
+ return found;
1823
+ }
1824
+ return undefined;
1825
+ }
1826
+ /**
1827
+ * Decide whether `binding`'s callee is an interpretable composite — rules 1-5
1828
+ * of the contract above, in that order, with no evaluation and no import.
1829
+ * `undefined` means "not interpretable", never "broken".
1830
+ */
1831
+ async function resolveInterpretableFactory(binding, ctx) {
1832
+ // Rule 1 — a project file, or an active lexicon package's TypeScript source.
1833
+ // A bare specifier outside the build's lexicons is refused on its text.
1834
+ const origin = factoryOrigin(binding.specifier, ctx.file, ctx);
1835
+ if (!origin)
1836
+ return undefined;
1837
+ // Not `undefined`: that answer means "not interpretable" and would invoke
1838
+ // the factory while still reporting the file folded. Giving up is a
1839
+ // different fact and falls the file back to run, naming the bound (#2370).
1840
+ if (ctx.interpretDepth >= MAX_INTERPRETATION_DEPTH) {
1841
+ throw new InterpretationDepthError(`interpretation depth exceeded ${MAX_INTERPRETATION_DEPTH} at "${binding.imported}" — a composite ` +
1842
+ "chain this deep is almost certainly recursive. Falling back to run rather than invoking the " +
1843
+ "factory, which would have reported this file as folded while evaluating it a different way.", 0, 0);
1844
+ }
1845
+ // Rule 2, following barrel re-exports to the defining module.
1846
+ const found = await findFactoryDefinition(origin, binding.imported, ctx);
1847
+ if (!found)
1848
+ return undefined;
1849
+ const { scope, definition } = found;
1850
+ // Rules 3-5.
1851
+ if (findFactorySubsetViolation(definition.fn) !== undefined)
1852
+ return undefined;
1853
+ return { scope, fn: definition.fn, compositeName: definition.compositeName, hostForm: definition.hostForm };
1854
+ }
1855
+ /**
1856
+ * The SHAPE half of the contract (rules 3-5) — a lone `ts` node in, a reason
1857
+ * string out, or `undefined` when the factory is admissible. Deliberately
1858
+ * takes nothing but the node: it is the half a caller with no module graph
1859
+ * could evaluate, and keeping it separable is what lets the doc above claim
1860
+ * the shape rules are checkable without the provenance ones.
1861
+ */
1862
+ export function findFactorySubsetViolation(fn) {
1863
+ // Rule 3 — at most one parameter, bound plainly.
1864
+ if (fn.parameters.length > 1)
1865
+ return "a composite factory takes a single props parameter";
1866
+ const param = fn.parameters[0];
1867
+ if (param) {
1868
+ if (param.dotDotDotToken)
1869
+ return "a rest parameter is not interpretable";
1870
+ if (param.initializer)
1871
+ return "a defaulted parameter is not interpretable";
1872
+ if (ts.isObjectBindingPattern(param.name)) {
1873
+ for (const el of param.name.elements) {
1874
+ if (bindingElementPropKey(el) === undefined) {
1875
+ return "a destructured props parameter with a rest, default, or nested element is not interpretable";
1876
+ }
1877
+ }
1878
+ }
1879
+ else if (!ts.isIdentifier(param.name)) {
1880
+ return "an array-destructured props parameter is not interpretable";
1881
+ }
1882
+ }
1883
+ // Rule 4 — a concise expression body, or `const`s then one `return`.
1884
+ if (!ts.isBlock(fn.body))
1885
+ return checkFactoryExpression(fn.body);
1886
+ const statements = fn.body.statements;
1887
+ if (statements.length === 0)
1888
+ return "an empty composite factory body has no members to interpret";
1889
+ for (let i = 0; i < statements.length; i += 1) {
1890
+ const statement = statements[i];
1891
+ const last = i === statements.length - 1;
1892
+ if (ts.isReturnStatement(statement)) {
1893
+ if (!last)
1894
+ return "an early `return` is not interpretable";
1895
+ if (!statement.expression)
1896
+ return "a composite factory must return its members";
1897
+ const violation = checkFactoryExpression(statement.expression);
1898
+ if (violation)
1899
+ return violation;
1900
+ continue;
1901
+ }
1902
+ if (last)
1903
+ return "a composite factory body must end in `return`";
1904
+ if (!ts.isVariableStatement(statement)) {
1905
+ return `\`${ts.SyntaxKind[statement.kind]}\` in a composite factory body is not interpretable`;
1906
+ }
1907
+ if ((statement.declarationList.flags & ts.NodeFlags.Const) === 0) {
1908
+ return "`let`/`var` in a composite factory body is not interpretable";
1909
+ }
1910
+ for (const decl of statement.declarationList.declarations) {
1911
+ if (!decl.initializer)
1912
+ return "an uninitialized `const` in a composite factory body is not interpretable";
1913
+ if (ts.isObjectBindingPattern(decl.name)) {
1914
+ for (const el of decl.name.elements) {
1915
+ if (bindingElementPropKey(el) === undefined) {
1916
+ return "a destructured `const` with a rest, default, or nested element is not interpretable";
1917
+ }
1918
+ }
1919
+ }
1920
+ else if (!ts.isIdentifier(decl.name)) {
1921
+ return "an array-destructured `const` is not interpretable";
1922
+ }
1923
+ const violation = checkFactoryExpression(decl.initializer);
1924
+ if (violation)
1925
+ return violation;
1926
+ }
1927
+ }
1928
+ return undefined;
1929
+ }
1930
+ /**
1931
+ * Rule 5 — the fold subset, extended with `new Type(...)` in any value
1932
+ * position and a call through a bare identifier.
1933
+ *
1934
+ * Shape only, in ../fold/subset.ts's sense: whether a `new`'s constructor or a
1935
+ * call's callee actually resolves to something invocable is settled at
1936
+ * evaluation, where the module graph is available. Every operator/key/member
1937
+ * rule is read from ../fold/subset.ts's own exported sets rather than
1938
+ * re-listed, so widening `fold()` widens this in the same commit.
1939
+ */
1940
+ function checkFactoryExpression(node) {
1941
+ if (ts.isParenthesizedExpression(node) ||
1942
+ ts.isAsExpression(node) ||
1943
+ ts.isSatisfiesExpression(node) ||
1944
+ ts.isNonNullExpression(node) ||
1945
+ ts.isTypeAssertionExpression(node)) {
1946
+ return checkFactoryExpression(node.expression);
1947
+ }
1948
+ if (ts.isStringLiteral(node) ||
1949
+ ts.isNoSubstitutionTemplateLiteral(node) ||
1950
+ ts.isNumericLiteral(node) ||
1951
+ ts.isIdentifier(node) ||
1952
+ node.kind === ts.SyntaxKind.TrueKeyword ||
1953
+ node.kind === ts.SyntaxKind.FalseKeyword ||
1954
+ node.kind === ts.SyntaxKind.NullKeyword) {
1955
+ return undefined;
1956
+ }
1957
+ if (ts.isNewExpression(node) || ts.isCallExpression(node)) {
1958
+ // A bare-identifier callee only — `ns.Foo(...)`, `arr.map(...)` and
1959
+ // `props.factory(...)` are all property accesses and all stay out, which
1960
+ // is what keeps a data transform (chant EVL010) from sneaking in.
1961
+ if (!ts.isIdentifier(node.expression)) {
1962
+ return ts.isNewExpression(node)
1963
+ ? `\`new ${briefNodeText(node.expression)}(...)\` needs a plain imported constructor to interpret`
1964
+ : callExpressionMessage(node);
1965
+ }
1966
+ for (const arg of node.arguments ?? []) {
1967
+ const violation = checkFactoryExpression(arg);
1968
+ if (violation)
1969
+ return violation;
1970
+ }
1971
+ return undefined;
1972
+ }
1973
+ if (ts.isTaggedTemplateExpression(node)) {
1974
+ if (!ts.isIdentifier(node.tag))
1975
+ return unsupportedExpressionMessage(node);
1976
+ if (ts.isNoSubstitutionTemplateLiteral(node.template))
1977
+ return undefined;
1978
+ for (const span of node.template.templateSpans) {
1979
+ const violation = checkFactoryExpression(span.expression);
1980
+ if (violation)
1981
+ return violation;
1982
+ }
1983
+ return undefined;
1984
+ }
1985
+ if (ts.isTemplateExpression(node)) {
1986
+ for (const span of node.templateSpans) {
1987
+ const violation = checkFactoryExpression(span.expression);
1988
+ if (violation)
1989
+ return violation;
1990
+ }
1991
+ return undefined;
1992
+ }
1993
+ if (ts.isObjectLiteralExpression(node)) {
1994
+ for (const prop of node.properties) {
1995
+ if (ts.isPropertyAssignment(prop)) {
1996
+ if (!isLiteralPropertyNameNode(prop.name))
1997
+ return computedPropertyNameMessage(prop.name);
1998
+ const violation = checkFactoryExpression(prop.initializer);
1999
+ if (violation)
2000
+ return violation;
2001
+ }
2002
+ else if (ts.isShorthandPropertyAssignment(prop)) {
2003
+ if (prop.objectAssignmentInitializer)
2004
+ return UNSUPPORTED_OBJECT_MEMBER_MESSAGE;
2005
+ }
2006
+ else if (ts.isSpreadAssignment(prop)) {
2007
+ const violation = checkFactoryExpression(prop.expression);
2008
+ if (violation)
2009
+ return violation;
2010
+ }
2011
+ else {
2012
+ return UNSUPPORTED_OBJECT_MEMBER_MESSAGE;
2013
+ }
2014
+ }
2015
+ return undefined;
2016
+ }
2017
+ if (ts.isArrayLiteralExpression(node)) {
2018
+ for (const el of node.elements) {
2019
+ const violation = checkFactoryExpression(ts.isSpreadElement(el) ? el.expression : el);
2020
+ if (violation)
2021
+ return violation;
2022
+ }
2023
+ return undefined;
2024
+ }
2025
+ if (ts.isPropertyAccessExpression(node))
2026
+ return checkFactoryExpression(node.expression);
2027
+ if (ts.isElementAccessExpression(node)) {
2028
+ if (!isLiteralElementKey(node.argumentExpression)) {
2029
+ return `dynamic element access [${briefNodeText(node.argumentExpression)}] is not interpretable`;
2030
+ }
2031
+ return checkFactoryExpression(node.expression);
2032
+ }
2033
+ if (ts.isPrefixUnaryExpression(node)) {
2034
+ if (!SUPPORTED_UNARY_OPERATORS.has(node.operator))
2035
+ return UNSUPPORTED_UNARY_MESSAGE;
2036
+ return checkFactoryExpression(node.operand);
2037
+ }
2038
+ if (ts.isBinaryExpression(node)) {
2039
+ if (!SUPPORTED_BINARY_OPERATORS.has(node.operatorToken.kind)) {
2040
+ return unsupportedBinaryMessage(node.operatorToken.kind);
2041
+ }
2042
+ return checkFactoryExpression(node.left) ?? checkFactoryExpression(node.right);
2043
+ }
2044
+ if (ts.isConditionalExpression(node)) {
2045
+ return (checkFactoryExpression(node.condition) ??
2046
+ checkFactoryExpression(node.whenTrue) ??
2047
+ checkFactoryExpression(node.whenFalse));
2048
+ }
2049
+ return unsupportedExpressionMessage(node);
2050
+ }
2051
+ /** `isLiteralPropertyName` narrowed to the node type {@link propName} accepts, without importing the type predicate's generic form twice. */
2052
+ function isLiteralPropertyNameNode(node) {
2053
+ return ts.isIdentifier(node) || ts.isStringLiteral(node) || ts.isNumericLiteral(node);
2054
+ }
2055
+ /**
2056
+ * chant #2161 — how one admissible factory's parameter is bound in its body,
2057
+ * plus the consts an attribution walk has to follow.
2058
+ *
2059
+ * Built from the SOURCE, not from the argument value: the question is which
2060
+ * expression a member's property was written as, not what it evaluated to. A
2061
+ * factory with no parameter at all still gets a recorder, so every path in its
2062
+ * members records as a literal the composite fixes, which is exactly what a
2063
+ * parameterless composite does.
2064
+ */
2065
+ function compositeParamRecorder(factory, moduleConsts, call) {
2066
+ const whole = new Set();
2067
+ const destructured = new Map();
2068
+ const param = factory.fn.parameters[0];
2069
+ if (param) {
2070
+ if (ts.isIdentifier(param.name)) {
2071
+ whole.add(param.name.text);
2072
+ }
2073
+ else if (ts.isObjectBindingPattern(param.name)) {
2074
+ for (const el of param.name.elements) {
2075
+ const key = bindingElementPropKey(el);
2076
+ if (key !== undefined && ts.isIdentifier(el.name))
2077
+ destructured.set(el.name.text, key);
2078
+ }
2079
+ }
2080
+ }
2081
+ // Body `const`s layered over the defining module's, so a hoisted value is
2082
+ // followed. `interpretCompositeFactory` deletes shadowed names from the
2083
+ // context's own const map as it binds values; this map is separate precisely
2084
+ // so the INITIALIZER stays available to the attribution walk.
2085
+ const consts = new Map(moduleConsts);
2086
+ const body = factory.fn.body;
2087
+ if (ts.isBlock(body)) {
2088
+ for (const statement of body.statements) {
2089
+ if (!ts.isVariableStatement(statement))
2090
+ continue;
2091
+ for (const decl of statement.declarationList.declarations) {
2092
+ if (ts.isIdentifier(decl.name) && decl.initializer)
2093
+ consts.set(decl.name.text, decl.initializer);
2094
+ }
2095
+ }
2096
+ }
2097
+ return { composite: factory.compositeName, scope: { whole, destructured }, consts, ...(call ? { call } : {}) };
2098
+ }
2099
+ /**
2100
+ * chant #2161 — record, for one `new Type({...})` written inside an interpreted
2101
+ * factory body, which factory parameter produced each of its property paths and
2102
+ * which paths the composite fixes.
2103
+ *
2104
+ * Best-effort and additive, exactly like {@link stampParamDependencies}: a
2105
+ * constructor called with no object literal records nothing, and
2106
+ * `setPathProvenance` keeps the first (innermost) writer.
2107
+ */
2108
+ function stampCompositeOrigins(entity, node, ctx) {
2109
+ const recorder = ctx.compositeParams;
2110
+ if (!recorder)
2111
+ return;
2112
+ if (typeof entity !== "object" || entity === null)
2113
+ return;
2114
+ let propsArg;
2115
+ for (const argument of node.arguments ?? []) {
2116
+ if (ts.isObjectLiteralExpression(argument)) {
2117
+ propsArg = argument;
2118
+ break;
2119
+ }
2120
+ }
2121
+ if (!propsArg)
2122
+ return;
2123
+ stampCompositeCall(entity, recorder);
2124
+ const origins = collectCompositeOrigins(propsArg, recorder.consts, recorder.scope, recorder.composite, recorder.call);
2125
+ for (const [path, origin] of Object.entries(origins))
2126
+ setPathProvenance(entity, path, origin);
2127
+ }
2128
+ /**
2129
+ * chant #3597 — record where the composite call that expanded `entity` was
2130
+ * written, beside the per-path origins and in the same pass. First writer
2131
+ * wins, as for every provenance field, so the innermost call is kept.
2132
+ */
2133
+ function stampCompositeCall(entity, recorder) {
2134
+ if (!recorder.call)
2135
+ return;
2136
+ setProvenance(entity, { compositeCall: sourceLocationOf(recorder.call.file, recorder.call.node) });
2137
+ }
2138
+ /**
2139
+ * chant #3212 — the same record for an entity a registered tag built inside an
2140
+ * interpreted factory body (`` table`CREATE TABLE ${props.name} ...` ``).
2141
+ *
2142
+ * A tag's props come from parsing its text, so no object literal says which
2143
+ * field each `${...}` landed in. The tag says so itself through
2144
+ * `setInterpolationFields`; a tag that does not leaves the entity's fields
2145
+ * `unknown`, as before.
2146
+ */
2147
+ function stampTagOrigins(entity, node, ctx) {
2148
+ const recorder = ctx.compositeParams;
2149
+ if (!recorder || !isDeclarable(entity) || !isResourceDeclarable(entity))
2150
+ return;
2151
+ const fields = getInterpolationFields(entity);
2152
+ if (!fields)
2153
+ return;
2154
+ const interpolations = ts.isTemplateExpression(node.template)
2155
+ ? node.template.templateSpans.map((span) => span.expression)
2156
+ : [];
2157
+ stampCompositeCall(entity, recorder);
2158
+ const origins = collectTagOrigins(interpolations, fields, entity.props, recorder.consts, recorder.scope, recorder.composite, recorder.call);
2159
+ for (const [path, origin] of Object.entries(origins))
2160
+ setPathProvenance(entity, path, origin);
2161
+ }
2162
+ /**
2163
+ * Interpret an admissible factory's body against `args`, and assemble the
2164
+ * result through chant's own {@link Composite}.
2165
+ *
2166
+ * Returns `undefined` — a DECLINE, not a failure — when anything in the body
2167
+ * fails to resolve (an identifier the defining module's imports don't reach, a
2168
+ * constructor `--sandbox` refuses, a nested call that isn't a composite). The
2169
+ * caller then invokes for real, landing exactly where it landed before #1023.
2170
+ * That asymmetry is deliberate: interpretation may only ever REMOVE an
2171
+ * execution, never introduce a fold failure that wasn't there.
2172
+ */
2173
+ async function interpretCompositeFactory(factory, args, ctx, call) {
2174
+ // A `Composite()` factory takes exactly one props argument. Anything else at
2175
+ // the call site means the callee is being used as something this doesn't
2176
+ // model.
2177
+ if (args.length > 1)
2178
+ return undefined;
2179
+ const { scope, fn } = factory;
2180
+ const resolved = await factoryModuleScopeResolved(scope, ctx.session);
2181
+ // The defining module's own scope, with the factory's locals layered on top.
2182
+ // `locals` is deliberately EMPTY: `resolveLiveValue`'s local-binding branch
2183
+ // exists to re-navigate a file's own top-level composite spine, which is not
2184
+ // what a name inside a factory body means. Body bindings live in `externals`
2185
+ // instead, where an identifier resolves to the value already computed for it
2186
+ // — which is what makes `role.Arn` a live `AttrRef` on the real instance.
2187
+ const consts = new Map(scope.consts);
2188
+ const externals = new Map(resolved.externals);
2189
+ const bodyCtx = {
2190
+ file: scope.file,
2191
+ consts,
2192
+ locals: new Map(),
2193
+ imports: scope.imports,
2194
+ namespaceImports: scope.namespaceImports,
2195
+ memo: new Map(),
2196
+ intrinsics: ctx.intrinsics,
2197
+ externals,
2198
+ crossFileFailures: resolved.failures,
2199
+ importCache: ctx.importCache,
2200
+ resolvePathCache: ctx.resolvePathCache,
2201
+ lexiconPackages: ctx.lexiconPackages,
2202
+ lexiconModules: ctx.lexiconModules,
2203
+ // chant#3247 — a package's composite trusts its own package directory for
2204
+ // the length of its body, and nothing outside it.
2205
+ lexiconRoots: scope.packageRoot === undefined ? ctx.lexiconRoots : [...ctx.lexiconRoots, scope.packageRoot],
2206
+ packageRoot: scope.packageRoot,
2207
+ sandbox: ctx.sandbox,
2208
+ executing: ctx.executing,
2209
+ session: ctx.session,
2210
+ interpretDepth: ctx.interpretDepth + 1,
2211
+ // chant #2161 — set unconditionally, and NOT inherited from `ctx`: a nested
2212
+ // composite records its own parameters, never the enclosing one's.
2213
+ compositeParams: compositeParamRecorder(factory, scope.consts, call),
2214
+ };
2215
+ const bind = (name, value) => {
2216
+ // A body binding SHADOWS a module-level const of the same name, so the
2217
+ // const has to go — `fold()` consults `consts` before `externals`.
2218
+ consts.delete(name);
2219
+ externals.set(name, value);
2220
+ };
2221
+ try {
2222
+ const param = fn.parameters[0];
2223
+ if (param) {
2224
+ const props = args[0];
2225
+ if (ts.isIdentifier(param.name)) {
2226
+ bind(param.name.text, props);
2227
+ }
2228
+ else if (ts.isObjectBindingPattern(param.name)) {
2229
+ if (!isIndexableObject(props))
2230
+ return undefined;
2231
+ for (const el of param.name.elements) {
2232
+ const key = bindingElementPropKey(el);
2233
+ if (key === undefined)
2234
+ return undefined;
2235
+ bind(el.name.getText(), props[key]);
2236
+ }
2237
+ }
2238
+ }
2239
+ const members = await interpretFactoryBody(fn, bodyCtx, bind);
2240
+ if (!isIndexableObject(members))
2241
+ return undefined;
2242
+ // Assembled by chant's own `Composite()` — see the contract's "what is
2243
+ // preserved" note. One definition object per interpreted call (rather than
2244
+ // one per module, as the run path has) is the single visible difference:
2245
+ // `_id` is a fresh symbol, which nothing outside `CompositeRegistry` — used
2246
+ // only by tests — reads.
2247
+ // chant#2442 — a HOST's registration form produces the host's own members,
2248
+ // so they are returned as the factory built them. Wrapping them in chant's
2249
+ // `Composite` would run chant's member validation, which asks for chant's
2250
+ // own `Declarable` and rejects an entity carrying a different marker —
2251
+ // rejecting it for not being chant's rather than for being malformed.
2252
+ // Chant's own form still goes through `Composite`, unchanged.
2253
+ const value = factory.hostForm
2254
+ ? markHostCompositeInstance(members, factory.compositeName)
2255
+ : (Composite(() => members, factory.compositeName))();
2256
+ executionCounts.factoryInterpretations += 1;
2257
+ return { value };
2258
+ }
2259
+ catch (err) {
2260
+ // Every other failure here means "this body did not interpret" and the
2261
+ // caller invokes instead. Exhausting the depth bound means "we stopped
2262
+ // looking", which has to reach the file's fold verdict rather than be
2263
+ // answered with an invocation (#2370).
2264
+ if (err instanceof InterpretationDepthError)
2265
+ throw err;
2266
+ return undefined;
2267
+ }
2268
+ }
2269
+ /** Evaluate an admissible factory body's statements in order, binding each `const`, and return the `return` expression's value. */
2270
+ async function interpretFactoryBody(fn, ctx, bind) {
2271
+ if (!ts.isBlock(fn.body))
2272
+ return interpretExpression(fn.body, ctx);
2273
+ for (const statement of fn.body.statements) {
2274
+ if (ts.isReturnStatement(statement)) {
2275
+ // `findFactorySubsetViolation` has already established this is the last
2276
+ // statement and has an expression.
2277
+ return interpretExpression(statement.expression, ctx);
2278
+ }
2279
+ const declarations = statement.declarationList.declarations;
2280
+ for (const decl of declarations) {
2281
+ const value = await interpretExpression(decl.initializer, ctx);
2282
+ if (ts.isIdentifier(decl.name)) {
2283
+ bind(decl.name.text, value);
2284
+ continue;
2285
+ }
2286
+ if (!isIndexableObject(value)) {
2287
+ throw cheapError(`destructured \`const\` source in "${briefNodeText(decl.name)}" is not an object`);
2288
+ }
2289
+ for (const el of decl.name.elements) {
2290
+ bind(el.name.getText(), value[bindingElementPropKey(el)]);
2291
+ }
2292
+ }
2293
+ }
2294
+ // Unreachable: rule 4 requires a trailing `return`.
2295
+ throw cheapError("composite factory body did not return");
2296
+ }
2297
+ /**
2298
+ * Evaluate one expression of a factory body.
2299
+ *
2300
+ * Only the cases that can CONTAIN a construction or a composite call are
2301
+ * handled here; everything else delegates to {@link resolveDeclaratorValue},
2302
+ * which is the same `resolveLiveValue` -> `fold()` -> revive pipeline every
2303
+ * other value in a fold takes. That split is the point: the interpreter owns
2304
+ * as little evaluation semantics as it possibly can, so `fold()` stays the one
2305
+ * definition of what an expression means, and the operators/short-circuiting
2306
+ * duplicated below are duplicated because they must not evaluate a branch
2307
+ * `fold()` would not have evaluated.
2308
+ */
2309
+ async function interpretExpression(node, ctx) {
2310
+ if (ts.isParenthesizedExpression(node) ||
2311
+ ts.isAsExpression(node) ||
2312
+ ts.isSatisfiesExpression(node) ||
2313
+ ts.isNonNullExpression(node)) {
2314
+ return interpretExpression(node.expression, ctx);
2315
+ }
2316
+ if (ts.isNewExpression(node))
2317
+ return interpretNewExpression(node, ctx);
2318
+ if (ts.isTaggedTemplateExpression(node)) {
2319
+ const value = (await resolveDeclaratorValue(node, ctx)).value;
2320
+ stampTagOrigins(value, node, ctx);
2321
+ return value;
2322
+ }
2323
+ if (ts.isObjectLiteralExpression(node)) {
2324
+ const obj = {};
2325
+ for (const prop of node.properties) {
2326
+ if (ts.isPropertyAssignment(prop)) {
2327
+ obj[propName(prop.name)] = await interpretExpression(prop.initializer, ctx);
2328
+ }
2329
+ else if (ts.isShorthandPropertyAssignment(prop)) {
2330
+ obj[prop.name.text] = await interpretExpression(prop.name, ctx);
2331
+ }
2332
+ else {
2333
+ const src = await interpretExpression(prop.expression, ctx);
2334
+ if (src === null || typeof src !== "object")
2335
+ throw cheapError("spread source not an object");
2336
+ Object.assign(obj, src);
2337
+ }
2338
+ }
2339
+ return obj;
2340
+ }
2341
+ if (ts.isArrayLiteralExpression(node)) {
2342
+ const arr = [];
2343
+ for (const el of node.elements) {
2344
+ if (ts.isSpreadElement(el)) {
2345
+ const src = await interpretExpression(el.expression, ctx);
2346
+ if (!Array.isArray(src))
2347
+ throw cheapError("spread source not an array");
2348
+ arr.push(...src);
2349
+ }
2350
+ else {
2351
+ arr.push(await interpretExpression(el, ctx));
2352
+ }
2353
+ }
2354
+ return arr;
2355
+ }
2356
+ if (ts.isConditionalExpression(node)) {
2357
+ return (await interpretExpression(node.condition, ctx))
2358
+ ? interpretExpression(node.whenTrue, ctx)
2359
+ : interpretExpression(node.whenFalse, ctx);
2360
+ }
2361
+ if (ts.isBinaryExpression(node)) {
2362
+ const S = ts.SyntaxKind;
2363
+ const opKind = node.operatorToken.kind;
2364
+ if (opKind === S.AmpersandAmpersandToken) {
2365
+ const left = await interpretExpression(node.left, ctx);
2366
+ return left ? interpretExpression(node.right, ctx) : left;
2367
+ }
2368
+ if (opKind === S.BarBarToken) {
2369
+ const left = await interpretExpression(node.left, ctx);
2370
+ return left ? left : interpretExpression(node.right, ctx);
2371
+ }
2372
+ if (opKind === S.QuestionQuestionToken) {
2373
+ const left = await interpretExpression(node.left, ctx);
2374
+ return left === null || left === undefined ? interpretExpression(node.right, ctx) : left;
2375
+ }
2376
+ }
2377
+ return (await resolveDeclaratorValue(node, ctx)).value;
2378
+ }
2379
+ /**
2380
+ * Construct a real resource from a `new Type(...)` anywhere inside a factory
2381
+ * body — a member, a nested property object, an array element.
2382
+ *
2383
+ * Deliberately NOT routed through {@link resolveResourceEntity}: a factory body
2384
+ * evaluates against the DEFINING module's scope with the caller's props already
2385
+ * bound to live values, so each argument is interpreted recursively (through
2386
+ * {@link interpretExpression}, which can produce a live composite instance a
2387
+ * plain `fold()` has no representation for) rather than folded and revived.
2388
+ *
2389
+ * chant #1169 removed the asymmetry that used to motivate this comment: a
2390
+ * nested `new` in a TOP-LEVEL value position now constructs too, via
2391
+ * {@link constructFoldedResource}. The two paths reach the same place — the
2392
+ * class named by an `import`, called with the arguments the source wrote — by
2393
+ * different routes, because a factory body and a file's own top level start
2394
+ * from different scopes.
2395
+ */
2396
+ async function interpretNewExpression(node, ctx) {
2397
+ // Guaranteed an identifier by {@link checkFactoryExpression}; re-checked so a
2398
+ // future caller can't reach this with a dotted callee and get a silent miss.
2399
+ if (!ts.isIdentifier(node.expression)) {
2400
+ throw cheapError(`\`new ${briefNodeText(node.expression)}(...)\` needs a plain imported constructor`);
2401
+ }
2402
+ const typeName = node.expression.text;
2403
+ const binding = ctx.imports.get(typeName);
2404
+ if (!binding)
2405
+ throw cheapError(`constructor "${typeName}" is not a resolvable import`);
2406
+ // chant #1093 — same gate, same reason, as every other site that imports a
2407
+ // module in order to execute something from it.
2408
+ const refusal = sandboxedExecutionRefusal(binding, ctx, typeName, "constructor");
2409
+ if (refusal)
2410
+ throw cheapError(refusal);
2411
+ const modulePath = resolveModulePathMemoized(binding.specifier, ctx.file, ctx.resolvePathCache);
2412
+ const mod = await importModuleMemoized(modulePath, ctx.importCache);
2413
+ const Ctor = mod[binding.imported];
2414
+ if (typeof Ctor !== "function") {
2415
+ throw cheapError(`"${binding.imported}" from "${binding.specifier}" is not a constructor`);
2416
+ }
2417
+ const ctorArgs = [];
2418
+ for (const arg of node.arguments ?? [])
2419
+ ctorArgs.push(await interpretExpression(arg, ctx));
2420
+ const instance = new Ctor(...ctorArgs);
2421
+ stampCompositeOrigins(instance, node, ctx);
2422
+ return instance;
2423
+ }
2424
+ /**
2425
+ * Record one exported name's fully-resolved value: unconditionally into
2426
+ * `exportedValues` (chant #1020) — the file's export namespace, which is
2427
+ * what discovery hands to `collectEntities` and what another file's
2428
+ * cross-file reference resolves against — and, additionally, into `entities`
2429
+ * when the value is a real `Declarable`/`CompositeInstance`.
2430
+ *
2431
+ * chant #1112 — `entities` is a REPORTING subset, not a filter. It used to
2432
+ * be the only thing discovery passed on, which made this function a second
2433
+ * owner of the "which export becomes an entity" decision; it had one fewer
2434
+ * case than the real owner (`enumerateEntries`, ../collect.ts), so a
2435
+ * `LexiconOutput` export folded fine and was then thrown away, and the
2436
+ * template silently lost its `Outputs` section. Discovery now passes
2437
+ * `exportedValues` — the whole namespace, exactly like the run path's real
2438
+ * `exports` object — and `collectEntities` filters it, so nothing here can
2439
+ * fall behind what collection understands. What `entities` still answers is
2440
+ * "how many resources did this file contribute", for the `[fold:fold] x.ts —
2441
+ * N resource(s)` decision line.
2442
+ */
2443
+ function applyResolvedValue(name, value, entities, exportedValues) {
2444
+ exportedValues.set(name, value);
2445
+ if (isDeclarable(value) || isCompositeInstance(value)) {
2446
+ entities.push([name, value]);
2447
+ }
2448
+ }
2449
+ // ─────────────────────────────────────────────────────────────────────────
2450
+ // Intrinsic revival (chant #1039).
2451
+ //
2452
+ // `fold()`/`foldResource()` reduce a registered intrinsic tagged template
2453
+ // (e.g. AWS `Sub`\`...\`) to a symbolic `FoldedIntrinsic` node —
2454
+ // `{ __intrinsic, strings, values }` — and an unresolved external symbol
2455
+ // chain inside it (e.g. `AWS.StackName`) to a `SymbolicValue` —
2456
+ // `{ __symbol }` (../fold/fold.ts's own doc: "mirrors the runtime call shape
2457
+ // `Tag(strings, ...values)` so a later build path can replay it into the
2458
+ // real intrinsic object"). This IS that later build path: without it, the
2459
+ // symbolic envelope would be passed straight into the constructed entity's
2460
+ // props as an inert plain object — the entity would be built (no crash), but
2461
+ // its serialized output would silently diverge from the run path's real
2462
+ // `Sub`/`PseudoParameter` instances (caught by the #1025 differential the
2463
+ // moment intrinsics started actually folding). `reviveFoldedProps` walks the
2464
+ // folded props/attributes tree and, for each `{__intrinsic}`/`{__symbol}`
2465
+ // node, resolves the real tag function / pseudo-parameter through this
2466
+ // file's own imports (same mechanism `resolveResourceEntity` already uses
2467
+ // for the resource constructor itself) and invokes/accesses it for real —
2468
+ // exactly what running the original tagged template would have done.
2469
+ //
2470
+ // `{__attrRef}` (a same-file sibling-resource reference, ../fold/fold.ts's
2471
+ // `AttrRefValue`) is left untouched when it is NOT nested inside a revived
2472
+ // intrinsic: the serializer's generic walker already recognizes that plain
2473
+ // envelope structurally (see ../serializer-walker.ts) with no revival
2474
+ // needed. But an intrinsic's OWN implementation (e.g. `SubIntrinsic`) needs
2475
+ // a genuine `AttrRef` instance internally (`instanceof` checks), which would
2476
+ // require wiring a live `WeakRef` to the sibling entity — out of scope here:
2477
+ // reject (fall back to run) rather than risk silently wrong output.
2478
+ //
2479
+ // chant #1169 adds the fourth envelope this walk revives, and the one that
2480
+ // closes the corpus's largest fold gate: `{__resource}`, a nested
2481
+ // `new Type(...)` used as a value. It is revived the same way and for the same
2482
+ // reason as the other three — the real class is resolved through the folding
2483
+ // file's own imports and called for real — so what the outer constructor
2484
+ // receives is the instance the run path would have handed it, not a look-alike.
2485
+ // See {@link constructFoldedResource}.
2486
+ // ─────────────────────────────────────────────────────────────────────────
2487
+ /** Resolve a bare name bound by this file's own `import` to its real, live export — the same two-step (resolve module path, then `importModule`) `resolveResourceEntity`/`resolveCallExpression` already use for constructors and composite factories. */
2488
+ async function resolveImportedExport(name, ctx) {
2489
+ const binding = ctx.imports.get(name);
2490
+ if (!binding) {
2491
+ throw cheapError(`"${name}" is not a resolvable import`);
2492
+ }
2493
+ // chant #1093 — reached for an intrinsic tag and for a symbolic chain's root
2494
+ // (`AWS.StackName`), both of which are resolved BY NAME out of the file's own
2495
+ // imports: nothing guarantees the module behind that name is a lexicon's.
2496
+ // `reviveHelperCall` checks chant-ownership before it gets here; this covers
2497
+ // the paths that don't.
2498
+ const refusal = sandboxedExecutionRefusal(binding, ctx, name, "import");
2499
+ if (refusal)
2500
+ throw cheapError(refusal);
2501
+ let modulePath;
2502
+ try {
2503
+ modulePath = resolveModulePathMemoized(binding.specifier, ctx.file, ctx.resolvePathCache);
2504
+ }
2505
+ catch (err) {
2506
+ throw cheapError(`could not resolve import "${binding.specifier}" for "${name}": ${err instanceof Error ? err.message : String(err)}`);
2507
+ }
2508
+ let mod;
2509
+ try {
2510
+ mod = await importModuleMemoized(modulePath, ctx.importCache);
2511
+ }
2512
+ catch (err) {
2513
+ throw cheapError(`could not import "${binding.specifier}" to resolve "${name}": ${err instanceof Error ? err.message : String(err)}`);
2514
+ }
2515
+ return mod[binding.imported];
2516
+ }
2517
+ /** A bare identifier, or simple dotted access chain rooted at one (`AWS.StackName`, `Azure.ResourceGroupName`) — the only shape {@link fold.ts}'s `isUnresolvedSymbolChain` actually produces `SymbolicValue` text for in practice (element access/non-null on a pseudo-parameter-style namespace isn't a real authoring pattern anywhere in a lexicon today). Anything else is rejected rather than guessed at. */
2518
+ const SIMPLE_DOTTED_CHAIN = /^[A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*)*$/;
2519
+ /** Resolve a folded `SymbolicValue`'s raw source text (e.g. `"AWS.StackName"`) back to the real value it refers to, by resolving its root identifier through this file's own imports and then doing real property access down the rest of the chain — reproducing exactly what evaluating that expression at runtime would have done. */
2520
+ async function resolveSymbolicValue(text, ctx) {
2521
+ if (!SIMPLE_DOTTED_CHAIN.test(text)) {
2522
+ throw cheapError(`symbol "${text}" is not a simple dotted import reference`);
2523
+ }
2524
+ const [root, ...path] = text.split(".");
2525
+ let value = await resolveImportedExport(root, ctx);
2526
+ for (const key of path) {
2527
+ if (value === null || value === undefined) {
2528
+ throw cheapError(`symbol "${text}": "${root}" has no "${key}"`);
2529
+ }
2530
+ value = value[key];
2531
+ }
2532
+ return value;
2533
+ }
2534
+ /**
2535
+ * True when {@link reviveFoldedValue} hands `value` back unchanged: a
2536
+ * primitive, or one of the live object kinds its passthrough keeps (an
2537
+ * `AttrRef`, a Declarable, a composite instance, an `Intrinsic`).
2538
+ */
2539
+ function revivesAsItself(value) {
2540
+ if (value === null || typeof value !== "object")
2541
+ return true;
2542
+ return isAttrRefLike(value) || isDeclarable(value) || isCompositeInstance(value) || isIntrinsic(value);
2543
+ }
2544
+ /**
2545
+ * Revive a folded value tree: replace any
2546
+ * `{__intrinsic}`/`{__helper}`/`{__symbol}` envelope with the real value it
2547
+ * represents.
2548
+ *
2549
+ * `requireLiveRefs` tracks whether the CURRENT node is (transitively) an
2550
+ * argument being handed to a real function that will inspect it — a
2551
+ * `{__intrinsic}`'s own interpolated `values`, or a `{__helper}` call's
2552
+ * arguments (chant #1082). In that position a symbolic `{__attrRef}` envelope
2553
+ * is rejected rather than passed along: the receiving implementation needs a
2554
+ * genuine `AttrRef` instance (`instanceof` checks, `WeakRef` derefs — see
2555
+ * `SubIntrinsic`, and `LexiconOutput`'s constructor in ../lexicon-output.ts),
2556
+ * and handing it a look-alike plain object produces output that is wrong
2557
+ * rather than absent. Everywhere else the envelope is left untouched, because
2558
+ * the serializer's generic walker already understands it — see the module-doc
2559
+ * note above.
2560
+ */
2561
+ async function reviveFoldedValue(value, ctx, requireLiveRefs) {
2562
+ if (value === null || typeof value !== "object")
2563
+ return value;
2564
+ // chant #1020 — a REAL, already-constructed live object reached via
2565
+ // cross-file resolution (`ctx.externals`, e.g. `network.vpc.VpcId` folding
2566
+ // to a genuine `AttrRef` wired to the actual shared `vpc` instance, or a
2567
+ // bare cross-file identifier folding directly to the shared
2568
+ // Declarable/CompositeInstance itself). None of these have an own
2569
+ // `__symbol`/`__intrinsic`/`__attrRef`/`__resource` marker KEY — an AttrRef
2570
+ // instance's own enumerable fields are `parent`/`attribute`, not a nested
2571
+ // `__attrRef` object — so without this early return the generic object
2572
+ // walk below would silently reconstruct a plain-object copy of it,
2573
+ // destroying the very identity #1020 exists to preserve. Passed through
2574
+ // completely unchanged, exactly like `resolveCallExpression`'s own
2575
+ // `live.value` passthrough for a composite-call argument.
2576
+ // chant #1063 adds the third kind of real object cross-file resolution can
2577
+ // now put here: a live `Intrinsic` instance read off an active lexicon
2578
+ // package (`Azure.ResourceGroupLocation`, `GCP.ProjectId` — see
2579
+ // `resolveActiveLexiconExport`). Same hazard, same fix: the generic walk
2580
+ // below would rebuild it as a plain `{}` copy, dropping the prototype that
2581
+ // carries `toJSON()` and so serializing `{}` where the run path emits
2582
+ // `[resourceGroup().location]`. `isIntrinsic` keys off
2583
+ // `Symbol.for("chant.intrinsic")` (../intrinsic.ts), a GLOBAL symbol, so it
2584
+ // holds across separately-loaded copies of chant-core the way a bare
2585
+ // `instanceof` would not.
2586
+ if (revivesAsItself(value))
2587
+ return value;
2588
+ if (Array.isArray(value)) {
2589
+ const revived = [];
2590
+ for (const el of value)
2591
+ revived.push(await reviveFoldedValue(el, ctx, requireLiveRefs));
2592
+ return revived;
2593
+ }
2594
+ if ("__symbol" in value) {
2595
+ const symbolic = value;
2596
+ return resolveSymbolicValue(symbolic.__symbol, ctx);
2597
+ }
2598
+ if ("__intrinsic" in value) {
2599
+ const intrinsic = value;
2600
+ const Fn = await resolveImportedExport(intrinsic.__intrinsic, ctx);
2601
+ if (typeof Fn !== "function") {
2602
+ throw cheapError(`intrinsic "${intrinsic.__intrinsic}" did not resolve to a function`);
2603
+ }
2604
+ // Two authored forms, one envelope family (../fold/fold.ts's
2605
+ // `FoldedIntrinsic`): the tagged template replays as
2606
+ // `Name(strings, ...values)`, the plain call (chant #1044) as
2607
+ // `Name(...args)`. Both hand their interior to the REAL function the
2608
+ // file itself imported, with `requireLiveRefs` — an intrinsic inspects
2609
+ // what it is given (`SubIntrinsic`'s `instanceof` checks, `Ref`'s
2610
+ // `getLogicalName`), so a look-alike `{__attrRef}` envelope must be
2611
+ // rejected here rather than silently serialized as something else.
2612
+ const revived = [];
2613
+ if ("args" in intrinsic) {
2614
+ for (const a of intrinsic.args)
2615
+ revived.push(await reviveFoldedValue(a, ctx, true));
2616
+ return Fn(...revived);
2617
+ }
2618
+ for (const v of intrinsic.values)
2619
+ revived.push(await reviveFoldedValue(v, ctx, true));
2620
+ // chant #3196 — hand the tag what a real tagged call hands it: a frozen
2621
+ // strings array carrying a frozen, non-enumerable `raw`. A tag that reads
2622
+ // `strings.raw` (a SQL tag keeps backslashes as written) would otherwise
2623
+ // build different text folded than run.
2624
+ const strings = Object.freeze(Object.defineProperty([...intrinsic.strings], "raw", { value: Object.freeze([...intrinsic.raw]) }));
2625
+ return Fn(strings, ...revived);
2626
+ }
2627
+ if ("__helper" in value) {
2628
+ return reviveHelperCall(value, ctx);
2629
+ }
2630
+ if ("__compositeStep" in value) {
2631
+ // chant #1174 — `<Identifier>(...).step`, see {@link FoldedCompositeStepCall}.
2632
+ // Arguments revive with `requireLiveRefs: false` — the same rule
2633
+ // `resolveCallArguments` applies to a NON-helper callee's arguments
2634
+ // (a composite factory stores its props, it doesn't inspect them the way
2635
+ // an intrinsic/helper does), so a `{__attrRef}` among them stays the
2636
+ // symbolic envelope the composite's own resource construction resolves
2637
+ // by name, exactly as a top-level resource's props would.
2638
+ const call = value;
2639
+ const revivedArgs = [];
2640
+ for (const a of call.args)
2641
+ revivedArgs.push(await reviveFoldedValue(a, ctx, false));
2642
+ const result = await resolveCompositeCall(call.__compositeStep, revivedArgs, ctx);
2643
+ if (!isIndexableObject(result)) {
2644
+ throw cheapError(`composite call \`${call.__compositeStep}(...)\` did not resolve to an object with a "step" member`);
2645
+ }
2646
+ return result.step;
2647
+ }
2648
+ if ("__attrRef" in value) {
2649
+ if (requireLiveRefs) {
2650
+ throw cheapError("a same-file resource reference passed to a folded intrinsic or authoring helper is not foldable yet");
2651
+ }
2652
+ return value;
2653
+ }
2654
+ if ("__resource" in value) {
2655
+ // chant #1169 — a nested `new Type(...)` used as a value. This is the
2656
+ // branch that makes the envelope safe: it is replaced, here, by a REAL
2657
+ // instance of the class the source named, built by the same
2658
+ // resolve-through-this-file's-imports machinery `resolveResourceEntity`
2659
+ // uses for a top-level resource and `interpretNewExpression` (#1023) uses
2660
+ // for a construction inside a factory body. Nothing symbolic reaches the
2661
+ // serializer — see {@link fold}'s `new` branch for why that is the whole
2662
+ // safety argument.
2663
+ //
2664
+ // `requireLiveRefs` is propagated rather than reset: a construction in an
2665
+ // ordinary prop position keeps the top-level rule (a `{__attrRef}` inside
2666
+ // it stays an envelope, which the serializer's own walker resolves by name
2667
+ // through `propertyDeclarable` exactly as it does for a top-level
2668
+ // resource's props), while a construction inside an intrinsic's interior
2669
+ // keeps the stricter one and rejects — the receiving implementation
2670
+ // inspects what it is handed, and this is the direction that falls back to
2671
+ // run rather than emitting something wrong.
2672
+ return constructFoldedResource(value, ctx, requireLiveRefs);
2673
+ }
2674
+ const revived = {};
2675
+ for (const [key, v] of Object.entries(value)) {
2676
+ revived[key] = await reviveFoldedValue(v, ctx, requireLiveRefs);
2677
+ }
2678
+ return revived;
2679
+ }
2680
+ /**
2681
+ * chant #1082 — the provenance half of the registered-authoring-helper check
2682
+ * (the shape/name half is `fold()`'s, see ../fold/foldable-helpers.ts's module
2683
+ * doc). Being in the allowlist is not permission to invoke whatever the name
2684
+ * happens to be bound to: the name must be bound by THIS FILE'S OWN `import`,
2685
+ * and that import must actually come from chant. Only then is the real
2686
+ * function called, with the folded arguments — the same function the run path
2687
+ * would have called, from the same module the source itself named, so fold
2688
+ * cannot diverge from run by construction.
2689
+ *
2690
+ * Anything short of that throws, which falls the whole file back to run: a
2691
+ * local `function phase(...)`, a `phase` imported from the project's own
2692
+ * helpers, a chant-owned import that turns out not to be a function.
2693
+ */
2694
+ async function reviveHelperCall(call, ctx) {
2695
+ const name = call.__helper;
2696
+ const binding = ctx.imports.get(name);
2697
+ if (!binding) {
2698
+ throw cheapError(`authoring helper "${name}(...)" is not a resolvable import`);
2699
+ }
2700
+ if (!isChantOwnedHelperBinding(binding, ctx)) {
2701
+ throw cheapError(`"${name}" is imported from "${binding.specifier}", which is not chant's own — ` +
2702
+ `only chant's registered authoring helpers fold as calls`);
2703
+ }
2704
+ const Fn = await resolveImportedExport(name, ctx);
2705
+ if (typeof Fn !== "function") {
2706
+ throw cheapError(`authoring helper "${name}" did not resolve to a function`);
2707
+ }
2708
+ // `requireLiveRefs` — a helper receives its arguments as real values and may
2709
+ // inspect them (`output()` derefs the ref's `WeakRef` parent), so a symbolic
2710
+ // `{__attrRef}` envelope is rejected here rather than silently wrapped into
2711
+ // a wrong result. A ref that resolved cross-file to a genuine live `AttrRef`
2712
+ // passes straight through (see `reviveFoldedValue`'s early return).
2713
+ const args = [];
2714
+ for (const arg of call.args)
2715
+ args.push(await reviveFoldedValue(arg, ctx, true));
2716
+ return Fn(...args);
2717
+ }
2718
+ /**
2719
+ * True when `binding` names a helper import chant itself owns: a published
2720
+ * chant package specifier ({@link isChantOwnedSpecifier}), or — for in-repo
2721
+ * and test callers, which import chant-core by relative/absolute path the way
2722
+ * this module's own fixtures do — a specifier that resolves to a file inside
2723
+ * chant-core's own tree.
2724
+ *
2725
+ * The path arm resolves only for a relative/absolute specifier, never a bare
2726
+ * one: resolving an arbitrary bare specifier here would reintroduce the
2727
+ * pathological cold-resolution cost chant#1020 measured (see
2728
+ * {@link fastResolveBareSpecifier}), and a bare specifier chant publishes is
2729
+ * already covered by the text arm.
2730
+ *
2731
+ * Not the same question as chant#1093's {@link isTrustedExecutableBinding}
2732
+ * below, and deliberately not shared with it: this one asks "may this NAME be
2733
+ * invoked as one of chant's registered authoring helpers", and a text match
2734
+ * is the right answer for it — a project that shadows `@intentius/chant` in
2735
+ * its own `node_modules` gets its own copy of `output()` invoked either way,
2736
+ * fold or run, so fold cannot diverge from run by trusting the text here.
2737
+ * #1093's question is "may this module execute in the CLI's process at all",
2738
+ * where a specifier the project controls the text of proves nothing.
2739
+ */
2740
+ function isChantOwnedHelperBinding(binding, ctx) {
2741
+ if (isChantOwnedSpecifier(binding.specifier))
2742
+ return true;
2743
+ if (!isProjectImport(binding.specifier, ctx.file))
2744
+ return false;
2745
+ let targetPath;
2746
+ try {
2747
+ targetPath = resolveModulePathMemoized(binding.specifier, ctx.file, ctx.resolvePathCache);
2748
+ }
2749
+ catch {
2750
+ return false;
2751
+ }
2752
+ return isChantCorePath(targetPath);
2753
+ }
2754
+ /**
2755
+ * chant #1093 — the closed allowlist of modules fold may import AND EXECUTE
2756
+ * in the CLI's own process when the #1045 sandbox is active. Exactly two
2757
+ * arms, and neither of them trusts text the project controls:
2758
+ *
2759
+ * 1. **An ACTIVE lexicon package of this build, root OR subpath** — the
2760
+ * exact specifier matched against {@link FoldSession.lexiconPackages}, a
2761
+ * closed set built from the lexicon names the BUILD resolved and
2762
+ * `loadPlugins` already imported (../cli/plugins.ts), not from anything
2763
+ * the file under fold says; OR (chant #1995) a subpath of one of those
2764
+ * same packages (`@intentius/chant-lexicon-azure/generated/index`), whose
2765
+ * package ROOT is extracted from the specifier TEXT
2766
+ * ({@link bareSpecifierPackageRoot}, no resolution) and checked against
2767
+ * the identical set. A subpath is trusted because the code it names
2768
+ * belongs to a package the CLI already imported and executed to load that
2769
+ * lexicon's serializers/lint rules — reaching one of that package's own
2770
+ * modules by subpath instead of through the barrel names code the process
2771
+ * already trusts, not code the project controls.
2772
+ * 2. **chant-core's own executing tree** — the specifier is RESOLVED and the
2773
+ * resulting path checked against {@link chantCoreRoots}. A text match is
2774
+ * not enough here: `@intentius/chant` and `@intentius/chant-lexicon-evil`
2775
+ * are both strings an untrusted repo can write into its own source and
2776
+ * back with its own `node_modules` directory, and
2777
+ * {@link isChantOwnedSpecifier} would accept either. Resolution happens
2778
+ * before the decision rather than after, so an allowed binding pays
2779
+ * exactly the resolution it was about to pay anyway (the memoized one —
2780
+ * see {@link resolveModulePathMemoized}); a bare specifier that isn't
2781
+ * even chant-shaped is rejected on text alone, so no arbitrary bare
2782
+ * specifier is ever resolved here (chant#1020's cold-resolution cost).
2783
+ *
2784
+ * This is deliberately the boundary chant #1045 drew: "the boundary is around
2785
+ * executing PROJECT SOURCE, which is the untrusted input" — not around chant
2786
+ * itself or the lexicon packages, which the CLI has already imported and
2787
+ * executed in its own process before discovery starts, to get the serializers
2788
+ * and lint rules it cannot run without. Fold reaching the same already-loaded
2789
+ * module (the identical un-cache-busted `import()` of the identical resolved
2790
+ * path) adds no execution the process wasn't already performing.
2791
+ *
2792
+ * Everything else is out: a sibling project file, a project-local helper
2793
+ * module, an arbitrary npm dependency, a lexicon this build didn't load. A
2794
+ * build that supplied no lexicon list keeps only arm 2, rather than falling
2795
+ * back to something more permissive — the same stance
2796
+ * {@link FoldSession.lexiconPackages} takes for #1063.
2797
+ */
2798
+ function isTrustedExecutableBinding(binding, ctx) {
2799
+ // chant#3090 — a specifier a tsconfig `paths` entry maps names a project
2800
+ // file whatever its text says, so neither text arm applies to it: it is
2801
+ // resolved and decided on the path below, like a relative specifier.
2802
+ const pathsMapped = pathsMappedTarget(binding.specifier, ctx.file) !== undefined;
2803
+ if (!pathsMapped && activeLexiconPackage(binding.specifier, ctx.lexiconPackages) !== undefined)
2804
+ return true;
2805
+ // chant #1995 — arm 1's subpath case: same allowlist, matched against the
2806
+ // specifier's package ROOT (text only, see bareSpecifierPackageRoot) rather
2807
+ // than the specifier itself, so `<activeLexiconPkg>/generated/index` is
2808
+ // trusted exactly like the bare package import already is.
2809
+ const subpathRoot = bareSpecifierPackageRoot(binding.specifier);
2810
+ if (!pathsMapped && subpathRoot !== undefined && activeLexiconPackage(subpathRoot, ctx.lexiconPackages) !== undefined) {
2811
+ return true;
2812
+ }
2813
+ // Only a chant-shaped or project-relative specifier is worth resolving; any
2814
+ // other bare specifier is untrusted by definition, and resolving it to find
2815
+ // that out would cost the pathological cold `require.resolve` (chant#1020).
2816
+ if (!pathsMapped && !isProjectFileSpecifier(binding.specifier) && !isChantOwnedSpecifier(binding.specifier)) {
2817
+ return false;
2818
+ }
2819
+ let targetPath;
2820
+ try {
2821
+ targetPath = resolveModulePathMemoized(binding.specifier, ctx.file, ctx.resolvePathCache);
2822
+ }
2823
+ catch {
2824
+ return false;
2825
+ }
2826
+ // chant#2577 — arm 1 for a lexicon declared by path: the module
2827
+ // `loadPlugins` already imported, reached by the path the project imports it by.
2828
+ if (ctx.lexiconModules.has(targetPath))
2829
+ return true;
2830
+ // chant#2590 — the subpath case of the same arm: another file of a
2831
+ // multi-file path lexicon, under its trusted directory.
2832
+ if (inPathLexiconRoot(targetPath, ctx.lexiconRoots))
2833
+ return true;
2834
+ return isChantCorePath(targetPath);
2835
+ }
2836
+ /**
2837
+ * chant #1093 — the one-line fold-fallback reason for a resolution that would
2838
+ * import and execute an untrusted module in the CLI's own process, or
2839
+ * `undefined` when the import may proceed (the sandbox wasn't asked for, or
2840
+ * the module is on {@link isTrustedExecutableBinding}'s allowlist).
2841
+ *
2842
+ * A refusal is not a failure to fold something folder-shaped — the shape is
2843
+ * perfectly foldable and folds fine under plain `--fold`. It is a deliberate
2844
+ * demotion: the file falls back to the run path, and under `--sandbox` the
2845
+ * run path is the sandboxed child (`./index.ts` queues every run-fallback
2846
+ * file for `./sandbox/run.ts`). So the factory/constructor/intrinsic still
2847
+ * executes, with the same arguments, in the same module graph as the rest of
2848
+ * that file — just behind Node's Permission Model and a scrubbed environment
2849
+ * instead of inside the CLI. Coverage drops; the boundary holds.
2850
+ *
2851
+ * @param what - What the binding is being resolved AS, for the message
2852
+ * ("composite factory", "constructor", …).
2853
+ */
2854
+ function sandboxedExecutionRefusal(binding, ctx, name, what) {
2855
+ if (!ctx.sandbox)
2856
+ return undefined;
2857
+ if (isTrustedExecutableBinding(binding, ctx))
2858
+ return undefined;
2859
+ return (`${what} "${name}" is imported from "${binding.specifier}", which is neither chant's own nor an active lexicon — ` +
2860
+ `under --sandbox it is executed in the sandboxed child, not in this process`);
2861
+ }
2862
+ /**
2863
+ * chant-core's own module roots, derived from THIS module's location:
2864
+ * `packages/core/src` in this repo, and in a published install both
2865
+ * `<pkg>/src` (where `@intentius/chant/*` specifiers resolve, through the
2866
+ * package exports) and `<pkg>/dist` (where the compiled CLI runs from, #3878).
2867
+ * Pure string arithmetic on `import.meta.url`, no filesystem access at all, so
2868
+ * it is safe in the #1045 sandbox child (which locks reads to an allowlist);
2869
+ * lazy and memoized purely to avoid doing it per call.
2870
+ */
2871
+ let chantCoreRootsMemo;
2872
+ function chantCoreRoots() {
2873
+ if (!chantCoreRootsMemo) {
2874
+ // .../<root>/discovery/fold-import.{ts,js} -> .../<root>
2875
+ const root = dirname(dirname(fileURLToPath(import.meta.url)));
2876
+ const name = basename(root);
2877
+ const sibling = name === "dist" ? join(dirname(root), "src") : name === "src" ? join(dirname(root), "dist") : undefined;
2878
+ chantCoreRootsMemo = sibling ? [root, sibling] : [root];
2879
+ }
2880
+ return chantCoreRootsMemo;
2881
+ }
2882
+ /** Whether `path` is one of chant-core's own modules (see {@link chantCoreRoots}). */
2883
+ function isChantCorePath(path) {
2884
+ return chantCoreRoots().some((root) => path === root || path.startsWith(root + sep));
2885
+ }
2886
+ /** Revive every value in a folded props/attributes object (see {@link reviveFoldedValue}). */
2887
+ async function reviveFoldedProps(props, ctx, requireLiveRefs) {
2888
+ const revived = {};
2889
+ for (const [key, value] of Object.entries(props)) {
2890
+ revived[key] = await reviveFoldedValue(value, ctx, requireLiveRefs);
2891
+ }
2892
+ return revived;
2893
+ }
2894
+ // ─────────────────────────────────────────────────────────────────────────
2895
+ // Resource construction — #1022's mechanism (fold the ctor call's arguments
2896
+ // via `fold()`, resolve the constructor through this file's imports,
2897
+ // construct the real Declarable), split by chant #1169 into two reusable
2898
+ // halves so a NESTED `new Type(...)` used as a value is built by exactly the
2899
+ // same code as a file's own top-level resource declaration, not by a second
2900
+ // implementation that could quietly differ.
2901
+ //
2902
+ // `resolveResourceEntity` below is the top-level entry point and keeps its
2903
+ // per-declarator reason strings verbatim; `reviveResourceCtorArgs` and
2904
+ // `instantiateFoldedResource` are the shared halves, and
2905
+ // {@link constructFoldedResource} is the one-call composition of the two that
2906
+ // `reviveFoldedValue` uses for a nested construction.
2907
+ // ─────────────────────────────────────────────────────────────────────────
2908
+ /**
2909
+ * Revive a folded constructor call's arguments into the real values the class
2910
+ * will receive.
2911
+ *
2912
+ * chant #1039 — replays any folded intrinsic/symbol envelopes into their real
2913
+ * runtime values before the entity is constructed. A no-op walk when the file
2914
+ * used no registered intrinsics (the overwhelming majority of cases today).
2915
+ *
2916
+ * chant #1082 — when `spec.args` is present the constructor's argument list
2917
+ * isn't the classic `(props)`/`(props, attributes)` shape (AWS's `Parameter` is
2918
+ * `(type, props)`), so the whole list is revived and spread by the caller
2919
+ * instead of `spec.props`, which in that case is only a view onto one of its
2920
+ * entries and would be double-counted.
2921
+ */
2922
+ async function reviveResourceCtorArgs(spec, ctx, requireLiveRefs) {
2923
+ if (spec.args) {
2924
+ const revived = [];
2925
+ for (const arg of spec.args)
2926
+ revived.push(await reviveFoldedValue(arg, ctx, requireLiveRefs));
2927
+ return revived;
2928
+ }
2929
+ const props = await reviveFoldedProps(spec.props, ctx, requireLiveRefs);
2930
+ // The runtime constructor's optional second argument (`attributes` — CFN's
2931
+ // DependsOn/Condition/DeletionPolicy/…, see createResource in ../runtime.ts)
2932
+ // is only present in `spec` when the source actually passed one (see
2933
+ // foldResource in ../fold/fold.ts). Passing `undefined` when it's absent
2934
+ // matches the run path's own default (`attributes ?? {}` inside the
2935
+ // constructor).
2936
+ return [props, spec.attributes ? await reviveFoldedProps(spec.attributes, ctx, requireLiveRefs) : undefined];
2937
+ }
2938
+ /**
2939
+ * Resolve a folded constructor's NAME through `ctx`'s own `import`
2940
+ * declarations and call the real class with `ctorArgs`.
2941
+ *
2942
+ * Throws a {@link cheapError} for every failure; `resolveResourceEntity` turns
2943
+ * those into its per-declarator `reason` strings and the nested path lets them
2944
+ * fall the whole file back to run. `forClause` is the ` for "<export name>"`
2945
+ * fragment the top-level messages carry and a nested construction has no name
2946
+ * for — the only difference between the two callers' diagnostics.
2947
+ */
2948
+ async function instantiateFoldedResource(typeName, ctorArgs, ctx, forClause) {
2949
+ const binding = ctx.imports.get(typeName);
2950
+ if (!binding) {
2951
+ throw cheapError(`constructor "${typeName}"${forClause} is not a resolvable import`);
2952
+ }
2953
+ // chant #1093 — a resource class is a lexicon export in every corpus entry
2954
+ // today, but nothing forces that: `new Thing(...)` where `Thing` comes from
2955
+ // a project file (or an arbitrary dependency) would import and run that
2956
+ // module here, in the CLI's process. Same refusal as the composite-factory
2957
+ // path above, and the reason a nested construction cannot widen the #1093
2958
+ // boundary: it reaches its class by exactly this gate.
2959
+ const refusal = sandboxedExecutionRefusal(binding, ctx, typeName, "constructor");
2960
+ if (refusal)
2961
+ throw cheapError(refusal);
2962
+ let modulePath;
2963
+ try {
2964
+ modulePath = resolveModulePathMemoized(binding.specifier, ctx.file, ctx.resolvePathCache);
2965
+ }
2966
+ catch (err) {
2967
+ throw cheapError(`could not resolve import "${binding.specifier}" for "${typeName}": ${err instanceof Error ? err.message : String(err)}`);
2968
+ }
2969
+ // Same import mechanism as `resolveCallExpression` above — see its comment.
2970
+ let mod;
2971
+ try {
2972
+ mod = await importModuleMemoized(modulePath, ctx.importCache);
2973
+ }
2974
+ catch (err) {
2975
+ throw cheapError(`could not import "${binding.specifier}" to resolve "${typeName}": ${err instanceof Error ? err.message : String(err)}`);
2976
+ }
2977
+ const Ctor = mod[binding.imported];
2978
+ if (typeof Ctor !== "function") {
2979
+ throw cheapError(`"${binding.imported}" from "${binding.specifier}" is not a constructor`);
2980
+ }
2981
+ // Constructed with exactly the arguments the source wrote.
2982
+ return new Ctor(...ctorArgs);
2983
+ }
2984
+ /**
2985
+ * chant #1169 — build a real instance from a nested `{__resource}` envelope:
2986
+ * revive its arguments, then resolve its class and call it. The composition
2987
+ * {@link reviveFoldedValue} reaches for; identical in every step to what
2988
+ * {@link resolveResourceEntity} does for a top-level declaration, which is the
2989
+ * point — a nested `new Image({...})` and a top-level `export const image = new
2990
+ * Image({...})` produce the same object, from the same class, from the same
2991
+ * resolved module path.
2992
+ */
2993
+ async function constructFoldedResource(spec, ctx, requireLiveRefs) {
2994
+ const ctorArgs = await reviveResourceCtorArgs(spec, ctx, requireLiveRefs);
2995
+ return instantiateFoldedResource(spec.__resource, ctorArgs, ctx, "");
2996
+ }
2997
+ /**
2998
+ * chant #1169 — construct every top-level `const x = new Type(...)` in the file
2999
+ * ONCE, in source order, before any exported declarator is resolved, and put
3000
+ * each instance in `ctx.externals` under its own name.
3001
+ *
3002
+ * This is what makes a same-file resource usable as a VALUE — `image:
3003
+ * nodeImage`, `DependsOn: [dbCluster]`, `export { app }` — and it is the half
3004
+ * of the #1169 gate the nested-`new` lift alone does not reach: the
3005
+ * `{__resource}` envelope covers a construction written INLINE at the value
3006
+ * position, while the far more common authoring shape names it once and refers
3007
+ * to it. `fold()` cannot answer that reference on its own (it is synchronous,
3008
+ * and constructing needs the module graph), so it defers to `externals` — see
3009
+ * its identifier branch for the full argument.
3010
+ *
3011
+ * ONE instance, and identity is the whole point. Every reference in the file
3012
+ * reads this map, and the exported-declarator loop reuses the same object
3013
+ * through `prebuilt` rather than constructing a second one, so a resource
3014
+ * referenced by name and the entity discovery registers are the same object —
3015
+ * which is what makes the serializer's `Ref`/`DependsOn` resolution land on a
3016
+ * logical name at all. Running the module top-to-bottom produces exactly this:
3017
+ * every top-level `const` evaluated once, in order, later ones seeing earlier
3018
+ * ones. Order matters and is preserved — `collectConsts` yields source order, so
3019
+ * `const b = new Thing({ x: a })` finds `a` already built.
3020
+ *
3021
+ * A construction that FAILS is skipped silently rather than failing the file:
3022
+ * the name stays absent from `externals`, so a reference to it rejects with the
3023
+ * identical message it produced before this existed, and an EXPORTED one falls
3024
+ * through to {@link resolveResourceEntity}, which reproduces the failure with
3025
+ * its own located reason. Strictly additive.
3026
+ *
3027
+ * Under `--sandbox` every construction here goes through the same
3028
+ * {@link sandboxedExecutionRefusal} as every other one, so a project-defined
3029
+ * class refuses, the name stays unresolved, and the file demotes to the
3030
+ * sandboxed child exactly as before. Under plain `--fold` this can import a
3031
+ * constructor's module for a const that is never exported — work the RUN path
3032
+ * performs unconditionally for the same file, and through the same
3033
+ * already-memoized `importModule`.
3034
+ */
3035
+ /**
3036
+ * The local names this file bound to the build's parameter object (chant
3037
+ * #1443). Object identity against {@link FoldSession.buildParams}, which
3038
+ * `buildExternals` substituted directly, so an unrelated import that happens to
3039
+ * be called `params` is not mistaken for it.
3040
+ */
3041
+ function paramLocalNames(ctx) {
3042
+ const out = new Set();
3043
+ const buildParams = ctx.session.buildParams;
3044
+ if (!buildParams)
3045
+ return out;
3046
+ for (const [name, value] of ctx.externals) {
3047
+ if (value === buildParams)
3048
+ out.add(name);
3049
+ }
3050
+ return out;
3051
+ }
3052
+ /**
3053
+ * chant #1443 — record which build parameters each of a resource's authored
3054
+ * property expressions reads, before fold substitutes them away. Best-effort
3055
+ * and additive: a constructor called with no object literal records nothing.
3056
+ *
3057
+ * INTENTIUS/typescript-as-data#248 — and which of them read host code, a
3058
+ * method call, an eager lexicon helper or a lexicon export, so fold provenance
3059
+ * reports those `unknown` rather than `direct`.
3060
+ */
3061
+ function stampParamDependencies(entity, node, ctx) {
3062
+ if (typeof entity !== "object" || entity === null)
3063
+ return;
3064
+ const paramLocals = paramLocalNames(ctx);
3065
+ const host = {
3066
+ values: ctx.hostValues ?? new Set(),
3067
+ eagerCalls: new Set(ctx.intrinsics.filter((i) => intrinsicCallFoldsEagerly(i)).map((i) => i.name)),
3068
+ };
3069
+ // The same argument `foldResource` treats as props: the first object literal.
3070
+ let propsArg;
3071
+ for (const argument of node.arguments ?? []) {
3072
+ if (ts.isObjectLiteralExpression(argument)) {
3073
+ propsArg = argument;
3074
+ break;
3075
+ }
3076
+ }
3077
+ if (!propsArg)
3078
+ return;
3079
+ for (const [path, origin] of Object.entries(collectParamDependencies(propsArg, ctx.consts, paramLocals, host))) {
3080
+ setPathProvenance(entity, path, origin);
3081
+ }
3082
+ }
3083
+ async function preresolveResourceConsts(ctx) {
3084
+ const built = new Map();
3085
+ for (const [name, initializer] of ctx.consts) {
3086
+ // chant #3196 — a same-file `const x = tag\`...\`` for a registered tag
3087
+ // is built once here too, so the export and every reference to `x` share
3088
+ // the one value running the module gives them. That matters when the tag
3089
+ // returns an entity (a SQL `table\`...\``): a second call would build a
3090
+ // second entity discovery never registers.
3091
+ if (ts.isTaggedTemplateExpression(initializer) &&
3092
+ ts.isIdentifier(initializer.tag) &&
3093
+ ctx.intrinsics.some((i) => i.name === initializer.tag.text && intrinsicTagFolds(i))) {
3094
+ try {
3095
+ const value = await reviveFoldedValue(fold(initializer, ctx.consts, ctx.intrinsics, ctx.externals), ctx, false);
3096
+ built.set(initializer, value);
3097
+ // Only a value `reviveFoldedValue` passes through unchanged is safe to
3098
+ // hand a reference. Any other object would be walked into a plain
3099
+ // copy, so a reference to one keeps re-folding the initializer.
3100
+ if (revivesAsItself(value))
3101
+ ctx.externals.set(name, value);
3102
+ }
3103
+ catch (err) {
3104
+ ctx.prebuildFailures?.set(name, describeFoldFailure(err, ctx));
3105
+ }
3106
+ continue;
3107
+ }
3108
+ if (!ts.isNewExpression(initializer) || !ts.isIdentifier(initializer.expression))
3109
+ continue;
3110
+ try {
3111
+ const spec = foldResource(initializer, ctx.consts, ctx.intrinsics, ctx.externals);
3112
+ const instance = await constructFoldedResource(spec, ctx, false);
3113
+ stampParamDependencies(instance, initializer, ctx);
3114
+ built.set(initializer, instance);
3115
+ ctx.externals.set(name, instance);
3116
+ }
3117
+ catch (err) {
3118
+ // Not constructible here (an unresolvable constructor import, a prop
3119
+ // outside the fold subset, a --sandbox refusal). Leave the name alone:
3120
+ // the failure is deliberately not fatal, and the first reference to the
3121
+ // name rejects on its own terms.
3122
+ //
3123
+ // chant#2423 — but keep the located reason. Without it the file's only
3124
+ // reported cause is the reference site, which is where the consequence
3125
+ // is, not where the problem is.
3126
+ ctx.prebuildFailures?.set(name, describeFoldFailure(err, ctx));
3127
+ }
3128
+ }
3129
+ return built;
3130
+ }
3131
+ async function resolveResourceEntity(name, node, ctx) {
3132
+ let spec;
3133
+ try {
3134
+ spec = foldResource(node, ctx.consts, ctx.intrinsics, ctx.externals);
3135
+ }
3136
+ catch (err) {
3137
+ if (err instanceof FoldError) {
3138
+ return { ok: false, reason: `"${name}" is not foldable: ${describeFoldFailure(err, ctx)}` };
3139
+ }
3140
+ throw err;
3141
+ }
3142
+ let ctorArgs;
3143
+ try {
3144
+ ctorArgs = await reviveResourceCtorArgs(spec, ctx, false);
3145
+ }
3146
+ catch (err) {
3147
+ return {
3148
+ ok: false,
3149
+ reason: `"${name}" is not foldable: ${describeFoldFailure(err, ctx)}`,
3150
+ };
3151
+ }
3152
+ try {
3153
+ const entity = (await instantiateFoldedResource(spec.__resource, ctorArgs, ctx, ` for "${name}"`));
3154
+ stampParamDependencies(entity, node, ctx);
3155
+ return { ok: true, entity };
3156
+ }
3157
+ catch (err) {
3158
+ return { ok: false, reason: err instanceof Error ? err.message : String(err) };
3159
+ }
3160
+ }
3161
+ // ─────────────────────────────────────────────────────────────────────────
3162
+ // Cross-file resolution (chant #1020) — resolves an imported PROJECT-file
3163
+ // binding to its real, already-constructed value by recursively folding the
3164
+ // DEFINING module (in that module's own scope: its own consts, its own
3165
+ // imports), memoized per {@link FoldSession} so each cross-file export folds
3166
+ // exactly once and every referrer shares the identical object — see
3167
+ // `planFoldTaint`'s doc below for why that identity is the entire hard part.
3168
+ // A bare package specifier (a lexicon/vendor module) is left alone here: it
3169
+ // resolves through the pre-existing `importModule` mechanism at the point a
3170
+ // constructor/composite-factory/intrinsic tag is actually used, same as
3171
+ // before #1020 — this path exists only to avoid executing PROJECT files.
3172
+ // ─────────────────────────────────────────────────────────────────────────
3173
+ const UNRESOLVED_IDENTIFIER_RE = /unresolved identifier: (\S+)$/;
3174
+ /** The rejection a reference to a const whose pre-build failed produces (chant#2423). */
3175
+ const SAME_FILE_RESOURCE_RE = /same-file resource `([^`]+)` used as a value is not foldable/;
3176
+ /**
3177
+ * Enrich an otherwise-generic "unresolved identifier: X" failure when X is a
3178
+ * name whose OWN cross-file resolution was attempted and failed for a known
3179
+ * reason (most notably an import cycle) — purely cosmetic, never changes
3180
+ * whether something resolves. Every other failure shape passes through
3181
+ * unchanged.
3182
+ */
3183
+ function describeFoldFailure(err, ctx) {
3184
+ if (!(err instanceof Error))
3185
+ return String(err);
3186
+ const match = UNRESOLVED_IDENTIFIER_RE.exec(err.message);
3187
+ if (match) {
3188
+ const reason = ctx.crossFileFailures.get(match[1]);
3189
+ if (reason)
3190
+ return `${err.message} (${reason})`;
3191
+ }
3192
+ // chant#2423 — the reference rejected because the pre-build never produced
3193
+ // the instance. Say what stopped the pre-build, at the line it stopped on.
3194
+ const sameFile = SAME_FILE_RESOURCE_RE.exec(err.message);
3195
+ if (sameFile) {
3196
+ const cause = ctx.prebuildFailures?.get(sameFile[1]);
3197
+ if (cause)
3198
+ return `${err.message} (${cause})`;
3199
+ }
3200
+ return err.message;
3201
+ }
3202
+ /**
3203
+ * chant #1054 — a short, single-line label for a destructured export's
3204
+ * source expression, for a fold fallback reason: the callee plus `(...)`
3205
+ * for the common composite-call source (`GkeCluster(...)`), or a brief,
3206
+ * bounded rendering of whatever else it is otherwise. Never the source's own
3207
+ * `getText()` — for a real composite call that's the entire multi-line
3208
+ * argument list.
3209
+ */
3210
+ function describeDestructureSource(node) {
3211
+ return ts.isCallExpression(node) ? `${briefNodeText(node.expression)}(...)` : briefNodeText(node);
3212
+ }
3213
+ /** Build a located `FoldError`'s formatted "line:col - message" string anchored at `node` — for a cross-file failure detected here in fold-import.ts (an import cycle, a name genuinely absent from the target module's exports) rather than inside `fold()` itself. */
3214
+ function locatedMessage(node, message) {
3215
+ const { line, column } = locate(node);
3216
+ return new FoldError(message, line, column).message;
3217
+ }
3218
+ /**
3219
+ * Fold `file` with session-wide memoization and cycle detection. The SAME
3220
+ * promise is handed back to every caller that asks for `file` within one
3221
+ * session — `discover()`'s own top-level per-file loop AND any cross-file
3222
+ * reference that reaches into `file` alike — so `file` is folded exactly
3223
+ * once and every referrer shares the identical constructed entities.
3224
+ */
3225
+ /**
3226
+ * A backstop, not a realistic limit: no legitimate project should have an
3227
+ * import chain this deep. Exists purely so a FUTURE regression that breaks
3228
+ * memoization (e.g. caching by the wrong key, or checking `stack` after
3229
+ * awaiting instead of before) fails loudly and immediately with a
3230
+ * diagnosable error, instead of recursing until the process runs out of
3231
+ * stack/memory or simply never terminates — a correctness bug in this exact
3232
+ * function is a termination hazard, not just a wrong-answer one, since
3233
+ * `foldFileMemoized` is the one place cross-file resolution can recurse.
3234
+ */
3235
+ const MAX_RESOLUTION_DEPTH = 200;
3236
+ function foldFileMemoized(file, session) {
3237
+ const cycleStart = session.stack.indexOf(file);
3238
+ if (cycleStart !== -1) {
3239
+ // A file currently being resolved (an ancestor of this very call, still
3240
+ // on the stack) is needed again — a genuine reference cycle, not merely
3241
+ // two files that happen to import each other with no real value
3242
+ // dependency (that could never re-enter here for an ancestor). The
3243
+ // caller ({@link buildExternals}/the re-export declarator handler, which
3244
+ // has the referencing import's own specifier node) attaches a source
3245
+ // position to this via {@link locatedMessage}; here we only have the
3246
+ // file-level chain to name.
3247
+ const cycle = [...session.stack.slice(cycleStart), file].map((f) => basename(f));
3248
+ return Promise.resolve({ ok: false, reason: `import cycle: ${cycle.join(" -> ")}` });
3249
+ }
3250
+ const cached = session.cache.get(file);
3251
+ if (cached)
3252
+ return cached;
3253
+ if (session.stack.length >= MAX_RESOLUTION_DEPTH) {
3254
+ const chain = [...session.stack.slice(-5), file].map((f) => basename(f));
3255
+ return Promise.resolve({
3256
+ ok: false,
3257
+ reason: `cross-file resolution depth exceeded ${MAX_RESOLUTION_DEPTH} ` +
3258
+ `(...-> ${chain.join(" -> ")}) — this is almost certainly a resolution ` +
3259
+ `bug (memoization not taking effect), not a genuinely thousand-file-deep import chain`,
3260
+ });
3261
+ }
3262
+ session.stack.push(file);
3263
+ const promise = tryFoldFileCore(file, session).finally(() => {
3264
+ const idx = session.stack.lastIndexOf(file);
3265
+ if (idx !== -1)
3266
+ session.stack.splice(idx, 1);
3267
+ });
3268
+ session.cache.set(file, promise);
3269
+ return promise;
3270
+ }
3271
+ /** True for a relative or absolute specifier — a sibling PROJECT file, as opposed to a bare package specifier (an installed lexicon/vendor module). */
3272
+ function isProjectFileSpecifier(specifier) {
3273
+ return specifier.startsWith(".") || isAbsolute(specifier);
3274
+ }
3275
+ /**
3276
+ * chant#3090 — {@link isProjectFileSpecifier} for an import in `fromFile`,
3277
+ * counting a bare specifier that a tsconfig `paths` entry maps to a file
3278
+ * ({@link pathsMappedTarget}) as the project file it names.
3279
+ */
3280
+ function isProjectImport(specifier, fromFile) {
3281
+ return isProjectFileSpecifier(specifier) || pathsMappedTarget(specifier, fromFile) !== undefined;
3282
+ }
3283
+ /**
3284
+ * Eagerly resolve every project-file import binding of `file`'s to its real
3285
+ * cross-file value (see this section's own doc above). A binding that isn't
3286
+ * a project-file specifier, doesn't resolve to a file on disk, or whose
3287
+ * target doesn't fold or doesn't actually export the requested name is
3288
+ * simply left OUT of `externals` — exactly like a name that was never
3289
+ * imported at all, `fold()`'s ordinary "unresolved identifier" failure still
3290
+ * fires if (and only if) that name is actually referenced. `failures`
3291
+ * records WHY, purely to enrich that later message (see
3292
+ * {@link describeFoldFailure}).
3293
+ */
3294
+ async function buildExternals(file, imports, namespaceImports, session,
3295
+ /**
3296
+ * chant#3247 — trusted lexicon directories beyond the session's own
3297
+ * {@link FoldSession.lexiconRoots}: the package root of a lexicon-package
3298
+ * composite whose defining module this is. Files under one are treated as
3299
+ * a path lexicon's are, never folded as project files.
3300
+ */
3301
+ extraLexiconRoots = []) {
3302
+ const lexiconRoots = extraLexiconRoots.length === 0 ? session.lexiconRoots : [...session.lexiconRoots, ...extraLexiconRoots];
3303
+ const externals = new Map();
3304
+ const failures = new Map();
3305
+ // chant #1044 — see `FoldFileResult.liveSources`.
3306
+ const liveSources = new Set();
3307
+ const hostValues = new Set();
3308
+ for (const [localName, binding] of imports) {
3309
+ // chant #1064 — a named `params` import that resolves to chant-core's own
3310
+ // build-time-parameters module (../params.ts) is substituted directly
3311
+ // from this build's already-resolved values, with NO import performed —
3312
+ // so `params.tier` folds to a LITERAL rather than a symbolic node
3313
+ // (contrast the `{__symbol}` deferral a pseudo-parameter namespace import
3314
+ // like `AWS.StackName` gets — that genuinely can't resolve until a real
3315
+ // module runs; a build parameter's value is already fully known here).
3316
+ //
3317
+ // A BARE specifier is recognized by an exact TEXT match against the one
3318
+ // real published subpath ({@link PARAMS_BARE_SPECIFIER}), NEVER by
3319
+ // resolving it: `resolveModulePathMemoized`'s bare-specifier branch falls
3320
+ // through to Node's own package resolution
3321
+ // (`createRequire(fromFile).resolve(specifier)`), which chant#1020's own
3322
+ // fix-history (see this module's other comments) measured at up to ~361s
3323
+ // for the FIRST resolution of a genuinely new bare specifier in a
3324
+ // process — a cost `buildExternals` previously never paid at all for
3325
+ // bare specifiers (the pre-#1064 code skipped them outright). Since
3326
+ // `binding.imported === "params"` alone says nothing about which package
3327
+ // a project actually imported from, resolving EVERY such bare specifier
3328
+ // to check it would reintroduce exactly that pathological cost for any
3329
+ // corpus/project file that happens to import a same-named binding from
3330
+ // an unrelated package — a real, measured regression this text-match
3331
+ // avoids entirely (no filesystem/package resolution for a bare
3332
+ // specifier, ever, in this branch).
3333
+ //
3334
+ // A RELATIVE/ABSOLUTE specifier is still resolved and path-compared
3335
+ // against {@link paramsModulePath} — that resolution is always cheap
3336
+ // (`existsSync`/`statSync` candidate probing, never Node's package
3337
+ // resolution), so it's safe for this module's own absolute-path test
3338
+ // fixtures to exercise the identical substitution a real bare import
3339
+ // takes, without the bare-specifier cost concern applying.
3340
+ if (session.buildParams && binding.imported === "params") {
3341
+ if (binding.specifier === PARAMS_BARE_SPECIFIER) {
3342
+ externals.set(localName, session.buildParams);
3343
+ continue;
3344
+ }
3345
+ if (isProjectImport(binding.specifier, file)) {
3346
+ try {
3347
+ const targetPath = resolveModulePathMemoized(binding.specifier, file, session.resolvePathCache);
3348
+ if (targetPath === paramsModulePath()) {
3349
+ externals.set(localName, session.buildParams);
3350
+ continue;
3351
+ }
3352
+ }
3353
+ catch {
3354
+ // Unresolvable specifier — fall through to the ordinary handling
3355
+ // below, same as any other import this loop can't resolve.
3356
+ }
3357
+ }
3358
+ }
3359
+ if (!isProjectImport(binding.specifier, file)) {
3360
+ // chant #1063 — a bare specifier naming one of THIS BUILD's active
3361
+ // lexicon packages resolves to that package's real export, so a plain
3362
+ // data export a lexicon publishes (`Azure`/`GCP`'s pseudo-parameter
3363
+ // namespaces, AWS's `S3Actions`, gitlab's `CI`) is an ordinary
3364
+ // identifier value here rather than fold's most common remaining
3365
+ // "unresolved identifier" failure. See
3366
+ // {@link resolveActiveLexiconExport} for the allowlist, the no-cold-
3367
+ // resolution rule, and why callable exports stay out.
3368
+ //
3369
+ // No `liveSources` edge is recorded for what comes back, unlike the
3370
+ // project-file case just below. `liveSources` (chant #1044) exists so
3371
+ // that a folded file which captured ANOTHER FILE's objects is
3372
+ // invalidated when that file is forced back to run — a fold/run
3373
+ // disagreement about identity. A lexicon package has no such duality:
3374
+ // it is not a discovered source file, `planFoldTaint` never considers
3375
+ // it (it filters to the discovered `files` set), it never falls back to
3376
+ // run, and both paths reach it through the identical un-cache-busted
3377
+ // `import()` of the identical resolved path — so the object fold
3378
+ // captures IS the object the run path holds. There is nothing for the
3379
+ // two sides to disagree about, hence nothing to taint.
3380
+ const lexiconExport = await resolveActiveLexiconExport(binding, file, session);
3381
+ if (lexiconExport) {
3382
+ externals.set(localName, lexiconExport.value);
3383
+ hostValues.add(localName);
3384
+ continue;
3385
+ }
3386
+ // Every other bare specifier (a non-lexicon vendor package, a lexicon
3387
+ // this build didn't load) is left alone here, exactly as before #1064:
3388
+ // it resolves lazily, through the pre-existing `importModule`
3389
+ // mechanism, only once a constructor/composite-factory/intrinsic tag
3390
+ // actually consumes it.
3391
+ //
3392
+ // chant#3090 — except that a tsconfig `paths` entry which matches the
3393
+ // specifier but names no file is recorded, so the reason says which
3394
+ // import failed and why.
3395
+ const unmatched = pathsMatch(binding.specifier, file);
3396
+ if (unmatched) {
3397
+ failures.set(localName, locatedMessage(binding.specifierNode, `tsconfig paths entry "${unmatched.key}" matches "${binding.specifier}" but names no file`));
3398
+ }
3399
+ continue;
3400
+ }
3401
+ let targetPath;
3402
+ try {
3403
+ targetPath = resolveModulePathMemoized(binding.specifier, file, session.resolvePathCache);
3404
+ }
3405
+ catch {
3406
+ continue;
3407
+ }
3408
+ // chant#2577 — a path-declared lexicon is a lexicon, not a project file:
3409
+ // resolve its data export the way the bare-specifier branch above does,
3410
+ // with no `liveSources` edge for the same reason.
3411
+ if (session.lexiconModules.has(targetPath)) {
3412
+ const lexiconExport = await resolveActiveLexiconExport(binding, file, session);
3413
+ if (lexiconExport) {
3414
+ externals.set(localName, lexiconExport.value);
3415
+ hostValues.add(localName);
3416
+ }
3417
+ continue;
3418
+ }
3419
+ // chant#2590 — another file of a multi-file path lexicon is a lexicon
3420
+ // subpath, not a project file: it is not folded, and like a package
3421
+ // subpath (see the bare-specifier branch above) its data exports are not
3422
+ // read as values. A constructor or composite from it resolves through the
3423
+ // import path, where `isTrustedExecutableBinding` admits it.
3424
+ //
3425
+ // chant#3247 — a file of the lexicon PACKAGE whose composite is being
3426
+ // interpreted is that package's own module, which `loadPlugins` already
3427
+ // imported through the package's barrel. Its plain data exports (an
3428
+ // action table, a policy constant) are read off the real module, so the
3429
+ // value is the run path's own object, identity included. Callables stay
3430
+ // out, as {@link resolveActiveLexiconExport} keeps them out: a
3431
+ // constructor or composite resolves through the import path.
3432
+ if (inPathLexiconRoot(targetPath, extraLexiconRoots)) {
3433
+ const value = await packageDataExport(targetPath, binding.imported, session);
3434
+ if (value) {
3435
+ externals.set(localName, value.value);
3436
+ hostValues.add(localName);
3437
+ }
3438
+ continue;
3439
+ }
3440
+ if (inPathLexiconRoot(targetPath, lexiconRoots))
3441
+ continue;
3442
+ const result = await foldFileMemoized(targetPath, session);
3443
+ if (!result.ok) {
3444
+ failures.set(localName, locatedMessage(binding.specifierNode, result.reason));
3445
+ continue;
3446
+ }
3447
+ if (result.exportedValues.has(binding.imported)) {
3448
+ const value = result.exportedValues.get(binding.imported);
3449
+ externals.set(localName, value);
3450
+ if (hasObjectIdentity(value))
3451
+ liveSources.add(targetPath);
3452
+ }
3453
+ else {
3454
+ failures.set(localName, locatedMessage(binding.specifierNode, `"${binding.imported}" is not exported by "${binding.specifier}"`));
3455
+ }
3456
+ }
3457
+ for (const [localName, binding] of namespaceImports) {
3458
+ if (!isProjectImport(binding.specifier, file))
3459
+ continue;
3460
+ let targetPath;
3461
+ try {
3462
+ targetPath = resolveModulePathMemoized(binding.specifier, file, session.resolvePathCache);
3463
+ }
3464
+ catch {
3465
+ continue;
3466
+ }
3467
+ // chant#2577 — a namespace import of a lexicon is left alone, as the
3468
+ // bare-specifier check above leaves a package's alone.
3469
+ if (session.lexiconModules.has(targetPath) || inPathLexiconRoot(targetPath, lexiconRoots))
3470
+ continue;
3471
+ const result = await foldFileMemoized(targetPath, session);
3472
+ if (!result.ok) {
3473
+ failures.set(localName, locatedMessage(binding.specifierNode, result.reason));
3474
+ continue;
3475
+ }
3476
+ // A plain object wrapping the target's own exported values — property
3477
+ // access on it (`ns.someExport`) is then just an ordinary bracket index,
3478
+ // exactly like on a real composite instance (see `isIndexableObject`).
3479
+ externals.set(localName, Object.fromEntries(result.exportedValues));
3480
+ for (const value of result.exportedValues.values()) {
3481
+ if (hasObjectIdentity(value)) {
3482
+ liveSources.add(targetPath);
3483
+ break;
3484
+ }
3485
+ }
3486
+ }
3487
+ return { externals, failures, liveSources, hostValues };
3488
+ }
3489
+ /**
3490
+ * True when `value` is something whose IDENTITY matters across the
3491
+ * fold/run boundary — any object or function, as opposed to a primitive
3492
+ * (chant #1044). Deliberately coarse: an object that merely *contains* a
3493
+ * `Declarable` is as identity-bearing as the Declarable itself, and cheaply
3494
+ * treating every object as such avoids a deep walk whose only payoff would
3495
+ * be keeping a handful of extra files folded inside an entry that already
3496
+ * falls back. See {@link FoldFileResult.liveSources}.
3497
+ */
3498
+ function hasObjectIdentity(value) {
3499
+ // chant #1373 — a foldable-function marker is a description of source, not
3500
+ // an object the run path could hold a different copy of: a call to it
3501
+ // produces the same value folded or run. No identity to disagree about.
3502
+ if (isFoldableFunction(value))
3503
+ return false;
3504
+ return value !== null && (typeof value === "object" || typeof value === "function");
3505
+ }
3506
+ /**
3507
+ * Resolve a declarator's initializer to its final value: first via the
3508
+ * "live spine" ({@link resolveLiveValue} — same-file composite-call
3509
+ * navigation, and, chant #1020, a cross-file identifier/property-access
3510
+ * chain through `ctx.externals`); when that shape isn't recognized at all
3511
+ * (a plain literal, object/array literal, template, binary expression, …),
3512
+ * via the general reducer ({@link fold} + intrinsic revival) — chant #1020:
3513
+ * this is what lets `export const REGION = "us-east-1";` (and an IMPORTED
3514
+ * REGION used the same way) fold, not just a `new Type(...)`/composite-call
3515
+ * shape. Throws when a shape WAS recognized but resolution genuinely failed
3516
+ * (unresolved import, non-function import, a nested argument's own fold
3517
+ * failed, an unresolved identifier, …) — the caller falls the whole file
3518
+ * back to run, exactly as before #1020.
3519
+ */
3520
+ async function resolveDeclaratorValue(node, ctx) {
3521
+ // chant#3325 — memoized, so an exported `const x = Composite(...)` and a
3522
+ // reference to `x` elsewhere in the file hold the one instance.
3523
+ const live = await resolveMemoized(node, ctx);
3524
+ if (live !== undefined)
3525
+ return live;
3526
+ const folded = fold(node, ctx.consts, ctx.intrinsics, ctx.externals);
3527
+ return { value: await reviveFoldedValue(folded, ctx, false) };
3528
+ }
3529
+ // ─────────────────────────────────────────────────────────────────────────
3530
+ // Entry point.
3531
+ // ─────────────────────────────────────────────────────────────────────────
3532
+ /**
3533
+ * Attempt to fold one source file with zero execution of its own top-level
3534
+ * code, sharing `session`'s cross-file memoization (chant #1020) — see
3535
+ * {@link foldFileMemoized}. Internal; {@link tryFoldFile} is the public
3536
+ * entry point below.
3537
+ */
3538
+ async function tryFoldFileCore(file, session) {
3539
+ // chant #1373 — chant-core's own modules are library code, not project
3540
+ // source: their exports are reached by importing them (a helper, a
3541
+ // constructor, `params`), never by folding their bodies. Before #1373 every
3542
+ // one of them happened to disqualify itself at the scan (each exports a
3543
+ // function); now that an exported function is a foldable shape, the
3544
+ // exclusion has to be explicit, or a fixture/in-repo import by path would
3545
+ // walk fold into chant's own tree and fold `params` to an empty object.
3546
+ {
3547
+ if (isChantCorePath(file)) {
3548
+ return { ok: false, reason: "chant's own module is not project source" };
3549
+ }
3550
+ }
3551
+ try {
3552
+ const source = await readFile(file, "utf-8");
3553
+ const sourceFile = ts.createSourceFile(file, source, ts.ScriptTarget.Latest, /* setParentNodes */ true);
3554
+ const scan = scanExports(sourceFile);
3555
+ if (scan.unfoldableReason)
3556
+ return { ok: false, reason: scan.unfoldableReason };
3557
+ if (scan.declarators.length === 0)
3558
+ return { ok: false, reason: "no foldable resource exports" };
3559
+ const collected = collectImports(sourceFile);
3560
+ const { externals, failures, liveSources, hostValues } = await buildExternals(file, collected.named, collected.namespaces, session);
3561
+ const ctx = {
3562
+ file,
3563
+ consts: collectConsts(sourceFile),
3564
+ locals: collectLocalBindings(sourceFile),
3565
+ imports: collected.named,
3566
+ namespaceImports: collected.namespaces,
3567
+ memo: new Map(),
3568
+ intrinsics: session.intrinsics,
3569
+ externals,
3570
+ hostValues,
3571
+ crossFileFailures: failures,
3572
+ importCache: session.importCache,
3573
+ resolvePathCache: session.resolvePathCache,
3574
+ lexiconPackages: session.lexiconPackages,
3575
+ lexiconModules: session.lexiconModules,
3576
+ lexiconRoots: session.lexiconRoots,
3577
+ sandbox: session.sandbox,
3578
+ executing: session.executing,
3579
+ session,
3580
+ interpretDepth: 0,
3581
+ // chant#2423 — filled by the pre-build below, read by
3582
+ // `describeFoldFailure` when a reference rejects for a const it could
3583
+ // not build.
3584
+ prebuildFailures: new Map(),
3585
+ };
3586
+ // chant #1169 — every same-file `const x = new Type(...)`, built once, in
3587
+ // source order, before anything references one. See
3588
+ // {@link preresolveResourceConsts}.
3589
+ const prebuiltResources = await preresolveResourceConsts(ctx);
3590
+ // chant #1373 — this file's own functions, callable from its own folds
3591
+ // (and, once exported, from an importer's). Registered after the
3592
+ // resource pre-pass so a marker never shadows a pre-built instance; a
3593
+ // function and a const cannot share a name in valid TypeScript anyway.
3594
+ const localFunctions = collectLocalFunctions(sourceFile, ctx);
3595
+ for (const [name, marker] of localFunctions) {
3596
+ if (!ctx.externals.has(name))
3597
+ ctx.externals.set(name, marker);
3598
+ }
3599
+ const entities = [];
3600
+ const exportedValues = new Map();
3601
+ for (const decl of scan.declarators) {
3602
+ if (decl.kind === "function") {
3603
+ applyResolvedValue(decl.name, localFunctions.get(decl.name), entities, exportedValues);
3604
+ continue;
3605
+ }
3606
+ if (decl.kind === "resource") {
3607
+ // chant #1169 — the pre-pass already built this exact node. Reuse that
3608
+ // instance rather than constructing a second one: a sibling prop that
3609
+ // referenced this resource by name holds the pre-pass object, and if
3610
+ // the entity discovery registers were a different one, the reference
3611
+ // would have no logical name to resolve against.
3612
+ const prebuilt = prebuiltResources.get(decl.node);
3613
+ if (prebuilt !== undefined) {
3614
+ applyResolvedValue(decl.name, prebuilt, entities, exportedValues);
3615
+ continue;
3616
+ }
3617
+ const result = await resolveResourceEntity(decl.name, decl.node, ctx);
3618
+ if (!result.ok)
3619
+ return result;
3620
+ applyResolvedValue(decl.name, result.entity, entities, exportedValues);
3621
+ continue;
3622
+ }
3623
+ if (decl.kind === "single") {
3624
+ // chant #1373 — `export const f = (…) => …` exports the function
3625
+ // marker, exactly like `export function f`; so does an alias of one
3626
+ // (`export const g = f`, `f` declared here or imported).
3627
+ const aliased = ts.isIdentifier(decl.node) ? ctx.externals.get(decl.node.text) : undefined;
3628
+ const marker = localFunctions.get(decl.name) ?? (isFoldableFunction(aliased) ? aliased : undefined);
3629
+ if (marker) {
3630
+ applyResolvedValue(decl.name, marker, entities, exportedValues);
3631
+ continue;
3632
+ }
3633
+ // chant #3196 — a registered tagged template the pre-pass built.
3634
+ if (prebuiltResources.has(decl.node)) {
3635
+ applyResolvedValue(decl.name, prebuiltResources.get(decl.node), entities, exportedValues);
3636
+ continue;
3637
+ }
3638
+ let value;
3639
+ try {
3640
+ value = (await resolveDeclaratorValue(decl.node, ctx)).value;
3641
+ }
3642
+ catch (err) {
3643
+ return { ok: false, reason: `"${decl.name}" is not foldable: ${describeFoldFailure(err, ctx)}` };
3644
+ }
3645
+ applyResolvedValue(decl.name, value, entities, exportedValues);
3646
+ continue;
3647
+ }
3648
+ if (decl.kind === "destructure") {
3649
+ let value;
3650
+ // chant #1054 — identify the destructured export by its BINDING
3651
+ // NAMES and the source's callee (`"cluster, nodePool" (destructured
3652
+ // from GkeCluster(...))`), never `decl.node.getText()`: for a real
3653
+ // composite call that's the entire multi-line source, which buries
3654
+ // the actual error after it and breaks any line-oriented consumer of
3655
+ // `[fold:run]` output.
3656
+ const boundNames = decl.elements.map((el) => el.bindingName).join(", ");
3657
+ const source = describeDestructureSource(decl.node);
3658
+ try {
3659
+ value = (await resolveDeclaratorValue(decl.node, ctx)).value;
3660
+ }
3661
+ catch (err) {
3662
+ return {
3663
+ ok: false,
3664
+ reason: `"${boundNames}" (destructured from ${source}) is not foldable: ${describeFoldFailure(err, ctx)}`,
3665
+ };
3666
+ }
3667
+ if (!isIndexableObject(value)) {
3668
+ return {
3669
+ ok: false,
3670
+ reason: `"${boundNames}" (destructured from ${source}) is not foldable: not a composite call or object`,
3671
+ };
3672
+ }
3673
+ for (const { propKey, bindingName } of decl.elements) {
3674
+ applyResolvedValue(bindingName, value[propKey], entities, exportedValues);
3675
+ }
3676
+ continue;
3677
+ }
3678
+ if (decl.kind === "named-export") {
3679
+ // Reuses resolveDeclaratorValue's own identifier handling
3680
+ // (ctx.locals/ctx.externals lookup + memoized destructured-member
3681
+ // extraction) rather than duplicating it here.
3682
+ for (const { localNameNode, exportedName } of decl.elements) {
3683
+ // chant #1373 — `function f() {…}; export { f }`.
3684
+ const marker = localFunctions.get(localNameNode.text);
3685
+ if (marker) {
3686
+ applyResolvedValue(exportedName, marker, entities, exportedValues);
3687
+ continue;
3688
+ }
3689
+ let value;
3690
+ try {
3691
+ value = (await resolveDeclaratorValue(localNameNode, ctx)).value;
3692
+ }
3693
+ catch (err) {
3694
+ return { ok: false, reason: `exported "${exportedName}" is not foldable: ${describeFoldFailure(err, ctx)}` };
3695
+ }
3696
+ applyResolvedValue(exportedName, value, entities, exportedValues);
3697
+ }
3698
+ continue;
3699
+ }
3700
+ // decl.kind === "re-export" (chant #1020) — resolve each element
3701
+ // through the target module's OWN fold, memoized via the session
3702
+ // exactly like any other cross-file reference (see `foldFileMemoized`
3703
+ // above); a cycle surfaces here too since a re-export is just another
3704
+ // edge in the same module graph.
3705
+ let targetPath;
3706
+ try {
3707
+ targetPath = resolveModulePathMemoized(decl.specifier, file, session.resolvePathCache);
3708
+ }
3709
+ catch (err) {
3710
+ return {
3711
+ ok: false,
3712
+ reason: `could not resolve re-export "${decl.specifier}": ${err instanceof Error ? err.message : String(err)}`,
3713
+ };
3714
+ }
3715
+ const result = await foldFileMemoized(targetPath, session);
3716
+ if (!result.ok) {
3717
+ return {
3718
+ ok: false,
3719
+ reason: `re-export from "${decl.specifier}" is not foldable: ${locatedMessage(decl.specifierNode, result.reason)}`,
3720
+ };
3721
+ }
3722
+ for (const { imported, exportedName } of decl.elements) {
3723
+ if (!result.exportedValues.has(imported)) {
3724
+ return {
3725
+ ok: false,
3726
+ reason: locatedMessage(decl.specifierNode, `"${imported}" is not exported by "${decl.specifier}"`),
3727
+ };
3728
+ }
3729
+ const value = result.exportedValues.get(imported);
3730
+ // chant #1044 — a re-export hands another file's OBJECT straight
3731
+ // through under this file's name, so it is a live-identity edge
3732
+ // exactly like an imported binding is (see `liveSources`).
3733
+ if (hasObjectIdentity(value))
3734
+ liveSources.add(targetPath);
3735
+ applyResolvedValue(exportedName, value, entities, exportedValues);
3736
+ }
3737
+ }
3738
+ return { ok: true, entities, exportedValues, liveSources };
3739
+ }
3740
+ catch (err) {
3741
+ // Any unexpected failure degrades to "fall back to run" rather than
3742
+ // taking discovery down with it — fold is opt-in, not a new failure mode.
3743
+ return { ok: false, reason: err instanceof Error ? err.message : String(err) };
3744
+ }
3745
+ }
3746
+ /**
3747
+ * Attempt to fold one source file with zero execution of its own top-level
3748
+ * code. Returns the folded, instantiated entities on success, or a reason
3749
+ * to fall back to the run path (`importModule`) on the first construct
3750
+ * outside the fold subset.
3751
+ *
3752
+ * @param intrinsics - Lexicon-registered intrinsic tags (chant #1039), e.g.
3753
+ * AWS's `Sub`. Threaded down to {@link fold}/{@link foldResource} so a
3754
+ * registered tagged template folds instead of unconditionally throwing
3755
+ * "unregistered tagged template intrinsic". Defaults to none — the caller
3756
+ * (`discover()`, ultimately `chant build --fold`) is expected to pass the
3757
+ * target lexicons' combined `intrinsics()`. Ignored when `session` is
3758
+ * given (its own `intrinsics`, fixed at creation, apply instead).
3759
+ * @param session - chant #1020: share ONE {@link FoldSession} across every
3760
+ * file in a build (as `discover()` does) so a project file imported by
3761
+ * several others folds exactly once and every referrer shares the same
3762
+ * constructed entities — see {@link FoldSession}'s doc. Omit for a
3763
+ * standalone, single-file fold attempt (creates a private session scoped
3764
+ * to just this call — a cross-file reference reachable from `file` still
3765
+ * resolves, just without sharing its cache with any other top-level call).
3766
+ */
3767
+ export async function tryFoldFile(file, intrinsics = [], session) {
3768
+ return foldFileMemoized(file, session ?? createFoldSession(intrinsics));
3769
+ }
3770
+ // ─────────────────────────────────────────────────────────────────────────
3771
+ // Cross-file fold/run identity hazard (#1023).
3772
+ // ─────────────────────────────────────────────────────────────────────────
3773
+ /**
3774
+ * A file that folds successfully in isolation must still be forced back to
3775
+ * run if some OTHER discovered file — one that itself falls back to run —
3776
+ * imports it (directly, or transitively through another sibling file it
3777
+ * imports). Composite folding is what makes this reachable: `network.ts`
3778
+ * exporting `export const network = VpcDefault({})` folds cleanly on its
3779
+ * own (no cross-file reference needed), landing its Declarables (`vpc`, its
3780
+ * subnets, …) in `entities` as ONE set of real objects, built by literally
3781
+ * invoking `VpcDefault` from inside `tryFoldFile` — bypassing `network.ts`
3782
+ * as a module entirely (that's the whole point of folding it).
3783
+ *
3784
+ * Before chant #1020, a sibling file like `alb.ts` that did
3785
+ * `import { network } from "./network"; ... vpcId: network.vpc.VpcId`
3786
+ * could never fold that cross-file reference and fell back to run — but
3787
+ * running `alb.ts` for real re-executes `import "./network"` for real too,
3788
+ * which (via Node's module cache) produces the SAME `network.ts` module
3789
+ * instance for every OTHER run-fallback file that imports it, but a
3790
+ * DIFFERENT one than the object `tryFoldFile("network.ts")` already built.
3791
+ * `alb.ts`'s AttrRef for `network.vpc.VpcId` then points at an object
3792
+ * that's never in the `entities` map (only the folded one is), so it can
3793
+ * never be assigned a logical name and serialization fails outright — not
3794
+ * drift, a crash.
3795
+ *
3796
+ * The fix: fold and run must never disagree about which object identity a
3797
+ * given file's exports have. Since `network.ts` itself doesn't need
3798
+ * anything cross-file to fold (only a file's own successful fold could ever
3799
+ * reach this taint — an unresolvable cross-file reference already fails
3800
+ * that file's OWN fold attempt), the safe rule is to force `network.ts`
3801
+ * back to run too, so both `alb.ts`'s real import and `network.ts`'s own
3802
+ * discovery entry resolve through the exact same `importModule` call and
3803
+ * share the exact same singleton module instance. This has to propagate
3804
+ * transitively (if `alb.ts` itself is only reachable by importing a file
3805
+ * that imports `network.ts`), so this is a forward-reachability walk over
3806
+ * the discovered files' relative-import graph, seeded from every file that
3807
+ * doesn't fold on its own.
3808
+ *
3809
+ * chant #1044 adds the OTHER half of the same hazard, in the opposite
3810
+ * direction along the same edges. Forward taint covers "a run file imports a
3811
+ * folded file"; it does not cover "a FOLDED file consumed the objects of a
3812
+ * file that later got forced to run". Once a plain-call intrinsic can fold,
3813
+ * that second case is easy to reach: in `lexicons/aws/examples/lambda-api`,
3814
+ * `health-api.ts` folds and captures `params.ts`'s real `Parameter` instance
3815
+ * through `Ref(environment)`, while `params.ts` itself is forced to run
3816
+ * because a DIFFERENT sibling (`data-bucket.ts`) imports it and falls back.
3817
+ * Discovery then collects the run instance and serializes the folded one —
3818
+ * the same "Logical name not set" crash described above, arriving from the
3819
+ * other side. So `liveSources` (see {@link FoldFileResult}) contributes
3820
+ * reverse edges here: a tainted file taints every folded file that captured
3821
+ * one of its objects. Only object identity propagates — a file that imported
3822
+ * a plain string from a tainted file has nothing to disagree about.
3823
+ *
3824
+ * chant #1020 changes the calculus but not this function: `alb.ts` can now
3825
+ * often fold `network.vpc.VpcId` too (see `buildExternals`/`foldFileMemoized`
3826
+ * above), by reusing THE EXACT SAME `tryFoldFile("network.ts")` call (memoized
3827
+ * per `FoldSession`) that `discover()`'s own per-file loop also uses — so
3828
+ * `alb.ts` and `network.ts` share one real `vpc` object without ever
3829
+ * disagreeing. This invariant is still needed for whatever STILL falls back
3830
+ * after #1020 (a call-as-a-value construct, #1044; a shape #1020 doesn't
3831
+ * cover): the edge collection below is unconditional — it doesn't care
3832
+ * whether an edge happens to ALSO be used for cross-file value resolution —
3833
+ * so the exact same forced-taint safety net still applies to that remaining
3834
+ * boundary, unchanged.
3835
+ */
3836
+ /**
3837
+ * file -> set of OTHER files in `files` that it relatively imports OR
3838
+ * re-exports from, regardless of that file's own fold outcome. Extracted
3839
+ * from {@link planFoldTaint} (chant #1083) so a second consumer — the fold
3840
+ * blocker dominator ranking (`./fold-rank.ts`) — can walk the exact same
3841
+ * project-file edges instead of re-parsing every file with its own
3842
+ * specifier-resolution logic. chant #1020 — this counts a namespace import
3843
+ * (`import * as ns from "./x"`, previously skipped — see `collectImports`)
3844
+ * and a re-export (`export { x } from "./y"`, which `collectImports` never
3845
+ * saw at all, since it only looks at `import` declarations): both are real
3846
+ * cross-file value dependencies fold's own resolution follows, so both need
3847
+ * the same edge an ordinary named import already gets.
3848
+ */
3849
+ export async function buildProjectImportEdges(files) {
3850
+ const fileSet = new Set(files);
3851
+ const edges = new Map();
3852
+ for (const file of files) {
3853
+ const targets = new Set();
3854
+ const addTarget = (specifier) => {
3855
+ if (!isProjectImport(specifier, file))
3856
+ return; // package import, not a sibling source file
3857
+ let resolved;
3858
+ try {
3859
+ resolved = resolveModulePath(specifier, file);
3860
+ }
3861
+ catch {
3862
+ return;
3863
+ }
3864
+ if (fileSet.has(resolved))
3865
+ targets.add(resolved);
3866
+ };
3867
+ try {
3868
+ const source = await readFile(file, "utf-8");
3869
+ const sourceFile = ts.createSourceFile(file, source, ts.ScriptTarget.Latest, true);
3870
+ const collected = collectImports(sourceFile);
3871
+ for (const binding of collected.named.values())
3872
+ addTarget(binding.specifier);
3873
+ for (const binding of collected.namespaces.values())
3874
+ addTarget(binding.specifier);
3875
+ for (const statement of sourceFile.statements) {
3876
+ if (ts.isExportDeclaration(statement) &&
3877
+ !statement.isTypeOnly &&
3878
+ statement.moduleSpecifier &&
3879
+ ts.isStringLiteral(statement.moduleSpecifier)) {
3880
+ addTarget(statement.moduleSpecifier.text);
3881
+ }
3882
+ }
3883
+ }
3884
+ catch {
3885
+ // Unreadable/unparseable — no edges contributed; tryFoldFile's own
3886
+ // top-level catch already turns this into a "run" decision for the
3887
+ // file itself, which is enough to seed it as tainted below (or, for
3888
+ // the dominator ranking, leaves it a childless node).
3889
+ }
3890
+ edges.set(file, targets);
3891
+ }
3892
+ return edges;
3893
+ }
3894
+ export async function planFoldTaint(files, wouldFold, liveSources) {
3895
+ return (await planFoldTaintWithEdges(files, wouldFold, liveSources)).tainted;
3896
+ }
3897
+ /**
3898
+ * {@link planFoldTaint}'s walk, keeping the edge that fired for each file it
3899
+ * reaches. Split out for two consumers that need to say *why*: the fallback
3900
+ * reason in `discover()`, and {@link foldProject}'s verdicts.
3901
+ */
3902
+ export async function planFoldTaintWithEdges(files, wouldFold, liveSources) {
3903
+ const fileSet = new Set(files);
3904
+ // file -> set of OTHER discovered files it relatively imports OR re-exports
3905
+ // from — see {@link buildProjectImportEdges}.
3906
+ const edges = await buildProjectImportEdges(files);
3907
+ // chant #1044 — reverse edges: consumed-file -> the folded files that
3908
+ // captured its objects. Same taint set, same fixpoint walk; see this
3909
+ // function's doc for the crash this closes. Kept in their own map as well,
3910
+ // so the walk below can tell which rule an edge belongs to.
3911
+ const reverse = new Map();
3912
+ for (const [consumer, sources] of liveSources ?? []) {
3913
+ if (!fileSet.has(consumer))
3914
+ continue;
3915
+ for (const source of sources) {
3916
+ if (!fileSet.has(source))
3917
+ continue;
3918
+ let back = edges.get(source);
3919
+ if (!back) {
3920
+ back = new Set();
3921
+ edges.set(source, back);
3922
+ }
3923
+ back.add(consumer);
3924
+ let mine = reverse.get(source);
3925
+ if (!mine) {
3926
+ mine = new Set();
3927
+ reverse.set(source, mine);
3928
+ }
3929
+ mine.add(consumer);
3930
+ }
3931
+ }
3932
+ const tainted = new Set(files.filter((f) => wouldFold.get(f) !== true));
3933
+ const reachedBy = new Map();
3934
+ const queue = [...tainted];
3935
+ while (queue.length > 0) {
3936
+ const current = queue.shift();
3937
+ for (const target of edges.get(current) ?? []) {
3938
+ if (!tainted.has(target)) {
3939
+ tainted.add(target);
3940
+ // A forward edge means `current` imports `target`; the reverse map is
3941
+ // consulted second so a file reachable both ways is reported as the
3942
+ // importer case, the one a reader can see in its own source.
3943
+ const forward = !reverse.get(current)?.has(target);
3944
+ reachedBy.set(target, { from: current, kind: forward ? "importer" : "capture" });
3945
+ queue.push(target);
3946
+ }
3947
+ }
3948
+ }
3949
+ return { tainted, reachedBy };
3950
+ }
3951
+ /**
3952
+ * Fold a whole set of project files and report every file's verdict
3953
+ * (chant#2408).
3954
+ *
3955
+ * `discover()` does this as part of building, and its per-file decisions are
3956
+ * not addressable from outside. Anything that wants to *check* the
3957
+ * cross-file rules needs the whole picture: the forward rule (#1023) needs an
3958
+ * importer, the reverse rule (#1044) needs a capturing sibling, and the
3959
+ * fixpoint needs the whole set, so none of the three is observable one file at
3960
+ * a time. The conformance suite of the specification is the first such caller
3961
+ * (INTENTIUS/typescript-as-data#62).
3962
+ *
3963
+ * Same session, same memo, same taint walk as a real build — this is not a
3964
+ * second implementation of the rules, it is the existing one with its
3965
+ * intermediate results kept instead of consumed.
3966
+ *
3967
+ * `options` carries the rest of what a build's session holds (chant#2422).
3968
+ * Omitting it answers a harsher question than a real build asks: with no
3969
+ * lexicon list nothing reading a lexicon data export folds, and with no build
3970
+ * parameters nothing reading `params` does. See {@link FoldProjectOptions}.
3971
+ */
3972
+ export async function foldProject(files, intrinsics = [], options = {}) {
3973
+ // chant#2455 — `sandbox` refuses to import project code at all and
3974
+ // `executing` exists to invoke it. Asking for both is not a preference
3975
+ // between two readings, it is a contradiction, and silently picking one would
3976
+ // make the fold's meaning depend on which.
3977
+ if (options.sandbox === true && options.executing === true) {
3978
+ throw new Error("foldProject: `sandbox` and `executing` are mutually exclusive — the first refuses to import " +
3979
+ "project code and the second exists to invoke it. Pass one.");
3980
+ }
3981
+ const session = createFoldSession(intrinsics, options.buildParams, options.lexicons ?? [], options.sandbox ?? false, options.lexiconPackages ?? [], options.executing ?? false, files);
3982
+ const attempts = new Map();
3983
+ for (const file of files)
3984
+ attempts.set(file, await tryFoldFile(file, intrinsics, session));
3985
+ const plan = await planFoldTaintWithEdges(files, new Map(files.map((file) => [file, attempts.get(file)?.ok === true])), new Map(files.flatMap((file) => {
3986
+ const attempt = attempts.get(file);
3987
+ return attempt?.ok === true ? [[file, attempt.liveSources]] : [];
3988
+ })));
3989
+ const folded = files.filter((file) => attempts.get(file)?.ok === true && !plan.tainted.has(file));
3990
+ const provenanceByFile = foldProvenanceByFile(folded.map((file) => {
3991
+ const attempt = attempts.get(file);
3992
+ return {
3993
+ file,
3994
+ exports: attempt?.ok === true ? Object.fromEntries(attempt.exportedValues) : {},
3995
+ };
3996
+ }));
3997
+ const out = new Map();
3998
+ for (const file of files) {
3999
+ const attempt = attempts.get(file);
4000
+ const tentative = attempt.ok ? "fold" : "run";
4001
+ if (attempt.ok && !plan.tainted.has(file)) {
4002
+ const foldProvenance = provenanceByFile?.get(file);
4003
+ out.set(file, {
4004
+ verdict: "fold",
4005
+ tentative,
4006
+ exports: attempt.exportedValues,
4007
+ ...(foldProvenance ? { foldProvenance } : {}),
4008
+ });
4009
+ continue;
4010
+ }
4011
+ out.set(file, {
4012
+ verdict: "run",
4013
+ tentative,
4014
+ reason: attempt.ok ? undefined : attempt.reason,
4015
+ taintedBy: attempt.ok ? plan.reachedBy.get(file) : undefined,
4016
+ });
4017
+ }
4018
+ return out;
4019
+ }
4020
+ /**
4021
+ * chant#3598 — the folded files' fold provenance, split by declaring file.
4022
+ *
4023
+ * This is `build()`'s computation, not a second one: the same
4024
+ * `collectEntities` over the same folded export namespaces `discover()` hands
4025
+ * it, then `foldProvenanceOfEntities` with `getProvenance`. One call over the
4026
+ * whole folded set rather than one per file, because composite expansion and
4027
+ * cross-directory name disambiguation (#932) are decided over the set and an
4028
+ * entity's name is part of what is being reported. The build root is the
4029
+ * files' common directory, which is what `chant build <dir>` passes when every
4030
+ * file sits under `<dir>`.
4031
+ *
4032
+ * Returns `undefined` when collection throws (a duplicate name), the case
4033
+ * where a build has no entities either.
4034
+ */
4035
+ function foldProvenanceByFile(modules) {
4036
+ if (modules.length === 0)
4037
+ return new Map();
4038
+ let entities;
4039
+ try {
4040
+ entities = collectEntities(modules, commonDirectory(modules.map((m) => m.file)));
4041
+ }
4042
+ catch {
4043
+ return undefined;
4044
+ }
4045
+ const byResolved = new Map();
4046
+ const out = new Map();
4047
+ for (const { file } of modules) {
4048
+ const record = {};
4049
+ byResolved.set(resolvePath(file), record);
4050
+ out.set(file, record);
4051
+ }
4052
+ const all = foldProvenanceOfEntities(entities, getProvenance);
4053
+ for (const [name, record] of Object.entries(all)) {
4054
+ const sourceFile = getProvenance(entities.get(name))?.sourceFile;
4055
+ const target = sourceFile === undefined ? undefined : byResolved.get(resolvePath(sourceFile));
4056
+ if (target)
4057
+ target[name] = record;
4058
+ }
4059
+ return out;
4060
+ }
4061
+ /** The deepest directory every one of `files` sits under. */
4062
+ function commonDirectory(files) {
4063
+ const split = files.map((file) => dirname(resolvePath(file)).split(sep));
4064
+ const first = split[0];
4065
+ let depth = first.length;
4066
+ for (const parts of split.slice(1)) {
4067
+ let i = 0;
4068
+ while (i < depth && i < parts.length && parts[i] === first[i])
4069
+ i++;
4070
+ depth = i;
4071
+ }
4072
+ return first.slice(0, depth).join(sep) || sep;
4073
+ }