@intentius/chant 0.57.0 → 0.59.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 (558) 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 +36 -1
  4. package/dist/audit/core.d.ts.map +1 -1
  5. package/dist/audit/discover.d.ts +26 -9
  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/carve-bridge.d.ts.map +1 -1
  34. package/dist/cli/commands/check-lexicon-docs.d.ts +1 -1
  35. package/dist/cli/commands/check-lexicon-examples.d.ts +16 -6
  36. package/dist/cli/commands/check-lexicon-examples.d.ts.map +1 -1
  37. package/dist/cli/commands/init-lexicon/templates/codegen.d.ts +1 -0
  38. package/dist/cli/commands/init-lexicon/templates/codegen.d.ts.map +1 -1
  39. package/dist/cli/commands/init-lexicon/templates/project.d.ts +7 -0
  40. package/dist/cli/commands/init-lexicon/templates/project.d.ts.map +1 -1
  41. package/dist/cli/commands/init-lexicon.d.ts.map +1 -1
  42. package/dist/cli/commands/init.d.ts.map +1 -1
  43. package/dist/cli/commands/lint.d.ts +13 -0
  44. package/dist/cli/commands/lint.d.ts.map +1 -1
  45. package/dist/cli/commands/onboard.d.ts +2 -0
  46. package/dist/cli/commands/onboard.d.ts.map +1 -1
  47. package/dist/cli/handlers/build.d.ts.map +1 -1
  48. package/dist/cli/handlers/lifecycle.d.ts.map +1 -1
  49. package/dist/cli/handlers/operator.d.ts +82 -16
  50. package/dist/cli/handlers/operator.d.ts.map +1 -1
  51. package/dist/cli/handlers/run.d.ts +49 -42
  52. package/dist/cli/handlers/run.d.ts.map +1 -1
  53. package/dist/cli/main.d.ts +12 -1
  54. package/dist/cli/main.d.ts.map +1 -1
  55. package/dist/cli/mcp/op-tools.d.ts +16 -1
  56. package/dist/cli/mcp/op-tools.d.ts.map +1 -1
  57. package/dist/cli/mcp/resource-handlers.d.ts.map +1 -1
  58. package/dist/cli/mcp/resources/context.d.ts.map +1 -1
  59. package/dist/cli/plugins.d.ts +22 -0
  60. package/dist/cli/plugins.d.ts.map +1 -1
  61. package/dist/cli/registry.d.ts +40 -9
  62. package/dist/cli/registry.d.ts.map +1 -1
  63. package/dist/codegen/generate-post-synth-barrel.d.ts +1 -1
  64. package/dist/components/auto-release.d.ts +10 -20
  65. package/dist/components/auto-release.d.ts.map +1 -1
  66. package/dist/components/capability.d.ts +4 -4
  67. package/dist/components/cli-support.d.ts +24 -22
  68. package/dist/components/cli-support.d.ts.map +1 -1
  69. package/dist/components/component.d.ts +21 -6
  70. package/dist/components/component.d.ts.map +1 -1
  71. package/dist/components/driver-output.d.ts.map +1 -1
  72. package/dist/components/driver.d.ts +73 -37
  73. package/dist/components/driver.d.ts.map +1 -1
  74. package/dist/components/index.d.ts +1 -2
  75. package/dist/components/index.d.ts.map +1 -1
  76. package/dist/components/manifest-persistence.d.ts +1 -9
  77. package/dist/components/manifest-persistence.d.ts.map +1 -1
  78. package/dist/components/run-progress.d.ts +6 -6
  79. package/dist/components/run-progress.d.ts.map +1 -1
  80. package/dist/components/verbs/run-agent.d.ts +10 -22
  81. package/dist/components/verbs/run-agent.d.ts.map +1 -1
  82. package/dist/components/verbs/verify.d.ts +2 -2
  83. package/dist/deep-observation.d.ts +17 -0
  84. package/dist/deep-observation.d.ts.map +1 -1
  85. package/dist/detectLexicon.d.ts +13 -0
  86. package/dist/detectLexicon.d.ts.map +1 -1
  87. package/dist/discovery/files.d.ts +4 -4
  88. package/dist/discovery/fold-import.d.ts.map +1 -1
  89. package/dist/discovery/param-deps.d.ts +33 -0
  90. package/dist/discovery/param-deps.d.ts.map +1 -1
  91. package/dist/discovery/sandbox/bundle.d.ts.map +1 -1
  92. package/dist/discovery/sandbox/config-wire.d.ts +2 -2
  93. package/dist/fold/foldable-helpers.d.ts +5 -1
  94. package/dist/fold/foldable-helpers.d.ts.map +1 -1
  95. package/dist/fold-provenance.d.ts +236 -0
  96. package/dist/fold-provenance.d.ts.map +1 -0
  97. package/dist/graph-ops.d.ts +1 -1
  98. package/dist/held-elsewhere.d.ts +85 -0
  99. package/dist/held-elsewhere.d.ts.map +1 -0
  100. package/dist/index.d.ts +3 -0
  101. package/dist/index.d.ts.map +1 -1
  102. package/dist/kubectl-context.d.ts +3 -3
  103. package/dist/lexicon-config.d.ts +4 -4
  104. package/dist/lexicon-plugin-helpers.d.ts +1 -1
  105. package/dist/lexicon.d.ts +273 -16
  106. package/dist/lexicon.d.ts.map +1 -1
  107. package/dist/lifecycle/change-set.d.ts +13 -0
  108. package/dist/lifecycle/change-set.d.ts.map +1 -1
  109. package/dist/lifecycle/deep-diff.d.ts +133 -6
  110. package/dist/lifecycle/deep-diff.d.ts.map +1 -1
  111. package/dist/lifecycle/deep-observe.d.ts.map +1 -1
  112. package/dist/lifecycle/gate-ledger.d.ts +79 -3
  113. package/dist/lifecycle/gate-ledger.d.ts.map +1 -1
  114. package/dist/lifecycle/index.d.ts +1 -0
  115. package/dist/lifecycle/index.d.ts.map +1 -1
  116. package/dist/lifecycle/release-ledger.d.ts +2 -2
  117. package/dist/lifecycle/run-ledger.d.ts +53 -0
  118. package/dist/lifecycle/run-ledger.d.ts.map +1 -0
  119. package/dist/lifecycle/symptoms.d.ts +3 -3
  120. package/dist/lifecycle/symptoms.d.ts.map +1 -1
  121. package/dist/lint/config.d.ts +81 -2
  122. package/dist/lint/config.d.ts.map +1 -1
  123. package/dist/lint/post-synth.d.ts +55 -2
  124. package/dist/lint/post-synth.d.ts.map +1 -1
  125. package/dist/lint/rules/__fixtures__/comp/comp004/pass/neo4j-cluster.component.d.ts +1 -1
  126. package/dist/lint/rules/__fixtures__/comp/comp004/pass/neo4j-cluster.component.d.ts.map +1 -1
  127. package/dist/lint/rules/__fixtures__/comp/comp004/pass/orders-table.component.d.ts +1 -1
  128. package/dist/lint/rules/__fixtures__/comp/comp004/pass/orders-table.component.d.ts.map +1 -1
  129. package/dist/lint/rules/comp/comp004-gate-needs-durable-runtime.d.ts +44 -0
  130. package/dist/lint/rules/comp/comp004-gate-needs-durable-runtime.d.ts.map +1 -0
  131. package/dist/lint/rules/comp/index.d.ts +1 -1
  132. package/dist/lint/rules/comp/index.d.ts.map +1 -1
  133. package/dist/lint/rules/op/index.d.ts +16 -0
  134. package/dist/lint/rules/op/index.d.ts.map +1 -0
  135. package/dist/lint/rules/op/ops012-activity-contract.d.ts +27 -0
  136. package/dist/lint/rules/op/ops012-activity-contract.d.ts.map +1 -0
  137. package/dist/lint/rules/op/ops013-step-output-ref.d.ts +61 -0
  138. package/dist/lint/rules/op/ops013-step-output-ref.d.ts.map +1 -0
  139. package/dist/lint/rules/op/ops014-converge-rule-refusals.d.ts +55 -0
  140. package/dist/lint/rules/op/ops014-converge-rule-refusals.d.ts.map +1 -0
  141. package/dist/lint/rules/op/support.d.ts +12 -0
  142. package/dist/lint/rules/op/support.d.ts.map +1 -0
  143. package/dist/lint/suppressions.d.ts +108 -0
  144. package/dist/lint/suppressions.d.ts.map +1 -0
  145. package/dist/live-endpoint.d.ts +1 -1
  146. package/dist/op/activities/activity-contracts.d.ts +54 -0
  147. package/dist/op/activities/activity-contracts.d.ts.map +1 -0
  148. package/dist/op/activities/apply.d.ts +262 -0
  149. package/dist/op/activities/apply.d.ts.map +1 -0
  150. package/dist/op/activities/build.d.ts +13 -0
  151. package/dist/op/activities/build.d.ts.map +1 -0
  152. package/dist/op/activities/converge.d.ts +110 -0
  153. package/dist/op/activities/converge.d.ts.map +1 -0
  154. package/dist/op/activities/env-teardown.d.ts +76 -0
  155. package/dist/op/activities/env-teardown.d.ts.map +1 -0
  156. package/dist/op/activities/guard-validate.d.ts +64 -0
  157. package/dist/op/activities/guard-validate.d.ts.map +1 -0
  158. package/dist/op/activities/http-check.d.ts +34 -0
  159. package/dist/op/activities/http-check.d.ts.map +1 -0
  160. package/dist/op/activities/index.d.ts +31 -0
  161. package/dist/op/activities/index.d.ts.map +1 -0
  162. package/dist/op/activities/lexicon-upgrade.d.ts +182 -0
  163. package/dist/op/activities/lexicon-upgrade.d.ts.map +1 -0
  164. package/dist/op/activities/lifecycle.d.ts +42 -0
  165. package/dist/op/activities/lifecycle.d.ts.map +1 -0
  166. package/dist/op/activities/pipeline-audit.d.ts +84 -0
  167. package/dist/op/activities/pipeline-audit.d.ts.map +1 -0
  168. package/dist/op/activities/policy.d.ts +17 -0
  169. package/dist/op/activities/policy.d.ts.map +1 -0
  170. package/dist/op/activities/reconcile.d.ts +149 -0
  171. package/dist/op/activities/reconcile.d.ts.map +1 -0
  172. package/dist/op/activities/shell.d.ts +13 -0
  173. package/dist/op/activities/shell.d.ts.map +1 -0
  174. package/dist/op/activities/teardown.d.ts +10 -0
  175. package/dist/op/activities/teardown.d.ts.map +1 -0
  176. package/dist/op/activities/util.d.ts +7 -0
  177. package/dist/op/activities/util.d.ts.map +1 -0
  178. package/dist/op/activities/wait.d.ts +28 -0
  179. package/dist/op/activities/wait.d.ts.map +1 -0
  180. package/dist/op/activities/workflow-audit.d.ts +92 -0
  181. package/dist/op/activities/workflow-audit.d.ts.map +1 -0
  182. package/dist/op/activity-contract-registry.d.ts +76 -0
  183. package/dist/op/activity-contract-registry.d.ts.map +1 -0
  184. package/dist/op/activity-contract.d.ts +25 -18
  185. package/dist/op/activity-contract.d.ts.map +1 -1
  186. package/dist/op/activity-failure.d.ts +28 -0
  187. package/dist/op/activity-failure.d.ts.map +1 -0
  188. package/dist/op/activity-profiles.d.ts +118 -0
  189. package/dist/op/activity-profiles.d.ts.map +1 -0
  190. package/dist/op/activity-registry.d.ts +22 -38
  191. package/dist/op/activity-registry.d.ts.map +1 -1
  192. package/dist/op/activity-runtime.d.ts +3 -14
  193. package/dist/op/activity-runtime.d.ts.map +1 -1
  194. package/dist/op/builders.d.ts +85 -30
  195. package/dist/op/builders.d.ts.map +1 -1
  196. package/dist/op/change-signal.d.ts +91 -0
  197. package/dist/op/change-signal.d.ts.map +1 -0
  198. package/dist/op/composites/apply-op.d.ts +118 -0
  199. package/dist/op/composites/apply-op.d.ts.map +1 -0
  200. package/dist/op/composites/converge-op.d.ts +120 -0
  201. package/dist/op/composites/converge-op.d.ts.map +1 -0
  202. package/dist/op/composites/index.d.ts +26 -0
  203. package/dist/op/composites/index.d.ts.map +1 -0
  204. package/dist/op/composites/lexicon-upgrade-op.d.ts +78 -0
  205. package/dist/op/composites/lexicon-upgrade-op.d.ts.map +1 -0
  206. package/dist/op/composites/pipeline-audit-op.d.ts +50 -0
  207. package/dist/op/composites/pipeline-audit-op.d.ts.map +1 -0
  208. package/dist/op/composites/reconcile-op.d.ts +57 -0
  209. package/dist/op/composites/reconcile-op.d.ts.map +1 -0
  210. package/dist/op/composites/watch-op.d.ts +60 -0
  211. package/dist/op/composites/watch-op.d.ts.map +1 -0
  212. package/dist/op/composites/workflow-audit-op.d.ts +57 -0
  213. package/dist/op/composites/workflow-audit-op.d.ts.map +1 -0
  214. package/dist/op/converge-rule.d.ts +12 -10
  215. package/dist/op/converge-rule.d.ts.map +1 -1
  216. package/dist/op/cron.d.ts +51 -0
  217. package/dist/op/cron.d.ts.map +1 -0
  218. package/dist/op/discover.d.ts +9 -0
  219. package/dist/op/discover.d.ts.map +1 -1
  220. package/dist/op/duration.d.ts +11 -0
  221. package/dist/op/duration.d.ts.map +1 -0
  222. package/dist/op/emulator-lifecycle.d.ts.map +1 -1
  223. package/dist/op/gate-name.d.ts +40 -0
  224. package/dist/op/gate-name.d.ts.map +1 -0
  225. package/dist/op/gate-summary.d.ts +43 -0
  226. package/dist/op/gate-summary.d.ts.map +1 -0
  227. package/dist/op/gate.d.ts +90 -0
  228. package/dist/op/gate.d.ts.map +1 -0
  229. package/dist/op/generate-pipeline.d.ts +9 -0
  230. package/dist/op/generate-pipeline.d.ts.map +1 -1
  231. package/dist/op/index.d.ts +29 -9
  232. package/dist/op/index.d.ts.map +1 -1
  233. package/dist/op/local-executor.d.ts +114 -28
  234. package/dist/op/local-executor.d.ts.map +1 -1
  235. package/dist/op/local-output.d.ts +11 -1
  236. package/dist/op/local-output.d.ts.map +1 -1
  237. package/dist/op/op-ir.d.ts +185 -0
  238. package/dist/op/op-ir.d.ts.map +1 -0
  239. package/dist/op/op-verb-class.d.ts +10 -2
  240. package/dist/op/op-verb-class.d.ts.map +1 -1
  241. package/dist/op/operator.d.ts +93 -5
  242. package/dist/op/operator.d.ts.map +1 -1
  243. package/dist/op/receipt-store.d.ts +1 -1
  244. package/dist/op/receipt-store.d.ts.map +1 -1
  245. package/dist/op/resource.d.ts +15 -1
  246. package/dist/op/resource.d.ts.map +1 -1
  247. package/dist/op/runtime.d.ts +200 -0
  248. package/dist/op/runtime.d.ts.map +1 -0
  249. package/dist/op/runtimes/local.d.ts +29 -0
  250. package/dist/op/runtimes/local.d.ts.map +1 -0
  251. package/dist/op/step-output-ref.d.ts +15 -13
  252. package/dist/op/step-output-ref.d.ts.map +1 -1
  253. package/dist/op/types.d.ts +96 -29
  254. package/dist/op/types.d.ts.map +1 -1
  255. package/dist/ownership.d.ts +1 -1
  256. package/dist/provenance.d.ts +21 -0
  257. package/dist/provenance.d.ts.map +1 -1
  258. package/dist/serializer-walker.d.ts.map +1 -1
  259. package/dist/terraform/bridge.d.ts +26 -7
  260. package/dist/terraform/bridge.d.ts.map +1 -1
  261. package/dist/terraform/carve-provider.d.ts +28 -2
  262. package/dist/terraform/carve-provider.d.ts.map +1 -1
  263. package/dist/terraform/data-source-shape.d.ts +77 -0
  264. package/dist/terraform/data-source-shape.d.ts.map +1 -0
  265. package/dist/terraform/graph.d.ts.map +1 -1
  266. package/dist/terraform/providers/kubernetes.d.ts +5 -0
  267. package/dist/terraform/providers/kubernetes.d.ts.map +1 -1
  268. package/dist/terraform/tier-map.d.ts +16 -4
  269. package/dist/terraform/tier-map.d.ts.map +1 -1
  270. package/dist/terraform/types.d.ts +8 -0
  271. package/dist/terraform/types.d.ts.map +1 -1
  272. package/package.json +6 -1
  273. package/src/audit/catalog.test.ts +79 -3
  274. package/src/audit/catalog.ts +111 -13
  275. package/src/audit/core.test.ts +26 -0
  276. package/src/audit/core.ts +84 -18
  277. package/src/audit/discover.test.ts +39 -0
  278. package/src/audit/discover.ts +202 -18
  279. package/src/audit/lineage.ts +100 -0
  280. package/src/audit/prior-art.ts +153 -0
  281. package/src/audit/proof.test.ts +67 -0
  282. package/src/audit/proof.ts +70 -0
  283. package/src/audit/report-html.ts +9 -4
  284. package/src/audit/report-model.ts +16 -2
  285. package/src/audit/report.test.ts +33 -0
  286. package/src/audit/report.ts +5 -3
  287. package/src/audit/rules-doc.test.ts +34 -2
  288. package/src/audit/rules-doc.ts +82 -9
  289. package/src/audit/secrets.ts +15 -3
  290. package/src/audit/terraform-state.test.ts +144 -0
  291. package/src/audit/terraform-state.ts +131 -0
  292. package/src/build.ts +31 -1
  293. package/src/claimed-fields.test.ts +145 -0
  294. package/src/claimed-fields.ts +208 -0
  295. package/src/cli/build-params-cli.ts +2 -2
  296. package/src/cli/command-group.ts +14 -0
  297. package/src/cli/commands/__fixtures__/audit-terraform/infra/.terraform/providers/lock.txt +1 -0
  298. package/src/cli/commands/__fixtures__/audit-terraform/infra/main.tf +1 -0
  299. package/src/cli/commands/__fixtures__/audit-terraform/infra/modules/foo/main.tf +1 -0
  300. package/src/cli/commands/__fixtures__/audit-terraform/infra/variables.tf +3 -0
  301. package/src/cli/commands/__fixtures__/init-lexicon-output/package.json +13 -4
  302. package/src/cli/commands/__fixtures__/init-lexicon-output/src/codegen/docs-cli.ts +4 -0
  303. package/src/cli/commands/__fixtures__/init-lexicon-output/src/codegen/package.ts +1 -0
  304. package/src/cli/commands/__fixtures__/init-lexicon-output/src/package-cli.ts +21 -0
  305. package/src/cli/commands/__snapshots__/init-lexicon.test.ts.snap +1 -0
  306. package/src/cli/commands/audit.test.ts +191 -11
  307. package/src/cli/commands/audit.ts +78 -28
  308. package/src/cli/commands/build.test.ts +330 -9
  309. package/src/cli/commands/build.ts +170 -24
  310. package/src/cli/commands/carve-bridge.test.ts +237 -14
  311. package/src/cli/commands/carve-bridge.ts +21 -17
  312. package/src/cli/commands/carve-emit-k8s.test.ts +17 -10
  313. package/src/cli/commands/check-lexicon-docs.ts +1 -1
  314. package/src/cli/commands/check-lexicon-examples.test.ts +29 -1
  315. package/src/cli/commands/check-lexicon-examples.ts +42 -10
  316. package/src/cli/commands/init-lexicon/templates/codegen.ts +9 -0
  317. package/src/cli/commands/init-lexicon/templates/project.ts +44 -4
  318. package/src/cli/commands/init-lexicon.test.ts +97 -2
  319. package/src/cli/commands/init-lexicon.ts +4 -2
  320. package/src/cli/commands/init.test.ts +35 -1
  321. package/src/cli/commands/init.ts +14 -3
  322. package/src/cli/commands/lint.test.ts +194 -1
  323. package/src/cli/commands/lint.ts +205 -7
  324. package/src/cli/commands/onboard.test.ts +75 -0
  325. package/src/cli/commands/onboard.ts +54 -23
  326. package/src/cli/handlers/build.test.ts +81 -1
  327. package/src/cli/handlers/build.ts +15 -1
  328. package/src/cli/handlers/graph.test.ts +3 -3
  329. package/src/cli/handlers/lifecycle.test.ts +195 -12
  330. package/src/cli/handlers/lifecycle.ts +123 -6
  331. package/src/cli/handlers/misc.ts +1 -1
  332. package/src/cli/handlers/operator.test.ts +183 -11
  333. package/src/cli/handlers/operator.ts +341 -55
  334. package/src/cli/handlers/run.test.ts +568 -1295
  335. package/src/cli/handlers/run.ts +446 -912
  336. package/src/cli/main.test.ts +22 -16
  337. package/src/cli/main.ts +66 -33
  338. package/src/cli/mcp/op-tools.ts +147 -88
  339. package/src/cli/mcp/resource-handlers.ts +10 -37
  340. package/src/cli/mcp/resources/context.ts +11 -12
  341. package/src/cli/mcp/server.test.ts +25 -27
  342. package/src/cli/mcp/server.ts +2 -2
  343. package/src/cli/plugins.test.ts +68 -2
  344. package/src/cli/plugins.ts +39 -0
  345. package/src/cli/registry.ts +40 -9
  346. package/src/codegen/docs.ts +1 -1
  347. package/src/codegen/generate-post-synth-barrel.test.ts +1 -1
  348. package/src/codegen/generate-post-synth-barrel.ts +1 -1
  349. package/src/components/README.md +4 -3
  350. package/src/components/__fixtures__/neo4j-fanout.json +1 -1
  351. package/src/components/auto-release.test.ts +4 -20
  352. package/src/components/auto-release.ts +17 -42
  353. package/src/components/capability.ts +4 -4
  354. package/src/components/cli-support.test.ts +28 -12
  355. package/src/components/cli-support.ts +60 -47
  356. package/src/components/component-schema.test.ts +18 -2
  357. package/src/components/component.schema.json +18 -5
  358. package/src/components/component.test.ts +2 -2
  359. package/src/components/component.ts +27 -7
  360. package/src/components/config-defaults.test.ts +2 -2
  361. package/src/components/driver-output.ts +14 -2
  362. package/src/components/driver.test.ts +109 -20
  363. package/src/components/driver.ts +201 -84
  364. package/src/components/index.ts +1 -6
  365. package/src/components/manifest-persistence.test.ts +2 -20
  366. package/src/components/manifest-persistence.ts +4 -24
  367. package/src/components/pilots/neo4j-fanout.pilot.ts +3 -3
  368. package/src/components/pilots/pilots-lint-clean.test.ts +11 -10
  369. package/src/components/run-progress.ts +6 -6
  370. package/src/components/verbs/run-agent.test.ts +19 -0
  371. package/src/components/verbs/run-agent.ts +12 -24
  372. package/src/components/verbs/verify.ts +2 -2
  373. package/src/deep-observation.test.ts +75 -0
  374. package/src/deep-observation.ts +48 -0
  375. package/src/detectLexicon.ts +18 -1
  376. package/src/discovery/collect.test.ts +3 -3
  377. package/src/discovery/files.ts +4 -4
  378. package/src/discovery/fold-import.test.ts +1 -1
  379. package/src/discovery/fold-import.ts +105 -2
  380. package/src/discovery/param-deps.test.ts +112 -1
  381. package/src/discovery/param-deps.ts +193 -0
  382. package/src/discovery/sandbox/bundle.ts +15 -1
  383. package/src/discovery/sandbox/config-wire.test.ts +2 -2
  384. package/src/discovery/sandbox/config-wire.ts +2 -2
  385. package/src/fold/fold.test.ts +56 -0
  386. package/src/fold/foldable-helpers.ts +82 -2
  387. package/src/fold-provenance-exclusion.test.ts +186 -0
  388. package/src/fold-provenance.test.ts +495 -0
  389. package/src/fold-provenance.ts +441 -0
  390. package/src/graph-ops.test.ts +3 -3
  391. package/src/graph-ops.ts +1 -1
  392. package/src/held-elsewhere.test.ts +102 -0
  393. package/src/held-elsewhere.ts +114 -0
  394. package/src/index.ts +3 -0
  395. package/src/kubectl-context.ts +3 -3
  396. package/src/lexicon-config.test.ts +5 -5
  397. package/src/lexicon-config.ts +4 -4
  398. package/src/lexicon-plugin-helpers.ts +1 -1
  399. package/src/lexicon.ts +296 -16
  400. package/src/lifecycle/change-set.test.ts +49 -0
  401. package/src/lifecycle/change-set.ts +67 -0
  402. package/src/lifecycle/converge-ledger.ts +1 -1
  403. package/src/lifecycle/deep-diff.test.ts +267 -14
  404. package/src/lifecycle/deep-diff.ts +217 -14
  405. package/src/lifecycle/deep-observe.test.ts +3 -1
  406. package/src/lifecycle/deep-observe.ts +16 -6
  407. package/src/lifecycle/gate-ledger.test.ts +74 -2
  408. package/src/lifecycle/gate-ledger.ts +154 -36
  409. package/src/lifecycle/index.ts +1 -0
  410. package/src/lifecycle/release-ledger.ts +2 -2
  411. package/src/lifecycle/run-ledger.test.ts +268 -0
  412. package/src/lifecycle/run-ledger.ts +201 -0
  413. package/src/lifecycle/symptoms.ts +3 -3
  414. package/src/lint/config.test.ts +82 -1
  415. package/src/lint/config.ts +112 -3
  416. package/src/lint/pipeline-change-gate.test.ts +2 -2
  417. package/src/lint/policy.test.ts +1 -1
  418. package/src/lint/post-synth.test.ts +31 -0
  419. package/src/lint/post-synth.ts +54 -1
  420. package/src/lint/rules/__fixtures__/comp/comp004/pass/neo4j-cluster.component.ts +2 -2
  421. package/src/lint/rules/__fixtures__/comp/comp004/pass/orders-table.component.ts +1 -1
  422. package/src/lint/rules/comp/comp.test.ts +27 -1
  423. package/src/lint/rules/comp/comp004-gate-needs-durable-runtime.ts +76 -0
  424. package/src/lint/rules/comp/index.ts +3 -3
  425. package/src/lint/rules/op/index.ts +23 -0
  426. package/src/lint/rules/op/ops012-activity-contract.test.ts +161 -0
  427. package/src/lint/rules/op/ops012-activity-contract.ts +64 -0
  428. package/src/lint/rules/op/ops013-step-output-ref.test.ts +237 -0
  429. package/src/lint/rules/op/ops013-step-output-ref.ts +98 -0
  430. package/src/lint/rules/op/ops014-converge-rule-refusals.test.ts +263 -0
  431. package/src/lint/rules/op/ops014-converge-rule-refusals.ts +179 -0
  432. package/src/lint/rules/op/support.ts +11 -0
  433. package/src/lint/suppressions.test.ts +163 -0
  434. package/src/lint/suppressions.ts +204 -0
  435. package/src/live-endpoint.ts +1 -1
  436. package/src/op/activities/activity-contracts.ts +66 -0
  437. package/src/op/activities/apply.test.ts +567 -0
  438. package/src/op/activities/apply.ts +662 -0
  439. package/src/op/activities/build.ts +26 -0
  440. package/src/op/activities/converge.test.ts +330 -0
  441. package/src/op/activities/converge.ts +449 -0
  442. package/src/op/activities/env-teardown.test.ts +187 -0
  443. package/src/op/activities/env-teardown.ts +171 -0
  444. package/src/op/activities/guard-validate.test.ts +139 -0
  445. package/src/op/activities/guard-validate.ts +223 -0
  446. package/src/op/activities/http-check.test.ts +48 -0
  447. package/src/op/activities/http-check.ts +55 -0
  448. package/src/op/activities/index.ts +119 -0
  449. package/src/op/activities/lexicon-upgrade.test.ts +492 -0
  450. package/src/op/activities/lexicon-upgrade.ts +673 -0
  451. package/src/op/activities/lifecycle.ts +85 -0
  452. package/src/op/activities/pipeline-audit.test.ts +67 -0
  453. package/src/op/activities/pipeline-audit.ts +201 -0
  454. package/src/op/activities/policy.test.ts +19 -0
  455. package/src/op/activities/policy.ts +37 -0
  456. package/src/op/activities/reconcile.test.ts +161 -0
  457. package/src/op/activities/reconcile.ts +354 -0
  458. package/src/op/activities/shell.ts +26 -0
  459. package/src/op/activities/teardown.ts +22 -0
  460. package/src/op/activities/util.ts +6 -0
  461. package/src/op/activities/wait.ts +69 -0
  462. package/src/op/activities/workflow-audit.test.ts +90 -0
  463. package/src/op/activities/workflow-audit.ts +203 -0
  464. package/src/op/activity-contract-registry.test.ts +98 -0
  465. package/src/op/activity-contract-registry.ts +114 -0
  466. package/src/op/activity-contract.test.ts +1 -1
  467. package/src/op/activity-contract.ts +37 -33
  468. package/src/op/activity-failure.ts +34 -0
  469. package/src/op/activity-profiles.test.ts +56 -0
  470. package/src/op/activity-profiles.ts +116 -0
  471. package/src/op/activity-registry.test.ts +37 -28
  472. package/src/op/activity-registry.ts +27 -56
  473. package/src/op/activity-runtime.ts +3 -47
  474. package/src/op/builders-exports.test.ts +0 -49
  475. package/src/op/builders-typed.test.ts +164 -0
  476. package/src/op/builders.ts +125 -41
  477. package/src/op/change-signal.test.ts +117 -0
  478. package/src/op/change-signal.ts +169 -0
  479. package/src/op/composites/apply-op.ts +218 -0
  480. package/src/op/composites/composites.test.ts +379 -0
  481. package/src/op/composites/converge-op.test.ts +168 -0
  482. package/src/op/composites/converge-op.ts +214 -0
  483. package/src/op/composites/index.ts +26 -0
  484. package/src/op/composites/lexicon-upgrade-op.test.ts +68 -0
  485. package/src/op/composites/lexicon-upgrade-op.ts +134 -0
  486. package/src/op/composites/pipeline-audit-op.test.ts +25 -0
  487. package/src/op/composites/pipeline-audit-op.ts +78 -0
  488. package/src/op/composites/reconcile-op.test.ts +51 -0
  489. package/src/op/composites/reconcile-op.ts +110 -0
  490. package/src/op/composites/watch-op.ts +108 -0
  491. package/src/op/composites/workflow-audit-op.test.ts +25 -0
  492. package/src/op/composites/workflow-audit-op.ts +88 -0
  493. package/src/op/converge-rule.ts +12 -10
  494. package/src/op/cron.test.ts +128 -0
  495. package/src/op/cron.ts +149 -0
  496. package/src/op/discover-root.test.ts +105 -1
  497. package/src/op/discover.test.ts +6 -2
  498. package/src/op/discover.ts +84 -16
  499. package/src/op/duration.ts +21 -0
  500. package/src/op/effect-step.test.ts +29 -14
  501. package/src/op/emulator-lifecycle.ts +1 -2
  502. package/src/op/gate-name.test.ts +65 -0
  503. package/src/op/gate-name.ts +60 -0
  504. package/src/op/gate-summary.ts +84 -0
  505. package/src/op/gate.ts +155 -0
  506. package/src/op/generate-pipeline.test.ts +65 -3
  507. package/src/op/generate-pipeline.ts +19 -2
  508. package/src/op/index.ts +48 -8
  509. package/src/op/local-executor.test.ts +178 -34
  510. package/src/op/local-executor.ts +371 -102
  511. package/src/op/local-output.test.ts +95 -11
  512. package/src/op/local-output.ts +33 -4
  513. package/src/op/op-ir.test.ts +292 -0
  514. package/src/op/op-ir.ts +419 -0
  515. package/src/op/op-verb-class.test.ts +2 -2
  516. package/src/op/op-verb-class.ts +19 -5
  517. package/src/op/op.test.ts +28 -7
  518. package/src/op/operator.test.ts +543 -6
  519. package/src/op/operator.ts +203 -20
  520. package/src/op/receipt-store.ts +1 -1
  521. package/src/op/resource.ts +20 -2
  522. package/src/op/runtime.ts +203 -0
  523. package/src/op/runtimes/local.test.ts +181 -0
  524. package/src/op/runtimes/local.ts +212 -0
  525. package/src/op/step-output-ref.ts +15 -13
  526. package/src/op/types.ts +106 -26
  527. package/src/ownership.ts +1 -1
  528. package/src/provenance.test.ts +6 -0
  529. package/src/provenance.ts +21 -1
  530. package/src/serializer-walker.test.ts +42 -0
  531. package/src/serializer-walker.ts +21 -2
  532. package/src/terraform/aws-resources.test.ts +13 -4
  533. package/src/terraform/bridge.test.ts +22 -9
  534. package/src/terraform/bridge.ts +89 -28
  535. package/src/terraform/carve-provider.ts +38 -2
  536. package/src/terraform/data-source-shape.ts +95 -0
  537. package/src/terraform/graph.ts +35 -7
  538. package/src/terraform/providers/kubernetes.ts +48 -2
  539. package/src/terraform/tier-map.ts +21 -6
  540. package/src/terraform/types.ts +8 -0
  541. package/dist/cli/handlers/op-progress.d.ts +0 -90
  542. package/dist/cli/handlers/op-progress.d.ts.map +0 -1
  543. package/dist/cli/handlers/run-client.d.ts +0 -127
  544. package/dist/cli/handlers/run-client.d.ts.map +0 -1
  545. package/dist/cli/handlers/run-report.d.ts +0 -5
  546. package/dist/cli/handlers/run-report.d.ts.map +0 -1
  547. package/dist/components/temporal-codegen-loader.d.ts +0 -27
  548. package/dist/components/temporal-codegen-loader.d.ts.map +0 -1
  549. package/dist/lint/rules/comp/comp004-gate-needs-temporal.d.ts +0 -37
  550. package/dist/lint/rules/comp/comp004-gate-needs-temporal.d.ts.map +0 -1
  551. package/src/cli/handlers/op-progress.test.ts +0 -268
  552. package/src/cli/handlers/op-progress.ts +0 -248
  553. package/src/cli/handlers/run-client.test.ts +0 -82
  554. package/src/cli/handlers/run-client.ts +0 -219
  555. package/src/cli/handlers/run-report.test.ts +0 -62
  556. package/src/cli/handlers/run-report.ts +0 -122
  557. package/src/components/temporal-codegen-loader.ts +0 -41
  558. package/src/lint/rules/comp/comp004-gate-needs-temporal.ts +0 -66
