@intentius/chant 0.124.28 → 0.124.30

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 (449) hide show
  1. package/dist/build-params.d.ts +34 -1
  2. package/dist/build-params.d.ts.map +1 -1
  3. package/dist/build-params.js +31 -1
  4. package/dist/change-set-graph.d.ts +8 -1
  5. package/dist/change-set-graph.d.ts.map +1 -1
  6. package/dist/change-set-graph.js +4 -2
  7. package/dist/change-set.d.ts +10 -0
  8. package/dist/change-set.d.ts.map +1 -1
  9. package/dist/change-set.js +4 -3
  10. package/dist/cli/build-params-cli.d.ts +7 -1
  11. package/dist/cli/build-params-cli.d.ts.map +1 -1
  12. package/dist/cli/build-params-cli.js +17 -8
  13. package/dist/cli/commands/build.d.ts.map +1 -1
  14. package/dist/cli/commands/build.js +6 -3
  15. package/dist/cli/handlers/approve.d.ts.map +1 -1
  16. package/dist/cli/handlers/approve.js +45 -0
  17. package/dist/cli/handlers/build.d.ts.map +1 -1
  18. package/dist/cli/handlers/build.js +18 -4
  19. package/dist/cli/handlers/fan-out.d.ts +9 -0
  20. package/dist/cli/handlers/fan-out.d.ts.map +1 -1
  21. package/dist/cli/handlers/fan-out.js +67 -8
  22. package/dist/cli/handlers/lifecycle-gc.d.ts +19 -0
  23. package/dist/cli/handlers/lifecycle-gc.d.ts.map +1 -0
  24. package/dist/cli/handlers/lifecycle-gc.js +143 -0
  25. package/dist/cli/handlers/pr.d.ts +11 -3
  26. package/dist/cli/handlers/pr.d.ts.map +1 -1
  27. package/dist/cli/handlers/pr.js +134 -26
  28. package/dist/cli/handlers/promote.d.ts +7 -0
  29. package/dist/cli/handlers/promote.d.ts.map +1 -1
  30. package/dist/cli/handlers/promote.js +210 -33
  31. package/dist/cli/handlers/release.d.ts +62 -5
  32. package/dist/cli/handlers/release.d.ts.map +1 -1
  33. package/dist/cli/handlers/release.js +414 -28
  34. package/dist/cli/handlers/run.d.ts +11 -1
  35. package/dist/cli/handlers/run.d.ts.map +1 -1
  36. package/dist/cli/handlers/run.js +67 -6
  37. package/dist/cli/main.d.ts.map +1 -1
  38. package/dist/cli/main.js +91 -10
  39. package/dist/cli/parse-args.d.ts.map +1 -1
  40. package/dist/cli/parse-args.js +73 -2
  41. package/dist/cli/registry.d.ts +32 -4
  42. package/dist/cli/registry.d.ts.map +1 -1
  43. package/dist/cli/run-events.d.ts +3 -2
  44. package/dist/cli/run-events.d.ts.map +1 -1
  45. package/dist/cli/run-events.js +5 -3
  46. package/dist/components/auto-release.d.ts +2 -0
  47. package/dist/components/auto-release.d.ts.map +1 -1
  48. package/dist/components/auto-release.js +1 -0
  49. package/dist/components/capability.d.ts +9 -0
  50. package/dist/components/capability.d.ts.map +1 -1
  51. package/dist/components/driver.d.ts +8 -0
  52. package/dist/components/driver.d.ts.map +1 -1
  53. package/dist/components/driver.js +35 -0
  54. package/dist/components/fan-out-inputs.d.ts +69 -0
  55. package/dist/components/fan-out-inputs.d.ts.map +1 -0
  56. package/dist/components/fan-out-inputs.js +169 -0
  57. package/dist/components/fan-out-record.d.ts +13 -0
  58. package/dist/components/fan-out-record.d.ts.map +1 -1
  59. package/dist/components/fan-out-record.js +16 -0
  60. package/dist/components/fan-out-run.d.ts.map +1 -1
  61. package/dist/components/fan-out-run.js +50 -0
  62. package/dist/components/fan-out.d.ts +4 -0
  63. package/dist/components/fan-out.d.ts.map +1 -1
  64. package/dist/components/pr-pipeline.d.ts +49 -0
  65. package/dist/components/pr-pipeline.d.ts.map +1 -1
  66. package/dist/components/pr-pipeline.js +38 -4
  67. package/dist/components/pr-run.d.ts +20 -2
  68. package/dist/components/pr-run.d.ts.map +1 -1
  69. package/dist/components/pr-run.js +26 -13
  70. package/dist/components/promote.d.ts +23 -0
  71. package/dist/components/promote.d.ts.map +1 -1
  72. package/dist/components/promote.js +60 -2
  73. package/dist/components/release-compile.d.ts +9 -0
  74. package/dist/components/release-compile.d.ts.map +1 -1
  75. package/dist/components/release-compile.js +9 -3
  76. package/dist/components/release-gate.d.ts +11 -1
  77. package/dist/components/release-gate.d.ts.map +1 -1
  78. package/dist/components/release-gate.js +10 -4
  79. package/dist/components/wave-plan.d.ts +16 -0
  80. package/dist/components/wave-plan.d.ts.map +1 -1
  81. package/dist/components/wave-plan.js +36 -2
  82. package/dist/config.d.ts +38 -0
  83. package/dist/config.d.ts.map +1 -1
  84. package/dist/config.js +2 -0
  85. package/dist/flag-params.d.ts +54 -0
  86. package/dist/flag-params.d.ts.map +1 -0
  87. package/dist/flag-params.js +130 -0
  88. package/dist/lifecycle/__fixtures__/publish-fakes.d.ts +120 -0
  89. package/dist/lifecycle/__fixtures__/publish-fakes.d.ts.map +1 -0
  90. package/dist/lifecycle/__fixtures__/publish-fakes.js +304 -0
  91. package/dist/lifecycle/change-set-store.d.ts +2 -1
  92. package/dist/lifecycle/change-set-store.d.ts.map +1 -1
  93. package/dist/lifecycle/change-set-store.js +2 -1
  94. package/dist/lifecycle/delivery-metrics.d.ts +139 -0
  95. package/dist/lifecycle/delivery-metrics.d.ts.map +1 -0
  96. package/dist/lifecycle/delivery-metrics.js +145 -0
  97. package/dist/lifecycle/deploy-outcome.d.ts +81 -0
  98. package/dist/lifecycle/deploy-outcome.d.ts.map +1 -0
  99. package/dist/lifecycle/deploy-outcome.js +101 -0
  100. package/dist/lifecycle/gate-change-set.d.ts +10 -1
  101. package/dist/lifecycle/gate-change-set.d.ts.map +1 -1
  102. package/dist/lifecycle/gate-change-set.js +35 -3
  103. package/dist/lifecycle/gate-ledger.d.ts +2 -3
  104. package/dist/lifecycle/gate-ledger.d.ts.map +1 -1
  105. package/dist/lifecycle/git.d.ts +30 -1
  106. package/dist/lifecycle/git.d.ts.map +1 -1
  107. package/dist/lifecycle/git.js +31 -5
  108. package/dist/lifecycle/index.d.ts +4 -0
  109. package/dist/lifecycle/index.d.ts.map +1 -1
  110. package/dist/lifecycle/index.js +4 -0
  111. package/dist/lifecycle/mixed-environment.d.ts +95 -0
  112. package/dist/lifecycle/mixed-environment.d.ts.map +1 -0
  113. package/dist/lifecycle/mixed-environment.js +154 -0
  114. package/dist/lifecycle/publish-attachments.d.ts +79 -0
  115. package/dist/lifecycle/publish-attachments.d.ts.map +1 -0
  116. package/dist/lifecycle/publish-attachments.js +179 -0
  117. package/dist/lifecycle/publish-credentials.d.ts +37 -0
  118. package/dist/lifecycle/publish-credentials.d.ts.map +1 -0
  119. package/dist/lifecycle/publish-credentials.js +69 -0
  120. package/dist/lifecycle/publish-declaration.d.ts +97 -0
  121. package/dist/lifecycle/publish-declaration.d.ts.map +1 -0
  122. package/dist/lifecycle/publish-declaration.js +83 -0
  123. package/dist/lifecycle/publish-forge.d.ts +53 -0
  124. package/dist/lifecycle/publish-forge.d.ts.map +1 -0
  125. package/dist/lifecycle/publish-forge.js +298 -0
  126. package/dist/lifecycle/publish-npm.d.ts +61 -0
  127. package/dist/lifecycle/publish-npm.d.ts.map +1 -0
  128. package/dist/lifecycle/publish-npm.js +242 -0
  129. package/dist/lifecycle/publish-oci.d.ts +64 -0
  130. package/dist/lifecycle/publish-oci.d.ts.map +1 -0
  131. package/dist/lifecycle/publish-oci.js +383 -0
  132. package/dist/lifecycle/publish-record.d.ts +66 -0
  133. package/dist/lifecycle/publish-record.d.ts.map +1 -0
  134. package/dist/lifecycle/publish-record.js +84 -0
  135. package/dist/lifecycle/publish-targets.d.ts +45 -0
  136. package/dist/lifecycle/publish-targets.d.ts.map +1 -0
  137. package/dist/lifecycle/publish-targets.js +85 -0
  138. package/dist/lifecycle/release-cut.d.ts +48 -1
  139. package/dist/lifecycle/release-cut.d.ts.map +1 -1
  140. package/dist/lifecycle/release-cut.js +116 -23
  141. package/dist/lifecycle/release-join.d.ts +95 -0
  142. package/dist/lifecycle/release-join.d.ts.map +1 -0
  143. package/dist/lifecycle/release-join.js +139 -0
  144. package/dist/lifecycle/release-ledger.d.ts +8 -3
  145. package/dist/lifecycle/release-ledger.d.ts.map +1 -1
  146. package/dist/lifecycle/release-ledger.js +2 -0
  147. package/dist/lifecycle/release-publish.d.ts +312 -0
  148. package/dist/lifecycle/release-publish.d.ts.map +1 -0
  149. package/dist/lifecycle/release-publish.js +335 -0
  150. package/dist/lifecycle/release-revocation.d.ts +80 -0
  151. package/dist/lifecycle/release-revocation.d.ts.map +1 -0
  152. package/dist/lifecycle/release-revocation.js +123 -0
  153. package/dist/lifecycle/release-store.d.ts +12 -2
  154. package/dist/lifecycle/release-store.d.ts.map +1 -1
  155. package/dist/lifecycle/release-store.js +4 -2
  156. package/dist/lifecycle/release-verify.d.ts +61 -0
  157. package/dist/lifecycle/release-verify.d.ts.map +1 -0
  158. package/dist/lifecycle/release-verify.js +92 -0
  159. package/dist/lifecycle/release-workstation.d.ts +91 -0
  160. package/dist/lifecycle/release-workstation.d.ts.map +1 -0
  161. package/dist/lifecycle/release-workstation.js +180 -0
  162. package/dist/lifecycle/retention-policy.d.ts +45 -0
  163. package/dist/lifecycle/retention-policy.d.ts.map +1 -0
  164. package/dist/lifecycle/retention-policy.js +43 -0
  165. package/dist/lifecycle/retention.d.ts +185 -0
  166. package/dist/lifecycle/retention.d.ts.map +1 -0
  167. package/dist/lifecycle/retention.js +400 -0
  168. package/dist/lifecycle/run-ledger.d.ts +1 -1
  169. package/dist/lifecycle/run-ledger.d.ts.map +1 -1
  170. package/dist/lifecycle/run-ledger.js +9 -0
  171. package/dist/op/activities/activity-contracts.d.ts +1 -0
  172. package/dist/op/activities/activity-contracts.d.ts.map +1 -1
  173. package/dist/op/activities/activity-contracts.js +1 -0
  174. package/dist/op/activities/shell.d.ts +6 -0
  175. package/dist/op/activities/shell.d.ts.map +1 -1
  176. package/dist/op/activities/shell.js +1 -1
  177. package/dist/op/local-executor.d.ts +23 -1
  178. package/dist/op/local-executor.d.ts.map +1 -1
  179. package/dist/op/local-executor.js +9 -3
  180. package/dist/op/runtime.d.ts +27 -0
  181. package/dist/op/runtime.d.ts.map +1 -1
  182. package/dist/op/runtime.js +20 -0
  183. package/dist/plan-summary.d.ts.map +1 -1
  184. package/dist/plan-summary.js +8 -2
  185. package/dist/pr-forge.d.ts +23 -0
  186. package/dist/pr-forge.d.ts.map +1 -1
  187. package/dist/pr-forge.js +44 -0
  188. package/dist/pr-loop.d.ts +17 -0
  189. package/dist/pr-loop.d.ts.map +1 -1
  190. package/dist/pr-loop.js +30 -4
  191. package/dist/pr-report-url.d.ts +82 -0
  192. package/dist/pr-report-url.d.ts.map +1 -0
  193. package/dist/pr-report-url.js +174 -0
  194. package/dist/provenance.d.ts +2 -2
  195. package/dist/provenance.d.ts.map +1 -1
  196. package/dist/run-event-store.d.ts +29 -2
  197. package/dist/run-event-store.d.ts.map +1 -1
  198. package/dist/run-event-store.js +50 -3
  199. package/dist/run-event-stream.d.ts +4 -3
  200. package/dist/run-event-stream.d.ts.map +1 -1
  201. package/dist/run-event-stream.js +7 -3
  202. package/dist/run-event.d.ts +34 -1
  203. package/dist/run-event.d.ts.map +1 -1
  204. package/dist/run-event.js +4 -0
  205. package/dist/wave-runtime.mjs +260 -229
  206. package/dist/workspace/__fixtures__/flag-workspace.d.ts +31 -0
  207. package/dist/workspace/__fixtures__/flag-workspace.d.ts.map +1 -0
  208. package/dist/workspace/__fixtures__/flag-workspace.js +114 -0
  209. package/dist/workspace/change-set.schema.json +5 -1
  210. package/dist/workspace/check.schema.json +4 -2
  211. package/dist/workspace/checks/records.d.ts +9 -1
  212. package/dist/workspace/checks/records.d.ts.map +1 -1
  213. package/dist/workspace/checks/records.js +35 -1
  214. package/dist/workspace/checks.d.ts +1 -0
  215. package/dist/workspace/checks.d.ts.map +1 -1
  216. package/dist/workspace/checks.js +2 -1
  217. package/dist/workspace/ci-green-forge.d.ts +2 -0
  218. package/dist/workspace/ci-green-forge.d.ts.map +1 -1
  219. package/dist/workspace/ci-green.d.ts +10 -0
  220. package/dist/workspace/ci-green.d.ts.map +1 -1
  221. package/dist/workspace/ci-green.js +14 -5
  222. package/dist/workspace/ci-run.d.ts +222 -0
  223. package/dist/workspace/ci-run.d.ts.map +1 -0
  224. package/dist/workspace/ci-run.js +464 -0
  225. package/dist/workspace/ci-serve.d.ts +214 -0
  226. package/dist/workspace/ci-serve.d.ts.map +1 -0
  227. package/dist/workspace/ci-serve.js +412 -0
  228. package/dist/workspace/declaration.d.ts +16 -0
  229. package/dist/workspace/declaration.d.ts.map +1 -1
  230. package/dist/workspace/declaration.js +26 -0
  231. package/dist/workspace/declaration.schema.json +64 -0
  232. package/dist/workspace/declared-kinds.d.ts +16 -0
  233. package/dist/workspace/declared-kinds.d.ts.map +1 -1
  234. package/dist/workspace/declared-kinds.js +25 -0
  235. package/dist/workspace/delivery-metrics.d.ts +91 -0
  236. package/dist/workspace/delivery-metrics.d.ts.map +1 -0
  237. package/dist/workspace/delivery-metrics.js +217 -0
  238. package/dist/workspace/delivery-metrics.schema.json +519 -0
  239. package/dist/workspace/flags-read.d.ts +37 -0
  240. package/dist/workspace/flags-read.d.ts.map +1 -0
  241. package/dist/workspace/flags-read.js +103 -0
  242. package/dist/workspace/flags.d.ts +100 -0
  243. package/dist/workspace/flags.d.ts.map +1 -0
  244. package/dist/workspace/flags.js +98 -0
  245. package/dist/workspace/graph-cli.js +1 -1
  246. package/dist/workspace/intent-cli.d.ts +11 -2
  247. package/dist/workspace/intent-cli.d.ts.map +1 -1
  248. package/dist/workspace/intent-cli.js +54 -5
  249. package/dist/workspace/intent-trace.d.ts +219 -0
  250. package/dist/workspace/intent-trace.d.ts.map +1 -0
  251. package/dist/workspace/intent-trace.js +314 -0
  252. package/dist/workspace/intent.d.ts +30 -6
  253. package/dist/workspace/intent.d.ts.map +1 -1
  254. package/dist/workspace/intent.js +141 -21
  255. package/dist/workspace/intent.schema.json +702 -6
  256. package/dist/workspace/overlap.d.ts +146 -0
  257. package/dist/workspace/overlap.d.ts.map +1 -0
  258. package/dist/workspace/overlap.js +276 -0
  259. package/dist/workspace/pr-report.schema.json +5 -0
  260. package/dist/workspace/reason-codes.d.ts +2 -0
  261. package/dist/workspace/reason-codes.d.ts.map +1 -1
  262. package/dist/workspace/reason-codes.js +3 -0
  263. package/dist/workspace/records.d.ts +15 -2
  264. package/dist/workspace/records.d.ts.map +1 -1
  265. package/dist/workspace/records.js +32 -0
  266. package/dist/workspace/records.schema.json +21 -2
  267. package/dist/workspace/reference-kinds/flag/flag.kind.mjs +32 -0
  268. package/dist/workspace/reference-kinds/flag/flag.schema.json +53 -0
  269. package/dist/workspace/releases.d.ts +9 -0
  270. package/dist/workspace/releases.d.ts.map +1 -1
  271. package/dist/workspace/releases.js +9 -0
  272. package/dist/workspace/releases.schema.json +123 -1
  273. package/dist/workspace/run-event.schema.json +24 -6
  274. package/dist/workspace/status-gates.d.ts +8 -0
  275. package/dist/workspace/status-gates.d.ts.map +1 -1
  276. package/dist/workspace/status-stewards.d.ts +2 -0
  277. package/dist/workspace/status-stewards.d.ts.map +1 -1
  278. package/dist/workspace/status-stewards.js +1 -0
  279. package/dist/workspace/status.d.ts +78 -0
  280. package/dist/workspace/status.d.ts.map +1 -1
  281. package/dist/workspace/status.js +170 -3
  282. package/dist/workspace/status.schema.json +210 -2
  283. package/dist/workspace/wip.d.ts.map +1 -1
  284. package/dist/workspace/wip.js +11 -3
  285. package/package.json +1 -1
  286. package/src/build-params.test.ts +46 -0
  287. package/src/build-params.ts +55 -1
  288. package/src/change-set-graph.ts +10 -2
  289. package/src/change-set.ts +14 -3
  290. package/src/cli/build-params-cli.ts +22 -9
  291. package/src/cli/commands/build.ts +6 -3
  292. package/src/cli/handlers/approve-release.test.ts +5 -1
  293. package/src/cli/handlers/approve-revoked.test.ts +155 -0
  294. package/src/cli/handlers/approve.ts +47 -0
  295. package/src/cli/handlers/build.test.ts +30 -0
  296. package/src/cli/handlers/build.ts +20 -4
  297. package/src/cli/handlers/fan-out.test.ts +262 -1
  298. package/src/cli/handlers/fan-out.ts +72 -8
  299. package/src/cli/handlers/intent-trace.e2e.test.ts +201 -0
  300. package/src/cli/handlers/lifecycle-gc.test.ts +126 -0
  301. package/src/cli/handlers/lifecycle-gc.ts +146 -0
  302. package/src/cli/handlers/pr.test.ts +148 -1
  303. package/src/cli/handlers/pr.ts +144 -28
  304. package/src/cli/handlers/promote.test.ts +280 -2
  305. package/src/cli/handlers/promote.ts +210 -34
  306. package/src/cli/handlers/release-implicit.e2e.test.ts +12 -4
  307. package/src/cli/handlers/release-join.e2e.test.ts +185 -0
  308. package/src/cli/handlers/release-publish-targets.e2e.test.ts +295 -0
  309. package/src/cli/handlers/release-verify.e2e.test.ts +117 -0
  310. package/src/cli/handlers/release-workstation-ci.e2e.test.ts +135 -0
  311. package/src/cli/handlers/release.e2e.test.ts +146 -32
  312. package/src/cli/handlers/release.ts +415 -28
  313. package/src/cli/handlers/run-auto-release.test.ts +9 -0
  314. package/src/cli/handlers/run-release.test.ts +55 -0
  315. package/src/cli/handlers/run.ts +69 -5
  316. package/src/cli/main.test.ts +1 -0
  317. package/src/cli/main.ts +91 -10
  318. package/src/cli/parse-args.ts +54 -2
  319. package/src/cli/registry.ts +32 -4
  320. package/src/cli/run-events.ts +5 -2
  321. package/src/components/auto-release.test.ts +9 -0
  322. package/src/components/auto-release.ts +3 -0
  323. package/src/components/capability.ts +9 -0
  324. package/src/components/driver.test.ts +24 -0
  325. package/src/components/driver.ts +36 -0
  326. package/src/components/fan-out-inputs.test.ts +85 -0
  327. package/src/components/fan-out-inputs.ts +192 -0
  328. package/src/components/fan-out-record.test.ts +10 -0
  329. package/src/components/fan-out-record.ts +19 -0
  330. package/src/components/fan-out-run.ts +48 -0
  331. package/src/components/fan-out.ts +4 -0
  332. package/src/components/pr-pipeline.ts +67 -4
  333. package/src/components/pr-run.test.ts +127 -0
  334. package/src/components/pr-run.ts +32 -12
  335. package/src/components/promote.ts +68 -2
  336. package/src/components/release-compile.ts +17 -3
  337. package/src/components/release-gate.ts +18 -4
  338. package/src/components/wave-plan.ts +45 -2
  339. package/src/config.ts +8 -0
  340. package/src/flag-params.test.ts +144 -0
  341. package/src/flag-params.ts +134 -0
  342. package/src/lifecycle/__fixtures__/publish-fakes.ts +351 -0
  343. package/src/lifecycle/change-set-store.ts +2 -1
  344. package/src/lifecycle/delivery-metrics.ts +263 -0
  345. package/src/lifecycle/deploy-outcome.ts +147 -0
  346. package/src/lifecycle/gate-change-set.ts +37 -6
  347. package/src/lifecycle/gate-ledger.ts +2 -2
  348. package/src/lifecycle/git.ts +61 -8
  349. package/src/lifecycle/index.ts +4 -0
  350. package/src/lifecycle/mixed-environment.test.ts +97 -0
  351. package/src/lifecycle/mixed-environment.ts +204 -0
  352. package/src/lifecycle/publish-attachments.ts +196 -0
  353. package/src/lifecycle/publish-credentials.ts +84 -0
  354. package/src/lifecycle/publish-declaration.ts +95 -0
  355. package/src/lifecycle/publish-forge.ts +325 -0
  356. package/src/lifecycle/publish-npm.ts +259 -0
  357. package/src/lifecycle/publish-oci.ts +396 -0
  358. package/src/lifecycle/publish-record.ts +113 -0
  359. package/src/lifecycle/publish-resume.test.ts +168 -0
  360. package/src/lifecycle/publish-targets.test.ts +494 -0
  361. package/src/lifecycle/publish-targets.ts +100 -0
  362. package/src/lifecycle/release-cut.test.ts +74 -2
  363. package/src/lifecycle/release-cut.ts +158 -22
  364. package/src/lifecycle/release-flag.test.ts +112 -0
  365. package/src/lifecycle/release-join.test.ts +68 -0
  366. package/src/lifecycle/release-join.ts +182 -0
  367. package/src/lifecycle/release-ledger.ts +9 -3
  368. package/src/lifecycle/release-publish.test.ts +225 -0
  369. package/src/lifecycle/release-publish.ts +568 -0
  370. package/src/lifecycle/release-revocation.ts +147 -0
  371. package/src/lifecycle/release-store.ts +15 -4
  372. package/src/lifecycle/release-verify.test.ts +110 -0
  373. package/src/lifecycle/release-verify.ts +120 -0
  374. package/src/lifecycle/release-workstation.test.ts +204 -0
  375. package/src/lifecycle/release-workstation.ts +216 -0
  376. package/src/lifecycle/retention-growth.e2e.test.ts +279 -0
  377. package/src/lifecycle/retention-policy.ts +63 -0
  378. package/src/lifecycle/retention.test.ts +363 -0
  379. package/src/lifecycle/retention.ts +508 -0
  380. package/src/lifecycle/run-ledger.ts +7 -1
  381. package/src/op/activities/activity-contracts.ts +1 -0
  382. package/src/op/activities/shell.ts +7 -1
  383. package/src/op/local-executor.ts +27 -4
  384. package/src/op/runtime.ts +42 -0
  385. package/src/plan-summary.test.ts +20 -0
  386. package/src/plan-summary.ts +8 -1
  387. package/src/pr-forge.test.ts +25 -0
  388. package/src/pr-forge.ts +62 -0
  389. package/src/pr-loop.test.ts +13 -1
  390. package/src/pr-loop.ts +41 -4
  391. package/src/pr-report-url.test.ts +131 -0
  392. package/src/pr-report-url.ts +209 -0
  393. package/src/provenance.ts +2 -2
  394. package/src/run-event-store.ts +63 -3
  395. package/src/run-event-stream.ts +8 -4
  396. package/src/run-event.ts +38 -1
  397. package/src/workspace/__fixtures__/flag-workspace.ts +132 -0
  398. package/src/workspace/__fixtures__/older-records/change-set-984534ea9.json +100 -0
  399. package/src/workspace/__fixtures__/older-records/older-records.json +326 -0
  400. package/src/workspace/change-set.schema.json +5 -1
  401. package/src/workspace/check.schema.json +4 -2
  402. package/src/workspace/checks/records.ts +35 -1
  403. package/src/workspace/checks.ts +2 -1
  404. package/src/workspace/ci-green-forge.ts +2 -0
  405. package/src/workspace/ci-green.ts +20 -5
  406. package/src/workspace/ci-run.test.ts +416 -0
  407. package/src/workspace/ci-run.ts +571 -0
  408. package/src/workspace/ci-serve.test.ts +268 -0
  409. package/src/workspace/ci-serve.ts +521 -0
  410. package/src/workspace/declaration.schema.json +64 -0
  411. package/src/workspace/declaration.test.ts +28 -0
  412. package/src/workspace/declaration.ts +38 -0
  413. package/src/workspace/declared-kinds.ts +40 -1
  414. package/src/workspace/delivery-metrics.schema.json +519 -0
  415. package/src/workspace/delivery-metrics.test.ts +236 -0
  416. package/src/workspace/delivery-metrics.ts +286 -0
  417. package/src/workspace/flags-read.ts +121 -0
  418. package/src/workspace/flags.test.ts +141 -0
  419. package/src/workspace/flags.ts +157 -0
  420. package/src/workspace/graph-cli.ts +1 -1
  421. package/src/workspace/intent-cli.ts +46 -5
  422. package/src/workspace/intent-trace.test.ts +173 -0
  423. package/src/workspace/intent-trace.ts +484 -0
  424. package/src/workspace/intent.schema.json +702 -6
  425. package/src/workspace/intent.ts +148 -25
  426. package/src/workspace/older-records.test.ts +412 -0
  427. package/src/workspace/overlap.test.ts +165 -0
  428. package/src/workspace/overlap.ts +340 -0
  429. package/src/workspace/pr-report.schema.json +5 -0
  430. package/src/workspace/read-contract.e2e.test.ts +2 -1
  431. package/src/workspace/reason-codes.test.ts +5 -0
  432. package/src/workspace/reason-codes.ts +3 -0
  433. package/src/workspace/record-assets.test.ts +2 -1
  434. package/src/workspace/records-contract.e2e.test.ts +2 -1
  435. package/src/workspace/records.schema.json +21 -2
  436. package/src/workspace/records.ts +36 -2
  437. package/src/workspace/reference-kinds/flag/flag.kind.mjs +32 -0
  438. package/src/workspace/reference-kinds/flag/flag.schema.json +53 -0
  439. package/src/workspace/releases.schema.json +123 -1
  440. package/src/workspace/releases.ts +11 -0
  441. package/src/workspace/run-event.schema.json +24 -6
  442. package/src/workspace/status-contract.test.ts +29 -0
  443. package/src/workspace/status-gates.ts +8 -0
  444. package/src/workspace/status-mixed.test.ts +152 -0
  445. package/src/workspace/status-revoked.test.ts +153 -0
  446. package/src/workspace/status-stewards.ts +3 -0
  447. package/src/workspace/status.schema.json +210 -2
  448. package/src/workspace/status.ts +254 -3
  449. package/src/workspace/wip.ts +11 -2
