@intentius/chant 0.13.1 → 0.15.1

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 (450) 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/proof.d.ts +3 -0
  4. package/dist/audit/proof.d.ts.map +1 -1
  5. package/dist/audit/report-model.d.ts +4 -1
  6. package/dist/audit/report-model.d.ts.map +1 -1
  7. package/dist/audit/report.d.ts +3 -0
  8. package/dist/audit/report.d.ts.map +1 -1
  9. package/dist/audit/rules-doc.d.ts +8 -2
  10. package/dist/audit/rules-doc.d.ts.map +1 -1
  11. package/dist/cli/commands/audit.d.ts.map +1 -1
  12. package/dist/cli/commands/doctor.d.ts.map +1 -1
  13. package/dist/cli/commands/lexicon-rolling-upgrade.d.ts +37 -0
  14. package/dist/cli/commands/lexicon-rolling-upgrade.d.ts.map +1 -0
  15. package/dist/cli/commands/lexicon-surface-diff.d.ts +48 -0
  16. package/dist/cli/commands/lexicon-surface-diff.d.ts.map +1 -0
  17. package/dist/cli/commands/lint.d.ts.map +1 -1
  18. package/dist/cli/commands/pinned-upgrade.d.ts +41 -0
  19. package/dist/cli/commands/pinned-upgrade.d.ts.map +1 -0
  20. package/dist/cli/handlers/build.d.ts.map +1 -1
  21. package/dist/cli/handlers/components.d.ts +39 -0
  22. package/dist/cli/handlers/components.d.ts.map +1 -0
  23. package/dist/cli/handlers/dev.d.ts +3 -0
  24. package/dist/cli/handlers/dev.d.ts.map +1 -1
  25. package/dist/cli/handlers/graph.d.ts +4 -2
  26. package/dist/cli/handlers/graph.d.ts.map +1 -1
  27. package/dist/cli/handlers/misc.d.ts.map +1 -1
  28. package/dist/cli/handlers/run.d.ts +49 -0
  29. package/dist/cli/handlers/run.d.ts.map +1 -1
  30. package/dist/cli/main.d.ts.map +1 -1
  31. package/dist/cli/registry.d.ts +35 -0
  32. package/dist/cli/registry.d.ts.map +1 -1
  33. package/dist/codegen/fetch.d.ts +22 -0
  34. package/dist/codegen/fetch.d.ts.map +1 -1
  35. package/dist/codegen/lexicon-regen.d.ts +110 -0
  36. package/dist/codegen/lexicon-regen.d.ts.map +1 -0
  37. package/dist/codegen/pinned-upgrade.d.ts +153 -0
  38. package/dist/codegen/pinned-upgrade.d.ts.map +1 -0
  39. package/dist/codegen/rolling-upgrade.d.ts +138 -0
  40. package/dist/codegen/rolling-upgrade.d.ts.map +1 -0
  41. package/dist/codegen/surface-snapshot.d.ts +173 -0
  42. package/dist/codegen/surface-snapshot.d.ts.map +1 -0
  43. package/dist/codegen/version-bump.d.ts +21 -0
  44. package/dist/codegen/version-bump.d.ts.map +1 -0
  45. package/dist/components/auto-release.d.ts +93 -0
  46. package/dist/components/auto-release.d.ts.map +1 -0
  47. package/dist/components/builders.d.ts +37 -0
  48. package/dist/components/builders.d.ts.map +1 -0
  49. package/dist/components/capability-plugin-loader.d.ts +111 -0
  50. package/dist/components/capability-plugin-loader.d.ts.map +1 -0
  51. package/dist/components/capability-plugin-schema.d.ts +24 -0
  52. package/dist/components/capability-plugin-schema.d.ts.map +1 -0
  53. package/dist/components/capability-plugin.d.ts +85 -0
  54. package/dist/components/capability-plugin.d.ts.map +1 -0
  55. package/dist/components/capability.d.ts +89 -0
  56. package/dist/components/capability.d.ts.map +1 -0
  57. package/dist/components/cli-support.d.ts +204 -0
  58. package/dist/components/cli-support.d.ts.map +1 -0
  59. package/dist/components/component.d.ts +179 -0
  60. package/dist/components/component.d.ts.map +1 -0
  61. package/dist/components/config-defaults.d.ts +41 -0
  62. package/dist/components/config-defaults.d.ts.map +1 -0
  63. package/dist/components/discover.d.ts +60 -0
  64. package/dist/components/discover.d.ts.map +1 -0
  65. package/dist/components/driver-output.d.ts +20 -0
  66. package/dist/components/driver-output.d.ts.map +1 -0
  67. package/dist/components/driver.d.ts +209 -0
  68. package/dist/components/driver.d.ts.map +1 -0
  69. package/dist/components/index.d.ts +22 -0
  70. package/dist/components/index.d.ts.map +1 -0
  71. package/dist/components/manifest-persistence.d.ts +79 -0
  72. package/dist/components/manifest-persistence.d.ts.map +1 -0
  73. package/dist/components/pilots/alb-ecs.pilot.d.ts +27 -0
  74. package/dist/components/pilots/alb-ecs.pilot.d.ts.map +1 -0
  75. package/dist/components/pilots/dynamodb.pilot.d.ts +22 -0
  76. package/dist/components/pilots/dynamodb.pilot.d.ts.map +1 -0
  77. package/dist/components/pilots/jar-emr.pilot.d.ts +37 -0
  78. package/dist/components/pilots/jar-emr.pilot.d.ts.map +1 -0
  79. package/dist/components/pilots/lambda.pilot.d.ts +35 -0
  80. package/dist/components/pilots/lambda.pilot.d.ts.map +1 -0
  81. package/dist/components/pilots/neo4j-fanout.pilot.d.ts +17 -0
  82. package/dist/components/pilots/neo4j-fanout.pilot.d.ts.map +1 -0
  83. package/dist/components/pilots/project.d.ts +13 -0
  84. package/dist/components/pilots/project.d.ts.map +1 -0
  85. package/dist/components/presets/ecs-fargate.d.ts +59 -0
  86. package/dist/components/presets/ecs-fargate.d.ts.map +1 -0
  87. package/dist/components/presets/index.d.ts +29 -0
  88. package/dist/components/presets/index.d.ts.map +1 -0
  89. package/dist/components/presets/lambda.d.ts +35 -0
  90. package/dist/components/presets/lambda.d.ts.map +1 -0
  91. package/dist/components/presets/single-host-compose.d.ts +43 -0
  92. package/dist/components/presets/single-host-compose.d.ts.map +1 -0
  93. package/dist/components/registry.d.ts +29 -0
  94. package/dist/components/registry.d.ts.map +1 -0
  95. package/dist/components/starter-plugin.d.ts +51 -0
  96. package/dist/components/starter-plugin.d.ts.map +1 -0
  97. package/dist/components/temporal-codegen-loader.d.ts +27 -0
  98. package/dist/components/temporal-codegen-loader.d.ts.map +1 -0
  99. package/dist/components/verbs/__tests__/mock-cloud-executor.d.ts +35 -0
  100. package/dist/components/verbs/__tests__/mock-cloud-executor.d.ts.map +1 -0
  101. package/dist/components/verbs/__tests__/mock-process-runner.d.ts +45 -0
  102. package/dist/components/verbs/__tests__/mock-process-runner.d.ts.map +1 -0
  103. package/dist/components/verbs/__tests__/mock-sbom-generator.d.ts +33 -0
  104. package/dist/components/verbs/__tests__/mock-sbom-generator.d.ts.map +1 -0
  105. package/dist/components/verbs/bom-writer.d.ts +155 -0
  106. package/dist/components/verbs/bom-writer.d.ts.map +1 -0
  107. package/dist/components/verbs/build-archive.d.ts +205 -0
  108. package/dist/components/verbs/build-archive.d.ts.map +1 -0
  109. package/dist/components/verbs/build.d.ts +150 -0
  110. package/dist/components/verbs/build.d.ts.map +1 -0
  111. package/dist/components/verbs/cloud-executor.d.ts +88 -0
  112. package/dist/components/verbs/cloud-executor.d.ts.map +1 -0
  113. package/dist/components/verbs/component-bom.d.ts +103 -0
  114. package/dist/components/verbs/component-bom.d.ts.map +1 -0
  115. package/dist/components/verbs/index.d.ts +49 -0
  116. package/dist/components/verbs/index.d.ts.map +1 -0
  117. package/dist/components/verbs/license-policy.d.ts +56 -0
  118. package/dist/components/verbs/license-policy.d.ts.map +1 -0
  119. package/dist/components/verbs/lockfile-sbom-generator.d.ts +81 -0
  120. package/dist/components/verbs/lockfile-sbom-generator.d.ts.map +1 -0
  121. package/dist/components/verbs/process-runner.d.ts +64 -0
  122. package/dist/components/verbs/process-runner.d.ts.map +1 -0
  123. package/dist/components/verbs/reproducibility.d.ts +80 -0
  124. package/dist/components/verbs/reproducibility.d.ts.map +1 -0
  125. package/dist/components/verbs/sbom-generator.d.ts +157 -0
  126. package/dist/components/verbs/sbom-generator.d.ts.map +1 -0
  127. package/dist/components/verbs/sbom.d.ts +79 -0
  128. package/dist/components/verbs/sbom.d.ts.map +1 -0
  129. package/dist/components/verbs/shell.d.ts +35 -0
  130. package/dist/components/verbs/shell.d.ts.map +1 -0
  131. package/dist/components/verbs/sign.d.ts +222 -0
  132. package/dist/components/verbs/sign.d.ts.map +1 -0
  133. package/dist/components/verbs/stub.d.ts +18 -0
  134. package/dist/components/verbs/stub.d.ts.map +1 -0
  135. package/dist/components/verbs/tool-sbom-generator.d.ts +51 -0
  136. package/dist/components/verbs/tool-sbom-generator.d.ts.map +1 -0
  137. package/dist/components/verbs/verify.d.ts +106 -0
  138. package/dist/components/verbs/verify.d.ts.map +1 -0
  139. package/dist/components/verbs/vex.d.ts +65 -0
  140. package/dist/components/verbs/vex.d.ts.map +1 -0
  141. package/dist/components/verbs/vuln-gate.d.ts +91 -0
  142. package/dist/components/verbs/vuln-gate.d.ts.map +1 -0
  143. package/dist/components/verbs/vuln-scan.d.ts +116 -0
  144. package/dist/components/verbs/vuln-scan.d.ts.map +1 -0
  145. package/dist/components/verbs/wait-verify.d.ts +85 -0
  146. package/dist/components/verbs/wait-verify.d.ts.map +1 -0
  147. package/dist/config.d.ts +205 -0
  148. package/dist/config.d.ts.map +1 -1
  149. package/dist/index.d.ts +4 -0
  150. package/dist/index.d.ts.map +1 -1
  151. package/dist/lexicon.d.ts +78 -0
  152. package/dist/lexicon.d.ts.map +1 -1
  153. package/dist/lifecycle/build-ledger-store.d.ts +121 -0
  154. package/dist/lifecycle/build-ledger-store.d.ts.map +1 -0
  155. package/dist/lifecycle/build-ledger.d.ts +225 -0
  156. package/dist/lifecycle/build-ledger.d.ts.map +1 -0
  157. package/dist/lifecycle/git.d.ts +84 -0
  158. package/dist/lifecycle/git.d.ts.map +1 -1
  159. package/dist/lifecycle/index.d.ts +5 -0
  160. package/dist/lifecycle/index.d.ts.map +1 -1
  161. package/dist/lifecycle/oras-referrer-lookup.d.ts +41 -0
  162. package/dist/lifecycle/oras-referrer-lookup.d.ts.map +1 -0
  163. package/dist/lifecycle/release-ledger.d.ts +122 -0
  164. package/dist/lifecycle/release-ledger.d.ts.map +1 -0
  165. package/dist/lifecycle/status.d.ts +150 -0
  166. package/dist/lifecycle/status.d.ts.map +1 -0
  167. package/dist/lint/component-checks.d.ts +95 -0
  168. package/dist/lint/component-checks.d.ts.map +1 -0
  169. package/dist/lint/engine.d.ts +23 -0
  170. package/dist/lint/engine.d.ts.map +1 -1
  171. package/dist/lint/presets/strict.json +8 -1
  172. package/dist/lint/rules/__fixtures__/comp/comp000-discovery-error/broken-a.component.d.ts +4 -0
  173. package/dist/lint/rules/__fixtures__/comp/comp000-discovery-error/broken-a.component.d.ts.map +1 -0
  174. package/dist/lint/rules/__fixtures__/comp/comp000-discovery-error/broken-b.component.d.ts +4 -0
  175. package/dist/lint/rules/__fixtures__/comp/comp000-discovery-error/broken-b.component.d.ts.map +1 -0
  176. package/dist/lint/rules/__fixtures__/comp/comp001/fail/orphan-lib.component.d.ts +4 -0
  177. package/dist/lint/rules/__fixtures__/comp/comp001/fail/orphan-lib.component.d.ts.map +1 -0
  178. package/dist/lint/rules/__fixtures__/comp/comp001/pass/emr-job.component.d.ts +4 -0
  179. package/dist/lint/rules/__fixtures__/comp/comp001/pass/emr-job.component.d.ts.map +1 -0
  180. package/dist/lint/rules/__fixtures__/comp/comp001/pass/jar-lib.component.d.ts +4 -0
  181. package/dist/lint/rules/__fixtures__/comp/comp001/pass/jar-lib.component.d.ts.map +1 -0
  182. package/dist/lint/rules/__fixtures__/comp/comp002/fail/search-service.component.d.ts +10 -0
  183. package/dist/lint/rules/__fixtures__/comp/comp002/fail/search-service.component.d.ts.map +1 -0
  184. package/dist/lint/rules/__fixtures__/comp/comp002/pass/jar-lib.component.d.ts +3 -0
  185. package/dist/lint/rules/__fixtures__/comp/comp002/pass/jar-lib.component.d.ts.map +1 -0
  186. package/dist/lint/rules/__fixtures__/comp/comp002/pass/search-service.component.d.ts +4 -0
  187. package/dist/lint/rules/__fixtures__/comp/comp002/pass/search-service.component.d.ts.map +1 -0
  188. package/dist/lint/rules/__fixtures__/comp/comp003/fail/migrator.component.d.ts +4 -0
  189. package/dist/lint/rules/__fixtures__/comp/comp003/fail/migrator.component.d.ts.map +1 -0
  190. package/dist/lint/rules/__fixtures__/comp/comp003/pass/migrator.component.d.ts +4 -0
  191. package/dist/lint/rules/__fixtures__/comp/comp003/pass/migrator.component.d.ts.map +1 -0
  192. package/dist/lint/rules/__fixtures__/comp/comp003/pass/orders-table.component.d.ts +4 -0
  193. package/dist/lint/rules/__fixtures__/comp/comp003/pass/orders-table.component.d.ts.map +1 -0
  194. package/dist/lint/rules/__fixtures__/comp/comp004/fail/neo4j-cluster.component.d.ts +4 -0
  195. package/dist/lint/rules/__fixtures__/comp/comp004/fail/neo4j-cluster.component.d.ts.map +1 -0
  196. package/dist/lint/rules/__fixtures__/comp/comp004/pass/neo4j-cluster.component.d.ts +4 -0
  197. package/dist/lint/rules/__fixtures__/comp/comp004/pass/neo4j-cluster.component.d.ts.map +1 -0
  198. package/dist/lint/rules/__fixtures__/comp/comp004/pass/orders-table.component.d.ts +4 -0
  199. package/dist/lint/rules/__fixtures__/comp/comp004/pass/orders-table.component.d.ts.map +1 -0
  200. package/dist/lint/rules/__fixtures__/comp/comp005/fail/search-service.component.d.ts +4 -0
  201. package/dist/lint/rules/__fixtures__/comp/comp005/fail/search-service.component.d.ts.map +1 -0
  202. package/dist/lint/rules/__fixtures__/comp/comp005/pass/search-service.component.d.ts +4 -0
  203. package/dist/lint/rules/__fixtures__/comp/comp005/pass/search-service.component.d.ts.map +1 -0
  204. package/dist/lint/rules/__fixtures__/comp/comp006/fail/legacy-tool.component.d.ts +4 -0
  205. package/dist/lint/rules/__fixtures__/comp/comp006/fail/legacy-tool.component.d.ts.map +1 -0
  206. package/dist/lint/rules/__fixtures__/comp/comp006/pass/legacy-tool.component.d.ts +4 -0
  207. package/dist/lint/rules/__fixtures__/comp/comp006/pass/legacy-tool.component.d.ts.map +1 -0
  208. package/dist/lint/rules/__fixtures__/comp/comp007/fail/inventory-table.component.d.ts +4 -0
  209. package/dist/lint/rules/__fixtures__/comp/comp007/fail/inventory-table.component.d.ts.map +1 -0
  210. package/dist/lint/rules/__fixtures__/comp/comp007/fail/orders-table.component.d.ts +4 -0
  211. package/dist/lint/rules/__fixtures__/comp/comp007/fail/orders-table.component.d.ts.map +1 -0
  212. package/dist/lint/rules/__fixtures__/comp/comp007/pass/orders-table.component.d.ts +3 -0
  213. package/dist/lint/rules/__fixtures__/comp/comp007/pass/orders-table.component.d.ts.map +1 -0
  214. package/dist/lint/rules/__fixtures__/comp/comp007/pass/search-service.component.d.ts +4 -0
  215. package/dist/lint/rules/__fixtures__/comp/comp007/pass/search-service.component.d.ts.map +1 -0
  216. package/dist/lint/rules/comp/comp001-publishes-never-applies.d.ts +20 -0
  217. package/dist/lint/rules/comp/comp001-publishes-never-applies.d.ts.map +1 -0
  218. package/dist/lint/rules/comp/comp002-dangling-wiring-ref.d.ts +28 -0
  219. package/dist/lint/rules/comp/comp002-dangling-wiring-ref.d.ts.map +1 -0
  220. package/dist/lint/rules/comp/comp003-mutating-no-rollback.d.ts +39 -0
  221. package/dist/lint/rules/comp/comp003-mutating-no-rollback.d.ts.map +1 -0
  222. package/dist/lint/rules/comp/comp004-gate-needs-temporal.d.ts +37 -0
  223. package/dist/lint/rules/comp/comp004-gate-needs-temporal.d.ts.map +1 -0
  224. package/dist/lint/rules/comp/comp005-capability-kind-is-noun.d.ts +43 -0
  225. package/dist/lint/rules/comp/comp005-capability-kind-is-noun.d.ts.map +1 -0
  226. package/dist/lint/rules/comp/comp006-shell-needs-reason.d.ts +22 -0
  227. package/dist/lint/rules/comp/comp006-shell-needs-reason.d.ts.map +1 -0
  228. package/dist/lint/rules/comp/comp007-composition-sprawl.d.ts +33 -0
  229. package/dist/lint/rules/comp/comp007-composition-sprawl.d.ts.map +1 -0
  230. package/dist/lint/rules/comp/index.d.ts +18 -0
  231. package/dist/lint/rules/comp/index.d.ts.map +1 -0
  232. package/dist/lint/rules/comp/support.d.ts +71 -0
  233. package/dist/lint/rules/comp/support.d.ts.map +1 -0
  234. package/dist/lsp/lexicon-providers.d.ts.map +1 -1
  235. package/dist/op/activity-registry.d.ts +11 -6
  236. package/dist/op/activity-registry.d.ts.map +1 -1
  237. package/dist/op/activity-runtime.d.ts +24 -0
  238. package/dist/op/activity-runtime.d.ts.map +1 -0
  239. package/dist/op/builders.d.ts +59 -0
  240. package/dist/op/builders.d.ts.map +1 -1
  241. package/dist/op/index.d.ts +2 -1
  242. package/dist/op/index.d.ts.map +1 -1
  243. package/dist/ownership.d.ts +20 -14
  244. package/dist/ownership.d.ts.map +1 -1
  245. package/package.json +14 -1
  246. package/src/audit/catalog.test.ts +74 -22
  247. package/src/audit/catalog.ts +74 -471
  248. package/src/audit/proof.ts +4 -2
  249. package/src/audit/report-html.test.ts +14 -7
  250. package/src/audit/report-model.ts +20 -9
  251. package/src/audit/report.test.ts +36 -13
  252. package/src/audit/report.ts +2 -0
  253. package/src/audit/rules-doc.test.ts +8 -6
  254. package/src/audit/rules-doc.ts +13 -5
  255. package/src/cli/commands/audit.ts +23 -18
  256. package/src/cli/commands/component-lint.test.ts +158 -0
  257. package/src/cli/commands/doctor.test.ts +116 -2
  258. package/src/cli/commands/doctor.ts +46 -0
  259. package/src/cli/commands/lexicon-rolling-upgrade.ts +198 -0
  260. package/src/cli/commands/lexicon-surface-diff.test.ts +104 -0
  261. package/src/cli/commands/lexicon-surface-diff.ts +185 -0
  262. package/src/cli/commands/lint.ts +142 -1
  263. package/src/cli/commands/pinned-upgrade.ts +204 -0
  264. package/src/cli/handlers/build.ts +58 -1
  265. package/src/cli/handlers/components.test.ts +504 -0
  266. package/src/cli/handlers/components.ts +404 -0
  267. package/src/cli/handlers/dev.ts +60 -1
  268. package/src/cli/handlers/graph.ts +47 -2
  269. package/src/cli/handlers/misc.ts +45 -1
  270. package/src/cli/handlers/run.test.ts +967 -3
  271. package/src/cli/handlers/run.ts +618 -11
  272. package/src/cli/main.test.ts +66 -0
  273. package/src/cli/main.ts +105 -2
  274. package/src/cli/registry.ts +35 -0
  275. package/src/codegen/fetch.test.ts +84 -3
  276. package/src/codegen/fetch.ts +76 -2
  277. package/src/codegen/lexicon-regen.test.ts +298 -0
  278. package/src/codegen/lexicon-regen.ts +545 -0
  279. package/src/codegen/pinned-upgrade.test.ts +399 -0
  280. package/src/codegen/pinned-upgrade.ts +423 -0
  281. package/src/codegen/rolling-upgrade.test.ts +333 -0
  282. package/src/codegen/rolling-upgrade.ts +320 -0
  283. package/src/codegen/surface-snapshot.test.ts +461 -0
  284. package/src/codegen/surface-snapshot.ts +546 -0
  285. package/src/codegen/version-bump.test.ts +47 -0
  286. package/src/codegen/version-bump.ts +39 -0
  287. package/src/components/README.md +130 -0
  288. package/src/components/SPRAWL-VALIDATION.md +181 -0
  289. package/src/components/__fixtures__/alb-ecs-service.json +51 -0
  290. package/src/components/__fixtures__/dynamodb-infra.json +32 -0
  291. package/src/components/__fixtures__/emr-job-consumer.json +26 -0
  292. package/src/components/__fixtures__/jar-lib-producer.json +16 -0
  293. package/src/components/__fixtures__/lambda-image-processor.json +28 -0
  294. package/src/components/__fixtures__/neo4j-fanout.json +34 -0
  295. package/src/components/__fixtures__/single-host-compose.json +29 -0
  296. package/src/components/auto-release.test.ts +217 -0
  297. package/src/components/auto-release.ts +216 -0
  298. package/src/components/builders.test.ts +45 -0
  299. package/src/components/builders.ts +71 -0
  300. package/src/components/capability-plugin-loader.ts +218 -0
  301. package/src/components/capability-plugin-schema.ts +56 -0
  302. package/src/components/capability-plugin.test.ts +235 -0
  303. package/src/components/capability-plugin.ts +108 -0
  304. package/src/components/capability.ts +117 -0
  305. package/src/components/cli-support.test.ts +579 -0
  306. package/src/components/cli-support.ts +481 -0
  307. package/src/components/component-schema.test.ts +265 -0
  308. package/src/components/component.schema.json +235 -0
  309. package/src/components/component.test.ts +251 -0
  310. package/src/components/component.ts +255 -0
  311. package/src/components/config-defaults.test.ts +263 -0
  312. package/src/components/config-defaults.ts +145 -0
  313. package/src/components/discover.test.ts +352 -0
  314. package/src/components/discover.ts +167 -0
  315. package/src/components/driver-output.ts +71 -0
  316. package/src/components/driver.test.ts +638 -0
  317. package/src/components/driver.ts +690 -0
  318. package/src/components/index.ts +113 -0
  319. package/src/components/manifest-persistence.test.ts +152 -0
  320. package/src/components/manifest-persistence.ts +164 -0
  321. package/src/components/pilots/README.md +33 -0
  322. package/src/components/pilots/alb-ecs.pilot.ts +63 -0
  323. package/src/components/pilots/dynamodb.pilot.ts +59 -0
  324. package/src/components/pilots/jar-emr.pilot.ts +69 -0
  325. package/src/components/pilots/lambda.pilot.ts +55 -0
  326. package/src/components/pilots/neo4j-fanout.pilot.ts +92 -0
  327. package/src/components/pilots/pilots-lint-clean.test.ts +78 -0
  328. package/src/components/pilots/pilots.test.ts +126 -0
  329. package/src/components/pilots/project.ts +13 -0
  330. package/src/components/presets/comp007-reduction.test.ts +122 -0
  331. package/src/components/presets/ecs-fargate.test.ts +84 -0
  332. package/src/components/presets/ecs-fargate.ts +103 -0
  333. package/src/components/presets/index.ts +32 -0
  334. package/src/components/presets/lambda.test.ts +35 -0
  335. package/src/components/presets/lambda.ts +53 -0
  336. package/src/components/presets/single-host-compose.test.ts +65 -0
  337. package/src/components/presets/single-host-compose.ts +64 -0
  338. package/src/components/registry.test.ts +112 -0
  339. package/src/components/registry.ts +37 -0
  340. package/src/components/starter-plugin.ts +105 -0
  341. package/src/components/temporal-codegen-loader.ts +41 -0
  342. package/src/components/verbs/__fixtures__/package-lock.json +45 -0
  343. package/src/components/verbs/__fixtures__/pom.xml +37 -0
  344. package/src/components/verbs/__fixtures__/schemas/cyclonedx-1.5.schema.json +3800 -0
  345. package/src/components/verbs/__fixtures__/schemas/cyclonedx-spdx-license.schema.json +621 -0
  346. package/src/components/verbs/__fixtures__/schemas/jsf-0.82.schema.json +240 -0
  347. package/src/components/verbs/__fixtures__/schemas/spdx-2.3.schema.json +740 -0
  348. package/src/components/verbs/__tests__/mock-cloud-executor.ts +102 -0
  349. package/src/components/verbs/__tests__/mock-process-runner.ts +79 -0
  350. package/src/components/verbs/__tests__/mock-sbom-generator.ts +85 -0
  351. package/src/components/verbs/bom-writer.test.ts +331 -0
  352. package/src/components/verbs/bom-writer.ts +386 -0
  353. package/src/components/verbs/build-archive.test.ts +251 -0
  354. package/src/components/verbs/build-archive.ts +296 -0
  355. package/src/components/verbs/build.test.ts +222 -0
  356. package/src/components/verbs/build.ts +252 -0
  357. package/src/components/verbs/cloud-executor.ts +181 -0
  358. package/src/components/verbs/component-bom.test.ts +219 -0
  359. package/src/components/verbs/component-bom.ts +189 -0
  360. package/src/components/verbs/index.ts +49 -0
  361. package/src/components/verbs/license-policy.ts +115 -0
  362. package/src/components/verbs/lockfile-sbom-generator.test.ts +195 -0
  363. package/src/components/verbs/lockfile-sbom-generator.ts +236 -0
  364. package/src/components/verbs/process-runner.test.ts +79 -0
  365. package/src/components/verbs/process-runner.ts +113 -0
  366. package/src/components/verbs/reproducibility.test.ts +24 -0
  367. package/src/components/verbs/reproducibility.ts +100 -0
  368. package/src/components/verbs/sbom-generator.test.ts +96 -0
  369. package/src/components/verbs/sbom-generator.ts +209 -0
  370. package/src/components/verbs/sbom.test.ts +184 -0
  371. package/src/components/verbs/sbom.ts +152 -0
  372. package/src/components/verbs/shell.test.ts +36 -0
  373. package/src/components/verbs/shell.ts +51 -0
  374. package/src/components/verbs/sign.test.ts +210 -0
  375. package/src/components/verbs/sign.ts +350 -0
  376. package/src/components/verbs/stub.ts +34 -0
  377. package/src/components/verbs/tool-sbom-generator.test.ts +194 -0
  378. package/src/components/verbs/tool-sbom-generator.ts +192 -0
  379. package/src/components/verbs/verify.test.ts +159 -0
  380. package/src/components/verbs/verify.ts +179 -0
  381. package/src/components/verbs/vex.ts +182 -0
  382. package/src/components/verbs/vuln-gate.test.ts +201 -0
  383. package/src/components/verbs/vuln-gate.ts +170 -0
  384. package/src/components/verbs/vuln-scan.test.ts +145 -0
  385. package/src/components/verbs/vuln-scan.ts +266 -0
  386. package/src/components/verbs/wait-verify.test.ts +111 -0
  387. package/src/components/verbs/wait-verify.ts +171 -0
  388. package/src/config.test.ts +74 -1
  389. package/src/config.ts +219 -0
  390. package/src/index.ts +32 -0
  391. package/src/lexicon.ts +88 -0
  392. package/src/lifecycle/build-ledger-store.test.ts +217 -0
  393. package/src/lifecycle/build-ledger-store.ts +165 -0
  394. package/src/lifecycle/build-ledger.test.ts +349 -0
  395. package/src/lifecycle/build-ledger.ts +305 -0
  396. package/src/lifecycle/git.test.ts +118 -0
  397. package/src/lifecycle/git.ts +149 -14
  398. package/src/lifecycle/index.ts +5 -0
  399. package/src/lifecycle/oras-referrer-lookup.test.ts +116 -0
  400. package/src/lifecycle/oras-referrer-lookup.ts +110 -0
  401. package/src/lifecycle/release-ledger.test.ts +209 -0
  402. package/src/lifecycle/release-ledger.ts +185 -0
  403. package/src/lifecycle/status.test.ts +330 -0
  404. package/src/lifecycle/status.ts +278 -0
  405. package/src/lint/component-checks.ts +148 -0
  406. package/src/lint/config.test.ts +21 -0
  407. package/src/lint/engine.ts +11 -2
  408. package/src/lint/presets/relaxed.json +8 -1
  409. package/src/lint/presets/strict.json +8 -1
  410. package/src/lint/rules/__fixtures__/comp/comp000-discovery-error/broken-a.component.ts +9 -0
  411. package/src/lint/rules/__fixtures__/comp/comp000-discovery-error/broken-b.component.ts +9 -0
  412. package/src/lint/rules/__fixtures__/comp/comp001/fail/orphan-lib.component.ts +11 -0
  413. package/src/lint/rules/__fixtures__/comp/comp001/pass/emr-job.component.ts +13 -0
  414. package/src/lint/rules/__fixtures__/comp/comp001/pass/jar-lib.component.ts +11 -0
  415. package/src/lint/rules/__fixtures__/comp/comp002/fail/search-service.component.ts +27 -0
  416. package/src/lint/rules/__fixtures__/comp/comp002/pass/jar-lib.component.ts +10 -0
  417. package/src/lint/rules/__fixtures__/comp/comp002/pass/search-service.component.ts +23 -0
  418. package/src/lint/rules/__fixtures__/comp/comp003/fail/migrator.component.ts +10 -0
  419. package/src/lint/rules/__fixtures__/comp/comp003/pass/migrator.component.ts +19 -0
  420. package/src/lint/rules/__fixtures__/comp/comp003/pass/orders-table.component.ts +10 -0
  421. package/src/lint/rules/__fixtures__/comp/comp004/fail/neo4j-cluster.component.ts +20 -0
  422. package/src/lint/rules/__fixtures__/comp/comp004/pass/neo4j-cluster.component.ts +23 -0
  423. package/src/lint/rules/__fixtures__/comp/comp004/pass/orders-table.component.ts +10 -0
  424. package/src/lint/rules/__fixtures__/comp/comp005/fail/search-service.component.ts +14 -0
  425. package/src/lint/rules/__fixtures__/comp/comp005/pass/search-service.component.ts +18 -0
  426. package/src/lint/rules/__fixtures__/comp/comp006/fail/legacy-tool.component.ts +10 -0
  427. package/src/lint/rules/__fixtures__/comp/comp006/pass/legacy-tool.component.ts +14 -0
  428. package/src/lint/rules/__fixtures__/comp/comp007/fail/inventory-table.component.ts +13 -0
  429. package/src/lint/rules/__fixtures__/comp/comp007/fail/orders-table.component.ts +13 -0
  430. package/src/lint/rules/__fixtures__/comp/comp007/pass/orders-table.component.ts +12 -0
  431. package/src/lint/rules/__fixtures__/comp/comp007/pass/search-service.component.ts +18 -0
  432. package/src/lint/rules/comp/comp.test.ts +378 -0
  433. package/src/lint/rules/comp/comp001-publishes-never-applies.ts +78 -0
  434. package/src/lint/rules/comp/comp002-dangling-wiring-ref.ts +94 -0
  435. package/src/lint/rules/comp/comp003-mutating-no-rollback.ts +115 -0
  436. package/src/lint/rules/comp/comp004-gate-needs-temporal.ts +66 -0
  437. package/src/lint/rules/comp/comp005-capability-kind-is-noun.ts +113 -0
  438. package/src/lint/rules/comp/comp006-shell-needs-reason.ts +55 -0
  439. package/src/lint/rules/comp/comp007-composition-sprawl.ts +71 -0
  440. package/src/lint/rules/comp/index.ts +37 -0
  441. package/src/lint/rules/comp/support.ts +173 -0
  442. package/src/lsp/lexicon-providers.test.ts +55 -0
  443. package/src/lsp/lexicon-providers.ts +13 -3
  444. package/src/op/activity-registry.test.ts +38 -0
  445. package/src/op/activity-registry.ts +31 -12
  446. package/src/op/activity-runtime.ts +73 -0
  447. package/src/op/builders.ts +99 -0
  448. package/src/op/index.ts +3 -1
  449. package/src/ownership.test.ts +24 -26
  450. package/src/ownership.ts +26 -38
