From 32db47f5358fc52c725b9140a443a2398b57f511 Mon Sep 17 00:00:00 2001 From: ScriptedAlchemy Date: Thu, 17 Sep 2026 23:12:32 +0000 Subject: [PATCH] ci(release): propose betas from master; stable only by manual dispatch Every push to master now runs release-please with the prerelease config, so the release PR it opens is the next vX.Y.Z-beta.N and merging it publishes a GitHub prerelease for release-beta.yml. A stable release requires dispatching release-please.yml with channel=stable and the exact version, which selects release-please-config-stable.json. The codex branch beta channel, its config, and its manifest are gone; one manifest tracks both channels. --- .github/workflows/beta-release-please.yml | 81 ------------------- .github/workflows/ci.yml | 34 ++++---- .github/workflows/release-please.yml | 69 +++++++++++++++- .../release-pr-distribution-acceptance.yml | 4 +- .release-please-manifest-beta.json | 3 - .release-please-manifest.json | 2 +- docs/RELEASE-AUTOMATION.md | 77 +++++++++++------- ....json => release-please-config-stable.json | 18 ++++- release-please-config.json | 19 +---- scripts/check-release-pr-integrity.sh | 2 +- tests/release_safety_test.sh | 32 +++++--- 11 files changed, 174 insertions(+), 167 deletions(-) delete mode 100644 .github/workflows/beta-release-please.yml delete mode 100644 .release-please-manifest-beta.json rename release-please-config-beta.json => release-please-config-stable.json (62%) diff --git a/.github/workflows/beta-release-please.yml b/.github/workflows/beta-release-please.yml deleted file mode 100644 index 9c2c72e135..0000000000 --- a/.github/workflows/beta-release-please.yml +++ /dev/null @@ -1,81 +0,0 @@ -# Beta prerelease channel for the codex/tracedecay-total-redesign-plan-reopened branch. -# -# Every push to the branch opens or updates a release PR proposing the next -# vX.Y.Z-beta.N version (release-please versioning strategy "prerelease", -# config: release-please-config-beta.json, manifest: -# .release-please-manifest-beta.json). Merging that PR makes this workflow tag -# the merge commit and publish a GitHub *prerelease* (never marked "latest"), -# which triggers .github/workflows/release-beta.yml to build, attest, and -# upload `tracedecay-beta--` archives — the exact asset names -# the CLI beta upgrade channel expects (src/cloud.rs::asset_name). -# -# Force a cycle without waiting for a push: -# gh workflow run beta-release-please.yml --ref codex/tracedecay-total-redesign-plan-reopened -# then merge the "chore(release): release X.Y.Z-beta.N" PR it opens. -# -# Download + install a published beta manually (macOS arm64): -# gh release download vX.Y.Z-beta.N -p 'tracedecay-beta-vX.Y.Z-beta.N-aarch64-macos.tar.gz' -# tar xzf tracedecay-beta-vX.Y.Z-beta.N-aarch64-macos.tar.gz -# install -m 755 tracedecay ~/.cargo/bin/tracedecay -# -# Rebuild assets for an existing beta tag (recovery): -# gh workflow run release-beta.yml --ref vX.Y.Z-beta.N -f release_tag=vX.Y.Z-beta.N - -name: Beta Release Please - -on: - push: - branches: [codex/tracedecay-total-redesign-plan-reopened] - workflow_dispatch: - -permissions: - contents: read - -concurrency: - group: beta-release-please-${{ github.ref }} - cancel-in-progress: false - -jobs: - release-please: - name: Open beta release PR or create GitHub prerelease - if: >- - github.repository == 'ScriptedAlchemy/tracedecay' && - vars.BETA_CHANNEL_ENABLED == 'true' - runs-on: ubuntu-latest - permissions: - contents: write - issues: write - pull-requests: write - steps: - - name: Open beta release PR or create GitHub prerelease - id: release - uses: googleapis/release-please-action@45996ed1f6d02564a971a2fa1b5860e934307cf7 # v5.0.0 - with: - token: ${{ secrets.RELEASE_PLZ_TOKEN }} - target-branch: codex/tracedecay-total-redesign-plan-reopened - config-file: release-please-config-beta.json - manifest-file: .release-please-manifest-beta.json - - - name: Checkout release PR - if: steps.release.outputs.prs_created == 'true' - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - with: - ref: ${{ fromJSON(steps.release.outputs.pr).headBranchName }} - token: ${{ secrets.RELEASE_PLZ_TOKEN }} - - # The release PR must carry a lockfile that matches the bumped - # Cargo.toml — the same invariant as the stable channel's lockfile step - # in release-please.yml and the later locked release build. - - name: Update release PR lockfile - if: steps.release.outputs.prs_created == 'true' - env: - GH_TOKEN: ${{ secrets.RELEASE_PLZ_TOKEN }} - RELEASE_PR_JSON: ${{ steps.release.outputs.pr }} - run: | - RELEASE_PR_NUMBER=$( - jq --exit-status --raw-output \ - '.number | select(type == "number")' \ - <<<"$RELEASE_PR_JSON" - ) - scripts/update-release-pr-lockfile.sh - gh pr ready "$RELEASE_PR_NUMBER" --repo "$GITHUB_REPOSITORY" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 628a7e9f55..fe2725d384 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2,7 +2,7 @@ name: CI on: push: - branches: [master, feature/holographic-memory, codex/tracedecay-total-redesign-plan-reopened] + branches: [master, feature/holographic-memory] pull_request: branches: ['**'] @@ -310,7 +310,7 @@ jobs: # standard methods the client declared, and the upstream `initialize` # response is the only authority for those. Without the component the # runner still has rustup's `rust-analyzer` proxy on PATH, so the daemon - # routes the language and only the spawn fails — the test would then + # routes the language and only the spawn fails. The test would then # measure a missing component instead of the negotiation contract. This # action exports `RUSTUP_TOOLCHAIN`, so the component has to be installed # here rather than through `rust-toolchain.toml`, which it overrides. @@ -366,8 +366,8 @@ jobs: # then nextest under the `ci` policy and the perf cargo profile with # `--no-tests=fail`. One target directory for the group, so a later # partition's shared units are fingerprint hits. Every partition runs - # even after an earlier one fails — as nextest's `ci` policy runs every - # test — and the step fails if any did. Every nextest run rewrites the + # even after an earlier one fails, as nextest's `ci` policy runs every + # test, and the step fails if any did. Every nextest run rewrites the # `ci` profile's junit.xml, so each partition's report is moved under # its own name before the next runs. The per-partition wall times this # prints are the measurements the manifest's macOS budgets wait for. @@ -463,9 +463,9 @@ jobs: # shape) keeps the whole 55 min compile plus an archive round trip on the # path and then the dashboard rebuild and parity on the same job: ~88 min, # or ~70 with both moved elsewhere. The partitions pay for the shorter path - # with duplicated compile — each root partition rebuilds the library chain + # with duplicated compile. Each root partition rebuilds the library chain # beneath the root crate, about 200 core-minutes per run across the seven - # jobs — which is free on hosted runners where only wall time and the + # jobs, which is free on hosted runners where only wall time and the # 20-job concurrency cap count. A cold dependency cache adds ~6 min to every # job alike (the dependencies compile at opt-level 0 in ~17 core-minutes). # The floor no partitioning reaches is the root chain itself: library @@ -511,7 +511,7 @@ jobs: # standard methods the client declared, and the upstream `initialize` # response is the only authority for those. Without the component the # runner still has rustup's `rust-analyzer` proxy on PATH, so the daemon - # routes the language and only the spawn fails — the test would then + # routes the language and only the spawn fails. The test would then # measure a missing component instead of the negotiation contract. This # action exports `RUSTUP_TOOLCHAIN`, so the component has to be installed # here rather than through `rust-toolchain.toml`, which it overrides. @@ -545,7 +545,7 @@ jobs: # the CLI and the evaluator), so its save covers the other root # partitions exactly. The three lower partitions unify dependency # features differently and each recompile ~30 small dependency units - # (syn, serde_json, futures, chrono, ...) against it every run — a few + # (syn, serde_json, futures, chrono, ...) against it every run. A few # minutes inside jobs that are far from the critical path, cheaper than # three more cache entries. Restored before the first `cargo metadata` # below so the registry index comes from the cache too. @@ -616,8 +616,8 @@ jobs: # hotpath/, and the `#[ignore]`d test in the crate's library test target # (`controlled_workloads::tests::hotpath_off_vs_on_durable_results_are_ # identical`) spawns them from there; `--run-ignored only` with - # `--no-tests=fail` is the only way it runs, and nothing else — no - # partition, artifact upload or junit merge — consumes the executables. + # `--no-tests=fail` is the only way it runs, and nothing else. No + # partition, artifact upload or junit merge consumes the executables. # # Its own job rather than a tail on the `root-lib` partition, where it ran # after the root library's tests: the feature-on build recompiles every @@ -625,12 +625,12 @@ jobs: # the feature flip changes each one's metadata hash) plus the example, # ~10 min on top of the ~26 min root chain, which made that job the lane's # critical path. Here the graph beneath the evaluator compiles from the - # checkout in both modes — a serial chain domain → contracts → + # checkout in both modes. A serial chain domain → contracts → # rusqlite-runtime → runtime-core → code-index → sessions → query → # search-eval → example of ~9 min per mode with four compile slots (cargo # `--timings`, -j4 on EPYC 7742 cores; the same pair of builds took 19.5 # min on a hosted runner in run 34138693824, when the single-job lane - # still built them as their own resolution) — then the library test target + # still built them as their own resolution). Then the library test target # (24 s against the feature-off graph) and the test itself (under 1 s). # With setup, ~24 min hosted: beside the partitions and no longer than the # slowest of them (25.9 and 27.4 min in run 34296614024), so the `Test @@ -1058,7 +1058,7 @@ jobs: # builds, a graph the perf-profile test lanes share nothing with, so there # is no compiled tree to reuse and nothing to gain from waiting for one. # As the last steps of the Linux test job they ran only after a green - # suite — never once on this branch — and would have held the Linux shards + # suite, never once on this branch, and would have held the Linux shards # back by their own duration. Here the verdict lands independently at # about clippy's cost: the cold `cargo clippy --workspace --all-targets` # took 13.7 min (run 34231734416), the second check re-resolves only the @@ -1095,8 +1095,8 @@ jobs: - uses: actions/checkout@v7 # Judge formatting with the toolchain `rust-toolchain.toml` pins, not # whatever `stable` is on the runner. rustfmt's output changes between - # releases — 1.98.1 breaks a `.unwrap_or_else(|err| { ... })` chain that - # 1.97.1 keeps inline — so a `stable` rustfmt rejects a tree the pinned + # releases. 1.98.1 breaks a `.unwrap_or_else(|err| { ... })` chain that + # 1.97.1 keeps inline, so a `stable` rustfmt rejects a tree the pinned # rustfmt developers run formats exactly, and every contributor sees a # red lane they cannot reproduce. `dtolnay/rust-toolchain@stable` also # exports `RUSTUP_TOOLCHAIN`, which overrides the toolchain file rather @@ -1153,7 +1153,7 @@ jobs: runs-on: ubuntu-24.04-arm timeout-minutes: 30 env: - # Stock (upstream) Hermes pin — the generated plugin must keep working + # Stock (upstream) Hermes pin. The generated plugin must keep working # against this exact upstream commit. Bump deliberately after rerunning # scripts/hermes_stock_integration.sh against the new ref locally. HERMES_UPSTREAM_REPO: https://github.com/NousResearch/hermes-agent.git @@ -1207,7 +1207,7 @@ jobs: runs-on: ubuntu-24.04-arm timeout-minutes: 30 env: - # Stock host pins — the installed bundle must keep working against these + # Stock host pins. The installed bundle must keep working against these # exact host releases. Bump deliberately after rerunning # scripts/claude_stock_integration.sh and # scripts/opencode_stock_integration.sh against the new versions locally. diff --git a/.github/workflows/release-please.yml b/.github/workflows/release-please.yml index 2c3fac1bd7..1c40773578 100644 --- a/.github/workflows/release-please.yml +++ b/.github/workflows/release-please.yml @@ -1,9 +1,37 @@ +# Release channels. +# +# Every push to master proposes the next beta prerelease: release-please +# (release-please-config.json, versioning "prerelease") opens or updates a +# "chore(release): release X.Y.Z-beta.N" PR. Merging it tags the merge commit +# and publishes a GitHub *prerelease*, which release-beta.yml turns into +# attested `tracedecay-beta--` assets, the names the CLI beta +# upgrade channel resolves. +# +# A stable release never happens on its own. Dispatch this workflow with +# channel=stable and the exact version to ship; release-please then opens a +# "chore(release): release X.Y.Z" PR from release-please-config-stable.json, +# and merging that publishes a full release for release.yml. +# +# Recover assets for an existing tag: +# gh workflow run release-beta.yml --ref vX.Y.Z-beta.N -f release_tag=vX.Y.Z-beta.N +# gh workflow run release.yml --ref vX.Y.Z -f release_tag=vX.Y.Z + name: Release Please on: push: branches: [master] workflow_dispatch: + inputs: + channel: + description: "beta proposes the next prerelease; stable opens a release PR for the exact version below" + type: choice + options: [beta, stable] + default: beta + version: + description: "Stable version to ship, e.g. 0.1.0 (required when channel=stable)" + type: string + default: "" permissions: contents: read @@ -22,12 +50,40 @@ jobs: issues: write pull-requests: write steps: + - name: Resolve the release channel + id: channel + env: + CHANNEL: ${{ inputs.channel || 'beta' }} + VERSION: ${{ inputs.version }} + run: | + case "$CHANNEL" in + beta) + [[ -z "$VERSION" ]] || + { echo "channel=beta does not take a version; release-please derives the next prerelease" >&2; exit 1; } + echo "config=release-please-config.json" >>"$GITHUB_OUTPUT" + echo "release_as=" >>"$GITHUB_OUTPUT" + ;; + stable) + [[ "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]] || + { echo "channel=stable requires an exact X.Y.Z version, got '${VERSION}'" >&2; exit 1; } + echo "config=release-please-config-stable.json" >>"$GITHUB_OUTPUT" + echo "release_as=$VERSION" >>"$GITHUB_OUTPUT" + ;; + *) + echo "unknown channel: $CHANNEL" >&2 + exit 1 + ;; + esac + - name: Open release PR or create GitHub release id: release uses: googleapis/release-please-action@45996ed1f6d02564a971a2fa1b5860e934307cf7 # v5.0.0 with: token: ${{ secrets.RELEASE_PLZ_TOKEN }} target-branch: master + config-file: ${{ steps.channel.outputs.config }} + manifest-file: .release-please-manifest.json + release-as: ${{ steps.channel.outputs.release_as }} - name: Checkout release PR if: steps.release.outputs.prs_created == 'true' @@ -36,11 +92,22 @@ jobs: ref: ${{ fromJSON(steps.release.outputs.pr).headBranchName }} token: ${{ secrets.RELEASE_PLZ_TOKEN }} + # The release PR is opened as a draft and stays one until it carries a + # lockfile matching the bumped Cargo.toml, the same invariant the later + # locked release build enforces. - name: Update release PR lockfile if: steps.release.outputs.prs_created == 'true' env: + GH_TOKEN: ${{ secrets.RELEASE_PLZ_TOKEN }} RELEASE_PR_JSON: ${{ steps.release.outputs.pr }} - run: scripts/update-release-pr-lockfile.sh + run: | + RELEASE_PR_NUMBER=$( + jq --exit-status --raw-output \ + '.number | select(type == "number")' \ + <<<"$RELEASE_PR_JSON" + ) + scripts/update-release-pr-lockfile.sh + gh pr ready "$RELEASE_PR_NUMBER" --repo "$GITHUB_REPOSITORY" - name: Checkout master if: always() diff --git a/.github/workflows/release-pr-distribution-acceptance.yml b/.github/workflows/release-pr-distribution-acceptance.yml index d0ab026152..3f25283c70 100644 --- a/.github/workflows/release-pr-distribution-acceptance.yml +++ b/.github/workflows/release-pr-distribution-acceptance.yml @@ -12,9 +12,7 @@ name: Release PR distribution acceptance on: pull_request: - branches: - - master - - codex/tracedecay-total-redesign-plan-reopened + branches: [master] permissions: contents: read diff --git a/.release-please-manifest-beta.json b/.release-please-manifest-beta.json deleted file mode 100644 index b536e4aa08..0000000000 --- a/.release-please-manifest-beta.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - ".": "0.1.0-beta.40" -} diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 3b9beca410..b536e4aa08 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "0.0.74" + ".": "0.1.0-beta.40" } diff --git a/docs/RELEASE-AUTOMATION.md b/docs/RELEASE-AUTOMATION.md index 216408e855..0827ddc269 100644 --- a/docs/RELEASE-AUTOMATION.md +++ b/docs/RELEASE-AUTOMATION.md @@ -1,14 +1,23 @@ # Release Automation -TraceDecay uses two workflows with one publication authority: +TraceDecay releases from `master` on two channels with one publication +authority: -1. `Release Please` runs on pushes to `master`. - - Opens or updates a release PR. +1. `Release Please` runs on pushes to `master` and proposes the next **beta + prerelease** (`release-please-config.json`, versioning `prerelease`). + - Opens or updates a draft release PR, then marks it ready once the + lockfile matches the bumped manifests. - Bumps `.release-please-manifest.json`, `version.txt`, `Cargo.toml`, - `server.json`, and `Cargo.lock`. - - Updates `CHANGELOG.md`. - - Creates the `vX.Y.Z` tag and GitHub Release. -2. `Release` runs after a GitHub Release is published. + `server.json`, and `Cargo.lock`; updates `CHANGELOG.md`. + - Merging the PR creates the `vX.Y.Z-beta.N` tag and a GitHub prerelease, + which `Release (Beta)` turns into `tracedecay-beta--` + assets. + - A **stable** release is a deliberate act: dispatch `Release Please` with + `channel=stable` and the exact `version`. That run uses + `release-please-config-stable.json` (which also bumps the TypeScript + SDK) and opens a `chore(release): release X.Y.Z` PR; merging it publishes + a full release for `Release`. +2. `Release` runs after a stable GitHub Release is published. - Builds platform binaries. - Uploads release assets, checksums, and `install.sh`. - Updates the in-repository `server.json` MCP registry manifest. @@ -41,7 +50,7 @@ Add these repository secrets: trigger the follow-up `release.yml` workflow. npm publication is tokenless: no npm secret exists anywhere in the repository or its workflows. The publish job authenticates through npm trusted publishing -(OIDC) — it holds `id-token: write`, and the pinned npm CLI (12.0.2, above the +(OIDC), it holds `id-token: write`, and the pinned npm CLI (12.0.2, above the 11.5.1 trusted-publishing floor; Node 22.23.2, above the 22.14.0 floor) exchanges the GitHub OIDC token for a short-lived publish credential itself. Provenance is attached automatically by trusted publishing. No @@ -64,7 +73,7 @@ exactly: - Allowed actions: `npm publish` All fields are case-sensitive and unvalidated at save time; mismatches only -surface as `ENEEDAUTH`/404 at publish time — the publish job names this +surface as `ENEEDAUTH`/404 at publish time, the publish job names this configuration in its failure message. Only GitHub-hosted runners are supported (the job uses `ubuntu-latest`). After the first successful OIDC publish, set Package → Settings → Publishing access to "Require two-factor @@ -127,29 +136,21 @@ byte-verified no-op; identical version with different bytes fails and requires an SDK version bump. The `scripts/check-sdk-publish-workflow.py` gate (run by SDK conformance CI) enforces this job isolation. -## Normal Release Flow +## Normal Release Flow (beta) 1. Merge feature/fix PRs into `master`. -2. `Release Please` opens or updates a release PR. -3. Review the generated version and changelog. -4. Merge the release PR. -5. `Release Please` creates the tag and GitHub Release. -6. The GitHub Release triggers `release.yml`, which builds and uploads - checksummed GitHub Release assets, refreshes `server.json`, and publishes - `@tracedecay/sdk` to npm once release verification passes. - -## Beta Channel - -The `codex/tracedecay-total-redesign-plan-reopened` branch runs its own release-please -channel: `beta-release-please.yml` with `release-please-config-beta.json` and -`.release-please-manifest-beta.json` (versioning strategy `prerelease`, -prerelease type `beta`). Every push to the branch opens or updates a release -PR; merging it tags `vX.Y.Z-beta.N` and publishes a GitHub prerelease, which -triggers `release-beta.yml` to build, attest, and upload -`tracedecay-beta--` archives plus `SHA256SUMS`. Prereleases are -never marked `latest`, and the CLI's beta upgrade channel -(`src/cloud.rs::asset_name`) resolves exactly these asset names. Manual -install (macOS arm64): +2. `Release Please` opens or updates the `chore(release): release + X.Y.Z-beta.N` PR. `Release PR distribution acceptance` runs the Linux + release battery on it. +3. Review the generated version and changelog, then merge. +4. `Release Please` tags `vX.Y.Z-beta.N` and publishes a GitHub prerelease + (never marked `latest`). +5. The prerelease triggers `release-beta.yml`, which builds, attests, and + uploads `tracedecay-beta--` archives plus `SHA256SUMS` — + exactly the names the CLI beta upgrade channel (`src/cloud.rs::asset_name`) + resolves. + +Manual install of a published beta (macOS arm64): ```sh gh release download -p "tracedecay-beta--aarch64-macos.tar.gz" @@ -157,6 +158,22 @@ tar xzf "tracedecay-beta--aarch64-macos.tar.gz" install -m 755 tracedecay ~/.cargo/bin/tracedecay ``` +## Stable Release (manual) + +Stable versions are never proposed automatically. When the current beta is +ready to ship: + +```sh +gh workflow run release-please.yml --ref master -f channel=stable -f version=X.Y.Z +``` + +`Release Please` then opens `chore(release): release X.Y.Z` from +`release-please-config-stable.json`. Merging it publishes a full GitHub +Release, and `release.yml` builds and uploads the assets, refreshes +`server.json`, marks the release `latest`, and publishes `@tracedecay/sdk` to +npm once release verification passes. The next push to `master` resumes +proposing betas above the new stable version. + ## Manual Recovery If the GitHub Release is created but the binary artifact workflow does not run, diff --git a/release-please-config-beta.json b/release-please-config-stable.json similarity index 62% rename from release-please-config-beta.json rename to release-please-config-stable.json index 9a4471a5f6..38ed27e81a 100644 --- a/release-please-config-beta.json +++ b/release-please-config-stable.json @@ -3,9 +3,6 @@ "release-type": "simple", "include-v-in-tag": true, "include-component-in-tag": false, - "versioning": "prerelease", - "prerelease": true, - "prerelease-type": "beta", "draft-pull-request": true, "pull-request-title-pattern": "chore(release): release ${version}", "packages": { @@ -22,6 +19,21 @@ "type": "json", "path": "server.json", "jsonpath": "$.version" + }, + { + "type": "json", + "path": "sdks/typescript/package.json", + "jsonpath": "$.version" + }, + { + "type": "json", + "path": "sdks/typescript/package-lock.json", + "jsonpath": "$.version" + }, + { + "type": "json", + "path": "sdks/typescript/package-lock.json", + "jsonpath": "$.packages[''].version" } ] } diff --git a/release-please-config.json b/release-please-config.json index 5dcc4d14a8..9a4471a5f6 100644 --- a/release-please-config.json +++ b/release-please-config.json @@ -3,6 +3,10 @@ "release-type": "simple", "include-v-in-tag": true, "include-component-in-tag": false, + "versioning": "prerelease", + "prerelease": true, + "prerelease-type": "beta", + "draft-pull-request": true, "pull-request-title-pattern": "chore(release): release ${version}", "packages": { ".": { @@ -18,21 +22,6 @@ "type": "json", "path": "server.json", "jsonpath": "$.version" - }, - { - "type": "json", - "path": "sdks/typescript/package.json", - "jsonpath": "$.version" - }, - { - "type": "json", - "path": "sdks/typescript/package-lock.json", - "jsonpath": "$.version" - }, - { - "type": "json", - "path": "sdks/typescript/package-lock.json", - "jsonpath": "$.packages[''].version" } ] } diff --git a/scripts/check-release-pr-integrity.sh b/scripts/check-release-pr-integrity.sh index a8a1f16a45..021e29d89e 100755 --- a/scripts/check-release-pr-integrity.sh +++ b/scripts/check-release-pr-integrity.sh @@ -44,7 +44,7 @@ destructive_metadata=() while IFS=$'\t' read -r status path _; do [[ -n ${status:-} ]] || continue case "$path" in - .release-please-manifest-beta.json | .release-please-manifest.json | CHANGELOG.md | Cargo.lock | Cargo.toml | crates/tracedecay-cli/Cargo.toml | server.json | version.txt) + .release-please-manifest.json | CHANGELOG.md | Cargo.lock | Cargo.toml | crates/tracedecay-cli/Cargo.toml | server.json | version.txt) if [[ $status != M ]]; then destructive_metadata+=("$status $path") fi diff --git a/tests/release_safety_test.sh b/tests/release_safety_test.sh index e604a34764..1bb2206055 100755 --- a/tests/release_safety_test.sh +++ b/tests/release_safety_test.sh @@ -25,11 +25,7 @@ with Path("Cargo.toml").open("rb") as handle: root = tomllib.load(handle) version = Path("version.txt").read_text(encoding="utf-8").strip() -release_manifest_path = Path( - ".release-please-manifest-beta.json" - if "-" in version - else ".release-please-manifest.json" -) +release_manifest_path = Path(".release-please-manifest.json") release_manifest = json.loads( release_manifest_path.read_text(encoding="utf-8") ) @@ -72,16 +68,28 @@ python3 - <<'PY' import json from pathlib import Path -config = json.loads( - Path("release-please-config-beta.json").read_text(encoding="utf-8") +beta = json.loads(Path("release-please-config.json").read_text(encoding="utf-8")) +stable = json.loads( + Path("release-please-config-stable.json").read_text(encoding="utf-8") ) -if config.get("draft-pull-request") is not True: - raise SystemExit( - "beta release PRs must remain draft while the generated lockfile is updated" - ) +# The default channel, what every push to master proposes, must be a +# prerelease. A stable release is only reachable through the explicit dispatch +# that selects the stable config. +if beta.get("versioning") != "prerelease" or beta.get("prerelease") is not True: + raise SystemExit("release-please-config.json must propose prereleases") +if stable.get("prerelease") or stable.get("versioning") == "prerelease": + raise SystemExit("release-please-config-stable.json must publish full releases") +for path, config in ( + ("release-please-config.json", beta), + ("release-please-config-stable.json", stable), +): + if config.get("draft-pull-request") is not True: + raise SystemExit( + f"{path}: release PRs must remain draft while the generated lockfile is updated" + ) sdk_paths = [ item.get("path", "") - for item in config["packages"]["."]["extra-files"] + for item in beta["packages"]["."]["extra-files"] if str(item.get("path", "")).startswith("sdks/") ] if sdk_paths: