@intentius/chant 0.79.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 (462) hide show
  1. package/dist/audit/core.d.ts.map +1 -1
  2. package/dist/audit/discover.d.ts +30 -0
  3. package/dist/audit/discover.d.ts.map +1 -1
  4. package/dist/audit/report-html.d.ts +5 -0
  5. package/dist/audit/report-html.d.ts.map +1 -1
  6. package/dist/audit/report-model.d.ts +13 -0
  7. package/dist/audit/report-model.d.ts.map +1 -1
  8. package/dist/audit/terraform-state.d.ts +13 -0
  9. package/dist/audit/terraform-state.d.ts.map +1 -1
  10. package/dist/cli/command-group.d.ts +6 -0
  11. package/dist/cli/command-group.d.ts.map +1 -1
  12. package/dist/cli/commands/audit.d.ts +44 -3
  13. package/dist/cli/commands/audit.d.ts.map +1 -1
  14. package/dist/cli/commands/check-lexicon-examples.d.ts.map +1 -1
  15. package/dist/cli/commands/check-lexicon.d.ts +8 -0
  16. package/dist/cli/commands/check-lexicon.d.ts.map +1 -1
  17. package/dist/cli/commands/doctor.d.ts.map +1 -1
  18. package/dist/cli/commands/import-agents.d.ts.map +1 -1
  19. package/dist/cli/commands/init.d.ts +6 -0
  20. package/dist/cli/commands/init.d.ts.map +1 -1
  21. package/dist/cli/commands/lint.d.ts.map +1 -1
  22. package/dist/cli/commands/onboard.d.ts.map +1 -1
  23. package/dist/cli/commands/update.d.ts +6 -0
  24. package/dist/cli/commands/update.d.ts.map +1 -1
  25. package/dist/cli/commands/vendor.d.ts +5 -0
  26. package/dist/cli/commands/vendor.d.ts.map +1 -1
  27. package/dist/cli/conflict-check.d.ts +6 -0
  28. package/dist/cli/conflict-check.d.ts.map +1 -1
  29. package/dist/cli/handlers/build.d.ts.map +1 -1
  30. package/dist/cli/handlers/components.d.ts.map +1 -1
  31. package/dist/cli/handlers/dev.d.ts.map +1 -1
  32. package/dist/cli/handlers/fan-out.d.ts.map +1 -1
  33. package/dist/cli/handlers/init.d.ts.map +1 -1
  34. package/dist/cli/handlers/lifecycle.d.ts.map +1 -1
  35. package/dist/cli/handlers/misc.d.ts.map +1 -1
  36. package/dist/cli/handlers/operator.d.ts +7 -0
  37. package/dist/cli/handlers/operator.d.ts.map +1 -1
  38. package/dist/cli/handlers/promote.d.ts +20 -0
  39. package/dist/cli/handlers/promote.d.ts.map +1 -0
  40. package/dist/cli/handlers/run-generate.d.ts +39 -0
  41. package/dist/cli/handlers/run-generate.d.ts.map +1 -0
  42. package/dist/cli/handlers/run.d.ts +4 -1
  43. package/dist/cli/handlers/run.d.ts.map +1 -1
  44. package/dist/cli/handlers/vendor.d.ts +8 -3
  45. package/dist/cli/handlers/vendor.d.ts.map +1 -1
  46. package/dist/cli/main.d.ts +12 -0
  47. package/dist/cli/main.d.ts.map +1 -1
  48. package/dist/cli/mcp/op-tools.d.ts.map +1 -1
  49. package/dist/cli/plugins.d.ts +14 -0
  50. package/dist/cli/plugins.d.ts.map +1 -1
  51. package/dist/cli/registry.d.ts +46 -1
  52. package/dist/cli/registry.d.ts.map +1 -1
  53. package/dist/codegen/fetch.d.ts +10 -0
  54. package/dist/codegen/fetch.d.ts.map +1 -1
  55. package/dist/components/capability-plugin-loader.d.ts.map +1 -1
  56. package/dist/components/cli-support.d.ts +3 -1
  57. package/dist/components/cli-support.d.ts.map +1 -1
  58. package/dist/components/discover.d.ts.map +1 -1
  59. package/dist/components/driver.d.ts +16 -2
  60. package/dist/components/driver.d.ts.map +1 -1
  61. package/dist/components/fan-out-support.d.ts.map +1 -1
  62. package/dist/components/gate-plan.d.ts +40 -0
  63. package/dist/components/gate-plan.d.ts.map +1 -0
  64. package/dist/components/pilots/alb-ecs.pilot.d.ts +7 -1
  65. package/dist/components/pilots/alb-ecs.pilot.d.ts.map +1 -1
  66. package/dist/components/presets/ecs-fargate.d.ts +10 -4
  67. package/dist/components/presets/ecs-fargate.d.ts.map +1 -1
  68. package/dist/components/promote.d.ts +264 -0
  69. package/dist/components/promote.d.ts.map +1 -0
  70. package/dist/components/sandbox/driver.d.ts.map +1 -1
  71. package/dist/components/sandbox/run.d.ts.map +1 -1
  72. package/dist/components/verbs/build-archive.d.ts +22 -11
  73. package/dist/components/verbs/build-archive.d.ts.map +1 -1
  74. package/dist/components/verbs/run-agent.d.ts.map +1 -1
  75. package/dist/config-static.d.ts +59 -0
  76. package/dist/config-static.d.ts.map +1 -0
  77. package/dist/config.d.ts +104 -5
  78. package/dist/config.d.ts.map +1 -1
  79. package/dist/content-digest.d.ts +17 -0
  80. package/dist/content-digest.d.ts.map +1 -0
  81. package/dist/discovery/files.d.ts +48 -4
  82. package/dist/discovery/files.d.ts.map +1 -1
  83. package/dist/discovery/fold-import.d.ts +32 -1
  84. package/dist/discovery/fold-import.d.ts.map +1 -1
  85. package/dist/discovery/index.d.ts.map +1 -1
  86. package/dist/discovery/sandbox/driver.d.ts +20 -0
  87. package/dist/discovery/sandbox/driver.d.ts.map +1 -1
  88. package/dist/discovery/sandbox/fork.d.ts +20 -0
  89. package/dist/discovery/sandbox/fork.d.ts.map +1 -1
  90. package/dist/discovery/walk.d.ts +119 -0
  91. package/dist/discovery/walk.d.ts.map +1 -0
  92. package/dist/graph-ir.d.ts +17 -1
  93. package/dist/graph-ir.d.ts.map +1 -1
  94. package/dist/join-key.d.ts +31 -0
  95. package/dist/join-key.d.ts.map +1 -0
  96. package/dist/lexicon-module.d.ts +110 -0
  97. package/dist/lexicon-module.d.ts.map +1 -0
  98. package/dist/lexicon.d.ts +82 -1
  99. package/dist/lexicon.d.ts.map +1 -1
  100. package/dist/lifecycle/build-ledger-store.d.ts.map +1 -1
  101. package/dist/lifecycle/digest.d.ts +5 -1
  102. package/dist/lifecycle/digest.d.ts.map +1 -1
  103. package/dist/lifecycle/gate-ledger.d.ts +20 -5
  104. package/dist/lifecycle/gate-ledger.d.ts.map +1 -1
  105. package/dist/lifecycle/git.d.ts +64 -0
  106. package/dist/lifecycle/git.d.ts.map +1 -1
  107. package/dist/lifecycle/lease.d.ts +8 -1
  108. package/dist/lifecycle/lease.d.ts.map +1 -1
  109. package/dist/lifecycle/legacy-digest.d.ts +59 -0
  110. package/dist/lifecycle/legacy-digest.d.ts.map +1 -0
  111. package/dist/lifecycle/member-ledger.d.ts +67 -0
  112. package/dist/lifecycle/member-ledger.d.ts.map +1 -0
  113. package/dist/lifecycle/plan-digest.d.ts +4 -3
  114. package/dist/lifecycle/plan-digest.d.ts.map +1 -1
  115. package/dist/lifecycle/release-ledger.d.ts +42 -1
  116. package/dist/lifecycle/release-ledger.d.ts.map +1 -1
  117. package/dist/lifecycle/status.d.ts +90 -5
  118. package/dist/lifecycle/status.d.ts.map +1 -1
  119. package/dist/lint/rules/comp/comp003-mutating-no-rollback.d.ts +2 -3
  120. package/dist/lint/rules/comp/comp003-mutating-no-rollback.d.ts.map +1 -1
  121. package/dist/op/activities/activity-contracts.d.ts +32 -0
  122. package/dist/op/activities/activity-contracts.d.ts.map +1 -1
  123. package/dist/op/activities/index.d.ts +2 -0
  124. package/dist/op/activities/index.d.ts.map +1 -1
  125. package/dist/op/activities/propose-upgrade.d.ts +75 -0
  126. package/dist/op/activities/propose-upgrade.d.ts.map +1 -0
  127. package/dist/op/activity-contract-registry.d.ts.map +1 -1
  128. package/dist/op/activity-registry.d.ts.map +1 -1
  129. package/dist/op/discover.d.ts.map +1 -1
  130. package/dist/op/gate-approval.d.ts.map +1 -1
  131. package/dist/op/gate-name.d.ts +7 -0
  132. package/dist/op/gate-name.d.ts.map +1 -1
  133. package/dist/op/gate-summary.d.ts +16 -0
  134. package/dist/op/gate-summary.d.ts.map +1 -1
  135. package/dist/op/gate.d.ts +16 -2
  136. package/dist/op/gate.d.ts.map +1 -1
  137. package/dist/op/generate-pipeline.d.ts.map +1 -1
  138. package/dist/op/runtimes/local.d.ts.map +1 -1
  139. package/dist/project-root.d.ts +26 -0
  140. package/dist/project-root.d.ts.map +1 -1
  141. package/dist/workspace/__fixtures__/contract-repo.d.ts +37 -0
  142. package/dist/workspace/__fixtures__/contract-repo.d.ts.map +1 -0
  143. package/dist/workspace/__fixtures__/two-member-workspace.d.ts +11 -0
  144. package/dist/workspace/__fixtures__/two-member-workspace.d.ts.map +1 -0
  145. package/dist/workspace/checks/generated.d.ts +156 -0
  146. package/dist/workspace/checks/generated.d.ts.map +1 -0
  147. package/dist/workspace/checks/ledgers.d.ts +78 -0
  148. package/dist/workspace/checks/ledgers.d.ts.map +1 -0
  149. package/dist/workspace/checks/links.d.ts +25 -0
  150. package/dist/workspace/checks/links.d.ts.map +1 -0
  151. package/dist/workspace/checks/pipelines.d.ts +76 -0
  152. package/dist/workspace/checks/pipelines.d.ts.map +1 -0
  153. package/dist/workspace/checks.d.ts +161 -0
  154. package/dist/workspace/checks.d.ts.map +1 -0
  155. package/dist/workspace/compose-graph.d.ts +115 -0
  156. package/dist/workspace/compose-graph.d.ts.map +1 -0
  157. package/dist/workspace/compose-reports.d.ts +68 -0
  158. package/dist/workspace/compose-reports.d.ts.map +1 -0
  159. package/dist/workspace/declaration.d.ts +220 -0
  160. package/dist/workspace/declaration.d.ts.map +1 -0
  161. package/dist/workspace/declaration.schema.json +434 -0
  162. package/dist/workspace/generated-files.d.ts +65 -0
  163. package/dist/workspace/generated-files.d.ts.map +1 -0
  164. package/dist/workspace/glob.d.ts +13 -0
  165. package/dist/workspace/glob.d.ts.map +1 -0
  166. package/dist/workspace/graph-cli.d.ts +75 -0
  167. package/dist/workspace/graph-cli.d.ts.map +1 -0
  168. package/dist/workspace/init.d.ts +96 -0
  169. package/dist/workspace/init.d.ts.map +1 -0
  170. package/dist/workspace/jsonc.d.ts +38 -0
  171. package/dist/workspace/jsonc.d.ts.map +1 -0
  172. package/dist/workspace/kinds.d.ts +192 -0
  173. package/dist/workspace/kinds.d.ts.map +1 -0
  174. package/dist/workspace/lineage-check.d.ts +84 -0
  175. package/dist/workspace/lineage-check.d.ts.map +1 -0
  176. package/dist/workspace/lineage-cli.d.ts +31 -0
  177. package/dist/workspace/lineage-cli.d.ts.map +1 -0
  178. package/dist/workspace/lineage-init.d.ts +101 -0
  179. package/dist/workspace/lineage-init.d.ts.map +1 -0
  180. package/dist/workspace/lineage-lock.d.ts +258 -0
  181. package/dist/workspace/lineage-lock.d.ts.map +1 -0
  182. package/dist/workspace/lineage-merge.d.ts +14 -0
  183. package/dist/workspace/lineage-merge.d.ts.map +1 -0
  184. package/dist/workspace/lineage-migrations.d.ts +178 -0
  185. package/dist/workspace/lineage-migrations.d.ts.map +1 -0
  186. package/dist/workspace/lineage-update.d.ts +59 -0
  187. package/dist/workspace/lineage-update.d.ts.map +1 -0
  188. package/dist/workspace/lineage-upgrade-cli.d.ts +40 -0
  189. package/dist/workspace/lineage-upgrade-cli.d.ts.map +1 -0
  190. package/dist/workspace/lineage-upgrade.d.ts +146 -0
  191. package/dist/workspace/lineage-upgrade.d.ts.map +1 -0
  192. package/dist/workspace/lineage-vendor.d.ts +63 -0
  193. package/dist/workspace/lineage-vendor.d.ts.map +1 -0
  194. package/dist/workspace/lineage-version.d.ts +32 -0
  195. package/dist/workspace/lineage-version.d.ts.map +1 -0
  196. package/dist/workspace/links.d.ts +150 -0
  197. package/dist/workspace/links.d.ts.map +1 -0
  198. package/dist/workspace/ls.d.ts +99 -0
  199. package/dist/workspace/ls.d.ts.map +1 -0
  200. package/dist/workspace/member-commands.d.ts +139 -0
  201. package/dist/workspace/member-commands.d.ts.map +1 -0
  202. package/dist/workspace/member-handles.d.ts +54 -0
  203. package/dist/workspace/member-handles.d.ts.map +1 -0
  204. package/dist/workspace/member-pipeline.d.ts +143 -0
  205. package/dist/workspace/member-pipeline.d.ts.map +1 -0
  206. package/dist/workspace/member-run.d.ts +67 -0
  207. package/dist/workspace/member-run.d.ts.map +1 -0
  208. package/dist/workspace/reason-codes.d.ts +65 -0
  209. package/dist/workspace/reason-codes.d.ts.map +1 -0
  210. package/dist/workspace/record-source.d.ts +22 -0
  211. package/dist/workspace/record-source.d.ts.map +1 -0
  212. package/dist/workspace/records-cli.d.ts +81 -0
  213. package/dist/workspace/records-cli.d.ts.map +1 -0
  214. package/dist/workspace/records.d.ts +118 -0
  215. package/dist/workspace/records.d.ts.map +1 -0
  216. package/dist/workspace/root-refusal.d.ts +53 -0
  217. package/dist/workspace/root-refusal.d.ts.map +1 -0
  218. package/dist/workspace/status.d.ts +150 -0
  219. package/dist/workspace/status.d.ts.map +1 -0
  220. package/dist/workspace/template-manifest.d.ts +90 -0
  221. package/dist/workspace/template-manifest.d.ts.map +1 -0
  222. package/dist/workspace/tree.d.ts +36 -0
  223. package/dist/workspace/tree.d.ts.map +1 -0
  224. package/dist/workspace/trust/attestor.d.ts +67 -0
  225. package/dist/workspace/trust/attestor.d.ts.map +1 -0
  226. package/dist/workspace/trust/policy.d.ts +111 -0
  227. package/dist/workspace/trust/policy.d.ts.map +1 -0
  228. package/dist/workspace/trust/provenance.d.ts +67 -0
  229. package/dist/workspace/trust/provenance.d.ts.map +1 -0
  230. package/dist/workspace/trust/ssh-commit.d.ts +43 -0
  231. package/dist/workspace/trust/ssh-commit.d.ts.map +1 -0
  232. package/dist/workspace/trust/test-repo.d.ts +42 -0
  233. package/dist/workspace/trust/test-repo.d.ts.map +1 -0
  234. package/dist/workspace/trust/verify-cli.d.ts +9 -0
  235. package/dist/workspace/trust/verify-cli.d.ts.map +1 -0
  236. package/dist/workspace/trust/verify.d.ts +66 -0
  237. package/dist/workspace/trust/verify.d.ts.map +1 -0
  238. package/dist/workspace/which-chant.d.ts +58 -0
  239. package/dist/workspace/which-chant.d.ts.map +1 -0
  240. package/dist/workspace/workspace-kinds.schema.json +133 -0
  241. package/package.json +4 -3
  242. package/src/audit/core.ts +13 -2
  243. package/src/audit/discover.ts +102 -36
  244. package/src/audit/report-html.ts +3 -1
  245. package/src/audit/report-model.ts +14 -1
  246. package/src/audit/terraform-state.test.ts +81 -1
  247. package/src/audit/terraform-state.ts +25 -0
  248. package/src/cli/command-group.ts +7 -0
  249. package/src/cli/commands/audit-truncation.test.ts +167 -0
  250. package/src/cli/commands/audit.ts +132 -29
  251. package/src/cli/commands/check-lexicon-examples.ts +2 -1
  252. package/src/cli/commands/check-lexicon.ts +24 -0
  253. package/src/cli/commands/doctor.test.ts +30 -0
  254. package/src/cli/commands/doctor.ts +21 -0
  255. package/src/cli/commands/import-agents.ts +15 -4
  256. package/src/cli/commands/init.ts +76 -25
  257. package/src/cli/commands/lint.test.ts +52 -1
  258. package/src/cli/commands/lint.ts +41 -71
  259. package/src/cli/commands/onboard.ts +15 -0
  260. package/src/cli/commands/update.ts +18 -5
  261. package/src/cli/commands/vendor.ts +11 -6
  262. package/src/cli/conflict-check.test.ts +18 -1
  263. package/src/cli/conflict-check.ts +13 -0
  264. package/src/cli/discovery-skip.test.ts +168 -0
  265. package/src/cli/handlers/__snapshots__/build-member.test.ts.snap +38 -0
  266. package/src/cli/handlers/build-member.test.ts +127 -0
  267. package/src/cli/handlers/build.test.ts +11 -0
  268. package/src/cli/handlers/build.ts +35 -1
  269. package/src/cli/handlers/components.test.ts +127 -0
  270. package/src/cli/handlers/components.ts +70 -8
  271. package/src/cli/handlers/dev.ts +6 -1
  272. package/src/cli/handlers/fan-out.ts +9 -2
  273. package/src/cli/handlers/graph.test.ts +20 -0
  274. package/src/cli/handlers/graph.ts +4 -2
  275. package/src/cli/handlers/init.ts +54 -1
  276. package/src/cli/handlers/lifecycle.ts +2 -1
  277. package/src/cli/handlers/misc.ts +16 -0
  278. package/src/cli/handlers/operator.test.ts +68 -0
  279. package/src/cli/handlers/operator.ts +39 -10
  280. package/src/cli/handlers/promote.test.ts +377 -0
  281. package/src/cli/handlers/promote.ts +391 -0
  282. package/src/cli/handlers/run-generate-member.test.ts +83 -0
  283. package/src/cli/handlers/run-generate.test.ts +158 -0
  284. package/src/cli/handlers/run-generate.ts +168 -0
  285. package/src/cli/handlers/run.test.ts +47 -0
  286. package/src/cli/handlers/run.ts +41 -6
  287. package/src/cli/handlers/vendor.ts +147 -43
  288. package/src/cli/main.test.ts +137 -1
  289. package/src/cli/main.ts +290 -24
  290. package/src/cli/mcp/op-tools.ts +2 -1
  291. package/src/cli/path-lexicon-messages.test.ts +152 -0
  292. package/src/cli/plugins.ts +43 -5
  293. package/src/cli/registry.ts +46 -1
  294. package/src/cli/static-config-read.test.ts +208 -0
  295. package/src/codegen/fetch.test.ts +63 -1
  296. package/src/codegen/fetch.ts +30 -1
  297. package/src/components/__fixtures__/alb-ecs-service.json +0 -8
  298. package/src/components/capability-plugin-loader.ts +3 -1
  299. package/src/components/cli-support.ts +9 -3
  300. package/src/components/discover.test.ts +27 -57
  301. package/src/components/discover.ts +20 -51
  302. package/src/components/driver.test.ts +90 -12
  303. package/src/components/driver.ts +43 -5
  304. package/src/components/fan-out-support.ts +2 -1
  305. package/src/components/gate-plan.ts +53 -0
  306. package/src/components/pilots/README.md +1 -1
  307. package/src/components/pilots/alb-ecs.pilot.ts +7 -7
  308. package/src/components/presets/ecs-fargate.ts +10 -5
  309. package/src/components/promote.test.ts +645 -0
  310. package/src/components/promote.ts +641 -0
  311. package/src/components/sandbox/driver.ts +8 -8
  312. package/src/components/sandbox/run.ts +4 -1
  313. package/src/components/verbs/build-archive.ts +27 -21
  314. package/src/components/verbs/run-agent.ts +3 -5
  315. package/src/config-static.test.ts +136 -0
  316. package/src/config-static.ts +232 -0
  317. package/src/config.test.ts +40 -0
  318. package/src/config.ts +161 -11
  319. package/src/content-digest.test.ts +120 -0
  320. package/src/content-digest.ts +21 -0
  321. package/src/discovery/files.test.ts +127 -1
  322. package/src/discovery/files.ts +91 -59
  323. package/src/discovery/fold-import.ts +127 -9
  324. package/src/discovery/index.ts +1 -0
  325. package/src/discovery/sandbox/driver.ts +73 -35
  326. package/src/discovery/sandbox/fork-diagnostic.test.ts +136 -13
  327. package/src/discovery/sandbox/fork.ts +83 -43
  328. package/src/discovery/sandbox/path-lexicon-multifile.test.ts +285 -0
  329. package/src/discovery/sandbox/path-lexicon-parity.test.ts +162 -0
  330. package/src/discovery/walk.test.ts +263 -0
  331. package/src/discovery/walk.ts +334 -0
  332. package/src/effect-receipt.ts +2 -2
  333. package/src/graph-ir.ts +18 -1
  334. package/src/join-key.test.ts +25 -0
  335. package/src/join-key.ts +39 -0
  336. package/src/lexicon-module.test.ts +203 -0
  337. package/src/lexicon-module.ts +215 -0
  338. package/src/lexicon.ts +105 -1
  339. package/src/lifecycle/build-ledger-store.ts +13 -2
  340. package/src/lifecycle/digest.ts +9 -5
  341. package/src/lifecycle/gate-ledger.test.ts +20 -0
  342. package/src/lifecycle/gate-ledger.ts +28 -6
  343. package/src/lifecycle/git.test.ts +13 -1
  344. package/src/lifecycle/git.ts +221 -176
  345. package/src/lifecycle/lease.ts +25 -10
  346. package/src/lifecycle/legacy-digest.test.ts +206 -0
  347. package/src/lifecycle/legacy-digest.ts +103 -0
  348. package/src/lifecycle/member-ledger.test.ts +319 -0
  349. package/src/lifecycle/member-ledger.ts +145 -0
  350. package/src/lifecycle/plan-digest.ts +7 -7
  351. package/src/lifecycle/release-ledger.test.ts +20 -0
  352. package/src/lifecycle/release-ledger.ts +53 -3
  353. package/src/lifecycle/status.test.ts +151 -1
  354. package/src/lifecycle/status.ts +228 -7
  355. package/src/lint/rules/comp/comp003-mutating-no-rollback.ts +2 -3
  356. package/src/op/activities/activity-contracts.ts +37 -0
  357. package/src/op/activities/index.ts +10 -0
  358. package/src/op/activities/propose-upgrade.ts +206 -0
  359. package/src/op/activity-contract-registry.ts +18 -4
  360. package/src/op/activity-registry.ts +26 -0
  361. package/src/op/discover.test.ts +14 -4
  362. package/src/op/discover.ts +19 -23
  363. package/src/op/gate-approval.ts +3 -1
  364. package/src/op/gate-name.ts +8 -0
  365. package/src/op/gate-summary.test.ts +14 -0
  366. package/src/op/gate-summary.ts +25 -2
  367. package/src/op/gate.ts +36 -7
  368. package/src/op/generate-pipeline.test.ts +14 -5
  369. package/src/op/generate-pipeline.ts +2 -1
  370. package/src/op/runtimes/local.ts +2 -1
  371. package/src/project-root.test.ts +66 -1
  372. package/src/project-root.ts +48 -0
  373. package/src/workspace/__fixtures__/contract-repo.ts +100 -0
  374. package/src/workspace/__fixtures__/two-member-workspace.ts +35 -0
  375. package/src/workspace/behold-kinds.test.ts +162 -0
  376. package/src/workspace/check-contract.test.ts +127 -0
  377. package/src/workspace/check.schema.json +347 -0
  378. package/src/workspace/checks/generated.test.ts +287 -0
  379. package/src/workspace/checks/generated.ts +475 -0
  380. package/src/workspace/checks/ledgers.test.ts +139 -0
  381. package/src/workspace/checks/ledgers.ts +246 -0
  382. package/src/workspace/checks/links.ts +183 -0
  383. package/src/workspace/checks/pipelines.test.ts +133 -0
  384. package/src/workspace/checks/pipelines.ts +225 -0
  385. package/src/workspace/checks-repo.test.ts +71 -0
  386. package/src/workspace/checks.test.ts +450 -0
  387. package/src/workspace/checks.ts +461 -0
  388. package/src/workspace/compose-graph.test.ts +146 -0
  389. package/src/workspace/compose-graph.ts +228 -0
  390. package/src/workspace/compose-reports.test.ts +109 -0
  391. package/src/workspace/compose-reports.ts +186 -0
  392. package/src/workspace/declaration.schema.json +434 -0
  393. package/src/workspace/declaration.test.ts +347 -0
  394. package/src/workspace/declaration.ts +623 -0
  395. package/src/workspace/generated-files.test.ts +114 -0
  396. package/src/workspace/generated-files.ts +94 -0
  397. package/src/workspace/glob.ts +49 -0
  398. package/src/workspace/graph-cli.ts +220 -0
  399. package/src/workspace/graph-contract.test.ts +204 -0
  400. package/src/workspace/graph.schema.json +662 -0
  401. package/src/workspace/init.test.ts +223 -0
  402. package/src/workspace/init.ts +585 -0
  403. package/src/workspace/jsonc.test.ts +65 -0
  404. package/src/workspace/jsonc.ts +231 -0
  405. package/src/workspace/kinds.test.ts +288 -0
  406. package/src/workspace/kinds.ts +506 -0
  407. package/src/workspace/lineage-check.ts +264 -0
  408. package/src/workspace/lineage-cli.ts +114 -0
  409. package/src/workspace/lineage-init.test.ts +231 -0
  410. package/src/workspace/lineage-init.ts +365 -0
  411. package/src/workspace/lineage-lock.test.ts +224 -0
  412. package/src/workspace/lineage-lock.ts +357 -0
  413. package/src/workspace/lineage-merge.ts +44 -0
  414. package/src/workspace/lineage-migrations.test.ts +178 -0
  415. package/src/workspace/lineage-migrations.ts +343 -0
  416. package/src/workspace/lineage-update.ts +160 -0
  417. package/src/workspace/lineage-upgrade-cli.ts +151 -0
  418. package/src/workspace/lineage-upgrade.test.ts +474 -0
  419. package/src/workspace/lineage-upgrade.ts +624 -0
  420. package/src/workspace/lineage-vendor.test.ts +126 -0
  421. package/src/workspace/lineage-vendor.ts +215 -0
  422. package/src/workspace/lineage-version.ts +127 -0
  423. package/src/workspace/links.test.ts +344 -0
  424. package/src/workspace/links.ts +331 -0
  425. package/src/workspace/ls-contract.test.ts +200 -0
  426. package/src/workspace/ls.schema.json +190 -0
  427. package/src/workspace/ls.ts +254 -0
  428. package/src/workspace/member-commands.e2e.test.ts +244 -0
  429. package/src/workspace/member-commands.test.ts +217 -0
  430. package/src/workspace/member-commands.ts +540 -0
  431. package/src/workspace/member-handles.test.ts +76 -0
  432. package/src/workspace/member-handles.ts +228 -0
  433. package/src/workspace/member-pipeline.test.ts +135 -0
  434. package/src/workspace/member-pipeline.ts +317 -0
  435. package/src/workspace/member-run.ts +146 -0
  436. package/src/workspace/read-contract.test.ts +129 -0
  437. package/src/workspace/reason-codes.test.ts +113 -0
  438. package/src/workspace/reason-codes.ts +81 -0
  439. package/src/workspace/record-source.ts +117 -0
  440. package/src/workspace/records-cli.ts +198 -0
  441. package/src/workspace/records-contract.test.ts +93 -0
  442. package/src/workspace/records.schema.json +150 -0
  443. package/src/workspace/records.test.ts +243 -0
  444. package/src/workspace/records.ts +384 -0
  445. package/src/workspace/root-refusal.ts +97 -0
  446. package/src/workspace/status-contract.test.ts +308 -0
  447. package/src/workspace/status.schema.json +210 -0
  448. package/src/workspace/status.ts +412 -0
  449. package/src/workspace/template-manifest.test.ts +79 -0
  450. package/src/workspace/template-manifest.ts +221 -0
  451. package/src/workspace/tree.ts +132 -0
  452. package/src/workspace/trust/attestor.ts +93 -0
  453. package/src/workspace/trust/policy.ts +306 -0
  454. package/src/workspace/trust/provenance.ts +181 -0
  455. package/src/workspace/trust/ssh-commit.ts +138 -0
  456. package/src/workspace/trust/test-repo.ts +138 -0
  457. package/src/workspace/trust/trust.test.ts +397 -0
  458. package/src/workspace/trust/verify-cli.ts +56 -0
  459. package/src/workspace/trust/verify.ts +194 -0
  460. package/src/workspace/which-chant.test.ts +117 -0
  461. package/src/workspace/which-chant.ts +151 -0
  462. package/src/workspace/workspace-kinds.schema.json +133 -0