@@ -0,0 +1,126 @@
1
+ /** #4036: `chant lifecycle gc [--yes] [--push] [--json]`, in temp repos. */
2
+
3
+ import { execFileSync } from "node:child_process";
4
+ import { mkdtempSync, realpathSync, rmSync, writeFileSync } from "node:fs";
5
+ import { tmpdir } from "node:os";
6
+ import { join } from "node:path";
7
+ import { afterEach, beforeEach, describe, expect, test, vi } from "vitest";
8
+ import type { ParsedArgs } from "../registry";
9
+ import { runLifecycleGc } from "./lifecycle-gc";
10
+ import { writeLedgerFiles } from "../../lifecycle/git";
11
+ import { makeRelease } from "../../lifecycle/release-store";
12
+ import type { GcReport } from "../../lifecycle/retention";
13
+
14
+ function args(o: Partial<ParsedArgs>): ParsedArgs {
15
+ return { command: "lifecycle", path: ".", format: "", fix: false, watch: false, verbose: false, help: false, live: false, ...o };
16
+ }
17
+
18
+ const git = (cwd: string, ...a: string[]) => execFileSync("git", a, { cwd, encoding: "utf-8" }).trim();
19
+
20
+ let dir: string;
21
+ let cwd: string;
22
+ let out: string[];
23
+ let err: string[];
24
+ beforeEach(() => {
25
+ cwd = process.cwd();
26
+ dir = realpathSync(mkdtempSync(join(tmpdir(), "lifecycle-gc-")));
27
+ vi.stubEnv("CHANT_RUN_EVENTS_DIR", join(dir, ".events"));
28
+ git(dir, "init", "-q", "-b", "main");
29
+ git(dir, "config", "user.email", "t@t");
30
+ git(dir, "config", "user.name", "t");
31
+ process.chdir(dir);
32
+ out = [];
33
+ err = [];
34
+ vi.spyOn(console, "log").mockImplementation((s: string) => void out.push(String(s)));
35
+ vi.spyOn(console, "error").mockImplementation((s: string) => void err.push(String(s)));
36
+ });
37
+ afterEach(() => {
38
+ vi.restoreAllMocks();
39
+ vi.unstubAllEnvs();
40
+ process.chdir(cwd);
41
+ rmSync(dir, { recursive: true, force: true });
42
+ });
43
+
44
+ function commitAll(): string {
45
+ writeFileSync(join(dir, "README.md"), "x\n");
46
+ git(dir, "add", ".");
47
+ git(dir, "commit", "-q", "-m", "init");
48
+ return git(dir, "rev-parse", "HEAD");
49
+ }
50
+
51
+ const changeSet = `_change-sets/jcs1-sha256_${"1".repeat(64)}.json`;
52
+ const ctx = (o: Partial<ParsedArgs>) => ({ args: args(o), plugins: [], serializers: [] });
53
+ const report = (): GcReport => JSON.parse(out.join("\n")) as GcReport;
54
+
55
+ describe("chant lifecycle gc", () => {
56
+ test("a dry run by default; --yes prunes in one local commit and pushes nothing", async () => {
57
+ writeFileSync(join(dir, "chant.workspace.json"), JSON.stringify({ name: "w", schema: 1, members: [], retention: { changeSets: { days: 0 } } }));
58
+ commitAll();
59
+ await writeLedgerFiles([{ path: changeSet, content: "{}\n", expectPriorSha: null }], "fixture", { cwd: dir });
60
+ const tip = git(dir, "rev-parse", "chant/lifecycle");
61
+
62
+ expect(await runLifecycleGc(ctx({}))).toBe(0);
63
+ expect(out[0]).toMatch(/^chant\/lifecycle at [0-9a-f]{12}: would remove 1 change set\(s\), 0 release\(s\) and 0 template\(s\)/);
64
+ expect(err.join("\n")).toContain("Dry run: nothing was removed");
65
+ expect(git(dir, "rev-parse", "chant/lifecycle")).toBe(tip);
66
+
67
+ out = [];
68
+ expect(await runLifecycleGc(ctx({ yes: true, json: true }))).toBe(0);
69
+ const r = report();
70
+ expect(r.dryRun).toBe(false);
71
+ expect(r.policy.changeSets.days).toBe(0);
72
+ expect(r.lifecycle.remove.map((f) => f.path)).toEqual([changeSet]);
73
+ expect(r.lifecycle.commit).toBe(git(dir, "rev-parse", "chant/lifecycle"));
74
+ expect(git(dir, "rev-parse", "chant/lifecycle^")).toBe(tip);
75
+ expect(r.lifecycle.pushed).toBe(false);
76
+ });
77
+
78
+ test("outside a workspace the defaults apply, and a release of a revoked commit is not promotable", async () => {
79
+ const head = commitAll();
80
+ const cut = (commit: string) => {
81
+ const r = makeRelease({ member: null, commit, artifactDigests: [], targets: [{ env: "prod", templates: [], params: [] }] });
82
+ return { r, line: JSON.stringify({ version: 1, releaseDigest: r.releaseDigest, label: null, actor: "a", timestamp: new Date().toISOString(), runOrigin: null, signature: null }) };
83
+ };
84
+ const revoked = cut(head);
85
+ const fine = cut("b".repeat(40));
86
+ const file = (d: string) => `_releases/${d.replace(/:/g, "_")}.json`;
87
+ await writeLedgerFiles(
88
+ [
89
+ { path: changeSet, content: "{}\n" },
90
+ { path: file(revoked.r.releaseDigest), content: `${JSON.stringify(revoked.r)}\n` },
91
+ { path: file(fine.r.releaseDigest), content: `${JSON.stringify(fine.r)}\n` },
92
+ { path: "_releases/cuts.jsonl", content: `${revoked.line}\n${fine.line}\n` },
93
+ ],
94
+ "fixture",
95
+ { cwd: dir },
96
+ );
97
+ git(dir, "tag", `ci/revoked/${head}`, head);
98
+
99
+ expect(await runLifecycleGc(ctx({ json: true }))).toBe(0);
100
+ const r = report();
101
+ expect(r.policy.releases).toEqual({ days: 14, keep: 10 });
102
+ expect(r.lifecycle.remove.map((f) => f.path)).toEqual([file(revoked.r.releaseDigest)]);
103
+ expect(r.lifecycle.keep.map((f) => [f.path, f.reasons.map((x) => x.code)])).toEqual(
104
+ expect.arrayContaining([[changeSet, ["within-window"]], [file(fine.r.releaseDigest), ["newest", "within-window"]]]),
105
+ );
106
+ });
107
+
108
+ test("a workspace whose declaration can't be read prunes nothing", async () => {
109
+ writeFileSync(join(dir, "chant.workspace.json"), JSON.stringify({ name: "w", schema: 1, members: [], retention: { changeSets: { days: -1 } } }));
110
+ commitAll();
111
+ expect(await runLifecycleGc(ctx({ yes: true }))).toBe(1);
112
+ expect(err.join("\n")).toContain("its retention policy is unknown and nothing is pruned");
113
+ });
114
+
115
+ test("a directory outside git is refused", async () => {
116
+ const plain = realpathSync(mkdtempSync(join(tmpdir(), "lifecycle-gc-plain-")));
117
+ try {
118
+ process.chdir(plain);
119
+ expect(await runLifecycleGc(ctx({}))).toBe(1);
120
+ expect(err.join("\n")).toContain("not in a git repository");
121
+ } finally {
122
+ process.chdir(dir);
123
+ rmSync(plain, { recursive: true, force: true });
124
+ }
125
+ });
126
+ });
@@ -0,0 +1,146 @@
1
+ /**
2
+ * `chant lifecycle gc [--yes] [--push] [--json] [--verbose]` (#4036): prune
3
+ * stored change sets, releases and their templates from `chant/lifecycle`, and
4
+ * run event files from this host, by the retention policy.
5
+ *
6
+ * Without `--yes` it is a dry run: it prints what it would remove and changes
7
+ * nothing. With `--yes` it writes one commit to the local `chant/lifecycle`
8
+ * that removes those files, and deletes the run event files. It pushes only
9
+ * with `--push`. History is never rewritten, so a pruned file stays in the
10
+ * branch's earlier commits. The rules are in ../../lifecycle/retention.ts.
11
+ *
12
+ * The policy is the defaults, overridden by the `retention` block of the
13
+ * workspace declaration at base when the checkout is a workspace. The prune
14
+ * covers the whole branch, every member, from wherever in the repository it
15
+ * runs.
16
+ */
17
+
18
+ import { execFileSync } from "node:child_process";
19
+ import { existsSync } from "node:fs";
20
+ import { dirname, join, resolve } from "node:path";
21
+ import { formatError, formatSuccess, formatWarning } from "../format";
22
+ import type { CommandContext } from "../registry";
23
+ import type { RetentionPolicy } from "../../lifecycle/retention-policy";
24
+
25
+ const USAGE = "chant lifecycle gc [--yes] [--push] [--json] [--verbose]";
26
+
27
+ function tryGit(cwd: string, args: string[]): string | null {
28
+ try {
29
+ return execFileSync("git", args, { cwd, encoding: "utf-8", stdio: ["ignore", "pipe", "ignore"] }).trim();
30
+ } catch {
31
+ return null;
32
+ }
33
+ }
34
+
35
+ /** Whether a workspace declaration sits in `cwd` or a directory above it, up to the repository's top. */
36
+ function inWorkspace(cwd: string, top: string): boolean {
37
+ for (let dir = cwd; ; dir = dirname(dir)) {
38
+ if (existsSync(join(dir, "chant.workspace.json")) || existsSync(join(dir, "chant.workspace.jsonc"))) return true;
39
+ if (dir === top || dirname(dir) === dir) return false;
40
+ }
41
+ }
42
+
43
+ /** Every commit with a `ci/revoked/<sha>` tag, counted as `chant release cut` counts it. */
44
+ async function revokedCommits(cwd: string, top: string, green: import("../../workspace/declaration").CiGreen | null, workspace: boolean): Promise<Set<string>> {
45
+ if (workspace) {
46
+ const { readCiTags } = await import("../../workspace/ci-green");
47
+ const base = green
48
+ ? (tryGit(top, ["rev-parse", "--verify", "--quiet", `refs/remotes/origin/${green.branch}^{commit}`]) ??
49
+ tryGit(top, ["rev-parse", "--verify", "--quiet", `refs/heads/${green.branch}^{commit}`]) ??
50
+ tryGit(top, ["rev-parse", "HEAD"]))
51
+ : null;
52
+ return new Set(readCiTags(top, green, base).revoked.keys());
53
+ }
54
+ // With no declaration every tag counts, as readCiTags counts them without `ci.green`.
55
+ const out = tryGit(cwd, ["for-each-ref", "--format=%(refname:lstrip=4)", "refs/tags/ci/revoked/"]) ?? "";
56
+ return new Set(out.split("\n").filter((s) => /^[0-9a-f]{40,64}$/.test(s)));
57
+ }
58
+
59
+ const kb = (bytes: number): string => (bytes >= 1024 * 1024 ? `${(bytes / 1024 / 1024).toFixed(1)} MB` : `${(bytes / 1024).toFixed(1)} KB`);
60
+
61
+ export async function runLifecycleGc(ctx: CommandContext): Promise<number> {
62
+ const { args } = ctx;
63
+ if (args.extraPositional) {
64
+ console.error(formatError({ message: `lifecycle gc takes no argument, and was given "${args.extraPositional}"`, hint: USAGE }));
65
+ return 1;
66
+ }
67
+ const cwd = resolve(".");
68
+ const top = tryGit(cwd, ["rev-parse", "--show-toplevel"]);
69
+ if (!top) {
70
+ console.error(formatError({ message: "lifecycle gc prunes the chant/lifecycle branch, and this directory is not in a git repository", hint: USAGE }));
71
+ return 1;
72
+ }
73
+ const dryRun = args.yes !== true;
74
+
75
+ const { resolveRetention } = await import("../../lifecycle/retention-policy");
76
+ let policy: RetentionPolicy = resolveRetention(null);
77
+ let green: import("../../workspace/declaration").CiGreen | null = null;
78
+ const workspace = inWorkspace(cwd, top);
79
+ if (workspace) {
80
+ const { scopeSource } = await import("../../workspace/write-scope");
81
+ const source = scopeSource(cwd);
82
+ if (!source.declaration) {
83
+ console.error(formatError({ message: `the workspace declaration can't be read, so its retention policy is unknown and nothing is pruned: ${source.failure?.message ?? "no declaration"}` }));
84
+ return 1;
85
+ }
86
+ policy = resolveRetention(source.declaration.retention);
87
+ green = source.declaration.ci?.green ?? null;
88
+ }
89
+
90
+ // Read the remote's facts first: a prune planned on a stale branch could
91
+ // remove what a newer gate binds, and a forked branch must not be pushed over.
92
+ const { fetchLifecycleStatus } = await import("../../lifecycle/git");
93
+ const fetched = await fetchLifecycleStatus({ cwd });
94
+ if (fetched.status === "diverged" || fetched.status === "unreachable") {
95
+ const why = fetched.status === "diverged" ? `the local chant/lifecycle and ${fetched.remote}'s have forked` : `${fetched.remote} did not answer: ${fetched.stderr}`;
96
+ if (!dryRun) {
97
+ console.error(formatError({ message: `nothing was pruned: ${why}`, hint: "Bring chant/lifecycle up to date with its remote, then run again." }));
98
+ return 1;
99
+ }
100
+ if (!args.json) console.error(formatWarning({ message: `planned on the local branch only: ${why}` }));
101
+ }
102
+
103
+ const { runLifecycleGc: run } = await import("../../lifecycle/retention");
104
+ const report = await run({
105
+ cwd,
106
+ policy,
107
+ now: new Date(),
108
+ revokedCommits: await revokedCommits(cwd, top, green, workspace),
109
+ dryRun,
110
+ push: args.push === true,
111
+ });
112
+
113
+ if (args.json) {
114
+ console.log(JSON.stringify(report, null, 2));
115
+ return 0;
116
+ }
117
+
118
+ const lc = report.lifecycle;
119
+ const count = (list: ReadonlyArray<{ kind: string }>, kind: string) => list.filter((f) => f.kind === kind).length;
120
+ const bytes = lc.remove.reduce((n, f) => n + f.bytes, 0);
121
+ const verb = dryRun ? "would remove" : "removed";
122
+ if (!lc.tip) {
123
+ console.error("No chant/lifecycle branch: nothing stored to prune.");
124
+ } else {
125
+ console.log(
126
+ `chant/lifecycle at ${lc.tip.slice(0, 12)}: ${verb} ${count(lc.remove, "change-set")} change set(s), ${count(lc.remove, "release")} release(s) and ${count(lc.remove, "template")} template(s), ${kb(bytes)}; ` +
127
+ `keeps ${count(lc.keep, "change-set")}, ${count(lc.keep, "release")} and ${count(lc.keep, "template")}`,
128
+ );
129
+ console.log(` tree at the tip: ${lc.before.files} file(s), ${kb(lc.before.bytes)} before; ${lc.after.files} file(s), ${kb(lc.after.bytes)} after. The ledgers (${kb(lc.before.ledgers.bytes)}) are never pruned, and history keeps every removed file.`);
130
+ if (args.verbose) for (const f of lc.remove) console.log(` - ${f.path}`);
131
+ }
132
+ const ev = report.runEvents;
133
+ if (ev.dir) {
134
+ console.log(` run events in ${ev.dir}: ${verb} ${ev.remove.length} file(s), ${kb(ev.remove.reduce((n, f) => n + f.bytes, 0))}; keeps ${ev.kept.files}`);
135
+ if (args.verbose) for (const f of ev.remove) console.log(` - ${f.file}`);
136
+ }
137
+ if (dryRun) {
138
+ console.error("Dry run: nothing was removed. Run again with --yes to prune.");
139
+ } else if (lc.commit) {
140
+ console.error(formatSuccess(`Pruned in commit ${lc.commit.slice(0, 12)} on chant/lifecycle`));
141
+ if (lc.pushed) console.error(" pushed");
142
+ else if (lc.pushWarning) console.error(formatWarning({ message: lc.pushWarning }));
143
+ else console.error(" not pushed: run with --push, or push chant/lifecycle yourself");
144
+ }
145
+ return 0;
146
+ }
@@ -8,6 +8,7 @@
8
8
 
