@intentius/chant 0.46.0 → 0.50.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 (377) hide show
  1. package/dist/audit/catalog.d.ts +13 -3
  2. package/dist/audit/catalog.d.ts.map +1 -1
  3. package/dist/audit/core.d.ts +30 -3
  4. package/dist/audit/core.d.ts.map +1 -1
  5. package/dist/audit/discover.d.ts +9 -2
  6. package/dist/audit/discover.d.ts.map +1 -1
  7. package/dist/audit/fetch.d.ts.map +1 -1
  8. package/dist/audit/report-html.d.ts.map +1 -1
  9. package/dist/audit/report-model.d.ts +6 -0
  10. package/dist/audit/report-model.d.ts.map +1 -1
  11. package/dist/audit/report.d.ts.map +1 -1
  12. package/dist/audit/rules-doc.d.ts.map +1 -1
  13. package/dist/audit/secrets.d.ts +95 -0
  14. package/dist/audit/secrets.d.ts.map +1 -0
  15. package/dist/audit/wrangler.d.ts +33 -0
  16. package/dist/audit/wrangler.d.ts.map +1 -0
  17. package/dist/build.d.ts +3 -3
  18. package/dist/build.d.ts.map +1 -1
  19. package/dist/cli/commands/audit.d.ts +7 -0
  20. package/dist/cli/commands/audit.d.ts.map +1 -1
  21. package/dist/cli/commands/build.d.ts +23 -0
  22. package/dist/cli/commands/build.d.ts.map +1 -1
  23. package/dist/cli/commands/lint.d.ts.map +1 -1
  24. package/dist/cli/handlers/build.d.ts.map +1 -1
  25. package/dist/cli/handlers/components.d.ts +31 -0
  26. package/dist/cli/handlers/components.d.ts.map +1 -1
  27. package/dist/cli/handlers/lifecycle.d.ts +12 -1
  28. package/dist/cli/handlers/lifecycle.d.ts.map +1 -1
  29. package/dist/cli/handlers/operator.d.ts +32 -0
  30. package/dist/cli/handlers/operator.d.ts.map +1 -0
  31. package/dist/cli/handlers/scenario.d.ts +39 -0
  32. package/dist/cli/handlers/scenario.d.ts.map +1 -0
  33. package/dist/cli/main.d.ts.map +1 -1
  34. package/dist/cli/mcp/resource-handlers.d.ts.map +1 -1
  35. package/dist/cli/mcp/server.d.ts +35 -2
  36. package/dist/cli/mcp/server.d.ts.map +1 -1
  37. package/dist/cli/mcp/tools/explain.d.ts +6 -0
  38. package/dist/cli/mcp/tools/explain.d.ts.map +1 -1
  39. package/dist/cli/mcp/types.d.ts +29 -1
  40. package/dist/cli/mcp/types.d.ts.map +1 -1
  41. package/dist/cli/plugins.d.ts +1 -1
  42. package/dist/cli/plugins.d.ts.map +1 -1
  43. package/dist/cli/registry.d.ts +14 -2
  44. package/dist/cli/registry.d.ts.map +1 -1
  45. package/dist/cli/reporters/stylish.d.ts +15 -1
  46. package/dist/cli/reporters/stylish.d.ts.map +1 -1
  47. package/dist/codegen/docs-rule-scanning.d.ts.map +1 -1
  48. package/dist/components/auto-release.d.ts +4 -0
  49. package/dist/components/auto-release.d.ts.map +1 -1
  50. package/dist/components/capability.d.ts +17 -2
  51. package/dist/components/capability.d.ts.map +1 -1
  52. package/dist/components/cli-support.d.ts +7 -0
  53. package/dist/components/cli-support.d.ts.map +1 -1
  54. package/dist/components/component.d.ts +15 -0
  55. package/dist/components/component.d.ts.map +1 -1
  56. package/dist/components/driver.d.ts.map +1 -1
  57. package/dist/components/starter-plugin.d.ts +2 -0
  58. package/dist/components/starter-plugin.d.ts.map +1 -1
  59. package/dist/components/verbs/ensure-secret.d.ts +50 -0
  60. package/dist/components/verbs/ensure-secret.d.ts.map +1 -0
  61. package/dist/components/verbs/index.d.ts +13 -0
  62. package/dist/components/verbs/index.d.ts.map +1 -1
  63. package/dist/components/verbs/r2-sync.d.ts +76 -0
  64. package/dist/components/verbs/r2-sync.d.ts.map +1 -0
  65. package/dist/components/verbs/run-agent.d.ts +499 -0
  66. package/dist/components/verbs/run-agent.d.ts.map +1 -0
  67. package/dist/components/verbs/sign.d.ts +30 -0
  68. package/dist/components/verbs/sign.d.ts.map +1 -1
  69. package/dist/components/verbs/wrangler.d.ts +108 -0
  70. package/dist/components/verbs/wrangler.d.ts.map +1 -0
  71. package/dist/composite.d.ts +6 -1
  72. package/dist/composite.d.ts.map +1 -1
  73. package/dist/config.d.ts +26 -0
  74. package/dist/config.d.ts.map +1 -1
  75. package/dist/deep-observation.d.ts +14 -0
  76. package/dist/deep-observation.d.ts.map +1 -1
  77. package/dist/discovery/collect.d.ts.map +1 -1
  78. package/dist/discovery/fold-import.d.ts +15 -1
  79. package/dist/discovery/fold-import.d.ts.map +1 -1
  80. package/dist/discovery/fold-rank.d.ts +66 -0
  81. package/dist/discovery/fold-rank.d.ts.map +1 -0
  82. package/dist/discovery/index.d.ts +15 -0
  83. package/dist/discovery/index.d.ts.map +1 -1
  84. package/dist/discovery/param-deps.d.ts +17 -0
  85. package/dist/discovery/param-deps.d.ts.map +1 -0
  86. package/dist/effect-receipt.d.ts +177 -0
  87. package/dist/effect-receipt.d.ts.map +1 -0
  88. package/dist/fold/fold.d.ts +55 -2
  89. package/dist/fold/fold.d.ts.map +1 -1
  90. package/dist/fold/subset.d.ts +20 -0
  91. package/dist/fold/subset.d.ts.map +1 -1
  92. package/dist/index.d.ts +4 -0
  93. package/dist/index.d.ts.map +1 -1
  94. package/dist/lexicon-schema.d.ts +2 -0
  95. package/dist/lexicon-schema.d.ts.map +1 -1
  96. package/dist/lexicon.d.ts +137 -3
  97. package/dist/lexicon.d.ts.map +1 -1
  98. package/dist/lifecycle/change-set.d.ts +33 -5
  99. package/dist/lifecycle/change-set.d.ts.map +1 -1
  100. package/dist/lifecycle/converge-ledger.d.ts +90 -0
  101. package/dist/lifecycle/converge-ledger.d.ts.map +1 -0
  102. package/dist/lifecycle/deep-diff.d.ts +18 -0
  103. package/dist/lifecycle/deep-diff.d.ts.map +1 -1
  104. package/dist/lifecycle/deep-observe.d.ts +9 -1
  105. package/dist/lifecycle/deep-observe.d.ts.map +1 -1
  106. package/dist/lifecycle/gate-ledger.d.ts +33 -0
  107. package/dist/lifecycle/gate-ledger.d.ts.map +1 -0
  108. package/dist/lifecycle/git.d.ts +145 -21
  109. package/dist/lifecycle/git.d.ts.map +1 -1
  110. package/dist/lifecycle/index.d.ts +6 -0
  111. package/dist/lifecycle/index.d.ts.map +1 -1
  112. package/dist/lifecycle/lease.d.ts +113 -0
  113. package/dist/lifecycle/lease.d.ts.map +1 -0
  114. package/dist/lifecycle/observation-baseline.d.ts +21 -3
  115. package/dist/lifecycle/observation-baseline.d.ts.map +1 -1
  116. package/dist/lifecycle/receipt-plan.d.ts +62 -0
  117. package/dist/lifecycle/receipt-plan.d.ts.map +1 -0
  118. package/dist/lifecycle/release-ledger.d.ts +20 -0
  119. package/dist/lifecycle/release-ledger.d.ts.map +1 -1
  120. package/dist/lifecycle/scenario-eval.d.ts +42 -0
  121. package/dist/lifecycle/scenario-eval.d.ts.map +1 -0
  122. package/dist/lifecycle/scenario.d.ts +163 -0
  123. package/dist/lifecycle/scenario.d.ts.map +1 -0
  124. package/dist/lifecycle/symptoms.d.ts +63 -0
  125. package/dist/lifecycle/symptoms.d.ts.map +1 -0
  126. package/dist/lifecycle/teardown.d.ts +6 -4
  127. package/dist/lifecycle/teardown.d.ts.map +1 -1
  128. package/dist/lifecycle/unobserved-gate.d.ts +67 -0
  129. package/dist/lifecycle/unobserved-gate.d.ts.map +1 -0
  130. package/dist/lint/knowledge-checks.d.ts +48 -0
  131. package/dist/lint/knowledge-checks.d.ts.map +1 -0
  132. package/dist/lint/output-checks.d.ts +5 -0
  133. package/dist/lint/output-checks.d.ts.map +1 -0
  134. package/dist/lint/output-docs.d.ts +94 -0
  135. package/dist/lint/output-docs.d.ts.map +1 -0
  136. package/dist/lint/pipeline-change-gate.d.ts +101 -0
  137. package/dist/lint/pipeline-change-gate.d.ts.map +1 -0
  138. package/dist/lint/post-synth.d.ts +41 -0
  139. package/dist/lint/post-synth.d.ts.map +1 -1
  140. package/dist/lint/receipt-checks.d.ts +9 -0
  141. package/dist/lint/receipt-checks.d.ts.map +1 -0
  142. package/dist/lint/rules/__fixtures__/comp/comp003/pass/agent-turn.component.d.ts +11 -0
  143. package/dist/lint/rules/__fixtures__/comp/comp003/pass/agent-turn.component.d.ts.map +1 -0
  144. package/dist/lint/rules/cor022-receipt-leaf.d.ts +13 -0
  145. package/dist/lint/rules/cor022-receipt-leaf.d.ts.map +1 -0
  146. package/dist/lint/rules/cor024-receipt-secret-pointer.d.ts +3 -0
  147. package/dist/lint/rules/cor024-receipt-secret-pointer.d.ts.map +1 -0
  148. package/dist/lint/rules/evl001-non-literal-expression.d.ts.map +1 -1
  149. package/dist/lint/rules/index.d.ts +3 -1
  150. package/dist/lint/rules/index.d.ts.map +1 -1
  151. package/dist/lsp/lexicon-providers.d.ts +7 -0
  152. package/dist/lsp/lexicon-providers.d.ts.map +1 -1
  153. package/dist/okf-read.d.ts +78 -0
  154. package/dist/okf-read.d.ts.map +1 -0
  155. package/dist/op/activity-contract.d.ts +139 -0
  156. package/dist/op/activity-contract.d.ts.map +1 -0
  157. package/dist/op/builders.d.ts +140 -3
  158. package/dist/op/builders.d.ts.map +1 -1
  159. package/dist/op/converge-rule.d.ts +161 -0
  160. package/dist/op/converge-rule.d.ts.map +1 -0
  161. package/dist/op/generate-pipeline.d.ts +39 -0
  162. package/dist/op/generate-pipeline.d.ts.map +1 -0
  163. package/dist/op/index.d.ts +18 -2
  164. package/dist/op/index.d.ts.map +1 -1
  165. package/dist/op/local-executor.d.ts +2 -1
  166. package/dist/op/local-executor.d.ts.map +1 -1
  167. package/dist/op/op-verb-class.d.ts +42 -0
  168. package/dist/op/op-verb-class.d.ts.map +1 -0
  169. package/dist/op/operator.d.ts +128 -0
  170. package/dist/op/operator.d.ts.map +1 -0
  171. package/dist/op/receipt-store.d.ts +138 -0
  172. package/dist/op/receipt-store.d.ts.map +1 -0
  173. package/dist/op/step-output-ref.d.ts +187 -0
  174. package/dist/op/step-output-ref.d.ts.map +1 -0
  175. package/dist/op/types.d.ts +49 -2
  176. package/dist/op/types.d.ts.map +1 -1
  177. package/dist/provenance.d.ts +73 -3
  178. package/dist/provenance.d.ts.map +1 -1
  179. package/dist/runtime-adapter.d.ts +7 -1
  180. package/dist/runtime-adapter.d.ts.map +1 -1
  181. package/dist/secret-materialization.d.ts +138 -0
  182. package/dist/secret-materialization.d.ts.map +1 -0
  183. package/dist/secret-provenance.d.ts +218 -0
  184. package/dist/secret-provenance.d.ts.map +1 -0
  185. package/dist/serializer.d.ts +29 -0
  186. package/dist/serializer.d.ts.map +1 -1
  187. package/dist/toml.d.ts +40 -5
  188. package/dist/toml.d.ts.map +1 -1
  189. package/dist/yaml.d.ts.map +1 -1
  190. package/package.json +4 -1
  191. package/src/audit/catalog.test.ts +1 -1
  192. package/src/audit/catalog.ts +75 -3
  193. package/src/audit/core.test.ts +57 -0
  194. package/src/audit/core.ts +0 -0
  195. package/src/audit/detect-bundle.test.ts +1 -1
  196. package/src/audit/discover.test.ts +24 -0
  197. package/src/audit/discover.ts +40 -4
  198. package/src/audit/fetch.test.ts +216 -3
  199. package/src/audit/fetch.ts +270 -59
  200. package/src/audit/report-html.ts +5 -2
  201. package/src/audit/report-model.ts +9 -0
  202. package/src/audit/report.test.ts +22 -0
  203. package/src/audit/report.ts +3 -2
  204. package/src/audit/rules-doc.ts +13 -1
  205. package/src/audit/secrets.test.ts +303 -0
  206. package/src/audit/secrets.ts +406 -0
  207. package/src/audit/wrangler.test.ts +230 -0
  208. package/src/audit/wrangler.ts +290 -0
  209. package/src/build.test.ts +41 -0
  210. package/src/build.ts +39 -6
  211. package/src/cli/command-group.ts +1 -1
  212. package/src/cli/commands/__fixtures__/audit-fountain/agents/fleet.yaml +27 -0
  213. package/src/cli/commands/__fixtures__/audit-fountain/k8s/deploy.yaml +16 -0
  214. package/src/cli/commands/__fixtures__/audit-fountain-clean/fleet.yaml +20 -0
  215. package/src/cli/commands/__fixtures__/schemas/sarif-2.1.0.schema.json +2882 -0
  216. package/src/cli/commands/audit.test.ts +268 -1
  217. package/src/cli/commands/audit.ts +87 -18
  218. package/src/cli/commands/build.test.ts +245 -0
  219. package/src/cli/commands/build.ts +210 -21
  220. package/src/cli/commands/lint.ts +15 -3
  221. package/src/cli/handlers/build.ts +2 -0
  222. package/src/cli/handlers/components.test.ts +199 -1
  223. package/src/cli/handlers/components.ts +160 -3
  224. package/src/cli/handlers/explain.test.ts +70 -1
  225. package/src/cli/handlers/graph.test.ts +20 -0
  226. package/src/cli/handlers/graph.ts +12 -3
  227. package/src/cli/handlers/lifecycle.test.ts +115 -1
  228. package/src/cli/handlers/lifecycle.ts +96 -15
  229. package/src/cli/handlers/operator.test.ts +255 -0
  230. package/src/cli/handlers/operator.ts +240 -0
  231. package/src/cli/handlers/scenario.test.ts +456 -0
  232. package/src/cli/handlers/scenario.ts +330 -0
  233. package/src/cli/main.test.ts +23 -0
  234. package/src/cli/main.ts +72 -1
  235. package/src/cli/mcp/resource-handlers.ts +38 -1
  236. package/src/cli/mcp/server.test.ts +323 -3
  237. package/src/cli/mcp/server.ts +84 -7
  238. package/src/cli/mcp/tools/explain.ts +51 -2
  239. package/src/cli/mcp/types.ts +27 -1
  240. package/src/cli/plugins.ts +4 -2
  241. package/src/cli/registry.ts +14 -2
  242. package/src/cli/reporters/stylish.test.ts +154 -0
  243. package/src/cli/reporters/stylish.ts +154 -33
  244. package/src/codegen/docs-rule-scanning.test.ts +42 -0
  245. package/src/codegen/docs-rule-scanning.ts +25 -2
  246. package/src/components/README.md +7 -0
  247. package/src/components/auto-release.ts +6 -0
  248. package/src/components/capability.ts +17 -2
  249. package/src/components/cli-support.test.ts +17 -0
  250. package/src/components/cli-support.ts +13 -1
  251. package/src/components/component-schema.test.ts +32 -0
  252. package/src/components/component.schema.json +6 -0
  253. package/src/components/component.test.ts +21 -0
  254. package/src/components/component.ts +15 -0
  255. package/src/components/driver.ts +12 -4
  256. package/src/components/registry.test.ts +7 -2
  257. package/src/components/starter-plugin.ts +17 -0
  258. package/src/components/verbs/ensure-secret.test.ts +130 -0
  259. package/src/components/verbs/ensure-secret.ts +79 -0
  260. package/src/components/verbs/index.ts +13 -0
  261. package/src/components/verbs/r2-sync.test.ts +107 -0
  262. package/src/components/verbs/r2-sync.ts +124 -0
  263. package/src/components/verbs/run-agent.test.ts +683 -0
  264. package/src/components/verbs/run-agent.ts +786 -0
  265. package/src/components/verbs/sign.test.ts +19 -0
  266. package/src/components/verbs/sign.ts +34 -2
  267. package/src/components/verbs/wrangler.test.ts +170 -0
  268. package/src/components/verbs/wrangler.ts +241 -0
  269. package/src/composite.ts +31 -2
  270. package/src/config.test.ts +15 -0
  271. package/src/config.ts +30 -0
  272. package/src/deep-observation.test.ts +19 -0
  273. package/src/deep-observation.ts +17 -0
  274. package/src/discovery/collect.ts +11 -2
  275. package/src/discovery/fold-import.test.ts +54 -0
  276. package/src/discovery/fold-import.ts +178 -38
  277. package/src/discovery/fold-rank.test.ts +197 -0
  278. package/src/discovery/fold-rank.ts +346 -0
  279. package/src/discovery/index.ts +16 -1
  280. package/src/discovery/param-deps.test.ts +118 -0
  281. package/src/discovery/param-deps.ts +170 -0
  282. package/src/effect-receipt-exclusion.test.ts +190 -0
  283. package/src/effect-receipt.test.ts +419 -0
  284. package/src/effect-receipt.ts +412 -0
  285. package/src/fold/fold.test.ts +6 -2
  286. package/src/fold/fold.ts +184 -3
  287. package/src/fold/subset.test.ts +95 -6
  288. package/src/fold/subset.ts +66 -2
  289. package/src/index.ts +4 -0
  290. package/src/lexicon-schema.ts +3 -0
  291. package/src/lexicon.ts +151 -5
  292. package/src/lifecycle/change-set.ts +46 -7
  293. package/src/lifecycle/converge-ledger.test.ts +199 -0
  294. package/src/lifecycle/converge-ledger.ts +179 -0
  295. package/src/lifecycle/deep-diff.test.ts +79 -1
  296. package/src/lifecycle/deep-diff.ts +23 -0
  297. package/src/lifecycle/deep-observe.ts +13 -2
  298. package/src/lifecycle/gate-ledger.test.ts +103 -0
  299. package/src/lifecycle/gate-ledger.ts +140 -0
  300. package/src/lifecycle/git.test.ts +430 -0
  301. package/src/lifecycle/git.ts +446 -84
  302. package/src/lifecycle/index.ts +6 -0
  303. package/src/lifecycle/lease.test.ts +343 -0
  304. package/src/lifecycle/lease.ts +270 -0
  305. package/src/lifecycle/observation-baseline.test.ts +46 -0
  306. package/src/lifecycle/observation-baseline.ts +33 -1
  307. package/src/lifecycle/receipt-plan.test.ts +250 -0
  308. package/src/lifecycle/receipt-plan.ts +249 -0
  309. package/src/lifecycle/release-ledger.ts +20 -0
  310. package/src/lifecycle/scenario-eval.test.ts +199 -0
  311. package/src/lifecycle/scenario-eval.ts +158 -0
  312. package/src/lifecycle/scenario.test.ts +195 -0
  313. package/src/lifecycle/scenario.ts +321 -0
  314. package/src/lifecycle/symptoms.test.ts +116 -0
  315. package/src/lifecycle/symptoms.ts +126 -0
  316. package/src/lifecycle/teardown.test.ts +31 -0
  317. package/src/lifecycle/teardown.ts +6 -4
  318. package/src/lifecycle/unobserved-gate.test.ts +109 -0
  319. package/src/lifecycle/unobserved-gate.ts +102 -0
  320. package/src/lint/knowledge-checks.test.ts +80 -0
  321. package/src/lint/knowledge-checks.ts +74 -0
  322. package/src/lint/output-checks.test.ts +85 -0
  323. package/src/lint/output-checks.ts +99 -0
  324. package/src/lint/output-docs.test.ts +220 -0
  325. package/src/lint/output-docs.ts +204 -0
  326. package/src/lint/pipeline-change-gate.test.ts +144 -0
  327. package/src/lint/pipeline-change-gate.ts +153 -0
  328. package/src/lint/post-synth.test.ts +97 -0
  329. package/src/lint/post-synth.ts +60 -0
  330. package/src/lint/receipt-checks.test.ts +101 -0
  331. package/src/lint/receipt-checks.ts +93 -0
  332. package/src/lint/rules/__fixtures__/comp/comp003/pass/agent-turn.component.ts +26 -0
  333. package/src/lint/rules/comp/comp.test.ts +49 -1
  334. package/src/lint/rules/cor022-receipt-leaf.test.ts +116 -0
  335. package/src/lint/rules/cor022-receipt-leaf.ts +130 -0
  336. package/src/lint/rules/cor024-receipt-secret-pointer.test.ts +121 -0
  337. package/src/lint/rules/cor024-receipt-secret-pointer.ts +218 -0
  338. package/src/lint/rules/evl001-non-literal-expression.test.ts +35 -3
  339. package/src/lint/rules/evl001-non-literal-expression.ts +7 -0
  340. package/src/lint/rules/index.ts +7 -1
  341. package/src/lsp/lexicon-providers.test.ts +44 -0
  342. package/src/lsp/lexicon-providers.ts +11 -1
  343. package/src/okf-read.test.ts +149 -0
  344. package/src/okf-read.ts +197 -0
  345. package/src/op/activity-contract.test.ts +180 -0
  346. package/src/op/activity-contract.ts +278 -0
  347. package/src/op/builders-exports.test.ts +17 -1
  348. package/src/op/builders.ts +198 -6
  349. package/src/op/converge-rule.test.ts +179 -0
  350. package/src/op/converge-rule.ts +311 -0
  351. package/src/op/effect-step.test.ts +311 -0
  352. package/src/op/generate-pipeline.test.ts +53 -0
  353. package/src/op/generate-pipeline.ts +99 -0
  354. package/src/op/index.ts +40 -3
  355. package/src/op/local-executor.test.ts +92 -0
  356. package/src/op/local-executor.ts +212 -29
  357. package/src/op/op-verb-class.test.ts +126 -0
  358. package/src/op/op-verb-class.ts +115 -0
  359. package/src/op/op.test.ts +25 -2
  360. package/src/op/operator.test.ts +346 -0
  361. package/src/op/operator.ts +213 -0
  362. package/src/op/receipt-store.ts +211 -0
  363. package/src/op/step-output-ref.test.ts +334 -0
  364. package/src/op/step-output-ref.ts +453 -0
  365. package/src/op/types.ts +51 -2
  366. package/src/provenance.test.ts +151 -4
  367. package/src/provenance.ts +118 -4
  368. package/src/runtime-adapter.ts +31 -10
  369. package/src/secret-materialization.test.ts +199 -0
  370. package/src/secret-materialization.ts +235 -0
  371. package/src/secret-provenance.test.ts +388 -0
  372. package/src/secret-provenance.ts +475 -0
  373. package/src/serializer.ts +30 -0
  374. package/src/toml.test.ts +157 -384
  375. package/src/toml.ts +371 -5
  376. package/src/yaml.test.ts +88 -0
  377. package/src/yaml.ts +76 -6
