@intentius/chant 0.124.13 → 0.124.15

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 (991) hide show
  1. package/bin/chant +11 -0
  2. package/dist/agents/checks.js +326 -0
  3. package/dist/agents/discover.js +885 -0
  4. package/dist/agents/importer.js +17 -0
  5. package/dist/agents/index.js +12 -0
  6. package/dist/agents/types.js +19 -0
  7. package/dist/apply.js +151 -0
  8. package/dist/attrref.js +70 -0
  9. package/dist/audit/catalog.js +350 -0
  10. package/dist/audit/core.js +252 -0
  11. package/dist/audit/discover.js +633 -0
  12. package/dist/audit/fetch.js +666 -0
  13. package/dist/audit/lineage.js +91 -0
  14. package/dist/audit/nginx.js +366 -0
  15. package/dist/audit/prior-art.js +132 -0
  16. package/dist/audit/proof.js +321 -0
  17. package/dist/audit/report-html.js +194 -0
  18. package/dist/audit/report-model.js +198 -0
  19. package/dist/audit/report.js +102 -0
  20. package/dist/audit/rules-doc.js +144 -0
  21. package/dist/audit/secrets.js +405 -0
  22. package/dist/audit/terraform-state.js +140 -0
  23. package/dist/audit/wrangler.js +262 -0
  24. package/dist/behaviour-delta.js +401 -0
  25. package/dist/behaviour-engine.js +357 -0
  26. package/dist/behaviour-graph.js +83 -0
  27. package/dist/behaviour-http.js +188 -0
  28. package/dist/behaviour-kinds.js +172 -0
  29. package/dist/behaviour-overlay.js +50 -0
  30. package/dist/behaviour-predict.js +188 -0
  31. package/dist/behaviour-request.js +194 -0
  32. package/dist/behaviour.js +1325 -0
  33. package/dist/build-params.js +108 -0
  34. package/dist/build.d.ts +6 -0
  35. package/dist/build.d.ts.map +1 -1
  36. package/dist/build.js +669 -0
  37. package/dist/builder.js +44 -0
  38. package/dist/cdk/advise.js +30 -0
  39. package/dist/cdk/assembly.js +122 -0
  40. package/dist/cdk/graph.js +444 -0
  41. package/dist/cdk/tier-map.js +64 -0
  42. package/dist/cdk/types.js +24 -0
  43. package/dist/change-set.d.ts +24 -2
  44. package/dist/change-set.d.ts.map +1 -1
  45. package/dist/change-set.js +361 -0
  46. package/dist/child-project.js +21 -0
  47. package/dist/ci-jobs.d.ts +227 -0
  48. package/dist/ci-jobs.d.ts.map +1 -0
  49. package/dist/ci-jobs.js +448 -0
  50. package/dist/claimed-fields.js +135 -0
  51. package/dist/cli/build-options.js +70 -0
  52. package/dist/cli/build-params-cli.js +139 -0
  53. package/dist/cli/command-group.js +123 -0
  54. package/dist/cli/commands/audit-agents.js +326 -0
  55. package/dist/cli/commands/audit.js +571 -0
  56. package/dist/cli/commands/build.js +920 -0
  57. package/dist/cli/commands/carve-apply.js +153 -0
  58. package/dist/cli/commands/carve-bridge.js +201 -0
  59. package/dist/cli/commands/carve-emit.js +332 -0
  60. package/dist/cli/commands/carve-status.js +119 -0
  61. package/dist/cli/commands/carve.js +204 -0
  62. package/dist/cli/commands/check-lexicon-docs.js +79 -0
  63. package/dist/cli/commands/check-lexicon-examples.js +177 -0
  64. package/dist/cli/commands/check-lexicon-intrinsics.js +291 -0
  65. package/dist/cli/commands/check-lexicon-mcp.js +82 -0
  66. package/dist/cli/commands/check-lexicon-plugin.d.ts.map +1 -1
  67. package/dist/cli/commands/check-lexicon-plugin.js +104 -0
  68. package/dist/cli/commands/check-lexicon.js +766 -0
  69. package/dist/cli/commands/describe.js +85 -0
  70. package/dist/cli/commands/diff.js +175 -0
  71. package/dist/cli/commands/doctor.js +350 -0
  72. package/dist/cli/commands/import-agents.js +155 -0
  73. package/dist/cli/commands/import.js +670 -0
  74. package/dist/cli/commands/init-lexicon/templates/codegen.js +194 -0
  75. package/dist/cli/commands/init-lexicon/templates/docs.js +100 -0
  76. package/dist/cli/commands/init-lexicon/templates/examples.js +29 -0
  77. package/dist/cli/commands/init-lexicon/templates/lint.js +34 -0
  78. package/dist/cli/commands/init-lexicon/templates/lsp.js +37 -0
  79. package/dist/cli/commands/init-lexicon/templates/plugin.js +108 -0
  80. package/dist/cli/commands/init-lexicon/templates/project.js +240 -0
  81. package/dist/cli/commands/init-lexicon/templates/spec.js +56 -0
  82. package/dist/cli/commands/init-lexicon/templates/tests.js +66 -0
  83. package/dist/cli/commands/init-lexicon.js +172 -0
  84. package/dist/cli/commands/init.js +381 -0
  85. package/dist/cli/commands/lexicon-rolling-upgrade.js +145 -0
  86. package/dist/cli/commands/lexicon-surface-diff.js +145 -0
  87. package/dist/cli/commands/licenses.js +125 -0
  88. package/dist/cli/commands/lint.d.ts.map +1 -1
  89. package/dist/cli/commands/lint.js +948 -0
  90. package/dist/cli/commands/list.js +63 -0
  91. package/dist/cli/commands/migrate.d.ts.map +1 -1
  92. package/dist/cli/commands/migrate.js +423 -0
  93. package/dist/cli/commands/onboard.js +403 -0
  94. package/dist/cli/commands/pinned-upgrade.js +191 -0
  95. package/dist/cli/commands/update.js +194 -0
  96. package/dist/cli/commands/vendor.js +217 -0
  97. package/dist/cli/conflict-check.js +104 -0
  98. package/dist/cli/debug.js +8 -0
  99. package/dist/cli/format.js +104 -0
  100. package/dist/cli/handlers/build.js +267 -0
  101. package/dist/cli/handlers/carve-apply.js +30 -0
  102. package/dist/cli/handlers/carve-bridge.js +28 -0
  103. package/dist/cli/handlers/carve-emit.js +50 -0
  104. package/dist/cli/handlers/carve-status.js +26 -0
  105. package/dist/cli/handlers/carve.js +40 -0
  106. package/dist/cli/handlers/change-set.js +46 -0
  107. package/dist/cli/handlers/components.js +730 -0
  108. package/dist/cli/handlers/dev.js +113 -0
  109. package/dist/cli/handlers/emulator.js +233 -0
  110. package/dist/cli/handlers/explain.js +54 -0
  111. package/dist/cli/handlers/fan-out.js +422 -0
  112. package/dist/cli/handlers/gate-resume.js +275 -0
  113. package/dist/cli/handlers/generate.js +84 -0
  114. package/dist/cli/handlers/graph.js +969 -0
  115. package/dist/cli/handlers/init.js +102 -0
  116. package/dist/cli/handlers/lifecycle.js +1727 -0
  117. package/dist/cli/handlers/lint.js +62 -0
  118. package/dist/cli/handlers/migrate.js +48 -0
  119. package/dist/cli/handlers/misc.js +391 -0
  120. package/dist/cli/handlers/operator.js +1162 -0
  121. package/dist/cli/handlers/override-approve.js +102 -0
  122. package/dist/cli/handlers/pr.js +503 -0
  123. package/dist/cli/handlers/promote.js +342 -0
  124. package/dist/cli/handlers/run-generate.js +206 -0
  125. package/dist/cli/handlers/run-wave.js +66 -0
  126. package/dist/cli/handlers/run.js +1191 -0
  127. package/dist/cli/handlers/scenario.js +325 -0
  128. package/dist/cli/handlers/search.js +924 -0
  129. package/dist/cli/handlers/serve.js +32 -0
  130. package/dist/cli/handlers/vendor.js +154 -0
  131. package/dist/cli/index.js +3 -0
  132. package/dist/cli/is-entry-point.js +23 -0
  133. package/dist/cli/lsp/capabilities.js +28 -0
  134. package/dist/cli/lsp/diagnostics.js +34 -0
  135. package/dist/cli/lsp/server.js +329 -0
  136. package/dist/cli/main.d.ts.map +1 -1
  137. package/dist/cli/main.js +2540 -0
  138. package/dist/cli/mcp/lifecycle-tools.js +80 -0
  139. package/dist/cli/mcp/op-tools.js +306 -0
  140. package/dist/cli/mcp/resource-handlers.js +258 -0
  141. package/dist/cli/mcp/resources/context.js +85 -0
  142. package/dist/cli/mcp/server.js +353 -0
  143. package/dist/cli/mcp/tools/build.js +111 -0
  144. package/dist/cli/mcp/tools/composites.js +95 -0
  145. package/dist/cli/mcp/tools/explain.js +166 -0
  146. package/dist/cli/mcp/tools/import.js +49 -0
  147. package/dist/cli/mcp/tools/lint.js +41 -0
  148. package/dist/cli/mcp/tools/scaffold.js +111 -0
  149. package/dist/cli/mcp/tools/search.js +112 -0
  150. package/dist/cli/mcp/types.js +1 -0
  151. package/dist/cli/mcp/workspace-plugins.js +91 -0
  152. package/dist/cli/mcp/workspace-tools.d.ts +1 -1
  153. package/dist/cli/mcp/workspace-tools.js +564 -0
  154. package/dist/cli/mcp-config.js +70 -0
  155. package/dist/cli/plugins.js +313 -0
  156. package/dist/cli/registry.js +23 -0
  157. package/dist/cli/reporters/stylish.js +329 -0
  158. package/dist/cli/version.js +15 -0
  159. package/dist/cli/watch.js +79 -0
  160. package/dist/codegen/bound-property-types.js +82 -0
  161. package/dist/codegen/coverage.js +119 -0
  162. package/dist/codegen/docs-file-markers.js +60 -0
  163. package/dist/codegen/docs-pages.js +115 -0
  164. package/dist/codegen/docs-rule-scanning.js +164 -0
  165. package/dist/codegen/docs-rules.js +76 -0
  166. package/dist/codegen/docs-sections.js +117 -0
  167. package/dist/codegen/docs-sidebar.js +65 -0
  168. package/dist/codegen/docs-types.js +4 -0
  169. package/dist/codegen/docs.js +356 -0
  170. package/dist/codegen/fetch.js +340 -0
  171. package/dist/codegen/generate-post-synth-barrel.js +63 -0
  172. package/dist/codegen/generate-registry.js +71 -0
  173. package/dist/codegen/generate-runtime-index.js +56 -0
  174. package/dist/codegen/generate-typescript.js +119 -0
  175. package/dist/codegen/generate.js +115 -0
  176. package/dist/codegen/json-patch.js +141 -0
  177. package/dist/codegen/json-schema-to-ts.js +344 -0
  178. package/dist/codegen/json-schema.js +246 -0
  179. package/dist/codegen/lexicon-regen.js +412 -0
  180. package/dist/codegen/naming.js +217 -0
  181. package/dist/codegen/okf-lexicon.js +245 -0
  182. package/dist/codegen/package.js +150 -0
  183. package/dist/codegen/pinned-upgrade.d.ts.map +1 -1
  184. package/dist/codegen/pinned-upgrade.js +494 -0
  185. package/dist/codegen/registry.js +60 -0
  186. package/dist/codegen/rehype-base-url.mjs +68 -0
  187. package/dist/codegen/rolling-upgrade.js +217 -0
  188. package/dist/codegen/surface-snapshot.js +537 -0
  189. package/dist/codegen/testdata/postgres-tags.json +242 -0
  190. package/dist/codegen/topo-sort.js +36 -0
  191. package/dist/codegen/typecheck.js +115 -0
  192. package/dist/codegen/validate.js +200 -0
  193. package/dist/codegen/version-bump.js +35 -0
  194. package/dist/components/auto-release.js +143 -0
  195. package/dist/components/builders.js +47 -0
  196. package/dist/components/capability-plugin-loader.js +175 -0
  197. package/dist/components/capability-plugin-schema.js +46 -0
  198. package/dist/components/capability-plugin.js +99 -0
  199. package/dist/components/capability.js +60 -0
  200. package/dist/components/changed-files.js +72 -0
  201. package/dist/components/cli-support.js +427 -0
  202. package/dist/components/component.js +120 -0
  203. package/dist/components/component.schema.json +271 -0
  204. package/dist/components/config-defaults.js +130 -0
  205. package/dist/components/deploy-units.js +60 -0
  206. package/dist/components/discover.d.ts.map +1 -1
  207. package/dist/components/discover.js +189 -0
  208. package/dist/components/driver-output.js +86 -0
  209. package/dist/components/driver.js +728 -0
  210. package/dist/components/fan-out-output.js +165 -0
  211. package/dist/components/fan-out-record.js +94 -0
  212. package/dist/components/fan-out-run.js +373 -0
  213. package/dist/components/fan-out-support.js +60 -0
  214. package/dist/components/fan-out.js +273 -0
  215. package/dist/components/gate-plan.js +41 -0
  216. package/dist/components/gated-wave-pipeline.js +46 -0
  217. package/dist/components/index.js +24 -0
  218. package/dist/components/layers.js +28 -0
  219. package/dist/components/manifest-persistence.js +108 -0
  220. package/dist/components/pilots/alb-ecs.pilot.js +59 -0
  221. package/dist/components/pilots/dynamodb.pilot.js +51 -0
  222. package/dist/components/pilots/jar-emr.pilot.js +65 -0
  223. package/dist/components/pilots/lambda.pilot.js +52 -0
  224. package/dist/components/pilots/neo4j-fanout.pilot.js +68 -0
  225. package/dist/components/pilots/project.js +12 -0
  226. package/dist/components/pr-pipeline.d.ts.map +1 -1
  227. package/dist/components/pr-pipeline.js +135 -0
  228. package/dist/components/pr-run.js +363 -0
  229. package/dist/components/presets/ecs-fargate.js +72 -0
  230. package/dist/components/presets/index.js +28 -0
  231. package/dist/components/presets/lambda.js +34 -0
  232. package/dist/components/presets/single-host-compose.js +41 -0
  233. package/dist/components/promote.js +549 -0
  234. package/dist/components/registry.js +34 -0
  235. package/dist/components/run-progress.js +33 -0
  236. package/dist/components/sandbox/driver.d.ts.map +1 -1
  237. package/dist/components/sandbox/driver.js +93 -0
  238. package/dist/components/sandbox/run.js +115 -0
  239. package/dist/components/starter-plugin.js +104 -0
  240. package/dist/components/verbs/__tests__/mock-cloud-executor.js +68 -0
  241. package/dist/components/verbs/__tests__/mock-process-runner.js +45 -0
  242. package/dist/components/verbs/__tests__/mock-sbom-generator.js +56 -0
  243. package/dist/components/verbs/bom-writer.js +287 -0
  244. package/dist/components/verbs/build-archive.js +188 -0
  245. package/dist/components/verbs/build.js +142 -0
  246. package/dist/components/verbs/cloud-executor.js +110 -0
  247. package/dist/components/verbs/component-bom.js +125 -0
  248. package/dist/components/verbs/ensure-secret.js +50 -0
  249. package/dist/components/verbs/index.js +61 -0
  250. package/dist/components/verbs/license-policy.js +75 -0
  251. package/dist/components/verbs/lockfile-sbom-generator.js +191 -0
  252. package/dist/components/verbs/process-runner.js +78 -0
  253. package/dist/components/verbs/r2-sync.js +93 -0
  254. package/dist/components/verbs/reproducibility.js +62 -0
  255. package/dist/components/verbs/run-agent.js +577 -0
  256. package/dist/components/verbs/sbom-generator.js +119 -0
  257. package/dist/components/verbs/sbom.js +97 -0
  258. package/dist/components/verbs/shell.js +32 -0
  259. package/dist/components/verbs/sign.js +221 -0
  260. package/dist/components/verbs/stub.js +28 -0
  261. package/dist/components/verbs/tool-sbom-generator.js +154 -0
  262. package/dist/components/verbs/verify.js +112 -0
  263. package/dist/components/verbs/vex.js +131 -0
  264. package/dist/components/verbs/vuln-gate.js +152 -0
  265. package/dist/components/verbs/vuln-scan.js +288 -0
  266. package/dist/components/verbs/wait-verify.js +106 -0
  267. package/dist/components/verbs/wrangler.js +179 -0
  268. package/dist/components/wave-plan.js +102 -0
  269. package/dist/composite.d.ts +10 -2
  270. package/dist/composite.d.ts.map +1 -1
  271. package/dist/composite.js +313 -0
  272. package/dist/config-import.d.ts.map +1 -1
  273. package/dist/config-import.js +22 -0
  274. package/dist/config-sandbox.js +122 -0
  275. package/dist/config-static.js +206 -0
  276. package/dist/config.js +605 -0
  277. package/dist/content-digest.js +23 -0
  278. package/dist/declarable.js +114 -0
  279. package/dist/deep-observation.js +437 -0
  280. package/dist/detectLexicon.js +54 -0
  281. package/dist/discovery/cache.js +70 -0
  282. package/dist/discovery/collect.d.ts +2 -1
  283. package/dist/discovery/collect.d.ts.map +1 -1
  284. package/dist/discovery/collect.js +223 -0
  285. package/dist/discovery/cycles.js +95 -0
  286. package/dist/discovery/entity-wire-codec.js +391 -0
  287. package/dist/discovery/entity-wire.js +42 -0
  288. package/dist/discovery/files.js +121 -0
  289. package/dist/discovery/fold-import.d.ts.map +1 -1
  290. package/dist/discovery/fold-import.js +4073 -0
  291. package/dist/discovery/fold-rank.js +292 -0
  292. package/dist/discovery/graph.js +115 -0
  293. package/dist/discovery/host-composite.d.ts +35 -0
  294. package/dist/discovery/host-composite.d.ts.map +1 -0
  295. package/dist/discovery/host-composite.js +93 -0
  296. package/dist/discovery/import.d.ts.map +1 -1
  297. package/dist/discovery/import.js +65 -0
  298. package/dist/discovery/index.d.ts +9 -0
  299. package/dist/discovery/index.d.ts.map +1 -1
  300. package/dist/discovery/index.js +333 -0
  301. package/dist/discovery/param-deps.js +487 -0
  302. package/dist/discovery/resolve.js +39 -0
  303. package/dist/discovery/sandbox/bundle.js +214 -0
  304. package/dist/discovery/sandbox/child-errors.js +26 -0
  305. package/dist/discovery/sandbox/config-run.js +100 -0
  306. package/dist/discovery/sandbox/config-wire.js +183 -0
  307. package/dist/discovery/sandbox/driver.d.ts.map +1 -1
  308. package/dist/discovery/sandbox/driver.js +333 -0
  309. package/dist/discovery/sandbox/fork.js +229 -0
  310. package/dist/discovery/sandbox/policy-run.js +142 -0
  311. package/dist/discovery/sandbox/policy-wire.js +152 -0
  312. package/dist/discovery/sandbox/run.js +77 -0
  313. package/dist/discovery/specifier-resolve.js +320 -0
  314. package/dist/discovery/walk.js +284 -0
  315. package/dist/effect-receipt.js +340 -0
  316. package/dist/env.js +55 -0
  317. package/dist/errors.js +62 -0
  318. package/dist/fold/fold.js +1434 -0
  319. package/dist/fold/foldable-helpers.js +229 -0
  320. package/dist/fold/subset.js +696 -0
  321. package/dist/fold-provenance.js +414 -0
  322. package/dist/gate-decision.d.ts +20 -9
  323. package/dist/gate-decision.d.ts.map +1 -1
  324. package/dist/gate-decision.js +313 -0
  325. package/dist/gate-resume-run.js +154 -0
  326. package/dist/gated-waves.js +165 -0
  327. package/dist/governance.js +43 -0
  328. package/dist/graph-declared.js +45 -0
  329. package/dist/graph-detail.js +207 -0
  330. package/dist/graph-dot.js +54 -0
  331. package/dist/graph-effective.js +107 -0
  332. package/dist/graph-ir.js +623 -0
  333. package/dist/graph-layout.js +184 -0
  334. package/dist/graph-lens.js +105 -0
  335. package/dist/graph-mermaid.js +83 -0
  336. package/dist/graph-ops.js +39 -0
  337. package/dist/graph-refs.js +156 -0
  338. package/dist/held-elsewhere.js +87 -0
  339. package/dist/identity.js +251 -0
  340. package/dist/import/base-parser.js +34 -0
  341. package/dist/import/embedded.js +255 -0
  342. package/dist/import/generator.js +1 -0
  343. package/dist/import/ir-utils.js +85 -0
  344. package/dist/import/parser-options.js +45 -0
  345. package/dist/import/parser.js +1 -0
  346. package/dist/index.js +118 -0
  347. package/dist/intrinsic-interpolation.js +73 -0
  348. package/dist/intrinsic.js +31 -0
  349. package/dist/join-key.js +35 -0
  350. package/dist/kubectl-context.js +150 -0
  351. package/dist/lexicon-config.js +58 -0
  352. package/dist/lexicon-integrity.js +47 -0
  353. package/dist/lexicon-manifest.js +70 -0
  354. package/dist/lexicon-module.d.ts.map +1 -1
  355. package/dist/lexicon-module.js +241 -0
  356. package/dist/lexicon-output.js +218 -0
  357. package/dist/lexicon-plugin-helpers.js +104 -0
  358. package/dist/lexicon-schema.js +136 -0
  359. package/dist/lexicon.js +130 -0
  360. package/dist/lifecycle/affected.js +224 -0
  361. package/dist/lifecycle/assert-live.js +110 -0
  362. package/dist/lifecycle/build-ledger-store.js +160 -0
  363. package/dist/lifecycle/build-ledger.js +155 -0
  364. package/dist/lifecycle/change-set.js +356 -0
  365. package/dist/lifecycle/converge-ledger.js +161 -0
  366. package/dist/lifecycle/deep-diff.js +233 -0
  367. package/dist/lifecycle/deep-observe.js +147 -0
  368. package/dist/lifecycle/digest.js +143 -0
  369. package/dist/lifecycle/disruption.js +135 -0
  370. package/dist/lifecycle/gate-applied.js +113 -0
  371. package/dist/lifecycle/gate-audit.d.ts +69 -2
  372. package/dist/lifecycle/gate-audit.d.ts.map +1 -1
  373. package/dist/lifecycle/gate-audit.js +415 -0
  374. package/dist/lifecycle/gate-ledger-select.js +89 -0
  375. package/dist/lifecycle/gate-ledger.d.ts +24 -0
  376. package/dist/lifecycle/gate-ledger.d.ts.map +1 -1
  377. package/dist/lifecycle/gate-ledger.js +201 -0
  378. package/dist/lifecycle/gate-origin.js +117 -0
  379. package/dist/lifecycle/gate-pending.js +58 -0
  380. package/dist/lifecycle/git.js +1182 -0
  381. package/dist/lifecycle/identity.js +58 -0
  382. package/dist/lifecycle/index.js +26 -0
  383. package/dist/lifecycle/lease.js +283 -0
  384. package/dist/lifecycle/ledger-port.js +1 -0
  385. package/dist/lifecycle/legacy-digest.js +91 -0
  386. package/dist/lifecycle/live-diff.js +274 -0
  387. package/dist/lifecycle/local-note.js +15 -0
  388. package/dist/lifecycle/member-ledger.js +122 -0
  389. package/dist/lifecycle/observation-baseline.js +155 -0
  390. package/dist/lifecycle/observe.js +375 -0
  391. package/dist/lifecycle/oras-referrer-lookup.js +82 -0
  392. package/dist/lifecycle/override-ledger.js +54 -0
  393. package/dist/lifecycle/plan-digest.js +110 -0
  394. package/dist/lifecycle/plan-ledger.js +129 -0
  395. package/dist/lifecycle/receipt-plan.js +192 -0
  396. package/dist/lifecycle/release-ledger.js +155 -0
  397. package/dist/lifecycle/release-read.js +64 -0
  398. package/dist/lifecycle/replay.js +193 -0
  399. package/dist/lifecycle/rollback.js +102 -0
  400. package/dist/lifecycle/run-ledger.js +183 -0
  401. package/dist/lifecycle/scenario-eval.js +204 -0
  402. package/dist/lifecycle/scenario.js +214 -0
  403. package/dist/lifecycle/snapshot.js +239 -0
  404. package/dist/lifecycle/status.js +447 -0
  405. package/dist/lifecycle/symptoms.js +117 -0
  406. package/dist/lifecycle/teardown.js +255 -0
  407. package/dist/lifecycle/types.js +1 -0
  408. package/dist/lifecycle/unobserved-gate.js +33 -0
  409. package/dist/lifecycle/wave-plans.js +68 -0
  410. package/dist/lifecycle/work-item-id.js +36 -0
  411. package/dist/lifecycle/work-lease.js +393 -0
  412. package/dist/lint/component-checks.js +87 -0
  413. package/dist/lint/config.js +483 -0
  414. package/dist/lint/declarative.js +122 -0
  415. package/dist/lint/discover.d.ts.map +1 -1
  416. package/dist/lint/discover.js +138 -0
  417. package/dist/lint/engine.js +246 -0
  418. package/dist/lint/knowledge-checks.js +64 -0
  419. package/dist/lint/named-checks.js +22 -0
  420. package/dist/lint/output-checks.js +87 -0
  421. package/dist/lint/output-docs.js +163 -0
  422. package/dist/lint/parser.js +33 -0
  423. package/dist/lint/pipeline-change-gate.js +70 -0
  424. package/dist/lint/policy-import.d.ts +1 -1
  425. package/dist/lint/policy-import.d.ts.map +1 -1
  426. package/dist/lint/policy-import.js +62 -0
  427. package/dist/lint/policy-sandbox.js +86 -0
  428. package/dist/lint/policy.js +122 -0
  429. package/dist/lint/post-synth.js +87 -0
  430. package/dist/lint/presets/relaxed.json +26 -0
  431. package/dist/lint/presets/strict.json +24 -24
  432. package/dist/lint/receipt-checks.js +83 -0
  433. package/dist/lint/rule-loader.d.ts.map +1 -1
  434. package/dist/lint/rule-loader.js +89 -0
  435. package/dist/lint/rule-registry.js +86 -0
  436. package/dist/lint/rule.js +1 -0
  437. package/dist/lint/rules/__fixtures__/comp/comp000-discovery-error/broken-a.component.js +7 -0
  438. package/dist/lint/rules/__fixtures__/comp/comp000-discovery-error/broken-b.component.js +7 -0
  439. package/dist/lint/rules/__fixtures__/comp/comp001/fail/orphan-lib.component.js +9 -0
  440. package/dist/lint/rules/__fixtures__/comp/comp001/pass/emr-job.component.js +11 -0
  441. package/dist/lint/rules/__fixtures__/comp/comp001/pass/jar-lib.component.js +9 -0
  442. package/dist/lint/rules/__fixtures__/comp/comp002/fail/search-service.component.js +25 -0
  443. package/dist/lint/rules/__fixtures__/comp/comp002/pass/jar-lib.component.js +8 -0
  444. package/dist/lint/rules/__fixtures__/comp/comp002/pass/search-service.component.js +21 -0
  445. package/dist/lint/rules/__fixtures__/comp/comp003/fail/migrator.component.js +8 -0
  446. package/dist/lint/rules/__fixtures__/comp/comp003/pass/agent-turn.component.js +24 -0
  447. package/dist/lint/rules/__fixtures__/comp/comp003/pass/migrator.component.js +17 -0
  448. package/dist/lint/rules/__fixtures__/comp/comp003/pass/orders-table.component.js +8 -0
  449. package/dist/lint/rules/__fixtures__/comp/comp004/fail/neo4j-cluster.component.js +18 -0
  450. package/dist/lint/rules/__fixtures__/comp/comp004/pass/neo4j-cluster.component.js +20 -0
  451. package/dist/lint/rules/__fixtures__/comp/comp004/pass/orders-table.component.js +8 -0
  452. package/dist/lint/rules/__fixtures__/comp/comp005/fail/search-service.component.js +12 -0
  453. package/dist/lint/rules/__fixtures__/comp/comp005/pass/search-service.component.js +16 -0
  454. package/dist/lint/rules/__fixtures__/comp/comp006/fail/legacy-tool.component.js +8 -0
  455. package/dist/lint/rules/__fixtures__/comp/comp006/pass/legacy-tool.component.js +12 -0
  456. package/dist/lint/rules/__fixtures__/comp/comp007/fail/inventory-table.component.js +11 -0
  457. package/dist/lint/rules/__fixtures__/comp/comp007/fail/orders-table.component.js +11 -0
  458. package/dist/lint/rules/__fixtures__/comp/comp007/pass/orders-table.component.js +10 -0
  459. package/dist/lint/rules/__fixtures__/comp/comp007/pass/search-service.component.js +16 -0
  460. package/dist/lint/rules/comp/comp001-publishes-never-applies.js +69 -0
  461. package/dist/lint/rules/comp/comp002-dangling-wiring-ref.js +84 -0
  462. package/dist/lint/rules/comp/comp003-mutating-no-rollback.js +103 -0
  463. package/dist/lint/rules/comp/comp004-gate-needs-durable-runtime.js +70 -0
  464. package/dist/lint/rules/comp/comp005-capability-kind-is-noun.js +109 -0
  465. package/dist/lint/rules/comp/comp006-shell-needs-reason.js +49 -0
  466. package/dist/lint/rules/comp/comp007-composition-sprawl.js +64 -0
  467. package/dist/lint/rules/comp/index.js +33 -0
  468. package/dist/lint/rules/comp/support.js +129 -0
  469. package/dist/lint/rules/composite-scope.js +51 -0
  470. package/dist/lint/rules/cor017-composite-name-match.js +93 -0
  471. package/dist/lint/rules/cor018-composite-prefer-lexicon-type.js +136 -0
  472. package/dist/lint/rules/cor021-env-literal-name.js +100 -0
  473. package/dist/lint/rules/cor022-receipt-leaf.js +118 -0
  474. package/dist/lint/rules/cor024-receipt-secret-pointer.js +199 -0
  475. package/dist/lint/rules/declarable-naming-convention.js +62 -0
  476. package/dist/lint/rules/evl001-non-literal-expression.js +66 -0
  477. package/dist/lint/rules/evl002-control-flow-resource.js +69 -0
  478. package/dist/lint/rules/evl003-dynamic-property-access.js +44 -0
  479. package/dist/lint/rules/evl004-spread-non-const.js +94 -0
  480. package/dist/lint/rules/evl005-resource-block-body.js +41 -0
  481. package/dist/lint/rules/evl007-invalid-siblings.js +105 -0
  482. package/dist/lint/rules/evl009-composite-no-constant.js +158 -0
  483. package/dist/lint/rules/evl010-composite-no-transform.js +59 -0
  484. package/dist/lint/rules/evl011-symbolic-in-template.js +106 -0
  485. package/dist/lint/rules/export-required.js +115 -0
  486. package/dist/lint/rules/file-declarable-limit.js +88 -0
  487. package/dist/lint/rules/flat-declarations.js +62 -0
  488. package/dist/lint/rules/index.js +90 -0
  489. package/dist/lint/rules/no-cyclic-declarable-ref.js +138 -0
  490. package/dist/lint/rules/no-redundant-type-import.js +76 -0
  491. package/dist/lint/rules/no-redundant-value-cast.js +41 -0
  492. package/dist/lint/rules/no-string-ref.js +59 -0
  493. package/dist/lint/rules/no-unused-declarable-import.js +75 -0
  494. package/dist/lint/rules/no-unused-declarable.js +142 -0
  495. package/dist/lint/rules/op/index.js +28 -0
  496. package/dist/lint/rules/op/ops012-activity-contract.js +54 -0
  497. package/dist/lint/rules/op/ops013-step-output-ref.js +88 -0
  498. package/dist/lint/rules/op/ops014-converge-rule-refusals.js +114 -0
  499. package/dist/lint/rules/op/ops015-gate-approval.js +45 -0
  500. package/dist/lint/rules/op/support.js +11 -0
  501. package/dist/lint/rules/op/sys010-key-over-plain-http.js +100 -0
  502. package/dist/lint/rules/property-kind.js +56 -0
  503. package/dist/lint/rules/single-concern-file.js +71 -0
  504. package/dist/lint/rules/sys001-no-literal-key.js +60 -0
  505. package/dist/lint/selectors.js +168 -0
  506. package/dist/lint/suppressions.js +129 -0
  507. package/dist/lint/typescript.js +17 -0
  508. package/dist/live-endpoint.js +113 -0
  509. package/dist/live-read-session.js +76 -0
  510. package/dist/lsp/lexicon-providers.js +163 -0
  511. package/dist/lsp/types.js +5 -0
  512. package/dist/managed-fields.js +293 -0
  513. package/dist/mcp/types.js +4 -0
  514. package/dist/notify-verify.js +0 -1
  515. package/dist/notify.js +0 -1
  516. package/dist/notify.schema.json +72 -0
  517. package/dist/observation.js +266 -0
  518. package/dist/okf-read.js +134 -0
  519. package/dist/okf.js +275 -0
  520. package/dist/op/__fixtures__/decide-stub-backend.js +125 -0
  521. package/dist/op/activities/activity-contracts.d.ts +1 -1
  522. package/dist/op/activities/activity-contracts.js +313 -0
  523. package/dist/op/activities/apply.js +582 -0
  524. package/dist/op/activities/build.js +18 -0
  525. package/dist/op/activities/change-coverage.js +40 -0
  526. package/dist/op/activities/change-set.d.ts +8 -3
  527. package/dist/op/activities/change-set.d.ts.map +1 -1
  528. package/dist/op/activities/change-set.js +67 -0
  529. package/dist/op/activities/converge.js +492 -0
  530. package/dist/op/activities/decide.js +211 -0
  531. package/dist/op/activities/env-teardown.js +110 -0
  532. package/dist/op/activities/factory.js +591 -0
  533. package/dist/op/activities/forge-pull-request.js +82 -0
  534. package/dist/op/activities/gh-host.js +78 -0
  535. package/dist/op/activities/guard-validate.js +165 -0
  536. package/dist/op/activities/http-check.js +40 -0
  537. package/dist/op/activities/index.js +59 -0
  538. package/dist/op/activities/lexicon-upgrade.js +629 -0
  539. package/dist/op/activities/lifecycle.js +109 -0
  540. package/dist/op/activities/pipeline-audit.js +147 -0
  541. package/dist/op/activities/policy.js +26 -0
  542. package/dist/op/activities/predict-behaviour.js +341 -0
  543. package/dist/op/activities/propose-upgrade.js +165 -0
  544. package/dist/op/activities/reconcile.d.ts +28 -16
  545. package/dist/op/activities/reconcile.d.ts.map +1 -1
  546. package/dist/op/activities/reconcile.js +1011 -0
  547. package/dist/op/activities/shell.js +118 -0
  548. package/dist/op/activities/source-release.js +97 -0
  549. package/dist/op/activities/source-rollback.js +165 -0
  550. package/dist/op/activities/teardown.js +17 -0
  551. package/dist/op/activities/util.js +6 -0
  552. package/dist/op/activities/wait.js +51 -0
  553. package/dist/op/activities/work-evidence.js +35 -0
  554. package/dist/op/activities/workflow-audit.js +133 -0
  555. package/dist/op/activity-contract-registry.js +106 -0
  556. package/dist/op/activity-contract.js +232 -0
  557. package/dist/op/activity-failure.js +31 -0
  558. package/dist/op/activity-profiles.js +129 -0
  559. package/dist/op/activity-registry.js +105 -0
  560. package/dist/op/activity-runtime.js +28 -0
  561. package/dist/op/builders.d.ts +1 -1
  562. package/dist/op/builders.d.ts.map +1 -1
  563. package/dist/op/builders.js +801 -0
  564. package/dist/op/change-signal.js +118 -0
  565. package/dist/op/composites/apply-op.js +151 -0
  566. package/dist/op/composites/behaviour-op.js +65 -0
  567. package/dist/op/composites/converge-op.js +141 -0
  568. package/dist/op/composites/index.js +19 -0
  569. package/dist/op/composites/lexicon-upgrade-op.js +107 -0
  570. package/dist/op/composites/pipeline-audit-op.js +50 -0
  571. package/dist/op/composites/reconcile-op.js +84 -0
  572. package/dist/op/composites/watch-op.js +69 -0
  573. package/dist/op/composites/workflow-audit-op.js +57 -0
  574. package/dist/op/converge-rule.js +205 -0
  575. package/dist/op/cron.js +146 -0
  576. package/dist/op/decide-backend.js +181 -0
  577. package/dist/op/decide-config.js +68 -0
  578. package/dist/op/decide-read-inputs.js +141 -0
  579. package/dist/op/discover.d.ts.map +1 -1
  580. package/dist/op/discover.js +213 -0
  581. package/dist/op/duration.js +21 -0
  582. package/dist/op/emulator-freshness.js +80 -0
  583. package/dist/op/emulator-lifecycle.js +269 -0
  584. package/dist/op/factory.js +83 -0
  585. package/dist/op/gate-approval.js +172 -0
  586. package/dist/op/gate-name.js +86 -0
  587. package/dist/op/gate-plan-context.js +167 -0
  588. package/dist/op/gate-point-run.js +67 -0
  589. package/dist/op/gate-point.js +111 -0
  590. package/dist/op/gate-resume-plan.js +155 -0
  591. package/dist/op/gate-resume.js +31 -0
  592. package/dist/op/gate-review.js +290 -0
  593. package/dist/op/gate-run-locator.js +85 -0
  594. package/dist/op/gate-summary.js +100 -0
  595. package/dist/op/gate-wait.js +56 -0
  596. package/dist/op/gate.js +449 -0
  597. package/dist/op/generate-pipeline.js +124 -0
  598. package/dist/op/index.js +51 -0
  599. package/dist/op/lifecycle-receipt-store.js +45 -0
  600. package/dist/op/local-executor.js +1164 -0
  601. package/dist/op/local-output.js +140 -0
  602. package/dist/op/op-ir.js +317 -0
  603. package/dist/op/op-verb-class.js +131 -0
  604. package/dist/op/op-waves-notify.js +136 -0
  605. package/dist/op/op-waves-run.d.ts +1 -1
  606. package/dist/op/op-waves-run.d.ts.map +1 -1
  607. package/dist/op/op-waves-run.js +1657 -0
  608. package/dist/op/op-waves.js +409 -0
  609. package/dist/op/operator.js +743 -0
  610. package/dist/op/receipt-store.js +94 -0
  611. package/dist/op/resource.js +23 -0
  612. package/dist/op/run-context.js +28 -0
  613. package/dist/op/run-live.js +256 -0
  614. package/dist/op/runtime.js +21 -0
  615. package/dist/op/runtimes/local.js +195 -0
  616. package/dist/op/source-archive.js +131 -0
  617. package/dist/op/step-output-ref.js +397 -0
  618. package/dist/op/steward-beside.js +169 -0
  619. package/dist/op/steward-points.js +138 -0
  620. package/dist/op/steward-turn.js +59 -0
  621. package/dist/op/steward.js +317 -0
  622. package/dist/op/types.js +25 -0
  623. package/dist/op/work-lease-decl.js +81 -0
  624. package/dist/op/work-lease-run.js +393 -0
  625. package/dist/override-gate.js +284 -0
  626. package/dist/owner-chain.js +74 -0
  627. package/dist/ownership.js +92 -0
  628. package/dist/params.js +61 -0
  629. package/dist/plan-summary.js +711 -0
  630. package/dist/point-decision.js +211 -0
  631. package/dist/pr-forge.js +810 -0
  632. package/dist/pr-loop.js +203 -0
  633. package/dist/project-codegen.js +191 -0
  634. package/dist/project-root.js +74 -0
  635. package/dist/project-validation.js +50 -0
  636. package/dist/provenance.js +157 -0
  637. package/dist/pseudo-parameter.js +40 -0
  638. package/dist/reconcile.js +450 -0
  639. package/dist/resource-attributes.js +33 -0
  640. package/dist/runtime-adapter.js +77 -0
  641. package/dist/runtime.js +66 -0
  642. package/dist/secret-materialization.js +153 -0
  643. package/dist/secret-provenance.js +250 -0
  644. package/dist/serializer-walker.js +112 -0
  645. package/dist/serializer.js +1 -0
  646. package/dist/sort.js +45 -0
  647. package/dist/stack-output.js +115 -0
  648. package/dist/telemetry-attribution.js +149 -0
  649. package/dist/terraform/__fixtures__/build-graph.js +63 -0
  650. package/dist/terraform/adopt-state.js +40 -0
  651. package/dist/terraform/aws-resources.js +384 -0
  652. package/dist/terraform/bridge.js +204 -0
  653. package/dist/terraform/carve-provider.js +155 -0
  654. package/dist/terraform/carve.js +138 -0
  655. package/dist/terraform/data-source-shape.js +57 -0
  656. package/dist/terraform/emit-source.js +36 -0
  657. package/dist/terraform/excise.js +123 -0
  658. package/dist/terraform/graduate.js +128 -0
  659. package/dist/terraform/graph.js +435 -0
  660. package/dist/terraform/hcl2json.js +60 -0
  661. package/dist/terraform/manifest.js +149 -0
  662. package/dist/terraform/parse.js +81 -0
  663. package/dist/terraform/providers/aws.js +146 -0
  664. package/dist/terraform/providers/gcp.js +294 -0
  665. package/dist/terraform/providers/index.js +17 -0
  666. package/dist/terraform/providers/kubernetes.js +253 -0
  667. package/dist/terraform/score.js +151 -0
  668. package/dist/terraform/state.js +67 -0
  669. package/dist/terraform/tier-map.js +74 -0
  670. package/dist/terraform/types.js +13 -0
  671. package/dist/terraform/unified-diff.js +132 -0
  672. package/dist/testing.js +295 -0
  673. package/dist/toml-emit.js +153 -0
  674. package/dist/toml-parse.js +355 -0
  675. package/dist/toml-utils.js +60 -0
  676. package/dist/toml.js +428 -0
  677. package/dist/ts-loader.d.ts +46 -0
  678. package/dist/ts-loader.d.ts.map +1 -0
  679. package/dist/ts-loader.js +103 -0
  680. package/dist/types.js +1 -0
  681. package/dist/utils.js +61 -0
  682. package/dist/validation.js +20 -0
  683. package/dist/vendor/js-yaml-load.js +1662 -0
  684. package/dist/workspace/__fixtures__/contract-repo.d.ts +7 -0
  685. package/dist/workspace/__fixtures__/contract-repo.d.ts.map +1 -1
  686. package/dist/workspace/__fixtures__/contract-repo.js +119 -0
  687. package/dist/workspace/__fixtures__/decide-workspace.js +112 -0
  688. package/dist/workspace/__fixtures__/sessions.d.ts +1 -1
  689. package/dist/workspace/__fixtures__/sessions.d.ts.map +1 -1
  690. package/dist/workspace/__fixtures__/sessions.js +91 -0
  691. package/dist/workspace/__fixtures__/two-member-workspace.js +34 -0
  692. package/dist/workspace/agent-cli.js +99 -0
  693. package/dist/workspace/agent.schema.json +390 -0
  694. package/dist/workspace/answers-ledger.js +89 -0
  695. package/dist/workspace/box-cli.js +142 -0
  696. package/dist/workspace/box-factory-write.js +196 -0
  697. package/dist/workspace/box-factory-write.schema.json +342 -0
  698. package/dist/workspace/box-factory.js +88 -0
  699. package/dist/workspace/box-intent.js +126 -0
  700. package/dist/workspace/box-isolation.js +169 -0
  701. package/dist/workspace/box-listing-write.schema.json +204 -0
  702. package/dist/workspace/box-listing.js +298 -0
  703. package/dist/workspace/box-publish.js +352 -0
  704. package/dist/workspace/box-publish.schema.json +498 -0
  705. package/dist/workspace/box-services.js +39 -0
  706. package/dist/workspace/broker-protocol.js +359 -0
  707. package/dist/workspace/broker-protocol.schema.json +228 -0
  708. package/dist/workspace/change-set.schema.json +462 -0
  709. package/dist/workspace/changes-cli.js +56 -0
  710. package/dist/workspace/changes.js +446 -0
  711. package/dist/workspace/changes.schema.json +642 -0
  712. package/dist/workspace/chant-migrations/chud-lexicon-exit-fly-site.js +198 -0
  713. package/dist/workspace/chant-migrations/chud-lexicon-exit-live-names.js +155 -0
  714. package/dist/workspace/chant-migrations/chud-lexicon-exit-rollback.js +214 -0
  715. package/dist/workspace/chant-migrations/chud-lexicon-exit-ship-inputs.js +180 -0
  716. package/dist/workspace/chant-migrations/chud-lexicon-exit-ship-policy.js +99 -0
  717. package/dist/workspace/chant-migrations/chud-lexicon-exit.js +847 -0
  718. package/dist/workspace/chant-migrations.js +113 -0
  719. package/dist/workspace/check.schema.json +433 -0
  720. package/dist/workspace/checks/box-isolation.js +62 -0
  721. package/dist/workspace/checks/boxes.js +491 -0
  722. package/dist/workspace/checks/diagrams.js +104 -0
  723. package/dist/workspace/checks/generated.js +419 -0
  724. package/dist/workspace/checks/ledgers.js +212 -0
  725. package/dist/workspace/checks/links.js +191 -0
  726. package/dist/workspace/checks/live.js +100 -0
  727. package/dist/workspace/checks/pipelines.js +188 -0
  728. package/dist/workspace/checks/records.js +169 -0
  729. package/dist/workspace/checks.js +426 -0
  730. package/dist/workspace/ci-green-cli.js +125 -0
  731. package/dist/workspace/ci-green-forge.js +83 -0
  732. package/dist/workspace/ci-green-workflow.d.ts +3 -1
  733. package/dist/workspace/ci-green-workflow.d.ts.map +1 -1
  734. package/dist/workspace/ci-green-workflow.js +202 -0
  735. package/dist/workspace/ci-green.js +325 -0
  736. package/dist/workspace/ci-last-green.schema.json +158 -0
  737. package/dist/workspace/class-data.d.ts +84 -0
  738. package/dist/workspace/class-data.d.ts.map +1 -0
  739. package/dist/workspace/class-data.js +238 -0
  740. package/dist/workspace/compose-graph.js +235 -0
  741. package/dist/workspace/compose-reports.js +146 -0
  742. package/dist/workspace/composites.js +264 -0
  743. package/dist/workspace/composites.schema.json +617 -0
  744. package/dist/workspace/conformance/__fixture__/app/package.json +7 -0
  745. package/dist/workspace/conformance/__fixture__/app/src/server.mjs +29 -0
  746. package/dist/workspace/conformance/__fixture__/decisions/decision.kind.mjs +54 -0
  747. package/dist/workspace/conformance/__fixture__/decisions/decision.schema.json +512 -0
  748. package/dist/workspace/conformance/__fixture__/decisions/fix-001-how-the-app-is-deployed.md +40 -0
  749. package/dist/workspace/conformance/__fixture__/delivery/chant.config.ts +7 -0
  750. package/dist/workspace/conformance/__fixture__/delivery/lexicon/index.ts +26 -0
  751. package/dist/workspace/conformance/__fixture__/delivery/package.json +7 -0
  752. package/dist/workspace/conformance/__fixture__/delivery/src/app.component.ts +14 -0
  753. package/dist/workspace/conformance/__fixture__/delivery/src/app.ts +4 -0
  754. package/dist/workspace/conformance/__fixtures__/reference-broker.js +205 -0
  755. package/dist/workspace/conformance/__writer_fixture__/answers/answer.kind.mjs +32 -0
  756. package/dist/workspace/conformance/__writer_fixture__/answers/answer.schema.json +135 -0
  757. package/dist/workspace/conformance/__writer_fixture__/contracts/contract.kind.mjs +37 -0
  758. package/dist/workspace/conformance/__writer_fixture__/contracts/contract.schema.json +213 -0
  759. package/dist/workspace/conformance/__writer_fixture__/decisions/points.json +228 -0
  760. package/dist/workspace/conformance/__writer_fixture__/drivers/driver.kind.mjs +15 -0
  761. package/dist/workspace/conformance/__writer_fixture__/drivers/driver.schema.json +38 -0
  762. package/dist/workspace/conformance/__writer_fixture__/sessions/session.kind.mjs +54 -0
  763. package/dist/workspace/conformance/__writer_fixture__/sessions/session.schema.json +289 -0
  764. package/dist/workspace/conformance/__writer_fixture__/work/W-001-the-writer-suite-works-this-item.md +25 -0
  765. package/dist/workspace/conformance/__writer_fixture__/work/work.kind.mjs +60 -0
  766. package/dist/workspace/conformance/__writer_fixture__/work/work.schema.json +603 -0
  767. package/dist/workspace/conformance/broker.js +736 -0
  768. package/dist/workspace/conformance/index.js +673 -0
  769. package/dist/workspace/conformance/vitest.js +108 -0
  770. package/dist/workspace/conformance/writer.js +1080 -0
  771. package/dist/workspace/decide.js +879 -0
  772. package/dist/workspace/decision-points.schema.json +135 -135
  773. package/dist/workspace/declaration-write-cli.js +74 -0
  774. package/dist/workspace/declaration-write.js +238 -0
  775. package/dist/workspace/declaration.js +938 -0
  776. package/dist/workspace/declaration.schema.json +1413 -1413
  777. package/dist/workspace/declared-kinds.js +106 -0
  778. package/dist/workspace/environments.js +164 -0
  779. package/dist/workspace/evidence.schema.json +279 -0
  780. package/dist/workspace/export-cli.js +185 -0
  781. package/dist/workspace/export.js +499 -0
  782. package/dist/workspace/factory-rules.js +175 -0
  783. package/dist/workspace/generated-files.js +92 -0
  784. package/dist/workspace/glob.js +45 -0
  785. package/dist/workspace/graph-cache.js +352 -0
  786. package/dist/workspace/graph-cli.js +348 -0
  787. package/dist/workspace/graph.schema.json +1338 -0
  788. package/dist/workspace/identity-gate-rule.js +75 -0
  789. package/dist/workspace/identity.js +126 -0
  790. package/dist/workspace/import.js +283 -0
  791. package/dist/workspace/init.js +620 -0
  792. package/dist/workspace/intent-cli.js +263 -0
  793. package/dist/workspace/intent-joins.js +201 -0
  794. package/dist/workspace/intent-record.js +352 -0
  795. package/dist/workspace/intent-record.schema.json +864 -0
  796. package/dist/workspace/intent.js +1582 -0
  797. package/dist/workspace/intent.schema.json +2420 -0
  798. package/dist/workspace/json-edit.js +221 -0
  799. package/dist/workspace/jsonc.js +235 -0
  800. package/dist/workspace/kind-readers.js +103 -0
  801. package/dist/workspace/kinds.d.ts +3 -7
  802. package/dist/workspace/kinds.d.ts.map +1 -1
  803. package/dist/workspace/kinds.js +553 -0
  804. package/dist/workspace/lexicon-symbols.js +112 -0
  805. package/dist/workspace/lineage-adopt-cli.js +102 -0
  806. package/dist/workspace/lineage-adopt.js +360 -0
  807. package/dist/workspace/lineage-check.js +282 -0
  808. package/dist/workspace/lineage-cli.js +121 -0
  809. package/dist/workspace/lineage-hash-index.js +284 -0
  810. package/dist/workspace/lineage-init.js +507 -0
  811. package/dist/workspace/lineage-lock.js +387 -0
  812. package/dist/workspace/lineage-merge.js +45 -0
  813. package/dist/workspace/lineage-migrations.d.ts.map +1 -1
  814. package/dist/workspace/lineage-migrations.js +329 -0
  815. package/dist/workspace/lineage-provenance.js +36 -0
  816. package/dist/workspace/lineage-update.js +138 -0
  817. package/dist/workspace/lineage-upgrade-cli.js +131 -0
  818. package/dist/workspace/lineage-upgrade.js +889 -0
  819. package/dist/workspace/lineage-vendor.js +171 -0
  820. package/dist/workspace/lineage-version.js +128 -0
  821. package/dist/workspace/lineage-versions.js +275 -0
  822. package/dist/workspace/links.js +330 -0
  823. package/dist/workspace/ls-generated.js +95 -0
  824. package/dist/workspace/ls.js +277 -0
  825. package/dist/workspace/ls.schema.json +333 -0
  826. package/dist/workspace/member-commands.js +508 -0
  827. package/dist/workspace/member-handles.js +216 -0
  828. package/dist/workspace/member-pipeline.js +258 -0
  829. package/dist/workspace/member-run.js +117 -0
  830. package/dist/workspace/member-write.schema.json +193 -0
  831. package/dist/workspace/nested-graph.js +156 -0
  832. package/dist/workspace/nesting.js +38 -0
  833. package/dist/workspace/patch.js +301 -0
  834. package/dist/workspace/patch.schema.json +321 -0
  835. package/dist/workspace/pin-cli.js +55 -0
  836. package/dist/workspace/pin-integrity.js +100 -0
  837. package/dist/workspace/plan-summary.schema.json +155 -0
  838. package/dist/workspace/point-answer.schema.json +129 -129
  839. package/dist/workspace/points-cli.js +291 -0
  840. package/dist/workspace/points-write.schema.json +660 -0
  841. package/dist/workspace/points.js +521 -0
  842. package/dist/workspace/points.schema.json +916 -0
  843. package/dist/workspace/pr-report.schema.json +108 -0
  844. package/dist/workspace/principal-classes.d.ts +4 -11
  845. package/dist/workspace/principal-classes.d.ts.map +1 -1
  846. package/dist/workspace/principal-classes.js +162 -0
  847. package/dist/workspace/reason-codes.js +326 -0
  848. package/dist/workspace/record-assets.js +151 -0
  849. package/dist/workspace/record-decided.js +105 -0
  850. package/dist/workspace/record-sessions.js +96 -0
  851. package/dist/workspace/record-source.js +114 -0
  852. package/dist/workspace/records-amend.schema.json +284 -0
  853. package/dist/workspace/records-checkout.js +76 -0
  854. package/dist/workspace/records-cli.js +707 -0
  855. package/dist/workspace/records-close.js +116 -0
  856. package/dist/workspace/records-close.schema.json +278 -0
  857. package/dist/workspace/records-new.schema.json +202 -0
  858. package/dist/workspace/records-review.schema.json +367 -0
  859. package/dist/workspace/records-since.js +317 -0
  860. package/dist/workspace/records-since.schema.json +218 -0
  861. package/dist/workspace/records-write.js +1229 -0
  862. package/dist/workspace/records.js +1345 -0
  863. package/dist/workspace/records.schema.json +604 -0
  864. package/dist/workspace/reference-kinds/app/package.json +10 -0
  865. package/dist/workspace/reference-kinds/app/workspace-kinds.json +45 -0
  866. package/dist/workspace/returns.js +303 -0
  867. package/dist/workspace/root-refusal.js +75 -0
  868. package/dist/workspace/run-statement.schema.json +622 -0
  869. package/dist/workspace/runs-cli.js +584 -0
  870. package/dist/workspace/runs-write.schema.json +994 -0
  871. package/dist/workspace/runs.js +708 -0
  872. package/dist/workspace/runs.schema.json +1257 -0
  873. package/dist/workspace/runtimes.js +76 -0
  874. package/dist/workspace/session-kinds.js +83 -0
  875. package/dist/workspace/signers.schema.json +206 -0
  876. package/dist/workspace/source-block.js +125 -0
  877. package/dist/workspace/squash.js +177 -0
  878. package/dist/workspace/status-gates.js +162 -0
  879. package/dist/workspace/status-stewards.js +330 -0
  880. package/dist/workspace/status.js +514 -0
  881. package/dist/workspace/status.schema.json +998 -0
  882. package/dist/workspace/steward-index.js +191 -0
  883. package/dist/workspace/symbols.js +188 -0
  884. package/dist/workspace/template-manifest.js +227 -0
  885. package/dist/workspace/template-pins.js +115 -0
  886. package/dist/workspace/trailer-joins.js +65 -0
  887. package/dist/workspace/trailers.js +111 -0
  888. package/dist/workspace/tree.js +136 -0
  889. package/dist/workspace/trust/attestor.js +63 -0
  890. package/dist/workspace/trust/dsse.js +108 -0
  891. package/dist/workspace/trust/evidence-cli.js +175 -0
  892. package/dist/workspace/trust/evidence.js +161 -0
  893. package/dist/workspace/trust/gate-seal.js +226 -0
  894. package/dist/workspace/trust/policy.js +190 -0
  895. package/dist/workspace/trust/provenance.js +180 -0
  896. package/dist/workspace/trust/rotation.js +298 -0
  897. package/dist/workspace/trust/run-commits.js +101 -0
  898. package/dist/workspace/trust/run-statement.js +182 -0
  899. package/dist/workspace/trust/seal.js +272 -0
  900. package/dist/workspace/trust/signers-cli.js +152 -0
  901. package/dist/workspace/trust/ssh-commit.js +178 -0
  902. package/dist/workspace/trust/test-repo.js +118 -0
  903. package/dist/workspace/trust/trust-files.js +193 -0
  904. package/dist/workspace/trust/verify-cli.js +63 -0
  905. package/dist/workspace/trust/verify.js +192 -0
  906. package/dist/workspace/which-chant.js +141 -0
  907. package/dist/workspace/wip-cli.js +95 -0
  908. package/dist/workspace/wip-write.schema.json +504 -0
  909. package/dist/workspace/wip.js +631 -0
  910. package/dist/workspace/wip.schema.json +334 -0
  911. package/dist/workspace/work-cli.js +448 -0
  912. package/dist/workspace/work-evidence.js +122 -0
  913. package/dist/workspace/work-evidence.schema.json +222 -0
  914. package/dist/workspace/work-history.schema.json +421 -0
  915. package/dist/workspace/work-lease.schema.json +246 -0
  916. package/dist/workspace/work-size.js +77 -0
  917. package/dist/workspace/work.js +283 -0
  918. package/dist/workspace/workspace-kinds.schema.json +259 -259
  919. package/dist/workspace/workspace-principals.schema.json +53 -53
  920. package/dist/workspace/write-lock-cli.js +105 -0
  921. package/dist/workspace/write-lock.js +424 -0
  922. package/dist/workspace/write-lock.schema.json +172 -0
  923. package/dist/workspace/write-scope.js +484 -0
  924. package/dist/yaml.js +459 -0
  925. package/package.json +2 -2
  926. package/src/build.ts +8 -0
  927. package/src/change-set.test.ts +48 -0
  928. package/src/change-set.ts +52 -2
  929. package/src/ci-jobs.test.ts +49 -0
  930. package/src/ci-jobs.ts +515 -0
  931. package/src/cli/commands/check-lexicon-plugin.ts +2 -0
  932. package/src/cli/commands/lint.ts +3 -0
  933. package/src/cli/commands/migrate.ts +2 -0
  934. package/src/cli/compiled-cli.e2e.test.ts +165 -0
  935. package/src/cli/main.ts +121 -27
  936. package/src/cli/mcp/workspace-tools.ts +1 -1
  937. package/src/codegen/pinned-upgrade.ts +2 -0
  938. package/src/components/discover.ts +2 -0
  939. package/src/components/pr-pipeline.ts +3 -2
  940. package/src/components/sandbox/driver.ts +3 -1
  941. package/src/composite.ts +15 -3
  942. package/src/config-import.ts +3 -0
  943. package/src/discovery/collect.ts +15 -1
  944. package/src/discovery/fold-build-parity.test.ts +71 -0
  945. package/src/discovery/fold-import.ts +25 -127
  946. package/src/discovery/fold-project-provenance.test.ts +21 -4
  947. package/src/discovery/host-composite.ts +101 -0
  948. package/src/discovery/import.ts +2 -0
  949. package/src/discovery/index.ts +11 -1
  950. package/src/discovery/sandbox/driver.ts +3 -1
  951. package/src/gate-decision-bundle.test.ts +5 -4
  952. package/src/gate-decision-identity.test.ts +59 -6
  953. package/src/gate-decision.ts +81 -21
  954. package/src/lexicon-module.ts +3 -0
  955. package/src/lifecycle/gate-audit.test.ts +55 -1
  956. package/src/lifecycle/gate-audit.ts +116 -3
  957. package/src/lifecycle/gate-ledger.test.ts +20 -1
  958. package/src/lifecycle/gate-ledger.ts +33 -1
  959. package/src/lint/config.ts +1 -1
  960. package/src/lint/discover.ts +28 -8
  961. package/src/lint/policy-import.ts +4 -1
  962. package/src/lint/rule-loader.ts +2 -0
  963. package/src/op/activities/activity-contracts.ts +1 -1
  964. package/src/op/activities/change-set.ts +11 -3
  965. package/src/op/activities/lexicon-upgrade.test.ts +2 -0
  966. package/src/op/activities/reconcile.test.ts +62 -2
  967. package/src/op/activities/reconcile.ts +46 -22
  968. package/src/op/builders.ts +1 -1
  969. package/src/op/discover.ts +3 -0
  970. package/src/op/op-waves-run.ts +4 -3
  971. package/src/op/op-waves.test.ts +2 -0
  972. package/src/op/work-lease-run.test.ts +12 -4
  973. package/src/ts-loader.test.ts +23 -0
  974. package/src/ts-loader.ts +122 -0
  975. package/src/workspace/__fixtures__/contract-repo.ts +17 -1
  976. package/src/workspace/__fixtures__/sessions.ts +3 -3
  977. package/src/workspace/change-set.schema.json +5 -3
  978. package/src/workspace/chant-migrations/chud-lexicon-exit.ts +1 -1
  979. package/src/workspace/ci-green-workflow.test.ts +4 -4
  980. package/src/workspace/ci-green-workflow.ts +5 -2
  981. package/src/workspace/class-data.test.ts +64 -0
  982. package/src/workspace/class-data.ts +236 -0
  983. package/src/workspace/declaration.ts +1 -1
  984. package/src/workspace/kinds.ts +7 -36
  985. package/src/workspace/lineage-migrations.ts +2 -0
  986. package/src/workspace/points.ts +1 -1
  987. package/src/workspace/principal-classes.ts +12 -95
  988. package/src/workspace/records-formats.test.ts +7 -2
  989. package/src/workspace/records-sessions-write.test.ts +3 -3
  990. package/src/workspace/status-contract.e2e.test.ts +37 -0
  991. package/src/workspace/status-contract.test.ts +23 -21
