@intentius/chant 0.124.39 → 0.124.41

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 (337) hide show
  1. package/dist/__fixtures__/oidc-emulator.d.ts +49 -0
  2. package/dist/__fixtures__/oidc-emulator.d.ts.map +1 -0
  3. package/dist/__fixtures__/oidc-emulator.js +89 -0
  4. package/dist/ci-handover.d.ts +117 -0
  5. package/dist/ci-handover.d.ts.map +1 -0
  6. package/dist/ci-handover.js +217 -0
  7. package/dist/ci-identity.d.ts +190 -0
  8. package/dist/ci-identity.d.ts.map +1 -0
  9. package/dist/ci-identity.js +219 -0
  10. package/dist/cli/component-endpoint.d.ts +20 -0
  11. package/dist/cli/component-endpoint.d.ts.map +1 -0
  12. package/dist/cli/component-endpoint.js +36 -0
  13. package/dist/cli/handlers/approve.d.ts +2 -1
  14. package/dist/cli/handlers/approve.d.ts.map +1 -1
  15. package/dist/cli/handlers/approve.js +20 -6
  16. package/dist/cli/handlers/build.d.ts.map +1 -1
  17. package/dist/cli/handlers/build.js +15 -1
  18. package/dist/cli/handlers/fan-out.d.ts.map +1 -1
  19. package/dist/cli/handlers/fan-out.js +26 -4
  20. package/dist/cli/handlers/gate-resume.d.ts +3 -0
  21. package/dist/cli/handlers/gate-resume.d.ts.map +1 -1
  22. package/dist/cli/handlers/gate-resume.js +15 -2
  23. package/dist/cli/handlers/lock.d.ts +8 -0
  24. package/dist/cli/handlers/lock.d.ts.map +1 -0
  25. package/dist/cli/handlers/lock.js +197 -0
  26. package/dist/cli/handlers/pr-env.d.ts +34 -0
  27. package/dist/cli/handlers/pr-env.d.ts.map +1 -0
  28. package/dist/cli/handlers/pr-env.js +436 -0
  29. package/dist/cli/handlers/pr.d.ts +74 -0
  30. package/dist/cli/handlers/pr.d.ts.map +1 -1
  31. package/dist/cli/handlers/pr.js +514 -58
  32. package/dist/cli/handlers/promote.d.ts.map +1 -1
  33. package/dist/cli/handlers/promote.js +16 -2
  34. package/dist/cli/handlers/query.d.ts +6 -0
  35. package/dist/cli/handlers/query.d.ts.map +1 -0
  36. package/dist/cli/handlers/query.js +80 -0
  37. package/dist/cli/handlers/run.d.ts +4 -1
  38. package/dist/cli/handlers/run.d.ts.map +1 -1
  39. package/dist/cli/handlers/run.js +17 -6
  40. package/dist/cli/main.d.ts.map +1 -1
  41. package/dist/cli/main.js +41 -2
  42. package/dist/cli/parse-args.d.ts.map +1 -1
  43. package/dist/cli/parse-args.js +31 -2
  44. package/dist/cli/registry.d.ts +12 -2
  45. package/dist/cli/registry.d.ts.map +1 -1
  46. package/dist/components/auto-release.d.ts +35 -3
  47. package/dist/components/auto-release.d.ts.map +1 -1
  48. package/dist/components/auto-release.js +29 -2
  49. package/dist/components/cli-support.d.ts +2 -0
  50. package/dist/components/cli-support.d.ts.map +1 -1
  51. package/dist/components/cli-support.js +2 -0
  52. package/dist/components/deploy-units.d.ts.map +1 -1
  53. package/dist/components/deploy-units.js +5 -0
  54. package/dist/components/fan-out-run.d.ts +28 -0
  55. package/dist/components/fan-out-run.d.ts.map +1 -1
  56. package/dist/components/fan-out-run.js +27 -3
  57. package/dist/components/pr-environment.d.ts +298 -0
  58. package/dist/components/pr-environment.d.ts.map +1 -0
  59. package/dist/components/pr-environment.js +406 -0
  60. package/dist/components/pr-hooks.d.ts +66 -0
  61. package/dist/components/pr-hooks.d.ts.map +1 -0
  62. package/dist/components/pr-hooks.js +119 -0
  63. package/dist/components/pr-pipeline.d.ts +72 -2
  64. package/dist/components/pr-pipeline.d.ts.map +1 -1
  65. package/dist/components/pr-pipeline.js +96 -5
  66. package/dist/components/pr-policy.d.ts +109 -0
  67. package/dist/components/pr-policy.d.ts.map +1 -0
  68. package/dist/components/pr-policy.js +150 -0
  69. package/dist/components/pr-run.d.ts.map +1 -1
  70. package/dist/components/pr-run.js +5 -3
  71. package/dist/components/promote.d.ts.map +1 -1
  72. package/dist/components/promote.js +39 -16
  73. package/dist/gate-decision.d.ts +15 -2
  74. package/dist/gate-decision.d.ts.map +1 -1
  75. package/dist/gate-decision.js +17 -2
  76. package/dist/gate-policy.d.ts +140 -0
  77. package/dist/gate-policy.d.ts.map +1 -0
  78. package/dist/gate-policy.js +242 -0
  79. package/dist/lifecycle/__fixtures__/publish-fakes.d.ts.map +1 -1
  80. package/dist/lifecycle/__fixtures__/publish-fakes.js +6 -1
  81. package/dist/lifecycle/gate-checkpoint.d.ts +1 -1
  82. package/dist/lifecycle/gate-checkpoint.js +1 -1
  83. package/dist/{workspace/trust → lifecycle}/gate-seal.d.ts +1 -1
  84. package/dist/lifecycle/gate-seal.d.ts.map +1 -0
  85. package/dist/lifecycle/publish-declaration.d.ts.map +1 -1
  86. package/dist/lifecycle/publish-declaration.js +4 -3
  87. package/dist/lifecycle/publish-npm.d.ts +2 -2
  88. package/dist/lifecycle/publish-npm.d.ts.map +1 -1
  89. package/dist/lifecycle/publish-npm.js +31 -8
  90. package/dist/lifecycle/query.d.ts +68 -0
  91. package/dist/lifecycle/query.d.ts.map +1 -0
  92. package/dist/lifecycle/query.js +246 -0
  93. package/dist/lifecycle/release-ledger.d.ts +31 -0
  94. package/dist/lifecycle/release-ledger.d.ts.map +1 -1
  95. package/dist/lifecycle/release-ledger.js +19 -1
  96. package/dist/lifecycle/run-ledger.d.ts +13 -0
  97. package/dist/lifecycle/run-ledger.d.ts.map +1 -1
  98. package/dist/lifecycle/run-ledger.js +16 -0
  99. package/dist/lifecycle/status.d.ts +5 -1
  100. package/dist/lifecycle/status.d.ts.map +1 -1
  101. package/dist/lifecycle/status.js +10 -5
  102. package/dist/lifecycle/target-lock.d.ts +97 -0
  103. package/dist/lifecycle/target-lock.d.ts.map +1 -0
  104. package/dist/lifecycle/target-lock.js +286 -0
  105. package/dist/live-endpoint.d.ts +18 -0
  106. package/dist/live-endpoint.d.ts.map +1 -1
  107. package/dist/live-endpoint.js +27 -0
  108. package/dist/op/activities/activity-contracts.d.ts +30 -0
  109. package/dist/op/activities/activity-contracts.d.ts.map +1 -1
  110. package/dist/op/activities/activity-contracts.js +26 -0
  111. package/dist/op/activities/index.d.ts +2 -0
  112. package/dist/op/activities/index.d.ts.map +1 -1
  113. package/dist/op/activities/index.js +4 -0
  114. package/dist/op/activities/review-agent.d.ts +137 -0
  115. package/dist/op/activities/review-agent.d.ts.map +1 -0
  116. package/dist/op/activities/review-agent.js +266 -0
  117. package/dist/op/builders.d.ts +9 -0
  118. package/dist/op/builders.d.ts.map +1 -1
  119. package/dist/op/builders.js +11 -0
  120. package/dist/op/cron.d.ts +8 -0
  121. package/dist/op/cron.d.ts.map +1 -1
  122. package/dist/op/cron.js +51 -0
  123. package/dist/op/gate-approval.d.ts +20 -13
  124. package/dist/op/gate-approval.d.ts.map +1 -1
  125. package/dist/op/gate-approval.js +13 -18
  126. package/dist/op/gate-quorum.d.ts +131 -0
  127. package/dist/op/gate-quorum.d.ts.map +1 -0
  128. package/dist/op/gate-quorum.js +183 -0
  129. package/dist/op/gate-resume.d.ts +4 -2
  130. package/dist/op/gate-resume.d.ts.map +1 -1
  131. package/dist/op/gate-resume.js +5 -4
  132. package/dist/op/gate-review.js +1 -1
  133. package/dist/op/gate.d.ts +32 -4
  134. package/dist/op/gate.d.ts.map +1 -1
  135. package/dist/op/gate.js +40 -24
  136. package/dist/op/index.d.ts +6 -4
  137. package/dist/op/index.d.ts.map +1 -1
  138. package/dist/op/index.js +4 -3
  139. package/dist/op/local-executor.d.ts.map +1 -1
  140. package/dist/op/local-executor.js +9 -3
  141. package/dist/op/op-waves-run.d.ts +62 -1
  142. package/dist/op/op-waves-run.d.ts.map +1 -1
  143. package/dist/op/op-waves-run.js +190 -11
  144. package/dist/op/op-waves.d.ts +55 -3
  145. package/dist/op/op-waves.d.ts.map +1 -1
  146. package/dist/op/op-waves.js +63 -8
  147. package/dist/op/schedule-overdue.d.ts +57 -0
  148. package/dist/op/schedule-overdue.d.ts.map +1 -0
  149. package/dist/op/schedule-overdue.js +138 -0
  150. package/dist/override-gate.d.ts +2 -2
  151. package/dist/override-gate.d.ts.map +1 -1
  152. package/dist/override-gate.js +1 -1
  153. package/dist/pr-forge.d.ts +36 -0
  154. package/dist/pr-forge.d.ts.map +1 -1
  155. package/dist/pr-forge.js +82 -0
  156. package/dist/pr-loop.d.ts +90 -1
  157. package/dist/pr-loop.d.ts.map +1 -1
  158. package/dist/pr-loop.js +77 -3
  159. package/dist/pr-post.d.ts +72 -0
  160. package/dist/pr-post.d.ts.map +1 -0
  161. package/dist/pr-post.js +134 -0
  162. package/dist/wave-blocked.d.ts +79 -0
  163. package/dist/wave-blocked.d.ts.map +1 -0
  164. package/dist/wave-blocked.js +245 -0
  165. package/dist/wave-engine.d.ts +6 -0
  166. package/dist/wave-engine.d.ts.map +1 -1
  167. package/dist/wave-engine.js +2 -0
  168. package/dist/wave-runtime.mjs +239 -239
  169. package/dist/workspace/__fixtures__/two-member-workspace.d.ts +1 -0
  170. package/dist/workspace/__fixtures__/two-member-workspace.d.ts.map +1 -1
  171. package/dist/workspace/__fixtures__/two-member-workspace.js +1 -1
  172. package/dist/workspace/ci-branches.d.ts +51 -0
  173. package/dist/workspace/ci-branches.d.ts.map +1 -0
  174. package/dist/workspace/ci-branches.js +69 -0
  175. package/dist/workspace/ci-cloud-identity.d.ts +11 -0
  176. package/dist/workspace/ci-cloud-identity.d.ts.map +1 -0
  177. package/dist/workspace/ci-cloud-identity.js +24 -0
  178. package/dist/workspace/ci-locks.d.ts +12 -0
  179. package/dist/workspace/ci-locks.d.ts.map +1 -0
  180. package/dist/workspace/ci-locks.js +6 -0
  181. package/dist/workspace/ci-run.d.ts +24 -1
  182. package/dist/workspace/ci-run.d.ts.map +1 -1
  183. package/dist/workspace/ci-run.js +85 -6
  184. package/dist/workspace/declaration.d.ts +63 -0
  185. package/dist/workspace/declaration.d.ts.map +1 -1
  186. package/dist/workspace/declaration.js +72 -1
  187. package/dist/workspace/declaration.schema.json +147 -0
  188. package/dist/workspace/identity-gate-rule.d.ts +1 -1
  189. package/dist/workspace/identity-gate-rule.d.ts.map +1 -1
  190. package/dist/workspace/identity-gate-rule.js +1 -1
  191. package/dist/workspace/identity.d.ts +1 -1
  192. package/dist/workspace/identity.d.ts.map +1 -1
  193. package/dist/workspace/lineage-upgrade.d.ts.map +1 -1
  194. package/dist/workspace/lineage-upgrade.js +8 -5
  195. package/dist/workspace/member-pipeline.d.ts +6 -0
  196. package/dist/workspace/member-pipeline.d.ts.map +1 -1
  197. package/dist/workspace/member-pipeline.js +23 -0
  198. package/dist/workspace/pr-report.schema.json +73 -2
  199. package/dist/workspace/security.d.ts.map +1 -1
  200. package/dist/workspace/security.js +4 -0
  201. package/dist/workspace/status-gates.d.ts +3 -2
  202. package/dist/workspace/status-gates.d.ts.map +1 -1
  203. package/dist/workspace/status-gates.js +8 -7
  204. package/dist/workspace/status.d.ts +3 -1
  205. package/dist/workspace/status.d.ts.map +1 -1
  206. package/dist/workspace/status.js +7 -3
  207. package/dist/workspace/status.schema.json +1 -0
  208. package/dist/workspace/trust/seal.d.ts +2 -2
  209. package/dist/workspace/trust/seal.d.ts.map +1 -1
  210. package/dist/workspace/trust/seal.js +2 -2
  211. package/package.json +1 -1
  212. package/src/__fixtures__/oidc-emulator.ts +118 -0
  213. package/src/ci-handover.test.ts +209 -0
  214. package/src/ci-handover.ts +237 -0
  215. package/src/ci-identity.test.ts +201 -0
  216. package/src/ci-identity.ts +310 -0
  217. package/src/cli/component-endpoint.ts +43 -0
  218. package/src/cli/handlers/approve.ts +18 -5
  219. package/src/cli/handlers/build.ts +15 -1
  220. package/src/cli/handlers/fan-out.test.ts +10 -0
  221. package/src/cli/handlers/fan-out.ts +29 -4
  222. package/src/cli/handlers/gate-resume.ts +16 -2
  223. package/src/cli/handlers/lock.test.ts +98 -0
  224. package/src/cli/handlers/lock.ts +202 -0
  225. package/src/cli/handlers/operator.test.ts +3 -1
  226. package/src/cli/handlers/pr-endpoint.test.ts +169 -0
  227. package/src/cli/handlers/pr-env.ts +462 -0
  228. package/src/cli/handlers/pr-waves-blocked.test.ts +247 -0
  229. package/src/cli/handlers/pr.test.ts +184 -0
  230. package/src/cli/handlers/pr.ts +508 -64
  231. package/src/cli/handlers/promote.ts +25 -2
  232. package/src/cli/handlers/query.ts +78 -0
  233. package/src/cli/handlers/run.test.ts +2 -1
  234. package/src/cli/handlers/run.ts +16 -5
  235. package/src/cli/main.ts +41 -2
  236. package/src/cli/parse-args.ts +23 -2
  237. package/src/cli/registry.ts +12 -2
  238. package/src/components/auto-release-infra.test.ts +131 -0
  239. package/src/components/auto-release.test.ts +40 -1
  240. package/src/components/auto-release.ts +50 -4
  241. package/src/components/cli-support.ts +4 -0
  242. package/src/components/deploy-units.ts +5 -0
  243. package/src/components/fan-out-run.ts +39 -2
  244. package/src/components/fan-out-waves.test.ts +45 -1
  245. package/src/components/pr-environment.test.ts +249 -0
  246. package/src/components/pr-environment.ts +538 -0
  247. package/src/components/pr-hooks.test.ts +118 -0
  248. package/src/components/pr-hooks.ts +157 -0
  249. package/src/components/pr-pipeline.ts +130 -5
  250. package/src/components/pr-policy.test.ts +145 -0
  251. package/src/components/pr-policy.ts +211 -0
  252. package/src/components/pr-run.ts +5 -2
  253. package/src/components/promote.test.ts +39 -0
  254. package/src/components/promote.ts +38 -14
  255. package/src/gate-decision-bundle.test.ts +4 -3
  256. package/src/gate-decision.ts +39 -2
  257. package/src/gate-policy-bundle.test.ts +28 -0
  258. package/src/gate-policy.test.ts +162 -0
  259. package/src/gate-policy.ts +298 -0
  260. package/src/lifecycle/__fixtures__/publish-fakes.ts +6 -1
  261. package/src/lifecycle/gate-checkpoint.ts +1 -1
  262. package/src/{workspace/trust → lifecycle}/gate-seal.ts +1 -1
  263. package/src/lifecycle/publish-declaration.ts +4 -3
  264. package/src/lifecycle/publish-npm.ts +33 -8
  265. package/src/lifecycle/publish-targets.test.ts +36 -3
  266. package/src/lifecycle/query.test.ts +91 -0
  267. package/src/lifecycle/query.ts +315 -0
  268. package/src/lifecycle/release-ledger.ts +42 -1
  269. package/src/lifecycle/run-ledger.ts +18 -0
  270. package/src/lifecycle/status.test.ts +19 -0
  271. package/src/lifecycle/status.ts +14 -6
  272. package/src/lifecycle/target-lock.test.ts +195 -0
  273. package/src/lifecycle/target-lock.ts +347 -0
  274. package/src/live-endpoint.ts +31 -0
  275. package/src/op/activities/activity-contracts.ts +31 -0
  276. package/src/op/activities/index.ts +6 -0
  277. package/src/op/activities/review-agent.test.ts +240 -0
  278. package/src/op/activities/review-agent.ts +355 -0
  279. package/src/op/builders.ts +13 -0
  280. package/src/op/cron.ts +44 -0
  281. package/src/op/gate-alias.test.ts +2 -2
  282. package/src/op/gate-approval.test.ts +30 -9
  283. package/src/op/gate-approval.ts +23 -26
  284. package/src/op/gate-quorum.test.ts +139 -0
  285. package/src/op/gate-quorum.ts +248 -0
  286. package/src/op/gate-resume.ts +6 -3
  287. package/src/op/gate-review.ts +1 -1
  288. package/src/op/gate.ts +64 -23
  289. package/src/op/index.ts +13 -1
  290. package/src/op/local-executor.ts +9 -2
  291. package/src/op/op-waves-hook-targets.test.ts +126 -0
  292. package/src/op/op-waves-policy-hold.test.ts +66 -0
  293. package/src/op/op-waves-quorum.test.ts +101 -0
  294. package/src/op/op-waves-run.ts +233 -11
  295. package/src/op/op-waves.ts +83 -10
  296. package/src/op/schedule-overdue.test.ts +220 -0
  297. package/src/op/schedule-overdue.ts +161 -0
  298. package/src/override-gate-bundle.test.ts +1 -1
  299. package/src/override-gate.ts +2 -2
  300. package/src/pr-forge-bundle.test.ts +2 -2
  301. package/src/pr-forge.test.ts +33 -0
  302. package/src/pr-forge.ts +114 -0
  303. package/src/pr-loop-bundle.test.ts +2 -2
  304. package/src/pr-loop.test.ts +58 -0
  305. package/src/pr-loop.ts +146 -4
  306. package/src/pr-post.test.ts +141 -0
  307. package/src/pr-post.ts +159 -0
  308. package/src/wave-blocked-bundle.test.ts +25 -0
  309. package/src/wave-blocked.test.ts +146 -0
  310. package/src/wave-blocked.ts +271 -0
  311. package/src/wave-engine.ts +8 -0
  312. package/src/workspace/__fixtures__/two-member-workspace.ts +2 -2
  313. package/src/workspace/ci-branches.test.ts +62 -0
  314. package/src/workspace/ci-branches.ts +79 -0
  315. package/src/workspace/ci-cloud-identity.test.ts +53 -0
  316. package/src/workspace/ci-cloud-identity.ts +23 -0
  317. package/src/workspace/ci-locks.ts +16 -0
  318. package/src/workspace/ci-run-index.test.ts +2 -1
  319. package/src/workspace/ci-run.test.ts +133 -8
  320. package/src/workspace/ci-run.ts +92 -7
  321. package/src/workspace/declaration.schema.json +147 -0
  322. package/src/workspace/declaration.test.ts +76 -0
  323. package/src/workspace/declaration.ts +134 -2
  324. package/src/workspace/identity-gate-rule.ts +1 -1
  325. package/src/workspace/identity.ts +1 -1
  326. package/src/workspace/lineage-upgrade.ts +11 -8
  327. package/src/workspace/member-pipeline.test.ts +29 -0
  328. package/src/workspace/member-pipeline.ts +30 -0
  329. package/src/workspace/pr-report.schema.json +73 -2
  330. package/src/workspace/security.ts +4 -0
  331. package/src/workspace/status-gates.ts +9 -6
  332. package/src/workspace/status.schema.json +1 -0
  333. package/src/workspace/status.ts +11 -4
  334. package/src/workspace/trust/gate-seal.test.ts +1 -1
  335. package/src/workspace/trust/seal.ts +2 -2
  336. package/dist/workspace/trust/gate-seal.d.ts.map +0 -1
  337. /package/dist/{workspace/trust → lifecycle}/gate-seal.js +0 -0
