@intentius/chant 0.79.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 (462) hide show
  1. package/dist/audit/core.d.ts.map +1 -1
  2. package/dist/audit/discover.d.ts +30 -0
  3. package/dist/audit/discover.d.ts.map +1 -1
  4. package/dist/audit/report-html.d.ts +5 -0
  5. package/dist/audit/report-html.d.ts.map +1 -1
  6. package/dist/audit/report-model.d.ts +13 -0
  7. package/dist/audit/report-model.d.ts.map +1 -1
  8. package/dist/audit/terraform-state.d.ts +13 -0
  9. package/dist/audit/terraform-state.d.ts.map +1 -1
  10. package/dist/cli/command-group.d.ts +6 -0
  11. package/dist/cli/command-group.d.ts.map +1 -1
  12. package/dist/cli/commands/audit.d.ts +44 -3
  13. package/dist/cli/commands/audit.d.ts.map +1 -1
  14. package/dist/cli/commands/check-lexicon-examples.d.ts.map +1 -1
  15. package/dist/cli/commands/check-lexicon.d.ts +8 -0
  16. package/dist/cli/commands/check-lexicon.d.ts.map +1 -1
  17. package/dist/cli/commands/doctor.d.ts.map +1 -1
  18. package/dist/cli/commands/import-agents.d.ts.map +1 -1
  19. package/dist/cli/commands/init.d.ts +6 -0
  20. package/dist/cli/commands/init.d.ts.map +1 -1
  21. package/dist/cli/commands/lint.d.ts.map +1 -1
  22. package/dist/cli/commands/onboard.d.ts.map +1 -1
  23. package/dist/cli/commands/update.d.ts +6 -0
  24. package/dist/cli/commands/update.d.ts.map +1 -1
  25. package/dist/cli/commands/vendor.d.ts +5 -0
  26. package/dist/cli/commands/vendor.d.ts.map +1 -1
  27. package/dist/cli/conflict-check.d.ts +6 -0
  28. package/dist/cli/conflict-check.d.ts.map +1 -1
  29. package/dist/cli/handlers/build.d.ts.map +1 -1
  30. package/dist/cli/handlers/components.d.ts.map +1 -1
  31. package/dist/cli/handlers/dev.d.ts.map +1 -1
  32. package/dist/cli/handlers/fan-out.d.ts.map +1 -1
  33. package/dist/cli/handlers/init.d.ts.map +1 -1
  34. package/dist/cli/handlers/lifecycle.d.ts.map +1 -1
  35. package/dist/cli/handlers/misc.d.ts.map +1 -1
  36. package/dist/cli/handlers/operator.d.ts +7 -0
  37. package/dist/cli/handlers/operator.d.ts.map +1 -1
  38. package/dist/cli/handlers/promote.d.ts +20 -0
  39. package/dist/cli/handlers/promote.d.ts.map +1 -0
  40. package/dist/cli/handlers/run-generate.d.ts +39 -0
  41. package/dist/cli/handlers/run-generate.d.ts.map +1 -0
  42. package/dist/cli/handlers/run.d.ts +4 -1
  43. package/dist/cli/handlers/run.d.ts.map +1 -1
  44. package/dist/cli/handlers/vendor.d.ts +8 -3
  45. package/dist/cli/handlers/vendor.d.ts.map +1 -1
  46. package/dist/cli/main.d.ts +12 -0
  47. package/dist/cli/main.d.ts.map +1 -1
  48. package/dist/cli/mcp/op-tools.d.ts.map +1 -1
  49. package/dist/cli/plugins.d.ts +14 -0
  50. package/dist/cli/plugins.d.ts.map +1 -1
  51. package/dist/cli/registry.d.ts +46 -1
  52. package/dist/cli/registry.d.ts.map +1 -1
  53. package/dist/codegen/fetch.d.ts +10 -0
  54. package/dist/codegen/fetch.d.ts.map +1 -1
  55. package/dist/components/capability-plugin-loader.d.ts.map +1 -1
  56. package/dist/components/cli-support.d.ts +3 -1
  57. package/dist/components/cli-support.d.ts.map +1 -1
  58. package/dist/components/discover.d.ts.map +1 -1
  59. package/dist/components/driver.d.ts +16 -2
  60. package/dist/components/driver.d.ts.map +1 -1
  61. package/dist/components/fan-out-support.d.ts.map +1 -1
  62. package/dist/components/gate-plan.d.ts +40 -0
  63. package/dist/components/gate-plan.d.ts.map +1 -0
  64. package/dist/components/pilots/alb-ecs.pilot.d.ts +7 -1
  65. package/dist/components/pilots/alb-ecs.pilot.d.ts.map +1 -1
  66. package/dist/components/presets/ecs-fargate.d.ts +10 -4
  67. package/dist/components/presets/ecs-fargate.d.ts.map +1 -1
  68. package/dist/components/promote.d.ts +264 -0
  69. package/dist/components/promote.d.ts.map +1 -0
  70. package/dist/components/sandbox/driver.d.ts.map +1 -1
  71. package/dist/components/sandbox/run.d.ts.map +1 -1
  72. package/dist/components/verbs/build-archive.d.ts +22 -11
  73. package/dist/components/verbs/build-archive.d.ts.map +1 -1
  74. package/dist/components/verbs/run-agent.d.ts.map +1 -1
  75. package/dist/config-static.d.ts +59 -0
  76. package/dist/config-static.d.ts.map +1 -0
  77. package/dist/config.d.ts +104 -5
  78. package/dist/config.d.ts.map +1 -1
  79. package/dist/content-digest.d.ts +17 -0
  80. package/dist/content-digest.d.ts.map +1 -0
  81. package/dist/discovery/files.d.ts +48 -4
  82. package/dist/discovery/files.d.ts.map +1 -1
  83. package/dist/discovery/fold-import.d.ts +32 -1
  84. package/dist/discovery/fold-import.d.ts.map +1 -1
  85. package/dist/discovery/index.d.ts.map +1 -1
  86. package/dist/discovery/sandbox/driver.d.ts +20 -0
  87. package/dist/discovery/sandbox/driver.d.ts.map +1 -1
  88. package/dist/discovery/sandbox/fork.d.ts +20 -0
  89. package/dist/discovery/sandbox/fork.d.ts.map +1 -1
  90. package/dist/discovery/walk.d.ts +119 -0
  91. package/dist/discovery/walk.d.ts.map +1 -0
  92. package/dist/graph-ir.d.ts +17 -1
  93. package/dist/graph-ir.d.ts.map +1 -1
  94. package/dist/join-key.d.ts +31 -0
  95. package/dist/join-key.d.ts.map +1 -0
  96. package/dist/lexicon-module.d.ts +110 -0
  97. package/dist/lexicon-module.d.ts.map +1 -0
  98. package/dist/lexicon.d.ts +82 -1
  99. package/dist/lexicon.d.ts.map +1 -1
  100. package/dist/lifecycle/build-ledger-store.d.ts.map +1 -1
  101. package/dist/lifecycle/digest.d.ts +5 -1
  102. package/dist/lifecycle/digest.d.ts.map +1 -1
  103. package/dist/lifecycle/gate-ledger.d.ts +20 -5
  104. package/dist/lifecycle/gate-ledger.d.ts.map +1 -1
  105. package/dist/lifecycle/git.d.ts +64 -0
  106. package/dist/lifecycle/git.d.ts.map +1 -1
  107. package/dist/lifecycle/lease.d.ts +8 -1
  108. package/dist/lifecycle/lease.d.ts.map +1 -1
  109. package/dist/lifecycle/legacy-digest.d.ts +59 -0
  110. package/dist/lifecycle/legacy-digest.d.ts.map +1 -0
  111. package/dist/lifecycle/member-ledger.d.ts +67 -0
  112. package/dist/lifecycle/member-ledger.d.ts.map +1 -0
  113. package/dist/lifecycle/plan-digest.d.ts +4 -3
  114. package/dist/lifecycle/plan-digest.d.ts.map +1 -1
  115. package/dist/lifecycle/release-ledger.d.ts +42 -1
  116. package/dist/lifecycle/release-ledger.d.ts.map +1 -1
  117. package/dist/lifecycle/status.d.ts +90 -5
  118. package/dist/lifecycle/status.d.ts.map +1 -1
  119. package/dist/lint/rules/comp/comp003-mutating-no-rollback.d.ts +2 -3
  120. package/dist/lint/rules/comp/comp003-mutating-no-rollback.d.ts.map +1 -1
  121. package/dist/op/activities/activity-contracts.d.ts +32 -0
  122. package/dist/op/activities/activity-contracts.d.ts.map +1 -1
  123. package/dist/op/activities/index.d.ts +2 -0
  124. package/dist/op/activities/index.d.ts.map +1 -1
  125. package/dist/op/activities/propose-upgrade.d.ts +75 -0
  126. package/dist/op/activities/propose-upgrade.d.ts.map +1 -0
  127. package/dist/op/activity-contract-registry.d.ts.map +1 -1
  128. package/dist/op/activity-registry.d.ts.map +1 -1
  129. package/dist/op/discover.d.ts.map +1 -1
  130. package/dist/op/gate-approval.d.ts.map +1 -1
  131. package/dist/op/gate-name.d.ts +7 -0
  132. package/dist/op/gate-name.d.ts.map +1 -1
  133. package/dist/op/gate-summary.d.ts +16 -0
  134. package/dist/op/gate-summary.d.ts.map +1 -1
  135. package/dist/op/gate.d.ts +16 -2
  136. package/dist/op/gate.d.ts.map +1 -1
  137. package/dist/op/generate-pipeline.d.ts.map +1 -1
  138. package/dist/op/runtimes/local.d.ts.map +1 -1
  139. package/dist/project-root.d.ts +26 -0
  140. package/dist/project-root.d.ts.map +1 -1
  141. package/dist/workspace/__fixtures__/contract-repo.d.ts +37 -0
  142. package/dist/workspace/__fixtures__/contract-repo.d.ts.map +1 -0
  143. package/dist/workspace/__fixtures__/two-member-workspace.d.ts +11 -0
  144. package/dist/workspace/__fixtures__/two-member-workspace.d.ts.map +1 -0
  145. package/dist/workspace/checks/generated.d.ts +156 -0
  146. package/dist/workspace/checks/generated.d.ts.map +1 -0
  147. package/dist/workspace/checks/ledgers.d.ts +78 -0
  148. package/dist/workspace/checks/ledgers.d.ts.map +1 -0
  149. package/dist/workspace/checks/links.d.ts +25 -0
  150. package/dist/workspace/checks/links.d.ts.map +1 -0
  151. package/dist/workspace/checks/pipelines.d.ts +76 -0
  152. package/dist/workspace/checks/pipelines.d.ts.map +1 -0
  153. package/dist/workspace/checks.d.ts +161 -0
  154. package/dist/workspace/checks.d.ts.map +1 -0
  155. package/dist/workspace/compose-graph.d.ts +115 -0
  156. package/dist/workspace/compose-graph.d.ts.map +1 -0
  157. package/dist/workspace/compose-reports.d.ts +68 -0
  158. package/dist/workspace/compose-reports.d.ts.map +1 -0
  159. package/dist/workspace/declaration.d.ts +220 -0
  160. package/dist/workspace/declaration.d.ts.map +1 -0
  161. package/dist/workspace/declaration.schema.json +434 -0
  162. package/dist/workspace/generated-files.d.ts +65 -0
  163. package/dist/workspace/generated-files.d.ts.map +1 -0
  164. package/dist/workspace/glob.d.ts +13 -0
  165. package/dist/workspace/glob.d.ts.map +1 -0
  166. package/dist/workspace/graph-cli.d.ts +75 -0
  167. package/dist/workspace/graph-cli.d.ts.map +1 -0
  168. package/dist/workspace/init.d.ts +96 -0
  169. package/dist/workspace/init.d.ts.map +1 -0
  170. package/dist/workspace/jsonc.d.ts +38 -0
  171. package/dist/workspace/jsonc.d.ts.map +1 -0
  172. package/dist/workspace/kinds.d.ts +192 -0
  173. package/dist/workspace/kinds.d.ts.map +1 -0
  174. package/dist/workspace/lineage-check.d.ts +84 -0
  175. package/dist/workspace/lineage-check.d.ts.map +1 -0
  176. package/dist/workspace/lineage-cli.d.ts +31 -0
  177. package/dist/workspace/lineage-cli.d.ts.map +1 -0
  178. package/dist/workspace/lineage-init.d.ts +101 -0
  179. package/dist/workspace/lineage-init.d.ts.map +1 -0
  180. package/dist/workspace/lineage-lock.d.ts +258 -0
  181. package/dist/workspace/lineage-lock.d.ts.map +1 -0
  182. package/dist/workspace/lineage-merge.d.ts +14 -0
  183. package/dist/workspace/lineage-merge.d.ts.map +1 -0
  184. package/dist/workspace/lineage-migrations.d.ts +178 -0
  185. package/dist/workspace/lineage-migrations.d.ts.map +1 -0
  186. package/dist/workspace/lineage-update.d.ts +59 -0
  187. package/dist/workspace/lineage-update.d.ts.map +1 -0
  188. package/dist/workspace/lineage-upgrade-cli.d.ts +40 -0
  189. package/dist/workspace/lineage-upgrade-cli.d.ts.map +1 -0
  190. package/dist/workspace/lineage-upgrade.d.ts +146 -0
  191. package/dist/workspace/lineage-upgrade.d.ts.map +1 -0
  192. package/dist/workspace/lineage-vendor.d.ts +63 -0
  193. package/dist/workspace/lineage-vendor.d.ts.map +1 -0
  194. package/dist/workspace/lineage-version.d.ts +32 -0
  195. package/dist/workspace/lineage-version.d.ts.map +1 -0
  196. package/dist/workspace/links.d.ts +150 -0
  197. package/dist/workspace/links.d.ts.map +1 -0
  198. package/dist/workspace/ls.d.ts +99 -0
  199. package/dist/workspace/ls.d.ts.map +1 -0
  200. package/dist/workspace/member-commands.d.ts +139 -0
  201. package/dist/workspace/member-commands.d.ts.map +1 -0
  202. package/dist/workspace/member-handles.d.ts +54 -0
  203. package/dist/workspace/member-handles.d.ts.map +1 -0
  204. package/dist/workspace/member-pipeline.d.ts +143 -0
  205. package/dist/workspace/member-pipeline.d.ts.map +1 -0
  206. package/dist/workspace/member-run.d.ts +67 -0
  207. package/dist/workspace/member-run.d.ts.map +1 -0
  208. package/dist/workspace/reason-codes.d.ts +65 -0
  209. package/dist/workspace/reason-codes.d.ts.map +1 -0
  210. package/dist/workspace/record-source.d.ts +22 -0
  211. package/dist/workspace/record-source.d.ts.map +1 -0
  212. package/dist/workspace/records-cli.d.ts +81 -0
  213. package/dist/workspace/records-cli.d.ts.map +1 -0
  214. package/dist/workspace/records.d.ts +118 -0
  215. package/dist/workspace/records.d.ts.map +1 -0
  216. package/dist/workspace/root-refusal.d.ts +53 -0
  217. package/dist/workspace/root-refusal.d.ts.map +1 -0
  218. package/dist/workspace/status.d.ts +150 -0
  219. package/dist/workspace/status.d.ts.map +1 -0
  220. package/dist/workspace/template-manifest.d.ts +90 -0
  221. package/dist/workspace/template-manifest.d.ts.map +1 -0
  222. package/dist/workspace/tree.d.ts +36 -0
  223. package/dist/workspace/tree.d.ts.map +1 -0
  224. package/dist/workspace/trust/attestor.d.ts +67 -0
  225. package/dist/workspace/trust/attestor.d.ts.map +1 -0
  226. package/dist/workspace/trust/policy.d.ts +111 -0
  227. package/dist/workspace/trust/policy.d.ts.map +1 -0
  228. package/dist/workspace/trust/provenance.d.ts +67 -0
  229. package/dist/workspace/trust/provenance.d.ts.map +1 -0
  230. package/dist/workspace/trust/ssh-commit.d.ts +43 -0
  231. package/dist/workspace/trust/ssh-commit.d.ts.map +1 -0
  232. package/dist/workspace/trust/test-repo.d.ts +42 -0
  233. package/dist/workspace/trust/test-repo.d.ts.map +1 -0
  234. package/dist/workspace/trust/verify-cli.d.ts +9 -0
  235. package/dist/workspace/trust/verify-cli.d.ts.map +1 -0
  236. package/dist/workspace/trust/verify.d.ts +66 -0
  237. package/dist/workspace/trust/verify.d.ts.map +1 -0
  238. package/dist/workspace/which-chant.d.ts +58 -0
  239. package/dist/workspace/which-chant.d.ts.map +1 -0
  240. package/dist/workspace/workspace-kinds.schema.json +133 -0
  241. package/package.json +4 -3
  242. package/src/audit/core.ts +13 -2
  243. package/src/audit/discover.ts +102 -36
  244. package/src/audit/report-html.ts +3 -1
  245. package/src/audit/report-model.ts +14 -1
  246. package/src/audit/terraform-state.test.ts +81 -1
  247. package/src/audit/terraform-state.ts +25 -0
  248. package/src/cli/command-group.ts +7 -0
  249. package/src/cli/commands/audit-truncation.test.ts +167 -0
  250. package/src/cli/commands/audit.ts +132 -29
  251. package/src/cli/commands/check-lexicon-examples.ts +2 -1
  252. package/src/cli/commands/check-lexicon.ts +24 -0
  253. package/src/cli/commands/doctor.test.ts +30 -0
  254. package/src/cli/commands/doctor.ts +21 -0
  255. package/src/cli/commands/import-agents.ts +15 -4
  256. package/src/cli/commands/init.ts +76 -25
  257. package/src/cli/commands/lint.test.ts +52 -1
  258. package/src/cli/commands/lint.ts +41 -71
  259. package/src/cli/commands/onboard.ts +15 -0
  260. package/src/cli/commands/update.ts +18 -5
  261. package/src/cli/commands/vendor.ts +11 -6
  262. package/src/cli/conflict-check.test.ts +18 -1
  263. package/src/cli/conflict-check.ts +13 -0
  264. package/src/cli/discovery-skip.test.ts +168 -0
  265. package/src/cli/handlers/__snapshots__/build-member.test.ts.snap +38 -0
  266. package/src/cli/handlers/build-member.test.ts +127 -0
  267. package/src/cli/handlers/build.test.ts +11 -0
  268. package/src/cli/handlers/build.ts +35 -1
  269. package/src/cli/handlers/components.test.ts +127 -0
  270. package/src/cli/handlers/components.ts +70 -8
  271. package/src/cli/handlers/dev.ts +6 -1
  272. package/src/cli/handlers/fan-out.ts +9 -2
  273. package/src/cli/handlers/graph.test.ts +20 -0
  274. package/src/cli/handlers/graph.ts +4 -2
  275. package/src/cli/handlers/init.ts +54 -1
  276. package/src/cli/handlers/lifecycle.ts +2 -1
  277. package/src/cli/handlers/misc.ts +16 -0
  278. package/src/cli/handlers/operator.test.ts +68 -0
  279. package/src/cli/handlers/operator.ts +39 -10
  280. package/src/cli/handlers/promote.test.ts +377 -0
  281. package/src/cli/handlers/promote.ts +391 -0
  282. package/src/cli/handlers/run-generate-member.test.ts +83 -0
  283. package/src/cli/handlers/run-generate.test.ts +158 -0
  284. package/src/cli/handlers/run-generate.ts +168 -0
  285. package/src/cli/handlers/run.test.ts +47 -0
  286. package/src/cli/handlers/run.ts +41 -6
  287. package/src/cli/handlers/vendor.ts +147 -43
  288. package/src/cli/main.test.ts +137 -1
  289. package/src/cli/main.ts +290 -24
  290. package/src/cli/mcp/op-tools.ts +2 -1
  291. package/src/cli/path-lexicon-messages.test.ts +152 -0
  292. package/src/cli/plugins.ts +43 -5
  293. package/src/cli/registry.ts +46 -1
  294. package/src/cli/static-config-read.test.ts +208 -0
  295. package/src/codegen/fetch.test.ts +63 -1
  296. package/src/codegen/fetch.ts +30 -1
  297. package/src/components/__fixtures__/alb-ecs-service.json +0 -8
  298. package/src/components/capability-plugin-loader.ts +3 -1
  299. package/src/components/cli-support.ts +9 -3
  300. package/src/components/discover.test.ts +27 -57
  301. package/src/components/discover.ts +20 -51
  302. package/src/components/driver.test.ts +90 -12
  303. package/src/components/driver.ts +43 -5
  304. package/src/components/fan-out-support.ts +2 -1
  305. package/src/components/gate-plan.ts +53 -0
  306. package/src/components/pilots/README.md +1 -1
  307. package/src/components/pilots/alb-ecs.pilot.ts +7 -7
  308. package/src/components/presets/ecs-fargate.ts +10 -5
  309. package/src/components/promote.test.ts +645 -0
  310. package/src/components/promote.ts +641 -0
  311. package/src/components/sandbox/driver.ts +8 -8
  312. package/src/components/sandbox/run.ts +4 -1
  313. package/src/components/verbs/build-archive.ts +27 -21
  314. package/src/components/verbs/run-agent.ts +3 -5
  315. package/src/config-static.test.ts +136 -0
  316. package/src/config-static.ts +232 -0
  317. package/src/config.test.ts +40 -0
  318. package/src/config.ts +161 -11
  319. package/src/content-digest.test.ts +120 -0
  320. package/src/content-digest.ts +21 -0
  321. package/src/discovery/files.test.ts +127 -1
  322. package/src/discovery/files.ts +91 -59
  323. package/src/discovery/fold-import.ts +127 -9
  324. package/src/discovery/index.ts +1 -0
  325. package/src/discovery/sandbox/driver.ts +73 -35
  326. package/src/discovery/sandbox/fork-diagnostic.test.ts +136 -13
  327. package/src/discovery/sandbox/fork.ts +83 -43
  328. package/src/discovery/sandbox/path-lexicon-multifile.test.ts +285 -0
  329. package/src/discovery/sandbox/path-lexicon-parity.test.ts +162 -0
  330. package/src/discovery/walk.test.ts +263 -0
  331. package/src/discovery/walk.ts +334 -0
  332. package/src/effect-receipt.ts +2 -2
  333. package/src/graph-ir.ts +18 -1
  334. package/src/join-key.test.ts +25 -0
  335. package/src/join-key.ts +39 -0
  336. package/src/lexicon-module.test.ts +203 -0
  337. package/src/lexicon-module.ts +215 -0
  338. package/src/lexicon.ts +105 -1
  339. package/src/lifecycle/build-ledger-store.ts +13 -2
  340. package/src/lifecycle/digest.ts +9 -5
  341. package/src/lifecycle/gate-ledger.test.ts +20 -0
  342. package/src/lifecycle/gate-ledger.ts +28 -6
  343. package/src/lifecycle/git.test.ts +13 -1
  344. package/src/lifecycle/git.ts +221 -176
  345. package/src/lifecycle/lease.ts +25 -10
  346. package/src/lifecycle/legacy-digest.test.ts +206 -0
  347. package/src/lifecycle/legacy-digest.ts +103 -0
  348. package/src/lifecycle/member-ledger.test.ts +319 -0
  349. package/src/lifecycle/member-ledger.ts +145 -0
  350. package/src/lifecycle/plan-digest.ts +7 -7
  351. package/src/lifecycle/release-ledger.test.ts +20 -0
  352. package/src/lifecycle/release-ledger.ts +53 -3
  353. package/src/lifecycle/status.test.ts +151 -1
  354. package/src/lifecycle/status.ts +228 -7
  355. package/src/lint/rules/comp/comp003-mutating-no-rollback.ts +2 -3
  356. package/src/op/activities/activity-contracts.ts +37 -0
  357. package/src/op/activities/index.ts +10 -0
  358. package/src/op/activities/propose-upgrade.ts +206 -0
  359. package/src/op/activity-contract-registry.ts +18 -4
  360. package/src/op/activity-registry.ts +26 -0
  361. package/src/op/discover.test.ts +14 -4
  362. package/src/op/discover.ts +19 -23
  363. package/src/op/gate-approval.ts +3 -1
  364. package/src/op/gate-name.ts +8 -0
  365. package/src/op/gate-summary.test.ts +14 -0
  366. package/src/op/gate-summary.ts +25 -2
  367. package/src/op/gate.ts +36 -7
  368. package/src/op/generate-pipeline.test.ts +14 -5
  369. package/src/op/generate-pipeline.ts +2 -1
  370. package/src/op/runtimes/local.ts +2 -1
  371. package/src/project-root.test.ts +66 -1
  372. package/src/project-root.ts +48 -0
  373. package/src/workspace/__fixtures__/contract-repo.ts +100 -0
  374. package/src/workspace/__fixtures__/two-member-workspace.ts +35 -0
  375. package/src/workspace/behold-kinds.test.ts +162 -0
  376. package/src/workspace/check-contract.test.ts +127 -0
  377. package/src/workspace/check.schema.json +347 -0
  378. package/src/workspace/checks/generated.test.ts +287 -0
  379. package/src/workspace/checks/generated.ts +475 -0
  380. package/src/workspace/checks/ledgers.test.ts +139 -0
  381. package/src/workspace/checks/ledgers.ts +246 -0
  382. package/src/workspace/checks/links.ts +183 -0
  383. package/src/workspace/checks/pipelines.test.ts +133 -0
  384. package/src/workspace/checks/pipelines.ts +225 -0
  385. package/src/workspace/checks-repo.test.ts +71 -0
  386. package/src/workspace/checks.test.ts +450 -0
  387. package/src/workspace/checks.ts +461 -0
  388. package/src/workspace/compose-graph.test.ts +146 -0
  389. package/src/workspace/compose-graph.ts +228 -0
  390. package/src/workspace/compose-reports.test.ts +109 -0
  391. package/src/workspace/compose-reports.ts +186 -0
  392. package/src/workspace/declaration.schema.json +434 -0
  393. package/src/workspace/declaration.test.ts +347 -0
  394. package/src/workspace/declaration.ts +623 -0
  395. package/src/workspace/generated-files.test.ts +114 -0
  396. package/src/workspace/generated-files.ts +94 -0
  397. package/src/workspace/glob.ts +49 -0
  398. package/src/workspace/graph-cli.ts +220 -0
  399. package/src/workspace/graph-contract.test.ts +204 -0
  400. package/src/workspace/graph.schema.json +662 -0
  401. package/src/workspace/init.test.ts +223 -0
  402. package/src/workspace/init.ts +585 -0
  403. package/src/workspace/jsonc.test.ts +65 -0
  404. package/src/workspace/jsonc.ts +231 -0
  405. package/src/workspace/kinds.test.ts +288 -0
  406. package/src/workspace/kinds.ts +506 -0
  407. package/src/workspace/lineage-check.ts +264 -0
  408. package/src/workspace/lineage-cli.ts +114 -0
  409. package/src/workspace/lineage-init.test.ts +231 -0
  410. package/src/workspace/lineage-init.ts +365 -0
  411. package/src/workspace/lineage-lock.test.ts +224 -0
  412. package/src/workspace/lineage-lock.ts +357 -0
  413. package/src/workspace/lineage-merge.ts +44 -0
  414. package/src/workspace/lineage-migrations.test.ts +178 -0
  415. package/src/workspace/lineage-migrations.ts +343 -0
  416. package/src/workspace/lineage-update.ts +160 -0
  417. package/src/workspace/lineage-upgrade-cli.ts +151 -0
  418. package/src/workspace/lineage-upgrade.test.ts +474 -0
  419. package/src/workspace/lineage-upgrade.ts +624 -0
  420. package/src/workspace/lineage-vendor.test.ts +126 -0
  421. package/src/workspace/lineage-vendor.ts +215 -0
  422. package/src/workspace/lineage-version.ts +127 -0
  423. package/src/workspace/links.test.ts +344 -0
  424. package/src/workspace/links.ts +331 -0
  425. package/src/workspace/ls-contract.test.ts +200 -0
  426. package/src/workspace/ls.schema.json +190 -0
  427. package/src/workspace/ls.ts +254 -0
  428. package/src/workspace/member-commands.e2e.test.ts +244 -0
  429. package/src/workspace/member-commands.test.ts +217 -0
  430. package/src/workspace/member-commands.ts +540 -0
  431. package/src/workspace/member-handles.test.ts +76 -0
  432. package/src/workspace/member-handles.ts +228 -0
  433. package/src/workspace/member-pipeline.test.ts +135 -0
  434. package/src/workspace/member-pipeline.ts +317 -0
  435. package/src/workspace/member-run.ts +146 -0
  436. package/src/workspace/read-contract.test.ts +129 -0
  437. package/src/workspace/reason-codes.test.ts +113 -0
  438. package/src/workspace/reason-codes.ts +81 -0
  439. package/src/workspace/record-source.ts +117 -0
  440. package/src/workspace/records-cli.ts +198 -0
  441. package/src/workspace/records-contract.test.ts +93 -0
  442. package/src/workspace/records.schema.json +150 -0
  443. package/src/workspace/records.test.ts +243 -0
  444. package/src/workspace/records.ts +384 -0
  445. package/src/workspace/root-refusal.ts +97 -0
  446. package/src/workspace/status-contract.test.ts +308 -0
  447. package/src/workspace/status.schema.json +210 -0
  448. package/src/workspace/status.ts +412 -0
  449. package/src/workspace/template-manifest.test.ts +79 -0
  450. package/src/workspace/template-manifest.ts +221 -0
  451. package/src/workspace/tree.ts +132 -0
  452. package/src/workspace/trust/attestor.ts +93 -0
  453. package/src/workspace/trust/policy.ts +306 -0
  454. package/src/workspace/trust/provenance.ts +181 -0
  455. package/src/workspace/trust/ssh-commit.ts +138 -0
  456. package/src/workspace/trust/test-repo.ts +138 -0
  457. package/src/workspace/trust/trust.test.ts +397 -0
  458. package/src/workspace/trust/verify-cli.ts +56 -0
  459. package/src/workspace/trust/verify.ts +194 -0
  460. package/src/workspace/which-chant.test.ts +117 -0
  461. package/src/workspace/which-chant.ts +151 -0
  462. package/src/workspace/workspace-kinds.schema.json +133 -0
