Enforced by What?
Coop has exactly one writer. Say that to anyone who has run a database and they ask the right question at once: enforced by what? A writer that is the only one because everybody agreed not to write is a convention, and Track 1 already showed you what a multi-writer convention does to a ledger. So the engine enforces it twice, with two mechanisms that guard two different doors.
The first guard keeps out a second engine. When Engine.__init__ runs, it opens a sidecar file named <store>.engine.lock, sets it to mode 0600, and asks the kernel for an exclusive, non-blocking flock. If another process already holds it, the call fails at once instead of waiting, and the constructor raises Conflict('Another Coop writer owns this store'). Two engines on one store is not a race that gets resolved later. It is a start-up that refuses.
That lock has one weakness, and it is the one that matters: flock is advisory. It stops processes that ask for the lock. A plain sqlite3.connect() on the same file never asks. Every old helper, every forgotten script, every session still holding a connection from yesterday walks straight past it. The second guard exists for exactly those.
Authority That Lives in a Function
Every connection the engine opens registers an application-defined SQL function, coop_write_permit(), which returns 1 while the engine is running and 0 once it has closed. Then, for each of the nineteen tracked tables (the seven legacy ones, the reply-review receipts and eleven engine tables) and for each of INSERT, UPDATE and DELETE, the engine installs a BEFORE trigger whose whole body is one line: if coop_write_permit() is not 1, RAISE(ABORT,'Coop writer fenced').
Here is the trick. A trigger lives in the database file, so every connection that writes the table fires it. The function does not live in the file. A function registered with create_function exists only on the connection that registered it. So the trigger is shared and the permission is private: the authority lives in a function only the owner defines.
Watch what that does to an outsider. Some other process opens a raw connection and tries an INSERT. SQLite prepares the statement, pulls in the BEFORE trigger, meets a call to a function this connection has never heard of, and refuses with no such function: coop_write_permit. The write never begins. When the connection was opened does not matter either: SQLite notices the schema changed and re-prepares, so a connection that has been open since before the fences existed fails on its next write. Coop's own test keeps exactly such a connection open, starts the engine, and asserts that the insert raises an OperationalError naming coop_write_permit and that the table is still empty. Reads, meanwhile, go through untouched. The fence is on writing, not on looking.
Where "Coop writer fenced" Actually Fires
Because that message sits right there in the trigger, it is tempting to assume an outsider sees it. It does not. The literal only fires on a connection that has the function and gets 0 back: an engine connection still in use after Engine.close() flipped the closed flag. New connections are refused before that by the engine itself ('Coop writer is stopped'), so the message is the last guard for a writer shutting down mid-request.
| Who writes | What happens | Why |
|---|---|---|
| A second engine | Another Coop writer owns this store | The exclusive flock is taken |
| Any outside connection | no such function: coop_write_permit | The trigger calls a function it lacks |
| The owner's own connection, after close | Coop writer fenced | The function exists and returns 0 |
| The owner, running | The write lands | The function exists and returns 1 |
What the Fence Is Not
The design note says it plainly: this protects ordinary stale clients, not hostile same-UID code that edits schema. A process running as the same user can drop the triggers, or register its own coop_write_permit that returns 1. On the real store that one forgery is not quite enough: the change-tracking triggers (lesson 4) call a second owner-only function, coop_context, so a write with the permit forged and the context missing still fails with no such function: coop_context. Forge both and the write lands. Even a schema change goes through, because DDL fires no row triggers; Coop's own test suite adds a column to todos through a plain connection. So this is not a security boundary. It is a seatbelt for what actually happens in a family of agents: an honest process with an outdated idea of who owns the store.