Merge pull request #135459 from NousResearch/fix/rc5-darwin-baseline

fix(release): stable releases pass signed-package acceptance when no upgrade baseline exists
This commit is contained in:
ethernet
2026-10-09 13:06:34 -04:00
committed by GitHub
4 changed files with 76 additions and 5 deletions

View File

@@ -279,6 +279,7 @@ jobs:
outputs:
windows: ${{ steps.plan.outputs.windows }}
macos: ${{ steps.plan.outputs.macos }}
baseline: ${{ steps.plan.outputs.baseline }}
steps:
- uses: actions/checkout@f548e57e544e1ff5a4c46bf1e1b8685f8e4a348a # v7.0.1
with:
@@ -313,6 +314,7 @@ jobs:
outputs:
windows: ${{ steps.plan.outputs.windows }}
macos: ${{ steps.plan.outputs.macos }}
baseline: ${{ steps.plan.outputs.baseline }}
steps:
- uses: actions/checkout@f548e57e544e1ff5a4c46bf1e1b8685f8e4a348a # v7.0.1
with:
@@ -347,6 +349,7 @@ jobs:
outputs:
windows: ${{ steps.plan.outputs.windows }}
macos: ${{ steps.plan.outputs.macos }}
baseline: ${{ steps.plan.outputs.baseline }}
steps:
- uses: actions/checkout@f548e57e544e1ff5a4c46bf1e1b8685f8e4a348a # v7.0.1
with:
@@ -412,6 +415,8 @@ jobs:
windows-packaged:
name: Windows signed-package acceptance
needs: transitions-win32
# Without a published stable baseline there is no OLD package to upgrade from.
if: needs.transitions-win32.outputs.baseline != 'none'
strategy:
fail-fast: false
matrix: ${{ fromJSON(needs.transitions-win32.outputs.windows) }}
@@ -428,6 +433,8 @@ jobs:
macos-packaged-arm64:
name: macOS arm64 signed-package acceptance
needs: transitions-darwin-arm64
# Without a published stable baseline there is no OLD package to upgrade from.
if: needs.transitions-darwin-arm64.outputs.baseline != 'none'
strategy:
fail-fast: false
matrix: ${{ fromJSON(needs.transitions-darwin-arm64.outputs.macos) }}
@@ -444,6 +451,8 @@ jobs:
macos-packaged-x64:
name: macOS x64 signed-package acceptance
needs: transitions-darwin-x64
# Without a published stable baseline there is no OLD package to upgrade from.
if: needs.transitions-darwin-x64.outputs.baseline != 'none'
strategy:
fail-fast: false
matrix: ${{ fromJSON(needs.transitions-darwin-x64.outputs.macos) }}

View File

@@ -83,6 +83,12 @@ SKIPPED_BY = {
}
# Each signed-package upgrade arm and the transitions job that plans it.
PACKAGED_BY = {"macos-packaged-arm64": "transitions-darwin-arm64",
"macos-packaged-x64": "transitions-darwin-x64",
"windows-packaged": "transitions-win32"}
def gate_expectations(required: list[str], *, skip_bundles: bool, skip_tests: bool) -> dict:
"""Each gated job's required result under the claim's flags."""
active = {flag for flag, on in (("skipBundles", skip_bundles), ("skipTests", skip_tests)) if on}
@@ -95,6 +101,13 @@ def require_gate(needs: dict, required: list[str], *, skip_bundles: bool, skip_t
if not required or len(set(required)) != len(required):
raise ValueError("Invalid required-job list")
expected = gate_expectations(required, skip_bundles=skip_bundles, skip_tests=skip_tests)
for packaged, planner in PACKAGED_BY.items():
# Without a published baseline there is no OLD package, so the planner
# skips the upgrade arm. Only that planner's own successful output can excuse it.
planned = needs.get(planner, {})
if (expected.get(packaged) == "success" and planned.get("result") == "success"
and (planned.get("outputs") or {}).get("baseline") == "none"):
expected[packaged] = "skipped"
failures = [f"{name}={needs.get(name, {}).get('result', 'missing')} (expected {want})"
for name, want in expected.items() if needs.get(name, {}).get("result") != want]
if failures:
@@ -640,14 +653,21 @@ def _stage_transition(env: dict, archive: str, base: str, row: dict) -> dict:
"manifest_sha256": hashlib.sha256(file.read_bytes()).hexdigest()}
def _published_baseline(env: dict, base: str) -> dict:
def _published_baseline(env: dict, base: str) -> dict | None:
"""The previous stable package manifest, or None when no stable release shipped one.
A release that skipped bundles never writes the pointer, so its absence
only means there is no OLD package to upgrade from.
"""
try:
previous = read_manifest(env.get("BASELINE_MANIFEST_URL") or f"{base}/releases/stable/release-candidates.json",
expected_origin=base)
except urllib.error.HTTPError as error:
if error.code == 404:
raise ValueError("No published stable package baseline. Supply baseline-manifest for an actual previous stable release; acceptance cannot be skipped.") from error
raise
if error.code != 404:
raise
if env.get("BASELINE_MANIFEST_URL"):
raise ValueError("The supplied baseline-manifest does not exist") from error
return None
published = json.loads(output(["gh", "release", "view", previous["tag"], "--repo", env["GITHUB_REPOSITORY"], "--json", "tagName,isDraft,isPrerelease"]))
if published["tagName"] != previous["tag"] or published["isDraft"] or published["isPrerelease"]:
raise ValueError("Upgrade baseline must be a published stable release")
@@ -677,10 +697,17 @@ def transitions(env: dict) -> None:
previous = _published_baseline(env, base)
receipt_manifest = _receipt_from_env(env, receipt, "RECEIPT", base)
matrices = {"windows": {"include": []}, "macos": {"include": []}}
if previous is None:
# Native smokes still gate this release. Its packages become the
# baseline that the next bundle release must upgrade from.
validate_receipt(receipt_manifest, receipt, receipt_manifest.get("tag"),
receipt_manifest.get("commit"), base, archive=receipt_manifest.get("archive"))
emit({**matrices, "baseline": "none"}, env)
return
for row in plan_receipt_transitions(previous, receipt_manifest, receipt, base):
matrices[row["transition"]["platform"]]["include"].append(
_stage_transition(env, archive, base, row))
emit(matrices, env)
emit({**matrices, "baseline": "published"}, env)
# The candidate manifest is written after the smokes (decision 23): the smoke

