@intentius/chant 0.124.45 → 0.124.48

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 (772) 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/capabilities/aws.paths +148 -0
  47. package/dist/capabilities/base-mappings.json +122 -0
  48. package/dist/capabilities/catalog.d.ts +89 -0
  49. package/dist/capabilities/catalog.d.ts.map +1 -0
  50. package/dist/capabilities/catalog.js +204 -0
  51. package/dist/capabilities/language.d.ts +103 -0
  52. package/dist/capabilities/language.d.ts.map +1 -0
  53. package/dist/capabilities/language.js +335 -0
  54. package/dist/capabilities/matcher.d.ts +65 -0
  55. package/dist/capabilities/matcher.d.ts.map +1 -0
  56. package/dist/capabilities/matcher.js +299 -0
  57. package/dist/capabilities/view.d.ts +207 -0
  58. package/dist/capabilities/view.d.ts.map +1 -0
  59. package/dist/capabilities/view.js +484 -0
  60. package/dist/cli/commands/carve.d.ts +3 -0
  61. package/dist/cli/commands/carve.d.ts.map +1 -1
  62. package/dist/cli/commands/carve.js +36 -7
  63. package/dist/cli/commands/import-report.d.ts +26 -0
  64. package/dist/cli/commands/import-report.d.ts.map +1 -0
  65. package/dist/cli/commands/import-report.js +65 -0
  66. package/dist/cli/commands/import.d.ts +11 -1
  67. package/dist/cli/commands/import.d.ts.map +1 -1
  68. package/dist/cli/commands/import.js +22 -3
  69. package/dist/cli/commands/lint.d.ts.map +1 -1
  70. package/dist/cli/commands/lint.js +1 -0
  71. package/dist/cli/commands/migrate.d.ts +13 -1
  72. package/dist/cli/commands/migrate.d.ts.map +1 -1
  73. package/dist/cli/commands/migrate.js +134 -10
  74. package/dist/cli/handlers/approve.d.ts.map +1 -1
  75. package/dist/cli/handlers/approve.js +21 -2
  76. package/dist/cli/handlers/build.d.ts.map +1 -1
  77. package/dist/cli/handlers/build.js +6 -1
  78. package/dist/cli/handlers/declared-graph.d.ts +44 -0
  79. package/dist/cli/handlers/declared-graph.d.ts.map +1 -0
  80. package/dist/cli/handlers/declared-graph.js +87 -0
  81. package/dist/cli/handlers/graph.d.ts +25 -0
  82. package/dist/cli/handlers/graph.d.ts.map +1 -1
  83. package/dist/cli/handlers/graph.js +93 -87
  84. package/dist/cli/handlers/ledger.d.ts +3 -0
  85. package/dist/cli/handlers/ledger.d.ts.map +1 -0
  86. package/dist/cli/handlers/ledger.js +55 -0
  87. package/dist/cli/handlers/migrate.d.ts.map +1 -1
  88. package/dist/cli/handlers/migrate.js +18 -11
  89. package/dist/cli/handlers/misc.d.ts.map +1 -1
  90. package/dist/cli/handlers/misc.js +12 -1
  91. package/dist/cli/handlers/pr-env.d.ts.map +1 -1
  92. package/dist/cli/handlers/pr-env.js +4 -3
  93. package/dist/cli/handlers/pr.d.ts +15 -1
  94. package/dist/cli/handlers/pr.d.ts.map +1 -1
  95. package/dist/cli/handlers/pr.js +143 -39
  96. package/dist/cli/handlers/query.d.ts +7 -2
  97. package/dist/cli/handlers/query.d.ts.map +1 -1
  98. package/dist/cli/handlers/query.js +16 -4
  99. package/dist/cli/handlers/release.d.ts.map +1 -1
  100. package/dist/cli/handlers/release.js +6 -3
  101. package/dist/cli/handlers/search-count.d.ts +89 -0
  102. package/dist/cli/handlers/search-count.d.ts.map +1 -0
  103. package/dist/cli/handlers/search-count.js +132 -0
  104. package/dist/cli/handlers/search.d.ts +356 -6
  105. package/dist/cli/handlers/search.d.ts.map +1 -1
  106. package/dist/cli/handlers/search.js +1039 -155
  107. package/dist/cli/main.d.ts.map +1 -1
  108. package/dist/cli/main.js +88 -17
  109. package/dist/cli/mcp/server.d.ts.map +1 -1
  110. package/dist/cli/mcp/server.js +3 -0
  111. package/dist/cli/mcp/tools/capabilities.d.ts +7 -0
  112. package/dist/cli/mcp/tools/capabilities.d.ts.map +1 -0
  113. package/dist/cli/mcp/tools/capabilities.js +27 -0
  114. package/dist/cli/mcp/workspace-tools.d.ts +3 -1
  115. package/dist/cli/mcp/workspace-tools.d.ts.map +1 -1
  116. package/dist/cli/mcp/workspace-tools.js +47 -2
  117. package/dist/cli/parse-args.d.ts.map +1 -1
  118. package/dist/cli/parse-args.js +100 -19
  119. package/dist/cli/registry.d.ts +36 -4
  120. package/dist/cli/registry.d.ts.map +1 -1
  121. package/dist/components/builders.d.ts +5 -1
  122. package/dist/components/builders.d.ts.map +1 -1
  123. package/dist/components/builders.js +5 -0
  124. package/dist/components/capability-plugin-loader.d.ts.map +1 -1
  125. package/dist/components/capability-plugin-loader.js +16 -1
  126. package/dist/components/capability-plugin.d.ts +14 -0
  127. package/dist/components/capability-plugin.d.ts.map +1 -1
  128. package/dist/components/pr-env-ports.d.ts +29 -0
  129. package/dist/components/pr-env-ports.d.ts.map +1 -0
  130. package/dist/components/pr-env-ports.js +77 -0
  131. package/dist/components/pr-env-run.d.ts +5 -21
  132. package/dist/components/pr-env-run.d.ts.map +1 -1
  133. package/dist/components/pr-env-run.js +6 -70
  134. package/dist/components/pr-environment.d.ts +2 -1
  135. package/dist/components/pr-environment.d.ts.map +1 -1
  136. package/dist/components/pr-environment.js +1 -1
  137. package/dist/components/pr-hooks.d.ts +21 -1
  138. package/dist/components/pr-hooks.d.ts.map +1 -1
  139. package/dist/components/pr-hooks.js +72 -4
  140. package/dist/components/pr-pipeline.d.ts +45 -10
  141. package/dist/components/pr-pipeline.d.ts.map +1 -1
  142. package/dist/components/pr-pipeline.js +47 -14
  143. package/dist/components/pr-policy.d.ts +36 -0
  144. package/dist/components/pr-policy.d.ts.map +1 -1
  145. package/dist/components/pr-policy.js +36 -0
  146. package/dist/components/starter-plugin.d.ts +1 -0
  147. package/dist/components/starter-plugin.d.ts.map +1 -1
  148. package/dist/components/starter-plugin.js +5 -1
  149. package/dist/components/verbs/index.d.ts +2 -0
  150. package/dist/components/verbs/index.d.ts.map +1 -1
  151. package/dist/components/verbs/index.js +2 -0
  152. package/dist/components/verbs/publish-artifact.d.ts +65 -0
  153. package/dist/components/verbs/publish-artifact.d.ts.map +1 -0
  154. package/dist/components/verbs/publish-artifact.js +74 -0
  155. package/dist/components/verbs/publish-image.d.ts +129 -0
  156. package/dist/components/verbs/publish-image.d.ts.map +1 -0
  157. package/dist/components/verbs/publish-image.js +145 -0
  158. package/dist/config-environments.d.ts +112 -0
  159. package/dist/config-environments.d.ts.map +1 -0
  160. package/dist/config-environments.js +114 -0
  161. package/dist/config.d.ts +32 -84
  162. package/dist/config.d.ts.map +1 -1
  163. package/dist/config.js +13 -96
  164. package/dist/gate-decision.d.ts +11 -2
  165. package/dist/gate-decision.d.ts.map +1 -1
  166. package/dist/gate-decision.js +15 -3
  167. package/dist/graph-detail.d.ts.map +1 -1
  168. package/dist/graph-detail.js +15 -3
  169. package/dist/graph-effective.d.ts +33 -2
  170. package/dist/graph-effective.d.ts.map +1 -1
  171. package/dist/graph-effective.js +215 -68
  172. package/dist/graph-ir.d.ts +48 -0
  173. package/dist/graph-ir.d.ts.map +1 -1
  174. package/dist/graph-ir.js +130 -0
  175. package/dist/graph-sub-template.d.ts +11 -0
  176. package/dist/graph-sub-template.d.ts.map +1 -0
  177. package/dist/graph-sub-template.js +19 -0
  178. package/dist/graph-walk.d.ts +66 -0
  179. package/dist/graph-walk.d.ts.map +1 -0
  180. package/dist/graph-walk.js +70 -0
  181. package/dist/import/parser.d.ts +19 -0
  182. package/dist/import/parser.d.ts.map +1 -1
  183. package/dist/lexicon.d.ts +80 -6
  184. package/dist/lexicon.d.ts.map +1 -1
  185. package/dist/lifecycle/gate-ledger.d.ts +11 -0
  186. package/dist/lifecycle/gate-ledger.d.ts.map +1 -1
  187. package/dist/lifecycle/git.d.ts +2 -5
  188. package/dist/lifecycle/git.d.ts.map +1 -1
  189. package/dist/lifecycle/ledger-read.d.ts +65 -0
  190. package/dist/lifecycle/ledger-read.d.ts.map +1 -0
  191. package/dist/lifecycle/ledger-read.js +160 -0
  192. package/dist/lifecycle/observe.d.ts +28 -0
  193. package/dist/lifecycle/observe.d.ts.map +1 -1
  194. package/dist/lifecycle/observe.js +23 -1
  195. package/dist/lifecycle/release-workstation.d.ts +7 -0
  196. package/dist/lifecycle/release-workstation.d.ts.map +1 -1
  197. package/dist/lifecycle/release-workstation.js +25 -0
  198. package/dist/lifecycle/run-ledger.d.ts +4 -3
  199. package/dist/lifecycle/run-ledger.d.ts.map +1 -1
  200. package/dist/lifecycle/run-ledger.js +1 -0
  201. package/dist/lint/discover.js +3 -1
  202. package/dist/lint/post-synth.d.ts +127 -1
  203. package/dist/lint/post-synth.d.ts.map +1 -1
  204. package/dist/lint/post-synth.js +84 -2
  205. package/dist/lint/rule.d.ts +6 -0
  206. package/dist/lint/rule.d.ts.map +1 -1
  207. package/dist/lint/rules/op/index.d.ts +1 -0
  208. package/dist/lint/rules/op/index.d.ts.map +1 -1
  209. package/dist/lint/rules/op/index.js +2 -0
  210. package/dist/lint/rules/op/ops019-workspace-op-target.d.ts +32 -0
  211. package/dist/lint/rules/op/ops019-workspace-op-target.d.ts.map +1 -0
  212. package/dist/lint/rules/op/ops019-workspace-op-target.js +63 -0
  213. package/dist/live-endpoint.d.ts +1 -1
  214. package/dist/live-endpoint.d.ts.map +1 -1
  215. package/dist/live-endpoint.js +1 -1
  216. package/dist/observation.d.ts +48 -1
  217. package/dist/observation.d.ts.map +1 -1
  218. package/dist/observation.js +21 -5
  219. package/dist/op/activities/activity-contracts.d.ts +9 -6
  220. package/dist/op/activities/activity-contracts.d.ts.map +1 -1
  221. package/dist/op/activities/activity-contracts.js +5 -2
  222. package/dist/op/activities/index.d.ts +2 -2
  223. package/dist/op/activities/index.d.ts.map +1 -1
  224. package/dist/op/activities/index.js +1 -1
  225. package/dist/op/activities/review-agent.d.ts +90 -4
  226. package/dist/op/activities/review-agent.d.ts.map +1 -1
  227. package/dist/op/activities/review-agent.js +217 -19
  228. package/dist/op/activities/work-evidence.d.ts +8 -1
  229. package/dist/op/activities/work-evidence.d.ts.map +1 -1
  230. package/dist/op/activities/work-evidence.js +1 -0
  231. package/dist/op/builders.d.ts +9 -5
  232. package/dist/op/builders.d.ts.map +1 -1
  233. package/dist/op/builders.js +9 -5
  234. package/dist/op/composites/apply-op.d.ts +29 -2
  235. package/dist/op/composites/apply-op.d.ts.map +1 -1
  236. package/dist/op/composites/apply-op.js +20 -1
  237. package/dist/op/gate-approval.d.ts +11 -5
  238. package/dist/op/gate-approval.d.ts.map +1 -1
  239. package/dist/op/gate-approval.js +11 -6
  240. package/dist/op/gate-quorum.d.ts +100 -3
  241. package/dist/op/gate-quorum.d.ts.map +1 -1
  242. package/dist/op/gate-quorum.js +108 -6
  243. package/dist/op/gate-review.d.ts +9 -0
  244. package/dist/op/gate-review.d.ts.map +1 -1
  245. package/dist/op/gate-review.js +13 -0
  246. package/dist/op/gate.d.ts +44 -3
  247. package/dist/op/gate.d.ts.map +1 -1
  248. package/dist/op/gate.js +25 -4
  249. package/dist/op/index.d.ts +3 -3
  250. package/dist/op/index.d.ts.map +1 -1
  251. package/dist/op/index.js +1 -1
  252. package/dist/op/local-executor.d.ts +38 -3
  253. package/dist/op/local-executor.d.ts.map +1 -1
  254. package/dist/op/local-executor.js +48 -6
  255. package/dist/op/op-waves-run.d.ts +26 -1
  256. package/dist/op/op-waves-run.d.ts.map +1 -1
  257. package/dist/op/op-waves-run.js +259 -13
  258. package/dist/op/op-waves.d.ts +72 -5
  259. package/dist/op/op-waves.d.ts.map +1 -1
  260. package/dist/op/op-waves.js +110 -15
  261. package/dist/op/runtime.d.ts +11 -0
  262. package/dist/op/runtime.d.ts.map +1 -1
  263. package/dist/op/schedule-overdue.d.ts +16 -0
  264. package/dist/op/schedule-overdue.d.ts.map +1 -1
  265. package/dist/op/schedule-overdue.js +36 -0
  266. package/dist/op/stage-hooks.d.ts +9 -2
  267. package/dist/op/stage-hooks.d.ts.map +1 -1
  268. package/dist/op/stage-hooks.js +7 -5
  269. package/dist/op/workspace-run-env.d.ts +22 -0
  270. package/dist/op/workspace-run-env.d.ts.map +1 -0
  271. package/dist/op/workspace-run-env.js +21 -0
  272. package/dist/pr-forge.d.ts +24 -0
  273. package/dist/pr-forge.d.ts.map +1 -1
  274. package/dist/pr-forge.js +47 -0
  275. package/dist/pr-post.d.ts +42 -0
  276. package/dist/pr-post.d.ts.map +1 -1
  277. package/dist/pr-post.js +67 -1
  278. package/dist/read-filter.d.ts +65 -0
  279. package/dist/read-filter.d.ts.map +1 -0
  280. package/dist/read-filter.js +179 -0
  281. package/dist/run-event.d.ts +1 -1
  282. package/dist/run-event.d.ts.map +1 -1
  283. package/dist/terraform/carve.d.ts +24 -3
  284. package/dist/terraform/carve.d.ts.map +1 -1
  285. package/dist/terraform/carve.js +32 -14
  286. package/dist/terraform/cuts.d.ts +97 -0
  287. package/dist/terraform/cuts.d.ts.map +1 -0
  288. package/dist/terraform/cuts.js +138 -0
  289. package/dist/terraform/folds.d.ts +26 -0
  290. package/dist/terraform/folds.d.ts.map +1 -0
  291. package/dist/terraform/folds.js +38 -0
  292. package/dist/terraform/graph.d.ts.map +1 -1
  293. package/dist/terraform/graph.js +10 -0
  294. package/dist/terraform/reach.d.ts +140 -0
  295. package/dist/terraform/reach.d.ts.map +1 -0
  296. package/dist/terraform/reach.js +170 -0
  297. package/dist/terraform/score.d.ts +14 -0
  298. package/dist/terraform/score.d.ts.map +1 -1
  299. package/dist/terraform/score.js +14 -31
  300. package/dist/terraform/state.d.ts.map +1 -1
  301. package/dist/terraform/state.js +4 -2
  302. package/dist/terraform/types.d.ts +8 -0
  303. package/dist/terraform/types.d.ts.map +1 -1
  304. package/dist/wave-blocked.d.ts +10 -1
  305. package/dist/wave-blocked.d.ts.map +1 -1
  306. package/dist/wave-blocked.js +31 -3
  307. package/dist/wave-runtime.mjs +266 -256
  308. package/dist/workspace/capabilities-contract.d.ts +66 -0
  309. package/dist/workspace/capabilities-contract.d.ts.map +1 -0
  310. package/dist/workspace/capabilities-contract.js +107 -0
  311. package/dist/workspace/capabilities.schema.json +229 -0
  312. package/dist/workspace/check.schema.json +11 -2
  313. package/dist/workspace/checks/ci-branch.d.ts +44 -0
  314. package/dist/workspace/checks/ci-branch.d.ts.map +1 -0
  315. package/dist/workspace/checks/ci-branch.js +133 -0
  316. package/dist/workspace/checks/links.d.ts +3 -0
  317. package/dist/workspace/checks/links.d.ts.map +1 -1
  318. package/dist/workspace/checks/links.js +12 -1
  319. package/dist/workspace/checks/org.d.ts +48 -0
  320. package/dist/workspace/checks/org.d.ts.map +1 -0
  321. package/dist/workspace/checks/org.js +156 -0
  322. package/dist/workspace/checks.d.ts +11 -1
  323. package/dist/workspace/checks.d.ts.map +1 -1
  324. package/dist/workspace/checks.js +59 -7
  325. package/dist/workspace/ci-branches.d.ts +16 -0
  326. package/dist/workspace/ci-branches.d.ts.map +1 -1
  327. package/dist/workspace/ci-branches.js +27 -0
  328. package/dist/workspace/ci-green-cli.js +7 -7
  329. package/dist/workspace/ci-green-workflow.d.ts +1 -1
  330. package/dist/workspace/ci-green-workflow.d.ts.map +1 -1
  331. package/dist/workspace/ci-green-workflow.js +13 -8
  332. package/dist/workspace/ci-green.d.ts +14 -0
  333. package/dist/workspace/ci-green.d.ts.map +1 -1
  334. package/dist/workspace/ci-green.js +44 -11
  335. package/dist/workspace/ci-run.d.ts +9 -1
  336. package/dist/workspace/ci-run.d.ts.map +1 -1
  337. package/dist/workspace/ci-run.js +67 -6
  338. package/dist/workspace/ci-serve.d.ts +9 -0
  339. package/dist/workspace/ci-serve.d.ts.map +1 -1
  340. package/dist/workspace/ci-serve.js +114 -76
  341. package/dist/workspace/compose-graph.d.ts +23 -0
  342. package/dist/workspace/compose-graph.d.ts.map +1 -1
  343. package/dist/workspace/compose-graph.js +3 -1
  344. package/dist/workspace/composites.d.ts +4 -1
  345. package/dist/workspace/composites.d.ts.map +1 -1
  346. package/dist/workspace/composites.js +5 -2
  347. package/dist/workspace/composites.schema.json +22 -0
  348. package/dist/workspace/conformance/__writer_fixture__/work/work.schema.json +105 -1
  349. package/dist/workspace/conformance/index.d.ts +1 -1
  350. package/dist/workspace/conformance/index.d.ts.map +1 -1
  351. package/dist/workspace/conformance/index.js +39 -2
  352. package/dist/workspace/cross-root-lint.d.ts +56 -0
  353. package/dist/workspace/cross-root-lint.d.ts.map +1 -0
  354. package/dist/workspace/cross-root-lint.js +111 -0
  355. package/dist/workspace/dbt-models-reader.d.ts +44 -0
  356. package/dist/workspace/dbt-models-reader.d.ts.map +1 -0
  357. package/dist/workspace/dbt-models-reader.js +356 -0
  358. package/dist/workspace/dbt-project-common.d.ts +40 -0
  359. package/dist/workspace/dbt-project-common.d.ts.map +1 -0
  360. package/dist/workspace/dbt-project-common.js +106 -0
  361. package/dist/workspace/dbt-project-reader.d.ts +4 -3
  362. package/dist/workspace/dbt-project-reader.d.ts.map +1 -1
  363. package/dist/workspace/dbt-project-reader.js +14 -108
  364. package/dist/workspace/decision-rank.d.ts +26 -0
  365. package/dist/workspace/decision-rank.d.ts.map +1 -0
  366. package/dist/workspace/decision-rank.js +16 -0
  367. package/dist/workspace/declaration.d.ts +43 -2
  368. package/dist/workspace/declaration.d.ts.map +1 -1
  369. package/dist/workspace/declaration.js +45 -11
  370. package/dist/workspace/declaration.schema.json +133 -24
  371. package/dist/workspace/environments.d.ts +26 -2
  372. package/dist/workspace/environments.d.ts.map +1 -1
  373. package/dist/workspace/environments.js +26 -2
  374. package/dist/workspace/flyway-dir.d.ts +39 -0
  375. package/dist/workspace/flyway-dir.d.ts.map +1 -0
  376. package/dist/workspace/flyway-dir.js +104 -0
  377. package/dist/workspace/flyway-layout.d.ts +96 -0
  378. package/dist/workspace/flyway-layout.d.ts.map +1 -0
  379. package/dist/workspace/flyway-layout.js +308 -0
  380. package/dist/workspace/graph-cache.d.ts +8 -0
  381. package/dist/workspace/graph-cache.d.ts.map +1 -1
  382. package/dist/workspace/graph-cache.js +27 -3
  383. package/dist/workspace/graph-cli.d.ts +39 -2
  384. package/dist/workspace/graph-cli.d.ts.map +1 -1
  385. package/dist/workspace/graph-cli.js +92 -32
  386. package/dist/workspace/graph.schema.json +182 -0
  387. package/dist/workspace/intent.d.ts +2 -3
  388. package/dist/workspace/intent.d.ts.map +1 -1
  389. package/dist/workspace/intent.js +5 -9
  390. package/dist/workspace/kind-link-readers.d.ts +59 -2
  391. package/dist/workspace/kind-link-readers.d.ts.map +1 -1
  392. package/dist/workspace/kind-link-readers.js +17 -1
  393. package/dist/workspace/kind-readers.d.ts +3 -0
  394. package/dist/workspace/kind-readers.d.ts.map +1 -1
  395. package/dist/workspace/kind-readers.js +7 -1
  396. package/dist/workspace/kinds.d.ts +66 -2
  397. package/dist/workspace/kinds.d.ts.map +1 -1
  398. package/dist/workspace/kinds.js +98 -4
  399. package/dist/workspace/ledger.schema.json +158 -0
  400. package/dist/workspace/lexicon-outputs.d.ts +20 -1
  401. package/dist/workspace/lexicon-outputs.d.ts.map +1 -1
  402. package/dist/workspace/lexicon-outputs.js +102 -10
  403. package/dist/workspace/lineage-check.d.ts +3 -0
  404. package/dist/workspace/lineage-check.d.ts.map +1 -1
  405. package/dist/workspace/lineage-check.js +23 -4
  406. package/dist/workspace/links.d.ts +8 -1
  407. package/dist/workspace/links.d.ts.map +1 -1
  408. package/dist/workspace/links.js +25 -1
  409. package/dist/workspace/ls.d.ts +8 -0
  410. package/dist/workspace/ls.d.ts.map +1 -1
  411. package/dist/workspace/ls.js +7 -1
  412. package/dist/workspace/ls.schema.json +7 -0
  413. package/dist/workspace/member-commands.d.ts +10 -1
  414. package/dist/workspace/member-commands.d.ts.map +1 -1
  415. package/dist/workspace/member-commands.js +71 -6
  416. package/dist/workspace/migrate-links.d.ts +45 -0
  417. package/dist/workspace/migrate-links.d.ts.map +1 -0
  418. package/dist/workspace/migrate-links.js +100 -0
  419. package/dist/workspace/migration-dirs.d.ts +74 -0
  420. package/dist/workspace/migration-dirs.d.ts.map +1 -0
  421. package/dist/workspace/migration-dirs.js +254 -0
  422. package/dist/workspace/nested-graph.d.ts +2 -0
  423. package/dist/workspace/nested-graph.d.ts.map +1 -1
  424. package/dist/workspace/nested-graph.js +32 -1
  425. package/dist/workspace/reason-codes.d.ts +18 -0
  426. package/dist/workspace/reason-codes.d.ts.map +1 -1
  427. package/dist/workspace/reason-codes.js +23 -0
  428. package/dist/workspace/records-cli.d.ts +11 -0
  429. package/dist/workspace/records-cli.d.ts.map +1 -1
  430. package/dist/workspace/records-cli.js +57 -5
  431. package/dist/workspace/records.schema.json +5 -0
  432. package/dist/workspace/release-candidates.d.ts +2 -0
  433. package/dist/workspace/release-candidates.d.ts.map +1 -1
  434. package/dist/workspace/release-candidates.js +5 -2
  435. package/dist/workspace/release-train.d.ts +53 -0
  436. package/dist/workspace/release-train.d.ts.map +1 -0
  437. package/dist/workspace/release-train.js +134 -0
  438. package/dist/workspace/release-workflow.d.ts.map +1 -1
  439. package/dist/workspace/release-workflow.js +4 -3
  440. package/dist/workspace/repo-ref.d.ts +45 -0
  441. package/dist/workspace/repo-ref.d.ts.map +1 -0
  442. package/dist/workspace/repo-ref.js +114 -0
  443. package/dist/workspace/run-event.schema.json +2 -2
  444. package/dist/workspace/runs-cli.d.ts +9 -0
  445. package/dist/workspace/runs-cli.d.ts.map +1 -1
  446. package/dist/workspace/runs-cli.js +48 -3
  447. package/dist/workspace/runs.schema.json +7 -0
  448. package/dist/workspace/search-cli.d.ts +204 -0
  449. package/dist/workspace/search-cli.d.ts.map +1 -0
  450. package/dist/workspace/search-cli.js +466 -0
  451. package/dist/workspace/search-contract.d.ts +56 -0
  452. package/dist/workspace/search-contract.d.ts.map +1 -0
  453. package/dist/workspace/search-contract.js +70 -0
  454. package/dist/workspace/search.schema.json +822 -0
  455. package/dist/workspace/status-decisions.d.ts +37 -0
  456. package/dist/workspace/status-decisions.d.ts.map +1 -0
  457. package/dist/workspace/status-decisions.js +147 -0
  458. package/dist/workspace/status-gates.d.ts +9 -0
  459. package/dist/workspace/status-gates.d.ts.map +1 -1
  460. package/dist/workspace/status.d.ts +4 -0
  461. package/dist/workspace/status.d.ts.map +1 -1
  462. package/dist/workspace/status.js +14 -2
  463. package/dist/workspace/status.schema.json +6 -0
  464. package/dist/workspace/work-cli.d.ts.map +1 -1
  465. package/dist/workspace/work-cli.js +5 -3
  466. package/dist/workspace/work-evidence-ops.d.ts +83 -0
  467. package/dist/workspace/work-evidence-ops.d.ts.map +1 -0
  468. package/dist/workspace/work-evidence-ops.js +169 -0
  469. package/dist/workspace/work-evidence.d.ts +12 -2
  470. package/dist/workspace/work-evidence.d.ts.map +1 -1
  471. package/dist/workspace/work-evidence.js +21 -3
  472. package/dist/workspace/work-evidence.schema.json +49 -2
  473. package/dist/workspace/workspace-gates.d.ts +110 -0
  474. package/dist/workspace/workspace-gates.d.ts.map +1 -0
  475. package/dist/workspace/workspace-gates.js +140 -0
  476. package/dist/workspace/workspace-kinds.schema.json +58 -3
  477. package/dist/workspace/workspace-ledger.d.ts +76 -0
  478. package/dist/workspace/workspace-ledger.d.ts.map +1 -0
  479. package/dist/workspace/workspace-ledger.js +91 -0
  480. package/dist/workspace/workspace-run.d.ts +112 -0
  481. package/dist/workspace/workspace-run.d.ts.map +1 -0
  482. package/dist/workspace/workspace-run.js +432 -0
  483. package/package.json +1 -1
  484. package/src/__golden__/wave-loop/op-by-apply-after-fails.json +10 -0
  485. package/src/__golden__/wave-loop/op-by-apply-hold.json +10 -0
  486. package/src/__golden__/wave-loop/op-by-apply-ignored.json +10 -0
  487. package/src/__golden__/wave-loop/op-by-apply-pass.json +10 -0
  488. package/src/__golden__/wave-loop/op-by-apply-skip.json +10 -0
  489. package/src/__golden__/wave-loop/op-cost-member-clash.json +6 -0
  490. package/src/__golden__/wave-loop/op-cost-member.json +13 -0
  491. package/src/__golden__/wave-loop/op-denied-source.json +16 -0
  492. package/src/__golden__/wave-loop/op-denied.json +16 -0
  493. package/src/__golden__/wave-loop/op-discover-rest-short.json +6 -0
  494. package/src/__golden__/wave-loop/op-discover-rest-stops.json +13 -0
  495. package/src/__golden__/wave-loop/op-discover-rest.json +26 -0
  496. package/src/__golden__/wave-loop/op-discover-wide.json +26 -0
  497. package/src/__golden__/wave-loop/op-held-on-again.json +16 -0
  498. package/src/__golden__/wave-loop/op-held-on-before-apply.json +16 -0
  499. package/src/__golden__/wave-loop/op-held-on.json +16 -0
  500. package/src/__golden__/wave-loop/op-hooks-apply-fails.json +13 -0
  501. package/src/__golden__/wave-loop/op-hooks-fail-after-apply.json +13 -0
  502. package/src/__golden__/wave-loop/op-hooks-fail-before-apply.json +13 -0
  503. package/src/__golden__/wave-loop/op-hooks-fail-before-plan.json +13 -0
  504. package/src/__golden__/wave-loop/op-hooks-held.json +13 -0
  505. package/src/__golden__/wave-loop/op-hooks-hold-before-apply.json +13 -0
  506. package/src/__golden__/wave-loop/op-hooks-pass.json +13 -0
  507. package/src/__golden__/wave-loop/op-hooks-refused-at-base.json +13 -0
  508. package/src/__golden__/wave-loop/op-hooks-share.json +33 -0
  509. package/src/__golden__/wave-loop/op-outcome-held.json +10 -0
  510. package/src/__golden__/wave-loop/op-outcome-replan-approved.json +10 -0
  511. package/src/__golden__/wave-loop/op-outcome-replan-forever.json +10 -0
  512. package/src/__golden__/wave-loop/op-outcome-replan.json +10 -0
  513. package/src/__golden__/wave-loop/op-outcome-stood-down.json +10 -0
  514. package/src/__golden__/wave-loop/op-outcome-unreadable.json +10 -0
  515. package/src/__golden__/wave-loop/op-overridden.json +16 -0
  516. package/src/__golden__/wave-loop/op-per-run.json +20 -0
  517. package/src/__golden__/wave-loop/op-point.json +13 -0
  518. package/src/__golden__/wave-loop/op-pr-review.json +10 -0
  519. package/src/__golden__/wave-loop/op-sealed.json +10 -0
  520. package/src/__golden__/wave-loop/op-spend-change-only.json +10 -0
  521. package/src/__golden__/wave-loop/op-spend.json +10 -0
  522. package/src/__golden__/wave-loop/op-wave-commands.json +55 -0
  523. package/src/__golden__/wave-loop/op-wide-refused.json +26 -0
  524. package/src/__golden__/wave-loop/op-wide-waiting.json +29 -0
  525. package/src/__golden__/wave-loop/op-wide.json +39 -0
  526. package/src/__snapshots__/okf.test.ts.snap +10 -0
  527. package/src/action-pins.test.ts +78 -0
  528. package/src/action-pins.ts +92 -0
  529. package/src/capabilities/aws.paths +148 -0
  530. package/src/capabilities/base-mappings.json +122 -0
  531. package/src/capabilities/catalog.ts +230 -0
  532. package/src/capabilities/language.test.ts +191 -0
  533. package/src/capabilities/language.ts +373 -0
  534. package/src/capabilities/matcher.ts +315 -0
  535. package/src/capabilities/view-compose.test.ts +76 -0
  536. package/src/capabilities/view.ts +631 -0
  537. package/src/cli/commands/carve-score-once.test.ts +99 -0
  538. package/src/cli/commands/carve.ts +37 -6
  539. package/src/cli/commands/check-lexicon.test.ts +2 -2
  540. package/src/cli/commands/import-report.ts +71 -0
  541. package/src/cli/commands/import.ts +33 -3
  542. package/src/cli/commands/lint.ts +1 -0
  543. package/src/cli/commands/migrate-dialect.test.ts +145 -0
  544. package/src/cli/commands/migrate.ts +132 -11
  545. package/src/cli/handlers/__snapshots__/build-member.test.ts.snap +4 -4
  546. package/src/cli/handlers/approve.ts +23 -2
  547. package/src/cli/handlers/build.ts +7 -1
  548. package/src/cli/handlers/declared-graph.ts +99 -0
  549. package/src/cli/handlers/graph.test.ts +94 -3
  550. package/src/cli/handlers/graph.ts +123 -96
  551. package/src/cli/handlers/ledger.ts +54 -0
  552. package/src/cli/handlers/migrate.ts +21 -12
  553. package/src/cli/handlers/misc.ts +10 -1
  554. package/src/cli/handlers/pr-env.ts +3 -4
  555. package/src/cli/handlers/pr.ts +138 -36
  556. package/src/cli/handlers/query.ts +16 -4
  557. package/src/cli/handlers/release.ts +6 -3
  558. package/src/cli/handlers/search-build-roots.test.ts +144 -0
  559. package/src/cli/handlers/search-count.test.ts +191 -0
  560. package/src/cli/handlers/search-count.ts +165 -0
  561. package/src/cli/handlers/search-json.test.ts +333 -0
  562. package/src/cli/handlers/search-live.test.ts +387 -0
  563. package/src/cli/handlers/search-paths.test.ts +235 -0
  564. package/src/cli/handlers/search.ts +1291 -166
  565. package/src/cli/main.test.ts +43 -3
  566. package/src/cli/main.ts +88 -17
  567. package/src/cli/mcp/server.test.ts +3 -3
  568. package/src/cli/mcp/server.ts +3 -0
  569. package/src/cli/mcp/tools/capabilities.ts +32 -0
  570. package/src/cli/mcp/workspace-tools.e2e.test.ts +21 -1
  571. package/src/cli/mcp/workspace-tools.ts +47 -2
  572. package/src/cli/parse-args.ts +79 -15
  573. package/src/cli/registry.ts +36 -4
  574. package/src/components/builders.ts +8 -0
  575. package/src/components/capability-plugin-loader.ts +13 -1
  576. package/src/components/capability-plugin.ts +16 -0
  577. package/src/components/pr-env-ports.ts +89 -0
  578. package/src/components/pr-env-run-bundle.test.ts +54 -0
  579. package/src/components/pr-env-run.ts +5 -76
  580. package/src/components/pr-environment.ts +2 -1
  581. package/src/components/pr-hooks.test.ts +73 -5
  582. package/src/components/pr-hooks.ts +95 -5
  583. package/src/components/pr-pipeline.ts +64 -16
  584. package/src/components/pr-policy.test.ts +64 -1
  585. package/src/components/pr-policy.ts +53 -0
  586. package/src/components/registry.test.ts +1 -1
  587. package/src/components/starter-plugin.ts +6 -0
  588. package/src/components/verbs/index.ts +2 -0
  589. package/src/components/verbs/publish-artifact.test.ts +60 -0
  590. package/src/components/verbs/publish-artifact.ts +122 -0
  591. package/src/components/verbs/publish-image.test.ts +101 -0
  592. package/src/components/verbs/publish-image.ts +265 -0
  593. package/src/config-environments.ts +160 -0
  594. package/src/config.ts +44 -124
  595. package/src/gate-decision-bundle.test.ts +1 -0
  596. package/src/gate-decision-identity.test.ts +9 -0
  597. package/src/gate-decision.ts +26 -3
  598. package/src/graph-detail.ts +15 -3
  599. package/src/graph-effective.ts +226 -75
  600. package/src/graph-ir.test.ts +163 -0
  601. package/src/graph-ir.ts +174 -0
  602. package/src/graph-sub-template.ts +22 -0
  603. package/src/graph-walk.test.ts +44 -0
  604. package/src/graph-walk.ts +102 -0
  605. package/src/import/parser.ts +20 -0
  606. package/src/lexicon.ts +84 -6
  607. package/src/lifecycle/gate-ledger.ts +8 -0
  608. package/src/lifecycle/git.ts +2 -2
  609. package/src/lifecycle/ledger-read.test.ts +107 -0
  610. package/src/lifecycle/ledger-read.ts +195 -0
  611. package/src/lifecycle/observe.test.ts +27 -1
  612. package/src/lifecycle/observe.ts +43 -1
  613. package/src/lifecycle/release-workstation.ts +24 -0
  614. package/src/lifecycle/run-ledger.ts +5 -2
  615. package/src/lint/cross-root.test.ts +122 -0
  616. package/src/lint/discover.ts +3 -1
  617. package/src/lint/post-synth.ts +212 -2
  618. package/src/lint/rule.ts +6 -0
  619. package/src/lint/rules/op/index.ts +2 -0
  620. package/src/lint/rules/op/ops019-workspace-op-target.ts +89 -0
  621. package/src/live-endpoint.ts +1 -1
  622. package/src/observation.test.ts +25 -0
  623. package/src/observation.ts +69 -5
  624. package/src/op/activities/activity-contracts.ts +5 -2
  625. package/src/op/activities/index.ts +2 -2
  626. package/src/op/activities/review-agent.test.ts +221 -4
  627. package/src/op/activities/review-agent.ts +265 -22
  628. package/src/op/activities/work-evidence.ts +9 -1
  629. package/src/op/builders.ts +9 -5
  630. package/src/op/composites/apply-op.test.ts +108 -0
  631. package/src/op/composites/apply-op.ts +37 -2
  632. package/src/op/gate-approval.test.ts +6 -1
  633. package/src/op/gate-approval.ts +12 -6
  634. package/src/op/gate-quorum-at-base.test.ts +211 -0
  635. package/src/op/gate-quorum.ts +156 -8
  636. package/src/op/gate-review.ts +14 -0
  637. package/src/op/gate.ts +67 -8
  638. package/src/op/index.ts +3 -2
  639. package/src/op/local-executor.ts +69 -9
  640. package/src/op/op-waves-depends-on.test.ts +81 -0
  641. package/src/op/op-waves-hook-targets.test.ts +4 -2
  642. package/src/op/op-waves-quorum-product.test.ts +158 -0
  643. package/src/op/op-waves-run.ts +288 -14
  644. package/src/op/op-waves.ts +143 -14
  645. package/src/op/runtime.ts +8 -0
  646. package/src/op/schedule-overdue.ts +33 -0
  647. package/src/op/stage-hooks.ts +13 -5
  648. package/src/op/workspace-run-env.ts +29 -0
  649. package/src/pr-forge-bundle.test.ts +2 -2
  650. package/src/pr-forge.test.ts +47 -0
  651. package/src/pr-forge.ts +68 -0
  652. package/src/pr-post-open.e2e.test.ts +36 -0
  653. package/src/pr-post.test.ts +102 -3
  654. package/src/pr-post.ts +89 -1
  655. package/src/read-filter.test.ts +54 -0
  656. package/src/read-filter.ts +193 -0
  657. package/src/run-event.ts +1 -1
  658. package/src/stage-hooks-bundle.test.ts +1 -0
  659. package/src/terraform/carve.ts +52 -15
  660. package/src/terraform/cuts.test.ts +173 -0
  661. package/src/terraform/cuts.ts +188 -0
  662. package/src/terraform/folds.ts +44 -0
  663. package/src/terraform/graph.ts +9 -0
  664. package/src/terraform/radius.test.ts +90 -0
  665. package/src/terraform/reach.test.ts +146 -0
  666. package/src/terraform/reach.ts +247 -0
  667. package/src/terraform/score.ts +27 -29
  668. package/src/terraform/state.ts +3 -1
  669. package/src/terraform/types.ts +8 -0
  670. package/src/wave-blocked.ts +28 -3
  671. package/src/wave-loop-golden.test.ts +4 -3
  672. package/src/workspace/behold-kinds.test.ts +1 -1
  673. package/src/workspace/capabilities-contract.test.ts +88 -0
  674. package/src/workspace/capabilities-contract.ts +132 -0
  675. package/src/workspace/capabilities.schema.json +229 -0
  676. package/src/workspace/check-contract.test.ts +7 -6
  677. package/src/workspace/check.schema.json +11 -2
  678. package/src/workspace/checks/ci-branch.test.ts +128 -0
  679. package/src/workspace/checks/ci-branch.ts +141 -0
  680. package/src/workspace/checks/links.ts +12 -1
  681. package/src/workspace/checks/org.ts +169 -0
  682. package/src/workspace/checks.ts +64 -7
  683. package/src/workspace/ci-branches.ts +31 -0
  684. package/src/workspace/ci-green-branches.test.ts +153 -0
  685. package/src/workspace/ci-green-cli.ts +7 -7
  686. package/src/workspace/ci-green-workflow.test.ts +4 -3
  687. package/src/workspace/ci-green-workflow.ts +13 -9
  688. package/src/workspace/ci-green.ts +49 -12
  689. package/src/workspace/ci-run.test.ts +72 -1
  690. package/src/workspace/ci-run.ts +68 -8
  691. package/src/workspace/ci-serve.test.ts +95 -0
  692. package/src/workspace/ci-serve.ts +106 -65
  693. package/src/workspace/component-environments.test.ts +25 -2
  694. package/src/workspace/compose-graph.ts +19 -3
  695. package/src/workspace/composites.schema.json +22 -0
  696. package/src/workspace/composites.test.ts +39 -8
  697. package/src/workspace/composites.ts +6 -2
  698. package/src/workspace/conformance/__writer_fixture__/work/work.schema.json +105 -1
  699. package/src/workspace/conformance/conformance.e2e.test.ts +14 -1
  700. package/src/workspace/conformance/index.ts +31 -2
  701. package/src/workspace/cross-root-lint.test.ts +90 -0
  702. package/src/workspace/cross-root-lint.ts +126 -0
  703. package/src/workspace/dbt-kind.test.ts +3 -2
  704. package/src/workspace/dbt-models-reader.ts +362 -0
  705. package/src/workspace/dbt-models.test.ts +248 -0
  706. package/src/workspace/dbt-project-common.ts +113 -0
  707. package/src/workspace/dbt-project-reader.ts +29 -112
  708. package/src/workspace/decision-rank.ts +40 -0
  709. package/src/workspace/declaration.schema.json +133 -24
  710. package/src/workspace/declaration.ts +84 -15
  711. package/src/workspace/environments.ts +40 -4
  712. package/src/workspace/flyway-dir.ts +113 -0
  713. package/src/workspace/flyway-layout.ts +337 -0
  714. package/src/workspace/graph-cache.ts +27 -3
  715. package/src/workspace/graph-cli.ts +119 -32
  716. package/src/workspace/graph-contract.test.ts +4 -2
  717. package/src/workspace/graph.schema.json +182 -0
  718. package/src/workspace/identity.test.ts +14 -2
  719. package/src/workspace/intent.ts +6 -14
  720. package/src/workspace/kind-link-readers.ts +68 -3
  721. package/src/workspace/kind-readers.e2e.test.ts +28 -0
  722. package/src/workspace/kind-readers.test.ts +1 -1
  723. package/src/workspace/kind-readers.ts +7 -1
  724. package/src/workspace/kinds.test.ts +12 -7
  725. package/src/workspace/kinds.ts +132 -7
  726. package/src/workspace/ledger.schema.json +158 -0
  727. package/src/workspace/lexicon-outputs.ts +132 -13
  728. package/src/workspace/lineage-check.ts +30 -7
  729. package/src/workspace/links.ts +24 -1
  730. package/src/workspace/ls-contract.test.ts +1 -1
  731. package/src/workspace/ls.schema.json +7 -0
  732. package/src/workspace/ls.ts +10 -1
  733. package/src/workspace/member-commands.ts +82 -3
  734. package/src/workspace/member-file-schema.test.ts +124 -0
  735. package/src/workspace/migrate-links.test.ts +99 -0
  736. package/src/workspace/migrate-links.ts +108 -0
  737. package/src/workspace/migration-dirs.test.ts +272 -0
  738. package/src/workspace/migration-dirs.ts +267 -0
  739. package/src/workspace/nested-graph.ts +32 -1
  740. package/src/workspace/org-scope.test.ts +238 -0
  741. package/src/workspace/reason-codes.test.ts +13 -1
  742. package/src/workspace/reason-codes.ts +23 -0
  743. package/src/workspace/records-cli.ts +58 -5
  744. package/src/workspace/records.schema.json +5 -0
  745. package/src/workspace/release-candidates.ts +7 -2
  746. package/src/workspace/release-train.ts +157 -0
  747. package/src/workspace/release-workflow.ts +4 -3
  748. package/src/workspace/repo-ref.ts +113 -0
  749. package/src/workspace/run-event.schema.json +2 -2
  750. package/src/workspace/runs-cli.ts +48 -4
  751. package/src/workspace/runs.schema.json +7 -0
  752. package/src/workspace/search-cli.e2e.test.ts +305 -0
  753. package/src/workspace/search-cli.ts +591 -0
  754. package/src/workspace/search-contract.ts +95 -0
  755. package/src/workspace/search.schema.json +822 -0
  756. package/src/workspace/status-contract.test.ts +46 -1
  757. package/src/workspace/status-decisions.test.ts +100 -0
  758. package/src/workspace/status-decisions.ts +151 -0
  759. package/src/workspace/status-gates.ts +9 -0
  760. package/src/workspace/status.schema.json +6 -0
  761. package/src/workspace/status.ts +17 -3
  762. package/src/workspace/where-filters.test.ts +39 -0
  763. package/src/workspace/work-cli.ts +5 -3
  764. package/src/workspace/work-evidence-ops.test.ts +176 -0
  765. package/src/workspace/work-evidence-ops.ts +189 -0
  766. package/src/workspace/work-evidence.schema.json +49 -2
  767. package/src/workspace/work-evidence.ts +22 -3
  768. package/src/workspace/workspace-gates.ts +200 -0
  769. package/src/workspace/workspace-kinds.schema.json +58 -3
  770. package/src/workspace/workspace-ledger.ts +130 -0
  771. package/src/workspace/workspace-run.test.ts +322 -0
  772. package/src/workspace/workspace-run.ts +461 -0
