jevstrudel.git / worker / migrations

For agents, on top of README.md, which they read first.

Notes for agents

Never edit a migration once it has deployed; add the next one. The real database has already run it and will never run it again, so an edit changes only fresh databases (local ones, tests) and they drift from production. Whether one has deployed: git log on it against the live release (curl https://jevstrudel.deizel.workers.dev/release-notes.json).

Numbers are contiguous and unique: 0001_, 0002_, …. src/migrations.test.ts fails on a gap or a repeat, which is how two branches that each took the same number find out when they meet: renumber yours to follow theirs.

Every table is STRICT, and says in CHECKs what cannot be true (the test enforces STRICT). The Worker validates first; the schema is where an invalid row becomes impossible, not merely unwritten.

The deploy migrates before it publishes, so each migration must work with the Worker that is live before it. Add tables and nullable or defaulted columns freely. To rename or drop, expand in one deploy (add the new, write both), contract in a later one (stop reading the old, then drop it).

One table's owner is one module. Say in the migration's header comment which src/ file reads and writes it, and add its row to the table in README.md.

Nothing identifying goes in without a reason written here. The site stores no address, visitor key or request body today (see worker/CLAUDE.md, "Logs carry no visitor"); a table that holds a user or a session says so in its header, with what it is for (0002_accounts.sql does: a random id and a chosen display name, passkey public keys, and session token hashes). 0005_pitch_votes.sql links an account to each pitch it voted for: that is public, who voted and when, since the site shows every vote (the pitches are a community board, and a hidden vote would be a secret the data tab had to withhold). 0006_performance_players.sql links an account to each take it played, and when: public too, so a listener finds their own takes and everyone sees who played what; the take outlives the account, anonymous.

Every row is public, secrets aside: a new table joins the data tab. src/data.ts shows every table to anyone (README.md), so a new table gets a read there and a section in website/src/jev/DataTab.jsx, and a column that is a secret (a credential, a hash that signs someone in, a one-time value) is listed in data.ts's header and added to data.test.ts's never-answered list. A header comment that says a row is seen only by its author predates the data tab: the migration cannot be edited, so README.md says what holds now.

A CHECK that is NULL passes. CHECK (length(x) = 22) lets a NULL x through; say x IS NOT NULL AND … when the column is nullable (0002_accounts.sql's auth_challenges does, and auth.test.ts holds it).