@intentius/chant 0.124.31 → 0.124.33

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 (509) hide show
  1. package/bin/chant +7 -2
  2. package/dist/ci-jobs.d.ts +4 -0
  3. package/dist/ci-jobs.d.ts.map +1 -1
  4. package/dist/ci-jobs.js +9 -2
  5. package/dist/cli/commands/check-lexicon-plugin.d.ts +7 -4
  6. package/dist/cli/commands/check-lexicon-plugin.d.ts.map +1 -1
  7. package/dist/cli/commands/check-lexicon-plugin.js +10 -7
  8. package/dist/cli/commands/check-lexicon.d.ts.map +1 -1
  9. package/dist/cli/commands/check-lexicon.js +18 -12
  10. package/dist/cli/commands/import.d.ts +10 -0
  11. package/dist/cli/commands/import.d.ts.map +1 -1
  12. package/dist/cli/commands/import.js +37 -0
  13. package/dist/cli/commands/init-lexicon/templates/project.d.ts.map +1 -1
  14. package/dist/cli/commands/init-lexicon/templates/project.js +4 -3
  15. package/dist/cli/commands/onboard.d.ts.map +1 -1
  16. package/dist/cli/commands/onboard.js +8 -4
  17. package/dist/cli/commands/update.d.ts.map +1 -1
  18. package/dist/cli/commands/update.js +2 -1
  19. package/dist/cli/dependencies.d.ts +18 -0
  20. package/dist/cli/dependencies.d.ts.map +1 -0
  21. package/dist/cli/dependencies.js +120 -0
  22. package/dist/cli/handlers/build.d.ts.map +1 -1
  23. package/dist/cli/handlers/build.js +38 -4
  24. package/dist/cli/handlers/fan-out.d.ts.map +1 -1
  25. package/dist/cli/handlers/fan-out.js +12 -4
  26. package/dist/cli/handlers/graph.d.ts.map +1 -1
  27. package/dist/cli/handlers/graph.js +16 -6
  28. package/dist/cli/handlers/lifecycle-health.d.ts +16 -0
  29. package/dist/cli/handlers/lifecycle-health.d.ts.map +1 -0
  30. package/dist/cli/handlers/lifecycle-health.js +150 -0
  31. package/dist/cli/handlers/lifecycle.d.ts +26 -0
  32. package/dist/cli/handlers/lifecycle.d.ts.map +1 -1
  33. package/dist/cli/handlers/lifecycle.js +1 -1
  34. package/dist/cli/handlers/lint.d.ts.map +1 -1
  35. package/dist/cli/handlers/lint.js +6 -0
  36. package/dist/cli/handlers/operator.d.ts +2 -2
  37. package/dist/cli/handlers/operator.js +3 -3
  38. package/dist/cli/handlers/pr.d.ts +16 -0
  39. package/dist/cli/handlers/pr.d.ts.map +1 -1
  40. package/dist/cli/handlers/pr.js +610 -32
  41. package/dist/cli/handlers/promote.d.ts.map +1 -1
  42. package/dist/cli/handlers/promote.js +19 -2
  43. package/dist/cli/handlers/release.d.ts +16 -1
  44. package/dist/cli/handlers/release.d.ts.map +1 -1
  45. package/dist/cli/handlers/release.js +83 -11
  46. package/dist/cli/handlers/run.d.ts +13 -1
  47. package/dist/cli/handlers/run.d.ts.map +1 -1
  48. package/dist/cli/handlers/run.js +38 -15
  49. package/dist/cli/main.d.ts.map +1 -1
  50. package/dist/cli/main.js +121 -33
  51. package/dist/cli/parse-args.d.ts.map +1 -1
  52. package/dist/cli/parse-args.js +37 -0
  53. package/dist/cli/registry.d.ts +29 -6
  54. package/dist/cli/registry.d.ts.map +1 -1
  55. package/dist/cli/registry.js +6 -2
  56. package/dist/codegen/generate-post-synth-barrel.js +2 -2
  57. package/dist/codegen/lexicon-regen.d.ts +4 -0
  58. package/dist/codegen/lexicon-regen.d.ts.map +1 -1
  59. package/dist/codegen/lexicon-regen.js +1 -1
  60. package/dist/codegen/pinned-upgrade.js +2 -2
  61. package/dist/components/capability.d.ts +44 -3
  62. package/dist/components/capability.d.ts.map +1 -1
  63. package/dist/components/capability.js +21 -0
  64. package/dist/components/changed-files.d.ts +7 -1
  65. package/dist/components/changed-files.d.ts.map +1 -1
  66. package/dist/components/changed-files.js +9 -1
  67. package/dist/components/deploy-inputs.d.ts +91 -0
  68. package/dist/components/deploy-inputs.d.ts.map +1 -0
  69. package/dist/components/deploy-inputs.js +184 -0
  70. package/dist/components/driver.d.ts +38 -0
  71. package/dist/components/driver.d.ts.map +1 -1
  72. package/dist/components/driver.js +56 -6
  73. package/dist/components/fan-out-record.d.ts +6 -1
  74. package/dist/components/fan-out-record.d.ts.map +1 -1
  75. package/dist/components/fan-out-record.js +24 -0
  76. package/dist/components/fan-out-run.d.ts +20 -0
  77. package/dist/components/fan-out-run.d.ts.map +1 -1
  78. package/dist/components/fan-out-run.js +21 -1
  79. package/dist/components/gate-plan.d.ts +8 -0
  80. package/dist/components/gate-plan.d.ts.map +1 -1
  81. package/dist/components/gate-plan.js +6 -0
  82. package/dist/components/pr-pipeline.d.ts +98 -12
  83. package/dist/components/pr-pipeline.d.ts.map +1 -1
  84. package/dist/components/pr-pipeline.js +146 -23
  85. package/dist/components/pr-run.d.ts +128 -30
  86. package/dist/components/pr-run.d.ts.map +1 -1
  87. package/dist/components/pr-run.js +290 -70
  88. package/dist/components/release-compile.d.ts +37 -2
  89. package/dist/components/release-compile.d.ts.map +1 -1
  90. package/dist/components/release-compile.js +64 -8
  91. package/dist/components/release-promote.d.ts +17 -3
  92. package/dist/components/release-promote.d.ts.map +1 -1
  93. package/dist/components/release-promote.js +79 -18
  94. package/dist/components/verbs/bom-writer.d.ts +1 -1
  95. package/dist/components/verbs/bom-writer.d.ts.map +1 -1
  96. package/dist/components/verbs/lockfile-sbom-generator.d.ts +20 -0
  97. package/dist/components/verbs/lockfile-sbom-generator.d.ts.map +1 -1
  98. package/dist/components/verbs/lockfile-sbom-generator.js +41 -4
  99. package/dist/components/wave-plan.d.ts +15 -2
  100. package/dist/components/wave-plan.d.ts.map +1 -1
  101. package/dist/components/wave-plan.js +44 -5
  102. package/dist/discovery/fold-import.d.ts.map +1 -1
  103. package/dist/discovery/fold-import.js +16 -6
  104. package/dist/discovery/sandbox/bundle.d.ts.map +1 -1
  105. package/dist/discovery/sandbox/bundle.js +6 -0
  106. package/dist/discovery/specifier-resolve.d.ts.map +1 -1
  107. package/dist/discovery/specifier-resolve.js +8 -2
  108. package/dist/fold/fold.d.ts.map +1 -1
  109. package/dist/fold/fold.js +68 -1
  110. package/dist/fold/subset.d.ts +37 -0
  111. package/dist/fold/subset.d.ts.map +1 -1
  112. package/dist/fold/subset.js +88 -0
  113. package/dist/graph-detail.d.ts.map +1 -1
  114. package/dist/graph-detail.js +32 -7
  115. package/dist/health.d.ts +110 -0
  116. package/dist/health.d.ts.map +1 -0
  117. package/dist/health.js +105 -0
  118. package/dist/health.schema.json +191 -0
  119. package/dist/lexicon-module.d.ts +10 -0
  120. package/dist/lexicon-module.d.ts.map +1 -1
  121. package/dist/lexicon-module.js +53 -8
  122. package/dist/lexicon-plugin-helpers.d.ts.map +1 -1
  123. package/dist/lexicon-plugin-helpers.js +6 -4
  124. package/dist/lexicon.d.ts +29 -0
  125. package/dist/lexicon.d.ts.map +1 -1
  126. package/dist/lifecycle/affected.d.ts.map +1 -1
  127. package/dist/lifecycle/affected.js +7 -1
  128. package/dist/lifecycle/merge-record.d.ts +104 -0
  129. package/dist/lifecycle/merge-record.d.ts.map +1 -0
  130. package/dist/lifecycle/merge-record.js +102 -0
  131. package/dist/lifecycle/publish-attachments.d.ts +8 -5
  132. package/dist/lifecycle/publish-attachments.d.ts.map +1 -1
  133. package/dist/lifecycle/publish-attachments.js +27 -13
  134. package/dist/lifecycle/publish-npm.d.ts +3 -2
  135. package/dist/lifecycle/publish-npm.d.ts.map +1 -1
  136. package/dist/lifecycle/publish-npm.js +4 -3
  137. package/dist/lifecycle/release-at.d.ts +40 -0
  138. package/dist/lifecycle/release-at.d.ts.map +1 -0
  139. package/dist/lifecycle/release-at.js +83 -0
  140. package/dist/lifecycle/release-cut.d.ts.map +1 -1
  141. package/dist/lifecycle/release-cut.js +23 -12
  142. package/dist/lifecycle/release-ledger.d.ts +21 -0
  143. package/dist/lifecycle/release-ledger.d.ts.map +1 -1
  144. package/dist/lifecycle/release-ledger.js +24 -0
  145. package/dist/lifecycle/release-publish.d.ts +28 -12
  146. package/dist/lifecycle/release-publish.d.ts.map +1 -1
  147. package/dist/lifecycle/release-publish.js +29 -17
  148. package/dist/lifecycle/release-store.d.ts +25 -2
  149. package/dist/lifecycle/release-store.d.ts.map +1 -1
  150. package/dist/lifecycle/release-store.js +40 -13
  151. package/dist/lifecycle/run-ledger.d.ts.map +1 -1
  152. package/dist/lifecycle/run-ledger.js +1 -0
  153. package/dist/lifecycle/work-dir.d.ts +45 -0
  154. package/dist/lifecycle/work-dir.d.ts.map +1 -0
  155. package/dist/lifecycle/work-dir.js +78 -0
  156. package/dist/lint/discover.d.ts +17 -9
  157. package/dist/lint/discover.d.ts.map +1 -1
  158. package/dist/lint/discover.js +62 -54
  159. package/dist/lint/rules/op/index.d.ts +2 -1
  160. package/dist/lint/rules/op/index.d.ts.map +1 -1
  161. package/dist/lint/rules/op/index.js +4 -2
  162. package/dist/lint/rules/op/ops018-shell-image-digest.d.ts +10 -0
  163. package/dist/lint/rules/op/ops018-shell-image-digest.d.ts.map +1 -0
  164. package/dist/lint/rules/op/ops018-shell-image-digest.js +38 -0
  165. package/dist/module-source.d.ts +57 -0
  166. package/dist/module-source.d.ts.map +1 -0
  167. package/dist/module-source.js +104 -0
  168. package/dist/op/activities/activity-contracts.d.ts +5 -0
  169. package/dist/op/activities/activity-contracts.d.ts.map +1 -1
  170. package/dist/op/activities/activity-contracts.js +3 -0
  171. package/dist/op/activities/factory.js +1 -1
  172. package/dist/op/activities/shell.d.ts +38 -0
  173. package/dist/op/activities/shell.d.ts.map +1 -1
  174. package/dist/op/activities/shell.js +89 -3
  175. package/dist/op/builders.d.ts +7 -0
  176. package/dist/op/builders.d.ts.map +1 -1
  177. package/dist/op/builders.js +7 -0
  178. package/dist/op/discover.d.ts.map +1 -1
  179. package/dist/op/discover.js +13 -3
  180. package/dist/op/gate.d.ts.map +1 -1
  181. package/dist/op/gate.js +3 -0
  182. package/dist/op/op-waves-run.d.ts.map +1 -1
  183. package/dist/op/op-waves-run.js +20 -5
  184. package/dist/op/operator.d.ts +2 -2
  185. package/dist/op/operator.js +2 -2
  186. package/dist/op/runtime.d.ts +2 -0
  187. package/dist/op/runtime.d.ts.map +1 -1
  188. package/dist/op/steward.d.ts +6 -4
  189. package/dist/op/steward.d.ts.map +1 -1
  190. package/dist/op/steward.js +5 -3
  191. package/dist/op/work-lease-run.d.ts +20 -7
  192. package/dist/op/work-lease-run.d.ts.map +1 -1
  193. package/dist/op/work-lease-run.js +56 -12
  194. package/dist/pr-forge.d.ts +10 -0
  195. package/dist/pr-forge.d.ts.map +1 -1
  196. package/dist/pr-forge.js +39 -0
  197. package/dist/pr-loop.d.ts +155 -7
  198. package/dist/pr-loop.d.ts.map +1 -1
  199. package/dist/pr-loop.js +137 -11
  200. package/dist/pr-report-url.d.ts +4 -4
  201. package/dist/pr-report-url.d.ts.map +1 -1
  202. package/dist/pr-report-url.js +5 -5
  203. package/dist/runner-start.d.ts +179 -0
  204. package/dist/runner-start.d.ts.map +1 -0
  205. package/dist/runner-start.js +277 -0
  206. package/dist/ts-loader.d.ts +38 -4
  207. package/dist/ts-loader.d.ts.map +1 -1
  208. package/dist/ts-loader.js +190 -16
  209. package/dist/wave-runtime.mjs +238 -231
  210. package/dist/workspace/chant-migrations/fountain-retired-kinds.d.ts +34 -0
  211. package/dist/workspace/chant-migrations/fountain-retired-kinds.d.ts.map +1 -0
  212. package/dist/workspace/chant-migrations/fountain-retired-kinds.js +176 -0
  213. package/dist/workspace/chant-migrations.d.ts.map +1 -1
  214. package/dist/workspace/chant-migrations.js +2 -0
  215. package/dist/workspace/check.schema.json +70 -2
  216. package/dist/workspace/checks/boxes.d.ts +6 -6
  217. package/dist/workspace/checks/boxes.js +9 -9
  218. package/dist/workspace/checks/security.d.ts +35 -0
  219. package/dist/workspace/checks/security.d.ts.map +1 -0
  220. package/dist/workspace/checks/security.js +305 -0
  221. package/dist/workspace/checks.d.ts +2 -0
  222. package/dist/workspace/checks.d.ts.map +1 -1
  223. package/dist/workspace/checks.js +5 -0
  224. package/dist/workspace/ci-builds.d.ts +163 -0
  225. package/dist/workspace/ci-builds.d.ts.map +1 -0
  226. package/dist/workspace/ci-builds.js +315 -0
  227. package/dist/workspace/ci-green-cli.d.ts +2 -1
  228. package/dist/workspace/ci-green-cli.d.ts.map +1 -1
  229. package/dist/workspace/ci-green-cli.js +25 -5
  230. package/dist/workspace/ci-green-forge.d.ts +18 -4
  231. package/dist/workspace/ci-green-forge.d.ts.map +1 -1
  232. package/dist/workspace/ci-green-forge.js +12 -0
  233. package/dist/workspace/ci-green-workflow.d.ts +55 -1
  234. package/dist/workspace/ci-green-workflow.d.ts.map +1 -1
  235. package/dist/workspace/ci-green-workflow.js +128 -1
  236. package/dist/workspace/ci-green.d.ts +117 -16
  237. package/dist/workspace/ci-green.d.ts.map +1 -1
  238. package/dist/workspace/ci-green.js +358 -43
  239. package/dist/workspace/ci-last-green.schema.json +49 -0
  240. package/dist/workspace/ci-list.schema.json +47 -7
  241. package/dist/workspace/ci-report-url.d.ts +18 -0
  242. package/dist/workspace/ci-report-url.d.ts.map +1 -0
  243. package/dist/workspace/ci-report-url.js +33 -0
  244. package/dist/workspace/ci-run-index.d.ts +13 -1
  245. package/dist/workspace/ci-run-index.d.ts.map +1 -1
  246. package/dist/workspace/ci-run-index.js +6 -4
  247. package/dist/workspace/ci-run-index.schema.json +6 -4
  248. package/dist/workspace/ci-run.d.ts +85 -6
  249. package/dist/workspace/ci-run.d.ts.map +1 -1
  250. package/dist/workspace/ci-run.js +262 -64
  251. package/dist/workspace/ci-runner-key.d.ts +16 -3
  252. package/dist/workspace/ci-runner-key.d.ts.map +1 -1
  253. package/dist/workspace/ci-runner-key.js +30 -10
  254. package/dist/workspace/ci-runner-start.d.ts +77 -0
  255. package/dist/workspace/ci-runner-start.d.ts.map +1 -0
  256. package/dist/workspace/ci-runner-start.js +185 -0
  257. package/dist/workspace/ci-serve.d.ts +130 -11
  258. package/dist/workspace/ci-serve.d.ts.map +1 -1
  259. package/dist/workspace/ci-serve.js +323 -40
  260. package/dist/workspace/declaration.d.ts +25 -0
  261. package/dist/workspace/declaration.d.ts.map +1 -1
  262. package/dist/workspace/declaration.js +14 -2
  263. package/dist/workspace/declaration.schema.json +30 -2
  264. package/dist/workspace/export.d.ts +1 -1
  265. package/dist/workspace/export.d.ts.map +1 -1
  266. package/dist/workspace/export.js +7 -3
  267. package/dist/workspace/ls.d.ts +8 -1
  268. package/dist/workspace/ls.d.ts.map +1 -1
  269. package/dist/workspace/ls.js +2 -1
  270. package/dist/workspace/ls.schema.json +9 -2
  271. package/dist/workspace/member-pipeline.d.ts +1 -0
  272. package/dist/workspace/member-pipeline.d.ts.map +1 -1
  273. package/dist/workspace/member-pipeline.js +2 -0
  274. package/dist/workspace/merge-record.schema.json +49 -0
  275. package/dist/workspace/pin-integrity.d.ts.map +1 -1
  276. package/dist/workspace/pin-integrity.js +3 -2
  277. package/dist/workspace/pr-report.schema.json +75 -8
  278. package/dist/workspace/reason-codes.d.ts +18 -1
  279. package/dist/workspace/reason-codes.d.ts.map +1 -1
  280. package/dist/workspace/reason-codes.js +20 -2
  281. package/dist/workspace/release-candidates.d.ts +129 -0
  282. package/dist/workspace/release-candidates.d.ts.map +1 -0
  283. package/dist/workspace/release-candidates.js +233 -0
  284. package/dist/workspace/release-candidates.schema.json +353 -0
  285. package/dist/workspace/run-event.schema.json +3 -1
  286. package/dist/workspace/security.d.ts +40 -0
  287. package/dist/workspace/security.d.ts.map +1 -0
  288. package/dist/workspace/security.js +99 -0
  289. package/dist/workspace/status.schema.json +3 -1
  290. package/dist/workspace/write-scope.d.ts +9 -0
  291. package/dist/workspace/write-scope.d.ts.map +1 -1
  292. package/dist/workspace/write-scope.js +11 -1
  293. package/package.json +8 -1
  294. package/src/__fixtures__/pr-loop/pr-apply-wave-waiting.note.golden.md +48 -0
  295. package/src/__fixtures__/pr-loop/pr-plan.note.golden.md +1 -1
  296. package/src/ci-jobs.ts +11 -2
  297. package/src/cli/commands/__fixtures__/init-lexicon-output/package.json +3 -3
  298. package/src/cli/commands/audit.test.ts +2 -2
  299. package/src/cli/commands/check-lexicon-plugin.test.ts +9 -0
  300. package/src/cli/commands/check-lexicon-plugin.ts +11 -8
  301. package/src/cli/commands/check-lexicon.test.ts +1 -1
  302. package/src/cli/commands/check-lexicon.ts +16 -11
  303. package/src/cli/commands/import.ts +40 -0
  304. package/src/cli/commands/init-lexicon/templates/project.ts +4 -3
  305. package/src/cli/commands/init-lexicon.test.ts +8 -5
  306. package/src/cli/commands/onboard.test.ts +45 -0
  307. package/src/cli/commands/onboard.ts +8 -4
  308. package/src/cli/commands/update.test.ts +6 -0
  309. package/src/cli/commands/update.ts +4 -1
  310. package/src/cli/compiled-cli.e2e.test.ts +76 -11
  311. package/src/cli/dependencies.e2e.test.ts +101 -0
  312. package/src/cli/dependencies.test.ts +93 -0
  313. package/src/cli/dependencies.ts +122 -0
  314. package/src/cli/handlers/build.test.ts +105 -0
  315. package/src/cli/handlers/build.ts +35 -4
  316. package/src/cli/handlers/fan-out.test.ts +73 -0
  317. package/src/cli/handlers/fan-out.ts +11 -4
  318. package/src/cli/handlers/graph.test.ts +76 -0
  319. package/src/cli/handlers/graph.ts +19 -12
  320. package/src/cli/handlers/lifecycle-health.test.ts +123 -0
  321. package/src/cli/handlers/lifecycle-health.ts +157 -0
  322. package/src/cli/handlers/lifecycle.ts +1 -1
  323. package/src/cli/handlers/lint.test.ts +42 -0
  324. package/src/cli/handlers/lint.ts +6 -0
  325. package/src/cli/handlers/operator.ts +3 -3
  326. package/src/cli/handlers/pr-deploy-inputs.test.ts +209 -0
  327. package/src/cli/handlers/pr-record.test.ts +201 -0
  328. package/src/cli/handlers/pr-waves.test.ts +449 -0
  329. package/src/cli/handlers/pr.test.ts +156 -0
  330. package/src/cli/handlers/pr.ts +648 -33
  331. package/src/cli/handlers/promote.test.ts +43 -1
  332. package/src/cli/handlers/promote.ts +19 -3
  333. package/src/cli/handlers/release-cut-at.e2e.test.ts +115 -0
  334. package/src/cli/handlers/release-publish-targets.e2e.test.ts +2 -2
  335. package/src/cli/handlers/release-verify.e2e.test.ts +12 -10
  336. package/src/cli/handlers/release.e2e.test.ts +19 -28
  337. package/src/cli/handlers/release.ts +81 -11
  338. package/src/cli/handlers/run-components-exit.test.ts +99 -0
  339. package/src/cli/handlers/run-signer.test.ts +79 -0
  340. package/src/cli/handlers/run.ts +34 -16
  341. package/src/cli/main.test.ts +18 -0
  342. package/src/cli/main.ts +120 -33
  343. package/src/cli/parse-args.ts +28 -0
  344. package/src/cli/registry.ts +35 -8
  345. package/src/codegen/generate-post-synth-barrel.ts +2 -2
  346. package/src/codegen/lexicon-regen.ts +1 -1
  347. package/src/codegen/pinned-upgrade.ts +2 -2
  348. package/src/codegen/post-synth-barrel-guards.test.ts +6 -11
  349. package/src/components/capability.ts +51 -3
  350. package/src/components/changed-files.test.ts +16 -1
  351. package/src/components/changed-files.ts +9 -1
  352. package/src/components/deploy-inputs.test.ts +108 -0
  353. package/src/components/deploy-inputs.ts +225 -0
  354. package/src/components/driver.test.ts +36 -1
  355. package/src/components/driver.ts +87 -4
  356. package/src/components/fan-out-record.ts +29 -1
  357. package/src/components/fan-out-run.test.ts +35 -0
  358. package/src/components/fan-out-run.ts +40 -1
  359. package/src/components/gate-plan.ts +9 -0
  360. package/src/components/pr-pipeline.test.ts +100 -0
  361. package/src/components/pr-pipeline.ts +184 -28
  362. package/src/components/pr-run.test.ts +323 -18
  363. package/src/components/pr-run.ts +355 -76
  364. package/src/components/release-compile.test.ts +34 -0
  365. package/src/components/release-compile.ts +89 -8
  366. package/src/components/release-promote.test.ts +55 -2
  367. package/src/components/release-promote.ts +93 -23
  368. package/src/components/verbs/bom-writer.ts +1 -1
  369. package/src/components/verbs/lockfile-sbom-generator.test.ts +36 -0
  370. package/src/components/verbs/lockfile-sbom-generator.ts +43 -5
  371. package/src/components/wave-plan.ts +48 -8
  372. package/src/discovery/fold-host-method.test.ts +87 -0
  373. package/src/discovery/fold-import.ts +15 -5
  374. package/src/discovery/fold-package-composite.test.ts +27 -0
  375. package/src/discovery/fold-specifiers.test.ts +8 -0
  376. package/src/discovery/sandbox/bundle.ts +6 -0
  377. package/src/discovery/specifier-resolve.ts +8 -2
  378. package/src/fold/fold.test.ts +72 -0
  379. package/src/fold/fold.ts +71 -0
  380. package/src/fold/subset.test.ts +31 -0
  381. package/src/fold/subset.ts +99 -0
  382. package/src/graph-detail.test.ts +57 -0
  383. package/src/graph-detail.ts +33 -7
  384. package/src/health.schema.json +191 -0
  385. package/src/health.test.ts +62 -0
  386. package/src/health.ts +178 -0
  387. package/src/lexicon-dist.e2e.test.ts +254 -0
  388. package/src/lexicon-module.test.ts +47 -0
  389. package/src/lexicon-module.ts +49 -7
  390. package/src/lexicon-plugin-helpers.ts +6 -4
  391. package/src/lexicon.ts +43 -1
  392. package/src/lifecycle/affected.test.ts +7 -0
  393. package/src/lifecycle/affected.ts +6 -1
  394. package/src/lifecycle/merge-record.test.ts +111 -0
  395. package/src/lifecycle/merge-record.ts +154 -0
  396. package/src/lifecycle/publish-attachments.ts +25 -13
  397. package/src/lifecycle/publish-npm.ts +4 -3
  398. package/src/lifecycle/publish-targets.test.ts +34 -4
  399. package/src/lifecycle/release-at.test.ts +48 -0
  400. package/src/lifecycle/release-at.ts +100 -0
  401. package/src/lifecycle/release-cut.test.ts +41 -0
  402. package/src/lifecycle/release-cut.ts +22 -11
  403. package/src/lifecycle/release-ledger.ts +34 -0
  404. package/src/lifecycle/release-publish.test.ts +43 -13
  405. package/src/lifecycle/release-publish.ts +38 -18
  406. package/src/lifecycle/release-store.ts +57 -13
  407. package/src/lifecycle/run-ledger.test.ts +13 -0
  408. package/src/lifecycle/run-ledger.ts +1 -0
  409. package/src/lifecycle/work-dir.test.ts +77 -0
  410. package/src/lifecycle/work-dir.ts +82 -0
  411. package/src/lint/discover.test.ts +46 -2
  412. package/src/lint/discover.ts +55 -49
  413. package/src/lint/rules/op/index.ts +4 -2
  414. package/src/lint/rules/op/ops018-shell-image-digest.test.ts +60 -0
  415. package/src/lint/rules/op/ops018-shell-image-digest.ts +48 -0
  416. package/src/module-source.test.ts +85 -0
  417. package/src/module-source.ts +112 -0
  418. package/src/op/activities/activity-contracts.ts +3 -0
  419. package/src/op/activities/factory.ts +1 -1
  420. package/src/op/activities/shell-container.e2e.test.ts +67 -0
  421. package/src/op/activities/shell.test.ts +44 -1
  422. package/src/op/activities/shell.ts +110 -3
  423. package/src/op/builders.ts +7 -0
  424. package/src/op/discover-root.test.ts +31 -2
  425. package/src/op/discover.ts +14 -4
  426. package/src/op/gate.test.ts +30 -0
  427. package/src/op/gate.ts +3 -0
  428. package/src/op/op-waves-run.ts +20 -5
  429. package/src/op/op-waves.test.ts +49 -0
  430. package/src/op/operator.ts +2 -2
  431. package/src/op/runtime.ts +2 -0
  432. package/src/op/step-console.test.ts +4 -2
  433. package/src/op/steward.ts +6 -4
  434. package/src/op/work-lease-run.test.ts +88 -2
  435. package/src/op/work-lease-run.ts +52 -12
  436. package/src/pr-forge-bundle.test.ts +2 -2
  437. package/src/pr-forge.test.ts +42 -0
  438. package/src/pr-forge.ts +40 -0
  439. package/src/pr-loop-bundle.test.ts +2 -1
  440. package/src/pr-loop.test.ts +146 -1
  441. package/src/pr-loop.ts +278 -17
  442. package/src/pr-report-url.ts +7 -7
  443. package/src/runner-start-bundle.test.ts +29 -0
  444. package/src/runner-start.test.ts +179 -0
  445. package/src/runner-start.ts +329 -0
  446. package/src/terraform/__fixtures__/advise.test.ts +21 -2
  447. package/src/ts-loader.test.ts +89 -1
  448. package/src/ts-loader.ts +180 -13
  449. package/src/workspace/changes.test.ts +1 -1
  450. package/src/workspace/chant-migrations/fountain-retired-kinds.test.ts +33 -0
  451. package/src/workspace/chant-migrations/fountain-retired-kinds.ts +190 -0
  452. package/src/workspace/chant-migrations.e2e.test.ts +5 -0
  453. package/src/workspace/chant-migrations.ts +2 -0
  454. package/src/workspace/check-contract.test.ts +3 -2
  455. package/src/workspace/check.schema.json +70 -2
  456. package/src/workspace/checks/boxes.ts +9 -9
  457. package/src/workspace/checks/security.test.ts +138 -0
  458. package/src/workspace/checks/security.ts +305 -0
  459. package/src/workspace/checks.ts +5 -0
  460. package/src/workspace/ci-builds.e2e.test.ts +208 -0
  461. package/src/workspace/ci-builds.test.ts +174 -0
  462. package/src/workspace/ci-builds.ts +385 -0
  463. package/src/workspace/ci-green-builds.test.ts +180 -0
  464. package/src/workspace/ci-green-cli.test.ts +54 -0
  465. package/src/workspace/ci-green-cli.ts +26 -5
  466. package/src/workspace/ci-green-forge.ts +30 -4
  467. package/src/workspace/ci-green-runs.test.ts +53 -11
  468. package/src/workspace/ci-green-workflow.test.ts +84 -1
  469. package/src/workspace/ci-green-workflow.ts +139 -2
  470. package/src/workspace/ci-green.test.ts +6 -3
  471. package/src/workspace/ci-green.ts +408 -48
  472. package/src/workspace/ci-last-green.schema.json +49 -0
  473. package/src/workspace/ci-list.schema.json +47 -7
  474. package/src/workspace/ci-report-url.test.ts +220 -0
  475. package/src/workspace/ci-report-url.ts +33 -0
  476. package/src/workspace/ci-run-index.schema.json +6 -4
  477. package/src/workspace/ci-run-index.test.ts +42 -3
  478. package/src/workspace/ci-run-index.ts +19 -5
  479. package/src/workspace/ci-run.test.ts +203 -8
  480. package/src/workspace/ci-run.ts +295 -63
  481. package/src/workspace/ci-runner-key.test.ts +55 -17
  482. package/src/workspace/ci-runner-key.ts +36 -11
  483. package/src/workspace/ci-runner-start.test.ts +151 -0
  484. package/src/workspace/ci-runner-start.ts +193 -0
  485. package/src/workspace/ci-serve-builds.test.ts +86 -0
  486. package/src/workspace/ci-serve.test.ts +209 -6
  487. package/src/workspace/ci-serve.ts +341 -42
  488. package/src/workspace/declaration.schema.json +30 -2
  489. package/src/workspace/declaration.test.ts +40 -0
  490. package/src/workspace/declaration.ts +40 -3
  491. package/src/workspace/export.ts +6 -3
  492. package/src/workspace/ls.schema.json +9 -2
  493. package/src/workspace/ls.ts +10 -4
  494. package/src/workspace/member-pipeline.ts +2 -1
  495. package/src/workspace/merge-record.schema.json +49 -0
  496. package/src/workspace/pin-integrity.test.ts +5 -0
  497. package/src/workspace/pin-integrity.ts +3 -2
  498. package/src/workspace/pr-report.schema.json +75 -8
  499. package/src/workspace/reason-codes.test.ts +2 -0
  500. package/src/workspace/reason-codes.ts +20 -2
  501. package/src/workspace/release-candidates.schema.json +353 -0
  502. package/src/workspace/release-candidates.test.ts +106 -0
  503. package/src/workspace/release-candidates.ts +315 -0
  504. package/src/workspace/run-event.schema.json +3 -1
  505. package/src/workspace/security.test.ts +70 -0
  506. package/src/workspace/security.ts +118 -0
  507. package/src/workspace/status.schema.json +3 -1
  508. package/src/workspace/write-scope.test.ts +29 -1
  509. package/src/workspace/write-scope.ts +12 -1
