@intentius/chant 0.57.0 → 0.59.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 (558) 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 +36 -1
  4. package/dist/audit/core.d.ts.map +1 -1
  5. package/dist/audit/discover.d.ts +26 -9
  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/carve-bridge.d.ts.map +1 -1
  34. package/dist/cli/commands/check-lexicon-docs.d.ts +1 -1
  35. package/dist/cli/commands/check-lexicon-examples.d.ts +16 -6
  36. package/dist/cli/commands/check-lexicon-examples.d.ts.map +1 -1
  37. package/dist/cli/commands/init-lexicon/templates/codegen.d.ts +1 -0
  38. package/dist/cli/commands/init-lexicon/templates/codegen.d.ts.map +1 -1
  39. package/dist/cli/commands/init-lexicon/templates/project.d.ts +7 -0
  40. package/dist/cli/commands/init-lexicon/templates/project.d.ts.map +1 -1
  41. package/dist/cli/commands/init-lexicon.d.ts.map +1 -1
  42. package/dist/cli/commands/init.d.ts.map +1 -1
  43. package/dist/cli/commands/lint.d.ts +13 -0
  44. package/dist/cli/commands/lint.d.ts.map +1 -1
  45. package/dist/cli/commands/onboard.d.ts +2 -0
  46. package/dist/cli/commands/onboard.d.ts.map +1 -1
  47. package/dist/cli/handlers/build.d.ts.map +1 -1
  48. package/dist/cli/handlers/lifecycle.d.ts.map +1 -1
  49. package/dist/cli/handlers/operator.d.ts +82 -16
  50. package/dist/cli/handlers/operator.d.ts.map +1 -1
  51. package/dist/cli/handlers/run.d.ts +49 -42
  52. package/dist/cli/handlers/run.d.ts.map +1 -1
  53. package/dist/cli/main.d.ts +12 -1
  54. package/dist/cli/main.d.ts.map +1 -1
  55. package/dist/cli/mcp/op-tools.d.ts +16 -1
  56. package/dist/cli/mcp/op-tools.d.ts.map +1 -1
  57. package/dist/cli/mcp/resource-handlers.d.ts.map +1 -1
  58. package/dist/cli/mcp/resources/context.d.ts.map +1 -1
  59. package/dist/cli/plugins.d.ts +22 -0
  60. package/dist/cli/plugins.d.ts.map +1 -1
  61. package/dist/cli/registry.d.ts +40 -9
  62. package/dist/cli/registry.d.ts.map +1 -1
  63. package/dist/codegen/generate-post-synth-barrel.d.ts +1 -1
  64. package/dist/components/auto-release.d.ts +10 -20
  65. package/dist/components/auto-release.d.ts.map +1 -1
  66. package/dist/components/capability.d.ts +4 -4
  67. package/dist/components/cli-support.d.ts +24 -22
  68. package/dist/components/cli-support.d.ts.map +1 -1
  69. package/dist/components/component.d.ts +21 -6
  70. package/dist/components/component.d.ts.map +1 -1
  71. package/dist/components/driver-output.d.ts.map +1 -1
  72. package/dist/components/driver.d.ts +73 -37
  73. package/dist/components/driver.d.ts.map +1 -1
  74. package/dist/components/index.d.ts +1 -2
  75. package/dist/components/index.d.ts.map +1 -1
  76. package/dist/components/manifest-persistence.d.ts +1 -9
  77. package/dist/components/manifest-persistence.d.ts.map +1 -1
  78. package/dist/components/run-progress.d.ts +6 -6
  79. package/dist/components/run-progress.d.ts.map +1 -1
  80. package/dist/components/verbs/run-agent.d.ts +10 -22
  81. package/dist/components/verbs/run-agent.d.ts.map +1 -1
  82. package/dist/components/verbs/verify.d.ts +2 -2
  83. package/dist/deep-observation.d.ts +17 -0
  84. package/dist/deep-observation.d.ts.map +1 -1
  85. package/dist/detectLexicon.d.ts +13 -0
  86. package/dist/detectLexicon.d.ts.map +1 -1
  87. package/dist/discovery/files.d.ts +4 -4
  88. package/dist/discovery/fold-import.d.ts.map +1 -1
  89. package/dist/discovery/param-deps.d.ts +33 -0
  90. package/dist/discovery/param-deps.d.ts.map +1 -1
  91. package/dist/discovery/sandbox/bundle.d.ts.map +1 -1
  92. package/dist/discovery/sandbox/config-wire.d.ts +2 -2
  93. package/dist/fold/foldable-helpers.d.ts +5 -1
  94. package/dist/fold/foldable-helpers.d.ts.map +1 -1
  95. package/dist/fold-provenance.d.ts +236 -0
  96. package/dist/fold-provenance.d.ts.map +1 -0
  97. package/dist/graph-ops.d.ts +1 -1
  98. package/dist/held-elsewhere.d.ts +85 -0
  99. package/dist/held-elsewhere.d.ts.map +1 -0
  100. package/dist/index.d.ts +3 -0
  101. package/dist/index.d.ts.map +1 -1
  102. package/dist/kubectl-context.d.ts +3 -3
  103. package/dist/lexicon-config.d.ts +4 -4
  104. package/dist/lexicon-plugin-helpers.d.ts +1 -1
  105. package/dist/lexicon.d.ts +273 -16
  106. package/dist/lexicon.d.ts.map +1 -1
  107. package/dist/lifecycle/change-set.d.ts +13 -0
  108. package/dist/lifecycle/change-set.d.ts.map +1 -1
  109. package/dist/lifecycle/deep-diff.d.ts +133 -6
  110. package/dist/lifecycle/deep-diff.d.ts.map +1 -1
  111. package/dist/lifecycle/deep-observe.d.ts.map +1 -1
  112. package/dist/lifecycle/gate-ledger.d.ts +79 -3
  113. package/dist/lifecycle/gate-ledger.d.ts.map +1 -1
  114. package/dist/lifecycle/index.d.ts +1 -0
  115. package/dist/lifecycle/index.d.ts.map +1 -1
  116. package/dist/lifecycle/release-ledger.d.ts +2 -2
  117. package/dist/lifecycle/run-ledger.d.ts +53 -0
  118. package/dist/lifecycle/run-ledger.d.ts.map +1 -0
  119. package/dist/lifecycle/symptoms.d.ts +3 -3
  120. package/dist/lifecycle/symptoms.d.ts.map +1 -1
  121. package/dist/lint/config.d.ts +81 -2
  122. package/dist/lint/config.d.ts.map +1 -1
  123. package/dist/lint/post-synth.d.ts +55 -2
  124. package/dist/lint/post-synth.d.ts.map +1 -1
  125. package/dist/lint/rules/__fixtures__/comp/comp004/pass/neo4j-cluster.component.d.ts +1 -1
  126. package/dist/lint/rules/__fixtures__/comp/comp004/pass/neo4j-cluster.component.d.ts.map +1 -1
  127. package/dist/lint/rules/__fixtures__/comp/comp004/pass/orders-table.component.d.ts +1 -1
  128. package/dist/lint/rules/__fixtures__/comp/comp004/pass/orders-table.component.d.ts.map +1 -1
  129. package/dist/lint/rules/comp/comp004-gate-needs-durable-runtime.d.ts +44 -0
  130. package/dist/lint/rules/comp/comp004-gate-needs-durable-runtime.d.ts.map +1 -0
  131. package/dist/lint/rules/comp/index.d.ts +1 -1
  132. package/dist/lint/rules/comp/index.d.ts.map +1 -1
  133. package/dist/lint/rules/op/index.d.ts +16 -0
  134. package/dist/lint/rules/op/index.d.ts.map +1 -0
  135. package/dist/lint/rules/op/ops012-activity-contract.d.ts +27 -0
  136. package/dist/lint/rules/op/ops012-activity-contract.d.ts.map +1 -0
  137. package/dist/lint/rules/op/ops013-step-output-ref.d.ts +61 -0
  138. package/dist/lint/rules/op/ops013-step-output-ref.d.ts.map +1 -0
  139. package/dist/lint/rules/op/ops014-converge-rule-refusals.d.ts +55 -0
  140. package/dist/lint/rules/op/ops014-converge-rule-refusals.d.ts.map +1 -0
  141. package/dist/lint/rules/op/support.d.ts +12 -0
  142. package/dist/lint/rules/op/support.d.ts.map +1 -0
  143. package/dist/lint/suppressions.d.ts +108 -0
  144. package/dist/lint/suppressions.d.ts.map +1 -0
  145. package/dist/live-endpoint.d.ts +1 -1
  146. package/dist/op/activities/activity-contracts.d.ts +54 -0
  147. package/dist/op/activities/activity-contracts.d.ts.map +1 -0
  148. package/dist/op/activities/apply.d.ts +262 -0
  149. package/dist/op/activities/apply.d.ts.map +1 -0
  150. package/dist/op/activities/build.d.ts +13 -0
  151. package/dist/op/activities/build.d.ts.map +1 -0
  152. package/dist/op/activities/converge.d.ts +110 -0
  153. package/dist/op/activities/converge.d.ts.map +1 -0
  154. package/dist/op/activities/env-teardown.d.ts +76 -0
  155. package/dist/op/activities/env-teardown.d.ts.map +1 -0
  156. package/dist/op/activities/guard-validate.d.ts +64 -0
  157. package/dist/op/activities/guard-validate.d.ts.map +1 -0
  158. package/dist/op/activities/http-check.d.ts +34 -0
  159. package/dist/op/activities/http-check.d.ts.map +1 -0
  160. package/dist/op/activities/index.d.ts +31 -0
  161. package/dist/op/activities/index.d.ts.map +1 -0
  162. package/dist/op/activities/lexicon-upgrade.d.ts +182 -0
  163. package/dist/op/activities/lexicon-upgrade.d.ts.map +1 -0
  164. package/dist/op/activities/lifecycle.d.ts +42 -0
  165. package/dist/op/activities/lifecycle.d.ts.map +1 -0
  166. package/dist/op/activities/pipeline-audit.d.ts +84 -0
  167. package/dist/op/activities/pipeline-audit.d.ts.map +1 -0
  168. package/dist/op/activities/policy.d.ts +17 -0
  169. package/dist/op/activities/policy.d.ts.map +1 -0
  170. package/dist/op/activities/reconcile.d.ts +149 -0
  171. package/dist/op/activities/reconcile.d.ts.map +1 -0
  172. package/dist/op/activities/shell.d.ts +13 -0
  173. package/dist/op/activities/shell.d.ts.map +1 -0
  174. package/dist/op/activities/teardown.d.ts +10 -0
  175. package/dist/op/activities/teardown.d.ts.map +1 -0
  176. package/dist/op/activities/util.d.ts +7 -0
  177. package/dist/op/activities/util.d.ts.map +1 -0
  178. package/dist/op/activities/wait.d.ts +28 -0
  179. package/dist/op/activities/wait.d.ts.map +1 -0
  180. package/dist/op/activities/workflow-audit.d.ts +92 -0
  181. package/dist/op/activities/workflow-audit.d.ts.map +1 -0
  182. package/dist/op/activity-contract-registry.d.ts +76 -0
  183. package/dist/op/activity-contract-registry.d.ts.map +1 -0
  184. package/dist/op/activity-contract.d.ts +25 -18
  185. package/dist/op/activity-contract.d.ts.map +1 -1
  186. package/dist/op/activity-failure.d.ts +28 -0
  187. package/dist/op/activity-failure.d.ts.map +1 -0
  188. package/dist/op/activity-profiles.d.ts +118 -0
  189. package/dist/op/activity-profiles.d.ts.map +1 -0
  190. package/dist/op/activity-registry.d.ts +22 -38
  191. package/dist/op/activity-registry.d.ts.map +1 -1
  192. package/dist/op/activity-runtime.d.ts +3 -14
  193. package/dist/op/activity-runtime.d.ts.map +1 -1
  194. package/dist/op/builders.d.ts +85 -30
  195. package/dist/op/builders.d.ts.map +1 -1
  196. package/dist/op/change-signal.d.ts +91 -0
  197. package/dist/op/change-signal.d.ts.map +1 -0
  198. package/dist/op/composites/apply-op.d.ts +118 -0
  199. package/dist/op/composites/apply-op.d.ts.map +1 -0
  200. package/dist/op/composites/converge-op.d.ts +120 -0
  201. package/dist/op/composites/converge-op.d.ts.map +1 -0
  202. package/dist/op/composites/index.d.ts +26 -0
  203. package/dist/op/composites/index.d.ts.map +1 -0
  204. package/dist/op/composites/lexicon-upgrade-op.d.ts +78 -0
  205. package/dist/op/composites/lexicon-upgrade-op.d.ts.map +1 -0
  206. package/dist/op/composites/pipeline-audit-op.d.ts +50 -0
  207. package/dist/op/composites/pipeline-audit-op.d.ts.map +1 -0
  208. package/dist/op/composites/reconcile-op.d.ts +57 -0
  209. package/dist/op/composites/reconcile-op.d.ts.map +1 -0
  210. package/dist/op/composites/watch-op.d.ts +60 -0
  211. package/dist/op/composites/watch-op.d.ts.map +1 -0
  212. package/dist/op/composites/workflow-audit-op.d.ts +57 -0
  213. package/dist/op/composites/workflow-audit-op.d.ts.map +1 -0
  214. package/dist/op/converge-rule.d.ts +12 -10
  215. package/dist/op/converge-rule.d.ts.map +1 -1
  216. package/dist/op/cron.d.ts +51 -0
  217. package/dist/op/cron.d.ts.map +1 -0
  218. package/dist/op/discover.d.ts +9 -0
  219. package/dist/op/discover.d.ts.map +1 -1
  220. package/dist/op/duration.d.ts +11 -0
  221. package/dist/op/duration.d.ts.map +1 -0
  222. package/dist/op/emulator-lifecycle.d.ts.map +1 -1
  223. package/dist/op/gate-name.d.ts +40 -0
  224. package/dist/op/gate-name.d.ts.map +1 -0
  225. package/dist/op/gate-summary.d.ts +43 -0
  226. package/dist/op/gate-summary.d.ts.map +1 -0
  227. package/dist/op/gate.d.ts +90 -0
  228. package/dist/op/gate.d.ts.map +1 -0
  229. package/dist/op/generate-pipeline.d.ts +9 -0
  230. package/dist/op/generate-pipeline.d.ts.map +1 -1
  231. package/dist/op/index.d.ts +29 -9
  232. package/dist/op/index.d.ts.map +1 -1
  233. package/dist/op/local-executor.d.ts +114 -28
  234. package/dist/op/local-executor.d.ts.map +1 -1
  235. package/dist/op/local-output.d.ts +11 -1
  236. package/dist/op/local-output.d.ts.map +1 -1
  237. package/dist/op/op-ir.d.ts +185 -0
  238. package/dist/op/op-ir.d.ts.map +1 -0
  239. package/dist/op/op-verb-class.d.ts +10 -2
  240. package/dist/op/op-verb-class.d.ts.map +1 -1
  241. package/dist/op/operator.d.ts +93 -5
  242. package/dist/op/operator.d.ts.map +1 -1
  243. package/dist/op/receipt-store.d.ts +1 -1
  244. package/dist/op/receipt-store.d.ts.map +1 -1
  245. package/dist/op/resource.d.ts +15 -1
  246. package/dist/op/resource.d.ts.map +1 -1
  247. package/dist/op/runtime.d.ts +200 -0
  248. package/dist/op/runtime.d.ts.map +1 -0
  249. package/dist/op/runtimes/local.d.ts +29 -0
  250. package/dist/op/runtimes/local.d.ts.map +1 -0
  251. package/dist/op/step-output-ref.d.ts +15 -13
  252. package/dist/op/step-output-ref.d.ts.map +1 -1
  253. package/dist/op/types.d.ts +96 -29
  254. package/dist/op/types.d.ts.map +1 -1
  255. package/dist/ownership.d.ts +1 -1
  256. package/dist/provenance.d.ts +21 -0
  257. package/dist/provenance.d.ts.map +1 -1
  258. package/dist/serializer-walker.d.ts.map +1 -1
  259. package/dist/terraform/bridge.d.ts +26 -7
  260. package/dist/terraform/bridge.d.ts.map +1 -1
  261. package/dist/terraform/carve-provider.d.ts +28 -2
  262. package/dist/terraform/carve-provider.d.ts.map +1 -1
  263. package/dist/terraform/data-source-shape.d.ts +77 -0
  264. package/dist/terraform/data-source-shape.d.ts.map +1 -0
  265. package/dist/terraform/graph.d.ts.map +1 -1
  266. package/dist/terraform/providers/kubernetes.d.ts +5 -0
  267. package/dist/terraform/providers/kubernetes.d.ts.map +1 -1
  268. package/dist/terraform/tier-map.d.ts +16 -4
  269. package/dist/terraform/tier-map.d.ts.map +1 -1
  270. package/dist/terraform/types.d.ts +8 -0
  271. package/dist/terraform/types.d.ts.map +1 -1
  272. package/package.json +6 -1
  273. package/src/audit/catalog.test.ts +79 -3
  274. package/src/audit/catalog.ts +111 -13
  275. package/src/audit/core.test.ts +26 -0
  276. package/src/audit/core.ts +84 -18
  277. package/src/audit/discover.test.ts +39 -0
  278. package/src/audit/discover.ts +202 -18
  279. package/src/audit/lineage.ts +100 -0
  280. package/src/audit/prior-art.ts +153 -0
  281. package/src/audit/proof.test.ts +67 -0
  282. package/src/audit/proof.ts +70 -0
  283. package/src/audit/report-html.ts +9 -4
  284. package/src/audit/report-model.ts +16 -2
  285. package/src/audit/report.test.ts +33 -0
  286. package/src/audit/report.ts +5 -3
  287. package/src/audit/rules-doc.test.ts +34 -2
  288. package/src/audit/rules-doc.ts +82 -9
  289. package/src/audit/secrets.ts +15 -3
  290. package/src/audit/terraform-state.test.ts +144 -0
  291. package/src/audit/terraform-state.ts +131 -0
  292. package/src/build.ts +31 -1
  293. package/src/claimed-fields.test.ts +145 -0
  294. package/src/claimed-fields.ts +208 -0
  295. package/src/cli/build-params-cli.ts +2 -2
  296. package/src/cli/command-group.ts +14 -0
  297. package/src/cli/commands/__fixtures__/audit-terraform/infra/.terraform/providers/lock.txt +1 -0
  298. package/src/cli/commands/__fixtures__/audit-terraform/infra/main.tf +1 -0
  299. package/src/cli/commands/__fixtures__/audit-terraform/infra/modules/foo/main.tf +1 -0
  300. package/src/cli/commands/__fixtures__/audit-terraform/infra/variables.tf +3 -0
  301. package/src/cli/commands/__fixtures__/init-lexicon-output/package.json +13 -4
  302. package/src/cli/commands/__fixtures__/init-lexicon-output/src/codegen/docs-cli.ts +4 -0
  303. package/src/cli/commands/__fixtures__/init-lexicon-output/src/codegen/package.ts +1 -0
  304. package/src/cli/commands/__fixtures__/init-lexicon-output/src/package-cli.ts +21 -0
  305. package/src/cli/commands/__snapshots__/init-lexicon.test.ts.snap +1 -0
  306. package/src/cli/commands/audit.test.ts +191 -11
  307. package/src/cli/commands/audit.ts +78 -28
  308. package/src/cli/commands/build.test.ts +330 -9
  309. package/src/cli/commands/build.ts +170 -24
  310. package/src/cli/commands/carve-bridge.test.ts +237 -14
  311. package/src/cli/commands/carve-bridge.ts +21 -17
  312. package/src/cli/commands/carve-emit-k8s.test.ts +17 -10
  313. package/src/cli/commands/check-lexicon-docs.ts +1 -1
  314. package/src/cli/commands/check-lexicon-examples.test.ts +29 -1
  315. package/src/cli/commands/check-lexicon-examples.ts +42 -10
  316. package/src/cli/commands/init-lexicon/templates/codegen.ts +9 -0
  317. package/src/cli/commands/init-lexicon/templates/project.ts +44 -4
  318. package/src/cli/commands/init-lexicon.test.ts +97 -2
  319. package/src/cli/commands/init-lexicon.ts +4 -2
  320. package/src/cli/commands/init.test.ts +35 -1
  321. package/src/cli/commands/init.ts +14 -3
  322. package/src/cli/commands/lint.test.ts +194 -1
  323. package/src/cli/commands/lint.ts +205 -7
  324. package/src/cli/commands/onboard.test.ts +75 -0
  325. package/src/cli/commands/onboard.ts +54 -23
  326. package/src/cli/handlers/build.test.ts +81 -1
  327. package/src/cli/handlers/build.ts +15 -1
  328. package/src/cli/handlers/graph.test.ts +3 -3
  329. package/src/cli/handlers/lifecycle.test.ts +195 -12
  330. package/src/cli/handlers/lifecycle.ts +123 -6
  331. package/src/cli/handlers/misc.ts +1 -1
  332. package/src/cli/handlers/operator.test.ts +183 -11
  333. package/src/cli/handlers/operator.ts +341 -55
  334. package/src/cli/handlers/run.test.ts +568 -1295
  335. package/src/cli/handlers/run.ts +446 -912
  336. package/src/cli/main.test.ts +22 -16
  337. package/src/cli/main.ts +66 -33
  338. package/src/cli/mcp/op-tools.ts +147 -88
  339. package/src/cli/mcp/resource-handlers.ts +10 -37
  340. package/src/cli/mcp/resources/context.ts +11 -12
  341. package/src/cli/mcp/server.test.ts +25 -27
  342. package/src/cli/mcp/server.ts +2 -2
  343. package/src/cli/plugins.test.ts +68 -2
  344. package/src/cli/plugins.ts +39 -0
  345. package/src/cli/registry.ts +40 -9
  346. package/src/codegen/docs.ts +1 -1
  347. package/src/codegen/generate-post-synth-barrel.test.ts +1 -1
  348. package/src/codegen/generate-post-synth-barrel.ts +1 -1
  349. package/src/components/README.md +4 -3
  350. package/src/components/__fixtures__/neo4j-fanout.json +1 -1
  351. package/src/components/auto-release.test.ts +4 -20
  352. package/src/components/auto-release.ts +17 -42
  353. package/src/components/capability.ts +4 -4
  354. package/src/components/cli-support.test.ts +28 -12
  355. package/src/components/cli-support.ts +60 -47
  356. package/src/components/component-schema.test.ts +18 -2
  357. package/src/components/component.schema.json +18 -5
  358. package/src/components/component.test.ts +2 -2
  359. package/src/components/component.ts +27 -7
  360. package/src/components/config-defaults.test.ts +2 -2
  361. package/src/components/driver-output.ts +14 -2
  362. package/src/components/driver.test.ts +109 -20
  363. package/src/components/driver.ts +201 -84
  364. package/src/components/index.ts +1 -6
  365. package/src/components/manifest-persistence.test.ts +2 -20
  366. package/src/components/manifest-persistence.ts +4 -24
  367. package/src/components/pilots/neo4j-fanout.pilot.ts +3 -3
  368. package/src/components/pilots/pilots-lint-clean.test.ts +11 -10
  369. package/src/components/run-progress.ts +6 -6
  370. package/src/components/verbs/run-agent.test.ts +19 -0
  371. package/src/components/verbs/run-agent.ts +12 -24
  372. package/src/components/verbs/verify.ts +2 -2
  373. package/src/deep-observation.test.ts +75 -0
  374. package/src/deep-observation.ts +48 -0
  375. package/src/detectLexicon.ts +18 -1
  376. package/src/discovery/collect.test.ts +3 -3
  377. package/src/discovery/files.ts +4 -4
  378. package/src/discovery/fold-import.test.ts +1 -1
  379. package/src/discovery/fold-import.ts +105 -2
  380. package/src/discovery/param-deps.test.ts +112 -1
  381. package/src/discovery/param-deps.ts +193 -0
  382. package/src/discovery/sandbox/bundle.ts +15 -1
  383. package/src/discovery/sandbox/config-wire.test.ts +2 -2
  384. package/src/discovery/sandbox/config-wire.ts +2 -2
  385. package/src/fold/fold.test.ts +56 -0
  386. package/src/fold/foldable-helpers.ts +82 -2
  387. package/src/fold-provenance-exclusion.test.ts +186 -0
  388. package/src/fold-provenance.test.ts +495 -0
  389. package/src/fold-provenance.ts +441 -0
  390. package/src/graph-ops.test.ts +3 -3
  391. package/src/graph-ops.ts +1 -1
  392. package/src/held-elsewhere.test.ts +102 -0
  393. package/src/held-elsewhere.ts +114 -0
  394. package/src/index.ts +3 -0
  395. package/src/kubectl-context.ts +3 -3
  396. package/src/lexicon-config.test.ts +5 -5
  397. package/src/lexicon-config.ts +4 -4
  398. package/src/lexicon-plugin-helpers.ts +1 -1
  399. package/src/lexicon.ts +296 -16
  400. package/src/lifecycle/change-set.test.ts +49 -0
  401. package/src/lifecycle/change-set.ts +67 -0
  402. package/src/lifecycle/converge-ledger.ts +1 -1
  403. package/src/lifecycle/deep-diff.test.ts +267 -14
  404. package/src/lifecycle/deep-diff.ts +217 -14
  405. package/src/lifecycle/deep-observe.test.ts +3 -1
  406. package/src/lifecycle/deep-observe.ts +16 -6
  407. package/src/lifecycle/gate-ledger.test.ts +74 -2
  408. package/src/lifecycle/gate-ledger.ts +154 -36
  409. package/src/lifecycle/index.ts +1 -0
  410. package/src/lifecycle/release-ledger.ts +2 -2
  411. package/src/lifecycle/run-ledger.test.ts +268 -0
  412. package/src/lifecycle/run-ledger.ts +201 -0
  413. package/src/lifecycle/symptoms.ts +3 -3
  414. package/src/lint/config.test.ts +82 -1
  415. package/src/lint/config.ts +112 -3
  416. package/src/lint/pipeline-change-gate.test.ts +2 -2
  417. package/src/lint/policy.test.ts +1 -1
  418. package/src/lint/post-synth.test.ts +31 -0
  419. package/src/lint/post-synth.ts +54 -1
  420. package/src/lint/rules/__fixtures__/comp/comp004/pass/neo4j-cluster.component.ts +2 -2
  421. package/src/lint/rules/__fixtures__/comp/comp004/pass/orders-table.component.ts +1 -1
  422. package/src/lint/rules/comp/comp.test.ts +27 -1
  423. package/src/lint/rules/comp/comp004-gate-needs-durable-runtime.ts +76 -0
  424. package/src/lint/rules/comp/index.ts +3 -3
  425. package/src/lint/rules/op/index.ts +23 -0
  426. package/src/lint/rules/op/ops012-activity-contract.test.ts +161 -0
  427. package/src/lint/rules/op/ops012-activity-contract.ts +64 -0
  428. package/src/lint/rules/op/ops013-step-output-ref.test.ts +237 -0
  429. package/src/lint/rules/op/ops013-step-output-ref.ts +98 -0
  430. package/src/lint/rules/op/ops014-converge-rule-refusals.test.ts +263 -0
  431. package/src/lint/rules/op/ops014-converge-rule-refusals.ts +179 -0
  432. package/src/lint/rules/op/support.ts +11 -0
  433. package/src/lint/suppressions.test.ts +163 -0
  434. package/src/lint/suppressions.ts +204 -0
  435. package/src/live-endpoint.ts +1 -1
  436. package/src/op/activities/activity-contracts.ts +66 -0
  437. package/src/op/activities/apply.test.ts +567 -0
  438. package/src/op/activities/apply.ts +662 -0
  439. package/src/op/activities/build.ts +26 -0
  440. package/src/op/activities/converge.test.ts +330 -0
  441. package/src/op/activities/converge.ts +449 -0
  442. package/src/op/activities/env-teardown.test.ts +187 -0
  443. package/src/op/activities/env-teardown.ts +171 -0
  444. package/src/op/activities/guard-validate.test.ts +139 -0
  445. package/src/op/activities/guard-validate.ts +223 -0
  446. package/src/op/activities/http-check.test.ts +48 -0
  447. package/src/op/activities/http-check.ts +55 -0
  448. package/src/op/activities/index.ts +119 -0
  449. package/src/op/activities/lexicon-upgrade.test.ts +492 -0
  450. package/src/op/activities/lexicon-upgrade.ts +673 -0
  451. package/src/op/activities/lifecycle.ts +85 -0
  452. package/src/op/activities/pipeline-audit.test.ts +67 -0
  453. package/src/op/activities/pipeline-audit.ts +201 -0
  454. package/src/op/activities/policy.test.ts +19 -0
  455. package/src/op/activities/policy.ts +37 -0
  456. package/src/op/activities/reconcile.test.ts +161 -0
  457. package/src/op/activities/reconcile.ts +354 -0
  458. package/src/op/activities/shell.ts +26 -0
  459. package/src/op/activities/teardown.ts +22 -0
  460. package/src/op/activities/util.ts +6 -0
  461. package/src/op/activities/wait.ts +69 -0
  462. package/src/op/activities/workflow-audit.test.ts +90 -0
  463. package/src/op/activities/workflow-audit.ts +203 -0
  464. package/src/op/activity-contract-registry.test.ts +98 -0
  465. package/src/op/activity-contract-registry.ts +114 -0
  466. package/src/op/activity-contract.test.ts +1 -1
  467. package/src/op/activity-contract.ts +37 -33
  468. package/src/op/activity-failure.ts +34 -0
  469. package/src/op/activity-profiles.test.ts +56 -0
  470. package/src/op/activity-profiles.ts +116 -0
  471. package/src/op/activity-registry.test.ts +37 -28
  472. package/src/op/activity-registry.ts +27 -56
  473. package/src/op/activity-runtime.ts +3 -47
  474. package/src/op/builders-exports.test.ts +0 -49
  475. package/src/op/builders-typed.test.ts +164 -0
  476. package/src/op/builders.ts +125 -41
  477. package/src/op/change-signal.test.ts +117 -0
  478. package/src/op/change-signal.ts +169 -0
  479. package/src/op/composites/apply-op.ts +218 -0
  480. package/src/op/composites/composites.test.ts +379 -0
  481. package/src/op/composites/converge-op.test.ts +168 -0
  482. package/src/op/composites/converge-op.ts +214 -0
  483. package/src/op/composites/index.ts +26 -0
  484. package/src/op/composites/lexicon-upgrade-op.test.ts +68 -0
  485. package/src/op/composites/lexicon-upgrade-op.ts +134 -0
  486. package/src/op/composites/pipeline-audit-op.test.ts +25 -0
  487. package/src/op/composites/pipeline-audit-op.ts +78 -0
  488. package/src/op/composites/reconcile-op.test.ts +51 -0
  489. package/src/op/composites/reconcile-op.ts +110 -0
  490. package/src/op/composites/watch-op.ts +108 -0
  491. package/src/op/composites/workflow-audit-op.test.ts +25 -0
  492. package/src/op/composites/workflow-audit-op.ts +88 -0
  493. package/src/op/converge-rule.ts +12 -10
  494. package/src/op/cron.test.ts +128 -0
  495. package/src/op/cron.ts +149 -0
  496. package/src/op/discover-root.test.ts +105 -1
  497. package/src/op/discover.test.ts +6 -2
  498. package/src/op/discover.ts +84 -16
  499. package/src/op/duration.ts +21 -0
  500. package/src/op/effect-step.test.ts +29 -14
  501. package/src/op/emulator-lifecycle.ts +1 -2
  502. package/src/op/gate-name.test.ts +65 -0
  503. package/src/op/gate-name.ts +60 -0
  504. package/src/op/gate-summary.ts +84 -0
  505. package/src/op/gate.ts +155 -0
  506. package/src/op/generate-pipeline.test.ts +65 -3
  507. package/src/op/generate-pipeline.ts +19 -2
  508. package/src/op/index.ts +48 -8
  509. package/src/op/local-executor.test.ts +178 -34
  510. package/src/op/local-executor.ts +371 -102
  511. package/src/op/local-output.test.ts +95 -11
  512. package/src/op/local-output.ts +33 -4
  513. package/src/op/op-ir.test.ts +292 -0
  514. package/src/op/op-ir.ts +419 -0
  515. package/src/op/op-verb-class.test.ts +2 -2
  516. package/src/op/op-verb-class.ts +19 -5
  517. package/src/op/op.test.ts +28 -7
  518. package/src/op/operator.test.ts +543 -6
  519. package/src/op/operator.ts +203 -20
  520. package/src/op/receipt-store.ts +1 -1
  521. package/src/op/resource.ts +20 -2
  522. package/src/op/runtime.ts +203 -0
  523. package/src/op/runtimes/local.test.ts +181 -0
  524. package/src/op/runtimes/local.ts +212 -0
  525. package/src/op/step-output-ref.ts +15 -13
  526. package/src/op/types.ts +106 -26
  527. package/src/ownership.ts +1 -1
  528. package/src/provenance.test.ts +6 -0
  529. package/src/provenance.ts +21 -1
  530. package/src/serializer-walker.test.ts +42 -0
  531. package/src/serializer-walker.ts +21 -2
  532. package/src/terraform/aws-resources.test.ts +13 -4
  533. package/src/terraform/bridge.test.ts +22 -9
  534. package/src/terraform/bridge.ts +89 -28
  535. package/src/terraform/carve-provider.ts +38 -2
  536. package/src/terraform/data-source-shape.ts +95 -0
  537. package/src/terraform/graph.ts +35 -7
  538. package/src/terraform/providers/kubernetes.ts +48 -2
  539. package/src/terraform/tier-map.ts +21 -6
  540. package/src/terraform/types.ts +8 -0
  541. package/dist/cli/handlers/op-progress.d.ts +0 -90
  542. package/dist/cli/handlers/op-progress.d.ts.map +0 -1
  543. package/dist/cli/handlers/run-client.d.ts +0 -127
  544. package/dist/cli/handlers/run-client.d.ts.map +0 -1
  545. package/dist/cli/handlers/run-report.d.ts +0 -5
  546. package/dist/cli/handlers/run-report.d.ts.map +0 -1
  547. package/dist/components/temporal-codegen-loader.d.ts +0 -27
  548. package/dist/components/temporal-codegen-loader.d.ts.map +0 -1
  549. package/dist/lint/rules/comp/comp004-gate-needs-temporal.d.ts +0 -37
  550. package/dist/lint/rules/comp/comp004-gate-needs-temporal.d.ts.map +0 -1
  551. package/src/cli/handlers/op-progress.test.ts +0 -268
  552. package/src/cli/handlers/op-progress.ts +0 -248
  553. package/src/cli/handlers/run-client.test.ts +0 -82
  554. package/src/cli/handlers/run-client.ts +0 -219
  555. package/src/cli/handlers/run-report.test.ts +0 -62
  556. package/src/cli/handlers/run-report.ts +0 -122
  557. package/src/components/temporal-codegen-loader.ts +0 -41
  558. package/src/lint/rules/comp/comp004-gate-needs-temporal.ts +0 -66
