@intentius/chant 0.56.0 → 0.58.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (515) hide show
  1. package/dist/audit/catalog.d.ts +51 -1
  2. package/dist/audit/catalog.d.ts.map +1 -1
  3. package/dist/audit/core.d.ts +20 -1
  4. package/dist/audit/core.d.ts.map +1 -1
  5. package/dist/audit/discover.d.ts +14 -8
  6. package/dist/audit/discover.d.ts.map +1 -1
  7. package/dist/audit/lineage.d.ts +10 -0
  8. package/dist/audit/lineage.d.ts.map +1 -0
  9. package/dist/audit/prior-art.d.ts +385 -0
  10. package/dist/audit/prior-art.d.ts.map +1 -0
  11. package/dist/audit/proof.d.ts +16 -0
  12. package/dist/audit/proof.d.ts.map +1 -1
  13. package/dist/audit/report-html.d.ts.map +1 -1
  14. package/dist/audit/report-model.d.ts +8 -1
  15. package/dist/audit/report-model.d.ts.map +1 -1
  16. package/dist/audit/report.d.ts.map +1 -1
  17. package/dist/audit/rules-doc.d.ts +3 -0
  18. package/dist/audit/rules-doc.d.ts.map +1 -1
  19. package/dist/audit/secrets.d.ts +15 -0
  20. package/dist/audit/secrets.d.ts.map +1 -1
  21. package/dist/audit/terraform-state.d.ts +46 -0
  22. package/dist/audit/terraform-state.d.ts.map +1 -0
  23. package/dist/build.d.ts +17 -1
  24. package/dist/build.d.ts.map +1 -1
  25. package/dist/claimed-fields.d.ts +161 -0
  26. package/dist/claimed-fields.d.ts.map +1 -0
  27. package/dist/cli/build-params-cli.d.ts +2 -2
  28. package/dist/cli/command-group.d.ts +14 -0
  29. package/dist/cli/command-group.d.ts.map +1 -1
  30. package/dist/cli/commands/audit.d.ts +17 -1
  31. package/dist/cli/commands/audit.d.ts.map +1 -1
  32. package/dist/cli/commands/build.d.ts.map +1 -1
  33. package/dist/cli/commands/check-lexicon-docs.d.ts +1 -1
  34. package/dist/cli/commands/check-lexicon-examples.d.ts +16 -6
  35. package/dist/cli/commands/check-lexicon-examples.d.ts.map +1 -1
  36. package/dist/cli/commands/import.d.ts.map +1 -1
  37. package/dist/cli/commands/init-lexicon/templates/codegen.d.ts +1 -0
  38. package/dist/cli/commands/init-lexicon/templates/codegen.d.ts.map +1 -1
  39. package/dist/cli/commands/init-lexicon/templates/project.d.ts +7 -0
  40. package/dist/cli/commands/init-lexicon/templates/project.d.ts.map +1 -1
  41. package/dist/cli/commands/init-lexicon.d.ts.map +1 -1
  42. package/dist/cli/commands/init.d.ts.map +1 -1
  43. package/dist/cli/commands/lint.d.ts.map +1 -1
  44. package/dist/cli/commands/onboard.d.ts +2 -0
  45. package/dist/cli/commands/onboard.d.ts.map +1 -1
  46. package/dist/cli/handlers/build.d.ts.map +1 -1
  47. package/dist/cli/handlers/lifecycle.d.ts.map +1 -1
  48. package/dist/cli/handlers/operator.d.ts +82 -16
  49. package/dist/cli/handlers/operator.d.ts.map +1 -1
  50. package/dist/cli/handlers/run.d.ts +49 -42
  51. package/dist/cli/handlers/run.d.ts.map +1 -1
  52. package/dist/cli/main.d.ts +28 -1
  53. package/dist/cli/main.d.ts.map +1 -1
  54. package/dist/cli/mcp/op-tools.d.ts +16 -1
  55. package/dist/cli/mcp/op-tools.d.ts.map +1 -1
  56. package/dist/cli/mcp/resource-handlers.d.ts.map +1 -1
  57. package/dist/cli/mcp/resources/context.d.ts.map +1 -1
  58. package/dist/cli/registry.d.ts +26 -9
  59. package/dist/cli/registry.d.ts.map +1 -1
  60. package/dist/codegen/generate-post-synth-barrel.d.ts +1 -1
  61. package/dist/components/auto-release.d.ts +10 -20
  62. package/dist/components/auto-release.d.ts.map +1 -1
  63. package/dist/components/capability.d.ts +4 -4
  64. package/dist/components/cli-support.d.ts +20 -21
  65. package/dist/components/cli-support.d.ts.map +1 -1
  66. package/dist/components/component.d.ts +3 -3
  67. package/dist/components/component.d.ts.map +1 -1
  68. package/dist/components/driver-output.d.ts.map +1 -1
  69. package/dist/components/driver.d.ts +66 -36
  70. package/dist/components/driver.d.ts.map +1 -1
  71. package/dist/components/index.d.ts +1 -2
  72. package/dist/components/index.d.ts.map +1 -1
  73. package/dist/components/manifest-persistence.d.ts +1 -9
  74. package/dist/components/manifest-persistence.d.ts.map +1 -1
  75. package/dist/components/run-progress.d.ts +6 -6
  76. package/dist/components/run-progress.d.ts.map +1 -1
  77. package/dist/components/verbs/run-agent.d.ts +9 -15
  78. package/dist/components/verbs/run-agent.d.ts.map +1 -1
  79. package/dist/components/verbs/verify.d.ts +2 -2
  80. package/dist/deep-observation.d.ts +17 -0
  81. package/dist/deep-observation.d.ts.map +1 -1
  82. package/dist/discovery/files.d.ts +4 -4
  83. package/dist/discovery/fold-import.d.ts.map +1 -1
  84. package/dist/discovery/param-deps.d.ts +33 -0
  85. package/dist/discovery/param-deps.d.ts.map +1 -1
  86. package/dist/discovery/sandbox/bundle.d.ts.map +1 -1
  87. package/dist/discovery/sandbox/config-wire.d.ts +2 -2
  88. package/dist/fold/foldable-helpers.d.ts +5 -1
  89. package/dist/fold/foldable-helpers.d.ts.map +1 -1
  90. package/dist/fold-provenance.d.ts +236 -0
  91. package/dist/fold-provenance.d.ts.map +1 -0
  92. package/dist/graph-ops.d.ts +1 -1
  93. package/dist/held-elsewhere.d.ts +85 -0
  94. package/dist/held-elsewhere.d.ts.map +1 -0
  95. package/dist/import/ir-utils.d.ts +1 -1
  96. package/dist/import/ir-utils.d.ts.map +1 -1
  97. package/dist/import/parser.d.ts +33 -0
  98. package/dist/import/parser.d.ts.map +1 -1
  99. package/dist/index.d.ts +3 -0
  100. package/dist/index.d.ts.map +1 -1
  101. package/dist/kubectl-context.d.ts +3 -3
  102. package/dist/lexicon-config.d.ts +4 -4
  103. package/dist/lexicon-plugin-helpers.d.ts +1 -1
  104. package/dist/lexicon.d.ts +104 -14
  105. package/dist/lexicon.d.ts.map +1 -1
  106. package/dist/lifecycle/change-set.d.ts +13 -0
  107. package/dist/lifecycle/change-set.d.ts.map +1 -1
  108. package/dist/lifecycle/deep-diff.d.ts +133 -6
  109. package/dist/lifecycle/deep-diff.d.ts.map +1 -1
  110. package/dist/lifecycle/deep-observe.d.ts.map +1 -1
  111. package/dist/lifecycle/gate-ledger.d.ts +71 -3
  112. package/dist/lifecycle/gate-ledger.d.ts.map +1 -1
  113. package/dist/lifecycle/index.d.ts +1 -0
  114. package/dist/lifecycle/index.d.ts.map +1 -1
  115. package/dist/lifecycle/release-ledger.d.ts +2 -2
  116. package/dist/lifecycle/run-ledger.d.ts +53 -0
  117. package/dist/lifecycle/run-ledger.d.ts.map +1 -0
  118. package/dist/lifecycle/symptoms.d.ts +3 -3
  119. package/dist/lifecycle/symptoms.d.ts.map +1 -1
  120. package/dist/lint/config.d.ts +81 -2
  121. package/dist/lint/config.d.ts.map +1 -1
  122. package/dist/lint/post-synth.d.ts +55 -2
  123. package/dist/lint/post-synth.d.ts.map +1 -1
  124. package/dist/lint/rules/__fixtures__/comp/comp004/pass/neo4j-cluster.component.d.ts +1 -1
  125. package/dist/lint/rules/__fixtures__/comp/comp004/pass/neo4j-cluster.component.d.ts.map +1 -1
  126. package/dist/lint/rules/__fixtures__/comp/comp004/pass/orders-table.component.d.ts +1 -1
  127. package/dist/lint/rules/__fixtures__/comp/comp004/pass/orders-table.component.d.ts.map +1 -1
  128. package/dist/lint/rules/comp/comp004-gate-needs-durable-runtime.d.ts +44 -0
  129. package/dist/lint/rules/comp/comp004-gate-needs-durable-runtime.d.ts.map +1 -0
  130. package/dist/lint/rules/comp/index.d.ts +1 -1
  131. package/dist/lint/rules/comp/index.d.ts.map +1 -1
  132. package/dist/lint/rules/op/index.d.ts +16 -0
  133. package/dist/lint/rules/op/index.d.ts.map +1 -0
  134. package/dist/lint/rules/op/ops012-activity-contract.d.ts +27 -0
  135. package/dist/lint/rules/op/ops012-activity-contract.d.ts.map +1 -0
  136. package/dist/lint/rules/op/ops013-step-output-ref.d.ts +61 -0
  137. package/dist/lint/rules/op/ops013-step-output-ref.d.ts.map +1 -0
  138. package/dist/lint/rules/op/ops014-converge-rule-refusals.d.ts +55 -0
  139. package/dist/lint/rules/op/ops014-converge-rule-refusals.d.ts.map +1 -0
  140. package/dist/lint/rules/op/support.d.ts +12 -0
  141. package/dist/lint/rules/op/support.d.ts.map +1 -0
  142. package/dist/lint/suppressions.d.ts +108 -0
  143. package/dist/lint/suppressions.d.ts.map +1 -0
  144. package/dist/live-endpoint.d.ts +1 -1
  145. package/dist/op/activities/activity-contracts.d.ts +54 -0
  146. package/dist/op/activities/activity-contracts.d.ts.map +1 -0
  147. package/dist/op/activities/apply.d.ts +262 -0
  148. package/dist/op/activities/apply.d.ts.map +1 -0
  149. package/dist/op/activities/build.d.ts +13 -0
  150. package/dist/op/activities/build.d.ts.map +1 -0
  151. package/dist/op/activities/converge.d.ts +110 -0
  152. package/dist/op/activities/converge.d.ts.map +1 -0
  153. package/dist/op/activities/env-teardown.d.ts +76 -0
  154. package/dist/op/activities/env-teardown.d.ts.map +1 -0
  155. package/dist/op/activities/guard-validate.d.ts +64 -0
  156. package/dist/op/activities/guard-validate.d.ts.map +1 -0
  157. package/dist/op/activities/http-check.d.ts +34 -0
  158. package/dist/op/activities/http-check.d.ts.map +1 -0
  159. package/dist/op/activities/index.d.ts +31 -0
  160. package/dist/op/activities/index.d.ts.map +1 -0
  161. package/dist/op/activities/lexicon-upgrade.d.ts +182 -0
  162. package/dist/op/activities/lexicon-upgrade.d.ts.map +1 -0
  163. package/dist/op/activities/lifecycle.d.ts +42 -0
  164. package/dist/op/activities/lifecycle.d.ts.map +1 -0
  165. package/dist/op/activities/pipeline-audit.d.ts +84 -0
  166. package/dist/op/activities/pipeline-audit.d.ts.map +1 -0
  167. package/dist/op/activities/policy.d.ts +17 -0
  168. package/dist/op/activities/policy.d.ts.map +1 -0
  169. package/dist/op/activities/reconcile.d.ts +89 -0
  170. package/dist/op/activities/reconcile.d.ts.map +1 -0
  171. package/dist/op/activities/shell.d.ts +13 -0
  172. package/dist/op/activities/shell.d.ts.map +1 -0
  173. package/dist/op/activities/teardown.d.ts +10 -0
  174. package/dist/op/activities/teardown.d.ts.map +1 -0
  175. package/dist/op/activities/util.d.ts +7 -0
  176. package/dist/op/activities/util.d.ts.map +1 -0
  177. package/dist/op/activities/wait.d.ts +28 -0
  178. package/dist/op/activities/wait.d.ts.map +1 -0
  179. package/dist/op/activities/workflow-audit.d.ts +92 -0
  180. package/dist/op/activities/workflow-audit.d.ts.map +1 -0
  181. package/dist/op/activity-contract-registry.d.ts +76 -0
  182. package/dist/op/activity-contract-registry.d.ts.map +1 -0
  183. package/dist/op/activity-contract.d.ts +25 -18
  184. package/dist/op/activity-contract.d.ts.map +1 -1
  185. package/dist/op/activity-failure.d.ts +28 -0
  186. package/dist/op/activity-failure.d.ts.map +1 -0
  187. package/dist/op/activity-profiles.d.ts +118 -0
  188. package/dist/op/activity-profiles.d.ts.map +1 -0
  189. package/dist/op/activity-registry.d.ts +22 -38
  190. package/dist/op/activity-registry.d.ts.map +1 -1
  191. package/dist/op/activity-runtime.d.ts +3 -14
  192. package/dist/op/activity-runtime.d.ts.map +1 -1
  193. package/dist/op/builders.d.ts +84 -29
  194. package/dist/op/builders.d.ts.map +1 -1
  195. package/dist/op/composites/apply-op.d.ts +113 -0
  196. package/dist/op/composites/apply-op.d.ts.map +1 -0
  197. package/dist/op/composites/converge-op.d.ts +120 -0
  198. package/dist/op/composites/converge-op.d.ts.map +1 -0
  199. package/dist/op/composites/index.d.ts +26 -0
  200. package/dist/op/composites/index.d.ts.map +1 -0
  201. package/dist/op/composites/lexicon-upgrade-op.d.ts +78 -0
  202. package/dist/op/composites/lexicon-upgrade-op.d.ts.map +1 -0
  203. package/dist/op/composites/pipeline-audit-op.d.ts +50 -0
  204. package/dist/op/composites/pipeline-audit-op.d.ts.map +1 -0
  205. package/dist/op/composites/reconcile-op.d.ts +57 -0
  206. package/dist/op/composites/reconcile-op.d.ts.map +1 -0
  207. package/dist/op/composites/watch-op.d.ts +60 -0
  208. package/dist/op/composites/watch-op.d.ts.map +1 -0
  209. package/dist/op/composites/workflow-audit-op.d.ts +57 -0
  210. package/dist/op/composites/workflow-audit-op.d.ts.map +1 -0
  211. package/dist/op/converge-rule.d.ts +12 -10
  212. package/dist/op/converge-rule.d.ts.map +1 -1
  213. package/dist/op/cron.d.ts +51 -0
  214. package/dist/op/cron.d.ts.map +1 -0
  215. package/dist/op/discover.d.ts +9 -0
  216. package/dist/op/discover.d.ts.map +1 -1
  217. package/dist/op/duration.d.ts +11 -0
  218. package/dist/op/duration.d.ts.map +1 -0
  219. package/dist/op/emulator-lifecycle.d.ts.map +1 -1
  220. package/dist/op/gate.d.ts +90 -0
  221. package/dist/op/gate.d.ts.map +1 -0
  222. package/dist/op/generate-pipeline.d.ts +9 -0
  223. package/dist/op/generate-pipeline.d.ts.map +1 -1
  224. package/dist/op/index.d.ts +23 -7
  225. package/dist/op/index.d.ts.map +1 -1
  226. package/dist/op/local-executor.d.ts +114 -28
  227. package/dist/op/local-executor.d.ts.map +1 -1
  228. package/dist/op/local-output.d.ts +11 -1
  229. package/dist/op/local-output.d.ts.map +1 -1
  230. package/dist/op/op-ir.d.ts +184 -0
  231. package/dist/op/op-ir.d.ts.map +1 -0
  232. package/dist/op/op-verb-class.d.ts +10 -2
  233. package/dist/op/op-verb-class.d.ts.map +1 -1
  234. package/dist/op/operator.d.ts +30 -5
  235. package/dist/op/operator.d.ts.map +1 -1
  236. package/dist/op/receipt-store.d.ts +1 -1
  237. package/dist/op/receipt-store.d.ts.map +1 -1
  238. package/dist/op/resource.d.ts +15 -1
  239. package/dist/op/resource.d.ts.map +1 -1
  240. package/dist/op/runtime.d.ts +200 -0
  241. package/dist/op/runtime.d.ts.map +1 -0
  242. package/dist/op/runtimes/local.d.ts +29 -0
  243. package/dist/op/runtimes/local.d.ts.map +1 -0
  244. package/dist/op/step-output-ref.d.ts +15 -13
  245. package/dist/op/step-output-ref.d.ts.map +1 -1
  246. package/dist/op/types.d.ts +79 -27
  247. package/dist/op/types.d.ts.map +1 -1
  248. package/dist/ownership.d.ts +1 -1
  249. package/dist/provenance.d.ts +21 -0
  250. package/dist/provenance.d.ts.map +1 -1
  251. package/dist/serializer-walker.d.ts.map +1 -1
  252. package/dist/stack-output.d.ts +5 -0
  253. package/dist/stack-output.d.ts.map +1 -1
  254. package/package.json +6 -1
  255. package/src/audit/catalog.test.ts +79 -3
  256. package/src/audit/catalog.ts +111 -13
  257. package/src/audit/core.test.ts +26 -0
  258. package/src/audit/core.ts +61 -17
  259. package/src/audit/discover.test.ts +39 -0
  260. package/src/audit/discover.ts +91 -15
  261. package/src/audit/lineage.ts +100 -0
  262. package/src/audit/prior-art.ts +153 -0
  263. package/src/audit/proof.test.ts +67 -0
  264. package/src/audit/proof.ts +70 -0
  265. package/src/audit/report-html.ts +9 -4
  266. package/src/audit/report-model.ts +16 -2
  267. package/src/audit/report.test.ts +33 -0
  268. package/src/audit/report.ts +5 -3
  269. package/src/audit/rules-doc.test.ts +34 -2
  270. package/src/audit/rules-doc.ts +82 -9
  271. package/src/audit/secrets.ts +15 -3
  272. package/src/audit/terraform-state.test.ts +144 -0
  273. package/src/audit/terraform-state.ts +131 -0
  274. package/src/build.ts +31 -1
  275. package/src/claimed-fields.test.ts +145 -0
  276. package/src/claimed-fields.ts +208 -0
  277. package/src/cli/build-params-cli.ts +2 -2
  278. package/src/cli/command-group.ts +14 -0
  279. package/src/cli/commands/__fixtures__/audit-terraform/infra/.terraform/providers/lock.txt +1 -0
  280. package/src/cli/commands/__fixtures__/audit-terraform/infra/main.tf +1 -0
  281. package/src/cli/commands/__fixtures__/audit-terraform/infra/modules/foo/main.tf +1 -0
  282. package/src/cli/commands/__fixtures__/audit-terraform/infra/variables.tf +3 -0
  283. package/src/cli/commands/__fixtures__/init-lexicon-output/package.json +13 -4
  284. package/src/cli/commands/__fixtures__/init-lexicon-output/src/codegen/docs-cli.ts +4 -0
  285. package/src/cli/commands/__fixtures__/init-lexicon-output/src/codegen/package.ts +1 -0
  286. package/src/cli/commands/__fixtures__/init-lexicon-output/src/package-cli.ts +21 -0
  287. package/src/cli/commands/__snapshots__/init-lexicon.test.ts.snap +1 -0
  288. package/src/cli/commands/audit.test.ts +189 -11
  289. package/src/cli/commands/audit.ts +73 -27
  290. package/src/cli/commands/build.test.ts +292 -9
  291. package/src/cli/commands/build.ts +156 -24
  292. package/src/cli/commands/check-lexicon-docs.ts +1 -1
  293. package/src/cli/commands/check-lexicon-examples.test.ts +29 -1
  294. package/src/cli/commands/check-lexicon-examples.ts +42 -10
  295. package/src/cli/commands/import.ts +6 -0
  296. package/src/cli/commands/init-lexicon/templates/codegen.ts +9 -0
  297. package/src/cli/commands/init-lexicon/templates/project.ts +44 -4
  298. package/src/cli/commands/init-lexicon.test.ts +97 -2
  299. package/src/cli/commands/init-lexicon.ts +4 -2
  300. package/src/cli/commands/init.test.ts +35 -1
  301. package/src/cli/commands/init.ts +14 -3
  302. package/src/cli/commands/lint.test.ts +48 -0
  303. package/src/cli/commands/lint.ts +123 -0
  304. package/src/cli/commands/onboard.test.ts +75 -0
  305. package/src/cli/commands/onboard.ts +54 -23
  306. package/src/cli/handlers/build.test.ts +81 -1
  307. package/src/cli/handlers/build.ts +15 -1
  308. package/src/cli/handlers/graph.test.ts +3 -3
  309. package/src/cli/handlers/lifecycle.test.ts +195 -12
  310. package/src/cli/handlers/lifecycle.ts +123 -6
  311. package/src/cli/handlers/misc.ts +1 -1
  312. package/src/cli/handlers/operator.test.ts +183 -11
  313. package/src/cli/handlers/operator.ts +260 -55
  314. package/src/cli/handlers/run.test.ts +431 -1287
  315. package/src/cli/handlers/run.ts +365 -915
  316. package/src/cli/main.test.ts +23 -15
  317. package/src/cli/main.ts +88 -33
  318. package/src/cli/mcp/op-tools.ts +147 -88
  319. package/src/cli/mcp/resource-handlers.ts +10 -37
  320. package/src/cli/mcp/resources/context.ts +11 -12
  321. package/src/cli/mcp/server.test.ts +25 -27
  322. package/src/cli/mcp/server.ts +2 -2
  323. package/src/cli/registry.ts +26 -9
  324. package/src/codegen/docs.ts +1 -1
  325. package/src/codegen/generate-post-synth-barrel.test.ts +1 -1
  326. package/src/codegen/generate-post-synth-barrel.ts +1 -1
  327. package/src/components/README.md +3 -2
  328. package/src/components/auto-release.test.ts +4 -20
  329. package/src/components/auto-release.ts +17 -42
  330. package/src/components/capability.ts +4 -4
  331. package/src/components/cli-support.test.ts +10 -5
  332. package/src/components/cli-support.ts +52 -44
  333. package/src/components/component.schema.json +2 -2
  334. package/src/components/component.ts +3 -3
  335. package/src/components/driver-output.ts +14 -2
  336. package/src/components/driver.test.ts +92 -22
  337. package/src/components/driver.ts +193 -83
  338. package/src/components/index.ts +1 -6
  339. package/src/components/manifest-persistence.test.ts +2 -20
  340. package/src/components/manifest-persistence.ts +4 -24
  341. package/src/components/pilots/pilots-lint-clean.test.ts +11 -10
  342. package/src/components/run-progress.ts +6 -6
  343. package/src/components/verbs/run-agent.ts +11 -17
  344. package/src/components/verbs/verify.ts +2 -2
  345. package/src/deep-observation.test.ts +75 -0
  346. package/src/deep-observation.ts +48 -0
  347. package/src/discovery/collect.test.ts +3 -3
  348. package/src/discovery/files.ts +4 -4
  349. package/src/discovery/fold-import.ts +105 -2
  350. package/src/discovery/param-deps.test.ts +112 -1
  351. package/src/discovery/param-deps.ts +193 -0
  352. package/src/discovery/sandbox/bundle.ts +15 -1
  353. package/src/discovery/sandbox/config-wire.test.ts +2 -2
  354. package/src/discovery/sandbox/config-wire.ts +2 -2
  355. package/src/fold/fold.test.ts +56 -0
  356. package/src/fold/foldable-helpers.ts +81 -1
  357. package/src/fold-provenance-exclusion.test.ts +186 -0
  358. package/src/fold-provenance.test.ts +495 -0
  359. package/src/fold-provenance.ts +441 -0
  360. package/src/graph-ops.test.ts +2 -2
  361. package/src/graph-ops.ts +1 -1
  362. package/src/held-elsewhere.test.ts +102 -0
  363. package/src/held-elsewhere.ts +114 -0
  364. package/src/import/ir-utils.ts +11 -1
  365. package/src/import/parser.ts +35 -0
  366. package/src/index.ts +3 -0
  367. package/src/kubectl-context.ts +3 -3
  368. package/src/lexicon-config.test.ts +5 -5
  369. package/src/lexicon-config.ts +4 -4
  370. package/src/lexicon-plugin-helpers.ts +1 -1
  371. package/src/lexicon.ts +120 -14
  372. package/src/lifecycle/change-set.test.ts +49 -0
  373. package/src/lifecycle/change-set.ts +67 -0
  374. package/src/lifecycle/converge-ledger.ts +1 -1
  375. package/src/lifecycle/deep-diff.test.ts +267 -14
  376. package/src/lifecycle/deep-diff.ts +217 -14
  377. package/src/lifecycle/deep-observe.test.ts +3 -1
  378. package/src/lifecycle/deep-observe.ts +16 -6
  379. package/src/lifecycle/gate-ledger.test.ts +74 -2
  380. package/src/lifecycle/gate-ledger.ts +143 -36
  381. package/src/lifecycle/index.ts +1 -0
  382. package/src/lifecycle/release-ledger.ts +2 -2
  383. package/src/lifecycle/run-ledger.test.ts +268 -0
  384. package/src/lifecycle/run-ledger.ts +201 -0
  385. package/src/lifecycle/symptoms.ts +3 -3
  386. package/src/lint/config.test.ts +82 -1
  387. package/src/lint/config.ts +112 -3
  388. package/src/lint/policy.test.ts +1 -1
  389. package/src/lint/post-synth.test.ts +31 -0
  390. package/src/lint/post-synth.ts +54 -1
  391. package/src/lint/rules/__fixtures__/comp/comp004/pass/neo4j-cluster.component.ts +2 -2
  392. package/src/lint/rules/__fixtures__/comp/comp004/pass/orders-table.component.ts +1 -1
  393. package/src/lint/rules/comp/comp.test.ts +1 -1
  394. package/src/lint/rules/comp/comp004-gate-needs-durable-runtime.ts +74 -0
  395. package/src/lint/rules/comp/index.ts +3 -3
  396. package/src/lint/rules/op/index.ts +23 -0
  397. package/src/lint/rules/op/ops012-activity-contract.test.ts +161 -0
  398. package/src/lint/rules/op/ops012-activity-contract.ts +64 -0
  399. package/src/lint/rules/op/ops013-step-output-ref.test.ts +237 -0
  400. package/src/lint/rules/op/ops013-step-output-ref.ts +98 -0
  401. package/src/lint/rules/op/ops014-converge-rule-refusals.test.ts +263 -0
  402. package/src/lint/rules/op/ops014-converge-rule-refusals.ts +179 -0
  403. package/src/lint/rules/op/support.ts +11 -0
  404. package/src/lint/suppressions.test.ts +163 -0
  405. package/src/lint/suppressions.ts +204 -0
  406. package/src/live-endpoint.ts +1 -1
  407. package/src/op/activities/activity-contracts.ts +66 -0
  408. package/src/op/activities/apply.test.ts +567 -0
  409. package/src/op/activities/apply.ts +662 -0
  410. package/src/op/activities/build.ts +26 -0
  411. package/src/op/activities/converge.test.ts +330 -0
  412. package/src/op/activities/converge.ts +449 -0
  413. package/src/op/activities/env-teardown.test.ts +187 -0
  414. package/src/op/activities/env-teardown.ts +171 -0
  415. package/src/op/activities/guard-validate.test.ts +139 -0
  416. package/src/op/activities/guard-validate.ts +223 -0
  417. package/src/op/activities/http-check.test.ts +48 -0
  418. package/src/op/activities/http-check.ts +55 -0
  419. package/src/op/activities/index.ts +119 -0
  420. package/src/op/activities/lexicon-upgrade.test.ts +492 -0
  421. package/src/op/activities/lexicon-upgrade.ts +673 -0
  422. package/src/op/activities/lifecycle.ts +85 -0
  423. package/src/op/activities/pipeline-audit.test.ts +67 -0
  424. package/src/op/activities/pipeline-audit.ts +201 -0
  425. package/src/op/activities/policy.test.ts +19 -0
  426. package/src/op/activities/policy.ts +37 -0
  427. package/src/op/activities/reconcile.test.ts +81 -0
  428. package/src/op/activities/reconcile.ts +187 -0
  429. package/src/op/activities/shell.ts +26 -0
  430. package/src/op/activities/teardown.ts +22 -0
  431. package/src/op/activities/util.ts +6 -0
  432. package/src/op/activities/wait.ts +69 -0
  433. package/src/op/activities/workflow-audit.test.ts +90 -0
  434. package/src/op/activities/workflow-audit.ts +203 -0
  435. package/src/op/activity-contract-registry.test.ts +98 -0
  436. package/src/op/activity-contract-registry.ts +114 -0
  437. package/src/op/activity-contract.test.ts +1 -1
  438. package/src/op/activity-contract.ts +37 -33
  439. package/src/op/activity-failure.ts +34 -0
  440. package/src/op/activity-profiles.test.ts +56 -0
  441. package/src/op/activity-profiles.ts +116 -0
  442. package/src/op/activity-registry.test.ts +37 -28
  443. package/src/op/activity-registry.ts +27 -56
  444. package/src/op/activity-runtime.ts +3 -47
  445. package/src/op/builders-exports.test.ts +0 -49
  446. package/src/op/builders-typed.test.ts +164 -0
  447. package/src/op/builders.ts +123 -39
  448. package/src/op/composites/apply-op.ts +208 -0
  449. package/src/op/composites/composites.test.ts +366 -0
  450. package/src/op/composites/converge-op.test.ts +168 -0
  451. package/src/op/composites/converge-op.ts +214 -0
  452. package/src/op/composites/index.ts +26 -0
  453. package/src/op/composites/lexicon-upgrade-op.test.ts +68 -0
  454. package/src/op/composites/lexicon-upgrade-op.ts +134 -0
  455. package/src/op/composites/pipeline-audit-op.test.ts +25 -0
  456. package/src/op/composites/pipeline-audit-op.ts +78 -0
  457. package/src/op/composites/reconcile-op.test.ts +51 -0
  458. package/src/op/composites/reconcile-op.ts +107 -0
  459. package/src/op/composites/watch-op.ts +108 -0
  460. package/src/op/composites/workflow-audit-op.test.ts +25 -0
  461. package/src/op/composites/workflow-audit-op.ts +88 -0
  462. package/src/op/converge-rule.ts +12 -10
  463. package/src/op/cron.test.ts +128 -0
  464. package/src/op/cron.ts +149 -0
  465. package/src/op/discover-root.test.ts +105 -1
  466. package/src/op/discover.test.ts +6 -2
  467. package/src/op/discover.ts +84 -16
  468. package/src/op/duration.ts +21 -0
  469. package/src/op/effect-step.test.ts +26 -11
  470. package/src/op/emulator-lifecycle.ts +1 -2
  471. package/src/op/gate.ts +155 -0
  472. package/src/op/generate-pipeline.test.ts +65 -3
  473. package/src/op/generate-pipeline.ts +19 -2
  474. package/src/op/index.ts +39 -6
  475. package/src/op/local-executor.test.ts +163 -34
  476. package/src/op/local-executor.ts +370 -102
  477. package/src/op/local-output.test.ts +95 -11
  478. package/src/op/local-output.ts +33 -4
  479. package/src/op/op-ir.test.ts +281 -0
  480. package/src/op/op-ir.ts +417 -0
  481. package/src/op/op-verb-class.ts +19 -5
  482. package/src/op/op.test.ts +26 -5
  483. package/src/op/operator.test.ts +175 -6
  484. package/src/op/operator.ts +63 -16
  485. package/src/op/receipt-store.ts +1 -1
  486. package/src/op/resource.ts +20 -2
  487. package/src/op/runtime.ts +203 -0
  488. package/src/op/runtimes/local.test.ts +181 -0
  489. package/src/op/runtimes/local.ts +212 -0
  490. package/src/op/step-output-ref.ts +15 -13
  491. package/src/op/types.ts +84 -24
  492. package/src/ownership.ts +1 -1
  493. package/src/provenance.test.ts +6 -0
  494. package/src/provenance.ts +21 -1
  495. package/src/serializer-walker.test.ts +42 -0
  496. package/src/serializer-walker.ts +21 -2
  497. package/src/stack-output.ts +6 -1
  498. package/dist/cli/handlers/op-progress.d.ts +0 -90
  499. package/dist/cli/handlers/op-progress.d.ts.map +0 -1
  500. package/dist/cli/handlers/run-client.d.ts +0 -127
  501. package/dist/cli/handlers/run-client.d.ts.map +0 -1
  502. package/dist/cli/handlers/run-report.d.ts +0 -5
  503. package/dist/cli/handlers/run-report.d.ts.map +0 -1
  504. package/dist/components/temporal-codegen-loader.d.ts +0 -27
  505. package/dist/components/temporal-codegen-loader.d.ts.map +0 -1
  506. package/dist/lint/rules/comp/comp004-gate-needs-temporal.d.ts +0 -37
  507. package/dist/lint/rules/comp/comp004-gate-needs-temporal.d.ts.map +0 -1
  508. package/src/cli/handlers/op-progress.test.ts +0 -268
  509. package/src/cli/handlers/op-progress.ts +0 -248
  510. package/src/cli/handlers/run-client.test.ts +0 -82
  511. package/src/cli/handlers/run-client.ts +0 -219
  512. package/src/cli/handlers/run-report.test.ts +0 -62
  513. package/src/cli/handlers/run-report.ts +0 -122
  514. package/src/components/temporal-codegen-loader.ts +0 -41
  515. package/src/lint/rules/comp/comp004-gate-needs-temporal.ts +0 -66
