@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,236 @@
1
+ /**
2
+ * Hermetic lockfile/manifest-derived `SbomGenerator` (#613, epic #551 closes
3
+ * the "HERMETIC-generator half of #610" per #613's scope note). Pure
4
+ * TypeScript: reads a declared-dependency manifest already on disk (Node
5
+ * `package-lock.json`, JVM `pom.xml`) and emits a real SPDX/CycloneDX
6
+ * document via ./bom-writer.ts. No `syft`, no `docker buildx`, no
7
+ * `cyclonedx-maven`, no network — every package in the resulting BOM comes
8
+ * from parsing bytes chant already has locally.
9
+ *
10
+ * **What this backend is, and isn't.** A lockfile enumerates every declared
11
+ * (transitive-resolved) dependency at the versions actually installed, so
12
+ * the resulting SBOM is complete for **declared** dependencies — accurate
13
+ * package identity and version for everything npm/Maven resolved. It is
14
+ * **not** a container-layer scan: it cannot see OS packages baked into a base
15
+ * image, binaries copied in by a Dockerfile `COPY`/`RUN`, or anything not
16
+ * expressed as a lockfile entry. That deeper, image-layer-aware scan is
17
+ * `syft`'s job (or BuildKit's native `--sbom` attestation) and stays exactly
18
+ * where #610 already scoped it: the optional *deep* path, layered on top of
19
+ * (never replacing) this hermetic baseline. See
20
+ * docs/components/build-archive.mdx's SBOM section, updated alongside this
21
+ * change, for the customer-facing version of this distinction.
22
+ *
23
+ * Registered as `forDir`/`forZip`/`forJar` implementations (the artifact
24
+ * shapes a source-tree or packaged-but-uninspected artifact naturally maps
25
+ * to); `forImage` still throws `SbomGeneratorNotImplementedError` — an image
26
+ * SBOM needs either BuildKit's attestation or a container-layer scan, neither
27
+ * of which a lockfile read can substitute for, so this backend deliberately
28
+ * does not claim to cover it (mirrors how ./sbom-generator.ts's own
29
+ * `notImplementedSbomGenerator` prefers a loud, specific failure over a
30
+ * silently wrong answer).
31
+ */
32
+
33
+ import { readFileSync, writeFileSync, mkdirSync } from "node:fs";
34
+ import { dirname, join } from "node:path";
35
+ import { writeBom, type BomPackage } from "./bom-writer";
36
+ import { DEFAULT_SBOM_FORMAT } from "./sbom-generator";
37
+ import type {
38
+ GenerateDirSbomInput,
39
+ GenerateImageSbomInput,
40
+ GenerateJarSbomInput,
41
+ GenerateZipSbomInput,
42
+ SbomDocument,
43
+ SbomGenerator,
44
+ } from "./sbom-generator";
45
+ import { SbomGeneratorNotImplementedError } from "./sbom-generator";
46
+
47
+ // ── package-lock.json (npm, lockfile v2/v3) ─────────────────────────────────
48
+
49
+ /** Shape of the subset of `package-lock.json` this parser reads (lockfile v2/v3 — npm >= 7). */
50
+ interface NpmPackageLock {
51
+ name?: string;
52
+ version?: string;
53
+ /** Lockfile v2/v3: every resolved package, keyed by its node_modules path (""=root). */
54
+ packages?: Record<string, { name?: string; version?: string; resolved?: string; dev?: boolean; dependencies?: Record<string, string> }>;
55
+ /** Lockfile v1 fallback: nested `dependencies` tree keyed by package name. */
56
+ dependencies?: Record<string, { version?: string; resolved?: string; dev?: boolean; requires?: Record<string, string> }>;
57
+ }
58
+
59
+ /**
60
+ * Parse a `package-lock.json` (v1, v2, or v3) into a flat `BomPackage[]`.
61
+ * Prefers the v2/v3 `packages` map (flat, one entry per resolved install
62
+ * location) since that's what `npm` >= 7 writes; falls back to the v1
63
+ * `dependencies` tree for older lockfiles. De-duplicates by `name@version`
64
+ * so a package appearing at multiple install paths (npm's dedup/hoisting)
65
+ * contributes one BOM entry, matching how a real scanner reports installed
66
+ * packages rather than filesystem paths.
67
+ */
68
+ export function parseNpmPackageLock(content: string): BomPackage[] {
69
+ const lock: NpmPackageLock = JSON.parse(content);
70
+ const seen = new Map<string, BomPackage>();
71
+
72
+ const add = (name: string, version: string | undefined, deps?: string[]) => {
73
+ if (!name) return;
74
+ const key = `${name}@${version ?? ""}`;
75
+ const existing = seen.get(key);
76
+ if (existing) {
77
+ if (deps?.length) existing.dependsOn = [...new Set([...(existing.dependsOn ?? []), ...deps])];
78
+ return;
79
+ }
80
+ seen.set(key, {
81
+ name,
82
+ version,
83
+ type: "npm",
84
+ purl: version ? `pkg:npm/${encodeURIComponent(name)}@${version}` : undefined,
85
+ dependsOn: deps?.length ? deps : undefined,
86
+ });
87
+ };
88
+
89
+ if (lock.packages) {
90
+ for (const [path, pkg] of Object.entries(lock.packages)) {
91
+ if (path === "") continue; // the root project itself, not a dependency
92
+ const name = pkg.name ?? path.replace(/^.*node_modules\//, "");
93
+ add(name, pkg.version, pkg.dependencies ? Object.keys(pkg.dependencies) : undefined);
94
+ }
95
+ } else if (lock.dependencies) {
96
+ for (const [name, pkg] of Object.entries(lock.dependencies)) {
97
+ add(name, pkg.version, pkg.requires ? Object.keys(pkg.requires) : undefined);
98
+ }
99
+ }
100
+
101
+ return [...seen.values()].sort((a, b) => a.name.localeCompare(b.name));
102
+ }
103
+
104
+ // ── pom.xml (Maven — cheap, declared <dependencies> only) ───────────────────
105
+
106
+ /**
107
+ * Parse the `<dependencies>` block of a `pom.xml` into a flat `BomPackage[]`.
108
+ * Deliberately minimal (#613 calls this out as "add JVM pom.xml if cheap"):
109
+ * a small tag-scoped regex walk over `<dependency>...</dependency>` blocks
110
+ * extracting `groupId`/`artifactId`/`version`, no XML DOM dependency, no
111
+ * property (`${...}`) resolution, no transitive resolution (Maven's own
112
+ * dependency-mediation graph isn't reproducible without invoking `mvn`,
113
+ * which would break the hermetic requirement). This is deliberately narrower
114
+ * than the npm parser above: it covers the common case (a flat, literal
115
+ * `<dependencies>` list) and documents its own limits rather than silently
116
+ * mis-resolving a `${property}` version or a `<dependencyManagement>`
117
+ * inheritance chain.
118
+ */
119
+ export function parsePomXml(content: string): BomPackage[] {
120
+ const packages: BomPackage[] = [];
121
+ // Strip <dependencyManagement>...</dependencyManagement> first — it
122
+ // declares version *policy*, not necessarily artifacts actually used, and
123
+ // (like the real <dependencies> block) can itself contain a nested
124
+ // <dependencies> block, which a naive non-nesting-aware regex would match
125
+ // in its place. Removing it up front guarantees the subsequent
126
+ // <dependencies> match is the real, top-level one.
127
+ const withoutManagement = content.replace(/<dependencyManagement>[\s\S]*?<\/dependencyManagement>/, "");
128
+ const depsBlockMatch = withoutManagement.match(/<dependencies>([\s\S]*?)<\/dependencies>/);
129
+ if (!depsBlockMatch) return packages;
130
+
131
+ const depBlock = depsBlockMatch[1]!;
132
+ const depRegex = /<dependency>([\s\S]*?)<\/dependency>/g;
133
+ let match: RegExpExecArray | null;
134
+ while ((match = depRegex.exec(depBlock)) !== null) {
135
+ const entry = match[1]!;
136
+ const groupId = entry.match(/<groupId>([^<]+)<\/groupId>/)?.[1]?.trim();
137
+ const artifactId = entry.match(/<artifactId>([^<]+)<\/artifactId>/)?.[1]?.trim();
138
+ const version = entry.match(/<version>([^<]+)<\/version>/)?.[1]?.trim();
139
+ if (!groupId || !artifactId) continue;
140
+ const name = `${groupId}:${artifactId}`;
141
+ packages.push({
142
+ name,
143
+ version,
144
+ type: "maven",
145
+ purl: version ? `pkg:maven/${encodeURIComponent(groupId)}/${encodeURIComponent(artifactId)}@${version}` : undefined,
146
+ });
147
+ }
148
+ return packages.sort((a, b) => a.name.localeCompare(b.name));
149
+ }
150
+
151
+ // ── hermetic SbomGenerator backend ───────────────────────────────────────────
152
+
153
+ export interface LockfileSbomGeneratorOptions {
154
+ /** Where generated SBOM documents are written on disk, as a sibling of the scanned path (see `sbomOutputPath`). Default: alongside the scanned manifest, named `sbom.<format>.json`. */
155
+ outDir?: string;
156
+ /** Clock override for deterministic tests. */
157
+ now?: () => Date;
158
+ }
159
+
160
+ /** Locate the first readable lockfile/manifest under `dir` this generator knows how to parse, in preference order (npm before Maven — most chant components are Node-first). Returns `undefined` if none is found. */
161
+ function findManifest(dir: string): { kind: "npm" | "maven"; path: string } | undefined {
162
+ for (const [kind, filename] of [["npm", "package-lock.json"], ["maven", "pom.xml"]] as const) {
163
+ const candidate = join(dir, filename);
164
+ try {
165
+ readFileSync(candidate, "utf-8");
166
+ return { kind, path: candidate };
167
+ } catch {
168
+ continue;
169
+ }
170
+ }
171
+ return undefined;
172
+ }
173
+
174
+ /** Default on-disk path for a generated SBOM: `<dir>/sbom.<format>.json`, matching #613's `sbom.<format>.json` naming convention. */
175
+ export function sbomOutputPath(dir: string, format: string): string {
176
+ return join(dir, `sbom.${format}.json`);
177
+ }
178
+
179
+ /**
180
+ * Build a hermetic `SbomGenerator` backed by lockfile parsing. `forDir` scans
181
+ * `input.path` for a known manifest (`package-lock.json`, then `pom.xml`),
182
+ * parses it into a `BomPackage[]`, and writes both the SBOM document (via
183
+ * ./bom-writer.ts) to disk and returns it as an `SbomDocument`. `forZip`/
184
+ * `forJar` reuse the same scan against the artifact's source directory
185
+ * (the directory the archive was built from is the natural place a lockfile
186
+ * lives — the packaged bytes themselves are not re-inspected, matching this
187
+ * backend's "declared deps, not a binary scan" scope). `forImage` is not
188
+ * implemented here: an image's dependency surface includes its base layers,
189
+ * which no lockfile enumerates — see this module's doc comment.
190
+ */
191
+ export function createLockfileSbomGenerator(options: LockfileSbomGeneratorOptions = {}): SbomGenerator {
192
+ const now = options.now ?? (() => new Date());
193
+
194
+ function generateFromDir(dir: string, subjectName: string, digest?: string, format = DEFAULT_SBOM_FORMAT): SbomDocument {
195
+ const manifest = findManifest(dir);
196
+ if (!manifest) {
197
+ throw new Error(
198
+ `no supported lockfile/manifest found under "${dir}" (looked for package-lock.json, pom.xml) — ` +
199
+ `the hermetic lockfile SBOM backend requires a lockfile on disk; use the deep-scan path (#610) for images with no lockfile`,
200
+ );
201
+ }
202
+ const content = readFileSync(manifest.path, "utf-8");
203
+ const packages = manifest.kind === "npm" ? parseNpmPackageLock(content) : parsePomXml(content);
204
+ const generatorName = manifest.kind === "npm" ? "chant-lockfile-sbom/package-lock.json" : "chant-lockfile-sbom/pom.xml";
205
+
206
+ const doc = writeBom(
207
+ format,
208
+ { subjectName, subjectId: digest ?? manifest.path, packages, generator: generatorName },
209
+ now,
210
+ );
211
+
212
+ const outDir = options.outDir ?? dir;
213
+ mkdirSync(outDir, { recursive: true });
214
+ writeFileSync(sbomOutputPath(outDir, format), doc.bytes);
215
+
216
+ return { ...doc, packageCount: packages.length, generator: generatorName };
217
+ }
218
+
219
+ return {
220
+ async forImage(_input: GenerateImageSbomInput): Promise<SbomDocument> {
221
+ throw new SbomGeneratorNotImplementedError("forImage");
222
+ },
223
+ async forJar(input: GenerateJarSbomInput): Promise<SbomDocument> {
224
+ return generateFromDir(dirname(input.jarPath), input.jarPath, input.digest, input.format);
225
+ },
226
+ async forZip(input: GenerateZipSbomInput): Promise<SbomDocument> {
227
+ return generateFromDir(dirname(input.zipPath), input.zipPath, input.digest, input.format);
228
+ },
229
+ async forDir(input: GenerateDirSbomInput): Promise<SbomDocument> {
230
+ return generateFromDir(input.path, input.path, undefined, input.format);
231
+ },
232
+ };
233
+ }
234
+
235
+ /** Process-wide hermetic lockfile-backed `SbomGenerator`, ready to inject wherever ./sbom-generator.ts's `notImplementedSbomGenerator` default previously required an explicit real backend. */
236
+ export const lockfileSbomGenerator: SbomGenerator = createLockfileSbomGenerator();
@@ -0,0 +1,79 @@
1
+ /**
2
+ * Tests `q`/`requireTool` (../process-runner.ts) plus `MockProcessRunner`
3
+ * itself (./__tests__/mock-process-runner.ts) — no real process spawned. The
4
+ * real `ProcessRunner`'s `child_process`-backed methods are exercised
5
+ * indirectly by every #610 backend's own tests via the mock; this file
6
+ * covers the shared helpers every one of those backends depends on.
7
+ */
8
+
9
+ import { describe, expect, it } from "vitest";
10
+ import { q, requireTool, ToolNotAvailableError } from "./process-runner";
11
+ import { createMockProcessRunner } from "./__tests__/mock-process-runner";
12
+
13
+ describe("q (shell quoting)", () => {
14
+ it("wraps a plain argument in single quotes", () => {
15
+ expect(q("hello")).toBe("'hello'");
16
+ });
17
+
18
+ it("escapes an embedded single quote", () => {
19
+ expect(q("it's")).toBe("'it'\\''s'");
20
+ });
21
+ });
22
+
23
+ describe("requireTool", () => {
24
+ it("resolves silently when the tool is available", async () => {
25
+ const mock = createMockProcessRunner({ tools: { syft: true } });
26
+ await expect(requireTool(mock.runner, "syft", "scan an artifact")).resolves.toBeUndefined();
27
+ });
28
+
29
+ it("throws ToolNotAvailableError with an actionable message when the tool is absent", async () => {
30
+ const mock = createMockProcessRunner({ tools: { syft: false } });
31
+ await expect(requireTool(mock.runner, "syft", "scan an artifact")).rejects.toThrow(ToolNotAvailableError);
32
+ await expect(requireTool(mock.runner, "syft", "scan an artifact")).rejects.toThrow(
33
+ /syft.*not installed.*scan an artifact/,
34
+ );
35
+ });
36
+ });
37
+
38
+ describe("MockProcessRunner", () => {
39
+ it("records every run() and available() call", async () => {
40
+ const mock = createMockProcessRunner();
41
+ await mock.runner.available("syft");
42
+ await mock.runner.run("syft dir:. -o spdx-json");
43
+ expect(mock.calls.map((c) => c.command)).toEqual([
44
+ "command -v syft",
45
+ "syft dir:. -o spdx-json",
46
+ ]);
47
+ });
48
+
49
+ it("defaults every tool to available unless scripted otherwise", async () => {
50
+ const mock = createMockProcessRunner();
51
+ expect(await mock.runner.available("anything")).toBe(true);
52
+ });
53
+
54
+ it("respects defaultAvailable: false", async () => {
55
+ const mock = createMockProcessRunner({ defaultAvailable: false, tools: { syft: true } });
56
+ expect(await mock.runner.available("syft")).toBe(true);
57
+ expect(await mock.runner.available("oras")).toBe(false);
58
+ });
59
+
60
+ it("matches a scripted response by command substring", async () => {
61
+ const mock = createMockProcessRunner({ responses: { "cat ": "canned-output" } });
62
+ const { stdout } = await mock.runner.run("cat /tmp/whatever.json");
63
+ expect(stdout).toBe("canned-output");
64
+ });
65
+
66
+ it("rejects when the command matches a scripted failure substring", async () => {
67
+ const mock = createMockProcessRunner({ failures: { syft: "syft: command failed" } });
68
+ await expect(mock.runner.run("syft dir:.")).rejects.toThrow("syft: command failed");
69
+ });
70
+
71
+ it("setAvailable/setResponse update behavior after construction", async () => {
72
+ const mock = createMockProcessRunner();
73
+ mock.setAvailable("oras", false);
74
+ expect(await mock.runner.available("oras")).toBe(false);
75
+ mock.setResponse("oras discover", '{"manifests":[]}');
76
+ const { stdout } = await mock.runner.run("oras discover --format json foo@sha256:abc");
77
+ expect(stdout).toBe('{"manifests":[]}');
78
+ });
79
+ });
@@ -0,0 +1,113 @@
1
+ /**
2
+ * Injectable external-process boundary for the #610 deep-scan/attach
3
+ * backends (`ToolSbomGenerator` in ./tool-sbom-generator.ts,
4
+ * `OrasReferrerLookup` in ../../lifecycle/oras-referrer-lookup.ts, and
5
+ * `publish-image`'s referrer-attach step in ./publish.ts). Mirrors
6
+ * ./cloud-executor.ts's `CloudExecutor` pattern exactly: production code gets
7
+ * a `RealProcessRunner` that shells out via `node:child_process`, tests get a
8
+ * `MockProcessRunner` (./__tests__/mock-process-runner.ts) that records calls
9
+ * and returns canned output — no live `syft`/`buildx`/`cyclonedx-maven`/
10
+ * `cdxgen`/`oras`, ever, in a test run.
11
+ *
12
+ * Kept deliberately narrower than `CloudExecutor`: every one of #610's real
13
+ * backends reduces to "run this external CLI, capture stdout, tell me if the
14
+ * tool exists" — a single `ProcessRunner` interface (`run` + `available`)
15
+ * covers `syft`, `docker buildx`, `cyclonedx-maven`, `cdxgen`, and `oras`
16
+ * alike, rather than one bespoke client per tool the way `CloudExecutor`
17
+ * models AWS's genuinely distinct service APIs (CloudFormation, ECS,
18
+ * CodeDeploy, ...).
19
+ */
20
+
21
+ import { exec } from "node:child_process";
22
+ import { promisify } from "node:util";
23
+
24
+ const execAsync = promisify(exec);
25
+
26
+ /** One external command invocation's result. */
27
+ export interface ProcessResult {
28
+ stdout: string;
29
+ stderr: string;
30
+ }
31
+
32
+ export interface RunOptions {
33
+ /** Working directory the command runs in. Defaults to the current process cwd. */
34
+ cwd?: string;
35
+ /** Max buffer size for stdout/stderr, matching ./cloud-executor.ts's `run`'s 64MB allowance (SBOM/BOM documents can be large). */
36
+ maxBuffer?: number;
37
+ }
38
+
39
+ /**
40
+ * Injectable boundary for shelling out to a deep-scan/attach CLI tool and for
41
+ * checking whether one is installed. A real implementation runs the command
42
+ * via `node:child_process`; every method here is injected so tests substitute
43
+ * `MockProcessRunner` (./__tests__/mock-process-runner.ts) and never invoke a
44
+ * real tool.
45
+ */
46
+ export interface ProcessRunner {
47
+ /** Run a shell command, returning its stdout/stderr. Rejects (with the tool's own stderr in the error) on a non-zero exit. */
48
+ run(command: string, options?: RunOptions): Promise<ProcessResult>;
49
+ /** True if `tool` (a CLI binary name, e.g. "syft", "oras") resolves on `PATH`. Never throws — a missing tool is a normal, expected outcome the caller checks before attempting to `run` it. */
50
+ available(tool: string): Promise<boolean>;
51
+ }
52
+
53
+ /** Shell-quote a single argument for POSIX shells (wrap in single quotes, escaping embedded ones) — same convention as ./cloud-executor.ts's `q`. */
54
+ export function q(value: string): string {
55
+ return `'${value.replace(/'/g, `'\\''`)}'`;
56
+ }
57
+
58
+ const realProcessRunner: ProcessRunner = {
59
+ async run(command, options) {
60
+ return execAsync(command, {
61
+ cwd: options?.cwd,
62
+ maxBuffer: options?.maxBuffer ?? 64 * 1024 * 1024,
63
+ });
64
+ },
65
+ async available(tool) {
66
+ try {
67
+ // `command -v` is POSIX-portable (works in sh/bash/zsh) and prints
68
+ // nothing to stderr for a normal miss, unlike `which` on some systems.
69
+ await execAsync(`command -v ${q(tool)}`);
70
+ return true;
71
+ } catch {
72
+ return false;
73
+ }
74
+ },
75
+ };
76
+
77
+ /** Build a `ProcessRunner` that shells out for real via `node:child_process`. Never used in tests. */
78
+ export function realProcessExecutor(): ProcessRunner {
79
+ return realProcessRunner;
80
+ }
81
+
82
+ /** Lazily-constructed process-wide default, mirroring ./cloud-executor.ts's `defaultCloudExecutor`. */
83
+ let defaultRunner: ProcessRunner | undefined;
84
+
85
+ /** The default `ProcessRunner` each real #610 backend falls back to when none is supplied. */
86
+ export function defaultProcessRunner(): ProcessRunner {
87
+ if (!defaultRunner) defaultRunner = realProcessExecutor();
88
+ return defaultRunner;
89
+ }
90
+
91
+ /**
92
+ * Thrown when a #610 real backend needs a CLI tool that `ProcessRunner.available`
93
+ * reports as absent — the "graceful, actionable error if the tool is absent"
94
+ * #610 asks for, distinct from a generic `Error` so callers (and tests) can
95
+ * assert on it specifically, mirroring `SbomGeneratorNotImplementedError`'s
96
+ * role in ./sbom-generator.ts.
97
+ */
98
+ export class ToolNotAvailableError extends Error {
99
+ constructor(
100
+ public readonly tool: string,
101
+ public readonly purpose: string,
102
+ ) {
103
+ super(`"${tool}" is not installed or not on PATH — required to ${purpose}. Install it and retry.`);
104
+ this.name = "ToolNotAvailableError";
105
+ }
106
+ }
107
+
108
+ /** Throw `ToolNotAvailableError` unless `tool` is available, per `runner.available`. Shared guard every #610 real backend calls before shelling out. */
109
+ export async function requireTool(runner: ProcessRunner, tool: string, purpose: string): Promise<void> {
110
+ if (!(await runner.available(tool))) {
111
+ throw new ToolNotAvailableError(tool, purpose);
112
+ }
113
+ }
@@ -0,0 +1,24 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { defaultReproducibility } from "./reproducibility";
3
+
4
+ describe("defaultReproducibility (#614)", () => {
5
+ it("a template (synthesized IaC) entry defaults to deterministic-synthesis, verifiable by re-synth", () => {
6
+ expect(defaultReproducibility("template")).toEqual({ basis: "deterministic-synthesis", verifyBy: "re-synth" });
7
+ });
8
+
9
+ it("an image entry defaults to best-effort, with no verification method claimed", () => {
10
+ expect(defaultReproducibility("image")).toEqual({ basis: "best-effort" });
11
+ });
12
+
13
+ it("an asset entry (jar/zip) defaults to best-effort, same as image — an externally-built artifact either way", () => {
14
+ expect(defaultReproducibility("asset")).toEqual({ basis: "best-effort" });
15
+ });
16
+
17
+ it("an sbom entry gets no reproducibility claim of its own — it describes another artifact, it isn't one", () => {
18
+ expect(defaultReproducibility("sbom")).toBeUndefined();
19
+ });
20
+
21
+ it("never gives an image the same basis claim as a template — the honest-per-artifact-type distinction #614 requires", () => {
22
+ expect(defaultReproducibility("image")!.basis).not.toBe(defaultReproducibility("template")!.basis);
23
+ });
24
+ });
@@ -0,0 +1,100 @@
1
+ /**
2
+ * Per-artifact reproducibility + provenance record (#614, epic #551 follow-up
3
+ * to #613's BOM-doc model). Attaches to a `BuildArchiveEntry`
4
+ * (./build-archive.ts) alongside the BOM entries #613 already added, so every
5
+ * artifact in a build archive carries not just "what's in it" (the BOM) but
6
+ * also "can this be reproduced, and from what source."
7
+ *
8
+ * **Honesty over convenience.** A single blanket "reproducible: true/false"
9
+ * bit on a *component* would misrepresent a component that builds more than
10
+ * one artifact type — a synthesized IaC template and a container image are
11
+ * not equally reproducible, and collapsing them into one flag would either
12
+ * overstate the image's reproducibility or understate the template's. This
13
+ * module therefore records `basis` **per artifact entry**, never per
14
+ * component:
15
+ *
16
+ * - `"deterministic-synthesis"` — chant's own synthesis produced this
17
+ * artifact from declarative input (a `template`-kind entry, see
18
+ * ../../build.ts). Re-running `chant build` against the same source
19
+ * reproduces byte-identical output — the same content-addressing property
20
+ * ./build-archive.ts's `contentDigest`/`manifestDigest` already rely on.
21
+ * Verification is exactly "re-synth and compare digests," expressed here
22
+ * as `verifyBy: "re-synth"`.
23
+ * - `"best-effort"` — an `image`/`asset` entry built by an external tool
24
+ * (`docker build`, a JVM/zip packager). Nothing here observes whether the
25
+ * underlying build was actually bit-for-bit reproducible (timestamps,
26
+ * build-arg ordering, and base-image drift are common, unglamorous causes
27
+ * of non-determinism that chant does not control) — `"best-effort"` is
28
+ * the honest default absent a real reproducible-build attestation. A
29
+ * future SLSA/reproducible-build-flagged backend can upgrade this to a
30
+ * stronger basis; nothing here forecloses that, it just refuses to claim
31
+ * it today.
32
+ *
33
+ * **Provenance** is the companion source -> output link: which source
34
+ * ref/commit produced this artifact's digest. Recorded per artifact for the
35
+ * same reason `basis` is — two artifacts from one component build may trace
36
+ * to different source trees (e.g. an image built from `services/search/` vs
37
+ * a template synthesized from `infra/search.component.ts`), so the link
38
+ * belongs on the entry, not hoisted to the component.
39
+ */
40
+
41
+ // ── reproducibility ─────────────────────────────────────────────────────────
42
+
43
+ /**
44
+ * How an artifact's reproducibility is established. Deliberately two values,
45
+ * not a spectrum: chant either produced the artifact itself via deterministic
46
+ * synthesis (verifiable by re-running `chant build`), or it didn't (an
47
+ * external build tool produced it, and no attestation proves determinism).
48
+ * Adding a third value (e.g. a real SLSA-attested "reproducible-build") is a
49
+ * later, additive change — widening `basis` never requires touching every
50
+ * existing artifact's recorded value, since `"best-effort"` remains true of
51
+ * anything not upgraded.
52
+ */
53
+ export type ReproducibilityBasis = "deterministic-synthesis" | "best-effort";
54
+
55
+ /** How a claimed `basis` can actually be checked. Optional — a `"best-effort"` entry commonly has no verification path at all. */
56
+ export type VerificationMethod = "re-synth";
57
+
58
+ /** The reproducibility record for one build-archive entry (#614). */
59
+ export interface ArtifactReproducibility {
60
+ /** Honest, per-artifact basis for the claim — never inferred from a sibling artifact's basis. */
61
+ basis: ReproducibilityBasis;
62
+ /** How to verify the claim, when there is one. `"re-synth"` applies only to `"deterministic-synthesis"` entries — re-run `chant build` and compare the resulting digest. */
63
+ verifyBy?: VerificationMethod;
64
+ }
65
+
66
+ /**
67
+ * Default reproducibility basis for a given `BuildArchiveEntryKind`, absent
68
+ * an explicit override. A `template` entry is chant's own synthesized IaC
69
+ * output — deterministic by construction (the same synthesis run over the
70
+ * same source produces the same bytes, per ./build-archive.ts's module doc).
71
+ * `image`/`asset` entries are produced by an external build tool chant only
72
+ * orchestrates (`docker build`, a JVM/zip packager) — best-effort absent a
73
+ * real reproducible-build attestation. `sbom` entries describe another
74
+ * artifact rather than being one themselves, so they carry no
75
+ * reproducibility claim of their own (`undefined`) — see
76
+ * `../build-archive.ts`'s `BuildArchiveEntry.reproducibility` doc.
77
+ */
78
+ export function defaultReproducibility(
79
+ kind: "image" | "template" | "asset" | "sbom",
80
+ ): ArtifactReproducibility | undefined {
81
+ switch (kind) {
82
+ case "template":
83
+ return { basis: "deterministic-synthesis", verifyBy: "re-synth" };
84
+ case "image":
85
+ case "asset":
86
+ return { basis: "best-effort" };
87
+ case "sbom":
88
+ return undefined;
89
+ }
90
+ }
91
+
92
+ // ── provenance ───────────────────────────────────────────────────────────────
93
+
94
+ /** One source -> output link: the source ref/commit that produced a given artifact digest. */
95
+ export interface ProvenanceLink {
96
+ /** Source ref/commit the artifact was built or synthesized from (e.g. a git sha, or `"<sha>:<path>"` when a monorepo subpath matters). */
97
+ sourceRef: string;
98
+ /** The artifact digest this provenance describes — the same `BuildArchiveEntry.digest` it is attached to, repeated here so a `ProvenanceLink` remains meaningful if ever extracted/serialized apart from its entry. */
99
+ artifactDigest: string;
100
+ }
@@ -0,0 +1,96 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { mkdtempSync, writeFileSync } from "node:fs";
3
+ import { tmpdir } from "node:os";
4
+ import { join } from "node:path";
5
+ import {
6
+ DEFAULT_SBOM_FORMAT,
7
+ SBOM_MEDIA_TYPES,
8
+ SbomGeneratorNotImplementedError,
9
+ defaultSbomGenerator,
10
+ notImplementedSbomGenerator,
11
+ } from "./sbom-generator";
12
+ import { lockfileSbomGenerator } from "./lockfile-sbom-generator";
13
+ import { createMockSbomGenerator } from "./__tests__/mock-sbom-generator";
14
+
15
+ describe("SBOM document media types (#606 — format-agnostic storage)", () => {
16
+ it("declares both SPDX and CycloneDX media types — no format is hardcoded as the only one", () => {
17
+ expect(SBOM_MEDIA_TYPES.spdx).toBe("application/spdx+json");
18
+ expect(SBOM_MEDIA_TYPES.cyclonedx).toBe("application/vnd.cyclonedx+json");
19
+ });
20
+
21
+ it("defaults to SPDX (BuildKit's native attestation format) per the epic's article-validated refinement", () => {
22
+ expect(DEFAULT_SBOM_FORMAT).toBe("spdx");
23
+ });
24
+ });
25
+
26
+ describe("notImplementedSbomGenerator (#606) — still exported, no longer the default", () => {
27
+ it("every method throws SbomGeneratorNotImplementedError, naming the method", async () => {
28
+ await expect(notImplementedSbomGenerator.forImage({ imagePath: "x", digest: "sha256:1" })).rejects.toBeInstanceOf(
29
+ SbomGeneratorNotImplementedError,
30
+ );
31
+ await expect(notImplementedSbomGenerator.forJar({ jarPath: "x", digest: "sha256:1" })).rejects.toThrow(
32
+ /forJar/,
33
+ );
34
+ await expect(notImplementedSbomGenerator.forZip({ zipPath: "x", digest: "sha256:1" })).rejects.toThrow(
35
+ /forZip/,
36
+ );
37
+ await expect(notImplementedSbomGenerator.forDir({ path: "x" })).rejects.toThrow(/forDir/);
38
+ });
39
+ });
40
+
41
+ describe("defaultSbomGenerator (#630 — hermetic by default)", () => {
42
+ it("returns the same lockfileSbomGenerator instance every call, never shelling out at import/call time", () => {
43
+ expect(defaultSbomGenerator()).toBe(lockfileSbomGenerator);
44
+ expect(defaultSbomGenerator()).toBe(defaultSbomGenerator());
45
+ });
46
+
47
+ it("forImage still throws SbomGeneratorNotImplementedError — no hermetic backend can see an image's base layers", async () => {
48
+ await expect(defaultSbomGenerator().forImage({ imagePath: "x", digest: "sha256:1" })).rejects.toBeInstanceOf(
49
+ SbomGeneratorNotImplementedError,
50
+ );
51
+ });
52
+
53
+ it("forDir generates a real SBOM tool-free from a package-lock.json on disk", async () => {
54
+ const dir = mkdtempSync(join(tmpdir(), "chant-sbom-default-"));
55
+ writeFileSync(
56
+ join(dir, "package-lock.json"),
57
+ JSON.stringify({
58
+ name: "fixture",
59
+ version: "1.0.0",
60
+ packages: { "": { name: "fixture" }, "node_modules/left-pad": { version: "1.3.0" } },
61
+ }),
62
+ );
63
+
64
+ const doc = await defaultSbomGenerator().forDir({ path: dir });
65
+ expect(doc.packageCount).toBe(1);
66
+ expect(doc.generator).toBe("chant-lockfile-sbom/package-lock.json");
67
+ expect(JSON.parse(doc.bytes)).toBeTruthy();
68
+ });
69
+ });
70
+
71
+ describe("MockSbomGenerator (test double)", () => {
72
+ it("produces a format-agnostic document whose mediaType always matches the requested/default format", async () => {
73
+ const mock = createMockSbomGenerator();
74
+ const spdxDoc = await mock.generator.forImage({ imagePath: "a.tar", digest: "sha256:1" });
75
+ expect(spdxDoc.format).toBe("spdx");
76
+ expect(spdxDoc.mediaType).toBe(SBOM_MEDIA_TYPES.spdx);
77
+
78
+ const cyclonedxDoc = await mock.generator.forJar({ jarPath: "a.jar", digest: "sha256:2", format: "cyclonedx" });
79
+ expect(cyclonedxDoc.format).toBe("cyclonedx");
80
+ expect(cyclonedxDoc.mediaType).toBe(SBOM_MEDIA_TYPES.cyclonedx);
81
+ });
82
+
83
+ it("records every call for assertion, keyed by artifact-type method", async () => {
84
+ const mock = createMockSbomGenerator();
85
+ await mock.generator.forImage({ imagePath: "a.tar", digest: "sha256:1" });
86
+ await mock.generator.forZip({ zipPath: "b.zip", digest: "sha256:2" });
87
+ expect(mock.calls.map((c) => c.method)).toEqual(["forImage", "forZip"]);
88
+ });
89
+
90
+ it("fail option surfaces a rejected promise from every method", async () => {
91
+ const mock = createMockSbomGenerator({ fail: true });
92
+ await expect(mock.generator.forImage({ imagePath: "a.tar", digest: "sha256:1" })).rejects.toThrow(
93
+ /sbom generation failed/,
94
+ );
95
+ });
96
+ });