src/nonce.ts exports a sweep that no line of this repository calls, and no code anywhere deletes an event — give the process one place where old rows go, and write the window down where a person will see it.
grep sweep src/*.ts finds the function and its test and nothing else: the day #8 lands, every signed request writes a nonce row that is never removed. Events have no bound at all, so the database under FORGE_APP_DATA only ever grows, and the first person to notice will be whoever finds the preview slow a year from now.
Done looks like
[ ] src/keep.ts — one concept, how long a row stays — exports KEEP_DAYS (90 by default) and sweepEvents(db, now = Date.now(), keepDays = KEEP_DAYS), returning how many rows it deleted, self-throttled to at most once an hour per database in exactly the way src/nonce.ts throttles its own sweep.
[ ] Retention is measured on received_at — when relay took custody — not on occurred_at, which a poster chooses; a comment says why.
[ ] The window is configurable through src/env.ts only (RELAY_KEEP_DAYS), never process.env elsewhere; a missing, zero, negative or unparseable value falls back to 90 rather than deleting everything, and each of those cases has a test.
[ ] src/server.ts gains one sweep point, after the response has been handed back and never before it, that calls sweepEvents and nonce.sweep together — so the next table with a sweep has an obvious place to be called from, and #8 inherits its nonce criterion already met. The terminal process never sweeps.
[ ] A migration appended to migrations in src/db.ts indexes event (received_at), and a test asserts the delete uses it.
[ ] A person is told before they rely on us as an archive: one sentence in README.md, the same window in docs/adopting.md beside the posting instructions, and a few words in the page footer ("events are kept 90 days").
[ ] Tests in src/keep.test.ts with a controlled clock: a row one second past the window goes and one a second inside stays; a second call within the hour deletes nothing; a swept event is gone from listEvents, from getEvent (not_found) and from GET /api/events/:id (404); sweeping an empty table is free and returns 0.
Risks: this deletes data irreversibly, so the default is generous, the sentence is visible, and the tests pin the boundary to the second — a sweep with a wrong clock is the one bug here that cannot be undone. Work done before the response is sent makes the fast path slow; the throttle has to hold even under a burst of requests arriving in the same millisecond. The migration is appended at the end of the list, which is positional — rebase before pushing if another branch appended one first.
Suggested: Torque — an exact window, a hand-checked boundary, and the units written down.
src/nonce.tsexports asweepthat no line of this repository calls, and no code anywhere deletes an event — give the process one place where old rows go, and write the window down where a person will see it.grep sweep src/*.tsfinds the function and its test and nothing else: the day #8 lands, every signed request writes a nonce row that is never removed. Events have no bound at all, so the database underFORGE_APP_DATAonly ever grows, and the first person to notice will be whoever finds the preview slow a year from now.Done looks like
src/keep.ts— one concept, how long a row stays — exportsKEEP_DAYS(90 by default) andsweepEvents(db, now = Date.now(), keepDays = KEEP_DAYS), returning how many rows it deleted, self-throttled to at most once an hour per database in exactly the waysrc/nonce.tsthrottles its own sweep.received_at— when relay took custody — not onoccurred_at, which a poster chooses; a comment says why.src/env.tsonly (RELAY_KEEP_DAYS), neverprocess.envelsewhere; a missing, zero, negative or unparseable value falls back to 90 rather than deleting everything, and each of those cases has a test.src/server.tsgains one sweep point, after the response has been handed back and never before it, that callssweepEventsandnonce.sweeptogether — so the next table with a sweep has an obvious place to be called from, and #8 inherits its nonce criterion already met. The terminal process never sweeps.migrationsinsrc/db.tsindexesevent (received_at), and a test asserts the delete uses it.README.md, the same window indocs/adopting.mdbeside the posting instructions, and a few words in the page footer ("events are kept 90 days").src/keep.test.tswith a controlled clock: a row one second past the window goes and one a second inside stays; a second call within the hour deletes nothing; a swept event is gone fromlistEvents, fromgetEvent(not_found) and fromGET /api/events/:id(404); sweeping an empty table is free and returns 0.Files:
src/keep.ts,src/keep.test.ts,src/env.ts,src/server.ts,src/db.ts,src/page.ts,README.md,docs/adopting.md,src/server.test.tsRisks: this deletes data irreversibly, so the default is generous, the sentence is visible, and the tests pin the boundary to the second — a sweep with a wrong clock is the one bug here that cannot be undone. Work done before the response is sent makes the fast path slow; the throttle has to hold even under a burst of requests arriving in the same millisecond. The migration is appended at the end of the list, which is positional — rebase before pushing if another branch appended one first.
Suggested: Torque — an exact window, a hand-checked boundary, and the units written down.