docs: the server is on the sprite and internet-exposed

The long-standing open question "where will the server actually live" is
answered for now: it stays on this sprite, bound to 0.0.0.0:8090 and published
over HTTPS by the sprite proxy. Several docs asserted the opposite — HANDOFF
said "127.0.0.1:8090, deliberately NOT internet-exposed (no --http-port, so the
sprite proxy can't reach it)", which is flatly wrong today.

The consequence is the part worth writing down: PocketBase's API rules are now
the only thing between this library and the internet. There is no NAT, no VPN,
no reverse proxy. So the anonymous-access curls stop being a formality, and they
have to run against the PUBLIC hostname — localhost cannot tell you what the
world can reach. Re-verified that way: books/shelves/bookcases LIST all 403,
self-registration 403, health 200.

One gap found while re-verifying, recorded but NOT fixed: users LIST answers 200
with an empty array instead of 403. Nothing is disclosed — two real accounts
exist and the listRule filters both out — but it is the same wrong-signal quirk
pb_hooks/main.pb.js exists to close, and that hook never listed the users
collection.

SPEC's offline-first rationale is amended rather than its rule: the reason is no
longer residential NAT but a sprite that suspends when idle and wakes on
request. The rule is unchanged and does not depend on which.

server/deploy/ still documents systemd/Docker/Tailscale on home hardware; it now
says up front that this is the intended end state, not what is running.

Also corrected, since it was adjacent and plainly false: README still claimed the
app had never run on a physical device. It has, since 2026-09-09. What is true is
that no *automated* test runs on a device — there is no emulator on this box.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PPpdG8VnRfS3KkisR3HUAE
This commit is contained in:
Spriteandclaude committed 2026-09-12 17:32:17 +00:00
1 parent 26f76bf0f7
commit 1385ad286f
5 files changed
+149 -26

No files matched your search