@@ -0,0 +1,186 @@
1
+ /**
2
+ * chant #2161 — the fold provenance record provably never reaches the
3
+ * apply-bound output.
4
+ *
5
+ * Appliers consume the serialized build outputs, so the exclusion has to hold
6
+ * at the serializer-input boundary, which is where `effect-receipt-exclusion.test.ts`
7
+ * puts the same question for receipts. These tests drive a real fold build over
8
+ * a composite fixture, spy on the serializer, and then drive the serialized
9
+ * output through a mock applier.
10
+ *
11
+ * The provenance markers are proven present in `BuildResult.foldProvenance`
12
+ * FIRST, so the string search below is known to be capable of firing before
13
+ * anything asserts that it does not.
14
+ */
15
+ import { describe, test, expect, beforeEach, afterEach } from "vitest";
16
+ import { mkdir, writeFile, rm, realpath } from "node:fs/promises";
17
+ import { join, dirname, resolve } from "node:path";
18
+ import { fileURLToPath } from "node:url";
19
+ import { build } from "./build";
20
+ import { walkValue } from "./serializer-walker";
21
+ import type { Serializer, SerializeContext } from "./serializer";
22
+ import type { Declarable } from "./declarable";
23
+
24
+ const thisDir = dirname(fileURLToPath(import.meta.url));
25
+ const repoRoot = resolve(thisDir, "../../..");
26
+ const compositePath = resolve(thisDir, "./composite");
27
+
28
+ const LEXICON = "@intentius/chant-lexicon-aws";
29
+ const LEXICON_NAME = "aws";
30
+
31
+ /** Every string that only appears if a provenance record leaked. */
32
+ const PROVENANCE_MARKERS = [
33
+ "composite-parameter",
34
+ "composite-literal",
35
+ "chant.provenance",
36
+ "foldProvenance",
37
+ "WebService",
38
+ ];
39
+
40
+ const COMPOSITES = `
41
+ import { Bucket } from ${JSON.stringify(LEXICON)};
42
+ import { Composite } from ${JSON.stringify(compositePath)};
43
+
44
+ export const WebService = Composite((props) => {
45
+ const bucket = new Bucket({
46
+ BucketName: props.name,
47
+ VersioningConfiguration: { Status: "Enabled" },
48
+ });
49
+ return { bucket };
50
+ }, "WebService");
51
+ `;
52
+
53
+ const MAIN = `
54
+ import { WebService } from "../composites";
55
+ export const web = WebService({ name: "data" });
56
+ `;
57
+
58
+ /** Renders every entity's real props, the way a lexicon serializer does. */
59
+ function renderDocument(entities: Map<string, Declarable>): string {
60
+ const names = new Map<Declarable, string>();
61
+ for (const [name, entity] of entities) names.set(entity, name);
62
+ const resources: Record<string, unknown> = {};
63
+ for (const [name, entity] of [...entities].sort(([a], [b]) => a.localeCompare(b))) {
64
+ resources[name] = {
65
+ Type: entity.entityType,
66
+ Properties: walkValue((entity as unknown as { props: unknown }).props, names, {
67
+ attrRef: (logicalName, attribute) => ({ GetAtt: [logicalName, attribute] }),
68
+ resourceRef: (logicalName) => ({ Ref: logicalName }),
69
+ propertyDeclarable: (e, walk) => walk((e as unknown as { props: unknown }).props),
70
+ }),
71
+ };
72
+ }
73
+ return JSON.stringify({ Resources: resources }, null, 2);
74
+ }
75
+
76
+ let seq = 0;
77
+
78
+ describe("fold provenance apply write-exclusion (#2161)", () => {
79
+ let testDir: string;
80
+ let srcDir: string;
81
+
82
+ beforeEach(async () => {
83
+ const dir = join(repoRoot, ".cache", `chant-2161-excl-${process.pid}-${seq++}`);
84
+ await rm(dir, { recursive: true, force: true });
85
+ await mkdir(dir, { recursive: true });
86
+ testDir = await realpath(dir);
87
+ srcDir = join(testDir, "src");
88
+ await mkdir(srcDir, { recursive: true });
89
+ await writeFile(join(testDir, "composites.ts"), COMPOSITES);
90
+ await writeFile(join(srcDir, "main.ts"), MAIN);
91
+ });
92
+
93
+ afterEach(async () => {
94
+ await rm(testDir, { recursive: true, force: true });
95
+ });
96
+
97
+ test("the serializer sees the entities and no provenance channel at all", async () => {
98
+ const seen: Array<{ entities: string[]; contextKeys: string[]; enumerable: string[] }> = [];
99
+ const serializer: Serializer = {
100
+ name: LEXICON_NAME,
101
+ rulePrefix: "TEST",
102
+ serialize: (entities, _outputs, context?: SerializeContext) => {
103
+ const enumerable: string[] = [];
104
+ for (const entity of entities.values()) {
105
+ enumerable.push(...Object.keys(entity));
106
+ // The record must not be reachable as an enumerable own property of
107
+ // `props` either, since that is what a serializer walks.
108
+ enumerable.push(...Object.keys((entity as unknown as { props: object }).props ?? {}));
109
+ }
110
+ seen.push({
111
+ entities: [...entities.keys()].sort(),
112
+ contextKeys: Object.keys(context ?? {}).sort(),
113
+ enumerable: [...new Set(enumerable)].sort(),
114
+ });
115
+ return renderDocument(entities);
116
+ },
117
+ };
118
+
119
+ const result = await build(srcDir, [serializer], undefined, { fold: true, lexicons: [LEXICON_NAME] });
120
+
121
+ expect(result.errors).toEqual([]);
122
+ // The probe fires: the record exists, and it carries the markers.
123
+ const record = JSON.stringify(result.foldProvenance);
124
+ expect(record).toContain("composite-parameter");
125
+ expect(record).toContain("composite-literal");
126
+ expect(record).toContain("WebService");
127
+
128
+ expect(seen).toHaveLength(1);
129
+ expect(seen[0].entities).toEqual(["webBucket"]);
130
+ // `SerializeContext` gained nothing. Provenance has no rendering, so unlike
131
+ // an effect receipt it is not handed to a serializer at all.
132
+ expect(seen[0].contextKeys).not.toContain("provenance");
133
+ expect(seen[0].contextKeys).not.toContain("foldProvenance");
134
+ expect(seen[0].contextKeys).not.toContain("pathOrigins");
135
+ for (const key of seen[0].enumerable) {
136
+ expect(PROVENANCE_MARKERS).not.toContain(key);
137
+ }
138
+ });
139
+
140
+ test("no provenance marker survives into the built document", async () => {
141
+ const serializer: Serializer = {
142
+ name: LEXICON_NAME,
143
+ rulePrefix: "TEST",
144
+ serialize: (entities) => renderDocument(entities),
145
+ };
146
+
147
+ const result = await build(srcDir, [serializer], undefined, { fold: true, lexicons: [LEXICON_NAME] });
148
+
149
+ expect(result.errors).toEqual([]);
150
+ expect(Object.keys(result.foldProvenance)).toEqual(["webBucket"]);
151
+
152
+ const document = result.outputs.get(LEXICON_NAME) as string;
153
+ expect(document).toContain("webBucket"); // the probe can see the document at all
154
+ for (const marker of PROVENANCE_MARKERS) {
155
+ expect(document, `"${marker}" leaked into the apply-bound document`).not.toContain(marker);
156
+ }
157
+ });
158
+
159
+ test("a mock apply driven from the build output writes the resource and nothing else", async () => {
160
+ const serializer: Serializer = {
161
+ name: LEXICON_NAME,
162
+ rulePrefix: "TEST",
163
+ serialize: (entities) => renderDocument(entities),
164
+ };
165
+
166
+ const result = await build(srcDir, [serializer], undefined, { fold: true, lexicons: [LEXICON_NAME] });
167
+ const document = JSON.parse(result.outputs.get(LEXICON_NAME) as string) as {
168
+ Resources: Record<string, { Type: string; Properties: Record<string, unknown> }>;
169
+ };
170
+
171
+ // The generic applier: everything in the document is desired; owned live
172
+ // resources absent from it are prune candidates.
173
+ const live = ["webBucket", "orphan"];
174
+ const desired = Object.keys(document.Resources);
175
+ const writes: Array<{ name: string; fields: string[] }> = [];
176
+ for (const name of desired) {
177
+ writes.push({ name, fields: Object.keys(document.Resources[name].Properties).sort() });
178
+ }
179
+ const pruneCandidates = live.filter((name) => !desired.includes(name));
180
+
181
+ expect(writes).toEqual([
182
+ { name: "webBucket", fields: ["BucketName", "VersioningConfiguration"] },
183
+ ]);
184
+ expect(pruneCandidates).toEqual(["orphan"]);
185
+ });
186
+ });
@@ -0,0 +1,495 @@
1
+ /**
2
+ * chant #2161 — fold provenance, and the return leg that reads it.
3
+ *
4
+ * Two altitudes. The unit half pins the four-way classification and the
5
+ * resolutions it licenses; the build half drives a REAL fold build over a
6
+ * fixture whose composite parameterizes some fields and fixes others, so the
7
+ * origins come from `discovery/fold-import.ts`'s interpretation rather than
8
+ * from a hand-written provenance record.
9
+ */
10
+ import { describe, test, expect, beforeEach, afterEach } from "vitest";
11
+ import { mkdir, writeFile, rm, realpath } from "node:fs/promises";
12
+ import { join, dirname, resolve } from "node:path";
13
+ import { fileURLToPath } from "node:url";
14
+ import { build } from "./build";
15
+ import { getProvenance, originOfPath, type EntityProvenance } from "./provenance";
16
+ import type { DeepEntityDrift } from "./lifecycle/deep-diff";
17
+ import {
18
+ classifyFieldOrigin,
19
+ emittedFieldPaths,
20
+ foldProvenanceOfEntity,
21
+ describeFoldFieldOrigin,
22
+ resolveDeepDrift,
23
+ resolveDriftedField,
24
+ } from "./fold-provenance";
25
+ import type { Serializer } from "./serializer";
26
+ import type { Declarable } from "./declarable";
27
+
28
+ const thisDir = dirname(fileURLToPath(import.meta.url));
29
+ const repoRoot = resolve(thisDir, "../../..");
30
+ const compositePath = resolve(thisDir, "./composite");
31
+
32
+ const LEXICON = "@intentius/chant-lexicon-aws";
33
+ const LEXICON_NAME = "aws";
34
+
35
+ /** Renders nothing but the entity names, so the exclusion assertions below have a document to look at. */
36
+ const namesSerializer: Serializer = {
37
+ name: LEXICON_NAME,
38
+ rulePrefix: "TEST",
39
+ serialize: (entities) => JSON.stringify({ resources: [...entities.keys()].sort() }, null, 2),
40
+ };
41
+
42
+ // ─────────────────────────────────────────────────────────────────────────
43
+ // The unit half.
44
+ // ─────────────────────────────────────────────────────────────────────────
45
+
46
+ describe("emittedFieldPaths", () => {
47
+ test("dots through plain objects and attributes anything else whole", () => {
48
+ expect(
49
+ emittedFieldPaths({
50
+ BucketName: "assets",
51
+ VersioningConfiguration: { Status: "Enabled" },
52
+ Tags: [{ Key: "env", Value: "prod" }],
53
+ Empty: {},
54
+ }),
55
+ ).toEqual(["BucketName", "Empty", "Tags", "VersioningConfiguration.Status"]);
56
+ });
57
+
58
+ test("an undefined value emits no path", () => {
59
+ expect(emittedFieldPaths({ a: 1, b: undefined })).toEqual(["a"]);
60
+ });
61
+
62
+ test("props that are not an object at all emit nothing", () => {
63
+ expect(emittedFieldPaths(undefined)).toEqual([]);
64
+ expect(emittedFieldPaths({})).toEqual([]);
65
+ });
66
+ });
67
+
68
+ describe("classifyFieldOrigin", () => {
69
+ const composite: EntityProvenance = { sourceFile: "/p/src/app.ts", composite: "WebService", compositeInstance: "web" };
70
+ const authored: EntityProvenance = { sourceFile: "/p/src/app.ts" };
71
+
72
+ test("a recorded composite parameter carries the parameter paths and the call", () => {
73
+ expect(
74
+ classifyFieldOrigin({ kind: "composite-parameter", composite: "WebService", parameters: ["name"] }, composite),
75
+ ).toEqual({ kind: "composite-parameter", composite: "WebService", instance: "web", parameters: ["name"] });
76
+ });
77
+
78
+ test("a recorded composite literal says which composite fixes it", () => {
79
+ expect(classifyFieldOrigin({ kind: "composite-literal", composite: "WebService" }, composite)).toEqual({
80
+ kind: "composite-literal",
81
+ composite: "WebService",
82
+ instance: "web",
83
+ });
84
+ });
85
+
86
+ test("authored and build-param are both the author's own declaration", () => {
87
+ expect(classifyFieldOrigin({ kind: "authored" }, authored)).toEqual({ kind: "direct" });
88
+ expect(classifyFieldOrigin({ kind: "build-param", params: ["tier"] }, authored)).toEqual({ kind: "direct" });
89
+ });
90
+
91
+ test("the COARSE composite origin is unknown, never a direct declaration", () => {
92
+ expect(classifyFieldOrigin({ kind: "composite", composite: "WebService", instance: "web" }, composite)).toEqual({
93
+ kind: "unknown",
94
+ reason: "composite-not-interpreted",
95
+ });
96
+ });
97
+
98
+ test("no origin on an entity a composite expanded is unknown, never a direct declaration", () => {
99
+ expect(classifyFieldOrigin(undefined, composite)).toEqual({
100
+ kind: "unknown",
101
+ reason: "composite-not-interpreted",
102
+ });
103
+ });
104
+
105
+ test("no origin on an entity no composite expanded is a direct declaration", () => {
106
+ expect(classifyFieldOrigin(undefined, authored)).toEqual({ kind: "direct" });
107
+ });
108
+
109
+ test("no provenance record at all is unknown, because not even the composite question is answerable", () => {
110
+ expect(classifyFieldOrigin(undefined, undefined)).toEqual({ kind: "unknown", reason: "no-provenance" });
111
+ expect(classifyFieldOrigin({ kind: "authored" }, undefined)).toEqual({ kind: "unknown", reason: "no-provenance" });
112
+ });
113
+ });
114
+
115
+ describe("foldProvenanceOfEntity", () => {
116
+ const entity = {
117
+ lexicon: "aws",
118
+ entityType: "AWS::S3::Bucket",
119
+ props: { BucketName: "assets", VersioningConfiguration: { Status: "Enabled" } },
120
+ [Symbol.for("chant.declarable")]: true,
121
+ } as unknown as Declarable;
122
+
123
+ test("every emitted path is present, unattributed ones included", () => {
124
+ const record = foldProvenanceOfEntity(entity, {
125
+ sourceFile: "/p/src/app.ts",
126
+ composite: "WebService",
127
+ compositeInstance: "web",
128
+ paths: { BucketName: { kind: "composite-parameter", composite: "WebService", parameters: ["name"] } },
129
+ });
130
+
131
+ expect(record).toEqual({
132
+ sourceFile: "/p/src/app.ts",
133
+ composite: "WebService",
134
+ instance: "web",
135
+ fields: {
136
+ BucketName: { kind: "composite-parameter", composite: "WebService", instance: "web", parameters: ["name"] },
137
+ // Nothing recorded for it, and a composite made the entity: unknown.
138
+ "VersioningConfiguration.Status": { kind: "unknown", reason: "composite-not-interpreted" },
139
+ },
140
+ });
141
+ });
142
+
143
+ test("an entity with no props at all has no record", () => {
144
+ const output = {
145
+ lexicon: "aws",
146
+ entityType: "AWS::Output",
147
+ [Symbol.for("chant.declarable")]: true,
148
+ } as unknown as Declarable;
149
+ expect(foldProvenanceOfEntity(output, { sourceFile: "/p/src/app.ts" })).toBeUndefined();
150
+ });
151
+ });
152
+
153
+ describe("resolveDriftedField", () => {
154
+ const compositeProv: EntityProvenance = {
155
+ sourceFile: "src/app.infra.ts",
156
+ composite: "WebService",
157
+ compositeInstance: "web",
158
+ };
159
+
160
+ test("a parameter is proposed by name, with the file and the composite call", () => {
161
+ const resolved = resolveDriftedField({
162
+ entity: "webBucket",
163
+ path: "Tags[#tier].Value",
164
+ declared: "prod",
165
+ live: "staging",
166
+ origin: { kind: "composite-parameter", composite: "WebService", parameters: ["tier"] },
167
+ provenance: compositeProv,
168
+ });
169
+
170
+ expect(resolved.resolution.kind).toBe("propose-parameter");
171
+ if (resolved.resolution.kind !== "propose-parameter") throw new Error("unreachable");
172
+ expect(resolved.resolution.parameters).toEqual(["tier"]);
173
+ expect(resolved.resolution.sourceFile).toBe("src/app.infra.ts");
174
+ expect(resolved.resolution.composite).toBe("WebService");
175
+ expect(resolved.resolution.instance).toBe("web");
176
+ // The file, the composite call and the parameter path, all three named.
177
+ expect(resolved.resolution.description).toContain("src/app.infra.ts");
178
+ expect(resolved.resolution.description).toContain("`web` call of composite WebService");
179
+ expect(resolved.resolution.description).toContain("`tier`");
180
+ expect(resolved.resolution.description).toContain("The composite stays.");
181
+ });
182
+
183
+ test("a field the composite fixes is a NAMED refusal, and proposes no resource change", () => {
184
+ const resolved = resolveDriftedField({
185
+ entity: "webBucket",
186
+ path: "VersioningConfiguration.Status",
187
+ declared: "Enabled",
188
+ live: "Suspended",
189
+ origin: { kind: "composite-literal", composite: "WebService" },
190
+ provenance: compositeProv,
191
+ });
192
+
193
+ expect(resolved.resolution.kind).toBe("refuse-fixed");
194
+ if (resolved.resolution.kind !== "refuse-fixed") throw new Error("unreachable");
195
+ expect(resolved.resolution.composite).toBe("WebService");
196
+ expect(resolved.resolution.description).toMatch(/^refused: /);
197
+ expect(resolved.resolution.description).toContain("VersioningConfiguration.Status");
198
+ expect(resolved.resolution.description).toContain("WebService");
199
+ expect(resolved.resolution.description).toContain("Parameterize the field");
200
+ expect(resolved.resolution.description).toContain("stop using the composite here");
201
+ });
202
+
203
+ test("a direct declaration keeps today's behaviour", () => {
204
+ const resolved = resolveDriftedField({
205
+ entity: "logs",
206
+ path: "BucketName",
207
+ declared: "logs",
208
+ live: "logs-2",
209
+ origin: { kind: "authored" },
210
+ provenance: { sourceFile: "src/app.infra.ts" },
211
+ });
212
+ expect(resolved.resolution.kind).toBe("edit-declaration");
213
+ expect(resolved.resolution.description).toContain("change the declared value of `BucketName`");
214
+ });
215
+
216
+ test("an unknown origin falls back to today's behaviour AND says that is what happened", () => {
217
+ const resolved = resolveDriftedField({
218
+ entity: "legacyBucket",
219
+ path: "BucketName",
220
+ declared: "old",
221
+ live: "older",
222
+ origin: { kind: "composite", composite: "LegacyService", instance: "legacy" },
223
+ provenance: { sourceFile: "src/app.infra.ts", composite: "LegacyService", compositeInstance: "legacy" },
224
+ });
225
+
226
+ expect(resolved.origin).toEqual({ kind: "unknown", reason: "composite-not-interpreted" });
227
+ expect(resolved.resolution.kind).toBe("fall-back");
228
+ if (resolved.resolution.kind !== "fall-back") throw new Error("unreachable");
229
+ expect(resolved.resolution.reason).toBe("composite-not-interpreted");
230
+ expect(resolved.resolution.description).toContain("origin unknown");
231
+ expect(resolved.resolution.description).toContain("falling back to changing the declared value");
232
+ });
233
+
234
+ test("an absent field still resolves by origin", () => {
235
+ const resolved = resolveDriftedField({
236
+ entity: "webRole",
237
+ path: "Path",
238
+ declared: "/service/",
239
+ origin: { kind: "composite-parameter", composite: "WebService", parameters: ["iam.path"] },
240
+ provenance: compositeProv,
241
+ });
242
+ expect(resolved.resolution.kind).toBe("propose-parameter");
243
+ expect(resolved.resolution.description).toContain("unset");
244
+ });
245
+ });
246
+
247
+ describe("resolveDeepDrift", () => {
248
+ test("reads the drifted list and nothing else", () => {
249
+ const verdicts = resolveDeepDrift(
250
+ [
251
+ {
252
+ name: "webBucket",
253
+ type: "AWS::S3::Bucket",
254
+ changes: [
255
+ {
256
+ path: "BucketName",
257
+ kind: "changed",
258
+ declared: "data",
259
+ live: "data-2",
260
+ origin: { kind: "composite-parameter", composite: "WebService", parameters: ["name"] },
261
+ },
262
+ {
263
+ path: "VersioningConfiguration.Status",
264
+ kind: "changed",
265
+ declared: "Enabled",
266
+ live: "Suspended",
267
+ origin: { kind: "composite-literal", composite: "WebService" },
268
+ },
269
+ ],
270
+ },
271
+ ],
272
+ () => ({ sourceFile: "src/app.infra.ts", composite: "WebService", compositeInstance: "web" }),
273
+ );
274
+
275
+ expect(verdicts.map((v) => v.resolution.kind)).toEqual(["propose-parameter", "refuse-fixed"]);
276
+ });
277
+ });
278
+
279
+ describe("describeFoldFieldOrigin", () => {
280
+ test("renders each of the four kinds", () => {
281
+ expect(
282
+ describeFoldFieldOrigin({ kind: "composite-parameter", composite: "W", instance: "web", parameters: ["a", "b"] }),
283
+ ).toBe("parameter a, b of the `web` call of composite W");
284
+ expect(describeFoldFieldOrigin({ kind: "composite-literal", composite: "W", instance: "web" })).toBe(
285
+ "fixed by the `web` call of composite W",
286
+ );
287
+ expect(describeFoldFieldOrigin({ kind: "direct" })).toBe("declared directly");
288
+ expect(describeFoldFieldOrigin({ kind: "unknown", reason: "no-provenance" })).toContain("origin unknown");
289
+ });
290
+ });
291
+
292
+ // ─────────────────────────────────────────────────────────────────────────
293
+ // The build half: one fixture, all four origin kinds.
294
+ // ─────────────────────────────────────────────────────────────────────────
295
+
296
+ /**
297
+ * The fixture composite. `WebService` is inside `fold-import.ts`'s admissible
298
+ * subset, so its body is interpreted and every member property is attributed:
299
+ *
300
+ * - `BucketName` and `Path` read a parameter directly, one of them nested
301
+ * (`props.iam.path`);
302
+ * - `RoleName` reads one through a body `const`;
303
+ * - `Tags` reads one from inside an array of object literals;
304
+ * - `VersioningConfiguration.Status` and `Description` read none, so the
305
+ * composite fixes them.
306
+ *
307
+ * `LegacyService` is one `if` outside the subset, so it is invoked rather than
308
+ * interpreted and its fields are honestly unknown.
309
+ */
310
+ const COMPOSITES = `
311
+ import { Bucket, Role } from ${JSON.stringify(LEXICON)};
312
+ import { Composite } from ${JSON.stringify(compositePath)};
313
+
314
+ export const WebService = Composite((props) => {
315
+ const roleName = \`role-for-\${props.name}\`;
316
+ const bucket = new Bucket({
317
+ BucketName: props.name,
318
+ VersioningConfiguration: { Status: "Enabled" },
319
+ Tags: [{ Key: "tier", Value: props.tier }],
320
+ });
321
+ const role = new Role({
322
+ RoleName: roleName,
323
+ Description: bucket.Arn,
324
+ Path: props.iam.path,
325
+ });
326
+ return { bucket, role };
327
+ }, "WebService");
328
+
329
+ export const LegacyService = Composite((props) => {
330
+ if (!props.name) { throw new Error("name required"); }
331
+ const bucket = new Bucket({ BucketName: props.name, ObjectLockEnabled: true });
332
+ return { bucket };
333
+ }, "LegacyService");
334
+ `;
335
+
336
+ const MAIN = `
337
+ import { Bucket } from ${JSON.stringify(LEXICON)};
338
+ import { WebService, LegacyService } from "../composites";
339
+
340
+ export const logs = new Bucket({ BucketName: "logs" });
341
+ export const web = WebService({ name: "data", tier: "prod", iam: { path: "/service/" } });
342
+ export const legacy = LegacyService({ name: "old" });
343
+ `;
344
+
345
+ let seq = 0;
346
+
347
+ describe("fold provenance over a real composite build (#2161)", () => {
348
+ let testDir: string;
349
+ let srcDir: string;
350
+
351
+ beforeEach(async () => {
352
+ // Inside the repo (`.cache/` is gitignored) rather than the system tmpdir,
353
+ // because the fixture imports the REAL lexicon package and that only
354
+ // resolves from a directory whose `node_modules` walk reaches this
355
+ // checkout's. Same reason `discovery/fold-composite.test.ts` does it.
356
+ const dir = join(repoRoot, ".cache", `chant-2161-${process.pid}-${seq++}`);
357
+ await rm(dir, { recursive: true, force: true });
358
+ await mkdir(dir, { recursive: true });
359
+ testDir = await realpath(dir);
360
+ srcDir = join(testDir, "src");
361
+ await mkdir(srcDir, { recursive: true });
362
+ await writeFile(join(testDir, "composites.ts"), COMPOSITES);
363
+ await writeFile(join(srcDir, "main.ts"), MAIN);
364
+ });
365
+
366
+ afterEach(async () => {
367
+ await rm(testDir, { recursive: true, force: true });
368
+ });
369
+
370
+ test("the fixture proves all four origin kinds", async () => {
371
+ const result = await build(srcDir, [namesSerializer], undefined, {
372
+ fold: true,
373
+ lexicons: [LEXICON_NAME],
374
+ });
375
+
376
+ expect(result.errors).toEqual([]);
377
+ expect(Object.keys(result.foldProvenance).sort()).toEqual([
378
+ "legacyBucket",
379
+ "logs",
380
+ "webBucket",
381
+ "webRole",
382
+ ]);
383
+
384
+ // 1. A field the composite PARAMETERIZES, including a nested parameter
385
+ // path, one read through a body `const`, and one read from inside an
386
+ // array literal.
387
+ expect(result.foldProvenance.webBucket.fields.BucketName).toEqual({
388
+ kind: "composite-parameter",
389
+ composite: "WebService",
390
+ instance: "web",
391
+ parameters: ["name"],
392
+ });
393
+ expect(result.foldProvenance.webBucket.fields.Tags).toEqual({
394
+ kind: "composite-parameter",
395
+ composite: "WebService",
396
+ instance: "web",
397
+ parameters: ["tier"],
398
+ });
399
+ expect(result.foldProvenance.webRole.fields.RoleName).toEqual({
400
+ kind: "composite-parameter",
401
+ composite: "WebService",
402
+ instance: "web",
403
+ parameters: ["name"],
404
+ });
405
+ expect(result.foldProvenance.webRole.fields.Path).toEqual({
406
+ kind: "composite-parameter",
407
+ composite: "WebService",
408
+ instance: "web",
409
+ parameters: ["iam.path"],
410
+ });
411
+
412
+ // 2. A field the composite FIXES.
413
+ expect(result.foldProvenance.webBucket.fields["VersioningConfiguration.Status"]).toEqual({
414
+ kind: "composite-literal",
415
+ composite: "WebService",
416
+ instance: "web",
417
+ });
418
+ expect(result.foldProvenance.webRole.fields.Description).toEqual({
419
+ kind: "composite-literal",
420
+ composite: "WebService",
421
+ instance: "web",
422
+ });
423
+
424
+ // 3. A DIRECT declaration, alongside the composite in the same file.
425
+ expect(result.foldProvenance.logs.fields.BucketName).toEqual({ kind: "direct" });
426
+ expect(result.foldProvenance.logs.composite).toBeUndefined();
427
+
428
+ // 4. UNKNOWN: a composite this build expanded without interpreting.
429
+ expect(result.foldProvenance.legacyBucket.fields.BucketName).toEqual({
430
+ kind: "unknown",
431
+ reason: "composite-not-interpreted",
432
+ });
433
+ expect(result.foldProvenance.legacyBucket.composite).toBe("LegacyService");
434
+
435
+ // The file and the composite call, for the proposal to name.
436
+ expect(result.foldProvenance.webBucket.sourceFile).toContain("main.ts");
437
+ expect(result.foldProvenance.webBucket.instance).toBe("web");
438
+ });
439
+
440
+ test("the return leg proposes a parameter change, refuses a fixed field, and says when it fell back", async () => {
441
+ const result = await build(srcDir, [namesSerializer], undefined, {
442
+ fold: true,
443
+ lexicons: [LEXICON_NAME],
444
+ });
445
+ const provenanceOf = (name: string): EntityProvenance | undefined => {
446
+ const entity = result.entities.get(name);
447
+ return entity ? getProvenance(entity) : undefined;
448
+ };
449
+
450
+ /**
451
+ * One drift row, with its `origin` resolved off the entity's own record
452
+ * the way `lifecycle/deep-diff.ts` resolves it with `originOfPath`. That is
453
+ * the only wiring the return leg needs, and doing it here keeps this test
454
+ * about the resolutions rather than about a live cloud.
455
+ */
456
+ const drift = (name: string, path: string, declared: unknown, live: unknown): DeepEntityDrift => {
457
+ const origin = originOfPath(provenanceOf(name)?.paths, path);
458
+ return {
459
+ name,
460
+ type: "AWS::S3::Bucket",
461
+ changes: [{ path, kind: "changed", declared, live, ...(origin ? { origin } : {}) }],
462
+ };
463
+ };
464
+
465
+ const verdicts = resolveDeepDrift(
466
+ [
467
+ drift("webBucket", "BucketName", "data", "data-renamed"),
468
+ drift("webBucket", "VersioningConfiguration.Status", "Enabled", "Suspended"),
469
+ drift("legacyBucket", "BucketName", "old", "older"),
470
+ drift("logs", "BucketName", "logs", "logs-archive"),
471
+ ],
472
+ provenanceOf,
473
+ );
474
+
475
+ // A parameter: proposed, naming the file, the composite call and the path.
476
+ expect(verdicts[0].resolution.kind).toBe("propose-parameter");
477
+ if (verdicts[0].resolution.kind !== "propose-parameter") throw new Error("unreachable");
478
+ expect(verdicts[0].resolution.parameters).toEqual(["name"]);
479
+ expect(verdicts[0].resolution.instance).toBe("web");
480
+ expect(verdicts[0].resolution.sourceFile).toContain("main.ts");
481
+ expect(verdicts[0].resolution.description).toContain("`name`");
482
+
483
+ // A field the composite fixes: a NAMED refusal, and no flat resource change.
484
+ expect(verdicts[1].resolution.kind).toBe("refuse-fixed");
485
+ expect(verdicts[1].resolution.description).toContain("fixed by the `web` call of composite WebService");
486
+
487
+ // Unknown: today's behaviour, and it says so.
488
+ expect(verdicts[2].origin).toEqual({ kind: "unknown", reason: "composite-not-interpreted" });
489
+ expect(verdicts[2].resolution.kind).toBe("fall-back");
490
+ expect(verdicts[2].resolution.description).toContain("origin unknown");
491
+
492
+ // A direct declaration: today's behaviour, unchanged and unremarked.
493
+ expect(verdicts[3].resolution.kind).toBe("edit-declaration");
494
+ });
495
+ });