@intentius/chant 0.124.46 → 0.124.49

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 (726) hide show
  1. package/dist/__golden__/wave-loop/op-by-apply-after-fails.json +10 -0
  2. package/dist/__golden__/wave-loop/op-by-apply-hold.json +10 -0
  3. package/dist/__golden__/wave-loop/op-by-apply-ignored.json +10 -0
  4. package/dist/__golden__/wave-loop/op-by-apply-pass.json +10 -0
  5. package/dist/__golden__/wave-loop/op-by-apply-skip.json +10 -0
  6. package/dist/__golden__/wave-loop/op-cost-member-clash.json +6 -0
  7. package/dist/__golden__/wave-loop/op-cost-member.json +13 -0
  8. package/dist/__golden__/wave-loop/op-denied-source.json +16 -0
  9. package/dist/__golden__/wave-loop/op-denied.json +16 -0
  10. package/dist/__golden__/wave-loop/op-discover-rest-short.json +6 -0
  11. package/dist/__golden__/wave-loop/op-discover-rest-stops.json +13 -0
  12. package/dist/__golden__/wave-loop/op-discover-rest.json +26 -0
  13. package/dist/__golden__/wave-loop/op-discover-wide.json +26 -0
  14. package/dist/__golden__/wave-loop/op-held-on-again.json +16 -0
  15. package/dist/__golden__/wave-loop/op-held-on-before-apply.json +16 -0
  16. package/dist/__golden__/wave-loop/op-held-on.json +16 -0
  17. package/dist/__golden__/wave-loop/op-hooks-apply-fails.json +13 -0
  18. package/dist/__golden__/wave-loop/op-hooks-fail-after-apply.json +13 -0
  19. package/dist/__golden__/wave-loop/op-hooks-fail-before-apply.json +13 -0
  20. package/dist/__golden__/wave-loop/op-hooks-fail-before-plan.json +13 -0
  21. package/dist/__golden__/wave-loop/op-hooks-held.json +13 -0
  22. package/dist/__golden__/wave-loop/op-hooks-hold-before-apply.json +13 -0
  23. package/dist/__golden__/wave-loop/op-hooks-pass.json +13 -0
  24. package/dist/__golden__/wave-loop/op-hooks-refused-at-base.json +13 -0
  25. package/dist/__golden__/wave-loop/op-hooks-share.json +33 -0
  26. package/dist/__golden__/wave-loop/op-outcome-held.json +10 -0
  27. package/dist/__golden__/wave-loop/op-outcome-replan-approved.json +10 -0
  28. package/dist/__golden__/wave-loop/op-outcome-replan-forever.json +10 -0
  29. package/dist/__golden__/wave-loop/op-outcome-replan.json +10 -0
  30. package/dist/__golden__/wave-loop/op-outcome-stood-down.json +10 -0
  31. package/dist/__golden__/wave-loop/op-outcome-unreadable.json +10 -0
  32. package/dist/__golden__/wave-loop/op-overridden.json +16 -0
  33. package/dist/__golden__/wave-loop/op-per-run.json +20 -0
  34. package/dist/__golden__/wave-loop/op-point.json +13 -0
  35. package/dist/__golden__/wave-loop/op-pr-review.json +10 -0
  36. package/dist/__golden__/wave-loop/op-sealed.json +10 -0
  37. package/dist/__golden__/wave-loop/op-spend-change-only.json +10 -0
  38. package/dist/__golden__/wave-loop/op-spend.json +10 -0
  39. package/dist/__golden__/wave-loop/op-wave-commands.json +55 -0
  40. package/dist/__golden__/wave-loop/op-wide-refused.json +26 -0
  41. package/dist/__golden__/wave-loop/op-wide-waiting.json +29 -0
  42. package/dist/__golden__/wave-loop/op-wide.json +39 -0
  43. package/dist/action-pins.d.ts +61 -0
  44. package/dist/action-pins.d.ts.map +1 -0
  45. package/dist/action-pins.js +77 -0
  46. package/dist/cli/commands/carve.d.ts +3 -0
  47. package/dist/cli/commands/carve.d.ts.map +1 -1
  48. package/dist/cli/commands/carve.js +36 -7
  49. package/dist/cli/commands/import-report.d.ts +26 -0
  50. package/dist/cli/commands/import-report.d.ts.map +1 -0
  51. package/dist/cli/commands/import-report.js +65 -0
  52. package/dist/cli/commands/import.d.ts +11 -1
  53. package/dist/cli/commands/import.d.ts.map +1 -1
  54. package/dist/cli/commands/import.js +22 -3
  55. package/dist/cli/commands/migrate.d.ts +13 -1
  56. package/dist/cli/commands/migrate.d.ts.map +1 -1
  57. package/dist/cli/commands/migrate.js +134 -10
  58. package/dist/cli/handlers/approve.d.ts.map +1 -1
  59. package/dist/cli/handlers/approve.js +41 -14
  60. package/dist/cli/handlers/build.d.ts.map +1 -1
  61. package/dist/cli/handlers/build.js +6 -1
  62. package/dist/cli/handlers/graph-reader.d.ts +26 -0
  63. package/dist/cli/handlers/graph-reader.d.ts.map +1 -0
  64. package/dist/cli/handlers/graph-reader.js +41 -0
  65. package/dist/cli/handlers/graph.d.ts.map +1 -1
  66. package/dist/cli/handlers/graph.js +14 -0
  67. package/dist/cli/handlers/ledger.d.ts +3 -0
  68. package/dist/cli/handlers/ledger.d.ts.map +1 -0
  69. package/dist/cli/handlers/ledger.js +55 -0
  70. package/dist/cli/handlers/migrate.d.ts.map +1 -1
  71. package/dist/cli/handlers/migrate.js +18 -11
  72. package/dist/cli/handlers/misc.d.ts.map +1 -1
  73. package/dist/cli/handlers/misc.js +12 -1
  74. package/dist/cli/handlers/pr-env.js +2 -2
  75. package/dist/cli/handlers/pr.d.ts +15 -1
  76. package/dist/cli/handlers/pr.d.ts.map +1 -1
  77. package/dist/cli/handlers/pr.js +183 -40
  78. package/dist/cli/handlers/query.d.ts +7 -2
  79. package/dist/cli/handlers/query.d.ts.map +1 -1
  80. package/dist/cli/handlers/query.js +16 -4
  81. package/dist/cli/handlers/release.d.ts.map +1 -1
  82. package/dist/cli/handlers/release.js +6 -3
  83. package/dist/cli/handlers/search.d.ts +2 -4
  84. package/dist/cli/handlers/search.d.ts.map +1 -1
  85. package/dist/cli/handlers/search.js +13 -39
  86. package/dist/cli/main.d.ts.map +1 -1
  87. package/dist/cli/main.js +87 -14
  88. package/dist/cli/parse-args.d.ts.map +1 -1
  89. package/dist/cli/parse-args.js +47 -2
  90. package/dist/cli/registry.d.ts +22 -4
  91. package/dist/cli/registry.d.ts.map +1 -1
  92. package/dist/components/builders.d.ts +5 -1
  93. package/dist/components/builders.d.ts.map +1 -1
  94. package/dist/components/builders.js +5 -0
  95. package/dist/components/capability-plugin-loader.d.ts.map +1 -1
  96. package/dist/components/capability-plugin-loader.js +16 -1
  97. package/dist/components/capability-plugin.d.ts +14 -0
  98. package/dist/components/capability-plugin.d.ts.map +1 -1
  99. package/dist/components/pr-hooks.d.ts +21 -1
  100. package/dist/components/pr-hooks.d.ts.map +1 -1
  101. package/dist/components/pr-hooks.js +72 -4
  102. package/dist/components/pr-pipeline.d.ts +45 -10
  103. package/dist/components/pr-pipeline.d.ts.map +1 -1
  104. package/dist/components/pr-pipeline.js +47 -14
  105. package/dist/components/pr-policy.d.ts +36 -0
  106. package/dist/components/pr-policy.d.ts.map +1 -1
  107. package/dist/components/pr-policy.js +36 -0
  108. package/dist/components/pr-run.d.ts +3 -0
  109. package/dist/components/pr-run.d.ts.map +1 -1
  110. package/dist/components/pr-run.js +1 -0
  111. package/dist/components/starter-plugin.d.ts +1 -0
  112. package/dist/components/starter-plugin.d.ts.map +1 -1
  113. package/dist/components/starter-plugin.js +5 -1
  114. package/dist/components/verbs/index.d.ts +2 -0
  115. package/dist/components/verbs/index.d.ts.map +1 -1
  116. package/dist/components/verbs/index.js +2 -0
  117. package/dist/components/verbs/publish-artifact.d.ts +65 -0
  118. package/dist/components/verbs/publish-artifact.d.ts.map +1 -0
  119. package/dist/components/verbs/publish-artifact.js +74 -0
  120. package/dist/components/verbs/publish-image.d.ts +129 -0
  121. package/dist/components/verbs/publish-image.d.ts.map +1 -0
  122. package/dist/components/verbs/publish-image.js +145 -0
  123. package/dist/config-environments.d.ts +20 -0
  124. package/dist/config-environments.d.ts.map +1 -1
  125. package/dist/config-environments.js +12 -0
  126. package/dist/config.d.ts +25 -1
  127. package/dist/config.d.ts.map +1 -1
  128. package/dist/config.js +10 -2
  129. package/dist/gate-decision.d.ts +20 -2
  130. package/dist/gate-decision.d.ts.map +1 -1
  131. package/dist/gate-decision.js +30 -3
  132. package/dist/graph-ir.d.ts +25 -0
  133. package/dist/graph-ir.d.ts.map +1 -1
  134. package/dist/graph-walk.d.ts +66 -0
  135. package/dist/graph-walk.d.ts.map +1 -0
  136. package/dist/graph-walk.js +70 -0
  137. package/dist/import/parser.d.ts +19 -0
  138. package/dist/import/parser.d.ts.map +1 -1
  139. package/dist/lexicon.d.ts +94 -6
  140. package/dist/lexicon.d.ts.map +1 -1
  141. package/dist/lifecycle/gate-ledger.d.ts +11 -0
  142. package/dist/lifecycle/gate-ledger.d.ts.map +1 -1
  143. package/dist/lifecycle/git.d.ts +2 -5
  144. package/dist/lifecycle/git.d.ts.map +1 -1
  145. package/dist/lifecycle/ledger-read.d.ts +65 -0
  146. package/dist/lifecycle/ledger-read.d.ts.map +1 -0
  147. package/dist/lifecycle/ledger-read.js +160 -0
  148. package/dist/lifecycle/release-workstation.d.ts +7 -0
  149. package/dist/lifecycle/release-workstation.d.ts.map +1 -1
  150. package/dist/lifecycle/release-workstation.js +25 -0
  151. package/dist/lifecycle/run-ledger.d.ts +4 -3
  152. package/dist/lifecycle/run-ledger.d.ts.map +1 -1
  153. package/dist/lifecycle/run-ledger.js +1 -0
  154. package/dist/lint/discover.js +3 -1
  155. package/dist/lint/post-synth.d.ts +127 -1
  156. package/dist/lint/post-synth.d.ts.map +1 -1
  157. package/dist/lint/post-synth.js +84 -2
  158. package/dist/lint/rules/op/index.d.ts +1 -0
  159. package/dist/lint/rules/op/index.d.ts.map +1 -1
  160. package/dist/lint/rules/op/index.js +2 -0
  161. package/dist/lint/rules/op/ops019-workspace-op-target.d.ts +32 -0
  162. package/dist/lint/rules/op/ops019-workspace-op-target.d.ts.map +1 -0
  163. package/dist/lint/rules/op/ops019-workspace-op-target.js +63 -0
  164. package/dist/op/activities/activity-contracts.d.ts +9 -6
  165. package/dist/op/activities/activity-contracts.d.ts.map +1 -1
  166. package/dist/op/activities/activity-contracts.js +5 -2
  167. package/dist/op/activities/index.d.ts +2 -2
  168. package/dist/op/activities/index.d.ts.map +1 -1
  169. package/dist/op/activities/index.js +1 -1
  170. package/dist/op/activities/review-agent.d.ts +90 -4
  171. package/dist/op/activities/review-agent.d.ts.map +1 -1
  172. package/dist/op/activities/review-agent.js +217 -19
  173. package/dist/op/activities/work-evidence.d.ts +8 -1
  174. package/dist/op/activities/work-evidence.d.ts.map +1 -1
  175. package/dist/op/activities/work-evidence.js +1 -0
  176. package/dist/op/builders.d.ts +9 -5
  177. package/dist/op/builders.d.ts.map +1 -1
  178. package/dist/op/builders.js +9 -5
  179. package/dist/op/composites/apply-op.d.ts +29 -2
  180. package/dist/op/composites/apply-op.d.ts.map +1 -1
  181. package/dist/op/composites/apply-op.js +20 -1
  182. package/dist/op/gate-approval-base.d.ts +25 -0
  183. package/dist/op/gate-approval-base.d.ts.map +1 -0
  184. package/dist/op/gate-approval-base.js +71 -0
  185. package/dist/op/gate-approval.d.ts +18 -9
  186. package/dist/op/gate-approval.d.ts.map +1 -1
  187. package/dist/op/gate-approval.js +13 -31
  188. package/dist/op/gate-name.d.ts +13 -0
  189. package/dist/op/gate-name.d.ts.map +1 -1
  190. package/dist/op/gate-name.js +14 -1
  191. package/dist/op/gate-quorum.d.ts +100 -3
  192. package/dist/op/gate-quorum.d.ts.map +1 -1
  193. package/dist/op/gate-quorum.js +108 -6
  194. package/dist/op/gate-review.d.ts +9 -0
  195. package/dist/op/gate-review.d.ts.map +1 -1
  196. package/dist/op/gate-review.js +13 -0
  197. package/dist/op/gate.d.ts +47 -5
  198. package/dist/op/gate.d.ts.map +1 -1
  199. package/dist/op/gate.js +25 -4
  200. package/dist/op/index.d.ts +3 -3
  201. package/dist/op/index.d.ts.map +1 -1
  202. package/dist/op/index.js +1 -1
  203. package/dist/op/local-executor.d.ts +45 -3
  204. package/dist/op/local-executor.d.ts.map +1 -1
  205. package/dist/op/local-executor.js +68 -7
  206. package/dist/op/op-waves-run.d.ts +26 -1
  207. package/dist/op/op-waves-run.d.ts.map +1 -1
  208. package/dist/op/op-waves-run.js +259 -13
  209. package/dist/op/op-waves.d.ts +72 -5
  210. package/dist/op/op-waves.d.ts.map +1 -1
  211. package/dist/op/op-waves.js +110 -15
  212. package/dist/op/runtime.d.ts +11 -0
  213. package/dist/op/runtime.d.ts.map +1 -1
  214. package/dist/op/schedule-overdue.d.ts +16 -0
  215. package/dist/op/schedule-overdue.d.ts.map +1 -1
  216. package/dist/op/schedule-overdue.js +36 -0
  217. package/dist/op/stage-hooks.d.ts +9 -2
  218. package/dist/op/stage-hooks.d.ts.map +1 -1
  219. package/dist/op/stage-hooks.js +7 -5
  220. package/dist/op/workspace-run-env.d.ts +22 -0
  221. package/dist/op/workspace-run-env.d.ts.map +1 -0
  222. package/dist/op/workspace-run-env.js +21 -0
  223. package/dist/pr-forge.d.ts +24 -0
  224. package/dist/pr-forge.d.ts.map +1 -1
  225. package/dist/pr-forge.js +47 -0
  226. package/dist/pr-loop.d.ts +10 -0
  227. package/dist/pr-loop.d.ts.map +1 -1
  228. package/dist/pr-loop.js +4 -0
  229. package/dist/pr-migrations.d.ts +62 -0
  230. package/dist/pr-migrations.d.ts.map +1 -0
  231. package/dist/pr-migrations.js +50 -0
  232. package/dist/pr-post.d.ts +42 -0
  233. package/dist/pr-post.d.ts.map +1 -1
  234. package/dist/pr-post.js +68 -2
  235. package/dist/read-filter.d.ts +65 -0
  236. package/dist/read-filter.d.ts.map +1 -0
  237. package/dist/read-filter.js +179 -0
  238. package/dist/run-event.d.ts +1 -1
  239. package/dist/run-event.d.ts.map +1 -1
  240. package/dist/terraform/carve.d.ts +24 -3
  241. package/dist/terraform/carve.d.ts.map +1 -1
  242. package/dist/terraform/carve.js +32 -14
  243. package/dist/terraform/cuts.d.ts +97 -0
  244. package/dist/terraform/cuts.d.ts.map +1 -0
  245. package/dist/terraform/cuts.js +138 -0
  246. package/dist/terraform/folds.d.ts +26 -0
  247. package/dist/terraform/folds.d.ts.map +1 -0
  248. package/dist/terraform/folds.js +38 -0
  249. package/dist/terraform/graph.d.ts.map +1 -1
  250. package/dist/terraform/graph.js +10 -0
  251. package/dist/terraform/reach.d.ts +140 -0
  252. package/dist/terraform/reach.d.ts.map +1 -0
  253. package/dist/terraform/reach.js +170 -0
  254. package/dist/terraform/score.d.ts +14 -0
  255. package/dist/terraform/score.d.ts.map +1 -1
  256. package/dist/terraform/score.js +14 -31
  257. package/dist/terraform/state.d.ts.map +1 -1
  258. package/dist/terraform/state.js +4 -2
  259. package/dist/terraform/types.d.ts +8 -0
  260. package/dist/terraform/types.d.ts.map +1 -1
  261. package/dist/wave-blocked.d.ts +10 -1
  262. package/dist/wave-blocked.d.ts.map +1 -1
  263. package/dist/wave-blocked.js +31 -3
  264. package/dist/wave-runtime.mjs +264 -254
  265. package/dist/workspace/check.schema.json +13 -2
  266. package/dist/workspace/checks/ci-branch.d.ts +44 -0
  267. package/dist/workspace/checks/ci-branch.d.ts.map +1 -0
  268. package/dist/workspace/checks/ci-branch.js +133 -0
  269. package/dist/workspace/checks/gate-approval.d.ts +58 -0
  270. package/dist/workspace/checks/gate-approval.d.ts.map +1 -0
  271. package/dist/workspace/checks/gate-approval.js +103 -0
  272. package/dist/workspace/checks/links.d.ts +3 -0
  273. package/dist/workspace/checks/links.d.ts.map +1 -1
  274. package/dist/workspace/checks/links.js +12 -1
  275. package/dist/workspace/checks/org.d.ts +48 -0
  276. package/dist/workspace/checks/org.d.ts.map +1 -0
  277. package/dist/workspace/checks/org.js +156 -0
  278. package/dist/workspace/checks.d.ts +12 -1
  279. package/dist/workspace/checks.d.ts.map +1 -1
  280. package/dist/workspace/checks.js +63 -7
  281. package/dist/workspace/ci-branches.d.ts +16 -0
  282. package/dist/workspace/ci-branches.d.ts.map +1 -1
  283. package/dist/workspace/ci-branches.js +27 -0
  284. package/dist/workspace/ci-green-cli.js +7 -7
  285. package/dist/workspace/ci-green-workflow.d.ts +1 -1
  286. package/dist/workspace/ci-green-workflow.d.ts.map +1 -1
  287. package/dist/workspace/ci-green-workflow.js +13 -8
  288. package/dist/workspace/ci-green.d.ts +14 -0
  289. package/dist/workspace/ci-green.d.ts.map +1 -1
  290. package/dist/workspace/ci-green.js +44 -11
  291. package/dist/workspace/ci-run.d.ts +9 -1
  292. package/dist/workspace/ci-run.d.ts.map +1 -1
  293. package/dist/workspace/ci-run.js +67 -6
  294. package/dist/workspace/ci-serve.d.ts +9 -0
  295. package/dist/workspace/ci-serve.d.ts.map +1 -1
  296. package/dist/workspace/ci-serve.js +114 -76
  297. package/dist/workspace/compose-graph.d.ts +14 -0
  298. package/dist/workspace/compose-graph.d.ts.map +1 -1
  299. package/dist/workspace/composites.d.ts +4 -1
  300. package/dist/workspace/composites.d.ts.map +1 -1
  301. package/dist/workspace/composites.js +5 -2
  302. package/dist/workspace/composites.schema.json +22 -0
  303. package/dist/workspace/conformance/__writer_fixture__/work/work.schema.json +105 -1
  304. package/dist/workspace/cross-root-lint.d.ts +56 -0
  305. package/dist/workspace/cross-root-lint.d.ts.map +1 -0
  306. package/dist/workspace/cross-root-lint.js +111 -0
  307. package/dist/workspace/dbt-models-reader.d.ts +44 -0
  308. package/dist/workspace/dbt-models-reader.d.ts.map +1 -0
  309. package/dist/workspace/dbt-models-reader.js +356 -0
  310. package/dist/workspace/dbt-project-common.d.ts +40 -0
  311. package/dist/workspace/dbt-project-common.d.ts.map +1 -0
  312. package/dist/workspace/dbt-project-common.js +106 -0
  313. package/dist/workspace/dbt-project-reader.d.ts +4 -3
  314. package/dist/workspace/dbt-project-reader.d.ts.map +1 -1
  315. package/dist/workspace/dbt-project-reader.js +14 -108
  316. package/dist/workspace/decision-rank.d.ts +26 -0
  317. package/dist/workspace/decision-rank.d.ts.map +1 -0
  318. package/dist/workspace/decision-rank.js +16 -0
  319. package/dist/workspace/declaration.d.ts +50 -2
  320. package/dist/workspace/declaration.d.ts.map +1 -1
  321. package/dist/workspace/declaration.js +57 -11
  322. package/dist/workspace/declaration.schema.json +166 -24
  323. package/dist/workspace/environments.d.ts +26 -2
  324. package/dist/workspace/environments.d.ts.map +1 -1
  325. package/dist/workspace/environments.js +26 -2
  326. package/dist/workspace/exit-cli.d.ts +44 -0
  327. package/dist/workspace/exit-cli.d.ts.map +1 -0
  328. package/dist/workspace/exit-cli.js +198 -0
  329. package/dist/workspace/exit.d.ts +159 -0
  330. package/dist/workspace/exit.d.ts.map +1 -0
  331. package/dist/workspace/exit.js +480 -0
  332. package/dist/workspace/exit.schema.json +257 -0
  333. package/dist/workspace/flyway-dir.d.ts +39 -0
  334. package/dist/workspace/flyway-dir.d.ts.map +1 -0
  335. package/dist/workspace/flyway-dir.js +104 -0
  336. package/dist/workspace/flyway-layout.d.ts +96 -0
  337. package/dist/workspace/flyway-layout.d.ts.map +1 -0
  338. package/dist/workspace/flyway-layout.js +308 -0
  339. package/dist/workspace/graph-cache.d.ts +3 -1
  340. package/dist/workspace/graph-cache.d.ts.map +1 -1
  341. package/dist/workspace/graph-cache.js +12 -1
  342. package/dist/workspace/graph-cli.d.ts.map +1 -1
  343. package/dist/workspace/graph-cli.js +26 -8
  344. package/dist/workspace/graph.schema.json +34 -0
  345. package/dist/workspace/import-fetch.d.ts +38 -0
  346. package/dist/workspace/import-fetch.d.ts.map +1 -0
  347. package/dist/workspace/import-fetch.js +99 -0
  348. package/dist/workspace/import-init.d.ts +26 -0
  349. package/dist/workspace/import-init.d.ts.map +1 -0
  350. package/dist/workspace/import-init.js +130 -0
  351. package/dist/workspace/import-source.d.ts +147 -0
  352. package/dist/workspace/import-source.d.ts.map +1 -0
  353. package/dist/workspace/import-source.js +370 -0
  354. package/dist/workspace/init.d.ts +4 -0
  355. package/dist/workspace/init.d.ts.map +1 -1
  356. package/dist/workspace/init.js +70 -3
  357. package/dist/workspace/intent.d.ts +2 -3
  358. package/dist/workspace/intent.d.ts.map +1 -1
  359. package/dist/workspace/intent.js +5 -9
  360. package/dist/workspace/kind-link-readers.d.ts +59 -2
  361. package/dist/workspace/kind-link-readers.d.ts.map +1 -1
  362. package/dist/workspace/kind-link-readers.js +17 -1
  363. package/dist/workspace/kind-readers.d.ts +6 -0
  364. package/dist/workspace/kind-readers.d.ts.map +1 -1
  365. package/dist/workspace/kind-readers.js +18 -0
  366. package/dist/workspace/kinds.d.ts +90 -3
  367. package/dist/workspace/kinds.d.ts.map +1 -1
  368. package/dist/workspace/kinds.js +161 -7
  369. package/dist/workspace/ledger.schema.json +158 -0
  370. package/dist/workspace/lexicon-outputs.d.ts +20 -1
  371. package/dist/workspace/lexicon-outputs.d.ts.map +1 -1
  372. package/dist/workspace/lexicon-outputs.js +102 -10
  373. package/dist/workspace/lineage-check.d.ts +3 -0
  374. package/dist/workspace/lineage-check.d.ts.map +1 -1
  375. package/dist/workspace/lineage-check.js +23 -4
  376. package/dist/workspace/lineage-upgrade.d.ts.map +1 -1
  377. package/dist/workspace/lineage-upgrade.js +13 -4
  378. package/dist/workspace/links.d.ts +8 -1
  379. package/dist/workspace/links.d.ts.map +1 -1
  380. package/dist/workspace/links.js +25 -1
  381. package/dist/workspace/ls.d.ts +8 -0
  382. package/dist/workspace/ls.d.ts.map +1 -1
  383. package/dist/workspace/ls.js +7 -1
  384. package/dist/workspace/ls.schema.json +7 -0
  385. package/dist/workspace/member-commands.d.ts.map +1 -1
  386. package/dist/workspace/member-commands.js +54 -5
  387. package/dist/workspace/migrate-links.d.ts +45 -0
  388. package/dist/workspace/migrate-links.d.ts.map +1 -0
  389. package/dist/workspace/migrate-links.js +100 -0
  390. package/dist/workspace/migration-dirs.d.ts +74 -0
  391. package/dist/workspace/migration-dirs.d.ts.map +1 -0
  392. package/dist/workspace/migration-dirs.js +254 -0
  393. package/dist/workspace/new-migrations.d.ts +47 -0
  394. package/dist/workspace/new-migrations.d.ts.map +1 -0
  395. package/dist/workspace/new-migrations.js +91 -0
  396. package/dist/workspace/pr-report.schema.json +46 -0
  397. package/dist/workspace/promote.d.ts +96 -0
  398. package/dist/workspace/promote.d.ts.map +1 -0
  399. package/dist/workspace/promote.js +263 -0
  400. package/dist/workspace/reason-codes.d.ts +21 -0
  401. package/dist/workspace/reason-codes.d.ts.map +1 -1
  402. package/dist/workspace/reason-codes.js +25 -0
  403. package/dist/workspace/records-cli.d.ts +11 -0
  404. package/dist/workspace/records-cli.d.ts.map +1 -1
  405. package/dist/workspace/records-cli.js +57 -5
  406. package/dist/workspace/records.schema.json +5 -0
  407. package/dist/workspace/release-candidates.d.ts +2 -0
  408. package/dist/workspace/release-candidates.d.ts.map +1 -1
  409. package/dist/workspace/release-candidates.js +5 -2
  410. package/dist/workspace/release-train.d.ts +53 -0
  411. package/dist/workspace/release-train.d.ts.map +1 -0
  412. package/dist/workspace/release-train.js +134 -0
  413. package/dist/workspace/release-workflow.d.ts.map +1 -1
  414. package/dist/workspace/release-workflow.js +4 -3
  415. package/dist/workspace/repo-ref.d.ts +45 -0
  416. package/dist/workspace/repo-ref.d.ts.map +1 -0
  417. package/dist/workspace/repo-ref.js +114 -0
  418. package/dist/workspace/run-event.schema.json +2 -2
  419. package/dist/workspace/runs-cli.d.ts +9 -0
  420. package/dist/workspace/runs-cli.d.ts.map +1 -1
  421. package/dist/workspace/runs-cli.js +48 -3
  422. package/dist/workspace/runs.schema.json +7 -0
  423. package/dist/workspace/search-contract.d.ts +1 -1
  424. package/dist/workspace/search-contract.d.ts.map +1 -1
  425. package/dist/workspace/status-decisions.d.ts +37 -0
  426. package/dist/workspace/status-decisions.d.ts.map +1 -0
  427. package/dist/workspace/status-decisions.js +147 -0
  428. package/dist/workspace/status-gates.d.ts +9 -0
  429. package/dist/workspace/status-gates.d.ts.map +1 -1
  430. package/dist/workspace/status.d.ts +4 -0
  431. package/dist/workspace/status.d.ts.map +1 -1
  432. package/dist/workspace/status.js +14 -2
  433. package/dist/workspace/status.schema.json +6 -0
  434. package/dist/workspace/work-cli.d.ts.map +1 -1
  435. package/dist/workspace/work-cli.js +5 -3
  436. package/dist/workspace/work-evidence-ops.d.ts +83 -0
  437. package/dist/workspace/work-evidence-ops.d.ts.map +1 -0
  438. package/dist/workspace/work-evidence-ops.js +169 -0
  439. package/dist/workspace/work-evidence.d.ts +12 -2
  440. package/dist/workspace/work-evidence.d.ts.map +1 -1
  441. package/dist/workspace/work-evidence.js +21 -3
  442. package/dist/workspace/work-evidence.schema.json +49 -2
  443. package/dist/workspace/workspace-gates.d.ts +110 -0
  444. package/dist/workspace/workspace-gates.d.ts.map +1 -0
  445. package/dist/workspace/workspace-gates.js +140 -0
  446. package/dist/workspace/workspace-kinds.schema.json +108 -10
  447. package/dist/workspace/workspace-ledger.d.ts +76 -0
  448. package/dist/workspace/workspace-ledger.d.ts.map +1 -0
  449. package/dist/workspace/workspace-ledger.js +91 -0
  450. package/dist/workspace/workspace-run.d.ts +112 -0
  451. package/dist/workspace/workspace-run.d.ts.map +1 -0
  452. package/dist/workspace/workspace-run.js +432 -0
  453. package/package.json +1 -1
  454. package/src/__golden__/wave-loop/op-by-apply-after-fails.json +10 -0
  455. package/src/__golden__/wave-loop/op-by-apply-hold.json +10 -0
  456. package/src/__golden__/wave-loop/op-by-apply-ignored.json +10 -0
  457. package/src/__golden__/wave-loop/op-by-apply-pass.json +10 -0
  458. package/src/__golden__/wave-loop/op-by-apply-skip.json +10 -0
  459. package/src/__golden__/wave-loop/op-cost-member-clash.json +6 -0
  460. package/src/__golden__/wave-loop/op-cost-member.json +13 -0
  461. package/src/__golden__/wave-loop/op-denied-source.json +16 -0
  462. package/src/__golden__/wave-loop/op-denied.json +16 -0
  463. package/src/__golden__/wave-loop/op-discover-rest-short.json +6 -0
  464. package/src/__golden__/wave-loop/op-discover-rest-stops.json +13 -0
  465. package/src/__golden__/wave-loop/op-discover-rest.json +26 -0
  466. package/src/__golden__/wave-loop/op-discover-wide.json +26 -0
  467. package/src/__golden__/wave-loop/op-held-on-again.json +16 -0
  468. package/src/__golden__/wave-loop/op-held-on-before-apply.json +16 -0
  469. package/src/__golden__/wave-loop/op-held-on.json +16 -0
  470. package/src/__golden__/wave-loop/op-hooks-apply-fails.json +13 -0
  471. package/src/__golden__/wave-loop/op-hooks-fail-after-apply.json +13 -0
  472. package/src/__golden__/wave-loop/op-hooks-fail-before-apply.json +13 -0
  473. package/src/__golden__/wave-loop/op-hooks-fail-before-plan.json +13 -0
  474. package/src/__golden__/wave-loop/op-hooks-held.json +13 -0
  475. package/src/__golden__/wave-loop/op-hooks-hold-before-apply.json +13 -0
  476. package/src/__golden__/wave-loop/op-hooks-pass.json +13 -0
  477. package/src/__golden__/wave-loop/op-hooks-refused-at-base.json +13 -0
  478. package/src/__golden__/wave-loop/op-hooks-share.json +33 -0
  479. package/src/__golden__/wave-loop/op-outcome-held.json +10 -0
  480. package/src/__golden__/wave-loop/op-outcome-replan-approved.json +10 -0
  481. package/src/__golden__/wave-loop/op-outcome-replan-forever.json +10 -0
  482. package/src/__golden__/wave-loop/op-outcome-replan.json +10 -0
  483. package/src/__golden__/wave-loop/op-outcome-stood-down.json +10 -0
  484. package/src/__golden__/wave-loop/op-outcome-unreadable.json +10 -0
  485. package/src/__golden__/wave-loop/op-overridden.json +16 -0
  486. package/src/__golden__/wave-loop/op-per-run.json +20 -0
  487. package/src/__golden__/wave-loop/op-point.json +13 -0
  488. package/src/__golden__/wave-loop/op-pr-review.json +10 -0
  489. package/src/__golden__/wave-loop/op-sealed.json +10 -0
  490. package/src/__golden__/wave-loop/op-spend-change-only.json +10 -0
  491. package/src/__golden__/wave-loop/op-spend.json +10 -0
  492. package/src/__golden__/wave-loop/op-wave-commands.json +55 -0
  493. package/src/__golden__/wave-loop/op-wide-refused.json +26 -0
  494. package/src/__golden__/wave-loop/op-wide-waiting.json +29 -0
  495. package/src/__golden__/wave-loop/op-wide.json +39 -0
  496. package/src/action-pins.test.ts +78 -0
  497. package/src/action-pins.ts +92 -0
  498. package/src/cli/commands/carve-score-once.test.ts +99 -0
  499. package/src/cli/commands/carve.ts +37 -6
  500. package/src/cli/commands/check-lexicon.test.ts +2 -2
  501. package/src/cli/commands/import-report.ts +71 -0
  502. package/src/cli/commands/import.ts +33 -3
  503. package/src/cli/commands/migrate-dialect.test.ts +145 -0
  504. package/src/cli/commands/migrate.ts +132 -11
  505. package/src/cli/handlers/__snapshots__/build-member.test.ts.snap +4 -4
  506. package/src/cli/handlers/approve.ts +33 -4
  507. package/src/cli/handlers/build.ts +7 -1
  508. package/src/cli/handlers/graph-reader.test.ts +31 -0
  509. package/src/cli/handlers/graph-reader.ts +51 -0
  510. package/src/cli/handlers/graph.ts +14 -0
  511. package/src/cli/handlers/ledger.ts +54 -0
  512. package/src/cli/handlers/migrate.ts +21 -12
  513. package/src/cli/handlers/misc.ts +10 -1
  514. package/src/cli/handlers/pr-env.ts +2 -2
  515. package/src/cli/handlers/pr.ts +176 -37
  516. package/src/cli/handlers/query.ts +16 -4
  517. package/src/cli/handlers/release.ts +6 -3
  518. package/src/cli/handlers/search.ts +13 -34
  519. package/src/cli/main.test.ts +17 -0
  520. package/src/cli/main.ts +87 -14
  521. package/src/cli/parse-args.ts +35 -2
  522. package/src/cli/registry.ts +22 -4
  523. package/src/components/builders.ts +8 -0
  524. package/src/components/capability-plugin-loader.ts +13 -1
  525. package/src/components/capability-plugin.ts +16 -0
  526. package/src/components/pr-env-run-bundle.test.ts +1 -0
  527. package/src/components/pr-hooks.test.ts +73 -5
  528. package/src/components/pr-hooks.ts +95 -5
  529. package/src/components/pr-pipeline.ts +64 -16
  530. package/src/components/pr-policy.test.ts +64 -1
  531. package/src/components/pr-policy.ts +53 -0
  532. package/src/components/pr-run.ts +4 -0
  533. package/src/components/registry.test.ts +1 -1
  534. package/src/components/starter-plugin.ts +6 -0
  535. package/src/components/verbs/index.ts +2 -0
  536. package/src/components/verbs/publish-artifact.test.ts +60 -0
  537. package/src/components/verbs/publish-artifact.ts +122 -0
  538. package/src/components/verbs/publish-image.test.ts +101 -0
  539. package/src/components/verbs/publish-image.ts +265 -0
  540. package/src/config-environments.ts +28 -1
  541. package/src/config.ts +24 -1
  542. package/src/gate-decision-bundle.test.ts +5 -2
  543. package/src/gate-decision-identity.test.ts +20 -0
  544. package/src/gate-decision.ts +46 -3
  545. package/src/graph-ir.ts +27 -0
  546. package/src/graph-walk.test.ts +44 -0
  547. package/src/graph-walk.ts +102 -0
  548. package/src/import/parser.ts +20 -0
  549. package/src/lexicon.ts +98 -6
  550. package/src/lifecycle/gate-ledger.ts +8 -0
  551. package/src/lifecycle/git.ts +2 -2
  552. package/src/lifecycle/ledger-read.test.ts +107 -0
  553. package/src/lifecycle/ledger-read.ts +195 -0
  554. package/src/lifecycle/release-workstation.ts +24 -0
  555. package/src/lifecycle/run-ledger.ts +5 -2
  556. package/src/lint/cross-root.test.ts +122 -0
  557. package/src/lint/discover.ts +3 -1
  558. package/src/lint/post-synth.ts +212 -2
  559. package/src/lint/rules/op/index.ts +2 -0
  560. package/src/lint/rules/op/ops019-workspace-op-target.ts +89 -0
  561. package/src/op/activities/activity-contracts.ts +5 -2
  562. package/src/op/activities/index.ts +2 -2
  563. package/src/op/activities/review-agent.test.ts +221 -4
  564. package/src/op/activities/review-agent.ts +265 -22
  565. package/src/op/activities/work-evidence.ts +9 -1
  566. package/src/op/builders.ts +9 -5
  567. package/src/op/composites/apply-op.test.ts +108 -0
  568. package/src/op/composites/apply-op.ts +37 -2
  569. package/src/op/gate-approval-at-base.test.ts +97 -0
  570. package/src/op/gate-approval-base.ts +72 -0
  571. package/src/op/gate-approval.test.ts +6 -1
  572. package/src/op/gate-approval.ts +22 -27
  573. package/src/op/gate-name.ts +16 -1
  574. package/src/op/gate-quorum-at-base.test.ts +211 -0
  575. package/src/op/gate-quorum.ts +156 -8
  576. package/src/op/gate-review.ts +14 -0
  577. package/src/op/gate.ts +69 -9
  578. package/src/op/index.ts +3 -2
  579. package/src/op/local-executor.ts +91 -11
  580. package/src/op/op-waves-depends-on.test.ts +81 -0
  581. package/src/op/op-waves-hook-targets.test.ts +4 -2
  582. package/src/op/op-waves-quorum-product.test.ts +158 -0
  583. package/src/op/op-waves-run.ts +288 -14
  584. package/src/op/op-waves.ts +143 -14
  585. package/src/op/runtime.ts +8 -0
  586. package/src/op/schedule-overdue.ts +33 -0
  587. package/src/op/stage-hooks.ts +13 -5
  588. package/src/op/workspace-run-env.ts +29 -0
  589. package/src/pr-forge-bundle.test.ts +2 -2
  590. package/src/pr-forge.test.ts +47 -0
  591. package/src/pr-forge.ts +68 -0
  592. package/src/pr-loop-bundle.test.ts +3 -2
  593. package/src/pr-loop.test.ts +52 -0
  594. package/src/pr-loop.ts +12 -0
  595. package/src/pr-migrations.ts +97 -0
  596. package/src/pr-post-open.e2e.test.ts +36 -0
  597. package/src/pr-post.test.ts +102 -3
  598. package/src/pr-post.ts +90 -2
  599. package/src/read-filter.test.ts +54 -0
  600. package/src/read-filter.ts +193 -0
  601. package/src/run-event.ts +1 -1
  602. package/src/stage-hooks-bundle.test.ts +1 -0
  603. package/src/terraform/carve.ts +52 -15
  604. package/src/terraform/cuts.test.ts +173 -0
  605. package/src/terraform/cuts.ts +188 -0
  606. package/src/terraform/folds.ts +44 -0
  607. package/src/terraform/graph.ts +9 -0
  608. package/src/terraform/radius.test.ts +90 -0
  609. package/src/terraform/reach.test.ts +146 -0
  610. package/src/terraform/reach.ts +247 -0
  611. package/src/terraform/score.ts +27 -29
  612. package/src/terraform/state.ts +3 -1
  613. package/src/terraform/types.ts +8 -0
  614. package/src/wave-blocked.ts +28 -3
  615. package/src/wave-loop-golden.test.ts +4 -3
  616. package/src/workspace/__fixtures__/import/spacelift.json +9 -0
  617. package/src/workspace/__fixtures__/import/tfc.json +13 -0
  618. package/src/workspace/behold-kinds.test.ts +2 -1
  619. package/src/workspace/check-contract.test.ts +8 -6
  620. package/src/workspace/check.schema.json +13 -2
  621. package/src/workspace/checks/ci-branch.test.ts +128 -0
  622. package/src/workspace/checks/ci-branch.ts +141 -0
  623. package/src/workspace/checks/gate-approval.test.ts +77 -0
  624. package/src/workspace/checks/gate-approval.ts +121 -0
  625. package/src/workspace/checks/links.ts +12 -1
  626. package/src/workspace/checks/org.ts +169 -0
  627. package/src/workspace/checks.ts +68 -7
  628. package/src/workspace/ci-branches.ts +31 -0
  629. package/src/workspace/ci-green-branches.test.ts +153 -0
  630. package/src/workspace/ci-green-cli.ts +7 -7
  631. package/src/workspace/ci-green-workflow.test.ts +4 -3
  632. package/src/workspace/ci-green-workflow.ts +13 -9
  633. package/src/workspace/ci-green.ts +49 -12
  634. package/src/workspace/ci-run.test.ts +72 -1
  635. package/src/workspace/ci-run.ts +68 -8
  636. package/src/workspace/ci-serve.test.ts +95 -0
  637. package/src/workspace/ci-serve.ts +106 -65
  638. package/src/workspace/component-environments.test.ts +25 -2
  639. package/src/workspace/compose-graph.ts +9 -2
  640. package/src/workspace/composites.schema.json +22 -0
  641. package/src/workspace/composites.test.ts +39 -8
  642. package/src/workspace/composites.ts +6 -2
  643. package/src/workspace/conformance/__writer_fixture__/work/work.schema.json +105 -1
  644. package/src/workspace/cross-root-lint.test.ts +90 -0
  645. package/src/workspace/cross-root-lint.ts +126 -0
  646. package/src/workspace/dbt-kind.test.ts +3 -2
  647. package/src/workspace/dbt-models-reader.ts +362 -0
  648. package/src/workspace/dbt-models.test.ts +248 -0
  649. package/src/workspace/dbt-project-common.ts +113 -0
  650. package/src/workspace/dbt-project-reader.ts +29 -112
  651. package/src/workspace/decision-rank.ts +40 -0
  652. package/src/workspace/declaration.schema.json +166 -24
  653. package/src/workspace/declaration.ts +101 -15
  654. package/src/workspace/environments.ts +40 -4
  655. package/src/workspace/exit-cli.ts +219 -0
  656. package/src/workspace/exit.schema.json +257 -0
  657. package/src/workspace/exit.test.ts +185 -0
  658. package/src/workspace/exit.ts +560 -0
  659. package/src/workspace/flyway-dir.ts +113 -0
  660. package/src/workspace/flyway-layout.ts +337 -0
  661. package/src/workspace/graph-cache.ts +12 -2
  662. package/src/workspace/graph-cli.ts +25 -10
  663. package/src/workspace/graph-contract.test.ts +4 -2
  664. package/src/workspace/graph.schema.json +34 -0
  665. package/src/workspace/identity.test.ts +29 -2
  666. package/src/workspace/import-fetch.ts +151 -0
  667. package/src/workspace/import-init.ts +147 -0
  668. package/src/workspace/import-source.test.ts +183 -0
  669. package/src/workspace/import-source.ts +462 -0
  670. package/src/workspace/init.test.ts +55 -1
  671. package/src/workspace/init.ts +66 -4
  672. package/src/workspace/intent.ts +6 -14
  673. package/src/workspace/kind-link-readers.ts +68 -3
  674. package/src/workspace/kind-readers.test.ts +36 -0
  675. package/src/workspace/kind-readers.ts +18 -1
  676. package/src/workspace/kinds.test.ts +35 -7
  677. package/src/workspace/kinds.ts +212 -11
  678. package/src/workspace/ledger.schema.json +158 -0
  679. package/src/workspace/lexicon-outputs.ts +132 -13
  680. package/src/workspace/lineage-check.ts +30 -7
  681. package/src/workspace/lineage-upgrade.test.ts +20 -0
  682. package/src/workspace/lineage-upgrade.ts +16 -7
  683. package/src/workspace/links.ts +24 -1
  684. package/src/workspace/ls-contract.test.ts +1 -1
  685. package/src/workspace/ls.schema.json +7 -0
  686. package/src/workspace/ls.ts +10 -1
  687. package/src/workspace/member-commands.ts +60 -2
  688. package/src/workspace/member-file-schema.test.ts +124 -0
  689. package/src/workspace/migrate-links.test.ts +99 -0
  690. package/src/workspace/migrate-links.ts +108 -0
  691. package/src/workspace/migration-dirs.test.ts +272 -0
  692. package/src/workspace/migration-dirs.ts +267 -0
  693. package/src/workspace/new-migrations.test.ts +98 -0
  694. package/src/workspace/new-migrations.ts +109 -0
  695. package/src/workspace/org-scope.test.ts +238 -0
  696. package/src/workspace/pr-report.schema.json +46 -0
  697. package/src/workspace/promote.test.ts +114 -0
  698. package/src/workspace/promote.ts +302 -0
  699. package/src/workspace/reason-codes.test.ts +10 -1
  700. package/src/workspace/reason-codes.ts +25 -0
  701. package/src/workspace/records-cli.ts +58 -5
  702. package/src/workspace/records.schema.json +5 -0
  703. package/src/workspace/release-candidates.ts +7 -2
  704. package/src/workspace/release-train.ts +157 -0
  705. package/src/workspace/release-workflow.ts +4 -3
  706. package/src/workspace/repo-ref.ts +113 -0
  707. package/src/workspace/run-event.schema.json +2 -2
  708. package/src/workspace/runs-cli.ts +48 -4
  709. package/src/workspace/runs.schema.json +7 -0
  710. package/src/workspace/status-contract.test.ts +46 -1
  711. package/src/workspace/status-decisions.test.ts +100 -0
  712. package/src/workspace/status-decisions.ts +151 -0
  713. package/src/workspace/status-gates.ts +9 -0
  714. package/src/workspace/status.schema.json +6 -0
  715. package/src/workspace/status.ts +17 -3
  716. package/src/workspace/where-filters.test.ts +39 -0
  717. package/src/workspace/work-cli.ts +5 -3
  718. package/src/workspace/work-evidence-ops.test.ts +176 -0
  719. package/src/workspace/work-evidence-ops.ts +189 -0
  720. package/src/workspace/work-evidence.schema.json +49 -2
  721. package/src/workspace/work-evidence.ts +22 -3
  722. package/src/workspace/workspace-gates.ts +200 -0
  723. package/src/workspace/workspace-kinds.schema.json +108 -10
  724. package/src/workspace/workspace-ledger.ts +130 -0
  725. package/src/workspace/workspace-run.test.ts +322 -0
  726. package/src/workspace/workspace-run.ts +461 -0
