setsid nohup did not survive orchestrator teardown on 09-06 (uptime was
continuous, so this was a teardown kill, not the hazard-#5 suspend).
tasks/service-worker.sh runs a worker under the sprite service supervisor
instead, which both outlives the orchestrator and holds the box awake, making
the task-lease guard redundant. It is sentinel-guarded so a supervisor restart
does not re-run a finished wave, and it stops its own service afterwards so the
sprite can suspend.
logs/WAVE4-DONE records that F3's files were all complete but the worker was
stuck re-running verification it could not finish, so the orchestrator ran the
verification itself and accepted the work. F3's own written report — including
the design critique of the rendered screens it was asked for — was never
produced; that gap is recorded in the sentinel.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014TyzeWmdTqi7U85iYNGy7P
Completes wave 4. Verified by the orchestrator, not self-reported:
assembleDebug / testDebugUnitTest / assembleRelease all exit 0;
102 tests, 1 skipped, 0 failures, 0 errors.
- Settings showed the PocketBase user id instead of the signed-in email,
because login never persisted the email. AuthRepository now writes it to
SettingsStore on success and sign-out clears it; SettingsUiState carries
userEmail in place of userId. AuthRepositoryTest asserts both directions.
- Paparazzi coverage for the five screens library was missing: setup, detail,
scan, locations, settings, each light + dark, populated rather than empty.
Scan cannot show a live camera under Paparazzi, so its tests render the
reticle overlay and the result bottom sheet over a static backdrop.
- Release signing via an optional gitignored app/keystore.properties. Without
it assembleRelease still works and comes out debug-signed, so the build is
not owner-only. R8 deliberately left off; nothing has proven Room, Retrofit,
kotlinx-serialization and ML Kit survive it.
- Top-level README: shared-library model, offline-first architecture, the
push-then-pull last-write-wins conflict rule SPEC requires be documented
here, build/deploy/install steps, and honest current limitations.
The signed APK and the keystore are intentionally not committed.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014TyzeWmdTqi7U85iYNGy7P
wave-guard.sh post-mortem: lease_hold() did DELETE-then-POST with both results
discarded and logged "lease renewed" unconditionally, so a failed re-POST left
the box with no lease while the log claimed it was protected. That matches the
wave-4 loss exactly (last "renewal" 11:39, workers dead 11:46, reboot 12:55).
Now: every acquire is verified against GET /v1/tasks before it is believed, a
failed acquire retries and is logged as a failure, and the lease is re-checked
every POLL rather than only every RENEW so a lease lost between renewals is
caught in seconds.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016mTs3kQXQsQwonXpEq7aEw
Both wave-4 workers hit the 5h session limit ~11 minutes in and were then lost
to a sprite suspend. This commit preserves the work that landed before that,
independently verified green (assembleDebug + testDebugUnitTest, 94 tests).
F1-livesync:
- LiveSyncTest + server/live-sync-test.sh: end-to-end exercise against a real
PocketBase (auth, push with client ids, pull, tombstones, cover round-trip).
Gated behind LIVE_SYNC=1 so the normal test task stays green with no server.
- Fix: BookDto.authors must be nullable. PocketBase serializes an unset `json`
field as literal null (unlike text/number, which come back ""/0), so decoding
any real response with empty authors threw. Found by the live test; no fake
had ever reproduced it.
- Fix: cover upload derived its media type from the filename instead of
hardcoding image/jpeg.
F2-release:
- ScreenFixtures + LibraryScreenPaparazziTest: library populated and empty,
light and dark (4 PNGs).
Still owed by wave 4: screenshots for the other five screens, release keystore
+ signed APK, top-level README.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016mTs3kQXQsQwonXpEq7aEw
Guard now polls every 30s but renews every 15 min. Coupling them meant a wave that
finished just after a renewal sat undetected for a full interval with the sprite
pinned hot.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Nav graph, setup with distinct URL-vs-credential errors, locations tree with reorder
and bulk move, settings; library grid with search/sort/filter, detail with soft-delete
undo, continuous scan with duplicate-ISBN warning.
Verified by orchestrator: assembleDebug exit 0; testDebugUnitTest exit 0,
91 tests, 0 failures, 0 errors (68 -> 91).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The wave-3 loss was caused by sprite auto-suspend, not nohup process-group
semantics. /.sprite/llm.txt: 'When idle, sprites pause automatically. Services and
sessions keep sprites alive.' Detached processes are on neither list, so setsid is
necessary but not sufficient.
tasks/wave-guard.sh holds a /v1/tasks lease (max 3600s, renewal is DELETE+POST since
re-POST returns 409), renews every 15 min while workers run, writes logs/WAVE<N>-DONE,
then releases the lease so the sprite can suspend rather than idle hot.
Also documents hazard #6 (pgrep -f / pkill -f matching the orchestrator's own shell)
and adds the wave-3 worker prompts and shared screen contract.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>