What shipping this blog taught us
Three planes, two processes, one database — and the bug that only production could find.
Three planes, two processes, one database — and the bug that only production could find.
Three planes had to agree before this page could exist: something to write posts, something to render them, and something to find them again. Getting each one working alone was straightforward. Getting the seams right was not.
The public site and the authoring API are separate processes. That is deliberate — a public, unauthenticated container should not hold the platform's LLM credentials or its session signing key. But two processes reading one database need a way to agree on when a page went stale.
The answer was a shared generation counter. The writer bumps it on every mutation; every rendered page's cache key embeds it. One write invalidates every page, and neither process has to know the other's key layout.
IN PRACTICE
The first version derived the preview-token key from the platform session key. That would have turned a compromise of a public container into session forgery. It now has its own secret, and the blog process has never seen the session key.
Local tests all passed. The first request to the deployed service still 500'd on search.
The cause was a stale line in a local env file pinning the vector-search project to staging. Every local run had been provisioning and querying the index in the wrong project, and agreeing with itself. Only the deployed service — which reads its configuration from the deploy, not from a developer's machine — disagreed.
A test that shares your machine's configuration is testing your machine.
Provision the search collection from the deploy target rather than from a laptop. The one command that touched a real cloud resource was the one command still reading local overrides.