@luanpdd/kit-mcp 1.29.0 → 1.30.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -21
- package/README.md +168 -168
- package/gates/agent-no-recursive-dispatch.md +82 -82
- package/kit/COMANDOS.md +138 -138
- package/kit/README.md +76 -76
- package/kit/agents/advisor-researcher.md +106 -106
- package/kit/agents/assumptions-analyzer.md +107 -107
- package/kit/agents/audit-log-implementer.md +313 -313
- package/kit/agents/auditor-consistencia-isolamento.md +413 -413
- package/kit/agents/b2b-saas-architect.md +156 -156
- package/kit/agents/cascading-failures-auditor.md +298 -298
- package/kit/agents/codebase-mapper.md +768 -768
- package/kit/agents/crm-pipeline-implementer.md +256 -256
- package/kit/agents/debugger.md +813 -813
- package/kit/agents/detector-tenant-quente.md +337 -337
- package/kit/agents/evolution-go-integrator.md +200 -200
- package/kit/agents/example-reviewer.md +21 -21
- package/kit/agents/executor.md +564 -564
- package/kit/agents/integration-checker.md +200 -200
- package/kit/agents/invite-flow-implementer.md +189 -189
- package/kit/agents/legacy-characterizer.md +368 -368
- package/kit/agents/lgpd-compliance-auditor.md +295 -295
- package/kit/agents/multi-tenant-isolation-auditor.md +253 -253
- package/kit/agents/multi-tenant-rls-writer.md +340 -340
- package/kit/agents/nyquist-auditor.md +178 -178
- package/kit/agents/observability-coverage-auditor.md +315 -315
- package/kit/agents/org-onboarding-implementer.md +223 -223
- package/kit/agents/payload-capture-instrumenter.md +273 -273
- package/kit/agents/phase-researcher.md +696 -696
- package/kit/agents/plan-checker.md +272 -272
- package/kit/agents/planner.md +922 -922
- package/kit/agents/project-researcher.md +652 -652
- package/kit/agents/refactor-safety-auditor.md +404 -404
- package/kit/agents/research-synthesizer.md +245 -245
- package/kit/agents/roadmapper.md +677 -677
- package/kit/agents/seam-finder.md +359 -359
- package/kit/agents/shotgun-surgery-detector.md +349 -349
- package/kit/agents/supabase-branching-architect.md +562 -562
- package/kit/agents/supabase-cicd-pipeline-implementer.md +777 -777
- package/kit/agents/supabase-column-privileges-writer.md +399 -399
- package/kit/agents/supabase-edge-fn-tester.md +287 -0
- package/kit/agents/supabase-edge-fn-writer.md +239 -210
- package/kit/agents/supabase-migration-writer.md +385 -385
- package/kit/agents/supabase-rbac-implementer.md +392 -392
- package/kit/agents/supabase-realtime-implementer.md +363 -267
- package/kit/agents/supabase-rls-hardener.md +521 -521
- package/kit/agents/supabase-rls-writer.md +323 -323
- package/kit/agents/supabase-roles-implementer.md +355 -355
- package/kit/agents/super-admin-implementer.md +281 -281
- package/kit/agents/ui-auditor.md +437 -437
- package/kit/agents/ui-checker.md +302 -302
- package/kit/agents/ui-researcher.md +355 -355
- package/kit/agents/user-profiler.md +175 -175
- package/kit/agents/validador-evolucao-schema.md +335 -335
- package/kit/agents/verifier.md +728 -728
- package/kit/commands/adicionar-backlog.md +75 -75
- package/kit/commands/adicionar-fase.md +42 -42
- package/kit/commands/adicionar-tarefa.md +45 -45
- package/kit/commands/adicionar-testes.md +41 -41
- package/kit/commands/ajuda.md +21 -21
- package/kit/commands/atualizar.md +37 -37
- package/kit/commands/auditar-cascading.md +111 -111
- package/kit/commands/auditar-marco.md +179 -179
- package/kit/commands/auditar-observabilidade-cobertura.md +183 -183
- package/kit/commands/auditar-refactor.md +219 -219
- package/kit/commands/auditar-release.md +109 -109
- package/kit/commands/auditar-uat.md +23 -23
- package/kit/commands/autonomo.md +40 -40
- package/kit/commands/branch-pr.md +24 -24
- package/kit/commands/burn-rate-status.md +408 -408
- package/kit/commands/capturar-payloads.md +193 -193
- package/kit/commands/caracterizar.md +212 -212
- package/kit/commands/concluir-marco.md +247 -247
- package/kit/commands/configuracoes.md +36 -36
- package/kit/commands/dados-distribuidos.md +188 -188
- package/kit/commands/definir-perfil.md +10 -10
- package/kit/commands/depurar.md +190 -190
- package/kit/commands/detectar-duplicacao.md +197 -197
- package/kit/commands/discutir-fase.md +131 -131
- package/kit/commands/encontrar-seams.md +136 -136
- package/kit/commands/entrar-discord.md +17 -17
- package/kit/commands/estatisticas.md +18 -18
- package/kit/commands/example-greeting.md +33 -33
- package/kit/commands/executar-fase.md +58 -58
- package/kit/commands/expresso.md +56 -56
- package/kit/commands/fase-ui.md +34 -34
- package/kit/commands/fazer.md +57 -57
- package/kit/commands/fio.md +125 -125
- package/kit/commands/fluxos-trabalho.md +64 -64
- package/kit/commands/forense.md +176 -176
- package/kit/commands/gerenciador.md +38 -38
- package/kit/commands/inserir-fase.md +31 -31
- package/kit/commands/legacy.md +263 -263
- package/kit/commands/limpeza.md +17 -17
- package/kit/commands/listar-hipoteses-fase.md +45 -45
- package/kit/commands/listar-workspaces.md +18 -18
- package/kit/commands/load-shedding.md +117 -117
- package/kit/commands/mapear-codebase.md +70 -70
- package/kit/commands/multi-tenant.md +163 -163
- package/kit/commands/nota.md +33 -33
- package/kit/commands/novo-marco.md +43 -43
- package/kit/commands/novo-projeto.md +41 -41
- package/kit/commands/novo-workspace.md +43 -43
- package/kit/commands/pausar-trabalho.md +37 -37
- package/kit/commands/perfil-usuario.md +45 -45
- package/kit/commands/pesquisar-fase.md +195 -195
- package/kit/commands/planejar-fase.md +67 -67
- package/kit/commands/planejar-lacunas.md +33 -33
- package/kit/commands/plantar-ideia.md +25 -25
- package/kit/commands/progresso.md +24 -24
- package/kit/commands/proximo.md +30 -30
- package/kit/commands/publicar.md +490 -490
- package/kit/commands/rapido.md +35 -35
- package/kit/commands/reaplicar-patches.md +124 -124
- package/kit/commands/refactor-seguro.md +321 -321
- package/kit/commands/relatorio-sessao.md +19 -19
- package/kit/commands/remover-fase.md +31 -31
- package/kit/commands/remover-workspace.md +26 -26
- package/kit/commands/resumo-marco.md +50 -50
- package/kit/commands/retomar-trabalho.md +40 -40
- package/kit/commands/revisar-backlog.md +60 -60
- package/kit/commands/revisar-ui.md +32 -32
- package/kit/commands/revisar.md +37 -37
- package/kit/commands/saude.md +21 -21
- package/kit/commands/setup-notion.md +93 -93
- package/kit/commands/storytelling.md +179 -179
- package/kit/commands/supabase.md +30 -7
- package/kit/commands/sync-main.md +68 -68
- package/kit/commands/validar-fase.md +35 -35
- package/kit/commands/verificar-tarefas.md +44 -44
- package/kit/commands/verificar-trabalho.md +64 -64
- package/kit/file-manifest.json +15 -8
- package/kit/framework/bin/lib/commands.cjs +959 -959
- package/kit/framework/bin/lib/config.cjs +442 -442
- package/kit/framework/bin/lib/core.cjs +1230 -1230
- package/kit/framework/bin/lib/frontmatter.cjs +336 -336
- package/kit/framework/bin/lib/init.cjs +1442 -1442
- package/kit/framework/bin/lib/milestone.cjs +252 -252
- package/kit/framework/bin/lib/model-profiles.cjs +68 -68
- package/kit/framework/bin/lib/phase.cjs +888 -888
- package/kit/framework/bin/lib/profile-output.cjs +952 -952
- package/kit/framework/bin/lib/profile-pipeline.cjs +539 -539
- package/kit/framework/bin/lib/roadmap.cjs +329 -329
- package/kit/framework/bin/lib/security.cjs +382 -382
- package/kit/framework/bin/lib/state.cjs +1031 -1031
- package/kit/framework/bin/lib/template.cjs +222 -222
- package/kit/framework/bin/lib/uat.cjs +282 -282
- package/kit/framework/bin/lib/verify.cjs +888 -888
- package/kit/framework/bin/lib/workstream.cjs +491 -491
- package/kit/framework/bin/tools.cjs +918 -918
- package/kit/framework/commands/workstreams.md +63 -63
- package/kit/framework/references/checkpoints.md +778 -778
- package/kit/framework/references/continuation-format.md +249 -249
- package/kit/framework/references/decimal-phase-calculation.md +64 -64
- package/kit/framework/references/git-integration.md +295 -295
- package/kit/framework/references/git-planning-commit.md +38 -38
- package/kit/framework/references/model-profile-resolution.md +36 -36
- package/kit/framework/references/model-profiles.md +139 -139
- package/kit/framework/references/phase-argument-parsing.md +61 -61
- package/kit/framework/references/planning-config.md +202 -202
- package/kit/framework/references/questioning.md +162 -162
- package/kit/framework/references/tdd.md +263 -263
- package/kit/framework/references/ui-brand.md +160 -160
- package/kit/framework/references/user-profiling.md +657 -657
- package/kit/framework/references/verification-patterns.md +612 -612
- package/kit/framework/references/workstream-flag.md +58 -58
- package/kit/framework/templates/DEBUG.md +164 -164
- package/kit/framework/templates/UAT.md +265 -265
- package/kit/framework/templates/UI-SPEC.md +100 -100
- package/kit/framework/templates/VALIDATION.md +76 -76
- package/kit/framework/templates/claude-md.md +122 -122
- package/kit/framework/templates/codebase/architecture.md +185 -185
- package/kit/framework/templates/codebase/concerns.md +205 -205
- package/kit/framework/templates/codebase/conventions.md +204 -204
- package/kit/framework/templates/codebase/integrations.md +192 -192
- package/kit/framework/templates/codebase/stack.md +158 -158
- package/kit/framework/templates/codebase/structure.md +199 -199
- package/kit/framework/templates/codebase/testing.md +301 -301
- package/kit/framework/templates/config.json +44 -44
- package/kit/framework/templates/context.md +352 -352
- package/kit/framework/templates/continue-here.md +78 -78
- package/kit/framework/templates/copilot-instructions.md +7 -7
- package/kit/framework/templates/debug-subagent-prompt.md +91 -91
- package/kit/framework/templates/dev-preferences.md +20 -20
- package/kit/framework/templates/discovery.md +146 -146
- package/kit/framework/templates/discussion-log.md +63 -63
- package/kit/framework/templates/milestone-archive.md +123 -123
- package/kit/framework/templates/milestone.md +115 -115
- package/kit/framework/templates/phase-prompt.md +610 -610
- package/kit/framework/templates/planner-subagent-prompt.md +117 -117
- package/kit/framework/templates/project.md +186 -186
- package/kit/framework/templates/requirements.md +231 -231
- package/kit/framework/templates/research-project/ARCHITECTURE.md +204 -204
- package/kit/framework/templates/research-project/FEATURES.md +147 -147
- package/kit/framework/templates/research-project/PITFALLS.md +200 -200
- package/kit/framework/templates/research-project/STACK.md +120 -120
- package/kit/framework/templates/research-project/SUMMARY.md +170 -170
- package/kit/framework/templates/research.md +419 -419
- package/kit/framework/templates/retrospective.md +54 -54
- package/kit/framework/templates/roadmap.md +202 -202
- package/kit/framework/templates/state.md +176 -176
- package/kit/framework/templates/summary-complex.md +59 -59
- package/kit/framework/templates/summary-minimal.md +41 -41
- package/kit/framework/templates/summary-standard.md +48 -48
- package/kit/framework/templates/summary.md +209 -209
- package/kit/framework/templates/user-profile.md +146 -146
- package/kit/framework/templates/user-setup.md +256 -256
- package/kit/framework/templates/verification-report.md +258 -258
- package/kit/framework/workflows/add-phase.md +112 -112
- package/kit/framework/workflows/add-tests.md +351 -351
- package/kit/framework/workflows/add-todo.md +158 -158
- package/kit/framework/workflows/audit-milestone.md +340 -340
- package/kit/framework/workflows/audit-uat.md +109 -109
- package/kit/framework/workflows/autonomous.md +891 -891
- package/kit/framework/workflows/check-todos.md +177 -177
- package/kit/framework/workflows/cleanup.md +152 -152
- package/kit/framework/workflows/complete-milestone.md +696 -696
- package/kit/framework/workflows/diagnose-issues.md +231 -231
- package/kit/framework/workflows/discovery-phase.md +289 -289
- package/kit/framework/workflows/discuss-phase-assumptions.md +653 -653
- package/kit/framework/workflows/discuss-phase.md +784 -784
- package/kit/framework/workflows/do.md +104 -104
- package/kit/framework/workflows/execute-phase.md +838 -838
- package/kit/framework/workflows/execute-plan.md +510 -510
- package/kit/framework/workflows/fast.md +102 -102
- package/kit/framework/workflows/forensics.md +265 -265
- package/kit/framework/workflows/health.md +181 -181
- package/kit/framework/workflows/help.md +619 -619
- package/kit/framework/workflows/insert-phase.md +130 -130
- package/kit/framework/workflows/list-phase-assumptions.md +178 -178
- package/kit/framework/workflows/list-workspaces.md +56 -56
- package/kit/framework/workflows/manager.md +362 -362
- package/kit/framework/workflows/map-codebase.md +377 -377
- package/kit/framework/workflows/milestone-summary.md +223 -223
- package/kit/framework/workflows/new-milestone.md +486 -486
- package/kit/framework/workflows/new-project.md +1159 -1159
- package/kit/framework/workflows/new-workspace.md +237 -237
- package/kit/framework/workflows/next.md +97 -97
- package/kit/framework/workflows/node-repair.md +92 -92
- package/kit/framework/workflows/note.md +156 -156
- package/kit/framework/workflows/pause-work.md +176 -176
- package/kit/framework/workflows/plan-milestone-gaps.md +273 -273
- package/kit/framework/workflows/plan-phase.md +765 -765
- package/kit/framework/workflows/plant-seed.md +169 -169
- package/kit/framework/workflows/pr-branch.md +129 -129
- package/kit/framework/workflows/profile-user.md +450 -450
- package/kit/framework/workflows/progress.md +507 -507
- package/kit/framework/workflows/quick.md +757 -757
- package/kit/framework/workflows/remove-phase.md +155 -155
- package/kit/framework/workflows/remove-workspace.md +90 -90
- package/kit/framework/workflows/research-phase.md +82 -82
- package/kit/framework/workflows/resume-project.md +326 -326
- package/kit/framework/workflows/review.md +228 -228
- package/kit/framework/workflows/session-report.md +146 -146
- package/kit/framework/workflows/settings.md +283 -283
- package/kit/framework/workflows/ship.md +228 -228
- package/kit/framework/workflows/stats.md +60 -60
- package/kit/framework/workflows/transition.md +671 -671
- package/kit/framework/workflows/ui-phase.md +302 -302
- package/kit/framework/workflows/ui-review.md +165 -165
- package/kit/framework/workflows/update.md +323 -323
- package/kit/framework/workflows/validate-phase.md +174 -174
- package/kit/framework/workflows/verify-phase.md +252 -252
- package/kit/framework/workflows/verify-work.md +637 -637
- package/kit/hooks/check-update.js +118 -118
- package/kit/hooks/context-monitor.js +163 -163
- package/kit/hooks/kit-attribution-reminder.cjs +98 -0
- package/kit/hooks/prompt-guard.js +103 -103
- package/kit/hooks/statusline.js +125 -125
- package/kit/hooks/workflow-guard.js +101 -101
- package/kit/settings.json +45 -45
- package/kit/skills/_shared-supabase/glossary.md +17 -0
- package/kit/skills/ai-prompt-characterization/SKILL.md +335 -335
- package/kit/skills/armadilhas-sistemas-distribuidos/SKILL.md +447 -447
- package/kit/skills/audit-log-multi-tenant/SKILL.md +340 -340
- package/kit/skills/b2b-saas-architecture/SKILL.md +300 -300
- package/kit/skills/consistencia-leitura-replica/SKILL.md +385 -385
- package/kit/skills/crm-lead-pipeline-patterns/SKILL.md +343 -343
- package/kit/skills/escolha-modelo-consistencia/SKILL.md +494 -494
- package/kit/skills/evolucao-schema-compativel/SKILL.md +448 -448
- package/kit/skills/evolution-go-whatsapp-integration/SKILL.md +322 -322
- package/kit/skills/example-skill/SKILL.md +42 -42
- package/kit/skills/legacy-api-only-applications/SKILL.md +358 -358
- package/kit/skills/legacy-characterization-tests/SKILL.md +330 -330
- package/kit/skills/legacy-effect-analysis/SKILL.md +331 -331
- package/kit/skills/legacy-extract-class/SKILL.md +203 -203
- package/kit/skills/legacy-programming-by-difference/SKILL.md +252 -252
- package/kit/skills/legacy-seams-and-test-harness/SKILL.md +460 -460
- package/kit/skills/legacy-shotgun-surgery/SKILL.md +286 -286
- package/kit/skills/legacy-sprout-wrap-techniques/SKILL.md +434 -434
- package/kit/skills/legacy-storytelling-naked-crc/SKILL.md +270 -270
- package/kit/skills/lgpd-multi-tenant-compliance/SKILL.md +340 -340
- package/kit/skills/member-invite-flow/SKILL.md +305 -305
- package/kit/skills/member-management-react-shadcn/SKILL.md +328 -328
- package/kit/skills/multi-tenant-performance-scaling/SKILL.md +316 -316
- package/kit/skills/multi-tenant-rls-hierarchy/SKILL.md +342 -342
- package/kit/skills/org-onboarding-flow/SKILL.md +257 -257
- package/kit/skills/org-switcher-react-pattern/SKILL.md +349 -349
- package/kit/skills/permission-gate-react-pattern/SKILL.md +271 -271
- package/kit/skills/postgres-isolamento-concorrencia/SKILL.md +552 -552
- package/kit/skills/pre-refactor-characterization/SKILL.md +421 -421
- package/kit/skills/rbac-permissions-matrix-supabase/SKILL.md +338 -338
- package/kit/skills/streams-eventos-cdc/SKILL.md +711 -711
- package/kit/skills/supabase-branching-workflow/SKILL.md +544 -544
- package/kit/skills/supabase-ci-cd-github-actions/SKILL.md +880 -880
- package/kit/skills/supabase-column-level-security/SKILL.md +426 -426
- package/kit/skills/supabase-config-toml-remotes/SKILL.md +807 -807
- package/kit/skills/supabase-custom-claims-rbac/SKILL.md +472 -472
- package/kit/skills/supabase-edge-functions/SKILL.md +229 -141
- package/kit/skills/supabase-edge-functions-auth/SKILL.md +309 -0
- package/kit/skills/supabase-edge-functions-limits/SKILL.md +302 -0
- package/kit/skills/supabase-edge-functions-mcp-server/SKILL.md +279 -0
- package/kit/skills/supabase-edge-functions-testing/SKILL.md +277 -0
- package/kit/skills/supabase-edge-runtime-builtins/SKILL.md +357 -0
- package/kit/skills/supabase-migration-repair/SKILL.md +823 -823
- package/kit/skills/supabase-migrations/SKILL.md +297 -297
- package/kit/skills/supabase-pgtap-testing/SKILL.md +1053 -1053
- package/kit/skills/supabase-postgres-roles/SKILL.md +392 -392
- package/kit/skills/supabase-realtime/SKILL.md +460 -236
- package/kit/skills/supabase-rls-defense-in-depth/SKILL.md +418 -418
- package/kit/skills/supabase-rls-policies/SKILL.md +635 -635
- package/kit/skills/super-admin-platform-pattern/SKILL.md +326 -326
- package/kit/skills/tenant-quente-mitigacao/SKILL.md +605 -605
- package/kit/skills/whatsapp-conversation-state-machine/SKILL.md +287 -287
- package/package.json +1 -1
- package/src/core/kit.js +216 -216
- package/src/core/reflect.js +247 -247
- package/src/core/reverse-sync.js +372 -372
- package/src/core/sync.js +418 -418
- package/src/core/watch.js +121 -121
- package/src/mcp-server/index.js +715 -693
|
@@ -1,340 +1,340 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: audit-log-multi-tenant
|
|
3
|
-
description: Use ao implementar audit log em B2B SaaS multi-tenant Supabase…
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# Audit Log Multi-Tenant — Compliance + Forensics
|
|
7
|
-
|
|
8
|
-
## Quando usar
|
|
9
|
-
|
|
10
|
-
LLM carrega esta skill ao implementar audit log em B2B SaaS multi-tenant. Trigger phrases:
|
|
11
|
-
|
|
12
|
-
- "audit log multi-tenant", "audit trail compliance"
|
|
13
|
-
- "append-only table Postgres", "REVOKE DELETE UPDATE"
|
|
14
|
-
- "retention pg_cron tiers"
|
|
15
|
-
- "legal hold LGPD erasure"
|
|
16
|
-
- "PII sanitization audit"
|
|
17
|
-
- "event taxonomy canônica"
|
|
18
|
-
|
|
19
|
-
Esta skill é consumida pelo agent `audit-log-implementer` (Phase 109) que materializa migration + retention scheduler.
|
|
20
|
-
|
|
21
|
-
## Regras absolutas
|
|
22
|
-
|
|
23
|
-
**REGRA #1 (append-only):** Tabela `audit_logs` é **append-only** via `REVOKE DELETE, UPDATE ON public.audit_logs FROM authenticated`. Apenas service_role pode mutar (via partition swap, raramente). Comprometimento de admin não consegue apagar evidências.
|
|
24
|
-
|
|
25
|
-
**REGRA #2 (tenant_id obrigatório indexed first):** Toda row em `audit_logs` tem `tenant_id` (= `org_id`) **NOT NULL** + index composite `(tenant_id, created_at desc)` como **primeira** coluna. Sem isso, queries "todos eventos da org X" viram table scan.
|
|
26
|
-
|
|
27
|
-
**REGRA #3 (legal_hold flag):** Coluna `legal_hold boolean default false`. Quando user da org X faz DSR LGPD de erasure, marcar `legal_hold = true` em todas suas rows pendentes — bloqueia delete pelo retention scheduler até DSR processada.
|
|
28
|
-
|
|
29
|
-
**REGRA #4 (PII sanitization):** `actor_email` e `target_email` armazenados como **SHA-256 hash** (não raw). Para investigação, forensic mode rehasha email candidato e busca match. PII em log = LGPD violation.
|
|
30
|
-
|
|
31
|
-
**REGRA #5 (retention 3 tiers):** Default retention via pg_cron:
|
|
32
|
-
- **Free tier**: 30 dias
|
|
33
|
-
- **Pro tier**: 90 dias
|
|
34
|
-
- **Enterprise tier**: 365 dias
|
|
35
|
-
- **Sempre respeitando legal_hold = true** (skip rows com legal hold)
|
|
36
|
-
|
|
37
|
-
**REGRA #6 (event taxonomy mínima — 7 events):** `login`, `member_invited`, `role_changed`, `data_exported`, `member_removed`, `settings_changed`, `super_admin_action`. Custom events permitidos via campo `event_type text` mas estes 7 são obrigatórios em qualquer app B2B.
|
|
38
|
-
|
|
39
|
-
## Patterns canônicos
|
|
40
|
-
|
|
41
|
-
### Tabela `audit_logs` — DDL completo
|
|
42
|
-
|
|
43
|
-
```sql
|
|
44
|
-
-- Tabela append-only, particionada por tenant_id (LIST partitioning) se >50k rows/tenant
|
|
45
|
-
create table public.audit_logs (
|
|
46
|
-
id uuid not null default gen_random_uuid(),
|
|
47
|
-
tenant_id uuid not null references public.organizations(id) on delete cascade,
|
|
48
|
-
event_type text not null check (event_type ~ '^[a-z_]+$'),
|
|
49
|
-
actor_id uuid references auth.users(id) on delete set null, -- NULL após erasure
|
|
50
|
-
actor_email_hash text, -- SHA-256, REGRA #4
|
|
51
|
-
target_id uuid, -- ID do recurso afetado (lead, member, etc.)
|
|
52
|
-
target_type text, -- 'lead', 'member', 'org', etc.
|
|
53
|
-
target_email_hash text, -- SHA-256 do email se aplicável
|
|
54
|
-
payload jsonb, -- detalhes do evento (campos changed, etc.)
|
|
55
|
-
ip_address inet, -- IP de origem (opcional)
|
|
56
|
-
user_agent text, -- UA (opcional)
|
|
57
|
-
legal_hold boolean not null default false, -- REGRA #3
|
|
58
|
-
created_at timestamptz not null default now(),
|
|
59
|
-
primary key (id, tenant_id), -- composite para particionamento
|
|
60
|
-
-- REGRA #2: index composite tenant_id first
|
|
61
|
-
constraint event_type_canonical check (
|
|
62
|
-
event_type in (
|
|
63
|
-
'login', 'member_invited', 'role_changed', 'data_exported',
|
|
64
|
-
'member_removed', 'settings_changed', 'super_admin_action'
|
|
65
|
-
) or event_type ~ '^custom_[a-z_]+$' -- custom events com prefix
|
|
66
|
-
)
|
|
67
|
-
);
|
|
68
|
-
|
|
69
|
-
-- Index composite tenant_id first (REGRA #2)
|
|
70
|
-
create index audit_logs_tenant_created_idx
|
|
71
|
-
on public.audit_logs (tenant_id, created_at desc);
|
|
72
|
-
|
|
73
|
-
-- Index para busca por actor (compliance investigation)
|
|
74
|
-
create index audit_logs_actor_id_idx
|
|
75
|
-
on public.audit_logs (actor_id, created_at desc) where actor_id is not null;
|
|
76
|
-
|
|
77
|
-
-- Index para legal hold (rotina retention precisa filtrar)
|
|
78
|
-
create index audit_logs_legal_hold_idx
|
|
79
|
-
on public.audit_logs (legal_hold, created_at) where legal_hold = false;
|
|
80
|
-
|
|
81
|
-
-- REGRA #1: append-only — REVOKE DELETE e UPDATE
|
|
82
|
-
alter table public.audit_logs enable row level security;
|
|
83
|
-
|
|
84
|
-
revoke delete on public.audit_logs from authenticated, anon;
|
|
85
|
-
revoke update on public.audit_logs from authenticated, anon;
|
|
86
|
-
-- service_role bypassa RLS — partition swap futuro é o único delete legítimo
|
|
87
|
-
|
|
88
|
-
-- POLICY SELECT: members da org com permission view:audit_logs
|
|
89
|
-
create policy "audit_logs_select_with_permission"
|
|
90
|
-
on public.audit_logs
|
|
91
|
-
for select
|
|
92
|
-
to authenticated
|
|
93
|
-
using (
|
|
94
|
-
private.has_permission('view', 'audit_logs', tenant_id)
|
|
95
|
-
);
|
|
96
|
-
|
|
97
|
-
-- POLICY INSERT: qualquer authenticated pode inserir (helper function escreve via SECURITY DEFINER)
|
|
98
|
-
create policy "audit_logs_insert_authenticated"
|
|
99
|
-
on public.audit_logs
|
|
100
|
-
for insert
|
|
101
|
-
to authenticated
|
|
102
|
-
with check (
|
|
103
|
-
tenant_id is not null
|
|
104
|
-
and event_type is not null
|
|
105
|
-
);
|
|
106
|
-
|
|
107
|
-
-- POLICY super_admin bypass (PERMISSIVE)
|
|
108
|
-
create policy "audit_logs_super_admin_bypass"
|
|
109
|
-
on public.audit_logs
|
|
110
|
-
as permissive
|
|
111
|
-
for select
|
|
112
|
-
to authenticated
|
|
113
|
-
using (private.is_super_admin());
|
|
114
|
-
```
|
|
115
|
-
|
|
116
|
-
### Helper function — emit audit event
|
|
117
|
-
|
|
118
|
-
```sql
|
|
119
|
-
-- SECURITY DEFINER porque precisa hash email mesmo se user não tiver INSERT direto
|
|
120
|
-
create or replace function private.audit_log(
|
|
121
|
-
p_event_type text,
|
|
122
|
-
p_tenant_id uuid,
|
|
123
|
-
p_target_id uuid default null,
|
|
124
|
-
p_target_type text default null,
|
|
125
|
-
p_target_email text default null,
|
|
126
|
-
p_payload jsonb default null
|
|
127
|
-
)
|
|
128
|
-
returns void
|
|
129
|
-
language plpgsql
|
|
130
|
-
security definer
|
|
131
|
-
set search_path = ''
|
|
132
|
-
as $$
|
|
133
|
-
declare
|
|
134
|
-
v_actor_id uuid;
|
|
135
|
-
v_actor_email text;
|
|
136
|
-
begin
|
|
137
|
-
v_actor_id := (select auth.uid());
|
|
138
|
-
select email into v_actor_email from auth.users where id = v_actor_id;
|
|
139
|
-
|
|
140
|
-
insert into public.audit_logs (
|
|
141
|
-
tenant_id,
|
|
142
|
-
event_type,
|
|
143
|
-
actor_id,
|
|
144
|
-
actor_email_hash,
|
|
145
|
-
target_id,
|
|
146
|
-
target_type,
|
|
147
|
-
target_email_hash,
|
|
148
|
-
payload
|
|
149
|
-
)
|
|
150
|
-
values (
|
|
151
|
-
p_tenant_id,
|
|
152
|
-
p_event_type,
|
|
153
|
-
v_actor_id,
|
|
154
|
-
case when v_actor_email is not null then encode(digest(v_actor_email, 'sha256'), 'hex') end,
|
|
155
|
-
p_target_id,
|
|
156
|
-
p_target_type,
|
|
157
|
-
case when p_target_email is not null then encode(digest(p_target_email, 'sha256'), 'hex') end,
|
|
158
|
-
p_payload
|
|
159
|
-
);
|
|
160
|
-
end;
|
|
161
|
-
$$;
|
|
162
|
-
|
|
163
|
-
-- Permitir authenticated chamar
|
|
164
|
-
grant execute on function private.audit_log(text, uuid, uuid, text, text, jsonb) to authenticated;
|
|
165
|
-
```
|
|
166
|
-
|
|
167
|
-
### Retention via pg_cron — 3 tiers
|
|
168
|
-
|
|
169
|
-
```sql
|
|
170
|
-
-- Schedule diário 03:00 UTC — apaga rows velhas respeitando legal_hold
|
|
171
|
-
select cron.schedule(
|
|
172
|
-
'audit-log-retention',
|
|
173
|
-
'0 3 * * *',
|
|
174
|
-
$$
|
|
175
|
-
-- Free tier orgs: 30 dias
|
|
176
|
-
delete from public.audit_logs al
|
|
177
|
-
using public.organizations o
|
|
178
|
-
where al.tenant_id = o.id
|
|
179
|
-
and o.plan = 'free'
|
|
180
|
-
and al.created_at < now() - interval '30 days'
|
|
181
|
-
and al.legal_hold = false;
|
|
182
|
-
|
|
183
|
-
-- Pro tier orgs: 90 dias
|
|
184
|
-
delete from public.audit_logs al
|
|
185
|
-
using public.organizations o
|
|
186
|
-
where al.tenant_id = o.id
|
|
187
|
-
and o.plan = 'pro'
|
|
188
|
-
and al.created_at < now() - interval '90 days'
|
|
189
|
-
and al.legal_hold = false;
|
|
190
|
-
|
|
191
|
-
-- Enterprise tier orgs: 365 dias
|
|
192
|
-
delete from public.audit_logs al
|
|
193
|
-
using public.organizations o
|
|
194
|
-
where al.tenant_id = o.id
|
|
195
|
-
and o.plan = 'enterprise'
|
|
196
|
-
and al.created_at < now() - interval '365 days'
|
|
197
|
-
and al.legal_hold = false;
|
|
198
|
-
$$
|
|
199
|
-
);
|
|
200
|
-
```
|
|
201
|
-
|
|
202
|
-
**Nota:** o DELETE acima é executado pelo pg_cron com role `postgres` (bypassa RLS). NÃO é `authenticated` — REGRA #1 não é violada.
|
|
203
|
-
|
|
204
|
-
### Emit eventos canônicos — exemplos
|
|
205
|
-
|
|
206
|
-
```sql
|
|
207
|
-
-- Login (após auth callback)
|
|
208
|
-
select private.audit_log(
|
|
209
|
-
p_event_type := 'login',
|
|
210
|
-
p_tenant_id := <org_id>,
|
|
211
|
-
p_payload := jsonb_build_object('ip', '<ip>', 'user_agent', '<ua>')
|
|
212
|
-
);
|
|
213
|
-
|
|
214
|
-
-- Member invited
|
|
215
|
-
select private.audit_log(
|
|
216
|
-
p_event_type := 'member_invited',
|
|
217
|
-
p_tenant_id := <org_id>,
|
|
218
|
-
p_target_id := <invited_user_id>,
|
|
219
|
-
p_target_email := <invited_email>,
|
|
220
|
-
p_payload := jsonb_build_object('role', 'admin', 'invited_by', '<actor_email_hash>')
|
|
221
|
-
);
|
|
222
|
-
|
|
223
|
-
-- Role changed
|
|
224
|
-
select private.audit_log(
|
|
225
|
-
p_event_type := 'role_changed',
|
|
226
|
-
p_tenant_id := <org_id>,
|
|
227
|
-
p_target_id := <member_user_id>,
|
|
228
|
-
p_payload := jsonb_build_object('from_role', 'member', 'to_role', 'admin')
|
|
229
|
-
);
|
|
230
|
-
|
|
231
|
-
-- super_admin action (chamado pelo trigger gerado por multi-tenant-rls-writer)
|
|
232
|
-
select private.audit_log(
|
|
233
|
-
p_event_type := 'super_admin_action',
|
|
234
|
-
p_tenant_id := <target_org_id>,
|
|
235
|
-
p_payload := jsonb_build_object('table', 'leads', 'op', 'DELETE', 'reason', 'cleanup')
|
|
236
|
-
);
|
|
237
|
-
```
|
|
238
|
-
|
|
239
|
-
### Query forensics — investigar incident
|
|
240
|
-
|
|
241
|
-
```sql
|
|
242
|
-
-- "Quem deletou todos os leads da org X em 2026-04-15?"
|
|
243
|
-
select
|
|
244
|
-
al.created_at,
|
|
245
|
-
al.event_type,
|
|
246
|
-
al.actor_email_hash, -- hash, mas pode rehasher candidates
|
|
247
|
-
al.payload
|
|
248
|
-
from public.audit_logs al
|
|
249
|
-
where al.tenant_id = '<org_id>'
|
|
250
|
-
and al.created_at::date = '2026-04-15'
|
|
251
|
-
and al.event_type in ('super_admin_action', 'data_exported')
|
|
252
|
-
and al.payload->>'table' = 'leads'
|
|
253
|
-
order by al.created_at;
|
|
254
|
-
|
|
255
|
-
-- Match actor por email (forensics) — calcular hash do email candidato
|
|
256
|
-
select * from public.audit_logs
|
|
257
|
-
where actor_email_hash = encode(digest('admin@acme.com', 'sha256'), 'hex')
|
|
258
|
-
and tenant_id = '<org_id>'
|
|
259
|
-
order by created_at desc
|
|
260
|
-
limit 100;
|
|
261
|
-
```
|
|
262
|
-
|
|
263
|
-
## Anti-patterns
|
|
264
|
-
|
|
265
|
-
### Anti-pattern 1: Tabela audit_logs sem REVOKE
|
|
266
|
-
|
|
267
|
-
**Errado:**
|
|
268
|
-
```sql
|
|
269
|
-
create table public.audit_logs (...);
|
|
270
|
-
alter table public.audit_logs enable row level security;
|
|
271
|
-
-- Sem REVOKE — admin pode delete via UPDATE policy
|
|
272
|
-
```
|
|
273
|
-
|
|
274
|
-
**Por quê:** atacante ou admin compromised pode `DELETE FROM audit_logs WHERE event_type = 'super_admin_action'` e apagar evidências de acesso indevido.
|
|
275
|
-
|
|
276
|
-
**Certo:** `REVOKE DELETE, UPDATE ON public.audit_logs FROM authenticated, anon;`
|
|
277
|
-
|
|
278
|
-
### Anti-pattern 2: actor_email/target_email em raw
|
|
279
|
-
|
|
280
|
-
**Errado:**
|
|
281
|
-
```sql
|
|
282
|
-
create table public.audit_logs (
|
|
283
|
-
...
|
|
284
|
-
actor_email text, -- raw email
|
|
285
|
-
target_email text -- raw email
|
|
286
|
-
);
|
|
287
|
-
```
|
|
288
|
-
|
|
289
|
-
**Por quê:** PII em audit log = LGPD violation. DSR de erasure de user X torna-se complexo (precisa anonimizar todas as rows com email do user).
|
|
290
|
-
|
|
291
|
-
**Certo:** SHA-256 hash. Para forensics, rehasher email candidato e buscar match.
|
|
292
|
-
|
|
293
|
-
### Anti-pattern 3: Retention sem respeitar legal_hold
|
|
294
|
-
|
|
295
|
-
**Errado:**
|
|
296
|
-
```sql
|
|
297
|
-
delete from public.audit_logs where created_at < now() - interval '30 days';
|
|
298
|
-
-- Sem filter legal_hold
|
|
299
|
-
```
|
|
300
|
-
|
|
301
|
-
**Por quê:** apaga evidências necessárias para responder DSR LGPD em curso. Legal violation.
|
|
302
|
-
|
|
303
|
-
**Certo:** `... and legal_hold = false`. Quando DSR processada, marcar `legal_hold = false` permite next retention run.
|
|
304
|
-
|
|
305
|
-
### Anti-pattern 4: tenant_id ausente ou nullable
|
|
306
|
-
|
|
307
|
-
**Errado:**
|
|
308
|
-
```sql
|
|
309
|
-
tenant_id uuid -- nullable
|
|
310
|
-
```
|
|
311
|
-
|
|
312
|
-
**Por quê:** queries "todos eventos da org X" precisam filtrar por tenant_id. NULL quebra filter (`tenant_id = $1` exclui NULLs). Index composite (tenant_id, created_at) menos eficaz com NULLs.
|
|
313
|
-
|
|
314
|
-
**Certo:** `tenant_id uuid not null references public.organizations(id) on delete cascade`. NOT NULL + FK garante consistência.
|
|
315
|
-
|
|
316
|
-
### Anti-pattern 5: Single audit_logs sem partitioning para org grande
|
|
317
|
-
|
|
318
|
-
**Errado:**
|
|
319
|
-
```sql
|
|
320
|
-
-- Org enterprise com 10M events/ano em tabela única
|
|
321
|
-
```
|
|
322
|
-
|
|
323
|
-
**Por quê:** vacuum lento, queries lentas, retention DELETE bloqueia outras escritas.
|
|
324
|
-
|
|
325
|
-
**Certo:** LIST partitioning por `tenant_id` (ver [`multi-tenant-performance-scaling`](../multi-tenant-performance-scaling/SKILL.md)). Cada org = partição própria. Retention vira `DROP TABLE <partition>` (instantâneo).
|
|
326
|
-
|
|
327
|
-
## Semântica Event Sourcing + Log Compaction (v1.22+)
|
|
328
|
-
|
|
329
|
-
> A tabela `audit_log` append-only mapeia diretamente para padrão **event sourcing** (DDIA Ch 11) — eventos são source-of-truth, projeções (denormalizações via trigger ou MVs) derivam estado atual. Padrão completo em [`streams-eventos-cdc`](../streams-eventos-cdc/SKILL.md) (v1.22).
|
|
330
|
-
|
|
331
|
-
**Log compaction:** após retention TTL (30d/90d/365d), considerar snapshot periódico do estado dos aggregates antes de purgar — permite replay parcial sem perder histórico de longo prazo importante (legal hold).
|
|
332
|
-
|
|
333
|
-
## Ver também
|
|
334
|
-
|
|
335
|
-
- [supabase-cron-queues](../supabase-cron-queues/SKILL.md) — pg_cron pattern usado para retention scheduler (cross-suite)
|
|
336
|
-
- [multi-tenant-performance-scaling](../multi-tenant-performance-scaling/SKILL.md) — partitioning por org_id (REGRA #5)
|
|
337
|
-
- [multi-tenant-rls-hierarchy](../multi-tenant-rls-hierarchy/SKILL.md) — `private.is_super_admin` + super_admin trigger pattern
|
|
338
|
-
- [lgpd-multi-tenant-compliance](../lgpd-multi-tenant-compliance/SKILL.md) — Phase 114, integração com legal_hold
|
|
339
|
-
- [super-admin-platform-pattern](../super-admin-platform-pattern/SKILL.md) — Phase 111, `super_admin_action` event obrigatório
|
|
340
|
-
- [_shared-multi-tenant/glossary.md](../_shared-multi-tenant/glossary.md) — termos `audit log`, `event taxonomy`, `legal hold`, `PII sanitization`
|
|
1
|
+
---
|
|
2
|
+
name: audit-log-multi-tenant
|
|
3
|
+
description: Use ao implementar audit log em B2B SaaS multi-tenant Supabase…
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Audit Log Multi-Tenant — Compliance + Forensics
|
|
7
|
+
|
|
8
|
+
## Quando usar
|
|
9
|
+
|
|
10
|
+
LLM carrega esta skill ao implementar audit log em B2B SaaS multi-tenant. Trigger phrases:
|
|
11
|
+
|
|
12
|
+
- "audit log multi-tenant", "audit trail compliance"
|
|
13
|
+
- "append-only table Postgres", "REVOKE DELETE UPDATE"
|
|
14
|
+
- "retention pg_cron tiers"
|
|
15
|
+
- "legal hold LGPD erasure"
|
|
16
|
+
- "PII sanitization audit"
|
|
17
|
+
- "event taxonomy canônica"
|
|
18
|
+
|
|
19
|
+
Esta skill é consumida pelo agent `audit-log-implementer` (Phase 109) que materializa migration + retention scheduler.
|
|
20
|
+
|
|
21
|
+
## Regras absolutas
|
|
22
|
+
|
|
23
|
+
**REGRA #1 (append-only):** Tabela `audit_logs` é **append-only** via `REVOKE DELETE, UPDATE ON public.audit_logs FROM authenticated`. Apenas service_role pode mutar (via partition swap, raramente). Comprometimento de admin não consegue apagar evidências.
|
|
24
|
+
|
|
25
|
+
**REGRA #2 (tenant_id obrigatório indexed first):** Toda row em `audit_logs` tem `tenant_id` (= `org_id`) **NOT NULL** + index composite `(tenant_id, created_at desc)` como **primeira** coluna. Sem isso, queries "todos eventos da org X" viram table scan.
|
|
26
|
+
|
|
27
|
+
**REGRA #3 (legal_hold flag):** Coluna `legal_hold boolean default false`. Quando user da org X faz DSR LGPD de erasure, marcar `legal_hold = true` em todas suas rows pendentes — bloqueia delete pelo retention scheduler até DSR processada.
|
|
28
|
+
|
|
29
|
+
**REGRA #4 (PII sanitization):** `actor_email` e `target_email` armazenados como **SHA-256 hash** (não raw). Para investigação, forensic mode rehasha email candidato e busca match. PII em log = LGPD violation.
|
|
30
|
+
|
|
31
|
+
**REGRA #5 (retention 3 tiers):** Default retention via pg_cron:
|
|
32
|
+
- **Free tier**: 30 dias
|
|
33
|
+
- **Pro tier**: 90 dias
|
|
34
|
+
- **Enterprise tier**: 365 dias
|
|
35
|
+
- **Sempre respeitando legal_hold = true** (skip rows com legal hold)
|
|
36
|
+
|
|
37
|
+
**REGRA #6 (event taxonomy mínima — 7 events):** `login`, `member_invited`, `role_changed`, `data_exported`, `member_removed`, `settings_changed`, `super_admin_action`. Custom events permitidos via campo `event_type text` mas estes 7 são obrigatórios em qualquer app B2B.
|
|
38
|
+
|
|
39
|
+
## Patterns canônicos
|
|
40
|
+
|
|
41
|
+
### Tabela `audit_logs` — DDL completo
|
|
42
|
+
|
|
43
|
+
```sql
|
|
44
|
+
-- Tabela append-only, particionada por tenant_id (LIST partitioning) se >50k rows/tenant
|
|
45
|
+
create table public.audit_logs (
|
|
46
|
+
id uuid not null default gen_random_uuid(),
|
|
47
|
+
tenant_id uuid not null references public.organizations(id) on delete cascade,
|
|
48
|
+
event_type text not null check (event_type ~ '^[a-z_]+$'),
|
|
49
|
+
actor_id uuid references auth.users(id) on delete set null, -- NULL após erasure
|
|
50
|
+
actor_email_hash text, -- SHA-256, REGRA #4
|
|
51
|
+
target_id uuid, -- ID do recurso afetado (lead, member, etc.)
|
|
52
|
+
target_type text, -- 'lead', 'member', 'org', etc.
|
|
53
|
+
target_email_hash text, -- SHA-256 do email se aplicável
|
|
54
|
+
payload jsonb, -- detalhes do evento (campos changed, etc.)
|
|
55
|
+
ip_address inet, -- IP de origem (opcional)
|
|
56
|
+
user_agent text, -- UA (opcional)
|
|
57
|
+
legal_hold boolean not null default false, -- REGRA #3
|
|
58
|
+
created_at timestamptz not null default now(),
|
|
59
|
+
primary key (id, tenant_id), -- composite para particionamento
|
|
60
|
+
-- REGRA #2: index composite tenant_id first
|
|
61
|
+
constraint event_type_canonical check (
|
|
62
|
+
event_type in (
|
|
63
|
+
'login', 'member_invited', 'role_changed', 'data_exported',
|
|
64
|
+
'member_removed', 'settings_changed', 'super_admin_action'
|
|
65
|
+
) or event_type ~ '^custom_[a-z_]+$' -- custom events com prefix
|
|
66
|
+
)
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
-- Index composite tenant_id first (REGRA #2)
|
|
70
|
+
create index audit_logs_tenant_created_idx
|
|
71
|
+
on public.audit_logs (tenant_id, created_at desc);
|
|
72
|
+
|
|
73
|
+
-- Index para busca por actor (compliance investigation)
|
|
74
|
+
create index audit_logs_actor_id_idx
|
|
75
|
+
on public.audit_logs (actor_id, created_at desc) where actor_id is not null;
|
|
76
|
+
|
|
77
|
+
-- Index para legal hold (rotina retention precisa filtrar)
|
|
78
|
+
create index audit_logs_legal_hold_idx
|
|
79
|
+
on public.audit_logs (legal_hold, created_at) where legal_hold = false;
|
|
80
|
+
|
|
81
|
+
-- REGRA #1: append-only — REVOKE DELETE e UPDATE
|
|
82
|
+
alter table public.audit_logs enable row level security;
|
|
83
|
+
|
|
84
|
+
revoke delete on public.audit_logs from authenticated, anon;
|
|
85
|
+
revoke update on public.audit_logs from authenticated, anon;
|
|
86
|
+
-- service_role bypassa RLS — partition swap futuro é o único delete legítimo
|
|
87
|
+
|
|
88
|
+
-- POLICY SELECT: members da org com permission view:audit_logs
|
|
89
|
+
create policy "audit_logs_select_with_permission"
|
|
90
|
+
on public.audit_logs
|
|
91
|
+
for select
|
|
92
|
+
to authenticated
|
|
93
|
+
using (
|
|
94
|
+
private.has_permission('view', 'audit_logs', tenant_id)
|
|
95
|
+
);
|
|
96
|
+
|
|
97
|
+
-- POLICY INSERT: qualquer authenticated pode inserir (helper function escreve via SECURITY DEFINER)
|
|
98
|
+
create policy "audit_logs_insert_authenticated"
|
|
99
|
+
on public.audit_logs
|
|
100
|
+
for insert
|
|
101
|
+
to authenticated
|
|
102
|
+
with check (
|
|
103
|
+
tenant_id is not null
|
|
104
|
+
and event_type is not null
|
|
105
|
+
);
|
|
106
|
+
|
|
107
|
+
-- POLICY super_admin bypass (PERMISSIVE)
|
|
108
|
+
create policy "audit_logs_super_admin_bypass"
|
|
109
|
+
on public.audit_logs
|
|
110
|
+
as permissive
|
|
111
|
+
for select
|
|
112
|
+
to authenticated
|
|
113
|
+
using (private.is_super_admin());
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
### Helper function — emit audit event
|
|
117
|
+
|
|
118
|
+
```sql
|
|
119
|
+
-- SECURITY DEFINER porque precisa hash email mesmo se user não tiver INSERT direto
|
|
120
|
+
create or replace function private.audit_log(
|
|
121
|
+
p_event_type text,
|
|
122
|
+
p_tenant_id uuid,
|
|
123
|
+
p_target_id uuid default null,
|
|
124
|
+
p_target_type text default null,
|
|
125
|
+
p_target_email text default null,
|
|
126
|
+
p_payload jsonb default null
|
|
127
|
+
)
|
|
128
|
+
returns void
|
|
129
|
+
language plpgsql
|
|
130
|
+
security definer
|
|
131
|
+
set search_path = ''
|
|
132
|
+
as $$
|
|
133
|
+
declare
|
|
134
|
+
v_actor_id uuid;
|
|
135
|
+
v_actor_email text;
|
|
136
|
+
begin
|
|
137
|
+
v_actor_id := (select auth.uid());
|
|
138
|
+
select email into v_actor_email from auth.users where id = v_actor_id;
|
|
139
|
+
|
|
140
|
+
insert into public.audit_logs (
|
|
141
|
+
tenant_id,
|
|
142
|
+
event_type,
|
|
143
|
+
actor_id,
|
|
144
|
+
actor_email_hash,
|
|
145
|
+
target_id,
|
|
146
|
+
target_type,
|
|
147
|
+
target_email_hash,
|
|
148
|
+
payload
|
|
149
|
+
)
|
|
150
|
+
values (
|
|
151
|
+
p_tenant_id,
|
|
152
|
+
p_event_type,
|
|
153
|
+
v_actor_id,
|
|
154
|
+
case when v_actor_email is not null then encode(digest(v_actor_email, 'sha256'), 'hex') end,
|
|
155
|
+
p_target_id,
|
|
156
|
+
p_target_type,
|
|
157
|
+
case when p_target_email is not null then encode(digest(p_target_email, 'sha256'), 'hex') end,
|
|
158
|
+
p_payload
|
|
159
|
+
);
|
|
160
|
+
end;
|
|
161
|
+
$$;
|
|
162
|
+
|
|
163
|
+
-- Permitir authenticated chamar
|
|
164
|
+
grant execute on function private.audit_log(text, uuid, uuid, text, text, jsonb) to authenticated;
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
### Retention via pg_cron — 3 tiers
|
|
168
|
+
|
|
169
|
+
```sql
|
|
170
|
+
-- Schedule diário 03:00 UTC — apaga rows velhas respeitando legal_hold
|
|
171
|
+
select cron.schedule(
|
|
172
|
+
'audit-log-retention',
|
|
173
|
+
'0 3 * * *',
|
|
174
|
+
$$
|
|
175
|
+
-- Free tier orgs: 30 dias
|
|
176
|
+
delete from public.audit_logs al
|
|
177
|
+
using public.organizations o
|
|
178
|
+
where al.tenant_id = o.id
|
|
179
|
+
and o.plan = 'free'
|
|
180
|
+
and al.created_at < now() - interval '30 days'
|
|
181
|
+
and al.legal_hold = false;
|
|
182
|
+
|
|
183
|
+
-- Pro tier orgs: 90 dias
|
|
184
|
+
delete from public.audit_logs al
|
|
185
|
+
using public.organizations o
|
|
186
|
+
where al.tenant_id = o.id
|
|
187
|
+
and o.plan = 'pro'
|
|
188
|
+
and al.created_at < now() - interval '90 days'
|
|
189
|
+
and al.legal_hold = false;
|
|
190
|
+
|
|
191
|
+
-- Enterprise tier orgs: 365 dias
|
|
192
|
+
delete from public.audit_logs al
|
|
193
|
+
using public.organizations o
|
|
194
|
+
where al.tenant_id = o.id
|
|
195
|
+
and o.plan = 'enterprise'
|
|
196
|
+
and al.created_at < now() - interval '365 days'
|
|
197
|
+
and al.legal_hold = false;
|
|
198
|
+
$$
|
|
199
|
+
);
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
**Nota:** o DELETE acima é executado pelo pg_cron com role `postgres` (bypassa RLS). NÃO é `authenticated` — REGRA #1 não é violada.
|
|
203
|
+
|
|
204
|
+
### Emit eventos canônicos — exemplos
|
|
205
|
+
|
|
206
|
+
```sql
|
|
207
|
+
-- Login (após auth callback)
|
|
208
|
+
select private.audit_log(
|
|
209
|
+
p_event_type := 'login',
|
|
210
|
+
p_tenant_id := <org_id>,
|
|
211
|
+
p_payload := jsonb_build_object('ip', '<ip>', 'user_agent', '<ua>')
|
|
212
|
+
);
|
|
213
|
+
|
|
214
|
+
-- Member invited
|
|
215
|
+
select private.audit_log(
|
|
216
|
+
p_event_type := 'member_invited',
|
|
217
|
+
p_tenant_id := <org_id>,
|
|
218
|
+
p_target_id := <invited_user_id>,
|
|
219
|
+
p_target_email := <invited_email>,
|
|
220
|
+
p_payload := jsonb_build_object('role', 'admin', 'invited_by', '<actor_email_hash>')
|
|
221
|
+
);
|
|
222
|
+
|
|
223
|
+
-- Role changed
|
|
224
|
+
select private.audit_log(
|
|
225
|
+
p_event_type := 'role_changed',
|
|
226
|
+
p_tenant_id := <org_id>,
|
|
227
|
+
p_target_id := <member_user_id>,
|
|
228
|
+
p_payload := jsonb_build_object('from_role', 'member', 'to_role', 'admin')
|
|
229
|
+
);
|
|
230
|
+
|
|
231
|
+
-- super_admin action (chamado pelo trigger gerado por multi-tenant-rls-writer)
|
|
232
|
+
select private.audit_log(
|
|
233
|
+
p_event_type := 'super_admin_action',
|
|
234
|
+
p_tenant_id := <target_org_id>,
|
|
235
|
+
p_payload := jsonb_build_object('table', 'leads', 'op', 'DELETE', 'reason', 'cleanup')
|
|
236
|
+
);
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
### Query forensics — investigar incident
|
|
240
|
+
|
|
241
|
+
```sql
|
|
242
|
+
-- "Quem deletou todos os leads da org X em 2026-04-15?"
|
|
243
|
+
select
|
|
244
|
+
al.created_at,
|
|
245
|
+
al.event_type,
|
|
246
|
+
al.actor_email_hash, -- hash, mas pode rehasher candidates
|
|
247
|
+
al.payload
|
|
248
|
+
from public.audit_logs al
|
|
249
|
+
where al.tenant_id = '<org_id>'
|
|
250
|
+
and al.created_at::date = '2026-04-15'
|
|
251
|
+
and al.event_type in ('super_admin_action', 'data_exported')
|
|
252
|
+
and al.payload->>'table' = 'leads'
|
|
253
|
+
order by al.created_at;
|
|
254
|
+
|
|
255
|
+
-- Match actor por email (forensics) — calcular hash do email candidato
|
|
256
|
+
select * from public.audit_logs
|
|
257
|
+
where actor_email_hash = encode(digest('admin@acme.com', 'sha256'), 'hex')
|
|
258
|
+
and tenant_id = '<org_id>'
|
|
259
|
+
order by created_at desc
|
|
260
|
+
limit 100;
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
## Anti-patterns
|
|
264
|
+
|
|
265
|
+
### Anti-pattern 1: Tabela audit_logs sem REVOKE
|
|
266
|
+
|
|
267
|
+
**Errado:**
|
|
268
|
+
```sql
|
|
269
|
+
create table public.audit_logs (...);
|
|
270
|
+
alter table public.audit_logs enable row level security;
|
|
271
|
+
-- Sem REVOKE — admin pode delete via UPDATE policy
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
**Por quê:** atacante ou admin compromised pode `DELETE FROM audit_logs WHERE event_type = 'super_admin_action'` e apagar evidências de acesso indevido.
|
|
275
|
+
|
|
276
|
+
**Certo:** `REVOKE DELETE, UPDATE ON public.audit_logs FROM authenticated, anon;`
|
|
277
|
+
|
|
278
|
+
### Anti-pattern 2: actor_email/target_email em raw
|
|
279
|
+
|
|
280
|
+
**Errado:**
|
|
281
|
+
```sql
|
|
282
|
+
create table public.audit_logs (
|
|
283
|
+
...
|
|
284
|
+
actor_email text, -- raw email
|
|
285
|
+
target_email text -- raw email
|
|
286
|
+
);
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
**Por quê:** PII em audit log = LGPD violation. DSR de erasure de user X torna-se complexo (precisa anonimizar todas as rows com email do user).
|
|
290
|
+
|
|
291
|
+
**Certo:** SHA-256 hash. Para forensics, rehasher email candidato e buscar match.
|
|
292
|
+
|
|
293
|
+
### Anti-pattern 3: Retention sem respeitar legal_hold
|
|
294
|
+
|
|
295
|
+
**Errado:**
|
|
296
|
+
```sql
|
|
297
|
+
delete from public.audit_logs where created_at < now() - interval '30 days';
|
|
298
|
+
-- Sem filter legal_hold
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
**Por quê:** apaga evidências necessárias para responder DSR LGPD em curso. Legal violation.
|
|
302
|
+
|
|
303
|
+
**Certo:** `... and legal_hold = false`. Quando DSR processada, marcar `legal_hold = false` permite next retention run.
|
|
304
|
+
|
|
305
|
+
### Anti-pattern 4: tenant_id ausente ou nullable
|
|
306
|
+
|
|
307
|
+
**Errado:**
|
|
308
|
+
```sql
|
|
309
|
+
tenant_id uuid -- nullable
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
**Por quê:** queries "todos eventos da org X" precisam filtrar por tenant_id. NULL quebra filter (`tenant_id = $1` exclui NULLs). Index composite (tenant_id, created_at) menos eficaz com NULLs.
|
|
313
|
+
|
|
314
|
+
**Certo:** `tenant_id uuid not null references public.organizations(id) on delete cascade`. NOT NULL + FK garante consistência.
|
|
315
|
+
|
|
316
|
+
### Anti-pattern 5: Single audit_logs sem partitioning para org grande
|
|
317
|
+
|
|
318
|
+
**Errado:**
|
|
319
|
+
```sql
|
|
320
|
+
-- Org enterprise com 10M events/ano em tabela única
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
**Por quê:** vacuum lento, queries lentas, retention DELETE bloqueia outras escritas.
|
|
324
|
+
|
|
325
|
+
**Certo:** LIST partitioning por `tenant_id` (ver [`multi-tenant-performance-scaling`](../multi-tenant-performance-scaling/SKILL.md)). Cada org = partição própria. Retention vira `DROP TABLE <partition>` (instantâneo).
|
|
326
|
+
|
|
327
|
+
## Semântica Event Sourcing + Log Compaction (v1.22+)
|
|
328
|
+
|
|
329
|
+
> A tabela `audit_log` append-only mapeia diretamente para padrão **event sourcing** (DDIA Ch 11) — eventos são source-of-truth, projeções (denormalizações via trigger ou MVs) derivam estado atual. Padrão completo em [`streams-eventos-cdc`](../streams-eventos-cdc/SKILL.md) (v1.22).
|
|
330
|
+
|
|
331
|
+
**Log compaction:** após retention TTL (30d/90d/365d), considerar snapshot periódico do estado dos aggregates antes de purgar — permite replay parcial sem perder histórico de longo prazo importante (legal hold).
|
|
332
|
+
|
|
333
|
+
## Ver também
|
|
334
|
+
|
|
335
|
+
- [supabase-cron-queues](../supabase-cron-queues/SKILL.md) — pg_cron pattern usado para retention scheduler (cross-suite)
|
|
336
|
+
- [multi-tenant-performance-scaling](../multi-tenant-performance-scaling/SKILL.md) — partitioning por org_id (REGRA #5)
|
|
337
|
+
- [multi-tenant-rls-hierarchy](../multi-tenant-rls-hierarchy/SKILL.md) — `private.is_super_admin` + super_admin trigger pattern
|
|
338
|
+
- [lgpd-multi-tenant-compliance](../lgpd-multi-tenant-compliance/SKILL.md) — Phase 114, integração com legal_hold
|
|
339
|
+
- [super-admin-platform-pattern](../super-admin-platform-pattern/SKILL.md) — Phase 111, `super_admin_action` event obrigatório
|
|
340
|
+
- [_shared-multi-tenant/glossary.md](../_shared-multi-tenant/glossary.md) — termos `audit log`, `event taxonomy`, `legal hold`, `PII sanitization`
|