@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
|
@@ -15,6 +15,8 @@ import { RECORD_FINDING_CODES } from "./checks/records";
|
|
|
15
15
|
import { DIAGRAM_FINDING_CODES } from "./checks/diagrams";
|
|
16
16
|
import { SECURITY_FINDING_CODES } from "./checks/security";
|
|
17
17
|
import { CI_WORKFLOW_FINDING_CODES } from "./ci-workflow";
|
|
18
|
+
import { CI_BRANCH_FINDING_CODES } from "./checks/ci-branch";
|
|
19
|
+
import { GATE_APPROVAL_FINDING_CODES } from "./checks/gate-approval";
|
|
18
20
|
import { WORKSPACE_ERROR_CODES } from "./declaration";
|
|
19
21
|
import { CHECK_CODES, CHECK_CONTRACT_VERSION, CHECK_ERROR_CODES, CHECK_OUTPUT_SCHEMA_ID, runChecks, type CheckDocument } from "./lineage-check";
|
|
20
22
|
|
|
@@ -40,8 +42,8 @@ describe("check output schema", () => {
|
|
|
40
42
|
test("lists exactly the codes the code can return", () => {
|
|
41
43
|
expect(schema.$defs.lockFinding.properties.code.enum).toEqual([...CHECK_CODES]);
|
|
42
44
|
expect(schema.$defs.failure.properties.error.properties.code.enum).toEqual([...CHECK_ERROR_CODES]);
|
|
43
|
-
expect(schema.$defs.diagnostic.properties.code.enum).toEqual([...WORKSPACE_ERROR_CODES, ...BOX_FINDING_CODES, ...RECORD_FINDING_CODES, ...DIAGRAM_FINDING_CODES, ...SECURITY_FINDING_CODES, ...CI_WORKFLOW_FINDING_CODES]);
|
|
44
|
-
expect(schema.$defs.suppressed.properties.code.enum).toEqual([...WORKSPACE_ERROR_CODES, ...BOX_FINDING_CODES, ...RECORD_FINDING_CODES, ...DIAGRAM_FINDING_CODES, ...SECURITY_FINDING_CODES, ...CI_WORKFLOW_FINDING_CODES]);
|
|
45
|
+
expect(schema.$defs.diagnostic.properties.code.enum).toEqual([...WORKSPACE_ERROR_CODES, ...BOX_FINDING_CODES, ...RECORD_FINDING_CODES, ...DIAGRAM_FINDING_CODES, ...SECURITY_FINDING_CODES, ...CI_WORKFLOW_FINDING_CODES, ...CI_BRANCH_FINDING_CODES, ...GATE_APPROVAL_FINDING_CODES]);
|
|
46
|
+
expect(schema.$defs.suppressed.properties.code.enum).toEqual([...WORKSPACE_ERROR_CODES, ...BOX_FINDING_CODES, ...RECORD_FINDING_CODES, ...DIAGRAM_FINDING_CODES, ...SECURITY_FINDING_CODES, ...CI_WORKFLOW_FINDING_CODES, ...CI_BRANCH_FINDING_CODES, ...GATE_APPROVAL_FINDING_CODES]);
|
|
45
47
|
});
|
|
46
48
|
});
|
|
47
49
|
|
|
@@ -49,13 +51,13 @@ describe("chant workspace check on the chant repo (#2557)", () => {
|
|
|
49
51
|
test("validates in the working tree and at HEAD", async () => {
|
|
50
52
|
const now = result(await runChecks(REPO));
|
|
51
53
|
expectValid(now);
|
|
52
|
-
expect(now.workspace).
|
|
54
|
+
expect(now.workspace).toMatchObject({ name: "chant", root: "." });
|
|
53
55
|
expect(now.declaration?.file).toBe("chant.workspace.json");
|
|
54
56
|
|
|
55
57
|
const head = result(await runChecks(join(REPO, "packages", "core"), "HEAD"));
|
|
56
58
|
expectValid(head);
|
|
57
59
|
expect(head.at).toBe(git(REPO, "rev-parse", "HEAD"));
|
|
58
|
-
expect(head.workspace).
|
|
60
|
+
expect(head.workspace).toMatchObject({ name: "chant", root: "." });
|
|
59
61
|
expect(head.declaration?.file).toBe("../../chant.workspace.json");
|
|
60
62
|
});
|
|
61
63
|
});
|
|
@@ -82,7 +84,7 @@ describe("chant workspace check on built workspaces", () => {
|
|
|
82
84
|
const doc = result(await runChecks(root));
|
|
83
85
|
expectValid(doc);
|
|
84
86
|
expect(doc.ok).toBe(false);
|
|
85
|
-
expect(doc.workspace).toEqual({ name: "acme", root: "." });
|
|
87
|
+
expect(doc.workspace).toEqual({ name: "acme", root: ".", repo: null });
|
|
86
88
|
expect(ruleIds(doc)).toEqual(["WSP004", "WSP003"]);
|
|
87
89
|
|
|
88
90
|
const tooOld = repo({ "chant.workspace.json": declaration([], { minReader: "999.0.0" }) });
|
|
@@ -93,7 +95,7 @@ describe("chant workspace check on built workspaces", () => {
|
|
|
93
95
|
] as const) {
|
|
94
96
|
const d = result(await runChecks(dir));
|
|
95
97
|
expectValid(d);
|
|
96
|
-
expect(d.workspace).toEqual({ name: null, root: "." });
|
|
98
|
+
expect(d.workspace).toEqual({ name: null, root: ".", repo: null });
|
|
97
99
|
expect(d.declaration?.diagnostics.map((x) => [x.ruleId, x.code])).toEqual([["WSP001", code]]);
|
|
98
100
|
}
|
|
99
101
|
});
|
|
@@ -65,6 +65,13 @@
|
|
|
65
65
|
],
|
|
66
66
|
"description": "The declared name, or null when the declaration can't be read."
|
|
67
67
|
},
|
|
68
|
+
"repo": {
|
|
69
|
+
"type": [
|
|
70
|
+
"string",
|
|
71
|
+
"null"
|
|
72
|
+
],
|
|
73
|
+
"description": "Added in contract 1 by #3168. The repository the document came from: the URL of the remote origin of the git repository that holds the workspace, with any user name or password removed, or null when there is no origin or the workspace is outside git. With name, root and the revision, it says which repository's workspace a document describes, so a reader that gathers several repositories' documents, such as an org workspace's repo members, tells them apart."
|
|
74
|
+
},
|
|
68
75
|
"root": {
|
|
69
76
|
"type": "string",
|
|
70
77
|
"description": "The workspace root relative to the git root, with / separators, \".\" for the git root itself. An absolute path outside a git repository."
|
|
@@ -341,7 +348,9 @@
|
|
|
341
348
|
"ci-workflow-ungated-env",
|
|
342
349
|
"ci-workflow-pipeline-missing",
|
|
343
350
|
"ci-workflow-pipeline-differs",
|
|
344
|
-
"ci-workflow-publish-missing"
|
|
351
|
+
"ci-workflow-publish-missing",
|
|
352
|
+
"ci-branch-route-differs",
|
|
353
|
+
"gate-approval-unbased"
|
|
345
354
|
],
|
|
346
355
|
"description": "For WSP001: why the declaration can't be read. For WSP121 to WSP127, the box finding (#2726, #2727, #2780, #2850). For WSP117, work-acceptance-unmet (#2772). For WSP118, flag-stale (#4024). For WSP131 to WSP133, the diagram finding (#2764)."
|
|
347
356
|
}
|
|
@@ -463,7 +472,9 @@
|
|
|
463
472
|
"ci-workflow-ungated-env",
|
|
464
473
|
"ci-workflow-pipeline-missing",
|
|
465
474
|
"ci-workflow-pipeline-differs",
|
|
466
|
-
"ci-workflow-publish-missing"
|
|
475
|
+
"ci-workflow-publish-missing",
|
|
476
|
+
"ci-branch-route-differs",
|
|
477
|
+
"gate-approval-unbased"
|
|
467
478
|
],
|
|
468
479
|
"description": "For WSP001: why the declaration can't be read. For WSP121 to WSP127, the box finding (#2726, #2727, #2780, #2850). For WSP117, work-acceptance-unmet (#2772). For WSP118, flag-stale (#4024). For WSP131 to WSP133, the diagram finding (#2764)."
|
|
469
480
|
},
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WSP152 (#4325): a member's generated pipeline targets the branch
|
|
3
|
+
* `ci.branches` routes the member to.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { execFileSync } from "node:child_process";
|
|
7
|
+
import { mkdirSync, mkdtempSync, realpathSync, rmSync, writeFileSync } from "node:fs";
|
|
8
|
+
import { tmpdir } from "node:os";
|
|
9
|
+
import { dirname, join } from "node:path";
|
|
10
|
+
import { afterAll, describe, expect, test } from "vitest";
|
|
11
|
+
import { runDeclarationChecks } from "../checks";
|
|
12
|
+
import { pipelineTargets, routeDifferences } from "./ci-branch";
|
|
13
|
+
import { REASONS } from "../reason-codes";
|
|
14
|
+
import { servedBranches } from "../ci-branches";
|
|
15
|
+
|
|
16
|
+
const scratch: string[] = [];
|
|
17
|
+
afterAll(() => {
|
|
18
|
+
for (const d of scratch) rmSync(d, { recursive: true, force: true });
|
|
19
|
+
});
|
|
20
|
+
function repo(files: Record<string, string>): string {
|
|
21
|
+
const root = realpathSync(mkdtempSync(join(tmpdir(), "chant-ci-branch-")));
|
|
22
|
+
scratch.push(root);
|
|
23
|
+
execFileSync("git", ["init", "-q"], { cwd: root });
|
|
24
|
+
for (const [path, text] of Object.entries(files)) {
|
|
25
|
+
mkdirSync(dirname(join(root, path)), { recursive: true });
|
|
26
|
+
writeFileSync(join(root, path), text);
|
|
27
|
+
}
|
|
28
|
+
return root;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** A generated pull-request pipeline, as chant writes it for a target `branch` (`--branch` is on the record and apply commands only). */
|
|
32
|
+
const pipeline = (branch: string | null, triggers: string[] = branch === null ? [] : [branch]): string =>
|
|
33
|
+
[
|
|
34
|
+
"on:",
|
|
35
|
+
" pull_request:",
|
|
36
|
+
...(triggers.length > 0 ? [` branches: [${triggers.join(", ")}]`] : []),
|
|
37
|
+
" push:",
|
|
38
|
+
...(triggers.length > 0 ? [` branches: [${triggers.join(", ")}]`] : []),
|
|
39
|
+
"jobs:",
|
|
40
|
+
" plan:",
|
|
41
|
+
" steps:",
|
|
42
|
+
" - run: chant ci run plan --in-place --no-push --env prod --gate pr-apply --forge github",
|
|
43
|
+
" record:",
|
|
44
|
+
" steps:",
|
|
45
|
+
` - run: chant ci run record --in-place --env prod --gate pr-apply --forge github${branch === null ? "" : ` --branch "${branch}"`}`,
|
|
46
|
+
"",
|
|
47
|
+
].join("\n");
|
|
48
|
+
|
|
49
|
+
const PIPELINE_PATH = ".github/workflows/chant-pr.app.prod.yml";
|
|
50
|
+
const record = JSON.stringify({ schema: 1, files: [{ path: PIPELINE_PATH, command: "chant build" }] });
|
|
51
|
+
const workspace = (branches: Record<string, string[]> | undefined, text: string, members = ["app"]): Record<string, string> => ({
|
|
52
|
+
"chant.workspace.json": JSON.stringify({ name: "acme", schema: 1, members: members.map((name) => ({ name, dir: name, kind: "other", because: name })), ...(branches ? { ci: { branches } } : {}) }),
|
|
53
|
+
...Object.fromEntries(members.map((m) => [`${m}/.chant/generated.json`, record])),
|
|
54
|
+
[PIPELINE_PATH]: text,
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
const found = async (root: string) =>
|
|
58
|
+
(await runDeclarationChecks(root, (f) => f, { gather: false })).diagnostics.filter((d) => d.ruleId === "WSP152").map((d) => [d.code, d.entity ?? null, d.severity]);
|
|
59
|
+
|
|
60
|
+
describe("what a pipeline targets", () => {
|
|
61
|
+
test("reads the --branch of each record and apply command, and the trigger branch lists", () => {
|
|
62
|
+
expect(pipelineTargets(pipeline("release"))).toEqual({
|
|
63
|
+
jobs: [{ line: 12, branch: "release" }],
|
|
64
|
+
triggers: [
|
|
65
|
+
{ trigger: "push", branches: ["release"] },
|
|
66
|
+
{ trigger: "pull_request", branches: ["release"] },
|
|
67
|
+
],
|
|
68
|
+
});
|
|
69
|
+
expect(pipelineTargets(pipeline(null)).jobs).toEqual([{ line: 10, branch: null }]);
|
|
70
|
+
expect(pipelineTargets("not: [valid").triggers).toEqual([]);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
test("a routed member's pipeline names its route in both places; an unrouted one does not name a reserved branch", () => {
|
|
74
|
+
const listed = ["release"];
|
|
75
|
+
expect(routeDifferences(pipelineTargets(pipeline("release")), "release", listed, "app")).toEqual([]);
|
|
76
|
+
expect(routeDifferences(pipelineTargets(pipeline("main")), "release", listed, "app")).toHaveLength(3);
|
|
77
|
+
expect(routeDifferences(pipelineTargets(pipeline(null)), "release", listed, "app")).toEqual([expect.stringContaining("the default branch")]);
|
|
78
|
+
expect(routeDifferences(pipelineTargets(pipeline("release", ["release", "main"])), "release", listed, "app")).toHaveLength(2);
|
|
79
|
+
expect(routeDifferences(pipelineTargets(pipeline("main")), null, listed, "app")).toEqual([]);
|
|
80
|
+
expect(routeDifferences(pipelineTargets(pipeline("release")), null, listed, "app")).toHaveLength(3);
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
describe("WSP152 over a workspace", () => {
|
|
85
|
+
test("a pipeline on the route passes, and a workspace with no ci.branches is not read", async () => {
|
|
86
|
+
expect(await found(repo(workspace({ release: ["app"] }, pipeline("release"))))).toEqual([]);
|
|
87
|
+
expect(await found(repo(workspace(undefined, pipeline("anything"))))).toEqual([]);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
test("a pipeline generated before the route changed fails", async () => {
|
|
91
|
+
const root = repo(workspace({ release: ["app"] }, pipeline("main")));
|
|
92
|
+
expect(await found(root)).toEqual([
|
|
93
|
+
["ci-branch-route-differs", "app", "error"],
|
|
94
|
+
["ci-branch-route-differs", "app", "error"],
|
|
95
|
+
["ci-branch-route-differs", "app", "error"],
|
|
96
|
+
]);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
test("an unrouted member's pipeline may not target a branch ci.branches reserves", async () => {
|
|
100
|
+
expect(await found(repo(workspace({ release: ["other"] }, pipeline("release"))))).toHaveLength(3);
|
|
101
|
+
expect(await found(repo(workspace({ release: ["other"] }, pipeline("main"))))).toEqual([]);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
test("the code is a documented reason code", () => {
|
|
105
|
+
expect(REASONS["ci-branch-route-differs"]).toMatch(/targets a branch other than/);
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
describe("the branches ci serve polls (#4325)", () => {
|
|
110
|
+
const branches = { release: ["prod-*"], staging: ["stg-*"] };
|
|
111
|
+
|
|
112
|
+
test("a routed member polls its route alone, and a branch that disagrees is refused", () => {
|
|
113
|
+
expect(servedBranches(branches, "prod-eu", "main", false)).toEqual({ served: ["release"], routed: ["release"] });
|
|
114
|
+
expect(servedBranches(branches, "prod-eu", "release", true).served).toEqual(["release"]);
|
|
115
|
+
expect(() => servedBranches(branches, "prod-eu", "main", true)).toThrow(/routes member prod-eu to release/);
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
test("an unrouted member polls the default branch, and not one reserved for other members", () => {
|
|
119
|
+
expect(servedBranches(branches, "docs", "main", false)).toEqual({ served: ["main"], routed: [] });
|
|
120
|
+
expect(() => servedBranches(branches, "docs", "release", true)).toThrow(/member docs is not one/);
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
test("with no member, the default branch and then each routed branch", () => {
|
|
124
|
+
expect(servedBranches(branches, undefined, "main", false)).toEqual({ served: ["main", "release", "staging"], routed: ["release", "staging"] });
|
|
125
|
+
expect(servedBranches(undefined, undefined, "main", false)).toEqual({ served: ["main"], routed: [] });
|
|
126
|
+
expect(servedBranches(branches, undefined, "release", true).served).toEqual(["release", "staging"]);
|
|
127
|
+
});
|
|
128
|
+
});
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WSP152 (#4325): a member's generated pull-request pipeline targets the
|
|
3
|
+
* branch `ci.branches` routes the member to.
|
|
4
|
+
*
|
|
5
|
+
* `chant build --pr-loop` refuses a branch that disagrees with the route
|
|
6
|
+
* (../member-pipeline.ts), but only when it writes the file. A route changed
|
|
7
|
+
* after the pipeline was generated, or a pipeline edited by hand, goes
|
|
8
|
+
* unnoticed until a push to the wrong branch applies nothing. This check
|
|
9
|
+
* reads the files each member's generated-file record lists and compares what
|
|
10
|
+
* they target with the route. It reads files and never runs them.
|
|
11
|
+
*
|
|
12
|
+
* A pipeline names its target in two places: the `--branch` its record and
|
|
13
|
+
* apply commands carry, and the `branches` of its `push` and `pull_request`
|
|
14
|
+
* triggers. A routed member's pipeline names the route in both. A member with
|
|
15
|
+
* no route may name a branch `ci.branches` does not list, and not one it
|
|
16
|
+
* lists, since that branch applies only the members its globs match.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import type { WorkspaceCheck, WorkspaceDiagnostic } from "../checks";
|
|
20
|
+
import type { Member } from "../declaration";
|
|
21
|
+
import { routedBranch } from "../ci-branches";
|
|
22
|
+
import { GENERATED_RECORD_FILE } from "../member-pipeline";
|
|
23
|
+
import { joinPath } from "../tree";
|
|
24
|
+
import { parseYAMLDocument } from "../../yaml";
|
|
25
|
+
import { read } from "./security";
|
|
26
|
+
|
|
27
|
+
export const WSP_CI_BRANCH_ROUTE = "WSP152";
|
|
28
|
+
|
|
29
|
+
/** The finding code WSP152 reports. */
|
|
30
|
+
export const CI_BRANCH_FINDING_CODES = ["ci-branch-route-differs"] as const;
|
|
31
|
+
|
|
32
|
+
const memberDir = (dir: string) => (dir === "." ? "" : dir);
|
|
33
|
+
|
|
34
|
+
/** A record or apply command of the pull-request loop: `ci run record|apply`, or `components pr-record|pr-apply`. */
|
|
35
|
+
const JOB = /\bci\s+run\s+(?:record|apply)\b|\bcomponents\s+pr-(?:record|apply)\b/;
|
|
36
|
+
const BRANCH_FLAG = /--branch(?:\s+|=)(?:"([^"]+)"|'([^']+)'|(\S+))/;
|
|
37
|
+
|
|
38
|
+
/** What a pipeline file says it targets. */
|
|
39
|
+
export interface PipelineTargets {
|
|
40
|
+
/** The `--branch` of each record or apply command, by line; null for a command with none. */
|
|
41
|
+
jobs: { line: number; branch: string | null }[];
|
|
42
|
+
/** The branches its `push` and `pull_request` triggers list, by trigger. Absent for a trigger with no list. */
|
|
43
|
+
triggers: { trigger: "push" | "pull_request"; branches: string[] }[];
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** The branches a pipeline file targets, read from its text. */
|
|
47
|
+
export function pipelineTargets(text: string): PipelineTargets {
|
|
48
|
+
const jobs: PipelineTargets["jobs"] = [];
|
|
49
|
+
text.split("\n").forEach((line, i) => {
|
|
50
|
+
if (!JOB.test(line)) return;
|
|
51
|
+
const m = BRANCH_FLAG.exec(line);
|
|
52
|
+
jobs.push({ line: i + 1, branch: m ? (m[1] ?? m[2] ?? m[3] ?? null) : null });
|
|
53
|
+
});
|
|
54
|
+
const triggers: PipelineTargets["triggers"] = [];
|
|
55
|
+
try {
|
|
56
|
+
const doc = parseYAMLDocument(text) as { on?: unknown; true?: unknown } | null;
|
|
57
|
+
// YAML 1.1 reads a bare `on` key as true.
|
|
58
|
+
const on = doc && typeof doc === "object" ? (doc.on ?? doc.true) : undefined;
|
|
59
|
+
if (on && typeof on === "object" && !Array.isArray(on)) {
|
|
60
|
+
for (const trigger of ["push", "pull_request"] as const) {
|
|
61
|
+
const t = (on as Record<string, unknown>)[trigger];
|
|
62
|
+
const branches = t && typeof t === "object" ? (t as { branches?: unknown }).branches : undefined;
|
|
63
|
+
if (Array.isArray(branches)) triggers.push({ trigger, branches: branches.filter((b): b is string => typeof b === "string") });
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
} catch {
|
|
67
|
+
// A file that is not YAML has no triggers to compare; its commands still are.
|
|
68
|
+
}
|
|
69
|
+
return { jobs, triggers };
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* What differs between `targets` and the branch `own` the member is routed to
|
|
74
|
+
* (null when it has no route), as sentences. `listed` is every branch
|
|
75
|
+
* `ci.branches` names.
|
|
76
|
+
*/
|
|
77
|
+
export function routeDifferences(targets: PipelineTargets, own: string | null, listed: readonly string[], member: string): string[] {
|
|
78
|
+
const out: string[] = [];
|
|
79
|
+
const said = (b: string | null): string => (b === null ? "the default branch (it passes no --branch)" : b);
|
|
80
|
+
for (const j of targets.jobs) {
|
|
81
|
+
if (own !== null && j.branch !== own) out.push(`line ${j.line} targets ${said(j.branch)}, and ci.branches routes member ${member} to ${own}`);
|
|
82
|
+
else if (own === null && j.branch !== null && listed.includes(j.branch)) out.push(`line ${j.line} targets ${j.branch}, which ci.branches reserves for the members its globs match, and member ${member} is not one`);
|
|
83
|
+
}
|
|
84
|
+
for (const t of targets.triggers) {
|
|
85
|
+
if (own !== null && !(t.branches.length === 1 && t.branches[0] === own)) out.push(`its ${t.trigger} trigger lists ${t.branches.join(", ") || "no branch"}, and ci.branches routes member ${member} to ${own}`);
|
|
86
|
+
else if (own === null) {
|
|
87
|
+
const reserved = t.branches.filter((b) => listed.includes(b));
|
|
88
|
+
if (reserved.length > 0) out.push(`its ${t.trigger} trigger lists ${reserved.join(", ")}, which ci.branches reserves for the members its globs match, and member ${member} is not one`);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return out;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/** The files a member's generated-file record lists, which are pipelines the check can read. */
|
|
95
|
+
function recordedFiles(ctx: Parameters<WorkspaceCheck["check"]>[0], member: Member): string[] {
|
|
96
|
+
const text = read(ctx.tree, joinPath(memberDir(member.dir), GENERATED_RECORD_FILE));
|
|
97
|
+
if (text === null) return [];
|
|
98
|
+
try {
|
|
99
|
+
const parsed = JSON.parse(text) as { files?: { path?: unknown }[] };
|
|
100
|
+
return (parsed.files ?? []).flatMap((f) => (typeof f.path === "string" && /\.ya?ml$/.test(f.path) ? [f.path] : []));
|
|
101
|
+
} catch {
|
|
102
|
+
return [];
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export const CI_BRANCH_CHECKS: readonly WorkspaceCheck[] = [
|
|
107
|
+
{
|
|
108
|
+
id: WSP_CI_BRANCH_ROUTE,
|
|
109
|
+
name: "ci-branch-route",
|
|
110
|
+
description: "A member's generated pull-request pipeline targets the branch ci.branches routes the member to, in its record and apply commands and its push and pull_request triggers.",
|
|
111
|
+
severity: "error",
|
|
112
|
+
configurable: true,
|
|
113
|
+
check(ctx) {
|
|
114
|
+
const branches = ctx.declaration.ci?.branches;
|
|
115
|
+
if (!branches || Object.keys(branches).length === 0) return [];
|
|
116
|
+
const listed = Object.keys(branches);
|
|
117
|
+
const out: WorkspaceDiagnostic[] = [];
|
|
118
|
+
for (const member of ctx.declaration.members) {
|
|
119
|
+
const own = routedBranch(branches, member.name);
|
|
120
|
+
for (const path of recordedFiles(ctx, member)) {
|
|
121
|
+
const text = read(ctx.tree, path);
|
|
122
|
+
if (text === null) continue;
|
|
123
|
+
for (const why of routeDifferences(pipelineTargets(text), own, listed, member.name)) {
|
|
124
|
+
out.push({
|
|
125
|
+
checkId: this.id,
|
|
126
|
+
severity: this.severity,
|
|
127
|
+
code: CI_BRANCH_FINDING_CODES[0],
|
|
128
|
+
message: `WSP152: ${path}: ${why}; regenerate it with chant build --components --generate <forge> --pr-loop`,
|
|
129
|
+
pointer: "/ci/branches",
|
|
130
|
+
entity: member.name,
|
|
131
|
+
treeFile: path,
|
|
132
|
+
line: 1,
|
|
133
|
+
column: 1,
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
return out;
|
|
139
|
+
},
|
|
140
|
+
},
|
|
141
|
+
];
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WSP153 (#4404): an Op gate's approval block that `identity.gates` does not
|
|
3
|
+
* set at base.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { execFileSync } from "node:child_process";
|
|
7
|
+
import { mkdirSync, mkdtempSync, realpathSync, rmSync, writeFileSync } from "node:fs";
|
|
8
|
+
import { tmpdir } from "node:os";
|
|
9
|
+
import { dirname, join } from "node:path";
|
|
10
|
+
import { afterAll, describe, expect, test } from "vitest";
|
|
11
|
+
import { runDeclarationChecks } from "../checks";
|
|
12
|
+
import { gateUses, unbasedFields } from "./gate-approval";
|
|
13
|
+
import { REASONS } from "../reason-codes";
|
|
14
|
+
|
|
15
|
+
const scratch: string[] = [];
|
|
16
|
+
afterAll(() => {
|
|
17
|
+
for (const d of scratch) rmSync(d, { recursive: true, force: true });
|
|
18
|
+
});
|
|
19
|
+
function repo(files: Record<string, string>): string {
|
|
20
|
+
const root = realpathSync(mkdtempSync(join(tmpdir(), "chant-gate-approval-")));
|
|
21
|
+
scratch.push(root);
|
|
22
|
+
execFileSync("git", ["init", "-q"], { cwd: root });
|
|
23
|
+
for (const [path, text] of Object.entries(files)) {
|
|
24
|
+
mkdirSync(dirname(join(root, path)), { recursive: true });
|
|
25
|
+
writeFileSync(join(root, path), text);
|
|
26
|
+
}
|
|
27
|
+
return root;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const op = (approval: unknown): string =>
|
|
31
|
+
JSON.stringify({ name: "release", phases: [{ name: "Gate", parallel: false, steps: [{ kind: "gate", gate: "ship", timeout: "48h", ...(approval ? { approval } : {}) }] }] });
|
|
32
|
+
const workspace = (gates: Record<string, unknown> | undefined, opText: string): Record<string, string> => ({
|
|
33
|
+
"chant.workspace.json": JSON.stringify({ name: "acme", schema: 1, members: [{ name: "app", dir: "app", kind: "other", because: "app" }], ...(gates ? { identity: { gates } } : {}) }),
|
|
34
|
+
"app/dist/ops/release/op.json": opText,
|
|
35
|
+
});
|
|
36
|
+
const found = async (root: string) =>
|
|
37
|
+
(await runDeclarationChecks(root, (f) => f, { gather: false })).diagnostics.filter((d) => d.ruleId === "WSP153");
|
|
38
|
+
|
|
39
|
+
describe("WSP153: an approval block base does not back", () => {
|
|
40
|
+
test("a gate with an approval block and no entry at base is reported, with the fields it sets", async () => {
|
|
41
|
+
const [d] = await found(repo(workspace(undefined, op({ quorum: { count: 2 }, mode: "enforce" }))));
|
|
42
|
+
expect(d).toMatchObject({ code: "gate-approval-unbased", severity: "warning", entity: "ship" });
|
|
43
|
+
expect(d.message).toMatch(/gate "ship" in Op release sets quorum, mode in its approval block/);
|
|
44
|
+
expect(d.message).toMatch(/no entry for it at base/);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
test("an entry that sets only some of the fields leaves the rest reported", async () => {
|
|
48
|
+
const root = repo(workspace({ ship: { quorum: { count: 2 } } }, op({ quorum: { count: 2 }, mode: "enforce", context: { risk: "low", team: "x" } })));
|
|
49
|
+
const [d] = await found(root);
|
|
50
|
+
expect(d.message).toMatch(/sets mode, context \(risk, team\)/);
|
|
51
|
+
expect(d.message).toMatch(/identity\.gates\.ship sets none of them/);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
test("an entry that sets every field the Op does is clean", async () => {
|
|
55
|
+
const approval = { quorum: { count: 2 }, mode: "enforce", context: { risk: "low" } };
|
|
56
|
+
const clean = repo(workspace({ ship: { quorum: { count: 2 }, mode: "enforce", context: { risk: "high" } } }, op(approval)));
|
|
57
|
+
expect(await found(clean)).toEqual([]);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
test("a gate with no approval block, or an Op that is not built, is not reported", async () => {
|
|
61
|
+
expect(await found(repo(workspace(undefined, op(undefined))))).toEqual([]);
|
|
62
|
+
expect(await found(repo({ "chant.workspace.json": JSON.stringify({ name: "acme", schema: 1, members: [{ name: "app", dir: "app", kind: "other", because: "app" }] }) }))).toEqual([]);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
test("it reads gates nested in an effect and in onFailure", () => {
|
|
66
|
+
const uses = gateUses({
|
|
67
|
+
phases: [{ steps: [{ kind: "effect", steps: [{ kind: "gate", gate: "inner", approval: { mode: "enforce" } }] }] }],
|
|
68
|
+
onFailure: [{ steps: [{ kind: "gate", signalName: "undo", approval: { quorum: { count: 2 } } }] }],
|
|
69
|
+
});
|
|
70
|
+
expect(uses.map((u) => u.gate)).toEqual(["inner", "undo"]);
|
|
71
|
+
expect(unbasedFields(uses[0], undefined)).toEqual(["mode"]);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
test("the code is a documented reason code", () => {
|
|
75
|
+
expect(REASONS["gate-approval-unbased"]).toMatch(/identity\.gates does not set at base/);
|
|
76
|
+
});
|
|
77
|
+
});
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WSP153 (#4404): an Op gate's `approval` block that `identity.gates` does
|
|
3
|
+
* not back at base.
|
|
4
|
+
*
|
|
5
|
+
* A run reads a gate's `quorum`, `policy`, `mode` and `context` from
|
|
6
|
+
* `identity.gates.<gate>` in the declaration at base (#4324, #4404). A field
|
|
7
|
+
* the entry leaves out is read from the Op being run, which the change under
|
|
8
|
+
* review can edit, so a pull request can lower a quorum or switch a policy to
|
|
9
|
+
* `log-only` and be judged by its own edit. The Op-side fallback stays so an
|
|
10
|
+
* existing Op does not drop to one approval overnight. This check names each
|
|
11
|
+
* gate that still leans on it, and the fields it leans on.
|
|
12
|
+
*
|
|
13
|
+
* It reads each member's built Ops (`dist/ops/<name>/op.json`) and runs
|
|
14
|
+
* nothing. An Op that has not been built is not seen.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import type { WorkspaceCheck, WorkspaceDiagnostic } from "../checks";
|
|
18
|
+
import { joinPath } from "../tree";
|
|
19
|
+
import { read } from "./security";
|
|
20
|
+
|
|
21
|
+
export const WSP_GATE_APPROVAL_UNBASED = "WSP153";
|
|
22
|
+
|
|
23
|
+
/** The finding code WSP153 reports. */
|
|
24
|
+
export const GATE_APPROVAL_FINDING_CODES = ["gate-approval-unbased"] as const;
|
|
25
|
+
|
|
26
|
+
const memberDir = (dir: string) => (dir === "." ? "" : dir);
|
|
27
|
+
|
|
28
|
+
type Fields = "quorum" | "policy" | "mode" | "context";
|
|
29
|
+
|
|
30
|
+
interface GateUse {
|
|
31
|
+
gate: string;
|
|
32
|
+
/** What the Op's `approval` block sets. Context is the keys it sets. */
|
|
33
|
+
quorum: boolean;
|
|
34
|
+
policy: boolean;
|
|
35
|
+
mode: boolean;
|
|
36
|
+
context: string[];
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
interface StepJson {
|
|
40
|
+
kind?: string;
|
|
41
|
+
gate?: string;
|
|
42
|
+
signalName?: string;
|
|
43
|
+
approval?: { quorum?: unknown; policy?: unknown; mode?: unknown; context?: Record<string, unknown> };
|
|
44
|
+
steps?: StepJson[];
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** The gates of a built Op that declare an `approval` block, with the fields it sets. A gate that asks a decision point has none. */
|
|
48
|
+
export function gateUses(op: { phases?: { steps?: StepJson[] }[]; onFailure?: { steps?: StepJson[] }[] }): GateUse[] {
|
|
49
|
+
const out: GateUse[] = [];
|
|
50
|
+
const visit = (step: StepJson): void => {
|
|
51
|
+
if (step.kind === "gate" && step.approval && typeof step.approval === "object") {
|
|
52
|
+
const a = step.approval;
|
|
53
|
+
out.push({
|
|
54
|
+
gate: step.gate ?? step.signalName ?? "",
|
|
55
|
+
quorum: a.quorum !== undefined,
|
|
56
|
+
policy: a.policy !== undefined,
|
|
57
|
+
mode: a.mode !== undefined,
|
|
58
|
+
context: a.context && typeof a.context === "object" ? Object.keys(a.context) : [],
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
if (step.kind === "effect") (step.steps ?? []).forEach(visit);
|
|
62
|
+
};
|
|
63
|
+
for (const p of [...(op.phases ?? []), ...(op.onFailure ?? [])]) (p.steps ?? []).forEach(visit);
|
|
64
|
+
return out;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** The fields of `use` that `entry` (the gate's `identity.gates` entry, or undefined) does not set, as phrases. Empty when base backs every one. */
|
|
68
|
+
export function unbasedFields(use: GateUse, entry: { quorum: unknown; policy: unknown; mode: unknown; context: Record<string, unknown> | null } | undefined): string[] {
|
|
69
|
+
const out: string[] = [];
|
|
70
|
+
const has = (f: Exclude<Fields, "context">): boolean => entry?.[f] !== null && entry?.[f] !== undefined;
|
|
71
|
+
for (const f of ["quorum", "policy", "mode"] as const) if (use[f] && !has(f)) out.push(f);
|
|
72
|
+
const loose = use.context.filter((k) => !(k in (entry?.context ?? {})));
|
|
73
|
+
if (loose.length > 0) out.push(`context (${loose.join(", ")})`);
|
|
74
|
+
return out;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export const GATE_APPROVAL_CHECKS: readonly WorkspaceCheck[] = [
|
|
78
|
+
{
|
|
79
|
+
id: WSP_GATE_APPROVAL_UNBASED,
|
|
80
|
+
name: "gate-approval-unbased",
|
|
81
|
+
description: "An Op gate declares an approval block whose quorum, policy, mode or context identity.gates does not set at base, so the change being run can edit it.",
|
|
82
|
+
severity: "warning",
|
|
83
|
+
configurable: true,
|
|
84
|
+
check(ctx) {
|
|
85
|
+
const out: WorkspaceDiagnostic[] = [];
|
|
86
|
+
const gates = ctx.declaration.identity?.gates ?? {};
|
|
87
|
+
for (const member of ctx.declaration.members) {
|
|
88
|
+
const opsDir = joinPath(memberDir(member.dir), "dist", "ops");
|
|
89
|
+
for (const entry of ctx.tree.list(opsDir) ?? []) {
|
|
90
|
+
if (entry.type !== "dir") continue;
|
|
91
|
+
const path = joinPath(opsDir, entry.name, "op.json");
|
|
92
|
+
const text = read(ctx.tree, path);
|
|
93
|
+
if (text === null) continue;
|
|
94
|
+
let op: Parameters<typeof gateUses>[0] & { name?: string };
|
|
95
|
+
try {
|
|
96
|
+
op = JSON.parse(text);
|
|
97
|
+
} catch {
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
for (const use of gateUses(op)) {
|
|
101
|
+
const base = gates[use.gate];
|
|
102
|
+
const missing = unbasedFields(use, base);
|
|
103
|
+
if (missing.length === 0) continue;
|
|
104
|
+
out.push({
|
|
105
|
+
checkId: this.id,
|
|
106
|
+
severity: this.severity,
|
|
107
|
+
code: GATE_APPROVAL_FINDING_CODES[0],
|
|
108
|
+
message: `WSP153: gate "${use.gate}" in Op ${op.name ?? entry.name} sets ${missing.join(", ")} in its approval block, and ${base ? `identity.gates.${use.gate}` : "identity.gates"} sets ${base ? "none of them" : `no entry for it`} at base, so a change can edit ${missing.length === 1 ? "it" : "them"} for its own run; declare ${base ? "them" : `identity.gates.${use.gate}`} in chant.workspace.json`,
|
|
109
|
+
pointer: base ? base.pointer : "/identity",
|
|
110
|
+
entity: use.gate,
|
|
111
|
+
treeFile: path,
|
|
112
|
+
line: 1,
|
|
113
|
+
column: 1,
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
return out;
|
|
119
|
+
},
|
|
120
|
+
},
|
|
121
|
+
];
|
|
@@ -22,6 +22,9 @@
|
|
|
22
22
|
* Links a kind's reader finds in a member's own files (#3932), such as a dbt
|
|
23
23
|
* project's sources, are checked by WSP093 to WSP095 like declared ones, and
|
|
24
24
|
* their findings name the member's file and line instead of the declaration.
|
|
25
|
+
* Links a member's lexicon reads from its declarations (#4397), such as the
|
|
26
|
+
* tables an otel `clickhouse` exporter writes, are checked the same way, with
|
|
27
|
+
* the reader named `lexicon <name>`.
|
|
25
28
|
*/
|
|
26
29
|
|
|
27
30
|
import type { WorkspaceCheck, WorkspaceCheckContext, WorkspaceDiagnostic } from "../checks";
|
|
@@ -33,11 +36,19 @@ import { DEFAULT_LINK_KIND, LINK_KINDS, TELEMETRY_LINK_KIND, linkTargetProblem,
|
|
|
33
36
|
const tables = new WeakMap<WorkspaceCheckContext, LinkTableRow[]>();
|
|
34
37
|
const reads = new WeakMap<WorkspaceCheckContext, Map<string, KindLinkRead>>();
|
|
35
38
|
|
|
36
|
-
/**
|
|
39
|
+
/**
|
|
40
|
+
* What each member's kind reader found (#3932), and the links its lexicons
|
|
41
|
+
* read from its declarations (#4397), read once per check run.
|
|
42
|
+
*/
|
|
37
43
|
function kindReads(ctx: WorkspaceCheckContext): Map<string, KindLinkRead> {
|
|
38
44
|
let read = reads.get(ctx);
|
|
39
45
|
if (!read) {
|
|
40
46
|
read = readKindLinks(ctx.declaration, ctx.tree, ctx.kinds);
|
|
47
|
+
for (const [member, lexicon] of ctx.facts?.lexiconLinks ?? []) {
|
|
48
|
+
const prev = read.get(member);
|
|
49
|
+
// resolveLinks names one reader per member; the lexicon's links keep their file and line.
|
|
50
|
+
read.set(member, prev ? { ...prev, links: [...prev.links, ...lexicon.links], gaps: [...prev.gaps, ...lexicon.gaps] } : lexicon);
|
|
51
|
+
}
|
|
41
52
|
reads.set(ctx, read);
|
|
42
53
|
}
|
|
43
54
|
return read;
|