@intentius/chant 0.80.0 → 0.81.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 (414) hide show
  1. package/dist/audit/discover.d.ts +8 -16
  2. package/dist/audit/discover.d.ts.map +1 -1
  3. package/dist/audit/report-html.d.ts +5 -0
  4. package/dist/audit/report-html.d.ts.map +1 -1
  5. package/dist/audit/report-model.d.ts +13 -0
  6. package/dist/audit/report-model.d.ts.map +1 -1
  7. package/dist/audit/terraform-state.d.ts +4 -5
  8. package/dist/audit/terraform-state.d.ts.map +1 -1
  9. package/dist/cli/commands/audit.d.ts +25 -9
  10. package/dist/cli/commands/audit.d.ts.map +1 -1
  11. package/dist/cli/commands/check-lexicon.d.ts +8 -0
  12. package/dist/cli/commands/check-lexicon.d.ts.map +1 -1
  13. package/dist/cli/commands/doctor.d.ts.map +1 -1
  14. package/dist/cli/commands/import-agents.d.ts.map +1 -1
  15. package/dist/cli/commands/init.d.ts.map +1 -1
  16. package/dist/cli/commands/lint.d.ts.map +1 -1
  17. package/dist/cli/commands/update.d.ts +6 -0
  18. package/dist/cli/commands/update.d.ts.map +1 -1
  19. package/dist/cli/commands/vendor.d.ts +5 -0
  20. package/dist/cli/commands/vendor.d.ts.map +1 -1
  21. package/dist/cli/handlers/build.d.ts.map +1 -1
  22. package/dist/cli/handlers/components.d.ts.map +1 -1
  23. package/dist/cli/handlers/dev.d.ts.map +1 -1
  24. package/dist/cli/handlers/fan-out.d.ts.map +1 -1
  25. package/dist/cli/handlers/init.d.ts.map +1 -1
  26. package/dist/cli/handlers/operator.d.ts +7 -0
  27. package/dist/cli/handlers/operator.d.ts.map +1 -1
  28. package/dist/cli/handlers/promote.d.ts +7 -4
  29. package/dist/cli/handlers/promote.d.ts.map +1 -1
  30. package/dist/cli/handlers/run-generate.d.ts.map +1 -1
  31. package/dist/cli/handlers/run.d.ts +4 -1
  32. package/dist/cli/handlers/run.d.ts.map +1 -1
  33. package/dist/cli/handlers/vendor.d.ts +8 -3
  34. package/dist/cli/handlers/vendor.d.ts.map +1 -1
  35. package/dist/cli/main.d.ts +12 -0
  36. package/dist/cli/main.d.ts.map +1 -1
  37. package/dist/cli/plugins.d.ts +12 -7
  38. package/dist/cli/plugins.d.ts.map +1 -1
  39. package/dist/cli/registry.d.ts +32 -0
  40. package/dist/cli/registry.d.ts.map +1 -1
  41. package/dist/codegen/fetch.d.ts +10 -0
  42. package/dist/codegen/fetch.d.ts.map +1 -1
  43. package/dist/components/cli-support.d.ts +3 -1
  44. package/dist/components/cli-support.d.ts.map +1 -1
  45. package/dist/components/discover.d.ts.map +1 -1
  46. package/dist/components/driver.d.ts +16 -2
  47. package/dist/components/driver.d.ts.map +1 -1
  48. package/dist/components/gate-plan.d.ts +40 -0
  49. package/dist/components/gate-plan.d.ts.map +1 -0
  50. package/dist/components/promote.d.ts +60 -7
  51. package/dist/components/promote.d.ts.map +1 -1
  52. package/dist/components/sandbox/driver.d.ts.map +1 -1
  53. package/dist/components/sandbox/run.d.ts.map +1 -1
  54. package/dist/components/verbs/build-archive.d.ts +22 -11
  55. package/dist/components/verbs/build-archive.d.ts.map +1 -1
  56. package/dist/components/verbs/run-agent.d.ts.map +1 -1
  57. package/dist/config-static.d.ts +59 -0
  58. package/dist/config-static.d.ts.map +1 -0
  59. package/dist/config.d.ts +32 -10
  60. package/dist/config.d.ts.map +1 -1
  61. package/dist/content-digest.d.ts +17 -0
  62. package/dist/content-digest.d.ts.map +1 -0
  63. package/dist/discovery/files.d.ts +10 -5
  64. package/dist/discovery/files.d.ts.map +1 -1
  65. package/dist/discovery/fold-import.d.ts +20 -4
  66. package/dist/discovery/fold-import.d.ts.map +1 -1
  67. package/dist/discovery/index.d.ts.map +1 -1
  68. package/dist/discovery/sandbox/driver.d.ts +20 -0
  69. package/dist/discovery/sandbox/driver.d.ts.map +1 -1
  70. package/dist/discovery/sandbox/fork.d.ts +20 -0
  71. package/dist/discovery/sandbox/fork.d.ts.map +1 -1
  72. package/dist/discovery/walk.d.ts +119 -0
  73. package/dist/discovery/walk.d.ts.map +1 -0
  74. package/dist/graph-ir.d.ts +4 -1
  75. package/dist/graph-ir.d.ts.map +1 -1
  76. package/dist/join-key.d.ts +31 -0
  77. package/dist/join-key.d.ts.map +1 -0
  78. package/dist/lexicon-module.d.ts +51 -6
  79. package/dist/lexicon-module.d.ts.map +1 -1
  80. package/dist/lexicon.d.ts +58 -1
  81. package/dist/lexicon.d.ts.map +1 -1
  82. package/dist/lifecycle/build-ledger-store.d.ts.map +1 -1
  83. package/dist/lifecycle/digest.d.ts +5 -1
  84. package/dist/lifecycle/digest.d.ts.map +1 -1
  85. package/dist/lifecycle/gate-ledger.d.ts +20 -5
  86. package/dist/lifecycle/gate-ledger.d.ts.map +1 -1
  87. package/dist/lifecycle/git.d.ts +64 -0
  88. package/dist/lifecycle/git.d.ts.map +1 -1
  89. package/dist/lifecycle/lease.d.ts +8 -1
  90. package/dist/lifecycle/lease.d.ts.map +1 -1
  91. package/dist/lifecycle/legacy-digest.d.ts +41 -8
  92. package/dist/lifecycle/legacy-digest.d.ts.map +1 -1
  93. package/dist/lifecycle/member-ledger.d.ts +67 -0
  94. package/dist/lifecycle/member-ledger.d.ts.map +1 -0
  95. package/dist/lifecycle/plan-digest.d.ts +4 -3
  96. package/dist/lifecycle/plan-digest.d.ts.map +1 -1
  97. package/dist/lifecycle/release-ledger.d.ts +18 -2
  98. package/dist/lifecycle/release-ledger.d.ts.map +1 -1
  99. package/dist/lifecycle/status.d.ts +90 -5
  100. package/dist/lifecycle/status.d.ts.map +1 -1
  101. package/dist/op/activities/activity-contracts.d.ts +32 -0
  102. package/dist/op/activities/activity-contracts.d.ts.map +1 -1
  103. package/dist/op/activities/index.d.ts +2 -0
  104. package/dist/op/activities/index.d.ts.map +1 -1
  105. package/dist/op/activities/propose-upgrade.d.ts +75 -0
  106. package/dist/op/activities/propose-upgrade.d.ts.map +1 -0
  107. package/dist/op/discover.d.ts.map +1 -1
  108. package/dist/op/gate-name.d.ts +7 -0
  109. package/dist/op/gate-name.d.ts.map +1 -1
  110. package/dist/op/gate-summary.d.ts +16 -0
  111. package/dist/op/gate-summary.d.ts.map +1 -1
  112. package/dist/op/gate.d.ts +16 -2
  113. package/dist/op/gate.d.ts.map +1 -1
  114. package/dist/project-root.d.ts +26 -0
  115. package/dist/project-root.d.ts.map +1 -1
  116. package/dist/workspace/__fixtures__/contract-repo.d.ts +37 -0
  117. package/dist/workspace/__fixtures__/contract-repo.d.ts.map +1 -0
  118. package/dist/workspace/__fixtures__/two-member-workspace.d.ts +11 -0
  119. package/dist/workspace/__fixtures__/two-member-workspace.d.ts.map +1 -0
  120. package/dist/workspace/checks/generated.d.ts +156 -0
  121. package/dist/workspace/checks/generated.d.ts.map +1 -0
  122. package/dist/workspace/checks/ledgers.d.ts +78 -0
  123. package/dist/workspace/checks/ledgers.d.ts.map +1 -0
  124. package/dist/workspace/checks/links.d.ts +25 -0
  125. package/dist/workspace/checks/links.d.ts.map +1 -0
  126. package/dist/workspace/checks/pipelines.d.ts +76 -0
  127. package/dist/workspace/checks/pipelines.d.ts.map +1 -0
  128. package/dist/workspace/checks.d.ts +161 -0
  129. package/dist/workspace/checks.d.ts.map +1 -0
  130. package/dist/workspace/compose-graph.d.ts +115 -0
  131. package/dist/workspace/compose-graph.d.ts.map +1 -0
  132. package/dist/workspace/compose-reports.d.ts +68 -0
  133. package/dist/workspace/compose-reports.d.ts.map +1 -0
  134. package/dist/workspace/declaration.d.ts +220 -0
  135. package/dist/workspace/declaration.d.ts.map +1 -0
  136. package/dist/workspace/declaration.schema.json +434 -0
  137. package/dist/workspace/generated-files.d.ts +65 -0
  138. package/dist/workspace/generated-files.d.ts.map +1 -0
  139. package/dist/workspace/glob.d.ts +13 -0
  140. package/dist/workspace/glob.d.ts.map +1 -0
  141. package/dist/workspace/graph-cli.d.ts +75 -0
  142. package/dist/workspace/graph-cli.d.ts.map +1 -0
  143. package/dist/workspace/init.d.ts +96 -0
  144. package/dist/workspace/init.d.ts.map +1 -0
  145. package/dist/workspace/jsonc.d.ts +38 -0
  146. package/dist/workspace/jsonc.d.ts.map +1 -0
  147. package/dist/workspace/kinds.d.ts +192 -0
  148. package/dist/workspace/kinds.d.ts.map +1 -0
  149. package/dist/workspace/lineage-check.d.ts +84 -0
  150. package/dist/workspace/lineage-check.d.ts.map +1 -0
  151. package/dist/workspace/lineage-cli.d.ts +31 -0
  152. package/dist/workspace/lineage-cli.d.ts.map +1 -0
  153. package/dist/workspace/lineage-init.d.ts +101 -0
  154. package/dist/workspace/lineage-init.d.ts.map +1 -0
  155. package/dist/workspace/lineage-lock.d.ts +258 -0
  156. package/dist/workspace/lineage-lock.d.ts.map +1 -0
  157. package/dist/workspace/lineage-merge.d.ts +14 -0
  158. package/dist/workspace/lineage-merge.d.ts.map +1 -0
  159. package/dist/workspace/lineage-migrations.d.ts +178 -0
  160. package/dist/workspace/lineage-migrations.d.ts.map +1 -0
  161. package/dist/workspace/lineage-update.d.ts +59 -0
  162. package/dist/workspace/lineage-update.d.ts.map +1 -0
  163. package/dist/workspace/lineage-upgrade-cli.d.ts +40 -0
  164. package/dist/workspace/lineage-upgrade-cli.d.ts.map +1 -0
  165. package/dist/workspace/lineage-upgrade.d.ts +146 -0
  166. package/dist/workspace/lineage-upgrade.d.ts.map +1 -0
  167. package/dist/workspace/lineage-vendor.d.ts +63 -0
  168. package/dist/workspace/lineage-vendor.d.ts.map +1 -0
  169. package/dist/workspace/lineage-version.d.ts +32 -0
  170. package/dist/workspace/lineage-version.d.ts.map +1 -0
  171. package/dist/workspace/links.d.ts +150 -0
  172. package/dist/workspace/links.d.ts.map +1 -0
  173. package/dist/workspace/ls.d.ts +99 -0
  174. package/dist/workspace/ls.d.ts.map +1 -0
  175. package/dist/workspace/member-commands.d.ts +139 -0
  176. package/dist/workspace/member-commands.d.ts.map +1 -0
  177. package/dist/workspace/member-handles.d.ts +54 -0
  178. package/dist/workspace/member-handles.d.ts.map +1 -0
  179. package/dist/workspace/member-pipeline.d.ts +143 -0
  180. package/dist/workspace/member-pipeline.d.ts.map +1 -0
  181. package/dist/workspace/member-run.d.ts +67 -0
  182. package/dist/workspace/member-run.d.ts.map +1 -0
  183. package/dist/workspace/reason-codes.d.ts +65 -0
  184. package/dist/workspace/reason-codes.d.ts.map +1 -0
  185. package/dist/workspace/records-cli.d.ts +28 -4
  186. package/dist/workspace/records-cli.d.ts.map +1 -1
  187. package/dist/workspace/records.d.ts.map +1 -1
  188. package/dist/workspace/root-refusal.d.ts +53 -0
  189. package/dist/workspace/root-refusal.d.ts.map +1 -0
  190. package/dist/workspace/status.d.ts +150 -0
  191. package/dist/workspace/status.d.ts.map +1 -0
  192. package/dist/workspace/template-manifest.d.ts +90 -0
  193. package/dist/workspace/template-manifest.d.ts.map +1 -0
  194. package/dist/workspace/tree.d.ts +36 -0
  195. package/dist/workspace/tree.d.ts.map +1 -0
  196. package/dist/workspace/trust/attestor.d.ts +67 -0
  197. package/dist/workspace/trust/attestor.d.ts.map +1 -0
  198. package/dist/workspace/trust/policy.d.ts +111 -0
  199. package/dist/workspace/trust/policy.d.ts.map +1 -0
  200. package/dist/workspace/trust/provenance.d.ts +67 -0
  201. package/dist/workspace/trust/provenance.d.ts.map +1 -0
  202. package/dist/workspace/trust/ssh-commit.d.ts +43 -0
  203. package/dist/workspace/trust/ssh-commit.d.ts.map +1 -0
  204. package/dist/workspace/trust/test-repo.d.ts +42 -0
  205. package/dist/workspace/trust/test-repo.d.ts.map +1 -0
  206. package/dist/workspace/trust/verify-cli.d.ts +9 -0
  207. package/dist/workspace/trust/verify-cli.d.ts.map +1 -0
  208. package/dist/workspace/trust/verify.d.ts +66 -0
  209. package/dist/workspace/trust/verify.d.ts.map +1 -0
  210. package/dist/workspace/which-chant.d.ts +58 -0
  211. package/dist/workspace/which-chant.d.ts.map +1 -0
  212. package/dist/workspace/workspace-kinds.schema.json +133 -0
  213. package/package.json +2 -1
  214. package/src/audit/discover.ts +66 -78
  215. package/src/audit/report-html.ts +3 -1
  216. package/src/audit/report-model.ts +14 -1
  217. package/src/audit/terraform-state.test.ts +4 -9
  218. package/src/audit/terraform-state.ts +4 -5
  219. package/src/cli/commands/audit-truncation.test.ts +167 -0
  220. package/src/cli/commands/audit.ts +89 -60
  221. package/src/cli/commands/check-lexicon.ts +24 -0
  222. package/src/cli/commands/doctor.test.ts +30 -0
  223. package/src/cli/commands/doctor.ts +18 -0
  224. package/src/cli/commands/import-agents.ts +6 -2
  225. package/src/cli/commands/init.ts +22 -3
  226. package/src/cli/commands/lint.test.ts +17 -23
  227. package/src/cli/commands/lint.ts +24 -84
  228. package/src/cli/commands/update.ts +13 -4
  229. package/src/cli/commands/vendor.ts +11 -6
  230. package/src/cli/handlers/__snapshots__/build-member.test.ts.snap +38 -0
  231. package/src/cli/handlers/build-member.test.ts +127 -0
  232. package/src/cli/handlers/build.ts +35 -1
  233. package/src/cli/handlers/components.test.ts +127 -0
  234. package/src/cli/handlers/components.ts +70 -8
  235. package/src/cli/handlers/dev.ts +5 -3
  236. package/src/cli/handlers/fan-out.ts +9 -2
  237. package/src/cli/handlers/init.ts +54 -1
  238. package/src/cli/handlers/operator.test.ts +68 -0
  239. package/src/cli/handlers/operator.ts +37 -9
  240. package/src/cli/handlers/promote.test.ts +118 -2
  241. package/src/cli/handlers/promote.ts +82 -16
  242. package/src/cli/handlers/run-generate-member.test.ts +83 -0
  243. package/src/cli/handlers/run-generate.test.ts +6 -8
  244. package/src/cli/handlers/run-generate.ts +36 -1
  245. package/src/cli/handlers/run.test.ts +47 -0
  246. package/src/cli/handlers/run.ts +28 -5
  247. package/src/cli/handlers/vendor.ts +147 -43
  248. package/src/cli/main.test.ts +105 -1
  249. package/src/cli/main.ts +230 -17
  250. package/src/cli/plugins.ts +17 -17
  251. package/src/cli/registry.ts +32 -0
  252. package/src/cli/static-config-read.test.ts +208 -0
  253. package/src/codegen/fetch.test.ts +63 -1
  254. package/src/codegen/fetch.ts +30 -1
  255. package/src/components/cli-support.ts +6 -1
  256. package/src/components/discover.test.ts +27 -57
  257. package/src/components/discover.ts +13 -68
  258. package/src/components/driver.test.ts +78 -1
  259. package/src/components/driver.ts +43 -7
  260. package/src/components/gate-plan.ts +53 -0
  261. package/src/components/promote.test.ts +276 -2
  262. package/src/components/promote.ts +119 -15
  263. package/src/components/sandbox/driver.ts +8 -8
  264. package/src/components/sandbox/run.ts +4 -1
  265. package/src/components/verbs/build-archive.ts +27 -21
  266. package/src/components/verbs/run-agent.ts +3 -5
  267. package/src/config-static.test.ts +136 -0
  268. package/src/config-static.ts +232 -0
  269. package/src/config.test.ts +2 -2
  270. package/src/config.ts +48 -15
  271. package/src/content-digest.test.ts +120 -0
  272. package/src/content-digest.ts +21 -0
  273. package/src/discovery/files.test.ts +10 -10
  274. package/src/discovery/files.ts +25 -81
  275. package/src/discovery/fold-import.ts +49 -5
  276. package/src/discovery/index.ts +1 -0
  277. package/src/discovery/sandbox/driver.ts +73 -35
  278. package/src/discovery/sandbox/fork-diagnostic.test.ts +136 -13
  279. package/src/discovery/sandbox/fork.ts +83 -43
  280. package/src/discovery/sandbox/path-lexicon-multifile.test.ts +285 -0
  281. package/src/discovery/walk.test.ts +263 -0
  282. package/src/discovery/walk.ts +334 -0
  283. package/src/effect-receipt.ts +2 -2
  284. package/src/graph-ir.ts +4 -1
  285. package/src/join-key.test.ts +25 -0
  286. package/src/join-key.ts +39 -0
  287. package/src/lexicon-module.ts +108 -10
  288. package/src/lexicon.ts +79 -1
  289. package/src/lifecycle/build-ledger-store.ts +11 -10
  290. package/src/lifecycle/digest.ts +9 -5
  291. package/src/lifecycle/gate-ledger.test.ts +20 -0
  292. package/src/lifecycle/gate-ledger.ts +28 -6
  293. package/src/lifecycle/git.test.ts +13 -1
  294. package/src/lifecycle/git.ts +221 -176
  295. package/src/lifecycle/lease.ts +25 -10
  296. package/src/lifecycle/legacy-digest.test.ts +115 -23
  297. package/src/lifecycle/legacy-digest.ts +60 -19
  298. package/src/lifecycle/member-ledger.test.ts +319 -0
  299. package/src/lifecycle/member-ledger.ts +145 -0
  300. package/src/lifecycle/plan-digest.ts +7 -7
  301. package/src/lifecycle/release-ledger.test.ts +20 -0
  302. package/src/lifecycle/release-ledger.ts +23 -7
  303. package/src/lifecycle/status.test.ts +151 -1
  304. package/src/lifecycle/status.ts +228 -7
  305. package/src/op/activities/activity-contracts.ts +37 -0
  306. package/src/op/activities/index.ts +10 -0
  307. package/src/op/activities/propose-upgrade.ts +206 -0
  308. package/src/op/discover.test.ts +14 -4
  309. package/src/op/discover.ts +19 -27
  310. package/src/op/gate-name.ts +8 -0
  311. package/src/op/gate-summary.test.ts +14 -0
  312. package/src/op/gate-summary.ts +25 -2
  313. package/src/op/gate.ts +36 -7
  314. package/src/op/generate-pipeline.test.ts +14 -5
  315. package/src/project-root.test.ts +66 -1
  316. package/src/project-root.ts +48 -0
  317. package/src/workspace/__fixtures__/contract-repo.ts +100 -0
  318. package/src/workspace/__fixtures__/two-member-workspace.ts +35 -0
  319. package/src/workspace/behold-kinds.test.ts +162 -0
  320. package/src/workspace/check-contract.test.ts +127 -0
  321. package/src/workspace/check.schema.json +347 -0
  322. package/src/workspace/checks/generated.test.ts +287 -0
  323. package/src/workspace/checks/generated.ts +475 -0
  324. package/src/workspace/checks/ledgers.test.ts +139 -0
  325. package/src/workspace/checks/ledgers.ts +246 -0
  326. package/src/workspace/checks/links.ts +183 -0
  327. package/src/workspace/checks/pipelines.test.ts +133 -0
  328. package/src/workspace/checks/pipelines.ts +225 -0
  329. package/src/workspace/checks-repo.test.ts +71 -0
  330. package/src/workspace/checks.test.ts +450 -0
  331. package/src/workspace/checks.ts +461 -0
  332. package/src/workspace/compose-graph.test.ts +146 -0
  333. package/src/workspace/compose-graph.ts +228 -0
  334. package/src/workspace/compose-reports.test.ts +109 -0
  335. package/src/workspace/compose-reports.ts +186 -0
  336. package/src/workspace/declaration.schema.json +434 -0
  337. package/src/workspace/declaration.test.ts +347 -0
  338. package/src/workspace/declaration.ts +623 -0
  339. package/src/workspace/generated-files.test.ts +114 -0
  340. package/src/workspace/generated-files.ts +94 -0
  341. package/src/workspace/glob.ts +49 -0
  342. package/src/workspace/graph-cli.ts +220 -0
  343. package/src/workspace/graph-contract.test.ts +204 -0
  344. package/src/workspace/graph.schema.json +662 -0
  345. package/src/workspace/init.test.ts +223 -0
  346. package/src/workspace/init.ts +585 -0
  347. package/src/workspace/jsonc.test.ts +65 -0
  348. package/src/workspace/jsonc.ts +231 -0
  349. package/src/workspace/kinds.test.ts +288 -0
  350. package/src/workspace/kinds.ts +506 -0
  351. package/src/workspace/lineage-check.ts +264 -0
  352. package/src/workspace/lineage-cli.ts +114 -0
  353. package/src/workspace/lineage-init.test.ts +231 -0
  354. package/src/workspace/lineage-init.ts +365 -0
  355. package/src/workspace/lineage-lock.test.ts +224 -0
  356. package/src/workspace/lineage-lock.ts +357 -0
  357. package/src/workspace/lineage-merge.ts +44 -0
  358. package/src/workspace/lineage-migrations.test.ts +178 -0
  359. package/src/workspace/lineage-migrations.ts +343 -0
  360. package/src/workspace/lineage-update.ts +160 -0
  361. package/src/workspace/lineage-upgrade-cli.ts +151 -0
  362. package/src/workspace/lineage-upgrade.test.ts +474 -0
  363. package/src/workspace/lineage-upgrade.ts +624 -0
  364. package/src/workspace/lineage-vendor.test.ts +126 -0
  365. package/src/workspace/lineage-vendor.ts +215 -0
  366. package/src/workspace/lineage-version.ts +127 -0
  367. package/src/workspace/links.test.ts +344 -0
  368. package/src/workspace/links.ts +331 -0
  369. package/src/workspace/ls-contract.test.ts +200 -0
  370. package/src/workspace/ls.schema.json +190 -0
  371. package/src/workspace/ls.ts +254 -0
  372. package/src/workspace/member-commands.e2e.test.ts +244 -0
  373. package/src/workspace/member-commands.test.ts +217 -0
  374. package/src/workspace/member-commands.ts +540 -0
  375. package/src/workspace/member-handles.test.ts +76 -0
  376. package/src/workspace/member-handles.ts +228 -0
  377. package/src/workspace/member-pipeline.test.ts +135 -0
  378. package/src/workspace/member-pipeline.ts +317 -0
  379. package/src/workspace/member-run.ts +146 -0
  380. package/src/workspace/read-contract.test.ts +129 -0
  381. package/src/workspace/reason-codes.test.ts +113 -0
  382. package/src/workspace/reason-codes.ts +81 -0
  383. package/src/workspace/records-cli.ts +86 -14
  384. package/src/workspace/records-contract.test.ts +5 -0
  385. package/src/workspace/records.schema.json +32 -2
  386. package/src/workspace/records.ts +3 -2
  387. package/src/workspace/root-refusal.ts +97 -0
  388. package/src/workspace/status-contract.test.ts +308 -0
  389. package/src/workspace/status.schema.json +210 -0
  390. package/src/workspace/status.ts +412 -0
  391. package/src/workspace/template-manifest.test.ts +79 -0
  392. package/src/workspace/template-manifest.ts +221 -0
  393. package/src/workspace/tree.ts +132 -0
  394. package/src/workspace/trust/attestor.ts +93 -0
  395. package/src/workspace/trust/policy.ts +306 -0
  396. package/src/workspace/trust/provenance.ts +181 -0
  397. package/src/workspace/trust/ssh-commit.ts +138 -0
  398. package/src/workspace/trust/test-repo.ts +138 -0
  399. package/src/workspace/trust/trust.test.ts +397 -0
  400. package/src/workspace/trust/verify-cli.ts +56 -0
  401. package/src/workspace/trust/verify.ts +194 -0
  402. package/src/workspace/which-chant.test.ts +117 -0
  403. package/src/workspace/which-chant.ts +151 -0
  404. package/src/workspace/workspace-kinds.schema.json +133 -0
  405. package/dist/components/unbound-gate-approval.d.ts +0 -24
  406. package/dist/components/unbound-gate-approval.d.ts.map +0 -1
  407. package/dist/discovery/convergence.d.ts +0 -97
  408. package/dist/discovery/convergence.d.ts.map +0 -1
  409. package/src/cli/commands/audit-walk-warnings.test.ts +0 -156
  410. package/src/components/unbound-gate-approval.test.ts +0 -82
  411. package/src/components/unbound-gate-approval.ts +0 -51
  412. package/src/discovery/convergence.test.ts +0 -231
  413. package/src/discovery/convergence.ts +0 -399
  414. package/src/op/discover-convergence.test.ts +0 -70
