142 Commits
Author SHA1 Message Date
Maciej Lisiewski d7244125e9 ci: run every cheap repository guard as steps of one job
Thirteen guard workflows (fifteen jobs) ran on the same 4-vCPU Ubuntu
runner, all started right after detect, each paying a checkout and a full
billed minute for 0-13s of work: infographics, profile archives, case
collisions, blob size, lazy_deps imports, stale icons, unrelated
histories, contributor attribution, uv.lock, prompt-surface and
package-lock diffs, the supply-chain scan and dependency bounds, and the
Docker lint.

repo-guards.yml runs them as steps of one job. Each step keeps its old
lane gate in its `if:`, and runs whatever an earlier step did
(`!cancelled()`), so one red guard never hides another; any failed step
fails the job, which all-checks-pass requires. The review-status-*
artifacts the live PR comment reads keep their names. Files the steps
write go under $RUNNER_TEMP, and the guards that need no toolchain run
before setup-pm, whose dependency phase also checks the lock.

The four PR-only jobs the strict release gate tolerated as skips are now
steps of a job that runs on every event, so the only tolerated release
skip left is the deferred Desktop E2E.
2026-11-06 02:15:36 +01:00
Teknium 52f15ab6a9 fix(release): a Store commit answered 202 Accepted counts as committed
Publishing v0.23.0 committed the Microsoft Store submission, and Partner
Center answered POST .../commit with 202 Accepted and status CommitStarted.
The submission API helper only accepted 200, so the publication pass logged
"commit failed: 202 {'status': 'CommitStarted'}" and told the release owner
to check Partner Center by hand, even though the commit had started.

Any 2xx is now success. The fake Partner Center in the tests answers a commit
with 202, as the live API did, so the six tests that commit a submission fail
on the old check.
2026-11-05 13:47:00 -08:00
Maciej Lisiewski aac7f1b696 Merge pull request #148651 from NousResearch/ci/desktop-targets-one-source
refactor(release): define the desktop targets once
2026-11-04 05:41:25 +01:00
unsupportedpastels 07274ac654 fix(release): don't say Publish now for a committed auto-publish Store build
With MS_STORE_HOLD set after a build was committed Immediate, the API
refuses the switch to Manual and the pass told the operator to select
Publish now, though the build goes live by itself once certified. A
refused switch now reads the committed mode: Immediate reports that it
publishes on its own and that the hold cannot apply.
2026-11-03 20:22:40 -08:00
Teknium d6c98faab0 fix(release): MS_STORE_HOLD opts a Store build back into Publish now
Committing the Store draft at publication makes every certified build go
live by itself, with no way left to keep one back for a chosen release time.
Setting the repository variable MS_STORE_HOLD=true makes the publication
pass commit the draft Manual instead: it is certified and then waits for
Publish now in Partner Center, with a warning that says so. A rerun of a
held submission is a no-op and never flips it to Immediate. Unset keeps the
auto-publish default.