9
9
  import { mkdtemp, rm } from "node:fs/promises";
10
10
  import { tmpdir } from "node:os";
11
+ import { readFileSync } from "node:fs";
11
12
  import { join } from "node:path";
12
13
  import { afterEach, beforeEach, describe, expect, test, vi } from "vitest";
13
14
  import type { DriverComponent } from "../../components/driver";
@@ -58,6 +59,8 @@ vi.mock("../../op/gate", async () => {
58
59
  });
59
60
 
60
61
  const failing = new Set<string>();
62
+ /** Called as a component's apply starts, before it can fail. */
63
+ let onRun: (root: string) => void = () => {};
61
64
  vi.mock("../../components/fan-out-support", async () => {
62
65
  const { CapabilityRegistry } = await import("../../components/capability");
63
66
  const { planFanOut } = await import("../../components/fan-out");
@@ -78,6 +81,7 @@ vi.mock("../../components/fan-out-support", async () => {
78
81
  return {};
79
82
  },
80
83
  async run(_ctx: DeployContext, input: { root: string }) {
84
+ onRun(input.root);
81
85
  if (failing.has(input.root)) throw new Error(`${input.root} failed`);
82
86
  return { outputs: {} };
83
87
  },
@@ -106,6 +110,7 @@ const { checkRunEventLines, stepTrail } = await import("../../workspace/__fixtur
106
110
 
107
111
  let dir: string;
108
112
  let out: string[];
113
+ let errors: string[];
109
114
 
110
115
  const ctx = (a: Partial<ParsedArgs>) =>
111
116
  ({ args: { command: "components", path: "pr-apply", format: "", fix: false, watch: false, verbose: false, help: false, live: false, base: "main", env: "prod", output: dir, eventsJson: true, ...a } as ParsedArgs, plugins: [] }) as never;
@@ -113,10 +118,12 @@ const ctx = (a: Partial<ParsedArgs>) =>
113
118
  beforeEach(async () => {
114
119
  dir = await mkdtemp(join(tmpdir(), "pr-events-"));
115
120
  out = [];
121
+ errors = [];
116
122
  failing.clear();
123
+ onRun = () => {};
117
124
  gateLedger = memoryGateLedgerPort();
118
125
  vi.spyOn(process.stdout, "write").mockImplementation((chunk: string | Uint8Array) => { out.push(String(chunk)); return true; });
119
- vi.spyOn(console, "error").mockImplementation(() => {});
126
+ vi.spyOn(console, "error").mockImplementation((...m: unknown[]) => { errors.push(m.map(String).join(" ")); });
120
127
  vi.spyOn(console, "log").mockImplementation(() => {});
121
128
  });
122
129
 
@@ -182,6 +189,146 @@ describe("pr-apply --events-json", () => {
182
189
  });
183
190
  });
184
191
 
192
+ /** Plan pull request 12 and approve its digest, as a reviewer would. */
193
+ async function planAndApprove(): Promise<void> {
194
+ await runComponentsPrPlan(ctx({ pr: 12 }));
195
+ const digest = await plannedDigest();
196
+ gateLedger = memoryGateLedgerPort({
197
+ resolutions: [{ version: 1, kind: "resolution", op: "pr-12", gate: "pr-apply", resolvedBy: "github:alice", timestamp: new Date().toISOString(), planDigest: digest }],
198
+ });
199
+ out = [];
200
+ errors = [];
201
+ }
202
+
203
+ /** The job log's wave and component lines, without colour. */
204
+ const logLines = (): string[] =>
205
+ errors.map((l) => l.replace(/\u001b\[[0-9;]*m/g, "")).filter((l) => /^wave \d|^ {2}\S+: /.test(l));
206
+
207
+ describe("pr-apply's job log (#4007)", () => {
208
+ test("without --events-json, a line as each wave starts and as each component settles, in order", async () => {
209
+ await planAndApprove();
210
+ expect(await runComponentsPrApply(ctx({ pr: 12, eventsJson: false }))).toBe(0);
211
+ expect(out).toEqual([]);
212
+ expect(logLines()).toEqual(["wave 1/2: net", " net: applied", "wave 2/2: app", " app: applied"]);
213
+ });
214
+
215
+ test("a failed dependency: the component behind it settles blocked, naming it, in the log and the stream", async () => {
216
+ await planAndApprove();
217
+ failing.add("net");
218
+ expect(await runComponentsPrApply(ctx({ pr: 12 }))).toBe(1);
219
+ expect(logLines()).toEqual(["wave 1/2: net", " net: failed", " app: blocked by net, not run"]);
220
+ const events = checkRunEventLines(out);
221
+ const blocked = events.find((e) => e.type === "component-settle" && e.component === "app");
222
+ expect(blocked).toMatchObject({ type: "component-settle", wave: 2, component: "app", status: "blocked", blockedBy: "net" });
223
+ expect(events.some((e) => e.type === "component-start" && e.component === "app")).toBe(false);
224
+ expect(events.some((e) => e.type === "wave-start" && e.wave === 2)).toBe(false);
225
+ expect(events.at(-1)).toMatchObject({ type: "run-settle", status: "failed" });
226
+ });
227
+
228
+ test("a resumed apply settles what the earlier attempt applied as already-applied, before the gate", async () => {
229
+ await planAndApprove();
230
+ const resume = join(dir, "attempt.json");
231
+ failing.add("app");
232
+ expect(await runComponentsPrApply(ctx({ pr: 12, resume, eventsJson: false }))).toBe(1);
233
+ failing.clear();
234
+ out = [];
235
+ errors = [];
236
+ expect(await runComponentsPrApply(ctx({ pr: 12, resume }))).toBe(0);
237
+ const events = checkRunEventLines(out);
238
+ const settles = events.filter((e) => e.type === "component-settle").map((e) => [e.component, (e as { status: string }).status, (e as { wave: number }).wave]);
239
+ expect(settles).toEqual([["net", "already-applied", 1], ["app", "ok", 1]]);
240
+ const gate = events.findIndex((e) => e.type === "phase-start" && e.phase === "gate");
241
+ expect(events.findIndex((e) => e.type === "component-settle" && e.component === "net")).toBeLessThan(gate);
242
+ expect(logLines()).toEqual([" net: already applied in an earlier attempt", "wave 1/1: app", " app: applied"]);
243
+ });
244
+ });
245
+
246
+ describe("--resume carries only an unchanged input (#4081)", () => {
247
+ test("a component whose input moved since the attempt runs again, and says why", async () => {
248
+ await planAndApprove();
249
+ const resume = join(dir, "attempt.json");
250
+ failing.add("app");
251
+ expect(await runComponentsPrApply(ctx({ pr: 12, resume, eventsJson: false }))).toBe(1);
252
+ expect(JSON.parse(readFileSync(resume, "utf8")).inputs).toEqual({ net: expect.stringMatching(/^jcs1-sha256:/) });
253
+ failing.clear();
254
+ errors = [];
255
+ const step = ESTATE[0]!.deploy[0]!.steps[0] as Record<string, unknown>;
256
+ step.note = "a different change of the same shape";
257
+ const ran: string[] = [];
258
+ onRun = (name) => ran.push(name);
259
+ try {
260
+ expect(await runComponentsPrApply(ctx({ pr: 12, resume, eventsJson: false }))).toBe(0);
261
+ } finally {
262
+ delete step.note;
263
+ }
264
+ expect(ran).toEqual(["net", "app"]);
265
+ expect(errors.join("\n")).toContain("net: changed since the earlier attempt, so it runs again");
266
+ });
267
+
268
+ test("a record without input digests does not carry", async () => {
269
+ await planAndApprove();
270
+ const resume = join(dir, "attempt.json");
271
+ failing.add("app");
272
+ expect(await runComponentsPrApply(ctx({ pr: 12, resume, eventsJson: false }))).toBe(1);
273
+ const { writeFileSync } = await import("node:fs");
274
+ const record = JSON.parse(readFileSync(resume, "utf8"));
275
+ delete record.inputs;
276
+ writeFileSync(resume, JSON.stringify(record));
277
+ failing.clear();
278
+ errors = [];
279
+ const ran: string[] = [];
280
+ onRun = (name) => ran.push(name);
281
+ expect(await runComponentsPrApply(ctx({ pr: 12, resume, eventsJson: false }))).toBe(0);
282
+ expect(ran).toEqual(["net", "app"]);
283
+ expect(errors.join("\n")).toMatch(/does not carry: it does not record what each component applied/);
284
+ });
285
+ });
286
+
287
+ describe("--report-url (#4007)", () => {
288
+ test("refuses a URL it can't upload to, before anything plans", async () => {
289
+ expect(await runComponentsPrPlan(ctx({ pr: 12, reportUrl: "https://example.invalid/reports" }))).toBe(1);
290
+ expect(errors.join("\n")).toMatch(/takes an s3:\/\/<bucket>\/<prefix> or file:\/\/<directory> URL/);
291
+ expect(out).toEqual([]);
292
+ });
293
+
294
+ test("file://: the report, the change set and the events land under <pr>/<commit>/, the events as each wave settles", async () => {
295
+ const { pathToFileURL } = await import("node:url");
296
+ const { readFile } = await import("node:fs/promises");
297
+ const reports = join(dir, "reports");
298
+ const reportUrl = pathToFileURL(reports).href;
299
+ await runComponentsPrPlan(ctx({ pr: 12, reportUrl }));
300
+ const at = join(reports, "12", HEAD);
301
+ expect(JSON.parse(await readFile(join(at, "pr-plan.json"), "utf8"))).toMatchObject({ stage: "plan", pr: 12, status: "planned" });
302
+ expect(checkRunEventLines((await readFile(join(at, "pr-plan.events.jsonl"), "utf8")).split(/(?<=\n)/))[0]).toMatchObject({ type: "run-start" });
303
+
304
+ await planAndApprove();
305
+ // What a reader of the URL sees while wave 2 applies: wave 1 settled.
306
+ let seenMidRun: string[] = [];
307
+ onRun = (root) => {
308
+ if (root === "app") seenMidRun = readFileSync(join(at, "pr-apply.events.jsonl"), "utf8").split(/(?<=\n)/);
309
+ };
310
+ expect(await runComponentsPrApply(ctx({ pr: 12, reportUrl }))).toBe(0);
311
+ const mid = seenMidRun.map((l) => JSON.parse(l) as { type: string; wave?: number; status?: string });
312
+ expect(mid.some((e) => e.type === "wave-settle" && e.wave === 1 && e.status === "ok")).toBe(true);
313
+ expect(mid.some((e) => e.type === "run-settle")).toBe(false);
314
+
315
+ const final = checkRunEventLines((await readFile(join(at, "pr-apply.events.jsonl"), "utf8")).split(/(?<=\n)/));
316
+ expect(final.at(-1)).toMatchObject({ type: "run-settle", status: "ok" });
317
+ expect(final.map((e) => e.type).join("\n")).toBe(out.map((l) => JSON.parse(l).type).join("\n"));
318
+ expect(JSON.parse(await readFile(join(at, "pr-apply.json"), "utf8"))).toMatchObject({ stage: "apply", status: "applied" });
319
+ expect(JSON.parse(await readFile(join(at, "change-set.json"), "utf8"))).toHaveProperty("digest");
320
+ expect(errors.join("\n")).toContain(`report : ${reportUrl}/12/${HEAD}/`);
321
+ });
322
+
323
+ test("without --events-json, only the report and the change set are copied", async () => {
324
+ const { pathToFileURL } = await import("node:url");
325
+ const { readdir } = await import("node:fs/promises");
326
+ const reports = join(dir, "reports");
327
+ await runComponentsPrPlan(ctx({ pr: 12, reportUrl: pathToFileURL(reports).href, eventsJson: false }));
328
+ expect((await readdir(join(reports, "12", HEAD))).sort()).toEqual(["change-set.json", "pr-plan.json"]);
329
+ });
330
+ });
331
+
185
332
  describe("--events-json beside --json", () => {
186
333
  test("is refused on pr-plan and pr-apply before anything plans, since both print to stdout", async () => {
187
334
  const errors: string[] = [];