@intentius/chant 0.13.0 → 0.15.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 (453) hide show
  1. package/dist/audit/catalog.d.ts +45 -5
  2. package/dist/audit/catalog.d.ts.map +1 -1
  3. package/dist/audit/fetch.d.ts.map +1 -1
  4. package/dist/audit/proof.d.ts +3 -0
  5. package/dist/audit/proof.d.ts.map +1 -1
  6. package/dist/audit/report-model.d.ts +4 -1
  7. package/dist/audit/report-model.d.ts.map +1 -1
  8. package/dist/audit/report.d.ts +3 -0
  9. package/dist/audit/report.d.ts.map +1 -1
  10. package/dist/audit/rules-doc.d.ts +8 -2
  11. package/dist/audit/rules-doc.d.ts.map +1 -1
  12. package/dist/cli/commands/audit.d.ts.map +1 -1
  13. package/dist/cli/commands/doctor.d.ts.map +1 -1
  14. package/dist/cli/commands/lexicon-rolling-upgrade.d.ts +37 -0
  15. package/dist/cli/commands/lexicon-rolling-upgrade.d.ts.map +1 -0
  16. package/dist/cli/commands/lexicon-surface-diff.d.ts +48 -0
  17. package/dist/cli/commands/lexicon-surface-diff.d.ts.map +1 -0
  18. package/dist/cli/commands/lint.d.ts.map +1 -1
  19. package/dist/cli/commands/pinned-upgrade.d.ts +41 -0
  20. package/dist/cli/commands/pinned-upgrade.d.ts.map +1 -0
  21. package/dist/cli/handlers/build.d.ts.map +1 -1
  22. package/dist/cli/handlers/components.d.ts +39 -0
  23. package/dist/cli/handlers/components.d.ts.map +1 -0
  24. package/dist/cli/handlers/dev.d.ts +3 -0
  25. package/dist/cli/handlers/dev.d.ts.map +1 -1
  26. package/dist/cli/handlers/graph.d.ts +4 -2
  27. package/dist/cli/handlers/graph.d.ts.map +1 -1
  28. package/dist/cli/handlers/misc.d.ts.map +1 -1
  29. package/dist/cli/handlers/run.d.ts +49 -0
  30. package/dist/cli/handlers/run.d.ts.map +1 -1
  31. package/dist/cli/main.d.ts.map +1 -1
  32. package/dist/cli/registry.d.ts +35 -0
  33. package/dist/cli/registry.d.ts.map +1 -1
  34. package/dist/codegen/fetch.d.ts +22 -0
  35. package/dist/codegen/fetch.d.ts.map +1 -1
  36. package/dist/codegen/lexicon-regen.d.ts +110 -0
  37. package/dist/codegen/lexicon-regen.d.ts.map +1 -0
  38. package/dist/codegen/pinned-upgrade.d.ts +153 -0
  39. package/dist/codegen/pinned-upgrade.d.ts.map +1 -0
  40. package/dist/codegen/rolling-upgrade.d.ts +138 -0
  41. package/dist/codegen/rolling-upgrade.d.ts.map +1 -0
  42. package/dist/codegen/surface-snapshot.d.ts +173 -0
  43. package/dist/codegen/surface-snapshot.d.ts.map +1 -0
  44. package/dist/codegen/version-bump.d.ts +21 -0
  45. package/dist/codegen/version-bump.d.ts.map +1 -0
  46. package/dist/components/auto-release.d.ts +93 -0
  47. package/dist/components/auto-release.d.ts.map +1 -0
  48. package/dist/components/builders.d.ts +37 -0
  49. package/dist/components/builders.d.ts.map +1 -0
  50. package/dist/components/capability-plugin-loader.d.ts +111 -0
  51. package/dist/components/capability-plugin-loader.d.ts.map +1 -0
  52. package/dist/components/capability-plugin-schema.d.ts +24 -0
  53. package/dist/components/capability-plugin-schema.d.ts.map +1 -0
  54. package/dist/components/capability-plugin.d.ts +85 -0
  55. package/dist/components/capability-plugin.d.ts.map +1 -0
  56. package/dist/components/capability.d.ts +89 -0
  57. package/dist/components/capability.d.ts.map +1 -0
  58. package/dist/components/cli-support.d.ts +204 -0
  59. package/dist/components/cli-support.d.ts.map +1 -0
  60. package/dist/components/component.d.ts +179 -0
  61. package/dist/components/component.d.ts.map +1 -0
  62. package/dist/components/config-defaults.d.ts +41 -0
  63. package/dist/components/config-defaults.d.ts.map +1 -0
  64. package/dist/components/discover.d.ts +60 -0
  65. package/dist/components/discover.d.ts.map +1 -0
  66. package/dist/components/driver-output.d.ts +20 -0
  67. package/dist/components/driver-output.d.ts.map +1 -0
  68. package/dist/components/driver.d.ts +209 -0
  69. package/dist/components/driver.d.ts.map +1 -0
  70. package/dist/components/index.d.ts +22 -0
  71. package/dist/components/index.d.ts.map +1 -0
  72. package/dist/components/manifest-persistence.d.ts +79 -0
  73. package/dist/components/manifest-persistence.d.ts.map +1 -0
  74. package/dist/components/pilots/alb-ecs.pilot.d.ts +27 -0
  75. package/dist/components/pilots/alb-ecs.pilot.d.ts.map +1 -0
  76. package/dist/components/pilots/dynamodb.pilot.d.ts +22 -0
  77. package/dist/components/pilots/dynamodb.pilot.d.ts.map +1 -0
  78. package/dist/components/pilots/jar-emr.pilot.d.ts +37 -0
  79. package/dist/components/pilots/jar-emr.pilot.d.ts.map +1 -0
  80. package/dist/components/pilots/lambda.pilot.d.ts +35 -0
  81. package/dist/components/pilots/lambda.pilot.d.ts.map +1 -0
  82. package/dist/components/pilots/neo4j-fanout.pilot.d.ts +17 -0
  83. package/dist/components/pilots/neo4j-fanout.pilot.d.ts.map +1 -0
  84. package/dist/components/pilots/project.d.ts +13 -0
  85. package/dist/components/pilots/project.d.ts.map +1 -0
  86. package/dist/components/presets/ecs-fargate.d.ts +59 -0
  87. package/dist/components/presets/ecs-fargate.d.ts.map +1 -0
  88. package/dist/components/presets/index.d.ts +29 -0
  89. package/dist/components/presets/index.d.ts.map +1 -0
  90. package/dist/components/presets/lambda.d.ts +35 -0
  91. package/dist/components/presets/lambda.d.ts.map +1 -0
  92. package/dist/components/presets/single-host-compose.d.ts +43 -0
  93. package/dist/components/presets/single-host-compose.d.ts.map +1 -0
  94. package/dist/components/registry.d.ts +29 -0
  95. package/dist/components/registry.d.ts.map +1 -0
  96. package/dist/components/starter-plugin.d.ts +51 -0
  97. package/dist/components/starter-plugin.d.ts.map +1 -0
  98. package/dist/components/temporal-codegen-loader.d.ts +27 -0
  99. package/dist/components/temporal-codegen-loader.d.ts.map +1 -0
  100. package/dist/components/verbs/__tests__/mock-cloud-executor.d.ts +35 -0
  101. package/dist/components/verbs/__tests__/mock-cloud-executor.d.ts.map +1 -0
  102. package/dist/components/verbs/__tests__/mock-process-runner.d.ts +45 -0
  103. package/dist/components/verbs/__tests__/mock-process-runner.d.ts.map +1 -0
  104. package/dist/components/verbs/__tests__/mock-sbom-generator.d.ts +33 -0
  105. package/dist/components/verbs/__tests__/mock-sbom-generator.d.ts.map +1 -0
  106. package/dist/components/verbs/bom-writer.d.ts +155 -0
  107. package/dist/components/verbs/bom-writer.d.ts.map +1 -0
  108. package/dist/components/verbs/build-archive.d.ts +205 -0
  109. package/dist/components/verbs/build-archive.d.ts.map +1 -0
  110. package/dist/components/verbs/build.d.ts +150 -0
  111. package/dist/components/verbs/build.d.ts.map +1 -0
  112. package/dist/components/verbs/cloud-executor.d.ts +88 -0
  113. package/dist/components/verbs/cloud-executor.d.ts.map +1 -0
  114. package/dist/components/verbs/component-bom.d.ts +103 -0
  115. package/dist/components/verbs/component-bom.d.ts.map +1 -0
  116. package/dist/components/verbs/index.d.ts +49 -0
  117. package/dist/components/verbs/index.d.ts.map +1 -0
  118. package/dist/components/verbs/license-policy.d.ts +56 -0
  119. package/dist/components/verbs/license-policy.d.ts.map +1 -0
  120. package/dist/components/verbs/lockfile-sbom-generator.d.ts +81 -0
  121. package/dist/components/verbs/lockfile-sbom-generator.d.ts.map +1 -0
  122. package/dist/components/verbs/process-runner.d.ts +64 -0
  123. package/dist/components/verbs/process-runner.d.ts.map +1 -0
  124. package/dist/components/verbs/reproducibility.d.ts +80 -0
  125. package/dist/components/verbs/reproducibility.d.ts.map +1 -0
  126. package/dist/components/verbs/sbom-generator.d.ts +157 -0
  127. package/dist/components/verbs/sbom-generator.d.ts.map +1 -0
  128. package/dist/components/verbs/sbom.d.ts +79 -0
  129. package/dist/components/verbs/sbom.d.ts.map +1 -0
  130. package/dist/components/verbs/shell.d.ts +35 -0
  131. package/dist/components/verbs/shell.d.ts.map +1 -0
  132. package/dist/components/verbs/sign.d.ts +222 -0
  133. package/dist/components/verbs/sign.d.ts.map +1 -0
  134. package/dist/components/verbs/stub.d.ts +18 -0
  135. package/dist/components/verbs/stub.d.ts.map +1 -0
  136. package/dist/components/verbs/tool-sbom-generator.d.ts +51 -0
  137. package/dist/components/verbs/tool-sbom-generator.d.ts.map +1 -0
  138. package/dist/components/verbs/verify.d.ts +106 -0
  139. package/dist/components/verbs/verify.d.ts.map +1 -0
  140. package/dist/components/verbs/vex.d.ts +65 -0
  141. package/dist/components/verbs/vex.d.ts.map +1 -0
  142. package/dist/components/verbs/vuln-gate.d.ts +91 -0
  143. package/dist/components/verbs/vuln-gate.d.ts.map +1 -0
  144. package/dist/components/verbs/vuln-scan.d.ts +116 -0
  145. package/dist/components/verbs/vuln-scan.d.ts.map +1 -0
  146. package/dist/components/verbs/wait-verify.d.ts +85 -0
  147. package/dist/components/verbs/wait-verify.d.ts.map +1 -0
  148. package/dist/config.d.ts +205 -0
  149. package/dist/config.d.ts.map +1 -1
  150. package/dist/index.d.ts +4 -0
  151. package/dist/index.d.ts.map +1 -1
  152. package/dist/lexicon.d.ts +78 -0
  153. package/dist/lexicon.d.ts.map +1 -1
  154. package/dist/lifecycle/build-ledger-store.d.ts +121 -0
  155. package/dist/lifecycle/build-ledger-store.d.ts.map +1 -0
  156. package/dist/lifecycle/build-ledger.d.ts +225 -0
  157. package/dist/lifecycle/build-ledger.d.ts.map +1 -0
  158. package/dist/lifecycle/git.d.ts +84 -0
  159. package/dist/lifecycle/git.d.ts.map +1 -1
  160. package/dist/lifecycle/index.d.ts +5 -0
  161. package/dist/lifecycle/index.d.ts.map +1 -1
  162. package/dist/lifecycle/oras-referrer-lookup.d.ts +41 -0
  163. package/dist/lifecycle/oras-referrer-lookup.d.ts.map +1 -0
  164. package/dist/lifecycle/release-ledger.d.ts +122 -0
  165. package/dist/lifecycle/release-ledger.d.ts.map +1 -0
  166. package/dist/lifecycle/status.d.ts +150 -0
  167. package/dist/lifecycle/status.d.ts.map +1 -0
  168. package/dist/lint/component-checks.d.ts +95 -0
  169. package/dist/lint/component-checks.d.ts.map +1 -0
  170. package/dist/lint/engine.d.ts +23 -0
  171. package/dist/lint/engine.d.ts.map +1 -1
  172. package/dist/lint/presets/strict.json +8 -1
  173. package/dist/lint/rules/__fixtures__/comp/comp000-discovery-error/broken-a.component.d.ts +4 -0
  174. package/dist/lint/rules/__fixtures__/comp/comp000-discovery-error/broken-a.component.d.ts.map +1 -0
  175. package/dist/lint/rules/__fixtures__/comp/comp000-discovery-error/broken-b.component.d.ts +4 -0
  176. package/dist/lint/rules/__fixtures__/comp/comp000-discovery-error/broken-b.component.d.ts.map +1 -0
  177. package/dist/lint/rules/__fixtures__/comp/comp001/fail/orphan-lib.component.d.ts +4 -0
  178. package/dist/lint/rules/__fixtures__/comp/comp001/fail/orphan-lib.component.d.ts.map +1 -0
  179. package/dist/lint/rules/__fixtures__/comp/comp001/pass/emr-job.component.d.ts +4 -0
  180. package/dist/lint/rules/__fixtures__/comp/comp001/pass/emr-job.component.d.ts.map +1 -0
  181. package/dist/lint/rules/__fixtures__/comp/comp001/pass/jar-lib.component.d.ts +4 -0
  182. package/dist/lint/rules/__fixtures__/comp/comp001/pass/jar-lib.component.d.ts.map +1 -0
  183. package/dist/lint/rules/__fixtures__/comp/comp002/fail/search-service.component.d.ts +10 -0
  184. package/dist/lint/rules/__fixtures__/comp/comp002/fail/search-service.component.d.ts.map +1 -0
  185. package/dist/lint/rules/__fixtures__/comp/comp002/pass/jar-lib.component.d.ts +3 -0
  186. package/dist/lint/rules/__fixtures__/comp/comp002/pass/jar-lib.component.d.ts.map +1 -0
  187. package/dist/lint/rules/__fixtures__/comp/comp002/pass/search-service.component.d.ts +4 -0
  188. package/dist/lint/rules/__fixtures__/comp/comp002/pass/search-service.component.d.ts.map +1 -0
  189. package/dist/lint/rules/__fixtures__/comp/comp003/fail/migrator.component.d.ts +4 -0
  190. package/dist/lint/rules/__fixtures__/comp/comp003/fail/migrator.component.d.ts.map +1 -0
  191. package/dist/lint/rules/__fixtures__/comp/comp003/pass/migrator.component.d.ts +4 -0
  192. package/dist/lint/rules/__fixtures__/comp/comp003/pass/migrator.component.d.ts.map +1 -0
  193. package/dist/lint/rules/__fixtures__/comp/comp003/pass/orders-table.component.d.ts +4 -0
  194. package/dist/lint/rules/__fixtures__/comp/comp003/pass/orders-table.component.d.ts.map +1 -0
  195. package/dist/lint/rules/__fixtures__/comp/comp004/fail/neo4j-cluster.component.d.ts +4 -0
  196. package/dist/lint/rules/__fixtures__/comp/comp004/fail/neo4j-cluster.component.d.ts.map +1 -0
  197. package/dist/lint/rules/__fixtures__/comp/comp004/pass/neo4j-cluster.component.d.ts +4 -0
  198. package/dist/lint/rules/__fixtures__/comp/comp004/pass/neo4j-cluster.component.d.ts.map +1 -0
  199. package/dist/lint/rules/__fixtures__/comp/comp004/pass/orders-table.component.d.ts +4 -0
  200. package/dist/lint/rules/__fixtures__/comp/comp004/pass/orders-table.component.d.ts.map +1 -0
  201. package/dist/lint/rules/__fixtures__/comp/comp005/fail/search-service.component.d.ts +4 -0
  202. package/dist/lint/rules/__fixtures__/comp/comp005/fail/search-service.component.d.ts.map +1 -0
  203. package/dist/lint/rules/__fixtures__/comp/comp005/pass/search-service.component.d.ts +4 -0
  204. package/dist/lint/rules/__fixtures__/comp/comp005/pass/search-service.component.d.ts.map +1 -0
  205. package/dist/lint/rules/__fixtures__/comp/comp006/fail/legacy-tool.component.d.ts +4 -0
  206. package/dist/lint/rules/__fixtures__/comp/comp006/fail/legacy-tool.component.d.ts.map +1 -0
  207. package/dist/lint/rules/__fixtures__/comp/comp006/pass/legacy-tool.component.d.ts +4 -0
  208. package/dist/lint/rules/__fixtures__/comp/comp006/pass/legacy-tool.component.d.ts.map +1 -0
  209. package/dist/lint/rules/__fixtures__/comp/comp007/fail/inventory-table.component.d.ts +4 -0
  210. package/dist/lint/rules/__fixtures__/comp/comp007/fail/inventory-table.component.d.ts.map +1 -0
  211. package/dist/lint/rules/__fixtures__/comp/comp007/fail/orders-table.component.d.ts +4 -0
  212. package/dist/lint/rules/__fixtures__/comp/comp007/fail/orders-table.component.d.ts.map +1 -0
  213. package/dist/lint/rules/__fixtures__/comp/comp007/pass/orders-table.component.d.ts +3 -0
  214. package/dist/lint/rules/__fixtures__/comp/comp007/pass/orders-table.component.d.ts.map +1 -0
  215. package/dist/lint/rules/__fixtures__/comp/comp007/pass/search-service.component.d.ts +4 -0
  216. package/dist/lint/rules/__fixtures__/comp/comp007/pass/search-service.component.d.ts.map +1 -0
  217. package/dist/lint/rules/comp/comp001-publishes-never-applies.d.ts +20 -0
  218. package/dist/lint/rules/comp/comp001-publishes-never-applies.d.ts.map +1 -0
  219. package/dist/lint/rules/comp/comp002-dangling-wiring-ref.d.ts +28 -0
  220. package/dist/lint/rules/comp/comp002-dangling-wiring-ref.d.ts.map +1 -0
  221. package/dist/lint/rules/comp/comp003-mutating-no-rollback.d.ts +39 -0
  222. package/dist/lint/rules/comp/comp003-mutating-no-rollback.d.ts.map +1 -0
  223. package/dist/lint/rules/comp/comp004-gate-needs-temporal.d.ts +37 -0
  224. package/dist/lint/rules/comp/comp004-gate-needs-temporal.d.ts.map +1 -0
  225. package/dist/lint/rules/comp/comp005-capability-kind-is-noun.d.ts +43 -0
  226. package/dist/lint/rules/comp/comp005-capability-kind-is-noun.d.ts.map +1 -0
  227. package/dist/lint/rules/comp/comp006-shell-needs-reason.d.ts +22 -0
  228. package/dist/lint/rules/comp/comp006-shell-needs-reason.d.ts.map +1 -0
  229. package/dist/lint/rules/comp/comp007-composition-sprawl.d.ts +33 -0
  230. package/dist/lint/rules/comp/comp007-composition-sprawl.d.ts.map +1 -0
  231. package/dist/lint/rules/comp/index.d.ts +18 -0
  232. package/dist/lint/rules/comp/index.d.ts.map +1 -0
  233. package/dist/lint/rules/comp/support.d.ts +71 -0
  234. package/dist/lint/rules/comp/support.d.ts.map +1 -0
  235. package/dist/lsp/lexicon-providers.d.ts.map +1 -1
  236. package/dist/op/activity-registry.d.ts +11 -6
  237. package/dist/op/activity-registry.d.ts.map +1 -1
  238. package/dist/op/activity-runtime.d.ts +24 -0
  239. package/dist/op/activity-runtime.d.ts.map +1 -0
  240. package/dist/op/builders.d.ts +56 -0
  241. package/dist/op/builders.d.ts.map +1 -1
  242. package/dist/op/index.d.ts +2 -1
  243. package/dist/op/index.d.ts.map +1 -1
  244. package/dist/ownership.d.ts +20 -14
  245. package/dist/ownership.d.ts.map +1 -1
  246. package/package.json +14 -1
  247. package/src/audit/catalog.test.ts +74 -22
  248. package/src/audit/catalog.ts +74 -471
  249. package/src/audit/fetch.test.ts +112 -8
  250. package/src/audit/fetch.ts +53 -8
  251. package/src/audit/proof.ts +4 -2
  252. package/src/audit/report-html.test.ts +14 -7
  253. package/src/audit/report-model.ts +20 -9
  254. package/src/audit/report.test.ts +36 -13
  255. package/src/audit/report.ts +2 -0
  256. package/src/audit/rules-doc.test.ts +8 -6
  257. package/src/audit/rules-doc.ts +13 -5
  258. package/src/cli/commands/audit.ts +23 -18
  259. package/src/cli/commands/component-lint.test.ts +158 -0
  260. package/src/cli/commands/doctor.test.ts +116 -2
  261. package/src/cli/commands/doctor.ts +46 -0
  262. package/src/cli/commands/lexicon-rolling-upgrade.ts +198 -0
  263. package/src/cli/commands/lexicon-surface-diff.test.ts +104 -0
  264. package/src/cli/commands/lexicon-surface-diff.ts +185 -0
  265. package/src/cli/commands/lint.ts +142 -1
  266. package/src/cli/commands/pinned-upgrade.ts +204 -0
  267. package/src/cli/handlers/build.ts +58 -1
  268. package/src/cli/handlers/components.test.ts +504 -0
  269. package/src/cli/handlers/components.ts +404 -0
  270. package/src/cli/handlers/dev.ts +60 -1
  271. package/src/cli/handlers/graph.ts +47 -2
  272. package/src/cli/handlers/misc.ts +45 -1
  273. package/src/cli/handlers/run.test.ts +967 -3
  274. package/src/cli/handlers/run.ts +618 -11
  275. package/src/cli/main.test.ts +66 -0
  276. package/src/cli/main.ts +105 -2
  277. package/src/cli/registry.ts +35 -0
  278. package/src/codegen/fetch.test.ts +84 -3
  279. package/src/codegen/fetch.ts +76 -2
  280. package/src/codegen/lexicon-regen.test.ts +298 -0
  281. package/src/codegen/lexicon-regen.ts +545 -0
  282. package/src/codegen/pinned-upgrade.test.ts +399 -0
  283. package/src/codegen/pinned-upgrade.ts +423 -0
  284. package/src/codegen/rolling-upgrade.test.ts +333 -0
  285. package/src/codegen/rolling-upgrade.ts +320 -0
  286. package/src/codegen/surface-snapshot.test.ts +461 -0
  287. package/src/codegen/surface-snapshot.ts +546 -0
  288. package/src/codegen/version-bump.test.ts +47 -0
  289. package/src/codegen/version-bump.ts +39 -0
  290. package/src/components/README.md +130 -0
  291. package/src/components/SPRAWL-VALIDATION.md +181 -0
  292. package/src/components/__fixtures__/alb-ecs-service.json +51 -0
  293. package/src/components/__fixtures__/dynamodb-infra.json +32 -0
  294. package/src/components/__fixtures__/emr-job-consumer.json +26 -0
  295. package/src/components/__fixtures__/jar-lib-producer.json +16 -0
  296. package/src/components/__fixtures__/lambda-image-processor.json +28 -0
  297. package/src/components/__fixtures__/neo4j-fanout.json +34 -0
  298. package/src/components/__fixtures__/single-host-compose.json +29 -0
  299. package/src/components/auto-release.test.ts +217 -0
  300. package/src/components/auto-release.ts +216 -0
  301. package/src/components/builders.test.ts +45 -0
  302. package/src/components/builders.ts +71 -0
  303. package/src/components/capability-plugin-loader.ts +218 -0
  304. package/src/components/capability-plugin-schema.ts +56 -0
  305. package/src/components/capability-plugin.test.ts +235 -0
  306. package/src/components/capability-plugin.ts +108 -0
  307. package/src/components/capability.ts +117 -0
  308. package/src/components/cli-support.test.ts +579 -0
  309. package/src/components/cli-support.ts +481 -0
  310. package/src/components/component-schema.test.ts +265 -0
  311. package/src/components/component.schema.json +235 -0
  312. package/src/components/component.test.ts +251 -0
  313. package/src/components/component.ts +255 -0
  314. package/src/components/config-defaults.test.ts +263 -0
  315. package/src/components/config-defaults.ts +145 -0
  316. package/src/components/discover.test.ts +352 -0
  317. package/src/components/discover.ts +167 -0
  318. package/src/components/driver-output.ts +71 -0
  319. package/src/components/driver.test.ts +638 -0
  320. package/src/components/driver.ts +690 -0
  321. package/src/components/index.ts +113 -0
  322. package/src/components/manifest-persistence.test.ts +152 -0
  323. package/src/components/manifest-persistence.ts +164 -0
  324. package/src/components/pilots/README.md +33 -0
  325. package/src/components/pilots/alb-ecs.pilot.ts +63 -0
  326. package/src/components/pilots/dynamodb.pilot.ts +59 -0
  327. package/src/components/pilots/jar-emr.pilot.ts +69 -0
  328. package/src/components/pilots/lambda.pilot.ts +55 -0
  329. package/src/components/pilots/neo4j-fanout.pilot.ts +92 -0
  330. package/src/components/pilots/pilots-lint-clean.test.ts +78 -0
  331. package/src/components/pilots/pilots.test.ts +126 -0
  332. package/src/components/pilots/project.ts +13 -0
  333. package/src/components/presets/comp007-reduction.test.ts +122 -0
  334. package/src/components/presets/ecs-fargate.test.ts +84 -0
  335. package/src/components/presets/ecs-fargate.ts +103 -0
  336. package/src/components/presets/index.ts +32 -0
  337. package/src/components/presets/lambda.test.ts +35 -0
  338. package/src/components/presets/lambda.ts +53 -0
  339. package/src/components/presets/single-host-compose.test.ts +65 -0
  340. package/src/components/presets/single-host-compose.ts +64 -0
  341. package/src/components/registry.test.ts +112 -0
  342. package/src/components/registry.ts +37 -0
  343. package/src/components/starter-plugin.ts +105 -0
  344. package/src/components/temporal-codegen-loader.ts +41 -0
  345. package/src/components/verbs/__fixtures__/package-lock.json +45 -0
  346. package/src/components/verbs/__fixtures__/pom.xml +37 -0
  347. package/src/components/verbs/__fixtures__/schemas/cyclonedx-1.5.schema.json +3800 -0
  348. package/src/components/verbs/__fixtures__/schemas/cyclonedx-spdx-license.schema.json +621 -0
  349. package/src/components/verbs/__fixtures__/schemas/jsf-0.82.schema.json +240 -0
  350. package/src/components/verbs/__fixtures__/schemas/spdx-2.3.schema.json +740 -0
  351. package/src/components/verbs/__tests__/mock-cloud-executor.ts +102 -0
  352. package/src/components/verbs/__tests__/mock-process-runner.ts +79 -0
  353. package/src/components/verbs/__tests__/mock-sbom-generator.ts +85 -0
  354. package/src/components/verbs/bom-writer.test.ts +331 -0
  355. package/src/components/verbs/bom-writer.ts +386 -0
  356. package/src/components/verbs/build-archive.test.ts +251 -0
  357. package/src/components/verbs/build-archive.ts +296 -0
  358. package/src/components/verbs/build.test.ts +222 -0
  359. package/src/components/verbs/build.ts +252 -0
  360. package/src/components/verbs/cloud-executor.ts +181 -0
  361. package/src/components/verbs/component-bom.test.ts +219 -0
  362. package/src/components/verbs/component-bom.ts +189 -0
  363. package/src/components/verbs/index.ts +49 -0
  364. package/src/components/verbs/license-policy.ts +115 -0
  365. package/src/components/verbs/lockfile-sbom-generator.test.ts +195 -0
  366. package/src/components/verbs/lockfile-sbom-generator.ts +236 -0
  367. package/src/components/verbs/process-runner.test.ts +79 -0
  368. package/src/components/verbs/process-runner.ts +113 -0
  369. package/src/components/verbs/reproducibility.test.ts +24 -0
  370. package/src/components/verbs/reproducibility.ts +100 -0
  371. package/src/components/verbs/sbom-generator.test.ts +96 -0
  372. package/src/components/verbs/sbom-generator.ts +209 -0
  373. package/src/components/verbs/sbom.test.ts +184 -0
  374. package/src/components/verbs/sbom.ts +152 -0
  375. package/src/components/verbs/shell.test.ts +36 -0
  376. package/src/components/verbs/shell.ts +51 -0
  377. package/src/components/verbs/sign.test.ts +210 -0
  378. package/src/components/verbs/sign.ts +350 -0
  379. package/src/components/verbs/stub.ts +34 -0
  380. package/src/components/verbs/tool-sbom-generator.test.ts +194 -0
  381. package/src/components/verbs/tool-sbom-generator.ts +192 -0
  382. package/src/components/verbs/verify.test.ts +159 -0
  383. package/src/components/verbs/verify.ts +179 -0
  384. package/src/components/verbs/vex.ts +182 -0
  385. package/src/components/verbs/vuln-gate.test.ts +201 -0
  386. package/src/components/verbs/vuln-gate.ts +170 -0
  387. package/src/components/verbs/vuln-scan.test.ts +145 -0
  388. package/src/components/verbs/vuln-scan.ts +266 -0
  389. package/src/components/verbs/wait-verify.test.ts +111 -0
  390. package/src/components/verbs/wait-verify.ts +171 -0
  391. package/src/config.test.ts +74 -1
  392. package/src/config.ts +219 -0
  393. package/src/index.ts +32 -0
  394. package/src/lexicon.ts +88 -0
  395. package/src/lifecycle/build-ledger-store.test.ts +217 -0
  396. package/src/lifecycle/build-ledger-store.ts +165 -0
  397. package/src/lifecycle/build-ledger.test.ts +349 -0
  398. package/src/lifecycle/build-ledger.ts +305 -0
  399. package/src/lifecycle/git.test.ts +118 -0
  400. package/src/lifecycle/git.ts +149 -14
  401. package/src/lifecycle/index.ts +5 -0
  402. package/src/lifecycle/oras-referrer-lookup.test.ts +116 -0
  403. package/src/lifecycle/oras-referrer-lookup.ts +110 -0
  404. package/src/lifecycle/release-ledger.test.ts +209 -0
  405. package/src/lifecycle/release-ledger.ts +185 -0
  406. package/src/lifecycle/status.test.ts +330 -0
  407. package/src/lifecycle/status.ts +278 -0
  408. package/src/lint/component-checks.ts +148 -0
  409. package/src/lint/config.test.ts +21 -0
  410. package/src/lint/engine.ts +11 -2
  411. package/src/lint/presets/relaxed.json +8 -1
  412. package/src/lint/presets/strict.json +8 -1
  413. package/src/lint/rules/__fixtures__/comp/comp000-discovery-error/broken-a.component.ts +9 -0
  414. package/src/lint/rules/__fixtures__/comp/comp000-discovery-error/broken-b.component.ts +9 -0
  415. package/src/lint/rules/__fixtures__/comp/comp001/fail/orphan-lib.component.ts +11 -0
  416. package/src/lint/rules/__fixtures__/comp/comp001/pass/emr-job.component.ts +13 -0
  417. package/src/lint/rules/__fixtures__/comp/comp001/pass/jar-lib.component.ts +11 -0
  418. package/src/lint/rules/__fixtures__/comp/comp002/fail/search-service.component.ts +27 -0
  419. package/src/lint/rules/__fixtures__/comp/comp002/pass/jar-lib.component.ts +10 -0
  420. package/src/lint/rules/__fixtures__/comp/comp002/pass/search-service.component.ts +23 -0
  421. package/src/lint/rules/__fixtures__/comp/comp003/fail/migrator.component.ts +10 -0
  422. package/src/lint/rules/__fixtures__/comp/comp003/pass/migrator.component.ts +19 -0
  423. package/src/lint/rules/__fixtures__/comp/comp003/pass/orders-table.component.ts +10 -0
  424. package/src/lint/rules/__fixtures__/comp/comp004/fail/neo4j-cluster.component.ts +20 -0
  425. package/src/lint/rules/__fixtures__/comp/comp004/pass/neo4j-cluster.component.ts +23 -0
  426. package/src/lint/rules/__fixtures__/comp/comp004/pass/orders-table.component.ts +10 -0
  427. package/src/lint/rules/__fixtures__/comp/comp005/fail/search-service.component.ts +14 -0
  428. package/src/lint/rules/__fixtures__/comp/comp005/pass/search-service.component.ts +18 -0
  429. package/src/lint/rules/__fixtures__/comp/comp006/fail/legacy-tool.component.ts +10 -0
  430. package/src/lint/rules/__fixtures__/comp/comp006/pass/legacy-tool.component.ts +14 -0
  431. package/src/lint/rules/__fixtures__/comp/comp007/fail/inventory-table.component.ts +13 -0
  432. package/src/lint/rules/__fixtures__/comp/comp007/fail/orders-table.component.ts +13 -0
  433. package/src/lint/rules/__fixtures__/comp/comp007/pass/orders-table.component.ts +12 -0
  434. package/src/lint/rules/__fixtures__/comp/comp007/pass/search-service.component.ts +18 -0
  435. package/src/lint/rules/comp/comp.test.ts +378 -0
  436. package/src/lint/rules/comp/comp001-publishes-never-applies.ts +78 -0
  437. package/src/lint/rules/comp/comp002-dangling-wiring-ref.ts +94 -0
  438. package/src/lint/rules/comp/comp003-mutating-no-rollback.ts +115 -0
  439. package/src/lint/rules/comp/comp004-gate-needs-temporal.ts +66 -0
  440. package/src/lint/rules/comp/comp005-capability-kind-is-noun.ts +113 -0
  441. package/src/lint/rules/comp/comp006-shell-needs-reason.ts +55 -0
  442. package/src/lint/rules/comp/comp007-composition-sprawl.ts +71 -0
  443. package/src/lint/rules/comp/index.ts +37 -0
  444. package/src/lint/rules/comp/support.ts +173 -0
  445. package/src/lsp/lexicon-providers.test.ts +55 -0
  446. package/src/lsp/lexicon-providers.ts +13 -3
  447. package/src/op/activity-registry.test.ts +38 -0
  448. package/src/op/activity-registry.ts +31 -12
  449. package/src/op/activity-runtime.ts +73 -0
  450. package/src/op/builders.ts +92 -0
  451. package/src/op/index.ts +3 -1
  452. package/src/ownership.test.ts +24 -26
  453. package/src/ownership.ts +26 -38
