@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
@@ -0,0 +1,378 @@
1
+ /**
2
+ * COMP* composition lint rule tests (#562, epic #551).
3
+ *
4
+ * Each rule gets one passing and one failing fixture directory under
5
+ * ../__fixtures__/comp/<ruleId>/{pass,fail}/ — real `*.component.ts` files
6
+ * discovered the same way `chant lint` discovers them (via
7
+ * ../../component-checks.ts's `runComponentChecks`, itself
8
+ * `../../../components/discover.ts`'s `discoverComponents` plus every
9
+ * `ComponentCheck`). Each fixture directory is its own tiny "project" so
10
+ * cross-component rules (COMP002, COMP007) only see the components meant for
11
+ * that specific case.
12
+ */
13
+
14
+ import { join } from "node:path";
15
+ import { describe, expect, it } from "vitest";
16
+ import { runComponentChecks } from "../../component-checks";
17
+ import type { RollbackPolicy } from "../../../components/capability";
18
+ import { loadComponentChecks } from "./index";
19
+
20
+ const FIXTURES_DIR = join(import.meta.dirname, "..", "__fixtures__", "comp");
21
+ const checks = loadComponentChecks();
22
+
23
+ /**
24
+ * Rollback dispositions the COMP003 fixtures reference, standing in for what
25
+ * `chant lint` derives from the project's registry (`cfn-deploy` has a native
26
+ * `rollback`; `run-migration` declares `needs-opt-out` in the aws lexicon).
27
+ */
28
+ const FIXTURE_ROLLBACK_POLICIES = new Map<string, RollbackPolicy>([
29
+ ["cfn-deploy", "native"],
30
+ ["run-migration", "needs-opt-out"],
31
+ ]);
32
+
33
+ async function lintFixture(
34
+ ruleId: string,
35
+ kind: "pass" | "fail",
36
+ registryContext?: { knownKinds?: ReadonlySet<string>; rollbackPolicies?: ReadonlyMap<string, RollbackPolicy> },
37
+ ) {
38
+ const dir = join(FIXTURES_DIR, ruleId.toLowerCase(), kind);
39
+ return runComponentChecks(dir, checks, registryContext);
40
+ }
41
+
42
+ describe("COMP000: component discovery errors", () => {
43
+ it("surfaces a duplicate component name as a COMP000 diagnostic instead of silently dropping it", async () => {
44
+ const dir = join(FIXTURES_DIR, "comp000-discovery-error");
45
+ const diagnostics = await runComponentChecks(dir, checks);
46
+ const hits = diagnostics.filter((d) => d.checkId === "COMP000");
47
+ expect(hits.length).toBeGreaterThan(0);
48
+ expect(hits[0].severity).toBe("error");
49
+ expect(hits[0].message).toContain("Duplicate component name");
50
+ });
51
+ });
52
+
53
+ describe("loadComponentChecks()", () => {
54
+ it("returns all seven COMP* checks, sorted by id, each error or warning severity", () => {
55
+ expect(checks.map((c) => c.id)).toEqual([
56
+ "COMP001",
57
+ "COMP002",
58
+ "COMP003",
59
+ "COMP004",
60
+ "COMP005",
61
+ "COMP006",
62
+ "COMP007",
63
+ ]);
64
+ for (const check of checks) {
65
+ expect(["error", "warning"]).toContain(check.severity);
66
+ expect(typeof check.description).toBe("string");
67
+ expect(check.description.length).toBeGreaterThan(0);
68
+ }
69
+ });
70
+ });
71
+
72
+ describe("COMP001: publishes-but-never-applies", () => {
73
+ it("flags a publish step whose output nothing ever consumes", async () => {
74
+ const diagnostics = await lintFixture("comp001", "fail");
75
+ const hits = diagnostics.filter((d) => d.checkId === "COMP001");
76
+ expect(hits).toHaveLength(1);
77
+ expect(hits[0].component).toBe("orphan-lib");
78
+ expect(hits[0].message).toContain("never consumed");
79
+ });
80
+
81
+ it("does not flag a publish step consumed by a downstream component", async () => {
82
+ const diagnostics = await lintFixture("comp001", "pass");
83
+ expect(diagnostics.filter((d) => d.checkId === "COMP001")).toHaveLength(0);
84
+ });
85
+ });
86
+
87
+ describe("COMP002: dangling-wiring-ref", () => {
88
+ it("flags a @Phase.field reference to a nonexistent phase and a @component.publish.* reference to an undiscovered component", async () => {
89
+ const diagnostics = await lintFixture("comp002", "fail");
90
+ const hits = diagnostics.filter((d) => d.checkId === "COMP002");
91
+ expect(hits.length).toBeGreaterThanOrEqual(2);
92
+ expect(hits.some((d) => d.message.includes('"@Build.digest"'))).toBe(true);
93
+ expect(hits.some((d) => d.message.includes('"@missing-lib.publish.uri"'))).toBe(true);
94
+ });
95
+
96
+ it("does not flag references that resolve to a real phase/dependency", async () => {
97
+ const diagnostics = await lintFixture("comp002", "pass");
98
+ expect(diagnostics.filter((d) => d.checkId === "COMP002")).toHaveLength(0);
99
+ });
100
+
101
+ it("flags a cross-component reference to a component missing from dependsOn", async () => {
102
+ // Reuses the pass fixture's components but confirms the dependsOn check
103
+ // specifically by asserting the pass fixture's searchService (which DOES
104
+ // list jar-lib in dependsOn) produces no COMP002 hit for that reference.
105
+ const diagnostics = await lintFixture("comp002", "pass");
106
+ const jarRefHits = diagnostics.filter(
107
+ (d) => d.checkId === "COMP002" && d.message.includes("jar-lib"),
108
+ );
109
+ expect(jarRefHits).toHaveLength(0);
110
+ });
111
+ });
112
+
113
+ describe("COMP003: mutating-no-rollback", () => {
114
+ it("flags a mutating step (run-migration) with no native rollback and no opt-out", async () => {
115
+ const diagnostics = await lintFixture("comp003", "fail", { rollbackPolicies: FIXTURE_ROLLBACK_POLICIES });
116
+ const hits = diagnostics.filter((d) => d.checkId === "COMP003");
117
+ expect(hits).toHaveLength(1);
118
+ expect(hits[0].component).toBe("migrator");
119
+ expect(hits[0].message).toContain("run-migration");
120
+ });
121
+
122
+ it("does not flag a step with a native rollback (cfn-deploy) or an explicit noRollback opt-out", async () => {
123
+ const diagnostics = await lintFixture("comp003", "pass", { rollbackPolicies: FIXTURE_ROLLBACK_POLICIES });
124
+ expect(diagnostics.filter((d) => d.checkId === "COMP003")).toHaveLength(0);
125
+ });
126
+
127
+ it("flags a needs-opt-out step nested inside a fan-out phase, not just top-level phases", () => {
128
+ // Regression test: the rule used to iterate component.deploy directly and
129
+ // never recursed into nested Phase entries (a fan-out unit, e.g. the
130
+ // Neo4j pilot's per-instance phases), so a mutating step buried inside one
131
+ // was silently skipped. It must now use the same walkComponent traversal
132
+ // every other COMP rule uses.
133
+ const [comp003] = checks.filter((c) => c.id === "COMP003");
134
+ const ctx = {
135
+ rollbackPolicies: FIXTURE_ROLLBACK_POLICIES,
136
+ components: new Map([
137
+ [
138
+ "fanout-cluster",
139
+ {
140
+ component: {
141
+ name: "fanout-cluster",
142
+ dependsOn: [],
143
+ deploy: [
144
+ {
145
+ phase: "Rolling",
146
+ steps: [
147
+ {
148
+ phase: "Node 1",
149
+ steps: [{ kind: "run-migration", tool: "flyway", target: "node-1" }],
150
+ },
151
+ ],
152
+ },
153
+ ],
154
+ },
155
+ filePath: "fanout-cluster.component.ts",
156
+ },
157
+ ],
158
+ ]),
159
+ };
160
+ const diagnostics = comp003.check(ctx as never);
161
+ expect(diagnostics).toHaveLength(1);
162
+ expect(diagnostics[0].component).toBe("fanout-cluster");
163
+ expect(diagnostics[0].message).toContain("run-migration");
164
+ });
165
+
166
+ it("a compensation sibling only counts within the same nested phase, not a same-named sibling phase elsewhere in the component", () => {
167
+ // Two different "Node" phases both contain a run-migration step; only the
168
+ // second one has a rollback-previous sibling. The first must still be
169
+ // flagged — object identity, not phase-name string, decides "same phase".
170
+ const [comp003] = checks.filter((c) => c.id === "COMP003");
171
+ const ctx = {
172
+ rollbackPolicies: FIXTURE_ROLLBACK_POLICIES,
173
+ components: new Map([
174
+ [
175
+ "fanout-cluster",
176
+ {
177
+ component: {
178
+ name: "fanout-cluster",
179
+ dependsOn: [],
180
+ deploy: [
181
+ { phase: "Node A", steps: [{ kind: "run-migration", tool: "flyway", target: "a" }] },
182
+ {
183
+ phase: "Node B",
184
+ steps: [
185
+ { kind: "run-migration", tool: "flyway", target: "b" },
186
+ { kind: "rollback-previous", resource: "b" },
187
+ ],
188
+ },
189
+ ],
190
+ },
191
+ filePath: "fanout-cluster.component.ts",
192
+ },
193
+ ],
194
+ ]),
195
+ };
196
+ const diagnostics = comp003.check(ctx as never);
197
+ expect(diagnostics).toHaveLength(1);
198
+ expect(diagnostics[0].message).toContain('phase "Node A"');
199
+ });
200
+ });
201
+
202
+ describe("COMP004: gate-needs-temporal", () => {
203
+ it("flags a gate step with no disable directive", async () => {
204
+ const diagnostics = await lintFixture("comp004", "fail");
205
+ const hits = diagnostics.filter((d) => d.checkId === "COMP004");
206
+ expect(hits).toHaveLength(1);
207
+ expect(hits[0].component).toBe("neo4j-cluster");
208
+ expect(hits[0].message).toContain("gate");
209
+ });
210
+
211
+ it("does not flag a component with no gate steps", async () => {
212
+ const diagnostics = await lintFixture("comp004", "pass");
213
+ const hits = diagnostics.filter((d) => d.checkId === "COMP004" && d.component !== "neo4j-cluster");
214
+ expect(hits).toHaveLength(0);
215
+ });
216
+
217
+ it("still raises the raw diagnostic for a gate with a file-level disable directive — runComponentChecks does not itself apply disable-directive filtering (that is the chant lint CLI layer's job, see ../../../cli/commands/component-lint.test.ts)", async () => {
218
+ const diagnostics = await lintFixture("comp004", "pass");
219
+ const hits = diagnostics.filter((d) => d.checkId === "COMP004" && d.component === "neo4j-cluster");
220
+ expect(hits).toHaveLength(1);
221
+ });
222
+ });
223
+
224
+ describe("COMP005: capability-kind-is-noun", () => {
225
+ it("flags a step kind named after the component itself", async () => {
226
+ const diagnostics = await lintFixture("comp005", "fail");
227
+ const hits = diagnostics.filter((d) => d.checkId === "COMP005");
228
+ expect(hits).toHaveLength(1);
229
+ expect(hits[0].message).toContain("deploy-search-service");
230
+ });
231
+
232
+ it("does not flag verb-named capability kinds", async () => {
233
+ const diagnostics = await lintFixture("comp005", "pass");
234
+ expect(diagnostics.filter((d) => d.checkId === "COMP005")).toHaveLength(0);
235
+ });
236
+
237
+ it("regression: never flags a registered verb, even when a component's name is a substring of it (e.g. a component named \"stack\")", () => {
238
+ // A component literally named "stack", "job", "image", or "host" must not
239
+ // falsely flag wait-for-stack / emr-start-job-run / publish-image /
240
+ // load-image-on-host by dash-segment substring collision. `ctx.knownKinds`
241
+ // is what `chant lint` passes for an aws-active project — the registry's
242
+ // resolved kinds, which include these lexicon leaves.
243
+ const [comp005] = checks.filter((c) => c.id === "COMP005");
244
+ const knownKinds = new Set([
245
+ "docker-build",
246
+ "wait-for-stack",
247
+ "emr-start-job-run",
248
+ "publish-image",
249
+ "load-image-on-host",
250
+ ]);
251
+ const ctx = {
252
+ knownKinds,
253
+ components: new Map([
254
+ [
255
+ "stack",
256
+ {
257
+ component: {
258
+ name: "stack",
259
+ dependsOn: [],
260
+ deploy: [{ phase: "Verify", steps: [{ kind: "wait-for-stack", stack: "stack" }] }],
261
+ },
262
+ filePath: "stack.component.ts",
263
+ },
264
+ ],
265
+ [
266
+ "job",
267
+ {
268
+ component: {
269
+ name: "job",
270
+ dependsOn: [],
271
+ deploy: [{ phase: "Submit", steps: [{ kind: "emr-start-job-run", jar: "x" }] }],
272
+ },
273
+ filePath: "job.component.ts",
274
+ },
275
+ ],
276
+ [
277
+ "image",
278
+ {
279
+ component: {
280
+ name: "image",
281
+ dependsOn: [],
282
+ build: { kind: "docker-build" },
283
+ deploy: [{ phase: "Publish", steps: [{ kind: "publish-image", from: "a", to: "b" }] }],
284
+ },
285
+ filePath: "image.component.ts",
286
+ },
287
+ ],
288
+ [
289
+ "host",
290
+ {
291
+ component: {
292
+ name: "host",
293
+ dependsOn: [],
294
+ deploy: [{ phase: "Publish", steps: [{ kind: "load-image-on-host", from: "a", host: "b" }] }],
295
+ },
296
+ filePath: "host.component.ts",
297
+ },
298
+ ],
299
+ ]),
300
+ };
301
+ expect(comp005.check(ctx as never)).toEqual([]);
302
+ });
303
+
304
+ it("falls back to the core starter set when no registry (knownKinds) was resolved — a core verb whose name overlaps a component is still not flagged", () => {
305
+ const [comp005] = checks.filter((c) => c.id === "COMP005");
306
+ const ctx = {
307
+ // no knownKinds -> fall back to core starter (includes wait-cluster-healthy)
308
+ components: new Map([
309
+ [
310
+ "cluster",
311
+ {
312
+ component: {
313
+ name: "cluster",
314
+ dependsOn: [],
315
+ deploy: [{ phase: "Verify", steps: [{ kind: "wait-cluster-healthy", size: 1 }] }],
316
+ },
317
+ filePath: "cluster.component.ts",
318
+ },
319
+ ],
320
+ ]),
321
+ };
322
+ expect(comp005.check(ctx as never)).toEqual([]);
323
+ });
324
+
325
+ it("without a resolved registry, an active-lexicon verb it can't know about IS flagged when it collides with a component name (registry-driven, not hard-coded)", () => {
326
+ const [comp005] = checks.filter((c) => c.id === "COMP005");
327
+ const ctx = {
328
+ // no knownKinds -> `wait-for-stack` is not a core verb, so for a component
329
+ // literally named "stack" it reads as a noun-collision. A real `chant lint`
330
+ // run passes knownKinds and avoids this; this documents the fallback edge.
331
+ components: new Map([
332
+ [
333
+ "stack",
334
+ {
335
+ component: {
336
+ name: "stack",
337
+ dependsOn: [],
338
+ deploy: [{ phase: "Verify", steps: [{ kind: "wait-for-stack", stack: "stack" }] }],
339
+ },
340
+ filePath: "stack.component.ts",
341
+ },
342
+ ],
343
+ ]),
344
+ };
345
+ const diagnostics = comp005.check(ctx as never);
346
+ expect(diagnostics).toHaveLength(1);
347
+ expect(diagnostics[0].checkId).toBe("COMP005");
348
+ });
349
+ });
350
+
351
+ describe("COMP006: shell-needs-reason", () => {
352
+ it('flags a "shell" step with no reason', async () => {
353
+ const diagnostics = await lintFixture("comp006", "fail");
354
+ const hits = diagnostics.filter((d) => d.checkId === "COMP006");
355
+ expect(hits).toHaveLength(1);
356
+ expect(hits[0].component).toBe("legacy-tool");
357
+ });
358
+
359
+ it('does not flag a "shell" step with a declared reason', async () => {
360
+ const diagnostics = await lintFixture("comp006", "pass");
361
+ expect(diagnostics.filter((d) => d.checkId === "COMP006")).toHaveLength(0);
362
+ });
363
+ });
364
+
365
+ describe("COMP007: composition-sprawl", () => {
366
+ it("flags two components with an identical phase/step-kind composition shape", async () => {
367
+ const diagnostics = await lintFixture("comp007", "fail");
368
+ const hits = diagnostics.filter((d) => d.checkId === "COMP007");
369
+ expect(hits).toHaveLength(2);
370
+ expect(hits.map((d) => d.component).sort()).toEqual(["inventory-table", "orders-table"]);
371
+ expect(hits[0].severity).toBe("warning");
372
+ });
373
+
374
+ it("does not flag structurally distinct components", async () => {
375
+ const diagnostics = await lintFixture("comp007", "pass");
376
+ expect(diagnostics.filter((d) => d.checkId === "COMP007")).toHaveLength(0);
377
+ });
378
+ });
@@ -0,0 +1,78 @@
1
+ /**
2
+ * COMP001: publishes-but-never-applies
3
+ *
4
+ * Flags a component that publishes an image/artifact (a `publish-image`,
5
+ * `publish-artifact`, or `load-image-on-host` step) that nothing ever
6
+ * consumes: neither an apply/submit step later in the same component (via a
7
+ * `@Phase.field` prior-step reference into the publish phase) nor any other
8
+ * component (via `@<this-component>.publish.uri|digest|key`). A publish step
9
+ * with no consumer either belongs to a producer/library component another
10
+ * component should depend on, or is dead composition — see epic #551 §7
11
+ * ("What chant adds") and §"Failure modes" (renamed/orphaned sprawl).
12
+ *
13
+ * Triggers on: a `service`-shaped component whose `publish-image` output is
14
+ * never referenced by its own Apply phase or by any sibling component.
15
+ * OK: the publish output is referenced via `@Publish.digest` (same
16
+ * component) or `@<name>.publish.uri` (a consumer component).
17
+ */
18
+
19
+ import type { ComponentCheck, ComponentCheckContext, ComponentCheckDiagnostic } from "../../component-checks";
20
+ import { collectWiringRefs, walkComponent } from "./support";
21
+
22
+ const PUBLISH_KIND = /^(publish-image|publish-artifact|load-image-on-host)$/;
23
+
24
+ export const comp001PublishesNeverAppliesRule: ComponentCheck = {
25
+ id: "COMP001",
26
+ severity: "error",
27
+ category: "correctness",
28
+ description: "A publish step's output is never consumed by an apply/submit step, in this component or any other",
29
+ check(ctx: ComponentCheckContext): ComponentCheckDiagnostic[] {
30
+ const diagnostics: ComponentCheckDiagnostic[] = [];
31
+
32
+ // Every `@<name>.publish.*` reference anywhere across every discovered
33
+ // component — a publish step in component X is "consumed" if some
34
+ // component (including X itself, via `@Phase.field`) references it.
35
+ const referencedComponentPublishes = new Set<string>();
36
+ for (const { component } of ctx.components.values()) {
37
+ const { steps } = walkComponent(component);
38
+ for (const { step } of steps) {
39
+ for (const ref of collectWiringRefs(step)) {
40
+ if (ref.kind === "component-artifact") referencedComponentPublishes.add(ref.componentName);
41
+ }
42
+ }
43
+ }
44
+
45
+ for (const [name, { component, filePath }] of ctx.components) {
46
+ const { steps } = walkComponent(component);
47
+ const publishSteps = steps.filter(({ step }) => PUBLISH_KIND.test(step.kind));
48
+ if (publishSteps.length === 0) continue;
49
+
50
+ // Consumed if: (a) some other/same component references
51
+ // `@<name>.publish.*`, or (b) a same-component prior-step reference
52
+ // points at the publish step's own phase (e.g. `@Publish.digest` used
53
+ // by a later Apply step in the same component).
54
+ if (referencedComponentPublishes.has(name)) continue;
55
+
56
+ const publishPhaseNames = new Set(publishSteps.map(({ phaseName }) => phaseName));
57
+ const consumedByPriorStepRef = steps.some(({ step }) =>
58
+ collectWiringRefs(step).some((ref) => ref.kind === "prior-step" && publishPhaseNames.has(ref.phaseName)),
59
+ );
60
+ if (consumedByPriorStepRef) continue;
61
+
62
+ diagnostics.push({
63
+ checkId: "COMP001",
64
+ severity: "error",
65
+ component: name,
66
+ file: filePath,
67
+ message:
68
+ `Component "${name}" publishes an artifact (${[...publishPhaseNames].join(", ")} phase) that is never ` +
69
+ `consumed — no apply/submit step in this component references it via "@${[...publishPhaseNames][0]}.<field>", ` +
70
+ `and no other component references "@${name}.publish.<field>". If this is a producer/library ` +
71
+ `component, a consumer should depend on it and reference its publish output; otherwise this publish step ` +
72
+ `is dead composition.`,
73
+ });
74
+ }
75
+
76
+ return diagnostics;
77
+ },
78
+ };
@@ -0,0 +1,94 @@
1
+ /**
2
+ * COMP002: dangling-wiring-ref
3
+ *
4
+ * Flags a wiring reference (`@Phase.field` or `@<component>.publish.*`,
5
+ * carried in a step's `imageRef`/`jar`/`revision`/`inputs`/any other
6
+ * capability-specific field — see ../../../components/component.ts's
7
+ * `Wiring`) that points at a phase or component that does not exist:
8
+ *
9
+ * - `@Phase.field` naming a phase not declared anywhere in this component's
10
+ * own `deploy` (top-level phase names only — a nested fan-out phase's
11
+ * name is addressed by its own name, never a parent's, matching how
12
+ * ../../../components/driver.ts's `resolveWiring` keys `phaseOutputs` by
13
+ * the phase the step ran directly under).
14
+ * - `@<component>.publish.<field>` naming a component that either is not
15
+ * discovered at all, or is discovered but not listed in this component's
16
+ * `dependsOn` — the graph only resolves an artifact reference for a
17
+ * declared dependency (composition-and-wiring.mdx#cross-component-outputs).
18
+ *
19
+ * Triggers on: `imageRef: "@Build.digest"` when no phase named "Build"
20
+ * exists, or `jar: "@some-lib.publish.uri"` when "some-lib" is undiscovered
21
+ * or missing from `dependsOn`.
22
+ * OK: `imageRef: "@Publish.digest"` when a "Publish" phase exists in the same
23
+ * component, or `jar: "@jar-lib.publish.uri"` when "jar-lib" is discovered
24
+ * and listed in `dependsOn`.
25
+ */
26
+
27
+ import type { ComponentCheck, ComponentCheckContext, ComponentCheckDiagnostic } from "../../component-checks";
28
+ import { collectWiringRefs, topLevelPhaseNames, walkComponent } from "./support";
29
+
30
+ export const comp002DanglingWiringRefRule: ComponentCheck = {
31
+ id: "COMP002",
32
+ severity: "error",
33
+ category: "correctness",
34
+ description: "A wiring reference (@Phase.field or @component.publish.*) points at a phase or component that does not exist",
35
+ check(ctx: ComponentCheckContext): ComponentCheckDiagnostic[] {
36
+ const diagnostics: ComponentCheckDiagnostic[] = [];
37
+
38
+ for (const [name, { component, filePath }] of ctx.components) {
39
+ const phaseNames = topLevelPhaseNames(component);
40
+ const dependsOn = new Set(component.dependsOn ?? []);
41
+ const { steps } = walkComponent(component);
42
+
43
+ for (const { step, phaseName } of steps) {
44
+ for (const ref of collectWiringRefs(step)) {
45
+ if (ref.kind === "prior-step") {
46
+ if (phaseNames.has(ref.phaseName)) continue;
47
+ diagnostics.push({
48
+ checkId: "COMP002",
49
+ severity: "error",
50
+ component: name,
51
+ file: filePath,
52
+ message:
53
+ `Component "${name}": step "${step.kind}" (in phase "${phaseName}") references "@${ref.phaseName}.${ref.field}", ` +
54
+ `but no phase named "${ref.phaseName}" exists in this component's "deploy". Known phases: ` +
55
+ `${[...phaseNames].join(", ") || "(none)"}.`,
56
+ });
57
+ continue;
58
+ }
59
+
60
+ // ref.kind === "component-artifact"
61
+ const target = ctx.components.get(ref.componentName);
62
+ if (!target) {
63
+ diagnostics.push({
64
+ checkId: "COMP002",
65
+ severity: "error",
66
+ component: name,
67
+ file: filePath,
68
+ message:
69
+ `Component "${name}": step "${step.kind}" (in phase "${phaseName}") references ` +
70
+ `"@${ref.componentName}.publish.${ref.field}", but no component named "${ref.componentName}" was discovered.`,
71
+ });
72
+ continue;
73
+ }
74
+
75
+ if (!dependsOn.has(ref.componentName)) {
76
+ diagnostics.push({
77
+ checkId: "COMP002",
78
+ severity: "error",
79
+ component: name,
80
+ file: filePath,
81
+ message:
82
+ `Component "${name}": step "${step.kind}" (in phase "${phaseName}") references ` +
83
+ `"@${ref.componentName}.publish.${ref.field}", but "${ref.componentName}" is not listed in this ` +
84
+ `component's "dependsOn" — the graph only resolves an artifact reference for a declared dependency. ` +
85
+ `Add "${ref.componentName}" to dependsOn.`,
86
+ });
87
+ }
88
+ }
89
+ }
90
+ }
91
+
92
+ return diagnostics;
93
+ },
94
+ };
@@ -0,0 +1,115 @@
1
+ /**
2
+ * COMP003: mutating-no-rollback
3
+ *
4
+ * Flags a mutating capability step whose family has no known native/starter
5
+ * rollback and no explicit opt-out — the "mutating capability with no
6
+ * rollback and no explicit opt-out reason" guardrail from epic #551 §7 and
7
+ * §"Failure modes".
8
+ *
9
+ * Each capability declares its rollback disposition via `rollbackPolicy`
10
+ * (../../../components/capability.ts): `"native"` (a paired `rollback`, e.g.
11
+ * `cfn-deploy`/`ecs-update-service`/`lambda-deploy`/`code-deploy`),
12
+ * `"none-by-design"` (nothing to compensate — the build/publish and wait/verify
13
+ * families, whose outputs are immutable/content-addressed or read-only), or
14
+ * `"needs-opt-out"` (a mutating verb with no native rollback and no safe undo,
15
+ * e.g. `s3-sync`/`run-migration`). This rule flags only `"needs-opt-out"` steps
16
+ * lacking an explicit opt-out. It reads the disposition from the project's
17
+ * capability registry (`ctx.rollbackPolicies`, built by `chant lint` from the
18
+ * active lexicons + core starter — the same seam COMP005 uses for
19
+ * `ctx.knownKinds`), never a hard-coded verb list, so the aws leaves'
20
+ * dispositions come from the aws lexicon that owns them.
21
+ *
22
+ * The opt-out: since the capability interface and schema are out of scope for
23
+ * this issue (`Step` already has `additionalProperties: true` — see
24
+ * component.schema.json), a component may attach a `noRollback: "<reason>"`
25
+ * string property directly on the step to declare the compensation gap is
26
+ * intentional and understood, or supply its own explicit compensation via a
27
+ * component-level `rollback` field (as the ALB/ECS pilot does with
28
+ * `rollback-previous`) or a sibling `rollback-previous`/`snapshot-before` step
29
+ * in the same phase.
30
+ *
31
+ * Triggers on: a bare `{ kind: "run-migration", ... }` step with no
32
+ * `noRollback` reason, no component-level `rollback`, and no sibling
33
+ * `rollback-previous`/`snapshot-before` step.
34
+ * OK: `{ kind: "run-migration", ..., noRollback: "forward-only migration, ..." }`,
35
+ * or a component `rollback` field, or a sibling compensation step.
36
+ */
37
+
38
+ import type { ComponentCheck, ComponentCheckContext, ComponentCheckDiagnostic } from "../../component-checks";
39
+ import { walkComponent } from "./support";
40
+
41
+ /**
42
+ * A step is flagged only when its verb's rollback disposition is
43
+ * `"needs-opt-out"` — a mutating capability with no native rollback and no safe
44
+ * undo. That disposition is the capability's own declaration (`rollbackPolicy`,
45
+ * ../../../components/capability.ts), surfaced to this rule via
46
+ * `ctx.rollbackPolicies` (built from the project's registry by `chant lint`,
47
+ * exactly like `ctx.knownKinds` for COMP005). Verbs with a native rollback
48
+ * (`cfn-deploy`, …) or nothing to compensate (build/publish/wait) are
49
+ * `"native"`/`"none-by-design"` and never flagged. When no registry was
50
+ * resolved (a direct unit test), an unknown verb defaults to
51
+ * `"none-by-design"` — the rule stays silent rather than guessing.
52
+ */
53
+ function policyFor(ctx: ComponentCheckContext, kind: string): string {
54
+ return ctx.rollbackPolicies?.get(kind) ?? "none-by-design";
55
+ }
56
+
57
+ /** A sibling step kind that itself is (or supplies) compensation — its presence in the same phase is treated as the component handling rollback explicitly, not silence. */
58
+ const COMPENSATION_SIBLING_KINDS = new Set(["rollback-previous", "snapshot-before"]);
59
+
60
+ export const comp003MutatingNoRollbackRule: ComponentCheck = {
61
+ id: "COMP003",
62
+ severity: "error",
63
+ category: "correctness",
64
+ description: "A mutating capability step has no known rollback and no explicit opt-out (noRollback reason, component rollback, or a compensation sibling step)",
65
+ check(ctx: ComponentCheckContext): ComponentCheckDiagnostic[] {
66
+ const diagnostics: ComponentCheckDiagnostic[] = [];
67
+
68
+ for (const [name, { component, filePath }] of ctx.components) {
69
+ const hasComponentRollback = Array.isArray(component.rollback) && component.rollback.length > 0;
70
+
71
+ // Walk every step (including inside nested fan-out phases and
72
+ // onFailure compensation phases — see support.ts's walkComponent) so a
73
+ // mutating step nested inside a fan-out unit (e.g. a Neo4j-style
74
+ // per-instance phase) is inspected the same as a top-level one.
75
+ const { steps } = walkComponent(component);
76
+
77
+ // Group steps by the exact Phase object they live under (identity, not
78
+ // name — two different nested phases can share a display name) so the
79
+ // "sibling in the same phase" compensation check only looks at true
80
+ // siblings, never steps from an unrelated phase of the same name.
81
+ const stepsByPhase = new Map<object, typeof steps>();
82
+ for (const walked of steps) {
83
+ const group = stepsByPhase.get(walked.phase) ?? [];
84
+ group.push(walked);
85
+ stepsByPhase.set(walked.phase, group);
86
+ }
87
+
88
+ for (const walked of steps) {
89
+ const { step, phaseName, phase } = walked;
90
+ if (policyFor(ctx, step.kind) !== "needs-opt-out") continue;
91
+
92
+ const noRollback = (step as { noRollback?: unknown }).noRollback;
93
+ const hasStepOptOut = typeof noRollback === "string" && noRollback.trim().length > 0;
94
+ if (hasStepOptOut || hasComponentRollback) continue;
95
+
96
+ const siblings = stepsByPhase.get(phase) ?? [];
97
+ const hasCompensationSibling = siblings.some((s) => COMPENSATION_SIBLING_KINDS.has(s.step.kind));
98
+ if (hasCompensationSibling) continue;
99
+
100
+ diagnostics.push({
101
+ checkId: "COMP003",
102
+ severity: "error",
103
+ component: name,
104
+ file: filePath,
105
+ message:
106
+ `Component "${name}": step "${step.kind}" (phase "${phaseName}") is a mutating capability with no ` +
107
+ `known native rollback and no explicit opt-out. Add a "noRollback: \\"<reason>\\"" property to the step, ` +
108
+ `a component-level "rollback" phase, or a sibling "rollback-previous"/"snapshot-before" step in the same phase.`,
109
+ });
110
+ }
111
+ }
112
+
113
+ return diagnostics;
114
+ },
115
+ };