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