@intentius/chant 0.13.0 → 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 (453) 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/fetch.d.ts.map +1 -1
  4. package/dist/audit/proof.d.ts +3 -0
  5. package/dist/audit/proof.d.ts.map +1 -1
  6. package/dist/audit/report-model.d.ts +4 -1
  7. package/dist/audit/report-model.d.ts.map +1 -1
  8. package/dist/audit/report.d.ts +3 -0
  9. package/dist/audit/report.d.ts.map +1 -1
  10. package/dist/audit/rules-doc.d.ts +8 -2
  11. package/dist/audit/rules-doc.d.ts.map +1 -1
  12. package/dist/cli/commands/audit.d.ts.map +1 -1
  13. package/dist/cli/commands/doctor.d.ts.map +1 -1
  14. package/dist/cli/commands/lexicon-rolling-upgrade.d.ts +37 -0
  15. package/dist/cli/commands/lexicon-rolling-upgrade.d.ts.map +1 -0
  16. package/dist/cli/commands/lexicon-surface-diff.d.ts +48 -0
  17. package/dist/cli/commands/lexicon-surface-diff.d.ts.map +1 -0
  18. package/dist/cli/commands/lint.d.ts.map +1 -1
  19. package/dist/cli/commands/pinned-upgrade.d.ts +41 -0
  20. package/dist/cli/commands/pinned-upgrade.d.ts.map +1 -0
  21. package/dist/cli/handlers/build.d.ts.map +1 -1
  22. package/dist/cli/handlers/components.d.ts +39 -0
  23. package/dist/cli/handlers/components.d.ts.map +1 -0
  24. package/dist/cli/handlers/dev.d.ts +3 -0
  25. package/dist/cli/handlers/dev.d.ts.map +1 -1
  26. package/dist/cli/handlers/graph.d.ts +4 -2
  27. package/dist/cli/handlers/graph.d.ts.map +1 -1
  28. package/dist/cli/handlers/misc.d.ts.map +1 -1
  29. package/dist/cli/handlers/run.d.ts +49 -0
  30. package/dist/cli/handlers/run.d.ts.map +1 -1
  31. package/dist/cli/main.d.ts.map +1 -1
  32. package/dist/cli/registry.d.ts +35 -0
  33. package/dist/cli/registry.d.ts.map +1 -1
  34. package/dist/codegen/fetch.d.ts +22 -0
  35. package/dist/codegen/fetch.d.ts.map +1 -1
  36. package/dist/codegen/lexicon-regen.d.ts +110 -0
  37. package/dist/codegen/lexicon-regen.d.ts.map +1 -0
  38. package/dist/codegen/pinned-upgrade.d.ts +153 -0
  39. package/dist/codegen/pinned-upgrade.d.ts.map +1 -0
  40. package/dist/codegen/rolling-upgrade.d.ts +138 -0
  41. package/dist/codegen/rolling-upgrade.d.ts.map +1 -0
  42. package/dist/codegen/surface-snapshot.d.ts +173 -0
  43. package/dist/codegen/surface-snapshot.d.ts.map +1 -0
  44. package/dist/codegen/version-bump.d.ts +21 -0
  45. package/dist/codegen/version-bump.d.ts.map +1 -0
  46. package/dist/components/auto-release.d.ts +93 -0
  47. package/dist/components/auto-release.d.ts.map +1 -0
  48. package/dist/components/builders.d.ts +37 -0
  49. package/dist/components/builders.d.ts.map +1 -0
  50. package/dist/components/capability-plugin-loader.d.ts +111 -0
  51. package/dist/components/capability-plugin-loader.d.ts.map +1 -0
  52. package/dist/components/capability-plugin-schema.d.ts +24 -0
  53. package/dist/components/capability-plugin-schema.d.ts.map +1 -0
  54. package/dist/components/capability-plugin.d.ts +85 -0
  55. package/dist/components/capability-plugin.d.ts.map +1 -0
  56. package/dist/components/capability.d.ts +89 -0
  57. package/dist/components/capability.d.ts.map +1 -0
  58. package/dist/components/cli-support.d.ts +204 -0
  59. package/dist/components/cli-support.d.ts.map +1 -0
  60. package/dist/components/component.d.ts +179 -0
  61. package/dist/components/component.d.ts.map +1 -0
  62. package/dist/components/config-defaults.d.ts +41 -0
  63. package/dist/components/config-defaults.d.ts.map +1 -0
  64. package/dist/components/discover.d.ts +60 -0
  65. package/dist/components/discover.d.ts.map +1 -0
  66. package/dist/components/driver-output.d.ts +20 -0
  67. package/dist/components/driver-output.d.ts.map +1 -0
  68. package/dist/components/driver.d.ts +209 -0
  69. package/dist/components/driver.d.ts.map +1 -0
  70. package/dist/components/index.d.ts +22 -0
  71. package/dist/components/index.d.ts.map +1 -0
  72. package/dist/components/manifest-persistence.d.ts +79 -0
  73. package/dist/components/manifest-persistence.d.ts.map +1 -0
  74. package/dist/components/pilots/alb-ecs.pilot.d.ts +27 -0
  75. package/dist/components/pilots/alb-ecs.pilot.d.ts.map +1 -0
  76. package/dist/components/pilots/dynamodb.pilot.d.ts +22 -0
  77. package/dist/components/pilots/dynamodb.pilot.d.ts.map +1 -0
  78. package/dist/components/pilots/jar-emr.pilot.d.ts +37 -0
  79. package/dist/components/pilots/jar-emr.pilot.d.ts.map +1 -0
  80. package/dist/components/pilots/lambda.pilot.d.ts +35 -0
  81. package/dist/components/pilots/lambda.pilot.d.ts.map +1 -0
  82. package/dist/components/pilots/neo4j-fanout.pilot.d.ts +17 -0
  83. package/dist/components/pilots/neo4j-fanout.pilot.d.ts.map +1 -0
  84. package/dist/components/pilots/project.d.ts +13 -0
  85. package/dist/components/pilots/project.d.ts.map +1 -0
  86. package/dist/components/presets/ecs-fargate.d.ts +59 -0
  87. package/dist/components/presets/ecs-fargate.d.ts.map +1 -0
  88. package/dist/components/presets/index.d.ts +29 -0
  89. package/dist/components/presets/index.d.ts.map +1 -0
  90. package/dist/components/presets/lambda.d.ts +35 -0
  91. package/dist/components/presets/lambda.d.ts.map +1 -0
  92. package/dist/components/presets/single-host-compose.d.ts +43 -0
  93. package/dist/components/presets/single-host-compose.d.ts.map +1 -0
  94. package/dist/components/registry.d.ts +29 -0
  95. package/dist/components/registry.d.ts.map +1 -0
  96. package/dist/components/starter-plugin.d.ts +51 -0
  97. package/dist/components/starter-plugin.d.ts.map +1 -0
  98. package/dist/components/temporal-codegen-loader.d.ts +27 -0
  99. package/dist/components/temporal-codegen-loader.d.ts.map +1 -0
  100. package/dist/components/verbs/__tests__/mock-cloud-executor.d.ts +35 -0
  101. package/dist/components/verbs/__tests__/mock-cloud-executor.d.ts.map +1 -0
  102. package/dist/components/verbs/__tests__/mock-process-runner.d.ts +45 -0
  103. package/dist/components/verbs/__tests__/mock-process-runner.d.ts.map +1 -0
  104. package/dist/components/verbs/__tests__/mock-sbom-generator.d.ts +33 -0
  105. package/dist/components/verbs/__tests__/mock-sbom-generator.d.ts.map +1 -0
  106. package/dist/components/verbs/bom-writer.d.ts +155 -0
  107. package/dist/components/verbs/bom-writer.d.ts.map +1 -0
  108. package/dist/components/verbs/build-archive.d.ts +205 -0
  109. package/dist/components/verbs/build-archive.d.ts.map +1 -0
  110. package/dist/components/verbs/build.d.ts +150 -0
  111. package/dist/components/verbs/build.d.ts.map +1 -0
  112. package/dist/components/verbs/cloud-executor.d.ts +88 -0
  113. package/dist/components/verbs/cloud-executor.d.ts.map +1 -0
  114. package/dist/components/verbs/component-bom.d.ts +103 -0
  115. package/dist/components/verbs/component-bom.d.ts.map +1 -0
  116. package/dist/components/verbs/index.d.ts +49 -0
  117. package/dist/components/verbs/index.d.ts.map +1 -0
  118. package/dist/components/verbs/license-policy.d.ts +56 -0
  119. package/dist/components/verbs/license-policy.d.ts.map +1 -0
  120. package/dist/components/verbs/lockfile-sbom-generator.d.ts +81 -0
  121. package/dist/components/verbs/lockfile-sbom-generator.d.ts.map +1 -0
  122. package/dist/components/verbs/process-runner.d.ts +64 -0
  123. package/dist/components/verbs/process-runner.d.ts.map +1 -0
  124. package/dist/components/verbs/reproducibility.d.ts +80 -0
  125. package/dist/components/verbs/reproducibility.d.ts.map +1 -0
  126. package/dist/components/verbs/sbom-generator.d.ts +157 -0
  127. package/dist/components/verbs/sbom-generator.d.ts.map +1 -0
  128. package/dist/components/verbs/sbom.d.ts +79 -0
  129. package/dist/components/verbs/sbom.d.ts.map +1 -0
  130. package/dist/components/verbs/shell.d.ts +35 -0
  131. package/dist/components/verbs/shell.d.ts.map +1 -0
  132. package/dist/components/verbs/sign.d.ts +222 -0
  133. package/dist/components/verbs/sign.d.ts.map +1 -0
  134. package/dist/components/verbs/stub.d.ts +18 -0
  135. package/dist/components/verbs/stub.d.ts.map +1 -0
  136. package/dist/components/verbs/tool-sbom-generator.d.ts +51 -0
  137. package/dist/components/verbs/tool-sbom-generator.d.ts.map +1 -0
  138. package/dist/components/verbs/verify.d.ts +106 -0
  139. package/dist/components/verbs/verify.d.ts.map +1 -0
  140. package/dist/components/verbs/vex.d.ts +65 -0
  141. package/dist/components/verbs/vex.d.ts.map +1 -0
  142. package/dist/components/verbs/vuln-gate.d.ts +91 -0
  143. package/dist/components/verbs/vuln-gate.d.ts.map +1 -0
  144. package/dist/components/verbs/vuln-scan.d.ts +116 -0
  145. package/dist/components/verbs/vuln-scan.d.ts.map +1 -0
  146. package/dist/components/verbs/wait-verify.d.ts +85 -0
  147. package/dist/components/verbs/wait-verify.d.ts.map +1 -0
  148. package/dist/config.d.ts +205 -0
  149. package/dist/config.d.ts.map +1 -1
  150. package/dist/index.d.ts +4 -0
  151. package/dist/index.d.ts.map +1 -1
  152. package/dist/lexicon.d.ts +78 -0
  153. package/dist/lexicon.d.ts.map +1 -1
  154. package/dist/lifecycle/build-ledger-store.d.ts +121 -0
  155. package/dist/lifecycle/build-ledger-store.d.ts.map +1 -0
  156. package/dist/lifecycle/build-ledger.d.ts +225 -0
  157. package/dist/lifecycle/build-ledger.d.ts.map +1 -0
  158. package/dist/lifecycle/git.d.ts +84 -0
  159. package/dist/lifecycle/git.d.ts.map +1 -1
  160. package/dist/lifecycle/index.d.ts +5 -0
  161. package/dist/lifecycle/index.d.ts.map +1 -1
  162. package/dist/lifecycle/oras-referrer-lookup.d.ts +41 -0
  163. package/dist/lifecycle/oras-referrer-lookup.d.ts.map +1 -0
  164. package/dist/lifecycle/release-ledger.d.ts +122 -0
  165. package/dist/lifecycle/release-ledger.d.ts.map +1 -0
  166. package/dist/lifecycle/status.d.ts +150 -0
  167. package/dist/lifecycle/status.d.ts.map +1 -0
  168. package/dist/lint/component-checks.d.ts +95 -0
  169. package/dist/lint/component-checks.d.ts.map +1 -0
  170. package/dist/lint/engine.d.ts +23 -0
  171. package/dist/lint/engine.d.ts.map +1 -1
  172. package/dist/lint/presets/strict.json +8 -1
  173. package/dist/lint/rules/__fixtures__/comp/comp000-discovery-error/broken-a.component.d.ts +4 -0
  174. package/dist/lint/rules/__fixtures__/comp/comp000-discovery-error/broken-a.component.d.ts.map +1 -0
  175. package/dist/lint/rules/__fixtures__/comp/comp000-discovery-error/broken-b.component.d.ts +4 -0
  176. package/dist/lint/rules/__fixtures__/comp/comp000-discovery-error/broken-b.component.d.ts.map +1 -0
  177. package/dist/lint/rules/__fixtures__/comp/comp001/fail/orphan-lib.component.d.ts +4 -0
  178. package/dist/lint/rules/__fixtures__/comp/comp001/fail/orphan-lib.component.d.ts.map +1 -0
  179. package/dist/lint/rules/__fixtures__/comp/comp001/pass/emr-job.component.d.ts +4 -0
  180. package/dist/lint/rules/__fixtures__/comp/comp001/pass/emr-job.component.d.ts.map +1 -0
  181. package/dist/lint/rules/__fixtures__/comp/comp001/pass/jar-lib.component.d.ts +4 -0
  182. package/dist/lint/rules/__fixtures__/comp/comp001/pass/jar-lib.component.d.ts.map +1 -0
  183. package/dist/lint/rules/__fixtures__/comp/comp002/fail/search-service.component.d.ts +10 -0
  184. package/dist/lint/rules/__fixtures__/comp/comp002/fail/search-service.component.d.ts.map +1 -0
  185. package/dist/lint/rules/__fixtures__/comp/comp002/pass/jar-lib.component.d.ts +3 -0
  186. package/dist/lint/rules/__fixtures__/comp/comp002/pass/jar-lib.component.d.ts.map +1 -0
  187. package/dist/lint/rules/__fixtures__/comp/comp002/pass/search-service.component.d.ts +4 -0
  188. package/dist/lint/rules/__fixtures__/comp/comp002/pass/search-service.component.d.ts.map +1 -0
  189. package/dist/lint/rules/__fixtures__/comp/comp003/fail/migrator.component.d.ts +4 -0
  190. package/dist/lint/rules/__fixtures__/comp/comp003/fail/migrator.component.d.ts.map +1 -0
  191. package/dist/lint/rules/__fixtures__/comp/comp003/pass/migrator.component.d.ts +4 -0
  192. package/dist/lint/rules/__fixtures__/comp/comp003/pass/migrator.component.d.ts.map +1 -0
  193. package/dist/lint/rules/__fixtures__/comp/comp003/pass/orders-table.component.d.ts +4 -0
  194. package/dist/lint/rules/__fixtures__/comp/comp003/pass/orders-table.component.d.ts.map +1 -0
  195. package/dist/lint/rules/__fixtures__/comp/comp004/fail/neo4j-cluster.component.d.ts +4 -0
  196. package/dist/lint/rules/__fixtures__/comp/comp004/fail/neo4j-cluster.component.d.ts.map +1 -0
  197. package/dist/lint/rules/__fixtures__/comp/comp004/pass/neo4j-cluster.component.d.ts +4 -0
  198. package/dist/lint/rules/__fixtures__/comp/comp004/pass/neo4j-cluster.component.d.ts.map +1 -0
  199. package/dist/lint/rules/__fixtures__/comp/comp004/pass/orders-table.component.d.ts +4 -0
  200. package/dist/lint/rules/__fixtures__/comp/comp004/pass/orders-table.component.d.ts.map +1 -0
  201. package/dist/lint/rules/__fixtures__/comp/comp005/fail/search-service.component.d.ts +4 -0
  202. package/dist/lint/rules/__fixtures__/comp/comp005/fail/search-service.component.d.ts.map +1 -0
  203. package/dist/lint/rules/__fixtures__/comp/comp005/pass/search-service.component.d.ts +4 -0
  204. package/dist/lint/rules/__fixtures__/comp/comp005/pass/search-service.component.d.ts.map +1 -0
  205. package/dist/lint/rules/__fixtures__/comp/comp006/fail/legacy-tool.component.d.ts +4 -0
  206. package/dist/lint/rules/__fixtures__/comp/comp006/fail/legacy-tool.component.d.ts.map +1 -0
  207. package/dist/lint/rules/__fixtures__/comp/comp006/pass/legacy-tool.component.d.ts +4 -0
  208. package/dist/lint/rules/__fixtures__/comp/comp006/pass/legacy-tool.component.d.ts.map +1 -0
  209. package/dist/lint/rules/__fixtures__/comp/comp007/fail/inventory-table.component.d.ts +4 -0
  210. package/dist/lint/rules/__fixtures__/comp/comp007/fail/inventory-table.component.d.ts.map +1 -0
  211. package/dist/lint/rules/__fixtures__/comp/comp007/fail/orders-table.component.d.ts +4 -0
  212. package/dist/lint/rules/__fixtures__/comp/comp007/fail/orders-table.component.d.ts.map +1 -0
  213. package/dist/lint/rules/__fixtures__/comp/comp007/pass/orders-table.component.d.ts +3 -0
  214. package/dist/lint/rules/__fixtures__/comp/comp007/pass/orders-table.component.d.ts.map +1 -0
  215. package/dist/lint/rules/__fixtures__/comp/comp007/pass/search-service.component.d.ts +4 -0
  216. package/dist/lint/rules/__fixtures__/comp/comp007/pass/search-service.component.d.ts.map +1 -0
  217. package/dist/lint/rules/comp/comp001-publishes-never-applies.d.ts +20 -0
  218. package/dist/lint/rules/comp/comp001-publishes-never-applies.d.ts.map +1 -0
  219. package/dist/lint/rules/comp/comp002-dangling-wiring-ref.d.ts +28 -0
  220. package/dist/lint/rules/comp/comp002-dangling-wiring-ref.d.ts.map +1 -0
  221. package/dist/lint/rules/comp/comp003-mutating-no-rollback.d.ts +39 -0
  222. package/dist/lint/rules/comp/comp003-mutating-no-rollback.d.ts.map +1 -0
  223. package/dist/lint/rules/comp/comp004-gate-needs-temporal.d.ts +37 -0
  224. package/dist/lint/rules/comp/comp004-gate-needs-temporal.d.ts.map +1 -0
  225. package/dist/lint/rules/comp/comp005-capability-kind-is-noun.d.ts +43 -0
  226. package/dist/lint/rules/comp/comp005-capability-kind-is-noun.d.ts.map +1 -0
  227. package/dist/lint/rules/comp/comp006-shell-needs-reason.d.ts +22 -0
  228. package/dist/lint/rules/comp/comp006-shell-needs-reason.d.ts.map +1 -0
  229. package/dist/lint/rules/comp/comp007-composition-sprawl.d.ts +33 -0
  230. package/dist/lint/rules/comp/comp007-composition-sprawl.d.ts.map +1 -0
  231. package/dist/lint/rules/comp/index.d.ts +18 -0
  232. package/dist/lint/rules/comp/index.d.ts.map +1 -0
  233. package/dist/lint/rules/comp/support.d.ts +71 -0
  234. package/dist/lint/rules/comp/support.d.ts.map +1 -0
  235. package/dist/lsp/lexicon-providers.d.ts.map +1 -1
  236. package/dist/op/activity-registry.d.ts +11 -6
  237. package/dist/op/activity-registry.d.ts.map +1 -1
  238. package/dist/op/activity-runtime.d.ts +24 -0
  239. package/dist/op/activity-runtime.d.ts.map +1 -0
  240. package/dist/op/builders.d.ts +56 -0
  241. package/dist/op/builders.d.ts.map +1 -1
  242. package/dist/op/index.d.ts +2 -1
  243. package/dist/op/index.d.ts.map +1 -1
  244. package/dist/ownership.d.ts +20 -14
  245. package/dist/ownership.d.ts.map +1 -1
  246. package/package.json +14 -1
  247. package/src/audit/catalog.test.ts +74 -22
  248. package/src/audit/catalog.ts +74 -471
  249. package/src/audit/fetch.test.ts +112 -8
  250. package/src/audit/fetch.ts +53 -8
  251. package/src/audit/proof.ts +4 -2
  252. package/src/audit/report-html.test.ts +14 -7
  253. package/src/audit/report-model.ts +20 -9
  254. package/src/audit/report.test.ts +36 -13
  255. package/src/audit/report.ts +2 -0
  256. package/src/audit/rules-doc.test.ts +8 -6
  257. package/src/audit/rules-doc.ts +13 -5
  258. package/src/cli/commands/audit.ts +23 -18
  259. package/src/cli/commands/component-lint.test.ts +158 -0
  260. package/src/cli/commands/doctor.test.ts +116 -2
  261. package/src/cli/commands/doctor.ts +46 -0
  262. package/src/cli/commands/lexicon-rolling-upgrade.ts +198 -0
  263. package/src/cli/commands/lexicon-surface-diff.test.ts +104 -0
  264. package/src/cli/commands/lexicon-surface-diff.ts +185 -0
  265. package/src/cli/commands/lint.ts +142 -1
  266. package/src/cli/commands/pinned-upgrade.ts +204 -0
  267. package/src/cli/handlers/build.ts +58 -1
  268. package/src/cli/handlers/components.test.ts +504 -0
  269. package/src/cli/handlers/components.ts +404 -0
  270. package/src/cli/handlers/dev.ts +60 -1
  271. package/src/cli/handlers/graph.ts +47 -2
  272. package/src/cli/handlers/misc.ts +45 -1
  273. package/src/cli/handlers/run.test.ts +967 -3
  274. package/src/cli/handlers/run.ts +618 -11
  275. package/src/cli/main.test.ts +66 -0
  276. package/src/cli/main.ts +105 -2
  277. package/src/cli/registry.ts +35 -0
  278. package/src/codegen/fetch.test.ts +84 -3
  279. package/src/codegen/fetch.ts +76 -2
  280. package/src/codegen/lexicon-regen.test.ts +298 -0
  281. package/src/codegen/lexicon-regen.ts +545 -0
  282. package/src/codegen/pinned-upgrade.test.ts +399 -0
  283. package/src/codegen/pinned-upgrade.ts +423 -0
  284. package/src/codegen/rolling-upgrade.test.ts +333 -0
  285. package/src/codegen/rolling-upgrade.ts +320 -0
  286. package/src/codegen/surface-snapshot.test.ts +461 -0
  287. package/src/codegen/surface-snapshot.ts +546 -0
  288. package/src/codegen/version-bump.test.ts +47 -0
  289. package/src/codegen/version-bump.ts +39 -0
  290. package/src/components/README.md +130 -0
  291. package/src/components/SPRAWL-VALIDATION.md +181 -0
  292. package/src/components/__fixtures__/alb-ecs-service.json +51 -0
  293. package/src/components/__fixtures__/dynamodb-infra.json +32 -0
  294. package/src/components/__fixtures__/emr-job-consumer.json +26 -0
  295. package/src/components/__fixtures__/jar-lib-producer.json +16 -0
  296. package/src/components/__fixtures__/lambda-image-processor.json +28 -0
  297. package/src/components/__fixtures__/neo4j-fanout.json +34 -0
  298. package/src/components/__fixtures__/single-host-compose.json +29 -0
  299. package/src/components/auto-release.test.ts +217 -0
  300. package/src/components/auto-release.ts +216 -0
  301. package/src/components/builders.test.ts +45 -0
  302. package/src/components/builders.ts +71 -0
  303. package/src/components/capability-plugin-loader.ts +218 -0
  304. package/src/components/capability-plugin-schema.ts +56 -0
  305. package/src/components/capability-plugin.test.ts +235 -0
  306. package/src/components/capability-plugin.ts +108 -0
  307. package/src/components/capability.ts +117 -0
  308. package/src/components/cli-support.test.ts +579 -0
  309. package/src/components/cli-support.ts +481 -0
  310. package/src/components/component-schema.test.ts +265 -0
  311. package/src/components/component.schema.json +235 -0
  312. package/src/components/component.test.ts +251 -0
  313. package/src/components/component.ts +255 -0
  314. package/src/components/config-defaults.test.ts +263 -0
  315. package/src/components/config-defaults.ts +145 -0
  316. package/src/components/discover.test.ts +352 -0
  317. package/src/components/discover.ts +167 -0
  318. package/src/components/driver-output.ts +71 -0
  319. package/src/components/driver.test.ts +638 -0
  320. package/src/components/driver.ts +690 -0
  321. package/src/components/index.ts +113 -0
  322. package/src/components/manifest-persistence.test.ts +152 -0
  323. package/src/components/manifest-persistence.ts +164 -0
  324. package/src/components/pilots/README.md +33 -0
  325. package/src/components/pilots/alb-ecs.pilot.ts +63 -0
  326. package/src/components/pilots/dynamodb.pilot.ts +59 -0
  327. package/src/components/pilots/jar-emr.pilot.ts +69 -0
  328. package/src/components/pilots/lambda.pilot.ts +55 -0
  329. package/src/components/pilots/neo4j-fanout.pilot.ts +92 -0
  330. package/src/components/pilots/pilots-lint-clean.test.ts +78 -0
  331. package/src/components/pilots/pilots.test.ts +126 -0
  332. package/src/components/pilots/project.ts +13 -0
  333. package/src/components/presets/comp007-reduction.test.ts +122 -0
  334. package/src/components/presets/ecs-fargate.test.ts +84 -0
  335. package/src/components/presets/ecs-fargate.ts +103 -0
  336. package/src/components/presets/index.ts +32 -0
  337. package/src/components/presets/lambda.test.ts +35 -0
  338. package/src/components/presets/lambda.ts +53 -0
  339. package/src/components/presets/single-host-compose.test.ts +65 -0
  340. package/src/components/presets/single-host-compose.ts +64 -0
  341. package/src/components/registry.test.ts +112 -0
  342. package/src/components/registry.ts +37 -0
  343. package/src/components/starter-plugin.ts +105 -0
  344. package/src/components/temporal-codegen-loader.ts +41 -0
  345. package/src/components/verbs/__fixtures__/package-lock.json +45 -0
  346. package/src/components/verbs/__fixtures__/pom.xml +37 -0
  347. package/src/components/verbs/__fixtures__/schemas/cyclonedx-1.5.schema.json +3800 -0
  348. package/src/components/verbs/__fixtures__/schemas/cyclonedx-spdx-license.schema.json +621 -0
  349. package/src/components/verbs/__fixtures__/schemas/jsf-0.82.schema.json +240 -0
  350. package/src/components/verbs/__fixtures__/schemas/spdx-2.3.schema.json +740 -0
  351. package/src/components/verbs/__tests__/mock-cloud-executor.ts +102 -0
  352. package/src/components/verbs/__tests__/mock-process-runner.ts +79 -0
  353. package/src/components/verbs/__tests__/mock-sbom-generator.ts +85 -0
  354. package/src/components/verbs/bom-writer.test.ts +331 -0
  355. package/src/components/verbs/bom-writer.ts +386 -0
  356. package/src/components/verbs/build-archive.test.ts +251 -0
  357. package/src/components/verbs/build-archive.ts +296 -0
  358. package/src/components/verbs/build.test.ts +222 -0
  359. package/src/components/verbs/build.ts +252 -0
  360. package/src/components/verbs/cloud-executor.ts +181 -0
  361. package/src/components/verbs/component-bom.test.ts +219 -0
  362. package/src/components/verbs/component-bom.ts +189 -0
  363. package/src/components/verbs/index.ts +49 -0
  364. package/src/components/verbs/license-policy.ts +115 -0
  365. package/src/components/verbs/lockfile-sbom-generator.test.ts +195 -0
  366. package/src/components/verbs/lockfile-sbom-generator.ts +236 -0
  367. package/src/components/verbs/process-runner.test.ts +79 -0
  368. package/src/components/verbs/process-runner.ts +113 -0
  369. package/src/components/verbs/reproducibility.test.ts +24 -0
  370. package/src/components/verbs/reproducibility.ts +100 -0
  371. package/src/components/verbs/sbom-generator.test.ts +96 -0
  372. package/src/components/verbs/sbom-generator.ts +209 -0
  373. package/src/components/verbs/sbom.test.ts +184 -0
  374. package/src/components/verbs/sbom.ts +152 -0
  375. package/src/components/verbs/shell.test.ts +36 -0
  376. package/src/components/verbs/shell.ts +51 -0
  377. package/src/components/verbs/sign.test.ts +210 -0
  378. package/src/components/verbs/sign.ts +350 -0
  379. package/src/components/verbs/stub.ts +34 -0
  380. package/src/components/verbs/tool-sbom-generator.test.ts +194 -0
  381. package/src/components/verbs/tool-sbom-generator.ts +192 -0
  382. package/src/components/verbs/verify.test.ts +159 -0
  383. package/src/components/verbs/verify.ts +179 -0
  384. package/src/components/verbs/vex.ts +182 -0
  385. package/src/components/verbs/vuln-gate.test.ts +201 -0
  386. package/src/components/verbs/vuln-gate.ts +170 -0
  387. package/src/components/verbs/vuln-scan.test.ts +145 -0
  388. package/src/components/verbs/vuln-scan.ts +266 -0
  389. package/src/components/verbs/wait-verify.test.ts +111 -0
  390. package/src/components/verbs/wait-verify.ts +171 -0
  391. package/src/config.test.ts +74 -1
  392. package/src/config.ts +219 -0
  393. package/src/index.ts +32 -0
  394. package/src/lexicon.ts +88 -0
  395. package/src/lifecycle/build-ledger-store.test.ts +217 -0
  396. package/src/lifecycle/build-ledger-store.ts +165 -0
  397. package/src/lifecycle/build-ledger.test.ts +349 -0
  398. package/src/lifecycle/build-ledger.ts +305 -0
  399. package/src/lifecycle/git.test.ts +118 -0
  400. package/src/lifecycle/git.ts +149 -14
  401. package/src/lifecycle/index.ts +5 -0
  402. package/src/lifecycle/oras-referrer-lookup.test.ts +116 -0
  403. package/src/lifecycle/oras-referrer-lookup.ts +110 -0
  404. package/src/lifecycle/release-ledger.test.ts +209 -0
  405. package/src/lifecycle/release-ledger.ts +185 -0
  406. package/src/lifecycle/status.test.ts +330 -0
  407. package/src/lifecycle/status.ts +278 -0
  408. package/src/lint/component-checks.ts +148 -0
  409. package/src/lint/config.test.ts +21 -0
  410. package/src/lint/engine.ts +11 -2
  411. package/src/lint/presets/relaxed.json +8 -1
  412. package/src/lint/presets/strict.json +8 -1
  413. package/src/lint/rules/__fixtures__/comp/comp000-discovery-error/broken-a.component.ts +9 -0
  414. package/src/lint/rules/__fixtures__/comp/comp000-discovery-error/broken-b.component.ts +9 -0
  415. package/src/lint/rules/__fixtures__/comp/comp001/fail/orphan-lib.component.ts +11 -0
  416. package/src/lint/rules/__fixtures__/comp/comp001/pass/emr-job.component.ts +13 -0
  417. package/src/lint/rules/__fixtures__/comp/comp001/pass/jar-lib.component.ts +11 -0
  418. package/src/lint/rules/__fixtures__/comp/comp002/fail/search-service.component.ts +27 -0
  419. package/src/lint/rules/__fixtures__/comp/comp002/pass/jar-lib.component.ts +10 -0
  420. package/src/lint/rules/__fixtures__/comp/comp002/pass/search-service.component.ts +23 -0
  421. package/src/lint/rules/__fixtures__/comp/comp003/fail/migrator.component.ts +10 -0
  422. package/src/lint/rules/__fixtures__/comp/comp003/pass/migrator.component.ts +19 -0
  423. package/src/lint/rules/__fixtures__/comp/comp003/pass/orders-table.component.ts +10 -0
  424. package/src/lint/rules/__fixtures__/comp/comp004/fail/neo4j-cluster.component.ts +20 -0
  425. package/src/lint/rules/__fixtures__/comp/comp004/pass/neo4j-cluster.component.ts +23 -0
  426. package/src/lint/rules/__fixtures__/comp/comp004/pass/orders-table.component.ts +10 -0
  427. package/src/lint/rules/__fixtures__/comp/comp005/fail/search-service.component.ts +14 -0
  428. package/src/lint/rules/__fixtures__/comp/comp005/pass/search-service.component.ts +18 -0
  429. package/src/lint/rules/__fixtures__/comp/comp006/fail/legacy-tool.component.ts +10 -0
  430. package/src/lint/rules/__fixtures__/comp/comp006/pass/legacy-tool.component.ts +14 -0
  431. package/src/lint/rules/__fixtures__/comp/comp007/fail/inventory-table.component.ts +13 -0
  432. package/src/lint/rules/__fixtures__/comp/comp007/fail/orders-table.component.ts +13 -0
  433. package/src/lint/rules/__fixtures__/comp/comp007/pass/orders-table.component.ts +12 -0
  434. package/src/lint/rules/__fixtures__/comp/comp007/pass/search-service.component.ts +18 -0
  435. package/src/lint/rules/comp/comp.test.ts +378 -0
  436. package/src/lint/rules/comp/comp001-publishes-never-applies.ts +78 -0
  437. package/src/lint/rules/comp/comp002-dangling-wiring-ref.ts +94 -0
  438. package/src/lint/rules/comp/comp003-mutating-no-rollback.ts +115 -0
  439. package/src/lint/rules/comp/comp004-gate-needs-temporal.ts +66 -0
  440. package/src/lint/rules/comp/comp005-capability-kind-is-noun.ts +113 -0
  441. package/src/lint/rules/comp/comp006-shell-needs-reason.ts +55 -0
  442. package/src/lint/rules/comp/comp007-composition-sprawl.ts +71 -0
  443. package/src/lint/rules/comp/index.ts +37 -0
  444. package/src/lint/rules/comp/support.ts +173 -0
  445. package/src/lsp/lexicon-providers.test.ts +55 -0
  446. package/src/lsp/lexicon-providers.ts +13 -3
  447. package/src/op/activity-registry.test.ts +38 -0
  448. package/src/op/activity-registry.ts +31 -12
  449. package/src/op/activity-runtime.ts +73 -0
  450. package/src/op/builders.ts +92 -0
  451. package/src/op/index.ts +3 -1
  452. package/src/ownership.test.ts +24 -26
  453. package/src/ownership.ts +26 -38
