@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
package/src/audit/core.ts CHANGED
@@ -22,7 +22,8 @@
22
22
 
23
23
  import { basename } from "path";
24
24
  import type { Severity } from "../lint/rule";
25
- import type { PostSynthCheck, PostSynthContext } from "../lint/post-synth";
25
+ import type { PostSynthCheck, PostSynthContext, PostSynthDiagnostic } from "../lint/post-synth";
26
+ import { applyInlineSuppressions, type SuppressionMetaFinding } from "../lint/suppressions";
26
27
  import type { SerializerResult } from "../serializer";
27
28
  import type { LexiconPlugin } from "../lexicon";
28
29
  import type { Declarable } from "../declarable";
@@ -82,6 +83,8 @@ export interface AuditFinding {
82
83
  * itself.
83
84
  */
84
85
  fingerprint?: string;
86
+ /** Carried verbatim from the producing `PostSynthDiagnostic.missing` (chant #2113) — see its doc comment. */
87
+ missing?: { kind: string; scope: string };
85
88
  }
86
89
 
87
90
  /**
@@ -94,8 +97,10 @@ export type ChecksProvider = (lexicon: AuditLexicon) => Promise<PostSynthCheck[]
94
97
  * Parse one classified file's content into the lexicon's entity graph — the
95
98
  * parse-to-graph half of the audit (#1567). The returned map is what
96
99
  * `ctx.entities` holds during a build, so entity-reading checks run unchanged.
100
+ * May return a `Promise` for a lexicon whose parser is inherently async (e.g.
101
+ * terraform's HCL parser); `auditLexicon`'s `entitiesFor` awaits it.
97
102
  */
98
- export type EntitiesParser = (content: string) => Map<string, Declarable>;
103
+ export type EntitiesParser = (content: string) => Map<string, Declarable> | Promise<Map<string, Declarable>>;
99
104
 
100
105
  /**
101
106
  * Resolve the entities parser for a lexicon (its plugin's `auditEntities`,
@@ -179,13 +184,25 @@ async function defaultEntitiesProvider(lexicon: AuditLexicon): Promise<EntitiesP
179
184
  return parser;
180
185
  }
181
186
 
187
+ /**
188
+ * Mutable out-param for the inline (`# chant-ignore*`) suppression count
189
+ * (chant #2111): `auditFiles`'s return type stays `AuditFinding[]` for every
190
+ * existing caller, so a caller that wants the count (`chant audit`'s CLI, for
191
+ * its summary line) passes this in and reads `.count` back afterward, rather
192
+ * than `auditFiles` growing a `{findings, suppressedCount}` shape that would
193
+ * touch every existing call site.
194
+ */
195
+ export interface SuppressionStats {
196
+ count: number;
197
+ }
198
+
182
199
  /**
183
200
  * Audit a set of CI files and return all findings. Pure with respect to the
184
201
  * filesystem and network — callers supply file contents.
185
202
  */
