@luanpdd/kit-mcp 1.21.0 → 1.26.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (275) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +914 -648
  3. package/kit/COMANDOS.md +138 -138
  4. package/kit/README.md +76 -52
  5. package/kit/agents/advisor-researcher.md +106 -106
  6. package/kit/agents/assumptions-analyzer.md +107 -107
  7. package/kit/agents/audit-log-implementer.md +138 -0
  8. package/kit/agents/auditor-consistencia-isolamento.md +413 -0
  9. package/kit/agents/codebase-mapper.md +768 -768
  10. package/kit/agents/crm-pipeline-implementer.md +106 -0
  11. package/kit/agents/debugger.md +813 -772
  12. package/kit/agents/detector-tenant-quente.md +337 -0
  13. package/kit/agents/evolution-go-integrator.md +21 -0
  14. package/kit/agents/example-reviewer.md +21 -21
  15. package/kit/agents/executor.md +564 -523
  16. package/kit/agents/integration-checker.md +200 -200
  17. package/kit/agents/invite-flow-implementer.md +52 -0
  18. package/kit/agents/lgpd-compliance-auditor.md +89 -0
  19. package/kit/agents/multi-tenant-isolation-auditor.md +10 -0
  20. package/kit/agents/multi-tenant-rls-writer.md +78 -0
  21. package/kit/agents/nyquist-auditor.md +178 -178
  22. package/kit/agents/org-onboarding-implementer.md +21 -0
  23. package/kit/agents/phase-researcher.md +696 -696
  24. package/kit/agents/plan-checker.md +272 -272
  25. package/kit/agents/planner.md +922 -891
  26. package/kit/agents/project-researcher.md +652 -652
  27. package/kit/agents/research-synthesizer.md +245 -245
  28. package/kit/agents/roadmapper.md +677 -677
  29. package/kit/agents/supabase-architect.md +27 -0
  30. package/kit/agents/supabase-auth-bootstrapper.md +80 -0
  31. package/kit/agents/supabase-column-privileges-writer.md +399 -0
  32. package/kit/agents/supabase-migration-writer.md +141 -14
  33. package/kit/agents/supabase-rbac-implementer.md +392 -0
  34. package/kit/agents/supabase-rls-hardener.md +521 -0
  35. package/kit/agents/supabase-rls-writer.md +105 -9
  36. package/kit/agents/supabase-roles-implementer.md +355 -0
  37. package/kit/agents/super-admin-implementer.md +99 -0
  38. package/kit/agents/ui-auditor.md +437 -437
  39. package/kit/agents/ui-checker.md +302 -302
  40. package/kit/agents/ui-researcher.md +355 -355
  41. package/kit/agents/user-profiler.md +175 -175
  42. package/kit/agents/validador-evolucao-schema.md +335 -0
  43. package/kit/agents/verifier.md +728 -728
  44. package/kit/commands/adicionar-backlog.md +75 -75
  45. package/kit/commands/adicionar-fase.md +42 -42
  46. package/kit/commands/adicionar-tarefa.md +45 -45
  47. package/kit/commands/adicionar-testes.md +41 -41
  48. package/kit/commands/ajuda.md +21 -21
  49. package/kit/commands/atualizar.md +37 -37
  50. package/kit/commands/auditar-marco.md +179 -179
  51. package/kit/commands/auditar-uat.md +23 -23
  52. package/kit/commands/autonomo.md +40 -40
  53. package/kit/commands/branch-pr.md +24 -24
  54. package/kit/commands/concluir-marco.md +247 -247
  55. package/kit/commands/configuracoes.md +36 -36
  56. package/kit/commands/dados-distribuidos.md +188 -0
  57. package/kit/commands/definir-perfil.md +10 -10
  58. package/kit/commands/depurar.md +190 -190
  59. package/kit/commands/discutir-fase.md +131 -131
  60. package/kit/commands/entrar-discord.md +17 -17
  61. package/kit/commands/estatisticas.md +18 -18
  62. package/kit/commands/example-greeting.md +33 -33
  63. package/kit/commands/executar-fase.md +58 -58
  64. package/kit/commands/expresso.md +56 -56
  65. package/kit/commands/fase-ui.md +34 -34
  66. package/kit/commands/fazer.md +57 -57
  67. package/kit/commands/fio.md +125 -125
  68. package/kit/commands/fluxos-trabalho.md +64 -64
  69. package/kit/commands/forense.md +176 -176
  70. package/kit/commands/gerenciador.md +38 -38
  71. package/kit/commands/inserir-fase.md +31 -31
  72. package/kit/commands/limpeza.md +17 -17
  73. package/kit/commands/listar-hipoteses-fase.md +45 -45
  74. package/kit/commands/listar-workspaces.md +18 -18
  75. package/kit/commands/mapear-codebase.md +70 -70
  76. package/kit/commands/nota.md +33 -33
  77. package/kit/commands/novo-marco.md +43 -43
  78. package/kit/commands/novo-projeto.md +41 -41
  79. package/kit/commands/novo-workspace.md +43 -43
  80. package/kit/commands/pausar-trabalho.md +37 -37
  81. package/kit/commands/perfil-usuario.md +45 -45
  82. package/kit/commands/pesquisar-fase.md +195 -195
  83. package/kit/commands/planejar-fase.md +67 -67
  84. package/kit/commands/planejar-lacunas.md +33 -33
  85. package/kit/commands/plantar-ideia.md +25 -25
  86. package/kit/commands/progresso.md +24 -24
  87. package/kit/commands/proximo.md +30 -30
  88. package/kit/commands/publicar.md +490 -490
  89. package/kit/commands/rapido.md +35 -35
  90. package/kit/commands/reaplicar-patches.md +124 -124
  91. package/kit/commands/relatorio-sessao.md +19 -19
  92. package/kit/commands/remover-fase.md +31 -31
  93. package/kit/commands/remover-workspace.md +26 -26
  94. package/kit/commands/resumo-marco.md +50 -50
  95. package/kit/commands/retomar-trabalho.md +40 -40
  96. package/kit/commands/revisar-backlog.md +60 -60
  97. package/kit/commands/revisar-ui.md +32 -32
  98. package/kit/commands/revisar.md +37 -37
  99. package/kit/commands/saude.md +21 -21
  100. package/kit/commands/setup-notion.md +93 -93
  101. package/kit/commands/supabase.md +55 -8
  102. package/kit/commands/sync-main.md +68 -68
  103. package/kit/commands/validar-fase.md +35 -35
  104. package/kit/commands/verificar-tarefas.md +44 -44
  105. package/kit/commands/verificar-trabalho.md +64 -64
  106. package/kit/file-manifest.json +52 -32
  107. package/kit/framework/bin/lib/commands.cjs +959 -959
  108. package/kit/framework/bin/lib/config.cjs +442 -442
  109. package/kit/framework/bin/lib/core.cjs +1230 -1230
  110. package/kit/framework/bin/lib/frontmatter.cjs +336 -336
  111. package/kit/framework/bin/lib/init.cjs +1442 -1442
  112. package/kit/framework/bin/lib/milestone.cjs +252 -252
  113. package/kit/framework/bin/lib/model-profiles.cjs +68 -68
  114. package/kit/framework/bin/lib/phase.cjs +888 -888
  115. package/kit/framework/bin/lib/profile-output.cjs +952 -952
  116. package/kit/framework/bin/lib/profile-pipeline.cjs +539 -539
  117. package/kit/framework/bin/lib/roadmap.cjs +329 -329
  118. package/kit/framework/bin/lib/security.cjs +382 -382
  119. package/kit/framework/bin/lib/state.cjs +1031 -1031
  120. package/kit/framework/bin/lib/template.cjs +222 -222
  121. package/kit/framework/bin/lib/uat.cjs +282 -282
  122. package/kit/framework/bin/lib/verify.cjs +888 -888
  123. package/kit/framework/bin/lib/workstream.cjs +491 -491
  124. package/kit/framework/bin/tools.cjs +918 -918
  125. package/kit/framework/commands/workstreams.md +63 -63
  126. package/kit/framework/references/checkpoints.md +778 -778
  127. package/kit/framework/references/continuation-format.md +249 -249
  128. package/kit/framework/references/decimal-phase-calculation.md +64 -64
  129. package/kit/framework/references/git-integration.md +295 -295
  130. package/kit/framework/references/git-planning-commit.md +38 -38
  131. package/kit/framework/references/model-profile-resolution.md +36 -36
  132. package/kit/framework/references/model-profiles.md +139 -139
  133. package/kit/framework/references/phase-argument-parsing.md +61 -61
  134. package/kit/framework/references/planning-config.md +202 -202
  135. package/kit/framework/references/questioning.md +162 -162
  136. package/kit/framework/references/tdd.md +263 -263
  137. package/kit/framework/references/ui-brand.md +160 -160
  138. package/kit/framework/references/user-profiling.md +657 -657
  139. package/kit/framework/references/verification-patterns.md +612 -612
  140. package/kit/framework/references/workstream-flag.md +58 -58
  141. package/kit/framework/templates/DEBUG.md +164 -164
  142. package/kit/framework/templates/UAT.md +265 -265
  143. package/kit/framework/templates/UI-SPEC.md +100 -100
  144. package/kit/framework/templates/VALIDATION.md +76 -76
  145. package/kit/framework/templates/claude-md.md +122 -122
  146. package/kit/framework/templates/codebase/architecture.md +185 -185
  147. package/kit/framework/templates/codebase/concerns.md +205 -205
  148. package/kit/framework/templates/codebase/conventions.md +204 -204
  149. package/kit/framework/templates/codebase/integrations.md +192 -192
  150. package/kit/framework/templates/codebase/stack.md +158 -158
  151. package/kit/framework/templates/codebase/structure.md +199 -199
  152. package/kit/framework/templates/codebase/testing.md +301 -301
  153. package/kit/framework/templates/config.json +44 -44
  154. package/kit/framework/templates/context.md +352 -352
  155. package/kit/framework/templates/continue-here.md +78 -78
  156. package/kit/framework/templates/copilot-instructions.md +7 -7
  157. package/kit/framework/templates/debug-subagent-prompt.md +91 -91
  158. package/kit/framework/templates/dev-preferences.md +20 -20
  159. package/kit/framework/templates/discovery.md +146 -146
  160. package/kit/framework/templates/discussion-log.md +63 -63
  161. package/kit/framework/templates/milestone-archive.md +123 -123
  162. package/kit/framework/templates/milestone.md +115 -115
  163. package/kit/framework/templates/phase-prompt.md +610 -610
  164. package/kit/framework/templates/planner-subagent-prompt.md +117 -117
  165. package/kit/framework/templates/project.md +186 -186
  166. package/kit/framework/templates/requirements.md +231 -231
  167. package/kit/framework/templates/research-project/ARCHITECTURE.md +204 -204
  168. package/kit/framework/templates/research-project/FEATURES.md +147 -147
  169. package/kit/framework/templates/research-project/PITFALLS.md +200 -200
  170. package/kit/framework/templates/research-project/STACK.md +120 -120
  171. package/kit/framework/templates/research-project/SUMMARY.md +170 -170
  172. package/kit/framework/templates/research.md +419 -419
  173. package/kit/framework/templates/retrospective.md +54 -54
  174. package/kit/framework/templates/roadmap.md +202 -202
  175. package/kit/framework/templates/state.md +176 -176
  176. package/kit/framework/templates/summary-complex.md +59 -59
  177. package/kit/framework/templates/summary-minimal.md +41 -41
  178. package/kit/framework/templates/summary-standard.md +48 -48
  179. package/kit/framework/templates/summary.md +209 -209
  180. package/kit/framework/templates/user-profile.md +146 -146
  181. package/kit/framework/templates/user-setup.md +256 -256
  182. package/kit/framework/templates/verification-report.md +258 -258
  183. package/kit/framework/workflows/add-phase.md +112 -112
  184. package/kit/framework/workflows/add-tests.md +351 -351
  185. package/kit/framework/workflows/add-todo.md +158 -158
  186. package/kit/framework/workflows/audit-milestone.md +340 -340
  187. package/kit/framework/workflows/audit-uat.md +109 -109
  188. package/kit/framework/workflows/autonomous.md +891 -891
  189. package/kit/framework/workflows/check-todos.md +177 -177
  190. package/kit/framework/workflows/cleanup.md +152 -152
  191. package/kit/framework/workflows/complete-milestone.md +696 -696
  192. package/kit/framework/workflows/diagnose-issues.md +231 -231
  193. package/kit/framework/workflows/discovery-phase.md +289 -289
  194. package/kit/framework/workflows/discuss-phase-assumptions.md +653 -653
  195. package/kit/framework/workflows/discuss-phase.md +784 -784
  196. package/kit/framework/workflows/do.md +104 -104
  197. package/kit/framework/workflows/execute-phase.md +838 -838
  198. package/kit/framework/workflows/execute-plan.md +510 -510
  199. package/kit/framework/workflows/fast.md +102 -102
  200. package/kit/framework/workflows/forensics.md +265 -265
  201. package/kit/framework/workflows/health.md +181 -181
  202. package/kit/framework/workflows/help.md +619 -619
  203. package/kit/framework/workflows/insert-phase.md +130 -130
  204. package/kit/framework/workflows/list-phase-assumptions.md +178 -178
  205. package/kit/framework/workflows/list-workspaces.md +56 -56
  206. package/kit/framework/workflows/manager.md +362 -362
  207. package/kit/framework/workflows/map-codebase.md +377 -377
  208. package/kit/framework/workflows/milestone-summary.md +223 -223
  209. package/kit/framework/workflows/new-milestone.md +486 -486
  210. package/kit/framework/workflows/new-project.md +1159 -1159
  211. package/kit/framework/workflows/new-workspace.md +237 -237
  212. package/kit/framework/workflows/next.md +97 -97
  213. package/kit/framework/workflows/node-repair.md +92 -92
  214. package/kit/framework/workflows/note.md +156 -156
  215. package/kit/framework/workflows/pause-work.md +176 -176
  216. package/kit/framework/workflows/plan-milestone-gaps.md +273 -273
  217. package/kit/framework/workflows/plan-phase.md +765 -765
  218. package/kit/framework/workflows/plant-seed.md +169 -169
  219. package/kit/framework/workflows/pr-branch.md +129 -129
  220. package/kit/framework/workflows/profile-user.md +450 -450
  221. package/kit/framework/workflows/progress.md +507 -507
  222. package/kit/framework/workflows/quick.md +757 -757
  223. package/kit/framework/workflows/remove-phase.md +155 -155
  224. package/kit/framework/workflows/remove-workspace.md +90 -90
  225. package/kit/framework/workflows/research-phase.md +82 -82
  226. package/kit/framework/workflows/resume-project.md +326 -326
  227. package/kit/framework/workflows/review.md +228 -228
  228. package/kit/framework/workflows/session-report.md +146 -146
  229. package/kit/framework/workflows/settings.md +283 -283
  230. package/kit/framework/workflows/ship.md +228 -228
  231. package/kit/framework/workflows/stats.md +60 -60
  232. package/kit/framework/workflows/transition.md +671 -671
  233. package/kit/framework/workflows/ui-phase.md +302 -302
  234. package/kit/framework/workflows/ui-review.md +165 -165
  235. package/kit/framework/workflows/update.md +323 -323
  236. package/kit/framework/workflows/validate-phase.md +174 -174
  237. package/kit/framework/workflows/verify-phase.md +252 -252
  238. package/kit/framework/workflows/verify-work.md +637 -637
  239. package/kit/hooks/check-update.js +118 -118
  240. package/kit/hooks/context-monitor.js +163 -163
  241. package/kit/hooks/prompt-guard.js +103 -103
  242. package/kit/hooks/statusline.js +125 -125
  243. package/kit/hooks/workflow-guard.js +101 -101
  244. package/kit/settings.json +45 -45
  245. package/kit/skills/_shared-dados-distribuidos/glossary.md +224 -0
  246. package/kit/skills/_shared-supabase/glossary.md +27 -0
  247. package/kit/skills/armadilhas-sistemas-distribuidos/SKILL.md +447 -0
  248. package/kit/skills/audit-log-multi-tenant/SKILL.md +6 -0
  249. package/kit/skills/cascading-failures/SKILL.md +4 -0
  250. package/kit/skills/consistencia-leitura-replica/SKILL.md +385 -0
  251. package/kit/skills/crm-lead-pipeline-patterns/SKILL.md +17 -0
  252. package/kit/skills/escolha-modelo-consistencia/SKILL.md +495 -0
  253. package/kit/skills/evolucao-schema-compativel/SKILL.md +448 -0
  254. package/kit/skills/example-skill/SKILL.md +42 -42
  255. package/kit/skills/multi-tenant-performance-scaling/SKILL.md +4 -0
  256. package/kit/skills/multi-tenant-rls-hierarchy/SKILL.md +4 -0
  257. package/kit/skills/postgres-isolamento-concorrencia/SKILL.md +552 -0
  258. package/kit/skills/rbac-permissions-matrix-supabase/SKILL.md +37 -0
  259. package/kit/skills/streams-eventos-cdc/SKILL.md +712 -0
  260. package/kit/skills/supabase-column-level-security/SKILL.md +426 -0
  261. package/kit/skills/supabase-cron-queues/SKILL.md +9 -0
  262. package/kit/skills/supabase-custom-claims-rbac/SKILL.md +472 -0
  263. package/kit/skills/supabase-database-functions/SKILL.md +85 -0
  264. package/kit/skills/supabase-migrations/SKILL.md +133 -11
  265. package/kit/skills/supabase-postgres-roles/SKILL.md +392 -0
  266. package/kit/skills/supabase-rls-defense-in-depth/SKILL.md +418 -0
  267. package/kit/skills/supabase-rls-policies/SKILL.md +462 -12
  268. package/kit/skills/super-admin-platform-pattern/SKILL.md +4 -0
  269. package/kit/skills/tenant-quente-mitigacao/SKILL.md +605 -0
  270. package/package.json +63 -63
  271. package/src/core/kit.js +216 -216
  272. package/src/core/reflect.js +247 -247
  273. package/src/core/reverse-sync.js +372 -372
  274. package/src/core/sync.js +418 -418
  275. package/src/core/watch.js +121 -121
