Skip to content

feat: add promote downgrade/monotonicity gate#156

Merged
joshua-temple merged 1 commit into
mainfrom
feat/promote-downgrade-gate
Jun 14, 2026
Merged

feat: add promote downgrade/monotonicity gate#156
joshua-temple merged 1 commit into
mainfrom
feat/promote-downgrade-gate

Conversation

@joshua-temple

Copy link
Copy Markdown
Collaborator

Problem

The promote path does no version comparison. It propagates the source env's version forward verbatim, so promoting an older version onto an env would silently regress it (and every later env, including prod) to an older release. Version.Compare existed but was unused by promote. This closes that silent-regression hole.

Fix

A hard monotonicity gate in promote preflight (internal/promote/preflight.go checkDowngrade), run alongside the existing divergence guard. For each planned promotion it parses the incoming version (the source version being promoted) and the target env's current version and compares them with version.Compare:

  • Forward (newer) and equal (idempotent re-promote): allowed, unaffected.
  • Strictly older (downgrade): blocked by default with a clear error naming both versions and the env.
  • --allow-downgrade (new workflow_dispatch boolean input, threaded input -> ALLOW_DOWNGRADE env -> --allow-downgrade flag -> PreflighterOptions, mirroring --rollback-on-failure): permits a non-prod downgrade with a recorded warning.
  • The terminal (prod) env always requires --allow-downgrade, with a prod-specific error, even when a lower env in the same cascade permitted the same downgrade. Prod is never silently downgraded.
  • Non-semver / unparseable versions fail open with a warning rather than hard-blocking, so non-semver pipelines keep working.

Additive: forward promotions are unchanged; the gate only fires on a detected downgrade, and the new input defaults to false.

Verification

  • Unit: 7 table-driven tests in internal/promote/downgrade_test.go (forward, equal, blocked-without-flag, allowed-with-flag, prod-always-requires-flag, non-semver warn+allow, empty-version skip). All pass.
  • e2e: scenario e2e/scenarios/20-promote-allow-downgrade.yaml asserts the generated promote.yaml exposes the allow_downgrade input and threads it into the preflight step. PASS locally:
    --- PASS: TestMultiStepScenarios/Promote_allow_downgrade_dispatch_input (19.37s)
  • go build ./... && go test ./... (1241 pass); e2e module go build/go vet clean; golangci-lint run ./... clean on both modules.
  • Dogfood drift synced: regenerated .github/workflows/promote.yaml committed.
  • Docs: --allow-downgrade documented in the CLI reference.

Signed-off-by: Joshua Temple <joshua.temple@stablekernel.com>
@joshua-temple joshua-temple merged commit 8e7f930 into main Jun 14, 2026
9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant