@intentius/chant 0.13.1 → 0.15.0

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 (450) hide show
  1. package/dist/audit/catalog.d.ts +45 -5
  2. package/dist/audit/catalog.d.ts.map +1 -1
  3. package/dist/audit/proof.d.ts +3 -0
  4. package/dist/audit/proof.d.ts.map +1 -1
  5. package/dist/audit/report-model.d.ts +4 -1
  6. package/dist/audit/report-model.d.ts.map +1 -1
  7. package/dist/audit/report.d.ts +3 -0
  8. package/dist/audit/report.d.ts.map +1 -1
  9. package/dist/audit/rules-doc.d.ts +8 -2
  10. package/dist/audit/rules-doc.d.ts.map +1 -1
  11. package/dist/cli/commands/audit.d.ts.map +1 -1
  12. package/dist/cli/commands/doctor.d.ts.map +1 -1
  13. package/dist/cli/commands/lexicon-rolling-upgrade.d.ts +37 -0
  14. package/dist/cli/commands/lexicon-rolling-upgrade.d.ts.map +1 -0
  15. package/dist/cli/commands/lexicon-surface-diff.d.ts +48 -0
  16. package/dist/cli/commands/lexicon-surface-diff.d.ts.map +1 -0
  17. package/dist/cli/commands/lint.d.ts.map +1 -1
  18. package/dist/cli/commands/pinned-upgrade.d.ts +41 -0
  19. package/dist/cli/commands/pinned-upgrade.d.ts.map +1 -0
  20. package/dist/cli/handlers/build.d.ts.map +1 -1
  21. package/dist/cli/handlers/components.d.ts +39 -0
  22. package/dist/cli/handlers/components.d.ts.map +1 -0
  23. package/dist/cli/handlers/dev.d.ts +3 -0
  24. package/dist/cli/handlers/dev.d.ts.map +1 -1
  25. package/dist/cli/handlers/graph.d.ts +4 -2
  26. package/dist/cli/handlers/graph.d.ts.map +1 -1
  27. package/dist/cli/handlers/misc.d.ts.map +1 -1
  28. package/dist/cli/handlers/run.d.ts +49 -0
  29. package/dist/cli/handlers/run.d.ts.map +1 -1
  30. package/dist/cli/main.d.ts.map +1 -1
  31. package/dist/cli/registry.d.ts +35 -0
  32. package/dist/cli/registry.d.ts.map +1 -1
  33. package/dist/codegen/fetch.d.ts +22 -0
  34. package/dist/codegen/fetch.d.ts.map +1 -1
  35. package/dist/codegen/lexicon-regen.d.ts +110 -0
  36. package/dist/codegen/lexicon-regen.d.ts.map +1 -0
  37. package/dist/codegen/pinned-upgrade.d.ts +153 -0
  38. package/dist/codegen/pinned-upgrade.d.ts.map +1 -0
  39. package/dist/codegen/rolling-upgrade.d.ts +138 -0
  40. package/dist/codegen/rolling-upgrade.d.ts.map +1 -0
  41. package/dist/codegen/surface-snapshot.d.ts +173 -0
  42. package/dist/codegen/surface-snapshot.d.ts.map +1 -0
  43. package/dist/codegen/version-bump.d.ts +21 -0
  44. package/dist/codegen/version-bump.d.ts.map +1 -0
  45. package/dist/components/auto-release.d.ts +93 -0
  46. package/dist/components/auto-release.d.ts.map +1 -0
  47. package/dist/components/builders.d.ts +37 -0
  48. package/dist/components/builders.d.ts.map +1 -0
  49. package/dist/components/capability-plugin-loader.d.ts +111 -0
  50. package/dist/components/capability-plugin-loader.d.ts.map +1 -0
  51. package/dist/components/capability-plugin-schema.d.ts +24 -0
  52. package/dist/components/capability-plugin-schema.d.ts.map +1 -0
  53. package/dist/components/capability-plugin.d.ts +85 -0
  54. package/dist/components/capability-plugin.d.ts.map +1 -0
  55. package/dist/components/capability.d.ts +89 -0
  56. package/dist/components/capability.d.ts.map +1 -0
  57. package/dist/components/cli-support.d.ts +204 -0
  58. package/dist/components/cli-support.d.ts.map +1 -0
  59. package/dist/components/component.d.ts +179 -0
  60. package/dist/components/component.d.ts.map +1 -0
  61. package/dist/components/config-defaults.d.ts +41 -0
  62. package/dist/components/config-defaults.d.ts.map +1 -0
  63. package/dist/components/discover.d.ts +60 -0
  64. package/dist/components/discover.d.ts.map +1 -0
  65. package/dist/components/driver-output.d.ts +20 -0
  66. package/dist/components/driver-output.d.ts.map +1 -0
  67. package/dist/components/driver.d.ts +209 -0
  68. package/dist/components/driver.d.ts.map +1 -0
  69. package/dist/components/index.d.ts +22 -0
  70. package/dist/components/index.d.ts.map +1 -0
  71. package/dist/components/manifest-persistence.d.ts +79 -0
  72. package/dist/components/manifest-persistence.d.ts.map +1 -0
  73. package/dist/components/pilots/alb-ecs.pilot.d.ts +27 -0
  74. package/dist/components/pilots/alb-ecs.pilot.d.ts.map +1 -0
  75. package/dist/components/pilots/dynamodb.pilot.d.ts +22 -0
  76. package/dist/components/pilots/dynamodb.pilot.d.ts.map +1 -0
  77. package/dist/components/pilots/jar-emr.pilot.d.ts +37 -0
  78. package/dist/components/pilots/jar-emr.pilot.d.ts.map +1 -0
  79. package/dist/components/pilots/lambda.pilot.d.ts +35 -0
  80. package/dist/components/pilots/lambda.pilot.d.ts.map +1 -0
  81. package/dist/components/pilots/neo4j-fanout.pilot.d.ts +17 -0
  82. package/dist/components/pilots/neo4j-fanout.pilot.d.ts.map +1 -0
  83. package/dist/components/pilots/project.d.ts +13 -0
  84. package/dist/components/pilots/project.d.ts.map +1 -0
  85. package/dist/components/presets/ecs-fargate.d.ts +59 -0
  86. package/dist/components/presets/ecs-fargate.d.ts.map +1 -0
  87. package/dist/components/presets/index.d.ts +29 -0
  88. package/dist/components/presets/index.d.ts.map +1 -0
  89. package/dist/components/presets/lambda.d.ts +35 -0
  90. package/dist/components/presets/lambda.d.ts.map +1 -0
  91. package/dist/components/presets/single-host-compose.d.ts +43 -0
  92. package/dist/components/presets/single-host-compose.d.ts.map +1 -0
  93. package/dist/components/registry.d.ts +29 -0
  94. package/dist/components/registry.d.ts.map +1 -0
  95. package/dist/components/starter-plugin.d.ts +51 -0
  96. package/dist/components/starter-plugin.d.ts.map +1 -0
  97. package/dist/components/temporal-codegen-loader.d.ts +27 -0
  98. package/dist/components/temporal-codegen-loader.d.ts.map +1 -0
  99. package/dist/components/verbs/__tests__/mock-cloud-executor.d.ts +35 -0
  100. package/dist/components/verbs/__tests__/mock-cloud-executor.d.ts.map +1 -0
  101. package/dist/components/verbs/__tests__/mock-process-runner.d.ts +45 -0
  102. package/dist/components/verbs/__tests__/mock-process-runner.d.ts.map +1 -0
  103. package/dist/components/verbs/__tests__/mock-sbom-generator.d.ts +33 -0
  104. package/dist/components/verbs/__tests__/mock-sbom-generator.d.ts.map +1 -0
  105. package/dist/components/verbs/bom-writer.d.ts +155 -0
  106. package/dist/components/verbs/bom-writer.d.ts.map +1 -0
  107. package/dist/components/verbs/build-archive.d.ts +205 -0
  108. package/dist/components/verbs/build-archive.d.ts.map +1 -0
  109. package/dist/components/verbs/build.d.ts +150 -0
  110. package/dist/components/verbs/build.d.ts.map +1 -0
  111. package/dist/components/verbs/cloud-executor.d.ts +88 -0
  112. package/dist/components/verbs/cloud-executor.d.ts.map +1 -0
  113. package/dist/components/verbs/component-bom.d.ts +103 -0
  114. package/dist/components/verbs/component-bom.d.ts.map +1 -0
  115. package/dist/components/verbs/index.d.ts +49 -0
  116. package/dist/components/verbs/index.d.ts.map +1 -0
  117. package/dist/components/verbs/license-policy.d.ts +56 -0
  118. package/dist/components/verbs/license-policy.d.ts.map +1 -0
  119. package/dist/components/verbs/lockfile-sbom-generator.d.ts +81 -0
  120. package/dist/components/verbs/lockfile-sbom-generator.d.ts.map +1 -0
  121. package/dist/components/verbs/process-runner.d.ts +64 -0
  122. package/dist/components/verbs/process-runner.d.ts.map +1 -0
  123. package/dist/components/verbs/reproducibility.d.ts +80 -0
  124. package/dist/components/verbs/reproducibility.d.ts.map +1 -0
  125. package/dist/components/verbs/sbom-generator.d.ts +157 -0
  126. package/dist/components/verbs/sbom-generator.d.ts.map +1 -0
  127. package/dist/components/verbs/sbom.d.ts +79 -0
  128. package/dist/components/verbs/sbom.d.ts.map +1 -0
  129. package/dist/components/verbs/shell.d.ts +35 -0
  130. package/dist/components/verbs/shell.d.ts.map +1 -0
  131. package/dist/components/verbs/sign.d.ts +222 -0
  132. package/dist/components/verbs/sign.d.ts.map +1 -0
  133. package/dist/components/verbs/stub.d.ts +18 -0
  134. package/dist/components/verbs/stub.d.ts.map +1 -0
  135. package/dist/components/verbs/tool-sbom-generator.d.ts +51 -0
  136. package/dist/components/verbs/tool-sbom-generator.d.ts.map +1 -0
  137. package/dist/components/verbs/verify.d.ts +106 -0
  138. package/dist/components/verbs/verify.d.ts.map +1 -0
  139. package/dist/components/verbs/vex.d.ts +65 -0
  140. package/dist/components/verbs/vex.d.ts.map +1 -0
  141. package/dist/components/verbs/vuln-gate.d.ts +91 -0
  142. package/dist/components/verbs/vuln-gate.d.ts.map +1 -0
  143. package/dist/components/verbs/vuln-scan.d.ts +116 -0
  144. package/dist/components/verbs/vuln-scan.d.ts.map +1 -0
  145. package/dist/components/verbs/wait-verify.d.ts +85 -0
  146. package/dist/components/verbs/wait-verify.d.ts.map +1 -0
  147. package/dist/config.d.ts +205 -0
  148. package/dist/config.d.ts.map +1 -1
  149. package/dist/index.d.ts +4 -0
  150. package/dist/index.d.ts.map +1 -1
  151. package/dist/lexicon.d.ts +78 -0
  152. package/dist/lexicon.d.ts.map +1 -1
  153. package/dist/lifecycle/build-ledger-store.d.ts +121 -0
  154. package/dist/lifecycle/build-ledger-store.d.ts.map +1 -0
  155. package/dist/lifecycle/build-ledger.d.ts +225 -0
  156. package/dist/lifecycle/build-ledger.d.ts.map +1 -0
  157. package/dist/lifecycle/git.d.ts +84 -0
  158. package/dist/lifecycle/git.d.ts.map +1 -1
  159. package/dist/lifecycle/index.d.ts +5 -0
  160. package/dist/lifecycle/index.d.ts.map +1 -1
  161. package/dist/lifecycle/oras-referrer-lookup.d.ts +41 -0
  162. package/dist/lifecycle/oras-referrer-lookup.d.ts.map +1 -0
  163. package/dist/lifecycle/release-ledger.d.ts +122 -0
  164. package/dist/lifecycle/release-ledger.d.ts.map +1 -0
  165. package/dist/lifecycle/status.d.ts +150 -0
  166. package/dist/lifecycle/status.d.ts.map +1 -0
  167. package/dist/lint/component-checks.d.ts +95 -0
  168. package/dist/lint/component-checks.d.ts.map +1 -0
  169. package/dist/lint/engine.d.ts +23 -0
  170. package/dist/lint/engine.d.ts.map +1 -1
  171. package/dist/lint/presets/strict.json +8 -1
  172. package/dist/lint/rules/__fixtures__/comp/comp000-discovery-error/broken-a.component.d.ts +4 -0
  173. package/dist/lint/rules/__fixtures__/comp/comp000-discovery-error/broken-a.component.d.ts.map +1 -0
  174. package/dist/lint/rules/__fixtures__/comp/comp000-discovery-error/broken-b.component.d.ts +4 -0
  175. package/dist/lint/rules/__fixtures__/comp/comp000-discovery-error/broken-b.component.d.ts.map +1 -0
  176. package/dist/lint/rules/__fixtures__/comp/comp001/fail/orphan-lib.component.d.ts +4 -0
  177. package/dist/lint/rules/__fixtures__/comp/comp001/fail/orphan-lib.component.d.ts.map +1 -0
  178. package/dist/lint/rules/__fixtures__/comp/comp001/pass/emr-job.component.d.ts +4 -0
  179. package/dist/lint/rules/__fixtures__/comp/comp001/pass/emr-job.component.d.ts.map +1 -0
  180. package/dist/lint/rules/__fixtures__/comp/comp001/pass/jar-lib.component.d.ts +4 -0
  181. package/dist/lint/rules/__fixtures__/comp/comp001/pass/jar-lib.component.d.ts.map +1 -0
  182. package/dist/lint/rules/__fixtures__/comp/comp002/fail/search-service.component.d.ts +10 -0
  183. package/dist/lint/rules/__fixtures__/comp/comp002/fail/search-service.component.d.ts.map +1 -0
  184. package/dist/lint/rules/__fixtures__/comp/comp002/pass/jar-lib.component.d.ts +3 -0
  185. package/dist/lint/rules/__fixtures__/comp/comp002/pass/jar-lib.component.d.ts.map +1 -0
  186. package/dist/lint/rules/__fixtures__/comp/comp002/pass/search-service.component.d.ts +4 -0
  187. package/dist/lint/rules/__fixtures__/comp/comp002/pass/search-service.component.d.ts.map +1 -0
  188. package/dist/lint/rules/__fixtures__/comp/comp003/fail/migrator.component.d.ts +4 -0
  189. package/dist/lint/rules/__fixtures__/comp/comp003/fail/migrator.component.d.ts.map +1 -0
  190. package/dist/lint/rules/__fixtures__/comp/comp003/pass/migrator.component.d.ts +4 -0
  191. package/dist/lint/rules/__fixtures__/comp/comp003/pass/migrator.component.d.ts.map +1 -0
  192. package/dist/lint/rules/__fixtures__/comp/comp003/pass/orders-table.component.d.ts +4 -0
  193. package/dist/lint/rules/__fixtures__/comp/comp003/pass/orders-table.component.d.ts.map +1 -0
  194. package/dist/lint/rules/__fixtures__/comp/comp004/fail/neo4j-cluster.component.d.ts +4 -0
  195. package/dist/lint/rules/__fixtures__/comp/comp004/fail/neo4j-cluster.component.d.ts.map +1 -0
  196. package/dist/lint/rules/__fixtures__/comp/comp004/pass/neo4j-cluster.component.d.ts +4 -0
  197. package/dist/lint/rules/__fixtures__/comp/comp004/pass/neo4j-cluster.component.d.ts.map +1 -0
  198. package/dist/lint/rules/__fixtures__/comp/comp004/pass/orders-table.component.d.ts +4 -0
  199. package/dist/lint/rules/__fixtures__/comp/comp004/pass/orders-table.component.d.ts.map +1 -0
  200. package/dist/lint/rules/__fixtures__/comp/comp005/fail/search-service.component.d.ts +4 -0
  201. package/dist/lint/rules/__fixtures__/comp/comp005/fail/search-service.component.d.ts.map +1 -0
  202. package/dist/lint/rules/__fixtures__/comp/comp005/pass/search-service.component.d.ts +4 -0
  203. package/dist/lint/rules/__fixtures__/comp/comp005/pass/search-service.component.d.ts.map +1 -0
  204. package/dist/lint/rules/__fixtures__/comp/comp006/fail/legacy-tool.component.d.ts +4 -0
  205. package/dist/lint/rules/__fixtures__/comp/comp006/fail/legacy-tool.component.d.ts.map +1 -0
  206. package/dist/lint/rules/__fixtures__/comp/comp006/pass/legacy-tool.component.d.ts +4 -0
  207. package/dist/lint/rules/__fixtures__/comp/comp006/pass/legacy-tool.component.d.ts.map +1 -0
  208. package/dist/lint/rules/__fixtures__/comp/comp007/fail/inventory-table.component.d.ts +4 -0
  209. package/dist/lint/rules/__fixtures__/comp/comp007/fail/inventory-table.component.d.ts.map +1 -0
  210. package/dist/lint/rules/__fixtures__/comp/comp007/fail/orders-table.component.d.ts +4 -0
  211. package/dist/lint/rules/__fixtures__/comp/comp007/fail/orders-table.component.d.ts.map +1 -0
  212. package/dist/lint/rules/__fixtures__/comp/comp007/pass/orders-table.component.d.ts +3 -0
  213. package/dist/lint/rules/__fixtures__/comp/comp007/pass/orders-table.component.d.ts.map +1 -0
  214. package/dist/lint/rules/__fixtures__/comp/comp007/pass/search-service.component.d.ts +4 -0
  215. package/dist/lint/rules/__fixtures__/comp/comp007/pass/search-service.component.d.ts.map +1 -0
  216. package/dist/lint/rules/comp/comp001-publishes-never-applies.d.ts +20 -0
  217. package/dist/lint/rules/comp/comp001-publishes-never-applies.d.ts.map +1 -0
  218. package/dist/lint/rules/comp/comp002-dangling-wiring-ref.d.ts +28 -0
  219. package/dist/lint/rules/comp/comp002-dangling-wiring-ref.d.ts.map +1 -0
  220. package/dist/lint/rules/comp/comp003-mutating-no-rollback.d.ts +39 -0
  221. package/dist/lint/rules/comp/comp003-mutating-no-rollback.d.ts.map +1 -0
  222. package/dist/lint/rules/comp/comp004-gate-needs-temporal.d.ts +37 -0
  223. package/dist/lint/rules/comp/comp004-gate-needs-temporal.d.ts.map +1 -0
  224. package/dist/lint/rules/comp/comp005-capability-kind-is-noun.d.ts +43 -0
  225. package/dist/lint/rules/comp/comp005-capability-kind-is-noun.d.ts.map +1 -0
  226. package/dist/lint/rules/comp/comp006-shell-needs-reason.d.ts +22 -0
  227. package/dist/lint/rules/comp/comp006-shell-needs-reason.d.ts.map +1 -0
  228. package/dist/lint/rules/comp/comp007-composition-sprawl.d.ts +33 -0
  229. package/dist/lint/rules/comp/comp007-composition-sprawl.d.ts.map +1 -0
  230. package/dist/lint/rules/comp/index.d.ts +18 -0
  231. package/dist/lint/rules/comp/index.d.ts.map +1 -0
  232. package/dist/lint/rules/comp/support.d.ts +71 -0
  233. package/dist/lint/rules/comp/support.d.ts.map +1 -0
  234. package/dist/lsp/lexicon-providers.d.ts.map +1 -1
  235. package/dist/op/activity-registry.d.ts +11 -6
  236. package/dist/op/activity-registry.d.ts.map +1 -1
  237. package/dist/op/activity-runtime.d.ts +24 -0
  238. package/dist/op/activity-runtime.d.ts.map +1 -0
  239. package/dist/op/builders.d.ts +56 -0
  240. package/dist/op/builders.d.ts.map +1 -1
  241. package/dist/op/index.d.ts +2 -1
  242. package/dist/op/index.d.ts.map +1 -1
  243. package/dist/ownership.d.ts +20 -14
  244. package/dist/ownership.d.ts.map +1 -1
  245. package/package.json +14 -1
  246. package/src/audit/catalog.test.ts +74 -22
  247. package/src/audit/catalog.ts +74 -471
  248. package/src/audit/proof.ts +4 -2
  249. package/src/audit/report-html.test.ts +14 -7
  250. package/src/audit/report-model.ts +20 -9
  251. package/src/audit/report.test.ts +36 -13
  252. package/src/audit/report.ts +2 -0
  253. package/src/audit/rules-doc.test.ts +8 -6
  254. package/src/audit/rules-doc.ts +13 -5
  255. package/src/cli/commands/audit.ts +23 -18
  256. package/src/cli/commands/component-lint.test.ts +158 -0
  257. package/src/cli/commands/doctor.test.ts +116 -2
  258. package/src/cli/commands/doctor.ts +46 -0
  259. package/src/cli/commands/lexicon-rolling-upgrade.ts +198 -0
  260. package/src/cli/commands/lexicon-surface-diff.test.ts +104 -0
  261. package/src/cli/commands/lexicon-surface-diff.ts +185 -0
  262. package/src/cli/commands/lint.ts +142 -1
  263. package/src/cli/commands/pinned-upgrade.ts +204 -0
  264. package/src/cli/handlers/build.ts +58 -1
  265. package/src/cli/handlers/components.test.ts +504 -0
  266. package/src/cli/handlers/components.ts +404 -0
  267. package/src/cli/handlers/dev.ts +60 -1
  268. package/src/cli/handlers/graph.ts +47 -2
  269. package/src/cli/handlers/misc.ts +45 -1
  270. package/src/cli/handlers/run.test.ts +967 -3
  271. package/src/cli/handlers/run.ts +618 -11
  272. package/src/cli/main.test.ts +66 -0
  273. package/src/cli/main.ts +105 -2
  274. package/src/cli/registry.ts +35 -0
  275. package/src/codegen/fetch.test.ts +84 -3
  276. package/src/codegen/fetch.ts +76 -2
  277. package/src/codegen/lexicon-regen.test.ts +298 -0
  278. package/src/codegen/lexicon-regen.ts +545 -0
  279. package/src/codegen/pinned-upgrade.test.ts +399 -0
  280. package/src/codegen/pinned-upgrade.ts +423 -0
  281. package/src/codegen/rolling-upgrade.test.ts +333 -0
  282. package/src/codegen/rolling-upgrade.ts +320 -0
  283. package/src/codegen/surface-snapshot.test.ts +461 -0
  284. package/src/codegen/surface-snapshot.ts +546 -0
  285. package/src/codegen/version-bump.test.ts +47 -0
  286. package/src/codegen/version-bump.ts +39 -0
  287. package/src/components/README.md +130 -0
  288. package/src/components/SPRAWL-VALIDATION.md +181 -0
  289. package/src/components/__fixtures__/alb-ecs-service.json +51 -0
  290. package/src/components/__fixtures__/dynamodb-infra.json +32 -0
  291. package/src/components/__fixtures__/emr-job-consumer.json +26 -0
  292. package/src/components/__fixtures__/jar-lib-producer.json +16 -0
  293. package/src/components/__fixtures__/lambda-image-processor.json +28 -0
  294. package/src/components/__fixtures__/neo4j-fanout.json +34 -0
  295. package/src/components/__fixtures__/single-host-compose.json +29 -0
  296. package/src/components/auto-release.test.ts +217 -0
  297. package/src/components/auto-release.ts +216 -0
  298. package/src/components/builders.test.ts +45 -0
  299. package/src/components/builders.ts +71 -0
  300. package/src/components/capability-plugin-loader.ts +218 -0
  301. package/src/components/capability-plugin-schema.ts +56 -0
  302. package/src/components/capability-plugin.test.ts +235 -0
  303. package/src/components/capability-plugin.ts +108 -0
  304. package/src/components/capability.ts +117 -0
  305. package/src/components/cli-support.test.ts +579 -0
  306. package/src/components/cli-support.ts +481 -0
  307. package/src/components/component-schema.test.ts +265 -0
  308. package/src/components/component.schema.json +235 -0
  309. package/src/components/component.test.ts +251 -0
  310. package/src/components/component.ts +255 -0
  311. package/src/components/config-defaults.test.ts +263 -0
  312. package/src/components/config-defaults.ts +145 -0
  313. package/src/components/discover.test.ts +352 -0
  314. package/src/components/discover.ts +167 -0
  315. package/src/components/driver-output.ts +71 -0
  316. package/src/components/driver.test.ts +638 -0
  317. package/src/components/driver.ts +690 -0
  318. package/src/components/index.ts +113 -0
  319. package/src/components/manifest-persistence.test.ts +152 -0
  320. package/src/components/manifest-persistence.ts +164 -0
  321. package/src/components/pilots/README.md +33 -0
  322. package/src/components/pilots/alb-ecs.pilot.ts +63 -0
  323. package/src/components/pilots/dynamodb.pilot.ts +59 -0
  324. package/src/components/pilots/jar-emr.pilot.ts +69 -0
  325. package/src/components/pilots/lambda.pilot.ts +55 -0
  326. package/src/components/pilots/neo4j-fanout.pilot.ts +92 -0
  327. package/src/components/pilots/pilots-lint-clean.test.ts +78 -0
  328. package/src/components/pilots/pilots.test.ts +126 -0
  329. package/src/components/pilots/project.ts +13 -0
  330. package/src/components/presets/comp007-reduction.test.ts +122 -0
  331. package/src/components/presets/ecs-fargate.test.ts +84 -0
  332. package/src/components/presets/ecs-fargate.ts +103 -0
  333. package/src/components/presets/index.ts +32 -0
  334. package/src/components/presets/lambda.test.ts +35 -0
  335. package/src/components/presets/lambda.ts +53 -0
  336. package/src/components/presets/single-host-compose.test.ts +65 -0
  337. package/src/components/presets/single-host-compose.ts +64 -0
  338. package/src/components/registry.test.ts +112 -0
  339. package/src/components/registry.ts +37 -0
  340. package/src/components/starter-plugin.ts +105 -0
  341. package/src/components/temporal-codegen-loader.ts +41 -0
  342. package/src/components/verbs/__fixtures__/package-lock.json +45 -0
  343. package/src/components/verbs/__fixtures__/pom.xml +37 -0
  344. package/src/components/verbs/__fixtures__/schemas/cyclonedx-1.5.schema.json +3800 -0
  345. package/src/components/verbs/__fixtures__/schemas/cyclonedx-spdx-license.schema.json +621 -0
  346. package/src/components/verbs/__fixtures__/schemas/jsf-0.82.schema.json +240 -0
  347. package/src/components/verbs/__fixtures__/schemas/spdx-2.3.schema.json +740 -0
  348. package/src/components/verbs/__tests__/mock-cloud-executor.ts +102 -0
  349. package/src/components/verbs/__tests__/mock-process-runner.ts +79 -0
  350. package/src/components/verbs/__tests__/mock-sbom-generator.ts +85 -0
  351. package/src/components/verbs/bom-writer.test.ts +331 -0
  352. package/src/components/verbs/bom-writer.ts +386 -0
  353. package/src/components/verbs/build-archive.test.ts +251 -0
  354. package/src/components/verbs/build-archive.ts +296 -0
  355. package/src/components/verbs/build.test.ts +222 -0
  356. package/src/components/verbs/build.ts +252 -0
  357. package/src/components/verbs/cloud-executor.ts +181 -0
  358. package/src/components/verbs/component-bom.test.ts +219 -0
  359. package/src/components/verbs/component-bom.ts +189 -0
  360. package/src/components/verbs/index.ts +49 -0
  361. package/src/components/verbs/license-policy.ts +115 -0
  362. package/src/components/verbs/lockfile-sbom-generator.test.ts +195 -0
  363. package/src/components/verbs/lockfile-sbom-generator.ts +236 -0
  364. package/src/components/verbs/process-runner.test.ts +79 -0
  365. package/src/components/verbs/process-runner.ts +113 -0
  366. package/src/components/verbs/reproducibility.test.ts +24 -0
  367. package/src/components/verbs/reproducibility.ts +100 -0
  368. package/src/components/verbs/sbom-generator.test.ts +96 -0
  369. package/src/components/verbs/sbom-generator.ts +209 -0
  370. package/src/components/verbs/sbom.test.ts +184 -0
  371. package/src/components/verbs/sbom.ts +152 -0
  372. package/src/components/verbs/shell.test.ts +36 -0
  373. package/src/components/verbs/shell.ts +51 -0
  374. package/src/components/verbs/sign.test.ts +210 -0
  375. package/src/components/verbs/sign.ts +350 -0
  376. package/src/components/verbs/stub.ts +34 -0
  377. package/src/components/verbs/tool-sbom-generator.test.ts +194 -0
  378. package/src/components/verbs/tool-sbom-generator.ts +192 -0
  379. package/src/components/verbs/verify.test.ts +159 -0
  380. package/src/components/verbs/verify.ts +179 -0
  381. package/src/components/verbs/vex.ts +182 -0
  382. package/src/components/verbs/vuln-gate.test.ts +201 -0
  383. package/src/components/verbs/vuln-gate.ts +170 -0
  384. package/src/components/verbs/vuln-scan.test.ts +145 -0
  385. package/src/components/verbs/vuln-scan.ts +266 -0
  386. package/src/components/verbs/wait-verify.test.ts +111 -0
  387. package/src/components/verbs/wait-verify.ts +171 -0
  388. package/src/config.test.ts +74 -1
  389. package/src/config.ts +219 -0
  390. package/src/index.ts +32 -0
  391. package/src/lexicon.ts +88 -0
  392. package/src/lifecycle/build-ledger-store.test.ts +217 -0
  393. package/src/lifecycle/build-ledger-store.ts +165 -0
  394. package/src/lifecycle/build-ledger.test.ts +349 -0
  395. package/src/lifecycle/build-ledger.ts +305 -0
  396. package/src/lifecycle/git.test.ts +118 -0
  397. package/src/lifecycle/git.ts +149 -14
  398. package/src/lifecycle/index.ts +5 -0
  399. package/src/lifecycle/oras-referrer-lookup.test.ts +116 -0
  400. package/src/lifecycle/oras-referrer-lookup.ts +110 -0
  401. package/src/lifecycle/release-ledger.test.ts +209 -0
  402. package/src/lifecycle/release-ledger.ts +185 -0
  403. package/src/lifecycle/status.test.ts +330 -0
  404. package/src/lifecycle/status.ts +278 -0
  405. package/src/lint/component-checks.ts +148 -0
  406. package/src/lint/config.test.ts +21 -0
  407. package/src/lint/engine.ts +11 -2
  408. package/src/lint/presets/relaxed.json +8 -1
  409. package/src/lint/presets/strict.json +8 -1
  410. package/src/lint/rules/__fixtures__/comp/comp000-discovery-error/broken-a.component.ts +9 -0
  411. package/src/lint/rules/__fixtures__/comp/comp000-discovery-error/broken-b.component.ts +9 -0
  412. package/src/lint/rules/__fixtures__/comp/comp001/fail/orphan-lib.component.ts +11 -0
  413. package/src/lint/rules/__fixtures__/comp/comp001/pass/emr-job.component.ts +13 -0
  414. package/src/lint/rules/__fixtures__/comp/comp001/pass/jar-lib.component.ts +11 -0
  415. package/src/lint/rules/__fixtures__/comp/comp002/fail/search-service.component.ts +27 -0
  416. package/src/lint/rules/__fixtures__/comp/comp002/pass/jar-lib.component.ts +10 -0
  417. package/src/lint/rules/__fixtures__/comp/comp002/pass/search-service.component.ts +23 -0
  418. package/src/lint/rules/__fixtures__/comp/comp003/fail/migrator.component.ts +10 -0
  419. package/src/lint/rules/__fixtures__/comp/comp003/pass/migrator.component.ts +19 -0
  420. package/src/lint/rules/__fixtures__/comp/comp003/pass/orders-table.component.ts +10 -0
  421. package/src/lint/rules/__fixtures__/comp/comp004/fail/neo4j-cluster.component.ts +20 -0
  422. package/src/lint/rules/__fixtures__/comp/comp004/pass/neo4j-cluster.component.ts +23 -0
  423. package/src/lint/rules/__fixtures__/comp/comp004/pass/orders-table.component.ts +10 -0
  424. package/src/lint/rules/__fixtures__/comp/comp005/fail/search-service.component.ts +14 -0
  425. package/src/lint/rules/__fixtures__/comp/comp005/pass/search-service.component.ts +18 -0
  426. package/src/lint/rules/__fixtures__/comp/comp006/fail/legacy-tool.component.ts +10 -0
  427. package/src/lint/rules/__fixtures__/comp/comp006/pass/legacy-tool.component.ts +14 -0
  428. package/src/lint/rules/__fixtures__/comp/comp007/fail/inventory-table.component.ts +13 -0
  429. package/src/lint/rules/__fixtures__/comp/comp007/fail/orders-table.component.ts +13 -0
  430. package/src/lint/rules/__fixtures__/comp/comp007/pass/orders-table.component.ts +12 -0
  431. package/src/lint/rules/__fixtures__/comp/comp007/pass/search-service.component.ts +18 -0
  432. package/src/lint/rules/comp/comp.test.ts +378 -0
  433. package/src/lint/rules/comp/comp001-publishes-never-applies.ts +78 -0
  434. package/src/lint/rules/comp/comp002-dangling-wiring-ref.ts +94 -0
  435. package/src/lint/rules/comp/comp003-mutating-no-rollback.ts +115 -0
  436. package/src/lint/rules/comp/comp004-gate-needs-temporal.ts +66 -0
  437. package/src/lint/rules/comp/comp005-capability-kind-is-noun.ts +113 -0
  438. package/src/lint/rules/comp/comp006-shell-needs-reason.ts +55 -0
  439. package/src/lint/rules/comp/comp007-composition-sprawl.ts +71 -0
  440. package/src/lint/rules/comp/index.ts +37 -0
  441. package/src/lint/rules/comp/support.ts +173 -0
  442. package/src/lsp/lexicon-providers.test.ts +55 -0
  443. package/src/lsp/lexicon-providers.ts +13 -3
  444. package/src/op/activity-registry.test.ts +38 -0
  445. package/src/op/activity-registry.ts +31 -12
  446. package/src/op/activity-runtime.ts +73 -0
  447. package/src/op/builders.ts +92 -0
  448. package/src/op/index.ts +3 -1
  449. package/src/ownership.test.ts +24 -26
  450. package/src/ownership.ts +26 -38
