@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
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* #4404: an Op gate's `policy`, `mode` and `context` are fields of
|
|
3
|
+
* `identity.gates.<gate>` in the workspace declaration at base, as the quorum
|
|
4
|
+
* is (#4324), so a change can't switch its own gate to `log-only` or swap in a
|
|
5
|
+
* permissive policy and be judged by that.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { afterEach, describe, expect, test } from "vitest";
|
|
9
|
+
import type { GateStep, OpConfig } from "./types";
|
|
10
|
+
import type { ActivityFn } from "./activity-registry";
|
|
11
|
+
import { resolveGateApproval, runOpLocally } from "./local-executor";
|
|
12
|
+
import { gitGateLedgerPort, memoryGateLedgerPort, type GateLedgerPort } from "./gate";
|
|
13
|
+
import { gatePolicyVersion, type GatePolicyRef } from "./gate-approval";
|
|
14
|
+
import { stepOutput } from "./step-output-ref";
|
|
15
|
+
import { resetGateOrigin } from "../lifecycle/gate-origin";
|
|
16
|
+
import { hasSshKeygen, TestRepo } from "../workspace/trust/test-repo";
|
|
17
|
+
|
|
18
|
+
const policy = (name: string, text: string): GatePolicyRef => ({ kind: "gate-policy", lexicon: "cedar", name, version: gatePolicyVersion(text), text });
|
|
19
|
+
const STRICT = policy("strict", "forbid unless maintainer");
|
|
20
|
+
const PERMISSIVE = policy("permissive", "permit all");
|
|
21
|
+
|
|
22
|
+
const step = (approval?: GateStep["approval"]): GateStep => ({ kind: "gate", gate: "ship", ...(approval ? { approval } : {}) }) as GateStep;
|
|
23
|
+
|
|
24
|
+
describe("resolveGateApproval reads policy, mode and context at base (#4404)", () => {
|
|
25
|
+
test("the base policy and mode replace the Op's own", () => {
|
|
26
|
+
const opSide = step({ policy: PERMISSIVE, mode: "log-only" });
|
|
27
|
+
const got = resolveGateApproval(opSide, new Map(), undefined, null, { policy: STRICT, mode: "enforce" });
|
|
28
|
+
expect(got).toEqual({ policy: STRICT, mode: "enforce" });
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
test("a gate whose Op dropped its approval block still takes the base policy and mode", () => {
|
|
32
|
+
expect(resolveGateApproval(step(), new Map(), undefined, null, { policy: STRICT, mode: "enforce" })).toEqual({ policy: STRICT, mode: "enforce" });
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
test("a field the entry leaves out comes from the Op", () => {
|
|
36
|
+
const opSide = step({ policy: PERMISSIVE, mode: "enforce" });
|
|
37
|
+
expect(resolveGateApproval(opSide, new Map(), undefined, null, { mode: "log-only" })).toEqual({ policy: PERMISSIVE, mode: "log-only" });
|
|
38
|
+
expect(resolveGateApproval(opSide, new Map(), undefined, null, { policy: STRICT })).toEqual({ policy: STRICT, mode: "enforce" });
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
test("the base context wins key by key, and a key only the Op sets still reaches the policy", () => {
|
|
42
|
+
const opSide = step({ policy: STRICT, context: { risk: "low", team: "payments" } });
|
|
43
|
+
const got = resolveGateApproval(opSide, new Map(), undefined, null, { context: { risk: "high" } });
|
|
44
|
+
expect(got?.context).toEqual({ risk: "high", team: "payments" });
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
test("a step output reference in the Op's context still resolves beside a base key", () => {
|
|
48
|
+
const opSide = step({ policy: STRICT, context: { rows: stepOutput("verify", "rows") } });
|
|
49
|
+
const got = resolveGateApproval(opSide, new Map([["verify", { rows: 12 }]]), undefined, null, { context: { risk: "high" } });
|
|
50
|
+
expect(got?.context).toEqual({ risk: "high", rows: 12 });
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
test("with nothing at base, the Op's own applies", () => {
|
|
54
|
+
expect(resolveGateApproval(step({ policy: PERMISSIVE, mode: "enforce" }), new Map(), undefined, null, { policy: null, mode: null, context: null })).toEqual({ policy: PERMISSIVE, mode: "enforce" });
|
|
55
|
+
expect(resolveGateApproval(step(), new Map(), undefined, null, { policy: null, mode: null, context: null })).toBeUndefined();
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
describe.skipIf(!hasSshKeygen)("an Op gate's policy and mode at base (#4404)", () => {
|
|
60
|
+
const repos: TestRepo[] = [];
|
|
61
|
+
afterEach(() => {
|
|
62
|
+
resetGateOrigin();
|
|
63
|
+
while (repos.length) repos.pop()!.cleanup();
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
const declaration = (gate: Record<string, unknown>): string => JSON.stringify({ name: "acme", schema: 1, members: [], identity: { gates: { ship: gate } } });
|
|
67
|
+
const config = (approval: NonNullable<GateStep["approval"]>): OpConfig => ({
|
|
68
|
+
name: "release",
|
|
69
|
+
overview: "",
|
|
70
|
+
phases: [
|
|
71
|
+
{ name: "Gate", steps: [{ kind: "gate", gate: "ship", approval }] },
|
|
72
|
+
{ name: "Apply", steps: [{ kind: "activity", fn: "ship", args: {} }] },
|
|
73
|
+
],
|
|
74
|
+
});
|
|
75
|
+
const activities = new Map<string, ActivityFn>([["ship", async () => ({})]]);
|
|
76
|
+
|
|
77
|
+
test("a change that switches its gate to a permissive log-only policy is judged by the base's", async () => {
|
|
78
|
+
const r = new TestRepo("4404-policy");
|
|
79
|
+
repos.push(r);
|
|
80
|
+
r.git(["config", "user.email", "t@example.test"]);
|
|
81
|
+
r.git(["config", "user.name", "t"]);
|
|
82
|
+
r.git(["config", "commit.gpgsign", "false"]);
|
|
83
|
+
r.write("chant.workspace.json", declaration({ policy: STRICT, mode: "enforce", context: { risk: "high" } }));
|
|
84
|
+
r.commit("base");
|
|
85
|
+
// The pull request rewrites the declaration's entry too; only base counts.
|
|
86
|
+
r.git(["checkout", "-q", "-b", "loosen"]);
|
|
87
|
+
r.write("chant.workspace.json", declaration({ policy: PERMISSIVE, mode: "log-only", context: { risk: "low" } }));
|
|
88
|
+
r.commit("loosen the gate");
|
|
89
|
+
|
|
90
|
+
const git = gitGateLedgerPort({ cwd: r.dir });
|
|
91
|
+
const gates: GateLedgerPort = { ...memoryGateLedgerPort(), approvalRule: git.approvalRule };
|
|
92
|
+
// The Op the change builds says permissive, log-only and low risk.
|
|
93
|
+
const run = await runOpLocally(config({ policy: PERMISSIVE, mode: "log-only", context: { risk: "low" } }), activities, {}, undefined, { gates, now: "2026-10-23T12:00:00.000Z" });
|
|
94
|
+
expect(run.status).toBe("gated");
|
|
95
|
+
expect(run.gate?.approval).toMatchObject({ policy: STRICT, mode: "enforce", context: { risk: "high" } });
|
|
96
|
+
});
|
|
97
|
+
});
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The parts of an Op gate's approval that an `identity.gates.<gate>` entry
|
|
3
|
+
* also carries (#4404): the modes, the policy check and the entry check. Its
|
|
4
|
+
* only runtime import is `node:crypto`, so `gate-decision.ts` (which checks an
|
|
5
|
+
* entry) bundles without the Op runtime or zod (gate-decision-bundle.test.ts).
|
|
6
|
+
* `gate-approval.ts` re-exports everything here.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { createHash } from "node:crypto";
|
|
10
|
+
import { GATE_PLAN_CONTEXT_KEY } from "./gate-plan-context";
|
|
11
|
+
import type { GateApprovalMode, GatePolicyRef } from "./gate-approval";
|
|
12
|
+
|
|
13
|
+
export const GATE_APPROVAL_MODES: readonly GateApprovalMode[] = ["log-only", "enforce"];
|
|
14
|
+
|
|
15
|
+
// The brand `stepOutput()` stamps (`step-output-ref.ts`), as a global symbol so
|
|
16
|
+
// this module need not import that file and its zod schemas; the same copy
|
|
17
|
+
// `activity-contract.ts` keeps for the same reason.
|
|
18
|
+
const STEP_OUTPUT_REF_BRAND = Symbol.for("chant.op.stepOutputRef");
|
|
19
|
+
function isStepOutputRef(value: unknown): boolean {
|
|
20
|
+
return typeof value === "object" && value !== null && (value as Record<symbol, unknown>)[STEP_OUTPUT_REF_BRAND] === true;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** The version of a gate policy: `sha256:` and the hex digest of its text. A lexicon's helper stamps it, and OPS015 checks it still matches. */
|
|
24
|
+
export function gatePolicyVersion(text: string): string {
|
|
25
|
+
return `sha256:${createHash("sha256").update(text).digest("hex")}`;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function isGatePolicyRef(value: unknown): value is GatePolicyRef {
|
|
29
|
+
if (typeof value !== "object" || value === null) return false;
|
|
30
|
+
const v = value as Record<string, unknown>;
|
|
31
|
+
return (
|
|
32
|
+
v.kind === "gate-policy" &&
|
|
33
|
+
typeof v.lexicon === "string" && v.lexicon !== "" &&
|
|
34
|
+
typeof v.name === "string" && v.name !== "" &&
|
|
35
|
+
typeof v.version === "string" && v.version !== "" &&
|
|
36
|
+
typeof v.text === "string" && v.text.trim() !== ""
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* What is wrong with the `policy`, `mode` and `context` of an
|
|
42
|
+
* `identity.gates.<gate>` entry (#4404), one line each, named by `at`. Each
|
|
43
|
+
* field stands alone: base `mode: "enforce"` with no base `policy` is fine, as
|
|
44
|
+
* the Op's own policy then applies. `chant workspace check` (WSP153) says so.
|
|
45
|
+
*/
|
|
46
|
+
export function gateBaseApprovalProblems(entry: { policy?: unknown; mode?: unknown; context?: unknown }, at: string): string[] {
|
|
47
|
+
const problems: string[] = [];
|
|
48
|
+
if (entry.policy !== undefined) {
|
|
49
|
+
if (!isGatePolicyRef(entry.policy)) problems.push(`\`${at}.policy\` does not resolve to a gate policy set: it needs kind "gate-policy", lexicon, name, version and text`);
|
|
50
|
+
else if (entry.policy.version !== gatePolicyVersion(entry.policy.text)) problems.push(`\`${at}.policy\` "${entry.policy.name}" has a version that is not the digest of its text`);
|
|
51
|
+
}
|
|
52
|
+
if (entry.mode !== undefined && !GATE_APPROVAL_MODES.includes(entry.mode as GateApprovalMode)) {
|
|
53
|
+
problems.push(`\`${at}.mode\` must be one of ${GATE_APPROVAL_MODES.map((m) => `"${m}"`).join(", ")}`);
|
|
54
|
+
}
|
|
55
|
+
if (entry.context !== undefined) {
|
|
56
|
+
if (typeof entry.context !== "object" || entry.context === null || Array.isArray(entry.context)) problems.push(`\`${at}.context\` must be an object of plan attributes`);
|
|
57
|
+
else {
|
|
58
|
+
for (const [key, value] of Object.entries(entry.context)) {
|
|
59
|
+
if (key === GATE_PLAN_CONTEXT_KEY) problems.push(`\`${at}.context.${key}\` is reserved: the run fills it with the plan's change-set summary (#3182)`);
|
|
60
|
+
else if (isStepOutputRef(value) || !isContextValue(value)) problems.push(`\`${at}.context.${key}\` must be a string, number, boolean or list of strings`);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return problems;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function isContextValue(value: unknown): boolean {
|
|
68
|
+
if (typeof value === "string" || typeof value === "boolean") return true;
|
|
69
|
+
if (typeof value === "number") return Number.isFinite(value);
|
|
70
|
+
if (Array.isArray(value)) return value.every((v) => typeof v === "string");
|
|
71
|
+
return isStepOutputRef(value);
|
|
72
|
+
}
|
|
@@ -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)", () => {
|
package/src/op/gate-approval.ts
CHANGED
|
@@ -33,16 +33,19 @@
|
|
|
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
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
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";
|
|
43
43
|
import { lexiconModulePath, importLexiconPackage } from "../lexicon-module";
|
|
44
44
|
import { isStepOutputRef, type StepOutputRef } from "./step-output-ref";
|
|
45
45
|
import { GATE_PLAN_CONTEXT_KEY } from "./gate-plan-context";
|
|
46
|
+
import { GATE_APPROVAL_MODES, gateBaseApprovalProblems, gatePolicyVersion, isContextValue, isGatePolicyRef } from "./gate-approval-base";
|
|
47
|
+
|
|
48
|
+
export { GATE_APPROVAL_MODES, gateBaseApprovalProblems, gatePolicyVersion, isGatePolicyRef } from "./gate-approval-base";
|
|
46
49
|
import { quorumProblems, type GateQuorumRule, type GateQuorumSpec } from "./gate-quorum";
|
|
47
50
|
|
|
48
51
|
export type { GateQuorumRule, GateQuorumSpec } from "./gate-quorum";
|
|
@@ -50,7 +53,6 @@ export type { GateQuorumRule, GateQuorumSpec } from "./gate-quorum";
|
|
|
50
53
|
/** What a policy's decision does to the gate. */
|
|
51
54
|
export type GateApprovalMode = "log-only" | "enforce";
|
|
52
55
|
|
|
53
|
-
export const GATE_APPROVAL_MODES: readonly GateApprovalMode[] = ["log-only", "enforce"];
|
|
54
56
|
|
|
55
57
|
/**
|
|
56
58
|
* A policy set a lexicon rendered for a gate — plain data, so it lands in the
|
|
@@ -159,10 +161,6 @@ export interface GatePolicyEvaluator {
|
|
|
159
161
|
evaluateGatePolicy(policy: GatePolicyRef, request: GatePolicyRequest): Promise<GatePolicyAnswer> | GatePolicyAnswer;
|
|
160
162
|
}
|
|
161
163
|
|
|
162
|
-
/** The version of a gate policy: `sha256:` and the hex digest of its text. A lexicon's helper stamps it, and OPS015 checks it still matches. */
|
|
163
|
-
export function gatePolicyVersion(text: string): string {
|
|
164
|
-
return `sha256:${createHash("sha256").update(text).digest("hex")}`;
|
|
165
|
-
}
|
|
166
164
|
|
|
167
165
|
/**
|
|
168
166
|
* The digest of a gate's resolved approval context (#3774): `sha256:` and the
|
|
@@ -180,17 +178,6 @@ function sortedKeys(_key: string, value: unknown): unknown {
|
|
|
180
178
|
return value;
|
|
181
179
|
}
|
|
182
180
|
|
|
183
|
-
export function isGatePolicyRef(value: unknown): value is GatePolicyRef {
|
|
184
|
-
if (typeof value !== "object" || value === null) return false;
|
|
185
|
-
const v = value as Record<string, unknown>;
|
|
186
|
-
return (
|
|
187
|
-
v.kind === "gate-policy" &&
|
|
188
|
-
typeof v.lexicon === "string" && v.lexicon !== "" &&
|
|
189
|
-
typeof v.name === "string" && v.name !== "" &&
|
|
190
|
-
typeof v.version === "string" && v.version !== "" &&
|
|
191
|
-
typeof v.text === "string" && v.text.trim() !== ""
|
|
192
|
-
);
|
|
193
|
-
}
|
|
194
181
|
|
|
195
182
|
/**
|
|
196
183
|
* Everything wrong with an `approval` block, one line each. The `gate()`
|
|
@@ -256,13 +243,15 @@ export function gateApprovalProblems(approval: unknown): string[] {
|
|
|
256
243
|
return problems;
|
|
257
244
|
}
|
|
258
245
|
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
246
|
+
/** A gate's `policy`, `mode` and `context` as `identity.gates.<gate>` declares them at base (#4404). A step output reference is not data a declaration can hold, so a context value is a plain one. */
|
|
247
|
+
export interface GateBaseApproval {
|
|
248
|
+
policy?: GatePolicyRef | null;
|
|
249
|
+
mode?: GateApprovalMode | null;
|
|
250
|
+
context?: Record<string, string | number | boolean | string[]> | null;
|
|
264
251
|
}
|
|
265
252
|
|
|
253
|
+
|
|
254
|
+
|
|
266
255
|
/** Import `lexicon`'s evaluator. Throws a message that names the package when it is not installed or exports none. */
|
|
267
256
|
export async function loadGatePolicyEvaluator(lexicon: string): Promise<GatePolicyEvaluator> {
|
|
268
257
|
// chant #2520 — a lexicon declared by module path exports its evaluator from that module.
|
|
@@ -282,17 +271,23 @@ export async function loadGatePolicyEvaluator(lexicon: string): Promise<GatePoli
|
|
|
282
271
|
return mod as GatePolicyEvaluator;
|
|
283
272
|
}
|
|
284
273
|
|
|
285
|
-
/**
|
|
274
|
+
/**
|
|
275
|
+
* The request a policy is asked about one approval. `roles` are the ones
|
|
276
|
+
* `.chant/trust.json` at base gives a proven approver (#4324), as a quorum
|
|
277
|
+
* judge reads them (`trustQuorumJudge` in `./gate-quorum.ts`). The roles the
|
|
278
|
+
* approver claims on `approver` are never read. Absent, the principal has none.
|
|
279
|
+
*/
|
|
286
280
|
export function gatePolicyRequest(input: {
|
|
287
281
|
op: string;
|
|
288
282
|
gate: string;
|
|
289
283
|
resolvedBy: string;
|
|
290
284
|
approver: GateApprover;
|
|
285
|
+
roles?: readonly string[];
|
|
291
286
|
planDigest?: string;
|
|
292
287
|
context?: Record<string, unknown>;
|
|
293
288
|
}): GatePolicyRequest {
|
|
294
289
|
return {
|
|
295
|
-
principal: { kind: input.approver.kind, name: input.resolvedBy, roles: input.
|
|
290
|
+
principal: { kind: input.approver.kind, name: input.resolvedBy, roles: [...(input.roles ?? [])] },
|
|
296
291
|
action: "PassGate",
|
|
297
292
|
resource: { op: input.op, gate: input.gate },
|
|
298
293
|
context: {
|
package/src/op/gate-name.ts
CHANGED
|
@@ -37,6 +37,21 @@ export const FAN_OUT_GATE_OP = "fan-out";
|
|
|
37
37
|
*/
|
|
38
38
|
export const WORKSPACE_UPGRADE_GATE_OP = "workspace-upgrade";
|
|
39
39
|
|
|
40
|
+
/**
|
|
41
|
+
* The op name a `chant workspace exit` gate is recorded under (#3177). The gate
|
|
42
|
+
* is the exit's scope (`workspace`, or the member names joined with `+`), and
|
|
43
|
+
* it binds the digest of the exit's patch. Like an upgrade it is a command
|
|
44
|
+
* with no `*.op.ts` behind it, and leaving is a person's decision.
|
|
45
|
+
*/
|
|
46
|
+
export const WORKSPACE_EXIT_GATE_OP = "workspace-exit";
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* The op name a `chant workspace promote` gate is recorded under (#3178). The
|
|
50
|
+
* gate is the member, and it binds the digest of the promotion's patch. A
|
|
51
|
+
* command with no `*.op.ts` behind it, like the upgrade.
|
|
52
|
+
*/
|
|
53
|
+
export const WORKSPACE_PROMOTE_GATE_OP = "workspace-promote";
|
|
54
|
+
|
|
40
55
|
/**
|
|
41
56
|
* The op name the override gate records denials and overrides under (#3760):
|
|
42
57
|
* `_gates/policy-override.jsonl`, gate the subject a policy denied. Like a
|
|
@@ -69,7 +84,7 @@ const PR_GATE_OP = /^pr-\d+(?:-[a-z0-9][a-z0-9-]*)?$/;
|
|
|
69
84
|
* no Op config to read `neverOverMcp` from, the pending record carries it.
|
|
70
85
|
*/
|
|
71
86
|
export function isNeverOverMcpGateOp(op: string): boolean {
|
|
72
|
-
return op === FAN_OUT_GATE_OP || op === WORKSPACE_UPGRADE_GATE_OP || op === OVERRIDE_GATE_OP || op === RELEASE_GATE_OP || PR_GATE_OP.test(op);
|
|
87
|
+
return op === FAN_OUT_GATE_OP || op === WORKSPACE_UPGRADE_GATE_OP || op === WORKSPACE_EXIT_GATE_OP || op === OVERRIDE_GATE_OP || op === RELEASE_GATE_OP || PR_GATE_OP.test(op);
|
|
73
88
|
}
|
|
74
89
|
|
|
75
90
|
/** Either spelling of a gate step's name. `gate` since #2202; `signalName` through 0.59.0. */
|
|
@@ -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
|
+
});
|