@@ -1,20 +1,38 @@
1
1
  /**
2
- * Local Op executor — runs an Op's phases in-process with no Temporal worker.
2
+ * Local Op executor — runs an Op's phases in this process.
3
3
  *
4
- * A first-class peer to Temporal mode for dev loops, CI, and drift/observation
5
- * Ops. Provides phase sequencing, parallel phases, per-step retry + timeout via
4
+ * The default runtime (#2121), and the one dev loops, CI, and drift/observation
5
+ * Ops use. Provides phase sequencing, parallel phases, per-step retry + timeout via
6
6
  * activity profiles, `outcomeAttribute` capture, and `onFailure` compensation.
7
- * Gates and schedules are unsupported and rejected before any phase runs.
8
7
  *
9
- * The executor is deliberately decoupled from the Temporal lexicon: activity
8
+ * A gate is a fact, not a wait (#2119). Reaching one, the executor consults the
9
+ * gate ledger through `./gate.ts`: a resolution newer than the gate's newest
10
+ * pending fact lets the step pass, carrying the approver onto its record;
11
+ * anything else records the pending fact and ends the run with status `gated`.
12
+ * No later phase runs and `onFailure` does not run — a gate is not a failure,
13
+ * and there is nothing to compensate for. The next run re-evaluates from the
14
+ * ledger, so `chant approve <op> <gate>` followed by `chant run <op>` is the
15
+ * whole loop.
16
+ *
17
+ * The executor is deliberately decoupled from any lexicon: activity
10
18
  * implementations and profiles are passed in (loaded dynamically by the CLI),
11
- * so core never statically depends on `@intentius/chant-lexicon-temporal`.
19
+ * so core never statically depends on one.
12
20
  */