@@ -54,14 +54,51 @@ function gitTreeMock(files: Record<string, string>, sizes: Record<string, number
54
54
  return { impl, calls };
55
55
  }
56
56
 
57
- /** A GitLab-shaped mock: project default_branch, paginated repository/tree, raw files. */
58
- function gitlabMock(files: Record<string, string>) {
57
+ /**
58
+ * GitLab mock for authenticated search-based discovery. `searchMap` maps search
59
+ * term → array of blob paths the search API returns. `files` maps path → content.
60
+ * Pass `token` to fetchRepoFiles when using this mock so the code takes the
61
+ * search path (unauthenticated calls fall back to BFS).
62
+ */
63
+ function gitlabMock(searchMap: Record<string, string[]>, files: Record<string, string>) {
64
+ const impl = (async (url: string | URL | Request) => {
65
+ const u = String(url);
66
+ if (u.includes("/search")) {
67
+ const params = new URL(u).searchParams;
68
+ const term = params.get("search") ?? "";
69
+ const page = Number(params.get("page") ?? "1");
70
+ const paths = page === 1 ? (searchMap[term] ?? []) : [];
71
+ return new Response(JSON.stringify(paths.map((p) => ({ path: p }))), { status: 200 });
72
+ }
73
+ const rm = u.match(/\/repository\/files\/(.+?)\/raw\?/);
74
+ if (rm) {
75
+ const path = decodeURIComponent(rm[1]);
76
+ if (files[path] === undefined) return new Response("not found", { status: 404 });
77
+ return new Response(files[path], { status: 200 });
78
+ }
79
+ if (/\/projects\/[^/]+(\?|$)/.test(u)) return new Response(JSON.stringify({ default_branch: "main" }), { status: 200 });
80
+ return new Response("not found", { status: 404 });
81
+ }) as unknown as typeof fetch;
82
+ return { impl };
83
+ }
84
+
85
+ /**
86
+ * GitLab mock for the unauthenticated BFS fallback. `dirEntries` maps directory
87
+ * path (empty = root) to entries returned by the non-recursive tree API. `files`
88
+ * maps path → content for the raw endpoint.
89
+ */
90
+ function gitlabBfsMock(
91
+ dirEntries: Record<string, Array<{ path: string; type: "blob" | "tree" }>>,
92
+ files: Record<string, string>,
93
+ ) {
59
94
  const impl = (async (url: string | URL | Request) => {
60
95
  const u = String(url);
61
96
  if (u.includes("/repository/tree")) {
62
- const page = Number(new URL(u).searchParams.get("page") ?? "1");
63
- const all = Object.keys(files).map((path) => ({ path, type: "blob" }));
64
- return new Response(JSON.stringify(page === 1 ? all : []), { status: 200 });
97
+ const params = new URL(u).searchParams;
98
+ const dir = params.get("path") ?? "";
99
+ const page = Number(params.get("page") ?? "1");
100
+ const entries = dirEntries[dir] ?? [];
101
+ return new Response(JSON.stringify(page === 1 ? entries : []), { status: 200 });
65
102
  }
66
103
  const rm = u.match(/\/repository\/files\/(.+?)\/raw\?/);
67
104
  if (rm) {
@@ -174,13 +211,80 @@ describe("fetchRepoFiles", () => {
174
211
  await expect(fetchRepoFiles("https://evil.example.com/o/r")).rejects.toThrow(/Host not allowed/);
175
212
  });
176
213
 
177
- test("gitlab: paginated tree + raw file content", async () => {
178
- const { impl } = gitlabMock({ ".gitlab-ci.yml": "stages:\n - build\n" });
179
- const files = await fetchRepoFiles("https://gitlab.com/acme/widgets", { fetchImpl: impl });
214
+ test("gitlab: authenticated search finds .gitlab-ci.yml via stages: term", async () => {
215
+ const { impl } = gitlabMock(
216
+ { "stages:": [".gitlab-ci.yml"] },
217
+ { ".gitlab-ci.yml": "stages:\n - build\n" },
218
+ );
219
+ const files = await fetchRepoFiles("https://gitlab.com/acme/widgets", { fetchImpl: impl, token: "tok" });
180
220
  expect(files.map((f) => f.path)).toEqual([".gitlab-ci.yml"]);
181
221
  expect(files[0].content).toContain("stages:");
182
222
  });
183
223
 
224
+ test("gitlab: search finds CI at non-canonical paths (include targets) (#520)", async () => {
225
+ // A .gitlab-ci.yml with `include:` referencing other CI files — the included
226
+ // file also has `stages:` so the search finds it without knowing the path.
227
+ const { impl } = gitlabMock(
228
+ { "stages:": [".gitlab-ci.yml", ".gitlab/ci/build.gitlab-ci.yml"] },
229
+ {
230
+ ".gitlab-ci.yml": "stages:\n - build\ninclude:\n - .gitlab/ci/build.gitlab-ci.yml\n",
231
+ ".gitlab/ci/build.gitlab-ci.yml": "stages:\n - build\nscript:\n - make\n",
232
+ },
233
+ );
234
+ const files = await fetchRepoFiles("https://gitlab.com/acme/widgets", { fetchImpl: impl, token: "tok" });
235
+ expect(files.map((f) => f.path)).toContain(".gitlab/ci/build.gitlab-ci.yml");
236
+ });
237
+
238
+ test("gitlab: search finds IaC files by content, unaffected by repo size (#518)", async () => {
239
+ // Previously, gitlab-org/gitlab returned 0 files because the recursive tree
240
+ // API listed thousands of directories before any blobs. Search-based discovery
241
+ // is independent of directory count — it goes directly to content.
242
+ const { impl } = gitlabMock(
243
+ {
244
+ "AWSTemplateFormatVersion": ["infra/stack.json"],
245
+ "stages:": [".gitlab-ci.yml"],
246
+ },
247
+ {
248
+ "infra/stack.json": '{"AWSTemplateFormatVersion":"2010-09-09","Resources":{}}',
249
+ ".gitlab-ci.yml": "stages:\n - deploy\n",
250
+ },
251
+ );
252
+ const files = await fetchRepoFiles("https://gitlab.com/acme/monorepo", { fetchImpl: impl, token: "tok" });
253
+ const paths = files.map((f) => f.path);
254
+ expect(paths).toContain(".gitlab-ci.yml");
255
+ expect(paths).toContain("infra/stack.json");
256
+ });
257
+
258
+ test("gitlab: search deduplicates paths found by multiple terms", async () => {
259
+ // A Helm Chart.yaml matches both "apiVersion: v2" and the broader "apiVersion".
260
+ const { impl } = gitlabMock(
261
+ {
262
+ "apiVersion: v2": ["helm/Chart.yaml"],
263
+ "apiVersion": ["helm/Chart.yaml", "k8s/deploy.yaml"],
264
+ },
265
+ {
266
+ "helm/Chart.yaml": "apiVersion: v2\nname: myapp\nversion: 1.0.0\n",
267
+ "k8s/deploy.yaml": "apiVersion: apps/v1\nkind: Deployment\n",
268
+ },
269
+ );
270
+ const files = await fetchRepoFiles("https://gitlab.com/acme/widgets", { fetchImpl: impl, token: "tok" });
271
+ const paths = files.map((f) => f.path);
272
+ expect(paths.filter((p) => p === "helm/Chart.yaml")).toHaveLength(1);
273
+ expect(paths).toContain("k8s/deploy.yaml");
274
+ });
275
+
276
+ test("gitlab: unauthenticated falls back to BFS tree walk", async () => {
277
+ // Without a token, GitLab search returns 401. The BFS fallback walks
278
+ // directories non-recursively so root blobs appear on the first request.
279
+ const { impl } = gitlabBfsMock(
280
+ { "": [{ path: ".gitlab-ci.yml", type: "blob" }] },
281
+ { ".gitlab-ci.yml": "stages:\n - build\n" },
282
+ );
283
+ // No token → BFS path
284
+ const files = await fetchRepoFiles("https://gitlab.com/acme/widgets", { fetchImpl: impl });
285
+ expect(files.map((f) => f.path)).toContain(".gitlab-ci.yml");
286
+ });
287
+
184
288
  test("forgejo (codeberg): gitea tree + contents", async () => {
185
289
  const { impl } = gitTreeMock({ ".forgejo/workflows/ci.yml": CI_YAML });
186
290
  const files = await fetchRepoFiles("https://codeberg.org/acme/widgets", { fetchImpl: impl });
@@ -157,16 +157,61 @@ interface TreeEntry {
157
157
  /** List every blob path in the repo (recursive), with size where the host reports it. */
158
158
  async function listTree(host: HostConfig, owner: string, repo: string, ref: string, doFetch: typeof fetch, headers: Record<string, string>, ms: number): Promise<TreeEntry[]> {
159
159
  if (host.kind === "gitlab") {
160
+ // GitLab search API requires authentication even for public projects. When a
161
+ // token is present, use content search — one query per lexicon finds only
162
+ // relevant files regardless of repo size, and catches non-canonical CI paths
163
+ // that path-based detection misses (#518, #520). Without a token, fall back
164
+ // to the non-recursive BFS which works for public repos.
165
+ if ("PRIVATE-TOKEN" in headers) {
166
+ const out: TreeEntry[] = [];
167
+ const seen = new Set<string>();
168
+ const addPath = (p: string) => { if (typeof p === "string" && !seen.has(p)) { seen.add(p); out.push({ path: p, type: "blob" }); } };
169
+ // Ordered most → least selective so the seen-set dedup reduces noise from
170
+ // broader terms (e.g. `apiVersion` won't re-add files already found by
171
+ // the more specific `apiVersion: v2`).
172
+ const SEARCH_TERMS = [
173
+ "AWSTemplateFormatVersion", // CloudFormation
174
+ "deploymentTemplate", // Azure ARM ($schema substring)
175
+ "cnrm.cloud.google.com", // GCP Config Connector
176
+ "apiVersion: v2", // Helm Chart.yaml
177
+ "stages:", // GitLab CI — root file + non-canonical includes (#520)
178
+ "FROM ", // Dockerfiles (content-based; space avoids false matches)
179
+ "services:", // Docker Compose
180
+ "apiVersion", // k8s manifests (broad; runs last)
181
+ ];
182
+ for (const term of SEARCH_TERMS) {
183
+ for (let page = 1; page <= 3; page++) {
184
+ const url = `${host.api}/projects/${projectId(owner, repo)}/search?scope=blobs&search=${encodeURIComponent(term)}&per_page=100&page=${page}&ref=${encodeURIComponent(ref)}`;
185
+ const { body } = await getJsonAt(url, headers, doFetch, ms);
186
+ if (!Array.isArray(body) || body.length === 0) break;
187
+ for (const e of body as Array<{ path: string }>) addPath(e.path);
188
+ if (body.length < 100) break;
189
+ }
190
+ }
191
+ return out;
192
+ }
193
+ // Unauthenticated fallback: non-recursive BFS. GitLab's recursive tree API
194
+ // lists ALL directories before any blobs for large repos (#518), so we walk
195
+ // directories breadth-first to ensure root blobs appear on the first request.
160
196
  const out: TreeEntry[] = [];
161
- const MAX_PAGES = 20; // 20 * 100 = 2000 entries — bounded; candidate filter + caps trim further
162
- for (let page = 1; page <= MAX_PAGES; page++) {
163
- const url = `${host.api}/projects/${projectId(owner, repo)}/repository/tree?recursive=true&per_page=100&page=${page}&ref=${encodeURIComponent(ref)}`;
164
- const { body } = await getJsonAt(url, headers, doFetch, ms);
165
- if (!Array.isArray(body) || body.length === 0) break;
166
- for (const e of body as Array<{ path: string; type: string }>) {
167
- if (e.type === "blob") out.push({ path: e.path, type: "blob" });
197
+ const queue: string[] = [""]; // "" = repo root
198
+ const MAX_DIRS = 30;
199
+ const MAX_BLOBS = 200;
200
+ let dirs = 0;
201
+ while (queue.length > 0 && dirs < MAX_DIRS && out.length < MAX_BLOBS) {
202
+ const dir = queue.shift()!;
203
+ dirs++;
204
+ const pathParam = dir ? `&path=${encodeURIComponent(dir)}` : "";
205
+ for (let page = 1; page <= 5; page++) {
206
+ const url = `${host.api}/projects/${projectId(owner, repo)}/repository/tree?per_page=100&page=${page}&ref=${encodeURIComponent(ref)}${pathParam}`;
207
+ const { body } = await getJsonAt(url, headers, doFetch, ms);
208
+ if (!Array.isArray(body) || body.length === 0) break;
209
+ for (const e of body as Array<{ path: string; type: string }>) {
210
+ if (e.type === "blob") out.push({ path: e.path, type: "blob" });
211
+ else if (e.type === "tree") queue.push(e.path);
212
+ }
213
+ if (body.length < 100) break;
168
214
  }
169
- if (body.length < 100) break;
170
215
  }
171
216
  return out;
172
217
  }
@@ -12,13 +12,15 @@
12
12
  * the patched output differs only where intended — the diff proves it.
13
13
  */
14
14
 
15
- import { RULE_CATALOG } from "./catalog";
15
+ import { RULE_CATALOG, type RuleMeta } from "./catalog";
16
16
 
17
17
  export interface ProveOptions {
18
18
  /** Resolve an action ref (e.g. "actions/checkout@v4") to a 40-char SHA. */
19
19
  resolveSha?: (action: string, ref: string) => string | undefined;
20
20
  /** Resolve a container image (e.g. "node:20") to a "sha256:..." digest. */
21
21
  resolveDigest?: (image: string) => string | undefined;
22
+ /** Resolved audit catalog for the check's `fixKind`/remediation lookup (#687). Defaults to core's static `RULE_CATALOG`. */
23
+ catalog?: Record<string, RuleMeta>;
22
24
  }
23
25
 
24
26
  export interface ProofResult {
@@ -150,7 +152,7 @@ function narrowWriteAll(content: string): { patched: string; changed: boolean }
150
152
  * Returns `applied: false` with guidance for non-deterministic findings.
151
153
  */
152
154
  export function proveFix(checkId: string, content: string, opts: ProveOptions = {}): ProofResult {
153
- const cat = RULE_CATALOG[checkId];
155
+ const cat = (opts.catalog ?? RULE_CATALOG)[checkId];
154
156
  if (cat && cat.fixKind !== "deterministic") {
155
157
  return notApplied(checkId, "guidance", cat.remediation || "Manual fix required.");
156
158
  }
@@ -1,4 +1,5 @@
1
- import { describe, test, expect } from "vitest";
1
+ import { describe, test, expect, beforeAll } from "vitest";
2
+ import { resolveAuditCatalog, type RuleMeta } from "./catalog";
2
3
  import { renderHtml, type AuditSnapshot } from "./report-html";
3
4
  import type { AuditFinding } from "./core";
4
5
 
@@ -29,9 +30,15 @@ const SNAPSHOT: AuditSnapshot = {
29
30
  toolVersion: "0.4.0",
30
31
  };
31
32
 
33
+
34
+ let CATALOG: Record<string, RuleMeta>;
35
+ beforeAll(async () => { CATALOG = await resolveAuditCatalog(["github", "gitlab"]); });
36
+ /** renderHtml with the migrated github/gitlab catalog injected (#687). */
37
+ const html2 = (findings: AuditFinding[], opts: Parameters<typeof renderHtml>[1] = {}) => renderHtml(findings, { ...opts, catalog: CATALOG });
38
+
32
39
  describe("renderHtml", () => {
33
40
  test("produces a self-contained HTML document with sections and snapshot", () => {
34
- const html = renderHtml(FINDINGS, { files: [{ path: ".github/workflows/ci.yml", content: CI }], snapshot: SNAPSHOT });
41
+ const html = html2(FINDINGS, { files: [{ path: ".github/workflows/ci.yml", content: CI }], snapshot: SNAPSHOT });
35
42
  expect(html.startsWith("<!doctype html>")).toBe(true);
36
43
  expect(html).toContain("<style>"); // inline CSS, self-contained
37
44
  expect(html).toContain("owner/repo");
@@ -43,7 +50,7 @@ describe("renderHtml", () => {
43
50
  });
44
51
 
45
52
  test("embeds the machine-readable JSON report (parseable, escaped)", () => {
46
- const html = renderHtml(FINDINGS, { snapshot: SNAPSHOT });
53
+ const html = html2(FINDINGS, { snapshot: SNAPSHOT });
47
54
  const m = html.match(/<script type="application\/json" id="chant-audit-report">(.*?)<\/script>/s);
48
55
  expect(m).not.toBeNull();
49
56
  // No raw "<" can break out of the script element.
@@ -58,25 +65,25 @@ describe("renderHtml", () => {
58
65
  const evil: AuditFinding[] = [
59
66
  { checkId: "GHA036", severity: "error", message: "<script>alert(1)</script>", file: "a.yml", lexicon: "github" },
60
67
  ];
61
- const html = renderHtml(evil);
68
+ const html = html2(evil);
62
69
  expect(html).not.toContain("<script>alert(1)</script>");
63
70
  expect(html).toContain("&lt;script&gt;");
64
71
  });
65
72
 
66
73
  test("applies theme knobs (title, accent)", () => {
67
- const html = renderHtml(FINDINGS, { theme: { title: "Acme Security", accent: "#ff0000" } });
74
+ const html = html2(FINDINGS, { theme: { title: "Acme Security", accent: "#ff0000" } });
68
75
  expect(html).toContain("<title>Acme Security</title>");
69
76
  expect(html).toContain("#ff0000");
70
77
  });
71
78
 
72
79
  test("honours a full template override", () => {
73
- const html = renderHtml(FINDINGS, { template: "<html>{{title}}::{{body}}</html>", theme: { title: "X" } });
80
+ const html = html2(FINDINGS, { template: "<html>{{title}}::{{body}}</html>", theme: { title: "X" } });
74
81
  expect(html.startsWith("<html>X::")).toBe(true);
75
82
  expect(html).toContain("Quick wins"); // body still rendered
76
83
  });
77
84
 
78
85
  test("clean report when no findings", () => {
79
- const html = renderHtml([]);
86
+ const html = html2([]);
80
87
  expect(html).toContain("No issues found.");
81
88
  expect(html).not.toContain("Quick wins");
82
89
  });
@@ -112,9 +112,9 @@ export interface AuditReportJson {
112
112
  findings: SerializedFinding[];
113
113
  }
114
114
 
115
- export function metaFor(id: string): RuleMeta {
115
+ export function metaFor(id: string, catalog: Record<string, RuleMeta> = RULE_CATALOG): RuleMeta {
116
116
  return (
117
- RULE_CATALOG[id] ?? {
117
+ catalog[id] ?? {
118
118
  id,
119
119
  tier: "report-only" as Tier,
120
120
  fixKind: "guidance",
@@ -146,7 +146,12 @@ function clusterName(m: RuleMeta): string {
146
146
  return m.authority?.[0]?.name ?? "General hardening";
147
147
  }
148
148
 
149
- function buildQuickWins(findings: EnrichedFinding[], contents: Map<string, string>, proveOpts: ProveOptions): QuickWinFile[] {
149
+ function buildQuickWins(
150
+ findings: EnrichedFinding[],
151
+ contents: Map<string, string>,
152
+ proveOpts: ProveOptions,
153
+ catalog: Record<string, RuleMeta> = RULE_CATALOG,
154
+ ): QuickWinFile[] {
150
155
  const out: QuickWinFile[] = [];
151
156
  for (const [file, group] of byFile(findings)) {
152
157
  const ids = [...new Set(group.map((f) => f.checkId))].sort();
@@ -157,10 +162,10 @@ function buildQuickWins(findings: EnrichedFinding[], contents: Map<string, strin
157
162
 
158
163
  if (original !== undefined) {
159
164
  for (const id of ids) {
160
- const res = proveFix(id, patched ?? original, proveOpts);
165
+ const res = proveFix(id, patched ?? original, { ...proveOpts, catalog });
161
166
  if (res.applied && res.patched !== undefined) {
162
167
  patched = res.patched;
163
- addressed.push(metaFor(id));
168
+ addressed.push(metaFor(id, catalog));
164
169
  } else if (res.reason === "needs-input") {
165
170
  needsInput.push(...group.filter((f) => f.checkId === id));
166
171
  }
@@ -215,11 +220,14 @@ export interface BuildModelOptions {
215
220
  files?: Array<{ path: string; content: string }>;
216
221
  resolveSha?: ProveOptions["resolveSha"];
217
222
  resolveDigest?: ProveOptions["resolveDigest"];
223
+ /** The resolved audit catalog (core static + active lexicons' contributions, #687). Defaults to core's static `RULE_CATALOG`. */
224
+ catalog?: Record<string, RuleMeta>;
218
225
  }
219
226
 
220
227
  /** Build the structured report model from raw findings. */
221
228
  export function buildReportModel(findings: AuditFinding[], opts: BuildModelOptions = {}): ReportModel {
222
- const enriched: EnrichedFinding[] = findings.map((f) => ({ ...f, meta: metaFor(f.checkId) }));
229
+ const catalog = opts.catalog ?? RULE_CATALOG;
230
+ const enriched: EnrichedFinding[] = findings.map((f) => ({ ...f, meta: metaFor(f.checkId, catalog) }));
223
231
  const contents = new Map((opts.files ?? []).map((f) => [f.path, f.content]));
224
232
 
225
233
  const mergeWorthy = enriched.filter((f) => f.meta.tier === "merge-worthy");
@@ -252,7 +260,7 @@ export function buildReportModel(findings: AuditFinding[], opts: BuildModelOptio
252
260
 
253
261
  return {
254
262
  counts,
255
- quickWins: buildQuickWins(quickWinFindings, contents, { resolveSha: opts.resolveSha, resolveDigest: opts.resolveDigest }),
263
+ quickWins: buildQuickWins(quickWinFindings, contents, { resolveSha: opts.resolveSha, resolveDigest: opts.resolveDigest }, catalog),
256
264
  needsReview: buildClusters(needsReviewFindings),
257
265
  reportOnly: [...reportOnly].sort(sortFindings),
258
266
  findings: [...shown].sort(sortFindings),
@@ -260,8 +268,11 @@ export function buildReportModel(findings: AuditFinding[], opts: BuildModelOptio
260
268
  }
261
269
 
262
270
  /** Build the versioned, machine-readable JSON report (stable contract). */
263
- export function buildReportJson(findings: AuditFinding[], opts: { snapshot?: AuditSnapshot; toolVersion?: string } = {}): AuditReportJson {
264
- const model = buildReportModel(findings);
271
+ export function buildReportJson(
272
+ findings: AuditFinding[],
273
+ opts: { snapshot?: AuditSnapshot; toolVersion?: string; catalog?: Record<string, RuleMeta> } = {},
274
+ ): AuditReportJson {
275
+ const model = buildReportModel(findings, { catalog: opts.catalog });
265
276
  const version = opts.toolVersion ?? opts.snapshot?.toolVersion ?? "0.0.0";
266
277
  return {
267
278
  schemaVersion: REPORT_SCHEMA_VERSION,
@@ -1,5 +1,6 @@
1
- import { describe, test, expect } from "vitest";
2
- import { renderMarkdown } from "./report";
1
+ import { describe, test, expect, beforeAll } from "vitest";
2
+ import { resolveAuditCatalog, type RuleMeta } from "./catalog";
3
+ import { renderMarkdown, type RenderOptions } from "./report";
3
4
  import type { AuditFinding } from "./core";
4
5
 
5
6
  const CI = `name: CI
@@ -21,15 +22,21 @@ const FINDINGS: AuditFinding[] = [
21
22
  { checkId: "GHA022", severity: "info", message: "no timeout.", file: ".github/workflows/ci.yml", lexicon: "github" },
22
23
  ];
23
24
 
25
+
26
+ let CATALOG: Record<string, RuleMeta>;
27
+ beforeAll(async () => { CATALOG = await resolveAuditCatalog(["github", "gitlab"]); });
28
+ /** renderMarkdown with the migrated github/gitlab catalog injected (#687). */
29
+ const md = (findings: AuditFinding[], opts: RenderOptions = {}) => renderMarkdown(findings, { ...opts, catalog: CATALOG });
30
+
24
31
  describe("renderMarkdown — reworked structure", () => {
25
32
  test("summary counts by tier and severity", () => {
26
- const out = renderMarkdown(FINDINGS, { target: "owner/repo" });
33
+ const out = md(FINDINGS, { target: "owner/repo" });
27
34
  expect(out).toContain("Target: owner/repo");
28
35
  expect(out).toContain("5 findings — 2 quick-win, 2 needs-review, 1 report-only (1 error, 3 warning, 1 info).");
29
36
  });
30
37
 
31
38
  test("quick wins show a real combined diff when file content is provided", () => {
32
- const out = renderMarkdown(FINDINGS, { files: [{ path: ".github/workflows/ci.yml", content: CI }] });
39
+ const out = md(FINDINGS, { files: [{ path: ".github/workflows/ci.yml", content: CI }] });
33
40
  expect(out).toContain("## Quick wins (deterministic)");
34
41
  expect(out).toContain("Addresses [GHA033](https://intentius.io/chant/lint-rules/audit-rules/#gha033) (Blanket write-all permissions)");
35
42
  // merge-worthy findings cite their external authority (#351 links surfaced in the layout)
@@ -41,14 +48,14 @@ describe("renderMarkdown — reworked structure", () => {
41
48
  });
42
49
 
43
50
  test("pin findings without a SHA resolver are listed, not diffed", () => {
44
- const out = renderMarkdown(FINDINGS, { files: [{ path: ".github/workflows/ci.yml", content: CI }] });
51
+ const out = md(FINDINGS, { files: [{ path: ".github/workflows/ci.yml", content: CI }] });
45
52
  expect(out).toContain("Needs a value before it can be auto-patched:");
46
53
  expect(out).toContain("**[GHA021](https://intentius.io/chant/lint-rules/audit-rules/#gha021)**");
47
54
  });
48
55
 
49
56
  test("pin findings are diffed when a SHA resolver is supplied", () => {
50
57
  const sha = "11bd71901bbe5b1630ceea73d27597364c9af683";
51
- const out = renderMarkdown(FINDINGS, {
58
+ const out = md(FINDINGS, {
52
59
  files: [{ path: ".github/workflows/ci.yml", content: CI }],
53
60
  resolveSha: () => sha,
54
61
  });
@@ -62,14 +69,14 @@ describe("renderMarkdown — reworked structure", () => {
62
69
  { checkId: "GHA021", severity: "warning", message: "unpinned checkout.", file: ".github/workflows/ci.yml", lexicon: "github", entity: "build" },
63
70
  { checkId: "GHA029", severity: "warning", message: "unpinned acme.", file: ".github/workflows/ci.yml", lexicon: "github", entity: "build" },
64
71
  ];
65
- const out = renderMarkdown(findings, { files: [{ path: ".github/workflows/ci.yml", content }], resolveSha: () => sha });
72
+ const out = md(findings, { files: [{ path: ".github/workflows/ci.yml", content }], resolveSha: () => sha });
66
73
  // Both actions pinned in one combined diff; neither listed as "needs a value".
67
74
  expect(out).toContain("```diff");
68
75
  expect(out).not.toContain("Needs a value before it can be auto-patched");
69
76
  });
70
77
 
71
78
  test("guidance findings cluster by authority", () => {
72
- const out = renderMarkdown(FINDINGS);
79
+ const out = md(FINDINGS);
73
80
  expect(out).toContain("<summary>Needs review (guidance)");
74
81
  // GHA035 and GHA018 both share the pwn-request authority → one cluster.
75
82
  expect(out).toContain("Preventing pwn requests");
@@ -80,7 +87,7 @@ describe("renderMarkdown — reworked structure", () => {
80
87
  });
81
88
 
82
89
  test("report-only hygiene goes in a collapsible table", () => {
83
- const out = renderMarkdown(FINDINGS);
90
+ const out = md(FINDINGS);
84
91
  expect(out).toContain("<details>");
85
92
  expect(out).toContain("<summary>Report-only (hygiene)");
86
93
  expect(out).toContain("| [GHA022](https://intentius.io/chant/lint-rules/audit-rules/#gha022) |");
@@ -91,7 +98,7 @@ describe("renderMarkdown — reworked structure", () => {
91
98
  { checkId: "WGL016", severity: "error", message: "hardcoded secret.", file: ".gitlab-ci.yml", lexicon: "gitlab", entity: "DB_PASSWORD" },
92
99
  { checkId: "WGL021", severity: "warning", message: "unused variable.", file: ".gitlab-ci.yml", lexicon: "gitlab", entity: "DB_PASSWORD" },
93
100
  ];
94
- const out = renderMarkdown(findings);
101
+ const out = md(findings);
95
102
  expect(out).toContain("WGL016");
96
103
  expect(out).not.toContain("WGL021"); // suppressed — same entity already flagged
97
104
  expect(out).toContain("1 finding — ");
@@ -102,20 +109,36 @@ describe("renderMarkdown — reworked structure", () => {
102
109
  { checkId: "GHA021", severity: "warning", message: "unpinned.", file: ".github/workflows/ci.yml", lexicon: "github", entity: "build" },
103
110
  { checkId: "GHA022", severity: "info", message: "no timeout.", file: ".github/workflows/ci.yml", lexicon: "github", entity: "build" },
104
111
  ];
105
- const out = renderMarkdown(findings);
112
+ const out = md(findings);
106
113
  expect(out).toContain("GHA022"); // unrelated hygiene on the same job is kept
107
114
  });
108
115
 
109
116
  test("clean report when there are no findings", () => {
110
- const out = renderMarkdown([]);
117
+ const out = md([]);
111
118
  expect(out).toContain("No issues found.");
112
119
  expect(out).not.toContain("Quick wins");
113
120
  });
114
121
 
115
122
  test("omits empty sections", () => {
116
- const onlyGuidance = renderMarkdown([FINDINGS[2]]); // GHA035 only
123
+ const onlyGuidance = md([FINDINGS[2]]); // GHA035 only
117
124
  expect(onlyGuidance).toContain("<summary>Needs review (guidance)");
118
125
  expect(onlyGuidance).not.toContain("## Quick wins");
119
126
  expect(onlyGuidance).not.toContain("Report-only");
120
127
  });
121
128
  });
129
+
130
+ describe("catalog threading (#687)", () => {
131
+ test("buildReportModel uses a passed-in catalog over core's static one", async () => {
132
+ const { buildReportModel } = await import("./report-model");
133
+ const findings: AuditFinding[] = [
134
+ { checkId: "ZZZ999", severity: "warning", message: "made up", file: "x.yml", lexicon: "github" },
135
+ ];
136
+ // Not in the static catalog → default model treats it as report-only.
137
+ expect(buildReportModel(findings).counts.quickWin).toBe(0);
138
+ // A resolved catalog (as a lexicon would contribute) makes it a deterministic merge-worthy fix.
139
+ const catalog = {
140
+ ZZZ999: { id: "ZZZ999", tier: "merge-worthy" as const, fixKind: "deterministic" as const, category: "security" as const, title: "Made up", remediation: "fix it", yamlBased: true },
141
+ };
142
+ expect(buildReportModel(findings, { catalog }).counts.quickWin).toBe(1);
143
+ });
144
+ });
@@ -37,6 +37,8 @@ export interface RenderOptions {
37
37
  resolveDigest?: ProveOptions["resolveDigest"];
38
38
  /** Coverage caveats shown near the top (e.g. unresolved GitLab includes). */
39
39
  notes?: string[];
40
+ /** Resolved audit catalog (core static + active lexicons' contributions, #687). Defaults to core's static catalog. */
41
+ catalog?: Record<string, RuleMeta>;
40
42
  }
41
43
 
42
44
  function escapeCell(s: string): string {
@@ -2,19 +2,21 @@ import { describe, test, expect } from "vitest";
2
2
  import { readFileSync } from "fs";
3
3
  import { fileURLToPath } from "url";
4
4
  import { renderRulesReference } from "./rules-doc";
5
- import { RULE_CATALOG, ruleDocUrl } from "./catalog";
5
+ import { resolveAuditCatalog, ruleDocUrl } from "./catalog";
6
+ import { AUDIT_LEXICONS } from "./discover";
6
7
 
7
8
  const PAGE = fileURLToPath(new URL("../../../../docs/src/content/docs/lint-rules/audit-rules.mdx", import.meta.url));
8
9
 
9
10
  describe("audit rules reference", () => {
10
- test("committed page is in sync with the catalog (regenerate if this fails)", () => {
11
+ test("committed page is in sync with the catalog (regenerate if this fails)", async () => {
11
12
  const committed = readFileSync(PAGE, "utf-8");
12
- expect(committed).toBe(renderRulesReference());
13
+ expect(committed).toBe(await renderRulesReference());
13
14
  });
14
15
 
15
- test("every rule has an anchor reachable from ruleDocUrl", () => {
16
- const page = renderRulesReference();
17
- for (const id of Object.keys(RULE_CATALOG)) {
16
+ test("every rule has an anchor reachable from ruleDocUrl", async () => {
17
+ const page = await renderRulesReference();
18
+ const catalog = await resolveAuditCatalog([...AUDIT_LEXICONS]);
19
+ for (const id of Object.keys(catalog)) {
18
20
  // `### GHA033` → Starlight slug `#gha033`, which ruleDocUrl targets.
19
21
  expect(page).toContain(`### ${id}`);
20
22
  expect(ruleDocUrl(id)).toBe(`https://intentius.io/chant/lint-rules/audit-rules/#${id.toLowerCase()}`);
@@ -7,7 +7,8 @@
7
7
  * (lowercased) — the anchor `ruleDocUrl()` links to.
8
8
  */
9
9
 
10
- import { RULE_CATALOG, type RuleMeta } from "./catalog";
10
+ import { resolveAuditCatalog, type RuleMeta } from "./catalog";
11
+ import { AUDIT_LEXICONS } from "./discover";
11
12
 
12
13
  const GROUPS: Array<{ heading: string; prefixes: string[]; blurb: string }> = [
13
14
  { heading: "GitHub Actions (GHA)", prefixes: ["GHA"], blurb: "Also applied to Forgejo workflows, which are GitHub-dialect." },
@@ -29,11 +30,18 @@ function ruleBlock(m: RuleMeta): string {
29
30
  return `### ${m.id}\n\n**${m.title}** — ${tags}\n\n${m.remediation}${authority}`;
30
31
  }
31
32
 
32
- /** Render the full audit rules reference page (frontmatter + body). */
33
- export function renderRulesReference(): string {
34
- const ids = Object.keys(RULE_CATALOG).sort();
33
+ /**
34
+ * Render the full audit rules reference page (frontmatter + body). Aggregates
35
+ * the catalog across every audit lexicon (#687) — each provider's rule metadata
36
+ * now lives in its own lexicon and is merged over core's static entries — so the
37
+ * doc lists them all. Async because it loads the lexicon plugins to collect
38
+ * their contributed catalogs; a doc-build step has all lexicons installed.
39
+ */
40
+ export async function renderRulesReference(): Promise<string> {
41
+ const catalog = await resolveAuditCatalog([...AUDIT_LEXICONS]);
42
+ const ids = Object.keys(catalog).sort();
35
43
  const sections = GROUPS.map(({ heading, prefixes, blurb }) => {
36
- const blocks = ids.filter((id) => prefixes.some((p) => id.startsWith(p))).map((id) => ruleBlock(RULE_CATALOG[id]));
44
+ const blocks = ids.filter((id) => prefixes.some((p) => id.startsWith(p))).map((id) => ruleBlock(catalog[id]));
37
45
  if (blocks.length === 0) return "";
38
46
  return `## ${heading}\n${blurb ? `\n${blurb}\n` : ""}\n${blocks.join("\n\n")}`;
39
47
  }).filter(Boolean);