@intentius/chant 0.124.12 → 0.124.14

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