@@ -38,7 +38,7 @@
38
38
  * the same spirit as `DEPLOY_UNIT_RULES` (./deploy-units.ts).
39
39
  */
40
40
  import { parseWiringRef } from "../lint/rules/comp/support.js";
41
- import { latestPerComponent } from "../lifecycle/release-ledger.js";
41
+ import { isApplyRecord, latestPerComponent } from "../lifecycle/release-ledger.js";
42
42
  import { CapabilityRegistry } from "./capability.js";
43
43
  import { runInterpretDriver, DriverRunFailure, } from "./driver.js";
44
44
  /**
@@ -68,20 +68,35 @@ export const PUBLISH_STEP_KINDS = [
68
68
  * A digest the ledger never recorded for the component is an error.
69
69
  */
70
70
  export function selectRelease(records, component, env, digest) {
71
- const own = records.filter((r) => r.component === component);
72
- if (own.length === 0)
73
- return { error: `no release of "${component}" is recorded in "${env}"` };
71
+ const own = deployableRecords(records, component, env);
72
+ if ("error" in own)
73
+ return own;
74
74
  if (digest === undefined)
75
- return { record: latestPerComponent(own).get(component) };
76
- const matching = own.filter((r) => r.digest === digest);
75
+ return { record: latestPerComponent(own.records).get(component) };
76
+ const matching = own.records.filter((r) => r.digest === digest);
77
77
  if (matching.length === 0) {
78
78
  return {
79
79
  error: `digest ${digest} is not recorded for "${component}" in "${env}"; ` +
80
- `its recorded digests are ${[...new Set(own.map((r) => r.digest))].join(", ")}`,
80
+ `its recorded digests are ${[...new Set(own.records.map((r) => r.digest))].join(", ")}`,
81
81
  };
82
82
  }
83
83
  return { record: latestPerComponent(matching).get(component) };
84
84
  }
