@luanpdd/kit-mcp 1.35.0 → 1.36.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/cli.js +2 -2
- package/bin/mcp.js +6 -6
- package/bin/ui.js +74 -74
- package/gates/ai-prompt-stability.md +120 -120
- package/gates/budget-description.md +68 -68
- package/gates/confidence.md +29 -29
- package/gates/dependency-check.md +33 -33
- package/gates/dept-cycle-prevention.md +179 -179
- package/gates/golden-signals-coverage.md +133 -133
- package/gates/legacy-refactor-safety.md +178 -178
- package/gates/multi-tenant-rls-coverage.md +102 -102
- package/gates/no-personal-uuid.md +72 -72
- package/gates/obs-agents-mcp-supabase.md +86 -86
- package/gates/obs-skills-frontmatter.md +76 -76
- package/gates/observability-coverage.md +151 -151
- package/gates/omm-no-regression.md +83 -83
- package/gates/postmortem-template-required.md +127 -127
- package/gates/prr-checklist-coverage.md +128 -128
- package/gates/regression.md +32 -32
- package/gates/release-pipeline-policy.md +132 -132
- package/gates/secrets-scan.md +33 -33
- package/gates/service-role-not-in-user-facing.md +113 -113
- package/gates/skill-must-include.md +71 -71
- package/gates/sync-idempotent.md +62 -62
- package/gates/verify-phase-goal.md +34 -34
- package/kit/agents/designer-ui.md +216 -216
- package/kit/agents/workflow-generator.md +537 -167
- package/kit/commands/adicionar-backlog.md +1 -1
- package/kit/commands/adicionar-fase.md +1 -1
- package/kit/commands/adicionar-tarefa.md +1 -1
- package/kit/commands/auditar-observabilidade.md +103 -103
- package/kit/commands/auditar-toil.md +129 -129
- package/kit/commands/caracterizar-prompt.md +195 -195
- package/kit/commands/criar-workflow.md +158 -158
- package/kit/commands/definir-perfil.md +1 -1
- package/kit/commands/definir-slo.md +108 -108
- package/kit/commands/fio.md +1 -1
- package/kit/commands/golden-signals.md +142 -142
- package/kit/commands/instrumentar-fase.md +200 -200
- package/kit/commands/investigar-producao.md +162 -162
- package/kit/commands/observabilidade.md +118 -118
- package/kit/commands/postmortem.md +179 -179
- package/kit/commands/prr.md +205 -205
- package/kit/commands/publicar-rapido.md +207 -207
- package/kit/commands/risk-budget.md +220 -220
- package/kit/commands/sre.md +230 -230
- package/kit/file-manifest.json +424 -424
- package/kit/framework/references/output-style.md +22 -22
- package/kit/hooks/post-apply-migration.js +199 -199
- package/kit/hooks/sidecar-tool-publisher.js +210 -210
- package/kit/skills/_shared-dados-distribuidos/glossary.md +224 -224
- package/kit/skills/_shared-legacy/glossary.md +389 -389
- package/kit/skills/_shared-multi-tenant/glossary.md +186 -186
- package/kit/skills/_shared-observability/glossary.md +396 -396
- package/kit/skills/_shared-sre/glossary.md +712 -712
- package/kit/skills/_shared-supabase/glossary.md +234 -234
- package/kit/skills/blameless-postmortems/SKILL.md +340 -340
- package/kit/skills/burn-rate-alerting/SKILL.md +258 -258
- package/kit/skills/cascading-failures/SKILL.md +311 -311
- package/kit/skills/core-analysis-loop/SKILL.md +352 -352
- package/kit/skills/distributed-tracing/SKILL.md +362 -362
- package/kit/skills/dynamic-workflow-authoring/SKILL.md +327 -223
- package/kit/skills/eliminating-toil/SKILL.md +243 -243
- package/kit/skills/event-based-slos/SKILL.md +296 -296
- package/kit/skills/four-golden-signals/SKILL.md +314 -314
- package/kit/skills/hermetic-builds/SKILL.md +323 -323
- package/kit/skills/legacy-monster-methods/SKILL.md +444 -444
- package/kit/skills/llm-as-dependency/SKILL.md +436 -436
- package/kit/skills/load-shedding-graceful-degradation/SKILL.md +396 -396
- package/kit/skills/observability-driven-development/SKILL.md +315 -315
- package/kit/skills/observability-maturity-model/SKILL.md +222 -222
- package/kit/skills/opentelemetry-standard/SKILL.md +351 -351
- package/kit/skills/production-readiness-review/SKILL.md +305 -305
- package/kit/skills/release-engineering/SKILL.md +367 -367
- package/kit/skills/retry-strategies/SKILL.md +372 -372
- package/kit/skills/sre-risk-management/SKILL.md +221 -221
- package/kit/skills/structured-events/SKILL.md +265 -265
- package/kit/skills/supabase-cron-queues/SKILL.md +275 -275
- package/kit/skills/supabase-database-functions/SKILL.md +332 -332
- package/kit/skills/supabase-declarative-schema/SKILL.md +183 -183
- package/kit/skills/supabase-pgvector-rag/SKILL.md +253 -253
- package/kit/skills/supabase-postgres-style/SKILL.md +138 -138
- package/kit/skills/supabase-storage/SKILL.md +234 -234
- package/kit/skills/telemetry-pipelines/SKILL.md +259 -259
- package/kit/skills/telemetry-sampling/SKILL.md +256 -256
- package/kit/skills/ui-anti-padroes-ia/SKILL.md +261 -261
- package/kit/skills/ui-contexto-produto/SKILL.md +248 -248
- package/kit/skills/ui-cor-estrategia/SKILL.md +213 -213
- package/kit/skills/ui-critica-auditoria/SKILL.md +260 -260
- package/kit/skills/ui-motion-funcional/SKILL.md +264 -264
- package/kit/skills/ui-ritmo-espacial/SKILL.md +259 -259
- package/kit/skills/ui-tipografia/SKILL.md +211 -211
- package/package.json +1 -1
- package/src/cli/index.js +1114 -1114
- package/src/cli/render.js +194 -194
- package/src/cli/upgrade-check.js +135 -135
- package/src/core/error-redaction.js +76 -76
- package/src/core/failures.js +153 -153
- package/src/core/gate-runner.js +205 -205
- package/src/core/gates.js +82 -82
- package/src/core/logger.js +170 -170
- package/src/core/manifest-verify.js +174 -174
- package/src/core/metrics.js +268 -268
- package/src/core/notify.js +60 -60
- package/src/core/path-safety.js +141 -141
- package/src/core/replays.js +120 -120
- package/src/core/ui.js +185 -185
- package/src/mcp-server/install.js +149 -149
- package/src/mcp-server/roots.js +124 -124
- package/src/ui/auto-spawn.js +113 -113
- package/src/ui/browser.js +78 -78
- package/src/ui/client.js +130 -130
- package/src/ui/events.js +65 -65
- package/src/ui/lockfile.js +191 -191
- package/src/ui/port.js +67 -67
- package/src/ui/server.js +547 -547
- package/src/ui/wrapper.js +129 -129
|
@@ -1,332 +1,332 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: supabase-database-functions
|
|
3
|
-
description: Use ao criar funções Postgres — SECURITY INVOKER por padrão, SET search_path = '' SEMPRE, schema-qualified names, IMMUTABLE/STABLE quando possível.
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# Supabase — Database Functions
|
|
7
|
-
|
|
8
|
-
## Quando usar
|
|
9
|
-
|
|
10
|
-
LLM carrega esta skill quando criar ou auditar funções Postgres em projeto Supabase. Trigger phrases:
|
|
11
|
-
|
|
12
|
-
- "criar função Postgres", "create or replace function"
|
|
13
|
-
- "trigger de banco", "function trigger"
|
|
14
|
-
- "SECURITY INVOKER vs DEFINER"
|
|
15
|
-
- "search_path", "set search_path"
|
|
16
|
-
- "função imutável", "stable function"
|
|
17
|
-
|
|
18
|
-
## Regras absolutas
|
|
19
|
-
|
|
20
|
-
- **Sempre `SECURITY INVOKER`** por default — função roda com permissões de quem invoca (mais seguro). `SECURITY DEFINER` apenas com justificativa explícita escrita em comentário no topo da função.
|
|
21
|
-
- **Sempre `set search_path = ''`** — sem isso, função vulnerável a hijack de schema. Documentado em [Database Advisors lint 0011](https://supabase.com/docs/guides/database/database-advisors).
|
|
22
|
-
- **Schema-qualified** (em todas as referências a tabelas, colunas, outras funções): `public.tasks`, não `tasks`. Sem qualifier, lookup falha quando `search_path = ''`.
|
|
23
|
-
- Marque **`IMMUTABLE`** se função não consulta DB e sempre retorna o mesmo para os mesmos inputs (ex: formatadores de string).
|
|
24
|
-
- Marque **`STABLE`** se função consulta DB mas não modifica e retorna o mesmo dentro de uma transação (ex: lookups). Permite Postgres cachear o resultado por query.
|
|
25
|
-
- Use **`VOLATILE`** apenas se função modifica dados ou tem side effects (default — não precisa explicitar).
|
|
26
|
-
- Error handling com `RAISE EXCEPTION 'mensagem'` — nunca silent fail.
|
|
27
|
-
- Para triggers: include `CREATE TRIGGER` válido junto com `CREATE FUNCTION` na mesma migration.
|
|
28
|
-
|
|
29
|
-
## Patterns canônicos
|
|
30
|
-
|
|
31
|
-
### Função simples — SECURITY INVOKER + search_path
|
|
32
|
-
|
|
33
|
-
```sql
|
|
34
|
-
-- formatador puro: IMMUTABLE
|
|
35
|
-
create or replace function public.format_full_name(first_name text, last_name text)
|
|
36
|
-
returns text
|
|
37
|
-
language sql
|
|
38
|
-
security invoker
|
|
39
|
-
set search_path = ''
|
|
40
|
-
immutable
|
|
41
|
-
as $$
|
|
42
|
-
select first_name || ' ' || last_name;
|
|
43
|
-
$$;
|
|
44
|
-
```
|
|
45
|
-
|
|
46
|
-
### Função com query — STABLE + schema-qualified
|
|
47
|
-
|
|
48
|
-
```sql
|
|
49
|
-
-- conta tasks de um usuário (não modifica) — STABLE permite caching
|
|
50
|
-
create or replace function public.get_user_task_count(p_user_id uuid)
|
|
51
|
-
returns integer
|
|
52
|
-
language plpgsql
|
|
53
|
-
security invoker
|
|
54
|
-
set search_path = ''
|
|
55
|
-
stable
|
|
56
|
-
as $$
|
|
57
|
-
declare
|
|
58
|
-
v_count integer;
|
|
59
|
-
begin
|
|
60
|
-
select count(*) into v_count
|
|
61
|
-
from public.tasks -- schema-qualified obrigatório
|
|
62
|
-
where user_id = p_user_id;
|
|
63
|
-
return v_count;
|
|
64
|
-
end;
|
|
65
|
-
$$;
|
|
66
|
-
```
|
|
67
|
-
|
|
68
|
-
### Trigger — atualizar `updated_at`
|
|
69
|
-
|
|
70
|
-
```sql
|
|
71
|
-
-- function + trigger juntos na mesma migration
|
|
72
|
-
create or replace function public.set_updated_at()
|
|
73
|
-
returns trigger
|
|
74
|
-
language plpgsql
|
|
75
|
-
security invoker
|
|
76
|
-
set search_path = ''
|
|
77
|
-
as $$
|
|
78
|
-
begin
|
|
79
|
-
new.updated_at := now();
|
|
80
|
-
return new;
|
|
81
|
-
end;
|
|
82
|
-
$$;
|
|
83
|
-
|
|
84
|
-
create trigger tasks_set_updated_at
|
|
85
|
-
before update on public.tasks
|
|
86
|
-
for each row
|
|
87
|
-
execute function public.set_updated_at();
|
|
88
|
-
```
|
|
89
|
-
|
|
90
|
-
### Função com error handling
|
|
91
|
-
|
|
92
|
-
```sql
|
|
93
|
-
create or replace function public.transfer_credits(
|
|
94
|
-
p_from_user uuid,
|
|
95
|
-
p_to_user uuid,
|
|
96
|
-
p_amount integer
|
|
97
|
-
)
|
|
98
|
-
returns void
|
|
99
|
-
language plpgsql
|
|
100
|
-
security invoker
|
|
101
|
-
set search_path = ''
|
|
102
|
-
as $$
|
|
103
|
-
declare
|
|
104
|
-
v_from_balance integer;
|
|
105
|
-
begin
|
|
106
|
-
if p_amount <= 0 then
|
|
107
|
-
raise exception 'Valor de transferência deve ser positivo: %', p_amount;
|
|
108
|
-
end if;
|
|
109
|
-
|
|
110
|
-
select balance into v_from_balance
|
|
111
|
-
from public.accounts
|
|
112
|
-
where user_id = p_from_user
|
|
113
|
-
for update; -- lock para evitar race
|
|
114
|
-
|
|
115
|
-
if v_from_balance < p_amount then
|
|
116
|
-
raise exception 'Saldo insuficiente';
|
|
117
|
-
end if;
|
|
118
|
-
|
|
119
|
-
update public.accounts
|
|
120
|
-
set balance = balance - p_amount
|
|
121
|
-
where user_id = p_from_user;
|
|
122
|
-
|
|
123
|
-
update public.accounts
|
|
124
|
-
set balance = balance + p_amount
|
|
125
|
-
where user_id = p_to_user;
|
|
126
|
-
end;
|
|
127
|
-
$$;
|
|
128
|
-
```
|
|
129
|
-
|
|
130
|
-
### `SECURITY DEFINER` — quando justificável
|
|
131
|
-
|
|
132
|
-
```sql
|
|
133
|
-
-- caso raro: função precisa fazer algo que invoker não pode fazer
|
|
134
|
-
-- ex: contar todos os usuários (acessível só para admins via app_metadata)
|
|
135
|
-
-- mas exposto via RPC para qualquer authenticated com auth check interno
|
|
136
|
-
|
|
137
|
-
-- comentário JUSTIFICANDO o DEFINER (obrigatório)
|
|
138
|
-
create or replace function public.count_active_users()
|
|
139
|
-
returns integer
|
|
140
|
-
-- security definer porque: precisamos bypassar RLS de auth.users que bloqueia leitura
|
|
141
|
-
-- mitigação: validamos role admin via app_metadata logo no topo
|
|
142
|
-
language plpgsql
|
|
143
|
-
security definer
|
|
144
|
-
set search_path = ''
|
|
145
|
-
stable
|
|
146
|
-
as $$
|
|
147
|
-
declare
|
|
148
|
-
v_count integer;
|
|
149
|
-
begin
|
|
150
|
-
-- validar admin via app_metadata (não user_metadata!)
|
|
151
|
-
if (auth.jwt()->'app_metadata'->>'role') is distinct from 'admin' then
|
|
152
|
-
raise exception 'Acesso negado: apenas admins';
|
|
153
|
-
end if;
|
|
154
|
-
|
|
155
|
-
select count(*) into v_count
|
|
156
|
-
from public.users
|
|
157
|
-
where last_seen_at > now() - interval '30 days';
|
|
158
|
-
return v_count;
|
|
159
|
-
end;
|
|
160
|
-
$$;
|
|
161
|
-
```
|
|
162
|
-
|
|
163
|
-
### GRANT EXECUTE por role hierarchy (v1.26)
|
|
164
|
-
|
|
165
|
-
Quando você cria função PG que será chamada por múltiplos service accounts, use role hierarchy + GRANT EXECUTE para gerenciar permissions:
|
|
166
|
-
|
|
167
|
-
```sql
|
|
168
|
-
-- group role para read-only services
|
|
169
|
-
create role "readers_group" noinherit;
|
|
170
|
-
|
|
171
|
-
-- service accounts individuais inheritam de readers_group
|
|
172
|
-
create role "metabase_reader" with login password '<secret>';
|
|
173
|
-
grant readers_group to metabase_reader;
|
|
174
|
-
|
|
175
|
-
create role "analytics_reader" with login password '<secret>';
|
|
176
|
-
grant readers_group to analytics_reader;
|
|
177
|
-
|
|
178
|
-
-- conceder EXECUTE em função canônica para o group
|
|
179
|
-
grant execute on function public.get_org_metrics(uuid) to readers_group;
|
|
180
|
-
-- agora metabase_reader e analytics_reader podem executar via inheritance
|
|
181
|
-
```
|
|
182
|
-
|
|
183
|
-
**Pattern com schema privado:**
|
|
184
|
-
|
|
185
|
-
```sql
|
|
186
|
-
-- função sensitive em schema private (não exposta via API)
|
|
187
|
-
create function private.expensive_aggregation(org_id uuid)
|
|
188
|
-
returns table(metric text, value bigint)
|
|
189
|
-
language plpgsql security definer set search_path = ''
|
|
190
|
-
as $$ ... $$;
|
|
191
|
-
|
|
192
|
-
-- revoke default
|
|
193
|
-
revoke execute on function private.expensive_aggregation(uuid) from public;
|
|
194
|
-
|
|
195
|
-
-- conceder apenas para custom role
|
|
196
|
-
grant execute on function private.expensive_aggregation(uuid) to readers_group;
|
|
197
|
-
```
|
|
198
|
-
|
|
199
|
-
Cross-ref completo de Postgres roles em [`supabase-postgres-roles`](../supabase-postgres-roles/SKILL.md) (v1.26).
|
|
200
|
-
|
|
201
|
-
### Pattern Custom Access Token Auth Hook (v1.25)
|
|
202
|
-
|
|
203
|
-
Functions invocadas como **Auth Hooks** (ex: Custom Access Token) precisam permissions específicos para `supabase_auth_admin` role + REVOKE de roles públicos. Pattern canônico:
|
|
204
|
-
|
|
205
|
-
```sql
|
|
206
|
-
-- 1. função hook (stable, language plpgsql, modifica claims do JWT)
|
|
207
|
-
create or replace function public.custom_access_token_hook(event jsonb)
|
|
208
|
-
returns jsonb
|
|
209
|
-
language plpgsql
|
|
210
|
-
stable
|
|
211
|
-
as $$
|
|
212
|
-
declare
|
|
213
|
-
claims jsonb;
|
|
214
|
-
user_role public.app_role;
|
|
215
|
-
begin
|
|
216
|
-
select role into user_role from public.user_roles
|
|
217
|
-
where user_id = (event->>'user_id')::uuid;
|
|
218
|
-
|
|
219
|
-
claims := event->'claims';
|
|
220
|
-
if user_role is not null then
|
|
221
|
-
claims := jsonb_set(claims, '{user_role}', to_jsonb(user_role));
|
|
222
|
-
else
|
|
223
|
-
claims := jsonb_set(claims, '{user_role}', 'null');
|
|
224
|
-
end if;
|
|
225
|
-
event := jsonb_set(event, '{claims}', claims);
|
|
226
|
-
return event;
|
|
227
|
-
end;
|
|
228
|
-
$$;
|
|
229
|
-
|
|
230
|
-
-- 2. permissions canônicos para supabase_auth_admin (6 GRANTs/REVOKEs)
|
|
231
|
-
grant usage on schema public to supabase_auth_admin;
|
|
232
|
-
grant execute on function public.custom_access_token_hook to supabase_auth_admin;
|
|
233
|
-
revoke execute on function public.custom_access_token_hook from authenticated, anon, public;
|
|
234
|
-
grant all on table public.user_roles to supabase_auth_admin;
|
|
235
|
-
revoke all on table public.user_roles from authenticated, anon, public;
|
|
236
|
-
|
|
237
|
-
create policy "Allow auth admin to read user roles" on public.user_roles
|
|
238
|
-
as permissive for select to supabase_auth_admin using (true);
|
|
239
|
-
```
|
|
240
|
-
|
|
241
|
-
**Decisões canônicas:**
|
|
242
|
-
- `stable` (não `volatile`) — hook não modifica DB, apenas lê user_roles
|
|
243
|
-
- **NÃO** usa `security definer` — hook roda com privilégios do `supabase_auth_admin` (que é o caller); GRANT EXECUTE necessário
|
|
244
|
-
- **REVOKE FROM authenticated/anon/public** — sem isso, qualquer cliente pode chamar o hook diretamente (abuse)
|
|
245
|
-
|
|
246
|
-
Padrão completo (RBAC end-to-end) em [`supabase-custom-claims-rbac`](../supabase-custom-claims-rbac/SKILL.md) (v1.25).
|
|
247
|
-
|
|
248
|
-
## Anti-patterns
|
|
249
|
-
|
|
250
|
-
### Anti-pattern 1: `SECURITY DEFINER` + sem `set search_path` + sem schema qualifier
|
|
251
|
-
|
|
252
|
-
**Errado:**
|
|
253
|
-
```sql
|
|
254
|
-
create or replace function f()
|
|
255
|
-
returns integer
|
|
256
|
-
language plpgsql
|
|
257
|
-
security definer -- ⚠ sem justificativa
|
|
258
|
-
as $$ -- ⚠ sem set search_path
|
|
259
|
-
begin
|
|
260
|
-
return (select count(*) from tasks); -- ⚠ sem public. qualifier
|
|
261
|
-
end;
|
|
262
|
-
$$;
|
|
263
|
-
```
|
|
264
|
-
|
|
265
|
-
**Por quê:** atacante pode criar `tasks` em schema próprio + manipular `search_path` via `set local search_path = atacante,public` antes de invocar. Função `SECURITY DEFINER` executa com permissões do owner — atacante consegue ler/escrever onde não deveria.
|
|
266
|
-
|
|
267
|
-
**Certo:**
|
|
268
|
-
```sql
|
|
269
|
-
create or replace function public.f()
|
|
270
|
-
returns integer
|
|
271
|
-
language plpgsql
|
|
272
|
-
security invoker -- prefira invoker
|
|
273
|
-
set search_path = '' -- bloqueia hijack
|
|
274
|
-
stable
|
|
275
|
-
as $$
|
|
276
|
-
begin
|
|
277
|
-
return (select count(*) from public.tasks); -- qualified
|
|
278
|
-
end;
|
|
279
|
-
$$;
|
|
280
|
-
```
|
|
281
|
-
|
|
282
|
-
### Anti-pattern 2: Função consulta DB mas marcada `IMMUTABLE`
|
|
283
|
-
|
|
284
|
-
**Errado:**
|
|
285
|
-
```sql
|
|
286
|
-
create or replace function public.user_count_immutable()
|
|
287
|
-
returns integer
|
|
288
|
-
language sql
|
|
289
|
-
immutable -- ⚠ função consulta DB — não imutável
|
|
290
|
-
set search_path = ''
|
|
291
|
-
as $$
|
|
292
|
-
select count(*) from public.users;
|
|
293
|
-
$$;
|
|
294
|
-
```
|
|
295
|
-
|
|
296
|
-
**Por quê:** `IMMUTABLE` diz para Postgres "este resultado nunca muda para os mesmos inputs". Postgres pode cachear ou pré-computar. Mas a contagem de usuários muda — Postgres pode retornar valor stale indefinidamente.
|
|
297
|
-
|
|
298
|
-
**Certo:** usar `stable` (consulta DB, não modifica, mesmo em uma transação) ou `volatile` (default — recompute sempre).
|
|
299
|
-
|
|
300
|
-
### Anti-pattern 3: Silent fail sem `raise exception`
|
|
301
|
-
|
|
302
|
-
**Errado:**
|
|
303
|
-
```sql
|
|
304
|
-
create or replace function public.deduct_credits(p_user uuid, p_amount integer)
|
|
305
|
-
returns void
|
|
306
|
-
language plpgsql
|
|
307
|
-
security invoker
|
|
308
|
-
set search_path = ''
|
|
309
|
-
as $$
|
|
310
|
-
begin
|
|
311
|
-
-- ⚠ sem validação — atualiza mesmo com saldo negativo
|
|
312
|
-
update public.accounts
|
|
313
|
-
set balance = balance - p_amount
|
|
314
|
-
where user_id = p_user;
|
|
315
|
-
end;
|
|
316
|
-
$$;
|
|
317
|
-
```
|
|
318
|
-
|
|
319
|
-
**Por quê:** silent fail oculta bugs. Saldo fica negativo sem aviso; testes downstream falham com mensagens enigmáticas.
|
|
320
|
-
|
|
321
|
-
**Certo:**
|
|
322
|
-
```sql
|
|
323
|
-
-- valida + raise exception se inválido (ver pattern "transfer_credits" acima)
|
|
324
|
-
```
|
|
325
|
-
|
|
326
|
-
## Ver também
|
|
327
|
-
|
|
328
|
-
- [supabase-postgres-style](../supabase-postgres-style/SKILL.md) — convenção de naming + style aplicada em funções
|
|
329
|
-
- [supabase-rls-policies](../supabase-rls-policies/SKILL.md) — funções e RLS interagem (SECURITY INVOKER respeita RLS do invoker)
|
|
330
|
-
- [supabase-migrations](../supabase-migrations/SKILL.md) — funções em migrations são versionadas
|
|
331
|
-
- [supabase-cron-queues](../supabase-cron-queues/SKILL.md) — funções invocadas por `pg_cron` jobs
|
|
332
|
-
- [glossário](../_shared-supabase/glossary.md) — termos PT-BR↔EN + comandos CLI
|
|
1
|
+
---
|
|
2
|
+
name: supabase-database-functions
|
|
3
|
+
description: Use ao criar funções Postgres — SECURITY INVOKER por padrão, SET search_path = '' SEMPRE, schema-qualified names, IMMUTABLE/STABLE quando possível.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Supabase — Database Functions
|
|
7
|
+
|
|
8
|
+
## Quando usar
|
|
9
|
+
|
|
10
|
+
LLM carrega esta skill quando criar ou auditar funções Postgres em projeto Supabase. Trigger phrases:
|
|
11
|
+
|
|
12
|
+
- "criar função Postgres", "create or replace function"
|
|
13
|
+
- "trigger de banco", "function trigger"
|
|
14
|
+
- "SECURITY INVOKER vs DEFINER"
|
|
15
|
+
- "search_path", "set search_path"
|
|
16
|
+
- "função imutável", "stable function"
|
|
17
|
+
|
|
18
|
+
## Regras absolutas
|
|
19
|
+
|
|
20
|
+
- **Sempre `SECURITY INVOKER`** por default — função roda com permissões de quem invoca (mais seguro). `SECURITY DEFINER` apenas com justificativa explícita escrita em comentário no topo da função.
|
|
21
|
+
- **Sempre `set search_path = ''`** — sem isso, função vulnerável a hijack de schema. Documentado em [Database Advisors lint 0011](https://supabase.com/docs/guides/database/database-advisors).
|
|
22
|
+
- **Schema-qualified** (em todas as referências a tabelas, colunas, outras funções): `public.tasks`, não `tasks`. Sem qualifier, lookup falha quando `search_path = ''`.
|
|
23
|
+
- Marque **`IMMUTABLE`** se função não consulta DB e sempre retorna o mesmo para os mesmos inputs (ex: formatadores de string).
|
|
24
|
+
- Marque **`STABLE`** se função consulta DB mas não modifica e retorna o mesmo dentro de uma transação (ex: lookups). Permite Postgres cachear o resultado por query.
|
|
25
|
+
- Use **`VOLATILE`** apenas se função modifica dados ou tem side effects (default — não precisa explicitar).
|
|
26
|
+
- Error handling com `RAISE EXCEPTION 'mensagem'` — nunca silent fail.
|
|
27
|
+
- Para triggers: include `CREATE TRIGGER` válido junto com `CREATE FUNCTION` na mesma migration.
|
|
28
|
+
|
|
29
|
+
## Patterns canônicos
|
|
30
|
+
|
|
31
|
+
### Função simples — SECURITY INVOKER + search_path
|
|
32
|
+
|
|
33
|
+
```sql
|
|
34
|
+
-- formatador puro: IMMUTABLE
|
|
35
|
+
create or replace function public.format_full_name(first_name text, last_name text)
|
|
36
|
+
returns text
|
|
37
|
+
language sql
|
|
38
|
+
security invoker
|
|
39
|
+
set search_path = ''
|
|
40
|
+
immutable
|
|
41
|
+
as $$
|
|
42
|
+
select first_name || ' ' || last_name;
|
|
43
|
+
$$;
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### Função com query — STABLE + schema-qualified
|
|
47
|
+
|
|
48
|
+
```sql
|
|
49
|
+
-- conta tasks de um usuário (não modifica) — STABLE permite caching
|
|
50
|
+
create or replace function public.get_user_task_count(p_user_id uuid)
|
|
51
|
+
returns integer
|
|
52
|
+
language plpgsql
|
|
53
|
+
security invoker
|
|
54
|
+
set search_path = ''
|
|
55
|
+
stable
|
|
56
|
+
as $$
|
|
57
|
+
declare
|
|
58
|
+
v_count integer;
|
|
59
|
+
begin
|
|
60
|
+
select count(*) into v_count
|
|
61
|
+
from public.tasks -- schema-qualified obrigatório
|
|
62
|
+
where user_id = p_user_id;
|
|
63
|
+
return v_count;
|
|
64
|
+
end;
|
|
65
|
+
$$;
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### Trigger — atualizar `updated_at`
|
|
69
|
+
|
|
70
|
+
```sql
|
|
71
|
+
-- function + trigger juntos na mesma migration
|
|
72
|
+
create or replace function public.set_updated_at()
|
|
73
|
+
returns trigger
|
|
74
|
+
language plpgsql
|
|
75
|
+
security invoker
|
|
76
|
+
set search_path = ''
|
|
77
|
+
as $$
|
|
78
|
+
begin
|
|
79
|
+
new.updated_at := now();
|
|
80
|
+
return new;
|
|
81
|
+
end;
|
|
82
|
+
$$;
|
|
83
|
+
|
|
84
|
+
create trigger tasks_set_updated_at
|
|
85
|
+
before update on public.tasks
|
|
86
|
+
for each row
|
|
87
|
+
execute function public.set_updated_at();
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### Função com error handling
|
|
91
|
+
|
|
92
|
+
```sql
|
|
93
|
+
create or replace function public.transfer_credits(
|
|
94
|
+
p_from_user uuid,
|
|
95
|
+
p_to_user uuid,
|
|
96
|
+
p_amount integer
|
|
97
|
+
)
|
|
98
|
+
returns void
|
|
99
|
+
language plpgsql
|
|
100
|
+
security invoker
|
|
101
|
+
set search_path = ''
|
|
102
|
+
as $$
|
|
103
|
+
declare
|
|
104
|
+
v_from_balance integer;
|
|
105
|
+
begin
|
|
106
|
+
if p_amount <= 0 then
|
|
107
|
+
raise exception 'Valor de transferência deve ser positivo: %', p_amount;
|
|
108
|
+
end if;
|
|
109
|
+
|
|
110
|
+
select balance into v_from_balance
|
|
111
|
+
from public.accounts
|
|
112
|
+
where user_id = p_from_user
|
|
113
|
+
for update; -- lock para evitar race
|
|
114
|
+
|
|
115
|
+
if v_from_balance < p_amount then
|
|
116
|
+
raise exception 'Saldo insuficiente';
|
|
117
|
+
end if;
|
|
118
|
+
|
|
119
|
+
update public.accounts
|
|
120
|
+
set balance = balance - p_amount
|
|
121
|
+
where user_id = p_from_user;
|
|
122
|
+
|
|
123
|
+
update public.accounts
|
|
124
|
+
set balance = balance + p_amount
|
|
125
|
+
where user_id = p_to_user;
|
|
126
|
+
end;
|
|
127
|
+
$$;
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
### `SECURITY DEFINER` — quando justificável
|
|
131
|
+
|
|
132
|
+
```sql
|
|
133
|
+
-- caso raro: função precisa fazer algo que invoker não pode fazer
|
|
134
|
+
-- ex: contar todos os usuários (acessível só para admins via app_metadata)
|
|
135
|
+
-- mas exposto via RPC para qualquer authenticated com auth check interno
|
|
136
|
+
|
|
137
|
+
-- comentário JUSTIFICANDO o DEFINER (obrigatório)
|
|
138
|
+
create or replace function public.count_active_users()
|
|
139
|
+
returns integer
|
|
140
|
+
-- security definer porque: precisamos bypassar RLS de auth.users que bloqueia leitura
|
|
141
|
+
-- mitigação: validamos role admin via app_metadata logo no topo
|
|
142
|
+
language plpgsql
|
|
143
|
+
security definer
|
|
144
|
+
set search_path = ''
|
|
145
|
+
stable
|
|
146
|
+
as $$
|
|
147
|
+
declare
|
|
148
|
+
v_count integer;
|
|
149
|
+
begin
|
|
150
|
+
-- validar admin via app_metadata (não user_metadata!)
|
|
151
|
+
if (auth.jwt()->'app_metadata'->>'role') is distinct from 'admin' then
|
|
152
|
+
raise exception 'Acesso negado: apenas admins';
|
|
153
|
+
end if;
|
|
154
|
+
|
|
155
|
+
select count(*) into v_count
|
|
156
|
+
from public.users
|
|
157
|
+
where last_seen_at > now() - interval '30 days';
|
|
158
|
+
return v_count;
|
|
159
|
+
end;
|
|
160
|
+
$$;
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
### GRANT EXECUTE por role hierarchy (v1.26)
|
|
164
|
+
|
|
165
|
+
Quando você cria função PG que será chamada por múltiplos service accounts, use role hierarchy + GRANT EXECUTE para gerenciar permissions:
|
|
166
|
+
|
|
167
|
+
```sql
|
|
168
|
+
-- group role para read-only services
|
|
169
|
+
create role "readers_group" noinherit;
|
|
170
|
+
|
|
171
|
+
-- service accounts individuais inheritam de readers_group
|
|
172
|
+
create role "metabase_reader" with login password '<secret>';
|
|
173
|
+
grant readers_group to metabase_reader;
|
|
174
|
+
|
|
175
|
+
create role "analytics_reader" with login password '<secret>';
|
|
176
|
+
grant readers_group to analytics_reader;
|
|
177
|
+
|
|
178
|
+
-- conceder EXECUTE em função canônica para o group
|
|
179
|
+
grant execute on function public.get_org_metrics(uuid) to readers_group;
|
|
180
|
+
-- agora metabase_reader e analytics_reader podem executar via inheritance
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
**Pattern com schema privado:**
|
|
184
|
+
|
|
185
|
+
```sql
|
|
186
|
+
-- função sensitive em schema private (não exposta via API)
|
|
187
|
+
create function private.expensive_aggregation(org_id uuid)
|
|
188
|
+
returns table(metric text, value bigint)
|
|
189
|
+
language plpgsql security definer set search_path = ''
|
|
190
|
+
as $$ ... $$;
|
|
191
|
+
|
|
192
|
+
-- revoke default
|
|
193
|
+
revoke execute on function private.expensive_aggregation(uuid) from public;
|
|
194
|
+
|
|
195
|
+
-- conceder apenas para custom role
|
|
196
|
+
grant execute on function private.expensive_aggregation(uuid) to readers_group;
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
Cross-ref completo de Postgres roles em [`supabase-postgres-roles`](../supabase-postgres-roles/SKILL.md) (v1.26).
|
|
200
|
+
|
|
201
|
+
### Pattern Custom Access Token Auth Hook (v1.25)
|
|
202
|
+
|
|
203
|
+
Functions invocadas como **Auth Hooks** (ex: Custom Access Token) precisam permissions específicos para `supabase_auth_admin` role + REVOKE de roles públicos. Pattern canônico:
|
|
204
|
+
|
|
205
|
+
```sql
|
|
206
|
+
-- 1. função hook (stable, language plpgsql, modifica claims do JWT)
|
|
207
|
+
create or replace function public.custom_access_token_hook(event jsonb)
|
|
208
|
+
returns jsonb
|
|
209
|
+
language plpgsql
|
|
210
|
+
stable
|
|
211
|
+
as $$
|
|
212
|
+
declare
|
|
213
|
+
claims jsonb;
|
|
214
|
+
user_role public.app_role;
|
|
215
|
+
begin
|
|
216
|
+
select role into user_role from public.user_roles
|
|
217
|
+
where user_id = (event->>'user_id')::uuid;
|
|
218
|
+
|
|
219
|
+
claims := event->'claims';
|
|
220
|
+
if user_role is not null then
|
|
221
|
+
claims := jsonb_set(claims, '{user_role}', to_jsonb(user_role));
|
|
222
|
+
else
|
|
223
|
+
claims := jsonb_set(claims, '{user_role}', 'null');
|
|
224
|
+
end if;
|
|
225
|
+
event := jsonb_set(event, '{claims}', claims);
|
|
226
|
+
return event;
|
|
227
|
+
end;
|
|
228
|
+
$$;
|
|
229
|
+
|
|
230
|
+
-- 2. permissions canônicos para supabase_auth_admin (6 GRANTs/REVOKEs)
|
|
231
|
+
grant usage on schema public to supabase_auth_admin;
|
|
232
|
+
grant execute on function public.custom_access_token_hook to supabase_auth_admin;
|
|
233
|
+
revoke execute on function public.custom_access_token_hook from authenticated, anon, public;
|
|
234
|
+
grant all on table public.user_roles to supabase_auth_admin;
|
|
235
|
+
revoke all on table public.user_roles from authenticated, anon, public;
|
|
236
|
+
|
|
237
|
+
create policy "Allow auth admin to read user roles" on public.user_roles
|
|
238
|
+
as permissive for select to supabase_auth_admin using (true);
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
**Decisões canônicas:**
|
|
242
|
+
- `stable` (não `volatile`) — hook não modifica DB, apenas lê user_roles
|
|
243
|
+
- **NÃO** usa `security definer` — hook roda com privilégios do `supabase_auth_admin` (que é o caller); GRANT EXECUTE necessário
|
|
244
|
+
- **REVOKE FROM authenticated/anon/public** — sem isso, qualquer cliente pode chamar o hook diretamente (abuse)
|
|
245
|
+
|
|
246
|
+
Padrão completo (RBAC end-to-end) em [`supabase-custom-claims-rbac`](../supabase-custom-claims-rbac/SKILL.md) (v1.25).
|
|
247
|
+
|
|
248
|
+
## Anti-patterns
|
|
249
|
+
|
|
250
|
+
### Anti-pattern 1: `SECURITY DEFINER` + sem `set search_path` + sem schema qualifier
|
|
251
|
+
|
|
252
|
+
**Errado:**
|
|
253
|
+
```sql
|
|
254
|
+
create or replace function f()
|
|
255
|
+
returns integer
|
|
256
|
+
language plpgsql
|
|
257
|
+
security definer -- ⚠ sem justificativa
|
|
258
|
+
as $$ -- ⚠ sem set search_path
|
|
259
|
+
begin
|
|
260
|
+
return (select count(*) from tasks); -- ⚠ sem public. qualifier
|
|
261
|
+
end;
|
|
262
|
+
$$;
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
**Por quê:** atacante pode criar `tasks` em schema próprio + manipular `search_path` via `set local search_path = atacante,public` antes de invocar. Função `SECURITY DEFINER` executa com permissões do owner — atacante consegue ler/escrever onde não deveria.
|
|
266
|
+
|
|
267
|
+
**Certo:**
|
|
268
|
+
```sql
|
|
269
|
+
create or replace function public.f()
|
|
270
|
+
returns integer
|
|
271
|
+
language plpgsql
|
|
272
|
+
security invoker -- prefira invoker
|
|
273
|
+
set search_path = '' -- bloqueia hijack
|
|
274
|
+
stable
|
|
275
|
+
as $$
|
|
276
|
+
begin
|
|
277
|
+
return (select count(*) from public.tasks); -- qualified
|
|
278
|
+
end;
|
|
279
|
+
$$;
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
### Anti-pattern 2: Função consulta DB mas marcada `IMMUTABLE`
|
|
283
|
+
|
|
284
|
+
**Errado:**
|
|
285
|
+
```sql
|
|
286
|
+
create or replace function public.user_count_immutable()
|
|
287
|
+
returns integer
|
|
288
|
+
language sql
|
|
289
|
+
immutable -- ⚠ função consulta DB — não imutável
|
|
290
|
+
set search_path = ''
|
|
291
|
+
as $$
|
|
292
|
+
select count(*) from public.users;
|
|
293
|
+
$$;
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
**Por quê:** `IMMUTABLE` diz para Postgres "este resultado nunca muda para os mesmos inputs". Postgres pode cachear ou pré-computar. Mas a contagem de usuários muda — Postgres pode retornar valor stale indefinidamente.
|
|
297
|
+
|
|
298
|
+
**Certo:** usar `stable` (consulta DB, não modifica, mesmo em uma transação) ou `volatile` (default — recompute sempre).
|
|
299
|
+
|
|
300
|
+
### Anti-pattern 3: Silent fail sem `raise exception`
|
|
301
|
+
|
|
302
|
+
**Errado:**
|
|
303
|
+
```sql
|
|
304
|
+
create or replace function public.deduct_credits(p_user uuid, p_amount integer)
|
|
305
|
+
returns void
|
|
306
|
+
language plpgsql
|
|
307
|
+
security invoker
|
|
308
|
+
set search_path = ''
|
|
309
|
+
as $$
|
|
310
|
+
begin
|
|
311
|
+
-- ⚠ sem validação — atualiza mesmo com saldo negativo
|
|
312
|
+
update public.accounts
|
|
313
|
+
set balance = balance - p_amount
|
|
314
|
+
where user_id = p_user;
|
|
315
|
+
end;
|
|
316
|
+
$$;
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
**Por quê:** silent fail oculta bugs. Saldo fica negativo sem aviso; testes downstream falham com mensagens enigmáticas.
|
|
320
|
+
|
|
321
|
+
**Certo:**
|
|
322
|
+
```sql
|
|
323
|
+
-- valida + raise exception se inválido (ver pattern "transfer_credits" acima)
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
## Ver também
|
|
327
|
+
|
|
328
|
+
- [supabase-postgres-style](../supabase-postgres-style/SKILL.md) — convenção de naming + style aplicada em funções
|
|
329
|
+
- [supabase-rls-policies](../supabase-rls-policies/SKILL.md) — funções e RLS interagem (SECURITY INVOKER respeita RLS do invoker)
|
|
330
|
+
- [supabase-migrations](../supabase-migrations/SKILL.md) — funções em migrations são versionadas
|
|
331
|
+
- [supabase-cron-queues](../supabase-cron-queues/SKILL.md) — funções invocadas por `pg_cron` jobs
|
|
332
|
+
- [glossário](../_shared-supabase/glossary.md) — termos PT-BR↔EN + comandos CLI
|