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