85
+ /**
86
+ * The records of `component` a promote, rollback or redeploy can deploy from
87
+ * (#4274): every one but those that only say it applied ({@link isApplyRecord}),
88
+ * whose digest is a plan or deploy-input digest and names no artifact.
89
+ */
90
+ function deployableRecords(records, component, env) {
91
+ const own = records.filter((r) => r.component === component);
92
+ if (own.length === 0)
93
+ return { error: `no release of "${component}" is recorded in "${env}"` };
94
+ const deployable = own.filter((r) => !isApplyRecord(r));
95
+ if (deployable.length === 0)
96
+ return { error: appliesOnly(component, env) };
97
+ return { records: deployable };
98
+ }
99
+ const appliesOnly = (component, env) => `"${component}" has only applies recorded in "${env}", each by a plan or deploy-input digest, so no artifact of it can be deployed again`;
85
100
  /**
86
101
  * Read `--digest <component>=<digest>` values (#2602) into a component-to-
87
102
  * digest map. A value with no `=` is left to the single-component form
@@ -150,7 +165,7 @@ export function planPromotion(input) {
150
165
  return picked;
151
166
  return { from, to, items: [{ component, digest: picked.record.digest, source: picked.record }], notPromoted: [] };
152
167
  }
153
- const latest = latestPerComponent(sourceRecords);
168
+ const latest = latestPerComponent(sourceRecords.filter((r) => !isApplyRecord(r)));
154
169
  const items = [];
155
170
  const notPromoted = [];
156
171
  for (const [name, record] of [...latest].sort(([a], [b]) => a.localeCompare(b))) {
@@ -159,6 +174,10 @@ export function planPromotion(input) {
159
174
  else
160
175
  notPromoted.push({ component: name, reason: "recorded but not declared in this checkout" });
161
176
  }
177
+ // #4274: a component that only applied (infrastructure, no artifact) has nothing to promote.
178
+ for (const name of [...new Set(sourceRecords.filter((r) => isApplyRecord(r) && !latest.has(r.component)).map((r) => r.component))].sort()) {
179
+ notPromoted.push({ component: name, reason: "only applies are recorded, by a plan or deploy-input digest; there is no artifact to promote" });
180
+ }
162
181
  if (items.length === 0) {
163
182
  return { error: `nothing to promote: no component declared here has a release recorded in "${from}"` };
164
183
  }
@@ -175,10 +194,11 @@ export function planRollback(input) {
175
194
  if (!declared.includes(component)) {
176
195
  return { error: `component "${component}" is not declared in this checkout` };
177
196
  }
178
- const own = records.filter((r) => r.component === component);
197
+ const deployable = deployableRecords(records, component, env);
198
+ if ("error" in deployable)
199
+ return deployable;
200
+ const own = deployable.records;
179
201
  const current = latestPerComponent(own).get(component);
180
- if (!current)
181
- return { error: `no release of "${component}" is recorded in "${env}"` };
182
202
  let target;
183
203
  if (digest !== undefined) {
184
204
  const picked = selectRelease(own, component, env, digest);
@@ -226,7 +246,7 @@ export function planReleaseRollback(input) {
226
246
  notPromoted.push({ component, reason: `in release ${name} but not declared in this checkout` });
227
247
  continue;
228
248
  }
229
- const ran = records.filter((r) => r.component === component && r.releaseDigest === release.releaseDigest);
249
+ const ran = records.filter((r) => r.component === component && r.releaseDigest === release.releaseDigest && !isApplyRecord(r));
230
250
  if (ran.length === 0) {
231
251
  neverRan.push(component);
232
252
  continue;
@@ -242,7 +262,7 @@ export function planReleaseRollback(input) {
242
262
  }
243
263
  if (items.length === 0)
244
264
  return { error: `release ${name} has no component declared in this checkout` };
245
- const current = latestPerComponent(records);
265
+ const current = latestPerComponent(records.filter((r) => !isApplyRecord(r)));
246
266
  const already = items.every((i) => {
247
267
  const now = current.get(i.component);
248
268
  return now !== undefined && now.releaseDigest === release.releaseDigest && now.digest === i.digest && !input.unsettled?.has(i.component);
@@ -265,9 +285,10 @@ export function planRedeploy(input) {
265
285
  if (!declared.includes(component)) {
266
286
  return { error: `component "${component}" is not declared in this checkout` };
267
287
  }
268
- const current = latestPerComponent(records.filter((r) => r.component === component)).get(component);
269
- if (!current)
270
- return { error: `no release of "${component}" is recorded in "${env}"` };
288
+ const deployable = deployableRecords(records, component, env);
289
+ if ("error" in deployable)
290
+ return deployable;
291
+ const current = latestPerComponent(deployable.records).get(component);
271
292
  if (digest !== undefined && digest !== current.digest) {
272
293
  return {
273
294
  error: `${digest} is not the current release of "${component}" in "${env}" (that is ${current.digest}); ` +
@@ -589,6 +610,8 @@ function sharedRecord(item, env, run) {
589
610
  ...(run.admission ? { admission: run.admission } : {}),
590
611
  ...(source.manifestDigest ? { manifestDigest: source.manifestDigest } : {}),
591
612
  ...(source.inputDigest ? { inputDigest: source.inputDigest } : {}),
613
+ // #4274: a release's templates digest stays one when it is deployed again.
614
+ ...(source.digestKind ? { digestKind: source.digestKind } : {}),
592
615
  };
593
616
  }
594
617
  /** Name an earlier record the way `promotedFrom`, `restores` and `redeploys` do. */