@@ -0,0 +1,218 @@
1
+ /**
2
+ * Discovery + loading for capability plugins — the capability-side analogue
3
+ * of ../cli/plugins.ts's `loadPlugin`/`loadPlugins` for lexicons.
4
+ *
5
+ * Naming convention mirrors lexicons exactly, substituting "capability" for
6
+ * "lexicon": a capability plugin package is
7
+ * `@intentius/chant-capability-<name>` and is expected to export a value
8
+ * satisfying `CapabilityPlugin` (./capability-plugin.ts), detected at
9
+ * runtime with `isCapabilityPlugin` — the same dynamic-import + shape-guard
10
+ * strategy `loadPlugin` uses for `@intentius/chant-lexicon-<name>`.
11
+ *
12
+ * `buildCapabilityRegistry` is the Phase 2 replacement for Phase 1's
13
+ * `createCapabilityRegistry` (./registry.ts): it always loads the built-in
14
+ * `starterCapabilityPlugin` (./starter-plugin.ts) first — preserving every
15
+ * Phase 1 kind with zero behavior change — then loads any additional
16
+ * requested plugin packages on top, registering every capability they
17
+ * contribute into one `CapabilityRegistry` the driver resolves verbs
18
+ * through. Duplicate `kind` registration across plugins throws (via
19
+ * `CapabilityRegistry.register`'s existing guard), the same hard-conflict
20
+ * discipline `checkConflicts` (../cli/conflict-check.ts) applies to
21
+ * duplicate lexicon rule IDs.
22
+ */
23
+
24
+ import { CapabilityRegistry } from "./capability";
25
+ import type { CapabilityPlugin } from "./capability-plugin";
26
+ import { isCapabilityPlugin } from "./capability-plugin";
27
+ import { starterCapabilityPlugin } from "./starter-plugin";
28
+
29
+ /** Thrown when a dynamically-imported capability package does not satisfy `CapabilityPlugin` — the "malformed capability package" case #559 asks doctor/validation to detect. */
30
+ export class MalformedCapabilityPluginError extends Error {
31
+ constructor(
32
+ public readonly packageName: string,
33
+ reason: string,
34
+ ) {
35
+ super(`capability plugin package "${packageName}" is malformed: ${reason}`);
36
+ this.name = "MalformedCapabilityPluginError";
37
+ }
38
+ }
39
+
40
+ /** Thrown when two loaded capability plugins register the same `kind` — mirrors the lexicon loader's hard rule-ID conflict. */
41
+ export class DuplicateCapabilityKindError extends Error {
42
+ constructor(
43
+ public readonly kind: string,
44
+ public readonly plugins: string[],
45
+ ) {
46
+ super(`capability "${kind}" is registered by more than one plugin: ${plugins.join(", ")}`);
47
+ this.name = "DuplicateCapabilityKindError";
48
+ }
49
+ }
50
+
51
+ /**
52
+ * Dynamically import and validate a single capability plugin package by
53
+ * name. Mirrors ../cli/plugins.ts's `loadPlugin` for lexicons: imports
54
+ * `@intentius/chant-capability-{name}`, scans its exports for a value
55
+ * satisfying `isCapabilityPlugin`, and throws a descriptive
56
+ * `MalformedCapabilityPluginError` if the package exists but exports nothing
57
+ * usable (the malformed-package case), or the original import error if the
58
+ * package cannot be found at all.
59
+ */
60
+ export async function loadCapabilityPlugin(name: string): Promise<CapabilityPlugin> {
61
+ const packageName = `@intentius/chant-capability-${name}`;
62
+ let mod: Record<string, unknown>;
63
+ try {
64
+ mod = (await import(packageName)) as Record<string, unknown>;
65
+ } catch (err) {
66
+ throw new Error(
67
+ `capability plugin package "${packageName}" could not be loaded: ${err instanceof Error ? err.message : String(err)}`,
68
+ );
69
+ }
70
+
71
+ for (const value of Object.values(mod)) {
72
+ if (isCapabilityPlugin(value)) {
73
+ return value;
74
+ }
75
+ }
76
+
77
+ throw new MalformedCapabilityPluginError(
78
+ packageName,
79
+ `does not export a CapabilityPlugin (expected an export with string "name", string "version", and a "capabilities" function)`,
80
+ );
81
+ }
82
+
83
+ /**
84
+ * Load and initialize multiple named capability plugins, in order. Calls
85
+ * `init()` on each plugin if present — mirrors `loadPlugins`'s lexicon
86
+ * `init()` hook.
87
+ */
88
+ export async function loadCapabilityPlugins(names: string[]): Promise<CapabilityPlugin[]> {
89
+ const plugins: CapabilityPlugin[] = [];
90
+ for (const name of names) {
91
+ const plugin = await loadCapabilityPlugin(name);
92
+ if (plugin.init) {
93
+ await plugin.init();
94
+ }
95
+ plugins.push(plugin);
96
+ }
97
+ return plugins;
98
+ }
99
+
100
+ /**
101
+ * Load the capability plugin a *lexicon* package contributes, if any. A cloud
102
+ * lexicon (`@intentius/chant-lexicon-<name>`) is the natural home for that
103
+ * cloud's operational leaves (`cfn-deploy`, `emr-*`, …) — the same seam that
104
+ * already carries the lexicon's synthesis, `describeResources`, and ownership
105
+ * markers (docs/components/cloud-boundary). So when a project declares
106
+ * `lexicons: ["aws"]`, its aws leaves come along automatically: this imports
107
+ * the lexicon package and returns the first export satisfying
108
+ * `isCapabilityPlugin`.
109
+ *
110
+ * Tolerant by design — a lexicon that contributes *no* capabilities (most do
111
+ * not yet) simply returns `null` rather than throwing, unlike
112
+ * `loadCapabilityPlugin`'s strict `@intentius/chant-capability-<name>` contract
113
+ * where the package's whole reason to exist is the plugin.
114
+ */
115
+ export async function loadCapabilityPluginFromLexicon(name: string): Promise<CapabilityPlugin | null> {
116
+ const packageName = `@intentius/chant-lexicon-${name}`;
117
+ let mod: Record<string, unknown>;
118
+ try {
119
+ mod = (await import(packageName)) as Record<string, unknown>;
120
+ } catch {
121
+ return null; // lexicon not installed / not resolvable here — nothing to contribute.
122
+ }
123
+ for (const value of Object.values(mod)) {
124
+ if (isCapabilityPlugin(value)) return value;
125
+ }
126
+ return null; // lexicon present but contributes no capability plugin.
127
+ }
128
+
129
+ /** Load the capability plugins contributed by the given lexicons (skipping those that contribute none), initializing each. */
130
+ export async function loadCapabilityPluginsFromLexicons(names: string[]): Promise<CapabilityPlugin[]> {
131
+ const plugins: CapabilityPlugin[] = [];
132
+ for (const name of names) {
133
+ const plugin = await loadCapabilityPluginFromLexicon(name);
134
+ if (!plugin) continue;
135
+ if (plugin.init) await plugin.init();
136
+ plugins.push(plugin);
137
+ }
138
+ return plugins;
139
+ }
140
+
141
+ /**
142
+ * Register every capability every plugin contributes into one registry, in
143
+ * plugin order. Throws `DuplicateCapabilityKindError` if two plugins
144
+ * register the same `kind` (via a pre-check, so the error names every
145
+ * offending plugin rather than surfacing only the second registration
146
+ * attempt's generic `CapabilityRegistry.register` message).
147
+ */
148
+ export function registerCapabilityPlugins(
149
+ registry: CapabilityRegistry,
150
+ plugins: CapabilityPlugin[],
151
+ ): CapabilityRegistry {
152
+ const owners = new Map<string, string[]>();
153
+ for (const plugin of plugins) {
154
+ for (const capability of plugin.capabilities()) {
155
+ const existing = owners.get(capability.kind) ?? [];
156
+ existing.push(plugin.name);
157
+ owners.set(capability.kind, existing);
158
+ }
159
+ }
160
+ for (const [kind, pluginNames] of owners) {
161
+ if (pluginNames.length > 1) {
162
+ throw new DuplicateCapabilityKindError(kind, pluginNames);
163
+ }
164
+ }
165
+
166
+ for (const plugin of plugins) {
167
+ for (const capability of plugin.capabilities()) {
168
+ registry.register(capability);
169
+ }
170
+ }
171
+ return registry;
172
+ }
173
+
174
+ export interface BuildCapabilityRegistryOptions {
175
+ /**
176
+ * Additional capability plugin package names to load and register on top
177
+ * of the built-in starter plugin (e.g. third-party/cloud-specific
178
+ * capability packages `@intentius/chant-capability-<name>`). Defaults to
179
+ * none — Phase 1 behavior (just the starter set) is preserved when omitted.
180
+ */
181
+ plugins?: string[];
182
+ /**
183
+ * Lexicon names (from `chant.config.ts`'s `lexicons`) whose packages may
184
+ * *also* contribute a capability plugin — the primary way a cloud's
185
+ * operational leaves are loaded (e.g. `["aws"]` pulls the aws lexicon's
186
+ * `cfn-deploy`/`emr-*`/… leaves). Lexicons contributing no capabilities are
187
+ * silently skipped; see `loadCapabilityPluginsFromLexicons`.
188
+ */
189
+ lexicons?: string[];
190
+ /**
191
+ * Skip loading the built-in starter plugin. Only meaningful for tests that
192
+ * want a registry seeded purely from explicit `plugins`; real callers
193
+ * should leave this unset so the Phase 1 verb set is always present.
194
+ */
195
+ includeStarter?: boolean;
196
+ }
197
+
198
+ /**
199
+ * Build a `CapabilityRegistry` through the Phase 2 plugin contract: always
200
+ * registers the built-in `starterCapabilityPlugin` first (preserving every
201
+ * Phase 1 `kind` — see ./starter-plugin.ts), then loads and registers any
202
+ * additional named plugin packages. This is the Phase 2 entry point;
203
+ * `createCapabilityRegistry` (./registry.ts) remains available and now
204
+ * delegates to this same starter plugin, so existing Phase 1 callers (and
205
+ * the pilots) are unaffected.
206
+ */
207
+ export async function buildCapabilityRegistry(
208
+ options: BuildCapabilityRegistryOptions = {},
209
+ ): Promise<CapabilityRegistry> {
210
+ const registry = new CapabilityRegistry();
211
+ const plugins: CapabilityPlugin[] = [];
212
+ if (options.includeStarter !== false) {
213
+ plugins.push(starterCapabilityPlugin);
214
+ }
215
+ plugins.push(...(await loadCapabilityPluginsFromLexicons(options.lexicons ?? [])));
216
+ plugins.push(...(await loadCapabilityPlugins(options.plugins ?? [])));
217
+ return registerCapabilityPlugins(registry, plugins);
218
+ }
@@ -0,0 +1,56 @@
1
+ /**
2
+ * Zod schema + validation for `CapabilityManifest` (./capability-plugin.ts) —
3
+ * the capability-side analogue of ../lexicon-schema.ts's
4
+ * `LexiconManifestSchema`/`validateManifest`. Used to detect a malformed
5
+ * capability package's manifest (#559 acceptance criteria: "Doctor/validation
6
+ * covers a malformed capability package") with the same descriptive-error
7
+ * shape lexicon manifest validation uses.
8
+ */
9
+
10
+ import { z } from "zod";
11
+ import type { CapabilityManifest } from "./capability-plugin";
12
+
13
+ const semverRegex = /^\d+\.\d+\.\d+(-[a-zA-Z0-9.]+)?(\+[a-zA-Z0-9.]+)?$/;
14
+
15
+ export const CapabilityManifestSchema = z.object({
16
+ name: z.string().min(1, "manifest name must not be empty"),
17
+ version: z.string().regex(semverRegex, "version must be valid semver (X.Y.Z)"),
18
+ chantVersion: z.string().optional(),
19
+ kinds: z.array(z.string().min(1)).optional(),
20
+ });
21
+
22
+ export type CapabilityManifestParsed = z.infer<typeof CapabilityManifestSchema>;
23
+
24
+ /**
25
+ * Validate a capability manifest from unknown input (object or JSON string).
26
+ * Throws a descriptive error on invalid input — mirrors
27
+ * ../lexicon-schema.ts's `validateManifest`.
28
+ */
29
+ export function validateCapabilityManifest(data: unknown): CapabilityManifest {
30
+ if (data === null || data === undefined) {
31
+ throw new Error("capability manifest data is empty");
32
+ }
33
+
34
+ if (typeof data === "string") {
35
+ try {
36
+ data = JSON.parse(data);
37
+ } catch (err) {
38
+ const msg = err instanceof SyntaxError ? err.message : String(err);
39
+ throw new Error(`invalid JSON in capability manifest: ${msg}`);
40
+ }
41
+ }
42
+
43
+ if (typeof data !== "object" || data === null || Array.isArray(data)) {
44
+ throw new Error("capability manifest must be a JSON object");
45
+ }
46
+
47
+ const result = CapabilityManifestSchema.safeParse(data);
48
+ if (!result.success) {
49
+ const issue = result.error.issues[0];
50
+ const path = issue.path.length > 0 ? issue.path.join(".") : undefined;
51
+ const prefix = path ? `capability manifest field ${path}` : "capability manifest";
52
+ throw new Error(`${prefix}: ${issue.message}`);
53
+ }
54
+
55
+ return result.data as CapabilityManifest;
56
+ }
@@ -0,0 +1,235 @@
1
+ /**
2
+ * Tests for the Phase 2 capability plugin contract (#559, epic #551):
3
+ * `isCapabilityPlugin` (./capability-plugin.ts), manifest validation
4
+ * (./capability-plugin-schema.ts), and plugin loading/registration/registry
5
+ * building (./capability-plugin-loader.ts) — mirroring how
6
+ * ../lexicon.test-equivalent coverage (../cli/plugins.test.ts,
7
+ * ../lexicon-schema.test.ts) validates the lexicon plugin contract.
8
+ */
9
+
10
+ import { describe, expect, test } from "vitest";
11
+ import { isCapabilityPlugin, type CapabilityPlugin } from "./capability-plugin";
12
+ import { validateCapabilityManifest, CapabilityManifestSchema } from "./capability-plugin-schema";
13
+ import {
14
+ loadCapabilityPlugin,
15
+ loadCapabilityPluginFromLexicon,
16
+ loadCapabilityPluginsFromLexicons,
17
+ registerCapabilityPlugins,
18
+ buildCapabilityRegistry,
19
+ MalformedCapabilityPluginError,
20
+ DuplicateCapabilityKindError,
21
+ } from "./capability-plugin-loader";
22
+ import { CapabilityRegistry } from "./capability";
23
+ import { STARTER_VERB_FAMILIES } from "./registry";
24
+ import { starterCapabilityPlugin } from "./starter-plugin";
25
+
26
+ const ALL_STARTER_KINDS = Object.values(STARTER_VERB_FAMILIES).flat();
27
+
28
+ // A trivial well-formed plugin used across several tests below.
29
+ function fakePlugin(name: string, kinds: string[]): CapabilityPlugin {
30
+ return {
31
+ name,
32
+ version: "1.0.0",
33
+ capabilities: () => kinds.map((kind) => ({ kind, run: async () => ({}) })),
34
+ };
35
+ }
36
+
37
+ describe("isCapabilityPlugin", () => {
38
+ test("accepts a well-formed plugin", () => {
39
+ expect(isCapabilityPlugin(fakePlugin("demo", ["demo-verb"]))).toBe(true);
40
+ });
41
+
42
+ test("accepts the real starter plugin", () => {
43
+ expect(isCapabilityPlugin(starterCapabilityPlugin)).toBe(true);
44
+ });
45
+
46
+ test("rejects null/non-object", () => {
47
+ expect(isCapabilityPlugin(null)).toBe(false);
48
+ expect(isCapabilityPlugin(undefined)).toBe(false);
49
+ expect(isCapabilityPlugin("a string")).toBe(false);
50
+ expect(isCapabilityPlugin(42)).toBe(false);
51
+ });
52
+
53
+ test("rejects a value missing name", () => {
54
+ expect(isCapabilityPlugin({ version: "1.0.0", capabilities: () => [] })).toBe(false);
55
+ });
56
+
57
+ test("rejects a value missing version", () => {
58
+ expect(isCapabilityPlugin({ name: "demo", capabilities: () => [] })).toBe(false);
59
+ });
60
+
61
+ test("rejects a value whose capabilities is not a function (malformed package)", () => {
62
+ expect(isCapabilityPlugin({ name: "demo", version: "1.0.0", capabilities: [] })).toBe(false);
63
+ });
64
+
65
+ test("rejects a value with no capabilities field at all (malformed package)", () => {
66
+ expect(isCapabilityPlugin({ name: "demo", version: "1.0.0" })).toBe(false);
67
+ });
68
+
69
+ test("rejects a value whose name/version are the wrong type", () => {
70
+ expect(isCapabilityPlugin({ name: 123, version: "1.0.0", capabilities: () => [] })).toBe(false);
71
+ expect(isCapabilityPlugin({ name: "demo", version: 1, capabilities: () => [] })).toBe(false);
72
+ });
73
+ });
74
+
75
+ describe("validateCapabilityManifest", () => {
76
+ test("parses a valid manifest object", () => {
77
+ const m = validateCapabilityManifest({
78
+ name: "aws",
79
+ version: "1.0.0",
80
+ chantVersion: ">=0.1.0",
81
+ kinds: ["cfn-deploy", "ecs-update-service"],
82
+ });
83
+ expect(m.name).toBe("aws");
84
+ expect(m.version).toBe("1.0.0");
85
+ expect(m.chantVersion).toBe(">=0.1.0");
86
+ expect(m.kinds).toEqual(["cfn-deploy", "ecs-update-service"]);
87
+ });
88
+
89
+ test("parses a valid JSON string", () => {
90
+ const m = validateCapabilityManifest(JSON.stringify({ name: "gcp", version: "0.1.0" }));
91
+ expect(m.name).toBe("gcp");
92
+ expect(m.version).toBe("0.1.0");
93
+ });
94
+
95
+ test("optional fields can be omitted", () => {
96
+ const m = validateCapabilityManifest({ name: "gcp", version: "2.0.0" });
97
+ expect(m.chantVersion).toBeUndefined();
98
+ expect(m.kinds).toBeUndefined();
99
+ });
100
+
101
+ test("rejects empty input", () => {
102
+ expect(() => validateCapabilityManifest(null)).toThrow(/empty/);
103
+ expect(() => validateCapabilityManifest(undefined)).toThrow(/empty/);
104
+ });
105
+
106
+ test("rejects invalid JSON string (malformed package manifest)", () => {
107
+ expect(() => validateCapabilityManifest("not valid json{{{")).toThrow(/invalid JSON/);
108
+ });
109
+
110
+ test("rejects a non-object (malformed package manifest)", () => {
111
+ expect(() => validateCapabilityManifest("just a string")).toThrow(/invalid JSON|must be a JSON object/);
112
+ expect(() => validateCapabilityManifest(["array", "not", "object"])).toThrow(/must be a JSON object/);
113
+ });
114
+
115
+ test("rejects a manifest missing name", () => {
116
+ expect(() => validateCapabilityManifest({ version: "1.0.0" })).toThrow(/name/);
117
+ });
118
+
119
+ test("rejects a manifest with an empty name", () => {
120
+ expect(() => validateCapabilityManifest({ name: "", version: "1.0.0" })).toThrow(/must not be empty/);
121
+ });
122
+
123
+ test("rejects a manifest with a non-semver version (malformed package)", () => {
124
+ expect(() => validateCapabilityManifest({ name: "aws", version: "not-a-version" })).toThrow(/semver/);
125
+ });
126
+
127
+ test("rejects a manifest with an empty kind entry", () => {
128
+ const result = CapabilityManifestSchema.safeParse({ name: "aws", version: "1.0.0", kinds: [""] });
129
+ expect(result.success).toBe(false);
130
+ });
131
+ });
132
+
133
+ describe("loadCapabilityPlugin", () => {
134
+ test("throws a descriptive error for a package that cannot be found", async () => {
135
+ await expect(loadCapabilityPlugin("nonexistent-does-not-exist")).rejects.toThrow(
136
+ /could not be loaded/,
137
+ );
138
+ });
139
+ });
140
+
141
+ describe("registerCapabilityPlugins", () => {
142
+ test("registers every capability every plugin contributes", () => {
143
+ const registry = new CapabilityRegistry();
144
+ registerCapabilityPlugins(registry, [fakePlugin("demo-a", ["verb-a", "verb-b"]), fakePlugin("demo-b", ["verb-c"])]);
145
+ expect(registry.has("verb-a")).toBe(true);
146
+ expect(registry.has("verb-b")).toBe(true);
147
+ expect(registry.has("verb-c")).toBe(true);
148
+ });
149
+
150
+ test("throws DuplicateCapabilityKindError when two plugins register the same kind (malformed/conflicting package set)", () => {
151
+ const registry = new CapabilityRegistry();
152
+ expect(() =>
153
+ registerCapabilityPlugins(registry, [fakePlugin("demo-a", ["shared-verb"]), fakePlugin("demo-b", ["shared-verb"])]),
154
+ ).toThrow(DuplicateCapabilityKindError);
155
+ });
156
+
157
+ test("DuplicateCapabilityKindError names every offending plugin", () => {
158
+ const registry = new CapabilityRegistry();
159
+ try {
160
+ registerCapabilityPlugins(registry, [fakePlugin("demo-a", ["shared-verb"]), fakePlugin("demo-b", ["shared-verb"])]);
161
+ expect.unreachable();
162
+ } catch (err) {
163
+ expect(err).toBeInstanceOf(DuplicateCapabilityKindError);
164
+ const dup = err as DuplicateCapabilityKindError;
165
+ expect(dup.kind).toBe("shared-verb");
166
+ expect(dup.plugins).toEqual(["demo-a", "demo-b"]);
167
+ }
168
+ });
169
+
170
+ test("a duplicate-kind conflict leaves the registry unmodified (all-or-nothing)", () => {
171
+ const registry = new CapabilityRegistry();
172
+ try {
173
+ registerCapabilityPlugins(registry, [fakePlugin("demo-a", ["shared-verb"]), fakePlugin("demo-b", ["shared-verb"])]);
174
+ } catch {
175
+ /* expected */
176
+ }
177
+ expect(registry.has("shared-verb")).toBe(false);
178
+ });
179
+ });
180
+
181
+ describe("buildCapabilityRegistry (Phase 2 entry point)", () => {
182
+ test("preserves every Phase 1 starter kind with no behavior change", async () => {
183
+ const registry = await buildCapabilityRegistry();
184
+ for (const kind of ALL_STARTER_KINDS) {
185
+ expect(registry.has(kind)).toBe(true);
186
+ }
187
+ expect(registry.kinds()).toEqual([...ALL_STARTER_KINDS].sort());
188
+ });
189
+
190
+ test("layers an explicitly-provided plugin on top of the starter set", async () => {
191
+ // buildCapabilityRegistry's `plugins` option loads packages by name via
192
+ // loadCapabilityPlugin; exercise the registration/layering behavior
193
+ // directly through registerCapabilityPlugins + the starter plugin,
194
+ // since a real third-party package isn't installed in this repo.
195
+ const registry = new CapabilityRegistry();
196
+ registerCapabilityPlugins(registry, [starterCapabilityPlugin, fakePlugin("acme", ["acme-verb"])]);
197
+ for (const kind of ALL_STARTER_KINDS) {
198
+ expect(registry.has(kind)).toBe(true);
199
+ }
200
+ expect(registry.has("acme-verb")).toBe(true);
201
+ });
202
+
203
+ test("includeStarter: false omits the starter plugin", async () => {
204
+ const registry = await buildCapabilityRegistry({ includeStarter: false, plugins: [] });
205
+ expect(registry.kinds()).toEqual([]);
206
+ });
207
+
208
+ test("a lexicon that contributes no capability plugin is silently skipped (starter set intact)", async () => {
209
+ // `loadCapabilityPluginsFromLexicons` is tolerant: an unresolvable or
210
+ // capability-less lexicon contributes nothing rather than throwing, unlike
211
+ // the strict `@intentius/chant-capability-<name>` path.
212
+ const registry = await buildCapabilityRegistry({ lexicons: ["definitely-not-a-real-lexicon"] });
213
+ expect(registry.kinds()).toEqual([...ALL_STARTER_KINDS].sort());
214
+ });
215
+ });
216
+
217
+ describe("loadCapabilityPluginFromLexicon", () => {
218
+ test("returns null for a lexicon package that cannot be resolved", async () => {
219
+ expect(await loadCapabilityPluginFromLexicon("definitely-not-a-real-lexicon")).toBeNull();
220
+ });
221
+
222
+ test("loadCapabilityPluginsFromLexicons skips unresolvable lexicons without throwing", async () => {
223
+ expect(await loadCapabilityPluginsFromLexicons(["definitely-not-a-real-lexicon"])).toEqual([]);
224
+ });
225
+ });
226
+
227
+ describe("MalformedCapabilityPluginError", () => {
228
+ test("carries the offending package name and a descriptive message", () => {
229
+ const err = new MalformedCapabilityPluginError("@intentius/chant-capability-broken", "no capabilities() export");
230
+ expect(err.packageName).toBe("@intentius/chant-capability-broken");
231
+ expect(err.message).toContain("@intentius/chant-capability-broken");
232
+ expect(err.message).toContain("no capabilities() export");
233
+ expect(err.name).toBe("MalformedCapabilityPluginError");
234
+ });
235
+ });
@@ -0,0 +1,108 @@
1
+ /**
2
+ * Capability plugin contract — Phase 2 (#559, epic #551).
3
+ *
4
+ * Promotes the Phase 1 capability registry convention
5
+ * (`createCapabilityRegistry`/`STARTER_VERB_FAMILIES`, ./registry.ts) to a
6
+ * first-class chant plugin contract, mirroring the lexicon plugin contract
7
+ * (../lexicon.ts, ../cli/plugins.ts) so capabilities become typed,
8
+ * discoverable packages resolved by `kind`, the same way lexicons are
9
+ * discovered and resolved by resource type.
10
+ *
11
+ * Shape parity with `LexiconPlugin` (../lexicon.ts):
12
+ * - `name`/`version` mirror `LexiconManifest.name`/`.version`.
13
+ * - `capabilities()` is the required lifecycle method (like a lexicon's
14
+ * `serializer`/`generate`/`validate`) every capability package must
15
+ * implement — it returns the typed `Capability` instances this package
16
+ * contributes, registered by `kind` the same way a lexicon's resource
17
+ * types are registered by `resourceType`.
18
+ * - `families()`, `init()` are optional extensions, mirroring
19
+ * `LexiconPlugin`'s optional `lintRules`/`init`/etc.
20
+ * - `isCapabilityPlugin` is the runtime type guard `loadCapabilityPlugin`
21
+ * (./capability-plugin-loader.ts) uses to validate a dynamically-imported
22
+ * package before trusting it — the same defensive shape check
23
+ * `isLexiconPlugin` performs for lexicons.
24
+ *
25
+ * The starter verb set (./registry.ts) becomes the built-in, always-loaded
26
+ * plugin (`starterCapabilityPlugin`, ./starter-plugin.ts) under this same
27
+ * contract — see that module's docstring for the Phase 1 -> Phase 2
28
+ * migration path and the "no behavior change" guarantee.
29
+ */
30
+
31
+ import type { Capability } from "./capability";
32
+
33
+ /**
34
+ * Manifest for a packaged capability plugin — the capability-side analogue
35
+ * of `LexiconManifest` (../lexicon.ts). Kept as a plain data shape (rather
36
+ * than folded into `CapabilityPlugin`) so it can be serialized alongside a
37
+ * package (e.g. into a future tarball/`meta.json`, matching how lexicons
38
+ * carry `LexiconManifest` in their `BundleSpec`) independent of the runtime
39
+ * plugin object.
40
+ */
41
+ export interface CapabilityManifest {
42
+ /** Package/plugin name (e.g. "aws", "gcp"), not a capability `kind`. */
43
+ name: string;
44
+ /** Plugin package version (semver). */
45
+ version: string;
46
+ /** Minimum/compatible chant core version, checked the same way as `LexiconManifest.chantVersion` (see ../lexicon-manifest.ts's `checkVersionCompatibility`). */
47
+ chantVersion?: string;
48
+ /** Every capability `kind` this plugin contributes — informational/validation aid; must match what `capabilities()` actually returns. */
49
+ kinds?: string[];
50
+ }
51
+
52
+ /**
53
+ * Plugin interface for capability packages — the capability-side analogue of
54
+ * `LexiconPlugin` (../lexicon.ts). A capability plugin ships one or more
55
+ * typed `Capability` implementations, discovered and loaded the same way a
56
+ * lexicon plugin is: dynamically imported by a package-naming convention
57
+ * (`@intentius/chant-capability-<name>`, see ./capability-plugin-loader.ts),
58
+ * validated with a runtime shape guard, then registered into a
59
+ * `CapabilityRegistry` the driver resolves verbs through by `kind`.
60
+ *
61
+ * Required lifecycle method enforces consistency: every capability package
62
+ * must be able to enumerate the capabilities it contributes.
63
+ */
64
+ export interface CapabilityPlugin {
65
+ // ── Required ──────────────────────────────────────────────
66
+ /** Human-readable plugin/package name (e.g. "aws", "gcp"), not a capability `kind`. */
67
+ readonly name: string;
68
+
69
+ /** Plugin package version (semver), mirrors `LexiconManifest.version`. */
70
+ readonly version: string;
71
+
72
+ /** Return every `Capability` this plugin contributes, keyed for registration by its own `kind`. */
73
+ capabilities(): Array<Capability<never, unknown>>;
74
+
75
+ // ── Optional extensions ───────────────────────────────────
76
+ /** Minimum/compatible chant core version this plugin requires (checked via ../lexicon-manifest.ts's `checkVersionCompatibility`). */
77
+ readonly chantVersion?: string;
78
+
79
+ /** Capability kinds grouped by family, informational — mirrors `STARTER_VERB_FAMILIES` (./registry.ts) shape for third-party plugins that want to publish the same grouping. */
80
+ families?(): Record<string, readonly string[]>;
81
+
82
+ /** Optional initialization hook, called once after load (mirrors `LexiconPlugin.init`). */
83
+ init?(): void | Promise<void>;
84
+ }
85
+
86
+ /**
87
+ * Type guard to check if a value is a `CapabilityPlugin`. Checks for the
88
+ * required `name`/`version`/`capabilities` shape, the same defensive check
89
+ * `isLexiconPlugin` (../lexicon.ts) performs before a dynamically-imported
90
+ * package is trusted and registered. This is also chant's detector for a
91
+ * *malformed* capability package (#559 acceptance criteria): a package
92
+ * missing any of these fails the guard and is rejected by the loader with a
93
+ * descriptive error rather than registered half-working.
94
+ */
95
+ export function isCapabilityPlugin(value: unknown): value is CapabilityPlugin {
96
+ if (
97
+ typeof value !== "object" ||
98
+ value === null ||
99
+ !("name" in value) ||
100
+ typeof (value as Record<string, unknown>).name !== "string" ||
101
+ !("version" in value) ||
102
+ typeof (value as Record<string, unknown>).version !== "string"
103
+ ) {
104
+ return false;
105
+ }
106
+ const obj = value as Record<string, unknown>;
107
+ return typeof obj.capabilities === "function";
108
+ }