@intentius/chant 0.13.0 → 0.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (453) hide show
  1. package/dist/audit/catalog.d.ts +45 -5
  2. package/dist/audit/catalog.d.ts.map +1 -1
  3. package/dist/audit/fetch.d.ts.map +1 -1
  4. package/dist/audit/proof.d.ts +3 -0
  5. package/dist/audit/proof.d.ts.map +1 -1
  6. package/dist/audit/report-model.d.ts +4 -1
  7. package/dist/audit/report-model.d.ts.map +1 -1
  8. package/dist/audit/report.d.ts +3 -0
  9. package/dist/audit/report.d.ts.map +1 -1
  10. package/dist/audit/rules-doc.d.ts +8 -2
  11. package/dist/audit/rules-doc.d.ts.map +1 -1
  12. package/dist/cli/commands/audit.d.ts.map +1 -1
  13. package/dist/cli/commands/doctor.d.ts.map +1 -1
  14. package/dist/cli/commands/lexicon-rolling-upgrade.d.ts +37 -0
  15. package/dist/cli/commands/lexicon-rolling-upgrade.d.ts.map +1 -0
  16. package/dist/cli/commands/lexicon-surface-diff.d.ts +48 -0
  17. package/dist/cli/commands/lexicon-surface-diff.d.ts.map +1 -0
  18. package/dist/cli/commands/lint.d.ts.map +1 -1
  19. package/dist/cli/commands/pinned-upgrade.d.ts +41 -0
  20. package/dist/cli/commands/pinned-upgrade.d.ts.map +1 -0
  21. package/dist/cli/handlers/build.d.ts.map +1 -1
  22. package/dist/cli/handlers/components.d.ts +39 -0
  23. package/dist/cli/handlers/components.d.ts.map +1 -0
  24. package/dist/cli/handlers/dev.d.ts +3 -0
  25. package/dist/cli/handlers/dev.d.ts.map +1 -1
  26. package/dist/cli/handlers/graph.d.ts +4 -2
  27. package/dist/cli/handlers/graph.d.ts.map +1 -1
  28. package/dist/cli/handlers/misc.d.ts.map +1 -1
  29. package/dist/cli/handlers/run.d.ts +49 -0
  30. package/dist/cli/handlers/run.d.ts.map +1 -1
  31. package/dist/cli/main.d.ts.map +1 -1
  32. package/dist/cli/registry.d.ts +35 -0
  33. package/dist/cli/registry.d.ts.map +1 -1
  34. package/dist/codegen/fetch.d.ts +22 -0
  35. package/dist/codegen/fetch.d.ts.map +1 -1
  36. package/dist/codegen/lexicon-regen.d.ts +110 -0
  37. package/dist/codegen/lexicon-regen.d.ts.map +1 -0
  38. package/dist/codegen/pinned-upgrade.d.ts +153 -0
  39. package/dist/codegen/pinned-upgrade.d.ts.map +1 -0
  40. package/dist/codegen/rolling-upgrade.d.ts +138 -0
  41. package/dist/codegen/rolling-upgrade.d.ts.map +1 -0
  42. package/dist/codegen/surface-snapshot.d.ts +173 -0
  43. package/dist/codegen/surface-snapshot.d.ts.map +1 -0
  44. package/dist/codegen/version-bump.d.ts +21 -0
  45. package/dist/codegen/version-bump.d.ts.map +1 -0
  46. package/dist/components/auto-release.d.ts +93 -0
  47. package/dist/components/auto-release.d.ts.map +1 -0
  48. package/dist/components/builders.d.ts +37 -0
  49. package/dist/components/builders.d.ts.map +1 -0
  50. package/dist/components/capability-plugin-loader.d.ts +111 -0
  51. package/dist/components/capability-plugin-loader.d.ts.map +1 -0
  52. package/dist/components/capability-plugin-schema.d.ts +24 -0
  53. package/dist/components/capability-plugin-schema.d.ts.map +1 -0
  54. package/dist/components/capability-plugin.d.ts +85 -0
  55. package/dist/components/capability-plugin.d.ts.map +1 -0
  56. package/dist/components/capability.d.ts +89 -0
  57. package/dist/components/capability.d.ts.map +1 -0
  58. package/dist/components/cli-support.d.ts +204 -0
  59. package/dist/components/cli-support.d.ts.map +1 -0
  60. package/dist/components/component.d.ts +179 -0
  61. package/dist/components/component.d.ts.map +1 -0
  62. package/dist/components/config-defaults.d.ts +41 -0
  63. package/dist/components/config-defaults.d.ts.map +1 -0
  64. package/dist/components/discover.d.ts +60 -0
  65. package/dist/components/discover.d.ts.map +1 -0
  66. package/dist/components/driver-output.d.ts +20 -0
  67. package/dist/components/driver-output.d.ts.map +1 -0
  68. package/dist/components/driver.d.ts +209 -0
  69. package/dist/components/driver.d.ts.map +1 -0
  70. package/dist/components/index.d.ts +22 -0
  71. package/dist/components/index.d.ts.map +1 -0
  72. package/dist/components/manifest-persistence.d.ts +79 -0
  73. package/dist/components/manifest-persistence.d.ts.map +1 -0
  74. package/dist/components/pilots/alb-ecs.pilot.d.ts +27 -0
  75. package/dist/components/pilots/alb-ecs.pilot.d.ts.map +1 -0
  76. package/dist/components/pilots/dynamodb.pilot.d.ts +22 -0
  77. package/dist/components/pilots/dynamodb.pilot.d.ts.map +1 -0
  78. package/dist/components/pilots/jar-emr.pilot.d.ts +37 -0
  79. package/dist/components/pilots/jar-emr.pilot.d.ts.map +1 -0
  80. package/dist/components/pilots/lambda.pilot.d.ts +35 -0
  81. package/dist/components/pilots/lambda.pilot.d.ts.map +1 -0
  82. package/dist/components/pilots/neo4j-fanout.pilot.d.ts +17 -0
  83. package/dist/components/pilots/neo4j-fanout.pilot.d.ts.map +1 -0
  84. package/dist/components/pilots/project.d.ts +13 -0
  85. package/dist/components/pilots/project.d.ts.map +1 -0
  86. package/dist/components/presets/ecs-fargate.d.ts +59 -0
  87. package/dist/components/presets/ecs-fargate.d.ts.map +1 -0
  88. package/dist/components/presets/index.d.ts +29 -0
  89. package/dist/components/presets/index.d.ts.map +1 -0
  90. package/dist/components/presets/lambda.d.ts +35 -0
  91. package/dist/components/presets/lambda.d.ts.map +1 -0
  92. package/dist/components/presets/single-host-compose.d.ts +43 -0
  93. package/dist/components/presets/single-host-compose.d.ts.map +1 -0
  94. package/dist/components/registry.d.ts +29 -0
  95. package/dist/components/registry.d.ts.map +1 -0
  96. package/dist/components/starter-plugin.d.ts +51 -0
  97. package/dist/components/starter-plugin.d.ts.map +1 -0
  98. package/dist/components/temporal-codegen-loader.d.ts +27 -0
  99. package/dist/components/temporal-codegen-loader.d.ts.map +1 -0
  100. package/dist/components/verbs/__tests__/mock-cloud-executor.d.ts +35 -0
  101. package/dist/components/verbs/__tests__/mock-cloud-executor.d.ts.map +1 -0
  102. package/dist/components/verbs/__tests__/mock-process-runner.d.ts +45 -0
  103. package/dist/components/verbs/__tests__/mock-process-runner.d.ts.map +1 -0
  104. package/dist/components/verbs/__tests__/mock-sbom-generator.d.ts +33 -0
  105. package/dist/components/verbs/__tests__/mock-sbom-generator.d.ts.map +1 -0
  106. package/dist/components/verbs/bom-writer.d.ts +155 -0
  107. package/dist/components/verbs/bom-writer.d.ts.map +1 -0
  108. package/dist/components/verbs/build-archive.d.ts +205 -0
  109. package/dist/components/verbs/build-archive.d.ts.map +1 -0
  110. package/dist/components/verbs/build.d.ts +150 -0
  111. package/dist/components/verbs/build.d.ts.map +1 -0
  112. package/dist/components/verbs/cloud-executor.d.ts +88 -0
  113. package/dist/components/verbs/cloud-executor.d.ts.map +1 -0
  114. package/dist/components/verbs/component-bom.d.ts +103 -0
  115. package/dist/components/verbs/component-bom.d.ts.map +1 -0
  116. package/dist/components/verbs/index.d.ts +49 -0
  117. package/dist/components/verbs/index.d.ts.map +1 -0
  118. package/dist/components/verbs/license-policy.d.ts +56 -0
  119. package/dist/components/verbs/license-policy.d.ts.map +1 -0
  120. package/dist/components/verbs/lockfile-sbom-generator.d.ts +81 -0
  121. package/dist/components/verbs/lockfile-sbom-generator.d.ts.map +1 -0
  122. package/dist/components/verbs/process-runner.d.ts +64 -0
  123. package/dist/components/verbs/process-runner.d.ts.map +1 -0
  124. package/dist/components/verbs/reproducibility.d.ts +80 -0
  125. package/dist/components/verbs/reproducibility.d.ts.map +1 -0
  126. package/dist/components/verbs/sbom-generator.d.ts +157 -0
  127. package/dist/components/verbs/sbom-generator.d.ts.map +1 -0
  128. package/dist/components/verbs/sbom.d.ts +79 -0
  129. package/dist/components/verbs/sbom.d.ts.map +1 -0
  130. package/dist/components/verbs/shell.d.ts +35 -0
  131. package/dist/components/verbs/shell.d.ts.map +1 -0
  132. package/dist/components/verbs/sign.d.ts +222 -0
  133. package/dist/components/verbs/sign.d.ts.map +1 -0
  134. package/dist/components/verbs/stub.d.ts +18 -0
  135. package/dist/components/verbs/stub.d.ts.map +1 -0
  136. package/dist/components/verbs/tool-sbom-generator.d.ts +51 -0
  137. package/dist/components/verbs/tool-sbom-generator.d.ts.map +1 -0
  138. package/dist/components/verbs/verify.d.ts +106 -0
  139. package/dist/components/verbs/verify.d.ts.map +1 -0
  140. package/dist/components/verbs/vex.d.ts +65 -0
  141. package/dist/components/verbs/vex.d.ts.map +1 -0
  142. package/dist/components/verbs/vuln-gate.d.ts +91 -0
  143. package/dist/components/verbs/vuln-gate.d.ts.map +1 -0
  144. package/dist/components/verbs/vuln-scan.d.ts +116 -0
  145. package/dist/components/verbs/vuln-scan.d.ts.map +1 -0
  146. package/dist/components/verbs/wait-verify.d.ts +85 -0
  147. package/dist/components/verbs/wait-verify.d.ts.map +1 -0
  148. package/dist/config.d.ts +205 -0
  149. package/dist/config.d.ts.map +1 -1
  150. package/dist/index.d.ts +4 -0
  151. package/dist/index.d.ts.map +1 -1
  152. package/dist/lexicon.d.ts +78 -0
  153. package/dist/lexicon.d.ts.map +1 -1
  154. package/dist/lifecycle/build-ledger-store.d.ts +121 -0
  155. package/dist/lifecycle/build-ledger-store.d.ts.map +1 -0
  156. package/dist/lifecycle/build-ledger.d.ts +225 -0
  157. package/dist/lifecycle/build-ledger.d.ts.map +1 -0
  158. package/dist/lifecycle/git.d.ts +84 -0
  159. package/dist/lifecycle/git.d.ts.map +1 -1
  160. package/dist/lifecycle/index.d.ts +5 -0
  161. package/dist/lifecycle/index.d.ts.map +1 -1
  162. package/dist/lifecycle/oras-referrer-lookup.d.ts +41 -0
  163. package/dist/lifecycle/oras-referrer-lookup.d.ts.map +1 -0
  164. package/dist/lifecycle/release-ledger.d.ts +122 -0
  165. package/dist/lifecycle/release-ledger.d.ts.map +1 -0
  166. package/dist/lifecycle/status.d.ts +150 -0
  167. package/dist/lifecycle/status.d.ts.map +1 -0
  168. package/dist/lint/component-checks.d.ts +95 -0
  169. package/dist/lint/component-checks.d.ts.map +1 -0
  170. package/dist/lint/engine.d.ts +23 -0
  171. package/dist/lint/engine.d.ts.map +1 -1
  172. package/dist/lint/presets/strict.json +8 -1
  173. package/dist/lint/rules/__fixtures__/comp/comp000-discovery-error/broken-a.component.d.ts +4 -0
  174. package/dist/lint/rules/__fixtures__/comp/comp000-discovery-error/broken-a.component.d.ts.map +1 -0
  175. package/dist/lint/rules/__fixtures__/comp/comp000-discovery-error/broken-b.component.d.ts +4 -0
  176. package/dist/lint/rules/__fixtures__/comp/comp000-discovery-error/broken-b.component.d.ts.map +1 -0
  177. package/dist/lint/rules/__fixtures__/comp/comp001/fail/orphan-lib.component.d.ts +4 -0
  178. package/dist/lint/rules/__fixtures__/comp/comp001/fail/orphan-lib.component.d.ts.map +1 -0
  179. package/dist/lint/rules/__fixtures__/comp/comp001/pass/emr-job.component.d.ts +4 -0
  180. package/dist/lint/rules/__fixtures__/comp/comp001/pass/emr-job.component.d.ts.map +1 -0
  181. package/dist/lint/rules/__fixtures__/comp/comp001/pass/jar-lib.component.d.ts +4 -0
  182. package/dist/lint/rules/__fixtures__/comp/comp001/pass/jar-lib.component.d.ts.map +1 -0
  183. package/dist/lint/rules/__fixtures__/comp/comp002/fail/search-service.component.d.ts +10 -0
  184. package/dist/lint/rules/__fixtures__/comp/comp002/fail/search-service.component.d.ts.map +1 -0
  185. package/dist/lint/rules/__fixtures__/comp/comp002/pass/jar-lib.component.d.ts +3 -0
  186. package/dist/lint/rules/__fixtures__/comp/comp002/pass/jar-lib.component.d.ts.map +1 -0
  187. package/dist/lint/rules/__fixtures__/comp/comp002/pass/search-service.component.d.ts +4 -0
  188. package/dist/lint/rules/__fixtures__/comp/comp002/pass/search-service.component.d.ts.map +1 -0
  189. package/dist/lint/rules/__fixtures__/comp/comp003/fail/migrator.component.d.ts +4 -0
  190. package/dist/lint/rules/__fixtures__/comp/comp003/fail/migrator.component.d.ts.map +1 -0
  191. package/dist/lint/rules/__fixtures__/comp/comp003/pass/migrator.component.d.ts +4 -0
  192. package/dist/lint/rules/__fixtures__/comp/comp003/pass/migrator.component.d.ts.map +1 -0
  193. package/dist/lint/rules/__fixtures__/comp/comp003/pass/orders-table.component.d.ts +4 -0
  194. package/dist/lint/rules/__fixtures__/comp/comp003/pass/orders-table.component.d.ts.map +1 -0
  195. package/dist/lint/rules/__fixtures__/comp/comp004/fail/neo4j-cluster.component.d.ts +4 -0
  196. package/dist/lint/rules/__fixtures__/comp/comp004/fail/neo4j-cluster.component.d.ts.map +1 -0
  197. package/dist/lint/rules/__fixtures__/comp/comp004/pass/neo4j-cluster.component.d.ts +4 -0
  198. package/dist/lint/rules/__fixtures__/comp/comp004/pass/neo4j-cluster.component.d.ts.map +1 -0
  199. package/dist/lint/rules/__fixtures__/comp/comp004/pass/orders-table.component.d.ts +4 -0
  200. package/dist/lint/rules/__fixtures__/comp/comp004/pass/orders-table.component.d.ts.map +1 -0
  201. package/dist/lint/rules/__fixtures__/comp/comp005/fail/search-service.component.d.ts +4 -0
  202. package/dist/lint/rules/__fixtures__/comp/comp005/fail/search-service.component.d.ts.map +1 -0
  203. package/dist/lint/rules/__fixtures__/comp/comp005/pass/search-service.component.d.ts +4 -0
  204. package/dist/lint/rules/__fixtures__/comp/comp005/pass/search-service.component.d.ts.map +1 -0
  205. package/dist/lint/rules/__fixtures__/comp/comp006/fail/legacy-tool.component.d.ts +4 -0
  206. package/dist/lint/rules/__fixtures__/comp/comp006/fail/legacy-tool.component.d.ts.map +1 -0
  207. package/dist/lint/rules/__fixtures__/comp/comp006/pass/legacy-tool.component.d.ts +4 -0
  208. package/dist/lint/rules/__fixtures__/comp/comp006/pass/legacy-tool.component.d.ts.map +1 -0
  209. package/dist/lint/rules/__fixtures__/comp/comp007/fail/inventory-table.component.d.ts +4 -0
  210. package/dist/lint/rules/__fixtures__/comp/comp007/fail/inventory-table.component.d.ts.map +1 -0
  211. package/dist/lint/rules/__fixtures__/comp/comp007/fail/orders-table.component.d.ts +4 -0
  212. package/dist/lint/rules/__fixtures__/comp/comp007/fail/orders-table.component.d.ts.map +1 -0
  213. package/dist/lint/rules/__fixtures__/comp/comp007/pass/orders-table.component.d.ts +3 -0
  214. package/dist/lint/rules/__fixtures__/comp/comp007/pass/orders-table.component.d.ts.map +1 -0
  215. package/dist/lint/rules/__fixtures__/comp/comp007/pass/search-service.component.d.ts +4 -0
  216. package/dist/lint/rules/__fixtures__/comp/comp007/pass/search-service.component.d.ts.map +1 -0
  217. package/dist/lint/rules/comp/comp001-publishes-never-applies.d.ts +20 -0
  218. package/dist/lint/rules/comp/comp001-publishes-never-applies.d.ts.map +1 -0
  219. package/dist/lint/rules/comp/comp002-dangling-wiring-ref.d.ts +28 -0
  220. package/dist/lint/rules/comp/comp002-dangling-wiring-ref.d.ts.map +1 -0
  221. package/dist/lint/rules/comp/comp003-mutating-no-rollback.d.ts +39 -0
  222. package/dist/lint/rules/comp/comp003-mutating-no-rollback.d.ts.map +1 -0
  223. package/dist/lint/rules/comp/comp004-gate-needs-temporal.d.ts +37 -0
  224. package/dist/lint/rules/comp/comp004-gate-needs-temporal.d.ts.map +1 -0
  225. package/dist/lint/rules/comp/comp005-capability-kind-is-noun.d.ts +43 -0
  226. package/dist/lint/rules/comp/comp005-capability-kind-is-noun.d.ts.map +1 -0
  227. package/dist/lint/rules/comp/comp006-shell-needs-reason.d.ts +22 -0
  228. package/dist/lint/rules/comp/comp006-shell-needs-reason.d.ts.map +1 -0
  229. package/dist/lint/rules/comp/comp007-composition-sprawl.d.ts +33 -0
  230. package/dist/lint/rules/comp/comp007-composition-sprawl.d.ts.map +1 -0
  231. package/dist/lint/rules/comp/index.d.ts +18 -0
  232. package/dist/lint/rules/comp/index.d.ts.map +1 -0
  233. package/dist/lint/rules/comp/support.d.ts +71 -0
  234. package/dist/lint/rules/comp/support.d.ts.map +1 -0
  235. package/dist/lsp/lexicon-providers.d.ts.map +1 -1
  236. package/dist/op/activity-registry.d.ts +11 -6
  237. package/dist/op/activity-registry.d.ts.map +1 -1
  238. package/dist/op/activity-runtime.d.ts +24 -0
  239. package/dist/op/activity-runtime.d.ts.map +1 -0
  240. package/dist/op/builders.d.ts +56 -0
  241. package/dist/op/builders.d.ts.map +1 -1
  242. package/dist/op/index.d.ts +2 -1
  243. package/dist/op/index.d.ts.map +1 -1
  244. package/dist/ownership.d.ts +20 -14
  245. package/dist/ownership.d.ts.map +1 -1
  246. package/package.json +14 -1
  247. package/src/audit/catalog.test.ts +74 -22
  248. package/src/audit/catalog.ts +74 -471
  249. package/src/audit/fetch.test.ts +112 -8
  250. package/src/audit/fetch.ts +53 -8
  251. package/src/audit/proof.ts +4 -2
  252. package/src/audit/report-html.test.ts +14 -7
  253. package/src/audit/report-model.ts +20 -9
  254. package/src/audit/report.test.ts +36 -13
  255. package/src/audit/report.ts +2 -0
  256. package/src/audit/rules-doc.test.ts +8 -6
  257. package/src/audit/rules-doc.ts +13 -5
  258. package/src/cli/commands/audit.ts +23 -18
  259. package/src/cli/commands/component-lint.test.ts +158 -0
  260. package/src/cli/commands/doctor.test.ts +116 -2
  261. package/src/cli/commands/doctor.ts +46 -0
  262. package/src/cli/commands/lexicon-rolling-upgrade.ts +198 -0
  263. package/src/cli/commands/lexicon-surface-diff.test.ts +104 -0
  264. package/src/cli/commands/lexicon-surface-diff.ts +185 -0
  265. package/src/cli/commands/lint.ts +142 -1
  266. package/src/cli/commands/pinned-upgrade.ts +204 -0
  267. package/src/cli/handlers/build.ts +58 -1
  268. package/src/cli/handlers/components.test.ts +504 -0
  269. package/src/cli/handlers/components.ts +404 -0
  270. package/src/cli/handlers/dev.ts +60 -1
  271. package/src/cli/handlers/graph.ts +47 -2
  272. package/src/cli/handlers/misc.ts +45 -1
  273. package/src/cli/handlers/run.test.ts +967 -3
  274. package/src/cli/handlers/run.ts +618 -11
  275. package/src/cli/main.test.ts +66 -0
  276. package/src/cli/main.ts +105 -2
  277. package/src/cli/registry.ts +35 -0
  278. package/src/codegen/fetch.test.ts +84 -3
  279. package/src/codegen/fetch.ts +76 -2
  280. package/src/codegen/lexicon-regen.test.ts +298 -0
  281. package/src/codegen/lexicon-regen.ts +545 -0
  282. package/src/codegen/pinned-upgrade.test.ts +399 -0
  283. package/src/codegen/pinned-upgrade.ts +423 -0
  284. package/src/codegen/rolling-upgrade.test.ts +333 -0
  285. package/src/codegen/rolling-upgrade.ts +320 -0
  286. package/src/codegen/surface-snapshot.test.ts +461 -0
  287. package/src/codegen/surface-snapshot.ts +546 -0
  288. package/src/codegen/version-bump.test.ts +47 -0
  289. package/src/codegen/version-bump.ts +39 -0
  290. package/src/components/README.md +130 -0
  291. package/src/components/SPRAWL-VALIDATION.md +181 -0
  292. package/src/components/__fixtures__/alb-ecs-service.json +51 -0
  293. package/src/components/__fixtures__/dynamodb-infra.json +32 -0
  294. package/src/components/__fixtures__/emr-job-consumer.json +26 -0
  295. package/src/components/__fixtures__/jar-lib-producer.json +16 -0
  296. package/src/components/__fixtures__/lambda-image-processor.json +28 -0
  297. package/src/components/__fixtures__/neo4j-fanout.json +34 -0
  298. package/src/components/__fixtures__/single-host-compose.json +29 -0
  299. package/src/components/auto-release.test.ts +217 -0
  300. package/src/components/auto-release.ts +216 -0
  301. package/src/components/builders.test.ts +45 -0
  302. package/src/components/builders.ts +71 -0
  303. package/src/components/capability-plugin-loader.ts +218 -0
  304. package/src/components/capability-plugin-schema.ts +56 -0
  305. package/src/components/capability-plugin.test.ts +235 -0
  306. package/src/components/capability-plugin.ts +108 -0
  307. package/src/components/capability.ts +117 -0
  308. package/src/components/cli-support.test.ts +579 -0
  309. package/src/components/cli-support.ts +481 -0
  310. package/src/components/component-schema.test.ts +265 -0
  311. package/src/components/component.schema.json +235 -0
  312. package/src/components/component.test.ts +251 -0
  313. package/src/components/component.ts +255 -0
  314. package/src/components/config-defaults.test.ts +263 -0
  315. package/src/components/config-defaults.ts +145 -0
  316. package/src/components/discover.test.ts +352 -0
  317. package/src/components/discover.ts +167 -0
  318. package/src/components/driver-output.ts +71 -0
  319. package/src/components/driver.test.ts +638 -0
  320. package/src/components/driver.ts +690 -0
  321. package/src/components/index.ts +113 -0
  322. package/src/components/manifest-persistence.test.ts +152 -0
  323. package/src/components/manifest-persistence.ts +164 -0
  324. package/src/components/pilots/README.md +33 -0
  325. package/src/components/pilots/alb-ecs.pilot.ts +63 -0
  326. package/src/components/pilots/dynamodb.pilot.ts +59 -0
  327. package/src/components/pilots/jar-emr.pilot.ts +69 -0
  328. package/src/components/pilots/lambda.pilot.ts +55 -0
  329. package/src/components/pilots/neo4j-fanout.pilot.ts +92 -0
  330. package/src/components/pilots/pilots-lint-clean.test.ts +78 -0
  331. package/src/components/pilots/pilots.test.ts +126 -0
  332. package/src/components/pilots/project.ts +13 -0
  333. package/src/components/presets/comp007-reduction.test.ts +122 -0
  334. package/src/components/presets/ecs-fargate.test.ts +84 -0
  335. package/src/components/presets/ecs-fargate.ts +103 -0
  336. package/src/components/presets/index.ts +32 -0
  337. package/src/components/presets/lambda.test.ts +35 -0
  338. package/src/components/presets/lambda.ts +53 -0
  339. package/src/components/presets/single-host-compose.test.ts +65 -0
  340. package/src/components/presets/single-host-compose.ts +64 -0
  341. package/src/components/registry.test.ts +112 -0
  342. package/src/components/registry.ts +37 -0
  343. package/src/components/starter-plugin.ts +105 -0
  344. package/src/components/temporal-codegen-loader.ts +41 -0
  345. package/src/components/verbs/__fixtures__/package-lock.json +45 -0
  346. package/src/components/verbs/__fixtures__/pom.xml +37 -0
  347. package/src/components/verbs/__fixtures__/schemas/cyclonedx-1.5.schema.json +3800 -0
  348. package/src/components/verbs/__fixtures__/schemas/cyclonedx-spdx-license.schema.json +621 -0
  349. package/src/components/verbs/__fixtures__/schemas/jsf-0.82.schema.json +240 -0
  350. package/src/components/verbs/__fixtures__/schemas/spdx-2.3.schema.json +740 -0
  351. package/src/components/verbs/__tests__/mock-cloud-executor.ts +102 -0
  352. package/src/components/verbs/__tests__/mock-process-runner.ts +79 -0
  353. package/src/components/verbs/__tests__/mock-sbom-generator.ts +85 -0
  354. package/src/components/verbs/bom-writer.test.ts +331 -0
  355. package/src/components/verbs/bom-writer.ts +386 -0
  356. package/src/components/verbs/build-archive.test.ts +251 -0
  357. package/src/components/verbs/build-archive.ts +296 -0
  358. package/src/components/verbs/build.test.ts +222 -0
  359. package/src/components/verbs/build.ts +252 -0
  360. package/src/components/verbs/cloud-executor.ts +181 -0
  361. package/src/components/verbs/component-bom.test.ts +219 -0
  362. package/src/components/verbs/component-bom.ts +189 -0
  363. package/src/components/verbs/index.ts +49 -0
  364. package/src/components/verbs/license-policy.ts +115 -0
  365. package/src/components/verbs/lockfile-sbom-generator.test.ts +195 -0
  366. package/src/components/verbs/lockfile-sbom-generator.ts +236 -0
  367. package/src/components/verbs/process-runner.test.ts +79 -0
  368. package/src/components/verbs/process-runner.ts +113 -0
  369. package/src/components/verbs/reproducibility.test.ts +24 -0
  370. package/src/components/verbs/reproducibility.ts +100 -0
  371. package/src/components/verbs/sbom-generator.test.ts +96 -0
  372. package/src/components/verbs/sbom-generator.ts +209 -0
  373. package/src/components/verbs/sbom.test.ts +184 -0
  374. package/src/components/verbs/sbom.ts +152 -0
  375. package/src/components/verbs/shell.test.ts +36 -0
  376. package/src/components/verbs/shell.ts +51 -0
  377. package/src/components/verbs/sign.test.ts +210 -0
  378. package/src/components/verbs/sign.ts +350 -0
  379. package/src/components/verbs/stub.ts +34 -0
  380. package/src/components/verbs/tool-sbom-generator.test.ts +194 -0
  381. package/src/components/verbs/tool-sbom-generator.ts +192 -0
  382. package/src/components/verbs/verify.test.ts +159 -0
  383. package/src/components/verbs/verify.ts +179 -0
  384. package/src/components/verbs/vex.ts +182 -0
  385. package/src/components/verbs/vuln-gate.test.ts +201 -0
  386. package/src/components/verbs/vuln-gate.ts +170 -0
  387. package/src/components/verbs/vuln-scan.test.ts +145 -0
  388. package/src/components/verbs/vuln-scan.ts +266 -0
  389. package/src/components/verbs/wait-verify.test.ts +111 -0
  390. package/src/components/verbs/wait-verify.ts +171 -0
  391. package/src/config.test.ts +74 -1
  392. package/src/config.ts +219 -0
  393. package/src/index.ts +32 -0
  394. package/src/lexicon.ts +88 -0
  395. package/src/lifecycle/build-ledger-store.test.ts +217 -0
  396. package/src/lifecycle/build-ledger-store.ts +165 -0
  397. package/src/lifecycle/build-ledger.test.ts +349 -0
  398. package/src/lifecycle/build-ledger.ts +305 -0
  399. package/src/lifecycle/git.test.ts +118 -0
  400. package/src/lifecycle/git.ts +149 -14
  401. package/src/lifecycle/index.ts +5 -0
  402. package/src/lifecycle/oras-referrer-lookup.test.ts +116 -0
  403. package/src/lifecycle/oras-referrer-lookup.ts +110 -0
  404. package/src/lifecycle/release-ledger.test.ts +209 -0
  405. package/src/lifecycle/release-ledger.ts +185 -0
  406. package/src/lifecycle/status.test.ts +330 -0
  407. package/src/lifecycle/status.ts +278 -0
  408. package/src/lint/component-checks.ts +148 -0
  409. package/src/lint/config.test.ts +21 -0
  410. package/src/lint/engine.ts +11 -2
  411. package/src/lint/presets/relaxed.json +8 -1
  412. package/src/lint/presets/strict.json +8 -1
  413. package/src/lint/rules/__fixtures__/comp/comp000-discovery-error/broken-a.component.ts +9 -0
  414. package/src/lint/rules/__fixtures__/comp/comp000-discovery-error/broken-b.component.ts +9 -0
  415. package/src/lint/rules/__fixtures__/comp/comp001/fail/orphan-lib.component.ts +11 -0
  416. package/src/lint/rules/__fixtures__/comp/comp001/pass/emr-job.component.ts +13 -0
  417. package/src/lint/rules/__fixtures__/comp/comp001/pass/jar-lib.component.ts +11 -0
  418. package/src/lint/rules/__fixtures__/comp/comp002/fail/search-service.component.ts +27 -0
  419. package/src/lint/rules/__fixtures__/comp/comp002/pass/jar-lib.component.ts +10 -0
  420. package/src/lint/rules/__fixtures__/comp/comp002/pass/search-service.component.ts +23 -0
  421. package/src/lint/rules/__fixtures__/comp/comp003/fail/migrator.component.ts +10 -0
  422. package/src/lint/rules/__fixtures__/comp/comp003/pass/migrator.component.ts +19 -0
  423. package/src/lint/rules/__fixtures__/comp/comp003/pass/orders-table.component.ts +10 -0
  424. package/src/lint/rules/__fixtures__/comp/comp004/fail/neo4j-cluster.component.ts +20 -0
  425. package/src/lint/rules/__fixtures__/comp/comp004/pass/neo4j-cluster.component.ts +23 -0
  426. package/src/lint/rules/__fixtures__/comp/comp004/pass/orders-table.component.ts +10 -0
  427. package/src/lint/rules/__fixtures__/comp/comp005/fail/search-service.component.ts +14 -0
  428. package/src/lint/rules/__fixtures__/comp/comp005/pass/search-service.component.ts +18 -0
  429. package/src/lint/rules/__fixtures__/comp/comp006/fail/legacy-tool.component.ts +10 -0
  430. package/src/lint/rules/__fixtures__/comp/comp006/pass/legacy-tool.component.ts +14 -0
  431. package/src/lint/rules/__fixtures__/comp/comp007/fail/inventory-table.component.ts +13 -0
  432. package/src/lint/rules/__fixtures__/comp/comp007/fail/orders-table.component.ts +13 -0
  433. package/src/lint/rules/__fixtures__/comp/comp007/pass/orders-table.component.ts +12 -0
  434. package/src/lint/rules/__fixtures__/comp/comp007/pass/search-service.component.ts +18 -0
  435. package/src/lint/rules/comp/comp.test.ts +378 -0
  436. package/src/lint/rules/comp/comp001-publishes-never-applies.ts +78 -0
  437. package/src/lint/rules/comp/comp002-dangling-wiring-ref.ts +94 -0
  438. package/src/lint/rules/comp/comp003-mutating-no-rollback.ts +115 -0
  439. package/src/lint/rules/comp/comp004-gate-needs-temporal.ts +66 -0
  440. package/src/lint/rules/comp/comp005-capability-kind-is-noun.ts +113 -0
  441. package/src/lint/rules/comp/comp006-shell-needs-reason.ts +55 -0
  442. package/src/lint/rules/comp/comp007-composition-sprawl.ts +71 -0
  443. package/src/lint/rules/comp/index.ts +37 -0
  444. package/src/lint/rules/comp/support.ts +173 -0
  445. package/src/lsp/lexicon-providers.test.ts +55 -0
  446. package/src/lsp/lexicon-providers.ts +13 -3
  447. package/src/op/activity-registry.test.ts +38 -0
  448. package/src/op/activity-registry.ts +31 -12
  449. package/src/op/activity-runtime.ts +73 -0
  450. package/src/op/builders.ts +92 -0
  451. package/src/op/index.ts +3 -1
  452. package/src/ownership.test.ts +24 -26
  453. package/src/ownership.ts +26 -38
