@intentius/chant 0.101.0 → 0.102.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (427) hide show
  1. package/dist/agents/checks.d.ts.map +1 -1
  2. package/dist/attrref.d.ts.map +1 -1
  3. package/dist/audit/discover.d.ts.map +1 -1
  4. package/dist/audit/nginx.d.ts.map +1 -1
  5. package/dist/audit/prior-art.d.ts +48 -0
  6. package/dist/audit/prior-art.d.ts.map +1 -1
  7. package/dist/behaviour-kinds.d.ts.map +1 -1
  8. package/dist/builder.d.ts.map +1 -1
  9. package/dist/cli/commands/carve-emit.d.ts.map +1 -1
  10. package/dist/cli/commands/check-lexicon.d.ts +16 -0
  11. package/dist/cli/commands/check-lexicon.d.ts.map +1 -1
  12. package/dist/cli/commands/import.d.ts +8 -0
  13. package/dist/cli/commands/import.d.ts.map +1 -1
  14. package/dist/cli/commands/init-lexicon.d.ts.map +1 -1
  15. package/dist/cli/commands/init.d.ts +19 -0
  16. package/dist/cli/commands/init.d.ts.map +1 -1
  17. package/dist/cli/commands/onboard.d.ts +9 -0
  18. package/dist/cli/commands/onboard.d.ts.map +1 -1
  19. package/dist/cli/commands/pinned-upgrade.d.ts +19 -5
  20. package/dist/cli/commands/pinned-upgrade.d.ts.map +1 -1
  21. package/dist/cli/handlers/dev.d.ts.map +1 -1
  22. package/dist/cli/handlers/lifecycle.d.ts.map +1 -1
  23. package/dist/cli/handlers/misc.d.ts.map +1 -1
  24. package/dist/cli/handlers/operator.d.ts +9 -0
  25. package/dist/cli/handlers/operator.d.ts.map +1 -1
  26. package/dist/cli/handlers/run.d.ts.map +1 -1
  27. package/dist/cli/lsp/server.d.ts.map +1 -1
  28. package/dist/cli/main.d.ts.map +1 -1
  29. package/dist/cli/mcp/server.d.ts.map +1 -1
  30. package/dist/cli/mcp/tools/build.d.ts.map +1 -1
  31. package/dist/cli/mcp/tools/composites.d.ts.map +1 -1
  32. package/dist/cli/mcp/tools/explain.d.ts.map +1 -1
  33. package/dist/cli/mcp/tools/import.d.ts.map +1 -1
  34. package/dist/cli/mcp/tools/lint.d.ts.map +1 -1
  35. package/dist/cli/mcp/tools/scaffold.d.ts.map +1 -1
  36. package/dist/cli/mcp/tools/search.d.ts.map +1 -1
  37. package/dist/cli/mcp/workspace-tools.d.ts +1 -1
  38. package/dist/cli/mcp/workspace-tools.d.ts.map +1 -1
  39. package/dist/cli/registry.d.ts +11 -2
  40. package/dist/cli/registry.d.ts.map +1 -1
  41. package/dist/codegen/fetch.d.ts.map +1 -1
  42. package/dist/codegen/naming.d.ts.map +1 -1
  43. package/dist/codegen/pinned-upgrade.d.ts +67 -3
  44. package/dist/codegen/pinned-upgrade.d.ts.map +1 -1
  45. package/dist/codegen/registry.d.ts.map +1 -1
  46. package/dist/components/capability-plugin-loader.d.ts.map +1 -1
  47. package/dist/components/capability.d.ts.map +1 -1
  48. package/dist/components/driver.d.ts.map +1 -1
  49. package/dist/components/fan-out.d.ts.map +1 -1
  50. package/dist/components/starter-plugin.d.ts.map +1 -1
  51. package/dist/components/verbs/process-runner.d.ts.map +1 -1
  52. package/dist/components/verbs/r2-sync.d.ts.map +1 -1
  53. package/dist/components/verbs/run-agent.d.ts.map +1 -1
  54. package/dist/components/verbs/sbom-generator.d.ts.map +1 -1
  55. package/dist/components/verbs/sign.d.ts.map +1 -1
  56. package/dist/components/verbs/verify.d.ts.map +1 -1
  57. package/dist/components/verbs/vuln-gate.d.ts.map +1 -1
  58. package/dist/components/verbs/vuln-scan.d.ts.map +1 -1
  59. package/dist/components/verbs/wrangler.d.ts.map +1 -1
  60. package/dist/composite.d.ts.map +1 -1
  61. package/dist/config.d.ts +19 -7
  62. package/dist/config.d.ts.map +1 -1
  63. package/dist/declarable.d.ts +16 -0
  64. package/dist/declarable.d.ts.map +1 -1
  65. package/dist/discovery/cache.d.ts.map +1 -1
  66. package/dist/discovery/fold-import.d.ts +15 -5
  67. package/dist/discovery/fold-import.d.ts.map +1 -1
  68. package/dist/discovery/param-deps.d.ts +16 -0
  69. package/dist/discovery/param-deps.d.ts.map +1 -1
  70. package/dist/discovery/sandbox/policy-wire.d.ts.map +1 -1
  71. package/dist/discovery/specifier-resolve.d.ts +37 -0
  72. package/dist/discovery/specifier-resolve.d.ts.map +1 -0
  73. package/dist/errors.d.ts.map +1 -1
  74. package/dist/fold/fold.d.ts +3 -1
  75. package/dist/fold/fold.d.ts.map +1 -1
  76. package/dist/graph-detail.d.ts.map +1 -1
  77. package/dist/graph-ir.d.ts.map +1 -1
  78. package/dist/graph-layout.d.ts.map +1 -1
  79. package/dist/import/base-parser.d.ts.map +1 -1
  80. package/dist/import/embedded.d.ts +7 -0
  81. package/dist/import/embedded.d.ts.map +1 -1
  82. package/dist/import/parser-options.d.ts +14 -0
  83. package/dist/import/parser-options.d.ts.map +1 -0
  84. package/dist/import/parser.d.ts +17 -0
  85. package/dist/import/parser.d.ts.map +1 -1
  86. package/dist/kubectl-context.d.ts.map +1 -1
  87. package/dist/lexicon-output.d.ts.map +1 -1
  88. package/dist/lexicon-schema.d.ts +3 -3
  89. package/dist/lexicon.d.ts +55 -3
  90. package/dist/lexicon.d.ts.map +1 -1
  91. package/dist/lifecycle/assert-live.d.ts.map +1 -1
  92. package/dist/lifecycle/gate-ledger.d.ts +15 -0
  93. package/dist/lifecycle/gate-ledger.d.ts.map +1 -1
  94. package/dist/lifecycle/git.d.ts.map +1 -1
  95. package/dist/lifecycle/member-ledger.d.ts.map +1 -1
  96. package/dist/lifecycle/observe.d.ts.map +1 -1
  97. package/dist/lifecycle/plan-ledger.d.ts.map +1 -1
  98. package/dist/lifecycle/release-ledger.d.ts.map +1 -1
  99. package/dist/lifecycle/scenario.d.ts.map +1 -1
  100. package/dist/lifecycle/status.d.ts.map +1 -1
  101. package/dist/lifecycle/work-lease.d.ts +61 -2
  102. package/dist/lifecycle/work-lease.d.ts.map +1 -1
  103. package/dist/lint/config.d.ts +8 -8
  104. package/dist/lint/engine.d.ts +6 -0
  105. package/dist/lint/engine.d.ts.map +1 -1
  106. package/dist/lsp/lexicon-providers.d.ts.map +1 -1
  107. package/dist/op/activities/activity-contracts.d.ts +11 -11
  108. package/dist/op/activities/apply.d.ts +79 -9
  109. package/dist/op/activities/apply.d.ts.map +1 -1
  110. package/dist/op/activities/decide.d.ts.map +1 -1
  111. package/dist/op/activities/index.d.ts +1 -1
  112. package/dist/op/activities/index.d.ts.map +1 -1
  113. package/dist/op/activities/lexicon-upgrade.d.ts +44 -12
  114. package/dist/op/activities/lexicon-upgrade.d.ts.map +1 -1
  115. package/dist/op/activity-contract.d.ts +1 -1
  116. package/dist/op/activity-failure.d.ts.map +1 -1
  117. package/dist/op/activity-profiles.d.ts.map +1 -1
  118. package/dist/op/builders.d.ts.map +1 -1
  119. package/dist/op/composites/apply-op.d.ts +12 -5
  120. package/dist/op/composites/apply-op.d.ts.map +1 -1
  121. package/dist/op/composites/lexicon-upgrade-op.d.ts +22 -6
  122. package/dist/op/composites/lexicon-upgrade-op.d.ts.map +1 -1
  123. package/dist/op/decide-backend.d.ts.map +1 -1
  124. package/dist/op/decide-read-inputs.d.ts.map +1 -1
  125. package/dist/op/emulator-lifecycle.d.ts +21 -2
  126. package/dist/op/emulator-lifecycle.d.ts.map +1 -1
  127. package/dist/op/gate-approval.d.ts +5 -1
  128. package/dist/op/gate-approval.d.ts.map +1 -1
  129. package/dist/op/gate-wait.d.ts.map +1 -1
  130. package/dist/op/gate.d.ts +19 -3
  131. package/dist/op/gate.d.ts.map +1 -1
  132. package/dist/op/index.d.ts +2 -0
  133. package/dist/op/index.d.ts.map +1 -1
  134. package/dist/op/local-executor.d.ts +6 -0
  135. package/dist/op/local-executor.d.ts.map +1 -1
  136. package/dist/op/operator.d.ts.map +1 -1
  137. package/dist/op/run-context.d.ts +39 -0
  138. package/dist/op/run-context.d.ts.map +1 -0
  139. package/dist/op/run-live.d.ts.map +1 -1
  140. package/dist/op/runtimes/local.d.ts.map +1 -1
  141. package/dist/op/source-archive.d.ts.map +1 -1
  142. package/dist/op/steward-beside.d.ts.map +1 -1
  143. package/dist/op/steward-points.d.ts.map +1 -1
  144. package/dist/op/steward.d.ts.map +1 -1
  145. package/dist/op/types.d.ts +8 -3
  146. package/dist/op/types.d.ts.map +1 -1
  147. package/dist/op/work-lease-run.d.ts +24 -4
  148. package/dist/op/work-lease-run.d.ts.map +1 -1
  149. package/dist/project-root.d.ts.map +1 -1
  150. package/dist/provenance.d.ts +21 -0
  151. package/dist/provenance.d.ts.map +1 -1
  152. package/dist/pseudo-parameter.d.ts.map +1 -1
  153. package/dist/reconcile.d.ts.map +1 -1
  154. package/dist/secret-materialization.d.ts.map +1 -1
  155. package/dist/terraform/emit-source.d.ts.map +1 -1
  156. package/dist/terraform/parse.d.ts.map +1 -1
  157. package/dist/testing.d.ts.map +1 -1
  158. package/dist/workspace/__fixtures__/contract-repo.d.ts.map +1 -1
  159. package/dist/workspace/__fixtures__/decide-workspace.d.ts +1 -1
  160. package/dist/workspace/__fixtures__/decide-workspace.d.ts.map +1 -1
  161. package/dist/workspace/agent-cli.d.ts +5 -0
  162. package/dist/workspace/agent-cli.d.ts.map +1 -1
  163. package/dist/workspace/box-factory.d.ts +73 -0
  164. package/dist/workspace/box-factory.d.ts.map +1 -0
  165. package/dist/workspace/changes.d.ts.map +1 -1
  166. package/dist/workspace/chant-migrations/chud-lexicon-exit.d.ts.map +1 -1
  167. package/dist/workspace/checks/boxes.d.ts.map +1 -1
  168. package/dist/workspace/checks.d.ts +5 -0
  169. package/dist/workspace/checks.d.ts.map +1 -1
  170. package/dist/workspace/compose-graph.d.ts +10 -0
  171. package/dist/workspace/compose-graph.d.ts.map +1 -1
  172. package/dist/workspace/conformance/index.d.ts +67 -6
  173. package/dist/workspace/conformance/index.d.ts.map +1 -1
  174. package/dist/workspace/conformance/vitest.d.ts +12 -3
  175. package/dist/workspace/conformance/vitest.d.ts.map +1 -1
  176. package/dist/workspace/conformance/writer.d.ts +368 -0
  177. package/dist/workspace/conformance/writer.d.ts.map +1 -0
  178. package/dist/workspace/decide.d.ts +1 -1
  179. package/dist/workspace/decide.d.ts.map +1 -1
  180. package/dist/workspace/declaration.d.ts +91 -4
  181. package/dist/workspace/declaration.d.ts.map +1 -1
  182. package/dist/workspace/declaration.schema.json +234 -5
  183. package/dist/workspace/generated-files.d.ts.map +1 -1
  184. package/dist/workspace/graph-cache.d.ts.map +1 -1
  185. package/dist/workspace/graph-cli.d.ts +2 -0
  186. package/dist/workspace/graph-cli.d.ts.map +1 -1
  187. package/dist/workspace/identity.d.ts +111 -0
  188. package/dist/workspace/identity.d.ts.map +1 -0
  189. package/dist/workspace/intent-cli.d.ts +7 -4
  190. package/dist/workspace/intent-cli.d.ts.map +1 -1
  191. package/dist/workspace/intent-record.d.ts +8 -1
  192. package/dist/workspace/intent-record.d.ts.map +1 -1
  193. package/dist/workspace/intent.d.ts +149 -5
  194. package/dist/workspace/intent.d.ts.map +1 -1
  195. package/dist/workspace/kinds.d.ts +18 -4
  196. package/dist/workspace/kinds.d.ts.map +1 -1
  197. package/dist/workspace/lineage-hash-index.d.ts.map +1 -1
  198. package/dist/workspace/lineage-lock.d.ts +2 -2
  199. package/dist/workspace/lineage-lock.d.ts.map +1 -1
  200. package/dist/workspace/links.d.ts.map +1 -1
  201. package/dist/workspace/points.d.ts.map +1 -1
  202. package/dist/workspace/principal-classes.d.ts +114 -0
  203. package/dist/workspace/principal-classes.d.ts.map +1 -0
  204. package/dist/workspace/reason-codes.d.ts +21 -1
  205. package/dist/workspace/reason-codes.d.ts.map +1 -1
  206. package/dist/workspace/record-assets.d.ts.map +1 -1
  207. package/dist/workspace/records-checkout.d.ts +54 -0
  208. package/dist/workspace/records-checkout.d.ts.map +1 -0
  209. package/dist/workspace/records-cli.d.ts +27 -1
  210. package/dist/workspace/records-cli.d.ts.map +1 -1
  211. package/dist/workspace/records-close.d.ts +1 -1
  212. package/dist/workspace/records-close.d.ts.map +1 -1
  213. package/dist/workspace/records-since.d.ts.map +1 -1
  214. package/dist/workspace/records-write.d.ts +5 -4
  215. package/dist/workspace/records-write.d.ts.map +1 -1
  216. package/dist/workspace/records.d.ts +15 -0
  217. package/dist/workspace/records.d.ts.map +1 -1
  218. package/dist/workspace/runs-cli.d.ts +164 -0
  219. package/dist/workspace/runs-cli.d.ts.map +1 -0
  220. package/dist/workspace/runs.d.ts +423 -0
  221. package/dist/workspace/runs.d.ts.map +1 -0
  222. package/dist/workspace/source-block.d.ts +1 -1
  223. package/dist/workspace/source-block.d.ts.map +1 -1
  224. package/dist/workspace/status-gates.d.ts +15 -3
  225. package/dist/workspace/status-gates.d.ts.map +1 -1
  226. package/dist/workspace/status.d.ts +10 -0
  227. package/dist/workspace/status.d.ts.map +1 -1
  228. package/dist/workspace/symbols.d.ts +52 -0
  229. package/dist/workspace/symbols.d.ts.map +1 -0
  230. package/dist/workspace/trailer-joins.d.ts +46 -0
  231. package/dist/workspace/trailer-joins.d.ts.map +1 -0
  232. package/dist/workspace/trailers.d.ts +89 -0
  233. package/dist/workspace/trailers.d.ts.map +1 -0
  234. package/dist/workspace/trust/attestor.d.ts.map +1 -1
  235. package/dist/workspace/trust/rotation.d.ts.map +1 -1
  236. package/dist/workspace/trust/seal.d.ts +33 -0
  237. package/dist/workspace/trust/seal.d.ts.map +1 -1
  238. package/dist/workspace/trust/test-repo.d.ts.map +1 -1
  239. package/dist/workspace/work-cli.d.ts +39 -3
  240. package/dist/workspace/work-cli.d.ts.map +1 -1
  241. package/dist/workspace/work-evidence.d.ts +1 -1
  242. package/dist/workspace/work-evidence.d.ts.map +1 -1
  243. package/dist/workspace/work.d.ts +26 -1
  244. package/dist/workspace/work.d.ts.map +1 -1
  245. package/dist/workspace/workspace-principals.schema.json +60 -0
  246. package/dist/workspace/write-scope.d.ts +79 -19
  247. package/dist/workspace/write-scope.d.ts.map +1 -1
  248. package/dist/yaml.d.ts +21 -39
  249. package/dist/yaml.d.ts.map +1 -1
  250. package/package.json +2 -2
  251. package/src/audit/prior-art.ts +15 -0
  252. package/src/cli/commands/__fixtures__/init-lexicon-output/package.json +1 -1
  253. package/src/cli/commands/carve-emit-state.test.ts +4 -0
  254. package/src/cli/commands/carve-emit.ts +5 -19
  255. package/src/cli/commands/check-lexicon.test.ts +62 -1
  256. package/src/cli/commands/check-lexicon.ts +36 -6
  257. package/src/cli/commands/import-live.test.ts +18 -0
  258. package/src/cli/commands/import-parser-options.test.ts +133 -0
  259. package/src/cli/commands/import.ts +21 -3
  260. package/src/cli/commands/init-lexicon/templates/project.ts +1 -1
  261. package/src/cli/commands/init.test.ts +8 -1
  262. package/src/cli/commands/init.ts +23 -8
  263. package/src/cli/commands/lint.test.ts +37 -0
  264. package/src/cli/commands/onboard.test.ts +53 -3
  265. package/src/cli/commands/onboard.ts +52 -4
  266. package/src/cli/commands/pinned-upgrade.ts +73 -33
  267. package/src/cli/handlers/__snapshots__/build-member.test.ts.snap +2 -2
  268. package/src/cli/handlers/components.ts +2 -2
  269. package/src/cli/handlers/dev.ts +8 -6
  270. package/src/cli/handlers/lifecycle.test.ts +67 -0
  271. package/src/cli/handlers/lifecycle.ts +6 -5
  272. package/src/cli/handlers/misc.ts +2 -0
  273. package/src/cli/handlers/operator.ts +87 -3
  274. package/src/cli/handlers/run.test.ts +111 -0
  275. package/src/cli/handlers/run.ts +85 -24
  276. package/src/cli/handlers/scenario.ts +2 -2
  277. package/src/cli/main.test.ts +1 -0
  278. package/src/cli/main.ts +65 -11
  279. package/src/cli/mcp/workspace-tools.test.ts +5 -2
  280. package/src/cli/mcp/workspace-tools.ts +29 -2
  281. package/src/cli/registry.ts +11 -2
  282. package/src/codegen/pinned-upgrade-multi.test.ts +157 -0
  283. package/src/codegen/pinned-upgrade-tags.test.ts +112 -0
  284. package/src/codegen/pinned-upgrade.test.ts +104 -0
  285. package/src/codegen/pinned-upgrade.ts +250 -56
  286. package/src/codegen/testdata/postgres-tags.json +242 -0
  287. package/src/config.test.ts +21 -0
  288. package/src/config.ts +15 -0
  289. package/src/declarable.test.ts +21 -0
  290. package/src/declarable.ts +19 -0
  291. package/src/discovery/fold-composite.test.ts +41 -5
  292. package/src/discovery/fold-import.test.ts +129 -0
  293. package/src/discovery/fold-import.ts +643 -131
  294. package/src/discovery/fold-package-composite.test.ts +264 -0
  295. package/src/discovery/fold-specifiers.test.ts +191 -0
  296. package/src/discovery/param-deps.test.ts +66 -1
  297. package/src/discovery/param-deps.ts +45 -0
  298. package/src/discovery/specifier-resolve.ts +344 -0
  299. package/src/fold/fold.test.ts +39 -1
  300. package/src/fold/fold.ts +83 -9
  301. package/src/fold-provenance.test.ts +140 -0
  302. package/src/graph-ir.test.ts +83 -0
  303. package/src/graph-ir.ts +29 -0
  304. package/src/import/embedded.test.ts +20 -0
  305. package/src/import/embedded.ts +13 -0
  306. package/src/import/parser-options.ts +46 -0
  307. package/src/import/parser.ts +20 -0
  308. package/src/lexicon.ts +55 -3
  309. package/src/lifecycle/gate-ledger.ts +16 -0
  310. package/src/lifecycle/observe.test.ts +18 -0
  311. package/src/lifecycle/observe.ts +6 -4
  312. package/src/lifecycle/snapshot.ts +2 -2
  313. package/src/lifecycle/work-lease.test.ts +1 -1
  314. package/src/lifecycle/work-lease.ts +93 -2
  315. package/src/lint/engine.test.ts +55 -0
  316. package/src/lint/engine.ts +30 -4
  317. package/src/lint/output-docs.test.ts +59 -56
  318. package/src/op/activities/apply.test.ts +189 -5
  319. package/src/op/activities/apply.ts +230 -8
  320. package/src/op/activities/index.ts +3 -0
  321. package/src/op/activities/lexicon-upgrade.test.ts +160 -6
  322. package/src/op/activities/lexicon-upgrade.ts +154 -51
  323. package/src/op/composites/apply-op.ts +12 -5
  324. package/src/op/composites/composites.test.ts +30 -3
  325. package/src/op/composites/lexicon-upgrade-op.test.ts +7 -2
  326. package/src/op/composites/lexicon-upgrade-op.ts +29 -8
  327. package/src/op/emulator-lifecycle.test.ts +23 -0
  328. package/src/op/emulator-lifecycle.ts +52 -14
  329. package/src/op/gate-approval.test.ts +74 -0
  330. package/src/op/gate-approval.ts +5 -1
  331. package/src/op/gate.ts +31 -5
  332. package/src/op/index.ts +2 -0
  333. package/src/op/local-executor.test.ts +15 -0
  334. package/src/op/local-executor.ts +40 -12
  335. package/src/op/local-output.ts +1 -1
  336. package/src/op/operator.ts +4 -0
  337. package/src/op/run-context.test.ts +141 -0
  338. package/src/op/run-context.ts +51 -0
  339. package/src/op/runtimes/local.test.ts +14 -0
  340. package/src/op/runtimes/local.ts +2 -0
  341. package/src/op/steward-beside.ts +2 -0
  342. package/src/op/types.ts +8 -3
  343. package/src/op/work-lease-run.test.ts +47 -0
  344. package/src/op/work-lease-run.ts +73 -11
  345. package/src/provenance.ts +35 -0
  346. package/src/workspace/__fixtures__/studio-template-84c8b21.json +1 -1
  347. package/src/workspace/agent-cli.ts +7 -3
  348. package/src/workspace/agent.schema.json +22 -0
  349. package/src/workspace/box-factory.test.ts +320 -0
  350. package/src/workspace/box-factory.ts +103 -0
  351. package/src/workspace/changes.schema.json +12 -20
  352. package/src/workspace/changes.ts +4 -1
  353. package/src/workspace/checks/boxes.test.ts +2 -0
  354. package/src/workspace/checks.ts +29 -5
  355. package/src/workspace/compose-graph.ts +7 -0
  356. package/src/workspace/conformance/__writer_fixture__/answers/answer.kind.mjs +27 -0
  357. package/src/workspace/conformance/__writer_fixture__/answers/answer.schema.json +95 -0
  358. package/src/workspace/conformance/__writer_fixture__/decisions/points.json +132 -0
  359. package/src/workspace/conformance/__writer_fixture__/sessions/session.kind.mjs +48 -0
  360. package/src/workspace/conformance/__writer_fixture__/sessions/session.schema.json +108 -0
  361. package/src/workspace/conformance/__writer_fixture__/work/W-001-the-writer-suite-works-this-item.md +25 -0
  362. package/src/workspace/conformance/__writer_fixture__/work/work.kind.mjs +60 -0
  363. package/src/workspace/conformance/__writer_fixture__/work/work.schema.json +578 -0
  364. package/src/workspace/conformance/conformance.e2e.test.ts +35 -3
  365. package/src/workspace/conformance/index.mjs +30 -3
  366. package/src/workspace/conformance/index.ts +103 -15
  367. package/src/workspace/conformance/vitest.ts +44 -2
  368. package/src/workspace/conformance/writer-conformance.e2e.test.ts +131 -0
  369. package/src/workspace/conformance/writer.test.ts +156 -0
  370. package/src/workspace/conformance/writer.ts +880 -0
  371. package/src/workspace/decide.ts +7 -1
  372. package/src/workspace/declaration.schema.json +234 -5
  373. package/src/workspace/declaration.ts +174 -10
  374. package/src/workspace/graph-cli.ts +5 -2
  375. package/src/workspace/graph.schema.json +247 -0
  376. package/src/workspace/identity.test.ts +196 -0
  377. package/src/workspace/identity.ts +186 -0
  378. package/src/workspace/intent-cli.ts +64 -10
  379. package/src/workspace/intent-record.schema.json +208 -0
  380. package/src/workspace/intent-record.ts +35 -1
  381. package/src/workspace/intent-why.test.ts +274 -0
  382. package/src/workspace/intent.schema.json +718 -7
  383. package/src/workspace/intent.test.ts +11 -2
  384. package/src/workspace/intent.ts +495 -32
  385. package/src/workspace/kinds.ts +44 -37
  386. package/src/workspace/points-write.schema.json +1 -0
  387. package/src/workspace/principal-classes.test.ts +175 -0
  388. package/src/workspace/principal-classes.ts +307 -0
  389. package/src/workspace/reason-codes.test.ts +11 -3
  390. package/src/workspace/reason-codes.ts +24 -1
  391. package/src/workspace/records-amend.schema.json +2 -0
  392. package/src/workspace/records-checkout.ts +106 -0
  393. package/src/workspace/records-cli.ts +103 -11
  394. package/src/workspace/records-close.schema.json +1 -0
  395. package/src/workspace/records-new.schema.json +2 -0
  396. package/src/workspace/records-review.schema.json +2 -0
  397. package/src/workspace/records-uncommitted.test.ts +171 -0
  398. package/src/workspace/records-write.ts +28 -6
  399. package/src/workspace/records.schema.json +72 -1
  400. package/src/workspace/records.ts +27 -0
  401. package/src/workspace/runs-cli.ts +381 -0
  402. package/src/workspace/runs-write.schema.json +583 -0
  403. package/src/workspace/runs.schema.json +810 -0
  404. package/src/workspace/runs.test.ts +242 -0
  405. package/src/workspace/runs.ts +709 -0
  406. package/src/workspace/status-contract.test.ts +5 -0
  407. package/src/workspace/status-gates.ts +22 -8
  408. package/src/workspace/status.schema.json +127 -2
  409. package/src/workspace/status.ts +21 -1
  410. package/src/workspace/symbols.ts +180 -0
  411. package/src/workspace/trailer-joins.test.ts +171 -0
  412. package/src/workspace/trailer-joins.ts +82 -0
  413. package/src/workspace/trailers.ts +148 -0
  414. package/src/workspace/trust/seal.ts +52 -1
  415. package/src/workspace/work-cli.e2e.test.ts +28 -1
  416. package/src/workspace/work-cli.ts +133 -8
  417. package/src/workspace/work-evidence.schema.json +2 -0
  418. package/src/workspace/work-history.schema.json +73 -1
  419. package/src/workspace/work-history.test.ts +48 -5
  420. package/src/workspace/work-kind-fields.test.ts +154 -0
  421. package/src/workspace/work-lease.schema.json +14 -1
  422. package/src/workspace/work.ts +82 -0
  423. package/src/workspace/workspace-principals.schema.json +60 -0
  424. package/src/workspace/write-scope.test.ts +128 -2
  425. package/src/workspace/write-scope.ts +170 -45
  426. package/src/yaml.test.ts +111 -1
  427. package/src/yaml.ts +141 -549
