Use the Google Books API key: the fallback source actually answers now
The user obtained a restricted Google Books key. Keyless requests 429 for every caller on the internet — all anonymous traffic bills to one shared Google Cloud project whose daily quota is permanently exhausted — so the documented fallback has never once answered. Because MetadataRepository.combine turns "a source failed, none found" into Unavailable, that standing failure meant every Open Library hiccup reached the user as "couldn't be reached". The app has been effectively single-sourced since it was written. Build plumbing reads GOOGLE_BOOKS_API_KEY from local.properties (gitignored), falling back to the environment and then to empty. A blank key is a supported state: a fresh clone still builds a working app that falls back to the keyless endpoint, rather than failing to build. GoogleBooksClient appends the key only when non-blank, building the URL with HttpUrl.Builder in a pure requestUrl() so it is testable without a socket. The key is scrubbed from SourceResult.Failed.reason before that string can reach the scan sheet — it is rendered to the user and is our only diagnostic channel from a real phone, and some okhttp/JDK IOExceptions embed the full request URL in their message. Defensive, not a response to an observed leak. Resolves the RATE_LIMITED decision parked in RetryPolicy's KDoc: a keyed 429 is the short per-user rate limit and gets exactly one retry, honouring Retry-After capped at 2s. A keyless 429 is still the dead daily quota and is still never retried. Verified against the live API, not only offline: both ISBNs that failed on the phone (9781883937386, 9781883937676) plus a control return HTTP 200, in the percent-encoded URL shape HttpUrl actually produces. Both books are in Google Books, so the restored fallback now covers precisely the Open Library TLS-reset failure that broke those scans. 189 unit tests (was 172), 0 failures; Paparazzi unchanged; release APK builds. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01J7WHnTx2Cso4VV245WDAJY
This commit is contained in:
1 parent
d6d02f788c
commit
486f6ebc48
11 files changed
+662
-37
No files matched your search
@@ -498,3 +498,66 @@ is our ONLY diagnostic channel from a real phone.** Nothing may parse it.
|
||||
- H2 (108 turns, $4.00) followed the brief closely, ran builds in the foreground,
|
||||
and reported honestly, including flagging its own stacked-sheet judgement call.
|
||||
Cost ratio to H1 ($0.66, 5 turns) is roughly the ratio of work actually done.
|
||||
|
||||
## Wave 7 — I-gbkey (Google Books API key): COMPLETE, verified by the orchestrator 2026-09-11
|
||||
Prompt: `tasks/I-gbkey.txt`. The user asked for the key on 09-11, which lifts the
|
||||
standing "do not add it unasked" instruction recorded in wave 6.
|
||||
|
||||
**Split of work, deliberately:** the ORCHESTRATOR did the build plumbing
|
||||
(`app/app/build.gradle.kts`: read `GOOGLE_BOOKS_API_KEY` from `local.properties`,
|
||||
enable `buildConfig`, emit `BuildConfig.GOOGLE_BOOKS_API_KEY`) and verified it
|
||||
green BEFORE launching the worker, because workers on this project are barred from
|
||||
build files. The Sonnet worker did the Kotlin against a tree where the key was
|
||||
already available. Reuse this pattern for anything needing a build-file change.
|
||||
|
||||
| Check | Result |
|
||||
|---|---|
|
||||
| `./tasks/gw assembleDebug` | exit 0 |
|
||||
| `./tasks/gw testDebugUnitTest --rerun-tasks` | exit 0 — **189 tests**, 1 skipped, 0 failures (was 172) |
|
||||
| test count source | summed from `TEST-*.xml`, not the console |
|
||||
| `./tasks/gw verifyPaparazziDebug` | exit 0 — no pixels moved, as intended |
|
||||
| `./tasks/gw assembleRelease` | exit 0 — 41,810,740 bytes |
|
||||
| `grep "always 'false'"` on a `--rerun-tasks` build | **0 hits** |
|
||||
| boundary check | clean — worker touched only `data/metadata` + the one AppContainer line |
|
||||
| key-leak check | `git grep` finds no real key in the tree; tests use `test-key-123` |
|
||||
| **live API check** | HTTP 200 for both previously-failing ISBNs and a control |
|
||||
|
||||
The worker's report was honest: every claim re-verified, including the test count,
|
||||
and it flagged its own judgement calls (how it reconciled the slightly ambiguous
|
||||
`Retry-After` cap wording) rather than papering over them. 51 turns, $1.59.
|
||||
|
||||
**The live check was not ceremony.** `HttpUrl.Builder` percent-encodes the colon,
|
||||
so the app sends `q=isbn%3A...` where every earlier hand-run test sent `q=isbn:...`.
|
||||
Offline tests cannot distinguish those. Verified: the API accepts both.
|
||||
|
||||
**Both books that failed on the phone are in Google Books** — so the restored
|
||||
fallback now covers exactly the Open Library TLS-reset failure mode that actually
|
||||
broke those two scans. The app has been effectively single-sourced since it was
|
||||
written and is now genuinely two-sourced. Details in `docs/METADATA-SOURCES.md`
|
||||
§ "The key landed".
|
||||
|
||||
### HAZARD #9 — `run-task.sh` reads the WORKER'S OWN OUTPUT for quota strings
|
||||
`run-task.sh`'s quota detector greps the worker's result blob for
|
||||
`usage limit|...|429|too many requests|...`. That blob includes `.result` — the
|
||||
worker's own prose. **This wave's task was ABOUT HTTP 429**, so the moment the
|
||||
worker finished and wrote a report mentioning 429, the runner declared
|
||||
`QUOTA hit (wait #1)`, slept 600s, and was about to `--resume` a session that had
|
||||
already SUCCEEDED — which would have burned quota redoing finished work and let a
|
||||
fresh worker turn loose on a completed tree.
|
||||
|
||||
Caught it by checking the log rather than trusting the state line:
|
||||
`jq '{is_error, subtype, num_turns}' logs/I-gbkey.json` said
|
||||
`is_error:false, subtype:"success", num_turns:51`. The orchestrator killed the
|
||||
runner (PID from `ps -o pid,args -p <pid>`, per HAZARD #6 — not `pkill -f`) before
|
||||
the sleep elapsed, and appended a note to `logs/<name>.state` saying why.
|
||||
|
||||
**Before believing any `QUOTA hit` line, check whether the worker actually
|
||||
finished:** a non-empty `logs/<name>.json` with `is_error:false` means it
|
||||
SUCCEEDED and the runner is about to waste a session.
|
||||
|
||||
The real fix, for whoever next touches the runner (write a NEW file; never edit
|
||||
`run-task.sh` while workers are running): the detector must read only the
|
||||
transport-level error stream, not `.result` prose — e.g. grep `$ERR` alone, or
|
||||
`jq -r 'select(.is_error==true) | .result'`, rather than `cat "$LOG" "$ERR"`.
|
||||
Leaving it as-is means any future wave whose subject matter mentions rate limits
|
||||
or 429 will loop this way.
|
||||
Reference in new issue
Block a user