@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,552 +1,552 @@
1
- ---
2
- name: postgres-isolamento-concorrencia
3
- description: Use ao escrever transação Postgres com risco de race condition…
4
- ---
5
-
6
- # Isolamento e Concorrência Postgres — 6 Race Conditions, Decision Tree, 3 Padrões para Lost Update
7
-
8
- ## Quando usar
9
-
10
- LLM carrega esta skill ao escrever ou revisar transação Postgres com risco de race condition concorrente. Trigger phrases:
11
-
12
- - "transação Postgres concorrente", "race condition", "isolamento", "isolation level"
13
- - "lost update", "write skew", "phantom read", "dirty read", "read skew"
14
- - "SELECT FOR UPDATE", "advisory lock", "compare-and-swap", "version optimistic lock"
15
- - "snapshot isolation", "SERIALIZABLE", "REPEATABLE READ", "READ COMMITTED"
16
- - "MVCC", "SSI", "predicate lock", "exclusion constraint"
17
- - "duplicate insert na concorrência", "contador errado", "saldo negativo"
18
- - "duas transações alterando a mesma row"
19
-
20
- Esta skill **estende** [`supabase-database-functions`](../supabase-database-functions/SKILL.md) (v1.8) — herda STABLE/IMMUTABLE/VOLATILE markers e adiciona escolha de isolation level + padrões para lost update/write skew/phantom em transações multi-statement.
21
-
22
- Termos canônicos preservados em EN porque são padrão internacional do manual oficial Postgres ([transaction-iso.html](https://www.postgresql.org/docs/current/transaction-iso.html)) e do livro DDIA Ch 7. Definições PT-BR ↔ EN no glossário [`_shared-dados-distribuidos/glossary.md`](../_shared-dados-distribuidos/glossary.md) seção (c).
23
-
24
- ## Regras absolutas
25
-
26
- **REGRA #1 (Postgres NÃO permite dirty read):** Mesmo se a aplicação solicitar `READ UNCOMMITTED`, o Postgres silenciosamente promove para `READ COMMITTED`. Citação: manual oficial — *"In PostgreSQL, you can request any of the four standard transaction isolation levels, but internally only three distinct isolation levels are implemented, namely PostgreSQL's Read Uncommitted mode behaves like Read Committed."*
27
-
28
- **REGRA #2 (default = READ COMMITTED é OK para 95% dos casos):** Se a transação faz UPDATE ou INSERT em uma única tabela com `WHERE id = $1`, READ COMMITTED basta — Postgres garante atomicidade no nível da row. Subir para REPEATABLE READ ou SERIALIZABLE custa abort rate (`serialization_failure`) e não traz benefício.
29
-
30
- **REGRA #3 (lost update tem 3 padrões — escolher por workload):** Sob READ COMMITTED, dois `UPDATE` concorrentes baseados em valor lido podem perder uma das mudanças (read-modify-write não-atômico). Resolver via uma de 3 técnicas — pessimista (`FOR UPDATE`), otimista (CAS via `WHERE version = $v`) ou semântica (`pg_advisory_xact_lock`). Veja Padrão 3 abaixo para escolher por workload.
31
-
32
- **REGRA #4 (write skew exige uma de 3 técnicas — não OR genérico):** Snapshot isolation (REPEATABLE READ Postgres) NÃO previne write skew. Resolver com uma de 3 técnicas — materializar conflito via `FOR UPDATE` em rows lidas, declarar conflito via `EXCLUDE USING gist` constraint, ou subir para `SERIALIZABLE` (Postgres SSI = predicate-aware) como fallback genérico.
33
-
34
- **REGRA #5 (REPEATABLE READ NÃO previne phantom em Postgres):** Apenas `SERIALIZABLE` (que usa SSI desde Postgres 9.1) previne phantom porque é predicate-aware. REPEATABLE READ implementa snapshot isolation = imagem consistente do start da trx, mas INSERT cross-trx que cai no predicate ainda altera resultado em re-query no mesmo snapshot.
35
-
36
- **REGRA #6 (SERIALIZABLE = aborts esporádicos — app precisa retry):** SSI é otimista — transações executam sem bloquear; ao commit verifica serializabilidade e aborta com `SQLSTATE 40001` se não. App **DEVE** ter retry loop com backoff (recomendado: até 3 tentativas, exponential backoff a partir de 50ms).
37
-
38
- ## Patterns canônicos
39
-
40
- ### REQ ISOLAMENTO-01 — Os 6 race conditions com SQL exemplo isolado
41
-
42
- #### 1. Dirty read
43
-
44
- Definição: T1 lê dados não-commitados de T2; depois T2 rollback.
45
-
46
- **Postgres NÃO permite (REGRA #1):**
47
-
48
- ```sql
49
- -- Sessão A
50
- begin isolation level read uncommitted; -- silenciosamente vira READ COMMITTED
51
- update public.accounts set balance = balance - 100 where id = 1;
52
- -- (sem commit)
53
-
54
- -- Sessão B
55
- begin;
56
- select balance from public.accounts where id = 1;
57
- -- Retorna o saldo ANTES do update da Sessão A — SEM dirty read.
58
- -- Isso é por design Postgres; nenhum nível de isolamento permite ver writes não-commitados.
59
- ```
60
-
61
- #### 2. Dirty write
62
-
63
- Definição: T1 sobrescreve write não-commitado de T2.
64
-
65
- **Postgres previne via row-level lock implícito em UPDATE:**
66
-
67
- ```sql
68
- -- Sessão A
69
- begin;
70
- update public.accounts set balance = 200 where id = 1; -- adquire ROW EXCLUSIVE lock
71
- -- (sem commit ainda)
72
-
73
- -- Sessão B
74
- begin;
75
- update public.accounts set balance = 300 where id = 1;
76
- -- Sessão B BLOQUEIA aqui até A commitar ou rollback.
77
- -- Quando A commita, B procede — sem dirty write.
78
- ```
79
-
80
- #### 3. Read skew (non-repeatable read)
81
-
82
- Definição: T1 lê row → T2 commita update na mesma row → T1 re-lê e vê valor diferente.
83
-
84
- **Acontece sob READ COMMITTED; prevenido por REPEATABLE READ (snapshot isolation):**
85
-
86
- ```sql
87
- -- Sessão A — READ COMMITTED (default)
88
- begin;
89
- select balance from public.accounts where id = 1; -- 100
90
-
91
- -- Sessão B (entre as queries)
92
- begin;
93
- update public.accounts set balance = 500 where id = 1;
94
- commit;
95
-
96
- -- Sessão A continua
97
- select balance from public.accounts where id = 1; -- 500 (DIFERENTE — read skew)
98
- commit;
99
-
100
- -- Solução: subir A para REPEATABLE READ
101
- begin isolation level repeatable read;
102
- select balance from public.accounts where id = 1; -- 100
103
- -- (B faz update e commit no meio)
104
- select balance from public.accounts where id = 1; -- 100 (mesmo snapshot)
105
- commit;
106
- ```
107
-
108
- #### 4. Lost update
109
-
110
- Definição: T1 e T2 fazem read-modify-write concorrentes; uma sobrescreve a outra sem incorporar mudanças.
111
-
112
- **Cenário clássico — contador de vendas:**
113
-
114
- ```sql
115
- -- Sessão A
116
- begin;
117
- select sales_count from public.products where id = 1; -- 10
118
- -- App computa: 10 + 1 = 11
119
- update public.products set sales_count = 11 where id = 1;
120
- commit;
121
-
122
- -- Sessão B (concorrente)
123
- begin;
124
- select sales_count from public.products where id = 1; -- 10 (mesmo snapshot)
125
- -- App computa: 10 + 1 = 11
126
- update public.products set sales_count = 11 where id = 1;
127
- commit;
128
-
129
- -- Resultado: sales_count = 11 (DEVERIA SER 12 — uma venda perdida)
130
- ```
131
-
132
- Solução: ver REQ ISOLAMENTO-03 (3 padrões abaixo).
133
-
134
- #### 5. Write skew
135
-
136
- Definição: T1 e T2 leem o mesmo predicate, decidem com base nele, escrevem coisas diferentes que invalidam o predicate.
137
-
138
- **Cenário canônico DDIA — doctor on-call (precisa ≥ 1 doctor on-call):**
139
-
140
- ```sql
141
- -- Schema
142
- create table public.doctors (
143
- id bigserial primary key,
144
- name text not null,
145
- on_call boolean not null
146
- );
147
- insert into public.doctors (name, on_call) values
148
- ('Alice', true),
149
- ('Bob', true);
150
-
151
- -- Sessão A — Alice quer sair de plantão
152
- begin isolation level repeatable read;
153
- -- Alice lê: 2 doctors on-call → invariante "≥ 1" mantido se eu sair → posso sair
154
- select count(*) from public.doctors where on_call = true; -- 2
155
-
156
- -- Sessão B (concorrente) — Bob quer sair de plantão
157
- begin isolation level repeatable read;
158
- -- Bob lê: 2 doctors on-call → invariante "≥ 1" mantido se eu sair → posso sair
159
- select count(*) from public.doctors where on_call = true; -- 2 (mesmo snapshot)
160
-
161
- -- Sessão A
162
- update public.doctors set on_call = false where name = 'Alice';
163
- commit;
164
-
165
- -- Sessão B (não vê o write de A — snapshot isolation)
166
- update public.doctors set on_call = false where name = 'Bob';
167
- commit;
168
-
169
- -- Resultado: 0 doctors on-call (INVARIANTE QUEBRADA — write skew)
170
- ```
171
-
172
- Solução: ver REQ ISOLAMENTO-04 (3 caminhos).
173
-
174
- #### 6. Phantom read
175
-
176
- Definição: T1 query com WHERE → T2 INSERT row matching WHERE → T1 re-query vê novo row.
177
-
178
- **Acontece sob READ COMMITTED e até REPEATABLE READ no manual SQL padrão; prevenido por SERIALIZABLE em Postgres:**
179
-
180
- ```sql
181
- -- Sessão A — booking de sala
182
- begin isolation level repeatable read;
183
- select count(*) from public.bookings
184
- where room_id = 'sala_a'
185
- and tstzrange(starts_at, ends_at, '[)') && tstzrange('2026-06-01 09:00', '2026-06-01 10:00', '[)');
186
- -- 0 conflitos → posso reservar
187
-
188
- -- Sessão B (concorrente)
189
- begin isolation level repeatable read;
190
- select count(*) from public.bookings
191
- where room_id = 'sala_a'
192
- and tstzrange(starts_at, ends_at, '[)') && tstzrange('2026-06-01 09:00', '2026-06-01 10:00', '[)');
193
- -- 0 conflitos → posso reservar (mesmo snapshot)
194
-
195
- insert into public.bookings (room_id, starts_at, ends_at) values ('sala_a', '2026-06-01 09:00', '2026-06-01 10:00');
196
- commit;
197
-
198
- -- Sessão A
199
- insert into public.bookings (room_id, starts_at, ends_at) values ('sala_a', '2026-06-01 09:00', '2026-06-01 10:00');
200
- commit;
201
-
202
- -- Resultado: 2 bookings overlap (PHANTOM — REPEATABLE READ não detecta INSERT cross-trx)
203
- ```
204
-
205
- Solução: SERIALIZABLE (REGRA #5) ou `EXCLUDE USING gist` constraint (ver REQ ISOLAMENTO-04).
206
-
207
- ---
208
-
209
- ### REQ ISOLAMENTO-02 — Árvore de decisão isolation level
210
-
211
- ```
212
- Qual transação você está escrevendo?
213
-
214
- ├─ CRUD simples: INSERT / UPDATE / DELETE em UMA tabela com WHERE id = $1
215
- │ └─► READ COMMITTED (default)
216
- │ • Postgres garante atomicidade na row (REGRA #2)
217
- │ • Sem aborts esporádicos
218
- │ • 95% dos casos em Edge Functions Supabase
219
-
220
- ├─ Relatório / query longa em MÚLTIPLAS tabelas, snapshot consistente importa
221
- │ └─► REPEATABLE READ (snapshot isolation via MVCC)
222
- │ • set transaction isolation level repeatable read;
223
- │ • Garante que todas as queries da trx veem a mesma "foto" do DB
224
- │ • Útil para: dashboards, exports CSV, agregações com JOINs
225
- │ • NÃO previne lost update / write skew / phantom
226
-
227
- └─ Invariante complexa cross-row: doctor on-call, booking sem overlap, saldo ≥ 0
228
- └─► SERIALIZABLE (Postgres SSI — predicate-aware desde 9.1)
229
- • set transaction isolation level serializable;
230
- • Detecta write skew, phantom, lost update genérico
231
- • Custo: aborts esporádicos com SQLSTATE 40001 — app DEVE ter retry (REGRA #6)
232
- • Quando outras técnicas (FOR UPDATE / EXCLUDE constraint) não cabem
233
- ```
234
-
235
- **Setando isolation level — 3 formas:**
236
-
237
- ```sql
238
- -- (a) Por transação — recomendado (escopo claro)
239
- begin isolation level serializable;
240
- -- ... queries ...
241
- commit;
242
-
243
- -- (b) Por sessão (afeta TODAS as transações da sessão até reset)
244
- set session characteristics as transaction isolation level serializable;
245
-
246
- -- (c) Por sistema (postgresql.conf — NÃO recomendado)
247
- -- default_transaction_isolation = 'serializable'
248
- ```
249
-
250
- ---
251
-
252
- ### REQ ISOLAMENTO-03 — 3 padrões para prevenir lost update
253
-
254
- | Padrão | Sintaxe | Tradeoff | Quando usar |
255
- |---|---|---|---|
256
- | **(a) Pessimista — `SELECT ... FOR UPDATE`** | `select balance from accounts where id = $1 for update;` | Bloqueia leitores concorrentes; baixo throughput se contention alta | Writes raros + contention baixa (ex: settlement bancário 1×/dia) |
257
- | **(b) Otimista — CAS via `WHERE version`** | `update accounts set balance = $1, version = version + 1 where id = $2 and version = $v;` | Sem bloqueio; precisa retry on miss (return 0 rows) | Writes frequentes + contention baixa-média (ex: contador de likes) |
258
- | **(c) Semântico — `pg_advisory_xact_lock`** | `select pg_advisory_xact_lock(hashtext('lock_name'));` | Lock por nome lógico, não amarra a row; libera no commit/rollback | Lock global / singleton operation (ex: cron job que NÃO deve rodar 2× concorrente) |
259
-
260
- #### Padrão (a) — SELECT FOR UPDATE pessimista
261
-
262
- ```sql
263
- -- Transferência bancária — débito + crédito atômico
264
- begin;
265
- -- Lock em ambas as rows ANTES de ler (ordering por ID previne deadlock)
266
- select balance from public.accounts where id = least($from_id, $to_id) for update;
267
- select balance from public.accounts where id = greatest($from_id, $to_id) for update;
268
-
269
- -- Validação no app
270
- -- if balance_from < amount: rollback
271
-
272
- update public.accounts set balance = balance - $amount where id = $from_id;
273
- update public.accounts set balance = balance + $amount where id = $to_id;
274
- commit;
275
- ```
276
-
277
- #### Padrão (b) — CAS otimista com version column
278
-
279
- ```sql
280
- -- Schema
281
- alter table public.products add column if not exists version bigint not null default 0;
282
-
283
- -- App — retry loop
284
- -- 1. Read
285
- select sales_count, version from public.products where id = $1;
286
- -- (assume: sales_count = 10, version = 7)
287
-
288
- -- 2. App computa: sales_count + 1 = 11
289
-
290
- -- 3. Atomic compare-and-swap
291
- update public.products
292
- set sales_count = 11,
293
- version = version + 1
294
- where id = $1
295
- and version = 7;
296
- -- if rowcount = 0: outra trx mudou — retry desde o read
297
-
298
- -- 4. Commit
299
- commit;
300
- ```
301
-
302
- #### Padrão (c) — Advisory lock semântico
303
-
304
- ```sql
305
- -- Cron job que sincroniza dados externos — NÃO pode rodar 2× concorrente
306
- begin;
307
- -- Lock por nome lógico (NÃO amarra row específica)
308
- select pg_advisory_xact_lock(hashtext('sync_external_api'));
309
-
310
- -- Se outra instância do cron já tem o lock, esta trx aguarda.
311
- -- Como é xact_lock, libera automaticamente no commit/rollback.
312
-
313
- -- ... lógica de sync ...
314
- commit;
315
- ```
316
-
317
- **Regra de polegar para escolher:**
318
- - Lock global, single-row ou multi-row arbitrário → **(c) advisory_xact_lock**
319
- - Lock em rows específicas conhecidas a priori, contention baixa → **(a) FOR UPDATE**
320
- - Update incremental sem dependência de outras rows, contention média-alta → **(b) CAS otimista**
321
-
322
- ---
323
-
324
- ### REQ ISOLAMENTO-04 — Prevenção write skew (3 caminhos)
325
-
326
- #### Caminho 1 — Materializar conflito via FOR UPDATE em rows lidas
327
-
328
- ```sql
329
- -- Sessão A — Alice quer sair de plantão
330
- begin;
331
- -- Lock em TODAS as rows do predicate (materializa o conflito)
332
- select count(*) from public.doctors where on_call = true for update; -- 2
333
-
334
- -- Sessão B aguarda aqui (até A commitar)
335
-
336
- update public.doctors set on_call = false where name = 'Alice';
337
- commit;
338
-
339
- -- Sessão B desbloqueia agora
340
- select count(*) from public.doctors where on_call = true for update; -- 1 (não 2!)
341
- -- Lógica do app verifica: count = 1, se eu sair vai para 0 → ABORT
342
- rollback;
343
- ```
344
-
345
- Limitação: precisa enumerar TODAS as rows do predicate. Se o predicate é grande/aberto (ex: `where price > 100`), `FOR UPDATE` não locka rows futuras — apenas existentes.
346
-
347
- #### Caminho 2 — Declarar conflito via EXCLUDE USING gist (ideal para overlap)
348
-
349
- ```sql
350
- -- Booking de sala SEM overlap (room_id + range temporal)
351
- create extension if not exists btree_gist;
352
-
353
- create table public.bookings (
354
- id bigserial primary key,
355
- room_id text not null,
356
- starts_at timestamptz not null,
357
- ends_at timestamptz not null,
358
- -- Constraint declarativo: zero overlap por sala
359
- exclude using gist (
360
- room_id with =,
361
- tstzrange(starts_at, ends_at, '[)') with &&
362
- )
363
- );
364
-
365
- -- Sessão A
366
- begin;
367
- insert into public.bookings (room_id, starts_at, ends_at)
368
- values ('sala_a', '2026-06-01 09:00', '2026-06-01 10:00');
369
- commit;
370
-
371
- -- Sessão B (concorrente)
372
- begin;
373
- insert into public.bookings (room_id, starts_at, ends_at)
374
- values ('sala_a', '2026-06-01 09:00', '2026-06-01 10:00');
375
- -- ERROR: conflicting key value violates exclusion constraint
376
- -- (DB rejeita declarativamente — sem app logic)
377
- ```
378
-
379
- Aplicação canônica: room booking, schedule slot, version range, IP allocation.
380
-
381
- #### Caminho 3 — SERIALIZABLE (Postgres SSI = predicate-aware) como fallback genérico
382
-
383
- Quando enumerar rows é caro (predicate aberto) e EXCLUDE constraint não modela o conflito, subir para SERIALIZABLE:
384
-
385
- ```sql
386
- -- Doctor on-call — solução genérica
387
- begin isolation level serializable;
388
- select count(*) from public.doctors where on_call = true; -- 2
389
-
390
- update public.doctors set on_call = false where name = 'Alice';
391
-
392
- commit;
393
- -- Se trx concorrente (Bob) tentou o mesmo: uma das duas aborta com
394
- -- SQLSTATE 40001 (serialization_failure). App precisa retry (REGRA #6).
395
- ```
396
-
397
- App retry loop canônico:
398
-
399
- ```typescript
400
- // Edge Function — Deno
401
- import { Pool } from "npm:pg@8";
402
-
403
- async function withSerializableRetry<T>(
404
- pool: Pool,
405
- fn: (client: PoolClient) => Promise<T>,
406
- maxRetries = 3,
407
- ): Promise<T> {
408
- for (let attempt = 0; attempt < maxRetries; attempt++) {
409
- const client = await pool.connect();
410
- try {
411
- await client.query("begin isolation level serializable");
412
- const result = await fn(client);
413
- await client.query("commit");
414
- return result;
415
- } catch (err: unknown) {
416
- await client.query("rollback");
417
- // SQLSTATE 40001 = serialization_failure (retryable)
418
- if ((err as { code?: string }).code === "40001" && attempt < maxRetries - 1) {
419
- // Backoff exponencial: 50ms, 100ms, 200ms
420
- await new Promise((r) => setTimeout(r, 50 * 2 ** attempt));
421
- continue;
422
- }
423
- throw err;
424
- } finally {
425
- client.release();
426
- }
427
- }
428
- throw new Error("Max retries excedido — contention demais ou bug");
429
- }
430
- ```
431
-
432
- ---
433
-
434
- ### REQ ISOLAMENTO-05 — Prevenção phantom read
435
-
436
- **Contraste canônico:**
437
-
438
- | Isolation level | Snapshot isolation | Predicate-aware | Previne phantom? |
439
- |---|---|---|---|
440
- | READ COMMITTED | Não (re-read pode ver writes commitados) | Não | NÃO |
441
- | REPEATABLE READ | Sim (snapshot do start da trx) | **Não** (apenas rows do snapshot) | **NÃO** — INSERT cross-trx ainda invalida count |
442
- | SERIALIZABLE (SSI) | Sim | **Sim** (rastreia predicate locks) | **SIM** |
443
-
444
- **Por quê REPEATABLE READ NÃO previne phantom:**
445
-
446
- REPEATABLE READ via MVCC garante: se T1 leu rows X e Y no momento `t`, re-ler depois mostra os mesmos X e Y. MAS — se T2 commitou um INSERT de Z (que cai no predicate `where price > 100`) entre as duas leituras de T1, o snapshot de T1 ainda não contém Z (porque snapshot é tirado no `begin`). Então `select * from p where price > 100` retorna o mesmo conjunto. Aparentemente OK.
447
-
448
- PROBLEMA: se T1 usa o count para tomar decisão (ex: "se não houver booking conflitante, faço meu booking"), o predicate continua válido para T1, mas para T2 que commitou primeiro, o booking de T1 vai conflitar APÓS commit. Esse é o phantom no contexto de write skew — exemplo do booking de sala (caso 6 acima).
449
-
450
- **Postgres SSI resolve:**
451
-
452
- SSI (Serializable Snapshot Isolation, padrão desde Postgres 9.1) rastreia predicate locks: quando T1 faz `select where price > 100`, SSI registra que T1 "depende" do predicate. Se T2 faz `INSERT (price = 150)` cross-trx, SSI detecta que essa INSERT poderia mudar o resultado da query de T1 — e ao commit de uma das duas, aborta com `SQLSTATE 40001`.
453
-
454
- **Custo:** False positives (aborts mesmo quando não havia conflito real). Tradeoff aceito porque SSI é otimista (sem locks pessimistas) — performance superior a 2PL na média.
455
-
456
- **Quando NÃO usar SERIALIZABLE:**
457
-
458
- - Read-mostly workload com poucos updates → READ COMMITTED basta (Padrão 1 acima)
459
- - Update incremental tipo contador (sem cross-row decision) → CAS otimista com version (Padrão (b) ISOLAMENTO-03)
460
- - Booking declarativo → EXCLUDE constraint (Caminho 2 ISOLAMENTO-04)
461
-
462
- ---
463
-
464
- ## Anti-patterns
465
-
466
- ### Anti-pattern 1: Read-modify-write sob READ COMMITTED sem proteção
467
-
468
- **Errado:**
469
- ```sql
470
- -- Edge Function que incrementa contador
471
- begin;
472
- select sales_count from public.products where id = 1; -- 10
473
- -- App: novo = 10 + 1 = 11
474
- update public.products set sales_count = 11 where id = 1;
475
- commit;
476
- ```
477
-
478
- **Por quê:** Lost update clássico (race condition #4). Sob carga concorrente, vários incrementos perdidos.
479
-
480
- **Certo:** uma das 3 técnicas da REGRA #3:
481
- ```sql
482
- -- (b) Otimista: increment atomic via expressão SQL (sem read-modify-write app-side)
483
- update public.products set sales_count = sales_count + 1 where id = 1;
484
- ```
485
-
486
- ### Anti-pattern 2: Subir tudo para SERIALIZABLE "para garantir"
487
-
488
- **Errado:**
489
- ```sql
490
- -- postgresql.conf
491
- default_transaction_isolation = 'serializable'
492
- ```
493
-
494
- **Por quê:** SERIALIZABLE tem custo — aborts esporádicos (`SQLSTATE 40001`). Se app não tem retry loop universal, queries falham aleatoriamente em produção sob carga. E para 95% dos casos (CRUD single-row), READ COMMITTED basta (REGRA #2).
495
-
496
- **Certo:** READ COMMITTED por default; subir explicitamente apenas em transações com invariante cross-row complexa (`begin isolation level serializable;`).
497
-
498
- ### Anti-pattern 3: SELECT FOR UPDATE sem ORDER BY (deadlock)
499
-
500
- **Errado:**
501
- ```sql
502
- -- Sessão A
503
- select * from public.accounts where id in ($from_id, $to_id) for update;
504
-
505
- -- Sessão B (concorrente, transferência inversa)
506
- select * from public.accounts where id in ($to_id, $from_id) for update;
507
- ```
508
-
509
- **Por quê:** Postgres pode adquirir locks em ordem diferente em A vs B (depende da ordem física das rows). Resultado: deadlock detectado, uma trx morre com `SQLSTATE 40P01`.
510
-
511
- **Certo:** ordenar por chave primária para garantir ordem global de aquisição de lock:
512
- ```sql
513
- select * from public.accounts where id = least($from_id, $to_id) for update;
514
- select * from public.accounts where id = greatest($from_id, $to_id) for update;
515
- ```
516
-
517
- ### Anti-pattern 4: Solicitar READ UNCOMMITTED esperando dirty read
518
-
519
- **Errado:**
520
- ```sql
521
- begin isolation level read uncommitted;
522
- -- "Vou ler mais rápido sem MVCC overhead"
523
- ```
524
-
525
- **Por quê:** Postgres ignora silenciosamente — promove para READ COMMITTED (REGRA #1). Programador acha que está em modo "rápido" mas comportamento é idêntico a READ COMMITTED. Dead code visível mas inerte.
526
-
527
- **Certo:** se o objetivo é performance de leitura, usar `select` em transação separada de leituras escolhidas (sem `begin`/`commit`) — Postgres roda em modo single-statement com mesmo isolation default.
528
-
529
- ### Anti-pattern 5: Confiar em REPEATABLE READ para prevenir write skew
530
-
531
- **Errado:**
532
- ```sql
533
- begin isolation level repeatable read;
534
- select count(*) from public.doctors where on_call = true;
535
- -- if count >= 2: update doctors set on_call = false where name = 'Alice';
536
- commit;
537
- ```
538
-
539
- **Por quê:** REPEATABLE READ implementa snapshot isolation, mas snapshot isolation NÃO previne write skew (REGRA #4 + race condition #5). Doctor on-call cai para 0 sob concorrência.
540
-
541
- **Certo:** uma das 3 técnicas de REQ ISOLAMENTO-04 — FOR UPDATE em rows lidas, EXCLUDE constraint, ou SERIALIZABLE com retry.
542
-
543
- ## Ver também
544
-
545
- - [supabase-database-functions](../supabase-database-functions/SKILL.md) — STABLE/IMMUTABLE/VOLATILE markers (esta skill estende para isolation em transações multi-statement)
546
- - [crm-lead-pipeline-patterns](../crm-lead-pipeline-patterns/SKILL.md) — lead deduplication usa `unique(org_id, contact_email)` + `INSERT ... ON CONFLICT` (lost update prevention canônico)
547
- - [member-invite-flow](../member-invite-flow/SKILL.md) — accept invite usa `FOR UPDATE` para idempotência (Padrão (a) REQ ISOLAMENTO-03)
548
- - [supabase-migrations](../supabase-migrations/SKILL.md) — migration que adiciona `version bigint` para CAS otimista (Padrão (b))
549
- - [_shared-dados-distribuidos/glossary.md](../_shared-dados-distribuidos/glossary.md) seção (c) — definições canônicas PT-BR ↔ EN dos 6 race conditions, MVCC, SSI, predicate lock
550
- - [PostgreSQL Documentation — Transaction Isolation](https://www.postgresql.org/docs/current/transaction-iso.html) — fonte canônica oficial
551
- - [PostgreSQL Documentation — Concurrency Control / MVCC](https://www.postgresql.org/docs/current/mvcc.html) — implementação interna
552
- - DDIA Cap 7 (Kleppmann, O'Reilly 2017) — Transactions — race conditions canônicos, summary p. 257-258
1
+ ---
2
+ name: postgres-isolamento-concorrencia
3
+ description: Use ao escrever transação Postgres com risco de race condition…
4
+ ---
5
+
6
+ # Isolamento e Concorrência Postgres — 6 Race Conditions, Decision Tree, 3 Padrões para Lost Update
7
+
8
+ ## Quando usar
9
+
10
+ LLM carrega esta skill ao escrever ou revisar transação Postgres com risco de race condition concorrente. Trigger phrases:
11
+
12
+ - "transação Postgres concorrente", "race condition", "isolamento", "isolation level"
13
+ - "lost update", "write skew", "phantom read", "dirty read", "read skew"
14
+ - "SELECT FOR UPDATE", "advisory lock", "compare-and-swap", "version optimistic lock"
15
+ - "snapshot isolation", "SERIALIZABLE", "REPEATABLE READ", "READ COMMITTED"
16
+ - "MVCC", "SSI", "predicate lock", "exclusion constraint"
17
+ - "duplicate insert na concorrência", "contador errado", "saldo negativo"
18
+ - "duas transações alterando a mesma row"
19
+
20
+ Esta skill **estende** [`supabase-database-functions`](../supabase-database-functions/SKILL.md) (v1.8) — herda STABLE/IMMUTABLE/VOLATILE markers e adiciona escolha de isolation level + padrões para lost update/write skew/phantom em transações multi-statement.
21
+
22
+ Termos canônicos preservados em EN porque são padrão internacional do manual oficial Postgres ([transaction-iso.html](https://www.postgresql.org/docs/current/transaction-iso.html)) e do livro DDIA Ch 7. Definições PT-BR ↔ EN no glossário [`_shared-dados-distribuidos/glossary.md`](../_shared-dados-distribuidos/glossary.md) seção (c).
23
+
24
+ ## Regras absolutas
25
+
26
+ **REGRA #1 (Postgres NÃO permite dirty read):** Mesmo se a aplicação solicitar `READ UNCOMMITTED`, o Postgres silenciosamente promove para `READ COMMITTED`. Citação: manual oficial — *"In PostgreSQL, you can request any of the four standard transaction isolation levels, but internally only three distinct isolation levels are implemented, namely PostgreSQL's Read Uncommitted mode behaves like Read Committed."*
27
+
28
+ **REGRA #2 (default = READ COMMITTED é OK para 95% dos casos):** Se a transação faz UPDATE ou INSERT em uma única tabela com `WHERE id = $1`, READ COMMITTED basta — Postgres garante atomicidade no nível da row. Subir para REPEATABLE READ ou SERIALIZABLE custa abort rate (`serialization_failure`) e não traz benefício.
29
+
30
+ **REGRA #3 (lost update tem 3 padrões — escolher por workload):** Sob READ COMMITTED, dois `UPDATE` concorrentes baseados em valor lido podem perder uma das mudanças (read-modify-write não-atômico). Resolver via uma de 3 técnicas — pessimista (`FOR UPDATE`), otimista (CAS via `WHERE version = $v`) ou semântica (`pg_advisory_xact_lock`). Veja Padrão 3 abaixo para escolher por workload.
31
+
32
+ **REGRA #4 (write skew exige uma de 3 técnicas — não OR genérico):** Snapshot isolation (REPEATABLE READ Postgres) NÃO previne write skew. Resolver com uma de 3 técnicas — materializar conflito via `FOR UPDATE` em rows lidas, declarar conflito via `EXCLUDE USING gist` constraint, ou subir para `SERIALIZABLE` (Postgres SSI = predicate-aware) como fallback genérico.
33
+
34
+ **REGRA #5 (REPEATABLE READ NÃO previne phantom em Postgres):** Apenas `SERIALIZABLE` (que usa SSI desde Postgres 9.1) previne phantom porque é predicate-aware. REPEATABLE READ implementa snapshot isolation = imagem consistente do start da trx, mas INSERT cross-trx que cai no predicate ainda altera resultado em re-query no mesmo snapshot.
35
+
36
+ **REGRA #6 (SERIALIZABLE = aborts esporádicos — app precisa retry):** SSI é otimista — transações executam sem bloquear; ao commit verifica serializabilidade e aborta com `SQLSTATE 40001` se não. App **DEVE** ter retry loop com backoff (recomendado: até 3 tentativas, exponential backoff a partir de 50ms).
37
+
38
+ ## Patterns canônicos
39
+
40
+ ### REQ ISOLAMENTO-01 — Os 6 race conditions com SQL exemplo isolado
41
+
42
+ #### 1. Dirty read
43
+
44
+ Definição: T1 lê dados não-commitados de T2; depois T2 rollback.
45
+
46
+ **Postgres NÃO permite (REGRA #1):**
47
+
48
+ ```sql
49
+ -- Sessão A
50
+ begin isolation level read uncommitted; -- silenciosamente vira READ COMMITTED
51
+ update public.accounts set balance = balance - 100 where id = 1;
52
+ -- (sem commit)
53
+
54
+ -- Sessão B
55
+ begin;
56
+ select balance from public.accounts where id = 1;
57
+ -- Retorna o saldo ANTES do update da Sessão A — SEM dirty read.
58
+ -- Isso é por design Postgres; nenhum nível de isolamento permite ver writes não-commitados.
59
+ ```
60
+
61
+ #### 2. Dirty write
62
+
63
+ Definição: T1 sobrescreve write não-commitado de T2.
64
+
65
+ **Postgres previne via row-level lock implícito em UPDATE:**
66
+
67
+ ```sql
68
+ -- Sessão A
69
+ begin;
70
+ update public.accounts set balance = 200 where id = 1; -- adquire ROW EXCLUSIVE lock
71
+ -- (sem commit ainda)
72
+
73
+ -- Sessão B
74
+ begin;
75
+ update public.accounts set balance = 300 where id = 1;
76
+ -- Sessão B BLOQUEIA aqui até A commitar ou rollback.
77
+ -- Quando A commita, B procede — sem dirty write.
78
+ ```
79
+
80
+ #### 3. Read skew (non-repeatable read)
81
+
82
+ Definição: T1 lê row → T2 commita update na mesma row → T1 re-lê e vê valor diferente.
83
+
84
+ **Acontece sob READ COMMITTED; prevenido por REPEATABLE READ (snapshot isolation):**
85
+
86
+ ```sql
87
+ -- Sessão A — READ COMMITTED (default)
88
+ begin;
89
+ select balance from public.accounts where id = 1; -- 100
90
+
91
+ -- Sessão B (entre as queries)
92
+ begin;
93
+ update public.accounts set balance = 500 where id = 1;
94
+ commit;
95
+
96
+ -- Sessão A continua
97
+ select balance from public.accounts where id = 1; -- 500 (DIFERENTE — read skew)
98
+ commit;
99
+
100
+ -- Solução: subir A para REPEATABLE READ
101
+ begin isolation level repeatable read;
102
+ select balance from public.accounts where id = 1; -- 100
103
+ -- (B faz update e commit no meio)
104
+ select balance from public.accounts where id = 1; -- 100 (mesmo snapshot)
105
+ commit;
106
+ ```
107
+
108
+ #### 4. Lost update
109
+
110
+ Definição: T1 e T2 fazem read-modify-write concorrentes; uma sobrescreve a outra sem incorporar mudanças.
111
+
112
+ **Cenário clássico — contador de vendas:**
113
+
114
+ ```sql
115
+ -- Sessão A
116
+ begin;
117
+ select sales_count from public.products where id = 1; -- 10
118
+ -- App computa: 10 + 1 = 11
119
+ update public.products set sales_count = 11 where id = 1;
120
+ commit;
121
+
122
+ -- Sessão B (concorrente)
123
+ begin;
124
+ select sales_count from public.products where id = 1; -- 10 (mesmo snapshot)
125
+ -- App computa: 10 + 1 = 11
126
+ update public.products set sales_count = 11 where id = 1;
127
+ commit;
128
+
129
+ -- Resultado: sales_count = 11 (DEVERIA SER 12 — uma venda perdida)
130
+ ```
131
+
132
+ Solução: ver REQ ISOLAMENTO-03 (3 padrões abaixo).
133
+
134
+ #### 5. Write skew
135
+
136
+ Definição: T1 e T2 leem o mesmo predicate, decidem com base nele, escrevem coisas diferentes que invalidam o predicate.
137
+
138
+ **Cenário canônico DDIA — doctor on-call (precisa ≥ 1 doctor on-call):**
139
+
140
+ ```sql
141
+ -- Schema
142
+ create table public.doctors (
143
+ id bigserial primary key,
144
+ name text not null,
145
+ on_call boolean not null
146
+ );
147
+ insert into public.doctors (name, on_call) values
148
+ ('Alice', true),
149
+ ('Bob', true);
150
+
151
+ -- Sessão A — Alice quer sair de plantão
152
+ begin isolation level repeatable read;
153
+ -- Alice lê: 2 doctors on-call → invariante "≥ 1" mantido se eu sair → posso sair
154
+ select count(*) from public.doctors where on_call = true; -- 2
155
+
156
+ -- Sessão B (concorrente) — Bob quer sair de plantão
157
+ begin isolation level repeatable read;
158
+ -- Bob lê: 2 doctors on-call → invariante "≥ 1" mantido se eu sair → posso sair
159
+ select count(*) from public.doctors where on_call = true; -- 2 (mesmo snapshot)
160
+
161
+ -- Sessão A
162
+ update public.doctors set on_call = false where name = 'Alice';
163
+ commit;
164
+
165
+ -- Sessão B (não vê o write de A — snapshot isolation)
166
+ update public.doctors set on_call = false where name = 'Bob';
167
+ commit;
168
+
169
+ -- Resultado: 0 doctors on-call (INVARIANTE QUEBRADA — write skew)
170
+ ```
171
+
172
+ Solução: ver REQ ISOLAMENTO-04 (3 caminhos).
173
+
174
+ #### 6. Phantom read
175
+
176
+ Definição: T1 query com WHERE → T2 INSERT row matching WHERE → T1 re-query vê novo row.
177
+
178
+ **Acontece sob READ COMMITTED e até REPEATABLE READ no manual SQL padrão; prevenido por SERIALIZABLE em Postgres:**
179
+
180
+ ```sql
181
+ -- Sessão A — booking de sala
182
+ begin isolation level repeatable read;
183
+ select count(*) from public.bookings
184
+ where room_id = 'sala_a'
185
+ and tstzrange(starts_at, ends_at, '[)') && tstzrange('2026-06-01 09:00', '2026-06-01 10:00', '[)');
186
+ -- 0 conflitos → posso reservar
187
+
188
+ -- Sessão B (concorrente)
189
+ begin isolation level repeatable read;
190
+ select count(*) from public.bookings
191
+ where room_id = 'sala_a'
192
+ and tstzrange(starts_at, ends_at, '[)') && tstzrange('2026-06-01 09:00', '2026-06-01 10:00', '[)');
193
+ -- 0 conflitos → posso reservar (mesmo snapshot)
194
+
195
+ insert into public.bookings (room_id, starts_at, ends_at) values ('sala_a', '2026-06-01 09:00', '2026-06-01 10:00');
196
+ commit;
197
+
198
+ -- Sessão A
199
+ insert into public.bookings (room_id, starts_at, ends_at) values ('sala_a', '2026-06-01 09:00', '2026-06-01 10:00');
200
+ commit;
201
+
202
+ -- Resultado: 2 bookings overlap (PHANTOM — REPEATABLE READ não detecta INSERT cross-trx)
203
+ ```
204
+
205
+ Solução: SERIALIZABLE (REGRA #5) ou `EXCLUDE USING gist` constraint (ver REQ ISOLAMENTO-04).
206
+
207
+ ---
208
+
209
+ ### REQ ISOLAMENTO-02 — Árvore de decisão isolation level
210
+
211
+ ```
212
+ Qual transação você está escrevendo?
213
+
214
+ ├─ CRUD simples: INSERT / UPDATE / DELETE em UMA tabela com WHERE id = $1
215
+ │ └─► READ COMMITTED (default)
216
+ │ • Postgres garante atomicidade na row (REGRA #2)
217
+ │ • Sem aborts esporádicos
218
+ │ • 95% dos casos em Edge Functions Supabase
219
+
220
+ ├─ Relatório / query longa em MÚLTIPLAS tabelas, snapshot consistente importa
221
+ │ └─► REPEATABLE READ (snapshot isolation via MVCC)
222
+ │ • set transaction isolation level repeatable read;
223
+ │ • Garante que todas as queries da trx veem a mesma "foto" do DB
224
+ │ • Útil para: dashboards, exports CSV, agregações com JOINs
225
+ │ • NÃO previne lost update / write skew / phantom
226
+
227
+ └─ Invariante complexa cross-row: doctor on-call, booking sem overlap, saldo ≥ 0
228
+ └─► SERIALIZABLE (Postgres SSI — predicate-aware desde 9.1)
229
+ • set transaction isolation level serializable;
230
+ • Detecta write skew, phantom, lost update genérico
231
+ • Custo: aborts esporádicos com SQLSTATE 40001 — app DEVE ter retry (REGRA #6)
232
+ • Quando outras técnicas (FOR UPDATE / EXCLUDE constraint) não cabem
233
+ ```
234
+
235
+ **Setando isolation level — 3 formas:**
236
+
237
+ ```sql
238
+ -- (a) Por transação — recomendado (escopo claro)
239
+ begin isolation level serializable;
240
+ -- ... queries ...
241
+ commit;
242
+
243
+ -- (b) Por sessão (afeta TODAS as transações da sessão até reset)
244
+ set session characteristics as transaction isolation level serializable;
245
+
246
+ -- (c) Por sistema (postgresql.conf — NÃO recomendado)
247
+ -- default_transaction_isolation = 'serializable'
248
+ ```
249
+
250
+ ---
251
+
252
+ ### REQ ISOLAMENTO-03 — 3 padrões para prevenir lost update
253
+
254
+ | Padrão | Sintaxe | Tradeoff | Quando usar |
255
+ |---|---|---|---|
256
+ | **(a) Pessimista — `SELECT ... FOR UPDATE`** | `select balance from accounts where id = $1 for update;` | Bloqueia leitores concorrentes; baixo throughput se contention alta | Writes raros + contention baixa (ex: settlement bancário 1×/dia) |
257
+ | **(b) Otimista — CAS via `WHERE version`** | `update accounts set balance = $1, version = version + 1 where id = $2 and version = $v;` | Sem bloqueio; precisa retry on miss (return 0 rows) | Writes frequentes + contention baixa-média (ex: contador de likes) |
258
+ | **(c) Semântico — `pg_advisory_xact_lock`** | `select pg_advisory_xact_lock(hashtext('lock_name'));` | Lock por nome lógico, não amarra a row; libera no commit/rollback | Lock global / singleton operation (ex: cron job que NÃO deve rodar 2× concorrente) |
259
+
260
+ #### Padrão (a) — SELECT FOR UPDATE pessimista
261
+
262
+ ```sql
263
+ -- Transferência bancária — débito + crédito atômico
264
+ begin;
265
+ -- Lock em ambas as rows ANTES de ler (ordering por ID previne deadlock)
266
+ select balance from public.accounts where id = least($from_id, $to_id) for update;
267
+ select balance from public.accounts where id = greatest($from_id, $to_id) for update;
268
+
269
+ -- Validação no app
270
+ -- if balance_from < amount: rollback
271
+
272
+ update public.accounts set balance = balance - $amount where id = $from_id;
273
+ update public.accounts set balance = balance + $amount where id = $to_id;
274
+ commit;
275
+ ```
276
+
277
+ #### Padrão (b) — CAS otimista com version column
278
+
279
+ ```sql
280
+ -- Schema
281
+ alter table public.products add column if not exists version bigint not null default 0;
282
+
283
+ -- App — retry loop
284
+ -- 1. Read
285
+ select sales_count, version from public.products where id = $1;
286
+ -- (assume: sales_count = 10, version = 7)
287
+
288
+ -- 2. App computa: sales_count + 1 = 11
289
+
290
+ -- 3. Atomic compare-and-swap
291
+ update public.products
292
+ set sales_count = 11,
293
+ version = version + 1
294
+ where id = $1
295
+ and version = 7;
296
+ -- if rowcount = 0: outra trx mudou — retry desde o read
297
+
298
+ -- 4. Commit
299
+ commit;
300
+ ```
301
+
302
+ #### Padrão (c) — Advisory lock semântico
303
+
304
+ ```sql
305
+ -- Cron job que sincroniza dados externos — NÃO pode rodar 2× concorrente
306
+ begin;
307
+ -- Lock por nome lógico (NÃO amarra row específica)
308
+ select pg_advisory_xact_lock(hashtext('sync_external_api'));
309
+
310
+ -- Se outra instância do cron já tem o lock, esta trx aguarda.
311
+ -- Como é xact_lock, libera automaticamente no commit/rollback.
312
+
313
+ -- ... lógica de sync ...
314
+ commit;
315
+ ```
316
+
317
+ **Regra de polegar para escolher:**
318
+ - Lock global, single-row ou multi-row arbitrário → **(c) advisory_xact_lock**
319
+ - Lock em rows específicas conhecidas a priori, contention baixa → **(a) FOR UPDATE**
320
+ - Update incremental sem dependência de outras rows, contention média-alta → **(b) CAS otimista**
321
+
322
+ ---
323
+
324
+ ### REQ ISOLAMENTO-04 — Prevenção write skew (3 caminhos)
325
+
326
+ #### Caminho 1 — Materializar conflito via FOR UPDATE em rows lidas
327
+
328
+ ```sql
329
+ -- Sessão A — Alice quer sair de plantão
330
+ begin;
331
+ -- Lock em TODAS as rows do predicate (materializa o conflito)
332
+ select count(*) from public.doctors where on_call = true for update; -- 2
333
+
334
+ -- Sessão B aguarda aqui (até A commitar)
335
+
336
+ update public.doctors set on_call = false where name = 'Alice';
337
+ commit;
338
+
339
+ -- Sessão B desbloqueia agora
340
+ select count(*) from public.doctors where on_call = true for update; -- 1 (não 2!)
341
+ -- Lógica do app verifica: count = 1, se eu sair vai para 0 → ABORT
342
+ rollback;
343
+ ```
344
+
345
+ Limitação: precisa enumerar TODAS as rows do predicate. Se o predicate é grande/aberto (ex: `where price > 100`), `FOR UPDATE` não locka rows futuras — apenas existentes.
346
+
347
+ #### Caminho 2 — Declarar conflito via EXCLUDE USING gist (ideal para overlap)
348
+
349
+ ```sql
350
+ -- Booking de sala SEM overlap (room_id + range temporal)
351
+ create extension if not exists btree_gist;
352
+
353
+ create table public.bookings (
354
+ id bigserial primary key,
355
+ room_id text not null,
356
+ starts_at timestamptz not null,
357
+ ends_at timestamptz not null,
358
+ -- Constraint declarativo: zero overlap por sala
359
+ exclude using gist (
360
+ room_id with =,
361
+ tstzrange(starts_at, ends_at, '[)') with &&
362
+ )
363
+ );
364
+
365
+ -- Sessão A
366
+ begin;
367
+ insert into public.bookings (room_id, starts_at, ends_at)
368
+ values ('sala_a', '2026-06-01 09:00', '2026-06-01 10:00');
369
+ commit;
370
+
371
+ -- Sessão B (concorrente)
372
+ begin;
373
+ insert into public.bookings (room_id, starts_at, ends_at)
374
+ values ('sala_a', '2026-06-01 09:00', '2026-06-01 10:00');
375
+ -- ERROR: conflicting key value violates exclusion constraint
376
+ -- (DB rejeita declarativamente — sem app logic)
377
+ ```
378
+
379
+ Aplicação canônica: room booking, schedule slot, version range, IP allocation.
380
+
381
+ #### Caminho 3 — SERIALIZABLE (Postgres SSI = predicate-aware) como fallback genérico
382
+
383
+ Quando enumerar rows é caro (predicate aberto) e EXCLUDE constraint não modela o conflito, subir para SERIALIZABLE:
384
+
385
+ ```sql
386
+ -- Doctor on-call — solução genérica
387
+ begin isolation level serializable;
388
+ select count(*) from public.doctors where on_call = true; -- 2
389
+
390
+ update public.doctors set on_call = false where name = 'Alice';
391
+
392
+ commit;
393
+ -- Se trx concorrente (Bob) tentou o mesmo: uma das duas aborta com
394
+ -- SQLSTATE 40001 (serialization_failure). App precisa retry (REGRA #6).
395
+ ```
396
+
397
+ App retry loop canônico:
398
+
399
+ ```typescript
400
+ // Edge Function — Deno
401
+ import { Pool } from "npm:pg@8";
402
+
403
+ async function withSerializableRetry<T>(
404
+ pool: Pool,
405
+ fn: (client: PoolClient) => Promise<T>,
406
+ maxRetries = 3,
407
+ ): Promise<T> {
408
+ for (let attempt = 0; attempt < maxRetries; attempt++) {
409
+ const client = await pool.connect();
410
+ try {
411
+ await client.query("begin isolation level serializable");
412
+ const result = await fn(client);
413
+ await client.query("commit");
414
+ return result;
415
+ } catch (err: unknown) {
416
+ await client.query("rollback");
417
+ // SQLSTATE 40001 = serialization_failure (retryable)
418
+ if ((err as { code?: string }).code === "40001" && attempt < maxRetries - 1) {
419
+ // Backoff exponencial: 50ms, 100ms, 200ms
420
+ await new Promise((r) => setTimeout(r, 50 * 2 ** attempt));
421
+ continue;
422
+ }
423
+ throw err;
424
+ } finally {
425
+ client.release();
426
+ }
427
+ }
428
+ throw new Error("Max retries excedido — contention demais ou bug");
429
+ }
430
+ ```
431
+
432
+ ---
433
+
434
+ ### REQ ISOLAMENTO-05 — Prevenção phantom read
435
+
436
+ **Contraste canônico:**
437
+
438
+ | Isolation level | Snapshot isolation | Predicate-aware | Previne phantom? |
439
+ |---|---|---|---|
440
+ | READ COMMITTED | Não (re-read pode ver writes commitados) | Não | NÃO |
441
+ | REPEATABLE READ | Sim (snapshot do start da trx) | **Não** (apenas rows do snapshot) | **NÃO** — INSERT cross-trx ainda invalida count |
442
+ | SERIALIZABLE (SSI) | Sim | **Sim** (rastreia predicate locks) | **SIM** |
443
+
444
+ **Por quê REPEATABLE READ NÃO previne phantom:**
445
+
446
+ REPEATABLE READ via MVCC garante: se T1 leu rows X e Y no momento `t`, re-ler depois mostra os mesmos X e Y. MAS — se T2 commitou um INSERT de Z (que cai no predicate `where price > 100`) entre as duas leituras de T1, o snapshot de T1 ainda não contém Z (porque snapshot é tirado no `begin`). Então `select * from p where price > 100` retorna o mesmo conjunto. Aparentemente OK.
447
+
448
+ PROBLEMA: se T1 usa o count para tomar decisão (ex: "se não houver booking conflitante, faço meu booking"), o predicate continua válido para T1, mas para T2 que commitou primeiro, o booking de T1 vai conflitar APÓS commit. Esse é o phantom no contexto de write skew — exemplo do booking de sala (caso 6 acima).
449
+
450
+ **Postgres SSI resolve:**
451
+
452
+ SSI (Serializable Snapshot Isolation, padrão desde Postgres 9.1) rastreia predicate locks: quando T1 faz `select where price > 100`, SSI registra que T1 "depende" do predicate. Se T2 faz `INSERT (price = 150)` cross-trx, SSI detecta que essa INSERT poderia mudar o resultado da query de T1 — e ao commit de uma das duas, aborta com `SQLSTATE 40001`.
453
+
454
+ **Custo:** False positives (aborts mesmo quando não havia conflito real). Tradeoff aceito porque SSI é otimista (sem locks pessimistas) — performance superior a 2PL na média.
455
+
456
+ **Quando NÃO usar SERIALIZABLE:**
457
+
458
+ - Read-mostly workload com poucos updates → READ COMMITTED basta (Padrão 1 acima)
459
+ - Update incremental tipo contador (sem cross-row decision) → CAS otimista com version (Padrão (b) ISOLAMENTO-03)
460
+ - Booking declarativo → EXCLUDE constraint (Caminho 2 ISOLAMENTO-04)
461
+
462
+ ---
463
+
464
+ ## Anti-patterns
465
+
466
+ ### Anti-pattern 1: Read-modify-write sob READ COMMITTED sem proteção
467
+
468
+ **Errado:**
469
+ ```sql
470
+ -- Edge Function que incrementa contador
471
+ begin;
472
+ select sales_count from public.products where id = 1; -- 10
473
+ -- App: novo = 10 + 1 = 11
474
+ update public.products set sales_count = 11 where id = 1;
475
+ commit;
476
+ ```
477
+
478
+ **Por quê:** Lost update clássico (race condition #4). Sob carga concorrente, vários incrementos perdidos.
479
+
480
+ **Certo:** uma das 3 técnicas da REGRA #3:
481
+ ```sql
482
+ -- (b) Otimista: increment atomic via expressão SQL (sem read-modify-write app-side)
483
+ update public.products set sales_count = sales_count + 1 where id = 1;
484
+ ```
485
+
486
+ ### Anti-pattern 2: Subir tudo para SERIALIZABLE "para garantir"
487
+
488
+ **Errado:**
489
+ ```sql
490
+ -- postgresql.conf
491
+ default_transaction_isolation = 'serializable'
492
+ ```
493
+
494
+ **Por quê:** SERIALIZABLE tem custo — aborts esporádicos (`SQLSTATE 40001`). Se app não tem retry loop universal, queries falham aleatoriamente em produção sob carga. E para 95% dos casos (CRUD single-row), READ COMMITTED basta (REGRA #2).
495
+
496
+ **Certo:** READ COMMITTED por default; subir explicitamente apenas em transações com invariante cross-row complexa (`begin isolation level serializable;`).
497
+
498
+ ### Anti-pattern 3: SELECT FOR UPDATE sem ORDER BY (deadlock)
499
+
500
+ **Errado:**
501
+ ```sql
502
+ -- Sessão A
503
+ select * from public.accounts where id in ($from_id, $to_id) for update;
504
+
505
+ -- Sessão B (concorrente, transferência inversa)
506
+ select * from public.accounts where id in ($to_id, $from_id) for update;
507
+ ```
508
+
509
+ **Por quê:** Postgres pode adquirir locks em ordem diferente em A vs B (depende da ordem física das rows). Resultado: deadlock detectado, uma trx morre com `SQLSTATE 40P01`.
510
+
511
+ **Certo:** ordenar por chave primária para garantir ordem global de aquisição de lock:
512
+ ```sql
513
+ select * from public.accounts where id = least($from_id, $to_id) for update;
514
+ select * from public.accounts where id = greatest($from_id, $to_id) for update;
515
+ ```
516
+
517
+ ### Anti-pattern 4: Solicitar READ UNCOMMITTED esperando dirty read
518
+
519
+ **Errado:**
520
+ ```sql
521
+ begin isolation level read uncommitted;
522
+ -- "Vou ler mais rápido sem MVCC overhead"
523
+ ```
524
+
525
+ **Por quê:** Postgres ignora silenciosamente — promove para READ COMMITTED (REGRA #1). Programador acha que está em modo "rápido" mas comportamento é idêntico a READ COMMITTED. Dead code visível mas inerte.
526
+
527
+ **Certo:** se o objetivo é performance de leitura, usar `select` em transação separada de leituras escolhidas (sem `begin`/`commit`) — Postgres roda em modo single-statement com mesmo isolation default.
528
+
529
+ ### Anti-pattern 5: Confiar em REPEATABLE READ para prevenir write skew
530
+
531
+ **Errado:**
532
+ ```sql
533
+ begin isolation level repeatable read;
534
+ select count(*) from public.doctors where on_call = true;
535
+ -- if count >= 2: update doctors set on_call = false where name = 'Alice';
536
+ commit;
537
+ ```
538
+
539
+ **Por quê:** REPEATABLE READ implementa snapshot isolation, mas snapshot isolation NÃO previne write skew (REGRA #4 + race condition #5). Doctor on-call cai para 0 sob concorrência.
540
+
541
+ **Certo:** uma das 3 técnicas de REQ ISOLAMENTO-04 — FOR UPDATE em rows lidas, EXCLUDE constraint, ou SERIALIZABLE com retry.
542
+
543
+ ## Ver também
544
+
545
+ - [supabase-database-functions](../supabase-database-functions/SKILL.md) — STABLE/IMMUTABLE/VOLATILE markers (esta skill estende para isolation em transações multi-statement)
546
+ - [crm-lead-pipeline-patterns](../crm-lead-pipeline-patterns/SKILL.md) — lead deduplication usa `unique(org_id, contact_email)` + `INSERT ... ON CONFLICT` (lost update prevention canônico)
547
+ - [member-invite-flow](../member-invite-flow/SKILL.md) — accept invite usa `FOR UPDATE` para idempotência (Padrão (a) REQ ISOLAMENTO-03)
548
+ - [supabase-migrations](../supabase-migrations/SKILL.md) — migration que adiciona `version bigint` para CAS otimista (Padrão (b))
549
+ - [_shared-dados-distribuidos/glossary.md](../_shared-dados-distribuidos/glossary.md) seção (c) — definições canônicas PT-BR ↔ EN dos 6 race conditions, MVCC, SSI, predicate lock
550
+ - [PostgreSQL Documentation — Transaction Isolation](https://www.postgresql.org/docs/current/transaction-iso.html) — fonte canônica oficial
551
+ - [PostgreSQL Documentation — Concurrency Control / MVCC](https://www.postgresql.org/docs/current/mvcc.html) — implementação interna
552
+ - DDIA Cap 7 (Kleppmann, O'Reilly 2017) — Transactions — race conditions canônicos, summary p. 257-258