docs: wave 11 accepted; SPEC records auth refresh + why the 404 rule needs it
HANDOFF: expired-token incident, local data loss, verification incl. live sync against the public URL, phone recovery path, HAZARD #12 (slow storage). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
009cb9a7cc
commit
4695731f91
4 files changed
+77
-1
No files matched your search
@@ -835,3 +835,64 @@ time with the monotonic clock (`/proc/uptime`), which does not advance while fro
|
||||
Also seen 2026-09-16: Claude Code's background-task runner killed Gradle runs "because the system
|
||||
is running low on memory" even with ~6GB available (8GB box, no swap; Kotlin daemon ~2.1GB,
|
||||
Gradle daemon ~1.2GB). Foreground runs of the same command succeeded.
|
||||
|
||||
## Wave 11 — M-authexpiry: sync broken by an expired token — ACCEPTED 2026-09-17
|
||||
**User report:** every sync on the phone said "Sync failed: HTTP 400". Server and public
|
||||
URL were both fine. PocketBase's request log (`pb_data/auxiliary.db`, `_logs`) showed
|
||||
every phone request with `auth: ""`: POST books -> 400 "create rule failure", PATCH
|
||||
books -> 404. **Root cause:** user tokens lasted PocketBase's default 5 days, the app
|
||||
logged in once and never refreshed, and PocketBase does NOT reject a bad token; it
|
||||
silently serves the request anonymously. Last good sync was 09-13.
|
||||
|
||||
**The worse half: data loss on the phone.** `SyncEngine.update*` hard-deletes the local
|
||||
row on 404 (SPEC rule). The anonymous PATCHes 404'd on five books that still exist on the
|
||||
server, so they were deleted locally, and the pull cursor was already past them.
|
||||
Server copies intact (kclh1e51t7hmj0z, insggwycbrd6o48, 9tp7rumid3rzsn9, 9plhmyj5s68f58e,
|
||||
mle7o641pn7zzvt). Any unsynced local edits to those five on the phone are lost.
|
||||
**Lesson: when you debug a server's 4xx, check the app's 4xx handling for side effects.**
|
||||
|
||||
**Fixes:**
|
||||
- `ab1d294` (orchestrator): migration sets user token duration to 180 days (the user chose it).
|
||||
Verified: fresh login token exp = 180.0 days; refresh also returns 180 days.
|
||||
- `009cb9a` (worker + chain fix-worker, $4.89 + $1.34): refresh at the top of every sync;
|
||||
401/403 -> `SyncResult.AuthExpired` (token cleared, URL/email kept, nothing pushed);
|
||||
library sync bar "Signed out — sign in again to sync" (tap -> setup); settings "Sign in"
|
||||
button; setup pre-fills URL + email; login clears pull cursors; one-time full re-pull
|
||||
(`full_repull_2026_09_17_done` flag) to restore the five books; cancellation rethrown in
|
||||
`sync()`. First gate RED: a pre-existing race in `LibraryViewModelTest` (cancelled
|
||||
search job not joined). The fix worker made the test join it. That was a test fix, not a
|
||||
logic change.
|
||||
|
||||
**Orchestrator verification:** read the full data-layer + VM diff; assembleDebug /
|
||||
testDebugUnitTest (355, 2 skipped, 0 fail; was 337) / verifyPaparazziDebug all exit 0; no
|
||||
"always 'false'"; eyeballed the 4 new PNGs (fine, light+dark). **LiveSyncTest run against
|
||||
the PUBLIC URL** (`PB_URL=https://bookshelf-dev-b2jqx.sprites.app server/live-sync-test.sh`):
|
||||
passed, not skipped; server log shows 4 auth-refresh calls, all 200, all authenticated.
|
||||
Anonymous LIST on all four collections still 403.
|
||||
|
||||
**Recovery on the phone:** install the new build. The first sync gets 401 on refresh and
|
||||
shows "Signed out". The user signs in (password only), which clears the cursors, and the
|
||||
next sync re-pulls everything, which brings back the five books. The queued PENDING_CREATE
|
||||
books from the failed syncs push normally. NOT verified on-device yet.
|
||||
|
||||
**Known soft spots (accepted):**
|
||||
- The 401/403 "belt and braces" catch mid-sync would NOT fire for a token that dies
|
||||
mid-sync. PocketBase would 404 anonymously, not 401. The window is one sync right after
|
||||
a successful refresh that issued a 180-day token, so it's negligible in practice. It would
|
||||
matter if tokens are ever revoked server-side (password change, tokenKey reset) during a sync.
|
||||
- The library sync bar is tappable, but nothing visual marks it as tappable. The label
|
||||
says what to do.
|
||||
- `SetupViewModel` pre-fill is async and could overwrite text the user types in the first
|
||||
few ms. Negligible.
|
||||
- One unnecessary full pull on fresh installs (the flag starts false). Harmless.
|
||||
- `SettingsStore.resetFullRepullDoneForTesting` is an internal test-only method.
|
||||
|
||||
### HAZARD #12 — sprite storage degrades; restart fixes it
|
||||
2026-09-17 ~01:30Z: `testDebugUnitTest` ran more than 20 min (normally ~75s).
|
||||
`ComponentGalleryPaparazziTest` took 763s and `BookDaoTest` 327s, and git was slow too.
|
||||
Nothing was hung: result XML was still being written, just very slowly. The user restarted
|
||||
the sprite and the same run took 74s. If builds or git crawl, suspect the box, not the
|
||||
code: compare against the gate logs' "BUILD SUCCESSFUL in" times and ask the user to restart.
|
||||
|
||||
`tasks/wave-chain.sh` now reads its commit subject from `tasks/<task>.subject`
|
||||
(it was hard-coded for waves 9/10).
|
||||
Reference in new issue
Block a user