"Could not save that change — ws-layout-platform was not written — Internal
Server Error", on a dashboard that had in fact been created.
`upsertDashboard` was update-then-insert, which is a read-then-write race, and
creating a dashboard walks straight into it: the client sends `workspaces` (the
list) and `ws-layout-<id>` (the layout) as two SEPARATE PATCHes, and both call
this. Both found no row, both INSERTed, and the loser hit the primary key. The
surviving row and the failing insert were stamped one millisecond apart in the
log, which is what gave it away — this was never a name collision.
One statement now, `ON CONFLICT DO UPDATE`. `set` carries only the fields the
caller passed, so whichever request lands second writes its own column and leaves
the other's alone. The two orderings become equivalent instead of one of them
fatal.
Proved against the real table rather than reasoned about:
two plain inserts, concurrent fulfilled, rejected <- the reported 500
two upserts, concurrent fulfilled, fulfilled
The `where` on the DO UPDATE is not decoration. `dashboards.id` is a GLOBAL
primary key fed by a slug, so two accounts naming a dashboard the same thing
collide, and an unguarded upsert would let the second silently take over the
first's row. Verified on a scratch table that a non-owner's upsert is DROPPED
rather than applied or thrown — the row is left untouched. This machine has one
account, so that path could not be exercised end to end; the SQL semantics were.
That is still the wrong data model — the real fix is a composite key — but it now
fails by refusing rather than by handing someone another account's dashboard.
tsgo clean, 840 pass / 7 fail unchanged.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>