@intentius/chant 0.57.0 → 0.58.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (504) hide show
  1. package/dist/audit/catalog.d.ts +51 -1
  2. package/dist/audit/catalog.d.ts.map +1 -1
  3. package/dist/audit/core.d.ts +20 -1
  4. package/dist/audit/core.d.ts.map +1 -1
  5. package/dist/audit/discover.d.ts +14 -8
  6. package/dist/audit/discover.d.ts.map +1 -1
  7. package/dist/audit/lineage.d.ts +10 -0
  8. package/dist/audit/lineage.d.ts.map +1 -0
  9. package/dist/audit/prior-art.d.ts +385 -0
  10. package/dist/audit/prior-art.d.ts.map +1 -0
  11. package/dist/audit/proof.d.ts +16 -0
  12. package/dist/audit/proof.d.ts.map +1 -1
  13. package/dist/audit/report-html.d.ts.map +1 -1
  14. package/dist/audit/report-model.d.ts +8 -1
  15. package/dist/audit/report-model.d.ts.map +1 -1
  16. package/dist/audit/report.d.ts.map +1 -1
  17. package/dist/audit/rules-doc.d.ts +3 -0
  18. package/dist/audit/rules-doc.d.ts.map +1 -1
  19. package/dist/audit/secrets.d.ts +15 -0
  20. package/dist/audit/secrets.d.ts.map +1 -1
  21. package/dist/audit/terraform-state.d.ts +46 -0
  22. package/dist/audit/terraform-state.d.ts.map +1 -0
  23. package/dist/build.d.ts +17 -1
  24. package/dist/build.d.ts.map +1 -1
  25. package/dist/claimed-fields.d.ts +161 -0
  26. package/dist/claimed-fields.d.ts.map +1 -0
  27. package/dist/cli/build-params-cli.d.ts +2 -2
  28. package/dist/cli/command-group.d.ts +14 -0
  29. package/dist/cli/command-group.d.ts.map +1 -1
  30. package/dist/cli/commands/audit.d.ts +17 -1
  31. package/dist/cli/commands/audit.d.ts.map +1 -1
  32. package/dist/cli/commands/build.d.ts.map +1 -1
  33. package/dist/cli/commands/check-lexicon-docs.d.ts +1 -1
  34. package/dist/cli/commands/check-lexicon-examples.d.ts +16 -6
  35. package/dist/cli/commands/check-lexicon-examples.d.ts.map +1 -1
  36. package/dist/cli/commands/init-lexicon/templates/codegen.d.ts +1 -0
  37. package/dist/cli/commands/init-lexicon/templates/codegen.d.ts.map +1 -1
  38. package/dist/cli/commands/init-lexicon/templates/project.d.ts +7 -0
  39. package/dist/cli/commands/init-lexicon/templates/project.d.ts.map +1 -1
  40. package/dist/cli/commands/init-lexicon.d.ts.map +1 -1
  41. package/dist/cli/commands/init.d.ts.map +1 -1
  42. package/dist/cli/commands/lint.d.ts.map +1 -1
  43. package/dist/cli/commands/onboard.d.ts +2 -0
  44. package/dist/cli/commands/onboard.d.ts.map +1 -1
  45. package/dist/cli/handlers/build.d.ts.map +1 -1
  46. package/dist/cli/handlers/lifecycle.d.ts.map +1 -1
  47. package/dist/cli/handlers/operator.d.ts +82 -16
  48. package/dist/cli/handlers/operator.d.ts.map +1 -1
  49. package/dist/cli/handlers/run.d.ts +49 -42
  50. package/dist/cli/handlers/run.d.ts.map +1 -1
  51. package/dist/cli/main.d.ts +28 -1
  52. package/dist/cli/main.d.ts.map +1 -1
  53. package/dist/cli/mcp/op-tools.d.ts +16 -1
  54. package/dist/cli/mcp/op-tools.d.ts.map +1 -1
  55. package/dist/cli/mcp/resource-handlers.d.ts.map +1 -1
  56. package/dist/cli/mcp/resources/context.d.ts.map +1 -1
  57. package/dist/cli/registry.d.ts +26 -9
  58. package/dist/cli/registry.d.ts.map +1 -1
  59. package/dist/codegen/generate-post-synth-barrel.d.ts +1 -1
  60. package/dist/components/auto-release.d.ts +10 -20
  61. package/dist/components/auto-release.d.ts.map +1 -1
  62. package/dist/components/capability.d.ts +4 -4
  63. package/dist/components/cli-support.d.ts +20 -21
  64. package/dist/components/cli-support.d.ts.map +1 -1
  65. package/dist/components/component.d.ts +3 -3
  66. package/dist/components/component.d.ts.map +1 -1
  67. package/dist/components/driver-output.d.ts.map +1 -1
  68. package/dist/components/driver.d.ts +66 -36
  69. package/dist/components/driver.d.ts.map +1 -1
  70. package/dist/components/index.d.ts +1 -2
  71. package/dist/components/index.d.ts.map +1 -1
  72. package/dist/components/manifest-persistence.d.ts +1 -9
  73. package/dist/components/manifest-persistence.d.ts.map +1 -1
  74. package/dist/components/run-progress.d.ts +6 -6
  75. package/dist/components/run-progress.d.ts.map +1 -1
  76. package/dist/components/verbs/run-agent.d.ts +9 -15
  77. package/dist/components/verbs/run-agent.d.ts.map +1 -1
  78. package/dist/components/verbs/verify.d.ts +2 -2
  79. package/dist/deep-observation.d.ts +17 -0
  80. package/dist/deep-observation.d.ts.map +1 -1
  81. package/dist/discovery/files.d.ts +4 -4
  82. package/dist/discovery/fold-import.d.ts.map +1 -1
  83. package/dist/discovery/param-deps.d.ts +33 -0
  84. package/dist/discovery/param-deps.d.ts.map +1 -1
  85. package/dist/discovery/sandbox/bundle.d.ts.map +1 -1
  86. package/dist/discovery/sandbox/config-wire.d.ts +2 -2
  87. package/dist/fold/foldable-helpers.d.ts +5 -1
  88. package/dist/fold/foldable-helpers.d.ts.map +1 -1
  89. package/dist/fold-provenance.d.ts +236 -0
  90. package/dist/fold-provenance.d.ts.map +1 -0
  91. package/dist/graph-ops.d.ts +1 -1
  92. package/dist/held-elsewhere.d.ts +85 -0
  93. package/dist/held-elsewhere.d.ts.map +1 -0
  94. package/dist/index.d.ts +3 -0
  95. package/dist/index.d.ts.map +1 -1
  96. package/dist/kubectl-context.d.ts +3 -3
  97. package/dist/lexicon-config.d.ts +4 -4
  98. package/dist/lexicon-plugin-helpers.d.ts +1 -1
  99. package/dist/lexicon.d.ts +104 -14
  100. package/dist/lexicon.d.ts.map +1 -1
  101. package/dist/lifecycle/change-set.d.ts +13 -0
  102. package/dist/lifecycle/change-set.d.ts.map +1 -1
  103. package/dist/lifecycle/deep-diff.d.ts +133 -6
  104. package/dist/lifecycle/deep-diff.d.ts.map +1 -1
  105. package/dist/lifecycle/deep-observe.d.ts.map +1 -1
  106. package/dist/lifecycle/gate-ledger.d.ts +71 -3
  107. package/dist/lifecycle/gate-ledger.d.ts.map +1 -1
  108. package/dist/lifecycle/index.d.ts +1 -0
  109. package/dist/lifecycle/index.d.ts.map +1 -1
  110. package/dist/lifecycle/release-ledger.d.ts +2 -2
  111. package/dist/lifecycle/run-ledger.d.ts +53 -0
  112. package/dist/lifecycle/run-ledger.d.ts.map +1 -0
  113. package/dist/lifecycle/symptoms.d.ts +3 -3
  114. package/dist/lifecycle/symptoms.d.ts.map +1 -1
  115. package/dist/lint/config.d.ts +81 -2
  116. package/dist/lint/config.d.ts.map +1 -1
  117. package/dist/lint/post-synth.d.ts +55 -2
  118. package/dist/lint/post-synth.d.ts.map +1 -1
  119. package/dist/lint/rules/__fixtures__/comp/comp004/pass/neo4j-cluster.component.d.ts +1 -1
  120. package/dist/lint/rules/__fixtures__/comp/comp004/pass/neo4j-cluster.component.d.ts.map +1 -1
  121. package/dist/lint/rules/__fixtures__/comp/comp004/pass/orders-table.component.d.ts +1 -1
  122. package/dist/lint/rules/__fixtures__/comp/comp004/pass/orders-table.component.d.ts.map +1 -1
  123. package/dist/lint/rules/comp/comp004-gate-needs-durable-runtime.d.ts +44 -0
  124. package/dist/lint/rules/comp/comp004-gate-needs-durable-runtime.d.ts.map +1 -0
  125. package/dist/lint/rules/comp/index.d.ts +1 -1
  126. package/dist/lint/rules/comp/index.d.ts.map +1 -1
  127. package/dist/lint/rules/op/index.d.ts +16 -0
  128. package/dist/lint/rules/op/index.d.ts.map +1 -0
  129. package/dist/lint/rules/op/ops012-activity-contract.d.ts +27 -0
  130. package/dist/lint/rules/op/ops012-activity-contract.d.ts.map +1 -0
  131. package/dist/lint/rules/op/ops013-step-output-ref.d.ts +61 -0
  132. package/dist/lint/rules/op/ops013-step-output-ref.d.ts.map +1 -0
  133. package/dist/lint/rules/op/ops014-converge-rule-refusals.d.ts +55 -0
  134. package/dist/lint/rules/op/ops014-converge-rule-refusals.d.ts.map +1 -0
  135. package/dist/lint/rules/op/support.d.ts +12 -0
  136. package/dist/lint/rules/op/support.d.ts.map +1 -0
  137. package/dist/lint/suppressions.d.ts +108 -0
  138. package/dist/lint/suppressions.d.ts.map +1 -0
  139. package/dist/live-endpoint.d.ts +1 -1
  140. package/dist/op/activities/activity-contracts.d.ts +54 -0
  141. package/dist/op/activities/activity-contracts.d.ts.map +1 -0
  142. package/dist/op/activities/apply.d.ts +262 -0
  143. package/dist/op/activities/apply.d.ts.map +1 -0
  144. package/dist/op/activities/build.d.ts +13 -0
  145. package/dist/op/activities/build.d.ts.map +1 -0
  146. package/dist/op/activities/converge.d.ts +110 -0
  147. package/dist/op/activities/converge.d.ts.map +1 -0
  148. package/dist/op/activities/env-teardown.d.ts +76 -0
  149. package/dist/op/activities/env-teardown.d.ts.map +1 -0
  150. package/dist/op/activities/guard-validate.d.ts +64 -0
  151. package/dist/op/activities/guard-validate.d.ts.map +1 -0
  152. package/dist/op/activities/http-check.d.ts +34 -0
  153. package/dist/op/activities/http-check.d.ts.map +1 -0
  154. package/dist/op/activities/index.d.ts +31 -0
  155. package/dist/op/activities/index.d.ts.map +1 -0
  156. package/dist/op/activities/lexicon-upgrade.d.ts +182 -0
  157. package/dist/op/activities/lexicon-upgrade.d.ts.map +1 -0
  158. package/dist/op/activities/lifecycle.d.ts +42 -0
  159. package/dist/op/activities/lifecycle.d.ts.map +1 -0
  160. package/dist/op/activities/pipeline-audit.d.ts +84 -0
  161. package/dist/op/activities/pipeline-audit.d.ts.map +1 -0
  162. package/dist/op/activities/policy.d.ts +17 -0
  163. package/dist/op/activities/policy.d.ts.map +1 -0
  164. package/dist/op/activities/reconcile.d.ts +89 -0
  165. package/dist/op/activities/reconcile.d.ts.map +1 -0
  166. package/dist/op/activities/shell.d.ts +13 -0
  167. package/dist/op/activities/shell.d.ts.map +1 -0
  168. package/dist/op/activities/teardown.d.ts +10 -0
  169. package/dist/op/activities/teardown.d.ts.map +1 -0
  170. package/dist/op/activities/util.d.ts +7 -0
  171. package/dist/op/activities/util.d.ts.map +1 -0
  172. package/dist/op/activities/wait.d.ts +28 -0
  173. package/dist/op/activities/wait.d.ts.map +1 -0
  174. package/dist/op/activities/workflow-audit.d.ts +92 -0
  175. package/dist/op/activities/workflow-audit.d.ts.map +1 -0
  176. package/dist/op/activity-contract-registry.d.ts +76 -0
  177. package/dist/op/activity-contract-registry.d.ts.map +1 -0
  178. package/dist/op/activity-contract.d.ts +25 -18
  179. package/dist/op/activity-contract.d.ts.map +1 -1
  180. package/dist/op/activity-failure.d.ts +28 -0
  181. package/dist/op/activity-failure.d.ts.map +1 -0
  182. package/dist/op/activity-profiles.d.ts +118 -0
  183. package/dist/op/activity-profiles.d.ts.map +1 -0
  184. package/dist/op/activity-registry.d.ts +22 -38
  185. package/dist/op/activity-registry.d.ts.map +1 -1
  186. package/dist/op/activity-runtime.d.ts +3 -14
  187. package/dist/op/activity-runtime.d.ts.map +1 -1
  188. package/dist/op/builders.d.ts +84 -29
  189. package/dist/op/builders.d.ts.map +1 -1
  190. package/dist/op/composites/apply-op.d.ts +113 -0
  191. package/dist/op/composites/apply-op.d.ts.map +1 -0
  192. package/dist/op/composites/converge-op.d.ts +120 -0
  193. package/dist/op/composites/converge-op.d.ts.map +1 -0
  194. package/dist/op/composites/index.d.ts +26 -0
  195. package/dist/op/composites/index.d.ts.map +1 -0
  196. package/dist/op/composites/lexicon-upgrade-op.d.ts +78 -0
  197. package/dist/op/composites/lexicon-upgrade-op.d.ts.map +1 -0
  198. package/dist/op/composites/pipeline-audit-op.d.ts +50 -0
  199. package/dist/op/composites/pipeline-audit-op.d.ts.map +1 -0
  200. package/dist/op/composites/reconcile-op.d.ts +57 -0
  201. package/dist/op/composites/reconcile-op.d.ts.map +1 -0
  202. package/dist/op/composites/watch-op.d.ts +60 -0
  203. package/dist/op/composites/watch-op.d.ts.map +1 -0
  204. package/dist/op/composites/workflow-audit-op.d.ts +57 -0
  205. package/dist/op/composites/workflow-audit-op.d.ts.map +1 -0
  206. package/dist/op/converge-rule.d.ts +12 -10
  207. package/dist/op/converge-rule.d.ts.map +1 -1
  208. package/dist/op/cron.d.ts +51 -0
  209. package/dist/op/cron.d.ts.map +1 -0
  210. package/dist/op/discover.d.ts +9 -0
  211. package/dist/op/discover.d.ts.map +1 -1
  212. package/dist/op/duration.d.ts +11 -0
  213. package/dist/op/duration.d.ts.map +1 -0
  214. package/dist/op/emulator-lifecycle.d.ts.map +1 -1
  215. package/dist/op/gate.d.ts +90 -0
  216. package/dist/op/gate.d.ts.map +1 -0
  217. package/dist/op/generate-pipeline.d.ts +9 -0
  218. package/dist/op/generate-pipeline.d.ts.map +1 -1
  219. package/dist/op/index.d.ts +23 -7
  220. package/dist/op/index.d.ts.map +1 -1
  221. package/dist/op/local-executor.d.ts +114 -28
  222. package/dist/op/local-executor.d.ts.map +1 -1
  223. package/dist/op/local-output.d.ts +11 -1
  224. package/dist/op/local-output.d.ts.map +1 -1
  225. package/dist/op/op-ir.d.ts +184 -0
  226. package/dist/op/op-ir.d.ts.map +1 -0
  227. package/dist/op/op-verb-class.d.ts +10 -2
  228. package/dist/op/op-verb-class.d.ts.map +1 -1
  229. package/dist/op/operator.d.ts +30 -5
  230. package/dist/op/operator.d.ts.map +1 -1
  231. package/dist/op/receipt-store.d.ts +1 -1
  232. package/dist/op/receipt-store.d.ts.map +1 -1
  233. package/dist/op/resource.d.ts +15 -1
  234. package/dist/op/resource.d.ts.map +1 -1
  235. package/dist/op/runtime.d.ts +200 -0
  236. package/dist/op/runtime.d.ts.map +1 -0
  237. package/dist/op/runtimes/local.d.ts +29 -0
  238. package/dist/op/runtimes/local.d.ts.map +1 -0
  239. package/dist/op/step-output-ref.d.ts +15 -13
  240. package/dist/op/step-output-ref.d.ts.map +1 -1
  241. package/dist/op/types.d.ts +79 -27
  242. package/dist/op/types.d.ts.map +1 -1
  243. package/dist/ownership.d.ts +1 -1
  244. package/dist/provenance.d.ts +21 -0
  245. package/dist/provenance.d.ts.map +1 -1
  246. package/dist/serializer-walker.d.ts.map +1 -1
  247. package/package.json +6 -1
  248. package/src/audit/catalog.test.ts +79 -3
  249. package/src/audit/catalog.ts +111 -13
  250. package/src/audit/core.test.ts +26 -0
  251. package/src/audit/core.ts +61 -17
  252. package/src/audit/discover.test.ts +39 -0
  253. package/src/audit/discover.ts +91 -15
  254. package/src/audit/lineage.ts +100 -0
  255. package/src/audit/prior-art.ts +153 -0
  256. package/src/audit/proof.test.ts +67 -0
  257. package/src/audit/proof.ts +70 -0
  258. package/src/audit/report-html.ts +9 -4
  259. package/src/audit/report-model.ts +16 -2
  260. package/src/audit/report.test.ts +33 -0
  261. package/src/audit/report.ts +5 -3
  262. package/src/audit/rules-doc.test.ts +34 -2
  263. package/src/audit/rules-doc.ts +82 -9
  264. package/src/audit/secrets.ts +15 -3
  265. package/src/audit/terraform-state.test.ts +144 -0
  266. package/src/audit/terraform-state.ts +131 -0
  267. package/src/build.ts +31 -1
  268. package/src/claimed-fields.test.ts +145 -0
  269. package/src/claimed-fields.ts +208 -0
  270. package/src/cli/build-params-cli.ts +2 -2
  271. package/src/cli/command-group.ts +14 -0
  272. package/src/cli/commands/__fixtures__/audit-terraform/infra/.terraform/providers/lock.txt +1 -0
  273. package/src/cli/commands/__fixtures__/audit-terraform/infra/main.tf +1 -0
  274. package/src/cli/commands/__fixtures__/audit-terraform/infra/modules/foo/main.tf +1 -0
  275. package/src/cli/commands/__fixtures__/audit-terraform/infra/variables.tf +3 -0
  276. package/src/cli/commands/__fixtures__/init-lexicon-output/package.json +13 -4
  277. package/src/cli/commands/__fixtures__/init-lexicon-output/src/codegen/docs-cli.ts +4 -0
  278. package/src/cli/commands/__fixtures__/init-lexicon-output/src/codegen/package.ts +1 -0
  279. package/src/cli/commands/__fixtures__/init-lexicon-output/src/package-cli.ts +21 -0
  280. package/src/cli/commands/__snapshots__/init-lexicon.test.ts.snap +1 -0
  281. package/src/cli/commands/audit.test.ts +189 -11
  282. package/src/cli/commands/audit.ts +73 -27
  283. package/src/cli/commands/build.test.ts +292 -9
  284. package/src/cli/commands/build.ts +156 -24
  285. package/src/cli/commands/check-lexicon-docs.ts +1 -1
  286. package/src/cli/commands/check-lexicon-examples.test.ts +29 -1
  287. package/src/cli/commands/check-lexicon-examples.ts +42 -10
  288. package/src/cli/commands/init-lexicon/templates/codegen.ts +9 -0
  289. package/src/cli/commands/init-lexicon/templates/project.ts +44 -4
  290. package/src/cli/commands/init-lexicon.test.ts +97 -2
  291. package/src/cli/commands/init-lexicon.ts +4 -2
  292. package/src/cli/commands/init.test.ts +35 -1
  293. package/src/cli/commands/init.ts +14 -3
  294. package/src/cli/commands/lint.test.ts +48 -0
  295. package/src/cli/commands/lint.ts +123 -0
  296. package/src/cli/commands/onboard.test.ts +75 -0
  297. package/src/cli/commands/onboard.ts +54 -23
  298. package/src/cli/handlers/build.test.ts +81 -1
  299. package/src/cli/handlers/build.ts +15 -1
  300. package/src/cli/handlers/graph.test.ts +3 -3
  301. package/src/cli/handlers/lifecycle.test.ts +195 -12
  302. package/src/cli/handlers/lifecycle.ts +123 -6
  303. package/src/cli/handlers/misc.ts +1 -1
  304. package/src/cli/handlers/operator.test.ts +183 -11
  305. package/src/cli/handlers/operator.ts +260 -55
  306. package/src/cli/handlers/run.test.ts +431 -1287
  307. package/src/cli/handlers/run.ts +365 -915
  308. package/src/cli/main.test.ts +23 -15
  309. package/src/cli/main.ts +88 -33
  310. package/src/cli/mcp/op-tools.ts +147 -88
  311. package/src/cli/mcp/resource-handlers.ts +10 -37
  312. package/src/cli/mcp/resources/context.ts +11 -12
  313. package/src/cli/mcp/server.test.ts +25 -27
  314. package/src/cli/mcp/server.ts +2 -2
  315. package/src/cli/registry.ts +26 -9
  316. package/src/codegen/docs.ts +1 -1
  317. package/src/codegen/generate-post-synth-barrel.test.ts +1 -1
  318. package/src/codegen/generate-post-synth-barrel.ts +1 -1
  319. package/src/components/README.md +3 -2
  320. package/src/components/auto-release.test.ts +4 -20
  321. package/src/components/auto-release.ts +17 -42
  322. package/src/components/capability.ts +4 -4
  323. package/src/components/cli-support.test.ts +10 -5
  324. package/src/components/cli-support.ts +52 -44
  325. package/src/components/component.schema.json +2 -2
  326. package/src/components/component.ts +3 -3
  327. package/src/components/driver-output.ts +14 -2
  328. package/src/components/driver.test.ts +92 -22
  329. package/src/components/driver.ts +193 -83
  330. package/src/components/index.ts +1 -6
  331. package/src/components/manifest-persistence.test.ts +2 -20
  332. package/src/components/manifest-persistence.ts +4 -24
  333. package/src/components/pilots/pilots-lint-clean.test.ts +11 -10
  334. package/src/components/run-progress.ts +6 -6
  335. package/src/components/verbs/run-agent.ts +11 -17
  336. package/src/components/verbs/verify.ts +2 -2
  337. package/src/deep-observation.test.ts +75 -0
  338. package/src/deep-observation.ts +48 -0
  339. package/src/discovery/collect.test.ts +3 -3
  340. package/src/discovery/files.ts +4 -4
  341. package/src/discovery/fold-import.ts +105 -2
  342. package/src/discovery/param-deps.test.ts +112 -1
  343. package/src/discovery/param-deps.ts +193 -0
  344. package/src/discovery/sandbox/bundle.ts +15 -1
  345. package/src/discovery/sandbox/config-wire.test.ts +2 -2
  346. package/src/discovery/sandbox/config-wire.ts +2 -2
  347. package/src/fold/fold.test.ts +56 -0
  348. package/src/fold/foldable-helpers.ts +81 -1
  349. package/src/fold-provenance-exclusion.test.ts +186 -0
  350. package/src/fold-provenance.test.ts +495 -0
  351. package/src/fold-provenance.ts +441 -0
  352. package/src/graph-ops.test.ts +2 -2
  353. package/src/graph-ops.ts +1 -1
  354. package/src/held-elsewhere.test.ts +102 -0
  355. package/src/held-elsewhere.ts +114 -0
  356. package/src/index.ts +3 -0
  357. package/src/kubectl-context.ts +3 -3
  358. package/src/lexicon-config.test.ts +5 -5
  359. package/src/lexicon-config.ts +4 -4
  360. package/src/lexicon-plugin-helpers.ts +1 -1
  361. package/src/lexicon.ts +120 -14
  362. package/src/lifecycle/change-set.test.ts +49 -0
  363. package/src/lifecycle/change-set.ts +67 -0
  364. package/src/lifecycle/converge-ledger.ts +1 -1
  365. package/src/lifecycle/deep-diff.test.ts +267 -14
  366. package/src/lifecycle/deep-diff.ts +217 -14
  367. package/src/lifecycle/deep-observe.test.ts +3 -1
  368. package/src/lifecycle/deep-observe.ts +16 -6
  369. package/src/lifecycle/gate-ledger.test.ts +74 -2
  370. package/src/lifecycle/gate-ledger.ts +143 -36
  371. package/src/lifecycle/index.ts +1 -0
  372. package/src/lifecycle/release-ledger.ts +2 -2
  373. package/src/lifecycle/run-ledger.test.ts +268 -0
  374. package/src/lifecycle/run-ledger.ts +201 -0
  375. package/src/lifecycle/symptoms.ts +3 -3
  376. package/src/lint/config.test.ts +82 -1
  377. package/src/lint/config.ts +112 -3
  378. package/src/lint/policy.test.ts +1 -1
  379. package/src/lint/post-synth.test.ts +31 -0
  380. package/src/lint/post-synth.ts +54 -1
  381. package/src/lint/rules/__fixtures__/comp/comp004/pass/neo4j-cluster.component.ts +2 -2
  382. package/src/lint/rules/__fixtures__/comp/comp004/pass/orders-table.component.ts +1 -1
  383. package/src/lint/rules/comp/comp.test.ts +1 -1
  384. package/src/lint/rules/comp/comp004-gate-needs-durable-runtime.ts +74 -0
  385. package/src/lint/rules/comp/index.ts +3 -3
  386. package/src/lint/rules/op/index.ts +23 -0
  387. package/src/lint/rules/op/ops012-activity-contract.test.ts +161 -0
  388. package/src/lint/rules/op/ops012-activity-contract.ts +64 -0
  389. package/src/lint/rules/op/ops013-step-output-ref.test.ts +237 -0
  390. package/src/lint/rules/op/ops013-step-output-ref.ts +98 -0
  391. package/src/lint/rules/op/ops014-converge-rule-refusals.test.ts +263 -0
  392. package/src/lint/rules/op/ops014-converge-rule-refusals.ts +179 -0
  393. package/src/lint/rules/op/support.ts +11 -0
  394. package/src/lint/suppressions.test.ts +163 -0
  395. package/src/lint/suppressions.ts +204 -0
  396. package/src/live-endpoint.ts +1 -1
  397. package/src/op/activities/activity-contracts.ts +66 -0
  398. package/src/op/activities/apply.test.ts +567 -0
  399. package/src/op/activities/apply.ts +662 -0
  400. package/src/op/activities/build.ts +26 -0
  401. package/src/op/activities/converge.test.ts +330 -0
  402. package/src/op/activities/converge.ts +449 -0
  403. package/src/op/activities/env-teardown.test.ts +187 -0
  404. package/src/op/activities/env-teardown.ts +171 -0
  405. package/src/op/activities/guard-validate.test.ts +139 -0
  406. package/src/op/activities/guard-validate.ts +223 -0
  407. package/src/op/activities/http-check.test.ts +48 -0
  408. package/src/op/activities/http-check.ts +55 -0
  409. package/src/op/activities/index.ts +119 -0
  410. package/src/op/activities/lexicon-upgrade.test.ts +492 -0
  411. package/src/op/activities/lexicon-upgrade.ts +673 -0
  412. package/src/op/activities/lifecycle.ts +85 -0
  413. package/src/op/activities/pipeline-audit.test.ts +67 -0
  414. package/src/op/activities/pipeline-audit.ts +201 -0
  415. package/src/op/activities/policy.test.ts +19 -0
  416. package/src/op/activities/policy.ts +37 -0
  417. package/src/op/activities/reconcile.test.ts +81 -0
  418. package/src/op/activities/reconcile.ts +187 -0
  419. package/src/op/activities/shell.ts +26 -0
  420. package/src/op/activities/teardown.ts +22 -0
  421. package/src/op/activities/util.ts +6 -0
  422. package/src/op/activities/wait.ts +69 -0
  423. package/src/op/activities/workflow-audit.test.ts +90 -0
  424. package/src/op/activities/workflow-audit.ts +203 -0
  425. package/src/op/activity-contract-registry.test.ts +98 -0
  426. package/src/op/activity-contract-registry.ts +114 -0
  427. package/src/op/activity-contract.test.ts +1 -1
  428. package/src/op/activity-contract.ts +37 -33
  429. package/src/op/activity-failure.ts +34 -0
  430. package/src/op/activity-profiles.test.ts +56 -0
  431. package/src/op/activity-profiles.ts +116 -0
  432. package/src/op/activity-registry.test.ts +37 -28
  433. package/src/op/activity-registry.ts +27 -56
  434. package/src/op/activity-runtime.ts +3 -47
  435. package/src/op/builders-exports.test.ts +0 -49
  436. package/src/op/builders-typed.test.ts +164 -0
  437. package/src/op/builders.ts +123 -39
  438. package/src/op/composites/apply-op.ts +208 -0
  439. package/src/op/composites/composites.test.ts +366 -0
  440. package/src/op/composites/converge-op.test.ts +168 -0
  441. package/src/op/composites/converge-op.ts +214 -0
  442. package/src/op/composites/index.ts +26 -0
  443. package/src/op/composites/lexicon-upgrade-op.test.ts +68 -0
  444. package/src/op/composites/lexicon-upgrade-op.ts +134 -0
  445. package/src/op/composites/pipeline-audit-op.test.ts +25 -0
  446. package/src/op/composites/pipeline-audit-op.ts +78 -0
  447. package/src/op/composites/reconcile-op.test.ts +51 -0
  448. package/src/op/composites/reconcile-op.ts +107 -0
  449. package/src/op/composites/watch-op.ts +108 -0
  450. package/src/op/composites/workflow-audit-op.test.ts +25 -0
  451. package/src/op/composites/workflow-audit-op.ts +88 -0
  452. package/src/op/converge-rule.ts +12 -10
  453. package/src/op/cron.test.ts +128 -0
  454. package/src/op/cron.ts +149 -0
  455. package/src/op/discover-root.test.ts +105 -1
  456. package/src/op/discover.test.ts +6 -2
  457. package/src/op/discover.ts +84 -16
  458. package/src/op/duration.ts +21 -0
  459. package/src/op/effect-step.test.ts +26 -11
  460. package/src/op/emulator-lifecycle.ts +1 -2
  461. package/src/op/gate.ts +155 -0
  462. package/src/op/generate-pipeline.test.ts +65 -3
  463. package/src/op/generate-pipeline.ts +19 -2
  464. package/src/op/index.ts +39 -6
  465. package/src/op/local-executor.test.ts +163 -34
  466. package/src/op/local-executor.ts +370 -102
  467. package/src/op/local-output.test.ts +95 -11
  468. package/src/op/local-output.ts +33 -4
  469. package/src/op/op-ir.test.ts +281 -0
  470. package/src/op/op-ir.ts +417 -0
  471. package/src/op/op-verb-class.ts +19 -5
  472. package/src/op/op.test.ts +26 -5
  473. package/src/op/operator.test.ts +175 -6
  474. package/src/op/operator.ts +63 -16
  475. package/src/op/receipt-store.ts +1 -1
  476. package/src/op/resource.ts +20 -2
  477. package/src/op/runtime.ts +203 -0
  478. package/src/op/runtimes/local.test.ts +181 -0
  479. package/src/op/runtimes/local.ts +212 -0
  480. package/src/op/step-output-ref.ts +15 -13
  481. package/src/op/types.ts +84 -24
  482. package/src/ownership.ts +1 -1
  483. package/src/provenance.test.ts +6 -0
  484. package/src/provenance.ts +21 -1
  485. package/src/serializer-walker.test.ts +42 -0
  486. package/src/serializer-walker.ts +21 -2
  487. package/dist/cli/handlers/op-progress.d.ts +0 -90
  488. package/dist/cli/handlers/op-progress.d.ts.map +0 -1
  489. package/dist/cli/handlers/run-client.d.ts +0 -127
  490. package/dist/cli/handlers/run-client.d.ts.map +0 -1
  491. package/dist/cli/handlers/run-report.d.ts +0 -5
  492. package/dist/cli/handlers/run-report.d.ts.map +0 -1
  493. package/dist/components/temporal-codegen-loader.d.ts +0 -27
  494. package/dist/components/temporal-codegen-loader.d.ts.map +0 -1
  495. package/dist/lint/rules/comp/comp004-gate-needs-temporal.d.ts +0 -37
  496. package/dist/lint/rules/comp/comp004-gate-needs-temporal.d.ts.map +0 -1
  497. package/src/cli/handlers/op-progress.test.ts +0 -268
  498. package/src/cli/handlers/op-progress.ts +0 -248
  499. package/src/cli/handlers/run-client.test.ts +0 -82
  500. package/src/cli/handlers/run-client.ts +0 -219
  501. package/src/cli/handlers/run-report.test.ts +0 -62
  502. package/src/cli/handlers/run-report.ts +0 -122
  503. package/src/components/temporal-codegen-loader.ts +0 -41
  504. package/src/lint/rules/comp/comp004-gate-needs-temporal.ts +0 -66