@@ -24,17 +24,24 @@
24
24
  *
25
25
  * Under `--at` the revision's files are not on disk to fold, so a member
26
26
  * whose lexicons report outputs gets a gap instead.
27
+ *
28
+ * The consumer side works the same way (#4397): a lexicon's `workspaceLinks`
29
+ * hook names the outputs of other members its declarations read, such as
30
+ * the tables an otel `clickhouse` exporter writes. {@link gatherLexiconLinks}
31
+ * folds every member whose lexicons have the hook and hands back what they
32
+ * report, in the shape a kind's link reader returns (`./kind-link-readers.ts`),
33
+ * so `resolveLinks` checks those links like a dbt project's sources.
27
34
  */
28
35
 
29
36
  import { join, posix } from "node:path";
30
37
  import type { StaticFieldsRead } from "../config-static";
31
38
  import { isDeclarable, type Declarable } from "../declarable";
32
- import type { LexiconUnreadSource, LexiconWorkspaceOutput } from "../graph-ir";
39
+ import type { LexiconUnreadSource, LexiconWorkspaceLinks, LexiconWorkspaceOutput } from "../graph-ir";
33
40
  import type { BuildRootContext, BuildRootContribution, IntrinsicDef } from "../lexicon";
34
41
  import type { Declaration, ResolvedGroup } from "./declaration";
35
42
  import type { KindRegistry } from "./kinds";
36
43
  import { kindHandles } from "./links";
37
- import { readKindLinks } from "./kind-link-readers";
44
+ import { readKindLinks, type KindLinkRead } from "./kind-link-readers";
38
45
  import { memberExcludes, sourceFiles } from "./member-handles";
39
46
  import { memberDirOnDisk } from "./lexicon-symbols";
40
47
  import type { WorkspaceTree } from "./tree";
@@ -51,6 +58,7 @@ export interface OutputsPlugin {
51
58
  intrinsics?: () => IntrinsicDef[];
52
59
  buildRoots?: (ctx: BuildRootContext) => Promise<BuildRootContribution>;
53
60
  workspaceOutputs?: (entities: ReadonlyMap<string, Declarable>, unread?: readonly LexiconUnreadSource[]) => { outputs: LexiconWorkspaceOutput[]; gaps?: string[] } | undefined;
61
+ workspaceLinks?: (entities: ReadonlyMap<string, Declarable>, unread?: readonly LexiconUnreadSource[]) => LexiconWorkspaceLinks | undefined;
54
62
  }
55
63
 
56
64
  /** Loads a lexicon plugin by name; `loadPlugin` from the CLI unless a test swaps it. */
@@ -60,6 +68,8 @@ export interface GatherLexiconOutputsOptions {
60
68
  /** Read under `--at`: the member's files are a revision's, not on disk, so nothing is folded. */
61
69
  atRevision?: boolean;
62
70
  load?: OutputsPluginLoader;
71
+ /** Links the members' lexicons read (#4397), from {@link gatherLexiconLinks}: the members they target are read too. */
72
+ lexiconLinks?: ReadonlyMap<string, KindLinkRead>;
63
73
  }
64
74
 
65
75
  const firstLine = (err: unknown): string => (err instanceof Error ? err.message : String(err)).split("\n")[0];
@@ -134,7 +144,7 @@ export async function gatherLexiconOutputs(
134
144
  const targets = new Set(declaration.members.flatMap((m) => m.links.filter((l) => l.member !== m.name).map((l) => l.member)));
135
145
  // Links a kind's reader finds in a member's files target members too (#3932). One that names no
136
146
  // producer may be any other member's, so every other member is read.
137
- for (const [consumer, read] of readKindLinks(declaration, tree, kinds)) {
147
+ for (const [consumer, read] of [...readKindLinks(declaration, tree, kinds), ...(options.lexiconLinks ?? new Map<string, KindLinkRead>())]) {
138
148
  for (const link of read.links) {
139
149
  if (link.member !== null) targets.add(link.member);
140
150
  else for (const m of declaration.members) if (m.name !== consumer) targets.add(m.name);
@@ -165,14 +175,36 @@ function importedLexicons(tree: WorkspaceTree, files: readonly string[]): string
165
175
  return [...names].sort();
166
176
  }
167
177
 
168
- async function readMemberLexiconOutputs(
178
+ type Hook = "workspaceOutputs" | "workspaceLinks";
179
+
180
+ /** A member's source folded for the lexicons that have `hook`, or why it wasn't. */
181
+ type FoldedMember =
182
+ | { status: "none" }
183
+ | { status: "gaps"; gaps: string[] }
184
+ | {
185
+ status: "folded";
186
+ reporting: { name: string; plugin: OutputsPlugin }[];
187
+ entities: Map<string, Declarable>;
188
+ unread: LexiconUnreadSource[];
189
+ gaps: string[];
190
+ /** The tree-relative file that exports each entity, and that file's text. */
191
+ sourceOf: (entity: Declarable) => { file: string; text: string } | undefined;
192
+ };
193
+
194
+ /**
195
+ * Fold one member's source for the lexicons that implement `hook`, never
196
+ * running it. "none" when no lexicon of the member's has the hook.
197
+ */
198
+ async function foldMember(
169
199
  root: string,
170
200
  declaration: Declaration,
171
201
  tree: WorkspaceTree,
172
202
  groups: readonly ResolvedGroup[],
173
203
  m: Declaration["members"][number],
174
204
  options: GatherLexiconOutputsOptions,
175
- ): Promise<MemberLexiconOutputs | undefined> {
205
+ hook: Hook,
206
+ ): Promise<FoldedMember> {
207
+ const what = hook === "workspaceOutputs" ? "outputs" : "links";
176
208
  const dir = memberDirOnDisk(root, m.dir);
177
209
  const { readConfigFieldsStatically } = await import("../config-static");
178
210
  const files = withinSourceDir(
@@ -188,7 +220,7 @@ async function readMemberLexiconOutputs(
188
220
  // source imports stand in for the ones it names.
189
221
  names = importedLexicons(tree, files);
190
222
  }
191
- if (names.length === 0) return undefined;
223
+ if (names.length === 0) return { status: "none" };
192
224
  const load = options.load ?? ((await import("../cli/plugins")).loadPlugin as unknown as OutputsPluginLoader);
193
225
  const plugins: { name: string; plugin: OutputsPlugin }[] = [];
194
226
  const gaps: string[] = [];
@@ -196,13 +228,13 @@ async function readMemberLexiconOutputs(
196
228
  try {
197
229
  plugins.push({ name, plugin: await load(name) });
198
230
  } catch (err) {
199
- gaps.push(`lexicon "${name}" could not be loaded, so the outputs it reads were not (${firstLine(err)})`);
231
+ gaps.push(`lexicon "${name}" could not be loaded, so the ${what} it reads were not (${firstLine(err)})`);
200
232
  }
201
233
  }
202
- const reporting = plugins.filter((p) => typeof p.plugin.workspaceOutputs === "function");
203
- if (reporting.length === 0) return gaps.length ? { outputs: [], gaps } : undefined;
234
+ const reporting = plugins.filter((p) => typeof p.plugin[hook] === "function");
235
+ if (reporting.length === 0) return gaps.length && hook === "workspaceOutputs" ? { status: "gaps", gaps } : { status: "none" };
204
236
  if (options.atRevision) {
205
- return { outputs: [], gaps: [`the outputs of lexicon ${reporting.map((p) => p.name).join(", ")} are read from the working tree's files, not a revision's`] };
237
+ return { status: "gaps", gaps: [`the ${what} of lexicon ${reporting.map((p) => p.name).join(", ")} are read from the working tree's files, not a revision's`] };
206
238
  }
207
239
 
208
240
  // Fold the member's source, never run it.
@@ -216,15 +248,15 @@ async function readMemberLexiconOutputs(
216
248
  });
217
249
  let entities: Map<string, Declarable> = new Map();
218
250
  const unread: LexiconUnreadSource[] = [];
251
+ const folded: { file: string; rel: string; exports: Record<string, unknown> }[] = [];
219
252
  try {
220
253
  const { foldProject } = await import("../discovery/fold-import");
221
254
  const { collectEntities } = await import("../discovery/collect");
222
255
  const verdicts = await foldProject(files.map(abs), intrinsics, { lexicons: names, sandbox: true });
223
- const folded: { file: string; exports: Record<string, unknown> }[] = [];
224
256
  for (const file of files) {
225
257
  const v = verdicts.get(abs(file));
226
258
  if (v?.verdict === "fold" && v.exports) {
227
- folded.push({ file: abs(file), exports: ownExports(v.exports, names) });
259
+ folded.push({ file: abs(file), rel: file, exports: ownExports(v.exports, names) });
228
260
  continue;
229
261
  }
230
262
  let text = "";
@@ -238,7 +270,7 @@ async function readMemberLexiconOutputs(
238
270
  }
239
271
  entities = collectEntities(folded, dir);
240
272
  } catch (err) {
241
- return { outputs: [], gaps: [...gaps, `${m.dir}: its source could not be folded, so its lexicons' outputs were not read (${firstLine(err)})`] };
273
+ return { status: "gaps", gaps: [...gaps, `${m.dir}: its source could not be folded, so its lexicons' ${what} were not read (${firstLine(err)})`] };
242
274
  }
243
275
 
244
276
  // A reporting lexicon's own build roots (the sql lexicon's `.sql` files) join
@@ -257,6 +289,32 @@ async function readMemberLexiconOutputs(
257
289
  }
258
290
  }
259
291
 
292
+ const sourceOf = (entity: Declarable) => {
293
+ const f = folded.find((x) => Object.values(x.exports).includes(entity));
294
+ if (!f) return undefined;
295
+ let text = "";
296
+ try {
297
+ text = tree.read(f.rel);
298
+ } catch {
299
+ // The line falls back to 1.
300
+ }
301
+ return { file: f.rel, text };
302
+ };
303
+ return { status: "folded", reporting, entities, unread, gaps, sourceOf };
304
+ }
305
+
306
+ async function readMemberLexiconOutputs(
307
+ root: string,
308
+ declaration: Declaration,
309
+ tree: WorkspaceTree,
310
+ groups: readonly ResolvedGroup[],
311
+ m: Declaration["members"][number],
312
+ options: GatherLexiconOutputsOptions,
313
+ ): Promise<MemberLexiconOutputs | undefined> {
314
+ const member = await foldMember(root, declaration, tree, groups, m, options, "workspaceOutputs");
315
+ if (member.status === "none") return undefined;
316
+ if (member.status === "gaps") return { outputs: [], gaps: member.gaps };
317
+ const { reporting, entities, unread, gaps } = member;
260
318
  const outputs: MemberLexiconOutputs["outputs"] = [];
261
319
  for (const { name, plugin } of reporting) {
262
320
  try {
@@ -270,3 +328,64 @@ async function readMemberLexiconOutputs(
270
328
  }
271
329
  return { outputs, gaps };
272
330
  }
331
+
332
+ /** The line of `export const <name>` (or any declaration of it) in `text`, from 1; 1 when not found. */
333
+ function declarationLine(text: string, name: string): number {
334
+ const escaped = name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
335
+ const at = text.search(new RegExp(`\\b(?:const|let|var|function|class)\\s+${escaped}\\b`));
336
+ return at < 0 ? 1 : text.slice(0, at).split("\n").length;
337
+ }
338
+
339
+ /**
340
+ * The links each `chant` member's lexicons read from its declarations
341
+ * (#4397), keyed by member name, in the shape a kind's link reader returns.
342
+ * Only members with a lexicon that has a `workspaceLinks` hook are folded. A
343
+ * link carries the file and line of the declaration that states it, and the
344
+ * reader is named `lexicon <name>`.
345
+ */
346
+ export async function gatherLexiconLinks(
347
+ root: string,
348
+ declaration: Declaration,
349
+ tree: WorkspaceTree,
350
+ groups: readonly ResolvedGroup[],
351
+ kinds: KindRegistry,
352
+ options: GatherLexiconOutputsOptions = {},
353
+ ): Promise<Map<string, KindLinkRead>> {
354
+ const out = new Map<string, KindLinkRead>();
355
+ for (const m of declaration.members) {
356
+ const kind = kinds.get(m.kind);
357
+ if (!kind || kindHandles(m, kind) || tree.stat(memberDir(m.dir)) !== "dir") continue;
358
+ const member = await foldMember(root, declaration, tree, groups, m, options, "workspaceLinks");
359
+ if (member.status === "none") continue;
360
+ const where = memberDir(m.dir) || ".";
361
+ if (member.status === "gaps") {
362
+ out.set(m.name, { reader: "lexicon", links: [], gaps: member.gaps.map((message) => ({ file: where, line: 1, message })) });
363
+ continue;
364
+ }
365
+ for (const { name, plugin } of member.reporting) {
366
+ const read: KindLinkRead = { reader: `lexicon ${name}`, links: [], gaps: member.gaps.map((message) => ({ file: where, line: 1, message })) };
367
+ try {
368
+ const report = plugin.workspaceLinks!(member.entities, member.unread);
369
+ if (!report) continue;
370
+ for (const l of report.links) {
371
+ const entity = member.entities.get(l.entity);
372
+ const src = entity ? member.sourceOf(entity) : undefined;
373
+ read.links.push({
374
+ member: l.member ?? null,
375
+ output: l.output,
376
+ ...(l.anchor ? { anchor: l.anchor } : {}),
377
+ file: src?.file ?? where,
378
+ line: src ? declarationLine(src.text, l.entity.split(".").pop() ?? l.entity) : 1,
379
+ });
380
+ }
381
+ for (const g of report.gaps ?? []) read.gaps.push({ file: where, line: 1, message: g });
382
+ } catch (err) {
383
+ read.gaps.push({ file: where, line: 1, message: `lexicon "${name}" workspaceLinks threw, so its links were not read (${firstLine(err)})` });
384
+ }
385
+ if (read.links.length === 0 && read.gaps.length === 0) continue;
386
+ const prev = out.get(m.name);
387
+ out.set(m.name, prev ? { reader: prev.reader, links: [...prev.links, ...read.links], gaps: [...prev.gaps, ...read.gaps] } : read);
388
+ }
389
+ }
390
+ return out;
391
+ }
@@ -24,13 +24,14 @@
24
24
  * (`changes-cli.ts`, #2773), which prints a document of its own.
25
25
  */
26
26
 
27
- import { realpathSync } from "node:fs";
27
+ import { readFileSync, realpathSync } from "node:fs";
28
28
  import { join, relative, resolve, sep } from "node:path";
29
29
  import { formatError, formatSuccess } from "../cli/format";
30
30
  import type { CommandContext } from "../cli/registry";
31
31
  import type { LintDiagnostic, LintRule } from "../lint/rule";
32
32
  import { findWorkspaceRoot } from "../project-root";
33
33
  import type { DeclarationCheckReport } from "./checks";
34
+ import type { ForgeRepoList } from "./checks/org";
34
35
  import type { RecordFacts } from "./checks/records";
35
36
  import { LOCK_FILE, LockError, parseLock, readLock } from "./lineage-lock";
36
37
  import type { ReasonCode } from "./reason-codes";
@@ -91,7 +92,7 @@ export function findingKey(f: CheckFinding): string {
91
92
  }
92
93
 
93
94
  const USAGE =
94
- "chant workspace check [--at <rev>] [--json] [--format stylish|json|sarif] [--generated] [--kind <kind file>] [--live --env <env>]\n chant workspace check --changes <base>..<head> [--work <id>] [--severity off|warn|fail] [--kind <kind file>...] [--json]";
95
+ "chant workspace check [--at <rev>] [--json] [--format stylish|json|sarif] [--generated] [--kind <kind file>] [--live --env <env>] [--forge-repos <file>]\n chant workspace check --changes <base>..<head> [--work <id>] [--severity off|warn|fail] [--kind <kind file>...] [--json]";
95
96
  const FORMATS = ["stylish", "json", "sarif"] as const;
96
97
 
97
98
  /** A lock finding as a lint diagnostic, for `--format json` and `--format sarif`. */
@@ -141,7 +142,7 @@ export type CheckDocument =
141
142
  chant: string;
142
143
  at: string | null;
143
144
  /** The declaration's workspace, or null when there is none; `name` is null when it can't be read. */
144
- workspace: { name: string | null; root: string } | null;
145
+ workspace: { name: string | null; root: string; repo: string | null } | null;
145
146
  declaration?: DeclarationCheckReport;
146
147
  })
147
148
  | { $schema: string; contract: number; chant: string; error: { code: CheckErrorCode; message: string; location: null } };
@@ -152,7 +153,11 @@ export type CheckDocument =
152
153
  * to `cwd`. `runGenerators` is `--generated`; with `at` it does nothing,
153
154
  * since the member facts describe the working tree, not the revision.
154
155
  */
155
- export async function runChecks(cwd: string, at?: string, options: { runGenerators?: boolean; kind?: string; live?: { env: string; onStderr?: (text: string) => void } } = {}): Promise<CheckDocument> {
156
+ export async function runChecks(
157
+ cwd: string,
158
+ at?: string,
159
+ options: { runGenerators?: boolean; kind?: string; live?: { env: string; onStderr?: (text: string) => void }; forgeRepos?: ForgeRepoList } = {},
160
+ ): Promise<CheckDocument> {
156
161
  // Loaded here, not at the top: `workspace upgrade` imports this module for checkLineage alone.
157
162
  const [{ findDeclarationDir, readDeclaration, readerVersion }, { gitTop, gitTree, resolveCommit, workingTree }] = await Promise.all([
158
163
  import("./declaration"),
@@ -190,7 +195,7 @@ export async function runChecks(cwd: string, at?: string, options: { runGenerato
190
195
  }
191
196
 
192
197
  let declaration: DeclarationCheckReport | undefined;
193
- let workspace: { name: string | null; root: string } | null = null;
198
+ let workspace: { name: string | null; root: string; repo: string | null } | null = null;
194
199
  if (found) {
195
200
  const { runDeclarationChecks } = await import("./checks");
196
201
  const { dir, tree } = found;
@@ -200,6 +205,7 @@ export async function runChecks(cwd: string, at?: string, options: { runGenerato
200
205
  ...(commit !== null ? { tree } : {}),
201
206
  ...(records ? { records } : {}),
202
207
  ...(options.live ? { live: options.live } : {}),
208
+ ...(options.forgeRepos ? { forgeRepos: options.forgeRepos } : {}),
203
209
  });
204
210
  let name: string | null = null;
205
211
  try {
@@ -207,7 +213,8 @@ export async function runChecks(cwd: string, at?: string, options: { runGenerato
207
213
  } catch {
208
214
  // The WSP001 finding already says why.
209
215
  }
210
- workspace = { name, root: found.root };
216
+ const { repoIdentity } = await import("./repo-ref");
217
+ workspace = { name, root: found.root, repo: repoIdentity(dir) };
211
218
  }
212
219
  const ok = report.ok && (declaration?.ok ?? true);
213
220
  return { ...head, at: commit, workspace, ...report, ok, ...(declaration ? { declaration } : {}) };
@@ -267,7 +274,23 @@ export async function runWorkspaceCheck(ctx: CommandContext): Promise<number> {
267
274
  const handed = await handToRootChant(root, ctx.args.at);
268
275
  if (handed !== undefined) return handed;
269
276
  }
270
- const doc = await runChecks(root, ctx.args.at, { runGenerators: ctx.args.generated === true, ...(ctx.args.kind !== undefined ? { kind: ctx.args.kind } : {}), ...(ctx.args.live && ctx.args.env ? { live: { env: ctx.args.env, onStderr: (text: string) => process.stderr.write(text) } } : {}) });
277
+ // The forge's repository list (#3168) is input a warden writes; chant never asks the forge itself.
278
+ let forgeRepos: ForgeRepoList | undefined;
279
+ if (ctx.args.forgeRepos !== undefined) {
280
+ const { parseForgeRepos } = await import("./checks/org");
281
+ try {
282
+ forgeRepos = parseForgeRepos(readFileSync(resolve(ctx.args.forgeRepos), "utf-8"), ctx.args.forgeRepos);
283
+ } catch (err) {
284
+ console.error(formatError({ message: `--forge-repos: ${(err as Error).message}`, hint: USAGE }));
285
+ return 1;
286
+ }
287
+ }
288
+ const doc = await runChecks(root, ctx.args.at, {
289
+ runGenerators: ctx.args.generated === true,
290
+ ...(ctx.args.kind !== undefined ? { kind: ctx.args.kind } : {}),
291
+ ...(ctx.args.live && ctx.args.env ? { live: { env: ctx.args.env, onStderr: (text: string) => process.stderr.write(text) } } : {}),
292
+ ...(forgeRepos ? { forgeRepos } : {}),
293
+ });
271
294
  if ("error" in doc) {
272
295
  if (ctx.args.json || format === "json") console.log(JSON.stringify(doc, null, 2));
273
296
  else console.error(formatError({ message: `${doc.error.code}: ${doc.error.message}`, hint: USAGE }));
@@ -497,6 +497,26 @@ describe("chant workspace upgrade", () => {
497
497
  expect(port.pending[0].approval).toEqual({ mode: "log-only", quorum: { count: 2, roles: ["maintainer"] } });
498
498
  });
499
499
 
500
+ test("a gate's quorum at HEAD in identity.gates is the one asked for, and the patch can't lower it (#4404)", async () => {
501
+ // The Op's own quorum is 2 at HEAD and 1 in the upgrade; the declaration at HEAD asks 3 of the gate.
502
+ const op = (overview: string, count: number): string =>
503
+ `export default { props: { name: "release", overview: ${JSON.stringify(overview)}, phases: [{ name: "Ship", steps: [{ kind: "gate", gate: "ship", approval: { quorum: { count: ${count} } } }] }] } };\n`;
504
+ release("v1.1.0", {
505
+ "chant.workspace.json": JSON.stringify({ name: "proj", schema: 1, members: [{ name: "delivery", dir: "delivery", kind: "chant" }], identity: { gates: { ship: { quorum: { count: 3 } } } } }, null, 2) + "\n",
506
+ "delivery/chant.config.ts": "export default {};\n",
507
+ "delivery/ops/release.op.ts": op("v1", 2),
508
+ });
509
+ proj = join(root, "members-4404");
510
+ expect((await initFromCommand({ from: `${tpl}@v1.1.0`, path: proj })).error).toBeUndefined();
511
+ git(proj, ["init", "-q", "-b", "main"]);
512
+ commitProject("init from v1.1");
513
+ release("v2.0.0", { "delivery/ops/release.op.ts": op("v2", 1) });
514
+ const port = ledger();
515
+ const result = await upgradeCommand({ root: proj, to: "v2.0.0", runChant: passing, ledger: port, now: "2026-09-23T10:00:00.000Z" });
516
+ expect(result.outcome).toBe("gated");
517
+ expect(result.staged!.governance).toMatchObject({ approval: { mode: "log-only", quorum: { count: 3 } } });
518
+ });
519
+
500
520
  test("refuses a scope with uncommitted changes, and a scope that does not exist", async () => {
501
521
  put(proj, "README.md", "dirty\n");
502
522
  await expect(stageUpgrade({ root: proj, to: "v1.0.0" })).rejects.toThrow(/uncommitted changes/);
@@ -573,16 +573,25 @@ export function isGovernancePath(path: string): boolean {
573
573
  * one. The Op files are read from the project's tree, which the upgrade
574
574
  * requires to match HEAD for every path the patch touches.
575
575
  *
576
+ * Both come from HEAD, never from the patch: the Op files are read from the
577
+ * clean project tree, and the declaration through `identityGatesAt(repo,
578
+ * head)` (`git show`). A change the upgrade itself makes to an Op or to
579
+ * `chant.workspace.json` cannot lower the approvals it asks for (#4404). A
580
+ * gate whose `identity.gates.<gate>.quorum` at HEAD sets one is asked for that
581
+ * quorum, as a run of the gate is (#4324), not the Op's own.
582
+ *
576
583
  * Discovery runs from each changed Op file's own directory, so it finds the
577
584
  * file under the project that owns it. Run from a workspace root, it would
578
585
  * skip every member's Op files (#2892).
579
586
  */
580
- async function rulesAtHead(repo: string, paths: string[]): Promise<ResolvedGateApproval> {
587
+ async function rulesAtHead(repo: string, paths: string[], head: string): Promise<ResolvedGateApproval> {
581
588
  const opFiles = new Set(paths.filter((p) => GOVERNANCE[0].test(p)));
582
589
  let count = 1;
583
590
  let roles: string[] | undefined;
584
591
  if (opFiles.size > 0) {
585
592
  const { discoverOps } = await import("../op/discover");
593
+ const { identityGatesAt } = await import("../gate-decision");
594
+ const atBase = new Map(identityGatesAt(repo, head).map((g) => [g.gate, g.quorum]));
586
595
  const dirs = new Set([...opFiles].map((p) => dirname(join(repo, p))));
587
596
  for (const dir of dirs) {
588
597
  if (!existsSync(dir)) continue;
@@ -592,7 +601,7 @@ async function rulesAtHead(repo: string, paths: string[]): Promise<ResolvedGateA
592
601
  if (!opFiles.has(rel)) continue;
593
602
  // #4290: a quorum may be a list of rules; the strictest count is asked for.
594
603
  for (const approval of gateApprovals(config)) {
595
- for (const rule of quorumRules(approval.quorum)) {
604
+ for (const rule of quorumRules(atBase.get(approval.gate) ?? approval.quorum)) {
596
605
  if (rule.count > count) {
597
606
  count = rule.count;
598
607
  roles = rule.roles;
@@ -605,12 +614,12 @@ async function rulesAtHead(repo: string, paths: string[]): Promise<ResolvedGateA
605
614
  return { mode: "log-only", quorum: { count, ...(roles && roles.length > 0 ? { roles } : {}) } };
606
615
  }
607
616
 
608
- type StepLike = { kind: string; approval?: { quorum?: GateQuorumSpec }; steps?: StepLike[] };
617
+ type StepLike = { kind: string; gate?: string; signalName?: string; approval?: { quorum?: GateQuorumSpec }; steps?: StepLike[] };
609
618
 
610
- function gateApprovals(config: { phases?: Array<{ steps: StepLike[] }>; onFailure?: Array<{ steps: StepLike[] }> }): Array<{ quorum?: GateQuorumSpec }> {
611
- const out: Array<{ quorum?: GateQuorumSpec }> = [];
619
+ function gateApprovals(config: { phases?: Array<{ steps: StepLike[] }>; onFailure?: Array<{ steps: StepLike[] }> }): Array<{ gate: string; quorum?: GateQuorumSpec }> {
620
+ const out: Array<{ gate: string; quorum?: GateQuorumSpec }> = [];
612
621
  const visit = (step: StepLike): void => {
613
- if (step.kind === "gate") out.push(step.approval ?? {});
622
+ if (step.kind === "gate") out.push({ gate: step.gate ?? step.signalName ?? "", ...(step.approval?.quorum !== undefined ? { quorum: step.approval.quorum } : {}) });
614
623
  if (step.kind === "effect") (step.steps ?? []).forEach(visit);
615
624
  };
616
625
  for (const p of [...(config.phases ?? []), ...(config.onFailure ?? [])]) p.steps.forEach(visit);
@@ -865,7 +874,7 @@ export async function stageUpgrade(options: UpgradeOptions): Promise<StagedUpgra
865
874
  if (touched.length > 0) {
866
875
  throw new UpgradeError(`the upgrade changes governance files that have uncommitted edits (${touched.map((l) => l.slice(3)).join(", ")}). Commit them first: their gates are read at HEAD.`);
867
876
  }
868
- governance = { paths: governed, approval: await rulesAtHead(repo, governed), rules: head };
877
+ governance = { paths: governed, approval: await rulesAtHead(repo, governed, head), rules: head };
869
878
  }
870
879
 
871
880
  // 5. Checks.
@@ -195,10 +195,11 @@ function nearNames(name: string, outputs: JoinHandle[]): string[] {
195
195
  */
196
196
  export function resolveLinks(
197
197
  declaration: Declaration,
198
- handles: ReadonlyMap<string, MemberHandles>,
198
+ sourceHandles: ReadonlyMap<string, MemberHandles>,
199
199
  telemetry?: TelemetryTargets,
200
200
  readLinks?: ReadonlyMap<string, KindLinkRead>,
201
201
  ): LinkTableRow[] {
202
+ const handles = withReaderOutputs(sourceHandles, readLinks);
202
203
  const rows: LinkTableRow[] = [];
203
204
  const declaredTargets = new Map<string, Set<string>>();
204
205
 
@@ -311,6 +312,28 @@ export function resolveLinks(
311
312
  return [...rows, ...inferred];
312
313
  }
313
314
 
315
+ /**
316
+ * `handles` with the outputs each member's kind reader found in its files
317
+ * (#3935) added to the member's own. The member stays complete unless a gap
318
+ * the reader flagged `outputs` left a name out. A member with no handles
319
+ * (its directory is missing, or its kind unknown) gets none.
320
+ */
321
+ export function withReaderOutputs(handles: ReadonlyMap<string, MemberHandles>, reads: ReadonlyMap<string, KindLinkRead> | undefined): ReadonlyMap<string, MemberHandles> {
322
+ if (!reads) return handles;
323
+ let out: Map<string, MemberHandles> | undefined;
324
+ for (const [member, read] of reads) {
325
+ const base = handles.get(member);
326
+ const gaps = read.gaps.filter((g) => g.outputs);
327
+ if (!base || (!read.outputs?.length && gaps.length === 0)) continue;
328
+ const have = new Set(base.outputs.map((o) => o.name));
329
+ const added = (read.outputs ?? []).filter((o) => !have.has(o.name) && have.add(o.name)).map((o) => ({ name: o.name }));
330
+ const complete = base.complete && gaps.length === 0;
331
+ const why = base.why ?? (gaps.length === 0 ? null : gaps.length === 1 ? gaps[0].message : `${gaps[0].message}, and ${gaps.length - 1} more`);
332
+ (out ??= new Map(handles)).set(member, { ...base, outputs: [...base.outputs, ...added], complete, why: complete ? base.why : why, exposesNone: false });
333
+ }
334
+ return out ?? handles;
335
+ }
336
+
314
337
  /** Match a declared or reader's output row against its producer's handles, setting its status and reason. */
315
338
  function resolveOutput(row: LinkRow, handles: ReadonlyMap<string, MemberHandles>): void {
316
339
  const producer = handles.get(row.producer);
@@ -129,7 +129,7 @@ describe("chant workspace ls on built workspaces", () => {
129
129
  ["tools", false, "kind-probe-failed"],
130
130
  ["vendor", true, null],
131
131
  ]);
132
- expect(doc.members[2].reason?.message).toMatch(/known kinds: chant, design, other, workspace/);
132
+ expect(doc.members[2].reason?.message).toMatch(/known kinds: chant, design, other, repo, workspace/);
133
133
  expect(doc.members[0].roles).toEqual([{ name: "frontend", path: "src" }]);
134
134
  expect(doc.groups[0].reason?.code).toBe("no-matches");
135
135
  expect(doc.summary).toEqual({ members: 5, unreadable: 3, groups: 1, matches: 0 });
@@ -51,6 +51,7 @@
51
51
  "required": ["name", "root", "file", "schema", "minReader", "pins"],
52
52
  "properties": {
53
53
  "name": { "type": "string", "description": "The declared name. With the revision, it identifies the workspace." },
54
+ "repo": { "type": ["string", "null"], "description": "Added in contract 1 by #3168. The repository the document came from: the URL of the remote origin of the git repository that holds the workspace, with any user name or password removed, or null when there is no origin or the workspace is outside git. With name, root and the revision, it says which repository's workspace a document describes, so a reader that gathers several repositories' documents, such as an org workspace's repo members, tells them apart." },
54
55
  "root": {
55
56
  "type": "string",
56
57
  "description": "The workspace root relative to the git root, with / separators, \".\" for the git root itself. An absolute path outside a git repository."
@@ -158,6 +159,12 @@
158
159
  "type": "array",
159
160
  "items": { "$ref": "#/$defs/generatedFile" }
160
161
  },
162
+ "repo": {
163
+ "description": "Added in contract 1 by #3168. For a member of kind repo, the other repository's workspace it names: url, the pinned rev and the workspace root inside that repository. Absent for every other kind. A repo member's dir is @repo/<name>, a marker that names no directory of this repository.",
164
+ "type": "object",
165
+ "required": ["url", "rev", "workspace"],
166
+ "properties": { "url": { "type": "string" }, "rev": { "type": "string" }, "workspace": { "type": "string" } }
167
+ },
161
168
  "agents": {
162
169
  "description": "Added in contract 1 by #3615. The names of the agent sessions the declaration binds to the member (agents[].member or agents[].members), in declaration order, read where workspace.agentsFrom says. Empty when none is. A session bound to several members is listed on each.",
163
170
  "type": "array",
@@ -39,7 +39,8 @@ import {
39
39
  } from "./declaration";
40
40
  import type { ReasonCode } from "./reason-codes";
41
41
  import { memberGenerated, type LsGenerated } from "./ls-generated";
42
- import { loadKindRegistry, probeKind, type KindRegistry } from "./kinds";
42
+ import { repoIdentity } from "./repo-ref";
43
+ import { loadKindRegistry, probeKind, REPO_KIND, type KindRegistry } from "./kinds";
43
44
  import type { WorkspaceTree } from "./tree";
44
45
  import { handToRootChant, locateWorkspace } from "./which-chant";
45
46
  import { declaredSessions } from "./write-scope";
@@ -117,6 +118,8 @@ export interface LsMember {
117
118
  generated: LsGenerated[];
118
119
  /** The agent sessions bound to the member, in declaration order (#3615); empty when none is. */
119
120
  agents: string[];
121
+ /** For a member of kind repo, the other repository's workspace it names (#3168). Absent for every other kind. */
122
+ repo?: { url: string; rev: string; workspace: string };
120
123
  }
121
124
 
122
125
  /**
@@ -168,6 +171,8 @@ export type LsDocument =
168
171
  at: string | null;
169
172
  workspace: {
170
173
  name: string;
174
+ /** The repository the document came from (#3168): its origin URL, redacted, or null. */
175
+ repo: string | null;
171
176
  /** The workspace root relative to the git root, `"."` for the git root itself. Absolute outside git. */
172
177
  root: string;
173
178
  file: string;
@@ -211,6 +216,8 @@ export interface LsQuery {
211
216
 
212
217
  /** A member's reason code, or null when its kind can read it. */
213
218
  export function memberReason(member: Member, tree: WorkspaceTree, kinds: KindRegistry): LsMember["reason"] {
219
+ // A repo member has no directory here (#3168); it is read from its own repository.
220
+ if (member.kind === REPO_KIND) return null;
214
221
  if (tree.stat(member.dir === "." ? "" : member.dir) !== "dir") {
215
222
  return { code: "dir-missing", message: `${member.dir} is not a directory${tree.label}` };
216
223
  }
@@ -283,6 +290,7 @@ function readListing(query: LsQuery): { doc: LsDocument; declaration?: Declarati
283
290
  records: unloaded(m.records),
284
291
  generated: memberGenerated(m, tree, repoPrefix),
285
292
  agents: sessions.agents.filter((a) => a.members.includes(m.name)).map((a) => a.name),
293
+ ...(m.repo ? { repo: { ...m.repo } } : {}),
286
294
  // The member's x- keys, as declared (#3595).
287
295
  ...extensionsAt(declaration, m.pointer),
288
296
  };
@@ -311,6 +319,7 @@ function readListing(query: LsQuery): { doc: LsDocument; declaration?: Declarati
311
319
  at,
312
320
  workspace: {
313
321
  name: declaration.name,
322
+ repo: repoIdentity(rootOnDisk),
314
323
  root: located.root,
315
324
  file: declaration.file,
316
325
  schema: declaration.schema,
@@ -241,6 +241,8 @@ export function planMembers(verb: WorkspaceVerb, root: string, options: PlanOpti
241
241
  const why =
242
242
  entry.kind === "workspace"
243
243
  ? "a nested workspace runs its own chant workspace commands"
244
+ : entry.kind === "repo"
245
+ ? "a repo member is another repository's workspace, which runs its own chant workspace commands; chant workspace graph reads it at its pinned revision"
244
246
  : entry.kind === "design"
245
247
  ? "a design member is data: chant reads its files for record pins and builds nothing"
246
248
  : `chant workspace ${verb} runs members of kind chant, and this one is kind ${entry.kind}`;
@@ -556,13 +558,17 @@ export async function runWorkspaceMembers(ctx: CommandContext, verb: WorkspaceVe
556
558
  }
557
559
 
558
560
  const results = await executePlan(plan, args);
559
- const anyFailed = results.some((r) => r.exitCode !== 0) || plan.unreadable.length > 0;
561
+ // #2549: the disagreement lint, over every member's graph, when asked for.
562
+ const cross = verb === "lint" && args.crossRoot ? await runCrossRoot(found.dir, args) : undefined;
563
+ const anyFailed = results.some((r) => r.exitCode !== 0) || plan.unreadable.length > 0 || cross?.failed === true;
560
564
  const workspace = { name: plan.workspace.name, root: plan.workspace.root };
561
565
 
562
566
  if (verb === "lint" && args.format === "sarif") {
563
567
  for (const r of results) if (r.stderr.trim()) process.stderr.write(r.stderr.endsWith("\n") ? r.stderr : `${r.stderr}\n`);
564
568
  const unreadable: MemberOutput[] = plan.unreadable.map((s) => ({ id: s.name, member: s.name, dir: s.dir ?? ".", exitCode: 1, stdout: "", stderr: `${s.reason.code}: ${s.reason.message}` }));
565
- emitDocument(mergeSarif([...results, ...unreadable], plan.workspace.root), args.output);
569
+ const sarif = mergeSarif([...results, ...unreadable], plan.workspace.root) as { runs?: unknown[] };
570
+ if (cross) (sarif.runs ??= []).push(crossRootSarifRun(cross));
571
+ emitDocument(sarif, args.output);
566
572
  console.error(summaryLine(plan, results));
567
573
  return anyFailed ? 1 : 0;
568
574
  }
@@ -598,6 +604,7 @@ export async function runWorkspaceMembers(ctx: CommandContext, verb: WorkspaceVe
598
604
  ...plan.unreadable.map((s) => ({ member: s.name, dir: s.dir, exitCode: 1, diagnostics: null, reason: s.reason })),
599
605
  ],
600
606
  skipped: plan.skipped,
607
+ ...(cross ? { crossRoot: crossRootJson(cross) } : {}),
601
608
  };
602
609
  }
603
610
  emitDocument(doc, args.output);
@@ -608,5 +615,56 @@ export async function runWorkspaceMembers(ctx: CommandContext, verb: WorkspaceVe
608
615
  // Text: each member's own output under a heading. A build without -o prints
609
616
  // no templates, since several members' templates can't share one stdout.
610
617
  printSections(plan, results, verb !== "build" || args.output !== undefined);
618
+ if (cross) printCrossRoot(cross);
611
619
  return anyFailed ? 1 : 0;
612
620
  }
621
+
622
+ // ── The disagreement lint (#2549) ────────────────────────────────────────────
623
+
624
+ interface CrossRootRun {
625
+ result?: import("./cross-root-lint").CrossRootLintResult;
626
+ /** Why the members' graphs could not be read. */
627
+ error?: string;
628
+ /** An error finding, or a graph that could not be read. */
629
+ failed: boolean;
630
+ }
631
+
632
+ async function runCrossRoot(root: string, args: ParsedArgs): Promise<CrossRootRun> {
633
+ const { workspaceGraph } = await import("./graph-cli");
634
+ const { crossRootLint } = await import("./cross-root-lint");
635
+ const { doc, failed } = await workspaceGraph({ cwd: root, ...(args.members?.length ? { members: args.members } : {}), onStderr: (t) => process.stderr.write(t) });
636
+ if ("error" in doc) return { error: `${doc.error.code}: ${doc.error.message}`, failed: true };
637
+ const result = await crossRootLint(doc, args.env ? { env: args.env } : {});
638
+ return { result, failed: failed || result.findings.some((f) => f.severity === "error") };
639
+ }
640
+
641
+ function crossRootJson(cross: CrossRootRun): unknown {
642
+ return cross.result ? { findings: cross.result.findings, checks: cross.result.checks, unloaded: cross.result.unloaded } : { error: cross.error };
643
+ }
644
+
645
+ function crossRootSarifRun(cross: CrossRootRun): unknown {
646
+ const findings = cross.result?.findings ?? [];
647
+ return {
648
+ tool: { driver: { name: "chant", rules: [...new Set(findings.map((f) => f.checkId))].sort().map((id) => ({ id })) } },
649
+ automationDetails: { id: "cross-root/" },
650
+ properties: { crossRoot: true, checks: cross.result?.checks ?? [], unloaded: cross.result?.unloaded ?? [] },
651
+ results: findings.map((f) => ({
652
+ ruleId: f.checkId,
653
+ level: f.severity === "error" ? "error" : f.severity === "warning" ? "warning" : "note",
654
+ message: { text: f.message },
655
+ ...(f.root ? { properties: { member: f.root } } : {}),
656
+ })),
657
+ ...(cross.error ? { invocations: [{ executionSuccessful: false, toolExecutionNotifications: [{ message: { text: cross.error } }] }] } : {}),
658
+ };
659
+ }
660
+
661
+ function printCrossRoot(cross: CrossRootRun): void {
662
+ if (!cross.result) {
663
+ console.log(`── cross-root (the members' graphs could not be read: ${cross.error})`);
664
+ return;
665
+ }
666
+ const { findings, checks, unloaded } = cross.result;
667
+ console.log(`── cross-root (${checks.join(", ")}; ${findings.length} finding${findings.length === 1 ? "" : "s"})`);
668
+ for (const f of findings) console.log(` ${f.severity.padEnd(7)} ${f.checkId}${f.root ? ` [${f.root}]` : ""} ${f.message}`);
669
+ for (const u of unloaded) console.log(` (lexicon ${u.lexicon}: its cross-root checks could not be loaded: ${u.reason})`);
670
+ }