@intentius/chant 0.124.46 → 0.124.48
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/__golden__/wave-loop/op-by-apply-after-fails.json +10 -0
- package/dist/__golden__/wave-loop/op-by-apply-hold.json +10 -0
- package/dist/__golden__/wave-loop/op-by-apply-ignored.json +10 -0
- package/dist/__golden__/wave-loop/op-by-apply-pass.json +10 -0
- package/dist/__golden__/wave-loop/op-by-apply-skip.json +10 -0
- package/dist/__golden__/wave-loop/op-cost-member-clash.json +6 -0
- package/dist/__golden__/wave-loop/op-cost-member.json +13 -0
- package/dist/__golden__/wave-loop/op-denied-source.json +16 -0
- package/dist/__golden__/wave-loop/op-denied.json +16 -0
- package/dist/__golden__/wave-loop/op-discover-rest-short.json +6 -0
- package/dist/__golden__/wave-loop/op-discover-rest-stops.json +13 -0
- package/dist/__golden__/wave-loop/op-discover-rest.json +26 -0
- package/dist/__golden__/wave-loop/op-discover-wide.json +26 -0
- package/dist/__golden__/wave-loop/op-held-on-again.json +16 -0
- package/dist/__golden__/wave-loop/op-held-on-before-apply.json +16 -0
- package/dist/__golden__/wave-loop/op-held-on.json +16 -0
- package/dist/__golden__/wave-loop/op-hooks-apply-fails.json +13 -0
- package/dist/__golden__/wave-loop/op-hooks-fail-after-apply.json +13 -0
- package/dist/__golden__/wave-loop/op-hooks-fail-before-apply.json +13 -0
- package/dist/__golden__/wave-loop/op-hooks-fail-before-plan.json +13 -0
- package/dist/__golden__/wave-loop/op-hooks-held.json +13 -0
- package/dist/__golden__/wave-loop/op-hooks-hold-before-apply.json +13 -0
- package/dist/__golden__/wave-loop/op-hooks-pass.json +13 -0
- package/dist/__golden__/wave-loop/op-hooks-refused-at-base.json +13 -0
- package/dist/__golden__/wave-loop/op-hooks-share.json +33 -0
- package/dist/__golden__/wave-loop/op-outcome-held.json +10 -0
- package/dist/__golden__/wave-loop/op-outcome-replan-approved.json +10 -0
- package/dist/__golden__/wave-loop/op-outcome-replan-forever.json +10 -0
- package/dist/__golden__/wave-loop/op-outcome-replan.json +10 -0
- package/dist/__golden__/wave-loop/op-outcome-stood-down.json +10 -0
- package/dist/__golden__/wave-loop/op-outcome-unreadable.json +10 -0
- package/dist/__golden__/wave-loop/op-overridden.json +16 -0
- package/dist/__golden__/wave-loop/op-per-run.json +20 -0
- package/dist/__golden__/wave-loop/op-point.json +13 -0
- package/dist/__golden__/wave-loop/op-pr-review.json +10 -0
- package/dist/__golden__/wave-loop/op-sealed.json +10 -0
- package/dist/__golden__/wave-loop/op-spend-change-only.json +10 -0
- package/dist/__golden__/wave-loop/op-spend.json +10 -0
- package/dist/__golden__/wave-loop/op-wave-commands.json +55 -0
- package/dist/__golden__/wave-loop/op-wide-refused.json +26 -0
- package/dist/__golden__/wave-loop/op-wide-waiting.json +29 -0
- package/dist/__golden__/wave-loop/op-wide.json +39 -0
- package/dist/action-pins.d.ts +61 -0
- package/dist/action-pins.d.ts.map +1 -0
- package/dist/action-pins.js +77 -0
- package/dist/cli/commands/carve.d.ts +3 -0
- package/dist/cli/commands/carve.d.ts.map +1 -1
- package/dist/cli/commands/carve.js +36 -7
- package/dist/cli/commands/import-report.d.ts +26 -0
- package/dist/cli/commands/import-report.d.ts.map +1 -0
- package/dist/cli/commands/import-report.js +65 -0
- package/dist/cli/commands/import.d.ts +11 -1
- package/dist/cli/commands/import.d.ts.map +1 -1
- package/dist/cli/commands/import.js +22 -3
- package/dist/cli/commands/migrate.d.ts +13 -1
- package/dist/cli/commands/migrate.d.ts.map +1 -1
- package/dist/cli/commands/migrate.js +134 -10
- package/dist/cli/handlers/approve.d.ts.map +1 -1
- package/dist/cli/handlers/approve.js +21 -2
- package/dist/cli/handlers/build.d.ts.map +1 -1
- package/dist/cli/handlers/build.js +6 -1
- package/dist/cli/handlers/ledger.d.ts +3 -0
- package/dist/cli/handlers/ledger.d.ts.map +1 -0
- package/dist/cli/handlers/ledger.js +55 -0
- package/dist/cli/handlers/migrate.d.ts.map +1 -1
- package/dist/cli/handlers/migrate.js +18 -11
- package/dist/cli/handlers/misc.d.ts.map +1 -1
- package/dist/cli/handlers/misc.js +12 -1
- package/dist/cli/handlers/pr-env.js +2 -2
- package/dist/cli/handlers/pr.d.ts +15 -1
- package/dist/cli/handlers/pr.d.ts.map +1 -1
- package/dist/cli/handlers/pr.js +143 -39
- package/dist/cli/handlers/query.d.ts +7 -2
- package/dist/cli/handlers/query.d.ts.map +1 -1
- package/dist/cli/handlers/query.js +16 -4
- package/dist/cli/handlers/release.d.ts.map +1 -1
- package/dist/cli/handlers/release.js +6 -3
- package/dist/cli/handlers/search.d.ts +2 -4
- package/dist/cli/handlers/search.d.ts.map +1 -1
- package/dist/cli/handlers/search.js +13 -39
- package/dist/cli/main.d.ts.map +1 -1
- package/dist/cli/main.js +65 -14
- package/dist/cli/parse-args.d.ts.map +1 -1
- package/dist/cli/parse-args.js +36 -2
- package/dist/cli/registry.d.ts +14 -4
- package/dist/cli/registry.d.ts.map +1 -1
- package/dist/components/builders.d.ts +5 -1
- package/dist/components/builders.d.ts.map +1 -1
- package/dist/components/builders.js +5 -0
- package/dist/components/capability-plugin-loader.d.ts.map +1 -1
- package/dist/components/capability-plugin-loader.js +16 -1
- package/dist/components/capability-plugin.d.ts +14 -0
- package/dist/components/capability-plugin.d.ts.map +1 -1
- package/dist/components/pr-hooks.d.ts +21 -1
- package/dist/components/pr-hooks.d.ts.map +1 -1
- package/dist/components/pr-hooks.js +72 -4
- package/dist/components/pr-pipeline.d.ts +45 -10
- package/dist/components/pr-pipeline.d.ts.map +1 -1
- package/dist/components/pr-pipeline.js +47 -14
- package/dist/components/pr-policy.d.ts +36 -0
- package/dist/components/pr-policy.d.ts.map +1 -1
- package/dist/components/pr-policy.js +36 -0
- package/dist/components/starter-plugin.d.ts +1 -0
- package/dist/components/starter-plugin.d.ts.map +1 -1
- package/dist/components/starter-plugin.js +5 -1
- package/dist/components/verbs/index.d.ts +2 -0
- package/dist/components/verbs/index.d.ts.map +1 -1
- package/dist/components/verbs/index.js +2 -0
- package/dist/components/verbs/publish-artifact.d.ts +65 -0
- package/dist/components/verbs/publish-artifact.d.ts.map +1 -0
- package/dist/components/verbs/publish-artifact.js +74 -0
- package/dist/components/verbs/publish-image.d.ts +129 -0
- package/dist/components/verbs/publish-image.d.ts.map +1 -0
- package/dist/components/verbs/publish-image.js +145 -0
- package/dist/config-environments.d.ts +20 -0
- package/dist/config-environments.d.ts.map +1 -1
- package/dist/config-environments.js +12 -0
- package/dist/config.d.ts +9 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +6 -2
- package/dist/gate-decision.d.ts +11 -2
- package/dist/gate-decision.d.ts.map +1 -1
- package/dist/gate-decision.js +15 -3
- package/dist/graph-ir.d.ts +25 -0
- package/dist/graph-ir.d.ts.map +1 -1
- package/dist/graph-walk.d.ts +66 -0
- package/dist/graph-walk.d.ts.map +1 -0
- package/dist/graph-walk.js +70 -0
- package/dist/import/parser.d.ts +19 -0
- package/dist/import/parser.d.ts.map +1 -1
- package/dist/lexicon.d.ts +71 -6
- package/dist/lexicon.d.ts.map +1 -1
- package/dist/lifecycle/gate-ledger.d.ts +11 -0
- package/dist/lifecycle/gate-ledger.d.ts.map +1 -1
- package/dist/lifecycle/git.d.ts +2 -5
- package/dist/lifecycle/git.d.ts.map +1 -1
- package/dist/lifecycle/ledger-read.d.ts +65 -0
- package/dist/lifecycle/ledger-read.d.ts.map +1 -0
- package/dist/lifecycle/ledger-read.js +160 -0
- package/dist/lifecycle/release-workstation.d.ts +7 -0
- package/dist/lifecycle/release-workstation.d.ts.map +1 -1
- package/dist/lifecycle/release-workstation.js +25 -0
- package/dist/lifecycle/run-ledger.d.ts +4 -3
- package/dist/lifecycle/run-ledger.d.ts.map +1 -1
- package/dist/lifecycle/run-ledger.js +1 -0
- package/dist/lint/discover.js +3 -1
- package/dist/lint/post-synth.d.ts +127 -1
- package/dist/lint/post-synth.d.ts.map +1 -1
- package/dist/lint/post-synth.js +84 -2
- package/dist/lint/rules/op/index.d.ts +1 -0
- package/dist/lint/rules/op/index.d.ts.map +1 -1
- package/dist/lint/rules/op/index.js +2 -0
- package/dist/lint/rules/op/ops019-workspace-op-target.d.ts +32 -0
- package/dist/lint/rules/op/ops019-workspace-op-target.d.ts.map +1 -0
- package/dist/lint/rules/op/ops019-workspace-op-target.js +63 -0
- package/dist/op/activities/activity-contracts.d.ts +5 -2
- package/dist/op/activities/activity-contracts.d.ts.map +1 -1
- package/dist/op/activities/activity-contracts.js +5 -2
- package/dist/op/activities/index.d.ts +2 -2
- package/dist/op/activities/index.d.ts.map +1 -1
- package/dist/op/activities/index.js +1 -1
- package/dist/op/activities/review-agent.d.ts +90 -4
- package/dist/op/activities/review-agent.d.ts.map +1 -1
- package/dist/op/activities/review-agent.js +217 -19
- package/dist/op/activities/work-evidence.d.ts +8 -1
- package/dist/op/activities/work-evidence.d.ts.map +1 -1
- package/dist/op/activities/work-evidence.js +1 -0
- package/dist/op/builders.d.ts +9 -5
- package/dist/op/builders.d.ts.map +1 -1
- package/dist/op/builders.js +9 -5
- package/dist/op/composites/apply-op.d.ts +29 -2
- package/dist/op/composites/apply-op.d.ts.map +1 -1
- package/dist/op/composites/apply-op.js +20 -1
- package/dist/op/gate-approval.d.ts +11 -5
- package/dist/op/gate-approval.d.ts.map +1 -1
- package/dist/op/gate-approval.js +11 -6
- package/dist/op/gate-quorum.d.ts +100 -3
- package/dist/op/gate-quorum.d.ts.map +1 -1
- package/dist/op/gate-quorum.js +108 -6
- package/dist/op/gate-review.d.ts +9 -0
- package/dist/op/gate-review.d.ts.map +1 -1
- package/dist/op/gate-review.js +13 -0
- package/dist/op/gate.d.ts +44 -3
- package/dist/op/gate.d.ts.map +1 -1
- package/dist/op/gate.js +25 -4
- package/dist/op/index.d.ts +3 -3
- package/dist/op/index.d.ts.map +1 -1
- package/dist/op/index.js +1 -1
- package/dist/op/local-executor.d.ts +38 -3
- package/dist/op/local-executor.d.ts.map +1 -1
- package/dist/op/local-executor.js +48 -6
- package/dist/op/op-waves-run.d.ts +26 -1
- package/dist/op/op-waves-run.d.ts.map +1 -1
- package/dist/op/op-waves-run.js +259 -13
- package/dist/op/op-waves.d.ts +72 -5
- package/dist/op/op-waves.d.ts.map +1 -1
- package/dist/op/op-waves.js +110 -15
- package/dist/op/runtime.d.ts +11 -0
- package/dist/op/runtime.d.ts.map +1 -1
- package/dist/op/schedule-overdue.d.ts +16 -0
- package/dist/op/schedule-overdue.d.ts.map +1 -1
- package/dist/op/schedule-overdue.js +36 -0
- package/dist/op/stage-hooks.d.ts +9 -2
- package/dist/op/stage-hooks.d.ts.map +1 -1
- package/dist/op/stage-hooks.js +7 -5
- package/dist/op/workspace-run-env.d.ts +22 -0
- package/dist/op/workspace-run-env.d.ts.map +1 -0
- package/dist/op/workspace-run-env.js +21 -0
- package/dist/pr-forge.d.ts +24 -0
- package/dist/pr-forge.d.ts.map +1 -1
- package/dist/pr-forge.js +47 -0
- package/dist/pr-post.d.ts +42 -0
- package/dist/pr-post.d.ts.map +1 -1
- package/dist/pr-post.js +67 -1
- package/dist/read-filter.d.ts +65 -0
- package/dist/read-filter.d.ts.map +1 -0
- package/dist/read-filter.js +179 -0
- package/dist/run-event.d.ts +1 -1
- package/dist/run-event.d.ts.map +1 -1
- package/dist/terraform/carve.d.ts +24 -3
- package/dist/terraform/carve.d.ts.map +1 -1
- package/dist/terraform/carve.js +32 -14
- package/dist/terraform/cuts.d.ts +97 -0
- package/dist/terraform/cuts.d.ts.map +1 -0
- package/dist/terraform/cuts.js +138 -0
- package/dist/terraform/folds.d.ts +26 -0
- package/dist/terraform/folds.d.ts.map +1 -0
- package/dist/terraform/folds.js +38 -0
- package/dist/terraform/graph.d.ts.map +1 -1
- package/dist/terraform/graph.js +10 -0
- package/dist/terraform/reach.d.ts +140 -0
- package/dist/terraform/reach.d.ts.map +1 -0
- package/dist/terraform/reach.js +170 -0
- package/dist/terraform/score.d.ts +14 -0
- package/dist/terraform/score.d.ts.map +1 -1
- package/dist/terraform/score.js +14 -31
- package/dist/terraform/state.d.ts.map +1 -1
- package/dist/terraform/state.js +4 -2
- package/dist/terraform/types.d.ts +8 -0
- package/dist/terraform/types.d.ts.map +1 -1
- package/dist/wave-blocked.d.ts +10 -1
- package/dist/wave-blocked.d.ts.map +1 -1
- package/dist/wave-blocked.js +31 -3
- package/dist/wave-runtime.mjs +263 -254
- package/dist/workspace/check.schema.json +11 -2
- package/dist/workspace/checks/ci-branch.d.ts +44 -0
- package/dist/workspace/checks/ci-branch.d.ts.map +1 -0
- package/dist/workspace/checks/ci-branch.js +133 -0
- package/dist/workspace/checks/links.d.ts +3 -0
- package/dist/workspace/checks/links.d.ts.map +1 -1
- package/dist/workspace/checks/links.js +12 -1
- package/dist/workspace/checks/org.d.ts +48 -0
- package/dist/workspace/checks/org.d.ts.map +1 -0
- package/dist/workspace/checks/org.js +156 -0
- package/dist/workspace/checks.d.ts +11 -1
- package/dist/workspace/checks.d.ts.map +1 -1
- package/dist/workspace/checks.js +59 -7
- package/dist/workspace/ci-branches.d.ts +16 -0
- package/dist/workspace/ci-branches.d.ts.map +1 -1
- package/dist/workspace/ci-branches.js +27 -0
- package/dist/workspace/ci-green-cli.js +7 -7
- package/dist/workspace/ci-green-workflow.d.ts +1 -1
- package/dist/workspace/ci-green-workflow.d.ts.map +1 -1
- package/dist/workspace/ci-green-workflow.js +13 -8
- package/dist/workspace/ci-green.d.ts +14 -0
- package/dist/workspace/ci-green.d.ts.map +1 -1
- package/dist/workspace/ci-green.js +44 -11
- package/dist/workspace/ci-run.d.ts +9 -1
- package/dist/workspace/ci-run.d.ts.map +1 -1
- package/dist/workspace/ci-run.js +67 -6
- package/dist/workspace/ci-serve.d.ts +9 -0
- package/dist/workspace/ci-serve.d.ts.map +1 -1
- package/dist/workspace/ci-serve.js +114 -76
- package/dist/workspace/compose-graph.d.ts +14 -0
- package/dist/workspace/compose-graph.d.ts.map +1 -1
- package/dist/workspace/composites.d.ts +4 -1
- package/dist/workspace/composites.d.ts.map +1 -1
- package/dist/workspace/composites.js +5 -2
- package/dist/workspace/composites.schema.json +22 -0
- package/dist/workspace/conformance/__writer_fixture__/work/work.schema.json +105 -1
- package/dist/workspace/cross-root-lint.d.ts +56 -0
- package/dist/workspace/cross-root-lint.d.ts.map +1 -0
- package/dist/workspace/cross-root-lint.js +111 -0
- package/dist/workspace/dbt-models-reader.d.ts +44 -0
- package/dist/workspace/dbt-models-reader.d.ts.map +1 -0
- package/dist/workspace/dbt-models-reader.js +356 -0
- package/dist/workspace/dbt-project-common.d.ts +40 -0
- package/dist/workspace/dbt-project-common.d.ts.map +1 -0
- package/dist/workspace/dbt-project-common.js +106 -0
- package/dist/workspace/dbt-project-reader.d.ts +4 -3
- package/dist/workspace/dbt-project-reader.d.ts.map +1 -1
- package/dist/workspace/dbt-project-reader.js +14 -108
- package/dist/workspace/decision-rank.d.ts +26 -0
- package/dist/workspace/decision-rank.d.ts.map +1 -0
- package/dist/workspace/decision-rank.js +16 -0
- package/dist/workspace/declaration.d.ts +43 -2
- package/dist/workspace/declaration.d.ts.map +1 -1
- package/dist/workspace/declaration.js +45 -11
- package/dist/workspace/declaration.schema.json +133 -24
- package/dist/workspace/environments.d.ts +26 -2
- package/dist/workspace/environments.d.ts.map +1 -1
- package/dist/workspace/environments.js +26 -2
- package/dist/workspace/flyway-dir.d.ts +39 -0
- package/dist/workspace/flyway-dir.d.ts.map +1 -0
- package/dist/workspace/flyway-dir.js +104 -0
- package/dist/workspace/flyway-layout.d.ts +96 -0
- package/dist/workspace/flyway-layout.d.ts.map +1 -0
- package/dist/workspace/flyway-layout.js +308 -0
- package/dist/workspace/graph-cli.d.ts.map +1 -1
- package/dist/workspace/graph-cli.js +26 -8
- package/dist/workspace/graph.schema.json +34 -0
- package/dist/workspace/intent.d.ts +2 -3
- package/dist/workspace/intent.d.ts.map +1 -1
- package/dist/workspace/intent.js +5 -9
- package/dist/workspace/kind-link-readers.d.ts +59 -2
- package/dist/workspace/kind-link-readers.d.ts.map +1 -1
- package/dist/workspace/kind-link-readers.js +17 -1
- package/dist/workspace/kinds.d.ts +66 -2
- package/dist/workspace/kinds.d.ts.map +1 -1
- package/dist/workspace/kinds.js +98 -4
- package/dist/workspace/ledger.schema.json +158 -0
- package/dist/workspace/lexicon-outputs.d.ts +20 -1
- package/dist/workspace/lexicon-outputs.d.ts.map +1 -1
- package/dist/workspace/lexicon-outputs.js +102 -10
- package/dist/workspace/lineage-check.d.ts +3 -0
- package/dist/workspace/lineage-check.d.ts.map +1 -1
- package/dist/workspace/lineage-check.js +23 -4
- package/dist/workspace/links.d.ts +8 -1
- package/dist/workspace/links.d.ts.map +1 -1
- package/dist/workspace/links.js +25 -1
- package/dist/workspace/ls.d.ts +8 -0
- package/dist/workspace/ls.d.ts.map +1 -1
- package/dist/workspace/ls.js +7 -1
- package/dist/workspace/ls.schema.json +7 -0
- package/dist/workspace/member-commands.d.ts.map +1 -1
- package/dist/workspace/member-commands.js +54 -5
- package/dist/workspace/migrate-links.d.ts +45 -0
- package/dist/workspace/migrate-links.d.ts.map +1 -0
- package/dist/workspace/migrate-links.js +100 -0
- package/dist/workspace/migration-dirs.d.ts +74 -0
- package/dist/workspace/migration-dirs.d.ts.map +1 -0
- package/dist/workspace/migration-dirs.js +254 -0
- package/dist/workspace/reason-codes.d.ts +8 -0
- package/dist/workspace/reason-codes.d.ts.map +1 -1
- package/dist/workspace/reason-codes.js +11 -0
- package/dist/workspace/records-cli.d.ts +11 -0
- package/dist/workspace/records-cli.d.ts.map +1 -1
- package/dist/workspace/records-cli.js +57 -5
- package/dist/workspace/records.schema.json +5 -0
- package/dist/workspace/release-candidates.d.ts +2 -0
- package/dist/workspace/release-candidates.d.ts.map +1 -1
- package/dist/workspace/release-candidates.js +5 -2
- package/dist/workspace/release-train.d.ts +53 -0
- package/dist/workspace/release-train.d.ts.map +1 -0
- package/dist/workspace/release-train.js +134 -0
- package/dist/workspace/release-workflow.d.ts.map +1 -1
- package/dist/workspace/release-workflow.js +4 -3
- package/dist/workspace/repo-ref.d.ts +45 -0
- package/dist/workspace/repo-ref.d.ts.map +1 -0
- package/dist/workspace/repo-ref.js +114 -0
- package/dist/workspace/run-event.schema.json +2 -2
- package/dist/workspace/runs-cli.d.ts +9 -0
- package/dist/workspace/runs-cli.d.ts.map +1 -1
- package/dist/workspace/runs-cli.js +48 -3
- package/dist/workspace/runs.schema.json +7 -0
- package/dist/workspace/search-contract.d.ts +1 -1
- package/dist/workspace/search-contract.d.ts.map +1 -1
- package/dist/workspace/status-decisions.d.ts +37 -0
- package/dist/workspace/status-decisions.d.ts.map +1 -0
- package/dist/workspace/status-decisions.js +147 -0
- package/dist/workspace/status-gates.d.ts +9 -0
- package/dist/workspace/status-gates.d.ts.map +1 -1
- package/dist/workspace/status.d.ts +4 -0
- package/dist/workspace/status.d.ts.map +1 -1
- package/dist/workspace/status.js +14 -2
- package/dist/workspace/status.schema.json +6 -0
- package/dist/workspace/work-cli.d.ts.map +1 -1
- package/dist/workspace/work-cli.js +5 -3
- package/dist/workspace/work-evidence-ops.d.ts +83 -0
- package/dist/workspace/work-evidence-ops.d.ts.map +1 -0
- package/dist/workspace/work-evidence-ops.js +169 -0
- package/dist/workspace/work-evidence.d.ts +12 -2
- package/dist/workspace/work-evidence.d.ts.map +1 -1
- package/dist/workspace/work-evidence.js +21 -3
- package/dist/workspace/work-evidence.schema.json +49 -2
- package/dist/workspace/workspace-gates.d.ts +110 -0
- package/dist/workspace/workspace-gates.d.ts.map +1 -0
- package/dist/workspace/workspace-gates.js +140 -0
- package/dist/workspace/workspace-kinds.schema.json +58 -3
- package/dist/workspace/workspace-ledger.d.ts +76 -0
- package/dist/workspace/workspace-ledger.d.ts.map +1 -0
- package/dist/workspace/workspace-ledger.js +91 -0
- package/dist/workspace/workspace-run.d.ts +112 -0
- package/dist/workspace/workspace-run.d.ts.map +1 -0
- package/dist/workspace/workspace-run.js +432 -0
- package/package.json +1 -1
- package/src/__golden__/wave-loop/op-by-apply-after-fails.json +10 -0
- package/src/__golden__/wave-loop/op-by-apply-hold.json +10 -0
- package/src/__golden__/wave-loop/op-by-apply-ignored.json +10 -0
- package/src/__golden__/wave-loop/op-by-apply-pass.json +10 -0
- package/src/__golden__/wave-loop/op-by-apply-skip.json +10 -0
- package/src/__golden__/wave-loop/op-cost-member-clash.json +6 -0
- package/src/__golden__/wave-loop/op-cost-member.json +13 -0
- package/src/__golden__/wave-loop/op-denied-source.json +16 -0
- package/src/__golden__/wave-loop/op-denied.json +16 -0
- package/src/__golden__/wave-loop/op-discover-rest-short.json +6 -0
- package/src/__golden__/wave-loop/op-discover-rest-stops.json +13 -0
- package/src/__golden__/wave-loop/op-discover-rest.json +26 -0
- package/src/__golden__/wave-loop/op-discover-wide.json +26 -0
- package/src/__golden__/wave-loop/op-held-on-again.json +16 -0
- package/src/__golden__/wave-loop/op-held-on-before-apply.json +16 -0
- package/src/__golden__/wave-loop/op-held-on.json +16 -0
- package/src/__golden__/wave-loop/op-hooks-apply-fails.json +13 -0
- package/src/__golden__/wave-loop/op-hooks-fail-after-apply.json +13 -0
- package/src/__golden__/wave-loop/op-hooks-fail-before-apply.json +13 -0
- package/src/__golden__/wave-loop/op-hooks-fail-before-plan.json +13 -0
- package/src/__golden__/wave-loop/op-hooks-held.json +13 -0
- package/src/__golden__/wave-loop/op-hooks-hold-before-apply.json +13 -0
- package/src/__golden__/wave-loop/op-hooks-pass.json +13 -0
- package/src/__golden__/wave-loop/op-hooks-refused-at-base.json +13 -0
- package/src/__golden__/wave-loop/op-hooks-share.json +33 -0
- package/src/__golden__/wave-loop/op-outcome-held.json +10 -0
- package/src/__golden__/wave-loop/op-outcome-replan-approved.json +10 -0
- package/src/__golden__/wave-loop/op-outcome-replan-forever.json +10 -0
- package/src/__golden__/wave-loop/op-outcome-replan.json +10 -0
- package/src/__golden__/wave-loop/op-outcome-stood-down.json +10 -0
- package/src/__golden__/wave-loop/op-outcome-unreadable.json +10 -0
- package/src/__golden__/wave-loop/op-overridden.json +16 -0
- package/src/__golden__/wave-loop/op-per-run.json +20 -0
- package/src/__golden__/wave-loop/op-point.json +13 -0
- package/src/__golden__/wave-loop/op-pr-review.json +10 -0
- package/src/__golden__/wave-loop/op-sealed.json +10 -0
- package/src/__golden__/wave-loop/op-spend-change-only.json +10 -0
- package/src/__golden__/wave-loop/op-spend.json +10 -0
- package/src/__golden__/wave-loop/op-wave-commands.json +55 -0
- package/src/__golden__/wave-loop/op-wide-refused.json +26 -0
- package/src/__golden__/wave-loop/op-wide-waiting.json +29 -0
- package/src/__golden__/wave-loop/op-wide.json +39 -0
- package/src/action-pins.test.ts +78 -0
- package/src/action-pins.ts +92 -0
- package/src/cli/commands/carve-score-once.test.ts +99 -0
- package/src/cli/commands/carve.ts +37 -6
- package/src/cli/commands/check-lexicon.test.ts +2 -2
- package/src/cli/commands/import-report.ts +71 -0
- package/src/cli/commands/import.ts +33 -3
- package/src/cli/commands/migrate-dialect.test.ts +145 -0
- package/src/cli/commands/migrate.ts +132 -11
- package/src/cli/handlers/__snapshots__/build-member.test.ts.snap +4 -4
- package/src/cli/handlers/approve.ts +23 -2
- package/src/cli/handlers/build.ts +7 -1
- package/src/cli/handlers/ledger.ts +54 -0
- package/src/cli/handlers/migrate.ts +21 -12
- package/src/cli/handlers/misc.ts +10 -1
- package/src/cli/handlers/pr-env.ts +2 -2
- package/src/cli/handlers/pr.ts +138 -36
- package/src/cli/handlers/query.ts +16 -4
- package/src/cli/handlers/release.ts +6 -3
- package/src/cli/handlers/search.ts +13 -34
- package/src/cli/main.test.ts +15 -0
- package/src/cli/main.ts +65 -14
- package/src/cli/parse-args.ts +27 -2
- package/src/cli/registry.ts +14 -4
- package/src/components/builders.ts +8 -0
- package/src/components/capability-plugin-loader.ts +13 -1
- package/src/components/capability-plugin.ts +16 -0
- package/src/components/pr-hooks.test.ts +73 -5
- package/src/components/pr-hooks.ts +95 -5
- package/src/components/pr-pipeline.ts +64 -16
- package/src/components/pr-policy.test.ts +64 -1
- package/src/components/pr-policy.ts +53 -0
- package/src/components/registry.test.ts +1 -1
- package/src/components/starter-plugin.ts +6 -0
- package/src/components/verbs/index.ts +2 -0
- package/src/components/verbs/publish-artifact.test.ts +60 -0
- package/src/components/verbs/publish-artifact.ts +122 -0
- package/src/components/verbs/publish-image.test.ts +101 -0
- package/src/components/verbs/publish-image.ts +265 -0
- package/src/config-environments.ts +28 -1
- package/src/config.ts +8 -1
- package/src/gate-decision-bundle.test.ts +1 -0
- package/src/gate-decision-identity.test.ts +9 -0
- package/src/gate-decision.ts +26 -3
- package/src/graph-ir.ts +27 -0
- package/src/graph-walk.test.ts +44 -0
- package/src/graph-walk.ts +102 -0
- package/src/import/parser.ts +20 -0
- package/src/lexicon.ts +74 -6
- package/src/lifecycle/gate-ledger.ts +8 -0
- package/src/lifecycle/git.ts +2 -2
- package/src/lifecycle/ledger-read.test.ts +107 -0
- package/src/lifecycle/ledger-read.ts +195 -0
- package/src/lifecycle/release-workstation.ts +24 -0
- package/src/lifecycle/run-ledger.ts +5 -2
- package/src/lint/cross-root.test.ts +122 -0
- package/src/lint/discover.ts +3 -1
- package/src/lint/post-synth.ts +212 -2
- package/src/lint/rules/op/index.ts +2 -0
- package/src/lint/rules/op/ops019-workspace-op-target.ts +89 -0
- package/src/op/activities/activity-contracts.ts +5 -2
- package/src/op/activities/index.ts +2 -2
- package/src/op/activities/review-agent.test.ts +221 -4
- package/src/op/activities/review-agent.ts +265 -22
- package/src/op/activities/work-evidence.ts +9 -1
- package/src/op/builders.ts +9 -5
- package/src/op/composites/apply-op.test.ts +108 -0
- package/src/op/composites/apply-op.ts +37 -2
- package/src/op/gate-approval.test.ts +6 -1
- package/src/op/gate-approval.ts +12 -6
- package/src/op/gate-quorum-at-base.test.ts +211 -0
- package/src/op/gate-quorum.ts +156 -8
- package/src/op/gate-review.ts +14 -0
- package/src/op/gate.ts +67 -8
- package/src/op/index.ts +3 -2
- package/src/op/local-executor.ts +69 -9
- package/src/op/op-waves-depends-on.test.ts +81 -0
- package/src/op/op-waves-hook-targets.test.ts +4 -2
- package/src/op/op-waves-quorum-product.test.ts +158 -0
- package/src/op/op-waves-run.ts +288 -14
- package/src/op/op-waves.ts +143 -14
- package/src/op/runtime.ts +8 -0
- package/src/op/schedule-overdue.ts +33 -0
- package/src/op/stage-hooks.ts +13 -5
- package/src/op/workspace-run-env.ts +29 -0
- package/src/pr-forge-bundle.test.ts +2 -2
- package/src/pr-forge.test.ts +47 -0
- package/src/pr-forge.ts +68 -0
- package/src/pr-post-open.e2e.test.ts +36 -0
- package/src/pr-post.test.ts +102 -3
- package/src/pr-post.ts +89 -1
- package/src/read-filter.test.ts +54 -0
- package/src/read-filter.ts +193 -0
- package/src/run-event.ts +1 -1
- package/src/stage-hooks-bundle.test.ts +1 -0
- package/src/terraform/carve.ts +52 -15
- package/src/terraform/cuts.test.ts +173 -0
- package/src/terraform/cuts.ts +188 -0
- package/src/terraform/folds.ts +44 -0
- package/src/terraform/graph.ts +9 -0
- package/src/terraform/radius.test.ts +90 -0
- package/src/terraform/reach.test.ts +146 -0
- package/src/terraform/reach.ts +247 -0
- package/src/terraform/score.ts +27 -29
- package/src/terraform/state.ts +3 -1
- package/src/terraform/types.ts +8 -0
- package/src/wave-blocked.ts +28 -3
- package/src/wave-loop-golden.test.ts +4 -3
- package/src/workspace/behold-kinds.test.ts +1 -1
- package/src/workspace/check-contract.test.ts +7 -6
- package/src/workspace/check.schema.json +11 -2
- package/src/workspace/checks/ci-branch.test.ts +128 -0
- package/src/workspace/checks/ci-branch.ts +141 -0
- package/src/workspace/checks/links.ts +12 -1
- package/src/workspace/checks/org.ts +169 -0
- package/src/workspace/checks.ts +64 -7
- package/src/workspace/ci-branches.ts +31 -0
- package/src/workspace/ci-green-branches.test.ts +153 -0
- package/src/workspace/ci-green-cli.ts +7 -7
- package/src/workspace/ci-green-workflow.test.ts +4 -3
- package/src/workspace/ci-green-workflow.ts +13 -9
- package/src/workspace/ci-green.ts +49 -12
- package/src/workspace/ci-run.test.ts +72 -1
- package/src/workspace/ci-run.ts +68 -8
- package/src/workspace/ci-serve.test.ts +95 -0
- package/src/workspace/ci-serve.ts +106 -65
- package/src/workspace/component-environments.test.ts +25 -2
- package/src/workspace/compose-graph.ts +9 -2
- package/src/workspace/composites.schema.json +22 -0
- package/src/workspace/composites.test.ts +39 -8
- package/src/workspace/composites.ts +6 -2
- package/src/workspace/conformance/__writer_fixture__/work/work.schema.json +105 -1
- package/src/workspace/cross-root-lint.test.ts +90 -0
- package/src/workspace/cross-root-lint.ts +126 -0
- package/src/workspace/dbt-kind.test.ts +3 -2
- package/src/workspace/dbt-models-reader.ts +362 -0
- package/src/workspace/dbt-models.test.ts +248 -0
- package/src/workspace/dbt-project-common.ts +113 -0
- package/src/workspace/dbt-project-reader.ts +29 -112
- package/src/workspace/decision-rank.ts +40 -0
- package/src/workspace/declaration.schema.json +133 -24
- package/src/workspace/declaration.ts +84 -15
- package/src/workspace/environments.ts +40 -4
- package/src/workspace/flyway-dir.ts +113 -0
- package/src/workspace/flyway-layout.ts +337 -0
- package/src/workspace/graph-cli.ts +25 -10
- package/src/workspace/graph-contract.test.ts +4 -2
- package/src/workspace/graph.schema.json +34 -0
- package/src/workspace/identity.test.ts +14 -2
- package/src/workspace/intent.ts +6 -14
- package/src/workspace/kind-link-readers.ts +68 -3
- package/src/workspace/kinds.test.ts +12 -7
- package/src/workspace/kinds.ts +132 -7
- package/src/workspace/ledger.schema.json +158 -0
- package/src/workspace/lexicon-outputs.ts +132 -13
- package/src/workspace/lineage-check.ts +30 -7
- package/src/workspace/links.ts +24 -1
- package/src/workspace/ls-contract.test.ts +1 -1
- package/src/workspace/ls.schema.json +7 -0
- package/src/workspace/ls.ts +10 -1
- package/src/workspace/member-commands.ts +60 -2
- package/src/workspace/member-file-schema.test.ts +124 -0
- package/src/workspace/migrate-links.test.ts +99 -0
- package/src/workspace/migrate-links.ts +108 -0
- package/src/workspace/migration-dirs.test.ts +272 -0
- package/src/workspace/migration-dirs.ts +267 -0
- package/src/workspace/org-scope.test.ts +238 -0
- package/src/workspace/reason-codes.test.ts +6 -1
- package/src/workspace/reason-codes.ts +11 -0
- package/src/workspace/records-cli.ts +58 -5
- package/src/workspace/records.schema.json +5 -0
- package/src/workspace/release-candidates.ts +7 -2
- package/src/workspace/release-train.ts +157 -0
- package/src/workspace/release-workflow.ts +4 -3
- package/src/workspace/repo-ref.ts +113 -0
- package/src/workspace/run-event.schema.json +2 -2
- package/src/workspace/runs-cli.ts +48 -4
- package/src/workspace/runs.schema.json +7 -0
- package/src/workspace/status-contract.test.ts +46 -1
- package/src/workspace/status-decisions.test.ts +100 -0
- package/src/workspace/status-decisions.ts +151 -0
- package/src/workspace/status-gates.ts +9 -0
- package/src/workspace/status.schema.json +6 -0
- package/src/workspace/status.ts +17 -3
- package/src/workspace/where-filters.test.ts +39 -0
- package/src/workspace/work-cli.ts +5 -3
- package/src/workspace/work-evidence-ops.test.ts +176 -0
- package/src/workspace/work-evidence-ops.ts +189 -0
- package/src/workspace/work-evidence.schema.json +49 -2
- package/src/workspace/work-evidence.ts +22 -3
- package/src/workspace/workspace-gates.ts +200 -0
- package/src/workspace/workspace-kinds.schema.json +58 -3
- package/src/workspace/workspace-ledger.ts +130 -0
- package/src/workspace/workspace-run.test.ts +322 -0
- package/src/workspace/workspace-run.ts +461 -0
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The readers for goose, golang-migrate and clickhouse-migrations
|
|
3
|
+
* directories (#4400). Each lists the migrations in the order the tool
|
|
4
|
+
* applies them. None runs the tool or a server: the schema the migrations
|
|
5
|
+
* produce comes from a replay.
|
|
6
|
+
*
|
|
7
|
+
* File layouts, checked against the tools' own docs and source:
|
|
8
|
+
*
|
|
9
|
+
* - goose (pressly/goose 9a201bb): `<version>_<name>.sql` directly in the
|
|
10
|
+
* directory, the version a positive integer before the first `_`
|
|
11
|
+
* (`NumericComponent`, migration.go); ordered by that number, so a
|
|
12
|
+
* sequential `00001` and a timestamp `20240101120000` sort together.
|
|
13
|
+
* A file holds `-- +goose Up` and, optionally, a later `-- +goose Down`
|
|
14
|
+
* (internal/sqlparser/parser.go); `-- +goose NO TRANSACTION` runs it
|
|
15
|
+
* outside a transaction. `.go` migrations are listed in the version order
|
|
16
|
+
* but are code, so each is a gap.
|
|
17
|
+
* - golang-migrate (golang-migrate/migrate 504568a): `<version>_<name>.up.<ext>`
|
|
18
|
+
* and `.down.<ext>` directly in the directory, matched by
|
|
19
|
+
* `^([0-9]+)_(.*)\.(down|up)\.(.*)$` (source/parse.go); ordered by the
|
|
20
|
+
* version, a 64-bit unsigned integer. The up files are the migrations; a
|
|
21
|
+
* version with a down file and no up file, or two files for one version
|
|
22
|
+
* and direction, is a gap.
|
|
23
|
+
* - clickhouse-migrations, the Python tool (zifter/clickhouse-migrations) and
|
|
24
|
+
* the Node tool (npm clickhouse-migrations): `<version>_<name>.sql` directly
|
|
25
|
+
* in the migrations directory, the version an integer before the first `_`,
|
|
26
|
+
* applied in ascending numeric order. The Python tool also reads an
|
|
27
|
+
* optional `<version>_<name>.down.sql` rollback beside it. The dialect is
|
|
28
|
+
* always ClickHouse.
|
|
29
|
+
*
|
|
30
|
+
* What the migrations produce comes from a replay (`chant sql replay`), which
|
|
31
|
+
* records the tables and columns in {@link REPLAY_FILE} beside the
|
|
32
|
+
* migrations. Each reader returns them as the member's outputs when the
|
|
33
|
+
* recorded digest still matches the files, and a gap that says to replay
|
|
34
|
+
* again when it does not (see {@link withReplay}).
|
|
35
|
+
*
|
|
36
|
+
* The member's `migrationsDir` field names the directory under the member's
|
|
37
|
+
* own, and `dialect` names the dialect for goose and golang-migrate, whose
|
|
38
|
+
* tools take it from a driver or URL the directory does not hold.
|
|
39
|
+
*/
|
|
40
|
+
|
|
41
|
+
import { createHash } from "node:crypto";
|
|
42
|
+
import type { KindLinkRead, KindReadGap, KindReadMigration, KindReaderInput } from "./kind-link-readers";
|
|
43
|
+
import type { FieldValue } from "./kinds";
|
|
44
|
+
import { joinPath, type WorkspaceTree } from "./tree";
|
|
45
|
+
|
|
46
|
+
/** The dialects a migration directory's `dialect` field names. */
|
|
47
|
+
export const MIGRATION_DIALECTS = ["postgres", "clickhouse"] as const;
|
|
48
|
+
|
|
49
|
+
export function fieldString(fields: KindReaderInput["fields"], name: string, fallback: string): string {
|
|
50
|
+
const v = fields?.[name] as FieldValue | null | undefined;
|
|
51
|
+
return typeof v === "string" && v !== "" ? v : fallback;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** The directory a reader lists: the member's `migrationsDir` under its own directory. */
|
|
55
|
+
export function migrationsDirOf(input: KindReaderInput): string {
|
|
56
|
+
const sub = fieldString(input.fields, "migrationsDir", ".");
|
|
57
|
+
return joinPath(input.dir, sub.replace(/^\.\/?/, ""));
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function filesIn(tree: WorkspaceTree, dir: string): string[] {
|
|
61
|
+
return (tree.list(dir) ?? []).filter((e) => e.type === "file").map((e) => e.name).sort();
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const lineOf = (text: string, pred: (line: string) => boolean): number => {
|
|
65
|
+
const i = text.split(/\r?\n/).findIndex(pred);
|
|
66
|
+
return i < 0 ? 0 : i + 1;
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
/** The goose annotation on `line` (`up`, `down`, `no transaction`, ...), or undefined when it holds none. Mirrors the parser's rule: a comment that contains `+goose`. */
|
|
70
|
+
export function gooseAnnotation(line: string): string | undefined {
|
|
71
|
+
const t = line.trim();
|
|
72
|
+
if (!t.startsWith("--") || !t.includes("+goose")) return undefined;
|
|
73
|
+
return t.replace("+goose", "").replace(/^--\s*/, "").trim().toLowerCase();
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function listGooseDir(input: KindReaderInput): KindLinkRead {
|
|
77
|
+
const { tree } = input;
|
|
78
|
+
const dir = migrationsDirOf(input);
|
|
79
|
+
const dialect = fieldString(input.fields, "dialect", "postgres");
|
|
80
|
+
const gaps: KindReadGap[] = [];
|
|
81
|
+
const result: KindLinkRead = { reader: "goose-dir", links: [], gaps, migrations: [] };
|
|
82
|
+
const found: { n: number; migration: KindReadMigration | null; file: string }[] = [];
|
|
83
|
+
for (const name of filesIn(tree, dir)) {
|
|
84
|
+
const ext = name.endsWith(".sql") ? ".sql" : name.endsWith(".go") && !name.endsWith("_test.go") ? ".go" : null;
|
|
85
|
+
if (!ext) continue;
|
|
86
|
+
const file = joinPath(dir, name);
|
|
87
|
+
const before = name.split("_")[0] ?? "";
|
|
88
|
+
const n = /^[0-9]+$/.test(before) && name.includes("_") ? Number(before) : NaN;
|
|
89
|
+
if (!Number.isSafeInteger(n) || n < 1) {
|
|
90
|
+
gaps.push({ file, line: 1, message: `${name} has no version goose can read: the name starts with a positive integer, then _, so goose would stop here` });
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
if (ext === ".go") {
|
|
94
|
+
gaps.push({ file, line: 1, message: `${name} is a Go migration; its code is not SQL, so it is not listed` });
|
|
95
|
+
found.push({ n, migration: null, file });
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
const text = tree.read(file);
|
|
99
|
+
const up = lineOf(text, (l) => gooseAnnotation(l) === "up");
|
|
100
|
+
if (up === 0) {
|
|
101
|
+
gaps.push({ file, line: 1, message: `${name} has no -- +goose Up annotation, so goose refuses it` });
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
const down = lineOf(text, (l) => gooseAnnotation(l) === "down");
|
|
105
|
+
if (down !== 0 && down < up) gaps.push({ file, line: down, message: `${name}: -- +goose Down comes before -- +goose Up, which goose refuses` });
|
|
106
|
+
found.push({
|
|
107
|
+
n,
|
|
108
|
+
file,
|
|
109
|
+
migration: {
|
|
110
|
+
position: 0,
|
|
111
|
+
version: String(n),
|
|
112
|
+
name: name.slice(before.length + 1, -ext.length),
|
|
113
|
+
type: "versioned",
|
|
114
|
+
file,
|
|
115
|
+
line: up,
|
|
116
|
+
down: down !== 0,
|
|
117
|
+
dialect,
|
|
118
|
+
transaction: !text.split(/\r?\n/).some((l) => gooseAnnotation(l) === "no transaction"),
|
|
119
|
+
},
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
found.sort((a, b) => a.n - b.n || (a.file < b.file ? -1 : 1));
|
|
123
|
+
const seen = new Map<number, string>();
|
|
124
|
+
for (const f of found) {
|
|
125
|
+
const prior = seen.get(f.n);
|
|
126
|
+
if (prior) gaps.push({ file: f.file, line: 1, message: `version ${String(f.n)} is also ${prior}; goose refuses a duplicate version` });
|
|
127
|
+
else seen.set(f.n, f.file);
|
|
128
|
+
}
|
|
129
|
+
for (const f of found) if (f.migration) result.migrations!.push(f.migration);
|
|
130
|
+
result.migrations!.forEach((m, i) => (m.position = i + 1));
|
|
131
|
+
return result;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const MIGRATE_NAME = /^([0-9]+)_(.*)\.(down|up)\.(.*)$/;
|
|
135
|
+
|
|
136
|
+
function listGolangMigrateDir(input: KindReaderInput): KindLinkRead {
|
|
137
|
+
const { tree } = input;
|
|
138
|
+
const dir = migrationsDirOf(input);
|
|
139
|
+
const dialect = fieldString(input.fields, "dialect", "postgres");
|
|
140
|
+
const gaps: KindReadGap[] = [];
|
|
141
|
+
const result: KindLinkRead = { reader: "golang-migrate-dir", links: [], gaps, migrations: [] };
|
|
142
|
+
const ups = new Map<bigint, { name: string; file: string; version: string }>();
|
|
143
|
+
const downs = new Map<bigint, string>();
|
|
144
|
+
for (const name of filesIn(tree, dir)) {
|
|
145
|
+
const m = MIGRATE_NAME.exec(name);
|
|
146
|
+
if (!m) continue;
|
|
147
|
+
const version = BigInt(m[1]!);
|
|
148
|
+
const file = joinPath(dir, name);
|
|
149
|
+
if (version > 18446744073709551615n) {
|
|
150
|
+
gaps.push({ file, line: 1, message: `${name}: the version does not fit a 64-bit unsigned integer, which golang-migrate requires` });
|
|
151
|
+
continue;
|
|
152
|
+
}
|
|
153
|
+
const table = m[3] === "up" ? ups : downs;
|
|
154
|
+
if (table.has(version)) {
|
|
155
|
+
gaps.push({ file, line: 1, message: `${name}: another ${m[3]!} file already has version ${m[1]!}; golang-migrate refuses a duplicate` });
|
|
156
|
+
continue;
|
|
157
|
+
}
|
|
158
|
+
if (m[3] === "up") ups.set(version, { name: m[2]!, file, version: m[1]! });
|
|
159
|
+
else downs.set(version, file);
|
|
160
|
+
}
|
|
161
|
+
for (const [v, file] of downs) if (!ups.has(v)) gaps.push({ file, line: 1, message: `version ${String(v)} has a down file and no up file` });
|
|
162
|
+
[...ups.entries()]
|
|
163
|
+
.sort((a, b) => (a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : 0))
|
|
164
|
+
.forEach(([v, u], i) => result.migrations!.push({ position: i + 1, version: u.version, name: u.name, type: "versioned", file: u.file, line: 1, down: downs.get(v) ?? false, dialect }));
|
|
165
|
+
return result;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const CLICKHOUSE_NAME = /^([0-9]+)_(.+)\.sql$/;
|
|
169
|
+
|
|
170
|
+
function listClickHouseMigrationsDir(input: KindReaderInput): KindLinkRead {
|
|
171
|
+
const { tree } = input;
|
|
172
|
+
const dir = migrationsDirOf(input);
|
|
173
|
+
const gaps: KindReadGap[] = [];
|
|
174
|
+
const result: KindLinkRead = { reader: "clickhouse-migrations-dir", links: [], gaps, migrations: [] };
|
|
175
|
+
const names = filesIn(tree, dir).filter((n) => n.endsWith(".sql"));
|
|
176
|
+
const rollbacks = new Map(names.filter((n) => n.endsWith(".down.sql")).map((n) => [n.slice(0, -".down.sql".length), joinPath(dir, n)]));
|
|
177
|
+
const ups: { n: bigint; version: string; name: string; file: string }[] = [];
|
|
178
|
+
for (const name of names) {
|
|
179
|
+
if (name.endsWith(".down.sql")) continue;
|
|
180
|
+
const m = CLICKHOUSE_NAME.exec(name);
|
|
181
|
+
if (!m) {
|
|
182
|
+
gaps.push({ file: joinPath(dir, name), line: 1, message: `${name} has no version before its first _, so clickhouse-migrations reports a bad file name` });
|
|
183
|
+
continue;
|
|
184
|
+
}
|
|
185
|
+
ups.push({ n: BigInt(m[1]!), version: m[1]!, name: m[2]!, file: joinPath(dir, name) });
|
|
186
|
+
}
|
|
187
|
+
ups.sort((a, b) => (a.n < b.n ? -1 : a.n > b.n ? 1 : a.file < b.file ? -1 : 1));
|
|
188
|
+
const seen = new Map<bigint, string>();
|
|
189
|
+
for (const u of ups) {
|
|
190
|
+
const prior = seen.get(u.n);
|
|
191
|
+
if (prior) gaps.push({ file: u.file, line: 1, message: `version ${u.version} is also ${prior}; leading zeros are not significant, so clickhouse-migrations reports a duplicate version` });
|
|
192
|
+
else seen.set(u.n, u.file);
|
|
193
|
+
}
|
|
194
|
+
ups.forEach((u, i) => result.migrations!.push({ position: i + 1, version: u.version, name: u.name, type: "versioned", file: u.file, line: 1, down: rollbacks.get(u.file.split("/").pop()!.slice(0, -".sql".length)) ?? false, dialect: "clickhouse" }));
|
|
195
|
+
return result;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/** The file a replay writes in the member's directory: the tables and columns the migrations produced, and the digest of the files it ran (#4400). */
|
|
199
|
+
export const REPLAY_FILE = "chant.replay.json";
|
|
200
|
+
|
|
201
|
+
/** What {@link REPLAY_FILE} holds. */
|
|
202
|
+
export interface ReplaySnapshot {
|
|
203
|
+
schema: 1;
|
|
204
|
+
dialect: string;
|
|
205
|
+
/** {@link migrationDigest} of the files the replay ran. */
|
|
206
|
+
digest: string;
|
|
207
|
+
/** Names as `<schema>.<table>` and `<schema>.<table>.<column>` (Postgres), or `<database>...` (ClickHouse), the contract of #3931. */
|
|
208
|
+
outputs: string[];
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/** A digest of the migration files in order, so a snapshot can tell the files changed. `extra` are files beside them that change what runs, such as Flyway's configuration. */
|
|
212
|
+
export function migrationDigest(read: (file: string) => string, files: readonly string[]): string {
|
|
213
|
+
const h = createHash("sha256");
|
|
214
|
+
for (const f of files) {
|
|
215
|
+
h.update(f);
|
|
216
|
+
h.update("\0");
|
|
217
|
+
h.update(read(f));
|
|
218
|
+
h.update("\0");
|
|
219
|
+
}
|
|
220
|
+
return `sha256:${h.digest("hex")}`;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/** The files a replay of `migrations` depends on, in order. */
|
|
224
|
+
export function replayFiles(tree: WorkspaceTree, migrations: readonly KindReadMigration[], extra: readonly string[] = []): string[] {
|
|
225
|
+
return [...extra.filter((f) => tree.stat(f) === "file"), ...migrations.map((m) => m.file)];
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Add the recorded replay to a reader's result: the snapshot's names as
|
|
230
|
+
* outputs when its digest matches the files, else a gap flagged `outputs`
|
|
231
|
+
* (a link into the member is then unresolved, not missing). A snapshot
|
|
232
|
+
* that does not parse is a gap too.
|
|
233
|
+
*/
|
|
234
|
+
export function withReplay(result: KindLinkRead, tree: WorkspaceTree, dir: string, extra: readonly string[] = []): KindLinkRead {
|
|
235
|
+
const file = joinPath(dir, REPLAY_FILE);
|
|
236
|
+
const migrations = result.migrations ?? [];
|
|
237
|
+
const gap = (message: string, line = 1) => result.gaps.push({ file: tree.stat(file) === "file" ? file : dir || ".", line, message, outputs: true });
|
|
238
|
+
if (tree.stat(file) !== "file") {
|
|
239
|
+
gap(`no ${REPLAY_FILE}: replay the migrations to record the tables and columns they produce (chant sql replay ${dir || "."})`);
|
|
240
|
+
return result;
|
|
241
|
+
}
|
|
242
|
+
let text: string;
|
|
243
|
+
let snap: Partial<ReplaySnapshot>;
|
|
244
|
+
try {
|
|
245
|
+
text = tree.read(file);
|
|
246
|
+
snap = JSON.parse(text) as Partial<ReplaySnapshot>;
|
|
247
|
+
} catch (err) {
|
|
248
|
+
gap(`${REPLAY_FILE} could not be read (${(err as Error).message}), so the tables the migrations produce are unknown`);
|
|
249
|
+
return result;
|
|
250
|
+
}
|
|
251
|
+
if (snap.schema !== 1 || typeof snap.digest !== "string" || !Array.isArray(snap.outputs) || snap.outputs.some((o) => typeof o !== "string")) {
|
|
252
|
+
gap(`${REPLAY_FILE} is not a replay snapshot (schema 1 with a digest and outputs), so the tables the migrations produce are unknown`);
|
|
253
|
+
return result;
|
|
254
|
+
}
|
|
255
|
+
const digest = migrationDigest((f) => tree.read(f), replayFiles(tree, migrations, extra));
|
|
256
|
+
if (digest !== snap.digest) {
|
|
257
|
+
gap(`the migrations changed since ${REPLAY_FILE} was recorded, so the tables they produce are unknown; replay again (chant sql replay ${dir || "."})`);
|
|
258
|
+
return result;
|
|
259
|
+
}
|
|
260
|
+
const lines = text.split("\n");
|
|
261
|
+
result.outputs = (snap.outputs as string[]).map((name) => ({ name, file, line: Math.max(1, lines.findIndex((l) => l.includes(JSON.stringify(name))) + 1) }));
|
|
262
|
+
return result;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
export const readGooseDir = (input: KindReaderInput): KindLinkRead => withReplay(listGooseDir(input), input.tree, input.dir);
|
|
266
|
+
export const readGolangMigrateDir = (input: KindReaderInput): KindLinkRead => withReplay(listGolangMigrateDir(input), input.tree, input.dir);
|
|
267
|
+
export const readClickHouseMigrationsDir = (input: KindReaderInput): KindLinkRead => withReplay(listClickHouseMigrationsDir(input), input.tree, input.dir);
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Org scope (#3168): a member of kind repo names another repository's
|
|
3
|
+
* workspace by URL and pinned revision, read read-only through that
|
|
4
|
+
* repository's own read contract; an org workspace holds only such members;
|
|
5
|
+
* `chant workspace check --forge-repos` compares them with the forge's list;
|
|
6
|
+
* and the read contract's identity names the repository a document came
|
|
7
|
+
* from. Every remote here is a local bare repository: nothing reaches a forge.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { execFileSync } from "node:child_process";
|
|
11
|
+
import { existsSync, mkdirSync, mkdtempSync, readFileSync, realpathSync, rmSync, writeFileSync } from "node:fs";
|
|
12
|
+
import { tmpdir } from "node:os";
|
|
13
|
+
import { dirname, join } from "node:path";
|
|
14
|
+
import { afterAll, describe, expect, test } from "vitest";
|
|
15
|
+
import { runDeclarationChecks } from "./checks";
|
|
16
|
+
import { parseForgeRepos, repoKey, urlCredential } from "./checks/org";
|
|
17
|
+
import { readDeclaration, repoMemberDir } from "./declaration";
|
|
18
|
+
import { workspaceGraph } from "./graph-cli";
|
|
19
|
+
import { listWorkspace } from "./ls";
|
|
20
|
+
import { fetchRepoAt, redactUrl, repoIdentity } from "./repo-ref";
|
|
21
|
+
import { workingTree } from "./tree";
|
|
22
|
+
|
|
23
|
+
const scratch: string[] = [];
|
|
24
|
+
afterAll(() => {
|
|
25
|
+
for (const d of scratch) rmSync(d, { recursive: true, force: true });
|
|
26
|
+
});
|
|
27
|
+
function tmp(prefix = "chant-3168-"): string {
|
|
28
|
+
const d = realpathSync(mkdtempSync(join(tmpdir(), prefix)));
|
|
29
|
+
scratch.push(d);
|
|
30
|
+
return d;
|
|
31
|
+
}
|
|
32
|
+
const git = (cwd: string, ...args: string[]) =>
|
|
33
|
+
execFileSync("git", ["-c", "user.email=t@example.com", "-c", "user.name=t", "-c", "commit.gpgsign=false", ...args], { cwd, encoding: "utf-8" }).trim();
|
|
34
|
+
function write(root: string, files: Record<string, string>): void {
|
|
35
|
+
for (const [path, text] of Object.entries(files)) {
|
|
36
|
+
mkdirSync(dirname(join(root, path)), { recursive: true });
|
|
37
|
+
writeFileSync(join(root, path), text);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** A repository with a workspace, pushed to a local bare remote. Returns the bare URL and the commit. */
|
|
42
|
+
function remoteWorkspace(name: string): { url: string; rev: string } {
|
|
43
|
+
const work = tmp();
|
|
44
|
+
git(work, "init", "-q", "-b", "main");
|
|
45
|
+
write(work, { "chant.workspace.json": JSON.stringify({ name, schema: 1, members: [{ name: "docs", dir: "docs", kind: "other", because: "prose" }] }), "docs/README.md": "" });
|
|
46
|
+
git(work, "add", "-A");
|
|
47
|
+
git(work, "commit", "-q", "-m", "init");
|
|
48
|
+
const bare = join(tmp(), `${name}.git`);
|
|
49
|
+
git(work, "clone", "-q", "--bare", work, bare);
|
|
50
|
+
return { url: bare, rev: git(work, "rev-parse", "HEAD") };
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** An org workspace in a git repository of its own, with origin set. */
|
|
54
|
+
function orgWorkspace(members: unknown[], extra: Record<string, unknown> = {}): string {
|
|
55
|
+
const root = tmp();
|
|
56
|
+
git(root, "init", "-q");
|
|
57
|
+
git(root, "remote", "add", "origin", "https://bot:secret@forge.example/acme/org.git");
|
|
58
|
+
write(root, { "chant.workspace.json": JSON.stringify({ name: "acme", schema: 1, scope: "org", members, ...extra }) });
|
|
59
|
+
return root;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const SHA = "0123456789abcdef0123456789abcdef01234567";
|
|
63
|
+
const repoMember = (name: string, url: string, rev = SHA) => ({ name, kind: "repo", repo: { url, rev } });
|
|
64
|
+
|
|
65
|
+
describe("the declaration: repo members and scope", () => {
|
|
66
|
+
test("a repo member has repo and no dir, and reads with a marker dir", () => {
|
|
67
|
+
const root = orgWorkspace([repoMember("chant", "https://github.com/INTENTIUS/chant"), { ...repoMember("ops", "git@github.com:INTENTIUS/ops.git"), repo: { url: "git@github.com:INTENTIUS/ops.git", rev: SHA, workspace: "infra" } }]);
|
|
68
|
+
const d = readDeclaration(workingTree(root));
|
|
69
|
+
expect(d.scope).toBe("org");
|
|
70
|
+
expect(d.members.map((m) => [m.dir, m.repo])).toEqual([
|
|
71
|
+
[repoMemberDir("chant"), { url: "https://github.com/INTENTIUS/chant", rev: SHA, workspace: "." }],
|
|
72
|
+
[repoMemberDir("ops"), { url: "git@github.com:INTENTIUS/ops.git", rev: SHA, workspace: "infra" }],
|
|
73
|
+
]);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
test("refuses a repo member with a dir, another kind with repo, and a revision that isn't a full commit id", () => {
|
|
77
|
+
const read = (members: unknown[]) => () => readDeclaration(workingTree(orgWorkspace(members)));
|
|
78
|
+
expect(read([{ ...repoMember("a", "x"), dir: "a" }])).toThrow(/a member of kind repo has no dir/);
|
|
79
|
+
expect(read([{ name: "a", dir: "a", kind: "other", because: "x", repo: { url: "x", rev: SHA } }])).toThrow(/only a member of kind repo has repo/);
|
|
80
|
+
expect(read([repoMember("a", "x", "main")])).toThrow(/"main" is not allowed here/);
|
|
81
|
+
expect(read([{ name: "a", kind: "repo" }])).toThrow(/needs "repo"/);
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
describe("repoKey and credentials", () => {
|
|
86
|
+
test("https, ssh and scp-style URLs of one repository compare equal", () => {
|
|
87
|
+
const keys = ["https://github.com/INTENTIUS/Chant.git", "https://github.com/intentius/chant/", "git@github.com:INTENTIUS/chant.git", "ssh://git@github.com/INTENTIUS/chant"].map((u) => repoKey(u));
|
|
88
|
+
expect(new Set(keys)).toEqual(new Set(["github.com/intentius/chant"]));
|
|
89
|
+
expect(repoKey("./x.git", "/base")).toBe("/base/x");
|
|
90
|
+
expect(repoKey("file:///srv/x.git")).toBe("/srv/x");
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
test("a password, or a user on https, is a credential; ssh's git user is not", () => {
|
|
94
|
+
expect(urlCredential("https://bot:tok@forge.example/a/b")).toBe(true);
|
|
95
|
+
expect(urlCredential("https://tok@forge.example/a/b")).toBe(true);
|
|
96
|
+
expect(urlCredential("ssh://git@forge.example/a/b")).toBe(false);
|
|
97
|
+
expect(urlCredential("git@forge.example:a/b")).toBe(false);
|
|
98
|
+
expect(redactUrl("https://bot:tok@forge.example/a/b")).toBe("https://forge.example/a/b");
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
describe("parseForgeRepos", () => {
|
|
103
|
+
test("reads an object with repos, a bare array, strings and objects", () => {
|
|
104
|
+
expect(parseForgeRepos(JSON.stringify({ forge: "github", org: "acme", repos: ["https://f/a/x", { url: "https://f/a/y", archived: true }] }), "f.json").repos).toEqual([
|
|
105
|
+
{ url: "https://f/a/x", archived: false },
|
|
106
|
+
{ url: "https://f/a/y", archived: true },
|
|
107
|
+
]);
|
|
108
|
+
expect(parseForgeRepos(JSON.stringify(["https://f/a/x"]), "f.json").repos).toHaveLength(1);
|
|
109
|
+
expect(() => parseForgeRepos("{}", "f.json")).toThrow(/repos array/);
|
|
110
|
+
expect(() => parseForgeRepos("[1]", "f.json")).toThrow(/repos\[0\]/);
|
|
111
|
+
expect(() => parseForgeRepos("nope", "f.json")).toThrow(/not JSON/);
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
describe("chant workspace check on an org workspace", () => {
|
|
116
|
+
const ids = async (root: string, forge?: unknown) =>
|
|
117
|
+
(await runDeclarationChecks(root, (f) => f, forge ? { forgeRepos: parseForgeRepos(JSON.stringify(forge), "forge.json") } : {})).diagnostics
|
|
118
|
+
.filter((d) => /^WSP0(0[1-9]|1[0-6])$/.test(d.ruleId))
|
|
119
|
+
.map((d) => `${d.ruleId}:${d.entity ?? ""}`);
|
|
120
|
+
|
|
121
|
+
test("repo members need no directory: no WSP004, and nothing else fires", async () => {
|
|
122
|
+
expect(await ids(orgWorkspace([repoMember("a", "https://forge.example/acme/a")]))).toEqual([]);
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
test("WSP013: an org workspace holds only repo members", async () => {
|
|
126
|
+
const root = orgWorkspace([repoMember("a", "https://forge.example/acme/a"), { name: "docs", dir: "docs", kind: "other", because: "x" }]);
|
|
127
|
+
mkdirSync(join(root, "docs"));
|
|
128
|
+
expect(await ids(root)).toContain("WSP013:docs");
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
test("WSP014 names a repository the forge has and the declaration lacks; WSP015 one the forge dropped; archived ones need not be declared", async () => {
|
|
132
|
+
const root = orgWorkspace([repoMember("a", "git@forge.example:acme/a.git"), repoMember("gone", "https://forge.example/acme/gone")]);
|
|
133
|
+
const forge = { repos: ["https://forge.example/acme/a", "https://forge.example/acme/new", { url: "https://forge.example/acme/old", archived: true }] };
|
|
134
|
+
const report = await runDeclarationChecks(root, (f) => f, { forgeRepos: parseForgeRepos(JSON.stringify(forge), "forge.json") });
|
|
135
|
+
const found = report.diagnostics.filter((d) => d.ruleId === "WSP014" || d.ruleId === "WSP015").map((d) => `${d.ruleId}: ${d.message}`);
|
|
136
|
+
expect(found).toEqual([
|
|
137
|
+
expect.stringMatching(/^WSP014: the forge lists https:\/\/forge.example\/acme\/new \(forge.json\), and no repo member names it/),
|
|
138
|
+
expect.stringMatching(/^WSP015: member gone names https:\/\/forge.example\/acme\/gone, which the forge no longer lists/),
|
|
139
|
+
]);
|
|
140
|
+
expect(report.ok).toBe(false);
|
|
141
|
+
// Without the list, neither runs: check never asks the forge.
|
|
142
|
+
expect(await ids(root)).toEqual([]);
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
test("WSP016: a repo url never carries a credential", async () => {
|
|
146
|
+
expect(await ids(orgWorkspace([repoMember("a", "https://bot:tok@forge.example/acme/a")]))).toEqual(["WSP016:a"]);
|
|
147
|
+
});
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
describe("the read contract's identity names the repository", () => {
|
|
151
|
+
test("ls prints workspace.repo, redacted, and each repo member's reference", () => {
|
|
152
|
+
const root = orgWorkspace([repoMember("a", "https://forge.example/acme/a")]);
|
|
153
|
+
const doc = listWorkspace({ cwd: root });
|
|
154
|
+
if ("error" in doc) throw new Error(doc.error.message);
|
|
155
|
+
expect(doc.workspace.repo).toBe("https://forge.example/acme/org.git");
|
|
156
|
+
expect(doc.members[0]).toMatchObject({ name: "a", kind: "repo", readable: true, reason: null, repo: { url: "https://forge.example/acme/a", rev: SHA, workspace: "." } });
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
test("is null without an origin, or outside git", () => {
|
|
160
|
+
const plain = tmp();
|
|
161
|
+
git(plain, "init", "-q");
|
|
162
|
+
expect(repoIdentity(plain)).toBeNull();
|
|
163
|
+
expect(repoIdentity(tmp())).toBeNull();
|
|
164
|
+
});
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
describe("fetching a repo member's revision into the cache", () => {
|
|
168
|
+
test("fetches the pinned commit from a local bare remote into the user cache, once, and reports a commit it lacks", () => {
|
|
169
|
+
const remote = remoteWorkspace("payments");
|
|
170
|
+
const cache = tmp("chant-3168-cache-");
|
|
171
|
+
const env = { ...process.env, CHANT_CACHE_DIR: cache };
|
|
172
|
+
const first = fetchRepoAt({ url: remote.url, rev: remote.rev, workspace: "." }, "/", env);
|
|
173
|
+
expect(first.ok).toBe(true);
|
|
174
|
+
if (!first.ok) return;
|
|
175
|
+
expect(first.dir.startsWith(join(cache, "repos"))).toBe(true);
|
|
176
|
+
expect(JSON.parse(readFileSync(join(first.dir, "chant.workspace.json"), "utf-8")).name).toBe("payments");
|
|
177
|
+
expect(git(first.dir, "rev-parse", "HEAD")).toBe(remote.rev);
|
|
178
|
+
expect(fetchRepoAt({ url: remote.url, rev: remote.rev, workspace: "." }, "/", env)).toEqual(first);
|
|
179
|
+
const missing = fetchRepoAt({ url: remote.url, rev: SHA, workspace: "." }, "/", env);
|
|
180
|
+
expect(missing).toMatchObject({ ok: false, message: expect.stringContaining(SHA) });
|
|
181
|
+
});
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
describe("chant workspace graph reads a repo member through its own read contract", () => {
|
|
185
|
+
test("runs the other repository's workspace graph at the pinned revision and folds it in, writing nothing in the workspace", async () => {
|
|
186
|
+
const remote = remoteWorkspace("payments");
|
|
187
|
+
const root = orgWorkspace([repoMember("payments", remote.url, remote.rev)]);
|
|
188
|
+
// A stand-in for the other repository's chant: it prints a graph document of the contract, with its argv and identity.
|
|
189
|
+
const bin = join(tmp(), "fake-chant.mjs");
|
|
190
|
+
writeFileSync(
|
|
191
|
+
bin,
|
|
192
|
+
`import { execFileSync } from "node:child_process";
|
|
193
|
+
const argv = process.argv.slice(2);
|
|
194
|
+
const at = argv[argv.indexOf("--at") + 1];
|
|
195
|
+
const repo = execFileSync("git", ["config", "--get", "remote.origin.url"], { encoding: "utf-8" }).trim();
|
|
196
|
+
const member = { name: "docs", dir: "docs", kind: "other", status: "composed", reason: null, chant: "9.9.9", irVersion: 1, live: false };
|
|
197
|
+
console.log(JSON.stringify({ $schema: "https://intentius.io/chant/schemas/workspace/graph/v1/graph.schema.json", contract: 1, chant: "9.9.9", at, version: 1,
|
|
198
|
+
workspace: { name: "payments", root: ".", repo }, members: [member],
|
|
199
|
+
nodes: [{ id: "docs/Page", kind: "Page", lexicon: "x", attrs: {}, member: "docs" }], edges: [], groups: { byMember: { docs: ["docs/Page"] } },
|
|
200
|
+
exports: [], imports: [], links: [], collectors: [], records: [] }));
|
|
201
|
+
`,
|
|
202
|
+
);
|
|
203
|
+
const cache = tmp("chant-3168-cache-");
|
|
204
|
+
const before = process.env.CHANT_CACHE_DIR;
|
|
205
|
+
process.env.CHANT_CACHE_DIR = cache;
|
|
206
|
+
try {
|
|
207
|
+
const { doc } = await workspaceGraph({ cwd: root, reader: { command: [process.execPath, bin], identity: bin, source: "path" } as never });
|
|
208
|
+
if ("error" in doc) throw new Error(doc.error.message);
|
|
209
|
+
expect(doc.workspace).toMatchObject({ name: "acme", repo: "https://forge.example/acme/org.git" });
|
|
210
|
+
const m = doc.members.find((x) => x.name === "payments")!;
|
|
211
|
+
expect(m).toMatchObject({ kind: "repo", status: "composed", repo: { url: remote.url, rev: remote.rev, workspace: ".", read: remote.url } });
|
|
212
|
+
expect(m.nested?.name).toBe("payments");
|
|
213
|
+
expect(m.nested?.members.map((x) => x.name)).toEqual(["docs"]);
|
|
214
|
+
expect(doc.nodes.map((n) => n.id)).toEqual(["payments/docs/Page"]);
|
|
215
|
+
expect(existsSync(join(root, "@repo"))).toBe(false);
|
|
216
|
+
expect(git(root, "status", "--porcelain")).toBe("?? chant.workspace.json");
|
|
217
|
+
} finally {
|
|
218
|
+
if (before === undefined) delete process.env.CHANT_CACHE_DIR;
|
|
219
|
+
else process.env.CHANT_CACHE_DIR = before;
|
|
220
|
+
}
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
test("a revision the remote lacks fails the member with command-failed", async () => {
|
|
224
|
+
const remote = remoteWorkspace("payments");
|
|
225
|
+
const root = orgWorkspace([repoMember("payments", remote.url, SHA)]);
|
|
226
|
+
const before = process.env.CHANT_CACHE_DIR;
|
|
227
|
+
process.env.CHANT_CACHE_DIR = tmp("chant-3168-cache-");
|
|
228
|
+
try {
|
|
229
|
+
const { doc, failed } = await workspaceGraph({ cwd: root, reader: { command: ["false"], identity: "false", source: "path" } as never });
|
|
230
|
+
if ("error" in doc) throw new Error(doc.error.message);
|
|
231
|
+
expect(failed).toBe(true);
|
|
232
|
+
expect(doc.members[0]).toMatchObject({ status: "failed", reason: { code: "command-failed", message: expect.stringContaining(SHA) } });
|
|
233
|
+
} finally {
|
|
234
|
+
if (before === undefined) delete process.env.CHANT_CACHE_DIR;
|
|
235
|
+
else process.env.CHANT_CACHE_DIR = before;
|
|
236
|
+
}
|
|
237
|
+
});
|
|
238
|
+
});
|
|
@@ -11,7 +11,7 @@ import { describe, expect, test } from "vitest";
|
|
|
11
11
|
import { REPO } from "./__fixtures__/contract-repo";
|
|
12
12
|
import { CHANGES_ERROR_CODES, CHANGES_FINDING_CODES, COMMIT_LINK_FINDING_CODES } from "./changes";
|
|
13
13
|
import { MEMBER_RUN_REASON_CODES } from "./compose-graph";
|
|
14
|
-
import { COMPOSITES_ENVIRONMENT_REASON_CODES, COMPOSITES_ERROR_CODES, COMPOSITES_REASON_CODES, COMPOSITES_RUNTIME_REASON_CODES } from "./composites";
|
|
14
|
+
import { COMPOSITES_ENVIRONMENT_REASON_CODES, COMPOSITES_ERROR_CODES, COMPOSITES_EXECUTOR_REASON_CODES, COMPOSITES_REASON_CODES, COMPOSITES_RUNTIME_REASON_CODES } from "./composites";
|
|
15
15
|
import { WORKSPACE_ERROR_CODES } from "./declaration";
|
|
16
16
|
import { GRAPH_ERROR_CODES } from "./graph-cli";
|
|
17
17
|
import { INTENT_ERROR_CODES, INTENT_FINDING_CODES, INTENT_REASON_CODES, INTENT_WHY_CODES } from "./intent";
|
|
@@ -30,9 +30,11 @@ import { BOX_FINDING_CODES } from "./checks/boxes";
|
|
|
30
30
|
import { DIAGRAM_FINDING_CODES } from "./checks/diagrams";
|
|
31
31
|
import { SECURITY_FINDING_CODES } from "./checks/security";
|
|
32
32
|
import { CI_WORKFLOW_FINDING_CODES } from "./ci-workflow";
|
|
33
|
+
import { CI_BRANCH_FINDING_CODES } from "./checks/ci-branch";
|
|
33
34
|
import { WORK_ERROR_CODES, WORK_HISTORY_ERROR_CODES, WORK_LEASE_REFUSALS } from "./work-cli";
|
|
34
35
|
import { RUN_STATEMENT_ERROR_CODES, RUNS_ERROR_CODES, RUNS_REASON_CODES, RUNS_WRITE_ERROR_CODES } from "./runs-cli";
|
|
35
36
|
import { RUN_STATEMENT_FAILURE_CODES } from "./trust/run-statement";
|
|
37
|
+
import { LEDGER_READ_ERROR_CODES } from "../lifecycle/ledger-read";
|
|
36
38
|
import { WORK_EVIDENCE_ERROR_CODES } from "./work-evidence";
|
|
37
39
|
import { RECORD_FINDING_CODES } from "./checks/records";
|
|
38
40
|
import { ANSWER_WARNING_CODES } from "./points";
|
|
@@ -72,6 +74,7 @@ const PER_COMMAND: Record<string, readonly string[]> = {
|
|
|
72
74
|
DIAGRAM_FINDING_CODES,
|
|
73
75
|
SECURITY_FINDING_CODES,
|
|
74
76
|
CI_WORKFLOW_FINDING_CODES,
|
|
77
|
+
CI_BRANCH_FINDING_CODES,
|
|
75
78
|
STATUS_REASON_CODES,
|
|
76
79
|
STATUS_ERROR_CODES,
|
|
77
80
|
STATUS_GATE_REASON_CODES,
|
|
@@ -100,6 +103,7 @@ const PER_COMMAND: Record<string, readonly string[]> = {
|
|
|
100
103
|
COMPOSITES_REASON_CODES,
|
|
101
104
|
COMPOSITES_RUNTIME_REASON_CODES,
|
|
102
105
|
COMPOSITES_ENVIRONMENT_REASON_CODES,
|
|
106
|
+
COMPOSITES_EXECUTOR_REASON_CODES,
|
|
103
107
|
WORK_ERROR_CODES,
|
|
104
108
|
WORK_HISTORY_ERROR_CODES,
|
|
105
109
|
WORK_LEASE_REFUSALS,
|
|
@@ -108,6 +112,7 @@ const PER_COMMAND: Record<string, readonly string[]> = {
|
|
|
108
112
|
RUNS_WRITE_ERROR_CODES,
|
|
109
113
|
RUN_STATEMENT_ERROR_CODES,
|
|
110
114
|
RUN_STATEMENT_FAILURE_CODES,
|
|
115
|
+
LEDGER_READ_ERROR_CODES,
|
|
111
116
|
WORK_EVIDENCE_ERROR_CODES,
|
|
112
117
|
RECORD_FINDING_CODES,
|
|
113
118
|
FLAG_WARNING_CODES,
|
|
@@ -66,6 +66,9 @@ export const REASONS = {
|
|
|
66
66
|
// A ledger status can't fully read.
|
|
67
67
|
"ledger-unreadable": "Reading the ledger failed, so nothing from it is listed.",
|
|
68
68
|
"ledger-malformed": "Some lines of the ledger aren't release records; the rest are listed.",
|
|
69
|
+
// chant ledger (#4363): a read that can't start.
|
|
70
|
+
"filter-invalid": "A --where or --since names a field the read doesn't have, an operator its type doesn't take, or a value not of its type; nothing was read.",
|
|
71
|
+
"table-unknown": "chant ledger was given a table that is not one of resources, snapshots, runs, gates, deploys, releases or cuts.",
|
|
69
72
|
// A member's gates status can't list (status, #2674).
|
|
70
73
|
"gates-no-ledger": "The checkout has no chant/lifecycle branch, so there is no gate ledger to read.",
|
|
71
74
|
"gates-no-gate-ledger": "The branch has no gate ledger for the member: no run of it has reached a gate.",
|
|
@@ -237,6 +240,9 @@ export const REASONS = {
|
|
|
237
240
|
"environments-ledger-undeclared": "The member's ledger has releases in an environment its config's environments don't cover, so chant run --env would refuse it and it is not listed.",
|
|
238
241
|
"environments-ledger-unreadable": "The chant/lifecycle branch exists and the member's ledger environments could not be listed.",
|
|
239
242
|
"environments-component-undeclared": "A component declares an environment its member's chant.config.ts environments don't cover, so chant run --env would refuse it and it is not listed (#3153).",
|
|
243
|
+
// The forge workflow that deploys an environment (graph --composites, #4402): an environment row's executor.reason.
|
|
244
|
+
"executor-none-declared": "The member's chant.config.ts declares no executor for the environment, so nothing names the forge workflow that deploys it.",
|
|
245
|
+
"executor-config-unreadable": "The member's chant.config.ts could not be read, so whether a forge workflow deploys the environment is unknown.",
|
|
240
246
|
// A declared diagram's source or render (check, #2764). Each is also a WSP finding.
|
|
241
247
|
"diagram-source-missing": "A declared diagram's source file does not exist in the tree read.",
|
|
242
248
|
"diagram-render-missing": "A declared diagram's render file does not exist in the tree read.",
|
|
@@ -269,6 +275,7 @@ export const REASONS = {
|
|
|
269
275
|
"ci-workflow-pipeline-missing": "Under ci.workflow, no pipeline file runs the plan job, the merge job or chant ci tick.",
|
|
270
276
|
"ci-workflow-pipeline-differs": "Under ci.workflow, the pipeline does not match the workflow: it applies on merge under trunk-curated, records under trunk-curated+apply-on-merge, or applies before merge, or fails to, against apply-before-merge.",
|
|
271
277
|
"ci-workflow-publish-missing": "Under ci.workflow, no pipeline file runs chant release publish.",
|
|
278
|
+
"ci-branch-route-differs": "A member's generated pull-request pipeline targets a branch other than the one ci.branches routes the member to.",
|
|
272
279
|
// A box that holds a credential (check, #2726). Each is also a WSP finding.
|
|
273
280
|
"box-credential-declared": "A file in a box member's directory carries a literal secret: a credential's shape, or a literal where a credential goes. A variable or secret-manager reference is not one.",
|
|
274
281
|
"box-capability-unbrokered": "A capability in a member's box block names no broker, so the box would hold its credential.",
|
|
@@ -291,6 +298,10 @@ export const REASONS = {
|
|
|
291
298
|
"lease-held": "Someone holds a live lease on the work item: another worker, or, for a claim, the same one.",
|
|
292
299
|
"lease-not-held": "Nobody holds a live lease on the work item: it expired, was released or was never claimed.",
|
|
293
300
|
"lease-token-mismatch": "The live lease on the work item carries another fencing token than the one given.",
|
|
301
|
+
// Evidence that cites an ops result (work evidence, #3171): why the citation was refused. Nothing is written.
|
|
302
|
+
"work-evidence-ops-missing": "Work evidence cites a run, deploy, gate answer or release plan the local chant/lifecycle branch does not hold.",
|
|
303
|
+
"work-evidence-ops-mismatch": "Work evidence cites an ops record that does not hash to the sha256 given, or a run whose outcome disagrees with the evidence's result.",
|
|
304
|
+
"work-evidence-ops-unfinished": "Work evidence cites a run that is still gated or waiting, so it has no outcome to show.",
|
|
294
305
|
"lease-race": "Another writer changed the lease between this command's read and its write.",
|
|
295
306
|
"lease-push-rejected": "The remote refused the lease push: another clone claimed the item first, or the remote could not be reached.",
|
|
296
307
|
// The agent run record (chant workspace runs, #3033): why a write was refused, and what a read could not see. Nothing is written on a refusal.
|