@intentius/chant 0.57.0 → 0.58.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (504) hide show
  1. package/dist/audit/catalog.d.ts +51 -1
  2. package/dist/audit/catalog.d.ts.map +1 -1
  3. package/dist/audit/core.d.ts +20 -1
  4. package/dist/audit/core.d.ts.map +1 -1
  5. package/dist/audit/discover.d.ts +14 -8
  6. package/dist/audit/discover.d.ts.map +1 -1
  7. package/dist/audit/lineage.d.ts +10 -0
  8. package/dist/audit/lineage.d.ts.map +1 -0
  9. package/dist/audit/prior-art.d.ts +385 -0
  10. package/dist/audit/prior-art.d.ts.map +1 -0
  11. package/dist/audit/proof.d.ts +16 -0
  12. package/dist/audit/proof.d.ts.map +1 -1
  13. package/dist/audit/report-html.d.ts.map +1 -1
  14. package/dist/audit/report-model.d.ts +8 -1
  15. package/dist/audit/report-model.d.ts.map +1 -1
  16. package/dist/audit/report.d.ts.map +1 -1
  17. package/dist/audit/rules-doc.d.ts +3 -0
  18. package/dist/audit/rules-doc.d.ts.map +1 -1
  19. package/dist/audit/secrets.d.ts +15 -0
  20. package/dist/audit/secrets.d.ts.map +1 -1
  21. package/dist/audit/terraform-state.d.ts +46 -0
  22. package/dist/audit/terraform-state.d.ts.map +1 -0
  23. package/dist/build.d.ts +17 -1
  24. package/dist/build.d.ts.map +1 -1
  25. package/dist/claimed-fields.d.ts +161 -0
  26. package/dist/claimed-fields.d.ts.map +1 -0
  27. package/dist/cli/build-params-cli.d.ts +2 -2
  28. package/dist/cli/command-group.d.ts +14 -0
  29. package/dist/cli/command-group.d.ts.map +1 -1
  30. package/dist/cli/commands/audit.d.ts +17 -1
  31. package/dist/cli/commands/audit.d.ts.map +1 -1
  32. package/dist/cli/commands/build.d.ts.map +1 -1
  33. package/dist/cli/commands/check-lexicon-docs.d.ts +1 -1
  34. package/dist/cli/commands/check-lexicon-examples.d.ts +16 -6
  35. package/dist/cli/commands/check-lexicon-examples.d.ts.map +1 -1
  36. package/dist/cli/commands/init-lexicon/templates/codegen.d.ts +1 -0
  37. package/dist/cli/commands/init-lexicon/templates/codegen.d.ts.map +1 -1
  38. package/dist/cli/commands/init-lexicon/templates/project.d.ts +7 -0
  39. package/dist/cli/commands/init-lexicon/templates/project.d.ts.map +1 -1
  40. package/dist/cli/commands/init-lexicon.d.ts.map +1 -1
  41. package/dist/cli/commands/init.d.ts.map +1 -1
  42. package/dist/cli/commands/lint.d.ts.map +1 -1
  43. package/dist/cli/commands/onboard.d.ts +2 -0
  44. package/dist/cli/commands/onboard.d.ts.map +1 -1
  45. package/dist/cli/handlers/build.d.ts.map +1 -1
  46. package/dist/cli/handlers/lifecycle.d.ts.map +1 -1
  47. package/dist/cli/handlers/operator.d.ts +82 -16
  48. package/dist/cli/handlers/operator.d.ts.map +1 -1
  49. package/dist/cli/handlers/run.d.ts +49 -42
  50. package/dist/cli/handlers/run.d.ts.map +1 -1
  51. package/dist/cli/main.d.ts +28 -1
  52. package/dist/cli/main.d.ts.map +1 -1
  53. package/dist/cli/mcp/op-tools.d.ts +16 -1
  54. package/dist/cli/mcp/op-tools.d.ts.map +1 -1
  55. package/dist/cli/mcp/resource-handlers.d.ts.map +1 -1
  56. package/dist/cli/mcp/resources/context.d.ts.map +1 -1
  57. package/dist/cli/registry.d.ts +26 -9
  58. package/dist/cli/registry.d.ts.map +1 -1
  59. package/dist/codegen/generate-post-synth-barrel.d.ts +1 -1
  60. package/dist/components/auto-release.d.ts +10 -20
  61. package/dist/components/auto-release.d.ts.map +1 -1
  62. package/dist/components/capability.d.ts +4 -4
  63. package/dist/components/cli-support.d.ts +20 -21
  64. package/dist/components/cli-support.d.ts.map +1 -1
  65. package/dist/components/component.d.ts +3 -3
  66. package/dist/components/component.d.ts.map +1 -1
  67. package/dist/components/driver-output.d.ts.map +1 -1
  68. package/dist/components/driver.d.ts +66 -36
  69. package/dist/components/driver.d.ts.map +1 -1
  70. package/dist/components/index.d.ts +1 -2
  71. package/dist/components/index.d.ts.map +1 -1
  72. package/dist/components/manifest-persistence.d.ts +1 -9
  73. package/dist/components/manifest-persistence.d.ts.map +1 -1
  74. package/dist/components/run-progress.d.ts +6 -6
  75. package/dist/components/run-progress.d.ts.map +1 -1
  76. package/dist/components/verbs/run-agent.d.ts +9 -15
  77. package/dist/components/verbs/run-agent.d.ts.map +1 -1
  78. package/dist/components/verbs/verify.d.ts +2 -2
  79. package/dist/deep-observation.d.ts +17 -0
  80. package/dist/deep-observation.d.ts.map +1 -1
  81. package/dist/discovery/files.d.ts +4 -4
  82. package/dist/discovery/fold-import.d.ts.map +1 -1
  83. package/dist/discovery/param-deps.d.ts +33 -0
  84. package/dist/discovery/param-deps.d.ts.map +1 -1
  85. package/dist/discovery/sandbox/bundle.d.ts.map +1 -1
  86. package/dist/discovery/sandbox/config-wire.d.ts +2 -2
  87. package/dist/fold/foldable-helpers.d.ts +5 -1
  88. package/dist/fold/foldable-helpers.d.ts.map +1 -1
  89. package/dist/fold-provenance.d.ts +236 -0
  90. package/dist/fold-provenance.d.ts.map +1 -0
  91. package/dist/graph-ops.d.ts +1 -1
  92. package/dist/held-elsewhere.d.ts +85 -0
  93. package/dist/held-elsewhere.d.ts.map +1 -0
  94. package/dist/index.d.ts +3 -0
  95. package/dist/index.d.ts.map +1 -1
  96. package/dist/kubectl-context.d.ts +3 -3
  97. package/dist/lexicon-config.d.ts +4 -4
  98. package/dist/lexicon-plugin-helpers.d.ts +1 -1
  99. package/dist/lexicon.d.ts +104 -14
  100. package/dist/lexicon.d.ts.map +1 -1
  101. package/dist/lifecycle/change-set.d.ts +13 -0
  102. package/dist/lifecycle/change-set.d.ts.map +1 -1
  103. package/dist/lifecycle/deep-diff.d.ts +133 -6
  104. package/dist/lifecycle/deep-diff.d.ts.map +1 -1
  105. package/dist/lifecycle/deep-observe.d.ts.map +1 -1
  106. package/dist/lifecycle/gate-ledger.d.ts +71 -3
  107. package/dist/lifecycle/gate-ledger.d.ts.map +1 -1
  108. package/dist/lifecycle/index.d.ts +1 -0
  109. package/dist/lifecycle/index.d.ts.map +1 -1
  110. package/dist/lifecycle/release-ledger.d.ts +2 -2
  111. package/dist/lifecycle/run-ledger.d.ts +53 -0
  112. package/dist/lifecycle/run-ledger.d.ts.map +1 -0
  113. package/dist/lifecycle/symptoms.d.ts +3 -3
  114. package/dist/lifecycle/symptoms.d.ts.map +1 -1
  115. package/dist/lint/config.d.ts +81 -2
  116. package/dist/lint/config.d.ts.map +1 -1
  117. package/dist/lint/post-synth.d.ts +55 -2
  118. package/dist/lint/post-synth.d.ts.map +1 -1
  119. package/dist/lint/rules/__fixtures__/comp/comp004/pass/neo4j-cluster.component.d.ts +1 -1
  120. package/dist/lint/rules/__fixtures__/comp/comp004/pass/neo4j-cluster.component.d.ts.map +1 -1
  121. package/dist/lint/rules/__fixtures__/comp/comp004/pass/orders-table.component.d.ts +1 -1
  122. package/dist/lint/rules/__fixtures__/comp/comp004/pass/orders-table.component.d.ts.map +1 -1
  123. package/dist/lint/rules/comp/comp004-gate-needs-durable-runtime.d.ts +44 -0
  124. package/dist/lint/rules/comp/comp004-gate-needs-durable-runtime.d.ts.map +1 -0
  125. package/dist/lint/rules/comp/index.d.ts +1 -1
  126. package/dist/lint/rules/comp/index.d.ts.map +1 -1
  127. package/dist/lint/rules/op/index.d.ts +16 -0
  128. package/dist/lint/rules/op/index.d.ts.map +1 -0
  129. package/dist/lint/rules/op/ops012-activity-contract.d.ts +27 -0
  130. package/dist/lint/rules/op/ops012-activity-contract.d.ts.map +1 -0
  131. package/dist/lint/rules/op/ops013-step-output-ref.d.ts +61 -0
  132. package/dist/lint/rules/op/ops013-step-output-ref.d.ts.map +1 -0
  133. package/dist/lint/rules/op/ops014-converge-rule-refusals.d.ts +55 -0
  134. package/dist/lint/rules/op/ops014-converge-rule-refusals.d.ts.map +1 -0
  135. package/dist/lint/rules/op/support.d.ts +12 -0
  136. package/dist/lint/rules/op/support.d.ts.map +1 -0
  137. package/dist/lint/suppressions.d.ts +108 -0
  138. package/dist/lint/suppressions.d.ts.map +1 -0
  139. package/dist/live-endpoint.d.ts +1 -1
  140. package/dist/op/activities/activity-contracts.d.ts +54 -0
  141. package/dist/op/activities/activity-contracts.d.ts.map +1 -0
  142. package/dist/op/activities/apply.d.ts +262 -0
  143. package/dist/op/activities/apply.d.ts.map +1 -0
  144. package/dist/op/activities/build.d.ts +13 -0
  145. package/dist/op/activities/build.d.ts.map +1 -0
  146. package/dist/op/activities/converge.d.ts +110 -0
  147. package/dist/op/activities/converge.d.ts.map +1 -0
  148. package/dist/op/activities/env-teardown.d.ts +76 -0
  149. package/dist/op/activities/env-teardown.d.ts.map +1 -0
  150. package/dist/op/activities/guard-validate.d.ts +64 -0
  151. package/dist/op/activities/guard-validate.d.ts.map +1 -0
  152. package/dist/op/activities/http-check.d.ts +34 -0
  153. package/dist/op/activities/http-check.d.ts.map +1 -0
  154. package/dist/op/activities/index.d.ts +31 -0
  155. package/dist/op/activities/index.d.ts.map +1 -0
  156. package/dist/op/activities/lexicon-upgrade.d.ts +182 -0
  157. package/dist/op/activities/lexicon-upgrade.d.ts.map +1 -0
  158. package/dist/op/activities/lifecycle.d.ts +42 -0
  159. package/dist/op/activities/lifecycle.d.ts.map +1 -0
  160. package/dist/op/activities/pipeline-audit.d.ts +84 -0
  161. package/dist/op/activities/pipeline-audit.d.ts.map +1 -0
  162. package/dist/op/activities/policy.d.ts +17 -0
  163. package/dist/op/activities/policy.d.ts.map +1 -0
  164. package/dist/op/activities/reconcile.d.ts +89 -0
  165. package/dist/op/activities/reconcile.d.ts.map +1 -0
  166. package/dist/op/activities/shell.d.ts +13 -0
  167. package/dist/op/activities/shell.d.ts.map +1 -0
  168. package/dist/op/activities/teardown.d.ts +10 -0
  169. package/dist/op/activities/teardown.d.ts.map +1 -0
  170. package/dist/op/activities/util.d.ts +7 -0
  171. package/dist/op/activities/util.d.ts.map +1 -0
  172. package/dist/op/activities/wait.d.ts +28 -0
  173. package/dist/op/activities/wait.d.ts.map +1 -0
  174. package/dist/op/activities/workflow-audit.d.ts +92 -0
  175. package/dist/op/activities/workflow-audit.d.ts.map +1 -0
  176. package/dist/op/activity-contract-registry.d.ts +76 -0
  177. package/dist/op/activity-contract-registry.d.ts.map +1 -0
  178. package/dist/op/activity-contract.d.ts +25 -18
  179. package/dist/op/activity-contract.d.ts.map +1 -1
  180. package/dist/op/activity-failure.d.ts +28 -0
  181. package/dist/op/activity-failure.d.ts.map +1 -0
  182. package/dist/op/activity-profiles.d.ts +118 -0
  183. package/dist/op/activity-profiles.d.ts.map +1 -0
  184. package/dist/op/activity-registry.d.ts +22 -38
  185. package/dist/op/activity-registry.d.ts.map +1 -1
  186. package/dist/op/activity-runtime.d.ts +3 -14
  187. package/dist/op/activity-runtime.d.ts.map +1 -1
  188. package/dist/op/builders.d.ts +84 -29
  189. package/dist/op/builders.d.ts.map +1 -1
  190. package/dist/op/composites/apply-op.d.ts +113 -0
  191. package/dist/op/composites/apply-op.d.ts.map +1 -0
  192. package/dist/op/composites/converge-op.d.ts +120 -0
  193. package/dist/op/composites/converge-op.d.ts.map +1 -0
  194. package/dist/op/composites/index.d.ts +26 -0
  195. package/dist/op/composites/index.d.ts.map +1 -0
  196. package/dist/op/composites/lexicon-upgrade-op.d.ts +78 -0
  197. package/dist/op/composites/lexicon-upgrade-op.d.ts.map +1 -0
  198. package/dist/op/composites/pipeline-audit-op.d.ts +50 -0
  199. package/dist/op/composites/pipeline-audit-op.d.ts.map +1 -0
  200. package/dist/op/composites/reconcile-op.d.ts +57 -0
  201. package/dist/op/composites/reconcile-op.d.ts.map +1 -0
  202. package/dist/op/composites/watch-op.d.ts +60 -0
  203. package/dist/op/composites/watch-op.d.ts.map +1 -0
  204. package/dist/op/composites/workflow-audit-op.d.ts +57 -0
  205. package/dist/op/composites/workflow-audit-op.d.ts.map +1 -0
  206. package/dist/op/converge-rule.d.ts +12 -10
  207. package/dist/op/converge-rule.d.ts.map +1 -1
  208. package/dist/op/cron.d.ts +51 -0
  209. package/dist/op/cron.d.ts.map +1 -0
  210. package/dist/op/discover.d.ts +9 -0
  211. package/dist/op/discover.d.ts.map +1 -1
  212. package/dist/op/duration.d.ts +11 -0
  213. package/dist/op/duration.d.ts.map +1 -0
  214. package/dist/op/emulator-lifecycle.d.ts.map +1 -1
  215. package/dist/op/gate.d.ts +90 -0
  216. package/dist/op/gate.d.ts.map +1 -0
  217. package/dist/op/generate-pipeline.d.ts +9 -0
  218. package/dist/op/generate-pipeline.d.ts.map +1 -1
  219. package/dist/op/index.d.ts +23 -7
  220. package/dist/op/index.d.ts.map +1 -1
  221. package/dist/op/local-executor.d.ts +114 -28
  222. package/dist/op/local-executor.d.ts.map +1 -1
  223. package/dist/op/local-output.d.ts +11 -1
  224. package/dist/op/local-output.d.ts.map +1 -1
  225. package/dist/op/op-ir.d.ts +184 -0
  226. package/dist/op/op-ir.d.ts.map +1 -0
  227. package/dist/op/op-verb-class.d.ts +10 -2
  228. package/dist/op/op-verb-class.d.ts.map +1 -1
  229. package/dist/op/operator.d.ts +30 -5
  230. package/dist/op/operator.d.ts.map +1 -1
  231. package/dist/op/receipt-store.d.ts +1 -1
  232. package/dist/op/receipt-store.d.ts.map +1 -1
  233. package/dist/op/resource.d.ts +15 -1
  234. package/dist/op/resource.d.ts.map +1 -1
  235. package/dist/op/runtime.d.ts +200 -0
  236. package/dist/op/runtime.d.ts.map +1 -0
  237. package/dist/op/runtimes/local.d.ts +29 -0
  238. package/dist/op/runtimes/local.d.ts.map +1 -0
  239. package/dist/op/step-output-ref.d.ts +15 -13
  240. package/dist/op/step-output-ref.d.ts.map +1 -1
  241. package/dist/op/types.d.ts +79 -27
  242. package/dist/op/types.d.ts.map +1 -1
  243. package/dist/ownership.d.ts +1 -1
  244. package/dist/provenance.d.ts +21 -0
  245. package/dist/provenance.d.ts.map +1 -1
  246. package/dist/serializer-walker.d.ts.map +1 -1
  247. package/package.json +6 -1
  248. package/src/audit/catalog.test.ts +79 -3
  249. package/src/audit/catalog.ts +111 -13
  250. package/src/audit/core.test.ts +26 -0
  251. package/src/audit/core.ts +61 -17
  252. package/src/audit/discover.test.ts +39 -0
  253. package/src/audit/discover.ts +91 -15
  254. package/src/audit/lineage.ts +100 -0
  255. package/src/audit/prior-art.ts +153 -0
  256. package/src/audit/proof.test.ts +67 -0
  257. package/src/audit/proof.ts +70 -0
  258. package/src/audit/report-html.ts +9 -4
  259. package/src/audit/report-model.ts +16 -2
  260. package/src/audit/report.test.ts +33 -0
  261. package/src/audit/report.ts +5 -3
  262. package/src/audit/rules-doc.test.ts +34 -2
  263. package/src/audit/rules-doc.ts +82 -9
  264. package/src/audit/secrets.ts +15 -3
  265. package/src/audit/terraform-state.test.ts +144 -0
  266. package/src/audit/terraform-state.ts +131 -0
  267. package/src/build.ts +31 -1
  268. package/src/claimed-fields.test.ts +145 -0
  269. package/src/claimed-fields.ts +208 -0
  270. package/src/cli/build-params-cli.ts +2 -2
  271. package/src/cli/command-group.ts +14 -0
  272. package/src/cli/commands/__fixtures__/audit-terraform/infra/.terraform/providers/lock.txt +1 -0
  273. package/src/cli/commands/__fixtures__/audit-terraform/infra/main.tf +1 -0
  274. package/src/cli/commands/__fixtures__/audit-terraform/infra/modules/foo/main.tf +1 -0
  275. package/src/cli/commands/__fixtures__/audit-terraform/infra/variables.tf +3 -0
  276. package/src/cli/commands/__fixtures__/init-lexicon-output/package.json +13 -4
  277. package/src/cli/commands/__fixtures__/init-lexicon-output/src/codegen/docs-cli.ts +4 -0
  278. package/src/cli/commands/__fixtures__/init-lexicon-output/src/codegen/package.ts +1 -0
  279. package/src/cli/commands/__fixtures__/init-lexicon-output/src/package-cli.ts +21 -0
  280. package/src/cli/commands/__snapshots__/init-lexicon.test.ts.snap +1 -0
  281. package/src/cli/commands/audit.test.ts +189 -11
  282. package/src/cli/commands/audit.ts +73 -27
  283. package/src/cli/commands/build.test.ts +292 -9
  284. package/src/cli/commands/build.ts +156 -24
  285. package/src/cli/commands/check-lexicon-docs.ts +1 -1
  286. package/src/cli/commands/check-lexicon-examples.test.ts +29 -1
  287. package/src/cli/commands/check-lexicon-examples.ts +42 -10
  288. package/src/cli/commands/init-lexicon/templates/codegen.ts +9 -0
  289. package/src/cli/commands/init-lexicon/templates/project.ts +44 -4
  290. package/src/cli/commands/init-lexicon.test.ts +97 -2
  291. package/src/cli/commands/init-lexicon.ts +4 -2
  292. package/src/cli/commands/init.test.ts +35 -1
  293. package/src/cli/commands/init.ts +14 -3
  294. package/src/cli/commands/lint.test.ts +48 -0
  295. package/src/cli/commands/lint.ts +123 -0
  296. package/src/cli/commands/onboard.test.ts +75 -0
  297. package/src/cli/commands/onboard.ts +54 -23
  298. package/src/cli/handlers/build.test.ts +81 -1
  299. package/src/cli/handlers/build.ts +15 -1
  300. package/src/cli/handlers/graph.test.ts +3 -3
  301. package/src/cli/handlers/lifecycle.test.ts +195 -12
  302. package/src/cli/handlers/lifecycle.ts +123 -6
  303. package/src/cli/handlers/misc.ts +1 -1
  304. package/src/cli/handlers/operator.test.ts +183 -11
  305. package/src/cli/handlers/operator.ts +260 -55
  306. package/src/cli/handlers/run.test.ts +431 -1287
  307. package/src/cli/handlers/run.ts +365 -915
  308. package/src/cli/main.test.ts +23 -15
  309. package/src/cli/main.ts +88 -33
  310. package/src/cli/mcp/op-tools.ts +147 -88
  311. package/src/cli/mcp/resource-handlers.ts +10 -37
  312. package/src/cli/mcp/resources/context.ts +11 -12
  313. package/src/cli/mcp/server.test.ts +25 -27
  314. package/src/cli/mcp/server.ts +2 -2
  315. package/src/cli/registry.ts +26 -9
  316. package/src/codegen/docs.ts +1 -1
  317. package/src/codegen/generate-post-synth-barrel.test.ts +1 -1
  318. package/src/codegen/generate-post-synth-barrel.ts +1 -1
  319. package/src/components/README.md +3 -2
  320. package/src/components/auto-release.test.ts +4 -20
  321. package/src/components/auto-release.ts +17 -42
  322. package/src/components/capability.ts +4 -4
  323. package/src/components/cli-support.test.ts +10 -5
  324. package/src/components/cli-support.ts +52 -44
  325. package/src/components/component.schema.json +2 -2
  326. package/src/components/component.ts +3 -3
  327. package/src/components/driver-output.ts +14 -2
  328. package/src/components/driver.test.ts +92 -22
  329. package/src/components/driver.ts +193 -83
  330. package/src/components/index.ts +1 -6
  331. package/src/components/manifest-persistence.test.ts +2 -20
  332. package/src/components/manifest-persistence.ts +4 -24
  333. package/src/components/pilots/pilots-lint-clean.test.ts +11 -10
  334. package/src/components/run-progress.ts +6 -6
  335. package/src/components/verbs/run-agent.ts +11 -17
  336. package/src/components/verbs/verify.ts +2 -2
  337. package/src/deep-observation.test.ts +75 -0
  338. package/src/deep-observation.ts +48 -0
  339. package/src/discovery/collect.test.ts +3 -3
  340. package/src/discovery/files.ts +4 -4
  341. package/src/discovery/fold-import.ts +105 -2
  342. package/src/discovery/param-deps.test.ts +112 -1
  343. package/src/discovery/param-deps.ts +193 -0
  344. package/src/discovery/sandbox/bundle.ts +15 -1
  345. package/src/discovery/sandbox/config-wire.test.ts +2 -2
  346. package/src/discovery/sandbox/config-wire.ts +2 -2
  347. package/src/fold/fold.test.ts +56 -0
  348. package/src/fold/foldable-helpers.ts +81 -1
  349. package/src/fold-provenance-exclusion.test.ts +186 -0
  350. package/src/fold-provenance.test.ts +495 -0
  351. package/src/fold-provenance.ts +441 -0
  352. package/src/graph-ops.test.ts +2 -2
  353. package/src/graph-ops.ts +1 -1
  354. package/src/held-elsewhere.test.ts +102 -0
  355. package/src/held-elsewhere.ts +114 -0
  356. package/src/index.ts +3 -0
  357. package/src/kubectl-context.ts +3 -3
  358. package/src/lexicon-config.test.ts +5 -5
  359. package/src/lexicon-config.ts +4 -4
  360. package/src/lexicon-plugin-helpers.ts +1 -1
  361. package/src/lexicon.ts +120 -14
  362. package/src/lifecycle/change-set.test.ts +49 -0
  363. package/src/lifecycle/change-set.ts +67 -0
  364. package/src/lifecycle/converge-ledger.ts +1 -1
  365. package/src/lifecycle/deep-diff.test.ts +267 -14
  366. package/src/lifecycle/deep-diff.ts +217 -14
  367. package/src/lifecycle/deep-observe.test.ts +3 -1
  368. package/src/lifecycle/deep-observe.ts +16 -6
  369. package/src/lifecycle/gate-ledger.test.ts +74 -2
  370. package/src/lifecycle/gate-ledger.ts +143 -36
  371. package/src/lifecycle/index.ts +1 -0
  372. package/src/lifecycle/release-ledger.ts +2 -2
  373. package/src/lifecycle/run-ledger.test.ts +268 -0
  374. package/src/lifecycle/run-ledger.ts +201 -0
  375. package/src/lifecycle/symptoms.ts +3 -3
  376. package/src/lint/config.test.ts +82 -1
  377. package/src/lint/config.ts +112 -3
  378. package/src/lint/policy.test.ts +1 -1
  379. package/src/lint/post-synth.test.ts +31 -0
  380. package/src/lint/post-synth.ts +54 -1
  381. package/src/lint/rules/__fixtures__/comp/comp004/pass/neo4j-cluster.component.ts +2 -2
  382. package/src/lint/rules/__fixtures__/comp/comp004/pass/orders-table.component.ts +1 -1
  383. package/src/lint/rules/comp/comp.test.ts +1 -1
  384. package/src/lint/rules/comp/comp004-gate-needs-durable-runtime.ts +74 -0
  385. package/src/lint/rules/comp/index.ts +3 -3
  386. package/src/lint/rules/op/index.ts +23 -0
  387. package/src/lint/rules/op/ops012-activity-contract.test.ts +161 -0
  388. package/src/lint/rules/op/ops012-activity-contract.ts +64 -0
  389. package/src/lint/rules/op/ops013-step-output-ref.test.ts +237 -0
  390. package/src/lint/rules/op/ops013-step-output-ref.ts +98 -0
  391. package/src/lint/rules/op/ops014-converge-rule-refusals.test.ts +263 -0
  392. package/src/lint/rules/op/ops014-converge-rule-refusals.ts +179 -0
  393. package/src/lint/rules/op/support.ts +11 -0
  394. package/src/lint/suppressions.test.ts +163 -0
  395. package/src/lint/suppressions.ts +204 -0
  396. package/src/live-endpoint.ts +1 -1
  397. package/src/op/activities/activity-contracts.ts +66 -0
  398. package/src/op/activities/apply.test.ts +567 -0
  399. package/src/op/activities/apply.ts +662 -0
  400. package/src/op/activities/build.ts +26 -0
  401. package/src/op/activities/converge.test.ts +330 -0
  402. package/src/op/activities/converge.ts +449 -0
  403. package/src/op/activities/env-teardown.test.ts +187 -0
  404. package/src/op/activities/env-teardown.ts +171 -0
  405. package/src/op/activities/guard-validate.test.ts +139 -0
  406. package/src/op/activities/guard-validate.ts +223 -0
  407. package/src/op/activities/http-check.test.ts +48 -0
  408. package/src/op/activities/http-check.ts +55 -0
  409. package/src/op/activities/index.ts +119 -0
  410. package/src/op/activities/lexicon-upgrade.test.ts +492 -0
  411. package/src/op/activities/lexicon-upgrade.ts +673 -0
  412. package/src/op/activities/lifecycle.ts +85 -0
  413. package/src/op/activities/pipeline-audit.test.ts +67 -0
  414. package/src/op/activities/pipeline-audit.ts +201 -0
  415. package/src/op/activities/policy.test.ts +19 -0
  416. package/src/op/activities/policy.ts +37 -0
  417. package/src/op/activities/reconcile.test.ts +81 -0
  418. package/src/op/activities/reconcile.ts +187 -0
  419. package/src/op/activities/shell.ts +26 -0
  420. package/src/op/activities/teardown.ts +22 -0
  421. package/src/op/activities/util.ts +6 -0
  422. package/src/op/activities/wait.ts +69 -0
  423. package/src/op/activities/workflow-audit.test.ts +90 -0
  424. package/src/op/activities/workflow-audit.ts +203 -0
  425. package/src/op/activity-contract-registry.test.ts +98 -0
  426. package/src/op/activity-contract-registry.ts +114 -0
  427. package/src/op/activity-contract.test.ts +1 -1
  428. package/src/op/activity-contract.ts +37 -33
  429. package/src/op/activity-failure.ts +34 -0
  430. package/src/op/activity-profiles.test.ts +56 -0
  431. package/src/op/activity-profiles.ts +116 -0
  432. package/src/op/activity-registry.test.ts +37 -28
  433. package/src/op/activity-registry.ts +27 -56
  434. package/src/op/activity-runtime.ts +3 -47
  435. package/src/op/builders-exports.test.ts +0 -49
  436. package/src/op/builders-typed.test.ts +164 -0
  437. package/src/op/builders.ts +123 -39
  438. package/src/op/composites/apply-op.ts +208 -0
  439. package/src/op/composites/composites.test.ts +366 -0
  440. package/src/op/composites/converge-op.test.ts +168 -0
  441. package/src/op/composites/converge-op.ts +214 -0
  442. package/src/op/composites/index.ts +26 -0
  443. package/src/op/composites/lexicon-upgrade-op.test.ts +68 -0
  444. package/src/op/composites/lexicon-upgrade-op.ts +134 -0
  445. package/src/op/composites/pipeline-audit-op.test.ts +25 -0
  446. package/src/op/composites/pipeline-audit-op.ts +78 -0
  447. package/src/op/composites/reconcile-op.test.ts +51 -0
  448. package/src/op/composites/reconcile-op.ts +107 -0
  449. package/src/op/composites/watch-op.ts +108 -0
  450. package/src/op/composites/workflow-audit-op.test.ts +25 -0
  451. package/src/op/composites/workflow-audit-op.ts +88 -0
  452. package/src/op/converge-rule.ts +12 -10
  453. package/src/op/cron.test.ts +128 -0
  454. package/src/op/cron.ts +149 -0
  455. package/src/op/discover-root.test.ts +105 -1
  456. package/src/op/discover.test.ts +6 -2
  457. package/src/op/discover.ts +84 -16
  458. package/src/op/duration.ts +21 -0
  459. package/src/op/effect-step.test.ts +26 -11
  460. package/src/op/emulator-lifecycle.ts +1 -2
  461. package/src/op/gate.ts +155 -0
  462. package/src/op/generate-pipeline.test.ts +65 -3
  463. package/src/op/generate-pipeline.ts +19 -2
  464. package/src/op/index.ts +39 -6
  465. package/src/op/local-executor.test.ts +163 -34
  466. package/src/op/local-executor.ts +370 -102
  467. package/src/op/local-output.test.ts +95 -11
  468. package/src/op/local-output.ts +33 -4
  469. package/src/op/op-ir.test.ts +281 -0
  470. package/src/op/op-ir.ts +417 -0
  471. package/src/op/op-verb-class.ts +19 -5
  472. package/src/op/op.test.ts +26 -5
  473. package/src/op/operator.test.ts +175 -6
  474. package/src/op/operator.ts +63 -16
  475. package/src/op/receipt-store.ts +1 -1
  476. package/src/op/resource.ts +20 -2
  477. package/src/op/runtime.ts +203 -0
  478. package/src/op/runtimes/local.test.ts +181 -0
  479. package/src/op/runtimes/local.ts +212 -0
  480. package/src/op/step-output-ref.ts +15 -13
  481. package/src/op/types.ts +84 -24
  482. package/src/ownership.ts +1 -1
  483. package/src/provenance.test.ts +6 -0
  484. package/src/provenance.ts +21 -1
  485. package/src/serializer-walker.test.ts +42 -0
  486. package/src/serializer-walker.ts +21 -2
  487. package/dist/cli/handlers/op-progress.d.ts +0 -90
  488. package/dist/cli/handlers/op-progress.d.ts.map +0 -1
  489. package/dist/cli/handlers/run-client.d.ts +0 -127
  490. package/dist/cli/handlers/run-client.d.ts.map +0 -1
  491. package/dist/cli/handlers/run-report.d.ts +0 -5
  492. package/dist/cli/handlers/run-report.d.ts.map +0 -1
  493. package/dist/components/temporal-codegen-loader.d.ts +0 -27
  494. package/dist/components/temporal-codegen-loader.d.ts.map +0 -1
  495. package/dist/lint/rules/comp/comp004-gate-needs-temporal.d.ts +0 -37
  496. package/dist/lint/rules/comp/comp004-gate-needs-temporal.d.ts.map +0 -1
  497. package/src/cli/handlers/op-progress.test.ts +0 -268
  498. package/src/cli/handlers/op-progress.ts +0 -248
  499. package/src/cli/handlers/run-client.test.ts +0 -82
  500. package/src/cli/handlers/run-client.ts +0 -219
  501. package/src/cli/handlers/run-report.test.ts +0 -62
  502. package/src/cli/handlers/run-report.ts +0 -122
  503. package/src/components/temporal-codegen-loader.ts +0 -41
  504. package/src/lint/rules/comp/comp004-gate-needs-temporal.ts +0 -66
