@intentius/chant 0.56.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 (515) 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/import.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.map +1 -1
  44. package/dist/cli/commands/onboard.d.ts +2 -0
  45. package/dist/cli/commands/onboard.d.ts.map +1 -1
  46. package/dist/cli/handlers/build.d.ts.map +1 -1
  47. package/dist/cli/handlers/lifecycle.d.ts.map +1 -1
  48. package/dist/cli/handlers/operator.d.ts +82 -16
  49. package/dist/cli/handlers/operator.d.ts.map +1 -1
  50. package/dist/cli/handlers/run.d.ts +49 -42
  51. package/dist/cli/handlers/run.d.ts.map +1 -1
  52. package/dist/cli/main.d.ts +28 -1
  53. package/dist/cli/main.d.ts.map +1 -1
  54. package/dist/cli/mcp/op-tools.d.ts +16 -1
  55. package/dist/cli/mcp/op-tools.d.ts.map +1 -1
  56. package/dist/cli/mcp/resource-handlers.d.ts.map +1 -1
  57. package/dist/cli/mcp/resources/context.d.ts.map +1 -1
  58. package/dist/cli/registry.d.ts +26 -9
  59. package/dist/cli/registry.d.ts.map +1 -1
  60. package/dist/codegen/generate-post-synth-barrel.d.ts +1 -1
  61. package/dist/components/auto-release.d.ts +10 -20
  62. package/dist/components/auto-release.d.ts.map +1 -1
  63. package/dist/components/capability.d.ts +4 -4
  64. package/dist/components/cli-support.d.ts +20 -21
  65. package/dist/components/cli-support.d.ts.map +1 -1
  66. package/dist/components/component.d.ts +3 -3
  67. package/dist/components/component.d.ts.map +1 -1
  68. package/dist/components/driver-output.d.ts.map +1 -1
  69. package/dist/components/driver.d.ts +66 -36
  70. package/dist/components/driver.d.ts.map +1 -1
  71. package/dist/components/index.d.ts +1 -2
  72. package/dist/components/index.d.ts.map +1 -1
  73. package/dist/components/manifest-persistence.d.ts +1 -9
  74. package/dist/components/manifest-persistence.d.ts.map +1 -1
  75. package/dist/components/run-progress.d.ts +6 -6
  76. package/dist/components/run-progress.d.ts.map +1 -1
  77. package/dist/components/verbs/run-agent.d.ts +9 -15
  78. package/dist/components/verbs/run-agent.d.ts.map +1 -1
  79. package/dist/components/verbs/verify.d.ts +2 -2
  80. package/dist/deep-observation.d.ts +17 -0
  81. package/dist/deep-observation.d.ts.map +1 -1
  82. package/dist/discovery/files.d.ts +4 -4
  83. package/dist/discovery/fold-import.d.ts.map +1 -1
  84. package/dist/discovery/param-deps.d.ts +33 -0
  85. package/dist/discovery/param-deps.d.ts.map +1 -1
  86. package/dist/discovery/sandbox/bundle.d.ts.map +1 -1
  87. package/dist/discovery/sandbox/config-wire.d.ts +2 -2
  88. package/dist/fold/foldable-helpers.d.ts +5 -1
  89. package/dist/fold/foldable-helpers.d.ts.map +1 -1
  90. package/dist/fold-provenance.d.ts +236 -0
  91. package/dist/fold-provenance.d.ts.map +1 -0
  92. package/dist/graph-ops.d.ts +1 -1
  93. package/dist/held-elsewhere.d.ts +85 -0
  94. package/dist/held-elsewhere.d.ts.map +1 -0
  95. package/dist/import/ir-utils.d.ts +1 -1
  96. package/dist/import/ir-utils.d.ts.map +1 -1
  97. package/dist/import/parser.d.ts +33 -0
  98. package/dist/import/parser.d.ts.map +1 -1
  99. package/dist/index.d.ts +3 -0
  100. package/dist/index.d.ts.map +1 -1
  101. package/dist/kubectl-context.d.ts +3 -3
  102. package/dist/lexicon-config.d.ts +4 -4
  103. package/dist/lexicon-plugin-helpers.d.ts +1 -1
  104. package/dist/lexicon.d.ts +104 -14
  105. package/dist/lexicon.d.ts.map +1 -1
  106. package/dist/lifecycle/change-set.d.ts +13 -0
  107. package/dist/lifecycle/change-set.d.ts.map +1 -1
  108. package/dist/lifecycle/deep-diff.d.ts +133 -6
  109. package/dist/lifecycle/deep-diff.d.ts.map +1 -1
  110. package/dist/lifecycle/deep-observe.d.ts.map +1 -1
  111. package/dist/lifecycle/gate-ledger.d.ts +71 -3
  112. package/dist/lifecycle/gate-ledger.d.ts.map +1 -1
  113. package/dist/lifecycle/index.d.ts +1 -0
  114. package/dist/lifecycle/index.d.ts.map +1 -1
  115. package/dist/lifecycle/release-ledger.d.ts +2 -2
  116. package/dist/lifecycle/run-ledger.d.ts +53 -0
  117. package/dist/lifecycle/run-ledger.d.ts.map +1 -0
  118. package/dist/lifecycle/symptoms.d.ts +3 -3
  119. package/dist/lifecycle/symptoms.d.ts.map +1 -1
  120. package/dist/lint/config.d.ts +81 -2
  121. package/dist/lint/config.d.ts.map +1 -1
  122. package/dist/lint/post-synth.d.ts +55 -2
  123. package/dist/lint/post-synth.d.ts.map +1 -1
  124. package/dist/lint/rules/__fixtures__/comp/comp004/pass/neo4j-cluster.component.d.ts +1 -1
  125. package/dist/lint/rules/__fixtures__/comp/comp004/pass/neo4j-cluster.component.d.ts.map +1 -1
  126. package/dist/lint/rules/__fixtures__/comp/comp004/pass/orders-table.component.d.ts +1 -1
  127. package/dist/lint/rules/__fixtures__/comp/comp004/pass/orders-table.component.d.ts.map +1 -1
  128. package/dist/lint/rules/comp/comp004-gate-needs-durable-runtime.d.ts +44 -0
  129. package/dist/lint/rules/comp/comp004-gate-needs-durable-runtime.d.ts.map +1 -0
  130. package/dist/lint/rules/comp/index.d.ts +1 -1
  131. package/dist/lint/rules/comp/index.d.ts.map +1 -1
  132. package/dist/lint/rules/op/index.d.ts +16 -0
  133. package/dist/lint/rules/op/index.d.ts.map +1 -0
  134. package/dist/lint/rules/op/ops012-activity-contract.d.ts +27 -0
  135. package/dist/lint/rules/op/ops012-activity-contract.d.ts.map +1 -0
  136. package/dist/lint/rules/op/ops013-step-output-ref.d.ts +61 -0
  137. package/dist/lint/rules/op/ops013-step-output-ref.d.ts.map +1 -0
  138. package/dist/lint/rules/op/ops014-converge-rule-refusals.d.ts +55 -0
  139. package/dist/lint/rules/op/ops014-converge-rule-refusals.d.ts.map +1 -0
  140. package/dist/lint/rules/op/support.d.ts +12 -0
  141. package/dist/lint/rules/op/support.d.ts.map +1 -0
  142. package/dist/lint/suppressions.d.ts +108 -0
  143. package/dist/lint/suppressions.d.ts.map +1 -0
  144. package/dist/live-endpoint.d.ts +1 -1
  145. package/dist/op/activities/activity-contracts.d.ts +54 -0
  146. package/dist/op/activities/activity-contracts.d.ts.map +1 -0
  147. package/dist/op/activities/apply.d.ts +262 -0
  148. package/dist/op/activities/apply.d.ts.map +1 -0
  149. package/dist/op/activities/build.d.ts +13 -0
  150. package/dist/op/activities/build.d.ts.map +1 -0
  151. package/dist/op/activities/converge.d.ts +110 -0
  152. package/dist/op/activities/converge.d.ts.map +1 -0
  153. package/dist/op/activities/env-teardown.d.ts +76 -0
  154. package/dist/op/activities/env-teardown.d.ts.map +1 -0
  155. package/dist/op/activities/guard-validate.d.ts +64 -0
  156. package/dist/op/activities/guard-validate.d.ts.map +1 -0
  157. package/dist/op/activities/http-check.d.ts +34 -0
  158. package/dist/op/activities/http-check.d.ts.map +1 -0
  159. package/dist/op/activities/index.d.ts +31 -0
  160. package/dist/op/activities/index.d.ts.map +1 -0
  161. package/dist/op/activities/lexicon-upgrade.d.ts +182 -0
  162. package/dist/op/activities/lexicon-upgrade.d.ts.map +1 -0
  163. package/dist/op/activities/lifecycle.d.ts +42 -0
  164. package/dist/op/activities/lifecycle.d.ts.map +1 -0
  165. package/dist/op/activities/pipeline-audit.d.ts +84 -0
  166. package/dist/op/activities/pipeline-audit.d.ts.map +1 -0
  167. package/dist/op/activities/policy.d.ts +17 -0
  168. package/dist/op/activities/policy.d.ts.map +1 -0
  169. package/dist/op/activities/reconcile.d.ts +89 -0
  170. package/dist/op/activities/reconcile.d.ts.map +1 -0
  171. package/dist/op/activities/shell.d.ts +13 -0
  172. package/dist/op/activities/shell.d.ts.map +1 -0
  173. package/dist/op/activities/teardown.d.ts +10 -0
  174. package/dist/op/activities/teardown.d.ts.map +1 -0
  175. package/dist/op/activities/util.d.ts +7 -0
  176. package/dist/op/activities/util.d.ts.map +1 -0
  177. package/dist/op/activities/wait.d.ts +28 -0
  178. package/dist/op/activities/wait.d.ts.map +1 -0
  179. package/dist/op/activities/workflow-audit.d.ts +92 -0
  180. package/dist/op/activities/workflow-audit.d.ts.map +1 -0
  181. package/dist/op/activity-contract-registry.d.ts +76 -0
  182. package/dist/op/activity-contract-registry.d.ts.map +1 -0
  183. package/dist/op/activity-contract.d.ts +25 -18
  184. package/dist/op/activity-contract.d.ts.map +1 -1
  185. package/dist/op/activity-failure.d.ts +28 -0
  186. package/dist/op/activity-failure.d.ts.map +1 -0
  187. package/dist/op/activity-profiles.d.ts +118 -0
  188. package/dist/op/activity-profiles.d.ts.map +1 -0
  189. package/dist/op/activity-registry.d.ts +22 -38
  190. package/dist/op/activity-registry.d.ts.map +1 -1
  191. package/dist/op/activity-runtime.d.ts +3 -14
  192. package/dist/op/activity-runtime.d.ts.map +1 -1
  193. package/dist/op/builders.d.ts +84 -29
  194. package/dist/op/builders.d.ts.map +1 -1
  195. package/dist/op/composites/apply-op.d.ts +113 -0
  196. package/dist/op/composites/apply-op.d.ts.map +1 -0
  197. package/dist/op/composites/converge-op.d.ts +120 -0
  198. package/dist/op/composites/converge-op.d.ts.map +1 -0
  199. package/dist/op/composites/index.d.ts +26 -0
  200. package/dist/op/composites/index.d.ts.map +1 -0
  201. package/dist/op/composites/lexicon-upgrade-op.d.ts +78 -0
  202. package/dist/op/composites/lexicon-upgrade-op.d.ts.map +1 -0
  203. package/dist/op/composites/pipeline-audit-op.d.ts +50 -0
  204. package/dist/op/composites/pipeline-audit-op.d.ts.map +1 -0
  205. package/dist/op/composites/reconcile-op.d.ts +57 -0
  206. package/dist/op/composites/reconcile-op.d.ts.map +1 -0
  207. package/dist/op/composites/watch-op.d.ts +60 -0
  208. package/dist/op/composites/watch-op.d.ts.map +1 -0
  209. package/dist/op/composites/workflow-audit-op.d.ts +57 -0
  210. package/dist/op/composites/workflow-audit-op.d.ts.map +1 -0
  211. package/dist/op/converge-rule.d.ts +12 -10
  212. package/dist/op/converge-rule.d.ts.map +1 -1
  213. package/dist/op/cron.d.ts +51 -0
  214. package/dist/op/cron.d.ts.map +1 -0
  215. package/dist/op/discover.d.ts +9 -0
  216. package/dist/op/discover.d.ts.map +1 -1
  217. package/dist/op/duration.d.ts +11 -0
  218. package/dist/op/duration.d.ts.map +1 -0
  219. package/dist/op/emulator-lifecycle.d.ts.map +1 -1
  220. package/dist/op/gate.d.ts +90 -0
  221. package/dist/op/gate.d.ts.map +1 -0
  222. package/dist/op/generate-pipeline.d.ts +9 -0
  223. package/dist/op/generate-pipeline.d.ts.map +1 -1
  224. package/dist/op/index.d.ts +23 -7
  225. package/dist/op/index.d.ts.map +1 -1
  226. package/dist/op/local-executor.d.ts +114 -28
  227. package/dist/op/local-executor.d.ts.map +1 -1
  228. package/dist/op/local-output.d.ts +11 -1
  229. package/dist/op/local-output.d.ts.map +1 -1
  230. package/dist/op/op-ir.d.ts +184 -0
  231. package/dist/op/op-ir.d.ts.map +1 -0
  232. package/dist/op/op-verb-class.d.ts +10 -2
  233. package/dist/op/op-verb-class.d.ts.map +1 -1
  234. package/dist/op/operator.d.ts +30 -5
  235. package/dist/op/operator.d.ts.map +1 -1
  236. package/dist/op/receipt-store.d.ts +1 -1
  237. package/dist/op/receipt-store.d.ts.map +1 -1
  238. package/dist/op/resource.d.ts +15 -1
  239. package/dist/op/resource.d.ts.map +1 -1
  240. package/dist/op/runtime.d.ts +200 -0
  241. package/dist/op/runtime.d.ts.map +1 -0
  242. package/dist/op/runtimes/local.d.ts +29 -0
  243. package/dist/op/runtimes/local.d.ts.map +1 -0
  244. package/dist/op/step-output-ref.d.ts +15 -13
  245. package/dist/op/step-output-ref.d.ts.map +1 -1
  246. package/dist/op/types.d.ts +79 -27
  247. package/dist/op/types.d.ts.map +1 -1
  248. package/dist/ownership.d.ts +1 -1
  249. package/dist/provenance.d.ts +21 -0
  250. package/dist/provenance.d.ts.map +1 -1
  251. package/dist/serializer-walker.d.ts.map +1 -1
  252. package/dist/stack-output.d.ts +5 -0
  253. package/dist/stack-output.d.ts.map +1 -1
  254. package/package.json +6 -1
  255. package/src/audit/catalog.test.ts +79 -3
  256. package/src/audit/catalog.ts +111 -13
  257. package/src/audit/core.test.ts +26 -0
  258. package/src/audit/core.ts +61 -17
  259. package/src/audit/discover.test.ts +39 -0
  260. package/src/audit/discover.ts +91 -15
  261. package/src/audit/lineage.ts +100 -0
  262. package/src/audit/prior-art.ts +153 -0
  263. package/src/audit/proof.test.ts +67 -0
  264. package/src/audit/proof.ts +70 -0
  265. package/src/audit/report-html.ts +9 -4
  266. package/src/audit/report-model.ts +16 -2
  267. package/src/audit/report.test.ts +33 -0
  268. package/src/audit/report.ts +5 -3
  269. package/src/audit/rules-doc.test.ts +34 -2
  270. package/src/audit/rules-doc.ts +82 -9
  271. package/src/audit/secrets.ts +15 -3
  272. package/src/audit/terraform-state.test.ts +144 -0
  273. package/src/audit/terraform-state.ts +131 -0
  274. package/src/build.ts +31 -1
  275. package/src/claimed-fields.test.ts +145 -0
  276. package/src/claimed-fields.ts +208 -0
  277. package/src/cli/build-params-cli.ts +2 -2
  278. package/src/cli/command-group.ts +14 -0
  279. package/src/cli/commands/__fixtures__/audit-terraform/infra/.terraform/providers/lock.txt +1 -0
  280. package/src/cli/commands/__fixtures__/audit-terraform/infra/main.tf +1 -0
  281. package/src/cli/commands/__fixtures__/audit-terraform/infra/modules/foo/main.tf +1 -0
  282. package/src/cli/commands/__fixtures__/audit-terraform/infra/variables.tf +3 -0
  283. package/src/cli/commands/__fixtures__/init-lexicon-output/package.json +13 -4
  284. package/src/cli/commands/__fixtures__/init-lexicon-output/src/codegen/docs-cli.ts +4 -0
  285. package/src/cli/commands/__fixtures__/init-lexicon-output/src/codegen/package.ts +1 -0
  286. package/src/cli/commands/__fixtures__/init-lexicon-output/src/package-cli.ts +21 -0
  287. package/src/cli/commands/__snapshots__/init-lexicon.test.ts.snap +1 -0
  288. package/src/cli/commands/audit.test.ts +189 -11
  289. package/src/cli/commands/audit.ts +73 -27
  290. package/src/cli/commands/build.test.ts +292 -9
  291. package/src/cli/commands/build.ts +156 -24
  292. package/src/cli/commands/check-lexicon-docs.ts +1 -1
  293. package/src/cli/commands/check-lexicon-examples.test.ts +29 -1
  294. package/src/cli/commands/check-lexicon-examples.ts +42 -10
  295. package/src/cli/commands/import.ts +6 -0
  296. package/src/cli/commands/init-lexicon/templates/codegen.ts +9 -0
  297. package/src/cli/commands/init-lexicon/templates/project.ts +44 -4
  298. package/src/cli/commands/init-lexicon.test.ts +97 -2
  299. package/src/cli/commands/init-lexicon.ts +4 -2
  300. package/src/cli/commands/init.test.ts +35 -1
  301. package/src/cli/commands/init.ts +14 -3
  302. package/src/cli/commands/lint.test.ts +48 -0
  303. package/src/cli/commands/lint.ts +123 -0
  304. package/src/cli/commands/onboard.test.ts +75 -0
  305. package/src/cli/commands/onboard.ts +54 -23
  306. package/src/cli/handlers/build.test.ts +81 -1
  307. package/src/cli/handlers/build.ts +15 -1
  308. package/src/cli/handlers/graph.test.ts +3 -3
  309. package/src/cli/handlers/lifecycle.test.ts +195 -12
  310. package/src/cli/handlers/lifecycle.ts +123 -6
  311. package/src/cli/handlers/misc.ts +1 -1
  312. package/src/cli/handlers/operator.test.ts +183 -11
  313. package/src/cli/handlers/operator.ts +260 -55
  314. package/src/cli/handlers/run.test.ts +431 -1287
  315. package/src/cli/handlers/run.ts +365 -915
  316. package/src/cli/main.test.ts +23 -15
  317. package/src/cli/main.ts +88 -33
  318. package/src/cli/mcp/op-tools.ts +147 -88
  319. package/src/cli/mcp/resource-handlers.ts +10 -37
  320. package/src/cli/mcp/resources/context.ts +11 -12
  321. package/src/cli/mcp/server.test.ts +25 -27
  322. package/src/cli/mcp/server.ts +2 -2
  323. package/src/cli/registry.ts +26 -9
  324. package/src/codegen/docs.ts +1 -1
  325. package/src/codegen/generate-post-synth-barrel.test.ts +1 -1
  326. package/src/codegen/generate-post-synth-barrel.ts +1 -1
  327. package/src/components/README.md +3 -2
  328. package/src/components/auto-release.test.ts +4 -20
  329. package/src/components/auto-release.ts +17 -42
  330. package/src/components/capability.ts +4 -4
  331. package/src/components/cli-support.test.ts +10 -5
  332. package/src/components/cli-support.ts +52 -44
  333. package/src/components/component.schema.json +2 -2
  334. package/src/components/component.ts +3 -3
  335. package/src/components/driver-output.ts +14 -2
  336. package/src/components/driver.test.ts +92 -22
  337. package/src/components/driver.ts +193 -83
  338. package/src/components/index.ts +1 -6
  339. package/src/components/manifest-persistence.test.ts +2 -20
  340. package/src/components/manifest-persistence.ts +4 -24
  341. package/src/components/pilots/pilots-lint-clean.test.ts +11 -10
  342. package/src/components/run-progress.ts +6 -6
  343. package/src/components/verbs/run-agent.ts +11 -17
  344. package/src/components/verbs/verify.ts +2 -2
  345. package/src/deep-observation.test.ts +75 -0
  346. package/src/deep-observation.ts +48 -0
  347. package/src/discovery/collect.test.ts +3 -3
  348. package/src/discovery/files.ts +4 -4
  349. package/src/discovery/fold-import.ts +105 -2
  350. package/src/discovery/param-deps.test.ts +112 -1
  351. package/src/discovery/param-deps.ts +193 -0
  352. package/src/discovery/sandbox/bundle.ts +15 -1
  353. package/src/discovery/sandbox/config-wire.test.ts +2 -2
  354. package/src/discovery/sandbox/config-wire.ts +2 -2
  355. package/src/fold/fold.test.ts +56 -0
  356. package/src/fold/foldable-helpers.ts +81 -1
  357. package/src/fold-provenance-exclusion.test.ts +186 -0
  358. package/src/fold-provenance.test.ts +495 -0
  359. package/src/fold-provenance.ts +441 -0
  360. package/src/graph-ops.test.ts +2 -2
  361. package/src/graph-ops.ts +1 -1
  362. package/src/held-elsewhere.test.ts +102 -0
  363. package/src/held-elsewhere.ts +114 -0
  364. package/src/import/ir-utils.ts +11 -1
  365. package/src/import/parser.ts +35 -0
  366. package/src/index.ts +3 -0
  367. package/src/kubectl-context.ts +3 -3
  368. package/src/lexicon-config.test.ts +5 -5
  369. package/src/lexicon-config.ts +4 -4
  370. package/src/lexicon-plugin-helpers.ts +1 -1
  371. package/src/lexicon.ts +120 -14
  372. package/src/lifecycle/change-set.test.ts +49 -0
  373. package/src/lifecycle/change-set.ts +67 -0
  374. package/src/lifecycle/converge-ledger.ts +1 -1
  375. package/src/lifecycle/deep-diff.test.ts +267 -14
  376. package/src/lifecycle/deep-diff.ts +217 -14
  377. package/src/lifecycle/deep-observe.test.ts +3 -1
  378. package/src/lifecycle/deep-observe.ts +16 -6
  379. package/src/lifecycle/gate-ledger.test.ts +74 -2
  380. package/src/lifecycle/gate-ledger.ts +143 -36
  381. package/src/lifecycle/index.ts +1 -0
  382. package/src/lifecycle/release-ledger.ts +2 -2
  383. package/src/lifecycle/run-ledger.test.ts +268 -0
  384. package/src/lifecycle/run-ledger.ts +201 -0
  385. package/src/lifecycle/symptoms.ts +3 -3
  386. package/src/lint/config.test.ts +82 -1
  387. package/src/lint/config.ts +112 -3
  388. package/src/lint/policy.test.ts +1 -1
  389. package/src/lint/post-synth.test.ts +31 -0
  390. package/src/lint/post-synth.ts +54 -1
  391. package/src/lint/rules/__fixtures__/comp/comp004/pass/neo4j-cluster.component.ts +2 -2
  392. package/src/lint/rules/__fixtures__/comp/comp004/pass/orders-table.component.ts +1 -1
  393. package/src/lint/rules/comp/comp.test.ts +1 -1
  394. package/src/lint/rules/comp/comp004-gate-needs-durable-runtime.ts +74 -0
  395. package/src/lint/rules/comp/index.ts +3 -3
  396. package/src/lint/rules/op/index.ts +23 -0
  397. package/src/lint/rules/op/ops012-activity-contract.test.ts +161 -0
  398. package/src/lint/rules/op/ops012-activity-contract.ts +64 -0
  399. package/src/lint/rules/op/ops013-step-output-ref.test.ts +237 -0
  400. package/src/lint/rules/op/ops013-step-output-ref.ts +98 -0
  401. package/src/lint/rules/op/ops014-converge-rule-refusals.test.ts +263 -0
  402. package/src/lint/rules/op/ops014-converge-rule-refusals.ts +179 -0
  403. package/src/lint/rules/op/support.ts +11 -0
  404. package/src/lint/suppressions.test.ts +163 -0
  405. package/src/lint/suppressions.ts +204 -0
  406. package/src/live-endpoint.ts +1 -1
  407. package/src/op/activities/activity-contracts.ts +66 -0
  408. package/src/op/activities/apply.test.ts +567 -0
  409. package/src/op/activities/apply.ts +662 -0
  410. package/src/op/activities/build.ts +26 -0
  411. package/src/op/activities/converge.test.ts +330 -0
  412. package/src/op/activities/converge.ts +449 -0
  413. package/src/op/activities/env-teardown.test.ts +187 -0
  414. package/src/op/activities/env-teardown.ts +171 -0
  415. package/src/op/activities/guard-validate.test.ts +139 -0
  416. package/src/op/activities/guard-validate.ts +223 -0
  417. package/src/op/activities/http-check.test.ts +48 -0
  418. package/src/op/activities/http-check.ts +55 -0
  419. package/src/op/activities/index.ts +119 -0
  420. package/src/op/activities/lexicon-upgrade.test.ts +492 -0
  421. package/src/op/activities/lexicon-upgrade.ts +673 -0
  422. package/src/op/activities/lifecycle.ts +85 -0
  423. package/src/op/activities/pipeline-audit.test.ts +67 -0
  424. package/src/op/activities/pipeline-audit.ts +201 -0
  425. package/src/op/activities/policy.test.ts +19 -0
  426. package/src/op/activities/policy.ts +37 -0
  427. package/src/op/activities/reconcile.test.ts +81 -0
  428. package/src/op/activities/reconcile.ts +187 -0
  429. package/src/op/activities/shell.ts +26 -0
  430. package/src/op/activities/teardown.ts +22 -0
  431. package/src/op/activities/util.ts +6 -0
  432. package/src/op/activities/wait.ts +69 -0
  433. package/src/op/activities/workflow-audit.test.ts +90 -0
  434. package/src/op/activities/workflow-audit.ts +203 -0
  435. package/src/op/activity-contract-registry.test.ts +98 -0
  436. package/src/op/activity-contract-registry.ts +114 -0
  437. package/src/op/activity-contract.test.ts +1 -1
  438. package/src/op/activity-contract.ts +37 -33
  439. package/src/op/activity-failure.ts +34 -0
  440. package/src/op/activity-profiles.test.ts +56 -0
  441. package/src/op/activity-profiles.ts +116 -0
  442. package/src/op/activity-registry.test.ts +37 -28
  443. package/src/op/activity-registry.ts +27 -56
  444. package/src/op/activity-runtime.ts +3 -47
  445. package/src/op/builders-exports.test.ts +0 -49
  446. package/src/op/builders-typed.test.ts +164 -0
  447. package/src/op/builders.ts +123 -39
  448. package/src/op/composites/apply-op.ts +208 -0
  449. package/src/op/composites/composites.test.ts +366 -0
  450. package/src/op/composites/converge-op.test.ts +168 -0
  451. package/src/op/composites/converge-op.ts +214 -0
  452. package/src/op/composites/index.ts +26 -0
  453. package/src/op/composites/lexicon-upgrade-op.test.ts +68 -0
  454. package/src/op/composites/lexicon-upgrade-op.ts +134 -0
  455. package/src/op/composites/pipeline-audit-op.test.ts +25 -0
  456. package/src/op/composites/pipeline-audit-op.ts +78 -0
  457. package/src/op/composites/reconcile-op.test.ts +51 -0
  458. package/src/op/composites/reconcile-op.ts +107 -0
  459. package/src/op/composites/watch-op.ts +108 -0
  460. package/src/op/composites/workflow-audit-op.test.ts +25 -0
  461. package/src/op/composites/workflow-audit-op.ts +88 -0
  462. package/src/op/converge-rule.ts +12 -10
  463. package/src/op/cron.test.ts +128 -0
  464. package/src/op/cron.ts +149 -0
  465. package/src/op/discover-root.test.ts +105 -1
  466. package/src/op/discover.test.ts +6 -2
  467. package/src/op/discover.ts +84 -16
  468. package/src/op/duration.ts +21 -0
  469. package/src/op/effect-step.test.ts +26 -11
  470. package/src/op/emulator-lifecycle.ts +1 -2
  471. package/src/op/gate.ts +155 -0
  472. package/src/op/generate-pipeline.test.ts +65 -3
  473. package/src/op/generate-pipeline.ts +19 -2
  474. package/src/op/index.ts +39 -6
  475. package/src/op/local-executor.test.ts +163 -34
  476. package/src/op/local-executor.ts +370 -102
  477. package/src/op/local-output.test.ts +95 -11
  478. package/src/op/local-output.ts +33 -4
  479. package/src/op/op-ir.test.ts +281 -0
  480. package/src/op/op-ir.ts +417 -0
  481. package/src/op/op-verb-class.ts +19 -5
  482. package/src/op/op.test.ts +26 -5
  483. package/src/op/operator.test.ts +175 -6
  484. package/src/op/operator.ts +63 -16
  485. package/src/op/receipt-store.ts +1 -1
  486. package/src/op/resource.ts +20 -2
  487. package/src/op/runtime.ts +203 -0
  488. package/src/op/runtimes/local.test.ts +181 -0
  489. package/src/op/runtimes/local.ts +212 -0
  490. package/src/op/step-output-ref.ts +15 -13
  491. package/src/op/types.ts +84 -24
  492. package/src/ownership.ts +1 -1
  493. package/src/provenance.test.ts +6 -0
  494. package/src/provenance.ts +21 -1
  495. package/src/serializer-walker.test.ts +42 -0
  496. package/src/serializer-walker.ts +21 -2
  497. package/src/stack-output.ts +6 -1
  498. package/dist/cli/handlers/op-progress.d.ts +0 -90
  499. package/dist/cli/handlers/op-progress.d.ts.map +0 -1
  500. package/dist/cli/handlers/run-client.d.ts +0 -127
  501. package/dist/cli/handlers/run-client.d.ts.map +0 -1
  502. package/dist/cli/handlers/run-report.d.ts +0 -5
  503. package/dist/cli/handlers/run-report.d.ts.map +0 -1
  504. package/dist/components/temporal-codegen-loader.d.ts +0 -27
  505. package/dist/components/temporal-codegen-loader.d.ts.map +0 -1
  506. package/dist/lint/rules/comp/comp004-gate-needs-temporal.d.ts +0 -37
  507. package/dist/lint/rules/comp/comp004-gate-needs-temporal.d.ts.map +0 -1
  508. package/src/cli/handlers/op-progress.test.ts +0 -268
  509. package/src/cli/handlers/op-progress.ts +0 -248
  510. package/src/cli/handlers/run-client.test.ts +0 -82
  511. package/src/cli/handlers/run-client.ts +0 -219
  512. package/src/cli/handlers/run-report.test.ts +0 -62
  513. package/src/cli/handlers/run-report.ts +0 -122
  514. package/src/components/temporal-codegen-loader.ts +0 -41
  515. package/src/lint/rules/comp/comp004-gate-needs-temporal.ts +0 -66