@@ -0,0 +1,138 @@
1
+ /**
2
+ * The security group checklists, WSP161 to WSP165 (#4168 step 7).
3
+ */
4
+
5
+ import { execFileSync } from "node:child_process";
6
+ import { mkdirSync, mkdtempSync, realpathSync, rmSync, writeFileSync } from "node:fs";
7
+ import { tmpdir } from "node:os";
8
+ import { dirname, join } from "node:path";
9
+ import { afterAll, describe, expect, test } from "vitest";
10
+ import { runDeclarationChecks } from "../checks";
11
+
12
+ const scratch: string[] = [];
13
+ afterAll(() => {
14
+ for (const d of scratch) rmSync(d, { recursive: true, force: true });
15
+ });
16
+ function repo(files: Record<string, string>): string {
17
+ const root = realpathSync(mkdtempSync(join(tmpdir(), "chant-security-")));
18
+ scratch.push(root);
19
+ execFileSync("git", ["init", "-q"], { cwd: root });
20
+ for (const [path, text] of Object.entries(files)) {
21
+ mkdirSync(dirname(join(root, path)), { recursive: true });
22
+ writeFileSync(join(root, path), text);
23
+ }
24
+ return root;
25
+ }
26
+
27
+ const member = (extra: Record<string, unknown> = {}) => ({ name: "app", dir: "app", kind: "other", because: "the app", ...extra });
28
+ const declaration = (extra: Record<string, unknown> = {}, members = [member()]) => JSON.stringify({ name: "acme", schema: 1, members, ...extra }, null, 2);
29
+
30
+ /** The security findings as [id, code, entity]. */
31
+ const found = async (root: string) =>
32
+ (await runDeclarationChecks(root, (f) => f, { gather: false })).diagnostics.filter((d) => /^WSP16/.test(d.ruleId)).map((d) => [d.ruleId, d.code, d.entity ?? null]);
33
+
34
+ describe("security groups (WSP161 to WSP165)", () => {
35
+ test("a workspace that opts into no group gets no security finding", async () => {
36
+ const root = repo({ "chant.workspace.json": declaration(), "app/chant.config.ts": "export default { release: { publish: [{ npm: {} , sbom: false }] } };" });
37
+ expect(await found(root)).toEqual([]);
38
+ });
39
+
40
+ test("verified-releases with nothing set reports the signers file and the release gate", async () => {
41
+ const root = repo({ "chant.workspace.json": declaration({ security: ["verified-releases"] }), "app/x": "" });
42
+ expect(await found(root)).toEqual([
43
+ ["WSP161", "security-signers-missing", null],
44
+ ["WSP161", "security-release-gate-missing", null],
45
+ ]);
46
+ });
47
+
48
+ test("verified-releases passes with signers and a release gate, and flags --allow-unsigned in a publish job", async () => {
49
+ const root = repo({
50
+ "chant.workspace.json": declaration({ security: ["verified-releases"], identity: { gates: { release: { class: "person" } } } }),
51
+ ".chant/allowed_signers": "",
52
+ ".github/workflows/publish.yml": "jobs:\n publish:\n steps:\n - run: npx chant release publish v1 --allow-unsigned\n",
53
+ "app/x": "",
54
+ });
55
+ expect(await found(root)).toEqual([["WSP161", "security-publish-allows-unsigned", null]]);
56
+ });
57
+
58
+ test("a member's group applies to that member only", async () => {
59
+ const root = repo({
60
+ "chant.workspace.json": declaration({}, [member({ security: ["supply-chain"] }), member({ name: "web", dir: "web" })]),
61
+ "app/chant.config.ts": "export default { release: { publish: [{ forge: {}, sbom: false }] } };",
62
+ "web/chant.config.ts": "export default { release: { publish: [{ forge: {}, sbom: false }] } };",
63
+ });
64
+ expect(await found(root)).toEqual([["WSP162", "security-sbom-disabled", "app"]]);
65
+ });
66
+
67
+ test("supply-chain reports an unsourced SBOM, missing component steps and a publish without provenance", async () => {
68
+ const root = repo({
69
+ "chant.workspace.json": declaration({ security: ["supply-chain"] }),
70
+ "app/chant.config.ts": "export default { release: { publish: [{ forge: {} }] } };",
71
+ "app/src/api.component.ts": "export const api = component({ deploy: [shell({ cmd: 'x' })] });",
72
+ ".github/workflows/publish.yml": " - run: chant release publish v1\n",
73
+ });
74
+ expect((await found(root)).map((f) => f[1]).sort()).toEqual(["security-component-no-sbom", "security-component-no-verify", "security-publish-no-provenance", "security-sbom-unsourced"]);
75
+ });
76
+
77
+ test("a Cargo.lock above the member is an SBOM source", async () => {
78
+ const root = repo({
79
+ "chant.workspace.json": declaration({ security: ["supply-chain"] }),
80
+ "Cargo.lock": "version = 4\n",
81
+ "app/chant.config.ts": "export default { release: { publish: [{ forge: {} }] } };",
82
+ });
83
+ expect(await found(root)).toEqual([]);
84
+ });
85
+
86
+ test("who-ran-it reports signedSince, the runner key and an unsigned tick", async () => {
87
+ const root = repo({
88
+ "chant.workspace.json": declaration({ security: ["who-ran-it"], ci: { green: { branch: "main", phases: { test: ["test"] }, require: ["test"] } } }),
89
+ ".github/workflows/chant-ci-green.yml": " run: npx --yes @intentius/chant@1 ci tick\n",
90
+ "app/x": "",
91
+ });
92
+ expect((await found(root)).map((f) => f[1]).sort()).toEqual(["security-runner-untrusted", "security-signed-since-unset", "security-tick-no-key"]);
93
+ });
94
+
95
+ test("who-ran-it passes with signedSince, a trusted runner and a keyed tick", async () => {
96
+ const root = repo({
97
+ "chant.workspace.json": declaration({ security: ["who-ran-it"], ci: { green: { branch: "main", phases: { test: ["test"] }, require: ["test"], signedSince: "abc1234" } } }),
98
+ ".chant/trust.json": JSON.stringify({ schema: 1, runners: [{ principal: "chant-runner:box", key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOMqqnkVzrm0SdG6UOoqKLsabgH5C9okWi0dh2l9GKJl", class: "runner" }] }),
99
+ ".github/workflows/chant-ci-green.yml": " env:\n CHANT_RUNNER_KEY: ${{ secrets.K }}\n run: npx chant ci tick\n",
100
+ "app/x": "",
101
+ });
102
+ expect(await found(root)).toEqual([]);
103
+ });
104
+
105
+ test("review-rules reports --no-require-review, attribution and an unsigned gate", async () => {
106
+ const root = repo({
107
+ "chant.workspace.json": declaration({ security: ["review-rules"] }),
108
+ ".github/workflows/pr.yml": " - run: chant components pr-apply --base \"$BASE_SHA\" --env prod --gate deploy --forge github --no-require-review\n",
109
+ "app/x": "",
110
+ });
111
+ expect(await found(root)).toEqual([
112
+ ["WSP164", "security-review-not-required", null],
113
+ ["WSP164", "security-attribution-any", null],
114
+ ["WSP164", "security-gate-unsigned", null],
115
+ ]);
116
+ });
117
+
118
+ test("review-rules passes with identified attribution and the apply gate signed", async () => {
119
+ const root = repo({
120
+ "chant.workspace.json": declaration({ security: ["review-rules"], identity: { attribution: "identified", gates: { deploy: { class: "person" } } } }),
121
+ ".github/workflows/pr.yml": " - run: chant components pr-apply --base \"$BASE_SHA\" --env prod --gate deploy --forge github\n",
122
+ "app/x": "",
123
+ });
124
+ expect(await found(root)).toEqual([]);
125
+ });
126
+
127
+ test("isolated-runners reports a missing runner key, then a runner without the runner role, then passes", async () => {
128
+ const files = { "chant.workspace.json": declaration({ security: ["isolated-runners"] }), "app/x": "" };
129
+ expect(await found(repo(files))).toEqual([["WSP165", "security-runner-start-no-key", null]]);
130
+ const runner = { principal: "chant-runner:box", key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOMqqnkVzrm0SdG6UOoqKLsabgH5C9okWi0dh2l9GKJl", class: "runner" };
131
+ expect(await found(repo({ ...files, ".chant/trust.json": JSON.stringify({ schema: 1, runners: [runner] }) }))).toEqual([["WSP165", "security-runner-start-no-role", null]]);
132
+ expect(await found(repo({ ...files, ".chant/trust.json": JSON.stringify({ schema: 1, runners: [runner], roles: { runner: ["chant-runner:box"] } }) }))).toEqual([]);
133
+ });
134
+
135
+ test("isolated-runners reports a trust.json that does not read", async () => {
136
+ expect(await found(repo({ "chant.workspace.json": declaration({ security: ["isolated-runners"] }), ".chant/trust.json": "{", "app/x": "" }))).toEqual([["WSP165", "security-trust-unreadable", null]]);
137
+ });
138
+ });
@@ -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
+
27
+ import type { WorkspaceCheck, WorkspaceCheckContext, WorkspaceDiagnostic } from "../checks";
28
+ import type { Declaration, Member } from "../declaration";
29
+ import type { ReasonCode } from "../reason-codes";
30
+ import { securityGroupsOf, type SecurityGroup } from "../security";
31
+ import { joinPath, type WorkspaceTree } from "../tree";
32
+ import { runnerStartReadiness } from "../ci-runner-start";
33
+ import type { RecordSource } from "../record-source";
34
+ import { readTrustPolicy, type TrustPolicy } from "../trust/policy";
35
+ import { DEFAULT_SIGNERS_PATH, TRUST_CONFIG_PATH } from "../trust/trust-files";
36
+
37
+ /** The read contract's codes for the security findings, carried as `code` on each. */
38
+ export const SECURITY_FINDING_CODES = [
39
+ "security-signers-missing",
40
+ "security-release-gate-missing",
41
+ "security-publish-allows-unsigned",
42
+ "security-sbom-disabled",
43
+ "security-sbom-unsourced",
44
+ "security-publish-no-provenance",
45
+ "security-component-no-sbom",
46
+ "security-component-no-verify",
47
+ "security-signed-since-unset",
48
+ "security-runner-untrusted",
49
+ "security-tick-no-key",
50
+ "security-review-not-required",
51
+ "security-attribution-any",
52
+ "security-gate-unsigned",
53
+ "security-runner-start-no-key",
54
+ "security-runner-start-no-role",
55
+ "security-trust-unreadable",
56
+ ] as const satisfies readonly ReasonCode[];
57
+
58
+ type SecurityCode = (typeof SECURITY_FINDING_CODES)[number];
59
+
60
+ export const WSP_SECURITY_VERIFIED_RELEASES = "WSP161";
61
+ export const WSP_SECURITY_SUPPLY_CHAIN = "WSP162";
62
+ export const WSP_SECURITY_WHO_RAN_IT = "WSP163";
63
+ export const WSP_SECURITY_REVIEW_RULES = "WSP164";
64
+ export const WSP_SECURITY_ISOLATED_RUNNERS = "WSP165";
65
+
66
+ const memberDir = (dir: string) => (dir === "." ? "" : dir);
67
+
68
+ /** The members under `group`: every member when the top level names it, else those that name it themselves. */
69
+ function membersUnder(declaration: Declaration, group: SecurityGroup): Member[] {
70
+ return declaration.members.filter((m) => securityGroupsOf(declaration, m.name).has(group));
71
+ }
72
+
73
+ /** Whether the declaration names `group` anywhere. */
74
+ function declares(declaration: Declaration, group: SecurityGroup): boolean {
75
+ return declaration.security.includes(group) || declaration.members.some((m) => m.security.includes(group));
76
+ }
77
+
78
+ /** Where the group is named, for the finding's pointer: the top level, else the first member naming it. */
79
+ function groupPointer(declaration: Declaration, group: SecurityGroup): string {
80
+ const top = declaration.security.indexOf(group);
81
+ if (top >= 0) return `/security/${top}`;
82
+ const m = declaration.members.find((e) => e.security.includes(group));
83
+ return m ? `${m.pointer}/security/${m.security.indexOf(group)}` : "/security";
84
+ }
85
+
86
+ function read(tree: WorkspaceTree, path: string): string | null {
87
+ if (tree.stat(path) !== "file") return null;
88
+ try {
89
+ return tree.read(path);
90
+ } catch {
91
+ return null;
92
+ }
93
+ }
94
+
95
+ /** The forge pipeline files under `dir`, each as its path and lines. */
96
+ function pipelineFiles(tree: WorkspaceTree, dir: string): { path: string; lines: string[] }[] {
97
+ const out: { path: string; lines: string[] }[] = [];
98
+ for (const folder of [".github/workflows", ".forgejo/workflows", ".gitea/workflows"]) {
99
+ for (const e of tree.list(joinPath(dir, folder)) ?? []) {
100
+ if (e.type !== "file" || !/\.ya?ml$/.test(e.name)) continue;
101
+ const path = joinPath(dir, folder, e.name);
102
+ const text = read(tree, path);
103
+ if (text !== null) out.push({ path, lines: text.split("\n") });
104
+ }
105
+ }
106
+ const gitlab = read(tree, joinPath(dir, ".gitlab-ci.yml"));
107
+ if (gitlab !== null) out.push({ path: joinPath(dir, ".gitlab-ci.yml"), lines: gitlab.split("\n") });
108
+ return out;
109
+ }
110
+
111
+ /** Every pipeline file of the workspace: its root's, and each member's. */
112
+ function allPipelineFiles(ctx: WorkspaceCheckContext): { path: string; lines: string[] }[] {
113
+ const dirs = new Set(["", ...ctx.declaration.members.map((m) => memberDir(m.dir))]);
114
+ return [...dirs].flatMap((d) => pipelineFiles(ctx.tree, d));
115
+ }
116
+
117
+ /** The lines of the pipeline files that run a chant command, `command` naming it, such as `release publish`. */
118
+ function commandLines(ctx: WorkspaceCheckContext, command: RegExp): { path: string; line: number; text: string }[] {
119
+ return allPipelineFiles(ctx).flatMap((f) => f.lines.flatMap((text, i) => (command.test(text) ? [{ path: f.path, line: i + 1, text }] : [])));
120
+ }
121
+
122
+ /** The member's `*.component.ts` files, walked a few levels down, past node_modules and build output. */
123
+ function componentFiles(tree: WorkspaceTree, dir: string, depth = 4): string[] {
124
+ const out: string[] = [];
125
+ for (const e of tree.list(dir) ?? []) {
126
+ if (e.type === "dir") {
127
+ if (depth > 0 && !["node_modules", "dist", ".git", ".chant", "target"].includes(e.name)) out.push(...componentFiles(tree, joinPath(dir, e.name), depth - 1));
128
+ } else if (e.name.endsWith(".component.ts")) out.push(joinPath(dir, e.name));
129
+ }
130
+ return out;
131
+ }
132
+
133
+ /** Whether a lockfile chant writes an SBOM from is in `dir` or a directory above it in the tree. */
134
+ function lockfileAbove(tree: WorkspaceTree, dir: string): boolean {
135
+ let at = dir;
136
+ for (;;) {
137
+ if (["package-lock.json", "pom.xml", "Cargo.lock"].some((f) => tree.stat(joinPath(at, f)) === "file")) return true;
138
+ if (at === "") return false;
139
+ at = at.includes("/") ? at.slice(0, at.lastIndexOf("/")) : "";
140
+ }
141
+ }
142
+
143
+ /** The trust policy in the tree read: `.chant/trust.json` and the signers file it names. */
144
+ function treePolicy(tree: WorkspaceTree): TrustPolicy {
145
+ const source: RecordSource = {
146
+ label: tree.label,
147
+ list: (dir) => tree.list(dir)?.filter((e) => e.type === "file").map((e) => e.name),
148
+ read: (path) => tree.read(path),
149
+ bytes: (path) => (tree.bytes ? tree.bytes(path) : new Uint8Array(Buffer.from(tree.read(path), "utf-8"))),
150
+ };
151
+ return readTrustPolicy(source, null);
152
+ }
153
+
154
+ function finding(check: WorkspaceCheck, code: SecurityCode, message: string, at: { pointer: string; entity?: string; treeFile?: string; line?: number }): WorkspaceDiagnostic {
155
+ return {
156
+ checkId: check.id,
157
+ severity: check.severity,
158
+ code,
159
+ message: `${code}: ${message}`,
160
+ pointer: at.pointer,
161
+ ...(at.entity !== undefined ? { entity: at.entity } : {}),
162
+ ...(at.treeFile !== undefined ? { treeFile: at.treeFile, line: at.line ?? 1, column: 1 } : {}),
163
+ };
164
+ }
165
+
166
+ const RELEASE_PUBLISH = /\bchant\b.*\brelease\s+publish\b/;
167
+ const CI_TICK = /\bchant\b.*\bci\s+tick\b|main\.ts\s+ci\s+tick\b/;
168
+ const PR_APPLY = /\bcomponents\s+pr-apply\b|\bci\s+run\s+apply\b/;
169
+
170
+ export const SECURITY_CHECKS: readonly WorkspaceCheck[] = [
171
+ {
172
+ id: WSP_SECURITY_VERIFIED_RELEASES,
173
+ name: "security-verified-releases",
174
+ description: "Under the verified-releases group: a signers file, a release gate naming a class, and no publish job that passes --allow-unsigned.",
175
+ severity: "warning",
176
+ configurable: true,
177
+ check(ctx) {
178
+ const d = ctx.declaration;
179
+ if (!declares(d, "verified-releases")) return [];
180
+ const pointer = groupPointer(d, "verified-releases");
181
+ const out: WorkspaceDiagnostic[] = [];
182
+ if (ctx.tree.stat(DEFAULT_SIGNERS_PATH) !== "file") {
183
+ 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 }));
184
+ }
185
+ const gate = d.identity?.gates.release;
186
+ if (!gate?.class) {
187
+ 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 }));
188
+ }
189
+ for (const l of commandLines(ctx, RELEASE_PUBLISH)) {
190
+ if (!/--allow-unsigned\b/.test(l.text)) continue;
191
+ 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 }));
192
+ }
193
+ return out;
194
+ },
195
+ },
196
+ {
197
+ id: WSP_SECURITY_SUPPLY_CHAIN,
198
+ name: "security-supply-chain",
199
+ 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.",
200
+ severity: "warning",
201
+ configurable: true,
202
+ check(ctx) {
203
+ const d = ctx.declaration;
204
+ if (!declares(d, "supply-chain")) return [];
205
+ const pointer = groupPointer(d, "supply-chain");
206
+ const out: WorkspaceDiagnostic[] = [];
207
+ for (const m of membersUnder(d, "supply-chain")) {
208
+ const configPath = joinPath(memberDir(m.dir), "chant.config.ts");
209
+ const config = read(ctx.tree, configPath);
210
+ if (config !== null && /\bpublish\s*:/.test(config)) {
211
+ const lines = config.split("\n");
212
+ const off = lines.findIndex((l) => /\bsbom\s*:\s*false\b/.test(l));
213
+ if (off >= 0) 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 }));
214
+ else if (!/\bsbom\s*:\s*["'`]/.test(config) && !lockfileAbove(ctx.tree, memberDir(m.dir))) {
215
+ 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 }));
216
+ }
217
+ }
218
+ const components = componentFiles(ctx.tree, memberDir(m.dir));
219
+ if (components.length === 0) continue;
220
+ const text = components.map((f) => read(ctx.tree, f) ?? "").join("\n");
221
+ if (!/\bgenerateSbom\s*\(|["']generate-sbom["']/.test(text)) {
222
+ 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] }));
223
+ }
224
+ if (!/\bverify\s*\(|["']verify["']/.test(text)) {
225
+ 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] }));
226
+ }
227
+ }
228
+ for (const l of commandLines(ctx, RELEASE_PUBLISH)) {
229
+ if (/--require-provenance\b/.test(l.text)) continue;
230
+ 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 }));
231
+ }
232
+ return out;
233
+ },
234
+ },
235
+ {
236
+ id: WSP_SECURITY_WHO_RAN_IT,
237
+ name: "security-who-ran-it",
238
+ 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.",
239
+ severity: "warning",
240
+ configurable: true,
241
+ check(ctx) {
242
+ const d = ctx.declaration;
243
+ if (!declares(d, "who-ran-it")) return [];
244
+ const pointer = groupPointer(d, "who-ran-it");
245
+ const out: WorkspaceDiagnostic[] = [];
246
+ if (!d.ci?.green?.signedSince) {
247
+ 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 }));
248
+ }
249
+ const runners = treePolicy(ctx.tree).runners.filter((r) => r.class === "runner").length;
250
+ if (runners === 0) {
251
+ 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 }));
252
+ }
253
+ for (const l of commandLines(ctx, CI_TICK)) {
254
+ const file = allPipelineFiles(ctx).find((f) => f.path === l.path);
255
+ if (/--key\b/.test(l.text) || file?.lines.some((t) => /CHANT_RUNNER_KEY/.test(t))) continue;
256
+ 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 }));
257
+ }
258
+ return out;
259
+ },
260
+ },
261
+ {
262
+ id: WSP_SECURITY_REVIEW_RULES,
263
+ name: "security-review-rules",
264
+ 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.",
265
+ severity: "warning",
266
+ configurable: true,
267
+ check(ctx) {
268
+ const d = ctx.declaration;
269
+ if (!declares(d, "review-rules")) return [];
270
+ const pointer = groupPointer(d, "review-rules");
271
+ const out: WorkspaceDiagnostic[] = [];
272
+ const applies = commandLines(ctx, PR_APPLY);
273
+ for (const l of applies) {
274
+ if (!/--no-require-review\b/.test(l.text)) continue;
275
+ 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 }));
276
+ }
277
+ if (d.identity?.attribution !== "identified") {
278
+ 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 }));
279
+ }
280
+ const signed = new Set(Object.keys(d.identity?.gates ?? {}));
281
+ const named = new Set(applies.flatMap((l) => [...l.text.matchAll(/--(?:wave-)?gate\s+["']?([A-Za-z0-9_.-]+)/g)].map((m) => m[1]!)));
282
+ const unsigned = [...named].filter((g) => !signed.has(g));
283
+ if (unsigned.length > 0) {
284
+ 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 }));
285
+ } else if (named.size === 0 && signed.size === 0) {
286
+ out.push(finding(this, "security-gate-unsigned", "identity.gates lists no gate, so every approval may be unsigned", { pointer }));
287
+ }
288
+ return out;
289
+ },
290
+ },
291
+ {
292
+ id: WSP_SECURITY_ISOLATED_RUNNERS,
293
+ name: "security-isolated-runners",
294
+ 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).",
295
+ severity: "warning",
296
+ configurable: true,
297
+ check(ctx) {
298
+ const d = ctx.declaration;
299
+ if (!declares(d, "isolated-runners")) 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" } as const;
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
+ ];
@@ -37,6 +37,8 @@
37
37
  * | WSP126, WSP127 | box intent: the decision record a box names exists, and constrains a member or path of this workspace (#2850, #2857) |
38
38
  * | WSP141, WSP142 | member links resolved against a live read, with `--live` (#2549) |
39
39
  * | WSP131 to WSP133 | diagram artifacts: source and render exist, a recorded source hash still matches (#2764) |
40
+ * | WSP151 | reserved for the `ci.workflow` preset (#4168 step 6) |
41
+ * | WSP161 to WSP165 | the security groups the declaration opts into: what each still lacks (#4168 step 7) |
40
42
  */
41
43
 
42
44
  import { realpathSync } from "node:fs";
@@ -58,6 +60,7 @@ import { gatherLiveLinkFacts, LIVE_CHECKS, type LiveLinkFacts } from "./checks/l
58
60
  import { RECORD_CHECKS, type RecordFacts } from "./checks/records";
59
61
  import { BOX_CHECKS } from "./checks/boxes";
60
62
  import { DIAGRAM_CHECKS } from "./checks/diagrams";
63
+ import { SECURITY_CHECKS } from "./checks/security";
61
64
  import { loadDeclaredKinds, type DeclaredKind } from "./declared-kinds";
62
65
  import { resolveBoxIntents, type ResolvedBoxIntent } from "./box-intent";
63
66
  import { gatherLexiconOutputs, type MemberLexiconOutputs } from "./lexicon-outputs";
@@ -371,6 +374,8 @@ export const WORKSPACE_CHECKS: readonly WorkspaceCheck[] = [
371
374
  ...DIAGRAM_CHECKS,
372
375
  // Links resolved against a live read, with --live (#2549), WSP141 and WSP142.
373
376
  ...LIVE_CHECKS,
377
+ // The security groups the declaration opts into (#4168 step 7), WSP161 to WSP165.
378
+ ...SECURITY_CHECKS,
374
379
  ];
375
380
 
376
381
  const BY_ID = new Map(WORKSPACE_CHECKS.map((c) => [c.id, c]));