docs: record the release pipeline status and legacy-client checks

This commit is contained in:
tiennm99 committed 2026-10-21 17:11:57 +07:00
1 parent 6c7771e04c
commit e712978ff6
1 file changed
+45 -10
@@ -1,7 +1,7 @@
---
phase: 10
title: "Release pipeline and legacy-client compatibility"
status: pending
status: in-progress
priority: P1
effort: "4h"
dependencies: [9]
@@ -88,14 +88,14 @@ host has no patchelf and no display.
| What the old client reads or needs | v0.4.0 | v0.4.1 | v0.5.0 | New release provides | Result |
|------------------------------------|--------|--------|--------|----------------------|--------|
| Endpoint: `GET /repos/tiennm99/claude-code-usage-bubble/releases/latest` (`release.rs:8-9`, `:49`) | same | same | same | The Tauri tag release is non-draft, non-prerelease and marked latest in the same repo | to check |
| Release JSON fields: `tag_name`, `assets[].name`, `assets[].browser_download_url`, optional `assets[].digest` (`release.rs:104-119`) | same | same | same | Standard GitHub release object; nothing from `latest.json` is read | to check |
| Version gate: `tag_name` parsed with the suffix-ignoring compare, must be greater than the running version (`release.rs:60-63`) | same | same | same | Tag `vX.Y.Z` above `v0.5.0`, equal to `Cargo.toml` (release.yml tag check) | to check |
| Asset pick: name equal, ASCII case-insensitive, to `claude-code-usage-bubble.exe`, else the first asset ending in `.exe` (`release.rs:7`, `:65-74`) | same | same | same | Exactly one `*.exe` asset, named `claude-code-usage-bubble.exe`; `.exe.sig`, `.AppImage`, `.AppImage.sig`, `.deb` and `latest.json` do not end in `.exe` (`make-latest-json.js` guard) | to check |
| Checksum: SHA-256 of the downloaded body must equal `digest` `sha256:<hex>` when present (`release.rs:78`, `:86-100`; `install.rs` `download`) | `install.rs:79` | `install.rs:88` | `install.rs:88` | GitHub computes `digest` for every uploaded asset (v0.5.0 shows one); the exe is uploaded unmodified after signing, so the digest matches | to check |
| Download: 2xx after redirects from `browser_download_url`, whole body in memory | same | same | same | Same GitHub download URL form as today; the exe grows from about 0.75 MB to several MB, which the in-memory read handles | to check |
| Swap and relaunch | Helper copy of the old exe runs `--apply-update` (`install.rs:44-45`, `:128`), waits for the parent, then starts the new exe with `--updated-to X` only (`install.rs:173`) | `self_replace` in-process, then `--wait-pid <pid> --updated-to X` (`install.rs:155-172`) | same as v0.4.1 (`install.rs:155-172`) | The Tauri exe accepts a bare start, `--updated-to X` alone, and `--wait-pid <pid> --updated-to X` (Phase 04 CLI). It never needs `--apply-update`. The v0.4.0 helper runs `run_cli` before any mutex (`main.rs:33`), and the Tauri lock is a different, per-session lock, so the helper cannot block the new instance | to check |
| Leftovers | `<exe>.old.<pid>`, `updates/updater-helper-*.exe`, `updates/update.exe` | `updates/update.exe` | same as v0.4.1 | Phase 04 startup cleanup deletes all three patterns | to check |
| Endpoint: `GET /repos/tiennm99/claude-code-usage-bubble/releases/latest` (`release.rs:8-9`, `:49`) | same | same | same | The Tauri tag release is non-draft, non-prerelease and marked latest in the same repo | pass (code): re-read at all three tags (`release.rs:8-9`, `:49`); a tag run of `release.yml` publishes without `--draft`/`--prerelease`, with `--latest`, then fails if `gh api …/releases/latest` returns another tag. Draft check: pending |
| Release JSON fields: `tag_name`, `assets[].name`, `assets[].browser_download_url`, optional `assets[].digest` (`release.rs:104-119`) | same | same | same | Standard GitHub release object; nothing from `latest.json` is read | pass (code): `release.rs:104-119` at all three tags; `gh release create` makes a standard release object |
| Version gate: `tag_name` parsed with the suffix-ignoring compare, must be greater than the running version (`release.rs:60-63`) | same | same | same | Tag `vX.Y.Z` above `v0.5.0`, equal to `Cargo.toml` (release.yml tag check) | pass (code): `release.rs:60-63` at all three tags; the `prepare` job requires `vX.Y.Z[-suffix]` equal to `Cargo.toml`. The first Tauri tag must be above v0.5.0 (a version bump, not a workflow check) |
| Asset pick: name equal, ASCII case-insensitive, to `claude-code-usage-bubble.exe`, else the first asset ending in `.exe` (`release.rs:7`, `:65-74`) | same | same | same | Exactly one `*.exe` asset, named `claude-code-usage-bubble.exe`; `.exe.sig`, `.AppImage`, `.AppImage.sig`, `.deb` and `latest.json` do not end in `.exe` (`make-latest-json.js` guard) | pass (code): `release.rs:7`, `:65-74` at all three tags; `make-latest-json.js` rejects any other `.exe` (any case) and any asset outside the exact set, and runs before `gh release create`. Draft check: pending |
| Checksum: SHA-256 of the downloaded body must equal `digest` `sha256:<hex>` when present (`release.rs:78`, `:86-100`; `install.rs` `download`) | `install.rs:79` | `install.rs:88` | `install.rs:88` | GitHub computes `digest` for every uploaded asset (v0.5.0 shows one); the exe is uploaded unmodified after signing, so the digest matches | pass (code): `release.rs:78`, `:86-98`; `install.rs:95` (v0.4.0) and `:104` (v0.4.1, v0.5.0) compare the body hash. `tauri signer sign` was run on this host with a throwaway key: it writes `<file>.sig` beside the binary and leaves the binary as built |
| Download: 2xx after redirects from `browser_download_url`, whole body in memory | same | same | same | Same GitHub download URL form as today; the exe grows from about 0.75 MB to several MB, which the in-memory read handles | pass (code): `install.rs:79-108` (v0.4.0) and `:88-117` (v0.4.1, v0.5.0) read the whole body after a 2xx; the URL is GitHub's `browser_download_url` as today |
| Swap and relaunch | Helper copy of the old exe runs `--apply-update` (`install.rs:44-45`, `:128`), waits for the parent, then starts the new exe with `--updated-to X` only (`install.rs:173`) | `self_replace` in-process, then `--wait-pid <pid> --updated-to X` (`install.rs:155-172`) | same as v0.4.1 (`install.rs:155-172`) | The Tauri exe accepts a bare start, `--updated-to X` alone, and `--wait-pid <pid> --updated-to X` (Phase 04 CLI). It never needs `--apply-update`. The v0.4.0 helper runs `run_cli` before any mutex (`main.rs:33`), and the Tauri lock is a different, per-session lock, so the helper cannot block the new instance | pass (code): v0.4.0 `install.rs:44-45`, `:128-143` (helper args), `:173` (`--updated-to` only), `main.rs:33` (`run_cli` before `app::run`; `main.rs` is identical at all three tags); v0.4.1 and v0.5.0 `install.rs:155-172`. The Tauri `src/cli.rs` `parse` accepts a bare start, `--updated-to X` and `--wait-pid <pid> --updated-to X` and ignores `--apply-update` (`unknown_flags_are_ignored`) |
| Leftovers | `<exe>.old.<pid>`, `updates/updater-helper-*.exe`, `updates/update.exe` | `updates/update.exe` | same as v0.4.1 | Phase 04 startup cleanup deletes all three patterns | pass (code): v0.4.0 `install.rs:214` (`.old.<pid>`), `:247` (`update.exe`), `:258` (`updater-helper-<pid>.exe`); v0.4.1 and v0.5.0 `install.rs:276` (`update.exe`). `src/handoff.rs` `cleanup_stale_files` (called at startup from `src/runtime.rs`) deletes all three patterns |
Versions before v0.4.0 use the same `ASSET_NAME` constant (checked at
v0.1.0, v0.1.9, v0.1.10, v0.2.0, v0.3.0, v0.3.12 and v0.3.13) and the
@@ -114,7 +114,8 @@ host has no patchelf and no display.
## Files
- Create: `.github/workflows/release-legacy.yml`, `scripts/make-latest-json.js`, `tests/js/make-latest-json.test.js`
- Modify: `.github/workflows/release.yml`, this phase file (the filled-in table and the residual risk)
- Modify: `.github/workflows/release.yml`, `docs/release-process.md` (assets, name contract, residual risk), this phase file (the filled-in table and the residual risk)
- Added during implementation: `.yamllint.yaml` (see notes)
## Steps
@@ -128,6 +129,40 @@ host has no patchelf and no display.
4. Re-run the `git show` reads, fill in the Result column of the compatibility
table against the dry-run asset list, and write the residual-risk note.
## Todo
- [x] `scripts/make-latest-json.js` with `tests/js/make-latest-json.test.js` (two-platform shape, signature content, URLs equal to the renamed names, `--windows-only`, a name with a space, a second `.exe` in any case, a missing `.sig`, an AppImage of another version, an empty signature, bad arguments)
- [x] `release.yml` rewritten: `prepare` (tag form, tag equals `Cargo.toml`, non-empty `pubkey`) gates `win-x64` and `linux-x64`; both refuse an empty `TAURI_SIGNING_PRIVATE_KEY`; `publish` runs the script and `gh release create` (draft on dispatch, `--latest` on tags, then checks `/releases/latest`)
- [x] `release-legacy.yml` added (`ref`, `version`, `tag`, `draft` default `true`)
- [x] `actionlint` and `yamllint` clean on all three workflows (with the new `.yamllint.yaml`)
- [x] Compatibility table re-read from `git show <tag>:src/update/{release,install}.rs` and `src/main.rs` at v0.4.0, v0.4.1 and v0.5.0; every row passes against the code
- [x] Residual risk recorded here and in `docs/release-process.md`
- [x] Host: `cargo fmt --check`, `cargo clippy --all-targets --locked -- -D warnings`, `cargo test --locked` (216 passed, 1 ignored), `npm run lint`, `npm test` (52 passed), `npm run tauri build -- --no-bundle -- --locked`; CI green on `windows-latest`, `ubuntu-22.04` and `macos-latest` for the workflow commits
- [ ] Step 3, needs the user: the `release.yml` dispatch dry run and the `release-legacy.yml` draft run, then the draft asset-list check of every table row. Blocked until the signing key exists (Phase 09 step 1), and it creates draft releases, which this run was not allowed to do
## Implementation notes
- **Dry runs not run.** Both workflows refuse to build without the signing secret, and `release.yml` also without `plugins.updater.pubkey`, which is still empty (Phase 09 step 1 is a user action). Creating draft releases was also outside what this run may touch. The table therefore says "pass (code)"; the "Draft check" items stay open until the user runs step 3.
- **Signing flow checked locally.** With a throwaway key (deleted afterwards) and the matching `pubkey` passed through `--config`, `npm run tauri build -- --bundles deb --config '{"bundle":{"createUpdaterArtifacts":true},…}' -- --locked` produced `Claude Code Usage Bubble_<ver>_<arch>.deb` plus a `.deb.sig`, and `tauri signer sign --app-version` wrote `<binary>.sig` beside an untouched binary. The bundler also signs the deb, so the rename step copies only the AppImage, its `.sig` and the deb; the deb signature is not published (no updater entry uses it). Bundle names carry spaces, as the phase expected.
- **Runner args.** `npm run tauri build -- <opts> -- --locked` hands `--locked` to cargo (checked with an invalid flag, which cargo rejected), so release builds stay locked like the old `cargo build --release --locked`.
- **`--app-version`.** Both signing steps bind the version into the signature's trusted comment, as `tauri build` does for the AppImage, so `requireSignedVersion` can be turned on later without re-signing anything.
- **Legacy version patch.** `release-legacy.yml` patches the first `version =` line of `Cargo.toml`, then runs `cargo update --workspace`, which rewrites only the package's own entry in `Cargo.lock`. Checked on this host against an archive of v0.5.0: without it `--locked` fails, with it the lock diff is the single version line. The release is created with `--target` set to the built commit, so the tag lands on the legacy sources, not the default branch.
- **`make-latest-json.js` scope.** Besides the listed guards it requires the exact asset set (any unexpected file fails), so a stray upload cannot slip into a release. It writes `<dir>/latest.json` and ignores an existing one, so a re-run is safe.
- **yamllint.** The repo had no yamllint config, and the default rules reject every GitHub workflow (`on:` as a truthy key, no `---`, 80-column lines), `ci.yml` included. `.yamllint.yaml` relaxes exactly those three rules (line length 140), so the check is reproducible.
- **Draft dry-run tag.** The tag check also runs for dispatch, so `v0.0.0-test` must point at a commit whose `Cargo.toml` says `0.0.0-test`; `docs/release-process.md` says so.
## Residual risk
The legacy-to-Tauri swap is never run end to end before release. Static
reading cannot prove runtime behaviour such as antivirus or SmartScreen
reactions to a self-replaced, larger, unsigned exe, WebView2 start-up from a
detached relaunch, or the v0.4.0 helper timing. The first Tauri release is the
first real run. Mitigations: the WebView2 guard (Phase 04), `--diagnose`
logging, `update-error.log`, and the `release-legacy.yml` rollback, which is
itself unproven end to end. The draft dry runs (step 3) are also still
outstanding, so the asset list has been checked against the workflow and the
script, not against a real draft.
## Validation
- `actionlint` and `yamllint` are clean on all workflows.