@@ -0,0 +1,1182 @@
1
+ /**
2
+ * Git plumbing operations for the chant/lifecycle orphan branch.
3
+ *
4
+ * All operations use git plumbing commands — no checkout, no branch switching,
5
+ * no working tree changes.
6
+ */
7
+ import { getRuntime } from "../runtime-adapter.js";
8
+ import { memberLedgerPrefix, resolveMemberLedger } from "./member-ledger.js";
9
+ import { LIFECYCLE_LOCAL_NOTE } from "./local-note.js";
10
+ const STATE_BRANCH = "chant/lifecycle";
11
+ /**
12
+ * The branch path of `dir` for the project at `cwd` (#2538): `dir` itself at
13
+ * level 0 and for the root member `.`, `_members/<member>/<dir>` for any
14
+ * other workspace member. Every store in this module and the ones built on it
15
+ * (releases, snapshots, observation baselines, runs, converge records, gates,
16
+ * build records) goes through here, so none of them names the prefix itself.
17
+ * See ./member-ledger.ts for who counts as a member.
18
+ */
19
+ export async function ledgerDir(dir, opts) {
20
+ return `${await ledgerPrefixOf(opts)}${dir}`;
21
+ }
22
+ async function ledgerPrefixOf(opts) {
23
+ if (opts?.ledgerPrefix !== undefined)
24
+ return opts.ledgerPrefix;
25
+ if (opts?.member !== undefined) {
26
+ return memberLedgerPrefix((typeof opts.member === "string" ? [opts.member] : [...opts.member]).filter((m) => m !== "."));
27
+ }
28
+ return (await resolveMemberLedger(opts?.cwd ?? process.cwd())).prefix;
29
+ }
30
+ /**
31
+ * The identity chant's ledger commits fall back to when the checkout has none
32
+ * of its own (#2301).
33
+ *
34
+ * A CI checkout is the ordinary case here, not an exotic one: GitLab CI,
35
+ * GitHub Actions and Forgejo Actions all clone without `user.email` or
36
+ * `user.name`, and `git commit-tree` refuses outright rather than inventing
37
+ * one — `fatal: unable to auto-detect email address`. Every write in this
38
+ * module goes through `commit-tree`, so before #2301 a gate's pending fact,
39
+ * a run record, a snapshot and a release record all died the same way the
40
+ * moment they ran anywhere but a developer's laptop.
41
+ *
42
+ * This is a *fallback*, applied per-invocation via `git -c` and field by
43
+ * field: only the half the checkout cannot supply is filled in. A configured
44
+ * `user.name` or `user.email` — a developer's, or a CI job that sets one
45
+ * deliberately — is left exactly as it is and still authors these commits, and
46
+ * nothing is written to the repository's config either way. Override the
47
+ * fallback with `CHANT_LIFECYCLE_COMMITTER_NAME` / `_EMAIL` when a project
48
+ * wants its ledger commits attributed to a bot account it controls.
49
+ */
50
+ const LEDGER_COMMITTER_NAME = "chant";
51
+ const LEDGER_COMMITTER_EMAIL = "chant@localhost";
52
+ /**
53
+ * `git -c` arguments that give {@link writeBlobToPath}'s `commit-tree` a
54
+ * committer when — and only when — the checkout cannot supply one itself.
55
+ * Empty for any checkout that has an identity, which keeps every existing
56
+ * caller's commits byte-identical to what they were before #2301.
57
+ */
58
+ async function ledgerCommitIdentityArgs(cwd) {
59
+ const rt = getRuntime();
60
+ const probe = await rt.spawn(["git", "var", "GIT_COMMITTER_IDENT"], { cwd, env: C_LOCALE_ENV });
61
+ if (probe.exitCode === 0)
62
+ return [];
63
+ // Per-field, not all-or-nothing (#2309 review). `git var` fails if *either*
64
+ // half is missing, so filling in both would overwrite a configured
65
+ // `user.name` in the checkout that has a name but no email — which is not
66
+ // what "a configured identity is left exactly as it is" promises.
67
+ const args = [];
68
+ for (const [key, envVar, fallback] of [
69
+ ["user.name", "CHANT_LIFECYCLE_COMMITTER_NAME", LEDGER_COMMITTER_NAME],
70
+ ["user.email", "CHANT_LIFECYCLE_COMMITTER_EMAIL", LEDGER_COMMITTER_EMAIL],
71
+ ]) {
72
+ const configured = await rt.spawn(["git", "config", "--get", key], { cwd, env: C_LOCALE_ENV });
73
+ if (configured.exitCode === 0 && configured.stdout.trim() !== "")
74
+ continue;
75
+ args.push("-c", `${key}=${process.env[envVar]?.trim() || fallback}`);
76
+ }
77
+ return args;
78
+ }
79
+ /**
80
+ * Prefix every failure out of the ledger write path with what was being
81
+ * written and where (#2301 deliverable 2). `git commit-tree failed: <stderr>`
82
+ * on its own does not say that the thing that failed was chant recording a
83
+ * fact on its own branch, and the caller that swallows it is one stack frame
84
+ * away from printing nothing at all.
85
+ */
86
+ function ledgerWriteError(stage, path, stderr) {
87
+ return new Error(`cannot write ${path} on the ${STATE_BRANCH} branch: ${stage} failed: ${(stderr ?? "").trim()}`);
88
+ }
89
+ /**
90
+ * Bounded retry budget for {@link writeBlobToPath}'s own internal CAS retry
91
+ * (#1959 finding 1). A conflict caused by some *other* env/file entry
92
+ * changing concurrently (the ordinary case — e.g. two operator ticks
93
+ * targeting different envs) is safe to resolve by simply rebuilding the tree
94
+ * against the new tip and retrying; this bounds how many times it does that
95
+ * before giving up and surfacing the conflict.
96
+ */
97
+ const WRITE_BLOB_RETRY_ATTEMPTS = 5;
98
+ /**
99
+ * Write a blob to an arbitrary `<environment>/<filename>` path on the orphan
100
+ * branch, preserving every other env/file entry already on the branch.
101
+ *
102
+ * Pipeline: hash-object → mktree → commit-tree → update-ref. Factored out of
103
+ * `writeSnapshot` so the release ledger (#568, epic #551 "Build & deploy
104
+ * observability") can reuse the identical git-plumbing path for a different
105
+ * filename (`releases.jsonl`) under the same env directory, rather than a
106
+ * parallel storage mechanism.
107
+ *
108
+ * Exported (not just used internally) so ./build-ledger-store.ts (#609) can
109
+ * reuse this exact plumbing for a top-level directory that isn't really an
110
+ * "environment" (`_builds`, keyed by manifest digest rather than env name) —
111
+ * a build archive is promoted by digest across environments, never owned by
112
+ * one, so it belongs in its own digest-keyed namespace on the same orphan
113
+ * branch rather than duplicated per env. The parameter is still named
114
+ * `environment` because it is literally the first path segment / root-tree
115
+ * entry name this function's tree-building logic groups by; callers outside
116
+ * this module that pass a non-env value (like `_builds`) are relying on that
117
+ * generic behavior, not on any env-specific semantics.
118
+ *
119
+ * `environment` is relative to the project's ledger (#2538): a workspace
120
+ * member's write lands under `_members/<member>/` ({@link ledgerDir}). The
121
+ * write itself is {@link writeLedgerFiles} with one file.
122
+ */
123
+ export async function writeBlobToPath(environment, filename, content, commitMessage, opts) {
124
+ const dir = await ledgerDir(environment, opts);
125
+ return writeLedgerFiles([{ path: `${dir}/${filename}`, content, ...(opts?.expectPriorPathSha !== undefined ? { expectPriorSha: opts.expectPriorPathSha } : {}) }], commitMessage, opts);
126
+ }
127
+ /**
128
+ * Write several files to the branch in one commit (#2538). Paths are full
129
+ * branch paths, so one commit can write the ledgers of several members, for
130
+ * example `_members/api/prod/releases.jsonl` and
131
+ * `_members/web/prod/releases.jsonl` together. {@link writeBlobToPath} is the
132
+ * one-file case with the project's own member prefix applied.
133
+ *
134
+ * Pipeline, per attempt: read the directories each path passes through at
135
+ * the branch tip, replace the entries, rebuild the touched trees bottom-up
136
+ * with `git mktree`, `commit-tree` on that tip, then CAS-update the ref.
137
+ * Every entry the write does not touch is carried over as it is, so the tree
138
+ * of an unchanged directory keeps its sha.
139
+ */
140
+ export async function writeLedgerFiles(files, commitMessage, opts) {
141
+ const rt = getRuntime();
142
+ const cwd = opts?.cwd;
143
+ if (files.length === 0)
144
+ throw new Error("writeLedgerFiles needs at least one file");
145
+ // Failures name the first path; one file is the ordinary case.
146
+ const path = files[0].path;
147
+ for (const f of files) {
148
+ const segs = f.path.split("/");
149
+ if (segs.length < 2 || segs.some((seg) => seg === "")) {
150
+ throw new Error(`cannot write ${f.path} on the ${STATE_BRANCH} branch: a ledger path is <dir>/<file>`);
151
+ }
152
+ }
153
+ // 1. Write the blobs — hash-object reads from stdin. Fed directly via
154
+ // spawn's stdin (no shell involved), so content is written byte-for-byte:
155
+ // no `sh` `echo` reinterpreting backslash-escape sequences (e.g. `\n`
156
+ // inside JSON, as in a serialized
157
+ // kubectl.kubernetes.io/last-applied-configuration annotation) and no
158
+ // shell-quoting dance around embedded single quotes. Content-addressed
159
+ // and idempotent, so this stays outside the retry loop below.
160
+ const blobs = [];
161
+ for (const f of files) {
162
+ const blobResult = await rt.spawn(["git", "hash-object", "-w", "--stdin"], { cwd, stdin: f.content });
163
+ if (blobResult.exitCode !== 0) {
164
+ throw ledgerWriteError("git hash-object", f.path, blobResult.stderr);
165
+ }
166
+ blobs.push({ path: f.path, sha: blobResult.stdout.trim() });
167
+ }
168
+ // Resolved once, outside the retry loop: whether the checkout can name a
169
+ // committer does not change between attempts (#2301).
170
+ const identityArgs = await ledgerCommitIdentityArgs(cwd);
171
+ // Creating the ledger branch is a different act from extending it, and only
172
+ // one of them is safe to do blind (#2309 review). With no local
173
+ // `chant/lifecycle`, everything below builds a tree from an empty read and
174
+ // commits it with no parent — a brand-new root commit that *replaces* the
175
+ // branch rather than appending to it, which `updateRefCAS` cannot catch
176
+ // because "the ref does not exist" is perfectly true locally.
177
+ //
178
+ // Until #2301 this was mostly hidden: `commit-tree` died for want of a
179
+ // committer before it could happen. Supplying an identity turned that loud
180
+ // failure into a silent branch fabrication on every writer that reaches
181
+ // here without a ledger — a run record, a snapshot, a release, a build
182
+ // manifest — and a fabricated branch is precisely the fork the guard above
183
+ // exists to refuse. So the guard runs here, at the one seam they all pass
184
+ // through, rather than at each of their call sites.
185
+ //
186
+ // Only when the branch is absent: an extend costs no fetch, and the check
187
+ // that matters is whether the history this is about to discard exists
188
+ // somewhere. `requireLifecycleLedger` allows `no-remote` and `absent`, so a
189
+ // genuinely first write still creates the branch.
190
+ if ((await getStateBranchTip(cwd)) === null) {
191
+ await requireLifecycleLedger({ ...(cwd ? { cwd } : {}) });
192
+ }
193
+ // 2-5. Read the trees, build the commit, and CAS-update the branch ref —
194
+ // retried on a conflict (#1959 finding 1). Before that retry existed a
195
+ // conflict from step 5's `updateRefCAS` simply threw, breaking every caller
196
+ // (observation-baseline.ts, snapshot.ts, build-ledger-store.ts, and — via
197
+ // `appendReleaseRecordLine` below — release-ledger.ts) the moment any
198
+ // concurrent writer touched the orphan branch, e.g. a live `chant
199
+ // operator` ticking a different env.
200
+ //
201
+ // Safety of a *blind* retry (same content and blob, freshly rebuilt tree)
202
+ // hinges on whether *our own* target paths are what actually caused the
203
+ // conflict:
204
+ // - If some OTHER path changed (the ordinary multi-env/multi-file case),
205
+ // our data is unaffected — rebuilding the tree from the new tip and
206
+ // retrying the ref update is always correct, no matter what kind of
207
+ // write the caller is doing (replace or append).
208
+ // - If one of OUR paths changed concurrently, a blind retry using
209
+ // content computed before that race would silently clobber the other
210
+ // writer's change — safe only for a caller whose content is a
211
+ // self-contained replacement (writeObservationBaseline, writeSnapshot,
212
+ // persistBuildManifest), never for a read-modify-write caller whose
213
+ // content already embeds a stale read (an append). Since this function
214
+ // can't tell those apart, it does NOT blind-retry in that case — it
215
+ // re-throws immediately so a read-modify-write caller's own outer retry
216
+ // (appendConvergeRecord, appendGateResolution, appendReleaseRecordLine
217
+ // below) can re-read and recompute content fresh before trying again.
218
+ //
219
+ // - A read-modify-write caller must pass `expectPriorSha`. Without it
220
+ // attempt 1 has no prior sha to compare, so a commit landing between the
221
+ // caller's baseline read and this function's first tree read reads as
222
+ // the ambient starting state rather than a conflict, and is overwritten
223
+ // from the caller's stale content.
224
+ const prior = new Map(files.map((f) => [f.path, f.expectPriorSha]));
225
+ let lastErr;
226
+ for (let attempt = 1; attempt <= WRITE_BLOB_RETRY_ATTEMPTS; attempt++) {
227
+ // 2. Read the tip once and every listing from that sha. `tip` must stay
228
+ // the commit parent and CAS oldValue below. Re-resolving the branch name
229
+ // there instead can observe a newer tip than the listings reflect, which
230
+ // makes the CAS succeed against a tree built from a stale read.
231
+ const tip = await getStateBranchTip(cwd);
232
+ const trees = new Map();
233
+ const listing = async (dir) => {
234
+ let entries = trees.get(dir);
235
+ if (!entries) {
236
+ entries = tip ? await listTreeAt(tip, dir, cwd) : new Map();
237
+ trees.set(dir, entries);
238
+ }
239
+ return entries;
240
+ };
241
+ for (const b of blobs) {
242
+ const { dir, name } = splitPath(b.path);
243
+ const current = (await listing(dir)).get(name);
244
+ const currentSha = current?.type === "blob" ? current.sha : null;
245
+ const expected = prior.get(b.path);
246
+ if (expected !== undefined && currentSha !== expected) {
247
+ // Our own path moved since the caller read it or our previous
248
+ // attempt started — a genuine content-level race on the exact file
249
+ // we're writing, not just a CAS conflict from a sibling path. Not
250
+ // safe to paper over here.
251
+ throw lastErr ?? new RefCASConflictError(`refs/heads/${STATE_BRANCH}`, expected, "target path changed concurrently");
252
+ }
253
+ prior.set(b.path, currentSha);
254
+ }
255
+ // 3. Replace the entries, listing every directory up to the root so the
256
+ // rebuild below has each parent in hand.
257
+ for (const b of blobs) {
258
+ const { dir, name } = splitPath(b.path);
259
+ (await listing(dir)).set(name, { mode: "100644", type: "blob", sha: b.sha });
260
+ for (let d = dir; d !== ""; d = parentDir(d))
261
+ await listing(parentDir(d));
262
+ }
263
+ // 4. Rebuild the touched trees, deepest first, then the root.
264
+ const touched = [...trees.keys()].sort((a, b) => depth(b) - depth(a) || (a < b ? -1 : 1));
265
+ let rootTreeSha = "";
266
+ for (const dir of touched) {
267
+ const lines = [...trees.get(dir)].map(([name, e]) => `${e.mode} ${e.type} ${e.sha}\t${name}`).join("\n");
268
+ const result = await rt.spawn(["git", "mktree"], { cwd, stdin: `${lines}\n` });
269
+ if (result.exitCode !== 0) {
270
+ throw ledgerWriteError(`git mktree (${dir === "" ? "root" : dir})`, path, result.stderr);
271
+ }
272
+ const sha = result.stdout.trim();
273
+ if (dir === "")
274
+ rootTreeSha = sha;
275
+ else
276
+ trees.get(parentDir(dir)).set(baseName(dir), { mode: "040000", type: "tree", sha });
277
+ }
278
+ // 5. Create the commit — parented on `tip`, the exact sha every listing
279
+ // was read from (see step 2), not a fresh re-resolution of the branch.
280
+ const parentRef = tip;
281
+ const parentArgs = parentRef ? ["-p", parentRef] : [];
282
+ const commitResult = await rt.spawn(["git", ...identityArgs, "commit-tree", ...parentArgs, "-m", commitMessage, rootTreeSha], { cwd });
283
+ if (commitResult.exitCode !== 0) {
284
+ throw ledgerWriteError("git commit-tree", path, commitResult.stderr);
285
+ }
286
+ const commitSha = commitResult.stdout.trim();
287
+ // 6. Update ref — CAS-guarded against `parentRef` (#1485): closes the
288
+ // local race two concurrent writers used to hit silently (whoever called
289
+ // update-ref last simply overwrote the other's tree, no error). A
290
+ // conflict here throws RefCASConflictError instead of clobbering.
291
+ try {
292
+ await updateRefCAS(`refs/heads/${STATE_BRANCH}`, commitSha, parentRef, { cwd });
293
+ return commitSha;
294
+ }
295
+ catch (err) {
296
+ if (!(err instanceof RefCASConflictError))
297
+ throw err;
298
+ lastErr = err;
299
+ // Loop and rebuild against the new tip — see the safety analysis
300
+ // above; the top of the next iteration decides whether that's safe.
301
+ }
302
+ }
303
+ throw lastErr;
304
+ }
305
+ /**
306
+ * Read a blob from an arbitrary `<environment>/<filename>` path on the orphan
307
+ * branch. Returns null when the path doesn't exist (missing branch, env, or
308
+ * file). Sibling of `writeBlobToPath`. Exported for the same reason
309
+ * `writeBlobToPath` is — ./build-ledger-store.ts (#609) reads manifests back
310
+ * from the `_builds` namespace through this identical helper.
311
+ */
312
+ export async function readBlobFromPath(environment, filename, opts) {
313
+ const rt = getRuntime();
314
+ const dir = await ledgerDir(environment, opts);
315
+ const result = await rt.spawn(["git", "show", `${STATE_BRANCH}:${dir}/${filename}`], { cwd: opts?.cwd });
316
+ if (result.exitCode !== 0)
317
+ return null;
318
+ return result.stdout;
319
+ }
320
+ /**
321
+ * Read the blob SHA stored at `<environment>/<filename>` on the orphan branch,
322
+ * or `null` if absent. Sibling of `readBlobFromPath` returning the
323
+ * content-address rather than the content. A read-modify-write ledger append
324
+ * pairs this with {@link readBlobBySha} to pin its baseline read to an exact
325
+ * sha, then passes that sha as `writeBlobToPath`'s `expectPriorPathSha`.
326
+ */
327
+ export async function readPathSha(environment, filename, opts) {
328
+ const rt = getRuntime();
329
+ const dir = await ledgerDir(environment, opts);
330
+ const result = await rt.spawn(["git", "rev-parse", "--verify", `${STATE_BRANCH}:${dir}/${filename}`], { cwd: opts?.cwd });
331
+ if (result.exitCode !== 0)
332
+ return null;
333
+ return result.stdout.trim() || null;
334
+ }
335
+ /**
336
+ * Storage key for a snapshot on the orphan branch. Single-stack projects key by
337
+ * lexicon (`<env>/<lexicon>.json`, unchanged). A multi-stack project (see
338
+ * `stacks` in ChantConfig, #932) folds the stack in as `<stack>__<lexicon>`
339
+ * (`<env>/<stack>__<lexicon>.json`) so sibling stacks that deploy the same
340
+ * lexicon don't overwrite each other's snapshots. The `__` separator can't
341
+ * collide with a lexicon name (lexicons are single tokens) and round-trips
342
+ * through `readSnapshot`/`readEnvironmentSnapshots` unchanged.
343
+ */
344
+ export function snapshotStorageKey(lexicon, stack) {
345
+ return stack ? `${stack}__${lexicon}` : lexicon;
346
+ }
347
+ /**
348
+ * Write a state snapshot JSON to the orphan branch.
349
+ *
350
+ * Pipeline: hash-object → mktree → commit-tree → update-ref
351
+ */
352
+ export async function writeSnapshot(environment, lexicon, json, opts) {
353
+ return writeBlobToPath(environment, `${lexicon}.json`, json, "State snapshot", opts);
354
+ }
355
+ /**
356
+ * Read a snapshot from the orphan branch.
357
+ */
358
+ export async function readSnapshot(environment, lexicon, opts) {
359
+ return readBlobFromPath(environment, `${lexicon}.json`, opts);
360
+ }
361
+ /**
362
+ * Read a snapshot at a specific orphan-branch commit (#822). `readSnapshot` reads
363
+ * the branch tip; this reads `<ref>:<env>/<lexicon>.json` for any commit `ref`
364
+ * (as listed by {@link listSnapshots}), so two historical snapshots can be diffed.
365
+ * Returns null if that env/lexicon wasn't captured at `ref`.
366
+ */
367
+ export async function readSnapshotAt(environment, lexicon, ref, opts) {
368
+ const rt = getRuntime();
369
+ const dir = await ledgerDir(environment, opts);
370
+ const result = await rt.spawn(["git", "show", `${ref}:${dir}/${lexicon}.json`], { cwd: opts?.cwd });
371
+ if (result.exitCode !== 0)
372
+ return null;
373
+ return result.stdout;
374
+ }
375
+ /**
376
+ * Append one immutable release record line to `<environment>/releases.jsonl`
377
+ * on the orphan branch (#568, epic #551 "Build & deploy observability"). Same
378
+ * git-plumbing path `writeSnapshot` uses, just a different filename under the
379
+ * env directory and append-only (JSON Lines) instead of replace-whole-file:
380
+ * each deploy adds one line, never rewrites a previous one, so the ledger
381
+ * stays a durable, append-only history rather than a point-in-time snapshot.
382
+ *
383
+ * Low-level plumbing over an already-serialized line; ../release-ledger.ts's
384
+ * `appendReleaseRecord` is the typed, validated public API most callers want
385
+ * — named distinctly here (`ReleaseRecordLine`) to avoid re-export ambiguity
386
+ * between the two modules.
387
+ *
388
+ * Returns the new orphan-branch commit SHA — the caller still owns pushing
389
+ * via `pushLifecycle` under the same concurrent-write lease `writeSnapshot`
390
+ * uses.
391
+ *
392
+ * Retries the whole read-modify-append cycle on `RefCASConflictError`
393
+ * (#1959 finding 1), the same shape `appendConvergeRecord`
394
+ * (./converge-ledger.ts) and `appendGateResolution` (./gate-ledger.ts) use
395
+ * for their own append-only ledgers: `writeBlobToPath`'s own retry only
396
+ * absorbs a conflict caused by some *other* env/file changing — a conflict
397
+ * on this exact `releases.jsonl` (e.g. two deploys to the same env racing)
398
+ * needs `existing` re-read fresh so the appended line list is rebuilt onto
399
+ * whatever the other writer just committed, not silently dropped by
400
+ * retrying with a blob computed from a stale read.
401
+ *
402
+ * The baseline read must be `readPathSha` + `readBlobBySha` rather than
403
+ * `readBlobFromPath`, so the exact sha `existing` came from can be passed as
404
+ * `expectPriorPathSha`. See `writeBlobToPath` for the race that closes.
405
+ */
406
+ export async function appendReleaseRecordLine(environment, recordJson, opts) {
407
+ const filename = "releases.jsonl";
408
+ let lastErr;
409
+ for (let attempt = 1; attempt <= WRITE_BLOB_RETRY_ATTEMPTS; attempt++) {
410
+ try {
411
+ const priorSha = await readPathSha(environment, filename, opts);
412
+ const existing = priorSha ? await readBlobBySha(priorSha, opts) : null;
413
+ const content = `${existing ? `${existing.replace(/\n$/, "")}\n` : ""}${recordJson}\n`;
414
+ return await writeBlobToPath(environment, filename, content, "Release record", {
415
+ ...opts,
416
+ expectPriorPathSha: priorSha,
417
+ });
418
+ }
419
+ catch (err) {
420
+ if (!(err instanceof RefCASConflictError))
421
+ throw err;
422
+ lastErr = err;
423
+ }
424
+ }
425
+ throw lastErr;
426
+ }
427
+ /**
428
+ * Read every release record line for `environment` from the orphan branch,
429
+ * oldest first, as raw JSON strings. Returns `[]` when no ledger exists yet
430
+ * for that env (never throws — a component that has never recorded a deploy
431
+ * is a normal, expected state). ../release-ledger.ts's `readReleaseLedger`
432
+ * parses/validates these lines into typed `ReleaseRecord`s.
433
+ */
434
+ export async function readReleaseLedgerLines(environment, opts) {
435
+ const content = await readBlobFromPath(environment, "releases.jsonl", opts);
436
+ if (!content)
437
+ return [];
438
+ return content.split("\n").map((line) => line.trim()).filter(Boolean);
439
+ }
440
+ /**
441
+ * List every environment that has a release ledger on the orphan branch —
442
+ * the root-level directories under `chant/lifecycle` that carry a
443
+ * `releases.jsonl` entry. Used by `chant components status` (no env arg) to
444
+ * discover which environments to report on.
445
+ */
446
+ export async function listLedgerEnvironments(opts) {
447
+ const envs = [];
448
+ for (const name of await listLedgerTrees(opts)) {
449
+ const hasLedger = await readBlobFromPath(name, "releases.jsonl", opts);
450
+ if (hasLedger)
451
+ envs.push(name);
452
+ }
453
+ return envs.sort();
454
+ }
455
+ /**
456
+ * The directories directly under the ledger's base: the root for the flat
457
+ * layout, `_members/<member>/` for a member. `[]` when the branch, or the
458
+ * base, is absent.
459
+ */
460
+ export async function listLedgerTrees(opts) {
461
+ const tip = await getStateBranchTip(opts?.cwd);
462
+ if (!tip)
463
+ return [];
464
+ const rt = getRuntime();
465
+ // A member lists the directories under its own `_members/<member>/`; the
466
+ // flat layout lists the root, where `_members` holds no releases.jsonl and
467
+ // so never reads as an environment (#2538).
468
+ const base = await ledgerDir("", opts);
469
+ // `--full-tree`: from a subdirectory, a bare `ls-tree` lists only that
470
+ // subdirectory's part of the tree (#2550).
471
+ const rootResult = await rt.spawn(["git", "ls-tree", "--full-tree", base === "" ? STATE_BRANCH : `${STATE_BRANCH}:${base}`], { cwd: opts?.cwd });
472
+ if (rootResult.exitCode !== 0)
473
+ return [];
474
+ const trees = [];
475
+ for (const line of rootResult.stdout.trim().split("\n").filter(Boolean)) {
476
+ const match = line.match(/^(\d+)\s+(\w+)\s+([0-9a-f]+)\t(.+)$/);
477
+ if (match && match[2] === "tree")
478
+ trees.push(match[4]);
479
+ }
480
+ return trees;
481
+ }
482
+ /**
483
+ * List every filename directly under `<dir>/` on the orphan branch (no
484
+ * recursion), or `[]` when the branch/directory doesn't exist yet. Generic
485
+ * sibling of `readEnvironmentSnapshots`'s inline file listing, factored out
486
+ * so ./build-ledger-store.ts (#609) can enumerate every persisted build
487
+ * manifest (`_builds/<digest>.json`) without duplicating this `git ls-tree
488
+ * --name-only` call.
489
+ */
490
+ export async function listFilesInDir(dir, opts) {
491
+ const tip = await getStateBranchTip(opts?.cwd);
492
+ if (!tip)
493
+ return [];
494
+ const rt = getRuntime();
495
+ const path = await ledgerDir(dir, opts);
496
+ const lsResult = await rt.spawn(["git", "ls-tree", "--full-tree", "--name-only", `${STATE_BRANCH}:${path}/`], { cwd: opts?.cwd });
497
+ if (lsResult.exitCode !== 0)
498
+ return [];
499
+ return lsResult.stdout.trim().split("\n").filter(Boolean);
500
+ }
501
+ /**
502
+ * Read all snapshots for an environment (all lexicons).
503
+ */
504
+ export async function readEnvironmentSnapshots(environment, opts) {
505
+ const rt = getRuntime();
506
+ const snapshots = new Map();
507
+ // List files in the environment directory
508
+ const dir = await ledgerDir(environment, opts);
509
+ const lsResult = await rt.spawn(["git", "ls-tree", "--full-tree", "--name-only", `${STATE_BRANCH}:${dir}/`], { cwd: opts?.cwd });
510
+ if (lsResult.exitCode !== 0)
511
+ return snapshots;
512
+ const files = lsResult.stdout.trim().split("\n").filter(Boolean);
513
+ for (const file of files) {
514
+ if (file.endsWith(".json")) {
515
+ const lexicon = file.replace(/\.json$/, "");
516
+ const content = await readSnapshot(environment, lexicon, opts);
517
+ if (content)
518
+ snapshots.set(lexicon, content);
519
+ }
520
+ }
521
+ return snapshots;
522
+ }
523
+ /**
524
+ * List snapshot history from the orphan branch.
525
+ */
526
+ export async function listSnapshots(opts) {
527
+ const rt = getRuntime();
528
+ // A member sees the commits that touched its own ledger (#2538); the flat
529
+ // layout keeps today's whole-branch history.
530
+ const base = await ledgerDir("", opts);
531
+ const result = await rt.spawn(
532
+ // `:(top)`: a pathspec is otherwise relative to the directory git runs in.
533
+ ["git", "log", "--format=%H %aI %s", STATE_BRANCH, ...(base === "" ? [] : ["--", `:(top)${base}`])], { cwd: opts?.cwd });
534
+ if (result.exitCode !== 0)
535
+ return [];
536
+ return result.stdout
537
+ .trim()
538
+ .split("\n")
539
+ .filter(Boolean)
540
+ .map((line) => {
541
+ const [commit, date, ...rest] = line.split(" ");
542
+ return { commit, date, message: rest.join(" ") };
543
+ });
544
+ }
545
+ /**
546
+ * Thrown by pushLifecycle when the remote chant/lifecycle branch has moved since
547
+ * the local snapshot was prepared — i.e. another snapshot for this or a
548
+ * different env was pushed concurrently. The caller should fetch and retry.
549
+ */
550
+ export class StaleLifecycleBranchError extends Error {
551
+ constructor(expected, stderr) {
552
+ super("chant/lifecycle remote branch has moved since this run started — " +
553
+ "another snapshot was pushed concurrently. " +
554
+ `git stderr: ${stderr.trim()}`);
555
+ this.name = "StaleLifecycleBranchError";
556
+ this.expected = expected;
557
+ }
558
+ }
559
+ /**
560
+ * Look up the remote-tracking SHA for chant/lifecycle, if any. Returns null when
561
+ * the remote ref doesn't exist locally yet (e.g. first-ever snapshot).
562
+ */
563
+ export async function getRemoteLifecycleBranchSha(remote, opts) {
564
+ const rt = getRuntime();
565
+ const ref = `refs/remotes/${remote}/${STATE_BRANCH}`;
566
+ const result = await rt.spawn(["git", "rev-parse", "--verify", ref], { cwd: opts?.cwd });
567
+ if (result.exitCode !== 0)
568
+ return null;
569
+ return result.stdout.trim() || null;
570
+ }
571
+ /**
572
+ * Point `refs/remotes/<remote>/chant/lifecycle` at `sha`, the remote tip this
573
+ * checkout has just seen (#3391).
574
+ *
575
+ * The push's lease is that ref, and git only keeps it up to date when the
576
+ * remote's configured fetch refspec covers `chant/lifecycle`. A single-branch
577
+ * clone (`git clone --single-branch`, a CI checkout of one ref, a shallow
578
+ * clone) has a refspec for its one branch only, so neither the explicit
579
+ * `chant/lifecycle:chant/lifecycle` fetch nor a successful push touches the
580
+ * tracking ref. The first push of a new branch then succeeds against an empty
581
+ * lease, and every later one is refused as stale against a remote that holds
582
+ * exactly what this checkout last pushed. Recording the tip ourselves, only at
583
+ * the two moments it is known (a fetch that fast-forwarded or found the local
584
+ * branch ahead, and a push that landed), keeps the lease honest without
585
+ * widening the clone's refspec. It is never set from a diverged fetch: that is
586
+ * the case the lease exists to refuse.
587
+ */
588
+ async function trackRemoteLifecycle(remote, sha, cwd) {
589
+ const rt = getRuntime();
590
+ await rt.spawn(["git", "update-ref", `refs/remotes/${remote}/${STATE_BRANCH}`, sha], { cwd, env: C_LOCALE_ENV });
591
+ }
592
+ /**
593
+ * Push the state branch to the ledger remote with --force-with-lease and say
594
+ * what happened (#3391). Never throws for a git refusal; {@link pushLifecycle}
595
+ * is the throwing form most callers use.
596
+ */
597
+ export async function pushLifecycleStatus(opts) {
598
+ const rt = getRuntime();
599
+ const cwd = opts?.cwd;
600
+ const found = await ledgerRemote(cwd);
601
+ if (found === null)
602
+ return { status: "no-remote" };
603
+ if ("failed" in found)
604
+ return { status: "failed", remote: "(unknown)", stderr: found.stderr };
605
+ const { remote } = found;
606
+ // Capture the lease SHA — if null, the remote ref doesn't exist yet
607
+ // (first-time push) and we send `--force-with-lease=ref:` (empty SHA),
608
+ // which git interprets as "ref does not exist on remote".
609
+ const expected = await getRemoteLifecycleBranchSha(remote, opts);
610
+ const lease = `refs/heads/${STATE_BRANCH}:${expected ?? ""}`;
611
+ const tip = await getStateBranchTip(cwd);
612
+ const pushResult = await rt.spawn(["git", "push", `--force-with-lease=${lease}`, remote, `${STATE_BRANCH}:${STATE_BRANCH}`], { cwd, env: C_LOCALE_ENV });
613
+ if (pushResult.exitCode !== 0) {
614
+ const stderr = (pushResult.stderr ?? "").trim();
615
+ if (stderr.includes("stale info") ||
616
+ stderr.includes("rejected") ||
617
+ stderr.includes("non-fast-forward")) {
618
+ return { status: "stale", remote, expected, stderr };
619
+ }
620
+ return { status: "failed", remote, stderr };
621
+ }
622
+ if (tip)
623
+ await trackRemoteLifecycle(remote, tip, cwd);
624
+ return { status: "pushed", remote, commit: tip ?? "" };
625
+ }
626
+ /**
627
+ * Push the state branch to remote with --force-with-lease.
628
+ *
629
+ * If the remote chant/lifecycle ref has advanced past the local remote-tracking
630
+ * SHA captured at the start of this push, the push is rejected and we throw
631
+ * StaleLifecycleBranchError so the caller can surface a recovery hint.
632
+ *
633
+ * Returns false (without throwing) when no remote is configured, or when git
634
+ * refused the push for a reason other than a stale lease.
635
+ *
636
+ * The remote is the ledger's ({@link ledgerRemote}: `origin` when there is one),
637
+ * the same one {@link fetchLifecycleStatus} reads, and a push that lands
638
+ * records the tip it pushed as the remote-tracking ref, so the next push's
639
+ * lease is right in a single-branch clone too (#3391).
640
+ */
641
+ export async function pushLifecycle(opts) {
642
+ const result = await pushLifecycleStatus(opts);
643
+ if (result.status === "stale")
644
+ throw new StaleLifecycleBranchError(result.expected, result.stderr);
645
+ return result.status === "pushed";
646
+ }
647
+ /**
648
+ * One line on why a push did not land, for a write document's `notPushed`
649
+ * (#3391), or null when it did.
650
+ */
651
+ export function describeLifecyclePush(result) {
652
+ const said = (stderr) => (stderr ? `; git said: ${stderr.split("\n").find((l) => l.trim()) ?? stderr}` : "");
653
+ switch (result.status) {
654
+ case "pushed":
655
+ return null;
656
+ case "no-remote":
657
+ return `this repository has no remote, so ${STATE_BRANCH} stays local`;
658
+ case "stale":
659
+ return `"${result.remote}" holds ${STATE_BRANCH} commits this checkout has not fetched, so the push was refused; fetch ${STATE_BRANCH} and write again${said(result.stderr)}`;
660
+ case "failed":
661
+ return `the push of ${STATE_BRANCH} to "${result.remote}" failed${said(result.stderr)}`;
662
+ }
663
+ }
664
+ /**
665
+ * Push the ledger branch and say, in one line, why it did not land (#2310,
666
+ * #3677). No remote reads as {@link LIFECYCLE_LOCAL_NOTE}; a push that git
667
+ * refused or that failed keeps its own reason, so a renderer can warn about
668
+ * that case alone. Never throws.
669
+ */
670
+ export async function pushLifecycleReport(opts) {
671
+ try {
672
+ const result = await pushLifecycleStatus(opts);
673
+ switch (result.status) {
674
+ case "pushed":
675
+ return { pushed: true };
676
+ case "no-remote":
677
+ return { pushed: false, pushWarning: LIFECYCLE_LOCAL_NOTE };
678
+ case "stale":
679
+ return { pushed: false, pushWarning: new StaleLifecycleBranchError(result.expected, result.stderr).message };
680
+ case "failed":
681
+ return { pushed: false, pushWarning: describeLifecyclePush(result) ?? "the push did not land" };
682
+ }
683
+ }
684
+ catch (err) {
685
+ return { pushed: false, pushWarning: err instanceof Error ? err.message : String(err) };
686
+ }
687
+ }
688
+ /**
689
+ * Fetch the state branch from remote.
690
+ */
691
+ export async function fetchLifecycle(opts) {
692
+ return (await fetchLifecycleStatus(opts)).status === "fetched";
693
+ }
694
+ /**
695
+ * git's wording when the remote answered and simply does not carry the ref.
696
+ *
697
+ * Matched against output forced to `LC_ALL=C` ({@link C_LOCALE_ENV}) — both of
698
+ * these strings are in git's translation catalogs, so on a distro git built
699
+ * with NLS and a non-C `LANG` an unpinned locale would fail to match and send
700
+ * a project's genuine first gate down the `unreachable` path, refusing it.
701
+ */
702
+ const NO_SUCH_REMOTE_REF_RE = /couldn't find remote ref|no such ref was fetched/i;
703
+ /** git's wording when the refspec would not fast-forward the local branch. Same locale caveat. */
704
+ const NON_FAST_FORWARD_RE = /non-fast-forward|rejected/i;
705
+ /**
706
+ * `git` invocations here are parsed by their output, so they are pinned to the
707
+ * C locale. `LC_ALL` alone is enough — it outranks `LC_MESSAGES` and `LANG` —
708
+ * but `LANGUAGE` is GNU gettext's own override and outranks all of them, so it
709
+ * is cleared too.
710
+ */
711
+ const C_LOCALE_ENV = { LC_ALL: "C", LANGUAGE: "" };
712
+ /**
713
+ * The remote the ledger lives on.
714
+ *
715
+ * `origin` when it exists, rather than whichever name sorts first — a repo
716
+ * with a `backup` remote alongside `origin` would otherwise fetch the ledger
717
+ * from `backup`. A non-zero exit is *not* read as "no remote": a git that
718
+ * failed to answer is not evidence that a project is single-machine, and
719
+ * treating it as one is what lets an unreadable ledger look empty.
720
+ */
721
+ async function ledgerRemote(cwd) {
722
+ const rt = getRuntime();
723
+ const result = await rt.spawn(["git", "remote"], { cwd, env: C_LOCALE_ENV });
724
+ if (result.exitCode !== 0)
725
+ return { failed: true, stderr: (result.stderr ?? "").trim() };
726
+ const names = result.stdout.trim().split("\n").map((n) => n.trim()).filter(Boolean);
727
+ if (names.length === 0)
728
+ return null;
729
+ return { remote: names.includes("origin") ? "origin" : names[0] };
730
+ }
731
+ /** A scratch ref this module owns, used to resolve the remote tip without touching the ledger branch. */
732
+ const PROBE_REF = "refs/chant/lifecycle-probe";
733
+ /**
734
+ * Fetch `chant/lifecycle` and say what happened, in the terms
735
+ * {@link LifecycleFetchStatus} defines.
736
+ *
737
+ * The refspec is deliberately un-forced (`chant/lifecycle:chant/lifecycle`,
738
+ * no leading `+`), so a local branch holding facts the remote does not have is
739
+ * never rewound by a read.
740
+ *
741
+ * git reports "non-fast-forward" for two states that could not be more
742
+ * different, which is why the rejection is not taken at face value (#2309
743
+ * review): a local branch *ahead* of the remote — the ordinary state right
744
+ * after an append whose push has not landed yet — and a local branch that has
745
+ * genuinely *forked*. Resolving the remote tip into a scratch ref and asking
746
+ * whether it is an ancestor of the local tip separates them. Reading a fork as
747
+ * the ledger is what destroys approvals; refusing an append that is merely
748
+ * ahead would break the retry that is supposed to repair a failed push.
749
+ */
750
+ export async function fetchLifecycleStatus(opts) {
751
+ const rt = getRuntime();
752
+ const cwd = opts?.cwd;
753
+ const found = await ledgerRemote(cwd);
754
+ if (found === null)
755
+ return { status: "no-remote" };
756
+ if ("failed" in found) {
757
+ return { status: "unreachable", remote: "(unknown)", stderr: found.stderr };
758
+ }
759
+ const { remote } = found;
760
+ const fetchResult = await rt.spawn(["git", "fetch", remote, `${STATE_BRANCH}:${STATE_BRANCH}`], { cwd, env: C_LOCALE_ENV });
761
+ if (fetchResult.exitCode === 0) {
762
+ // The local branch is now the remote's tip; record it for the push's lease (#3391).
763
+ const tip = await getStateBranchTip(cwd);
764
+ if (tip)
765
+ await trackRemoteLifecycle(remote, tip, cwd);
766
+ return { status: "fetched" };
767
+ }
768
+ const stderr = (fetchResult.stderr ?? "").trim();
769
+ if (NO_SUCH_REMOTE_REF_RE.test(stderr))
770
+ return { status: "absent", remote };
771
+ if (!NON_FAST_FORWARD_RE.test(stderr))
772
+ return { status: "unreachable", remote, stderr };
773
+ // Rejected. Resolve the remote tip into our own scratch ref (forced: the ref
774
+ // is this module's, and nothing reads it but the next two lines) and ask
775
+ // whether the local branch already contains it.
776
+ const probe = await rt.spawn(["git", "fetch", "--force", remote, `${STATE_BRANCH}:${PROBE_REF}`], { cwd, env: C_LOCALE_ENV });
777
+ if (probe.exitCode !== 0) {
778
+ return { status: "unreachable", remote, stderr: (probe.stderr ?? "").trim() || stderr };
779
+ }
780
+ const contains = await rt.spawn(["git", "merge-base", "--is-ancestor", PROBE_REF, `refs/heads/${STATE_BRANCH}`], { cwd, env: C_LOCALE_ENV });
781
+ const remoteTip = contains.exitCode === 0 ? (await rt.spawn(["git", "rev-parse", "--verify", PROBE_REF], { cwd, env: C_LOCALE_ENV })).stdout.trim() : "";
782
+ await rt.spawn(["git", "update-ref", "-d", PROBE_REF], { cwd, env: C_LOCALE_ENV });
783
+ if (contains.exitCode === 0) {
784
+ // Ahead of the remote: its tip is in our history, so it is the right lease (#3391).
785
+ if (remoteTip)
786
+ await trackRemoteLifecycle(remote, remoteTip, cwd);
787
+ return { status: "ahead", remote };
788
+ }
789
+ return { status: "diverged", remote, stderr };
790
+ }
791
+ /**
792
+ * Thrown when the ledger branch cannot be read, rather than being reported as
793
+ * empty (#2303).
794
+ */
795
+ export class LifecycleLedgerUnreadableError extends Error {
796
+ constructor(reason, remote, stderr, detail) {
797
+ super(`the ${STATE_BRANCH} ledger branch ${detail}, so what is on it cannot be read. ` +
798
+ `Refusing rather than treating an unreadable ledger as an empty one.` +
799
+ (reason === "diverged"
800
+ ? `\n Two histories claim to be the ledger; appending to either one drops the other's facts. ` +
801
+ `Inspect both before doing anything else:\n` +
802
+ ` git fetch ${remote} ${STATE_BRANCH}:refs/chant/remote-ledger\n` +
803
+ ` git log --oneline ${STATE_BRANCH} refs/chant/remote-ledger`
804
+ : `\n Fetch it and retry: git fetch ${remote} ${STATE_BRANCH}:${STATE_BRANCH}`) +
805
+ (stderr ? `\n git said: ${stderr.split("\n")[0]}` : ""));
806
+ this.reason = reason;
807
+ this.remote = remote;
808
+ this.stderr = stderr;
809
+ this.name = "LifecycleLedgerUnreadableError";
810
+ }
811
+ }
812
+ /**
813
+ * Bring the ledger branch into this checkout before reading or appending to
814
+ * it, and refuse when it cannot be (#2303).
815
+ *
816
+ * The gate's read is the one that has to be right: `evaluateGate` decides
817
+ * whether to walk through or to record a *new* pending fact purely from what
818
+ * the ledger says, so a read that reports history it cannot see as an empty
819
+ * ledger makes the second run of an already-approved commit record a second
820
+ * pending fact and gate again. That is what a GitLab CI retry did on
821
+ * INTENTIUS/choudoufu#1026 — two pending records for one commit, with
822
+ * different expiries — because a GitLab checkout fetches the pipeline's own
823
+ * ref at depth 20 and nothing else. `GIT_DEPTH: 0` does not help: GitLab
824
+ * fetches refspecs, not all branches.
825
+ *
826
+ * There was a separate, laxer guard for the read until #2309's review, on the
827
+ * reasoning that a checkout which already has the branch "has real history to
828
+ * read". That reasoning is only sound when the local branch *is* the remote's
829
+ * history, and it let the worst case through: on a fork, the gate read a
830
+ * ledger missing every remote approval, recorded a fresh pending fact, and
831
+ * pushed. The push was not protected either — a rejected non-fast-forward
832
+ * fetch still updates `refs/remotes/<remote>/chant/lifecycle` opportunistically
833
+ * in a full clone, so `pushLifecycle`'s `--force-with-lease` matched the tip
834
+ * it had just learned and forced. Verified on git 2.50.1: a remote holding a
835
+ * pending fact and an approval ended up holding neither.
836
+ *
837
+ * So there is one rule now, for readers and writers alike:
838
+ *
839
+ * - `diverged` always refuses. A fork is not the ledger, and nothing good
840
+ * comes of reading one as if it were.
841
+ * - `unreachable` refuses only when the checkout has no local history at all,
842
+ * which is the case that would otherwise read as an empty ledger. With
843
+ * local history and no answer from the remote, the read proceeds and the
844
+ * push's lease fails safely on its own — refusing there would strand every
845
+ * offline run.
846
+ * - `fetched`, `no-remote`, `absent` and `ahead` all proceed. `ahead` is the
847
+ * ordinary state right after an append whose push has not landed, and is
848
+ * exactly what a retry needs to be able to repair.
849
+ */
850
+ export async function requireLifecycleLedger(opts) {
851
+ const hadLocal = (await getStateBranchTip(opts?.cwd)) !== null;
852
+ const result = await fetchLifecycleStatus(opts);
853
+ if (result.status === "diverged") {
854
+ throw new LifecycleLedgerUnreadableError("diverged", result.remote, result.stderr, `has diverged from the copy on "${result.remote}" — neither contains the other`);
855
+ }
856
+ if (result.status === "unreachable" && !hadLocal) {
857
+ throw new LifecycleLedgerUnreadableError("unreachable", result.remote, result.stderr, `is not in this checkout and could not be fetched from "${result.remote}"`);
858
+ }
859
+ }
860
+ // ── Generic ref CAS (#1485) ──────────────────────────────────────────────────
861
+ //
862
+ // `writeBlobToPath`'s own `update-ref` call (above) had no compare-and-swap
863
+ // until this issue: two concurrent local writers could each build a tree from
864
+ // what they read as "current", and whichever called `update-ref` last simply
865
+ // overwrote the ref with no error — the other writer's change vanished
866
+ // silently. The primitives below give any caller (this module's own
867
+ // `writeBlobToPath`, and `./lease.ts`'s lease ref) a compare-and-swap guard
868
+ // building on what `git update-ref` already supports natively: pass the
869
+ // value you last observed as `<oldvalue>`, and the update only lands if the
870
+ // ref still points there. No lock file — `update-ref` itself is already an
871
+ // atomic local mutex (lockfile-then-rename under the hood), so this is
872
+ // exactly the "extend the ref-write helper" shape, not a second mechanism.
873
+ /**
874
+ * Thrown by {@link updateRefCAS}/{@link deleteRefCAS} when `ref` no longer
875
+ * points at the `oldValue` the caller last observed — another writer moved
876
+ * it concurrently. Deliberately a distinct type from a generic git failure so
877
+ * callers (a lease acquire, a retried ledger append) can tell "I lost a race"
878
+ * from "git itself failed" and react differently to each.
879
+ */
880
+ export class RefCASConflictError extends Error {
881
+ constructor(ref, expected, stderr) {
882
+ super(`ref "${ref}" moved concurrently — expected ${expected ?? "(ref must not exist)"}, ` +
883
+ `but another writer updated it first. git stderr: ${stderr.trim()}`);
884
+ this.ref = ref;
885
+ this.expected = expected;
886
+ this.name = "RefCASConflictError";
887
+ }
888
+ }
889
+ /**
890
+ * Thrown by {@link updateRefCAS}/{@link deleteRefCAS} when the ref update
891
+ * failed because git found a stale `.lock` file already sitting next to the
892
+ * ref (#1959 finding 2) — what a `chant operator`/`chant approve`/etc.
893
+ * process leaves behind when it is killed (SIGKILL, OOM, `kill -9`) mid-write,
894
+ * *before* `git update-ref`'s own lockfile-then-rename completes. This is
895
+ * exactly the crash this feature must recover from, and it is NOT a CAS
896
+ * conflict: nobody else actually holds the ref (no other writer is racing,
897
+ * the previous one is simply dead), so it must never be misread as "someone
898
+ * else updated it first" — see `./lease.ts`'s `acquireLease`, which used to
899
+ * (before this fix) read this as "lease held by someone else" and quietly
900
+ * back off forever, since the dead process's lock file never goes away on
901
+ * its own.
902
+ */
903
+ export class StaleLockError extends Error {
904
+ constructor(ref, lockPath, stderr) {
905
+ super(`ref "${ref}" has a stale lock file at ${lockPath} — a previous git process ` +
906
+ `(e.g. a killed \`chant operator\`) was interrupted mid-write and left it ` +
907
+ `behind. Fix: remove it (\`rm ${lockPath}\`) and retry. git stderr: ${stderr.trim()}`);
908
+ this.ref = ref;
909
+ this.lockPath = lockPath;
910
+ this.name = "StaleLockError";
911
+ }
912
+ }
913
+ /** Matches git's "Unable to create '<path>.lock': File exists." — the one stable, version-independent phrase every `git update-ref`/`git commit` etc. failure due to a leftover lock file uses, regardless of which ref or which git version. */
914
+ const STALE_LOCK_RE = /Unable to create '([^']+)':\s*File exists/;
915
+ /** Read the SHA `ref` currently points to, or `null` if it doesn't exist. Works for any ref, not just the lifecycle branch. */
916
+ export async function readRefSha(ref, opts) {
917
+ const rt = getRuntime();
918
+ const result = await rt.spawn(["git", "rev-parse", "--verify", ref], { cwd: opts?.cwd });
919
+ if (result.exitCode !== 0)
920
+ return null;
921
+ return result.stdout.trim() || null;
922
+ }
923
+ /**
924
+ * Turn a failed `git update-ref`'s stderr into the right error type (#1959
925
+ * finding 2). Real git collapses several distinct failure modes into the
926
+ * same nonzero exit code — a genuine CAS mismatch, a stale lock file left by
927
+ * a killed process, and an outright bad ref name all just say "update_ref
928
+ * failed" — so this doesn't trust the exit code alone:
929
+ *
930
+ * 1. A stale `.lock` file has a stable, distinctive message (see
931
+ * `STALE_LOCK_RE`) — checked first and reported as its own
932
+ * {@link StaleLockError}, never as a conflict.
933
+ * 2. Otherwise, re-read the ref's actual current value and compare it to
934
+ * `oldValue`: if it genuinely differs, this is a real CAS conflict —
935
+ * {@link RefCASConflictError}. This is the authoritative check (not
936
+ * stderr text matching) so it holds across git versions/locales.
937
+ * 3. If the ref's actual value still matches `oldValue` — the CAS itself
938
+ * should have succeeded — the failure is something else entirely (a bad
939
+ * ref name, a permissions problem, disk full, ...) and is surfaced as a
940
+ * plain `Error`, not miscategorized as either of the above.
941
+ */
942
+ async function classifyRefFailure(ref, oldValue, stderr, opts) {
943
+ const lockMatch = stderr.match(STALE_LOCK_RE);
944
+ if (lockMatch) {
945
+ return new StaleLockError(ref, lockMatch[1], stderr);
946
+ }
947
+ const actual = await readRefSha(ref, opts);
948
+ if (actual !== oldValue) {
949
+ return new RefCASConflictError(ref, oldValue, stderr);
950
+ }
951
+ return new Error(`git update-ref failed for ref "${ref}": ${stderr.trim()}`);
952
+ }
953
+ /**
954
+ * Compare-and-swap update of an arbitrary ref. `oldValue` is the SHA the
955
+ * caller last observed the ref at, or `null` to assert the ref does not yet
956
+ * exist (git's own convention: an empty `<oldvalue>` argument to
957
+ * `update-ref` means "must not exist"). Throws {@link RefCASConflictError}
958
+ * when the ref genuinely moved since `oldValue` was read, {@link
959
+ * StaleLockError} when a leftover lock file from a killed process is
960
+ * blocking the write, or a plain `Error` for anything else — never silently
961
+ * overwrites, and never misclassifies one failure as another (#1959 finding
962
+ * 2; see {@link classifyRefFailure}).
963
+ */
964
+ export async function updateRefCAS(ref, newValue, oldValue, opts) {
965
+ const rt = getRuntime();
966
+ const result = await rt.spawn(["git", "update-ref", ref, newValue, oldValue ?? ""], { cwd: opts?.cwd });
967
+ if (result.exitCode !== 0) {
968
+ throw await classifyRefFailure(ref, oldValue, result.stderr ?? "", opts);
969
+ }
970
+ }
971
+ /**
972
+ * Compare-and-swap delete of an arbitrary ref — `oldValue` is required (no
973
+ * "delete unconditionally" escape hatch here) so releasing a lease you no
974
+ * longer hold can never delete someone else's newer one. Same failure
975
+ * classification as {@link updateRefCAS} (#1959 finding 2).
976
+ */
977
+ export async function deleteRefCAS(ref, oldValue, opts) {
978
+ const rt = getRuntime();
979
+ const result = await rt.spawn(["git", "update-ref", "-d", ref, oldValue], { cwd: opts?.cwd });
980
+ if (result.exitCode !== 0) {
981
+ throw await classifyRefFailure(ref, oldValue, result.stderr ?? "", opts);
982
+ }
983
+ }
984
+ /**
985
+ * Write arbitrary content as a git blob object — no tree, no commit, no ref
986
+ * update. The building block a CAS ref's value can point at directly: a
987
+ * lease record (`./lease.ts`) has no meaningful "tree of files", so its ref
988
+ * targets a blob SHA rather than a commit the way `writeBlobToPath`'s tree-
989
+ * building pipeline does.
990
+ */
991
+ export async function writeBlob(content, opts) {
992
+ const rt = getRuntime();
993
+ const result = await rt.spawn(["git", "hash-object", "-w", "--stdin"], { cwd: opts?.cwd, stdin: content });
994
+ if (result.exitCode !== 0)
995
+ throw new Error(`git hash-object failed: ${result.stderr}`);
996
+ return result.stdout.trim();
997
+ }
998
+ /** Read a blob's raw content by its SHA (whatever object a ref points at directly). Returns `null` when the object doesn't exist locally. */
999
+ export async function readBlobBySha(sha, opts) {
1000
+ const rt = getRuntime();
1001
+ const result = await rt.spawn(["git", "cat-file", "blob", sha], { cwd: opts?.cwd });
1002
+ if (result.exitCode !== 0)
1003
+ return null;
1004
+ return result.stdout;
1005
+ }
1006
+ /**
1007
+ * Push one arbitrary ref (e.g. a lease ref) to the remote, guarded the same
1008
+ * way {@link pushLifecycle} guards the ledger branch: `--force-with-lease`
1009
+ * keyed to the remote SHA last observed locally, so a concurrent push from a
1010
+ * second machine is rejected rather than silently clobbered. The lease alone
1011
+ * lets a push replace a value that is not an ancestor — a lease ref's value is
1012
+ * a bare blob SHA, not a commit descending from the previous one — as long as
1013
+ * the remote still holds the expected value. No `--force` beside it: git lets
1014
+ * `--force` override `--force-with-lease`, which made every lease push
1015
+ * unconditional before #2732.
1016
+ *
1017
+ * Returns `false` (never throws) when no remote is configured — a
1018
+ * remote-less project's lease is local-only by construction (see
1019
+ * `./lease.ts`'s module doc), or when the push itself is rejected (the
1020
+ * caller re-reads and retries; see `acquireLease`). {@link pushRefStatus}
1021
+ * tells those two apart.
1022
+ */
1023
+ export async function pushRef(ref, opts) {
1024
+ return (await pushRefStatus(ref, opts)) === "pushed";
1025
+ }
1026
+ /**
1027
+ * {@link pushRef}, saying why a push did not land (#2732).
1028
+ *
1029
+ * `opts.expect` is the value the caller last saw the ref at on the remote
1030
+ * (`null`: absent there), and becomes the `--force-with-lease` expectation.
1031
+ * Without it the expectation is `refs/remotes/<remote>/<ref>`, which nothing
1032
+ * fetches a lease ref into, so it reads as "absent" and only a first push
1033
+ * lands. A lease passes the value of its own remote-tracking ref instead
1034
+ * (`./lease.ts`'s `readLease`).
1035
+ *
1036
+ * A ref that no longer exists locally is pushed as a deletion, under the same
1037
+ * expectation, so a released lease is released on the remote too. Deleting a
1038
+ * ref the caller expects to be absent already is a no-op that reports
1039
+ * `pushed`.
1040
+ */
1041
+ export async function pushRefStatus(ref, opts) {
1042
+ const rt = getRuntime();
1043
+ const remoteResult = await rt.spawn(["git", "remote"], { cwd: opts?.cwd });
1044
+ if (remoteResult.exitCode !== 0 || !remoteResult.stdout.trim())
1045
+ return "no-remote";
1046
+ const remote = remoteResult.stdout.trim().split("\n")[0];
1047
+ let expected;
1048
+ if (opts?.expect !== undefined) {
1049
+ expected = opts.expect;
1050
+ }
1051
+ else {
1052
+ const remoteRef = `refs/remotes/${remote}/${ref.replace(/^refs\//, "")}`;
1053
+ const expectedResult = await rt.spawn(["git", "rev-parse", "--verify", remoteRef], { cwd: opts?.cwd });
1054
+ expected = expectedResult.exitCode === 0 ? expectedResult.stdout.trim() : null;
1055
+ }
1056
+ const lease = `${ref}:${expected ?? ""}`;
1057
+ const local = await readRefSha(ref, opts);
1058
+ if (local === null && expected === null)
1059
+ return "pushed";
1060
+ const refspec = local === null ? `:${ref}` : `${ref}:${ref}`;
1061
+ const pushResult = await rt.spawn(["git", "push", `--force-with-lease=${lease}`, remote, refspec], { cwd: opts?.cwd });
1062
+ return pushResult.exitCode === 0 ? "pushed" : "rejected";
1063
+ }
1064
+ /**
1065
+ * Fetch one arbitrary remote ref into a local ref of a possibly *different*
1066
+ * name (#1959 finding 3). `+` forces the update even when it isn't a
1067
+ * fast-forward (a lease ref's new value is rarely a descendant of its old
1068
+ * one). Returns `false` (never throws) when no remote is configured.
1069
+ *
1070
+ * The `localRef !== remoteRef` shape exists so a read path can observe what
1071
+ * the remote currently holds without ever touching a local ref another code
1072
+ * path treats as CAS-authoritative — see {@link fetchRef}'s doc and
1073
+ * `./lease.ts`'s `readLease`, which fetches into a side tracking ref
1074
+ * (`refs/chant/lease-remote/<op>`) for exactly this reason.
1075
+ */
1076
+ export async function fetchRefInto(remoteRef, localRef, opts) {
1077
+ return (await fetchRefIntoStatus(remoteRef, localRef, opts)) === "fetched";
1078
+ }
1079
+ /**
1080
+ * {@link fetchRefInto}, saying why nothing was fetched (#2732). `missing`
1081
+ * means the remote answered and does not carry the ref, which for a lease is
1082
+ * the news that it was released there.
1083
+ */
1084
+ export async function fetchRefIntoStatus(remoteRef, localRef, opts) {
1085
+ const rt = getRuntime();
1086
+ const remoteResult = await rt.spawn(["git", "remote"], { cwd: opts?.cwd });
1087
+ if (remoteResult.exitCode !== 0 || !remoteResult.stdout.trim())
1088
+ return "no-remote";
1089
+ const remote = remoteResult.stdout.trim().split("\n")[0];
1090
+ const fetchResult = await rt.spawn(["git", "fetch", remote, `+${remoteRef}:${localRef}`], { cwd: opts?.cwd, env: C_LOCALE_ENV });
1091
+ if (fetchResult.exitCode === 0)
1092
+ return "fetched";
1093
+ return NO_SUCH_REMOTE_REF_RE.test(fetchResult.stderr ?? "") ? "missing" : "failed";
1094
+ }
1095
+ /**
1096
+ * Fetch one arbitrary ref from remote into the same local ref name.
1097
+ *
1098
+ * **Caution for a read path (#1959 finding 3):** this force-overwrites
1099
+ * `ref` locally (`+ref:ref`) — safe for a ref only a CAS write path ever
1100
+ * mutates locally between fetches, but NOT safe to call from a plain read
1101
+ * before every read if some other local writer (in the same clone) might be
1102
+ * mid-write: fetching here would force the local ref back to whatever the
1103
+ * remote last had, clobbering a just-written, not-yet-pushed local value out
1104
+ * from under it. `./lease.ts`'s `readLease` used to do exactly that; it now
1105
+ * uses {@link fetchRefInto} against a side tracking ref instead. Prefer
1106
+ * `fetchRefInto` for any new read-before-decide path.
1107
+ */
1108
+ export async function fetchRef(ref, opts) {
1109
+ return fetchRefInto(ref, ref, opts);
1110
+ }
1111
+ /**
1112
+ * Get the current HEAD commit SHA of the main working branch.
1113
+ */
1114
+ export async function getHeadCommit(opts) {
1115
+ const rt = getRuntime();
1116
+ const result = await rt.spawn(["git", "rev-parse", "HEAD"], { cwd: opts?.cwd });
1117
+ if (result.exitCode !== 0) {
1118
+ throw new Error(`git rev-parse HEAD failed: ${result.stderr}`);
1119
+ }
1120
+ return result.stdout.trim();
1121
+ }
1122
+ async function getStateBranchTip(cwd) {
1123
+ const rt = getRuntime();
1124
+ const result = await rt.spawn(["git", "rev-parse", "--verify", `refs/heads/${STATE_BRANCH}`], { cwd });
1125
+ if (result.exitCode !== 0)
1126
+ return null;
1127
+ return result.stdout.trim();
1128
+ }
1129
+ /**
1130
+ * The entries of directory `dir` (branch-relative, `""` for the root) in
1131
+ * commit `tip`, or an empty map when it does not exist. Always pinned to the
1132
+ * resolved `tip` sha, never to `STATE_BRANCH`: a write reads several
1133
+ * directories, and a branch name re-resolves on each read, so a concurrent
1134
+ * commit would splice entries from two commits into one tree undetectably.
1135
+ *
1136
+ * `--full-tree` (#2550): run from a project in a subdirectory of the
1137
+ * repository, a bare `ls-tree` lists only the part of the tree under that
1138
+ * subdirectory, which on the orphan branch is nothing. The write then
1139
+ * rebuilt the branch from an empty tree, and an append's CAS refused it as a
1140
+ * concurrent change.
1141
+ */
1142
+ async function listTreeAt(tip, dir, cwd) {
1143
+ const rt = getRuntime();
1144
+ const entries = new Map();
1145
+ const result = await rt.spawn(["git", "ls-tree", "--full-tree", dir === "" ? tip : `${tip}:${dir}/`], { cwd });
1146
+ if (result.exitCode !== 0)
1147
+ return entries;
1148
+ for (const line of result.stdout.split("\n")) {
1149
+ // Format: mode type sha\tname
1150
+ const match = line.match(/^(\d+)\s+(\w+)\s+([0-9a-f]+)\t(.+)$/);
1151
+ if (match)
1152
+ entries.set(match[4], { mode: match[1], type: match[2], sha: match[3] });
1153
+ }
1154
+ return entries;
1155
+ }
1156
+ function splitPath(path) {
1157
+ const slash = path.lastIndexOf("/");
1158
+ return { dir: path.slice(0, slash), name: path.slice(slash + 1) };
1159
+ }
1160
+ function parentDir(dir) {
1161
+ const slash = dir.lastIndexOf("/");
1162
+ return slash < 0 ? "" : dir.slice(0, slash);
1163
+ }
1164
+ function baseName(dir) {
1165
+ return dir.slice(dir.lastIndexOf("/") + 1);
1166
+ }
1167
+ function depth(dir) {
1168
+ return dir === "" ? 0 : dir.split("/").length;
1169
+ }
1170
+ /**
1171
+ * The branch's read side as a {@link LedgerReadPort} for the member `opts`
1172
+ * names (#3865): what the bundle-safe readers (`./release-read.ts`,
1173
+ * `./wave-plans.ts`) are handed. A product with its own way to read the
1174
+ * branch passes its own port instead and never loads this module.
1175
+ */
1176
+ export function gitLedgerReadPort(opts) {
1177
+ return {
1178
+ read: (dir, file) => readBlobFromPath(dir, file, opts),
1179
+ list: (dir) => listFilesInDir(dir, opts),
1180
+ trees: () => listLedgerTrees(opts),
1181
+ };
1182
+ }