@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.
- package/dist/__golden__/wave-loop/op-by-apply-after-fails.json +10 -0
- package/dist/__golden__/wave-loop/op-by-apply-hold.json +10 -0
- package/dist/__golden__/wave-loop/op-by-apply-ignored.json +10 -0
- package/dist/__golden__/wave-loop/op-by-apply-pass.json +10 -0
- package/dist/__golden__/wave-loop/op-by-apply-skip.json +10 -0
- package/dist/__golden__/wave-loop/op-cost-member-clash.json +6 -0
- package/dist/__golden__/wave-loop/op-cost-member.json +13 -0
- package/dist/__golden__/wave-loop/op-denied-source.json +16 -0
- package/dist/__golden__/wave-loop/op-denied.json +16 -0
- package/dist/__golden__/wave-loop/op-discover-rest-short.json +6 -0
- package/dist/__golden__/wave-loop/op-discover-rest-stops.json +13 -0
- package/dist/__golden__/wave-loop/op-discover-rest.json +26 -0
- package/dist/__golden__/wave-loop/op-discover-wide.json +26 -0
- package/dist/__golden__/wave-loop/op-held-on-again.json +16 -0
- package/dist/__golden__/wave-loop/op-held-on-before-apply.json +16 -0
- package/dist/__golden__/wave-loop/op-held-on.json +16 -0
- package/dist/__golden__/wave-loop/op-hooks-apply-fails.json +13 -0
- package/dist/__golden__/wave-loop/op-hooks-fail-after-apply.json +13 -0
- package/dist/__golden__/wave-loop/op-hooks-fail-before-apply.json +13 -0
- package/dist/__golden__/wave-loop/op-hooks-fail-before-plan.json +13 -0
- package/dist/__golden__/wave-loop/op-hooks-held.json +13 -0
- package/dist/__golden__/wave-loop/op-hooks-hold-before-apply.json +13 -0
- package/dist/__golden__/wave-loop/op-hooks-pass.json +13 -0
- package/dist/__golden__/wave-loop/op-hooks-refused-at-base.json +13 -0
- package/dist/__golden__/wave-loop/op-hooks-share.json +33 -0
- package/dist/__golden__/wave-loop/op-outcome-held.json +10 -0
- package/dist/__golden__/wave-loop/op-outcome-replan-approved.json +10 -0
- package/dist/__golden__/wave-loop/op-outcome-replan-forever.json +10 -0
- package/dist/__golden__/wave-loop/op-outcome-replan.json +10 -0
- package/dist/__golden__/wave-loop/op-outcome-stood-down.json +10 -0
- package/dist/__golden__/wave-loop/op-outcome-unreadable.json +10 -0
- package/dist/__golden__/wave-loop/op-overridden.json +16 -0
- package/dist/__golden__/wave-loop/op-per-run.json +20 -0
- package/dist/__golden__/wave-loop/op-point.json +13 -0
- package/dist/__golden__/wave-loop/op-pr-review.json +10 -0
- package/dist/__golden__/wave-loop/op-sealed.json +10 -0
- package/dist/__golden__/wave-loop/op-spend-change-only.json +10 -0
- package/dist/__golden__/wave-loop/op-spend.json +10 -0
- package/dist/__golden__/wave-loop/op-wave-commands.json +55 -0
- package/dist/__golden__/wave-loop/op-wide-refused.json +26 -0
- package/dist/__golden__/wave-loop/op-wide-waiting.json +29 -0
- package/dist/__golden__/wave-loop/op-wide.json +39 -0
- package/dist/action-pins.d.ts +61 -0
- package/dist/action-pins.d.ts.map +1 -0
- package/dist/action-pins.js +77 -0
- package/dist/cli/commands/carve.d.ts +3 -0
- package/dist/cli/commands/carve.d.ts.map +1 -1
- package/dist/cli/commands/carve.js +36 -7
- package/dist/cli/commands/import-report.d.ts +26 -0
- package/dist/cli/commands/import-report.d.ts.map +1 -0
- package/dist/cli/commands/import-report.js +65 -0
- package/dist/cli/commands/import.d.ts +11 -1
- package/dist/cli/commands/import.d.ts.map +1 -1
- package/dist/cli/commands/import.js +22 -3
- package/dist/cli/commands/migrate.d.ts +13 -1
- package/dist/cli/commands/migrate.d.ts.map +1 -1
- package/dist/cli/commands/migrate.js +134 -10
- package/dist/cli/handlers/approve.d.ts.map +1 -1
- package/dist/cli/handlers/approve.js +41 -14
- package/dist/cli/handlers/build.d.ts.map +1 -1
- package/dist/cli/handlers/build.js +6 -1
- package/dist/cli/handlers/graph-reader.d.ts +26 -0
- package/dist/cli/handlers/graph-reader.d.ts.map +1 -0
- package/dist/cli/handlers/graph-reader.js +41 -0
- package/dist/cli/handlers/graph.d.ts.map +1 -1
- package/dist/cli/handlers/graph.js +14 -0
- package/dist/cli/handlers/ledger.d.ts +3 -0
- package/dist/cli/handlers/ledger.d.ts.map +1 -0
- package/dist/cli/handlers/ledger.js +55 -0
- package/dist/cli/handlers/migrate.d.ts.map +1 -1
- package/dist/cli/handlers/migrate.js +18 -11
- package/dist/cli/handlers/misc.d.ts.map +1 -1
- package/dist/cli/handlers/misc.js +12 -1
- package/dist/cli/handlers/pr-env.js +2 -2
- package/dist/cli/handlers/pr.d.ts +15 -1
- package/dist/cli/handlers/pr.d.ts.map +1 -1
- package/dist/cli/handlers/pr.js +183 -40
- package/dist/cli/handlers/query.d.ts +7 -2
- package/dist/cli/handlers/query.d.ts.map +1 -1
- package/dist/cli/handlers/query.js +16 -4
- package/dist/cli/handlers/release.d.ts.map +1 -1
- package/dist/cli/handlers/release.js +6 -3
- package/dist/cli/handlers/search.d.ts +2 -4
- package/dist/cli/handlers/search.d.ts.map +1 -1
- package/dist/cli/handlers/search.js +13 -39
- package/dist/cli/main.d.ts.map +1 -1
- package/dist/cli/main.js +87 -14
- package/dist/cli/parse-args.d.ts.map +1 -1
- package/dist/cli/parse-args.js +47 -2
- package/dist/cli/registry.d.ts +22 -4
- package/dist/cli/registry.d.ts.map +1 -1
- package/dist/components/builders.d.ts +5 -1
- package/dist/components/builders.d.ts.map +1 -1
- package/dist/components/builders.js +5 -0
- package/dist/components/capability-plugin-loader.d.ts.map +1 -1
- package/dist/components/capability-plugin-loader.js +16 -1
- package/dist/components/capability-plugin.d.ts +14 -0
- package/dist/components/capability-plugin.d.ts.map +1 -1
- package/dist/components/pr-hooks.d.ts +21 -1
- package/dist/components/pr-hooks.d.ts.map +1 -1
- package/dist/components/pr-hooks.js +72 -4
- package/dist/components/pr-pipeline.d.ts +45 -10
- package/dist/components/pr-pipeline.d.ts.map +1 -1
- package/dist/components/pr-pipeline.js +47 -14
- package/dist/components/pr-policy.d.ts +36 -0
- package/dist/components/pr-policy.d.ts.map +1 -1
- package/dist/components/pr-policy.js +36 -0
- package/dist/components/pr-run.d.ts +3 -0
- package/dist/components/pr-run.d.ts.map +1 -1
- package/dist/components/pr-run.js +1 -0
- package/dist/components/starter-plugin.d.ts +1 -0
- package/dist/components/starter-plugin.d.ts.map +1 -1
- package/dist/components/starter-plugin.js +5 -1
- package/dist/components/verbs/index.d.ts +2 -0
- package/dist/components/verbs/index.d.ts.map +1 -1
- package/dist/components/verbs/index.js +2 -0
- package/dist/components/verbs/publish-artifact.d.ts +65 -0
- package/dist/components/verbs/publish-artifact.d.ts.map +1 -0
- package/dist/components/verbs/publish-artifact.js +74 -0
- package/dist/components/verbs/publish-image.d.ts +129 -0
- package/dist/components/verbs/publish-image.d.ts.map +1 -0
- package/dist/components/verbs/publish-image.js +145 -0
- package/dist/config-environments.d.ts +20 -0
- package/dist/config-environments.d.ts.map +1 -1
- package/dist/config-environments.js +12 -0
- package/dist/config.d.ts +25 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +10 -2
- package/dist/gate-decision.d.ts +20 -2
- package/dist/gate-decision.d.ts.map +1 -1
- package/dist/gate-decision.js +30 -3
- package/dist/graph-ir.d.ts +25 -0
- package/dist/graph-ir.d.ts.map +1 -1
- package/dist/graph-walk.d.ts +66 -0
- package/dist/graph-walk.d.ts.map +1 -0
- package/dist/graph-walk.js +70 -0
- package/dist/import/parser.d.ts +19 -0
- package/dist/import/parser.d.ts.map +1 -1
- package/dist/lexicon.d.ts +94 -6
- package/dist/lexicon.d.ts.map +1 -1
- package/dist/lifecycle/gate-ledger.d.ts +11 -0
- package/dist/lifecycle/gate-ledger.d.ts.map +1 -1
- package/dist/lifecycle/git.d.ts +2 -5
- package/dist/lifecycle/git.d.ts.map +1 -1
- package/dist/lifecycle/ledger-read.d.ts +65 -0
- package/dist/lifecycle/ledger-read.d.ts.map +1 -0
- package/dist/lifecycle/ledger-read.js +160 -0
- package/dist/lifecycle/release-workstation.d.ts +7 -0
- package/dist/lifecycle/release-workstation.d.ts.map +1 -1
- package/dist/lifecycle/release-workstation.js +25 -0
- package/dist/lifecycle/run-ledger.d.ts +4 -3
- package/dist/lifecycle/run-ledger.d.ts.map +1 -1
- package/dist/lifecycle/run-ledger.js +1 -0
- package/dist/lint/discover.js +3 -1
- package/dist/lint/post-synth.d.ts +127 -1
- package/dist/lint/post-synth.d.ts.map +1 -1
- package/dist/lint/post-synth.js +84 -2
- package/dist/lint/rules/op/index.d.ts +1 -0
- package/dist/lint/rules/op/index.d.ts.map +1 -1
- package/dist/lint/rules/op/index.js +2 -0
- package/dist/lint/rules/op/ops019-workspace-op-target.d.ts +32 -0
- package/dist/lint/rules/op/ops019-workspace-op-target.d.ts.map +1 -0
- package/dist/lint/rules/op/ops019-workspace-op-target.js +63 -0
- package/dist/op/activities/activity-contracts.d.ts +9 -6
- package/dist/op/activities/activity-contracts.d.ts.map +1 -1
- package/dist/op/activities/activity-contracts.js +5 -2
- package/dist/op/activities/index.d.ts +2 -2
- package/dist/op/activities/index.d.ts.map +1 -1
- package/dist/op/activities/index.js +1 -1
- package/dist/op/activities/review-agent.d.ts +90 -4
- package/dist/op/activities/review-agent.d.ts.map +1 -1
- package/dist/op/activities/review-agent.js +217 -19
- package/dist/op/activities/work-evidence.d.ts +8 -1
- package/dist/op/activities/work-evidence.d.ts.map +1 -1
- package/dist/op/activities/work-evidence.js +1 -0
- package/dist/op/builders.d.ts +9 -5
- package/dist/op/builders.d.ts.map +1 -1
- package/dist/op/builders.js +9 -5
- package/dist/op/composites/apply-op.d.ts +29 -2
- package/dist/op/composites/apply-op.d.ts.map +1 -1
- package/dist/op/composites/apply-op.js +20 -1
- package/dist/op/gate-approval-base.d.ts +25 -0
- package/dist/op/gate-approval-base.d.ts.map +1 -0
- package/dist/op/gate-approval-base.js +71 -0
- package/dist/op/gate-approval.d.ts +18 -9
- package/dist/op/gate-approval.d.ts.map +1 -1
- package/dist/op/gate-approval.js +13 -31
- package/dist/op/gate-name.d.ts +13 -0
- package/dist/op/gate-name.d.ts.map +1 -1
- package/dist/op/gate-name.js +14 -1
- package/dist/op/gate-quorum.d.ts +100 -3
- package/dist/op/gate-quorum.d.ts.map +1 -1
- package/dist/op/gate-quorum.js +108 -6
- package/dist/op/gate-review.d.ts +9 -0
- package/dist/op/gate-review.d.ts.map +1 -1
- package/dist/op/gate-review.js +13 -0
- package/dist/op/gate.d.ts +47 -5
- package/dist/op/gate.d.ts.map +1 -1
- package/dist/op/gate.js +25 -4
- package/dist/op/index.d.ts +3 -3
- package/dist/op/index.d.ts.map +1 -1
- package/dist/op/index.js +1 -1
- package/dist/op/local-executor.d.ts +45 -3
- package/dist/op/local-executor.d.ts.map +1 -1
- package/dist/op/local-executor.js +68 -7
- package/dist/op/op-waves-run.d.ts +26 -1
- package/dist/op/op-waves-run.d.ts.map +1 -1
- package/dist/op/op-waves-run.js +259 -13
- package/dist/op/op-waves.d.ts +72 -5
- package/dist/op/op-waves.d.ts.map +1 -1
- package/dist/op/op-waves.js +110 -15
- package/dist/op/runtime.d.ts +11 -0
- package/dist/op/runtime.d.ts.map +1 -1
- package/dist/op/schedule-overdue.d.ts +16 -0
- package/dist/op/schedule-overdue.d.ts.map +1 -1
- package/dist/op/schedule-overdue.js +36 -0
- package/dist/op/stage-hooks.d.ts +9 -2
- package/dist/op/stage-hooks.d.ts.map +1 -1
- package/dist/op/stage-hooks.js +7 -5
- package/dist/op/workspace-run-env.d.ts +22 -0
- package/dist/op/workspace-run-env.d.ts.map +1 -0
- package/dist/op/workspace-run-env.js +21 -0
- package/dist/pr-forge.d.ts +24 -0
- package/dist/pr-forge.d.ts.map +1 -1
- package/dist/pr-forge.js +47 -0
- package/dist/pr-loop.d.ts +10 -0
- package/dist/pr-loop.d.ts.map +1 -1
- package/dist/pr-loop.js +4 -0
- package/dist/pr-migrations.d.ts +62 -0
- package/dist/pr-migrations.d.ts.map +1 -0
- package/dist/pr-migrations.js +50 -0
- package/dist/pr-post.d.ts +42 -0
- package/dist/pr-post.d.ts.map +1 -1
- package/dist/pr-post.js +68 -2
- package/dist/read-filter.d.ts +65 -0
- package/dist/read-filter.d.ts.map +1 -0
- package/dist/read-filter.js +179 -0
- package/dist/run-event.d.ts +1 -1
- package/dist/run-event.d.ts.map +1 -1
- package/dist/terraform/carve.d.ts +24 -3
- package/dist/terraform/carve.d.ts.map +1 -1
- package/dist/terraform/carve.js +32 -14
- package/dist/terraform/cuts.d.ts +97 -0
- package/dist/terraform/cuts.d.ts.map +1 -0
- package/dist/terraform/cuts.js +138 -0
- package/dist/terraform/folds.d.ts +26 -0
- package/dist/terraform/folds.d.ts.map +1 -0
- package/dist/terraform/folds.js +38 -0
- package/dist/terraform/graph.d.ts.map +1 -1
- package/dist/terraform/graph.js +10 -0
- package/dist/terraform/reach.d.ts +140 -0
- package/dist/terraform/reach.d.ts.map +1 -0
- package/dist/terraform/reach.js +170 -0
- package/dist/terraform/score.d.ts +14 -0
- package/dist/terraform/score.d.ts.map +1 -1
- package/dist/terraform/score.js +14 -31
- package/dist/terraform/state.d.ts.map +1 -1
- package/dist/terraform/state.js +4 -2
- package/dist/terraform/types.d.ts +8 -0
- package/dist/terraform/types.d.ts.map +1 -1
- package/dist/wave-blocked.d.ts +10 -1
- package/dist/wave-blocked.d.ts.map +1 -1
- package/dist/wave-blocked.js +31 -3
- package/dist/wave-runtime.mjs +264 -254
- package/dist/workspace/check.schema.json +13 -2
- package/dist/workspace/checks/ci-branch.d.ts +44 -0
- package/dist/workspace/checks/ci-branch.d.ts.map +1 -0
- package/dist/workspace/checks/ci-branch.js +133 -0
- package/dist/workspace/checks/gate-approval.d.ts +58 -0
- package/dist/workspace/checks/gate-approval.d.ts.map +1 -0
- package/dist/workspace/checks/gate-approval.js +103 -0
- package/dist/workspace/checks/links.d.ts +3 -0
- package/dist/workspace/checks/links.d.ts.map +1 -1
- package/dist/workspace/checks/links.js +12 -1
- package/dist/workspace/checks/org.d.ts +48 -0
- package/dist/workspace/checks/org.d.ts.map +1 -0
- package/dist/workspace/checks/org.js +156 -0
- package/dist/workspace/checks.d.ts +12 -1
- package/dist/workspace/checks.d.ts.map +1 -1
- package/dist/workspace/checks.js +63 -7
- package/dist/workspace/ci-branches.d.ts +16 -0
- package/dist/workspace/ci-branches.d.ts.map +1 -1
- package/dist/workspace/ci-branches.js +27 -0
- package/dist/workspace/ci-green-cli.js +7 -7
- package/dist/workspace/ci-green-workflow.d.ts +1 -1
- package/dist/workspace/ci-green-workflow.d.ts.map +1 -1
- package/dist/workspace/ci-green-workflow.js +13 -8
- package/dist/workspace/ci-green.d.ts +14 -0
- package/dist/workspace/ci-green.d.ts.map +1 -1
- package/dist/workspace/ci-green.js +44 -11
- package/dist/workspace/ci-run.d.ts +9 -1
- package/dist/workspace/ci-run.d.ts.map +1 -1
- package/dist/workspace/ci-run.js +67 -6
- package/dist/workspace/ci-serve.d.ts +9 -0
- package/dist/workspace/ci-serve.d.ts.map +1 -1
- package/dist/workspace/ci-serve.js +114 -76
- package/dist/workspace/compose-graph.d.ts +14 -0
- package/dist/workspace/compose-graph.d.ts.map +1 -1
- package/dist/workspace/composites.d.ts +4 -1
- package/dist/workspace/composites.d.ts.map +1 -1
- package/dist/workspace/composites.js +5 -2
- package/dist/workspace/composites.schema.json +22 -0
- package/dist/workspace/conformance/__writer_fixture__/work/work.schema.json +105 -1
- package/dist/workspace/cross-root-lint.d.ts +56 -0
- package/dist/workspace/cross-root-lint.d.ts.map +1 -0
- package/dist/workspace/cross-root-lint.js +111 -0
- package/dist/workspace/dbt-models-reader.d.ts +44 -0
- package/dist/workspace/dbt-models-reader.d.ts.map +1 -0
- package/dist/workspace/dbt-models-reader.js +356 -0
- package/dist/workspace/dbt-project-common.d.ts +40 -0
- package/dist/workspace/dbt-project-common.d.ts.map +1 -0
- package/dist/workspace/dbt-project-common.js +106 -0
- package/dist/workspace/dbt-project-reader.d.ts +4 -3
- package/dist/workspace/dbt-project-reader.d.ts.map +1 -1
- package/dist/workspace/dbt-project-reader.js +14 -108
- package/dist/workspace/decision-rank.d.ts +26 -0
- package/dist/workspace/decision-rank.d.ts.map +1 -0
- package/dist/workspace/decision-rank.js +16 -0
- package/dist/workspace/declaration.d.ts +50 -2
- package/dist/workspace/declaration.d.ts.map +1 -1
- package/dist/workspace/declaration.js +57 -11
- package/dist/workspace/declaration.schema.json +166 -24
- package/dist/workspace/environments.d.ts +26 -2
- package/dist/workspace/environments.d.ts.map +1 -1
- package/dist/workspace/environments.js +26 -2
- package/dist/workspace/exit-cli.d.ts +44 -0
- package/dist/workspace/exit-cli.d.ts.map +1 -0
- package/dist/workspace/exit-cli.js +198 -0
- package/dist/workspace/exit.d.ts +159 -0
- package/dist/workspace/exit.d.ts.map +1 -0
- package/dist/workspace/exit.js +480 -0
- package/dist/workspace/exit.schema.json +257 -0
- package/dist/workspace/flyway-dir.d.ts +39 -0
- package/dist/workspace/flyway-dir.d.ts.map +1 -0
- package/dist/workspace/flyway-dir.js +104 -0
- package/dist/workspace/flyway-layout.d.ts +96 -0
- package/dist/workspace/flyway-layout.d.ts.map +1 -0
- package/dist/workspace/flyway-layout.js +308 -0
- package/dist/workspace/graph-cache.d.ts +3 -1
- package/dist/workspace/graph-cache.d.ts.map +1 -1
- package/dist/workspace/graph-cache.js +12 -1
- package/dist/workspace/graph-cli.d.ts.map +1 -1
- package/dist/workspace/graph-cli.js +26 -8
- package/dist/workspace/graph.schema.json +34 -0
- package/dist/workspace/import-fetch.d.ts +38 -0
- package/dist/workspace/import-fetch.d.ts.map +1 -0
- package/dist/workspace/import-fetch.js +99 -0
- package/dist/workspace/import-init.d.ts +26 -0
- package/dist/workspace/import-init.d.ts.map +1 -0
- package/dist/workspace/import-init.js +130 -0
- package/dist/workspace/import-source.d.ts +147 -0
- package/dist/workspace/import-source.d.ts.map +1 -0
- package/dist/workspace/import-source.js +370 -0
- package/dist/workspace/init.d.ts +4 -0
- package/dist/workspace/init.d.ts.map +1 -1
- package/dist/workspace/init.js +70 -3
- package/dist/workspace/intent.d.ts +2 -3
- package/dist/workspace/intent.d.ts.map +1 -1
- package/dist/workspace/intent.js +5 -9
- package/dist/workspace/kind-link-readers.d.ts +59 -2
- package/dist/workspace/kind-link-readers.d.ts.map +1 -1
- package/dist/workspace/kind-link-readers.js +17 -1
- package/dist/workspace/kind-readers.d.ts +6 -0
- package/dist/workspace/kind-readers.d.ts.map +1 -1
- package/dist/workspace/kind-readers.js +18 -0
- package/dist/workspace/kinds.d.ts +90 -3
- package/dist/workspace/kinds.d.ts.map +1 -1
- package/dist/workspace/kinds.js +161 -7
- package/dist/workspace/ledger.schema.json +158 -0
- package/dist/workspace/lexicon-outputs.d.ts +20 -1
- package/dist/workspace/lexicon-outputs.d.ts.map +1 -1
- package/dist/workspace/lexicon-outputs.js +102 -10
- package/dist/workspace/lineage-check.d.ts +3 -0
- package/dist/workspace/lineage-check.d.ts.map +1 -1
- package/dist/workspace/lineage-check.js +23 -4
- package/dist/workspace/lineage-upgrade.d.ts.map +1 -1
- package/dist/workspace/lineage-upgrade.js +13 -4
- package/dist/workspace/links.d.ts +8 -1
- package/dist/workspace/links.d.ts.map +1 -1
- package/dist/workspace/links.js +25 -1
- package/dist/workspace/ls.d.ts +8 -0
- package/dist/workspace/ls.d.ts.map +1 -1
- package/dist/workspace/ls.js +7 -1
- package/dist/workspace/ls.schema.json +7 -0
- package/dist/workspace/member-commands.d.ts.map +1 -1
- package/dist/workspace/member-commands.js +54 -5
- package/dist/workspace/migrate-links.d.ts +45 -0
- package/dist/workspace/migrate-links.d.ts.map +1 -0
- package/dist/workspace/migrate-links.js +100 -0
- package/dist/workspace/migration-dirs.d.ts +74 -0
- package/dist/workspace/migration-dirs.d.ts.map +1 -0
- package/dist/workspace/migration-dirs.js +254 -0
- package/dist/workspace/new-migrations.d.ts +47 -0
- package/dist/workspace/new-migrations.d.ts.map +1 -0
- package/dist/workspace/new-migrations.js +91 -0
- package/dist/workspace/pr-report.schema.json +46 -0
- package/dist/workspace/promote.d.ts +96 -0
- package/dist/workspace/promote.d.ts.map +1 -0
- package/dist/workspace/promote.js +263 -0
- package/dist/workspace/reason-codes.d.ts +21 -0
- package/dist/workspace/reason-codes.d.ts.map +1 -1
- package/dist/workspace/reason-codes.js +25 -0
- package/dist/workspace/records-cli.d.ts +11 -0
- package/dist/workspace/records-cli.d.ts.map +1 -1
- package/dist/workspace/records-cli.js +57 -5
- package/dist/workspace/records.schema.json +5 -0
- package/dist/workspace/release-candidates.d.ts +2 -0
- package/dist/workspace/release-candidates.d.ts.map +1 -1
- package/dist/workspace/release-candidates.js +5 -2
- package/dist/workspace/release-train.d.ts +53 -0
- package/dist/workspace/release-train.d.ts.map +1 -0
- package/dist/workspace/release-train.js +134 -0
- package/dist/workspace/release-workflow.d.ts.map +1 -1
- package/dist/workspace/release-workflow.js +4 -3
- package/dist/workspace/repo-ref.d.ts +45 -0
- package/dist/workspace/repo-ref.d.ts.map +1 -0
- package/dist/workspace/repo-ref.js +114 -0
- package/dist/workspace/run-event.schema.json +2 -2
- package/dist/workspace/runs-cli.d.ts +9 -0
- package/dist/workspace/runs-cli.d.ts.map +1 -1
- package/dist/workspace/runs-cli.js +48 -3
- package/dist/workspace/runs.schema.json +7 -0
- package/dist/workspace/search-contract.d.ts +1 -1
- package/dist/workspace/search-contract.d.ts.map +1 -1
- package/dist/workspace/status-decisions.d.ts +37 -0
- package/dist/workspace/status-decisions.d.ts.map +1 -0
- package/dist/workspace/status-decisions.js +147 -0
- package/dist/workspace/status-gates.d.ts +9 -0
- package/dist/workspace/status-gates.d.ts.map +1 -1
- package/dist/workspace/status.d.ts +4 -0
- package/dist/workspace/status.d.ts.map +1 -1
- package/dist/workspace/status.js +14 -2
- package/dist/workspace/status.schema.json +6 -0
- package/dist/workspace/work-cli.d.ts.map +1 -1
- package/dist/workspace/work-cli.js +5 -3
- package/dist/workspace/work-evidence-ops.d.ts +83 -0
- package/dist/workspace/work-evidence-ops.d.ts.map +1 -0
- package/dist/workspace/work-evidence-ops.js +169 -0
- package/dist/workspace/work-evidence.d.ts +12 -2
- package/dist/workspace/work-evidence.d.ts.map +1 -1
- package/dist/workspace/work-evidence.js +21 -3
- package/dist/workspace/work-evidence.schema.json +49 -2
- package/dist/workspace/workspace-gates.d.ts +110 -0
- package/dist/workspace/workspace-gates.d.ts.map +1 -0
- package/dist/workspace/workspace-gates.js +140 -0
- package/dist/workspace/workspace-kinds.schema.json +108 -10
- package/dist/workspace/workspace-ledger.d.ts +76 -0
- package/dist/workspace/workspace-ledger.d.ts.map +1 -0
- package/dist/workspace/workspace-ledger.js +91 -0
- package/dist/workspace/workspace-run.d.ts +112 -0
- package/dist/workspace/workspace-run.d.ts.map +1 -0
- package/dist/workspace/workspace-run.js +432 -0
- package/package.json +1 -1
- package/src/__golden__/wave-loop/op-by-apply-after-fails.json +10 -0
- package/src/__golden__/wave-loop/op-by-apply-hold.json +10 -0
- package/src/__golden__/wave-loop/op-by-apply-ignored.json +10 -0
- package/src/__golden__/wave-loop/op-by-apply-pass.json +10 -0
- package/src/__golden__/wave-loop/op-by-apply-skip.json +10 -0
- package/src/__golden__/wave-loop/op-cost-member-clash.json +6 -0
- package/src/__golden__/wave-loop/op-cost-member.json +13 -0
- package/src/__golden__/wave-loop/op-denied-source.json +16 -0
- package/src/__golden__/wave-loop/op-denied.json +16 -0
- package/src/__golden__/wave-loop/op-discover-rest-short.json +6 -0
- package/src/__golden__/wave-loop/op-discover-rest-stops.json +13 -0
- package/src/__golden__/wave-loop/op-discover-rest.json +26 -0
- package/src/__golden__/wave-loop/op-discover-wide.json +26 -0
- package/src/__golden__/wave-loop/op-held-on-again.json +16 -0
- package/src/__golden__/wave-loop/op-held-on-before-apply.json +16 -0
- package/src/__golden__/wave-loop/op-held-on.json +16 -0
- package/src/__golden__/wave-loop/op-hooks-apply-fails.json +13 -0
- package/src/__golden__/wave-loop/op-hooks-fail-after-apply.json +13 -0
- package/src/__golden__/wave-loop/op-hooks-fail-before-apply.json +13 -0
- package/src/__golden__/wave-loop/op-hooks-fail-before-plan.json +13 -0
- package/src/__golden__/wave-loop/op-hooks-held.json +13 -0
- package/src/__golden__/wave-loop/op-hooks-hold-before-apply.json +13 -0
- package/src/__golden__/wave-loop/op-hooks-pass.json +13 -0
- package/src/__golden__/wave-loop/op-hooks-refused-at-base.json +13 -0
- package/src/__golden__/wave-loop/op-hooks-share.json +33 -0
- package/src/__golden__/wave-loop/op-outcome-held.json +10 -0
- package/src/__golden__/wave-loop/op-outcome-replan-approved.json +10 -0
- package/src/__golden__/wave-loop/op-outcome-replan-forever.json +10 -0
- package/src/__golden__/wave-loop/op-outcome-replan.json +10 -0
- package/src/__golden__/wave-loop/op-outcome-stood-down.json +10 -0
- package/src/__golden__/wave-loop/op-outcome-unreadable.json +10 -0
- package/src/__golden__/wave-loop/op-overridden.json +16 -0
- package/src/__golden__/wave-loop/op-per-run.json +20 -0
- package/src/__golden__/wave-loop/op-point.json +13 -0
- package/src/__golden__/wave-loop/op-pr-review.json +10 -0
- package/src/__golden__/wave-loop/op-sealed.json +10 -0
- package/src/__golden__/wave-loop/op-spend-change-only.json +10 -0
- package/src/__golden__/wave-loop/op-spend.json +10 -0
- package/src/__golden__/wave-loop/op-wave-commands.json +55 -0
- package/src/__golden__/wave-loop/op-wide-refused.json +26 -0
- package/src/__golden__/wave-loop/op-wide-waiting.json +29 -0
- package/src/__golden__/wave-loop/op-wide.json +39 -0
- package/src/action-pins.test.ts +78 -0
- package/src/action-pins.ts +92 -0
- package/src/cli/commands/carve-score-once.test.ts +99 -0
- package/src/cli/commands/carve.ts +37 -6
- package/src/cli/commands/check-lexicon.test.ts +2 -2
- package/src/cli/commands/import-report.ts +71 -0
- package/src/cli/commands/import.ts +33 -3
- package/src/cli/commands/migrate-dialect.test.ts +145 -0
- package/src/cli/commands/migrate.ts +132 -11
- package/src/cli/handlers/__snapshots__/build-member.test.ts.snap +4 -4
- package/src/cli/handlers/approve.ts +33 -4
- package/src/cli/handlers/build.ts +7 -1
- package/src/cli/handlers/graph-reader.test.ts +31 -0
- package/src/cli/handlers/graph-reader.ts +51 -0
- package/src/cli/handlers/graph.ts +14 -0
- package/src/cli/handlers/ledger.ts +54 -0
- package/src/cli/handlers/migrate.ts +21 -12
- package/src/cli/handlers/misc.ts +10 -1
- package/src/cli/handlers/pr-env.ts +2 -2
- package/src/cli/handlers/pr.ts +176 -37
- package/src/cli/handlers/query.ts +16 -4
- package/src/cli/handlers/release.ts +6 -3
- package/src/cli/handlers/search.ts +13 -34
- package/src/cli/main.test.ts +17 -0
- package/src/cli/main.ts +87 -14
- package/src/cli/parse-args.ts +35 -2
- package/src/cli/registry.ts +22 -4
- package/src/components/builders.ts +8 -0
- package/src/components/capability-plugin-loader.ts +13 -1
- package/src/components/capability-plugin.ts +16 -0
- package/src/components/pr-env-run-bundle.test.ts +1 -0
- package/src/components/pr-hooks.test.ts +73 -5
- package/src/components/pr-hooks.ts +95 -5
- package/src/components/pr-pipeline.ts +64 -16
- package/src/components/pr-policy.test.ts +64 -1
- package/src/components/pr-policy.ts +53 -0
- package/src/components/pr-run.ts +4 -0
- package/src/components/registry.test.ts +1 -1
- package/src/components/starter-plugin.ts +6 -0
- package/src/components/verbs/index.ts +2 -0
- package/src/components/verbs/publish-artifact.test.ts +60 -0
- package/src/components/verbs/publish-artifact.ts +122 -0
- package/src/components/verbs/publish-image.test.ts +101 -0
- package/src/components/verbs/publish-image.ts +265 -0
- package/src/config-environments.ts +28 -1
- package/src/config.ts +24 -1
- package/src/gate-decision-bundle.test.ts +5 -2
- package/src/gate-decision-identity.test.ts +20 -0
- package/src/gate-decision.ts +46 -3
- package/src/graph-ir.ts +27 -0
- package/src/graph-walk.test.ts +44 -0
- package/src/graph-walk.ts +102 -0
- package/src/import/parser.ts +20 -0
- package/src/lexicon.ts +98 -6
- package/src/lifecycle/gate-ledger.ts +8 -0
- package/src/lifecycle/git.ts +2 -2
- package/src/lifecycle/ledger-read.test.ts +107 -0
- package/src/lifecycle/ledger-read.ts +195 -0
- package/src/lifecycle/release-workstation.ts +24 -0
- package/src/lifecycle/run-ledger.ts +5 -2
- package/src/lint/cross-root.test.ts +122 -0
- package/src/lint/discover.ts +3 -1
- package/src/lint/post-synth.ts +212 -2
- package/src/lint/rules/op/index.ts +2 -0
- package/src/lint/rules/op/ops019-workspace-op-target.ts +89 -0
- package/src/op/activities/activity-contracts.ts +5 -2
- package/src/op/activities/index.ts +2 -2
- package/src/op/activities/review-agent.test.ts +221 -4
- package/src/op/activities/review-agent.ts +265 -22
- package/src/op/activities/work-evidence.ts +9 -1
- package/src/op/builders.ts +9 -5
- package/src/op/composites/apply-op.test.ts +108 -0
- package/src/op/composites/apply-op.ts +37 -2
- package/src/op/gate-approval-at-base.test.ts +97 -0
- package/src/op/gate-approval-base.ts +72 -0
- package/src/op/gate-approval.test.ts +6 -1
- package/src/op/gate-approval.ts +22 -27
- package/src/op/gate-name.ts +16 -1
- package/src/op/gate-quorum-at-base.test.ts +211 -0
- package/src/op/gate-quorum.ts +156 -8
- package/src/op/gate-review.ts +14 -0
- package/src/op/gate.ts +69 -9
- package/src/op/index.ts +3 -2
- package/src/op/local-executor.ts +91 -11
- package/src/op/op-waves-depends-on.test.ts +81 -0
- package/src/op/op-waves-hook-targets.test.ts +4 -2
- package/src/op/op-waves-quorum-product.test.ts +158 -0
- package/src/op/op-waves-run.ts +288 -14
- package/src/op/op-waves.ts +143 -14
- package/src/op/runtime.ts +8 -0
- package/src/op/schedule-overdue.ts +33 -0
- package/src/op/stage-hooks.ts +13 -5
- package/src/op/workspace-run-env.ts +29 -0
- package/src/pr-forge-bundle.test.ts +2 -2
- package/src/pr-forge.test.ts +47 -0
- package/src/pr-forge.ts +68 -0
- package/src/pr-loop-bundle.test.ts +3 -2
- package/src/pr-loop.test.ts +52 -0
- package/src/pr-loop.ts +12 -0
- package/src/pr-migrations.ts +97 -0
- package/src/pr-post-open.e2e.test.ts +36 -0
- package/src/pr-post.test.ts +102 -3
- package/src/pr-post.ts +90 -2
- package/src/read-filter.test.ts +54 -0
- package/src/read-filter.ts +193 -0
- package/src/run-event.ts +1 -1
- package/src/stage-hooks-bundle.test.ts +1 -0
- package/src/terraform/carve.ts +52 -15
- package/src/terraform/cuts.test.ts +173 -0
- package/src/terraform/cuts.ts +188 -0
- package/src/terraform/folds.ts +44 -0
- package/src/terraform/graph.ts +9 -0
- package/src/terraform/radius.test.ts +90 -0
- package/src/terraform/reach.test.ts +146 -0
- package/src/terraform/reach.ts +247 -0
- package/src/terraform/score.ts +27 -29
- package/src/terraform/state.ts +3 -1
- package/src/terraform/types.ts +8 -0
- package/src/wave-blocked.ts +28 -3
- package/src/wave-loop-golden.test.ts +4 -3
- package/src/workspace/__fixtures__/import/spacelift.json +9 -0
- package/src/workspace/__fixtures__/import/tfc.json +13 -0
- package/src/workspace/behold-kinds.test.ts +2 -1
- package/src/workspace/check-contract.test.ts +8 -6
- package/src/workspace/check.schema.json +13 -2
- package/src/workspace/checks/ci-branch.test.ts +128 -0
- package/src/workspace/checks/ci-branch.ts +141 -0
- package/src/workspace/checks/gate-approval.test.ts +77 -0
- package/src/workspace/checks/gate-approval.ts +121 -0
- package/src/workspace/checks/links.ts +12 -1
- package/src/workspace/checks/org.ts +169 -0
- package/src/workspace/checks.ts +68 -7
- package/src/workspace/ci-branches.ts +31 -0
- package/src/workspace/ci-green-branches.test.ts +153 -0
- package/src/workspace/ci-green-cli.ts +7 -7
- package/src/workspace/ci-green-workflow.test.ts +4 -3
- package/src/workspace/ci-green-workflow.ts +13 -9
- package/src/workspace/ci-green.ts +49 -12
- package/src/workspace/ci-run.test.ts +72 -1
- package/src/workspace/ci-run.ts +68 -8
- package/src/workspace/ci-serve.test.ts +95 -0
- package/src/workspace/ci-serve.ts +106 -65
- package/src/workspace/component-environments.test.ts +25 -2
- package/src/workspace/compose-graph.ts +9 -2
- package/src/workspace/composites.schema.json +22 -0
- package/src/workspace/composites.test.ts +39 -8
- package/src/workspace/composites.ts +6 -2
- package/src/workspace/conformance/__writer_fixture__/work/work.schema.json +105 -1
- package/src/workspace/cross-root-lint.test.ts +90 -0
- package/src/workspace/cross-root-lint.ts +126 -0
- package/src/workspace/dbt-kind.test.ts +3 -2
- package/src/workspace/dbt-models-reader.ts +362 -0
- package/src/workspace/dbt-models.test.ts +248 -0
- package/src/workspace/dbt-project-common.ts +113 -0
- package/src/workspace/dbt-project-reader.ts +29 -112
- package/src/workspace/decision-rank.ts +40 -0
- package/src/workspace/declaration.schema.json +166 -24
- package/src/workspace/declaration.ts +101 -15
- package/src/workspace/environments.ts +40 -4
- package/src/workspace/exit-cli.ts +219 -0
- package/src/workspace/exit.schema.json +257 -0
- package/src/workspace/exit.test.ts +185 -0
- package/src/workspace/exit.ts +560 -0
- package/src/workspace/flyway-dir.ts +113 -0
- package/src/workspace/flyway-layout.ts +337 -0
- package/src/workspace/graph-cache.ts +12 -2
- package/src/workspace/graph-cli.ts +25 -10
- package/src/workspace/graph-contract.test.ts +4 -2
- package/src/workspace/graph.schema.json +34 -0
- package/src/workspace/identity.test.ts +29 -2
- package/src/workspace/import-fetch.ts +151 -0
- package/src/workspace/import-init.ts +147 -0
- package/src/workspace/import-source.test.ts +183 -0
- package/src/workspace/import-source.ts +462 -0
- package/src/workspace/init.test.ts +55 -1
- package/src/workspace/init.ts +66 -4
- package/src/workspace/intent.ts +6 -14
- package/src/workspace/kind-link-readers.ts +68 -3
- package/src/workspace/kind-readers.test.ts +36 -0
- package/src/workspace/kind-readers.ts +18 -1
- package/src/workspace/kinds.test.ts +35 -7
- package/src/workspace/kinds.ts +212 -11
- package/src/workspace/ledger.schema.json +158 -0
- package/src/workspace/lexicon-outputs.ts +132 -13
- package/src/workspace/lineage-check.ts +30 -7
- package/src/workspace/lineage-upgrade.test.ts +20 -0
- package/src/workspace/lineage-upgrade.ts +16 -7
- package/src/workspace/links.ts +24 -1
- package/src/workspace/ls-contract.test.ts +1 -1
- package/src/workspace/ls.schema.json +7 -0
- package/src/workspace/ls.ts +10 -1
- package/src/workspace/member-commands.ts +60 -2
- package/src/workspace/member-file-schema.test.ts +124 -0
- package/src/workspace/migrate-links.test.ts +99 -0
- package/src/workspace/migrate-links.ts +108 -0
- package/src/workspace/migration-dirs.test.ts +272 -0
- package/src/workspace/migration-dirs.ts +267 -0
- package/src/workspace/new-migrations.test.ts +98 -0
- package/src/workspace/new-migrations.ts +109 -0
- package/src/workspace/org-scope.test.ts +238 -0
- package/src/workspace/pr-report.schema.json +46 -0
- package/src/workspace/promote.test.ts +114 -0
- package/src/workspace/promote.ts +302 -0
- package/src/workspace/reason-codes.test.ts +10 -1
- package/src/workspace/reason-codes.ts +25 -0
- package/src/workspace/records-cli.ts +58 -5
- package/src/workspace/records.schema.json +5 -0
- package/src/workspace/release-candidates.ts +7 -2
- package/src/workspace/release-train.ts +157 -0
- package/src/workspace/release-workflow.ts +4 -3
- package/src/workspace/repo-ref.ts +113 -0
- package/src/workspace/run-event.schema.json +2 -2
- package/src/workspace/runs-cli.ts +48 -4
- package/src/workspace/runs.schema.json +7 -0
- package/src/workspace/status-contract.test.ts +46 -1
- package/src/workspace/status-decisions.test.ts +100 -0
- package/src/workspace/status-decisions.ts +151 -0
- package/src/workspace/status-gates.ts +9 -0
- package/src/workspace/status.schema.json +6 -0
- package/src/workspace/status.ts +17 -3
- package/src/workspace/where-filters.test.ts +39 -0
- package/src/workspace/work-cli.ts +5 -3
- package/src/workspace/work-evidence-ops.test.ts +176 -0
- package/src/workspace/work-evidence-ops.ts +189 -0
- package/src/workspace/work-evidence.schema.json +49 -2
- package/src/workspace/work-evidence.ts +22 -3
- package/src/workspace/workspace-gates.ts +200 -0
- package/src/workspace/workspace-kinds.schema.json +108 -10
- package/src/workspace/workspace-ledger.ts +130 -0
- package/src/workspace/workspace-run.test.ts +322 -0
- package/src/workspace/workspace-run.ts +461 -0
package/src/op/gate-quorum.ts
CHANGED
|
@@ -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
|
|
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
|
-
/**
|
|
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
|
-
/**
|
|
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
|
-
|
|
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
|
-
...(
|
|
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
|
}
|
package/src/op/gate-review.ts
CHANGED
|
@@ -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
|
@@ -50,10 +50,10 @@ import { describePlanDigest, samePlanDigest } from "../lifecycle/plan-digest";
|
|
|
50
50
|
import { isModelAuthored } from "../lifecycle/gate-origin";
|
|
51
51
|
import { appliedGatePath, isApprovalSpent, parseAppliedGateLines, type AppliedGateRecord } from "../lifecycle/gate-applied";
|
|
52
52
|
import { sortedJsonReplacer } from "../utils";
|
|
53
|
-
import { gateContextDigest, type GateApprover, type ResolvedGateApproval } from "./gate-approval";
|
|
53
|
+
import { gateContextDigest, type GateApprover, type GateBaseApproval, 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,13 @@ 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
|
-
|
|
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. `policy`,
|
|
134
|
+
* `mode` and `context` (#4404) are read the same way. Each is absent or
|
|
135
|
+
* null when the entry sets none.
|
|
136
|
+
*/
|
|
137
|
+
requirement: { gate: string; class: string | null; quorum?: GateQuorumSpec | null } & GateBaseApproval;
|
|
132
138
|
/** Why `approval` doesn't count, or null when it does. */
|
|
133
139
|
refuses(approval: GateResolutionRecord): string | null;
|
|
134
140
|
}
|
|
@@ -318,6 +324,12 @@ export interface GateCheckInput {
|
|
|
318
324
|
* and the merged pull request's review.
|
|
319
325
|
*/
|
|
320
326
|
judge?: GateQuorumJudge;
|
|
327
|
+
/**
|
|
328
|
+
* The port's {@link GateLedgerPort.approvalRule} for this gate, when the
|
|
329
|
+
* caller already read it (#4324): the Op executor reads it first, for the
|
|
330
|
+
* quorum at base. Absent, it is read here; null means none applies.
|
|
331
|
+
*/
|
|
332
|
+
rule?: GateApprovalRule | null;
|
|
321
333
|
/** ISO-8601 "now" — supplied by the caller, so the decision is deterministic under test. */
|
|
322
334
|
now?: string;
|
|
323
335
|
/**
|
|
@@ -371,6 +383,31 @@ export interface GateCheckInput {
|
|
|
371
383
|
* no `empty` resolution counts.
|
|
372
384
|
*/
|
|
373
385
|
verifyEmpty?: (planDigest: string) => Promise<boolean>;
|
|
386
|
+
/**
|
|
387
|
+
* A workspace gate that may stand in for this gate (#2554, #2524 D17,
|
|
388
|
+
* ws-039). Read only on a gate bound to a plan, and only when the gate's
|
|
389
|
+
* own approvals don't pass it. The stand-in decides: it answers an approval
|
|
390
|
+
* only when the member opted in at base and the workspace approval's
|
|
391
|
+
* digest covers this member's plan. Absent, a workspace gate never
|
|
392
|
+
* satisfies this gate.
|
|
393
|
+
*/
|
|
394
|
+
workspace?: WorkspaceGateStandIn;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
/** What a workspace gate approval that stands in for a member gate names (#2554). */
|
|
398
|
+
export interface WorkspaceGateStood {
|
|
399
|
+
/** The workspace gate's name. */
|
|
400
|
+
gate: string;
|
|
401
|
+
/** The digest of the member plans the approval covers. */
|
|
402
|
+
digest: string;
|
|
403
|
+
approvedBy: string;
|
|
404
|
+
/** When the workspace approval was recorded. */
|
|
405
|
+
timestamp: string;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
/** See {@link GateCheckInput.workspace}. */
|
|
409
|
+
export interface WorkspaceGateStandIn {
|
|
410
|
+
standIn(q: { op: string; gate: string; planDigest: string; environment?: string }): Promise<WorkspaceGateStood | null>;
|
|
374
411
|
}
|
|
375
412
|
|
|
376
413
|
/**
|
|
@@ -441,9 +478,10 @@ export type GateCheck =
|
|
|
441
478
|
* under one of the gate's aliases counted, on any gate. `"empty"`
|
|
442
479
|
* (#4045) when a release gate passed over an empty change set, on the
|
|
443
480
|
* `empty: true` resolution the promote wrote, after the stored
|
|
444
|
-
* document was read back and found empty.
|
|
481
|
+
* document was read back and found empty. `"workspace"` (#2554) when a
|
|
482
|
+
* workspace gate's approval stood in for it ({@link GateCheckInput.workspace}).
|
|
445
483
|
*/
|
|
446
|
-
via?: "quorum" | "policy" | "alias" | "empty";
|
|
484
|
+
via?: "quorum" | "policy" | "alias" | "empty" | "workspace";
|
|
447
485
|
/** Every approval that counted toward the quorum. Set with `via`. */
|
|
448
486
|
approvals?: GateResolutionRecord[];
|
|
449
487
|
/** The approvals this run carried from an alias and wrote under the gate's name (#3949). Set when `via` is `"alias"`. */
|
|
@@ -774,7 +812,7 @@ export async function evaluateGate(port: GateLedgerPort, input: GateCheckInput):
|
|
|
774
812
|
// newest one since the standing fact comes back as a mismatch instead.
|
|
775
813
|
const forOtherRelease = (r: GateResolutionRecord) => r.gate === input.gate && r.releaseDigest !== input.releaseDigest;
|
|
776
814
|
// #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;
|
|
815
|
+
const rule = input.rule !== undefined ? input.rule : ((await port.approvalRule?.(input.gate)) ?? null);
|
|
778
816
|
const standing = latestPendingGate(pending, input.gate);
|
|
779
817
|
const since = standing?.timestamp ?? EPOCH;
|
|
780
818
|
|
|
@@ -845,9 +883,8 @@ export async function evaluateGate(port: GateLedgerPort, input: GateCheckInput):
|
|
|
845
883
|
? humanQuorumSince(pending, standing)
|
|
846
884
|
: since;
|
|
847
885
|
// #4290: who an approval proves, and their roles at base, for rules that read them.
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
: UNPROVEN_JUDGE;
|
|
886
|
+
// #4333: a caller's judge applies to every rule, since it may leave out everyone involved in the change.
|
|
887
|
+
const judge = input.judge ?? (quorumRules(input.approval.quorum).some(ruleNeedsProof) ? ((await port.quorumJudge?.(input.gate)) ?? UNPROVEN_JUDGE) : UNPROVEN_JUDGE);
|
|
851
888
|
const tally = tallyGateApprovals(resolutions, input.gate, since, input.planDigest, input.approval, humansSince, judge);
|
|
852
889
|
if (tally.permit) {
|
|
853
890
|
return { satisfied: true, resolution: tally.permit, via: "policy", approvals: tally.counted };
|
|
@@ -887,6 +924,29 @@ export async function evaluateGate(port: GateLedgerPort, input: GateCheckInput):
|
|
|
887
924
|
mismatched = found.mismatched;
|
|
888
925
|
}
|
|
889
926
|
|
|
927
|
+
// #2554, ws-039: a workspace gate stands in only when the member opted in
|
|
928
|
+
// at base and the workspace approval's digest covers this plan.
|
|
929
|
+
if (input.workspace && input.planDigest !== undefined) {
|
|
930
|
+
const stood = await input.workspace.standIn({ op: input.op, gate: input.gate, planDigest: input.planDigest, ...(env !== undefined ? { environment: env } : {}) });
|
|
931
|
+
if (stood) {
|
|
932
|
+
return {
|
|
933
|
+
satisfied: true,
|
|
934
|
+
via: "workspace",
|
|
935
|
+
resolution: {
|
|
936
|
+
version: 1,
|
|
937
|
+
kind: "resolution",
|
|
938
|
+
op: input.op,
|
|
939
|
+
gate: input.gate,
|
|
940
|
+
resolvedBy: stood.approvedBy,
|
|
941
|
+
timestamp: stood.timestamp,
|
|
942
|
+
planDigest: input.planDigest,
|
|
943
|
+
...(env !== undefined ? { environment: env } : {}),
|
|
944
|
+
workspaceGate: { gate: stood.gate, digest: stood.digest },
|
|
945
|
+
},
|
|
946
|
+
};
|
|
947
|
+
}
|
|
948
|
+
}
|
|
949
|
+
|
|
890
950
|
// #4045: an approval of this gate for another release stands as a mismatch
|
|
891
951
|
// when nothing closer does, so the refusal says an approval exists.
|
|
892
952
|
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,
|
package/src/op/local-executor.ts
CHANGED
|
@@ -35,13 +35,14 @@ import { contentDigest } from "../content-digest";
|
|
|
35
35
|
import { parseDuration } from "./duration";
|
|
36
36
|
import { stepLabel, withRunConsole, withStepConsole } from "./step-console";
|
|
37
37
|
import { stepTimeoutProblem } from "./activity-profiles";
|
|
38
|
-
import { describeGateMismatch, evaluateGate, gateIsSealed, gitGateLedgerPort, type GateCheck, type GateLedgerPort } from "./gate";
|
|
38
|
+
import { describeGateMismatch, evaluateGate, gateIsSealed, gitGateLedgerPort, type GateApprovalRule, type GateCheck, type GateLedgerPort, type WorkspaceGateStandIn } from "./gate";
|
|
39
|
+
import { resolveWorkspaceRun, type WorkspaceRunCitation } from "./workspace-run-env";
|
|
39
40
|
import { gateName } from "./gate-name";
|
|
40
41
|
import { resolveGateAliases } from "./gate-alias";
|
|
41
42
|
import { gatePointOf } from "./gate-point";
|
|
42
43
|
import { evaluatePointGate, workspaceGatePointAsker, type GatePointAsker } from "./gate-point-run";
|
|
43
|
-
import type { ResolvedGateApproval } from "./gate-approval";
|
|
44
|
-
import { quorumRules, quorumRulesFor } from "./gate-quorum";
|
|
44
|
+
import type { GateBaseApproval, ResolvedGateApproval } from "./gate-approval";
|
|
45
|
+
import { quorumRules, quorumRulesFor, type GateQuorumSpec } from "./gate-quorum";
|
|
45
46
|
import { GATE_PLAN_CONTEXT_KEY, gatePlanSummaryOfResult } from "./gate-plan-context";
|
|
46
47
|
import { withOpRunContext, type OpRunContext, type PassedGate } from "./run-context";
|
|
47
48
|
import type { GateAnswerRef, PendingGateRecord } from "../lifecycle/gate-ledger";
|
|
@@ -109,8 +110,11 @@ export interface StepRecord {
|
|
|
109
110
|
/**
|
|
110
111
|
* On a gate with an `approval` block (#2508): whether a quorum or a policy permit passed it. `"point"` on a gate that asks a decision point (#3170).
|
|
111
112
|
* `"alias"` (#3949) when an approval recorded under one of the gate's aliases counted, on any gate.
|
|
113
|
+
* `"workspace"` (#2554) when a workspace gate's approval stood in for it.
|
|
112
114
|
*/
|
|
113
|
-
via?: "quorum" | "policy" | "point" | "alias" | "empty";
|
|
115
|
+
via?: "quorum" | "policy" | "point" | "alias" | "empty" | "workspace";
|
|
116
|
+
/** With `via: "workspace"` (#2554): the workspace gate and the digest of the member plans its approval covers. */
|
|
117
|
+
workspaceGate?: { gate: string; digest: string };
|
|
114
118
|
/** On a gate with a quorum (#2508): every approver who counted toward it. */
|
|
115
119
|
approvers?: string[];
|
|
116
120
|
/** On a gate that asks a decision point (#3170): the answer record that passed it, which the gate ledger's resolution cites too. */
|
|
@@ -612,6 +616,8 @@ interface GateContext {
|
|
|
612
616
|
onRecord?: (record: StepRecord) => void;
|
|
613
617
|
/** Where the run's events go (#4041): a step's start, its settle and its gate's decision. */
|
|
614
618
|
emit?: RunEventSink;
|
|
619
|
+
/** A workspace gate that may stand in for this run's gates (#2554). Absent outside a workspace run started with a gate. */
|
|
620
|
+
workspace?: WorkspaceGateStandIn;
|
|
615
621
|
/** The run context's list of passed gates (#2522). A gate that passes is appended here. */
|
|
616
622
|
passed?: PassedGate[];
|
|
617
623
|
/** The run's hold on its work item's lease (#2748). Absent in compensation phases, which run whatever the lease did. */
|
|
@@ -729,14 +735,43 @@ function gateFn(step: GateStep): string {
|
|
|
729
735
|
* returned, the same walk `plan` goes through. A reference that resolves to
|
|
730
736
|
* nothing is dropped rather than failing the run, so a policy reading it sees
|
|
731
737
|
* the attribute as absent.
|
|
738
|
+
*
|
|
739
|
+
* #4324: the quorum is `identity.gates.<gate>.quorum` in the workspace
|
|
740
|
+
* declaration at base (`atBase`) whenever that sets one, and the Op's own
|
|
741
|
+
* `approval.quorum` is not read then, so a change can't lower the quorum its
|
|
742
|
+
* own run needs. The base quorum applies to a gate whose Op declares no
|
|
743
|
+
* `approval` block too. With none at base, the Op's quorum applies as before.
|
|
744
|
+
*
|
|
745
|
+
* #4404: `policy`, `mode` and `context` are read the same way, each from its
|
|
746
|
+
* own `identity.gates.<gate>` field (`baseApproval`). A field the entry sets
|
|
747
|
+
* replaces the Op's. The base `context` wins key by key: a change can't edit a
|
|
748
|
+
* key the entry declares, but a key only the Op declares (often a step output
|
|
749
|
+
* the run computes) still reaches the policy. A field the entry leaves out
|
|
750
|
+
* still comes from the Op, and `chant workspace check` (WSP153) reports it.
|
|
732
751
|
*/
|
|
733
|
-
function resolveGateApproval(
|
|
752
|
+
export function resolveGateApproval(
|
|
734
753
|
step: GateStep,
|
|
735
754
|
resultsById: ReadonlyMap<string, unknown>,
|
|
736
755
|
env?: string,
|
|
756
|
+
atBase?: GateQuorumSpec | null,
|
|
757
|
+
baseApproval?: GateBaseApproval | null,
|
|
737
758
|
): ResolvedGateApproval | undefined {
|
|
738
|
-
const
|
|
739
|
-
|
|
759
|
+
const baseQuorum = atBase ?? undefined;
|
|
760
|
+
const base = {
|
|
761
|
+
policy: baseApproval?.policy ?? undefined,
|
|
762
|
+
mode: baseApproval?.mode ?? undefined,
|
|
763
|
+
context: baseApproval?.context ?? undefined,
|
|
764
|
+
};
|
|
765
|
+
const anyBase = baseQuorum !== undefined || base.policy !== undefined || base.mode !== undefined || base.context !== undefined;
|
|
766
|
+
const opApproval = step.approval ?? (anyBase ? {} : undefined);
|
|
767
|
+
if (!opApproval) return undefined;
|
|
768
|
+
const authored = {
|
|
769
|
+
...opApproval,
|
|
770
|
+
...(base.policy !== undefined ? { policy: base.policy } : {}),
|
|
771
|
+
...(base.mode !== undefined ? { mode: base.mode } : {}),
|
|
772
|
+
...(base.context !== undefined ? { context: { ...(opApproval.context ?? {}), ...base.context } } : {}),
|
|
773
|
+
};
|
|
774
|
+
const quorum = baseQuorum ?? authored.quorum;
|
|
740
775
|
let context: Record<string, unknown> | undefined;
|
|
741
776
|
if (authored.context) {
|
|
742
777
|
context = {};
|
|
@@ -755,10 +790,10 @@ function resolveGateApproval(
|
|
|
755
790
|
// #4290: the rules this run's gate takes, by its environment. A rule with
|
|
756
791
|
// `targets` never covers a run with no environment. One rule with no
|
|
757
792
|
// `targets` keeps its own shape, so a standing pending fact still matches.
|
|
758
|
-
const single =
|
|
759
|
-
const rules =
|
|
793
|
+
const single = quorum !== undefined && !Array.isArray(quorum) && (quorum as { targets?: unknown }).targets === undefined;
|
|
794
|
+
const rules = quorum ? quorumRulesFor(quorumRules(quorum), env !== undefined ? [env] : []) : [];
|
|
760
795
|
return {
|
|
761
|
-
...(single ? { quorum
|
|
796
|
+
...(single ? { quorum } : rules.length > 0 ? { quorum: rules } : {}),
|
|
762
797
|
...(authored.policy ? { policy: authored.policy } : {}),
|
|
763
798
|
mode: authored.mode ?? "log-only",
|
|
764
799
|
...(context ? { context } : {}),
|
|
@@ -786,7 +821,16 @@ async function runGateStep(
|
|
|
786
821
|
// a run over a missing digest.
|
|
787
822
|
const resolvedPlan = resolveStepOutputRefs(step.plan, resultsById);
|
|
788
823
|
const planDigest = typeof resolvedPlan === "string" && resolvedPlan !== "" ? resolvedPlan : undefined;
|
|
789
|
-
|
|
824
|
+
// #4324, #4404: the workspace's rule for this gate at base, read once: its quorum, policy, mode and context
|
|
825
|
+
// replaces the Op's, and evaluateGate judges approvals by it. A read that
|
|
826
|
+
// fails ends the step as a failing record, as a ledger read does below.
|
|
827
|
+
let rule: GateApprovalRule | null;
|
|
828
|
+
try {
|
|
829
|
+
rule = (await gates.port.approvalRule?.(gateName(step))) ?? null;
|
|
830
|
+
} catch (err) {
|
|
831
|
+
return { record: { phase: phaseName, fn: gateFn(step), args: {}, status: "fail", durationMs: Date.now() - start, error: errMessage(err) } };
|
|
832
|
+
}
|
|
833
|
+
const approval = resolveGateApproval(step, resultsById, gates.env, rule?.requirement.quorum, rule?.requirement);
|
|
790
834
|
// #3947: a gate that resumes stores the outputs the steps after it read.
|
|
791
835
|
const resumeAt = gates.resume?.gates.get(step);
|
|
792
836
|
let checkpoint: GateCheckpoint | undefined;
|
|
@@ -814,9 +858,11 @@ async function runGateStep(
|
|
|
814
858
|
...(gates.gitSha ? { gitSha: gates.gitSha } : {}),
|
|
815
859
|
...(planDigest !== undefined ? { planDigest } : {}),
|
|
816
860
|
...(approval ? { approval } : {}),
|
|
861
|
+
rule,
|
|
817
862
|
...(gates.now ? { now: gates.now } : {}),
|
|
818
863
|
...(checkpoint ? { checkpoint } : {}),
|
|
819
864
|
...(step.aliases?.length ? { aliases: resolveGateAliases(step, gates.op) } : {}),
|
|
865
|
+
...(gates.workspace ? { workspace: gates.workspace } : {}),
|
|
820
866
|
});
|
|
821
867
|
} catch (err) {
|
|
822
868
|
// Deciding a gate reads and writes the `chant/lifecycle` branch, so it
|
|
@@ -847,6 +893,7 @@ async function runGateStep(
|
|
|
847
893
|
timestamp: resolution.timestamp,
|
|
848
894
|
...(resolution.url ? { url: resolution.url } : {}),
|
|
849
895
|
...(check.via ? { via: check.via } : {}),
|
|
896
|
+
...(resolution.workspaceGate ? { workspaceGate: resolution.workspaceGate } : {}),
|
|
850
897
|
...(check.approvals ? { approvers: check.approvals.map((r) => r.resolvedBy) } : {}),
|
|
851
898
|
...(check.carried?.length
|
|
852
899
|
? { carriedFrom: check.carried.map((r) => ({ op: r.carriedFrom!.op, gate: r.carriedFrom!.gate, resolvedBy: r.resolvedBy, timestamp: r.carriedFrom!.timestamp })) }
|
|
@@ -1278,6 +1325,18 @@ export interface RunOpOptions {
|
|
|
1278
1325
|
gates?: GateLedgerPort;
|
|
1279
1326
|
/** Working directory for the default git-backed gate ledger. */
|
|
1280
1327
|
cwd?: string;
|
|
1328
|
+
/**
|
|
1329
|
+
* The workspace run that started this run (#2554). Defaults to the one the
|
|
1330
|
+
* environment names (`CHANT_WORKSPACE_RUN`, `./workspace-run-env.ts`). The
|
|
1331
|
+
* run record cites it.
|
|
1332
|
+
*/
|
|
1333
|
+
workspaceRun?: WorkspaceRunCitation;
|
|
1334
|
+
/**
|
|
1335
|
+
* The workspace gate that may stand in for this run's gates (#2554). Defaults
|
|
1336
|
+
* to the gate the workspace run names, read from the workspace ledger with
|
|
1337
|
+
* the member's opt-in at base; a test passes a stub.
|
|
1338
|
+
*/
|
|
1339
|
+
workspaceGate?: WorkspaceGateStandIn;
|
|
1281
1340
|
/** ISO-8601 "now", so a gate decision is deterministic under test. */
|
|
1282
1341
|
now?: string;
|
|
1283
1342
|
/** Identifies this run on any pending fact it records. */
|
|
@@ -1492,6 +1551,8 @@ async function runOpInTurn(
|
|
|
1492
1551
|
const commit = options.commit ?? (await resolveCommitSha(options.cwd ? { cwd: options.cwd } : {}));
|
|
1493
1552
|
// #4020: the CI run this one is in, in the shape a deploy record carries it.
|
|
1494
1553
|
const ciRun = resolveCiRun();
|
|
1554
|
+
// #2554: the workspace run that started this one, which its record cites.
|
|
1555
|
+
const workspaceRun = options.workspaceRun ?? resolveWorkspaceRun();
|
|
1495
1556
|
// #4041: the run's events name it the way its run record does: its own id,
|
|
1496
1557
|
// the commit, and the CI run it is in.
|
|
1497
1558
|
// #4042: a run that writes to the ledger also keeps its events on this host,
|
|
@@ -1535,6 +1596,11 @@ async function runOpInTurn(
|
|
|
1535
1596
|
passed: run.passedGates,
|
|
1536
1597
|
points: options.points ?? workspaceGatePointAsker(options.cwd ?? process.cwd()),
|
|
1537
1598
|
...(options.env !== undefined ? { env: options.env } : {}),
|
|
1599
|
+
...(options.workspaceGate
|
|
1600
|
+
? { workspace: options.workspaceGate }
|
|
1601
|
+
: workspaceRun?.gate
|
|
1602
|
+
? { workspace: lazyWorkspaceGate(options.cwd ?? process.cwd(), workspaceRun.gate) }
|
|
1603
|
+
: {}),
|
|
1538
1604
|
};
|
|
1539
1605
|
|
|
1540
1606
|
// Effect steps are ordered (read-compare-run-write): refuse them in a
|
|
@@ -1658,6 +1724,7 @@ async function runOpInTurn(
|
|
|
1658
1724
|
...(resumed ? { resumed } : {}),
|
|
1659
1725
|
...(commit ? { gitSha: commit } : {}),
|
|
1660
1726
|
...(options.runner ? { runOrigin: options.runner.runOrigin } : ciRun ? { runOrigin: ciRun.runOrigin } : {}),
|
|
1727
|
+
...(workspaceRun ? { workspaceRun: { id: workspaceRun.id, ...(workspaceRun.gate ? { gate: workspaceRun.gate } : {}) } } : {}),
|
|
1661
1728
|
phaseDurations,
|
|
1662
1729
|
});
|
|
1663
1730
|
if (status === "fail" && options.describeFailure) {
|
|
@@ -1954,3 +2021,16 @@ function phaseVerdict(records: readonly StepRecord[]): "ok" | "fail" | "skipped"
|
|
|
1954
2021
|
function errMessage(err: unknown): string {
|
|
1955
2022
|
return err instanceof Error ? err.message : String(err);
|
|
1956
2023
|
}
|
|
2024
|
+
|
|
2025
|
+
/**
|
|
2026
|
+
* The workspace gate stand-in for a member run (#2554), loading the workspace
|
|
2027
|
+
* code only when a gate is asked about it.
|
|
2028
|
+
*/
|
|
2029
|
+
function lazyWorkspaceGate(cwd: string, gate: string): WorkspaceGateStandIn {
|
|
2030
|
+
return {
|
|
2031
|
+
async standIn(q) {
|
|
2032
|
+
const { memberGateStandIn } = await import("../workspace/workspace-gates");
|
|
2033
|
+
return memberGateStandIn(cwd, gate, q);
|
|
2034
|
+
},
|
|
2035
|
+
};
|
|
2036
|
+
}
|