Day 84: Rebuilding the Snapshot Chain
Tuesday. The day after a release is usually quiet, and today mostly is. The exception is a structural fix in the migrations directory that took the morning, plus a small chat-state fix that closes the last review thread on yesterday's attachment work. The migrations fix is the kind of thing that nobody notices until a deployment goes sideways on a fresh database, and that exact failure mode just narrowly didn't ship in v0.5.3.
The Snapshot Chain
Drizzle's migration model carries a snapshot per migration. The snapshot is a JSON description of the cumulative schema state immediately after the migration runs, and each migration's snapshot has a prevId field pointing at the previous migration's snapshot. The chain matters for two things: drizzle-kit generate uses it to compute what diff a new migration should apply, and the migrator at startup uses it as a sanity check — if the chain is broken, the migrator refuses to run rather than guessing.
The chain broke during last week's rebase. Migrations 0025 through 0030 were authored on three different feature branches that landed in different orders than they were written. After the rebase, the SQL files were sitting in the correct linear order, but the snapshot files still pointed at their original predecessors — 0027's snapshot pointed at 0024 (where it had been written), not at 0026 (where it now lives). On the team's databases this didn't matter because every database was already past migration 0030 and the chain was only consulted for future migrations. On a fresh database, the migrator would have refused to start. The first new customer on v0.5.3 would have hit the wall.
The fix walks the migrations in their on-disk order and regenerates the snapshot chain from scratch. Migration 0025's snapshot keeps its prevId from 0024 (which didn't move); 0026 onwards each get a fresh prevId matching the predecessor in the linear order; the snapshot file contents (the actual schema description) stay byte-for-byte identical, because the schema state is the same — only the chain metadata moved. A dry-run of drizzle-kit generate against a fresh database confirmed the migrator now walks the full chain cleanly.
The Guard Test
The rebuild is correct. The harder property is that the chain doesn't break again next time three feature branches rebase against each other. The new contract test asserts the chain integrity directly: it loads every migration in order, asserts that each prevId matches the previous migration's ID, and fails red on any gap or fork. Same suite asserts no prefix collisions — the four-digit migration prefix (0025_...) must be unique across the directory, because two migrations with the same prefix is the failure mode that lands a half-applied schema. The test runs as part of CI and is fast (parses a few dozen JSON files); the cost of having it is invisible, and the cost of not having it would have been the one a customer would have found.
A Runbook for Next Time
The matching docs commit lands a runbook in docs/contributing for migration ordering and snapshot recovery. The runbook spells out the two scenarios — rebasing a feature branch that contains migrations, and recovering a broken chain after the fact — with the exact drizzle-kit commands for each. The first scenario is the prevention: before merging, regenerate the snapshot for any migration whose prevId changed. The second is the recovery: walk the chain in linear order and rebuild. The runbook is the answer to why did the chain break being also the answer to here's how to make sure it doesn't break next time.
The Payload Rejection
The other thread today is the loose end from yesterday's PR #316. When an attachment gets rejected at the chat side — too large, wrong MIME type, BiDi-character filename, anything that the upload-validation layer refuses — the previous behaviour was to drop the rejection on the floor and never surface it in the chat. The composer would clear (because the local send completed), the user's message would land without the attachment, and the agent would respond to a message that referenced a file the agent couldn't see. Confusing to the user, more confusing to the agent.
The fix surfaces the rejection as an explicit chat status: Pinchy refused to upload [filename] — [reason], rendered as a system-style notice inline with the message stream rather than as a toast. The notice carries the same shape as the structured model-unavailable bubble from Day 81: file name, the specific reason from the validation layer, a clear next-step suggestion. A small follow-up commit landed during the review pass: the previous draft put the notice before the user's message in the stream, which made it look like an error from the agent; the fix puts it after the user's message so the chronology reads right.
Two Smaller Fixes
The Zod dep got re-aligned after yesterday's rebase. The dep was pinned in one of the workspace packages but unpinned in another, and the unpin had picked up a minor version bump that the rest of the codebase wasn't ready for. The fix pins both to the same version explicitly; the contributing docs already specify the convention (workspace deps are version-pinned across packages), so this is bringing the implementation back in line rather than changing the rule.
The security-scan deps got their weekly refresh. Three transitive bumps for known CVEs, no application-level code change. The kind of housekeeping that's invisible if you keep up with it and a four-hour audit if you don't.
Day 84
The post-release day is usually one of two things: a hotfix that ships immediately, or a quieter day spent on the kind of work the release pace had been putting off. Today is the second kind. The snapshot chain isn't broken anywhere any customer can see; the runbook documents a failure mode the team learned the existence of through luck rather than design. Tomorrow is the start of a week with no release on the calendar — the first such week in a while — and that's the space the next set of changes needs to settle into. The shape of what comes next is starting to come into focus: more visible UI surface for what the per-agent runtimes already make possible, and the conversation-export feature that's been requested by three customers in a row.