@luanpdd/kit-mcp 1.33.0 → 1.35.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 (379) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +168 -168
  3. package/gates/agent-no-recursive-dispatch.md +84 -84
  4. package/kit/COMANDOS.md +138 -138
  5. package/kit/COMPATIBILITY.md +70 -70
  6. package/kit/README.md +76 -76
  7. package/kit/agents/advisor-researcher.md +109 -109
  8. package/kit/agents/ai-mutation-tester.md +289 -289
  9. package/kit/agents/assumptions-analyzer.md +110 -110
  10. package/kit/agents/audit-log-implementer.md +314 -314
  11. package/kit/agents/auditor-consistencia-isolamento.md +414 -414
  12. package/kit/agents/b2b-saas-architect.md +157 -157
  13. package/kit/agents/burn-rate-forecaster.md +153 -153
  14. package/kit/agents/cascading-failures-auditor.md +299 -299
  15. package/kit/agents/codebase-mapper.md +769 -769
  16. package/kit/agents/crm-pipeline-implementer.md +257 -257
  17. package/kit/agents/debugger.md +814 -814
  18. package/kit/agents/designer-ui.md +216 -216
  19. package/kit/agents/detector-tenant-quente.md +338 -338
  20. package/kit/agents/evolution-go-integrator.md +201 -201
  21. package/kit/agents/example-reviewer.md +22 -22
  22. package/kit/agents/executor.md +565 -565
  23. package/kit/agents/golden-signals-instrumenter.md +232 -232
  24. package/kit/agents/incident-investigator.md +238 -238
  25. package/kit/agents/integration-checker.md +203 -203
  26. package/kit/agents/invite-flow-implementer.md +190 -190
  27. package/kit/agents/legacy-characterizer.md +369 -369
  28. package/kit/agents/lgpd-compliance-auditor.md +296 -296
  29. package/kit/agents/load-shedding-instrumenter.md +290 -290
  30. package/kit/agents/multi-tenant-isolation-auditor.md +254 -254
  31. package/kit/agents/multi-tenant-rls-writer.md +341 -341
  32. package/kit/agents/nyquist-auditor.md +181 -181
  33. package/kit/agents/observability-coverage-auditor.md +316 -316
  34. package/kit/agents/observability-instrumenter.md +191 -191
  35. package/kit/agents/omm-auditor.md +291 -291
  36. package/kit/agents/org-onboarding-implementer.md +224 -224
  37. package/kit/agents/payload-capture-instrumenter.md +274 -274
  38. package/kit/agents/phase-researcher.md +697 -697
  39. package/kit/agents/plan-checker.md +275 -275
  40. package/kit/agents/planner.md +923 -923
  41. package/kit/agents/postmortem-writer.md +273 -273
  42. package/kit/agents/project-researcher.md +653 -653
  43. package/kit/agents/prr-conductor.md +287 -287
  44. package/kit/agents/refactor-safety-auditor.md +405 -405
  45. package/kit/agents/release-pipeline-auditor.md +364 -364
  46. package/kit/agents/research-synthesizer.md +246 -246
  47. package/kit/agents/roadmapper.md +678 -678
  48. package/kit/agents/schema-checker.md +160 -160
  49. package/kit/agents/seam-finder.md +360 -360
  50. package/kit/agents/shotgun-surgery-detector.md +350 -350
  51. package/kit/agents/slo-engineer.md +217 -217
  52. package/kit/agents/storytelling-analyst.md +300 -300
  53. package/kit/agents/supabase-architect.md +249 -249
  54. package/kit/agents/supabase-auth-bootstrapper.md +400 -400
  55. package/kit/agents/supabase-auth-hook-writer.md +418 -418
  56. package/kit/agents/supabase-branching-architect.md +563 -563
  57. package/kit/agents/supabase-cicd-pipeline-implementer.md +778 -778
  58. package/kit/agents/supabase-column-privileges-writer.md +400 -400
  59. package/kit/agents/supabase-edge-fn-tester.md +288 -288
  60. package/kit/agents/supabase-edge-fn-writer.md +341 -341
  61. package/kit/agents/supabase-mfa-implementer.md +439 -439
  62. package/kit/agents/supabase-migration-writer.md +386 -386
  63. package/kit/agents/supabase-oauth-server-implementer.md +507 -507
  64. package/kit/agents/supabase-rbac-implementer.md +393 -393
  65. package/kit/agents/supabase-realtime-implementer.md +364 -364
  66. package/kit/agents/supabase-rls-hardener.md +522 -522
  67. package/kit/agents/supabase-rls-writer.md +324 -324
  68. package/kit/agents/supabase-roles-implementer.md +356 -356
  69. package/kit/agents/supabase-social-auth-implementer.md +451 -451
  70. package/kit/agents/supabase-sso-saml-architect.md +549 -549
  71. package/kit/agents/supabase-storage-implementer.md +407 -407
  72. package/kit/agents/super-admin-implementer.md +282 -282
  73. package/kit/agents/toil-auditor.md +268 -268
  74. package/kit/agents/ui-auditor.md +438 -438
  75. package/kit/agents/ui-checker.md +305 -305
  76. package/kit/agents/ui-researcher.md +356 -356
  77. package/kit/agents/user-profiler.md +176 -176
  78. package/kit/agents/validador-evolucao-schema.md +336 -336
  79. package/kit/agents/verifier.md +729 -729
  80. package/kit/agents/workflow-generator.md +167 -0
  81. package/kit/commands/adicionar-backlog.md +75 -75
  82. package/kit/commands/adicionar-fase.md +42 -42
  83. package/kit/commands/adicionar-tarefa.md +45 -45
  84. package/kit/commands/adicionar-testes.md +41 -41
  85. package/kit/commands/ajuda.md +21 -21
  86. package/kit/commands/atualizar.md +37 -37
  87. package/kit/commands/auditar-cascading.md +111 -111
  88. package/kit/commands/auditar-marco.md +179 -179
  89. package/kit/commands/auditar-observabilidade-cobertura-workflow.md +121 -0
  90. package/kit/commands/auditar-observabilidade-cobertura.md +183 -183
  91. package/kit/commands/auditar-refactor.md +219 -219
  92. package/kit/commands/auditar-release.md +109 -109
  93. package/kit/commands/auditar-uat.md +23 -23
  94. package/kit/commands/autonomo.md +40 -40
  95. package/kit/commands/branch-pr.md +24 -24
  96. package/kit/commands/burn-rate-status.md +408 -408
  97. package/kit/commands/capturar-payloads.md +193 -193
  98. package/kit/commands/caracterizar.md +212 -212
  99. package/kit/commands/concluir-marco.md +247 -247
  100. package/kit/commands/configuracoes.md +36 -36
  101. package/kit/commands/criar-workflow.md +158 -0
  102. package/kit/commands/dados-distribuidos.md +188 -188
  103. package/kit/commands/definir-perfil.md +10 -10
  104. package/kit/commands/depurar.md +190 -190
  105. package/kit/commands/detectar-duplicacao.md +197 -197
  106. package/kit/commands/discutir-fase.md +131 -131
  107. package/kit/commands/encontrar-seams.md +136 -136
  108. package/kit/commands/entrar-discord.md +17 -17
  109. package/kit/commands/estatisticas.md +18 -18
  110. package/kit/commands/example-greeting.md +33 -33
  111. package/kit/commands/executar-fase.md +58 -58
  112. package/kit/commands/expresso.md +56 -56
  113. package/kit/commands/fase-ui.md +34 -34
  114. package/kit/commands/fazer.md +57 -57
  115. package/kit/commands/fio.md +125 -125
  116. package/kit/commands/fluxos-trabalho.md +64 -64
  117. package/kit/commands/forense.md +176 -176
  118. package/kit/commands/gerenciador.md +38 -38
  119. package/kit/commands/inserir-fase.md +31 -31
  120. package/kit/commands/legacy.md +263 -263
  121. package/kit/commands/limpeza.md +17 -17
  122. package/kit/commands/listar-hipoteses-fase.md +45 -45
  123. package/kit/commands/listar-workspaces.md +18 -18
  124. package/kit/commands/load-shedding.md +117 -117
  125. package/kit/commands/mapear-codebase.md +70 -70
  126. package/kit/commands/multi-tenant.md +163 -163
  127. package/kit/commands/nota.md +33 -33
  128. package/kit/commands/novo-marco.md +43 -43
  129. package/kit/commands/novo-projeto.md +41 -41
  130. package/kit/commands/novo-workspace.md +43 -43
  131. package/kit/commands/pausar-trabalho.md +37 -37
  132. package/kit/commands/perfil-usuario.md +45 -45
  133. package/kit/commands/pesquisar-fase.md +195 -195
  134. package/kit/commands/planejar-fase.md +67 -67
  135. package/kit/commands/planejar-lacunas.md +33 -33
  136. package/kit/commands/plantar-ideia.md +25 -25
  137. package/kit/commands/progresso.md +24 -24
  138. package/kit/commands/proximo.md +30 -30
  139. package/kit/commands/publicar.md +490 -490
  140. package/kit/commands/rapido.md +35 -35
  141. package/kit/commands/reaplicar-patches.md +124 -124
  142. package/kit/commands/refactor-seguro.md +321 -321
  143. package/kit/commands/relatorio-sessao.md +19 -19
  144. package/kit/commands/remover-fase.md +31 -31
  145. package/kit/commands/remover-workspace.md +26 -26
  146. package/kit/commands/resumo-marco.md +50 -50
  147. package/kit/commands/retomar-trabalho.md +40 -40
  148. package/kit/commands/revisar-backlog.md +60 -60
  149. package/kit/commands/revisar-ui.md +32 -32
  150. package/kit/commands/revisar.md +37 -37
  151. package/kit/commands/saude.md +21 -21
  152. package/kit/commands/setup-notion.md +93 -93
  153. package/kit/commands/storytelling.md +179 -179
  154. package/kit/commands/supabase.md +238 -238
  155. package/kit/commands/sync-main.md +68 -68
  156. package/kit/commands/validar-fase.md +35 -35
  157. package/kit/commands/verificar-tarefas.md +44 -44
  158. package/kit/commands/verificar-trabalho.md +64 -64
  159. package/kit/file-manifest.json +424 -419
  160. package/kit/framework/bin/lib/commands.cjs +959 -959
  161. package/kit/framework/bin/lib/config.cjs +442 -442
  162. package/kit/framework/bin/lib/core.cjs +1230 -1230
  163. package/kit/framework/bin/lib/frontmatter.cjs +336 -336
  164. package/kit/framework/bin/lib/init.cjs +1442 -1442
  165. package/kit/framework/bin/lib/milestone.cjs +252 -252
  166. package/kit/framework/bin/lib/model-profiles.cjs +68 -68
  167. package/kit/framework/bin/lib/phase.cjs +888 -888
  168. package/kit/framework/bin/lib/profile-output.cjs +952 -952
  169. package/kit/framework/bin/lib/profile-pipeline.cjs +539 -539
  170. package/kit/framework/bin/lib/roadmap.cjs +329 -329
  171. package/kit/framework/bin/lib/security.cjs +382 -382
  172. package/kit/framework/bin/lib/state.cjs +1031 -1031
  173. package/kit/framework/bin/lib/template.cjs +222 -222
  174. package/kit/framework/bin/lib/uat.cjs +282 -282
  175. package/kit/framework/bin/lib/verify.cjs +888 -888
  176. package/kit/framework/bin/lib/workstream.cjs +491 -491
  177. package/kit/framework/bin/tools.cjs +918 -918
  178. package/kit/framework/commands/workstreams.md +63 -63
  179. package/kit/framework/references/checkpoints.md +778 -778
  180. package/kit/framework/references/continuation-format.md +249 -249
  181. package/kit/framework/references/decimal-phase-calculation.md +64 -64
  182. package/kit/framework/references/git-integration.md +295 -295
  183. package/kit/framework/references/git-planning-commit.md +38 -38
  184. package/kit/framework/references/model-profile-resolution.md +36 -36
  185. package/kit/framework/references/model-profiles.md +139 -139
  186. package/kit/framework/references/phase-argument-parsing.md +61 -61
  187. package/kit/framework/references/planning-config.md +202 -202
  188. package/kit/framework/references/questioning.md +162 -162
  189. package/kit/framework/references/tdd.md +263 -263
  190. package/kit/framework/references/ui-brand.md +160 -160
  191. package/kit/framework/references/user-profiling.md +657 -657
  192. package/kit/framework/references/verification-patterns.md +612 -612
  193. package/kit/framework/references/workstream-flag.md +58 -58
  194. package/kit/framework/templates/DEBUG.md +164 -164
  195. package/kit/framework/templates/UAT.md +265 -265
  196. package/kit/framework/templates/UI-SPEC.md +100 -100
  197. package/kit/framework/templates/VALIDATION.md +76 -76
  198. package/kit/framework/templates/claude-md.md +122 -122
  199. package/kit/framework/templates/codebase/architecture.md +185 -185
  200. package/kit/framework/templates/codebase/concerns.md +205 -205
  201. package/kit/framework/templates/codebase/conventions.md +204 -204
  202. package/kit/framework/templates/codebase/integrations.md +192 -192
  203. package/kit/framework/templates/codebase/stack.md +158 -158
  204. package/kit/framework/templates/codebase/structure.md +199 -199
  205. package/kit/framework/templates/codebase/testing.md +301 -301
  206. package/kit/framework/templates/config.json +44 -44
  207. package/kit/framework/templates/context.md +352 -352
  208. package/kit/framework/templates/continue-here.md +78 -78
  209. package/kit/framework/templates/copilot-instructions.md +7 -7
  210. package/kit/framework/templates/debug-subagent-prompt.md +91 -91
  211. package/kit/framework/templates/dev-preferences.md +20 -20
  212. package/kit/framework/templates/discovery.md +146 -146
  213. package/kit/framework/templates/discussion-log.md +63 -63
  214. package/kit/framework/templates/milestone-archive.md +123 -123
  215. package/kit/framework/templates/milestone.md +115 -115
  216. package/kit/framework/templates/phase-prompt.md +610 -610
  217. package/kit/framework/templates/planner-subagent-prompt.md +117 -117
  218. package/kit/framework/templates/project.md +186 -186
  219. package/kit/framework/templates/requirements.md +231 -231
  220. package/kit/framework/templates/research-project/ARCHITECTURE.md +204 -204
  221. package/kit/framework/templates/research-project/FEATURES.md +147 -147
  222. package/kit/framework/templates/research-project/PITFALLS.md +200 -200
  223. package/kit/framework/templates/research-project/STACK.md +120 -120
  224. package/kit/framework/templates/research-project/SUMMARY.md +170 -170
  225. package/kit/framework/templates/research.md +419 -419
  226. package/kit/framework/templates/retrospective.md +54 -54
  227. package/kit/framework/templates/roadmap.md +202 -202
  228. package/kit/framework/templates/state.md +176 -176
  229. package/kit/framework/templates/summary-complex.md +59 -59
  230. package/kit/framework/templates/summary-minimal.md +41 -41
  231. package/kit/framework/templates/summary-standard.md +48 -48
  232. package/kit/framework/templates/summary.md +209 -209
  233. package/kit/framework/templates/user-profile.md +146 -146
  234. package/kit/framework/templates/user-setup.md +256 -256
  235. package/kit/framework/templates/verification-report.md +258 -258
  236. package/kit/framework/workflows/add-phase.md +112 -112
  237. package/kit/framework/workflows/add-tests.md +351 -351
  238. package/kit/framework/workflows/add-todo.md +158 -158
  239. package/kit/framework/workflows/audit-milestone.md +340 -340
  240. package/kit/framework/workflows/audit-uat.md +109 -109
  241. package/kit/framework/workflows/autonomous.md +891 -891
  242. package/kit/framework/workflows/check-todos.md +177 -177
  243. package/kit/framework/workflows/cleanup.md +152 -152
  244. package/kit/framework/workflows/complete-milestone.md +696 -696
  245. package/kit/framework/workflows/diagnose-issues.md +231 -231
  246. package/kit/framework/workflows/discovery-phase.md +289 -289
  247. package/kit/framework/workflows/discuss-phase-assumptions.md +653 -653
  248. package/kit/framework/workflows/discuss-phase.md +784 -784
  249. package/kit/framework/workflows/do.md +104 -104
  250. package/kit/framework/workflows/execute-phase.md +838 -838
  251. package/kit/framework/workflows/execute-plan.md +510 -510
  252. package/kit/framework/workflows/fast.md +102 -102
  253. package/kit/framework/workflows/forensics.md +265 -265
  254. package/kit/framework/workflows/health.md +181 -181
  255. package/kit/framework/workflows/help.md +619 -619
  256. package/kit/framework/workflows/insert-phase.md +130 -130
  257. package/kit/framework/workflows/list-phase-assumptions.md +178 -178
  258. package/kit/framework/workflows/list-workspaces.md +56 -56
  259. package/kit/framework/workflows/manager.md +362 -362
  260. package/kit/framework/workflows/map-codebase.md +377 -377
  261. package/kit/framework/workflows/milestone-summary.md +223 -223
  262. package/kit/framework/workflows/new-milestone.md +486 -486
  263. package/kit/framework/workflows/new-project.md +1159 -1159
  264. package/kit/framework/workflows/new-workspace.md +237 -237
  265. package/kit/framework/workflows/next.md +97 -97
  266. package/kit/framework/workflows/node-repair.md +92 -92
  267. package/kit/framework/workflows/note.md +156 -156
  268. package/kit/framework/workflows/pause-work.md +176 -176
  269. package/kit/framework/workflows/plan-milestone-gaps.md +273 -273
  270. package/kit/framework/workflows/plan-phase.md +765 -765
  271. package/kit/framework/workflows/plant-seed.md +169 -169
  272. package/kit/framework/workflows/pr-branch.md +129 -129
  273. package/kit/framework/workflows/profile-user.md +450 -450
  274. package/kit/framework/workflows/progress.md +507 -507
  275. package/kit/framework/workflows/quick.md +757 -757
  276. package/kit/framework/workflows/remove-phase.md +155 -155
  277. package/kit/framework/workflows/remove-workspace.md +90 -90
  278. package/kit/framework/workflows/research-phase.md +82 -82
  279. package/kit/framework/workflows/resume-project.md +326 -326
  280. package/kit/framework/workflows/review.md +228 -228
  281. package/kit/framework/workflows/session-report.md +146 -146
  282. package/kit/framework/workflows/settings.md +283 -283
  283. package/kit/framework/workflows/ship.md +228 -228
  284. package/kit/framework/workflows/stats.md +60 -60
  285. package/kit/framework/workflows/transition.md +671 -671
  286. package/kit/framework/workflows/ui-phase.md +302 -302
  287. package/kit/framework/workflows/ui-review.md +165 -165
  288. package/kit/framework/workflows/update.md +323 -323
  289. package/kit/framework/workflows/validate-phase.md +174 -174
  290. package/kit/framework/workflows/verify-phase.md +252 -252
  291. package/kit/framework/workflows/verify-work.md +637 -637
  292. package/kit/hooks/check-update.js +118 -118
  293. package/kit/hooks/context-monitor.js +163 -163
  294. package/kit/hooks/kit-attribution-reminder.cjs +92 -92
  295. package/kit/hooks/kit-router.cjs +137 -137
  296. package/kit/hooks/prompt-guard.js +103 -103
  297. package/kit/hooks/statusline.js +125 -125
  298. package/kit/hooks/workflow-guard.js +101 -101
  299. package/kit/settings.json +45 -45
  300. package/kit/skills/ai-prompt-characterization/SKILL.md +335 -335
  301. package/kit/skills/armadilhas-sistemas-distribuidos/SKILL.md +447 -447
  302. package/kit/skills/audit-log-multi-tenant/SKILL.md +340 -340
  303. package/kit/skills/b2b-saas-architecture/SKILL.md +300 -300
  304. package/kit/skills/consistencia-leitura-replica/SKILL.md +385 -385
  305. package/kit/skills/crm-lead-pipeline-patterns/SKILL.md +343 -343
  306. package/kit/skills/dynamic-workflow-authoring/SKILL.md +223 -0
  307. package/kit/skills/escolha-modelo-consistencia/SKILL.md +494 -494
  308. package/kit/skills/evolucao-schema-compativel/SKILL.md +448 -448
  309. package/kit/skills/evolution-go-whatsapp-integration/SKILL.md +322 -322
  310. package/kit/skills/example-skill/SKILL.md +42 -42
  311. package/kit/skills/legacy-api-only-applications/SKILL.md +358 -358
  312. package/kit/skills/legacy-characterization-tests/SKILL.md +330 -330
  313. package/kit/skills/legacy-effect-analysis/SKILL.md +331 -331
  314. package/kit/skills/legacy-extract-class/SKILL.md +203 -203
  315. package/kit/skills/legacy-programming-by-difference/SKILL.md +252 -252
  316. package/kit/skills/legacy-seams-and-test-harness/SKILL.md +460 -460
  317. package/kit/skills/legacy-shotgun-surgery/SKILL.md +286 -286
  318. package/kit/skills/legacy-sprout-wrap-techniques/SKILL.md +434 -434
  319. package/kit/skills/legacy-storytelling-naked-crc/SKILL.md +270 -270
  320. package/kit/skills/lgpd-multi-tenant-compliance/SKILL.md +340 -340
  321. package/kit/skills/member-invite-flow/SKILL.md +305 -305
  322. package/kit/skills/member-management-react-shadcn/SKILL.md +328 -328
  323. package/kit/skills/multi-tenant-performance-scaling/SKILL.md +316 -316
  324. package/kit/skills/multi-tenant-rls-hierarchy/SKILL.md +342 -342
  325. package/kit/skills/org-onboarding-flow/SKILL.md +257 -257
  326. package/kit/skills/org-switcher-react-pattern/SKILL.md +349 -349
  327. package/kit/skills/permission-gate-react-pattern/SKILL.md +271 -271
  328. package/kit/skills/postgres-isolamento-concorrencia/SKILL.md +552 -552
  329. package/kit/skills/pre-refactor-characterization/SKILL.md +421 -421
  330. package/kit/skills/rbac-permissions-matrix-supabase/SKILL.md +338 -338
  331. package/kit/skills/streams-eventos-cdc/SKILL.md +711 -711
  332. package/kit/skills/supabase-auth-hardening/SKILL.md +674 -674
  333. package/kit/skills/supabase-auth-hooks/SKILL.md +875 -875
  334. package/kit/skills/supabase-auth-methods/SKILL.md +486 -486
  335. package/kit/skills/supabase-auth-sessions/SKILL.md +579 -579
  336. package/kit/skills/supabase-auth-ssr/SKILL.md +306 -306
  337. package/kit/skills/supabase-branching-workflow/SKILL.md +544 -544
  338. package/kit/skills/supabase-ci-cd-github-actions/SKILL.md +880 -880
  339. package/kit/skills/supabase-column-level-security/SKILL.md +426 -426
  340. package/kit/skills/supabase-config-toml-remotes/SKILL.md +807 -807
  341. package/kit/skills/supabase-custom-claims-rbac/SKILL.md +472 -472
  342. package/kit/skills/supabase-edge-functions/SKILL.md +330 -330
  343. package/kit/skills/supabase-edge-functions-auth/SKILL.md +309 -309
  344. package/kit/skills/supabase-edge-functions-limits/SKILL.md +302 -302
  345. package/kit/skills/supabase-edge-functions-mcp-server/SKILL.md +279 -279
  346. package/kit/skills/supabase-edge-functions-testing/SKILL.md +277 -277
  347. package/kit/skills/supabase-edge-runtime-builtins/SKILL.md +357 -357
  348. package/kit/skills/supabase-enterprise-sso-saml/SKILL.md +545 -545
  349. package/kit/skills/supabase-jwt-signing-keys/SKILL.md +399 -399
  350. package/kit/skills/supabase-mfa/SKILL.md +488 -488
  351. package/kit/skills/supabase-migration-repair/SKILL.md +823 -823
  352. package/kit/skills/supabase-migrations/SKILL.md +297 -297
  353. package/kit/skills/supabase-oauth-server/SKILL.md +537 -537
  354. package/kit/skills/supabase-pgtap-testing/SKILL.md +1053 -1053
  355. package/kit/skills/supabase-postgres-roles/SKILL.md +392 -392
  356. package/kit/skills/supabase-realtime/SKILL.md +460 -460
  357. package/kit/skills/supabase-rls-defense-in-depth/SKILL.md +418 -418
  358. package/kit/skills/supabase-rls-policies/SKILL.md +635 -635
  359. package/kit/skills/supabase-social-oauth/SKILL.md +480 -480
  360. package/kit/skills/supabase-third-party-auth/SKILL.md +450 -450
  361. package/kit/skills/super-admin-platform-pattern/SKILL.md +326 -326
  362. package/kit/skills/tenant-quente-mitigacao/SKILL.md +605 -605
  363. package/kit/skills/ui-anti-padroes-ia/SKILL.md +261 -261
  364. package/kit/skills/ui-contexto-produto/SKILL.md +248 -248
  365. package/kit/skills/ui-cor-estrategia/SKILL.md +213 -213
  366. package/kit/skills/ui-critica-auditoria/SKILL.md +260 -260
  367. package/kit/skills/ui-motion-funcional/SKILL.md +264 -264
  368. package/kit/skills/ui-ritmo-espacial/SKILL.md +259 -259
  369. package/kit/skills/ui-tipografia/SKILL.md +211 -211
  370. package/kit/skills/whatsapp-conversation-state-machine/SKILL.md +287 -287
  371. package/kit/workflows/auditar-observabilidade-cobertura.workflow.js +250 -0
  372. package/package.json +65 -63
  373. package/src/core/kit.js +333 -216
  374. package/src/core/reflect.js +247 -247
  375. package/src/core/registry.js +123 -112
  376. package/src/core/reverse-sync.js +448 -372
  377. package/src/core/sync.js +477 -437
  378. package/src/core/watch.js +121 -121
  379. package/src/mcp-server/index.js +794 -794
@@ -1,712 +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, ev…
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>
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, ev…
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
712
  </invoke>