@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,102 @@
1
+ /**
2
+ * `MockCloudExecutor` — an in-memory fake of the *agnostic* `CloudExecutor`
3
+ * (../cloud-executor.ts) for tests: the `docker` build/push client and the
4
+ * Neo4j bolt probe, the two clients core still owns. No live docker, ever, in
5
+ * a test run — every method records its call and returns a canned result.
6
+ *
7
+ * The AWS clients' mock moved to the aws lexicon alongside their
8
+ * implementations (`@intentius/chant-lexicon-aws/components`).
9
+ */
10
+
11
+ import type { CloudExecutor, DockerBuildArgs, DockerLoadArgs, DockerPushArgs, DockerSaveArgs, DockerTagArgs, Neo4jProbeArgs } from "../cloud-executor";
12
+
13
+ export interface RecordedCall {
14
+ client: string;
15
+ method: string;
16
+ args: unknown;
17
+ }
18
+
19
+ export interface FakeClusterConfig {
20
+ /** Number of bolt endpoints (out of however many `cluster` lists) that report healthy. Default: all of them. */
21
+ healthyCount?: number;
22
+ }
23
+
24
+ export interface MockCloudExecutorOptions {
25
+ /** Neo4j cluster health, keyed by the comma-joined endpoint string `wait-cluster-healthy` probes. */
26
+ clusters?: Record<string, FakeClusterConfig>;
27
+ /** Force every docker call to fail (simulates a build/push failure). */
28
+ failDocker?: boolean;
29
+ }
30
+
31
+ /** An injected agnostic `CloudExecutor` plus the call log and cluster-health controls tests use. */
32
+ export interface MockCloudExecutor {
33
+ executor: CloudExecutor;
34
+ calls: RecordedCall[];
35
+ /** Change how many cluster members report healthy after construction (simulates a follower catching up). */
36
+ setClusterHealth(cluster: string, healthyCount: number): void;
37
+ }
38
+
39
+ /** Build a fresh mock agnostic `CloudExecutor`. Every method is deterministic and synchronous-fast — no real polling delay. */
40
+ export function createMockCloudExecutor(options: MockCloudExecutorOptions = {}): MockCloudExecutor {
41
+ const calls: RecordedCall[] = [];
42
+ const record = (client: string, method: string, args: unknown) => calls.push({ client, method, args });
43
+
44
+ const clusters = new Map<string, number>(
45
+ Object.entries(options.clusters ?? {}).map(([k, v]) => [k, v.healthyCount ?? Number.MAX_SAFE_INTEGER]),
46
+ );
47
+
48
+ const docker: CloudExecutor["docker"] = {
49
+ async build(args: DockerBuildArgs) {
50
+ record("docker", "build", args);
51
+ if (options.failDocker) throw new Error(`docker build failed for ${args.context}`);
52
+ return { digest: `sha256:${fakeDigest("build", args.context, args.tag)}` };
53
+ },
54
+ async save(args: DockerSaveArgs) {
55
+ record("docker", "save", args);
56
+ },
57
+ async load(args: DockerLoadArgs) {
58
+ record("docker", "load", args);
59
+ return { digest: `sha256:${fakeDigest("load", args.inFile)}` };
60
+ },
61
+ async tag(args: DockerTagArgs) {
62
+ record("docker", "tag", args);
63
+ },
64
+ async push(args: DockerPushArgs) {
65
+ record("docker", "push", args);
66
+ if (options.failDocker) throw new Error(`docker push failed for ${args.image}`);
67
+ return { digest: `sha256:${fakeDigest("push", args.image)}` };
68
+ },
69
+ async remoteDigest(image: string) {
70
+ record("docker", "remoteDigest", image);
71
+ return `sha256:${fakeDigest("remote", image)}`;
72
+ },
73
+ };
74
+
75
+ const neo4j: CloudExecutor["neo4j"] = {
76
+ async probe(args: Neo4jProbeArgs) {
77
+ record("neo4j", "probe", args);
78
+ const endpoints = args.cluster
79
+ .split(",")
80
+ .map((s) => s.trim())
81
+ .filter(Boolean);
82
+ const healthy = clusters.get(args.cluster) ?? endpoints.length;
83
+ return { healthyCount: Math.min(healthy, endpoints.length), total: endpoints.length };
84
+ },
85
+ };
86
+
87
+ return {
88
+ executor: { docker, neo4j },
89
+ calls,
90
+ setClusterHealth: (cluster, healthyCount) => clusters.set(cluster, healthyCount),
91
+ };
92
+ }
93
+
94
+ /** Deterministic fake digest suffix derived from its inputs — stable across calls with the same args, distinct otherwise. */
95
+ function fakeDigest(...parts: string[]): string {
96
+ let hash = 0;
97
+ const input = parts.join("|");
98
+ for (let i = 0; i < input.length; i++) {
99
+ hash = (hash * 31 + input.charCodeAt(i)) | 0;
100
+ }
101
+ return Math.abs(hash).toString(16).padStart(8, "0").repeat(4).slice(0, 64);
102
+ }
@@ -0,0 +1,79 @@
1
+ /**
2
+ * `MockProcessRunner` — an in-memory fake of `ProcessRunner`
3
+ * (../process-runner.ts) for tests. No live `syft`/`buildx`/
4
+ * `cyclonedx-maven`/`cdxgen`/`oras`, no child process spawned at all: every
5
+ * call is recorded, and `run`/`available` return scripted results — the same
6
+ * convention `../__tests__/mock-cloud-executor.ts` uses for `CloudExecutor`.
7
+ *
8
+ * Every test that exercises a #610 real backend (`ToolSbomGenerator`,
9
+ * `OrasReferrerLookup`, `publish-image`'s referrer-attach step) builds one
10
+ * `MockProcessRunner`, scripts which tools are "installed" and what each
11
+ * command's canned stdout is, and passes it in — never touching the real,
12
+ * `child_process`-backed runner.
13
+ */
14
+
15
+ import type { ProcessResult, ProcessRunner, RunOptions } from "../process-runner";
16
+
17
+ export interface RecordedProcessCall {
18
+ command: string;
19
+ options?: RunOptions;
20
+ }
21
+
22
+ export interface MockProcessRunnerOptions {
23
+ /** Which tools `available()` reports as installed. Default: every tool is available (opt a test into "missing tool" by naming it here with `false`, or by omitting it when `defaultAvailable` is `false`). */
24
+ tools?: Record<string, boolean>;
25
+ /** `available()`'s answer for a tool not named in `tools`. Default: `true` (most tests aren't about the missing-tool path). */
26
+ defaultAvailable?: boolean;
27
+ /**
28
+ * Canned stdout per command, matched by a substring (the first entry whose
29
+ * key is included in the command string wins) — this is deliberately loose
30
+ * rather than an exact-string map, since the SUT constructs full shell
31
+ * command lines the test doesn't want to hand-reconstruct byte-for-byte
32
+ * just to script a response.
33
+ */
34
+ responses?: Record<string, string>;
35
+ /** Command substrings that should reject (simulates the tool exiting non-zero), mapped to the error message. */
36
+ failures?: Record<string, string>;
37
+ }
38
+
39
+ export interface MockProcessRunner {
40
+ runner: ProcessRunner;
41
+ calls: RecordedProcessCall[];
42
+ /** Change a tool's reported availability after construction. */
43
+ setAvailable(tool: string, available: boolean): void;
44
+ /** Script (or replace) a command substring's canned stdout after construction. */
45
+ setResponse(commandSubstring: string, stdout: string): void;
46
+ }
47
+
48
+ /** Build a fresh mock `ProcessRunner`. Every method is deterministic and synchronous-fast — no real process spawned, no real delay. */
49
+ export function createMockProcessRunner(options: MockProcessRunnerOptions = {}): MockProcessRunner {
50
+ const calls: RecordedProcessCall[] = [];
51
+ const tools = new Map<string, boolean>(Object.entries(options.tools ?? {}));
52
+ const responses = new Map<string, string>(Object.entries(options.responses ?? {}));
53
+ const failures = new Map<string, string>(Object.entries(options.failures ?? {}));
54
+ const defaultAvailable = options.defaultAvailable ?? true;
55
+
56
+ const runner: ProcessRunner = {
57
+ async run(command: string, runOptions?: RunOptions): Promise<ProcessResult> {
58
+ calls.push({ command, options: runOptions });
59
+ for (const [substring, message] of failures) {
60
+ if (command.includes(substring)) throw new Error(message);
61
+ }
62
+ for (const [substring, stdout] of responses) {
63
+ if (command.includes(substring)) return { stdout, stderr: "" };
64
+ }
65
+ return { stdout: "", stderr: "" };
66
+ },
67
+ async available(tool: string): Promise<boolean> {
68
+ calls.push({ command: `command -v ${tool}` });
69
+ return tools.has(tool) ? tools.get(tool)! : defaultAvailable;
70
+ },
71
+ };
72
+
73
+ return {
74
+ runner,
75
+ calls,
76
+ setAvailable: (tool, available) => tools.set(tool, available),
77
+ setResponse: (commandSubstring, stdout) => responses.set(commandSubstring, stdout),
78
+ };
79
+ }
@@ -0,0 +1,85 @@
1
+ /**
2
+ * `MockSbomGenerator` — an in-memory fake of `SbomGenerator`
3
+ * (../sbom-generator.ts) for tests. No live `syft`/`docker buildx`/
4
+ * `cyclonedx-maven`/`cdxgen`, no network: every method records its call and
5
+ * returns a deterministic, canned `SbomDocument` derived from its inputs, the
6
+ * same convention `../__tests__/mock-cloud-executor.ts` uses for
7
+ * `CloudExecutor` (a fake digest suffix derived from the call's arguments,
8
+ * stable across calls with the same args).
9
+ *
10
+ * Every test that exercises `generate-sbom` (../sbom.ts) builds one
11
+ * `MockSbomGenerator` and passes it to `createGenerateSbomCapability`,
12
+ * never touching a real scanner.
13
+ */
14
+
15
+ import type {
16
+ GenerateDirSbomInput,
17
+ GenerateImageSbomInput,
18
+ GenerateJarSbomInput,
19
+ GenerateZipSbomInput,
20
+ SbomDocument,
21
+ SbomFormat,
22
+ SbomGenerator,
23
+ } from "../sbom-generator";
24
+ import { SBOM_MEDIA_TYPES, DEFAULT_SBOM_FORMAT } from "../sbom-generator";
25
+
26
+ export interface RecordedSbomCall {
27
+ method: "forImage" | "forJar" | "forZip" | "forDir";
28
+ args: unknown;
29
+ }
30
+
31
+ export interface MockSbomGeneratorOptions {
32
+ /** Force every call to fail (simulates a scanner crash/timeout). */
33
+ fail?: boolean;
34
+ /** Package count reported for every generated SBOM. Default: 3. */
35
+ packageCount?: number;
36
+ /** Generator/tool name reported. Default: "mock-syft". */
37
+ generatorName?: string;
38
+ }
39
+
40
+ export interface MockSbomGenerator {
41
+ generator: SbomGenerator;
42
+ calls: RecordedSbomCall[];
43
+ }
44
+
45
+ /** Build a fresh mock `SbomGenerator`. Every method is deterministic and synchronous-fast — no real scan delay. */
46
+ export function createMockSbomGenerator(options: MockSbomGeneratorOptions = {}): MockSbomGenerator {
47
+ const calls: RecordedSbomCall[] = [];
48
+ const record = (method: RecordedSbomCall["method"], args: unknown) => calls.push({ method, args });
49
+
50
+ function fakeSbom(kind: string, subject: string, format: SbomFormat | undefined, generatorName: string): SbomDocument {
51
+ const resolvedFormat = format ?? DEFAULT_SBOM_FORMAT;
52
+ return {
53
+ format: resolvedFormat,
54
+ mediaType: SBOM_MEDIA_TYPES[resolvedFormat],
55
+ bytes: JSON.stringify({ fake: true, kind, subject, format: resolvedFormat }),
56
+ packageCount: options.packageCount ?? 3,
57
+ generator: options.generatorName ?? generatorName,
58
+ };
59
+ }
60
+
61
+ const generator: SbomGenerator = {
62
+ async forImage(input: GenerateImageSbomInput) {
63
+ record("forImage", input);
64
+ if (options.fail) throw new Error(`sbom generation failed for image ${input.imagePath}`);
65
+ return fakeSbom("image", input.imagePath, input.format, "mock-buildkit");
66
+ },
67
+ async forJar(input: GenerateJarSbomInput) {
68
+ record("forJar", input);
69
+ if (options.fail) throw new Error(`sbom generation failed for jar ${input.jarPath}`);
70
+ return fakeSbom("jar", input.jarPath, input.format, "mock-syft");
71
+ },
72
+ async forZip(input: GenerateZipSbomInput) {
73
+ record("forZip", input);
74
+ if (options.fail) throw new Error(`sbom generation failed for zip ${input.zipPath}`);
75
+ return fakeSbom("zip", input.zipPath, input.format, "mock-syft");
76
+ },
77
+ async forDir(input: GenerateDirSbomInput) {
78
+ record("forDir", input);
79
+ if (options.fail) throw new Error(`sbom generation failed for dir ${input.path}`);
80
+ return fakeSbom("dir", input.path, input.format, "mock-cdxgen");
81
+ },
82
+ };
83
+
84
+ return { generator, calls };
85
+ }
@@ -0,0 +1,331 @@
1
+ /**
2
+ * Validates the native BOM writer (#613) against the *actual* published
3
+ * SPDX-2.3 and CycloneDX-1.5 JSON Schemas — vendored as fixtures under
4
+ * ./__fixtures__/schemas/, the same "vendor the spec schema as a devDep test
5
+ * fixture" convention #553 used for the Component contract schema and #607
6
+ * used for the GitLab CI schema. No network at test time: the schemas are
7
+ * committed, read from disk, and compiled once with `ajv`.
8
+ *
9
+ * Source of the vendored schemas (for provenance, not fetched at test time):
10
+ * - spdx-2.3.schema.json: https://github.com/spdx/spdx-spec/blob/support/2.3/schemas/spdx-schema.json
11
+ * - cyclonedx-1.5.schema.json: https://github.com/CycloneDX/specification/blob/1.5/schema/bom-1.5.schema.json
12
+ * - jsf-0.82.schema.json / cyclonedx-spdx-license.schema.json: CycloneDX's
13
+ * own `$ref` dependencies (renamed from the upstream `spdx.schema.json` to
14
+ * avoid confusion with SPDX's own document schema above, which is a
15
+ * different document entirely).
16
+ */
17
+
18
+ import { readFileSync } from "node:fs";
19
+ import { join } from "node:path";
20
+ import { describe, expect, it } from "vitest";
21
+ import Ajv2020 from "ajv/dist/2020";
22
+ import Ajv from "ajv";
23
+ import { writeSpdx, writeCycloneDx, writeBom, type BomInput, type BomPackage, type BomSubDocument } from "./bom-writer";
24
+
25
+ const SCHEMAS_DIR = join(import.meta.dirname, "__fixtures__/schemas");
26
+
27
+ function loadSchema(name: string): unknown {
28
+ return JSON.parse(readFileSync(join(SCHEMAS_DIR, name), "utf-8"));
29
+ }
30
+
31
+ const FIXED_NOW = () => new Date("2026-01-01T00:00:00.000Z");
32
+
33
+ const samplePackages: BomPackage[] = [
34
+ { name: "left-pad", version: "1.3.0", type: "npm", purl: "pkg:npm/left-pad@1.3.0" },
35
+ { name: "express", version: "4.19.2", type: "npm", purl: "pkg:npm/express@4.19.2", dependsOn: ["left-pad"] },
36
+ ];
37
+
38
+ const sampleInput: BomInput = {
39
+ subjectName: "search-service",
40
+ subjectVersion: "1.0.0",
41
+ subjectId: "sha256:" + "a".repeat(64),
42
+ packages: samplePackages,
43
+ generator: "chant-lockfile-sbom/package-lock.json",
44
+ };
45
+
46
+ describe("writeSpdx (#613) — structural validation against the real SPDX-2.3 JSON Schema", () => {
47
+ const schema = loadSchema("spdx-2.3.schema.json");
48
+ const ajv = new Ajv2020({ strict: false, allErrors: true });
49
+ // The vendored spdx-2.3.schema.json declares draft-07 ($schema), while
50
+ // Ajv2020 is instantiated for draft 2020-12 fixtures elsewhere in this
51
+ // repo (component-schema.test.ts) — use a plain Ajv (draft-07 capable) for
52
+ // this one instead, matching the schema's own declared dialect.
53
+ const ajvDraft07 = new Ajv({ strict: false, allErrors: true });
54
+ const validate = ajvDraft07.compile(schema as object);
55
+
56
+ it("is a valid JSON Schema document", () => {
57
+ expect(ajv.validateSchema).toBeDefined();
58
+ expect((schema as { $schema: string }).$schema).toContain("draft-07");
59
+ });
60
+
61
+ it("produces a document with every SPDX-2.3-required top-level field", () => {
62
+ const bytes = writeSpdx(sampleInput, FIXED_NOW);
63
+ const doc = JSON.parse(bytes);
64
+ expect(doc.spdxVersion).toBe("SPDX-2.3");
65
+ expect(doc.SPDXID).toBe("SPDXRef-DOCUMENT");
66
+ expect(doc.documentNamespace).toEqual(expect.stringMatching(/^https:\/\//));
67
+ expect(doc.name).toBe("search-service");
68
+ expect(Array.isArray(doc.packages)).toBe(true);
69
+ expect(doc.packages.length).toBeGreaterThan(0);
70
+ for (const pkg of doc.packages) {
71
+ expect(pkg.name).toBeTruthy();
72
+ expect(pkg.SPDXID).toEqual(expect.stringMatching(/^SPDXRef-/));
73
+ }
74
+ });
75
+
76
+ it("validates against the real SPDX-2.3 JSON Schema", () => {
77
+ const bytes = writeSpdx(sampleInput, FIXED_NOW);
78
+ const doc = JSON.parse(bytes);
79
+ const valid = validate(doc);
80
+ if (!valid) throw new Error(`SPDX document failed schema validation: ${ajvDraft07.errorsText(validate.errors)}`);
81
+ expect(valid).toBe(true);
82
+ });
83
+
84
+ it("includes a DESCRIBES relationship from the document to the subject package, and DEPENDS_ON edges for declared deps", () => {
85
+ const bytes = writeSpdx(sampleInput, FIXED_NOW);
86
+ const doc = JSON.parse(bytes);
87
+ expect(doc.relationships).toContainEqual(
88
+ expect.objectContaining({ relationshipType: "DESCRIBES", spdxElementId: "SPDXRef-DOCUMENT" }),
89
+ );
90
+ expect(doc.relationships).toContainEqual(
91
+ expect.objectContaining({ relationshipType: "DEPENDS_ON", relatedSpdxElement: "SPDXRef-Package-left-pad" }),
92
+ );
93
+ });
94
+
95
+ it("is deterministic — identical input produces byte-identical output", () => {
96
+ const a = writeSpdx(sampleInput, FIXED_NOW);
97
+ const b = writeSpdx(sampleInput, FIXED_NOW);
98
+ expect(a).toBe(b);
99
+ });
100
+
101
+ it("handles an empty package list (a subject with no declared deps) without failing schema validation", () => {
102
+ const bytes = writeSpdx({ ...sampleInput, packages: [] }, FIXED_NOW);
103
+ const doc = JSON.parse(bytes);
104
+ expect(validate(doc)).toBe(true);
105
+ });
106
+ });
107
+
108
+ describe("writeCycloneDx (#613) — structural validation against the real CycloneDX-1.5 JSON Schema", () => {
109
+ const cdxSchema = loadSchema("cyclonedx-1.5.schema.json");
110
+ const jsfSchema = loadSchema("jsf-0.82.schema.json");
111
+ const spdxLicenseSchema = loadSchema("cyclonedx-spdx-license.schema.json");
112
+
113
+ const ajv = new Ajv({ strict: false, allErrors: true });
114
+ ajv.addSchema(jsfSchema as object);
115
+ ajv.addSchema(spdxLicenseSchema as object);
116
+ const validate = ajv.compile(cdxSchema as object);
117
+
118
+ it("produces a document with every CycloneDX-1.5-required top-level field", () => {
119
+ const bytes = writeCycloneDx(sampleInput, FIXED_NOW);
120
+ const doc = JSON.parse(bytes);
121
+ expect(doc.bomFormat).toBe("CycloneDX");
122
+ expect(doc.specVersion).toBe("1.5");
123
+ expect(doc.serialNumber).toEqual(expect.stringMatching(/^urn:uuid:/));
124
+ expect(typeof doc.version).toBe("number");
125
+ expect(doc.metadata?.component?.name).toBe("search-service");
126
+ expect(Array.isArray(doc.components)).toBe(true);
127
+ for (const c of doc.components) {
128
+ expect(["application", "library", "framework", "container", "operating-system", "device", "firmware", "file"]).toContain(
129
+ c.type,
130
+ );
131
+ expect(c.name).toBeTruthy();
132
+ }
133
+ });
134
+
135
+ it("validates against the real CycloneDX-1.5 JSON Schema", () => {
136
+ const bytes = writeCycloneDx(sampleInput, FIXED_NOW);
137
+ const doc = JSON.parse(bytes);
138
+ const valid = validate(doc);
139
+ if (!valid) throw new Error(`CycloneDX document failed schema validation: ${ajv.errorsText(validate.errors)}`);
140
+ expect(valid).toBe(true);
141
+ });
142
+
143
+ it("includes a dependencies section: the root subject depends on every listed component, plus declared inter-component deps", () => {
144
+ const bytes = writeCycloneDx(sampleInput, FIXED_NOW);
145
+ const doc = JSON.parse(bytes);
146
+ const rootDep = doc.dependencies.find((d: { ref: string }) => d.ref === "subject:search-service");
147
+ expect(rootDep.dependsOn).toEqual(expect.arrayContaining(["left-pad", "express"]));
148
+ const expressDep = doc.dependencies.find((d: { ref: string }) => d.ref === "express");
149
+ expect(expressDep.dependsOn).toEqual(["left-pad"]);
150
+ });
151
+
152
+ it("is deterministic — identical input produces a byte-identical serialNumber and document", () => {
153
+ const a = writeCycloneDx(sampleInput, FIXED_NOW);
154
+ const b = writeCycloneDx(sampleInput, FIXED_NOW);
155
+ expect(a).toBe(b);
156
+ });
157
+
158
+ it("handles an empty package list without failing schema validation", () => {
159
+ const bytes = writeCycloneDx({ ...sampleInput, packages: [] }, FIXED_NOW);
160
+ const doc = JSON.parse(bytes);
161
+ expect(validate(doc)).toBe(true);
162
+ });
163
+ });
164
+
165
+ describe("writeBom (#613) — format dispatch wrapping the project-wide {format, mediaType, bytes} doc shape", () => {
166
+ it("dispatches spdx to writeSpdx and sets the SPDX media type", () => {
167
+ const doc = writeBom("spdx", sampleInput, FIXED_NOW);
168
+ expect(doc.format).toBe("spdx");
169
+ expect(doc.mediaType).toBe("application/spdx+json");
170
+ expect(JSON.parse(doc.bytes).spdxVersion).toBe("SPDX-2.3");
171
+ });
172
+
173
+ it("dispatches cyclonedx to writeCycloneDx and sets the CycloneDX media type", () => {
174
+ const doc = writeBom("cyclonedx", sampleInput, FIXED_NOW);
175
+ expect(doc.format).toBe("cyclonedx");
176
+ expect(doc.mediaType).toBe("application/vnd.cyclonedx+json");
177
+ expect(JSON.parse(doc.bytes).bomFormat).toBe("CycloneDX");
178
+ });
179
+ });
180
+
181
+ // ── assembly: BomInput.subDocuments (#614) ──────────────────────────────────
182
+
183
+ const subDocuments: BomSubDocument[] = [
184
+ {
185
+ subjectName: "software:image.tar.sbom.json",
186
+ subjectId: "sha256:" + "b".repeat(64),
187
+ packages: [{ name: "left-pad", version: "1.3.0", type: "npm" }],
188
+ },
189
+ {
190
+ subjectName: "config:search.template.json.config-bom.json",
191
+ subjectId: "sha256:" + "c".repeat(64),
192
+ packages: [{ name: "SearchBucket", version: "AWS::S3::Bucket", type: "config" }],
193
+ },
194
+ ];
195
+
196
+ const assemblyInput: BomInput = {
197
+ subjectName: "search-service",
198
+ subjectId: "sha256:" + "d".repeat(64),
199
+ packages: [],
200
+ generator: "chant-component-bom-aggregator",
201
+ subDocuments,
202
+ };
203
+
204
+ describe("writeSpdx assembly (#614) — CONTAINS/DEPENDS_ON relationships for a multi-leaf component BOM", () => {
205
+ const schema = loadSchema("spdx-2.3.schema.json");
206
+ const ajvDraft07 = new Ajv({ strict: false, allErrors: true });
207
+ const validate = ajvDraft07.compile(schema as object);
208
+
209
+ it("validates against the real SPDX-2.3 JSON Schema", () => {
210
+ const bytes = writeSpdx(assemblyInput, FIXED_NOW);
211
+ const doc = JSON.parse(bytes);
212
+ const valid = validate(doc);
213
+ if (!valid) throw new Error(`SPDX assembly failed schema validation: ${ajvDraft07.errorsText(validate.errors)}`);
214
+ expect(valid).toBe(true);
215
+ });
216
+
217
+ it("emits a CONTAINS relationship from the root subject to each sub-document's own root package", () => {
218
+ const doc = JSON.parse(writeSpdx(assemblyInput, FIXED_NOW));
219
+ const containsFromRoot = doc.relationships.filter(
220
+ (r: { spdxElementId: string; relationshipType: string }) =>
221
+ r.spdxElementId === "SPDXRef-Package-search-service" && r.relationshipType === "CONTAINS",
222
+ );
223
+ expect(containsFromRoot).toHaveLength(2);
224
+ });
225
+
226
+ it("nests each sub-document's own packages under a namespace unique to it, never colliding across sub-documents", () => {
227
+ const doc = JSON.parse(writeSpdx(assemblyInput, FIXED_NOW));
228
+ const ids = doc.packages.map((p: { SPDXID: string }) => p.SPDXID);
229
+ expect(new Set(ids).size).toBe(ids.length); // every SPDXID unique
230
+ expect(ids).toContain("SPDXRef-Package-sub0-left-pad");
231
+ expect(ids).toContain("SPDXRef-Package-sub1-SearchBucket");
232
+ });
233
+
234
+ it("a same-named package in two different sub-documents does not collide (distinct SPDX IDs)", () => {
235
+ const collidingInput: BomInput = {
236
+ ...assemblyInput,
237
+ subDocuments: [
238
+ { subjectName: "leaf-a", subjectId: "sha256:1", packages: [{ name: "shared-name", version: "1.0.0" }] },
239
+ { subjectName: "leaf-b", subjectId: "sha256:2", packages: [{ name: "shared-name", version: "2.0.0" }] },
240
+ ],
241
+ };
242
+ const doc = JSON.parse(writeSpdx(collidingInput, FIXED_NOW));
243
+ const ids = doc.packages.map((p: { SPDXID: string }) => p.SPDXID);
244
+ expect(new Set(ids).size).toBe(ids.length);
245
+ expect(validate(doc)).toBe(true);
246
+ });
247
+
248
+ it("without subDocuments, behaves exactly as before #614 (no CONTAINS relationships, no behavior change)", () => {
249
+ const doc = JSON.parse(writeSpdx(sampleInput, FIXED_NOW));
250
+ const contains = doc.relationships.filter((r: { relationshipType: string }) => r.relationshipType === "CONTAINS");
251
+ expect(contains).toHaveLength(0);
252
+ });
253
+ });
254
+
255
+ describe("writeCycloneDx assembly (#614) — nested components + compositions + dependencies for a multi-leaf component BOM", () => {
256
+ const cdxSchema = loadSchema("cyclonedx-1.5.schema.json");
257
+ const jsfSchema = loadSchema("jsf-0.82.schema.json");
258
+ const spdxLicenseSchema = loadSchema("cyclonedx-spdx-license.schema.json");
259
+ const ajv = new Ajv({ strict: false, allErrors: true });
260
+ ajv.addSchema(jsfSchema as object);
261
+ ajv.addSchema(spdxLicenseSchema as object);
262
+ const validate = ajv.compile(cdxSchema as object);
263
+
264
+ it("validates against the real CycloneDX-1.5 JSON Schema", () => {
265
+ const bytes = writeCycloneDx(assemblyInput, FIXED_NOW);
266
+ const doc = JSON.parse(bytes);
267
+ const valid = validate(doc);
268
+ if (!valid) throw new Error(`CycloneDX assembly failed schema validation: ${ajv.errorsText(validate.errors)}`);
269
+ expect(valid).toBe(true);
270
+ });
271
+
272
+ it("nests each sub-document as a real component under metadata.component.components, not flattened into the root's own components array", () => {
273
+ const doc = JSON.parse(writeCycloneDx(assemblyInput, FIXED_NOW));
274
+ expect(doc.metadata.component.components).toHaveLength(2);
275
+ const names = doc.metadata.component.components.map((c: { name: string }) => c.name);
276
+ expect(names).toEqual(["software:image.tar.sbom.json", "config:search.template.json.config-bom.json"]);
277
+ // the root-level `components` array (the flat single-document list)
278
+ // stays empty here since assemblyInput.packages is [] — the aggregate's
279
+ // own packages live in the nested sub-documents, not flattened up.
280
+ expect(doc.components).toEqual([]);
281
+ });
282
+
283
+ it("records a compositions entry with aggregate: incomplete, referencing the root and every sub-document", () => {
284
+ const doc = JSON.parse(writeCycloneDx(assemblyInput, FIXED_NOW));
285
+ expect(doc.compositions).toHaveLength(1);
286
+ expect(doc.compositions[0].aggregate).toBe("incomplete");
287
+ expect(doc.compositions[0].assemblies).toEqual(
288
+ expect.arrayContaining(["subject:search-service", "sub0:subject:software:image.tar.sbom.json", "sub1:subject:config:search.template.json.config-bom.json"]),
289
+ );
290
+ });
291
+
292
+ it("dependencies: the root subject depends on each sub-document root, and each sub-document depends on its own packages", () => {
293
+ const doc = JSON.parse(writeCycloneDx(assemblyInput, FIXED_NOW));
294
+ const rootDep = doc.dependencies.find((d: { ref: string }) => d.ref === "subject:search-service");
295
+ expect(rootDep.dependsOn).toEqual(
296
+ expect.arrayContaining(["sub0:subject:software:image.tar.sbom.json", "sub1:subject:config:search.template.json.config-bom.json"]),
297
+ );
298
+ const sub0Dep = doc.dependencies.find((d: { ref: string }) => d.ref === "sub0:subject:software:image.tar.sbom.json");
299
+ expect(sub0Dep.dependsOn).toEqual(["sub0:left-pad"]);
300
+ });
301
+
302
+ it("a same-named package in two different sub-documents does not collide (distinct bom-refs)", () => {
303
+ const collidingInput: BomInput = {
304
+ ...assemblyInput,
305
+ subDocuments: [
306
+ { subjectName: "leaf-a", subjectId: "sha256:1", packages: [{ name: "shared-name", version: "1.0.0" }] },
307
+ { subjectName: "leaf-b", subjectId: "sha256:2", packages: [{ name: "shared-name", version: "2.0.0" }] },
308
+ ],
309
+ };
310
+ const doc = JSON.parse(writeCycloneDx(collidingInput, FIXED_NOW));
311
+ const allRefs = [
312
+ doc.metadata.component["bom-ref"],
313
+ ...doc.metadata.component.components.map((c: { "bom-ref": string }) => c["bom-ref"]),
314
+ ...doc.metadata.component.components.flatMap((c: { components: Array<{ "bom-ref": string }> }) => c.components.map((cc) => cc["bom-ref"])),
315
+ ];
316
+ expect(new Set(allRefs).size).toBe(allRefs.length);
317
+ expect(validate(doc)).toBe(true);
318
+ });
319
+
320
+ it("without subDocuments, behaves exactly as before #614 (no compositions, no nested components, no behavior change)", () => {
321
+ const doc = JSON.parse(writeCycloneDx(sampleInput, FIXED_NOW));
322
+ expect(doc.compositions).toBeUndefined();
323
+ expect(doc.metadata.component.components).toBeUndefined();
324
+ });
325
+
326
+ it("is deterministic — identical assembly input produces byte-identical output", () => {
327
+ const a = writeCycloneDx(assemblyInput, FIXED_NOW);
328
+ const b = writeCycloneDx(assemblyInput, FIXED_NOW);
329
+ expect(a).toBe(b);
330
+ });
331
+ });