Also quote the real v0.22.7 refusal (state 'PreProcessing') and update the
stable-releases developer guide, which still described the held commit.
2026-11-03 20:22:40 -08:00
unsupportedpastels b7ed4972da fix(release): commit the Store draft at publication so it goes live once certified
The green run committed the Store submission held (Manual) and the
publication pass tried to switch it to Immediate. Partner Center refuses
to update a committed submission (v0.22.7: 409 InvalidState, "Cannot
update the submission because it is in certification"), so every
release needed Publish now by hand.

stable-store now leaves an uncommitted draft set to Immediate, and the
publication pass commits it. Nothing reaches certification or customers
before the release is published, and the build goes live as soon as it
passes certification.
2026-11-03 20:22:40 -08:00
Maciej Lisiewski 79c1648300 refactor(release): define the desktop targets once
scripts/releases/desktop_targets.py holds the (electron platform, arch)
triple and derives the manifest spelling and the native handoff legs from it.
channel_artifacts, channel_publish, channel_releases, stable and the builds
table read it instead of five hand-written copies, and NATIVE_LEGS has one
definition instead of two.

read_native_receipts always reads every native leg, so its legs parameter
and _leg_targets go. A claim with includeMacosIntel: true now says why it is
refused. Tests iterate the targets directly instead of skipping a 2x2 case.
2026-11-03 23:54:59 +01:00
Maciej Lisiewski 06192b894c ci(release): stop building and publishing macOS Intel desktop bundles
Remove the darwin-x64 build, receipt and smoke jobs from Desktop Bundled
Release and the candidates/transitions/packaged-x64 jobs from Stable Release,
with the release scripts that required them: the macos-intel lane, the
darwin-x64 job group, the x64 macOS target in candidate/channel manifests and
the two-leg macOS feed merge. The macOS updater feed is arm64-only.

Pre-lanes claims still read: the published ones all carry
includeMacosIntel:false; a claim with true is now refused.
2026-11-03 20:58:05 +01:00
ethernet d6d77c3649 Merge pull request #141725 from NousResearch/feat/store-auto-publish
feat(release): Store certifies before the gate; publication switches it live
2026-10-20 17:43:36 -04:00
ethernet 01af57a06c Merge pull request #141716 from NousResearch/perf/release-metadata-only-receipts
perf(release): stage candidates from metadata; verify package bytes at publish
2026-10-20 17:31:33 -04:00
ethernet 4148f6d1f3 feat(release): Store certifies before the gate; publication switches it live
stable-store commits the Store submission held (targetPublishMode Manual)
instead of leaving an uncommitted draft, so certification runs before the
release gate and a run whose stable-publish fails still shows customers
nothing. The publication pass then PUTs the held submission with
Immediate and commits it again, so it goes live without a Partner Center
click.

Microsoft documents no call that releases a held submission and does not
say whether update accepts a committed one (409 is documented only as
"the current state of the app"). A 409 from the switch leaves the
submission held and prints the Publish now instruction; the release
stays green. The first real release shows which answer the API gives.

Reconcile receipt-bound Store work independently of channel advancement, including
already-current published releases, so an interruption cannot strand a held
submission. Record each upload's claim in certification notes and replace a held
Manual predecessor only after its exact remote abandon marker matches the claim
commit. Unknown owners and publishing submissions remain protected.
2026-10-20 15:50:30 -04:00
ethernet fa6a127ed6 perf(release): stage candidates from metadata; verify package bytes at publish
candidate-manifest spent ~4 of its 5.2 min downloading 8.7 GB (stable run
38491090442: both MSIX bundles, including the 4.3 GB Store bundle) only to
re-hash them, and each stage-receipt job re-downloaded its group's ZIPs and
bundle (60-256 s). The same bytes are hashed by the build job that uploaded
them, bound by digest in its handoff receipt, and downloaded and digest-
checked again by release_artifacts.publish (materialize) before anything
goes live.

Candidate staging and receipts now download only the metadata JSONs; package
rows still carry the receipt digests. The one check that read package
contents, the universal bundle identity against its per-arch rows, moves to
publish, after materialize has verified the bytes. A Store-only claim has no
metadata to fetch, so candidate-manifest reads its receipts alone.
2026-10-20 15:50:30 -04:00
ethernet 550e2a891f feat(release): publication commits the Store draft with auto-publish on
The Store build used to be committed at green with targetPublishMode
Manual, and the publication pass could only print "click Publish now":
the submission API has no call that releases a held submission (update
and commit return 409 once it is committed). Every stable release needed a
person in Partner Center.

stable-store now uploads the verified bundle as an uncommitted draft
(msstore publish --noCommit) and stops. The publication pass, which runs
only when the release really goes out, reads the receipt-bound candidate
manifest, checks the pending draft carries that release's Store bundle,
sets targetPublishMode Immediate and commits it. Certification starts
then, and the build goes live when it passes.

- A draft from a newer green run is left for its own release; a committed
  or live submission is a no-op, so reruns are safe.
- submit refuses to replace a committed submission still on its way live
  (an earlier release); msstore publish would delete it.
- complete's Reconcile step now receives the MS_STORE_* credentials; it had
  none, so the success path silently skipped the Store.
- A failed or unreadable Store step stays a warning: the release publishes.
2026-10-20 15:50:30 -04:00
Maciej Lisiewski ba78364593 fix: update tag selection to work without local refs 2026-10-20 20:36:30 +02:00
Teknium fe45cf6684 ci(release): start the Windows bundle when both arches are built, not after their smokes
Stable builds each Windows arch in its own desktop-bundled-release call and
assembles the universal bundle in a third call that needs both. Each per-arch
call also ran that arch's MSIX install-and-chat smoke, so assembly waited for
the slower arch's smoke after its build: on rc.2-v0.22.2 the bundle sat behind
the arm64 smoke from 07:16 to 07:28.

A new windows-smokes-with-bundle input moves both MSIX smokes into the
win32-bundle call. The smokes already fetch the staged package from the R2
handoff, so they run there beside assembly. The per-arch calls then only
build, the bundle call's phase result requires both smokes, and the candidate
manifest records the Windows smoke results from the bundle call, so a failed
smoke still blocks the release. Callers that build everything in one call
(canary, channel, commit builds) leave the input off and are unchanged.
2026-10-15 02:06:01 -07:00
ethernet 9686d55ca8 ci(release): admit stable releases from release/* branches, not just main
The release entrypoint and the stable gate both required the claimed commit
to be an ancestor of main. Both now accept an ancestor of main or of any
release/* branch, via one shared git query (stable.release_line_contains).
The entrypoint prunes its mirror of release/* so a deleted branch stops
admitting commits. The termux previous-package ancestry check follows the
same rule. Workflows still dispatch from main.
2026-10-14 16:04:20 -04:00
ethernet a8cc19ebfd fix(release): a Store-only Windows build gets no signing vars; Store-plus-macOS smokes record only macOS
- The Windows build step passes AZURE_* empty when skip-signed is set.
  after-pack and electron-builder sign whenever the vars exist, and the
  Azure login is skipped with the signed lane, so a Store-only build with
  the vars configured would sign without credentials.
- candidate_manifest records a smoke result only for calls whose package is
  a target. `--without windows` runs the Windows candidate calls for the
  Store bundle; their success is not a smoke, and counting it made assemble
  refuse the manifest.
2026-10-14 14:59:28 -04:00
ethernet f9d8a74035 feat(release): split the Microsoft Store bundle into its own windows-store lane
`windows` used to build the signed MSIX, the universal bundle and the Store
bundle together, so skipping the signed pipeline meant skipping the Store too.
Now `windows` is the signed pipeline and `windows-store` the unsigned Store
bundle; both default on and the shared per-arch builds only make what the
lanes ask for.

- lanes: new `windows-store` lane and derived `windows-build` (either lane),
  which gates the shared candidates-win32-* jobs and handoffs.
- desktop-bundled-release: `skip-signed` builds only the Store variant and
  needs no Azure login, signing, metadata, smoke or receipt; `skip-store`
  builds and submits no Store bundle (replaces the old `store` input).
- manifest: a Store-only release has no package rows, only the Store bundle
  file; assemble() now takes the claim's lanes and checks each Windows bundle
  against its lane.
- claims record `lanesRevision: 2`. A lanes claim without it was cut when
  `windows` also meant the Store bundle (rc.2-v0.22.1), so it reads as
  windows + windows-store.
- release_artifacts: drop the unused `assemble` CLI command, which could not
  name the lanes.
2026-10-14 14:39:03 -04:00
ethernet 6743ab711a fix(release): termux-only claims finish and partial releases keep the shared downloads page
Two review findings on the lanes change:

- candidate-manifest judged only the desktop calls, and when a claim built
  none (--only termux) it called require_success with an empty list and could
  never finish. It now requires every candidate call the claim built, Termux
  included, and judges smoke results only for the desktop calls that have one.
- `complete` rendered the shared releases/stable/index.html for any release
  with bundles, so a windows-only release replaced the previous full release's
  macOS links. admit now emits a derived `desktop-channel` flag and the
  renderer takes --no-channel-page, leaving the tag page and release table
  alone while the shared page waits for a release that serves the channel.
2026-10-14 11:24:03 -04:00
ethernet 36a8ee18cd feat(release): choose what a stable release builds and tests with lanes
The skip and opt-in flags (--skip-bundles, --skip-tests, --include-macos-intel,
--include-termux) were four booleans in the claim, and every consumer
re-derived what they meant. A release is now a set of lanes, defined once in
scripts/releases/lanes.py: five build lanes (docker, windows, macos-arm64,
macos-intel, termux) and ten test lanes (ci, nix, docker-tests, pm-bundle,
windows-live, install-e2e, bootstrap-version, termux-checks, smoke, upgrade).
The default is every lane except macos-intel and termux.

  release.py release --only windows,docker      # the Store bundle + image, untested
  release.py release --with macos-intel --without install-e2e

- The claim records `lanes`. A claim cut before lanes existed still parses:
  skipBundles/skipTests/include* translate to the lane set that gated the
  same jobs, because the sequencer reads every claim tag ever cut.
- `admit` emits `lanes` and `jobs` (a run flag per lane-controlled job). Each
  job's `if` reads its own `jobs` entry and the gates derive their expected
  skips from the same table, so the workflow and the gates cannot drift.
  tests/ci/test_stable_release_graph.py evaluates every `if` against 37 lane
  sets.
- A release may build any subset: the candidate manifest carries exactly the
  claim's targets, and the final tag records null for the docker digest or the
  manifest digest when the claim built neither.
- The protected desktop channel, feeds, APT index and upgrade baseline move
  only when the claim built windows and macos-arm64. A partial set is staged,
  verified and (with windows) submitted to the Store, and leaves the channels
  on the previous release. A release that moves no channel is finished once
  public.
- publish-bundles passes `store` so a claim without windows submits nothing
  to the Microsoft Store.

Not exercised end to end: no real release was cut with a partial set.
2026-10-14 11:03:26 -04:00
ethernet a4aeb141af Merge pull request #138159 from NousResearch/ci/stable-release-dispatch-from-main
feat(release): dispatch Stable Release from the default branch
2026-10-14 00:05:26 -04:00
Teknium df8c3ca54e fix(release): publish replaces the stable head instead of create-only
The channel advance wrote releases/stable/release-candidates.json with no
ETag, which the store sends as If-None-Match: *. After the first bundled
release the head always exists, so every later publish failed with R2 412
(ChannelConflict) after making the GitHub release public: v0.22.0 went
public while the stable head, desktop feeds and Docker aliases stayed on
v0.21.7. Read the head and write under its current ETag.
2026-10-13 20:24:59 -07:00
ethernet 667e6639af feat(release): dispatch Stable Release from the default branch
Stable ran on its claim tag, so its Actions caches lived in a scope no
later claim could read. Every attempt rebuilt wheels and re-signed every
Windows payload file. Canary already runs from the default branch with the
tag as an input. Stable now does the same.

- `check_claim` reads the commit from the claim tag. It requires the
  default-branch dispatch of stable-release.yml instead of GITHUB_REF and
  GITHUB_SHA.
- The protected publisher accepts stable-release.yml on the default branch.
- `release.py` and the release entrypoint dispatch on the default branch.
- Runs are matched to a claim by their run name. Every run now has the same
  head branch. Runs that started on the tag still match.
- `release_artifacts` reads RELEASE_COMMIT before GITHUB_SHA. The publish
  and Store jobs now set it to the admitted commit.
2026-10-13 21:22:30 -04:00
ethernet c11002329b fix(release): publish the final vX.Y.Z Docker tag again
After #135054 the images are pushed under the attempt ref (rc.N-vX.Y.Z),
and promote_stable only moved stable/latest, so :vX.Y.Z and
:vX.Y.Z-desktop were never created. The publication pass now tags both
variants with the final release tag alongside the stable/latest aliases,
from the attempt's digest, and reads each back. The final tag still only
appears at publish, so an earlier attempt cannot collide with it.
2026-10-13 20:02:43 -04:00
alt-glitch 303b70f75b chore(release): retire HERMES_GUEST_ONBOARDING from bundles, build tooling and the updater
The Microsoft Store variant forced HERMES_GUEST_ONBOARDING=1 into its bundle env,
and commit/channel builds could bake it with --bundle-env. Nothing reads it now.

- scripts/bundles/desktop_inputs.py: the Store bundle env is just the request's.
- bundle_env.py / bundle-env.mjs: the key is no longer allowed for new builds.
- channel-protocol.ts: the key moves to RETIRED_BUNDLE_ENV_KEYS, so preview
  manifests already published with it still decode (the HERMES_SKIP_INTRO
  precedent).
- BUILDING.md example uses HERMES_PREVIEW_FULL_CONNECTORS.
- Build tests use another allowed key as their sample; both validators reject the
  retired key and the decoder drops it.
- msix-virtualization.test.mjs: every generated MSIX manifest (store, release,
  canary, channel) keeps filesystem and registry write virtualisation disabled,
  and the builder config declares unvirtualizedResources. The machine marker and
  the shared %LOCALAPPDATA%\hermes root depend on that.
2026-10-14 00:32:21 +05:30
ethernet 4b050f6731 fix(release): stable advance trusts receipts instead of downloading ~33 GB
The 0.21.7 publication pass downloaded every package twice (~13 GB of
native handoffs, then all 20.5 GB of candidate files including the Store
packages the feeds never reference) to recompute digests the receipts
already bind, then died in the macOS feed merge:

  ModuleNotFoundError: No module named 'ruamel'

- The sequencer steps run under python_packages with ruamel.yaml (the
  node toolchain has no project deps), as the canary finalize already does.
- Stable advance fetches only metadata-*.json and the per-arch *-mac.yml
  legs. assemble(local_bytes=False) binds each artifact by its receipt
  sha256 and takes the macOS sha512s from the receipt-bound feed legs,
  cross-checked against receipt sizes.
- promote() materializes only non-package files (feed legs, APT indexes).
- finalize(verify_bytes=False) checks remote sizes with HEAD instead of
  streaming the zip/dmg back down.

Canary (no accepted candidate) keeps the full download path unchanged.
2026-10-12 02:18:43 -04:00
ethernet bfa4f5098c feat(release): the stable sequencer narrates what it is doing
A publication run sat silent for 8+ minutes while it downloaded ~20 GB,
with no way to tell which phase it was in or why a claim was held.

scripts/releases/progress.py logs to stderr (stdout stays the JSON steps)
with GitHub ::group::/::notice::/::error:: markers:

- every git/gh/docker command the sequencer runs, with its time and result
- discovery counts, and each claim's state with the reason (run attempt
  and conclusion, draft location, abandon marker, receipt/release match)
- the plan: why each version advances, is skipped, or is held
- each step as a timed group: publish (receipt digests) and advance
- the channel advance's phases, plus per-file progress for the handoff
  fetch and candidate materialize downloads (the slow part)
- the head before and after, and a top-level ::error:: on failure
2026-10-12 02:13:17 -04:00
ethernet 36efcb8c87 fix(release): a fresh flip advances the head with the receipt it just wrote
Publishing 0.21.7 made the release public, then failed the channel
advance in the same pass with

  ChannelError: Stable candidate manifest differs from the final release receipt

discover() builds each record before the vX.Y.Z receipt tag exists, so a
green claim's record has candidate_manifest_sha256 = None.
publish_attempt() writes the receipt but returned only the Docker digest,
so the advance compared the archive's real digest against None. Every
first-pass publish of a bundle release failed here; only a rerun (which
rediscovers the receipt) could advance the head.

publish_attempt() now returns both receipt digests and the sequencer
applies them to the record before the advance. The sequencer fixture can
now stage a fresh green claim (no receipt tag, draft on the attempt ref,
one successful run); the new test fails on main with the production error.
2026-10-12 02:00:07 -04:00
ethernet 310e05c027 fix(release): the final stable tag push authenticates with GH_TOKEN
The publication reconcile checks out with persist-credentials: false, so
when the sequencer reached ensure_final_tag() its `git push origin
refs/tags/vX.Y.Z` died with

  fatal: could not read Username for 'https://github.com'

GH_TOKEN authenticated gh but never git. The push now carries its own
auth: when GH_TOKEN is set it resets inherited credential helpers and
uses `gh auth git-credential`, so the token stays out of argv and out of
.git/config, and the checkout keeps persisting nothing.
2026-10-12 01:36:01 -04:00
ethernet 576012d329 fix(release): stable advance checks native metadata against the payload tag
Stable Release Publication for 0.21.7 failed in advance_stable with

  ChannelError: Native metadata release identity mismatch

publish_release() passes the attempt ref (rc.15-v0.21.7) to
read_native_receipts(), which is right for the handoff receipts: they are
staged under releases/tag/<attempt>/. But record() stamps each
metadata-<platform>-<arch>.json with the payload tag (v0.21.7), so the
row check compared v0.21.7 against rc.15-v0.21.7 and refused every
stable release. Canary never noticed because its tag == payload_tag.

read_native_receipts() now takes payload_tag for the metadata row check
while receipts keep binding the attempt ref. The existing fixture used a
plain vX.Y.Z tag for both, which is how this stayed hidden; the new test
stages attempt-ref receipts with payload-tag rows.
2026-10-12 01:26:03 -04:00
Teknium ddfe1d7662 fix(release): a Store submission failure never blocks publishing
The Microsoft Store step is no longer a release gate:

- stable-store runs with continue-on-error, and stable-phase-result no
  longer requires it, so a failed submission leaves the run green and
  'release.py publish' available.
- On failure it prints a warning and a run-summary section with the exact
  Store .msixbundle URL to upload in Partner Center by hand.
- The publication pass turns a failed or unreadable Store check into a
  warning instead of failing after the release has moved.

Also covers a camelCase (REST-shaped) submission (review).
2026-10-11 17:28:32 -07:00
Teknium 6992df4cdc fix(release): the Store submit reads the CLI's PascalCase submission keys
rc.13-v0.21.7 failed 'Submit the verified Store package' with
'unreadable submission: {"Id": ..., "ApplicationCategory": ...'.

msstore submission get serializes the .NET DevCenterSubmission property names
(Status, TargetPublishMode), not the REST API's camelCase, so the in-flight
check never found 'status' and the Manual-publish write added a second
'targetPublishMode' key the CLI would ignore. Both now use the key as the CLI
spells it. The test fake emits the CLI's casing; with it main fails 5 tests
with the production error.
2026-10-11 17:28:32 -07:00
Teknium 1fd1827726 fix(release): the Store submit reads the submission with 'get', not 'status'
rc.12-v0.21.7 failed 'Submit the verified Store package' with
StoreError: unreadable submission status: ''.

store.submit parsed 'msstore submission status' as JSON. For a packaged
(MSIX) app that command prints a Spectre markup line ('Submission Status =
...') plus status tables, never JSON, and prints nothing when no submission
is pending (msstore-cli Commands/Submission/StatusCommand.cs). The first real
Store submission therefore always failed. 'msstore submission get' serializes
the submission as JSON (GetCommand.cs) and is already what submit reads after
the upload, so the in-flight check reads that instead.

The test fake now answers 'status' the way the CLI does; with it, main fails
5 tests with the production error.
2026-10-11 13:31:55 -07:00
ethernet a12c3417c8 fix(release): abandon only the approved attempt, and only after the new cut is viable
Review findings on the abandon prompt:

- The prompt approved one ref but `abandon()` re-read the remote and cleared
  every outstanding attempt of that version, so a concurrent operator's
  replacement attempt could be cancelled and its draft deleted. The remote
  is now read again after the answer, and anything other than the same single
  attempt is refused; only that attempt is abandoned.
- Abandonment ran before the ancestry, final-tag and draft-body checks, so a
  yes could destroy the old attempt and then refuse the replacement. Those
  checks now run first; the prompt is the last step before the claim. The
  attempt number ignores markers, so abandoning does not change the tag.
- The prompt test now varies stdin and stdout separately and asserts `input`
  is never called unless both are terminals.
2026-10-11 15:22:52 -04:00
ethernet 74d788fcc9 feat(release): offer to abandon a blocking attempt from an interactive terminal
`release.py release` refuses while any attempt is outstanding and prints
the abandon command for the operator to copy. When stdin and stdout are
both terminals it now asks `Abandon <ref> and cut a new attempt? [y/N]`
right after that guidance. Yes goes through the same `abandon` as the
subcommand (cancel runs, delete the draft, write the marker) and the cut
continues as the next attempt; the default is no, and anything
non-interactive still refuses as before.

The confirmer and `inspect` are parameters of `release()`, not globals,
so only the CLI entrypoint opts in.
2026-10-11 15:22:52 -04:00
Teknium 0d3b11c21c ci(release): refuse Intel-less retirement targets and define opt-in platforms once
- Retiring a preview channel now requires the stable head to carry every
  package the preview ships. An Intel preview could otherwise be pinned to an
  arm64-only stable head it has no package in, and its client would fail to
  resolve the retirement for good.
- The docs and release.py's next-steps text said Intel Macs stay on the last
  Intel release's feed. They do not: promote rewrites the one shared macOS
  stable feed with the arm64 leg, so Intel Macs get no update until a release
  opts Intel back in. Only the Termux APT channel stays on its previous release.
- New scripts/releases/optional_platforms.py holds the claim flag, jobs,
  handoff, target and smoke of each opt-in platform; stable.py, channel
  releases, release artifacts and the entrypoint read from it. stable.py is
  back under 1,000 lines. No behavior change.
- Test: a macOS Intel receipt against a baseline without Intel plans no
  upgrade arm (baseline=none); against a baseline with Intel it plans one.
2026-10-11 11:38:33 -07:00
Teknium 5f9b77cce0 ci(release): stop building macOS Intel and Termux in stable releases by default
Stable releases no longer build the macOS Intel (darwin-x64) desktop bundle
or the Termux APT package unless the release is cut with an opt-in flag.
Intel macOS is not supported on main, and its macos-15-intel bundle leg
takes 76-88 minutes, which is most of the release's wall time.

    python scripts/release.py release --commit SHA --include-macos-intel
    python scripts/release.py release --commit SHA --include-termux

The flags are written into the signed claim as includeMacosIntel and
includeTermux, like skipBundles/skipTests. admit emits them, and the
candidates-darwin-x64, transitions-darwin-x64 and macos-packaged-x64 jobs
(Intel) and candidates-termux and termux-checks (Termux) run only when
their flag is on. The gate requires those jobs to report skipped when the
flag is off; the skip flags keep their meaning.

Claims cut before these keys existed (every rc.* tag so far) have neither
key. validate_claim treats a missing key as false and still refuses a
non-boolean value or any unknown key, so sequencer discover/reconcile keep
working over the historical tags.

Without the opt-in platforms the candidate manifest, receipts, mac feed,
protected R2 head, APT promotion and downloads table all work with those
rows absent: the macOS feed carries the arm64 leg alone, the APT indexes
are left on the previous Termux release, and the unbuilt smoke is recorded
as skipped (a passed smoke for an unbuilt target is refused). A release
that opts Intel back in after one that left it out has no Intel baseline,
so its Intel upgrade arm is skipped like a first release.

Preview channels, canary and the PM bundle artifacts are unchanged.
2026-10-11 11:38:33 -07:00
Teknium 76c1a50eb1 fix(release): an artifact download that fails its checksum is fetched again
rc.11-v0.21.7's Windows x64 MSIX smoke failed with 'Artifact checksum
mismatch' on the 2.6 GB package. The stored object was correct (re-downloaded:
size and SHA256 match the receipt exactly), so the runner received bad bytes.
_download_url retried HTTP and connection errors but raised on a completed
body that failed its hash, so one bad transfer failed the job and its
dependents until a whole-run rerun.

A size/SHA256 mismatch now counts as a bad transfer and is fetched again
within the same three attempts. A stored object that really differs from its
receipt fails all three the same way, and the verified destination is never
replaced.
2026-10-11 06:06:05 -07:00
Teknium b30f95ac4a fix(release): discover orders attempts numerically so rc.10 is the version's record
Review on #136434 (@jquesnelle): discover() sorted records by version only,
so attempts inside a version kept git ls-remote's NAME order (rc.10 between
rc.1 and rc.2). reconcile indexes records by version with the last one
winning, so once a version reached rc.10 it handed rc.9 (burned, no release)
to publish_attempt, which refuses with "Final tag release database ID is
invalid". v0.21.7 is at rc.9, so its next cut is exactly rc.10.

- Sort by (version, attempt).
- The test fakes list refs sorted by name, as git does; insertion order hid this.
- rc.10 test (fails without the sort), and a same-commit retry test (rc.1
  abandoned, rc.2 re-cut at the same commit wins).
2026-10-10 23:58:25 -07:00
Teknium 3e1d9dc59a fix(release): an abandoned attempt is burned before its version's final tag is checked
Every Stable Release Publication run since v0.21.6 has crashed in
sequencer.discover():

  ValueError: v0.21.6 points at a different commit than rc.1-v0.21.6

v0.21.6 took four attempts: rc.1-3 were abandoned (each has its
abandoned-rc.<N> marker) and rc.4 won and became v0.21.6. discover() tested
"does the final tag exist" before "is this attempt abandoned", so for rc.1 it
compared v0.21.6's commit (rc.4's) with rc.1's and raised; the abandoned
branch was only reachable for versions that never got a final tag. Any
version whose winner was not rc.1 broke every later reconcile, including the
next release's publish.

The abandoned marker is now checked first: an abandoned attempt is burned
whatever its version became. A NON-abandoned attempt whose final tag points
elsewhere still raises.
2026-10-10 23:58:25 -07:00
Maciej Lisiewski bda3aea496 ci: remove unused variables (linter) 2026-10-10 15:20:14 +02:00
Maciej Lisiewski 0006a5e229 ci: fix PLW1509, PLW1510, PLW2101 linter errors 2026-10-10 15:20:13 +02:00
ethernet 040dbb9053 Merge pull request #135348 from NousResearch/harder-linter-fixes-part2
Harder linter fixes part 2
2026-10-09 13:08:10 -04:00
Maciej Lisiewski 2ff58849f4 ci: ruff UP037 fixes 2026-10-09 17:34:38 +02:00
Maciej Lisiewski 9eab688b42 ci: ruff UP017 fixes 2026-10-09 17:34:36 +02:00
Maciej Lisiewski 32b70e9733 ci: FURB188, FURB192 linter fixes 2026-10-09 17:31:52 +02:00
ethernet 25a096f317 fix(release): treat any missing stable baseline as no upgrade arm
A release that skipped bundles never writes the stable baseline pointer,
so its absence is normal at any point, not only before the first bundle
release. Drop the final-tag scan. A 404 on the default pointer now always
plans no upgrade arm. A supplied baseline-manifest that does not exist
still blocks, because that is a bad input.
2026-10-09 01:41:35 -04:00
ethernet 4e23d79de4 fix(release): let the first bundle release pass without an upgrade baseline
No stable release has shipped signed packages yet. Every final tag so far
skipped bundles or predates the claim pipeline. So
releases/stable/release-candidates.json does not exist, and the three
"Pin actual OLD and NEW signed packages" jobs refused with "No published
stable package baseline".

transitions now treats a 404 as a first release only when no final stable
tag records a candidate manifest and no baseline-manifest input was given.
It still validates the receipt, emits empty matrices and baseline=none.
The packaged upgrade arms skip on that output. The gate accepts their skip
only when their own planner succeeded with baseline=none, so a missing
baseline after a real bundle release still blocks.
2026-10-09 01:37:59 -04:00
ethernet 8ac5c74432 Harder linter fixes (#135281)
* ci: fix B004 linter rule

* ci: prefix unused variables with underscore (RUF059)

* ci: don't cast int to int

* ci: remove unnecessary if/else where both blocks yield same result

* ci: RUF024

* ci: fix RUF017, RUF018, RUF022

* ci: RUF013, RUF015

* ci: RUF007

* ci: RUF009

* ci: address code review
2026-10-08 18:56:50 -04:00
Maciej Lisiewski aa2d120cba ci: prefix unused variables with underscore (RUF059) 2026-10-08 19:22:03 +02:00