@intentius/chant 0.124.27 → 0.124.29

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 (681) hide show
  1. package/README.md +1 -1
  2. package/dist/__golden__/wave-loop/fan-out-gated.json +8 -0
  3. package/dist/__golden__/wave-loop/fan-out-no-members.json +4 -0
  4. package/dist/__golden__/wave-loop/fan-out-only.json +6 -0
  5. package/dist/__golden__/wave-loop/op-by-apply-hold.json +2 -0
  6. package/dist/__golden__/wave-loop/op-cost-member.json +3 -0
  7. package/dist/__golden__/wave-loop/op-held-on-before-apply.json +1 -0
  8. package/dist/__golden__/wave-loop/op-held-on.json +2 -0
  9. package/dist/__golden__/wave-loop/op-hooks-held.json +2 -0
  10. package/dist/__golden__/wave-loop/op-hooks-hold-before-apply.json +2 -0
  11. package/dist/__golden__/wave-loop/op-hooks-refused-at-base.json +3 -0
  12. package/dist/__golden__/wave-loop/op-hooks-share.json +3 -0
  13. package/dist/__golden__/wave-loop/op-per-run.json +7 -0
  14. package/dist/__golden__/wave-loop/op-pr-review.json +7 -0
  15. package/dist/__golden__/wave-loop/op-sealed.json +3 -0
  16. package/dist/__golden__/wave-loop/op-spend.json +3 -0
  17. package/dist/__golden__/wave-loop/op-wave-commands.json +7 -0
  18. package/dist/__golden__/wave-loop/op-wide-waiting.json +4 -0
  19. package/dist/build-params.d.ts +34 -1
  20. package/dist/build-params.d.ts.map +1 -1
  21. package/dist/build-params.js +31 -1
  22. package/dist/change-set-graph.d.ts +63 -0
  23. package/dist/change-set-graph.d.ts.map +1 -0
  24. package/dist/change-set-graph.js +130 -0
  25. package/dist/change-set.d.ts +62 -3
  26. package/dist/change-set.d.ts.map +1 -1
  27. package/dist/change-set.js +36 -4
  28. package/dist/chant-version.d.ts +9 -0
  29. package/dist/chant-version.d.ts.map +1 -0
  30. package/dist/chant-version.js +16 -0
  31. package/dist/cli/build-params-cli.d.ts +7 -1
  32. package/dist/cli/build-params-cli.d.ts.map +1 -1
  33. package/dist/cli/build-params-cli.js +17 -8
  34. package/dist/cli/commands/build.d.ts +8 -0
  35. package/dist/cli/commands/build.d.ts.map +1 -1
  36. package/dist/cli/commands/build.js +15 -11
  37. package/dist/cli/commands/check-lexicon-docs.d.ts.map +1 -1
  38. package/dist/cli/commands/check-lexicon-docs.js +30 -2
  39. package/dist/cli/commands/check-lexicon.d.ts.map +1 -1
  40. package/dist/cli/commands/check-lexicon.js +13 -2
  41. package/dist/cli/commands/init-lexicon/templates/docs.d.ts +4 -1
  42. package/dist/cli/commands/init-lexicon/templates/docs.d.ts.map +1 -1
  43. package/dist/cli/commands/init-lexicon/templates/docs.js +6 -6
  44. package/dist/cli/commands/init-lexicon.d.ts.map +1 -1
  45. package/dist/cli/commands/init-lexicon.js +17 -8
  46. package/dist/cli/handlers/approve.d.ts.map +1 -1
  47. package/dist/cli/handlers/approve.js +64 -4
  48. package/dist/cli/handlers/build.d.ts.map +1 -1
  49. package/dist/cli/handlers/build.js +43 -4
  50. package/dist/cli/handlers/change-set.d.ts +10 -0
  51. package/dist/cli/handlers/change-set.d.ts.map +1 -1
  52. package/dist/cli/handlers/change-set.js +51 -1
  53. package/dist/cli/handlers/fan-out.d.ts +10 -1
  54. package/dist/cli/handlers/fan-out.d.ts.map +1 -1
  55. package/dist/cli/handlers/fan-out.js +54 -6
  56. package/dist/cli/handlers/lifecycle-gc.d.ts +19 -0
  57. package/dist/cli/handlers/lifecycle-gc.d.ts.map +1 -0
  58. package/dist/cli/handlers/lifecycle-gc.js +143 -0
  59. package/dist/cli/handlers/pr.d.ts +16 -2
  60. package/dist/cli/handlers/pr.d.ts.map +1 -1
  61. package/dist/cli/handlers/pr.js +269 -37
  62. package/dist/cli/handlers/promote.d.ts +7 -0
  63. package/dist/cli/handlers/promote.d.ts.map +1 -1
  64. package/dist/cli/handlers/promote.js +440 -28
  65. package/dist/cli/handlers/release.d.ts +91 -0
  66. package/dist/cli/handlers/release.d.ts.map +1 -0
  67. package/dist/cli/handlers/release.js +589 -0
  68. package/dist/cli/handlers/run.d.ts +38 -2
  69. package/dist/cli/handlers/run.d.ts.map +1 -1
  70. package/dist/cli/handlers/run.js +234 -16
  71. package/dist/cli/main.d.ts.map +1 -1
  72. package/dist/cli/main.js +143 -17
  73. package/dist/cli/mcp/workspace-tools.js +1 -1
  74. package/dist/cli/parse-args.d.ts.map +1 -1
  75. package/dist/cli/parse-args.js +73 -5
  76. package/dist/cli/registry.d.ts +43 -5
  77. package/dist/cli/registry.d.ts.map +1 -1
  78. package/dist/cli/run-events.d.ts +30 -0
  79. package/dist/cli/run-events.d.ts.map +1 -0
  80. package/dist/cli/run-events.js +49 -0
  81. package/dist/cli/version.d.ts +3 -5
  82. package/dist/cli/version.d.ts.map +1 -1
  83. package/dist/cli/version.js +3 -13
  84. package/dist/codegen/docs-in-site.d.ts +71 -0
  85. package/dist/codegen/docs-in-site.d.ts.map +1 -0
  86. package/dist/codegen/docs-in-site.js +177 -0
  87. package/dist/codegen/docs-types.d.ts +14 -1
  88. package/dist/codegen/docs-types.d.ts.map +1 -1
  89. package/dist/codegen/docs.d.ts +18 -1
  90. package/dist/codegen/docs.d.ts.map +1 -1
  91. package/dist/codegen/docs.js +40 -7
  92. package/dist/codegen/rehype-base-url.mjs +15 -9
  93. package/dist/components/auto-release.d.ts +6 -1
  94. package/dist/components/auto-release.d.ts.map +1 -1
  95. package/dist/components/auto-release.js +3 -0
  96. package/dist/components/capability.d.ts +9 -0
  97. package/dist/components/capability.d.ts.map +1 -1
  98. package/dist/components/cli-support.d.ts +6 -0
  99. package/dist/components/cli-support.d.ts.map +1 -1
  100. package/dist/components/cli-support.js +10 -9
  101. package/dist/components/driver.d.ts +50 -9
  102. package/dist/components/driver.d.ts.map +1 -1
  103. package/dist/components/driver.js +116 -54
  104. package/dist/components/fan-out-inputs.d.ts +69 -0
  105. package/dist/components/fan-out-inputs.d.ts.map +1 -0
  106. package/dist/components/fan-out-inputs.js +169 -0
  107. package/dist/components/fan-out-record.d.ts +13 -0
  108. package/dist/components/fan-out-record.d.ts.map +1 -1
  109. package/dist/components/fan-out-record.js +16 -0
  110. package/dist/components/fan-out-run.d.ts +7 -0
  111. package/dist/components/fan-out-run.d.ts.map +1 -1
  112. package/dist/components/fan-out-run.js +85 -26
  113. package/dist/components/fan-out.d.ts +4 -0
  114. package/dist/components/fan-out.d.ts.map +1 -1
  115. package/dist/components/pr-pipeline.d.ts +25 -0
  116. package/dist/components/pr-pipeline.d.ts.map +1 -1
  117. package/dist/components/pr-pipeline.js +22 -4
  118. package/dist/components/pr-run.d.ts +11 -0
  119. package/dist/components/pr-run.d.ts.map +1 -1
  120. package/dist/components/pr-run.js +20 -1
  121. package/dist/components/promote-order.d.ts +38 -0
  122. package/dist/components/promote-order.d.ts.map +1 -0
  123. package/dist/components/promote-order.js +62 -0
  124. package/dist/components/promote.d.ts +38 -2
  125. package/dist/components/promote.d.ts.map +1 -1
  126. package/dist/components/promote.js +77 -2
  127. package/dist/components/release-compile.d.ts +142 -0
  128. package/dist/components/release-compile.d.ts.map +1 -0
  129. package/dist/components/release-compile.js +382 -0
  130. package/dist/components/release-gate.d.ts +135 -0
  131. package/dist/components/release-gate.d.ts.map +1 -0
  132. package/dist/components/release-gate.js +288 -0
  133. package/dist/components/release-promote.d.ts +96 -0
  134. package/dist/components/release-promote.d.ts.map +1 -0
  135. package/dist/components/release-promote.js +204 -0
  136. package/dist/components/run-progress.d.ts +7 -0
  137. package/dist/components/run-progress.d.ts.map +1 -1
  138. package/dist/components/wave-plan.d.ts +8 -1
  139. package/dist/components/wave-plan.d.ts.map +1 -1
  140. package/dist/components/wave-plan.js +11 -3
  141. package/dist/config.d.ts +45 -0
  142. package/dist/config.d.ts.map +1 -1
  143. package/dist/config.js +3 -0
  144. package/dist/flag-params.d.ts +54 -0
  145. package/dist/flag-params.d.ts.map +1 -0
  146. package/dist/flag-params.js +130 -0
  147. package/dist/index.d.ts +1 -0
  148. package/dist/index.d.ts.map +1 -1
  149. package/dist/index.js +2 -0
  150. package/dist/lifecycle/__fixtures__/publish-fakes.d.ts +120 -0
  151. package/dist/lifecycle/__fixtures__/publish-fakes.d.ts.map +1 -0
  152. package/dist/lifecycle/__fixtures__/publish-fakes.js +304 -0
  153. package/dist/lifecycle/change-set-store.d.ts +68 -0
  154. package/dist/lifecycle/change-set-store.d.ts.map +1 -0
  155. package/dist/lifecycle/change-set-store.js +97 -0
  156. package/dist/lifecycle/commit-sha.d.ts +13 -0
  157. package/dist/lifecycle/commit-sha.d.ts.map +1 -0
  158. package/dist/lifecycle/commit-sha.js +21 -0
  159. package/dist/lifecycle/delivery-metrics.d.ts +139 -0
  160. package/dist/lifecycle/delivery-metrics.d.ts.map +1 -0
  161. package/dist/lifecycle/delivery-metrics.js +145 -0
  162. package/dist/lifecycle/deploy-outcome.d.ts +81 -0
  163. package/dist/lifecycle/deploy-outcome.d.ts.map +1 -0
  164. package/dist/lifecycle/deploy-outcome.js +101 -0
  165. package/dist/lifecycle/gate-change-set.d.ts +78 -0
  166. package/dist/lifecycle/gate-change-set.d.ts.map +1 -0
  167. package/dist/lifecycle/gate-change-set.js +138 -0
  168. package/dist/lifecycle/gate-ledger.d.ts +51 -4
  169. package/dist/lifecycle/gate-ledger.d.ts.map +1 -1
  170. package/dist/lifecycle/gate-ledger.js +18 -0
  171. package/dist/lifecycle/git.d.ts +30 -1
  172. package/dist/lifecycle/git.d.ts.map +1 -1
  173. package/dist/lifecycle/git.js +31 -5
  174. package/dist/lifecycle/index.d.ts +4 -0
  175. package/dist/lifecycle/index.d.ts.map +1 -1
  176. package/dist/lifecycle/index.js +4 -0
  177. package/dist/lifecycle/mixed-environment.d.ts +95 -0
  178. package/dist/lifecycle/mixed-environment.d.ts.map +1 -0
  179. package/dist/lifecycle/mixed-environment.js +154 -0
  180. package/dist/lifecycle/publish-attachments.d.ts +79 -0
  181. package/dist/lifecycle/publish-attachments.d.ts.map +1 -0
  182. package/dist/lifecycle/publish-attachments.js +179 -0
  183. package/dist/lifecycle/publish-credentials.d.ts +37 -0
  184. package/dist/lifecycle/publish-credentials.d.ts.map +1 -0
  185. package/dist/lifecycle/publish-credentials.js +69 -0
  186. package/dist/lifecycle/publish-declaration.d.ts +97 -0
  187. package/dist/lifecycle/publish-declaration.d.ts.map +1 -0
  188. package/dist/lifecycle/publish-declaration.js +83 -0
  189. package/dist/lifecycle/publish-forge.d.ts +53 -0
  190. package/dist/lifecycle/publish-forge.d.ts.map +1 -0
  191. package/dist/lifecycle/publish-forge.js +298 -0
  192. package/dist/lifecycle/publish-npm.d.ts +61 -0
  193. package/dist/lifecycle/publish-npm.d.ts.map +1 -0
  194. package/dist/lifecycle/publish-npm.js +242 -0
  195. package/dist/lifecycle/publish-oci.d.ts +64 -0
  196. package/dist/lifecycle/publish-oci.d.ts.map +1 -0
  197. package/dist/lifecycle/publish-oci.js +383 -0
  198. package/dist/lifecycle/publish-record.d.ts +66 -0
  199. package/dist/lifecycle/publish-record.d.ts.map +1 -0
  200. package/dist/lifecycle/publish-record.js +84 -0
  201. package/dist/lifecycle/publish-targets.d.ts +45 -0
  202. package/dist/lifecycle/publish-targets.d.ts.map +1 -0
  203. package/dist/lifecycle/publish-targets.js +85 -0
  204. package/dist/lifecycle/release-cut.d.ts +154 -0
  205. package/dist/lifecycle/release-cut.d.ts.map +1 -0
  206. package/dist/lifecycle/release-cut.js +366 -0
  207. package/dist/lifecycle/release-join.d.ts +95 -0
  208. package/dist/lifecycle/release-join.d.ts.map +1 -0
  209. package/dist/lifecycle/release-join.js +139 -0
  210. package/dist/lifecycle/release-ledger.d.ts +54 -0
  211. package/dist/lifecycle/release-ledger.d.ts.map +1 -1
  212. package/dist/lifecycle/release-ledger.js +53 -20
  213. package/dist/lifecycle/release-publish.d.ts +312 -0
  214. package/dist/lifecycle/release-publish.d.ts.map +1 -0
  215. package/dist/lifecycle/release-publish.js +335 -0
  216. package/dist/lifecycle/release-revocation.d.ts +80 -0
  217. package/dist/lifecycle/release-revocation.d.ts.map +1 -0
  218. package/dist/lifecycle/release-revocation.js +123 -0
  219. package/dist/lifecycle/release-store.d.ts +200 -0
  220. package/dist/lifecycle/release-store.d.ts.map +1 -0
  221. package/dist/lifecycle/release-store.js +320 -0
  222. package/dist/lifecycle/release-verify.d.ts +61 -0
  223. package/dist/lifecycle/release-verify.d.ts.map +1 -0
  224. package/dist/lifecycle/release-verify.js +92 -0
  225. package/dist/lifecycle/release-workstation.d.ts +91 -0
  226. package/dist/lifecycle/release-workstation.d.ts.map +1 -0
  227. package/dist/lifecycle/release-workstation.js +180 -0
  228. package/dist/lifecycle/retention-policy.d.ts +45 -0
  229. package/dist/lifecycle/retention-policy.d.ts.map +1 -0
  230. package/dist/lifecycle/retention-policy.js +43 -0
  231. package/dist/lifecycle/retention.d.ts +185 -0
  232. package/dist/lifecycle/retention.d.ts.map +1 -0
  233. package/dist/lifecycle/retention.js +400 -0
  234. package/dist/lifecycle/run-ledger.d.ts +4 -0
  235. package/dist/lifecycle/run-ledger.d.ts.map +1 -1
  236. package/dist/lifecycle/run-ledger.js +2 -0
  237. package/dist/op/activities/change-coverage.d.ts.map +1 -1
  238. package/dist/op/activities/change-coverage.js +4 -2
  239. package/dist/op/activities/source-release.d.ts.map +1 -1
  240. package/dist/op/activities/source-release.js +7 -3
  241. package/dist/op/activities/source-rollback.d.ts.map +1 -1
  242. package/dist/op/activities/source-rollback.js +7 -3
  243. package/dist/op/gate-name.d.ts +11 -2
  244. package/dist/op/gate-name.d.ts.map +1 -1
  245. package/dist/op/gate-name.js +12 -3
  246. package/dist/op/gate-run-locator.d.ts +22 -0
  247. package/dist/op/gate-run-locator.d.ts.map +1 -1
  248. package/dist/op/gate-run-locator.js +31 -0
  249. package/dist/op/gate.d.ts +27 -3
  250. package/dist/op/gate.d.ts.map +1 -1
  251. package/dist/op/gate.js +57 -3
  252. package/dist/op/local-executor.d.ts +36 -1
  253. package/dist/op/local-executor.d.ts.map +1 -1
  254. package/dist/op/local-executor.js +158 -52
  255. package/dist/op/op-waves-run.d.ts.map +1 -1
  256. package/dist/op/op-waves-run.js +4 -0
  257. package/dist/op/run-live.d.ts +2 -0
  258. package/dist/op/run-live.d.ts.map +1 -1
  259. package/dist/op/run-live.js +1 -1
  260. package/dist/op/runtime.d.ts +46 -0
  261. package/dist/op/runtime.d.ts.map +1 -1
  262. package/dist/op/runtimes/local.d.ts.map +1 -1
  263. package/dist/op/runtimes/local.js +8 -0
  264. package/dist/plan-summary.d.ts.map +1 -1
  265. package/dist/plan-summary.js +8 -2
  266. package/dist/point-decision.d.ts +2 -0
  267. package/dist/point-decision.d.ts.map +1 -1
  268. package/dist/point-decision.js +9 -2
  269. package/dist/pr-loop.d.ts +7 -0
  270. package/dist/pr-loop.d.ts.map +1 -1
  271. package/dist/pr-loop.js +23 -4
  272. package/dist/pr-report-url.d.ts +82 -0
  273. package/dist/pr-report-url.d.ts.map +1 -0
  274. package/dist/pr-report-url.js +174 -0
  275. package/dist/provenance.d.ts +2 -2
  276. package/dist/provenance.d.ts.map +1 -1
  277. package/dist/run-event-store.d.ts +104 -0
  278. package/dist/run-event-store.d.ts.map +1 -0
  279. package/dist/run-event-store.js +251 -0
  280. package/dist/run-event-stream.d.ts +133 -0
  281. package/dist/run-event-stream.d.ts.map +1 -0
  282. package/dist/run-event-stream.js +242 -0
  283. package/dist/run-event.d.ts +285 -0
  284. package/dist/run-event.d.ts.map +1 -0
  285. package/dist/run-event.js +209 -0
  286. package/dist/wave-runtime.mjs +262 -226
  287. package/dist/workspace/__fixtures__/flag-workspace.d.ts +31 -0
  288. package/dist/workspace/__fixtures__/flag-workspace.d.ts.map +1 -0
  289. package/dist/workspace/__fixtures__/flag-workspace.js +114 -0
  290. package/dist/workspace/__fixtures__/run-event-check.d.ts +19 -0
  291. package/dist/workspace/__fixtures__/run-event-check.d.ts.map +1 -0
  292. package/dist/workspace/__fixtures__/run-event-check.js +59 -0
  293. package/dist/workspace/change-set-show.d.ts +56 -0
  294. package/dist/workspace/change-set-show.d.ts.map +1 -0
  295. package/dist/workspace/change-set-show.js +54 -0
  296. package/dist/workspace/change-set-show.schema.json +102 -0
  297. package/dist/workspace/change-set.schema.json +32 -1
  298. package/dist/workspace/changes-cli.d.ts.map +1 -1
  299. package/dist/workspace/changes-cli.js +10 -1
  300. package/dist/workspace/changes.d.ts +37 -0
  301. package/dist/workspace/changes.d.ts.map +1 -1
  302. package/dist/workspace/changes.js +49 -2
  303. package/dist/workspace/changes.schema.json +135 -0
  304. package/dist/workspace/check.schema.json +4 -2
  305. package/dist/workspace/checks/records.d.ts +9 -1
  306. package/dist/workspace/checks/records.d.ts.map +1 -1
  307. package/dist/workspace/checks/records.js +35 -1
  308. package/dist/workspace/checks.d.ts +1 -0
  309. package/dist/workspace/checks.d.ts.map +1 -1
  310. package/dist/workspace/checks.js +2 -1
  311. package/dist/workspace/ci-green-cli.d.ts +19 -4
  312. package/dist/workspace/ci-green-cli.d.ts.map +1 -1
  313. package/dist/workspace/ci-green-cli.js +75 -12
  314. package/dist/workspace/ci-green-forge.d.ts +20 -3
  315. package/dist/workspace/ci-green-forge.d.ts.map +1 -1
  316. package/dist/workspace/ci-green-forge.js +6 -4
  317. package/dist/workspace/ci-green-workflow.d.ts +11 -0
  318. package/dist/workspace/ci-green-workflow.d.ts.map +1 -1
  319. package/dist/workspace/ci-green-workflow.js +6 -0
  320. package/dist/workspace/ci-green.d.ts +281 -13
  321. package/dist/workspace/ci-green.d.ts.map +1 -1
  322. package/dist/workspace/ci-green.js +456 -38
  323. package/dist/workspace/ci-last-green.schema.json +32 -2
  324. package/dist/workspace/ci-list.schema.json +525 -0
  325. package/dist/workspace/commit-links.d.ts +85 -0
  326. package/dist/workspace/commit-links.d.ts.map +1 -0
  327. package/dist/workspace/commit-links.js +163 -0
  328. package/dist/workspace/declaration.d.ts +35 -0
  329. package/dist/workspace/declaration.d.ts.map +1 -1
  330. package/dist/workspace/declaration.js +27 -1
  331. package/dist/workspace/declaration.schema.json +76 -0
  332. package/dist/workspace/declared-kinds.d.ts +16 -0
  333. package/dist/workspace/declared-kinds.d.ts.map +1 -1
  334. package/dist/workspace/declared-kinds.js +25 -0
  335. package/dist/workspace/delivery-metrics.d.ts +91 -0
  336. package/dist/workspace/delivery-metrics.d.ts.map +1 -0
  337. package/dist/workspace/delivery-metrics.js +217 -0
  338. package/dist/workspace/delivery-metrics.schema.json +519 -0
  339. package/dist/workspace/flags-read.d.ts +37 -0
  340. package/dist/workspace/flags-read.d.ts.map +1 -0
  341. package/dist/workspace/flags-read.js +103 -0
  342. package/dist/workspace/flags.d.ts +100 -0
  343. package/dist/workspace/flags.d.ts.map +1 -0
  344. package/dist/workspace/flags.js +98 -0
  345. package/dist/workspace/graph-cli.js +1 -1
  346. package/dist/workspace/identity-gate-rule.d.ts +37 -0
  347. package/dist/workspace/identity-gate-rule.d.ts.map +1 -1
  348. package/dist/workspace/identity-gate-rule.js +72 -7
  349. package/dist/workspace/identity.d.ts +12 -0
  350. package/dist/workspace/identity.d.ts.map +1 -1
  351. package/dist/workspace/identity.js +13 -1
  352. package/dist/workspace/intent-cli.d.ts +11 -2
  353. package/dist/workspace/intent-cli.d.ts.map +1 -1
  354. package/dist/workspace/intent-cli.js +54 -5
  355. package/dist/workspace/intent-trace.d.ts +219 -0
  356. package/dist/workspace/intent-trace.d.ts.map +1 -0
  357. package/dist/workspace/intent-trace.js +314 -0
  358. package/dist/workspace/intent.d.ts +36 -7
  359. package/dist/workspace/intent.d.ts.map +1 -1
  360. package/dist/workspace/intent.js +192 -22
  361. package/dist/workspace/intent.schema.json +702 -6
  362. package/dist/workspace/ls.d.ts +4 -0
  363. package/dist/workspace/ls.d.ts.map +1 -1
  364. package/dist/workspace/ls.js +2 -0
  365. package/dist/workspace/ls.schema.json +3 -1
  366. package/dist/workspace/member-pipeline.d.ts +6 -0
  367. package/dist/workspace/member-pipeline.d.ts.map +1 -1
  368. package/dist/workspace/member-pipeline.js +6 -0
  369. package/dist/workspace/overlap.d.ts +146 -0
  370. package/dist/workspace/overlap.d.ts.map +1 -0
  371. package/dist/workspace/overlap.js +276 -0
  372. package/dist/workspace/promotion-order.d.ts +41 -0
  373. package/dist/workspace/promotion-order.d.ts.map +1 -0
  374. package/dist/workspace/promotion-order.js +95 -0
  375. package/dist/workspace/reason-codes.d.ts +4 -0
  376. package/dist/workspace/reason-codes.d.ts.map +1 -1
  377. package/dist/workspace/reason-codes.js +7 -1
  378. package/dist/workspace/records.d.ts +15 -2
  379. package/dist/workspace/records.d.ts.map +1 -1
  380. package/dist/workspace/records.js +32 -0
  381. package/dist/workspace/records.schema.json +21 -2
  382. package/dist/workspace/reference-kinds/flag/flag.kind.mjs +32 -0
  383. package/dist/workspace/reference-kinds/flag/flag.schema.json +53 -0
  384. package/dist/workspace/releases.d.ts +81 -0
  385. package/dist/workspace/releases.d.ts.map +1 -0
  386. package/dist/workspace/releases.js +103 -0
  387. package/dist/workspace/releases.schema.json +513 -0
  388. package/dist/workspace/run-event.schema.json +295 -0
  389. package/dist/workspace/status-gates.d.ts +34 -1
  390. package/dist/workspace/status-gates.d.ts.map +1 -1
  391. package/dist/workspace/status-gates.js +31 -2
  392. package/dist/workspace/status-stewards.d.ts +4 -0
  393. package/dist/workspace/status-stewards.d.ts.map +1 -1
  394. package/dist/workspace/status-stewards.js +2 -0
  395. package/dist/workspace/status.d.ts +117 -0
  396. package/dist/workspace/status.d.ts.map +1 -1
  397. package/dist/workspace/status.js +218 -4
  398. package/dist/workspace/status.schema.json +268 -2
  399. package/dist/workspace/trust/ci-tag-statement.d.ts +102 -0
  400. package/dist/workspace/trust/ci-tag-statement.d.ts.map +1 -0
  401. package/dist/workspace/trust/ci-tag-statement.js +123 -0
  402. package/dist/workspace/trust/op-run-statement.d.ts +91 -0
  403. package/dist/workspace/trust/op-run-statement.d.ts.map +1 -0
  404. package/dist/workspace/trust/op-run-statement.js +143 -0
  405. package/dist/workspace/trust/rotation.d.ts +18 -2
  406. package/dist/workspace/trust/rotation.d.ts.map +1 -1
  407. package/dist/workspace/trust/rotation.js +65 -16
  408. package/dist/workspace/trust/seal.d.ts +11 -0
  409. package/dist/workspace/trust/seal.d.ts.map +1 -1
  410. package/dist/workspace/trust/seal.js +9 -0
  411. package/dist/workspace/trust/ssh-commit.d.ts.map +1 -1
  412. package/dist/workspace/trust/ssh-commit.js +10 -10
  413. package/dist/workspace/wip.d.ts.map +1 -1
  414. package/dist/workspace/wip.js +11 -3
  415. package/package.json +1 -1
  416. package/src/__golden__/wave-loop/fan-out-gated.json +8 -0
  417. package/src/__golden__/wave-loop/fan-out-no-members.json +4 -0
  418. package/src/__golden__/wave-loop/fan-out-only.json +6 -0
  419. package/src/__golden__/wave-loop/op-by-apply-hold.json +2 -0
  420. package/src/__golden__/wave-loop/op-cost-member.json +3 -0
  421. package/src/__golden__/wave-loop/op-held-on-before-apply.json +1 -0
  422. package/src/__golden__/wave-loop/op-held-on.json +2 -0
  423. package/src/__golden__/wave-loop/op-hooks-held.json +2 -0
  424. package/src/__golden__/wave-loop/op-hooks-hold-before-apply.json +2 -0
  425. package/src/__golden__/wave-loop/op-hooks-refused-at-base.json +3 -0
  426. package/src/__golden__/wave-loop/op-hooks-share.json +3 -0
  427. package/src/__golden__/wave-loop/op-per-run.json +7 -0
  428. package/src/__golden__/wave-loop/op-pr-review.json +7 -0
  429. package/src/__golden__/wave-loop/op-sealed.json +3 -0
  430. package/src/__golden__/wave-loop/op-spend.json +3 -0
  431. package/src/__golden__/wave-loop/op-wave-commands.json +7 -0
  432. package/src/__golden__/wave-loop/op-wide-waiting.json +4 -0
  433. package/src/build-params.test.ts +46 -0
  434. package/src/build-params.ts +55 -1
  435. package/src/change-set-graph.test.ts +72 -0
  436. package/src/change-set-graph.ts +141 -0
  437. package/src/change-set.ts +87 -6
  438. package/src/chant-version.ts +16 -0
  439. package/src/cli/build-params-cli.ts +22 -9
  440. package/src/cli/commands/build.ts +25 -11
  441. package/src/cli/commands/check-lexicon-docs.test.ts +51 -0
  442. package/src/cli/commands/check-lexicon-docs.ts +39 -2
  443. package/src/cli/commands/check-lexicon.ts +14 -2
  444. package/src/cli/commands/init-lexicon/templates/docs.ts +7 -6
  445. package/src/cli/commands/init-lexicon.test.ts +25 -0
  446. package/src/cli/commands/init-lexicon.ts +18 -8
  447. package/src/cli/handlers/approve-release.test.ts +84 -0
  448. package/src/cli/handlers/approve-revoked.test.ts +155 -0
  449. package/src/cli/handlers/approve.ts +65 -2
  450. package/src/cli/handlers/build.test.ts +67 -0
  451. package/src/cli/handlers/build.ts +46 -4
  452. package/src/cli/handlers/change-set.ts +49 -1
  453. package/src/cli/handlers/fan-out.test.ts +228 -1
  454. package/src/cli/handlers/fan-out.ts +53 -6
  455. package/src/cli/handlers/intent-trace.e2e.test.ts +201 -0
  456. package/src/cli/handlers/lifecycle-gc.test.ts +126 -0
  457. package/src/cli/handlers/lifecycle-gc.ts +146 -0
  458. package/src/cli/handlers/operator.test.ts +37 -0
  459. package/src/cli/handlers/pr.test.ts +341 -0
  460. package/src/cli/handlers/pr.ts +292 -47
  461. package/src/cli/handlers/promote.test.ts +669 -7
  462. package/src/cli/handlers/promote.ts +464 -29
  463. package/src/cli/handlers/release-implicit.e2e.test.ts +161 -0
  464. package/src/cli/handlers/release-join.e2e.test.ts +185 -0
  465. package/src/cli/handlers/release-publish-targets.e2e.test.ts +295 -0
  466. package/src/cli/handlers/release-verify.e2e.test.ts +117 -0
  467. package/src/cli/handlers/release-workstation-ci.e2e.test.ts +135 -0
  468. package/src/cli/handlers/release.e2e.test.ts +199 -0
  469. package/src/cli/handlers/release.ts +593 -0
  470. package/src/cli/handlers/run-auto-release.test.ts +80 -0
  471. package/src/cli/handlers/run-events-cmd.test.ts +92 -0
  472. package/src/cli/handlers/run-release.test.ts +55 -0
  473. package/src/cli/handlers/run.test.ts +30 -0
  474. package/src/cli/handlers/run.ts +243 -18
  475. package/src/cli/main.test.ts +8 -0
  476. package/src/cli/main.ts +143 -17
  477. package/src/cli/mcp/never-over-mcp.test.ts +2 -0
  478. package/src/cli/mcp/workspace-tools.ts +1 -1
  479. package/src/cli/parse-args.ts +56 -5
  480. package/src/cli/registry.ts +43 -5
  481. package/src/cli/run-events.ts +57 -0
  482. package/src/cli/version.ts +3 -13
  483. package/src/codegen/docs-in-site.test.ts +166 -0
  484. package/src/codegen/docs-in-site.ts +182 -0
  485. package/src/codegen/docs-site/AgentPrompt.astro +26 -1
  486. package/src/codegen/docs-types.ts +14 -1
  487. package/src/codegen/docs.ts +60 -7
  488. package/src/codegen/rehype-base-url.d.mts +3 -1
  489. package/src/codegen/rehype-base-url.mjs +15 -9
  490. package/src/codegen/rehype-base-url.test.ts +34 -0
  491. package/src/components/auto-release.test.ts +30 -0
  492. package/src/components/auto-release.ts +9 -1
  493. package/src/components/capability.ts +9 -0
  494. package/src/components/cli-support.test.ts +82 -0
  495. package/src/components/cli-support.ts +16 -9
  496. package/src/components/driver.test.ts +24 -0
  497. package/src/components/driver.ts +165 -56
  498. package/src/components/fan-out-inputs.test.ts +85 -0
  499. package/src/components/fan-out-inputs.ts +192 -0
  500. package/src/components/fan-out-record.test.ts +10 -0
  501. package/src/components/fan-out-record.ts +19 -0
  502. package/src/components/fan-out-run.ts +104 -35
  503. package/src/components/fan-out.ts +4 -0
  504. package/src/components/pr-pipeline.ts +35 -4
  505. package/src/components/pr-run.ts +30 -1
  506. package/src/components/promote-order.test.ts +90 -0
  507. package/src/components/promote-order.ts +75 -0
  508. package/src/components/promote.test.ts +10 -1
  509. package/src/components/promote.ts +94 -3
  510. package/src/components/release-compile.test.ts +147 -0
  511. package/src/components/release-compile.ts +459 -0
  512. package/src/components/release-gate.test.ts +215 -0
  513. package/src/components/release-gate.ts +355 -0
  514. package/src/components/release-promote.test.ts +159 -0
  515. package/src/components/release-promote.ts +232 -0
  516. package/src/components/run-progress.ts +7 -0
  517. package/src/components/wave-plan.ts +16 -3
  518. package/src/config.ts +15 -1
  519. package/src/flag-params.test.ts +144 -0
  520. package/src/flag-params.ts +134 -0
  521. package/src/index.ts +2 -0
  522. package/src/lifecycle/__fixtures__/publish-fakes.ts +351 -0
  523. package/src/lifecycle/change-set-store.test.ts +217 -0
  524. package/src/lifecycle/change-set-store.ts +113 -0
  525. package/src/lifecycle/commit-sha.ts +23 -0
  526. package/src/lifecycle/delivery-metrics.ts +263 -0
  527. package/src/lifecycle/deploy-outcome.ts +147 -0
  528. package/src/lifecycle/gate-change-set.e2e.test.ts +122 -0
  529. package/src/lifecycle/gate-change-set.ts +164 -0
  530. package/src/lifecycle/gate-ledger.test.ts +33 -0
  531. package/src/lifecycle/gate-ledger.ts +68 -3
  532. package/src/lifecycle/git.ts +61 -8
  533. package/src/lifecycle/index.ts +4 -0
  534. package/src/lifecycle/mixed-environment.test.ts +97 -0
  535. package/src/lifecycle/mixed-environment.ts +204 -0
  536. package/src/lifecycle/publish-attachments.ts +196 -0
  537. package/src/lifecycle/publish-credentials.ts +84 -0
  538. package/src/lifecycle/publish-declaration.ts +95 -0
  539. package/src/lifecycle/publish-forge.ts +325 -0
  540. package/src/lifecycle/publish-npm.ts +259 -0
  541. package/src/lifecycle/publish-oci.ts +396 -0
  542. package/src/lifecycle/publish-record.ts +113 -0
  543. package/src/lifecycle/publish-resume.test.ts +168 -0
  544. package/src/lifecycle/publish-targets.test.ts +494 -0
  545. package/src/lifecycle/publish-targets.ts +100 -0
  546. package/src/lifecycle/release-cut-signed.test.ts +180 -0
  547. package/src/lifecycle/release-cut.test.ts +483 -0
  548. package/src/lifecycle/release-cut.ts +503 -0
  549. package/src/lifecycle/release-flag.test.ts +112 -0
  550. package/src/lifecycle/release-join.test.ts +68 -0
  551. package/src/lifecycle/release-join.ts +182 -0
  552. package/src/lifecycle/release-ledger.test.ts +14 -0
  553. package/src/lifecycle/release-ledger.ts +85 -17
  554. package/src/lifecycle/release-publish.test.ts +225 -0
  555. package/src/lifecycle/release-publish.ts +568 -0
  556. package/src/lifecycle/release-revocation.ts +147 -0
  557. package/src/lifecycle/release-store.ts +434 -0
  558. package/src/lifecycle/release-verify.test.ts +110 -0
  559. package/src/lifecycle/release-verify.ts +120 -0
  560. package/src/lifecycle/release-workstation.test.ts +204 -0
  561. package/src/lifecycle/release-workstation.ts +216 -0
  562. package/src/lifecycle/retention-growth.e2e.test.ts +279 -0
  563. package/src/lifecycle/retention-policy.ts +63 -0
  564. package/src/lifecycle/retention.test.ts +363 -0
  565. package/src/lifecycle/retention.ts +508 -0
  566. package/src/lifecycle/run-ledger.test.ts +10 -0
  567. package/src/lifecycle/run-ledger.ts +6 -0
  568. package/src/op/activities/change-coverage.ts +4 -1
  569. package/src/op/activities/source-release.ts +8 -3
  570. package/src/op/activities/source-rollback.ts +8 -4
  571. package/src/op/commit-run-gate-deploy-join.test.ts +117 -0
  572. package/src/op/gate-name.ts +13 -3
  573. package/src/op/gate-release.test.ts +126 -0
  574. package/src/op/gate-run-locator.test.ts +110 -0
  575. package/src/op/gate-run-locator.ts +35 -0
  576. package/src/op/gate.ts +88 -6
  577. package/src/op/local-executor.ts +207 -52
  578. package/src/op/op-waves-run.ts +4 -0
  579. package/src/op/run-events.test.ts +166 -0
  580. package/src/op/run-live.ts +1 -1
  581. package/src/op/runtime.ts +39 -0
  582. package/src/op/runtimes/local.test.ts +16 -0
  583. package/src/op/runtimes/local.ts +9 -0
  584. package/src/plan-summary.test.ts +20 -0
  585. package/src/plan-summary.ts +8 -1
  586. package/src/point-decision.ts +12 -2
  587. package/src/pr-loop.ts +25 -4
  588. package/src/pr-report-url.test.ts +131 -0
  589. package/src/pr-report-url.ts +209 -0
  590. package/src/provenance.ts +2 -2
  591. package/src/run-event-store.test.ts +144 -0
  592. package/src/run-event-store.ts +273 -0
  593. package/src/run-event-stream.test.ts +175 -0
  594. package/src/run-event-stream.ts +309 -0
  595. package/src/run-event.test.ts +277 -0
  596. package/src/run-event.ts +471 -0
  597. package/src/wave-loop-golden.test.ts +9 -0
  598. package/src/workspace/__fixtures__/flag-workspace.ts +132 -0
  599. package/src/workspace/__fixtures__/older-records/change-set-984534ea9.json +100 -0
  600. package/src/workspace/__fixtures__/older-records/older-records.json +326 -0
  601. package/src/workspace/__fixtures__/run-event-check.ts +58 -0
  602. package/src/workspace/ancestry-index.test.ts +93 -0
  603. package/src/workspace/change-set-show.schema.json +102 -0
  604. package/src/workspace/change-set-show.ts +72 -0
  605. package/src/workspace/change-set.schema.json +32 -1
  606. package/src/workspace/changes-cli.ts +10 -1
  607. package/src/workspace/changes.schema.json +135 -0
  608. package/src/workspace/changes.test.ts +1 -1
  609. package/src/workspace/changes.ts +84 -2
  610. package/src/workspace/check.schema.json +4 -2
  611. package/src/workspace/checks/records.ts +35 -1
  612. package/src/workspace/checks.ts +2 -1
  613. package/src/workspace/ci-green-cli.ts +75 -13
  614. package/src/workspace/ci-green-forge.ts +24 -4
  615. package/src/workspace/ci-green-runs.test.ts +242 -0
  616. package/src/workspace/ci-green-signed.test.ts +302 -0
  617. package/src/workspace/ci-green-workflow.test.ts +16 -0
  618. package/src/workspace/ci-green-workflow.ts +12 -0
  619. package/src/workspace/ci-green.test.ts +131 -2
  620. package/src/workspace/ci-green.ts +635 -42
  621. package/src/workspace/ci-last-green.schema.json +32 -2
  622. package/src/workspace/ci-list.schema.json +525 -0
  623. package/src/workspace/commit-links.test.ts +279 -0
  624. package/src/workspace/commit-links.ts +183 -0
  625. package/src/workspace/declaration.schema.json +76 -0
  626. package/src/workspace/declaration.test.ts +28 -0
  627. package/src/workspace/declaration.ts +60 -2
  628. package/src/workspace/declared-kinds.ts +40 -1
  629. package/src/workspace/delivery-metrics.schema.json +519 -0
  630. package/src/workspace/delivery-metrics.test.ts +236 -0
  631. package/src/workspace/delivery-metrics.ts +286 -0
  632. package/src/workspace/flags-read.ts +121 -0
  633. package/src/workspace/flags.test.ts +141 -0
  634. package/src/workspace/flags.ts +157 -0
  635. package/src/workspace/graph-cli.ts +1 -1
  636. package/src/workspace/identity-gate-rule.ts +91 -7
  637. package/src/workspace/identity.ts +14 -1
  638. package/src/workspace/intent-cli.ts +46 -5
  639. package/src/workspace/intent-trace.test.ts +173 -0
  640. package/src/workspace/intent-trace.ts +484 -0
  641. package/src/workspace/intent.schema.json +702 -6
  642. package/src/workspace/intent.ts +196 -26
  643. package/src/workspace/ls.schema.json +3 -1
  644. package/src/workspace/ls.ts +6 -0
  645. package/src/workspace/member-pipeline.test.ts +17 -0
  646. package/src/workspace/member-pipeline.ts +9 -0
  647. package/src/workspace/older-records.test.ts +412 -0
  648. package/src/workspace/overlap.test.ts +165 -0
  649. package/src/workspace/overlap.ts +340 -0
  650. package/src/workspace/promotion-order.ts +97 -0
  651. package/src/workspace/read-contract.e2e.test.ts +5 -1
  652. package/src/workspace/reason-codes.test.ts +7 -1
  653. package/src/workspace/reason-codes.ts +7 -1
  654. package/src/workspace/record-assets.test.ts +2 -1
  655. package/src/workspace/records-contract.e2e.test.ts +2 -1
  656. package/src/workspace/records.schema.json +21 -2
  657. package/src/workspace/records.ts +36 -2
  658. package/src/workspace/reference-kinds/flag/flag.kind.mjs +32 -0
  659. package/src/workspace/reference-kinds/flag/flag.schema.json +53 -0
  660. package/src/workspace/releases.schema.json +513 -0
  661. package/src/workspace/releases.test.ts +108 -0
  662. package/src/workspace/releases.ts +133 -0
  663. package/src/workspace/run-event.schema.json +295 -0
  664. package/src/workspace/status-contract.test.ts +81 -1
  665. package/src/workspace/status-gates.ts +64 -1
  666. package/src/workspace/status-mixed.test.ts +152 -0
  667. package/src/workspace/status-revoked.test.ts +153 -0
  668. package/src/workspace/status-stewards.ts +6 -0
  669. package/src/workspace/status.schema.json +268 -2
  670. package/src/workspace/status.ts +334 -4
  671. package/src/workspace/trust/ci-tag-statement.ts +151 -0
  672. package/src/workspace/trust/op-run-statement.ts +148 -0
  673. package/src/workspace/trust/rotation.ts +61 -14
  674. package/src/workspace/trust/seal.ts +10 -0
  675. package/src/workspace/trust/ssh-commit.ts +10 -9
  676. package/src/workspace/wip.ts +11 -2
  677. package/src/cli/commands/__fixtures__/init-lexicon-output/docs/astro.config.mjs +0 -14
  678. package/src/cli/commands/__fixtures__/init-lexicon-output/docs/package.json +0 -16
  679. package/src/cli/commands/__fixtures__/init-lexicon-output/docs/src/content/docs/index.mdx +0 -8
  680. package/src/cli/commands/__fixtures__/init-lexicon-output/docs/src/content.config.ts +0 -15
  681. package/src/cli/commands/__fixtures__/init-lexicon-output/docs/tsconfig.json +0 -10
