@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.
Files changed (117) hide show
  1. package/bin/cli.js +2 -2
  2. package/bin/mcp.js +6 -6
  3. package/bin/ui.js +74 -74
  4. package/gates/ai-prompt-stability.md +120 -120
  5. package/gates/budget-description.md +68 -68
  6. package/gates/confidence.md +29 -29
  7. package/gates/dependency-check.md +33 -33
  8. package/gates/dept-cycle-prevention.md +179 -179
  9. package/gates/golden-signals-coverage.md +133 -133
  10. package/gates/legacy-refactor-safety.md +178 -178
  11. package/gates/multi-tenant-rls-coverage.md +102 -102
  12. package/gates/no-personal-uuid.md +72 -72
  13. package/gates/obs-agents-mcp-supabase.md +86 -86
  14. package/gates/obs-skills-frontmatter.md +76 -76
  15. package/gates/observability-coverage.md +151 -151
  16. package/gates/omm-no-regression.md +83 -83
  17. package/gates/postmortem-template-required.md +127 -127
  18. package/gates/prr-checklist-coverage.md +128 -128
  19. package/gates/regression.md +32 -32
  20. package/gates/release-pipeline-policy.md +132 -132
  21. package/gates/secrets-scan.md +33 -33
  22. package/gates/service-role-not-in-user-facing.md +113 -113
  23. package/gates/skill-must-include.md +71 -71
  24. package/gates/sync-idempotent.md +62 -62
  25. package/gates/verify-phase-goal.md +34 -34
  26. package/kit/agents/designer-ui.md +216 -216
  27. package/kit/agents/workflow-generator.md +537 -167
  28. package/kit/commands/adicionar-backlog.md +1 -1
  29. package/kit/commands/adicionar-fase.md +1 -1
  30. package/kit/commands/adicionar-tarefa.md +1 -1
  31. package/kit/commands/auditar-observabilidade.md +103 -103
  32. package/kit/commands/auditar-toil.md +129 -129
  33. package/kit/commands/caracterizar-prompt.md +195 -195
  34. package/kit/commands/criar-workflow.md +158 -158
  35. package/kit/commands/definir-perfil.md +1 -1
  36. package/kit/commands/definir-slo.md +108 -108
  37. package/kit/commands/fio.md +1 -1
  38. package/kit/commands/golden-signals.md +142 -142
  39. package/kit/commands/instrumentar-fase.md +200 -200
  40. package/kit/commands/investigar-producao.md +162 -162
  41. package/kit/commands/observabilidade.md +118 -118
  42. package/kit/commands/postmortem.md +179 -179
  43. package/kit/commands/prr.md +205 -205
  44. package/kit/commands/publicar-rapido.md +207 -207
  45. package/kit/commands/risk-budget.md +220 -220
  46. package/kit/commands/sre.md +230 -230
  47. package/kit/file-manifest.json +424 -424
  48. package/kit/framework/references/output-style.md +22 -22
  49. package/kit/hooks/post-apply-migration.js +199 -199
  50. package/kit/hooks/sidecar-tool-publisher.js +210 -210
  51. package/kit/skills/_shared-dados-distribuidos/glossary.md +224 -224
  52. package/kit/skills/_shared-legacy/glossary.md +389 -389
  53. package/kit/skills/_shared-multi-tenant/glossary.md +186 -186
  54. package/kit/skills/_shared-observability/glossary.md +396 -396
  55. package/kit/skills/_shared-sre/glossary.md +712 -712
  56. package/kit/skills/_shared-supabase/glossary.md +234 -234
  57. package/kit/skills/blameless-postmortems/SKILL.md +340 -340
  58. package/kit/skills/burn-rate-alerting/SKILL.md +258 -258
  59. package/kit/skills/cascading-failures/SKILL.md +311 -311
  60. package/kit/skills/core-analysis-loop/SKILL.md +352 -352
  61. package/kit/skills/distributed-tracing/SKILL.md +362 -362
  62. package/kit/skills/dynamic-workflow-authoring/SKILL.md +327 -223
  63. package/kit/skills/eliminating-toil/SKILL.md +243 -243
  64. package/kit/skills/event-based-slos/SKILL.md +296 -296
  65. package/kit/skills/four-golden-signals/SKILL.md +314 -314
  66. package/kit/skills/hermetic-builds/SKILL.md +323 -323
  67. package/kit/skills/legacy-monster-methods/SKILL.md +444 -444
  68. package/kit/skills/llm-as-dependency/SKILL.md +436 -436
  69. package/kit/skills/load-shedding-graceful-degradation/SKILL.md +396 -396
  70. package/kit/skills/observability-driven-development/SKILL.md +315 -315
  71. package/kit/skills/observability-maturity-model/SKILL.md +222 -222
  72. package/kit/skills/opentelemetry-standard/SKILL.md +351 -351
  73. package/kit/skills/production-readiness-review/SKILL.md +305 -305
  74. package/kit/skills/release-engineering/SKILL.md +367 -367
  75. package/kit/skills/retry-strategies/SKILL.md +372 -372
  76. package/kit/skills/sre-risk-management/SKILL.md +221 -221
  77. package/kit/skills/structured-events/SKILL.md +265 -265
  78. package/kit/skills/supabase-cron-queues/SKILL.md +275 -275
  79. package/kit/skills/supabase-database-functions/SKILL.md +332 -332
  80. package/kit/skills/supabase-declarative-schema/SKILL.md +183 -183
  81. package/kit/skills/supabase-pgvector-rag/SKILL.md +253 -253
  82. package/kit/skills/supabase-postgres-style/SKILL.md +138 -138
  83. package/kit/skills/supabase-storage/SKILL.md +234 -234
  84. package/kit/skills/telemetry-pipelines/SKILL.md +259 -259
  85. package/kit/skills/telemetry-sampling/SKILL.md +256 -256
  86. package/kit/skills/ui-anti-padroes-ia/SKILL.md +261 -261
  87. package/kit/skills/ui-contexto-produto/SKILL.md +248 -248
  88. package/kit/skills/ui-cor-estrategia/SKILL.md +213 -213
  89. package/kit/skills/ui-critica-auditoria/SKILL.md +260 -260
  90. package/kit/skills/ui-motion-funcional/SKILL.md +264 -264
  91. package/kit/skills/ui-ritmo-espacial/SKILL.md +259 -259
  92. package/kit/skills/ui-tipografia/SKILL.md +211 -211
  93. package/package.json +1 -1
  94. package/src/cli/index.js +1114 -1114
  95. package/src/cli/render.js +194 -194
  96. package/src/cli/upgrade-check.js +135 -135
  97. package/src/core/error-redaction.js +76 -76
  98. package/src/core/failures.js +153 -153
  99. package/src/core/gate-runner.js +205 -205
  100. package/src/core/gates.js +82 -82
  101. package/src/core/logger.js +170 -170
  102. package/src/core/manifest-verify.js +174 -174
  103. package/src/core/metrics.js +268 -268
  104. package/src/core/notify.js +60 -60
  105. package/src/core/path-safety.js +141 -141
  106. package/src/core/replays.js +120 -120
  107. package/src/core/ui.js +185 -185
  108. package/src/mcp-server/install.js +149 -149
  109. package/src/mcp-server/roots.js +124 -124
  110. package/src/ui/auto-spawn.js +113 -113
  111. package/src/ui/browser.js +78 -78
  112. package/src/ui/client.js +130 -130
  113. package/src/ui/events.js +65 -65
  114. package/src/ui/lockfile.js +191 -191
  115. package/src/ui/port.js +67 -67
  116. package/src/ui/server.js +547 -547
  117. 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