@@ -21,6 +21,12 @@
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 its own AuditInput, UNLESS another
27
+ * directory's `.tf` calls it as a local module, in which case it is a
28
+ * child module of that root and is parsed as part of it (#2217). `.tf`
29
+ * isn't YAML/JSON either, so `detectTemplate` can't see it.
24
30
  * - k8s `detectTemplate` matches any `apiVersion`+`kind`, including GCP Config
25
31
  * Connector (`cnrm.cloud.google.com`) resources and fountain
26
32
  * (`fountain.dev/v1`) manifests, so gcp and fountain must be tried first.
@@ -29,14 +35,15 @@
29
35
  */
30
36
 
31
37
  import { readdirSync, readFileSync, statSync } from "fs";
32
- import { basename, join, relative } from "path";
38
+ import { basename, join, relative, resolve } from "path";
33
39
  import { parseYAML } from "../yaml";
34
40
  import type { LexiconPlugin } from "../lexicon";
35
41
  import type { AuditInput, AuditLexicon } from "./core";
36
42
  import { isNginxConfigPath } from "./nginx";
43
+ import { gitignoreCoversTerraformState, isTerraformStatePath } from "./terraform-state";
37
44
 
38
45
  /** 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;
46
+ export const AUDIT_LEXICONS = ["github", "gitlab", "forgejo", "k8s", "docker", "aws", "azure", "gcp", "helm", "fountain", "terraform"] as const;
40
47
 
41
48
  /**
42
49
  * Load the plugins used for detection. Each is loaded in isolation (no
@@ -63,6 +70,8 @@ export async function loadAuditPlugins(names: readonly string[] = AUDIT_LEXICONS
63
70
  }
64
71
 
65
72
  const WALK_SKIP = new Set(["node_modules", ".git", "dist"]);
73
+ /** Terraform's local working directory: recorded by the walk, never entered (TF023). */
74
+ const TERRAFORM_WORK_DIR = ".terraform";
66
75
  /** Dot-directories the walk descends into anyway (CI lives here). */
67
76
  const WALK_DOT_DIRS = new Set([".github", ".forgejo"]);
68
77
  const MAX_WALK_FILES = 1000;
@@ -80,7 +89,15 @@ function walkFiles(dir: string, out: string[]): void {
80
89
  }
81
90
  for (const e of entries.sort((a, b) => (a.name < b.name ? -1 : 1))) {
82
91
  if (out.length >= MAX_WALK_FILES) return;
83
- if (e.name.startsWith(".") && e.isDirectory() && !WALK_DOT_DIRS.has(e.name)) continue;
92
+ if (e.name.startsWith(".") && e.isDirectory() && !WALK_DOT_DIRS.has(e.name)) {
93
+ // `.terraform/` is never descended into: it is machine-generated,
94
+ // routinely hundreds of megabytes of provider binaries, and nothing in
95
+ // it is worth reading. Its presence is itself the finding (TF023), so
96
+ // the directory path alone is recorded and `collectCandidates` hands it
97
+ // on with no content.
98
+ if (e.name === TERRAFORM_WORK_DIR) out.push(join(dir, e.name));
99
+ continue;
100
+ }
84
101
  if (WALK_SKIP.has(e.name)) continue;
85
102
  const full = join(dir, e.name);
86
103
  if (e.isDirectory()) walkFiles(full, out);
@@ -105,6 +122,10 @@ function isDockerfileName(name: string): boolean {
105
122
  return name === "Dockerfile" || name.startsWith("Dockerfile.") || name.endsWith(".Dockerfile") || name.endsWith(".dockerfile");
106
123
  }
107
124
 
125
+ function isTerraformFileName(name: string): boolean {
126
+ return /\.tf$/i.test(name);
127
+ }
128
+
108
129
  /**
109
130
  * Filenames the secrets scan (#443) wants that no lexicon otherwise reads —
110
131
  * dotenv files and key/cert material. Included in `isCandidatePath` so both
@@ -294,11 +315,11 @@ export function isCandidatePath(path: string): boolean {
294
315
  * (path, filename, a few content markers) with no lexicon plugin involved.
295
316
  * Names the lexicon that would have claimed a file when that lexicon isn't
296
317
  * 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`).
318
+ * `detectTemplate`, which is exactly what's missing in that case. `terraform`
319
+ * is a lexicon like any other here: a `.tf` file hints at it by name, the
320
+ * same way `Chart.yaml` hints at helm.
300
321
  */
301
- export type LexiconHint = AuditLexicon | "terraform";
322
+ export type LexiconHint = AuditLexicon;
302
323
 
303
324
  export function hintLexiconForFile(path: string, content: string): LexiconHint | undefined {
304
325
  const name = basename(path);
@@ -306,7 +327,7 @@ export function hintLexiconForFile(path: string, content: string): LexiconHint |
306
327
  if (ci) return ci;
307
328
  if (isDockerfileName(name)) return "docker";
308
329
  if (name === "Chart.yaml") return "helm";
309
- if (/\.tf$/i.test(name)) return "terraform";
330
+ if (isTerraformFileName(name)) return "terraform";
310
331
  const head = content.slice(0, 64 * 1024);
311
332
  if (/cnrm\.cloud\.google\.com/.test(head)) return "gcp";
312
333
  if (/fountain\.dev\/v1/.test(head)) return "fountain";
@@ -371,25 +392,175 @@ function classifyHelm(files: RepoFile[], plugin: DetectPlugin | undefined): { in
371
392
  return { inputs, prefixes };
372
393
  }
373
394
 
395
+ /**
396
+ * A `source` attribute naming a local path, the only module source Terraform
397
+ * reads from the repository itself. A registry or git source ("hashicorp/aws",
398
+ * "git::https://...") never starts with `./` or `../`, and `source` inside
399
+ * `required_providers` is a registry address, so the prefix is what tells the
400
+ * two apart without an HCL parser here. `classifyTerraform` uses this to tell
401
+ * a root module from a directory that is somebody's child module; the lexicon
402
+ * plugin parses the calls properly once it has the root.
403
+ */
404
+ const LOCAL_MODULE_SOURCE = /(?:^|[\s{,])source\s*=\s*"(\.{1,2}\/[^"]*)"/g;
405
+
406
+ /**
407
+ * Resolve `rel` against the repo-relative directory `dir`. Returns undefined
408
+ * when the path climbs above the audited root, which is a directory this walk
409
+ * never read and so can never be one of its inputs.
410
+ */
411
+ function resolveRepoDir(dir: string, rel: string): string | undefined {
412
+ const parts = dir === "" ? [] : dir.split("/");
413
+ for (const segment of rel.split("/")) {
414
+ if (segment === "" || segment === ".") continue;
415
+ if (segment === "..") {
416
+ if (parts.length === 0) return undefined;
417
+ parts.pop();
418
+ continue;
419
+ }
420
+ parts.push(segment);
421
+ }
422
+ return parts.join("/");
423
+ }
424
+
425
+ /**
426
+ * The directories that are somebody's local child module: a `.tf` file in
427
+ * another directory of this walk calls them with a relative `source`.
428
+ *
429
+ * They are dropped as audit inputs of their own, because the root that calls
430
+ * them parses them as child scopes (the terraform plugin's `auditEntities`
431
+ * descends from `AuditInput.dir`, #2217). Auditing them twice would report
432
+ * every finding in them twice, once per scope, and auditing them as roots is
433
+ * what made `chant audit` disagree with `chant build` about which rules apply
434
+ * to a child module at all.
435
+ *
436
+ * A directory a cycle reaches but no root does is kept: `a` calling `b`
437
+ * calling `a` is a configuration Terraform refuses, and dropping both would
438
+ * leave the files audited by nothing.
439
+ */
440
+ function calledAsLocalModule(byDir: Map<string, Record<string, string>>): Set<string> {
441
+ const callees = new Map<string, Set<string>>();
442
+ const called = new Set<string>();
443
+ for (const [dir, bundle] of byDir) {
444
+ const targets = new Set<string>();
445
+ for (const source of Object.values(bundle)) {
446
+ for (const match of source.matchAll(LOCAL_MODULE_SOURCE)) {
447
+ const target = resolveRepoDir(dir, match[1]);
448
+ if (target === undefined || target === dir || !byDir.has(target)) continue;
449
+ targets.add(target);
450
+ called.add(target);
451
+ }
452
+ }
453
+ callees.set(dir, targets);
454
+ }
455
+
456
+ const queue = [...byDir.keys()].filter((dir) => !called.has(dir));
457
+ const reached = new Set<string>(queue);
458
+ while (queue.length > 0) {
459
+ for (const target of callees.get(queue.shift()!) ?? []) {
460
+ if (reached.has(target)) continue;
461
+ reached.add(target);
462
+ queue.push(target);
463
+ }
464
+ }
465
+ return new Set([...called].filter((dir) => reached.has(dir)));
466
+ }
467
+
468
+ /**
469
+ * Collect Terraform root modules as directory bundles from an in-memory file
470
+ * set, modeled on `classifyHelm`. Unlike a Helm chart (its whole subtree is
471
+ * one bundle), a Terraform root module references only its own sibling
472
+ * files, so grouping is by each `.tf` file's immediate directory, not
473
+ * recursive.
474
+ *
475
+ * A directory another one calls as a local module is not a root module and
476
+ * gets no input of its own (#2217): the root that calls it parses it as a
477
+ * child scope, which is what Terraform does and what `chant build` already
478
+ * did. That descent reads the child's directory from disk, so it only happens
479
+ * when `baseDir` says a local walk found these files; an in-memory file set
480
+ * with no directory behind it keeps every `.tf` directory as its own input,
481
+ * as before.
482
+ *
483
+ * Returns inputs plus the exact `.tf` paths claimed, so the loose-file pass in
484
+ * `classifyFiles` can skip them — a child module's files are claimed too, by
485
+ * the root that will parse them.
486
+ */
487
+ function classifyTerraform(
488
+ files: RepoFile[],
489
+ plugin: DetectPlugin | undefined,
490
+ baseDir?: string,
491
+ ): { inputs: AuditInput[]; claimed: Set<string> } {
492
+ const inputs: AuditInput[] = [];
493
+ const claimed = new Set<string>();
494
+ if (!plugin) return { inputs, claimed };
495
+ const byDir = new Map<string, Record<string, string>>();
496
+ for (const f of files) {
497
+ if (!isTerraformFileName(basename(f.path))) continue;
498
+ const slash = f.path.lastIndexOf("/");
499
+ const dir = slash === -1 ? "" : f.path.slice(0, slash);
500
+ const name = slash === -1 ? f.path : f.path.slice(slash + 1);
501
+ const bundle = byDir.get(dir) ?? {};
502
+ bundle[name] = f.content;
503
+ byDir.set(dir, bundle);
504
+ claimed.add(f.path);
505
+ }
506
+ const childModules = baseDir === undefined ? new Set<string>() : calledAsLocalModule(byDir);
507
+ for (const [dir, bundle] of byDir) {
508
+ if (childModules.has(dir)) continue;
509
+ // `content` is the whole root module, every `.tf` in filename order with a
510
+ // `# file:` line comment (legal HCL) marking each boundary, so a lexicon's
511
+ // `auditEntities(content)` sees the module Terraform itself would load,
512
+ // not one arbitrary file of it. `files` keeps the per-file split, and
513
+ // `dir` is where the local modules it calls are read from.
514
+ const content = Object.keys(bundle)
515
+ .sort()
516
+ .map((name) => `# file: ${name}\n${bundle[name]}`)
517
+ .join("\n");
518
+ inputs.push({
519
+ path: dir === "" ? "." : dir,
520
+ content,
521
+ lexicon: "terraform",
522
+ files: bundle,
523
+ ...(baseDir === undefined ? {} : { dir: resolve(baseDir, dir), baseDir }),
524
+ });
525
+ }
526
+ return { inputs, claimed };
527
+ }
528
+
529
+ /** Where a set of classified files came from, when they came from a local walk (#2217). */
530
+ export interface ClassifyOptions {
531
+ /**
532
+ * Absolute path of the directory that was walked. Travels onto the inputs
533
+ * whose `path` names a directory, so a lexicon's `auditEntities` can read
534
+ * the files that directory references (terraform's local module calls).
535
+ * Omit for an in-memory or fetched file set: there is no directory to read.
536
+ */
537
+ baseDir?: string;
538
+ }
539
+
374
540
  /**
375
541
  * Classify an in-memory set of repo files into per-lexicon audit inputs. Pure
376
542
  * (no fs, no network) so the local walk and the remote tree-fetch share exactly
377
543
  * 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.
544
+ * Dockerfiles by name, Helm charts and Terraform root modules as directory
545
+ * bundles, everything else by the precedence-ordered content detectors
546
+ * (delegating to each plugin's `detectTemplate`). Lexicons whose plugin isn't
547
+ * provided are skipped.
381
548
  */
382
- export function classifyFiles(files: RepoFile[], plugins: DetectPlugin[]): AuditInput[] {
549
+ export function classifyFiles(files: RepoFile[], plugins: DetectPlugin[], opts: ClassifyOptions = {}): AuditInput[] {
383
550
  const byName = new Map(plugins.map((p) => [p.name, p]));
551
+ const baseDir = opts.baseDir === undefined ? undefined : resolve(opts.baseDir);
384
552
 
385
553
  // Helm claims whole chart directories first; chart-internal files are excluded
386
554
  // from the loose-file pass so templates aren't double-audited.
387
555
  const helm = classifyHelm(files, byName.get("helm"));
388
556
  const underChart = (p: string): boolean => helm.prefixes.some((pre) => (pre === "" ? true : p.startsWith(pre)));
389
557
 
390
- const inputs: AuditInput[] = [...helm.inputs];
558
+ const terraform = classifyTerraform(files, byName.get("terraform"), baseDir);
559
+
560
+ const inputs: AuditInput[] = [...helm.inputs, ...terraform.inputs];
391
561
  for (const { path, content } of files) {
392
562
  if (underChart(path)) continue;
563
+ if (terraform.claimed.has(path)) continue;
393
564
  const name = basename(path);
394
565
 
395
566
  const ci = ciLexiconForPath(path);
@@ -420,20 +591,33 @@ export function classifyFiles(files: RepoFile[], plugins: DetectPlugin[]): Audit
420
591
  * lexicon whose plugin isn't provided, so a caller can scope discovery.
421
592
  */
422
593
  export function discoverByDetection(root: string, plugins: DetectPlugin[]): AuditInput[] {
423
- return classifyFiles(collectCandidates(root), plugins);
594
+ return classifyFiles(collectCandidates(root), plugins, { baseDir: root });
424
595
  }
425
596
 
426
597
  /** The walk half of `discoverByDetection`: candidate files read into memory, paths relative to the root. */
427
598
  export function collectCandidates(root: string): RepoFile[] {
428
599
  const all: string[] = [];
429
600
  walkFiles(root, all);
601
+ // TF023 reads paths, not content: a `.tfstate` can be tens of megabytes of
602
+ // machine-generated JSON, and the finding is that it is in the repository at
603
+ // all. The root `.gitignore` is what separates "committed" from "merely on
604
+ // disk" here — a Terraform working tree almost always has an ignored
605
+ // `.terraform/` in it, and reporting that would make TF023 fire on every
606
+ // local audit. A fetched repository has no such ambiguity (its file list is
607
+ // the tracked files) and is not filtered.
608
+ const gitignore = readSafe(join(root, ".gitignore")) ?? "";
430
609
  const files: RepoFile[] = [];
431
610
  for (const full of all) {
432
611
  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: "" });
612
+ if (isTerraformStatePath(path)) {
613
+ if (!gitignoreCoversTerraformState(gitignore, path)) files.push({ path, content: "" });
614
+ continue;
615
+ }
616
+ // `.tf` is read locally so `classifyTerraform` can bundle real content.
617
+ // It stays out of `isCandidatePath` so remote fetches still never pull HCL.
618
+ if (isTerraformFileName(basename(path))) {
619
+ const content = readSafe(full);
620
+ if (content !== undefined) files.push({ path, content });
437
621
  continue;
438
622
  }
439
623
  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
+ };
@@ -0,0 +1,153 @@
1
+ /**
2
+ * Prior art for chant's audit rules.
3
+ *
4
+ * Most of what `chant audit` checks has been checked before by a dedicated
5
+ * open-source tool: zizmor and poutine for GitHub Actions, hadolint for
6
+ * Dockerfiles, cfn-lint and cfn_nag for CloudFormation, kube-linter and
7
+ * kube-score for Kubernetes manifests, gitleaks for secrets, and so on. chant's
8
+ * rules were written independently against the emitted output, but the ideas
9
+ * are shared, and the people who wrote them down first deserve the credit.
10
+ *
11
+ * A rule declares its lineage as `Lineage[]` on its `RuleMeta` entry: which
12
+ * tool, which upstream rule, where it is documented, and how the two relate.
13
+ * `PRIOR_ART` is the registry those entries key into, so a tool is named,
14
+ * linked and licensed once. The audit rules reference renders both, the
15
+ * markdown and HTML reports credit the tool beside the finding, and the SARIF
16
+ * and JSON outputs carry the entries verbatim for consumers.
17
+ *
18
+ * Lineage is credit, not authority. `authority` (./catalog.ts) is a standard
19
+ * or vendor document that says a finding matters; lineage is a tool that
20
+ * checks the same condition. The two are independent: a rule may have either,
21
+ * both, or neither, and lineage never changes a rule's category or tier.
22
+ */
23
+
24
+ /** How a chant rule relates to the upstream check it credits. */
25
+ export type LineageRelation =
26
+ /** Same condition, same scope. */
27
+ | "equivalent"
28
+ /** The core condition is shared; one side is narrower or broader, or a different dialect of the same control. */
29
+ | "overlaps"
30
+ /** chant checks a strict superset of the upstream check. */
31
+ | "extends";
32
+
33
+ /** One credit: an upstream tool's rule that checks what this chant rule checks. */
34
+ export interface Lineage {
35
+ /** A key of {@link PRIOR_ART}. */
36
+ tool: PriorArtTool;
37
+ /** The upstream rule id or check name, in the tool's own vocabulary (e.g. `template-injection`, `DL3007`, `CKV_AWS_18`). Omitted when the tool has no per-rule ids. */
38
+ rule?: string;
39
+ /** Where the upstream rule is documented. */
40
+ url: string;
41
+ relation: LineageRelation;
42
+ }
43
+
44
+ /** A tool (or, for a few families, a vendor validator) that chant's rules credit. */
45
+ export interface PriorArtEntry {
46
+ name: string;
47
+ url: string;
48
+ /** SPDX identifier where confirmed, else "unverified" or "n/a" for documentation. */
49
+ license: string;
50
+ /** What kind of thing this is, so the rendered credit is honest about it. */
51
+ kind: "scanner" | "vendor-validator" | "specification";
52
+ }
53
+
54
+ /**
55
+ * The registry of prior art. Keys are the slugs `Lineage.tool` uses. Populated
56
+ * from the per-family research recorded in the PR that introduced lineage; the
57
+ * prior-art sweep (scripts/prior-art-sweep.ts) reads the same table.
58
+ */
59
+ export const PRIOR_ART = {
60
+ "checkov": { name: "Checkov", url: "https://www.checkov.io/", license: "Apache-2.0", kind: "scanner" },
61
+ "kics": { name: "KICS", url: "https://docs.kics.io/", license: "Apache-2.0", kind: "scanner" },
62
+ "zizmor": { name: "zizmor", url: "https://docs.zizmor.sh/", license: "MIT", kind: "scanner" },
63
+ "cfn-nag": { name: "cfn_nag", url: "https://github.com/stelligent/cfn_nag", license: "MIT", kind: "scanner" },
64
+ "gitlab-docs": { name: "GitLab CI/CD YAML reference", url: "https://docs.gitlab.com/ci/yaml/", license: "CC-BY-SA-4.0", kind: "specification" },
65
+ "guard-rules-registry": { name: "AWS Guard Rules Registry", url: "https://github.com/aws-cloudformation/aws-guard-rules-registry", license: "Apache-2.0", kind: "scanner" },
66
+ "polaris": { name: "Fairwinds Polaris", url: "https://polaris.docs.fairwinds.com/", license: "Apache-2.0", kind: "scanner" },
67
+ "kube-linter": { name: "KubeLinter", url: "https://github.com/stackrox/kube-linter", license: "Apache-2.0", kind: "scanner" },
68
+ "kube-score": { name: "kube-score", url: "https://github.com/zegl/kube-score", license: "MIT", kind: "scanner" },
69
+ "psrule-azure": { name: "PSRule for Azure", url: "https://azure.github.io/PSRule.Rules.Azure/", license: "MIT", kind: "scanner" },
70
+ "gcp-policy-library": { name: "Google Cloud Policy Library", url: "https://github.com/GoogleCloudPlatform/policy-library", license: "Apache-2.0", kind: "scanner" },
71
+ "poutine": { name: "poutine", url: "https://boostsecurityio.github.io/poutine/", license: "Apache-2.0", kind: "scanner" },
72
+ "scorecard": { name: "OpenSSF Scorecard", url: "https://github.com/ossf/scorecard", license: "Apache-2.0", kind: "scanner" },
73
+ "cfn-lint": { name: "cfn-lint", url: "https://github.com/aws-cloudformation/cfn-lint", license: "MIT-0", kind: "scanner" },
74
+ "controlplane-docs": { name: "Control Plane reference documentation", url: "https://docs.controlplane.com", license: "n/a", kind: "specification" },
75
+ "gitleaks": { name: "gitleaks", url: "https://github.com/gitleaks/gitleaks", license: "MIT", kind: "scanner" },
76
+ "octoscan": { name: "octoscan", url: "https://github.com/synacktiv/octoscan", license: "GPL-3.0", kind: "scanner" },
77
+ "detect-secrets": { name: "detect-secrets", url: "https://github.com/Yelp/detect-secrets", license: "Apache-2.0", kind: "scanner" },
78
+ "actionlint": { name: "actionlint", url: "https://github.com/rhysd/actionlint", license: "MIT", kind: "scanner" },
79
+ "trufflehog": { name: "TruffleHog", url: "https://github.com/trufflesecurity/trufflehog", license: "AGPL-3.0", kind: "scanner" },
80
+ "gixy-ng": { name: "gixy-ng (maintained Gixy fork)", url: "https://github.com/dvershinin/gixy", license: "MPL-2.0", kind: "scanner" },
81
+ "kubesec": { name: "kubesec", url: "https://kubesec.io/", license: "Apache-2.0", kind: "scanner" },
82
+ "datree": { name: "Datree", url: "https://hub.datree.io/built-in-rules", license: "Apache-2.0", kind: "scanner" },
83
+ "helm-lint": { name: "helm lint", url: "https://helm.sh/docs/helm/helm_lint/", license: "Apache-2.0", kind: "vendor-validator" },
84
+ "cedar-validator": { name: "Cedar policy validator", url: "https://docs.cedarpolicy.com/policies/validation.html", license: "Apache-2.0", kind: "vendor-validator" },
85
+ "flux-docs": { name: "Flux Kustomization API reference", url: "https://fluxcd.io/flux/components/kustomize/kustomizations/", license: "n/a", kind: "specification" },
86
+ "hadolint": { name: "hadolint", url: "https://github.com/hadolint/hadolint", license: "GPL-3.0", kind: "scanner" },
87
+ "agent-audit": { name: "agent-audit", url: "https://github.com/piiiico/agent-audit", license: "MIT", kind: "scanner" },
88
+ "arm-ttk": { name: "ARM Template Toolkit (arm-ttk)", url: "https://github.com/Azure/arm-ttk", license: "MIT", kind: "scanner" },
89
+ "bicep-linter": { name: "Bicep linter", url: "https://learn.microsoft.com/en-us/azure/azure-resource-manager/bicep/linter", license: "MIT", kind: "scanner" },
90
+ "cedar-cli": { name: "Cedar CLI", url: "https://github.com/cedar-policy/cedar/tree/main/cedar-policy-cli", license: "Apache-2.0", kind: "vendor-validator" },
91
+ "dockle": { name: "Dockle", url: "https://github.com/goodwithtech/dockle", license: "Apache-2.0", kind: "scanner" },
92
+ "mcp-audit": { name: "mcp-audit (APIsec)", url: "https://github.com/apisec-inc/mcp-audit", license: "MIT", kind: "scanner" },
93
+ "render-docs": { name: "Render Blueprint YAML reference", url: "https://render.com/docs/blueprint-spec", license: "n/a", kind: "specification" },
94
+ "agent-scan": { name: "Snyk Agent Scan (formerly mcp-scan)", url: "https://github.com/invariantlabs-ai/mcp-scan", license: "Apache-2.0", kind: "scanner" },
95
+ "cedar-docs": { name: "Cedar policy language reference", url: "https://docs.cedarpolicy.com/policies/json-format.html", license: "n/a", kind: "specification" },
96
+ "cedar-policy-crate": { name: "cedar-policy crate (PolicySet loader)", url: "https://docs.rs/cedar-policy/latest/cedar_policy/", license: "Apache-2.0", kind: "vendor-validator" },
97
+ "chart-testing": { name: "chart-testing (ct)", url: "https://github.com/helm/chart-testing", license: "Apache-2.0", kind: "vendor-validator" },
98
+ "fly-docs": { name: "Fly Machines API reference", url: "https://fly.io/docs/machines/api/machines-resource/", license: "n/a", kind: "specification" },
99
+ "gixy": { name: "Gixy", url: "https://github.com/yandex/gixy", license: "MPL-2.0", kind: "scanner" },
100
+ "kube-bench": { name: "kube-bench", url: "https://github.com/aquasecurity/kube-bench", license: "Apache-2.0", kind: "scanner" },
101
+ "pluto": { name: "Pluto", url: "https://github.com/FairwindsOps/pluto", license: "Apache-2.0", kind: "scanner" },
102
+ "choudoufu": { name: "choudoufu", url: "https://github.com/INTENTIUS/choudoufu", license: "MPL-2.0", kind: "scanner" },
103
+ // Terraform lint family (#2107). Registry entries recorded in
104
+ // lexicons/terraform/src/lint/audit-lineage.ts's header comment; added here
105
+ // the first time a shipped rule cites them (#2109).
106
+ "tflint-ruleset-terraform": { name: "tflint-ruleset-terraform", url: "https://github.com/terraform-linters/tflint-ruleset-terraform", license: "MPL-2.0", kind: "scanner" },
107
+ "terraform-sentinel-policies": { name: "HashiCorp reference Sentinel policies", url: "https://github.com/hashicorp/terraform-sentinel-policies", license: "MPL-2.0", kind: "specification" },
108
+ "hashicorp-style-guide": { name: "HashiCorp Terraform style guide", url: "https://developer.hashicorp.com/terraform/language/style", license: "n/a", kind: "specification" },
109
+ "aws-terraform-prescriptive-guidance": { name: "AWS prescriptive guidance for Terraform", url: "https://docs.aws.amazon.com/prescriptive-guidance/latest/terraform-aws-provider-best-practices/", license: "n/a", kind: "specification" },
110
+ // A multi-page series; a credit cites the specific page and fragment, not
111
+ // this landing URL. First cited by TF014 and TF015 (#2112).
112
+ "gcp-terraform-best-practices": { name: "Google Cloud Terraform best-practices series", url: "https://docs.cloud.google.com/docs/terraform/best-practices/general-style-structure", license: "n/a", kind: "specification" },
113
+ "tflint-ruleset-redeploy": { name: "tflint-ruleset-redeploy", url: "https://github.com/RedeployAB/tflint-ruleset-redeploy", license: "0BSD", kind: "scanner" },
114
+ "tflint-ruleset-avm": { name: "tflint-ruleset-avm (Azure Verified Modules)", url: "https://github.com/Azure/tflint-ruleset-avm", license: "MIT", kind: "scanner" },
115
+ // tfsec is frozen rather than archived, and its docs site 404s above v0.61.x,
116
+ // so its credits cite pinned repository paths (a tag or `master`), never the
117
+ // docs site. The three `general/secrets` checks chant credits live only here:
118
+ // trivy dropped them when it absorbed tfsec.
119
+ "tfsec": { name: "tfsec", url: "https://github.com/aquasecurity/tfsec", license: "MIT", kind: "scanner" },
120
+ // Semgrep's engine is LGPL-2.1 but its rules are not: they ship under the
121
+ // proprietary Semgrep Rules License v1.0, which forbids redistribution. So
122
+ // this entry carries no SPDX id, and no semgrep rule text is ever copied into
123
+ // this repository — a credit by name and link is the whole of it (#2107).
124
+ "semgrep": { name: "Semgrep Registry rules", url: "https://github.com/semgrep/semgrep-rules", license: "Semgrep Rules License v1.0 (proprietary, not an SPDX id)", kind: "scanner" },
125
+ } as const satisfies Record<string, PriorArtEntry>;
126
+
127
+ export type PriorArtTool = keyof typeof PRIOR_ART;
128
+
129
+ /**
130
+ * Attach lineage to a catalog in place. A lexicon keeps its lineage in a
131
+ * sibling `audit-lineage.ts` (one `Record<ruleId, Lineage[]>`) and calls this
132
+ * once at the bottom of its `audit-catalog.ts`, so the prose metadata and the
133
+ * credits stay next to each other but the credits can be regenerated by the
134
+ * prior-art sweep without touching the rule text. Throws at module load if the
135
+ * lineage names a rule the catalog does not have: a stale id is a bug, not a
136
+ * silent no-op.
137
+ */
138
+ export function applyLineage<T extends { id: string; lineage?: Lineage[] }>(
139
+ catalog: Record<string, T>,
140
+ lineage: Record<string, Lineage[]>,
141
+ ): void {
142
+ for (const [id, entries] of Object.entries(lineage)) {
143
+ const rule = catalog[id];
144
+ if (!rule) throw new Error(`audit lineage names ${id}, which this catalog does not define`);
145
+ if (entries.length > 0) rule.lineage = entries;
146
+ }
147
+ }
148
+
149
+ /** Render one lineage entry as "tool rule (relation)" for prose contexts. */
150
+ export function lineageLabel(l: Lineage): string {
151
+ const tool = (PRIOR_ART as Record<string, PriorArtEntry>)[l.tool]?.name ?? l.tool;
152
+ return l.rule ? `${tool} ${l.rule}` : tool;
153
+ }