Skip to content

TML-2503: service_role admin access to Supabase-internal namespaces (slice D)#845

Open
wmadden-electric wants to merge 2 commits into
mainfrom
tml-2503-extension-supabase-slice-d
Open

TML-2503: service_role admin access to Supabase-internal namespaces (slice D)#845
wmadden-electric wants to merge 2 commits into
mainfrom
tml-2503-extension-supabase-slice-d

Conversation

@wmadden-electric

@wmadden-electric wmadden-electric commented Jun 17, 2026

Copy link
Copy Markdown
Contributor

Linked issue

Refs TML-2503. Slice D of the extension-supabase project. Builds on the merged slice A (#839). Slice contract: projects/extension-supabase/slices/d-service-role-internal-namespaces/spec.md.

At a glance

The service_role-bound db can now query Supabase-internal tables — the admin path — while app roles can't see them:

const admin = db.asServiceRole();
await admin.sql.auth.users.select({ id: true, email: true }).build().execute();  // ✅ "auth"."users"
await admin.sql.public.profile.select({}).build().execute();                  // app tables too

db.asAnon().sql.auth   // ✗ not on the type — anon/authenticated have no auth.* grants

Decision

auth.* / storage.* are exposed only on the service_role-bound db, flat (asServiceRole().sql.auth.users). asUser/asAnon keep exactly today's surface (app namespaces only; auth.* absent from their type). The capability is bound to the one role that actually holds the grant.

This is the considered answer to "how does an admin reach extension-owned tables" — preferred over making users hand-construct a second db from the extension's contract. Full rationale + the Supabase permission reality (direct-connection service_role can read auth.*; the "you can't" advice is PostgREST-only) is recorded in umbrella decision C15; the boundary (cross-space querying off the app db was deliberately not built) is part of the same decision.

How it works

The facade composes the Supabase extension contract's namespaces into the service_role execution context itself — facade-local, since the package ships both contracts. It does not use generic cross-space querying (deliberately not built in cross-contract-refs). Concretely: a second SupabaseRuntimeImpl built from the merged contract that shares the app runtime's driver/pool (one facade, one pool, two internal runtimes differing only by contract). Role binding is the inherited session-coupled-connection path — set_config('role'='service_role', …) + RESET ALL — unchanged.

Behavior changes & evidence

  • asServiceRole().sql.auth.users is queryable; app roles aren't. packages/3-extensions/supabase/src/runtime/supabase.ts (WithExtensionNamespaces, buildServiceRoleContract, serviceRoleRuntime). Evidence: examples/supabase/test/explicit-namespace-query.integration.test.ts — reads a seeded row, asserts emitted SQL targets "auth"."users" and current_setting('role') = 'service_role'.
  • The role-bound type surfaces are correct. Evidence: examples/supabase/test/service-role-namespaces.test-d.ts (typed against the real app contract) — asServiceRole().sql gains auth/storage; asAnon()/asUser() .sql do not have them.

Reviewer notes

  • Reviewed by an independent opus pass before this PR: role-binding integrity, single shared pool, facade-locality (no framework edits), and no-leak-to-app-roles all confirmed; its required fix (the original type-test was tautological) and nits (pin the bound role; comment the intentionally-stale merged storageHash) are applied.
  • The two-internal-runtimes shape is intentional (each SupabaseRuntimeImpl lowers against its own contract; they share the driver, so no second pool). The merged service-role contract intentionally keeps the app's storageHash (commented at the merge site) so the read-only marker check still matches.
  • No new public /contract export; asUser/asAnon byte-for-byte unchanged.

Verification

pnpm exec turbo run typecheck test lint --filter @prisma-next/example-supabase --filter @prisma-next/extension-supabase on the rebased tree — 39/39 tasks pass; example suite 7/7 (incl. the two new Slice D tests).

Checklist

  • All commits are signed off (git commit -s).
  • I read CONTRIBUTING.md and the change is scoped to one logical concern.
  • Tests are updated (new integration + type-level tests).
  • The PR title is Linear-prefixed.
  • Skill update — n/a: no public-surface change beyond the documented asServiceRole namespace widening (covered in decision C15 + overview.md); no CLI/config/error-code change.

Summary by CodeRabbit

  • New Features

    • Service-role database access now properly exposes Supabase extension namespaces (auth, storage) alongside your application contract namespaces, enabling access to system tables.
  • Tests

    • Added comprehensive integration and type-level tests validating service-role namespace access, SQL execution context, and contract behavior.

…ia the facade

db.asServiceRole().sql.auth.users (and storage.*) is now queryable on the
service-role-bound db — the admin path. asUser/asAnon expose only the app
namespaces (auth.* absent from their type). The facade composes the extension
contract's namespaces into the service_role execution context (facade-local:
a second SupabaseRuntimeImpl sharing the app runtime's driver/pool). No
framework changes; no new /contract export.

- runtime: WithExtensionNamespaces<T>, buildServiceRoleContract, serviceRoleRuntime
- example tests: asServiceRole reads auth.users (SQL targets "auth"."users",
  current_setting(role)=service_role); type-level test (app contract) proves
  asServiceRole gains auth/storage while asAnon/asUser do not
- docs: overview.md corrected (admin path vs common FK+RLS path; anon/authenticated
  lack auth.* grants), decision C15 (cross-space-querying boundary + entrypoint),
  plan.md slice D rewritten

Reviewed (opus): APPROVE-WITH-NITS; nits applied (non-vacuous type test, role
assertion pinned, stale-hash comment).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
@wmadden-electric wmadden-electric requested a review from a team as a code owner June 17, 2026 09:20
@pkg-pr-new

pkg-pr-new Bot commented Jun 17, 2026

Copy link
Copy Markdown

Open in StackBlitz

@prisma-next/extension-author-tools

npm i https://pkg.pr.new/@prisma-next/extension-author-tools@845

@prisma-next/mongo-runtime

npm i https://pkg.pr.new/@prisma-next/mongo-runtime@845

@prisma-next/family-mongo

npm i https://pkg.pr.new/@prisma-next/family-mongo@845

@prisma-next/sql-runtime

npm i https://pkg.pr.new/@prisma-next/sql-runtime@845

@prisma-next/family-sql

npm i https://pkg.pr.new/@prisma-next/family-sql@845

@prisma-next/extension-arktype-json

npm i https://pkg.pr.new/@prisma-next/extension-arktype-json@845

@prisma-next/middleware-cache

npm i https://pkg.pr.new/@prisma-next/middleware-cache@845

@prisma-next/mongo

npm i https://pkg.pr.new/@prisma-next/mongo@845

@prisma-next/extension-paradedb

npm i https://pkg.pr.new/@prisma-next/extension-paradedb@845

@prisma-next/extension-pgvector

npm i https://pkg.pr.new/@prisma-next/extension-pgvector@845

@prisma-next/extension-postgis

npm i https://pkg.pr.new/@prisma-next/extension-postgis@845

@prisma-next/postgres

npm i https://pkg.pr.new/@prisma-next/postgres@845

@prisma-next/sql-orm-client

npm i https://pkg.pr.new/@prisma-next/sql-orm-client@845

@prisma-next/sqlite

npm i https://pkg.pr.new/@prisma-next/sqlite@845

@prisma-next/extension-supabase

npm i https://pkg.pr.new/@prisma-next/extension-supabase@845

@prisma-next/target-mongo

npm i https://pkg.pr.new/@prisma-next/target-mongo@845

@prisma-next/adapter-mongo

npm i https://pkg.pr.new/@prisma-next/adapter-mongo@845

@prisma-next/driver-mongo

npm i https://pkg.pr.new/@prisma-next/driver-mongo@845

@prisma-next/contract

npm i https://pkg.pr.new/@prisma-next/contract@845

@prisma-next/utils

npm i https://pkg.pr.new/@prisma-next/utils@845

@prisma-next/config

npm i https://pkg.pr.new/@prisma-next/config@845

@prisma-next/errors

npm i https://pkg.pr.new/@prisma-next/errors@845

@prisma-next/framework-components

npm i https://pkg.pr.new/@prisma-next/framework-components@845

@prisma-next/operations

npm i https://pkg.pr.new/@prisma-next/operations@845

@prisma-next/ts-render

npm i https://pkg.pr.new/@prisma-next/ts-render@845

@prisma-next/contract-authoring

npm i https://pkg.pr.new/@prisma-next/contract-authoring@845

@prisma-next/ids

npm i https://pkg.pr.new/@prisma-next/ids@845

@prisma-next/psl-parser

npm i https://pkg.pr.new/@prisma-next/psl-parser@845

@prisma-next/psl-printer

npm i https://pkg.pr.new/@prisma-next/psl-printer@845

@prisma-next/cli

npm i https://pkg.pr.new/@prisma-next/cli@845

@prisma-next/cli-telemetry

npm i https://pkg.pr.new/@prisma-next/cli-telemetry@845

@prisma-next/emitter

npm i https://pkg.pr.new/@prisma-next/emitter@845

@prisma-next/migration-tools

npm i https://pkg.pr.new/@prisma-next/migration-tools@845

prisma-next

npm i https://pkg.pr.new/prisma-next@845

@prisma-next/vite-plugin-contract-emit

npm i https://pkg.pr.new/@prisma-next/vite-plugin-contract-emit@845

@prisma-next/mongo-codec

npm i https://pkg.pr.new/@prisma-next/mongo-codec@845

@prisma-next/mongo-contract

npm i https://pkg.pr.new/@prisma-next/mongo-contract@845

@prisma-next/mongo-value

npm i https://pkg.pr.new/@prisma-next/mongo-value@845

@prisma-next/mongo-contract-psl

npm i https://pkg.pr.new/@prisma-next/mongo-contract-psl@845

@prisma-next/mongo-contract-ts

npm i https://pkg.pr.new/@prisma-next/mongo-contract-ts@845

@prisma-next/mongo-emitter

npm i https://pkg.pr.new/@prisma-next/mongo-emitter@845

@prisma-next/mongo-schema-ir

npm i https://pkg.pr.new/@prisma-next/mongo-schema-ir@845

@prisma-next/mongo-query-ast

npm i https://pkg.pr.new/@prisma-next/mongo-query-ast@845

@prisma-next/mongo-orm

npm i https://pkg.pr.new/@prisma-next/mongo-orm@845

@prisma-next/mongo-query-builder

npm i https://pkg.pr.new/@prisma-next/mongo-query-builder@845

@prisma-next/mongo-lowering

npm i https://pkg.pr.new/@prisma-next/mongo-lowering@845

@prisma-next/mongo-wire

npm i https://pkg.pr.new/@prisma-next/mongo-wire@845

@prisma-next/sql-contract

npm i https://pkg.pr.new/@prisma-next/sql-contract@845

@prisma-next/sql-errors

npm i https://pkg.pr.new/@prisma-next/sql-errors@845

@prisma-next/sql-operations

npm i https://pkg.pr.new/@prisma-next/sql-operations@845

@prisma-next/sql-schema-ir

npm i https://pkg.pr.new/@prisma-next/sql-schema-ir@845

@prisma-next/sql-contract-psl

npm i https://pkg.pr.new/@prisma-next/sql-contract-psl@845

@prisma-next/sql-contract-ts

npm i https://pkg.pr.new/@prisma-next/sql-contract-ts@845

@prisma-next/sql-contract-emitter

npm i https://pkg.pr.new/@prisma-next/sql-contract-emitter@845

@prisma-next/sql-lane-query-builder

npm i https://pkg.pr.new/@prisma-next/sql-lane-query-builder@845

@prisma-next/sql-relational-core

npm i https://pkg.pr.new/@prisma-next/sql-relational-core@845

@prisma-next/sql-builder

npm i https://pkg.pr.new/@prisma-next/sql-builder@845

@prisma-next/target-postgres

npm i https://pkg.pr.new/@prisma-next/target-postgres@845

@prisma-next/target-sqlite

npm i https://pkg.pr.new/@prisma-next/target-sqlite@845

@prisma-next/adapter-postgres

npm i https://pkg.pr.new/@prisma-next/adapter-postgres@845

@prisma-next/adapter-sqlite

npm i https://pkg.pr.new/@prisma-next/adapter-sqlite@845

@prisma-next/driver-postgres

npm i https://pkg.pr.new/@prisma-next/driver-postgres@845

@prisma-next/driver-sqlite

npm i https://pkg.pr.new/@prisma-next/driver-sqlite@845

commit: a93eb6e

@github-actions

github-actions Bot commented Jun 17, 2026

Copy link
Copy Markdown

size-limit report 📦

Path Size
postgres / no-emit 153.31 KB (0%)
postgres / emit 140.92 KB (0%)
mongo / no-emit 78 KB (0%)
mongo / emit 72.09 KB (0%)
cf-worker / no-emit 181.74 KB (0%)
cf-worker / emit 167.76 KB (0%)

Mirror the .sql coverage on the .orm surface (the whole role-bound db is
parameterized with the merged contract, so .orm projects the internal
namespaces the same way .sql does — verified against OrmClient, not assumed):

- integration: db.asServiceRole().orm.auth.AuthUser.select(...).first({id})
  reads the same seeded auth.users row (whole-shape assertion)
- type-level: asServiceRole().orm has auth/storage; asAnon()/asUser().orm do not

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
@coderabbitai

coderabbitai Bot commented Jun 17, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

Run ID: d2c106a5-32d4-41c8-b68b-780dcfd1dca8

📥 Commits

Reviewing files that changed from the base of the PR and between 69b6e74 and a93eb6e.

⛔ Files ignored due to path filters (4)
  • projects/extension-supabase/plan.md is excluded by !projects/**
  • projects/extension-supabase/slices/d-service-role-internal-namespaces/spec.md is excluded by !projects/**
  • projects/supabase-integration/decisions.md is excluded by !projects/**
  • projects/supabase-integration/overview.md is excluded by !projects/**
📒 Files selected for processing (4)
  • examples/supabase/test/explicit-namespace-query.integration.test.ts
  • examples/supabase/test/service-role-namespaces.test-d.ts
  • packages/3-extensions/supabase/src/exports/runtime.ts
  • packages/3-extensions/supabase/src/runtime/supabase.ts

📝 Walkthrough

Walkthrough

SupabaseDb.asServiceRole() now returns RoleBoundDb<WithExtensionNamespaces<TContract>>, exposing merged auth and storage namespaces only for the service-role execution context. A new buildServiceRoleContract helper merges extension contract namespaces into the app contract, and buildRoleBoundDbWithContext refactors role-bound DB construction to route execution through roleRuntime.executeWithRole and transactions through roleRuntime.openRoleSession. New type-level and integration tests validate the compile-time surface and runtime behavior.

Changes

Service-role extension namespace surface

Layer / File(s) Summary
WithExtensionNamespaces type, asServiceRole() signature, and public re-export
packages/3-extensions/supabase/src/runtime/supabase.ts, packages/3-extensions/supabase/src/exports/runtime.ts
Defines the exported WithExtensionNamespaces<TAppContract> generic type merging auth/storage extension namespaces, updates SupabaseDb.asServiceRole() to return RoleBoundDb<WithExtensionNamespaces<TContract>>, and adds WithExtensionNamespaces to the package re-export list.
buildServiceRoleContract and buildRoleBoundDbWithContext runtime implementation
packages/3-extensions/supabase/src/runtime/supabase.ts
Adds buildServiceRoleContract to deserialize the extension contract JSON and merge storage/domain namespaces into the app contract. Introduces buildRoleBoundDbWithContext routing execute through roleRuntime.executeWithRole and transaction through roleRuntime.openRoleSession. Updates asServiceRole() to use the precomputed service-role context/runtime.
Compile-time namespace surface type tests
examples/supabase/test/service-role-namespaces.test-d.ts
Asserts asServiceRole() exposes public, auth, and storage on .sql and .orm; asAnon()/asUser() expose only public and match RoleBoundDb<Contract>.
Integration tests for service-role auth namespace queries
examples/supabase/test/explicit-namespace-query.integration.test.ts
Boots a dev Supabase database, seeds auth.users, and asserts db.asServiceRole().sql.auth.users emits correct SQL, executes under the service_role Postgres role, and returns the seeded row. A second test confirms public app-contract namespaces coexist with extension namespaces.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐇 Hoppin' through the schema lanes,
Auth and storage now remain
Bound to service_role alone —
Each namespace finds its rightful home.
The contract merges, tests all pass,
No rogue role sneaks through the grass! ✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title directly and accurately describes the main change: adding service_role admin access to Supabase-internal namespaces (auth and storage), which is the core objective of Slice D.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch tml-2503-extension-supabase-slice-d

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant