@intentius/chant 0.124.51 → 0.124.54

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 (593) hide show
  1. package/dist/audit/discover.d.ts.map +1 -1
  2. package/dist/audit/discover.js +3 -1
  3. package/dist/behaviour.d.ts +1 -1
  4. package/dist/behaviour.d.ts.map +1 -1
  5. package/dist/capabilities/templates.d.ts +65 -0
  6. package/dist/capabilities/templates.d.ts.map +1 -0
  7. package/dist/capabilities/templates.js +193 -0
  8. package/dist/capabilities/templates.json +145 -0
  9. package/dist/change-set-graph.d.ts +35 -3
  10. package/dist/change-set-graph.d.ts.map +1 -1
  11. package/dist/change-set-graph.js +60 -9
  12. package/dist/change-set.d.ts +65 -7
  13. package/dist/change-set.d.ts.map +1 -1
  14. package/dist/change-set.js +23 -12
  15. package/dist/ci-identity.d.ts +7 -2
  16. package/dist/ci-identity.d.ts.map +1 -1
  17. package/dist/ci-identity.js +4 -1
  18. package/dist/cli/commands/import-index.d.ts +52 -0
  19. package/dist/cli/commands/import-index.d.ts.map +1 -0
  20. package/dist/cli/commands/import-index.js +226 -0
  21. package/dist/cli/commands/lint.d.ts.map +1 -1
  22. package/dist/cli/commands/lint.js +4 -2
  23. package/dist/cli/handlers/components.d.ts.map +1 -1
  24. package/dist/cli/handlers/components.js +11 -2
  25. package/dist/cli/handlers/fan-out.d.ts.map +1 -1
  26. package/dist/cli/handlers/fan-out.js +16 -3
  27. package/dist/cli/handlers/graph.d.ts.map +1 -1
  28. package/dist/cli/handlers/graph.js +22 -4
  29. package/dist/cli/handlers/index-cloud.d.ts +14 -0
  30. package/dist/cli/handlers/index-cloud.d.ts.map +1 -0
  31. package/dist/cli/handlers/index-cloud.js +22 -0
  32. package/dist/cli/handlers/index-diff.d.ts +18 -0
  33. package/dist/cli/handlers/index-diff.d.ts.map +1 -0
  34. package/dist/cli/handlers/index-diff.js +142 -0
  35. package/dist/cli/handlers/lifecycle-drift.d.ts +25 -0
  36. package/dist/cli/handlers/lifecycle-drift.d.ts.map +1 -0
  37. package/dist/cli/handlers/lifecycle-drift.js +64 -0
  38. package/dist/cli/handlers/lifecycle.d.ts.map +1 -1
  39. package/dist/cli/handlers/lifecycle.js +188 -28
  40. package/dist/cli/handlers/misc.d.ts +9 -0
  41. package/dist/cli/handlers/misc.d.ts.map +1 -1
  42. package/dist/cli/handlers/misc.js +77 -0
  43. package/dist/cli/handlers/pr.d.ts.map +1 -1
  44. package/dist/cli/handlers/pr.js +17 -3
  45. package/dist/cli/handlers/promote.d.ts.map +1 -1
  46. package/dist/cli/handlers/promote.js +89 -15
  47. package/dist/cli/handlers/query.d.ts.map +1 -1
  48. package/dist/cli/handlers/query.js +28 -4
  49. package/dist/cli/handlers/scenario.d.ts.map +1 -1
  50. package/dist/cli/handlers/scenario.js +8 -7
  51. package/dist/cli/handlers/search.d.ts +34 -4
  52. package/dist/cli/handlers/search.d.ts.map +1 -1
  53. package/dist/cli/handlers/search.js +216 -36
  54. package/dist/cli/main.d.ts.map +1 -1
  55. package/dist/cli/main.js +55 -1
  56. package/dist/cli/mcp/lifecycle-tools.d.ts.map +1 -1
  57. package/dist/cli/mcp/lifecycle-tools.js +3 -2
  58. package/dist/cli/mcp/resource-handlers.d.ts.map +1 -1
  59. package/dist/cli/mcp/resource-handlers.js +4 -3
  60. package/dist/cli/mcp/workspace-tools.js +1 -1
  61. package/dist/cli/parse-args.d.ts.map +1 -1
  62. package/dist/cli/parse-args.js +55 -2
  63. package/dist/cli/registry.d.ts +19 -1
  64. package/dist/cli/registry.d.ts.map +1 -1
  65. package/dist/cloud-lister/aws.d.ts +35 -0
  66. package/dist/cloud-lister/aws.d.ts.map +1 -0
  67. package/dist/cloud-lister/aws.js +203 -0
  68. package/dist/cloud-lister/azure.d.ts +34 -0
  69. package/dist/cloud-lister/azure.d.ts.map +1 -0
  70. package/dist/cloud-lister/azure.js +129 -0
  71. package/dist/cloud-lister/common.d.ts +40 -0
  72. package/dist/cloud-lister/common.d.ts.map +1 -0
  73. package/dist/cloud-lister/common.js +83 -0
  74. package/dist/cloud-lister/credentials.d.ts +50 -0
  75. package/dist/cloud-lister/credentials.d.ts.map +1 -0
  76. package/dist/cloud-lister/credentials.js +86 -0
  77. package/dist/cloud-lister/fakes.d.ts +27 -0
  78. package/dist/cloud-lister/fakes.d.ts.map +1 -0
  79. package/dist/cloud-lister/fakes.js +50 -0
  80. package/dist/cloud-lister/gcp.d.ts +42 -0
  81. package/dist/cloud-lister/gcp.d.ts.map +1 -0
  82. package/dist/cloud-lister/gcp.js +143 -0
  83. package/dist/cloud-lister/guard.d.ts +49 -0
  84. package/dist/cloud-lister/guard.d.ts.map +1 -0
  85. package/dist/cloud-lister/guard.js +103 -0
  86. package/dist/cloud-lister/ids.d.ts +23 -0
  87. package/dist/cloud-lister/ids.d.ts.map +1 -0
  88. package/dist/cloud-lister/ids.js +42 -0
  89. package/dist/cloud-lister/index.d.ts +67 -0
  90. package/dist/cloud-lister/index.d.ts.map +1 -0
  91. package/dist/cloud-lister/index.js +102 -0
  92. package/dist/cloud-lister/kubernetes.d.ts +64 -0
  93. package/dist/cloud-lister/kubernetes.d.ts.map +1 -0
  94. package/dist/cloud-lister/kubernetes.js +276 -0
  95. package/dist/cloud-lister/markers.d.ts +31 -0
  96. package/dist/cloud-lister/markers.d.ts.map +1 -0
  97. package/dist/cloud-lister/markers.js +73 -0
  98. package/dist/cloud-lister/mask.d.ts +31 -0
  99. package/dist/cloud-lister/mask.d.ts.map +1 -0
  100. package/dist/cloud-lister/mask.js +74 -0
  101. package/dist/cloud-lister/sigv4.d.ts +29 -0
  102. package/dist/cloud-lister/sigv4.d.ts.map +1 -0
  103. package/dist/cloud-lister/sigv4.js +54 -0
  104. package/dist/cloud-lister/transport.d.ts +55 -0
  105. package/dist/cloud-lister/transport.d.ts.map +1 -0
  106. package/dist/cloud-lister/transport.js +86 -0
  107. package/dist/cloud-lister/types.d.ts +121 -0
  108. package/dist/cloud-lister/types.d.ts.map +1 -0
  109. package/dist/cloud-lister/types.js +24 -0
  110. package/dist/components/capability-plugin-loader.d.ts.map +1 -1
  111. package/dist/components/capability-plugin-loader.js +6 -0
  112. package/dist/components/capability-plugin.d.ts +9 -0
  113. package/dist/components/capability-plugin.d.ts.map +1 -1
  114. package/dist/components/capability.d.ts +224 -0
  115. package/dist/components/capability.d.ts.map +1 -1
  116. package/dist/components/cli-support.d.ts +21 -0
  117. package/dist/components/cli-support.d.ts.map +1 -1
  118. package/dist/components/cli-support.js +58 -14
  119. package/dist/components/covered-round.d.ts +173 -0
  120. package/dist/components/covered-round.d.ts.map +1 -0
  121. package/dist/components/covered-round.js +200 -0
  122. package/dist/components/declared-reads.d.ts +38 -0
  123. package/dist/components/declared-reads.d.ts.map +1 -0
  124. package/dist/components/declared-reads.js +131 -0
  125. package/dist/components/deploy-units.d.ts.map +1 -1
  126. package/dist/components/deploy-units.js +4 -0
  127. package/dist/components/driver.d.ts +42 -1
  128. package/dist/components/driver.d.ts.map +1 -1
  129. package/dist/components/driver.js +74 -3
  130. package/dist/components/fan-out-run.d.ts +23 -1
  131. package/dist/components/fan-out-run.d.ts.map +1 -1
  132. package/dist/components/fan-out-run.js +82 -14
  133. package/dist/components/fan-out-support.d.ts +6 -0
  134. package/dist/components/fan-out-support.d.ts.map +1 -1
  135. package/dist/components/fan-out-support.js +4 -1
  136. package/dist/components/fan-out.d.ts +28 -0
  137. package/dist/components/fan-out.d.ts.map +1 -1
  138. package/dist/components/groups.d.ts +229 -0
  139. package/dist/components/groups.d.ts.map +1 -0
  140. package/dist/components/groups.js +760 -0
  141. package/dist/components/index.d.ts +7 -2
  142. package/dist/components/index.d.ts.map +1 -1
  143. package/dist/components/index.js +5 -0
  144. package/dist/components/plan-shape-conformance.d.ts +48 -0
  145. package/dist/components/plan-shape-conformance.d.ts.map +1 -0
  146. package/dist/components/plan-shape-conformance.js +122 -0
  147. package/dist/components/pr-run.d.ts +62 -7
  148. package/dist/components/pr-run.d.ts.map +1 -1
  149. package/dist/components/pr-run.js +354 -53
  150. package/dist/components/promote-rounds.d.ts +9 -0
  151. package/dist/components/promote-rounds.d.ts.map +1 -1
  152. package/dist/components/promote-rounds.js +23 -2
  153. package/dist/components/promote.d.ts +17 -0
  154. package/dist/components/promote.d.ts.map +1 -1
  155. package/dist/components/promote.js +36 -6
  156. package/dist/components/release-gate.d.ts +23 -1
  157. package/dist/components/release-gate.d.ts.map +1 -1
  158. package/dist/components/release-gate.js +97 -33
  159. package/dist/components/run-groups.d.ts +58 -0
  160. package/dist/components/run-groups.d.ts.map +1 -0
  161. package/dist/components/run-groups.js +78 -0
  162. package/dist/components/starter-plugin.d.ts +1 -0
  163. package/dist/components/starter-plugin.d.ts.map +1 -1
  164. package/dist/components/starter-plugin.js +6 -1
  165. package/dist/components/verbs/index.d.ts +1 -0
  166. package/dist/components/verbs/index.d.ts.map +1 -1
  167. package/dist/components/verbs/index.js +1 -0
  168. package/dist/components/verbs/member-outputs.d.ts +187 -0
  169. package/dist/components/verbs/member-outputs.d.ts.map +1 -0
  170. package/dist/components/verbs/member-outputs.js +218 -0
  171. package/dist/components/wave-plan.d.ts +2 -0
  172. package/dist/components/wave-plan.d.ts.map +1 -1
  173. package/dist/components/wave-plan.js +4 -1
  174. package/dist/config.d.ts +60 -0
  175. package/dist/config.d.ts.map +1 -1
  176. package/dist/config.js +27 -0
  177. package/dist/deep-observation.d.ts +1 -13
  178. package/dist/deep-observation.d.ts.map +1 -1
  179. package/dist/deep-observation.js +5 -22
  180. package/dist/drift-record.d.ts +542 -0
  181. package/dist/drift-record.d.ts.map +1 -0
  182. package/dist/drift-record.js +886 -0
  183. package/dist/engine-facts.d.ts +144 -0
  184. package/dist/engine-facts.d.ts.map +1 -0
  185. package/dist/engine-facts.js +14 -0
  186. package/dist/graph-ir.d.ts +153 -2
  187. package/dist/graph-ir.d.ts.map +1 -1
  188. package/dist/graph-ir.js +224 -6
  189. package/dist/graph-refs.d.ts +145 -0
  190. package/dist/graph-refs.d.ts.map +1 -1
  191. package/dist/graph-refs.js +305 -0
  192. package/dist/graph-schema/fixtures/adopt.json +15 -0
  193. package/dist/graph-schema/fixtures/choudoufu.engine-graph.json +169 -0
  194. package/dist/graph-schema/fixtures/index.json +68 -0
  195. package/dist/graph-schema/fixtures/plunge.engine-graph.json +158 -0
  196. package/dist/graph-schema/fixtures.d.ts +23 -0
  197. package/dist/graph-schema/fixtures.d.ts.map +1 -0
  198. package/dist/graph-schema/fixtures.js +246 -0
  199. package/dist/graph-schema/generate.d.ts +15 -0
  200. package/dist/graph-schema/generate.d.ts.map +1 -0
  201. package/dist/graph-schema/generate.js +267 -0
  202. package/dist/graph-schema/graph.schema.json +1387 -0
  203. package/dist/health.schema.json +2 -1
  204. package/dist/identity.d.ts.map +1 -1
  205. package/dist/identity.js +2 -0
  206. package/dist/index.d.ts +1 -0
  207. package/dist/index.d.ts.map +1 -1
  208. package/dist/index.js +2 -0
  209. package/dist/instance-nodes.d.ts +159 -0
  210. package/dist/instance-nodes.d.ts.map +1 -0
  211. package/dist/instance-nodes.js +231 -0
  212. package/dist/lexicon.d.ts +72 -0
  213. package/dist/lexicon.d.ts.map +1 -1
  214. package/dist/lifecycle/adoption-file.d.ts +56 -0
  215. package/dist/lifecycle/adoption-file.d.ts.map +1 -0
  216. package/dist/lifecycle/adoption-file.js +123 -0
  217. package/dist/lifecycle/cloud-index.d.ts +186 -0
  218. package/dist/lifecycle/cloud-index.d.ts.map +1 -0
  219. package/dist/lifecycle/cloud-index.js +618 -0
  220. package/dist/lifecycle/derive-edges.d.ts +59 -0
  221. package/dist/lifecycle/derive-edges.d.ts.map +1 -0
  222. package/dist/lifecycle/derive-edges.js +126 -0
  223. package/dist/lifecycle/git.d.ts +10 -9
  224. package/dist/lifecycle/git.d.ts.map +1 -1
  225. package/dist/lifecycle/git.js +7 -1
  226. package/dist/lifecycle/identity-match.d.ts +133 -0
  227. package/dist/lifecycle/identity-match.d.ts.map +1 -0
  228. package/dist/lifecycle/identity-match.js +147 -0
  229. package/dist/lifecycle/index-diff.d.ts +224 -0
  230. package/dist/lifecycle/index-diff.d.ts.map +1 -0
  231. package/dist/lifecycle/index-diff.js +327 -0
  232. package/dist/lifecycle/index-document.d.ts +130 -0
  233. package/dist/lifecycle/index-document.d.ts.map +1 -0
  234. package/dist/lifecycle/index-document.js +189 -0
  235. package/dist/lifecycle/index-drift.d.ts +121 -0
  236. package/dist/lifecycle/index-drift.d.ts.map +1 -0
  237. package/dist/lifecycle/index-drift.js +178 -0
  238. package/dist/lifecycle/index-import.d.ts +168 -0
  239. package/dist/lifecycle/index-import.d.ts.map +1 -0
  240. package/dist/lifecycle/index-import.js +515 -0
  241. package/dist/lifecycle/index-join.d.ts +129 -0
  242. package/dist/lifecycle/index-join.d.ts.map +1 -0
  243. package/dist/lifecycle/index-join.js +248 -0
  244. package/dist/lifecycle/index.d.ts +4 -0
  245. package/dist/lifecycle/index.d.ts.map +1 -1
  246. package/dist/lifecycle/index.js +4 -0
  247. package/dist/lifecycle/observation-baseline.d.ts +61 -21
  248. package/dist/lifecycle/observation-baseline.d.ts.map +1 -1
  249. package/dist/lifecycle/observation-baseline.js +144 -19
  250. package/dist/lifecycle/observe.d.ts.map +1 -1
  251. package/dist/lifecycle/observe.js +6 -1
  252. package/dist/lifecycle/query.d.ts +45 -4
  253. package/dist/lifecycle/query.d.ts.map +1 -1
  254. package/dist/lifecycle/query.js +89 -7
  255. package/dist/lifecycle/replay.d.ts +10 -3
  256. package/dist/lifecycle/replay.d.ts.map +1 -1
  257. package/dist/lifecycle/replay.js +10 -6
  258. package/dist/lifecycle/scenario.d.ts +4 -3
  259. package/dist/lifecycle/scenario.d.ts.map +1 -1
  260. package/dist/lifecycle/snapshot-store-fake.d.ts +9 -0
  261. package/dist/lifecycle/snapshot-store-fake.d.ts.map +1 -0
  262. package/dist/lifecycle/snapshot-store-fake.js +21 -0
  263. package/dist/lifecycle/snapshot-store-s3.d.ts +102 -0
  264. package/dist/lifecycle/snapshot-store-s3.d.ts.map +1 -0
  265. package/dist/lifecycle/snapshot-store-s3.js +254 -0
  266. package/dist/lifecycle/snapshot-store.d.ts +170 -0
  267. package/dist/lifecycle/snapshot-store.d.ts.map +1 -0
  268. package/dist/lifecycle/snapshot-store.js +832 -0
  269. package/dist/lifecycle/snapshot.d.ts +11 -0
  270. package/dist/lifecycle/snapshot.d.ts.map +1 -1
  271. package/dist/lifecycle/snapshot.js +18 -5
  272. package/dist/lifecycle/type-listing.d.ts +148 -0
  273. package/dist/lifecycle/type-listing.d.ts.map +1 -0
  274. package/dist/lifecycle/type-listing.js +166 -0
  275. package/dist/lint/component-checks.d.ts +15 -1
  276. package/dist/lint/component-checks.d.ts.map +1 -1
  277. package/dist/lint/component-checks.js +1 -0
  278. package/dist/lint/presets/relaxed.json +2 -1
  279. package/dist/lint/presets/strict.json +2 -1
  280. package/dist/lint/rules/__fixtures__/comp/comp008/fail/dns.component.d.ts +4 -0
  281. package/dist/lint/rules/__fixtures__/comp/comp008/fail/dns.component.d.ts.map +1 -0
  282. package/dist/lint/rules/__fixtures__/comp/comp008/fail/dns.component.js +13 -0
  283. package/dist/lint/rules/__fixtures__/comp/comp008/pass/dns.component.d.ts +4 -0
  284. package/dist/lint/rules/__fixtures__/comp/comp008/pass/dns.component.d.ts.map +1 -0
  285. package/dist/lint/rules/__fixtures__/comp/comp008/pass/dns.component.js +8 -0
  286. package/dist/lint/rules/comp/comp008-applies-signal-only-root.d.ts +33 -0
  287. package/dist/lint/rules/comp/comp008-applies-signal-only-root.d.ts.map +1 -0
  288. package/dist/lint/rules/comp/comp008-applies-signal-only-root.js +123 -0
  289. package/dist/lint/rules/comp/index.d.ts +2 -1
  290. package/dist/lint/rules/comp/index.d.ts.map +1 -1
  291. package/dist/lint/rules/comp/index.js +4 -1
  292. package/dist/observation.d.ts +19 -2
  293. package/dist/observation.d.ts.map +1 -1
  294. package/dist/observation.js +18 -1
  295. package/dist/op/activities/activity-contracts.d.ts +1 -1
  296. package/dist/op/activities/activity-contracts.js +1 -1
  297. package/dist/op/activities/change-set.d.ts +6 -4
  298. package/dist/op/activities/change-set.d.ts.map +1 -1
  299. package/dist/op/activities/change-set.js +4 -2
  300. package/dist/op/activity-failure.d.ts +27 -0
  301. package/dist/op/activity-failure.d.ts.map +1 -1
  302. package/dist/op/activity-failure.js +31 -0
  303. package/dist/op/index.d.ts +2 -1
  304. package/dist/op/index.d.ts.map +1 -1
  305. package/dist/op/index.js +1 -1
  306. package/dist/op/local-executor.d.ts +5 -1
  307. package/dist/op/local-executor.d.ts.map +1 -1
  308. package/dist/op/local-executor.js +22 -8
  309. package/dist/pr-loop.d.ts +92 -3
  310. package/dist/pr-loop.d.ts.map +1 -1
  311. package/dist/pr-loop.js +62 -3
  312. package/dist/pulumi-urn.d.ts +101 -0
  313. package/dist/pulumi-urn.d.ts.map +1 -0
  314. package/dist/pulumi-urn.js +131 -0
  315. package/dist/release-floors.d.ts +23 -0
  316. package/dist/release-floors.d.ts.map +1 -0
  317. package/dist/release-floors.js +22 -0
  318. package/dist/sensitive-key.d.ts +23 -0
  319. package/dist/sensitive-key.d.ts.map +1 -0
  320. package/dist/sensitive-key.js +31 -0
  321. package/dist/wave-runtime.mjs +234 -234
  322. package/dist/workspace/adoption-report.d.ts +53 -0
  323. package/dist/workspace/adoption-report.d.ts.map +1 -0
  324. package/dist/workspace/adoption-report.js +76 -0
  325. package/dist/workspace/capabilities-contract.d.ts +7 -0
  326. package/dist/workspace/capabilities-contract.d.ts.map +1 -1
  327. package/dist/workspace/capabilities-contract.js +2 -1
  328. package/dist/workspace/capabilities.schema.json +98 -0
  329. package/dist/workspace/change-set.schema.json +42 -3
  330. package/dist/workspace/ci-cloud-identity.d.ts +13 -1
  331. package/dist/workspace/ci-cloud-identity.d.ts.map +1 -1
  332. package/dist/workspace/ci-cloud-identity.js +30 -1
  333. package/dist/workspace/compose-graph.d.ts +10 -1
  334. package/dist/workspace/compose-graph.d.ts.map +1 -1
  335. package/dist/workspace/compose-graph.js +6 -1
  336. package/dist/workspace/conformance/index.d.ts +1 -1
  337. package/dist/workspace/conformance/index.d.ts.map +1 -1
  338. package/dist/workspace/conformance/index.js +6 -1
  339. package/dist/workspace/drift-cli.d.ts +81 -0
  340. package/dist/workspace/drift-cli.d.ts.map +1 -0
  341. package/dist/workspace/drift-cli.js +317 -0
  342. package/dist/workspace/drift.schema.json +672 -0
  343. package/dist/workspace/graph-cli.d.ts +2 -0
  344. package/dist/workspace/graph-cli.d.ts.map +1 -1
  345. package/dist/workspace/graph-cli.js +3 -0
  346. package/dist/workspace/graph.schema.json +72 -0
  347. package/dist/workspace/init.d.ts +5 -1
  348. package/dist/workspace/init.d.ts.map +1 -1
  349. package/dist/workspace/init.js +55 -9
  350. package/dist/workspace/ledger.schema.json +1 -1
  351. package/dist/workspace/nested-graph.d.ts.map +1 -1
  352. package/dist/workspace/nested-graph.js +2 -0
  353. package/dist/workspace/pr-report.schema.json +76 -3
  354. package/dist/workspace/promote-waves.d.ts +112 -0
  355. package/dist/workspace/promote-waves.d.ts.map +1 -0
  356. package/dist/workspace/promote-waves.js +290 -0
  357. package/dist/workspace/promote.d.ts +84 -2
  358. package/dist/workspace/promote.d.ts.map +1 -1
  359. package/dist/workspace/promote.js +347 -61
  360. package/dist/workspace/reason-codes.d.ts +12 -0
  361. package/dist/workspace/reason-codes.d.ts.map +1 -1
  362. package/dist/workspace/reason-codes.js +14 -0
  363. package/dist/workspace/search-contract.d.ts +3 -3
  364. package/dist/workspace/search-contract.d.ts.map +1 -1
  365. package/dist/workspace/search-contract.js +2 -0
  366. package/dist/workspace/search.schema.json +103 -5
  367. package/package.json +14 -1
  368. package/src/__fixtures__/drift-record/README.md +58 -0
  369. package/src/__fixtures__/drift-record/choudoufu-cone.ndjson +4 -0
  370. package/src/__fixtures__/drift-record/choudoufu-resource.ndjson +3 -0
  371. package/src/__fixtures__/drift-record/component-status-ledger.json +65 -0
  372. package/src/__fixtures__/drift-record/component-status.json +91 -0
  373. package/src/__fixtures__/drift-record/deep-diff.json +259 -0
  374. package/src/__fixtures__/drift-record/lifecycle-diff-live.json +825 -0
  375. package/src/__fixtures__/drift-record/live-diff.json +125 -0
  376. package/src/audit/discover.e2e.test.ts +14 -0
  377. package/src/audit/discover.ts +3 -1
  378. package/src/behaviour.ts +1 -1
  379. package/src/capabilities/templates.json +145 -0
  380. package/src/capabilities/templates.test.ts +212 -0
  381. package/src/capabilities/templates.ts +204 -0
  382. package/src/change-set-bundle.test.ts +83 -0
  383. package/src/change-set-graph.ts +80 -9
  384. package/src/change-set.test.ts +24 -0
  385. package/src/change-set.ts +77 -14
  386. package/src/ci-identity-targets.test.ts +23 -0
  387. package/src/ci-identity.ts +10 -4
  388. package/src/cli/commands/import-index.test.ts +117 -0
  389. package/src/cli/commands/import-index.ts +265 -0
  390. package/src/cli/commands/lint.ts +4 -2
  391. package/src/cli/handlers/components.test.ts +9 -0
  392. package/src/cli/handlers/components.ts +12 -2
  393. package/src/cli/handlers/fan-out.ts +17 -3
  394. package/src/cli/handlers/graph.test.ts +42 -0
  395. package/src/cli/handlers/graph.ts +26 -4
  396. package/src/cli/handlers/index-cloud.ts +32 -0
  397. package/src/cli/handlers/index-diff.test.ts +68 -0
  398. package/src/cli/handlers/index-diff.ts +146 -0
  399. package/src/cli/handlers/ledger-root-member.test.ts +161 -0
  400. package/src/cli/handlers/lifecycle-drift.ts +68 -0
  401. package/src/cli/handlers/lifecycle.test.ts +33 -11
  402. package/src/cli/handlers/lifecycle.ts +205 -30
  403. package/src/cli/handlers/misc.ts +73 -0
  404. package/src/cli/handlers/pr-waves-blocked.test.ts +37 -2
  405. package/src/cli/handlers/pr.ts +16 -3
  406. package/src/cli/handlers/promote.test.ts +64 -0
  407. package/src/cli/handlers/promote.ts +81 -6
  408. package/src/cli/handlers/query.ts +27 -4
  409. package/src/cli/handlers/run-declared-reads-cli.test.ts +187 -0
  410. package/src/cli/handlers/run-groups-cli.test.ts +229 -0
  411. package/src/cli/handlers/scenario.test.ts +13 -0
  412. package/src/cli/handlers/scenario.ts +8 -7
  413. package/src/cli/handlers/search-index.test.ts +255 -0
  414. package/src/cli/handlers/search-json.test.ts +1 -1
  415. package/src/cli/handlers/search-ownership.test.ts +194 -0
  416. package/src/cli/handlers/search.ts +271 -38
  417. package/src/cli/main.test.ts +7 -0
  418. package/src/cli/main.ts +56 -1
  419. package/src/cli/mcp/lifecycle-tools.ts +3 -2
  420. package/src/cli/mcp/resource-handlers.ts +4 -3
  421. package/src/cli/mcp/workspace-tools.ts +1 -1
  422. package/src/cli/parse-args.ts +40 -2
  423. package/src/cli/registry.ts +19 -1
  424. package/src/cloud-lister/aws.test.ts +202 -0
  425. package/src/cloud-lister/aws.ts +226 -0
  426. package/src/cloud-lister/azure.test.ts +132 -0
  427. package/src/cloud-lister/azure.ts +142 -0
  428. package/src/cloud-lister/common.ts +115 -0
  429. package/src/cloud-lister/credentials.ts +110 -0
  430. package/src/cloud-lister/fakes.ts +66 -0
  431. package/src/cloud-lister/gcp.test.ts +121 -0
  432. package/src/cloud-lister/gcp.ts +159 -0
  433. package/src/cloud-lister/guard.test.ts +198 -0
  434. package/src/cloud-lister/guard.ts +111 -0
  435. package/src/cloud-lister/ids.ts +54 -0
  436. package/src/cloud-lister/index.ts +134 -0
  437. package/src/cloud-lister/kubernetes.test.ts +160 -0
  438. package/src/cloud-lister/kubernetes.ts +284 -0
  439. package/src/cloud-lister/markers.ts +74 -0
  440. package/src/cloud-lister/mask.ts +77 -0
  441. package/src/cloud-lister/shape.test.ts +81 -0
  442. package/src/cloud-lister/sigv4.ts +70 -0
  443. package/src/cloud-lister/transport.ts +118 -0
  444. package/src/cloud-lister/types.ts +135 -0
  445. package/src/components/capability-plugin-loader.ts +4 -0
  446. package/src/components/capability-plugin.ts +10 -0
  447. package/src/components/capability.ts +238 -0
  448. package/src/components/cli-support.ts +70 -14
  449. package/src/components/covered-round.test.ts +166 -0
  450. package/src/components/covered-round.ts +293 -0
  451. package/src/components/declared-reads.test.ts +103 -0
  452. package/src/components/declared-reads.ts +134 -0
  453. package/src/components/deploy-units.ts +4 -0
  454. package/src/components/driver.ts +101 -4
  455. package/src/components/fan-out-run.ts +95 -15
  456. package/src/components/fan-out-support.ts +10 -1
  457. package/src/components/fan-out.ts +30 -0
  458. package/src/components/groups.test.ts +320 -0
  459. package/src/components/groups.ts +880 -0
  460. package/src/components/index.ts +59 -0
  461. package/src/components/plan-shape-conformance.ts +130 -0
  462. package/src/components/pr-env-run-bundle.test.ts +2 -0
  463. package/src/components/pr-run.test.ts +728 -4
  464. package/src/components/pr-run.ts +395 -55
  465. package/src/components/promote-rounds.ts +25 -2
  466. package/src/components/promote.ts +55 -5
  467. package/src/components/registry.test.ts +1 -1
  468. package/src/components/release-gate.ts +115 -30
  469. package/src/components/release-groups.test.ts +362 -0
  470. package/src/components/run-groups.ts +113 -0
  471. package/src/components/starter-plugin.ts +6 -0
  472. package/src/components/verbs/index.ts +1 -0
  473. package/src/components/verbs/member-outputs.test.ts +205 -0
  474. package/src/components/verbs/member-outputs.ts +308 -0
  475. package/src/components/wave-plan.ts +5 -2
  476. package/src/config.ts +44 -0
  477. package/src/deep-observation.ts +5 -23
  478. package/src/drift-record.test.ts +596 -0
  479. package/src/drift-record.ts +1206 -0
  480. package/src/engine-facts.guard.test.ts +111 -0
  481. package/src/engine-facts.test.ts +178 -0
  482. package/src/engine-facts.ts +154 -0
  483. package/src/gated-waves-bundle.test.ts +2 -0
  484. package/src/graph-ir-live.test.ts +92 -0
  485. package/src/graph-ir.ts +317 -7
  486. package/src/graph-refs.ts +416 -0
  487. package/src/graph-schema/fixtures/adopt.json +15 -0
  488. package/src/graph-schema/fixtures/choudoufu.engine-graph.json +169 -0
  489. package/src/graph-schema/fixtures/index.json +68 -0
  490. package/src/graph-schema/fixtures/plunge.engine-graph.json +158 -0
  491. package/src/graph-schema/fixtures.ts +267 -0
  492. package/src/graph-schema/generate.ts +263 -0
  493. package/src/graph-schema/graph-schema.test.ts +153 -0
  494. package/src/graph-schema/graph.schema.json +1387 -0
  495. package/src/health.schema.json +2 -1
  496. package/src/identity.ts +2 -0
  497. package/src/index.ts +2 -0
  498. package/src/instance-nodes.ts +288 -0
  499. package/src/lexicon.ts +76 -0
  500. package/src/lifecycle/__fixtures__/index-diff/20261026T120000Z-aaaaaaaaaaaa.json +149 -0
  501. package/src/lifecycle/__fixtures__/index-diff/20261026T130000Z-bbbbbbbbbbbb.json +127 -0
  502. package/src/lifecycle/adoption-file.test.ts +100 -0
  503. package/src/lifecycle/adoption-file.ts +154 -0
  504. package/src/lifecycle/cloud-index.test.ts +517 -0
  505. package/src/lifecycle/cloud-index.ts +736 -0
  506. package/src/lifecycle/derive-edges.test.ts +193 -0
  507. package/src/lifecycle/derive-edges.ts +182 -0
  508. package/src/lifecycle/git.ts +10 -4
  509. package/src/lifecycle/identity-match.test.ts +308 -0
  510. package/src/lifecycle/identity-match.ts +257 -0
  511. package/src/lifecycle/index-diff.test.ts +198 -0
  512. package/src/lifecycle/index-diff.ts +476 -0
  513. package/src/lifecycle/index-document.test.ts +64 -0
  514. package/src/lifecycle/index-document.ts +299 -0
  515. package/src/lifecycle/index-drift.test.ts +191 -0
  516. package/src/lifecycle/index-drift.ts +304 -0
  517. package/src/lifecycle/index-import.test.ts +255 -0
  518. package/src/lifecycle/index-import.ts +634 -0
  519. package/src/lifecycle/index-join.test.ts +250 -0
  520. package/src/lifecycle/index-join.ts +304 -0
  521. package/src/lifecycle/index-store-load-bearing.test.ts +187 -0
  522. package/src/lifecycle/index.ts +4 -0
  523. package/src/lifecycle/member-ledger.test.ts +25 -7
  524. package/src/lifecycle/observation-baseline.test.ts +72 -1
  525. package/src/lifecycle/observation-baseline.ts +190 -27
  526. package/src/lifecycle/observe.ts +6 -1
  527. package/src/lifecycle/query.test.ts +69 -1
  528. package/src/lifecycle/query.ts +103 -10
  529. package/src/lifecycle/replay.test.ts +9 -5
  530. package/src/lifecycle/replay.ts +9 -5
  531. package/src/lifecycle/scenario.ts +4 -3
  532. package/src/lifecycle/snapshot-store-fake.ts +29 -0
  533. package/src/lifecycle/snapshot-store-s3.ts +316 -0
  534. package/src/lifecycle/snapshot-store.test.ts +385 -0
  535. package/src/lifecycle/snapshot-store.ts +981 -0
  536. package/src/lifecycle/snapshot.test.ts +13 -0
  537. package/src/lifecycle/snapshot.ts +23 -6
  538. package/src/lifecycle/type-listing.test.ts +166 -0
  539. package/src/lifecycle/type-listing.ts +309 -0
  540. package/src/lint/component-checks.ts +17 -1
  541. package/src/lint/config.test.ts +3 -0
  542. package/src/lint/presets/relaxed.json +2 -1
  543. package/src/lint/presets/strict.json +2 -1
  544. package/src/lint/rules/__fixtures__/comp/comp008/fail/dns.component.ts +15 -0
  545. package/src/lint/rules/__fixtures__/comp/comp008/pass/dns.component.ts +10 -0
  546. package/src/lint/rules/comp/comp.test.ts +2 -1
  547. package/src/lint/rules/comp/comp008-applies-signal-only-root.ts +121 -0
  548. package/src/lint/rules/comp/comp008.test.ts +77 -0
  549. package/src/lint/rules/comp/index.ts +4 -1
  550. package/src/observation-edges.test.ts +33 -0
  551. package/src/observation.ts +36 -1
  552. package/src/op/activities/activity-contracts.ts +1 -1
  553. package/src/op/activities/change-set.ts +8 -5
  554. package/src/op/activity-failure.ts +36 -0
  555. package/src/op/index.ts +2 -1
  556. package/src/op/local-executor.test.ts +80 -0
  557. package/src/op/local-executor.ts +27 -9
  558. package/src/pr-loop-bundle.test.ts +5 -2
  559. package/src/pr-loop.test.ts +57 -1
  560. package/src/pr-loop.ts +154 -6
  561. package/src/pulumi-urn.ts +151 -0
  562. package/src/release-floors.ts +22 -0
  563. package/src/sensitive-key.ts +34 -0
  564. package/src/workspace/adoption-report.test.ts +61 -0
  565. package/src/workspace/adoption-report.ts +103 -0
  566. package/src/workspace/capabilities-contract.test.ts +4 -0
  567. package/src/workspace/capabilities-contract.ts +9 -1
  568. package/src/workspace/capabilities.schema.json +98 -0
  569. package/src/workspace/change-set.schema.json +42 -3
  570. package/src/workspace/ci-cloud-identity.ts +31 -1
  571. package/src/workspace/compose-graph.test.ts +12 -0
  572. package/src/workspace/compose-graph.ts +12 -1
  573. package/src/workspace/conformance/conformance.e2e.test.ts +2 -1
  574. package/src/workspace/conformance/index.ts +6 -1
  575. package/src/workspace/drift-cli.test.ts +65 -0
  576. package/src/workspace/drift-cli.ts +365 -0
  577. package/src/workspace/drift.schema.json +672 -0
  578. package/src/workspace/graph-cli.ts +5 -0
  579. package/src/workspace/graph.schema.json +72 -0
  580. package/src/workspace/init.test.ts +38 -1
  581. package/src/workspace/init.ts +50 -8
  582. package/src/workspace/intent-gaps.test.ts +18 -2
  583. package/src/workspace/ledger.schema.json +1 -1
  584. package/src/workspace/nested-graph.ts +1 -0
  585. package/src/workspace/pr-report.schema.json +76 -3
  586. package/src/workspace/promote-waves.test.ts +296 -0
  587. package/src/workspace/promote-waves.ts +340 -0
  588. package/src/workspace/promote.test.ts +158 -4
  589. package/src/workspace/promote.ts +398 -57
  590. package/src/workspace/reason-codes.test.ts +4 -1
  591. package/src/workspace/reason-codes.ts +14 -0
  592. package/src/workspace/search-contract.ts +2 -0
  593. package/src/workspace/search.schema.json +103 -5
