Play edits under a machine-wide per-package lock (parallel reads of one app no longer delete each other's edits; a read whose edit was deleted is redone once, writes are not); a store that failed to read is 'Couldn't read Google Play' and keeps its last good releases with 'couldn't refresh'; stores on different versions: neutral 'Current · Play x · App Store y' header and each lane its own state, 'Not on this store' only without a record; Play status decided from the public store page's live version (Live / In review / In review (live: x) / managed publishing / Rolling out / Halted / Draft; only a network failure says 'Couldn't check the store page', with the last known answer and its time); tests/play-edit-lock.test.ts, recorded page fixture, boot check auto-read cases (pitfall 58)

This commit is contained in:
CRTkafa 2026-10-14 00:14:32 +08:00
parent e59f439101
commit 714352ec18
24 changed files with 570 additions and 103 deletions

View file

@ -653,9 +653,11 @@ that fails without the fix (see CONTRIBUTING.md).
## 56. "Live on Play" while Google was still reviewing, and 1.0 / 1.0.0 shown as two versions
- **Do:** never call a Google Play production release live from the Developer API alone: a release reads `completed`
(userFraction 1) from the moment it is sent, also while Google reviews the first release. `play status` checks the
public store page (no auth, 4 s timeout, cached 5 minutes): 404 = "Sent to Google (in review or not yet
published)"; 200 = live for the first release (or when the page shows the same versionName), otherwise "Released to
production (Google may still be reviewing)"; offline = unknown, shown as unknown. Match versions across the stores
public store page (no auth, 4 s timeout, cached 5 minutes) and decides, never "maybe": the page's "Version" (About
this app, read from its AF_initDataCallback data) equals the release's versionName = "Live"; 404 = "In review" (with
play.managedPublishing: "In review / ready to publish (managed publishing)"); an older version on the page = "In
review (live: <older>)"; "Varies with device" = live. Only a network failure gives "Couldn't check the store page",
and then the last known page answer is used with its time. Match versions across the stores
by `versionKey` (trailing .0 parts dropped: 1 == 1.0 == 1.0.0); show each store's own string.
- **Guard:** `playPublicPage` (`cli/lib/play-public.ts`), `playReleases` / `versionKey` / `sameVersion`
(`cli/lib/portfolio.ts`), `versionHistory` (`cli/lib/train.ts`), `tests/play-public-version-match.test.ts`, the
@ -671,3 +673,18 @@ that fails without the fix (see CONTRIBUTING.md).
findings; a real failure shows the command's own error line, never a bare exit code.
- **Guard:** `cli/lib/google-errors.ts`, `outcomeOf` / `errorLine` (`cli/lib/desktop-protocol.ts`),
`tests/google-errors.test.ts`, the desktop boot check's `errors` cases (api-disabled, scan-findings, scan-failed).
## 58. Parallel Play reads deleted each other's edits, and a failed read said "not on Google Play yet"
- **Do:** Google Play keeps one open edit per app: a second `edits.insert` for the same package deletes the first, so a
read in it answers 400 "This Edit has been deleted". Every edit (read or write) of a package takes the machine-wide
lock `~/.storebound/locks/play-edit-<package>.lock` (short wait with a heartbeat log, 90 s timeout for reads, 10 min
for writes; stale when its process is gone or untouched for a minute); other packages stay parallel. A read whose
edit was deleted, committed elsewhere or expired is redone once with a new edit; a write is never redone that way
(it keeps its own invalidation retry). A store that could not be read is "couldn't read Google Play" (unknown),
never "not on Google Play yet"; its last good releases stay with "couldn't refresh". When the stores' current
versions differ (normal), each lane shows its own ("4.1.2 · Live (4.1.9 not submitted)") under a neutral "Current"
header; "Not on this store" only when the store has no record of the app.
- **Guard:** `withMachineLock` (`cli/lib/lock.ts`), `read()` / `edit()` (`cli/play/client.ts`),
`laneGap` / `storesDiffer` / `withLastGood` (`cli/lib/train.ts`), `tests/play-edit-lock.test.ts` (a fake Play where a
second insert invalidates the first), the desktop boot check's auto-read case (two Play reads of one app at once,
Orbit Hop on different versions per store).