@@ -0,0 +1,170 @@
1
+ /**
2
+ * `vuln-gate` capability (#626) — the deploy-time gate that closes the
3
+ * supply-chain loop: scan the SBOM (./vuln-scan.ts) for known CVEs, suppress
4
+ * the ones VEX says don't matter (./vex.ts), check licenses (./license-policy.ts),
5
+ * and **throw** on a policy violation so composing it before an apply phase
6
+ * fails the deploy — the same throw-to-halt mechanism as ./verify.ts and
7
+ * `policyGate` (../../lint/policy.ts). No new gate plumbing.
8
+ *
9
+ * **Beginner-safe default policy:** fail on `severity >= critical AND fixable
10
+ * AND not VEX-suppressed`; findings at/above the warn severity (default
11
+ * `high`) that don't reach the fail bar are reported as warnings, not blocked;
12
+ * license findings are report-only unless `failOnLicense` is set. Everything
13
+ * is configurable via `chant.config.ts`'s `vulnPolicy` section (see
14
+ * ../../config.ts `resolveVulnPolicy`). The intent is that a first-timer who
15
+ * just adds a `vuln-gate` step gets a sensible, non-noisy gate that blocks the
16
+ * genuinely actionable (critical + fixable) and reports the rest.
17
+ */
18
+
19
+ import type { Capability } from "../capability";
20
+ import type { SbomDocument } from "./sbom-generator";
21
+ import {
22
+ defaultVulnScanner,
23
+ SEVERITY_RANK,
24
+ type Severity,
25
+ type VulnFinding,
26
+ type VulnScanner,
27
+ } from "./vuln-scan";
28
+ import { applyVex, parseVexDocument, type SuppressedFinding, type VexStatement } from "./vex";
29
+ import { evaluateLicensePolicy, type LicensePolicy, type LicenseViolation } from "./license-policy";
30
+
31
+ /** The gate's policy. Defaults (per `resolveVulnPolicy`, ../../config.ts) are beginner-safe: fail critical+fixable, warn high, license report-only. */
32
+ export interface VulnPolicy {
33
+ /** Minimum severity that FAILS the gate. Default `"critical"`. */
34
+ failSeverity: Severity;
35
+ /** When true (default), only *fixable* findings at/above `failSeverity` fail the gate — an unfixable finding can't be actioned by upgrading, so it warns instead of blocking. */
36
+ fixableOnly: boolean;
37
+ /** Minimum severity reported as a warning (below `failSeverity`). Default `"high"`. */
38
+ warnSeverity: Severity;
39
+ /** License allow/deny policy. Evaluated always; only *blocks* when `failOnLicense` is true. */
40
+ license?: LicensePolicy;
41
+ /** Block the deploy on a license violation. Default `false` (report-only) — license posture is context-dependent. */
42
+ failOnLicense: boolean;
43
+ /** Block on an `unknown`-severity finding (a scanner that didn't report a severity chant could map). Default `false` — but such a finding is ALWAYS at least warned, never silently dropped, regardless of this flag. Set true for a strict shop that won't ship an unclassifiable finding. */
44
+ failOnUnknownSeverity: boolean;
45
+ }
46
+
47
+ /** Beginner-safe defaults, also encoded in `resolveVulnPolicy` (../../config.ts). */
48
+ export const DEFAULT_VULN_POLICY: VulnPolicy = {
49
+ failSeverity: "critical",
50
+ fixableOnly: true,
51
+ warnSeverity: "high",
52
+ failOnLicense: false,
53
+ failOnUnknownSeverity: false,
54
+ };
55
+
56
+ export interface VulnGateInput {
57
+ /** The artifact's SBOM. Scanned in-place unless `findings` is supplied. */
58
+ sbom: SbomDocument;
59
+ /** Pre-scanned findings (e.g. from a prior `scan-vulnerabilities` step). If omitted, the gate scans `sbom` itself via the injected scanner. */
60
+ findings?: VulnFinding[];
61
+ /** VEX documents (OpenVEX or CycloneDX-embedded), as serialized JSON, applied to suppress findings. */
62
+ vex?: string[];
63
+ /** Policy override; merged over `DEFAULT_VULN_POLICY`. */
64
+ policy?: Partial<VulnPolicy>;
65
+ /** Artifact digest, for reporting. */
66
+ digest?: string;
67
+ }
68
+
69
+ /** A finding that fails the gate, with why. */
70
+ export interface BlockingFinding {
71
+ finding: VulnFinding;
72
+ reason: "severity-threshold" | "unknown-severity";
73
+ }
74
+
75
+ export interface VulnGateOutput {
76
+ /** Always `true` when returned — a violation throws instead (mirrors ./verify.ts's `verified`). */
77
+ passed: true;
78
+ /** Findings at/above `warnSeverity` that did not reach the fail bar. */
79
+ warnings: VulnFinding[];
80
+ /** Findings suppressed by VEX, with justification (never silently dropped). */
81
+ suppressed: SuppressedFinding[];
82
+ /** License findings — reported even when `failOnLicense` is false. */
83
+ licenseFindings: LicenseViolation[];
84
+ }
85
+
86
+ /**
87
+ * Thrown when the gate blocks a deploy — carries the blocking findings and any
88
+ * blocking license violations so a reviewer sees exactly what to fix (or VEX).
89
+ * Distinct from a scanner-not-installed error (`VulnScannerNotImplementedError`/
90
+ * `ToolNotAvailableError`) so a caller can tell "policy said no" from "no
91
+ * scanner."
92
+ */
93
+ export class VulnGateFailedError extends Error {
94
+ constructor(
95
+ public readonly blocking: BlockingFinding[],
96
+ public readonly blockingLicenses: LicenseViolation[],
97
+ ) {
98
+ const cveList = blocking.map((b) => `${b.finding.cveId} (${b.finding.severity}, ${b.finding.package})`).join(", ");
99
+ const licList = blockingLicenses.map((l) => `${l.license} in ${l.package}`).join(", ");
100
+ const parts: string[] = [];
101
+ if (blocking.length) parts.push(`${blocking.length} vulnerability finding(s): ${cveList}`);
102
+ if (blockingLicenses.length) parts.push(`${blockingLicenses.length} license violation(s): ${licList}`);
103
+ super(`vuln-gate blocked the deploy — ${parts.join("; ")}. Fix the dependency, or record a VEX statement if it is not exploitable.`);
104
+ this.name = "VulnGateFailedError";
105
+ }
106
+ }
107
+
108
+ /** Parse every supplied VEX document into a flat statement list (ignoring any that fail to parse into a known shape). */
109
+ function collectVex(docs: string[] | undefined): VexStatement[] {
110
+ const out: VexStatement[] = [];
111
+ for (const bytes of docs ?? []) out.push(...parseVexDocument(bytes));
112
+ return out;
113
+ }
114
+
115
+ /**
116
+ * Build the `vuln-gate` capability. Scans `sbom` (or uses supplied `findings`),
117
+ * applies VEX, evaluates the license policy, and classifies every gating
118
+ * finding: it BLOCKS (throws `VulnGateFailedError`) any finding at/above
119
+ * `failSeverity` that satisfies `fixableOnly`, plus license violations when
120
+ * `failOnLicense`; everything at/above `warnSeverity` below the fail bar is a
121
+ * warning. On a clean pass it returns the warnings/suppressed/license report
122
+ * for logging.
123
+ */
124
+ export function createVulnGateCapability(
125
+ scanner: VulnScanner = defaultVulnScanner(),
126
+ ): Capability<VulnGateInput, VulnGateOutput> {
127
+ return {
128
+ kind: "vuln-gate",
129
+ async run(_ctx, input) {
130
+ const policy: VulnPolicy = { ...DEFAULT_VULN_POLICY, ...input.policy };
131
+ const rawFindings = input.findings ?? (await scanner.scan({ sbom: input.sbom, digest: input.digest }));
132
+
133
+ // 1. VEX suppression.
134
+ const { gating, suppressed } = applyVex(rawFindings, collectVex(input.vex));
135
+
136
+ // 2. Classify gating findings against the severity policy.
137
+ const failRank = SEVERITY_RANK[policy.failSeverity];
138
+ const warnRank = SEVERITY_RANK[policy.warnSeverity];
139
+ const blocking: BlockingFinding[] = [];
140
+ const warnings: VulnFinding[] = [];
141
+ for (const f of gating) {
142
+ const rank = SEVERITY_RANK[f.severity];
143
+ if (rank >= failRank && (!policy.fixableOnly || f.fixable)) {
144
+ blocking.push({ finding: f, reason: "severity-threshold" });
145
+ } else if (f.severity === "unknown") {
146
+ // Never silently pass an unclassifiable finding — a real critical
147
+ // could arrive mislabeled from a broken/compromised scanner. Surface
148
+ // it always; block it when the policy opts in.
149
+ if (policy.failOnUnknownSeverity) blocking.push({ finding: f, reason: "unknown-severity" });
150
+ else warnings.push(f);
151
+ } else if (rank >= warnRank) {
152
+ warnings.push(f);
153
+ }
154
+ }
155
+
156
+ // 3. License policy (always evaluated; blocks only when opted in).
157
+ const licenseFindings = policy.license ? evaluateLicensePolicy(input.sbom, policy.license) : [];
158
+ const blockingLicenses = policy.failOnLicense ? licenseFindings : [];
159
+
160
+ if (blocking.length > 0 || blockingLicenses.length > 0) {
161
+ throw new VulnGateFailedError(blocking, blockingLicenses);
162
+ }
163
+
164
+ return { passed: true, warnings, suppressed, licenseFindings };
165
+ },
166
+ };
167
+ }
168
+
169
+ /** Default `vuln-gate` capability (inject a real/mock scanner, or supply `findings`). */
170
+ export const vulnGateCapability: Capability<VulnGateInput, VulnGateOutput> = createVulnGateCapability();
@@ -0,0 +1,145 @@
1
+ /**
2
+ * Tests for #626's vulnerability scanning (./vuln-scan.ts): scanner output
3
+ * parsing, the real `ProcessRunner`-backed tool scanner (against a
4
+ * `MockProcessRunner` — never a live grype/trivy), and the
5
+ * `scan-vulnerabilities` capability over an injected scanner.
6
+ */
7
+
8
+ import { describe, test, expect } from "vitest";
9
+ import {
10
+ normalizeSeverity,
11
+ parseGrypeOutput,
12
+ parseTrivyOutput,
13
+ createToolVulnScanner,
14
+ createScanVulnerabilitiesCapability,
15
+ autoDetectVulnScanner,
16
+ type VulnFinding,
17
+ type VulnScanner,
18
+ } from "./vuln-scan";
19
+ import { ToolNotAvailableError } from "./process-runner";
20
+ import type { SbomDocument } from "./sbom-generator";
21
+ import { createMockProcessRunner } from "./__tests__/mock-process-runner";
22
+
23
+ const ctx = { env: "prod", component: "search-service" };
24
+
25
+ const SBOM: SbomDocument = {
26
+ format: "spdx",
27
+ mediaType: "application/spdx+json",
28
+ bytes: JSON.stringify({ packages: [{ name: "lodash", licenseConcluded: "MIT" }] }),
29
+ generator: "lockfile",
30
+ };
31
+
32
+ const GRYPE_JSON = JSON.stringify({
33
+ matches: [
34
+ {
35
+ vulnerability: { id: "CVE-2024-0001", severity: "Critical", fix: { versions: ["4.17.21"], state: "fixed" } },
36
+ artifact: { name: "lodash", version: "4.17.20" },
37
+ },
38
+ {
39
+ vulnerability: { id: "CVE-2024-0002", severity: "Low", fix: { versions: [], state: "not-fixed" } },
40
+ artifact: { name: "left-pad", version: "1.0.0" },
41
+ },
42
+ ],
43
+ });
44
+
45
+ describe("normalizeSeverity", () => {
46
+ test("normalizes case and maps unknowns to 'unknown'", () => {
47
+ expect(normalizeSeverity("Critical")).toBe("critical");
48
+ expect(normalizeSeverity("HIGH")).toBe("high");
49
+ expect(normalizeSeverity("Negligible")).toBe("negligible");
50
+ expect(normalizeSeverity("bananas")).toBe("unknown");
51
+ expect(normalizeSeverity(undefined)).toBe("unknown");
52
+ });
53
+ });
54
+
55
+ describe("parseGrypeOutput", () => {
56
+ test("maps matches to findings, deriving fixability", () => {
57
+ const findings = parseGrypeOutput(GRYPE_JSON);
58
+ expect(findings).toHaveLength(2);
59
+ expect(findings[0]).toEqual<VulnFinding>({
60
+ cveId: "CVE-2024-0001",
61
+ severity: "critical",
62
+ package: "lodash",
63
+ installedVersion: "4.17.20",
64
+ fixedVersion: "4.17.21",
65
+ fixable: true,
66
+ });
67
+ expect(findings[1].fixable).toBe(false);
68
+ expect(findings[1].severity).toBe("low");
69
+ });
70
+ });
71
+
72
+ describe("parseTrivyOutput", () => {
73
+ test("flattens Results[].Vulnerabilities into findings", () => {
74
+ const trivy = JSON.stringify({
75
+ Results: [
76
+ { Vulnerabilities: [{ VulnerabilityID: "CVE-2024-0003", Severity: "HIGH", PkgName: "openssl", InstalledVersion: "1.1.1", FixedVersion: "1.1.1w" }] },
77
+ ],
78
+ });
79
+ const findings = parseTrivyOutput(trivy);
80
+ expect(findings).toHaveLength(1);
81
+ expect(findings[0].cveId).toBe("CVE-2024-0003");
82
+ expect(findings[0].fixable).toBe(true);
83
+ expect(findings[0].fixedVersion).toBe("1.1.1w");
84
+ });
85
+ });
86
+
87
+ describe("createToolVulnScanner (grype, via MockProcessRunner)", () => {
88
+ test("scans the SBOM with `grype sbom:<file>` and parses the result", async () => {
89
+ const mock = createMockProcessRunner({ tools: { grype: true }, responses: { "grype sbom:": GRYPE_JSON } });
90
+ const scanner = createToolVulnScanner("grype", mock.runner);
91
+ const findings = await scanner.scan({ sbom: SBOM, digest: "sha256:abc" });
92
+ expect(findings).toHaveLength(2);
93
+ const scanCall = mock.calls.find((c) => c.command.includes("grype sbom:"));
94
+ expect(scanCall).toBeDefined();
95
+ expect(scanCall!.command).toContain("-o json");
96
+ });
97
+
98
+ test("throws ToolNotAvailableError when grype is absent (never a silent empty scan)", async () => {
99
+ const mock = createMockProcessRunner({ tools: { grype: false }, defaultAvailable: false });
100
+ const scanner = createToolVulnScanner("grype", mock.runner);
101
+ await expect(scanner.scan({ sbom: SBOM })).rejects.toBeInstanceOf(ToolNotAvailableError);
102
+ });
103
+ });
104
+
105
+ describe("autoDetectVulnScanner — the registered default (#634)", () => {
106
+ const TRIVY_JSON = JSON.stringify({
107
+ Results: [{ Vulnerabilities: [{ VulnerabilityID: "CVE-2024-9999", Severity: "HIGH", PkgName: "lodash", InstalledVersion: "4.17.20", FixedVersion: "4.17.21" }] }],
108
+ });
109
+
110
+ test("prefers grype when present", async () => {
111
+ const mock = createMockProcessRunner({ tools: { grype: true, trivy: true }, responses: { "grype sbom:": GRYPE_JSON } });
112
+ const findings = await autoDetectVulnScanner(mock.runner).scan({ sbom: SBOM });
113
+ expect(findings).toHaveLength(2);
114
+ expect(mock.calls.some((c) => c.command.includes("grype sbom:"))).toBe(true);
115
+ expect(mock.calls.some((c) => c.command.includes("trivy sbom"))).toBe(false);
116
+ });
117
+
118
+ test("falls back to trivy when grype is absent", async () => {
119
+ const mock = createMockProcessRunner({ tools: { grype: false, trivy: true }, defaultAvailable: false, responses: { "trivy sbom": TRIVY_JSON } });
120
+ const findings = await autoDetectVulnScanner(mock.runner).scan({ sbom: SBOM });
121
+ expect(findings).toHaveLength(1);
122
+ expect(mock.calls.some((c) => c.command.includes("trivy sbom"))).toBe(true);
123
+ });
124
+
125
+ test("throws ToolNotAvailableError naming both tools when neither is installed (not VulnScannerNotImplementedError)", async () => {
126
+ const mock = createMockProcessRunner({ tools: { grype: false, trivy: false }, defaultAvailable: false });
127
+ await expect(autoDetectVulnScanner(mock.runner).scan({ sbom: SBOM })).rejects.toBeInstanceOf(ToolNotAvailableError);
128
+ await expect(autoDetectVulnScanner(mock.runner).scan({ sbom: SBOM })).rejects.toThrow(/grype or trivy/);
129
+ });
130
+ });
131
+
132
+ describe("scan-vulnerabilities capability", () => {
133
+ test("returns the scanner's findings and echoes the digest", async () => {
134
+ const fake: VulnScanner = {
135
+ async scan() {
136
+ return parseGrypeOutput(GRYPE_JSON);
137
+ },
138
+ };
139
+ const cap = createScanVulnerabilitiesCapability(fake);
140
+ expect(cap.kind).toBe("scan-vulnerabilities");
141
+ const out = await cap.run(ctx, { sbom: SBOM, digest: "sha256:xyz" });
142
+ expect(out.findings).toHaveLength(2);
143
+ expect(out.digest).toBe("sha256:xyz");
144
+ });
145
+ });
@@ -0,0 +1,266 @@
1
+ /**
2
+ * Vulnerability scanning of an already-generated SBOM (#626, epic #551
3
+ * supply-chain follow-up to the SBOM stack #606/#613/#614/#609/#610). An SBOM
4
+ * says *what's in* an artifact; a scanner says *which of those have known
5
+ * CVEs*. This module produces findings; ./vex.ts suppresses the ones that
6
+ * don't matter and ./vuln-gate.ts fails the deploy on the ones that do.
7
+ *
8
+ * Prefer scanning the **SBOM** (already generated + persisted, keyed by
9
+ * digest) over re-scanning the image: deterministic (same SBOM -> same
10
+ * findings), fast, and reuses work already done. Real backends shell out to
11
+ * `grype`/`trivy` through the injectable `ProcessRunner` (./process-runner.ts),
12
+ * exactly like ./tool-sbom-generator.ts's deep-scan backend — tests inject a
13
+ * `MockVulnScanner` (./__tests__/mock-vuln-scanner.ts) and never invoke a real
14
+ * scanner, network, or vuln DB. The scanner's vuln DB currency is the tool's
15
+ * job, not chant's.
16
+ */
17
+
18
+ import { writeFileSync } from "node:fs";
19
+ import { tmpdir } from "node:os";
20
+ import { join } from "node:path";
21
+ import { createHash } from "node:crypto";
22
+ import type { Capability } from "../capability";
23
+ import type { SbomDocument } from "./sbom-generator";
24
+ import { defaultProcessRunner, q, requireTool, ToolNotAvailableError, type ProcessRunner } from "./process-runner";
25
+
26
+ // ── findings ─────────────────────────────────────────────────────────────────
27
+
28
+ /** CVE severity, highest to lowest. `unknown` sorts lowest so it never trips a threshold gate by accident. */
29
+ export type Severity = "critical" | "high" | "medium" | "low" | "negligible" | "unknown";
30
+
31
+ /** Severity rank (higher = more severe) so a gate can ask "severity >= critical". */
32
+ export const SEVERITY_RANK: Record<Severity, number> = {
33
+ critical: 5,
34
+ high: 4,
35
+ medium: 3,
36
+ low: 2,
37
+ negligible: 1,
38
+ unknown: 0,
39
+ };
40
+
41
+ /** Normalize a scanner's free-form severity string ("High", "CRITICAL", "Negligible", …) to a `Severity`. Unrecognized -> `unknown`. */
42
+ export function normalizeSeverity(raw: string | undefined): Severity {
43
+ const s = (raw ?? "").toLowerCase();
44
+ if (s === "critical" || s === "high" || s === "medium" || s === "low" || s === "negligible") return s;
45
+ return "unknown";
46
+ }
47
+
48
+ /** One known vulnerability affecting one package in the scanned artifact. */
49
+ export interface VulnFinding {
50
+ /** Advisory id, e.g. `"CVE-2024-12345"` or `"GHSA-…"` — the join key with VEX statements (./vex.ts). */
51
+ cveId: string;
52
+ severity: Severity;
53
+ /** Affected package name. */
54
+ package: string;
55
+ installedVersion: string;
56
+ /** First version that fixes it, when the scanner reports one. */
57
+ fixedVersion?: string;
58
+ /** True when a fix exists (upgradeable) — the beginner-safe default gate blocks only fixable findings, since an unfixable one can't be actioned by bumping. */
59
+ fixable: boolean;
60
+ }
61
+
62
+ // ── injectable scanner boundary ──────────────────────────────────────────────
63
+
64
+ export interface ScanInput {
65
+ /** The SBOM to scan (the artifact's already-generated SPDX/CycloneDX doc). */
66
+ sbom: SbomDocument;
67
+ /** Artifact digest the SBOM belongs to — informational, for logging/keying. */
68
+ digest?: string;
69
+ }
70
+
71
+ /**
72
+ * Injectable vulnerability-scan boundary — the scan-side analogue of
73
+ * `SbomGenerator` (./sbom-generator.ts) and `CloudExecutor`
74
+ * (./cloud-executor.ts). A real implementation shells out to `grype`/`trivy`;
75
+ * tests substitute `MockVulnScanner` (./__tests__/mock-vuln-scanner.ts) and
76
+ * never touch a real tool, network, or vuln DB.
77
+ */
78
+ export interface VulnScanner {
79
+ /** Scan an SBOM, returning every known vulnerability it surfaces. */
80
+ scan(input: ScanInput): Promise<VulnFinding[]>;
81
+ }
82
+
83
+ /** Which real CLI scanner a `ProcessRunner`-backed scanner shells out to. */
84
+ export type ScannerTool = "grype" | "trivy";
85
+
86
+ /** grype `-o json` output shape (the subset we read). */
87
+ interface GrypeOutput {
88
+ matches?: Array<{
89
+ vulnerability?: { id?: string; severity?: string; fix?: { versions?: string[]; state?: string } };
90
+ artifact?: { name?: string; version?: string };
91
+ }>;
92
+ }
93
+
94
+ /** trivy `--format json` output shape (the subset we read). */
95
+ interface TrivyOutput {
96
+ Results?: Array<{
97
+ Vulnerabilities?: Array<{
98
+ VulnerabilityID?: string;
99
+ Severity?: string;
100
+ PkgName?: string;
101
+ InstalledVersion?: string;
102
+ FixedVersion?: string;
103
+ }>;
104
+ }>;
105
+ }
106
+
107
+ /** Parse `grype -o json` stdout into findings. Exported for tests to assert parsing without a live tool. */
108
+ export function parseGrypeOutput(stdout: string): VulnFinding[] {
109
+ const doc = JSON.parse(stdout) as GrypeOutput;
110
+ return (doc.matches ?? []).map((m) => {
111
+ const fixVersions = m.vulnerability?.fix?.versions ?? [];
112
+ return {
113
+ cveId: m.vulnerability?.id ?? "UNKNOWN",
114
+ severity: normalizeSeverity(m.vulnerability?.severity),
115
+ package: m.artifact?.name ?? "unknown",
116
+ installedVersion: m.artifact?.version ?? "",
117
+ fixedVersion: fixVersions[0],
118
+ fixable: m.vulnerability?.fix?.state === "fixed" || fixVersions.length > 0,
119
+ } satisfies VulnFinding;
120
+ });
121
+ }
122
+
123
+ /** Parse `trivy sbom --format json` stdout into findings. */
124
+ export function parseTrivyOutput(stdout: string): VulnFinding[] {
125
+ const doc = JSON.parse(stdout) as TrivyOutput;
126
+ const out: VulnFinding[] = [];
127
+ for (const r of doc.Results ?? []) {
128
+ for (const v of r.Vulnerabilities ?? []) {
129
+ out.push({
130
+ cveId: v.VulnerabilityID ?? "UNKNOWN",
131
+ severity: normalizeSeverity(v.Severity),
132
+ package: v.PkgName ?? "unknown",
133
+ installedVersion: v.InstalledVersion ?? "",
134
+ fixedVersion: v.FixedVersion || undefined,
135
+ fixable: Boolean(v.FixedVersion),
136
+ });
137
+ }
138
+ }
139
+ return out;
140
+ }
141
+
142
+ /**
143
+ * A real `VulnScanner` that writes the SBOM to a temp file and scans it with
144
+ * `grype` (default) or `trivy`, through the injectable `ProcessRunner`.
145
+ * `requireTool` throws `ToolNotAvailableError` if the scanner is absent — a
146
+ * missing scanner is a hard stop (like ./verify.ts's missing `cosign`), never
147
+ * a silent "no vulns found," because that would let an unscanned artifact
148
+ * through a gate. Never used in tests.
149
+ */
150
+ export function createToolVulnScanner(
151
+ tool: ScannerTool = "grype",
152
+ processRunner: ProcessRunner = defaultProcessRunner(),
153
+ ): VulnScanner {
154
+ return {
155
+ async scan(input) {
156
+ await requireTool(processRunner, tool, `scan the SBOM for known vulnerabilities`);
157
+ // Content-address the temp file so concurrent scans never collide and we
158
+ // avoid Date.now()/Math.random() (blocked in some chant runtimes).
159
+ const hash = createHash("sha256").update(input.sbom.bytes).digest("hex").slice(0, 16);
160
+ const ext = input.sbom.format === "cyclonedx" ? "cdx.json" : "spdx.json";
161
+ const path = join(tmpdir(), `chant-scan-${hash}.${ext}`);
162
+ writeFileSync(path, input.sbom.bytes);
163
+ if (tool === "trivy") {
164
+ const { stdout } = await processRunner.run(`trivy sbom --quiet --format json ${q(path)}`);
165
+ return parseTrivyOutput(stdout);
166
+ }
167
+ const { stdout } = await processRunner.run(`grype sbom:${q(path)} -o json`);
168
+ return parseGrypeOutput(stdout);
169
+ },
170
+ };
171
+ }
172
+
173
+ /**
174
+ * Thrown by `notImplementedVulnScanner` — the "no real scanner wired yet"
175
+ * signal, mirroring `SbomGeneratorNotImplementedError` (./sbom-generator.ts).
176
+ * The default is loud-and-specific rather than a silent empty scan, so a
177
+ * caller that forgets to inject a scanner (or a mock in tests) fails obviously
178
+ * instead of appearing to find zero vulnerabilities.
179
+ */
180
+ export class VulnScannerNotImplementedError extends Error {
181
+ constructor() {
182
+ super(
183
+ `VulnScanner has no real scanner configured — inject a real scanner ` +
184
+ `(createToolVulnScanner("grype"|"trivy")) or a mock in tests`,
185
+ );
186
+ this.name = "VulnScannerNotImplementedError";
187
+ }
188
+ }
189
+
190
+ /** Kept for tests and for a caller that wants the old loud "no scanner wired" behavior; no longer the registered default (#634). */
191
+ export const notImplementedVulnScanner: VulnScanner = {
192
+ async scan() {
193
+ throw new VulnScannerNotImplementedError();
194
+ },
195
+ };
196
+
197
+ /**
198
+ * A `VulnScanner` that picks a real backend at scan time — `grype` if present,
199
+ * else `trivy`. Unlike SBOM generation there is no hermetic fallback (a scan
200
+ * needs a real vuln DB), so when neither tool is on `PATH` this throws a
201
+ * `ToolNotAvailableError` naming what to install — an error a config-only user
202
+ * can act on — rather than `VulnScannerNotImplementedError`, which told them to
203
+ * edit code. This is the vuln-side analog of #630's hermetic-by-default SBOM
204
+ * generator: the registered `scan-vulnerabilities`/`vuln-gate` capabilities now
205
+ * work the moment a scanner is installed, with no code wiring.
206
+ */
207
+ export function autoDetectVulnScanner(processRunner: ProcessRunner = defaultProcessRunner()): VulnScanner {
208
+ return {
209
+ async scan(input) {
210
+ const tool: ScannerTool | undefined = (await processRunner.available("grype"))
211
+ ? "grype"
212
+ : (await processRunner.available("trivy"))
213
+ ? "trivy"
214
+ : undefined;
215
+ if (!tool) {
216
+ throw new ToolNotAvailableError("grype or trivy", "scan the SBOM for known vulnerabilities");
217
+ }
218
+ return createToolVulnScanner(tool, processRunner).scan(input);
219
+ },
220
+ };
221
+ }
222
+
223
+ let defaultScanner: VulnScanner | undefined;
224
+
225
+ /** The default `VulnScanner` the `scan-vulnerabilities`/`vuln-gate` capabilities fall back to when none is supplied: auto-detects `grype`/`trivy` and throws `ToolNotAvailableError` if neither is installed (#634). */
226
+ export function defaultVulnScanner(): VulnScanner {
227
+ if (!defaultScanner) defaultScanner = autoDetectVulnScanner();
228
+ return defaultScanner;
229
+ }
230
+
231
+ // ── scan-vulnerabilities capability ──────────────────────────────────────────
232
+
233
+ export interface ScanVulnerabilitiesInput {
234
+ /** The SBOM to scan. */
235
+ sbom: SbomDocument;
236
+ /** Artifact digest the SBOM belongs to (recorded on the output). */
237
+ digest?: string;
238
+ }
239
+
240
+ export interface ScanVulnerabilitiesOutput {
241
+ findings: VulnFinding[];
242
+ /** Echoed back so a downstream `vuln-gate` step can key results to the artifact. */
243
+ digest?: string;
244
+ }
245
+
246
+ /**
247
+ * `scan-vulnerabilities` capability — scan an SBOM for known CVEs via the
248
+ * injectable `VulnScanner`. Produces findings only; suppression (VEX) and the
249
+ * pass/fail decision live in ./vuln-gate.ts, so a composition can scan once
250
+ * and reuse the findings, or run the gate directly (which scans for you).
251
+ */
252
+ export function createScanVulnerabilitiesCapability(
253
+ scanner: VulnScanner = defaultVulnScanner(),
254
+ ): Capability<ScanVulnerabilitiesInput, ScanVulnerabilitiesOutput> {
255
+ return {
256
+ kind: "scan-vulnerabilities",
257
+ async run(_ctx, input) {
258
+ const findings = await scanner.scan({ sbom: input.sbom, digest: input.digest });
259
+ return { findings, digest: input.digest };
260
+ },
261
+ };
262
+ }
263
+
264
+ /** Default `scan-vulnerabilities` capability, backed by the not-implemented scanner (inject a real/mock one). */
265
+ export const scanVulnerabilitiesCapability: Capability<ScanVulnerabilitiesInput, ScanVulnerabilitiesOutput> =
266
+ createScanVulnerabilitiesCapability();