13
21
 
22
+ import { outcomeAttributesOf } from "./types";
14
23
  import type { OpConfig, PhaseDefinition, ActivityStep, GateStep, EffectStep, StepDefinition } from "./types";
15
24
  import { resolveActivity, type ActivityFn, type ActivityProfile } from "./activity-registry";
16
25
  import type { ReceiptReadResult } from "./receipt-store";
17
26
  import { isStepOutputRef } from "./step-output-ref";
27
+ import { parseDuration } from "./duration";
28
+ import { evaluateGate, gitGateLedgerPort, type GateLedgerPort } from "./gate";
29
+ import { gateName } from "./gate-name";
30
+ import type { PendingGateRecord } from "../lifecycle/gate-ledger";
31
+ import { appendRunRecord, buildRunRecord } from "../lifecycle/run-ledger";
32
+ import type { OpRunRecord } from "./runtime";
33
+ import { randomUUID } from "node:crypto";
34
+
35
+ export { parseDuration } from "./duration";
18
36
 
19
37
  // ── Records ─────────────────────────────────────────────────────────────────
20
38
 
@@ -22,38 +40,53 @@ export interface StepRecord {
22
40
  phase: string;
23
41
  fn: string;
24
42
  /**
25
- * Present for a local-executor record. Absent for one reconstructed from
26
- * Temporal workflow history (op-progress.ts) — an activity's scheduled
27
- * input isn't decoded there, so the field is simply omitted rather than
28
- * populated with a guess.
43
+ * Present for a local-executor record. A runtime that reconstructs records
44
+ * from its own run history may not have an activity's scheduled input to
45
+ * hand, and omits the field rather than guessing at it.
29
46
  */
30
47
  args?: Record<string, unknown>;
31
48
  status: "ok" | "fail" | "skipped";
32
49
  durationMs: number;
50
+ /**
51
+ * The first outcome attribute the step published, kept singular because a
52
+ * step publishing one is the ordinary case and every reader of this field
53
+ * predates the plural form. {@link StepRecord.outcomes} is the whole list.
54
+ */
33
55
  outcome?: { name: string; value: unknown };
56
+ /** Every outcome attribute the step published, in authored order (#2105). Absent when it published none. */
57
+ outcomes?: Array<{ name: string; value: unknown }>;
34
58
  error?: string;
59
+ /** Set on a `gate` step that passed (#2119): who resolved it, when, and at what address. */
60
+ approval?: { gate: string; resolvedBy: string; timestamp: string; url?: string };
35
61
  }
