feat(bsky): EXPECTED_NA['upgrade'] suppresses the upgrade-tier base deploy — single deploy = PR head; bluesky-pds declares it (no deployable base: every published tag pins the republished moving :0.4). upgrade_base() extracted pure + 6 unit tests; meta-key doc regenerated. 253 unit tests + repo lint PASS
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone Build is passing

This commit is contained in:
2026-06-11 11:51:12 +00:00
parent f88c6bc78d
commit e9745c8c74
5 changed files with 130 additions and 14 deletions

View File

@ -6,3 +6,17 @@ HEALTH_PATH = "/xrpc/_health" # PDS health endpoint; returns {"version": ...} o
HEALTH_OK = (200,)
DEPLOY_TIMEOUT = 600
HTTP_TIMEOUT = 600
# UPGRADE rung: published versions exist (0.1.1+v0.4, 0.2.0+v0.4) but BOTH pin the moving image
# tag ghcr.io/bluesky-social/pds:0.4, which upstream republished with main-branch builds
# (@atproto/pds 0.5.1, Node 24, /app/index.ts — no index.js), so NO published version can deploy
# as an upgrade base anymore: the base crash-loops MODULE_NOT_FOUND before the PR head is ever
# exercised (phase bsky root cause; cc-ci-plan/upstream/bluesky-pds.md). Declared intentional
# until a fixed exact-pinned version (0.3.0+v0.4.219, mirror PR #2) is merged AND published —
# then DROP this and set UPGRADE_BASE_VERSION = "0.3.0+v0.4.219" so the upgrade rung is
# exercised again from the first deployable base.
EXPECTED_NA = {
"upgrade": "no deployable upgrade base: every published version pins the moving tag "
"pds:0.4, which upstream republished with incompatible main builds (index.js removed) — "
"re-enable via UPGRADE_BASE_VERSION once a fixed version is published post-merge",
}

View File

@ -0,0 +1,79 @@
"""Unit tests for `run_recipe_ci.upgrade_base` — the deploy-once base-version decision.
Phase bsky: a recipe whose published versions ALL pin a moving image tag that upstream
republished with incompatible builds (bluesky-pds) has no deployable upgrade base — deploying
one fails the INSTALL tier before the PR head is ever exercised. The sanctioned escape hatch is
the EXISTING declared-intentional-skip mechanism: EXPECTED_NA["upgrade"] now also suppresses
the base deploy (single deploy = PR head; the tier records "skip"; derive_rungs classifies it
intentional with the declared reason). These tests lock the decision matrix; derive_rungs'
classification of the resulting skip is already covered in test_results.py.
"""
from __future__ import annotations
import os
import sys
from types import SimpleNamespace
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "..", "runner"))
import run_recipe_ci # noqa: E402
from harness import lifecycle # noqa: E402
ALL = {"install", "upgrade", "backup", "restore", "custom"}
def _meta(expected_na=None, upgrade_base_version=None):
return SimpleNamespace(EXPECTED_NA=expected_na, UPGRADE_BASE_VERSION=upgrade_base_version)
def test_default_prev_published(monkeypatch):
# upgrade in stages, ≥2 published versions, nothing declared → recipe_versions[-2]
monkeypatch.setattr(lifecycle, "previous_version", lambda r: "0.1.0+v1")
assert run_recipe_ci.upgrade_base(ALL, _meta(), "somerecipe") == "0.1.0+v1"
def test_single_published_version_no_base(monkeypatch):
monkeypatch.setattr(lifecycle, "previous_version", lambda r: None)
assert run_recipe_ci.upgrade_base(ALL, _meta(), "somerecipe") is None
def test_upgrade_not_in_stages_no_base(monkeypatch):
monkeypatch.setattr(
lifecycle,
"previous_version",
lambda r: (_ for _ in ()).throw(AssertionError("not consulted")),
)
assert run_recipe_ci.upgrade_base(ALL - {"upgrade"}, _meta(), "somerecipe") is None
def test_upgrade_base_version_override_wins(monkeypatch):
monkeypatch.setattr(
lifecycle,
"previous_version",
lambda r: (_ for _ in ()).throw(AssertionError("not consulted")),
)
meta = _meta(upgrade_base_version="0.7.0+3.3.1")
assert run_recipe_ci.upgrade_base(ALL, meta, "discourse") == "0.7.0+3.3.1"
def test_expected_na_upgrade_suppresses_base(monkeypatch):
# bluesky-pds shape: published versions exist but are undeployable — declared EXPECTED_NA
# upgrade → NO base (single deploy is the PR head), even though previous_version would
# return one and even if UPGRADE_BASE_VERSION is set (the declaration is the stronger,
# documented fact).
monkeypatch.setattr(lifecycle, "previous_version", lambda r: "0.1.1+v0.4")
declared = {"upgrade": "no deployable upgrade base (moving tag republished)"}
assert run_recipe_ci.upgrade_base(ALL, _meta(expected_na=declared), "bluesky-pds") is None
assert (
run_recipe_ci.upgrade_base(
ALL, _meta(expected_na=declared, upgrade_base_version="0.2.0+v0.4"), "bluesky-pds"
)
is None
)
def test_expected_na_other_rung_does_not_suppress(monkeypatch):
# an EXPECTED_NA for backup_restore (custom-html-tiny shape) must NOT touch the upgrade base
monkeypatch.setattr(lifecycle, "previous_version", lambda r: "0.1.0+v1")
meta = _meta(expected_na={"backup_restore": "stateless"})
assert run_recipe_ci.upgrade_base(ALL, meta, "custom-html-tiny") == "0.1.0+v1"