@@ -27,6 +27,11 @@
27
27
  * counts for nothing, as terragucci's wave gate has it: the caller hands in
28
28
  * the lines of `_gates/<op>/applied.jsonl` ({@link parseAppliedGateLines}).
29
29
  *
30
+ * Approval rules (#4290), a quorum as a list of `{ targets, count, roles,
31
+ * notAuthor }`, are judged with `./op/gate-quorum.ts`, re-exported here:
32
+ * {@link trustRolesAt} and {@link gateSignersAt} read what
33
+ * `trustQuorumJudge` judges by, at base.
34
+ *
30
35
  * The module reads the ledger lines and the review it is handed; reading
31
36
  * them is the caller's. It imports no TypeScript, esbuild, zod, ajv, lint,
32
37
  * fold or codegen module, no package's code, and none of chant's git ledger
@@ -36,19 +41,20 @@
36
41
  * `gate-decision.test.ts` decides the same ledgers both ways.
37
42
  */
38
43
  import { type AppliedGateRecord } from "./lifecycle/gate-applied.js";
39
- import { type GateSigners, type SealedGateApproval } from "./workspace/trust/gate-seal.js";
44
+ import { type GateSigners, type SealedGateApproval } from "./lifecycle/gate-seal.js";
40
45
  import { type ClassGrant } from "./workspace/identity-gate-rule.js";
41
46
  import type { GateResolutionRecord, PendingGateRecord } from "./lifecycle/gate-ledger.js";
42
47
  import type { MergedReview } from "./op/gate-review.js";
43
48
  import type { GateApprovalSource } from "./op/op-waves.js";
44
49
  export { GATE_APPROVAL_SOURCES, type GateApprovalSource } from "./op/op-waves.js";
45
50
  export { isPendingGateExpired, latestPendingGate, latestResolutionForPlan, latestResolutionSince, type PlanBoundResolution, } from "./lifecycle/gate-ledger-select.js";
46
- export { GATE_SEAL_NAMESPACE, gateSealPayload, gateSealRefusal, gateSealVersion, sealedFieldsOf, sshKeyFingerprint, verifySshSignatureWith, type GateSigners, type SealedGateApproval, type SshSignatureCheck, } from "./workspace/trust/gate-seal.js";
51
+ export { GATE_SEAL_NAMESPACE, gateSealPayload, gateSealRefusal, gateSealVersion, sealedFieldsOf, sshKeyFingerprint, verifySshSignatureWith, type GateSigners, type SealedGateApproval, type SshSignatureCheck, } from "./lifecycle/gate-seal.js";
47
52
  export { DEFAULT_SIGNERS_PATH, TRUST_CONFIG_PATH, parseAllowedSigners, trustConfigProblems, type Signer, type SignerSet, } from "./workspace/trust/trust-files.js";
48
53
  export type { GateResolutionRecord, PendingGateRecord } from "./lifecycle/gate-ledger.js";
49
54
  export { CORE_CLASSES, admitApprover, grantedClasses, type ClassGrant, type GateAdmissionRule, type PrincipalClass, } from "./workspace/identity-gate-rule.js";
50
55
  export { appliedGateApplies, appliedGatePath, isApprovalSpent, parseAppliedGateLines, type AppliedGateRecord, } from "./lifecycle/gate-applied.js";
51
56
  export type { MergedReview } from "./op/gate-review.js";
57
+ export { quorumNeed, quorumProblems, quorumRuleLine, quorumRuleProgress, quorumRules, quorumRulesFor, quorumWaiting, rolesOf, ruleNeedsProof, samePrincipal, targetGlobMatch, trustQuorumJudge, UNPROVEN_JUDGE, type GateQuorumJudge, type GateQuorumRule, type GateQuorumRuleProgress, type GateQuorumSpec, type QuorumApprover, type QuorumJudgement, type TrustQuorumInput, } from "./op/gate-quorum.js";
52
58
  /**
53
59
  * The commit a gate's rule is read at when the default branch applies its
54
60
  * own commit: the first parent of HEAD, the branch as it stood before the
@@ -65,6 +71,13 @@ export declare function baseCommit(repo: string): string;
65
71
  * commit the repository holds, such as {@link baseCommit}'s answer.
66
72
  */
67
73
  export declare function gateSignersAt(repo: string, base: string): GateSigners;
74
+ /**
75
+ * The role grants of `.chant/trust.json` at `base` (#4290): role name to the
76
+ * principals that hold it, as chant reads them. A missing file gives none,
77
+ * and so does one that is not valid, which turns chant's trust off.
78
+ * {@link trustQuorumJudge} takes them with {@link gateSignersAt}'s signers.
79
+ */
80
+ export declare function trustRolesAt(repo: string, base: string): Record<string, string[]>;
68
81
  /** A gate the declaration at base names under `identity.gates`, and the principal class its signer must be in, if any. */
69
82
  export interface IdentityGate {
70
83
  gate: string;
@@ -1 +1 @@
1
- {"version":3,"file":"gate-decision.d.ts","sourceRoot":"","sources":["../src/gate-decision.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"gate-decision.d.ts","sourceRoot":"","sources":["../src/gate-decision.ts"],"names":[],"mappings":"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"}
@@ -27,6 +27,11 @@
27
27
  * counts for nothing, as terragucci's wave gate has it: the caller hands in
28
28
  * the lines of `_gates/<op>/applied.jsonl` ({@link parseAppliedGateLines}).
29
29
  *
30
+ * Approval rules (#4290), a quorum as a list of `{ targets, count, roles,
31
+ * notAuthor }`, are judged with `./op/gate-quorum.ts`, re-exported here:
32
+ * {@link trustRolesAt} and {@link gateSignersAt} read what
33
+ * `trustQuorumJudge` judges by, at base.
34
+ *
30
35
  * The module reads the ledger lines and the review it is handed; reading
31
36
  * them is the caller's. It imports no TypeScript, esbuild, zod, ajv, lint,
32
37
  * fold or codegen module, no package's code, and none of chant's git ledger
@@ -41,17 +46,18 @@ import { join, posix } from "node:path";
41
46
  import { isPendingGateExpired, latestPendingGate, latestResolutionForPlan, } from "./lifecycle/gate-ledger-select.js";
42
47
  import { samePlanDigest } from "./lifecycle/plan-digest.js";
43
48
  import { isApprovalSpent } from "./lifecycle/gate-applied.js";
44
- import { gateSealRefusal } from "./workspace/trust/gate-seal.js";
49
+ import { gateSealRefusal } from "./lifecycle/gate-seal.js";
45
50
  import { DEFAULT_SIGNERS_PATH, TRUST_CONFIG_PATH, parseAllowedSigners, trustConfigProblems, } from "./workspace/trust/trust-files.js";
46
51
  import { parseJsonText } from "./workspace/jsonc.js";
47
52
  import { CORE_CLASSES, admitApprover } from "./workspace/identity-gate-rule.js";
48
53
  import { diskFiles, findInstalledPackage, mergePinClasses, parseClassDataByHand, readClassFiles } from "./workspace/class-data.js";
49
54
  export { GATE_APPROVAL_SOURCES } from "./op/op-waves.js";
50
55
  export { isPendingGateExpired, latestPendingGate, latestResolutionForPlan, latestResolutionSince, } from "./lifecycle/gate-ledger-select.js";
51
- export { GATE_SEAL_NAMESPACE, gateSealPayload, gateSealRefusal, gateSealVersion, sealedFieldsOf, sshKeyFingerprint, verifySshSignatureWith, } from "./workspace/trust/gate-seal.js";
56
+ export { GATE_SEAL_NAMESPACE, gateSealPayload, gateSealRefusal, gateSealVersion, sealedFieldsOf, sshKeyFingerprint, verifySshSignatureWith, } from "./lifecycle/gate-seal.js";
52
57
  export { DEFAULT_SIGNERS_PATH, TRUST_CONFIG_PATH, parseAllowedSigners, trustConfigProblems, } from "./workspace/trust/trust-files.js";
53
58
  export { CORE_CLASSES, admitApprover, grantedClasses, } from "./workspace/identity-gate-rule.js";
54
59
  export { appliedGateApplies, appliedGatePath, isApprovalSpent, parseAppliedGateLines, } from "./lifecycle/gate-applied.js";
60
+ export { quorumNeed, quorumProblems, quorumRuleLine, quorumRuleProgress, quorumRules, quorumRulesFor, quorumWaiting, rolesOf, ruleNeedsProof, samePrincipal, targetGlobMatch, trustQuorumJudge, UNPROVEN_JUDGE, } from "./op/gate-quorum.js";
55
61
  // ── reading at base ──────────────────────────────────────────────────────
56
62
  function git(repo, args) {
57
63
  return spawnSync("git", args, { cwd: repo, encoding: "utf-8" });
@@ -118,6 +124,15 @@ function trustAt(repo, base) {
118
124
  export function gateSignersAt(repo, base) {
119
125
  return trustAt(repo, base).signers;
120
126
  }
127
+ /**
128
+ * The role grants of `.chant/trust.json` at `base` (#4290): role name to the
129
+ * principals that hold it, as chant reads them. A missing file gives none,
130
+ * and so does one that is not valid, which turns chant's trust off.
131
+ * {@link trustQuorumJudge} takes them with {@link gateSignersAt}'s signers.
132
+ */
133
+ export function trustRolesAt(repo, base) {
134
+ return trustAt(repo, base).roles;
135
+ }
121
136
  /** The declaration at `base`, parsed, or null when there is none. Throws when it cannot be parsed. */
122
137
  function declarationAt(repo, base) {
123
138
  for (const file of ["chant.workspace.json", "chant.workspace.jsonc"]) {
@@ -0,0 +1,140 @@
1
+ /**
2
+ * Gate-time policy (#4294): a policy engine run over a plan's change set,
3
+ * with the policy read at base, whose rules deny a plan or hold it for a
4
+ * person.
5
+ *
6
+ * The engine is any command that prints conftest's JSON result
7
+ * (`conftest test --output json`) or OPA's (`opa eval --format json`). The
8
+ * rules are counted as conftest counts them:
9
+ *
10
+ * - `deny`, `violation`, `deny_<name>` and `violation_<name>` deny the plan.
11
+ * A denied plan applies only under an override that counts
12
+ * (`./override-gate.ts`), on top of the gate's own approval.
13
+ * - `hold` and `hold_<name>` hold the plan: it waits for a person's approval
14
+ * of its digest, even where the gate would let it through without one.
15
+ * conftest runs only deny and warn rules, so a hold needs the opa shape.
16
+ * - `warn` and `warn_<name>` are advice. They fail nothing.
17
+ *
18
+ * There is no outcome that lets a plan apply with nobody approving it: a
19
+ * non-empty change set always waits for a person (ws-104, ws-080). A rule
20
+ * named `allow_unattended` is read as no rule at all.
21
+ *
22
+ * A policy that cannot run, or gives no answer this module can read, is an
23
+ * error, and the caller stops the apply: no path waives a policy.
24
+ *
25
+ * Like `./override-gate.ts` this module reads what it is handed and imports
26
+ * only node built-ins, so a product can bundle it into one file
27
+ * (`gate-policy-bundle.test.ts`). The pull-request jobs' use of it is in
28
+ * `./components/pr-policy.ts`.
29
+ */
30
+ /** The result shapes chant reads: conftest's and OPA's. */
31
+ export declare const GATE_POLICY_ENGINES: readonly ["conftest", "opa"];
32
+ export type GatePolicyEngine = (typeof GATE_POLICY_ENGINES)[number];
33
+ /** Where the policy is read when the settings name no `path`. */
34
+ export declare const DEFAULT_POLICY_PATH = "policy";
35
+ /** The settings of one policy step, read at base. */
36
+ export interface GatePolicySettings {
37
+ /** The result shape, and the default command. Default `conftest`. */
38
+ engine?: GatePolicyEngine;
39
+ /** The policy directory, relative to the declaration at base. Default {@link DEFAULT_POLICY_PATH}. */
40
+ path?: string;
41
+ /** The package to read. conftest default: every namespace. opa default: `main`. */
42
+ namespace?: string;
43
+ /**
44
+ * The command to run instead of the engine's own, as argv. `{policy}` is
45
+ * the policy directory exported from base and `{input}` the input file. It
46
+ * prints the result shape `engine` names.
47
+ */
48
+ command?: string[];
49
+ }
50
+ /** A rule that matched, and what it said. */
51
+ export interface GatePolicyFinding {
52
+ /** `<package>.<rule>`, such as `main.deny_public_bucket`. */
53
+ rule: string;
54
+ message: string;
55
+ }
56
+ /** What an engine's answer holds. */
57
+ export interface GatePolicyFindings {
58
+ denials: GatePolicyFinding[];
59
+ holds: GatePolicyFinding[];
60
+ warnings: string[];
61
+ }
62
+ /**
63
+ * One policy check's outcome. `denied` wins over `held`. `error`: the
64
+ * policy did not run or gave no readable answer, and the apply stops.
65
+ */
66
+ export interface GatePolicyVerdict {
67
+ result: "passed" | "denied" | "held" | "error";
68
+ engine: GatePolicyEngine;
69
+ denials: GatePolicyFinding[];
70
+ /** The ids of the rules that denied, sorted, each once: what an override names. */
71
+ rules: string[];
72
+ holds: GatePolicyFinding[];
73
+ warnings: string[];
74
+ error?: string;
75
+ }
76
+ export declare const DENY_RULE: RegExp;
77
+ export declare const WARN_RULE: RegExp;
78
+ export declare const HOLD_RULE: RegExp;
79
+ /** The findings in `conftest test --output json`. Undefined when it is not that shape. */
80
+ export declare function conftestFindings(stdout: string): GatePolicyFindings | undefined;
81
+ /**
82
+ * The findings in `opa eval --format json data.<namespace>`. The query's
83
+ * value is the package: its rules are read by name. An array value is a
84
+ * deny set named after the query. A query that is undefined has no rule
85
+ * that matched. Undefined when the output is not that shape.
86
+ */
87
+ export declare function opaFindings(stdout: string, namespace?: string): GatePolicyFindings | undefined;
88
+ /** A verdict from what the engine found. */
89
+ export declare function gatePolicyVerdict(engine: GatePolicyEngine, found: GatePolicyFindings): GatePolicyVerdict;
90
+ /** The argv one check runs: the settings' `command` with its placeholders filled, or the engine's own. */
91
+ export declare function gatePolicyArgv(settings: GatePolicySettings, at: {
92
+ policy: string;
93
+ input: string;
94
+ }): string[];
95
+ /** What one run of the engine printed and how it exited. `status` is null when it did not start. */
96
+ export interface GatePolicyRun {
97
+ status: number | null;
98
+ stdout: string;
99
+ stderr: string;
100
+ }
101
+ export type GatePolicyExec = (argv: string[], cwd: string) => GatePolicyRun;
102
+ /** Runs the engine with the job's environment. */
103
+ export declare const defaultGatePolicyExec: GatePolicyExec;
104
+ export interface CheckGatePolicyOptions {
105
+ settings: GatePolicySettings;
106
+ /** The input document, or its JSON text. */
107
+ input: unknown;
108
+ /** The policy directory, already exported from base. */
109
+ policyDir: string;
110
+ /** Where the engine runs. Default the policy directory. */
111
+ cwd?: string;
112
+ exec?: GatePolicyExec;
113
+ }
114
+ /** Run the engine once over `input` and read its answer. */
115
+ export declare function checkGatePolicy(options: CheckGatePolicyOptions): GatePolicyVerdict;
116
+ /** The part of a tree a policy is exported from: a workspace tree at base fits. */
117
+ export interface GatePolicyTree {
118
+ stat(path: string): "file" | "dir" | undefined;
119
+ list(path: string): Array<{
120
+ name: string;
121
+ type: "file" | "dir";
122
+ }> | undefined;
123
+ read(path: string): string;
124
+ bytes?(path: string): Uint8Array;
125
+ }
126
+ /**
127
+ * Copy the policy directory `path` out of `tree` (the tree at base) into a
128
+ * new temporary directory, so the engine reads the policy as the base has
129
+ * it and never the change's copy. Returns the directory and how to remove
130
+ * it, or the reason there is no policy to read.
131
+ */
132
+ export declare function exportGatePolicy(tree: GatePolicyTree, path: string): {
133
+ dir: string;
134
+ cleanup: () => void;
135
+ } | {
136
+ error: string;
137
+ };
138
+ /** The settings, checked. Throws with what is wrong. `where` names them, such as `ci.policy`. */
139
+ export declare function assertGatePolicySettings(where: string, raw: unknown): asserts raw is GatePolicySettings;
140
+ //# sourceMappingURL=gate-policy.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gate-policy.d.ts","sourceRoot":"","sources":["../src/gate-policy.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAOH,2DAA2D;AAC3D,eAAO,MAAM,mBAAmB,YAAI,UAAU,EAAE,KAAK,CAAU,CAAC;AAChE,MAAM,MAAM,gBAAgB,GAAG,CAAC,OAAO,mBAAmB,CAAC,CAAC,MAAM,CAAC,CAAC;AAEpE,iEAAiE;AACjE,eAAO,MAAM,mBAAmB,WAAW,CAAC;AAE5C,qDAAqD;AACrD,MAAM,WAAW,kBAAkB;IACjC,qEAAqE;IACrE,MAAM,CAAC,EAAE,gBAAgB,CAAC;IAC1B,sGAAsG;IACtG,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,mFAAmF;IACnF,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;;OAIG;IACH,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;CACpB;AAED,6CAA6C;AAC7C,MAAM,WAAW,iBAAiB;IAChC,6DAA6D;IAC7D,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,qCAAqC;AACrC,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,iBAAiB,EAAE,CAAC;IAC7B,KAAK,EAAE,iBAAiB,EAAE,CAAC;IAC3B,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB;AAED;;;GAGG;AACH,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,QAAQ,GAAG,QAAQ,GAAG,MAAM,GAAG,OAAO,CAAC;IAC/C,MAAM,EAAE,gBAAgB,CAAC;IACzB,OAAO,EAAE,iBAAiB,EAAE,CAAC;IAC7B,mFAAmF;IACnF,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,KAAK,EAAE,iBAAiB,EAAE,CAAC;IAC3B,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,eAAO,MAAM,SAAS,QAAuC,CAAC;AAC9D,eAAO,MAAM,SAAS,QAA2B,CAAC;AAClD,eAAO,MAAM,SAAS,QAA2B,CAAC;AAUlD,0FAA0F;AAC1F,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,MAAM,GAAG,kBAAkB,GAAG,SAAS,CAmB/E;AAmBD;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,SAAS,GAAG,kBAAkB,GAAG,SAAS,CAkB9F;AAED,4CAA4C;AAC5C,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,gBAAgB,EAAE,KAAK,EAAE,kBAAkB,GAAG,iBAAiB,CAUxG;AAID,0GAA0G;AAC1G,wBAAgB,cAAc,CAAC,QAAQ,EAAE,kBAAkB,EAAE,EAAE,EAAE;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GAAG,MAAM,EAAE,CAM5G;AAED,oGAAoG;AACpG,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,MAAM,cAAc,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,GAAG,EAAE,MAAM,KAAK,aAAa,CAAC;AAE5E,kDAAkD;AAClD,eAAO,MAAM,qBAAqB,EAAE,cAGnC,CAAC;AAEF,MAAM,WAAW,sBAAsB;IACrC,QAAQ,EAAE,kBAAkB,CAAC;IAC7B,4CAA4C;IAC5C,KAAK,EAAE,OAAO,CAAC;IACf,wDAAwD;IACxD,SAAS,EAAE,MAAM,CAAC;IAClB,2DAA2D;IAC3D,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,cAAc,CAAC;CACvB;AAID,4DAA4D;AAC5D,wBAAgB,eAAe,CAAC,OAAO,EAAE,sBAAsB,GAAG,iBAAiB,CAkBlF;AAED,mFAAmF;AACnF,MAAM,WAAW,cAAc;IAC7B,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,KAAK,GAAG,SAAS,CAAC;IAC/C,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,GAAG,KAAK,CAAA;KAAE,CAAC,GAAG,SAAS,CAAC;IAC9E,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC;IAC3B,KAAK,CAAC,CAAC,IAAI,EAAE,MAAM,GAAG,UAAU,CAAC;CAClC;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,cAAc,EAAE,IAAI,EAAE,MAAM,GAAG;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,IAAI,CAAA;CAAE,GAAG;IAAE,KAAK,EAAE,MAAM,CAAA;CAAE,CA0B7H;AAED,iGAAiG;AACjG,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,OAAO,GAAG,OAAO,CAAC,GAAG,IAAI,kBAAkB,CAiBvG"}
@@ -0,0 +1,242 @@
1
+ /**
2
+ * Gate-time policy (#4294): a policy engine run over a plan's change set,
3
+ * with the policy read at base, whose rules deny a plan or hold it for a
4
+ * person.
5
+ *
6
+ * The engine is any command that prints conftest's JSON result
7
+ * (`conftest test --output json`) or OPA's (`opa eval --format json`). The
8
+ * rules are counted as conftest counts them:
9
+ *
10
+ * - `deny`, `violation`, `deny_<name>` and `violation_<name>` deny the plan.
11
+ * A denied plan applies only under an override that counts
12
+ * (`./override-gate.ts`), on top of the gate's own approval.
13
+ * - `hold` and `hold_<name>` hold the plan: it waits for a person's approval
14
+ * of its digest, even where the gate would let it through without one.
15
+ * conftest runs only deny and warn rules, so a hold needs the opa shape.
16
+ * - `warn` and `warn_<name>` are advice. They fail nothing.
17
+ *
18
+ * There is no outcome that lets a plan apply with nobody approving it: a
19
+ * non-empty change set always waits for a person (ws-104, ws-080). A rule
20
+ * named `allow_unattended` is read as no rule at all.
21
+ *
22
+ * A policy that cannot run, or gives no answer this module can read, is an
23
+ * error, and the caller stops the apply: no path waives a policy.
24
+ *
25
+ * Like `./override-gate.ts` this module reads what it is handed and imports
26
+ * only node built-ins, so a product can bundle it into one file
27
+ * (`gate-policy-bundle.test.ts`). The pull-request jobs' use of it is in
28
+ * `./components/pr-policy.ts`.
29
+ */
30
+ import { spawnSync } from "node:child_process";
31
+ import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
32
+ import { tmpdir } from "node:os";
33
+ import { join } from "node:path";
34
+ /** The result shapes chant reads: conftest's and OPA's. */
35
+ export const GATE_POLICY_ENGINES = ["conftest", "opa"];
36
+ /** Where the policy is read when the settings name no `path`. */
37
+ export const DEFAULT_POLICY_PATH = "policy";
38
+ export const DENY_RULE = /^(deny|violation)(_[A-Za-z0-9]+)*$/;
39
+ export const WARN_RULE = /^warn(_[A-Za-z0-9]+)*$/;
40
+ export const HOLD_RULE = /^hold(_[A-Za-z0-9]+)*$/;
41
+ const ruleId = (query) => query.replace(/^data\./, "");
42
+ function messageOf(v) {
43
+ if (typeof v === "string")
44
+ return v;
45
+ if (v !== null && typeof v === "object" && typeof v.msg === "string")
46
+ return v.msg;
47
+ return JSON.stringify(v);
48
+ }
49
+ /** The findings in `conftest test --output json`. Undefined when it is not that shape. */
50
+ export function conftestFindings(stdout) {
51
+ let results;
52
+ try {
53
+ results = JSON.parse(stdout);
54
+ }
55
+ catch {
56
+ return undefined;
57
+ }
58
+ if (!Array.isArray(results))
59
+ return undefined;
60
+ const out = { denials: [], holds: [], warnings: [] };
61
+ for (const r of results) {
62
+ if (r === null || typeof r !== "object")
63
+ return undefined;
64
+ for (const f of r.failures ?? []) {
65
+ // conftest names the query that denied in the failure's metadata; an older one gives only the namespace.
66
+ const rule = typeof f.metadata?.query === "string" ? ruleId(f.metadata.query) : `${r.namespace ?? "main"}.deny`;
67
+ out.denials.push({ rule, message: messageOf(f.msg) });
68
+ }
69
+ for (const w of r.warnings ?? [])
70
+ out.warnings.push(messageOf(w.msg));
71
+ }
72
+ return out;
73
+ }
74
+ /** The rules of one package's value, and with `nested` its child packages one level down. */
75
+ function packageFindings(doc, out, pkg, nested) {
76
+ for (const [name, value] of Object.entries(doc).sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0))) {
77
+ // A rule matches when its value is true, a message, or a non-empty set of messages.
78
+ const said = Array.isArray(value) ? value.map(messageOf) : value === true ? [name] : typeof value === "string" && value !== "" ? [value] : [];
79
+ if (DENY_RULE.test(name)) {
80
+ for (const message of said)
81
+ out.denials.push({ rule: `${pkg}.${name}`, message });
82
+ }
83
+ else if (HOLD_RULE.test(name)) {
84
+ for (const message of said)
85
+ out.holds.push({ rule: `${pkg}.${name}`, message });
86
+ }
87
+ else if (WARN_RULE.test(name)) {
88
+ out.warnings.push(...said);
89
+ }
90
+ else if (nested && value !== null && typeof value === "object" && !Array.isArray(value)) {
91
+ packageFindings(value, out, `${pkg}.${name}`, false);
92
+ }
93
+ }
94
+ }
95
+ /**
96
+ * The findings in `opa eval --format json data.<namespace>`. The query's
97
+ * value is the package: its rules are read by name. An array value is a
98
+ * deny set named after the query. A query that is undefined has no rule
99
+ * that matched. Undefined when the output is not that shape.
100
+ */
101
+ export function opaFindings(stdout, namespace = "main") {
102
+ let parsed;
103
+ try {
104
+ parsed = JSON.parse(stdout);
105
+ }
106
+ catch {
107
+ return undefined;
108
+ }
109
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed) || parsed.errors !== undefined)
110
+ return undefined;
111
+ const value = parsed.result?.[0]?.expressions?.[0]?.value;
112
+ const out = { denials: [], holds: [], warnings: [] };
113
+ if (value === undefined)
114
+ return out;
115
+ if (Array.isArray(value)) {
116
+ out.denials.push(...value.map((v) => ({ rule: namespace, message: messageOf(v) })));
117
+ return out;
118
+ }
119
+ if (value === null || typeof value !== "object")
120
+ return undefined;
121
+ packageFindings(value, out, namespace, true);
122
+ return out;
123
+ }
124
+ /** A verdict from what the engine found. */
125
+ export function gatePolicyVerdict(engine, found) {
126
+ const rules = [...new Set(found.denials.map((d) => d.rule))].sort();
127
+ return {
128
+ result: found.denials.length > 0 ? "denied" : found.holds.length > 0 ? "held" : "passed",
129
+ engine,
130
+ denials: found.denials,
131
+ rules,
132
+ holds: found.holds,
133
+ warnings: found.warnings,
134
+ };
135
+ }
136
+ const errorVerdict = (engine, error) => ({ result: "error", engine, denials: [], rules: [], holds: [], warnings: [], error });
137
+ /** The argv one check runs: the settings' `command` with its placeholders filled, or the engine's own. */
138
+ export function gatePolicyArgv(settings, at) {
139
+ if (settings.command)
140
+ return settings.command.map((a) => a.replaceAll("{policy}", at.policy).replaceAll("{input}", at.input));
141
+ if ((settings.engine ?? "conftest") === "opa") {
142
+ return ["opa", "eval", "--format", "json", "--data", at.policy, "--input", at.input, `data.${settings.namespace ?? "main"}`];
143
+ }
144
+ return ["conftest", "test", "--no-color", "--output", "json", "--policy", at.policy, ...(settings.namespace ? ["--namespace", settings.namespace] : ["--all-namespaces"]), at.input];
145
+ }
146
+ /** Runs the engine with the job's environment. */
147
+ export const defaultGatePolicyExec = (argv, cwd) => {
148
+ const r = spawnSync(argv[0], argv.slice(1), { cwd, encoding: "utf-8", maxBuffer: 64 * 1024 * 1024 });
149
+ return { status: r.error ? null : r.status, stdout: r.stdout ?? "", stderr: `${r.stderr ?? ""}${r.error ? r.error.message : ""}` };
150
+ };
151
+ const tail = (text) => text.trim().split("\n").slice(-3).join(" ");
152
+ /** Run the engine once over `input` and read its answer. */
153
+ export function checkGatePolicy(options) {
154
+ const { settings } = options;
155
+ const engine = settings.engine ?? "conftest";
156
+ const work = mkdtempSync(join(tmpdir(), "chant-policy-input-"));
157
+ try {
158
+ const file = join(work, "input.json");
159
+ writeFileSync(file, typeof options.input === "string" ? options.input : JSON.stringify(options.input));
160
+ const argv = gatePolicyArgv(settings, { policy: options.policyDir, input: file });
161
+ const r = (options.exec ?? defaultGatePolicyExec)(argv, options.cwd ?? options.policyDir);
162
+ if (r.status === null)
163
+ return errorVerdict(engine, `${argv[0]} did not run: ${tail(r.stderr) || "no such command"}`);
164
+ const found = engine === "opa" ? opaFindings(r.stdout, settings.namespace ?? "main") : conftestFindings(r.stdout);
165
+ if (found === undefined)
166
+ return errorVerdict(engine, `${argv[0]} gave no ${engine} result (exit ${r.status}): ${tail(r.stderr || r.stdout)}`);
167
+ // conftest exits 1 on a denial and 2 or more when it could not run; opa exits 0 on a query it ran.
168
+ if (found.denials.length === 0 && r.status !== 0)
169
+ return errorVerdict(engine, `${argv[0]} exited ${r.status}: ${tail(r.stderr)}`);
170
+ return gatePolicyVerdict(engine, found);
171
+ }
172
+ finally {
173
+ rmSync(work, { recursive: true, force: true });
174
+ }
175
+ }
176
+ /**
177
+ * Copy the policy directory `path` out of `tree` (the tree at base) into a
178
+ * new temporary directory, so the engine reads the policy as the base has
179
+ * it and never the change's copy. Returns the directory and how to remove
180
+ * it, or the reason there is no policy to read.
181
+ */
182
+ export function exportGatePolicy(tree, path) {
183
+ const root = path.replace(/^\.\/+/, "").replace(/\/+$/, "");
184
+ const kind = root === "" || root === "." ? "dir" : tree.stat(root);
185
+ if (kind === undefined)
186
+ return { error: `the policy directory ${path} does not exist at base` };
187
+ const dir = mkdtempSync(join(tmpdir(), "chant-policy-"));
188
+ const cleanup = () => rmSync(dir, { recursive: true, force: true });
189
+ const copy = (from, to) => {
190
+ for (const entry of tree.list(from) ?? []) {
191
+ const src = from === "" || from === "." ? entry.name : `${from}/${entry.name}`;
192
+ const dest = join(to, entry.name);
193
+ if (entry.type === "dir") {
194
+ mkdirSync(dest, { recursive: true });
195
+ copy(src, dest);
196
+ }
197
+ else {
198
+ writeFileSync(dest, tree.bytes ? tree.bytes(src) : tree.read(src));
199
+ }
200
+ }
201
+ };
202
+ try {
203
+ if (kind === "file")
204
+ writeFileSync(join(dir, root.split("/").pop()), tree.bytes ? tree.bytes(root) : tree.read(root));
205
+ else
206
+ copy(root, dir);
207
+ }
208
+ catch (err) {
209
+ cleanup();
210
+ return { error: `the policy at ${path} could not be read at base: ${err instanceof Error ? err.message : String(err)}` };
211
+ }
212
+ return { dir, cleanup };
213
+ }
214
+ /** The settings, checked. Throws with what is wrong. `where` names them, such as `ci.policy`. */
215
+ export function assertGatePolicySettings(where, raw) {
216
+ if (raw === null || typeof raw !== "object" || Array.isArray(raw))
217
+ throw new Error(`${where} must be { engine?, path?, namespace?, command? }`);
218
+ for (const [key, value] of Object.entries(raw)) {
219
+ if (key === "engine") {
220
+ if (!GATE_POLICY_ENGINES.includes(value))
221
+ throw new Error(`${where}.engine must be ${GATE_POLICY_ENGINES.join(" or ")}, the result shape the command prints`);
222
+ }
223
+ else if (key === "path" || key === "namespace") {
224
+ if (typeof value !== "string" || value.trim() === "")
225
+ throw new Error(`${where}.${key} must be a non-empty string`);
226
+ if (key === "path" && (value.startsWith("/") || value.split("/").includes("..")))
227
+ throw new Error(`${where}.path must be a directory inside the workspace, relative to its declaration`);
228
+ }
229
+ else if (key === "command") {
230
+ if (!Array.isArray(value) || value.length === 0 || !value.every((a) => typeof a === "string" && a !== ""))
231
+ throw new Error(`${where}.command must be argv, a non-empty list of strings`);
232
+ if (!value.some((a) => a.includes("{input}")))
233
+ throw new Error(`${where}.command must pass the input file as {input}`);
234
+ }
235
+ else if (key === "outcomes" || key === "allow_unattended" || key === "allowUnattended") {
236
+ throw new Error(`${where}.${key}: chant's policy step denies and holds only; a non-empty plan always waits for a person (ws-104), so there is no unattended outcome`);
237
+ }
238
+ else {
239
+ throw new Error(`${where}.${key} is not a setting; the settings are engine, path, namespace and command`);
240
+ }
241
+ }
242
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"publish-fakes.d.ts","sourceRoot":"","sources":["../../../src/lifecycle/__fixtures__/publish-fakes.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"publish-fakes.d.ts","sourceRoot":"","sources":["../../../src/lifecycle/__fixtures__/publish-fakes.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAUH,MAAM,WAAW,YAAY;IAC3B,sDAAsD;IACtD,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC3B,SAAS,EAAE,GAAG,CAAC,MAAM,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACtE,0EAA0E;IAC1E,QAAQ,EAAE,KAAK,CAAC;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,aAAa,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,CAAC,CAAC;IAChF,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxB;AAED,0FAA0F;AAC1F,wBAAsB,iBAAiB,CAAC,KAAK,EAAE;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GAAG,OAAO,CAAC,YAAY,CAAC,CAmE3H;AAED,gGAAgG;AAChG,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,SAAgB,GAAG;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,MAAM,CAAA;CAAE,CAqB7G;AAED,iFAAiF;AACjF,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE;QAAE,QAAQ,EAAE,MAAM,EAAE,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;KAAE,CAAC,CAAC;CACpF;AAED,8EAA8E;AAC9E,eAAO,MAAM,mBAAmB,QAAS,MAAM,WAA2B,CAAC;AAE3E,oCAAoC;AACpC,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,MAAM,GAAG,eAAe,CAGhE;AAED,iFAAiF;AACjF,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,eAAe,GAAG,IAAI,CAEhF;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAiFhE;AAED,0DAA0D;AAC1D,MAAM,WAAW,gBAAgB;IAC/B,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,OAAO,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CACvE;AAED,oJAAoJ;AACpJ,MAAM,WAAW,SAAS;IACxB,KAAK,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QAAC,IAAI,CAAC,EAAE,UAAU,GAAG,MAAM,CAAA;KAAE,KAAK,OAAO,CAAC;QAAE,EAAE,EAAE,OAAO,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE;YAAE,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAA;SAAE,CAAC;QAAC,IAAI,IAAI,OAAO,CAAC,MAAM,CAAC,CAAA;KAAE,CAAC,CAAC;IAChO,KAAK,EAAE,KAAK,CAAC;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QAAC,IAAI,CAAC,EAAE,UAAU,GAAG,MAAM,CAAA;KAAE,CAAC,CAAC;IAC3G,QAAQ,EAAE,gBAAgB,EAAE,CAAC;IAC7B,oDAAoD;IACpD,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC9B,wFAAwF;IACxF,OAAO,IAAI,KAAK,CAAC;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC,CAAC;IACpD,gFAAgF;IAChF,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;CAChC;AAED,8EAA8E;AAC9E,wBAAgB,cAAc,CAAC,KAAK,EAAE,QAAQ,GAAG,SAAS,GAAG,QAAQ,EAAE,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,GAAE,gBAAgB,EAAO,GAAG,SAAS,CAmF9I;AAED,0EAA0E;AAC1E,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEjD;AAED,2GAA2G;AAC3G,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,UAAO,GAAG,IAAI,CAI1F;AAED,2GAA2G;AAC3G,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAM/C"}