+59 -7
View File
@@ -64,11 +64,60 @@ change it, write a NEW file and use that for the next wave.
- Android SDK: `~/toolchain/android-sdk` (platforms;android-37.0, build-tools;37.0.0, platform-tools)
- **No KVM, no emulator.** Verify only via `./gradlew assembleDebug`, JVM unit tests, and
Paparazzi PNG rendering. Never claim the app was "run".
- PocketBase v0.40.2 service, **127.0.0.1:8090, deliberately NOT internet-exposed**
(no `--http-port`, so the sprite proxy can't reach it). Restart:
`sprite-env services restart pocketbase`. Logs: `/.sprite/logs/services/pocketbase.log`.
- PocketBase v0.40.2 service, **bound to `0.0.0.0:8090` and INTERNET-EXPOSED** via the
sprite proxy at **https://bookshelf-dev-b2jqx.sprites.app** (the service is registered
with `--http-port 8090`, so the proxy routes to it). This is the live server the user's
phone talks to. Restart: `sprite-env services restart pocketbase`.
Logs: `/.sprite/logs/services/pocketbase.log`. See "Where the server lives" below.
- Superuser creds: `server/.dev-credentials` (gitignored).
## Where the server lives — ANSWERED 2026-09-12: on this sprite, publicly
This was an open question through waves 1-7 and it now has an answer: **for now the
server stays here on the sprite and is reachable from the open internet.** The user's
phone talks to `https://bookshelf-dev-b2jqx.sprites.app`. Earlier notes in this file
and in `server/deploy/` said the opposite (localhost-only, unreachable from outside);
they were true when written and are now corrected in place. `server/deploy/` still
documents systemd/Docker/Tailscale for an eventual move to home hardware — that is a
future option, not what is running.
Consequences that were not true when the app was designed:
1. **PocketBase's own API rules are now the ONLY thing between this library and the
internet.** There is no NAT, no Tailscale, no reverse proxy in front of it. The
security curls in "Verification standard" below stopped being a formality the day
this changed — run them against the PUBLIC hostname, not `127.0.0.1`, because
localhost cannot tell you what the world can reach.
2. **The sprite auto-suspends when idle** and wakes on an incoming HTTP request
(that is what `--http-port` buys). So the server is still "often unreachable" in
the sense SPEC's offline-first rule cares about — just for a different reason than
residential NAT, and with a cold-start delay on the first request after a pause
rather than a hard failure. The size of that delay has **not** been measured; if a
sync or a first login ever looks pathologically slow, measure it before assuming a
bug in the app.
3. The URL is a sprite-scoped hostname. If this sprite is ever renamed or rebuilt the
URL changes, and every phone has to be re-pointed at it on the setup screen. That
is an argument for moving to Tailscale + real hardware eventually, not a reason to
hardcode anything.
### Posture re-verified against the PUBLIC hostname, 2026-09-12
| Check (anonymous, over the internet) | Result |
|---|---|
| `GET /api/collections/books/records` | **403** `{"message":"Authentication required."}` |
| `GET /api/collections/shelves/records` | **403** |
| `GET /api/collections/bookcases/records` | **403** |
| `POST /api/collections/users/records` (self-registration) | **403** |
| `GET /api/health` | 200 (intended — it is a health check and leaks nothing) |
| `GET /api/collections/users/records` | **200 `{"items":[]}`** — see below |
**Known gap, not a leak:** `users` LIST answers **200 with an empty array** instead of
403. No data escapes — there are two real accounts in the DB and an anonymous caller
sees neither, because `listRule` filters the rows out — but the status code is wrong
and it is the exact PocketBase quirk `pb_hooks/main.pb.js` exists to paper over. That
hook lists only `bookcases`, `shelves`, `books`; `users` was never added to it. One
word of a fix, and worth doing now that the collection is world-reachable: some
clients read "200 with []" as an allowed request. **Not yet done — do not record it as
done until the curl above returns 403.**
## STATE: what is DONE
### Wave 1A — server: COMPLETE and verified by the orchestrator (not just self-reported)
`server/` contains `setup-schema.sh` (idempotent), `create-user.sh`, `pb_hooks/main.pb.js`,
@@ -158,7 +207,8 @@ Workers self-report optimistically. Before accepting any wave:
the user, not something to paper over — the user explicitly cares how this looks.
## Open questions for the user (not yet asked — deferred, not forgotten)
- Where the server will actually live (home box vs a sprite) — only affects the deploy README.
- ~~Where the server will actually live (home box vs a sprite)~~ — **ANSWERED 2026-09-12:
it stays on this sprite, internet-exposed. See "Where the server lives" above.**
- Their two account emails, for `create-user.sh`. Not needed until the app can log in.
## HAZARD #5 — THE SPRITE AUTO-SUSPENDS; detached workers do NOT keep it awake
@@ -283,8 +333,9 @@ library, scaffold — each light + dark).
3. **R8 is off.** Acceptable per F3's prompt, but the release APK is 41.8MB.
Turning it on requires proving Room/Retrofit/kotlinx-serialization/ML Kit
survive minification.
4. Still-open user questions, unchanged: where the server will actually live, and
the two account emails for `create-user.sh`.
4. Still-open user questions: the two account emails for `create-user.sh`.
(Where the server will live was ANSWERED on 2026-09-12 — it stays on this
sprite, internet-exposed. See "Where the server lives" near the top.)
## First on-device test — 2026-09-09
The user installed the signed APK on a real phone. It runs. This closes the
@@ -372,7 +423,8 @@ that.
**Still open, unchanged:** the free Google Books API key (keyless returns 429;
worth doing on its own merits but no longer the leading theory), R8 still off so
the release APK is 41.8MB and too large to send over the file channel (30MB cap),
where the server will live, and the two account emails for `create-user.sh`.
and the two account emails for `create-user.sh`. (Where the server will live
was ANSWERED on 2026-09-12: it stays on this sprite, internet-exposed.)
## Wave 5 — G-diagnostics: COMPLETE, verified by the orchestrator 2026-09-09
Commit `93f972b`. The app now distinguishes barcode-didn't-decode from