@intentius/chant 0.13.1 → 0.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (450) hide show
  1. package/dist/audit/catalog.d.ts +45 -5
  2. package/dist/audit/catalog.d.ts.map +1 -1
  3. package/dist/audit/proof.d.ts +3 -0
  4. package/dist/audit/proof.d.ts.map +1 -1
  5. package/dist/audit/report-model.d.ts +4 -1
  6. package/dist/audit/report-model.d.ts.map +1 -1
  7. package/dist/audit/report.d.ts +3 -0
  8. package/dist/audit/report.d.ts.map +1 -1
  9. package/dist/audit/rules-doc.d.ts +8 -2
  10. package/dist/audit/rules-doc.d.ts.map +1 -1
  11. package/dist/cli/commands/audit.d.ts.map +1 -1
  12. package/dist/cli/commands/doctor.d.ts.map +1 -1
  13. package/dist/cli/commands/lexicon-rolling-upgrade.d.ts +37 -0
  14. package/dist/cli/commands/lexicon-rolling-upgrade.d.ts.map +1 -0
  15. package/dist/cli/commands/lexicon-surface-diff.d.ts +48 -0
  16. package/dist/cli/commands/lexicon-surface-diff.d.ts.map +1 -0
  17. package/dist/cli/commands/lint.d.ts.map +1 -1
  18. package/dist/cli/commands/pinned-upgrade.d.ts +41 -0
  19. package/dist/cli/commands/pinned-upgrade.d.ts.map +1 -0
  20. package/dist/cli/handlers/build.d.ts.map +1 -1
  21. package/dist/cli/handlers/components.d.ts +39 -0
  22. package/dist/cli/handlers/components.d.ts.map +1 -0
  23. package/dist/cli/handlers/dev.d.ts +3 -0
  24. package/dist/cli/handlers/dev.d.ts.map +1 -1
  25. package/dist/cli/handlers/graph.d.ts +4 -2
  26. package/dist/cli/handlers/graph.d.ts.map +1 -1
  27. package/dist/cli/handlers/misc.d.ts.map +1 -1
  28. package/dist/cli/handlers/run.d.ts +49 -0
  29. package/dist/cli/handlers/run.d.ts.map +1 -1
  30. package/dist/cli/main.d.ts.map +1 -1
  31. package/dist/cli/registry.d.ts +35 -0
  32. package/dist/cli/registry.d.ts.map +1 -1
  33. package/dist/codegen/fetch.d.ts +22 -0
  34. package/dist/codegen/fetch.d.ts.map +1 -1
  35. package/dist/codegen/lexicon-regen.d.ts +110 -0
  36. package/dist/codegen/lexicon-regen.d.ts.map +1 -0
  37. package/dist/codegen/pinned-upgrade.d.ts +153 -0
  38. package/dist/codegen/pinned-upgrade.d.ts.map +1 -0
  39. package/dist/codegen/rolling-upgrade.d.ts +138 -0
  40. package/dist/codegen/rolling-upgrade.d.ts.map +1 -0
  41. package/dist/codegen/surface-snapshot.d.ts +173 -0
  42. package/dist/codegen/surface-snapshot.d.ts.map +1 -0
  43. package/dist/codegen/version-bump.d.ts +21 -0
  44. package/dist/codegen/version-bump.d.ts.map +1 -0
  45. package/dist/components/auto-release.d.ts +93 -0
  46. package/dist/components/auto-release.d.ts.map +1 -0
  47. package/dist/components/builders.d.ts +37 -0
  48. package/dist/components/builders.d.ts.map +1 -0
  49. package/dist/components/capability-plugin-loader.d.ts +111 -0
  50. package/dist/components/capability-plugin-loader.d.ts.map +1 -0
  51. package/dist/components/capability-plugin-schema.d.ts +24 -0
  52. package/dist/components/capability-plugin-schema.d.ts.map +1 -0
  53. package/dist/components/capability-plugin.d.ts +85 -0
  54. package/dist/components/capability-plugin.d.ts.map +1 -0
  55. package/dist/components/capability.d.ts +89 -0
  56. package/dist/components/capability.d.ts.map +1 -0
  57. package/dist/components/cli-support.d.ts +204 -0
  58. package/dist/components/cli-support.d.ts.map +1 -0
  59. package/dist/components/component.d.ts +179 -0
  60. package/dist/components/component.d.ts.map +1 -0
  61. package/dist/components/config-defaults.d.ts +41 -0
  62. package/dist/components/config-defaults.d.ts.map +1 -0
  63. package/dist/components/discover.d.ts +60 -0
  64. package/dist/components/discover.d.ts.map +1 -0
  65. package/dist/components/driver-output.d.ts +20 -0
  66. package/dist/components/driver-output.d.ts.map +1 -0
  67. package/dist/components/driver.d.ts +209 -0
  68. package/dist/components/driver.d.ts.map +1 -0
  69. package/dist/components/index.d.ts +22 -0
  70. package/dist/components/index.d.ts.map +1 -0
  71. package/dist/components/manifest-persistence.d.ts +79 -0
  72. package/dist/components/manifest-persistence.d.ts.map +1 -0
  73. package/dist/components/pilots/alb-ecs.pilot.d.ts +27 -0
  74. package/dist/components/pilots/alb-ecs.pilot.d.ts.map +1 -0
  75. package/dist/components/pilots/dynamodb.pilot.d.ts +22 -0
  76. package/dist/components/pilots/dynamodb.pilot.d.ts.map +1 -0
  77. package/dist/components/pilots/jar-emr.pilot.d.ts +37 -0
  78. package/dist/components/pilots/jar-emr.pilot.d.ts.map +1 -0
  79. package/dist/components/pilots/lambda.pilot.d.ts +35 -0
  80. package/dist/components/pilots/lambda.pilot.d.ts.map +1 -0
  81. package/dist/components/pilots/neo4j-fanout.pilot.d.ts +17 -0
  82. package/dist/components/pilots/neo4j-fanout.pilot.d.ts.map +1 -0
  83. package/dist/components/pilots/project.d.ts +13 -0
  84. package/dist/components/pilots/project.d.ts.map +1 -0
  85. package/dist/components/presets/ecs-fargate.d.ts +59 -0
  86. package/dist/components/presets/ecs-fargate.d.ts.map +1 -0
  87. package/dist/components/presets/index.d.ts +29 -0
  88. package/dist/components/presets/index.d.ts.map +1 -0
  89. package/dist/components/presets/lambda.d.ts +35 -0
  90. package/dist/components/presets/lambda.d.ts.map +1 -0
  91. package/dist/components/presets/single-host-compose.d.ts +43 -0
  92. package/dist/components/presets/single-host-compose.d.ts.map +1 -0
  93. package/dist/components/registry.d.ts +29 -0
  94. package/dist/components/registry.d.ts.map +1 -0
  95. package/dist/components/starter-plugin.d.ts +51 -0
  96. package/dist/components/starter-plugin.d.ts.map +1 -0
  97. package/dist/components/temporal-codegen-loader.d.ts +27 -0
  98. package/dist/components/temporal-codegen-loader.d.ts.map +1 -0
  99. package/dist/components/verbs/__tests__/mock-cloud-executor.d.ts +35 -0
  100. package/dist/components/verbs/__tests__/mock-cloud-executor.d.ts.map +1 -0
  101. package/dist/components/verbs/__tests__/mock-process-runner.d.ts +45 -0
  102. package/dist/components/verbs/__tests__/mock-process-runner.d.ts.map +1 -0
  103. package/dist/components/verbs/__tests__/mock-sbom-generator.d.ts +33 -0
  104. package/dist/components/verbs/__tests__/mock-sbom-generator.d.ts.map +1 -0
  105. package/dist/components/verbs/bom-writer.d.ts +155 -0
  106. package/dist/components/verbs/bom-writer.d.ts.map +1 -0
  107. package/dist/components/verbs/build-archive.d.ts +205 -0
  108. package/dist/components/verbs/build-archive.d.ts.map +1 -0
  109. package/dist/components/verbs/build.d.ts +150 -0
  110. package/dist/components/verbs/build.d.ts.map +1 -0
  111. package/dist/components/verbs/cloud-executor.d.ts +88 -0
  112. package/dist/components/verbs/cloud-executor.d.ts.map +1 -0
  113. package/dist/components/verbs/component-bom.d.ts +103 -0
  114. package/dist/components/verbs/component-bom.d.ts.map +1 -0
  115. package/dist/components/verbs/index.d.ts +49 -0
  116. package/dist/components/verbs/index.d.ts.map +1 -0
  117. package/dist/components/verbs/license-policy.d.ts +56 -0
  118. package/dist/components/verbs/license-policy.d.ts.map +1 -0
  119. package/dist/components/verbs/lockfile-sbom-generator.d.ts +81 -0
  120. package/dist/components/verbs/lockfile-sbom-generator.d.ts.map +1 -0
  121. package/dist/components/verbs/process-runner.d.ts +64 -0
  122. package/dist/components/verbs/process-runner.d.ts.map +1 -0
  123. package/dist/components/verbs/reproducibility.d.ts +80 -0
  124. package/dist/components/verbs/reproducibility.d.ts.map +1 -0
  125. package/dist/components/verbs/sbom-generator.d.ts +157 -0
  126. package/dist/components/verbs/sbom-generator.d.ts.map +1 -0
  127. package/dist/components/verbs/sbom.d.ts +79 -0
  128. package/dist/components/verbs/sbom.d.ts.map +1 -0
  129. package/dist/components/verbs/shell.d.ts +35 -0
  130. package/dist/components/verbs/shell.d.ts.map +1 -0
  131. package/dist/components/verbs/sign.d.ts +222 -0
  132. package/dist/components/verbs/sign.d.ts.map +1 -0
  133. package/dist/components/verbs/stub.d.ts +18 -0
  134. package/dist/components/verbs/stub.d.ts.map +1 -0
  135. package/dist/components/verbs/tool-sbom-generator.d.ts +51 -0
  136. package/dist/components/verbs/tool-sbom-generator.d.ts.map +1 -0
  137. package/dist/components/verbs/verify.d.ts +106 -0
  138. package/dist/components/verbs/verify.d.ts.map +1 -0
  139. package/dist/components/verbs/vex.d.ts +65 -0
  140. package/dist/components/verbs/vex.d.ts.map +1 -0
  141. package/dist/components/verbs/vuln-gate.d.ts +91 -0
  142. package/dist/components/verbs/vuln-gate.d.ts.map +1 -0
  143. package/dist/components/verbs/vuln-scan.d.ts +116 -0
  144. package/dist/components/verbs/vuln-scan.d.ts.map +1 -0
  145. package/dist/components/verbs/wait-verify.d.ts +85 -0
  146. package/dist/components/verbs/wait-verify.d.ts.map +1 -0
  147. package/dist/config.d.ts +205 -0
  148. package/dist/config.d.ts.map +1 -1
  149. package/dist/index.d.ts +4 -0
  150. package/dist/index.d.ts.map +1 -1
  151. package/dist/lexicon.d.ts +78 -0
  152. package/dist/lexicon.d.ts.map +1 -1
  153. package/dist/lifecycle/build-ledger-store.d.ts +121 -0
  154. package/dist/lifecycle/build-ledger-store.d.ts.map +1 -0
  155. package/dist/lifecycle/build-ledger.d.ts +225 -0
  156. package/dist/lifecycle/build-ledger.d.ts.map +1 -0
  157. package/dist/lifecycle/git.d.ts +84 -0
  158. package/dist/lifecycle/git.d.ts.map +1 -1
  159. package/dist/lifecycle/index.d.ts +5 -0
  160. package/dist/lifecycle/index.d.ts.map +1 -1
  161. package/dist/lifecycle/oras-referrer-lookup.d.ts +41 -0
  162. package/dist/lifecycle/oras-referrer-lookup.d.ts.map +1 -0
  163. package/dist/lifecycle/release-ledger.d.ts +122 -0
  164. package/dist/lifecycle/release-ledger.d.ts.map +1 -0
  165. package/dist/lifecycle/status.d.ts +150 -0
  166. package/dist/lifecycle/status.d.ts.map +1 -0
  167. package/dist/lint/component-checks.d.ts +95 -0
  168. package/dist/lint/component-checks.d.ts.map +1 -0
  169. package/dist/lint/engine.d.ts +23 -0
  170. package/dist/lint/engine.d.ts.map +1 -1
  171. package/dist/lint/presets/strict.json +8 -1
  172. package/dist/lint/rules/__fixtures__/comp/comp000-discovery-error/broken-a.component.d.ts +4 -0
  173. package/dist/lint/rules/__fixtures__/comp/comp000-discovery-error/broken-a.component.d.ts.map +1 -0
  174. package/dist/lint/rules/__fixtures__/comp/comp000-discovery-error/broken-b.component.d.ts +4 -0
  175. package/dist/lint/rules/__fixtures__/comp/comp000-discovery-error/broken-b.component.d.ts.map +1 -0
  176. package/dist/lint/rules/__fixtures__/comp/comp001/fail/orphan-lib.component.d.ts +4 -0
  177. package/dist/lint/rules/__fixtures__/comp/comp001/fail/orphan-lib.component.d.ts.map +1 -0
  178. package/dist/lint/rules/__fixtures__/comp/comp001/pass/emr-job.component.d.ts +4 -0
  179. package/dist/lint/rules/__fixtures__/comp/comp001/pass/emr-job.component.d.ts.map +1 -0
  180. package/dist/lint/rules/__fixtures__/comp/comp001/pass/jar-lib.component.d.ts +4 -0
  181. package/dist/lint/rules/__fixtures__/comp/comp001/pass/jar-lib.component.d.ts.map +1 -0
  182. package/dist/lint/rules/__fixtures__/comp/comp002/fail/search-service.component.d.ts +10 -0
  183. package/dist/lint/rules/__fixtures__/comp/comp002/fail/search-service.component.d.ts.map +1 -0
  184. package/dist/lint/rules/__fixtures__/comp/comp002/pass/jar-lib.component.d.ts +3 -0
  185. package/dist/lint/rules/__fixtures__/comp/comp002/pass/jar-lib.component.d.ts.map +1 -0
  186. package/dist/lint/rules/__fixtures__/comp/comp002/pass/search-service.component.d.ts +4 -0
  187. package/dist/lint/rules/__fixtures__/comp/comp002/pass/search-service.component.d.ts.map +1 -0
  188. package/dist/lint/rules/__fixtures__/comp/comp003/fail/migrator.component.d.ts +4 -0
  189. package/dist/lint/rules/__fixtures__/comp/comp003/fail/migrator.component.d.ts.map +1 -0
  190. package/dist/lint/rules/__fixtures__/comp/comp003/pass/migrator.component.d.ts +4 -0
  191. package/dist/lint/rules/__fixtures__/comp/comp003/pass/migrator.component.d.ts.map +1 -0
  192. package/dist/lint/rules/__fixtures__/comp/comp003/pass/orders-table.component.d.ts +4 -0
  193. package/dist/lint/rules/__fixtures__/comp/comp003/pass/orders-table.component.d.ts.map +1 -0
  194. package/dist/lint/rules/__fixtures__/comp/comp004/fail/neo4j-cluster.component.d.ts +4 -0
  195. package/dist/lint/rules/__fixtures__/comp/comp004/fail/neo4j-cluster.component.d.ts.map +1 -0
  196. package/dist/lint/rules/__fixtures__/comp/comp004/pass/neo4j-cluster.component.d.ts +4 -0
  197. package/dist/lint/rules/__fixtures__/comp/comp004/pass/neo4j-cluster.component.d.ts.map +1 -0
  198. package/dist/lint/rules/__fixtures__/comp/comp004/pass/orders-table.component.d.ts +4 -0
  199. package/dist/lint/rules/__fixtures__/comp/comp004/pass/orders-table.component.d.ts.map +1 -0
  200. package/dist/lint/rules/__fixtures__/comp/comp005/fail/search-service.component.d.ts +4 -0
  201. package/dist/lint/rules/__fixtures__/comp/comp005/fail/search-service.component.d.ts.map +1 -0
  202. package/dist/lint/rules/__fixtures__/comp/comp005/pass/search-service.component.d.ts +4 -0
  203. package/dist/lint/rules/__fixtures__/comp/comp005/pass/search-service.component.d.ts.map +1 -0
  204. package/dist/lint/rules/__fixtures__/comp/comp006/fail/legacy-tool.component.d.ts +4 -0
  205. package/dist/lint/rules/__fixtures__/comp/comp006/fail/legacy-tool.component.d.ts.map +1 -0
  206. package/dist/lint/rules/__fixtures__/comp/comp006/pass/legacy-tool.component.d.ts +4 -0
  207. package/dist/lint/rules/__fixtures__/comp/comp006/pass/legacy-tool.component.d.ts.map +1 -0
  208. package/dist/lint/rules/__fixtures__/comp/comp007/fail/inventory-table.component.d.ts +4 -0
  209. package/dist/lint/rules/__fixtures__/comp/comp007/fail/inventory-table.component.d.ts.map +1 -0
  210. package/dist/lint/rules/__fixtures__/comp/comp007/fail/orders-table.component.d.ts +4 -0
  211. package/dist/lint/rules/__fixtures__/comp/comp007/fail/orders-table.component.d.ts.map +1 -0
  212. package/dist/lint/rules/__fixtures__/comp/comp007/pass/orders-table.component.d.ts +3 -0
  213. package/dist/lint/rules/__fixtures__/comp/comp007/pass/orders-table.component.d.ts.map +1 -0
  214. package/dist/lint/rules/__fixtures__/comp/comp007/pass/search-service.component.d.ts +4 -0
  215. package/dist/lint/rules/__fixtures__/comp/comp007/pass/search-service.component.d.ts.map +1 -0
  216. package/dist/lint/rules/comp/comp001-publishes-never-applies.d.ts +20 -0
  217. package/dist/lint/rules/comp/comp001-publishes-never-applies.d.ts.map +1 -0
  218. package/dist/lint/rules/comp/comp002-dangling-wiring-ref.d.ts +28 -0
  219. package/dist/lint/rules/comp/comp002-dangling-wiring-ref.d.ts.map +1 -0
  220. package/dist/lint/rules/comp/comp003-mutating-no-rollback.d.ts +39 -0
  221. package/dist/lint/rules/comp/comp003-mutating-no-rollback.d.ts.map +1 -0
  222. package/dist/lint/rules/comp/comp004-gate-needs-temporal.d.ts +37 -0
  223. package/dist/lint/rules/comp/comp004-gate-needs-temporal.d.ts.map +1 -0
  224. package/dist/lint/rules/comp/comp005-capability-kind-is-noun.d.ts +43 -0
  225. package/dist/lint/rules/comp/comp005-capability-kind-is-noun.d.ts.map +1 -0
  226. package/dist/lint/rules/comp/comp006-shell-needs-reason.d.ts +22 -0
  227. package/dist/lint/rules/comp/comp006-shell-needs-reason.d.ts.map +1 -0
  228. package/dist/lint/rules/comp/comp007-composition-sprawl.d.ts +33 -0
  229. package/dist/lint/rules/comp/comp007-composition-sprawl.d.ts.map +1 -0
  230. package/dist/lint/rules/comp/index.d.ts +18 -0
  231. package/dist/lint/rules/comp/index.d.ts.map +1 -0
  232. package/dist/lint/rules/comp/support.d.ts +71 -0
  233. package/dist/lint/rules/comp/support.d.ts.map +1 -0
  234. package/dist/lsp/lexicon-providers.d.ts.map +1 -1
  235. package/dist/op/activity-registry.d.ts +11 -6
  236. package/dist/op/activity-registry.d.ts.map +1 -1
  237. package/dist/op/activity-runtime.d.ts +24 -0
  238. package/dist/op/activity-runtime.d.ts.map +1 -0
  239. package/dist/op/builders.d.ts +56 -0
  240. package/dist/op/builders.d.ts.map +1 -1
  241. package/dist/op/index.d.ts +2 -1
  242. package/dist/op/index.d.ts.map +1 -1
  243. package/dist/ownership.d.ts +20 -14
  244. package/dist/ownership.d.ts.map +1 -1
  245. package/package.json +14 -1
  246. package/src/audit/catalog.test.ts +74 -22
  247. package/src/audit/catalog.ts +74 -471
  248. package/src/audit/proof.ts +4 -2
  249. package/src/audit/report-html.test.ts +14 -7
  250. package/src/audit/report-model.ts +20 -9
  251. package/src/audit/report.test.ts +36 -13
  252. package/src/audit/report.ts +2 -0
  253. package/src/audit/rules-doc.test.ts +8 -6
  254. package/src/audit/rules-doc.ts +13 -5
  255. package/src/cli/commands/audit.ts +23 -18
  256. package/src/cli/commands/component-lint.test.ts +158 -0
  257. package/src/cli/commands/doctor.test.ts +116 -2
  258. package/src/cli/commands/doctor.ts +46 -0
  259. package/src/cli/commands/lexicon-rolling-upgrade.ts +198 -0
  260. package/src/cli/commands/lexicon-surface-diff.test.ts +104 -0
  261. package/src/cli/commands/lexicon-surface-diff.ts +185 -0
  262. package/src/cli/commands/lint.ts +142 -1
  263. package/src/cli/commands/pinned-upgrade.ts +204 -0
  264. package/src/cli/handlers/build.ts +58 -1
  265. package/src/cli/handlers/components.test.ts +504 -0
  266. package/src/cli/handlers/components.ts +404 -0
  267. package/src/cli/handlers/dev.ts +60 -1
  268. package/src/cli/handlers/graph.ts +47 -2
  269. package/src/cli/handlers/misc.ts +45 -1
  270. package/src/cli/handlers/run.test.ts +967 -3
  271. package/src/cli/handlers/run.ts +618 -11
  272. package/src/cli/main.test.ts +66 -0
  273. package/src/cli/main.ts +105 -2
  274. package/src/cli/registry.ts +35 -0
  275. package/src/codegen/fetch.test.ts +84 -3
  276. package/src/codegen/fetch.ts +76 -2
  277. package/src/codegen/lexicon-regen.test.ts +298 -0
  278. package/src/codegen/lexicon-regen.ts +545 -0
  279. package/src/codegen/pinned-upgrade.test.ts +399 -0
  280. package/src/codegen/pinned-upgrade.ts +423 -0
  281. package/src/codegen/rolling-upgrade.test.ts +333 -0
  282. package/src/codegen/rolling-upgrade.ts +320 -0
  283. package/src/codegen/surface-snapshot.test.ts +461 -0
  284. package/src/codegen/surface-snapshot.ts +546 -0
  285. package/src/codegen/version-bump.test.ts +47 -0
  286. package/src/codegen/version-bump.ts +39 -0
  287. package/src/components/README.md +130 -0
  288. package/src/components/SPRAWL-VALIDATION.md +181 -0
  289. package/src/components/__fixtures__/alb-ecs-service.json +51 -0
  290. package/src/components/__fixtures__/dynamodb-infra.json +32 -0
  291. package/src/components/__fixtures__/emr-job-consumer.json +26 -0
  292. package/src/components/__fixtures__/jar-lib-producer.json +16 -0
  293. package/src/components/__fixtures__/lambda-image-processor.json +28 -0
  294. package/src/components/__fixtures__/neo4j-fanout.json +34 -0
  295. package/src/components/__fixtures__/single-host-compose.json +29 -0
  296. package/src/components/auto-release.test.ts +217 -0
  297. package/src/components/auto-release.ts +216 -0
  298. package/src/components/builders.test.ts +45 -0
  299. package/src/components/builders.ts +71 -0
  300. package/src/components/capability-plugin-loader.ts +218 -0
  301. package/src/components/capability-plugin-schema.ts +56 -0
  302. package/src/components/capability-plugin.test.ts +235 -0
  303. package/src/components/capability-plugin.ts +108 -0
  304. package/src/components/capability.ts +117 -0
  305. package/src/components/cli-support.test.ts +579 -0
  306. package/src/components/cli-support.ts +481 -0
  307. package/src/components/component-schema.test.ts +265 -0
  308. package/src/components/component.schema.json +235 -0
  309. package/src/components/component.test.ts +251 -0
  310. package/src/components/component.ts +255 -0
  311. package/src/components/config-defaults.test.ts +263 -0
  312. package/src/components/config-defaults.ts +145 -0
  313. package/src/components/discover.test.ts +352 -0
  314. package/src/components/discover.ts +167 -0
  315. package/src/components/driver-output.ts +71 -0
  316. package/src/components/driver.test.ts +638 -0
  317. package/src/components/driver.ts +690 -0
  318. package/src/components/index.ts +113 -0
  319. package/src/components/manifest-persistence.test.ts +152 -0
  320. package/src/components/manifest-persistence.ts +164 -0
  321. package/src/components/pilots/README.md +33 -0
  322. package/src/components/pilots/alb-ecs.pilot.ts +63 -0
  323. package/src/components/pilots/dynamodb.pilot.ts +59 -0
  324. package/src/components/pilots/jar-emr.pilot.ts +69 -0
  325. package/src/components/pilots/lambda.pilot.ts +55 -0
  326. package/src/components/pilots/neo4j-fanout.pilot.ts +92 -0
  327. package/src/components/pilots/pilots-lint-clean.test.ts +78 -0
  328. package/src/components/pilots/pilots.test.ts +126 -0
  329. package/src/components/pilots/project.ts +13 -0
  330. package/src/components/presets/comp007-reduction.test.ts +122 -0
  331. package/src/components/presets/ecs-fargate.test.ts +84 -0
  332. package/src/components/presets/ecs-fargate.ts +103 -0
  333. package/src/components/presets/index.ts +32 -0
  334. package/src/components/presets/lambda.test.ts +35 -0
  335. package/src/components/presets/lambda.ts +53 -0
  336. package/src/components/presets/single-host-compose.test.ts +65 -0
  337. package/src/components/presets/single-host-compose.ts +64 -0
  338. package/src/components/registry.test.ts +112 -0
  339. package/src/components/registry.ts +37 -0
  340. package/src/components/starter-plugin.ts +105 -0
  341. package/src/components/temporal-codegen-loader.ts +41 -0
  342. package/src/components/verbs/__fixtures__/package-lock.json +45 -0
  343. package/src/components/verbs/__fixtures__/pom.xml +37 -0
  344. package/src/components/verbs/__fixtures__/schemas/cyclonedx-1.5.schema.json +3800 -0
  345. package/src/components/verbs/__fixtures__/schemas/cyclonedx-spdx-license.schema.json +621 -0
  346. package/src/components/verbs/__fixtures__/schemas/jsf-0.82.schema.json +240 -0
  347. package/src/components/verbs/__fixtures__/schemas/spdx-2.3.schema.json +740 -0
  348. package/src/components/verbs/__tests__/mock-cloud-executor.ts +102 -0
  349. package/src/components/verbs/__tests__/mock-process-runner.ts +79 -0
  350. package/src/components/verbs/__tests__/mock-sbom-generator.ts +85 -0
  351. package/src/components/verbs/bom-writer.test.ts +331 -0
  352. package/src/components/verbs/bom-writer.ts +386 -0
  353. package/src/components/verbs/build-archive.test.ts +251 -0
  354. package/src/components/verbs/build-archive.ts +296 -0
  355. package/src/components/verbs/build.test.ts +222 -0
  356. package/src/components/verbs/build.ts +252 -0
  357. package/src/components/verbs/cloud-executor.ts +181 -0
  358. package/src/components/verbs/component-bom.test.ts +219 -0
  359. package/src/components/verbs/component-bom.ts +189 -0
  360. package/src/components/verbs/index.ts +49 -0
  361. package/src/components/verbs/license-policy.ts +115 -0
  362. package/src/components/verbs/lockfile-sbom-generator.test.ts +195 -0
  363. package/src/components/verbs/lockfile-sbom-generator.ts +236 -0
  364. package/src/components/verbs/process-runner.test.ts +79 -0
  365. package/src/components/verbs/process-runner.ts +113 -0
  366. package/src/components/verbs/reproducibility.test.ts +24 -0
  367. package/src/components/verbs/reproducibility.ts +100 -0
  368. package/src/components/verbs/sbom-generator.test.ts +96 -0
  369. package/src/components/verbs/sbom-generator.ts +209 -0
  370. package/src/components/verbs/sbom.test.ts +184 -0
  371. package/src/components/verbs/sbom.ts +152 -0
  372. package/src/components/verbs/shell.test.ts +36 -0
  373. package/src/components/verbs/shell.ts +51 -0
  374. package/src/components/verbs/sign.test.ts +210 -0
  375. package/src/components/verbs/sign.ts +350 -0
  376. package/src/components/verbs/stub.ts +34 -0
  377. package/src/components/verbs/tool-sbom-generator.test.ts +194 -0
  378. package/src/components/verbs/tool-sbom-generator.ts +192 -0
  379. package/src/components/verbs/verify.test.ts +159 -0
  380. package/src/components/verbs/verify.ts +179 -0
  381. package/src/components/verbs/vex.ts +182 -0
  382. package/src/components/verbs/vuln-gate.test.ts +201 -0
  383. package/src/components/verbs/vuln-gate.ts +170 -0
  384. package/src/components/verbs/vuln-scan.test.ts +145 -0
  385. package/src/components/verbs/vuln-scan.ts +266 -0
  386. package/src/components/verbs/wait-verify.test.ts +111 -0
  387. package/src/components/verbs/wait-verify.ts +171 -0
  388. package/src/config.test.ts +74 -1
  389. package/src/config.ts +219 -0
  390. package/src/index.ts +32 -0
  391. package/src/lexicon.ts +88 -0
  392. package/src/lifecycle/build-ledger-store.test.ts +217 -0
  393. package/src/lifecycle/build-ledger-store.ts +165 -0
  394. package/src/lifecycle/build-ledger.test.ts +349 -0
  395. package/src/lifecycle/build-ledger.ts +305 -0
  396. package/src/lifecycle/git.test.ts +118 -0
  397. package/src/lifecycle/git.ts +149 -14
  398. package/src/lifecycle/index.ts +5 -0
  399. package/src/lifecycle/oras-referrer-lookup.test.ts +116 -0
  400. package/src/lifecycle/oras-referrer-lookup.ts +110 -0
  401. package/src/lifecycle/release-ledger.test.ts +209 -0
  402. package/src/lifecycle/release-ledger.ts +185 -0
  403. package/src/lifecycle/status.test.ts +330 -0
  404. package/src/lifecycle/status.ts +278 -0
  405. package/src/lint/component-checks.ts +148 -0
  406. package/src/lint/config.test.ts +21 -0
  407. package/src/lint/engine.ts +11 -2
  408. package/src/lint/presets/relaxed.json +8 -1
  409. package/src/lint/presets/strict.json +8 -1
  410. package/src/lint/rules/__fixtures__/comp/comp000-discovery-error/broken-a.component.ts +9 -0
  411. package/src/lint/rules/__fixtures__/comp/comp000-discovery-error/broken-b.component.ts +9 -0
  412. package/src/lint/rules/__fixtures__/comp/comp001/fail/orphan-lib.component.ts +11 -0
  413. package/src/lint/rules/__fixtures__/comp/comp001/pass/emr-job.component.ts +13 -0
  414. package/src/lint/rules/__fixtures__/comp/comp001/pass/jar-lib.component.ts +11 -0
  415. package/src/lint/rules/__fixtures__/comp/comp002/fail/search-service.component.ts +27 -0
  416. package/src/lint/rules/__fixtures__/comp/comp002/pass/jar-lib.component.ts +10 -0
  417. package/src/lint/rules/__fixtures__/comp/comp002/pass/search-service.component.ts +23 -0
  418. package/src/lint/rules/__fixtures__/comp/comp003/fail/migrator.component.ts +10 -0
  419. package/src/lint/rules/__fixtures__/comp/comp003/pass/migrator.component.ts +19 -0
  420. package/src/lint/rules/__fixtures__/comp/comp003/pass/orders-table.component.ts +10 -0
  421. package/src/lint/rules/__fixtures__/comp/comp004/fail/neo4j-cluster.component.ts +20 -0
  422. package/src/lint/rules/__fixtures__/comp/comp004/pass/neo4j-cluster.component.ts +23 -0
  423. package/src/lint/rules/__fixtures__/comp/comp004/pass/orders-table.component.ts +10 -0
  424. package/src/lint/rules/__fixtures__/comp/comp005/fail/search-service.component.ts +14 -0
  425. package/src/lint/rules/__fixtures__/comp/comp005/pass/search-service.component.ts +18 -0
  426. package/src/lint/rules/__fixtures__/comp/comp006/fail/legacy-tool.component.ts +10 -0
  427. package/src/lint/rules/__fixtures__/comp/comp006/pass/legacy-tool.component.ts +14 -0
  428. package/src/lint/rules/__fixtures__/comp/comp007/fail/inventory-table.component.ts +13 -0
  429. package/src/lint/rules/__fixtures__/comp/comp007/fail/orders-table.component.ts +13 -0
  430. package/src/lint/rules/__fixtures__/comp/comp007/pass/orders-table.component.ts +12 -0
  431. package/src/lint/rules/__fixtures__/comp/comp007/pass/search-service.component.ts +18 -0
  432. package/src/lint/rules/comp/comp.test.ts +378 -0
  433. package/src/lint/rules/comp/comp001-publishes-never-applies.ts +78 -0
  434. package/src/lint/rules/comp/comp002-dangling-wiring-ref.ts +94 -0
  435. package/src/lint/rules/comp/comp003-mutating-no-rollback.ts +115 -0
  436. package/src/lint/rules/comp/comp004-gate-needs-temporal.ts +66 -0
  437. package/src/lint/rules/comp/comp005-capability-kind-is-noun.ts +113 -0
  438. package/src/lint/rules/comp/comp006-shell-needs-reason.ts +55 -0
  439. package/src/lint/rules/comp/comp007-composition-sprawl.ts +71 -0
  440. package/src/lint/rules/comp/index.ts +37 -0
  441. package/src/lint/rules/comp/support.ts +173 -0
  442. package/src/lsp/lexicon-providers.test.ts +55 -0
  443. package/src/lsp/lexicon-providers.ts +13 -3
  444. package/src/op/activity-registry.test.ts +38 -0
  445. package/src/op/activity-registry.ts +31 -12
  446. package/src/op/activity-runtime.ts +73 -0
  447. package/src/op/builders.ts +92 -0
  448. package/src/op/index.ts +3 -1
  449. package/src/ownership.test.ts +24 -26
  450. package/src/ownership.ts +26 -38
