@intentius/chant 0.13.1 → 0.15.1

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 +59 -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 +99 -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,638 @@
1
+ /**
2
+ * Tests for the thin interpret driver (#556, epic #551).
3
+ *
4
+ * All capabilities registered here are fakes/stubs — the real AWS capability
5
+ * implementations are a separate later issue (#557). These tests exercise the
6
+ * driver's generic behavior only: dependency ordering + parallel-safe waves,
7
+ * sequential vs parallel step execution, `onFailure`/saga rollback in reverse
8
+ * order, output wiring (`@Phase.field` and `@<component>.publish.*`), and that
9
+ * a `gate` errors on the local executor. Several cases replay the real pilot
10
+ * declarations (../pilots/*.pilot.ts) as realistic inputs.
11
+ */
12
+
13
+ import { describe, expect, it } from "vitest";
14
+ import { CapabilityRegistry, type DeployContext } from "./capability";
15
+ import { stubCapability } from "./verbs/stub";
16
+ import {
17
+ DependencyCycleError,
18
+ DriverGateUnsupportedError,
19
+ DriverRunFailure,
20
+ UnknownDependencyError,
21
+ resolveComponentGraph,
22
+ resolveWiring,
23
+ runComponentDeploy,
24
+ runInterpretDriver,
25
+ type DriverComponent,
26
+ } from "./driver";
27
+ import { neo4jCluster } from "./pilots/neo4j-fanout.pilot";
28
+ import { ordersTable } from "./pilots/dynamodb.pilot";
29
+ import { searchService } from "./pilots/alb-ecs.pilot";
30
+ import { imageProcessor } from "./pilots/lambda.pilot";
31
+ import { projectToJson } from "./pilots/project";
32
+
33
+ /** Build a fake capability that records every call and returns a canned/derived output. */
34
+ function fakeCapability(
35
+ kind: string,
36
+ opts?: {
37
+ run?: (input: unknown) => unknown | Promise<unknown>;
38
+ rollback?: (input: unknown) => void | Promise<void>;
39
+ failRun?: boolean;
40
+ },
41
+ ) {
42
+ const calls: { fn: "run" | "rollback"; ctx: DeployContext; input: unknown }[] = [];
43
+ const capability = {
44
+ kind,
45
+ async run(ctx: DeployContext, input: unknown) {
46
+ calls.push({ fn: "run", ctx, input });
47
+ if (opts?.failRun) throw new Error(`${kind} failed`);
48
+ return opts?.run ? await opts.run(input) : { ok: true };
49
+ },
50
+ ...(opts?.rollback
51
+ ? {
52
+ async rollback(ctx: DeployContext, input: unknown) {
53
+ calls.push({ fn: "rollback", ctx, input });
54
+ await opts.rollback!(input);
55
+ },
56
+ }
57
+ : {}),
58
+ };
59
+ return { capability, calls };
60
+ }
61
+
62
+ describe("resolveComponentGraph", () => {
63
+ it("orders independent components into a single wave", () => {
64
+ const components: DriverComponent[] = [
65
+ { name: "a", dependsOn: [], deploy: [] },
66
+ { name: "b", dependsOn: [], deploy: [] },
67
+ ];
68
+ const graph = resolveComponentGraph(components);
69
+ expect(graph.waves).toEqual([["a", "b"]]);
70
+ expect(graph.order.sort()).toEqual(["a", "b"]);
71
+ });
72
+
73
+ it("orders a dependent component into a later wave than its dependency", () => {
74
+ const components: DriverComponent[] = [
75
+ { name: "shared-alb", dependsOn: [], deploy: [] },
76
+ { name: "search-service", dependsOn: ["shared-alb"], deploy: [] },
77
+ ];
78
+ const graph = resolveComponentGraph(components);
79
+ expect(graph.waves).toEqual([["shared-alb"], ["search-service"]]);
80
+ expect(graph.order).toEqual(["shared-alb", "search-service"]);
81
+ });
82
+
83
+ it("groups multiple independent dependents of the same producer into one wave", () => {
84
+ const components: DriverComponent[] = [
85
+ { name: "jar-lib", dependsOn: [], deploy: [] },
86
+ { name: "emr-job-a", dependsOn: ["jar-lib"], deploy: [] },
87
+ { name: "emr-job-b", dependsOn: ["jar-lib"], deploy: [] },
88
+ ];
89
+ const graph = resolveComponentGraph(components);
90
+ expect(graph.waves).toEqual([["jar-lib"], ["emr-job-a", "emr-job-b"]]);
91
+ });
92
+
93
+ it("throws DependencyCycleError for a cyclic dependsOn graph", () => {
94
+ const components: DriverComponent[] = [
95
+ { name: "a", dependsOn: ["b"], deploy: [] },
96
+ { name: "b", dependsOn: ["a"], deploy: [] },
97
+ ];
98
+ expect(() => resolveComponentGraph(components)).toThrow(DependencyCycleError);
99
+ });
100
+
101
+ it("throws UnknownDependencyError when dependsOn names a component outside the run set", () => {
102
+ const components: DriverComponent[] = [{ name: "a", dependsOn: ["missing"], deploy: [] }];
103
+ expect(() => resolveComponentGraph(components)).toThrow(UnknownDependencyError);
104
+ });
105
+
106
+ it("resolves the three pilots' combined graph (no shared dependsOn among them, one wave)", () => {
107
+ const components = [neo4jCluster, ordersTable, searchService].map(
108
+ (p) => projectToJson(p) as unknown as DriverComponent,
109
+ );
110
+ // searchService depends on "shared-alb", which isn't in this run set —
111
+ // add a stub producer so the graph resolves, mirroring a real project.
112
+ const withSharedAlb: DriverComponent[] = [...components, { name: "shared-alb", dependsOn: [], deploy: [] }];
113
+ const graph = resolveComponentGraph(withSharedAlb);
114
+ expect(graph.waves[0]!.sort()).toEqual(["orders-table", "neo4j-cluster", "shared-alb"].sort());
115
+ expect(graph.waves[1]).toEqual(["search-service"]);
116
+ });
117
+ });
118
+
119
+ describe("resolveWiring", () => {
120
+ it("resolves a prior-step reference (@Phase.field)", () => {
121
+ const phaseOutputs = { Publish: { digest: "sha256:abc" } };
122
+ expect(resolveWiring("@Publish.digest", phaseOutputs, {})).toBe("sha256:abc");
123
+ });
124
+
125
+ it("resolves a cross-component artifact reference (@component.publish.uri)", () => {
126
+ const componentOutputs = { "jar-lib": { publish: { uri: "s3://bucket/jar-lib.jar" } } };
127
+ expect(resolveWiring("@jar-lib.publish.uri", {}, componentOutputs)).toBe("s3://bucket/jar-lib.jar");
128
+ });
129
+
130
+ it("resolves a stackOutput reference", () => {
131
+ const componentOutputs = { "shared-alb": { ListenerArn: "arn:aws:elb:listener/abc" } };
132
+ expect(
133
+ resolveWiring({ stackOutput: { stack: "shared-alb", name: "ListenerArn" } }, {}, componentOutputs),
134
+ ).toBe("arn:aws:elb:listener/abc");
135
+ });
136
+
137
+ it("passes through a $env.* reference and plain literals unresolved", () => {
138
+ expect(resolveWiring("$env.registry", {}, {})).toBe("$env.registry");
139
+ expect(resolveWiring("plain-literal", {}, {})).toBe("plain-literal");
140
+ });
141
+ });
142
+
143
+ describe("runComponentDeploy — sequential vs parallel", () => {
144
+ it("runs sequential steps in declared order", async () => {
145
+ const order: string[] = [];
146
+ const registry = new CapabilityRegistry();
147
+ registry.register(fakeCapability("step-a", { run: () => (order.push("a"), { ok: true }) }).capability);
148
+ registry.register(fakeCapability("step-b", { run: () => (order.push("b"), { ok: true }) }).capability);
149
+
150
+ const component: DriverComponent = {
151
+ name: "c",
152
+ deploy: [{ phase: "Apply", steps: [{ kind: "step-a" }, { kind: "step-b" }] }],
153
+ };
154
+ const result = await runComponentDeploy(component, { env: "dev", component: "c" }, registry, {});
155
+ expect(result.ok).toBe(true);
156
+ expect(order).toEqual(["a", "b"]);
157
+ });
158
+
159
+ it("runs parallel steps concurrently (interleaved start before either finishes)", async () => {
160
+ const started: string[] = [];
161
+ const registry = new CapabilityRegistry();
162
+ registry.register(
163
+ fakeCapability("slow", {
164
+ run: async () => {
165
+ started.push("slow-start");
166
+ await new Promise((r) => setTimeout(r, 20));
167
+ return { ok: true };
168
+ },
169
+ }).capability,
170
+ );
171
+ registry.register(
172
+ fakeCapability("fast", {
173
+ run: async () => {
174
+ started.push("fast-start");
175
+ return { ok: true };
176
+ },
177
+ }).capability,
178
+ );
179
+
180
+ const component: DriverComponent = {
181
+ name: "c",
182
+ deploy: [{ phase: "Verify", parallel: true, steps: [{ kind: "slow" }, { kind: "fast" }] }],
183
+ };
184
+ const result = await runComponentDeploy(component, { env: "dev", component: "c" }, registry, {});
185
+ expect(result.ok).toBe(true);
186
+ // Both started before the slow one's 20ms completed — proves concurrency, not queueing.
187
+ expect(started).toEqual(["slow-start", "fast-start"]);
188
+ });
189
+
190
+ it("dispatches purely by step kind — the same registry serves any component with no per-component branch", async () => {
191
+ const registry = new CapabilityRegistry();
192
+ const { capability, calls } = fakeCapability("docker-build", { run: () => ({ digest: "sha256:x" }) });
193
+ registry.register(capability);
194
+
195
+ const componentX: DriverComponent = {
196
+ name: "x",
197
+ deploy: [{ phase: "Build", steps: [{ kind: "docker-build", context: "." }] }],
198
+ };
199
+ const componentY: DriverComponent = {
200
+ name: "y",
201
+ deploy: [{ phase: "Build", steps: [{ kind: "docker-build", context: "./y" }] }],
202
+ };
203
+ await runComponentDeploy(componentX, { env: "dev", component: "x" }, registry, {});
204
+ await runComponentDeploy(componentY, { env: "dev", component: "y" }, registry, {});
205
+ expect(calls.map((c) => c.ctx.component)).toEqual(["x", "y"]);
206
+ });
207
+ });
208
+
209
+ describe("runComponentDeploy — gate on the local executor", () => {
210
+ it("throws DriverGateUnsupportedError when a phase contains a gate", async () => {
211
+ const registry = new CapabilityRegistry();
212
+ const component: DriverComponent = {
213
+ name: "neo4j-cluster",
214
+ deploy: [
215
+ {
216
+ phase: "Node 1",
217
+ steps: [{ kind: "gate", signalName: "approve-node-1" }, { kind: "cfn-deploy" }],
218
+ },
219
+ ],
220
+ };
221
+ await expect(
222
+ runComponentDeploy(component, { env: "dev", component: "neo4j-cluster" }, registry, {}),
223
+ ).rejects.toThrow(DriverGateUnsupportedError);
224
+ });
225
+
226
+ it("the neo4j pilot's gated Node 1 phase errors locally (matches chant's local-executor behavior)", async () => {
227
+ const registry = new CapabilityRegistry();
228
+ for (const kind of ["cfn-deploy", "code-deploy", "wait-cluster-healthy"]) {
229
+ registry.register(fakeCapability(kind, { run: () => ({ ok: true }) }).capability);
230
+ }
231
+ const json = projectToJson(neo4jCluster) as unknown as DriverComponent;
232
+ await expect(runComponentDeploy(json, { env: "dev", component: "neo4j-cluster" }, registry, {})).rejects.toThrow(
233
+ /gate "approve-neo4j-node-1" is not supported/,
234
+ );
235
+ });
236
+ });
237
+
238
+ describe("runComponentDeploy — onFailure saga rollback", () => {
239
+ it("unwinds executed steps in reverse order via each capability's rollback", async () => {
240
+ const rollbackOrder: string[] = [];
241
+ const registry = new CapabilityRegistry();
242
+ registry.register(
243
+ fakeCapability("provision", {
244
+ run: () => ({ id: "res-1" }),
245
+ rollback: () => {
246
+ rollbackOrder.push("provision");
247
+ },
248
+ }).capability,
249
+ );
250
+ registry.register(
251
+ fakeCapability("configure", {
252
+ run: () => ({ ok: true }),
253
+ rollback: () => {
254
+ rollbackOrder.push("configure");
255
+ },
256
+ }).capability,
257
+ );
258
+ registry.register(fakeCapability("apply-final", { failRun: true }).capability);
259
+
260
+ const component: DriverComponent = {
261
+ name: "c",
262
+ deploy: [
263
+ { phase: "Provision", steps: [{ kind: "provision" }] },
264
+ { phase: "Configure", steps: [{ kind: "configure" }] },
265
+ { phase: "Apply", steps: [{ kind: "apply-final" }] },
266
+ ],
267
+ };
268
+ const result = await runComponentDeploy(component, { env: "dev", component: "c" }, registry, {});
269
+ expect(result.ok).toBe(false);
270
+ // Reverse order: configure (2nd executed) rolls back before provision (1st executed).
271
+ expect(rollbackOrder).toEqual(["configure", "provision"]);
272
+ });
273
+
274
+ it("does not call rollback for a capability that never declared one, but reports it as opted-out rather than silently skipping it", async () => {
275
+ const registry = new CapabilityRegistry();
276
+ const { capability, calls } = fakeCapability("no-rollback-step", { run: () => ({ ok: true }) });
277
+ registry.register(capability);
278
+ registry.register(fakeCapability("failing-step", { failRun: true }).capability);
279
+
280
+ const component: DriverComponent = {
281
+ name: "c",
282
+ deploy: [
283
+ { phase: "One", steps: [{ kind: "no-rollback-step" }] },
284
+ { phase: "Two", steps: [{ kind: "failing-step" }] },
285
+ ],
286
+ };
287
+ const result = await runComponentDeploy(component, { env: "dev", component: "c" }, registry, {});
288
+ expect(calls.some((c) => c.fn === "rollback")).toBe(false);
289
+
290
+ const optedOutRecord = result.records.find(
291
+ (r) => r.kind === "no-rollback-step" && r.status === "rollback-opted-out",
292
+ );
293
+ expect(optedOutRecord).toBeDefined();
294
+ expect(optedOutRecord?.error).toMatch(/declares no rollback/);
295
+ });
296
+
297
+ it("carries the step's declared noRollback reason into the opted-out record instead of the generic fallback message", async () => {
298
+ const registry = new CapabilityRegistry();
299
+ registry.register(fakeCapability("emr-start-job-run", { run: () => ({ runId: "run-1" }) }).capability);
300
+ registry.register(fakeCapability("failing-step", { failRun: true }).capability);
301
+
302
+ const component: DriverComponent = {
303
+ name: "c",
304
+ deploy: [
305
+ {
306
+ phase: "Submit",
307
+ steps: [
308
+ {
309
+ kind: "emr-start-job-run",
310
+ jar: "s3://bucket/lib.jar",
311
+ noRollback: "a submitted job run has no automatic undo; cancelling it does not revert data it already wrote",
312
+ },
313
+ ],
314
+ },
315
+ { phase: "Verify", steps: [{ kind: "failing-step" }] },
316
+ ],
317
+ };
318
+ const result = await runComponentDeploy(component, { env: "dev", component: "c" }, registry, {});
319
+ expect(result.ok).toBe(false);
320
+
321
+ const optedOutRecord = result.records.find(
322
+ (r) => r.kind === "emr-start-job-run" && r.status === "rollback-opted-out",
323
+ );
324
+ expect(optedOutRecord?.error).toBe(
325
+ "a submitted job run has no automatic undo; cancelling it does not revert data it already wrote",
326
+ );
327
+ });
328
+
329
+ it("runs a component-level rollback (schema `rollback` field) after step rollback, matching the ALB/ECS pilot's explicit compensation", async () => {
330
+ const registry = new CapabilityRegistry();
331
+ registry.register(fakeCapability("docker-build", { run: () => ({ digest: "sha256:abc" }) }).capability);
332
+ registry.register(
333
+ fakeCapability("publish-image", { run: () => ({ digest: "sha256:abc", uri: "reg/img@sha256:abc" }) })
334
+ .capability,
335
+ );
336
+ registry.register(fakeCapability("cfn-deploy", { run: () => ({ stackStatus: "UPDATE_COMPLETE", outputs: {} }) }).capability);
337
+ // ecs-update-service fails, and (per the pilot's comment) has no native
338
+ // rollback — the component's own `rollback` phase is what should fire.
339
+ registry.register(fakeCapability("ecs-update-service", { failRun: true }).capability);
340
+ const { capability: rollbackPreviousCap, calls: rollbackPreviousCalls } = fakeCapability("rollback-previous", {
341
+ run: () => ({ restored: true }),
342
+ });
343
+ registry.register(rollbackPreviousCap);
344
+ // Verify/rollback phases never reached since Apply fails first, but register
345
+ // stubs anyway so a hypothetical reordering wouldn't crash the test on an
346
+ // unregistered kind.
347
+ registry.register(fakeCapability("wait-steady-state", { run: () => ({ runningCount: 1 }) }).capability);
348
+ registry.register(fakeCapability("health-gate", { run: () => ({ healthy: true }) }).capability);
349
+
350
+ const json = projectToJson(searchService) as unknown as DriverComponent;
351
+ const result = await runComponentDeploy(json, { env: "dev", component: "search-service" }, registry, {});
352
+
353
+ expect(result.ok).toBe(false);
354
+ expect(rollbackPreviousCalls).toHaveLength(1);
355
+ expect(rollbackPreviousCalls[0]!.input).toMatchObject({ service: "search" });
356
+ const rollbackRecord = result.records.find((r) => r.phase === "Rollback" && r.kind === "rollback-previous");
357
+ expect(rollbackRecord?.status).toBe("ok");
358
+ });
359
+ });
360
+
361
+ describe("runComponentDeploy — output wiring", () => {
362
+ it("wires a prior step's output into a later step in the same component (@Phase.field)", async () => {
363
+ const registry = new CapabilityRegistry();
364
+ registry.register(fakeCapability("publish-image", { run: () => ({ digest: "sha256:deadbeef" }) }).capability);
365
+ const { capability: applyCap, calls: applyCalls } = fakeCapability("cfn-deploy", {
366
+ run: () => ({ stackStatus: "UPDATE_COMPLETE", outputs: {} }),
367
+ });
368
+ registry.register(applyCap);
369
+
370
+ const component: DriverComponent = {
371
+ name: "search-service",
372
+ deploy: [
373
+ { phase: "Publish", steps: [{ kind: "publish-image", from: "archive", to: "$env.registry" }] },
374
+ { phase: "Apply", steps: [{ kind: "cfn-deploy", template: "t.json", imageRef: "@Publish.digest" }] },
375
+ ],
376
+ };
377
+ const result = await runComponentDeploy(component, { env: "dev", component: "search-service" }, registry, {});
378
+ expect(result.ok).toBe(true);
379
+ expect(applyCalls[0]!.input).toMatchObject({ imageRef: "sha256:deadbeef" });
380
+ });
381
+
382
+ it("wires a cross-component artifact reference (@<component>.publish.uri) into a consumer", async () => {
383
+ const registry = new CapabilityRegistry();
384
+ registry.register(fakeCapability("jvm-build", { run: () => ({ archivePath: "lib.jar", digest: "sha256:jar" }) }).capability);
385
+ registry.register(fakeCapability("publish-artifact", { run: () => ({ uri: "s3://bucket/jar-lib.jar", digest: "sha256:jar" }) }).capability);
386
+ const { capability: submitCap, calls: submitCalls } = fakeCapability("emr-start-job-run", {
387
+ run: () => ({ runId: "run-1" }),
388
+ });
389
+ registry.register(submitCap);
390
+ registry.register(fakeCapability("wait-job", { run: () => ({ state: "COMPLETED" }) }).capability);
391
+
392
+ const jarLib: DriverComponent = {
393
+ name: "jar-lib",
394
+ dependsOn: [],
395
+ deploy: [{ phase: "Publish", steps: [{ kind: "publish-artifact", from: "archive", to: "$env.s3" }] }],
396
+ };
397
+ const emrJob: DriverComponent = {
398
+ name: "emr-job",
399
+ dependsOn: ["jar-lib"],
400
+ deploy: [
401
+ { phase: "Submit", steps: [{ kind: "emr-start-job-run", jar: "@jar-lib.publish.uri" }] },
402
+ { phase: "Verify", steps: [{ kind: "wait-job", runId: "@Submit.runId" }] },
403
+ ],
404
+ };
405
+
406
+ const result = await runInterpretDriver([jarLib, emrJob], registry, { env: "dev" });
407
+ expect(result.ok).toBe(true);
408
+ expect(submitCalls[0]!.input).toMatchObject({ jar: "s3://bucket/jar-lib.jar" });
409
+ });
410
+ });
411
+
412
+ describe("runInterpretDriver — end to end", () => {
413
+ it("runs a producer before its consumer and threads env/vars into every capability call", async () => {
414
+ const registry = new CapabilityRegistry();
415
+ const seen: DeployContext[] = [];
416
+ registry.register({
417
+ kind: "noop",
418
+ async run(ctx: DeployContext) {
419
+ seen.push(ctx);
420
+ return {};
421
+ },
422
+ });
423
+
424
+ const a: DriverComponent = { name: "a", dependsOn: [], deploy: [{ phase: "P", steps: [{ kind: "noop" }] }] };
425
+ const b: DriverComponent = { name: "b", dependsOn: ["a"], deploy: [{ phase: "P", steps: [{ kind: "noop" }] }] };
426
+
427
+ const result = await runInterpretDriver([b, a], registry, { env: "staging", vars: { cluster: "prod-1" } });
428
+ expect(result.ok).toBe(true);
429
+ expect(result.waves).toEqual([["a"], ["b"]]);
430
+ expect(seen.map((c) => c.component)).toEqual(["a", "b"]);
431
+ expect(seen.every((c) => c.env === "staging" && c.vars?.cluster === "prod-1")).toBe(true);
432
+ });
433
+
434
+ it("stops the run at the first failed component and throws DriverRunFailure carrying partial results", async () => {
435
+ const registry = new CapabilityRegistry();
436
+ registry.register(fakeCapability("ok-step", { run: () => ({ ok: true }) }).capability);
437
+ registry.register(fakeCapability("bad-step", { failRun: true }).capability);
438
+
439
+ const a: DriverComponent = { name: "a", dependsOn: [], deploy: [{ phase: "P", steps: [{ kind: "bad-step" }] }] };
440
+ const b: DriverComponent = { name: "b", dependsOn: ["a"], deploy: [{ phase: "P", steps: [{ kind: "ok-step" }] }] };
441
+
442
+ await expect(runInterpretDriver([a, b], registry, { env: "dev" })).rejects.toThrow(DriverRunFailure);
443
+ try {
444
+ await runInterpretDriver([a, b], registry, { env: "dev" });
445
+ expect.unreachable();
446
+ } catch (err) {
447
+ expect(err).toBeInstanceOf(DriverRunFailure);
448
+ const failure = err as DriverRunFailure;
449
+ expect(failure.result.failedComponent).toBe("a");
450
+ expect(failure.result.results.map((r) => r.component)).toEqual(["a"]);
451
+ expect(failure.result.ok).toBe(false);
452
+ }
453
+ });
454
+
455
+ it("runs the three pilots through one driver instance with zero per-component driver code (sprawl metric)", async () => {
456
+ const registry = new CapabilityRegistry();
457
+ const kinds = [
458
+ "cfn-deploy",
459
+ "code-deploy",
460
+ "wait-cluster-healthy",
461
+ "docker-build",
462
+ "publish-image",
463
+ "ecs-update-service",
464
+ "wait-steady-state",
465
+ "health-gate",
466
+ "rollback-previous",
467
+ "wait-for-stack",
468
+ "run-migration",
469
+ ];
470
+ for (const kind of kinds) {
471
+ registry.register(fakeCapability(kind, { run: () => ({ ok: true }) }).capability);
472
+ }
473
+
474
+ const sharedAlb: DriverComponent = { name: "shared-alb", dependsOn: [], deploy: [{ phase: "Apply", steps: [{ kind: "cfn-deploy" }] }] };
475
+ const dynamo = projectToJson(ordersTable) as unknown as DriverComponent;
476
+
477
+ // neo4j has a gate at Node 1 — omit it here since this test asserts the
478
+ // happy path across heterogeneous pilots; the gate-specific behavior is
479
+ // covered above. Swap in a gate-free variant by stripping the gate step.
480
+ const neo4jJson = projectToJson(neo4jCluster) as unknown as DriverComponent;
481
+ const neo4jNoGate: DriverComponent = {
482
+ ...neo4jJson,
483
+ deploy: neo4jJson.deploy.map((p) => ({ ...p, steps: p.steps.filter((s) => (s as { kind?: string }).kind !== "gate") })),
484
+ };
485
+
486
+ const albEcs = projectToJson(searchService) as unknown as DriverComponent;
487
+
488
+ const result = await runInterpretDriver([sharedAlb, dynamo, neo4jNoGate, albEcs], registry, {
489
+ env: "dev",
490
+ vars: { registry: "123.dkr.ecr.us-east-1.amazonaws.com", cluster: "prod" },
491
+ });
492
+ expect(result.ok).toBe(true);
493
+ // search-service depends on shared-alb, so it must land in a later wave.
494
+ const albWave = result.waves.findIndex((w) => w.includes("shared-alb"));
495
+ const searchWave = result.waves.findIndex((w) => w.includes("search-service"));
496
+ expect(searchWave).toBeGreaterThan(albWave);
497
+ expect(result.results.map((r) => r.component).sort()).toEqual(
498
+ ["shared-alb", "orders-table", "neo4j-cluster", "search-service"].sort(),
499
+ );
500
+ });
501
+
502
+ it("feeds a deployed stack's outputs to a downstream component's stackOutput references (cross-stack apply-order, #556 follow-up)", async () => {
503
+ const registry = new CapabilityRegistry();
504
+ // shared-alb's apply returns real stack outputs — the values a downstream
505
+ // service needs (its ECR repo URI, the cluster ARN).
506
+ registry.register(
507
+ fakeCapability("cfn-deploy", {
508
+ run: () => ({
509
+ stackStatus: "CREATE_COMPLETE",
510
+ outputs: {
511
+ ApiRepoUri: "123.dkr.ecr.us-east-1.amazonaws.com/alb-api",
512
+ ClusterArn: "arn:aws:ecs:us-east-1:123:cluster/shared",
513
+ },
514
+ }),
515
+ }).capability,
516
+ );
517
+ registry.register(fakeCapability("docker-build", { run: () => ({ archivePath: "api.tar", digest: "sha256:abc" }) }).capability);
518
+ const publish = fakeCapability("publish-image", { run: () => ({ uri: "repo@sha256:abc", digest: "sha256:abc" }) });
519
+ registry.register(publish.capability);
520
+ const verify = fakeCapability("wait-steady-state", { run: () => ({ ok: true }) });
521
+ registry.register(verify.capability);
522
+
523
+ const sharedAlb: DriverComponent = {
524
+ name: "shared-alb",
525
+ dependsOn: [],
526
+ deploy: [{ phase: "Apply", steps: [{ kind: "cfn-deploy", stack: "shared-alb", template: "shared-alb.json" }] }],
527
+ };
528
+ const api: DriverComponent = {
529
+ name: "api",
530
+ dependsOn: ["shared-alb"],
531
+ deploy: [
532
+ { phase: "Build", steps: [{ kind: "docker-build", context: "./api", into: "api.tar" }] },
533
+ { phase: "Publish", steps: [{ kind: "publish-image", from: "archive:api.tar", to: { stackOutput: { stack: "shared-alb", name: "ApiRepoUri" } } }] },
534
+ { phase: "Verify", steps: [{ kind: "wait-steady-state", service: "api", cluster: { stackOutput: { stack: "shared-alb", name: "ClusterArn" } } }] },
535
+ ],
536
+ };
537
+
538
+ const result = await runInterpretDriver([sharedAlb, api], registry, { env: "prod" });
539
+ expect(result.ok).toBe(true);
540
+ // Before this fix the driver dropped shared-alb's outputs, so these refs
541
+ // resolved to undefined. Now the downstream steps receive the real values.
542
+ expect((publish.calls[0].input as { to: string }).to).toBe("123.dkr.ecr.us-east-1.amazonaws.com/alb-api");
543
+ expect((verify.calls[0].input as { cluster: string }).cluster).toBe("arn:aws:ecs:us-east-1:123:cluster/shared");
544
+ // The accumulated outputs are exposed on the result so the CLI can
545
+ // `--dump-outputs` them for a downstream job to `--seed-outputs`.
546
+ expect(result.componentOutputs["shared-alb"]).toMatchObject({
547
+ ApiRepoUri: "123.dkr.ecr.us-east-1.amazonaws.com/alb-api",
548
+ ClusterArn: "arn:aws:ecs:us-east-1:123:cluster/shared",
549
+ });
550
+ });
551
+
552
+ it("seeded componentOutputs let a single downstream component resolve a cross-job stackOutput (artifact threading)", async () => {
553
+ // Simulates the generated pipeline's downstream job: api runs alone (via the
554
+ // single-component `runComponentDeploy` path, bypassing whole-graph
555
+ // resolution — its dependency shared-alb is not in this process), seeded with
556
+ // shared-alb's outputs from an earlier job, and still resolves the reference.
557
+ const registry = new CapabilityRegistry();
558
+ const publish = fakeCapability("publish-image", { run: () => ({ uri: "repo@sha256:abc", digest: "sha256:abc" }) });
559
+ registry.register(publish.capability);
560
+
561
+ const api: DriverComponent = {
562
+ name: "api",
563
+ dependsOn: ["shared-alb"],
564
+ deploy: [
565
+ { phase: "Publish", steps: [{ kind: "publish-image", from: "archive:api.tar", to: { stackOutput: { stack: "shared-alb", name: "ApiRepoUri" } } }] },
566
+ ],
567
+ };
568
+
569
+ const seeded = { "shared-alb": { ApiRepoUri: "123.dkr.ecr.us-east-1.amazonaws.com/alb-api" } };
570
+ const result = await runComponentDeploy(api, { env: "prod", component: "api" }, registry, seeded);
571
+ expect(result.ok).toBe(true);
572
+ expect((publish.calls[0].input as { to: string }).to).toBe("123.dkr.ecr.us-east-1.amazonaws.com/alb-api");
573
+ });
574
+
575
+ it("runs all four components — the original three pilots plus the #558 validation component — through one driver instance with zero per-component driver code (sprawl metric, extended)", async () => {
576
+ const registry = new CapabilityRegistry();
577
+ const kinds = [
578
+ "cfn-deploy",
579
+ "code-deploy",
580
+ "wait-cluster-healthy",
581
+ "docker-build",
582
+ "publish-image",
583
+ "ecs-update-service",
584
+ "wait-steady-state",
585
+ "health-gate",
586
+ "rollback-previous",
587
+ "wait-for-stack",
588
+ "run-migration",
589
+ "lambda-deploy",
590
+ ];
591
+ for (const kind of kinds) {
592
+ registry.register(fakeCapability(kind, { run: () => ({ ok: true }) }).capability);
593
+ }
594
+
595
+ const sharedAlb: DriverComponent = {
596
+ name: "shared-alb",
597
+ dependsOn: [],
598
+ deploy: [{ phase: "Apply", steps: [{ kind: "cfn-deploy" }] }],
599
+ };
600
+ const dynamo = projectToJson(ordersTable) as unknown as DriverComponent;
601
+ const neo4jJson = projectToJson(neo4jCluster) as unknown as DriverComponent;
602
+ const neo4jNoGate: DriverComponent = {
603
+ ...neo4jJson,
604
+ deploy: neo4jJson.deploy.map((p) => ({ ...p, steps: p.steps.filter((s) => (s as { kind?: string }).kind !== "gate") })),
605
+ };
606
+ const albEcs = projectToJson(searchService) as unknown as DriverComponent;
607
+ const lambda = projectToJson(imageProcessor) as unknown as DriverComponent;
608
+
609
+ const result = await runInterpretDriver([sharedAlb, dynamo, neo4jNoGate, albEcs, lambda], registry, {
610
+ env: "dev",
611
+ vars: { registry: "123.dkr.ecr.us-east-1.amazonaws.com", cluster: "prod" },
612
+ });
613
+
614
+ expect(result.ok).toBe(true);
615
+ expect(result.results.map((r) => r.component).sort()).toEqual(
616
+ ["shared-alb", "orders-table", "neo4j-cluster", "search-service", "image-processor-lambda"].sort(),
617
+ );
618
+ });
619
+ });
620
+
621
+ describe("capabilities are consumed exactly as the registry provides them", () => {
622
+ // No starter verb is a stub any more, but the stub *mechanism*
623
+ // (./verbs/stub.ts) stays for third-party plugins / future verbs. A stub
624
+ // registered by kind must surface its CapabilityNotImplementedError as an
625
+ // ordinary failed step rather than crashing the driver.
626
+ it("a stubbed capability (CapabilityNotImplementedError) surfaces as a failed step, not a driver crash", async () => {
627
+ const registry = new CapabilityRegistry();
628
+ registry.register(stubCapability("some-future-verb"));
629
+ const component: DriverComponent = {
630
+ name: "c",
631
+ deploy: [{ phase: "Apply", steps: [{ kind: "some-future-verb" }] }],
632
+ };
633
+ const result = await runComponentDeploy(component, { env: "dev", component: "c" }, registry, {});
634
+ expect(result.ok).toBe(false);
635
+ const failed = result.records.find((r) => r.status === "fail");
636
+ expect(failed?.error).toMatch(/capability "some-future-verb" is not implemented/);
637
+ });
638
+ });