@@ -0,0 +1,366 @@
1
+ /**
2
+ * Composite unit tests (#2120) — WatchOp, ReconcileOp, ApplyOp and the
3
+ * receipt-staleness watch, moved here with the composites themselves. The
4
+ * serialization half of this suite stays in
5
+ * a hosting lexicon's own composites suite: that one asserts on its
6
+ * serializer's output, which is the lexicon's business.
7
+ */
8
+
9
+ import { describe, test, expect } from "vitest";
10
+ import { WatchOp } from "./watch-op";
11
+ import { ReconcileOp } from "./reconcile-op";
12
+ import { ApplyOp } from "./apply-op";
13
+ import { DECLARABLE_MARKER } from "../../declarable";
14
+ import { EffectReceipt, receiptExpectation } from "../../effect-receipt";
15
+
16
+ function getProps(entity: unknown): Record<string, unknown> {
17
+ return (entity as { props: Record<string, unknown> }).props;
18
+ }
19
+
20
+ function getEntityType(entity: unknown): string {
21
+ return (entity as Record<string, unknown>).entityType as string;
22
+ }
23
+
24
+ // ── WatchOp ──────────────────────────────────────────────────────────
25
+
26
+ describe("WatchOp: shape", () => {
27
+ test("returns { op } — the cadence is on the op, not a second resource (#2120)", () => {
28
+ const result = WatchOp({ name: "prod-watch", env: "prod", schedule: "*/15 * * * *" });
29
+ expect(result.op).toBeDefined();
30
+ expect(Object.keys(result)).toEqual(["op"]);
31
+ });
32
+
33
+ test("op has entityType Chant::Op", () => {
34
+ const { op } = WatchOp({ name: "prod-watch", env: "prod", schedule: "*/15 * * * *" });
35
+ expect(getEntityType(op)).toBe("Chant::Op");
36
+ });
37
+
38
+ test("the op is Declarable", () => {
39
+ const { op } = WatchOp({ name: "prod-watch", env: "prod", schedule: "*/15 * * * *" });
40
+ expect((op as unknown as Record<symbol, unknown>)[DECLARABLE_MARKER]).toBe(true);
41
+ });
42
+
43
+ test("op.schedule carries the cron with the only overlap policy there is", () => {
44
+ const { op } = WatchOp({ name: "prod-watch", env: "prod", schedule: "*/15 * * * *" });
45
+ expect(getProps(op).schedule).toEqual({ cron: "*/15 * * * *", overlap: "skip" });
46
+ });
47
+
48
+ test("no cron: a one-shot watch, no schedule on the op", () => {
49
+ const { op } = WatchOp({ name: "prod-watch", env: "prod" });
50
+ expect(getProps(op).schedule).toBeUndefined();
51
+ });
52
+
53
+ test("an invalid cron is refused at construction with cronSyntaxMessage's wording", () => {
54
+ expect(() => WatchOp({ name: "prod-watch", env: "prod", schedule: "every 15 minutes" })).toThrow(
55
+ /does not look like valid 5- or 6-field cron syntax/,
56
+ );
57
+ });
58
+ });
59
+
60
+ describe("WatchOp: configuration", () => {
61
+ test("op has Snapshot + Diff phases referencing the right activities", () => {
62
+ const { op } = WatchOp({ name: "p", env: "prod", schedule: "*/15 * * * *" });
63
+ const phases = (getProps(op).phases as Array<Record<string, unknown>>) ?? [];
64
+ expect(phases.map((p) => p.name)).toEqual(["Snapshot", "Diff"]);
65
+ const snapStep = (phases[0].steps as Array<Record<string, unknown>>)[0];
66
+ const diffStep = (phases[1].steps as Array<Record<string, unknown>>)[0];
67
+ expect(snapStep.fn).toBe("lifecycleSnapshot");
68
+ expect(diffStep.fn).toBe("lifecycleDiff");
69
+ expect(snapStep.args).toEqual({ env: "prod" });
70
+ expect(diffStep.args).toEqual({ env: "prod", live: true });
71
+ // Drift is surfaced as a run outcome via outcomeAttribute (#41)
72
+ expect(diffStep.outcomeAttribute).toEqual({ name: "Drift", from: "drifted" });
73
+ });
74
+
75
+ test("labels include Watch + Env", () => {
76
+ const { op } = WatchOp({ name: "p", env: "prod", schedule: "* * * * *" });
77
+ expect(getProps(op).labels).toEqual({ Watch: "true", Env: "prod" });
78
+ });
79
+
80
+ test("the configured cron reaches op.schedule verbatim", () => {
81
+ const { op } = WatchOp({ name: "p", env: "prod", schedule: "0 0 * * *" });
82
+ expect((getProps(op).schedule as Record<string, unknown>).cron).toBe("0 0 * * *");
83
+ });
84
+
85
+ test("live: false produces a digest-only diff step", () => {
86
+ const { op } = WatchOp({ name: "p", env: "prod", schedule: "* * * * *", live: false });
87
+ const phases = getProps(op).phases as Array<Record<string, unknown>>;
88
+ const diffStep = (phases[1].steps as Array<Record<string, unknown>>)[0];
89
+ expect(diffStep.args).toEqual({ env: "prod", live: false });
90
+ });
91
+ });
92
+
93
+ // ── ReconcileOp ──────────────────────────────────────────────────────
94
+
95
+ describe("ReconcileOp: shape", () => {
96
+ test("one-shot (no cron) returns an op with no cadence", () => {
97
+ const result = ReconcileOp({ name: "prod-reconcile", env: "prod" });
98
+ expect(result.op).toBeDefined();
99
+ expect(getProps(result.op).schedule).toBeUndefined();
100
+ expect(getEntityType(result.op)).toBe("Chant::Op");
101
+ expect((result.op as unknown as Record<symbol, unknown>)[DECLARABLE_MARKER]).toBe(true);
102
+ });
103
+
104
+ test("with a cron the op carries the schedule and nothing else is returned", () => {
105
+ const result = ReconcileOp({ name: "prod-reconcile", env: "prod", schedule: "0 * * * *" });
106
+ expect(Object.keys(result)).toEqual(["op"]);
107
+ expect(getProps(result.op).schedule).toEqual({ cron: "0 * * * *", overlap: "skip" });
108
+ });
109
+ });
110
+
111
+ describe("ReconcileOp: configuration", () => {
112
+ test("phases are Snapshot → Plan → Reconcile with the right activities", () => {
113
+ const { op } = ReconcileOp({ name: "p", env: "prod" });
114
+ const phases = (getProps(op).phases as Array<Record<string, unknown>>) ?? [];
115
+ expect(phases.map((p) => p.name)).toEqual(["Snapshot", "Plan", "Reconcile"]);
116
+ const reconcileStep = (phases[2].steps as Array<Record<string, unknown>>)[0];
117
+ expect(reconcileStep.fn).toBe("reconcilePr");
118
+ expect(reconcileStep.args).toEqual({ env: "prod", mode: "pull-request", owned: false });
119
+ });
120
+
121
+ test("scope.owned + onDrift flow into the reconcilePr step", () => {
122
+ const { op } = ReconcileOp({ name: "p", env: "prod", onDrift: "issue", scope: { owned: true } });
123
+ const phases = getProps(op).phases as Array<Record<string, unknown>>;
124
+ const reconcileStep = (phases[2].steps as Array<Record<string, unknown>>)[0];
125
+ expect(reconcileStep.args).toEqual({ env: "prod", mode: "issue", owned: true });
126
+ });
127
+
128
+ test("labels include Reconcile + Env", () => {
129
+ const { op } = ReconcileOp({ name: "p", env: "prod" });
130
+ expect(getProps(op).labels).toEqual({ Reconcile: "true", Env: "prod" });
131
+ });
132
+
133
+ test("Plan phase surfaces Drift as a run outcome", () => {
134
+ const { op } = ReconcileOp({ name: "p", env: "prod" });
135
+ const phases = getProps(op).phases as Array<Record<string, unknown>>;
136
+ const diffStep = (phases[1].steps as Array<Record<string, unknown>>)[0];
137
+ expect(diffStep.outcomeAttribute).toEqual({ name: "Drift", from: "drifted" });
138
+ });
139
+ });
140
+
141
+ // ── ApplyOp ──────────────────────────────────────────────────────────
142
+
143
+ describe("ApplyOp: shape", () => {
144
+ test("returns an op (no schedule)", () => {
145
+ const result = ApplyOp({ name: "prod-apply", env: "prod" });
146
+ expect(result.op).toBeDefined();
147
+ expect(getEntityType(result.op)).toBe("Chant::Op");
148
+ expect((result.op as unknown as Record<symbol, unknown>)[DECLARABLE_MARKER]).toBe(true);
149
+ });
150
+
151
+ test("ungated apply: Build → Plan → Apply (no Approve phase)", () => {
152
+ const { op } = ApplyOp({ name: "p", env: "prod", target: "kubectl" });
153
+ const phases = getProps(op).phases as Array<Record<string, unknown>>;
154
+ expect(phases.map((p) => p.name)).toEqual(["Build", "Plan", "Apply"]);
155
+ const applyStep = (phases[2].steps as Array<Record<string, unknown>>)[0];
156
+ expect(applyStep.fn).toBe("nativeApply");
157
+ expect(applyStep.args).toEqual({ target: "kubectl", env: "prod", output: "dist", deleteMode: "never" });
158
+ });
159
+ });
160
+
161
+ describe("ApplyOp: gating + deletes", () => {
162
+ test("delete: gated inserts an Approve gate phase before Apply", () => {
163
+ const { op } = ApplyOp({ name: "p", env: "prod", delete: "gated" });
164
+ const phases = getProps(op).phases as Array<Record<string, unknown>>;
165
+ expect(phases.map((p) => p.name)).toEqual(["Build", "Plan", "Approve", "Apply"]);
166
+ const gateStep = (phases[2].steps as Array<Record<string, unknown>>)[0];
167
+ expect(gateStep.kind).toBe("gate");
168
+ expect(gateStep.signalName).toBe("approve-p");
169
+ });
170
+
171
+ test("explicit gate config is honored", () => {
172
+ const { op } = ApplyOp({
173
+ name: "p",
174
+ env: "prod",
175
+ gate: { signalName: "go", description: "ship it" },
176
+ });
177
+ const phases = getProps(op).phases as Array<Record<string, unknown>>;
178
+ const gateStep = (phases[2].steps as Array<Record<string, unknown>>)[0];
179
+ expect(gateStep.signalName).toBe("go");
180
+ expect(gateStep.description).toBe("ship it");
181
+ });
182
+
183
+ test("deleteMode flows into the nativeApply step", () => {
184
+ const { op } = ApplyOp({ name: "p", env: "prod", delete: "owned-only" });
185
+ const phases = getProps(op).phases as Array<Record<string, unknown>>;
186
+ const applyStep = (phases.find((p) => p.name === "Apply")!.steps as Array<Record<string, unknown>>)[0];
187
+ expect((applyStep.args as Record<string, unknown>).deleteMode).toBe("owned-only");
188
+ });
189
+
190
+ test("labels include Apply + Env", () => {
191
+ const { op } = ApplyOp({ name: "p", env: "prod" });
192
+ expect(getProps(op).labels).toEqual({ Apply: "true", Env: "prod" });
193
+ });
194
+ });
195
+
196
+ describe("ApplyOp: compensation (#125, total-or-refused in #1449)", () => {
197
+ test("destructive apply on a target with a native rollback compensates by default", () => {
198
+ const { op } = ApplyOp({ name: "p", env: "prod", target: "cloudformation", delete: "owned-only" });
199
+ const onFailure = getProps(op).onFailure as Array<Record<string, unknown>> | undefined;
200
+ expect(onFailure?.map((p) => p.name)).toEqual(["Rollback"]);
201
+ const step = (onFailure![0].steps as Array<Record<string, unknown>>)[0];
202
+ expect(step.fn).toBe("compensateApply");
203
+ expect(step.args).toEqual({ target: "cloudformation", env: "prod" });
204
+ });
205
+
206
+ test("destructive apply on a rollback-less target has no compensation by default", () => {
207
+ // Compensation is total: without a rollback path there is nothing the
208
+ // phase could run, so none is wired — rather than one that could only
209
+ // warn at rollback time.
210
+ const { op } = ApplyOp({ name: "p", env: "prod", target: "kubectl", delete: "owned-only" });
211
+ expect(getProps(op).onFailure).toBeUndefined();
212
+ });
213
+
214
+ test("additive apply has no compensation by default", () => {
215
+ const { op } = ApplyOp({ name: "p", env: "prod", delete: "never" });
216
+ expect(getProps(op).onFailure).toBeUndefined();
217
+ });
218
+
219
+ test("compensate: false disables rollback even when destructive", () => {
220
+ const { op } = ApplyOp({
221
+ name: "p",
222
+ env: "prod",
223
+ target: "cloudformation",
224
+ delete: "owned-only",
225
+ compensate: false,
226
+ });
227
+ expect(getProps(op).onFailure).toBeUndefined();
228
+ });
229
+
230
+ test("compensate.command supplies a rollback for targets without a native one", () => {
231
+ const { op } = ApplyOp({
232
+ name: "p",
233
+ env: "prod",
234
+ target: "kubectl",
235
+ compensate: { command: "kubectl rollout undo deployment/web" },
236
+ });
237
+ const onFailure = getProps(op).onFailure as Array<Record<string, unknown>>;
238
+ const step = (onFailure[0].steps as Array<Record<string, unknown>>)[0];
239
+ expect((step.args as Record<string, unknown>).command).toBe("kubectl rollout undo deployment/web");
240
+ });
241
+
242
+ test("compensate: true on cloudformation builds — the native rollbackStack is the path", () => {
243
+ const { op } = ApplyOp({ name: "p", env: "prod", target: "cloudformation", compensate: true });
244
+ const onFailure = getProps(op).onFailure as Array<Record<string, unknown>>;
245
+ const step = (onFailure[0].steps as Array<Record<string, unknown>>)[0];
246
+ expect(step.fn).toBe("compensateApply");
247
+ expect((step.args as Record<string, unknown>).command).toBeUndefined();
248
+ });
249
+
250
+ test("compensate: true is refused at build time for every rollback-less target (#1449)", () => {
251
+ for (const target of ["kubectl", "kustomize", "arm", "gcp", "fly"] as const) {
252
+ expect(() => ApplyOp({ name: "p", env: "prod", target, compensate: true })).toThrow(
253
+ new RegExp(`ApplyOp "p": compensate is enabled, but target "${target}" has no automatic rollback`),
254
+ );
255
+ }
256
+ });
257
+
258
+ test("the refusal names the two ways out — compensate.command or compensate: false", () => {
259
+ const err = (() => {
260
+ try {
261
+ ApplyOp({ name: "web-apply", env: "prod", target: "fly", compensate: true });
262
+ return undefined;
263
+ } catch (e) {
264
+ return String(e);
265
+ }
266
+ })();
267
+ expect(err).toMatch(/compensate: \{ command/);
268
+ expect(err).toMatch(/compensate: false/);
269
+ expect(err).toMatch(/rollbackStack/);
270
+ });
271
+
272
+ test("an object without a command is refused the same way as true", () => {
273
+ for (const target of ["kubectl", "kustomize", "arm", "gcp", "fly"] as const) {
274
+ expect(() => ApplyOp({ name: "p", env: "prod", target, compensate: {} })).toThrow(
275
+ /has no automatic rollback/,
276
+ );
277
+ }
278
+ });
279
+
280
+ test("a command lifts the refusal on every target", () => {
281
+ for (const target of ["kubectl", "kustomize", "arm", "gcp", "fly", "cloudformation"] as const) {
282
+ const { op } = ApplyOp({
283
+ name: "p",
284
+ env: "prod",
285
+ target,
286
+ compensate: { command: "echo rollback" },
287
+ });
288
+ const onFailure = getProps(op).onFailure as Array<Record<string, unknown>>;
289
+ const step = (onFailure[0].steps as Array<Record<string, unknown>>)[0];
290
+ expect((step.args as Record<string, unknown>).command).toBe("echo rollback");
291
+ }
292
+ });
293
+ });
294
+
295
+ // ── ApplyOp: gated effects (#1834) ───────────────────────────────────
296
+
297
+ describe("ApplyOp: effects gated (#1834, #1703 decision 6)", () => {
298
+ test("effects: gated inserts the Approve gate phase before Apply, like delete: gated", () => {
299
+ const { op } = ApplyOp({ name: "p", env: "prod", effects: "gated" });
300
+ const phases = getProps(op).phases as Array<Record<string, unknown>>;
301
+ expect(phases.map((p) => p.name)).toEqual(["Build", "Plan", "Approve", "Apply"]);
302
+ const gateStep = (phases[2].steps as Array<Record<string, unknown>>)[0];
303
+ expect(gateStep.kind).toBe("gate");
304
+ expect(gateStep.signalName).toBe("approve-p");
305
+ expect(gateStep.description).toBe("Approve apply to prod (delete mode: never, effects: gated)");
306
+ });
307
+
308
+ test("effects: gated does not change the delete mode riding into nativeApply", () => {
309
+ const { op } = ApplyOp({ name: "p", env: "prod", effects: "gated" });
310
+ const phases = getProps(op).phases as Array<Record<string, unknown>>;
311
+ const applyStep = (phases.find((p) => p.name === "Apply")!.steps as Array<Record<string, unknown>>)[0];
312
+ expect((applyStep.args as Record<string, unknown>).deleteMode).toBe("never");
313
+ });
314
+
315
+ test("effects: gated composes with delete: gated in one Approve gate", () => {
316
+ const { op } = ApplyOp({ name: "p", env: "prod", delete: "gated", effects: "gated" });
317
+ const phases = getProps(op).phases as Array<Record<string, unknown>>;
318
+ expect(phases.map((p) => p.name)).toEqual(["Build", "Plan", "Approve", "Apply"]);
319
+ const gateStep = (phases[2].steps as Array<Record<string, unknown>>)[0];
320
+ expect(gateStep.description).toBe("Approve apply to prod (delete mode: gated, effects: gated)");
321
+ });
322
+
323
+ });
324
+
325
+ // ── WatchOp: receipt staleness (#1834) ───────────────────────────────
326
+
327
+ describe("WatchOp: receipt staleness (#1834)", () => {
328
+ const seeded = EffectReceipt("seeded", {
329
+ effect: "db-seed",
330
+ flavor: "hash",
331
+ inputs: { file: "seed.sql" },
332
+ });
333
+
334
+ test("without receipts the op is unchanged (no Receipts phase)", () => {
335
+ const { op } = WatchOp({ name: "p", env: "prod", schedule: "* * * * *" });
336
+ const phases = getProps(op).phases as Array<Record<string, unknown>>;
337
+ expect(phases.map((p) => p.name)).toEqual(["Snapshot", "Diff"]);
338
+ });
339
+
340
+ test("receipts add a read-only Receipts phase carrying identity + expectation data", () => {
341
+ const { op } = WatchOp({ name: "p", env: "prod", schedule: "* * * * *", receipts: [seeded] });
342
+ const phases = getProps(op).phases as Array<Record<string, unknown>>;
343
+ expect(phases.map((p) => p.name)).toEqual(["Snapshot", "Diff", "Receipts"]);
344
+ const step = (phases[2].steps as Array<Record<string, unknown>>)[0];
345
+ expect(step.fn).toBe("receiptStaleness");
346
+ expect(step.args).toEqual({
347
+ receipts: [
348
+ {
349
+ receipt: { name: "seeded", effect: "db-seed", flavor: "hash", inputs: { file: "seed.sql" } },
350
+ expectation: receiptExpectation(seeded),
351
+ },
352
+ ],
353
+ });
354
+ // Staleness surfaces as a run outcome, like Drift.
355
+ expect(step.outcomeAttribute).toEqual({ name: "StaleReceipts", from: "stale" });
356
+ });
357
+
358
+ test("the Receipts phase runs nothing: its only step is the staleness read", () => {
359
+ const { op } = WatchOp({ name: "p", env: "prod", schedule: "* * * * *", receipts: [seeded] });
360
+ const phases = getProps(op).phases as Array<Record<string, unknown>>;
361
+ const steps = phases[2].steps as Array<Record<string, unknown>>;
362
+ expect(steps).toHaveLength(1);
363
+ expect(steps.map((s) => s.fn)).toEqual(["receiptStaleness"]);
364
+ });
365
+
366
+ });
@@ -0,0 +1,168 @@
1
+ import { describe, test, expect } from "vitest";
2
+ import { runOpLocally } from "../local-executor";
3
+ import type { ActivityFn, ActivityProfile } from "../activity-registry";
4
+ import type { OpConfig } from "../types";
5
+ import { eq, gt, run, report, when } from "../converge-rule";
6
+ import type { ConvergeSymptom } from "../../lifecycle/symptoms";
7
+ import { ConvergeOp } from "./converge-op";
8
+
9
+ function props(op: unknown): OpConfig {
10
+ return (op as { props: OpConfig }).props;
11
+ }
12
+
13
+ function phaseNamed(op: unknown, name: string) {
14
+ const phase = props(op).phases.find((p) => p.name === name);
15
+ if (!phase) throw new Error(`no "${name}" phase`);
16
+ return phase;
17
+ }
18
+
19
+ const driftRule = when<ConvergeSymptom>(eq("status", "drifted"), run("fountain-apply"), {
20
+ id: "drift-apply",
21
+ why: "Re-apply converges drift back to declared source.",
22
+ });
23
+
24
+ describe("ConvergeOp composite (#1484)", () => {
25
+ test("builds an Observe -> Converge phase pair", () => {
26
+ const { op } = ConvergeOp({ name: "staging-converge", env: "staging", rules: [driftRule] });
27
+ const config = props(op);
28
+ expect(config.phases.map((p) => p.name)).toEqual(["Observe", "Converge"]);
29
+ });
30
+
31
+ test("Observe phase snapshots then diffs, with a Drift outcome attribute", () => {
32
+ const { op } = ConvergeOp({ name: "staging-converge", env: "staging", rules: [driftRule] });
33
+ const observe = phaseNamed(op, "Observe");
34
+ expect(observe.steps).toHaveLength(2);
35
+ expect((observe.steps[0] as { fn: string }).fn).toBe("lifecycleSnapshot");
36
+ const diff = observe.steps[1] as { fn: string; args: Record<string, unknown>; outcomeAttribute: unknown };
37
+ expect(diff.fn).toBe("lifecycleDiff");
38
+ expect(diff.args.env).toBe("staging");
39
+ expect(diff.outcomeAttribute).toEqual({ name: "Drift", from: "drifted" });
40
+ });
41
+
42
+ test("Converge phase calls convergeTick with the rule table, dial, and budget, threading the Observe diff via a step-output reference", () => {
43
+ const { op } = ConvergeOp({ name: "staging-converge", env: "staging", dial: "apply", budget: 5, rules: [driftRule] });
44
+ const converge = phaseNamed(op, "Converge");
45
+ expect(converge.steps).toHaveLength(1);
46
+ const tick = converge.steps[0] as { fn: string; args: Record<string, unknown>; profile: string; outcomeAttribute: unknown };
47
+ expect(tick.fn).toBe("convergeTick");
48
+ expect(tick.profile).toBe("longInfra");
49
+ expect(tick.args.opName).toBe("staging-converge");
50
+ expect(tick.args.env).toBe("staging");
51
+ expect(tick.args.dial).toBe("apply");
52
+ expect(tick.args.budget).toBe(5);
53
+ expect(tick.args.rules).toEqual([driftRule]);
54
+ expect(tick.args.preflightDrift).toMatchObject({ kind: "step-output-ref", step: "diff", path: "drifted" });
55
+ expect(tick.outcomeAttribute).toEqual({ name: "Remediated", from: "remediated" });
56
+ });
57
+
58
+ test("defaults: dial observe, budget 3, live true", () => {
59
+ const { op } = ConvergeOp({ name: "staging-converge", env: "staging", rules: [driftRule] });
60
+ const config = props(op);
61
+ expect(config.labels).toEqual({ Converge: "true", Env: "staging", Dial: "observe" });
62
+ const tick = phaseNamed(op, "Converge").steps[0] as { args: Record<string, unknown> };
63
+ expect(tick.args.dial).toBe("observe");
64
+ expect(tick.args.budget).toBe(3);
65
+ const diff = phaseNamed(op, "Observe").steps[1] as { args: Record<string, unknown> };
66
+ expect(diff.args.live).toBe(true);
67
+ });
68
+
69
+ test("no schedule -> no cadence on the op; a schedule -> op.schedule.cron (#2120)", () => {
70
+ const noSchedule = ConvergeOp({ name: "staging-converge", env: "staging", rules: [driftRule] });
71
+ expect(props(noSchedule.op).schedule).toBeUndefined();
72
+
73
+ const scheduled = ConvergeOp({ name: "staging-converge", env: "staging", rules: [driftRule], schedule: "*/10 * * * *" });
74
+ expect(props(scheduled.op).schedule?.cron).toBe("*/10 * * * *");
75
+ });
76
+
77
+ // Finding D (#1954 pre-merge review): issue #1484's acceptance criterion is
78
+ // "skip-and-report when a prior remediation is in flight ... never queue".
79
+ // `"skip"` is the overlap policy that drops a fire whose predecessor is
80
+ // still running, rather than buffering/queuing it.
81
+ test("a scheduled ConvergeOp sets an explicit \"skip\" overlap — never queue an in-flight tick", () => {
82
+ const { op } = ConvergeOp({ name: "staging-converge", env: "staging", rules: [driftRule], schedule: "*/10 * * * *" });
83
+ expect(props(op).schedule?.overlap).toBe("skip");
84
+ });
85
+
86
+ test("an invalid cron is refused at construction, with cronSyntaxMessage's wording (#2120)", () => {
87
+ expect(() =>
88
+ ConvergeOp({ name: "staging-converge", env: "staging", rules: [driftRule], schedule: "every ten minutes" }),
89
+ ).toThrow(/does not look like valid 5- or 6-field cron syntax/);
90
+ });
91
+ });
92
+
93
+ describe("ConvergeOp composite — build-time refusals (#1484)", () => {
94
+ test("refuses an empty rule table", () => {
95
+ expect(() => ConvergeOp({ name: "x", env: "staging", rules: [] })).toThrow(/at least one rule/);
96
+ });
97
+
98
+ test("refuses duplicate rule ids", () => {
99
+ const dup = when<ConvergeSymptom>(eq("status", "reconciled"), report("noop"), { id: "drift-apply", why: "y" });
100
+ expect(() => ConvergeOp({ name: "x", env: "staging", rules: [driftRule, dup] })).toThrow(/duplicate rule id/);
101
+ });
102
+
103
+ test("refuses a negative or non-integer budget", () => {
104
+ expect(() => ConvergeOp({ name: "x", env: "staging", rules: [driftRule], budget: -1 })).toThrow(/budget must be/);
105
+ expect(() => ConvergeOp({ name: "x", env: "staging", rules: [driftRule], budget: 1.5 })).toThrow(/budget must be/);
106
+ });
107
+
108
+ test("refuses a rule assembled by hand with a blank why", () => {
109
+ const noWhy = { id: "r1", when: { kind: "field-comparison", field: "status", op: "eq", value: "drifted" }, then: { kind: "report", reason: "x" }, why: "" } as unknown as typeof driftRule;
110
+ expect(() => ConvergeOp({ name: "x", env: "staging", rules: [noWhy] })).toThrow(/carry its why/);
111
+ });
112
+
113
+ test("refuses a rule assembled by hand outside the evaluable subset", () => {
114
+ const bad = { id: "r1", when: { kind: "javascript-closure" }, then: { kind: "report", reason: "x" }, why: "y" } as unknown as typeof driftRule;
115
+ expect(() => ConvergeOp({ name: "x", env: "staging", rules: [bad] })).toThrow(/evaluable subset/);
116
+ });
117
+ });
118
+
119
+ describe("ConvergeOp — end-to-end local run (#1484)", () => {
120
+ const PROFILES: Record<string, ActivityProfile> = {
121
+ fastIdempotent: { timeout: "5m", retry: { maximumAttempts: 1 } },
122
+ longInfra: { timeout: "20m", retry: { maximumAttempts: 1 } },
123
+ };
124
+
125
+ test("chant run <converge-op>: observes, then ticks, resolving the step-output reference into the tick's args", async () => {
126
+ const { op } = ConvergeOp({ name: "staging-converge", env: "staging", dial: "apply", budget: 2, rules: [driftRule] });
127
+ const config = props(op);
128
+
129
+ let receivedArgs: Record<string, unknown> | undefined;
130
+ const activities = new Map<string, ActivityFn>([
131
+ ["lifecycleSnapshot", (async () => undefined) as ActivityFn],
132
+ ["lifecycleDiff", (async () => ({ output: "DRIFTED (1)", exitCode: 0, drifted: true })) as ActivityFn],
133
+ [
134
+ "convergeTick",
135
+ (async (args) => {
136
+ receivedArgs = args;
137
+ return { drifted: true, remediated: 1, reported: 0, skippedBudget: 0, skippedFlap: 0, unobserved: 0, adopted: 0, log: "converge(staging): remediated=1" };
138
+ }) as ActivityFn,
139
+ ],
140
+ ]);
141
+
142
+ const result = await runOpLocally(config, activities, PROFILES);
143
+
144
+ expect(result.status).toBe("ok");
145
+ // The step-output reference resolved to the real value the Observe phase produced.
146
+ expect(receivedArgs?.preflightDrift).toBe(true);
147
+ expect(receivedArgs?.dial).toBe("apply");
148
+ expect(receivedArgs?.budget).toBe(2);
149
+ expect(receivedArgs?.opName).toBe("staging-converge");
150
+ });
151
+
152
+ test("a ConvergeOp with a gated dispatch target still runs locally itself — the tick's own phases carry no gate", async () => {
153
+ // ConvergeOp's own generated workflow never contains a gate step (dispatch
154
+ // to a gated Op happens inside convergeTick's own subprocess dispatch, not
155
+ // as a step in this Op's phases) — so the local runner never refuses it.
156
+ const { op } = ConvergeOp({ name: "staging-converge", env: "staging", dial: "apply", rules: [driftRule] });
157
+ const config = props(op);
158
+ expect(config.phases.every((p) => p.steps.every((s) => s.kind !== "gate"))).toBe(true);
159
+
160
+ const activities = new Map<string, ActivityFn>([
161
+ ["lifecycleSnapshot", (async () => undefined) as ActivityFn],
162
+ ["lifecycleDiff", (async () => ({ output: "", exitCode: 0, drifted: false })) as ActivityFn],
163
+ ["convergeTick", (async () => ({ drifted: false, remediated: 0, reported: 0, skippedBudget: 0, skippedFlap: 0, unobserved: 0, adopted: 0, log: "" })) as ActivityFn],
164
+ ]);
165
+ const result = await runOpLocally(config, activities, PROFILES);
166
+ expect(result.status).toBe("ok");
167
+ });
168
+ });