@intentius/chant 0.124.32 → 0.124.34

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 (320) hide show
  1. package/dist/ci-jobs.d.ts +4 -0
  2. package/dist/ci-jobs.d.ts.map +1 -1
  3. package/dist/ci-jobs.js +9 -2
  4. package/dist/cli/handlers/build.d.ts.map +1 -1
  5. package/dist/cli/handlers/build.js +18 -3
  6. package/dist/cli/handlers/lifecycle-health.d.ts +16 -0
  7. package/dist/cli/handlers/lifecycle-health.d.ts.map +1 -0
  8. package/dist/cli/handlers/lifecycle-health.js +150 -0
  9. package/dist/cli/handlers/lifecycle.d.ts +26 -0
  10. package/dist/cli/handlers/lifecycle.d.ts.map +1 -1
  11. package/dist/cli/handlers/lifecycle.js +1 -1
  12. package/dist/cli/handlers/pr.d.ts +2 -0
  13. package/dist/cli/handlers/pr.d.ts.map +1 -1
  14. package/dist/cli/handlers/pr.js +124 -2
  15. package/dist/cli/handlers/promote.d.ts.map +1 -1
  16. package/dist/cli/handlers/promote.js +277 -74
  17. package/dist/cli/handlers/release.d.ts +16 -1
  18. package/dist/cli/handlers/release.d.ts.map +1 -1
  19. package/dist/cli/handlers/release.js +83 -11
  20. package/dist/cli/main.d.ts.map +1 -1
  21. package/dist/cli/main.js +99 -22
  22. package/dist/cli/parse-args.d.ts.map +1 -1
  23. package/dist/cli/parse-args.js +30 -1
  24. package/dist/cli/registry.d.ts +21 -5
  25. package/dist/cli/registry.d.ts.map +1 -1
  26. package/dist/components/deploy-inputs.d.ts +91 -0
  27. package/dist/components/deploy-inputs.d.ts.map +1 -0
  28. package/dist/components/deploy-inputs.js +184 -0
  29. package/dist/components/driver.d.ts.map +1 -1
  30. package/dist/components/driver.js +9 -0
  31. package/dist/components/gate-plan.d.ts +8 -0
  32. package/dist/components/gate-plan.d.ts.map +1 -1
  33. package/dist/components/gate-plan.js +6 -0
  34. package/dist/components/pr-pipeline.d.ts +33 -10
  35. package/dist/components/pr-pipeline.d.ts.map +1 -1
  36. package/dist/components/pr-pipeline.js +42 -11
  37. package/dist/components/pr-run.d.ts +1 -1
  38. package/dist/components/pr-run.d.ts.map +1 -1
  39. package/dist/components/promote-rounds.d.ts +102 -0
  40. package/dist/components/promote-rounds.d.ts.map +1 -0
  41. package/dist/components/promote-rounds.js +174 -0
  42. package/dist/components/release-compile.d.ts +37 -2
  43. package/dist/components/release-compile.d.ts.map +1 -1
  44. package/dist/components/release-compile.js +64 -8
  45. package/dist/components/release-gate.d.ts +30 -0
  46. package/dist/components/release-gate.d.ts.map +1 -1
  47. package/dist/components/release-gate.js +92 -42
  48. package/dist/components/release-promote.d.ts +17 -3
  49. package/dist/components/release-promote.d.ts.map +1 -1
  50. package/dist/components/release-promote.js +79 -18
  51. package/dist/components/verbs/bom-writer.d.ts +1 -1
  52. package/dist/components/verbs/bom-writer.d.ts.map +1 -1
  53. package/dist/components/verbs/lockfile-sbom-generator.d.ts +20 -0
  54. package/dist/components/verbs/lockfile-sbom-generator.d.ts.map +1 -1
  55. package/dist/components/verbs/lockfile-sbom-generator.js +41 -4
  56. package/dist/components/wave-plan.d.ts +10 -0
  57. package/dist/components/wave-plan.d.ts.map +1 -1
  58. package/dist/components/wave-plan.js +32 -4
  59. package/dist/graph-declared.d.ts +4 -0
  60. package/dist/graph-declared.d.ts.map +1 -1
  61. package/dist/graph-declared.js +11 -1
  62. package/dist/graph-detail.d.ts.map +1 -1
  63. package/dist/graph-detail.js +32 -7
  64. package/dist/health.d.ts +110 -0
  65. package/dist/health.d.ts.map +1 -0
  66. package/dist/health.js +105 -0
  67. package/dist/health.schema.json +191 -0
  68. package/dist/lexicon.d.ts +27 -0
  69. package/dist/lexicon.d.ts.map +1 -1
  70. package/dist/lifecycle/merge-record.d.ts +104 -0
  71. package/dist/lifecycle/merge-record.d.ts.map +1 -0
  72. package/dist/lifecycle/merge-record.js +102 -0
  73. package/dist/lifecycle/publish-attachments.d.ts +8 -5
  74. package/dist/lifecycle/publish-attachments.d.ts.map +1 -1
  75. package/dist/lifecycle/publish-attachments.js +27 -13
  76. package/dist/lifecycle/publish-npm.d.ts +3 -2
  77. package/dist/lifecycle/publish-npm.d.ts.map +1 -1
  78. package/dist/lifecycle/publish-npm.js +4 -3
  79. package/dist/lifecycle/release-at.d.ts +40 -0
  80. package/dist/lifecycle/release-at.d.ts.map +1 -0
  81. package/dist/lifecycle/release-at.js +83 -0
  82. package/dist/lifecycle/release-cut.d.ts.map +1 -1
  83. package/dist/lifecycle/release-cut.js +23 -12
  84. package/dist/lifecycle/release-publish.d.ts +28 -12
  85. package/dist/lifecycle/release-publish.d.ts.map +1 -1
  86. package/dist/lifecycle/release-publish.js +29 -17
  87. package/dist/lifecycle/release-store.d.ts +25 -2
  88. package/dist/lifecycle/release-store.d.ts.map +1 -1
  89. package/dist/lifecycle/release-store.js +40 -13
  90. package/dist/lifecycle/run-ledger.d.ts.map +1 -1
  91. package/dist/lifecycle/run-ledger.js +1 -0
  92. package/dist/lint/rules/op/index.d.ts +2 -1
  93. package/dist/lint/rules/op/index.d.ts.map +1 -1
  94. package/dist/lint/rules/op/index.js +4 -2
  95. package/dist/lint/rules/op/ops018-shell-image-digest.d.ts +10 -0
  96. package/dist/lint/rules/op/ops018-shell-image-digest.d.ts.map +1 -0
  97. package/dist/lint/rules/op/ops018-shell-image-digest.js +38 -0
  98. package/dist/op/activities/activity-contracts.d.ts +5 -0
  99. package/dist/op/activities/activity-contracts.d.ts.map +1 -1
  100. package/dist/op/activities/activity-contracts.js +3 -0
  101. package/dist/op/activities/shell.d.ts +38 -0
  102. package/dist/op/activities/shell.d.ts.map +1 -1
  103. package/dist/op/activities/shell.js +89 -3
  104. package/dist/op/builders.d.ts +7 -0
  105. package/dist/op/builders.d.ts.map +1 -1
  106. package/dist/op/builders.js +7 -0
  107. package/dist/op/runtime.d.ts +2 -0
  108. package/dist/op/runtime.d.ts.map +1 -1
  109. package/dist/pr-loop.d.ts +14 -3
  110. package/dist/pr-loop.d.ts.map +1 -1
  111. package/dist/pr-loop.js +15 -5
  112. package/dist/pr-report-url.d.ts +2 -2
  113. package/dist/pr-report-url.d.ts.map +1 -1
  114. package/dist/runner-start.d.ts +179 -0
  115. package/dist/runner-start.d.ts.map +1 -0
  116. package/dist/runner-start.js +277 -0
  117. package/dist/wave-runtime.mjs +236 -229
  118. package/dist/workspace/__fixtures__/contract-repo.d.ts.map +1 -1
  119. package/dist/workspace/__fixtures__/contract-repo.js +13 -2
  120. package/dist/workspace/check.schema.json +88 -2
  121. package/dist/workspace/checks/ci-workflow.d.ts +17 -0
  122. package/dist/workspace/checks/ci-workflow.d.ts.map +1 -0
  123. package/dist/workspace/checks/ci-workflow.js +123 -0
  124. package/dist/workspace/checks/security.d.ts +50 -0
  125. package/dist/workspace/checks/security.d.ts.map +1 -0
  126. package/dist/workspace/checks/security.js +305 -0
  127. package/dist/workspace/checks.d.ts +2 -0
  128. package/dist/workspace/checks.d.ts.map +1 -1
  129. package/dist/workspace/checks.js +8 -0
  130. package/dist/workspace/ci-builds.d.ts +163 -0
  131. package/dist/workspace/ci-builds.d.ts.map +1 -0
  132. package/dist/workspace/ci-builds.js +315 -0
  133. package/dist/workspace/ci-green-cli.d.ts +2 -1
  134. package/dist/workspace/ci-green-cli.d.ts.map +1 -1
  135. package/dist/workspace/ci-green-cli.js +25 -5
  136. package/dist/workspace/ci-green-forge.d.ts +12 -0
  137. package/dist/workspace/ci-green-forge.d.ts.map +1 -1
  138. package/dist/workspace/ci-green-forge.js +12 -0
  139. package/dist/workspace/ci-green-workflow.d.ts +55 -1
  140. package/dist/workspace/ci-green-workflow.d.ts.map +1 -1
  141. package/dist/workspace/ci-green-workflow.js +128 -1
  142. package/dist/workspace/ci-green.d.ts +89 -4
  143. package/dist/workspace/ci-green.d.ts.map +1 -1
  144. package/dist/workspace/ci-green.js +310 -23
  145. package/dist/workspace/ci-init.d.ts +100 -0
  146. package/dist/workspace/ci-init.d.ts.map +1 -0
  147. package/dist/workspace/ci-init.js +434 -0
  148. package/dist/workspace/ci-last-green.schema.json +49 -0
  149. package/dist/workspace/ci-list.schema.json +35 -1
  150. package/dist/workspace/ci-run-index.d.ts +1 -1
  151. package/dist/workspace/ci-run-index.d.ts.map +1 -1
  152. package/dist/workspace/ci-run-index.schema.json +1 -1
  153. package/dist/workspace/ci-run.d.ts +12 -6
  154. package/dist/workspace/ci-run.d.ts.map +1 -1
  155. package/dist/workspace/ci-run.js +72 -16
  156. package/dist/workspace/ci-runner-start.d.ts +77 -0
  157. package/dist/workspace/ci-runner-start.d.ts.map +1 -0
  158. package/dist/workspace/ci-runner-start.js +185 -0
  159. package/dist/workspace/ci-serve.d.ts +76 -7
  160. package/dist/workspace/ci-serve.d.ts.map +1 -1
  161. package/dist/workspace/ci-serve.js +153 -19
  162. package/dist/workspace/ci-workflow.d.ts +91 -0
  163. package/dist/workspace/ci-workflow.d.ts.map +1 -0
  164. package/dist/workspace/ci-workflow.js +33 -0
  165. package/dist/workspace/declaration.d.ts +35 -0
  166. package/dist/workspace/declaration.d.ts.map +1 -1
  167. package/dist/workspace/declaration.js +12 -2
  168. package/dist/workspace/declaration.schema.json +51 -0
  169. package/dist/workspace/init.js +2 -2
  170. package/dist/workspace/ls.d.ts +2 -0
  171. package/dist/workspace/ls.d.ts.map +1 -1
  172. package/dist/workspace/ls.js +1 -1
  173. package/dist/workspace/ls.schema.json +2 -1
  174. package/dist/workspace/member-pipeline.d.ts +1 -0
  175. package/dist/workspace/member-pipeline.d.ts.map +1 -1
  176. package/dist/workspace/member-pipeline.js +2 -0
  177. package/dist/workspace/merge-record.schema.json +49 -0
  178. package/dist/workspace/pr-report.schema.json +3 -3
  179. package/dist/workspace/reason-codes.d.ts +26 -0
  180. package/dist/workspace/reason-codes.d.ts.map +1 -1
  181. package/dist/workspace/reason-codes.js +28 -0
  182. package/dist/workspace/release-candidates.d.ts +129 -0
  183. package/dist/workspace/release-candidates.d.ts.map +1 -0
  184. package/dist/workspace/release-candidates.js +233 -0
  185. package/dist/workspace/release-candidates.schema.json +353 -0
  186. package/dist/workspace/release-workflow.d.ts +31 -0
  187. package/dist/workspace/release-workflow.d.ts.map +1 -0
  188. package/dist/workspace/release-workflow.js +150 -0
  189. package/dist/workspace/security.d.ts +40 -0
  190. package/dist/workspace/security.d.ts.map +1 -0
  191. package/dist/workspace/security.js +99 -0
  192. package/package.json +8 -1
  193. package/src/__fixtures__/pr-loop/pr-plan.note.golden.md +1 -1
  194. package/src/ci-jobs.ts +11 -2
  195. package/src/cli/handlers/build.test.ts +36 -0
  196. package/src/cli/handlers/build.ts +18 -3
  197. package/src/cli/handlers/lifecycle-health.test.ts +123 -0
  198. package/src/cli/handlers/lifecycle-health.ts +157 -0
  199. package/src/cli/handlers/lifecycle.ts +1 -1
  200. package/src/cli/handlers/pr-deploy-inputs.test.ts +209 -0
  201. package/src/cli/handlers/pr-record.test.ts +201 -0
  202. package/src/cli/handlers/pr.test.ts +22 -0
  203. package/src/cli/handlers/pr.ts +127 -4
  204. package/src/cli/handlers/promote.test.ts +17 -0
  205. package/src/cli/handlers/promote.ts +296 -84
  206. package/src/cli/handlers/release-cut-at.e2e.test.ts +115 -0
  207. package/src/cli/handlers/release-publish-targets.e2e.test.ts +2 -2
  208. package/src/cli/handlers/release-verify.e2e.test.ts +12 -10
  209. package/src/cli/handlers/release.e2e.test.ts +19 -28
  210. package/src/cli/handlers/release.ts +81 -11
  211. package/src/cli/main.ts +99 -22
  212. package/src/cli/parse-args.ts +22 -1
  213. package/src/cli/registry.ts +21 -5
  214. package/src/components/deploy-inputs.test.ts +108 -0
  215. package/src/components/deploy-inputs.ts +225 -0
  216. package/src/components/driver.ts +11 -0
  217. package/src/components/gate-plan.ts +9 -0
  218. package/src/components/pr-pipeline.ts +56 -15
  219. package/src/components/pr-run.ts +1 -1
  220. package/src/components/promote-rounds.test.ts +220 -0
  221. package/src/components/promote-rounds.ts +232 -0
  222. package/src/components/release-compile.test.ts +34 -0
  223. package/src/components/release-compile.ts +89 -8
  224. package/src/components/release-gate.test.ts +47 -0
  225. package/src/components/release-gate.ts +111 -46
  226. package/src/components/release-promote.test.ts +55 -2
  227. package/src/components/release-promote.ts +93 -23
  228. package/src/components/verbs/bom-writer.ts +1 -1
  229. package/src/components/verbs/lockfile-sbom-generator.test.ts +36 -0
  230. package/src/components/verbs/lockfile-sbom-generator.ts +43 -5
  231. package/src/components/wave-plan.ts +32 -4
  232. package/src/graph-declared-exports.test.ts +40 -0
  233. package/src/graph-declared.ts +9 -1
  234. package/src/graph-detail.test.ts +57 -0
  235. package/src/graph-detail.ts +33 -7
  236. package/src/health.schema.json +191 -0
  237. package/src/health.test.ts +62 -0
  238. package/src/health.ts +178 -0
  239. package/src/lexicon.ts +41 -1
  240. package/src/lifecycle/merge-record.test.ts +111 -0
  241. package/src/lifecycle/merge-record.ts +154 -0
  242. package/src/lifecycle/publish-attachments.ts +25 -13
  243. package/src/lifecycle/publish-npm.ts +4 -3
  244. package/src/lifecycle/publish-targets.test.ts +34 -4
  245. package/src/lifecycle/release-at.test.ts +48 -0
  246. package/src/lifecycle/release-at.ts +100 -0
  247. package/src/lifecycle/release-cut.test.ts +41 -0
  248. package/src/lifecycle/release-cut.ts +22 -11
  249. package/src/lifecycle/release-publish.test.ts +43 -13
  250. package/src/lifecycle/release-publish.ts +38 -18
  251. package/src/lifecycle/release-store.ts +57 -13
  252. package/src/lifecycle/run-ledger.test.ts +13 -0
  253. package/src/lifecycle/run-ledger.ts +1 -0
  254. package/src/lint/rules/op/index.ts +4 -2
  255. package/src/lint/rules/op/ops018-shell-image-digest.test.ts +60 -0
  256. package/src/lint/rules/op/ops018-shell-image-digest.ts +48 -0
  257. package/src/op/activities/activity-contracts.ts +3 -0
  258. package/src/op/activities/shell-container.e2e.test.ts +67 -0
  259. package/src/op/activities/shell.test.ts +44 -1
  260. package/src/op/activities/shell.ts +110 -3
  261. package/src/op/builders.ts +7 -0
  262. package/src/op/runtime.ts +2 -0
  263. package/src/op/step-console.test.ts +4 -2
  264. package/src/pr-loop.test.ts +29 -0
  265. package/src/pr-loop.ts +26 -7
  266. package/src/pr-report-url.ts +2 -2
  267. package/src/runner-start-bundle.test.ts +29 -0
  268. package/src/runner-start.test.ts +179 -0
  269. package/src/runner-start.ts +329 -0
  270. package/src/workspace/__fixtures__/contract-repo.ts +11 -2
  271. package/src/workspace/check-contract.test.ts +4 -2
  272. package/src/workspace/check.schema.json +88 -2
  273. package/src/workspace/checks/ci-workflow.test.ts +150 -0
  274. package/src/workspace/checks/ci-workflow.ts +129 -0
  275. package/src/workspace/checks/security.test.ts +138 -0
  276. package/src/workspace/checks/security.ts +305 -0
  277. package/src/workspace/checks.ts +8 -0
  278. package/src/workspace/ci-builds.e2e.test.ts +208 -0
  279. package/src/workspace/ci-builds.test.ts +174 -0
  280. package/src/workspace/ci-builds.ts +385 -0
  281. package/src/workspace/ci-green-builds.test.ts +180 -0
  282. package/src/workspace/ci-green-cli.test.ts +54 -0
  283. package/src/workspace/ci-green-cli.ts +26 -5
  284. package/src/workspace/ci-green-forge.ts +24 -0
  285. package/src/workspace/ci-green-workflow.test.ts +84 -1
  286. package/src/workspace/ci-green-workflow.ts +139 -2
  287. package/src/workspace/ci-green.test.ts +5 -2
  288. package/src/workspace/ci-green.ts +359 -27
  289. package/src/workspace/ci-init.test.ts +159 -0
  290. package/src/workspace/ci-init.ts +445 -0
  291. package/src/workspace/ci-last-green.schema.json +49 -0
  292. package/src/workspace/ci-list.schema.json +35 -1
  293. package/src/workspace/ci-report-url.test.ts +6 -1
  294. package/src/workspace/ci-run-index.schema.json +1 -1
  295. package/src/workspace/ci-run-index.ts +1 -1
  296. package/src/workspace/ci-run.test.ts +24 -7
  297. package/src/workspace/ci-run.ts +78 -16
  298. package/src/workspace/ci-runner-start.test.ts +151 -0
  299. package/src/workspace/ci-runner-start.ts +193 -0
  300. package/src/workspace/ci-serve-builds.test.ts +86 -0
  301. package/src/workspace/ci-serve.test.ts +59 -0
  302. package/src/workspace/ci-serve.ts +177 -22
  303. package/src/workspace/ci-workflow.ts +71 -0
  304. package/src/workspace/declaration.schema.json +51 -0
  305. package/src/workspace/declaration.test.ts +17 -0
  306. package/src/workspace/declaration.ts +53 -3
  307. package/src/workspace/init.ts +2 -2
  308. package/src/workspace/ls.schema.json +2 -1
  309. package/src/workspace/ls.ts +3 -2
  310. package/src/workspace/member-pipeline.ts +2 -1
  311. package/src/workspace/merge-record.schema.json +49 -0
  312. package/src/workspace/pr-report.schema.json +3 -3
  313. package/src/workspace/reason-codes.test.ts +4 -0
  314. package/src/workspace/reason-codes.ts +28 -0
  315. package/src/workspace/release-candidates.schema.json +353 -0
  316. package/src/workspace/release-candidates.test.ts +106 -0
  317. package/src/workspace/release-candidates.ts +315 -0
  318. package/src/workspace/release-workflow.ts +167 -0
  319. package/src/workspace/security.test.ts +70 -0
  320. package/src/workspace/security.ts +118 -0
