@luanpdd/kit-mcp 1.21.0 → 1.22.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -21
- package/README.md +648 -648
- package/kit/COMANDOS.md +138 -138
- package/kit/README.md +76 -52
- package/kit/agents/advisor-researcher.md +106 -106
- package/kit/agents/assumptions-analyzer.md +107 -107
- package/kit/agents/auditor-consistencia-isolamento.md +380 -0
- package/kit/agents/codebase-mapper.md +768 -768
- package/kit/agents/crm-pipeline-implementer.md +17 -0
- package/kit/agents/debugger.md +772 -772
- package/kit/agents/detector-tenant-quente.md +337 -0
- package/kit/agents/example-reviewer.md +21 -21
- package/kit/agents/executor.md +523 -523
- package/kit/agents/integration-checker.md +200 -200
- package/kit/agents/multi-tenant-isolation-auditor.md +10 -0
- package/kit/agents/nyquist-auditor.md +178 -178
- package/kit/agents/phase-researcher.md +696 -696
- package/kit/agents/plan-checker.md +272 -272
- package/kit/agents/planner.md +891 -891
- package/kit/agents/project-researcher.md +652 -652
- package/kit/agents/research-synthesizer.md +245 -245
- package/kit/agents/roadmapper.md +677 -677
- package/kit/agents/supabase-architect.md +10 -0
- package/kit/agents/supabase-migration-writer.md +12 -0
- 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 -0
- 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-marco.md +179 -179
- 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/concluir-marco.md +247 -247
- package/kit/commands/configuracoes.md +36 -36
- package/kit/commands/dados-distribuidos.md +188 -0
- package/kit/commands/definir-perfil.md +10 -10
- package/kit/commands/depurar.md +190 -190
- package/kit/commands/discutir-fase.md +131 -131
- 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/limpeza.md +17 -17
- package/kit/commands/listar-hipoteses-fase.md +45 -45
- package/kit/commands/listar-workspaces.md +18 -18
- package/kit/commands/mapear-codebase.md +70 -70
- 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/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/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 +27 -15
- 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/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-dados-distribuidos/glossary.md +224 -0
- package/kit/skills/armadilhas-sistemas-distribuidos/SKILL.md +447 -0
- package/kit/skills/audit-log-multi-tenant/SKILL.md +6 -0
- package/kit/skills/cascading-failures/SKILL.md +4 -0
- package/kit/skills/consistencia-leitura-replica/SKILL.md +385 -0
- package/kit/skills/crm-lead-pipeline-patterns/SKILL.md +17 -0
- package/kit/skills/escolha-modelo-consistencia/SKILL.md +495 -0
- package/kit/skills/evolucao-schema-compativel/SKILL.md +448 -0
- package/kit/skills/example-skill/SKILL.md +42 -42
- package/kit/skills/multi-tenant-performance-scaling/SKILL.md +4 -0
- package/kit/skills/multi-tenant-rls-hierarchy/SKILL.md +4 -0
- package/kit/skills/postgres-isolamento-concorrencia/SKILL.md +552 -0
- package/kit/skills/streams-eventos-cdc/SKILL.md +712 -0
- package/kit/skills/supabase-cron-queues/SKILL.md +9 -0
- package/kit/skills/supabase-migrations/SKILL.md +10 -0
- package/kit/skills/super-admin-platform-pattern/SKILL.md +4 -0
- package/kit/skills/tenant-quente-mitigacao/SKILL.md +605 -0
- package/package.json +63 -63
- 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
|
@@ -0,0 +1,495 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: escolha-modelo-consistencia
|
|
3
|
+
description: Use ao desenhar feature distribuída em Supabase decidindo o modelo de consistência — árvore de decisão linearizabilidade (uniqueness constraint cross-tenant, slug global) vs causal (chat, comentários) vs eventual (feed social, métricas), uniqueness via single-leader Postgres (UNIQUE constraint nativo, app-level UPDATE+SELECT é race), análogos total order broadcast em Postgres (logical replication slots, posição WAL, pg_logical_emit_message), CAP/PACELC mapeado ao real, limitações 2PC + alternativas modernas (sagas, transactional outbox).
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Escolha de Modelo de Consistência — Decision Tree + Patterns Postgres
|
|
7
|
+
|
|
8
|
+
## Quando usar
|
|
9
|
+
|
|
10
|
+
LLM carrega esta skill ao desenhar feature distribuída em Supabase + Postgres com necessidade de escolher um modelo de consistência. Trigger phrases:
|
|
11
|
+
|
|
12
|
+
- "qual modelo de consistência usar", "linearizabilidade vs causal vs eventual"
|
|
13
|
+
- "uniqueness constraint cross-tenant", "slug único global"
|
|
14
|
+
- "ordem causal de eventos", "consistência forte vs eventual"
|
|
15
|
+
- "CAP teorema Postgres", "PACELC trade-off"
|
|
16
|
+
- "2PC alternativas", "saga pattern", "transactional outbox"
|
|
17
|
+
- "total order broadcast Postgres", "WAL position monotônica"
|
|
18
|
+
- "race condition em INSERT app-level", "ON CONFLICT vs SELECT FOR UPDATE"
|
|
19
|
+
|
|
20
|
+
Esta skill **estende** [`audit-log-multi-tenant`](../audit-log-multi-tenant/SKILL.md) (v1.21) ao usar o pattern transactional outbox para garantir publish atomic com write principal, e [`supabase-cron-queues`](../supabase-cron-queues/SKILL.md) (v1.8) ao consumir pgmq como destino do outbox.
|
|
21
|
+
|
|
22
|
+
Material-fonte: *Designing Data-Intensive Applications*, Martin Kleppmann (O'Reilly 2017), capítulo 9 "Consistency and Consensus" (linhas 13198-15600 do material extraído; summary 15323-15425). Termos canônicos PT-BR ↔ EN definidos em [`../_shared-dados-distribuidos/glossary.md`](../_shared-dados-distribuidos/glossary.md) seções (a) e (f).
|
|
23
|
+
|
|
24
|
+
## Regras absolutas
|
|
25
|
+
|
|
26
|
+
**REGRA #1 (uniqueness cross-tenant exige linearizabilidade):** Slug global, license key, custom domain — qualquer chave que precise ser **única em todo o sistema** (não apenas dentro de um tenant) **DEVE** usar `UNIQUE` constraint nativo Postgres no líder. Eventual consistency permite duplicatas durante janela de divergência; aceitável APENAS se não houver invariante de unicidade.
|
|
27
|
+
|
|
28
|
+
**REGRA #2 (NUNCA usar UPDATE+SELECT app-level para uniqueness):** O padrão `SELECT id FROM users WHERE slug = $1; if (!exists) INSERT ...` é **race condition garantida**, mesmo com `SELECT FOR UPDATE`. Dois clientes podem chegar simultaneamente, cada um fazer SELECT, ambos verem ausência da row, ambos fazerem INSERT — conflito ou duplicata. Padrão correto: deixar o `UNIQUE` constraint disparar erro via `INSERT ... ON CONFLICT DO NOTHING RETURNING id`.
|
|
29
|
+
|
|
30
|
+
**REGRA #3 (ordem causal não basta para invariantes globais):** Consistência causal preserva A causa B — boa para chat ("resposta após pergunta"), comentários ("reply após post"). NÃO basta para invariantes globais como "saldo da conta nunca negativo" ou "no máximo 100 licenças vendidas". Esses exigem linearizabilidade.
|
|
31
|
+
|
|
32
|
+
**REGRA #4 (CAP é trade-off PARTICIONADO; PACELC inclui caso normal):** Durante partição de rede o sistema escolhe Consistência (rejeita writes) OU Disponibilidade (aceita writes em ambos lados, divergência). PACELC adiciona o caso normal (sem partição): escolher Latência (rede async) ou Consistência (sync replication). Postgres single-leader = **CP/PC** — rejeita writes durante partition do líder; latência alta para garantir consistência sync.
|
|
33
|
+
|
|
34
|
+
**REGRA #5 (2PC é blocking — prefira sagas ou transactional outbox para distribuídas):** 2PC (two-phase commit) trava recursos se coordinator morre entre prepare e commit (resource locks held forever). Não tem heuristic recovery automática. **Alternativas canônicas modernas**: (a) **Sagas** — transações locais com `compensate()` reverso; (b) **Transactional outbox** — write DB + event no outbox em mesma transação local, async worker publica.
|
|
35
|
+
|
|
36
|
+
**REGRA #6 (eventual consistency exige convergência testável):** Se escolher eventual consistency (feed social, contadores, métricas analytics), é obrigatório ter **teste de convergência**: simular partition + writes em ambos lados + healing → verificar que ambos lados convergem ao mesmo estado em tempo limitado. Sem teste = bug latente que aparece em produção.
|
|
37
|
+
|
|
38
|
+
## Patterns canônicos
|
|
39
|
+
|
|
40
|
+
### REQ MODELO-01 — Árvore de decisão linearizabilidade vs causal vs eventual
|
|
41
|
+
|
|
42
|
+
```
|
|
43
|
+
┌─────────────────────────────────────────────┐
|
|
44
|
+
│ A operação precisa ver TODAS escritas │
|
|
45
|
+
│ anteriores como atomic ordered global? │
|
|
46
|
+
└─────────────────────────────────────────────┘
|
|
47
|
+
│ │
|
|
48
|
+
Sim │ │ Não
|
|
49
|
+
▼ ▼
|
|
50
|
+
┌──────────────────────┐ ┌─────────────────────────────────┐
|
|
51
|
+
│ LINEARIZABILIDADE │ │ Existe relação causal A causa B │
|
|
52
|
+
│ (single-leader, │ │ que precisa ser preservada? │
|
|
53
|
+
│ UNIQUE constraint) │ └─────────────────────────────────┘
|
|
54
|
+
└──────────────────────┘ │ │
|
|
55
|
+
Sim │ │ Não
|
|
56
|
+
▼ ▼
|
|
57
|
+
┌──────────────────┐ ┌────────────────┐
|
|
58
|
+
│ CAUSAL │ │ EVENTUAL │
|
|
59
|
+
│ (chat, │ │ (feed social, │
|
|
60
|
+
│ comentários) │ │ contadores, │
|
|
61
|
+
└──────────────────┘ │ métricas) │
|
|
62
|
+
└────────────────┘
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
**3 exemplos canônicos por modelo:**
|
|
66
|
+
|
|
67
|
+
| Modelo | Exemplo 1 | Exemplo 2 | Exemplo 3 |
|
|
68
|
+
|---|---|---|---|
|
|
69
|
+
| **Linearizabilidade** | Slug global de organização (`acme-corp`) — único cross-tenant | License key — única em todo o sistema | Custom domain — único globalmente |
|
|
70
|
+
| **Causal** | Chat — pergunta causa resposta, ordem importa para mesmo participante | Comentários em post — reply após post | Issue tracker — comentário após mudança de status |
|
|
71
|
+
| **Eventual** | Feed social timeline — posts podem aparecer fora de ordem entre devices | Contadores de likes/views — eventual ≠ exato OK | Métricas analytics — agregação eventual OK |
|
|
72
|
+
|
|
73
|
+
**Trade-offs em latência:**
|
|
74
|
+
|
|
75
|
+
- **Linearizabilidade**: latência alta (round-trip ao líder), throughput limitado pelo líder
|
|
76
|
+
- **Causal**: latência média, mais paralelizável (réplica responde se já viu causalmente o write requerido)
|
|
77
|
+
- **Eventual**: latência mínima (réplica mais próxima), throughput máximo
|
|
78
|
+
|
|
79
|
+
### REQ MODELO-02 — Uniqueness constraints distribuídos via single-leader Postgres
|
|
80
|
+
|
|
81
|
+
`UNIQUE` constraint nativo Postgres é **linearizável** porque o single-leader é a fonte da verdade — todas as writes passam pelo mesmo node, conflito é detectado atomicamente.
|
|
82
|
+
|
|
83
|
+
```sql
|
|
84
|
+
-- Schema canônico — slug global cross-tenant (DDIA Ch 9 — uniqueness exige consenso)
|
|
85
|
+
create table public.organizations (
|
|
86
|
+
id uuid primary key default gen_random_uuid(),
|
|
87
|
+
slug text not null unique, -- UNIQUE = linearizável (single-leader)
|
|
88
|
+
name text not null,
|
|
89
|
+
created_at timestamptz not null default now()
|
|
90
|
+
);
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
**ANTI-PATTERN (race condition garantida):**
|
|
94
|
+
|
|
95
|
+
```sql
|
|
96
|
+
-- ❌ ERRADO — race entre SELECT e INSERT
|
|
97
|
+
-- Cliente A
|
|
98
|
+
begin;
|
|
99
|
+
select id from public.organizations where slug = 'acme-corp' for update;
|
|
100
|
+
-- Suponha que retorna 0 rows
|
|
101
|
+
-- Cliente B nesse instante faz a mesma query — também retorna 0 rows
|
|
102
|
+
insert into public.organizations (slug, name) values ('acme-corp', 'ACME Corp');
|
|
103
|
+
-- Cliente B também insere → ERRO 23505 OU duplicate (se sem UNIQUE)
|
|
104
|
+
commit;
|
|
105
|
+
|
|
106
|
+
-- Mesmo com SELECT FOR UPDATE, o lock é por row existente
|
|
107
|
+
-- Se a row NÃO existe ainda, não há nada para lockar
|
|
108
|
+
-- Postgres NÃO oferece "lock the absence of a row" sem uma row sentinel
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
**Por quê:** `SELECT FOR UPDATE` lockeia rows existentes. Quando `WHERE slug = $1` retorna 0 rows, **não há row para lockar**. Dois clientes concorrentes ambos veem ausência, ambos tentam INSERT, um falha com erro 23505 (unique violation).
|
|
112
|
+
|
|
113
|
+
**PADRÃO CORRETO:**
|
|
114
|
+
|
|
115
|
+
```sql
|
|
116
|
+
-- ✅ CERTO — deixa UNIQUE constraint disparar erro atomicamente
|
|
117
|
+
insert into public.organizations (slug, name)
|
|
118
|
+
values ('acme-corp', 'ACME Corp')
|
|
119
|
+
on conflict (slug) do nothing
|
|
120
|
+
returning id;
|
|
121
|
+
|
|
122
|
+
-- Se RETURNING retornar 1 row → INSERT bem-sucedido, slug agora pertence a este client
|
|
123
|
+
-- Se RETURNING retornar 0 rows → slug já existia (outro client ganhou)
|
|
124
|
+
-- Atomicidade garantida pelo Postgres single-leader (linearizável)
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
**Variação com leitura do owner existente:**
|
|
128
|
+
|
|
129
|
+
```sql
|
|
130
|
+
-- Quando o caller também precisa do id quando já existe
|
|
131
|
+
with ins as (
|
|
132
|
+
insert into public.organizations (slug, name)
|
|
133
|
+
values ($1, $2)
|
|
134
|
+
on conflict (slug) do nothing
|
|
135
|
+
returning id
|
|
136
|
+
)
|
|
137
|
+
select id from ins
|
|
138
|
+
union all
|
|
139
|
+
select id from public.organizations where slug = $1
|
|
140
|
+
limit 1;
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
### REQ MODELO-03 — Análogos de total order broadcast em Postgres
|
|
144
|
+
|
|
145
|
+
Total order broadcast = entrega de mensagens a todos os nodes na **mesma ordem**. Reducível a consenso (Ch 9). Em Postgres, três análogos canônicos:
|
|
146
|
+
|
|
147
|
+
```sql
|
|
148
|
+
-- Análogo 1: pg_current_wal_lsn() — posição global no WAL, MONOTONICAMENTE crescente
|
|
149
|
+
-- Equivale a um "global counter" que todos os consumers veem na mesma ordem
|
|
150
|
+
select pg_current_wal_lsn();
|
|
151
|
+
-- → 0/1A2B3C4D — LSN (Log Sequence Number) opaco mas comparável (>, <, =)
|
|
152
|
+
|
|
153
|
+
-- Uso típico: ordenar eventos persistidos cronologicamente (cross-tabela)
|
|
154
|
+
select event_id, created_at, pg_current_wal_lsn() as lsn_at_insert
|
|
155
|
+
from public.events
|
|
156
|
+
order by lsn_at_insert;
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
```sql
|
|
160
|
+
-- Análogo 2: pg_logical_emit_message — broadcast de evento custom no WAL stream
|
|
161
|
+
-- Eventos NÃO precisam estar em uma tabela; ainda assim entram no WAL ordenadamente
|
|
162
|
+
-- Consumers de logical replication veem todos na mesma ordem
|
|
163
|
+
|
|
164
|
+
-- transactional=true → mensagem só vira visível se a transação commitar
|
|
165
|
+
select pg_logical_emit_message(
|
|
166
|
+
true, -- transactional
|
|
167
|
+
'app_events', -- prefix (usado para filtrar consumers)
|
|
168
|
+
'{"type":"order_paid","order_id":"abc-123"}'::text -- payload arbitrário
|
|
169
|
+
);
|
|
170
|
+
|
|
171
|
+
-- Consumer (replication slot) consome via pg_logical_slot_get_changes
|
|
172
|
+
-- Todos os consumers veem 'order_paid' antes de qualquer evento posterior
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
```sql
|
|
176
|
+
-- Análogo 3: Logical replication slots — todos consumers veem mesma ordem de WAL
|
|
177
|
+
-- Replication slot = posição persistente no WAL stream
|
|
178
|
+
|
|
179
|
+
create publication app_pub for table public.events, public.organizations;
|
|
180
|
+
|
|
181
|
+
-- Cada consumer cria seu próprio slot
|
|
182
|
+
select * from pg_create_logical_replication_slot('consumer_1', 'pgoutput');
|
|
183
|
+
|
|
184
|
+
-- Consumir mudanças em ordem
|
|
185
|
+
select * from pg_logical_slot_get_changes('consumer_1', null, null);
|
|
186
|
+
-- Retorna mudanças desde último get_changes em ORDEM DE WAL
|
|
187
|
+
-- Múltiplos consumers veem a MESMA ordem (total order broadcast)
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
**Quando usar cada análogo:**
|
|
191
|
+
|
|
192
|
+
| Análogo | Use case | Trade-off |
|
|
193
|
+
|---|---|---|
|
|
194
|
+
| `pg_current_wal_lsn()` em coluna | Ordenar eventos cross-tabela cronologicamente | Storage extra; só funciona dentro do mesmo cluster |
|
|
195
|
+
| `pg_logical_emit_message` | Broadcast de evento sem persistir em tabela | Consumer precisa estar online; mensagem perdida se sem slot |
|
|
196
|
+
| Logical replication slots | Pipeline robusto CDC ou cross-cluster sync | Slot inativo retém WAL → risco de disk full |
|
|
197
|
+
|
|
198
|
+
**Quando necessário (motivação canônica do livro Ch 9):** invariantes globais cross-tenant — licença unique global, billing event ordering, total ordering de eventos para reconciliação financeira.
|
|
199
|
+
|
|
200
|
+
### REQ MODELO-04 — CAP teorema → PACELC com tabela 4 quadrantes
|
|
201
|
+
|
|
202
|
+
CAP é trade-off **DURANTE PARTIÇÃO**. PACELC adiciona o caso **NORMAL (sem partição)** — escolher Latência ou Consistência.
|
|
203
|
+
|
|
204
|
+
| Estado | Trade-off | Sistemas exemplares | Quando faz sentido |
|
|
205
|
+
|---|---|---|---|
|
|
206
|
+
| **Partição + escolha CP** | Rejeita writes (consistência preservada) | Postgres single-leader (rejeita writes durante partition do líder), HBase | Invariantes financeiros, uniqueness global, transações monetárias |
|
|
207
|
+
| **Partição + escolha AP** | Aceita writes em ambos lados (divergência) | Cassandra, DynamoDB, CouchDB | Feed social, métricas analytics, contadores onde divergência é tolerável |
|
|
208
|
+
| **Normal + escolha PC** | Latência alta para garantir consistência (sync replication) | Spanner (commit timestamp via TrueTime), CockroachDB (Raft), Postgres synchronous_commit | Multi-region com SLA de consistência, financeiro distribuído |
|
|
209
|
+
| **Normal + escolha PL** | Latência baixa, eventual consistency (async replication) | DynamoDB, Cassandra, Postgres com async replicas | Geo-distribuído com priority em latência local, leitura de dados não-críticos |
|
|
210
|
+
|
|
211
|
+
**Mapeamento Postgres + Supabase:**
|
|
212
|
+
|
|
213
|
+
- **Postgres single-leader** = **CP/PC** — rejeita writes se líder em partition; sync replication se `synchronous_commit = on` com sync replica.
|
|
214
|
+
- **Supabase read replicas (Pro+)** = **CP no líder + PL na réplica** — write é CP (líder rejeita se isolado); read pode ser PL (réplica retorna stale data com baixa latência).
|
|
215
|
+
- **Eventual consistency manual via app** (escrever em pgmq + processar async) = **AP/PL** — write aceita sempre (não bloqueia se consumer offline); eventual no consumer side.
|
|
216
|
+
|
|
217
|
+
**REGRA derivada do PACELC:** declarar EXPLICITAMENTE no design de cada feature: (a) o que acontece durante partition, (b) o que acontece em operação normal. Não declarar = bug latente quando partition acontecer.
|
|
218
|
+
|
|
219
|
+
### REQ MODELO-05 — 2PC limitações + alternativas modernas
|
|
220
|
+
|
|
221
|
+
**2PC (two-phase commit)** é o protocolo clássico de commit atômico distribuído:
|
|
222
|
+
|
|
223
|
+
```
|
|
224
|
+
Coordinator Participant 1 Participant 2
|
|
225
|
+
│ ─── prepare? ──────► │ │
|
|
226
|
+
│ │ ─── prepare? ─────► │
|
|
227
|
+
│ ◄──── prepared ──── │ │
|
|
228
|
+
│ │ ◄──── prepared ─── │
|
|
229
|
+
│ ─── commit ──────────► │ │
|
|
230
|
+
│ │ ─── commit ────────► │
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
**Limitações canônicas (DDIA Ch 9):**
|
|
234
|
+
|
|
235
|
+
1. **Blocking se coordinator morre entre prepare e commit** — participants seguram resource locks indefinidamente, esperando decisão do coordinator. "Heuristic recovery" exige intervenção humana e pode resultar em divergência.
|
|
236
|
+
2. **Performance impact** — 2 round-trips (prepare + commit), latência multiplicada por 2 vs single-node commit. Em sistemas de alta concorrência, contention nos resource locks.
|
|
237
|
+
3. **Falta de heuristic recovery automática** — se coordinator nunca volta, participant não sabe se outros commitam ou abortam. Decisão unilateral pode quebrar atomicidade.
|
|
238
|
+
|
|
239
|
+
**Alternativa 1: Sagas (compensação local)**
|
|
240
|
+
|
|
241
|
+
Cada step é uma transação local. Cada step tem `compensate()` reverso. Se step N falha, executar `compensate()` de N-1, N-2, ... 1.
|
|
242
|
+
|
|
243
|
+
```sql
|
|
244
|
+
-- Saga: cancelar order + estornar pagamento + restaurar inventário
|
|
245
|
+
|
|
246
|
+
-- Step 1: cancelar order
|
|
247
|
+
begin;
|
|
248
|
+
update public.orders set status = 'cancelled' where id = $1;
|
|
249
|
+
insert into public.saga_steps (saga_id, step, status) values ($2, 'cancel_order', 'done');
|
|
250
|
+
commit;
|
|
251
|
+
|
|
252
|
+
-- Step 2: estornar pagamento (chama API externa via Edge Function)
|
|
253
|
+
-- Se falha → compensate Step 1: restore order status
|
|
254
|
+
-- Compensate é uma transação local, não distribuída
|
|
255
|
+
|
|
256
|
+
-- Step 3: restaurar inventário
|
|
257
|
+
-- Se falha → compensate Step 2 (recharge) + Step 1 (restore order)
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
**Quando usar sagas:** microservices distribuídos onde cada serviço tem seu próprio DB; latência tolerável (sequencial); compensação faz sentido semanticamente (idempotência + reversibilidade).
|
|
261
|
+
|
|
262
|
+
**Alternativa 2: Transactional outbox**
|
|
263
|
+
|
|
264
|
+
Write DB + event no outbox em **mesma transação local** (atomic, single-node). Worker async lê outbox e publica em broker (Kafka/pgmq). Garante exactly-once entre DB e broker.
|
|
265
|
+
|
|
266
|
+
```sql
|
|
267
|
+
-- Schema do outbox
|
|
268
|
+
create table public.outbox (
|
|
269
|
+
id bigserial primary key,
|
|
270
|
+
event_type text not null,
|
|
271
|
+
payload jsonb not null,
|
|
272
|
+
created_at timestamptz not null default now(),
|
|
273
|
+
processed_at timestamptz
|
|
274
|
+
);
|
|
275
|
+
|
|
276
|
+
-- Index parcial para worker pegar só pendentes
|
|
277
|
+
create index outbox_pending_idx on public.outbox (id)
|
|
278
|
+
where processed_at is null;
|
|
279
|
+
|
|
280
|
+
-- Pattern de uso (atomic write + event)
|
|
281
|
+
begin;
|
|
282
|
+
insert into public.orders (customer_id, total) values ($1, $2);
|
|
283
|
+
insert into public.outbox (event_type, payload)
|
|
284
|
+
values ('order_created', jsonb_build_object('order_id', currval('orders_id_seq')));
|
|
285
|
+
commit;
|
|
286
|
+
-- Atomic: ou ambos commitam, ou nenhum
|
|
287
|
+
|
|
288
|
+
-- Worker assíncrono (Edge Function via pg_cron)
|
|
289
|
+
with claimed as (
|
|
290
|
+
update public.outbox
|
|
291
|
+
set processed_at = now()
|
|
292
|
+
where id = (
|
|
293
|
+
select id from public.outbox
|
|
294
|
+
where processed_at is null
|
|
295
|
+
order by id
|
|
296
|
+
limit 1
|
|
297
|
+
for update skip locked
|
|
298
|
+
)
|
|
299
|
+
returning *
|
|
300
|
+
)
|
|
301
|
+
select pgmq.send('events', payload) from claimed;
|
|
302
|
+
-- skip locked → múltiplos workers processam em paralelo sem conflito
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
**Vantagens transactional outbox vs 2PC:**
|
|
306
|
+
|
|
307
|
+
- Sem coordinator distribuído — DB local é o único ponto atomicidade.
|
|
308
|
+
- Sem blocking — se worker morre, outro worker pega via `for update skip locked`.
|
|
309
|
+
- Escala horizontalmente (múltiplos workers).
|
|
310
|
+
- Cross-ref ATIVO para [`audit-log-multi-tenant`](../audit-log-multi-tenant/SKILL.md) — pattern audit_log v1.21 É um caso específico de outbox (write principal + audit_log row em mesma transação).
|
|
311
|
+
|
|
312
|
+
**Quando usar transactional outbox:** publish-after-write em pipeline async, sem necessidade de blocking commit cross-service. Default canônico para event-driven em B2B SaaS.
|
|
313
|
+
|
|
314
|
+
## Anti-patterns
|
|
315
|
+
|
|
316
|
+
### Anti-pattern 1: SELECT-then-INSERT app-level para uniqueness
|
|
317
|
+
|
|
318
|
+
**Errado:**
|
|
319
|
+
|
|
320
|
+
```typescript
|
|
321
|
+
// ❌ Race condition garantida
|
|
322
|
+
const existing = await supabase
|
|
323
|
+
.from('organizations')
|
|
324
|
+
.select('id')
|
|
325
|
+
.eq('slug', slug)
|
|
326
|
+
.single();
|
|
327
|
+
|
|
328
|
+
if (!existing.data) {
|
|
329
|
+
await supabase
|
|
330
|
+
.from('organizations')
|
|
331
|
+
.insert({ slug, name });
|
|
332
|
+
}
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
**Por quê:** entre o SELECT e o INSERT, outro cliente pode INSERT — duplicata ou erro 23505.
|
|
336
|
+
|
|
337
|
+
**Certo:** ON CONFLICT (REGRA #2):
|
|
338
|
+
|
|
339
|
+
```typescript
|
|
340
|
+
// ✅ Atomic, sem race
|
|
341
|
+
const { data, error } = await supabase
|
|
342
|
+
.from('organizations')
|
|
343
|
+
.insert({ slug, name })
|
|
344
|
+
.select('id')
|
|
345
|
+
.single();
|
|
346
|
+
|
|
347
|
+
if (error?.code === '23505') {
|
|
348
|
+
// Slug já existia — pode optar por buscar o id existente
|
|
349
|
+
const existing = await supabase
|
|
350
|
+
.from('organizations')
|
|
351
|
+
.select('id')
|
|
352
|
+
.eq('slug', slug)
|
|
353
|
+
.single();
|
|
354
|
+
return existing.data?.id;
|
|
355
|
+
}
|
|
356
|
+
return data?.id;
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
### Anti-pattern 2: Usar eventual consistency para invariantes financeiros
|
|
360
|
+
|
|
361
|
+
**Errado:** "saldo da conta nunca negativo" implementado lendo do read replica:
|
|
362
|
+
|
|
363
|
+
```typescript
|
|
364
|
+
// ❌ Read replica pode estar atrasada → permite débito que faria saldo negativo
|
|
365
|
+
const balance = await supabaseReadReplica
|
|
366
|
+
.from('accounts')
|
|
367
|
+
.select('balance')
|
|
368
|
+
.eq('id', accountId)
|
|
369
|
+
.single();
|
|
370
|
+
|
|
371
|
+
if (balance.data.balance >= amount) {
|
|
372
|
+
await supabasePrimary
|
|
373
|
+
.from('accounts')
|
|
374
|
+
.update({ balance: balance.data.balance - amount })
|
|
375
|
+
.eq('id', accountId);
|
|
376
|
+
}
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
**Por quê:** read replica retorna saldo desatualizado (eventual). Cliente pode passar a verificação local e debitar, mas o líder já tem outras transações em andamento → saldo negativo possível.
|
|
380
|
+
|
|
381
|
+
**Certo:** invariantes financeiros = linearizabilidade no líder + lock pessimista (`SELECT FOR UPDATE`) ou condicional (`UPDATE WHERE balance >= amount`):
|
|
382
|
+
|
|
383
|
+
```sql
|
|
384
|
+
-- ✅ Lock pessimista
|
|
385
|
+
begin;
|
|
386
|
+
select balance from public.accounts where id = $1 for update;
|
|
387
|
+
-- Verifica balance + amount em mesmo transação
|
|
388
|
+
update public.accounts set balance = balance - $2 where id = $1;
|
|
389
|
+
commit;
|
|
390
|
+
|
|
391
|
+
-- OU update condicional atomic
|
|
392
|
+
update public.accounts
|
|
393
|
+
set balance = balance - $2
|
|
394
|
+
where id = $1 and balance >= $2
|
|
395
|
+
returning balance;
|
|
396
|
+
-- Se RETURNING vazio → saldo insuficiente; nada foi alterado
|
|
397
|
+
```
|
|
398
|
+
|
|
399
|
+
### Anti-pattern 3: 2PC entre Edge Function e API externa
|
|
400
|
+
|
|
401
|
+
**Errado:** tentar usar `BEGIN; ... ; PREPARE TRANSACTION; ...` para coordenar Postgres + API externa:
|
|
402
|
+
|
|
403
|
+
```sql
|
|
404
|
+
-- ❌ PREPARE TRANSACTION precisa que API externa também suporte 2PC
|
|
405
|
+
-- Quase nenhuma API externa suporta (Stripe, Twilio, Meta — nenhuma)
|
|
406
|
+
begin;
|
|
407
|
+
insert into public.orders (...) values (...);
|
|
408
|
+
-- chamada à Stripe API aqui não pode participar de 2PC
|
|
409
|
+
prepare transaction 'order_with_payment';
|
|
410
|
+
```
|
|
411
|
+
|
|
412
|
+
**Por quê:** API externa não tem `prepare` e `commit` separados. Se Stripe processa o cobro mas Postgres falha em commit, dinheiro foi cobrado sem order. Inconsistência.
|
|
413
|
+
|
|
414
|
+
**Certo:** transactional outbox + worker idempotente (REGRA #5):
|
|
415
|
+
|
|
416
|
+
```sql
|
|
417
|
+
begin;
|
|
418
|
+
insert into public.orders (...) values (...);
|
|
419
|
+
insert into public.outbox (event_type, payload)
|
|
420
|
+
values ('charge_payment', jsonb_build_object('order_id', currval('orders_id_seq'), 'amount', $1));
|
|
421
|
+
commit;
|
|
422
|
+
-- Worker async lê outbox, chama Stripe com idempotency key, retry-safe
|
|
423
|
+
```
|
|
424
|
+
|
|
425
|
+
Idempotency key garante que retry não cobra duas vezes. Stripe expõe `Idempotency-Key` header para isso.
|
|
426
|
+
|
|
427
|
+
### Anti-pattern 4: Confundir consistência causal com linearizabilidade
|
|
428
|
+
|
|
429
|
+
**Errado:** assumir que "ordem causal preservada" = "invariante global respeitado".
|
|
430
|
+
|
|
431
|
+
```typescript
|
|
432
|
+
// ❌ "Vou usar consistência causal porque chat tem ordem causal"
|
|
433
|
+
// Mas o use case real era: limite de 10 mensagens grátis por user
|
|
434
|
+
const messageCount = await supabaseCausal
|
|
435
|
+
.from('messages')
|
|
436
|
+
.select('count', { count: 'exact', head: true })
|
|
437
|
+
.eq('user_id', userId)
|
|
438
|
+
.single();
|
|
439
|
+
|
|
440
|
+
if (messageCount.count < 10) {
|
|
441
|
+
await supabaseCausal.from('messages').insert({ user_id: userId, ... });
|
|
442
|
+
}
|
|
443
|
+
```
|
|
444
|
+
|
|
445
|
+
**Por quê:** consistência causal preserva A→B no caminho causal, mas count é uma agregação **global**. Dois inserts concorrentes em devices diferentes podem ambos passar a verificação (cada um vê count=9) → user envia 11 mensagens.
|
|
446
|
+
|
|
447
|
+
**Certo:** invariantes globais (limite N, saldo, uniqueness) = linearizabilidade. Use single-leader Postgres + atomic check:
|
|
448
|
+
|
|
449
|
+
```sql
|
|
450
|
+
-- ✅ Atomic check via UPDATE condicional
|
|
451
|
+
update public.users
|
|
452
|
+
set messages_count = messages_count + 1
|
|
453
|
+
where id = $1 and messages_count < 10
|
|
454
|
+
returning messages_count;
|
|
455
|
+
-- Se RETURNING vazio → limite atingido
|
|
456
|
+
```
|
|
457
|
+
|
|
458
|
+
### Anti-pattern 5: Outbox sem cleanup → tabela cresce sem limite
|
|
459
|
+
|
|
460
|
+
**Errado:** `INSERT INTO outbox` sem nunca DELETAR rows processadas:
|
|
461
|
+
|
|
462
|
+
```sql
|
|
463
|
+
update public.outbox set processed_at = now() where id = $1;
|
|
464
|
+
-- Mas nunca DELETE → tabela cresce 1M+ rows/mês
|
|
465
|
+
```
|
|
466
|
+
|
|
467
|
+
**Por quê:** outbox processada vira lixo — não precisa mais. Tabela inflada degrada performance dos índices, vacuum lento, disk usage cresce.
|
|
468
|
+
|
|
469
|
+
**Certo:** cron periódico para arquivar/deletar processadas > N dias:
|
|
470
|
+
|
|
471
|
+
```sql
|
|
472
|
+
-- pg_cron job daily
|
|
473
|
+
select cron.schedule(
|
|
474
|
+
'cleanup_outbox',
|
|
475
|
+
'0 3 * * *',
|
|
476
|
+
$$
|
|
477
|
+
delete from public.outbox
|
|
478
|
+
where processed_at is not null
|
|
479
|
+
and processed_at < now() - interval '7 days';
|
|
480
|
+
$$
|
|
481
|
+
);
|
|
482
|
+
```
|
|
483
|
+
|
|
484
|
+
Cross-ref para [`supabase-cron-queues`](../supabase-cron-queues/SKILL.md) (v1.8) — pattern pg_cron + retention.
|
|
485
|
+
|
|
486
|
+
## Ver também
|
|
487
|
+
|
|
488
|
+
- [_shared-dados-distribuidos/glossary.md](../_shared-dados-distribuidos/glossary.md) — termos `linearizability`, `causal consistency`, `eventual consistency`, `total order broadcast`, `CAP theorem`, `PACELC`, `two-phase commit`, `saga pattern`, `transactional outbox` (seções a + f)
|
|
489
|
+
- [audit-log-multi-tenant](../audit-log-multi-tenant/SKILL.md) — Phase 109 v1.21, pattern audit_log É transactional outbox
|
|
490
|
+
- [supabase-cron-queues](../supabase-cron-queues/SKILL.md) — v1.8, pgmq como destino do outbox + cleanup retention
|
|
491
|
+
- [supabase-database-functions](../supabase-database-functions/SKILL.md) — v1.8, STABLE/IMMUTABLE markers em helpers consumidos
|
|
492
|
+
- [streams-eventos-cdc](../streams-eventos-cdc/SKILL.md) — Phase 121 (irmã), event sourcing como aplicação prática de transactional outbox
|
|
493
|
+
- DDIA Ch 9 (Consistency and Consensus, summary p.354) — material-fonte canônico
|
|
494
|
+
</content>
|
|
495
|
+
</invoke>
|