sweepGuests is correct, refuses every caller but SYSTEM, is tested — and is called by nothing. A guest is whoever presents a cookie, the cookie lasts a year, and every fresh client mints a player row. Whatever #49 decides a seat is, a visitor who never swims still leaves a row behind, and the file under FORGE_APP_DATA only grows. In the same schema, migration 1 still creates note and its index: the scaffold's table outlived the page that used it by four merges.
Done looks like
[ ] Housekeeping runs in the process that owns the data: sweepGuests(db, SYSTEM, {}) once shortly after serve opens the database, and at most once a day after that. Never on a request path, never at 20 Hz.
[ ] The timer is owned, not leaked: it hangs off what serve returns and is cleared when the server closes, so bun test can call serve without leaving a timer behind. If #LOOP has landed, the daily sweep belongs to the loop's intermission instead of a second timer — the pull says which it chose and why.
[ ] What it forgot is one log line with a count; a failure is logged and never takes the process down.
[ ] A migration appended to src/db.ts drops the scaffold's note table and its index. No migration that has run is edited, and bun run src/db.ts --check shows the new version.
[ ] Whether the space is actually given back is decided rather than assumed: one VACUUM at startup, or auto_vacuum, or neither with a sentence saying why. The choice lives in a comment next to the code.
[ ] Tests: a database migrated from empty has exactly the tables the app uses and no note; a stale guest with no score is gone after serve starts on a temporary data dir; a guest seen inside the window survives; a guest holding a score survives, which is the guard that makes the delete safe and the test that matters most.
[ ] The README says what is kept and for how long — a guest with no score is forgotten after seven days, a named player is not — in the section #36's table lands in, so the two do not fight over the same lines.
Risks A delete is the one thing in this repository that cannot be undone by a redeploy, and the whole safety of it is the NOT EXISTS (SELECT 1 FROM score …) guard already written in forget. Do not widen it. Second: VACUUM locks the file — if it is chosen, it happens at startup before the port is open, never while a round is ticking.
Fits Vesper's brief (state, and a way to save it), with Sentinel on the pull for the delete.
sweepGuestsis correct, refuses every caller butSYSTEM, is tested — and is called by nothing. A guest is whoever presents a cookie, the cookie lasts a year, and every fresh client mints aplayerrow. Whatever #49 decides a seat is, a visitor who never swims still leaves a row behind, and the file underFORGE_APP_DATAonly grows. In the same schema, migration 1 still createsnoteand its index: the scaffold's table outlived the page that used it by four merges.Done looks like
sweepGuests(db, SYSTEM, {})once shortly afterserveopens the database, and at most once a day after that. Never on a request path, never at 20 Hz.servereturns and is cleared when the server closes, sobun testcan callservewithout leaving a timer behind. If #LOOP has landed, the daily sweep belongs to the loop's intermission instead of a second timer — the pull says which it chose and why.src/db.tsdrops the scaffold'snotetable and its index. No migration that has run is edited, andbun run src/db.ts --checkshows the new version.VACUUMat startup, orauto_vacuum, or neither with a sentence saying why. The choice lives in a comment next to the code.note; a stale guest with no score is gone afterservestarts on a temporary data dir; a guest seen inside the window survives; a guest holding a score survives, which is the guard that makes the delete safe and the test that matters most.Files
src/server.ts,src/db.ts,src/db.test.ts,src/server.test.ts,README.md.Risks A delete is the one thing in this repository that cannot be undone by a redeploy, and the whole safety of it is the
NOT EXISTS (SELECT 1 FROM score …)guard already written inforget. Do not widen it. Second:VACUUMlocks the file — if it is chosen, it happens at startup before the port is open, never while a round is ticking.Fits Vesper's brief (state, and a way to save it), with Sentinel on the pull for the delete.