feat(db): fail fast on cross-module table access in dev mode#136
Merged
Conversation
In dev mode every module shares one owner pool with search_path-only scoping, so a raw SQL read/JOIN against another module's tables silently succeeds locally but can never work deployed (the per-(app,module) DB role holds no grant on foreign tables) — decision 17 §2's works-on-my- machine trap. Close it at the SDK's query chokepoint: mod.DB/Tx/ModuleDB/ModuleTx now wrap the dev-pool Querier with a guard that rejects any statement referencing another module's physical m<hex>_ tables (or mod_m<hex> schema) with an error naming ms.DependencyDB as the sanctioned path. String literals and comments are stripped before scanning so mentions don't false-positive. Production connections are untouched — there the DB role's grants are the enforcement. Known limits (documented in db_guard.go): view/function indirection is not caught, unprefixed foreign names are not caught, and direct db.Open() use bypasses Module.DB entirely. This is an honest dev-mode fail-fast, not a security boundary. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… reality The comments claimed exposed tables live in the producer's cross-app mod_<id> schema; the built grant path targets the per-app tenant schema app_<id>."<prefix><table>" (decision 17 §3 drift correction). Cross-app module state sharing is explicitly out of scope. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Implements decision-17 §2's dev-mode fail-fast at the SDK query chokepoint (Option 2 — no API break). In dev mode every module shares one owner pool with search_path-only scoping, so a raw SQL read/JOIN against another module's tables silently succeeds locally but can never work deployed, where the per-(app,module) DB role holds no grant on foreign tables — the "works on my machine" trap.
mod.DB/mod.Tx/mod.ModuleDB/mod.ModuleTxnow wrap the shared dev pool'sdb.Querierwith a guard that rejects any statement referencing another module's physicalm<32hex>_tables ormod_m<32hex>schema, erroring immediately with a message that names the offending table, its owning module, andms.DependsOn+n.Table(...)+ms.DependencyDBas the sanctioned path.Production/deployed connections are untouched — when a platform credential is in ctx the querier passes through unwrapped; Postgres grants are the enforcement there.
How
internal/core/db_guard.go:guardQuerierinterceptsExec/Query/QueryRow(QueryRowdefers the rejection toScan, matching pgx's own deferred-error rows). Before scanning, string literals (including dollar-quoted), line comments, and nested block comments are stripped so a foreign table name mentioned in a logged message or comment does not false-positive. Only platform-mintedm<32hex>IDs are recognized.devGuardFordecides dev-vs-prod exactly the wayresolvePoolForpicks the pool (no platform credential in ctx), per scope: the app-scope credential does not disable the guard for the module-scope path.Known limits (documented in
db_guard.go— this is an honest dev-mode fail-fast, not a security boundary): view/function indirection is not caught, unprefixed foreign names are not caught, and directdb.Open()use bypassesModule.DBentirely.Also
Second commit fixes decision-17 §3-B comment drift:
ExposeTable/Need.Tabledocs claimed exposed tables live in the producer's cross-appmod_<id>schema; the built grant path targets the per-app tenant schemaapp_<id>."<prefix><table>". Cross-app module state sharing is explicitly out of scope.Testing
$nparams vs dollar-quotes.go build ./...,go vet, fullgo test ./internal/core/ ./db/green.🤖 Generated with Claude Code