@@ -0,0 +1,231 @@
1
+ /**
2
+ * A small JSON reader that keeps positions, for the workspace declaration
3
+ * (#2534). `chant.workspace.json` is strict JSON; `chant.workspace.jsonc` also
4
+ * allows `//` and `/* *\/` comments and trailing commas.
5
+ *
6
+ * `JSON.parse` would do for the strict file, but it reports neither a line
7
+ * nor a column in every runtime, it accepts duplicate keys by keeping the last
8
+ * one, and it can't say where a value sits once parsed. A schema error has to
9
+ * name the line it comes from, so this reader records where each value and
10
+ * each key starts, by JSON Pointer.
11
+ */
12
+
13
+ export interface TextLocation {
14
+ /** 1-based. */
15
+ line: number;
16
+ /** 1-based, counted in UTF-16 code units. */
17
+ column: number;
18
+ }
19
+
20
+ export interface JsonParseOptions {
21
+ /** Allow comments and trailing commas (`.jsonc`). */
22
+ jsonc: boolean;
23
+ }
24
+
25
+ export type JsonParseResult =
26
+ | {
27
+ ok: true;
28
+ value: unknown;
29
+ /**
30
+ * Where the value at `pointer` starts, or where its key starts when
31
+ * `key` is set. Falls back to the nearest ancestor that has a position.
32
+ */
33
+ locate(pointer: string, key?: boolean): TextLocation;
34
+ }
35
+ | { ok: false; message: string; location: TextLocation };
36
+
37
+ class JsonSyntaxError extends Error {
38
+ constructor(
39
+ message: string,
40
+ readonly offset: number,
41
+ ) {
42
+ super(message);
43
+ }
44
+ }
45
+
46
+ /** Escape one reference token for a JSON Pointer (RFC 6901). */
47
+ export function pointerToken(token: string | number): string {
48
+ return String(token).replace(/~/g, "~0").replace(/\//g, "~1");
49
+ }
50
+
51
+ export function parseJsonText(text: string, options: JsonParseOptions): JsonParseResult {
52
+ const lineStarts = [0];
53
+ for (let i = 0; i < text.length; i++) if (text.charCodeAt(i) === 10) lineStarts.push(i + 1);
54
+ const toLocation = (offset: number): TextLocation => {
55
+ let lo = 0;
56
+ let hi = lineStarts.length - 1;
57
+ while (lo < hi) {
58
+ const mid = (lo + hi + 1) >> 1;
59
+ if (lineStarts[mid] <= offset) lo = mid;
60
+ else hi = mid - 1;
61
+ }
62
+ return { line: lo + 1, column: offset - lineStarts[lo] + 1 };
63
+ };
64
+
65
+ const values = new Map<string, number>();
66
+ const keys = new Map<string, number>();
67
+ let i = text.charCodeAt(0) === 0xfeff ? 1 : 0;
68
+
69
+ const fail = (message: string, offset = i): never => {
70
+ throw new JsonSyntaxError(message, offset);
71
+ };
72
+ const describe = (offset: number): string =>
73
+ offset >= text.length ? "the end of the file" : `${JSON.stringify(text[offset])}`;
74
+
75
+ function skip(): void {
76
+ for (;;) {
77
+ const c = text[i];
78
+ if (c === " " || c === "\t" || c === "\n" || c === "\r") {
79
+ i++;
80
+ } else if (c === "/" && (text[i + 1] === "/" || text[i + 1] === "*")) {
81
+ if (!options.jsonc) fail("comments are only allowed in chant.workspace.jsonc");
82
+ if (text[i + 1] === "/") {
83
+ while (i < text.length && text[i] !== "\n") i++;
84
+ } else {
85
+ const end = text.indexOf("*/", i + 2);
86
+ if (end < 0) fail("unterminated /* comment");
87
+ i = end + 2;
88
+ }
89
+ } else {
90
+ return;
91
+ }
92
+ }
93
+ }
94
+
95
+ function parseString(): string {
96
+ const start = i;
97
+ i++;
98
+ for (;;) {
99
+ if (i >= text.length) fail("unterminated string", start);
100
+ const c = text.charCodeAt(i);
101
+ if (c === 0x22) break;
102
+ if (c < 0x20) fail("control character in a string; escape it", i);
103
+ i += c === 0x5c ? 2 : 1;
104
+ }
105
+ i++;
106
+ try {
107
+ return JSON.parse(text.slice(start, i)) as string;
108
+ } catch {
109
+ return fail("invalid escape in a string", start);
110
+ }
111
+ }
112
+
113
+ function parseValue(pointer: string): unknown {
114
+ skip();
115
+ values.set(pointer, i);
116
+ const c = text[i];
117
+ if (c === "{") return parseObject(pointer);
118
+ if (c === "[") return parseArray(pointer);
119
+ if (c === '"') return parseString();
120
+ for (const [word, value] of [
121
+ ["true", true],
122
+ ["false", false],
123
+ ["null", null],
124
+ ] as const) {
125
+ if (text.startsWith(word, i)) {
126
+ i += word.length;
127
+ return value;
128
+ }
129
+ }
130
+ const m = /^-?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+-]?\d+)?/.exec(text.slice(i, i + 64));
131
+ if (m) {
132
+ i += m[0].length;
133
+ return Number(m[0]);
134
+ }
135
+ return fail(`expected a value, found ${describe(i)}`);
136
+ }
137
+
138
+ function parseObject(pointer: string): Record<string, unknown> {
139
+ const obj: Record<string, unknown> = {};
140
+ const seen = new Set<string>();
141
+ i++;
142
+ skip();
143
+ if (text[i] === "}") {
144
+ i++;
145
+ return obj;
146
+ }
147
+ for (;;) {
148
+ skip();
149
+ if (text[i] !== '"') fail(`expected a quoted key, found ${describe(i)}`);
150
+ const keyAt = i;
151
+ const key = parseString();
152
+ if (seen.has(key)) fail(`duplicate key ${JSON.stringify(key)}`, keyAt);
153
+ seen.add(key);
154
+ const child = `${pointer}/${pointerToken(key)}`;
155
+ keys.set(child, keyAt);
156
+ skip();
157
+ if (text[i] !== ":") fail(`expected ":" after key ${JSON.stringify(key)}, found ${describe(i)}`);
158
+ i++;
159
+ // defineProperty, so a "__proto__" key is data and never a prototype.
160
+ Object.defineProperty(obj, key, { value: parseValue(child), enumerable: true, writable: true, configurable: true });
161
+ skip();
162
+ if (text[i] === ",") {
163
+ i++;
164
+ skip();
165
+ if (text[i] === "}") {
166
+ if (!options.jsonc) fail("trailing comma; only chant.workspace.jsonc allows one");
167
+ i++;
168
+ return obj;
169
+ }
170
+ continue;
171
+ }
172
+ if (text[i] === "}") {
173
+ i++;
174
+ return obj;
175
+ }
176
+ fail(`expected "," or "}", found ${describe(i)}`);
177
+ }
178
+ }
179
+
180
+ function parseArray(pointer: string): unknown[] {
181
+ const arr: unknown[] = [];
182
+ i++;
183
+ skip();
184
+ if (text[i] === "]") {
185
+ i++;
186
+ return arr;
187
+ }
188
+ for (;;) {
189
+ arr.push(parseValue(`${pointer}/${arr.length}`));
190
+ skip();
191
+ if (text[i] === ",") {
192
+ i++;
193
+ skip();
194
+ if (text[i] === "]") {
195
+ if (!options.jsonc) fail("trailing comma; only chant.workspace.jsonc allows one");
196
+ i++;
197
+ return arr;
198
+ }
199
+ continue;
200
+ }
201
+ if (text[i] === "]") {
202
+ i++;
203
+ return arr;
204
+ }
205
+ fail(`expected "," or "]", found ${describe(i)}`);
206
+ }
207
+ }
208
+
209
+ try {
210
+ skip();
211
+ if (i >= text.length) fail("the file is empty");
212
+ const value = parseValue("");
213
+ skip();
214
+ if (i < text.length) fail(`unexpected ${describe(i)} after the value`);
215
+ return {
216
+ ok: true,
217
+ value,
218
+ locate(pointer, key) {
219
+ if (key && keys.has(pointer)) return toLocation(keys.get(pointer)!);
220
+ for (let p = pointer; ; p = p.slice(0, p.lastIndexOf("/"))) {
221
+ const at = values.get(p);
222
+ if (at !== undefined) return toLocation(at);
223
+ if (p === "") return toLocation(0);
224
+ }
225
+ },
226
+ };
227
+ } catch (err) {
228
+ if (!(err instanceof JsonSyntaxError)) throw err;
229
+ return { ok: false, message: err.message, location: toLocation(err.offset) };
230
+ }
231
+ }
@@ -0,0 +1,288 @@
1
+ import { existsSync, mkdirSync, mkdtempSync, realpathSync, rmSync, writeFileSync } from "node:fs";
2
+ import { tmpdir } from "node:os";
3
+ import { dirname, join } from "node:path";
4
+ import Ajv2020 from "ajv/dist/2020";
5
+ import { afterAll, describe, expect, test } from "vitest";
6
+ import declarationSchema from "./declaration.schema.json";
7
+ import {
8
+ BUILTIN_KINDS,
9
+ builtinKindRegistry,
10
+ createKindRegistry,
11
+ EXAMPLES_KIND,
12
+ KINDS_SCHEMA_ID,
13
+ kindsExportTarget,
14
+ kindsFileShips,
15
+ loadKindRegistry,
16
+ parseKindData,
17
+ probeKind,
18
+ readPackageKinds,
19
+ resolveKind,
20
+ type MemberKind,
21
+ } from "./kinds";
22
+ import schema from "./workspace-kinds.schema.json";
23
+ import { workingTree } from "./tree";
24
+
25
+ const scratch: string[] = [];
26
+ afterAll(() => {
27
+ for (const d of scratch) rmSync(d, { recursive: true, force: true });
28
+ });
29
+ function dir(files: Record<string, string>): string {
30
+ const root = realpathSync(mkdtempSync(join(tmpdir(), "chant-kinds-")));
31
+ scratch.push(root);
32
+ for (const [path, text] of Object.entries(files)) {
33
+ mkdirSync(dirname(join(root, path)), { recursive: true });
34
+ writeFileSync(join(root, path), text);
35
+ }
36
+ return root;
37
+ }
38
+
39
+ const kindsFile = (kinds: unknown[]) => JSON.stringify({ schema: 1, kinds });
40
+ const tf = { name: "terraform", description: "a Terraform root module", precedence: 400, probe: { anyFile: ["main.tf"] } };
41
+
42
+ /**
43
+ * A package whose code writes a marker file when it is imported, so a test
44
+ * can tell that reading its kinds imported nothing.
45
+ */
46
+ function pkg(name: string, version: string, kinds: unknown[] | null, extra: Record<string, unknown> = {}): Record<string, string> {
47
+ const marker = `globalThis.__chantKindsImported = true; require("node:fs").writeFileSync(require("node:path").join(__dirname, "IMPORTED"), "");\n`;
48
+ return {
49
+ [`node_modules/${name}/package.json`]: JSON.stringify({
50
+ name,
51
+ version,
52
+ main: "./index.js",
53
+ exports: { ".": "./index.js", "./*": "./src/*.js", ...(kinds ? { "./workspace-kinds": "./workspace-kinds.json" } : {}) },
54
+ ...extra,
55
+ }),
56
+ [`node_modules/${name}/index.js`]: marker,
57
+ [`node_modules/${name}/src/workspace-kinds.js`]: marker,
58
+ ...(kinds ? { [`node_modules/${name}/workspace-kinds.json`]: kindsFile(kinds) } : {}),
59
+ };
60
+ }
61
+
62
+ describe("the built-in kinds (#2535)", () => {
63
+ test("are chant, workspace and other, and the group kind examples", () => {
64
+ expect(BUILTIN_KINDS.map((k) => `${k.name}:${k.shape}`)).toEqual(["chant:member", "workspace:member", "other:member", "examples:group"]);
65
+ const registry = builtinKindRegistry();
66
+ expect(registry.names()).toEqual(["chant", "other", "workspace"]);
67
+ expect(registry.get(EXAMPLES_KIND)?.shape).toBe("group");
68
+ });
69
+
70
+ test("agree with the declaration schema: examples is the one entry kind that makes a group", () => {
71
+ const entry = declarationSchema.$defs.entry;
72
+ expect(entry.if.properties.kind.const).toBe(EXAMPLES_KIND);
73
+ expect(declarationSchema.$defs.group.properties.kind.const).toBe(EXAMPLES_KIND);
74
+ expect(declarationSchema.$defs.member.if.properties.kind.const).toBe("other");
75
+ expect(declarationSchema.$defs.member.then.required).toEqual(["because"]);
76
+ });
77
+
78
+ test("a registry refuses a name twice", () => {
79
+ expect(() => createKindRegistry([{ ...BUILTIN_KINDS[0], source: "x" }])).toThrow(/registered twice/);
80
+ });
81
+ });
82
+
83
+ describe("the kinds schema", () => {
84
+ test("is a valid draft 2020-12 document that compiles in strict mode, with the published $id", () => {
85
+ const ajv = new Ajv2020({ strict: true, allErrors: true });
86
+ expect(ajv.validateSchema(schema)).toBe(true);
87
+ expect(() => ajv.compile(schema)).not.toThrow();
88
+ expect(KINDS_SCHEMA_ID).toBe("https://intentius.io/chant/schemas/workspace/kinds/v1/workspace-kinds.schema.json");
89
+ });
90
+ });
91
+
92
+ describe("parseKindData", () => {
93
+ test("reads valid kinds, with their source", () => {
94
+ const data = parseKindData(kindsFile([tf]), "@acme/tf-kinds");
95
+ expect(data.problems).toEqual([]);
96
+ expect(data.kinds).toEqual([{ ...tf, shape: "member", outputs: { from: "declared", names: [] }, source: "@acme/tf-kinds" }]);
97
+ });
98
+
99
+ test("a kind may list the outputs its members expose as link targets (#2539)", () => {
100
+ const data = parseKindData(kindsFile([{ ...tf, outputs: ["state_bucket", "vpc_id"] }]), "p");
101
+ expect(data.problems).toEqual([]);
102
+ expect(data.kinds[0].outputs).toEqual({ from: "declared", names: ["state_bucket", "vpc_id"] });
103
+ expect(parseKindData(kindsFile([{ ...tf, outputs: ["a", "a"] }]), "p").problems[0]).toMatch(/duplicate/);
104
+ });
105
+
106
+ test("refuses a built-in name, a name twice, a schema mismatch and text that isn't JSON", () => {
107
+ expect(parseKindData(kindsFile([{ ...tf, name: "chant" }]), "p").problems).toEqual(["p: kind chant is built in and can't be supplied by a package"]);
108
+ expect(parseKindData(kindsFile([{ ...tf, name: "examples" }]), "p").problems[0]).toMatch(/examples is built in/);
109
+ expect(parseKindData(kindsFile([tf, tf]), "p").problems).toEqual(["p: kind terraform is listed twice"]);
110
+ expect(parseKindData(kindsFile([{ ...tf, precedence: 1000 }]), "p").problems[0]).toMatch(/precedence/);
111
+ expect(parseKindData(kindsFile([{ ...tf, probe: { anyFile: ["../main.tf"] } }]), "p").problems[0]).toMatch(/anyFile/);
112
+ expect(parseKindData(kindsFile([{ ...tf, run: "node x.js" }]), "p").problems[0]).toMatch(/additional properties/);
113
+ expect(parseKindData("{", "p").problems[0]).toMatch(/^p: the kinds file is not JSON/);
114
+ });
115
+ });
116
+
117
+ describe("reading a package's kinds", () => {
118
+ test("follows only the literal ./workspace-kinds key, and only to a JSON file inside the package", () => {
119
+ expect(kindsExportTarget({ exports: { "./*": "./src/*.ts" } })).toBeUndefined();
120
+ expect(kindsExportTarget({ exports: { "./workspace-kinds": "./k.json" } })).toBe("./k.json");
121
+ expect(kindsExportTarget({ exports: { "./workspace-kinds": { types: "./k.d.ts", default: "./k.json" } } })).toBe("./k.json");
122
+ expect(kindsExportTarget({ exports: { "./workspace-kinds": "./src/kinds.ts" } })).toEqual({ problem: expect.stringMatching(/must name a \.json file/) });
123
+ expect(kindsExportTarget({ exports: { "./workspace-kinds": ["./a.json"] } })).toEqual({ problem: expect.stringMatching(/must name one file/) });
124
+ });
125
+
126
+ test("refuses a target outside the package, and a target that doesn't exist", () => {
127
+ const root = dir({
128
+ "a/package.json": JSON.stringify({ name: "a", exports: { "./workspace-kinds": "./../b/k.json" } }),
129
+ "b/k.json": kindsFile([tf]),
130
+ "c/package.json": JSON.stringify({ name: "c", exports: { "./workspace-kinds": "./missing.json" } }),
131
+ });
132
+ expect(readPackageKinds(join(root, "a")).problems[0]).toMatch(/outside the package/);
133
+ expect(readPackageKinds(join(root, "c")).problems[0]).toMatch(/does not exist/);
134
+ });
135
+
136
+ test("a package with no subpath supplies no kinds and no problem", () => {
137
+ const root = dir({ "package.json": JSON.stringify({ name: "lexicon", exports: { ".": "./index.js" } }) });
138
+ expect(readPackageKinds(root)).toEqual({ kinds: [], problems: [], file: undefined });
139
+ });
140
+
141
+ test("knows whether the kinds file ships, from package.json files", () => {
142
+ const root = dir({ "package.json": JSON.stringify({ files: ["dist/", "./kinds"] }) });
143
+ expect(kindsFileShips(root, join(root, "kinds/workspace-kinds.json"))).toBe(true);
144
+ expect(kindsFileShips(root, join(root, "dist/k.json"))).toBe(true);
145
+ expect(kindsFileShips(root, join(root, "workspace-kinds.json"))).toBe(false);
146
+ const open = dir({ "package.json": JSON.stringify({ name: "x" }) });
147
+ expect(kindsFileShips(open, join(open, "workspace-kinds.json"))).toBe(true);
148
+ });
149
+ });
150
+
151
+ describe("loadKindRegistry: kinds from the pins", () => {
152
+ test("reads an installed package's kinds as data and never imports the package", () => {
153
+ const root = dir(pkg("@acme/chant-lexicon-tf", "1.2.3", [tf]));
154
+ const { registry, problems } = loadKindRegistry([{ package: "@acme/chant-lexicon-tf", version: "1.2.3", path: null }], root);
155
+ expect(problems).toEqual([]);
156
+ expect(registry.get("terraform")).toMatchObject({ source: "@acme/chant-lexicon-tf", precedence: 400 });
157
+ expect(registry.names()).toEqual(["chant", "other", "terraform", "workspace"]);
158
+ expect(existsSync(join(root, "node_modules/@acme/chant-lexicon-tf/IMPORTED"))).toBe(false);
159
+ expect((globalThis as { __chantKindsImported?: boolean }).__chantKindsImported).toBeUndefined();
160
+ });
161
+
162
+ test("finds the package in an ancestor's node_modules, as Node does", () => {
163
+ const root = dir({ ...pkg("tf-kinds", "1.0.0", [tf]), "ws/.keep": "" });
164
+ expect(loadKindRegistry([{ package: "tf-kinds", version: "1.0.0", path: null }], join(root, "ws")).registry.get("terraform")).toBeDefined();
165
+ });
166
+
167
+ test("a package without the subpath supplies nothing; one not installed, or at another version, is a problem", () => {
168
+ const root = dir({ ...pkg("plain", "1.0.0", null), ...pkg("tf-kinds", "2.0.0", [tf]) });
169
+ const { registry, problems } = loadKindRegistry(
170
+ [
171
+ { package: "plain", version: "1.0.0", path: null },
172
+ { package: "absent", version: "1.0.0", path: null },
173
+ { package: "tf-kinds", version: "1.0.0", path: null },
174
+ ],
175
+ root,
176
+ );
177
+ expect(registry.names()).toEqual(["chant", "other", "workspace"]);
178
+ expect(problems).toEqual([
179
+ { pin: 1, message: "pinned package absent is not installed; install it to read the kinds it supplies" },
180
+ { pin: 2, message: "tf-kinds is pinned at 1.0.0, and 2.0.0 is installed" },
181
+ ]);
182
+ expect(existsSync(join(root, "node_modules/plain/IMPORTED"))).toBe(false);
183
+ });
184
+
185
+ test("reads a local plugin by path, and leaves out a kind two sources both supply", () => {
186
+ const root = dir({
187
+ "plugins/tf/package.json": JSON.stringify({ name: "tf", exports: { "./workspace-kinds": "./k.json" } }),
188
+ "plugins/tf/k.json": kindsFile([tf, { ...tf, name: "helm-chart", probe: { anyFile: ["Chart.yaml"] } }]),
189
+ "plugins/tf2/package.json": JSON.stringify({ name: "tf2", exports: { "./workspace-kinds": "./k.json" } }),
190
+ "plugins/tf2/k.json": kindsFile([tf]),
191
+ });
192
+ const { registry, problems } = loadKindRegistry(
193
+ [
194
+ { package: null, version: null, path: "plugins/tf" },
195
+ { package: null, version: null, path: "plugins/tf2" },
196
+ ],
197
+ root,
198
+ );
199
+ expect(registry.get("helm-chart")?.source).toBe("plugins/tf");
200
+ expect(registry.get("terraform")).toBeUndefined();
201
+ expect(problems).toEqual([{ pin: 1, message: "kind terraform is supplied by both plugins/tf and plugins/tf2; a kind name has one source" }]);
202
+ });
203
+
204
+ test("a kinds subpath that names code is refused, and the code is not run", () => {
205
+ const files = pkg("sneaky", "1.0.0", null, { exports: { "./workspace-kinds": "./index.js" } });
206
+ const root = dir(files);
207
+ const { problems } = loadKindRegistry([{ package: "sneaky", version: "1.0.0", path: null }], root);
208
+ expect(problems[0].message).toMatch(/must name a \.json file/);
209
+ expect(existsSync(join(root, "node_modules/sneaky/IMPORTED"))).toBe(false);
210
+ });
211
+ });
212
+
213
+ describe("resolveKind: overlapping probes", () => {
214
+ const kind = (name: string, precedence: number, file: string): MemberKind => ({
215
+ name,
216
+ description: name,
217
+ precedence,
218
+ probe: { anyFile: [file] },
219
+ shape: "member",
220
+ outputs: { from: "declared", names: [] },
221
+ source: "test",
222
+ });
223
+
224
+ test("the highest precedence decides", () => {
225
+ const registry = createKindRegistry([kind("terraform", 400, "main.tf")]);
226
+ const tree = workingTree(dir({ "infra/main.tf": "", "infra/chant.config.ts": "" }));
227
+ const r = resolveKind(registry, tree, "infra");
228
+ expect(r.claims.map((k) => k.name)).toEqual(["chant", "terraform"]);
229
+ expect(r.winner?.name).toBe("chant");
230
+ expect(r.tie).toEqual([]);
231
+ });
232
+
233
+ test("equal highest precedences are a tie, and there is no winner", () => {
234
+ const registry = createKindRegistry([kind("terraform", 400, "main.tf"), kind("opentofu", 400, "main.tf")]);
235
+ const r = resolveKind(registry, workingTree(dir({ "main.tf": "" })), "");
236
+ expect(r.winner).toBeUndefined();
237
+ expect(r.tie.map((k) => k.name)).toEqual(["opentofu", "terraform"]);
238
+ });
239
+
240
+ test("other and examples never claim, and exclude leaves a kind out", () => {
241
+ const tree = workingTree(dir({ "chant.workspace.json": "{}", "chant.config.ts": "" }));
242
+ expect(resolveKind(builtinKindRegistry(), tree, "").winner?.name).toBe("workspace");
243
+ expect(resolveKind(builtinKindRegistry(), tree, "", ["workspace"]).winner?.name).toBe("chant");
244
+ expect(resolveKind(builtinKindRegistry(), workingTree(dir({ "README.md": "" })), "").claims).toEqual([]);
245
+ });
246
+ });
247
+
248
+ describe("file probes: names with *, and blocks (#2545)", () => {
249
+ const kind = (probe: MemberKind["probe"]): MemberKind => ({ name: "k", description: "k", precedence: 400, probe, shape: "member", outputs: { from: "declared", names: [] }, source: "test" });
250
+
251
+ test("a * in anyFile matches any run of characters in one name, directly in the directory", () => {
252
+ const tree = workingTree(dir({ "a/network.tf": "", "b/modules/net/main.tf": "", "c/main.tfvars": "", "d/x.tf.json": "" }));
253
+ const tf = kind({ anyFile: ["*.tf"] });
254
+ expect(["a", "b", "c", "d"].map((d) => probeKind(tf, tree, d))).toEqual([true, false, false, false]);
255
+ expect(probeKind(kind({ anyFile: ["*.tf", "*.tf.json"] }), tree, "d")).toBe(true);
256
+ });
257
+
258
+ test("anyBlock finds an unlabelled block opened on a line of its own, in the files it names", () => {
259
+ const tree = workingTree(
260
+ dir({
261
+ "nested/main.tf": 'terraform {\n live {\n estate = "e"\n }\n}\n',
262
+ "tight/main.tf": "live{\n}\n",
263
+ "comment/main.tf": "# live {\n",
264
+ "attribute/main.tf": "locals {\n live = true\n}\n",
265
+ "labelled/main.tf": 'live "x" {\n}\n',
266
+ "other-file/notes.hcl": "live {\n}\n",
267
+ }),
268
+ );
269
+ const live = kind({ anyBlock: { in: ["*.tf"], blocks: ["live"] } });
270
+ expect(["nested", "tight", "comment", "attribute", "labelled", "other-file"].map((d) => probeKind(live, tree, d))).toEqual([true, true, false, false, false, false]);
271
+ });
272
+
273
+ test("a probe passes when any clause does, and its kind claims the directory", () => {
274
+ const k = kind({ anyFile: ["estate.chdf.hcl"], anyBlock: { in: ["*.tf"], blocks: ["live"] } });
275
+ const tree = workingTree(dir({ "s/estate.chdf.hcl": "", "s/main.tf": "", "i/main.tf": "live {}\n", "n/main.tf": "" }));
276
+ expect(["s", "i", "n"].map((d) => probeKind(k, tree, d))).toEqual([true, true, false]);
277
+ expect(resolveKind(createKindRegistry([k]), tree, "i").winner?.name).toBe("k");
278
+ });
279
+
280
+ test("the schema takes anyBlock, refuses an empty probe, and parseKindData keeps only the clauses given", () => {
281
+ const both = { ...tf, probe: { anyFile: ["estate.chdf.hcl"], anyBlock: { in: ["*.tf"], blocks: ["live"] } } };
282
+ expect(parseKindData(kindsFile([both]), "p").kinds[0].probe).toEqual(both.probe);
283
+ expect(parseKindData(kindsFile([tf]), "p").kinds[0].probe).toEqual({ anyFile: ["main.tf"] });
284
+ expect(parseKindData(kindsFile([{ ...tf, probe: {} }]), "p").problems[0]).toMatch(/probe/);
285
+ expect(parseKindData(kindsFile([{ ...tf, probe: { anyBlock: { in: ["*.tf"], blocks: ["live {"] } } }]), "p").problems[0]).toMatch(/blocks/);
286
+ expect(parseKindData(kindsFile([{ ...tf, probe: { anyBlock: { in: ["../x.tf"], blocks: ["live"] } } }]), "p").problems[0]).toMatch(/in/);
287
+ });
288
+ });