@@ -0,0 +1,146 @@
1
+ /**
2
+ * `chant workspace member-run` (#2537): the one process a toolchain runs for
3
+ * `chant workspace build|lint|audit|graph`.
4
+ *
5
+ * `member-commands.ts` groups the members by the chant each one resolves
6
+ * (#2524 D3, "one process per toolchain identity") and spawns that chant's
7
+ * `workspace member-run` once per group. The request arrives as JSON on
8
+ * stdin: the level-0 command line for each member, and the member's
9
+ * directory. This process runs them one after another, in-process, from
10
+ * inside each directory, so every member gets what `chant <verb> .` would
11
+ * print there. It answers on stdout with one {@link PROTOCOL_PREFIX} line per
12
+ * member, after a header line naming the protocol and this chant's version.
13
+ *
14
+ * A chant older than this command has no header to print. The caller sees
15
+ * that and falls back to one level-0 process per member.
16
+ *
17
+ * Internal: not listed in `chant --help`, and the protocol may change with
18
+ * the caller, which is always a chant of the same or a newer release.
19
+ */
20
+
21
+ import { ENV_VAR } from "../env";
22
+ import type { CommandContext } from "../cli/registry";
23
+ import { readerVersion } from "./declaration";
24
+
25
+ /** Marks the lines of this protocol on stdout; anything else there is stray output. */
26
+ export const PROTOCOL_PREFIX = "@@chant-member-run@@ ";
27
+ export const MEMBER_RUN_PROTOCOL = 1;
28
+
29
+ export interface MemberRunRequest {
30
+ protocol: number;
31
+ units: MemberRunUnit[];
32
+ }
33
+
34
+ export interface MemberRunUnit {
35
+ /** Echoed back on the answer line. */
36
+ id: string;
37
+ /** The member's directory, absolute. The command runs from inside it. */
38
+ dir: string;
39
+ /** The level-0 command line, such as `["lint", ".", "--format", "sarif"]`. */
40
+ argv: string[];
41
+ /**
42
+ * Directories (relative to `dir`) to leave out of discovery. Set for member
43
+ * `.`, whose project is the root minus every other member. Kept in the
44
+ * protocol for a member-run from before #2527, which applied it itself;
45
+ * from #2527 on, discovery reads the same set from the declaration.
46
+ */
47
+ exclude?: string[];
48
+ }
49
+
50
+ export type MemberRunLine =
51
+ | { type: "header"; protocol: number; chant: string }
52
+ | { type: "result"; id: string; exitCode: number; stdout: string; stderr: string };
53
+
54
+ /** A `process.exit` inside a command, turned into a value. */
55
+ class ExitRequest extends Error {
56
+ constructor(readonly code: number) {
57
+ super(`process.exit(${code})`);
58
+ }
59
+ }
60
+
61
+ type Write = typeof process.stdout.write;
62
+
63
+ /**
64
+ * Run `fn` with stdout, stderr and `process.exit` captured. Commands print
65
+ * through `console`, which writes through the stream's `write` at call time,
66
+ * so replacing `write` catches everything they print.
67
+ */
68
+ export async function captureRun(fn: () => Promise<number>): Promise<{ exitCode: number; stdout: string; stderr: string }> {
69
+ const out: string[] = [];
70
+ const err: string[] = [];
71
+ const sink =
72
+ (into: string[]): Write =>
73
+ ((chunk: string | Uint8Array, encoding?: unknown, cb?: unknown) => {
74
+ into.push(typeof chunk === "string" ? chunk : Buffer.from(chunk).toString("utf8"));
75
+ const done = typeof encoding === "function" ? encoding : cb;
76
+ if (typeof done === "function") (done as () => void)();
77
+ return true;
78
+ }) as Write;
79
+ const saved = { out: process.stdout.write, err: process.stderr.write, exit: process.exit };
80
+ process.stdout.write = sink(out);
81
+ process.stderr.write = sink(err);
82
+ process.exit = ((code?: number) => {
83
+ throw new ExitRequest(typeof code === "number" ? code : 0);
84
+ }) as typeof process.exit;
85
+ let exitCode: number;
86
+ try {
87
+ exitCode = await fn();
88
+ } catch (e) {
89
+ if (e instanceof ExitRequest) {
90
+ exitCode = e.code;
91
+ } else {
92
+ err.push(`${e instanceof Error ? e.message : String(e)}\n`);
93
+ exitCode = 1;
94
+ }
95
+ } finally {
96
+ process.stdout.write = saved.out;
97
+ process.stderr.write = saved.err;
98
+ process.exit = saved.exit;
99
+ }
100
+ return { exitCode, stdout: out.join(""), stderr: err.join("") };
101
+ }
102
+
103
+ function emit(line: MemberRunLine): void {
104
+ process.stdout.write(`${PROTOCOL_PREFIX}${JSON.stringify(line)}\n`);
105
+ }
106
+
107
+ async function readStdin(): Promise<string> {
108
+ const chunks: Buffer[] = [];
109
+ for await (const chunk of process.stdin) chunks.push(chunk as Buffer);
110
+ return Buffer.concat(chunks).toString("utf8");
111
+ }
112
+
113
+ /** Run each unit of `request` in turn, answering on stdout. */
114
+ export async function runMemberUnits(request: MemberRunRequest, run: (argv: string[]) => Promise<number>): Promise<number> {
115
+ emit({ type: "header", protocol: MEMBER_RUN_PROTOCOL, chant: readerVersion() });
116
+ const startDir = process.cwd();
117
+ const startEnv = process.env[ENV_VAR];
118
+ for (const unit of request.units) {
119
+ const result = await captureRun(async () => {
120
+ process.chdir(unit.dir);
121
+ // `unit.exclude` needs no action here: discovery reads the declaration
122
+ // itself and leaves member `.`'s exclusions out of every walk (#2527).
123
+ return run(unit.argv);
124
+ });
125
+ if (startEnv === undefined) delete process.env[ENV_VAR];
126
+ else process.env[ENV_VAR] = startEnv;
127
+ process.chdir(startDir);
128
+ emit({ type: "result", id: unit.id, ...result });
129
+ }
130
+ return 0;
131
+ }
132
+
133
+ export async function runWorkspaceMemberRun(_ctx: CommandContext, run: (argv: string[]) => Promise<number>): Promise<number> {
134
+ let request: MemberRunRequest;
135
+ try {
136
+ request = JSON.parse(await readStdin()) as MemberRunRequest;
137
+ } catch (err) {
138
+ console.error(`chant workspace member-run: the request on stdin is not JSON: ${err instanceof Error ? err.message : String(err)}`);
139
+ return 1;
140
+ }
141
+ if (request.protocol !== MEMBER_RUN_PROTOCOL || !Array.isArray(request.units)) {
142
+ console.error(`chant workspace member-run: this chant speaks protocol ${MEMBER_RUN_PROTOCOL}, and the request asks for ${String(request.protocol)}`);
143
+ return 1;
144
+ }
145
+ return runMemberUnits(request, run);
146
+ }
@@ -0,0 +1,129 @@
1
+ /**
2
+ * The read contract as a whole (#2536, #2524 D15): every output schema, run
3
+ * against the reference workspace (#2543) in this checkout.
4
+ *
5
+ * `reference-workspace/` is a nested workspace of the chant repo, with a
6
+ * `chant` member (`delivery`), three `other` members and decision records.
7
+ * Each read-contract command reads it here, and its output must validate
8
+ * against the command's schema: `ls`, `graph` (running delivery's real
9
+ * `chant graph`), `check`, `status` and `records`, in the working tree and,
10
+ * for `ls`, `graph` and `check`, at `HEAD` through `--at`.
11
+ *
12
+ * The schemas themselves are checked here too: each is a draft 2020-12
13
+ * document under `https://intentius.io/chant/schemas/workspace/<command>/v1/`,
14
+ * at the contract version this chant writes, naming the chant floor.
15
+ */
16
+
17
+ import { realpathSync } from "node:fs";
18
+ import { join } from "node:path";
19
+ import { pathToFileURL } from "node:url";
20
+ import { describe, expect, test } from "vitest";
21
+ import { contract, git, REPO, validSchema } from "./__fixtures__/contract-repo";
22
+ import checkSchema from "./check.schema.json";
23
+ import { workspaceGraph } from "./graph-cli";
24
+ import graphSchema from "./graph.schema.json";
25
+ import { runChecks } from "./lineage-check";
26
+ import { listWorkspace } from "./ls";
27
+ import lsSchema from "./ls.schema.json";
28
+ import { readerBin, type Toolchain } from "./member-commands";
29
+ import { READ_CONTRACT_FLOOR, READ_CONTRACT_VERSION } from "./reason-codes";
30
+ import { queryRecords } from "./records-cli";
31
+ import recordsSchema from "./records.schema.json";
32
+ import { workspaceStatus } from "./status";
33
+ import statusSchema from "./status.schema.json";
34
+
35
+ const FIXTURE = join(REPO, "reference-workspace");
36
+ const TIMEOUT = 240_000;
37
+
38
+ const SCHEMAS = { ls: lsSchema, graph: graphSchema, check: checkSchema, status: statusSchema, records: recordsSchema };
39
+
40
+ /** This checkout's chant, started the way the CLI starts it, for members with no toolchain of their own. */
41
+ const reader: Toolchain = {
42
+ command: [process.execPath, "--import", pathToFileURL(join(REPO, "node_modules", "tsx", "dist", "loader.mjs")).href, join(REPO, "packages", "core", "src", "cli", "main.ts")],
43
+ identity: realpathSync(readerBin()),
44
+ source: "reader",
45
+ };
46
+
47
+ describe("the read contract's schemas", () => {
48
+ test.each(Object.entries(SCHEMAS))("%s is a valid draft 2020-12 document at the contract version, naming the floor", (name, schema) => {
49
+ expect(validSchema(schema)).toBe(true);
50
+ expect(schema.$id).toBe(`https://intentius.io/chant/schemas/workspace/${name}/v${READ_CONTRACT_VERSION}/${name}.schema.json`);
51
+ for (const branch of ["result", "failure"] as const) {
52
+ const props = (schema.$defs as Record<string, { properties: Record<string, unknown> }>)[branch].properties;
53
+ expect(props.contract, `${name} ${branch}`).toEqual({ const: READ_CONTRACT_VERSION });
54
+ expect(props.$schema, `${name} ${branch}`).toEqual({ const: schema.$id });
55
+ }
56
+ expect(schema.description).toContain(`chant ${READ_CONTRACT_FLOOR} and newer`);
57
+ });
58
+ });
59
+
60
+ describe("every schema against the reference workspace (#2543)", () => {
61
+ const head = git(REPO, "rev-parse", "HEAD");
62
+
63
+ test("ls, in the working tree and at HEAD", () => {
64
+ const { expectValid } = contract(lsSchema);
65
+ for (const at of [undefined, "HEAD"]) {
66
+ const doc = listWorkspace({ cwd: join(FIXTURE, "delivery"), at });
67
+ expectValid(doc);
68
+ if ("error" in doc) throw new Error(doc.error.message);
69
+ expect(doc.workspace).toMatchObject({ name: "reference", root: "reference-workspace" });
70
+ expect(doc.at).toBe(at ? head : null);
71
+ expect(doc.members.map((m) => [m.name, m.readable])).toEqual([
72
+ ["app", true],
73
+ ["delivery", true],
74
+ ["design-client", true],
75
+ ["design", true],
76
+ ]);
77
+ }
78
+ });
79
+
80
+ test(
81
+ "graph runs delivery's own chant graph, in the working tree and at HEAD",
82
+ async () => {
83
+ const { expectValid } = contract(graphSchema);
84
+ for (const at of [undefined, "HEAD"]) {
85
+ const { doc, failed } = await workspaceGraph({ cwd: FIXTURE, at, reader });
86
+ expectValid(doc);
87
+ if ("error" in doc) throw new Error(doc.error.message);
88
+ expect(failed, JSON.stringify(doc.members)).toBe(false);
89
+ expect(doc.workspace).toEqual({ name: "reference", root: "reference-workspace" });
90
+ expect(doc.at).toBe(at ? head : null);
91
+ const delivery = doc.members.find((m) => m.name === "delivery");
92
+ expect(delivery).toMatchObject({ status: "composed", reason: null, irVersion: 1 });
93
+ expect(doc.groups.byMember.delivery.length).toBeGreaterThan(0);
94
+ expect(doc.nodes.every((n) => n.id.startsWith("delivery/") && n.member === "delivery")).toBe(true);
95
+ }
96
+ },
97
+ TIMEOUT,
98
+ );
99
+
100
+ test("check, in the working tree and at HEAD", async () => {
101
+ const { expectValid } = contract(checkSchema);
102
+ for (const at of [undefined, "HEAD"]) {
103
+ const doc = await runChecks(FIXTURE, at);
104
+ expectValid(doc);
105
+ if ("error" in doc) throw new Error(doc.error.message);
106
+ expect(doc.workspace).toEqual({ name: "reference", root: "reference-workspace" });
107
+ expect(doc.declaration?.diagnostics.filter((d) => d.severity === "error")).toEqual([]);
108
+ }
109
+ });
110
+
111
+ test("status", async () => {
112
+ const doc = await workspaceStatus({ cwd: FIXTURE, env: "dev" });
113
+ contract(statusSchema).expectValid(doc);
114
+ if ("error" in doc) throw new Error(doc.error.message);
115
+ expect(doc.workspace).toMatchObject({ name: "reference", root: "reference-workspace" });
116
+ expect(doc.members.map((m) => m.name)).toEqual(["app", "delivery", "design-client", "design"]);
117
+ });
118
+
119
+ test("records, in the working tree and at HEAD", async () => {
120
+ const { expectValid } = contract(recordsSchema);
121
+ for (const at of [undefined, "HEAD"]) {
122
+ const doc = await queryRecords({ kind: "decisions/decision.kind.mjs", cwd: FIXTURE, at });
123
+ expectValid(doc);
124
+ if ("error" in doc) throw new Error(doc.error.message);
125
+ expect(doc.summary.invalid).toBe(0);
126
+ expect(doc.records.map((r) => r.id)).toContain("ref-001");
127
+ }
128
+ });
129
+ });
@@ -0,0 +1,113 @@
1
+ /**
2
+ * One closed list of reason codes (#2536, #2524 D15): every command's own
3
+ * list is a subset of `REASON_CODES`, together they cover it, the output
4
+ * schemas name nothing outside it, and no source file under `workspace/`
5
+ * emits a code outside it. The read-contract page documents every code.
6
+ */
7
+
8
+ import { readdirSync, readFileSync } from "node:fs";
9
+ import { join } from "node:path";
10
+ import { describe, expect, test } from "vitest";
11
+ import { REPO } from "./__fixtures__/contract-repo";
12
+ import { MEMBER_RUN_REASON_CODES } from "./compose-graph";
13
+ import { WORKSPACE_ERROR_CODES } from "./declaration";
14
+ import { GRAPH_ERROR_CODES } from "./graph-cli";
15
+ import { CHECK_CODES, CHECK_ERROR_CODES } from "./lineage-check";
16
+ import { GROUP_REASON_CODES, MEMBER_REASON_CODES } from "./ls";
17
+ import { isReasonCode, REASON_CODES, REASONS } from "./reason-codes";
18
+ import { READ_ERROR_CODES, RECORD_REASON_CODES } from "./records";
19
+ import { STATUS_ERROR_CODES, STATUS_REASON_CODES } from "./status";
20
+
21
+ const HERE = import.meta.dirname;
22
+
23
+ const PER_COMMAND: Record<string, readonly string[]> = {
24
+ WORKSPACE_ERROR_CODES,
25
+ MEMBER_REASON_CODES,
26
+ GROUP_REASON_CODES,
27
+ MEMBER_RUN_REASON_CODES,
28
+ GRAPH_ERROR_CODES,
29
+ CHECK_CODES,
30
+ CHECK_ERROR_CODES,
31
+ STATUS_REASON_CODES,
32
+ STATUS_ERROR_CODES,
33
+ RECORD_REASON_CODES,
34
+ READ_ERROR_CODES,
35
+ };
36
+
37
+ /** Every string in an `enum` under a property named `code`, anywhere in a schema. */
38
+ function schemaCodes(node: unknown, underCode = false, out = new Set<string>()): Set<string> {
39
+ if (Array.isArray(node)) {
40
+ for (const n of node) schemaCodes(n, underCode, out);
41
+ } else if (node !== null && typeof node === "object") {
42
+ for (const [k, v] of Object.entries(node)) {
43
+ if (k === "enum" && underCode) for (const e of v as unknown[]) out.add(String(e));
44
+ schemaCodes(v, k === "code" || (underCode && k !== "properties"), out);
45
+ }
46
+ }
47
+ return out;
48
+ }
49
+
50
+ function sourceFiles(dir: string): string[] {
51
+ const out: string[] = [];
52
+ for (const d of readdirSync(dir, { withFileTypes: true })) {
53
+ if (d.isDirectory() && d.name !== "__fixtures__") out.push(...sourceFiles(join(dir, d.name)));
54
+ else if (d.isFile() && d.name.endsWith(".ts") && !d.name.endsWith(".test.ts")) out.push(join(dir, d.name));
55
+ }
56
+ return out;
57
+ }
58
+
59
+ /** Codes that are someone else's: zod's issue codes. */
60
+ const NOT_OURS = new Set(["custom"]);
61
+
62
+ describe("the closed list of reason codes", () => {
63
+ test("has no duplicates, and every code says what it means", () => {
64
+ expect(new Set(REASON_CODES).size).toBe(REASON_CODES.length);
65
+ for (const c of REASON_CODES) {
66
+ expect(c).toMatch(/^[a-z]+(-[a-z0-9]+)*$/);
67
+ expect(REASONS[c].length).toBeGreaterThan(10);
68
+ }
69
+ expect(isReasonCode("dir-missing")).toBe(true);
70
+ expect(isReasonCode("toString")).toBe(false);
71
+ });
72
+
73
+ test("each command's list is a subset, and together they are the whole list", () => {
74
+ const union = new Set<string>();
75
+ for (const [name, codes] of Object.entries(PER_COMMAND)) {
76
+ for (const c of codes) {
77
+ expect(isReasonCode(c), `${name} has ${c}, which is not in reason-codes.ts`).toBe(true);
78
+ union.add(c);
79
+ }
80
+ }
81
+ expect([...union].sort()).toEqual([...REASON_CODES].sort());
82
+ });
83
+
84
+ test("the output schemas name exactly these codes", () => {
85
+ const named = new Set<string>();
86
+ for (const f of readdirSync(HERE).filter((f) => f.endsWith(".schema.json") && !f.startsWith("declaration") && !f.startsWith("workspace-kinds"))) {
87
+ for (const c of schemaCodes(JSON.parse(readFileSync(join(HERE, f), "utf-8")))) {
88
+ expect(isReasonCode(c), `${f} names ${c}, which is not in reason-codes.ts`).toBe(true);
89
+ named.add(c);
90
+ }
91
+ }
92
+ expect([...named].sort()).toEqual([...REASON_CODES].sort());
93
+ });
94
+
95
+ test("no source file emits a code outside the list", () => {
96
+ const emitted = /(?:\bcode:\s*|(?:WorkspaceReadError|RecordReadError|StatusError)\(\s*)"([a-z0-9-]+)"/g;
97
+ let seen = 0;
98
+ for (const file of sourceFiles(HERE)) {
99
+ const text = readFileSync(file, "utf-8");
100
+ for (const m of text.matchAll(emitted)) {
101
+ if (NOT_OURS.has(m[1])) continue;
102
+ seen++;
103
+ expect(isReasonCode(m[1]), `${file.slice(HERE.length + 1)} emits ${m[1]}, which is not in reason-codes.ts`).toBe(true);
104
+ }
105
+ }
106
+ expect(seen).toBeGreaterThan(30);
107
+ });
108
+
109
+ test("the read-contract page documents every code", () => {
110
+ const page = readFileSync(join(REPO, "docs", "src", "content", "docs", "reference", "workspace-read-contract.mdx"), "utf-8");
111
+ for (const c of REASON_CODES) expect(page, c).toContain(`| \`${c}\` |`);
112
+ });
113
+ });
@@ -0,0 +1,81 @@
1
+ /**
2
+ * The workspace read contract's closed list of reason codes (#2536, #2524
3
+ * D15, ws-017, ws-020).
4
+ *
5
+ * Every code a `chant workspace` read prints is here: the error that stops a
6
+ * read, the reason an unreadable member, group, ledger or record is listed
7
+ * with, and the lineage lock findings of `check`. Each command's own list
8
+ * (`WORKSPACE_ERROR_CODES`, `MEMBER_REASON_CODES`, `STATUS_REASON_CODES`,
9
+ * `RECORD_REASON_CODES` and the rest) is a subset of this one, which the
10
+ * compiler checks through `satisfies`. `reason-codes.test.ts` checks that the
11
+ * output schemas and the source emit nothing outside it.
12
+ *
13
+ * The list is closed: a reader may switch on a code, and a new code is a
14
+ * contract change. Within contract version 1, before the floor release, codes
15
+ * may still be added; after it, a new code means a new contract version.
16
+ *
17
+ * `chant workspace check` also reports `WSP` ids. Those are the declaration
18
+ * check catalog (`checks.ts`), a separate closed list, and a finding whose
19
+ * cause is one of the codes here carries that code too.
20
+ */
21
+
22
+ /** The version of the read contract this chant writes: the declaration format, the output schemas and these codes. */
23
+ export const READ_CONTRACT_VERSION = 1;
24
+
25
+ /** The first chant release that writes contract version 1. A reader that needs the contract refuses an older chant. */
26
+ export const READ_CONTRACT_FLOOR = "0.81.0";
27
+
28
+ /** Each code and what it means. The key order is the order the docs list them in. */
29
+ export const REASONS = {
30
+ // Reading the declaration (ls, graph, check, status).
31
+ "declaration-missing": "No chant.workspace.json or .jsonc between the directory and the git root.",
32
+ "declaration-ambiguous": "Both chant.workspace.json and chant.workspace.jsonc exist.",
33
+ "declaration-unparseable": "The declaration is not valid JSON, or not valid JSONC for .jsonc.",
34
+ "declaration-invalid": "The declaration does not match its schema, repeats a name, or --member names no entry.",
35
+ "placement-invalid": "A member or group match breaks a placement rule.",
36
+ "reader-too-old": "The declaration's minReader is newer than the chant reading it.",
37
+ "root-chant-required": "The declaration pins a chant version other than the reader's, and that chant is not installed at the workspace root.",
38
+ // --at and git.
39
+ "not-a-git-repository": "--at, or a ledger read, needs a git repository and there is none.",
40
+ "revision-unknown": "--at names no commit.",
41
+ // status.
42
+ "environment-invalid": "An environment name that can't name a ledger directory.",
43
+ // A member or group that can't be read (ls, graph).
44
+ "dir-missing": "The member's directory does not exist.",
45
+ "unknown-kind": "No built-in kind or pinned package supplies the member's kind.",
46
+ "kind-probe-failed": "The member's directory is not what its kind reads.",
47
+ "no-matches": "An example group matches no directory holding a chant project.",
48
+ // A member graph leaves out (graph, and the other per-member commands).
49
+ "kind-not-run": "The member's kind is one the per-member commands don't run, such as other.",
50
+ "command-failed": "The member's own command exited with a failure.",
51
+ "output-unreadable": "The member's command printed something that isn't the document asked for.",
52
+ "ir-version-unsupported": "The member's IR has a version this chant can't read.",
53
+ // A ledger status can't fully read.
54
+ "ledger-unreadable": "Reading the ledger failed, so nothing from it is listed.",
55
+ "ledger-malformed": "Some lines of the ledger aren't release records; the rest are listed.",
56
+ // A record that isn't valid (records).
57
+ "record-unparseable": "No front matter, a YAML error, or a value outside the JSON subset of YAML.",
58
+ "record-schema-invalid": "The front matter does not match the kind's schema.",
59
+ "record-id-duplicate": "Another record earlier in path order has the same id.",
60
+ "record-supersedes-unknown": "A supersedes link names an id no record has.",
61
+ "record-supersedes-conflict": "A second closed record supersedes a record another one already superseded.",
62
+ // A records read that fails (records).
63
+ "kind-unreadable": "The record kind file is missing or could not be imported.",
64
+ "kind-invalid": "The record kind file exports no recordKind, or its shape is wrong.",
65
+ "schema-unreadable": "The schema file the record kind names is missing or is not JSON.",
66
+ "schema-id-mismatch": "The schema's $id differs from the id the record kind names.",
67
+ "schema-invalid": "The record schema itself does not compile.",
68
+ "location-missing": "The records directory does not exist, in the tree or at the revision.",
69
+ // The lineage lock (check).
70
+ "lock-invalid": "The lineage lock can't be read.",
71
+ "manual-step-open": "A scope in the lineage lock has an open manual step.",
72
+ } as const;
73
+
74
+ export type ReasonCode = keyof typeof REASONS;
75
+
76
+ /** Every code, in the order of {@link REASONS}. */
77
+ export const REASON_CODES = Object.keys(REASONS) as ReasonCode[];
78
+
79
+ export function isReasonCode(value: unknown): value is ReasonCode {
80
+ return typeof value === "string" && Object.prototype.hasOwnProperty.call(REASONS, value);
81
+ }
@@ -0,0 +1,117 @@
1
+ /**
2
+ * Where records are read from: the working tree, or a commit's git objects
3
+ * (`--at <rev>`, #2536). Reading at a revision needs no checkout and no
4
+ * network; it asks the local git object store only.
5
+ */
6
+
7
+ import { execFileSync } from "node:child_process";
8
+ import { readdirSync, readFileSync } from "node:fs";
9
+ import { join } from "node:path";
10
+ import { RecordReadError } from "./records";
11
+
12
+ export interface RecordSource {
13
+ /** Appended to messages, such as " at 91c7547e". Empty for the working tree. */
14
+ label: string;
15
+ /** File names directly inside `dir` (relative to the root, `/`-separated), or undefined when `dir` is missing. */
16
+ list(dir: string): string[] | undefined;
17
+ /** The text of a file listed by `list`. */
18
+ read(path: string): string;
19
+ }
20
+
21
+ /** Records in the working tree under `root`. */
22
+ export function workingTreeSource(root: string): RecordSource {
23
+ return {
24
+ label: "",
25
+ list(dir) {
26
+ try {
27
+ return readdirSync(join(root, dir), { withFileTypes: true })
28
+ .filter((d) => d.isFile())
29
+ .map((d) => d.name);
30
+ } catch {
31
+ return undefined;
32
+ }
33
+ },
34
+ read(path) {
35
+ return readFileSync(join(root, path), "utf-8");
36
+ },
37
+ };
38
+ }
39
+
40
+ function git(cwd: string, args: string[], input?: string): string {
41
+ return execFileSync("git", args, {
42
+ cwd,
43
+ encoding: "utf-8",
44
+ input,
45
+ stdio: ["pipe", "pipe", "pipe"],
46
+ maxBuffer: 256 * 1024 * 1024,
47
+ });
48
+ }
49
+
50
+ /** The top of the git repository holding `cwd`, or undefined outside one. */
51
+ export function gitRoot(cwd: string): string | undefined {
52
+ try {
53
+ return git(cwd, ["rev-parse", "--show-toplevel"]).trim();
54
+ } catch {
55
+ return undefined;
56
+ }
57
+ }
58
+
59
+ /** The full commit id `rev` names in the repository at `root`. */
60
+ export function resolveRevision(root: string, rev: string): string {
61
+ if (rev.startsWith("-")) throw new RecordReadError("revision-unknown", `--at ${rev} is not a revision`);
62
+ try {
63
+ return git(root, ["rev-parse", "--verify", "--quiet", `${rev}^{commit}`]).trim();
64
+ } catch {
65
+ throw new RecordReadError("revision-unknown", `--at ${rev} names no commit in this repository`);
66
+ }
67
+ }
68
+
69
+ /** Records as they were at `commit`, read from the object store of the repository at `root`. */
70
+ export function gitRevisionSource(root: string, commit: string): RecordSource {
71
+ const blobs = new Map<string, string>();
72
+ return {
73
+ label: ` at ${commit.slice(0, 8)}`,
74
+ list(dir) {
75
+ const treeish = dir === "." ? `${commit}^{tree}` : `${commit}:${dir}`;
76
+ try {
77
+ if (git(root, ["cat-file", "-t", treeish]).trim() !== "tree") return undefined;
78
+ } catch {
79
+ return undefined;
80
+ }
81
+ const entries = git(root, ["ls-tree", "-z", treeish])
82
+ .split("\0")
83
+ .filter(Boolean)
84
+ .map((line) => {
85
+ const tab = line.indexOf("\t");
86
+ const [, type, oid] = line.slice(0, tab).split(" ");
87
+ return { type, oid, name: line.slice(tab + 1) };
88
+ })
89
+ .filter((e) => e.type === "blob");
90
+ // One `cat-file --batch` for the whole directory: records are small, and
91
+ // one process beats one per file.
92
+ if (entries.length > 0) {
93
+ const out = Buffer.from(
94
+ execFileSync("git", ["cat-file", "--batch"], {
95
+ cwd: root,
96
+ input: entries.map((e) => e.oid).join("\n") + "\n",
97
+ maxBuffer: 256 * 1024 * 1024,
98
+ }),
99
+ );
100
+ let at = 0;
101
+ for (const e of entries) {
102
+ const nl = out.indexOf(0x0a, at);
103
+ const size = Number(out.subarray(at, nl).toString("utf-8").split(" ")[2]);
104
+ const start = nl + 1;
105
+ blobs.set(dir === "." ? e.name : `${dir}/${e.name}`, out.subarray(start, start + size).toString("utf-8"));
106
+ at = start + size + 1;
107
+ }
108
+ }
109
+ return entries.map((e) => e.name);
110
+ },
111
+ read(path) {
112
+ const text = blobs.get(path);
113
+ if (text === undefined) throw new Error(`${path} was not listed at ${commit}`);
114
+ return text;
115
+ },
116
+ };
117
+ }