36
62
 
37
63
  export interface OpRunResult {
38
64
  op: string;
39
65
  records: StepRecord[];
40
66
  totalMs: number;
41
- ok: boolean;
67
+ /**
68
+ * Three-state outcome (#2119). `gated` is neither success nor failure: the
69
+ * run reached a gate nobody has approved, recorded the fact, and stopped.
70
+ * The CLI exits 3 for it so CI can tell "waiting on a human" from "broken".
71
+ *
72
+ * Written to the run ledger as the record's own `status` (#2118).
73
+ */
74
+ status: "ok" | "fail" | "gated";
75
+ /** ISO-8601 start of this run. */
76
+ startedAt: string;
77
+ /** Present when `status === "gated"`: the pending fact the run ended on. */
78
+ gate?: PendingGateRecord;
79
+ /**
80
+ * The run's ledger record (#2118) — always built, whether or not it was
81
+ * appended. `chant run <op> --json` prints exactly this, so what a caller
82
+ * reads on stdout and what a later `readRunLedger` reads back are the same
83
+ * document.
84
+ */
85
+ record: OpRunRecord;
42
86
  }
43
87
 
44
88
  // ── Errors ────────────────────────────────────────────────────────────────��─
45
89
 
46
- /** Thrown when an Op contains a gate (or schedule) that local mode cannot run. */
47
- export class LocalGateUnsupportedError extends Error {
48
- constructor(public readonly signalName: string) {
49
- super(
50
- `gate "${signalName}" is not supported in local mode — gates and schedules ` +
51
- `need a durable runtime. Re-run with --temporal.`,
52
- );
53
- this.name = "LocalGateUnsupportedError";
54
- }
55
- }
56
-
57
90
  /** Thrown on terminal Op failure; carries the partial run result for rendering. */