View File

@@ -937,6 +937,38 @@ def test_transitions_from_the_bundle_receipt_emit_two_windows_rows(tmp_path, r2_
assert macos["include"] == []
def test_missing_baseline_plans_no_upgrade_arm_but_a_missing_supplied_one_blocks(
tmp_path, r2_server, https_origin, monkeypatch):
from scripts.releases import stable
url, digest = _staged_receipt(tmp_path, r2_server, https_origin, monkeypatch, "darwin-arm64")
env = {**_transitions_env(tmp_path, https_origin.base, "darwin-arm64", url, digest),
"BASELINE_MANIFEST_URL": ""}
stable.main(["transitions"], env)
emitted = dict(line.split("=", 1)
for line in (tmp_path / "output").read_text(encoding="utf-8").splitlines())
assert emitted["baseline"] == "none"
assert json.loads(emitted["macos"]) == json.loads(emitted["windows"]) == {"include": []}
env["BASELINE_MANIFEST_URL"] = f"{https_origin.base}/baseline.json"
with pytest.raises(ValueError, match="supplied baseline-manifest does not exist"):
stable.main(["transitions"], env)
def test_gate_excuses_an_upgrade_arm_only_for_its_planners_no_baseline_output():
from scripts.releases.stable import PACKAGED_BY, require_gate
required = [*PACKAGED_BY, *PACKAGED_BY.values()]
needs = {name: {"result": "skipped"} for name in PACKAGED_BY}
needs.update({name: {"result": "success", "outputs": {"baseline": "none"}}
for name in PACKAGED_BY.values()})
require_gate(needs, required, skip_bundles=False, skip_tests=False)
for packaged, planner in PACKAGED_BY.items():
for planned in ({"result": "success", "outputs": {"baseline": "published"}},
{"result": "success"}, {"result": "failure", "outputs": {"baseline": "none"}}):
with pytest.raises(ValueError, match=packaged):
require_gate({**needs, planner: planned}, required, skip_bundles=False, skip_tests=False)
def test_candidate_manifest_needs_every_call_and_stages_the_archive_manifest(
tmp_path, r2_server, https_origin, monkeypatch):
from scripts.releases import stable

View File

@@ -415,6 +415,9 @@ local helper suite. Never store the bootstrap output as a repo channel list.
The last successful stable release that shipped bundles records
`releases/stable/release-candidates.json` on the configured R2 public origin.
A release that skipped bundles does not replace it.
When no baseline exists, the release still runs every native smoke but skips
the signed-package upgrade arms. A `baseline-manifest` input that does not
exist is a blocker.
It identifies actual Windows universal MSIX bundles, macOS ZIPs and package
provenance. The next run combines those records with its candidate manifest
and uses the existing native bundled-update drivers.