@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
@@ -11,6 +11,12 @@ import {
11
11
  getHeadCommit,
12
12
  pushLifecycle,
13
13
  StaleLifecycleBranchError,
14
+ appendReleaseRecordLine,
15
+ readReleaseLedgerLines,
16
+ listLedgerEnvironments,
17
+ writeBlobToPath,
18
+ readBlobFromPath,
19
+ listFilesInDir,
14
20
  } from "./git";
15
21
 
16
22
  function git(args: string[], cwd: string): { stdout: string; exitCode: number } {
@@ -83,6 +89,57 @@ describe("lifecycle/git", () => {
83
89
  });
84
90
  });
85
91
 
92
+ // ── Generic blob helpers used by ./build-ledger-store.ts (#609) ────────────
93
+
94
+ describe("writeBlobToPath / readBlobFromPath / listFilesInDir (generic, non-env namespaces)", () => {
95
+ test("writes/reads a blob under an arbitrary top-level directory, not just an env", async () => {
96
+ await withTestDir(async (dir) => {
97
+ await initRepo(dir);
98
+ const sha = await writeBlobToPath("_builds", "sha256_abc.json", '{"a":1}', "Build manifest", { cwd: dir });
99
+ expect(sha).toMatch(/^[0-9a-f]{40}$/);
100
+
101
+ const out = await readBlobFromPath("_builds", "sha256_abc.json", { cwd: dir });
102
+ expect(JSON.parse(out!)).toEqual({ a: 1 });
103
+ });
104
+ });
105
+
106
+ test("readBlobFromPath returns null for a missing directory/file", async () => {
107
+ await withTestDir(async (dir) => {
108
+ await initRepo(dir);
109
+ expect(await readBlobFromPath("_builds", "missing.json", { cwd: dir })).toBeNull();
110
+ });
111
+ });
112
+
113
+ test("listFilesInDir lists every file directly under a directory", async () => {
114
+ await withTestDir(async (dir) => {
115
+ await initRepo(dir);
116
+ await writeBlobToPath("_builds", "a.json", "{}", "m", { cwd: dir });
117
+ await writeBlobToPath("_builds", "b.json", "{}", "m", { cwd: dir });
118
+ const files = await listFilesInDir("_builds", { cwd: dir });
119
+ expect(files.sort()).toEqual(["a.json", "b.json"]);
120
+ });
121
+ });
122
+
123
+ test("listFilesInDir returns empty for a directory that doesn't exist yet", async () => {
124
+ await withTestDir(async (dir) => {
125
+ await initRepo(dir);
126
+ expect(await listFilesInDir("_builds", { cwd: dir })).toEqual([]);
127
+ });
128
+ });
129
+
130
+ test("a non-env top-level directory (_builds) coexists with per-env snapshot/release directories", async () => {
131
+ await withTestDir(async (dir) => {
132
+ await initRepo(dir);
133
+ await writeSnapshot("prod", "aws", JSON.stringify({ a: 1 }), { cwd: dir });
134
+ await writeBlobToPath("_builds", "sha256_abc.json", '{"b":2}', "Build manifest", { cwd: dir });
135
+
136
+ expect(await readSnapshot("prod", "aws", { cwd: dir })).toBeTruthy();
137
+ const out = await readBlobFromPath("_builds", "sha256_abc.json", { cwd: dir });
138
+ expect(JSON.parse(out!)).toEqual({ b: 2 });
139
+ });
140
+ });
141
+ });
142
+
86
143
  test("listSnapshots returns commit history of the orphan branch", async () => {
87
144
  await withTestDir(async (dir) => {
88
145
  await initRepo(dir);
@@ -188,4 +245,65 @@ describe("lifecycle/git", () => {
188
245
  expect(err.expected).toBeNull();
189
246
  expect(err.message).toContain("moved");
190
247
  });
248
+
249
+ // ── Release ledger plumbing (#568) ──────────────────────────────────────
250
+
251
+ describe("release ledger plumbing", () => {
252
+ test("appendReleaseRecordLine creates the orphan branch and is readable via readReleaseLedgerLines", async () => {
253
+ await withTestDir(async (dir) => {
254
+ await initRepo(dir);
255
+ const sha = await appendReleaseRecordLine("prod", '{"v":1}', { cwd: dir });
256
+ expect(sha).toMatch(/^[0-9a-f]{40}$/);
257
+ const lines = await readReleaseLedgerLines("prod", { cwd: dir });
258
+ expect(lines).toEqual(['{"v":1}']);
259
+ });
260
+ });
261
+
262
+ test("readReleaseLedgerLines returns [] for an environment with no ledger", async () => {
263
+ await withTestDir(async (dir) => {
264
+ await initRepo(dir);
265
+ expect(await readReleaseLedgerLines("prod", { cwd: dir })).toEqual([]);
266
+ });
267
+ });
268
+
269
+ test("appendReleaseRecordLine appends rather than overwrites", async () => {
270
+ await withTestDir(async (dir) => {
271
+ await initRepo(dir);
272
+ await appendReleaseRecordLine("prod", '{"v":1}', { cwd: dir });
273
+ await appendReleaseRecordLine("prod", '{"v":2}', { cwd: dir });
274
+ await appendReleaseRecordLine("prod", '{"v":3}', { cwd: dir });
275
+ expect(await readReleaseLedgerLines("prod", { cwd: dir })).toEqual(['{"v":1}', '{"v":2}', '{"v":3}']);
276
+ });
277
+ });
278
+
279
+ test("release ledger and snapshot files coexist under the same env directory", async () => {
280
+ await withTestDir(async (dir) => {
281
+ await initRepo(dir);
282
+ await writeSnapshot("prod", "aws", JSON.stringify({ a: 1 }), { cwd: dir });
283
+ await appendReleaseRecordLine("prod", '{"v":1}', { cwd: dir });
284
+ // Both must survive — the release ledger uses a different filename
285
+ // ("releases.jsonl") than any lexicon snapshot ("<lexicon>.json").
286
+ expect(await readSnapshot("prod", "aws", { cwd: dir })).toBeTruthy();
287
+ expect(await readReleaseLedgerLines("prod", { cwd: dir })).toEqual(['{"v":1}']);
288
+ });
289
+ });
290
+
291
+ test("listLedgerEnvironments finds every env with a release ledger, ignoring envs that only have snapshots", async () => {
292
+ await withTestDir(async (dir) => {
293
+ await initRepo(dir);
294
+ await writeSnapshot("dev", "aws", JSON.stringify({ a: 1 }), { cwd: dir }); // snapshot only, no ledger
295
+ await appendReleaseRecordLine("staging", '{"v":1}', { cwd: dir });
296
+ await appendReleaseRecordLine("prod", '{"v":1}', { cwd: dir });
297
+
298
+ expect(await listLedgerEnvironments({ cwd: dir })).toEqual(["prod", "staging"]);
299
+ });
300
+ });
301
+
302
+ test("listLedgerEnvironments returns [] when the orphan branch doesn't exist yet", async () => {
303
+ await withTestDir(async (dir) => {
304
+ await initRepo(dir);
305
+ expect(await listLedgerEnvironments({ cwd: dir })).toEqual([]);
306
+ });
307
+ });
308
+ });
191
309
  });
@@ -9,14 +9,31 @@ import { getRuntime } from "../runtime-adapter";
9
9
  const STATE_BRANCH = "chant/lifecycle";
10
10
 
11
11
  /**
12
- * Write a state snapshot JSON to the orphan branch.
12
+ * Write a blob to an arbitrary `<environment>/<filename>` path on the orphan
13
+ * branch, preserving every other env/file entry already on the branch.
13
14
  *
14
- * Pipeline: hash-object → mktree → commit-tree → update-ref
15
+ * Pipeline: hash-object → mktree → commit-tree → update-ref. Factored out of
16
+ * `writeSnapshot` so the release ledger (#568, epic #551 "Build & deploy
17
+ * observability") can reuse the identical git-plumbing path for a different
18
+ * filename (`releases.jsonl`) under the same env directory, rather than a
19
+ * parallel storage mechanism.
20
+ *
21
+ * Exported (not just used internally) so ./build-ledger-store.ts (#609) can
22
+ * reuse this exact plumbing for a top-level directory that isn't really an
23
+ * "environment" (`_builds`, keyed by manifest digest rather than env name) —
24
+ * a build archive is promoted by digest across environments, never owned by
25
+ * one, so it belongs in its own digest-keyed namespace on the same orphan
26
+ * branch rather than duplicated per env. The parameter is still named
27
+ * `environment` because it is literally the first path segment / root-tree
28
+ * entry name this function's tree-building logic groups by; callers outside
29
+ * this module that pass a non-env value (like `_builds`) are relying on that
30
+ * generic behavior, not on any env-specific semantics.
15
31
  */
16
- export async function writeSnapshot(
32
+ export async function writeBlobToPath(
17
33
  environment: string,
18
- lexicon: string,
19
- json: string,
34
+ filename: string,
35
+ content: string,
36
+ commitMessage: string,
20
37
  opts?: { cwd?: string },
21
38
  ): Promise<string> {
22
39
  const rt = getRuntime();
@@ -25,7 +42,7 @@ export async function writeSnapshot(
25
42
  // 1. Write blob — hash-object reads from stdin, but spawn() doesn't expose
26
43
  // a stdin handle, so we run via a shell pipeline (`echo … | git hash-object`).
27
44
  const blobResult = await rt.spawn(
28
- ["sh", "-c", `echo '${json.replace(/'/g, "'\\''")}' | git hash-object -w --stdin`],
45
+ ["sh", "-c", `echo '${content.replace(/'/g, "'\\''")}' | git hash-object -w --stdin`],
29
46
  { cwd },
30
47
  );
31
48
  if (blobResult.exitCode !== 0) {
@@ -33,13 +50,12 @@ export async function writeSnapshot(
33
50
  }
34
51
  const blobSha = blobResult.stdout.trim();
35
52
 
36
- // 2. Read existing tree (if branch exists) to preserve other env/lexicon entries
53
+ // 2. Read existing tree (if branch exists) to preserve other env/file entries
37
54
  const existingTree = await readTree(cwd);
38
55
 
39
56
  // 3. Build new tree entries
40
- const path = `${environment}/${lexicon}.json`;
57
+ const path = `${environment}/${filename}`;
41
58
  const entries = mergeTreeEntry(existingTree, path, blobSha);
42
- const treeInput = entries.map((e) => `${e.mode} ${e.type} ${e.sha}\t${e.name}`).join("\n");
43
59
 
44
60
  // mktree needs a nested tree structure. Build env subtree first, then root tree.
45
61
  // Build env subtree
@@ -84,7 +100,7 @@ export async function writeSnapshot(
84
100
  const parentRef = await getStateBranchTip(cwd);
85
101
  const parentArgs = parentRef ? ["-p", parentRef] : [];
86
102
  const commitResult = await rt.spawn(
87
- ["git", "commit-tree", ...parentArgs, "-m", "State snapshot", rootTreeSha],
103
+ ["git", "commit-tree", ...parentArgs, "-m", commitMessage, rootTreeSha],
88
104
  { cwd },
89
105
  );
90
106
  if (commitResult.exitCode !== 0) {
@@ -105,22 +121,141 @@ export async function writeSnapshot(
105
121
  }
106
122
 
107
123
  /**
108
- * Read a snapshot from the orphan branch.
124
+ * Read a blob from an arbitrary `<environment>/<filename>` path on the orphan
125
+ * branch. Returns null when the path doesn't exist (missing branch, env, or
126
+ * file). Sibling of `writeBlobToPath`. Exported for the same reason
127
+ * `writeBlobToPath` is — ./build-ledger-store.ts (#609) reads manifests back
128
+ * from the `_builds` namespace through this identical helper.
109
129
  */
110
- export async function readSnapshot(
130
+ export async function readBlobFromPath(
111
131
  environment: string,
112
- lexicon: string,
132
+ filename: string,
113
133
  opts?: { cwd?: string },
114
134
  ): Promise<string | null> {
115
135
  const rt = getRuntime();
116
136
  const result = await rt.spawn(
117
- ["git", "show", `${STATE_BRANCH}:${environment}/${lexicon}.json`],
137
+ ["git", "show", `${STATE_BRANCH}:${environment}/${filename}`],
118
138
  { cwd: opts?.cwd },
119
139
  );
120
140
  if (result.exitCode !== 0) return null;
121
141
  return result.stdout;
122
142
  }
123
143
 
144
+ /**
145
+ * Write a state snapshot JSON to the orphan branch.
146
+ *
147
+ * Pipeline: hash-object → mktree → commit-tree → update-ref
148
+ */
149
+ export async function writeSnapshot(
150
+ environment: string,
151
+ lexicon: string,
152
+ json: string,
153
+ opts?: { cwd?: string },
154
+ ): Promise<string> {
155
+ return writeBlobToPath(environment, `${lexicon}.json`, json, "State snapshot", opts);
156
+ }
157
+
158
+ /**
159
+ * Read a snapshot from the orphan branch.
160
+ */
161
+ export async function readSnapshot(
162
+ environment: string,
163
+ lexicon: string,
164
+ opts?: { cwd?: string },
165
+ ): Promise<string | null> {
166
+ return readBlobFromPath(environment, `${lexicon}.json`, opts);
167
+ }
168
+
169
+ /**
170
+ * Append one immutable release record line to `<environment>/releases.jsonl`
171
+ * on the orphan branch (#568, epic #551 "Build & deploy observability"). Same
172
+ * git-plumbing path `writeSnapshot` uses, just a different filename under the
173
+ * env directory and append-only (JSON Lines) instead of replace-whole-file:
174
+ * each deploy adds one line, never rewrites a previous one, so the ledger
175
+ * stays a durable, append-only history rather than a point-in-time snapshot.
176
+ *
177
+ * Low-level plumbing over an already-serialized line; ../release-ledger.ts's
178
+ * `appendReleaseRecord` is the typed, validated public API most callers want
179
+ * — named distinctly here (`ReleaseRecordLine`) to avoid re-export ambiguity
180
+ * between the two modules.
181
+ *
182
+ * Returns the new orphan-branch commit SHA — the caller still owns pushing
183
+ * via `pushLifecycle` under the same concurrent-write lease `writeSnapshot`
184
+ * uses.
185
+ */
186
+ export async function appendReleaseRecordLine(
187
+ environment: string,
188
+ recordJson: string,
189
+ opts?: { cwd?: string },
190
+ ): Promise<string> {
191
+ const filename = "releases.jsonl";
192
+ const existing = await readBlobFromPath(environment, filename, opts);
193
+ const content = existing ? `${existing.replace(/\n$/, "")}\n${recordJson}` : recordJson;
194
+ return writeBlobToPath(environment, filename, content, "Release record", opts);
195
+ }
196
+
197
+ /**
198
+ * Read every release record line for `environment` from the orphan branch,
199
+ * oldest first, as raw JSON strings. Returns `[]` when no ledger exists yet
200
+ * for that env (never throws — a component that has never recorded a deploy
201
+ * is a normal, expected state). ../release-ledger.ts's `readReleaseLedger`
202
+ * parses/validates these lines into typed `ReleaseRecord`s.
203
+ */
204
+ export async function readReleaseLedgerLines(
205
+ environment: string,
206
+ opts?: { cwd?: string },
207
+ ): Promise<string[]> {
208
+ const content = await readBlobFromPath(environment, "releases.jsonl", opts);
209
+ if (!content) return [];
210
+ return content.split("\n").map((line) => line.trim()).filter(Boolean);
211
+ }
212
+
213
+ /**
214
+ * List every environment that has a release ledger on the orphan branch —
215
+ * the root-level directories under `chant/lifecycle` that carry a
216
+ * `releases.jsonl` entry. Used by `chant components status` (no env arg) to
217
+ * discover which environments to report on.
218
+ */
219
+ export async function listLedgerEnvironments(opts?: { cwd?: string }): Promise<string[]> {
220
+ const tip = await getStateBranchTip(opts?.cwd);
221
+ if (!tip) return [];
222
+ const rt = getRuntime();
223
+ const rootResult = await rt.spawn(["git", "ls-tree", STATE_BRANCH], { cwd: opts?.cwd });
224
+ if (rootResult.exitCode !== 0) return [];
225
+
226
+ const envs: string[] = [];
227
+ const lines = rootResult.stdout.trim().split("\n").filter(Boolean);
228
+ for (const line of lines) {
229
+ const match = line.match(/^(\d+)\s+(\w+)\s+([0-9a-f]+)\t(.+)$/);
230
+ if (!match) continue;
231
+ const [, , type, , name] = match;
232
+ if (type !== "tree") continue;
233
+ const hasLedger = await readBlobFromPath(name, "releases.jsonl", opts);
234
+ if (hasLedger) envs.push(name);
235
+ }
236
+ return envs.sort();
237
+ }
238
+
239
+ /**
240
+ * List every filename directly under `<dir>/` on the orphan branch (no
241
+ * recursion), or `[]` when the branch/directory doesn't exist yet. Generic
242
+ * sibling of `readEnvironmentSnapshots`'s inline file listing, factored out
243
+ * so ./build-ledger-store.ts (#609) can enumerate every persisted build
244
+ * manifest (`_builds/<digest>.json`) without duplicating this `git ls-tree
245
+ * --name-only` call.
246
+ */
247
+ export async function listFilesInDir(dir: string, opts?: { cwd?: string }): Promise<string[]> {
248
+ const tip = await getStateBranchTip(opts?.cwd);
249
+ if (!tip) return [];
250
+ const rt = getRuntime();
251
+ const lsResult = await rt.spawn(
252
+ ["git", "ls-tree", "--name-only", `${STATE_BRANCH}:${dir}/`],
253
+ { cwd: opts?.cwd },
254
+ );
255
+ if (lsResult.exitCode !== 0) return [];
256
+ return lsResult.stdout.trim().split("\n").filter(Boolean);
257
+ }
258
+
124
259
  /**
125
260
  * Read all snapshots for an environment (all lexicons).
126
261
  */
@@ -5,3 +5,8 @@ export * from "./snapshot";
5
5
  export * from "./live-diff";
6
6
  export * from "./change-set";
7
7
  export * from "./affected";
8
+ export * from "./release-ledger";
9
+ export * from "./build-ledger";
10
+ export * from "./build-ledger-store";
11
+ export * from "./oras-referrer-lookup";
12
+ export * from "./status";
@@ -0,0 +1,116 @@
1
+ /**
2
+ * Tests the real `ReferrerLookup` backend (#610, ./oras-referrer-lookup.ts)
3
+ * against a `MockProcessRunner`
4
+ * (../components/verbs/__tests__/mock-process-runner.ts) — no live `oras`,
5
+ * no live registry. Asserts the `oras discover` command constructed, that
6
+ * its JSON output is parsed and classified into `Referrer[]`, and that a
7
+ * missing `oras` binary surfaces `ToolNotAvailableError`.
8
+ */
9
+
10
+ import { describe, expect, it } from "vitest";
11
+ import { createOrasReferrerLookup } from "./oras-referrer-lookup";
12
+ import { ToolNotAvailableError } from "../components/verbs/process-runner";
13
+ import { createMockProcessRunner } from "../components/verbs/__tests__/mock-process-runner";
14
+
15
+ const REPO = "123.dkr.ecr.us-east-1.amazonaws.com/search";
16
+ const DIGEST = "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
17
+
18
+ function orasOutput(manifests: Array<{ digest: string; mediaType: string; artifactType?: string }>): string {
19
+ return JSON.stringify({ manifests });
20
+ }
21
+
22
+ describe("createOrasReferrerLookup (#610)", () => {
23
+ it("constructs the oras discover command against repo@digest", async () => {
24
+ const mock = createMockProcessRunner({ responses: { "oras discover": orasOutput([]) } });
25
+ const lookup = createOrasReferrerLookup({ repo: REPO, runner: mock.runner });
26
+
27
+ await lookup.discover(DIGEST);
28
+
29
+ const call = mock.calls.find((c) => c.command.startsWith("oras discover"))!;
30
+ expect(call.command).toBe(`oras discover --format json '${REPO}@${DIGEST}'`);
31
+ });
32
+
33
+ it("classifies an spdx/cyclonedx manifest as an sbom referrer", async () => {
34
+ const mock = createMockProcessRunner({
35
+ responses: {
36
+ "oras discover": orasOutput([
37
+ { digest: "sha256:sbom1", mediaType: "application/vnd.oci.image.manifest.v1+json", artifactType: "application/spdx+json" },
38
+ ]),
39
+ },
40
+ });
41
+ const lookup = createOrasReferrerLookup({ repo: REPO, runner: mock.runner });
42
+
43
+ const referrers = await lookup.discover(DIGEST);
44
+
45
+ expect(referrers).toEqual([
46
+ { kind: "sbom", mediaType: "application/spdx+json", digest: "sha256:sbom1", location: `${REPO}@sha256:sbom1` },
47
+ ]);
48
+ });
49
+
50
+ it("classifies an in-toto/slsa manifest as a provenance referrer", async () => {
51
+ const mock = createMockProcessRunner({
52
+ responses: {
53
+ "oras discover": orasOutput([
54
+ { digest: "sha256:prov1", mediaType: "application/vnd.oci.image.manifest.v1+json", artifactType: "application/vnd.in-toto+json" },
55
+ ]),
56
+ },
57
+ });
58
+ const lookup = createOrasReferrerLookup({ repo: REPO, runner: mock.runner });
59
+
60
+ const referrers = await lookup.discover(DIGEST);
61
+ expect(referrers[0]?.kind).toBe("provenance");
62
+ });
63
+
64
+ it("classifies a cosign signature manifest as a signature referrer", async () => {
65
+ const mock = createMockProcessRunner({
66
+ responses: {
67
+ "oras discover": orasOutput([
68
+ { digest: "sha256:sig1", mediaType: "application/vnd.dev.cosign.simplesigning.v1+json", artifactType: "application/vnd.dev.cosign.signature" },
69
+ ]),
70
+ },
71
+ });
72
+ const lookup = createOrasReferrerLookup({ repo: REPO, runner: mock.runner });
73
+
74
+ const referrers = await lookup.discover(DIGEST);
75
+ expect(referrers[0]?.kind).toBe("signature");
76
+ });
77
+
78
+ it("skips a manifest of an unrecognized artifact type rather than guessing", async () => {
79
+ const mock = createMockProcessRunner({
80
+ responses: {
81
+ "oras discover": orasOutput([
82
+ { digest: "sha256:unknown1", mediaType: "application/vnd.acme.something+json", artifactType: "application/vnd.acme.something+json" },
83
+ ]),
84
+ },
85
+ });
86
+ const lookup = createOrasReferrerLookup({ repo: REPO, runner: mock.runner });
87
+
88
+ expect(await lookup.discover(DIGEST)).toEqual([]);
89
+ });
90
+
91
+ it("returns [] when oras reports no manifests", async () => {
92
+ const mock = createMockProcessRunner({ responses: { "oras discover": orasOutput([]) } });
93
+ const lookup = createOrasReferrerLookup({ repo: REPO, runner: mock.runner });
94
+ expect(await lookup.discover(DIGEST)).toEqual([]);
95
+ });
96
+
97
+ it("returns [] rather than throwing on unparseable oras output", async () => {
98
+ const mock = createMockProcessRunner({ responses: { "oras discover": "not json" } });
99
+ const lookup = createOrasReferrerLookup({ repo: REPO, runner: mock.runner });
100
+ expect(await lookup.discover(DIGEST)).toEqual([]);
101
+ });
102
+
103
+ it("throws ToolNotAvailableError when oras is not installed", async () => {
104
+ const mock = createMockProcessRunner({ tools: { oras: false } });
105
+ const lookup = createOrasReferrerLookup({ repo: REPO, runner: mock.runner });
106
+ await expect(lookup.discover(DIGEST)).rejects.toThrow(ToolNotAvailableError);
107
+ });
108
+
109
+ it("strips a trailing slash from repo before composing the reference", async () => {
110
+ const mock = createMockProcessRunner({ responses: { "oras discover": orasOutput([]) } });
111
+ const lookup = createOrasReferrerLookup({ repo: `${REPO}/`, runner: mock.runner });
112
+ await lookup.discover(DIGEST);
113
+ const call = mock.calls.find((c) => c.command.startsWith("oras discover"))!;
114
+ expect(call.command).toContain(`${REPO}@${DIGEST}`);
115
+ });
116
+ });
@@ -0,0 +1,110 @@
1
+ /**
2
+ * Real `ReferrerLookup` backend (#610, epic #551 follow-up to #568's
3
+ * `ReferrerLookup` interface). Shells out to `oras discover --format json
4
+ * <digest>` — the OCI distribution spec's referrers API, as exposed by the
5
+ * `oras` CLI — through the same injectable `ProcessRunner`
6
+ * (../components/verbs/process-runner.ts) the deep-scan `SbomGenerator`
7
+ * backend (../components/verbs/tool-sbom-generator.ts) and `publish-image`'s
8
+ * referrer-attach step share, mirroring ../components/verbs/cloud-executor.ts's
9
+ * `CloudExecutor` pattern once more: production code shells out, tests inject
10
+ * a `MockProcessRunner` and never touch a real registry/`oras` binary.
11
+ *
12
+ * Not the process-wide default: ./build-ledger.ts's `noopReferrerLookup`
13
+ * remains that (reports no referrers, no network, no registry credentials
14
+ * required) for exactly the reason `notImplementedSbomGenerator` and
15
+ * `SbomGenerator`'s hermetic backend aren't `sbom-generator.ts`'s default
16
+ * either — a project opts into the real registry-backed lookup explicitly
17
+ * (`buildLedgerEntries(manifest, createOrasReferrerLookup())`) rather than
18
+ * every previously-network-free build-ledger query silently starting to hit
19
+ * a registry.
20
+ */
21
+
22
+ import {
23
+ defaultProcessRunner,
24
+ q,
25
+ requireTool,
26
+ type ProcessRunner,
27
+ } from "../components/verbs/process-runner";
28
+ import type { Referrer, ReferrerKind, ReferrerLookup } from "./build-ledger";
29
+
30
+ /** Shape of one entry in `oras discover --format json`'s `manifests[]` array — the fields this module actually reads, not oras's full manifest descriptor. */
31
+ interface OrasManifestDescriptor {
32
+ digest: string;
33
+ mediaType: string;
34
+ /** oras nests the referrer's own artifact type here for a referrers-API-discovered entry. */
35
+ artifactType?: string;
36
+ annotations?: Record<string, string>;
37
+ }
38
+
39
+ /** Shape of `oras discover --format json <ref>`'s top-level output. */
40
+ interface OrasDiscoverOutput {
41
+ manifests?: OrasManifestDescriptor[];
42
+ }
43
+
44
+ /**
45
+ * Classify one oras-reported referrer manifest into a `ReferrerKind`, keyed
46
+ * off its `artifactType`/media type — the same three kinds
47
+ * ../components/verbs/build-ledger.ts's `Referrer.kind` already models
48
+ * (`sbom`, `provenance`, `signature`). An unrecognized artifact type is
49
+ * skipped (returns `undefined`) rather than guessed at, since a wrong
50
+ * classification (e.g. reporting a signature as an SBOM) would be worse than
51
+ * omitting it — the caller only ever sees referrers this module is confident
52
+ * about.
53
+ */
54
+ function classify(descriptor: OrasManifestDescriptor): ReferrerKind | undefined {
55
+ const type = (descriptor.artifactType ?? descriptor.mediaType ?? "").toLowerCase();
56
+ if (type.includes("spdx") || type.includes("cyclonedx") || type.includes("sbom")) return "sbom";
57
+ if (type.includes("in-toto") || type.includes("provenance") || type.includes("slsa")) return "provenance";
58
+ if (type.includes("cosign") || type.includes("signature") || type.includes("sig.")) return "signature";
59
+ return undefined;
60
+ }
61
+
62
+ export interface OrasReferrerLookupOptions {
63
+ /** Injected process boundary. Defaults to the real, `child_process`-backed runner. */
64
+ runner?: ProcessRunner;
65
+ /** Registry/repo prefix `digest` is resolved against (e.g. `123.dkr.ecr.us-east-1.amazonaws.com/search`) — `oras discover` needs a full `repo@digest` reference, not a bare digest. Required; a `ReferrerLookup.discover(digest)` call with no repo context has nothing to query. */
66
+ repo: string;
67
+ }
68
+
69
+ /**
70
+ * Build a real `ReferrerLookup` backed by `oras discover`. `discover(digest)`
71
+ * runs `oras discover --format json <repo>@<digest>` and maps every returned
72
+ * manifest into a `Referrer`, classified by `classify` above. Returns `[]`
73
+ * (never throws) when `oras` reports no referrers for a digest — an image
74
+ * with no attached SBOM/provenance/signature is a normal outcome, not an
75
+ * error. Throws `ToolNotAvailableError`
76
+ * (../components/verbs/process-runner.ts) if `oras` itself is not installed,
77
+ * so a misconfigured environment fails loudly rather than silently reporting
78
+ * "no referrers" for every digest.
79
+ */
80
+ export function createOrasReferrerLookup(options: OrasReferrerLookupOptions): ReferrerLookup {
81
+ const runner = options.runner ?? defaultProcessRunner();
82
+
83
+ return {
84
+ async discover(digest: string): Promise<Referrer[]> {
85
+ await requireTool(runner, "oras", `discover OCI referrers for ${options.repo}@${digest}`);
86
+ const ref = `${options.repo.replace(/\/+$/, "")}@${digest}`;
87
+ const { stdout } = await runner.run(`oras discover --format json ${q(ref)}`);
88
+
89
+ let parsed: OrasDiscoverOutput;
90
+ try {
91
+ parsed = JSON.parse(stdout) as OrasDiscoverOutput;
92
+ } catch {
93
+ return []; // no referrers / unexpected output shape — degrade to "none known", never throw on a parse hiccup.
94
+ }
95
+
96
+ const referrers: Referrer[] = [];
97
+ for (const descriptor of parsed.manifests ?? []) {
98
+ const kind = classify(descriptor);
99
+ if (!kind) continue;
100
+ referrers.push({
101
+ kind,
102
+ mediaType: descriptor.artifactType ?? descriptor.mediaType,
103
+ digest: descriptor.digest,
104
+ location: `${options.repo}@${descriptor.digest}`,
105
+ });
106
+ }
107
+ return referrers;
108
+ },
109
+ };
110
+ }