@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
@@ -13,9 +13,27 @@ import {
13
13
  } from "./operator";
14
14
  import { readLease } from "../lifecycle/lease";
15
15
  import { appendConvergeRecord, readConvergeLedger } from "../lifecycle/converge-ledger";
16
+ import { readRunLedger } from "../lifecycle/run-ledger";
17
+ import type { OpRunRecord } from "./runtime";
16
18
 
17
19
  const PROFILES: Record<string, ActivityProfile> = {};
18
20
 
21
+ /** A minimal `OpRunRecord` for a result literal that never touches a ledger. */
22
+ function runRecord(op: string, status: OpRunRecord["status"] = "ok"): OpRunRecord {
23
+ return {
24
+ version: 1,
25
+ id: "00000000-0000-4000-8000-000000000000",
26
+ op,
27
+ env: "staging",
28
+ started: "2026-01-01T00:00:00.000Z",
29
+ ended: "2026-01-01T00:00:01.000Z",
30
+ status,
31
+ labels: {},
32
+ outcomes: {},
33
+ phases: [],
34
+ };
35
+ }
36
+
19
37
  function git(args: string[], cwd: string): { stdout: string; exitCode: number } {
20
38
  const r = spawnSync("git", args, { cwd, encoding: "utf-8" });
21
39
  return { stdout: r.stdout ?? "", exitCode: r.status ?? -1 };
@@ -34,7 +52,7 @@ async function initRepo(dir: string): Promise<void> {
34
52
  * Write a minimal fixture ConvergeOp — the same `{ props: OpConfig }` shape
35
53
  * `discoverOps` reads from a real `*.op.ts` file's default export
36
54
  * (`../op/discover.ts`), hand-built here rather than going through the real
37
- * `ConvergeOp()` composite (lexicons/temporal) so this suite tests the
55
+ * `ConvergeOp()` composite so this suite tests the
38
56
  * operator's own discovery/lease/tick-loop logic without depending on that
39
57
  * lexicon at all. One "Converge" phase calling a single fake activity —
40
58
  * `runOpLocally` doesn't care what the phases are named or how many there
@@ -50,7 +68,26 @@ function writeFixtureConvergeOp(dir: string, opName: string, env: string): void
50
68
  name: ${JSON.stringify(opName)},
51
69
  overview: "fixture converge op",
52
70
  phases: [{ name: "Converge", steps: [{ kind: "activity", fn: "fakeTick", args: {} }] }],
53
- searchAttributes: { Converge: "true", Env: ${JSON.stringify(env)}, Dial: "observe" },
71
+ labels: { Converge: "true", Env: ${JSON.stringify(env)}, Dial: "observe" },
72
+ },
73
+ };
74
+ `,
75
+ );
76
+ }
77
+
78
+ /** The same fixture with a cadence of its own (#2120) — what `ConvergeOp({ schedule })` produces. */
79
+ function writeFixtureScheduledConvergeOp(dir: string, opName: string, env: string, cron: string): void {
80
+ const opsDir = join(dir, "ops");
81
+ mkdirSync(opsDir, { recursive: true });
82
+ writeFileSync(
83
+ join(opsDir, `${opName}.op.ts`),
84
+ `export default {
85
+ props: {
86
+ name: ${JSON.stringify(opName)},
87
+ overview: "fixture scheduled converge op",
88
+ phases: [{ name: "Converge", steps: [{ kind: "activity", fn: "fakeTick", args: {} }] }],
89
+ labels: { Converge: "true", Env: ${JSON.stringify(env)}, Dial: "observe" },
90
+ schedule: { cron: ${JSON.stringify(cron)}, overlap: "skip" },
54
91
  },
55
92
  };