@@ -1 +1 @@
1
- {"version":3,"file":"contract-repo.d.ts","sourceRoot":"","sources":["../../../src/workspace/__fixtures__/contract-repo.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAMH,OAAO,OAAO,MAAM,eAAe,CAAC;AAGpC,6CAA6C;AAC7C,eAAO,MAAM,IAAI,QAAwE,CAAC;AAI1F,gGAAgG;AAChG,wBAAgB,YAAY,IAAI,IAAI,CAEnC;AAED,wBAAgB,UAAU,CAAC,MAAM,SAAoB,GAAG,MAAM,CAI7D;AAED,wBAAgB,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,MAAM,EAAE,GAAG,MAAM,CAM1D;AAED,iFAAiF;AACjF,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC,GAAG,IAAI,CAO7G;AAED,uFAAuF;AACvF,wBAAgB,IAAI,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC,EAAE,MAAM,UAAQ,GAAG,MAAM,CAS3G;AAED;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,MAAM,EAAE,MAAM,SAAyB,GAAG,MAAM,MAAM,CAQ9F;AAED,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,SAAS,GAAG,MAAM,CAIhE;AAED,QAAA,MAAM,GAAG,SAAiD,CAAC;AAE3D,4EAA4E;AAC5E,wBAAgB,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG;IAAE,QAAQ,EAAE,UAAU,CAAC,OAAO,GAAG,CAAC,OAAO,CAAC,CAAC;IAAC,WAAW,CAAC,GAAG,EAAE,OAAO,GAAG,IAAI,CAAA;CAAE,CAStH;AAED,wBAAgB,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAEnD;AAED,eAAO,MAAM,WAAW,YAAa,OAAO,EAAE,UAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,WACL,CAAC;AAE1E;;;GAGG;AACH,eAAO,MAAM,gBAAgB,6XAU5B,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,qBAAqB,sZASjC,CAAC"}
1
+ {"version":3,"file":"contract-repo.d.ts","sourceRoot":"","sources":["../../../src/workspace/__fixtures__/contract-repo.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAMH,OAAO,OAAO,MAAM,eAAe,CAAC;AAGpC,6CAA6C;AAC7C,eAAO,MAAM,IAAI,QAAwE,CAAC;AAI1F,gGAAgG;AAChG,wBAAgB,YAAY,IAAI,IAAI,CAEnC;AAED,wBAAgB,UAAU,CAAC,MAAM,SAAoB,GAAG,MAAM,CAI7D;AAED,wBAAgB,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,MAAM,EAAE,GAAG,MAAM,CAM1D;AAED,iFAAiF;AACjF,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC,GAAG,IAAI,CAO7G;AAED,uFAAuF;AACvF,wBAAgB,IAAI,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC,EAAE,MAAM,UAAQ,GAAG,MAAM,CAS3G;AAED;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,MAAM,EAAE,MAAM,SAAyB,GAAG,MAAM,MAAM,CAiB9F;AAED,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,SAAS,GAAG,MAAM,CAIhE;AAED,QAAA,MAAM,GAAG,SAAiD,CAAC;AAE3D,4EAA4E;AAC5E,wBAAgB,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG;IAAE,QAAQ,EAAE,UAAU,CAAC,OAAO,GAAG,CAAC,OAAO,CAAC,CAAC;IAAC,WAAW,CAAC,GAAG,EAAE,OAAO,GAAG,IAAI,CAAA;CAAE,CAStH;AAED,wBAAgB,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAEnD;AAED,eAAO,MAAM,WAAW,YAAa,OAAO,EAAE,UAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,WACL,CAAC;AAE1E;;;GAGG;AACH,eAAO,MAAM,gBAAgB,6XAU5B,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,qBAAqB,sZASjC,CAAC"}
@@ -22,7 +22,7 @@ export function scratchDir(prefix = "chant-contract-") {
22
22
  return d;
23
23
  }
24
24
  export function git(cwd, ...args) {
25
- return execFileSync("git", ["-c", "user.name=t", "-c", "user.email=t@example.com", "-c", "commit.gpgsign=false", ...args], {
25
+ return execFileSync("git", ["-c", "user.name=t", "-c", "user.email=t@example.com", "-c", "commit.gpgsign=false", "-c", "gc.auto=0", "-c", "maintenance.auto=false", ...args], {
26
26
  cwd,
27
27
  encoding: "utf-8",
28
28
  stdio: ["ignore", "pipe", "pipe"],
@@ -61,7 +61,18 @@ export function repoFixture(build, prefix = "chant-contract-copy-") {
61
61
  if (template === undefined || !existsSync(template))
62
62
  template = build();
63
63
  const root = scratchDir(prefix);
64
- cpSync(template, root, { recursive: true });
64
+ // A commit can leave a detached `git gc --auto` or maintenance run moving
65
+ // the template's objects, and a copy that meets it fails with ENOENT on
66
+ // .git/objects. Copy once more after it settles.
67
+ try {
68
+ cpSync(template, root, { recursive: true });
69
+ }
70
+ catch (err) {
71
+ if (err.code !== "ENOENT")
72
+ throw err;
73
+ rmSync(root, { recursive: true, force: true });
74
+ cpSync(template, root, { recursive: true });
75
+ }
65
76
  return root;
66
77
  };
67
78
  }
@@ -298,7 +298,50 @@
298
298
  "flag-stale",
299
299
  "diagram-source-missing",
300
300
  "diagram-render-missing",
301
- "diagram-render-drift"
301
+ "diagram-render-drift",
302
+
303
+ "security-signers-missing",
304
+
305
+ "security-release-gate-missing",
306
+
307
+ "security-publish-allows-unsigned",
308
+
309
+ "security-sbom-disabled",
310
+
311
+ "security-sbom-unsourced",
312
+
313
+ "security-publish-no-provenance",
314
+
315
+ "security-component-no-sbom",
316
+
317
+ "security-component-no-verify",
318
+
319
+ "security-signed-since-unset",
320
+
321
+ "security-runner-untrusted",
322
+
323
+ "security-tick-no-key",
324
+
325
+ "security-review-not-required",
326
+
327
+ "security-attribution-any",
328
+
329
+ "security-gate-unsigned",
330
+
331
+ "security-runner-start-no-key",
332
+
333
+ "security-runner-start-no-role",
334
+
335
+ "security-trust-unreadable",
336
+ "ci-workflow-green-undeclared",
337
+ "ci-workflow-no-pre-merge",
338
+ "ci-workflow-apply-in-green",
339
+ "ci-workflow-no-components",
340
+ "ci-workflow-no-environments",
341
+ "ci-workflow-ungated-env",
342
+ "ci-workflow-pipeline-missing",
343
+ "ci-workflow-pipeline-differs",
344
+ "ci-workflow-publish-missing"
302
345
  ],
303
346
  "description": "For WSP001: why the declaration can't be read. For WSP121 to WSP127, the box finding (#2726, #2727, #2780, #2850). For WSP117, work-acceptance-unmet (#2772). For WSP118, flag-stale (#4024). For WSP131 to WSP133, the diagram finding (#2764)."
304
347
  }
@@ -377,7 +420,50 @@
377
420
  "flag-stale",
378
421
  "diagram-source-missing",
379
422
  "diagram-render-missing",
380
- "diagram-render-drift"
423
+ "diagram-render-drift",
424
+
425
+ "security-signers-missing",
426
+
427
+ "security-release-gate-missing",
428
+
429
+ "security-publish-allows-unsigned",
430
+
431
+ "security-sbom-disabled",
432
+
433
+ "security-sbom-unsourced",
434
+
435
+ "security-publish-no-provenance",
436
+
437
+ "security-component-no-sbom",
438
+
439
+ "security-component-no-verify",
440
+
441
+ "security-signed-since-unset",
442
+
443
+ "security-runner-untrusted",
444
+
445
+ "security-tick-no-key",
446
+
447
+ "security-review-not-required",
448
+
449
+ "security-attribution-any",
450
+
451
+ "security-gate-unsigned",
452
+
453
+ "security-runner-start-no-key",
454
+
455
+ "security-runner-start-no-role",
456
+
457
+ "security-trust-unreadable",
458
+ "ci-workflow-green-undeclared",
459
+ "ci-workflow-no-pre-merge",
460
+ "ci-workflow-apply-in-green",
461
+ "ci-workflow-no-components",
462
+ "ci-workflow-no-environments",
463
+ "ci-workflow-ungated-env",
464
+ "ci-workflow-pipeline-missing",
465
+ "ci-workflow-pipeline-differs",
466
+ "ci-workflow-publish-missing"
381
467
  ],
382
468
  "description": "For WSP001: why the declaration can't be read. For WSP121 to WSP127, the box finding (#2726, #2727, #2780, #2850). For WSP117, work-acceptance-unmet (#2772). For WSP118, flag-stale (#4024). For WSP131 to WSP133, the diagram finding (#2764)."
383
469
  },
@@ -0,0 +1,17 @@
1
+ /**
2
+ * WSP151 (#4168 step 6): what a member under `ci.workflow` still lacks.
3
+ *
4
+ * A declaration that names no workflow gets no finding from this file. The
5
+ * requirements and their codes are {@link CI_WORKFLOW_REQUIREMENTS}
6
+ * (../ci-workflow.ts). Like the security checklists (./security.ts), the
7
+ * check reads files and never runs them: `chant.config.ts` for the
8
+ * environments (its literal `environments`, as `workspace status` reads it),
9
+ * `*.component.ts` for components and gates, and the forge pipeline files for
10
+ * the jobs. A generated file that differs from what its generator writes is
11
+ * WSP101 to WSP106's to report; this check asks only whether the jobs the
12
+ * workflow needs are there.
13
+ */
14
+ import type { WorkspaceCheck } from "../checks.js";
15
+ export declare const WSP_CI_WORKFLOW = "WSP151";
16
+ export declare const CI_WORKFLOW_CHECKS: readonly WorkspaceCheck[];
17
+ //# sourceMappingURL=ci-workflow.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ci-workflow.d.ts","sourceRoot":"","sources":["../../../src/workspace/checks/ci-workflow.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAuB,MAAM,WAAW,CAAC;AAQrE,eAAO,MAAM,eAAe,WAAW,CAAC;AAaxC,eAAO,MAAM,kBAAkB,EAAE,SAAS,cAAc,EA6FvD,CAAC"}
@@ -0,0 +1,123 @@
1
+ /**
2
+ * WSP151 (#4168 step 6): what a member under `ci.workflow` still lacks.
3
+ *
4
+ * A declaration that names no workflow gets no finding from this file. The
5
+ * requirements and their codes are {@link CI_WORKFLOW_REQUIREMENTS}
6
+ * (../ci-workflow.ts). Like the security checklists (./security.ts), the
7
+ * check reads files and never runs them: `chant.config.ts` for the
8
+ * environments (its literal `environments`, as `workspace status` reads it),
9
+ * `*.component.ts` for components and gates, and the forge pipeline files for
10
+ * the jobs. A generated file that differs from what its generator writes is
11
+ * WSP101 to WSP106's to report; this check asks only whether the jobs the
12
+ * workflow needs are there.
13
+ */
14
+ import { appliesOnMerge, STUB_MARKER, workflowOf } from "../ci-workflow.js";
15
+ import { matchesCheckRun } from "../ci-green.js";
16
+ import { staticPromotionOrder } from "../promotion-order.js";
17
+ import { joinPath } from "../tree.js";
18
+ import { allPipelineFiles, commandLines, componentFiles, read } from "./security.js";
19
+ export const WSP_CI_WORKFLOW = "WSP151";
20
+ const memberDir = (dir) => (dir === "." ? "" : dir);
21
+ const PLAN = /\bci\s+run\s+plan\b|\bcomponents\s+pr-plan\b/;
22
+ const RECORD = /\bci\s+run\s+record\b|\bcomponents\s+pr-record\b/;
23
+ const APPLY = /\bci\s+run\s+apply\b|\bcomponents\s+pr-apply\b/;
24
+ const TICK = /\bci\s+tick\b/;
25
+ const PUBLISH = /\brelease\s+publish\b/;
26
+ /** The apply job names a required phase could match. */
27
+ const APPLY_JOB_NAMES = ["apply", "pr-apply"];
28
+ export const CI_WORKFLOW_CHECKS = [
29
+ {
30
+ id: WSP_CI_WORKFLOW,
31
+ name: "ci-workflow",
32
+ description: "Under ci.workflow: ci.green with pre-merge phases and no apply job required, each member with a component, environments and a gate on each component, and pipeline files that plan, record (or apply under apply on merge), tick and publish.",
33
+ severity: "warning",
34
+ configurable: true,
35
+ check(ctx) {
36
+ const d = ctx.declaration;
37
+ const under = d.members
38
+ .map((m) => ({ member: m, workflow: workflowOf(d, m) }))
39
+ .filter((u) => u.workflow !== null)
40
+ // The top level's workflow covers the members that build with chant; a member that names one itself is held to it.
41
+ .filter((u) => u.member.ciWorkflow !== null || ctx.tree.stat(joinPath(memberDir(u.member.dir), "chant.config.ts")) === "file");
42
+ if (under.length === 0 && !d.ci?.workflow)
43
+ return [];
44
+ const pointer = d.ci?.workflow ? "/ci/workflow" : `${under[0].member.pointer}/ci/workflow`;
45
+ const out = [];
46
+ const finding = (code, message, at = {}) => {
47
+ out.push({
48
+ checkId: this.id,
49
+ severity: this.severity,
50
+ code,
51
+ message: `${code}: ${message}`,
52
+ pointer: at.pointer ?? pointer,
53
+ ...(at.entity !== undefined ? { entity: at.entity } : {}),
54
+ ...(at.treeFile !== undefined ? { treeFile: at.treeFile, line: at.line ?? 1, column: 1 } : {}),
55
+ });
56
+ };
57
+ const green = d.ci?.green ?? null;
58
+ if (!green) {
59
+ finding("ci-workflow-green-undeclared", "the workspace follows a ci.workflow and declares no ci.green, so no commit gets a verdict and no release can be cut; run chant ci init or add a ci.green block");
60
+ }
61
+ else {
62
+ if ((green.preMerge ?? []).length === 0) {
63
+ finding("ci-workflow-no-pre-merge", "ci.green.preMerge names no phase, so nothing says which checks run before the branch moves; name the quick ones", { pointer: green.pointer });
64
+ }
65
+ for (const name of green.require) {
66
+ const phase = green.phases.find((p) => p.name === name);
67
+ const hit = phase?.runs.find((pattern) => APPLY_JOB_NAMES.some((job) => matchesCheckRun(pattern, job)));
68
+ if (phase && hit) {
69
+ finding("ci-workflow-apply-in-green", `the required phase ${name} matches the apply job with ${JSON.stringify(hit)}, so an apply that waits for a green commit would wait on itself; drop the pattern`, { pointer: phase.pointer });
70
+ }
71
+ }
72
+ }
73
+ for (const { member } of under) {
74
+ const dir = memberDir(member.dir);
75
+ const components = componentFiles(ctx.tree, dir);
76
+ if (components.length === 0) {
77
+ finding("ci-workflow-no-components", `member ${member.name} declares no component, so nothing of it can be cut or promoted; chant ci init writes a stub`, { pointer: member.pointer, entity: member.name });
78
+ }
79
+ else if (components.every((f) => (read(ctx.tree, f) ?? "").includes(STUB_MARKER))) {
80
+ finding("ci-workflow-no-components", `member ${member.name}'s only component is the stub chant ci init wrote (${components[0]}); say how it is built and deployed, then delete its ${STUB_MARKER} line`, { pointer: member.pointer, entity: member.name, treeFile: components[0] });
81
+ }
82
+ const configPath = joinPath(dir, "chant.config.ts");
83
+ const config = read(ctx.tree, configPath);
84
+ // A config whose environments aren't literals can't be read without running it; say nothing rather than guess.
85
+ const order = config === null ? [] : staticPromotionOrder(config);
86
+ if (order !== undefined && order.length === 0) {
87
+ finding("ci-workflow-no-environments", `member ${member.name}'s chant.config.ts declares no environments, so a release has nowhere to be promoted to`, { pointer: member.pointer, entity: member.name, ...(config !== null ? { treeFile: configPath } : {}) });
88
+ }
89
+ for (const file of components) {
90
+ const text = read(ctx.tree, file) ?? "";
91
+ if (/\bgate\s*\(|kind\s*:\s*["']gate["']/.test(text))
92
+ continue;
93
+ const last = order && order.length > 0 ? order[order.length - 1].name : "the last environment";
94
+ finding("ci-workflow-ungated-env", `${file} has no gate, so a promote to ${last} waits on no approval; add a gate() step`, { pointer: member.pointer, entity: member.name, treeFile: file });
95
+ }
96
+ }
97
+ const files = allPipelineFiles(ctx);
98
+ const has = (re) => files.some((f) => f.lines.some((l) => re.test(l)));
99
+ const applyOnMerge = under.some((u) => appliesOnMerge(u.workflow)) || (d.ci?.workflow ? appliesOnMerge(d.ci.workflow) : false);
100
+ const missing = [
101
+ ...(has(PLAN) ? [] : ["a plan job (ci run plan)"]),
102
+ ...(has(RECORD) || has(APPLY) ? [] : [applyOnMerge ? "an apply job (ci run apply)" : "a record job (ci run record)"]),
103
+ ...(has(TICK) ? [] : ["chant ci tick"]),
104
+ ];
105
+ if (missing.length > 0) {
106
+ finding("ci-workflow-pipeline-missing", `no pipeline file runs ${missing.join(", ")}; generate them with chant build --components --generate <forge> --pr-loop and chant ci workflow`);
107
+ }
108
+ if (!applyOnMerge) {
109
+ for (const l of commandLines(ctx, APPLY)) {
110
+ finding("ci-workflow-pipeline-differs", `${l.path} applies on merge, and the workflow is trunk-curated, where a merge records the plan and deploys nothing; regenerate without --apply-on-merge, or set ci.workflow to trunk-curated+apply-on-merge`, { treeFile: l.path, line: l.line });
111
+ }
112
+ }
113
+ else if (!has(APPLY) && has(RECORD)) {
114
+ const l = commandLines(ctx, RECORD)[0];
115
+ finding("ci-workflow-pipeline-differs", `${l.path} records on merge, and the workflow is trunk-curated+apply-on-merge; regenerate with --apply-on-merge`, { treeFile: l.path, line: l.line });
116
+ }
117
+ if (!has(PUBLISH)) {
118
+ finding("ci-workflow-publish-missing", "no pipeline file runs chant release publish, so a cut release is never published; generate one with chant release workflow");
119
+ }
120
+ return out;
121
+ },
122
+ },
123
+ ];
@@ -0,0 +1,50 @@
1
+ /**
2
+ * The security group checklists, WSP161 to WSP165 (#4168 step 7).
3
+ *
4
+ * A workspace opts into a security group with `security` in the
5
+ * declaration, at the top level or on a member (../security.ts). Each group
6
+ * has one check here, which reports what the group still lacks in the tree
7
+ * read. A group the declaration does not name is never checked, so a
8
+ * workspace that opts into nothing gets no finding from this file (#4168:
9
+ * security features are opt-in).
10
+ *
11
+ * | Id | Group | Codes |
12
+ * |---|---|---|
13
+ * | WSP161 | `verified-releases` | `security-signers-missing`, `security-release-gate-missing`, `security-publish-allows-unsigned` |
14
+ * | WSP162 | `supply-chain` | `security-sbom-disabled`, `security-sbom-unsourced`, `security-publish-no-provenance`, `security-component-no-sbom`, `security-component-no-verify` |
15
+ * | WSP163 | `who-ran-it` | `security-signed-since-unset`, `security-runner-untrusted`, `security-tick-no-key` |
16
+ * | WSP164 | `review-rules` | `security-review-not-required`, `security-attribution-any`, `security-gate-unsigned` |
17
+ * | WSP165 | `isolated-runners` | `security-runner-start-no-key`, `security-runner-start-no-role`, `security-trust-unreadable` |
18
+ *
19
+ * The checks read files, never run them: a member's `chant.config.ts` and
20
+ * `*.component.ts` are scanned as text, and the pipeline files are the
21
+ * forge files at the workspace root and in each member (`.github/workflows`,
22
+ * `.forgejo/workflows`, `.gitlab-ci.yml`). A command line is one line of a
23
+ * pipeline file that runs `chant release publish`, `chant ci tick` or
24
+ * `chant components pr-apply`.
25
+ */
26
+ import type { WorkspaceCheck, WorkspaceCheckContext } from "../checks.js";
27
+ import { type WorkspaceTree } from "../tree.js";
28
+ /** The read contract's codes for the security findings, carried as `code` on each. */
29
+ export declare const SECURITY_FINDING_CODES: readonly ["security-signers-missing", "security-release-gate-missing", "security-publish-allows-unsigned", "security-sbom-disabled", "security-sbom-unsourced", "security-publish-no-provenance", "security-component-no-sbom", "security-component-no-verify", "security-signed-since-unset", "security-runner-untrusted", "security-tick-no-key", "security-review-not-required", "security-attribution-any", "security-gate-unsigned", "security-runner-start-no-key", "security-runner-start-no-role", "security-trust-unreadable"];
30
+ export declare const WSP_SECURITY_VERIFIED_RELEASES = "WSP161";
31
+ export declare const WSP_SECURITY_SUPPLY_CHAIN = "WSP162";
32
+ export declare const WSP_SECURITY_WHO_RAN_IT = "WSP163";
33
+ export declare const WSP_SECURITY_REVIEW_RULES = "WSP164";
34
+ export declare const WSP_SECURITY_ISOLATED_RUNNERS = "WSP165";
35
+ export declare function read(tree: WorkspaceTree, path: string): string | null;
36
+ /** Every pipeline file of the workspace: its root's, and each member's. */
37
+ export declare function allPipelineFiles(ctx: WorkspaceCheckContext): {
38
+ path: string;
39
+ lines: string[];
40
+ }[];
41
+ /** The lines of the pipeline files that run a chant command, `command` naming it, such as `release publish`. */
42
+ export declare function commandLines(ctx: WorkspaceCheckContext, command: RegExp): {
43
+ path: string;
44
+ line: number;
45
+ text: string;
46
+ }[];
47
+ /** The member's `*.component.ts` files, walked a few levels down, past node_modules and build output. */
48
+ export declare function componentFiles(tree: WorkspaceTree, dir: string, depth?: number): string[];
49
+ export declare const SECURITY_CHECKS: readonly WorkspaceCheck[];
50
+ //# sourceMappingURL=security.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"security.d.ts","sourceRoot":"","sources":["../../../src/workspace/checks/security.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,qBAAqB,EAAuB,MAAM,WAAW,CAAC;AAI5F,OAAO,EAAY,KAAK,aAAa,EAAE,MAAM,SAAS,CAAC;AAMvD,sFAAsF;AACtF,eAAO,MAAM,sBAAsB,ygBAkBO,CAAC;AAI3C,eAAO,MAAM,8BAA8B,WAAW,CAAC;AACvD,eAAO,MAAM,yBAAyB,WAAW,CAAC;AAClD,eAAO,MAAM,uBAAuB,WAAW,CAAC;AAChD,eAAO,MAAM,yBAAyB,WAAW,CAAC;AAClD,eAAO,MAAM,6BAA6B,WAAW,CAAC;AAsBtD,wBAAgB,IAAI,CAAC,IAAI,EAAE,aAAa,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAOrE;AAkBD,2EAA2E;AAC3E,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,qBAAqB,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,EAAE,CAAA;CAAE,EAAE,CAGhG;AAED,gHAAgH;AAChH,wBAAgB,YAAY,CAAC,GAAG,EAAE,qBAAqB,EAAE,OAAO,EAAE,MAAM,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,EAAE,CAExH;AAED,yGAAyG;AACzG,wBAAgB,cAAc,CAAC,IAAI,EAAE,aAAa,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,SAAI,GAAG,MAAM,EAAE,CAQpF;AAuCD,eAAO,MAAM,eAAe,EAAE,SAAS,cAAc,EAuIpD,CAAC"}
@@ -0,0 +1,305 @@
1
+ /**
2
+ * The security group checklists, WSP161 to WSP165 (#4168 step 7).
3
+ *
4
+ * A workspace opts into a security group with `security` in the
5
+ * declaration, at the top level or on a member (../security.ts). Each group
6
+ * has one check here, which reports what the group still lacks in the tree
7
+ * read. A group the declaration does not name is never checked, so a
8
+ * workspace that opts into nothing gets no finding from this file (#4168:
9
+ * security features are opt-in).
10
+ *
11
+ * | Id | Group | Codes |
12
+ * |---|---|---|
13
+ * | WSP161 | `verified-releases` | `security-signers-missing`, `security-release-gate-missing`, `security-publish-allows-unsigned` |
14
+ * | WSP162 | `supply-chain` | `security-sbom-disabled`, `security-sbom-unsourced`, `security-publish-no-provenance`, `security-component-no-sbom`, `security-component-no-verify` |
15
+ * | WSP163 | `who-ran-it` | `security-signed-since-unset`, `security-runner-untrusted`, `security-tick-no-key` |
16
+ * | WSP164 | `review-rules` | `security-review-not-required`, `security-attribution-any`, `security-gate-unsigned` |
17
+ * | WSP165 | `isolated-runners` | `security-runner-start-no-key`, `security-runner-start-no-role`, `security-trust-unreadable` |
18
+ *
19
+ * The checks read files, never run them: a member's `chant.config.ts` and
20
+ * `*.component.ts` are scanned as text, and the pipeline files are the
21
+ * forge files at the workspace root and in each member (`.github/workflows`,
22
+ * `.forgejo/workflows`, `.gitlab-ci.yml`). A command line is one line of a
23
+ * pipeline file that runs `chant release publish`, `chant ci tick` or
24
+ * `chant components pr-apply`.
25
+ */
26
+ import { securityGroupsOf } from "../security.js";
27
+ import { joinPath } from "../tree.js";
28
+ import { runnerStartReadiness } from "../ci-runner-start.js";
29
+ import { readTrustPolicy } from "../trust/policy.js";
30
+ import { DEFAULT_SIGNERS_PATH, TRUST_CONFIG_PATH } from "../trust/trust-files.js";
31
+ /** The read contract's codes for the security findings, carried as `code` on each. */
32
+ export const SECURITY_FINDING_CODES = [
33
+ "security-signers-missing",
34
+ "security-release-gate-missing",
35
+ "security-publish-allows-unsigned",
36
+ "security-sbom-disabled",
37
+ "security-sbom-unsourced",
38
+ "security-publish-no-provenance",
39
+ "security-component-no-sbom",
40
+ "security-component-no-verify",
41
+ "security-signed-since-unset",
42
+ "security-runner-untrusted",
43
+ "security-tick-no-key",
44
+ "security-review-not-required",
45
+ "security-attribution-any",
46
+ "security-gate-unsigned",
47
+ "security-runner-start-no-key",
48
+ "security-runner-start-no-role",
49
+ "security-trust-unreadable",
50
+ ];
51
+ export const WSP_SECURITY_VERIFIED_RELEASES = "WSP161";
52
+ export const WSP_SECURITY_SUPPLY_CHAIN = "WSP162";
53
+ export const WSP_SECURITY_WHO_RAN_IT = "WSP163";
54
+ export const WSP_SECURITY_REVIEW_RULES = "WSP164";
55
+ export const WSP_SECURITY_ISOLATED_RUNNERS = "WSP165";
56
+ const memberDir = (dir) => (dir === "." ? "" : dir);
57
+ /** The members under `group`: every member when the top level names it, else those that name it themselves. */
58
+ function membersUnder(declaration, group) {
59
+ return declaration.members.filter((m) => securityGroupsOf(declaration, m.name).has(group));
60
+ }
61
+ /** Whether the declaration names `group` anywhere. */
62
+ function declares(declaration, group) {
63
+ return declaration.security.includes(group) || declaration.members.some((m) => m.security.includes(group));
64
+ }
65
+ /** Where the group is named, for the finding's pointer: the top level, else the first member naming it. */
66
+ function groupPointer(declaration, group) {
67
+ const top = declaration.security.indexOf(group);
68
+ if (top >= 0)
69
+ return `/security/${top}`;
70
+ const m = declaration.members.find((e) => e.security.includes(group));
71
+ return m ? `${m.pointer}/security/${m.security.indexOf(group)}` : "/security";
72
+ }
73
+ export function read(tree, path) {
74
+ if (tree.stat(path) !== "file")
75
+ return null;
76
+ try {
77
+ return tree.read(path);
78
+ }
79
+ catch {
80
+ return null;
81
+ }
82
+ }
83
+ /** The forge pipeline files under `dir`, each as its path and lines. */
84
+ function pipelineFiles(tree, dir) {
85
+ const out = [];
86
+ for (const folder of [".github/workflows", ".forgejo/workflows", ".gitea/workflows"]) {
87
+ for (const e of tree.list(joinPath(dir, folder)) ?? []) {
88
+ if (e.type !== "file" || !/\.ya?ml$/.test(e.name))
89
+ continue;
90
+ const path = joinPath(dir, folder, e.name);
91
+ const text = read(tree, path);
92
+ if (text !== null)
93
+ out.push({ path, lines: text.split("\n") });
94
+ }
95
+ }
96
+ const gitlab = read(tree, joinPath(dir, ".gitlab-ci.yml"));
97
+ if (gitlab !== null)
98
+ out.push({ path: joinPath(dir, ".gitlab-ci.yml"), lines: gitlab.split("\n") });
99
+ return out;
100
+ }
101
+ /** Every pipeline file of the workspace: its root's, and each member's. */
102
+ export function allPipelineFiles(ctx) {
103
+ const dirs = new Set(["", ...ctx.declaration.members.map((m) => memberDir(m.dir))]);
104
+ return [...dirs].flatMap((d) => pipelineFiles(ctx.tree, d));
105
+ }
106
+ /** The lines of the pipeline files that run a chant command, `command` naming it, such as `release publish`. */
107
+ export function commandLines(ctx, command) {
108
+ return allPipelineFiles(ctx).flatMap((f) => f.lines.flatMap((text, i) => (command.test(text) ? [{ path: f.path, line: i + 1, text }] : [])));
109
+ }
110
+ /** The member's `*.component.ts` files, walked a few levels down, past node_modules and build output. */
111
+ export function componentFiles(tree, dir, depth = 4) {
112
+ const out = [];
113
+ for (const e of tree.list(dir) ?? []) {
114
+ if (e.type === "dir") {
115
+ if (depth > 0 && !["node_modules", "dist", ".git", ".chant", "target"].includes(e.name))
116
+ out.push(...componentFiles(tree, joinPath(dir, e.name), depth - 1));
117
+ }
118
+ else if (e.name.endsWith(".component.ts"))
119
+ out.push(joinPath(dir, e.name));
120
+ }
121
+ return out;
122
+ }
123
+ /** Whether a lockfile chant writes an SBOM from is in `dir` or a directory above it in the tree. */
124
+ function lockfileAbove(tree, dir) {
125
+ let at = dir;
126
+ for (;;) {
127
+ if (["package-lock.json", "pom.xml", "Cargo.lock"].some((f) => tree.stat(joinPath(at, f)) === "file"))
128
+ return true;
129
+ if (at === "")
130
+ return false;
131
+ at = at.includes("/") ? at.slice(0, at.lastIndexOf("/")) : "";
132
+ }
133
+ }
134
+ /** The trust policy in the tree read: `.chant/trust.json` and the signers file it names. */
135
+ function treePolicy(tree) {
136
+ const source = {
137
+ label: tree.label,
138
+ list: (dir) => tree.list(dir)?.filter((e) => e.type === "file").map((e) => e.name),
139
+ read: (path) => tree.read(path),
140
+ bytes: (path) => (tree.bytes ? tree.bytes(path) : new Uint8Array(Buffer.from(tree.read(path), "utf-8"))),
141
+ };
142
+ return readTrustPolicy(source, null);
143
+ }
144
+ function finding(check, code, message, at) {
145
+ return {
146
+ checkId: check.id,
147
+ severity: check.severity,
148
+ code,
149
+ message: `${code}: ${message}`,
150
+ pointer: at.pointer,
151
+ ...(at.entity !== undefined ? { entity: at.entity } : {}),
152
+ ...(at.treeFile !== undefined ? { treeFile: at.treeFile, line: at.line ?? 1, column: 1 } : {}),
153
+ };
154
+ }
155
+ const RELEASE_PUBLISH = /\bchant\b.*\brelease\s+publish\b/;
156
+ const CI_TICK = /\bchant\b.*\bci\s+tick\b|main\.ts\s+ci\s+tick\b/;
157
+ const PR_APPLY = /\bcomponents\s+pr-apply\b|\bci\s+run\s+apply\b/;
158
+ export const SECURITY_CHECKS = [
159
+ {
160
+ id: WSP_SECURITY_VERIFIED_RELEASES,
161
+ name: "security-verified-releases",
162
+ description: "Under the verified-releases group: a signers file, a release gate naming a class, and no publish job that passes --allow-unsigned.",
163
+ severity: "warning",
164
+ configurable: true,
165
+ check(ctx) {
166
+ const d = ctx.declaration;
167
+ if (!declares(d, "verified-releases"))
168
+ return [];
169
+ const pointer = groupPointer(d, "verified-releases");
170
+ const out = [];
171
+ if (ctx.tree.stat(DEFAULT_SIGNERS_PATH) !== "file") {
172
+ out.push(finding(this, "security-signers-missing", `the workspace opts into verified-releases, and there is no ${DEFAULT_SIGNERS_PATH}${ctx.tree.label} to verify a release's signature against; add the keys of the people who cut releases to it, in ssh-keygen's allowed_signers format`, { pointer }));
173
+ }
174
+ const gate = d.identity?.gates.release;
175
+ if (!gate?.class) {
176
+ out.push(finding(this, "security-release-gate-missing", gate ? `identity.gates.release names no class, so any signer in ${DEFAULT_SIGNERS_PATH} may cut a release; name the class that may` : "the workspace opts into verified-releases, and identity.gates has no release entry, so a cut is not held to a class of signers", { pointer: gate?.pointer ?? pointer }));
177
+ }
178
+ for (const l of commandLines(ctx, RELEASE_PUBLISH)) {
179
+ if (!/--allow-unsigned\b/.test(l.text))
180
+ continue;
181
+ out.push(finding(this, "security-publish-allows-unsigned", `${l.path} runs release publish with --allow-unsigned, which publishes a release whose signature does not verify`, { pointer, treeFile: l.path, line: l.line }));
182
+ }
183
+ return out;
184
+ },
185
+ },
186
+ {
187
+ id: WSP_SECURITY_SUPPLY_CHAIN,
188
+ name: "security-supply-chain",
189
+ description: "Under the supply-chain group: every publish target attaches an SBOM it has a source for, publish jobs require OIDC provenance, and components generate an SBOM and verify what they deploy.",
190
+ severity: "warning",
191
+ configurable: true,
192
+ check(ctx) {
193
+ const d = ctx.declaration;
194
+ if (!declares(d, "supply-chain"))
195
+ return [];
196
+ const pointer = groupPointer(d, "supply-chain");
197
+ const out = [];
198
+ for (const m of membersUnder(d, "supply-chain")) {
199
+ const configPath = joinPath(memberDir(m.dir), "chant.config.ts");
200
+ const config = read(ctx.tree, configPath);
201
+ if (config !== null && /\bpublish\s*:/.test(config)) {
202
+ const lines = config.split("\n");
203
+ const off = lines.findIndex((l) => /\bsbom\s*:\s*false\b/.test(l));
204
+ if (off >= 0)
205
+ out.push(finding(this, "security-sbom-disabled", `member ${m.name}'s chant.config.ts sets sbom: false on a publish target, so what it publishes carries no SBOM`, { pointer, entity: m.name, treeFile: configPath, line: off + 1 }));
206
+ else if (!/\bsbom\s*:\s*["'`]/.test(config) && !lockfileAbove(ctx.tree, memberDir(m.dir))) {
207
+ out.push(finding(this, "security-sbom-unsourced", `member ${m.name} publishes, names no sbom file and has no package-lock.json, pom.xml or Cargo.lock in its directory or above it, so publish would refuse it as publish-sbom-missing`, { pointer, entity: m.name, treeFile: configPath }));
208
+ }
209
+ }
210
+ const components = componentFiles(ctx.tree, memberDir(m.dir));
211
+ if (components.length === 0)
212
+ continue;
213
+ const text = components.map((f) => read(ctx.tree, f) ?? "").join("\n");
214
+ if (!/\bgenerateSbom\s*\(|["']generate-sbom["']/.test(text)) {
215
+ out.push(finding(this, "security-component-no-sbom", `member ${m.name}'s components have no generate-sbom step, so what they build carries no SBOM`, { pointer, entity: m.name, treeFile: components[0] }));
216
+ }
217
+ if (!/\bverify\s*\(|["']verify["']/.test(text)) {
218
+ out.push(finding(this, "security-component-no-verify", `member ${m.name}'s components have no verify step, so a signature or attestation is never checked before a deploy`, { pointer, entity: m.name, treeFile: components[0] }));
219
+ }
220
+ }
221
+ for (const l of commandLines(ctx, RELEASE_PUBLISH)) {
222
+ if (/--require-provenance\b/.test(l.text))
223
+ continue;
224
+ out.push(finding(this, "security-publish-no-provenance", `${l.path} runs release publish without --require-provenance, so a run with no OIDC token publishes with only a warning`, { pointer, treeFile: l.path, line: l.line }));
225
+ }
226
+ return out;
227
+ },
228
+ },
229
+ {
230
+ id: WSP_SECURITY_WHO_RAN_IT,
231
+ name: "security-who-ran-it",
232
+ description: "Under the who-ran-it group: ci.green.signedSince is set, .chant/trust.json lists a runner key, and the tick signs its tags.",
233
+ severity: "warning",
234
+ configurable: true,
235
+ check(ctx) {
236
+ const d = ctx.declaration;
237
+ if (!declares(d, "who-ran-it"))
238
+ return [];
239
+ const pointer = groupPointer(d, "who-ran-it");
240
+ const out = [];
241
+ if (!d.ci?.green?.signedSince) {
242
+ out.push(finding(this, "security-signed-since-unset", d.ci?.green ? "ci.green sets no signedSince, so an unsigned green tag still counts" : "the workspace opts into who-ran-it, and has no ci.green block, so no commit's green tag is signed", { pointer: d.ci?.green?.pointer ?? pointer }));
243
+ }
244
+ const runners = treePolicy(ctx.tree).runners.filter((r) => r.class === "runner").length;
245
+ if (runners === 0) {
246
+ out.push(finding(this, "security-runner-untrusted", `${TRUST_CONFIG_PATH}${ctx.tree.label} lists no runner key, so no run or tag can be traced to a runner; add one with chant ci serve --setup-key --trust`, { pointer }));
247
+ }
248
+ for (const l of commandLines(ctx, CI_TICK)) {
249
+ const file = allPipelineFiles(ctx).find((f) => f.path === l.path);
250
+ if (/--key\b/.test(l.text) || file?.lines.some((t) => /CHANT_RUNNER_KEY/.test(t)))
251
+ continue;
252
+ out.push(finding(this, "security-tick-no-key", `${l.path} runs ci tick with no runner key, so its tags are unsigned; regenerate it with chant ci workflow --runner-key-secret <NAME>`, { pointer, treeFile: l.path, line: l.line }));
253
+ }
254
+ return out;
255
+ },
256
+ },
257
+ {
258
+ id: WSP_SECURITY_REVIEW_RULES,
259
+ name: "security-review-rules",
260
+ description: "Under the review-rules group: no apply job turns forge review off, attribution is identified, and every gate an apply job names needs a signed approval.",
261
+ severity: "warning",
262
+ configurable: true,
263
+ check(ctx) {
264
+ const d = ctx.declaration;
265
+ if (!declares(d, "review-rules"))
266
+ return [];
267
+ const pointer = groupPointer(d, "review-rules");
268
+ const out = [];
269
+ const applies = commandLines(ctx, PR_APPLY);
270
+ for (const l of applies) {
271
+ if (!/--no-require-review\b/.test(l.text))
272
+ continue;
273
+ out.push(finding(this, "security-review-not-required", `${l.path} passes --no-require-review, which the review-rules group overrides; drop it so the file says what runs`, { pointer, treeFile: l.path, line: l.line }));
274
+ }
275
+ if (d.identity?.attribution !== "identified") {
276
+ out.push(finding(this, "security-attribution-any", "identity.attribution is not identified, so a person-attributed write may name anyone", { pointer: d.identity ? "/identity" : pointer }));
277
+ }
278
+ const signed = new Set(Object.keys(d.identity?.gates ?? {}));
279
+ const named = new Set(applies.flatMap((l) => [...l.text.matchAll(/--(?:wave-)?gate\s+["']?([A-Za-z0-9_.-]+)/g)].map((m) => m[1])));
280
+ const unsigned = [...named].filter((g) => !signed.has(g));
281
+ if (unsigned.length > 0) {
282
+ out.push(finding(this, "security-gate-unsigned", `the apply jobs name the gate${unsigned.length > 1 ? "s" : ""} ${unsigned.join(", ")}, which identity.gates does not list, so an unsigned approval passes ${unsigned.length > 1 ? "them" : "it"}`, { pointer }));
283
+ }
284
+ else if (named.size === 0 && signed.size === 0) {
285
+ out.push(finding(this, "security-gate-unsigned", "identity.gates lists no gate, so every approval may be unsigned", { pointer }));
286
+ }
287
+ return out;
288
+ },
289
+ },
290
+ {
291
+ id: WSP_SECURITY_ISOLATED_RUNNERS,
292
+ name: "security-isolated-runners",
293
+ description: "Under the isolated-runners group: .chant/trust.json lists a runner key whose principal holds the runner role, so an apply job can require a runner-start statement (#4147).",
294
+ severity: "warning",
295
+ configurable: true,
296
+ check(ctx) {
297
+ const d = ctx.declaration;
298
+ if (!declares(d, "isolated-runners"))
299
+ return [];
300
+ const pointer = groupPointer(d, "isolated-runners");
301
+ const codes = { "no-runner-key": "security-runner-start-no-key", "no-runner-role": "security-runner-start-no-role", "trust-unreadable": "security-trust-unreadable" };
302
+ return runnerStartReadiness(treePolicy(ctx.tree)).gaps.map((g) => finding(this, codes[g.gap], `the workspace opts into isolated-runners, so every apply job requires a runner-start statement, and ${g.reason}`, { pointer }));
303
+ },
304
+ },
305
+ ];