@@ -0,0 +1,832 @@
1
+ /**
2
+ * The index store (#4519): one seam for every snapshot and every other index,
3
+ * with the store chosen per project.
4
+ *
5
+ * A snapshot is one kind of index, a chant project's observed resources. The
6
+ * same store holds what behold indexes from a direct cloud read and the
7
+ * graphs choudoufu and plunge print (INTENTIUS/behold#1025). Every index is
8
+ * derived, stamped, never read for a mutation decision, and safe to lose:
9
+ * losing the store costs history and the cost of observing again, never
10
+ * correctness. The rule has a test, ./index-store-load-bearing.test.ts.
11
+ *
12
+ * ## The seam
13
+ *
14
+ * {@link SnapshotStore}: write, read latest, list, read at, for snapshots;
15
+ * the same four for index versions. Every snapshot read and write in chant
16
+ * goes through it. The git plumbing in ./git.ts (`readSnapshot` and the rest)
17
+ * is now the read side of the orphan-branch store and nothing else.
18
+ *
19
+ * ## One logical key space, two families
20
+ *
21
+ * - `snapshots`: `<env>/<key>.json` and `_members/<member>/<env>/<key>.json`,
22
+ * exactly the orphan branch's layout (`snapshotStorageKey`). A snapshot
23
+ * keeps one key, and its history is the store's own: commits on a git ref,
24
+ * object versions in a versioned bucket.
25
+ * - `index`: `<source path>/<scope path>/<observedAt>-<digest12>.json`
26
+ * (./index-document.ts), one immutable object per read, written once and
27
+ * never overwritten, with `scope.json` beside the versions.
28
+ *
29
+ * ## Stores
30
+ *
31
+ * | kind | snapshots | index versions |
32
+ * |---|---|---|
33
+ * | `git-ref` (default) | `refs/chant/snapshots`, root of the ref | `_index/...` on the same ref |
34
+ * | `local` | `<dir>/snapshots/...` | `<dir>/index/...` |
35
+ * | `s3` | `<prefix>/snapshots/...` | `<prefix>/index/...` |
36
+ * | `none` | dropped | dropped |
37
+ * | `orphan-branch` | read-only: `chant/lifecycle` | none |
38
+ *
39
+ * The default `git-ref` store also reads the `chant/lifecycle` branch when
40
+ * the ref has no answer, so snapshots written before #4519 stay readable. It
41
+ * never writes there. A project created after #4519 has no `chant/lifecycle`
42
+ * branch unless it writes an Op record: gates, run ledgers, deploy records,
43
+ * releases and the other Ops records are not an index and stay on the branch.
44
+ *
45
+ * ## Choosing a store
46
+ *
47
+ * `snapshots` in chant.config.ts, and these environment variables over it:
48
+ * `CHANT_SNAPSHOT_STORE` (`git-ref`, `local`, `s3`, `none`, `orphan-branch`),
49
+ * `CHANT_SNAPSHOT_STORE_DIR` (local), and `CHANT_SNAPSHOT_S3_BUCKET`,
50
+ * `_PREFIX`, `_REGION`, `_ENDPOINT`, `_ACCESS_KEY_ID`, `_SECRET_ACCESS_KEY`,
51
+ * `_SESSION_TOKEN` (s3). See ./snapshot-store-s3.ts for the credential rule.
52
+ *
53
+ * Secret masking happens before a store sees the data (./snapshot.ts and
54
+ * ../deep-observation.ts's one list), so no store holds a secret.
55
+ */
56
+ import { existsSync, mkdirSync, readdirSync, readFileSync, renameSync, statSync, writeFileSync, mkdtempSync, unlinkSync, rmdirSync } from "node:fs";
57
+ import { tmpdir } from "node:os";
58
+ import { dirname, join, resolve } from "node:path";
59
+ import { getRuntime } from "../runtime-adapter.js";
60
+ import { NEXT_RELEASE } from "../release-floors.js";
61
+ import { fetchLifecycle, ledgerDir, listFilesInDir, listLedgerTrees, listSnapshots as listOrphanSnapshots, readBlobFromPath, readRefSha, readSnapshotAt as readOrphanSnapshotAt, RefCASConflictError, updateRefCAS, } from "./git.js";
62
+ import { ledgerRecordingOff } from "./no-record.js";
63
+ import { INDEX_SCOPE_FILE, indexVersionName, isIndexVersionName, validateIndexDocument, } from "./index-document.js";
64
+ import { S3Medium } from "./snapshot-store-s3.js";
65
+ /** The first chant that writes snapshots through the store, to `refs/chant/snapshots` by default. */
66
+ export const SNAPSHOT_STORE_FLOOR = NEXT_RELEASE;
67
+ /** The ref the default store writes. */
68
+ export const SNAPSHOT_REF = "refs/chant/snapshots";
69
+ /** Where index versions sit on a git ref. The underscore keeps it clear of every environment name, as `_gates` and `_members` are. */
70
+ export const GIT_INDEX_DIR = "_index";
71
+ /** Every snapshot an environment holds, by key. The old `readEnvironmentSnapshots`, through a store. */
72
+ export async function readEnvironmentSnapshotsFrom(store, environment, at) {
73
+ const out = new Map();
74
+ for (const key of await store.keys(environment, at)) {
75
+ const content = await store.readLatest(environment, key, at);
76
+ if (content !== null)
77
+ out.set(key, content);
78
+ }
79
+ return out;
80
+ }
81
+ const C_LOCALE = { LC_ALL: "C", LANGUAGE: "" };
82
+ const ISO_TIME_RE = /^\d{4}-\d{2}-\d{2}T/;
83
+ async function git(args, opts) {
84
+ return getRuntime().spawn(["git", ...args], { ...(opts.cwd ? { cwd: opts.cwd } : {}), ...(opts.stdin !== undefined ? { stdin: opts.stdin } : {}), env: { ...C_LOCALE, ...(opts.env ?? {}) } });
85
+ }
86
+ /** The remote a ref is pushed to: `origin` when it exists, else the first. */
87
+ async function remoteOf(cwd) {
88
+ const result = await git(["remote"], { cwd });
89
+ if (result.exitCode !== 0)
90
+ return null;
91
+ const names = result.stdout.trim().split("\n").map((n) => n.trim()).filter(Boolean);
92
+ if (names.length === 0)
93
+ return null;
94
+ return names.includes("origin") ? "origin" : names[0];
95
+ }
96
+ /** `user.name`/`user.email` for a checkout that has none, as the ledger's own writes do (#2301). */
97
+ async function identityEnv(cwd) {
98
+ const probe = await git(["var", "GIT_COMMITTER_IDENT"], { cwd });
99
+ if (probe.exitCode === 0)
100
+ return {};
101
+ const name = process.env.CHANT_LIFECYCLE_COMMITTER_NAME?.trim() || "chant";
102
+ const email = process.env.CHANT_LIFECYCLE_COMMITTER_EMAIL?.trim() || "chant@localhost";
103
+ return { GIT_AUTHOR_NAME: name, GIT_AUTHOR_EMAIL: email, GIT_COMMITTER_NAME: name, GIT_COMMITTER_EMAIL: email };
104
+ }
105
+ /**
106
+ * Commit files onto `ref`, keeping every other path the ref holds, with a
107
+ * compare-and-swap on the ref. Builds the tree in a scratch index, so the
108
+ * working tree, the real index and HEAD are never touched. A conflict with
109
+ * another writer is retried against the new tip: every caller writes a
110
+ * self-contained replacement or a new path, so a retry is always correct.
111
+ *
112
+ * Returns the new commit, or null when every `ifAbsent` file existed.
113
+ */
114
+ export async function commitFilesToRef(ref, files, message, opts) {
115
+ const cwd = opts?.cwd;
116
+ const top = await git(["rev-parse", "--show-toplevel"], { cwd });
117
+ const root = top.exitCode === 0 && top.stdout.trim() !== "" ? top.stdout.trim() : cwd;
118
+ const blobs = [];
119
+ for (const f of files) {
120
+ if (f.path.split("/").some((s) => s === "" || s === "." || s === ".."))
121
+ throw new Error(`cannot write ${f.path} on ${ref}: not a clean path`);
122
+ const h = await git(["hash-object", "-w", "--stdin"], { cwd: root, stdin: f.content });
123
+ if (h.exitCode !== 0)
124
+ throw new Error(`cannot write ${f.path} on ${ref}: git hash-object failed: ${h.stderr.trim()}`);
125
+ blobs.push({ path: f.path, sha: h.stdout.trim(), ifAbsent: !!f.ifAbsent });
126
+ }
127
+ const ident = await identityEnv(root);
128
+ const scratch = mkdtempSync(join(tmpdir(), "chant-store-"));
129
+ const indexFile = join(scratch, "index");
130
+ const env = { ...ident, GIT_INDEX_FILE: indexFile };
131
+ let lastErr;
132
+ try {
133
+ for (let attempt = 1; attempt <= 5; attempt++) {
134
+ // With `parent` the commit builds on that commit (a remote tip) and swaps the ref from whatever it holds now.
135
+ const current = await readRefSha(ref, { cwd: root });
136
+ const tip = opts?.parent !== undefined ? opts.parent : current;
137
+ const read = await git(tip ? ["read-tree", tip] : ["read-tree", "--empty"], { cwd: root, env });
138
+ if (read.exitCode !== 0)
139
+ throw new Error(`cannot write ${ref}: git read-tree failed: ${read.stderr.trim()}`);
140
+ const existed = [];
141
+ const writes = [];
142
+ for (const b of blobs) {
143
+ if (b.ifAbsent && tip) {
144
+ const there = await git(["cat-file", "-e", `${tip}:${b.path}`], { cwd: root });
145
+ if (there.exitCode === 0) {
146
+ existed.push(b.path);
147
+ continue;
148
+ }
149
+ }
150
+ writes.push(b);
151
+ }
152
+ if (writes.length === 0)
153
+ return { commit: null, existed };
154
+ for (const b of writes) {
155
+ const u = await git(["update-index", "--add", "--cacheinfo", `100644,${b.sha},${b.path}`], { cwd: root, env });
156
+ if (u.exitCode !== 0)
157
+ throw new Error(`cannot write ${b.path} on ${ref}: git update-index failed: ${u.stderr.trim()}`);
158
+ }
159
+ const tree = await git(["write-tree"], { cwd: root, env });
160
+ if (tree.exitCode !== 0)
161
+ throw new Error(`cannot write ${ref}: git write-tree failed: ${tree.stderr.trim()}`);
162
+ const commit = await git(["commit-tree", ...(tip ? ["-p", tip] : []), "-m", message, tree.stdout.trim()], { cwd: root, env: ident });
163
+ if (commit.exitCode !== 0)
164
+ throw new Error(`cannot write ${ref}: git commit-tree failed: ${commit.stderr.trim()}`);
165
+ const sha = commit.stdout.trim();
166
+ try {
167
+ await updateRefCAS(ref, sha, current, { cwd: root });
168
+ return { commit: sha, existed };
169
+ }
170
+ catch (err) {
171
+ if (!(err instanceof RefCASConflictError))
172
+ throw err;
173
+ lastErr = err;
174
+ }
175
+ }
176
+ throw lastErr;
177
+ }
178
+ finally {
179
+ try {
180
+ if (existsSync(indexFile))
181
+ unlinkSync(indexFile);
182
+ rmdirSync(scratch);
183
+ }
184
+ catch {
185
+ // A scratch directory left in tmpdir is harmless.
186
+ }
187
+ }
188
+ }
189
+ /** `refs/chant/snapshots` → `refs/chant/remote/snapshots`: where a fetch lands, so a read never rewinds local writes. */
190
+ function trackingRefOf(ref) {
191
+ return ref.replace(/^refs\/chant\//, "refs/chant/remote/");
192
+ }
193
+ /**
194
+ * Fetch `ref` and fast-forward the local ref when it is behind. A local ref
195
+ * ahead of or diverged from the remote is kept as it is: its writes go out
196
+ * on the next {@link publishRef}, which rebases them.
197
+ */
198
+ export async function fetchRefFastForward(ref, opts) {
199
+ const cwd = opts?.cwd;
200
+ const remote = await remoteOf(cwd);
201
+ if (!remote)
202
+ return "no-remote";
203
+ const tracking = trackingRefOf(ref);
204
+ const f = await git(["fetch", "--no-tags", remote, `+${ref}:${tracking}`], { cwd });
205
+ if (f.exitCode !== 0)
206
+ return /couldn't find remote ref|no such ref was fetched/i.test(f.stderr ?? "") ? "missing" : "failed";
207
+ const remoteSha = await readRefSha(tracking, { cwd });
208
+ if (!remoteSha)
209
+ return "missing";
210
+ const local = await readRefSha(ref, { cwd });
211
+ if (local === remoteSha)
212
+ return "fetched";
213
+ if (local === null) {
214
+ await updateRefCAS(ref, remoteSha, null, { cwd }).catch(() => undefined);
215
+ return "fetched";
216
+ }
217
+ const behind = await git(["merge-base", "--is-ancestor", local, remoteSha], { cwd });
218
+ if (behind.exitCode === 0) {
219
+ await updateRefCAS(ref, remoteSha, local, { cwd }).catch(() => undefined);
220
+ return "fetched";
221
+ }
222
+ return "kept";
223
+ }
224
+ /**
225
+ * Push `ref` under a lease on the remote tip last fetched. A refused push
226
+ * (another machine pushed first) fetches, commits `pending` again on the
227
+ * remote tip, and pushes once more, up to three times.
228
+ */
229
+ export async function publishRef(ref, pending, message, opts) {
230
+ const cwd = opts?.cwd;
231
+ if (ledgerRecordingOff())
232
+ return { status: "nothing", detail: "recording is off for this run" };
233
+ const local = await readRefSha(ref, { cwd });
234
+ if (!local)
235
+ return { status: "nothing" };
236
+ const remote = await remoteOf(cwd);
237
+ if (!remote)
238
+ return { status: "local", detail: "no remote configured" };
239
+ const tracking = trackingRefOf(ref);
240
+ for (let attempt = 1; attempt <= 3; attempt++) {
241
+ const expected = await readRefSha(tracking, { cwd });
242
+ const tip = await readRefSha(ref, { cwd });
243
+ if (tip === expected)
244
+ return { status: "pushed", detail: `${remote} ${ref}` };
245
+ const push = await git(["push", "--no-verify", `--force-with-lease=${ref}:${expected ?? ""}`, remote, `${ref}:${ref}`], { cwd });
246
+ if (push.exitCode === 0) {
247
+ if (tip)
248
+ await git(["update-ref", tracking, tip], { cwd });
249
+ return { status: "pushed", detail: `${remote} ${ref}` };
250
+ }
251
+ // Refused: bring the remote in and put this process's writes on top of it.
252
+ const fetched = await fetchRefFastForward(ref, { cwd });
253
+ if (fetched === "failed")
254
+ return { status: "failed", detail: (push.stderr ?? "").trim().split("\n")[0] };
255
+ const remoteTip = await readRefSha(tracking, { cwd });
256
+ if (pending.size === 0 || !remoteTip)
257
+ return { status: "stale", detail: (push.stderr ?? "").trim().split("\n")[0] };
258
+ const files = [...pending].map(([path, content]) => ({ path, content }));
259
+ // Local commits that are not this process's writes are dropped here: a snapshot is observed again, never needed.
260
+ const rebuilt = await commitFilesToRef(ref, files, message, { cwd, parent: remoteTip });
261
+ if (rebuilt.commit === null)
262
+ return { status: "pushed", detail: `${remote} ${ref}` };
263
+ }
264
+ return { status: "stale", detail: `${remote} kept moving; run the command again` };
265
+ }
266
+ class GitRefMedium {
267
+ constructor(ref, cwd) {
268
+ this.ref = ref;
269
+ this.cwd = cwd;
270
+ /** This process's writes, by path, for {@link publishRef} to replay on a refused push. */
271
+ this.pending = new Map();
272
+ }
273
+ async get(path) {
274
+ const r = await git(["cat-file", "blob", `${this.ref}:${path}`], { cwd: this.cwd });
275
+ return r.exitCode === 0 ? r.stdout : null;
276
+ }
277
+ async put(path, content, opts) {
278
+ if (ledgerRecordingOff())
279
+ throw new NotRecorded();
280
+ const { commit, existed } = await commitFilesToRef(this.ref, [{ path, content, ifAbsent: opts?.ifAbsent }], opts?.message ?? "Index store write", { cwd: this.cwd });
281
+ if (existed.includes(path))
282
+ return { exists: true };
283
+ this.pending.set(path, content);
284
+ return { exists: false, ...(commit ? { version: commit } : {}) };
285
+ }
286
+ async list(dir) {
287
+ const tip = await readRefSha(this.ref, { cwd: this.cwd });
288
+ if (!tip)
289
+ return { files: [], dirs: [] };
290
+ const r = await git(["ls-tree", "--full-tree", dir === "" ? tip : `${tip}:${dir}`], { cwd: this.cwd });
291
+ const out = { files: [], dirs: [] };
292
+ if (r.exitCode !== 0)
293
+ return out;
294
+ for (const line of r.stdout.split("\n")) {
295
+ const m = line.match(/^\d+\s+(\w+)\s+[0-9a-f]+\t(.+)$/);
296
+ if (!m)
297
+ continue;
298
+ (m[1] === "tree" ? out.dirs : out.files).push(m[2]);
299
+ }
300
+ return out;
301
+ }
302
+ async history(dir) {
303
+ const tip = await readRefSha(this.ref, { cwd: this.cwd });
304
+ if (!tip)
305
+ return [];
306
+ const r = await git(["log", "--format=%H %aI %s", tip, ...(dir === "" ? [] : ["--", `:(top)${dir}`])], { cwd: this.cwd });
307
+ if (r.exitCode !== 0)
308
+ return [];
309
+ return r.stdout.trim().split("\n").filter(Boolean).map((line) => {
310
+ const [version, date, ...rest] = line.split(" ");
311
+ return { version, date, message: rest.join(" ") };
312
+ });
313
+ }
314
+ async getAt(path, version) {
315
+ const r = await git(["cat-file", "blob", `${version}:${path}`], { cwd: this.cwd });
316
+ return r.exitCode === 0 ? r.stdout : null;
317
+ }
318
+ async fetch() {
319
+ try {
320
+ await fetchRefFastForward(this.ref, { cwd: this.cwd });
321
+ }
322
+ catch {
323
+ // A fetch that fails leaves local history as it is, which every reader handles.
324
+ }
325
+ }
326
+ async publish() {
327
+ try {
328
+ return await publishRef(this.ref, this.pending, "Index store write", { cwd: this.cwd });
329
+ }
330
+ catch (err) {
331
+ return { status: "failed", detail: err instanceof Error ? err.message : String(err) };
332
+ }
333
+ }
334
+ }
335
+ class NotRecorded extends Error {
336
+ }
337
+ class LocalMedium {
338
+ constructor(root) {
339
+ this.root = root;
340
+ }
341
+ async get(path) {
342
+ try {
343
+ return readFileSync(join(this.root, path), "utf8");
344
+ }
345
+ catch {
346
+ return null;
347
+ }
348
+ }
349
+ async put(path, content, opts) {
350
+ const file = join(this.root, path);
351
+ mkdirSync(dirname(file), { recursive: true });
352
+ if (opts?.ifAbsent) {
353
+ try {
354
+ writeFileSync(file, content, { flag: "wx" });
355
+ }
356
+ catch (err) {
357
+ if (err.code === "EEXIST")
358
+ return { exists: true };
359
+ throw err;
360
+ }
361
+ }
362
+ else {
363
+ const tmp = `${file}.${process.pid}.tmp`;
364
+ writeFileSync(tmp, content);
365
+ renameSync(tmp, file);
366
+ }
367
+ return { exists: false, version: statSync(file).mtime.toISOString() };
368
+ }
369
+ async list(dir) {
370
+ const out = { files: [], dirs: [] };
371
+ try {
372
+ for (const e of readdirSync(join(this.root, dir), { withFileTypes: true })) {
373
+ if (e.name.endsWith(".tmp"))
374
+ continue;
375
+ (e.isDirectory() ? out.dirs : out.files).push(e.name);
376
+ }
377
+ }
378
+ catch {
379
+ // Absent: nothing there.
380
+ }
381
+ return out;
382
+ }
383
+ /** A directory keeps no history: one entry per file there now, at its modification time. */
384
+ async history(dir) {
385
+ const out = [];
386
+ const walk = async (d) => {
387
+ const l = await this.list(d);
388
+ for (const f of l.files) {
389
+ const rel = d === "" ? f : `${d}/${f}`;
390
+ const date = statSync(join(this.root, rel)).mtime.toISOString();
391
+ out.push({ version: date, date, message: rel, key: rel });
392
+ }
393
+ for (const sub of l.dirs)
394
+ await walk(d === "" ? sub : `${d}/${sub}`);
395
+ };
396
+ await walk(dir);
397
+ return out.sort((a, b) => (a.date < b.date ? 1 : a.date > b.date ? -1 : 0));
398
+ }
399
+ /** `latest`, or a time: the file as it is now, when it was written at or before that time. */
400
+ async getAt(path, version) {
401
+ const content = await this.get(path);
402
+ if (content === null || version === "latest")
403
+ return content;
404
+ if (!ISO_TIME_RE.test(version))
405
+ return null;
406
+ return statSync(join(this.root, path)).mtime.toISOString() <= new Date(version).toISOString() ? content : null;
407
+ }
408
+ async fetch() { }
409
+ async publish() {
410
+ return { status: "local", detail: this.root };
411
+ }
412
+ }
413
+ class S3StoreMedium {
414
+ constructor(s3) {
415
+ this.s3 = s3;
416
+ }
417
+ get(path) {
418
+ return this.s3.get(path);
419
+ }
420
+ async put(path, content, opts) {
421
+ const r = await this.s3.put(path, content, opts);
422
+ return { exists: r.exists, ...(r.versionId ? { version: r.versionId } : {}) };
423
+ }
424
+ async list(dir) {
425
+ const out = { files: [], dirs: [] };
426
+ for (const name of await this.s3.list(dir.replace(/\/$/, ""))) {
427
+ if (name.endsWith("/"))
428
+ out.dirs.push(name.slice(0, -1));
429
+ else
430
+ out.files.push(name);
431
+ }
432
+ return out;
433
+ }
434
+ /** The bucket's object versions under `dir`. Without versioning on, only the latest of each key. */
435
+ async history(dir) {
436
+ return (await this.s3.versions(dir.replace(/\/$/, ""))).map((v) => ({ version: v.versionId, date: v.lastModified, message: v.key, key: v.key }));
437
+ }
438
+ /** A version id, or a time: the newest version written at or before it. */
439
+ async getAt(path, version) {
440
+ if (!ISO_TIME_RE.test(version))
441
+ return this.s3.get(path, version);
442
+ const at = new Date(version).toISOString();
443
+ const versions = (await this.s3.versions(dirname(path))).filter((v) => v.key === path && new Date(v.lastModified).toISOString() <= at);
444
+ return versions.length === 0 ? null : this.s3.get(path, versions[0].versionId);
445
+ }
446
+ async fetch() { }
447
+ async publish() {
448
+ return { status: "pushed", detail: this.s3.where };
449
+ }
450
+ }
451
+ const isSnapshotFile = (name) => name.endsWith(".json") && name !== "observation-baseline.json";
452
+ /** A store over one medium, for the `git-ref`, `local` and `s3` kinds. */
453
+ class MediumStore {
454
+ constructor(kind, where, medium, layout, cwd) {
455
+ this.kind = kind;
456
+ this.where = where;
457
+ this.medium = medium;
458
+ this.layout = layout;
459
+ this.cwd = cwd;
460
+ this.writable = true;
461
+ }
462
+ loc(at) {
463
+ return { ...(this.cwd ? { cwd: this.cwd } : {}), ...(at ?? {}) };
464
+ }
465
+ async envDir(environment, at) {
466
+ return `${this.layout.snapshots}${await ledgerDir(environment, this.loc(at))}`;
467
+ }
468
+ async write(environment, key, json, at) {
469
+ const path = `${await this.envDir(environment, at)}/${key}.json`;
470
+ try {
471
+ const r = await this.medium.put(path, json, { message: "State snapshot" });
472
+ return { written: true, path, ...(r.version ? { version: r.version } : {}) };
473
+ }
474
+ catch (err) {
475
+ if (err instanceof NotRecorded)
476
+ return { written: false, path, reason: "recording is off for this run" };
477
+ throw err;
478
+ }
479
+ }
480
+ async readLatest(environment, key, at) {
481
+ return this.medium.get(`${await this.envDir(environment, at)}/${key}.json`);
482
+ }
483
+ async readAt(environment, key, version, at) {
484
+ return this.medium.getAt(`${await this.envDir(environment, at)}/${key}.json`, version);
485
+ }
486
+ async list(opts) {
487
+ const { environment, ...at } = opts ?? {};
488
+ const base = `${this.layout.snapshots}${await ledgerDir(environment ?? "", this.loc(at))}`.replace(/\/$/, "");
489
+ const all = await this.medium.history(base);
490
+ // A git ref's root also holds the index family; its commits are not snapshots.
491
+ return all.filter((v) => !(v.key ?? "").startsWith(this.layout.index) && v.message !== "Index version");
492
+ }
493
+ async keys(environment, at) {
494
+ const l = await this.medium.list(await this.envDir(environment, at));
495
+ return l.files.filter(isSnapshotFile).map((f) => f.slice(0, -".json".length)).sort();
496
+ }
497
+ async environments(at) {
498
+ const base = `${this.layout.snapshots}${await ledgerDir("", this.loc(at))}`.replace(/\/$/, "");
499
+ return (await this.medium.list(base)).dirs.filter((d) => !d.startsWith("_")).sort();
500
+ }
501
+ async writeIndex(doc) {
502
+ validateIndexDocument(doc);
503
+ const { stamp } = doc;
504
+ const dir = `${this.layout.index}${stamp.scopeKey}`;
505
+ const path = `${dir}/${indexVersionName(stamp.observedAt, stamp.digest)}`;
506
+ try {
507
+ await this.medium.put(`${dir}/${INDEX_SCOPE_FILE}`, `${JSON.stringify(stamp.scope, null, 2)}\n`, { ifAbsent: true, message: "Index version" });
508
+ const r = await this.medium.put(path, `${JSON.stringify(doc, null, 2)}\n`, { ifAbsent: true, message: "Index version" });
509
+ if (r.exists)
510
+ return { written: false, path, reason: "this version is already in the store, and a version is never overwritten" };
511
+ return { written: true, path, ...(r.version ? { version: r.version } : {}) };
512
+ }
513
+ catch (err) {
514
+ if (err instanceof NotRecorded)
515
+ return { written: false, path, reason: "recording is off for this run" };
516
+ throw err;
517
+ }
518
+ }
519
+ async listIndex(scopeKey) {
520
+ return (await this.medium.list(`${this.layout.index}${scopeKey}`)).files.filter(isIndexVersionName).sort();
521
+ }
522
+ async readIndexAt(scopeKey, version) {
523
+ if (!isIndexVersionName(version))
524
+ return null;
525
+ const text = await this.medium.get(`${this.layout.index}${scopeKey}/${version}`);
526
+ return text === null ? null : JSON.parse(text);
527
+ }
528
+ async readIndexLatest(scopeKey) {
529
+ const versions = await this.listIndex(scopeKey);
530
+ return versions.length === 0 ? null : this.readIndexAt(scopeKey, versions[versions.length - 1]);
531
+ }
532
+ async readIndexScope(scopeKey) {
533
+ const text = await this.medium.get(`${this.layout.index}${scopeKey}/${INDEX_SCOPE_FILE}`);
534
+ return text === null ? null : JSON.parse(text);
535
+ }
536
+ fetch() {
537
+ return this.medium.fetch();
538
+ }
539
+ publish() {
540
+ return this.medium.publish();
541
+ }
542
+ }
543
+ /** `none`: every write is dropped and every read is empty, which is every reader's fallback. */
544
+ class NoneStore {
545
+ constructor() {
546
+ this.kind = "none";
547
+ this.where = "nowhere (snapshots are off)";
548
+ this.writable = false;
549
+ this.dropped = (path) => ({ written: false, ...(path ? { path } : {}), reason: "the snapshot store is none" });
550
+ }
551
+ async write(environment, key) {
552
+ return this.dropped(`${environment}/${key}.json`);
553
+ }
554
+ async readLatest() {
555
+ return null;
556
+ }
557
+ async readAt() {
558
+ return null;
559
+ }
560
+ async list() {
561
+ return [];
562
+ }
563
+ async keys() {
564
+ return [];
565
+ }
566
+ async environments() {
567
+ return [];
568
+ }
569
+ async writeIndex(doc) {
570
+ validateIndexDocument(doc);
571
+ return this.dropped();
572
+ }
573
+ async listIndex() {
574
+ return [];
575
+ }
576
+ async readIndexAt() {
577
+ return null;
578
+ }
579
+ async readIndexLatest() {
580
+ return null;
581
+ }
582
+ async readIndexScope() {
583
+ return null;
584
+ }
585
+ async fetch() { }
586
+ async publish() {
587
+ return { status: "nothing" };
588
+ }
589
+ }
590
+ /** `orphan-branch`: the snapshots on `chant/lifecycle`, read-only, so the ones written before #4519 stay readable. */
591
+ class OrphanBranchStore {
592
+ constructor(cwd) {
593
+ this.cwd = cwd;
594
+ this.kind = "orphan-branch";
595
+ this.where = "chant/lifecycle (read-only)";
596
+ this.writable = false;
597
+ }
598
+ loc(at) {
599
+ return { ...(this.cwd ? { cwd: this.cwd } : {}), ...(at ?? {}) };
600
+ }
601
+ async write(environment, key) {
602
+ return { written: false, path: `${environment}/${key}.json`, reason: "the orphan-branch store is read-only; snapshots are written to refs/chant/snapshots" };
603
+ }
604
+ readLatest(environment, key, at) {
605
+ return readBlobFromPath(environment, `${key}.json`, this.loc(at));
606
+ }
607
+ readAt(environment, key, version, at) {
608
+ return readOrphanSnapshotAt(environment, key, version, this.loc(at));
609
+ }
610
+ async list(opts) {
611
+ const { environment: _env, ...at } = opts ?? {};
612
+ return (await listOrphanSnapshots(this.loc(at)))
613
+ .filter((e) => e.message === "State snapshot")
614
+ .map((e) => ({ version: e.commit, date: e.date, message: e.message }));
615
+ }
616
+ async keys(environment, at) {
617
+ return (await listFilesInDir(environment, this.loc(at))).filter(isSnapshotFile).map((f) => f.slice(0, -".json".length)).sort();
618
+ }
619
+ async environments(at) {
620
+ const out = [];
621
+ for (const t of await listLedgerTrees(this.loc(at))) {
622
+ if (t.startsWith("_"))
623
+ continue;
624
+ if ((await this.keys(t, at)).length > 0)
625
+ out.push(t);
626
+ }
627
+ return out.sort();
628
+ }
629
+ async writeIndex(doc) {
630
+ validateIndexDocument(doc);
631
+ return { written: false, reason: "the orphan-branch store is read-only and never held an index" };
632
+ }
633
+ async listIndex() {
634
+ return [];
635
+ }
636
+ async readIndexAt() {
637
+ return null;
638
+ }
639
+ async readIndexLatest() {
640
+ return null;
641
+ }
642
+ async readIndexScope() {
643
+ return null;
644
+ }
645
+ async fetch() {
646
+ try {
647
+ await fetchLifecycle(this.loc());
648
+ }
649
+ catch {
650
+ // Unreadable remote: local history is what there is.
651
+ }
652
+ }
653
+ async publish() {
654
+ return { status: "nothing" };
655
+ }
656
+ }
657
+ /**
658
+ * A store that writes to `primary` and reads `legacy` when `primary` has no
659
+ * answer: the default, `refs/chant/snapshots` over the old orphan branch.
660
+ */
661
+ class FallbackStore {
662
+ constructor(primary, legacy) {
663
+ this.primary = primary;
664
+ this.legacy = legacy;
665
+ }
666
+ get kind() {
667
+ return this.primary.kind;
668
+ }
669
+ get where() {
670
+ return `${this.primary.where} (reading ${this.legacy.where} for older snapshots)`;
671
+ }
672
+ get writable() {
673
+ return this.primary.writable;
674
+ }
675
+ write(environment, key, json, at) {
676
+ return this.primary.write(environment, key, json, at);
677
+ }
678
+ async readLatest(environment, key, at) {
679
+ return (await this.primary.readLatest(environment, key, at)) ?? this.legacy.readLatest(environment, key, at);
680
+ }
681
+ async readAt(environment, key, version, at) {
682
+ // Both are git commits with the same layout, so one version id reads from either history.
683
+ return (await this.primary.readAt(environment, key, version, at)) ?? this.legacy.readAt(environment, key, version, at);
684
+ }
685
+ async list(opts) {
686
+ const merged = [...(await this.primary.list(opts)), ...(await this.legacy.list(opts))];
687
+ return merged.sort((a, b) => (a.date < b.date ? 1 : a.date > b.date ? -1 : 0));
688
+ }
689
+ async keys(environment, at) {
690
+ return [...new Set([...(await this.primary.keys(environment, at)), ...(await this.legacy.keys(environment, at))])].sort();
691
+ }
692
+ async environments(at) {
693
+ return [...new Set([...(await this.primary.environments(at)), ...(await this.legacy.environments(at))])].sort();
694
+ }
695
+ writeIndex(doc) {
696
+ return this.primary.writeIndex(doc);
697
+ }
698
+ listIndex(scopeKey) {
699
+ return this.primary.listIndex(scopeKey);
700
+ }
701
+ readIndexAt(scopeKey, version) {
702
+ return this.primary.readIndexAt(scopeKey, version);
703
+ }
704
+ readIndexLatest(scopeKey) {
705
+ return this.primary.readIndexLatest(scopeKey);
706
+ }
707
+ readIndexScope(scopeKey) {
708
+ return this.primary.readIndexScope(scopeKey);
709
+ }
710
+ async fetch(at) {
711
+ await this.primary.fetch(at);
712
+ await this.legacy.fetch(at);
713
+ }
714
+ publish(at) {
715
+ return this.primary.publish(at);
716
+ }
717
+ }
718
+ export const SNAPSHOT_STORE_KINDS = ["git-ref", "orphan-branch", "local", "s3", "none"];
719
+ /** Default directory of a `local` store, relative to the project. */
720
+ export const DEFAULT_LOCAL_STORE_DIR = ".chant/store";
721
+ /**
722
+ * The store configuration in effect: `CHANT_SNAPSHOT_STORE` and its
723
+ * companions over `snapshots` in chant.config.ts over the default `git-ref`.
724
+ */
725
+ export function snapshotStoreConfig(configured, env = process.env) {
726
+ const base = (configured && typeof configured === "object" ? configured : {});
727
+ const kind = (env.CHANT_SNAPSHOT_STORE?.trim() || base.store || "git-ref");
728
+ if (!SNAPSHOT_STORE_KINDS.includes(kind)) {
729
+ throw new Error(`snapshot store "${kind}" is not one of ${SNAPSHOT_STORE_KINDS.join(", ")}`);
730
+ }
731
+ const fromBase = base.store === kind ? base : {};
732
+ switch (kind) {
733
+ case "git-ref":
734
+ return { store: "git-ref", ...(typeof fromBase.ref === "string" ? { ref: fromBase.ref } : {}), ...(fromBase.readOrphanBranch === false ? { readOrphanBranch: false } : {}) };
735
+ case "orphan-branch":
736
+ return { store: "orphan-branch" };
737
+ case "none":
738
+ return { store: "none" };
739
+ case "local": {
740
+ const dir = env.CHANT_SNAPSHOT_STORE_DIR?.trim() || (typeof fromBase.dir === "string" ? fromBase.dir : undefined);
741
+ return { store: "local", ...(dir ? { dir } : {}) };
742
+ }
743
+ case "s3": {
744
+ const pick = (envName, key) => env[envName]?.trim() || (typeof fromBase[key] === "string" ? fromBase[key] : undefined);
745
+ const bucket = pick("CHANT_SNAPSHOT_S3_BUCKET", "bucket");
746
+ if (!bucket)
747
+ throw new Error("the s3 snapshot store needs a bucket: snapshots.bucket in chant.config.ts or CHANT_SNAPSHOT_S3_BUCKET");
748
+ const prefix = pick("CHANT_SNAPSHOT_S3_PREFIX", "prefix");
749
+ const region = pick("CHANT_SNAPSHOT_S3_REGION", "region");
750
+ const endpoint = pick("CHANT_SNAPSHOT_S3_ENDPOINT", "endpoint");
751
+ const creds = fromBase.credentials;
752
+ return {
753
+ store: "s3",
754
+ bucket,
755
+ ...(prefix ? { prefix } : {}),
756
+ ...(region ? { region } : {}),
757
+ ...(endpoint ? { endpoint } : {}),
758
+ ...(typeof fromBase.forcePathStyle === "boolean" ? { forcePathStyle: fromBase.forcePathStyle } : {}),
759
+ ...(creds ? { credentials: creds } : {}),
760
+ };
761
+ }
762
+ }
763
+ }
764
+ /** Build a store from its configuration. `cwd` is the project directory. */
765
+ export function createSnapshotStore(config, opts) {
766
+ const cwd = opts?.cwd;
767
+ switch (config.store) {
768
+ case "git-ref": {
769
+ const ref = config.ref ?? SNAPSHOT_REF;
770
+ const store = new MediumStore("git-ref", ref, new GitRefMedium(ref, cwd), { snapshots: "", index: `${GIT_INDEX_DIR}/` }, cwd);
771
+ return config.readOrphanBranch === false ? store : new FallbackStore(store, new OrphanBranchStore(cwd));
772
+ }
773
+ case "orphan-branch":
774
+ return new OrphanBranchStore(cwd);
775
+ case "local": {
776
+ const root = resolve(cwd ?? process.cwd(), config.dir ?? DEFAULT_LOCAL_STORE_DIR);
777
+ return new MediumStore("local", root, new LocalMedium(root), { snapshots: "snapshots/", index: "index/" }, cwd);
778
+ }
779
+ case "s3": {
780
+ const { store: _s, ...settings } = config;
781
+ const s3 = new S3Medium(settings, { ...(opts?.fetch ? { fetch: opts.fetch } : {}), ...(opts?.env ? { env: opts.env } : {}) });
782
+ return new MediumStore("s3", s3.where, new S3StoreMedium(s3), { snapshots: "snapshots/", index: "index/" }, cwd);
783
+ }
784
+ case "none":
785
+ return new NoneStore();
786
+ }
787
+ }
788
+ const resolved = new Map();
789
+ /**
790
+ * The project's store, built once per directory and configuration so a
791
+ * write and the publish after it share one instance. `config` is the
792
+ * project's loaded chant.config; without it the config is loaded from `cwd`
793
+ * upward, and a directory with none uses the default.
794
+ */
795
+ export async function resolveSnapshotStore(opts) {
796
+ const cwd = resolve(opts?.cwd ?? process.cwd());
797
+ let configured = opts?.config?.snapshots;
798
+ if (opts?.config === undefined) {
799
+ try {
800
+ const { loadChantConfigUpward } = await import("../config.js");
801
+ configured = (await loadChantConfigUpward(cwd)).config.snapshots;
802
+ }
803
+ catch {
804
+ configured = undefined;
805
+ }
806
+ }
807
+ const config = snapshotStoreConfig(configured);
808
+ const cacheKey = `${cwd}\0${JSON.stringify(config)}`;
809
+ let store = resolved.get(cacheKey);
810
+ if (!store) {
811
+ store = createSnapshotStore(config, { cwd });
812
+ resolved.set(cacheKey, store);
813
+ }
814
+ return store;
815
+ }
816
+ /** Forget every resolved store. For tests. */
817
+ export function resetResolvedSnapshotStores() {
818
+ resolved.clear();
819
+ }
820
+ /**
821
+ * The snapshot family as a {@link import("./ledger-port.js").LedgerReadPort}:
822
+ * `trees` are the environments, `list(env)` the snapshot files, `read` a file.
823
+ * `chant query` reads its `resources` and `snapshots` tables through this, so
824
+ * the tables are the same whichever store holds them.
825
+ */
826
+ export function snapshotReadPort(store, at) {
827
+ return {
828
+ read: async (dir, file) => (file.endsWith(".json") ? store.readLatest(dir, file.slice(0, -".json".length), at) : null),
829
+ list: async (dir) => (await store.keys(dir, at)).map((k) => `${k}.json`),
830
+ trees: () => store.environments(at),
831
+ };
832
+ }