56
93
  `,
@@ -99,7 +136,7 @@ function fakeTickActivities(dir: string, env: string, opName: string, opts?: { t
99
136
  }
100
137
 
101
138
  describe("discoverConvergeOps", () => {
102
- test("finds only ops with searchAttributes.Converge === 'true'", async () => {
139
+ test("finds only ops with labels.Converge === 'true'", async () => {
103
140
  await withTestDir(async (dir) => {
104
141
  await initRepo(dir);
105
142
  writeFixtureConvergeOp(dir, "staging-converge", "staging");
@@ -132,6 +169,28 @@ describe("discoverConvergeOps", () => {
132
169
  expect(ops.map((d) => d.config.name)).toEqual(["prod-converge", "staging-converge"]);
133
170
  });
134
171
  });
172
+
173
+ test("a ConvergeOp declaring no labels.Env is discovered unfiltered but never matches --env", async () => {
174
+ await withTestDir(async (dir) => {
175
+ await initRepo(dir);
176
+ mkdirSync(join(dir, "ops"), { recursive: true });
177
+ writeFileSync(
178
+ join(dir, "ops", "envless-converge.op.ts"),
179
+ `export default {
180
+ props: {
181
+ name: "envless-converge",
182
+ overview: "a hand-built ConvergeOp with no Env label",
183
+ phases: [{ name: "Converge", steps: [{ kind: "activity", fn: "fakeTick", args: {} }] }],
184
+ labels: { Converge: "true" },
185
+ },
186
+ };
187
+ `,
188
+ );
189
+
190
+ expect((await discoverConvergeOps({ cwd: dir })).ops.map((d) => d.config.name)).toEqual(["envless-converge"]);
191
+ expect((await discoverConvergeOps({ cwd: dir, env: "staging" })).ops).toEqual([]);
192
+ });
193
+ });
135
194
  });
136
195
 
137
196
  describe("runOperatorRound — lease + tick execution over a fixture ConvergeOp (offline)", () => {
@@ -142,7 +201,7 @@ describe("runOperatorRound — lease + tick execution over a fixture ConvergeOp
142
201
  const activities = fakeTickActivities(dir, "staging", "staging-converge");
143
202
 
144
203
  const events = await runOperatorRound({ cwd: dir, holder: "op-a", activities, profiles: PROFILES });
145
- expect(events).toEqual([{ kind: "ticked", op: "staging-converge", env: "staging", result: expect.objectContaining({ ok: true }) }]);
204
+ expect(events).toEqual([{ kind: "ticked", op: "staging-converge", env: "staging", result: expect.objectContaining({ status: "ok" }) }]);
146
205
 
147
206
  const { records } = await readConvergeLedger("staging", { cwd: dir });
148
207
  expect(records).toHaveLength(1);
@@ -150,6 +209,27 @@ describe("runOperatorRound — lease + tick execution over a fixture ConvergeOp
150
209
  });
151
210
  });
152
211
 
212
+ test("the tick's own run lands on the run ledger, keyed by the op's labels.Env (#2118)", async () => {
213
+ await withTestDir(async (dir) => {
214
+ await initRepo(dir);
215
+ writeFixtureConvergeOp(dir, "staging-converge", "staging");
216
+ const activities = fakeTickActivities(dir, "staging", "staging-converge");
217
+
218
+ await runOperatorRound({ cwd: dir, holder: "op-a", activities, profiles: PROFILES });
219
+
220
+ const { records, malformed } = await readRunLedger("staging", "staging-converge", { cwd: dir });
221
+ expect(malformed).toBe(0);
222
+ expect(records).toHaveLength(1);
223
+ expect(records[0]).toMatchObject({
224
+ op: "staging-converge",
225
+ env: "staging",
226
+ status: "ok",
227
+ labels: { Converge: "true", Env: "staging", Dial: "observe" },
228
+ });
229
+ expect(records[0].phases.map((p) => p.name)).toEqual(["Converge"]);
230
+ });
231
+ });
232
+
153
233
  test("a second operator process (different holder) sees the lease held and skips — never queues", async () => {
154
234
  await withTestDir(async (dir) => {
155
235
  await initRepo(dir);
@@ -330,10 +410,97 @@ describe("runOperatorForever — interval/timer behavior", () => {
330
410
  });
331
411
  });
332
412
 
413
+ describe("runOperatorRound — an Op's own schedule is its tick cadence (#2120)", () => {
414
+ /** 2026-09-06T13:30 local — a minute `*​/10 * * * *` fires on. */
415
+ const firing = () => new Date(2026, 8, 6, 13, 30, 0, 0);
416
+ /** …and 13:33, which it does not. */
417
+ const quiet = () => new Date(2026, 8, 6, 13, 33, 0, 0);
418
+
419
+ test("a round on a firing minute ticks the op", async () => {
420
+ await withTestDir(async (dir) => {
421
+ await initRepo(dir);
422
+ writeFixtureScheduledConvergeOp(dir, "staging-converge", "staging", "*/10 * * * *");
423
+ const activities = fakeTickActivities(dir, "staging", "staging-converge");
424
+
425
+ const events = await runOperatorRound({ cwd: dir, holder: "op-a", activities, profiles: PROFILES, now: firing });
426
+ expect(events).toEqual([
427
+ { kind: "ticked", op: "staging-converge", env: "staging", result: expect.objectContaining({ status: "ok" }) },
428
+ ]);
429
+ });
430
+ });
431
+
432
+ test("a round off the cron skips without touching the lease, and writes no ledger record", async () => {
433
+ await withTestDir(async (dir) => {
434
+ await initRepo(dir);
435
+ writeFixtureScheduledConvergeOp(dir, "staging-converge", "staging", "*/10 * * * *");
436
+ const activities = fakeTickActivities(dir, "staging", "staging-converge");
437
+
438
+ const events = await runOperatorRound({ cwd: dir, holder: "op-a", activities, profiles: PROFILES, now: quiet });
439
+ expect(events).toEqual([
440
+ { kind: "skipped-not-due", op: "staging-converge", env: "staging", cron: "*/10 * * * *" },
441
+ ]);
442
+
443
+ const { records } = await readConvergeLedger("staging", { cwd: dir });
444
+ expect(records).toHaveLength(0);
445
+ expect((await readLease("staging-converge", { cwd: dir })).record).toBeUndefined();
446
+ });
447
+ });
448
+
449
+ test("a late round still owes the tick: a firing minute passed since this op was last considered", async () => {
450
+ await withTestDir(async (dir) => {
451
+ await initRepo(dir);
452
+ writeFixtureScheduledConvergeOp(dir, "staging-converge", "staging", "*/10 * * * *");
453
+ const activities = fakeTickActivities(dir, "staging", "staging-converge");
454
+ const scheduleState = new Map<string, Date>();
455
+
456
+ // 13:33 — first sight, not a firing minute: recorded, not ticked.
457
+ const first = await runOperatorRound({ cwd: dir, holder: "op-a", activities, profiles: PROFILES, now: quiet, scheduleState });
458
+ expect(first[0].kind).toBe("skipped-not-due");
459
+
460
+ // 13:42 — 13:40 fired while nobody was looking, so this round ticks.
461
+ const late = () => new Date(2026, 8, 6, 13, 42, 0, 0);
462
+ const second = await runOperatorRound({ cwd: dir, holder: "op-a", activities, profiles: PROFILES, now: late, scheduleState });
463
+ expect(second[0].kind).toBe("ticked");
464
+
465
+ // 13:43 — nothing has fired since 13:42.
466
+ const after = () => new Date(2026, 8, 6, 13, 43, 0, 0);
467
+ const third = await runOperatorRound({ cwd: dir, holder: "op-a", activities, profiles: PROFILES, now: after, scheduleState });
468
+ expect(third[0].kind).toBe("skipped-not-due");
469
+ });
470
+ });
471
+
472
+ test("an op with no cadence of its own still ticks every round — `--interval` is the fallback", async () => {
473
+ await withTestDir(async (dir) => {
474
+ await initRepo(dir);
475
+ writeFixtureConvergeOp(dir, "staging-converge", "staging");
476
+ const activities = fakeTickActivities(dir, "staging", "staging-converge");
477
+
478
+ const events = await runOperatorRound({ cwd: dir, holder: "op-a", activities, profiles: PROFILES, now: quiet });
479
+ expect(events[0].kind).toBe("ticked");
480
+ });
481
+ });
482
+ });
483
+
333
484
  describe("formatRoundLine", () => {
334
485
  test("renders each event kind as one line", () => {
335
- expect(formatRoundLine({ kind: "ticked", op: "x", env: "staging", result: { op: "x", records: [], totalMs: 1, ok: true } }))
336
- .toContain("ticked=1 ok=true");
486
+ expect(formatRoundLine({
487
+ kind: "ticked", op: "x", env: "staging",
488
+ result: {
489
+ op: "x", records: [], totalMs: 1, status: "ok", startedAt: "2026-09-05T12:00:00.000Z",
490
+ record: runRecord("x", "ok"),
491
+ },
492
+ })).toContain("ticked=1 status=ok");
493
+ expect(formatRoundLine({
494
+ kind: "ticked", op: "x", env: "staging",
495
+ result: {
496
+ op: "x", records: [], totalMs: 1, status: "gated", startedAt: "2026-09-05T12:00:00.000Z",
497
+ gate: {
498
+ version: 1, kind: "pending", op: "x", gate: "rollout-gate",
499
+ timestamp: "2026-09-05T12:00:00.000Z", expiresAt: "2026-09-07T12:00:00.000Z",
500
+ },
501
+ record: runRecord("x", "gated"),
502
+ },
503
+ })).toContain('status=gated gate="rollout-gate"');
337
504
  expect(formatRoundLine({ kind: "skipped-lease-held", op: "x", env: "staging", heldBy: "holder-a" }))
338
505
  .toContain("lease-held:holder-a");
339
506
  expect(formatRoundLine({ kind: "tick-failed", op: "x", env: "staging", error: "boom" }))
@@ -342,5 +509,7 @@ describe("formatRoundLine", () => {
342
509
  .toContain("fenced=1");
343
510
  expect(formatRoundLine({ kind: "lease-error", op: "x", env: "staging", error: "stale lock at .../x.lock" }))
344
511
  .toContain("stale lock at .../x.lock");
512
+ expect(formatRoundLine({ kind: "skipped-not-due", op: "x", env: "staging", cron: "*/10 * * * *" }))
513
+ .toContain("not-due:*/10 * * * *");
345
514
  });
346
515
  });
@@ -1,13 +1,13 @@
1
1
  /**
2
- * `chant operator` (#1485, epic #1487) — native durable ticks without
3
- * Temporal. A deliberately small daemon: a timer, a lease
2
+ * `chant operator` (#1485, epic #1487) — durable ticks with no service behind
3
+ * them. A deliberately small daemon: a timer, a lease
4
4
  * (../lifecycle/lease.ts), and the existing local executor
5
5
  * (./local-executor.ts) in a loop. No new service, no new state store — the
6
6
  * lease lives on a git ref, tick state lives on the `chant/lifecycle`
7
7
  * orphan branch via the same `convergeTick` activity `chant run <op>`
8
8
  * already calls for a single one-shot tick.
9
9
  *
10
- * One round: discover every `ConvergeOp` (searchAttributes `Converge:
10
+ * One round: discover every `ConvergeOp` (label `Converge:
11
11
  * "true"`, optionally filtered to one env), and for each one, try to