@@ -0,0 +1,189 @@
1
+ /**
2
+ * Component-level BOM aggregation (#614, epic #551 follow-up to #613).
3
+ *
4
+ * A component may produce more than one leaf BOM: a software SBOM
5
+ * (./sbom.ts's `generate-sbom`, over an image/jar/zip/dir) for its built
6
+ * artifact, and/or an IaC config-BOM (./config-bom.ts's `extract-config-bom`)
7
+ * for its synthesized template. Each leaf BOM already validates as its own
8
+ * standalone SPDX/CycloneDX document (#613). This module composes every leaf
9
+ * BOM a component's build archive carries into **one** component-level BOM —
10
+ * a real assembly (CycloneDX `metadata.component` + nested `components` +
11
+ * `compositions` + `dependencies`, or SPDX `documentDescribes`-equivalent
12
+ * `CONTAINS`/`DEPENDS_ON` relationships), not a re-implementation of the
13
+ * writer: `aggregateComponentBom` below reads each leaf BOM's own structural
14
+ * inventory back out (it does not re-parse the leaf's serialized bytes) and
15
+ * hands the result to ./bom-writer.ts's `writeBom`, which gained
16
+ * `BomInput.subDocuments` support for exactly this purpose.
17
+ *
18
+ * **Why not just concatenate/parse the leaf documents' bytes.** The leaf BOM
19
+ * bytes are the source of truth for each artifact independently, but this
20
+ * module needs each leaf's package list as structured data to nest it
21
+ * correctly in the assembly — reparsing SPDX/CycloneDX JSON back into
22
+ * `BomPackage[]` would be a second, format-aware reader this codebase does
23
+ * not otherwise need. Callers therefore pass each leaf's already-known
24
+ * `BomPackage[]` (what `generate-sbom`/`extract-config-bom` built before
25
+ * calling `writeBom` themselves) plus the archive entry it produced — see
26
+ * `ComponentBomLeaf` below. A caller assembling a component's archive
27
+ * end-to-end has both on hand already; nothing here re-derives them from
28
+ * scratch.
29
+ *
30
+ * **Single-artifact = 1:1, multi-artifact = a real assembly.** A component
31
+ * with exactly one leaf BOM produces a component BOM whose only "assembly"
32
+ * is itself — see `aggregateComponentBom`'s single-leaf fast path, which
33
+ * still runs through the same writer and produces a valid document, just
34
+ * with an empty `subDocuments` composition (nothing to nest). A component
35
+ * with two or more leaves gets `subDocuments` populated, the real multi-leaf
36
+ * case #614 asks for.
37
+ *
38
+ * **Release-level rollup is out of scope here** (deferred per #614's note) —
39
+ * this module aggregates *within* one component's archive, never across
40
+ * components/releases.
41
+ */
42
+
43
+ import type { BuildArchiveEntry, BuildArchiveManifest } from "./build-archive";
44
+ import { sbomEntries } from "./build-archive";
45
+ import { writeBom, type BomPackage, type BomSubDocument } from "./bom-writer";
46
+ import { DEFAULT_SBOM_FORMAT, type SbomDocument, type SbomFormat } from "./sbom-generator";
47
+
48
+ // ── inputs ───────────────────────────────────────────────────────────────────
49
+
50
+ /**
51
+ * One leaf BOM a component produced, ready to fold into the component-level
52
+ * aggregate. `entry` is the `sbom`-kind `BuildArchiveEntry` `generate-sbom`/
53
+ * `extract-config-bom` already wrote into the manifest (carries `bomKind`,
54
+ * `subjectDigest`, `packageCount`, `generator`); `packages` is the same
55
+ * `BomPackage[]` that capability built before calling `writeBom` for its own
56
+ * standalone document.
57
+ */
58
+ export interface ComponentBomLeaf {
59
+ /** The `sbom`-kind manifest entry this leaf corresponds to (see `findSbomForSubject`/`findConfigBomForSubject`, ./build-archive.ts). */
60
+ entry: BuildArchiveEntry;
61
+ /** The leaf's own enumerated packages/components/resources. */
62
+ packages: BomPackage[];
63
+ }
64
+
65
+ export interface AggregateComponentBomInput {
66
+ /** Component name — becomes the aggregate BOM's subject name. */
67
+ component: string;
68
+ /** The build archive manifest this component's leaves live in — used to derive a stable subject id from `manifestDigest` and to validate that every leaf's `entry` actually belongs to this manifest. */
69
+ manifest: BuildArchiveManifest;
70
+ /** Every leaf BOM to compose. Order is preserved in the resulting `subDocuments`/nested components. Must be non-empty — a component with no BOM to aggregate has nothing for this function to do (see module doc's "skips cleanly" convention elsewhere in this directory). */
71
+ leaves: ComponentBomLeaf[];
72
+ /** BOM format for the aggregate document. Defaults to `DEFAULT_SBOM_FORMAT` (SPDX), same precedence convention as every other BOM-producing capability in this directory. */
73
+ format?: SbomFormat;
74
+ /** Clock override for deterministic tests. */
75
+ now?: () => Date;
76
+ }
77
+
78
+ export interface AggregateComponentBomOutput {
79
+ /** The composed component-level BOM document. */
80
+ bom: SbomDocument;
81
+ /** Total package/component count across every leaf (the sum `chant components status` surfaces, see ../../lifecycle/build-ledger.ts). */
82
+ totalPackageCount: number;
83
+ /** How many leaf BOMs were composed — 1 for a single-artifact component (still a valid document, just with nothing to nest), 2+ for a real multi-artifact assembly. */
84
+ leafCount: number;
85
+ }
86
+
87
+ /** Tool name recorded on the aggregate document — distinct from any individual leaf's own `generator` (e.g. "syft", "chant-config-bom-extractor"), since the aggregate is produced by this module, not by whichever tool generated a leaf. */
88
+ const COMPONENT_BOM_GENERATOR = "chant-component-bom-aggregator";
89
+
90
+ /**
91
+ * Compose a component's leaf BOMs (a software SBOM and/or an IaC config-BOM,
92
+ * per `input.leaves`) into one component-level BOM via ./bom-writer.ts's
93
+ * assembly support (`BomInput.subDocuments`). Pure — no I/O, no manifest
94
+ * mutation; the caller decides whether/where to write the result to disk
95
+ * (mirroring `extract-config-bom`'s `outDir` convention) and whether to fold
96
+ * it back into the archive as its own entry.
97
+ *
98
+ * Throws if `input.leaves` is empty — aggregation over zero leaves is a
99
+ * caller bug (compose only when at least one BOM exists), not a case this
100
+ * function silently no-ops on.
101
+ */
102
+ export function aggregateComponentBom(input: AggregateComponentBomInput): AggregateComponentBomOutput {
103
+ if (input.leaves.length === 0) {
104
+ throw new Error(
105
+ `aggregateComponentBom: component "${input.component}" has no leaf BOMs to aggregate — ` +
106
+ `only call this once at least one generate-sbom/extract-config-bom leaf exists`,
107
+ );
108
+ }
109
+
110
+ const format = input.format ?? DEFAULT_SBOM_FORMAT;
111
+ const totalPackageCount = input.leaves.reduce((sum, l) => sum + l.packages.length, 0);
112
+
113
+ // Single leaf: the component BOM *is* that leaf's BOM, structurally — no
114
+ // sub-document nesting needed, its packages become the aggregate's own
115
+ // flat package list directly (still a real, standalone valid document).
116
+ if (input.leaves.length === 1) {
117
+ const leaf = input.leaves[0]!;
118
+ const doc = writeBom(
119
+ format,
120
+ {
121
+ subjectName: input.component,
122
+ subjectId: input.manifest.manifestDigest,
123
+ packages: leaf.packages,
124
+ generator: COMPONENT_BOM_GENERATOR,
125
+ },
126
+ input.now,
127
+ );
128
+ return {
129
+ bom: { ...doc, packageCount: totalPackageCount, generator: COMPONENT_BOM_GENERATOR },
130
+ totalPackageCount,
131
+ leafCount: 1,
132
+ };
133
+ }
134
+
135
+ // Multi-leaf: a real assembly — each leaf becomes its own nested
136
+ // sub-document, keyed by which artifact digest it describes (its
137
+ // `subjectDigest`) so the assembly is traceable back to a specific
138
+ // BuildArchiveEntry, never flattened into one anonymous package list.
139
+ const subDocuments: BomSubDocument[] = input.leaves.map((leaf) => ({
140
+ subjectName: leafSubjectName(leaf),
141
+ subjectId: leaf.entry.subjectDigest ?? leaf.entry.digest,
142
+ packages: leaf.packages,
143
+ }));
144
+
145
+ const doc = writeBom(
146
+ format,
147
+ {
148
+ subjectName: input.component,
149
+ subjectId: input.manifest.manifestDigest,
150
+ packages: [],
151
+ generator: COMPONENT_BOM_GENERATOR,
152
+ subDocuments,
153
+ },
154
+ input.now,
155
+ );
156
+
157
+ return {
158
+ bom: { ...doc, packageCount: totalPackageCount, generator: COMPONENT_BOM_GENERATOR },
159
+ totalPackageCount,
160
+ leafCount: input.leaves.length,
161
+ };
162
+ }
163
+
164
+ /** Human-readable sub-document name: the leaf kind plus the archive path of the BOM entry itself, so two leaves of the same `bomKind` (unlikely but not disallowed) remain distinguishable. */
165
+ function leafSubjectName(leaf: ComponentBomLeaf): string {
166
+ const kind = leaf.entry.bomKind ?? "software";
167
+ return `${kind}:${leaf.entry.path}`;
168
+ }
169
+
170
+ // ── convenience: build leaves straight from a manifest ──────────────────────
171
+
172
+ /**
173
+ * Convenience for the common case: given a manifest that already carries
174
+ * `sbom`-kind entries (written by `generate-sbom`/`extract-config-bom`) and
175
+ * the `BomPackage[]` each one was built from (keyed by archive path, since
176
+ * that's the one stable identifier a caller has for "which leaf is this"),
177
+ * assemble the `ComponentBomLeaf[]` `aggregateComponentBom` expects. Kept
178
+ * separate from `aggregateComponentBom` itself so a caller that already has
179
+ * `ComponentBomLeaf[]` in hand (e.g. straight from the capabilities that just
180
+ * ran) can skip this lookup entirely.
181
+ */
182
+ export function componentBomLeavesFromManifest(
183
+ manifest: BuildArchiveManifest,
184
+ packagesByPath: Map<string, BomPackage[]>,
185
+ ): ComponentBomLeaf[] {
186
+ return sbomEntries(manifest)
187
+ .filter((entry) => packagesByPath.has(entry.path))
188
+ .map((entry) => ({ entry, packages: packagesByPath.get(entry.path)! }));
189
+ }
@@ -0,0 +1,49 @@
1
+ /**
2
+ * The starter verb set, grouped by family, per epic #551 and
3
+ * docs/components/capabilities.mdx. The pilot AWS leaves — `docker-build`,
4
+ * `publish-image`, `load-image-on-host`, `cfn-deploy`, `ecs-update-service`,
5
+ * `code-deploy`, `wait-for-stack`, `wait-steady-state`, `wait-cluster-healthy`
6
+ * — are real implementations over the injectable `CloudExecutor` (#557/#564,
7
+ * epic #551, see ./cloud-executor.ts); every other verb remains a typed stub
8
+ * (../capability.ts). `generate-sbom` (#606) is a real implementation over
9
+ * the injectable, artifact-type-keyed `SbomGenerator` (./sbom-generator.ts);
10
+ * `./lockfile-sbom-generator.ts` (#613) is a real, hermetic backend for it
11
+ * (lockfile/manifest parsing, no external tool), and `./tool-sbom-generator.ts`
12
+ * (#610) is a real, deep-scan backend for it (`syft`/`docker buildx --sbom`/
13
+ * `cyclonedx-maven`/`cdxgen`, shelling out through the injectable
14
+ * `ProcessRunner`, ./process-runner.ts). `extract-config-bom` (#613) parses a
15
+ * synthesized CloudFormation template, so it moved to the aws lexicon (#684);
16
+ * core keeps the agnostic SPDX/CycloneDX writer, ./bom-writer.ts, which both it
17
+ * and `generate-sbom` share. ./component-bom.ts
18
+ * (#614) composes a component's leaf BOMs into one component-level BOM over
19
+ * that same writer, and ./reproducibility.ts (#614) records per-artifact
20
+ * reproducibility + provenance on each `BuildArchiveEntry`. `publish-image`
21
+ * (#610) also gained a best-effort OCI-referrer attach step (`oras attach`,
22
+ * also via `ProcessRunner`) for a supplied SBOM/component BOM — see ./publish.ts.
23
+ * `sign`/`attest-provenance` (#622, ./sign.ts) are real implementations over
24
+ * the same injectable `ProcessRunner`: keyless-by-default `cosign sign`/
25
+ * `cosign attest` for signature + SLSA provenance, reusing #614's
26
+ * `ProvenanceLink` material. `verify` (#622, ./verify.ts) is the matching
27
+ * deploy-time gate — `cosign verify`/`verify-attestation` against a
28
+ * configured identity policy, mirroring ../../lint/policy.ts's `policyGate`.
29
+ */
30
+
31
+ export * from "./build-archive";
32
+ export * from "./build";
33
+ export * from "./bom-writer";
34
+ export * from "./sbom-generator";
35
+ export * from "./sbom";
36
+ export * from "./lockfile-sbom-generator";
37
+ export * from "./tool-sbom-generator";
38
+ export * from "./component-bom";
39
+ export * from "./reproducibility";
40
+ export * from "./sign";
41
+ export * from "./verify";
42
+ export * from "./vuln-scan";
43
+ export * from "./vex";
44
+ export * from "./license-policy";
45
+ export * from "./vuln-gate";
46
+ export * from "./wait-verify";
47
+ export * from "./shell";
48
+ export * from "./cloud-executor";
49
+ export * from "./process-runner";
@@ -0,0 +1,115 @@
1
+ /**
2
+ * License policy over an SBOM's declared licenses (#626). The SBOM already
3
+ * enumerates every component and its license; this evaluates those against an
4
+ * allow/deny list (e.g. block a copyleft license in a proprietary product).
5
+ *
6
+ * **Report-only by default.** License posture is context-dependent — GPL is a
7
+ * problem for a proprietary binary but fine for a GPL project — so the gate
8
+ * (./vuln-gate.ts) does NOT block on license violations unless a project opts
9
+ * in (`failOnLicense: true`). Extraction reads both SPDX
10
+ * (`packages[].licenseConcluded`/`licenseDeclared`) and CycloneDX
11
+ * (`components[].licenses[]`) without a spec dependency.
12
+ */
13
+
14
+ import type { SbomDocument } from "./sbom-generator";
15
+
16
+ /** A package/component and the license the SBOM declares for it. */
17
+ export interface PackageLicense {
18
+ package: string;
19
+ /** SPDX license expression/id as the SBOM reports it, or `"NOASSERTION"`/`""` when none is declared. */
20
+ license: string;
21
+ }
22
+
23
+ export interface LicensePolicy {
24
+ /** If set, ONLY these licenses are permitted — anything else is a violation. */
25
+ allow?: string[];
26
+ /** These licenses are violations (takes precedence; useful without an allowlist). */
27
+ deny?: string[];
28
+ }
29
+
30
+ /** One package whose license violates the policy. */
31
+ export interface LicenseViolation {
32
+ package: string;
33
+ license: string;
34
+ /** Which rule it broke. */
35
+ reason: "denied" | "not-allowed";
36
+ }
37
+
38
+ // ── extraction ───────────────────────────────────────────────────────────────
39
+
40
+ interface SpdxDoc {
41
+ packages?: Array<{ name?: string; licenseConcluded?: string; licenseDeclared?: string }>;
42
+ }
43
+ interface CycloneDxDoc {
44
+ components?: Array<{
45
+ name?: string;
46
+ licenses?: Array<{ license?: { id?: string; name?: string }; expression?: string }>;
47
+ }>;
48
+ }
49
+
50
+ /** Extract each package's declared license from an SBOM, dispatching on media type (SPDX vs CycloneDX). Never throws on shape — a package with no license reads as `"NOASSERTION"`. */
51
+ export function extractLicenses(sbom: SbomDocument): PackageLicense[] {
52
+ const doc = JSON.parse(sbom.bytes) as SpdxDoc & CycloneDxDoc;
53
+ if (sbom.format === "cyclonedx" || Array.isArray(doc.components)) {
54
+ return (doc.components ?? []).map((c) => {
55
+ const first = c.licenses?.[0];
56
+ const license = first?.expression ?? first?.license?.id ?? first?.license?.name ?? "NOASSERTION";
57
+ return { package: c.name ?? "unknown", license };
58
+ });
59
+ }
60
+ return (doc.packages ?? []).map((p) => ({
61
+ package: p.name ?? "unknown",
62
+ license: p.licenseConcluded && p.licenseConcluded !== "NOASSERTION" ? p.licenseConcluded : (p.licenseDeclared ?? "NOASSERTION"),
63
+ }));
64
+ }
65
+
66
+ // ── evaluation ───────────────────────────────────────────────────────────────
67
+
68
+ /** Case-insensitive membership, so `"mit"` matches `"MIT"`. */
69
+ function includesCi(list: string[], value: string): boolean {
70
+ const v = value.toLowerCase();
71
+ return list.some((x) => x.toLowerCase() === v);
72
+ }
73
+
74
+ /**
75
+ * Split an SPDX license expression into its component license atoms, so a
76
+ * deny/allow list matches a license hidden inside an expression like
77
+ * `"GPL-3.0 OR MIT"` or `"(Apache-2.0 AND MIT)"` — an exact-string compare on
78
+ * the whole expression would miss `GPL-3.0` and let it through. Conservative:
79
+ * we only need the *set* of licenses referenced, so we split on `OR`/`AND`,
80
+ * drop parentheses, and strip any `WITH <exception>` suffix. A single license
81
+ * id returns itself unchanged.
82
+ */
83
+ export function licenseAtoms(expr: string): string[] {
84
+ return expr
85
+ .replace(/[()]/g, " ")
86
+ .split(/\s+(?:OR|AND)\s+/i)
87
+ .map((a) => a.split(/\s+WITH\s+/i)[0].trim())
88
+ .filter(Boolean);
89
+ }
90
+
91
+ /**
92
+ * Evaluate an SBOM's licenses against `policy`. A package violates if ANY atom
93
+ * of its (possibly compound) SPDX license expression is in `deny`, or (when
94
+ * `allow` is set) any atom is not in `allow`. This is conservative on `OR`
95
+ * expressions — `"GPL-3.0 OR MIT"` counts as a `GPL-3.0` deny hit even though a
96
+ * consumer could choose MIT — because a policy gate should surface the presence
97
+ * of a denied license, not silently rely on the consumer picking the permissive
98
+ * branch. Packages with no declared license (`NOASSERTION`/empty) are not
99
+ * flagged — an undeclared license is not asserted to be denied.
100
+ */
101
+ export function evaluateLicensePolicy(sbom: SbomDocument, policy: LicensePolicy): LicenseViolation[] {
102
+ const out: LicenseViolation[] = [];
103
+ for (const { package: pkg, license } of extractLicenses(sbom)) {
104
+ if (!license || license === "NOASSERTION") continue;
105
+ const atoms = licenseAtoms(license);
106
+ if (policy.deny && atoms.some((a) => includesCi(policy.deny!, a))) {
107
+ out.push({ package: pkg, license, reason: "denied" });
108
+ continue;
109
+ }
110
+ if (policy.allow && policy.allow.length > 0 && atoms.some((a) => !includesCi(policy.allow!, a))) {
111
+ out.push({ package: pkg, license, reason: "not-allowed" });
112
+ }
113
+ }
114
+ return out;
115
+ }
@@ -0,0 +1,195 @@
1
+ /**
2
+ * Tests the hermetic lockfile/manifest-derived `SbomGenerator` (#613) against
3
+ * fixture lockfiles (./__fixtures__/package-lock.json,
4
+ * ./__fixtures__/pom.xml) — no network, no `syft`/`docker`/`mvn` invoked.
5
+ */
6
+
7
+ import { readFileSync, writeFileSync, mkdirSync, mkdtempSync, rmSync } from "node:fs";
8
+ import { tmpdir } from "node:os";
9
+ import { join } from "node:path";
10
+ import { describe, expect, it, afterEach } from "vitest";
11
+ import {
12
+ parseNpmPackageLock,
13
+ parsePomXml,
14
+ createLockfileSbomGenerator,
15
+ sbomOutputPath,
16
+ } from "./lockfile-sbom-generator";
17
+ import { SbomGeneratorNotImplementedError } from "./sbom-generator";
18
+
19
+ const FIXTURES_DIR = join(import.meta.dirname, "__fixtures__");
20
+ const NPM_LOCK = readFileSync(join(FIXTURES_DIR, "package-lock.json"), "utf-8");
21
+ const POM_XML = readFileSync(join(FIXTURES_DIR, "pom.xml"), "utf-8");
22
+
23
+ describe("parseNpmPackageLock (#613)", () => {
24
+ it("extracts every non-root package with its resolved version", () => {
25
+ const packages = parseNpmPackageLock(NPM_LOCK);
26
+ const names = packages.map((p) => p.name).sort();
27
+ expect(names).toEqual(["accepts", "body-parser", "express", "left-pad", "mocha"]);
28
+ const express = packages.find((p) => p.name === "express")!;
29
+ expect(express.version).toBe("4.19.2");
30
+ expect(express.type).toBe("npm");
31
+ expect(express.purl).toBe("pkg:npm/express@4.19.2");
32
+ });
33
+
34
+ it("records declared dependency edges for DEPENDS_ON relationships", () => {
35
+ const packages = parseNpmPackageLock(NPM_LOCK);
36
+ const express = packages.find((p) => p.name === "express")!;
37
+ expect(express.dependsOn).toEqual(expect.arrayContaining(["accepts", "body-parser"]));
38
+ });
39
+
40
+ it("does not include the root project itself as a dependency", () => {
41
+ const packages = parseNpmPackageLock(NPM_LOCK);
42
+ expect(packages.find((p) => p.name === "fixture-search-service")).toBeUndefined();
43
+ });
44
+
45
+ it("falls back to the v1 nested dependencies tree when packages is absent", () => {
46
+ const v1Lock = JSON.stringify({
47
+ name: "fixture-v1",
48
+ version: "1.0.0",
49
+ dependencies: {
50
+ "left-pad": { version: "1.3.0", requires: {} },
51
+ chalk: { version: "4.1.2", requires: { "left-pad": "1.3.0" } },
52
+ },
53
+ });
54
+ const packages = parseNpmPackageLock(v1Lock);
55
+ expect(packages.map((p) => p.name).sort()).toEqual(["chalk", "left-pad"]);
56
+ expect(packages.find((p) => p.name === "chalk")!.dependsOn).toEqual(["left-pad"]);
57
+ });
58
+ });
59
+
60
+ describe("parsePomXml (#613)", () => {
61
+ it("extracts every declared <dependency> from the top-level <dependencies> block", () => {
62
+ const packages = parsePomXml(POM_XML);
63
+ const names = packages.map((p) => p.name).sort();
64
+ expect(names).toEqual([
65
+ "com.fasterxml.jackson.core:jackson-databind",
66
+ "org.apache.spark:spark-core_2.12",
67
+ "org.junit.jupiter:junit-jupiter",
68
+ ]);
69
+ });
70
+
71
+ it("does not pull dependencies declared only in <dependencyManagement>", () => {
72
+ const packages = parsePomXml(POM_XML);
73
+ expect(packages.find((p) => p.name.includes("should-not-appear"))).toBeUndefined();
74
+ });
75
+
76
+ it("derives a maven purl from groupId/artifactId/version", () => {
77
+ const packages = parsePomXml(POM_XML);
78
+ const spark = packages.find((p) => p.name === "org.apache.spark:spark-core_2.12")!;
79
+ expect(spark.version).toBe("3.5.1");
80
+ expect(spark.type).toBe("maven");
81
+ expect(spark.purl).toBe("pkg:maven/org.apache.spark/spark-core_2.12@3.5.1");
82
+ });
83
+
84
+ it("returns an empty list when there is no <dependencies> block", () => {
85
+ expect(parsePomXml("<project></project>")).toEqual([]);
86
+ });
87
+ });
88
+
89
+ describe("createLockfileSbomGenerator (#613) — hermetic SbomGenerator backend", () => {
90
+ let tmpDir: string;
91
+
92
+ afterEach(() => {
93
+ if (tmpDir) rmSync(tmpDir, { recursive: true, force: true });
94
+ });
95
+
96
+ function withNpmFixture(): string {
97
+ tmpDir = mkdtempSync(join(tmpdir(), "chant-sbom-npm-"));
98
+ writeFileSync(join(tmpDir, "package-lock.json"), NPM_LOCK);
99
+ return tmpDir;
100
+ }
101
+
102
+ function withMavenFixture(): string {
103
+ tmpDir = mkdtempSync(join(tmpdir(), "chant-sbom-maven-"));
104
+ writeFileSync(join(tmpDir, "pom.xml"), POM_XML);
105
+ return tmpDir;
106
+ }
107
+
108
+ it("forDir scans a directory's package-lock.json and emits a valid SPDX doc by default", async () => {
109
+ const dir = withNpmFixture();
110
+ const generator = createLockfileSbomGenerator();
111
+ const doc = await generator.forDir({ path: dir });
112
+ expect(doc.format).toBe("spdx");
113
+ expect(doc.mediaType).toBe("application/spdx+json");
114
+ expect(doc.packageCount).toBe(5);
115
+ expect(doc.generator).toBe("chant-lockfile-sbom/package-lock.json");
116
+ const parsed = JSON.parse(doc.bytes);
117
+ expect(parsed.spdxVersion).toBe("SPDX-2.3");
118
+ });
119
+
120
+ it("forDir scans a directory's pom.xml when no package-lock.json is present", async () => {
121
+ const dir = withMavenFixture();
122
+ const generator = createLockfileSbomGenerator();
123
+ const doc = await generator.forDir({ path: dir });
124
+ expect(doc.packageCount).toBe(3);
125
+ expect(doc.generator).toBe("chant-lockfile-sbom/pom.xml");
126
+ });
127
+
128
+ it("honors an explicit cyclonedx format request", async () => {
129
+ const dir = withNpmFixture();
130
+ const generator = createLockfileSbomGenerator();
131
+ const doc = await generator.forDir({ path: dir, format: "cyclonedx" });
132
+ expect(doc.format).toBe("cyclonedx");
133
+ expect(doc.mediaType).toBe("application/vnd.cyclonedx+json");
134
+ expect(JSON.parse(doc.bytes).bomFormat).toBe("CycloneDX");
135
+ });
136
+
137
+ it("writes the SBOM to disk as sbom.<format>.json alongside the scanned directory", async () => {
138
+ const dir = withNpmFixture();
139
+ const generator = createLockfileSbomGenerator();
140
+ await generator.forDir({ path: dir });
141
+ const written = readFileSync(sbomOutputPath(dir, "spdx"), "utf-8");
142
+ expect(JSON.parse(written).spdxVersion).toBe("SPDX-2.3");
143
+ });
144
+
145
+ it("writes to a distinct outDir when configured, instead of alongside the scanned directory", async () => {
146
+ const dir = withNpmFixture();
147
+ const outDir = join(dir, "out");
148
+ const generator = createLockfileSbomGenerator({ outDir });
149
+ await generator.forDir({ path: dir });
150
+ const written = readFileSync(sbomOutputPath(outDir, "spdx"), "utf-8");
151
+ expect(JSON.parse(written).spdxVersion).toBe("SPDX-2.3");
152
+ });
153
+
154
+ it("forJar/forZip scan the artifact's containing directory for a lockfile", async () => {
155
+ const dir = withNpmFixture();
156
+ const generator = createLockfileSbomGenerator();
157
+ const jarDoc = await generator.forJar({ jarPath: join(dir, "lib.jar"), digest: "sha256:jar1" });
158
+ expect(jarDoc.packageCount).toBe(5);
159
+ const zipDoc = await generator.forZip({ zipPath: join(dir, "fn.zip"), digest: "sha256:zip1" });
160
+ expect(zipDoc.packageCount).toBe(5);
161
+ });
162
+
163
+ it("forImage throws SbomGeneratorNotImplementedError — an image's base layers are outside a lockfile's scope", async () => {
164
+ const generator = createLockfileSbomGenerator();
165
+ await expect(generator.forImage({ imagePath: "x.tar", digest: "sha256:1" })).rejects.toBeInstanceOf(
166
+ SbomGeneratorNotImplementedError,
167
+ );
168
+ });
169
+
170
+ it("throws a clear error when no supported lockfile/manifest is found", async () => {
171
+ tmpDir = mkdtempSync(join(tmpdir(), "chant-sbom-empty-"));
172
+ const generator = createLockfileSbomGenerator();
173
+ await expect(generator.forDir({ path: tmpDir })).rejects.toThrow(/no supported lockfile/);
174
+ });
175
+
176
+ it("two generations from identical (path + content) input produce the same digest/bytes (content-addressed)", async () => {
177
+ // Use two fixed, identically-named subdirectories under one root so
178
+ // `path` (which feeds both subjectName and, absent a digest, subjectId)
179
+ // is identical across both scans — isolating the assertion to "same
180
+ // input, same output" rather than incidentally varying by directory
181
+ // identity (which mkdtempSync's random suffix would otherwise do).
182
+ const root = mkdtempSync(join(tmpdir(), "chant-sbom-determinism-"));
183
+ tmpDir = root;
184
+ const dirA = join(root, "component");
185
+ const dirB = join(root, "component"); // same path, scanned twice in sequence
186
+ mkdirSync(dirA, { recursive: true });
187
+ writeFileSync(join(dirA, "package-lock.json"), NPM_LOCK);
188
+
189
+ const fixedNow = () => new Date("2026-01-01T00:00:00.000Z");
190
+ const docA = await createLockfileSbomGenerator({ now: fixedNow }).forDir({ path: dirA });
191
+ const docB = await createLockfileSbomGenerator({ now: fixedNow }).forDir({ path: dirB });
192
+
193
+ expect(docA.bytes).toBe(docB.bytes);
194
+ });
195
+ });