@intentius/chant 0.57.0 → 0.58.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (504) hide show
  1. package/dist/audit/catalog.d.ts +51 -1
  2. package/dist/audit/catalog.d.ts.map +1 -1
  3. package/dist/audit/core.d.ts +20 -1
  4. package/dist/audit/core.d.ts.map +1 -1
  5. package/dist/audit/discover.d.ts +14 -8
  6. package/dist/audit/discover.d.ts.map +1 -1
  7. package/dist/audit/lineage.d.ts +10 -0
  8. package/dist/audit/lineage.d.ts.map +1 -0
  9. package/dist/audit/prior-art.d.ts +385 -0
  10. package/dist/audit/prior-art.d.ts.map +1 -0
  11. package/dist/audit/proof.d.ts +16 -0
  12. package/dist/audit/proof.d.ts.map +1 -1
  13. package/dist/audit/report-html.d.ts.map +1 -1
  14. package/dist/audit/report-model.d.ts +8 -1
  15. package/dist/audit/report-model.d.ts.map +1 -1
  16. package/dist/audit/report.d.ts.map +1 -1
  17. package/dist/audit/rules-doc.d.ts +3 -0
  18. package/dist/audit/rules-doc.d.ts.map +1 -1
  19. package/dist/audit/secrets.d.ts +15 -0
  20. package/dist/audit/secrets.d.ts.map +1 -1
  21. package/dist/audit/terraform-state.d.ts +46 -0
  22. package/dist/audit/terraform-state.d.ts.map +1 -0
  23. package/dist/build.d.ts +17 -1
  24. package/dist/build.d.ts.map +1 -1
  25. package/dist/claimed-fields.d.ts +161 -0
  26. package/dist/claimed-fields.d.ts.map +1 -0
  27. package/dist/cli/build-params-cli.d.ts +2 -2
  28. package/dist/cli/command-group.d.ts +14 -0
  29. package/dist/cli/command-group.d.ts.map +1 -1
  30. package/dist/cli/commands/audit.d.ts +17 -1
  31. package/dist/cli/commands/audit.d.ts.map +1 -1
  32. package/dist/cli/commands/build.d.ts.map +1 -1
  33. package/dist/cli/commands/check-lexicon-docs.d.ts +1 -1
  34. package/dist/cli/commands/check-lexicon-examples.d.ts +16 -6
  35. package/dist/cli/commands/check-lexicon-examples.d.ts.map +1 -1
  36. package/dist/cli/commands/init-lexicon/templates/codegen.d.ts +1 -0
  37. package/dist/cli/commands/init-lexicon/templates/codegen.d.ts.map +1 -1
  38. package/dist/cli/commands/init-lexicon/templates/project.d.ts +7 -0
  39. package/dist/cli/commands/init-lexicon/templates/project.d.ts.map +1 -1
  40. package/dist/cli/commands/init-lexicon.d.ts.map +1 -1
  41. package/dist/cli/commands/init.d.ts.map +1 -1
  42. package/dist/cli/commands/lint.d.ts.map +1 -1
  43. package/dist/cli/commands/onboard.d.ts +2 -0
  44. package/dist/cli/commands/onboard.d.ts.map +1 -1
  45. package/dist/cli/handlers/build.d.ts.map +1 -1
  46. package/dist/cli/handlers/lifecycle.d.ts.map +1 -1
  47. package/dist/cli/handlers/operator.d.ts +82 -16
  48. package/dist/cli/handlers/operator.d.ts.map +1 -1
  49. package/dist/cli/handlers/run.d.ts +49 -42
  50. package/dist/cli/handlers/run.d.ts.map +1 -1
  51. package/dist/cli/main.d.ts +28 -1
  52. package/dist/cli/main.d.ts.map +1 -1
  53. package/dist/cli/mcp/op-tools.d.ts +16 -1
  54. package/dist/cli/mcp/op-tools.d.ts.map +1 -1
  55. package/dist/cli/mcp/resource-handlers.d.ts.map +1 -1
  56. package/dist/cli/mcp/resources/context.d.ts.map +1 -1
  57. package/dist/cli/registry.d.ts +26 -9
  58. package/dist/cli/registry.d.ts.map +1 -1
  59. package/dist/codegen/generate-post-synth-barrel.d.ts +1 -1
  60. package/dist/components/auto-release.d.ts +10 -20
  61. package/dist/components/auto-release.d.ts.map +1 -1
  62. package/dist/components/capability.d.ts +4 -4
  63. package/dist/components/cli-support.d.ts +20 -21
  64. package/dist/components/cli-support.d.ts.map +1 -1
  65. package/dist/components/component.d.ts +3 -3
  66. package/dist/components/component.d.ts.map +1 -1
  67. package/dist/components/driver-output.d.ts.map +1 -1
  68. package/dist/components/driver.d.ts +66 -36
  69. package/dist/components/driver.d.ts.map +1 -1
  70. package/dist/components/index.d.ts +1 -2
  71. package/dist/components/index.d.ts.map +1 -1
  72. package/dist/components/manifest-persistence.d.ts +1 -9
  73. package/dist/components/manifest-persistence.d.ts.map +1 -1
  74. package/dist/components/run-progress.d.ts +6 -6
  75. package/dist/components/run-progress.d.ts.map +1 -1
  76. package/dist/components/verbs/run-agent.d.ts +9 -15
  77. package/dist/components/verbs/run-agent.d.ts.map +1 -1
  78. package/dist/components/verbs/verify.d.ts +2 -2
  79. package/dist/deep-observation.d.ts +17 -0
  80. package/dist/deep-observation.d.ts.map +1 -1
  81. package/dist/discovery/files.d.ts +4 -4
  82. package/dist/discovery/fold-import.d.ts.map +1 -1
  83. package/dist/discovery/param-deps.d.ts +33 -0
  84. package/dist/discovery/param-deps.d.ts.map +1 -1
  85. package/dist/discovery/sandbox/bundle.d.ts.map +1 -1
  86. package/dist/discovery/sandbox/config-wire.d.ts +2 -2
  87. package/dist/fold/foldable-helpers.d.ts +5 -1
  88. package/dist/fold/foldable-helpers.d.ts.map +1 -1
  89. package/dist/fold-provenance.d.ts +236 -0
  90. package/dist/fold-provenance.d.ts.map +1 -0
  91. package/dist/graph-ops.d.ts +1 -1
  92. package/dist/held-elsewhere.d.ts +85 -0
  93. package/dist/held-elsewhere.d.ts.map +1 -0
  94. package/dist/index.d.ts +3 -0
  95. package/dist/index.d.ts.map +1 -1
  96. package/dist/kubectl-context.d.ts +3 -3
  97. package/dist/lexicon-config.d.ts +4 -4
  98. package/dist/lexicon-plugin-helpers.d.ts +1 -1
  99. package/dist/lexicon.d.ts +104 -14
  100. package/dist/lexicon.d.ts.map +1 -1
  101. package/dist/lifecycle/change-set.d.ts +13 -0
  102. package/dist/lifecycle/change-set.d.ts.map +1 -1
  103. package/dist/lifecycle/deep-diff.d.ts +133 -6
  104. package/dist/lifecycle/deep-diff.d.ts.map +1 -1
  105. package/dist/lifecycle/deep-observe.d.ts.map +1 -1
  106. package/dist/lifecycle/gate-ledger.d.ts +71 -3
  107. package/dist/lifecycle/gate-ledger.d.ts.map +1 -1
  108. package/dist/lifecycle/index.d.ts +1 -0
  109. package/dist/lifecycle/index.d.ts.map +1 -1
  110. package/dist/lifecycle/release-ledger.d.ts +2 -2
  111. package/dist/lifecycle/run-ledger.d.ts +53 -0
  112. package/dist/lifecycle/run-ledger.d.ts.map +1 -0
  113. package/dist/lifecycle/symptoms.d.ts +3 -3
  114. package/dist/lifecycle/symptoms.d.ts.map +1 -1
  115. package/dist/lint/config.d.ts +81 -2
  116. package/dist/lint/config.d.ts.map +1 -1
  117. package/dist/lint/post-synth.d.ts +55 -2
  118. package/dist/lint/post-synth.d.ts.map +1 -1
  119. package/dist/lint/rules/__fixtures__/comp/comp004/pass/neo4j-cluster.component.d.ts +1 -1
  120. package/dist/lint/rules/__fixtures__/comp/comp004/pass/neo4j-cluster.component.d.ts.map +1 -1
  121. package/dist/lint/rules/__fixtures__/comp/comp004/pass/orders-table.component.d.ts +1 -1
  122. package/dist/lint/rules/__fixtures__/comp/comp004/pass/orders-table.component.d.ts.map +1 -1
  123. package/dist/lint/rules/comp/comp004-gate-needs-durable-runtime.d.ts +44 -0
  124. package/dist/lint/rules/comp/comp004-gate-needs-durable-runtime.d.ts.map +1 -0
  125. package/dist/lint/rules/comp/index.d.ts +1 -1
  126. package/dist/lint/rules/comp/index.d.ts.map +1 -1
  127. package/dist/lint/rules/op/index.d.ts +16 -0
  128. package/dist/lint/rules/op/index.d.ts.map +1 -0
  129. package/dist/lint/rules/op/ops012-activity-contract.d.ts +27 -0
  130. package/dist/lint/rules/op/ops012-activity-contract.d.ts.map +1 -0
  131. package/dist/lint/rules/op/ops013-step-output-ref.d.ts +61 -0
  132. package/dist/lint/rules/op/ops013-step-output-ref.d.ts.map +1 -0
  133. package/dist/lint/rules/op/ops014-converge-rule-refusals.d.ts +55 -0
  134. package/dist/lint/rules/op/ops014-converge-rule-refusals.d.ts.map +1 -0
  135. package/dist/lint/rules/op/support.d.ts +12 -0
  136. package/dist/lint/rules/op/support.d.ts.map +1 -0
  137. package/dist/lint/suppressions.d.ts +108 -0
  138. package/dist/lint/suppressions.d.ts.map +1 -0
  139. package/dist/live-endpoint.d.ts +1 -1
  140. package/dist/op/activities/activity-contracts.d.ts +54 -0
  141. package/dist/op/activities/activity-contracts.d.ts.map +1 -0
  142. package/dist/op/activities/apply.d.ts +262 -0
  143. package/dist/op/activities/apply.d.ts.map +1 -0
  144. package/dist/op/activities/build.d.ts +13 -0
  145. package/dist/op/activities/build.d.ts.map +1 -0
  146. package/dist/op/activities/converge.d.ts +110 -0
  147. package/dist/op/activities/converge.d.ts.map +1 -0
  148. package/dist/op/activities/env-teardown.d.ts +76 -0
  149. package/dist/op/activities/env-teardown.d.ts.map +1 -0
  150. package/dist/op/activities/guard-validate.d.ts +64 -0
  151. package/dist/op/activities/guard-validate.d.ts.map +1 -0
  152. package/dist/op/activities/http-check.d.ts +34 -0
  153. package/dist/op/activities/http-check.d.ts.map +1 -0
  154. package/dist/op/activities/index.d.ts +31 -0
  155. package/dist/op/activities/index.d.ts.map +1 -0
  156. package/dist/op/activities/lexicon-upgrade.d.ts +182 -0
  157. package/dist/op/activities/lexicon-upgrade.d.ts.map +1 -0
  158. package/dist/op/activities/lifecycle.d.ts +42 -0
  159. package/dist/op/activities/lifecycle.d.ts.map +1 -0
  160. package/dist/op/activities/pipeline-audit.d.ts +84 -0
  161. package/dist/op/activities/pipeline-audit.d.ts.map +1 -0
  162. package/dist/op/activities/policy.d.ts +17 -0
  163. package/dist/op/activities/policy.d.ts.map +1 -0
  164. package/dist/op/activities/reconcile.d.ts +89 -0
  165. package/dist/op/activities/reconcile.d.ts.map +1 -0
  166. package/dist/op/activities/shell.d.ts +13 -0
  167. package/dist/op/activities/shell.d.ts.map +1 -0
  168. package/dist/op/activities/teardown.d.ts +10 -0
  169. package/dist/op/activities/teardown.d.ts.map +1 -0
  170. package/dist/op/activities/util.d.ts +7 -0
  171. package/dist/op/activities/util.d.ts.map +1 -0
  172. package/dist/op/activities/wait.d.ts +28 -0
  173. package/dist/op/activities/wait.d.ts.map +1 -0
  174. package/dist/op/activities/workflow-audit.d.ts +92 -0
  175. package/dist/op/activities/workflow-audit.d.ts.map +1 -0
  176. package/dist/op/activity-contract-registry.d.ts +76 -0
  177. package/dist/op/activity-contract-registry.d.ts.map +1 -0
  178. package/dist/op/activity-contract.d.ts +25 -18
  179. package/dist/op/activity-contract.d.ts.map +1 -1
  180. package/dist/op/activity-failure.d.ts +28 -0
  181. package/dist/op/activity-failure.d.ts.map +1 -0
  182. package/dist/op/activity-profiles.d.ts +118 -0
  183. package/dist/op/activity-profiles.d.ts.map +1 -0
  184. package/dist/op/activity-registry.d.ts +22 -38
  185. package/dist/op/activity-registry.d.ts.map +1 -1
  186. package/dist/op/activity-runtime.d.ts +3 -14
  187. package/dist/op/activity-runtime.d.ts.map +1 -1
  188. package/dist/op/builders.d.ts +84 -29
  189. package/dist/op/builders.d.ts.map +1 -1
  190. package/dist/op/composites/apply-op.d.ts +113 -0
  191. package/dist/op/composites/apply-op.d.ts.map +1 -0
  192. package/dist/op/composites/converge-op.d.ts +120 -0
  193. package/dist/op/composites/converge-op.d.ts.map +1 -0
  194. package/dist/op/composites/index.d.ts +26 -0
  195. package/dist/op/composites/index.d.ts.map +1 -0
  196. package/dist/op/composites/lexicon-upgrade-op.d.ts +78 -0
  197. package/dist/op/composites/lexicon-upgrade-op.d.ts.map +1 -0
  198. package/dist/op/composites/pipeline-audit-op.d.ts +50 -0
  199. package/dist/op/composites/pipeline-audit-op.d.ts.map +1 -0
  200. package/dist/op/composites/reconcile-op.d.ts +57 -0
  201. package/dist/op/composites/reconcile-op.d.ts.map +1 -0
  202. package/dist/op/composites/watch-op.d.ts +60 -0
  203. package/dist/op/composites/watch-op.d.ts.map +1 -0
  204. package/dist/op/composites/workflow-audit-op.d.ts +57 -0
  205. package/dist/op/composites/workflow-audit-op.d.ts.map +1 -0
  206. package/dist/op/converge-rule.d.ts +12 -10
  207. package/dist/op/converge-rule.d.ts.map +1 -1
  208. package/dist/op/cron.d.ts +51 -0
  209. package/dist/op/cron.d.ts.map +1 -0
  210. package/dist/op/discover.d.ts +9 -0
  211. package/dist/op/discover.d.ts.map +1 -1
  212. package/dist/op/duration.d.ts +11 -0
  213. package/dist/op/duration.d.ts.map +1 -0
  214. package/dist/op/emulator-lifecycle.d.ts.map +1 -1
  215. package/dist/op/gate.d.ts +90 -0
  216. package/dist/op/gate.d.ts.map +1 -0
  217. package/dist/op/generate-pipeline.d.ts +9 -0
  218. package/dist/op/generate-pipeline.d.ts.map +1 -1
  219. package/dist/op/index.d.ts +23 -7
  220. package/dist/op/index.d.ts.map +1 -1
  221. package/dist/op/local-executor.d.ts +114 -28
  222. package/dist/op/local-executor.d.ts.map +1 -1
  223. package/dist/op/local-output.d.ts +11 -1
  224. package/dist/op/local-output.d.ts.map +1 -1
  225. package/dist/op/op-ir.d.ts +184 -0
  226. package/dist/op/op-ir.d.ts.map +1 -0
  227. package/dist/op/op-verb-class.d.ts +10 -2
  228. package/dist/op/op-verb-class.d.ts.map +1 -1
  229. package/dist/op/operator.d.ts +30 -5
  230. package/dist/op/operator.d.ts.map +1 -1
  231. package/dist/op/receipt-store.d.ts +1 -1
  232. package/dist/op/receipt-store.d.ts.map +1 -1
  233. package/dist/op/resource.d.ts +15 -1
  234. package/dist/op/resource.d.ts.map +1 -1
  235. package/dist/op/runtime.d.ts +200 -0
  236. package/dist/op/runtime.d.ts.map +1 -0
  237. package/dist/op/runtimes/local.d.ts +29 -0
  238. package/dist/op/runtimes/local.d.ts.map +1 -0
  239. package/dist/op/step-output-ref.d.ts +15 -13
  240. package/dist/op/step-output-ref.d.ts.map +1 -1
  241. package/dist/op/types.d.ts +79 -27
  242. package/dist/op/types.d.ts.map +1 -1
  243. package/dist/ownership.d.ts +1 -1
  244. package/dist/provenance.d.ts +21 -0
  245. package/dist/provenance.d.ts.map +1 -1
  246. package/dist/serializer-walker.d.ts.map +1 -1
  247. package/package.json +6 -1
  248. package/src/audit/catalog.test.ts +79 -3
  249. package/src/audit/catalog.ts +111 -13
  250. package/src/audit/core.test.ts +26 -0
  251. package/src/audit/core.ts +61 -17
  252. package/src/audit/discover.test.ts +39 -0
  253. package/src/audit/discover.ts +91 -15
  254. package/src/audit/lineage.ts +100 -0
  255. package/src/audit/prior-art.ts +153 -0
  256. package/src/audit/proof.test.ts +67 -0
  257. package/src/audit/proof.ts +70 -0
  258. package/src/audit/report-html.ts +9 -4
  259. package/src/audit/report-model.ts +16 -2
  260. package/src/audit/report.test.ts +33 -0
  261. package/src/audit/report.ts +5 -3
  262. package/src/audit/rules-doc.test.ts +34 -2
  263. package/src/audit/rules-doc.ts +82 -9
  264. package/src/audit/secrets.ts +15 -3
  265. package/src/audit/terraform-state.test.ts +144 -0
  266. package/src/audit/terraform-state.ts +131 -0
  267. package/src/build.ts +31 -1
  268. package/src/claimed-fields.test.ts +145 -0
  269. package/src/claimed-fields.ts +208 -0
  270. package/src/cli/build-params-cli.ts +2 -2
  271. package/src/cli/command-group.ts +14 -0
  272. package/src/cli/commands/__fixtures__/audit-terraform/infra/.terraform/providers/lock.txt +1 -0
  273. package/src/cli/commands/__fixtures__/audit-terraform/infra/main.tf +1 -0
  274. package/src/cli/commands/__fixtures__/audit-terraform/infra/modules/foo/main.tf +1 -0
  275. package/src/cli/commands/__fixtures__/audit-terraform/infra/variables.tf +3 -0
  276. package/src/cli/commands/__fixtures__/init-lexicon-output/package.json +13 -4
  277. package/src/cli/commands/__fixtures__/init-lexicon-output/src/codegen/docs-cli.ts +4 -0
  278. package/src/cli/commands/__fixtures__/init-lexicon-output/src/codegen/package.ts +1 -0
  279. package/src/cli/commands/__fixtures__/init-lexicon-output/src/package-cli.ts +21 -0
  280. package/src/cli/commands/__snapshots__/init-lexicon.test.ts.snap +1 -0
  281. package/src/cli/commands/audit.test.ts +189 -11
  282. package/src/cli/commands/audit.ts +73 -27
  283. package/src/cli/commands/build.test.ts +292 -9
  284. package/src/cli/commands/build.ts +156 -24
  285. package/src/cli/commands/check-lexicon-docs.ts +1 -1
  286. package/src/cli/commands/check-lexicon-examples.test.ts +29 -1
  287. package/src/cli/commands/check-lexicon-examples.ts +42 -10
  288. package/src/cli/commands/init-lexicon/templates/codegen.ts +9 -0
  289. package/src/cli/commands/init-lexicon/templates/project.ts +44 -4
  290. package/src/cli/commands/init-lexicon.test.ts +97 -2
  291. package/src/cli/commands/init-lexicon.ts +4 -2
  292. package/src/cli/commands/init.test.ts +35 -1
  293. package/src/cli/commands/init.ts +14 -3
  294. package/src/cli/commands/lint.test.ts +48 -0
  295. package/src/cli/commands/lint.ts +123 -0
  296. package/src/cli/commands/onboard.test.ts +75 -0
  297. package/src/cli/commands/onboard.ts +54 -23
  298. package/src/cli/handlers/build.test.ts +81 -1
  299. package/src/cli/handlers/build.ts +15 -1
  300. package/src/cli/handlers/graph.test.ts +3 -3
  301. package/src/cli/handlers/lifecycle.test.ts +195 -12
  302. package/src/cli/handlers/lifecycle.ts +123 -6
  303. package/src/cli/handlers/misc.ts +1 -1
  304. package/src/cli/handlers/operator.test.ts +183 -11
  305. package/src/cli/handlers/operator.ts +260 -55
  306. package/src/cli/handlers/run.test.ts +431 -1287
  307. package/src/cli/handlers/run.ts +365 -915
  308. package/src/cli/main.test.ts +23 -15
  309. package/src/cli/main.ts +88 -33
  310. package/src/cli/mcp/op-tools.ts +147 -88
  311. package/src/cli/mcp/resource-handlers.ts +10 -37
  312. package/src/cli/mcp/resources/context.ts +11 -12
  313. package/src/cli/mcp/server.test.ts +25 -27
  314. package/src/cli/mcp/server.ts +2 -2
  315. package/src/cli/registry.ts +26 -9
  316. package/src/codegen/docs.ts +1 -1
  317. package/src/codegen/generate-post-synth-barrel.test.ts +1 -1
  318. package/src/codegen/generate-post-synth-barrel.ts +1 -1
  319. package/src/components/README.md +3 -2
  320. package/src/components/auto-release.test.ts +4 -20
  321. package/src/components/auto-release.ts +17 -42
  322. package/src/components/capability.ts +4 -4
  323. package/src/components/cli-support.test.ts +10 -5
  324. package/src/components/cli-support.ts +52 -44
  325. package/src/components/component.schema.json +2 -2
  326. package/src/components/component.ts +3 -3
  327. package/src/components/driver-output.ts +14 -2
  328. package/src/components/driver.test.ts +92 -22
  329. package/src/components/driver.ts +193 -83
  330. package/src/components/index.ts +1 -6
  331. package/src/components/manifest-persistence.test.ts +2 -20
  332. package/src/components/manifest-persistence.ts +4 -24
  333. package/src/components/pilots/pilots-lint-clean.test.ts +11 -10
  334. package/src/components/run-progress.ts +6 -6
  335. package/src/components/verbs/run-agent.ts +11 -17
  336. package/src/components/verbs/verify.ts +2 -2
  337. package/src/deep-observation.test.ts +75 -0
  338. package/src/deep-observation.ts +48 -0
  339. package/src/discovery/collect.test.ts +3 -3
  340. package/src/discovery/files.ts +4 -4
  341. package/src/discovery/fold-import.ts +105 -2
  342. package/src/discovery/param-deps.test.ts +112 -1
  343. package/src/discovery/param-deps.ts +193 -0
  344. package/src/discovery/sandbox/bundle.ts +15 -1
  345. package/src/discovery/sandbox/config-wire.test.ts +2 -2
  346. package/src/discovery/sandbox/config-wire.ts +2 -2
  347. package/src/fold/fold.test.ts +56 -0
  348. package/src/fold/foldable-helpers.ts +81 -1
  349. package/src/fold-provenance-exclusion.test.ts +186 -0
  350. package/src/fold-provenance.test.ts +495 -0
  351. package/src/fold-provenance.ts +441 -0
  352. package/src/graph-ops.test.ts +2 -2
  353. package/src/graph-ops.ts +1 -1
  354. package/src/held-elsewhere.test.ts +102 -0
  355. package/src/held-elsewhere.ts +114 -0
  356. package/src/index.ts +3 -0
  357. package/src/kubectl-context.ts +3 -3
  358. package/src/lexicon-config.test.ts +5 -5
  359. package/src/lexicon-config.ts +4 -4
  360. package/src/lexicon-plugin-helpers.ts +1 -1
  361. package/src/lexicon.ts +120 -14
  362. package/src/lifecycle/change-set.test.ts +49 -0
  363. package/src/lifecycle/change-set.ts +67 -0
  364. package/src/lifecycle/converge-ledger.ts +1 -1
  365. package/src/lifecycle/deep-diff.test.ts +267 -14
  366. package/src/lifecycle/deep-diff.ts +217 -14
  367. package/src/lifecycle/deep-observe.test.ts +3 -1
  368. package/src/lifecycle/deep-observe.ts +16 -6
  369. package/src/lifecycle/gate-ledger.test.ts +74 -2
  370. package/src/lifecycle/gate-ledger.ts +143 -36
  371. package/src/lifecycle/index.ts +1 -0
  372. package/src/lifecycle/release-ledger.ts +2 -2
  373. package/src/lifecycle/run-ledger.test.ts +268 -0
  374. package/src/lifecycle/run-ledger.ts +201 -0
  375. package/src/lifecycle/symptoms.ts +3 -3
  376. package/src/lint/config.test.ts +82 -1
  377. package/src/lint/config.ts +112 -3
  378. package/src/lint/policy.test.ts +1 -1
  379. package/src/lint/post-synth.test.ts +31 -0
  380. package/src/lint/post-synth.ts +54 -1
  381. package/src/lint/rules/__fixtures__/comp/comp004/pass/neo4j-cluster.component.ts +2 -2
  382. package/src/lint/rules/__fixtures__/comp/comp004/pass/orders-table.component.ts +1 -1
  383. package/src/lint/rules/comp/comp.test.ts +1 -1
  384. package/src/lint/rules/comp/comp004-gate-needs-durable-runtime.ts +74 -0
  385. package/src/lint/rules/comp/index.ts +3 -3
  386. package/src/lint/rules/op/index.ts +23 -0
  387. package/src/lint/rules/op/ops012-activity-contract.test.ts +161 -0
  388. package/src/lint/rules/op/ops012-activity-contract.ts +64 -0
  389. package/src/lint/rules/op/ops013-step-output-ref.test.ts +237 -0
  390. package/src/lint/rules/op/ops013-step-output-ref.ts +98 -0
  391. package/src/lint/rules/op/ops014-converge-rule-refusals.test.ts +263 -0
  392. package/src/lint/rules/op/ops014-converge-rule-refusals.ts +179 -0
  393. package/src/lint/rules/op/support.ts +11 -0
  394. package/src/lint/suppressions.test.ts +163 -0
  395. package/src/lint/suppressions.ts +204 -0
  396. package/src/live-endpoint.ts +1 -1
  397. package/src/op/activities/activity-contracts.ts +66 -0
  398. package/src/op/activities/apply.test.ts +567 -0
  399. package/src/op/activities/apply.ts +662 -0
  400. package/src/op/activities/build.ts +26 -0
  401. package/src/op/activities/converge.test.ts +330 -0
  402. package/src/op/activities/converge.ts +449 -0
  403. package/src/op/activities/env-teardown.test.ts +187 -0
  404. package/src/op/activities/env-teardown.ts +171 -0
  405. package/src/op/activities/guard-validate.test.ts +139 -0
  406. package/src/op/activities/guard-validate.ts +223 -0
  407. package/src/op/activities/http-check.test.ts +48 -0
  408. package/src/op/activities/http-check.ts +55 -0
  409. package/src/op/activities/index.ts +119 -0
  410. package/src/op/activities/lexicon-upgrade.test.ts +492 -0
  411. package/src/op/activities/lexicon-upgrade.ts +673 -0
  412. package/src/op/activities/lifecycle.ts +85 -0
  413. package/src/op/activities/pipeline-audit.test.ts +67 -0
  414. package/src/op/activities/pipeline-audit.ts +201 -0
  415. package/src/op/activities/policy.test.ts +19 -0
  416. package/src/op/activities/policy.ts +37 -0
  417. package/src/op/activities/reconcile.test.ts +81 -0
  418. package/src/op/activities/reconcile.ts +187 -0
  419. package/src/op/activities/shell.ts +26 -0
  420. package/src/op/activities/teardown.ts +22 -0
  421. package/src/op/activities/util.ts +6 -0
  422. package/src/op/activities/wait.ts +69 -0
  423. package/src/op/activities/workflow-audit.test.ts +90 -0
  424. package/src/op/activities/workflow-audit.ts +203 -0
  425. package/src/op/activity-contract-registry.test.ts +98 -0
  426. package/src/op/activity-contract-registry.ts +114 -0
  427. package/src/op/activity-contract.test.ts +1 -1
  428. package/src/op/activity-contract.ts +37 -33
  429. package/src/op/activity-failure.ts +34 -0
  430. package/src/op/activity-profiles.test.ts +56 -0
  431. package/src/op/activity-profiles.ts +116 -0
  432. package/src/op/activity-registry.test.ts +37 -28
  433. package/src/op/activity-registry.ts +27 -56
  434. package/src/op/activity-runtime.ts +3 -47
  435. package/src/op/builders-exports.test.ts +0 -49
  436. package/src/op/builders-typed.test.ts +164 -0
  437. package/src/op/builders.ts +123 -39
  438. package/src/op/composites/apply-op.ts +208 -0
  439. package/src/op/composites/composites.test.ts +366 -0
  440. package/src/op/composites/converge-op.test.ts +168 -0
  441. package/src/op/composites/converge-op.ts +214 -0
  442. package/src/op/composites/index.ts +26 -0
  443. package/src/op/composites/lexicon-upgrade-op.test.ts +68 -0
  444. package/src/op/composites/lexicon-upgrade-op.ts +134 -0
  445. package/src/op/composites/pipeline-audit-op.test.ts +25 -0
  446. package/src/op/composites/pipeline-audit-op.ts +78 -0
  447. package/src/op/composites/reconcile-op.test.ts +51 -0
  448. package/src/op/composites/reconcile-op.ts +107 -0
  449. package/src/op/composites/watch-op.ts +108 -0
  450. package/src/op/composites/workflow-audit-op.test.ts +25 -0
  451. package/src/op/composites/workflow-audit-op.ts +88 -0
  452. package/src/op/converge-rule.ts +12 -10
  453. package/src/op/cron.test.ts +128 -0
  454. package/src/op/cron.ts +149 -0
  455. package/src/op/discover-root.test.ts +105 -1
  456. package/src/op/discover.test.ts +6 -2
  457. package/src/op/discover.ts +84 -16
  458. package/src/op/duration.ts +21 -0
  459. package/src/op/effect-step.test.ts +26 -11
  460. package/src/op/emulator-lifecycle.ts +1 -2
  461. package/src/op/gate.ts +155 -0
  462. package/src/op/generate-pipeline.test.ts +65 -3
  463. package/src/op/generate-pipeline.ts +19 -2
  464. package/src/op/index.ts +39 -6
  465. package/src/op/local-executor.test.ts +163 -34
  466. package/src/op/local-executor.ts +370 -102
  467. package/src/op/local-output.test.ts +95 -11
  468. package/src/op/local-output.ts +33 -4
  469. package/src/op/op-ir.test.ts +281 -0
  470. package/src/op/op-ir.ts +417 -0
  471. package/src/op/op-verb-class.ts +19 -5
  472. package/src/op/op.test.ts +26 -5
  473. package/src/op/operator.test.ts +175 -6
  474. package/src/op/operator.ts +63 -16
  475. package/src/op/receipt-store.ts +1 -1
  476. package/src/op/resource.ts +20 -2
  477. package/src/op/runtime.ts +203 -0
  478. package/src/op/runtimes/local.test.ts +181 -0
  479. package/src/op/runtimes/local.ts +212 -0
  480. package/src/op/step-output-ref.ts +15 -13
  481. package/src/op/types.ts +84 -24
  482. package/src/ownership.ts +1 -1
  483. package/src/provenance.test.ts +6 -0
  484. package/src/provenance.ts +21 -1
  485. package/src/serializer-walker.test.ts +42 -0
  486. package/src/serializer-walker.ts +21 -2
  487. package/dist/cli/handlers/op-progress.d.ts +0 -90
  488. package/dist/cli/handlers/op-progress.d.ts.map +0 -1
  489. package/dist/cli/handlers/run-client.d.ts +0 -127
  490. package/dist/cli/handlers/run-client.d.ts.map +0 -1
  491. package/dist/cli/handlers/run-report.d.ts +0 -5
  492. package/dist/cli/handlers/run-report.d.ts.map +0 -1
  493. package/dist/components/temporal-codegen-loader.d.ts +0 -27
  494. package/dist/components/temporal-codegen-loader.d.ts.map +0 -1
  495. package/dist/lint/rules/comp/comp004-gate-needs-temporal.d.ts +0 -37
  496. package/dist/lint/rules/comp/comp004-gate-needs-temporal.d.ts.map +0 -1
  497. package/src/cli/handlers/op-progress.test.ts +0 -268
  498. package/src/cli/handlers/op-progress.ts +0 -248
  499. package/src/cli/handlers/run-client.test.ts +0 -82
  500. package/src/cli/handlers/run-client.ts +0 -219
  501. package/src/cli/handlers/run-report.test.ts +0 -62
  502. package/src/cli/handlers/run-report.ts +0 -122
  503. package/src/components/temporal-codegen-loader.ts +0 -41
  504. package/src/lint/rules/comp/comp004-gate-needs-temporal.ts +0 -66