@@ -173,7 +173,13 @@ export function parseSecretsConfig(content: string): SecretsScanOptions {
173
173
 
174
174
  // ── Shannon entropy ─────────────────────────────────────────────────────
175
175
 
176
- function shannonEntropy(s: string): number {
176
+ /**
177
+ * Shannon entropy in bits per character. Exported so a lexicon's own
178
+ * secret-shape heuristic can score a candidate on the same scale this
179
+ * module's SEC010 catch-all uses (the terraform lexicon's
180
+ * `lint/secret-shape.ts`, #2110) instead of inventing a second one.
181
+ */
182
+ export function shannonEntropy(s: string): number {
177
183
  const freq = new Map<string, number>();
178
184
  for (const ch of s) freq.set(ch, (freq.get(ch) ?? 0) + 1);
179
185
  let entropy = 0;
@@ -188,8 +194,14 @@ function shannonEntropy(s: string): number {
188
194
 
189
195
  const PLACEHOLDER_RE = /example|placeholder|dummy|changeme|change-me|redacted|sample|fixme|todo|xxxxxxxx|your[-_]?(api[-_]?)?(key|token|secret)|fake[-_]?(key|token|secret)|test[-_]?(key|token|secret)/i;
190
196
 
191
- /** Cheap, format-agnostic "this doesn't look like a real secret" filter. */
192
- function looksLikePlaceholder(value: string): boolean {
197
+ /**
198
+ * Cheap, format-agnostic "this doesn't look like a real secret" filter.
199
+ * Exported for the same reason {@link shannonEntropy} is: the placeholder
200
+ * vocabulary a scanner has to ignore (`changeme`, `example`, `your-api-key`,
201
+ * a run of one repeated character) is the same wherever the candidate came
202
+ * from, and a second copy of this list would drift from this one.
203
+ */
204
+ export function looksLikePlaceholder(value: string): boolean {
193
205
  if (PLACEHOLDER_RE.test(value)) return true;
194
206
  const uniq = new Set(value.toLowerCase()).size;
195
207
  if (value.length > 8 && uniq <= 2) return true; // e.g. "0000000000000000"
@@ -0,0 +1,144 @@
1
+ import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { tmpdir } from "node:os";
4
+ import { describe, expect, test } from "vitest";
5
+ import {
6
+ auditTerraformState,
7
+ gitignoreCoversTerraformState,
8
+ isTerraformStateFile,
9
+ isTerraformStatePath,
10
+ isTerraformWorkDir,
11
+ type ScannableFile,
12
+ } from "./terraform-state";
13
+ import { collectCandidates } from "./discover";
14
+ import { RULE_CATALOG, RULE_CATEGORY } from "./catalog";
15
+
16
+ const file = (path: string): ScannableFile => ({ path, content: "" });
17
+
18
+ function tmpRepo(): string {
19
+ return mkdtempSync(join(tmpdir(), "chant-tf023-"));
20
+ }
21
+
22
+ describe("the TF023 path detectors", () => {
23
+ test.each([
24
+ "terraform.tfstate",
25
+ "terraform.tfstate.backup",
26
+ "infra/prod/terraform.tfstate",
27
+ "infra/prod.tfstate",
28
+ "infra/prod.tfstate.backup",
29
+ ])("%s is state", (path) => {
30
+ expect(isTerraformStateFile(path)).toBe(true);
31
+ expect(isTerraformStatePath(path)).toBe(true);
32
+ });
33
+
34
+ test.each(["main.tf", "terraform.tfvars", "state.json", "docs/tfstate.md"])("%s is not", (path) => {
35
+ expect(isTerraformStateFile(path)).toBe(false);
36
+ });
37
+
38
+ test("the working directory is matched as a path segment, at any depth", () => {
39
+ expect(isTerraformWorkDir(".terraform")).toBe(true);
40
+ expect(isTerraformWorkDir(".terraform/providers/registry.terraform.io/hashicorp/aws/5.0.0")).toBe(true);
41
+ expect(isTerraformWorkDir("infra/prod/.terraform")).toBe(true);
42
+ expect(isTerraformWorkDir("infra/terraform/main.tf")).toBe(false);
43
+ });
44
+ });
45
+
46
+ describe("auditTerraformState", () => {
47
+ test("reports a committed state file as an error, without reading it", () => {
48
+ const findings = auditTerraformState([file("infra/prod/terraform.tfstate")]);
49
+ expect(findings).toHaveLength(1);
50
+ expect(findings[0].checkId).toBe("TF023");
51
+ expect(findings[0].severity).toBe("error");
52
+ expect(findings[0].lexicon).toBe("terraform");
53
+ expect(findings[0].file).toBe("infra/prod/terraform.tfstate");
54
+ expect(findings[0].message).toContain("secret store");
55
+ });
56
+
57
+ test("reports a backup state file too", () => {
58
+ expect(auditTerraformState([file("terraform.tfstate.backup")])).toHaveLength(1);
59
+ });
60
+
61
+ test("reports a `.terraform` directory once, not once per file inside it", () => {
62
+ const findings = auditTerraformState([
63
+ file(".terraform"),
64
+ file(".terraform/modules/modules.json"),
65
+ file(".terraform/providers/registry.terraform.io/hashicorp/aws/5.0.0/terraform-provider-aws"),
66
+ ]);
67
+ expect(findings).toHaveLength(1);
68
+ expect(findings[0].file).toBe(".terraform");
69
+ expect(findings[0].entity).toBe(".terraform");
70
+ });
71
+
72
+ test("reports each root's own working directory separately", () => {
73
+ const findings = auditTerraformState([file("infra/prod/.terraform"), file("infra/staging/.terraform")]);
74
+ expect(findings.map((f) => f.file)).toEqual(["infra/prod/.terraform", "infra/staging/.terraform"]);
75
+ });
76
+
77
+ test("says nothing about a repository with neither", () => {
78
+ expect(auditTerraformState([{ path: "main.tf", content: 'resource "null_resource" "a" {}' }])).toEqual([]);
79
+ });
80
+
81
+ test("is catalogued in core, as a merge-worthy security rule", () => {
82
+ expect(RULE_CATALOG.TF023.tier).toBe("merge-worthy");
83
+ expect(RULE_CATALOG.TF023.category).toBe("security");
84
+ expect(RULE_CATEGORY.TF023).toBe("security");
85
+ expect(RULE_CATALOG.TF023.authority?.[0].url).toContain("#gitignore");
86
+ });
87
+ });
88
+
89
+ describe("gitignoreCoversTerraformState", () => {
90
+ test.each([
91
+ ["*.tfstate", "terraform.tfstate"],
92
+ ["*.tfstate.*", "terraform.tfstate.backup"],
93
+ [".terraform/", ".terraform"],
94
+ [".terraform*", ".terraform"],
95
+ ["/terraform.tfstate", "terraform.tfstate"],
96
+ ["**/.terraform", "infra/prod/.terraform"],
97
+ ["# comment\n\n*.tfstate\n", "infra/prod/terraform.tfstate"],
98
+ ])("%s covers %s", (gitignore, path) => {
99
+ expect(gitignoreCoversTerraformState(gitignore, path)).toBe(true);
100
+ });
101
+
102
+ test.each([
103
+ ["node_modules/\ndist/", "terraform.tfstate"],
104
+ ["*.tfvars", "terraform.tfstate"],
105
+ ["!terraform.tfstate", "terraform.tfstate"],
106
+ ])("%s does not cover %s", (gitignore, path) => {
107
+ expect(gitignoreCoversTerraformState(gitignore, path)).toBe(false);
108
+ });
109
+ });
110
+
111
+ describe("discovery hands TF023 its paths, and nothing else changes", () => {
112
+ test("a state file becomes a candidate with no content, so nothing reads it", () => {
113
+ const dir = tmpRepo();
114
+ writeFileSync(join(dir, "main.tf"), 'resource "null_resource" "a" {}\n');
115
+ writeFileSync(join(dir, "terraform.tfstate"), JSON.stringify({ version: 4, resources: [] }));
116
+ const candidates = collectCandidates(dir);
117
+ const state = candidates.find((c) => c.path === "terraform.tfstate");
118
+ expect(state).toBeDefined();
119
+ expect(state!.content).toBe("");
120
+ expect(auditTerraformState(candidates)).toHaveLength(1);
121
+ rmSync(dir, { recursive: true, force: true });
122
+ });
123
+
124
+ test("the walk records `.terraform` without descending into it", () => {
125
+ const dir = tmpRepo();
126
+ writeFileSync(join(dir, "main.tf"), 'resource "null_resource" "a" {}\n');
127
+ mkdirSync(join(dir, ".terraform", "providers"), { recursive: true });
128
+ writeFileSync(join(dir, ".terraform", "providers", "huge.json"), "{}");
129
+ const candidates = collectCandidates(dir);
130
+ expect(candidates.map((c) => c.path).sort()).toEqual([".terraform", "main.tf"]);
131
+ expect(auditTerraformState(candidates)).toHaveLength(1);
132
+ rmSync(dir, { recursive: true, force: true });
133
+ });
134
+
135
+ test("a gitignored state file is on disk, not in the repository, so it is dropped", () => {
136
+ const dir = tmpRepo();
137
+ writeFileSync(join(dir, ".gitignore"), ".terraform/\n*.tfstate\n*.tfstate.*\n");
138
+ writeFileSync(join(dir, "main.tf"), 'resource "null_resource" "a" {}\n');
139
+ writeFileSync(join(dir, "terraform.tfstate"), "{}");
140
+ mkdirSync(join(dir, ".terraform"), { recursive: true });
141
+ expect(auditTerraformState(collectCandidates(dir))).toEqual([]);
142
+ rmSync(dir, { recursive: true, force: true });
143
+ });
144
+ });
@@ -0,0 +1,131 @@
1
+ /**
2
+ * TF023 (#2110): Terraform state, or the `.terraform/` working directory,
3
+ * committed to the repository.
4
+ *
5
+ * Every other rule in the TF family is a post-synth check over a parsed root
6
+ * module, contributed by the terraform lexicon. This one is not about HCL at
7
+ * all: the finding is that a path exists in version control, so there is
8
+ * nothing for `chant build` to check and nothing for the lexicon's parser to
9
+ * read. It therefore takes the shape `secrets.ts` established and `wrangler.ts`
10
+ * and `nginx.ts` copied, a path detector plus a check over the discovered file
11
+ * list, called from the audit CLI beside `scanForSecrets`, with its catalog
12
+ * entry in core's own `RULE_CATALOG` (./catalog.ts) rather than a lexicon's.
13
+ * It runs whether or not the terraform lexicon is installed.
14
+ *
15
+ * A state file is a committed secret store: it records every attribute of every
16
+ * managed resource, including the ones providers mark sensitive, in plaintext
17
+ * JSON. `.terraform/` is milder (providers and vendored modules) but is still
18
+ * hundreds of megabytes of machine-generated content, and it can hold a
19
+ * `terraform.tfstate` of its own for the workspace-local backend.
20
+ *
21
+ * Pure: no fs, no network, no Node-only global, so it is Workers-safe (epic #350). The
22
+ * walk that decides which paths reach here is `discover.ts`'s
23
+ * `collectCandidates`, which is also where a locally ignored path is dropped:
24
+ * see `isTerraformStatePath` there and `gitignoreCoversTerraformState` below.
25
+ */
26
+
27
+ import type { AuditFinding } from "./core";
28
+
29
+ /** The minimal file shape the check needs (matches `discover.ts`'s `RepoFile`). */
30
+ export interface ScannableFile {
31
+ path: string;
32
+ content: string;
33
+ }
34
+
35
+ /** `terraform.tfstate`, `terraform.tfstate.backup`, and any other `*.tfstate`. */
36
+ export function isTerraformStateFile(path: string): boolean {
37
+ const name = path.split("/").pop() ?? path;
38
+ return /\.tfstate(\.backup)?$/i.test(name);
39
+ }
40
+
41
+ /** The `.terraform` working directory itself, or anything inside it. */
42
+ export function isTerraformWorkDir(path: string): boolean {
43
+ const parts = path.split("/");
44
+ return parts.includes(".terraform");
45
+ }
46
+
47
+ /** Either of the two shapes TF023 reports. */
48
+ export function isTerraformStatePath(path: string): boolean {
49
+ return isTerraformStateFile(path) || isTerraformWorkDir(path);
50
+ }
51
+
52
+ /**
53
+ * Does a `.gitignore` body already exclude Terraform state? Deliberately
54
+ * narrow: it recognises the handful of patterns the style guide's own
55
+ * `.gitignore` section and `github/gitignore`'s `Terraform.gitignore` use
56
+ * (`*.tfstate`, `*.tfstate.*`, `.terraform/`, `.terraform*`, with an optional
57
+ * leading slash or a leading double-star), not the whole gitignore grammar. A repository that
58
+ * writes something more exotic gets a finding it can suppress; the alternative,
59
+ * a partial gitignore engine, would be wrong in quieter ways.
60
+ *
61
+ * Used by the local walk only (`discover.ts`), where "on disk" and "tracked by
62
+ * git" are different questions and nearly every Terraform working tree has an
63
+ * ignored `.terraform/` in it. A fetched repository's file list is already
64
+ * exactly the tracked files, so nothing filters it.
65
+ */
66
+ function escapeRegExp(literal: string): string {
67
+ return literal.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
68
+ }
69
+
70
+ export function gitignoreCoversTerraformState(gitignore: string, path: string): boolean {
71
+ const name = path.split("/").pop() ?? path;
72
+ for (const raw of gitignore.split("\n")) {
73
+ const line = raw.trim();
74
+ if (line === "" || line.startsWith("#") || line.startsWith("!")) continue;
75
+ const pattern = line.replace(/^\*\*\//, "").replace(/^\//, "").replace(/\/$/, "");
76
+ if (pattern === "" || pattern.includes("/")) continue;
77
+ // `*` matches within one path segment, as git's own glob does. The pattern
78
+ // is compared against the file's own name and against every segment of its
79
+ // path, which is what makes `.terraform/` match `infra/prod/.terraform`.
80
+ const re = new RegExp(`^${pattern.split("*").map(escapeRegExp).join("[^/]*")}$`);
81
+ if (re.test(name) || path.split("/").some((segment) => re.test(segment))) return true;
82
+ }
83
+ return false;
84
+ }
85
+
86
+ /**
87
+ * Report TF023 for every committed state file and `.terraform/` directory in
88
+ * the file list. One finding per path, and one for the `.terraform` directory
89
+ * as a whole rather than per file inside it: a vendored provider binary is not
90
+ * a separate finding from the directory that holds it.
91
+ */
92
+ export function auditTerraformState(files: ScannableFile[]): AuditFinding[] {
93
+ const findings: AuditFinding[] = [];
94
+ const workDirs = new Set<string>();
95
+
96
+ for (const file of files) {
97
+ if (isTerraformWorkDir(file.path)) {
98
+ const parts = file.path.split("/");
99
+ const dir = parts.slice(0, parts.indexOf(".terraform") + 1).join("/");
100
+ if (workDirs.has(dir)) continue;
101
+ workDirs.add(dir);
102
+ findings.push({
103
+ checkId: "TF023",
104
+ severity: "warning",
105
+ message:
106
+ `\`${dir}\` is committed to the repository. Terraform's working directory holds downloaded ` +
107
+ "providers and vendored child modules, and the local backend keeps its state there too. " +
108
+ "Delete it from version control and add `.terraform/` to .gitignore.",
109
+ file: dir,
110
+ lexicon: "terraform",
111
+ entity: ".terraform",
112
+ });
113
+ continue;
114
+ }
115
+ if (!isTerraformStateFile(file.path)) continue;
116
+ findings.push({
117
+ checkId: "TF023",
118
+ severity: "error",
119
+ message:
120
+ `\`${file.path}\` is committed to the repository. A state file records every attribute of every ` +
121
+ "managed resource in plaintext, including the ones providers mark sensitive, so a committed " +
122
+ "state file is a committed secret store. Remove it from version control, add `*.tfstate` and " +
123
+ "`*.tfstate.backup` to .gitignore, move the state to a remote backend, and rotate anything it held.",
124
+ file: file.path,
125
+ lexicon: "terraform",
126
+ entity: file.path.split("/").pop() ?? file.path,
127
+ });
128
+ }
129
+
130
+ return findings;
131
+ }
package/src/build.ts CHANGED
@@ -3,12 +3,14 @@ import type { Serializer, SerializerResult } from "./serializer";
3
3
  import type { OwnershipMarker } from "./ownership";
4
4
  import type { BuildError, DiscoveryErrorType } from "./errors";
5
5
  import type { IntrinsicDef, BuildRootContribution, BuildRootContributor } from "./lexicon";
6
- import type { BuildParamProvenance } from "./provenance";
6
+ import { getProvenance, type BuildParamProvenance } from "./provenance";
7
+ import { foldProvenanceOfEntities, type FoldProvenance } from "./fold-provenance";
7
8
  import { DiscoveryError, BuildError as BuildErrorClass } from "./errors";
8
9
  import { LexiconOutput, isLexiconOutput } from "./lexicon-output";
9
10
  import { isSecretDeclaration } from "./secret-provenance";
10
11
  import { splitReceiptEntities } from "./effect-receipt";
11
12
  import { isScenario } from "./lifecycle/scenario";
13
+ import { isOpEntity } from "./op/resource";
12
14
  import { AttrRef } from "./attrref";
13
15
  import { isAttrRefLike } from "./utils";
14
16
  import { isChildProject, type ChildProjectInstance } from "./child-project";
@@ -282,6 +284,22 @@ export interface BuildResult {
282
284
  * supplies none.
283
285
  */
284
286
  buildParams: BuildParamProvenance[];
287
+
288
+ /**
289
+ * chant #2161 — emitted property path to origin, per entity: which composite
290
+ * parameter produced the field, which literal the composite fixes, which the
291
+ * author declared directly, and which this build could not attribute.
292
+ *
293
+ * Beside the output on purpose, and never inside it. Nothing here is passed
294
+ * to a serializer, so it cannot reach the document an applier writes; see
295
+ * ./fold-provenance.ts's module doc and `fold-provenance-exclusion.test.ts`.
296
+ *
297
+ * Every entity with properties appears. The origins are as fine as the build
298
+ * managed: a run build, or a fold build over a factory outside the
299
+ * interpretable subset, records `unknown` for a composite's fields rather
300
+ * than guessing.
301
+ */
302
+ foldProvenance: FoldProvenance;
285
303
  }
286
304
 
287
305
  /**
@@ -308,6 +326,13 @@ export function partitionByLexicon(
308
326
  // Plan scenarios (#1292) are serializer-neutral the same way: a checkable
309
327
  // expectation the CLI reads off the entity map, never output.
310
328
  if (isScenario(entity)) continue;
329
+ // Op declarations (#2118) too. An Op's build output is its `op.json` IR,
330
+ // and core writes that itself from the entity map (`./cli/commands/build.ts`)
331
+ // rather than routing it through a lexicon serializer — the Op model, the
332
+ // executor and the IR are all core's, and no lexicon renders an Op entity
333
+ // into its own manifest. Keeping them out of every partition means no
334
+ // "No serializer found" warning fires for core's own `chant` lexicon.
335
+ if (isOpEntity(entity)) continue;
311
336
  const lexicon = entity.lexicon;
312
337
  if (!partitions.has(lexicon)) {
313
338
  partitions.set(lexicon, new Map());
@@ -852,6 +877,11 @@ async function buildFromDiscoveryResult(
852
877
  foldDecisions: discoveryResult.foldDecisions,
853
878
  lexiconVersions: { ...(options?.lexiconVersions ?? {}) },
854
879
  buildParams: options?.buildParams ?? [],
880
+ // chant #2161 — read off the entities AFTER every step that can change
881
+ // them (the shared-props merge in `expandComposite`, the build-root merge
882
+ // above), and deliberately after Step 7, so nothing about computing it can
883
+ // reach a serializer.
884
+ foldProvenance: foldProvenanceOfEntities(discoveryResult.entities, getProvenance),
855
885
  };
856
886
  }
857
887
 
@@ -0,0 +1,145 @@
1
+ import { describe, test, expect } from "vitest";
2
+ import {
3
+ NO_CLAIMED_FIELDS,
4
+ claimedFieldPaths,
5
+ claimedFieldsFromPaths,
6
+ claimedFieldsOfProps,
7
+ claimedFieldsOfTree,
8
+ classifyLiveField,
9
+ heldBy,
10
+ isClaimed,
11
+ } from "./claimed-fields";
12
+ import { UNRESOLVED, type DeepNormalizationHooks } from "./deep-observation";
13
+
14
+ describe("claimedFieldsOfProps", () => {
15
+ test("walks props to leaf paths in the diff's grammar", () => {
16
+ const claimed = claimedFieldsOfProps(
17
+ { spec: { replicas: 2, template: { spec: { containers: [{ name: "app", image: "a:1" }] } } } },
18
+ { entityType: "K8s::Apps::Deployment" },
19
+ );
20
+ expect(claimedFieldPaths(claimed)).toEqual([
21
+ "spec.replicas",
22
+ "spec.template.spec.containers[0].image",
23
+ "spec.template.spec.containers[0].name",
24
+ ]);
25
+ });
26
+
27
+ test("addresses set-like arrays by the lexicon's own order key", () => {
28
+ const hooks: DeepNormalizationHooks = {
29
+ orderKey: (el) => (el.pattern === "Tags" ? String((el.element as { Key: string }).Key) : undefined),
30
+ };
31
+ const claimed = claimedFieldsOfProps(
32
+ { Tags: [{ Key: "env", Value: "prod" }] },
33
+ { entityType: "AWS::S3::Bucket", hooks },
34
+ );
35
+ expect(claimedFieldPaths(claimed)).toEqual(["Tags[#env].Key", "Tags[#env].Value"]);
36
+ });
37
+
38
+ test("a pruned property is not claimed", () => {
39
+ // The hooks decide what is even in the tree, so the claim inherits them
40
+ // rather than second-guessing them: a field the lexicon subtracts on both
41
+ // sides is not a field chant is asserting anything about.
42
+ const hooks: DeepNormalizationHooks = { prune: (node) => node.pattern.startsWith("status") };
43
+ const claimed = claimedFieldsOfProps(
44
+ { spec: { replicas: 2 }, status: { readyReplicas: 2 } },
45
+ { entityType: "T", hooks },
46
+ );
47
+ expect(claimedFieldPaths(claimed)).toEqual(["spec.replicas"]);
48
+ });
49
+
50
+ test("an unevaluated intrinsic is still claimed", () => {
51
+ // chant set the field; it just cannot say what the reference resolves to.
52
+ const claimed = claimedFieldsOfProps({ BucketName: UNRESOLVED }, { entityType: "T" });
53
+ expect(isClaimed(claimed, "BucketName")).toBe(true);
54
+ });
55
+
56
+ test("an empty declaration claims nothing", () => {
57
+ expect(claimedFieldPaths(claimedFieldsOfProps({}, { entityType: "T" }))).toEqual([]);
58
+ expect(claimedFieldPaths(NO_CLAIMED_FIELDS)).toEqual([]);
59
+ });
60
+
61
+ test("agrees with the tree form on an already-normalized tree", () => {
62
+ const props = { spec: { replicas: 2 } };
63
+ expect(claimedFieldPaths(claimedFieldsOfProps(props, { entityType: "T" }))).toEqual(
64
+ claimedFieldPaths(claimedFieldsOfTree(props, { entityType: "T" })),
65
+ );
66
+ });
67
+ });
68
+
69
+ describe("isClaimed", () => {
70
+ const claimed = claimedFieldsFromPaths(["Tags[0].Value", "spec.replicas"]);
71
+
72
+ test("matches exactly", () => {
73
+ expect(isClaimed(claimed, "spec.replicas")).toBe(true);
74
+ });
75
+
76
+ test("does not match by index-erased pattern", () => {
77
+ // A pattern match would answer "declared" for a path with no declared value
78
+ // to compare against, which is neither of the two declared classifications.
79
+ expect(isClaimed(claimed, "Tags[3].Value")).toBe(false);
80
+ });
81
+
82
+ test("does not match an ancestor's claim", () => {
83
+ // Declaring `spec.replicas` says nothing about `spec.strategy.type`.
84
+ expect(isClaimed(claimed, "spec.strategy.type")).toBe(false);
85
+ });
86
+
87
+ test("an absent claim claims nothing", () => {
88
+ expect(isClaimed(undefined, "spec.replicas")).toBe(false);
89
+ });
90
+ });
91
+
92
+ describe("classifyLiveField", () => {
93
+ const claimed = claimedFieldsFromPaths(["spec.replicas"]);
94
+
95
+ test("declared and equal", () => {
96
+ expect(
97
+ classifyLiveField({ claimed, path: "spec.replicas", declaredValue: 2, liveValue: 2 }),
98
+ ).toBe("declared-equal");
99
+ });
100
+
101
+ test("declared and different", () => {
102
+ expect(
103
+ classifyLiveField({ claimed, path: "spec.replicas", declaredValue: 2, liveValue: 5 }),
104
+ ).toBe("declared-changed");
105
+ });
106
+
107
+ test("undeclared", () => {
108
+ expect(
109
+ classifyLiveField({ claimed, path: "metadata.labels.team", declaredValue: undefined, liveValue: "platform" }),
110
+ ).toBe("undeclared");
111
+ });
112
+
113
+ test("structural equality, not identity", () => {
114
+ const shaped = claimedFieldsFromPaths(["spec.selector"]);
115
+ expect(
116
+ classifyLiveField({
117
+ claimed: shaped,
118
+ path: "spec.selector",
119
+ declaredValue: { app: "web" },
120
+ liveValue: { app: "web" },
121
+ }),
122
+ ).toBe("declared-equal");
123
+ });
124
+ });
125
+
126
+ describe("heldBy", () => {
127
+ test("the substrate's manager answers where it has one", () => {
128
+ expect(heldBy({ "spec.replicas": "hpa-controller" }, "spec.replicas")).toEqual({
129
+ holder: "hpa-controller",
130
+ source: "field-manager",
131
+ });
132
+ });
133
+
134
+ test("the claim answers where the substrate records nothing", () => {
135
+ expect(heldBy(undefined, "spec.replicas")).toEqual({ source: "claimed-fields" });
136
+ });
137
+
138
+ test("the claim answers for a path the ownership metadata does not cover", () => {
139
+ // Kubernetes' own case: a keyed list segment has no `managedFields` entry
140
+ // under that path, so the declaration is the only witness left.
141
+ expect(heldBy({ "spec.replicas": "hpa-controller" }, "spec.template.spec.containers[#sidecar].image")).toEqual({
142
+ source: "claimed-fields",
143
+ });
144
+ });
145
+ });