@@ -4,7 +4,7 @@ import { existsSync, statSync, readFileSync, realpathSync } from "node:fs";
4
4
  import { dirname, basename, join, sep, isAbsolute, resolve as resolvePath } from "node:path";
5
5
  import { fileURLToPath } from "node:url";
6
6
  import { createRequire } from "node:module";
7
- import { isDeclarable, type Declarable } from "../declarable";
7
+ import { isDeclarable, isResourceDeclarable, type Declarable } from "../declarable";
8
8
  import { Composite, isCompositeInstance, type CompositeInstance, type CompositeMembers } from "../composite";
9
9
  import { isAttrRefLike } from "../utils";
10
10
  import { isIntrinsic } from "../intrinsic";
@@ -28,6 +28,13 @@ import {
28
28
  } from "../fold/fold";
29
29
  import { isChantOwnedSpecifier, isFoldableHelperName } from "../fold/foldable-helpers";
30
30
  import { lexiconModulePath, lexiconModuleRoot } from "../lexicon-module";
31
+ import {
32
+ isFile,
33
+ packageExportsTarget,
34
+ probeModuleFile,
35
+ splitBareSpecifier,
36
+ tsconfigPathsMatch,
37
+ } from "./specifier-resolve";
31
38
  import {
32
39
  briefNodeText,
33
40
  callExpressionMessage,
@@ -44,10 +51,11 @@ import { importModule } from "./import";
44
51
  import {
45
52
  collectCompositeOrigins,
46
53
  collectParamDependencies,
54
+ collectTagOrigins,
47
55
  type CompositeParamScope,
48
56
  } from "./param-deps";
49
- import { setPathProvenance } from "../provenance";
50
- import { intrinsicCallFoldsEagerly, type IntrinsicDef } from "../lexicon";
57
+ import { getInterpolationFields, setPathProvenance } from "../provenance";
58
+ import { intrinsicCallFoldsEagerly, intrinsicTagFolds, type IntrinsicDef } from "../lexicon";
51
59
  import type { BuildParamValue } from "../build-params";
52
60
 
53
61
  /**
@@ -292,7 +300,8 @@ export interface FoldSession {
292
300
  readonly executing: boolean;
293
301
  /**
294
302
  * chant #1023 — per-build memo for {@link readFactoryModule}, keyed by the
295
- * resolved absolute path of a module that DEFINES a composite. A composite
303
+ * resolved absolute path of a module that DEFINES a composite (and, since
304
+ * chant#3247, the lexicon package root it was reached in, if any). A composite
296
305
  * defined once and called from a dozen sibling files is parsed, and its
297
306
  * imports resolved, exactly once per build — the same reason
298
307
  * {@link FoldSession.cache} exists for the files discovery folds directly.
@@ -454,14 +463,24 @@ function resolveModulePathMemoized(
454
463
  resolvePathCache: Map<string, string>,
455
464
  ): string {
456
465
  const isBare = !specifier.startsWith(".") && !isAbsolute(specifier);
466
+ // chant#3090 — a tsconfig `paths` answer depends on the importing file's
467
+ // directory and can name a project file that comes and goes between
468
+ // builds, so it is memoized per session and directory, like a relative one.
469
+ const key = `${dirname(fromFile)}\0${specifier}`;
457
470
  if (isBare) {
471
+ const sessionCached = resolvePathCache.get(key);
472
+ if (sessionCached !== undefined) return sessionCached;
473
+ const mapped = pathsMappedTarget(specifier, fromFile);
474
+ if (mapped !== undefined) {
475
+ resolvePathCache.set(key, mapped);
476
+ return mapped;
477
+ }
458
478
  const cached = bareSpecifierPathCache.get(specifier);
459
479
  if (cached !== undefined) return cached;
460
480
  const resolved = resolveModulePath(specifier, fromFile);
461
481
  bareSpecifierPathCache.set(specifier, resolved);
462
482
  return resolved;
463
483
  }
464
- const key = `${dirname(fromFile)}\0${specifier}`;
465
484
  const cached = resolvePathCache.get(key);
466
485
  if (cached !== undefined) return cached;
467
486
  const resolved = resolveModulePath(specifier, fromFile);
@@ -543,10 +562,10 @@ export interface FoldExecutionCounts {
543
562
  factoryInvocations: number;
544
563
  /**
545
564
  * Of {@link factoryInvocations}, the ones whose callee came from a PROJECT
546
- * FILE (a relative/absolute specifier) rather than a lexicon package or
547
- * chant's own — i.e. the ones that execute project-authored code here. Text
548
- * only ({@link isProjectFileSpecifier}); no resolution is performed to
549
- * classify.
565
+ * FILE (a relative/absolute specifier, or one a tsconfig `paths` entry
566
+ * maps) rather than a lexicon package or chant's own — i.e. the ones that
567
+ * execute project-authored code here ({@link isProjectImport}); no package
568
+ * resolution is performed to classify.
550
569
  */
551
570
  projectFactoryInvocations: number;
552
571
  /**
@@ -927,16 +946,21 @@ function collectImports(sourceFile: ts.SourceFile): CollectedImports {
927
946
  /**
928
947
  * chant #1020 hang fix — best-effort fast path for resolving a BARE
929
948
  * specifier by walking `node_modules` directly with the same primitives
930
- * (`existsSync`/`readFileSync`) the relative-specifier branch below already
949
+ * (`statSync`/`readFileSync`) the relative-specifier branch below already
931
950
  * uses, reading `package.json`'s "exports"/"main" field by hand instead of
932
951
  * calling `createRequire(fromFile).resolve(specifier)`. Returns `undefined`
933
- * — never throws — for anything beyond the simple, single-target shape
934
- * (a string `"exports"`, or an object whose `"."` entry is a string or a
935
- * flat, string-valued condition map): the caller falls back to the slow,
936
- * authoritative `createRequire().resolve()` path whenever this returns
937
- * `undefined`, so a genuinely complex `package.json` (a conditions array, a
938
- * self-reference, a `"."` entry the fast path doesn't recognize) is still
939
- * resolved correctly, just not quickly.
952
+ * — never throws — when the package is not found, its `exports` map does not
953
+ * export the subpath under the `import`/`node`/`default` conditions, or a
954
+ * subpath of a package without `exports` has no `package.json` of its own:
955
+ * the caller then falls back to the slow, authoritative
956
+ * `createRequire().resolve()` path, so such a specifier is still resolved
957
+ * correctly, just not quickly.
958
+ *
959
+ * chant#3090 — subpaths (`pkg/sub/x`) and `exports` pattern keys
960
+ * (`"./sub/*"`) resolve here too, with condition objects read in key order
961
+ * as an ESM import reads them ({@link packageExportsTarget}). Before, a
962
+ * subpath always reached the `createRequire` fallback, whose CommonJS
963
+ * conditions miss an `import`-only target.
940
964
  *
941
965
  * Why this exists at all: profiling traced the hang to
942
966
  * `createRequire(fromFile).resolve(specifier)` ITSELF taking upwards of a
@@ -946,22 +970,22 @@ function collectImports(sourceFile: ts.SourceFile): CollectedImports {
946
970
  * process (session-level and even process-wide-by-specifier caching, see
947
971
  * {@link bareSpecifierPathCache}, only avoid paying that cost a SECOND
948
972
  * time). The relative-specifier branch just below, using these exact same
949
- * `existsSync`/`statSync` primitives, never showed this slowdown anywhere
973
+ * file-system primitives, never showed this slowdown anywhere
950
974
  * in the same profiling — the cost is specific to Node's own
951
975
  * `Module._resolveFilename` machinery, not to file-system access in
952
- * general, so replacing just that one call with plain `existsSync`/
976
+ * general, so replacing just that one call with plain `statSync`/
953
977
  * `readFileSync` sidesteps it entirely for the common case every
954
978
  * `@intentius/chant*` package (and most well-formed npm packages) ships.
955
979
  */
956
980
  function fastResolveBareSpecifier(specifier: string, fromFile: string): string | undefined {
981
+ const parts = splitBareSpecifier(specifier);
982
+ if (parts === undefined) return undefined;
957
983
  let dir = dirname(fromFile);
958
984
  for (;;) {
959
- const packageDir = join(dir, "node_modules", specifier);
985
+ const packageDir = join(dir, "node_modules", parts.name);
960
986
  if (existsSync(packageDir)) {
961
- const entry = fastResolvePackageEntry(packageDir);
962
- if (entry === undefined) return undefined;
963
- const resolved = resolvePath(packageDir, entry);
964
- if (!existsSync(resolved) || !statSync(resolved).isFile()) return undefined;
987
+ const resolved = fastResolvePackageEntry(packageDir, parts.subpath);
988
+ if (resolved === undefined || !isFile(resolved)) return undefined;
965
989
  try {
966
990
  return realpathSync(resolved);
967
991
  } catch {
@@ -974,8 +998,14 @@ function fastResolveBareSpecifier(specifier: string, fromFile: string): string |
974
998
  }
975
999
  }
976
1000
 
977
- /** Read `<packageDir>/package.json`'s "." export target — see {@link fastResolveBareSpecifier}'s doc for exactly which shapes this recognizes; anything else returns `undefined`. */
978
- function fastResolvePackageEntry(packageDir: string): string | undefined {
1001
+ /**
1002
+ * The absolute file `<packageDir>/package.json` names for `subpath` (`"."` or
1003
+ * `"./x"`), or `undefined` — see {@link fastResolveBareSpecifier}'s doc for
1004
+ * which shapes this recognizes. With an `exports` field, that map decides.
1005
+ * Without one, the root is `main` (or `index.js`), and a subpath is read as a
1006
+ * nested package directory with its own `package.json`.
1007
+ */
1008
+ function fastResolvePackageEntry(packageDir: string, subpath: string): string | undefined {
979
1009
  const pkgJsonPath = join(packageDir, "package.json");
980
1010
  if (!existsSync(pkgJsonPath)) return undefined;
981
1011
  let pkg: unknown;
@@ -988,68 +1018,63 @@ function fastResolvePackageEntry(packageDir: string): string | undefined {
988
1018
  const exportsField = (pkg as Record<string, unknown>).exports;
989
1019
 
990
1020
  if (exportsField !== undefined) {
991
- if (typeof exportsField === "string") return exportsField;
992
- if (typeof exportsField !== "object" || exportsField === null || Array.isArray(exportsField)) {
993
- return undefined;
994
- }
995
- const exportsObj = exportsField as Record<string, unknown>;
996
- // No "." key at all means the WHOLE object IS the "." export's own
997
- // condition map (the shorthand form) — only when none of its OWN keys
998
- // look like a subpath/condition-name ambiguity risk (a key starting
999
- // with "." that isn't literally "." itself signals real subpath
1000
- // exports present, so bail rather than misparse).
1001
- const hasSubpathKeys = Object.keys(exportsObj).some((k) => k.startsWith(".") && k !== ".");
1002
- const target = "." in exportsObj ? exportsObj["."] : hasSubpathKeys ? undefined : exportsObj;
1003
- if (target === undefined) return undefined;
1004
- if (typeof target === "string") return target;
1005
- if (typeof target !== "object" || target === null || Array.isArray(target)) return undefined;
1006
- const conditions = target as Record<string, unknown>;
1007
- // Prefer "default" (present on every chant/lexicon package and the
1008
- // overwhelming majority of well-formed dual-mode npm packages); "node"/
1009
- // "import" as narrower fallbacks. Every chant package's own "default"
1010
- // and "development" conditions point at the identical source file, so
1011
- // which one Node's own algorithm would have picked never matters here.
1012
- for (const key of ["default", "node", "import"]) {
1013
- const val = conditions[key];
1014
- if (typeof val === "string") return val;
1015
- }
1016
- return undefined;
1021
+ const target = packageExportsTarget(exportsField, subpath);
1022
+ return target === undefined ? undefined : resolvePath(packageDir, target);
1017
1023
  }
1018
1024
 
1025
+ if (subpath !== ".") return fastResolvePackageEntry(join(packageDir, subpath), ".");
1019
1026
  const main = (pkg as Record<string, unknown>).main;
1020
- if (main !== undefined) return typeof main === "string" ? main : undefined;
1021
- return "index.js";
1027
+ if (main !== undefined) return typeof main === "string" ? resolvePath(packageDir, main) : undefined;
1028
+ return resolvePath(packageDir, "index.js");
1029
+ }
1030
+
1031
+ /**
1032
+ * chant#3090 — the file a tsconfig `paths` entry maps a bare `specifier` to,
1033
+ * from the nearest `tsconfig.json` above `fromFile` ({@link tsconfigPathsTarget}),
1034
+ * or `undefined`.
1035
+ *
1036
+ * chant's own package specifiers ({@link isChantOwnedSpecifier}) are never
1037
+ * mapped. They resolve through `node_modules` to the modules the CLI has
1038
+ * already loaded, which is what {@link isTrustedExecutableBinding} relies on,
1039
+ * and a project's `paths` cannot point them at its own files. This repository's
1040
+ * root tsconfig maps every chant package to its source, and those entries
1041
+ * name the same files `node_modules` does.
1042
+ *
1043
+ * A specifier this maps is a project import ({@link isProjectImport}): it is
1044
+ * folded as a project file and its trust is decided on the resolved path,
1045
+ * even when its text names an active lexicon package.
1046
+ */
1047
+ function pathsMappedTarget(specifier: string, fromFile: string): string | undefined {
1048
+ return pathsMatch(specifier, fromFile)?.target;
1049
+ }
1050
+
1051
+ /** The tsconfig `paths` match behind {@link pathsMappedTarget}, including a match whose targets name no file. */
1052
+ function pathsMatch(specifier: string, fromFile: string): { key: string; target: string | undefined } | undefined {
1053
+ if (specifier.startsWith(".") || isAbsolute(specifier) || isChantOwnedSpecifier(specifier)) return undefined;
1054
+ return tsconfigPathsMatch(specifier, fromFile);
1022
1055
  }
1023
1056
 
1024
1057
  /**
1025
1058
  * Resolve an import specifier to an absolute module path, the way the
1026
1059
  * declaring file's own `import` would — without depending on a TS-aware
1027
1060
  * loader being active. Relative/absolute specifiers are probed against real
1028
- * TS/JS candidate files on disk; bare package specifiers try
1029
- * {@link fastResolveBareSpecifier} first, falling back to Node's own CJS
1030
- * algorithm from the declaring file's location (lexicon packages ship built
1031
- * JS, so this needs no `.ts` awareness) whenever that returns `undefined`.
1061
+ * TS/JS candidate files on disk ({@link probeModuleFile}, which maps `./x.js`
1062
+ * to `./x.ts` as `tsc` does); a bare specifier a tsconfig `paths` entry maps
1063
+ * resolves to that file ({@link pathsMappedTarget}); other bare package
1064
+ * specifiers try {@link fastResolveBareSpecifier} first, falling back to
1065
+ * Node's own CJS algorithm from the declaring file's location whenever that
1066
+ * returns `undefined`.
1032
1067
  */
1033
1068
  function resolveModulePath(specifier: string, fromFile: string): string {
1034
1069
  if (specifier.startsWith(".") || isAbsolute(specifier)) {
1035
1070
  const base = specifier.startsWith(".") ? resolvePath(dirname(fromFile), specifier) : specifier;
1036
- const candidates = [
1037
- base,
1038
- `${base}.ts`,
1039
- `${base}.tsx`,
1040
- `${base}.js`,
1041
- `${base}.mjs`,
1042
- join(base, "index.ts"),
1043
- join(base, "index.js"),
1044
- ];
1045
- for (const candidate of candidates) {
1046
- if (existsSync(candidate) && statSync(candidate).isFile()) return candidate;
1047
- }
1048
1071
  // Nothing found on disk under any probed extension — hand back the bare
1049
1072
  // base and let `import()` fail with its own, more specific error.
1050
- return base;
1073
+ return probeModuleFile(base) ?? base;
1051
1074
  }
1052
1075
 
1076
+ const mapped = pathsMappedTarget(specifier, fromFile);
1077
+ if (mapped !== undefined) return mapped;
1053
1078
  const fast = fastResolveBareSpecifier(specifier, fromFile);
1054
1079
  if (fast !== undefined) return fast;
1055
1080
  return createRequire(fromFile).resolve(specifier);
@@ -1150,6 +1175,22 @@ function activeLexiconPackage(specifier: string, lexiconPackages: ReadonlySet<st
1150
1175
  return lexiconPackages.has(specifier) ? specifier : undefined;
1151
1176
  }
1152
1177
 
1178
+ /**
1179
+ * chant#3090 — {@link activeLexiconPackage} for a specifier imported by
1180
+ * `fromFile`: `undefined` as well when a tsconfig `paths` entry maps the
1181
+ * specifier ({@link pathsMappedTarget}), because the import then names a
1182
+ * project file, not the package the build loaded. Only a specifier whose text
1183
+ * already matches pays the tsconfig lookup.
1184
+ */
1185
+ function activeLexiconPackageImport(
1186
+ specifier: string,
1187
+ fromFile: string,
1188
+ lexiconPackages: ReadonlySet<string>,
1189
+ ): string | undefined {
1190
+ if (activeLexiconPackage(specifier, lexiconPackages) === undefined) return undefined;
1191
+ return pathsMappedTarget(specifier, fromFile) === undefined ? specifier : undefined;
1192
+ }
1193
+
1153
1194
  /**
1154
1195
  * chant#2577 — the path-declared counterpart of {@link activeLexiconPackage}:
1155
1196
  * the resolved module path when a relative or absolute `specifier` reaches
@@ -1163,7 +1204,7 @@ function activeLexiconModule(
1163
1204
  lexiconModules: ReadonlySet<string>,
1164
1205
  resolvePathCache: Map<string, string>,
1165
1206
  ): string | undefined {
1166
- if (lexiconModules.size === 0 || !isProjectFileSpecifier(specifier)) return undefined;
1207
+ if (lexiconModules.size === 0 || !isProjectImport(specifier, fromFile)) return undefined;
1167
1208
  let modulePath: string;
1168
1209
  try {
1169
1210
  modulePath = resolveModulePathMemoized(specifier, fromFile, resolvePathCache);
@@ -1248,7 +1289,7 @@ async function resolveActiveLexiconExport(
1248
1289
  session: FoldSession,
1249
1290
  ): Promise<{ value: unknown } | undefined> {
1250
1291
  let modulePath: string | undefined;
1251
- if (activeLexiconPackage(binding.specifier, session.lexiconPackages)) {
1292
+ if (activeLexiconPackageImport(binding.specifier, fromFile, session.lexiconPackages)) {
1252
1293
  try {
1253
1294
  modulePath = resolveModulePathMemoized(binding.specifier, fromFile, session.resolvePathCache);
1254
1295
  } catch {
@@ -1276,6 +1317,27 @@ async function resolveActiveLexiconExport(
1276
1317
  return { value };
1277
1318
  }
1278
1319
 
1320
+ /**
1321
+ * chant#3247 — a plain data export of a lexicon package's own module, read off
1322
+ * the real module (see the package-root branch of {@link buildExternals}).
1323
+ * `undefined` for a missing export, a callable one, or a module that does not
1324
+ * import.
1325
+ */
1326
+ async function packageDataExport(
1327
+ modulePath: string,
1328
+ name: string,
1329
+ session: FoldSession,
1330
+ ): Promise<{ value: unknown } | undefined> {
1331
+ let mod: Record<string, unknown>;
1332
+ try {
1333
+ mod = await importModuleMemoized(modulePath, session.importCache);
1334
+ } catch {
1335
+ return undefined;
1336
+ }
1337
+ if (!(name in mod) || typeof mod[name] === "function") return undefined;
1338
+ return { value: mod[name] };
1339
+ }
1340
+
1279
1341
  // ─────────────────────────────────────────────────────────────────────────
1280
1342
  // Resolution: given the scan + import map, compute the REAL runtime value
1281
1343
  // (Declarable | CompositeInstance) each foldable export would have had if
@@ -1394,6 +1456,14 @@ interface ResolveCtx {
1394
1456
  * innermost factory rather than the enclosing one.
1395
1457
  */
1396
1458
  compositeParams?: CompositeParamRecorder;
1459
+ /**
1460
+ * chant#3247 — set only inside the body of a composite an active lexicon
1461
+ * PACKAGE publishes: that package's directory (see
1462
+ * {@link FactoryModuleScope.packageRoot}). It is also in this context's
1463
+ * {@link lexiconRoots}, which is what trusts the package's relative imports;
1464
+ * this field is what confines a nested factory lookup to the package.
1465
+ */
1466
+ packageRoot?: string;
1397
1467
  }
1398
1468
 
1399
1469
  /** chant #2161 — what {@link stampCompositeOrigins} needs at one `new Type(...)` inside a factory body. */
@@ -1677,7 +1747,10 @@ async function invokeResolvedCallee(
1677
1747
  // A package factory still runs here, which is what `F-Host-Admission`
1678
1748
  // governs. A project-file callee that a `Composite` registered is
1679
1749
  // INTERPRETED at step 4 and never reaches this line; only one that
1680
- // interpretation declined does, and for that the verdict is `run`.
1750
+ // interpretation declined does, and for that the verdict is `run`. Since
1751
+ // chant#3247 a package's registered composite is interpreted at step 4 too,
1752
+ // and reaches this line only when interpretation declined; it is a
1753
+ // package's code, so it is still invoked.
1681
1754
  // "Project file" is the specifier's ORIGIN, not its syntax.
1682
1755
  // {@link isProjectFileSpecifier} is true of any absolute path, and chant's
1683
1756
  // own modules are imported by absolute path throughout the tests, so the
@@ -1686,7 +1759,7 @@ async function invokeResolvedCallee(
1686
1759
  // means: an active lexicon package or chant-core's own tree is a package's
1687
1760
  // code, and everything else reached by a relative or absolute specifier is
1688
1761
  // the project's.
1689
- if (!ctx.executing && isProjectFileSpecifier(binding.specifier) && !isTrustedExecutableBinding(binding, ctx)) {
1762
+ if (!ctx.executing && isProjectImport(binding.specifier, ctx.file) && !isTrustedExecutableBinding(binding, ctx)) {
1690
1763
  throw cheapError(
1691
1764
  `"${calleeName}" is imported from "${binding.specifier}", which is a project file; ` +
1692
1765
  "the default mode does not invoke project-owned code (`executing` would run it and fold what it returns)",
@@ -1730,7 +1803,10 @@ async function invokeResolvedCallee(
1730
1803
  }
1731
1804
 
1732
1805
  executionCounts.factoryInvocations += 1;
1733
- if (isProjectFileSpecifier(binding.specifier)) executionCounts.projectFactoryInvocations += 1;
1806
+ // chant#3247 — a relative import inside a lexicon package's own composite is
1807
+ // the package's code, not the project's.
1808
+ const packageInternal = ctx.packageRoot !== undefined && isUnderDirectory(modulePath, ctx.packageRoot);
1809
+ if (isProjectImport(binding.specifier, ctx.file) && !packageInternal) executionCounts.projectFactoryInvocations += 1;
1734
1810
 
1735
1811
  return (Fn as (...fnArgs: unknown[]) => unknown)(...args);
1736
1812
  }
@@ -1767,11 +1843,12 @@ async function invokeImportedCallee(
1767
1843
  * the resolved argument values — which `fold()`'s envelope already folded
1768
1844
  * and {@link reviveFoldedValue} has already revived by the time this runs.
1769
1845
  *
1770
- * Same two arms, same order, as the top-level path: interpret a project-file
1771
- * registered `Composite` (chant #1023) when its body qualifies, otherwise
1772
- * import and invoke for real — which is the arm every lexicon-package
1773
- * composite (`Checkout`, `SetupNode`, …) always takes, exactly as it does
1774
- * today for a top-level `export const x = Checkout({...})`. An unbound
1846
+ * Same two arms, same order, as the top-level path: interpret a registered
1847
+ * `Composite` (chant #1023; a lexicon package's own since chant#3247) when its
1848
+ * body qualifies, otherwise import and invoke for real — the arm a
1849
+ * lexicon-package composite whose body leaves the subset (`Checkout`,
1850
+ * `SetupNode`, …) takes, exactly as it does for a top-level
1851
+ * `export const x = Checkout({...})`. An unbound
1775
1852
  * callee name throws the same "function call as a value" message `fold()`
1776
1853
  * itself would have, for a caller with no `FoldedCompositeStepCall` special
1777
1854
  * case to fall into.
@@ -1825,6 +1902,7 @@ async function resolveCallArguments(
1825
1902
  const args: unknown[] = [];
1826
1903
  for (const argNode of node.arguments) {
1827
1904
  const live = await resolveLiveValue(argNode, ctx);
1905
+ if (live === undefined) await resolveSameFileCallReferences(argNode, ctx);
1828
1906
  // chant #1039 — a folded (non-live) argument may itself contain a
1829
1907
  // registered intrinsic tagged template; revive it into the real value
1830
1908
  // before the composite factory actually runs on it (see the "Intrinsic
@@ -1838,6 +1916,69 @@ async function resolveCallArguments(
1838
1916
  return args;
1839
1917
  }
1840
1918
 
1919
+ /**
1920
+ * chant#3325 — a composite call's props can name a same-file `const` bound to
1921
+ * another call (`left: notes.table`, with `export const notes =
1922
+ * TenantTable({...})` earlier in the file). `fold()` cannot answer that name:
1923
+ * it is synchronous, and the value is a composite instance only the live spine
1924
+ * builds. So before the argument folds, each such name it mentions is resolved
1925
+ * through {@link resolveMemoized}, the same promise the declarator and every
1926
+ * other reference to it share, and the instance goes into `externals`, where
1927
+ * `fold()`'s identifier branch reads it as it reads a pre-built `new` or tag
1928
+ * const (#1169, #3196).
1929
+ *
1930
+ * Only a call `fold()` has no case for qualifies: an imported callee that is
1931
+ * neither a registered authoring helper nor a registered intrinsic. For
1932
+ * anything else the name is left alone and folds exactly as before. A
1933
+ * resolution that fails, or gives anything but a Declarable or a composite
1934
+ * instance, is left alone too, so the reference rejects with the message it
1935
+ * always had. Inside a factory body `locals` is empty, so nothing here applies.
1936
+ */
1937
+ async function resolveSameFileCallReferences(node: ts.Node, ctx: ResolveCtx): Promise<void> {
1938
+ const names = new Set<string>();
1939
+ const visit = (n: ts.Node): void => {
1940
+ if (ts.isIdentifier(n)) {
1941
+ if (isUnfoldableSameFileCall(n.text, ctx)) names.add(n.text);
1942
+ return;
1943
+ }
1944
+ if (ts.isFunctionLike(n) || ts.isTypeNode(n)) return;
1945
+ if (ts.isPropertyAccessExpression(n)) return visit(n.expression);
1946
+ if (ts.isPropertyAssignment(n)) {
1947
+ if (ts.isComputedPropertyName(n.name)) visit(n.name);
1948
+ return visit(n.initializer);
1949
+ }
1950
+ ts.forEachChild(n, visit);
1951
+ };
1952
+ visit(node);
1953
+ for (const name of names) {
1954
+ let resolved: LiveResolution;
1955
+ try {
1956
+ resolved = await resolveMemoized(ctx.locals.get(name)!.source, ctx);
1957
+ } catch {
1958
+ continue;
1959
+ }
1960
+ if (resolved && (isDeclarable(resolved.value) || isCompositeInstance(resolved.value))) {
1961
+ ctx.externals.set(name, resolved.value);
1962
+ }
1963
+ }
1964
+ }
1965
+
1966
+ /** A same-file `const name = Callee(...)` that `fold()` would refuse as a function call used as a value. */
1967
+ function isUnfoldableSameFileCall(name: string, ctx: ResolveCtx): boolean {
1968
+ if (ctx.externals.has(name)) return false;
1969
+ const binding = ctx.locals.get(name);
1970
+ if (!binding || binding.propKey !== undefined || ctx.consts.get(name) !== binding.source) return false;
1971
+ const source = binding.source;
1972
+ if (!ts.isCallExpression(source) || !ts.isIdentifier(source.expression)) return false;
1973
+ const callee = source.expression.text;
1974
+ return (
1975
+ ctx.imports.has(callee) &&
1976
+ !isFoldableHelperName(callee) &&
1977
+ !ctx.intrinsics.some((i) => i.name === callee) &&
1978
+ !isFoldableFunction(ctx.externals.get(callee))
1979
+ );
1980
+ }
1981
+
1841
1982
  // ─────────────────────────────────────────────────────────────────────────
1842
1983
  // Composite factory interpretation (chant #1023, epic #1019 Phase 5).
1843
1984
  //
@@ -1855,15 +1996,30 @@ async function resolveCallArguments(
1855
1996
  // factory outside the subset costs a parse and nothing else:
1856
1997
  //
1857
1998
  // 1. **The callee is bound by an `import` from a PROJECT FILE** (a
1858
- // relative/absolute specifier). A lexicon-package composite is
1859
- // deliberately NOT interpreted, for two independent reasons: an installed
1860
- // lexicon ships compiled JS, so whether its factory bodies were
1861
- // interpretable would depend on whether the package happened to ship
1862
- // `.ts` — fold coverage must not vary with a dependency's build shape —
1863
- // and a lexicon package is on {@link isTrustedExecutableBinding}'s
1864
- // allowlist already, so calling it is exactly as safe under `--sandbox` as
1865
- // the `loadPlugins` import the CLI performed before discovery began. There
1866
- // is nothing to buy and a build-shape dependency to lose.
1999
+ // relative/absolute specifier, or one a tsconfig `paths` entry maps), **or
2000
+ // from an ACTIVE LEXICON PACKAGE of this build whose module is TypeScript
2001
+ // source** (chant#3247). The package case is matched by text first — the
2002
+ // specifier, or its package root for a subpath, against
2003
+ // {@link FoldSession.lexiconPackages}, exactly as
2004
+ // {@link isTrustedExecutableBinding}'s arm 1 matches it — and only then
2005
+ // resolved; a module that resolves to compiled JavaScript or a declaration
2006
+ // file is not interpreted, so a package that ships only `dist` keeps
2007
+ // invoking. Every chant lexicon routes its exports at `./src/*.ts`
2008
+ // (check-lexicon tier 1), so in practice the source is always there.
2009
+ // Interpreting buys provenance: the run path cannot say which parameter
2010
+ // fed a field, and interpretation can (#2161, #3246).
2011
+ //
2012
+ // The trust boundary does not move. A package's composite is interpreted
2013
+ // with that PACKAGE's directory ({@link FactoryModuleScope.packageRoot})
2014
+ // as a trusted lexicon root for the length of its body and nothing else:
2015
+ // the package's own relative imports are trusted under `--sandbox` the way
2016
+ // a subpath of the same package already is, and they are never folded as
2017
+ // project files. The package was imported and run by `loadPlugins` before
2018
+ // discovery began, and every module of it is reachable as a subpath, so
2019
+ // reading its source adds no trust. What it may NOT do is reach outside
2020
+ // that directory: a relative import that leaves the package root is not
2021
+ // followed. A lexicon this build did not load stays as out of scope as
2022
+ // any other bare specifier.
1867
2023
  // 2. **The defining module declares that export as `Composite(<fn>, "<name>")`**
1868
2024
  // — a top-level `export const <name> = Composite(...)`, where `Composite`
1869
2025
  // is bound in THAT module to an import of chant's own
@@ -1871,8 +2027,12 @@ async function resolveCallArguments(
1871
2027
  // asks of an authoring helper, and the same answer). This is #1023's
1872
2028
  // "recognize the callee is a registered Composite": a bare call to
1873
2029
  // anything else — a project helper function, a `withDefaults(...)`
1874
- // wrapper, a re-exported binding — is not recognized and is not
1875
- // interpreted.
2030
+ // wrapper, an `import`-then-`export` alias — is not recognized and is not
2031
+ // interpreted. The defining module may sit behind BARREL re-exports
2032
+ // (chant#3247): `export { X } from "./x"`, `export { Y as X } from "./x"`
2033
+ // and `export * from "./x"` are followed, relative specifiers only, at most
2034
+ // {@link MAX_REEXPORT_HOPS} deep, and never out of a package's root. A
2035
+ // module that declares the name itself is answered by that declaration.
1876
2036
  // 3. **`<fn>` is an arrow/function expression taking at most one parameter**,
1877
2037
  // bound as a plain identifier or a simple object binding pattern (no
1878
2038
  // default, rest, or nested pattern).
@@ -1986,6 +2146,15 @@ class InterpretationDepthError extends FoldError {}
1986
2146
  interface FactoryModuleScope {
1987
2147
  file: string;
1988
2148
  sourceFile: ts.SourceFile;
2149
+ /**
2150
+ * chant#3247 — the directory of the active lexicon PACKAGE this module was
2151
+ * reached in, when it was reached through one (see rule 1 of the contract).
2152
+ * Interpreting a factory from it trusts that directory as a lexicon root —
2153
+ * its files are not folded as project files and are on the `--sandbox`
2154
+ * allowlist — and confines every further hop to it. `undefined` for a
2155
+ * project file, whose imports are decided exactly as before.
2156
+ */
2157
+ packageRoot?: string;
1989
2158
  /**
1990
2159
  * The module's top-level `const`s, MINUS every one that resolves to a
1991
2160
  * `new Type(...)` resource.
@@ -2046,15 +2215,25 @@ interface InterpretableFactory {
2046
2215
  * module's own imports are not resolved yet — see
2047
2216
  * {@link FactoryModuleScope.resolved}.
2048
2217
  */
2049
- function readFactoryModule(modulePath: string, session: FoldSession): Promise<FactoryModuleScope | undefined> {
2050
- const cached = session.factoryModules.get(modulePath);
2218
+ function readFactoryModule(
2219
+ modulePath: string,
2220
+ packageRoot: string | undefined,
2221
+ session: FoldSession,
2222
+ ): Promise<FactoryModuleScope | undefined> {
2223
+ // chant#3247 — keyed by the package root as well: the same file read as a
2224
+ // project file and as package code resolves its imports differently.
2225
+ const key = packageRoot === undefined ? modulePath : `${packageRoot}\0${modulePath}`;
2226
+ const cached = session.factoryModules.get(key);
2051
2227
  if (cached) return cached;
2052
- const promise = readFactoryModuleCore(modulePath);
2053
- session.factoryModules.set(modulePath, promise);
2228
+ const promise = readFactoryModuleCore(modulePath, packageRoot);
2229
+ session.factoryModules.set(key, promise);
2054
2230
  return promise;
2055
2231
  }
2056
2232
 
2057
- async function readFactoryModuleCore(modulePath: string): Promise<FactoryModuleScope | undefined> {
2233
+ async function readFactoryModuleCore(
2234
+ modulePath: string,
2235
+ packageRoot: string | undefined,
2236
+ ): Promise<FactoryModuleScope | undefined> {
2058
2237
  let sourceFile: ts.SourceFile;
2059
2238
  try {
2060
2239
  const source = await readFile(modulePath, "utf-8");
@@ -2074,6 +2253,7 @@ async function readFactoryModuleCore(modulePath: string): Promise<FactoryModuleS
2074
2253
  return {
2075
2254
  file: modulePath,
2076
2255
  sourceFile,
2256
+ packageRoot,
2077
2257
  consts,
2078
2258
  imports: collected.named,
2079
2259
  namespaceImports: collected.namespaces,
@@ -2097,7 +2277,8 @@ function factoryModuleScopeResolved(
2097
2277
  return Promise.resolve({ externals: new Map(), failures: new Map() });
2098
2278
  }
2099
2279
  session.stack.push(scope.file);
2100
- scope.resolved = buildExternals(scope.file, scope.imports, scope.namespaceImports, session).finally(() => {
2280
+ const roots = scope.packageRoot === undefined ? [] : [scope.packageRoot];
2281
+ scope.resolved = buildExternals(scope.file, scope.imports, scope.namespaceImports, session, roots).finally(() => {
2101
2282
  const idx = session.stack.lastIndexOf(scope.file);
2102
2283
  if (idx !== -1) session.stack.splice(idx, 1);
2103
2284
  });
@@ -2141,7 +2322,7 @@ function findCompositeDefinition(
2141
2322
  // lexicon exports a `Composite`, so nothing about a chant project moves.
2142
2323
  const chantOwned = isChantOwnedHelperBinding(compositeBinding, { ...ctx, file: scope.file });
2143
2324
  const hostOwned =
2144
- activeLexiconPackage(compositeBinding.specifier, ctx.lexiconPackages) !== undefined ||
2325
+ activeLexiconPackageImport(compositeBinding.specifier, scope.file, ctx.lexiconPackages) !== undefined ||
2145
2326
  activeLexiconModule(compositeBinding.specifier, scope.file, ctx.lexiconModules, ctx.resolvePathCache) !==
2146
2327
  undefined;
2147
2328
  if (!chantOwned && !hostOwned) return undefined;
@@ -2158,6 +2339,218 @@ function findCompositeDefinition(
2158
2339
  return undefined;
2159
2340
  }
2160
2341
 
2342
+ /**
2343
+ * chant#3247 — how many barrel re-exports {@link findFactoryDefinition}
2344
+ * follows from the imported module to the one that defines the composite. A
2345
+ * lexicon's barrel reaches its composites in two (`src/index.ts` ->
2346
+ * `src/composites/index.ts` -> the file), a dialect subpath in three; past
2347
+ * this the lookup declines, and the composite is invoked as before.
2348
+ */
2349
+ const MAX_REEXPORT_HOPS = 8;
2350
+
2351
+ /** Where an import of a composite factory leads: the module, and the lexicon package it lies in, if any. */
2352
+ interface FactoryOrigin {
2353
+ modulePath: string;
2354
+ /** See {@link FactoryModuleScope.packageRoot}. */
2355
+ packageRoot?: string;
2356
+ }
2357
+
2358
+ /** A TypeScript source file, not compiled JavaScript and not a declaration file. */
2359
+ function isTypeScriptSource(path: string): boolean {
2360
+ return /\.[cm]?tsx?$/.test(path) && !/\.d\.[cm]?ts$/.test(path);
2361
+ }
2362
+
2363
+ /** Process-wide memo for {@link lexiconPackageDirectory}, keyed by package name and directory. */
2364
+ const lexiconPackageDirectoryCache = new Map<string, string | undefined>();
2365
+
2366
+ /**
2367
+ * chant#3247 — the directory of package `packageName` that holds the resolved
2368
+ * `modulePath`: the nearest ancestor whose `package.json` names that package.
2369
+ * `undefined` when none does before a `node_modules` directory or the
2370
+ * filesystem root, which declines interpretation. Reads `package.json` files
2371
+ * only; nothing is resolved or imported.
2372
+ */
2373
+ function lexiconPackageDirectory(modulePath: string, packageName: string): string | undefined {
2374
+ const start = dirname(modulePath);
2375
+ const key = `${packageName}\0${start}`;
2376
+ if (lexiconPackageDirectoryCache.has(key)) return lexiconPackageDirectoryCache.get(key);
2377
+ let found: string | undefined;
2378
+ for (let dir = start; basename(dir) !== "node_modules"; ) {
2379
+ const pkgJson = join(dir, "package.json");
2380
+ if (existsSync(pkgJson)) {
2381
+ try {
2382
+ if ((JSON.parse(readFileSync(pkgJson, "utf-8")) as { name?: unknown }).name === packageName) {
2383
+ found = dir;
2384
+ break;
2385
+ }
2386
+ } catch {
2387
+ // An unreadable package.json is not the package's; keep walking.
2388
+ }
2389
+ }
2390
+ const parent = dirname(dir);
2391
+ if (parent === dir) break;
2392
+ dir = parent;
2393
+ }
2394
+ lexiconPackageDirectoryCache.set(key, found);
2395
+ return found;
2396
+ }
2397
+
2398
+ /**
2399
+ * Rule 1 of the contract: where an import of a composite leads, or `undefined`
2400
+ * when the import is not one interpretation follows. No module is imported.
2401
+ *
2402
+ * - A project import (relative, absolute, or `paths`-mapped) resolves to its
2403
+ * file. Inside a package composite's body ({@link ResolveCtx.packageRoot})
2404
+ * it must stay inside that package.
2405
+ * - A bare specifier naming an active lexicon package, root or subpath, by
2406
+ * text, resolves through `node_modules` to a TypeScript source file inside
2407
+ * that package.
2408
+ * - Anything else is not followed.
2409
+ */
2410
+ function factoryOrigin(specifier: string, fromFile: string, ctx: ResolveCtx): FactoryOrigin | undefined {
2411
+ if (isProjectImport(specifier, fromFile)) {
2412
+ let modulePath: string;
2413
+ try {
2414
+ modulePath = resolveModulePathMemoized(specifier, fromFile, ctx.resolvePathCache);
2415
+ } catch {
2416
+ return undefined;
2417
+ }
2418
+ if (ctx.packageRoot === undefined) return { modulePath };
2419
+ return isUnderDirectory(modulePath, ctx.packageRoot) ? { modulePath, packageRoot: ctx.packageRoot } : undefined;
2420
+ }
2421
+ // Text first, as {@link isTrustedExecutableBinding}'s arm 1 does: no bare
2422
+ // specifier outside the build's own lexicons is ever resolved here.
2423
+ const packageName =
2424
+ activeLexiconPackage(specifier, ctx.lexiconPackages) ??
2425
+ activeLexiconPackage(bareSpecifierPackageRoot(specifier) ?? "", ctx.lexiconPackages);
2426
+ if (packageName === undefined) return undefined;
2427
+ let modulePath: string;
2428
+ try {
2429
+ modulePath = resolveModulePathMemoized(specifier, fromFile, ctx.resolvePathCache);
2430
+ } catch {
2431
+ return undefined;
2432
+ }
2433
+ if (!isTypeScriptSource(modulePath)) return undefined;
2434
+ const packageRoot = lexiconPackageDirectory(modulePath, packageName);
2435
+ return packageRoot === undefined ? undefined : { modulePath, packageRoot };
2436
+ }
2437
+
2438
+ /** One re-export {@link reExportsOf} found: where it leads, under which name, and whether it is an `export *`. */
2439
+ interface ReExport {
2440
+ specifier: string;
2441
+ imported: string;
2442
+ star: boolean;
2443
+ }
2444
+
2445
+ /**
2446
+ * chant#3247 — the re-exports of `exportName` that `sourceFile` forwards, in the order they are worth
2447
+ * trying. Empty when the module declares the name itself (its own
2448
+ * declaration is the answer, interpretable or not) or forwards it from nowhere
2449
+ * this follows. An explicit `export { ... } from` wins over `export *`, as it
2450
+ * does in the language; a type-only re-export, an `export * as ns`, and a bare
2451
+ * `export { X }` of an imported binding are not followed.
2452
+ */
2453
+ function reExportsOf(sourceFile: ts.SourceFile, exportName: string): ReExport[] {
2454
+ const stars: ReExport[] = [];
2455
+ for (const statement of sourceFile.statements) {
2456
+ if (ts.isExportDeclaration(statement)) {
2457
+ if (statement.isTypeOnly) continue;
2458
+ const spec =
2459
+ statement.moduleSpecifier && ts.isStringLiteral(statement.moduleSpecifier)
2460
+ ? statement.moduleSpecifier.text
2461
+ : undefined;
2462
+ const clause = statement.exportClause;
2463
+ if (clause === undefined) {
2464
+ if (spec !== undefined) stars.push({ specifier: spec, imported: exportName, star: true });
2465
+ continue;
2466
+ }
2467
+ if (!ts.isNamedExports(clause)) {
2468
+ if (clause.name.text === exportName) return [];
2469
+ continue;
2470
+ }
2471
+ for (const el of clause.elements) {
2472
+ if (el.name.text !== exportName) continue;
2473
+ if (spec === undefined || el.isTypeOnly) return [];
2474
+ return [{ specifier: spec, imported: (el.propertyName ?? el.name).text, star: false }];
2475
+ }
2476
+ continue;
2477
+ }
2478
+ if (!hasExportModifier(statement as { modifiers?: ts.NodeArray<ts.ModifierLike> })) continue;
2479
+ if (ts.isVariableStatement(statement)) {
2480
+ for (const decl of statement.declarationList.declarations) {
2481
+ if (ts.isIdentifier(decl.name) && decl.name.text === exportName) return [];
2482
+ }
2483
+ } else if (
2484
+ (ts.isFunctionDeclaration(statement) || ts.isClassDeclaration(statement) || ts.isEnumDeclaration(statement)) &&
2485
+ statement.name?.text === exportName
2486
+ ) {
2487
+ return [];
2488
+ }
2489
+ }
2490
+ return stars;
2491
+ }
2492
+
2493
+ /** A text pre-check for an `export *` target: false only when the file cannot export `name`. */
2494
+ async function mayProvideExport(file: string, name: string): Promise<boolean> {
2495
+ try {
2496
+ const text = await readFile(file, "utf-8");
2497
+ return text.includes(name) || /export\s*\*\s*from/.test(text);
2498
+ } catch {
2499
+ return false;
2500
+ }
2501
+ }
2502
+
2503
+ /**
2504
+ * Rule 2, through barrels: find `exportName`'s `Composite(...)` declaration
2505
+ * starting at `origin` and following {@link reExportsOf} — relative
2506
+ * specifiers only, confined to `origin.packageRoot` when there is one, cycle
2507
+ * safe, and at most {@link MAX_REEXPORT_HOPS} deep. Reads and parses; imports
2508
+ * nothing.
2509
+ */
2510
+ async function findFactoryDefinition(
2511
+ origin: FactoryOrigin,
2512
+ exportName: string,
2513
+ ctx: ResolveCtx,
2514
+ seen: Set<string> = new Set(),
2515
+ hops = 0,
2516
+ ): Promise<
2517
+ | { scope: FactoryModuleScope; definition: NonNullable<ReturnType<typeof findCompositeDefinition>> }
2518
+ | undefined
2519
+ > {
2520
+ seen.add(origin.modulePath);
2521
+ const scope = await readFactoryModule(origin.modulePath, origin.packageRoot, ctx.session);
2522
+ if (!scope) return undefined;
2523
+ const definition = findCompositeDefinition(scope, exportName, ctx);
2524
+ if (definition) return { scope, definition };
2525
+ if (hops >= MAX_REEXPORT_HOPS) return undefined;
2526
+
2527
+ for (const { specifier, imported, star } of reExportsOf(scope.sourceFile, exportName)) {
2528
+ if (!isProjectFileSpecifier(specifier)) continue;
2529
+ let target: string;
2530
+ try {
2531
+ target = resolveModulePathMemoized(specifier, scope.file, ctx.resolvePathCache);
2532
+ } catch {
2533
+ continue;
2534
+ }
2535
+ if (seen.has(target) || !isTypeScriptSource(target)) continue;
2536
+ if (origin.packageRoot !== undefined && !isUnderDirectory(target, origin.packageRoot)) continue;
2537
+ // An `export *` target that neither names the export nor forwards a star
2538
+ // of its own cannot provide it, so it is not parsed. That keeps a
2539
+ // lexicon's generated barrel (megabytes, behind `export *` in most
2540
+ // lexicons) from being parsed for every call through the package root.
2541
+ if (star && !(await mayProvideExport(target, imported))) continue;
2542
+ const found = await findFactoryDefinition(
2543
+ { modulePath: target, packageRoot: origin.packageRoot },
2544
+ imported,
2545
+ ctx,
2546
+ seen,
2547
+ hops + 1,
2548
+ );
2549
+ if (found) return found;
2550
+ }
2551
+ return undefined;
2552
+ }
2553
+
2161
2554
  /**
2162
2555
  * Decide whether `binding`'s callee is an interpretable composite — rules 1-5
2163
2556
  * of the contract above, in that order, with no evaluation and no import.
@@ -2167,9 +2560,10 @@ async function resolveInterpretableFactory(
2167
2560
  binding: ImportBinding,
2168
2561
  ctx: ResolveCtx,
2169
2562
  ): Promise<InterpretableFactory | undefined> {
2170
- // Rule 1 — project files only. A text check; no resolution performed for a
2171
- // bare specifier, so this costs nothing for the (common) lexicon case.
2172
- if (!isProjectFileSpecifier(binding.specifier)) return undefined;
2563
+ // Rule 1 — a project file, or an active lexicon package's TypeScript source.
2564
+ // A bare specifier outside the build's lexicons is refused on its text.
2565
+ const origin = factoryOrigin(binding.specifier, ctx.file, ctx);
2566
+ if (!origin) return undefined;
2173
2567
  // Not `undefined`: that answer means "not interpretable" and would invoke
2174
2568
  // the factory while still reporting the file folded. Giving up is a
2175
2569
  // different fact and falls the file back to run, naming the bound (#2370).
@@ -2183,19 +2577,10 @@ async function resolveInterpretableFactory(
2183
2577
  );
2184
2578
  }
2185
2579
 
2186
- let modulePath: string;
2187
- try {
2188
- modulePath = resolveModulePathMemoized(binding.specifier, ctx.file, ctx.resolvePathCache);
2189
- } catch {
2190
- return undefined;
2191
- }
2192
-
2193
- const scope = await readFactoryModule(modulePath, ctx.session);
2194
- if (!scope) return undefined;
2195
-
2196
- // Rule 2.
2197
- const definition = findCompositeDefinition(scope, binding.imported, ctx);
2198
- if (!definition) return undefined;
2580
+ // Rule 2, following barrel re-exports to the defining module.
2581
+ const found = await findFactoryDefinition(origin, binding.imported, ctx);
2582
+ if (!found) return undefined;
2583
+ const { scope, definition } = found;
2199
2584
 
2200
2585
  // Rules 3-5.
2201
2586
  if (findFactorySubsetViolation(definition.fn) !== undefined) return undefined;
@@ -2471,6 +2856,27 @@ function stampCompositeOrigins(entity: unknown, node: ts.NewExpression, ctx: Res
2471
2856
  for (const [path, origin] of Object.entries(origins)) setPathProvenance(entity, path, origin);
2472
2857
  }
2473
2858
 
2859
+ /**
2860
+ * chant #3212 — the same record for an entity a registered tag built inside an
2861
+ * interpreted factory body (`` table`CREATE TABLE ${props.name} ...` ``).
2862
+ *
2863
+ * A tag's props come from parsing its text, so no object literal says which
2864
+ * field each `${...}` landed in. The tag says so itself through
2865
+ * `setInterpolationFields`; a tag that does not leaves the entity's fields
2866
+ * `unknown`, as before.
2867
+ */
2868
+ function stampTagOrigins(entity: unknown, node: ts.TaggedTemplateExpression, ctx: ResolveCtx): void {
2869
+ const recorder = ctx.compositeParams;
2870
+ if (!recorder || !isDeclarable(entity) || !isResourceDeclarable(entity)) return;
2871
+ const fields = getInterpolationFields(entity);
2872
+ if (!fields) return;
2873
+ const interpolations = ts.isTemplateExpression(node.template)
2874
+ ? node.template.templateSpans.map((span) => span.expression)
2875
+ : [];
2876
+ const origins = collectTagOrigins(interpolations, fields, entity.props, recorder.consts, recorder.scope, recorder.composite);
2877
+ for (const [path, origin] of Object.entries(origins)) setPathProvenance(entity, path, origin);
2878
+ }
2879
+
2474
2880
  /**
2475
2881
  * Interpret an admissible factory's body against `args`, and assemble the
2476
2882
  * result through chant's own {@link Composite}.
@@ -2516,7 +2922,10 @@ async function interpretCompositeFactory(
2516
2922
  resolvePathCache: ctx.resolvePathCache,
2517
2923
  lexiconPackages: ctx.lexiconPackages,
2518
2924
  lexiconModules: ctx.lexiconModules,
2519
- lexiconRoots: ctx.lexiconRoots,
2925
+ // chant#3247 — a package's composite trusts its own package directory for
2926
+ // the length of its body, and nothing outside it.
2927
+ lexiconRoots: scope.packageRoot === undefined ? ctx.lexiconRoots : [...ctx.lexiconRoots, scope.packageRoot],
2928
+ packageRoot: scope.packageRoot,
2520
2929
  sandbox: ctx.sandbox,
2521
2930
  executing: ctx.executing,
2522
2931
  session: ctx.session,
@@ -2635,6 +3044,12 @@ async function interpretExpression(node: ts.Expression, ctx: ResolveCtx): Promis
2635
3044
 
2636
3045
  if (ts.isNewExpression(node)) return interpretNewExpression(node, ctx);
2637
3046
 
3047
+ if (ts.isTaggedTemplateExpression(node)) {
3048
+ const value = (await resolveDeclaratorValue(node, ctx)).value;
3049
+ stampTagOrigins(value, node, ctx);
3050
+ return value;
3051
+ }
3052
+
2638
3053
  if (ts.isObjectLiteralExpression(node)) {
2639
3054
  const obj: Record<string, unknown> = {};
2640
3055
  for (const prop of node.properties) {
@@ -2862,6 +3277,16 @@ async function resolveSymbolicValue(text: string, ctx: ResolveCtx): Promise<unkn
2862
3277
  return value;
2863
3278
  }
2864
3279
 
3280
+ /**
3281
+ * True when {@link reviveFoldedValue} hands `value` back unchanged: a
3282
+ * primitive, or one of the live object kinds its passthrough keeps (an
3283
+ * `AttrRef`, a Declarable, a composite instance, an `Intrinsic`).
3284
+ */
3285
+ function revivesAsItself(value: unknown): boolean {
3286
+ if (value === null || typeof value !== "object") return true;
3287
+ return isAttrRefLike(value) || isDeclarable(value) || isCompositeInstance(value) || isIntrinsic(value);
3288
+ }
3289
+
2865
3290
  /**
2866
3291
  * Revive a folded value tree: replace any
2867
3292
  * `{__intrinsic}`/`{__helper}`/`{__symbol}` envelope with the real value it
@@ -2904,9 +3329,7 @@ async function reviveFoldedValue(value: FoldedValue, ctx: ResolveCtx, requireLiv
2904
3329
  // `Symbol.for("chant.intrinsic")` (../intrinsic.ts), a GLOBAL symbol, so it
2905
3330
  // holds across separately-loaded copies of chant-core the way a bare
2906
3331
  // `instanceof` would not.
2907
- if (isAttrRefLike(value) || isDeclarable(value) || isCompositeInstance(value) || isIntrinsic(value)) {
2908
- return value;
2909
- }
3332
+ if (revivesAsItself(value)) return value;
2910
3333
 
2911
3334
  if (Array.isArray(value)) {
2912
3335
  const revived: unknown[] = [];
@@ -2939,7 +3362,14 @@ async function reviveFoldedValue(value: FoldedValue, ctx: ResolveCtx, requireLiv
2939
3362
  return (Fn as (...fnArgs: unknown[]) => unknown)(...revived);
2940
3363
  }
2941
3364
  for (const v of intrinsic.values) revived.push(await reviveFoldedValue(v, ctx, true));
2942
- return (Fn as (...fnArgs: unknown[]) => unknown)(intrinsic.strings, ...revived);
3365
+ // chant #3196 — hand the tag what a real tagged call hands it: a frozen
3366
+ // strings array carrying a frozen, non-enumerable `raw`. A tag that reads
3367
+ // `strings.raw` (a SQL tag keeps backslashes as written) would otherwise
3368
+ // build different text folded than run.
3369
+ const strings = Object.freeze(
3370
+ Object.defineProperty([...intrinsic.strings], "raw", { value: Object.freeze([...intrinsic.raw]) }),
3371
+ );
3372
+ return (Fn as (...fnArgs: unknown[]) => unknown)(strings, ...revived);
2943
3373
  }
2944
3374
 
2945
3375
  if ("__helper" in value) {
@@ -3067,7 +3497,7 @@ async function reviveHelperCall(call: FoldedHelperCall, ctx: ResolveCtx): Promis
3067
3497
  */
3068
3498
  function isChantOwnedHelperBinding(binding: ImportBinding, ctx: ResolveCtx): boolean {
3069
3499
  if (isChantOwnedSpecifier(binding.specifier)) return true;
3070
- if (!isProjectFileSpecifier(binding.specifier)) return false;
3500
+ if (!isProjectImport(binding.specifier, ctx.file)) return false;
3071
3501
  let targetPath: string;
3072
3502
  try {
3073
3503
  targetPath = resolveModulePathMemoized(binding.specifier, ctx.file, ctx.resolvePathCache);
@@ -3123,17 +3553,25 @@ function isChantOwnedHelperBinding(binding: ImportBinding, ctx: ResolveCtx): boo
3123
3553
  * {@link FoldSession.lexiconPackages} takes for #1063.
3124
3554
  */
3125
3555
  function isTrustedExecutableBinding(binding: ImportBinding, ctx: ResolveCtx): boolean {
3126
- if (activeLexiconPackage(binding.specifier, ctx.lexiconPackages) !== undefined) return true;
3556
+ // chant#3090 — a specifier a tsconfig `paths` entry maps names a project
3557
+ // file whatever its text says, so neither text arm applies to it: it is
3558
+ // resolved and decided on the path below, like a relative specifier.
3559
+ const pathsMapped = pathsMappedTarget(binding.specifier, ctx.file) !== undefined;
3560
+ if (!pathsMapped && activeLexiconPackage(binding.specifier, ctx.lexiconPackages) !== undefined) return true;
3127
3561
  // chant #1995 — arm 1's subpath case: same allowlist, matched against the
3128
3562
  // specifier's package ROOT (text only, see bareSpecifierPackageRoot) rather
3129
3563
  // than the specifier itself, so `<activeLexiconPkg>/generated/index` is
3130
3564
  // trusted exactly like the bare package import already is.
3131
3565
  const subpathRoot = bareSpecifierPackageRoot(binding.specifier);
3132
- if (subpathRoot !== undefined && activeLexiconPackage(subpathRoot, ctx.lexiconPackages) !== undefined) return true;
3566
+ if (!pathsMapped && subpathRoot !== undefined && activeLexiconPackage(subpathRoot, ctx.lexiconPackages) !== undefined) {
3567
+ return true;
3568
+ }
3133
3569
  // Only a chant-shaped or project-relative specifier is worth resolving; any
3134
3570
  // other bare specifier is untrusted by definition, and resolving it to find
3135
3571
  // that out would cost the pathological cold `require.resolve` (chant#1020).
3136
- if (!isProjectFileSpecifier(binding.specifier) && !isChantOwnedSpecifier(binding.specifier)) return false;
3572
+ if (!pathsMapped && !isProjectFileSpecifier(binding.specifier) && !isChantOwnedSpecifier(binding.specifier)) {
3573
+ return false;
3574
+ }
3137
3575
  let targetPath: string;
3138
3576
  try {
3139
3577
  targetPath = resolveModulePathMemoized(binding.specifier, ctx.file, ctx.resolvePathCache);
@@ -3415,6 +3853,28 @@ function stampParamDependencies(entity: unknown, node: ts.NewExpression, ctx: Re
3415
3853
  async function preresolveResourceConsts(ctx: ResolveCtx): Promise<Map<ts.Expression, unknown>> {
3416
3854
  const built = new Map<ts.Expression, unknown>();
3417
3855
  for (const [name, initializer] of ctx.consts) {
3856
+ // chant #3196 — a same-file `const x = tag\`...\`` for a registered tag
3857
+ // is built once here too, so the export and every reference to `x` share
3858
+ // the one value running the module gives them. That matters when the tag
3859
+ // returns an entity (a SQL `table\`...\``): a second call would build a
3860
+ // second entity discovery never registers.
3861
+ if (
3862
+ ts.isTaggedTemplateExpression(initializer) &&
3863
+ ts.isIdentifier(initializer.tag) &&
3864
+ ctx.intrinsics.some((i) => i.name === (initializer.tag as ts.Identifier).text && intrinsicTagFolds(i))
3865
+ ) {
3866
+ try {
3867
+ const value = await reviveFoldedValue(fold(initializer, ctx.consts, ctx.intrinsics, ctx.externals), ctx, false);
3868
+ built.set(initializer, value);
3869
+ // Only a value `reviveFoldedValue` passes through unchanged is safe to
3870
+ // hand a reference. Any other object would be walked into a plain
3871
+ // copy, so a reference to one keeps re-folding the initializer.
3872
+ if (revivesAsItself(value)) ctx.externals.set(name, value);
3873
+ } catch (err) {
3874
+ ctx.prebuildFailures?.set(name, describeFoldFailure(err, ctx));
3875
+ }
3876
+ continue;
3877
+ }
3418
3878
  if (!ts.isNewExpression(initializer) || !ts.isIdentifier(initializer.expression)) continue;
3419
3879
  try {
3420
3880
  const spec = foldResource(initializer, ctx.consts, ctx.intrinsics, ctx.externals);
@@ -3593,6 +4053,15 @@ function isProjectFileSpecifier(specifier: string): boolean {
3593
4053
  return specifier.startsWith(".") || isAbsolute(specifier);
3594
4054
  }
3595
4055
 
4056
+ /**
4057
+ * chant#3090 — {@link isProjectFileSpecifier} for an import in `fromFile`,
4058
+ * counting a bare specifier that a tsconfig `paths` entry maps to a file
4059
+ * ({@link pathsMappedTarget}) as the project file it names.
4060
+ */
4061
+ function isProjectImport(specifier: string, fromFile: string): boolean {
4062
+ return isProjectFileSpecifier(specifier) || pathsMappedTarget(specifier, fromFile) !== undefined;
4063
+ }
4064
+
3596
4065
  /**
3597
4066
  * Eagerly resolve every project-file import binding of `file`'s to its real
3598
4067
  * cross-file value (see this section's own doc above). A binding that isn't
@@ -3609,7 +4078,16 @@ async function buildExternals(
3609
4078
  imports: Map<string, ImportBinding>,
3610
4079
  namespaceImports: Map<string, NamespaceImportBinding>,
3611
4080
  session: FoldSession,
4081
+ /**
4082
+ * chant#3247 — trusted lexicon directories beyond the session's own
4083
+ * {@link FoldSession.lexiconRoots}: the package root of a lexicon-package
4084
+ * composite whose defining module this is. Files under one are treated as
4085
+ * a path lexicon's are, never folded as project files.
4086
+ */
4087
+ extraLexiconRoots: readonly string[] = [],
3612
4088
  ): Promise<{ externals: Map<string, unknown>; failures: Map<string, string>; liveSources: Set<string> }> {
4089
+ const lexiconRoots =
4090
+ extraLexiconRoots.length === 0 ? session.lexiconRoots : [...session.lexiconRoots, ...extraLexiconRoots];
3613
4091
  const externals = new Map<string, unknown>();
3614
4092
  const failures = new Map<string, string>();
3615
4093
  // chant #1044 — see `FoldFileResult.liveSources`.
@@ -3652,7 +4130,7 @@ async function buildExternals(
3652
4130
  externals.set(localName, session.buildParams);
3653
4131
  continue;
3654
4132
  }
3655
- if (isProjectFileSpecifier(binding.specifier)) {
4133
+ if (isProjectImport(binding.specifier, file)) {
3656
4134
  try {
3657
4135
  const targetPath = resolveModulePathMemoized(binding.specifier, file, session.resolvePathCache);
3658
4136
  if (targetPath === paramsModulePath()) {
@@ -3666,7 +4144,7 @@ async function buildExternals(
3666
4144
  }
3667
4145
  }
3668
4146
 
3669
- if (!isProjectFileSpecifier(binding.specifier)) {
4147
+ if (!isProjectImport(binding.specifier, file)) {
3670
4148
  // chant #1063 — a bare specifier naming one of THIS BUILD's active
3671
4149
  // lexicon packages resolves to that package's real export, so a plain
3672
4150
  // data export a lexicon publishes (`Azure`/`GCP`'s pseudo-parameter
@@ -3697,6 +4175,20 @@ async function buildExternals(
3697
4175
  // it resolves lazily, through the pre-existing `importModule`
3698
4176
  // mechanism, only once a constructor/composite-factory/intrinsic tag
3699
4177
  // actually consumes it.
4178
+ //
4179
+ // chant#3090 — except that a tsconfig `paths` entry which matches the
4180
+ // specifier but names no file is recorded, so the reason says which
4181
+ // import failed and why.
4182
+ const unmatched = pathsMatch(binding.specifier, file);
4183
+ if (unmatched) {
4184
+ failures.set(
4185
+ localName,
4186
+ locatedMessage(
4187
+ binding.specifierNode,
4188
+ `tsconfig paths entry "${unmatched.key}" matches "${binding.specifier}" but names no file`,
4189
+ ),
4190
+ );
4191
+ }
3700
4192
  continue;
3701
4193
  }
3702
4194
  let targetPath: string;
@@ -3718,7 +4210,20 @@ async function buildExternals(
3718
4210
  // subpath (see the bare-specifier branch above) its data exports are not
3719
4211
  // read as values. A constructor or composite from it resolves through the
3720
4212
  // import path, where `isTrustedExecutableBinding` admits it.
3721
- if (inPathLexiconRoot(targetPath, session.lexiconRoots)) continue;
4213
+ //
4214
+ // chant#3247 — a file of the lexicon PACKAGE whose composite is being
4215
+ // interpreted is that package's own module, which `loadPlugins` already
4216
+ // imported through the package's barrel. Its plain data exports (an
4217
+ // action table, a policy constant) are read off the real module, so the
4218
+ // value is the run path's own object, identity included. Callables stay
4219
+ // out, as {@link resolveActiveLexiconExport} keeps them out: a
4220
+ // constructor or composite resolves through the import path.
4221
+ if (inPathLexiconRoot(targetPath, extraLexiconRoots)) {
4222
+ const value = await packageDataExport(targetPath, binding.imported, session);
4223
+ if (value) externals.set(localName, value.value);
4224
+ continue;
4225
+ }
4226
+ if (inPathLexiconRoot(targetPath, lexiconRoots)) continue;
3722
4227
  const result = await foldFileMemoized(targetPath, session);
3723
4228
  if (!result.ok) {
3724
4229
  failures.set(localName, locatedMessage(binding.specifierNode, result.reason));
@@ -3737,7 +4242,7 @@ async function buildExternals(
3737
4242
  }
3738
4243
 
3739
4244
  for (const [localName, binding] of namespaceImports) {
3740
- if (!isProjectFileSpecifier(binding.specifier)) continue;
4245
+ if (!isProjectImport(binding.specifier, file)) continue;
3741
4246
  let targetPath: string;
3742
4247
  try {
3743
4248
  targetPath = resolveModulePathMemoized(binding.specifier, file, session.resolvePathCache);
@@ -3746,7 +4251,7 @@ async function buildExternals(
3746
4251
  }
3747
4252
  // chant#2577 — a namespace import of a lexicon is left alone, as the
3748
4253
  // bare-specifier check above leaves a package's alone.
3749
- if (session.lexiconModules.has(targetPath) || inPathLexiconRoot(targetPath, session.lexiconRoots)) continue;
4254
+ if (session.lexiconModules.has(targetPath) || inPathLexiconRoot(targetPath, lexiconRoots)) continue;
3750
4255
  const result = await foldFileMemoized(targetPath, session);
3751
4256
  if (!result.ok) {
3752
4257
  failures.set(localName, locatedMessage(binding.specifierNode, result.reason));
@@ -3799,7 +4304,9 @@ function hasObjectIdentity(value: unknown): boolean {
3799
4304
  * back to run, exactly as before #1020.
3800
4305
  */
3801
4306
  async function resolveDeclaratorValue(node: ts.Expression, ctx: ResolveCtx): Promise<{ value: unknown }> {
3802
- const live = await resolveLiveValue(node, ctx);
4307
+ // chant#3325 — memoized, so an exported `const x = Composite(...)` and a
4308
+ // reference to `x` elsewhere in the file hold the one instance.
4309
+ const live = await resolveMemoized(node, ctx);
3803
4310
  if (live !== undefined) return live;
3804
4311
  const folded = fold(node, ctx.consts, ctx.intrinsics, ctx.externals);
3805
4312
  return { value: await reviveFoldedValue(folded, ctx, false) };
@@ -3915,6 +4422,11 @@ async function tryFoldFileCore(file: string, session: FoldSession): Promise<Fold
3915
4422
  applyResolvedValue(decl.name, marker, entities, exportedValues);
3916
4423
  continue;
3917
4424
  }
4425
+ // chant #3196 — a registered tagged template the pre-pass built.
4426
+ if (prebuiltResources.has(decl.node)) {
4427
+ applyResolvedValue(decl.name, prebuiltResources.get(decl.node), entities, exportedValues);
4428
+ continue;
4429
+ }
3918
4430
  let value: unknown;
3919
4431
  try {
3920
4432
  value = (await resolveDeclaratorValue(decl.node, ctx)).value;
@@ -4137,7 +4649,7 @@ export async function buildProjectImportEdges(files: readonly string[]): Promise
4137
4649
  for (const file of files) {
4138
4650
  const targets = new Set<string>();
4139
4651
  const addTarget = (specifier: string): void => {
4140
- if (!isProjectFileSpecifier(specifier)) return; // package import, not a sibling source file
4652
+ if (!isProjectImport(specifier, file)) return; // package import, not a sibling source file
4141
4653
  let resolved: string;
4142
4654
  try {
4143
4655
  resolved = resolveModulePath(specifier, file);