@@ -0,0 +1,456 @@
1
+ import { describe, test, expect, vi, beforeEach, afterEach } from "vitest";
2
+ import { mkdtemp, rm, writeFile } from "node:fs/promises";
3
+ import { join } from "node:path";
4
+ import { tmpdir } from "node:os";
5
+ import type { BuildResult } from "../../build";
6
+ import type { ParsedArgs } from "../registry";
7
+ import { Scenario, snapshot } from "../../lifecycle/scenario";
8
+ import { EffectReceipt, receiptExpectation, type EffectReceiptDeclaration } from "../../effect-receipt";
9
+ import type { ResourceMetadata } from "../../lexicon";
10
+ import type { LifecycleSnapshot } from "../../lifecycle/types";
11
+
12
+ const buildMock = vi.fn();
13
+ const fetchLifecycleMock = vi.fn();
14
+ const readEnvironmentSnapshotsMock = vi.fn();
15
+ const loadChantConfigMock = vi.fn();
16
+
17
+ vi.mock("../../build", async () => {
18
+ const actual = await vi.importActual<typeof import("../../build")>("../../build");
19
+ return { ...actual, build: (...args: unknown[]) => buildMock(...args) };
20
+ });
21
+ vi.mock("../../lifecycle/git", () => ({
22
+ fetchLifecycle: () => fetchLifecycleMock(),
23
+ readEnvironmentSnapshots: (...args: unknown[]) => readEnvironmentSnapshotsMock(...args),
24
+ }));
25
+ vi.mock("../../config", async () => {
26
+ const actual = await vi.importActual<typeof import("../../config")>("../../config");
27
+ return { ...actual, loadChantConfig: (...args: unknown[]) => loadChantConfigMock(...args) };
28
+ });
29
+
30
+ const { runScenarioCheck, runScenarioUnknown } = await import("./scenario");
31
+
32
+ function makeArgs(overrides: Partial<ParsedArgs> = {}): ParsedArgs {
33
+ return {
34
+ command: "scenario",
35
+ path: "check",
36
+ format: "",
37
+ fix: false,
38
+ watch: false,
39
+ verbose: false,
40
+ help: false,
41
+ live: false,
42
+ ...overrides,
43
+ };
44
+ }
45
+
46
+ /** A synthetic BuildResult — same shape ./lifecycle.test.ts's makeBuildResult
47
+ * uses: plain `{lexicon, entityType, props}` objects, not real Declarables,
48
+ * since `isResourceDeclarable` only checks for a `props` key. Scenario
49
+ * entities must be real `Scenario(...)` objects — collection keys on the
50
+ * marker symbol. */
51
+ function makeBuildResult(
52
+ resourcesByLexicon: Record<string, string[]>,
53
+ scenarios: Record<string, ReturnType<typeof Scenario>>,
54
+ receipts: Record<string, EffectReceiptDeclaration> = {},
55
+ ): BuildResult {
56
+ const entities = new Map<string, unknown>();
57
+ for (const [lexicon, names] of Object.entries(resourcesByLexicon)) {
58
+ for (const name of names) entities.set(name, { lexicon, entityType: `${lexicon}::Mock`, props: {} });
59
+ }
60
+ for (const [name, decl] of Object.entries(scenarios)) entities.set(name, decl);
61
+ for (const [name, decl] of Object.entries(receipts)) entities.set(name, decl);
62
+ return {
63
+ outputs: new Map(Object.keys(resourcesByLexicon).map((l) => [l, "{}"])),
64
+ entities,
65
+ dependencies: new Map(),
66
+ errors: [],
67
+ warnings: [],
68
+ manifest: { lexicons: Object.keys(resourcesByLexicon), outputs: {}, deployOrder: Object.keys(resourcesByLexicon) },
69
+ sourceFileCount: 1,
70
+ } as unknown as BuildResult;
71
+ }
72
+
73
+ /**
74
+ * An aws-materialized effect receipt shaped exactly like
75
+ * lexicons/aws/src/effect-receipt-row.ts's real factory (an `AWS::SSM::Parameter`
76
+ * row with `props: { Type: "String" }`) — built inline off the core
77
+ * `EffectReceipt` factory rather than importing the lexicon package, the same
78
+ * "minimal test EffectReceipt" pattern receipt-plan.test.ts uses. Carrying
79
+ * `props` is the load-bearing detail: it is what made the reviewer's bug
80
+ * reproducible — `isResourceDeclarable` (packages/core/src/declarable.ts) key
81
+ * on `"props" in value`, so an aws-materialized receipt WAS swept into
82
+ * `evaluateOneScenario`'s old isResourceDeclarable-only declared axis and
83
+ * generically misclassified, exactly as a bare `AWS::S3::Bucket` would be.
84
+ */
85
+ function makeAwsReceipt(
86
+ name: string,
87
+ effect: string,
88
+ flavor: "existence" | "hash",
89
+ inputs?: Record<string, unknown>,
90
+ ): EffectReceiptDeclaration {
91
+ const core = EffectReceipt(name, { effect, flavor, inputs });
92
+ return {
93
+ ...core,
94
+ lexicon: "aws",
95
+ entityType: "AWS::SSM::Parameter",
96
+ props: { Type: "String" },
97
+ } as unknown as EffectReceiptDeclaration;
98
+ }
99
+
100
+ const meta = (overrides: Partial<ResourceMetadata> = {}): ResourceMetadata => ({
101
+ type: "AWS::S3::Bucket",
102
+ status: "CREATE_COMPLETE",
103
+ ownership: "owned",
104
+ ...overrides,
105
+ });
106
+
107
+ const snap = (overrides: Partial<LifecycleSnapshot> = {}): LifecycleSnapshot => ({
108
+ lexicon: "aws",
109
+ environment: "prod",
110
+ commit: "abc123",
111
+ timestamp: "2026-01-01T00:00:00Z",
112
+ resources: {},
113
+ ...overrides,
114
+ });
115
+
116
+ describe("runScenarioCheck", () => {
117
+ let stdoutBuf: string[];
118
+ let stderrBuf: string[];
119
+ let tmpFiles: string[];
120
+
121
+ beforeEach(() => {
122
+ stdoutBuf = [];
123
+ stderrBuf = [];
124
+ tmpFiles = [];
125
+ vi.spyOn(console, "log").mockImplementation((s: string) => { stdoutBuf.push(s); });
126
+ vi.spyOn(console, "error").mockImplementation((s: string) => { stderrBuf.push(s); });
127
+ buildMock.mockReset();
128
+ fetchLifecycleMock.mockReset();
129
+ fetchLifecycleMock.mockResolvedValue(undefined);
130
+ readEnvironmentSnapshotsMock.mockReset();
131
+ readEnvironmentSnapshotsMock.mockResolvedValue(new Map());
132
+ loadChantConfigMock.mockReset();
133
+ loadChantConfigMock.mockResolvedValue({ config: { environments: [{ name: "prod" }, { name: "staging" }] } });
134
+ });
135
+
136
+ afterEach(async () => {
137
+ for (const d of tmpFiles) await rm(d, { recursive: true, force: true });
138
+ });
139
+
140
+ function combined(): string {
141
+ return [...stdoutBuf, ...stderrBuf].join("\n");
142
+ }
143
+
144
+ async function writeFixture(content: LifecycleSnapshot): Promise<string> {
145
+ const dir = await mkdtemp(join(tmpdir(), "chant-scenario-"));
146
+ const path = join(dir, "baseline.json");
147
+ await writeFile(path, JSON.stringify(content));
148
+ tmpFiles.push(dir);
149
+ return path;
150
+ }
151
+
152
+ test("no scenarios declared: exits 0 and says so", async () => {
153
+ buildMock.mockResolvedValue(makeBuildResult({ aws: ["bucket"] }, {}));
154
+ const exit = await runScenarioCheck({ args: makeArgs(), plugins: [], serializers: [] });
155
+ expect(exit).toBe(0);
156
+ expect(stdoutBuf.join("\n")).toContain("No scenarios declared");
157
+ });
158
+
159
+ test("build failure exits 1 before any scenario runs", async () => {
160
+ buildMock.mockResolvedValue({ ...makeBuildResult({}, {}), errors: [{}] });
161
+ const exit = await runScenarioCheck({ args: makeArgs(), plugins: [], serializers: [] });
162
+ expect(exit).toBe(1);
163
+ expect(stderrBuf.join("\n")).toContain("Build failed");
164
+ });
165
+
166
+ test("a noop scenario passes against a fixture matching the declared entity", async () => {
167
+ const fixturePath = await writeFixture(snap({ resources: { bucket: meta() } }));
168
+ const scenario = Scenario("plan-neutral", { given: snapshot(fixturePath), expect: { noop: true } });
169
+ buildMock.mockResolvedValue(makeBuildResult({ aws: ["bucket"] }, { plan: scenario }));
170
+
171
+ const exit = await runScenarioCheck({ args: makeArgs(), plugins: [], serializers: [] });
172
+ expect(exit).toBe(0);
173
+ expect(stdoutBuf.join("\n")).toContain("plan-neutral");
174
+ expect(stdoutBuf.join("\n")).toContain("PASS");
175
+ });
176
+
177
+ test("a noop scenario fails with a legible detail when the fixture lacks a declared resource (create proposed)", async () => {
178
+ const fixturePath = await writeFixture(snap({ resources: {} }));
179
+ const scenario = Scenario("should-be-neutral", { given: snapshot(fixturePath), expect: { noop: true } });
180
+ buildMock.mockResolvedValue(makeBuildResult({ aws: ["bucket"] }, { plan: scenario }));
181
+
182
+ const exit = await runScenarioCheck({ args: makeArgs(), plugins: [], serializers: [] });
183
+ expect(exit).toBe(1);
184
+ const out = stdoutBuf.join("\n");
185
+ expect(out).toContain("FAIL");
186
+ expect(out).toContain("noop:");
187
+ expect(out).toContain("1 create");
188
+ expect(out).toContain("bucket");
189
+ });
190
+
191
+ test("a deletes scenario passes when the fixture's extra owned resource is proposed for delete", async () => {
192
+ const fixturePath = await writeFixture(snap({ resources: { legacy: meta({ ownership: "owned" }) } }));
193
+ const scenario = Scenario("drop legacy", {
194
+ given: snapshot(fixturePath),
195
+ expect: { deletes: [{ name: "legacy", ownership: "owned" }], create: 0, update: 0 },
196
+ });
197
+ buildMock.mockResolvedValue(makeBuildResult({}, { plan: scenario }));
198
+
199
+ const exit = await runScenarioCheck({ args: makeArgs(), plugins: [], serializers: [] });
200
+ expect(exit).toBe(0);
201
+ });
202
+
203
+ // buildChangeSet never classifies a non-owned resource as `delete` (only
204
+ // `adopt`) — the ownership safety property is structural, upstream of
205
+ // evaluateScenario. A `deletes` expectation against a foreign resource
206
+ // therefore fails as "not proposed", never as an ownership mismatch; that
207
+ // IS the assertion worth making — the scenario correctly refuses to treat
208
+ // an adopt candidate as the delete it expected.
209
+ test("a deletes scenario fails and names the resource when the fixture marks it foreign (never auto-deleted)", async () => {
210
+ const fixturePath = await writeFixture(snap({ resources: { legacy: meta({ ownership: "foreign" }) } }));
211
+ const scenario = Scenario("drop legacy", {
212
+ given: snapshot(fixturePath),
213
+ expect: { deletes: [{ name: "legacy", ownership: "owned" }] },
214
+ });
215
+ buildMock.mockResolvedValue(makeBuildResult({}, { plan: scenario }));
216
+
217
+ const exit = await runScenarioCheck({ args: makeArgs(), plugins: [], serializers: [] });
218
+ expect(exit).toBe(1);
219
+ const out = stdoutBuf.join("\n");
220
+ expect(out).toContain("deletes:");
221
+ expect(out).toContain("legacy");
222
+ expect(out).toContain("not proposed for delete");
223
+ });
224
+
225
+ test("given: snapshot(path) — a missing fixture file fails the scenario, not the whole run", async () => {
226
+ const scenario = Scenario("s", { given: snapshot("fixtures/does-not-exist.json"), expect: { noop: true } });
227
+ buildMock.mockResolvedValue(makeBuildResult({}, { plan: scenario }));
228
+
229
+ const exit = await runScenarioCheck({ args: makeArgs(), plugins: [], serializers: [] });
230
+ expect(exit).toBe(1);
231
+ expect(stdoutBuf.join("\n")).toContain("fixture not found");
232
+ });
233
+
234
+ test("given: snapshot(env) — an unknown environment fails with the declared-environments hint", async () => {
235
+ const scenario = Scenario("s", { given: snapshot("nowhere"), expect: { noop: true } });
236
+ buildMock.mockResolvedValue(makeBuildResult({}, { plan: scenario }));
237
+
238
+ const exit = await runScenarioCheck({ args: makeArgs(), plugins: [], serializers: [] });
239
+ expect(exit).toBe(1);
240
+ expect(stdoutBuf.join("\n")).toMatch(/Unknown environment/);
241
+ });
242
+
243
+ test("given: snapshot(env) — no recorded snapshot fails with a recording hint", async () => {
244
+ readEnvironmentSnapshotsMock.mockResolvedValue(new Map());
245
+ const scenario = Scenario("s", { given: snapshot("prod"), expect: { noop: true } });
246
+ buildMock.mockResolvedValue(makeBuildResult({}, { plan: scenario }));
247
+
248
+ const exit = await runScenarioCheck({ args: makeArgs(), plugins: [], serializers: [] });
249
+ expect(exit).toBe(1);
250
+ expect(stdoutBuf.join("\n")).toContain("chant lifecycle snapshot prod");
251
+ expect(fetchLifecycleMock).toHaveBeenCalled();
252
+ });
253
+
254
+ test("given: snapshot(env) — replays every recorded lexicon's snapshot", async () => {
255
+ readEnvironmentSnapshotsMock.mockResolvedValue(
256
+ new Map([["aws", JSON.stringify(snap({ resources: { bucket: meta() } }))]]),
257
+ );
258
+ const scenario = Scenario("s", { given: snapshot("prod"), expect: { noop: true } });
259
+ buildMock.mockResolvedValue(makeBuildResult({ aws: ["bucket"] }, { plan: scenario }));
260
+
261
+ const exit = await runScenarioCheck({ args: makeArgs(), plugins: [], serializers: [] });
262
+ expect(exit).toBe(0);
263
+ });
264
+
265
+ test("a lexicon the fixture doesn't cover is marked unobserved, not silently create", async () => {
266
+ const fixturePath = await writeFixture(snap({ lexicon: "aws", resources: { bucket: meta() } }));
267
+ const scenario = Scenario("multi-lexicon", {
268
+ given: snapshot(fixturePath),
269
+ expect: { unobserved: "refuse" },
270
+ });
271
+ buildMock.mockResolvedValue(makeBuildResult({ aws: ["bucket"], k8s: ["web"] }, { plan: scenario }));
272
+
273
+ const exit = await runScenarioCheck({ args: makeArgs(), plugins: [], serializers: [] });
274
+ expect(exit).toBe(1);
275
+ const out = stdoutBuf.join("\n");
276
+ expect(out).toContain("unobserved:");
277
+ expect(out).toContain("web");
278
+ expect(out).not.toContain("1 create"); // bucket must not have become a spurious pass/fail signal here
279
+ });
280
+
281
+ test("multiple scenarios: overall exit reflects any failure, and every scenario is reported", async () => {
282
+ const fixturePath = await writeFixture(snap({ resources: { bucket: meta() } }));
283
+ const passing = Scenario("passing", { given: snapshot(fixturePath), expect: { noop: true } });
284
+ const failing = Scenario("failing", { given: snapshot(fixturePath), expect: { create: 5 } });
285
+ buildMock.mockResolvedValue(makeBuildResult({ aws: ["bucket"] }, { passing, failing }));
286
+
287
+ const exit = await runScenarioCheck({ args: makeArgs(), plugins: [], serializers: [] });
288
+ expect(exit).toBe(1);
289
+ const out = combined();
290
+ expect(out).toContain("passing");
291
+ expect(out).toContain("failing");
292
+ expect(out).toContain("1/2 scenario(s) failed");
293
+ });
294
+
295
+ test("--json emits every scenario's verdict as structured data", async () => {
296
+ const fixturePath = await writeFixture(snap({ resources: { bucket: meta() } }));
297
+ const scenario = Scenario("plan-neutral", { given: snapshot(fixturePath), expect: { noop: true } });
298
+ buildMock.mockResolvedValue(makeBuildResult({ aws: ["bucket"] }, { plan: scenario }));
299
+
300
+ const exit = await runScenarioCheck({ args: makeArgs({ json: true }), plugins: [], serializers: [] });
301
+ expect(exit).toBe(0);
302
+ const parsed = JSON.parse(stdoutBuf[0]);
303
+ expect(parsed).toEqual([{ name: "plan-neutral", entity: "plan", env: "prod", pass: true, checks: [{ clause: "noop", pass: true }] }]);
304
+ });
305
+
306
+ // ───────────────────────────────────────────────────────────────────────
307
+ // Effect receipts (#1832 parity): the reviewer's two reproductions against
308
+ // `evaluateOneScenario`, now that it routes receipts through the same
309
+ // collectEffectReceipts → planReceipts → mergeReceiptEntries pipeline
310
+ // `runLifecyclePlan` uses (lifecycle.ts), instead of classifying them
311
+ // through isResourceDeclarable + buildChangeSet alone.
312
+ // ───────────────────────────────────────────────────────────────────────
313
+
314
+ test("a declared-but-unfired receipt classifies as effect, never as a generic create", async () => {
315
+ // Confirmed absent: the fixture's aws lexicon has no "dbMigrated" entry
316
+ // in `resources` and names nothing `unobserved` for it — the same
317
+ // "looked and it's not there" claim a live read makes.
318
+ const receipt = makeAwsReceipt("dbMigrated", "db-migrate", "existence");
319
+ const fixturePath = await writeFixture(snap({ lexicon: "aws", resources: {} }));
320
+ const scenario = Scenario("nothing pending", { given: snapshot(fixturePath), expect: { noop: true } });
321
+ buildMock.mockResolvedValue(makeBuildResult({}, { check: scenario }, { dbMigrated: receipt }));
322
+
323
+ const exit = await runScenarioCheck({ args: makeArgs(), plugins: [], serializers: [] });
324
+
325
+ // Before the fix, isResourceDeclarable(receipt) is true (the aws row
326
+ // carries `props`), so the generic classification proposed a `create` —
327
+ // exactly the reviewer's "false noop FAILURE" reproduction. Fixed, the
328
+ // receipt pipeline reclassifies it as `effect` (the fire is pending),
329
+ // which never reads as a `create`.
330
+ expect(exit).toBe(1);
331
+ const out = combined();
332
+ expect(out).toContain("FAIL");
333
+ expect(out).not.toContain("1 create");
334
+ expect(out).toContain("0 create");
335
+ expect(out).toContain("1 effect");
336
+ expect(out).toContain("dbMigrated");
337
+ });
338
+
339
+ test("a stale receipt in the fixture never reads as a clean noop pass", async () => {
340
+ // hash-flavor, static inputs: `receiptExpectation` is computable offline,
341
+ // no reference resolver needed. The fixture's live value is a DIFFERENT
342
+ // digest — a stale receipt, present but wrong, exactly the reviewer's
343
+ // "false noop PASS" reproduction (observedThen is always undefined for a
344
+ // scenario, so the generic pass sees no drift and calls it `noop`).
345
+ const receipt = makeAwsReceipt("seeded", "seed", "hash", { rows: 10 });
346
+ const staleValue = receiptExpectation(makeAwsReceipt("seeded", "seed", "hash", { rows: 9 }));
347
+ const fixturePath = await writeFixture(
348
+ snap({ lexicon: "aws", resources: { seeded: meta({ type: "AWS::SSM::Parameter", attributes: { value: staleValue } }) } }),
349
+ );
350
+ const scenario = Scenario("seed already ran", { given: snapshot(fixturePath), expect: { noop: true } });
351
+ buildMock.mockResolvedValue(makeBuildResult({}, { check: scenario }, { seeded: receipt }));
352
+
353
+ const exit = await runScenarioCheck({ args: makeArgs(), plugins: [], serializers: [] });
354
+
355
+ expect(exit).toBe(1);
356
+ const out = combined();
357
+ expect(out).toContain("FAIL");
358
+ expect(out).toContain("1 effect");
359
+ expect(out).toContain("seeded");
360
+ });
361
+
362
+ test("a receipt whose live value matches its expectation is a genuine noop pass", async () => {
363
+ // The positive control for the two reproductions above: a receipt that
364
+ // HAS fired, with the right value, is still a clean noop — the pipeline
365
+ // doesn't just fail every scenario touching a receipt.
366
+ const receipt = makeAwsReceipt("bootstrapped", "bootstrap", "existence");
367
+ const fixturePath = await writeFixture(
368
+ snap({
369
+ lexicon: "aws",
370
+ resources: { bootstrapped: meta({ type: "AWS::SSM::Parameter", attributes: { value: receiptExpectation(receipt) } }) },
371
+ }),
372
+ );
373
+ const scenario = Scenario("already bootstrapped", { given: snapshot(fixturePath), expect: { noop: true } });
374
+ buildMock.mockResolvedValue(makeBuildResult({}, { check: scenario }, { bootstrapped: receipt }));
375
+
376
+ const exit = await runScenarioCheck({ args: makeArgs(), plugins: [], serializers: [] });
377
+
378
+ expect(exit).toBe(0);
379
+ expect(combined()).toContain("PASS");
380
+ });
381
+ });
382
+
383
+ describe("runScenarioUnknown", () => {
384
+ test("reports the unknown subcommand and exits 1", async () => {
385
+ const stderrBuf: string[] = [];
386
+ vi.spyOn(console, "error").mockImplementation((s: string) => { stderrBuf.push(s); });
387
+ const exit = await runScenarioUnknown({ args: makeArgs({ path: "bogus" }), plugins: [], serializers: [] });
388
+ expect(exit).toBe(1);
389
+ expect(stderrBuf.join("\n")).toContain("bogus");
390
+ });
391
+ });
392
+
393
+ // ─────────────────────────────────────────────────────────────────────────
394
+ // Fixture-driven integration test (#1292): a tiny declared project + a
395
+ // committed fixture snapshot. Exercises the real path end to end — factory,
396
+ // evaluator, and the CLI handler's fixture replay — with no mocked fixture
397
+ // I/O: the JSON file is written to disk and read back for real.
398
+ // ─────────────────────────────────────────────────────────────────────────
399
+ describe("fixture-driven integration", () => {
400
+ let stdoutBuf: string[];
401
+ let dir: string;
402
+
403
+ beforeEach(async () => {
404
+ stdoutBuf = [];
405
+ vi.spyOn(console, "log").mockImplementation((s: string) => { stdoutBuf.push(s); });
406
+ vi.spyOn(console, "error").mockImplementation(() => {});
407
+ buildMock.mockReset();
408
+ loadChantConfigMock.mockReset();
409
+ loadChantConfigMock.mockResolvedValue({ config: { environments: [{ name: "prod" }] } });
410
+ dir = await mkdtemp(join(tmpdir(), "chant-scenario-integration-"));
411
+ await writeFile(
412
+ join(dir, "prod-baseline.json"),
413
+ JSON.stringify(
414
+ snap({
415
+ resources: {
416
+ webBucket: meta({ type: "AWS::S3::Bucket", physicalId: "arn:aws:s3:::web" }),
417
+ },
418
+ }),
419
+ ),
420
+ );
421
+ });
422
+
423
+ afterEach(async () => {
424
+ await rm(dir, { recursive: true, force: true });
425
+ });
426
+
427
+ test("the committed fixture: the declared project as-is is plan-neutral", async () => {
428
+ const fixturePath = join(dir, "prod-baseline.json");
429
+ const scenario = Scenario("web stack is plan-neutral", { given: snapshot(fixturePath), expect: { noop: true } });
430
+ buildMock.mockResolvedValue(makeBuildResult({ aws: ["webBucket"] }, { neutralityCheck: scenario }));
431
+
432
+ const exit = await runScenarioCheck({ args: makeArgs(), plugins: [], serializers: [] });
433
+
434
+ expect(exit).toBe(0);
435
+ expect(stdoutBuf.join("\n")).toContain("PASS");
436
+ });
437
+
438
+ test("dropping the declaration from source, against the same fixture, fails the scenario with a legible diff naming the resource", async () => {
439
+ const fixturePath = join(dir, "prod-baseline.json");
440
+ const scenario = Scenario("web stack is plan-neutral", { given: snapshot(fixturePath), expect: { noop: true } });
441
+ // The mutation: webBucket no longer declared (dropped from source), same
442
+ // committed fixture. The fixture still remembers it as owned+live, so the
443
+ // plan now proposes deleting it — exactly the silent-delete the issue
444
+ // describes chant having no way to catch today.
445
+ buildMock.mockResolvedValue(makeBuildResult({}, { neutralityCheck: scenario }));
446
+
447
+ const exit = await runScenarioCheck({ args: makeArgs(), plugins: [], serializers: [] });
448
+
449
+ expect(exit).toBe(1);
450
+ const out = stdoutBuf.join("\n");
451
+ expect(out).toContain("FAIL");
452
+ expect(out).toContain("noop:");
453
+ expect(out).toContain("1 delete");
454
+ expect(out).toContain("webBucket");
455
+ });
456
+ });