12
12
  * acquire (or renew) its lease. Win it — run one tick via `runOpLocally` on
13
13
  * the Op's own Observe → Converge phases, the identical path `chant run
@@ -37,23 +37,32 @@
37
37
  */
38
38
  import type { ActivityFn, ActivityProfile } from "./activity-registry";
39
39
  import { discoverOps, type DiscoveredOp } from "./discover";
40
- import { runOpLocally, OpRunFailure, LocalGateUnsupportedError, type OpRunResult } from "./local-executor";
40
+ import { runOpLocally, OpRunFailure, type OpRunResult } from "./local-executor";
41
41
  import { acquireLease, stillHoldsLease, currentHolderId, DEFAULT_LEASE_TTL_MS, type LeaseRecord, type AcquireLeaseResult } from "../lifecycle/lease";
42
42
  import { StaleLockError } from "../lifecycle/git";
43
+ import { cronMatches, cronDueBetween } from "./cron";
43
44
 
44
- /** Poll interval between rounds — the operator's own cadence, distinct from a `ConvergeOp`'s Temporal `schedule` cron (that field drives the durable path's `TemporalSchedule`, not anything the local executor can read back at discovery time; see this module's doc). Chosen short enough to converge promptly, long enough not to hammer `chant lifecycle plan --live` every few seconds. */
45
+ /**
46
+ * Poll interval between rounds — the operator's own cadence, and the fallback
47
+ * for an Op that declares none. Since #2120 an Op can carry its own
48
+ * `schedule.cron` (`./composites/converge-op.ts`), which this loop honours per
49
+ * Op: the round still wakes every `intervalMs`, but an Op with a cron is
50
+ * ticked only on the minutes that cron fires. Chosen short enough to converge
51
+ * promptly, long enough not to hammer `chant lifecycle plan --live` every few
52
+ * seconds — and short enough that a per-minute cron is not missed.
53
+ */
45
54
  export const DEFAULT_OPERATOR_INTERVAL_MS = 60_000;
