prisma-next-supabase について
Prisma Next を Supabase に接続する開発者向けのスキルです。契約の設定、RLS ポリシー、ロールに紐づくクエリを扱い、クエリ実行前にロールを明示して JWT を検証します。
主な機能
- 01Supabase 外部契約の合成
- 02RLS ポリシーの定義
- 03auth.users への外部キー参照
- 04ロールに紐づくクエリ実行
- 05ユーザー JWT の検証
- 06Supabase 内部テーブルの読み取り
活用シーン
- 01アプリケーションテーブルへの RLS 設定
- 02モデルから auth.users への関連付け
- 03ユーザーまたは匿名ロールでのクエリ
- 04service_role による auth.* テーブルの読み取り
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ prisma/ prisma-next --skill prisma-next-supabaseインストール後、Claude Code で/prisma-next-supabaseを入力して読み込まれたか確認します。
詳細
Prisma Next — Supabase
データコントラクトを編集すれば、あとは Prisma にお任せください。
このスキルでは、Prisma Next を Supabase プロジェクトでエンドツーエンドに使用する方法を扱います。Supabase 拡張パックの組み合わせ、コントラクトから Supabase 所有のテーブルを参照する方法、行レベルセキュリティ(RLS)ポリシーの作成、supabase() ランタイムを使ったロールに紐づくクエリの実行について説明します。
使用する場面
- ユーザーが Supabase プロジェクトを持っている(または作成を希望している)場合に、Prisma Next を接続するとき。
- ユーザーがテーブルに RLS ポリシー(
policy_select、@@rls、auth.uid())を設定したいとき。 - ユーザーがリクエストごとにロールを紐づけたいとき(
asUser(jwt)、asAnon()、asServiceRole())。 - ユーザーが
auth.usersへの外部キー(スペースをまたぐ FK)を設定したいとき。 - ユーザーが管理者として Supabase 内部テーブル(
auth.*、storage.*)を読み取りたいとき。 - ユーザーが次の内容に言及している場合:supabase、RLS、行レベルセキュリティ、ポリシー、anon、authenticated、service_role、auth.users、auth.uid()、JWT、jwtSecret、jwksUrl、SUPABASE.JWT_INVALID、RoleBoundDb、セッションプーラー。
使用しない場面
- 一般的なコントラクトの編集(モデル、フィールド、リレーション)→
prisma-next-contract。 - Supabase 以外の
db.ts接続、ミドルウェア、終了処理 →prisma-next-runtime。 - 一般的なクエリの形式(フィルタリング、include、集計)→
prisma-next-queries— ここにある内容は、ロールに紐づくdbにもすべて適用されます。 - マイグレーションの計画や適用 →
prisma-next-migrations。
主な概念
- パックは
externalの契約空間です。@prisma-next/extension-supabase/packは、Supabase が管理するすべてのもの、すなわちauthスキーマとstorageスキーマ、そのネイティブの enum 型、プラットフォームロール(anon、authenticated、service_role)の完全なイントロスペクション生成済み契約を、すべて制御ポリシーexternalとして提供します。extensions経由で合成すると、移行プランナーはこれらのオブジェクトに対する DDL を一切出力しません(Supabase が管理します)。一方、db verifyはライブデータベースにそれらが存在することを確認します。独自のテーブルは通常どおりmanagedのままです。 - ロールはパックから取得されるため、自分で宣言する必要はありません。 RLS の
roles = [authenticated]識別子は、合成された契約に対して解決されます。ランタイムの接続先が Supabase 以外の Postgres である場合、verify は不足しているロールを示すnot-foundの問題で失敗します。よくある「データベースの指定間違い」という設定ミスが、クエリの実行前に検出されます。 - ランタイムはロール優先です。
supabase()は、トップレベルのクエリ用インターフェースを持たないSupabaseDbを返します。ロールをバインドするまで、db.sql/db.ormはありません。await db.asUser(jwt)/db.asAnon()/db.asServiceRole()は、それぞれ.sql、.orm、.raw、.execute(plan)、.transaction(fn)を公開するRoleBoundDbを返します。これは意図的な設計です。Supabase アプリでは「ロールなし」の実行コンテキストに意味がなく、接続のログインロールをデフォルトにすると、気づかないうちに RLS を回避してしまう危険があるためです。 - ロールのバインドはミドルウェアの下位で行われ、外部に漏れません。 ロールにバインドされた各クエリは、ユーザーミドルウェアチェーンの下位で
set_config('role', …)とset_config('request.jwt.claims', …)を適用し、接続の解放時にRESET ALLを実行する接続上で動作します。Postgres 側のauth.uid()/auth.jwt()はこれらのセッション変数を読み取ります。RLS の適用は Postgres の役割であり、コンテキストのバインドはランタイムの役割です。 - RLS はポリシーと権限の両方によって適用されます。 ポリシーは行をフィルタリングし、
GRANTはテーブルへのアクセスを制御します。Prisma Next はポリシーを作成して移行しますが、権限は作成しません(Prisma Next がまだ対応していないことを参照)。ポリシーがあってもGRANTがないロールでは、行がフィルタリングされるのではなく、権限エラーが発生します。Supabase では、publicテーブルにはデフォルト権限によってプラットフォームロールの権限がすでに付与されています。初期状態で実際に不足している権限は、auth.*/storage.*に対するservice_roleの権限です(ワークフロー — 権限を参照)。 - JWT 検証は接続取得前に実行され、設定可能です。現在の Supabase プロジェクトには
jwksUrlが必要です。asUser(jwt)は接続を取得する前に(joseを介して)トークンを検証します。検証ではjwksUrlに対して署名と有効期限を確認します(非対称署名キー — ES256 で署名する現在の Supabase プロジェクトのデフォルト)。またはjwtSecretを使用します(対称 HS256 シークレット — レガシープロジェクトのみ)。両方を指定した場合、またはどちらも指定しなかった場合は、コードSUPABASE.CONFIG_INVALIDを持つ構造化エラーが発生します。不正なトークンの場合は、コードSUPABASE.JWT_INVALIDと型付きのmeta.reasonを持つ構造化エラーが発生します。これには、トークンのアルゴリズムと設定されたキーソースの不一致も含まれます(jwtSecretを指定したクライアントに対して ES256 トークンを使うと、問題を示し、jwksUrlに切り替えるよう案内します)。Postgres ロールはトークンのroleクレームから決まり、デフォルトはauthenticatedです。注:supabase statusは、ES256 で署名するプロジェクトでもJWT_SECRETを表示します。表示されていても、プロジェクトでそれが使われているとは限りません。 auth.*/storage.*への管理者アクセスは、service_roleだけが持つ副次的なルートであり、一度限りの権限付与が必要です。db.asServiceRole().supabaseは、パック独自の契約(.sql、.orm、.nativeEnums、.execute)を公開します。このルートは設計上service_roleにのみ存在しますが、実際の Supabase プロジェクトではservice_roleにauth.*/storage.*のテーブル権限が一切付与されていません(スキーマのUSAGEのみで、テーブル権限を持つのはpostgresのみです)。管理者ルートで Supabase 内部のテーブルを読み取る前に、対象を絞った権限を一度付与してください(ワークフロー — 権限を参照)。asUser/asAnonには.supabaseがなく、主要なasServiceRole().sql/.ormは引き続き独自の契約の範囲内にとどまります。
ワークフロー — パックを設定に組み込む
コンセプト:パックは Supabase のコントラクト空間を登録するため、コントラクトから参照でき、プランナー/検証ツールは Supabase が何を所有するかを把握できます。この拡張機能にはまだ /control サブパスがないため、ターゲットファサードの defineConfig({ extensions: [...] }) 経由では組み込めません。低レベル設定の extensions に組み込みます(What Prisma Next doesn't do yet を参照)。以下の低レベルインポートは、ファサードのみをインポートするルールに対する意図的な例外であり、この不足によるものです。このブロックは examples/supabase/prisma-next.config.ts をそのまま反映しています。独自に組み立てず、コピーしてください。
// prisma-next.config.ts
import postgresAdapter from '@prisma-next/adapter-postgres/control';
import { defineConfig } from '@prisma-next/cli/config-types';
import postgresDriver from '@prisma-next/driver-postgres/control';
import supabasePack from '@prisma-next/extension-supabase/pack';
import sql from '@prisma-next/family-sql/control';
import { prismaContract } from '@prisma-next/sql-contract-psl/provider';
import postgres from '@prisma-next/target-postgres/control';
import postgresPackRef from '@prisma-next/target-postgres/pack';
import { postgresCreateNamespace } from '@prisma-next/target-postgres/types';
export default defineConfig({
family: sql,
target: postgres,
adapter: postgresAdapter,
driver: postgresDriver,
extensions: [supabasePack],
contract: prismaContract('./src/contract.prisma', {
output: 'src/contract.json',
target: postgresPackRef,
createNamespace: postgresCreateNamespace,
}),
migrations: { dir: 'migrations' },
});
ワークフロー — コントラクト:auth.users への FK + RLS ポリシー
コンセプト:モデルは自分の名前空間(public)に配置し、Supabase のモデルはパックの名前空間(auth、storage)に配置します。supabase:auth.AuthUser 型のリレーションフィールドは空間をまたぐ FKです。プランナーは REFERENCES "auth"."users"("id") を出力し、ターゲットテーブルは検証されるだけで、マイグレーションされません。RLS ポリシーは、対象モデルと同じ名前空間にあるトップレベルの policy_<operation> ブロックで、対象モデルには @@rls による明示的な有効化が必要です。examples/supabase/src/contract.prisma をそのまま反映してください。
namespace public {
model Profile {
id Uuid @id @default(uuid())
username String
userId Uuid @unique
user supabase:auth.AuthUser @relation(fields: [userId], references: [id], onDelete: Cascade)
@@map("profile")
@@rls
}
// authenticated may read only their own profile.
policy_select profile_owner_read {
target = Profile
roles = [authenticated]
using = "\"userId\"::uuid = auth.uid()"
}
// anon may read every profile (a public directory listing).
policy_select profile_public_read {
target = Profile
roles = [anon]
using = "true"
}
// authenticated may update only their own profile, and may not
// reassign it to another owner (WITH CHECK).
policy_update profile_owner_write {
target = Profile
roles = [authenticated]
using = "\"userId\"::uuid = auth.uid()"
withCheck = "\"userId\"::uuid = auth.uid()"
}
}
Uuid コンストラクターは、型の位置でネイティブ UUID ストレージを選択します。従来の @db.Uuid 表記は削除されています。出力する前に、String @db.Uuid というエイリアスまたはフィールドはすべて Uuid に書き換えてください。
各要素:
- 操作ごとのポリシーブロック:
policy_select、policy_insert、policy_update、policy_delete、policy_all。本文はkey = value形式です。target(この名前空間内のモデル)、roles(合成後のコントラクトに対して解決されます。パックはanon/authenticated/service_roleを提供します)、using、および(書き込み操作の場合は)withCheckを指定します。同じ(target, operation)に対する複数の許可ポリシーは有効です。Postgres はそれらを OR で評価します。 - ポリシー対象には
@@rlsが必須です。 対象モデルに@@rlsがないpolicy_*ブロックは、PSL_EXTENSION_TARGET_MODEL_MISSING_ATTRIBUTEにより出力時に失敗します。@@rlsがあり、ポリシーがないモデルにも意味があります。RLS が有効になり、すべて拒否されます。 - 述語は SQL 文字列としてそのまま扱われます。 述語内の camelCase の列名は引用符で囲み(
\"userId\")、必要に応じてキャストしてください。auth.uid()はuuidを返します。コントラクト内での名前変更によって述語本文が書き換えられることはありません。 - TS ビルダーとの同等性が確保されています。
@prisma-next/postgres/contract-builderは、policySelect/policyInsert/policyUpdate/policyDelete/policyAll、rlsEnabled(Model)、およびrole('anon')をエクスポートします。これらは PSL の低レベル表現とキー単位で対応し、出力されるワイヤ名も同一です。ここでは PSL を標準的な方法として示しています。
通常どおり出力してマイグレーションします(prisma-next contract emit の後に prisma-next-migrations を実行します)。プランによって、テーブル、FK、ENABLE ROW LEVEL SECURITY、CREATE POLICY ステートメントが作成されます。auth.* に対する DDL は作成されません。
ワークフロー — supabase() ファクトリーを使った db.ts
コンセプト:標準の postgres() ファクトリーの代わりに、Supabase アプリは拡張機能の /runtime サブパスから supabase() を使ってクライアントを構築します。このファクトリーは非同期です(JWT キーマテリアルを準備し、jwksUrl が設定されている場合は初回の JWKS 取得も行います)。戻り値はロール優先です。
// src/prisma/db.ts
import { supabase } from '@prisma-next/extension-supabase/runtime';
import type { Contract } from './contract.d';
import contractJson from './contract.json' with { type: 'json' };
export const db = await supabase<Contract>({
contractJson,
url: process.env['DATABASE_URL'], // direct Postgres connection — see pitfalls
jwksUrl: process.env['SUPABASE_JWKS_URL'], // https://<project-ref>.supabase.co/auth/v1/.well-known/jwks.json
// Legacy HS256 projects use jwtSecret: process.env['SUPABASE_JWT_SECRET'] instead — exactly one of the two.
});
基本設定以外のオプション:middleware(postgres() と同じ合成方法です。prisma-next-runtime を参照してください。ミドルウェアからロールバインディングの set_config 呼び出しが見えることはありません)、poolOptions、pg(url の代わりに BYO の pg.Pool/pg.Client を使用)。終了処理は prisma-next-runtime とまったく同じく await db.close()/await using で行います。同じスクリプトのハングに関するルールが適用されます。
ワークフロー — ロールに紐づけたクエリ
コンセプト:リクエストを実行するロールを紐づけ、返された RoleBoundDb を通じてクエリを実行します。prisma-next-queries のすべてのクエリサーフェスが利用でき、Postgres によって RLS でフィルタリングされます。
// A signed-in user: rows are RLS-scoped to the JWT's auth.uid().
const userDb = await db.asUser(jwt); // async — rejects with code SUPABASE.JWT_INVALID on a bad/expired token
const mine = await userDb.orm.public.Profile.select('id', 'username').all();
// The anon role: sees what anon policies permit.
const listing = await db.asAnon().orm.public.Profile.select('id', 'username').all();
// service_role: BYPASSRLS — sees everything in YOUR contract.
const all = await db.asServiceRole().orm.public.Profile.select('id', 'username').all();
// Writes ride the same surfaces; RLS filters them too. An UPDATE against
// another owner's row affects 0 rows; a withCheck violation raises an error.
const updated = await userDb.orm.public.Profile
.where({ userId: me })
.updateAndCount({ username: 'new-name' });
注:asAnon() / asServiceRole() は同期処理で、非同期なのは asUser のみです。マルチネームスペースのコントラクトでは、座標を使ってモデルにアクセスします(orm.public.Profile、sql.public.profile)。詳しくは prisma-next-queries § ネームスペース対応アクセサー を参照してください。RoleBoundDb.transaction(fn) は、ロールに紐づいたセッションで処理をトランザクション内にラップします。
ワークフロー — auth.* / storage.* の管理者読み取り
コンセプト:Supabase 内部テーブルはコントラクトに含まれないため、クエリサーフェスにも含まれません。service_role バインディングにはセカンダリルートがあり、db.asServiceRole().supabase がパッケージのコントラクトサーフェスです。
const admin = db.asServiceRole();
// SQL builder over the pack contract:
const users = await admin.supabase
.execute(admin.supabase.sql.auth.users.select('id', 'email').build())
.toArray();
// ORM over the pack contract:
const sessions = await admin.supabase.orm.auth.AuthSession.select('id', 'aal').all();
// Native enum values (e.g. auth.aal_level) come typed:
type AalLevel = (typeof admin.supabase.nativeEnums.auth.AalLevel)['Value'];
管理者ルートには一度だけ GRANT が必要です。 実際の Supabase プロジェクトでは、service_role に auth.* / storage.* のテーブル権限はありません。そのため、初期状態では上記の読み取りは permission denied for table users(sqlState 42501)で失敗します。読み取る対象だけに、必要最小限の権限を付与してください。
GRANT USAGE ON SCHEMA auth TO service_role;
GRANT SELECT ON TABLE auth.users TO service_role;
ほかにも守るべき境界があります。asUser / asAnon に .supabase はありません。管理者ルートには .transaction がありません(プールを共有する別個のコントラクトバウンドランタイムであり、両方のルートにまたがるトランザクションは対象外です)。また、ユーザーの管理(ユーザーの作成、パスワードのリセット)には GoTrue Admin API を優先してください。Supabase 内部スキーマはプラットフォームのアップグレードによって変更される可能性があります。service_role による直接の SQL は、臨時の管理者読み取りに使用します。
ワークフロー — GRANT
コンセプト:RLS ポリシーは通常のテーブル権限に加わる行フィルターです。ポリシーがあっても GRANT のないロールは、行がフィルタリングされるのではなく permission denied になります。Supabase では、次の 2 つの方向を混同しやすいので注意してください。
- 独自の
publicテーブルには何も必要ありません。 Supabase ではpublicに対してALTER DEFAULT PRIVILEGESが設定されているため、prisma-next db init/migrateで作成したテーブルには、ダッシュボードで作成したテーブルと同様に、anon/authenticated/service_roleの完全な権限が自動的に継承されます。行を実際に保護するのは RLS ポリシーです。テーブルごとに GRANT を追加したり、理由なくデフォルト権限を狭めたりしないでください。 - 必要な GRANT は、Supabase 内部テーブルを管理者が読み取るためのものだけです。
service_roleにはauth.*/storage.*のテーブル権限がありません(必要最小限のGRANT USAGE/GRANT SELECTの組み合わせについては、上記の管理者読み取りを参照してください)。
GRANT は Supabase SQL エディターまたは psql から実行します。GRANT が不足している場合、空の結果ではなく permission denied for table …(sqlState 42501)が発生します。
ワークフロー — 実際の Supabase プロジェクトへの接続
コンセプト:ランタイムには、直接接続でき、セッションを扱える Postgres 接続が必要です。ロールの紐づけには、セッションスコープの set_config + RESET ALL を使用します。
- セッションプーラー(
aws-0-<region>.pooler.supabase.com:5432、ユーザー名postgres.<project-ref>)— あらゆる環境で動作し、IPv4 に対応しています。デフォルトの選択肢です。 - 直接接続(
db.<project-ref>.supabase.co:5432)— 動作しますが、新しいプロジェクトでは IPv6 専用です。IPv4 のみの環境からは DNS / 接続に失敗します。 - トランザクションプーラー(ポート 6543)— 使用しないでください。 トランザクションプーリングはセッション GUC を壊すため、ロールの紐づけが正しく動作しません。
.env には DATABASE_URL と JWT キーの取得元を設定します。現在のプロジェクトでは SUPABASE_JWKS_URL を使用します。値は https://<project-ref>.supabase.co/auth/v1/.well-known/jwks.json です(ローカルスタックでは http://127.0.0.1:54321/auth/v1/.well-known/jwks.json)。旧式の HS256 プロジェクトでのみ SUPABASE_JWT_SECRET(Project Settings → API → JWT Secret)を使用します。なお、ES256 プロジェクトでも supabase status は JWT_SECRET を出力するため、その存在だけで方式を判断しないでください。JWKS エンドポイント、またはトークンのヘッダー alg を確認してください。
よくある落とし穴
- トランザクションプーラー(ポート 6543)を使用する。 セッション GUC のロールバインディングにはセッション対応の接続が必要です。セッションプーラー(5432)または直接接続を使用してください。
supabase statusにJWT_SECRETと表示されるため、jwtSecretを設定する。 現在のプロジェクトでは ES256 で署名されます。そのため、asUserはトークンが ES256 であり、クライアントにはjwksUrlが必要だと説明するSUPABASE.JWT_INVALIDをスローします。SUPABASE_JWKS_URLを設定し、jwtSecretはレガシーな HS256 プロジェクト用にしてください。- 誤った方向に権限を付与する。
publicテーブルには権限付与は不要です(Supabase のデフォルト権限でカバーされ、RLS が行を保護します)。必要なのは、service_roleによる管理者読み取り用の限定的なauth.*の権限ペアです。permission denied(42501)は、結果がフィルタリングされたのではなく、権限付与が不足していることを意味します。 - トップレベルの
dbにdb.sql/db.ormがあると思い込む。 Supabase の db はロール優先です。ロールをバインドして、RoleBoundDbにクエリを実行してください。 awaitを忘れる —supabase()ファクトリーとasUser(jwt)の両方に必要です。どちらも非同期ですが、asAnon()/asServiceRole()は非同期ではありません。asUser/asAnonに.supabaseがあると思い込む。auth.*への管理者アクセスは構成上service_role専用です。また、service_roleであっても、事前に一度だけ限定的な権限付与が必要です。- 対象に
@@rlsがないpolicy_*ブロック。 Emit はPSL_EXTENSION_TARGET_MODEL_MISSING_ATTRIBUTEで失敗します。モデルに@@rlsを追加してください。 - 引用符で囲まれていない camelCase のカラム、または述語内のキャスト漏れ。 述語はそのまま SQL として扱われます。
"userId"には引用符が必要です。カラムの型がすでにuuidでない場合は、uuidとauth.uid()を::uuidキャストを使って比較してください。 jwksUrlとjwtSecretの両方(またはどちらも指定しない)を渡す。supabase()の Promise はSUPABASE.CONFIG_INVALIDで reject されます。非同期ファクトリーのため、設定ミスは同期的な throw ではなく reject として現れます(await/.catch)。- RLS でフィルタリングされた書き込みをエラーとして扱う。 ロールから見えない行に対する
UPDATEは0 行に影響するだけで、例外は発生しません。エラーになるのはwithCheck違反のみです。
Prisma Next がまだ対応していないこと
- 拡張機能に
/controlサブパスがない — 対象ファサードのdefineConfig({ extensions: [...] })を通じて登録できません。上記のとおり、低レベル設定のextensionsを通じて接続します。機能の要望はprisma-next-feedbackからお寄せください。 GRANTの定義。 テーブル権限は契約の要素ではありません。Supabase アプリに必要な唯一の権限付与(管理者読み取り用のservice_roleのauth.*権限ペア)は、SQL エディターまたはpsqlで一度だけ手動実行します。権限付与を契約で管理したい場合は、prisma-next-feedbackからご要望ください。- アプリのルートと
.supabase管理者ルートをまたぐトランザクション。 2 つのルートは、1 つのプールを共有する別々の契約バインド済みランタイムです。ルートをまたぐトランザクションには対応していません。 - 契約要素としてのトリガー / 関数。 サインアップ時にプロフィール行を作成する従来型の
auth.usersトリガーは、契約内ではなくデータベースに対する生の SQL として定義します。auth.uid()などは、不透明なポリシー述語文字列内でのみ使用します。 - Supabase Realtime、ストレージへのアップロード、PostgREST /
@supabase/supabase-jsとの相互運用、エッジランタイム。 これらは拡張機能の対象外です。拡張機能は Postgres と直接通信します(Node.js / Bun)。
リファレンスファイル
examples/supabase— 設定、契約、db.ts、受け入れテスト、README を含む、実行可能な標準アプリです。packages/3-extensions/supabase/README.md— パッケージレベルのリファレンス(JWT モード、ロールバインディングモデル、未対応の範囲)。packages/3-extensions/supabase/src/runtime/supabase.ts— オプションと型の正式な定義(SupabaseOptions、RoleBoundDb、ServiceRoleDb)。
チェックリスト
- 低レベルの
defineConfigにextensions: [supabasePack]が設定されている(/controlサブパスは存在しません)。 - スペースをまたぐ外部キーが
supabase:auth.AuthUserとして型指定され、明示的なfields/references(必要に応じてonDelete)が指定されている。 - ポリシーの対象となるすべてのモデルに
@@rlsが設定されている。述語ではキャメルケースのカラム名を引用符で囲み、auth.uid()の呼び出しにキャストを適用している。 -
db.tsではawait supabase<Contract>({ contractJson, url, jwksUrl | jwtSecret })を使用している(JWT キーソースは1つだけ)。現在のプロジェクトではjwksUrlを、レガシーの HS256 でのみjwtSecretを使用する。 - クエリは
asUser/asAnon/asServiceRoleから取得したRoleBoundDbを通して実行している。asUserはawaitしている。 -
auth.*/storage.*の読み取りは、1回限りの最小限の権限付与(GRANT USAGE ON SCHEMA authと、読み取るテーブルに対するGRANT SELECT)の後、asServiceRole().supabaseのみを通して行っている。 - 独自の
publicテーブルに対するテーブルごとの権限付与を追加していない。Supabase のデフォルト権限でカバーされ、RLS が保護を担う。 - 接続はセッション対応である。セッションプーラーまたは直接接続を使用し、6543 のトランザクションプーラーは決して使用しない。
-
/controlサブパス、トップレベルのdb.sql、サービスロール以外のロールに対する.supabase、またはコントラクト内での権限付与の定義を、誤って作り出していない。