From 0088fda09548cf63a54d794602653ba2d9701bb5 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 6 Sep 2026 10:54:03 +0000 Subject: [PATCH] orchestration: wave 4 prompts; decouple guard poll from lease renewal 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 --- tasks/F1-livesync.txt | 81 +++++++++++++++++++++++++++++++++++++++++++ tasks/F2-release.txt | 73 ++++++++++++++++++++++++++++++++++++++ tasks/wave-guard.sh | 14 ++++++-- 3 files changed, 165 insertions(+), 3 deletions(-) create mode 100644 tasks/F1-livesync.txt create mode 100644 tasks/F2-release.txt diff --git a/tasks/F1-livesync.txt b/tasks/F1-livesync.txt new file mode 100644 index 0000000..8c9337a --- /dev/null +++ b/tasks/F1-livesync.txt @@ -0,0 +1,81 @@ +You are Worker F1 on the Bookshelf project (~/bookshelf). Wave 4, part 1: +PROVE THE SYNC + COVER PIPELINE WORKS AGAINST THE REAL SERVER. + +FIRST READ (contract — follow exactly, do not restate): + ~/bookshelf/docs/SPEC.md (see "Sync design", "PocketBase schema") + ~/bookshelf/docs/HANDOFF.md (state + hazards already paid for — read the hazards) + +Waves 1-3 are DONE and verified: server, data layer, metadata/scanning, all six screens. +91 unit tests pass. But EVERYTHING so far was tested against FAKES. Worker C's own +report flagged the biggest hole: + + "Cover pipeline is best-effort, not fully spec-complete. Download-on-create and + multipart upload-on-sync are UNTESTED against a real PocketBase (only via fakes). + Logic is sound but should get a real-server smoke test." + +That is your job. This is the single most likely place a real bug is hiding. + +## The live server +- PocketBase v0.40.2 at http://127.0.0.1:8090 (NOT internet-exposed, by design). + `curl -s http://127.0.0.1:8090/api/health` currently returns 200. +- Superuser creds: `server/.dev-credentials` (gitignored). Schema setup: `server/setup-schema.sh` + (idempotent). Create a normal app user with `server/create-user.sh`. +- If the server is down: `sprite-env services restart pocketbase`; + logs at `/.sprite/logs/services/pocketbase.log`. +- Create a THROWAWAY test user for this (e.g. livetest@example.com). Do NOT invent or + use the owner's real email addresses — they have not been provided. + +## Your scope +Build a real end-to-end exercise, driven from JVM code (no emulator on this box), that +against the LIVE server proves: + 1. auth: login as the test user, token used by PbAuthInterceptor + 2. push: create bookcase -> shelf -> book locally (client-generated 15-char ids) and + PUSH them; verify by reading them back through the REST API that the ids were + preserved and NOT remapped + 3. pull: mutate a record server-side, pull, confirm last-write-wins on `updated` + 4. tombstones: soft-delete locally, push, confirm `deleted=true` propagates and the + record is filtered from local queries + 5. **COVER ROUND-TRIP (the point of this task):** attach a cover image to a book, + multipart-upload it to the `cover` file field, then fetch the stored file back + through `/api/files/...` and assert the bytes match what was uploaded. Use a small + locally-generated PNG — do NOT depend on openlibrary.org being reachable. + 6. cursor handling: the per-collection `updated` cursor advances and a second pull + is a no-op (no duplicate rows). + +## CRITICAL — do not poison the normal test suite +This test hits a real server and MUST NOT run in the default `testDebugUnitTest`. +Machines without PocketBase must stay green. Gate it behind a system property, e.g. +`assumeTrue(System.getProperty("liveServer") != null)`, and provide a runner script +`server/live-sync-test.sh` that sets it. State plainly in your report how it is invoked. + +## Boundaries — Worker F2 is running RIGHT NOW in this repo +- F2 owns `app/app/build.gradle.kts` (it is adding release signing), the root `README.md`, + and all Paparazzi/screenshot code. DO NOT TOUCH THOSE. If you need a build change, + REPORT it, do not make it. +- You own `server/` scripts and the live-test code. Put it in its own package + (e.g. `org.modg.bookshelf.livesync`) so it cannot collide with F2's screenshot tests. +- You MAY fix genuine bugs you find in data.repo / data.remote / data.local — that is + the expected outcome. Every fix must keep the existing 91 tests passing. + +## Build/verify — use the serialized wrapper, never ./gradlew directly + ~/bookshelf/tasks/gw assembleDebug + ~/bookshelf/tasks/gw testDebugUnitTest + +## Definition of done — actually run these, wait for them, report REAL exit codes +1. `~/bookshelf/tasks/gw assembleDebug` exits 0. +2. `~/bookshelf/tasks/gw testDebugUnitTest` exits 0, ZERO failures, and the live test is + SKIPPED in that run (prove it). +3. The live test, run explicitly against 127.0.0.1:8090, PASSES all six points above. +4. Report the actual bugs you found. "Everything worked first try" on an untested + pipeline is a suspicious claim — if that is genuinely the outcome, show the evidence + (the assertions that ran, the bytes compared). + +## Do not repeat an earlier worker's mistake +A worker once ended its turn saying "tests are running in the background, I'll report +when done." That is a FAILURE. Run it, WAIT, report observed exit codes. A truthfully +reported gap is worth more than a false green. + +## Report (short — read by a token-constrained orchestrator) +- real exit codes; how to invoke the live test; PASS/FAIL per numbered point above +- every bug found and how you fixed it +- anything you could NOT verify, and why diff --git a/tasks/F2-release.txt b/tasks/F2-release.txt new file mode 100644 index 0000000..0d1d2e3 --- /dev/null +++ b/tasks/F2-release.txt @@ -0,0 +1,73 @@ +You are Worker F2 on the Bookshelf project (~/bookshelf). Wave 4, part 2: +SHOW THE OWNER WHAT IT LOOKS LIKE, AND SHIP AN INSTALLABLE APK. + +FIRST READ (contract — follow exactly, do not restate): + ~/bookshelf/docs/SPEC.md (see "Design language", "Screens", "Quality bar") + ~/bookshelf/docs/HANDOFF.md (state + hazards already paid for — read the hazards) + +Waves 1-3 are DONE and verified: 91 unit tests pass, all six screens exist, and there +is an existing Paparazzi setup (`ComponentGalleryPaparazziTest`, 10 PNGs of the shared +components) that ALREADY WORKS — `~/bookshelf/tasks/gw recordPaparazziDebug` is green. +Follow its patterns rather than inventing a new approach. + +Context that shapes this task: there is NO EMULATOR on this box (no KVM). Nothing in +this project has ever actually been RUN — only compiled and unit-tested. Your screenshots +are the ONLY way the owner can judge how their library app looks, and the signed APK is +the only way they can run it for real. Both matter more than usual. + +## Your scope +1. **Paparazzi screenshots of all SIX screens** — setup, library, detail, scan, + locations, settings — each in BOTH light and dark. Render them with realistic + populated state (a plausible shelf of books, covers as placeholder art), NOT empty + states, plus the library empty state as its own shot since SPEC calls it out. + The scan screen cannot show a live camera in Paparazzi — render its overlay/reticle + and the result bottom sheet over a static backdrop, and say so in your report. +2. **Release signing + signed APK.** Generate a release keystore, wire a signingConfig + into `app/app/build.gradle.kts`, and produce a signed release APK. Keystore and its + passwords MUST be gitignored — put credentials in `app/keystore.properties` (gitignored) + and read them from the build file, with a graceful fallback so the build still works + for anyone without the keystore. Record the passwords in `app/keystore.properties` and + state them in your report so the owner can rebuild. + `minifyEnabled` is currently false for release — if you enable R8, you must prove the + signed APK still builds AND that Room/Retrofit/kotlinx-serialization/ML Kit survive it + (proguard rules). If you cannot prove it, leave R8 off and say so. +3. **Top-level `README.md`** — what the project is, the two-person shared-library model, + architecture (offline-first Room + PocketBase, push-then-pull LWW sync), how to build, + how to deploy the server (point at `server/deploy/`), how to install the APK, and an + honest "current limitations" section. Reuse the real content in `server/README.md` and + `docs/SPEC.md`; do not contradict them. + +## Boundaries — Worker F1 is running RIGHT NOW in this repo +- F1 owns `server/` and live-server sync testing, in package `org.modg.bookshelf.livesync`. + DO NOT touch `server/` or anything under that package. +- F1 may be fixing bugs in data.repo / data.remote / data.local. DO NOT edit those + packages. If a screenshot needs a data-layer change, REPORT it instead. +- You own `app/app/build.gradle.kts`, the root `README.md`, and all screenshot code. +- Do not modify the six screens' behavior to make them easier to screenshot. If a screen + is hard to render in isolation, that is a finding to report, not a licence to change it. + +## Build/verify — use the serialized wrapper, never ./gradlew directly + ~/bookshelf/tasks/gw assembleDebug + ~/bookshelf/tasks/gw testDebugUnitTest + ~/bookshelf/tasks/gw recordPaparazziDebug + ~/bookshelf/tasks/gw assembleRelease + +## Definition of done — actually run these, wait for them, report REAL exit codes +1. All four commands above exit 0. `testDebugUnitTest` keeps ZERO failures (91 tests + currently pass — do not break them). +2. Screenshot PNGs exist on disk for all six screens x light/dark. List their paths. +3. A signed release APK exists. Give its exact path, size, and the output of + `~/toolchain/android-sdk/build-tools/37.0.0/apksigner verify --print-certs ` + (or an explanation if that tool is unavailable). +4. Do NOT commit the keystore or its passwords to git. Verify with `git status`. + +## Do not repeat an earlier worker's mistake +A worker once ended its turn saying "tests are running in the background, I'll report +when done." That is a FAILURE. Run it, WAIT, report observed exit codes. A truthfully +reported gap is worth more than a false green. + +## Report (short — read by a token-constrained orchestrator) +- real exit codes for all four commands +- the screenshot PNG paths, and the signed APK path + size + signer info +- anything in SPEC's design language you think the screens get WRONG, now that you can + finally see them rendered. Be candid — the owner explicitly cares how this looks. diff --git a/tasks/wave-guard.sh b/tasks/wave-guard.sh index 81079b4..c8d090e 100755 --- a/tasks/wave-guard.sh +++ b/tasks/wave-guard.sh @@ -14,6 +14,7 @@ SENT="$1"; shift TASKS=("$@") L="$HOME/bookshelf/logs"; OUT="$L/$SENT"; LEASE="bookshelf-wave" RENEW="${RENEW:-900}" # renew every 15 min against a 60 min lease +POLL="${POLL:-30}" # but CHECK for completion every 30s (see loop below) GUARD_LOG="$L/wave-guard.log" lease_hold() { @@ -28,11 +29,18 @@ trap 'lease_release; log "guard exiting, lease released"; exit 0' TERM INT log "guard start: sentinel=$SENT tasks=${TASKS[*]} renew=${RENEW}s" lease_hold; log "lease '$LEASE' acquired (3600s)" +last_renew=$(date +%s) +# Poll FREQUENTLY (POLL) but renew SLOWLY (RENEW). Coupling the two, as the first +# version did, meant a wave that finished right after a renewal sat undetected for a +# full RENEW interval with the sprite pinned hot the whole time. while pgrep -f 'run-task\.sh|run-resume\.sh' >/dev/null; do - sleep "$RENEW" - lease_hold - log "lease renewed; workers still running" + sleep "$POLL" + now=$(date +%s) + if [ $(( now - last_renew )) -ge "$RENEW" ]; then + lease_hold; last_renew=$now + log "lease renewed; workers still running" + fi done log "workers stopped; writing $SENT"