E3 — lists that update when Postgres commits
Polling is a lie a UI tells politely: every “live” list that refetches on a timer is either stale or wasteful. E3 replaced it with the database itself.
colophon_notify()triggers on 30 tables emit a small hint ({kind, ids…}) inside the committing transaction viapg_notify— environment-aware, so a hint can never leak which project changed. Hints are ephemeral on purpose: they say “something moved”, never what, so clients refetch through the real permissioned API and the hint carries no authority.- One
PgListenerper server feeds a bounded per-project broadcast;GET {P}/eventsis the SSE endpoint. Caps are refused before subscribe (per credential, per project, global), a lagging subscriber is toldresyncrather than replayed, and the listener task is cancel-wrapped — a shutdown bug where the listener outlived the server was caught by the stack tests and fixed before merge. - The Studio consumes it:
live.tswires anEventSourceper project view, the poll loops are gone, and the wiring exercise caught a real UX bug — aRoleEditorremount on live refetch was silently wiping unsaved edits. A load of “live” infrastructure that loses a user’s typing is worse than polling; the fix keys the component on identity, not data freshness. - Proof, not vibes: a two-server stack test writes on one and watches the hint arrive on the other through Postgres alone; a worker-driven write produces hints for human subscribers; the k6 live scenario holds streams to clean ends while writes maintain their p99 gate.
DX.6–10 — the developer surface, finished
- DX.6 — no leaks through the query language. Filters and sorts that name a field the
role can’t read refuse
422 field_not_readablewith a JSON Pointer — refusing, not silently dropping, since silence is how query languages exfiltrate.own-conditioned fields are handled honestly. - DX.7 — the contract can’t drift.
GET /openapi.jsonis rendered fromopenapi::TABLE— the same tableroutes()loops over — so served methods and documented methods are the same value by construction. A probe test proves every declared verb answers and every undeclared one is 405 withAllow; the committeddocs/api/openapi.jsonis byte-compared. - DX.8 —
@colophon-cms/sdk. Types are generated from the committed document (openapi-typescript, drift-checked); the client is hand-written — the invariants that make it correct (requiredIdempotency-Keyon writes,If-Match, ETag/replayed/requestId surfaces, generation pinning,retryablehonesty) live outside what codegen expresses. 33/33 tests. - DX.9 — import parity with a real dry run.
?dry_run=trueprocesses every item under a savepoint inside a transaction that never commits — so the plan reports real refusals (uniqueness, types, permissions) while writing nothing, not even an idempotency row. Strapi v4{id, attributes}and v5documentIdshapes and Sanity NDJSON map onto the same validated pipeline as every other write. - DX.10 — GraphQL, without a second engine.
POST {P}/collections/{c}/graphqlcompiles eachentries/entryselection into the canonical boundedDocument— soDocument::validate,whosenarrowing,depthpopulation and field-leak refusals are literally the same code REST runs.applied.queryHashis byte-equal to the REST twin’s, proven request-for-request in the stack tests. k6: 10,518 requests, 637/s, p99 163 ms against a 250 ms budget.
Milestone E and the DX program are fully closed. Next: the P5 checkpoint — budgets held in Postgres, resumable bulk jobs, a zero-unauthorized eval gate, request quotas, and the CLI’s machine contract.