@@ -0,0 +1,236 @@
1
+ /**
2
+ * #4039: the four DORA metrics from the deploy records, the releases and the
3
+ * ci/revoked tags, with a failure window of 7 days. Against a temp repo;
4
+ * nothing is pushed.
5
+ *
6
+ * The fixture's prod ledger, one member, failure window 7 days:
7
+ *
8
+ * api 09-01 R1 | 09-03 R2 (R2's commit revoked 09-05) | 09-06 R3
9
+ * web 09-01 R1 | 09-10 R3 | 09-11 redeploy R3 | 09-14 rollback to R1 | 09-20 R4 | 09-30 rollback to R3
10
+ * db 09-21 no release, commit c4
11
+ *
12
+ * api's 09-03 deploy fails by revocation and is restored by the 09-06 deploy.
13
+ * web's 09-10 deploy fails by the rollback 4 days later, which restores it;
14
+ * the redeploy between them neither restores it nor hides the rollback, and
15
+ * is counted apart from the deploys.
16
+ * web's 09-20 deploy is rolled back 10 days later, outside the window, so it
17
+ * did not fail.
18
+ */
19
+
20
+ import { afterAll, beforeAll, describe, expect, test } from "vitest";
21
+ import { spawnSync } from "node:child_process";
22
+ import { mkdtempSync, realpathSync, rmSync, writeFileSync } from "node:fs";
23
+ import { tmpdir } from "node:os";
24
+ import { join } from "node:path";
25
+ import schema from "./delivery-metrics.schema.json";
26
+ import { contract } from "./__fixtures__/contract-repo";
27
+ import { deliveryMetrics, formatMetrics, type DeliveryMetricsDocument } from "./delivery-metrics";
28
+ import { appendCut, makeRelease, persistRelease, type Release } from "../lifecycle/release-store";
29
+ import { appendReleaseRecord, type ReleaseRecord, type ReleaseRecordInput } from "../lifecycle/release-ledger";
30
+ import { environmentMetrics, rollbackOf, type MetricsLookups } from "../lifecycle/delivery-metrics";
31
+ import { contentDigest } from "../content-digest";
32
+
33
+ const { expectValid } = contract(schema);
34
+
35
+ function git(cwd: string, env: Record<string, string>, ...args: string[]): string {
36
+ const r = spawnSync("git", args, { cwd, encoding: "utf-8", env: { ...process.env, ...env } });
37
+ if (r.status !== 0) throw new Error(`git ${args.join(" ")}: ${r.stderr}`);
38
+ return r.stdout.trim();
39
+ }
40
+
41
+ const DAY = 86_400;
42
+ const t = (day: string) => `2026-09-${day}T00:00:00.000Z`;
43
+ const dated = (iso: string) => ({ GIT_AUTHOR_DATE: iso, GIT_COMMITTER_DATE: iso });
44
+
45
+ interface Fixture {
46
+ commits: string[];
47
+ releases: Release[];
48
+ }
49
+
50
+ /** Write the declaration, with `metrics` when given. Only the working tree's copy is read for it. */
51
+ function declare(dir: string, metrics: object | undefined): void {
52
+ writeFileSync(
53
+ join(dir, "chant.workspace.json"),
54
+ JSON.stringify({ name: "acme", schema: 1, minReader: "0.80.0", members: [{ name: "root", dir: ".", kind: "other", because: "the fixture" }], ...(metrics ? { metrics } : {}) }, null, 2),
55
+ );
56
+ }
57
+
58
+ /** The workspace, its four commits and releases, and the prod ledger above. */
59
+ async function setup(dir: string): Promise<Fixture> {
60
+ git(dir, {}, "init", "-q", "-b", "main");
61
+ git(dir, {}, "config", "user.email", "t@chant.dev");
62
+ git(dir, {}, "config", "user.name", "T");
63
+ declare(dir, { failureWindowDays: 7 });
64
+ git(dir, {}, "add", ".");
65
+ git(dir, dated("2026-08-30T00:00:00.000Z"), "commit", "-q", "-m", "c1");
66
+ const commits = [git(dir, {}, "rev-parse", "HEAD")];
67
+ for (const day of ["02", "05", "19"]) {
68
+ git(dir, dated(t(day)), "commit", "-q", "--allow-empty", "-m", `c ${day}`);
69
+ commits.push(git(dir, {}, "rev-parse", "HEAD"));
70
+ }
71
+ const releases = commits.map((commit) =>
72
+ makeRelease({ member: null, commit, artifactDigests: [], targets: [{ env: "prod", templates: [{ component: "api", path: "t.json", digest: `sha256:${"c".repeat(64)}` }], params: [] }] }),
73
+ );
74
+ for (const [i, r] of releases.entries()) {
75
+ await persistRelease(r, [], { cwd: dir });
76
+ await appendCut({ releaseDigest: r.releaseDigest, label: `v1.${i}.0`, actor: "alice", timestamp: t("01"), runOrigin: null }, { cwd: dir });
77
+ }
78
+
79
+ const [R1, R2, R3, R4] = releases;
80
+ const deploy = (component: string, day: string, release: Release | null, extra: Partial<ReleaseRecordInput> = {}): ReleaseRecordInput => ({
81
+ component,
82
+ env: "prod",
83
+ digest: contentDigest(`${component} ${release?.releaseDigest ?? "plain"}`),
84
+ gitSha: release?.commit ?? commits[3],
85
+ runId: `${component}-${day}`,
86
+ timestamp: t(day),
87
+ actor: "ci",
88
+ ...(release ? { releaseDigest: release.releaseDigest } : {}),
89
+ ...extra,
90
+ });
91
+ const ref = (component: string, day: string) => ({ env: "prod", runId: `${component}-${day}`, timestamp: t(day) });
92
+ const ledger: ReleaseRecordInput[] = [
93
+ deploy("api", "01", R1),
94
+ deploy("web", "01", R1),
95
+ deploy("api", "03", R2),
96
+ deploy("api", "06", R3),
97
+ deploy("web", "10", R3),
98
+ deploy("web", "11", R3, { redeploys: ref("web", "10") }),
99
+ deploy("web", "14", R1, { restores: ref("web", "01") }),
100
+ deploy("web", "20", R4),
101
+ deploy("db", "21", null),
102
+ deploy("web", "30", R3, { restores: ref("web", "10") }),
103
+ ];
104
+ for (const record of ledger) await appendReleaseRecord(record, { cwd: dir });
105
+
106
+ // R2's commit is revoked on 09-05, two days after api deployed it.
107
+ const record = { schema: 1, tag: "revoked", commit: commits[1], branch: "main", at: t("05"), phase: "test", run: { name: "test", id: 7, conclusion: "failure", url: null } };
108
+ git(dir, dated(t("05")), "tag", "-a", "-m", `ci: phase test failed\n\n${JSON.stringify(record)}\n`, `ci/revoked/${commits[1]}`, commits[1]);
109
+ return { commits, releases };
110
+ }
111
+
112
+ function result(doc: DeliveryMetricsDocument): Extract<DeliveryMetricsDocument, { members: unknown }> {
113
+ expectValid(doc);
114
+ if ("error" in doc) throw new Error(`${doc.error.code}: ${doc.error.message}`);
115
+ return doc;
116
+ }
117
+
118
+ const NOW = "2026-10-01T00:00:00.000Z";
119
+
120
+ // One fixture for the file: its ledger takes a commit per record. Each test
121
+ // sets the declaration it needs.
122
+ let dir: string;
123
+ let fixture: Fixture;
124
+ beforeAll(async () => {
125
+ dir = realpathSync(mkdtempSync(join(tmpdir(), "chant-4039-")));
126
+ fixture = await setup(dir);
127
+ }, 60_000);
128
+ afterAll(() => rmSync(dir, { recursive: true, force: true }));
129
+
130
+ describe("DORA metrics from release, gate and revocation records (#4039)", () => {
131
+ test("with N = 7: a revoked release, a rollback inside the window, a rollback outside it, and the restore time of each failure", async () => {
132
+ declare(dir, { failureWindowDays: 7 });
133
+ const { commits, releases } = fixture;
134
+ const doc = result(await deliveryMetrics({ cwd: dir, since: t("01"), now: NOW }));
135
+ expect(doc.failureWindowDays).toBe(7);
136
+ expect(doc.workspace).toEqual({ name: "acme" });
137
+ expect(doc.window).toEqual({ since: t("01"), until: NOW, days: 30 });
138
+ const [prod] = doc.members[0].environments;
139
+ expect(doc.members[0]).toMatchObject({ member: "root", reason: null });
140
+
141
+ expect(prod.deployments).toEqual({ count: 9, redeploys: 1, runs: 9, fromRelease: 8, perWeek: 2.1 });
142
+
143
+ // R1 2 days after c1, R2, R3 and R4 a day after theirs, db's plain commit c4 2 days after it.
144
+ expect(prod.leadTime).toEqual({ changes: 5, fromRelease: 4, fromCommit: 1, unknown: 0, medianSeconds: DAY });
145
+
146
+ expect(prod.changeFailure).toEqual({ deployments: 9, failed: 2, revoked: 1, rolledBack: 1, rate: 0.2222 });
147
+ expect(prod.failures).toEqual([
148
+ {
149
+ component: "api",
150
+ timestamp: t("03"),
151
+ runId: "api-03",
152
+ releaseDigest: releases[1].releaseDigest,
153
+ label: "v1.1.0",
154
+ commit: commits[1],
155
+ causes: ["revoked"],
156
+ revoked: { tag: `ci/revoked/${commits[1]}`, tagged: expect.stringMatching(/^2026-09-05T00:00:00/) },
157
+ rolledBackBy: null,
158
+ restoredBy: { runId: "api-06", timestamp: t("06") },
159
+ restoreSeconds: 3 * DAY,
160
+ },
161
+ {
162
+ component: "web",
163
+ timestamp: t("10"),
164
+ runId: "web-10",
165
+ releaseDigest: releases[2].releaseDigest,
166
+ label: "v1.2.0",
167
+ commit: commits[2],
168
+ causes: ["rolled-back"],
169
+ revoked: null,
170
+ rolledBackBy: { runId: "web-14", timestamp: t("14") },
171
+ restoredBy: { runId: "web-14", timestamp: t("14") },
172
+ restoreSeconds: 4 * DAY,
173
+ },
174
+ ]);
175
+ // web's 09-20 deploy, rolled back on 09-30, is outside the 7 days and did not fail.
176
+ expect(prod.failures.some((f) => f.timestamp === t("20"))).toBe(false);
177
+ expect(prod.restore).toEqual({ restored: 2, open: 0, medianSeconds: 3.5 * DAY });
178
+
179
+ expect(formatMetrics(doc)).toContain("root prod: 9 deploys (2.1/week), lead time 1d, change failure 22.2% (2/9), restore 3.5d");
180
+ });
181
+
182
+ test("the declaration's failure window decides, and 7 is the default", async () => {
183
+ declare(dir, { failureWindowDays: 14 });
184
+ const wide = result(await deliveryMetrics({ cwd: dir, since: t("01"), now: NOW }));
185
+ // With 14 days the 09-30 rollback of the 09-20 deploy counts too.
186
+ expect(wide.members[0].environments[0].changeFailure).toMatchObject({ failed: 3, rolledBack: 2 });
187
+ expect(wide.members[0].environments[0].failures.at(-1)).toMatchObject({ timestamp: t("20"), restoredBy: { runId: "web-30" }, restoreSeconds: 10 * DAY });
188
+
189
+ declare(dir, undefined);
190
+ const doc = result(await deliveryMetrics({ cwd: dir, since: t("01"), now: NOW, env: "prod", members: ["root"] }));
191
+ expect(doc.failureWindowDays).toBe(7);
192
+ expect(doc.members[0].environments[0].changeFailure.failed).toBe(2);
193
+ expect(await deliveryMetrics({ cwd: dir, env: "../x" })).toMatchObject({ error: { code: "environment-invalid" } });
194
+ expect(await deliveryMetrics({ cwd: dir, members: ["nope"] })).toMatchObject({ error: { code: "declaration-invalid" } });
195
+ });
196
+ });
197
+
198
+ describe("environmentMetrics", () => {
199
+ const record = (component: string, day: string, extra: Partial<ReleaseRecord> = {}): ReleaseRecord => ({
200
+ version: 1,
201
+ component,
202
+ env: "prod",
203
+ digest: `sha256:${"a".repeat(64)}`,
204
+ gitSha: "c".repeat(40),
205
+ runId: `${component}-${day}`,
206
+ timestamp: t(day),
207
+ actor: "ci",
208
+ ...extra,
209
+ });
210
+ const lookups: MetricsLookups = {
211
+ releaseOf: (r) => r.releaseDigest,
212
+ commitOfRelease: () => null,
213
+ labelOf: () => null,
214
+ revocationOf: () => null,
215
+ commitTime: () => Date.parse("2026-08-31T00:00:00.000Z"),
216
+ };
217
+
218
+ test("a deploy before the window is not counted, and a failed deploy with no restore is open", () => {
219
+ const m = environmentMetrics(
220
+ "prod",
221
+ [record("api", "01"), record("api", "10"), record("api", "12", { restores: { env: "prod", runId: "api-01", timestamp: t("01") } })],
222
+ { ...lookups, revocationOf: (sha) => (sha === "c".repeat(40) ? { tag: "ci/revoked/c", tagged: null } : null) },
223
+ { since: t("05"), until: NOW },
224
+ );
225
+ // Every commit is revoked here, with a lightweight tag, so nothing restores.
226
+ expect(m.changeFailure).toEqual({ deployments: 2, failed: 2, revoked: 2, rolledBack: 1, rate: 1 });
227
+ expect(m.restore).toEqual({ restored: 0, open: 2, medianSeconds: null });
228
+ // The commit's first deploy was before the window, so it has no lead time here.
229
+ expect(m.leadTime.changes).toBe(0);
230
+ });
231
+
232
+ test("a rollback is a record with restores; nothing else is", () => {
233
+ expect(rollbackOf(record("api", "01"))).toBeNull();
234
+ expect(rollbackOf(record("api", "02", { restores: { env: "prod", runId: "x", timestamp: t("01") } }))).toEqual({ env: "prod", runId: "x", timestamp: t("01") });
235
+ });
236
+ });
@@ -0,0 +1,286 @@
1
+ /**
2
+ * `chant workspace metrics [--env <env>] [--member <name>] [--since <date>] [--json]`
3
+ * (#4039): the four DORA metrics per member and environment, from the deploy
4
+ * records, the releases and their revocations on `chant/lifecycle`.
5
+ *
6
+ * The `--json` document is `delivery-metrics.schema.json`, a read-contract
7
+ * output, so behold, a Grafana panel and terragucci's own estate read the
8
+ * same numbers. The metrics are computed in `../lifecycle/delivery-metrics.ts`;
9
+ * this module reads what they need:
10
+ *
11
+ * - each member's `<env>/releases.jsonl`, every environment it has, read in
12
+ * full, since deploys before the window decide lead times and deploys after
13
+ * a failure decide its restore;
14
+ * - its releases, for the commit each was cut from, and the release gate's
15
+ * resolutions, for a deploy that names its release only through its
16
+ * admission (`releaseOfDeploy`, #4043);
17
+ * - the counted `ci/revoked/<sha>` tags, judged as `release cut` judges them
18
+ * (`readRevokedCommits`, #4033), and each commit's committer time from git.
19
+ *
20
+ * Outside a workspace the project's own ledger is read as one member, `null`.
21
+ * It reads the local branch and the local tags, and never fetches.
22
+ */
23
+
24
+ import { execFileSync } from "node:child_process";
25
+ import { existsSync } from "node:fs";
26
+ import { resolve } from "node:path";
27
+ import { formatError } from "../cli/format";
28
+ import type { CommandContext } from "../cli/registry";
29
+ import { DEFAULT_FAILURE_WINDOW_DAYS, environmentMetrics, type EnvironmentMetrics, type MetricsWindow } from "../lifecycle/delivery-metrics";
30
+ import type { LedgerLocation } from "../lifecycle/git";
31
+ import { findWorkspaceRoot } from "../project-root";
32
+ import { readDeclaration, readerVersion, WorkspaceReadError } from "./declaration";
33
+ import type { ReasonCode } from "./reason-codes";
34
+ import { gitTop, workingTree } from "./tree";
35
+ import { handToRootChant } from "./which-chant";
36
+
37
+ export const DELIVERY_METRICS_CONTRACT_VERSION = 1;
38
+ export const DELIVERY_METRICS_SCHEMA_ID = "https://intentius.io/chant/schemas/workspace/delivery-metrics/v1/delivery-metrics.schema.json";
39
+
40
+ /** The window when `--since` is not given: eight weeks back from now. */
41
+ export const DEFAULT_METRICS_DAYS = 56;
42
+
43
+ /** Why the read stopped. Closed. Outside a workspace there is no declaration to fail, so `declaration-missing` never comes back. */
44
+ export const METRICS_ERROR_CODES = [
45
+ "declaration-ambiguous",
46
+ "declaration-unparseable",
47
+ "declaration-invalid",
48
+ "placement-invalid",
49
+ "reader-too-old",
50
+ "root-chant-required",
51
+ "not-a-git-repository",
52
+ "environment-invalid",
53
+ ] as const satisfies readonly ReasonCode[];
54
+ export type MetricsErrorCode = (typeof METRICS_ERROR_CODES)[number];
55
+
56
+ /** Why a member's metrics are missing or partial. Closed. */
57
+ export const METRICS_REASON_CODES = ["ledger-unreadable", "ledger-malformed"] as const satisfies readonly ReasonCode[];
58
+ export type MetricsReasonCode = (typeof METRICS_REASON_CODES)[number];
59
+
60
+ const ENV_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]{0,99}$/;
61
+
62
+ const USAGE = "chant workspace metrics [dir] [--env <env>] [--member <name>] [--since <date>] [--json]";
63
+
64
+ /** One member's metrics. */
65
+ export interface MemberMetrics {
66
+ /** The member's name, or null for a project outside a workspace. */
67
+ member: string | null;
68
+ /** Each environment with a deploy ledger, sorted by name. */
69
+ environments: EnvironmentMetrics[];
70
+ /** Null when everything was read. */
71
+ reason: { code: MetricsReasonCode; message: string } | null;
72
+ }
73
+
74
+ export type DeliveryMetricsDocument =
75
+ | {
76
+ $schema: string;
77
+ contract: number;
78
+ chant: string;
79
+ /** The workspace's name, or null for a project outside a workspace. */
80
+ workspace: { name: string } | null;
81
+ lifecycle: { ref: string; commit: string | null };
82
+ window: MetricsWindow & { days: number };
83
+ failureWindowDays: number;
84
+ members: MemberMetrics[];
85
+ }
86
+ | { $schema: string; contract: number; chant: string; error: { code: MetricsErrorCode; message: string } };
87
+
88
+ export interface MetricsQuery {
89
+ cwd: string;
90
+ /** Only this environment. */
91
+ env?: string;
92
+ /** Only these members. */
93
+ members?: string[];
94
+ /** ISO-8601 date or time; the default is {@link DEFAULT_METRICS_DAYS} days before `now`. */
95
+ since?: string;
96
+ /** For tests: the end of the window. */
97
+ now?: string;
98
+ }
99
+
100
+ class MetricsError extends Error {
101
+ constructor(
102
+ readonly code: MetricsErrorCode,
103
+ message: string,
104
+ ) {
105
+ super(message);
106
+ }
107
+ }
108
+
109
+ function tryGit(cwd: string, args: string[]): string | null {
110
+ try {
111
+ return execFileSync("git", args, { cwd, encoding: "utf-8", stdio: ["ignore", "pipe", "pipe"] }).trim();
112
+ } catch {
113
+ return null;
114
+ }
115
+ }
116
+
117
+ /** Read the metrics document for the workspace or project holding `query.cwd`. */
118
+ export async function deliveryMetrics(query: MetricsQuery): Promise<DeliveryMetricsDocument> {
119
+ const head = { $schema: DELIVERY_METRICS_SCHEMA_ID, contract: DELIVERY_METRICS_CONTRACT_VERSION, chant: readerVersion() };
120
+ try {
121
+ if (query.env !== undefined && (!ENV_PATTERN.test(query.env) || query.env.includes(".."))) {
122
+ throw new MetricsError("environment-invalid", `--env ${JSON.stringify(query.env)} can't be an environment: use letters, digits, ".", "_" and "-", starting with a letter or digit`);
123
+ }
124
+ const top = gitTop(query.cwd);
125
+ if (!top) throw new MetricsError("not-a-git-repository", `${query.cwd} is not in a git repository, so it has no chant/lifecycle branch to read`);
126
+
127
+ const found = findWorkspaceRoot(query.cwd);
128
+ const declaration = found ? readDeclaration(workingTree(found.dir), "", { rootChant: true }) : null;
129
+ const root = found?.dir ?? query.cwd;
130
+ const failureWindowDays = declaration?.metrics?.failureWindowDays ?? DEFAULT_FAILURE_WINDOW_DAYS;
131
+
132
+ const until = query.now ?? new Date().toISOString();
133
+ const since = query.since ?? new Date(Date.parse(until) - DEFAULT_METRICS_DAYS * 86_400_000).toISOString();
134
+ const window = { since: new Date(Date.parse(since)).toISOString(), until: new Date(Date.parse(until)).toISOString() };
135
+ const days = Math.round(((Date.parse(window.until) - Date.parse(window.since)) / 86_400_000) * 100) / 100;
136
+
137
+ // The revoked commits, judged against ci.green at base (#4033). Tags that
138
+ // can't be judged stop the read: the failure rate would be wrong otherwise.
139
+ const { readRevokedCommits } = await import("../lifecycle/release-revocation");
140
+ const revoked = await readRevokedCommits(root);
141
+
142
+ const commitTimes = new Map<string, number | null>();
143
+ const commitTime = (sha: string): number | null => {
144
+ if (!commitTimes.has(sha)) {
145
+ const iso = tryGit(top, ["show", "-s", "--format=%cI", `${sha}^{commit}`]);
146
+ commitTimes.set(sha, iso ? Date.parse(iso) : null);
147
+ }
148
+ return commitTimes.get(sha)!;
149
+ };
150
+
151
+ let ledgers: { member: string | null; loc: LedgerLocation }[];
152
+ if (declaration) {
153
+ const wanted = query.members && query.members.length > 0 ? new Set(query.members) : null;
154
+ if (wanted) {
155
+ const unknown = [...wanted].filter((n) => !declaration.members.some((m) => m.name === n));
156
+ if (unknown.length > 0) throw new WorkspaceReadError("declaration-invalid", `--member ${unknown.join(", ")} names no member of the workspace`);
157
+ }
158
+ ledgers = declaration.members
159
+ .filter((m) => !wanted || wanted.has(m.name))
160
+ .map((m) => ({ member: m.name, loc: { cwd: root, member: m.dir === "." ? "." : m.name } }));
161
+ } else {
162
+ ledgers = [{ member: null, loc: { cwd: query.cwd } }];
163
+ }
164
+
165
+ const members: MemberMetrics[] = [];
166
+ for (const l of ledgers) members.push(await memberMetrics(l.member, l.loc, { env: query.env, window, failureWindowDays, revoked, commitTime }));
167
+
168
+ return {
169
+ ...head,
170
+ workspace: declaration ? { name: declaration.name } : null,
171
+ lifecycle: { ref: "chant/lifecycle", commit: tryGit(top, ["rev-parse", "--verify", "--quiet", "refs/heads/chant/lifecycle"]) || null },
172
+ window: { ...window, days },
173
+ failureWindowDays,
174
+ members,
175
+ };
176
+ } catch (err) {
177
+ if (err instanceof WorkspaceReadError) return { ...head, error: { code: err.code as MetricsErrorCode, message: err.message } };
178
+ if (err instanceof MetricsError) return { ...head, error: { code: err.code, message: err.message } };
179
+ throw err;
180
+ }
181
+ }
182
+
183
+ async function memberMetrics(
184
+ member: string | null,
185
+ loc: LedgerLocation,
186
+ ctx: {
187
+ env: string | undefined;
188
+ window: MetricsWindow;
189
+ failureWindowDays: number;
190
+ revoked: ReadonlyMap<string, { tag: string; tagged: string | null }>;
191
+ commitTime: (sha: string) => number | null;
192
+ },
193
+ ): Promise<MemberMetrics> {
194
+ try {
195
+ const { listReleaseEnvironments, readReleaseLedger } = await import("../lifecycle/release-ledger");
196
+ const { readAllReleases, readCuts, labelsByDigest } = await import("../lifecycle/release-store");
197
+ const { readGateLedger } = await import("../lifecycle/gate-ledger");
198
+ const { releaseOfDeploy } = await import("../lifecycle/release-join");
199
+ const { RELEASE_GATE_OP } = await import("../op/gate-name");
200
+
201
+ let envs = (await listReleaseEnvironments(loc)).sort();
202
+ if (ctx.env !== undefined) envs = envs.filter((e) => e === ctx.env);
203
+ if (envs.length === 0) return { member, environments: [], reason: null };
204
+
205
+ const { releases, malformed: badReleases } = await readAllReleases(loc);
206
+ const commits = new Map(releases.map((r) => [r.releaseDigest, r.commit]));
207
+ const labels = labelsByDigest((await readCuts(loc)).cuts);
208
+ const { resolutions } = await readGateLedger(RELEASE_GATE_OP, loc);
209
+
210
+ const environments: EnvironmentMetrics[] = [];
211
+ let badLines = 0;
212
+ for (const env of envs) {
213
+ const { records, malformed } = await readReleaseLedger(env, loc);
214
+ badLines += malformed;
215
+ environments.push(
216
+ environmentMetrics(
217
+ env,
218
+ records,
219
+ {
220
+ releaseOf: (r) => releaseOfDeploy(r, resolutions),
221
+ commitOfRelease: (d) => commits.get(d) ?? null,
222
+ labelOf: (d) => labels.get(d) ?? null,
223
+ revocationOf: (sha) => {
224
+ const r = ctx.revoked.get(sha);
225
+ return r ? { tag: r.tag, tagged: r.tagged } : null;
226
+ },
227
+ commitTime: ctx.commitTime,
228
+ },
229
+ ctx.window,
230
+ ctx.failureWindowDays,
231
+ ),
232
+ );
233
+ }
234
+ const reason =
235
+ badLines + badReleases > 0
236
+ ? { code: "ledger-malformed" as const, message: `${badLines} deploy record line(s) and ${badReleases} release file(s) could not be read and are left out` }
237
+ : null;
238
+ return { member, environments, reason };
239
+ } catch (err) {
240
+ return { member, environments: [], reason: { code: "ledger-unreadable", message: `reading the ledger failed: ${err instanceof Error ? err.message : String(err)}` } };
241
+ }
242
+ }
243
+
244
+ const seconds = (s: number | null): string => {
245
+ if (s === null) return "none";
246
+ if (s < 3600) return `${Math.round(s / 60)}m`;
247
+ if (s < 86_400) return `${Math.round((s / 3600) * 10) / 10}h`;
248
+ return `${Math.round((s / 86_400) * 10) / 10}d`;
249
+ };
250
+
251
+ /** The text view: one line per member and environment. */
252
+ export function formatMetrics(doc: Extract<DeliveryMetricsDocument, { members: unknown }>): string {
253
+ const lines = [`window ${doc.window.since} to ${doc.window.until} (${doc.window.days} days), failure window ${doc.failureWindowDays} days`];
254
+ for (const m of doc.members) {
255
+ if (m.reason) lines.push(`${m.member ?? "."}: ${m.reason.code}: ${m.reason.message}`);
256
+ for (const e of m.environments) {
257
+ const rate = e.changeFailure.rate === null ? "-" : `${Math.round(e.changeFailure.rate * 1000) / 10}%`;
258
+ lines.push(
259
+ `${m.member ?? "."} ${e.env}: ${e.deployments.count} deploys (${e.deployments.perWeek}/week), lead time ${seconds(e.leadTime.medianSeconds)}, ` +
260
+ `change failure ${rate} (${e.changeFailure.failed}/${e.changeFailure.deployments}), restore ${seconds(e.restore.medianSeconds)}` +
261
+ (e.restore.open > 0 ? `, ${e.restore.open} open` : ""),
262
+ );
263
+ }
264
+ }
265
+ return lines.join("\n");
266
+ }
267
+
268
+ export async function runWorkspaceMetrics(ctx: CommandContext): Promise<number> {
269
+ const { args } = ctx;
270
+ if (args.since !== undefined && (args.since === "" || Number.isNaN(Date.parse(args.since)))) {
271
+ console.error(formatError({ message: `--since ${JSON.stringify(args.since)} is not a date: give an ISO-8601 date such as 2026-09-01`, hint: USAGE }));
272
+ return 1;
273
+ }
274
+ const cwd = resolve(args.extraPositional ?? ".");
275
+ if (!existsSync(cwd)) {
276
+ console.error(formatError({ message: `${cwd} does not exist`, hint: USAGE }));
277
+ return 1;
278
+ }
279
+ const handed = await handToRootChant(cwd, undefined);
280
+ if (handed !== undefined) return handed;
281
+ const doc = await deliveryMetrics({ cwd, ...(args.env ? { env: args.env } : {}), ...(args.members ? { members: args.members } : {}), ...(args.since ? { since: args.since } : {}) });
282
+ if (args.json) console.log(JSON.stringify(doc, null, 2));
283
+ else if ("error" in doc) console.error(formatError({ message: `${doc.error.code}: ${doc.error.message}`, hint: USAGE }));
284
+ else console.log(formatMetrics(doc));
285
+ return "error" in doc ? 1 : 0;
286
+ }
@@ -0,0 +1,121 @@
1
+ /**
2
+ * Reading a workspace's feature flags (#4024): every flag of the declared
3
+ * flag kinds, and the flags of one member by parameter. Kept apart from
4
+ * `./flags.ts`, which `records.ts` loads, since reading goes through
5
+ * `records-cli.ts`.
6
+ */
7
+
8
+ import { join, relative, resolve, sep } from "node:path";
9
+ import { loadRecordKind } from "./records";
10
+ import type { FlagValue, WorkspaceFlags } from "./flags";
11
+
12
+ const str = (v: unknown): string | null => (typeof v === "string" && v !== "" ? v : null);
13
+
14
+ /** A flag value as the record writes it, or undefined for one a build-time parameter can't hold. */
15
+ function flagValue(v: unknown): FlagValue | undefined {
16
+ return typeof v === "string" || typeof v === "number" || typeof v === "boolean" ? v : undefined;
17
+ }
18
+
19
+ /**
20
+ * Every flag of every flag kind the declaration at `root` names, read in the
21
+ * working tree. A kind without a flag block is skipped without reading its
22
+ * records. Throws a `RecordReadError` when a flag kind or its records can't
23
+ * be read, and a `WorkspaceReadError` when the declaration can't.
24
+ */
25
+ export async function readWorkspaceFlags(root: string): Promise<WorkspaceFlags> {
26
+ const { readDeclaration, declaredRecordKinds } = await import("./declaration");
27
+ const { workingTree } = await import("./tree");
28
+ const declaration = readDeclaration(workingTree(root));
29
+ const out: WorkspaceFlags = { flags: [], invalid: [] };
30
+ const declared = declaredRecordKinds(declaration);
31
+ if (declared.length === 0) return out;
32
+ const { readRecordsFor } = await import("./records-cli");
33
+ for (const d of declared) {
34
+ const file = join(root, ...d.path.split("/"));
35
+ const loaded = await loadRecordKind(file, root);
36
+ const flag = loaded.kind.flag;
37
+ if (!flag) continue;
38
+ const { result, root: repo } = await readRecordsFor({ kind: file, cwd: root, current: true });
39
+ for (const r of result.records) {
40
+ const path = relative(root, join(repo, ...r.path.split("/"))).split(sep).join("/");
41
+ const member = r.data ? str(r.data[flag.member]) : null;
42
+ if (!r.valid || r.id === null || r.data === null) {
43
+ out.invalid.push({ kind: d.path, path, member, message: r.reasons.map((x) => `${x.code}: ${x.message}`).join("; ") || "the record can't be read" });
44
+ continue;
45
+ }
46
+ const values: Record<string, FlagValue> = {};
47
+ const raw = r.data[flag.values];
48
+ if (raw !== null && typeof raw === "object" && !Array.isArray(raw)) {
49
+ for (const [env, v] of Object.entries(raw as Record<string, unknown>)) {
50
+ const value = flagValue(v);
51
+ if (value !== undefined) values[env] = value;
52
+ }
53
+ }
54
+ out.flags.push({
55
+ id: r.id,
56
+ kind: d.path,
57
+ path,
58
+ member: member ?? "",
59
+ param: str(r.data[flag.param]) ?? "",
60
+ owner: str(r.data[flag.owner]) ?? "",
61
+ values,
62
+ work: r.flag?.work ?? null,
63
+ implements: r.flag?.implements ?? [],
64
+ stale: r.flag?.stale ?? false,
65
+ });
66
+ }
67
+ }
68
+ return out;
69
+ }
70
+
71
+ /** One flag-backed parameter, as a build in one environment reads it. */
72
+ export interface FlagBinding {
73
+ /** The flag's name. */
74
+ flag: string;
75
+ /** The flag's value in the environment, or undefined when the record gives it none. */
76
+ value: FlagValue | undefined;
77
+ }
78
+
79
+ /** The flags that back the parameters of the member holding a directory. */
80
+ export interface MemberFlagBindings {
81
+ /** The member's name in the declaration. */
82
+ member: string;
83
+ /** Each flag-backed parameter by name. */
84
+ byParam: Record<string, FlagBinding>;
85
+ /** Why a flag of this member can't be trusted: an invalid record that names it, or two flags for one parameter. */
86
+ errors: string[];
87
+ }
88
+
89
+ /**
90
+ * The flags that back parameters of the member holding `projectDir`, in the
91
+ * workspace whose declaration is in `root`, with each flag's value for `env`.
92
+ * Null when no member holds the directory, since only a member has flags.
93
+ */
94
+ export async function memberFlagBindings(root: string, projectDir: string, env: string): Promise<MemberFlagBindings | null> {
95
+ const { readDeclaration, resolveGroups, ownerOf } = await import("./declaration");
96
+ const { workingTree } = await import("./tree");
97
+ const tree = workingTree(root);
98
+ const declaration = readDeclaration(tree);
99
+ const rel = relative(root, resolve(projectDir)).split(sep).join("/");
100
+ const owner = ownerOf(declaration, resolveGroups(declaration, tree), rel === "" ? "" : rel);
101
+ if (!owner || !("member" in owner)) return null;
102
+ const member = owner.member.name;
103
+ const read = await readWorkspaceFlags(root);
104
+ const errors: string[] = [];
105
+ for (const bad of read.invalid) {
106
+ if (bad.member === member || bad.member === null) errors.push(`flag record ${bad.path} is not valid, so no build reads it: ${bad.message}`);
107
+ }
108
+ const byParam: Record<string, FlagBinding> = {};
109
+ const seen = new Map<string, string>();
110
+ for (const f of read.flags) {
111
+ if (f.member !== member) continue;
112
+ const other = seen.get(f.param);
113
+ if (other !== undefined) {
114
+ errors.push(`flags ${other} and ${f.id} both set build parameter "${f.param}" of member ${member}; one flag backs one parameter`);
115
+ continue;
116
+ }
117
+ seen.set(f.param, f.id);
118
+ byParam[f.param] = { flag: f.id, value: f.values[env] };
119
+ }
120
+ return { member, byParam, errors };
121
+ }