@luanpdd/kit-mcp 1.30.2 → 1.32.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 (365) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +168 -168
  3. package/gates/agent-no-recursive-dispatch.md +84 -82
  4. package/kit/COMANDOS.md +138 -138
  5. package/kit/COMPATIBILITY.md +5 -0
  6. package/kit/README.md +76 -76
  7. package/kit/agents/advisor-researcher.md +107 -106
  8. package/kit/agents/ai-mutation-tester.md +1 -0
  9. package/kit/agents/assumptions-analyzer.md +108 -107
  10. package/kit/agents/audit-log-implementer.md +314 -313
  11. package/kit/agents/auditor-consistencia-isolamento.md +414 -413
  12. package/kit/agents/b2b-saas-architect.md +157 -156
  13. package/kit/agents/burn-rate-forecaster.md +1 -0
  14. package/kit/agents/cascading-failures-auditor.md +299 -298
  15. package/kit/agents/codebase-mapper.md +769 -768
  16. package/kit/agents/crm-pipeline-implementer.md +257 -256
  17. package/kit/agents/debugger.md +814 -813
  18. package/kit/agents/detector-tenant-quente.md +338 -337
  19. package/kit/agents/evolution-go-integrator.md +201 -200
  20. package/kit/agents/example-reviewer.md +22 -21
  21. package/kit/agents/executor.md +565 -564
  22. package/kit/agents/golden-signals-instrumenter.md +1 -0
  23. package/kit/agents/incident-investigator.md +1 -0
  24. package/kit/agents/integration-checker.md +201 -200
  25. package/kit/agents/invite-flow-implementer.md +190 -189
  26. package/kit/agents/legacy-characterizer.md +369 -368
  27. package/kit/agents/lgpd-compliance-auditor.md +296 -295
  28. package/kit/agents/load-shedding-instrumenter.md +1 -0
  29. package/kit/agents/multi-tenant-isolation-auditor.md +254 -253
  30. package/kit/agents/multi-tenant-rls-writer.md +341 -340
  31. package/kit/agents/nyquist-auditor.md +179 -178
  32. package/kit/agents/observability-coverage-auditor.md +316 -315
  33. package/kit/agents/observability-instrumenter.md +1 -0
  34. package/kit/agents/omm-auditor.md +1 -0
  35. package/kit/agents/org-onboarding-implementer.md +224 -223
  36. package/kit/agents/payload-capture-instrumenter.md +274 -273
  37. package/kit/agents/phase-researcher.md +697 -696
  38. package/kit/agents/plan-checker.md +273 -272
  39. package/kit/agents/planner.md +923 -922
  40. package/kit/agents/postmortem-writer.md +1 -0
  41. package/kit/agents/project-researcher.md +653 -652
  42. package/kit/agents/prr-conductor.md +1 -0
  43. package/kit/agents/refactor-safety-auditor.md +405 -404
  44. package/kit/agents/release-pipeline-auditor.md +1 -0
  45. package/kit/agents/research-synthesizer.md +246 -245
  46. package/kit/agents/roadmapper.md +678 -677
  47. package/kit/agents/schema-checker.md +1 -0
  48. package/kit/agents/seam-finder.md +360 -359
  49. package/kit/agents/shotgun-surgery-detector.md +350 -349
  50. package/kit/agents/slo-engineer.md +1 -0
  51. package/kit/agents/storytelling-analyst.md +1 -0
  52. package/kit/agents/supabase-architect.md +1 -0
  53. package/kit/agents/supabase-auth-bootstrapper.md +16 -1
  54. package/kit/agents/supabase-auth-hook-writer.md +418 -0
  55. package/kit/agents/supabase-branching-architect.md +563 -562
  56. package/kit/agents/supabase-cicd-pipeline-implementer.md +778 -777
  57. package/kit/agents/supabase-column-privileges-writer.md +400 -399
  58. package/kit/agents/supabase-edge-fn-tester.md +2 -1
  59. package/kit/agents/supabase-edge-fn-writer.md +2 -1
  60. package/kit/agents/supabase-mfa-implementer.md +439 -0
  61. package/kit/agents/supabase-migration-writer.md +386 -385
  62. package/kit/agents/supabase-oauth-server-implementer.md +507 -0
  63. package/kit/agents/supabase-rbac-implementer.md +393 -392
  64. package/kit/agents/supabase-realtime-implementer.md +364 -363
  65. package/kit/agents/supabase-rls-hardener.md +522 -521
  66. package/kit/agents/supabase-rls-writer.md +324 -323
  67. package/kit/agents/supabase-roles-implementer.md +356 -355
  68. package/kit/agents/supabase-social-auth-implementer.md +451 -0
  69. package/kit/agents/supabase-sso-saml-architect.md +549 -0
  70. package/kit/agents/supabase-storage-implementer.md +1 -0
  71. package/kit/agents/super-admin-implementer.md +282 -281
  72. package/kit/agents/toil-auditor.md +1 -0
  73. package/kit/agents/ui-auditor.md +438 -437
  74. package/kit/agents/ui-checker.md +303 -302
  75. package/kit/agents/ui-researcher.md +356 -355
  76. package/kit/agents/user-profiler.md +176 -175
  77. package/kit/agents/validador-evolucao-schema.md +336 -335
  78. package/kit/agents/verifier.md +729 -728
  79. package/kit/commands/adicionar-backlog.md +75 -75
  80. package/kit/commands/adicionar-fase.md +42 -42
  81. package/kit/commands/adicionar-tarefa.md +45 -45
  82. package/kit/commands/adicionar-testes.md +41 -41
  83. package/kit/commands/ajuda.md +21 -21
  84. package/kit/commands/atualizar.md +37 -37
  85. package/kit/commands/auditar-cascading.md +111 -111
  86. package/kit/commands/auditar-marco.md +179 -179
  87. package/kit/commands/auditar-observabilidade-cobertura.md +183 -183
  88. package/kit/commands/auditar-refactor.md +219 -219
  89. package/kit/commands/auditar-release.md +109 -109
  90. package/kit/commands/auditar-uat.md +23 -23
  91. package/kit/commands/autonomo.md +40 -40
  92. package/kit/commands/branch-pr.md +24 -24
  93. package/kit/commands/burn-rate-status.md +408 -408
  94. package/kit/commands/capturar-payloads.md +193 -193
  95. package/kit/commands/caracterizar.md +212 -212
  96. package/kit/commands/concluir-marco.md +247 -247
  97. package/kit/commands/configuracoes.md +36 -36
  98. package/kit/commands/dados-distribuidos.md +188 -188
  99. package/kit/commands/definir-perfil.md +10 -10
  100. package/kit/commands/depurar.md +190 -190
  101. package/kit/commands/detectar-duplicacao.md +197 -197
  102. package/kit/commands/discutir-fase.md +131 -131
  103. package/kit/commands/encontrar-seams.md +136 -136
  104. package/kit/commands/entrar-discord.md +17 -17
  105. package/kit/commands/estatisticas.md +18 -18
  106. package/kit/commands/example-greeting.md +33 -33
  107. package/kit/commands/executar-fase.md +58 -58
  108. package/kit/commands/expresso.md +56 -56
  109. package/kit/commands/fase-ui.md +34 -34
  110. package/kit/commands/fazer.md +57 -57
  111. package/kit/commands/fio.md +125 -125
  112. package/kit/commands/fluxos-trabalho.md +64 -64
  113. package/kit/commands/forense.md +176 -176
  114. package/kit/commands/gerenciador.md +38 -38
  115. package/kit/commands/inserir-fase.md +31 -31
  116. package/kit/commands/legacy.md +263 -263
  117. package/kit/commands/limpeza.md +17 -17
  118. package/kit/commands/listar-hipoteses-fase.md +45 -45
  119. package/kit/commands/listar-workspaces.md +18 -18
  120. package/kit/commands/load-shedding.md +117 -117
  121. package/kit/commands/mapear-codebase.md +70 -70
  122. package/kit/commands/multi-tenant.md +163 -163
  123. package/kit/commands/nota.md +33 -33
  124. package/kit/commands/novo-marco.md +43 -43
  125. package/kit/commands/novo-projeto.md +41 -41
  126. package/kit/commands/novo-workspace.md +43 -43
  127. package/kit/commands/pausar-trabalho.md +37 -37
  128. package/kit/commands/perfil-usuario.md +45 -45
  129. package/kit/commands/pesquisar-fase.md +195 -195
  130. package/kit/commands/planejar-fase.md +67 -67
  131. package/kit/commands/planejar-lacunas.md +33 -33
  132. package/kit/commands/plantar-ideia.md +25 -25
  133. package/kit/commands/progresso.md +24 -24
  134. package/kit/commands/proximo.md +30 -30
  135. package/kit/commands/publicar.md +490 -490
  136. package/kit/commands/rapido.md +35 -35
  137. package/kit/commands/reaplicar-patches.md +124 -124
  138. package/kit/commands/refactor-seguro.md +321 -321
  139. package/kit/commands/relatorio-sessao.md +19 -19
  140. package/kit/commands/remover-fase.md +31 -31
  141. package/kit/commands/remover-workspace.md +26 -26
  142. package/kit/commands/resumo-marco.md +50 -50
  143. package/kit/commands/retomar-trabalho.md +40 -40
  144. package/kit/commands/revisar-backlog.md +60 -60
  145. package/kit/commands/revisar-ui.md +32 -32
  146. package/kit/commands/revisar.md +37 -37
  147. package/kit/commands/saude.md +21 -21
  148. package/kit/commands/setup-notion.md +93 -93
  149. package/kit/commands/storytelling.md +179 -179
  150. package/kit/commands/supabase.md +21 -1
  151. package/kit/commands/sync-main.md +68 -68
  152. package/kit/commands/validar-fase.md +35 -35
  153. package/kit/commands/verificar-tarefas.md +44 -44
  154. package/kit/commands/verificar-trabalho.md +64 -64
  155. package/kit/file-manifest.json +100 -84
  156. package/kit/framework/bin/lib/commands.cjs +959 -959
  157. package/kit/framework/bin/lib/config.cjs +442 -442
  158. package/kit/framework/bin/lib/core.cjs +1230 -1230
  159. package/kit/framework/bin/lib/frontmatter.cjs +336 -336
  160. package/kit/framework/bin/lib/init.cjs +1442 -1442
  161. package/kit/framework/bin/lib/milestone.cjs +252 -252
  162. package/kit/framework/bin/lib/model-profiles.cjs +68 -68
  163. package/kit/framework/bin/lib/phase.cjs +888 -888
  164. package/kit/framework/bin/lib/profile-output.cjs +952 -952
  165. package/kit/framework/bin/lib/profile-pipeline.cjs +539 -539
  166. package/kit/framework/bin/lib/roadmap.cjs +329 -329
  167. package/kit/framework/bin/lib/security.cjs +382 -382
  168. package/kit/framework/bin/lib/state.cjs +1031 -1031
  169. package/kit/framework/bin/lib/template.cjs +222 -222
  170. package/kit/framework/bin/lib/uat.cjs +282 -282
  171. package/kit/framework/bin/lib/verify.cjs +888 -888
  172. package/kit/framework/bin/lib/workstream.cjs +491 -491
  173. package/kit/framework/bin/tools.cjs +918 -918
  174. package/kit/framework/commands/workstreams.md +63 -63
  175. package/kit/framework/references/checkpoints.md +778 -778
  176. package/kit/framework/references/continuation-format.md +249 -249
  177. package/kit/framework/references/decimal-phase-calculation.md +64 -64
  178. package/kit/framework/references/git-integration.md +295 -295
  179. package/kit/framework/references/git-planning-commit.md +38 -38
  180. package/kit/framework/references/model-profile-resolution.md +36 -36
  181. package/kit/framework/references/model-profiles.md +139 -139
  182. package/kit/framework/references/phase-argument-parsing.md +61 -61
  183. package/kit/framework/references/planning-config.md +202 -202
  184. package/kit/framework/references/questioning.md +162 -162
  185. package/kit/framework/references/tdd.md +263 -263
  186. package/kit/framework/references/ui-brand.md +160 -160
  187. package/kit/framework/references/user-profiling.md +657 -657
  188. package/kit/framework/references/verification-patterns.md +612 -612
  189. package/kit/framework/references/workstream-flag.md +58 -58
  190. package/kit/framework/templates/DEBUG.md +164 -164
  191. package/kit/framework/templates/UAT.md +265 -265
  192. package/kit/framework/templates/UI-SPEC.md +100 -100
  193. package/kit/framework/templates/VALIDATION.md +76 -76
  194. package/kit/framework/templates/claude-md.md +122 -122
  195. package/kit/framework/templates/codebase/architecture.md +185 -185
  196. package/kit/framework/templates/codebase/concerns.md +205 -205
  197. package/kit/framework/templates/codebase/conventions.md +204 -204
  198. package/kit/framework/templates/codebase/integrations.md +192 -192
  199. package/kit/framework/templates/codebase/stack.md +158 -158
  200. package/kit/framework/templates/codebase/structure.md +199 -199
  201. package/kit/framework/templates/codebase/testing.md +301 -301
  202. package/kit/framework/templates/config.json +44 -44
  203. package/kit/framework/templates/context.md +352 -352
  204. package/kit/framework/templates/continue-here.md +78 -78
  205. package/kit/framework/templates/copilot-instructions.md +7 -7
  206. package/kit/framework/templates/debug-subagent-prompt.md +91 -91
  207. package/kit/framework/templates/dev-preferences.md +20 -20
  208. package/kit/framework/templates/discovery.md +146 -146
  209. package/kit/framework/templates/discussion-log.md +63 -63
  210. package/kit/framework/templates/milestone-archive.md +123 -123
  211. package/kit/framework/templates/milestone.md +115 -115
  212. package/kit/framework/templates/phase-prompt.md +610 -610
  213. package/kit/framework/templates/planner-subagent-prompt.md +117 -117
  214. package/kit/framework/templates/project.md +186 -186
  215. package/kit/framework/templates/requirements.md +231 -231
  216. package/kit/framework/templates/research-project/ARCHITECTURE.md +204 -204
  217. package/kit/framework/templates/research-project/FEATURES.md +147 -147
  218. package/kit/framework/templates/research-project/PITFALLS.md +200 -200
  219. package/kit/framework/templates/research-project/STACK.md +120 -120
  220. package/kit/framework/templates/research-project/SUMMARY.md +170 -170
  221. package/kit/framework/templates/research.md +419 -419
  222. package/kit/framework/templates/retrospective.md +54 -54
  223. package/kit/framework/templates/roadmap.md +202 -202
  224. package/kit/framework/templates/state.md +176 -176
  225. package/kit/framework/templates/summary-complex.md +59 -59
  226. package/kit/framework/templates/summary-minimal.md +41 -41
  227. package/kit/framework/templates/summary-standard.md +48 -48
  228. package/kit/framework/templates/summary.md +209 -209
  229. package/kit/framework/templates/user-profile.md +146 -146
  230. package/kit/framework/templates/user-setup.md +256 -256
  231. package/kit/framework/templates/verification-report.md +258 -258
  232. package/kit/framework/workflows/add-phase.md +112 -112
  233. package/kit/framework/workflows/add-tests.md +351 -351
  234. package/kit/framework/workflows/add-todo.md +158 -158
  235. package/kit/framework/workflows/audit-milestone.md +340 -340
  236. package/kit/framework/workflows/audit-uat.md +109 -109
  237. package/kit/framework/workflows/autonomous.md +891 -891
  238. package/kit/framework/workflows/check-todos.md +177 -177
  239. package/kit/framework/workflows/cleanup.md +152 -152
  240. package/kit/framework/workflows/complete-milestone.md +696 -696
  241. package/kit/framework/workflows/diagnose-issues.md +231 -231
  242. package/kit/framework/workflows/discovery-phase.md +289 -289
  243. package/kit/framework/workflows/discuss-phase-assumptions.md +653 -653
  244. package/kit/framework/workflows/discuss-phase.md +784 -784
  245. package/kit/framework/workflows/do.md +104 -104
  246. package/kit/framework/workflows/execute-phase.md +838 -838
  247. package/kit/framework/workflows/execute-plan.md +510 -510
  248. package/kit/framework/workflows/fast.md +102 -102
  249. package/kit/framework/workflows/forensics.md +265 -265
  250. package/kit/framework/workflows/health.md +181 -181
  251. package/kit/framework/workflows/help.md +619 -619
  252. package/kit/framework/workflows/insert-phase.md +130 -130
  253. package/kit/framework/workflows/list-phase-assumptions.md +178 -178
  254. package/kit/framework/workflows/list-workspaces.md +56 -56
  255. package/kit/framework/workflows/manager.md +362 -362
  256. package/kit/framework/workflows/map-codebase.md +377 -377
  257. package/kit/framework/workflows/milestone-summary.md +223 -223
  258. package/kit/framework/workflows/new-milestone.md +486 -486
  259. package/kit/framework/workflows/new-project.md +1159 -1159
  260. package/kit/framework/workflows/new-workspace.md +237 -237
  261. package/kit/framework/workflows/next.md +97 -97
  262. package/kit/framework/workflows/node-repair.md +92 -92
  263. package/kit/framework/workflows/note.md +156 -156
  264. package/kit/framework/workflows/pause-work.md +176 -176
  265. package/kit/framework/workflows/plan-milestone-gaps.md +273 -273
  266. package/kit/framework/workflows/plan-phase.md +765 -765
  267. package/kit/framework/workflows/plant-seed.md +169 -169
  268. package/kit/framework/workflows/pr-branch.md +129 -129
  269. package/kit/framework/workflows/profile-user.md +450 -450
  270. package/kit/framework/workflows/progress.md +507 -507
  271. package/kit/framework/workflows/quick.md +757 -757
  272. package/kit/framework/workflows/remove-phase.md +155 -155
  273. package/kit/framework/workflows/remove-workspace.md +90 -90
  274. package/kit/framework/workflows/research-phase.md +82 -82
  275. package/kit/framework/workflows/resume-project.md +326 -326
  276. package/kit/framework/workflows/review.md +228 -228
  277. package/kit/framework/workflows/session-report.md +146 -146
  278. package/kit/framework/workflows/settings.md +283 -283
  279. package/kit/framework/workflows/ship.md +228 -228
  280. package/kit/framework/workflows/stats.md +60 -60
  281. package/kit/framework/workflows/transition.md +671 -671
  282. package/kit/framework/workflows/ui-phase.md +302 -302
  283. package/kit/framework/workflows/ui-review.md +165 -165
  284. package/kit/framework/workflows/update.md +323 -323
  285. package/kit/framework/workflows/validate-phase.md +174 -174
  286. package/kit/framework/workflows/verify-phase.md +252 -252
  287. package/kit/framework/workflows/verify-work.md +637 -637
  288. package/kit/hooks/check-update.js +118 -118
  289. package/kit/hooks/context-monitor.js +163 -163
  290. package/kit/hooks/kit-attribution-reminder.cjs +29 -50
  291. package/kit/hooks/kit-router.cjs +137 -0
  292. package/kit/hooks/prompt-guard.js +103 -103
  293. package/kit/hooks/statusline.js +125 -125
  294. package/kit/hooks/workflow-guard.js +101 -101
  295. package/kit/settings.json +45 -45
  296. package/kit/skills/ai-prompt-characterization/SKILL.md +335 -335
  297. package/kit/skills/armadilhas-sistemas-distribuidos/SKILL.md +447 -447
  298. package/kit/skills/audit-log-multi-tenant/SKILL.md +340 -340
  299. package/kit/skills/b2b-saas-architecture/SKILL.md +300 -300
  300. package/kit/skills/consistencia-leitura-replica/SKILL.md +385 -385
  301. package/kit/skills/crm-lead-pipeline-patterns/SKILL.md +343 -343
  302. package/kit/skills/escolha-modelo-consistencia/SKILL.md +494 -494
  303. package/kit/skills/evolucao-schema-compativel/SKILL.md +448 -448
  304. package/kit/skills/evolution-go-whatsapp-integration/SKILL.md +322 -322
  305. package/kit/skills/example-skill/SKILL.md +42 -42
  306. package/kit/skills/legacy-api-only-applications/SKILL.md +358 -358
  307. package/kit/skills/legacy-characterization-tests/SKILL.md +330 -330
  308. package/kit/skills/legacy-effect-analysis/SKILL.md +331 -331
  309. package/kit/skills/legacy-extract-class/SKILL.md +203 -203
  310. package/kit/skills/legacy-programming-by-difference/SKILL.md +252 -252
  311. package/kit/skills/legacy-seams-and-test-harness/SKILL.md +460 -460
  312. package/kit/skills/legacy-shotgun-surgery/SKILL.md +286 -286
  313. package/kit/skills/legacy-sprout-wrap-techniques/SKILL.md +434 -434
  314. package/kit/skills/legacy-storytelling-naked-crc/SKILL.md +270 -270
  315. package/kit/skills/lgpd-multi-tenant-compliance/SKILL.md +340 -340
  316. package/kit/skills/member-invite-flow/SKILL.md +305 -305
  317. package/kit/skills/member-management-react-shadcn/SKILL.md +328 -328
  318. package/kit/skills/multi-tenant-performance-scaling/SKILL.md +316 -316
  319. package/kit/skills/multi-tenant-rls-hierarchy/SKILL.md +342 -342
  320. package/kit/skills/org-onboarding-flow/SKILL.md +257 -257
  321. package/kit/skills/org-switcher-react-pattern/SKILL.md +349 -349
  322. package/kit/skills/permission-gate-react-pattern/SKILL.md +271 -271
  323. package/kit/skills/postgres-isolamento-concorrencia/SKILL.md +552 -552
  324. package/kit/skills/pre-refactor-characterization/SKILL.md +421 -421
  325. package/kit/skills/rbac-permissions-matrix-supabase/SKILL.md +338 -338
  326. package/kit/skills/streams-eventos-cdc/SKILL.md +711 -711
  327. package/kit/skills/supabase-auth-hardening/SKILL.md +674 -0
  328. package/kit/skills/supabase-auth-hooks/SKILL.md +875 -0
  329. package/kit/skills/supabase-auth-methods/SKILL.md +486 -0
  330. package/kit/skills/supabase-auth-sessions/SKILL.md +579 -0
  331. package/kit/skills/supabase-auth-ssr/SKILL.md +60 -14
  332. package/kit/skills/supabase-branching-workflow/SKILL.md +544 -544
  333. package/kit/skills/supabase-ci-cd-github-actions/SKILL.md +880 -880
  334. package/kit/skills/supabase-column-level-security/SKILL.md +426 -426
  335. package/kit/skills/supabase-config-toml-remotes/SKILL.md +807 -807
  336. package/kit/skills/supabase-custom-claims-rbac/SKILL.md +472 -472
  337. package/kit/skills/supabase-edge-functions/SKILL.md +1 -1
  338. package/kit/skills/supabase-edge-functions-auth/SKILL.md +1 -1
  339. package/kit/skills/supabase-edge-functions-limits/SKILL.md +1 -1
  340. package/kit/skills/supabase-edge-functions-mcp-server/SKILL.md +1 -1
  341. package/kit/skills/supabase-edge-functions-testing/SKILL.md +1 -1
  342. package/kit/skills/supabase-edge-runtime-builtins/SKILL.md +1 -1
  343. package/kit/skills/supabase-enterprise-sso-saml/SKILL.md +545 -0
  344. package/kit/skills/supabase-jwt-signing-keys/SKILL.md +399 -0
  345. package/kit/skills/supabase-mfa/SKILL.md +488 -0
  346. package/kit/skills/supabase-migration-repair/SKILL.md +823 -823
  347. package/kit/skills/supabase-migrations/SKILL.md +297 -297
  348. package/kit/skills/supabase-oauth-server/SKILL.md +537 -0
  349. package/kit/skills/supabase-pgtap-testing/SKILL.md +1053 -1053
  350. package/kit/skills/supabase-postgres-roles/SKILL.md +392 -392
  351. package/kit/skills/supabase-realtime/SKILL.md +460 -460
  352. package/kit/skills/supabase-rls-defense-in-depth/SKILL.md +418 -418
  353. package/kit/skills/supabase-rls-policies/SKILL.md +635 -635
  354. package/kit/skills/supabase-social-oauth/SKILL.md +480 -0
  355. package/kit/skills/supabase-third-party-auth/SKILL.md +450 -0
  356. package/kit/skills/super-admin-platform-pattern/SKILL.md +326 -326
  357. package/kit/skills/tenant-quente-mitigacao/SKILL.md +605 -605
  358. package/kit/skills/whatsapp-conversation-state-machine/SKILL.md +287 -287
  359. package/package.json +1 -1
  360. package/src/core/kit.js +216 -216
  361. package/src/core/reflect.js +247 -247
  362. package/src/core/reverse-sync.js +372 -372
  363. package/src/core/sync.js +437 -418
  364. package/src/core/watch.js +121 -121
  365. package/src/mcp-server/index.js +794 -746
