@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,252 @@
1
+ /**
2
+ * build family — source -> BuildArchive, keyed by artifact type.
3
+ * Fully cloud-agnostic (see docs/components/cloud-boundary). `docker-build` is
4
+ * a real implementation (#557, epic #551) built over the injectable
5
+ * `CloudExecutor` (./cloud-executor.ts) so tests never shell out to a real
6
+ * `docker` daemon. `zip-package` (via `zip`) and `jvm-build` (via `mvn`/the
7
+ * gradle wrapper) are real, run through the injectable `ProcessRunner`.
8
+ *
9
+ * `docker-build` also assembles the component's `BuildArchiveManifest`
10
+ * (#564, epic #551 "4. Build archive + deferred publish" — see
11
+ * ./build-archive.ts): every build appends an `image`-kind entry recording
12
+ * the archive path and digest it just produced, so the archive is
13
+ * self-describing (content-addressed, with a manifest of contents) rather
14
+ * than a bare tarball. `addArchiveTemplate` lets a caller fold synthesized
15
+ * deploy templates (e.g. CloudFormation JSON, already written into the
16
+ * archive by chant's existing synthesis step) into the same manifest, so one
17
+ * document enumerates every content kind the archive holds.
18
+ *
19
+ * Per #614, both entry points accept an optional `sourceRef` (a git sha, or
20
+ * `"<sha>:<path>"` for a monorepo subpath) that becomes the entry's
21
+ * `provenance` link. `image`/`template` entries otherwise default to their
22
+ * kind-appropriate `reproducibility` basis via `addArchiveEntry` — see
23
+ * ./reproducibility.ts.
24
+ */
25
+
26
+ import type { Capability } from "../capability";
27
+ import { readFileSync } from "node:fs";
28
+ import { createHash } from "node:crypto";
29
+ import { defaultCloudExecutor, type CloudExecutor } from "./cloud-executor";
30
+ import { defaultProcessRunner, q, type ProcessRunner } from "./process-runner";
31
+ import {
32
+ addArchiveEntry,
33
+ contentDigest,
34
+ createBuildArchiveManifest,
35
+ type BuildArchiveManifest,
36
+ } from "./build-archive";
37
+ import type { ProvenanceLink } from "./reproducibility";
38
+
39
+ // ── docker-build ────────────────────────────────────────────────────────────
40
+
41
+ export interface DockerBuildInput {
42
+ /** Build context directory. */
43
+ context: string;
44
+ /** Path to the Dockerfile, relative to `context`. Default: "Dockerfile". */
45
+ dockerfile?: string;
46
+ /** Build args passed to `docker build --build-arg`. */
47
+ buildArgs?: Record<string, string>;
48
+ /** Target stage for a multi-stage build. */
49
+ target?: string;
50
+ /** Where the produced image tarball is written inside the build archive. */
51
+ into: string;
52
+ /**
53
+ * Manifest to extend with this build's `image` entry, rather than starting
54
+ * a fresh one. Pass the manifest returned by a prior build/template step in
55
+ * the same archive (e.g. via `@Build.manifest` wiring) to accumulate one
56
+ * manifest across a component's whole build phase. Omit to start a new
57
+ * single-entry manifest for this build alone.
58
+ */
59
+ manifest?: BuildArchiveManifest;
60
+ /** Source ref/commit this build was run against (#614) — recorded as the entry's `provenance.sourceRef`. Omit when unknown (e.g. a build run outside a git checkout); no `provenance` is recorded in that case. */
61
+ sourceRef?: string;
62
+ }
63
+
64
+ export interface DockerBuildOutput {
65
+ /** Path of the produced image tarball (OCI layout) inside the build archive. */
66
+ archivePath: string;
67
+ /** Content-addressed digest of the built image (`sha256:...`). */
68
+ digest: string;
69
+ /** The build archive's manifest, now including this image's entry — content-addressed via `manifest.manifestDigest`. */
70
+ manifest: BuildArchiveManifest;
71
+ }
72
+
73
+ /**
74
+ * Build a container image from a Dockerfile into the build archive: `docker
75
+ * build`, tagged with a build-local reference derived from `into`, then
76
+ * `docker save`d to the archive path (OCI layout, per `docker save`'s output
77
+ * format). Records the produced tarball as an `image` entry in the archive's
78
+ * manifest (extending `input.manifest` when the caller threads one through,
79
+ * so a component with more than one image build accumulates a single
80
+ * manifest for the whole archive). No rollback — a local build produces no
81
+ * remote/mutable state to compensate; the archive path is simply not consumed
82
+ * further if a later step fails.
83
+ */
84
+ export function createDockerBuildCapability(
85
+ executor: CloudExecutor = defaultCloudExecutor(),
86
+ ): Capability<DockerBuildInput, DockerBuildOutput> {
87
+ return {
88
+ kind: "docker-build",
89
+ async run(ctx, input) {
90
+ const tag = `chant-build/${input.into.replace(/[^a-zA-Z0-9_.-]/g, "-")}:latest`;
91
+ const { digest } = await executor.docker.build({
92
+ context: input.context,
93
+ dockerfile: input.dockerfile,
94
+ buildArgs: input.buildArgs,
95
+ target: input.target,
96
+ tag,
97
+ });
98
+ await executor.docker.save({ image: tag, outFile: input.into });
99
+ const base = input.manifest ?? createBuildArchiveManifest(ctx.component);
100
+ const provenance: ProvenanceLink | undefined = input.sourceRef
101
+ ? { sourceRef: input.sourceRef, artifactDigest: digest }
102
+ : undefined;
103
+ const manifest = addArchiveEntry(base, {
104
+ kind: "image",
105
+ path: input.into,
106
+ digest,
107
+ ...(provenance ? { provenance } : {}),
108
+ });
109
+ return { archivePath: input.into, digest, manifest };
110
+ },
111
+ };
112
+ }
113
+
114
+ /** Default `docker-build` capability, backed by the real `CloudExecutor`. */
115
+ export const dockerBuildCapability: Capability<DockerBuildInput, DockerBuildOutput> = createDockerBuildCapability();
116
+
117
+ // ── archive templates ────────────────────────────────────────────────────────
118
+
119
+ export interface AddArchiveTemplateInput {
120
+ /** Where the synthesized template is written inside the build archive (e.g. `"search.template.json"`). */
121
+ path: string;
122
+ /** Serialized template content — its digest is computed from these exact bytes, so an unrelated re-synthesis with identical content keeps the same manifest digest. */
123
+ content: string;
124
+ /** Manifest to extend. Omit to start a new manifest holding just this template. */
125
+ manifest?: BuildArchiveManifest;
126
+ /** Source ref/commit this template was synthesized from (#614) — recorded as the entry's `provenance.sourceRef`. Omit when unknown; no `provenance` is recorded in that case. */
127
+ sourceRef?: string;
128
+ }
129
+
130
+ export interface AddArchiveTemplateOutput {
131
+ /** Content-addressed digest computed over `content`. */
132
+ digest: string;
133
+ /** The build archive's manifest, now including this template's entry. */
134
+ manifest: BuildArchiveManifest;
135
+ }
136
+
137
+ /**
138
+ * Fold one synthesized deploy template (e.g. the CloudFormation JSON chant's
139
+ * existing synthesis already produces) into a build archive's manifest as a
140
+ * `template`-kind entry, alongside the image(s) `docker-build` recorded. Not
141
+ * a registered capability — template synthesis is chant's existing `build`
142
+ * pipeline (see ../../build.ts), not a new deploy-time verb — so this is a
143
+ * plain function a build orchestration step calls directly to keep one
144
+ * manifest across every content kind the archive holds (image + templates).
145
+ * Exported alongside the build-family capabilities since it is the other
146
+ * half of "what goes in the manifest" `docker-build` documents above.
147
+ */
148
+ export function addArchiveTemplate(input: AddArchiveTemplateInput): AddArchiveTemplateOutput {
149
+ const digest = contentDigest(input.content);
150
+ const base = input.manifest ?? createBuildArchiveManifest("unknown");
151
+ const provenance: ProvenanceLink | undefined = input.sourceRef
152
+ ? { sourceRef: input.sourceRef, artifactDigest: digest }
153
+ : undefined;
154
+ const manifest = addArchiveEntry(base, {
155
+ kind: "template",
156
+ path: input.path,
157
+ digest,
158
+ ...(provenance ? { provenance } : {}),
159
+ });
160
+ return { digest, manifest };
161
+ }
162
+
163
+ // ── zip-package ─────────────────────────────────────────────────────────────
164
+
165
+ export interface ZipPackageInput {
166
+ /** Directory (or file list) to package. */
167
+ source: string;
168
+ /** Glob patterns to exclude from the archive. */
169
+ exclude?: string[];
170
+ /** Where the produced zip is written inside the build archive. */
171
+ into: string;
172
+ }
173
+
174
+ export interface ZipPackageOutput {
175
+ /** Path of the produced zip inside the build archive. */
176
+ archivePath: string;
177
+ /** Content hash of the zip (for promote-by-digest style referencing). */
178
+ digest: string;
179
+ }
180
+
181
+ /**
182
+ * Package a directory into a zip artifact (e.g. a Lambda deployment package)
183
+ * inside the build archive, via the `zip` CLI through the injectable
184
+ * `ProcessRunner`. Removes any prior `into` first so the archive is rebuilt from
185
+ * scratch (not appended to), then returns its content digest for
186
+ * promote-by-digest referencing.
187
+ */
188
+ export function createZipPackageCapability(processRunner: ProcessRunner = defaultProcessRunner()): Capability<ZipPackageInput, ZipPackageOutput> {
189
+ return {
190
+ kind: "zip-package",
191
+ async run(_ctx, input) {
192
+ const excludes = (input.exclude ?? []).map((p) => `-x ${q(p)}`).join(" ");
193
+ // `-X` drops extra file attributes; `rm -f` first so a re-run rebuilds rather than updates in place.
194
+ await processRunner.run(`rm -f ${q(input.into)} && zip -r -X ${q(input.into)} ${q(input.source)}${excludes ? ` ${excludes}` : ""}`);
195
+ const digest = `sha256:${createHash("sha256").update(readFileSync(input.into)).digest("hex")}`;
196
+ return { archivePath: input.into, digest };
197
+ },
198
+ };
199
+ }
200
+
201
+ /** Default `zip-package` capability, backed by the real process runner. */
202
+ export const zipPackageCapability: Capability<ZipPackageInput, ZipPackageOutput> = createZipPackageCapability();
203
+
204
+ // ── jvm-build ────────────────────────────────────────────────────────────────
205
+
206
+ export interface JvmBuildInput {
207
+ /** Build tool driving the compile/package step. */
208
+ tool: "maven" | "gradle";
209
+ /** Project directory containing the build file. */
210
+ path: string;
211
+ /** Build tool goals/tasks to run. Default: tool-appropriate package goal. */
212
+ goals?: string[];
213
+ /** Where the produced jar is written inside the build archive. */
214
+ into: string;
215
+ }
216
+
217
+ export interface JvmBuildOutput {
218
+ /** Path of the produced jar inside the build archive. */
219
+ archivePath: string;
220
+ /** Content hash of the jar. */
221
+ digest: string;
222
+ }
223
+
224
+ /**
225
+ * Compile and package a JVM project (Maven or Gradle) into a jar in the build
226
+ * archive, via the build tool through the injectable `ProcessRunner`. Runs the
227
+ * package goal, copies the produced jar (skipping `-sources`/`-javadoc`
228
+ * classifiers) to `into`, and returns its content digest.
229
+ */
230
+ export function createJvmBuildCapability(processRunner: ProcessRunner = defaultProcessRunner()): Capability<JvmBuildInput, JvmBuildOutput> {
231
+ return {
232
+ kind: "jvm-build",
233
+ async run(_ctx, input) {
234
+ if (input.tool === "gradle") {
235
+ const tasks = (input.goals ?? ["build"]).join(" ");
236
+ await processRunner.run(`./gradlew -q ${tasks}`, { cwd: input.path });
237
+ await processRunner.run(`cp "$(ls ${q(input.path)}/build/libs/*.jar | head -1)" ${q(input.into)}`);
238
+ } else {
239
+ const goals = (input.goals ?? ["package"]).join(" ");
240
+ await processRunner.run(`mvn -q -B -f ${q(`${input.path}/pom.xml`)} ${goals}`);
241
+ await processRunner.run(
242
+ `cp "$(ls ${q(`${input.path}/target`)}/*.jar | grep -Ev '(-sources|-javadoc)\\.jar$' | head -1)" ${q(input.into)}`,
243
+ );
244
+ }
245
+ const digest = `sha256:${createHash("sha256").update(readFileSync(input.into)).digest("hex")}`;
246
+ return { archivePath: input.into, digest };
247
+ },
248
+ };
249
+ }
250
+
251
+ /** Default `jvm-build` capability, backed by the real process runner. */
252
+ export const jvmBuildCapability: Capability<JvmBuildInput, JvmBuildOutput> = createJvmBuildCapability();
@@ -0,0 +1,181 @@
1
+ /**
2
+ * Injectable cloud I/O boundary for the *agnostic* capability implementations
3
+ * that remain in core: the `docker` build/push client and the Neo4j bolt-port
4
+ * probe. Every capability here that talks to a real tool goes through a
5
+ * `CloudExecutor` instead of shelling out directly, so production code gets a
6
+ * real, `child_process`/`net`-backed executor and tests get a
7
+ * `MockCloudExecutor` (./__tests__/mock-cloud-executor.ts) that records calls
8
+ * and returns canned results — no live docker, ever, in a test run.
9
+ *
10
+ * The AWS-specific clients (CloudFormation, ECS, Lambda, EMR, CodeDeploy, SSM
11
+ * host, S3, CloudFront, snapshot, ECR) used to live here too. They now live in
12
+ * the aws lexicon (`@intentius/chant-lexicon-aws/components`), contributed
13
+ * through the capability-plugin seam — see docs/components/cloud-boundary. The
14
+ * `docker` client stays in core because it is genuinely cloud-agnostic and
15
+ * shared: `docker-build` (agnostic) uses it here, and the aws lexicon's
16
+ * `publish-image` reuses the exported `realDocker` rather than duplicating it.
17
+ */
18
+
19
+ import { exec } from "node:child_process";
20
+ import { createConnection } from "node:net";
21
+ import { promisify } from "node:util";
22
+
23
+ export const execFileAsync = promisify(exec);
24
+
25
+ // ── docker ────────────────────────────────────────────────────────────────
26
+
27
+ export interface DockerBuildArgs {
28
+ context: string;
29
+ dockerfile?: string;
30
+ buildArgs?: Record<string, string>;
31
+ target?: string;
32
+ /** Tag applied to the built image so it can be referenced by name (e.g. saved to a tarball). */
33
+ tag: string;
34
+ }
35
+
36
+ export interface DockerSaveArgs {
37
+ /** Image reference to save (tag or digest). */
38
+ image: string;
39
+ /** Destination tarball path. */
40
+ outFile: string;
41
+ }
42
+
43
+ export interface DockerLoadArgs {
44
+ /** Tarball path to load. */
45
+ inFile: string;
46
+ }
47
+
48
+ export interface DockerTagArgs {
49
+ source: string;
50
+ target: string;
51
+ }
52
+
53
+ export interface DockerPushArgs {
54
+ /** Fully qualified image reference to push (registry/repo:tag). */
55
+ image: string;
56
+ }
57
+
58
+ export interface DockerClient {
59
+ /** Build an image from a Dockerfile; returns the built image's content digest. */
60
+ build(args: DockerBuildArgs): Promise<{ digest: string }>;
61
+ /** Save an image to an OCI/tarball archive on disk. */
62
+ save(args: DockerSaveArgs): Promise<void>;
63
+ /** Load an image tarball into the local docker store; returns the loaded image's digest. */
64
+ load(args: DockerLoadArgs): Promise<{ digest: string }>;
65
+ /** Tag a local image with an additional reference (e.g. before push). */
66
+ tag(args: DockerTagArgs): Promise<void>;
67
+ /** Push a tagged image to its registry; returns the pushed image's registry digest. */
68
+ push(args: DockerPushArgs): Promise<{ digest: string }>;
69
+ /** Inspect a remote image's manifest digest without pulling it (used to verify a promoted tag). */
70
+ remoteDigest(image: string): Promise<string>;
71
+ }
72
+
73
+ // ── Neo4j cluster probe (bolt) ───────────────────────────────────────────────
74
+
75
+ export interface Neo4jProbeArgs {
76
+ /** Cluster identifier — a comma-separated list of `host:port` bolt endpoints. */
77
+ cluster: string;
78
+ }
79
+
80
+ export interface Neo4jClusterClient {
81
+ /** Probe every member's bolt port; returns how many are reachable/healthy. */
82
+ probe(args: Neo4jProbeArgs): Promise<{ healthyCount: number; total: number }>;
83
+ }
84
+
85
+ // ── the agnostic executor surface ────────────────────────────────────────────
86
+
87
+ export interface CloudExecutor {
88
+ docker: DockerClient;
89
+ neo4j: Neo4jClusterClient;
90
+ }
91
+
92
+ // ── Real executor — shells out to `docker`; used outside tests ───────────────
93
+
94
+ /** Run a shell command, capturing stdout/stderr. Internal to the docker client; the aws lexicon's executor has its own (endpoint-aware) runner. */
95
+ function run(command: string): Promise<{ stdout: string; stderr: string }> {
96
+ return execFileAsync(command, { maxBuffer: 64 * 1024 * 1024 });
97
+ }
98
+
99
+ /** Shell-quote a single argument for POSIX shells (wrap in single quotes, escaping embedded ones). Internal; the shared, exported `q` lives in ./process-runner. */
100
+ function q(value: string): string {
101
+ return `'${value.replace(/'/g, `'\\''`)}'`;
102
+ }
103
+
104
+ export const realDocker: DockerClient = {
105
+ async build(args) {
106
+ const parts = [`docker build`, `-t ${q(args.tag)}`];
107
+ if (args.dockerfile) parts.push(`-f ${q(args.dockerfile)}`);
108
+ if (args.target) parts.push(`--target ${q(args.target)}`);
109
+ for (const [k, v] of Object.entries(args.buildArgs ?? {})) parts.push(`--build-arg ${q(`${k}=${v}`)}`);
110
+ parts.push(q(args.context));
111
+ await run(parts.join(" "));
112
+ const { stdout } = await run(`docker image inspect ${q(args.tag)} --format '{{.Id}}'`);
113
+ return { digest: stdout.trim() };
114
+ },
115
+ async save(args) {
116
+ await run(`docker save ${q(args.image)} -o ${q(args.outFile)}`);
117
+ },
118
+ async load(args) {
119
+ const { stdout } = await run(`docker load -i ${q(args.inFile)}`);
120
+ const match = stdout.match(/[Ll]oaded image(?: ID)?:\s*(\S+)/);
121
+ return { digest: match?.[1] ?? stdout.trim() };
122
+ },
123
+ async tag(args) {
124
+ await run(`docker tag ${q(args.source)} ${q(args.target)}`);
125
+ },
126
+ async push(args) {
127
+ await run(`docker push ${q(args.image)}`);
128
+ const { stdout } = await run(`docker image inspect ${q(args.image)} --format '{{index .RepoDigests 0}}'`);
129
+ return { digest: stdout.trim() };
130
+ },
131
+ async remoteDigest(image) {
132
+ const { stdout } = await run(`docker manifest inspect -v ${q(image)}`);
133
+ return stdout.trim();
134
+ },
135
+ };
136
+
137
+ const realNeo4j: Neo4jClusterClient = {
138
+ async probe(args) {
139
+ const endpoints = args.cluster
140
+ .split(",")
141
+ .map((s) => s.trim())
142
+ .filter(Boolean);
143
+ const results = await Promise.all(endpoints.map((endpoint) => probeBoltPort(endpoint)));
144
+ return { healthyCount: results.filter(Boolean).length, total: endpoints.length };
145
+ },
146
+ };
147
+
148
+ /** Probe one `host:port` bolt endpoint by opening (and immediately closing) a TCP socket. */
149
+ function probeBoltPort(endpoint: string, timeoutMs = 5000): Promise<boolean> {
150
+ const [host, portStr] = endpoint.split(":");
151
+ const port = Number(portStr ?? 7687);
152
+ return new Promise((resolve) => {
153
+ const socket = createConnection({ host, port, timeout: timeoutMs });
154
+ const finish = (healthy: boolean) => {
155
+ socket.destroy();
156
+ resolve(healthy);
157
+ };
158
+ socket.once("connect", () => finish(true));
159
+ socket.once("timeout", () => finish(false));
160
+ socket.once("error", () => finish(false));
161
+ });
162
+ }
163
+
164
+ /** Build a `CloudExecutor` that shells out to the real `docker` CLI and probes real bolt ports. Never used in tests. */
165
+ export function realCloudExecutor(): CloudExecutor {
166
+ return { docker: realDocker, neo4j: realNeo4j };
167
+ }
168
+
169
+ /** Lazily-constructed process-wide default so importing a capability module never shells out at import time. */
170
+ let defaultExecutor: CloudExecutor | undefined;
171
+
172
+ /** The default `CloudExecutor` each capability factory falls back to when none is supplied. */
173
+ export function defaultCloudExecutor(): CloudExecutor {
174
+ if (!defaultExecutor) defaultExecutor = realCloudExecutor();
175
+ return defaultExecutor;
176
+ }
177
+
178
+ /** Sleep for `ms`. Shared by every polling capability (`wait-*`) between attempts. */
179
+ export function sleep(ms: number): Promise<void> {
180
+ return new Promise((resolve) => setTimeout(resolve, ms));
181
+ }
@@ -0,0 +1,219 @@
1
+ /**
2
+ * Tests `aggregateComponentBom` (#614) — the component-level BOM aggregation
3
+ * that composes a component's leaf BOMs (a software SBOM + an IaC
4
+ * config-BOM) into one component BOM. Structural validation against the
5
+ * real, vendored SPDX-2.3/CycloneDX-1.5 JSON Schemas, same convention as
6
+ * ./bom-writer.test.ts.
7
+ */
8
+
9
+ import { readFileSync } from "node:fs";
10
+ import { join } from "node:path";
11
+ import { describe, expect, it } from "vitest";
12
+ import Ajv from "ajv";
13
+ import { aggregateComponentBom, componentBomLeavesFromManifest, type ComponentBomLeaf } from "./component-bom";
14
+ import { addArchiveEntry, createBuildArchiveManifest, findArchiveEntry } from "./build-archive";
15
+ import type { BomPackage } from "./bom-writer";
16
+
17
+ const SCHEMAS_DIR = join(import.meta.dirname, "__fixtures__/schemas");
18
+ function loadSchema(name: string): unknown {
19
+ return JSON.parse(readFileSync(join(SCHEMAS_DIR, name), "utf-8"));
20
+ }
21
+
22
+ const FIXED_NOW = () => new Date("2026-01-01T00:00:00.000Z");
23
+
24
+ /** Build a manifest for "search-service": an image + a software SBOM leaf, a template + a config-BOM leaf. */
25
+ function multiArtifactManifest() {
26
+ let manifest = createBuildArchiveManifest("search-service", { now: FIXED_NOW });
27
+ manifest = addArchiveEntry(manifest, { kind: "image", path: "image.tar", digest: "sha256:img1" });
28
+ manifest = addArchiveEntry(manifest, { kind: "template", path: "search.template.json", digest: "sha256:tmpl1" });
29
+ manifest = addArchiveEntry(manifest, {
30
+ kind: "sbom",
31
+ bomKind: "software",
32
+ path: "image.tar.sbom.json",
33
+ digest: "sha256:sbomsoft",
34
+ subjectDigest: "sha256:img1",
35
+ mediaType: "application/spdx+json",
36
+ packageCount: 1,
37
+ generator: "chant-lockfile-sbom/package-lock.json",
38
+ });
39
+ manifest = addArchiveEntry(manifest, {
40
+ kind: "sbom",
41
+ bomKind: "config",
42
+ path: "search.template.json.config-bom.json",
43
+ digest: "sha256:sbomconfig",
44
+ subjectDigest: "sha256:tmpl1",
45
+ mediaType: "application/spdx+json",
46
+ packageCount: 1,
47
+ generator: "chant-config-bom-extractor",
48
+ });
49
+ return manifest;
50
+ }
51
+
52
+ function multiArtifactLeaves(manifest: ReturnType<typeof multiArtifactManifest>): ComponentBomLeaf[] {
53
+ return [
54
+ {
55
+ entry: findArchiveEntry(manifest, "image.tar.sbom.json")!,
56
+ packages: [{ name: "left-pad", version: "1.3.0", type: "npm" } satisfies BomPackage],
57
+ },
58
+ {
59
+ entry: findArchiveEntry(manifest, "search.template.json.config-bom.json")!,
60
+ packages: [{ name: "SearchBucket", version: "AWS::S3::Bucket", type: "config" } satisfies BomPackage],
61
+ },
62
+ ];
63
+ }
64
+
65
+ describe("aggregateComponentBom (#614)", () => {
66
+ describe("multi-artifact component — a real assembly", () => {
67
+ it("SPDX: composes both leaves into one valid document", () => {
68
+ const manifest = multiArtifactManifest();
69
+ const leaves = multiArtifactLeaves(manifest);
70
+ const result = aggregateComponentBom({ component: "search-service", manifest, leaves, format: "spdx", now: FIXED_NOW });
71
+
72
+ expect(result.leafCount).toBe(2);
73
+ expect(result.totalPackageCount).toBe(2);
74
+ expect(result.bom.format).toBe("spdx");
75
+
76
+ const doc = JSON.parse(result.bom.bytes) as Record<string, unknown>;
77
+ const schema = loadSchema("spdx-2.3.schema.json");
78
+ const ajv = new Ajv({ strict: false, allErrors: true });
79
+ const validate = ajv.compile(schema as object);
80
+ const valid = validate(doc);
81
+ if (!valid) throw new Error(ajv.errorsText(validate.errors));
82
+ expect(valid).toBe(true);
83
+ expect(doc.name).toBe("search-service");
84
+ });
85
+
86
+ it("CycloneDX: composes both leaves into one valid document with nested components + compositions", () => {
87
+ const manifest = multiArtifactManifest();
88
+ const leaves = multiArtifactLeaves(manifest);
89
+ const result = aggregateComponentBom({ component: "search-service", manifest, leaves, format: "cyclonedx", now: FIXED_NOW });
90
+
91
+ const doc = JSON.parse(result.bom.bytes) as {
92
+ metadata: { component: { name: string; components: unknown[] } };
93
+ compositions: Array<{ aggregate: string }>;
94
+ };
95
+ const cdxSchema = loadSchema("cyclonedx-1.5.schema.json");
96
+ const jsfSchema = loadSchema("jsf-0.82.schema.json");
97
+ const spdxLicenseSchema = loadSchema("cyclonedx-spdx-license.schema.json");
98
+ const ajv = new Ajv({ strict: false, allErrors: true });
99
+ ajv.addSchema(jsfSchema as object);
100
+ ajv.addSchema(spdxLicenseSchema as object);
101
+ const validate = ajv.compile(cdxSchema as object);
102
+ const valid = validate(doc);
103
+ if (!valid) throw new Error(ajv.errorsText(validate.errors));
104
+ expect(valid).toBe(true);
105
+
106
+ expect(doc.metadata.component.name).toBe("search-service");
107
+ expect(doc.metadata.component.components).toHaveLength(2);
108
+ expect(doc.compositions[0]!.aggregate).toBe("incomplete");
109
+ });
110
+
111
+ it("preserves which packages came from which leaf — never flattens software and config packages into one anonymous list", () => {
112
+ const manifest = multiArtifactManifest();
113
+ const leaves = multiArtifactLeaves(manifest);
114
+ const result = aggregateComponentBom({ component: "search-service", manifest, leaves, format: "cyclonedx", now: FIXED_NOW });
115
+ const doc = JSON.parse(result.bom.bytes);
116
+
117
+ const softwareLeaf = doc.metadata.component.components.find((c: { name: string }) => c.name.startsWith("software:"));
118
+ const configLeaf = doc.metadata.component.components.find((c: { name: string }) => c.name.startsWith("config:"));
119
+ expect(softwareLeaf.components.map((c: { name: string }) => c.name)).toEqual(["left-pad"]);
120
+ expect(configLeaf.components.map((c: { name: string }) => c.name)).toEqual(["SearchBucket"]);
121
+ });
122
+
123
+ it("each sub-document's subjectId traces back to the artifact digest it describes (subjectDigest), not the BOM document's own digest", () => {
124
+ const manifest = multiArtifactManifest();
125
+ const leaves = multiArtifactLeaves(manifest);
126
+ const result = aggregateComponentBom({ component: "search-service", manifest, leaves, format: "cyclonedx", now: FIXED_NOW });
127
+ const doc = JSON.parse(result.bom.bytes);
128
+
129
+ const softwareLeaf = doc.metadata.component.components.find((c: { name: string }) => c.name.startsWith("software:"));
130
+ const configLeaf = doc.metadata.component.components.find((c: { name: string }) => c.name.startsWith("config:"));
131
+ const subjectIdOf = (c: { properties: Array<{ name: string; value: string }> }) =>
132
+ c.properties.find((p) => p.name === "chant:subjectId")!.value;
133
+ expect(subjectIdOf(softwareLeaf)).toBe("sha256:img1");
134
+ expect(subjectIdOf(configLeaf)).toBe("sha256:tmpl1");
135
+ });
136
+
137
+ it("is deterministic — identical leaves + manifest produce byte-identical output", () => {
138
+ const manifest = multiArtifactManifest();
139
+ const leaves = multiArtifactLeaves(manifest);
140
+ const a = aggregateComponentBom({ component: "search-service", manifest, leaves, format: "spdx", now: FIXED_NOW });
141
+ const b = aggregateComponentBom({ component: "search-service", manifest, leaves, format: "spdx", now: FIXED_NOW });
142
+ expect(a.bom.bytes).toBe(b.bom.bytes);
143
+ });
144
+ });
145
+
146
+ describe("single-artifact component — 1:1, still a valid standalone document", () => {
147
+ it("one leaf produces a valid document with no sub-document nesting", () => {
148
+ let manifest = createBuildArchiveManifest("infra-only", { now: FIXED_NOW });
149
+ manifest = addArchiveEntry(manifest, { kind: "template", path: "infra.template.json", digest: "sha256:tmpl1" });
150
+ manifest = addArchiveEntry(manifest, {
151
+ kind: "sbom",
152
+ bomKind: "config",
153
+ path: "infra.template.json.config-bom.json",
154
+ digest: "sha256:sbomconfig",
155
+ subjectDigest: "sha256:tmpl1",
156
+ mediaType: "application/spdx+json",
157
+ packageCount: 1,
158
+ });
159
+ const leaves: ComponentBomLeaf[] = [
160
+ {
161
+ entry: findArchiveEntry(manifest, "infra.template.json.config-bom.json")!,
162
+ packages: [{ name: "SearchTable", version: "AWS::DynamoDB::Table", type: "config" }],
163
+ },
164
+ ];
165
+
166
+ const result = aggregateComponentBom({ component: "infra-only", manifest, leaves, format: "spdx", now: FIXED_NOW });
167
+ expect(result.leafCount).toBe(1);
168
+ expect(result.totalPackageCount).toBe(1);
169
+
170
+ const doc = JSON.parse(result.bom.bytes);
171
+ const schema = loadSchema("spdx-2.3.schema.json");
172
+ const ajv = new Ajv({ strict: false, allErrors: true });
173
+ const validate = ajv.compile(schema as object);
174
+ expect(validate(doc)).toBe(true);
175
+ // no CONTAINS relationship — nothing nested, the leaf's packages are
176
+ // the aggregate's own flat package list directly.
177
+ expect(doc.relationships.some((r: { relationshipType: string }) => r.relationshipType === "CONTAINS")).toBe(false);
178
+ });
179
+ });
180
+
181
+ describe("error handling", () => {
182
+ it("throws when given zero leaves — aggregation over nothing is a caller bug, not a silent no-op", () => {
183
+ const manifest = createBuildArchiveManifest("empty");
184
+ expect(() => aggregateComponentBom({ component: "empty", manifest, leaves: [] })).toThrow(/no leaf BOMs/);
185
+ });
186
+ });
187
+
188
+ describe("defaults", () => {
189
+ it("defaults to DEFAULT_SBOM_FORMAT (spdx) when format is omitted", () => {
190
+ const manifest = multiArtifactManifest();
191
+ const leaves = multiArtifactLeaves(manifest);
192
+ const result = aggregateComponentBom({ component: "search-service", manifest, leaves, now: FIXED_NOW });
193
+ expect(result.bom.format).toBe("spdx");
194
+ });
195
+ });
196
+ });
197
+
198
+ describe("componentBomLeavesFromManifest (#614)", () => {
199
+ it("builds ComponentBomLeaf[] for every sbom-kind entry that has a known package list", () => {
200
+ const manifest = multiArtifactManifest();
201
+ const packagesByPath = new Map<string, BomPackage[]>([
202
+ ["image.tar.sbom.json", [{ name: "left-pad", version: "1.3.0", type: "npm" }]],
203
+ ["search.template.json.config-bom.json", [{ name: "SearchBucket", version: "AWS::S3::Bucket", type: "config" }]],
204
+ ]);
205
+ const leaves = componentBomLeavesFromManifest(manifest, packagesByPath);
206
+ expect(leaves).toHaveLength(2);
207
+ expect(leaves.map((l) => l.entry.path).sort()).toEqual(["image.tar.sbom.json", "search.template.json.config-bom.json"]);
208
+ });
209
+
210
+ it("skips sbom entries with no known package list rather than failing", () => {
211
+ const manifest = multiArtifactManifest();
212
+ const packagesByPath = new Map<string, BomPackage[]>([
213
+ ["image.tar.sbom.json", [{ name: "left-pad", version: "1.3.0", type: "npm" }]],
214
+ ]);
215
+ const leaves = componentBomLeavesFromManifest(manifest, packagesByPath);
216
+ expect(leaves).toHaveLength(1);
217
+ expect(leaves[0]!.entry.path).toBe("image.tar.sbom.json");
218
+ });
219
+ });