@intentius/chant 0.80.0 → 0.81.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 (414) hide show
  1. package/dist/audit/discover.d.ts +8 -16
  2. package/dist/audit/discover.d.ts.map +1 -1
  3. package/dist/audit/report-html.d.ts +5 -0
  4. package/dist/audit/report-html.d.ts.map +1 -1
  5. package/dist/audit/report-model.d.ts +13 -0
  6. package/dist/audit/report-model.d.ts.map +1 -1
  7. package/dist/audit/terraform-state.d.ts +4 -5
  8. package/dist/audit/terraform-state.d.ts.map +1 -1
  9. package/dist/cli/commands/audit.d.ts +25 -9
  10. package/dist/cli/commands/audit.d.ts.map +1 -1
  11. package/dist/cli/commands/check-lexicon.d.ts +8 -0
  12. package/dist/cli/commands/check-lexicon.d.ts.map +1 -1
  13. package/dist/cli/commands/doctor.d.ts.map +1 -1
  14. package/dist/cli/commands/import-agents.d.ts.map +1 -1
  15. package/dist/cli/commands/init.d.ts.map +1 -1
  16. package/dist/cli/commands/lint.d.ts.map +1 -1
  17. package/dist/cli/commands/update.d.ts +6 -0
  18. package/dist/cli/commands/update.d.ts.map +1 -1
  19. package/dist/cli/commands/vendor.d.ts +5 -0
  20. package/dist/cli/commands/vendor.d.ts.map +1 -1
  21. package/dist/cli/handlers/build.d.ts.map +1 -1
  22. package/dist/cli/handlers/components.d.ts.map +1 -1
  23. package/dist/cli/handlers/dev.d.ts.map +1 -1
  24. package/dist/cli/handlers/fan-out.d.ts.map +1 -1
  25. package/dist/cli/handlers/init.d.ts.map +1 -1
  26. package/dist/cli/handlers/operator.d.ts +7 -0
  27. package/dist/cli/handlers/operator.d.ts.map +1 -1
  28. package/dist/cli/handlers/promote.d.ts +7 -4
  29. package/dist/cli/handlers/promote.d.ts.map +1 -1
  30. package/dist/cli/handlers/run-generate.d.ts.map +1 -1
  31. package/dist/cli/handlers/run.d.ts +4 -1
  32. package/dist/cli/handlers/run.d.ts.map +1 -1
  33. package/dist/cli/handlers/vendor.d.ts +8 -3
  34. package/dist/cli/handlers/vendor.d.ts.map +1 -1
  35. package/dist/cli/main.d.ts +12 -0
  36. package/dist/cli/main.d.ts.map +1 -1
  37. package/dist/cli/plugins.d.ts +12 -7
  38. package/dist/cli/plugins.d.ts.map +1 -1
  39. package/dist/cli/registry.d.ts +32 -0
  40. package/dist/cli/registry.d.ts.map +1 -1
  41. package/dist/codegen/fetch.d.ts +10 -0
  42. package/dist/codegen/fetch.d.ts.map +1 -1
  43. package/dist/components/cli-support.d.ts +3 -1
  44. package/dist/components/cli-support.d.ts.map +1 -1
  45. package/dist/components/discover.d.ts.map +1 -1
  46. package/dist/components/driver.d.ts +16 -2
  47. package/dist/components/driver.d.ts.map +1 -1
  48. package/dist/components/gate-plan.d.ts +40 -0
  49. package/dist/components/gate-plan.d.ts.map +1 -0
  50. package/dist/components/promote.d.ts +60 -7
  51. package/dist/components/promote.d.ts.map +1 -1
  52. package/dist/components/sandbox/driver.d.ts.map +1 -1
  53. package/dist/components/sandbox/run.d.ts.map +1 -1
  54. package/dist/components/verbs/build-archive.d.ts +22 -11
  55. package/dist/components/verbs/build-archive.d.ts.map +1 -1
  56. package/dist/components/verbs/run-agent.d.ts.map +1 -1
  57. package/dist/config-static.d.ts +59 -0
  58. package/dist/config-static.d.ts.map +1 -0
  59. package/dist/config.d.ts +32 -10
  60. package/dist/config.d.ts.map +1 -1
  61. package/dist/content-digest.d.ts +17 -0
  62. package/dist/content-digest.d.ts.map +1 -0
  63. package/dist/discovery/files.d.ts +10 -5
  64. package/dist/discovery/files.d.ts.map +1 -1
  65. package/dist/discovery/fold-import.d.ts +20 -4
  66. package/dist/discovery/fold-import.d.ts.map +1 -1
  67. package/dist/discovery/index.d.ts.map +1 -1
  68. package/dist/discovery/sandbox/driver.d.ts +20 -0
  69. package/dist/discovery/sandbox/driver.d.ts.map +1 -1
  70. package/dist/discovery/sandbox/fork.d.ts +20 -0
  71. package/dist/discovery/sandbox/fork.d.ts.map +1 -1
  72. package/dist/discovery/walk.d.ts +119 -0
  73. package/dist/discovery/walk.d.ts.map +1 -0
  74. package/dist/graph-ir.d.ts +4 -1
  75. package/dist/graph-ir.d.ts.map +1 -1
  76. package/dist/join-key.d.ts +31 -0
  77. package/dist/join-key.d.ts.map +1 -0
  78. package/dist/lexicon-module.d.ts +51 -6
  79. package/dist/lexicon-module.d.ts.map +1 -1
  80. package/dist/lexicon.d.ts +58 -1
  81. package/dist/lexicon.d.ts.map +1 -1
  82. package/dist/lifecycle/build-ledger-store.d.ts.map +1 -1
  83. package/dist/lifecycle/digest.d.ts +5 -1
  84. package/dist/lifecycle/digest.d.ts.map +1 -1
  85. package/dist/lifecycle/gate-ledger.d.ts +20 -5
  86. package/dist/lifecycle/gate-ledger.d.ts.map +1 -1
  87. package/dist/lifecycle/git.d.ts +64 -0
  88. package/dist/lifecycle/git.d.ts.map +1 -1
  89. package/dist/lifecycle/lease.d.ts +8 -1
  90. package/dist/lifecycle/lease.d.ts.map +1 -1
  91. package/dist/lifecycle/legacy-digest.d.ts +41 -8
  92. package/dist/lifecycle/legacy-digest.d.ts.map +1 -1
  93. package/dist/lifecycle/member-ledger.d.ts +67 -0
  94. package/dist/lifecycle/member-ledger.d.ts.map +1 -0
  95. package/dist/lifecycle/plan-digest.d.ts +4 -3
  96. package/dist/lifecycle/plan-digest.d.ts.map +1 -1
  97. package/dist/lifecycle/release-ledger.d.ts +18 -2
  98. package/dist/lifecycle/release-ledger.d.ts.map +1 -1
  99. package/dist/lifecycle/status.d.ts +90 -5
  100. package/dist/lifecycle/status.d.ts.map +1 -1
  101. package/dist/op/activities/activity-contracts.d.ts +32 -0
  102. package/dist/op/activities/activity-contracts.d.ts.map +1 -1
  103. package/dist/op/activities/index.d.ts +2 -0
  104. package/dist/op/activities/index.d.ts.map +1 -1
  105. package/dist/op/activities/propose-upgrade.d.ts +75 -0
  106. package/dist/op/activities/propose-upgrade.d.ts.map +1 -0
  107. package/dist/op/discover.d.ts.map +1 -1
  108. package/dist/op/gate-name.d.ts +7 -0
  109. package/dist/op/gate-name.d.ts.map +1 -1
  110. package/dist/op/gate-summary.d.ts +16 -0
  111. package/dist/op/gate-summary.d.ts.map +1 -1
  112. package/dist/op/gate.d.ts +16 -2
  113. package/dist/op/gate.d.ts.map +1 -1
  114. package/dist/project-root.d.ts +26 -0
  115. package/dist/project-root.d.ts.map +1 -1
  116. package/dist/workspace/__fixtures__/contract-repo.d.ts +37 -0
  117. package/dist/workspace/__fixtures__/contract-repo.d.ts.map +1 -0
  118. package/dist/workspace/__fixtures__/two-member-workspace.d.ts +11 -0
  119. package/dist/workspace/__fixtures__/two-member-workspace.d.ts.map +1 -0
  120. package/dist/workspace/checks/generated.d.ts +156 -0
  121. package/dist/workspace/checks/generated.d.ts.map +1 -0
  122. package/dist/workspace/checks/ledgers.d.ts +78 -0
  123. package/dist/workspace/checks/ledgers.d.ts.map +1 -0
  124. package/dist/workspace/checks/links.d.ts +25 -0
  125. package/dist/workspace/checks/links.d.ts.map +1 -0
  126. package/dist/workspace/checks/pipelines.d.ts +76 -0
  127. package/dist/workspace/checks/pipelines.d.ts.map +1 -0
  128. package/dist/workspace/checks.d.ts +161 -0
  129. package/dist/workspace/checks.d.ts.map +1 -0
  130. package/dist/workspace/compose-graph.d.ts +115 -0
  131. package/dist/workspace/compose-graph.d.ts.map +1 -0
  132. package/dist/workspace/compose-reports.d.ts +68 -0
  133. package/dist/workspace/compose-reports.d.ts.map +1 -0
  134. package/dist/workspace/declaration.d.ts +220 -0
  135. package/dist/workspace/declaration.d.ts.map +1 -0
  136. package/dist/workspace/declaration.schema.json +434 -0
  137. package/dist/workspace/generated-files.d.ts +65 -0
  138. package/dist/workspace/generated-files.d.ts.map +1 -0
  139. package/dist/workspace/glob.d.ts +13 -0
  140. package/dist/workspace/glob.d.ts.map +1 -0
  141. package/dist/workspace/graph-cli.d.ts +75 -0
  142. package/dist/workspace/graph-cli.d.ts.map +1 -0
  143. package/dist/workspace/init.d.ts +96 -0
  144. package/dist/workspace/init.d.ts.map +1 -0
  145. package/dist/workspace/jsonc.d.ts +38 -0
  146. package/dist/workspace/jsonc.d.ts.map +1 -0
  147. package/dist/workspace/kinds.d.ts +192 -0
  148. package/dist/workspace/kinds.d.ts.map +1 -0
  149. package/dist/workspace/lineage-check.d.ts +84 -0
  150. package/dist/workspace/lineage-check.d.ts.map +1 -0
  151. package/dist/workspace/lineage-cli.d.ts +31 -0
  152. package/dist/workspace/lineage-cli.d.ts.map +1 -0
  153. package/dist/workspace/lineage-init.d.ts +101 -0
  154. package/dist/workspace/lineage-init.d.ts.map +1 -0
  155. package/dist/workspace/lineage-lock.d.ts +258 -0
  156. package/dist/workspace/lineage-lock.d.ts.map +1 -0
  157. package/dist/workspace/lineage-merge.d.ts +14 -0
  158. package/dist/workspace/lineage-merge.d.ts.map +1 -0
  159. package/dist/workspace/lineage-migrations.d.ts +178 -0
  160. package/dist/workspace/lineage-migrations.d.ts.map +1 -0
  161. package/dist/workspace/lineage-update.d.ts +59 -0
  162. package/dist/workspace/lineage-update.d.ts.map +1 -0
  163. package/dist/workspace/lineage-upgrade-cli.d.ts +40 -0
  164. package/dist/workspace/lineage-upgrade-cli.d.ts.map +1 -0
  165. package/dist/workspace/lineage-upgrade.d.ts +146 -0
  166. package/dist/workspace/lineage-upgrade.d.ts.map +1 -0
  167. package/dist/workspace/lineage-vendor.d.ts +63 -0
  168. package/dist/workspace/lineage-vendor.d.ts.map +1 -0
  169. package/dist/workspace/lineage-version.d.ts +32 -0
  170. package/dist/workspace/lineage-version.d.ts.map +1 -0
  171. package/dist/workspace/links.d.ts +150 -0
  172. package/dist/workspace/links.d.ts.map +1 -0
  173. package/dist/workspace/ls.d.ts +99 -0
  174. package/dist/workspace/ls.d.ts.map +1 -0
  175. package/dist/workspace/member-commands.d.ts +139 -0
  176. package/dist/workspace/member-commands.d.ts.map +1 -0
  177. package/dist/workspace/member-handles.d.ts +54 -0
  178. package/dist/workspace/member-handles.d.ts.map +1 -0
  179. package/dist/workspace/member-pipeline.d.ts +143 -0
  180. package/dist/workspace/member-pipeline.d.ts.map +1 -0
  181. package/dist/workspace/member-run.d.ts +67 -0
  182. package/dist/workspace/member-run.d.ts.map +1 -0
  183. package/dist/workspace/reason-codes.d.ts +65 -0
  184. package/dist/workspace/reason-codes.d.ts.map +1 -0
  185. package/dist/workspace/records-cli.d.ts +28 -4
  186. package/dist/workspace/records-cli.d.ts.map +1 -1
  187. package/dist/workspace/records.d.ts.map +1 -1
  188. package/dist/workspace/root-refusal.d.ts +53 -0
  189. package/dist/workspace/root-refusal.d.ts.map +1 -0
  190. package/dist/workspace/status.d.ts +150 -0
  191. package/dist/workspace/status.d.ts.map +1 -0
  192. package/dist/workspace/template-manifest.d.ts +90 -0
  193. package/dist/workspace/template-manifest.d.ts.map +1 -0
  194. package/dist/workspace/tree.d.ts +36 -0
  195. package/dist/workspace/tree.d.ts.map +1 -0
  196. package/dist/workspace/trust/attestor.d.ts +67 -0
  197. package/dist/workspace/trust/attestor.d.ts.map +1 -0
  198. package/dist/workspace/trust/policy.d.ts +111 -0
  199. package/dist/workspace/trust/policy.d.ts.map +1 -0
  200. package/dist/workspace/trust/provenance.d.ts +67 -0
  201. package/dist/workspace/trust/provenance.d.ts.map +1 -0
  202. package/dist/workspace/trust/ssh-commit.d.ts +43 -0
  203. package/dist/workspace/trust/ssh-commit.d.ts.map +1 -0
  204. package/dist/workspace/trust/test-repo.d.ts +42 -0
  205. package/dist/workspace/trust/test-repo.d.ts.map +1 -0
  206. package/dist/workspace/trust/verify-cli.d.ts +9 -0
  207. package/dist/workspace/trust/verify-cli.d.ts.map +1 -0
  208. package/dist/workspace/trust/verify.d.ts +66 -0
  209. package/dist/workspace/trust/verify.d.ts.map +1 -0
  210. package/dist/workspace/which-chant.d.ts +58 -0
  211. package/dist/workspace/which-chant.d.ts.map +1 -0
  212. package/dist/workspace/workspace-kinds.schema.json +133 -0
  213. package/package.json +2 -1
  214. package/src/audit/discover.ts +66 -78
  215. package/src/audit/report-html.ts +3 -1
  216. package/src/audit/report-model.ts +14 -1
  217. package/src/audit/terraform-state.test.ts +4 -9
  218. package/src/audit/terraform-state.ts +4 -5
  219. package/src/cli/commands/audit-truncation.test.ts +167 -0
  220. package/src/cli/commands/audit.ts +89 -60
  221. package/src/cli/commands/check-lexicon.ts +24 -0
  222. package/src/cli/commands/doctor.test.ts +30 -0
  223. package/src/cli/commands/doctor.ts +18 -0
  224. package/src/cli/commands/import-agents.ts +6 -2
  225. package/src/cli/commands/init.ts +22 -3
  226. package/src/cli/commands/lint.test.ts +17 -23
  227. package/src/cli/commands/lint.ts +24 -84
  228. package/src/cli/commands/update.ts +13 -4
  229. package/src/cli/commands/vendor.ts +11 -6
  230. package/src/cli/handlers/__snapshots__/build-member.test.ts.snap +38 -0
  231. package/src/cli/handlers/build-member.test.ts +127 -0
  232. package/src/cli/handlers/build.ts +35 -1
  233. package/src/cli/handlers/components.test.ts +127 -0
  234. package/src/cli/handlers/components.ts +70 -8
  235. package/src/cli/handlers/dev.ts +5 -3
  236. package/src/cli/handlers/fan-out.ts +9 -2
  237. package/src/cli/handlers/init.ts +54 -1
  238. package/src/cli/handlers/operator.test.ts +68 -0
  239. package/src/cli/handlers/operator.ts +37 -9
  240. package/src/cli/handlers/promote.test.ts +118 -2
  241. package/src/cli/handlers/promote.ts +82 -16
  242. package/src/cli/handlers/run-generate-member.test.ts +83 -0
  243. package/src/cli/handlers/run-generate.test.ts +6 -8
  244. package/src/cli/handlers/run-generate.ts +36 -1
  245. package/src/cli/handlers/run.test.ts +47 -0
  246. package/src/cli/handlers/run.ts +28 -5
  247. package/src/cli/handlers/vendor.ts +147 -43
  248. package/src/cli/main.test.ts +105 -1
  249. package/src/cli/main.ts +230 -17
  250. package/src/cli/plugins.ts +17 -17
  251. package/src/cli/registry.ts +32 -0
  252. package/src/cli/static-config-read.test.ts +208 -0
  253. package/src/codegen/fetch.test.ts +63 -1
  254. package/src/codegen/fetch.ts +30 -1
  255. package/src/components/cli-support.ts +6 -1
  256. package/src/components/discover.test.ts +27 -57
  257. package/src/components/discover.ts +13 -68
  258. package/src/components/driver.test.ts +78 -1
  259. package/src/components/driver.ts +43 -7
  260. package/src/components/gate-plan.ts +53 -0
  261. package/src/components/promote.test.ts +276 -2
  262. package/src/components/promote.ts +119 -15
  263. package/src/components/sandbox/driver.ts +8 -8
  264. package/src/components/sandbox/run.ts +4 -1
  265. package/src/components/verbs/build-archive.ts +27 -21
  266. package/src/components/verbs/run-agent.ts +3 -5
  267. package/src/config-static.test.ts +136 -0
  268. package/src/config-static.ts +232 -0
  269. package/src/config.test.ts +2 -2
  270. package/src/config.ts +48 -15
  271. package/src/content-digest.test.ts +120 -0
  272. package/src/content-digest.ts +21 -0
  273. package/src/discovery/files.test.ts +10 -10
  274. package/src/discovery/files.ts +25 -81
  275. package/src/discovery/fold-import.ts +49 -5
  276. package/src/discovery/index.ts +1 -0
  277. package/src/discovery/sandbox/driver.ts +73 -35
  278. package/src/discovery/sandbox/fork-diagnostic.test.ts +136 -13
  279. package/src/discovery/sandbox/fork.ts +83 -43
  280. package/src/discovery/sandbox/path-lexicon-multifile.test.ts +285 -0
  281. package/src/discovery/walk.test.ts +263 -0
  282. package/src/discovery/walk.ts +334 -0
  283. package/src/effect-receipt.ts +2 -2
  284. package/src/graph-ir.ts +4 -1
  285. package/src/join-key.test.ts +25 -0
  286. package/src/join-key.ts +39 -0
  287. package/src/lexicon-module.ts +108 -10
  288. package/src/lexicon.ts +79 -1
  289. package/src/lifecycle/build-ledger-store.ts +11 -10
  290. package/src/lifecycle/digest.ts +9 -5
  291. package/src/lifecycle/gate-ledger.test.ts +20 -0
  292. package/src/lifecycle/gate-ledger.ts +28 -6
  293. package/src/lifecycle/git.test.ts +13 -1
  294. package/src/lifecycle/git.ts +221 -176
  295. package/src/lifecycle/lease.ts +25 -10
  296. package/src/lifecycle/legacy-digest.test.ts +115 -23
  297. package/src/lifecycle/legacy-digest.ts +60 -19
  298. package/src/lifecycle/member-ledger.test.ts +319 -0
  299. package/src/lifecycle/member-ledger.ts +145 -0
  300. package/src/lifecycle/plan-digest.ts +7 -7
  301. package/src/lifecycle/release-ledger.test.ts +20 -0
  302. package/src/lifecycle/release-ledger.ts +23 -7
  303. package/src/lifecycle/status.test.ts +151 -1
  304. package/src/lifecycle/status.ts +228 -7
  305. package/src/op/activities/activity-contracts.ts +37 -0
  306. package/src/op/activities/index.ts +10 -0
  307. package/src/op/activities/propose-upgrade.ts +206 -0
  308. package/src/op/discover.test.ts +14 -4
  309. package/src/op/discover.ts +19 -27
  310. package/src/op/gate-name.ts +8 -0
  311. package/src/op/gate-summary.test.ts +14 -0
  312. package/src/op/gate-summary.ts +25 -2
  313. package/src/op/gate.ts +36 -7
  314. package/src/op/generate-pipeline.test.ts +14 -5
  315. package/src/project-root.test.ts +66 -1
  316. package/src/project-root.ts +48 -0
  317. package/src/workspace/__fixtures__/contract-repo.ts +100 -0
  318. package/src/workspace/__fixtures__/two-member-workspace.ts +35 -0
  319. package/src/workspace/behold-kinds.test.ts +162 -0
  320. package/src/workspace/check-contract.test.ts +127 -0
  321. package/src/workspace/check.schema.json +347 -0
  322. package/src/workspace/checks/generated.test.ts +287 -0
  323. package/src/workspace/checks/generated.ts +475 -0
  324. package/src/workspace/checks/ledgers.test.ts +139 -0
  325. package/src/workspace/checks/ledgers.ts +246 -0
  326. package/src/workspace/checks/links.ts +183 -0
  327. package/src/workspace/checks/pipelines.test.ts +133 -0
  328. package/src/workspace/checks/pipelines.ts +225 -0
  329. package/src/workspace/checks-repo.test.ts +71 -0
  330. package/src/workspace/checks.test.ts +450 -0
  331. package/src/workspace/checks.ts +461 -0
  332. package/src/workspace/compose-graph.test.ts +146 -0
  333. package/src/workspace/compose-graph.ts +228 -0
  334. package/src/workspace/compose-reports.test.ts +109 -0
  335. package/src/workspace/compose-reports.ts +186 -0
  336. package/src/workspace/declaration.schema.json +434 -0
  337. package/src/workspace/declaration.test.ts +347 -0
  338. package/src/workspace/declaration.ts +623 -0
  339. package/src/workspace/generated-files.test.ts +114 -0
  340. package/src/workspace/generated-files.ts +94 -0
  341. package/src/workspace/glob.ts +49 -0
  342. package/src/workspace/graph-cli.ts +220 -0
  343. package/src/workspace/graph-contract.test.ts +204 -0
  344. package/src/workspace/graph.schema.json +662 -0
  345. package/src/workspace/init.test.ts +223 -0
  346. package/src/workspace/init.ts +585 -0
  347. package/src/workspace/jsonc.test.ts +65 -0
  348. package/src/workspace/jsonc.ts +231 -0
  349. package/src/workspace/kinds.test.ts +288 -0
  350. package/src/workspace/kinds.ts +506 -0
  351. package/src/workspace/lineage-check.ts +264 -0
  352. package/src/workspace/lineage-cli.ts +114 -0
  353. package/src/workspace/lineage-init.test.ts +231 -0
  354. package/src/workspace/lineage-init.ts +365 -0
  355. package/src/workspace/lineage-lock.test.ts +224 -0
  356. package/src/workspace/lineage-lock.ts +357 -0
  357. package/src/workspace/lineage-merge.ts +44 -0
  358. package/src/workspace/lineage-migrations.test.ts +178 -0
  359. package/src/workspace/lineage-migrations.ts +343 -0
  360. package/src/workspace/lineage-update.ts +160 -0
  361. package/src/workspace/lineage-upgrade-cli.ts +151 -0
  362. package/src/workspace/lineage-upgrade.test.ts +474 -0
  363. package/src/workspace/lineage-upgrade.ts +624 -0
  364. package/src/workspace/lineage-vendor.test.ts +126 -0
  365. package/src/workspace/lineage-vendor.ts +215 -0
  366. package/src/workspace/lineage-version.ts +127 -0
  367. package/src/workspace/links.test.ts +344 -0
  368. package/src/workspace/links.ts +331 -0
  369. package/src/workspace/ls-contract.test.ts +200 -0
  370. package/src/workspace/ls.schema.json +190 -0
  371. package/src/workspace/ls.ts +254 -0
  372. package/src/workspace/member-commands.e2e.test.ts +244 -0
  373. package/src/workspace/member-commands.test.ts +217 -0
  374. package/src/workspace/member-commands.ts +540 -0
  375. package/src/workspace/member-handles.test.ts +76 -0
  376. package/src/workspace/member-handles.ts +228 -0
  377. package/src/workspace/member-pipeline.test.ts +135 -0
  378. package/src/workspace/member-pipeline.ts +317 -0
  379. package/src/workspace/member-run.ts +146 -0
  380. package/src/workspace/read-contract.test.ts +129 -0
  381. package/src/workspace/reason-codes.test.ts +113 -0
  382. package/src/workspace/reason-codes.ts +81 -0
  383. package/src/workspace/records-cli.ts +86 -14
  384. package/src/workspace/records-contract.test.ts +5 -0
  385. package/src/workspace/records.schema.json +32 -2
  386. package/src/workspace/records.ts +3 -2
  387. package/src/workspace/root-refusal.ts +97 -0
  388. package/src/workspace/status-contract.test.ts +308 -0
  389. package/src/workspace/status.schema.json +210 -0
  390. package/src/workspace/status.ts +412 -0
  391. package/src/workspace/template-manifest.test.ts +79 -0
  392. package/src/workspace/template-manifest.ts +221 -0
  393. package/src/workspace/tree.ts +132 -0
  394. package/src/workspace/trust/attestor.ts +93 -0
  395. package/src/workspace/trust/policy.ts +306 -0
  396. package/src/workspace/trust/provenance.ts +181 -0
  397. package/src/workspace/trust/ssh-commit.ts +138 -0
  398. package/src/workspace/trust/test-repo.ts +138 -0
  399. package/src/workspace/trust/trust.test.ts +397 -0
  400. package/src/workspace/trust/verify-cli.ts +56 -0
  401. package/src/workspace/trust/verify.ts +194 -0
  402. package/src/workspace/which-chant.test.ts +117 -0
  403. package/src/workspace/which-chant.ts +151 -0
  404. package/src/workspace/workspace-kinds.schema.json +133 -0
  405. package/dist/components/unbound-gate-approval.d.ts +0 -24
  406. package/dist/components/unbound-gate-approval.d.ts.map +0 -1
  407. package/dist/discovery/convergence.d.ts +0 -97
  408. package/dist/discovery/convergence.d.ts.map +0 -1
  409. package/src/cli/commands/audit-walk-warnings.test.ts +0 -156
  410. package/src/components/unbound-gate-approval.test.ts +0 -82
  411. package/src/components/unbound-gate-approval.ts +0 -51
  412. package/src/discovery/convergence.test.ts +0 -231
  413. package/src/discovery/convergence.ts +0 -399
  414. package/src/op/discover-convergence.test.ts +0 -70
