fix(graphile): tenant-isolation fixes for i18n/llm/search/meta-schema + @pgsql/quotes adoption (1/6)#1330
Open
yyyyaaa wants to merge 4 commits into
Open
fix(graphile): tenant-isolation fixes for i18n/llm/search/meta-schema + @pgsql/quotes adoption (1/6)#1330yyyyaaa wants to merge 4 commits into
yyyyaaa wants to merge 4 commits into
Conversation
…search, meta-schema Four defects found while auditing plugin behavior on shared/multi-schema processes; every fix is live behavior today (none depends on any caching or pooling change): - graphile-i18n: run the translations query through withPgClient with the REQUEST's pgSettings instead of null. With null the query ran outside the requester's role/claims — bypassing RLS on base + translation tables. Also reset the locale GraphQLObjectType cache per build (init hook) so a rebuilt schema never reuses type objects from a previous build. - graphile-llm (agent-discovery): the discovery query had no database filter — `LIMIT 1` over a control-plane table that holds EVERY database's rows, so a process serving multiple databases could return another database's agent config. Filter by the requester's jwt.claims.database_id, fail closed when absent, and key the 60s cache by database id (with a :nodb spillover key) instead of dbname. - graphile-llm (rag): keep chunk-table references schema-qualified and export the SQL builders for unit testing. - graphile-search (bm25): pass the schema-qualified index name to to_bm25query; document the store-lookup keying accurately. - graphile-settings (meta-schema): key collected table metadata by the build object (WeakMap) instead of a module-global array. Concurrent schema builds in one process interleave init hooks, so the global let one build's _meta serve another build's tables. The flat mirror stays for out-of-process codegen consumers (single-build, last-write-wins is safe there). Extracted from #1325 (commits d320a27, f62fb69, 0d1496e, b3bad9e). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0122xqM2VkNbuAmZshK1YNSb
…ndency changes) `pnpm install --lockfile-only` with UNCHANGED manifests rewrites the lockfile into pnpm 10.30's canonical form (mostly dedupe). Committed separately so the dependency changes in this stack stay readable; identical normalization churn is embedded in #1325's lockfile diff. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0122xqM2VkNbuAmZshK1YNSb
…rag, bm25
Replace hand-rolled `"${schema}"."${table}"` templates with
QuoteUtils.quoteIdentifier / quoteQualifiedIdentifier (requested in #1325
review). The library ports PostgreSQL's quote_ident: identifiers are emitted
bare when lexically safe and double-quoted (with embedded-quote doubling) when
required — strictly more correct than the always-quote templates for
case-sensitive names, reserved keywords, and names containing '"'.
Load-bearing property, now pinned by tests: hashed multi-tenant schema names
contain '-' and therefore ALWAYS come out double-quoted. Schema-identifier
rewriting at the pool seam (later in this stack) matches double-quoted schema
tokens in SQL text, so tenant-schema references in plugin SQL must never be
emitted bare. Plain snake_case identifiers (columns, index names, control-
plane schemas that are never rewritten) may now be emitted bare; assertions
updated accordingly.
@pgsql/quotes@17.1.0 is already in the workspace dependency graph (used by
graphile-settings, presigned-url, realtime, pgpm) — zero new install weight.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0122xqM2VkNbuAmZshK1YNSb
This was referenced Jul 6, 2026
feat(server,graphile): opt-in blueprint pooling integration — GRAPHILE_BLUEPRINT_POOLING (5/6)
#1334
Open
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.
Summary
First PR of the 6-PR stack that splits #1325 into incrementally mergeable pieces (per review feedback there). This one carries the four standalone correctness/tenant-isolation bugs the #1325 audit found in plugins — every one of them is live behavior today and none depends on caching or pooling — plus the requested @pgsql/quotes adoption.
Fixes
localeStringstranslations query ran throughwithPgClient(null, …): no role, no JWT claims, so RLS on the base + translation tables was not applied for that read. It now passes the request'spgSettings. Also: the localeGraphQLObjectTypecache is reset per build (inithook) so a rebuilt schema can't reuse type objects from a previous build.LIMIT 1over a control-plane table holding every database's rows, so a process serving multiple databases could return another database's agent config. Now filtered by the requester'sjwt.claims.database_id, failing closed when absent, with the 60s cache keyed by database id._metableed. Collected table metadata lived in a module-global; concurrent schema builds interleaveinithooks, so one build's_metacould serve another build's tables. Now keyed by thebuildobject (WeakMap). The flat mirror stays for out-of-process codegen consumers (single-build, last-write-wins is safe there).@pgsql/quotes adoption
Addresses the review comment on
graphile-i18n/src/plugin.tsin #1325. Hand-rolled"${schema}"."${table}"templates in i18n, rag, and bm25 are replaced withQuoteUtils.quoteIdentifier/quoteQualifiedIdentifier.Two properties worth knowing:
quote_ident: identifiers come out bare when lexically safe and double-quoted (with"doubled) when required — strictly more correct than always-quote templates for case-sensitive names, reserved keywords, and embedded quotes. Test assertions were updated for the bare snake_case forms.-, so they are always emitted double-quoted. The pooling rewrite seam (later in this stack) matches double-quoted schema tokens in SQL text, so tenant-schema references in plugin SQL must never be emitted bare. New test cases assert the dashed-schema quoting in both rag and bm25.Where it is deliberately not used: the rewrite seam itself (PR 4 of the stack) must always emit quoted identifiers when substituting tokens, so
quote_ident-style bare output is wrong there by construction.@pgsql/quotes@17.1.0is already in the workspace dependency graph (graphile-settings, presigned-url, realtime, pgpm/cli) — zero new install weight; the three plugin package.jsons just declare it.Commits
chore(lockfile)— pnpm 10.30 re-normalization with no dependency changes, split out so the real deltas stay readable (~10k mechanical lines; the same churn is embedded in (DO NOT MERGE: superseded by stacked PRs) (WIP) proof of concept: feat(server): heap-safe instance cache + opt-in blueprint pooling (one instance per schema-shape) #1325's lockfile diff).Testing
graphile-i18n5/5 ·graphile-llm58/58 non-Ollama (11 env-dependent Ollama integration tests need the CI service container) incl. 11 new agent-discovery/rag tests ·graphile-search78/78 incl. new quoting tests ·graphile-settings158/158.Stack
feat/scale-s1-plugin-fixesfeat/scale-s2-cache-hardeningfeat/scale-s3-introspection-filterfeat/scale-s4-pooling-corefeat/scale-s5-pooling-integrationGRAPHILE_BLUEPRINT_POOLING)feat/scale-s6-perf-toolingEach PR targets the previous branch; merge top-to-bottom (GitHub retargets children automatically when a base merges). Extracted from #1325, which stays open as the validated end-to-end reference — all numbers cited in this stack were measured there on a production-shaped rig (48 tenant DBs, 61k
pg_classrows).🤖 Generated with Claude Code
https://claude.ai/code/session_0122xqM2VkNbuAmZshK1YNSb