@@ -0,0 +1,712 @@
1
+ ---
2
+ name: streams-eventos-cdc
3
+ description: Use ao implementar event stream em Supabase — diferença AMQP/JMS-style (LISTEN/NOTIFY) vs log-based (pgmq) brokers, padrões CDC via wal2json + Realtime broadcast OU pglogical → Kafka, event sourcing em Postgres com tabela eventos source-of-truth + projeções via MV ou trigger, exactly-once em pgmq via dedup table com unique(event_id) + idempotency key + transactional outbox, 3 tipos stream join (stream-stream com janela, stream-table CDC+atividade, table-table merge changelogs), log compaction strategy (pgmq retention TTL + snapshot manual).
4
+ ---
5
+
6
+ # Streams, Eventos e CDC — Brokers, Event Sourcing, Exactly-Once em Postgres
7
+
8
+ ## Quando usar
9
+
10
+ LLM carrega esta skill ao implementar pipeline event-driven em Supabase + Postgres. Trigger phrases:
11
+
12
+ - "event stream Postgres", "CDC Supabase", "wal2json + Realtime"
13
+ - "pgmq vs LISTEN/NOTIFY", "broker log-based vs AMQP"
14
+ - "event sourcing Postgres", "tabela append-only de eventos"
15
+ - "exactly-once pgmq", "dedup table idempotency"
16
+ - "stream join com janela", "stream-table CDC enrichment"
17
+ - "log compaction Postgres", "snapshot eventos"
18
+ - "projeção materialized view de eventos", "denormalization via trigger"
19
+
20
+ Esta skill **estende** [`audit-log-multi-tenant`](../audit-log-multi-tenant/SKILL.md) (v1.21) ao reconhecer audit_log como event sourcing parcial; [`supabase-cron-queues`](../supabase-cron-queues/SKILL.md) (v1.8) para pgmq pattern; e [`supabase-realtime`](../supabase-realtime/SKILL.md) (v1.8) para broadcast como CDC stream.
21
+
22
+ Material-fonte: *Designing Data-Intensive Applications*, Martin Kleppmann (O'Reilly 2017), capítulo 11 "Stream Processing" (linhas 17812-19637 do material extraído; summary 19408-19481). Termos canônicos PT-BR ↔ EN definidos em [`../_shared-dados-distribuidos/glossary.md`](../_shared-dados-distribuidos/glossary.md) seção (h).
23
+
24
+ ## Regras absolutas
25
+
26
+ **REGRA #1 (broker log-based default para event sourcing):** Para event sourcing, CDC ou pipeline com replay obrigatório, escolher **log-based broker** (Kafka, pgmq) — mensagem retida (TTL configurável), múltiplos consumers tracked offset independente, replay possível. AMQP/JMS-style (RabbitMQ, LISTEN/NOTIFY) deletam mensagem após ack — sem replay, single-consumer.
27
+
28
+ **REGRA #2 (CDC via wal2json + Realtime é default Supabase):** Se ambiente é Supabase + use case é sync índice/desnormalização/multi-region, default é wal2json + Supabase Realtime broadcast. Zero infra extra. Apenas considerar pglogical → Kafka externo se warehousing analítico for o uso primário.
29
+
30
+ **REGRA #3 (event sourcing exige tabela append-only + projeções derivadas):** Tabela `events` deve ser **append-only** (REVOKE DELETE/UPDATE como audit_log v1.21). Estado atual = projeção derivada via Materialized View ou trigger-maintained denormalization — NUNCA escrever direto em "tabela de estado". Source of truth = stream de eventos.
31
+
32
+ **REGRA #4 (exactly-once pgmq exige dedup + idempotency + transactional outbox):** pgmq não garante exactly-once nativo (at-least-once entrega). Para semântica exactly-once: (a) **dedup table** com `unique(event_id)` rejeitando duplicatas; (b) **handler idempotente** (mesmo input → mesmo output, sem efeitos colaterais); (c) **transactional outbox** para cross-service writes.
33
+
34
+ **REGRA #5 (stream join exige janela temporal explícita):** Stream-stream join sem janela = memória cresce sem limite (cada evento aguarda match indefinidamente). Toda janela deve ter TTL explícito (tumbling, sliding, session). Default: tumbling 5min para business events; sliding 1min para latency-sensitive.
35
+
36
+ **REGRA #6 (log compaction não-trivial em pgmq — exige snapshot manual):** pgmq não tem log compaction nativa (Kafka tem). Para event sourcing com snapshot: criar tabela `snapshots` periodicamente, deletar `events.id < snapshot_lsn` correspondente. Sem snapshot = tabela `events` cresce sem limite, replay torna-se O(n) caro.
37
+
38
+ ## Patterns canônicos
39
+
40
+ ### REQ STREAMS-01 — Brokers AMQP/JMS-style vs log-based
41
+
42
+ | Tipo | Exemplos | Mensagem após ack | Multi-consumer | Replay | Use case |
43
+ |---|---|---|---|---|---|
44
+ | **AMQP/JMS-style** | RabbitMQ, postgres `LISTEN/NOTIFY`, ActiveMQ | Deletada (consumida) | Single (work queue — distribui rounds robin) | Não (gone after ack) | Task queue async (envio email, geração PDF) |
45
+ | **Log-based** | Kafka, pgmq, Redpanda, Pulsar | Retida (TTL configurável) | Multiple (cada consumer tracks offset independente) | Sim (replay desde offset N) | Event sourcing, CDC, audit, analytics |
46
+
47
+ **Como escolher:**
48
+
49
+ ```
50
+ Use case precisa de replay? ─── Sim ──► log-based (pgmq, Kafka)
51
+
52
+ Não
53
+
54
+
55
+ Múltiplos consumers veem mesma mensagem? ─── Sim ──► log-based
56
+
57
+ Não
58
+
59
+
60
+ Mensagem é "task" descartável após processada? ─── Sim ──► AMQP/JMS-style (RabbitMQ, LISTEN/NOTIFY)
61
+
62
+ Não
63
+
64
+
65
+ Default (event-driven em B2B SaaS): log-based (pgmq)
66
+ ```
67
+
68
+ **Exemplo postgres LISTEN/NOTIFY (AMQP-style — single consumer, sem replay):**
69
+
70
+ ```sql
71
+ -- Producer
72
+ notify ch_orders, '{"order_id":"abc-123","status":"paid"}';
73
+
74
+ -- Consumer (Edge Function)
75
+ listen ch_orders;
76
+ -- Sleep até receber notification — single consumer recebe, mensagem some
77
+ ```
78
+
79
+ **Exemplo pgmq (log-based — multi-consumer, replay):**
80
+
81
+ ```sql
82
+ -- Setup (uma vez)
83
+ select pgmq.create('orders');
84
+
85
+ -- Producer
86
+ select pgmq.send('orders', '{"order_id":"abc-123","status":"paid"}');
87
+
88
+ -- Consumer 1 (worker A)
89
+ select * from pgmq.read('orders', 30, 1);
90
+ -- vt=30s (visibility timeout), 1 mensagem por leitura
91
+ -- Após ler: mensagem fica invisível por 30s — outro worker não pega
92
+ -- Worker A processa e dá ack:
93
+ select pgmq.delete('orders', msg_id);
94
+ -- Sem ack em 30s → mensagem volta à queue (at-least-once)
95
+
96
+ -- Consumer 2 (worker B / archive)
97
+ -- Se queue tem retention, archive table mantém histórico para replay
98
+ select * from pgmq.archive('orders', msg_id);
99
+ -- Mensagens em archive são replayable
100
+ ```
101
+
102
+ ### REQ STREAMS-02 — 3 padrões CDC em Postgres
103
+
104
+ CDC (Change Data Capture) = capturar mudanças no DB como stream de eventos. 3 abordagens canônicas em Supabase:
105
+
106
+ **Abordagem 1: wal2json + Supabase Realtime broadcast** (default)
107
+
108
+ ```sql
109
+ -- Habilitar replication identity (necessário para wal2json capturar UPDATE/DELETE com colunas)
110
+ alter table public.orders replica identity full;
111
+
112
+ -- Supabase Realtime já consome WAL via wal2json internamente
113
+ -- Cliente subscreve canal específico via JS client
114
+ ```
115
+
116
+ ```typescript
117
+ // Cliente Supabase consume CDC stream via Realtime
118
+ const channel = supabase
119
+ .channel('orders-cdc', { config: { private: true } })
120
+ .on(
121
+ 'postgres_changes',
122
+ { event: '*', schema: 'public', table: 'orders' },
123
+ (payload) => {
124
+ // payload.eventType: INSERT | UPDATE | DELETE
125
+ // payload.new: nova row (INSERT/UPDATE)
126
+ // payload.old: row antiga (UPDATE/DELETE — exige replica identity full)
127
+ console.log('CDC event:', payload);
128
+ }
129
+ )
130
+ .subscribe();
131
+ ```
132
+
133
+ **Trade-offs:** zero infra extra; baixa latência (sub-segundo); RLS aplicada nas mensagens (cada cliente vê só rows permitidas). Limite: scale na ordem de milhares de subscribers por canal.
134
+
135
+ **Abordagem 2: pglogical → Kafka externo** (warehousing analítico)
136
+
137
+ ```sql
138
+ -- Em Supabase Pro+ habilitar pglogical (extensão)
139
+ create extension if not exists pglogical;
140
+
141
+ -- Setup nó provider (Postgres source)
142
+ select pglogical.create_node(
143
+ node_name := 'supabase_prod',
144
+ dsn := 'host=db.xxx.supabase.co dbname=postgres'
145
+ );
146
+
147
+ -- Replication set para tabelas que viram stream
148
+ select pglogical.create_replication_set(set_name := 'cdc_set');
149
+ select pglogical.replication_set_add_table('cdc_set', 'public.orders', synchronize_data := false);
150
+
151
+ -- Conector Kafka (Debezium ou similar) consome pglogical → publica em Kafka topic
152
+ -- Trade-off: requer infra Kafka externa, latência maior (segundos), throughput muito maior
153
+ ```
154
+
155
+ **Abordagem 3: Trigger-based** (casos custom onde wal2json não cobre)
156
+
157
+ ```sql
158
+ -- Trigger que emite evento custom quando flag X muda
159
+ create or replace function public.emit_lead_qualified_event()
160
+ returns trigger
161
+ language plpgsql
162
+ security invoker
163
+ set search_path = ''
164
+ as $$
165
+ begin
166
+ if old.stage != 'qualified' and new.stage = 'qualified' then
167
+ insert into public.outbox (event_type, payload)
168
+ values (
169
+ 'lead_qualified',
170
+ jsonb_build_object(
171
+ 'lead_id', new.id,
172
+ 'org_id', new.org_id,
173
+ 'qualified_by', (select auth.uid()),
174
+ 'qualified_at', now()
175
+ )
176
+ );
177
+ end if;
178
+ return new;
179
+ end;
180
+ $$;
181
+
182
+ create trigger lead_qualified_trigger
183
+ after update on public.leads
184
+ for each row
185
+ execute function public.emit_lead_qualified_event();
186
+ ```
187
+
188
+ **Quando usar trigger-based:** semântica de evento mais rica que "linha mudou" (ex: business event "qualified" derivado de mudança específica). Worker async lê outbox e publica downstream.
189
+
190
+ **Use cases canônicos:**
191
+
192
+ | Use case | Abordagem recomendada |
193
+ |---|---|
194
+ | Sync índice de busca (Elasticsearch, Meilisearch) | wal2json + Realtime → função client que sincroniza |
195
+ | Desnormalização (Materialized View atualizada por evento) | Trigger-based (mais controle sobre quando refresh) |
196
+ | Sync multi-region cold standby | pglogical → Kafka → consumer remoto |
197
+ | Audit log retroativo + análise comportamental | wal2json (captura cru) → analytics warehouse |
198
+ | Notificação push (mobile app) | Realtime broadcast direto (zero step intermediário) |
199
+
200
+ ### REQ STREAMS-03 — Event sourcing em Postgres
201
+
202
+ **Princípio canônico:** eventos imutáveis são source of truth; estado atual é projeção derivada.
203
+
204
+ **Schema canônico:**
205
+
206
+ ```sql
207
+ -- Tabela events — source of truth (append-only)
208
+ create table public.events (
209
+ id bigserial primary key,
210
+ aggregate_id uuid not null, -- ID da entidade (order, user, ...)
211
+ aggregate_type text not null, -- Tipo da entidade ('order', 'user')
212
+ event_type text not null, -- 'order_created', 'order_paid', 'order_shipped'
213
+ payload jsonb not null, -- Detalhes do evento
214
+ metadata jsonb, -- actor_id, request_id, trace_id
215
+ created_at timestamptz not null default now()
216
+ );
217
+
218
+ -- Index canônico (para reproduzir histórico de uma entidade)
219
+ create index events_aggregate_idx on public.events (aggregate_id, id);
220
+
221
+ -- Index para query por tipo (analytics)
222
+ create index events_type_created_idx on public.events (event_type, created_at);
223
+
224
+ -- REGRA #3 — append-only: REVOKE DELETE/UPDATE
225
+ revoke delete, update on public.events from public, authenticated, anon, service_role;
226
+ -- Apenas postgres role pode deletar (cleanup com snapshot)
227
+ ```
228
+
229
+ **Cross-ref ATIVO** para [`audit-log-multi-tenant`](../audit-log-multi-tenant/SKILL.md) (v1.21) — audit_log É event sourcing semantics: append-only, imutável, retém histórico cronológico. Quem implementou audit_log já fez event sourcing parcial.
230
+
231
+ **Projeção via Materialized View:**
232
+
233
+ ```sql
234
+ -- Projeção: estado atual de cada order derivado dos eventos
235
+ create materialized view public.order_state as
236
+ select
237
+ aggregate_id as order_id,
238
+ -- Reconstrói estado a partir dos eventos (último win)
239
+ (array_agg(payload->>'status' order by id desc))[1] as current_status,
240
+ (array_agg(payload->>'total' order by id desc))[1]::numeric as current_total,
241
+ min(created_at) as created_at,
242
+ max(created_at) as updated_at,
243
+ count(*) as event_count
244
+ from public.events
245
+ where aggregate_type = 'order'
246
+ group by aggregate_id;
247
+
248
+ create unique index on public.order_state (order_id);
249
+
250
+ -- Refresh (incremental via concurrent OR full)
251
+ refresh materialized view concurrently public.order_state;
252
+ -- Ou via pg_cron a cada N minutos
253
+ ```
254
+
255
+ **Projeção via trigger-maintained denormalization:**
256
+
257
+ ```sql
258
+ -- Tabela de estado mantida por trigger (atualizada por cada novo evento)
259
+ create table public.order_current_state (
260
+ order_id uuid primary key,
261
+ status text not null,
262
+ total numeric,
263
+ updated_at timestamptz not null default now()
264
+ );
265
+
266
+ create or replace function public.project_order_event()
267
+ returns trigger
268
+ language plpgsql
269
+ security invoker
270
+ set search_path = ''
271
+ as $$
272
+ begin
273
+ if new.aggregate_type = 'order' then
274
+ insert into public.order_current_state (order_id, status, total, updated_at)
275
+ values (
276
+ new.aggregate_id,
277
+ new.payload->>'status',
278
+ (new.payload->>'total')::numeric,
279
+ new.created_at
280
+ )
281
+ on conflict (order_id) do update
282
+ set status = excluded.status,
283
+ total = coalesce(excluded.total, public.order_current_state.total),
284
+ updated_at = excluded.updated_at;
285
+ end if;
286
+ return new;
287
+ end;
288
+ $$;
289
+
290
+ create trigger project_order_event_trigger
291
+ after insert on public.events
292
+ for each row
293
+ execute function public.project_order_event();
294
+ ```
295
+
296
+ **Quando MV vs trigger:**
297
+
298
+ | Critério | MV concurrent refresh | Trigger denormalization |
299
+ |---|---|---|
300
+ | **Latência** | Periódica (minutos) | Imediata (no commit do evento) |
301
+ | **Custo write** | Baixo (write apenas em events) | Alto (write em events + state) |
302
+ | **Custo read** | Baixo (state já agregado) | Baixo |
303
+ | **Use case** | Analytics, dashboards | UI real-time, business state |
304
+
305
+ ### REQ STREAMS-04 — Exactly-once em pgmq
306
+
307
+ pgmq oferece **at-least-once** nativo (mensagem reenviada se worker crash sem ack). Para semântica **exactly-once**, combinação de 3 técnicas:
308
+
309
+ **Técnica 1: Dedup table com unique(event_id)**
310
+
311
+ ```sql
312
+ -- Tabela de eventos já processados
313
+ create table public.processed_events (
314
+ event_id uuid primary key,
315
+ processed_at timestamptz not null default now(),
316
+ processor text not null -- nome do worker para debug
317
+ );
318
+ ```
319
+
320
+ **Técnica 2: Handler atomic — INSERT na dedup + processamento na MESMA transação**
321
+
322
+ ```sql
323
+ -- Worker (Edge Function ou função PG)
324
+ create or replace function public.process_order_event(p_msg_id bigint)
325
+ returns void
326
+ language plpgsql
327
+ security definer -- worker tem privilégios elevados
328
+ set search_path = ''
329
+ as $$
330
+ declare
331
+ v_msg record;
332
+ v_event_id uuid;
333
+ begin
334
+ -- Lê mensagem da queue com visibility timeout
335
+ select msg_id, message into v_msg
336
+ from pgmq.read('orders', 30, 1)
337
+ limit 1;
338
+
339
+ if v_msg is null then return; end if;
340
+
341
+ v_event_id := (v_msg.message->>'event_id')::uuid;
342
+
343
+ begin
344
+ -- Atomic: INSERT dedup + processamento
345
+ insert into public.processed_events (event_id, processor)
346
+ values (v_event_id, 'process_order_event');
347
+ -- Falha (unique violation) se já processado → exception abort tudo
348
+
349
+ -- ... lógica de processamento idempotente ...
350
+ update public.orders set status = 'paid' where id = (v_msg.message->>'order_id')::uuid;
351
+
352
+ -- Ack — remove da queue
353
+ perform pgmq.delete('orders', v_msg.msg_id);
354
+
355
+ exception when unique_violation then
356
+ -- Já processado — apenas dar ack para remover da queue
357
+ perform pgmq.delete('orders', v_msg.msg_id);
358
+ end;
359
+ end;
360
+ $$;
361
+ ```
362
+
363
+ **Técnica 3: Idempotency key no handler — mesmo input → mesmo output (sem efeitos colaterais)**
364
+
365
+ Idempotency = processar a mesma mensagem N vezes produz o mesmo resultado. Padrões:
366
+
367
+ ```sql
368
+ -- Idempotente via UPDATE condicional (não muda se já está no estado)
369
+ update public.orders
370
+ set status = 'paid'
371
+ where id = $1 and status != 'paid';
372
+ -- Se já 'paid' → no-op, RETURNING vazio
373
+
374
+ -- Idempotente via INSERT ON CONFLICT
375
+ insert into public.payments (order_id, amount, transaction_id)
376
+ values ($1, $2, $3)
377
+ on conflict (transaction_id) do nothing;
378
+ -- Mesmo transaction_id → no-op
379
+ ```
380
+
381
+ **Cross-ref ATIVO** para [`escolha-modelo-consistencia`](../escolha-modelo-consistencia/SKILL.md) — pattern transactional outbox descrito lá é a base de exactly-once entre DB e broker (write atomic em mesma transação).
382
+
383
+ ### REQ STREAMS-05 — 3 tipos de stream join com SQL exemplo
384
+
385
+ **Tipo 1: Stream-stream join (com janela temporal)**
386
+
387
+ Match de eventos de 2 streams dentro de uma janela. Ex: matching pedido + pagamento dentro de 5min via tumbling window.
388
+
389
+ ```sql
390
+ -- Materialização: 2 tabelas event log + JOIN com window
391
+ create table public.order_events (
392
+ order_id uuid not null,
393
+ event_at timestamptz not null,
394
+ event_type text not null,
395
+ payload jsonb
396
+ );
397
+
398
+ create table public.payment_events (
399
+ payment_id uuid not null,
400
+ order_id uuid not null,
401
+ event_at timestamptz not null,
402
+ amount numeric
403
+ );
404
+
405
+ -- Stream-stream join via tumbling window 5min
406
+ create or replace view public.order_payment_join_5min as
407
+ select
408
+ o.order_id,
409
+ o.event_at as order_at,
410
+ p.event_at as paid_at,
411
+ p.amount,
412
+ date_trunc('minute', o.event_at) as window_start
413
+ from public.order_events o
414
+ join public.payment_events p on p.order_id = o.order_id
415
+ where o.event_type = 'order_created'
416
+ and p.event_at between o.event_at and o.event_at + interval '5 minutes'
417
+ order by o.event_at;
418
+ ```
419
+
420
+ **Trade-off:** janela tumbling = não-overlapping, mais simples; sliding = overlapping, mais alertas; session = dinâmica, agrupada por user activity.
421
+
422
+ **Tipo 2: Stream-table join (CDC + atividade — enrichment)**
423
+
424
+ Stream de eventos enriquecido com lookup em tabela de referência atualizada por CDC.
425
+
426
+ ```sql
427
+ -- Tabela users mantida atualizada via CDC (Realtime ou trigger)
428
+ -- Stream de eventos: clicks, logins, purchases — precisa enriched com user info
429
+
430
+ select
431
+ e.event_id,
432
+ e.event_type,
433
+ e.event_at,
434
+ -- Enrichment: lookup do user no momento atual (não do momento do evento)
435
+ u.email,
436
+ u.tier,
437
+ u.country
438
+ from public.user_events e
439
+ join public.users u on u.id = e.user_id
440
+ where e.event_at > now() - interval '1 hour';
441
+
442
+ -- Para latência baixa: keep tabela users em memória do worker (CDC stream → cache)
443
+ ```
444
+
445
+ **Cuidado canônico:** se a tabela mudou desde o evento, enrichment usa o estado **atual** do user, não o estado **no momento do evento**. Para histórico fiel: capturar snapshot no payload do evento (ex: `payload.user_email_at_event`).
446
+
447
+ **Tipo 3: Table-table join (merge de changelogs CDC)**
448
+
449
+ Merge de 2 changelogs CDC para produzir view denormalizada. Ex: orders changelog + customers changelog → view denormalizada de pedidos com info do cliente.
450
+
451
+ ```sql
452
+ -- Materialized view derivada de 2 streams CDC mergeados
453
+ create materialized view public.orders_denorm as
454
+ select
455
+ o.order_id,
456
+ o.status,
457
+ o.total,
458
+ o.created_at as order_created_at,
459
+ c.email as customer_email,
460
+ c.tier as customer_tier,
461
+ c.country as customer_country
462
+ from public.orders o
463
+ join public.customers c on c.id = o.customer_id;
464
+
465
+ create unique index on public.orders_denorm (order_id);
466
+
467
+ -- Refresh disparado por CDC events em orders OU customers
468
+ create or replace function public.refresh_orders_denorm()
469
+ returns trigger
470
+ language plpgsql
471
+ as $$
472
+ begin
473
+ refresh materialized view concurrently public.orders_denorm;
474
+ return null;
475
+ end;
476
+ $$;
477
+
478
+ create trigger orders_changelog_trigger
479
+ after insert or update on public.orders
480
+ for each statement
481
+ execute function public.refresh_orders_denorm();
482
+
483
+ create trigger customers_changelog_trigger
484
+ after update on public.customers
485
+ for each statement
486
+ execute function public.refresh_orders_denorm();
487
+ ```
488
+
489
+ **Trade-off:** refresh CONCURRENTLY exige unique index, latência maior. Para tabelas grandes, usar incremental refresh via trigger denormalization (REQ STREAMS-03).
490
+
491
+ ### REQ STREAMS-06 — Log compaction strategy
492
+
493
+ Log compaction = para cada chave, manter apenas o último valor. Reduz storage sem perder estado atual.
494
+
495
+ **pgmq não tem nativa** — usa retention TTL via `vacuum_archive`:
496
+
497
+ ```sql
498
+ -- pgmq archive movido para tabela archive periodicamente
499
+ select pgmq.archive('orders', 12345);
500
+ -- Após N dias na archive, vacuum_archive deleta hard
501
+
502
+ -- Configurar TTL via pg_cron
503
+ select cron.schedule(
504
+ 'pgmq_vacuum_archive',
505
+ '0 2 * * *',
506
+ $$ select pgmq.purge_archive('orders', 30); $$
507
+ -- Deleta da archive mensagens > 30 dias
508
+ );
509
+ ```
510
+
511
+ **Event sourcing exige snapshot periódico + compact:**
512
+
513
+ ```sql
514
+ -- Tabela de snapshots — estado materializado a cada N eventos
515
+ create table public.snapshots (
516
+ aggregate_id uuid primary key,
517
+ snapshot_lsn bigint not null, -- até qual event.id este snapshot reflete
518
+ state jsonb not null, -- estado serializado
519
+ created_at timestamptz not null default now()
520
+ );
521
+
522
+ -- Função: criar snapshot para um aggregate quando event_count > threshold
523
+ create or replace function public.create_snapshot(p_aggregate_id uuid)
524
+ returns void
525
+ language plpgsql
526
+ security invoker
527
+ set search_path = ''
528
+ as $$
529
+ declare
530
+ v_state jsonb;
531
+ v_snapshot_lsn bigint;
532
+ begin
533
+ -- Reproduzir todos os eventos para construir estado atual
534
+ select
535
+ jsonb_build_object(
536
+ 'status', (array_agg(payload->>'status' order by id desc))[1],
537
+ 'total', (array_agg(payload->>'total' order by id desc))[1]::numeric,
538
+ 'event_count', count(*)
539
+ ),
540
+ max(id)
541
+ into v_state, v_snapshot_lsn
542
+ from public.events
543
+ where aggregate_id = p_aggregate_id;
544
+
545
+ -- Salvar snapshot (insert or update)
546
+ insert into public.snapshots (aggregate_id, snapshot_lsn, state)
547
+ values (p_aggregate_id, v_snapshot_lsn, v_state)
548
+ on conflict (aggregate_id) do update
549
+ set snapshot_lsn = excluded.snapshot_lsn,
550
+ state = excluded.state,
551
+ created_at = now();
552
+ end;
553
+ $$;
554
+
555
+ -- Compact: deletar eventos < snapshot_lsn (tomados em consideração no snapshot)
556
+ -- ATENÇÃO: requer privilégio especial (REGRA #3 — REVOKE DELETE em events)
557
+ -- Apenas postgres role + função SECURITY DEFINER
558
+ create or replace function public.compact_aggregate_events(p_aggregate_id uuid)
559
+ returns int
560
+ language plpgsql
561
+ security definer
562
+ set search_path = ''
563
+ as $$
564
+ declare
565
+ v_deleted int;
566
+ v_snapshot_lsn bigint;
567
+ begin
568
+ -- Confirmar que snapshot existe
569
+ select snapshot_lsn into v_snapshot_lsn
570
+ from public.snapshots
571
+ where aggregate_id = p_aggregate_id;
572
+
573
+ if v_snapshot_lsn is null then
574
+ raise exception 'Snapshot ausente para aggregate_id %', p_aggregate_id;
575
+ end if;
576
+
577
+ -- Deletar eventos antes do snapshot
578
+ delete from public.events
579
+ where aggregate_id = p_aggregate_id
580
+ and id <= v_snapshot_lsn;
581
+
582
+ get diagnostics v_deleted = row_count;
583
+ return v_deleted;
584
+ end;
585
+ $$;
586
+
587
+ revoke execute on function public.compact_aggregate_events from public, authenticated, anon;
588
+ -- Apenas service_role pode chamar
589
+ ```
590
+
591
+ **Estratégia canônica:** snapshot a cada 1000 eventos por aggregate; compact após snapshot validado (replay do snapshot reproduz estado atual). Sem snapshot/compact, replay para reconstruir estado torna-se O(n) caro em aggregates antigos.
592
+
593
+ ## Anti-patterns
594
+
595
+ ### Anti-pattern 1: Usar LISTEN/NOTIFY para event sourcing
596
+
597
+ **Errado:**
598
+
599
+ ```sql
600
+ -- ❌ LISTEN/NOTIFY como "event log"
601
+ notify ch_orders, '{"order_id":"abc","event":"paid"}';
602
+ -- Consumer offline → mensagem perdida
603
+ -- Sem replay, sem multi-consumer
604
+ ```
605
+
606
+ **Por quê:** LISTEN/NOTIFY é AMQP/JMS-style — single consumer ativo recebe, mensagem some. Se consumer offline durante notify, evento perdido. Sem replay.
607
+
608
+ **Certo:** pgmq (log-based) ou tabela `events` append-only para event sourcing (REGRA #1).
609
+
610
+ ### Anti-pattern 2: Event sourcing sem dedup → eventos duplicados
611
+
612
+ **Errado:**
613
+
614
+ ```sql
615
+ -- ❌ Worker pgmq processa sem dedup table
616
+ create or replace function public.process_event(p_msg jsonb)
617
+ returns void
618
+ language plpgsql
619
+ as $$
620
+ begin
621
+ -- Processa direto, sem checar se já processado
622
+ update public.orders set status = 'paid' where id = (p_msg->>'order_id')::uuid;
623
+ -- Se mensagem reentregue (worker crash + redelivery) → status setado 2×
624
+ -- Se webhook externo → cobra cliente 2×
625
+ end;
626
+ $$;
627
+ ```
628
+
629
+ **Por quê:** pgmq é at-least-once. Mensagem pode ser entregue >1× (worker crash sem ack, visibility timeout expirado). Sem dedup, processamento repetido = side effect duplicado.
630
+
631
+ **Certo:** dedup table + handler idempotente (REGRA #4). Mesmo input → mesmo output.
632
+
633
+ ### Anti-pattern 3: Stream-stream join sem janela temporal
634
+
635
+ **Errado:**
636
+
637
+ ```sql
638
+ -- ❌ Sem janela temporal: memória cresce indefinidamente
639
+ select o.order_id, p.payment_id
640
+ from public.order_events o
641
+ join public.payment_events p on p.order_id = o.order_id;
642
+ -- Cada evento aguarda match indefinido — payment de 3 anos atrás casa com order recente
643
+ -- Memória do worker cresce sem limite
644
+ ```
645
+
646
+ **Por quê:** stream join sem TTL = sistema mantém eventos em memória aguardando match. Memória cresce linearmente com tempo, eventualmente OOM.
647
+
648
+ **Certo:** janela explícita (REGRA #5):
649
+
650
+ ```sql
651
+ -- ✅ Tumbling window 5min
652
+ join public.payment_events p on p.order_id = o.order_id
653
+ where p.event_at between o.event_at and o.event_at + interval '5 minutes';
654
+ ```
655
+
656
+ ### Anti-pattern 4: Materialized View sem CONCURRENTLY → bloqueio em refresh
657
+
658
+ **Errado:**
659
+
660
+ ```sql
661
+ -- ❌ refresh sem CONCURRENTLY trava reads na MV durante refresh
662
+ refresh materialized view public.order_state;
663
+ -- Bloqueia SELECT na MV até terminar — minutos em MVs grandes
664
+ ```
665
+
666
+ **Por quê:** refresh exclusivo locka a MV. Leitores ficam bloqueados.
667
+
668
+ **Certo:** CONCURRENTLY + unique index na MV:
669
+
670
+ ```sql
671
+ -- ✅ Unique index obrigatório para CONCURRENTLY
672
+ create unique index on public.order_state (order_id);
673
+
674
+ refresh materialized view concurrently public.order_state;
675
+ -- Refresh em background; reads continuam funcionando
676
+ ```
677
+
678
+ ### Anti-pattern 5: Event sourcing sem snapshot → replay O(n) caro
679
+
680
+ **Errado:**
681
+
682
+ ```sql
683
+ -- ❌ Reconstruir estado de aggregate antigo via replay completo
684
+ select * from public.events
685
+ where aggregate_id = $1
686
+ order by id;
687
+ -- Aggregate com 1M eventos → query lenta, alocação memória pesada
688
+ ```
689
+
690
+ **Por quê:** sem snapshot, replay para reconstruir estado é O(n) onde n = número total de eventos do aggregate. Em aggregates antigos (orders de 5 anos), aggregação fica cara.
691
+
692
+ **Certo:** snapshot periódico + replay incremental (REGRA #6):
693
+
694
+ ```sql
695
+ -- ✅ Carregar snapshot + replay apenas eventos posteriores
696
+ select state from public.snapshots where aggregate_id = $1;
697
+ -- Aplicar eventos com id > snapshot_lsn (poucos eventos recentes)
698
+ select * from public.events
699
+ where aggregate_id = $1 and id > (select snapshot_lsn from public.snapshots where aggregate_id = $1);
700
+ ```
701
+
702
+ ## Ver também
703
+
704
+ - [_shared-dados-distribuidos/glossary.md](../_shared-dados-distribuidos/glossary.md) — termos `AMQP/JMS-style broker`, `log-based broker`, `CDC`, `event sourcing`, `exactly-once semantics`, `at-least-once semantics`, `stream-stream join`, `stream-table join`, `table-table join`, `log compaction` (seção h)
705
+ - [audit-log-multi-tenant](../audit-log-multi-tenant/SKILL.md) — Phase 109 v1.21, audit_log É event sourcing semantics (REQ STREAMS-03 cross-ref ATIVO)
706
+ - [supabase-cron-queues](../supabase-cron-queues/SKILL.md) — v1.8, pgmq pattern + cleanup retention TTL
707
+ - [supabase-realtime](../supabase-realtime/SKILL.md) — v1.8, broadcast como CDC stream (REQ STREAMS-02 abordagem 1)
708
+ - [escolha-modelo-consistencia](../escolha-modelo-consistencia/SKILL.md) — Phase 121 (irmã), transactional outbox como base de exactly-once (REQ STREAMS-04 cross-ref ATIVO)
709
+ - [supabase-database-functions](../supabase-database-functions/SKILL.md) — v1.8, security invoker + search_path canônicos
710
+ - DDIA Ch 11 (Stream Processing, summary p.464) — material-fonte canônico
711
+ </content>
712
+ </invoke>