46
55
 
47
- /** One ConvergeOp's own `searchAttributes.Env` (`../../lexicons/temporal/src/composites/converge-op.ts` always sets it). `undefined` for a non-ConvergeOp or a hand-built one missing it — filtered out by `isConvergeOp` before this is trusted. */
56
+ /** One ConvergeOp's own `labels.Env` (`./composites/converge-op.ts` always sets it). `undefined` for a non-ConvergeOp or a hand-built one missing it — filtered out by `isConvergeOp` before this is trusted. */
48
57
  function envOf(config: DiscoveredOp["config"]): string | undefined {
49
- return config.searchAttributes?.Env;
58
+ return config.labels?.Env;
50
59
  }
51
60
 
52
61
  function isConvergeOp(config: DiscoveredOp["config"]): boolean {
53
- return config.searchAttributes?.Converge === "true";
62
+ return config.labels?.Converge === "true";
54
63
  }
55
64
 
56
- /** Discover every `ConvergeOp` in the project (`searchAttributes.Converge === "true"`), optionally filtered to one environment. Ops that don't declare an `Env` search attribute are excluded from an `--env`-filtered discovery (there's nothing to match), but included when no `env` filter is given. */
65
+ /** Discover every `ConvergeOp` in the project (`labels.Converge === "true"`), optionally filtered to one environment. Ops that don't declare an `Env` label are excluded from an `--env`-filtered discovery (there's nothing to match), but included when no `env` filter is given. */
57
66
  export async function discoverConvergeOps(
58
67
  opts?: { cwd?: string; env?: string },
59
68
  ): Promise<{ ops: DiscoveredOp[]; errors: string[] }> {
@@ -67,6 +76,8 @@ export async function discoverConvergeOps(
67
76
  export type OperatorTickEvent =
68
77
  | { kind: "ticked"; op: string; env: string; result: OpRunResult }
69
78
  | { kind: "skipped-lease-held"; op: string; env: string; heldBy?: string }
79
+ /** The Op declares its own `schedule.cron` (#2120) and this round did not land on a firing minute — the lease was never touched. An Op without a cron is never reported this way: it ticks every round, on `--interval`. */
80
+ | { kind: "skipped-not-due"; op: string; env: string; cron: string }
70
81
  | { kind: "tick-failed"; op: string; env: string; error: string }
71
82
  /** The lease was lost between acquiring it and the tick finishing (e.g. this process stalled past its TTL and another operator reclaimed it) — the tick's own ledger record (written inside `convergeTick`) still landed, since a converge tick is idempotent by design; this event exists purely so `chant operator`'s log and the ledger-independent test surface can see the fencing violation happened. Never a hard failure. */
72
83
  | { kind: "fenced"; op: string; env: string }
@@ -96,6 +107,16 @@ export interface OperatorRoundOptions {
96
107
  profiles: Record<string, ActivityProfile>;
97
108
  now?: () => Date;
98
109
  signal?: AbortSignal;
110
+ /**
111
+ * When each Op with its own `schedule.cron` was last considered (#2120) —
112
+ * the state a round needs to answer "has a firing minute passed since I
113
+ * last looked at this Op?". `runOperatorForever` owns one map across
114
+ * rounds; a bare `runOperatorRound` with none given falls back to "is this
115
+ * minute a firing minute", which is what a single round can know on its
116
+ * own. Every considered Op's entry is updated, ticked or not, so a late
117
+ * round makes up exactly one tick rather than a backlog.
118
+ */
119
+ scheduleState?: Map<string, Date>;
99
120
  }
100
121
 
101
122
  /**
@@ -112,6 +133,24 @@ export async function runOperatorRound(opts: OperatorRoundOptions): Promise<Oper
112
133
  for (const { config } of ops) {
113
134
  const env = envOf(config) ?? "unknown";
114
135
 
136
+ // An Op that declares its own cadence (#2120) is ticked on that cron
137
+ // rather than on every round. Level-triggered: the question is whether a
138
+ // firing minute has passed since this Op was last looked at, so a round
139
+ // that arrives late still owes exactly one tick — a converge tick
140
+ // re-observes everything anyway, and several missed fires are one tick's
141
+ // worth of work.
142
+ const cron = config.schedule?.cron;
143
+ if (cron) {
144
+ const now = (opts.now ?? (() => new Date()))();
145
+ const lastSeen = opts.scheduleState?.get(config.name);
146
+ const due = lastSeen === undefined ? cronMatches(cron, now) : cronDueBetween(cron, lastSeen, now);
147
+ opts.scheduleState?.set(config.name, now);
148
+ if (!due) {
149
+ events.push({ kind: "skipped-not-due", op: config.name, env, cron });
150
+ continue;
151
+ }
152
+ }
153
+
115
154
  let acquired: AcquireLeaseResult;
116
155
  try {
117
156
  acquired = await acquireLease(config.name, holder, {
@@ -137,18 +176,18 @@ export async function runOperatorRound(opts: OperatorRoundOptions): Promise<Oper
137
176
 
138
177
  const lease = acquired.lease as LeaseRecord;
139
178
  try {
140
- const result = await runOpLocally(config, opts.activities, opts.profiles, opts.signal);
179
+ const result = await runOpLocally(config, opts.activities, opts.profiles, opts.signal, {
180
+ ledger: { cwd: opts.cwd },
181
+ });
141
182
  const held = await stillHoldsLease(config.name, holder, lease.token, { cwd: opts.cwd });
142
183
  events.push(held ? { kind: "ticked", op: config.name, env, result } : { kind: "fenced", op: config.name, env });
143
184
  } catch (err) {
144
185
  const message =
145
186
  err instanceof OpRunFailure
146
187
  ? `Op "${config.name}" failed — see its ledger record for step-level detail`
147
- : err instanceof LocalGateUnsupportedError
188
+ : err instanceof Error
148
189
  ? err.message
149
- : err instanceof Error
150
- ? err.message
151
- : String(err);
190
+ : String(err);
152
191
  events.push({ kind: "tick-failed", op: config.name, env, error: message });
153
192
  }
154
193
  }
@@ -188,8 +227,13 @@ export interface OperatorLoopOptions extends OperatorRoundOptions {
188
227
  */
189
228
  export async function runOperatorForever(opts: OperatorLoopOptions): Promise<void> {
190
229
  const holder = opts.holder ?? currentHolderId();
230
+ // Per-op cron state (#2120) lives for the life of the loop. A restart starts
231
+ // it empty, which means the first round ticks a scheduled Op only if it
232
+ // lands on a firing minute — a fresh operator owes no catch-up for the ticks
233
+ // it was not running for.
234
+ const scheduleState = opts.scheduleState ?? new Map<string, Date>();
191
235
  while (!opts.signal?.aborted) {
192
- const events = await runOperatorRound({ ...opts, holder });
236
+ const events = await runOperatorRound({ ...opts, holder, scheduleState });
193
237
  opts.onRound?.(events);
194
238
  if (opts.signal?.aborted) break;
195
239
  await sleepAbortable(opts.intervalMs ?? DEFAULT_OPERATOR_INTERVAL_MS, opts.signal);
@@ -200,9 +244,12 @@ export async function runOperatorForever(opts: OperatorLoopOptions): Promise<voi
200
244
  export function formatRoundLine(event: OperatorTickEvent): string {
201
245
  switch (event.kind) {
202
246
  case "ticked":
203
- return `operator: ${event.op}@${event.env} ticked=1 ok=${event.result.ok}`;
247
+ return `operator: ${event.op}@${event.env} ticked=1 status=${event.result.status}` +
248
+ (event.result.gate ? ` gate="${event.result.gate.gate}"` : "");
204
249
  case "skipped-lease-held":
205
250
  return `operator: ${event.op}@${event.env} skipped=1(lease-held${event.heldBy ? `:${event.heldBy}` : ""})`;
251
+ case "skipped-not-due":
252
+ return `operator: ${event.op}@${event.env} skipped=1(not-due:${event.cron})`;
206
253
  case "tick-failed":
207
254
  return `operator: ${event.op}@${event.env} failed=1 error="${event.error}"`;
208
255
  case "fenced":
@@ -108,7 +108,7 @@ export interface ReceiptReadArgs extends ReceiptCheckInput {}
108
108
  export interface ReceiptReadResult {
109
109
  /** The receipt's live stored value, or null when absent. */
110
110
  current: string | null;
111
- /** The resolved expectation the workflow compares and later writes. */
111
+ /** The resolved expectation the effect step compares and later writes. */
112
112
  expectation: string;
113
113
  /** Convenience: `current === expectation`. */
114
114
  applied: boolean;
@@ -2,7 +2,25 @@ import { createResource } from "../runtime";
2
2
 
3
3
  /**
4
4
  * The Declarable resource backing an Op definition.
5
- * entityType: "Temporal::Op", lexicon: "temporal"
5
+ * entityType: "Chant::Op", lexicon: "chant"
6
6
  * Discovered automatically alongside infra files — no pipeline changes needed.
7
+ *
8
+ * An Op is chant's own verb, not a runtime's (#2118, epic #2114): the model,
9
+ * the executor and the ledger all live in core, and a lexicon that hosts runs
10
+ * is one runtime among several. The entity type says so.
7
11
  */
8
- export const OpResource = createResource("Temporal::Op", "temporal", {});
12
+ export const OP_ENTITY_TYPE = "Chant::Op";
13
+
14
+ export const OpResource = createResource(OP_ENTITY_TYPE, "chant", {});
15
+
16
+ /**
17
+ * Is this entity an Op declaration?
18
+ *
19
+ * Duck-typed on `entityType` rather than `instanceof OpResource` for the same
20
+ * reason `../build.ts`'s dependency inference is: an entity can be built
21
+ * against a different copy of core (a linked lexicon, a child project) and
22
+ * still be the same declaration.
23
+ */
24
+ export function isOpEntity(entity: unknown): boolean {
25
+ return (entity as { entityType?: string } | null)?.entityType === OP_ENTITY_TYPE;
26
+ }
@@ -0,0 +1,203 @@
1
+ /**
2
+ * The op-runtime seam (#2121, epic #2114).
3
+ *
4
+ * An Op is a convergent verb; *where* it runs is a separate question from
5
+ * *what* it does. This module states that question once, as a provider
6
+ * contract, so `chant run` has one dispatch path instead of a branch per
7
+ * runtime. Core ships the `local` provider (./runtimes/local.ts) and never
8
+ * imports a hosting lexicon; a lexicon that hosts runs implements
9
+ * {@link OpRuntimeProvider} and hangs it off `LexiconPlugin.opRuntime`, where
10
+ * `chant run <op> --on <lexicon>` finds it.
11
+ *
12
+ * The record shapes below are the run ledger's (#2118): what a run wrote to
13
+ * `<env>/runs__<op>.jsonl` is what a provider's `status` and `log` read back,
14
+ * so a run's outcome survives the process that produced it. A provider with no
15
+ * ledger behind it (a hosting lexicon reporting a live run) fills the same
16
+ * shape from whatever it does know.
17
+ */
18
+
19
+ import type { OpConfig } from "./types";
20
+ import type { StepRecord, OpRunResult } from "./local-executor";
21
+ import type { GateResolutionRecord } from "../lifecycle/gate-ledger";
22
+ import type { RunComponentsOptions, RunComponentsResult } from "../components/cli-support";
23
+
24
+ /**
25
+ * How a run ended, or that it has not. `gated` is the state a run reaches when
26
+ * a gate has no recorded resolution — a fact, not a wait (#2119).
27
+ *
28
+ * This is the coarse state a *runtime* reports. The ledger records the
29
+ * executor's own three-state outcome instead ({@link OpRunRecord.status}),
30
+ * which has no `running` or `cancelled`: a record is only written once a run
31
+ * has settled.
32
+ */
33
+ export type OpRunState = "running" | "completed" | "failed" | "gated" | "cancelled";
34
+
35
+ /** Map a settled run's ledger status onto the coarser runtime state. */
36
+ export function runStateOf(status: OpRunRecord["status"]): OpRunState {
37
+ return status === "ok" ? "completed" : status === "gated" ? "gated" : "failed";
38
+ }
39
+
40
+ /** One step's outcome as the run ledger keeps it — `StepRecord` minus the fields only a live renderer needs. */
41
+ export interface OpRunStepRecord {
42
+ /** The activity function name, or `gate:<signal>` for a gate step. */
43
+ fn: string;
44
+ status: StepRecord["status"];
45
+ durationMs: number;
46
+ /** The step's `outcomeAttribute` capture, when it declared one. */
47
+ outcome?: { name: string; value: unknown };
48
+ /** Who cleared this gate step and when (#2119), for a gate the run passed. */
49
+ approval?: { gate: string; resolvedBy: string; timestamp: string; url?: string };
50
+ /** The failure message, for `status: "fail"`. */
51
+ error?: string;
52
+ }
53
+
54
+ /** One phase's steps and the verdict they add up to. */
55
+ export interface OpRunPhaseRecord {
56
+ name: string;
57
+ /** `fail` if any step failed, `skipped` if every step was skipped, else `ok`. */
58
+ status: "ok" | "fail" | "skipped";
59
+ steps: OpRunStepRecord[];
60
+ }
61
+
62
+ /**
63
+ * A run's current state, as every runtime reports it.
64
+ *
65
+ * `records` and `result` are populated by a runtime that has the executor's
66
+ * own step records to hand (the local one does, for a run it just executed in
67
+ * this process); a runtime answering from the ledger alone omits them rather
68
+ * than inventing them.
69
+ */
70
+ export interface OpRunStatus {
71
+ op: string;
72
+ runId: string;
73
+ state: OpRunState;
74
+ /** ISO-8601 instant the run started. */
75
+ startedAt: string;
76
+ /** ISO-8601 instant the run settled; absent while it is still running. */
77
+ endedAt?: string;
78
+ /** Per-step records, when the runtime has them. */
79
+ records?: StepRecord[];
80
+ /** The executor's own result, when the runtime is executing in this process. */
81
+ result?: OpRunResult;
82
+ /** The gate this run is waiting on, when `state` is `gated`. */
83
+ gate?: { name: string; since: string };
84
+ /** Free-text detail for a failed run. */
85
+ error?: string;
86
+ }
87
+
88
+ /**
89
+ * One immutable, settled Op run — the run ledger's line
90
+ * (`../lifecycle/run-ledger.ts`), and what `chant run log <op>` prints a row
91
+ * per.
92
+ *
93
+ * This is where a run's outcome lives now that `OpConfig` no longer carries
94
+ * `searchAttributes` for the generated workflow to upsert (#2118). `labels` is
95
+ * copied off the declaration so a reader can filter runs the way it filters
96
+ * declarations; `outcomes` is every `outcomeAttribute` the run captured, later
97
+ * steps winning over earlier ones for a repeated name, exactly as an upsert
98
+ * would have.
99
+ */
100
+ export interface OpRunRecord {
101
+ /** Schema version, so an incompatible future shape is detected before being misread. */
102
+ version: 1;
103
+ /** Stable run id — the same string a pending gate fact records for this run. */
104
+ id: string;
105
+ /** The Op's name (`OpConfig.name`). */
106
+ op: string;
107
+ /** The Op's `labels.Env`, or `local` when it declares none. */
108
+ env: string;
109
+ /** ISO-8601 instant the run started. */
110
+ started: string;
111
+ /** ISO-8601 instant the run settled. */
112
+ ended: string;
113
+ /**
114
+ * The executor's three-state outcome. `gated` is neither success nor
115
+ * failure: the run reached a gate nobody has approved, recorded the fact,
116
+ * and stopped (#2119).
117
+ */
118
+ status: "ok" | "fail" | "gated";
119
+ /** The Op's own `labels`, copied at run time. */
120
+ labels: Record<string, string>;
121
+ /** Every `outcomeAttribute` the run captured, name → value. */
122
+ outcomes: Record<string, unknown>;
123
+ /** Per-phase, per-step status, in execution order. */
124
+ phases: OpRunPhaseRecord[];
125
+ /** The gate the run stopped on, for `status: "gated"`. */
126
+ gate?: { name: string; since: string };
127
+ }
128
+
129
+ /** An {@link OpRunRecord} before the ledger stamps its version and mints an id. */
130
+ export type OpRunRecordInput = Omit<OpRunRecord, "version" | "id"> & { id?: string };
131
+
132
+ /** A started run. `result()` settles with the run's final status. */
133
+ export interface OpRunHandle {
134
+ op: string;
135
+ runId: string;
136
+ result(): Promise<OpRunStatus>;
137
+ }
138
+
139
+ /** What a caller hands a runtime when it starts a run. */
140
+ export interface OpRunStartOptions {
141
+ /** Build-time parameter bindings, when the caller resolved any. */
142
+ params?: Record<string, unknown>;
143
+ /** Target environment (`--env`). */
144
+ env?: string;
145
+ /**
146
+ * `-p, --profile <name>` (#2124, restored in #2192) — the named connection
147
+ * profile this runtime should target. Only a runtime that has profiles
148
+ * reads it; the local one ignores it, and a runtime that reads it decides
149
+ * for itself what an unknown name means (fountain refuses it rather than
150
+ * falling back to its default).
151
+ */
152
+ profile?: string;
153
+ /** Called once per settled step, so `--progress-json` can stream. */
154
+ progress?: (record: StepRecord) => void;
155
+ /** Aborts in-flight work (Ctrl-C). */
156
+ signal?: AbortSignal;
157
+ }
158
+
159
+ /**
160
+ * The contract a runtime implements to host Op runs.
161
+ *
162
+ * `chant run` resolves exactly one provider per invocation — the named
163
+ * lexicon's for `--on <name>`, the built-in `local` one otherwise — and every
164
+ * subcommand calls it. A provider that cannot do something (cancel a
165
+ * foreground run, host components) says so by throwing a message the CLI
166
+ * prints verbatim, or by omitting the optional member.
167
+ */
168
+ export interface OpRuntimeProvider {
169
+ /** How the CLI names this runtime in messages, and what `--on` matches. */
170
+ readonly name: string;
171
+
172
+ /** Start a run. Rejects with an actionable Error when it cannot. */
173
+ start(op: OpConfig, opts: OpRunStartOptions): Promise<OpRunHandle>;
174
+
175
+ /** The latest run's state, or `undefined` when this runtime has none recorded. */
176
+ status(op: string): Promise<OpRunStatus | undefined>;
177
+
178
+ /** Run history, newest first. Empty when nothing is recorded. */
179
+ log(op: string, opts?: { limit?: number }): Promise<OpRunRecord[]>;
180
+
181
+ /** Latest state for each of `ops`, keyed by op name. */
182
+ list(ops: OpConfig[]): Promise<Map<string, OpRunStatus | undefined>>;
183
+
184
+ /** Cancel the active run. `force` is the CLI's confirmation, already checked. */
185
+ cancel(op: string, opts: { force: boolean }): Promise<void>;
186
+
187
+ /**
188
+ * Wake a run whose gate has just been resolved. The ledger write is the
189
+ * fact and `chant approve` owns it; this is the runtime's chance to act on
190
+ * it. Omit when a run re-reads the ledger on its own.
191
+ */
192
+ resolveGate?(op: string, gate: string, resolution: GateResolutionRecord): Promise<void>;
193
+
194
+ /**
195
+ * Host `chant run --components`. Optional: a runtime without it refuses the
196
+ * flag with one line naming itself.
197
+ */
198
+ runComponents?(
199
+ projectPath: string,
200
+ selector: string,
201
+ options: RunComponentsOptions,
202
+ ): Promise<RunComponentsResult>;
203
+ }