@@ -0,0 +1,171 @@
1
+ /**
2
+ * `envTeardown` — the durable form of `chant lifecycle teardown <env> --yes`
3
+ * (#1222).
4
+ *
5
+ * Wraps core's {@link executeTeardown}: enumerate the live resources whose
6
+ * ownership marker carries this project's `ownership.stack` + the requested
7
+ * environment, delete them through each lexicon's `executeTeardown`
8
+ * capability, run core's one bounded retry pass, and fail the activity loudly
9
+ * when any candidate is still `failed` afterwards. Selection is marker-scoped
10
+ * by construction — the same engine the CLI verb drives, reached in-process
11
+ * rather than through a shell.
12
+ *
13
+ * The guards are the CLI's guards, unchanged:
14
+ * - an environment the project does not declare is refused;
15
+ * - a project with no `ownership.stack` is refused — teardown has nothing to
16
+ * select on;
17
+ * - a production-like environment name (`prod`, `production`, `prod-eu`, ...)
18
+ * is refused unless the step's args carry an explicit `confirmProd: true`.
19
+ * An Op is non-interactive, so there is no re-type-the-name prompt to fall
20
+ * back to — the confirmation must be authored into the Op.
21
+ *
22
+ * All three refusals throw before any live read, so a refused teardown
23
+ * touches nothing at all. They are configuration errors, not transient ones —
24
+ * no retry can make `prod` stop looking like production.
25
+ *
26
+ * Distinct from {@link chantTeardown} (`npm run teardown`), which runs
27
+ * whatever script a project wired under that name. This activity is
28
+ * product-agnostic: it imports core only, and core reaches each product
29
+ * through the lexicons the project configures.
30
+ */
31
+
32
+ import { resolve } from "node:path";
33
+ import { loadChantConfig, resolveOwnershipStack, type ChantConfig } from "../../config";
34
+ import { unknownEnvError, isProdLikeEnvironment } from "../../env";
35
+ import { applyLiveEndpoint } from "../../live-endpoint";
36
+ import { executeTeardown, type TeardownReport } from "../../lifecycle/teardown";
37
+ import type { ObservationLexicon } from "../../lexicon";
38
+
39
+ export interface EnvTeardownArgs {
40
+ /** The environment to tear down — the marker env core selects on. */
41
+ env: string;
42
+ /**
43
+ * Required (as an explicit `true`) when `env` looks production-like.
44
+ * The Op's counterpart of the CLI's `--confirm-prod`: activities never
45
+ * prompt, so the confirmation is authored, reviewed, and versioned with
46
+ * the Op instead.
47
+ */
48
+ confirmProd?: boolean;
49
+ /** Path to the chant project (where chant.config.ts lives). Default: the chant process's cwd. */
50
+ path?: string;
51
+ }
52
+
53
+ /** What a completed (fully successful) teardown reports back to the run. */
54
+ export interface EnvTeardownResult {
55
+ environment: string;
56
+ /** The ownership stack everything was selected on. */
57
+ stack: string;
58
+ deleted: number;
59
+ /** Deliberately refused by a lexicon, with the reason in the report. */
60
+ notPrunable: number;
61
+ /** Planned candidates whose lexicon implements no execution yet. */
62
+ skipped: number;
63
+ /** Kinds that could not be read (#1089) — the plan was incomplete, not clean. */
64
+ holes: number;
65
+ /** The full per-candidate report core produced. */
66
+ report: TeardownReport;
67
+ }
68
+
69
+ /** Injectable seams for testing — production loads both from the project. */
70
+ export interface EnvTeardownDeps {
71
+ /** Pre-loaded project config (skips reading chant.config.ts). */
72
+ config?: ChantConfig;
73
+ /** Pre-loaded lexicon plugins (skips resolving + importing lexicon packages). */
74
+ plugins?: ObservationLexicon[];
75
+ }
76
+
77
+ /**
78
+ * Load the project's lexicon plugins through core's own resolution: the
79
+ * `lexicons` list in chant.config, falling back to source-file detection.
80
+ * A dynamic import so this module does not pull the whole CLI surface in at
81
+ * load time — the registry imports every activity module eagerly.
82
+ */
83
+ async function loadProjectPlugins(projectPath: string): Promise<ObservationLexicon[]> {
84
+ const { loadPlugins, resolveProjectLexicons } = await import("../../cli/plugins");
85
+ return loadPlugins(await resolveProjectLexicons(projectPath));
86
+ }
87
+
88
+ /**
89
+ * Tear down one environment's marker-owned resources through the core
90
+ * teardown engine. Uses the `longInfra` profile — deletion waits on the
91
+ * target's own pace. Safe to retry: the engine re-plans from live markers on
92
+ * every run, and a resource already gone is simply no longer a candidate.
93
+ */
94
+ export async function envTeardown(
95
+ args: EnvTeardownArgs,
96
+ _signal?: AbortSignal,
97
+ deps?: EnvTeardownDeps,
98
+ ): Promise<EnvTeardownResult> {
99
+ const projectPath = resolve(args.path ?? ".");
100
+ const config = deps?.config ?? (await loadChantConfig(projectPath)).config;
101
+
102
+ // Refuse an env the project does not declare — a typo here is the
103
+ // difference between tearing down `dev` and tearing down `prod`.
104
+ const envErr = unknownEnvError(args.env, config.environments);
105
+ if (envErr) throw new Error(`envTeardown: ${envErr}`);
106
+
107
+ // Teardown selects on the ownership marker; a project that stamps none has
108
+ // nothing to key on, and "delete what looks like mine" is not a fallback.
109
+ const stack = resolveOwnershipStack(config);
110
+ if (stack === undefined) {
111
+ throw new Error(
112
+ "envTeardown: this project declares no ownership.stack — teardown is marker-scoped and has nothing to select on. " +
113
+ 'Set `ownership: { stack: "<name>" }` in chant.config.ts and deploy, so resources carry the marker teardown keys on.',
114
+ );
115
+ }
116
+
117
+ // The prod guard (#1222). Checked before any live read, so a refused
118
+ // teardown touches nothing at all.
119
+ if (isProdLikeEnvironment(args.env) && args.confirmProd !== true) {
120
+ throw new Error(
121
+ `envTeardown: "${args.env}" looks like a production environment — ` +
122
+ "add `confirmProd: true` to the envTeardown step's args to tear it down from an Op.",
123
+ );
124
+ }
125
+
126
+ const plugins = deps?.plugins ?? (await loadProjectPlugins(projectPath));
127
+
128
+ // #1166 — teardown is a live read and a live write, so an environment's
129
+ // declared endpoint applies here too, unless the ambient shell already set it.
130
+ const reading = plugins.filter((p) => p.teardownOwned || p.describeResources || p.executeTeardown);
131
+ const endpoint = applyLiveEndpoint(config.environments, args.env, reading);
132
+ if (endpoint.notice) console.log(endpoint.notice);
133
+
134
+ let report: TeardownReport;
135
+ try {
136
+ report = await executeTeardown({ environment: args.env, stack, plugins });
137
+ } finally {
138
+ endpoint.restore();
139
+ }
140
+
141
+ const count = (outcome: string) => report.outcomes.filter((o) => o.outcome === outcome).length;
142
+ const deleted = count("deleted");
143
+ const failed = report.outcomes.filter((o) => o.outcome === "failed");
144
+ const notPrunable = count("not-prunable");
145
+ const skipped = count("skipped");
146
+
147
+ console.log(
148
+ `envTeardown ${args.env} (stack ${stack}): ${deleted} deleted, ${failed.length} failed, ` +
149
+ `${notPrunable} not-prunable, ${skipped} skipped, ${report.plan.holes.length} hole(s)`,
150
+ );
151
+
152
+ // Failures that survived core's bounded retry pass fail the activity —
153
+ // silence is never success. A failed step is what the executor aborts the
154
+ // phase on and what the Op's `onFailure` compensation runs for.
155
+ if (failed.length > 0) {
156
+ const names = failed.map((o) => `${o.lexicon}/${o.name}${o.detail ? ` (${o.detail})` : ""}`).join(", ");
157
+ throw new Error(
158
+ `envTeardown: ${failed.length} of ${report.outcomes.length} candidate(s) still failed after the retry pass: ${names}`,
159
+ );
160
+ }
161
+
162
+ return {
163
+ environment: args.env,
164
+ stack,
165
+ deleted,
166
+ notPrunable,
167
+ skipped,
168
+ holes: report.plan.holes.length,
169
+ report,
170
+ };
171
+ }
@@ -0,0 +1,139 @@
1
+ import { describe, test, expect, vi, beforeEach } from "vitest";
2
+ import { resolve } from "node:path";
3
+ import type { NonRetryableActivityError } from "../activity-failure";
4
+
5
+ // guardValidate reaches cfn-guard through the runtime adapter's spawn (not
6
+ // node:child_process), so the I/O seam is the runtime-adapter module — same
7
+ // pattern `lexicons/aws/src/import/live-export-io.test.ts` uses for the aws
8
+ // plugin's own spawn calls. Partial mock (`importOriginal`) rather than a
9
+ // full replacement, for the same reason that test gives: the module is
10
+ // reachable from other real exports this file's own imports touch
11
+ // transitively, so replacing it wholesale would break more than `spawn`.
12
+ const spawnMock = vi.fn();
13
+ vi.mock("../../runtime-adapter", async (importOriginal) => {
14
+ const actual = await importOriginal<typeof import("../../runtime-adapter")>();
15
+ return { ...actual, getRuntime: () => ({ ...actual.getRuntime(), spawn: spawnMock }) };
16
+ });
17
+
18
+ import { guardValidate, parseGuardFindings } from "./guard-validate";
19
+
20
+ const CLEAN = JSON.stringify([{ name: "template.json", not_compliant: [], compliant: [{ Rule: { name: "s3_bucket_ok" } }] }]);
21
+
22
+ const ONE_VIOLATION = JSON.stringify([
23
+ {
24
+ name: "template.json",
25
+ not_compliant: [
26
+ {
27
+ Rule: {
28
+ name: "s3_bucket_public_read_prohibited",
29
+ messages: { error: "PublicAccessBlockConfiguration is missing" },
30
+ checks: [{ Clause: { Unary: { check: { UnResolved: { path: "/Resources/MyBucket/Properties/PublicAccessBlockConfiguration" } } } } }],
31
+ },
32
+ },
33
+ ],
34
+ compliant: [],
35
+ },
36
+ ]);
37
+
38
+ describe("guardValidate activity (#522)", () => {
39
+ beforeEach(() => spawnMock.mockReset());
40
+
41
+ test("spawns cfn-guard validate with -r/-d/--output-format json; template defaults to <path>/template.json", async () => {
42
+ spawnMock.mockResolvedValue({ stdout: CLEAN, stderr: "", exitCode: 0 });
43
+ await guardValidate({ rules: "rules.guard", path: "proj" });
44
+ expect(spawnMock).toHaveBeenCalledTimes(1);
45
+ const argv = spawnMock.mock.calls[0][0] as string[];
46
+ expect(argv).toEqual([
47
+ "cfn-guard", "validate",
48
+ "-r", "rules.guard",
49
+ "-d", resolve("proj", "template.json"),
50
+ "--output-format", "json",
51
+ ]);
52
+ });
53
+
54
+ test("an explicit template/binary override both take", async () => {
55
+ spawnMock.mockResolvedValue({ stdout: CLEAN, stderr: "", exitCode: 0 });
56
+ await guardValidate({ rules: "rules.guard", template: "dist/out.json", binary: "/opt/bin/cfn-guard" });
57
+ const argv = spawnMock.mock.calls[0][0] as string[];
58
+ expect(argv).toEqual(["/opt/bin/cfn-guard", "validate", "-r", "rules.guard", "-d", "dist/out.json", "--output-format", "json"]);
59
+ });
60
+
61
+ test("a clean run resolves with no findings", async () => {
62
+ spawnMock.mockResolvedValue({ stdout: CLEAN, stderr: "", exitCode: 0 });
63
+ await expect(guardValidate({ rules: "rules.guard" })).resolves.toEqual({
64
+ findings: [],
65
+ summary: expect.stringContaining("compliant"),
66
+ });
67
+ });
68
+
69
+ test("a violation throws (non-retryable) and names the rule and the entity", async () => {
70
+ spawnMock.mockResolvedValue({ stdout: ONE_VIOLATION, stderr: "", exitCode: 5 });
71
+ await expect(guardValidate({ rules: "rules.guard" })).rejects.toThrow(
72
+ /s3_bucket_public_read_prohibited.*MyBucket/s,
73
+ );
74
+ });
75
+
76
+ test("the thrown failure is a non-retryable activity failure typed GuardViolation — same shape policyGate uses for PolicyViolation", async () => {
77
+ spawnMock.mockResolvedValue({ stdout: ONE_VIOLATION, stderr: "", exitCode: 5 });
78
+ try {
79
+ await guardValidate({ rules: "rules.guard" });
80
+ expect.unreachable("guardValidate should have thrown");
81
+ } catch (err) {
82
+ expect((err as NonRetryableActivityError).type).toBe("GuardViolation");
83
+ expect((err as NonRetryableActivityError).nonRetryable).toBe(true);
84
+ }
85
+ });
86
+
87
+ test("cfn-guard producing no output at all (binary missing / failed to run) is a setup error, not a clean pass", async () => {
88
+ spawnMock.mockResolvedValue({ stdout: "", stderr: "spawn cfn-guard ENOENT", exitCode: 1 });
89
+ await expect(guardValidate({ rules: "rules.guard" })).rejects.toThrow(/cfn-guard.*no output/i);
90
+ });
91
+
92
+ test("onFinding other than \"report\" is refused rather than silently ignored", async () => {
93
+ // @ts-expect-error — the type only admits "report"; a dynamically loaded
94
+ // Op config could still pass through an unsupported value at runtime.
95
+ await expect(guardValidate({ rules: "rules.guard", onFinding: "issue" })).rejects.toThrow(
96
+ /onFinding "issue" is not implemented/,
97
+ );
98
+ expect(spawnMock).not.toHaveBeenCalled();
99
+ });
100
+ });
101
+
102
+ describe("parseGuardFindings (#522 — pure mapping, no spawn)", () => {
103
+ test("no not_compliant entries → no findings", () => {
104
+ expect(parseGuardFindings(CLEAN)).toEqual([]);
105
+ });
106
+
107
+ test("maps a not_compliant entry to a finding with rule, message, and entity", () => {
108
+ const findings = parseGuardFindings(ONE_VIOLATION);
109
+ expect(findings).toHaveLength(1);
110
+ expect(findings[0]).toMatchObject({
111
+ rule: "s3_bucket_public_read_prohibited",
112
+ severity: "error",
113
+ entity: "MyBucket",
114
+ });
115
+ expect(findings[0].message).toContain("PublicAccessBlockConfiguration is missing");
116
+ });
117
+
118
+ test("a single (non-array) file report is accepted", () => {
119
+ const single = JSON.stringify({ name: "template.json", not_compliant: [{ Rule: { name: "r1" } }] });
120
+ expect(parseGuardFindings(single)).toEqual([{ rule: "r1", severity: "error", message: 'cfn-guard rule "r1" violated' }]);
121
+ });
122
+
123
+ test("a rule violation with no recognizable message field still produces a finding, not a silent drop", () => {
124
+ const noMessage = JSON.stringify([{ not_compliant: [{ Rule: { name: "r2" } }] }]);
125
+ expect(parseGuardFindings(noMessage)).toEqual([{ rule: "r2", severity: "error", message: 'cfn-guard rule "r2" violated' }]);
126
+ });
127
+
128
+ test("multiple violations across multiple file reports are all collected", () => {
129
+ const multi = JSON.stringify([
130
+ { name: "a.json", not_compliant: [{ Rule: { name: "r1" } }] },
131
+ { name: "b.json", not_compliant: [{ Rule: { name: "r2" } }, { Rule: { name: "r3" } }] },
132
+ ]);
133
+ expect(parseGuardFindings(multi).map((f) => f.rule)).toEqual(["r1", "r2", "r3"]);
134
+ });
135
+
136
+ test("non-JSON stdout throws a clear setup error rather than reporting zero violations", () => {
137
+ expect(() => parseGuardFindings("cfn-guard: error: rules file not found")).toThrow(/did not return parseable JSON/);
138
+ });
139
+ });
@@ -0,0 +1,223 @@
1
+ /**
2
+ * guardValidate — chant #522: reuse an existing CloudFormation Guard rules
3
+ * pack as a first-class Op step, instead of reimplementing compliance packs
4
+ * as native `WAW` rules.
5
+ *
6
+ * chant already emits standard CloudFormation `template.json` (no
7
+ * chant-specific translation), so an org's existing `.guard` files run
8
+ * against it unchanged: this activity is a thin shell around
9
+ * `cfn-guard validate -r <rules> -d <template> --output-format json`, run
10
+ * through the runtime adapter's `spawn` (never `node:child_process`
11
+ * directly — see `lexicons/aws/src/plugin.ts`'s `exportResources` for the
12
+ * same seam, and `../../import/live-export-io.test.ts` for how a test mocks
13
+ * it). `cfn-guard` is an operational dependency, not a `chant build`
14
+ * dependency — nothing here runs inside the pure core.
15
+ *
16
+ * Mirrors `./policy.ts`'s `policyGate`: `report` is the only finding-mode
17
+ * today (the type only admits that one value; `issue`/`pull-request` modes
18
+ * are a follow-up, chant #522), and an error-severity finding throws
19
+ * (a non-retryable activity failure) so the run exits non-zero — the same gate
20
+ * CI relies on to fail the pipeline — and the same activity blocks an `ApplyOp`
21
+ * when placed before it.
22
+ */
23
+ import { resolve } from "node:path";
24
+ import { nonRetryableFailure } from "../activity-failure";
25
+
26
+ export interface GuardValidateArgs {
27
+ /**
28
+ * Path to a `.guard` rules file or directory, passed to `cfn-guard validate
29
+ * -r`. No chant-specific rule format — these are ordinary cfn-guard rules,
30
+ * the same ones CDK's `CfnGuardValidator` would run.
31
+ */
32
+ rules: string;
33
+ /**
34
+ * Path to the built CloudFormation template to validate. Default:
35
+ * `<path>/template.json` — the convention `chant build -o template.json`
36
+ * writes to (see the aws lexicon's own examples and the native-output
37
+ * policy docs).
38
+ */
39
+ template?: string;
40
+ /** Project directory `template` is resolved relative to, when `template` is not given. Default ".". */
41
+ path?: string;
42
+ /**
43
+ * What to produce on a finding. Only `"report"` runs today — it prints a
44
+ * summary and throws on any finding, no external service involved.
45
+ * `issue`/`pull-request` modes (reusing the audit-Op finding-mode
46
+ * plumbing) are left to a follow-up. Default: `"report"`.
47
+ */
48
+ onFinding?: "report";
49
+ /** Path to (or name of) the `cfn-guard` binary. Default: `"cfn-guard"`, resolved via `PATH`. */
50
+ binary?: string;
51
+ }
52
+
53
+ /** One cfn-guard rule violation, mapped to a chant-shaped diagnostic. */
54
+ export interface GuardFinding {
55
+ /** The cfn-guard rule name that failed. */
56
+ rule: string;
57
+ /** Always `"error"` today — a cfn-guard non-compliant result is a policy violation, the same weight `policyGate` gives a violation. */
58
+ severity: "error";
59
+ /** Human-readable violation message. */
60
+ message: string;
61
+ /** Best-effort CloudFormation logical id the violation is about, when cfn-guard's output names one. */
62
+ entity?: string;
63
+ }
64
+
65
+ export interface GuardValidateResult {
66
+ findings: GuardFinding[];
67
+ summary: string;
68
+ }
69
+
70
+ type JsonValue = unknown;
71
+
72
+ /**
73
+ * Recursively collect message-shaped string values anywhere inside a parsed
74
+ * cfn-guard `not_compliant` entry. cfn-guard's exact JSON nesting for a
75
+ * violation's human message has shifted across versions (Unary/Clause/Check
76
+ * wrappers, `messages.custom_message` vs `messages.error`, …); rather than
77
+ * betting on one fixed path that a version bump silently breaks, this walks
78
+ * the whole entry and takes every message-shaped string it finds — the same
79
+ * "degrade, don't false-negative" instinct `defaultGitlabRefResolver`
80
+ * (`./pipeline-audit.ts`) uses for a resolver failure.
81
+ */
82
+ function collectMessages(value: JsonValue, out: string[] = [], seen = new Set<string>()): string[] {
83
+ if (typeof value !== "object" || value === null) return out;
84
+ if (Array.isArray(value)) {
85
+ for (const v of value) collectMessages(v, out, seen);
86
+ return out;
87
+ }
88
+ for (const [key, v] of Object.entries(value as Record<string, unknown>)) {
89
+ if (typeof v === "string" && /^(message|custom_message|error)$/i.test(key) && v.trim() && !seen.has(v)) {
90
+ seen.add(v);
91
+ out.push(v);
92
+ } else {
93
+ collectMessages(v, out, seen);
94
+ }
95
+ }
96
+ return out;
97
+ }
98
+
99
+ /** Collect every string value found under a `*path*`-shaped key, anywhere in the entry. */
100
+ function collectPaths(value: JsonValue, out: string[] = []): string[] {
101
+ if (typeof value !== "object" || value === null) return out;
102
+ if (Array.isArray(value)) {
103
+ for (const v of value) collectPaths(v, out);
104
+ return out;
105
+ }
106
+ for (const [key, v] of Object.entries(value as Record<string, unknown>)) {
107
+ if (typeof v === "string" && /path/i.test(key)) out.push(v);
108
+ else collectPaths(v, out);
109
+ }
110
+ return out;
111
+ }
112
+
113
+ /**
114
+ * Best-effort CloudFormation logical id for a violation: cfn-guard reports
115
+ * the offending property as a template path such as
116
+ * `/Resources/MyBucket/Properties/...`. Pull the segment right after
117
+ * `Resources` from the first path-shaped string found, if any.
118
+ */
119
+ function findEntity(value: JsonValue): string | undefined {
120
+ for (const p of collectPaths(value)) {
121
+ const m = /Resources[./]([A-Za-z0-9]+)/.exec(p);
122
+ if (m) return m[1];
123
+ }
124
+ return undefined;
125
+ }
126
+
127
+ /**
128
+ * Map cfn-guard's `--output-format json` report to chant-shaped findings.
129
+ * Exported and tested directly (independent of `spawn`) — same split
130
+ * `collectPipelineRefs`/`pipelineSupplyChainAudit` (`./pipeline-audit.ts`)
131
+ * use between pure mapping and the orchestrating activity.
132
+ *
133
+ * Throws if `stdout` is not parseable JSON — a cfn-guard invocation that
134
+ * didn't run with `--output-format json` (a bad rules path, a version too
135
+ * old to support it) is a setup problem, not "zero violations"; reporting
136
+ * a clean run here would be a false negative.
137
+ */
138
+ export function parseGuardFindings(stdout: string): GuardFinding[] {
139
+ let parsed: unknown;
140
+ try {
141
+ parsed = JSON.parse(stdout);
142
+ } catch (err) {
143
+ throw new Error(
144
+ `cfn-guard did not return parseable JSON (guardValidate always passes --output-format json) — ` +
145
+ `${err instanceof Error ? err.message : String(err)}\nraw output:\n${stdout}`,
146
+ );
147
+ }
148
+
149
+ const fileReports = Array.isArray(parsed) ? parsed : [parsed];
150
+ const findings: GuardFinding[] = [];
151
+ for (const report of fileReports) {
152
+ const notCompliant = (report as { not_compliant?: unknown[] } | null)?.not_compliant ?? [];
153
+ for (const entry of notCompliant) {
154
+ const rule =
155
+ (entry as { Rule?: { name?: string }; name?: string } | null)?.Rule?.name ??
156
+ (entry as { name?: string } | null)?.name ??
157
+ "unknown-rule";
158
+ const messages = collectMessages(entry);
159
+ const message = messages.length > 0 ? `cfn-guard rule "${rule}" violated: ${messages.join("; ")}` : `cfn-guard rule "${rule}" violated`;
160
+ const entity = findEntity(entry);
161
+ findings.push({ rule, severity: "error", message, ...(entity ? { entity } : {}) });
162
+ }
163
+ }
164
+ return findings;
165
+ }
166
+
167
+ function renderSummary(findings: GuardFinding[], template: string, rules: string): string {
168
+ if (findings.length === 0) return `## cfn-guard\n\n${template} is compliant with ${rules}.\n`;
169
+ let out = `## cfn-guard\n\n${findings.length} violation(s) in ${template} against ${rules}:\n\n`;
170
+ out += "| Rule | Entity | Message |\n|---|---|---|\n";
171
+ for (const f of findings) out += `| ${f.rule} | ${f.entity ?? ""} | ${f.message} |\n`;
172
+ return out;
173
+ }
174
+
175
+ /**
176
+ * Run an external CloudFormation Guard rules pack against the project's
177
+ * built CloudFormation template. `report` mode: prints a summary, and
178
+ * throws (non-retryable) when any rule is violated, so the local executor
179
+ * (`chant run`) exits non-zero and CI fails the pipeline — the same shape
180
+ * `policyGate` uses to block an apply. A clean run passes through.
181
+ *
182
+ * A plain activity on every runtime, not a `gate` step.
183
+ */
184
+ export async function guardValidate(args: GuardValidateArgs, _signal?: AbortSignal): Promise<GuardValidateResult> {
185
+ const mode = args.onFinding ?? "report";
186
+ if (mode !== "report") {
187
+ throw new Error(
188
+ `guardValidate: onFinding "${mode}" is not implemented — only "report" runs today ` +
189
+ `(chant #522 leaves issue/pull-request modes to a follow-up).`,
190
+ );
191
+ }
192
+
193
+ const projectPath = args.path ?? ".";
194
+ const template = args.template ?? resolve(projectPath, "template.json");
195
+ const binary = args.binary ?? "cfn-guard";
196
+
197
+ const { getRuntime } = await import("../../runtime-adapter");
198
+ const rt = getRuntime();
199
+ const result = await rt.spawn([binary, "validate", "-r", args.rules, "-d", template, "--output-format", "json"]);
200
+
201
+ if (result.stdout.trim() === "" && result.stderr.trim() !== "") {
202
+ throw new Error(
203
+ `cfn-guard ("${binary}") produced no output — ${result.stderr.trim()}\n` +
204
+ `Install cfn-guard (https://github.com/aws-cloudformation/cloudformation-guard) or pass { binary } ` +
205
+ `to point at an existing install.`,
206
+ );
207
+ }
208
+
209
+ const findings = parseGuardFindings(result.stdout);
210
+ const summary = renderSummary(findings, template, args.rules);
211
+
212
+ if (findings.length > 0) {
213
+ console.log(summary);
214
+ const detail = findings.map((f) => `[${f.rule}]${f.entity ? ` ${f.entity}:` : ""} ${f.message}`).join("; ");
215
+ throw nonRetryableFailure(
216
+ `cfn-guard blocked the build — ${findings.length} violation(s) against ${args.rules}: ${detail}`,
217
+ "GuardViolation",
218
+ );
219
+ }
220
+
221
+ console.log(`[guard] ${template} is compliant with ${args.rules}`);
222
+ return { findings, summary };
223
+ }
@@ -0,0 +1,48 @@
1
+ import { describe, test, expect } from "vitest";
2
+ import { httpCheck, statusOk, type HttpFetch } from "./http-check";
3
+
4
+ const res = (status: number, body = "") => ({ status, text: async () => body });
5
+
6
+ describe("statusOk (#typed-verify)", () => {
7
+ test("any 2xx when no expectation, exact match otherwise", () => {
8
+ expect(statusOk(200)).toBe(true);
9
+ expect(statusOk(204)).toBe(true);
10
+ expect(statusOk(404)).toBe(false);
11
+ expect(statusOk(404, 404)).toBe(true);
12
+ expect(statusOk(200, 201)).toBe(false);
13
+ });
14
+ });
15
+
16
+ describe("httpCheck (#typed-verify)", () => {
17
+ test("passes on 2xx and returns the status", async () => {
18
+ const fetchFn: HttpFetch = async () => res(200);
19
+ expect(await httpCheck({ url: "http://x" }, undefined, fetchFn)).toEqual({ status: 200 });
20
+ });
21
+
22
+ test("throws when the status doesn't match", async () => {
23
+ const fetchFn: HttpFetch = async () => res(500, "boom");
24
+ await expect(httpCheck({ url: "http://x" }, undefined, fetchFn)).rejects.toThrow(/status 500 \(want 2xx\)/);
25
+ });
26
+
27
+ test("checks a body substring when `contains` is set", async () => {
28
+ const fetchFn: HttpFetch = async () => res(200, '{"name":"trio-bucket"}');
29
+ expect(await httpCheck({ url: "http://x", contains: "trio-bucket" }, undefined, fetchFn)).toEqual({ status: 200 });
30
+ await expect(
31
+ httpCheck({ url: "http://x", contains: "missing" }, undefined, fetchFn),
32
+ ).rejects.toThrow(/body missing "missing"/);
33
+ });
34
+
35
+ test("retries then succeeds", async () => {
36
+ let n = 0;
37
+ const fetchFn: HttpFetch = async () => (++n < 3 ? res(503) : res(200));
38
+ expect(await httpCheck({ url: "http://x", retries: 3, intervalMs: 1 }, undefined, fetchFn)).toEqual({ status: 200 });
39
+ expect(n).toBe(3);
40
+ });
41
+
42
+ test("surfaces a fetch (connection) error", async () => {
43
+ const fetchFn: HttpFetch = async () => {
44
+ throw new Error("ECONNREFUSED");
45
+ };
46
+ await expect(httpCheck({ url: "http://x" }, undefined, fetchFn)).rejects.toThrow(/ECONNREFUSED/);
47
+ });
48
+ });
@@ -0,0 +1,55 @@
1
+ import { sleep } from "../activity-runtime";
2
+
3
+ export interface HttpCheckArgs {
4
+ /** URL to GET (or `method`). */
5
+ url: string;
6
+ /** HTTP method. Default: `GET`. */
7
+ method?: string;
8
+ /** Exact expected status. Default: any 2xx. */
9
+ status?: number;
10
+ /** Substring the response body must contain. */
11
+ contains?: string;
12
+ /** Retries before failing, for a resource that may lag. Default: `0`. */
13
+ retries?: number;
14
+ /** Delay between retries in ms. Default: `1000`. */
15
+ intervalMs?: number;
16
+ }
17
+
18
+ /** Injectable fetch — the response shape `httpCheck` needs. Defaults to global `fetch`. */
19
+ export type HttpFetch = (url: string, init?: { method?: string; signal?: AbortSignal }) => Promise<{ status: number; text(): Promise<string> }>;
20
+
21
+ /** Whether a status satisfies the expectation (exact if given, else any 2xx). Pure. */
22
+ export function statusOk(status: number, expected?: number): boolean {
23
+ return expected === undefined ? status >= 200 && status < 300 : status === expected;
24
+ }
25
+
26
+ /**
27
+ * Assert an HTTP endpoint responds as expected — a typed verify step, the modeled
28
+ * replacement for `shell("curl -fs ...")`. Fails (throwing) if the status or body
29
+ * doesn't match after the allowed retries. `fetchFn` is injectable for tests.
30
+ * Uses fastIdempotent profile — 5m timeout.
31
+ */
32
+ export async function httpCheck(args: HttpCheckArgs, signal?: AbortSignal, fetchFn: HttpFetch = fetch): Promise<{ status: number }> {
33
+ const retries = args.retries ?? 0;
34
+ const intervalMs = args.intervalMs ?? 1_000;
35
+ let lastErr = "";
36
+ for (let attempt = 0; attempt <= retries; attempt++) {
37
+ if (signal?.aborted) throw new Error("httpCheck aborted");
38
+ try {
39
+ const res = await fetchFn(args.url, { method: args.method ?? "GET", signal });
40
+ const body = args.contains !== undefined ? await res.text() : "";
41
+ if (!statusOk(res.status, args.status)) {
42
+ lastErr = `status ${res.status} (want ${args.status ?? "2xx"})`;
43
+ } else if (args.contains !== undefined && !body.includes(args.contains)) {
44
+ lastErr = `body missing "${args.contains}"`;
45
+ } else {
46
+ console.log(`httpCheck OK: ${args.url} (${res.status})`);
47
+ return { status: res.status };
48
+ }
49
+ } catch (e) {
50
+ lastErr = e instanceof Error ? e.message : String(e);
51
+ }
52
+ if (attempt < retries) await sleep(intervalMs, signal);
53
+ }
54
+ throw new Error(`httpCheck failed for ${args.url}: ${lastErr}`);
55
+ }