@@ -17,6 +17,9 @@
17
17
  * before anything after it runs;
18
18
  * - gates, apply, verify and every other step run exactly as a normal deploy
19
19
  * to the target environment would run them, so the target's gates apply.
20
+ * A gate approval is bound to the target environment, the transformed
21
+ * composition and the digest being deployed (#2574), so an approval for a
22
+ * plain deploy or for another release does not pass it.
20
23
  *
21
24
  * A composition the transform cannot pin to one recorded digest is refused
22
25
  * before anything runs: one with no publish step (nothing would carry the
@@ -27,7 +30,9 @@
27
30
  * A rollback (#2531) is the same run against one environment's own earlier
28
31
  * release. There the publish step is replaced by the recorded digest rather
29
32
  * than run, since the environment already received that artifact when the
30
- * release was first deployed (see `withoutBuildSteps`'s `pinDigest`).
33
+ * release was first deployed (see `withoutBuildSteps`'s `pinDigest`). A
34
+ * redeploy (#2604) is a rollback to the release the ledger already records as
35
+ * current, for an environment a failed deploy left running something else.
31
36
  *
32
37
  * The driver stays capability-agnostic. The two kind lists below are data, in
33
38
  * the same spirit as `DEPLOY_UNIT_RULES` (./deploy-units.ts).
@@ -114,12 +119,41 @@ export function selectRelease(
114
119
  return { record: latestPerComponent(matching).get(component)! };
115
120
  }
116
121
 
122
+ /**
123
+ * Read `--digest <component>=<digest>` values (#2602) into a component-to-
124
+ * digest map. A value with no `=` is left to the single-component form
125
+ * (`--component <name> --digest <digest>`), so it is an error here. An empty
126
+ * digest is an error too: a generated promote job passes one when the job that
127
+ * deployed the component recorded no release, and promoting whatever is
128
+ * latest instead is the thing the pin is there to prevent.
129
+ */
130
+ export function parseDigestPins(values: string[]): { pins: Record<string, string> } | { error: string } {
131
+ const pins: Record<string, string> = {};
132
+ for (const value of values) {
133
+ const at = value.indexOf("=");
134
+ if (at < 0) return { error: "--digest picks one release of one component, so it needs --component <name>, or the form <component>=<digest>" };
135
+ const component = value.slice(0, at);
136
+ const digest = value.slice(at + 1);
137
+ if (component === "") return { error: `--digest ${value} names no component` };
138
+ if (digest === "") {
139
+ return { error: `--digest ${value} names no digest; the run that deployed "${component}" recorded no release for it` };
140
+ }
141
+ if (component in pins && pins[component] !== digest) {
142
+ return { error: `--digest pins "${component}" twice, to ${pins[component]} and ${digest}` };
143
+ }
144
+ pins[component] = digest;
145
+ }
146
+ return { pins };
147
+ }
148
+
117
149
  /**
118
150
  * Decide which releases a promote deploys. With `component`, exactly that
119
- * component's latest source release (or the one recording `digest`). Without
120
- * it, the latest source release of every component this checkout declares;
121
- * a recorded component the checkout no longer declares is reported and left
122
- * alone, since there is no composition to deploy it with.
151
+ * component's latest source release (or the one recording `digest`). With
152
+ * `pins` (#2602), exactly the pinned components, each at the source release
153
+ * recording its pinned digest. Otherwise, the latest source release of every
154
+ * component this checkout declares; a recorded component the checkout no
155
+ * longer declares is reported and left alone, since there is no composition
156
+ * to deploy it with.
123
157
  */
124
158
  export function planPromotion(input: {
125
159
  from: string;
@@ -129,13 +163,29 @@ export function planPromotion(input: {
129
163
  declared: string[];
130
164
  component?: string;
131
165
  digest?: string;
166
+ /** Component name to the digest to promote for it, from `--digest <component>=<digest>`. */
167
+ pins?: Record<string, string>;
132
168
  }): PromotionPlan | { error: string } {
133
- const { from, to, sourceRecords, declared, component, digest } = input;
169
+ const { from, to, sourceRecords, declared, component, digest, pins } = input;
134
170
  if (from === to) return { error: `--from and --to name the same environment ("${from}")` };
135
171
  if (digest !== undefined && component === undefined) {
136
172
  return { error: "--digest picks one release of one component, so it needs --component <name>" };
137
173
  }
138
174
 
175
+ if (pins !== undefined && Object.keys(pins).length > 0) {
176
+ if (component !== undefined) {
177
+ return { error: "--digest <component>=<digest> names its own component, so it does not take --component" };
178
+ }
179
+ const items: PromotionItem[] = [];
180
+ for (const [name, pinned] of Object.entries(pins).sort(([a], [b]) => a.localeCompare(b))) {
181
+ if (!declared.includes(name)) return { error: `component "${name}" is not declared in this checkout` };
182
+ const picked = selectRelease(sourceRecords, name, from, pinned);
183
+ if ("error" in picked) return picked;
184
+ items.push({ component: name, digest: picked.record.digest, source: picked.record });
185
+ }
186
+ return { from, to, items, notPromoted: [] };
187
+ }
188
+
139
189
  if (component !== undefined) {
140
190
  if (!declared.includes(component)) {
141
191
  return { error: `component "${component}" is not declared in this checkout` };
@@ -197,6 +247,36 @@ export function planRollback(input: {
197
247
  return { from: env, to: env, items: [{ component, digest: target.digest, source: target }], notPromoted: [] };
198
248
  }
199
249
 
250
+ /**
251
+ * Decide what a redeploy deploys: the release of `component` the ledger in
252
+ * `env` records as current. A deploy that fails partway records nothing, so
253
+ * after one the ledger still names the last good release while the
254
+ * environment may be running part of the failed one. `digest`, when given,
255
+ * has to be that current digest. It guards against a deploy recorded since
256
+ * the caller last looked; any earlier release is a rollback.
257
+ */
258
+ export function planRedeploy(input: {
259
+ env: string;
260
+ records: ReleaseRecord[];
261
+ declared: string[];
262
+ component: string;
263
+ digest?: string;
264
+ }): PromotionPlan | { error: string } {
265
+ const { env, records, declared, component, digest } = input;
266
+ if (!declared.includes(component)) {
267
+ return { error: `component "${component}" is not declared in this checkout` };
268
+ }
269
+ const current = latestPerComponent(records.filter((r) => r.component === component)).get(component);
270
+ if (!current) return { error: `no release of "${component}" is recorded in "${env}"` };
271
+ if (digest !== undefined && digest !== current.digest) {
272
+ return {
273
+ error: `${digest} is not the current release of "${component}" in "${env}" (that is ${current.digest}); ` +
274
+ `use \`chant components rollback ${env} --component ${component} --digest ${digest}\` to restore an earlier release`,
275
+ };
276
+ }
277
+ return { from: env, to: env, items: [{ component, digest: current.digest, source: current }], notPromoted: [] };
278
+ }
279
+
200
280
  function isGate(step: DriverStep | DriverGate | DriverPhase): step is DriverGate {
201
281
  return (step as { kind?: unknown }).kind === "gate";
202
282
  }
@@ -243,6 +323,17 @@ export function promoteArchivePaths(component: DriverComponent): string[] {
243
323
  return paths;
244
324
  }
245
325
 
326
+ /**
327
+ * Whether `component`'s deploy has a publish step (#2602). Only such a
328
+ * component records a release when it deploys, so only its release can be
329
+ * promoted; a generated pipeline pins exactly these components' digests.
330
+ */
331
+ export function hasPublishStep(component: DriverComponent): boolean {
332
+ const walk = (steps: Array<DriverStep | DriverGate | DriverPhase>): boolean =>
333
+ steps.some((step) => isPhase(step) ? walk(step.steps) : !isGate(step) && PUBLISH_STEP_KINDS.includes(step.kind));
334
+ return walk(component.deploy);
335
+ }
336
+
246
337
  /** A component's composition with its build-time steps taken out. */
247
338
  export interface PromotableComponent {
248
339
  component: DriverComponent;
@@ -270,7 +361,7 @@ export const recordedDigestCapability: Capability<{ digest: string }, { digest:
270
361
  * check that what is left can be pinned to one recorded digest. A phase left
271
362
  * with nothing to run is dropped. `rollback` phases are untouched.
272
363
  *
273
- * With `pinDigest` (a rollback, #2531), the publish step is replaced too, by a
364
+ * With `pinDigest` (a rollback, #2531, or a redeploy, #2604), the publish step is replaced too, by a
274
365
  * {@link RECORDED_DIGEST_KIND} step carrying that digest: the environment
275
366
  * already received the artifact when the release being restored was
276
367
  * deployed, and the archive it came from is usually long gone. Only the
@@ -279,9 +370,9 @@ export const recordedDigestCapability: Capability<{ digest: string }, { digest:
279
370
  */
280
371
  export function withoutBuildSteps(
281
372
  component: DriverComponent,
282
- opts: { pinDigest?: string } = {},
373
+ opts: { pinDigest?: string; verb?: string } = {},
283
374
  ): PromotableComponent | { error: string } {
284
- const verb = opts.pinDigest === undefined ? "a promote" : "a rollback";
375
+ const verb = opts.verb ?? (opts.pinDigest === undefined ? "a promote" : "a rollback");
285
376
  const removed: string[] = [];
286
377
  /** Phases a build step was removed from, and whether anything else is left in them. */
287
378
  const emptied = new Map<string, boolean>();
@@ -466,6 +557,9 @@ export async function runPromotion(options: RunPromotionOptions): Promise<Driver
466
557
  return await runInterpretDriver(targets, checkPublishedDigest(options.registry, expected), {
467
558
  env: plan.to,
468
559
  componentOutputs: options.componentOutputs ?? {},
560
+ // #2574: a gate approval for this promote or rollback is bound to the
561
+ // release it deploys, as well as the environment and composition.
562
+ releases: expected,
469
563
  ...(options.onProgress ? { onProgress: options.onProgress } : {}),
470
564
  ...(options.gates ? { gates: options.gates } : {}),
471
565
  ...(options.now ? { now: options.now } : {}),
@@ -485,7 +579,7 @@ export function gateApprover(result: DriverComponentResult | undefined): string
485
579
  return approver;
486
580
  }
487
581
 
488
- /** What a promote or rollback knows about its own run, for the records it writes. */
582
+ /** What a promote, rollback or redeploy knows about its own run, for the records it writes. */
489
583
  export interface DeployRunInfo {
490
584
  runId: string;
491
585
  runOrigin?: RunOrigin;
@@ -494,8 +588,8 @@ export interface DeployRunInfo {
494
588
  approver?: string;
495
589
  }
496
590
 
497
- /** The fields a promote and a rollback record share: the artifact is the earlier release's own. */
498
- function redeployRecord(item: PromotionItem, env: string, run: DeployRunInfo): ReleaseRecordInput {
591
+ /** The fields a promote, a rollback and a redeploy record share: the artifact is the earlier release's own. */
592
+ function sharedRecord(item: PromotionItem, env: string, run: DeployRunInfo): ReleaseRecordInput {
499
593
  const { source } = item;
500
594
  return {
501
595
  component: item.component,
@@ -512,7 +606,7 @@ function redeployRecord(item: PromotionItem, env: string, run: DeployRunInfo): R
512
606
  };
513
607
  }
514
608
 
515
- /** Name an earlier record the way `promotedFrom` and `restores` do. */
609
+ /** Name an earlier record the way `promotedFrom`, `restores` and `redeploys` do. */
516
610
  function refTo(record: ReleaseRecord) {
517
611
  return { env: record.env, runId: record.runId, timestamp: record.timestamp };
518
612
  }
@@ -524,7 +618,7 @@ function refTo(record: ReleaseRecord) {
524
618
  * release.
525
619
  */
526
620
  export function promotionRecord(item: PromotionItem, to: string, run: DeployRunInfo): ReleaseRecordInput {
527
- return { ...redeployRecord(item, to, run), promotedFrom: refTo(item.source) };
621
+ return { ...sharedRecord(item, to, run), promotedFrom: refTo(item.source) };
528
622
  }
529
623
 
530
624
  /**
@@ -533,5 +627,15 @@ export function promotionRecord(item: PromotionItem, to: string, run: DeployRunI
533
627
  * release.
534
628
  */
535
629
  export function rollbackRecord(item: PromotionItem, env: string, run: DeployRunInfo): ReleaseRecordInput {
536
- return { ...redeployRecord(item, env, run), restores: refTo(item.source) };
630
+ return { ...sharedRecord(item, env, run), restores: refTo(item.source) };
631
+ }
632
+
633
+ /**
634
+ * The release record a successful redeploy appends. It carries the current
635
+ * release's artifact identities, as a rollback's carries the restored one's;
636
+ * `redeploys` names that release. The environment's current digest does not
637
+ * change, and the record says when it was last put back.
638
+ */
639
+ export function redeployRecord(item: PromotionItem, env: string, run: DeployRunInfo): ReleaseRecordInput {
640
+ return { ...sharedRecord(item, env, run), redeploys: refTo(item.source) };
537
641
  }
@@ -1,5 +1,6 @@
1
1
  import { join, resolve } from "node:path";
2
2
  import { params as currentBuildParams } from "../../params";
3
+ import { sendFunctionSource } from "../../discovery/sandbox/driver";
3
4
 
4
5
  /**
5
6
  * chant #1051 — generates the source of the "driver" module that runs INSIDE
@@ -60,15 +61,14 @@ export function generateComponentDriverSource(options: GenerateComponentDriverOp
60
61
  `import { collectComponents } from ${lit(DISCOVER_MODULE)};`,
61
62
  `import { classifyChildError } from ${lit(CHILD_ERRORS_MODULE)};`,
62
63
  `import { setBuildParams } from ${lit(PARAMS_MODULE)};`,
64
+ `import { writeSync } from "node:fs";`,
63
65
  ``,
64
66
  // chant #1108 — snapshot of the parent's resolved build-time parameter
65
67
  // values (scalars only), bound before any component file is imported.
66
68
  `setBuildParams(${lit({ ...currentBuildParams })});`,
67
69
  ``,
68
- `function send(payload) {`,
69
- ` if (typeof process.send === "function") process.send(payload);`,
70
- ` else console.log(JSON.stringify(payload));`,
71
- `}`,
70
+ // chant#2461 — awaits the send and fails loudly if it cannot be delivered.
71
+ ...sendFunctionSource(),
72
72
  ``,
73
73
  `async function main() {`,
74
74
  ` const modules = [];`,
@@ -114,12 +114,12 @@ export function generateComponentDriverSource(options: GenerateComponentDriverOp
114
114
  ` for (const [name, discovered] of collected.components) {`,
115
115
  ` components.push({ name, component: discovered.component, exportName: discovered.exportName, filePath: discovered.filePath });`,
116
116
  ` }`,
117
- ` send({ components, errors });`,
117
+ ` await send({ components, errors });`,
118
118
  `}`,
119
119
  ``,
120
- `main().catch((err) => {`,
121
- ` send({ components: [], errors: [classifyChildError("", err).toJSON()], fatal: true });`,
122
- `});`,
120
+ `main().catch((err) =>`,
121
+ ` send({ components: [], errors: [classifyChildError("", err).toJSON()], fatal: true }),`,
122
+ `);`,
123
123
  );
124
124
 
125
125
  return lines.join("\n");
@@ -3,6 +3,7 @@ import { realpathSync, rmSync } from "node:fs";
3
3
  import { resolve } from "node:path";
4
4
  import { bundleDriver } from "../../discovery/sandbox/bundle";
5
5
  import { classifyChildError } from "../../discovery/sandbox/child-errors";
6
+ import { settleOnExitAfterChannelDrains } from "../../discovery/sandbox/fork";
6
7
  import { DiscoveryError, type DiscoveryErrorType } from "../../errors";
7
8
  import type { Component } from "../component";
8
9
  import type { DiscoveredComponent } from "../discover";
@@ -163,7 +164,9 @@ function runChildProcess(
163
164
  reject(err);
164
165
  });
165
166
 
166
- child.on("exit", (code, signal) => {
167
+ // chant#2461 — a payload can still be in the IPC socket when the exit is
168
+ // handled; see settleOnExitAfterChannelDrains.
169
+ settleOnExitAfterChannelDrains(child, (code, signal) => {
167
170
  if (settled) return;
168
171
  settled = true;
169
172
  clearTimeout(timeout);
@@ -55,6 +55,8 @@
55
55
 
56
56
  import type { ArtifactReproducibility, ProvenanceLink } from "./reproducibility";
57
57
  import { defaultReproducibility } from "./reproducibility";
58
+ import { contentDigest } from "../../content-digest";
59
+ import { canonicalJson } from "../../effect-receipt";
58
60
 
59
61
  export type BuildArchiveEntryKind = "image" | "template" | "asset" | "sbom";
60
62
 
@@ -129,6 +131,12 @@ export interface BuildArchiveManifest {
129
131
  * changed contents, not merely a re-run.
130
132
  */
131
133
  manifestDigest: string;
134
+ /**
135
+ * Set on read, never written: `["legacy-digest"]` when a manifest
136
+ * persisted on `chant/lifecycle` holds digests from the 32-bit hash chant
137
+ * used before real SHA-256 (#2514). See ../../lifecycle/legacy-digest.ts.
138
+ */
139
+ flags?: Array<"legacy-digest">;
132
140
  }
133
141
 
134
142
  /**
@@ -147,30 +155,28 @@ const DEFAULT_MEDIA_TYPES: Record<BuildArchiveEntryKind, string> = {
147
155
  };
148
156
 
149
157
  /**
150
- * Deterministic content digest over arbitrary string content. Same shape as
151
- * the FNV-ish fake digest `MockCloudExecutor` uses, so tests can assert on it
152
- * without a real `sha256` implementation. Exported so callers that need to
153
- * digest content that never goes through the `CloudExecutor` (e.g. a
154
- * synthesized template's serialized bytes, see ./build.ts's
155
- * `addArchiveTemplate`) share this one algorithm rather than each defining
156
- * their own. Production `image`/`asset` entries instead carry the real
157
- * digest their producing capability already computed (e.g. `docker-build`'s
158
- * `executor.docker.build` digest).
158
+ * The content digest every entry chant computes itself carries: real
159
+ * SHA-256 over the UTF-8 bytes of its input (../../content-digest.ts,
160
+ * chant #2514). Callers that digest content that never goes through the
161
+ * `CloudExecutor` (a synthesized template's serialized bytes, see ./build.ts's
162
+ * `addArchiveTemplate`, or an SBOM) use it. Production `image`/`asset`
163
+ * entries instead carry the digest their producing capability already
164
+ * computed (e.g. `docker-build`'s `executor.docker.build` digest).
159
165
  */
160
- export function contentDigest(input: string): string {
161
- let hash = 0;
162
- for (let i = 0; i < input.length; i++) {
163
- hash = (Math.imul(hash, 31) + input.charCodeAt(i)) | 0;
164
- }
165
- const hex = Math.abs(hash).toString(16).padStart(8, "0");
166
- return `sha256:${hex.repeat(8).slice(0, 64)}`;
167
- }
166
+ export { contentDigest };
168
167
 
169
- /** Compute the aggregate `manifestDigest` over a set of entries — sorted by path so entry order never changes the digest, only content does. */
168
+ /**
169
+ * Compute the aggregate `manifestDigest` over a set of entries. Each entry
170
+ * contributes its `kind`, `path` and `digest`; entries are sorted by path
171
+ * (UTF-16 code units, the order `canonicalJson` sorts keys in) so entry order
172
+ * never changes the digest, only content does. The list is hashed as
173
+ * `canonicalJson`, so no path or digest can be read as a field separator.
174
+ */
170
175
  export function computeManifestDigest(contents: BuildArchiveEntry[]): string {
171
- const sorted = [...contents].sort((a, b) => a.path.localeCompare(b.path));
172
- const canonical = sorted.map((e) => `${e.kind}:${e.path}:${e.digest}`).join("\n");
173
- return contentDigest(canonical);
176
+ const sorted = contents
177
+ .map((e) => ({ kind: e.kind, path: e.path, digest: e.digest }))
178
+ .sort((a, b) => (a.path < b.path ? -1 : a.path > b.path ? 1 : 0));
179
+ return contentDigest(canonicalJson(sorted));
174
180
  }
175
181
 
176
182
  /** Start a new, empty manifest for `component`. Entries are added with `addArchiveEntry`. */
@@ -237,7 +237,7 @@
237
237
  * `externalParameters`, never replacing it), not a shape break.
238
238
  */
239
239
 
240
- import { createHash } from "node:crypto";
240
+ import { contentDigest } from "../../content-digest";
241
241
  import type { Capability, DeployContext } from "../capability";
242
242
  import type { ProvenanceLink } from "./reproducibility";
243
243
  import type { BuildArchiveEntry } from "./build-archive";
@@ -555,10 +555,8 @@ export function createRunAgentCapability(
555
555
  const PROMPT_PATH = "/work/prompt";
556
556
  const OUTPUT_PATH = "/work/output";
557
557
 
558
- /** `sha256:<hex>` over a string, prefixed the same way ./build-archive.ts's `contentDigest` and ./build.ts's zip/jar digests are. */
559
- function sha256Digest(content: string): string {
560
- return `sha256:${createHash("sha256").update(content, "utf8").digest("hex")}`;
561
- }
558
+ /** `sha256:<hex>` over a string: ../../content-digest.ts's `contentDigest`, the same digest ./build-archive.ts uses. */
559
+ const sha256Digest = contentDigest;
562
560
 
563
561
  const EMPTY_DIGEST = sha256Digest("");
564
562
 
@@ -0,0 +1,136 @@
1
+ /**
2
+ * chant#2591 — the static reader of a config's `lexicons`: what it reads, and
3
+ * what it reports as unreadable instead of running the config.
4
+ */
5
+ import { describe, test, expect, beforeEach, afterEach } from "vitest";
6
+ import { existsSync, mkdirSync, mkdtempSync, realpathSync, rmSync, writeFileSync } from "node:fs";
7
+ import { tmpdir } from "node:os";
8
+ import { join } from "node:path";
9
+ import { readConfigFieldsStatically, readLexiconDeclarationsStatically, unknownPathLexiconsNotice } from "./config-static";
10
+
11
+ let dir: string;
12
+
13
+ beforeEach(() => {
14
+ dir = realpathSync(mkdtempSync(join(tmpdir(), "chant-2591-static-")));
15
+ // A project boundary, so the upward walk stops here.
16
+ writeFileSync(join(dir, "package.json"), "{}");
17
+ });
18
+
19
+ afterEach(() => {
20
+ rmSync(dir, { recursive: true, force: true });
21
+ });
22
+
23
+ function readTs(source: string) {
24
+ writeFileSync(join(dir, "chant.config.ts"), source);
25
+ return readLexiconDeclarationsStatically(dir);
26
+ }
27
+
28
+ const SITE = { name: "site", module: "./lexicon/index.ts" };
29
+
30
+ describe("readable configs", () => {
31
+ test.each([
32
+ ["an object literal default export", `export default { lexicons: ["aws", { name: "site", module: "./lexicon/index.ts" }] };`],
33
+ [
34
+ "satisfies and a type import",
35
+ `import type { ChantConfig } from "@intentius/chant";\nexport default { lexicons: ["aws", { name: "site", module: "./lexicon/index.ts" }] } satisfies ChantConfig;`,
36
+ ],
37
+ [
38
+ "a const alias",
39
+ `const config = { lexicons: ["aws", { name: "site", module: "./lexicon/index.ts" }], sourceDir: "src" };\nexport default config;`,
40
+ ],
41
+ ["a named config export", `export const config = { lexicons: ["aws", { name: "site", module: "./lexicon/index.ts" }] };`],
42
+ ["named exports", `export const lexicons = ["aws", { name: "site", module: "./lexicon/index.ts" }];\nexport const sourceDir = "src";`],
43
+ [
44
+ "consts, a spread and a template",
45
+ `const dir = "lexicon";\nconst base = ["aws"];\nexport default { lexicons: [...base, { name: "site", module: \`./\${dir}/index.ts\` }] };`,
46
+ ],
47
+ [
48
+ "a spread config",
49
+ `const shared = { lexicons: ["aws", { name: "site", module: "./lexicon/index.ts" }] };\nexport default { ...shared, sourceDir: "src" };`,
50
+ ],
51
+ [
52
+ "other keys that are not readable",
53
+ `import { plugin } from "./rules";\nexport default { lexicons: ["aws", { name: "site", module: "./lexicon/index.ts" }], lint: { plugins: [plugin()] } };`,
54
+ ],
55
+ ])("%s", (_label, source) => {
56
+ const read = readTs(source);
57
+ expect(read).toEqual({ status: "read", configPath: join(dir, "chant.config.ts"), entries: ["aws", SITE] });
58
+ });
59
+
60
+ test("a same-file function the fold subset covers is interpreted, not run", () => {
61
+ const read = readTs(`const pick = (extra) => ["aws", extra];\nexport default { lexicons: pick("k8s") };`);
62
+ expect(read).toMatchObject({ status: "read", entries: ["aws", "k8s"] });
63
+ });
64
+
65
+ test("a config with no lexicons reads as none", () => {
66
+ expect(readTs(`export default { sourceDir: "src" };`)).toMatchObject({ status: "read", entries: [] });
67
+ });
68
+
69
+ test("chant.config.json is parsed", () => {
70
+ writeFileSync(join(dir, "chant.config.json"), JSON.stringify({ lexicons: ["aws", SITE] }));
71
+ expect(readLexiconDeclarationsStatically(dir)).toMatchObject({ status: "read", entries: ["aws", SITE] });
72
+ });
73
+
74
+ test("the walk goes up from a subdirectory", () => {
75
+ writeFileSync(join(dir, "chant.config.ts"), `export default { lexicons: [{ name: "site", module: "./lexicon/index.ts" }] };`);
76
+ mkdirSync(join(dir, "src", "deep"), { recursive: true });
77
+ expect(readLexiconDeclarationsStatically(join(dir, "src", "deep"))).toMatchObject({ status: "read", entries: [SITE] });
78
+ });
79
+
80
+ test("no config", () => {
81
+ expect(readLexiconDeclarationsStatically(dir)).toEqual({ status: "no-config" });
82
+ });
83
+ });
84
+
85
+ describe("unreadable configs are reported, never run", () => {
86
+ test.each([
87
+ ["an imported binding", `import { lexicons } from "./shared";\nexport default { lexicons };`, /lexicons/],
88
+ ["process.env", `export default { lexicons: process.env.CI ? ["aws"] : ["aws", "k8s"] };`, /process/],
89
+ ["a call as the default export", `export default defineConfig({ lexicons: ["aws"] });`, /not an object literal/],
90
+ ["a re-export", `export { default } from "./base";`, /export/],
91
+ ["an imported function in lexicons", `import { pick } from "./pick";\nexport default { lexicons: pick() };`, /line 4: .*pick/],
92
+ ["entries of the wrong shape", `export default { lexicons: [{ name: "site" }] };`, /not a list of lexicon entries/],
93
+ ])("%s", (_label, source, reason) => {
94
+ const marker = join(dir, "ran");
95
+ const read = readTs(`import { writeFileSync } from "node:fs";\nwriteFileSync(${JSON.stringify(marker)}, "x");\n${source}`);
96
+ expect(read.status).toBe("unknown");
97
+ expect(read.status === "unknown" && read.reason).toMatch(reason);
98
+ expect(unknownPathLexiconsNotice(read)).toContain("is unknown here and is treated as a package");
99
+ expect(existsSync(marker)).toBe(false);
100
+ });
101
+
102
+ test("a readable config has no notice", () => {
103
+ expect(unknownPathLexiconsNotice(readTs(`export default { lexicons: ["aws"] };`))).toBeUndefined();
104
+ });
105
+ });
106
+
107
+ describe("readConfigFieldsStatically (#2641)", () => {
108
+ test("reads the asked-for fields of a default export, a named config or named exports, and leaves unset ones out", () => {
109
+ writeFileSync(join(dir, "chant.config.ts"), `const env = "prod";\nexport default { environments: [env, { name: "staging" }], ownership: { stack: "shop", env }, lexicons: ["aws"] };`);
110
+ expect(readConfigFieldsStatically(dir, ["ownership", "environments", "sourceDir"])).toEqual({
111
+ status: "read",
112
+ configPath: join(dir, "chant.config.ts"),
113
+ fields: { ownership: { stack: "shop", env: "prod" }, environments: ["prod", { name: "staging" }] },
114
+ });
115
+ writeFileSync(join(dir, "chant.config.ts"), `export const ownership = { stack: "named" };`);
116
+ expect(readConfigFieldsStatically(dir, ["ownership"])).toMatchObject({ status: "read", fields: { ownership: { stack: "named" } } });
117
+ });
118
+
119
+ test("a value it can't fold is unknown, with the reason, and the config is not run", () => {
120
+ const marker = join(dir, "ran");
121
+ writeFileSync(
122
+ join(dir, "chant.config.ts"),
123
+ `import { writeFileSync } from "node:fs";\nwriteFileSync(${JSON.stringify(marker)}, "x");\nexport default { ownership: { stack: process.env.STACK } };`,
124
+ );
125
+ const read = readConfigFieldsStatically(dir, ["ownership"]);
126
+ expect(read.status).toBe("unknown");
127
+ expect(existsSync(marker)).toBe(false);
128
+ });
129
+
130
+ test("reads chant.config.json, and only in the directory itself", () => {
131
+ writeFileSync(join(dir, "chant.config.json"), JSON.stringify({ environments: ["prod"] }));
132
+ expect(readConfigFieldsStatically(dir, ["environments"])).toMatchObject({ status: "read", fields: { environments: ["prod"] } });
133
+ mkdirSync(join(dir, "sub"));
134
+ expect(readConfigFieldsStatically(join(dir, "sub"), ["environments"])).toEqual({ status: "no-config" });
135
+ });
136
+ });