@intentius/chant 0.124.46 → 0.124.49
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/__golden__/wave-loop/op-by-apply-after-fails.json +10 -0
- package/dist/__golden__/wave-loop/op-by-apply-hold.json +10 -0
- package/dist/__golden__/wave-loop/op-by-apply-ignored.json +10 -0
- package/dist/__golden__/wave-loop/op-by-apply-pass.json +10 -0
- package/dist/__golden__/wave-loop/op-by-apply-skip.json +10 -0
- package/dist/__golden__/wave-loop/op-cost-member-clash.json +6 -0
- package/dist/__golden__/wave-loop/op-cost-member.json +13 -0
- package/dist/__golden__/wave-loop/op-denied-source.json +16 -0
- package/dist/__golden__/wave-loop/op-denied.json +16 -0
- package/dist/__golden__/wave-loop/op-discover-rest-short.json +6 -0
- package/dist/__golden__/wave-loop/op-discover-rest-stops.json +13 -0
- package/dist/__golden__/wave-loop/op-discover-rest.json +26 -0
- package/dist/__golden__/wave-loop/op-discover-wide.json +26 -0
- package/dist/__golden__/wave-loop/op-held-on-again.json +16 -0
- package/dist/__golden__/wave-loop/op-held-on-before-apply.json +16 -0
- package/dist/__golden__/wave-loop/op-held-on.json +16 -0
- package/dist/__golden__/wave-loop/op-hooks-apply-fails.json +13 -0
- package/dist/__golden__/wave-loop/op-hooks-fail-after-apply.json +13 -0
- package/dist/__golden__/wave-loop/op-hooks-fail-before-apply.json +13 -0
- package/dist/__golden__/wave-loop/op-hooks-fail-before-plan.json +13 -0
- package/dist/__golden__/wave-loop/op-hooks-held.json +13 -0
- package/dist/__golden__/wave-loop/op-hooks-hold-before-apply.json +13 -0
- package/dist/__golden__/wave-loop/op-hooks-pass.json +13 -0
- package/dist/__golden__/wave-loop/op-hooks-refused-at-base.json +13 -0
- package/dist/__golden__/wave-loop/op-hooks-share.json +33 -0
- package/dist/__golden__/wave-loop/op-outcome-held.json +10 -0
- package/dist/__golden__/wave-loop/op-outcome-replan-approved.json +10 -0
- package/dist/__golden__/wave-loop/op-outcome-replan-forever.json +10 -0
- package/dist/__golden__/wave-loop/op-outcome-replan.json +10 -0
- package/dist/__golden__/wave-loop/op-outcome-stood-down.json +10 -0
- package/dist/__golden__/wave-loop/op-outcome-unreadable.json +10 -0
- package/dist/__golden__/wave-loop/op-overridden.json +16 -0
- package/dist/__golden__/wave-loop/op-per-run.json +20 -0
- package/dist/__golden__/wave-loop/op-point.json +13 -0
- package/dist/__golden__/wave-loop/op-pr-review.json +10 -0
- package/dist/__golden__/wave-loop/op-sealed.json +10 -0
- package/dist/__golden__/wave-loop/op-spend-change-only.json +10 -0
- package/dist/__golden__/wave-loop/op-spend.json +10 -0
- package/dist/__golden__/wave-loop/op-wave-commands.json +55 -0
- package/dist/__golden__/wave-loop/op-wide-refused.json +26 -0
- package/dist/__golden__/wave-loop/op-wide-waiting.json +29 -0
- package/dist/__golden__/wave-loop/op-wide.json +39 -0
- package/dist/action-pins.d.ts +61 -0
- package/dist/action-pins.d.ts.map +1 -0
- package/dist/action-pins.js +77 -0
- package/dist/cli/commands/carve.d.ts +3 -0
- package/dist/cli/commands/carve.d.ts.map +1 -1
- package/dist/cli/commands/carve.js +36 -7
- package/dist/cli/commands/import-report.d.ts +26 -0
- package/dist/cli/commands/import-report.d.ts.map +1 -0
- package/dist/cli/commands/import-report.js +65 -0
- package/dist/cli/commands/import.d.ts +11 -1
- package/dist/cli/commands/import.d.ts.map +1 -1
- package/dist/cli/commands/import.js +22 -3
- package/dist/cli/commands/migrate.d.ts +13 -1
- package/dist/cli/commands/migrate.d.ts.map +1 -1
- package/dist/cli/commands/migrate.js +134 -10
- package/dist/cli/handlers/approve.d.ts.map +1 -1
- package/dist/cli/handlers/approve.js +41 -14
- package/dist/cli/handlers/build.d.ts.map +1 -1
- package/dist/cli/handlers/build.js +6 -1
- package/dist/cli/handlers/graph-reader.d.ts +26 -0
- package/dist/cli/handlers/graph-reader.d.ts.map +1 -0
- package/dist/cli/handlers/graph-reader.js +41 -0
- package/dist/cli/handlers/graph.d.ts.map +1 -1
- package/dist/cli/handlers/graph.js +14 -0
- package/dist/cli/handlers/ledger.d.ts +3 -0
- package/dist/cli/handlers/ledger.d.ts.map +1 -0
- package/dist/cli/handlers/ledger.js +55 -0
- package/dist/cli/handlers/migrate.d.ts.map +1 -1
- package/dist/cli/handlers/migrate.js +18 -11
- package/dist/cli/handlers/misc.d.ts.map +1 -1
- package/dist/cli/handlers/misc.js +12 -1
- package/dist/cli/handlers/pr-env.js +2 -2
- package/dist/cli/handlers/pr.d.ts +15 -1
- package/dist/cli/handlers/pr.d.ts.map +1 -1
- package/dist/cli/handlers/pr.js +183 -40
- package/dist/cli/handlers/query.d.ts +7 -2
- package/dist/cli/handlers/query.d.ts.map +1 -1
- package/dist/cli/handlers/query.js +16 -4
- package/dist/cli/handlers/release.d.ts.map +1 -1
- package/dist/cli/handlers/release.js +6 -3
- package/dist/cli/handlers/search.d.ts +2 -4
- package/dist/cli/handlers/search.d.ts.map +1 -1
- package/dist/cli/handlers/search.js +13 -39
- package/dist/cli/main.d.ts.map +1 -1
- package/dist/cli/main.js +87 -14
- package/dist/cli/parse-args.d.ts.map +1 -1
- package/dist/cli/parse-args.js +47 -2
- package/dist/cli/registry.d.ts +22 -4
- package/dist/cli/registry.d.ts.map +1 -1
- package/dist/components/builders.d.ts +5 -1
- package/dist/components/builders.d.ts.map +1 -1
- package/dist/components/builders.js +5 -0
- package/dist/components/capability-plugin-loader.d.ts.map +1 -1
- package/dist/components/capability-plugin-loader.js +16 -1
- package/dist/components/capability-plugin.d.ts +14 -0
- package/dist/components/capability-plugin.d.ts.map +1 -1
- package/dist/components/pr-hooks.d.ts +21 -1
- package/dist/components/pr-hooks.d.ts.map +1 -1
- package/dist/components/pr-hooks.js +72 -4
- package/dist/components/pr-pipeline.d.ts +45 -10
- package/dist/components/pr-pipeline.d.ts.map +1 -1
- package/dist/components/pr-pipeline.js +47 -14
- package/dist/components/pr-policy.d.ts +36 -0
- package/dist/components/pr-policy.d.ts.map +1 -1
- package/dist/components/pr-policy.js +36 -0
- package/dist/components/pr-run.d.ts +3 -0
- package/dist/components/pr-run.d.ts.map +1 -1
- package/dist/components/pr-run.js +1 -0
- package/dist/components/starter-plugin.d.ts +1 -0
- package/dist/components/starter-plugin.d.ts.map +1 -1
- package/dist/components/starter-plugin.js +5 -1
- package/dist/components/verbs/index.d.ts +2 -0
- package/dist/components/verbs/index.d.ts.map +1 -1
- package/dist/components/verbs/index.js +2 -0
- package/dist/components/verbs/publish-artifact.d.ts +65 -0
- package/dist/components/verbs/publish-artifact.d.ts.map +1 -0
- package/dist/components/verbs/publish-artifact.js +74 -0
- package/dist/components/verbs/publish-image.d.ts +129 -0
- package/dist/components/verbs/publish-image.d.ts.map +1 -0
- package/dist/components/verbs/publish-image.js +145 -0
- package/dist/config-environments.d.ts +20 -0
- package/dist/config-environments.d.ts.map +1 -1
- package/dist/config-environments.js +12 -0
- package/dist/config.d.ts +25 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +10 -2
- package/dist/gate-decision.d.ts +20 -2
- package/dist/gate-decision.d.ts.map +1 -1
- package/dist/gate-decision.js +30 -3
- package/dist/graph-ir.d.ts +25 -0
- package/dist/graph-ir.d.ts.map +1 -1
- package/dist/graph-walk.d.ts +66 -0
- package/dist/graph-walk.d.ts.map +1 -0
- package/dist/graph-walk.js +70 -0
- package/dist/import/parser.d.ts +19 -0
- package/dist/import/parser.d.ts.map +1 -1
- package/dist/lexicon.d.ts +94 -6
- package/dist/lexicon.d.ts.map +1 -1
- package/dist/lifecycle/gate-ledger.d.ts +11 -0
- package/dist/lifecycle/gate-ledger.d.ts.map +1 -1
- package/dist/lifecycle/git.d.ts +2 -5
- package/dist/lifecycle/git.d.ts.map +1 -1
- package/dist/lifecycle/ledger-read.d.ts +65 -0
- package/dist/lifecycle/ledger-read.d.ts.map +1 -0
- package/dist/lifecycle/ledger-read.js +160 -0
- package/dist/lifecycle/release-workstation.d.ts +7 -0
- package/dist/lifecycle/release-workstation.d.ts.map +1 -1
- package/dist/lifecycle/release-workstation.js +25 -0
- package/dist/lifecycle/run-ledger.d.ts +4 -3
- package/dist/lifecycle/run-ledger.d.ts.map +1 -1
- package/dist/lifecycle/run-ledger.js +1 -0
- package/dist/lint/discover.js +3 -1
- package/dist/lint/post-synth.d.ts +127 -1
- package/dist/lint/post-synth.d.ts.map +1 -1
- package/dist/lint/post-synth.js +84 -2
- package/dist/lint/rules/op/index.d.ts +1 -0
- package/dist/lint/rules/op/index.d.ts.map +1 -1
- package/dist/lint/rules/op/index.js +2 -0
- package/dist/lint/rules/op/ops019-workspace-op-target.d.ts +32 -0
- package/dist/lint/rules/op/ops019-workspace-op-target.d.ts.map +1 -0
- package/dist/lint/rules/op/ops019-workspace-op-target.js +63 -0
- package/dist/op/activities/activity-contracts.d.ts +9 -6
- package/dist/op/activities/activity-contracts.d.ts.map +1 -1
- package/dist/op/activities/activity-contracts.js +5 -2
- package/dist/op/activities/index.d.ts +2 -2
- package/dist/op/activities/index.d.ts.map +1 -1
- package/dist/op/activities/index.js +1 -1
- package/dist/op/activities/review-agent.d.ts +90 -4
- package/dist/op/activities/review-agent.d.ts.map +1 -1
- package/dist/op/activities/review-agent.js +217 -19
- package/dist/op/activities/work-evidence.d.ts +8 -1
- package/dist/op/activities/work-evidence.d.ts.map +1 -1
- package/dist/op/activities/work-evidence.js +1 -0
- package/dist/op/builders.d.ts +9 -5
- package/dist/op/builders.d.ts.map +1 -1
- package/dist/op/builders.js +9 -5
- package/dist/op/composites/apply-op.d.ts +29 -2
- package/dist/op/composites/apply-op.d.ts.map +1 -1
- package/dist/op/composites/apply-op.js +20 -1
- package/dist/op/gate-approval-base.d.ts +25 -0
- package/dist/op/gate-approval-base.d.ts.map +1 -0
- package/dist/op/gate-approval-base.js +71 -0
- package/dist/op/gate-approval.d.ts +18 -9
- package/dist/op/gate-approval.d.ts.map +1 -1
- package/dist/op/gate-approval.js +13 -31
- package/dist/op/gate-name.d.ts +13 -0
- package/dist/op/gate-name.d.ts.map +1 -1
- package/dist/op/gate-name.js +14 -1
- package/dist/op/gate-quorum.d.ts +100 -3
- package/dist/op/gate-quorum.d.ts.map +1 -1
- package/dist/op/gate-quorum.js +108 -6
- package/dist/op/gate-review.d.ts +9 -0
- package/dist/op/gate-review.d.ts.map +1 -1
- package/dist/op/gate-review.js +13 -0
- package/dist/op/gate.d.ts +47 -5
- package/dist/op/gate.d.ts.map +1 -1
- package/dist/op/gate.js +25 -4
- package/dist/op/index.d.ts +3 -3
- package/dist/op/index.d.ts.map +1 -1
- package/dist/op/index.js +1 -1
- package/dist/op/local-executor.d.ts +45 -3
- package/dist/op/local-executor.d.ts.map +1 -1
- package/dist/op/local-executor.js +68 -7
- package/dist/op/op-waves-run.d.ts +26 -1
- package/dist/op/op-waves-run.d.ts.map +1 -1
- package/dist/op/op-waves-run.js +259 -13
- package/dist/op/op-waves.d.ts +72 -5
- package/dist/op/op-waves.d.ts.map +1 -1
- package/dist/op/op-waves.js +110 -15
- package/dist/op/runtime.d.ts +11 -0
- package/dist/op/runtime.d.ts.map +1 -1
- package/dist/op/schedule-overdue.d.ts +16 -0
- package/dist/op/schedule-overdue.d.ts.map +1 -1
- package/dist/op/schedule-overdue.js +36 -0
- package/dist/op/stage-hooks.d.ts +9 -2
- package/dist/op/stage-hooks.d.ts.map +1 -1
- package/dist/op/stage-hooks.js +7 -5
- package/dist/op/workspace-run-env.d.ts +22 -0
- package/dist/op/workspace-run-env.d.ts.map +1 -0
- package/dist/op/workspace-run-env.js +21 -0
- package/dist/pr-forge.d.ts +24 -0
- package/dist/pr-forge.d.ts.map +1 -1
- package/dist/pr-forge.js +47 -0
- package/dist/pr-loop.d.ts +10 -0
- package/dist/pr-loop.d.ts.map +1 -1
- package/dist/pr-loop.js +4 -0
- package/dist/pr-migrations.d.ts +62 -0
- package/dist/pr-migrations.d.ts.map +1 -0
- package/dist/pr-migrations.js +50 -0
- package/dist/pr-post.d.ts +42 -0
- package/dist/pr-post.d.ts.map +1 -1
- package/dist/pr-post.js +68 -2
- package/dist/read-filter.d.ts +65 -0
- package/dist/read-filter.d.ts.map +1 -0
- package/dist/read-filter.js +179 -0
- package/dist/run-event.d.ts +1 -1
- package/dist/run-event.d.ts.map +1 -1
- package/dist/terraform/carve.d.ts +24 -3
- package/dist/terraform/carve.d.ts.map +1 -1
- package/dist/terraform/carve.js +32 -14
- package/dist/terraform/cuts.d.ts +97 -0
- package/dist/terraform/cuts.d.ts.map +1 -0
- package/dist/terraform/cuts.js +138 -0
- package/dist/terraform/folds.d.ts +26 -0
- package/dist/terraform/folds.d.ts.map +1 -0
- package/dist/terraform/folds.js +38 -0
- package/dist/terraform/graph.d.ts.map +1 -1
- package/dist/terraform/graph.js +10 -0
- package/dist/terraform/reach.d.ts +140 -0
- package/dist/terraform/reach.d.ts.map +1 -0
- package/dist/terraform/reach.js +170 -0
- package/dist/terraform/score.d.ts +14 -0
- package/dist/terraform/score.d.ts.map +1 -1
- package/dist/terraform/score.js +14 -31
- package/dist/terraform/state.d.ts.map +1 -1
- package/dist/terraform/state.js +4 -2
- package/dist/terraform/types.d.ts +8 -0
- package/dist/terraform/types.d.ts.map +1 -1
- package/dist/wave-blocked.d.ts +10 -1
- package/dist/wave-blocked.d.ts.map +1 -1
- package/dist/wave-blocked.js +31 -3
- package/dist/wave-runtime.mjs +264 -254
- package/dist/workspace/check.schema.json +13 -2
- package/dist/workspace/checks/ci-branch.d.ts +44 -0
- package/dist/workspace/checks/ci-branch.d.ts.map +1 -0
- package/dist/workspace/checks/ci-branch.js +133 -0
- package/dist/workspace/checks/gate-approval.d.ts +58 -0
- package/dist/workspace/checks/gate-approval.d.ts.map +1 -0
- package/dist/workspace/checks/gate-approval.js +103 -0
- package/dist/workspace/checks/links.d.ts +3 -0
- package/dist/workspace/checks/links.d.ts.map +1 -1
- package/dist/workspace/checks/links.js +12 -1
- package/dist/workspace/checks/org.d.ts +48 -0
- package/dist/workspace/checks/org.d.ts.map +1 -0
- package/dist/workspace/checks/org.js +156 -0
- package/dist/workspace/checks.d.ts +12 -1
- package/dist/workspace/checks.d.ts.map +1 -1
- package/dist/workspace/checks.js +63 -7
- package/dist/workspace/ci-branches.d.ts +16 -0
- package/dist/workspace/ci-branches.d.ts.map +1 -1
- package/dist/workspace/ci-branches.js +27 -0
- package/dist/workspace/ci-green-cli.js +7 -7
- package/dist/workspace/ci-green-workflow.d.ts +1 -1
- package/dist/workspace/ci-green-workflow.d.ts.map +1 -1
- package/dist/workspace/ci-green-workflow.js +13 -8
- package/dist/workspace/ci-green.d.ts +14 -0
- package/dist/workspace/ci-green.d.ts.map +1 -1
- package/dist/workspace/ci-green.js +44 -11
- package/dist/workspace/ci-run.d.ts +9 -1
- package/dist/workspace/ci-run.d.ts.map +1 -1
- package/dist/workspace/ci-run.js +67 -6
- package/dist/workspace/ci-serve.d.ts +9 -0
- package/dist/workspace/ci-serve.d.ts.map +1 -1
- package/dist/workspace/ci-serve.js +114 -76
- package/dist/workspace/compose-graph.d.ts +14 -0
- package/dist/workspace/compose-graph.d.ts.map +1 -1
- package/dist/workspace/composites.d.ts +4 -1
- package/dist/workspace/composites.d.ts.map +1 -1
- package/dist/workspace/composites.js +5 -2
- package/dist/workspace/composites.schema.json +22 -0
- package/dist/workspace/conformance/__writer_fixture__/work/work.schema.json +105 -1
- package/dist/workspace/cross-root-lint.d.ts +56 -0
- package/dist/workspace/cross-root-lint.d.ts.map +1 -0
- package/dist/workspace/cross-root-lint.js +111 -0
- package/dist/workspace/dbt-models-reader.d.ts +44 -0
- package/dist/workspace/dbt-models-reader.d.ts.map +1 -0
- package/dist/workspace/dbt-models-reader.js +356 -0
- package/dist/workspace/dbt-project-common.d.ts +40 -0
- package/dist/workspace/dbt-project-common.d.ts.map +1 -0
- package/dist/workspace/dbt-project-common.js +106 -0
- package/dist/workspace/dbt-project-reader.d.ts +4 -3
- package/dist/workspace/dbt-project-reader.d.ts.map +1 -1
- package/dist/workspace/dbt-project-reader.js +14 -108
- package/dist/workspace/decision-rank.d.ts +26 -0
- package/dist/workspace/decision-rank.d.ts.map +1 -0
- package/dist/workspace/decision-rank.js +16 -0
- package/dist/workspace/declaration.d.ts +50 -2
- package/dist/workspace/declaration.d.ts.map +1 -1
- package/dist/workspace/declaration.js +57 -11
- package/dist/workspace/declaration.schema.json +166 -24
- package/dist/workspace/environments.d.ts +26 -2
- package/dist/workspace/environments.d.ts.map +1 -1
- package/dist/workspace/environments.js +26 -2
- package/dist/workspace/exit-cli.d.ts +44 -0
- package/dist/workspace/exit-cli.d.ts.map +1 -0
- package/dist/workspace/exit-cli.js +198 -0
- package/dist/workspace/exit.d.ts +159 -0
- package/dist/workspace/exit.d.ts.map +1 -0
- package/dist/workspace/exit.js +480 -0
- package/dist/workspace/exit.schema.json +257 -0
- package/dist/workspace/flyway-dir.d.ts +39 -0
- package/dist/workspace/flyway-dir.d.ts.map +1 -0
- package/dist/workspace/flyway-dir.js +104 -0
- package/dist/workspace/flyway-layout.d.ts +96 -0
- package/dist/workspace/flyway-layout.d.ts.map +1 -0
- package/dist/workspace/flyway-layout.js +308 -0
- package/dist/workspace/graph-cache.d.ts +3 -1
- package/dist/workspace/graph-cache.d.ts.map +1 -1
- package/dist/workspace/graph-cache.js +12 -1
- package/dist/workspace/graph-cli.d.ts.map +1 -1
- package/dist/workspace/graph-cli.js +26 -8
- package/dist/workspace/graph.schema.json +34 -0
- package/dist/workspace/import-fetch.d.ts +38 -0
- package/dist/workspace/import-fetch.d.ts.map +1 -0
- package/dist/workspace/import-fetch.js +99 -0
- package/dist/workspace/import-init.d.ts +26 -0
- package/dist/workspace/import-init.d.ts.map +1 -0
- package/dist/workspace/import-init.js +130 -0
- package/dist/workspace/import-source.d.ts +147 -0
- package/dist/workspace/import-source.d.ts.map +1 -0
- package/dist/workspace/import-source.js +370 -0
- package/dist/workspace/init.d.ts +4 -0
- package/dist/workspace/init.d.ts.map +1 -1
- package/dist/workspace/init.js +70 -3
- package/dist/workspace/intent.d.ts +2 -3
- package/dist/workspace/intent.d.ts.map +1 -1
- package/dist/workspace/intent.js +5 -9
- package/dist/workspace/kind-link-readers.d.ts +59 -2
- package/dist/workspace/kind-link-readers.d.ts.map +1 -1
- package/dist/workspace/kind-link-readers.js +17 -1
- package/dist/workspace/kind-readers.d.ts +6 -0
- package/dist/workspace/kind-readers.d.ts.map +1 -1
- package/dist/workspace/kind-readers.js +18 -0
- package/dist/workspace/kinds.d.ts +90 -3
- package/dist/workspace/kinds.d.ts.map +1 -1
- package/dist/workspace/kinds.js +161 -7
- package/dist/workspace/ledger.schema.json +158 -0
- package/dist/workspace/lexicon-outputs.d.ts +20 -1
- package/dist/workspace/lexicon-outputs.d.ts.map +1 -1
- package/dist/workspace/lexicon-outputs.js +102 -10
- package/dist/workspace/lineage-check.d.ts +3 -0
- package/dist/workspace/lineage-check.d.ts.map +1 -1
- package/dist/workspace/lineage-check.js +23 -4
- package/dist/workspace/lineage-upgrade.d.ts.map +1 -1
- package/dist/workspace/lineage-upgrade.js +13 -4
- package/dist/workspace/links.d.ts +8 -1
- package/dist/workspace/links.d.ts.map +1 -1
- package/dist/workspace/links.js +25 -1
- package/dist/workspace/ls.d.ts +8 -0
- package/dist/workspace/ls.d.ts.map +1 -1
- package/dist/workspace/ls.js +7 -1
- package/dist/workspace/ls.schema.json +7 -0
- package/dist/workspace/member-commands.d.ts.map +1 -1
- package/dist/workspace/member-commands.js +54 -5
- package/dist/workspace/migrate-links.d.ts +45 -0
- package/dist/workspace/migrate-links.d.ts.map +1 -0
- package/dist/workspace/migrate-links.js +100 -0
- package/dist/workspace/migration-dirs.d.ts +74 -0
- package/dist/workspace/migration-dirs.d.ts.map +1 -0
- package/dist/workspace/migration-dirs.js +254 -0
- package/dist/workspace/new-migrations.d.ts +47 -0
- package/dist/workspace/new-migrations.d.ts.map +1 -0
- package/dist/workspace/new-migrations.js +91 -0
- package/dist/workspace/pr-report.schema.json +46 -0
- package/dist/workspace/promote.d.ts +96 -0
- package/dist/workspace/promote.d.ts.map +1 -0
- package/dist/workspace/promote.js +263 -0
- package/dist/workspace/reason-codes.d.ts +21 -0
- package/dist/workspace/reason-codes.d.ts.map +1 -1
- package/dist/workspace/reason-codes.js +25 -0
- package/dist/workspace/records-cli.d.ts +11 -0
- package/dist/workspace/records-cli.d.ts.map +1 -1
- package/dist/workspace/records-cli.js +57 -5
- package/dist/workspace/records.schema.json +5 -0
- package/dist/workspace/release-candidates.d.ts +2 -0
- package/dist/workspace/release-candidates.d.ts.map +1 -1
- package/dist/workspace/release-candidates.js +5 -2
- package/dist/workspace/release-train.d.ts +53 -0
- package/dist/workspace/release-train.d.ts.map +1 -0
- package/dist/workspace/release-train.js +134 -0
- package/dist/workspace/release-workflow.d.ts.map +1 -1
- package/dist/workspace/release-workflow.js +4 -3
- package/dist/workspace/repo-ref.d.ts +45 -0
- package/dist/workspace/repo-ref.d.ts.map +1 -0
- package/dist/workspace/repo-ref.js +114 -0
- package/dist/workspace/run-event.schema.json +2 -2
- package/dist/workspace/runs-cli.d.ts +9 -0
- package/dist/workspace/runs-cli.d.ts.map +1 -1
- package/dist/workspace/runs-cli.js +48 -3
- package/dist/workspace/runs.schema.json +7 -0
- package/dist/workspace/search-contract.d.ts +1 -1
- package/dist/workspace/search-contract.d.ts.map +1 -1
- package/dist/workspace/status-decisions.d.ts +37 -0
- package/dist/workspace/status-decisions.d.ts.map +1 -0
- package/dist/workspace/status-decisions.js +147 -0
- package/dist/workspace/status-gates.d.ts +9 -0
- package/dist/workspace/status-gates.d.ts.map +1 -1
- package/dist/workspace/status.d.ts +4 -0
- package/dist/workspace/status.d.ts.map +1 -1
- package/dist/workspace/status.js +14 -2
- package/dist/workspace/status.schema.json +6 -0
- package/dist/workspace/work-cli.d.ts.map +1 -1
- package/dist/workspace/work-cli.js +5 -3
- package/dist/workspace/work-evidence-ops.d.ts +83 -0
- package/dist/workspace/work-evidence-ops.d.ts.map +1 -0
- package/dist/workspace/work-evidence-ops.js +169 -0
- package/dist/workspace/work-evidence.d.ts +12 -2
- package/dist/workspace/work-evidence.d.ts.map +1 -1
- package/dist/workspace/work-evidence.js +21 -3
- package/dist/workspace/work-evidence.schema.json +49 -2
- package/dist/workspace/workspace-gates.d.ts +110 -0
- package/dist/workspace/workspace-gates.d.ts.map +1 -0
- package/dist/workspace/workspace-gates.js +140 -0
- package/dist/workspace/workspace-kinds.schema.json +108 -10
- package/dist/workspace/workspace-ledger.d.ts +76 -0
- package/dist/workspace/workspace-ledger.d.ts.map +1 -0
- package/dist/workspace/workspace-ledger.js +91 -0
- package/dist/workspace/workspace-run.d.ts +112 -0
- package/dist/workspace/workspace-run.d.ts.map +1 -0
- package/dist/workspace/workspace-run.js +432 -0
- package/package.json +1 -1
- package/src/__golden__/wave-loop/op-by-apply-after-fails.json +10 -0
- package/src/__golden__/wave-loop/op-by-apply-hold.json +10 -0
- package/src/__golden__/wave-loop/op-by-apply-ignored.json +10 -0
- package/src/__golden__/wave-loop/op-by-apply-pass.json +10 -0
- package/src/__golden__/wave-loop/op-by-apply-skip.json +10 -0
- package/src/__golden__/wave-loop/op-cost-member-clash.json +6 -0
- package/src/__golden__/wave-loop/op-cost-member.json +13 -0
- package/src/__golden__/wave-loop/op-denied-source.json +16 -0
- package/src/__golden__/wave-loop/op-denied.json +16 -0
- package/src/__golden__/wave-loop/op-discover-rest-short.json +6 -0
- package/src/__golden__/wave-loop/op-discover-rest-stops.json +13 -0
- package/src/__golden__/wave-loop/op-discover-rest.json +26 -0
- package/src/__golden__/wave-loop/op-discover-wide.json +26 -0
- package/src/__golden__/wave-loop/op-held-on-again.json +16 -0
- package/src/__golden__/wave-loop/op-held-on-before-apply.json +16 -0
- package/src/__golden__/wave-loop/op-held-on.json +16 -0
- package/src/__golden__/wave-loop/op-hooks-apply-fails.json +13 -0
- package/src/__golden__/wave-loop/op-hooks-fail-after-apply.json +13 -0
- package/src/__golden__/wave-loop/op-hooks-fail-before-apply.json +13 -0
- package/src/__golden__/wave-loop/op-hooks-fail-before-plan.json +13 -0
- package/src/__golden__/wave-loop/op-hooks-held.json +13 -0
- package/src/__golden__/wave-loop/op-hooks-hold-before-apply.json +13 -0
- package/src/__golden__/wave-loop/op-hooks-pass.json +13 -0
- package/src/__golden__/wave-loop/op-hooks-refused-at-base.json +13 -0
- package/src/__golden__/wave-loop/op-hooks-share.json +33 -0
- package/src/__golden__/wave-loop/op-outcome-held.json +10 -0
- package/src/__golden__/wave-loop/op-outcome-replan-approved.json +10 -0
- package/src/__golden__/wave-loop/op-outcome-replan-forever.json +10 -0
- package/src/__golden__/wave-loop/op-outcome-replan.json +10 -0
- package/src/__golden__/wave-loop/op-outcome-stood-down.json +10 -0
- package/src/__golden__/wave-loop/op-outcome-unreadable.json +10 -0
- package/src/__golden__/wave-loop/op-overridden.json +16 -0
- package/src/__golden__/wave-loop/op-per-run.json +20 -0
- package/src/__golden__/wave-loop/op-point.json +13 -0
- package/src/__golden__/wave-loop/op-pr-review.json +10 -0
- package/src/__golden__/wave-loop/op-sealed.json +10 -0
- package/src/__golden__/wave-loop/op-spend-change-only.json +10 -0
- package/src/__golden__/wave-loop/op-spend.json +10 -0
- package/src/__golden__/wave-loop/op-wave-commands.json +55 -0
- package/src/__golden__/wave-loop/op-wide-refused.json +26 -0
- package/src/__golden__/wave-loop/op-wide-waiting.json +29 -0
- package/src/__golden__/wave-loop/op-wide.json +39 -0
- package/src/action-pins.test.ts +78 -0
- package/src/action-pins.ts +92 -0
- package/src/cli/commands/carve-score-once.test.ts +99 -0
- package/src/cli/commands/carve.ts +37 -6
- package/src/cli/commands/check-lexicon.test.ts +2 -2
- package/src/cli/commands/import-report.ts +71 -0
- package/src/cli/commands/import.ts +33 -3
- package/src/cli/commands/migrate-dialect.test.ts +145 -0
- package/src/cli/commands/migrate.ts +132 -11
- package/src/cli/handlers/__snapshots__/build-member.test.ts.snap +4 -4
- package/src/cli/handlers/approve.ts +33 -4
- package/src/cli/handlers/build.ts +7 -1
- package/src/cli/handlers/graph-reader.test.ts +31 -0
- package/src/cli/handlers/graph-reader.ts +51 -0
- package/src/cli/handlers/graph.ts +14 -0
- package/src/cli/handlers/ledger.ts +54 -0
- package/src/cli/handlers/migrate.ts +21 -12
- package/src/cli/handlers/misc.ts +10 -1
- package/src/cli/handlers/pr-env.ts +2 -2
- package/src/cli/handlers/pr.ts +176 -37
- package/src/cli/handlers/query.ts +16 -4
- package/src/cli/handlers/release.ts +6 -3
- package/src/cli/handlers/search.ts +13 -34
- package/src/cli/main.test.ts +17 -0
- package/src/cli/main.ts +87 -14
- package/src/cli/parse-args.ts +35 -2
- package/src/cli/registry.ts +22 -4
- package/src/components/builders.ts +8 -0
- package/src/components/capability-plugin-loader.ts +13 -1
- package/src/components/capability-plugin.ts +16 -0
- package/src/components/pr-env-run-bundle.test.ts +1 -0
- package/src/components/pr-hooks.test.ts +73 -5
- package/src/components/pr-hooks.ts +95 -5
- package/src/components/pr-pipeline.ts +64 -16
- package/src/components/pr-policy.test.ts +64 -1
- package/src/components/pr-policy.ts +53 -0
- package/src/components/pr-run.ts +4 -0
- package/src/components/registry.test.ts +1 -1
- package/src/components/starter-plugin.ts +6 -0
- package/src/components/verbs/index.ts +2 -0
- package/src/components/verbs/publish-artifact.test.ts +60 -0
- package/src/components/verbs/publish-artifact.ts +122 -0
- package/src/components/verbs/publish-image.test.ts +101 -0
- package/src/components/verbs/publish-image.ts +265 -0
- package/src/config-environments.ts +28 -1
- package/src/config.ts +24 -1
- package/src/gate-decision-bundle.test.ts +5 -2
- package/src/gate-decision-identity.test.ts +20 -0
- package/src/gate-decision.ts +46 -3
- package/src/graph-ir.ts +27 -0
- package/src/graph-walk.test.ts +44 -0
- package/src/graph-walk.ts +102 -0
- package/src/import/parser.ts +20 -0
- package/src/lexicon.ts +98 -6
- package/src/lifecycle/gate-ledger.ts +8 -0
- package/src/lifecycle/git.ts +2 -2
- package/src/lifecycle/ledger-read.test.ts +107 -0
- package/src/lifecycle/ledger-read.ts +195 -0
- package/src/lifecycle/release-workstation.ts +24 -0
- package/src/lifecycle/run-ledger.ts +5 -2
- package/src/lint/cross-root.test.ts +122 -0
- package/src/lint/discover.ts +3 -1
- package/src/lint/post-synth.ts +212 -2
- package/src/lint/rules/op/index.ts +2 -0
- package/src/lint/rules/op/ops019-workspace-op-target.ts +89 -0
- package/src/op/activities/activity-contracts.ts +5 -2
- package/src/op/activities/index.ts +2 -2
- package/src/op/activities/review-agent.test.ts +221 -4
- package/src/op/activities/review-agent.ts +265 -22
- package/src/op/activities/work-evidence.ts +9 -1
- package/src/op/builders.ts +9 -5
- package/src/op/composites/apply-op.test.ts +108 -0
- package/src/op/composites/apply-op.ts +37 -2
- package/src/op/gate-approval-at-base.test.ts +97 -0
- package/src/op/gate-approval-base.ts +72 -0
- package/src/op/gate-approval.test.ts +6 -1
- package/src/op/gate-approval.ts +22 -27
- package/src/op/gate-name.ts +16 -1
- package/src/op/gate-quorum-at-base.test.ts +211 -0
- package/src/op/gate-quorum.ts +156 -8
- package/src/op/gate-review.ts +14 -0
- package/src/op/gate.ts +69 -9
- package/src/op/index.ts +3 -2
- package/src/op/local-executor.ts +91 -11
- package/src/op/op-waves-depends-on.test.ts +81 -0
- package/src/op/op-waves-hook-targets.test.ts +4 -2
- package/src/op/op-waves-quorum-product.test.ts +158 -0
- package/src/op/op-waves-run.ts +288 -14
- package/src/op/op-waves.ts +143 -14
- package/src/op/runtime.ts +8 -0
- package/src/op/schedule-overdue.ts +33 -0
- package/src/op/stage-hooks.ts +13 -5
- package/src/op/workspace-run-env.ts +29 -0
- package/src/pr-forge-bundle.test.ts +2 -2
- package/src/pr-forge.test.ts +47 -0
- package/src/pr-forge.ts +68 -0
- package/src/pr-loop-bundle.test.ts +3 -2
- package/src/pr-loop.test.ts +52 -0
- package/src/pr-loop.ts +12 -0
- package/src/pr-migrations.ts +97 -0
- package/src/pr-post-open.e2e.test.ts +36 -0
- package/src/pr-post.test.ts +102 -3
- package/src/pr-post.ts +90 -2
- package/src/read-filter.test.ts +54 -0
- package/src/read-filter.ts +193 -0
- package/src/run-event.ts +1 -1
- package/src/stage-hooks-bundle.test.ts +1 -0
- package/src/terraform/carve.ts +52 -15
- package/src/terraform/cuts.test.ts +173 -0
- package/src/terraform/cuts.ts +188 -0
- package/src/terraform/folds.ts +44 -0
- package/src/terraform/graph.ts +9 -0
- package/src/terraform/radius.test.ts +90 -0
- package/src/terraform/reach.test.ts +146 -0
- package/src/terraform/reach.ts +247 -0
- package/src/terraform/score.ts +27 -29
- package/src/terraform/state.ts +3 -1
- package/src/terraform/types.ts +8 -0
- package/src/wave-blocked.ts +28 -3
- package/src/wave-loop-golden.test.ts +4 -3
- package/src/workspace/__fixtures__/import/spacelift.json +9 -0
- package/src/workspace/__fixtures__/import/tfc.json +13 -0
- package/src/workspace/behold-kinds.test.ts +2 -1
- package/src/workspace/check-contract.test.ts +8 -6
- package/src/workspace/check.schema.json +13 -2
- package/src/workspace/checks/ci-branch.test.ts +128 -0
- package/src/workspace/checks/ci-branch.ts +141 -0
- package/src/workspace/checks/gate-approval.test.ts +77 -0
- package/src/workspace/checks/gate-approval.ts +121 -0
- package/src/workspace/checks/links.ts +12 -1
- package/src/workspace/checks/org.ts +169 -0
- package/src/workspace/checks.ts +68 -7
- package/src/workspace/ci-branches.ts +31 -0
- package/src/workspace/ci-green-branches.test.ts +153 -0
- package/src/workspace/ci-green-cli.ts +7 -7
- package/src/workspace/ci-green-workflow.test.ts +4 -3
- package/src/workspace/ci-green-workflow.ts +13 -9
- package/src/workspace/ci-green.ts +49 -12
- package/src/workspace/ci-run.test.ts +72 -1
- package/src/workspace/ci-run.ts +68 -8
- package/src/workspace/ci-serve.test.ts +95 -0
- package/src/workspace/ci-serve.ts +106 -65
- package/src/workspace/component-environments.test.ts +25 -2
- package/src/workspace/compose-graph.ts +9 -2
- package/src/workspace/composites.schema.json +22 -0
- package/src/workspace/composites.test.ts +39 -8
- package/src/workspace/composites.ts +6 -2
- package/src/workspace/conformance/__writer_fixture__/work/work.schema.json +105 -1
- package/src/workspace/cross-root-lint.test.ts +90 -0
- package/src/workspace/cross-root-lint.ts +126 -0
- package/src/workspace/dbt-kind.test.ts +3 -2
- package/src/workspace/dbt-models-reader.ts +362 -0
- package/src/workspace/dbt-models.test.ts +248 -0
- package/src/workspace/dbt-project-common.ts +113 -0
- package/src/workspace/dbt-project-reader.ts +29 -112
- package/src/workspace/decision-rank.ts +40 -0
- package/src/workspace/declaration.schema.json +166 -24
- package/src/workspace/declaration.ts +101 -15
- package/src/workspace/environments.ts +40 -4
- package/src/workspace/exit-cli.ts +219 -0
- package/src/workspace/exit.schema.json +257 -0
- package/src/workspace/exit.test.ts +185 -0
- package/src/workspace/exit.ts +560 -0
- package/src/workspace/flyway-dir.ts +113 -0
- package/src/workspace/flyway-layout.ts +337 -0
- package/src/workspace/graph-cache.ts +12 -2
- package/src/workspace/graph-cli.ts +25 -10
- package/src/workspace/graph-contract.test.ts +4 -2
- package/src/workspace/graph.schema.json +34 -0
- package/src/workspace/identity.test.ts +29 -2
- package/src/workspace/import-fetch.ts +151 -0
- package/src/workspace/import-init.ts +147 -0
- package/src/workspace/import-source.test.ts +183 -0
- package/src/workspace/import-source.ts +462 -0
- package/src/workspace/init.test.ts +55 -1
- package/src/workspace/init.ts +66 -4
- package/src/workspace/intent.ts +6 -14
- package/src/workspace/kind-link-readers.ts +68 -3
- package/src/workspace/kind-readers.test.ts +36 -0
- package/src/workspace/kind-readers.ts +18 -1
- package/src/workspace/kinds.test.ts +35 -7
- package/src/workspace/kinds.ts +212 -11
- package/src/workspace/ledger.schema.json +158 -0
- package/src/workspace/lexicon-outputs.ts +132 -13
- package/src/workspace/lineage-check.ts +30 -7
- package/src/workspace/lineage-upgrade.test.ts +20 -0
- package/src/workspace/lineage-upgrade.ts +16 -7
- package/src/workspace/links.ts +24 -1
- package/src/workspace/ls-contract.test.ts +1 -1
- package/src/workspace/ls.schema.json +7 -0
- package/src/workspace/ls.ts +10 -1
- package/src/workspace/member-commands.ts +60 -2
- package/src/workspace/member-file-schema.test.ts +124 -0
- package/src/workspace/migrate-links.test.ts +99 -0
- package/src/workspace/migrate-links.ts +108 -0
- package/src/workspace/migration-dirs.test.ts +272 -0
- package/src/workspace/migration-dirs.ts +267 -0
- package/src/workspace/new-migrations.test.ts +98 -0
- package/src/workspace/new-migrations.ts +109 -0
- package/src/workspace/org-scope.test.ts +238 -0
- package/src/workspace/pr-report.schema.json +46 -0
- package/src/workspace/promote.test.ts +114 -0
- package/src/workspace/promote.ts +302 -0
- package/src/workspace/reason-codes.test.ts +10 -1
- package/src/workspace/reason-codes.ts +25 -0
- package/src/workspace/records-cli.ts +58 -5
- package/src/workspace/records.schema.json +5 -0
- package/src/workspace/release-candidates.ts +7 -2
- package/src/workspace/release-train.ts +157 -0
- package/src/workspace/release-workflow.ts +4 -3
- package/src/workspace/repo-ref.ts +113 -0
- package/src/workspace/run-event.schema.json +2 -2
- package/src/workspace/runs-cli.ts +48 -4
- package/src/workspace/runs.schema.json +7 -0
- package/src/workspace/status-contract.test.ts +46 -1
- package/src/workspace/status-decisions.test.ts +100 -0
- package/src/workspace/status-decisions.ts +151 -0
- package/src/workspace/status-gates.ts +9 -0
- package/src/workspace/status.schema.json +6 -0
- package/src/workspace/status.ts +17 -3
- package/src/workspace/where-filters.test.ts +39 -0
- package/src/workspace/work-cli.ts +5 -3
- package/src/workspace/work-evidence-ops.test.ts +176 -0
- package/src/workspace/work-evidence-ops.ts +189 -0
- package/src/workspace/work-evidence.schema.json +49 -2
- package/src/workspace/work-evidence.ts +22 -3
- package/src/workspace/workspace-gates.ts +200 -0
- package/src/workspace/workspace-kinds.schema.json +108 -10
- package/src/workspace/workspace-ledger.ts +130 -0
- package/src/workspace/workspace-run.test.ts +322 -0
- package/src/workspace/workspace-run.ts +461 -0
package/src/pr-loop.ts
CHANGED
|
@@ -49,6 +49,7 @@
|
|
|
49
49
|
|
|
50
50
|
import type { ChangeSetAction, ChangeSetDocument } from "./change-set";
|
|
51
51
|
import type { PrRequireOutcome } from "./pr-requires";
|
|
52
|
+
import { renderMigrationLines, type PrMigrationFile } from "./pr-migrations";
|
|
52
53
|
import { GITHUB_COMMENT_LIMIT, groupChangeSet, renderPlanSummaryMarkdown } from "./plan-summary";
|
|
53
54
|
|
|
54
55
|
/** The schema a pull-request report names in `$schema`. */
|
|
@@ -84,6 +85,9 @@ export const PR_REPORT_POLICY_FLOOR = "0.124.41";
|
|
|
84
85
|
/** The first chant whose apply report can carry `beforeMerge` and the `requires-unmet` and `before-merge-off` refusals (#4301, ws-106). */
|
|
85
86
|
export const PR_REPORT_BEFORE_MERGE_FLOOR = "0.124.42";
|
|
86
87
|
|
|
88
|
+
/** The first chant whose plan report can carry `migrations` (#4407). */
|
|
89
|
+
export const PR_REPORT_MIGRATIONS_FLOOR = "0.124.49";
|
|
90
|
+
|
|
87
91
|
/** The gate a pull request's apply waits on, unless the pipeline names another. */
|
|
88
92
|
export const PR_APPLY_GATE = "pr-apply";
|
|
89
93
|
|
|
@@ -436,6 +440,13 @@ export interface PrReport {
|
|
|
436
440
|
* {@link PR_REPORT_OVERDUE_FLOOR}.
|
|
437
441
|
*/
|
|
438
442
|
overdue?: PrOverdue[];
|
|
443
|
+
/**
|
|
444
|
+
* Plan stage (#4407): the migration files the pull request adds in a
|
|
445
|
+
* migration-directory workspace member, each statement classified offline.
|
|
446
|
+
* Absent when it adds none. Added within contract 1 by chant
|
|
447
|
+
* {@link PR_REPORT_MIGRATIONS_FLOOR}.
|
|
448
|
+
*/
|
|
449
|
+
migrations?: PrMigrationFile[];
|
|
439
450
|
/** Component names: which changed, which run because they depend on one, and what the change touched that no component deploys. */
|
|
440
451
|
selection: {
|
|
441
452
|
changed: string[];
|
|
@@ -690,6 +701,7 @@ export function renderPrNote(report: PrReport, options: { limit?: number; approv
|
|
|
690
701
|
out.push("", `Hooks: ${passed} passed${other.length > 0 ? `; ${other.join("; ")}` : ""}.`);
|
|
691
702
|
}
|
|
692
703
|
out.push(...policyLines(report));
|
|
704
|
+
out.push(...renderMigrationLines(report.migrations));
|
|
693
705
|
if (report.selection.unclaimed.length > 0) {
|
|
694
706
|
out.push("", `Changed, and deployed by no component: ${report.selection.unclaimed.map(code).join(", ")}.`);
|
|
695
707
|
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The migration files a pull request adds, classified for the plan note
|
|
3
|
+
* (#4407). This is the "check" adoption level: each new migration in a
|
|
4
|
+
* migration-directory workspace member (goose, golang-migrate,
|
|
5
|
+
* clickhouse-migrations, Flyway) is read offline and its statements are given
|
|
6
|
+
* the class the lexicon's classifier gives the same change. Nothing is applied
|
|
7
|
+
* and no server is contacted.
|
|
8
|
+
*
|
|
9
|
+
* Core holds the shapes and the note's lines. The classes come from a
|
|
10
|
+
* lexicon's `classifyMigrations` (`./lexicon.ts`), the way a lexicon's
|
|
11
|
+
* `classifyDisruption` supplies the disruption of an update. Pure: it imports
|
|
12
|
+
* nothing, so `./pr-loop.ts` can render it.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
/** One change a statement makes, under one classifier rule. */
|
|
16
|
+
export interface MigrationChange {
|
|
17
|
+
rule: string;
|
|
18
|
+
/** The rule's class, e.g. `rebuild`, `concurrently`, `metadata`. */
|
|
19
|
+
class: string;
|
|
20
|
+
/** The class as a report names it, e.g. `REBUILD`. */
|
|
21
|
+
label: string;
|
|
22
|
+
title: string;
|
|
23
|
+
/** Postgres: the lock the rule names. */
|
|
24
|
+
lock?: string;
|
|
25
|
+
destructive?: boolean;
|
|
26
|
+
note?: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** What one statement came to: `undecided` lists `candidates`; `unclassified` and `data` say why in `note`. */
|
|
30
|
+
export interface MigrationStatement {
|
|
31
|
+
/** 1-based position among the file's up statements. */
|
|
32
|
+
index: number;
|
|
33
|
+
/** The statement's first line, cut to 120 characters. */
|
|
34
|
+
sql: string;
|
|
35
|
+
outcome: "classified" | "undecided" | "unclassified" | "data";
|
|
36
|
+
changes: MigrationChange[];
|
|
37
|
+
candidates?: MigrationChange[];
|
|
38
|
+
note?: string;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** What a lexicon answers for one new migration file. */
|
|
42
|
+
export interface MigrationFileClassification {
|
|
43
|
+
/** The file, relative to the workspace root. */
|
|
44
|
+
file: string;
|
|
45
|
+
statements: MigrationStatement[];
|
|
46
|
+
/** Set when the file could not be read (a Flyway placeholder with no value): why. */
|
|
47
|
+
error?: string;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** A new migration file in the report's `migrations`. */
|
|
51
|
+
export interface PrMigrationFile extends MigrationFileClassification {
|
|
52
|
+
/** The workspace member whose directory holds it. */
|
|
53
|
+
member: string;
|
|
54
|
+
/** The reader that listed it: `goose-dir`, `golang-migrate-dir`, `clickhouse-migrations-dir` or `flyway-dir`. */
|
|
55
|
+
reader: string;
|
|
56
|
+
version: string;
|
|
57
|
+
name: string;
|
|
58
|
+
type: "versioned" | "repeatable" | "baseline" | "undo";
|
|
59
|
+
dialect: string;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** One line of plain text: the report is read from a file the head's job wrote, so nothing in it breaks the note's layout. */
|
|
63
|
+
const line = (s: string): string => s.replace(/\s+/g, " ").trim();
|
|
64
|
+
|
|
65
|
+
const code = (s: string): string => `\`${line(s).replace(/`/g, "'")}\``;
|
|
66
|
+
|
|
67
|
+
const changeText = (c: MigrationChange): string => `${c.label} (${c.rule}${c.lock ? `, ${c.lock}` : ""})${c.destructive ? ", removes data" : ""}`;
|
|
68
|
+
|
|
69
|
+
/** The statement's row in the note. */
|
|
70
|
+
function statementText(s: MigrationStatement): string {
|
|
71
|
+
if (s.outcome === "classified") return s.changes.map(changeText).join("; ") + (s.note ? `. ${line(s.note)}` : "");
|
|
72
|
+
if (s.outcome === "undecided") {
|
|
73
|
+
const decided = s.changes.length > 0 ? `${s.changes.map(changeText).join("; ")}; ` : "";
|
|
74
|
+
return `${decided}undecided between ${(s.candidates ?? []).map((c) => `${c.rule} ${c.label}`).join(" and ")}${s.note ? `: ${line(s.note)}` : ""}`;
|
|
75
|
+
}
|
|
76
|
+
if (s.outcome === "unclassified") return `no class${s.note ? `: ${line(s.note)}` : ""}`;
|
|
77
|
+
return `data, not schema${s.note ? `: ${line(s.note)}` : ""}`;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* The note's section: one heading line per new file and a list of its
|
|
82
|
+
* statements with their classes. Empty when the pull request adds none.
|
|
83
|
+
*/
|
|
84
|
+
export function renderMigrationLines(files: readonly PrMigrationFile[] | undefined): string[] {
|
|
85
|
+
if (!files || files.length === 0) return [];
|
|
86
|
+
const out: string[] = ["", `New migration files (${files.length}), classified offline. Nothing is applied and no server is contacted.`];
|
|
87
|
+
for (const f of files) {
|
|
88
|
+
out.push("", `${code(f.file)} (member ${code(f.member)}, ${f.dialect}${f.type === "versioned" ? "" : `, ${f.type}`})`);
|
|
89
|
+
if (f.error) {
|
|
90
|
+
out.push(`- not read: ${line(f.error)}`);
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
if (f.statements.length === 0) out.push("- no up statements");
|
|
94
|
+
for (const s of f.statements) out.push(`- ${s.index}. ${code(s.sql)}: ${statementText(s)}`);
|
|
95
|
+
}
|
|
96
|
+
return out;
|
|
97
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `chant components pr-post --open` refusals (#4328), through the CLI handler.
|
|
3
|
+
* Importing the handler loads the CLI's lexicon machinery, which runs past the
|
|
4
|
+
* unit-test budget (#2817), so this lives in the e2e project.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { describe, expect, test, vi } from "vitest";
|
|
8
|
+
import type { CommandContext } from "./cli/registry";
|
|
9
|
+
|
|
10
|
+
describe("chant components pr-post --open refuses what it cannot post for (#4328)", () => {
|
|
11
|
+
const run = async (args: Record<string, unknown>): Promise<number> => {
|
|
12
|
+
const { runComponentsPrPost } = await import("./cli/handlers/pr");
|
|
13
|
+
return runComponentsPrPost({ args } as unknown as CommandContext);
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
test("--pr or --head, a forge other than GitLab, and no branch are each refused", async () => {
|
|
17
|
+
const errors: string[] = [];
|
|
18
|
+
const spy = vi.spyOn(console, "error").mockImplementation((line: unknown) => void errors.push(String(line)));
|
|
19
|
+
try {
|
|
20
|
+
expect(await run({ open: true, pr: 7, env: "prod", forge: "gitlab" })).toBe(1);
|
|
21
|
+
expect(await run({ open: true, env: "prod", forge: "github" })).toBe(1);
|
|
22
|
+
const branch = process.env.CI_COMMIT_BRANCH;
|
|
23
|
+
delete process.env.CI_COMMIT_BRANCH;
|
|
24
|
+
try {
|
|
25
|
+
expect(await run({ open: true, env: "prod", forge: "gitlab" })).toBe(1);
|
|
26
|
+
} finally {
|
|
27
|
+
if (branch !== undefined) process.env.CI_COMMIT_BRANCH = branch;
|
|
28
|
+
}
|
|
29
|
+
} finally {
|
|
30
|
+
spy.mockRestore();
|
|
31
|
+
}
|
|
32
|
+
expect(errors.join("\n")).toMatch(/takes no --pr or --head/);
|
|
33
|
+
expect(errors.join("\n")).toMatch(/--forge gitlab/);
|
|
34
|
+
expect(errors.join("\n")).toMatch(/pass --branch <branch>/);
|
|
35
|
+
});
|
|
36
|
+
});
|
package/src/pr-post.test.ts
CHANGED
|
@@ -8,9 +8,11 @@
|
|
|
8
8
|
import { mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
9
9
|
import { tmpdir } from "node:os";
|
|
10
10
|
import { join } from "node:path";
|
|
11
|
-
import { afterAll, describe, expect, test } from "vitest";
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
11
|
+
import { afterAll, describe, expect, test, vi } from "vitest";
|
|
12
|
+
import type { CommandContext } from "./cli/registry";
|
|
13
|
+
import { parseArgs } from "./cli/parse-args";
|
|
14
|
+
import { postOpenPlanReports, postPlanReport, readPlanReport } from "./pr-post";
|
|
15
|
+
import { prNoteMarker, prStatusState, type PrReport } from "./pr-loop";
|
|
14
16
|
import type { ForgeStatus, PrForge } from "./pr-forge";
|
|
15
17
|
|
|
16
18
|
const FIXTURE = join(import.meta.dirname, "__fixtures__", "pr-loop", "pr-plan.golden.json");
|
|
@@ -139,3 +141,100 @@ describe("pr-post asks for overdue scheduled Ops itself (#4289, #4295)", () => {
|
|
|
139
141
|
expect(quiet.notes[0]!.body).not.toContain("forged warning");
|
|
140
142
|
});
|
|
141
143
|
});
|
|
144
|
+
|
|
145
|
+
describe("pr-post --open posts each open merge request's plan from a pipeline of the target branch (#4328)", () => {
|
|
146
|
+
const JOB_URL = (id: number) => `https://gitlab.com/acme/estate/-/jobs/${id}`;
|
|
147
|
+
const OTHER = "c".repeat(40);
|
|
148
|
+
|
|
149
|
+
/** A GitLab client with the merge requests' plan jobs, their artifacts and the statuses already posted. */
|
|
150
|
+
function gitlab(input: { jobs: Array<{ pr: number; head: string; id: number; status: string }>; artifacts: Record<number, string>; posted?: Record<string, string> }) {
|
|
151
|
+
const statuses: Array<{ sha: string; status: ForgeStatus; url?: string }> = [];
|
|
152
|
+
const notes: Array<{ pr: number; body: string }> = [];
|
|
153
|
+
const asked: Array<[string, string]> = [];
|
|
154
|
+
const reads: string[] = [];
|
|
155
|
+
let overdue = 0;
|
|
156
|
+
const forge = {
|
|
157
|
+
kind: "gitlab",
|
|
158
|
+
host: "gitlab.com",
|
|
159
|
+
async mergeRequestJobs(branch: string, job: string) {
|
|
160
|
+
asked.push([branch, job]);
|
|
161
|
+
return input.jobs.map((j) => ({ ...j, url: JOB_URL(j.id) }));
|
|
162
|
+
},
|
|
163
|
+
async jobArtifact(id: number, path: string) {
|
|
164
|
+
reads.push(`${id}:${path}`);
|
|
165
|
+
return input.artifacts[id];
|
|
166
|
+
},
|
|
167
|
+
async statusUrl(sha: string) {
|
|
168
|
+
return input.posted?.[sha];
|
|
169
|
+
},
|
|
170
|
+
async setStatus(this: PrForge, sha: string, status: ForgeStatus) {
|
|
171
|
+
statuses.push({ sha, status, ...(this.runUrl ? { url: this.runUrl } : {}) });
|
|
172
|
+
},
|
|
173
|
+
async upsertNote(pr: number, _marker: string, body: string) {
|
|
174
|
+
notes.push({ pr, body });
|
|
175
|
+
return "1";
|
|
176
|
+
},
|
|
177
|
+
} as unknown as PrForge;
|
|
178
|
+
const count = async () => {
|
|
179
|
+
overdue++;
|
|
180
|
+
return [{ op: "drift", cron: "0 * * * *", since: "2025-10-01T00:00:00Z", message: "Op `drift` is overdue" }];
|
|
181
|
+
};
|
|
182
|
+
return { forge, statuses, notes, asked, reads, overdue: count, overdueCalls: () => overdue };
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
test("a settled plan job's report is read from its artifacts and posted, linking the job; a running one waits", async () => {
|
|
186
|
+
const g = gitlab({
|
|
187
|
+
jobs: [
|
|
188
|
+
{ pr: REPORT.pr!, head: HEAD, id: 101, status: "success" },
|
|
189
|
+
{ pr: 8, head: OTHER, id: 102, status: "running" },
|
|
190
|
+
],
|
|
191
|
+
artifacts: { 101: JSON.stringify(REPORT) },
|
|
192
|
+
});
|
|
193
|
+
const { posts, warnings } = await postOpenPlanReports({ forge: g.forge, branch: "main", job: "plan", artifactDir: ".chant/pr/", env: REPORT.env, overdue: g.overdue });
|
|
194
|
+
expect(warnings).toEqual([]);
|
|
195
|
+
expect(g.asked).toEqual([["main", "plan"]]);
|
|
196
|
+
expect(g.reads).toEqual(["101:.chant/pr/pr-plan.json"]);
|
|
197
|
+
expect(posts.map((p) => [p.pr, p.skipped ?? p.posted!.state])).toEqual([[REPORT.pr, prStatusState(REPORT)], [8, "the plan job is running"]]);
|
|
198
|
+
expect(g.statuses).toEqual([{ sha: HEAD, status: expect.objectContaining({ context: "chant/plan", state: prStatusState(REPORT), url: JOB_URL(101) }), url: JOB_URL(101) }]);
|
|
199
|
+
expect(g.notes.map((n) => n.pr)).toEqual([REPORT.pr]);
|
|
200
|
+
expect(g.notes[0]!.body).toContain("Op `drift` is overdue");
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
test("a job whose result is already posted is not posted again", async () => {
|
|
204
|
+
const g = gitlab({ jobs: [{ pr: REPORT.pr!, head: HEAD, id: 101, status: "success" }], artifacts: { 101: JSON.stringify(REPORT) }, posted: { [HEAD]: JOB_URL(101) } });
|
|
205
|
+
const { posts } = await postOpenPlanReports({ forge: g.forge, branch: "main", job: "plan", artifactDir: ".chant/pr", env: REPORT.env });
|
|
206
|
+
expect(posts).toEqual([{ pr: REPORT.pr, head: HEAD, skipped: "this plan job's result is already posted" }]);
|
|
207
|
+
expect(g.statuses).toEqual([]);
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
test("the pull request and head are GitLab's: a report for another head, a missing one and a failed job each fail the status", async () => {
|
|
211
|
+
const g = gitlab({
|
|
212
|
+
jobs: [
|
|
213
|
+
{ pr: REPORT.pr!, head: OTHER, id: 101, status: "success" },
|
|
214
|
+
{ pr: 8, head: HEAD, id: 102, status: "failed" },
|
|
215
|
+
{ pr: 9, head: HEAD, id: 103, status: "failed" },
|
|
216
|
+
],
|
|
217
|
+
artifacts: { 101: JSON.stringify(REPORT), 103: JSON.stringify({ ...REPORT, pr: 9 }) },
|
|
218
|
+
});
|
|
219
|
+
const { posts } = await postOpenPlanReports({ forge: g.forge, branch: "main", job: "plan", artifactDir: ".chant/pr", env: REPORT.env, overdue: g.overdue });
|
|
220
|
+
expect(posts.map((p) => [p.pr, p.posted!.state, p.posted!.refused ?? ""])).toEqual([
|
|
221
|
+
[REPORT.pr, "failure", expect.stringMatching(/the report is for head/)],
|
|
222
|
+
[8, "failure", "the plan job wrote no report"],
|
|
223
|
+
[9, "failure", ""],
|
|
224
|
+
]);
|
|
225
|
+
expect(g.notes.map((n) => n.pr)).toEqual([9]);
|
|
226
|
+
// The check runs once, however many merge requests it posts for.
|
|
227
|
+
expect(g.overdueCalls()).toBe(1);
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
test("a forge that cannot list merge request jobs is refused", async () => {
|
|
231
|
+
await expect(postOpenPlanReports({ forge: fakeForge().forge, branch: "main", job: "plan", artifactDir: ".chant/pr", env: REPORT.env })).rejects.toThrow(/it is for GitLab/);
|
|
232
|
+
});
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
describe("chant components pr-post --open refuses what it cannot post for (#4328)", () => {
|
|
236
|
+
|
|
237
|
+
test("the flags parse", () => {
|
|
238
|
+
expect(parseArgs(["components", "pr-post", "--open", "--env", "prod", "--forge", "gitlab", "--branch", "main"])).toMatchObject({ open: true, env: "prod", forge: "gitlab", branch: "main" });
|
|
239
|
+
});
|
|
240
|
+
});
|
package/src/pr-post.ts
CHANGED
|
@@ -14,9 +14,16 @@
|
|
|
14
14
|
* every hidden HTML comment after its first line is defused, so a forged
|
|
15
15
|
* marker cannot claim another environment's note. A plan job that failed
|
|
16
16
|
* fails the status whatever its report says.
|
|
17
|
+
*
|
|
18
|
+
* On GitLab the post runs in a pipeline of the target branch (#4328): a
|
|
19
|
+
* merge request pipeline is built from the merge request's own files, so a
|
|
20
|
+
* job there that holds `CHANT_FORGE_TOKEN` runs whatever the change wrote.
|
|
21
|
+
* `pr-post --open` ({@link postOpenPlanReports}) asks GitLab for each open
|
|
22
|
+
* merge request's plan job and reads the report from its artifacts.
|
|
17
23
|
*/
|
|
18
24
|
|
|
19
|
-
import { existsSync, readFileSync } from "node:fs";
|
|
25
|
+
import { existsSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
26
|
+
import { tmpdir } from "node:os";
|
|
20
27
|
import { join } from "node:path";
|
|
21
28
|
import { prNoteMarker, prStatusContext, prStatusDescription, prStatusState, renderPrNote, type PrOverdue, type PrReport } from "./pr-loop";
|
|
22
29
|
import { forgePrincipalOf, type PrForge } from "./pr-forge";
|
|
@@ -130,7 +137,7 @@ export async function postPlanReport(input: PlanPostInput): Promise<PlanPostResu
|
|
|
130
137
|
state = prStatusState(report);
|
|
131
138
|
description = prStatusDescription(report);
|
|
132
139
|
// A member the change does not reach has nothing to say on the pull request (as pr-plan's own publish).
|
|
133
|
-
if (!(input.member && report.status === "nothing" && !report.overdue?.length)) note = planNoteFor(report, input.forge, prNoteMarker(input.env, input.member));
|
|
140
|
+
if (!(input.member && report.status === "nothing" && !report.overdue?.length && !report.migrations?.length)) note = planNoteFor(report, input.forge, prNoteMarker(input.env, input.member));
|
|
134
141
|
} catch (err) {
|
|
135
142
|
refused = `the plan report does not render: ${err instanceof Error ? err.message : String(err)}`;
|
|
136
143
|
state = "failure";
|
|
@@ -157,3 +164,84 @@ export async function postPlanReport(input: PlanPostInput): Promise<PlanPostResu
|
|
|
157
164
|
}
|
|
158
165
|
return { state, description: cut(description), noted: note !== undefined, ...(refused ? { refused } : {}), warnings };
|
|
159
166
|
}
|
|
167
|
+
|
|
168
|
+
/** What {@link postOpenPlanReports} posts for. */
|
|
169
|
+
export interface OpenPlanPostInput {
|
|
170
|
+
/** A GitLab client with {@link PrForge.mergeRequestJobs}, {@link PrForge.jobArtifact} and {@link PrForge.statusUrl}. */
|
|
171
|
+
forge: PrForge;
|
|
172
|
+
/** The branch the merge requests merge into: the pipeline's own. */
|
|
173
|
+
branch: string;
|
|
174
|
+
/** The plan job's name in the merge request pipeline: `plan`, or `<member>-plan`. */
|
|
175
|
+
job: string;
|
|
176
|
+
/** The report directory from the repository root, as the plan job kept it as its artifact. */
|
|
177
|
+
artifactDir: string;
|
|
178
|
+
env: string;
|
|
179
|
+
member?: string;
|
|
180
|
+
overdue?: () => Promise<PrOverdue[]>;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/** What {@link postOpenPlanReports} did for one merge request. */
|
|
184
|
+
export interface OpenPlanPost {
|
|
185
|
+
pr: number;
|
|
186
|
+
head: string;
|
|
187
|
+
/** What was posted, or why nothing was: the plan job has not settled, or its result is already posted. */
|
|
188
|
+
posted?: PlanPostResult;
|
|
189
|
+
skipped?: string;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Post the plan note and status of every open merge request into `branch`
|
|
194
|
+
* whose plan job settled (#4328), from a pipeline of `branch`. GitLab says
|
|
195
|
+
* which merge requests are open, their heads, and their newest pipeline's
|
|
196
|
+
* plan job; the report is the one file `pr-plan.json` of that job's
|
|
197
|
+
* artifacts, read as data as {@link postPlanReport} reads any. The status
|
|
198
|
+
* links to the plan job, and a status that already links to it is the
|
|
199
|
+
* job's result posted, so a job is posted for once. The overdue check runs
|
|
200
|
+
* once for every merge request.
|
|
201
|
+
*/
|
|
202
|
+
export async function postOpenPlanReports(input: OpenPlanPostInput): Promise<{ posts: OpenPlanPost[]; warnings: string[] }> {
|
|
203
|
+
const { forge } = input;
|
|
204
|
+
if (!forge.mergeRequestJobs || !forge.jobArtifact || !forge.statusUrl) {
|
|
205
|
+
throw new Error(`pr-post --open reads the plan jobs of open merge requests, which ${forge.kind} cannot say; it is for GitLab`);
|
|
206
|
+
}
|
|
207
|
+
const context = prStatusContext("plan", input.member);
|
|
208
|
+
const warnings: string[] = [];
|
|
209
|
+
let overdue: Promise<PrOverdue[]> | undefined;
|
|
210
|
+
const overdueOnce = input.overdue ? () => (overdue ??= input.overdue!()) : undefined;
|
|
211
|
+
const posts: OpenPlanPost[] = [];
|
|
212
|
+
for (const job of await forge.mergeRequestJobs(input.branch, input.job)) {
|
|
213
|
+
const settled = job.status === "success" ? "success" : job.status === "failed" ? "failure" : job.status === "canceled" ? "cancelled" : undefined;
|
|
214
|
+
if (!settled) {
|
|
215
|
+
posts.push({ pr: job.pr, head: job.head, skipped: `the plan job is ${job.status}` });
|
|
216
|
+
continue;
|
|
217
|
+
}
|
|
218
|
+
try {
|
|
219
|
+
if (job.url && (await forge.statusUrl(job.head, context)) === job.url) {
|
|
220
|
+
posts.push({ pr: job.pr, head: job.head, skipped: "this plan job's result is already posted" });
|
|
221
|
+
continue;
|
|
222
|
+
}
|
|
223
|
+
const dir = mkdtempSync(join(tmpdir(), "chant-pr-post-"));
|
|
224
|
+
try {
|
|
225
|
+
const text = await forge.jobArtifact(job.id, `${input.artifactDir.replace(/\/+$/, "")}/${PR_PLAN_REPORT_FILE}`);
|
|
226
|
+
if (text !== undefined) writeFileSync(join(dir, PR_PLAN_REPORT_FILE), text);
|
|
227
|
+
const posted = await postPlanReport({
|
|
228
|
+
// The status links to the plan job it reports on, which is how the next run knows it is posted.
|
|
229
|
+
forge: job.url ? { ...forge, runUrl: job.url } : forge,
|
|
230
|
+
dir,
|
|
231
|
+
pr: job.pr,
|
|
232
|
+
head: job.head,
|
|
233
|
+
env: input.env,
|
|
234
|
+
...(input.member ? { member: input.member } : {}),
|
|
235
|
+
result: settled,
|
|
236
|
+
...(overdueOnce ? { overdue: overdueOnce } : {}),
|
|
237
|
+
});
|
|
238
|
+
posts.push({ pr: job.pr, head: job.head, posted });
|
|
239
|
+
} finally {
|
|
240
|
+
rmSync(dir, { recursive: true, force: true });
|
|
241
|
+
}
|
|
242
|
+
} catch (err) {
|
|
243
|
+
warnings.push(`!${job.pr}: ${err instanceof Error ? err.message : String(err)}`);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
return { posts, warnings };
|
|
247
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { FilterError, matchesWhere, parseInstant, parseSince, parseWhere, type FilterFields } from "./read-filter";
|
|
3
|
+
|
|
4
|
+
const fields: FilterFields = { name: "text", count: "number", ok: "boolean", at: "time", labels: "json", "unit.id": "text" };
|
|
5
|
+
const now = new Date("2026-10-23T12:00:00.000Z");
|
|
6
|
+
const keep = (rows: { name: string }[], ...where: string[]) => rows.filter((r) => matchesWhere(r, parseWhere(where, fields, now))).map((r) => r.name);
|
|
7
|
+
|
|
8
|
+
describe("typed filters (#4363)", () => {
|
|
9
|
+
const rows = [
|
|
10
|
+
{ name: "web", count: 3, ok: true, at: "2026-10-22T00:00:00.000Z", labels: { team: "core", size: 2 }, unit: { id: "W-1" } },
|
|
11
|
+
{ name: "worker", count: 10, ok: false, at: "2026-10-01T00:00:00.000Z", labels: '{"team":"edge","size":12}', unit: null },
|
|
12
|
+
{ name: "db", count: null, ok: true, at: null, labels: null, unit: { id: "W-2" } },
|
|
13
|
+
];
|
|
14
|
+
|
|
15
|
+
it("filters each type with its operators, every clause holding", () => {
|
|
16
|
+
expect(keep(rows, "name=web")).toEqual(["web"]);
|
|
17
|
+
expect(keep(rows, "name~w")).toEqual(["web", "worker"]);
|
|
18
|
+
expect(keep(rows, "count>=3", "count<10")).toEqual(["web"]);
|
|
19
|
+
expect(keep(rows, "ok=false")).toEqual(["worker"]);
|
|
20
|
+
expect(keep(rows, "at>7d")).toEqual(["web"]);
|
|
21
|
+
expect(keep(rows, "at<2026-10-10")).toEqual(["worker"]);
|
|
22
|
+
expect(keep(rows, "unit.id=W-2")).toEqual(["db"]);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it("reads a key under a json field, parsing JSON text, as the type it holds", () => {
|
|
26
|
+
expect(keep(rows, "labels.team=edge")).toEqual(["worker"]);
|
|
27
|
+
expect(keep(rows, "labels.size>5")).toEqual(["worker"]);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it("matches null with = and !=, and a null field only with !=", () => {
|
|
31
|
+
expect(keep(rows, "count=null")).toEqual(["db"]);
|
|
32
|
+
expect(keep(rows, "count!=null")).toEqual(["web", "worker"]);
|
|
33
|
+
expect(keep(rows, "count!=3")).toEqual(["worker", "db"]);
|
|
34
|
+
expect(keep(rows, "count<100")).toEqual(["web", "worker"]);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it("refuses a field, operator or value that doesn't fit, before reading", () => {
|
|
38
|
+
expect(() => parseWhere(["nope=1"], fields)).toThrow(/no such field; this read filters on name, count/);
|
|
39
|
+
expect(() => parseWhere(["count=many"], fields)).toThrow(FilterError);
|
|
40
|
+
expect(() => parseWhere(["ok>true"], fields)).toThrow(/= or != only/);
|
|
41
|
+
expect(() => parseWhere(["count~1"], fields)).toThrow(/takes a text field/);
|
|
42
|
+
expect(() => parseWhere(["at=yesterday"], fields)).toThrow(/is a time/);
|
|
43
|
+
expect(() => parseWhere(["labels=x"], fields)).toThrow(/name a key under it/);
|
|
44
|
+
expect(() => parseWhere(["name>=null"], fields)).toThrow(/null takes = or != only/);
|
|
45
|
+
expect(() => parseWhere(["just-a-word"], fields)).toThrow(/<field><op><value>/);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it("reads --since as an ISO time or a span back from now", () => {
|
|
49
|
+
expect(parseSince("7d", now)).toBe("2026-10-16T12:00:00.000Z");
|
|
50
|
+
expect(parseSince("2026-10-01", now)).toBe("2026-10-01T00:00:00.000Z");
|
|
51
|
+
expect(parseInstant("90m", now)).toBe(now.getTime() - 90 * 60_000);
|
|
52
|
+
expect(() => parseSince("last week", now)).toThrow(FilterError);
|
|
53
|
+
});
|
|
54
|
+
});
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Typed filters for the contract reads (#4363): `--where <field><op><value>`
|
|
3
|
+
* and `--since <time>`, in place of an embedded SQL engine.
|
|
4
|
+
*
|
|
5
|
+
* Each read declares the fields it can be filtered on and their types
|
|
6
|
+
* ({@link FilterFields}). A clause naming a field the read doesn't declare, an
|
|
7
|
+
* operator its type doesn't take, or a value that isn't of its type is a
|
|
8
|
+
* {@link FilterError} before anything is read, so a typo never filters to an
|
|
9
|
+
* empty answer. Several `--where` clauses all have to hold.
|
|
10
|
+
*
|
|
11
|
+
* Operators: `=` and `!=` for every type; `<`, `<=`, `>` and `>=` for
|
|
12
|
+
* numbers, times and text (text compares by code point); `~` for text, which
|
|
13
|
+
* holds when the field contains the value. The value `null` matches a field
|
|
14
|
+
* that is null or absent, with `=` and `!=` only.
|
|
15
|
+
*
|
|
16
|
+
* A field of type `json` holds an object: a clause can name a key under it
|
|
17
|
+
* with dots (`labels.team=core`), and the value is compared as the type it
|
|
18
|
+
* holds there (a number with a number, a boolean with `true` or `false`, any
|
|
19
|
+
* other value as text). A JSON text is parsed first, so a ledger row that
|
|
20
|
+
* keeps the object as text filters the same way.
|
|
21
|
+
*
|
|
22
|
+
* A null or absent field matches only `=null` and `!=` with a value other
|
|
23
|
+
* than null; the other operators never hold for it.
|
|
24
|
+
*
|
|
25
|
+
* Pure: no I/O, so a bundle that filters rows loads nothing else.
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
export type FilterFieldType = "text" | "number" | "boolean" | "time" | "json";
|
|
29
|
+
|
|
30
|
+
/** The fields a read can be filtered on, by name, with their types. */
|
|
31
|
+
export type FilterFields = Readonly<Record<string, FilterFieldType>>;
|
|
32
|
+
|
|
33
|
+
/** Every operator, longest first so `!=` is not read as `=`. */
|
|
34
|
+
export const WHERE_OPERATORS = ["!=", "<=", ">=", "=", "<", ">", "~"] as const;
|
|
35
|
+
export type WhereOperator = (typeof WHERE_OPERATORS)[number];
|
|
36
|
+
|
|
37
|
+
/** One parsed `--where` clause. */
|
|
38
|
+
export interface WhereClause {
|
|
39
|
+
/** The field as written, with any path under a `json` field. */
|
|
40
|
+
field: string;
|
|
41
|
+
/** The declared field the clause starts at. */
|
|
42
|
+
base: string;
|
|
43
|
+
/** The keys under a `json` field, or `[]`. */
|
|
44
|
+
path: string[];
|
|
45
|
+
type: FilterFieldType;
|
|
46
|
+
op: WhereOperator;
|
|
47
|
+
/** The value as written. */
|
|
48
|
+
raw: string;
|
|
49
|
+
/** The value read as the field's type: a number for `number` and `time` (epoch ms), a boolean, a string, or null. */
|
|
50
|
+
value: string | number | boolean | null;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** A clause or a `--since` that can't be applied. The message is for a person. */
|
|
54
|
+
export class FilterError extends Error {}
|
|
55
|
+
|
|
56
|
+
const ORDERED: readonly WhereOperator[] = ["<", "<=", ">", ">="];
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* An instant from an ISO 8601 date or time, or from a span back from `now`:
|
|
60
|
+
* `<n>m`, `<n>h`, `<n>d` or `<n>w`. Epoch milliseconds, or null when `text`
|
|
61
|
+
* is neither.
|
|
62
|
+
*/
|
|
63
|
+
export function parseInstant(text: string, now: Date = new Date()): number | null {
|
|
64
|
+
const span = /^(\d+)([mhdw])$/.exec(text.trim());
|
|
65
|
+
if (span) {
|
|
66
|
+
const unit = { m: 60_000, h: 3_600_000, d: 86_400_000, w: 604_800_000 }[span[2] as "m" | "h" | "d" | "w"];
|
|
67
|
+
return now.getTime() - Number(span[1]) * unit;
|
|
68
|
+
}
|
|
69
|
+
if (!/^\d{4}-\d{2}-\d{2}/.test(text.trim())) return null;
|
|
70
|
+
const ms = Date.parse(text);
|
|
71
|
+
return Number.isNaN(ms) ? null : ms;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** `--since <time>` as an ISO instant, or a {@link FilterError}. */
|
|
75
|
+
export function parseSince(text: string, now: Date = new Date()): string {
|
|
76
|
+
const ms = parseInstant(text, now);
|
|
77
|
+
if (ms === null) throw new FilterError(`--since takes an ISO 8601 date or time, or a span back from now such as 24h or 7d, not ${JSON.stringify(text)}`);
|
|
78
|
+
return new Date(ms).toISOString();
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** The declared field a dotted name starts at: the whole name, or its longest declared `json` prefix. */
|
|
82
|
+
function resolveField(name: string, fields: FilterFields): { base: string; path: string[]; type: FilterFieldType } | null {
|
|
83
|
+
if (Object.prototype.hasOwnProperty.call(fields, name)) return { base: name, path: [], type: fields[name] };
|
|
84
|
+
const parts = name.split(".");
|
|
85
|
+
for (let i = parts.length - 1; i > 0; i--) {
|
|
86
|
+
const base = parts.slice(0, i).join(".");
|
|
87
|
+
if (Object.prototype.hasOwnProperty.call(fields, base) && fields[base] === "json") return { base, path: parts.slice(i), type: "json" };
|
|
88
|
+
}
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** Parse each `--where` against the fields a read declares. Throws a {@link FilterError}. */
|
|
93
|
+
export function parseWhere(exprs: readonly string[], fields: FilterFields, now: Date = new Date()): WhereClause[] {
|
|
94
|
+
const names = Object.keys(fields).join(", ");
|
|
95
|
+
return exprs.map((expr) => {
|
|
96
|
+
const m = /^([A-Za-z_][\w.-]*)\s*(!=|<=|>=|=|<|>|~)(.*)$/s.exec(expr);
|
|
97
|
+
if (!m) throw new FilterError(`--where takes <field><op><value>, with op one of ${WHERE_OPERATORS.join(" ")}, not ${JSON.stringify(expr)}`);
|
|
98
|
+
const [, field, op, rawValue] = m as unknown as [string, string, WhereOperator, string];
|
|
99
|
+
const raw = rawValue.trim();
|
|
100
|
+
const found = resolveField(field, fields);
|
|
101
|
+
if (!found) throw new FilterError(`--where ${field}: no such field; this read filters on ${names}`);
|
|
102
|
+
const { base, path, type } = found;
|
|
103
|
+
if (type === "json" && path.length === 0) throw new FilterError(`--where ${field}: holds an object; name a key under it, such as ${field}.<key>`);
|
|
104
|
+
const clause = (value: WhereClause["value"]): WhereClause => ({ field, base, path, type, op, raw, value });
|
|
105
|
+
if (raw === "null") {
|
|
106
|
+
if (op !== "=" && op !== "!=") throw new FilterError(`--where ${field}${op}null: null takes = or != only`);
|
|
107
|
+
return clause(null);
|
|
108
|
+
}
|
|
109
|
+
if (op === "~" && type !== "text" && type !== "json") throw new FilterError(`--where ${field}~: ~ (contains) takes a text field; ${field} is ${type}`);
|
|
110
|
+
switch (type) {
|
|
111
|
+
case "number": {
|
|
112
|
+
const n = Number(raw);
|
|
113
|
+
if (raw === "" || !Number.isFinite(n)) throw new FilterError(`--where ${field}: ${field} is a number, not ${JSON.stringify(raw)}`);
|
|
114
|
+
return clause(n);
|
|
115
|
+
}
|
|
116
|
+
case "boolean": {
|
|
117
|
+
if (ORDERED.includes(op)) throw new FilterError(`--where ${field}${op}: a boolean takes = or != only`);
|
|
118
|
+
if (raw !== "true" && raw !== "false") throw new FilterError(`--where ${field}: ${field} is true or false, not ${JSON.stringify(raw)}`);
|
|
119
|
+
return clause(raw === "true");
|
|
120
|
+
}
|
|
121
|
+
case "time": {
|
|
122
|
+
const ms = parseInstant(raw, now);
|
|
123
|
+
if (ms === null) throw new FilterError(`--where ${field}: ${field} is a time; give an ISO 8601 date or time, or a span back from now such as 24h or 7d, not ${JSON.stringify(raw)}`);
|
|
124
|
+
return clause(ms);
|
|
125
|
+
}
|
|
126
|
+
default:
|
|
127
|
+
return clause(raw);
|
|
128
|
+
}
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/** The value at a clause's field in `row`, parsing a JSON text on the way into a `json` field. */
|
|
133
|
+
function valueAt(row: unknown, c: WhereClause): unknown {
|
|
134
|
+
let v: unknown = row;
|
|
135
|
+
for (const key of c.base.split(".")) v = v !== null && typeof v === "object" ? (v as Record<string, unknown>)[key] : undefined;
|
|
136
|
+
for (const key of c.path) {
|
|
137
|
+
if (typeof v === "string") {
|
|
138
|
+
try {
|
|
139
|
+
v = JSON.parse(v);
|
|
140
|
+
} catch {
|
|
141
|
+
return undefined;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
v = v !== null && typeof v === "object" && !Array.isArray(v) ? (v as Record<string, unknown>)[key] : undefined;
|
|
145
|
+
}
|
|
146
|
+
return v;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function compare(a: number | string, b: number | string, op: WhereOperator): boolean {
|
|
150
|
+
switch (op) {
|
|
151
|
+
case "=": return a === b;
|
|
152
|
+
case "!=": return a !== b;
|
|
153
|
+
case "<": return a < b;
|
|
154
|
+
case "<=": return a <= b;
|
|
155
|
+
case ">": return a > b;
|
|
156
|
+
case ">=": return a >= b;
|
|
157
|
+
case "~": return String(a).includes(String(b));
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/** Whether one clause holds for `row`. */
|
|
162
|
+
export function clauseHolds(row: unknown, c: WhereClause): boolean {
|
|
163
|
+
const v = valueAt(row, c);
|
|
164
|
+
if (c.value === null) return c.op === "=" ? v === null || v === undefined : v !== null && v !== undefined;
|
|
165
|
+
if (v === null || v === undefined) return c.op === "!=";
|
|
166
|
+
switch (c.type) {
|
|
167
|
+
case "number":
|
|
168
|
+
return typeof v === "number" && compare(v, c.value as number, c.op);
|
|
169
|
+
case "boolean":
|
|
170
|
+
return typeof v === "boolean" ? (c.op === "=") === (v === c.value) : false;
|
|
171
|
+
case "time": {
|
|
172
|
+
const ms = typeof v === "string" ? Date.parse(v) : typeof v === "number" ? v : NaN;
|
|
173
|
+
return !Number.isNaN(ms) && compare(ms, c.value as number, c.op);
|
|
174
|
+
}
|
|
175
|
+
case "text":
|
|
176
|
+
return typeof v === "string" && compare(v, c.value as string, c.op);
|
|
177
|
+
case "json": {
|
|
178
|
+
// Compared as the type the value holds there.
|
|
179
|
+
if (typeof v === "number" && c.op !== "~") {
|
|
180
|
+
const n = Number(c.raw);
|
|
181
|
+
return Number.isFinite(n) && compare(v, n, c.op);
|
|
182
|
+
}
|
|
183
|
+
if (typeof v === "boolean") return (c.raw === "true" || c.raw === "false") && (c.op === "=" || c.op === "!=") ? (c.op === "=") === (String(v) === c.raw) : false;
|
|
184
|
+
const s = typeof v === "string" ? v : JSON.stringify(v);
|
|
185
|
+
return compare(s, c.raw, c.op);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/** Whether every clause holds for `row`. */
|
|
191
|
+
export function matchesWhere(row: unknown, clauses: readonly WhereClause[]): boolean {
|
|
192
|
+
return clauses.every((c) => clauseHolds(row, c));
|
|
193
|
+
}
|
package/src/run-event.ts
CHANGED
|
@@ -58,7 +58,7 @@ export type RunEventStepStatus = "ok" | "failed" | "skipped" | "cached" | "gated
|
|
|
58
58
|
export type RunEventSkipReason = "receipt" | "resumed" | "refused" | "not-run";
|
|
59
59
|
|
|
60
60
|
/** How a gate's approval passed, when it was more than one person's approval of that gate. */
|
|
61
|
-
export type RunEventGateVia = "quorum" | "policy" | "point" | "alias" | "empty";
|
|
61
|
+
export type RunEventGateVia = "quorum" | "policy" | "point" | "alias" | "empty" | "workspace";
|
|
62
62
|
|
|
63
63
|
/** The run every event of a stream names: the same on every line. */
|
|
64
64
|
export interface RunEventRun {
|