@@ -15,26 +15,24 @@
15
15
  * second non-env top-level directory.
16
16
  *
17
17
  * `chant approve <op> <gate>` (`../cli/handlers/operator.ts`) is what
18
- * appends here — issue #1485's "resolution is an out-of-band act that
19
- * writes the counterpart fact". Per the issue's own leaning on open
18
+ * appends a resolution — issue #1485's "resolution is an out-of-band act
19
+ * that writes the counterpart fact". Per that issue's leaning on open
20
20
  * question 3 ("local trust in v1, signature as an additive follow-up"),
21
21
  * this record is *not* itself an authorization check — anyone who can run
22
22
  * `chant approve` locally can write one, the same trust boundary a local
23
- * `git commit` already has. What it changes: `chant operator status` (and
24
- * any future gate-aware dispatch retry) can tell a resolved gate from a
25
- * still-pending one by finding a resolution newer than the tick that
26
- * recorded it. It does **not** (v1) retroactively make a gated op's local
27
- * dispatch succeed — the local executor still refuses any op containing a
28
- * gate outright (`../op/local-executor.ts`'s `LocalGateUnsupportedError`),
29
- * unconditionally, gate resolution or not. Wiring an approved gate back
30
- * into the local executor's dispatch path is the GateStep semantic change
31
- * issue #1485 itself flags as open question 1 ("suspension → fact... does
32
- * it land as its own issue first? Leaning: yes, split it out") — deferred
33
- * here for the same reason the issue defers it: it touches both the local
34
- * executor and the generated Temporal workflow, and has migration impact on
35
- * shipped ops. `chant approve` in v1 is the durable, queryable record of
36
- * "this gate is cleared" that a human (or a future auto-resume path) reads;
37
- * it is not itself the unblock.
23
+ * commit already has.
24
+ *
25
+ * Since #2119 the file carries both halves of the loop. A `gate` step the
26
+ * local executor reaches (`../op/local-executor.ts`) appends a
27
+ * {@link PendingGateRecord} and ends that run with status `gated`; `chant
28
+ * approve` appends the {@link GateResolutionRecord} that answers it; the next
29
+ * run reads both, finds a resolution newer than the pending fact, and walks
30
+ * through the gate carrying the approver. A resolution older than the newest
31
+ * pending fact is not an answer to it — {@link latestResolutionSince} applies
32
+ * that rule for the executor and for `chant operator status` alike. Both kinds
33
+ * of line share one file, told apart by {@link GateLedgerRecord}'s `kind`
34
+ * (absent on the resolution lines written before #2119, which is why
35
+ * `"resolution"` is the default reading).
38
36
  */
39
37
  import { sortedJsonReplacer } from "../utils";
40
38
  import { readBlobFromPath, readPathSha, readBlobBySha, writeBlobToPath, RefCASConflictError } from "./git";
@@ -98,11 +96,13 @@ export function isApprovalUrl(raw: string): boolean {
98
96
  export interface GateResolutionRecord {
99
97
  /** Schema version, so an incompatible future shape is detected before being misread. */
100
98
  version: 1;
99
+ /** Discriminator against {@link PendingGateRecord}, which shares this file. Absent on every line written before #2119, so a reader must treat "no kind" as `"resolution"`. */
100
+ kind?: "resolution";
101
101
  /** The dispatched op the gate belongs to. */
102
102
  op: string;
103
103
  /** The gate's signal name (matches `ConvergeRuleOutcome.gateName`). */
104
104
  gate: string;
105
- /** Who resolved it — an actor name, the same convention `components release --actor` and `run signal --approver` use. */
105
+ /** Who resolved it — an actor name, the same convention `components release --actor` and `run approve --approver` use. */
106
106
  resolvedBy: string;
107
107
  /** ISO-8601 timestamp, caller-supplied (library code never calls `Date.now()` internally). */
108
108
  timestamp: string;
@@ -121,7 +121,53 @@ export interface GateResolutionRecord {
121
121
  url?: string;
122
122
  }
123
123
 
124
- export type GateResolutionInput = Omit<GateResolutionRecord, "version">;
124
+ export type GateResolutionInput = Omit<GateResolutionRecord, "version" | "kind">;
125
+
126
+ /**
127
+ * One immutable pending-gate record (#2119) — what an executor writes when a
128
+ * run reaches a `gate` step with no resolution standing against it. The run
129
+ * ends here with status `gated`; this line is the durable trace of that, and
130
+ * the anchor a later {@link GateResolutionRecord} has to be newer than to
131
+ * count as its answer.
132
+ *
133
+ * Idempotent by design: a run that finds a live (unexpired) pending fact for
134
+ * the same gate reuses it rather than appending a second one, so a converge
135
+ * loop ticking every minute against an unapproved gate does not grow the
136
+ * ledger by a line a minute. Once `expiresAt` passes, the fact is stale and
137
+ * the next run records a fresh one.
138
+ */
139
+ export interface PendingGateRecord {
140
+ /** Schema version, so an incompatible future shape is detected before being misread. */
141
+ version: 1;
142
+ kind: "pending";
143
+ /** The op (or, on the component driver, the component) the gate belongs to. */
144
+ op: string;
145
+ /** The gate's signal name (matches `GateStep.signalName`). */
146
+ gate: string;
147
+ /** The gate's human-readable description, when it declared one — what `chant operator status` shows a reader who wasn't there for the run. */
148
+ description?: string;
149
+ /** The run that reached the gate, when the caller identifies its runs. */
150
+ runId?: string;
151
+ /** ISO-8601 timestamp, caller-supplied (library code never calls `Date.now()` internally). */
152
+ timestamp: string;
153
+ /** ISO-8601 expiry, from the gate's `timeout` (default {@link DEFAULT_GATE_EXPIRY}). Past it, the fact is stale and a run re-records it. */
154
+ expiresAt: string;
155
+ /** The address approval happens at, when the run knew one — see {@link resolveApprovalUrl}. */
156
+ url?: string;
157
+ }
158
+
159
+ export type PendingGateInput = Omit<PendingGateRecord, "version" | "kind">;
160
+
161
+ /** Either kind of line in `_gates/<op>.jsonl`. */
162
+ export type GateLedgerRecord = GateResolutionRecord | PendingGateRecord;
163
+
164
+ /** The default a gate's pending fact expires after when its `GateStep` declares no `timeout` — the same 48h `GateStep.timeout` documents as its own default. */
165
+ export const DEFAULT_GATE_EXPIRY = "48h";
166
+
167
+ /** Whether a pending fact has aged out of relevance at `nowIso`. */
168
+ export function isPendingGateExpired(record: PendingGateRecord, nowIso: string): boolean {
169
+ return new Date(record.expiresAt).getTime() <= new Date(nowIso).getTime();
170
+ }
125
171
 
126
172
  function filename(op: string): string {
127
173
  return `${op}.jsonl`;
@@ -133,6 +179,30 @@ export async function appendGateResolution(
133
179
  opts?: { cwd?: string },
134
180
  ): Promise<{ commit: string; record: GateResolutionRecord }> {
135
181
  const record: GateResolutionRecord = { version: 1, ...input };
182
+ const commit = await appendGateLine(record, "Gate resolution record", opts);
183
+ return { commit, record };
184
+ }
185
+
186
+ /**
187
+ * Append one immutable pending-gate record (#2119) — the fact a run leaves
188
+ * behind when it reaches a gate nobody has approved. Same file, same
189
+ * append-and-retry discipline, same "does not push" contract as
190
+ * {@link appendGateResolution}.
191
+ */
192
+ export async function appendPendingGate(
193
+ input: PendingGateInput,
194
+ opts?: { cwd?: string },
195
+ ): Promise<{ commit: string; record: PendingGateRecord }> {
196
+ const record: PendingGateRecord = { version: 1, kind: "pending", ...input };
197
+ const commit = await appendGateLine(record, "Pending gate record", opts);
198
+ return { commit, record };
199
+ }
200
+
201
+ async function appendGateLine(
202
+ record: GateLedgerRecord,
203
+ message: string,
204
+ opts?: { cwd?: string },
205
+ ): Promise<string> {
136
206
  const json = JSON.stringify(record, sortedJsonReplacer);
137
207
 
138
208
  let lastErr: unknown;
@@ -141,11 +211,10 @@ export async function appendGateResolution(
141
211
  const priorSha = await readPathSha(DIR, filename(record.op), opts);
142
212
  const existing = priorSha ? await readBlobBySha(priorSha, opts) : null;
143
213
  const content = existing ? `${existing.replace(/\n$/, "")}\n${json}` : json;
144
- const commit = await writeBlobToPath(DIR, filename(record.op), content, "Gate resolution record", {
214
+ return await writeBlobToPath(DIR, filename(record.op), content, message, {
145
215
  ...opts,
146
216
  expectPriorPathSha: priorSha,
147
217
  });
148
- return { commit, record };
149
218
  } catch (err) {
150
219
  if (!(err instanceof RefCASConflictError)) throw err;
151
220
  lastErr = err;
@@ -154,36 +223,74 @@ export async function appendGateResolution(
154
223
  throw lastErr;
155
224
  }
156
225
 
157
- /** Read every gate-resolution record for `op`, oldest first. Malformed lines are skipped, not thrown on, the same graceful-degradation stance `readConvergeLedger` takes. Returns `[]` (never throws) when `op` has no resolutions recorded yet. */
158
- export async function readGateResolutions(
226
+ /**
227
+ * Read every line of `op`'s gate file, oldest first, split into the two kinds
228
+ * (#2119). Malformed lines are skipped and counted, not thrown on — the same
229
+ * graceful-degradation stance `readConvergeLedger` takes. Returns empty arrays
230
+ * (never throws) when `op` has nothing recorded yet.
231
+ */
232
+ export async function readGateLedger(
159
233
  op: string,
160
234
  opts?: { cwd?: string },
161
- ): Promise<{ records: GateResolutionRecord[]; malformed: number }> {
235
+ ): Promise<{ resolutions: GateResolutionRecord[]; pending: PendingGateRecord[]; malformed: number }> {
162
236
  const content = await readBlobFromPath(DIR, filename(op), opts);
163
- if (!content) return { records: [], malformed: 0 };
237
+ if (!content) return { resolutions: [], pending: [], malformed: 0 };
164
238
 
165
239
  const lines = content.split("\n").map((l) => l.trim()).filter(Boolean);
166
- const records: GateResolutionRecord[] = [];
240
+ const resolutions: GateResolutionRecord[] = [];
241
+ const pending: PendingGateRecord[] = [];
167
242
  let malformed = 0;
168
243
  for (const line of lines) {
169
244
  try {
170
- const parsed = JSON.parse(line) as Partial<GateResolutionRecord>;
171
- if (
172
- parsed.version !== 1 ||
173
- typeof parsed.op !== "string" ||
174
- typeof parsed.gate !== "string" ||
175
- typeof parsed.resolvedBy !== "string" ||
176
- typeof parsed.timestamp !== "string"
177
- ) {
245
+ const parsed = JSON.parse(line) as Partial<Omit<GateResolutionRecord, "kind">> &
246
+ Partial<Omit<PendingGateRecord, "kind">> & { kind?: string };
247
+ const common =
248
+ parsed.version === 1 && typeof parsed.op === "string" &&
249
+ typeof parsed.gate === "string" && typeof parsed.timestamp === "string";
250
+ if (!common) {
251
+ malformed++;
252
+ continue;
253
+ }
254
+ if (parsed.kind === "pending") {
255
+ if (typeof parsed.expiresAt !== "string") {
256
+ malformed++;
257
+ continue;
258
+ }
259
+ pending.push(parsed as PendingGateRecord);
260
+ continue;
261
+ }
262
+ if (typeof parsed.resolvedBy !== "string") {
178
263
  malformed++;
179
264
  continue;
180
265
  }
181
- records.push(parsed as GateResolutionRecord);
266
+ resolutions.push(parsed as GateResolutionRecord);
182
267
  } catch {
183
268
  malformed++;
184
269
  }
185
270
  }
186
- return { records, malformed };
271
+ return { resolutions, pending, malformed };
272
+ }
273
+
274
+ /** Read every gate-*resolution* record for `op`, oldest first — {@link readGateLedger} narrowed to the half every caller before #2119 wanted. Pending facts are not malformed lines and are not counted as such. */
275
+ export async function readGateResolutions(
276
+ op: string,
277
+ opts?: { cwd?: string },
278
+ ): Promise<{ records: GateResolutionRecord[]; malformed: number }> {
279
+ const { resolutions, malformed } = await readGateLedger(op, opts);
280
+ return { records: resolutions, malformed };
281
+ }
282
+
283
+ /** The most recent pending fact for `gate`, expired or not — the anchor {@link latestResolutionSince} measures a resolution against. `undefined` when the gate has never been recorded pending. */
284
+ export function latestPendingGate(
285
+ records: PendingGateRecord[],
286
+ gate: string,
287
+ ): PendingGateRecord | undefined {
288
+ let latest: PendingGateRecord | undefined;
289
+ for (const r of records) {
290
+ if (r.gate !== gate) continue;
291
+ if (!latest || new Date(r.timestamp).getTime() >= new Date(latest.timestamp).getTime()) latest = r;
292
+ }
293
+ return latest;
187
294
  }
188
295
 
189
296
  /** The most recent resolution for `gate` recorded after `sinceIso` (a gated tick's own timestamp) — what `chant operator status` uses to tell a resolved gate from a still-pending one. `undefined` when no such resolution exists. */
@@ -20,5 +20,6 @@ export * from "./teardown";
20
20
  export * from "./assert-live";
21
21
  export * from "./symptoms";
22
22
  export * from "./converge-ledger";
23
+ export * from "./run-ledger";
23
24
  export * from "./scenario";
24
25
  export * from "./scenario-eval";
@@ -98,8 +98,8 @@ export interface ReleaseRecord {
98
98
  /**
99
99
  * Who approved the change at its durable approval gate (#1035). Populated for
100
100
  * a gated change — the same identity the approver supplied when they cleared
101
- * the Temporal gate (`chant run signal ... --approver`, carried in the gate
102
- * signal payload and thus the workflow history). Optional and omitted for an
101
+ * the gate (`chant approve <op> <gate> --approver`, recorded on the gate
102
+ * resolution fact). Optional and omitted for an
103
103
  * ungated change (nothing to approve), so `actor` alone answers "who made
104
104
  * it" while `approver` answers "who approved it" only where a gate exists.
105
105
  * Distinct from `actor`: the approver is deliberately a different person than
@@ -0,0 +1,268 @@
1
+ /**
2
+ * Run ledger tests (#2118) — the same git fixture `converge-ledger.test.ts`
3
+ * uses, so the two ledgers are exercised against one real orphan branch and a
4
+ * change to the shared plumbing under them fails in both places.
5
+ */
6
+ import { describe, test, expect } from "vitest";
7
+ import { withTestDir } from "@intentius/chant-test-utils";
8
+ import { spawnSync } from "node:child_process";
9
+ import { writeFileSync } from "node:fs";
10
+ import { join } from "node:path";
11
+ import {
12
+ appendRunRecord,
13
+ readRunLedger,
14
+ buildRunRecord,
15
+ runEnvOf,
16
+ DEFAULT_RUN_ENV,
17
+ } from "./run-ledger";
18
+ import type { OpRunRecordInput } from "../op/runtime";
19
+ import { appendConvergeRecord, readConvergeLedger } from "./converge-ledger";
20
+ import { runOpLocally, OpRunFailure } from "../op/local-executor";
21
+ import type { ActivityFn } from "../op/activity-registry";
22
+ import type { OpConfig } from "../op/types";
23
+
24
+ function git(args: string[], cwd: string): { stdout: string; exitCode: number } {
25
+ const r = spawnSync("git", args, { cwd, encoding: "utf-8" });
26
+ return { stdout: r.stdout ?? "", exitCode: r.status ?? -1 };
27
+ }
28
+
29
+ async function initRepo(dir: string): Promise<void> {
30
+ git(["init", "-q", "-b", "main"], dir);
31
+ git(["config", "user.email", "test@chant.dev"], dir);
32
+ git(["config", "user.name", "Test"], dir);
33
+ writeFileSync(join(dir, "README.md"), "fixture\n");
34
+ git(["add", "README.md"], dir);
35
+ git(["commit", "-q", "-m", "init"], dir);
36
+ }
37
+
38
+ function makeInput(overrides?: Partial<OpRunRecordInput>): OpRunRecordInput {
39
+ return {
40
+ op: "alb-deploy",
41
+ env: "staging",
42
+ started: "2026-01-01T00:00:00.000Z",
43
+ ended: "2026-01-01T00:00:12.000Z",
44
+ status: "ok",
45
+ labels: { Apply: "true", Env: "staging" },
46
+ outcomes: {},
47
+ phases: [{ name: "Apply", status: "ok", steps: [{ fn: "kubectlApply", status: "ok", durationMs: 12_000 }] }],
48
+ ...overrides,
49
+ };
50
+ }
51
+
52
+ const NO_PROFILES = {};
53
+
54
+ function opConfig(overrides?: Partial<OpConfig>): OpConfig {
55
+ return {
56
+ name: "alb-deploy",
57
+ overview: "deploy the alb",
58
+ labels: { Env: "staging" },
59
+ phases: [{ name: "Apply", steps: [{ kind: "activity", fn: "ok", args: {} }] }],
60
+ ...overrides,
61
+ };
62
+ }
63
+
64
+ describe("runEnvOf", () => {
65
+ test("reads labels.Env", () => {
66
+ expect(runEnvOf({ labels: { Env: "prod" } })).toBe("prod");
67
+ });
68
+
69
+ test("falls back to the default env when the Op declares no labels", () => {
70
+ expect(runEnvOf({})).toBe(DEFAULT_RUN_ENV);
71
+ expect(runEnvOf({ labels: { Apply: "true" } })).toBe(DEFAULT_RUN_ENV);
72
+ });
73
+ });
74
+
75
+ describe("buildRunRecord", () => {
76
+ test("groups step records into phases in execution order and folds outcomes", () => {
77
+ const input = buildRunRecord(
78
+ { name: "watch", labels: { Watch: "true", Env: "prod" } },
79
+ [
80
+ { phase: "Snapshot", fn: "lifecycleSnapshot", status: "ok", durationMs: 5 },
81
+ { phase: "Diff", fn: "lifecycleDiff", status: "ok", durationMs: 7, outcome: { name: "Drift", value: true } },
82
+ { phase: "Diff", fn: "report", status: "skipped", durationMs: 0 },
83
+ ],
84
+ { started: "2026-01-01T00:00:00.000Z", ended: "2026-01-01T00:00:01.000Z", status: "ok" },
85
+ );
86
+
87
+ expect(input.env).toBe("prod");
88
+ expect(input.status).toBe("ok");
89
+ expect(input.labels).toEqual({ Watch: "true", Env: "prod" });
90
+ expect(input.outcomes).toEqual({ Drift: true });
91
+ expect(input.phases.map((p) => [p.name, p.status])).toEqual([["Snapshot", "ok"], ["Diff", "ok"]]);
92
+ expect(input.phases[1].steps.map((s) => s.status)).toEqual(["ok", "skipped"]);
93
+ });
94
+
95
+ test("a phase with a failed step is fail; a phase whose steps all skipped is skipped", () => {
96
+ const input = buildRunRecord(
97
+ { name: "deploy" },
98
+ [
99
+ { phase: "Apply", fn: "kubectlApply", status: "fail", durationMs: 3, error: "boom" },
100
+ { phase: "Verify", fn: "httpCheck", status: "skipped", durationMs: 0 },
101
+ ],
102
+ { started: "2026-01-01T00:00:00.000Z", ended: "2026-01-01T00:00:01.000Z", status: "fail" },
103
+ );
104
+
105
+ expect(input.status).toBe("fail");
106
+ expect(input.env).toBe(DEFAULT_RUN_ENV);
107
+ expect(input.phases.map((p) => [p.name, p.status])).toEqual([["Apply", "fail"], ["Verify", "skipped"]]);
108
+ expect(input.phases[0].steps[0].error).toBe("boom");
109
+ });
110
+ });
111
+
112
+ describe("appendRunRecord / readRunLedger", () => {
113
+ test("round-trips a single record", async () => {
114
+ await withTestDir(async (dir) => {
115
+ await initRepo(dir);
116
+ const { record } = await appendRunRecord(makeInput(), { cwd: dir });
117
+ expect(record.version).toBe(1);
118
+ expect(record.id).toMatch(/^[0-9a-f-]{36}$/);
119
+
120
+ const { records, malformed } = await readRunLedger("staging", "alb-deploy", { cwd: dir });
121
+ expect(malformed).toBe(0);
122
+ expect(records).toEqual([record]);
123
+ });
124
+ });
125
+
126
+ test("appends without clobbering prior records", async () => {
127
+ await withTestDir(async (dir) => {
128
+ await initRepo(dir);
129
+ await appendRunRecord(makeInput({ ended: "2026-01-01T00:00:12.000Z" }), { cwd: dir });
130
+ await appendRunRecord(makeInput({ ended: "2026-01-01T00:10:12.000Z" }), { cwd: dir });
131
+ await appendRunRecord(makeInput({ ended: "2026-01-01T00:20:12.000Z" }), { cwd: dir });
132
+
133
+ const { records } = await readRunLedger("staging", "alb-deploy", { cwd: dir });
134
+ expect(records.map((r) => r.ended)).toEqual([
135
+ "2026-01-01T00:00:12.000Z",
136
+ "2026-01-01T00:10:12.000Z",
137
+ "2026-01-01T00:20:12.000Z",
138
+ ]);
139
+ });
140
+ });
141
+
142
+ test("one op's ledger is not another's, and neither disturbs the converge ledger in the same env", async () => {
143
+ await withTestDir(async (dir) => {
144
+ await initRepo(dir);
145
+ await appendRunRecord(makeInput({ op: "alb-deploy" }), { cwd: dir });
146
+ await appendRunRecord(makeInput({ op: "alb-teardown" }), { cwd: dir });
147
+ await appendConvergeRecord(
148
+ {
149
+ op: "staging-converge",
150
+ env: "staging",
151
+ timestamp: "2026-01-01T00:00:00.000Z",
152
+ firedRuleIds: [],
153
+ outcomes: [],
154
+ summary: { drifted: 0, remediated: 0, reported: 0, skippedBudget: 0, skippedFlap: 0, unobserved: 0, adopted: 0 },
155
+ log: "converge(staging): drifted=0",
156
+ },
157
+ { cwd: dir },
158
+ );
159
+
160
+ expect((await readRunLedger("staging", "alb-deploy", { cwd: dir })).records).toHaveLength(1);
161
+ expect((await readRunLedger("staging", "alb-teardown", { cwd: dir })).records).toHaveLength(1);
162
+ expect((await readConvergeLedger("staging", { cwd: dir })).records).toHaveLength(1);
163
+ });
164
+ });
165
+
166
+ test("an unwritten ledger reads empty — never throws", async () => {
167
+ await withTestDir(async (dir) => {
168
+ await initRepo(dir);
169
+ const { records, malformed } = await readRunLedger("nowhere", "nothing", { cwd: dir });
170
+ expect(records).toEqual([]);
171
+ expect(malformed).toBe(0);
172
+ });
173
+ });
174
+
175
+ test("skips malformed lines but reports the count", async () => {
176
+ await withTestDir(async (dir) => {
177
+ await initRepo(dir);
178
+ await appendRunRecord(makeInput(), { cwd: dir });
179
+
180
+ const { readBlobFromPath, writeBlobToPath } = await import("./git");
181
+ const existing = await readBlobFromPath("staging", "runs__alb-deploy.jsonl", { cwd: dir });
182
+ const corrupted = `${existing}\n${JSON.stringify({ version: 1, op: "alb-deploy", env: "staging" })}`;
183
+ await writeBlobToPath("staging", "runs__alb-deploy.jsonl", corrupted, "corrupt", { cwd: dir });
184
+
185
+ const { records, malformed } = await readRunLedger("staging", "alb-deploy", { cwd: dir });
186
+ expect(records).toHaveLength(1);
187
+ expect(malformed).toBe(1);
188
+ });
189
+ });
190
+
191
+ test("a gated record written by hand round-trips (#2119 produces these; the type carries the status now)", async () => {
192
+ await withTestDir(async (dir) => {
193
+ await initRepo(dir);
194
+ await appendRunRecord(
195
+ makeInput({
196
+ status: "gated",
197
+ phases: [
198
+ { name: "Plan", status: "ok", steps: [{ fn: "terraformPlan", status: "ok", durationMs: 900 }] },
199
+ { name: "Approve", status: "skipped", steps: [{ fn: "gate:approve-apply", status: "skipped", durationMs: 0 }] },
200
+ ],
201
+ }),
202
+ { cwd: dir },
203
+ );
204
+
205
+ const { records } = await readRunLedger("staging", "alb-deploy", { cwd: dir });
206
+ expect(records[0].status).toBe("gated");
207
+ expect(records[0].phases[1]).toMatchObject({ name: "Approve", status: "skipped" });
208
+ });
209
+ });
210
+ });
211
+
212
+ describe("runOpLocally → the run ledger", () => {
213
+ test("a successful run appends exactly one record, and the result carries the same document", async () => {
214
+ await withTestDir(async (dir) => {
215
+ await initRepo(dir);
216
+ const activities = new Map<string, ActivityFn>([["ok", async () => ({ drifted: false })]]);
217
+ const config = opConfig({
218
+ phases: [
219
+ {
220
+ name: "Apply",
221
+ steps: [{ kind: "activity", fn: "ok", args: {}, outcomeAttribute: { name: "Drift", from: "drifted" } }],
222
+ },
223
+ ],
224
+ });
225
+
226
+ const result = await runOpLocally(config, activities, NO_PROFILES, undefined, { ledger: { cwd: dir } });
227
+
228
+ const { records } = await readRunLedger("staging", "alb-deploy", { cwd: dir });
229
+ expect(records).toHaveLength(1);
230
+ expect(records[0]).toEqual(result.record);
231
+ expect(records[0].status).toBe("ok");
232
+ expect(records[0].outcomes).toEqual({ Drift: false });
233
+ expect(records[0].labels).toEqual({ Env: "staging" });
234
+ });
235
+ });
236
+
237
+ test("a failed run appends a fail record and still rejects with OpRunFailure", async () => {
238
+ await withTestDir(async (dir) => {
239
+ await initRepo(dir);
240
+ const activities = new Map<string, ActivityFn>([
241
+ ["ok", async () => { throw new Error("boom"); }],
242
+ ]);
243
+
244
+ const err = await runOpLocally(opConfig(), activities, NO_PROFILES, undefined, { ledger: { cwd: dir } })
245
+ .catch((e: unknown) => e);
246
+ expect(err).toBeInstanceOf(OpRunFailure);
247
+
248
+ const { records } = await readRunLedger("staging", "alb-deploy", { cwd: dir });
249
+ expect(records).toHaveLength(1);
250
+ expect(records[0].status).toBe("fail");
251
+ expect(records[0].phases[0].steps[0].error).toContain("boom");
252
+ expect((err as OpRunFailure).result.record).toEqual(records[0]);
253
+ });
254
+ });
255
+
256
+ test("without the ledger option a run writes nothing, but still carries its record", async () => {
257
+ await withTestDir(async (dir) => {
258
+ await initRepo(dir);
259
+ const activities = new Map<string, ActivityFn>([["ok", async () => ({})]]);
260
+
261
+ const result = await runOpLocally(opConfig(), activities, NO_PROFILES);
262
+
263
+ expect(result.record.op).toBe("alb-deploy");
264
+ expect(result.record.status).toBe("ok");
265
+ expect((await readRunLedger("staging", "alb-deploy", { cwd: dir })).records).toEqual([]);
266
+ });
267
+ });
268
+ });