The problem: alive is not the same as useful
A hosting platform checks your server regularly. If the check fails, it restarts the process or stops sending it traffic. Most teams point that check at one route that returns “OK” if the process answers at all. For Parmana that is not enough. Every approved action needs a database: the request is recorded, a signed Execution Intent is stored before release, the authorization’s nonce is consumed so it can be used only once, and the caller audit trail is written. A Parmana process whose database is unreachable is up, but every real request will fail. If your health check says “OK”, the platform keeps sending it traffic, and an agent asking to refund ₹9,500 gets an error instead of a decision. The opposite mistake is just as bad. If the liveness check also tests the database, a short database blip makes the platform restart every instance at once. Restarting does not fix a database. It only adds a cold start to the outage.The idea: two routes, two questions
Parmana has two routes, deliberately different, both open without a caller key so a platform can call them:GET /healthasks: is this process running? It always answers200withstatusUPand checks nothing else. If the process can answer HTTP, it answers this. Use it for restart decisions.GET /readyasks: can this process serve real traffic now? When storage is Postgres backed, it runsSELECT 1against the database. When Execution Intents are enforced, which in production they always are, it also checks that theexecution_intentstable exists. If either fails, it answers503withstatusNOT_READYand areason. Use it for traffic decisions.
503 EXECUTION_INTENT_UNAVAILABLE. The readiness check catches it first and the reason tells you which migration to apply.
One more field: is authentication off?
The readiness response also carries anauthDisabled field, with a warning when it is true. Setting PARMANA_AUTH_DISABLED=true is meant only for local development, and it is exactly the kind of line someone copies from an example file without reading. A warning in a startup log is easy to miss. A field on a route your monitoring already checks every 30 seconds is easy to alert on. The production runbook says to alert when /ready is not 200 or when authDisabled is true.
The bug the readiness check found in itself
The test for/ready runs three cases: test mode, memory storage, and a database URL pointed at an address that cannot be reached. On 2026-09-16 that third case kept reporting READY. Two real bugs were behind it. The Postgres pool had no connection timeout, so a connection to a dead address could hang instead of failing. And a change made that same day opened the shared database pool when a module was first loaded, so the pool was already connected to the working database before the test switched the URL. The test looked correct and tested nothing.
Both were fixed: a five second connection timeout, and building that repository lazily, only on first use. Now the third case reports NOT_READY with the real connection error. I keep this story because it is a good reminder that a readiness check needs its own test against a database that is really down.
What it does not do
- It checks the database, not everything.
/readydoes not test that the signing key works, that a connector is reachable, or that your policies are approved. Signing is checked separately before each release, and an unreachable connector shows up as502 EXECUTION_OUTCOME_UNKNOWN. - Memory storage is always ready. With
PARMANA_STORAGE=memory, or in test mode, there is nothing to probe, so/readysaysREADYwithout any check. - It is a snapshot. A database that drops a second after the check passes still fails the next request. Readiness lowers the chance of sending traffic to a broken instance; it cannot remove it.
- Where it drives traffic depends on the host. On Fly.io or any Docker platform, the runbook points the platform’s own health check at
/ready. My production runs as a Vercel function, and there the runbook uses/readyas a check after each deploy and as a target for your own monitoring, not as a switch the platform routes on.