@intentius/chant 0.124.46 → 0.124.48
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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 +21 -2
- package/dist/cli/handlers/build.d.ts.map +1 -1
- package/dist/cli/handlers/build.js +6 -1
- 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 +143 -39
- 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 +65 -14
- package/dist/cli/parse-args.d.ts.map +1 -1
- package/dist/cli/parse-args.js +36 -2
- package/dist/cli/registry.d.ts +14 -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/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 +9 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +6 -2
- package/dist/gate-decision.d.ts +11 -2
- package/dist/gate-decision.d.ts.map +1 -1
- package/dist/gate-decision.js +15 -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 +71 -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 +5 -2
- 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.d.ts +11 -5
- package/dist/op/gate-approval.d.ts.map +1 -1
- package/dist/op/gate-approval.js +11 -6
- 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 +44 -3
- 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 +38 -3
- package/dist/op/local-executor.d.ts.map +1 -1
- package/dist/op/local-executor.js +48 -6
- 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-post.d.ts +42 -0
- package/dist/pr-post.d.ts.map +1 -1
- package/dist/pr-post.js +67 -1
- 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 +263 -254
- package/dist/workspace/check.schema.json +11 -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/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 +11 -1
- package/dist/workspace/checks.d.ts.map +1 -1
- package/dist/workspace/checks.js +59 -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 +43 -2
- package/dist/workspace/declaration.d.ts.map +1 -1
- package/dist/workspace/declaration.js +45 -11
- package/dist/workspace/declaration.schema.json +133 -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/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-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/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/kinds.d.ts +66 -2
- package/dist/workspace/kinds.d.ts.map +1 -1
- package/dist/workspace/kinds.js +98 -4
- 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/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/reason-codes.d.ts +8 -0
- package/dist/workspace/reason-codes.d.ts.map +1 -1
- package/dist/workspace/reason-codes.js +11 -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 +58 -3
- 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 +23 -2
- package/src/cli/handlers/build.ts +7 -1
- 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 +138 -36
- 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 +15 -0
- package/src/cli/main.ts +65 -14
- package/src/cli/parse-args.ts +27 -2
- package/src/cli/registry.ts +14 -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-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/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 +8 -1
- package/src/gate-decision-bundle.test.ts +1 -0
- package/src/gate-decision-identity.test.ts +9 -0
- package/src/gate-decision.ts +26 -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 +74 -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.test.ts +6 -1
- package/src/op/gate-approval.ts +12 -6
- 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 +67 -8
- package/src/op/index.ts +3 -2
- package/src/op/local-executor.ts +69 -9
- 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-post-open.e2e.test.ts +36 -0
- package/src/pr-post.test.ts +102 -3
- package/src/pr-post.ts +89 -1
- 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/behold-kinds.test.ts +1 -1
- package/src/workspace/check-contract.test.ts +7 -6
- package/src/workspace/check.schema.json +11 -2
- package/src/workspace/checks/ci-branch.test.ts +128 -0
- package/src/workspace/checks/ci-branch.ts +141 -0
- package/src/workspace/checks/links.ts +12 -1
- package/src/workspace/checks/org.ts +169 -0
- package/src/workspace/checks.ts +64 -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 +133 -24
- package/src/workspace/declaration.ts +84 -15
- package/src/workspace/environments.ts +40 -4
- package/src/workspace/flyway-dir.ts +113 -0
- package/src/workspace/flyway-layout.ts +337 -0
- 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 +14 -2
- package/src/workspace/intent.ts +6 -14
- package/src/workspace/kind-link-readers.ts +68 -3
- package/src/workspace/kinds.test.ts +12 -7
- package/src/workspace/kinds.ts +132 -7
- 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/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/org-scope.test.ts +238 -0
- package/src/workspace/reason-codes.test.ts +6 -1
- package/src/workspace/reason-codes.ts +11 -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 +58 -3
- 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
|
@@ -16,11 +16,13 @@
|
|
|
16
16
|
* component's members and the plans its steps hand the apply.
|
|
17
17
|
*/
|
|
18
18
|
|
|
19
|
-
import { mkdirSync, writeFileSync } from "node:fs";
|
|
19
|
+
import { mkdirSync, rmSync, writeFileSync } from "node:fs";
|
|
20
20
|
import { join, relative, resolve } from "node:path";
|
|
21
21
|
import type { OpWaveHook, OpWaveHookMoment, OpWaveHooks } from "../op/op-waves";
|
|
22
22
|
import { PR_LOOP_HOOK_MOMENTS } from "../op/op-waves";
|
|
23
|
-
import { runStageHooks, type OpWaveExec, type OpWaveHookRun, type StageHooksRun } from "../op/op-waves-run";
|
|
23
|
+
import { OP_WAVE_MAX_CHAINED_HOLDS, runStageHooks, type OpWaveExec, type OpWaveHookRun, type StageHooksRun } from "../op/op-waves-run";
|
|
24
|
+
import { readHoldRequest, spawnExec, type OpWaveHookOutcome, type OtherGateHold } from "../op/stage-hooks";
|
|
25
|
+
import { approveCommand } from "../op/gate";
|
|
24
26
|
import { readDeclaration, WorkspaceReadError, type Declaration } from "../workspace/declaration";
|
|
25
27
|
import { memberAtCwd } from "../workspace/security";
|
|
26
28
|
import { locateWorkspace } from "../workspace/which-chant";
|
|
@@ -101,6 +103,20 @@ export interface PrHookPlace {
|
|
|
101
103
|
log?: (line: string) => void;
|
|
102
104
|
}
|
|
103
105
|
|
|
106
|
+
/** How a gate a hook holds the job on stands (#4323). */
|
|
107
|
+
export interface PrHoldSettled {
|
|
108
|
+
status: "approved" | "waiting";
|
|
109
|
+
approvedBy?: string;
|
|
110
|
+
approvedAt?: string;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Settle a hold on another gate (#4323): an approval of `hold.request.gate`
|
|
115
|
+
* at `hold.request.digest`, else a pending fact for it. The pull-request
|
|
116
|
+
* jobs give this the ledger they decide their own gate by.
|
|
117
|
+
*/
|
|
118
|
+
export type PrHoldSettler = (hold: OtherGateHold) => Promise<PrHoldSettled>;
|
|
119
|
+
|
|
104
120
|
/** What one moment's hooks came to on the pull-request path. */
|
|
105
121
|
export interface PrHooksRun {
|
|
106
122
|
outcomes: PrHookOutcome[];
|
|
@@ -108,18 +124,27 @@ export interface PrHooksRun {
|
|
|
108
124
|
holds: string[];
|
|
109
125
|
}
|
|
110
126
|
|
|
127
|
+
/** The run's options: the gate's decision for `after-gate`, and who settles a hold on another gate. */
|
|
128
|
+
export interface PrHooksRunOptions {
|
|
129
|
+
/** Added to every hook's environment: `after-gate` carries `CHANT_GATE_STATUS`, `CHANT_GATE_OP`, `CHANT_GATE` and `CHANT_GATE_DIGEST`. */
|
|
130
|
+
env?: Record<string, string>;
|
|
131
|
+
/** Without it a hold on another gate is a plain hold. */
|
|
132
|
+
settle?: PrHoldSettler;
|
|
133
|
+
}
|
|
134
|
+
|
|
111
135
|
/**
|
|
112
136
|
* Run the hooks of `moment` for `components` (#4288). `plans`, after the
|
|
113
137
|
* plan, gives each component's members and step plans, written where a
|
|
114
138
|
* `perRun` hook's `{plan}` points.
|
|
115
139
|
*/
|
|
116
|
-
export function runPrHooks(
|
|
140
|
+
export async function runPrHooks(
|
|
117
141
|
read: PrLoopHooks,
|
|
118
142
|
moment: OpWaveHookMoment,
|
|
119
143
|
place: PrHookPlace,
|
|
120
144
|
components: readonly string[],
|
|
121
145
|
planned?: { members: readonly PrMember[]; plans: Record<string, Record<string, unknown>> },
|
|
122
|
-
|
|
146
|
+
options: PrHooksRunOptions = {},
|
|
147
|
+
): Promise<PrHooksRun> {
|
|
123
148
|
const hooks = read.hooks[moment];
|
|
124
149
|
if (!hooks || hooks.length === 0) return { outcomes: [], holds: [] };
|
|
125
150
|
// Relative to cwd, as an Op waves job's paths are, whether --output was given relative or absolute.
|
|
@@ -141,11 +166,18 @@ export function runPrHooks(
|
|
|
141
166
|
runs,
|
|
142
167
|
runsFile,
|
|
143
168
|
holdFile: join(dir, "hold.json"),
|
|
144
|
-
env: { CHANT_ENV: place.env, CHANT_PR_STAGE: place.stage, CHANT_HOOKS_BASE: read.base },
|
|
169
|
+
env: { CHANT_ENV: place.env, CHANT_PR_STAGE: place.stage, CHANT_HOOKS_BASE: read.base, ...options.env },
|
|
170
|
+
keepOtherGates: options.settle !== undefined,
|
|
145
171
|
cwd: place.cwd,
|
|
146
172
|
...(place.exec ? { exec: place.exec } : {}),
|
|
147
173
|
...(place.log ? { log: place.log } : {}),
|
|
148
174
|
});
|
|
175
|
+
const log = place.log ?? ((line: string) => console.error(line));
|
|
176
|
+
const settled = ran.failed || !options.settle ? undefined : await settleHolds(ran.otherGates ?? [], options.settle, place, log);
|
|
177
|
+
if (settled) {
|
|
178
|
+
ran.outcomes.push(...settled.outcomes);
|
|
179
|
+
if (settled.failed) ran.failed = settled.failed;
|
|
180
|
+
}
|
|
149
181
|
const outcomes = ran.outcomes.map((o): PrHookOutcome => ({
|
|
150
182
|
moment: o.moment,
|
|
151
183
|
name: o.name,
|
|
@@ -155,3 +187,61 @@ export function runPrHooks(
|
|
|
155
187
|
}));
|
|
156
188
|
return { outcomes, ...(ran.failed ? { failed: ran.failed } : {}), holds: ran.holds };
|
|
157
189
|
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Settle each hook call that held the job on another gate (#4323), as an Op
|
|
193
|
+
* waves job does (`runHooks` in op-waves-run.ts): once the gate is approved
|
|
194
|
+
* the hook runs again with `CHANT_HOLD_APPROVED` (and `CHANT_HOLDS_APPROVED`,
|
|
195
|
+
* the gates approved so far), and it may hold on a further gate, settled in
|
|
196
|
+
* turn, up to {@link OP_WAVE_MAX_CHAINED_HOLDS}. A gate nobody approved fails
|
|
197
|
+
* the stage, with the command that approves it.
|
|
198
|
+
*/
|
|
199
|
+
async function settleHolds(holds: readonly OtherGateHold[], settle: PrHoldSettler, place: PrHookPlace, log: (line: string) => void): Promise<{ outcomes: OpWaveHookOutcome[]; failed?: string }> {
|
|
200
|
+
const outcomes: OpWaveHookOutcome[] = [];
|
|
201
|
+
const exec = place.exec ?? spawnExec;
|
|
202
|
+
for (const first of holds) {
|
|
203
|
+
let hold = first;
|
|
204
|
+
const approved: Array<{ op: string; gate: string; digest: string; approvedBy: string; approvedAt: string }> = [];
|
|
205
|
+
for (;;) {
|
|
206
|
+
const op = hold.request.op ?? place.op;
|
|
207
|
+
const held = await settle(hold);
|
|
208
|
+
if (held.status === "waiting") {
|
|
209
|
+
return { outcomes, failed: `${hold.label} waits on ${op} / ${hold.request.gate}, which nobody approved at ${hold.request.digest}. Approve: ${approveCommand(op, hold.request.gate, undefined, hold.request.digest)}` };
|
|
210
|
+
}
|
|
211
|
+
const now = { op, gate: hold.request.gate, digest: hold.request.digest, approvedBy: held.approvedBy ?? "", approvedAt: held.approvedAt ?? "" };
|
|
212
|
+
approved.push(now);
|
|
213
|
+
const holdFile = join(place.cwd, hold.env.CHANT_HOLD!);
|
|
214
|
+
rmSync(holdFile, { force: true });
|
|
215
|
+
let code: number;
|
|
216
|
+
try {
|
|
217
|
+
code = exec(hold.argv, place.cwd, { ...hold.env, CHANT_HOLD_APPROVED: JSON.stringify(now), CHANT_HOLDS_APPROVED: JSON.stringify(approved) });
|
|
218
|
+
} catch (err) {
|
|
219
|
+
log(`${hold.label} did not run: ${err instanceof Error ? err.message : String(err)}`);
|
|
220
|
+
code = 127;
|
|
221
|
+
}
|
|
222
|
+
const target = hold.target ? { target: hold.target } : {};
|
|
223
|
+
if (code === 0) {
|
|
224
|
+
outcomes.push({ moment: hold.moment, name: hold.name, ...target, exit: code, outcome: "passed" });
|
|
225
|
+
log(`${hold.label}: ${now.approvedBy} approved ${op} / ${now.gate} at ${now.digest}, and the hook ran again and passed`);
|
|
226
|
+
break;
|
|
227
|
+
}
|
|
228
|
+
const next = readHoldRequest(holdFile, log, hold.label);
|
|
229
|
+
const nextOp = next ? (next.op ?? place.op) : "";
|
|
230
|
+
const problem = !next
|
|
231
|
+
? `${hold.label} exited ${code} after ${now.approvedBy} approved ${now.gate}`
|
|
232
|
+
: approved.some((a) => a.op === nextOp && a.gate === next.gate && a.digest === next.digest)
|
|
233
|
+
? `${hold.label} exited ${code} after ${now.approvedBy} approved ${now.gate}, and holds again on ${nextOp} / ${next.gate} at ${next.digest}, which is already approved`
|
|
234
|
+
: approved.length >= OP_WAVE_MAX_CHAINED_HOLDS
|
|
235
|
+
? `${hold.label} held the job on ${approved.length} gates in turn and asks for one more; a hook call holds on at most ${OP_WAVE_MAX_CHAINED_HOLDS}`
|
|
236
|
+
: undefined;
|
|
237
|
+
if (problem) {
|
|
238
|
+
outcomes.push({ moment: hold.moment, name: hold.name, ...target, exit: code, outcome: "failed" });
|
|
239
|
+
log(problem);
|
|
240
|
+
return { outcomes, failed: problem };
|
|
241
|
+
}
|
|
242
|
+
outcomes.push({ moment: hold.moment, name: hold.name, ...target, exit: code, outcome: "held" });
|
|
243
|
+
hold = { ...hold, request: next! };
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
return { outcomes };
|
|
247
|
+
}
|
|
@@ -73,6 +73,10 @@
|
|
|
73
73
|
* forge's run as its `runOrigin`. `ci run` runs `command` in the job's own
|
|
74
74
|
* checkout with the job's environment, less the key. The plan passes
|
|
75
75
|
* `--no-push`, so it still writes nothing to the remote `chant/lifecycle`.
|
|
76
|
+
* The plan job gets no `CHANT_RUNNER_KEY` (#4328): it runs the pull request's
|
|
77
|
+
* code, and a key in the job's environment is one that code could read and
|
|
78
|
+
* sign with, whatever `ci run` leaves out of its steps. Its record is
|
|
79
|
+
* unsigned.
|
|
76
80
|
*
|
|
77
81
|
* In a workspace member (#3465) both commands run in the member's directory
|
|
78
82
|
* and pass `--member <name>`, so the member's gate (`pr-<number>-<member>`),
|
|
@@ -211,6 +215,14 @@ export interface PrLoopPipelineOptions {
|
|
|
211
215
|
* ({@link prEnvJobs}). Set from `--pr-env` beside `--pr-loop`. Default false.
|
|
212
216
|
*/
|
|
213
217
|
prEnvironment?: boolean;
|
|
218
|
+
/**
|
|
219
|
+
* `ci.locks: plan` (#4293, #4327): a `lock` job after the plan that runs
|
|
220
|
+
* `chant lock --from-report`, and an `unlock` job when the pull request
|
|
221
|
+
* closes that runs `chant unlock`. Both check out the target branch's
|
|
222
|
+
* commit, so neither runs the change's code. Set from the declaration at
|
|
223
|
+
* base; GitHub and Forgejo only. Absent: neither job.
|
|
224
|
+
*/
|
|
225
|
+
locks?: "plan";
|
|
214
226
|
/**
|
|
215
227
|
* The cloud identity a pull request environment's jobs take (#4330), from
|
|
216
228
|
* `ci.identity.cloud` (`resolvePrEnvCloudIdentity` in ../ci-identity.ts):
|
|
@@ -222,6 +234,18 @@ export interface PrLoopPipelineOptions {
|
|
|
222
234
|
prEnvIdentity?: CloudTradeSpec;
|
|
223
235
|
}
|
|
224
236
|
|
|
237
|
+
/** The lock job's step names and commands (#4327), shared by the GitHub-shaped generators. */
|
|
238
|
+
export const PR_LOCK_JOB = "lock";
|
|
239
|
+
export const PR_UNLOCK_JOB = "unlock";
|
|
240
|
+
|
|
241
|
+
/** `chant lock --from-report` for the plan's report in {@link PR_LOOP_REPORT_DIR}; reads `PR_NUMBER` and `HEAD_SHA`. */
|
|
242
|
+
export function prLockCommand(forge: ForgeKind): string {
|
|
243
|
+
return ["chant", "lock", "--from-report", `${PR_LOOP_REPORT_DIR}/pr-plan.json`, "--pr", '"$PR_NUMBER"', "--head", '"$HEAD_SHA"', "--stage", "plan", "--forge", forge].join(" ");
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/** `chant unlock` for the pull request that closed; reads `PR_NUMBER`. */
|
|
247
|
+
export const PR_UNLOCK_COMMAND = 'chant unlock --pr "$PR_NUMBER"';
|
|
248
|
+
|
|
225
249
|
/** One job's command and the forge values it reads, as environment variables for its step. */
|
|
226
250
|
export interface PrLoopJob {
|
|
227
251
|
/** `plan`, `apply` or `record` (#4168). */
|
|
@@ -258,8 +282,10 @@ export interface PrLoopJob {
|
|
|
258
282
|
* <job> --in-place` with the same environment, gate, forge, events and
|
|
259
283
|
* report URL, which runs `command` and records the run. `env` is the
|
|
260
284
|
* job's `env` and, on GitHub and Forgejo, `CHANT_RUNNER_KEY` from the
|
|
261
|
-
* repository secret of that name
|
|
262
|
-
*
|
|
285
|
+
* repository secret of that name, except for the plan, which runs the
|
|
286
|
+
* pull request's code (#4328). GitLab hands its CI/CD variables to a job
|
|
287
|
+
* itself, so there the key is a protected variable, which a merge request
|
|
288
|
+
* pipeline never gets.
|
|
263
289
|
*/
|
|
264
290
|
run: { command: string; env: Record<string, string> };
|
|
265
291
|
}
|
|
@@ -354,7 +380,8 @@ export function prLoopJobs(forge: ForgeKind, env: string, options: PrLoopPipelin
|
|
|
354
380
|
command: ["chant", "components", "pr-plan", "--base", '"$BASE_SHA"', "--pr", '"$PR_NUMBER"', ...common, ...(options.waveGate ? ["--wave-gate", options.waveGate] : []), "--no-status"].join(" "),
|
|
355
381
|
env: planEnv,
|
|
356
382
|
...build,
|
|
357
|
-
|
|
383
|
+
// #4328: no runner key either. The step drops it, but the job's environment is the pull request's code's too.
|
|
384
|
+
run: { command: ciRun("plan").join(" "), env: planEnv },
|
|
358
385
|
},
|
|
359
386
|
early ? earlyApply : greenApply,
|
|
360
387
|
{
|
|
@@ -372,31 +399,52 @@ export function prLoopJobs(forge: ForgeKind, env: string, options: PrLoopPipelin
|
|
|
372
399
|
export const PR_PLAN_NOTE_JOB = "plan-note";
|
|
373
400
|
|
|
374
401
|
/**
|
|
375
|
-
* The plan-note job's command and the values it reads (#4295).
|
|
376
|
-
* fresh job after the plan, at the target branch's
|
|
377
|
-
* note and the `chant/plan` status from the report
|
|
378
|
-
* with the forge token the plan job never held.
|
|
379
|
-
* the plan job checked out, `PR_NUMBER` the pull
|
|
380
|
-
*
|
|
381
|
-
* the plan job failed.
|
|
382
|
-
*
|
|
402
|
+
* The plan-note job's command and the values it reads (#4295). On GitHub
|
|
403
|
+
* and Forgejo it runs in a fresh job after the plan, at the target branch's
|
|
404
|
+
* commit, and posts the note and the `chant/plan` status from the report
|
|
405
|
+
* the plan job handed on, with the forge token the plan job never held.
|
|
406
|
+
* `HEAD_SHA` is the commit the plan job checked out, `PR_NUMBER` the pull
|
|
407
|
+
* request, and `PLAN_RESULT` the plan job's result, which fails the status
|
|
408
|
+
* when the plan job failed.
|
|
409
|
+
*
|
|
410
|
+
* On GitLab (#4328) it runs `pr-post --open` in a pipeline of the target
|
|
411
|
+
* branch, which GitLab builds from that branch's files and gives the
|
|
412
|
+
* protected `CHANT_FORGE_TOKEN`. A merge request pipeline is built from the
|
|
413
|
+
* merge request's files, so no job in it holds the token. `pr-post --open`
|
|
414
|
+
* asks GitLab for each open merge request's plan job and reads its report
|
|
415
|
+
* from the job's artifacts.
|
|
383
416
|
*/
|
|
384
417
|
export function prLoopPostJob(forge: ForgeKind, env: string, member?: string): { command: string; env: Record<string, string> } {
|
|
385
|
-
|
|
418
|
+
if (forge === "gitlab") {
|
|
419
|
+
return {
|
|
420
|
+
command: ["chant", "components", "pr-post", "--open", "--env", env, "--output", PR_LOOP_REPORT_DIR, "--forge", forge, ...(member ? ["--member", member] : [])].join(" "),
|
|
421
|
+
env: {},
|
|
422
|
+
};
|
|
423
|
+
}
|
|
386
424
|
const command = [
|
|
387
425
|
"chant", "components", "pr-post", "--pr", '"$PR_NUMBER"', "--head", '"$HEAD_SHA"',
|
|
388
426
|
"--env", env, "--output", PR_LOOP_REPORT_DIR, "--forge", forge,
|
|
389
427
|
...(member ? ["--member", member] : []),
|
|
390
|
-
|
|
428
|
+
"--result", '"$PLAN_RESULT"',
|
|
391
429
|
].join(" ");
|
|
392
430
|
return {
|
|
393
431
|
command,
|
|
394
|
-
env: github
|
|
395
|
-
? { PR_NUMBER: "${{ github.event.pull_request.number }}", HEAD_SHA: "${{ github.sha }}", PLAN_RESULT: "${{ needs.plan.result }}", GITHUB_TOKEN: GITHUB_JOB_TOKEN }
|
|
396
|
-
: { PR_NUMBER: "$CI_MERGE_REQUEST_IID", HEAD_SHA: "$CI_COMMIT_SHA", BASE_SHA: "$CI_MERGE_REQUEST_DIFF_BASE_SHA" },
|
|
432
|
+
env: { PR_NUMBER: "${{ github.event.pull_request.number }}", HEAD_SHA: "${{ github.sha }}", PLAN_RESULT: "${{ needs.plan.result }}", GITHUB_TOKEN: GITHUB_JOB_TOKEN },
|
|
397
433
|
};
|
|
398
434
|
}
|
|
399
435
|
|
|
436
|
+
/** The plan job's name in a GitLab merge request pipeline (#3465, #4328): `plan`, or `<member>-plan` in a workspace member's included file. */
|
|
437
|
+
export function gitlabPlanJobName(member?: string): string {
|
|
438
|
+
return member ? `${member}-plan` : "plan";
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
/**
|
|
442
|
+
* The GitLab variable a pipeline schedule (or a person starting a pipeline
|
|
443
|
+
* of the target branch) sets to `1` to post the open merge requests' plan
|
|
444
|
+
* notes (#4328).
|
|
445
|
+
*/
|
|
446
|
+
export const GITLAB_PLAN_NOTES_VAR = "CHANT_PLAN_NOTES";
|
|
447
|
+
|
|
400
448
|
/**
|
|
401
449
|
* What starts the apply under apply on merge (#4168 step 9): on GitHub the
|
|
402
450
|
* tick's workflow finishing (`workflow_run`, since tags its token pushes start
|
|
@@ -14,7 +14,9 @@ import { composeChangeSet } from "../change-set";
|
|
|
14
14
|
import { memoryGateLedgerPort } from "../op/gate";
|
|
15
15
|
import { overrideDigest } from "../override-gate";
|
|
16
16
|
import type { GatePolicyExec } from "../gate-policy";
|
|
17
|
-
import {
|
|
17
|
+
import type { FoundComment, MarkerMatch, PrForge } from "../pr-forge";
|
|
18
|
+
import { reviewMarker, reviewNoteBody } from "../op/activities/review-agent";
|
|
19
|
+
import { decidePrPolicyOverride, policyReviewOf, prLoopPolicyAt, prPolicyReport, REVIEW_NOTE_AUTHORS, runPrPolicy, spendPrPolicyOverride, type PrPolicyInput } from "./pr-policy";
|
|
18
20
|
|
|
19
21
|
const scratch: string[] = [];
|
|
20
22
|
afterAll(() => {
|
|
@@ -143,3 +145,64 @@ describe("a denial and its override (#4294)", () => {
|
|
|
143
145
|
await expect(spendPrPolicyOverride(port, "pr-7", { by: "github:carol", at: "2026-10-22T12:00:00Z", reason: "ok", digest: "sha256:1" })).rejects.toThrow(/did not reach the remote/);
|
|
144
146
|
});
|
|
145
147
|
});
|
|
148
|
+
|
|
149
|
+
describe("input.review: a policy reads the review's risk (#4326)", () => {
|
|
150
|
+
const HEAD = "a".repeat(40);
|
|
151
|
+
const noteBody = (risk: "low" | "high", head = HEAD) =>
|
|
152
|
+
`${reviewMarker("pr-review")}\n\n${reviewNoteBody({ review: `Replaces the bucket.\n\nrisk: ${risk}\n`, exitCode: 0, digest: "sha256:0123456789ab", instructions: "base", head })}`;
|
|
153
|
+
|
|
154
|
+
/** A forge whose comments are `notes`; findComment applies the marker and author rules the real clients do. */
|
|
155
|
+
function forgeWith(notes: Array<{ body: string; author: string }>): PrForge {
|
|
156
|
+
return {
|
|
157
|
+
async findComment(_pr: number, marker: string, opts: MarkerMatch = {}): Promise<FoundComment | undefined> {
|
|
158
|
+
const want = typeof opts.author === "string" ? [opts.author] : (opts.author ?? []);
|
|
159
|
+
const hit = notes.find((n) => n.body.startsWith(marker) && (!opts.author || want.map((w) => w.toLowerCase()).includes(n.author.toLowerCase())));
|
|
160
|
+
return hit ? { id: 1, body: hit.body } : undefined;
|
|
161
|
+
},
|
|
162
|
+
} as unknown as PrForge;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
test("a note the run's token wrote gives the risk and the head it read", async () => {
|
|
166
|
+
const forge = forgeWith([{ body: noteBody("high"), author: "github-actions[bot]" }]);
|
|
167
|
+
expect(await policyReviewOf(forge, 7)).toEqual({ found: true, risk: "high", head: HEAD });
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
test("a note a person wrote, even with the marker and a low risk, is not the review", async () => {
|
|
171
|
+
const forge = forgeWith([{ body: noteBody("low"), author: "mallory" }]);
|
|
172
|
+
expect(await policyReviewOf(forge, 7)).toMatchObject({ found: false, risk: "unknown", head: null });
|
|
173
|
+
expect(REVIEW_NOTE_AUTHORS).not.toContain("mallory");
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
test("no pull request, no forge and a forge that fails all read as no review, with the reason", async () => {
|
|
177
|
+
expect(await policyReviewOf(forgeWith([]), 7)).toMatchObject({ found: false, reason: expect.stringContaining("no review note") });
|
|
178
|
+
expect(await policyReviewOf(undefined, 7)).toMatchObject({ found: false });
|
|
179
|
+
expect(await policyReviewOf(forgeWith([]), null)).toMatchObject({ found: false });
|
|
180
|
+
const broken = { findComment: async () => { throw new Error("503"); } } as unknown as PrForge;
|
|
181
|
+
expect(await policyReviewOf(broken, 7)).toMatchObject({ found: false, reason: expect.stringContaining("503") });
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
test("a note whose command failed names the risk unknown", async () => {
|
|
185
|
+
const failed = `${reviewMarker("pr-review")}\n\n${reviewNoteBody({ review: "risk: low", exitCode: 2, digest: "sha256:01", instructions: "base", head: HEAD })}`;
|
|
186
|
+
expect(await policyReviewOf(forgeWith([{ body: failed, author: "gitea-actions" }]), 7)).toEqual({ found: true, risk: "unknown", head: HEAD });
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
// A stand-in for `deny[msg] { input.review.risk == "high" }`: it reads the input file the engine was handed, as opa does.
|
|
190
|
+
const denyHighRisk: GatePolicyExec = (argv) => {
|
|
191
|
+
const fed = JSON.parse(readFileSync(argv[argv.indexOf("--input") + 1]!, "utf-8")) as PrPolicyInput;
|
|
192
|
+
const deny = fed.review?.risk === "high" ? ["the review rated this plan high risk"] : [];
|
|
193
|
+
return { status: 0, stdout: opaOut({ deny }), stderr: "" };
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
test("a policy that denies on high risk denies a plan the review rated high, and passes one rated low", async () => {
|
|
197
|
+
const { repo, base } = repoWithPolicy();
|
|
198
|
+
const read = prLoopPolicyAt(join(repo, "infra"), base);
|
|
199
|
+
const high = await policyReviewOf(forgeWith([{ body: noteBody("high"), author: "github-actions[bot]" }]), 7);
|
|
200
|
+
const low = await policyReviewOf(forgeWith([{ body: noteBody("low"), author: "github-actions[bot]" }]), 7);
|
|
201
|
+
const denied = runPrPolicy(read, { ...input(base), review: high }, denyHighRisk)!;
|
|
202
|
+
expect(denied.result).toBe("denied");
|
|
203
|
+
expect(denied.denials).toEqual([{ rule: "main.deny", message: "the review rated this plan high risk" }]);
|
|
204
|
+
expect(runPrPolicy(read, { ...input(base), review: low }, denyHighRisk)!.result).toBe("passed");
|
|
205
|
+
// A plan nobody reviewed is not high risk; a policy that wants a review denies on `found` itself.
|
|
206
|
+
expect(runPrPolicy(read, { ...input(base), review: await policyReviewOf(forgeWith([]), 7) }, denyHighRisk)!.result).toBe("passed");
|
|
207
|
+
});
|
|
208
|
+
});
|
|
@@ -20,6 +20,9 @@
|
|
|
20
20
|
* no longer applies under the approvals of the merges it carries.
|
|
21
21
|
* - error: the policy did not run or gave no readable answer. pr-apply
|
|
22
22
|
* applies nothing.
|
|
23
|
+
*
|
|
24
|
+
* A policy also reads `input.review` (#4326): the risk the pull request's
|
|
25
|
+
* review note names, when the review agent posted one ({@link policyReviewOf}).
|
|
23
26
|
*/
|
|
24
27
|
|
|
25
28
|
import { readDeclaration, WorkspaceReadError } from "../workspace/declaration";
|
|
@@ -36,6 +39,8 @@ import {
|
|
|
36
39
|
import type { ChangeSetDocument } from "../change-set";
|
|
37
40
|
import type { GateLedgerPort } from "../op/gate";
|
|
38
41
|
import type { PrPolicyReport } from "../pr-loop";
|
|
42
|
+
import type { PrForge } from "../pr-forge";
|
|
43
|
+
import { parseReviewResult, REVIEW_MARKER_PREFIX, type ReviewRisk } from "../op/activities/review-agent";
|
|
39
44
|
|
|
40
45
|
/** `ci.policy` as read at base. */
|
|
41
46
|
export interface PrLoopPolicy {
|
|
@@ -92,6 +97,54 @@ export interface PrPolicyInput {
|
|
|
92
97
|
head: string;
|
|
93
98
|
/** The change-set document the gate binds. Its `entries` are every change the plan proposes. */
|
|
94
99
|
changeSet: ChangeSetDocument;
|
|
100
|
+
/** What the pull request's review note says (#4326). Absent only when the caller did not look. */
|
|
101
|
+
review?: PolicyReview;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* The review agent's finding as a policy reads it, `input.review`. The risk is
|
|
106
|
+
* the model's, so a policy treats it as one signal beside the change set, never
|
|
107
|
+
* as a verdict. `head` is the commit the review read; a policy that cares
|
|
108
|
+
* whether the review is current compares it with `input.head`.
|
|
109
|
+
*/
|
|
110
|
+
export interface PolicyReview {
|
|
111
|
+
/** Whether a review note written by the run's own token was found on the pull request. */
|
|
112
|
+
found: boolean;
|
|
113
|
+
/** The risk the review named, or `unknown` when none was found, the command failed or the review gave none. */
|
|
114
|
+
risk: ReviewRisk | "unknown";
|
|
115
|
+
/** The commit the review read, or null when none was found. */
|
|
116
|
+
head: string | null;
|
|
117
|
+
/** Why nothing was found, for the log. Absent when a review was. */
|
|
118
|
+
reason?: string;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* The users the run's own token acts as on GitHub and Forgejo. Only a note one
|
|
123
|
+
* of them wrote counts as the review: a pull request's author can post a
|
|
124
|
+
* comment that starts with the marker, and cannot post as these.
|
|
125
|
+
*/
|
|
126
|
+
export const REVIEW_NOTE_AUTHORS: readonly string[] = ["github-actions[bot]", "gitea-actions", "forgejo-actions"];
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* The review of pull request `pr`, read from the sticky note the review job
|
|
130
|
+
* posted (`postReviewNote`, ../op/activities/review-agent.ts). A forge that
|
|
131
|
+
* cannot be read, a pull request that is not known and a note nobody trusted
|
|
132
|
+
* all give `found: false`, which a policy sees as `risk: "unknown"`.
|
|
133
|
+
*/
|
|
134
|
+
export async function policyReviewOf(forge: PrForge | undefined, pr: number | null): Promise<PolicyReview> {
|
|
135
|
+
const none = (reason: string): PolicyReview => ({ found: false, risk: "unknown", head: null, reason });
|
|
136
|
+
if (!forge) return none("no forge client, so no pull request note was read");
|
|
137
|
+
if (pr === null) return none("no pull request to read a review from");
|
|
138
|
+
let note;
|
|
139
|
+
try {
|
|
140
|
+
note = await forge.findComment(pr, REVIEW_MARKER_PREFIX, { author: REVIEW_NOTE_AUTHORS });
|
|
141
|
+
} catch (err) {
|
|
142
|
+
return none(`could not read the notes of #${pr}: ${err instanceof Error ? err.message : String(err)}`);
|
|
143
|
+
}
|
|
144
|
+
if (!note) return none(`#${pr} has no review note written by the run's token`);
|
|
145
|
+
const result = parseReviewResult(note.body);
|
|
146
|
+
if (!result) return none(`the review note on #${pr} names no head and risk`);
|
|
147
|
+
return { found: true, risk: result.risk, head: result.head };
|
|
95
148
|
}
|
|
96
149
|
|
|
97
150
|
/** Run the policy over `input`. Undefined when the base sets no policy. */
|
|
@@ -99,7 +99,7 @@ describe("createCapabilityRegistry", () => {
|
|
|
99
99
|
// for them to live in instead.
|
|
100
100
|
test("family table lists exactly the agnostic starter families plus the Cloudflare apply leaves (#1293)", () => {
|
|
101
101
|
expect(Object.keys(STARTER_VERB_FAMILIES).sort()).toEqual(
|
|
102
|
-
["build", "cloudflare", "escapeHatch", "sbom", "secrets", "supplyChainPolicy", "supplyChainSecurity", "waitVerify"].sort(),
|
|
102
|
+
["build", "cloudflare", "escapeHatch", "publish", "sbom", "secrets", "supplyChainPolicy", "supplyChainSecurity", "waitVerify"].sort(),
|
|
103
103
|
);
|
|
104
104
|
});
|
|
105
105
|
|
|
@@ -32,6 +32,8 @@ import {
|
|
|
32
32
|
zipPackageCapability,
|
|
33
33
|
jvmBuildCapability,
|
|
34
34
|
generateSbomCapability,
|
|
35
|
+
publishImageCapability,
|
|
36
|
+
publishArtifactCapability,
|
|
35
37
|
signCapability,
|
|
36
38
|
attestProvenanceCapability,
|
|
37
39
|
verifyCapability,
|
|
@@ -62,6 +64,8 @@ export const STARTER_VERB_FAMILIES = {
|
|
|
62
64
|
// moved to the aws lexicon (#684) alongside the other AWS leaves; core's sbom
|
|
63
65
|
// family keeps only the agnostic, artifact-type-keyed `generate-sbom`.
|
|
64
66
|
sbom: ["generate-sbom"],
|
|
67
|
+
// `publish-image` is one agnostic verb (#3142, #3143); each cloud lexicon contributes its registry login as a backend (`CapabilityPlugin.registryBackends`).
|
|
68
|
+
publish: ["publish-image", "publish-artifact"],
|
|
65
69
|
supplyChainSecurity: ["sign", "attest-provenance", "verify"],
|
|
66
70
|
supplyChainPolicy: ["scan-vulnerabilities", "vuln-gate"],
|
|
67
71
|
// Only the *agnostic* wait/verify verbs are in the starter set; the
|
|
@@ -93,6 +97,8 @@ function starterCapabilities(): Array<Capability<never, unknown>> {
|
|
|
93
97
|
zipPackageCapability,
|
|
94
98
|
jvmBuildCapability,
|
|
95
99
|
generateSbomCapability,
|
|
100
|
+
publishImageCapability,
|
|
101
|
+
publishArtifactCapability,
|
|
96
102
|
signCapability,
|
|
97
103
|
attestProvenanceCapability,
|
|
98
104
|
verifyCapability,
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { mkdtempSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { createHash } from "node:crypto";
|
|
6
|
+
import {
|
|
7
|
+
createPublishArtifactCapability,
|
|
8
|
+
selectStorageBackend,
|
|
9
|
+
type ArtifactStorageBackend,
|
|
10
|
+
} from "./publish-artifact";
|
|
11
|
+
|
|
12
|
+
function fake(name: string, prefix: string) {
|
|
13
|
+
const uploads: Array<{ from: string; to: string }> = [];
|
|
14
|
+
const backend: ArtifactStorageBackend = {
|
|
15
|
+
name,
|
|
16
|
+
matches: (uri) => uri.startsWith(prefix),
|
|
17
|
+
upload: async (args) => void uploads.push(args),
|
|
18
|
+
};
|
|
19
|
+
return { backend, uploads };
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
describe("selectStorageBackend (#4408)", () => {
|
|
23
|
+
const s3 = fake("s3", "s3://").backend;
|
|
24
|
+
const gcs = fake("gcs", "gs://").backend;
|
|
25
|
+
it("prefers a named backend, then the matching one, then the only one", () => {
|
|
26
|
+
expect(selectStorageBackend([s3, gcs], "s3://b/k", "gcs")).toBe(gcs);
|
|
27
|
+
expect(selectStorageBackend([s3, gcs], "gs://b/k")).toBe(gcs);
|
|
28
|
+
expect(selectStorageBackend([s3], "weird://x")).toBe(s3);
|
|
29
|
+
});
|
|
30
|
+
it("refuses an unknown name, an ambiguous match and an undecidable pick", () => {
|
|
31
|
+
expect(() => selectStorageBackend([s3], "s3://b/k", "nope")).toThrow(/no storage backend "nope"/);
|
|
32
|
+
expect(() => selectStorageBackend([s3, fake("s3b", "s3://").backend], "s3://b/k")).toThrow(/matches several/);
|
|
33
|
+
expect(() => selectStorageBackend([s3, gcs], "weird://x")).toThrow(/cannot tell/);
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
describe("publish-artifact (core)", () => {
|
|
38
|
+
it("uploads through the backend the environment names and returns uri + digest", async () => {
|
|
39
|
+
const dir = mkdtempSync(join(tmpdir(), "chant-pa-core-"));
|
|
40
|
+
const file = join(dir, "app.jar");
|
|
41
|
+
writeFileSync(file, "JAR");
|
|
42
|
+
const a = fake("s3", "s3://");
|
|
43
|
+
const b = fake("gcs", "gs://");
|
|
44
|
+
const cap = createPublishArtifactCapability({ backends: [a.backend, b.backend] });
|
|
45
|
+
const out = await cap.run({ env: "dev", component: "c", vars: { publish: { backend: "gcs" } } } as never, { from: file, to: "gs://bk/jars/" });
|
|
46
|
+
expect(out).toEqual({ uri: "gs://bk/jars/app.jar", digest: `sha256:${createHash("sha256").update("JAR").digest("hex")}` });
|
|
47
|
+
expect(b.uploads).toEqual([{ from: file, to: "gs://bk/jars/app.jar" }]);
|
|
48
|
+
expect(a.uploads).toEqual([]);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it("withStorageBackends rebinds the verb", async () => {
|
|
52
|
+
const dir = mkdtempSync(join(tmpdir(), "chant-pa-core-"));
|
|
53
|
+
const file = join(dir, "x.zip");
|
|
54
|
+
writeFileSync(file, "Z");
|
|
55
|
+
const a = fake("s3", "s3://");
|
|
56
|
+
const cap = createPublishArtifactCapability().withStorageBackends([a.backend]) as ReturnType<typeof createPublishArtifactCapability>;
|
|
57
|
+
await cap.run({ env: "dev", component: "c" } as never, { from: file, to: "s3://b/x.zip" });
|
|
58
|
+
expect(a.uploads).toHaveLength(1);
|
|
59
|
+
});
|
|
60
|
+
});
|