@@ -0,0 +1,412 @@
1
+ /**
2
+ * `chant workspace status <env> [--compare-to <env>] [--json]` (#2544): a
3
+ * read-only view of each member's latest release in an environment, and
4
+ * optionally a second environment beside it (#2524 D19, ws-043).
5
+ *
6
+ * Releases stay per member: a release is an artifact digest plus a git SHA in
7
+ * the member's own ledger on the `chant/lifecycle` branch. Member ledgers
8
+ * live under `_members/<member>/<env>/releases.jsonl` (#2524 D7, #2538), and
9
+ * the root member `.` keeps the flat `<env>/releases.jsonl`. A member whose
10
+ * `_members/<member>/` directory doesn't exist yet is read from the flat
11
+ * ledger, which is where a chant from before #2538, or below the floor,
12
+ * still writes it. Several members read that way share one ledger, and the
13
+ * output says so.
14
+ *
15
+ * The command reads the local `chant/lifecycle` branch and never fetches it.
16
+ * The output names the branch tip it read, so a stale copy is visible.
17
+ *
18
+ * A member whose ledger can't be read is still listed, with a reason code,
19
+ * and the command still exits 0 (#2524 D15, ws-020), as `ls` does. Only a
20
+ * declaration that can't be read, or an environment name that can't be one,
21
+ * exits 1. The `--json` output is part of the read contract and is described
22
+ * by `status.schema.json` beside this file.
23
+ */
24
+
25
+ import { execFileSync } from "node:child_process";
26
+ import { existsSync, realpathSync } from "node:fs";
27
+ import { relative, resolve } from "node:path";
28
+ import { formatError } from "../cli/format";
29
+ import type { CommandContext } from "../cli/registry";
30
+ import { readPathSha } from "../lifecycle/git";
31
+ import { latestPerComponent, readReleaseLedger, type ReleaseRecord } from "../lifecycle/release-ledger";
32
+ import { findWorkspaceRoot } from "../project-root";
33
+ import { readDeclaration, readerVersion, WorkspaceReadError, type ErrorLocation, type Member } from "./declaration";
34
+ import type { ReasonCode } from "./reason-codes";
35
+ import { gitTop, workingTree } from "./tree";
36
+ import { handToRootChant } from "./which-chant";
37
+
38
+ /** The version of the `status` output this chant writes. */
39
+ export const STATUS_CONTRACT_VERSION = 1;
40
+
41
+ /** `$id` of the JSON Schema for the `--json` output, shipped beside this file. */
42
+ export const STATUS_OUTPUT_SCHEMA_ID = "https://intentius.io/chant/schemas/workspace/status/v1/status.schema.json";
43
+
44
+ /** The branch every ledger lives on. */
45
+ export const LIFECYCLE_REF = "chant/lifecycle";
46
+
47
+ /** The directory on {@link LIFECYCLE_REF} that holds member ledgers (#2538). */
48
+ export const MEMBERS_DIR = "_members";
49
+
50
+ const USAGE = "chant workspace status <env> [dir] [--compare-to <env>] [--json]";
51
+
52
+ /**
53
+ * Why a member's ledger for one environment can't be fully read. Closed: a
54
+ * new code is a contract change.
55
+ */
56
+ export const STATUS_REASON_CODES = [
57
+ /** Reading the ledger failed, so nothing from it is listed. */
58
+ "ledger-unreadable",
59
+ /** Some lines of the ledger aren't release records; the rest are listed. */
60
+ "ledger-malformed",
61
+ ] as const satisfies readonly ReasonCode[];
62
+ export type StatusReasonCode = (typeof STATUS_REASON_CODES)[number];
63
+
64
+ /**
65
+ * Why the status couldn't be read at all. The declaration's own codes, except
66
+ * the two that only `--at` returns, and one for the environment name.
67
+ */
68
+ export const STATUS_ERROR_CODES = [
69
+ "declaration-missing",
70
+ "declaration-ambiguous",
71
+ "declaration-unparseable",
72
+ "declaration-invalid",
73
+ "placement-invalid",
74
+ "reader-too-old",
75
+ "root-chant-required",
76
+ /** The workspace isn't in a git repository, so it has no ledger branch. */
77
+ "not-a-git-repository",
78
+ /** An environment name that can't name a ledger directory. */
79
+ "environment-invalid",
80
+ ] as const satisfies readonly ReasonCode[];
81
+ export type StatusErrorCode = (typeof STATUS_ERROR_CODES)[number];
82
+
83
+ /** Environment names a ledger directory can have: no `/`, no leading `_` or `.` (those are chant's own directories). */
84
+ const ENV_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]{0,99}$/;
85
+
86
+ export interface StatusRelease {
87
+ component: string;
88
+ digest: string;
89
+ gitSha: string;
90
+ /** The input-side digest, when `digest` is a rendered one (a pinned helm deploy). Compare joins on it. */
91
+ inputDigest: string | null;
92
+ runId: string;
93
+ timestamp: string;
94
+ actor: string;
95
+ /** Read flags, such as `legacy-digest` (#2514). */
96
+ flags: string[];
97
+ }
98
+
99
+ export interface StatusLedger {
100
+ /** `members` for `_members/<member>/<env>/releases.jsonl`, `flat` for `<env>/releases.jsonl`. */
101
+ layout: "members" | "flat";
102
+ /** The ledger's path on the branch. */
103
+ path: string;
104
+ /** True when more than one member in this output reads this same flat ledger. */
105
+ shared: boolean;
106
+ }
107
+
108
+ export interface StatusEnvironment {
109
+ env: string;
110
+ ledger: StatusLedger;
111
+ /** The latest release of each component in the ledger, sorted by component. */
112
+ releases: StatusRelease[];
113
+ reason: { code: StatusReasonCode; message: string } | null;
114
+ }
115
+
116
+ export type CompareState = "same" | "differs" | "only-env" | "only-compare";
117
+
118
+ export interface StatusCompare {
119
+ /** True when any component is not `same`. */
120
+ differs: boolean;
121
+ components: { component: string; state: CompareState; digest: string | null; compareDigest: string | null }[];
122
+ }
123
+
124
+ export interface StatusMember {
125
+ name: string;
126
+ dir: string;
127
+ kind: string;
128
+ /** The environment asked for first, then the one compared to, if any. */
129
+ environments: StatusEnvironment[];
130
+ /** Null without `--compare-to`. */
131
+ compare: StatusCompare | null;
132
+ readable: boolean;
133
+ }
134
+
135
+ export type StatusDocument =
136
+ | {
137
+ $schema: string;
138
+ contract: number;
139
+ chant: string;
140
+ env: string;
141
+ compareTo: string | null;
142
+ lifecycle: { ref: string; commit: string | null };
143
+ workspace: { name: string; root: string; file: string };
144
+ members: StatusMember[];
145
+ summary: { members: number; released: number; unreadable: number; differing: number | null };
146
+ }
147
+ | {
148
+ $schema: string;
149
+ contract: number;
150
+ chant: string;
151
+ error: { code: StatusErrorCode; message: string; location: ErrorLocation | null };
152
+ };
153
+
154
+ type LedgerReader = (path: string, cwd: string) => Promise<{ records: ReleaseRecord[]; malformed: number }>;
155
+
156
+ export interface StatusQuery {
157
+ /** Where the walk up to the declaration starts. */
158
+ cwd: string;
159
+ env: string;
160
+ compareTo?: string;
161
+ /** Reads one ledger; the lifecycle reader unless a test swaps it. */
162
+ readLedger?: LedgerReader;
163
+ }
164
+
165
+ class StatusError extends Error {
166
+ constructor(
167
+ readonly code: StatusErrorCode,
168
+ message: string,
169
+ ) {
170
+ super(message);
171
+ }
172
+ }
173
+
174
+ function checkEnv(env: string, flag: string): void {
175
+ if (!ENV_PATTERN.test(env) || env.includes("..")) {
176
+ throw new StatusError(
177
+ "environment-invalid",
178
+ `${flag}${JSON.stringify(env)} can't be an environment: use letters, digits, ".", "_" and "-", starting with a letter or digit`,
179
+ );
180
+ }
181
+ }
182
+
183
+ function lifecycleTip(cwd: string): string | null {
184
+ try {
185
+ return execFileSync("git", ["rev-parse", "--verify", "--quiet", `refs/heads/${LIFECYCLE_REF}`], {
186
+ cwd,
187
+ encoding: "utf-8",
188
+ stdio: ["ignore", "pipe", "pipe"],
189
+ }).trim() || null;
190
+ } catch {
191
+ return null;
192
+ }
193
+ }
194
+
195
+ /** Which ledger a member's releases for `env` are in (#2524 D7). */
196
+ async function ledgerFor(member: Member, env: string, cwd: string): Promise<Omit<StatusLedger, "shared">> {
197
+ if (member.dir !== "." && (await readPathSha(MEMBERS_DIR, member.name, { cwd })) !== null) {
198
+ return { layout: "members", path: `${MEMBERS_DIR}/${member.name}/${env}/releases.jsonl` };
199
+ }
200
+ return { layout: "flat", path: `${env}/releases.jsonl` };
201
+ }
202
+
203
+ const defaultReader: LedgerReader = (path, cwd) => readReleaseLedger(path.slice(0, -"/releases.jsonl".length), { cwd });
204
+
205
+ async function readEnvironment(member: Member, env: string, cwd: string, read: LedgerReader): Promise<StatusEnvironment> {
206
+ const ledger = await ledgerFor(member, env, cwd);
207
+ let records: ReleaseRecord[];
208
+ let malformed: number;
209
+ try {
210
+ ({ records, malformed } = await read(ledger.path, cwd));
211
+ } catch (err) {
212
+ return {
213
+ env,
214
+ ledger: { ...ledger, shared: false },
215
+ releases: [],
216
+ reason: { code: "ledger-unreadable", message: `${ledger.path}: ${err instanceof Error ? err.message.split("\n")[0] : String(err)}` },
217
+ };
218
+ }
219
+ const releases = [...latestPerComponent(records).values()]
220
+ .map((r) => ({
221
+ component: r.component,
222
+ digest: r.digest,
223
+ gitSha: r.gitSha,
224
+ inputDigest: r.inputDigest ?? null,
225
+ runId: r.runId,
226
+ timestamp: r.timestamp,
227
+ actor: r.actor,
228
+ flags: [...(r.flags ?? [])],
229
+ }))
230
+ .sort((a, b) => (a.component < b.component ? -1 : a.component > b.component ? 1 : 0));
231
+ return {
232
+ env,
233
+ ledger: { ...ledger, shared: false },
234
+ releases,
235
+ reason:
236
+ malformed > 0
237
+ ? { code: "ledger-malformed", message: `${ledger.path}: ${malformed} line${malformed === 1 ? " is" : "s are"} not a release record and ${malformed === 1 ? "was" : "were"} skipped` }
238
+ : null,
239
+ };
240
+ }
241
+
242
+ /** The digest two environments are compared on: the input side when there is one. */
243
+ const compareKey = (r: StatusRelease): string => r.inputDigest ?? r.digest;
244
+
245
+ export function compareEnvironments(a: StatusEnvironment, b: StatusEnvironment): StatusCompare {
246
+ const left = new Map(a.releases.map((r) => [r.component, r]));
247
+ const right = new Map(b.releases.map((r) => [r.component, r]));
248
+ const names = [...new Set([...left.keys(), ...right.keys()])].sort();
249
+ const components = names.map((component) => {
250
+ const l = left.get(component);
251
+ const r = right.get(component);
252
+ const state: CompareState = !r ? "only-env" : !l ? "only-compare" : compareKey(l) === compareKey(r) ? "same" : "differs";
253
+ return { component, state, digest: l?.digest ?? null, compareDigest: r?.digest ?? null };
254
+ });
255
+ return { differs: components.some((c) => c.state !== "same"), components };
256
+ }
257
+
258
+ /** Find the workspace, read every member's ledger and build the document `--json` prints. */
259
+ export async function workspaceStatus(query: StatusQuery): Promise<StatusDocument> {
260
+ const chant = readerVersion();
261
+ const head = { $schema: STATUS_OUTPUT_SCHEMA_ID, contract: STATUS_CONTRACT_VERSION, chant };
262
+ try {
263
+ checkEnv(query.env, "");
264
+ if (query.compareTo !== undefined) checkEnv(query.compareTo, "--compare-to ");
265
+ const found = findWorkspaceRoot(query.cwd);
266
+ if (!found) {
267
+ throw new WorkspaceReadError(
268
+ "declaration-missing",
269
+ "no chant.workspace.json or .jsonc between this directory and the git root; chant workspace init proposes one",
270
+ );
271
+ }
272
+ const declaration = readDeclaration(workingTree(found.dir), "", { rootChant: true });
273
+ const top = gitTop(found.dir);
274
+ if (!top) throw new StatusError("not-a-git-repository", `${found.dir} is not in a git repository, so it has no ${LIFECYCLE_REF} branch to read`);
275
+ const rootDir = relative(top, realpathSync(found.dir)).split("\\").join("/");
276
+ const read = query.readLedger ?? defaultReader;
277
+ const envs = query.compareTo !== undefined && query.compareTo !== query.env ? [query.env, query.compareTo] : [query.env];
278
+
279
+ const members: StatusMember[] = [];
280
+ for (const m of declaration.members) {
281
+ const environments: StatusEnvironment[] = [];
282
+ for (const env of envs) environments.push(await readEnvironment(m, env, found.dir, read));
283
+ members.push({
284
+ name: m.name,
285
+ dir: m.dir,
286
+ kind: m.kind,
287
+ environments,
288
+ compare: query.compareTo === undefined ? null : compareEnvironments(environments[0], environments[environments.length - 1]),
289
+ readable: environments.every((e) => e.reason === null),
290
+ });
291
+ }
292
+ // Several members read from one flat ledger see the same records; say so.
293
+ const readers = new Map<string, number>();
294
+ for (const m of members) for (const e of m.environments) if (e.ledger.layout === "flat") readers.set(e.ledger.path, (readers.get(e.ledger.path) ?? 0) + 1);
295
+ for (const m of members) for (const e of m.environments) e.ledger.shared = e.ledger.layout === "flat" && (readers.get(e.ledger.path) ?? 0) > 1;
296
+
297
+ return {
298
+ ...head,
299
+ env: query.env,
300
+ compareTo: query.compareTo ?? null,
301
+ lifecycle: { ref: LIFECYCLE_REF, commit: lifecycleTip(found.dir) },
302
+ workspace: { name: declaration.name, root: rootDir === "" ? "." : rootDir, file: declaration.file },
303
+ members,
304
+ summary: {
305
+ members: members.length,
306
+ released: members.filter((m) => m.environments[0].releases.length > 0).length,
307
+ unreadable: members.filter((m) => !m.readable).length,
308
+ differing: query.compareTo === undefined ? null : members.filter((m) => m.compare!.differs).length,
309
+ },
310
+ };
311
+ } catch (err) {
312
+ if (err instanceof WorkspaceReadError) {
313
+ // Without --at, the two --at codes can't come back; anything else is the declaration's.
314
+ return { ...head, error: { code: err.code as StatusErrorCode, message: err.message, location: err.location ?? null } };
315
+ }
316
+ if (err instanceof StatusError) return { ...head, error: { code: err.code, message: err.message, location: null } };
317
+ throw err;
318
+ }
319
+ }
320
+
321
+ export async function runWorkspaceStatus(ctx: CommandContext): Promise<number> {
322
+ const { args } = ctx;
323
+ const env = args.extraPositional;
324
+ if (!env) {
325
+ console.error(formatError({ message: "chant workspace status needs an environment", hint: USAGE }));
326
+ return 1;
327
+ }
328
+ if (args.compareTo === "--live" || args.live) {
329
+ console.error(
330
+ formatError({
331
+ message: "chant workspace status doesn't read live state yet",
332
+ hint: "Run chant components status <env> --live in a member's directory for that member's live state. The workspace view reads the ledgers only.",
333
+ }),
334
+ );
335
+ return 1;
336
+ }
337
+ if (args.compareTo !== undefined && (args.compareTo === "" || args.compareTo.startsWith("-"))) {
338
+ console.error(formatError({ message: "--compare-to needs an environment", hint: USAGE }));
339
+ return 1;
340
+ }
341
+ const cwd = resolve(args.extraPositional2 ?? ".");
342
+ if (!existsSync(cwd)) {
343
+ console.error(formatError({ message: `${cwd} does not exist`, hint: USAGE }));
344
+ return 1;
345
+ }
346
+ // The root's chant reads the declaration (ws-021).
347
+ const handed = await handToRootChant(cwd, undefined);
348
+ if (handed !== undefined) return handed;
349
+ const doc = await workspaceStatus({ cwd, env, compareTo: args.compareTo });
350
+ if (args.json) {
351
+ console.log(JSON.stringify(doc, null, 2));
352
+ } else if ("error" in doc) {
353
+ const l = doc.error.location;
354
+ const where = l ? `${l.file}:${l.line}:${l.column}: ` : "";
355
+ console.error(formatError({ message: `${doc.error.code}: ${where}${doc.error.message}`, hint: USAGE }));
356
+ } else {
357
+ console.log(formatStatus(doc));
358
+ }
359
+ return "error" in doc ? 1 : 0;
360
+ }
361
+
362
+ function table(rows: string[][]): string[] {
363
+ const widths = rows[0].map((_, c) => Math.max(...rows.map((r) => r[c].length)));
364
+ return rows.map((r) => r.map((cell, c) => (c === r.length - 1 ? cell : cell.padEnd(widths[c]))).join(" ").trimEnd());
365
+ }
366
+
367
+ /** `sha256:0123456789ab 1a2b3c4d`: enough of the digest and the SHA to tell releases apart. */
368
+ function shortRelease(r: StatusRelease | undefined): string {
369
+ if (!r) return "-";
370
+ const colon = r.digest.indexOf(":");
371
+ const digest = colon < 0 ? r.digest.slice(0, 12) : r.digest.slice(0, colon + 13);
372
+ return `${digest} ${r.gitSha.slice(0, 8)}`;
373
+ }
374
+
375
+ export function formatStatus(doc: Extract<StatusDocument, { members: unknown }>): string {
376
+ const lines: string[] = [];
377
+ const envs = doc.members[0]?.environments.map((e) => e.env) ?? [doc.env];
378
+ const title = doc.compareTo !== null && doc.compareTo !== doc.env ? `${doc.env} compared to ${doc.compareTo}` : doc.env;
379
+ lines.push(`${doc.workspace.name} ${title} (${doc.lifecycle.ref} ${doc.lifecycle.commit ? `at ${doc.lifecycle.commit.slice(0, 8)}` : "is not in this checkout"})`);
380
+ if (doc.members.length > 0) {
381
+ lines.push("");
382
+ const rows: string[][] = [["MEMBER", "COMPONENT", ...envs.map((e) => e.toUpperCase()), ...(doc.compareTo !== null ? [""] : [])]];
383
+ const notes = new Map<number, string[]>();
384
+ for (const m of doc.members) {
385
+ const byEnv = m.environments.map((e) => new Map(e.releases.map((r) => [r.component, r])));
386
+ const components = [...new Set(m.environments.flatMap((e) => e.releases.map((r) => r.component)))].sort();
387
+ if (components.length === 0) rows.push([m.name, "-", ...envs.map(() => "no release"), ...(doc.compareTo !== null ? [""] : [])]);
388
+ for (const c of components) {
389
+ const state = m.compare?.components.find((x) => x.component === c)?.state;
390
+ rows.push([m.name, c, ...byEnv.map((b) => shortRelease(b.get(c))), ...(doc.compareTo !== null ? [state && state !== "same" ? state : ""] : [])]);
391
+ }
392
+ const memberNotes: string[] = [];
393
+ for (const e of m.environments) {
394
+ if (e.reason) memberNotes.push(` ${e.reason.code}: ${e.reason.message}`);
395
+ else if (e.ledger.shared && e.releases.length > 0) memberNotes.push(` ${e.env} is read from the shared flat ledger ${e.ledger.path}`);
396
+ }
397
+ if (memberNotes.length > 0) notes.set(rows.length - 1, memberNotes);
398
+ }
399
+ table(rows).forEach((line, i) => {
400
+ lines.push(line);
401
+ for (const n of notes.get(i) ?? []) lines.push(n);
402
+ });
403
+ }
404
+ const s = doc.summary;
405
+ lines.push("");
406
+ lines.push(
407
+ `${s.members} member${s.members === 1 ? "" : "s"}, ${s.released} with a release in ${doc.env}` +
408
+ (s.differing !== null ? `, ${s.differing} differ${s.differing === 1 ? "s" : ""} from ${doc.compareTo}` : "") +
409
+ `, ${s.unreadable} unreadable`,
410
+ );
411
+ return lines.join("\n");
412
+ }
@@ -0,0 +1,79 @@
1
+ import { describe, expect, test } from "vitest";
2
+ import { TEMPLATE_MANIFEST, carryParameters, parseParamArgs, readManifest, resolveParameters, substituteParameters } from "./template-manifest";
3
+
4
+ function tree(files: Record<string, string>): Map<string, Buffer> {
5
+ return new Map(Object.entries(files).map(([k, v]) => [k, Buffer.from(v)]));
6
+ }
7
+ const manifest = (m: unknown): Map<string, Buffer> => tree({ [TEMPLATE_MANIFEST]: JSON.stringify(m) });
8
+
9
+ describe("chant.template.json", () => {
10
+ test("a template without one declares no parameters", () => {
11
+ expect(readManifest(tree({ "a.ts": "x" }))).toBeNull();
12
+ expect(resolveParameters(null, {})).toEqual({});
13
+ expect(() => resolveParameters(null, { name: "x" })).toThrow("unknown parameter name (the template declares none)");
14
+ });
15
+
16
+ test("refuses bad JSON, unknown keys, bad names, bad paths and a default that fails its pattern", () => {
17
+ expect(() => readManifest(tree({ [TEMPLATE_MANIFEST]: "{" }))).toThrow(/not valid JSON/);
18
+ expect(() => readManifest(manifest({ parameters: {}, files: [], extra: 1 }))).toThrow(/invalid chant.template.json/);
19
+ expect(() => readManifest(manifest({ parameters: { "a-b": { type: "string" } }, files: [] }))).toThrow(/parameter name/);
20
+ expect(() => readManifest(manifest({ parameters: { n: { type: "number" } }, files: [] }))).toThrow(/parameters.n.type/);
21
+ expect(() => readManifest(manifest({ parameters: {}, files: ["../x"] }))).toThrow(/not a normalised path/);
22
+ expect(() => readManifest(manifest({ parameters: {}, files: [".chant/migrations/m.json"] }))).toThrow(/not a normalised path/);
23
+ expect(() => readManifest(manifest({ parameters: { n: { type: "string", default: "a b", pattern: "^\\S+$" } }, files: [] }))).toThrow(
24
+ /its default "a b" does not match/,
25
+ );
26
+ });
27
+
28
+ test("resolves given values and defaults; refuses undeclared names, listing the declared ones", () => {
29
+ const m = readManifest(
30
+ manifest({
31
+ parameters: {
32
+ name: { type: "string", default: "Starter", pattern: "^[A-Za-z ]+$" },
33
+ owner: { type: "string" },
34
+ url: { type: "string", default: "http://localhost", hostBound: true },
35
+ },
36
+ files: [],
37
+ }),
38
+ );
39
+ expect(resolveParameters(m, { owner: "me" })).toEqual({ name: "Starter", owner: "me", url: "http://localhost" });
40
+ expect(resolveParameters(m, { owner: "me", name: "Untitled app" }).name).toBe("Untitled app");
41
+ expect(() => resolveParameters(m, { owner: "me", titel: "x" })).toThrow("unknown parameter titel (declared: name, owner, url)");
42
+ expect(() => resolveParameters(m, {})).toThrow("parameter owner has no default; pass --param owner=<value>");
43
+ expect(() => resolveParameters(m, { owner: "me", name: 'x"; rm' })).toThrow(/does not match/);
44
+ });
45
+
46
+ test("substitutes {{chant:<name>}} in listed files only, and drops the manifest", () => {
47
+ const m = readManifest(manifest({ parameters: { name: { type: "string" } }, files: ["a.ts"] }));
48
+ const files = tree({
49
+ [TEMPLATE_MANIFEST]: "{}",
50
+ "a.ts": 'const t = "{{chant:name}}"; const u = `${t}`; const g = "${{ github.sha }}"; const h = "{{name}}";\n',
51
+ "b.ts": 'const t = "{{chant:name}}";\n',
52
+ });
53
+ const out = substituteParameters(files, m, { name: "Acme" });
54
+ expect(out.has(TEMPLATE_MANIFEST)).toBe(false);
55
+ expect(out.get("a.ts")!.toString()).toBe('const t = "Acme"; const u = `${t}`; const g = "${{ github.sha }}"; const h = "{{name}}";\n');
56
+ expect(out.get("b.ts")!.toString()).toBe('const t = "{{chant:name}}";\n');
57
+ // Values are inserted once: a value that looks like a placeholder stays as written.
58
+ expect(substituteParameters(files, m, { name: "{{chant:name}}" }).get("a.ts")!.toString()).toContain('"{{chant:name}}"');
59
+ });
60
+
61
+ test("refuses a listed file that is missing and a placeholder with no declaration", () => {
62
+ const m = readManifest(manifest({ parameters: { name: { type: "string" } }, files: ["a.ts"] }));
63
+ expect(() => substituteParameters(tree({}), m, { name: "x" })).toThrow("chant.template.json lists a.ts, which is not in the template");
64
+ expect(() => substituteParameters(tree({ "a.ts": "{{chant:owner}}" }), m, { name: "x" })).toThrow(/declares no parameter owner/);
65
+ });
66
+
67
+ test("--param arguments split at the first =, and a repeated name is refused", () => {
68
+ expect(parseParamArgs(["name=Untitled app", "expr=a=b", "empty="])).toEqual({ name: "Untitled app", expr: "a=b", empty: "" });
69
+ expect(() => parseParamArgs(["name"])).toThrow(/expected <name>=<value>/);
70
+ expect(() => parseParamArgs(["=x"])).toThrow(/expected <name>=<value>/);
71
+ expect(() => parseParamArgs(["a=1", "a=2"])).toThrow(/given twice/);
72
+ });
73
+
74
+ test("an upgrade carries recorded values, adds defaults and drops undeclared names", () => {
75
+ const m = readManifest(manifest({ parameters: { name: { type: "string" }, owner: { type: "string", default: "platform" } }, files: [] }));
76
+ expect(carryParameters(m, { name: "Acme", gone: "x" })).toEqual({ name: "Acme", owner: "platform" });
77
+ expect(carryParameters(null, { name: "Acme" })).toEqual({});
78
+ });
79
+ });