@@ -1,472 +1,472 @@
1
- ---
2
- name: supabase-custom-claims-rbac
3
- description: Use ao implementar Custom Claims via Custom Access Token Auth Hook para RBAC em Supabase…
4
- ---
5
-
6
- # Supabase — Custom Claims & RBAC via Auth Hooks
7
-
8
- ## Quando usar
9
-
10
- LLM carrega esta skill quando implementar **Role-Based Access Control (RBAC)** via Custom Claims no JWT. Pattern canônico Supabase v1.25.
11
-
12
- Trigger phrases:
13
-
14
- - "custom claims Supabase", "RBAC Supabase"
15
- - "Custom Access Token Auth Hook", "auth hook RBAC"
16
- - "user_role no JWT", "authorize() function"
17
- - "role-based access control com auth hook"
18
- - "como evitar JOIN em RLS policy" (custom claims é a alternativa moderna)
19
-
20
- ## Princípio canônico
21
-
22
- RBAC em Supabase tem **3 mecanismos de delivery** dos claims:
23
-
24
- 1. **`app_metadata`** (skill `supabase-rls-policies` v1.23) — JSONB no JWT setado via service_role admin API. Simples mas limitado (não suporta enum types, sem normalização).
25
- 2. **Dedicated role table com helper function STABLE** (skill `rbac-permissions-matrix-supabase` v1.21) — `user_roles` + `private.has_role(role)` consultada em policies. Dinâmico mas faz JOIN custoso por query.
26
- 3. **Custom Claims via Custom Access Token Auth Hook** (v1.25 — **RECOMENDADO**) — `user_role` injetado no JWT durante geração do token via auth hook. Evita JOIN em policies (claim lido direto via `auth.jwt() ->> 'user_role'`). Eventually consistent (refresh TTL).
27
-
28
- **Quando usar custom claims (v1.25):**
29
-
30
- - ✅ RBAC com 2-10 roles fixos por user (admin, moderator, user, etc.)
31
- - ✅ Permission matrix relativamente estática (mudanças em horas/dias, não segundos)
32
- - ✅ Policies que precisam ser rápidas (sem JOIN custoso)
33
- - ✅ Cliente front-end precisa saber o role (UI conditional rendering)
34
-
35
- **Quando NÃO usar custom claims:**
36
-
37
- - ❌ Permission mudada em real-time (segundos) — JWT freshness não cobre
38
- - ❌ Permission depende de row context (ex: "can edit if owner of this row") — use RLS row-level com `auth.uid()`
39
- - ❌ Multi-tenant com user em N orgs com role diferente em cada — custom claim é per-user, não per-org-context
40
-
41
- Para multi-tenant complexo, combine: custom claim para role global (super_admin) + RLS hierárquica com `private.has_role(role, org_id)` para context-aware (skill `multi-tenant-rls-hierarchy`).
42
-
43
- ## Pattern canônico — 7 passos
44
-
45
- ### Passo 1: Enum types
46
-
47
- Defina enum types Postgres para roles e permissions — tipo-seguro, refactorable, documentado.
48
-
49
- ```sql
50
- -- enum de roles canônicos
51
- create type public.app_role as enum ('admin', 'moderator', 'user');
52
-
53
- -- enum de permissions canônicos (resource.action)
54
- create type public.app_permission as enum (
55
- 'channels.delete',
56
- 'channels.create',
57
- 'messages.delete',
58
- 'messages.update',
59
- 'users.ban'
60
- );
61
- ```
62
-
63
- **Caveat:** adicionar novo enum value exige migration `alter type public.app_permission add value 'novo.permission'` — não pode ser feito dentro de transação (Postgres limitation).
64
-
65
- ### Passo 2: Tabelas user_roles + role_permissions
66
-
67
- ```sql
68
- -- USER → ROLE mapping (N:1 user pode ter múltiplos roles)
69
- create table public.user_roles (
70
- id bigint generated by default as identity primary key,
71
- user_id uuid references auth.users on delete cascade not null,
72
- role app_role not null,
73
- unique (user_id, role)
74
- );
75
- comment on table public.user_roles is 'Application roles for each user.';
76
-
77
- -- ROLE → PERMISSION mapping (N:N role tem múltiplas permissions)
78
- create table public.role_permissions (
79
- id bigint generated by default as identity primary key,
80
- role app_role not null,
81
- permission app_permission not null,
82
- unique (role, permission)
83
- );
84
- comment on table public.role_permissions is 'Application permissions for each role.';
85
- ```
86
-
87
- Seed inicial:
88
-
89
- ```sql
90
- insert into public.role_permissions (role, permission)
91
- values
92
- ('admin', 'channels.delete'),
93
- ('admin', 'channels.create'),
94
- ('admin', 'messages.delete'),
95
- ('admin', 'messages.update'),
96
- ('admin', 'users.ban'),
97
- ('moderator', 'messages.delete'),
98
- ('moderator', 'messages.update');
99
- ```
100
-
101
- ### Passo 3: Custom Access Token Auth Hook function
102
-
103
- A função `custom_access_token_hook(event jsonb) returns jsonb` roda **antes** do JWT ser issued — recebe o event (com `user_id` + claims atuais) e devolve event modificado.
104
-
105
- ```sql
106
- create or replace function public.custom_access_token_hook(event jsonb)
107
- returns jsonb
108
- language plpgsql
109
- stable
110
- as $$
111
- declare
112
- claims jsonb;
113
- user_role public.app_role;
114
- begin
115
- -- buscar role do user em user_roles
116
- select role into user_role
117
- from public.user_roles
118
- where user_id = (event->>'user_id')::uuid;
119
-
120
- claims := event->'claims';
121
-
122
- if user_role is not null then
123
- claims := jsonb_set(claims, '{user_role}', to_jsonb(user_role));
124
- else
125
- claims := jsonb_set(claims, '{user_role}', 'null');
126
- end if;
127
-
128
- -- atualiza objeto claims no event original
129
- event := jsonb_set(event, '{claims}', claims);
130
-
131
- return event;
132
- end;
133
- $$;
134
- ```
135
-
136
- **Caveat para multi-role:** se user tem múltiplos roles em `user_roles`, esta versão pega o primeiro (`select role into user_role`). Para múltiplos roles no JWT, use:
137
-
138
- ```sql
139
- -- variante multi-role: claim user_roles como array
140
- select array_agg(role) into user_roles_array
141
- from public.user_roles
142
- where user_id = (event->>'user_id')::uuid;
143
- -- ...
144
- claims := jsonb_set(claims, '{user_roles}', to_jsonb(user_roles_array));
145
- ```
146
-
147
- ### Passo 4: Permissions canônicos para `supabase_auth_admin`
148
-
149
- `supabase_auth_admin` é o Postgres role usado pelo Supabase Auth service ao invocar o hook. Precisa de permissions específicos:
150
-
151
- ```sql
152
- -- 1. acesso ao schema public
153
- grant usage on schema public to supabase_auth_admin;
154
-
155
- -- 2. permissão de EXECUTE no hook function (apenas auth_admin pode invocar)
156
- grant execute
157
- on function public.custom_access_token_hook
158
- to supabase_auth_admin;
159
-
160
- -- 3. REVOKE EXECUTE de roles públicos (cliente NÃO pode chamar diretamente)
161
- revoke execute
162
- on function public.custom_access_token_hook
163
- from authenticated, anon, public;
164
-
165
- -- 4. acesso à tabela user_roles
166
- grant all
167
- on table public.user_roles
168
- to supabase_auth_admin;
169
-
170
- -- 5. REVOKE acesso público à user_roles (cliente NÃO pode mutar roles)
171
- revoke all
172
- on table public.user_roles
173
- from authenticated, anon, public;
174
-
175
- -- 6. RLS policy permitindo supabase_auth_admin ler user_roles
176
- create policy "Allow auth admin to read user roles" on public.user_roles
177
- as permissive for select
178
- to supabase_auth_admin
179
- using (true);
180
- ```
181
-
182
- **Por quê REVOKE EXECUTE do hook:** sem isso, qualquer cliente autenticado poderia chamar `custom_access_token_hook(...)` e potencialmente abusar.
183
-
184
- **Por quê REVOKE ALL FROM authenticated em user_roles:** roles são metadado privilegiado — apenas admins (service_role) podem inserir/atualizar. Cliente não deve ter SELECT direto na tabela (consultar via claim do JWT).
185
-
186
- ### Passo 5: Habilitar o hook
187
-
188
- **Dashboard (production):**
189
-
190
- 1. Acesse `Authentication > Hooks (Beta)` no Dashboard Supabase
191
- 2. Selecione "Custom Access Token" hook type
192
- 3. Dropdown: selecione `public.custom_access_token_hook` (função criada no Passo 3)
193
- 4. Save
194
-
195
- **Local development:**
196
-
197
- Em `supabase/config.toml`:
198
-
199
- ```toml
200
- [auth.hook.custom_access_token]
201
- enabled = true
202
- uri = "pg-functions://postgres/public/custom_access_token_hook"
203
- ```
204
-
205
- Reinicie o Supabase local (`supabase stop && supabase start`).
206
-
207
- ### Passo 6: `authorize()` function
208
-
209
- A função `authorize(permission app_permission) returns boolean` é o coração do RBAC — lê `user_role` do JWT e checa se o role tem a permission.
210
-
211
- ```sql
212
- create or replace function public.authorize(
213
- requested_permission app_permission
214
- )
215
- returns boolean
216
- language plpgsql
217
- stable
218
- security definer
219
- set search_path = ''
220
- as $$
221
- declare
222
- bind_permissions int;
223
- user_role public.app_role;
224
- begin
225
- -- ler user_role do JWT (delivered via auth hook do Passo 3)
226
- select (auth.jwt() ->> 'user_role')::public.app_role into user_role;
227
-
228
- -- contar permissions matching
229
- select count(*)
230
- into bind_permissions
231
- from public.role_permissions
232
- where role_permissions.permission = requested_permission
233
- and role_permissions.role = user_role;
234
-
235
- return bind_permissions > 0;
236
- end;
237
- $$;
238
- ```
239
-
240
- **Decisões canônicas:**
241
-
242
- - `stable` — função consulta DB mas não muta; resultado é estável dentro de uma transação (Postgres pode cachear)
243
- - `security definer` — roda com privilégios do owner (geralmente `postgres`) — necessário para acessar role_permissions sem RLS recursivo
244
- - `set search_path = ''` — anti schema injection (cross-ref skill `supabase-database-functions`)
245
-
246
- ### Passo 7: RLS policies usando authorize()
247
-
248
- Em vez de hard-code role em policy, use `authorize(permission)`:
249
-
250
- ```sql
251
- -- ANTES (anti-pattern): hard-code role direto na policy
252
- create policy "Allow admin delete channels" on public.channels for delete
253
- to authenticated
254
- using ((auth.jwt() ->> 'user_role') = 'admin');
255
-
256
- -- DEPOIS (canônico v1.25): authorize() abstrai role → permission
257
- create policy "Allow authorized delete access" on public.channels for delete
258
- to authenticated
259
- using ((SELECT authorize('channels.delete')));
260
-
261
- create policy "Allow authorized delete access" on public.messages for delete
262
- to authenticated
263
- using ((SELECT authorize('messages.delete')));
264
- ```
265
-
266
- **Vantagens canônicas:**
267
-
268
- - Adicionar nova permission = INSERT em role_permissions (sem alterar policy)
269
- - Mudar quem tem permission = UPDATE em role_permissions
270
- - Policies ficam estáveis; matriz permissions evolui
271
- - Reutilizável em N policies
272
-
273
- **Por que `(SELECT authorize(...))` com wrapper:** caching (cross-ref REGRA #2 da skill `supabase-rls-policies` v1.23) — função roda 1 vez por query, não 1 vez por linha.
274
-
275
- ## Client-side — acessar custom claims
276
-
277
- Auth hook só modifica o **access_token JWT**, não a auth response. Para acessar custom claims no cliente, decode o JWT:
278
-
279
- ```bash
280
- npm install jwt-decode
281
- ```
282
-
283
- ```js
284
- import { jwtDecode } from 'jwt-decode'
285
- import { createClient } from '@supabase/supabase-js'
286
-
287
- const supabase = createClient(URL, ANON_KEY)
288
-
289
- // listener para mudanças de auth state (login, logout, refresh)
290
- supabase.auth.onAuthStateChange(async (event, session) => {
291
- if (session) {
292
- const jwt = jwtDecode(session.access_token)
293
- const userRole = jwt.user_role // 'admin' | 'moderator' | 'user' | null
294
-
295
- // usar userRole para UI conditional rendering
296
- if (userRole === 'admin') {
297
- // show admin panel
298
- }
299
- }
300
- })
301
- ```
302
-
303
- **Para Next.js v16 + SSR:** decode o JWT no server-side via middleware ou Server Component:
304
-
305
- ```ts
306
- // app/middleware.ts ou Server Component
307
- import { jwtDecode } from 'jwt-decode'
308
-
309
- const { data: { session } } = await supabase.auth.getSession()
310
- if (session) {
311
- const jwt = jwtDecode(session.access_token)
312
- const userRole = jwt.user_role
313
- // ...
314
- }
315
- ```
316
-
317
- **Para backend (Node.js/Python/etc):** use bibliotecas equivalentes:
318
-
319
- - Node.js: `jsonwebtoken`, `express-jwt`, `koa-jwt`
320
- - Python: `PyJWT`
321
- - Dart: `dart_jsonwebtoken`
322
- - .NET: `Microsoft.AspNetCore.Authentication.JwtBearer`
323
-
324
- ## ⚠ Caveat — JWT Freshness
325
-
326
- Mudanças em `user_roles` **NÃO** refletem imediatamente no JWT do user — apenas após **token refresh** (default TTL 1h em Supabase).
327
-
328
- **Cenários:**
329
-
330
- | Situação | Comportamento |
331
- |----------|---------------|
332
- | Admin adiciona role moderator ao user X | User X continua com `user_role: null` até próximo refresh (até 1h) |
333
- | Admin remove role admin do user Y | User Y continua com `user_role: admin` no JWT atual; após refresh, policies bloqueiam |
334
- | Permission adicionada em role_permissions (admin → users.ban) | Reflete IMEDIATAMENTE — authorize() consulta DB cada query (stable mas re-executa em query nova) |
335
-
336
- **Implicações:**
337
-
338
- - Adicionar role: aceitável demora (user pode logout/login para forçar refresh)
339
- - **Remover role: PROBLEMA** — user comprometido pode usar JWT antigo até expirar. Para invalidação imediata:
340
-
341
- ```ts
342
- // force logout via admin API (server-side com service_role)
343
- await supabase.auth.admin.signOut(userId)
344
- // próximo request do user vai falhar; user precisa login novamente
345
- ```
346
-
347
- - Mudança em permissions matrix (role_permissions): IMEDIATA — não precisa refresh
348
-
349
- ## Anti-patterns
350
-
351
- ### Anti-pattern 1: Esquecer GRANT EXECUTE ao supabase_auth_admin
352
-
353
- **Errado:**
354
- ```sql
355
- create or replace function public.custom_access_token_hook(event jsonb) returns jsonb ...;
356
- -- esqueceu o GRANT EXECUTE TO supabase_auth_admin
357
- ```
358
-
359
- **Por quê:** auth hook falha silenciosamente — JWT é issued mas SEM o claim `user_role`. Difícil de debug.
360
-
361
- **Certo:** sempre incluir os 6 GRANTs/REVOKEs do Passo 4.
362
-
363
- ### Anti-pattern 2: Hardcode role em policy ao invés de authorize()
364
-
365
- **Errado:**
366
- ```sql
367
- create policy "admin_delete" on public.channels for delete to authenticated
368
- using ((auth.jwt() ->> 'user_role') = 'admin');
369
- ```
370
-
371
- **Por quê:** acoplamento policy ↔ role. Mudar quem pode deletar requer ALTER POLICY (DDL); com authorize(), basta UPDATE em role_permissions.
372
-
373
- **Certo:** `using ((SELECT authorize('channels.delete')))` — policy estável, matriz evolui.
374
-
375
- ### Anti-pattern 3: Assumir JWT fresh sem invalidação
376
-
377
- **Errado:**
378
- ```ts
379
- // admin remove role admin do user X
380
- await supabase.from('user_roles').delete().eq('user_id', xId).eq('role', 'admin')
381
- // CRÍTICO: user X ainda tem JWT antigo válido por até 1h
382
- ```
383
-
384
- **Por quê:** revogação de role não é imediata. Em casos de comprometimento, atacante usa o JWT antigo para abusar de privilégios admin.
385
-
386
- **Certo:** sempre force logout após mudança crítica de role:
387
- ```ts
388
- await supabase.from('user_roles').delete().eq('user_id', xId).eq('role', 'admin')
389
- await supabase.auth.admin.signOut(xId) // força refresh imediato
390
- ```
391
-
392
- ### Anti-pattern 4: Mutar app_metadata do cliente para mudar role
393
-
394
- **Errado:**
395
- ```ts
396
- // no cliente
397
- await supabase.auth.updateUser({ data: { role: 'admin' } })
398
- // user_metadata é editável pelo cliente — privilege escalation
399
- ```
400
-
401
- **Por quê:** mesmo anti-pattern de `user_metadata` da skill `supabase-rls-policies` (REGRA #1). Pior — confunde "role via custom claim" com "role via user_metadata".
402
-
403
- **Certo:** roles são mutados APENAS via INSERT em `public.user_roles` por backend (service_role) ou admin API. Cliente nunca toca em metadata de role.
404
-
405
- ### Anti-pattern 5: Auth hook que faz query custosa
406
-
407
- **Errado:**
408
- ```sql
409
- create or replace function public.custom_access_token_hook(event jsonb) returns jsonb ... as $$
410
- begin
411
- -- query custosa em cada token issuance
412
- select * into user_data from public.users
413
- join public.organizations o on u.org_id = o.id
414
- join public.subscriptions s on o.id = s.org_id
415
- where u.id = (event->>'user_id')::uuid;
416
- -- ...
417
- end;
418
- $$;
419
- ```
420
-
421
- **Por quê:** hook roda em **cada login + cada refresh**. Query lenta degrada latência de auth. Bom hook é < 10ms.
422
-
423
- **Certo:** mantenha hook simples — query single table (user_roles), aggregate se necessário, sem JOINs. Se precisa de info complexa, materialize em coluna de user_roles (denormalize).
424
-
425
- ## Postgres Roles vs Custom Claims — distinção canônica (v1.26)
426
-
427
- **Custom Claims** (v1.25) e **Postgres Roles** (v1.26) são conceitos **complementares**, não substitutos:
428
-
429
- | | Custom Claims (v1.25) | Postgres Roles (v1.26) |
430
- |---|---|---|
431
- | Tipo de access | Application access (end-users) | System access (service accounts) |
432
- | Identidade | JWT claim `user_role` | Postgres login |
433
- | Quem usa | End-users via PostgREST | Cron jobs, BI tools, ETL, admin scripts |
434
- | Granularidade | Per-permission via `authorize()` | Per schema/table/function/column |
435
- | Dinamicidade | Eventually consistent (TTL refresh) | Estática (alterações via SQL DDL) |
436
- | Exemplo | "User admin pode deletar messages" | "Cron job pode SELECT em todas tabelas para backup" |
437
-
438
- **Combine quando ambos aplicam:**
439
-
440
- - End-user com role `admin` (via custom claim) acessa endpoint que invoca função `private.org_analytics()` com SECURITY DEFINER (que tem GRANT EXECUTE apenas para Postgres role `analytics_reader`)
441
- - Service account `metabase_reader` (Postgres role) acessa view `public.user_active_tasks` com `security_invoker=true` (que respeita RLS via auth.jwt() — mas auth.jwt() é null para Postgres role login, então policies precisam considerar service_role bypass)
442
-
443
- **NÃO use Postgres roles para application access:**
444
-
445
- - ❌ Criar role Postgres `app_admin` para gerenciar quem é admin no app
446
- - ❌ Criar role Postgres por end-user
447
- - ✅ Use RLS + Custom Claims via auth hook (pattern v1.25)
448
-
449
- **NÃO use Custom Claims para system access:**
450
-
451
- - ❌ Service account cron job autenticando via JWT customizado
452
- - ❌ BI tool authenticando via auth hook
453
- - ✅ Use Postgres role dedicado (pattern v1.26)
454
-
455
- Padrão completo de Postgres roles em [`supabase-postgres-roles`](../supabase-postgres-roles/SKILL.md) (v1.26).
456
-
457
- ## Cross-suite integration (v1.25)
458
-
459
- Esta skill é base para o agent novo `supabase-rbac-implementer` (Phase 139) — recebe spec (roles + permissions matrix) via `Task()` e materializa setup completo. Pattern de handoff cooperativo herdado de v1.23/v1.24.
460
-
461
- Para multi-tenant com role por org, **combine** custom claim (role global) + helper function PG (`private.has_role(role, org_id)` em RLS hierárquica — skill `multi-tenant-rls-hierarchy` v1.21). Custom claim sozinho não cobre context-aware multi-tenant.
462
-
463
- ## Ver também
464
-
465
- - [supabase-rbac-implementer](../../agents/supabase-rbac-implementer.md) (v1.25) — canonical materializer
466
- - [supabase-rls-policies](../supabase-rls-policies/SKILL.md) (v1.23) — section "RBAC via Custom Claims + authorize() function (v1.25)"
467
- - [supabase-rls-defense-in-depth](../supabase-rls-defense-in-depth/SKILL.md) (v1.24) — Camada 9 (Auth Hooks - Custom Claims) v1.25
468
- - [supabase-database-functions](../supabase-database-functions/SKILL.md) — pattern SECURITY DEFINER + supabase_auth_admin grants
469
- - [supabase-auth-ssr](../supabase-auth-ssr/SKILL.md) — Next.js v16 + onAuthStateChange + jwt-decode integration
470
- - [supabase-rls-hardener](../../agents/supabase-rls-hardener.md) (v1.23) — Detector 9 valida auth hook instalado (v1.25)
471
- - [rbac-permissions-matrix-supabase](../rbac-permissions-matrix-supabase/SKILL.md) (v1.21) — alternativa via helper function STABLE (vs custom claim v1.25)
472
- - [glossário compartilhado](../_shared-supabase/glossary.md) — termos custom claims, Custom Access Token Auth Hook, JWT user_role claim, authorize() function, supabase_auth_admin role, app_role enum, app_permission enum, jwt-decode client pattern
1
+ ---
2
+ name: supabase-custom-claims-rbac
3
+ description: Use ao implementar Custom Claims via Custom Access Token Auth Hook para RBAC em Supabase…
4
+ ---
5
+
6
+ # Supabase — Custom Claims & RBAC via Auth Hooks
7
+
8
+ ## Quando usar
9
+
10
+ LLM carrega esta skill quando implementar **Role-Based Access Control (RBAC)** via Custom Claims no JWT. Pattern canônico Supabase v1.25.
11
+
12
+ Trigger phrases:
13
+
14
+ - "custom claims Supabase", "RBAC Supabase"
15
+ - "Custom Access Token Auth Hook", "auth hook RBAC"
16
+ - "user_role no JWT", "authorize() function"
17
+ - "role-based access control com auth hook"
18
+ - "como evitar JOIN em RLS policy" (custom claims é a alternativa moderna)
19
+
20
+ ## Princípio canônico
21
+
22
+ RBAC em Supabase tem **3 mecanismos de delivery** dos claims:
23
+
24
+ 1. **`app_metadata`** (skill `supabase-rls-policies` v1.23) — JSONB no JWT setado via service_role admin API. Simples mas limitado (não suporta enum types, sem normalização).
25
+ 2. **Dedicated role table com helper function STABLE** (skill `rbac-permissions-matrix-supabase` v1.21) — `user_roles` + `private.has_role(role)` consultada em policies. Dinâmico mas faz JOIN custoso por query.
26
+ 3. **Custom Claims via Custom Access Token Auth Hook** (v1.25 — **RECOMENDADO**) — `user_role` injetado no JWT durante geração do token via auth hook. Evita JOIN em policies (claim lido direto via `auth.jwt() ->> 'user_role'`). Eventually consistent (refresh TTL).
27
+
28
+ **Quando usar custom claims (v1.25):**
29
+
30
+ - ✅ RBAC com 2-10 roles fixos por user (admin, moderator, user, etc.)
31
+ - ✅ Permission matrix relativamente estática (mudanças em horas/dias, não segundos)
32
+ - ✅ Policies que precisam ser rápidas (sem JOIN custoso)
33
+ - ✅ Cliente front-end precisa saber o role (UI conditional rendering)
34
+
35
+ **Quando NÃO usar custom claims:**
36
+
37
+ - ❌ Permission mudada em real-time (segundos) — JWT freshness não cobre
38
+ - ❌ Permission depende de row context (ex: "can edit if owner of this row") — use RLS row-level com `auth.uid()`
39
+ - ❌ Multi-tenant com user em N orgs com role diferente em cada — custom claim é per-user, não per-org-context
40
+
41
+ Para multi-tenant complexo, combine: custom claim para role global (super_admin) + RLS hierárquica com `private.has_role(role, org_id)` para context-aware (skill `multi-tenant-rls-hierarchy`).
42
+
43
+ ## Pattern canônico — 7 passos
44
+
45
+ ### Passo 1: Enum types
46
+
47
+ Defina enum types Postgres para roles e permissions — tipo-seguro, refactorable, documentado.
48
+
49
+ ```sql
50
+ -- enum de roles canônicos
51
+ create type public.app_role as enum ('admin', 'moderator', 'user');
52
+
53
+ -- enum de permissions canônicos (resource.action)
54
+ create type public.app_permission as enum (
55
+ 'channels.delete',
56
+ 'channels.create',
57
+ 'messages.delete',
58
+ 'messages.update',
59
+ 'users.ban'
60
+ );
61
+ ```
62
+
63
+ **Caveat:** adicionar novo enum value exige migration `alter type public.app_permission add value 'novo.permission'` — não pode ser feito dentro de transação (Postgres limitation).
64
+
65
+ ### Passo 2: Tabelas user_roles + role_permissions
66
+
67
+ ```sql
68
+ -- USER → ROLE mapping (N:1 user pode ter múltiplos roles)
69
+ create table public.user_roles (
70
+ id bigint generated by default as identity primary key,
71
+ user_id uuid references auth.users on delete cascade not null,
72
+ role app_role not null,
73
+ unique (user_id, role)
74
+ );
75
+ comment on table public.user_roles is 'Application roles for each user.';
76
+
77
+ -- ROLE → PERMISSION mapping (N:N role tem múltiplas permissions)
78
+ create table public.role_permissions (
79
+ id bigint generated by default as identity primary key,
80
+ role app_role not null,
81
+ permission app_permission not null,
82
+ unique (role, permission)
83
+ );
84
+ comment on table public.role_permissions is 'Application permissions for each role.';
85
+ ```
86
+
87
+ Seed inicial:
88
+
89
+ ```sql
90
+ insert into public.role_permissions (role, permission)
91
+ values
92
+ ('admin', 'channels.delete'),
93
+ ('admin', 'channels.create'),
94
+ ('admin', 'messages.delete'),
95
+ ('admin', 'messages.update'),
96
+ ('admin', 'users.ban'),
97
+ ('moderator', 'messages.delete'),
98
+ ('moderator', 'messages.update');
99
+ ```
100
+
101
+ ### Passo 3: Custom Access Token Auth Hook function
102
+
103
+ A função `custom_access_token_hook(event jsonb) returns jsonb` roda **antes** do JWT ser issued — recebe o event (com `user_id` + claims atuais) e devolve event modificado.
104
+
105
+ ```sql
106
+ create or replace function public.custom_access_token_hook(event jsonb)
107
+ returns jsonb
108
+ language plpgsql
109
+ stable
110
+ as $$
111
+ declare
112
+ claims jsonb;
113
+ user_role public.app_role;
114
+ begin
115
+ -- buscar role do user em user_roles
116
+ select role into user_role
117
+ from public.user_roles
118
+ where user_id = (event->>'user_id')::uuid;
119
+
120
+ claims := event->'claims';
121
+
122
+ if user_role is not null then
123
+ claims := jsonb_set(claims, '{user_role}', to_jsonb(user_role));
124
+ else
125
+ claims := jsonb_set(claims, '{user_role}', 'null');
126
+ end if;
127
+
128
+ -- atualiza objeto claims no event original
129
+ event := jsonb_set(event, '{claims}', claims);
130
+
131
+ return event;
132
+ end;
133
+ $$;
134
+ ```
135
+
136
+ **Caveat para multi-role:** se user tem múltiplos roles em `user_roles`, esta versão pega o primeiro (`select role into user_role`). Para múltiplos roles no JWT, use:
137
+
138
+ ```sql
139
+ -- variante multi-role: claim user_roles como array
140
+ select array_agg(role) into user_roles_array
141
+ from public.user_roles
142
+ where user_id = (event->>'user_id')::uuid;
143
+ -- ...
144
+ claims := jsonb_set(claims, '{user_roles}', to_jsonb(user_roles_array));
145
+ ```
146
+
147
+ ### Passo 4: Permissions canônicos para `supabase_auth_admin`
148
+
149
+ `supabase_auth_admin` é o Postgres role usado pelo Supabase Auth service ao invocar o hook. Precisa de permissions específicos:
150
+
151
+ ```sql
152
+ -- 1. acesso ao schema public
153
+ grant usage on schema public to supabase_auth_admin;
154
+
155
+ -- 2. permissão de EXECUTE no hook function (apenas auth_admin pode invocar)
156
+ grant execute
157
+ on function public.custom_access_token_hook
158
+ to supabase_auth_admin;
159
+
160
+ -- 3. REVOKE EXECUTE de roles públicos (cliente NÃO pode chamar diretamente)
161
+ revoke execute
162
+ on function public.custom_access_token_hook
163
+ from authenticated, anon, public;
164
+
165
+ -- 4. acesso à tabela user_roles
166
+ grant all
167
+ on table public.user_roles
168
+ to supabase_auth_admin;
169
+
170
+ -- 5. REVOKE acesso público à user_roles (cliente NÃO pode mutar roles)
171
+ revoke all
172
+ on table public.user_roles
173
+ from authenticated, anon, public;
174
+
175
+ -- 6. RLS policy permitindo supabase_auth_admin ler user_roles
176
+ create policy "Allow auth admin to read user roles" on public.user_roles
177
+ as permissive for select
178
+ to supabase_auth_admin
179
+ using (true);
180
+ ```
181
+
182
+ **Por quê REVOKE EXECUTE do hook:** sem isso, qualquer cliente autenticado poderia chamar `custom_access_token_hook(...)` e potencialmente abusar.
183
+
184
+ **Por quê REVOKE ALL FROM authenticated em user_roles:** roles são metadado privilegiado — apenas admins (service_role) podem inserir/atualizar. Cliente não deve ter SELECT direto na tabela (consultar via claim do JWT).
185
+
186
+ ### Passo 5: Habilitar o hook
187
+
188
+ **Dashboard (production):**
189
+
190
+ 1. Acesse `Authentication > Hooks (Beta)` no Dashboard Supabase
191
+ 2. Selecione "Custom Access Token" hook type
192
+ 3. Dropdown: selecione `public.custom_access_token_hook` (função criada no Passo 3)
193
+ 4. Save
194
+
195
+ **Local development:**
196
+
197
+ Em `supabase/config.toml`:
198
+
199
+ ```toml
200
+ [auth.hook.custom_access_token]
201
+ enabled = true
202
+ uri = "pg-functions://postgres/public/custom_access_token_hook"
203
+ ```
204
+
205
+ Reinicie o Supabase local (`supabase stop && supabase start`).
206
+
207
+ ### Passo 6: `authorize()` function
208
+
209
+ A função `authorize(permission app_permission) returns boolean` é o coração do RBAC — lê `user_role` do JWT e checa se o role tem a permission.
210
+
211
+ ```sql
212
+ create or replace function public.authorize(
213
+ requested_permission app_permission
214
+ )
215
+ returns boolean
216
+ language plpgsql
217
+ stable
218
+ security definer
219
+ set search_path = ''
220
+ as $$
221
+ declare
222
+ bind_permissions int;
223
+ user_role public.app_role;
224
+ begin
225
+ -- ler user_role do JWT (delivered via auth hook do Passo 3)
226
+ select (auth.jwt() ->> 'user_role')::public.app_role into user_role;
227
+
228
+ -- contar permissions matching
229
+ select count(*)
230
+ into bind_permissions
231
+ from public.role_permissions
232
+ where role_permissions.permission = requested_permission
233
+ and role_permissions.role = user_role;
234
+
235
+ return bind_permissions > 0;
236
+ end;
237
+ $$;
238
+ ```
239
+
240
+ **Decisões canônicas:**
241
+
242
+ - `stable` — função consulta DB mas não muta; resultado é estável dentro de uma transação (Postgres pode cachear)
243
+ - `security definer` — roda com privilégios do owner (geralmente `postgres`) — necessário para acessar role_permissions sem RLS recursivo
244
+ - `set search_path = ''` — anti schema injection (cross-ref skill `supabase-database-functions`)
245
+
246
+ ### Passo 7: RLS policies usando authorize()
247
+
248
+ Em vez de hard-code role em policy, use `authorize(permission)`:
249
+
250
+ ```sql
251
+ -- ANTES (anti-pattern): hard-code role direto na policy
252
+ create policy "Allow admin delete channels" on public.channels for delete
253
+ to authenticated
254
+ using ((auth.jwt() ->> 'user_role') = 'admin');
255
+
256
+ -- DEPOIS (canônico v1.25): authorize() abstrai role → permission
257
+ create policy "Allow authorized delete access" on public.channels for delete
258
+ to authenticated
259
+ using ((SELECT authorize('channels.delete')));
260
+
261
+ create policy "Allow authorized delete access" on public.messages for delete
262
+ to authenticated
263
+ using ((SELECT authorize('messages.delete')));
264
+ ```
265
+
266
+ **Vantagens canônicas:**
267
+
268
+ - Adicionar nova permission = INSERT em role_permissions (sem alterar policy)
269
+ - Mudar quem tem permission = UPDATE em role_permissions
270
+ - Policies ficam estáveis; matriz permissions evolui
271
+ - Reutilizável em N policies
272
+
273
+ **Por que `(SELECT authorize(...))` com wrapper:** caching (cross-ref REGRA #2 da skill `supabase-rls-policies` v1.23) — função roda 1 vez por query, não 1 vez por linha.
274
+
275
+ ## Client-side — acessar custom claims
276
+
277
+ Auth hook só modifica o **access_token JWT**, não a auth response. Para acessar custom claims no cliente, decode o JWT:
278
+
279
+ ```bash
280
+ npm install jwt-decode
281
+ ```
282
+
283
+ ```js
284
+ import { jwtDecode } from 'jwt-decode'
285
+ import { createClient } from '@supabase/supabase-js'
286
+
287
+ const supabase = createClient(URL, ANON_KEY)
288
+
289
+ // listener para mudanças de auth state (login, logout, refresh)
290
+ supabase.auth.onAuthStateChange(async (event, session) => {
291
+ if (session) {
292
+ const jwt = jwtDecode(session.access_token)
293
+ const userRole = jwt.user_role // 'admin' | 'moderator' | 'user' | null
294
+
295
+ // usar userRole para UI conditional rendering
296
+ if (userRole === 'admin') {
297
+ // show admin panel
298
+ }
299
+ }
300
+ })
301
+ ```
302
+
303
+ **Para Next.js v16 + SSR:** decode o JWT no server-side via middleware ou Server Component:
304
+
305
+ ```ts
306
+ // app/middleware.ts ou Server Component
307
+ import { jwtDecode } from 'jwt-decode'
308
+
309
+ const { data: { session } } = await supabase.auth.getSession()
310
+ if (session) {
311
+ const jwt = jwtDecode(session.access_token)
312
+ const userRole = jwt.user_role
313
+ // ...
314
+ }
315
+ ```
316
+
317
+ **Para backend (Node.js/Python/etc):** use bibliotecas equivalentes:
318
+
319
+ - Node.js: `jsonwebtoken`, `express-jwt`, `koa-jwt`
320
+ - Python: `PyJWT`
321
+ - Dart: `dart_jsonwebtoken`
322
+ - .NET: `Microsoft.AspNetCore.Authentication.JwtBearer`
323
+
324
+ ## ⚠ Caveat — JWT Freshness
325
+
326
+ Mudanças em `user_roles` **NÃO** refletem imediatamente no JWT do user — apenas após **token refresh** (default TTL 1h em Supabase).
327
+
328
+ **Cenários:**
329
+
330
+ | Situação | Comportamento |
331
+ |----------|---------------|
332
+ | Admin adiciona role moderator ao user X | User X continua com `user_role: null` até próximo refresh (até 1h) |
333
+ | Admin remove role admin do user Y | User Y continua com `user_role: admin` no JWT atual; após refresh, policies bloqueiam |
334
+ | Permission adicionada em role_permissions (admin → users.ban) | Reflete IMEDIATAMENTE — authorize() consulta DB cada query (stable mas re-executa em query nova) |
335
+
336
+ **Implicações:**
337
+
338
+ - Adicionar role: aceitável demora (user pode logout/login para forçar refresh)
339
+ - **Remover role: PROBLEMA** — user comprometido pode usar JWT antigo até expirar. Para invalidação imediata:
340
+
341
+ ```ts
342
+ // force logout via admin API (server-side com service_role)
343
+ await supabase.auth.admin.signOut(userId)
344
+ // próximo request do user vai falhar; user precisa login novamente
345
+ ```
346
+
347
+ - Mudança em permissions matrix (role_permissions): IMEDIATA — não precisa refresh
348
+
349
+ ## Anti-patterns
350
+
351
+ ### Anti-pattern 1: Esquecer GRANT EXECUTE ao supabase_auth_admin
352
+
353
+ **Errado:**
354
+ ```sql
355
+ create or replace function public.custom_access_token_hook(event jsonb) returns jsonb ...;
356
+ -- esqueceu o GRANT EXECUTE TO supabase_auth_admin
357
+ ```
358
+
359
+ **Por quê:** auth hook falha silenciosamente — JWT é issued mas SEM o claim `user_role`. Difícil de debug.
360
+
361
+ **Certo:** sempre incluir os 6 GRANTs/REVOKEs do Passo 4.
362
+
363
+ ### Anti-pattern 2: Hardcode role em policy ao invés de authorize()
364
+
365
+ **Errado:**
366
+ ```sql
367
+ create policy "admin_delete" on public.channels for delete to authenticated
368
+ using ((auth.jwt() ->> 'user_role') = 'admin');
369
+ ```
370
+
371
+ **Por quê:** acoplamento policy ↔ role. Mudar quem pode deletar requer ALTER POLICY (DDL); com authorize(), basta UPDATE em role_permissions.
372
+
373
+ **Certo:** `using ((SELECT authorize('channels.delete')))` — policy estável, matriz evolui.
374
+
375
+ ### Anti-pattern 3: Assumir JWT fresh sem invalidação
376
+
377
+ **Errado:**
378
+ ```ts
379
+ // admin remove role admin do user X
380
+ await supabase.from('user_roles').delete().eq('user_id', xId).eq('role', 'admin')
381
+ // CRÍTICO: user X ainda tem JWT antigo válido por até 1h
382
+ ```
383
+
384
+ **Por quê:** revogação de role não é imediata. Em casos de comprometimento, atacante usa o JWT antigo para abusar de privilégios admin.
385
+
386
+ **Certo:** sempre force logout após mudança crítica de role:
387
+ ```ts
388
+ await supabase.from('user_roles').delete().eq('user_id', xId).eq('role', 'admin')
389
+ await supabase.auth.admin.signOut(xId) // força refresh imediato
390
+ ```
391
+
392
+ ### Anti-pattern 4: Mutar app_metadata do cliente para mudar role
393
+
394
+ **Errado:**
395
+ ```ts
396
+ // no cliente
397
+ await supabase.auth.updateUser({ data: { role: 'admin' } })
398
+ // user_metadata é editável pelo cliente — privilege escalation
399
+ ```
400
+
401
+ **Por quê:** mesmo anti-pattern de `user_metadata` da skill `supabase-rls-policies` (REGRA #1). Pior — confunde "role via custom claim" com "role via user_metadata".
402
+
403
+ **Certo:** roles são mutados APENAS via INSERT em `public.user_roles` por backend (service_role) ou admin API. Cliente nunca toca em metadata de role.
404
+
405
+ ### Anti-pattern 5: Auth hook que faz query custosa
406
+
407
+ **Errado:**
408
+ ```sql
409
+ create or replace function public.custom_access_token_hook(event jsonb) returns jsonb ... as $$
410
+ begin
411
+ -- query custosa em cada token issuance
412
+ select * into user_data from public.users
413
+ join public.organizations o on u.org_id = o.id
414
+ join public.subscriptions s on o.id = s.org_id
415
+ where u.id = (event->>'user_id')::uuid;
416
+ -- ...
417
+ end;
418
+ $$;
419
+ ```
420
+
421
+ **Por quê:** hook roda em **cada login + cada refresh**. Query lenta degrada latência de auth. Bom hook é < 10ms.
422
+
423
+ **Certo:** mantenha hook simples — query single table (user_roles), aggregate se necessário, sem JOINs. Se precisa de info complexa, materialize em coluna de user_roles (denormalize).
424
+
425
+ ## Postgres Roles vs Custom Claims — distinção canônica (v1.26)
426
+
427
+ **Custom Claims** (v1.25) e **Postgres Roles** (v1.26) são conceitos **complementares**, não substitutos:
428
+
429
+ | | Custom Claims (v1.25) | Postgres Roles (v1.26) |
430
+ |---|---|---|
431
+ | Tipo de access | Application access (end-users) | System access (service accounts) |
432
+ | Identidade | JWT claim `user_role` | Postgres login |
433
+ | Quem usa | End-users via PostgREST | Cron jobs, BI tools, ETL, admin scripts |
434
+ | Granularidade | Per-permission via `authorize()` | Per schema/table/function/column |
435
+ | Dinamicidade | Eventually consistent (TTL refresh) | Estática (alterações via SQL DDL) |
436
+ | Exemplo | "User admin pode deletar messages" | "Cron job pode SELECT em todas tabelas para backup" |
437
+
438
+ **Combine quando ambos aplicam:**
439
+
440
+ - End-user com role `admin` (via custom claim) acessa endpoint que invoca função `private.org_analytics()` com SECURITY DEFINER (que tem GRANT EXECUTE apenas para Postgres role `analytics_reader`)
441
+ - Service account `metabase_reader` (Postgres role) acessa view `public.user_active_tasks` com `security_invoker=true` (que respeita RLS via auth.jwt() — mas auth.jwt() é null para Postgres role login, então policies precisam considerar service_role bypass)
442
+
443
+ **NÃO use Postgres roles para application access:**
444
+
445
+ - ❌ Criar role Postgres `app_admin` para gerenciar quem é admin no app
446
+ - ❌ Criar role Postgres por end-user
447
+ - ✅ Use RLS + Custom Claims via auth hook (pattern v1.25)
448
+
449
+ **NÃO use Custom Claims para system access:**
450
+
451
+ - ❌ Service account cron job autenticando via JWT customizado
452
+ - ❌ BI tool authenticando via auth hook
453
+ - ✅ Use Postgres role dedicado (pattern v1.26)
454
+
455
+ Padrão completo de Postgres roles em [`supabase-postgres-roles`](../supabase-postgres-roles/SKILL.md) (v1.26).
456
+
457
+ ## Cross-suite integration (v1.25)
458
+
459
+ Esta skill é base para o agent novo `supabase-rbac-implementer` (Phase 139) — recebe spec (roles + permissions matrix) via `Task()` e materializa setup completo. Pattern de handoff cooperativo herdado de v1.23/v1.24.
460
+
461
+ Para multi-tenant com role por org, **combine** custom claim (role global) + helper function PG (`private.has_role(role, org_id)` em RLS hierárquica — skill `multi-tenant-rls-hierarchy` v1.21). Custom claim sozinho não cobre context-aware multi-tenant.
462
+
463
+ ## Ver também
464
+
465
+ - [supabase-rbac-implementer](../../agents/supabase-rbac-implementer.md) (v1.25) — canonical materializer
466
+ - [supabase-rls-policies](../supabase-rls-policies/SKILL.md) (v1.23) — section "RBAC via Custom Claims + authorize() function (v1.25)"
467
+ - [supabase-rls-defense-in-depth](../supabase-rls-defense-in-depth/SKILL.md) (v1.24) — Camada 9 (Auth Hooks - Custom Claims) v1.25
468
+ - [supabase-database-functions](../supabase-database-functions/SKILL.md) — pattern SECURITY DEFINER + supabase_auth_admin grants
469
+ - [supabase-auth-ssr](../supabase-auth-ssr/SKILL.md) — Next.js v16 + onAuthStateChange + jwt-decode integration
470
+ - [supabase-rls-hardener](../../agents/supabase-rls-hardener.md) (v1.23) — Detector 9 valida auth hook instalado (v1.25)
471
+ - [rbac-permissions-matrix-supabase](../rbac-permissions-matrix-supabase/SKILL.md) (v1.21) — alternativa via helper function STABLE (vs custom claim v1.25)
472
+ - [glossário compartilhado](../_shared-supabase/glossary.md) — termos custom claims, Custom Access Token Auth Hook, JWT user_role claim, authorize() function, supabase_auth_admin role, app_role enum, app_permission enum, jwt-decode client pattern