@@ -504,7 +504,7 @@ describe("gate approval — a permit counts only under the context it was given
504
504
  describe("gate approval — the request a policy is asked (#2508)", () => {
505
505
  test("carries the approver, the gate and the context, with the plan digest", () => {
506
506
  expect(gatePolicyRequest({
507
- op: "release", gate: "ship", resolvedBy: "release-bot", approver: { kind: "agent", roles: ["deployer"] },
507
+ op: "release", gate: "ship", resolvedBy: "release-bot", approver: { kind: "agent" }, roles: ["deployer"],
508
508
  planDigest: PLAN_A, context: { risk: "low" },
509
509
  })).toEqual({
510
510
  principal: { kind: "agent", name: "release-bot", roles: ["deployer"] },
@@ -513,6 +513,11 @@ describe("gate approval — the request a policy is asked (#2508)", () => {
513
513
  context: { risk: "low", planDigest: PLAN_A },
514
514
  });
515
515
  });
516
+
517
+ test("never carries the roles the approver claims (#4324)", () => {
518
+ const request = gatePolicyRequest({ op: "release", gate: "ship", resolvedBy: "github:sam", approver: { kind: "human", roles: ["maintainer"] } });
519
+ expect(request.principal.roles).toEqual([]);
520
+ });
516
521
  });
517
522
 
518
523
  describe("gate approval — the approval block is checked where it is written (#2508)", () => {
@@ -33,10 +33,10 @@
33
33
  * permit counts only while the run resolves that same context (#3774).
34
34
  *
35
35
  * A resolution is a local fact, not a signed one (`../lifecycle/gate-ledger.ts`).
36
- * Roles on it are what the approver claimed, and since #4290 the quorum
37
- * never reads them: a role counts only when `.chant/trust.json` at base
38
- * grants it and a seal proves the approver (`./gate-quorum.ts`). A policy
39
- * still sees the claimed roles in its request.
36
+ * Roles on it are what the approver claimed, and nothing trusts them: since
37
+ * #4290 the quorum counts a role only when `.chant/trust.json` at base grants
38
+ * it and a seal proves the approver (`./gate-quorum.ts`), and since #4324 a
39
+ * policy's request carries those same roles, never the claimed ones.
40
40
  */
41
41
 
42
42
  import { createHash } from "node:crypto";
@@ -282,17 +282,23 @@ export async function loadGatePolicyEvaluator(lexicon: string): Promise<GatePoli
282
282
  return mod as GatePolicyEvaluator;
283
283
  }
284
284
 
285
- /** The request a policy is asked about one approval. */
285
+ /**
286
+ * The request a policy is asked about one approval. `roles` are the ones
287
+ * `.chant/trust.json` at base gives a proven approver (#4324), as a quorum
288
+ * judge reads them (`trustQuorumJudge` in `./gate-quorum.ts`). The roles the
289
+ * approver claims on `approver` are never read. Absent, the principal has none.
290
+ */
286
291
  export function gatePolicyRequest(input: {
287
292
  op: string;
288
293
  gate: string;
289
294
  resolvedBy: string;
290
295
  approver: GateApprover;
296
+ roles?: readonly string[];
291
297
  planDigest?: string;
292
298
  context?: Record<string, unknown>;
293
299
  }): GatePolicyRequest {
294
300
  return {
295
- principal: { kind: input.approver.kind, name: input.resolvedBy, roles: input.approver.roles ?? [] },
301
+ principal: { kind: input.approver.kind, name: input.resolvedBy, roles: [...(input.roles ?? [])] },
296
302
  action: "PassGate",
297
303
  resource: { op: input.op, gate: input.gate },
298
304
  context: {
@@ -0,0 +1,211 @@
1
+ /**
2
+ * #4324: an Op gate's quorum is `identity.gates.<gate>.quorum` in the
3
+ * workspace declaration at base, so a change can't lower the quorum its own
4
+ * run needs, and a gate policy's `principal.roles` come from
5
+ * `.chant/trust.json` at base for a proven approver, never from `chant
6
+ * approve --role`.
7
+ */
8
+
9
+ import { afterAll, afterEach, beforeAll, describe, expect, test } from "vitest";
10
+ import type { GateStep, OpConfig } from "./types";
11
+ import type { ActivityFn } from "./activity-registry";
12
+ import { resolveGateApproval, runOpLocally } from "./local-executor";
13
+ import { gitGateLedgerPort, memoryGateLedgerPort, type GateLedgerPort } from "./gate";
14
+ import type { GatePolicyEvaluator, GatePolicyRef, GatePolicyRequest } from "./gate-approval";
15
+ import { appendPendingGate, type GateResolutionInput } from "../lifecycle/gate-ledger";
16
+ import { resetGateOrigin } from "../lifecycle/gate-origin";
17
+ import { sealGateApproval } from "../workspace/trust/seal";
18
+ import { hasSshKeygen, TestRepo, type Key } from "../workspace/trust/test-repo";
19
+ import { recordGateApproval } from "../cli/handlers/approve";
20
+
21
+ const repos: TestRepo[] = [];
22
+ const home = process.cwd();
23
+ afterEach(() => {
24
+ process.chdir(home);
25
+ resetGateOrigin();
26
+ while (repos.length) repos.pop()!.cleanup();
27
+ });
28
+
29
+ let keys: TestRepo;
30
+ let alice: Key;
31
+ let bob: Key;
32
+ beforeAll(() => {
33
+ if (!hasSshKeygen) return;
34
+ keys = new TestRepo("4324-keys");
35
+ alice = keys.key("alice");
36
+ bob = keys.key("bob");
37
+ });
38
+ afterAll(() => keys?.cleanup());
39
+
40
+ /** A repository whose `main` is the base, with a git identity the ledger writes can use. */
41
+ function repo(label: string, files: Record<string, string>): TestRepo {
42
+ const r = new TestRepo(label);
43
+ repos.push(r);
44
+ r.git(["config", "user.email", "t@example.test"]);
45
+ r.git(["config", "user.name", "t"]);
46
+ r.git(["config", "commit.gpgsign", "false"]);
47
+ for (const [path, text] of Object.entries(files)) r.write(path, text);
48
+ r.commit("base");
49
+ return r;
50
+ }
51
+
52
+ const declaration = (gates: Record<string, unknown>): string =>
53
+ JSON.stringify({ name: "acme", schema: 1, members: [], identity: { gates } });
54
+
55
+ describe("resolveGateApproval reads the quorum at base (#4324)", () => {
56
+ const step = (approval?: GateStep["approval"]): GateStep => ({ kind: "gate", gate: "ship", ...(approval ? { approval } : {}) }) as GateStep;
57
+
58
+ test("the base quorum replaces the Op's own", () => {
59
+ expect(resolveGateApproval(step({ quorum: { count: 1 } }), new Map(), undefined, { count: 2 })).toEqual({ quorum: { count: 2 }, mode: "log-only" });
60
+ });
61
+
62
+ test("a gate whose Op dropped its approval block still takes the base quorum", () => {
63
+ expect(resolveGateApproval(step(), new Map(), undefined, { count: 2 })).toEqual({ quorum: { count: 2 }, mode: "log-only" });
64
+ });
65
+
66
+ test("a base list is matched against the run's environment", () => {
67
+ const atBase = [{ targets: ["prod"], count: 2 }, { count: 1 }];
68
+ expect(resolveGateApproval(step({ quorum: { count: 1 } }), new Map(), "prod", atBase)?.quorum).toEqual(atBase);
69
+ expect(resolveGateApproval(step({ quorum: { count: 1 } }), new Map(), "dev", atBase)?.quorum).toEqual([{ count: 1 }]);
70
+ });
71
+
72
+ test("with no quorum at base, the Op's own applies", () => {
73
+ expect(resolveGateApproval(step({ quorum: { count: 3 } }), new Map(), undefined, null)).toEqual({ quorum: { count: 3 }, mode: "log-only" });
74
+ expect(resolveGateApproval(step(), new Map(), undefined, null)).toBeUndefined();
75
+ });
76
+ });
77
+
78
+ describe.skipIf(!hasSshKeygen)("an Op gate's quorum at base (#4324)", () => {
79
+ const NOW = "2026-10-23T12:00:00.000Z";
80
+ const config = (count: number): OpConfig => ({
81
+ name: "release",
82
+ overview: "",
83
+ phases: [
84
+ { name: "Gate", steps: [{ kind: "gate", gate: "ship", approval: { quorum: { count } } }] },
85
+ { name: "Apply", steps: [{ kind: "activity", fn: "ship", args: {} }] },
86
+ ],
87
+ });
88
+ const activities = new Map<string, ActivityFn>([["ship", async () => ({})]]);
89
+
90
+ test("a change that lowers its own quorum still needs the quorum at base", async () => {
91
+ const r = repo("4324-quorum", {
92
+ "chant.workspace.json": declaration({ ship: { quorum: { count: 2 } } }),
93
+ ".chant/allowed_signers": `github:alice ${alice.pub}\ngithub:bob ${bob.pub}\n`,
94
+ });
95
+ // The pull request lowers both the declaration's quorum and the Op's.
96
+ r.git(["checkout", "-q", "-b", "lower-quorum"]);
97
+ r.write("chant.workspace.json", declaration({ ship: { quorum: { count: 1 } } }));
98
+ r.commit("lower the quorum");
99
+
100
+ const memory = memoryGateLedgerPort();
101
+ const git = gitGateLedgerPort({ cwd: r.dir });
102
+ const gates: GateLedgerPort = { ...memory, approvalRule: git.approvalRule, quorumJudge: git.quorumJudge };
103
+ const approve = async (by: string, key: Key, timestamp: string) => {
104
+ const line = { op: "release", gate: "ship", resolvedBy: by, timestamp, approver: { kind: "human" as const } };
105
+ const input: GateResolutionInput = { ...line, seal: sealGateApproval(key.file, line) };
106
+ await memory.appendResolution!(input);
107
+ };
108
+
109
+ const first = await runOpLocally(config(1), activities, {}, undefined, { gates, now: NOW });
110
+ expect(first.status).toBe("gated");
111
+ expect(first.gate?.approval?.quorum).toEqual({ count: 2 });
112
+ expect(memory.appended[0]?.approval?.quorum).toEqual({ count: 2 });
113
+
114
+ await approve("github:alice", alice, "2026-10-23T12:01:00.000Z");
115
+ const second = await runOpLocally(config(1), activities, {}, undefined, { gates, now: "2026-10-23T12:02:00.000Z" });
116
+ expect(second.status).toBe("gated");
117
+
118
+ await approve("github:bob", bob, "2026-10-23T12:03:00.000Z");
119
+ const third = await runOpLocally(config(1), activities, {}, undefined, { gates, now: "2026-10-23T12:04:00.000Z" });
120
+ expect(third.status).toBe("ok");
121
+ expect(third.records.find((rec) => rec.fn === "gate:ship")?.approval).toMatchObject({ via: "quorum", approvers: ["github:alice", "github:bob"] });
122
+ });
123
+
124
+ test("a gate the declaration names with no quorum keeps the Op's", async () => {
125
+ const r = repo("4324-no-quorum", {
126
+ "chant.workspace.json": declaration({ ship: {} }),
127
+ ".chant/allowed_signers": `github:alice ${alice.pub}\n`,
128
+ });
129
+ const git = gitGateLedgerPort({ cwd: r.dir });
130
+ const gates: GateLedgerPort = { ...memoryGateLedgerPort(), approvalRule: git.approvalRule };
131
+ const run = await runOpLocally(config(3), activities, {}, undefined, { gates, now: NOW });
132
+ expect(run.gate?.approval?.quorum).toEqual({ count: 3 });
133
+ });
134
+ });
135
+
136
+ describe.skipIf(!hasSshKeygen)("a gate policy's roles come from trust.json at base (#4324)", () => {
137
+ const POLICY: GatePolicyRef = { kind: "gate-policy", lexicon: "cedar", name: "ship", version: "sha256:v1", text: "permit when maintainer" };
138
+ /** Permits only a principal with the maintainer role, and remembers what it was asked. */
139
+ function maintainersOnly(): GatePolicyEvaluator & { asked: GatePolicyRequest[] } {
140
+ const asked: GatePolicyRequest[] = [];
141
+ return {
142
+ asked,
143
+ evaluateGatePolicy: (_policy, request) => {
144
+ asked.push(request);
145
+ return request.principal.roles.includes("maintainer")
146
+ ? { decision: "allow", determining: ["maintainers"], errors: [] }
147
+ : { decision: "deny", determining: [], errors: [] };
148
+ },
149
+ };
150
+ }
151
+
152
+ async function gated(): Promise<TestRepo> {
153
+ const r = repo("4324-policy", {
154
+ "README.md": "x\n",
155
+ ".chant/allowed_signers": `github:alice ${alice.pub}\n`,
156
+ ".chant/trust.json": JSON.stringify({ schema: 1, roles: { maintainer: ["github:alice"] } }),
157
+ });
158
+ await appendPendingGate(
159
+ {
160
+ op: "release",
161
+ gate: "ship",
162
+ timestamp: "2026-10-23T12:00:00.000Z",
163
+ origin: "cli",
164
+ expiresAt: "2999-01-01T00:00:00.000Z",
165
+ approval: { policy: POLICY, mode: "enforce" },
166
+ },
167
+ { cwd: r.dir },
168
+ );
169
+ process.chdir(r.dir);
170
+ return r;
171
+ }
172
+
173
+ test("a claimed --role the trust file does not grant fails a policy that requires it", async () => {
174
+ const r = await gated();
175
+ const evaluator = maintainersOnly();
176
+ const outcome = await recordGateApproval("release", "ship", { origin: "cli", actor: "github:sam", roles: ["maintainer"], evaluator, cwd: r.dir });
177
+ expect(outcome.ok).toBe(true);
178
+ expect(evaluator.asked[0]?.principal).toEqual({ kind: "human", name: "github:sam", roles: [] });
179
+ if (!outcome.ok) return;
180
+ expect(outcome.record.policyDecision?.decision).toBe("deny");
181
+ // The claim is still recorded, as data nothing trusts.
182
+ expect(outcome.record.approver).toEqual({ kind: "human", roles: ["maintainer"] });
183
+ });
184
+
185
+ test("an unsealed approval by a granted principal gets no roles either", async () => {
186
+ const r = await gated();
187
+ const evaluator = maintainersOnly();
188
+ const outcome = await recordGateApproval("release", "ship", { origin: "cli", actor: "github:alice", roles: ["maintainer"], evaluator, cwd: r.dir });
189
+ expect(evaluator.asked[0]?.principal.roles).toEqual([]);
190
+ expect(outcome.ok && outcome.record.policyDecision?.decision).toBe("deny");
191
+ });
192
+
193
+ test("a sealed approval gets the roles trust.json at base grants, and only those", async () => {
194
+ const r = await gated();
195
+ const evaluator = maintainersOnly();
196
+ const outcome = await recordGateApproval("release", "ship", { origin: "cli", actor: "github:alice", roles: ["admin"], sign: alice.file, evaluator, cwd: r.dir });
197
+ expect(evaluator.asked[0]?.principal).toEqual({ kind: "human", name: "github:alice", roles: ["maintainer"] });
198
+ expect(outcome.ok && outcome.record.policyDecision?.decision).toBe("allow");
199
+ });
200
+
201
+ test("a grant the change adds to trust.json does not count until it is at base", async () => {
202
+ const r = await gated();
203
+ r.git(["checkout", "-q", "-b", "grant-sam"]);
204
+ r.write(".chant/allowed_signers", `github:alice ${alice.pub}\ngithub:bob ${bob.pub}\n`);
205
+ r.write(".chant/trust.json", JSON.stringify({ schema: 1, roles: { maintainer: ["github:alice", "github:bob"] } }));
206
+ r.commit("grant bob");
207
+ const evaluator = maintainersOnly();
208
+ await recordGateApproval("release", "ship", { origin: "cli", actor: "github:bob", sign: bob.file, evaluator, cwd: r.dir });
209
+ expect(evaluator.asked[0]?.principal.roles).toEqual([]);
210
+ });
211
+ });
@@ -18,10 +18,16 @@
18
18
  * approver counts toward `roles`, and as someone who is not the author, only
19
19
  * when the approval proves who they are: a seal that verifies against the
20
20
  * signers file at base, or under `pr-review` the forge review of the merged
21
- * pull request. The judge of a gate decides that ({@link GateQuorumJudge});
21
+ * pull request, or a review a product recorded on a ledger the spec at base
22
+ * names (#4333). The judge of a gate decides that ({@link GateQuorumJudge});
22
23
  * with none, no approval proves anything, so a rule naming roles or
23
24
  * `notAuthor` is never met.
24
25
  *
26
+ * `notAuthor` reads who was involved in the change (#4333): the pull
27
+ * request's author, each login a `Requested-by:` trailer on its commits
28
+ * names, and the accounts an agent acts as. With `excludeInvolved`, an
29
+ * approval by any of them does not count at all.
30
+ *
25
31
  * This module is pure: no git, no file reads, no package, so
26
32
  * `@intentius/chant/gate-decision` bundles it. The judge is built by the
27
33
  * caller from the trust policy it read at base ({@link trustQuorumJudge}).
@@ -51,8 +57,14 @@ export interface QuorumJudgement {
51
57
  proven: boolean;
52
58
  /** The roles `.chant/trust.json` at base gives the approver. Empty unless `proven`. */
53
59
  roles: string[];
54
- /** Whether the approver is the change's author. Undefined when nobody knows who the author is. */
60
+ /**
61
+ * Whether the approver was involved in the change: its author, or anyone
62
+ * else {@link TrustQuorumInput.involved} names. Undefined when nobody knows
63
+ * who the author is and the approver is not named as involved otherwise.
64
+ */
55
65
  author?: boolean;
66
+ /** Why the approval does not count at all (#4333): the approver was involved and the judge excludes everyone involved. */
67
+ excluded?: string;
56
68
  }
57
69
 
58
70
  /** Judges one approval for the rules. */
@@ -174,7 +186,7 @@ export function quorumRuleProgress(rule: GateQuorumRule, approvers: readonly Quo
174
186
  const seen = new Set<string>();
175
187
  const counted: QuorumApprover[] = [];
176
188
  for (const a of [...approvers].sort((x, y) => Date.parse(x.at) - Date.parse(y.at))) {
177
- if (seen.has(norm(a.who))) continue;
189
+ if (seen.has(norm(a.who)) || a.judged.excluded !== undefined) continue;
178
190
  if (rule.roles?.length && !(a.judged.proven && a.judged.roles.some((r) => rule.roles!.includes(r)))) continue;
179
191
  seen.add(norm(a.who));
180
192
  counted.push(a);
@@ -216,6 +228,118 @@ export function quorumWaiting(progress: readonly GateQuorumRuleProgress[]): stri
216
228
  /** Whether a rule needs a proven approver: it names roles, or `notAuthor`. */
217
229
  export const ruleNeedsProof = (rule: GateQuorumRule): boolean => (rule.roles?.length ?? 0) > 0 || rule.notAuthor === true;
218
230
 
231
+ /** Someone involved in a change (#4333), and why: `github:bob asked for abc1234`. */
232
+ export interface QuorumInvolvement {
233
+ who: string;
234
+ why: string;
235
+ }
236
+
237
+ /** A login as a forge allows it, and a GitHub App's `[bot]` suffix. */
238
+ const LOGIN = /^[A-Za-z0-9][A-Za-z0-9._-]*(\[bot\])?$/;
239
+
240
+ /**
241
+ * The logins `Requested-by:` trailers on a commit message name, each once.
242
+ * Any line of the message counts, since a squash merge keeps the trailers of
243
+ * the commits it squashed in its body.
244
+ */
245
+ export function requestedByTrailers(message: string): string[] {
246
+ const out: string[] = [];
247
+ for (const line of message.replace(/\r\n/g, "\n").split("\n")) {
248
+ const m = /^requested-by:\s*(\S+)\s*$/i.exec(line.trim());
249
+ if (m && LOGIN.test(m[1]!) && !out.some((o) => samePrincipal(o, m[1]!))) out.push(m[1]!);
250
+ }
251
+ return out;
252
+ }
253
+
254
+ /**
255
+ * The names a principal goes by: itself, and for a forge principal
256
+ * (`github:alice`, `gitlab@gitlab.acme.com:bob`) the login after the colon,
257
+ * so a trailer's bare login and an approval's principal name one person.
258
+ */
259
+ export function principalNames(who: string): string[] {
260
+ const m = /^(?:github|gitlab|forgejo|gitea|codeberg)(?:@[^:\s]+)?:(.+)$/i.exec(who.trim());
261
+ return m ? [who, m[1]!] : [who];
262
+ }
263
+
264
+ /** Why `who` was involved in the change, or undefined when `involved` does not name them. */
265
+ export function involvedWhy(involved: readonly QuorumInvolvement[], who: string): string | undefined {
266
+ const names = principalNames(who);
267
+ return involved.find((p) => principalNames(p.who).some((a) => names.some((b) => samePrincipal(a, b))))?.why;
268
+ }
269
+
270
+ /**
271
+ * Everyone involved in a change (#4333): the pull request's `author`, each
272
+ * login a `Requested-by:` trailer on `commits` names, and the `agents`'
273
+ * accounts. `principal` turns a trailer's login into the principal the
274
+ * forge's approvals name (`github:<login>`); absent, the login stands.
275
+ */
276
+ export function changeInvolvement(o: {
277
+ author?: string;
278
+ commits?: readonly { sha: string; message: string }[];
279
+ agents?: readonly string[];
280
+ principal?: (login: string) => string;
281
+ }): QuorumInvolvement[] {
282
+ const out: QuorumInvolvement[] = [];
283
+ const add = (who: string, why: string): void => {
284
+ if (who.trim() && !out.some((p) => samePrincipal(p.who, who))) out.push({ who, why });
285
+ };
286
+ if (o.author) add(o.author, `${o.author} opened the change`);
287
+ for (const c of o.commits ?? []) {
288
+ for (const login of requestedByTrailers(c.message)) add(o.principal ? o.principal(login) : login, `${login} asked for ${c.sha.slice(0, 7)}`);
289
+ }
290
+ for (const a of o.agents ?? []) add(a, `${a} is an account an agent acts as`);
291
+ return out;
292
+ }
293
+
294
+ /**
295
+ * A review a product recorded on its own ledger (#4333), as terragucci does
296
+ * under `pr-review`: a resolution with `via: "pr-review"`, the reviewed
297
+ * `head`, and the `reviewers` who approved it, by forge login. One line
298
+ * names every reviewer, so `resolvedBy` is not one person.
299
+ */
300
+ export interface RecordedReview {
301
+ pr?: number;
302
+ head: string;
303
+ reviewers: string[];
304
+ }
305
+
306
+ /** The review a ledger line records, when it is a product's review line; null otherwise. */
307
+ export function recordedReviewOf(r: GateResolutionRecord): RecordedReview | null {
308
+ const x = r as unknown as { via?: unknown; head?: unknown; reviewers?: unknown; pr?: unknown };
309
+ if (x.via !== "pr-review" || typeof x.head !== "string" || x.head === "") return null;
310
+ if (!Array.isArray(x.reviewers) || x.reviewers.length === 0 || x.reviewers.some((v) => typeof v !== "string" || v === "")) return null;
311
+ return { ...(typeof x.pr === "number" ? { pr: x.pr } : {}), head: x.head, reviewers: [...(x.reviewers as string[])] };
312
+ }
313
+
314
+ /**
315
+ * The approvals a product's review lines stand for (#4333): each line
316
+ * {@link recordedReviewOf} reads becomes one approval per reviewer, named by
317
+ * `principal(login)` (the login itself when absent), a human's, with the
318
+ * review on it. A seal on the line is dropped: it signs the line, not any
319
+ * one reviewer. Every other line is kept as it is. `reviews` lists the
320
+ * reviews read, for {@link TrustQuorumInput.recorded}.
321
+ */
322
+ export function recordedReviewApprovals(
323
+ records: readonly GateResolutionRecord[],
324
+ principal: (login: string) => string = (login) => login,
325
+ ): { records: GateResolutionRecord[]; reviews: { head: string; approvers: string[] }[] } {
326
+ const out: GateResolutionRecord[] = [];
327
+ const reviews: { head: string; approvers: string[] }[] = [];
328
+ for (const r of records) {
329
+ const review = recordedReviewOf(r);
330
+ if (!review) {
331
+ out.push(r);
332
+ continue;
333
+ }
334
+ const approvers = review.reviewers.map(principal);
335
+ reviews.push({ head: review.head, approvers });
336
+ const { seal: _seal, ...rest } = r;
337
+ const base = Object.fromEntries(Object.entries(rest).filter(([k]) => !["via", "reviewers", "head", "pr", "excluded"].includes(k))) as unknown as GateResolutionRecord;
338
+ for (const who of approvers) out.push({ ...base, resolvedBy: who, approver: { kind: "human" }, review: { pr: review.pr ?? 0, head: review.head, approvers } });
339
+ }
340
+ return { records: out, reviews };
341
+ }
342
+
219
343
  /** What {@link trustQuorumJudge} judges by. */
220
344
  export interface TrustQuorumInput {
221
345
  /** The signers at base: an approval whose seal verifies for its approver against them is proven. */
@@ -224,6 +348,22 @@ export interface TrustQuorumInput {
224
348
  roles: Readonly<Record<string, readonly string[]>>;
225
349
  /** The change's authors, as principals. Absent, nobody knows who they are. */
226
350
  authors?: readonly string[];
351
+ /**
352
+ * Everyone else involved in the change (#4333), from {@link
353
+ * changeInvolvement}: an approver named here is judged as an author, for
354
+ * `notAuthor`, whether or not `authors` is known.
355
+ */
356
+ involved?: readonly QuorumInvolvement[];
357
+ /** An approval by an author or anyone `involved` names does not count at all (#4333), toward any rule. */
358
+ excludeInvolved?: boolean;
359
+ /**
360
+ * Reviews a product recorded on the ledger the spec at base names (#4333),
361
+ * from {@link recordedReviewApprovals}: a resolution that names one's head
362
+ * and one of its approvers as `resolvedBy` is proven, as for `review`. Pass
363
+ * them only under `pr-review`, as the product proves its reviewers only
364
+ * then.
365
+ */
366
+ recorded?: readonly { head: string; approvers: readonly string[] }[];
227
367
  /**
228
368
  * Under `pr-review`, the merged pull request's review, read from the forge
229
369
  * in this run: a resolution that names this review's head and one of its
@@ -232,17 +372,25 @@ export interface TrustQuorumInput {
232
372
  review?: { head: string; approvers: readonly string[] };
233
373
  }
234
374
 
235
- /** The judge a gate's rules are met by: proof from a seal or the live review, roles from trust.json at base. */
375
+ /**
376
+ * The judge a gate's rules are met by: proof from a seal, the live review or
377
+ * a review a product recorded, roles from trust.json at base, and the
378
+ * change's authors and everyone else involved in it left out.
379
+ */
236
380
  export function trustQuorumJudge(input: TrustQuorumInput): GateQuorumJudge {
381
+ const named = (list: readonly string[], who: string) => list.some((a) => samePrincipal(a, who));
237
382
  return (r) => {
238
383
  const sealed = r.seal !== undefined && r.seal !== null && gateSealRefusal(input.signers, r) === null;
239
- const reviewed =
240
- input.review !== undefined && r.review?.head === input.review.head && input.review.approvers.some((a) => samePrincipal(a, r.resolvedBy));
241
- const proven = sealed || reviewed;
384
+ const reviewed = input.review !== undefined && r.review?.head === input.review.head && named(input.review.approvers, r.resolvedBy);
385
+ const recorded = r.review !== undefined && (input.recorded ?? []).some((x) => x.head === r.review!.head && named(x.approvers, r.resolvedBy));
386
+ const proven = sealed || reviewed || recorded;
387
+ const authored = input.authors !== undefined && input.authors.some((a) => samePrincipal(a, r.resolvedBy));
388
+ const why = authored ? `${r.resolvedBy} wrote the change` : involvedWhy(input.involved ?? [], r.resolvedBy);
242
389
  return {
243
390
  proven,
244
391
  roles: proven ? rolesOf(r.resolvedBy, input.roles) : [],
245
- ...(input.authors !== undefined ? { author: input.authors.some((a) => samePrincipal(a, r.resolvedBy)) } : {}),
392
+ ...(why !== undefined ? { author: true } : input.authors !== undefined ? { author: false } : {}),
393
+ ...(why !== undefined && input.excludeInvolved ? { excluded: `${why}, so ${r.resolvedBy}'s approval does not count` } : {}),
246
394
  };
247
395
  };
248
396
  }
@@ -300,6 +300,20 @@ export function reviewSourceFromEnv(env: Record<string, string | undefined> = pr
300
300
  return forgejo ? forgejoReviews(opts) : githubReviews(opts);
301
301
  }
302
302
 
303
+ /**
304
+ * The principal a forge login on the CI job's forge goes by (#4333):
305
+ * `github:<login>` on github.com, `gitlab@<host>:<login>` on another host,
306
+ * as {@link reviewSourceFromEnv}'s reviews name their approvers. A product
307
+ * records its reviewers and its `Requested-by:` trailers by login, and this
308
+ * reads them as the approvals they stand beside. A value that already has a
309
+ * forge's prefix is kept.
310
+ */
311
+ export function forgePrincipalFromEnv(env: Record<string, string | undefined> = process.env): (login: string) => string {
312
+ const kind: ForgeKind = env.GITLAB_CI === "true" ? "gitlab" : env.FORGEJO_ACTIONS === "true" || env.GITEA_ACTIONS === "true" ? "forgejo" : "github";
313
+ const host = kind === "gitlab" ? (env.CI_SERVER_HOST ?? hostOf(env.CI_SERVER_URL, "gitlab.com")) : hostOf(env.GITHUB_SERVER_URL, "github.com");
314
+ return (login) => (/^(?:github|gitlab|forgejo|gitea|codeberg)(?:@[^:\s]+)?:/i.test(login.trim()) ? login : forgePrincipalOf(kind, host, login));
315
+ }
316
+
303
317
  /**
304
318
  * The head of the pull request a CI job runs for, from its environment: the
305
319
  * event payload's `pull_request.head.sha` on GitHub and Forgejo (whose
package/src/op/gate.ts CHANGED
@@ -53,7 +53,7 @@ import { sortedJsonReplacer } from "../utils";
53
53
  import { gateContextDigest, type GateApprover, type ResolvedGateApproval } from "./gate-approval";
54
54
  import {
55
55
  quorumNeed, quorumRuleProgress, quorumRules, ruleNeedsProof, samePrincipal, trustQuorumJudge, UNPROVEN_JUDGE,
56
- type GateQuorumJudge, type GateQuorumRuleProgress,
56
+ type GateQuorumJudge, type GateQuorumRuleProgress, type GateQuorumSpec,
57
57
  } from "./gate-quorum";
58
58
  import { pushLifecycleReport, readBlobBySha, readBlobFromPath, readPathSha, requireLifecycleLedger, writeBlobToPath, RefCASConflictError } from "../lifecycle/git";
59
59
  import { parseDuration } from "./duration";
@@ -128,7 +128,12 @@ export interface GateLedgerPort {
128
128
 
129
129
  /** A workspace's rule for which approvals of a gate count (#3163): a signed approval, from a class when it names one. */
130
130
  export interface GateApprovalRule {
131
- requirement: { gate: string; class: string | null };
131
+ /**
132
+ * `quorum` (#4324) is the entry's `identity.gates.<gate>.quorum` at base:
133
+ * the rules an Op gate of that name takes in place of its own. Absent or
134
+ * null when the entry sets none.
135
+ */
136
+ requirement: { gate: string; class: string | null; quorum?: GateQuorumSpec | null };
132
137
  /** Why `approval` doesn't count, or null when it does. */
133
138
  refuses(approval: GateResolutionRecord): string | null;
134
139
  }
@@ -318,6 +323,12 @@ export interface GateCheckInput {
318
323
  * and the merged pull request's review.
319
324
  */
320
325
  judge?: GateQuorumJudge;
326
+ /**
327
+ * The port's {@link GateLedgerPort.approvalRule} for this gate, when the
328
+ * caller already read it (#4324): the Op executor reads it first, for the
329
+ * quorum at base. Absent, it is read here; null means none applies.
330
+ */
331
+ rule?: GateApprovalRule | null;
321
332
  /** ISO-8601 "now" — supplied by the caller, so the decision is deterministic under test. */
322
333
  now?: string;
323
334
  /**
@@ -371,6 +382,31 @@ export interface GateCheckInput {
371
382
  * no `empty` resolution counts.
372
383
  */
373
384
  verifyEmpty?: (planDigest: string) => Promise<boolean>;
385
+ /**
386
+ * A workspace gate that may stand in for this gate (#2554, #2524 D17,
387
+ * ws-039). Read only on a gate bound to a plan, and only when the gate's
388
+ * own approvals don't pass it. The stand-in decides: it answers an approval
389
+ * only when the member opted in at base and the workspace approval's
390
+ * digest covers this member's plan. Absent, a workspace gate never
391
+ * satisfies this gate.
392
+ */
393
+ workspace?: WorkspaceGateStandIn;
394
+ }
395
+
396
+ /** What a workspace gate approval that stands in for a member gate names (#2554). */
397
+ export interface WorkspaceGateStood {
398
+ /** The workspace gate's name. */
399
+ gate: string;
400
+ /** The digest of the member plans the approval covers. */
401
+ digest: string;
402
+ approvedBy: string;
403
+ /** When the workspace approval was recorded. */
404
+ timestamp: string;
405
+ }
406
+
407
+ /** See {@link GateCheckInput.workspace}. */
408
+ export interface WorkspaceGateStandIn {
409
+ standIn(q: { op: string; gate: string; planDigest: string; environment?: string }): Promise<WorkspaceGateStood | null>;
374
410
  }
375
411
 
376
412
  /**
@@ -441,9 +477,10 @@ export type GateCheck =
441
477
  * under one of the gate's aliases counted, on any gate. `"empty"`
442
478
  * (#4045) when a release gate passed over an empty change set, on the
443
479
  * `empty: true` resolution the promote wrote, after the stored
444
- * document was read back and found empty.
480
+ * document was read back and found empty. `"workspace"` (#2554) when a
481
+ * workspace gate's approval stood in for it ({@link GateCheckInput.workspace}).
445
482
  */
446
- via?: "quorum" | "policy" | "alias" | "empty";
483
+ via?: "quorum" | "policy" | "alias" | "empty" | "workspace";
447
484
  /** Every approval that counted toward the quorum. Set with `via`. */
448
485
  approvals?: GateResolutionRecord[];
449
486
  /** The approvals this run carried from an alias and wrote under the gate's name (#3949). Set when `via` is `"alias"`. */
@@ -774,7 +811,7 @@ export async function evaluateGate(port: GateLedgerPort, input: GateCheckInput):
774
811
  // newest one since the standing fact comes back as a mismatch instead.
775
812
  const forOtherRelease = (r: GateResolutionRecord) => r.gate === input.gate && r.releaseDigest !== input.releaseDigest;
776
813
  // #3163: a gate the workspace's identity.gates names counts only signed approvals from its class.
777
- const rule = (await port.approvalRule?.(input.gate)) ?? null;
814
+ const rule = input.rule !== undefined ? input.rule : ((await port.approvalRule?.(input.gate)) ?? null);
778
815
  const standing = latestPendingGate(pending, input.gate);
779
816
  const since = standing?.timestamp ?? EPOCH;
780
817
 
@@ -845,9 +882,8 @@ export async function evaluateGate(port: GateLedgerPort, input: GateCheckInput):
845
882
  ? humanQuorumSince(pending, standing)
846
883
  : since;
847
884
  // #4290: who an approval proves, and their roles at base, for rules that read them.
848
- const judge = quorumRules(input.approval.quorum).some(ruleNeedsProof)
849
- ? (input.judge ?? (await port.quorumJudge?.(input.gate)) ?? UNPROVEN_JUDGE)
850
- : UNPROVEN_JUDGE;
885
+ // #4333: a caller's judge applies to every rule, since it may leave out everyone involved in the change.
886
+ const judge = input.judge ?? (quorumRules(input.approval.quorum).some(ruleNeedsProof) ? ((await port.quorumJudge?.(input.gate)) ?? UNPROVEN_JUDGE) : UNPROVEN_JUDGE);
851
887
  const tally = tallyGateApprovals(resolutions, input.gate, since, input.planDigest, input.approval, humansSince, judge);
852
888
  if (tally.permit) {
853
889
  return { satisfied: true, resolution: tally.permit, via: "policy", approvals: tally.counted };
@@ -887,6 +923,29 @@ export async function evaluateGate(port: GateLedgerPort, input: GateCheckInput):
887
923
  mismatched = found.mismatched;
888
924
  }
889
925
 
926
+ // #2554, ws-039: a workspace gate stands in only when the member opted in
927
+ // at base and the workspace approval's digest covers this plan.
928
+ if (input.workspace && input.planDigest !== undefined) {
929
+ const stood = await input.workspace.standIn({ op: input.op, gate: input.gate, planDigest: input.planDigest, ...(env !== undefined ? { environment: env } : {}) });
930
+ if (stood) {
931
+ return {
932
+ satisfied: true,
933
+ via: "workspace",
934
+ resolution: {
935
+ version: 1,
936
+ kind: "resolution",
937
+ op: input.op,
938
+ gate: input.gate,
939
+ resolvedBy: stood.approvedBy,
940
+ timestamp: stood.timestamp,
941
+ planDigest: input.planDigest,
942
+ ...(env !== undefined ? { environment: env } : {}),
943
+ workspaceGate: { gate: stood.gate, digest: stood.digest },
944
+ },
945
+ };
946
+ }
947
+ }
948
+
890
949
  // #4045: an approval of this gate for another release stands as a mismatch
891
950
  // when nothing closer does, so the refusal says an approval exists.
892
951
  if (!mismatched && input.planDigest !== undefined) {
package/src/op/index.ts CHANGED
@@ -69,7 +69,7 @@ export {
69
69
  opWaveRecordPath,
70
70
  opWaveShare,
71
71
  } from "./op-waves";
72
- export type { OpWave, OpWaveApplyHooksRunner, OpWaveGatePolicy, OpWaveHook, OpWaveHookFailure, OpWaveHookMoment, OpWaveHooks, OpWaveJob, OpWaveRun, OpWavesDigest, OpWavesOverride, OpWavesSpec } from "./op-waves";
72
+ export type { OpWave, OpWaveApplyHooksRunner, OpWaveGatePolicy, OpWaveHook, OpWaveHookFailure, OpWaveHookMoment, OpWaveHooks, OpWaveJob, OpWaveRun, OpWavesApprovals, OpWavesDigest, OpWavesInvolved, OpWavesOverride, OpWavesSpec } from "./op-waves";
73
73
  export {
74
74
  OP_WAVE_APPLY_OUTCOMES,
75
75
  OP_WAVE_MAX_REPLANS,
@@ -142,9 +142,10 @@ export type {
142
142
  } from "./gate-approval";
143
143
  export {
144
144
  quorumRules, quorumRulesFor, quorumNeed, quorumProblems, quorumRuleProgress, quorumRuleLine, quorumWaiting, rolesOf, trustQuorumJudge, UNPROVEN_JUDGE,
145
+ changeInvolvement, involvedWhy, principalNames, recordedReviewApprovals, recordedReviewOf, requestedByTrailers,
145
146
  } from "./gate-quorum";
146
147
  export type {
147
- GateQuorumRule, GateQuorumSpec, GateQuorumJudge, GateQuorumRuleProgress, QuorumJudgement, QuorumApprover, TrustQuorumInput,
148
+ GateQuorumRule, GateQuorumSpec, GateQuorumJudge, GateQuorumRuleProgress, QuorumJudgement, QuorumApprover, TrustQuorumInput, QuorumInvolvement, RecordedReview,
148
149
  } from "./gate-quorum";
149
150
  export {
150
151
  gatePlanSummary, gatePlanSummaryOfResult, changeSetOfResult, GATE_PLAN_CONTEXT_KEY, GATE_PLAN_ADDRESS_LIMIT,