@@ -0,0 +1,217 @@
1
+ import { describe, test, expect, vi, beforeEach } from "vitest";
2
+ import type { DriverStepRecord } from "./driver";
3
+
4
+ const getHeadCommitMock = vi.fn();
5
+ const pushLifecycleMock = vi.fn();
6
+ const appendReleaseRecordMock = vi.fn();
7
+
8
+ vi.mock("../lifecycle/git", () => ({
9
+ getHeadCommit: (...args: unknown[]) => getHeadCommitMock(...args),
10
+ pushLifecycle: (...args: unknown[]) => pushLifecycleMock(...args),
11
+ StaleLifecycleBranchError: class StaleLifecycleBranchError extends Error {},
12
+ }));
13
+
14
+ vi.mock("../lifecycle/release-ledger", async () => {
15
+ const actual = await vi.importActual<typeof import("../lifecycle/release-ledger")>("../lifecycle/release-ledger");
16
+ return {
17
+ ...actual,
18
+ appendReleaseRecord: (...args: unknown[]) => appendReleaseRecordMock(...args),
19
+ };
20
+ });
21
+
22
+ const {
23
+ maybeRecordAutoRelease,
24
+ extractRunDigest,
25
+ extractRunDigestFromPhaseOutputs,
26
+ } = await import("./auto-release");
27
+
28
+ function publishRecord(digest: string): DriverStepRecord {
29
+ return { component: "svc", phase: "Publish", kind: "publish-image", status: "ok", durationMs: 5, output: { digest, uri: `repo@${digest}` } };
30
+ }
31
+
32
+ function applyRecord(): DriverStepRecord {
33
+ return { component: "svc", phase: "Apply", kind: "cfn-deploy", status: "ok", durationMs: 5, output: { ok: true } };
34
+ }
35
+
36
+ // A generate-sbom step output: carries the SBOM document's own content `digest`
37
+ // but no location `uri` — must NOT count as a promoted artifact (#665).
38
+ function sbomRecord(digest: string): DriverStepRecord {
39
+ return { component: "svc", phase: "Sbom", kind: "generate-sbom", status: "ok", durationMs: 5, output: { digest, archivePath: "sbom.spdx.json" } };
40
+ }
41
+
42
+ describe("extractRunDigest", () => {
43
+ test("finds the digest from a publish-shaped step output", () => {
44
+ expect(extractRunDigest([publishRecord("sha256:abc"), applyRecord()])).toBe("sha256:abc");
45
+ });
46
+
47
+ test("returns undefined when no step published a digest", () => {
48
+ expect(extractRunDigest([applyRecord()])).toBeUndefined();
49
+ });
50
+
51
+ test("ignores failed/skipped steps", () => {
52
+ const failed: DriverStepRecord = { component: "svc", phase: "Publish", kind: "publish-image", status: "fail", durationMs: 5, error: "boom" };
53
+ expect(extractRunDigest([failed])).toBeUndefined();
54
+ });
55
+
56
+ test("takes the last digest-bearing output when multiple steps publish", () => {
57
+ expect(extractRunDigest([publishRecord("sha256:first"), publishRecord("sha256:second")])).toBe("sha256:second");
58
+ });
59
+
60
+ test("ignores an SBOM output's content digest — no uri means nothing was promoted (#665)", () => {
61
+ expect(extractRunDigest([sbomRecord("sha256:sbombytes")])).toBeUndefined();
62
+ // an SBOM step alongside a real publish still yields the publish digest, not the SBOM's
63
+ expect(extractRunDigest([sbomRecord("sha256:sbombytes"), publishRecord("sha256:real")])).toBe("sha256:real");
64
+ });
65
+ });
66
+
67
+ describe("extractRunDigestFromPhaseOutputs", () => {
68
+ test("finds the digest across phase outputs (Temporal workflow result shape)", () => {
69
+ expect(extractRunDigestFromPhaseOutputs({ Publish: { digest: "sha256:abc", uri: "repo@sha256:abc" }, Apply: { ok: true } })).toBe("sha256:abc");
70
+ });
71
+
72
+ test("returns undefined for undefined/empty phaseOutputs", () => {
73
+ expect(extractRunDigestFromPhaseOutputs(undefined)).toBeUndefined();
74
+ expect(extractRunDigestFromPhaseOutputs({})).toBeUndefined();
75
+ });
76
+
77
+ test("ignores a phase output carrying only a content digest (SBOM, #665)", () => {
78
+ expect(extractRunDigestFromPhaseOutputs({ Sbom: { digest: "sha256:sbombytes" } })).toBeUndefined();
79
+ });
80
+ });
81
+
82
+ describe("maybeRecordAutoRelease", () => {
83
+ beforeEach(() => {
84
+ getHeadCommitMock.mockReset().mockResolvedValue("abc123headsha");
85
+ pushLifecycleMock.mockReset().mockResolvedValue(true);
86
+ appendReleaseRecordMock.mockReset();
87
+ delete process.env.GITHUB_ACTOR;
88
+ delete process.env.GITLAB_USER_LOGIN;
89
+ delete process.env.USER;
90
+ });
91
+
92
+ test("success -> writes exactly one record and pushes", async () => {
93
+ appendReleaseRecordMock.mockResolvedValue({
94
+ commit: "a".repeat(40),
95
+ record: {
96
+ version: 1, component: "svc", env: "prod", digest: "sha256:abc",
97
+ gitSha: "abc123headsha", runId: "run-1", timestamp: "2026-01-01T00:00:00.000Z", actor: "alice",
98
+ },
99
+ });
100
+
101
+ const outcome = await maybeRecordAutoRelease(
102
+ { component: "svc", env: "prod", success: true, records: [publishRecord("sha256:abc")], runId: "run-1" },
103
+ { actor: "alice" },
104
+ );
105
+
106
+ expect(outcome.recorded).toBe(true);
107
+ expect(appendReleaseRecordMock).toHaveBeenCalledTimes(1);
108
+ const [input] = appendReleaseRecordMock.mock.calls[0];
109
+ expect(input).toMatchObject({ component: "svc", env: "prod", digest: "sha256:abc", gitSha: "abc123headsha", runId: "run-1", actor: "alice" });
110
+ expect(typeof input.timestamp).toBe("string");
111
+ expect(pushLifecycleMock).toHaveBeenCalledTimes(1);
112
+ });
113
+
114
+ test("a pre-resolved digest (Temporal path) takes precedence and is used directly", async () => {
115
+ appendReleaseRecordMock.mockResolvedValue({
116
+ commit: "b".repeat(40),
117
+ record: { version: 1, component: "svc", env: "prod", digest: "sha256:temporal", gitSha: "x", runId: "r", timestamp: "t", actor: "a" },
118
+ });
119
+
120
+ await maybeRecordAutoRelease(
121
+ { component: "svc", env: "prod", success: true, digest: "sha256:temporal", records: [publishRecord("sha256:ignored")], runId: "run-1" },
122
+ { actor: "alice" },
123
+ );
124
+
125
+ const [input] = appendReleaseRecordMock.mock.calls[0];
126
+ expect(input.digest).toBe("sha256:temporal");
127
+ });
128
+
129
+ test("failure (run.success = false) -> writes nothing", async () => {
130
+ const outcome = await maybeRecordAutoRelease(
131
+ { component: "svc", env: "prod", success: false, records: [publishRecord("sha256:abc")], runId: "run-1" },
132
+ { actor: "alice" },
133
+ );
134
+ expect(outcome).toEqual({ recorded: false, reason: "run-not-successful" });
135
+ expect(appendReleaseRecordMock).not.toHaveBeenCalled();
136
+ expect(pushLifecycleMock).not.toHaveBeenCalled();
137
+ });
138
+
139
+ test("opt-out (options.disabled) -> writes nothing, even on a successful run with a digest", async () => {
140
+ const outcome = await maybeRecordAutoRelease(
141
+ { component: "svc", env: "prod", success: true, records: [publishRecord("sha256:abc")], runId: "run-1" },
142
+ { actor: "alice", disabled: true },
143
+ );
144
+ expect(outcome).toEqual({ recorded: false, reason: "opted-out" });
145
+ expect(appendReleaseRecordMock).not.toHaveBeenCalled();
146
+ expect(pushLifecycleMock).not.toHaveBeenCalled();
147
+ expect(getHeadCommitMock).not.toHaveBeenCalled();
148
+ });
149
+
150
+ test("no digest in the run's records -> skipped, not an error", async () => {
151
+ const outcome = await maybeRecordAutoRelease(
152
+ { component: "svc", env: "prod", success: true, records: [applyRecord()], runId: "run-1" },
153
+ { actor: "alice" },
154
+ );
155
+ expect(outcome).toMatchObject({ recorded: false, reason: "no-digest" });
156
+ expect(appendReleaseRecordMock).not.toHaveBeenCalled();
157
+ });
158
+
159
+ test("no resolvable actor -> skipped, not an error", async () => {
160
+ const outcome = await maybeRecordAutoRelease(
161
+ { component: "svc", env: "prod", success: true, records: [publishRecord("sha256:abc")], runId: "run-1" },
162
+ );
163
+ expect(outcome).toMatchObject({ recorded: false, reason: "no-actor" });
164
+ expect(appendReleaseRecordMock).not.toHaveBeenCalled();
165
+ });
166
+
167
+ test("falls back to GITHUB_ACTOR/GITLAB_USER_LOGIN/USER when --actor is not passed", async () => {
168
+ process.env.GITHUB_ACTOR = "octocat";
169
+ appendReleaseRecordMock.mockResolvedValue({
170
+ commit: "c".repeat(40),
171
+ record: { version: 1, component: "svc", env: "prod", digest: "sha256:abc", gitSha: "x", runId: "r", timestamp: "t", actor: "octocat" },
172
+ });
173
+
174
+ await maybeRecordAutoRelease(
175
+ { component: "svc", env: "prod", success: true, records: [publishRecord("sha256:abc")], runId: "run-1" },
176
+ );
177
+
178
+ const [input] = appendReleaseRecordMock.mock.calls[0];
179
+ expect(input.actor).toBe("octocat");
180
+ });
181
+
182
+ test("git sha resolution failure -> reported as an error result, not thrown", async () => {
183
+ getHeadCommitMock.mockRejectedValue(new Error("not a git repo"));
184
+ const outcome = await maybeRecordAutoRelease(
185
+ { component: "svc", env: "prod", success: true, records: [publishRecord("sha256:abc")], runId: "run-1" },
186
+ { actor: "alice" },
187
+ );
188
+ expect(outcome).toMatchObject({ recorded: false, reason: "error" });
189
+ expect(appendReleaseRecordMock).not.toHaveBeenCalled();
190
+ });
191
+
192
+ test("ledger write failure -> reported as an error result, not thrown, and never masks the successful deploy", async () => {
193
+ appendReleaseRecordMock.mockRejectedValue(new Error("ledger write failed"));
194
+ const outcome = await expect(maybeRecordAutoRelease(
195
+ { component: "svc", env: "prod", success: true, records: [publishRecord("sha256:abc")], runId: "run-1" },
196
+ { actor: "alice" },
197
+ )).resolves.toMatchObject({ recorded: false, reason: "error", error: "ledger write failed" });
198
+ void outcome;
199
+ expect(pushLifecycleMock).not.toHaveBeenCalled();
200
+ });
201
+
202
+ test("an explicit gitSha override skips getHeadCommit entirely", async () => {
203
+ appendReleaseRecordMock.mockResolvedValue({
204
+ commit: "d".repeat(40),
205
+ record: { version: 1, component: "svc", env: "prod", digest: "sha256:abc", gitSha: "override-sha", runId: "r", timestamp: "t", actor: "alice" },
206
+ });
207
+
208
+ await maybeRecordAutoRelease(
209
+ { component: "svc", env: "prod", success: true, records: [publishRecord("sha256:abc")], runId: "run-1" },
210
+ { actor: "alice", gitSha: "override-sha" },
211
+ );
212
+
213
+ expect(getHeadCommitMock).not.toHaveBeenCalled();
214
+ const [input] = appendReleaseRecordMock.mock.calls[0];
215
+ expect(input.gitSha).toBe("override-sha");
216
+ });
217
+ });
@@ -0,0 +1,216 @@
1
+ /**
2
+ * Auto-emit a release-ledger record from a successful `chant run --components
3
+ * <name> --env <env>` (#597, follow-up to #568/epic #551). Before this, the
4
+ * release ledger (../lifecycle/release-ledger.ts) was only populated by the
5
+ * standalone `chant components release <env>` command — a deploy that never
6
+ * called it left the ledger silently empty. This module makes recording
7
+ * happen *by construction*: the CLI post-run step for both the local
8
+ * executor (`runOpComponents`) and the durable Temporal path
9
+ * (`runComponentTemporal`) calls `maybeRecordAutoRelease` after the run
10
+ * reports success.
11
+ *
12
+ * Deliberately CLI-side, not driver-side or workflow-side:
13
+ * - `../components/driver.ts` stays capability-agnostic — it already
14
+ * resists per-component/per-capability branching, and "write to git"
15
+ * is exactly the kind of side effect that doesn't belong in a step
16
+ * dispatcher tests exercise against a bare `CapabilityRegistry`.
17
+ * - The Temporal workflow (lexicons/temporal/src/component-op/) must stay
18
+ * deterministic and replay-safe. A ledger write is a non-idempotent git
19
+ * push with real-world side effects (network, `Date.now()`, file writes
20
+ * outside workflow state) — precisely what Temporal workflow code must
21
+ * never do directly. It happens in the CLI, after `handle.describe()`
22
+ * reports a terminal COMPLETED status, the same place `runOpTemporal`
23
+ * already writes its deployment report post-run.
24
+ *
25
+ * Reuses ../lifecycle/release-ledger.ts's `appendReleaseRecord` verbatim —
26
+ * this module only decides *whether* to call it and *what digest* to record,
27
+ * mirroring the field-resolution `runComponentsReleaseRecord`
28
+ * (../cli/handlers/components.ts) already does for the standalone command
29
+ * (`--git-sha` defaults to HEAD, `--run-id`/`--actor` fall back to common CI
30
+ * env vars, timestamp is a real `new Date()` taken at record time).
31
+ */
32
+ import { getHeadCommit, pushLifecycle, StaleLifecycleBranchError } from "../lifecycle/git";
33
+ import { appendReleaseRecord, InvalidReleaseRecordError, type ReleaseRecord } from "../lifecycle/release-ledger";
34
+ import type { DriverStepRecord } from "./driver";
35
+
36
+ /**
37
+ * A step output that represents a *promoted* artifact — a publish-family result
38
+ * (publish-image / publish-artifact / load-image-on-host), identified by the
39
+ * location `uri` all three carry (`repo@sha256:…`, `host:…#…`, an artifact URL).
40
+ * This deliberately excludes outputs that carry only a content `digest`: e.g.
41
+ * `generate-sbom`'s `digest` is the SBOM document's own bytes, and
42
+ * `extract-config-bom` likewise *describes* an artifact rather than promoting
43
+ * one. Those must not trigger a release record (#665) — a release means "we
44
+ * promoted this by digest to a location", not "we hashed some bytes". The
45
+ * driver's own `findPublishOutput` stays looser (uri/digest/key) because it
46
+ * only feeds `@<component>.publish.*` wiring, where a false positive is harmless.
47
+ */
48
+ function isPromotedArtifact(output: unknown): output is { uri: string; digest?: string } {
49
+ return !!output && typeof output === "object" && "uri" in output && typeof (output as { uri?: unknown }).uri === "string";
50
+ }
51
+
52
+ /**
53
+ * Extract the artifact digest a component run promoted, by scanning its step
54
+ * records for the last {@link isPromotedArtifact promoted-artifact} output (a
55
+ * publish-family result carrying a location `uri`) and taking its `digest`, so
56
+ * a release record is only ever attempted when the run actually promoted
57
+ * something by digest. Returns undefined when no step promoted an artifact —
58
+ * e.g. a component whose deploy has no publish step, even if it ran
59
+ * `generate-sbom` (whose content `digest` is not a promotion, #665) — the
60
+ * caller treats that as "nothing to record", not an error. Used by the
61
+ * local-executor CLI path, which has the full `DriverStepRecord[]` from
62
+ * `runComponentDeploy`.
63
+ */
64
+ export function extractRunDigest(records: DriverStepRecord[]): string | undefined {
65
+ let found: string | undefined;
66
+ for (const record of records) {
67
+ if (record.status !== "ok") continue;
68
+ if (isPromotedArtifact(record.output) && typeof record.output.digest === "string") {
69
+ found = record.output.digest;
70
+ }
71
+ }
72
+ return found;
73
+ }
74
+
75
+ /**
76
+ * Same digest extraction as `extractRunDigest`, but over a component
77
+ * Temporal workflow's returned `phaseOutputs` (`{ [phase]: output }`) instead
78
+ * of a `DriverStepRecord[]` — the shape `handle.result()` resolves to for a
79
+ * COMPLETED component workflow (see lexicons/temporal/src/component-op/
80
+ * serializer.ts's generated `return { phaseOutputs, componentOutputs }`).
81
+ * Used by the `--temporal` CLI path, which never sees individual step
82
+ * records (those exist only inside the workflow/activities).
83
+ */
84
+ export function extractRunDigestFromPhaseOutputs(
85
+ phaseOutputs: Record<string, Record<string, unknown>> | undefined,
86
+ ): string | undefined {
87
+ let found: string | undefined;
88
+ for (const output of Object.values(phaseOutputs ?? {})) {
89
+ if (isPromotedArtifact(output) && typeof output.digest === "string") {
90
+ found = output.digest;
91
+ }
92
+ }
93
+ return found;
94
+ }
95
+
96
+ /** Explicit reasons `maybeRecordAutoRelease` declined to write a record — never a thrown error, since "nothing to record" (or "opted out") is an expected, common outcome. */
97
+ export type AutoReleaseSkipReason =
98
+ | "opted-out"
99
+ | "run-not-successful"
100
+ | "no-digest"
101
+ | "no-actor";
102
+
103
+ export type AutoReleaseResult =
104
+ | { recorded: true; commit: string; record: ReleaseRecord }
105
+ | { recorded: false; reason: AutoReleaseSkipReason; detail?: string }
106
+ | { recorded: false; reason: "error"; error: string };
107
+
108
+ /** Input describing one component's completed run, common to both the local-executor and Temporal-durable CLI paths. */
109
+ export interface AutoReleaseRunInfo {
110
+ component: string;
111
+ env: string;
112
+ /** Whether the run reported overall success — `DriverRunResult.ok` (local) or a terminal `COMPLETED` Temporal status. A caller must not call this for a non-terminal/in-progress run. */
113
+ success: boolean;
114
+ /** The component's step records (local executor), used to locate the published digest via `extractRunDigest`. Mutually exclusive with `digest` — pass whichever the caller already has. */
115
+ records?: DriverStepRecord[];
116
+ /** A digest already resolved by the caller (e.g. the `--temporal` path, via `extractRunDigestFromPhaseOutputs` over the workflow's `handle.result()`). Takes precedence over `records` when both are given. */
117
+ digest?: string;
118
+ /** Orchestrator run identifier — a Temporal `runId`, or a locally generated id for the local executor (mirrors `runComponentsReleaseRecord`'s `--run-id` default). */
119
+ runId: string;
120
+ }
121
+
122
+ /** Opt-out + field-override knobs for auto-release recording, threaded from CLI flags/config (#597: "opt-out-able (flag/config), documented default"). */
123
+ export interface AutoReleaseOptions {
124
+ /** `--no-release-record` / `chant.config.ts`'s `release.autoRecord: false` — skip emission entirely. Default: emission is ON. */
125
+ disabled?: boolean;
126
+ /** Override the git sha to record (tests, or a caller that already resolved it). Defaults to `git rev-parse HEAD`. */
127
+ gitSha?: string;
128
+ /** Override the actor to record. Defaults to `$GITHUB_ACTOR`/`$GITLAB_USER_LOGIN`/`$USER`, matching `runComponentsReleaseRecord`. */
129
+ actor?: string;
130
+ /** Working directory for the git plumbing calls (tests). */
131
+ cwd?: string;
132
+ }
133
+
134
+ function resolveActor(override?: string): string | undefined {
135
+ return override ?? process.env.GITHUB_ACTOR ?? process.env.GITLAB_USER_LOGIN ?? process.env.USER;
136
+ }
137
+
138
+ /**
139
+ * After a successful `chant run --components <name> --env <env>` (local or
140
+ * `--temporal`), append exactly one immutable release record to the ledger
141
+ * and push it — or explain, without throwing, why it didn't. Never called
142
+ * for a failed run (`run.success` must be true before the caller invokes
143
+ * this at all; see `runOpComponents`/`runComponentTemporal`) — a failed
144
+ * deploy writes nothing, by construction, since this function is simply
145
+ * never reached on that path.
146
+ *
147
+ * Skips (returns `{ recorded: false, reason }`, never throws for these
148
+ * expected cases):
149
+ * - `options.disabled` — the opt-out flag/config was set.
150
+ * - `run.success` false — defensive; callers should not reach here on failure.
151
+ * - no digest found in the run's records — a component with no publish step
152
+ * has nothing to bind a release record to.
153
+ * - no resolvable actor — mirrors `runComponentsReleaseRecord`'s hard
154
+ * requirement; an unattributed record defeats the ledger's purpose.
155
+ *
156
+ * Git-sha resolution failure and ledger-write failures
157
+ * (`InvalidReleaseRecordError`, `StaleLifecycleBranchError`, or any other
158
+ * error `appendReleaseRecord`/`pushLifecycle` throw) are reported as `{
159
+ * recorded: false, reason: "error", error }` rather than propagated — an
160
+ * auto-recorded ledger write is a best-effort observability side effect of a
161
+ * successful deploy; it must never turn a successful `chant run --components`
162
+ * into a failing CLI invocation. The caller should still surface the message
163
+ * as a warning.
164
+ */
165
+ export async function maybeRecordAutoRelease(
166
+ run: AutoReleaseRunInfo,
167
+ options: AutoReleaseOptions = {},
168
+ ): Promise<AutoReleaseResult> {
169
+ if (options.disabled) {
170
+ return { recorded: false, reason: "opted-out" };
171
+ }
172
+ if (!run.success) {
173
+ return { recorded: false, reason: "run-not-successful" };
174
+ }
175
+
176
+ const digest = run.digest ?? extractRunDigest(run.records ?? []);
177
+ if (!digest) {
178
+ return { recorded: false, reason: "no-digest", detail: `component "${run.component}" published no digest-bearing artifact` };
179
+ }
180
+
181
+ const actor = resolveActor(options.actor);
182
+ if (!actor) {
183
+ return { recorded: false, reason: "no-actor", detail: "could not resolve an actor from GITHUB_ACTOR/GITLAB_USER_LOGIN/USER" };
184
+ }
185
+
186
+ let gitSha: string | undefined = options.gitSha;
187
+ try {
188
+ gitSha ??= await getHeadCommit({ cwd: options.cwd });
189
+ } catch (err) {
190
+ return { recorded: false, reason: "error", error: err instanceof Error ? err.message : String(err) };
191
+ }
192
+
193
+ const timestamp = new Date().toISOString();
194
+
195
+ try {
196
+ const { commit, record } = await appendReleaseRecord(
197
+ {
198
+ component: run.component,
199
+ env: run.env,
200
+ digest,
201
+ gitSha,
202
+ runId: run.runId,
203
+ timestamp,
204
+ actor,
205
+ },
206
+ { cwd: options.cwd },
207
+ );
208
+ await pushLifecycle({ cwd: options.cwd });
209
+ return { recorded: true, commit, record };
210
+ } catch (err) {
211
+ if (err instanceof InvalidReleaseRecordError || err instanceof StaleLifecycleBranchError) {
212
+ return { recorded: false, reason: "error", error: err.message };
213
+ }
214
+ return { recorded: false, reason: "error", error: err instanceof Error ? err.message : String(err) };
215
+ }
216
+ }
@@ -0,0 +1,45 @@
1
+ import { describe, test, expect } from "vitest";
2
+ import { phase, projectToJson, type Component } from "./component";
3
+ import { dockerBuild, jvmBuild, healthGate, waitEndpoint, waitClusterHealthy } from "./builders";
4
+
5
+ describe("step builders (#658)", () => {
6
+ test("a step builder tags its input with the verb kind", () => {
7
+ expect(healthGate({ path: "/healthz" })).toEqual({ kind: "health-gate", path: "/healthz" });
8
+ expect(waitEndpoint({ url: "http://x/health" })).toEqual({ kind: "wait-endpoint", url: "http://x/health" });
9
+ expect(waitClusterHealthy({ cluster: "h:7687", size: 1 })).toEqual({
10
+ kind: "wait-cluster-healthy",
11
+ cluster: "h:7687",
12
+ size: 1,
13
+ });
14
+ });
15
+
16
+ test("build-family builders produce a BuildSpec (for the `build` field)", () => {
17
+ expect(dockerBuild({ context: ".", into: "archive" })).toEqual({
18
+ kind: "docker-build",
19
+ context: ".",
20
+ into: "archive",
21
+ });
22
+ expect(jvmBuild({ tool: "maven", path: ".", into: "archive" })).toEqual({
23
+ kind: "jvm-build",
24
+ tool: "maven",
25
+ path: ".",
26
+ into: "archive",
27
+ });
28
+ });
29
+
30
+ test("a component authored with builders projects identically to kind-literals", () => {
31
+ const built: Component = {
32
+ name: "svc",
33
+ dependsOn: [],
34
+ build: dockerBuild({ context: ".", into: "archive" }),
35
+ deploy: [phase("Verify", [healthGate({ path: "/healthz" })])],
36
+ };
37
+ const literal: Component = {
38
+ name: "svc",
39
+ dependsOn: [],
40
+ build: { kind: "docker-build", context: ".", into: "archive" },
41
+ deploy: [phase("Verify", [{ kind: "health-gate", path: "/healthz" }])],
42
+ };
43
+ expect(projectToJson(built)).toEqual(projectToJson(literal));
44
+ });
45
+ });
@@ -0,0 +1,71 @@
1
+ /**
2
+ * Typed step-builder API (#658) — ergonomic sugar over the kind-literal
3
+ * `Step`/`BuildSpec` contract. `phase("Verify", [healthGate({ path })])`
4
+ * is exactly `phase("Verify", [{ kind: "health-gate", path }])`, but
5
+ * with per-verb argument checking and autocomplete from each capability's own
6
+ * `Input` type. Core exports the agnostic verbs' builders; a lexicon exports
7
+ * its own (e.g. the aws lexicon's `cfnDeploy`, reusing the exported `step`).
8
+ *
9
+ * These are pure projections: a builder returns the same kind-literal the
10
+ * driver dispatches on (see ./component.ts's `Step`), so the JSON contract
11
+ * (component.schema.json) stays authoritative and a hand-written or
12
+ * non-chant-authored component keeps working. The runtime `Capability`
13
+ * objects live under `*Capability` names (./verbs/*), registered by `.kind`.
14
+ */
15
+
16
+ import type { BuildSpec, Step } from "./component";
17
+ import type {
18
+ DockerBuildInput,
19
+ ZipPackageInput,
20
+ JvmBuildInput,
21
+ GenerateSbomInput,
22
+ SignInput,
23
+ AttestProvenanceInput,
24
+ VerifyInput,
25
+ ScanVulnerabilitiesInput,
26
+ VulnGateInput,
27
+ WaitClusterHealthyInput,
28
+ WaitEndpointInput,
29
+ HealthGateInput,
30
+ ShellInput,
31
+ } from "./verbs/index";
32
+
33
+ /**
34
+ * Build a `(input) => Step` for one deploy verb: tags the input with its
35
+ * `kind`. Exported so a lexicon's capability plugin (e.g. the aws lexicon,
36
+ * which owns the `cfn-deploy`/`emr-*`/… builders) can offer the same typed
37
+ * sugar for its own verbs without re-implementing this projection.
38
+ */
39
+ export function step<In extends object>(kind: string): (input: In) => Step {
40
+ return (input) => ({ kind, ...input }) as Step;
41
+ }
42
+
43
+ /** Build a `(input) => BuildSpec` for one build verb (the `build` field, not `deploy`). */
44
+ function buildSpec<In extends object>(kind: string): (input: In) => BuildSpec {
45
+ return (input) => ({ kind, ...input }) as BuildSpec;
46
+ }
47
+
48
+ // ── build family → BuildSpec (the component's `build` field) ─────────────────
49
+ export const dockerBuild = buildSpec<DockerBuildInput>("docker-build");
50
+ export const zipPackage = buildSpec<ZipPackageInput>("zip-package");
51
+ export const jvmBuild = buildSpec<JvmBuildInput>("jvm-build");
52
+
53
+ // ── sbom ─────────────────────────────────────────────────────────────────────
54
+ export const generateSbom = step<GenerateSbomInput>("generate-sbom");
55
+
56
+ // ── supply-chain security / policy ───────────────────────────────────────────
57
+ export const sign = step<SignInput>("sign");
58
+ export const attestProvenance = step<AttestProvenanceInput>("attest-provenance");
59
+ export const verify = step<VerifyInput>("verify");
60
+ export const scanVulnerabilities = step<ScanVulnerabilitiesInput>("scan-vulnerabilities");
61
+ export const vulnGate = step<VulnGateInput>("vuln-gate");
62
+
63
+ // ── wait / verify (agnostic) ─────────────────────────────────────────────────
64
+ // The cloud-specific waits (`wait-for-stack`/`wait-steady-state`/`wait-job`)
65
+ // ship as builders from the aws lexicon, alongside their capabilities.
66
+ export const waitClusterHealthy = step<WaitClusterHealthyInput>("wait-cluster-healthy");
67
+ export const waitEndpoint = step<WaitEndpointInput>("wait-endpoint");
68
+ export const healthGate = step<HealthGateInput>("health-gate");
69
+
70
+ // ── escape hatch ─────────────────────────────────────────────────────────────
71
+ export const shell = step<ShellInput>("shell");