186
203
  export async function auditFiles(
187
204
  inputs: AuditInput[],
188
- opts: { checksProvider?: ChecksProvider; entitiesProvider?: EntitiesProvider } = {},
205
+ opts: { checksProvider?: ChecksProvider; entitiesProvider?: EntitiesProvider; suppressionStats?: SuppressionStats } = {},
189
206
  ): Promise<AuditFinding[]> {
190
207
  const provider = opts.checksProvider ?? defaultChecksProvider;
191
208
  const entitiesProvider = opts.entitiesProvider ?? defaultEntitiesProvider;
@@ -203,7 +220,7 @@ export async function auditFiles(
203
220
  const checks = await provider(lexicon);
204
221
  if (checks.length === 0) continue;
205
222
  const parseEntities = await entitiesProvider(lexicon);
206
- findings.push(...auditLexicon(lexicon, files, checks, parseEntities));
223
+ findings.push(...(await auditLexicon(lexicon, files, checks, parseEntities, opts.suppressionStats)));
207
224
  }
208
225
 
209
226
  return findings;
@@ -224,14 +241,20 @@ function toOutput(file: AuditInput): string | SerializerResult {
224
241
  return { primary: file.content, files: { [basename(file.path)]: file.content } };
225
242
  }
226
243
 
244
+ interface CheckRunResult {
245
+ diagnostics: PostSynthDiagnostic[];
246
+ suppressed: PostSynthDiagnostic[];
247
+ meta: SuppressionMetaFinding[];
248
+ }
249
+
227
250
  function runChecks(
228
251
  checks: PostSynthCheck[],
229
252
  outputs: Map<string, string | SerializerResult>,
230
253
  entities: Map<string, Declarable> = new Map(),
231
- ): ReturnType<PostSynthCheck["check"]> {
254
+ ): CheckRunResult {
232
255
  const buildResult: PostSynthContext["buildResult"] = { outputs, entities, warnings: [], errors: [], sourceFileCount: outputs.size };
233
256
  const ctx: PostSynthContext = { outputs, entities: buildResult.entities, buildResult };
234
- const diags = [];
257
+ const diags: PostSynthDiagnostic[] = [];
235
258
  for (const check of checks) {
236
259
  try {
237
260
  diags.push(...check.check(ctx));
@@ -239,7 +262,11 @@ function runChecks(
239
262
  // A check that throws on unusual external YAML must not abort the audit.
240
263
  }
241
264
  }
242
- return diags;
265
+ // chant #2111: `rules` (for `ignorable: false`) is omitted, since `chant audit`
266
+ // has no project `lint.rules` to read here (see `applyInlineSuppressions`'s
267
+ // doc comment for why that's a deliberate scope line, not an oversight), so
268
+ // every rule id reads as ignorable, same as an unconfigured one everywhere.
269
+ return applyInlineSuppressions(diags, entities);
243
270
  }
244
271
 
245
272
  function diagKey(d: { checkId: string; entity?: string; message: string }): string {
@@ -281,40 +308,57 @@ function mergeEntities(maps: Array<Map<string, Declarable>>): Map<string, Declar
281
308
  * the entity graph parsed from its files — per file for the per-file pass, all
282
309
  * files merged for the all-files pass — so entity-reading checks fire and
283
310
  * cross-file facts (an Agent's Environment declared elsewhere, a name declared
284
- * twice across the apply directory) resolve in the all-files pass.
311
+ * twice across the apply directory) resolve in the all-files pass. The parser
312
+ * may be async (a lexicon's own choice, e.g. a wasm-backed HCL parser); this
313
+ * function awaits each file's entities before either pass runs.
285
314
  */
286
- function auditLexicon(lexicon: AuditLexicon, files: AuditInput[], checks: PostSynthCheck[], parseEntities?: EntitiesParser): AuditFinding[] {
287
- const entitiesFor = (file: AuditInput): Map<string, Declarable> => {
315
+ async function auditLexicon(
316
+ lexicon: AuditLexicon,
317
+ files: AuditInput[],
318
+ checks: PostSynthCheck[],
319
+ parseEntities?: EntitiesParser,
320
+ suppressionStats?: SuppressionStats,
321
+ ): Promise<AuditFinding[]> {
322
+ const entitiesFor = async (file: AuditInput): Promise<Map<string, Declarable>> => {
288
323
  if (!parseEntities) return new Map();
289
324
  try {
290
- return parseEntities(file.content);
325
+ return await parseEntities(file.content);
291
326
  } catch {
292
327
  // The audit contract is "runs against any repo" — unparseable content
293
328
  // contributes no entities, never a crash.
294
329
  return new Map();
295
330
  }
296
331
  };
297
- const perEntities = new Map(files.map((f) => [f.path, entitiesFor(f)]));
332
+ const perEntities = new Map(await Promise.all(files.map(async (f) => [f.path, await entitiesFor(f)] as const)));
298
333
 
299
334
  const perFindings: AuditFinding[] = [];
300
335
  const perKeys = new Set<string>();
336
+ // chant #2111: meta findings (an expired/misplaced/denied suppression
337
+ // directive) are collected from the per-file pass only: the all-files pass
338
+ // below sees the same entities merged, so it would report the identical
339
+ // directive again. Counted the same way, once, here too.
340
+ const metaFindings: AuditFinding[] = [];
301
341
  for (const file of files) {
302
- const diags = runChecks(checks, new Map([[file.path, toOutput(file)]]), perEntities.get(file.path));
342
+ const { diagnostics: diags, suppressed, meta } = runChecks(checks, new Map([[file.path, toOutput(file)]]), perEntities.get(file.path));
343
+ if (suppressionStats) suppressionStats.count += suppressed.length;
344
+ for (const m of meta) {
345
+ metaFindings.push({ checkId: m.checkId, severity: m.severity, message: m.message, file: m.file, lexicon, line: m.line });
346
+ }
303
347
  for (const d of diags) {
304
- perFindings.push({ checkId: d.checkId, severity: d.severity, message: d.message, file: file.path, lexicon: d.lexicon ?? lexicon, entity: d.entity });
348
+ perFindings.push({ checkId: d.checkId, severity: d.severity, message: d.message, file: file.path, lexicon: d.lexicon ?? lexicon, entity: d.entity, missing: d.missing });
305
349
  perKeys.add(diagKey(d));
306
350
  }
307
351
  }
308
352
 
309
353
  const allOutputs = new Map<string, string | SerializerResult>(files.map((f) => [f.path, toOutput(f)]));
310
- const allDiags = runChecks(checks, allOutputs, mergeEntities([...perEntities.values()]));
354
+ const { diagnostics: allDiags } = runChecks(checks, allOutputs, mergeEntities([...perEntities.values()]));
311
355
  const allKeys = new Set(allDiags.map(diagKey));
312
356
 
313
357
  const out: AuditFinding[] = perFindings.filter((f) => allKeys.has(diagKey(f)));
314
358
  for (const d of allDiags) {
315
359
  if (!perKeys.has(diagKey(d))) {
316
- out.push({ checkId: d.checkId, severity: d.severity, message: d.message, file: CROSS_FILE, lexicon: d.lexicon ?? lexicon, entity: d.entity });
360
+ out.push({ checkId: d.checkId, severity: d.severity, message: d.message, file: CROSS_FILE, lexicon: d.lexicon ?? lexicon, entity: d.entity, missing: d.missing });
317
361
  }
318
362
  }
319
- return out;
363
+ return [...out, ...metaFindings];
320
364
  }
@@ -92,6 +92,45 @@ describe("discoverByDetection (unified, detectTemplate-driven)", () => {
92
92
  const plugins = await loadAuditPlugins(["github", "definitely-not-a-real-lexicon"]);
93
93
  expect(plugins.map((p) => p.name)).toEqual(["github"]);
94
94
  });
95
+
96
+ test("loadAuditPlugins skips a lexicon whose package is not installed, quietly", async () => {
97
+ const plugins = await loadAuditPlugins(["github", "not-a-lexicon"]);
98
+ expect(plugins.map((p) => p.name)).toEqual(["github"]);
99
+ });
100
+
101
+ describe("Terraform directories as bundles, modeled on classifyHelm", () => {
102
+ // No real @intentius/chant-lexicon-terraform package exists yet (#2085), so
103
+ // these tests supply a bare stand-in plugin rather than loadAuditPlugins().
104
+ const terraform = { name: "terraform" };
105
+
106
+ test("a directory with two .tf files, and a nested subdirectory with its own .tf file, classify as two separate terraform inputs", () => {
107
+ const found = discoverByDetection(fixture("audit-terraform"), [terraform]);
108
+ const tf = found.filter((i) => i.lexicon === "terraform");
109
+ expect(targets(tf)).toEqual(["terraform:infra", "terraform:infra/modules/foo"]);
110
+ const root = tf.find((i) => i.path === "infra")!;
111
+ expect(Object.keys(root.files!).sort()).toEqual(["main.tf", "variables.tf"]);
112
+ const nested = tf.find((i) => i.path === "infra/modules/foo")!;
113
+ expect(Object.keys(nested.files!).sort()).toEqual(["main.tf"]);
114
+ });
115
+
116
+ test("a bundle's content is the whole root module in filename order, each file behind a `# file:` marker", () => {
117
+ const found = discoverByDetection(fixture("audit-terraform"), [terraform]);
118
+ const root = found.find((i) => i.lexicon === "terraform" && i.path === "infra")!;
119
+ expect(root.content).toContain("# file: main.tf\nresource \"null_resource\" \"root\" {}");
120
+ expect(root.content).toContain("# file: variables.tf\nvariable \"region\"");
121
+ expect(root.content.indexOf("# file: main.tf")).toBeLessThan(root.content.indexOf("# file: variables.tf"));
122
+ });
123
+
124
+ test(".terraform/ contributes no bundle (walkFiles already skips dot-directories outside WALK_DOT_DIRS)", () => {
125
+ const found = discoverByDetection(fixture("audit-terraform"), [terraform]);
126
+ expect(found.some((i) => i.path.includes(".terraform"))).toBe(false);
127
+ });
128
+
129
+ test("without the terraform plugin, .tf files are classified into nothing (skipped, same as an unloaded helm)", () => {
130
+ const found = discoverByDetection(fixture("audit-terraform"), []);
131
+ expect(found).toEqual([]);
132
+ });
133
+ });
95
134
  });
96
135
 
97
136
  describe("lexicon hints for unclaimed files (#1623)", () => {
@@ -21,6 +21,11 @@
21
21
  * `detectTemplate` (which only knows Compose's `services:`) can't see them.
22
22
  * - Helm charts are a directory BUNDLE keyed by `Chart.yaml`; the helm checks
23
23
  * read `output.files`, so the whole chart is one AuditInput.
24
+ * - Terraform is also a directory BUNDLE, keyed by that directory's own
25
+ * `.tf` filenames, not recursively: a nested directory with its own
26
+ * `.tf` files (e.g. `modules/foo`) is a separate root module and gets its
27
+ * own AuditInput. `.tf` isn't YAML/JSON either, so `detectTemplate` can't
28
+ * see it.
24
29
  * - k8s `detectTemplate` matches any `apiVersion`+`kind`, including GCP Config
25
30
  * Connector (`cnrm.cloud.google.com`) resources and fountain
26
31
  * (`fountain.dev/v1`) manifests, so gcp and fountain must be tried first.
@@ -34,9 +39,10 @@ import { parseYAML } from "../yaml";
34
39
  import type { LexiconPlugin } from "../lexicon";
35
40
  import type { AuditInput, AuditLexicon } from "./core";
36
41
  import { isNginxConfigPath } from "./nginx";
42
+ import { gitignoreCoversTerraformState, isTerraformStatePath } from "./terraform-state";
37
43
 
38
44
  /** Lexicons the auditor knows how to detect and run checks for. */
39
- export const AUDIT_LEXICONS = ["github", "gitlab", "forgejo", "k8s", "docker", "aws", "azure", "gcp", "helm", "fountain"] as const;
45
+ export const AUDIT_LEXICONS = ["github", "gitlab", "forgejo", "k8s", "docker", "aws", "azure", "gcp", "helm", "fountain", "terraform"] as const;
40
46
 
41
47
  /**
42
48
  * Load the plugins used for detection. Each is loaded in isolation (no
@@ -63,6 +69,8 @@ export async function loadAuditPlugins(names: readonly string[] = AUDIT_LEXICONS
63
69
  }
64
70
 
65
71
  const WALK_SKIP = new Set(["node_modules", ".git", "dist"]);
72
+ /** Terraform's local working directory: recorded by the walk, never entered (TF023). */
73
+ const TERRAFORM_WORK_DIR = ".terraform";
66
74
  /** Dot-directories the walk descends into anyway (CI lives here). */
67
75
  const WALK_DOT_DIRS = new Set([".github", ".forgejo"]);
68
76
  const MAX_WALK_FILES = 1000;
@@ -80,7 +88,15 @@ function walkFiles(dir: string, out: string[]): void {
80
88
  }
81
89
  for (const e of entries.sort((a, b) => (a.name < b.name ? -1 : 1))) {
82
90
  if (out.length >= MAX_WALK_FILES) return;
83
- if (e.name.startsWith(".") && e.isDirectory() && !WALK_DOT_DIRS.has(e.name)) continue;
91
+ if (e.name.startsWith(".") && e.isDirectory() && !WALK_DOT_DIRS.has(e.name)) {
92
+ // `.terraform/` is never descended into: it is machine-generated,
93
+ // routinely hundreds of megabytes of provider binaries, and nothing in
94
+ // it is worth reading. Its presence is itself the finding (TF023), so
95
+ // the directory path alone is recorded and `collectCandidates` hands it
96
+ // on with no content.
97
+ if (e.name === TERRAFORM_WORK_DIR) out.push(join(dir, e.name));
98
+ continue;
99
+ }
84
100
  if (WALK_SKIP.has(e.name)) continue;
85
101
  const full = join(dir, e.name);
86
102
  if (e.isDirectory()) walkFiles(full, out);
@@ -105,6 +121,10 @@ function isDockerfileName(name: string): boolean {
105
121
  return name === "Dockerfile" || name.startsWith("Dockerfile.") || name.endsWith(".Dockerfile") || name.endsWith(".dockerfile");
106
122
  }
107
123
 
124
+ function isTerraformFileName(name: string): boolean {
125
+ return /\.tf$/i.test(name);
126
+ }
127
+
108
128
  /**
109
129
  * Filenames the secrets scan (#443) wants that no lexicon otherwise reads —
110
130
  * dotenv files and key/cert material. Included in `isCandidatePath` so both
@@ -294,11 +314,11 @@ export function isCandidatePath(path: string): boolean {
294
314
  * (path, filename, a few content markers) with no lexicon plugin involved.
295
315
  * Names the lexicon that would have claimed a file when that lexicon isn't
296
316
  * installed (#1623). Deliberately coarse: precision comes from the plugin's
297
- * `detectTemplate`, which is exactly what's missing in that case.
298
- * `terraform` is not an audit lexicon; `*.tf` is surfaced so the user learns
299
- * the audit never reads it (see `chant carve`).
317
+ * `detectTemplate`, which is exactly what's missing in that case. `terraform`
318
+ * is a lexicon like any other here: a `.tf` file hints at it by name, the
319
+ * same way `Chart.yaml` hints at helm.
300
320
  */
301
- export type LexiconHint = AuditLexicon | "terraform";
321
+ export type LexiconHint = AuditLexicon;
302
322
 
303
323
  export function hintLexiconForFile(path: string, content: string): LexiconHint | undefined {
304
324
  const name = basename(path);
@@ -306,7 +326,7 @@ export function hintLexiconForFile(path: string, content: string): LexiconHint |
306
326
  if (ci) return ci;
307
327
  if (isDockerfileName(name)) return "docker";
308
328
  if (name === "Chart.yaml") return "helm";
309
- if (/\.tf$/i.test(name)) return "terraform";
329
+ if (isTerraformFileName(name)) return "terraform";
310
330
  const head = content.slice(0, 64 * 1024);
311
331
  if (/cnrm\.cloud\.google\.com/.test(head)) return "gcp";
312
332
  if (/fountain\.dev\/v1/.test(head)) return "fountain";
@@ -371,13 +391,53 @@ function classifyHelm(files: RepoFile[], plugin: DetectPlugin | undefined): { in
371
391
  return { inputs, prefixes };
372
392
  }
373
393
 
394
+ /**
395
+ * Collect Terraform root modules as directory bundles from an in-memory file
396
+ * set, modeled on `classifyHelm`. Unlike a Helm chart (its whole subtree is
397
+ * one bundle), a Terraform root module references only its own sibling
398
+ * files, so grouping is by each `.tf` file's immediate directory, not
399
+ * recursive: a nested directory with its own `.tf` files (`modules/foo`) is
400
+ * a separate root module and gets its own AuditInput. Returns inputs plus
401
+ * the exact `.tf` paths claimed, so the loose-file pass in `classifyFiles`
402
+ * can skip them.
403
+ */
404
+ function classifyTerraform(files: RepoFile[], plugin: DetectPlugin | undefined): { inputs: AuditInput[]; claimed: Set<string> } {
405
+ const inputs: AuditInput[] = [];
406
+ const claimed = new Set<string>();
407
+ if (!plugin) return { inputs, claimed };
408
+ const byDir = new Map<string, Record<string, string>>();
409
+ for (const f of files) {
410
+ if (!isTerraformFileName(basename(f.path))) continue;
411
+ const slash = f.path.lastIndexOf("/");
412
+ const dir = slash === -1 ? "" : f.path.slice(0, slash);
413
+ const name = slash === -1 ? f.path : f.path.slice(slash + 1);
414
+ const bundle = byDir.get(dir) ?? {};
415
+ bundle[name] = f.content;
416
+ byDir.set(dir, bundle);
417
+ claimed.add(f.path);
418
+ }
419
+ for (const [dir, bundle] of byDir) {
420
+ // `content` is the whole root module, every `.tf` in filename order with a
421
+ // `# file:` line comment (legal HCL) marking each boundary, so a lexicon's
422
+ // `auditEntities(content)` sees the module Terraform itself would load,
423
+ // not one arbitrary file of it. `files` keeps the per-file split.
424
+ const content = Object.keys(bundle)
425
+ .sort()
426
+ .map((name) => `# file: ${name}\n${bundle[name]}`)
427
+ .join("\n");
428
+ inputs.push({ path: dir === "" ? "." : dir, content, lexicon: "terraform", files: bundle });
429
+ }
430
+ return { inputs, claimed };
431
+ }
432
+
374
433
  /**
375
434
  * Classify an in-memory set of repo files into per-lexicon audit inputs. Pure
376
435
  * (no fs, no network) so the local walk and the remote tree-fetch share exactly
377
436
  * one detection path. Each file maps to at most one lexicon: CI by path,
378
- * Dockerfiles by name, Helm charts as a bundle, everything else by the
379
- * precedence-ordered content detectors (delegating to each plugin's
380
- * `detectTemplate`). Lexicons whose plugin isn't provided are skipped.
437
+ * Dockerfiles by name, Helm charts and Terraform root modules as directory
438
+ * bundles, everything else by the precedence-ordered content detectors
439
+ * (delegating to each plugin's `detectTemplate`). Lexicons whose plugin isn't
440
+ * provided are skipped.
381
441
  */
382
442
  export function classifyFiles(files: RepoFile[], plugins: DetectPlugin[]): AuditInput[] {
383
443
  const byName = new Map(plugins.map((p) => [p.name, p]));
@@ -387,9 +447,12 @@ export function classifyFiles(files: RepoFile[], plugins: DetectPlugin[]): Audit
387
447
  const helm = classifyHelm(files, byName.get("helm"));
388
448
  const underChart = (p: string): boolean => helm.prefixes.some((pre) => (pre === "" ? true : p.startsWith(pre)));
389
449
 
390
- const inputs: AuditInput[] = [...helm.inputs];
450
+ const terraform = classifyTerraform(files, byName.get("terraform"));
451
+
452
+ const inputs: AuditInput[] = [...helm.inputs, ...terraform.inputs];
391
453
  for (const { path, content } of files) {
392
454
  if (underChart(path)) continue;
455
+ if (terraform.claimed.has(path)) continue;
393
456
  const name = basename(path);
394
457
 
395
458
  const ci = ciLexiconForPath(path);
@@ -427,13 +490,26 @@ export function discoverByDetection(root: string, plugins: DetectPlugin[]): Audi
427
490
  export function collectCandidates(root: string): RepoFile[] {
428
491
  const all: string[] = [];
429
492
  walkFiles(root, all);
493
+ // TF023 reads paths, not content: a `.tfstate` can be tens of megabytes of
494
+ // machine-generated JSON, and the finding is that it is in the repository at
495
+ // all. The root `.gitignore` is what separates "committed" from "merely on
496
+ // disk" here — a Terraform working tree almost always has an ignored
497
+ // `.terraform/` in it, and reporting that would make TF023 fire on every
498
+ // local audit. A fetched repository has no such ambiguity (its file list is
499
+ // the tracked files) and is not filtered.
500
+ const gitignore = readSafe(join(root, ".gitignore")) ?? "";
430
501
  const files: RepoFile[] = [];
431
502
  for (const full of all) {
432
503
  const path = relative(root, full);
433
- // Terraform is never audited; the path alone is enough to say so (#1623).
434
- // Kept out of `isCandidatePath` so remote fetches never pull HCL.
435
- if (/\.tf$/i.test(path)) {
436
- files.push({ path, content: "" });
504
+ if (isTerraformStatePath(path)) {
505
+ if (!gitignoreCoversTerraformState(gitignore, path)) files.push({ path, content: "" });
506
+ continue;
507
+ }
508
+ // `.tf` is read locally so `classifyTerraform` can bundle real content.
509
+ // It stays out of `isCandidatePath` so remote fetches still never pull HCL.
510
+ if (isTerraformFileName(basename(path))) {
511
+ const content = readSafe(full);
512
+ if (content !== undefined) files.push({ path, content });
437
513
  continue;
438
514
  }
439
515
  if (!isCandidatePath(path)) continue;
@@ -0,0 +1,100 @@
1
+ /**
2
+ * Prior art for these audit rules: the open-source tools whose checks cover the
3
+ * same condition, credited per rule. See packages/core/src/audit/prior-art.ts for
4
+ * the registry, the relation vocabulary, and why this is credit rather than
5
+ * authority. Kept by hand; the prior-art sweep (scripts/prior-art-sweep.ts) reports
6
+ * when a credited tool's index no longer lists a rule cited here.
7
+ */
8
+ import type { Lineage } from "./prior-art";
9
+
10
+ export const coreAuditLineage: Record<string, Lineage[]> = {
11
+ AGT002: [
12
+ { tool: "agent-audit", rule: "auth-bypass/env-secret-in-config", url: "https://raw.githubusercontent.com/piiiico/agent-audit/main/README.md", relation: "equivalent" },
13
+ { tool: "mcp-audit", rule: "Secrets Detection", url: "https://raw.githubusercontent.com/apisec-inc/mcp-audit/main/README.md", relation: "overlaps" },
14
+ { tool: "agent-scan", rule: "W008", url: "https://raw.githubusercontent.com/invariantlabs-ai/mcp-scan/main/docs/issue-codes.md", relation: "overlaps" },
15
+ ],
16
+ COR020: [
17
+ { tool: "cfn-lint", rule: "E3004", url: "https://github.com/aws-cloudformation/cfn-lint/blob/main/docs/rules.md#E3004", relation: "equivalent" },
18
+ ],
19
+ EXT001: [
20
+ { tool: "cfn-lint", url: "https://github.com/aws-cloudformation/cfn-lint/blob/main/docs/cfn-schema-specification.md#extending-the-schemas-with-new-keywords", relation: "equivalent" },
21
+ { tool: "cfn-lint", rule: "E3014", url: "https://github.com/aws-cloudformation/cfn-lint/blob/main/docs/rules.md#E3014", relation: "equivalent" },
22
+ { tool: "cfn-lint", rule: "E3021", url: "https://github.com/aws-cloudformation/cfn-lint/blob/main/docs/rules.md#E3021", relation: "equivalent" },
23
+ ],
24
+ NGX001: [
25
+ { tool: "gixy-ng", rule: "weak_ssl_tls", url: "https://gixy.getpagespeed.com/plugins/weak_ssl_tls/", relation: "overlaps" },
26
+ ],
27
+ NGX002: [
28
+ { tool: "gixy-ng", rule: "weak_ssl_tls", url: "https://gixy.getpagespeed.com/plugins/weak_ssl_tls/", relation: "overlaps" },
29
+ ],
30
+ NGX004: [
31
+ { tool: "gixy", rule: "alias_traversal", url: "https://github.com/yandex/gixy", relation: "equivalent" },
32
+ { tool: "gixy-ng", rule: "alias_traversal", url: "https://gixy.getpagespeed.com/plugins/aliastraversal/", relation: "equivalent" },
33
+ ],
34
+ NGX005: [
35
+ { tool: "gixy-ng", rule: "status_page_exposed", url: "https://gixy.getpagespeed.com/checks/status-page-exposed/", relation: "overlaps" },
36
+ ],
37
+ NGX006: [
38
+ { tool: "gixy-ng", rule: "version_disclosure", url: "https://gixy.getpagespeed.com/plugins/version_disclosure/", relation: "equivalent" },
39
+ ],
40
+ SEC001: [
41
+ { tool: "gitleaks", rule: "aws-access-token", url: "https://github.com/gitleaks/gitleaks/blob/master/config/gitleaks.toml", relation: "equivalent" },
42
+ { tool: "trufflehog", rule: "aws", url: "https://github.com/trufflesecurity/trufflehog/blob/main/pkg/detectors/aws/access_keys/accesskey.go", relation: "overlaps" },
43
+ { tool: "detect-secrets", rule: "AWSKeyDetector", url: "https://github.com/Yelp/detect-secrets/blob/master/detect_secrets/plugins/aws.py", relation: "equivalent" },
44
+ ],
45
+ SEC002: [
46
+ { tool: "gitleaks", rule: "generic-api-key", url: "https://github.com/gitleaks/gitleaks/blob/master/config/gitleaks.toml", relation: "overlaps" },
47
+ { tool: "trufflehog", rule: "aws", url: "https://github.com/trufflesecurity/trufflehog/blob/main/pkg/detectors/aws/access_keys/accesskey.go", relation: "overlaps" },
48
+ { tool: "detect-secrets", rule: "AWSKeyDetector", url: "https://github.com/Yelp/detect-secrets/blob/master/detect_secrets/plugins/aws.py", relation: "overlaps" },
49
+ ],
50
+ SEC003: [
51
+ { tool: "gitleaks", rule: "github-pat", url: "https://github.com/gitleaks/gitleaks/blob/master/config/gitleaks.toml", relation: "overlaps" },
52
+ { tool: "trufflehog", rule: "github", url: "https://github.com/trufflesecurity/trufflehog/blob/main/pkg/detectors/github/v2/github.go", relation: "equivalent" },
53
+ { tool: "detect-secrets", rule: "GitHubTokenDetector", url: "https://github.com/Yelp/detect-secrets/blob/master/detect_secrets/plugins/github_token.py", relation: "overlaps" },
54
+ ],
55
+ SEC004: [
56
+ { tool: "gitleaks", rule: "slack-bot-token", url: "https://github.com/gitleaks/gitleaks/blob/master/config/gitleaks.toml", relation: "overlaps" },
57
+ { tool: "trufflehog", rule: "slack", url: "https://github.com/trufflesecurity/trufflehog/blob/main/pkg/detectors/slack/slack.go", relation: "overlaps" },
58
+ { tool: "detect-secrets", rule: "SlackDetector", url: "https://github.com/Yelp/detect-secrets/blob/master/detect_secrets/plugins/slack.py", relation: "overlaps" },
59
+ ],
60
+ SEC005: [
61
+ { tool: "gitleaks", rule: "gcp-api-key", url: "https://github.com/gitleaks/gitleaks/blob/master/config/gitleaks.toml", relation: "equivalent" },
62
+ { tool: "trufflehog", rule: "googlegemini", url: "https://github.com/trufflesecurity/trufflehog/blob/main/pkg/detectors/googlegemini/googlegemini.go", relation: "overlaps" },
63
+ ],
64
+ SEC006: [
65
+ { tool: "gitleaks", rule: "stripe-access-token", url: "https://github.com/gitleaks/gitleaks/blob/master/config/gitleaks.toml", relation: "overlaps" },
66
+ { tool: "trufflehog", rule: "stripe", url: "https://github.com/trufflesecurity/trufflehog/blob/main/pkg/detectors/stripe/stripe.go", relation: "overlaps" },
67
+ { tool: "detect-secrets", rule: "StripeDetector", url: "https://github.com/Yelp/detect-secrets/blob/master/detect_secrets/plugins/stripe.py", relation: "overlaps" },
68
+ ],
69
+ SEC007: [
70
+ { tool: "gitleaks", rule: "private-key", url: "https://github.com/gitleaks/gitleaks/blob/master/config/gitleaks.toml", relation: "equivalent" },
71
+ { tool: "trufflehog", rule: "privatekey", url: "https://github.com/trufflesecurity/trufflehog/blob/main/pkg/detectors/privatekey/privatekey.go", relation: "equivalent" },
72
+ { tool: "detect-secrets", rule: "PrivateKeyDetector", url: "https://github.com/Yelp/detect-secrets/blob/master/detect_secrets/plugins/private_key.py", relation: "equivalent" },
73
+ ],
74
+ SEC008: [
75
+ { tool: "gitleaks", rule: "curl-auth-header", url: "https://github.com/gitleaks/gitleaks/blob/master/config/gitleaks.toml", relation: "overlaps" },
76
+ ],
77
+ SEC009: [
78
+ { tool: "detect-secrets", rule: "BasicAuthDetector", url: "https://github.com/Yelp/detect-secrets/blob/master/detect_secrets/plugins/basic_auth.py", relation: "equivalent" },
79
+ { tool: "trufflehog", rule: "uri", url: "https://github.com/trufflesecurity/trufflehog/blob/main/pkg/detectors/uri/uri.go", relation: "overlaps" },
80
+ { tool: "gitleaks", rule: "curl-auth-user", url: "https://github.com/gitleaks/gitleaks/blob/master/config/gitleaks.toml", relation: "overlaps" },
81
+ ],
82
+ SEC010: [
83
+ { tool: "detect-secrets", rule: "Base64HighEntropyString", url: "https://github.com/Yelp/detect-secrets/blob/master/detect_secrets/plugins/high_entropy_strings.py", relation: "overlaps" },
84
+ { tool: "detect-secrets", rule: "HexHighEntropyString", url: "https://github.com/Yelp/detect-secrets/blob/master/detect_secrets/plugins/high_entropy_strings.py", relation: "overlaps" },
85
+ { tool: "gitleaks", rule: "generic-api-key", url: "https://github.com/gitleaks/gitleaks/blob/master/config/gitleaks.toml", relation: "overlaps" },
86
+ ],
87
+ // TF023 is the one TF id core owns (./terraform-state.ts); the rest of the
88
+ // family credits its prior art from lexicons/terraform/src/lint/audit-lineage.ts.
89
+ // No linter or scanner in #2107's survey checks for committed state, but the
90
+ // style guide's .gitignore section names the exact files, so the document is
91
+ // both the authority and the first written statement of the rule.
92
+ TF023: [
93
+ {
94
+ tool: "hashicorp-style-guide",
95
+ rule: ".gitignore: do not commit terraform.tfstate, terraform.tfstate.* backups, or the .terraform directory",
96
+ url: "https://developer.hashicorp.com/terraform/language/style#gitignore",
97
+ relation: "equivalent",
98
+ },
99
+ ],
100
+ };