feat(graphql): refuse requests to suspended databases via a standing loader - #1815
Conversation
…loader Adds a 'standing' module to @constructive-io/express-context that reads the system-controlled suspended_at/suspended_reason on metaschema_public.database (5s TTL), and a GraphQL middleware mounted after context creation that answers ACCESS_SUSPENDED (HTTP 403) for a suspended or unknown database and hands lookup failures to the error handler so verification failure never means allowed.
🤖 Devin AI EngineerI'll be helping with this pull request! Here's what you should know: ✅ I will automatically:
Note: I can only respond to comments from users who have write access to this repository. ⚙️ Control Options:
|
|
Review complete. No blocking issues — approved ✅; 2 nitpicks below. 🧹 Nitpicks (2) — 🟢 2 low
This PR introduces a database standing/suspension enforcement layer. It adds a
Reviewed commit: ecb3659 |
Summary
GraphQL is the one serving lane that does not use
@constructive-db/module-loader, so it gets the same database-standing check through its own loader registry (@constructive-io/express-context). Companion to constructive-db#3701 (state) / #3705 (pg-wire enforcement) and the lane-consumers PR in constructive-db.Loader —
packages/express-context/src/loaders/standing.ts, registered asstandingincreateDefaultRegistry()and typed onBuiltinModuleMap:exists: false(a request pinned to an unknown database has nothing to run against)undefinedvia the factory's existing 42P01 handling like any absent module.Middleware —
graphql/server/src/middleware/standing.ts, mounted inserver.tsright aftercreateContextMiddlewareand before request-protection, so a suspended tenant spends nothing:Uses the existing public
ACCESS_SUSPENDEDerror (#1814);reason(billing/admin) travels inextensions.context.Tests: loader (scoping, suspended/missing rows, propagation, cache+invalidate, tenant isolation) and middleware (pass/refuse/unknown/no-module/fail-closed).
Link to Devin session: https://app.devin.ai/sessions/86a73d903b3546c1afbf5361bfa384b6
Open in Devin Desktop: https://app.devin.ai/desktop/session/86a73d903b3546c1afbf5361bfa384b6?variant=devin
Requested by: @pyramation