58
91
  export class OpRunFailure extends Error {
59
92
  constructor(public readonly result: OpRunResult) {
@@ -70,6 +103,18 @@ class PhaseFailure extends Error {
70
103
  }
71
104
  }
72
105
 
106
+ /** Internal: a phase stopped on an unapproved gate. Not a failure — no compensation follows it. */
107
+ class GateStop extends Error {
108
+ constructor(
109
+ public readonly records: StepRecord[],
110
+ public readonly pending: PendingGateRecord,
111
+ public readonly phase: string,
112
+ ) {
113
+ super(`gate "${pending.gate}" is pending approval`);
114
+ this.name = "GateStop";
115
+ }
116
+ }
117
+
73
118
  // ── Helpers ───────────────────────────────────────────────────────────────��─
74
119
 
75
120
  const DEFAULT_PROFILE = "fastIdempotent";
@@ -79,19 +124,6 @@ const isActivity = (s: StepDefinition): s is ActivityStep => s.kind === "activit
79
124
  const isGate = (s: StepDefinition): s is GateStep => s.kind === "gate";
80
125
  const isEffect = (s: StepDefinition): s is EffectStep => s.kind === "effect";
81
126
 
82
- /** Parse a Temporal duration string ("5m", "30s", "1h30m", "100ms") to ms. */
83
- export function parseDuration(s: string): number {
84
- const units: Record<string, number> = { ms: 1, s: 1000, m: 60_000, h: 3_600_000, d: 86_400_000 };
85
- let total = 0;
86
- let matched = false;
87
- for (const m of s.matchAll(/(\d+)(ms|s|m|h|d)/g)) {
88
- total += Number(m[1]) * units[m[2]];
89
- matched = true;
90
- }
91
- if (!matched) throw new Error(`unparseable duration: "${s}"`);
92
- return total;
93
- }
94
-
95
127
  /** Resolve a dot-path into a value; returns the whole value when path is absent. */
96
128
  function resolvePath(value: unknown, path?: string): unknown {
97
129
  if (!path) return value;
@@ -120,22 +152,6 @@ function resolveStepOutputRefs(value: unknown, resultsById: ReadonlyMap<string,
120
152
  return value;
121
153
  }
122
154
 
123
- /** Find the first gate step anywhere in the Op (phases + onFailure, including
124
- * gates nested inside effect steps), if any. */
125
- export function findGate(config: OpConfig): GateStep | undefined {
126
- const all = [...config.phases, ...(config.onFailure ?? [])];
127
- for (const phase of all) {
128
- for (const step of phase.steps) {
129
- if (isGate(step)) return step;
130
- if (isEffect(step)) {
131
- const nested = step.steps.find(isGate);
132
- if (nested) return nested;
133
- }
134
- }
135
- }
136
- return undefined;
137
- }
138
-
139
155
  /**
140
156
  * Find the first `policyGate` step anywhere in the Op (phases + `onFailure`,
141
157
  * including steps nested inside effect steps), if any.
@@ -147,7 +163,8 @@ export function findGate(config: OpConfig): GateStep | undefined {
147
163
  * shown to a user who passed a documented flag. The combination cannot be
148
164
  * honoured until the gate can build and load policies inside the boundary
149
165
  * (#1157), so both `chant run` paths pre-flight it with this and refuse before
150
- * any phase executes, the same shape as {@link findGate}'s pre-flight.
166
+ * any phase executes. This is the one pre-flight refusal left on the Op path —
167
+ * a `gate` no longer is one (#2119).
151
168
  */
152
169
  export function findPolicyGateStep(config: OpConfig): ActivityStep | undefined {
153
170
  const all = [...config.phases, ...(config.onFailure ?? [])];
@@ -217,9 +234,9 @@ interface RanStep {
217
234
  /**
218
235
  * Run one activity step with retry + timeout. Never throws — returns a
219
236
  * record. Any {@link StepOutputRef} in `step.args` is resolved against
220
- * `resultsById` before the activity is called (#1290) — the local executor
221
- * is a first-class peer of the Temporal path, so an unresolved placeholder
222
- * must never reach an activity function; see `resolveStepOutputRefs`.
237
+ * `resultsById` before the activity is called (#1290) — an unresolved
238
+ * placeholder must never reach an activity function, on this runtime or any
239
+ * other; see `resolveStepOutputRefs`.
223
240
  */
224
241
  async function runStep(
225
242
  step: ActivityStep,
@@ -241,8 +258,8 @@ async function runStep(
241
258
  }
242
259
 
243
260
  const profile = profiles[step.profile ?? DEFAULT_PROFILE] ?? {};
244
- const timeoutMs = profile.startToCloseTimeout
245
- ? parseDuration(profile.startToCloseTimeout)
261
+ const timeoutMs = profile.timeout
262
+ ? parseDuration(profile.timeout)
246
263
  : FALLBACK_TIMEOUT_MS;
247
264
  const maxAttempts =
248
265
  profile.retry?.maximumAttempts && profile.retry.maximumAttempts > 0
@@ -261,11 +278,10 @@ async function runStep(
261
278
  const result = await callWithTimeout(fn, args, timeoutMs, signal);
262
279
  if (step.id) resultsById.set(step.id, result);
263
280
  const record: StepRecord = { ...base, status: "ok", durationMs: Date.now() - start };
264
- if (step.outcomeAttribute) {
265
- record.outcome = {
266
- name: step.outcomeAttribute.name,
267
- value: resolvePath(result, step.outcomeAttribute.from),
268
- };
281
+ const attrs = outcomeAttributesOf(step);
282
+ if (attrs.length > 0) {
283
+ record.outcomes = attrs.map((a) => ({ name: a.name, value: resolvePath(result, a.from) }));
284
+ record.outcome = record.outcomes[0];
269
285
  }
270
286
  return { record, result };
271
287
  } catch (err) {
@@ -307,6 +323,75 @@ function skippedRecord(phaseName: string, fn: string, args?: Record<string, unkn
307
323
  return { phase: phaseName, fn, args: args ?? {}, status: "skipped", durationMs: 0 };
308
324
  }
309
325
 
326
+ // ── Gate steps (#2119) ───────────────────────────────────────────────────────
327
+
328
+ /** What a run needs to decide a gate against the ledger — see `./gate.ts`. */
329
+ interface GateContext {
330
+ op: string;
331
+ port: GateLedgerPort;
332
+ now?: string;
333
+ runId?: string;
334
+ /** Called once per settled step, in production order (#2121) — what `--progress-json` streams from. */
335
+ onRecord?: (record: StepRecord) => void;
336
+ }
337
+
338
+ /** Collect records and hand each to the caller's progress sink in one move. */
339
+ function pushRecord(sink: StepRecord[], ctx: GateContext, ...recs: StepRecord[]): void {
340
+ sink.push(...recs);
341
+ for (const r of recs) ctx.onRecord?.(r);
342
+ }
343
+
344
+ /** The record name a gate step lands under, so a reader (and a JSON consumer) can pick it out of `records`. */
345
+ function gateFn(step: GateStep): string {
346
+ return `gate:${gateName(step)}`;
347
+ }
348
+
349
+ /**
350
+ * Decide one gate against the ledger. A resolution newer than the gate's
351
+ * newest pending fact passes it, and the approver lands on the step record; a
352
+ * pending fact is recorded (or left standing) otherwise, and the caller stops
353
+ * the run.
354
+ */
355
+ async function runGateStep(
356
+ step: GateStep,
357
+ phaseName: string,
358
+ gates: GateContext,
359
+ ): Promise<{ record: StepRecord; pending?: PendingGateRecord }> {
360
+ const start = Date.now();
361
+ const check = await evaluateGate(gates.port, {
362
+ op: gates.op,
363
+ gate: gateName(step),
364
+ ...(step.description ? { description: step.description } : {}),
365
+ ...(step.timeout ? { timeout: step.timeout } : {}),
366
+ ...(gates.runId ? { runId: gates.runId } : {}),
367
+ ...(gates.now ? { now: gates.now } : {}),
368
+ });
369
+
370
+ if (check.satisfied) {
371
+ const { resolution } = check;
372
+ return {
373
+ record: {
374
+ phase: phaseName,
375
+ fn: gateFn(step),
376
+ args: {},
377
+ status: "ok",
378
+ durationMs: Date.now() - start,
379
+ approval: {
380
+ gate: gateName(step),
381
+ resolvedBy: resolution.resolvedBy,
382
+ timestamp: resolution.timestamp,
383
+ ...(resolution.url ? { url: resolution.url } : {}),
384
+ },
385
+ },
386
+ };
387
+ }
388
+
389
+ return {
390
+ record: { phase: phaseName, fn: gateFn(step), args: {}, status: "skipped", durationMs: Date.now() - start },
391
+ pending: check.pending,
392
+ };
393
+ }
394
+
310
395
  /**
311
396
  * Run one effect step: read-compare-run-write. On a match the nested steps are
312
397
  * recorded as skipped ("effect already applied") and nothing is written. On a
@@ -314,6 +399,11 @@ function skippedRecord(phaseName: string, fn: string, args?: Record<string, unkn
314
399
  * succeeds is the receipt written — last, once (the sole writer, #1703
315
400
  * decision 3). Any failure leaves the receipt untouched (stale), so the next
316
401
  * run re-proposes the effect.
402
+ *
403
+ * A nested `gate` is decided in authored order like any other nested step
404
+ * (#2119). Unapproved, it stops the effect before the receipt is written, so
405
+ * the next run re-reads the receipt, re-proposes the effect, and re-evaluates
406
+ * the gate — the receipt stays honest about what actually happened.
317
407
  */
318
408
  async function runEffectStep(
319
409
  step: EffectStep,
@@ -321,17 +411,18 @@ async function runEffectStep(
321
411
  activities: Map<string, ActivityFn>,
322
412
  profiles: Record<string, ActivityProfile>,
323
413
  resultsById: Map<string, unknown>,
414
+ gates: GateContext,
324
415
  signal?: AbortSignal,
325
- ): Promise<{ records: StepRecord[]; failed: boolean }> {
416
+ ): Promise<{ records: StepRecord[]; failed: boolean; pending?: PendingGateRecord }> {
326
417
  const records: StepRecord[] = [];
327
418
 
328
419
  const read = await runStep(receiptReadStep(step), phaseName, activities, profiles, resultsById, signal);
329
- records.push(read.record);
420
+ pushRecord(records, gates, read.record);
330
421
  if (read.record.status === "fail") return { records, failed: true };
331
422
 
332
423
  const result = read.result as Partial<ReceiptReadResult> | undefined;
333
424
  if (typeof result?.expectation !== "string") {
334
- records.push({
425
+ pushRecord(records, gates, {
335
426
  phase: phaseName,
336
427
  fn: `effect:${step.receipt.name}`,
337
428
  args: {},
@@ -346,22 +437,44 @@ async function runEffectStep(
346
437
  if (result.current === expectation) {
347
438
  // Effect already applied — skip the nested steps, write nothing.
348
439
  for (const nested of step.steps) {
349
- if (nested.kind === "activity") records.push(skippedRecord(phaseName, nested.fn, nested.args));
440
+ pushRecord(records, gates,
441
+ nested.kind === "activity"
442
+ ? skippedRecord(phaseName, nested.fn, nested.args)
443
+ : skippedRecord(phaseName, gateFn(nested)),
444
+ );
350
445
  }
351
446
  return { records, failed: false };
352
447
  }
353
448
 
354
- // Gates are pre-flighted by findGate; only activities remain here.
355
- const nestedActivities = step.steps.filter(isActivity);
356
- for (let i = 0; i < nestedActivities.length; i++) {
357
- const ran = await runStep(nestedActivities[i], phaseName, activities, profiles, resultsById, signal);
358
- records.push(ran.record);
449
+ const skipRest = (from: number) => {
450
+ for (const skipped of step.steps.slice(from)) {
451
+ pushRecord(records, gates,
452
+ skipped.kind === "activity"
453
+ ? skippedRecord(phaseName, skipped.fn, skipped.args)
454
+ : skippedRecord(phaseName, gateFn(skipped)),
455
+ );
456
+ }
457
+ pushRecord(records, gates, skippedRecord(phaseName, "receiptWrite"));
458
+ };
459
+
460
+ for (let i = 0; i < step.steps.length; i++) {
461
+ const nested = step.steps[i];
462
+ if (isGate(nested)) {
463
+ const { record, pending } = await runGateStep(nested, phaseName, gates);
464
+ pushRecord(records, gates, record);
465
+ if (pending) {
466
+ // Receipt left untouched — the next run re-proposes the effect and
467
+ // re-evaluates the gate against whatever the ledger says by then.
468
+ skipRest(i + 1);
469
+ return { records, failed: false, pending };
470
+ }
471
+ continue;
472
+ }
473
+ const ran = await runStep(nested, phaseName, activities, profiles, resultsById, signal);
474
+ pushRecord(records, gates, ran.record);
359
475
  if (ran.record.status === "fail") {
360
476
  // Receipt left untouched (stale) — the next run re-proposes the effect.
361
- for (const skipped of nestedActivities.slice(i + 1)) {
362
- records.push(skippedRecord(phaseName, skipped.fn, skipped.args));
363
- }
364
- records.push(skippedRecord(phaseName, "receiptWrite"));
477
+ skipRest(i + 1);
365
478
  return { records, failed: true };
366
479
  }
367
480
  }
@@ -380,25 +493,22 @@ async function runEffectStep(
380
493
  resultsById,
381
494
  signal,
382
495
  );
383
- records.push(wrote.record);
496
+ pushRecord(records, gates, wrote.record);
384
497
  return { records, failed: wrote.record.status === "fail" };
385
498
  }
386
499
 
387
- /** Run a phase. Throws PhaseFailure (with records so far) if any step fails. */
500
+ /**
501
+ * Run a phase. Throws `PhaseFailure` (with the records so far) if any step
502
+ * fails, or `GateStop` if a `gate` step in it is still pending approval.
503
+ */
388
504
  async function runPhase(
389
505
  phase: PhaseDefinition,
390
506
  activities: Map<string, ActivityFn>,
391
507
  profiles: Record<string, ActivityProfile>,
392
508
  resultsById: Map<string, unknown>,
509
+ gates: GateContext,
393
510
  signal?: AbortSignal,
394
511
  ): Promise<StepRecord[]> {
395
- // Defensive: gates are pre-flighted, but never execute one if it slips
396
- // through — including a gate nested inside an effect step.
397
- const gate =
398
- phase.steps.find(isGate) ??
399
- phase.steps.filter(isEffect).flatMap((e) => e.steps).find(isGate);
400
- if (gate) throw new LocalGateUnsupportedError(gate.signalName);
401
-
402
512
  if (phase.parallel) {
403
513
  const eff = phase.steps.find(isEffect);
404
514
  if (eff) {
@@ -406,32 +516,65 @@ async function runPhase(
406
516
  `effect step "${eff.receipt.name}" cannot run in a parallel phase — read-compare-run-write is ordered`,
407
517
  );
408
518
  }
519
+ // Gates in a parallel phase are decided before the fan-out: a gate is a
520
+ // read of the ledger, not work, and starting activities that a pending
521
+ // gate is about to strand would defeat the point of stopping at it.
522
+ const gateRecords: StepRecord[] = [];
523
+ for (const step of phase.steps.filter(isGate)) {
524
+ const { record, pending } = await runGateStep(step, phase.name, gates);
525
+ pushRecord(gateRecords, gates, record);
526
+ if (pending) {
527
+ for (const skipped of phase.steps.filter(isActivity)) {
528
+ pushRecord(gateRecords, gates, skippedRecord(phase.name, skipped.fn, skipped.args));
529
+ }
530
+ throw new GateStop(gateRecords, pending, phase.name);
531
+ }
532
+ }
409
533
  const steps = phase.steps.filter(isActivity);
410
- const records = (
534
+ const ran = (
411
535
  await Promise.all(steps.map((s) => runStep(s, phase.name, activities, profiles, resultsById, signal)))
412
536
  ).map((r) => r.record);
537
+ for (const r of ran) gates.onRecord?.(r);
538
+ const records = gateRecords.concat(ran);
413
539
  if (records.some((r) => r.status === "fail")) throw new PhaseFailure(records);
414
540
  return records;
415
541
  }
416
542
 
417
- const steps = phase.steps.filter((s): s is ActivityStep | EffectStep => !isGate(s));
543
+ const steps = phase.steps;
418
544
  const records: StepRecord[] = [];
419
545
 
420
546
  const skipRemaining = (from: number) => {
421
547
  for (const skipped of steps.slice(from)) {
422
548
  if (isEffect(skipped)) {
423
- records.push(skippedRecord(phase.name, `effect:${skipped.receipt.name}`));
549
+ pushRecord(records, gates, skippedRecord(phase.name, `effect:${skipped.receipt.name}`));
550
+ } else if (isGate(skipped)) {
551
+ pushRecord(records, gates, skippedRecord(phase.name, gateFn(skipped)));
424
552
  } else {
425
- records.push(skippedRecord(phase.name, skipped.fn, skipped.args));
553
+ pushRecord(records, gates, skippedRecord(phase.name, skipped.fn, skipped.args));
426
554
  }
427
555
  }
428
556
  };
429
557
 
430
558
  for (let i = 0; i < steps.length; i++) {
431
559
  const step = steps[i];
560
+ if (isGate(step)) {
561
+ const { record, pending } = await runGateStep(step, phase.name, gates);
562
+ pushRecord(records, gates, record);
563
+ if (pending) {
564
+ skipRemaining(i + 1);
565
+ throw new GateStop(records, pending, phase.name);
566
+ }
567
+ continue;
568
+ }
432
569
  if (isEffect(step)) {
433
- const { records: effRecords, failed } = await runEffectStep(step, phase.name, activities, profiles, resultsById, signal);
434
- records.push(...effRecords);
570
+ const { records: effRecords, failed, pending } = await runEffectStep(
571
+ step, phase.name, activities, profiles, resultsById, gates, signal,
572
+ );
573
+ records.push(...effRecords); // already emitted by runEffectStep
574
+ if (pending) {
575
+ skipRemaining(i + 1);
576
+ throw new GateStop(records, pending, phase.name);
577
+ }
435
578
  if (failed) {
436
579
  skipRemaining(i + 1);
437
580
  throw new PhaseFailure(records);
@@ -439,7 +582,7 @@ async function runPhase(
439
582
  continue;
440
583
  }
441
584
  const { record } = await runStep(step, phase.name, activities, profiles, resultsById, signal);
442
- records.push(record);
585
+ pushRecord(records, gates, record);
443
586
  if (record.status === "fail") {
444
587
  // Mark the remaining steps in this phase as skipped, then abort.
445
588
  skipRemaining(i + 1);
@@ -451,20 +594,78 @@ async function runPhase(
451
594
 
452
595
  // ── Public API ────────────────────────────────────────────────────────────��─
453
596
 
597
+ /** How a run reaches the gate ledger, and what it calls itself there (#2119). */
598
+ export interface RunOpOptions {
599
+ /**
600
+ * Where gate facts are read and written. Defaults to the `chant/lifecycle`
601
+ * orphan branch under `cwd`; a test (or a caller that already holds the
602
+ * ledger) passes `memoryGateLedgerPort()` from `./gate.ts` instead.
603
+ */
604
+ gates?: GateLedgerPort;
605
+ /** Working directory for the default git-backed gate ledger. */
606
+ cwd?: string;
607
+ /** ISO-8601 "now", so a gate decision is deterministic under test. */
608
+ now?: string;
609
+ /** Identifies this run on any pending fact it records. */
610
+ runId?: string;
611
+ /**
612
+ * Called once per settled step, in the order the records are produced
613
+ * (#2121) — what the local op runtime (./runtimes/local.ts) feeds
614
+ * `--progress-json` from. Side-effect free when omitted.
615
+ */
616
+ onRecord?: (record: StepRecord) => void;
617
+ /**
618
+ * Append the run's {@link OpRunRecord} to the run ledger
619
+ * (`../lifecycle/run-ledger.ts`) when the run settles (#2118). Opt-in on
620
+ * purpose: the executor is called from unit tests and in-process harnesses
621
+ * whose working directory is an ordinary checkout, and a run there must not
622
+ * write to the lifecycle branch. The local runtime (./runtimes/local.ts) and
623
+ * `chant operator` pass it; nothing else does.
624
+ */
625
+ ledger?: { cwd?: string };
626
+ /**
627
+ * Called when the ledger append itself fails. The run's own outcome is
628
+ * already decided by then, so a ledger failure is reported to the caller,
629
+ * never promoted into a run failure.
630
+ */
631
+ onLedgerError?: (err: unknown) => void;
632
+ }
633
+
454
634
  /**
455
- * Execute an Op locally. Resolves with the run result on success; rejects with
456
- * `OpRunFailure` (carrying the partial result) on terminal failure, after
457
- * running any `onFailure` phases in reverse order. Throws
458
- * `LocalGateUnsupportedError` up front if the Op contains a gate.
635
+ * Execute an Op locally.
636
+ *
637
+ * Resolves with the run result when every phase succeeds (`status: "ok"`), and
638
+ * also when the run stopped at an unapproved gate (`status: "gated"`, with the
639
+ * pending fact on `result.gate`) — a gate is a fact, not an error, so it is not
640
+ * thrown. Rejects with `OpRunFailure` (carrying the partial result) on terminal
641
+ * failure, after running any `onFailure` phases in reverse order; a gated run
642
+ * runs no `onFailure` phase, because nothing failed and nothing was left
643
+ * half-applied to compensate for.
644
+ *
645
+ * Whichever of the three ways it settles, the run carries an
646
+ * {@link OpRunRecord} on `result.record` (#2118). With `options.ledger` that
647
+ * record is also appended to `<env>/runs__<op>.jsonl` on the lifecycle branch
648
+ * before this returns or rejects, so an Op's outcome outlives the process that
649
+ * produced it.
459
650
  */
460
651
  export async function runOpLocally(
461
652
  config: OpConfig,
462
653
  activities: Map<string, ActivityFn>,
463
654
  profiles: Record<string, ActivityProfile>,
464
655
  signal?: AbortSignal,
656
+ options: RunOpOptions = {},
465
657
  ): Promise<OpRunResult> {
466
- const gate = findGate(config);
467
- if (gate) throw new LocalGateUnsupportedError(gate.signalName);
658
+ // The run id is the gate ledger's and the run ledger's alike: a pending gate
659
+ // fact and the record naming that gate carry the same string.
660
+ const runId = options.runId ?? randomUUID();
661
+
662
+ const gates: GateContext = {
663
+ op: config.name,
664
+ port: options.gates ?? gitGateLedgerPort(options.cwd ? { cwd: options.cwd } : undefined),
665
+ ...(options.now ? { now: options.now } : {}),
666
+ runId,
667
+ ...(options.onRecord ? { onRecord: options.onRecord } : {}),
668
+ };
468
669
 
469
670
  // Effect steps are ordered (read-compare-run-write): refuse them in a
470
671
  // parallel phase up front, with the phase named, rather than mid-run.
@@ -480,6 +681,33 @@ export async function runOpLocally(
480
681
 
481
682
  const records: StepRecord[] = [];
482
683
  const start = Date.now();
684
+ const startedAt = options.now ?? new Date(start).toISOString();
685
+
686
+ /**
687
+ * Build the run's ledger record, append it when asked, and hand it back for
688
+ * the result. Called once, at whichever of the three exits the run takes.
689
+ */
690
+ const settle = async (
691
+ settled: StepRecord[],
692
+ status: OpRunResult["status"],
693
+ gate?: PendingGateRecord,
694
+ ): Promise<OpRunRecord> => {
695
+ const input = buildRunRecord(config, settled, {
696
+ started: startedAt,
697
+ ended: options.now ?? new Date().toISOString(),
698
+ status,
699
+ id: runId,
700
+ ...(gate ? { gate: { name: gate.gate, since: gate.timestamp } } : {}),
701
+ });
702
+ const record: OpRunRecord = { version: 1, ...input, id: runId };
703
+ if (!options.ledger) return record;
704
+ try {
705
+ return (await appendRunRecord(input, options.ledger)).record;
706
+ } catch (err) {
707
+ options.onLedgerError?.(err);
708
+ return record;
709
+ }
710
+ };
483
711
 
484
712
  // Completed step id → its activity result (#1290). Populated as main-phase
485
713
  // steps finish, so a later step's step-output references resolve to real
@@ -489,9 +717,36 @@ export async function runOpLocally(
489
717
  try {
490
718
  for (const phase of config.phases) {
491
719
  if (signal?.aborted) throw new PhaseFailure([]);
492
- records.push(...(await runPhase(phase, activities, profiles, resultsById, signal)));
720
+ records.push(...(await runPhase(phase, activities, profiles, resultsById, gates, signal)));
493
721
  }
494
722
  } catch (err) {
723
+ // A pending gate ends the run where it stands: no later phase, and no
724
+ // `onFailure` compensation — nothing failed, so there is nothing to undo.
725
+ if (err instanceof GateStop) {
726
+ records.push(...err.records);
727
+ const stoppedAt = config.phases.findIndex((p) => p.name === err.phase);
728
+ for (const phase of config.phases.slice(stoppedAt + 1)) {
729
+ for (const step of phase.steps) {
730
+ records.push(
731
+ isEffect(step)
732
+ ? skippedRecord(phase.name, `effect:${step.receipt.name}`)
733
+ : isGate(step)
734
+ ? skippedRecord(phase.name, gateFn(step))
735
+ : skippedRecord(phase.name, step.fn, step.args),
736
+ );
737
+ }
738
+ }
739
+ return {
740
+ op: config.name,
741
+ records,
742
+ totalMs: Date.now() - start,
743
+ status: "gated",
744
+ startedAt,
745
+ gate: err.pending,
746
+ record: await settle(records, "gated", err.pending),
747
+ };
748
+ }
749
+
495
750
  if (err instanceof PhaseFailure) records.push(...err.records);
496
751
 
497
752
  // Compensation: run onFailure phases in reverse order (best-effort). Skipped
@@ -499,17 +754,31 @@ export async function runOpLocally(
499
754
  if (!signal?.aborted) {
500
755
  for (const phase of [...(config.onFailure ?? [])].reverse()) {
501
756
  try {
502
- records.push(...(await runPhase(phase, activities, profiles, resultsById, signal)));
757
+ records.push(...(await runPhase(phase, activities, profiles, resultsById, gates, signal)));
503
758
  } catch (compErr) {
504
- if (compErr instanceof PhaseFailure) records.push(...compErr.records);
759
+ if (compErr instanceof PhaseFailure || compErr instanceof GateStop) records.push(...compErr.records);
505
760
  }
506
761
  }
507
762
  }
508
763
 
509
- throw new OpRunFailure({ op: config.name, records, totalMs: Date.now() - start, ok: false });
764
+ throw new OpRunFailure({
765
+ op: config.name,
766
+ records,
767
+ totalMs: Date.now() - start,
768
+ status: "fail",
769
+ startedAt,
770
+ record: await settle(records, "fail"),
771
+ });
510
772
  }
511
773
 
512
- return { op: config.name, records, totalMs: Date.now() - start, ok: true };
774
+ return {
775
+ op: config.name,
776
+ records,
777
+ totalMs: Date.now() - start,
778
+ status: "ok",
779
+ startedAt,
780
+ record: await settle(records, "ok"),
781
+ };
513
782
  }
514
783
 
515
784
  function errMessage(err: unknown): string {