@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,209 @@
1
+ import { describe, test, expect } from "vitest";
2
+ import { withTestDir } from "@intentius/chant-test-utils";
3
+ import { spawnSync } from "node:child_process";
4
+ import { writeFileSync } from "node:fs";
5
+ import { join } from "node:path";
6
+ import {
7
+ appendReleaseRecord,
8
+ readReleaseLedger,
9
+ listReleaseEnvironments,
10
+ validateReleaseRecord,
11
+ InvalidReleaseRecordError,
12
+ latestPerComponent,
13
+ recordsForDigest,
14
+ type ReleaseRecord,
15
+ type ReleaseRecordInput,
16
+ } from "./release-ledger";
17
+
18
+ function git(args: string[], cwd: string): { stdout: string; exitCode: number } {
19
+ const r = spawnSync("git", args, { cwd, encoding: "utf-8" });
20
+ return { stdout: r.stdout ?? "", exitCode: r.status ?? -1 };
21
+ }
22
+
23
+ async function initRepo(dir: string): Promise<void> {
24
+ git(["init", "-q", "-b", "main"], dir);
25
+ git(["config", "user.email", "test@chant.dev"], dir);
26
+ git(["config", "user.name", "Test"], dir);
27
+ writeFileSync(join(dir, "README.md"), "fixture\n");
28
+ git(["add", "README.md"], dir);
29
+ git(["commit", "-q", "-m", "init"], dir);
30
+ }
31
+
32
+ function makeInput(overrides?: Partial<ReleaseRecordInput>): ReleaseRecordInput {
33
+ return {
34
+ component: "search-service",
35
+ env: "prod",
36
+ digest: "sha256:abc123",
37
+ gitSha: "deadbeef",
38
+ runId: "run-1",
39
+ timestamp: "2026-01-01T00:00:00.000Z",
40
+ actor: "ci-bot",
41
+ ...overrides,
42
+ };
43
+ }
44
+
45
+ describe("release-ledger", () => {
46
+ describe("validateReleaseRecord", () => {
47
+ test("no missing fields for a complete record", () => {
48
+ expect(validateReleaseRecord({ version: 1, ...makeInput() })).toEqual([]);
49
+ });
50
+
51
+ test("reports every missing required field", () => {
52
+ const missing = validateReleaseRecord({ component: "x" });
53
+ expect(missing).toContain("env");
54
+ expect(missing).toContain("digest");
55
+ expect(missing).toContain("gitSha");
56
+ expect(missing).toContain("runId");
57
+ expect(missing).toContain("timestamp");
58
+ expect(missing).toContain("actor");
59
+ expect(missing).not.toContain("component");
60
+ });
61
+
62
+ test("empty string counts as missing", () => {
63
+ expect(validateReleaseRecord({ ...makeInput(), actor: "" })).toEqual(["actor"]);
64
+ });
65
+ });
66
+
67
+ describe("appendReleaseRecord", () => {
68
+ test("throws InvalidReleaseRecordError before any git operation on an incomplete record", async () => {
69
+ await withTestDir(async (dir) => {
70
+ await initRepo(dir);
71
+ await expect(
72
+ appendReleaseRecord({ ...makeInput(), actor: "" }, { cwd: dir }),
73
+ ).rejects.toBeInstanceOf(InvalidReleaseRecordError);
74
+ const ledger = await readReleaseLedger("prod", { cwd: dir });
75
+ expect(ledger.records).toEqual([]);
76
+ });
77
+ });
78
+
79
+ test("writes one JSON-lines record readable back via readReleaseLedger", async () => {
80
+ await withTestDir(async (dir) => {
81
+ await initRepo(dir);
82
+ const { commit, record } = await appendReleaseRecord(makeInput(), { cwd: dir });
83
+ expect(commit).toMatch(/^[0-9a-f]{40}$/);
84
+ expect(record.version).toBe(1);
85
+
86
+ const { records, malformed } = await readReleaseLedger("prod", { cwd: dir });
87
+ expect(malformed).toBe(0);
88
+ expect(records).toHaveLength(1);
89
+ expect(records[0]).toMatchObject(makeInput());
90
+ });
91
+ });
92
+
93
+ test("appends rather than replaces — multiple releases accumulate in order", async () => {
94
+ await withTestDir(async (dir) => {
95
+ await initRepo(dir);
96
+ await appendReleaseRecord(makeInput({ digest: "sha256:v1", timestamp: "2026-01-01T00:00:00.000Z" }), { cwd: dir });
97
+ await appendReleaseRecord(makeInput({ digest: "sha256:v2", timestamp: "2026-01-02T00:00:00.000Z" }), { cwd: dir });
98
+ await appendReleaseRecord(makeInput({ digest: "sha256:v3", timestamp: "2026-01-03T00:00:00.000Z" }), { cwd: dir });
99
+
100
+ const { records } = await readReleaseLedger("prod", { cwd: dir });
101
+ expect(records.map((r) => r.digest)).toEqual(["sha256:v1", "sha256:v2", "sha256:v3"]);
102
+ });
103
+ });
104
+
105
+ test("never mutates a previously written record — old lines survive new appends byte-identical", async () => {
106
+ await withTestDir(async (dir) => {
107
+ await initRepo(dir);
108
+ await appendReleaseRecord(makeInput({ digest: "sha256:v1" }), { cwd: dir });
109
+ const { records: afterFirst } = await readReleaseLedger("prod", { cwd: dir });
110
+
111
+ await appendReleaseRecord(makeInput({ digest: "sha256:v2", component: "other-service" }), { cwd: dir });
112
+ const { records: afterSecond } = await readReleaseLedger("prod", { cwd: dir });
113
+
114
+ expect(afterSecond[0]).toEqual(afterFirst[0]);
115
+ expect(afterSecond).toHaveLength(2);
116
+ });
117
+ });
118
+
119
+ test("separate environments get separate ledgers", async () => {
120
+ await withTestDir(async (dir) => {
121
+ await initRepo(dir);
122
+ await appendReleaseRecord(makeInput({ env: "staging", digest: "sha256:staging1" }), { cwd: dir });
123
+ await appendReleaseRecord(makeInput({ env: "prod", digest: "sha256:prod1" }), { cwd: dir });
124
+
125
+ const staging = await readReleaseLedger("staging", { cwd: dir });
126
+ const prod = await readReleaseLedger("prod", { cwd: dir });
127
+ expect(staging.records.map((r) => r.digest)).toEqual(["sha256:staging1"]);
128
+ expect(prod.records.map((r) => r.digest)).toEqual(["sha256:prod1"]);
129
+ });
130
+ });
131
+
132
+ test("release ledger and snapshot coexist on the same orphan branch without clobbering each other", async () => {
133
+ const { writeSnapshot, readSnapshot } = await import("./git");
134
+ await withTestDir(async (dir) => {
135
+ await initRepo(dir);
136
+ await writeSnapshot("prod", "aws", JSON.stringify({ a: 1 }), { cwd: dir });
137
+ await appendReleaseRecord(makeInput({ env: "prod" }), { cwd: dir });
138
+
139
+ expect(await readSnapshot("prod", "aws", { cwd: dir })).toBeTruthy();
140
+ const { records } = await readReleaseLedger("prod", { cwd: dir });
141
+ expect(records).toHaveLength(1);
142
+ });
143
+ });
144
+ });
145
+
146
+ describe("readReleaseLedger", () => {
147
+ test("returns empty for an environment with no ledger", async () => {
148
+ await withTestDir(async (dir) => {
149
+ await initRepo(dir);
150
+ const { records, malformed } = await readReleaseLedger("prod", { cwd: dir });
151
+ expect(records).toEqual([]);
152
+ expect(malformed).toBe(0);
153
+ });
154
+ });
155
+ });
156
+
157
+ describe("listReleaseEnvironments", () => {
158
+ test("lists every environment carrying a release ledger", async () => {
159
+ await withTestDir(async (dir) => {
160
+ await initRepo(dir);
161
+ await appendReleaseRecord(makeInput({ env: "prod" }), { cwd: dir });
162
+ await appendReleaseRecord(makeInput({ env: "staging" }), { cwd: dir });
163
+ expect(await listReleaseEnvironments({ cwd: dir })).toEqual(["prod", "staging"]);
164
+ });
165
+ });
166
+
167
+ test("returns empty when no orphan branch exists yet", async () => {
168
+ await withTestDir(async (dir) => {
169
+ await initRepo(dir);
170
+ expect(await listReleaseEnvironments({ cwd: dir })).toEqual([]);
171
+ });
172
+ });
173
+ });
174
+
175
+ describe("latestPerComponent", () => {
176
+ test("picks the record with the latest timestamp per component", () => {
177
+ const records: ReleaseRecord[] = [
178
+ { version: 1, ...makeInput({ digest: "sha256:v1", timestamp: "2026-01-01T00:00:00.000Z" }) },
179
+ { version: 1, ...makeInput({ digest: "sha256:v2", timestamp: "2026-01-03T00:00:00.000Z" }) },
180
+ { version: 1, ...makeInput({ digest: "sha256:v1-mid", timestamp: "2026-01-02T00:00:00.000Z" }) },
181
+ ];
182
+ const latest = latestPerComponent(records);
183
+ expect(latest.get("search-service")?.digest).toBe("sha256:v2");
184
+ });
185
+
186
+ test("keeps separate entries per component", () => {
187
+ const records: ReleaseRecord[] = [
188
+ { version: 1, ...makeInput({ component: "a", digest: "sha256:a1" }) },
189
+ { version: 1, ...makeInput({ component: "b", digest: "sha256:b1" }) },
190
+ ];
191
+ const latest = latestPerComponent(records);
192
+ expect(latest.get("a")?.digest).toBe("sha256:a1");
193
+ expect(latest.get("b")?.digest).toBe("sha256:b1");
194
+ });
195
+ });
196
+
197
+ describe("recordsForDigest", () => {
198
+ test("finds every record referencing a given digest", () => {
199
+ const records: ReleaseRecord[] = [
200
+ { version: 1, ...makeInput({ env: "staging", digest: "sha256:shared" }) },
201
+ { version: 1, ...makeInput({ env: "prod", digest: "sha256:shared" }) },
202
+ { version: 1, ...makeInput({ env: "prod", digest: "sha256:other" }) },
203
+ ];
204
+ const found = recordsForDigest(records, "sha256:shared");
205
+ expect(found).toHaveLength(2);
206
+ expect(found.every((r) => r.digest === "sha256:shared")).toBe(true);
207
+ });
208
+ });
209
+ });
@@ -0,0 +1,185 @@
1
+ /**
2
+ * Release ledger — the recorded half of build & deploy observability (#568,
3
+ * epic #551 "Build & deploy observability").
4
+ *
5
+ * chant already has the *live-truth* half: ownership markers stamp stack/env
6
+ * identity on live resources, `lifecycle diff --live` / `lifecycle plan`
7
+ * compare declared vs live, and snapshots on `chant/lifecycle` give a
8
+ * forensic timeline (see ./snapshot.ts, ./live-diff.ts, ./change-set.ts).
9
+ * What was missing is the *record* half: "what we recorded deploying",
10
+ * independent of whether the thing is still running.
11
+ *
12
+ * A release record is immutable and append-only: each deploy appends one line
13
+ * to `<env>/releases.jsonl` on the `chant/lifecycle` orphan branch (see
14
+ * ./git.ts's `appendReleaseRecord`/`readReleaseLedger`) — never rewritten,
15
+ * never replaced in place, mirroring how the snapshot branch itself is
16
+ * treated as observational evidence rather than a mutable state file. The
17
+ * append-only property is what makes "which build was in staging on
18
+ * Tuesday" answerable months later even after staging has moved on to a
19
+ * dozen newer digests.
20
+ *
21
+ * The binding key is the artifact **digest** (`sha256:...`), the same
22
+ * identity `BuildArchive` manifests and `publish-image`/`load-image-on-host`
23
+ * promote by (see ../components/verbs/build-archive.ts,
24
+ * ../components/verbs/publish.ts). Because publish promotes by digest never
25
+ * rebuilding, the same identity threads build → dev → staging → prod, so
26
+ * "which build is in prod, and is it the one tested in staging" is a
27
+ * straightforward digest comparison over this ledger — never a guess through
28
+ * CI logs.
29
+ */
30
+
31
+ import { sortedJsonReplacer } from "../utils";
32
+ import { appendReleaseRecordLine, readReleaseLedgerLines, listLedgerEnvironments as gitListLedgerEnvironments } from "./git";
33
+
34
+ /**
35
+ * One immutable deploy record: `(component, env, artifact digest, git sha,
36
+ * run id, timestamp, actor)`, referencing the build archive by digest — the
37
+ * exact shape epic #551 "Build & deploy observability" asks for.
38
+ */
39
+ export interface ReleaseRecord {
40
+ /** Schema version, so an incompatible future shape can be detected before being misread. */
41
+ version: 1;
42
+ /** Component this deploy released (matches `Component.name`, ../components/component.ts). */
43
+ component: string;
44
+ /** Environment this deploy targeted. */
45
+ env: string;
46
+ /**
47
+ * Content-addressed digest of the promoted artifact (`sha256:...`) — the
48
+ * same digest `publish-image`/`load-image-on-host` return
49
+ * (`PublishImageOutput.digest`, ../components/verbs/publish.ts) and the
50
+ * one `BuildArchiveManifest` entries carry
51
+ * (../components/verbs/build-archive.ts). The join key for every query
52
+ * this ledger answers.
53
+ */
54
+ digest: string;
55
+ /** Git commit SHA the deploy was built from. */
56
+ gitSha: string;
57
+ /** Orchestrator run identifier (a `DriverRunResult`/Op run id, or a CI run id in generate mode) — the pointer back into the deploy event log. */
58
+ runId: string;
59
+ /**
60
+ * When this record was written. The caller supplies this — the session
61
+ * cannot call `Date.now()` reliably in every context, so the CLI resolves
62
+ * a real `new Date()` at record time and passes it in as an ISO-8601
63
+ * string; library code never defaults this internally.
64
+ */
65
+ timestamp: string;
66
+ /** Who/what triggered the deploy (a username, a CI actor, a service account). */
67
+ actor: string;
68
+ /** Optional: the archive's own manifest digest (../components/verbs/build-archive.ts's `manifestDigest`), when the caller has it — lets a reader recover full build contents/provenance, not just the promoted image digest. */
69
+ manifestDigest?: string;
70
+ }
71
+
72
+ /** Required, non-empty-string fields every `ReleaseRecord` must carry. */
73
+ const REQUIRED_STRING_FIELDS: Array<keyof ReleaseRecord> = [
74
+ "component",
75
+ "env",
76
+ "digest",
77
+ "gitSha",
78
+ "runId",
79
+ "timestamp",
80
+ "actor",
81
+ ];
82
+
83
+ /** Thrown by `appendReleaseRecord` when the record is missing a required field — a release record can never be recorded partially, since it is never editable afterward. */
84
+ export class InvalidReleaseRecordError extends Error {
85
+ constructor(public readonly missing: string[]) {
86
+ super(`release record is missing required field(s): ${missing.join(", ")}`);
87
+ this.name = "InvalidReleaseRecordError";
88
+ }
89
+ }
90
+
91
+ /** Validate that a release record carries every required field as a non-empty string. */
92
+ export function validateReleaseRecord(record: Partial<ReleaseRecord>): string[] {
93
+ const missing: string[] = [];
94
+ for (const field of REQUIRED_STRING_FIELDS) {
95
+ const value = record[field];
96
+ if (typeof value !== "string" || value.length === 0) missing.push(field);
97
+ }
98
+ return missing;
99
+ }
100
+
101
+ /** Input a caller supplies to record one deploy — `version` is filled in, everything else is required (see `ReleaseRecord`). */
102
+ export type ReleaseRecordInput = Omit<ReleaseRecord, "version">;
103
+
104
+ /**
105
+ * Append one immutable release record to the `<env>` ledger on the
106
+ * `chant/lifecycle` orphan branch. Throws `InvalidReleaseRecordError` before
107
+ * any git operation runs if a required field is missing — never partially
108
+ * records, since nothing about a release record is editable after the fact.
109
+ *
110
+ * Does not push to the remote; call `pushLifecycle` (./git.ts) afterward, the
111
+ * same two-step (`write` then `push`) shape `takeSnapshot` (./snapshot.ts)
112
+ * uses, so a caller recording several releases in one run can batch the push.
113
+ */
114
+ export async function appendReleaseRecord(
115
+ input: ReleaseRecordInput,
116
+ opts?: { cwd?: string },
117
+ ): Promise<{ commit: string; record: ReleaseRecord }> {
118
+ const missing = validateReleaseRecord(input);
119
+ if (missing.length > 0) throw new InvalidReleaseRecordError(missing);
120
+
121
+ const record: ReleaseRecord = { version: 1, ...input };
122
+ const json = JSON.stringify(record, sortedJsonReplacer);
123
+ const commit = await appendReleaseRecordLine(record.env, json, opts);
124
+ return { commit, record };
125
+ }
126
+
127
+ /**
128
+ * Read every release record for `environment`, oldest first. Malformed lines
129
+ * (a hand-edited or corrupted ledger) are skipped rather than throwing, since
130
+ * a status query should degrade gracefully rather than fail outright on one
131
+ * bad line — but each skip is reported so callers/tests can surface it.
132
+ */
133
+ export async function readReleaseLedger(
134
+ environment: string,
135
+ opts?: { cwd?: string },
136
+ ): Promise<{ records: ReleaseRecord[]; malformed: number }> {
137
+ const lines = await readReleaseLedgerLines(environment, opts);
138
+ const records: ReleaseRecord[] = [];
139
+ let malformed = 0;
140
+ for (const line of lines) {
141
+ try {
142
+ const parsed = JSON.parse(line) as ReleaseRecord;
143
+ if (validateReleaseRecord(parsed).length > 0) {
144
+ malformed++;
145
+ continue;
146
+ }
147
+ records.push(parsed);
148
+ } catch {
149
+ malformed++;
150
+ }
151
+ }
152
+ return { records, malformed };
153
+ }
154
+
155
+ /** List every environment that has release records on the orphan branch. */
156
+ export async function listReleaseEnvironments(opts?: { cwd?: string }): Promise<string[]> {
157
+ return gitListLedgerEnvironments(opts);
158
+ }
159
+
160
+ /**
161
+ * The latest release record per component in `records` — "what we recorded
162
+ * deploying" reduces to one row per component by taking the most recent
163
+ * `timestamp` (ties broken by ledger order, i.e. later append wins), since
164
+ * a component may have many historical records in one environment's ledger.
165
+ */
166
+ export function latestPerComponent(records: ReleaseRecord[]): Map<string, ReleaseRecord> {
167
+ const latest = new Map<string, ReleaseRecord>();
168
+ for (const record of records) {
169
+ const current = latest.get(record.component);
170
+ if (!current || record.timestamp >= current.timestamp) {
171
+ latest.set(record.component, record);
172
+ }
173
+ }
174
+ return latest;
175
+ }
176
+
177
+ /**
178
+ * Find every release record across `records` that references `digest` — the
179
+ * cross-environment query "is the build in prod the one tested in staging"
180
+ * reduces to: read both envs' ledgers, then check whether the same digest
181
+ * appears in each. Sorted oldest first, matching ledger order.
182
+ */
183
+ export function recordsForDigest(records: ReleaseRecord[], digest: string): ReleaseRecord[] {
184
+ return records.filter((r) => r.digest === digest);
185
+ }