@@ -0,0 +1,209 @@
1
+ /**
2
+ * SBOM document type + the injectable, artifact-type-keyed `SbomGenerator`
3
+ * boundary (#606, epic #551 "Build & deploy observability" follow-up to
4
+ * #564/#568). Mirrors ./cloud-executor.ts's `CloudExecutor` pattern: a real
5
+ * backend may shell out to `syft`/`docker buildx --sbom`/`cyclonedx-maven`,
6
+ * but no test and no default import ever does — every backend here is
7
+ * injected, and tests construct a `MockSbomGenerator`
8
+ * (./__tests__/mock-sbom-generator.ts) instead.
9
+ *
10
+ * **Format-agnostic by construction.** An SBOM is stored as an opaque typed
11
+ * doc: `{ format, mediaType, bytes }`. Nothing downstream (the build-archive
12
+ * manifest, the build ledger, `chant components status`) branches on
13
+ * `format` — every consumer keys off `mediaType`, the same convention
14
+ * `BuildArchiveEntry.mediaType` already uses for image/template/asset
15
+ * entries (./build-archive.ts). SPDX (`application/spdx+json`) and
16
+ * CycloneDX (`application/vnd.cyclonedx+json`) are both first-class; neither
17
+ * is hardcoded as *the* format anywhere in this module or its consumers.
18
+ *
19
+ * **Default format:** SPDX, matching the epic's refinement from the
20
+ * devops-cloud.news SBOM article — BuildKit natively emits SPDX attestations
21
+ * for images, so the image/BuildKit backend defaults to SPDX; the `syft`
22
+ * backend can emit either and defaults to SPDX here too, for one predictable
23
+ * default across artifact types. Override via `chant.config.ts`'s
24
+ * `sbom.format` (see ../../config.ts) and/or a component's own `build.sbom`
25
+ * (see ../component.ts's `BuildSpec`). Documented in
26
+ * docs/components/build-archive.mdx.
27
+ *
28
+ * **Artifact-type-keyed generation.** "Generate an SBOM" has a different
29
+ * real implementation per artifact type — an image asks BuildKit or scans
30
+ * the built image with `syft`; a JAR is scanned with `syft` or
31
+ * `cyclonedx-maven`; a zip/Lambda package is scanned with `syft`; a bare
32
+ * source directory is scanned with `syft`/`cdxgen`. `SbomGenerator` is one
33
+ * interface with one method per artifact type — analogous to
34
+ * `PublishImageBackend` being one interface satisfied by two backends
35
+ * (./publish.ts) — so the *capability* (./sbom.ts) stays artifact-type
36
+ * agnostic and simply calls the method matching the archive entry it was
37
+ * handed.
38
+ */
39
+
40
+ // `lockfileSbomGenerator` (#630's hermetic default, below) lives in
41
+ // ./lockfile-sbom-generator.ts, which itself imports `DEFAULT_SBOM_FORMAT`/
42
+ // `SbomGeneratorNotImplementedError` from *this* module — a deliberate
43
+ // circular import. Safe here because the only use of `lockfileSbomGenerator`
44
+ // in this file is inside `defaultSbomGenerator()`'s function body (evaluated
45
+ // lazily, on first call), never at this module's own top-level/import time,
46
+ // so it doesn't matter which module's evaluation finishes first.
47
+ import { lockfileSbomGenerator } from "./lockfile-sbom-generator";
48
+
49
+ // ── the SBOM document ────────────────────────────────────────────────────────
50
+
51
+ /** The two SBOM standards this module supports. Never hardcode one — consumers key off `mediaType`. */
52
+ export type SbomFormat = "spdx" | "cyclonedx";
53
+
54
+ /** Canonical media type per format, mirroring `BuildArchiveEntry.mediaType`'s convention (./build-archive.ts). */
55
+ export const SBOM_MEDIA_TYPES: Record<SbomFormat, string> = {
56
+ spdx: "application/spdx+json",
57
+ cyclonedx: "application/vnd.cyclonedx+json",
58
+ };
59
+
60
+ /** Project-wide default SBOM format when neither `chant.config.ts`'s `sbom.format` nor a component's `build.sbom.format` says otherwise. Documented in docs/components/build-archive.mdx. */
61
+ export const DEFAULT_SBOM_FORMAT: SbomFormat = "spdx";
62
+
63
+ /**
64
+ * The artifact types an `SbomGenerator` backend can be asked to scan —
65
+ * mirrors the build-family's artifact shapes (`docker-build` -> `image`,
66
+ * `jvm-build` -> `jar`, `zip-package` -> `zip`, a bare source tree -> `dir`),
67
+ * never a component/cloud name.
68
+ */
69
+ export type SbomArtifactType = "image" | "jar" | "zip" | "dir";
70
+
71
+ /**
72
+ * An SBOM as an opaque typed document: format, its media type (what every
73
+ * consumer actually keys off), and the serialized bytes. Never inspected or
74
+ * re-parsed by chant itself — chant's job is to generate, store, and surface
75
+ * it, not to interpret SBOM content (that is the scan-result/VEX policy-gate
76
+ * fast-follow, out of scope here — see #606's "out of scope" section).
77
+ */
78
+ export interface SbomDocument {
79
+ /** Which standard this document is. Informational — consumers should key off `mediaType`, not this field, for anything that dispatches on the wire format. */
80
+ format: SbomFormat;
81
+ /** The document's IANA/OCI media type (`application/spdx+json` or `application/vnd.cyclonedx+json`). This is the format-agnostic key every consumer (build-archive manifest, build ledger, `chant components status`) actually reads. */
82
+ mediaType: string;
83
+ /** Serialized SBOM content (the actual SPDX/CycloneDX JSON document, as bytes/string). */
84
+ bytes: string;
85
+ /** Number of packages/components the SBOM enumerates, when the generator reports it — surfaced by `chant components status` without parsing `bytes`. */
86
+ packageCount?: number;
87
+ /** Which tool produced this document (e.g. "syft", "buildkit", "cyclonedx-maven") — surfaced alongside format/package count so `chant components status` can report SBOM *source*, not just presence. */
88
+ generator: string;
89
+ }
90
+
91
+ // ── per-artifact-type generator inputs ───────────────────────────────────────
92
+
93
+ export interface GenerateImageSbomInput {
94
+ /** Archive-relative or local path to the image tarball (OCI layout, as `docker save` produces — see ./build.ts's `DockerBuildOutput.archivePath`). */
95
+ imagePath: string;
96
+ /** Image digest the SBOM is being generated for (the join key with the build-archive manifest / build ledger). */
97
+ digest: string;
98
+ format?: SbomFormat;
99
+ }
100
+
101
+ export interface GenerateJarSbomInput {
102
+ /** Path to the built jar (see ./build.ts's `JvmBuildOutput.archivePath`). */
103
+ jarPath: string;
104
+ digest: string;
105
+ format?: SbomFormat;
106
+ }
107
+
108
+ export interface GenerateZipSbomInput {
109
+ /** Path to the built zip/Lambda package (see ./build.ts's `ZipPackageOutput.archivePath`). */
110
+ zipPath: string;
111
+ digest: string;
112
+ format?: SbomFormat;
113
+ }
114
+
115
+ export interface GenerateDirSbomInput {
116
+ /** Source directory to scan (a config-only/producer component with no packaged artifact, or a pre-build source scan). */
117
+ path: string;
118
+ format?: SbomFormat;
119
+ }
120
+
121
+ /**
122
+ * Injectable, artifact-type-keyed SBOM generation boundary — the SBOM-side
123
+ * analogue of `CloudExecutor` (./cloud-executor.ts). A real implementation
124
+ * shells out (BuildKit `docker buildx build --sbom=true`/`syft <target>
125
+ * -o spdx-json`/`cyclonedx-maven`/`cdxgen`); every method here is injected so
126
+ * tests substitute `MockSbomGenerator`
127
+ * (./__tests__/mock-sbom-generator.ts) and never invoke a real tool or the
128
+ * network.
129
+ */
130
+ export interface SbomGenerator {
131
+ /** Generate (or attest) an SBOM for a built container image. A real implementation may prefer BuildKit's native `--sbom` attestation (defaults to SPDX, per the epic's refinement) or fall back to a `syft` scan of the saved tarball. */
132
+ forImage(input: GenerateImageSbomInput): Promise<SbomDocument>;
133
+ /** Generate an SBOM for a built JVM jar (`syft`/`cyclonedx-maven`). */
134
+ forJar(input: GenerateJarSbomInput): Promise<SbomDocument>;
135
+ /** Generate an SBOM for a built zip/Lambda deployment package (`syft` on the archive). */
136
+ forZip(input: GenerateZipSbomInput): Promise<SbomDocument>;
137
+ /** Generate an SBOM for a bare source directory (`syft`/`cdxgen`). */
138
+ forDir(input: GenerateDirSbomInput): Promise<SbomDocument>;
139
+ }
140
+
141
+ /**
142
+ * Thrown by `NotImplementedSbomGenerator` — the "no real backend wired yet"
143
+ * signal, matching `CapabilityNotImplementedError`'s role for verb stubs
144
+ * (../capability.ts). Kept distinct from a generic `Error` so callers (and
145
+ * tests) can assert on it specifically.
146
+ */
147
+ export class SbomGeneratorNotImplementedError extends Error {
148
+ constructor(public readonly method: string) {
149
+ super(
150
+ `SbomGenerator.${method} has no real implementation configured — inject a real generator ` +
151
+ `(shelling out to syft/buildx/cyclonedx-maven/cdxgen) or use a mock in tests`,
152
+ );
153
+ this.name = "SbomGeneratorNotImplementedError";
154
+ }
155
+ }
156
+
157
+ /**
158
+ * All-throwing `SbomGenerator`: every method throws
159
+ * `SbomGeneratorNotImplementedError`. No longer the process-wide default (see
160
+ * `defaultSbomGenerator` below, #630) — kept exported for callers that
161
+ * deliberately want the loud, no-backend-at-all behavior (e.g. a test
162
+ * asserting on the thrown error itself, or a caller that wants to force
163
+ * "nothing is wired" rather than silently getting the hermetic backend).
164
+ */
165
+ export const notImplementedSbomGenerator: SbomGenerator = {
166
+ async forImage(): Promise<SbomDocument> {
167
+ throw new SbomGeneratorNotImplementedError("forImage");
168
+ },
169
+ async forJar(): Promise<SbomDocument> {
170
+ throw new SbomGeneratorNotImplementedError("forJar");
171
+ },
172
+ async forZip(): Promise<SbomDocument> {
173
+ throw new SbomGeneratorNotImplementedError("forZip");
174
+ },
175
+ async forDir(): Promise<SbomDocument> {
176
+ throw new SbomGeneratorNotImplementedError("forDir");
177
+ },
178
+ };
179
+
180
+ /** Lazily-constructed process-wide default, mirroring ./cloud-executor.ts's `defaultCloudExecutor`. */
181
+ let defaultGenerator: SbomGenerator | undefined;
182
+
183
+ /**
184
+ * The default `SbomGenerator` the `generate-sbom` capability falls back to
185
+ * when none is supplied.
186
+ *
187
+ * **Hermetic by default (#630).** This is `./lockfile-sbom-generator.ts`'s
188
+ * `lockfileSbomGenerator` — `forDir`/`forZip`/`forJar` parse a
189
+ * `package-lock.json`/`pom.xml` already on disk (no `syft`, no network, no
190
+ * external tool) and emit a real SPDX/CycloneDX document. That means a
191
+ * newcomer composing `generate-sbom` over a source directory, a zip/Lambda
192
+ * package, or a JAR gets a working SBOM the moment they `chant run
193
+ * --components`, with nothing to install first — the whole point of #630's
194
+ * "runnable proof" bar. `forImage` still throws
195
+ * `SbomGeneratorNotImplementedError`: no hermetic backend can see an image's
196
+ * base layers, so an image SBOM still needs the real deep-scan tool backend
197
+ * (`syft`/`docker buildx --sbom`/`cyclonedx-maven`/`cdxgen`, #610) — a
198
+ * documented, deliberate gap, not an oversight. This was previously
199
+ * `notImplementedSbomGenerator` (every method throwing); switching the
200
+ * default is intentional per #630 — the prior "loud failure by default"
201
+ * traded newcomer runnability for a default nobody exercised without opting
202
+ * in, which #613 already built a hermetic backend to fix but never wired up.
203
+ * A project that truly wants the old all-throwing behavior can construct
204
+ * `createGenerateSbomCapability(notImplementedSbomGenerator)` explicitly.
205
+ */
206
+ export function defaultSbomGenerator(): SbomGenerator {
207
+ if (!defaultGenerator) defaultGenerator = lockfileSbomGenerator;
208
+ return defaultGenerator;
209
+ }
@@ -0,0 +1,184 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { createGenerateSbomCapability } from "./sbom";
3
+ import { createMockSbomGenerator } from "./__tests__/mock-sbom-generator";
4
+ import { addArchiveTemplate } from "./build";
5
+ import { findArchiveEntry, findSbomForSubject } from "./build-archive";
6
+ import { DEFAULT_SBOM_FORMAT, SBOM_MEDIA_TYPES } from "./sbom-generator";
7
+
8
+ const ctx = { env: "dev", component: "search-service" };
9
+
10
+ describe("generate-sbom (#606)", () => {
11
+ it("defaults to SPDX when no format is requested", async () => {
12
+ expect(DEFAULT_SBOM_FORMAT).toBe("spdx");
13
+ const mock = createMockSbomGenerator();
14
+ const capability = createGenerateSbomCapability(mock.generator);
15
+
16
+ const output = await capability.run(ctx, {
17
+ artifactType: "image",
18
+ path: "archive/search.tar",
19
+ digest: "sha256:image1",
20
+ });
21
+
22
+ expect(output.sbom.format).toBe("spdx");
23
+ expect(output.sbom.mediaType).toBe("application/spdx+json");
24
+ });
25
+
26
+ it("dispatches image artifacts to forImage (BuildKit/syft)", async () => {
27
+ const mock = createMockSbomGenerator();
28
+ const capability = createGenerateSbomCapability(mock.generator);
29
+
30
+ await capability.run(ctx, { artifactType: "image", path: "archive/search.tar", digest: "sha256:image1" });
31
+
32
+ expect(mock.calls.map((c) => c.method)).toEqual(["forImage"]);
33
+ expect(mock.calls[0]!.args).toMatchObject({ imagePath: "archive/search.tar", digest: "sha256:image1" });
34
+ });
35
+
36
+ it("dispatches jar artifacts to forJar (syft/cyclonedx-maven)", async () => {
37
+ const mock = createMockSbomGenerator();
38
+ const capability = createGenerateSbomCapability(mock.generator);
39
+
40
+ const output = await capability.run(ctx, {
41
+ artifactType: "jar",
42
+ path: "archive/lib.jar",
43
+ digest: "sha256:jar1",
44
+ format: "cyclonedx",
45
+ });
46
+
47
+ expect(mock.calls.map((c) => c.method)).toEqual(["forJar"]);
48
+ expect(output.sbom.format).toBe("cyclonedx");
49
+ expect(output.sbom.mediaType).toBe("application/vnd.cyclonedx+json");
50
+ });
51
+
52
+ it("dispatches zip artifacts to forZip (syft on the archive)", async () => {
53
+ const mock = createMockSbomGenerator();
54
+ const capability = createGenerateSbomCapability(mock.generator);
55
+
56
+ await capability.run(ctx, { artifactType: "zip", path: "archive/fn.zip", digest: "sha256:zip1" });
57
+
58
+ expect(mock.calls.map((c) => c.method)).toEqual(["forZip"]);
59
+ expect(mock.calls[0]!.args).toMatchObject({ zipPath: "archive/fn.zip" });
60
+ });
61
+
62
+ it("dispatches dir artifacts to forDir (syft/cdxgen) — no artifact digest required", async () => {
63
+ const mock = createMockSbomGenerator();
64
+ const capability = createGenerateSbomCapability(mock.generator);
65
+
66
+ const output = await capability.run(ctx, { artifactType: "dir", path: "src/" });
67
+
68
+ expect(mock.calls.map((c) => c.method)).toEqual(["forDir"]);
69
+ expect(mock.calls[0]!.args).toMatchObject({ path: "src/" });
70
+ expect(output.manifest.contents[0]!.subjectDigest).toBeUndefined();
71
+ });
72
+
73
+ it("writes an sbom-kind entry into the build-archive manifest, linked to the subject digest", async () => {
74
+ const mock = createMockSbomGenerator();
75
+ const capability = createGenerateSbomCapability(mock.generator);
76
+
77
+ const output = await capability.run(ctx, {
78
+ artifactType: "image",
79
+ path: "archive/search.tar",
80
+ digest: "sha256:image1",
81
+ });
82
+
83
+ expect(output.manifest.component).toBe("search-service");
84
+ const entry = findArchiveEntry(output.manifest, output.archivePath);
85
+ expect(entry).toMatchObject({
86
+ kind: "sbom",
87
+ digest: output.digest,
88
+ mediaType: SBOM_MEDIA_TYPES.spdx,
89
+ subjectDigest: "sha256:image1",
90
+ });
91
+ expect(findSbomForSubject(output.manifest, "sha256:image1")).toBe(entry);
92
+ });
93
+
94
+ it("defaults the archive path to <artifact path>.sbom.json", async () => {
95
+ const mock = createMockSbomGenerator();
96
+ const capability = createGenerateSbomCapability(mock.generator);
97
+ const output = await capability.run(ctx, {
98
+ artifactType: "image",
99
+ path: "archive/search.tar",
100
+ digest: "sha256:image1",
101
+ });
102
+ expect(output.archivePath).toBe("archive/search.tar.sbom.json");
103
+ });
104
+
105
+ it("accepts an explicit `into` path", async () => {
106
+ const mock = createMockSbomGenerator();
107
+ const capability = createGenerateSbomCapability(mock.generator);
108
+ const output = await capability.run(ctx, {
109
+ artifactType: "image",
110
+ path: "archive/search.tar",
111
+ digest: "sha256:image1",
112
+ into: "sbom/search.spdx.json",
113
+ });
114
+ expect(output.archivePath).toBe("sbom/search.spdx.json");
115
+ });
116
+
117
+ it("accepts an archive: wiring reference the same way publish-image's from does", async () => {
118
+ const mock = createMockSbomGenerator();
119
+ const capability = createGenerateSbomCapability(mock.generator);
120
+ await capability.run(ctx, { artifactType: "image", path: "archive:search.tar", digest: "sha256:image1" });
121
+ expect(mock.calls[0]!.args).toMatchObject({ imagePath: "search.tar" });
122
+ });
123
+
124
+ it("accumulates onto a manifest threaded through from a prior build/template step", async () => {
125
+ const mock = createMockSbomGenerator();
126
+ const capability = createGenerateSbomCapability(mock.generator);
127
+
128
+ const { manifest: templateManifest } = addArchiveTemplate({
129
+ path: "search.template.json",
130
+ content: JSON.stringify({ Resources: {} }),
131
+ });
132
+ const output = await capability.run(ctx, {
133
+ artifactType: "image",
134
+ path: "archive/search.tar",
135
+ digest: "sha256:image1",
136
+ manifest: templateManifest,
137
+ });
138
+
139
+ expect(output.manifest.contents.map((e) => e.kind).sort()).toEqual(["sbom", "template"]);
140
+ });
141
+
142
+ it("surfaces package count and generator name from the SbomDocument onto the archive entry", async () => {
143
+ const mock = createMockSbomGenerator({ packageCount: 42, generatorName: "syft-1.2.3" });
144
+ const capability = createGenerateSbomCapability(mock.generator);
145
+ const output = await capability.run(ctx, {
146
+ artifactType: "image",
147
+ path: "archive/search.tar",
148
+ digest: "sha256:image1",
149
+ });
150
+ const entry = findArchiveEntry(output.manifest, output.archivePath)!;
151
+ expect(entry.packageCount).toBe(42);
152
+ expect(entry.generator).toBe("syft-1.2.3");
153
+ });
154
+
155
+ it("two generations from identical inputs produce the same manifest digest (content-addressed)", async () => {
156
+ const mockA = createMockSbomGenerator();
157
+ const mockB = createMockSbomGenerator();
158
+ const outputA = await createGenerateSbomCapability(mockA.generator).run(ctx, {
159
+ artifactType: "image",
160
+ path: "archive/search.tar",
161
+ digest: "sha256:image1",
162
+ });
163
+ const outputB = await createGenerateSbomCapability(mockB.generator).run(ctx, {
164
+ artifactType: "image",
165
+ path: "archive/search.tar",
166
+ digest: "sha256:image1",
167
+ });
168
+ expect(outputA.digest).toBe(outputB.digest);
169
+ expect(outputA.manifest.manifestDigest).toBe(outputB.manifest.manifestDigest);
170
+ });
171
+
172
+ it("surfaces a generation failure (e.g. scanner crash) as a rejected promise (never swallowed)", async () => {
173
+ const mock = createMockSbomGenerator({ fail: true });
174
+ const capability = createGenerateSbomCapability(mock.generator);
175
+ await expect(
176
+ capability.run(ctx, { artifactType: "image", path: "archive/search.tar", digest: "sha256:image1" }),
177
+ ).rejects.toThrow(/sbom generation failed/);
178
+ });
179
+
180
+ it("declares no rollback — an already-generated, content-addressed SBOM is not itself something to undo", () => {
181
+ const capability = createGenerateSbomCapability(createMockSbomGenerator().generator);
182
+ expect(capability.rollback).toBeUndefined();
183
+ });
184
+ });
@@ -0,0 +1,152 @@
1
+ /**
2
+ * `generate-sbom` capability (#606, epic #551 follow-up to #564's build
3
+ * archive / #568's build ledger — closes the "produce" side #568 explicitly
4
+ * deferred).
5
+ *
6
+ * One capability, artifact-type-keyed via the injectable `SbomGenerator`
7
+ * (./sbom-generator.ts — mirrors ./cloud-executor.ts's `CloudExecutor`
8
+ * pattern), so a build-producing component of *any* artifact type — image,
9
+ * JAR, zip/Lambda package, or a bare source directory for a config-only
10
+ * producer — generates its SBOM through the same verb. Never docker-only:
11
+ * dispatch is on `input.artifactType`, not on which build capability
12
+ * produced the artifact.
13
+ *
14
+ * Writes the result into the build-archive manifest as an `sbom`-kind entry
15
+ * (../verbs/build-archive.ts), content-addressed and linked to its subject
16
+ * artifact's digest via `subjectDigest` — the **universal home** for the
17
+ * SBOM regardless of artifact type or registry availability. Registering the
18
+ * SBOM as an OCI referrer for image artifacts at publish time is a separate,
19
+ * later concern (the publish step/registry tooling's job, see
20
+ * ../../lifecycle/build-ledger.ts's `ReferrerLookup` for the consume side);
21
+ * this capability only ever writes the archive-carried copy, so it works
22
+ * unconditionally — including the registry-less `load-image-on-host` path.
23
+ *
24
+ * Config-only / infra components have no built artifact and simply never
25
+ * compose this step into their `deploy`/`build` phase — "skips cleanly" is
26
+ * structural (no step to run), not a special case this module needs to
27
+ * detect. Opt-out is the same: a component that wants no SBOM omits the
28
+ * `generate-sbom` step from its composition.
29
+ */
30
+
31
+ import type { Capability } from "../capability";
32
+ import {
33
+ addArchiveEntry,
34
+ archiveRelativePath,
35
+ contentDigest,
36
+ createBuildArchiveManifest,
37
+ type BuildArchiveManifest,
38
+ } from "./build-archive";
39
+ import {
40
+ defaultSbomGenerator,
41
+ DEFAULT_SBOM_FORMAT,
42
+ type SbomArtifactType,
43
+ type SbomDocument,
44
+ type SbomFormat,
45
+ type SbomGenerator,
46
+ } from "./sbom-generator";
47
+
48
+ // ── generate-sbom ────────────────────────────────────────────────────────────
49
+
50
+ export interface GenerateSbomInput {
51
+ /** Which artifact type to generate an SBOM for — selects the `SbomGenerator` method dispatched to (image -> BuildKit/syft, jar -> syft/cyclonedx-maven, zip -> syft, dir -> syft/cdxgen). */
52
+ artifactType: SbomArtifactType;
53
+ /** Archive-relative (or local, for `dir`) path to the artifact being scanned — the archive entry's `path` for `image`/`jar`/`zip`, or a source directory for `dir`. An `archive:`-prefixed reference is accepted and stripped, matching `publish-image`'s `from` convention. */
54
+ path: string;
55
+ /** Digest of the artifact this SBOM describes (the `image`/`asset` archive entry's `digest`) — omitted for `dir` scans that precede any build (no artifact digest exists yet), in which case the SBOM entry's `subjectDigest` is left unset. */
56
+ digest?: string;
57
+ /** SBOM format to request. Defaults to `chant.config.ts`'s `sbom.format`, then `DEFAULT_SBOM_FORMAT` (SPDX) when neither this input nor project config says otherwise — see ./sbom-generator.ts. */
58
+ format?: SbomFormat;
59
+ /** Where the SBOM document is written inside the build archive (e.g. `"search.sbom.json"`). Defaults to `<path>.sbom.json`. */
60
+ into?: string;
61
+ /** Manifest to extend with this SBOM's entry, rather than starting a fresh one — the same accumulation convention `docker-build`'s `manifest` input uses (./build.ts), so a component's whole build phase (image + SBOM, or JAR + SBOM) shares one manifest. */
62
+ manifest?: BuildArchiveManifest;
63
+ }
64
+
65
+ export interface GenerateSbomOutput {
66
+ /** The generated SBOM document (format, media type, bytes, package count, generator) — see ./sbom-generator.ts. */
67
+ sbom: SbomDocument;
68
+ /** Where the SBOM was written inside the build archive. */
69
+ archivePath: string;
70
+ /** Content-addressed digest of the SBOM document's own bytes. */
71
+ digest: string;
72
+ /** The build archive's manifest, now including this SBOM's entry alongside whatever `input.manifest` already held. */
73
+ manifest: BuildArchiveManifest;
74
+ }
75
+
76
+ /** Default archive path for a generated SBOM when `into` is omitted: `<artifact path>.sbom.json`. */
77
+ function defaultSbomPath(artifactPath: string): string {
78
+ return `${archiveRelativePath(artifactPath)}.sbom.json`;
79
+ }
80
+
81
+ /**
82
+ * Dispatch one `SbomGenerator` call by `artifactType` — the only place this
83
+ * capability branches on artifact type, matching how `selectPublishBackend`
84
+ * (./publish.ts) is the only place the publish family branches on backend
85
+ * kind. Never a component/cloud name, always an artifact shape.
86
+ */
87
+ async function generate(
88
+ generator: SbomGenerator,
89
+ input: GenerateSbomInput,
90
+ artifactPath: string,
91
+ ): Promise<SbomDocument> {
92
+ switch (input.artifactType) {
93
+ case "image":
94
+ return generator.forImage({ imagePath: artifactPath, digest: input.digest ?? "", format: input.format });
95
+ case "jar":
96
+ return generator.forJar({ jarPath: artifactPath, digest: input.digest ?? "", format: input.format });
97
+ case "zip":
98
+ return generator.forZip({ zipPath: artifactPath, digest: input.digest ?? "", format: input.format });
99
+ case "dir":
100
+ return generator.forDir({ path: artifactPath, format: input.format });
101
+ }
102
+ }
103
+
104
+ /**
105
+ * Generate an SBOM for a build-producing component's artifact (image, JAR,
106
+ * zip, or source directory) via the artifact-type-keyed `SbomGenerator`, and
107
+ * fold the result into the build-archive manifest as an `sbom`-kind entry
108
+ * linked to the subject artifact's digest. Format defaults to
109
+ * `DEFAULT_SBOM_FORMAT` (SPDX) when neither `input.format` nor the injected
110
+ * generator overrides it — see ./sbom-generator.ts's module doc for the
111
+ * full default-resolution story. `chant.config.ts`'s `sbom.format` (see
112
+ * ../../config.ts's `ChantConfig.sbom` and `resolveSbomFormat`) is resolved
113
+ * by the caller/orchestrator into `input.format` before this capability
114
+ * runs, the same way env config is resolved before any other capability's
115
+ * `run` (this module never imports ../../config.ts directly, matching how
116
+ * no other verb module does either).
117
+ *
118
+ * No rollback: an already-generated SBOM sitting in the archive (or, later,
119
+ * projected as a registry referrer) is immutable, content-addressed
120
+ * evidence — nothing to compensate, the same opt-out `docker-build` and
121
+ * `publish-image` already take for their own no-mutable-remote-state reason.
122
+ */
123
+ export function createGenerateSbomCapability(
124
+ generator: SbomGenerator = defaultSbomGenerator(),
125
+ ): Capability<GenerateSbomInput, GenerateSbomOutput> {
126
+ return {
127
+ kind: "generate-sbom",
128
+ async run(ctx, input) {
129
+ const artifactPath = archiveRelativePath(input.path);
130
+ const format = input.format ?? DEFAULT_SBOM_FORMAT;
131
+ const sbom = await generate(generator, { ...input, format }, artifactPath);
132
+
133
+ const into = input.into ?? defaultSbomPath(input.path);
134
+ const digest = contentDigest(sbom.bytes);
135
+ const base = input.manifest ?? createBuildArchiveManifest(ctx.component);
136
+ const manifest = addArchiveEntry(base, {
137
+ kind: "sbom",
138
+ path: into,
139
+ digest,
140
+ mediaType: sbom.mediaType,
141
+ subjectDigest: input.digest,
142
+ packageCount: sbom.packageCount,
143
+ generator: sbom.generator,
144
+ });
145
+
146
+ return { sbom, archivePath: into, digest, manifest };
147
+ },
148
+ };
149
+ }
150
+
151
+ /** Default `generate-sbom` capability, backed by the process-wide default `SbomGenerator` (throws until a real backend is injected — see ./sbom-generator.ts's `notImplementedSbomGenerator`). */
152
+ export const generateSbomCapability: Capability<GenerateSbomInput, GenerateSbomOutput> = createGenerateSbomCapability();
@@ -0,0 +1,36 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { createShellCapability } from "./shell";
3
+ import { createMockProcessRunner } from "./__tests__/mock-process-runner";
4
+
5
+ const ctx = { env: "dev", component: "svc" };
6
+
7
+ describe("shell (#557)", () => {
8
+ it("runs the command and returns its stdout with exit 0", async () => {
9
+ const mock = createMockProcessRunner({ responses: { "echo hi": "hi\n" } });
10
+ const out = await createShellCapability(mock.runner).run(ctx, { cmd: "echo hi", reason: "demo" });
11
+ expect(out).toEqual({ stdout: "hi\n", exitCode: 0 });
12
+ expect(mock.calls.at(-1)?.command).toBe("echo hi");
13
+ });
14
+
15
+ it("prefixes env vars and passes cwd through", async () => {
16
+ const mock = createMockProcessRunner();
17
+ await createShellCapability(mock.runner).run(ctx, {
18
+ cmd: "deploy.sh",
19
+ cwd: "/app",
20
+ env: { FOO: "bar" },
21
+ reason: "vendor CLI not yet wrapped by a capability",
22
+ });
23
+ const call = mock.calls.at(-1)!;
24
+ expect(call.command).toBe("env FOO='bar' deploy.sh");
25
+ expect(call.options?.cwd).toBe("/app");
26
+ });
27
+
28
+ it("rejects when the command exits non-zero (surfaces the tool's error)", async () => {
29
+ const mock = createMockProcessRunner({ failures: { "bad-cmd": "boom" } });
30
+ await expect(createShellCapability(mock.runner).run(ctx, { cmd: "bad-cmd", reason: "x" })).rejects.toThrow("boom");
31
+ });
32
+
33
+ it("declares no rollback — an escape-hatch shell owns its own compensation", () => {
34
+ expect(createShellCapability(createMockProcessRunner().runner).rollback).toBeUndefined();
35
+ });
36
+ });
@@ -0,0 +1,51 @@
1
+ /**
2
+ * escape hatch family — `shell`. Typed, but flagged: a raw shell step run by
3
+ * exactly one component is the "capability-per-component" smell the model
4
+ * exists to avoid (see docs/components/capabilities.mdx#the-discipline).
5
+ * `reason` is required so lint (COMP006) can flag an escape hatch used without
6
+ * a stated justification.
7
+ */
8
+
9
+ import type { Capability } from "../capability";
10
+ import { defaultProcessRunner, q, type ProcessRunner } from "./process-runner";
11
+
12
+ export interface ShellInput {
13
+ /** Command to run. */
14
+ cmd: string;
15
+ /** Working directory. Default: process cwd. */
16
+ cwd?: string;
17
+ /** Additional environment variables. */
18
+ env?: Record<string, string>;
19
+ /** Required justification for reaching past the capability set — lint-checked. */
20
+ reason: string;
21
+ }
22
+
23
+ export interface ShellOutput {
24
+ /** Captured stdout. */
25
+ stdout: string;
26
+ /** Process exit code. Always 0 on success — a non-zero exit rejects the run. */
27
+ exitCode: number;
28
+ }
29
+
30
+ /**
31
+ * Run an arbitrary shell command through the injectable {@link ProcessRunner}.
32
+ * The escape hatch — typed, lint-flagged, and requires a `reason`. A non-zero
33
+ * exit rejects (with the command's own stderr), failing the step like any other
34
+ * capability error, so `onFailure`/rollback fires.
35
+ */
36
+ export function createShellCapability(processRunner: ProcessRunner = defaultProcessRunner()): Capability<ShellInput, ShellOutput> {
37
+ return {
38
+ kind: "shell",
39
+ async run(_ctx, input) {
40
+ const envPrefix =
41
+ input.env && Object.keys(input.env).length > 0
42
+ ? `env ${Object.entries(input.env).map(([k, v]) => `${k}=${q(v)}`).join(" ")} `
43
+ : "";
44
+ const { stdout } = await processRunner.run(`${envPrefix}${input.cmd}`, { cwd: input.cwd });
45
+ return { stdout, exitCode: 0 };
46
+ },
47
+ };
48
+ }
49
+
50
+ /** Default `shell` capability, backed by the real process runner. */
51
+ export const shellCapability: Capability<ShellInput, ShellOutput> = createShellCapability();