@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
package/src/op/cron.ts ADDED
@@ -0,0 +1,149 @@
1
+ /**
2
+ * Cron for an Op's own schedule (#2120) — validation at `Op()` construction
3
+ * and minute-granularity matching for `chant operator`'s per-op cadence.
4
+ *
5
+ * `OpConfig.schedule.cron` is runtime-neutral data: the github lexicon turns
6
+ * it into `on.schedule`, a hosting lexicon hands it to its own scheduler,
7
+ * `chant operator` reads it as that Op's tick cadence, and the local one-shot
8
+ * executor ignores it entirely. Nothing here talks to a scheduler.
9
+ *
10
+ * The validator a lexicon's own post-synth cron check imports rather than
11
+ * keeping its own copy:
12
+ * deliberately permissive, a pre-submission guard rather than a full parser,
13
+ * since the final word belongs to whichever scheduler runs the cron.
14
+ *
15
+ * {@link cronMatches} is stricter about what it can *evaluate* than
16
+ * {@link isValidCronExpression} is about what it accepts, so a field carrying
17
+ * a Quartz extension the evaluator does not implement (`L`, `W`, `#`, `?`)
18
+ * matches everything rather than nothing — an operator that ticks too often
19
+ * is a wasted converge, one that never ticks is a silent outage.
20
+ */
21
+
22
+ /** Very permissive cron field pattern — catches obvious syntax errors. */
23
+ const CRON_FIELD = /^[0-9*,/\-?LW#]+$/;
24
+
25
+ /**
26
+ * True when `expr` looks like 5- or 6-field cron. Field-shape only: it does
27
+ * not check that a number is in range for its position, and it accepts the
28
+ * Quartz extensions (`L`, `W`, `#`, `?`) some schedulers take.
29
+ */
30
+ export function isValidCronExpression(expr: string): boolean {
31
+ const fields = expr.trim().split(/\s+/);
32
+ if (fields.length < 5 || fields.length > 6) return false;
33
+ return fields.every((f) => CRON_FIELD.test(f));
34
+ }
35
+
36
+ /**
37
+ * The message `Op()` throws with and a lexicon's post-synth cron check
38
+ * reports, so one wording covers both the build-time refusal and the
39
+ * post-synth diagnostic.
40
+ */
41
+ export function cronSyntaxMessage(expr: string): string {
42
+ return `cron expression "${expr}" does not look like valid 5- or 6-field cron syntax`;
43
+ }
44
+
45
+ /** A parsed cron field: either "matches anything" or the exact set it matches. */
46
+ type FieldSpec = { any: true } | { any: false; values: Set<number> };
47
+
48
+ const ANY: FieldSpec = { any: true };
49
+
50
+ /**
51
+ * Parse one field into the set of values it matches. Anything the evaluator
52
+ * cannot make sense of — a Quartz extension, an inverted range, a zero step —
53
+ * degrades to {@link ANY} rather than throwing: this runs on an expression
54
+ * `Op()` already accepted, and refusing at tick time would strand the Op.
55
+ */
56
+ function parseField(field: string, min: number, max: number): FieldSpec {
57
+ if (field === "*" || field === "?" || /[LW#]/.test(field)) return ANY;
58
+
59
+ const values = new Set<number>();
60
+ for (const part of field.split(",")) {
61
+ const [rangePart, stepPart] = part.split("/");
62
+ const step = stepPart === undefined ? 1 : Number(stepPart);
63
+ if (!Number.isInteger(step) || step < 1) return ANY;
64
+
65
+ let lo: number;
66
+ let hi: number;
67
+ if (rangePart === "*" || rangePart === "") {
68
+ lo = min;
69
+ hi = max;
70
+ } else if (rangePart.includes("-")) {
71
+ const [a, b] = rangePart.split("-");
72
+ lo = Number(a);
73
+ hi = Number(b);
74
+ } else {
75
+ lo = Number(rangePart);
76
+ // `5/10` means "from 5, every 10" — a bare `5` means only 5.
77
+ hi = stepPart === undefined ? lo : max;
78
+ }
79
+ if (!Number.isInteger(lo) || !Number.isInteger(hi) || lo > hi) return ANY;
80
+
81
+ for (let v = lo; v <= hi; v += step) values.add(v);
82
+ }
83
+ return values.size === 0 ? ANY : { any: false, values };
84
+ }
85
+
86
+ function fieldMatches(spec: FieldSpec, value: number): boolean {
87
+ return spec.any || spec.values.has(value);
88
+ }
89
+
90
+ /**
91
+ * True when `date` falls on a minute `expr` fires at, in the host's local
92
+ * time zone (the only clock the operator daemon has).
93
+ *
94
+ * A 6-field expression is read as Quartz's `second minute hour dom month dow`
95
+ * and the seconds field is dropped — nothing in chant ticks sub-minute.
96
+ * Day-of-month and day-of-week follow the usual cron rule: when both are
97
+ * restricted, either one matching is enough.
98
+ */
99
+ export function cronMatches(expr: string, date: Date): boolean {
100
+ const fields = expr.trim().split(/\s+/);
101
+ const five = fields.length === 6 ? fields.slice(1) : fields;
102
+ if (five.length !== 5) return false;
103
+
104
+ const [minuteF, hourF, domF, monthF, dowF] = five;
105
+ if (!fieldMatches(parseField(minuteF, 0, 59), date.getMinutes())) return false;
106
+ if (!fieldMatches(parseField(hourF, 0, 23), date.getHours())) return false;
107
+ if (!fieldMatches(parseField(monthF, 1, 12), date.getMonth() + 1)) return false;
108
+
109
+ const dom = parseField(domF, 1, 31);
110
+ const dow = parseField(dowF, 0, 7);
111
+ const weekday = date.getDay();
112
+ const domOk = fieldMatches(dom, date.getDate());
113
+ const dowOk = fieldMatches(dow, weekday) || (weekday === 0 && fieldMatches(dow, 7));
114
+
115
+ if (dom.any && dow.any) return true;
116
+ if (dom.any) return dowOk;
117
+ if (dow.any) return domOk;
118
+ return domOk || dowOk;
119
+ }
120
+
121
+ /**
122
+ * The longest window {@link cronDueBetween} scans minute by minute. Past it
123
+ * the answer is "yes" without scanning: an operator that has not looked at an
124
+ * op for two days has certainly missed a fire of any cron worth putting on an
125
+ * Op, and the alternative — 100k iterations on a clock jump — buys nothing.
126
+ */
127
+ const MAX_SCAN_MINUTES = 2 * 24 * 60;
128
+
129
+ /**
130
+ * True when `expr` fires at some minute in `(after, upTo]` — what a round
131
+ * asks for an Op it last ticked at `after`. Level-triggered on purpose: a
132
+ * round that arrives late still ticks the Op it owes rather than waiting for
133
+ * the next exact match, and several missed fires collapse into the one tick a
134
+ * convergent verb needs.
135
+ */
136
+ export function cronDueBetween(expr: string, after: Date, upTo: Date): boolean {
137
+ if (upTo.getTime() <= after.getTime()) return false;
138
+
139
+ const startMs = Math.floor(after.getTime() / 60_000) * 60_000 + 60_000;
140
+ if (startMs > upTo.getTime()) return false;
141
+
142
+ const spanMinutes = Math.floor((upTo.getTime() - startMs) / 60_000) + 1;
143
+ if (spanMinutes > MAX_SCAN_MINUTES) return true;
144
+
145
+ for (let i = 0; i < spanMinutes; i++) {
146
+ if (cronMatches(expr, new Date(startMs + i * 60_000))) return true;
147
+ }
148
+ return false;
149
+ }
@@ -16,7 +16,8 @@ import { discoverOps } from "./discover";
16
16
  * symlink and the walk compares resolved paths).
17
17
  */
18
18
  let fakeGitRoot = "";
19
- vi.mock("../runtime-adapter", () => ({
19
+ vi.mock("../runtime-adapter", async (importOriginal) => ({
20
+ ...(await importOriginal<typeof import("../runtime-adapter")>()),
20
21
  getRuntime: () => ({
21
22
  spawn: async (cmd: string[]) =>
22
23
  cmd[0] === "git" && cmd[1] === "rev-parse"
@@ -66,3 +67,106 @@ describe("discoverOps — the scan root is the project, not the checkout (#2058)
66
67
  expect([...ops.keys()].sort()).toEqual(["mine", "root", "theirs"]);
67
68
  });
68
69
  });
70
+
71
+ /**
72
+ * #2171. An Op may be exported by name, not only as the file's default.
73
+ *
74
+ * Discovery used to read `mod.default` alone, which forced every runnable Op
75
+ * into the one export shape the fold path refuses, so an Op under a project's
76
+ * `sourceDir` cost that project its fold coverage. The default export is still
77
+ * accepted, unchanged; these cases are the widening around it.
78
+ *
79
+ * Same fake-git-root harness as the suite above: the Op files here are written
80
+ * as plain object literals with a `props` field, which is the shape discovery
81
+ * actually validates, so the fixtures stay free of the Op builders.
82
+ */
83
+ describe("discoverOps: an Op may be exported by name (#2171)", () => {
84
+ const OP_VALUE = (name: string): string =>
85
+ `{ props: { name: ${JSON.stringify(name)}, overview: "t", phases: [{ name: "Run", steps: [] }] } }`;
86
+
87
+ let checkout: string;
88
+ const write = (file: string, body: string): void => {
89
+ writeFileSync(join(checkout, "ops", file), body);
90
+ };
91
+
92
+ beforeEach(() => {
93
+ checkout = realpathSync(mkdtempSync(join(tmpdir(), "chant-op-named-")));
94
+ fakeGitRoot = checkout;
95
+ mkdirSync(join(checkout, "ops"), { recursive: true });
96
+ writeFileSync(join(checkout, "chant.config.json"), JSON.stringify({ lexicons: ["aws"] }));
97
+ });
98
+ afterEach(() => rmSync(checkout, { recursive: true, force: true }));
99
+
100
+ test("a named export is discovered", async () => {
101
+ write("named.op.ts", `export const deploy = ${OP_VALUE("deploy")};\n`);
102
+ const { ops, errors } = await discoverOps({ cwd: checkout });
103
+ expect(errors).toEqual([]);
104
+ expect([...ops.keys()]).toEqual(["deploy"]);
105
+ expect(ops.get("deploy")!.exportName).toBe("deploy");
106
+ });
107
+
108
+ test("the default export still works, and reports itself as the default", async () => {
109
+ write("legacy.op.ts", `export default ${OP_VALUE("legacy")};\n`);
110
+ const { ops, errors } = await discoverOps({ cwd: checkout });
111
+ expect(errors).toEqual([]);
112
+ expect([...ops.keys()]).toEqual(["legacy"]);
113
+ expect(ops.get("legacy")!.exportName).toBe("default");
114
+ });
115
+
116
+ test("one file may declare several Ops, and each is registered on its own", async () => {
117
+ write("many.op.ts", `export const a = ${OP_VALUE("alpha")};\nexport const b = ${OP_VALUE("beta")};\n`);
118
+ const { ops, errors } = await discoverOps({ cwd: checkout });
119
+ expect(errors).toEqual([]);
120
+ expect([...ops.keys()].sort()).toEqual(["alpha", "beta"]);
121
+ expect(ops.get("alpha")!.filePath).toBe(ops.get("beta")!.filePath);
122
+ });
123
+
124
+ test("a default and a named Op in one file are both registered", async () => {
125
+ write("both.op.ts", `export default ${OP_VALUE("first")};\nexport const other = ${OP_VALUE("second")};\n`);
126
+ const { ops, errors } = await discoverOps({ cwd: checkout });
127
+ expect(errors).toEqual([]);
128
+ expect([...ops.keys()].sort()).toEqual(["first", "second"]);
129
+ });
130
+
131
+ test("the same Op exported twice is one Op, not a self-collision", async () => {
132
+ write("aliased.op.ts", `const op = ${OP_VALUE("aliased")};\nexport default op;\nexport { op };\n`);
133
+ const { ops, errors } = await discoverOps({ cwd: checkout });
134
+ expect(errors).toEqual([]);
135
+ expect([...ops.keys()]).toEqual(["aliased"]);
136
+ expect(ops.get("aliased")!.exportName).toBe("default");
137
+ });
138
+
139
+ test("exports that are not Ops are skipped in silence", async () => {
140
+ write("mixed.op.ts", `export const meta = { team: "infra" };\nexport const notQuite = { props: { name: "x" } };\nexport const real = ${OP_VALUE("real")};\n`);
141
+ const { ops, errors } = await discoverOps({ cwd: checkout });
142
+ expect(errors).toEqual([]);
143
+ expect([...ops.keys()]).toEqual(["real"]);
144
+ });
145
+
146
+ test("a file exporting no Op at all is the error, and names both accepted shapes", async () => {
147
+ write("empty.op.ts", `export const meta = { team: "infra" };\n`);
148
+ const { ops, errors } = await discoverOps({ cwd: checkout });
149
+ expect(ops.size).toBe(0);
150
+ expect(errors).toHaveLength(1);
151
+ expect(errors[0]).toContain("exports no Op");
152
+ expect(errors[0]).toContain("export default Op({...})");
153
+ expect(errors[0]).toContain("export const deploy = Op({...})");
154
+ });
155
+
156
+ test("two files declaring the same Op name still collide", async () => {
157
+ write("one.op.ts", `export const a = ${OP_VALUE("same")};\n`);
158
+ write("two.op.ts", `export const b = ${OP_VALUE("same")};\n`);
159
+ const { ops, errors } = await discoverOps({ cwd: checkout });
160
+ expect(ops.size).toBe(1);
161
+ expect(errors).toHaveLength(1);
162
+ expect(errors[0]).toMatch(/^Duplicate Op name "same" in .+ and .+$/);
163
+ });
164
+
165
+ test("two Ops sharing a name inside one file collide, and the message says so", async () => {
166
+ write("clash.op.ts", `export const a = ${OP_VALUE("twice")};\nexport const b = ${OP_VALUE("twice")};\n`);
167
+ const { ops, errors } = await discoverOps({ cwd: checkout });
168
+ expect(ops.size).toBe(1);
169
+ expect(errors).toHaveLength(1);
170
+ expect(errors[0]).toContain(`Duplicate Op name "twice" declared twice in`);
171
+ });
172
+ });
@@ -1,8 +1,12 @@
1
1
  import { describe, test, expect, vi, beforeEach } from "vitest";
2
2
  import { discoverOps } from "./discover";
3
3
 
4
- // Mock getRuntime to return git root pointing at the repo root
5
- vi.mock("../runtime-adapter", () => ({
4
+ // Mock getRuntime to return git root pointing at the repo root. Partial, via
5
+ // `importOriginal`: the modules a discovered Op file pulls in reach the rest of
6
+ // this module (`moduleDir`, for the lint presets), and a wholesale replacement
7
+ // would make them fail to load rather than fail an assertion.
8
+ vi.mock("../runtime-adapter", async (importOriginal) => ({
9
+ ...(await importOriginal<typeof import("../runtime-adapter")>()),
6
10
  getRuntime: () => ({
7
11
  spawn: async (cmd: string[]) => {
8
12
  if (cmd[0] === "git" && cmd[1] === "rev-parse") {
@@ -7,6 +7,12 @@ import type { OpConfig } from "./types";
7
7
  export interface DiscoveredOp {
8
8
  config: OpConfig;
9
9
  filePath: string;
10
+ /**
11
+ * The export this Op arrived on: `"default"`, or the named export's own
12
+ * name (#2171). Diagnostic only, and the reason a file may carry more than
13
+ * one Op without either becoming ambiguous.
14
+ */
15
+ exportName: string;
10
16
  }
11
17
 
12
18
  export interface OpDiscoveryResult {
@@ -70,11 +76,71 @@ async function collectOpFiles(dir: string): Promise<string[]> {
70
76
  return files;
71
77
  }
72
78
 
79
+ /** The `OpConfig` behind an exported value, or `undefined` when the value is not an Op. An Op entity carries its config on `.props` (`./resource.ts`); `name` and `phases` are what every consumer of a discovered Op reads. */
80
+ function opConfigOf(value: unknown): OpConfig | undefined {
81
+ if (!value || typeof value !== "object") return undefined;
82
+ const config = (value as { props?: unknown }).props as OpConfig | undefined;
83
+ if (!config || typeof config.name !== "string" || !Array.isArray(config.phases)) return undefined;
84
+ return config;
85
+ }
86
+
87
+ /**
88
+ * Every Op a module exports, default first (#2171).
89
+ *
90
+ * Discovery used to read `mod.default` and nothing else, which made
91
+ * `export default op` the only shape a runnable Op could take. That is also
92
+ * the one export shape the fold path refuses (`../discovery/fold-import.ts`
93
+ * scans for it by name and falls the whole file back to run), so an Op living
94
+ * under a project's `sourceDir` cost that project its fold coverage, and took
95
+ * every file importing it down too. Accepting a named export removes the
96
+ * conflict without moving the fold's rule: the default export still works, so
97
+ * every Op file written against the documented shape keeps running unchanged.
98
+ *
99
+ * Non-Op exports are skipped in silence, not reported. An Op file is ordinary
100
+ * TypeScript and may export a helper, a config literal or a type alongside its
101
+ * Op; only a file exporting no Op at all is worth an error.
102
+ *
103
+ * A file may carry more than one Op, and each is registered on its own. Ops
104
+ * are keyed by their declared `config.name`, never by the export name or the
105
+ * file, so two Ops in one file are no more ambiguous to `chant run` than two
106
+ * Ops in two files, and the duplicate-name check in {@link discoverOps}
107
+ * covers both the same way. The same object exported twice (`export default
108
+ * op` alongside `export { op }`) is one Op, deduplicated by identity, not a
109
+ * self-collision.
110
+ *
111
+ * Export order is the module namespace's own: `default` first by construction
112
+ * here, then the named exports, which the ECMAScript specification requires a
113
+ * namespace object to enumerate in sorted order. So the discovered set is
114
+ * deterministic across runs and platforms.
115
+ */
116
+ function opsExportedBy(mod: Record<string, unknown>): Array<{ exportName: string; config: OpConfig }> {
117
+ const found: Array<{ exportName: string; config: OpConfig }> = [];
118
+ const seen = new Set<unknown>();
119
+
120
+ const consider = (exportName: string, value: unknown): void => {
121
+ if (seen.has(value)) return;
122
+ const config = opConfigOf(value);
123
+ if (!config) return;
124
+ seen.add(value);
125
+ found.push({ exportName, config });
126
+ };
127
+
128
+ consider("default", mod.default);
129
+ for (const exportName of Object.keys(mod)) {
130
+ if (exportName === "default") continue;
131
+ consider(exportName, mod[exportName]);
132
+ }
133
+ return found;
134
+ }
135
+
73
136
  /**
74
137
  * Discover all Op definitions from `*.op.ts` files under the nearest chant
75
138
  * project root (the directory holding `chant.config.ts`/`.json`, walking up
76
139
  * from `cwd`), or under the git root when no config exists — see
77
140
  * {@link findDiscoveryRoot} (#2058).
141
+ *
142
+ * An Op may be the file's default export or a named one, and a file may hold
143
+ * several. See {@link opsExportedBy} (#2171).
78
144
  */
79
145
  export async function discoverOps(opts?: { cwd?: string }): Promise<OpDiscoveryResult> {
80
146
  const errors: string[] = [];
@@ -87,28 +153,30 @@ export async function discoverOps(opts?: { cwd?: string }): Promise<OpDiscoveryR
87
153
 
88
154
  for (const filePath of files) {
89
155
  try {
90
- const mod = await import(filePath);
91
- const entity = mod.default;
156
+ const mod = (await import(filePath)) as Record<string, unknown>;
157
+ const exported = opsExportedBy(mod);
92
158
 
93
- if (!entity || typeof entity !== "object") {
94
- errors.push(`${filePath}: default export is not an object`);
159
+ if (exported.length === 0) {
160
+ errors.push(
161
+ `${filePath}: exports no Op. Expected \`export default Op({...})\` or a named export such as \`export const deploy = Op({...})\``,
162
+ );
95
163
  continue;
96
164
  }
97
165
 
98
- const config = entity.props as OpConfig | undefined;
166
+ for (const { exportName, config } of exported) {
167
+ const priorFile = nameToFile.get(config.name);
168
+ if (priorFile !== undefined) {
169
+ errors.push(
170
+ priorFile === filePath
171
+ ? `Duplicate Op name "${config.name}" declared twice in ${filePath}`
172
+ : `Duplicate Op name "${config.name}" in ${filePath} and ${priorFile}`,
173
+ );
174
+ continue;
175
+ }
99
176
 
100
- if (!config || typeof config.name !== "string" || !Array.isArray(config.phases)) {
101
- errors.push(`${filePath}: default export is not a valid Op (missing name or phases)`);
102
- continue;
177
+ nameToFile.set(config.name, filePath);
178
+ ops.set(config.name, { config, filePath, exportName });
103
179
  }
104
-
105
- if (nameToFile.has(config.name)) {
106
- errors.push(`Duplicate Op name "${config.name}" in ${filePath} and ${nameToFile.get(config.name)}`);
107
- continue;
108
- }
109
-
110
- nameToFile.set(config.name, filePath);
111
- ops.set(config.name, { config, filePath });
112
180
  } catch (err) {
113
181
  errors.push(`${filePath}: ${err instanceof Error ? err.message : String(err)}`);
114
182
  }
@@ -0,0 +1,21 @@
1
+ /**
2
+ * The duration-string parser the Op model uses everywhere a timeout is
3
+ * authored ("5m", "30s", "1h30m", "100ms"). Its own leaf module so both
4
+ * `./local-executor.ts` (activity start-to-close timeouts) and `./gate.ts`
5
+ * (a gate's pending-fact expiry) can reach it without importing each other.
6
+ * Re-exported from `./local-executor.ts`, which is where it lived and where
7
+ * every existing caller still imports it from.
8
+ */
9
+
10
+ /** Parse a duration string ("5m", "30s", "1h30m", "100ms") to milliseconds. Throws when nothing in `s` parses as a quantity+unit. */
11
+ export function parseDuration(s: string): number {
12
+ const units: Record<string, number> = { ms: 1, s: 1000, m: 60_000, h: 3_600_000, d: 86_400_000 };
13
+ let total = 0;
14
+ let matched = false;
15
+ for (const m of s.matchAll(/(\d+)(ms|s|m|h|d)/g)) {
16
+ total += Number(m[1]) * units[m[2]];
17
+ matched = true;
18
+ }
19
+ if (!matched) throw new Error(`unparseable duration: "${s}"`);
20
+ return total;
21
+ }
@@ -11,7 +11,7 @@
11
11
  * - `receiptStaleness` (WatchOp's phase) is read-only: it reports absent and
12
12
  * differing receipts as findings and never writes.
13
13
  */
14
- import { describe, test, expect } from "vitest";
14
+ import { describe, test, expect, vi } from "vitest";
15
15
  import { EffectReceipt, receiptExpectation, EXISTENCE_EXPECTATION } from "../effect-receipt";
16
16
  import { INTRINSIC_MARKER, type Intrinsic } from "../intrinsic";
17
17
  import { effect, phase } from "./builders";
@@ -23,7 +23,8 @@ import {
23
23
  } from "./receipt-store";
24
24
  import type { ActivityFn, ActivityProfile } from "./activity-registry";
25
25
  import type { OpConfig, ActivityStep } from "./types";
26
- import { runOpLocally, findGate, LocalGateUnsupportedError, OpRunFailure } from "./local-executor";
26
+ import { runOpLocally, OpRunFailure } from "./local-executor";
27
+ import { memoryGateLedgerPort } from "./gate";
27
28
 
28
29
  function fakeIntrinsic(json: unknown): Intrinsic {
29
30
  return {
@@ -57,7 +58,7 @@ describe("effect() builder", () => {
57
58
  inputs: { file: "seed.sql", version: 3 },
58
59
  });
59
60
  expect(s.expectation).toBe(receiptExpectation(seeded));
60
- expect(s.steps.map((n) => (n.kind === "activity" ? n.fn : n.signalName))).toEqual(["shellCmd"]);
61
+ expect(s.steps.map((n) => (n.kind === "activity" ? n.fn : n.gate))).toEqual(["shellCmd"]);
61
62
  });
62
63
 
63
64
  test("existence receipts always get the constant expectation, reference inputs or not", () => {
@@ -82,7 +83,7 @@ describe("effect() builder", () => {
82
83
 
83
84
  test("preserves authored order and nested gates", () => {
84
85
  const s = effect(seeded, [
85
- { kind: "gate", signalName: "approve-seed" },
86
+ { kind: "gate", gate: "approve-seed" },
86
87
  step("shellCmd", { cmd: "seed" }),
87
88
  ]);
88
89
  expect(s.steps.map((n) => n.kind)).toEqual(["gate", "activity"]);
@@ -187,7 +188,7 @@ describe("receiptActivities (the injectable store seam)", () => {
187
188
  // ── Local executor: read-compare-run-write ────────────────────────────────────
188
189
 
189
190
  const PROFILES: Record<string, ActivityProfile> = {
190
- fastIdempotent: { startToCloseTimeout: "5m", retry: { maximumAttempts: 1 } },
191
+ fastIdempotent: { timeout: "5m", retry: { maximumAttempts: 1 } },
191
192
  };
192
193
 
193
194
  function activityMap(
@@ -216,7 +217,7 @@ describe("runOpLocally — effect steps", () => {
216
217
  const ran: string[] = [];
217
218
  const activities = activityMap(store, { runSeed: async () => void ran.push("runSeed") });
218
219
  const result = await runOpLocally(seedOp(), activities, PROFILES);
219
- expect(result.ok).toBe(true);
220
+ expect(result.status).toBe("ok");
220
221
  expect(ran).toEqual([]);
221
222
  expect(writes).toEqual([]);
222
223
  // The read records the effect as already applied; the nested step is skipped.
@@ -235,7 +236,7 @@ describe("runOpLocally — effect steps", () => {
235
236
  { runSeed: async () => void order.push("runSeed") },
236
237
  );
237
238
  const result = await runOpLocally(seedOp(), activities, PROFILES);
238
- expect(result.ok).toBe(true);
239
+ expect(result.status).toBe("ok");
239
240
  expect(order).toEqual(["runSeed", "write"]);
240
241
  expect(writes).toEqual([{ name: "seeded", expectation: receiptExpectation(seeded) }]);
241
242
  expect(result.records.map((r) => [r.fn, r.status])).toEqual([
@@ -262,7 +263,7 @@ describe("runOpLocally — effect steps", () => {
262
263
  // then writes.
263
264
  healthy = true;
264
265
  const rerun = await runOpLocally(seedOp(), activities, PROFILES);
265
- expect(rerun.ok).toBe(true);
266
+ expect(rerun.status).toBe("ok");
266
267
  expect(writes).toEqual([{ name: "seeded", expectation: receiptExpectation(seeded) }]);
267
268
  });
268
269
 
@@ -282,19 +283,33 @@ describe("runOpLocally — effect steps", () => {
282
283
  ]);
283
284
  });
284
285
 
285
- test("a gate nested in an effect step is rejected up front, like any other gate", () => {
286
+ test("a gate nested in an effect step stops before the receipt is written (#2119)", async () => {
286
287
  const config: OpConfig = {
287
288
  name: "gated",
288
289
  overview: "",
289
290
  phases: [
290
- phase("Seed", [effect(seeded, [{ kind: "gate", signalName: "approve-seed" }, step("runSeed")])]),
291
+ phase("Seed", [effect(seeded, [{ kind: "gate", gate: "approve-seed" }, step("runSeed")])]),
291
292
  ],
292
293
  };
293
- expect(findGate(config)?.signalName).toBe("approve-seed");
294
294
  const { store } = memStore();
295
- return expect(
296
- runOpLocally(config, activityMap(store, { runSeed: async () => {} }), PROFILES),
297
- ).rejects.toThrow(LocalGateUnsupportedError);
295
+ const ran = vi.fn();
296
+ const result = await runOpLocally(
297
+ config,
298
+ activityMap(store, { runSeed: async () => { ran(); } }),
299
+ PROFILES,
300
+ undefined,
301
+ { gates: memoryGateLedgerPort(), now: "2026-09-05T12:00:00.000Z" },
302
+ );
303
+ expect(result.status).toBe("gated");
304
+ expect(result.gate?.gate).toBe("approve-seed");
305
+ expect(ran).not.toHaveBeenCalled();
306
+ // The receipt is untouched, so the next run re-proposes the effect.
307
+ expect(result.records.map((r) => [r.fn, r.status])).toEqual([
308
+ ["receiptRead", "ok"],
309
+ ["gate:approve-seed", "skipped"],
310
+ ["runSeed", "skipped"],
311
+ ["receiptWrite", "skipped"],
312
+ ]);
298
313
  });
299
314
 
300
315
  test("an effect step in a parallel phase is refused — read-compare-run-write is ordered", async () => {
@@ -1,6 +1,6 @@
1
1
  import { exec } from "node:child_process";
2
2
  import { promisify } from "node:util";
3
- import { safeHeartbeat, sleep } from "./activity-runtime";
3
+ import { sleep } from "./activity-runtime";
4
4
 
5
5
  const execAsync = promisify(exec);
6
6
 
@@ -158,7 +158,6 @@ export function emulatorLifecycle(spec: EmulatorSpec): EmulatorLifecycle {
158
158
  let ok = false;
159
159
  while (Date.now() < deadline) {
160
160
  if (signal?.aborted) throw new Error(`emulator "${name}" wait aborted`);
161
- safeHeartbeat({ step: "emulatorUp", container: name });
162
161
  try {
163
162
  const res = await fetch(url, { signal });
164
163
  if (res.ok && ready(await res.text())) {
@@ -0,0 +1,65 @@
1
+ /**
2
+ * The `signalName` -> `gate` rename (#2202) and the one-version bridge that
3
+ * keeps the old key readable through 0.59.0.
4
+ */
5
+ import { describe, it, expect } from "vitest";
6
+ import { gateName, usesDeprecatedGateKey, opUsesDeprecatedGateKey } from "./gate-name";
7
+ import type { OpConfig } from "./types";
8
+
9
+ describe("gateName", () => {
10
+ it("reads the new key", () => {
11
+ expect(gateName({ gate: "approve-prod" })).toBe("approve-prod");
12
+ });
13
+
14
+ it("falls back to the deprecated key", () => {
15
+ expect(gateName({ signalName: "approve-prod" })).toBe("approve-prod");
16
+ });
17
+
18
+ it("prefers the new key when a step somehow carries both", () => {
19
+ expect(gateName({ gate: "new", signalName: "old" })).toBe("new");
20
+ });
21
+ });
22
+
23
+ describe("usesDeprecatedGateKey", () => {
24
+ it("is true only when the new key is absent", () => {
25
+ expect(usesDeprecatedGateKey({ signalName: "old" })).toBe(true);
26
+ expect(usesDeprecatedGateKey({ gate: "new" })).toBe(false);
27
+ expect(usesDeprecatedGateKey({ gate: "new", signalName: "old" })).toBe(false);
28
+ expect(usesDeprecatedGateKey({})).toBe(false);
29
+ });
30
+ });
31
+
32
+ describe("opUsesDeprecatedGateKey", () => {
33
+ const op = (phases: OpConfig["phases"], onFailure?: OpConfig["phases"]): OpConfig =>
34
+ ({ name: "x", overview: "x", phases, ...(onFailure ? { onFailure } : {}) }) as OpConfig;
35
+
36
+ it("finds a gate on the old key in a plain phase", () => {
37
+ expect(opUsesDeprecatedGateKey(op([{ name: "Approve", steps: [{ kind: "gate", signalName: "go" }] }]))).toBe(true);
38
+ });
39
+
40
+ it("finds one nested inside an effect step", () => {
41
+ const config = op([
42
+ {
43
+ name: "Seed",
44
+ steps: [
45
+ {
46
+ kind: "effect",
47
+ receipt: { name: "seeded", effect: "db-seed", flavor: "hash", inputs: { file: "seed.sql" } },
48
+ steps: [{ kind: "gate", signalName: "go" }],
49
+ },
50
+ ],
51
+ },
52
+ ]);
53
+ expect(opUsesDeprecatedGateKey(config)).toBe(true);
54
+ });
55
+
56
+ it("finds one in an onFailure phase", () => {
57
+ expect(
58
+ opUsesDeprecatedGateKey(op([{ name: "Apply", steps: [] }], [{ name: "Undo", steps: [{ kind: "gate", signalName: "go" }] }])),
59
+ ).toBe(true);
60
+ });
61
+
62
+ it("is false for an Op on the new key", () => {
63
+ expect(opUsesDeprecatedGateKey(op([{ name: "Approve", steps: [{ kind: "gate", gate: "go" }] }]))).toBe(false);
64
+ });
65
+ });