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