@luanpdd/kit-mcp 1.29.0 → 1.30.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 (330) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +168 -168
  3. package/gates/agent-no-recursive-dispatch.md +82 -82
  4. package/kit/COMANDOS.md +138 -138
  5. package/kit/README.md +76 -76
  6. package/kit/agents/advisor-researcher.md +106 -106
  7. package/kit/agents/assumptions-analyzer.md +107 -107
  8. package/kit/agents/audit-log-implementer.md +313 -313
  9. package/kit/agents/auditor-consistencia-isolamento.md +413 -413
  10. package/kit/agents/b2b-saas-architect.md +156 -156
  11. package/kit/agents/cascading-failures-auditor.md +298 -298
  12. package/kit/agents/codebase-mapper.md +768 -768
  13. package/kit/agents/crm-pipeline-implementer.md +256 -256
  14. package/kit/agents/debugger.md +813 -813
  15. package/kit/agents/detector-tenant-quente.md +337 -337
  16. package/kit/agents/evolution-go-integrator.md +200 -200
  17. package/kit/agents/example-reviewer.md +21 -21
  18. package/kit/agents/executor.md +564 -564
  19. package/kit/agents/integration-checker.md +200 -200
  20. package/kit/agents/invite-flow-implementer.md +189 -189
  21. package/kit/agents/legacy-characterizer.md +368 -368
  22. package/kit/agents/lgpd-compliance-auditor.md +295 -295
  23. package/kit/agents/multi-tenant-isolation-auditor.md +253 -253
  24. package/kit/agents/multi-tenant-rls-writer.md +340 -340
  25. package/kit/agents/nyquist-auditor.md +178 -178
  26. package/kit/agents/observability-coverage-auditor.md +315 -315
  27. package/kit/agents/org-onboarding-implementer.md +223 -223
  28. package/kit/agents/payload-capture-instrumenter.md +273 -273
  29. package/kit/agents/phase-researcher.md +696 -696
  30. package/kit/agents/plan-checker.md +272 -272
  31. package/kit/agents/planner.md +922 -922
  32. package/kit/agents/project-researcher.md +652 -652
  33. package/kit/agents/refactor-safety-auditor.md +404 -404
  34. package/kit/agents/research-synthesizer.md +245 -245
  35. package/kit/agents/roadmapper.md +677 -677
  36. package/kit/agents/seam-finder.md +359 -359
  37. package/kit/agents/shotgun-surgery-detector.md +349 -349
  38. package/kit/agents/supabase-branching-architect.md +562 -562
  39. package/kit/agents/supabase-cicd-pipeline-implementer.md +777 -777
  40. package/kit/agents/supabase-column-privileges-writer.md +399 -399
  41. package/kit/agents/supabase-edge-fn-tester.md +287 -0
  42. package/kit/agents/supabase-edge-fn-writer.md +239 -210
  43. package/kit/agents/supabase-migration-writer.md +385 -385
  44. package/kit/agents/supabase-rbac-implementer.md +392 -392
  45. package/kit/agents/supabase-realtime-implementer.md +363 -267
  46. package/kit/agents/supabase-rls-hardener.md +521 -521
  47. package/kit/agents/supabase-rls-writer.md +323 -323
  48. package/kit/agents/supabase-roles-implementer.md +355 -355
  49. package/kit/agents/super-admin-implementer.md +281 -281
  50. package/kit/agents/ui-auditor.md +437 -437
  51. package/kit/agents/ui-checker.md +302 -302
  52. package/kit/agents/ui-researcher.md +355 -355
  53. package/kit/agents/user-profiler.md +175 -175
  54. package/kit/agents/validador-evolucao-schema.md +335 -335
  55. package/kit/agents/verifier.md +728 -728
  56. package/kit/commands/adicionar-backlog.md +75 -75
  57. package/kit/commands/adicionar-fase.md +42 -42
  58. package/kit/commands/adicionar-tarefa.md +45 -45
  59. package/kit/commands/adicionar-testes.md +41 -41
  60. package/kit/commands/ajuda.md +21 -21
  61. package/kit/commands/atualizar.md +37 -37
  62. package/kit/commands/auditar-cascading.md +111 -111
  63. package/kit/commands/auditar-marco.md +179 -179
  64. package/kit/commands/auditar-observabilidade-cobertura.md +183 -183
  65. package/kit/commands/auditar-refactor.md +219 -219
  66. package/kit/commands/auditar-release.md +109 -109
  67. package/kit/commands/auditar-uat.md +23 -23
  68. package/kit/commands/autonomo.md +40 -40
  69. package/kit/commands/branch-pr.md +24 -24
  70. package/kit/commands/burn-rate-status.md +408 -408
  71. package/kit/commands/capturar-payloads.md +193 -193
  72. package/kit/commands/caracterizar.md +212 -212
  73. package/kit/commands/concluir-marco.md +247 -247
  74. package/kit/commands/configuracoes.md +36 -36
  75. package/kit/commands/dados-distribuidos.md +188 -188
  76. package/kit/commands/definir-perfil.md +10 -10
  77. package/kit/commands/depurar.md +190 -190
  78. package/kit/commands/detectar-duplicacao.md +197 -197
  79. package/kit/commands/discutir-fase.md +131 -131
  80. package/kit/commands/encontrar-seams.md +136 -136
  81. package/kit/commands/entrar-discord.md +17 -17
  82. package/kit/commands/estatisticas.md +18 -18
  83. package/kit/commands/example-greeting.md +33 -33
  84. package/kit/commands/executar-fase.md +58 -58
  85. package/kit/commands/expresso.md +56 -56
  86. package/kit/commands/fase-ui.md +34 -34
  87. package/kit/commands/fazer.md +57 -57
  88. package/kit/commands/fio.md +125 -125
  89. package/kit/commands/fluxos-trabalho.md +64 -64
  90. package/kit/commands/forense.md +176 -176
  91. package/kit/commands/gerenciador.md +38 -38
  92. package/kit/commands/inserir-fase.md +31 -31
  93. package/kit/commands/legacy.md +263 -263
  94. package/kit/commands/limpeza.md +17 -17
  95. package/kit/commands/listar-hipoteses-fase.md +45 -45
  96. package/kit/commands/listar-workspaces.md +18 -18
  97. package/kit/commands/load-shedding.md +117 -117
  98. package/kit/commands/mapear-codebase.md +70 -70
  99. package/kit/commands/multi-tenant.md +163 -163
  100. package/kit/commands/nota.md +33 -33
  101. package/kit/commands/novo-marco.md +43 -43
  102. package/kit/commands/novo-projeto.md +41 -41
  103. package/kit/commands/novo-workspace.md +43 -43
  104. package/kit/commands/pausar-trabalho.md +37 -37
  105. package/kit/commands/perfil-usuario.md +45 -45
  106. package/kit/commands/pesquisar-fase.md +195 -195
  107. package/kit/commands/planejar-fase.md +67 -67
  108. package/kit/commands/planejar-lacunas.md +33 -33
  109. package/kit/commands/plantar-ideia.md +25 -25
  110. package/kit/commands/progresso.md +24 -24
  111. package/kit/commands/proximo.md +30 -30
  112. package/kit/commands/publicar.md +490 -490
  113. package/kit/commands/rapido.md +35 -35
  114. package/kit/commands/reaplicar-patches.md +124 -124
  115. package/kit/commands/refactor-seguro.md +321 -321
  116. package/kit/commands/relatorio-sessao.md +19 -19
  117. package/kit/commands/remover-fase.md +31 -31
  118. package/kit/commands/remover-workspace.md +26 -26
  119. package/kit/commands/resumo-marco.md +50 -50
  120. package/kit/commands/retomar-trabalho.md +40 -40
  121. package/kit/commands/revisar-backlog.md +60 -60
  122. package/kit/commands/revisar-ui.md +32 -32
  123. package/kit/commands/revisar.md +37 -37
  124. package/kit/commands/saude.md +21 -21
  125. package/kit/commands/setup-notion.md +93 -93
  126. package/kit/commands/storytelling.md +179 -179
  127. package/kit/commands/supabase.md +30 -7
  128. package/kit/commands/sync-main.md +68 -68
  129. package/kit/commands/validar-fase.md +35 -35
  130. package/kit/commands/verificar-tarefas.md +44 -44
  131. package/kit/commands/verificar-trabalho.md +64 -64
  132. package/kit/file-manifest.json +14 -8
  133. package/kit/framework/bin/lib/commands.cjs +959 -959
  134. package/kit/framework/bin/lib/config.cjs +442 -442
  135. package/kit/framework/bin/lib/core.cjs +1230 -1230
  136. package/kit/framework/bin/lib/frontmatter.cjs +336 -336
  137. package/kit/framework/bin/lib/init.cjs +1442 -1442
  138. package/kit/framework/bin/lib/milestone.cjs +252 -252
  139. package/kit/framework/bin/lib/model-profiles.cjs +68 -68
  140. package/kit/framework/bin/lib/phase.cjs +888 -888
  141. package/kit/framework/bin/lib/profile-output.cjs +952 -952
  142. package/kit/framework/bin/lib/profile-pipeline.cjs +539 -539
  143. package/kit/framework/bin/lib/roadmap.cjs +329 -329
  144. package/kit/framework/bin/lib/security.cjs +382 -382
  145. package/kit/framework/bin/lib/state.cjs +1031 -1031
  146. package/kit/framework/bin/lib/template.cjs +222 -222
  147. package/kit/framework/bin/lib/uat.cjs +282 -282
  148. package/kit/framework/bin/lib/verify.cjs +888 -888
  149. package/kit/framework/bin/lib/workstream.cjs +491 -491
  150. package/kit/framework/bin/tools.cjs +918 -918
  151. package/kit/framework/commands/workstreams.md +63 -63
  152. package/kit/framework/references/checkpoints.md +778 -778
  153. package/kit/framework/references/continuation-format.md +249 -249
  154. package/kit/framework/references/decimal-phase-calculation.md +64 -64
  155. package/kit/framework/references/git-integration.md +295 -295
  156. package/kit/framework/references/git-planning-commit.md +38 -38
  157. package/kit/framework/references/model-profile-resolution.md +36 -36
  158. package/kit/framework/references/model-profiles.md +139 -139
  159. package/kit/framework/references/phase-argument-parsing.md +61 -61
  160. package/kit/framework/references/planning-config.md +202 -202
  161. package/kit/framework/references/questioning.md +162 -162
  162. package/kit/framework/references/tdd.md +263 -263
  163. package/kit/framework/references/ui-brand.md +160 -160
  164. package/kit/framework/references/user-profiling.md +657 -657
  165. package/kit/framework/references/verification-patterns.md +612 -612
  166. package/kit/framework/references/workstream-flag.md +58 -58
  167. package/kit/framework/templates/DEBUG.md +164 -164
  168. package/kit/framework/templates/UAT.md +265 -265
  169. package/kit/framework/templates/UI-SPEC.md +100 -100
  170. package/kit/framework/templates/VALIDATION.md +76 -76
  171. package/kit/framework/templates/claude-md.md +122 -122
  172. package/kit/framework/templates/codebase/architecture.md +185 -185
  173. package/kit/framework/templates/codebase/concerns.md +205 -205
  174. package/kit/framework/templates/codebase/conventions.md +204 -204
  175. package/kit/framework/templates/codebase/integrations.md +192 -192
  176. package/kit/framework/templates/codebase/stack.md +158 -158
  177. package/kit/framework/templates/codebase/structure.md +199 -199
  178. package/kit/framework/templates/codebase/testing.md +301 -301
  179. package/kit/framework/templates/config.json +44 -44
  180. package/kit/framework/templates/context.md +352 -352
  181. package/kit/framework/templates/continue-here.md +78 -78
  182. package/kit/framework/templates/copilot-instructions.md +7 -7
  183. package/kit/framework/templates/debug-subagent-prompt.md +91 -91
  184. package/kit/framework/templates/dev-preferences.md +20 -20
  185. package/kit/framework/templates/discovery.md +146 -146
  186. package/kit/framework/templates/discussion-log.md +63 -63
  187. package/kit/framework/templates/milestone-archive.md +123 -123
  188. package/kit/framework/templates/milestone.md +115 -115
  189. package/kit/framework/templates/phase-prompt.md +610 -610
  190. package/kit/framework/templates/planner-subagent-prompt.md +117 -117
  191. package/kit/framework/templates/project.md +186 -186
  192. package/kit/framework/templates/requirements.md +231 -231
  193. package/kit/framework/templates/research-project/ARCHITECTURE.md +204 -204
  194. package/kit/framework/templates/research-project/FEATURES.md +147 -147
  195. package/kit/framework/templates/research-project/PITFALLS.md +200 -200
  196. package/kit/framework/templates/research-project/STACK.md +120 -120
  197. package/kit/framework/templates/research-project/SUMMARY.md +170 -170
  198. package/kit/framework/templates/research.md +419 -419
  199. package/kit/framework/templates/retrospective.md +54 -54
  200. package/kit/framework/templates/roadmap.md +202 -202
  201. package/kit/framework/templates/state.md +176 -176
  202. package/kit/framework/templates/summary-complex.md +59 -59
  203. package/kit/framework/templates/summary-minimal.md +41 -41
  204. package/kit/framework/templates/summary-standard.md +48 -48
  205. package/kit/framework/templates/summary.md +209 -209
  206. package/kit/framework/templates/user-profile.md +146 -146
  207. package/kit/framework/templates/user-setup.md +256 -256
  208. package/kit/framework/templates/verification-report.md +258 -258
  209. package/kit/framework/workflows/add-phase.md +112 -112
  210. package/kit/framework/workflows/add-tests.md +351 -351
  211. package/kit/framework/workflows/add-todo.md +158 -158
  212. package/kit/framework/workflows/audit-milestone.md +340 -340
  213. package/kit/framework/workflows/audit-uat.md +109 -109
  214. package/kit/framework/workflows/autonomous.md +891 -891
  215. package/kit/framework/workflows/check-todos.md +177 -177
  216. package/kit/framework/workflows/cleanup.md +152 -152
  217. package/kit/framework/workflows/complete-milestone.md +696 -696
  218. package/kit/framework/workflows/diagnose-issues.md +231 -231
  219. package/kit/framework/workflows/discovery-phase.md +289 -289
  220. package/kit/framework/workflows/discuss-phase-assumptions.md +653 -653
  221. package/kit/framework/workflows/discuss-phase.md +784 -784
  222. package/kit/framework/workflows/do.md +104 -104
  223. package/kit/framework/workflows/execute-phase.md +838 -838
  224. package/kit/framework/workflows/execute-plan.md +510 -510
  225. package/kit/framework/workflows/fast.md +102 -102
  226. package/kit/framework/workflows/forensics.md +265 -265
  227. package/kit/framework/workflows/health.md +181 -181
  228. package/kit/framework/workflows/help.md +619 -619
  229. package/kit/framework/workflows/insert-phase.md +130 -130
  230. package/kit/framework/workflows/list-phase-assumptions.md +178 -178
  231. package/kit/framework/workflows/list-workspaces.md +56 -56
  232. package/kit/framework/workflows/manager.md +362 -362
  233. package/kit/framework/workflows/map-codebase.md +377 -377
  234. package/kit/framework/workflows/milestone-summary.md +223 -223
  235. package/kit/framework/workflows/new-milestone.md +486 -486
  236. package/kit/framework/workflows/new-project.md +1159 -1159
  237. package/kit/framework/workflows/new-workspace.md +237 -237
  238. package/kit/framework/workflows/next.md +97 -97
  239. package/kit/framework/workflows/node-repair.md +92 -92
  240. package/kit/framework/workflows/note.md +156 -156
  241. package/kit/framework/workflows/pause-work.md +176 -176
  242. package/kit/framework/workflows/plan-milestone-gaps.md +273 -273
  243. package/kit/framework/workflows/plan-phase.md +765 -765
  244. package/kit/framework/workflows/plant-seed.md +169 -169
  245. package/kit/framework/workflows/pr-branch.md +129 -129
  246. package/kit/framework/workflows/profile-user.md +450 -450
  247. package/kit/framework/workflows/progress.md +507 -507
  248. package/kit/framework/workflows/quick.md +757 -757
  249. package/kit/framework/workflows/remove-phase.md +155 -155
  250. package/kit/framework/workflows/remove-workspace.md +90 -90
  251. package/kit/framework/workflows/research-phase.md +82 -82
  252. package/kit/framework/workflows/resume-project.md +326 -326
  253. package/kit/framework/workflows/review.md +228 -228
  254. package/kit/framework/workflows/session-report.md +146 -146
  255. package/kit/framework/workflows/settings.md +283 -283
  256. package/kit/framework/workflows/ship.md +228 -228
  257. package/kit/framework/workflows/stats.md +60 -60
  258. package/kit/framework/workflows/transition.md +671 -671
  259. package/kit/framework/workflows/ui-phase.md +302 -302
  260. package/kit/framework/workflows/ui-review.md +165 -165
  261. package/kit/framework/workflows/update.md +323 -323
  262. package/kit/framework/workflows/validate-phase.md +174 -174
  263. package/kit/framework/workflows/verify-phase.md +252 -252
  264. package/kit/framework/workflows/verify-work.md +637 -637
  265. package/kit/hooks/check-update.js +118 -118
  266. package/kit/hooks/context-monitor.js +163 -163
  267. package/kit/hooks/prompt-guard.js +103 -103
  268. package/kit/hooks/statusline.js +125 -125
  269. package/kit/hooks/workflow-guard.js +101 -101
  270. package/kit/settings.json +45 -45
  271. package/kit/skills/_shared-supabase/glossary.md +17 -0
  272. package/kit/skills/ai-prompt-characterization/SKILL.md +335 -335
  273. package/kit/skills/armadilhas-sistemas-distribuidos/SKILL.md +447 -447
  274. package/kit/skills/audit-log-multi-tenant/SKILL.md +340 -340
  275. package/kit/skills/b2b-saas-architecture/SKILL.md +300 -300
  276. package/kit/skills/consistencia-leitura-replica/SKILL.md +385 -385
  277. package/kit/skills/crm-lead-pipeline-patterns/SKILL.md +343 -343
  278. package/kit/skills/escolha-modelo-consistencia/SKILL.md +494 -494
  279. package/kit/skills/evolucao-schema-compativel/SKILL.md +448 -448
  280. package/kit/skills/evolution-go-whatsapp-integration/SKILL.md +322 -322
  281. package/kit/skills/example-skill/SKILL.md +42 -42
  282. package/kit/skills/legacy-api-only-applications/SKILL.md +358 -358
  283. package/kit/skills/legacy-characterization-tests/SKILL.md +330 -330
  284. package/kit/skills/legacy-effect-analysis/SKILL.md +331 -331
  285. package/kit/skills/legacy-extract-class/SKILL.md +203 -203
  286. package/kit/skills/legacy-programming-by-difference/SKILL.md +252 -252
  287. package/kit/skills/legacy-seams-and-test-harness/SKILL.md +460 -460
  288. package/kit/skills/legacy-shotgun-surgery/SKILL.md +286 -286
  289. package/kit/skills/legacy-sprout-wrap-techniques/SKILL.md +434 -434
  290. package/kit/skills/legacy-storytelling-naked-crc/SKILL.md +270 -270
  291. package/kit/skills/lgpd-multi-tenant-compliance/SKILL.md +340 -340
  292. package/kit/skills/member-invite-flow/SKILL.md +305 -305
  293. package/kit/skills/member-management-react-shadcn/SKILL.md +328 -328
  294. package/kit/skills/multi-tenant-performance-scaling/SKILL.md +316 -316
  295. package/kit/skills/multi-tenant-rls-hierarchy/SKILL.md +342 -342
  296. package/kit/skills/org-onboarding-flow/SKILL.md +257 -257
  297. package/kit/skills/org-switcher-react-pattern/SKILL.md +349 -349
  298. package/kit/skills/permission-gate-react-pattern/SKILL.md +271 -271
  299. package/kit/skills/postgres-isolamento-concorrencia/SKILL.md +552 -552
  300. package/kit/skills/pre-refactor-characterization/SKILL.md +421 -421
  301. package/kit/skills/rbac-permissions-matrix-supabase/SKILL.md +338 -338
  302. package/kit/skills/streams-eventos-cdc/SKILL.md +711 -711
  303. package/kit/skills/supabase-branching-workflow/SKILL.md +544 -544
  304. package/kit/skills/supabase-ci-cd-github-actions/SKILL.md +880 -880
  305. package/kit/skills/supabase-column-level-security/SKILL.md +426 -426
  306. package/kit/skills/supabase-config-toml-remotes/SKILL.md +807 -807
  307. package/kit/skills/supabase-custom-claims-rbac/SKILL.md +472 -472
  308. package/kit/skills/supabase-edge-functions/SKILL.md +229 -141
  309. package/kit/skills/supabase-edge-functions-auth/SKILL.md +309 -0
  310. package/kit/skills/supabase-edge-functions-limits/SKILL.md +302 -0
  311. package/kit/skills/supabase-edge-functions-mcp-server/SKILL.md +279 -0
  312. package/kit/skills/supabase-edge-functions-testing/SKILL.md +277 -0
  313. package/kit/skills/supabase-edge-runtime-builtins/SKILL.md +357 -0
  314. package/kit/skills/supabase-migration-repair/SKILL.md +823 -823
  315. package/kit/skills/supabase-migrations/SKILL.md +297 -297
  316. package/kit/skills/supabase-pgtap-testing/SKILL.md +1053 -1053
  317. package/kit/skills/supabase-postgres-roles/SKILL.md +392 -392
  318. package/kit/skills/supabase-realtime/SKILL.md +460 -236
  319. package/kit/skills/supabase-rls-defense-in-depth/SKILL.md +418 -418
  320. package/kit/skills/supabase-rls-policies/SKILL.md +635 -635
  321. package/kit/skills/super-admin-platform-pattern/SKILL.md +326 -326
  322. package/kit/skills/tenant-quente-mitigacao/SKILL.md +605 -605
  323. package/kit/skills/whatsapp-conversation-state-machine/SKILL.md +287 -287
  324. package/package.json +1 -1
  325. package/src/core/kit.js +216 -216
  326. package/src/core/reflect.js +247 -247
  327. package/src/core/reverse-sync.js +372 -372
  328. package/src/core/sync.js +418 -418
  329. package/src/core/watch.js +121 -121
  330. package/src/mcp-server/index.js +693 -693
@@ -1,385 +1,385 @@
1
- ---
2
- name: consistencia-leitura-replica
3
- description: Use ao usar Supabase read replicas via Supavisor (porta 6543) ou ao combinar Realtime broadcast + leitura DB…
4
- ---
5
-
6
- # Consistência Leitura Réplica — Supabase + Supavisor + Realtime
7
-
8
- ## Quando usar
9
-
10
- LLM carrega esta skill ao usar Supabase Pro+ com **read replicas** ou ao combinar Realtime broadcast com leitura subsequente do DB. Trigger phrases:
11
-
12
- - "Supabase read replica", "réplica de leitura"
13
- - "porta 6543 vs 5432", "Supavisor session vs transaction"
14
- - "read-after-write", "leitura após escrita inconsistente"
15
- - "monotonic reads", "leituras não-monotônicas"
16
- - "consistent prefix reads", "prefixo causal violado"
17
- - "replication lag Supabase", "atraso de replicação"
18
- - "broadcast Realtime + SELECT stale"
19
- - "pg_last_wal_replay_lsn", "WAL position detection"
20
-
21
- Esta skill aplica **DDIA Ch 5 "Problems With Replication Lag"** ao stack Supabase. Cross-referenciada por `supabase-realtime` (v1.8) ao bundlear broadcast + leitura, e por `multi-tenant-performance-scaling` (v1.21) ao escalar Postgres em Pro+.
22
-
23
- Termos canônicos (`read-after-write consistency`, `monotonic reads`, `consistent prefix reads`, `replication lag`, `leader-follower replication`) definidos em [`_shared-dados-distribuidos/glossary.md`](../_shared-dados-distribuidos/glossary.md) seções (a) e (b) — esta skill **não duplica**, apenas linka.
24
-
25
- ## Regras absolutas
26
-
27
- **REGRA #1 (read-after-write own data):** Para leituras do **próprio dado do usuário** dentro de janela de **5s após write**, rotear para **porta 5432 (líder)** — não 6543. Sem isso, usuário cria post → GET retorna 404 → percepção de bug. Justify: DDIA p. 156 "always read the user's own profile from the leader".
28
-
29
- **REGRA #2 (sticky session monotonic):** Para usuários ativos lendo de réplicas, escolher réplica via `hash(user_id) mod N` — **não round-robin**. Round-robin viola monotonic reads (DDIA p. 158). Mitigação obrigatória: fallback para líder se réplica down.
30
-
31
- **REGRA #3 (broadcast trust payload):** Após receber Realtime broadcast com `payload.record`, **NÃO** fazer SELECT subsequente. Confiar no payload — server é a fonte canônica do evento. SELECT pode atingir réplica que ainda não replicou (lag típico 50-500ms).
32
-
33
- **REGRA #4 (causal partition):** Writes causalmente relacionados (pergunta + resposta em chat, parent + child em árvore) **DEVEM** ir para a mesma partição lógica. Em Supabase: usar mesmo `org_id` ou `conversation_id` como partition key. DDIA p. 159 "any writes which are causally related to each other are written to the same partition".
34
-
35
- **REGRA #5 (LSN wait com timeout):** Quando usar `pg_last_wal_replay_lsn() >= captured_lsn`, **SEMPRE** com timeout (3-5s). Sem timeout, query trava se réplica falhou. Após timeout, fallback para líder.
36
-
37
- ## Patterns canônicos
38
-
39
- ### Problema 1: Read-after-write inconsistente (DDIA Ch 5, p. 156)
40
-
41
- **Cenário canônico:** usuário cria post via form submit → tela mostra "criado com sucesso" → usuário clica "ver post" → request vai para réplica → réplica ainda não replicou → 404. Da perspectiva do usuário: "perdi meu dado".
42
-
43
- ```
44
- User 1234 ──INSERT─→ Leader (5432) ┐
45
- │ │ replication lag (50-500ms)
46
- └──WAL──────────────→ Follower (6543, replica)
47
-
48
- User 1234 ─SELECT────────────────────────────→ ❌ 404
49
- (vai para replica via Supavisor)
50
- ```
51
-
52
- **Solução A — leitura no líder após write do mesmo usuário:**
53
-
54
- ```typescript
55
- // PT-BR: client mantém timestamp do último write em memória
56
- class SupabaseRouter {
57
- private lastWriteAt: Map<string, number> = new Map() // userId → timestamp ms
58
- private readonly STICKY_WINDOW_MS = 5000 // 5s leitura no líder
59
-
60
- async write(userId: string, table: string, payload: unknown) {
61
- const result = await this.leaderClient.from(table).insert(payload)
62
- this.lastWriteAt.set(userId, Date.now())
63
- return result
64
- }
65
-
66
- async read(userId: string, table: string, filter: object) {
67
- const lastWrite = this.lastWriteAt.get(userId) ?? 0
68
- const elapsedMs = Date.now() - lastWrite
69
-
70
- // PT-BR: dentro da janela 5s, ler do líder (porta 5432)
71
- if (elapsedMs < this.STICKY_WINDOW_MS) {
72
- return this.leaderClient.from(table).select().match(filter)
73
- }
74
- // PT-BR: fora da janela, OK ler de replica via pooler 6543
75
- return this.replicaClient.from(table).select().match(filter)
76
- }
77
- }
78
- ```
79
-
80
- **Trade-off:** dentro da janela, perde benefício do read scaling. DDIA recomenda janela curta (1-5s) — cobre 99% dos casos UX sem sobrecarregar líder.
81
-
82
- ### Problema 2: Leituras não-monotônicas (DDIA Ch 5, p. 158)
83
-
84
- **Cenário canônico:** usuário 2345 abre lista de comentários — primeira leitura vai para réplica 1 (lag 100ms) → vê comentário X. Segundo refresh vai para réplica 2 (lag 800ms) → comentário X **desapareceu**. Da perspectiva do usuário: "dado voltou no tempo".
85
-
86
- ```
87
- User 2345 ─SELECT(1)────→ Replica 1 (lag 100ms) → 1 result ✅
88
- User 2345 ─SELECT(2)────→ Replica 2 (lag 800ms) → 0 results ❌ (parecia ter sumido)
89
- ```
90
-
91
- **Solução B — sticky session por `user_id` em routing:**
92
-
93
- ```typescript
94
- // PT-BR: hash determinístico do user_id → escolhe replica fixa para esse user
95
- import { createHash } from 'node:crypto'
96
-
97
- function pickReplica(userId: string, replicas: ReadonlyArray<SupabaseClient>): SupabaseClient {
98
- const hash = createHash('sha256').update(userId).digest()
99
- const idx = hash.readUInt32BE(0) % replicas.length
100
- return replicas[idx]
101
- }
102
-
103
- // PT-BR: usage com fallback para líder se replica falhar
104
- async function readWithStickyReplica(userId: string, query: () => Promise<Result>) {
105
- const replica = pickReplica(userId, REPLICAS)
106
- try {
107
- return await query.call(replica)
108
- } catch (err) {
109
- if (isReplicaDownError(err)) {
110
- // PT-BR: fallback obrigatório — REGRA #2
111
- return await query.call(LEADER)
112
- }
113
- throw err
114
- }
115
- }
116
- ```
117
-
118
- **Pitfall:** réplica fica down → todos os usuários alocados a ela ficam sem leitura. Mitigação: detectar via health check + reroute para líder até réplica voltar.
119
-
120
- ### Problema 3: Prefixo causal violado (DDIA Ch 5, p. 159)
121
-
122
- **Cenário canônico chat:** Mr Poons pergunta "How far into the future can you see, Mrs Cake?" → write vai para partição A. Mrs Cake responde "About ten seconds usually, Mr Poons." → write vai para partição B. Observador lê de B (lag baixo) e A (lag alto): **vê resposta antes da pergunta**.
123
-
124
- ```
125
- Partição A (lag 800ms): "How far into the future..."
126
- Partição B (lag 100ms): "About ten seconds..."
127
-
128
- Observer lê B → vê resposta ✅
129
- Observer lê A → vê pergunta ✅
130
- Observer ordem percebida: resposta → pergunta ❌
131
- ```
132
-
133
- **Solução parcial — particionamento por chave causal:**
134
-
135
- ```sql
136
- -- PT-BR: ambas msgs (pergunta + resposta) particionadas por conversation_id
137
- -- garante mesma partição lógica = mesma ordem WAL
138
- create table public.messages (
139
- id uuid primary key default gen_random_uuid(),
140
- conversation_id uuid not null,
141
- author_id uuid not null,
142
- body text not null,
143
- created_at timestamptz not null default now()
144
- ) partition by hash (conversation_id);
145
-
146
- -- PT-BR: indexes ajudam ordering
147
- create index messages_conv_created_idx
148
- on public.messages (conversation_id, created_at);
149
- ```
150
-
151
- **Limitação:** garante consistent prefix dentro de uma partição. Cross-partition (ex: usuário em duas conversações simultâneas), DDIA p. 159 conclui "in general, ensuring consistent prefix reads requires snapshot isolation". Em Supabase prático: **manter conversação em uma tabela com chave causal explícita**.
152
-
153
- ### Solução C — Detecção stale via `pg_last_wal_replay_lsn()`
154
-
155
- Quando precisa de garantia "este read viu meu write" sem rotear ao líder:
156
-
157
- ```sql
158
- -- PT-BR: capturar LSN no líder após write (chamada do app via RPC)
159
- create or replace function public.get_current_lsn()
160
- returns text
161
- language sql
162
- security invoker
163
- set search_path = ''
164
- as $$
165
- -- PT-BR: pg_current_wal_lsn() retorna posição atual do WAL no líder
166
- select pg_current_wal_lsn()::text;
167
- $$;
168
-
169
- -- PT-BR: na replica, esperar até replay alcançar o LSN capturado
170
- create or replace function public.wait_for_lsn(target_lsn text, timeout_ms int default 5000)
171
- returns boolean
172
- language plpgsql
173
- security invoker
174
- set search_path = ''
175
- as $$
176
- declare
177
- start_at timestamptz := clock_timestamp();
178
- elapsed_ms int;
179
- begin
180
- loop
181
- -- PT-BR: pg_last_wal_replay_lsn() na replica = última posição replayed
182
- if pg_last_wal_replay_lsn() >= target_lsn::pg_lsn then
183
- return true;
184
- end if;
185
-
186
- elapsed_ms := extract(milliseconds from (clock_timestamp() - start_at))::int;
187
- if elapsed_ms >= timeout_ms then
188
- return false; -- PT-BR: REGRA #5 — timeout sem bloquear infinito
189
- end if;
190
-
191
- perform pg_sleep(0.05); -- PT-BR: 50ms entre polls
192
- end loop;
193
- end;
194
- $$;
195
- ```
196
-
197
- **Uso típico no client:**
198
-
199
- ```typescript
200
- // PT-BR: 1) write no líder, captura LSN
201
- const { data: lsn } = await leaderClient.rpc('get_current_lsn')
202
- await leaderClient.from('orders').insert(order)
203
-
204
- // PT-BR: 2) read no líder, espera replay
205
- const { data: ready } = await replicaClient.rpc('wait_for_lsn', {
206
- target_lsn: lsn,
207
- timeout_ms: 3000,
208
- })
209
-
210
- if (ready) {
211
- // PT-BR: replica caught up, leitura é safe
212
- return replicaClient.from('orders').select().eq('id', order.id)
213
- }
214
- // PT-BR: timeout — fallback para líder (REGRA #5)
215
- return leaderClient.from('orders').select().eq('id', order.id)
216
- ```
217
-
218
- ### Supavisor read replica routing
219
-
220
- | Porta | Modo | Use case | Connection string |
221
- |---|---|---|---|
222
- | **6543** | Transaction (default Pro+) | Apps com pooler já configurado, edge runtimes, serverless | `postgresql://postgres.[ref]:pwd@aws-0-region.pooler.supabase.com:6543/postgres` |
223
- | **5432** | Session (líder) | Reads críticas (read-after-write), writes, prepared statements, advisory locks | `postgresql://postgres.[ref]:pwd@aws-0-region.pooler.supabase.com:5432/postgres` |
224
- | `pooler.read.*` | Réplica routing | Read-heavy workloads em Pro+ com replicas habilitadas | (futuro Supabase feature — placeholder hoje) |
225
-
226
- **Decisão por tipo de query:**
227
-
228
- ```
229
- SELECT do próprio dado dentro 5s do write? → 5432 (líder, REGRA #1)
230
- SELECT cross-user, sem janela sticky? → 6543 (replica via Supavisor)
231
- INSERT / UPDATE / DELETE? → 5432 (sempre líder)
232
- SELECT FOR UPDATE / advisory lock? → 5432 (transaction precisa session mode)
233
- ```
234
-
235
- Cross-ref ATIVO: [`multi-tenant-performance-scaling/SKILL.md`](../multi-tenant-performance-scaling/SKILL.md) (v1.21) cobre Supavisor REGRA #1 sob lente de connection pooling — esta skill cobre a mesma porta sob lente de consistência.
236
-
237
- ### Realtime broadcast + leitura DB — padrão "ler o próprio broadcast"
238
-
239
- **Cenário canônico:** client A faz INSERT em `orders` → server emite Realtime broadcast `new_order` no canal `org:orders:org_42` → client B recebe broadcast → client B faz SELECT para refresh da lista. **Pode receber dado stale** (replica não replicou ainda).
240
-
241
- **Sequência do bug:**
242
-
243
- ```
244
- t=0ms Client A INSERT → Leader
245
- t=10ms Server emite broadcast → todos clients no canal recebem
246
- t=15ms Client B recebe broadcast → triggers re-fetch
247
- t=20ms Client B SELECT → Replica (lag ainda 80ms)
248
- t=20ms Replica retorna lista SEM o novo order ❌
249
- t=80ms Replica finalmente replicou (mas client B já desenhou stale)
250
- ```
251
-
252
- **Padrão correto — confiar no payload broadcast:**
253
-
254
- ```typescript
255
- // PT-BR: client confia no payload, NÃO faz SELECT subsequente
256
- import { createClient } from '@supabase/supabase-js'
257
-
258
- const supabase = createClient(SUPABASE_URL, SUPABASE_ANON_KEY)
259
-
260
- const channel = supabase
261
- .channel('org:orders:org_42', { config: { private: true } })
262
- .on('broadcast', { event: 'new_order' }, ({ payload }) => {
263
- // PT-BR: REGRA #3 — confie no payload, NÃO faça SELECT
264
- setOrders((prev) => [...prev, payload.record])
265
- })
266
- .subscribe()
267
-
268
- // PT-BR: cleanup obrigatório — pattern de supabase-realtime v1.8
269
- return () => {
270
- supabase.removeChannel(channel)
271
- }
272
- ```
273
-
274
- **Server side — emitir payload completo no broadcast:**
275
-
276
- ```typescript
277
- // PT-BR: Edge Function que cria order e broadcast com record completo
278
- Deno.serve(async (req) => {
279
- const supabase = createClient(SUPABASE_URL, SERVICE_ROLE_KEY)
280
- const order = await req.json()
281
-
282
- // PT-BR: 1) INSERT no líder
283
- const { data: created } = await supabase
284
- .from('orders')
285
- .insert(order)
286
- .select()
287
- .single()
288
-
289
- // PT-BR: 2) broadcast com record completo — clients confiam neste payload
290
- await supabase
291
- .channel(`org:orders:${order.org_id}`)
292
- .send({
293
- type: 'broadcast',
294
- event: 'new_order',
295
- payload: { record: created }, // PT-BR: payload canônico
296
- })
297
-
298
- return new Response(JSON.stringify(created), { status: 201 })
299
- })
300
- ```
301
-
302
- **Cross-ref ATIVO:** [`supabase-realtime/SKILL.md`](../supabase-realtime/SKILL.md) (v1.8) define padrão de canal (`scope:entity:id`, `private:true`, `removeChannel` cleanup). Esta skill estende com o padrão `payload.record` específico para evitar replica lag bug.
303
-
304
- ## Anti-patterns
305
-
306
- ### Anti-pattern 1: Round-robin entre réplicas para o mesmo usuário
307
-
308
- **Errado:**
309
- ```typescript
310
- // PT-BR: pegar replica aleatoriamente cada read
311
- const replica = REPLICAS[Math.floor(Math.random() * REPLICAS.length)]
312
- return replica.from('messages').select()
313
- ```
314
-
315
- **Por quê:** viola monotonic reads (DDIA p. 158). User vê mensagem X em uma leitura, depois X some na próxima (replica 2 ainda não replicou). Leituras "voltam no tempo".
316
-
317
- **Certo:** sticky session por `hash(user_id) mod N` (Solução B acima).
318
-
319
- ### Anti-pattern 2: Re-fetch após broadcast
320
-
321
- **Errado:**
322
- ```typescript
323
- .on('broadcast', { event: 'new_order' }, async () => {
324
- // PT-BR: re-fetch que pode atingir replica stale
325
- const { data } = await supabase.from('orders').select() // ❌
326
- setOrders(data)
327
- })
328
- ```
329
-
330
- **Por quê:** broadcast chega em 10-15ms, mas replication lag tipicamente 50-500ms. SELECT no callback **garantido** vai chegar antes da replica replicar. Bug "intermittent missing data".
331
-
332
- **Certo:** confiar no `payload.record` enviado pelo server (REGRA #3 + Solução padrão "ler o próprio broadcast").
333
-
334
- ### Anti-pattern 3: `pg_last_wal_replay_lsn()` sem timeout
335
-
336
- **Errado:**
337
- ```sql
338
- -- PT-BR: loop infinito se replica falhou
339
- do $$
340
- begin
341
- loop
342
- if pg_last_wal_replay_lsn() >= captured_lsn::pg_lsn then exit; end if;
343
- perform pg_sleep(0.05);
344
- end loop;
345
- end$$;
346
- ```
347
-
348
- **Por quê:** se replica desconectou do WAL stream (network partition, disk full), `pg_last_wal_replay_lsn()` nunca alcança o LSN do líder. Query trava indefinidamente, esgota connection pool.
349
-
350
- **Certo:** timeout 3-5s + fallback explícito para líder (REGRA #5 + função `wait_for_lsn` acima).
351
-
352
- ### Anti-pattern 4: Cross-partition para conversação causal
353
-
354
- **Errado:**
355
- ```sql
356
- -- PT-BR: messages particionadas por created_at (range temporal)
357
- create table public.messages (...) partition by range (created_at);
358
- ```
359
-
360
- **Por quê:** pergunta e resposta em uma conversação podem cair em partições diferentes (se virada de mês entre as duas). Viola consistent prefix reads — observador vê resposta antes da pergunta.
361
-
362
- **Certo:** particionar por `conversation_id` (HASH), garante que toda a conversação fica na mesma partição = mesma ordem WAL = consistent prefix.
363
-
364
- ### Anti-pattern 5: Porta 6543 para `SELECT FOR UPDATE`
365
-
366
- **Errado:**
367
- ```typescript
368
- // PT-BR: tentando lock pessimista via Supavisor transaction mode
369
- const { data } = await client6543.rpc('lock_order', { id })
370
- ```
371
-
372
- **Por quê:** Supavisor 6543 (transaction mode) não preserva sessão entre statements — `SELECT FOR UPDATE` libera o lock na próxima query. Lock vira no-op silencioso.
373
-
374
- **Certo:** porta 5432 (session mode) para qualquer operação que precisa estado de sessão (locks, prepared statements, `SET LOCAL`, advisory locks).
375
-
376
- ## Ver também
377
-
378
- - [`_shared-dados-distribuidos/glossary.md`](../_shared-dados-distribuidos/glossary.md) — termos canônicos `read-after-write consistency`, `monotonic reads`, `consistent prefix reads`, `replication lag`, `leader-follower replication` (Phase 117)
379
- - [`supabase-realtime/SKILL.md`](../supabase-realtime/SKILL.md) — broadcast com `private:true`, naming `scope:entity:id`, cleanup `removeChannel` (v1.8)
380
- - [`multi-tenant-performance-scaling/SKILL.md`](../multi-tenant-performance-scaling/SKILL.md) — Supavisor connection string canônica, REGRA #1 porta 6543 (v1.21)
381
- - [`supabase-database-functions/SKILL.md`](../supabase-database-functions/SKILL.md) — padrões PG functions (security invoker, search_path) usados em `get_current_lsn` e `wait_for_lsn`
382
- - [Designing Data-Intensive Applications, Martin Kleppmann (O'Reilly 2017)](https://www.oreilly.com/library/view/designing-data-intensive-applications/9781491903063/) — Ch 5 "Problems With Replication Lag" (p. 155-160)
383
- - [PostgreSQL Documentation — pg_last_wal_replay_lsn](https://www.postgresql.org/docs/current/functions-admin.html#FUNCTIONS-RECOVERY-INFO)
384
- - [Supabase Read Replicas](https://supabase.com/docs/guides/platform/read-replicas)
385
- - [Supabase Supavisor 1M Connections](https://supabase.com/blog/supavisor-1-million)
1
+ ---
2
+ name: consistencia-leitura-replica
3
+ description: Use ao usar Supabase read replicas via Supavisor (porta 6543) ou ao combinar Realtime broadcast + leitura DB…
4
+ ---
5
+
6
+ # Consistência Leitura Réplica — Supabase + Supavisor + Realtime
7
+
8
+ ## Quando usar
9
+
10
+ LLM carrega esta skill ao usar Supabase Pro+ com **read replicas** ou ao combinar Realtime broadcast com leitura subsequente do DB. Trigger phrases:
11
+
12
+ - "Supabase read replica", "réplica de leitura"
13
+ - "porta 6543 vs 5432", "Supavisor session vs transaction"
14
+ - "read-after-write", "leitura após escrita inconsistente"
15
+ - "monotonic reads", "leituras não-monotônicas"
16
+ - "consistent prefix reads", "prefixo causal violado"
17
+ - "replication lag Supabase", "atraso de replicação"
18
+ - "broadcast Realtime + SELECT stale"
19
+ - "pg_last_wal_replay_lsn", "WAL position detection"
20
+
21
+ Esta skill aplica **DDIA Ch 5 "Problems With Replication Lag"** ao stack Supabase. Cross-referenciada por `supabase-realtime` (v1.8) ao bundlear broadcast + leitura, e por `multi-tenant-performance-scaling` (v1.21) ao escalar Postgres em Pro+.
22
+
23
+ Termos canônicos (`read-after-write consistency`, `monotonic reads`, `consistent prefix reads`, `replication lag`, `leader-follower replication`) definidos em [`_shared-dados-distribuidos/glossary.md`](../_shared-dados-distribuidos/glossary.md) seções (a) e (b) — esta skill **não duplica**, apenas linka.
24
+
25
+ ## Regras absolutas
26
+
27
+ **REGRA #1 (read-after-write own data):** Para leituras do **próprio dado do usuário** dentro de janela de **5s após write**, rotear para **porta 5432 (líder)** — não 6543. Sem isso, usuário cria post → GET retorna 404 → percepção de bug. Justify: DDIA p. 156 "always read the user's own profile from the leader".
28
+
29
+ **REGRA #2 (sticky session monotonic):** Para usuários ativos lendo de réplicas, escolher réplica via `hash(user_id) mod N` — **não round-robin**. Round-robin viola monotonic reads (DDIA p. 158). Mitigação obrigatória: fallback para líder se réplica down.
30
+
31
+ **REGRA #3 (broadcast trust payload):** Após receber Realtime broadcast com `payload.record`, **NÃO** fazer SELECT subsequente. Confiar no payload — server é a fonte canônica do evento. SELECT pode atingir réplica que ainda não replicou (lag típico 50-500ms).
32
+
33
+ **REGRA #4 (causal partition):** Writes causalmente relacionados (pergunta + resposta em chat, parent + child em árvore) **DEVEM** ir para a mesma partição lógica. Em Supabase: usar mesmo `org_id` ou `conversation_id` como partition key. DDIA p. 159 "any writes which are causally related to each other are written to the same partition".
34
+
35
+ **REGRA #5 (LSN wait com timeout):** Quando usar `pg_last_wal_replay_lsn() >= captured_lsn`, **SEMPRE** com timeout (3-5s). Sem timeout, query trava se réplica falhou. Após timeout, fallback para líder.
36
+
37
+ ## Patterns canônicos
38
+
39
+ ### Problema 1: Read-after-write inconsistente (DDIA Ch 5, p. 156)
40
+
41
+ **Cenário canônico:** usuário cria post via form submit → tela mostra "criado com sucesso" → usuário clica "ver post" → request vai para réplica → réplica ainda não replicou → 404. Da perspectiva do usuário: "perdi meu dado".
42
+
43
+ ```
44
+ User 1234 ──INSERT─→ Leader (5432) ┐
45
+ │ │ replication lag (50-500ms)
46
+ └──WAL──────────────→ Follower (6543, replica)
47
+
48
+ User 1234 ─SELECT────────────────────────────→ ❌ 404
49
+ (vai para replica via Supavisor)
50
+ ```
51
+
52
+ **Solução A — leitura no líder após write do mesmo usuário:**
53
+
54
+ ```typescript
55
+ // PT-BR: client mantém timestamp do último write em memória
56
+ class SupabaseRouter {
57
+ private lastWriteAt: Map<string, number> = new Map() // userId → timestamp ms
58
+ private readonly STICKY_WINDOW_MS = 5000 // 5s leitura no líder
59
+
60
+ async write(userId: string, table: string, payload: unknown) {
61
+ const result = await this.leaderClient.from(table).insert(payload)
62
+ this.lastWriteAt.set(userId, Date.now())
63
+ return result
64
+ }
65
+
66
+ async read(userId: string, table: string, filter: object) {
67
+ const lastWrite = this.lastWriteAt.get(userId) ?? 0
68
+ const elapsedMs = Date.now() - lastWrite
69
+
70
+ // PT-BR: dentro da janela 5s, ler do líder (porta 5432)
71
+ if (elapsedMs < this.STICKY_WINDOW_MS) {
72
+ return this.leaderClient.from(table).select().match(filter)
73
+ }
74
+ // PT-BR: fora da janela, OK ler de replica via pooler 6543
75
+ return this.replicaClient.from(table).select().match(filter)
76
+ }
77
+ }
78
+ ```
79
+
80
+ **Trade-off:** dentro da janela, perde benefício do read scaling. DDIA recomenda janela curta (1-5s) — cobre 99% dos casos UX sem sobrecarregar líder.
81
+
82
+ ### Problema 2: Leituras não-monotônicas (DDIA Ch 5, p. 158)
83
+
84
+ **Cenário canônico:** usuário 2345 abre lista de comentários — primeira leitura vai para réplica 1 (lag 100ms) → vê comentário X. Segundo refresh vai para réplica 2 (lag 800ms) → comentário X **desapareceu**. Da perspectiva do usuário: "dado voltou no tempo".
85
+
86
+ ```
87
+ User 2345 ─SELECT(1)────→ Replica 1 (lag 100ms) → 1 result ✅
88
+ User 2345 ─SELECT(2)────→ Replica 2 (lag 800ms) → 0 results ❌ (parecia ter sumido)
89
+ ```
90
+
91
+ **Solução B — sticky session por `user_id` em routing:**
92
+
93
+ ```typescript
94
+ // PT-BR: hash determinístico do user_id → escolhe replica fixa para esse user
95
+ import { createHash } from 'node:crypto'
96
+
97
+ function pickReplica(userId: string, replicas: ReadonlyArray<SupabaseClient>): SupabaseClient {
98
+ const hash = createHash('sha256').update(userId).digest()
99
+ const idx = hash.readUInt32BE(0) % replicas.length
100
+ return replicas[idx]
101
+ }
102
+
103
+ // PT-BR: usage com fallback para líder se replica falhar
104
+ async function readWithStickyReplica(userId: string, query: () => Promise<Result>) {
105
+ const replica = pickReplica(userId, REPLICAS)
106
+ try {
107
+ return await query.call(replica)
108
+ } catch (err) {
109
+ if (isReplicaDownError(err)) {
110
+ // PT-BR: fallback obrigatório — REGRA #2
111
+ return await query.call(LEADER)
112
+ }
113
+ throw err
114
+ }
115
+ }
116
+ ```
117
+
118
+ **Pitfall:** réplica fica down → todos os usuários alocados a ela ficam sem leitura. Mitigação: detectar via health check + reroute para líder até réplica voltar.
119
+
120
+ ### Problema 3: Prefixo causal violado (DDIA Ch 5, p. 159)
121
+
122
+ **Cenário canônico chat:** Mr Poons pergunta "How far into the future can you see, Mrs Cake?" → write vai para partição A. Mrs Cake responde "About ten seconds usually, Mr Poons." → write vai para partição B. Observador lê de B (lag baixo) e A (lag alto): **vê resposta antes da pergunta**.
123
+
124
+ ```
125
+ Partição A (lag 800ms): "How far into the future..."
126
+ Partição B (lag 100ms): "About ten seconds..."
127
+
128
+ Observer lê B → vê resposta ✅
129
+ Observer lê A → vê pergunta ✅
130
+ Observer ordem percebida: resposta → pergunta ❌
131
+ ```
132
+
133
+ **Solução parcial — particionamento por chave causal:**
134
+
135
+ ```sql
136
+ -- PT-BR: ambas msgs (pergunta + resposta) particionadas por conversation_id
137
+ -- garante mesma partição lógica = mesma ordem WAL
138
+ create table public.messages (
139
+ id uuid primary key default gen_random_uuid(),
140
+ conversation_id uuid not null,
141
+ author_id uuid not null,
142
+ body text not null,
143
+ created_at timestamptz not null default now()
144
+ ) partition by hash (conversation_id);
145
+
146
+ -- PT-BR: indexes ajudam ordering
147
+ create index messages_conv_created_idx
148
+ on public.messages (conversation_id, created_at);
149
+ ```
150
+
151
+ **Limitação:** garante consistent prefix dentro de uma partição. Cross-partition (ex: usuário em duas conversações simultâneas), DDIA p. 159 conclui "in general, ensuring consistent prefix reads requires snapshot isolation". Em Supabase prático: **manter conversação em uma tabela com chave causal explícita**.
152
+
153
+ ### Solução C — Detecção stale via `pg_last_wal_replay_lsn()`
154
+
155
+ Quando precisa de garantia "este read viu meu write" sem rotear ao líder:
156
+
157
+ ```sql
158
+ -- PT-BR: capturar LSN no líder após write (chamada do app via RPC)
159
+ create or replace function public.get_current_lsn()
160
+ returns text
161
+ language sql
162
+ security invoker
163
+ set search_path = ''
164
+ as $$
165
+ -- PT-BR: pg_current_wal_lsn() retorna posição atual do WAL no líder
166
+ select pg_current_wal_lsn()::text;
167
+ $$;
168
+
169
+ -- PT-BR: na replica, esperar até replay alcançar o LSN capturado
170
+ create or replace function public.wait_for_lsn(target_lsn text, timeout_ms int default 5000)
171
+ returns boolean
172
+ language plpgsql
173
+ security invoker
174
+ set search_path = ''
175
+ as $$
176
+ declare
177
+ start_at timestamptz := clock_timestamp();
178
+ elapsed_ms int;
179
+ begin
180
+ loop
181
+ -- PT-BR: pg_last_wal_replay_lsn() na replica = última posição replayed
182
+ if pg_last_wal_replay_lsn() >= target_lsn::pg_lsn then
183
+ return true;
184
+ end if;
185
+
186
+ elapsed_ms := extract(milliseconds from (clock_timestamp() - start_at))::int;
187
+ if elapsed_ms >= timeout_ms then
188
+ return false; -- PT-BR: REGRA #5 — timeout sem bloquear infinito
189
+ end if;
190
+
191
+ perform pg_sleep(0.05); -- PT-BR: 50ms entre polls
192
+ end loop;
193
+ end;
194
+ $$;
195
+ ```
196
+
197
+ **Uso típico no client:**
198
+
199
+ ```typescript
200
+ // PT-BR: 1) write no líder, captura LSN
201
+ const { data: lsn } = await leaderClient.rpc('get_current_lsn')
202
+ await leaderClient.from('orders').insert(order)
203
+
204
+ // PT-BR: 2) read no líder, espera replay
205
+ const { data: ready } = await replicaClient.rpc('wait_for_lsn', {
206
+ target_lsn: lsn,
207
+ timeout_ms: 3000,
208
+ })
209
+
210
+ if (ready) {
211
+ // PT-BR: replica caught up, leitura é safe
212
+ return replicaClient.from('orders').select().eq('id', order.id)
213
+ }
214
+ // PT-BR: timeout — fallback para líder (REGRA #5)
215
+ return leaderClient.from('orders').select().eq('id', order.id)
216
+ ```
217
+
218
+ ### Supavisor read replica routing
219
+
220
+ | Porta | Modo | Use case | Connection string |
221
+ |---|---|---|---|
222
+ | **6543** | Transaction (default Pro+) | Apps com pooler já configurado, edge runtimes, serverless | `postgresql://postgres.[ref]:pwd@aws-0-region.pooler.supabase.com:6543/postgres` |
223
+ | **5432** | Session (líder) | Reads críticas (read-after-write), writes, prepared statements, advisory locks | `postgresql://postgres.[ref]:pwd@aws-0-region.pooler.supabase.com:5432/postgres` |
224
+ | `pooler.read.*` | Réplica routing | Read-heavy workloads em Pro+ com replicas habilitadas | (futuro Supabase feature — placeholder hoje) |
225
+
226
+ **Decisão por tipo de query:**
227
+
228
+ ```
229
+ SELECT do próprio dado dentro 5s do write? → 5432 (líder, REGRA #1)
230
+ SELECT cross-user, sem janela sticky? → 6543 (replica via Supavisor)
231
+ INSERT / UPDATE / DELETE? → 5432 (sempre líder)
232
+ SELECT FOR UPDATE / advisory lock? → 5432 (transaction precisa session mode)
233
+ ```
234
+
235
+ Cross-ref ATIVO: [`multi-tenant-performance-scaling/SKILL.md`](../multi-tenant-performance-scaling/SKILL.md) (v1.21) cobre Supavisor REGRA #1 sob lente de connection pooling — esta skill cobre a mesma porta sob lente de consistência.
236
+
237
+ ### Realtime broadcast + leitura DB — padrão "ler o próprio broadcast"
238
+
239
+ **Cenário canônico:** client A faz INSERT em `orders` → server emite Realtime broadcast `new_order` no canal `org:orders:org_42` → client B recebe broadcast → client B faz SELECT para refresh da lista. **Pode receber dado stale** (replica não replicou ainda).
240
+
241
+ **Sequência do bug:**
242
+
243
+ ```
244
+ t=0ms Client A INSERT → Leader
245
+ t=10ms Server emite broadcast → todos clients no canal recebem
246
+ t=15ms Client B recebe broadcast → triggers re-fetch
247
+ t=20ms Client B SELECT → Replica (lag ainda 80ms)
248
+ t=20ms Replica retorna lista SEM o novo order ❌
249
+ t=80ms Replica finalmente replicou (mas client B já desenhou stale)
250
+ ```
251
+
252
+ **Padrão correto — confiar no payload broadcast:**
253
+
254
+ ```typescript
255
+ // PT-BR: client confia no payload, NÃO faz SELECT subsequente
256
+ import { createClient } from '@supabase/supabase-js'
257
+
258
+ const supabase = createClient(SUPABASE_URL, SUPABASE_ANON_KEY)
259
+
260
+ const channel = supabase
261
+ .channel('org:orders:org_42', { config: { private: true } })
262
+ .on('broadcast', { event: 'new_order' }, ({ payload }) => {
263
+ // PT-BR: REGRA #3 — confie no payload, NÃO faça SELECT
264
+ setOrders((prev) => [...prev, payload.record])
265
+ })
266
+ .subscribe()
267
+
268
+ // PT-BR: cleanup obrigatório — pattern de supabase-realtime v1.8
269
+ return () => {
270
+ supabase.removeChannel(channel)
271
+ }
272
+ ```
273
+
274
+ **Server side — emitir payload completo no broadcast:**
275
+
276
+ ```typescript
277
+ // PT-BR: Edge Function que cria order e broadcast com record completo
278
+ Deno.serve(async (req) => {
279
+ const supabase = createClient(SUPABASE_URL, SERVICE_ROLE_KEY)
280
+ const order = await req.json()
281
+
282
+ // PT-BR: 1) INSERT no líder
283
+ const { data: created } = await supabase
284
+ .from('orders')
285
+ .insert(order)
286
+ .select()
287
+ .single()
288
+
289
+ // PT-BR: 2) broadcast com record completo — clients confiam neste payload
290
+ await supabase
291
+ .channel(`org:orders:${order.org_id}`)
292
+ .send({
293
+ type: 'broadcast',
294
+ event: 'new_order',
295
+ payload: { record: created }, // PT-BR: payload canônico
296
+ })
297
+
298
+ return new Response(JSON.stringify(created), { status: 201 })
299
+ })
300
+ ```
301
+
302
+ **Cross-ref ATIVO:** [`supabase-realtime/SKILL.md`](../supabase-realtime/SKILL.md) (v1.8) define padrão de canal (`scope:entity:id`, `private:true`, `removeChannel` cleanup). Esta skill estende com o padrão `payload.record` específico para evitar replica lag bug.
303
+
304
+ ## Anti-patterns
305
+
306
+ ### Anti-pattern 1: Round-robin entre réplicas para o mesmo usuário
307
+
308
+ **Errado:**
309
+ ```typescript
310
+ // PT-BR: pegar replica aleatoriamente cada read
311
+ const replica = REPLICAS[Math.floor(Math.random() * REPLICAS.length)]
312
+ return replica.from('messages').select()
313
+ ```
314
+
315
+ **Por quê:** viola monotonic reads (DDIA p. 158). User vê mensagem X em uma leitura, depois X some na próxima (replica 2 ainda não replicou). Leituras "voltam no tempo".
316
+
317
+ **Certo:** sticky session por `hash(user_id) mod N` (Solução B acima).
318
+
319
+ ### Anti-pattern 2: Re-fetch após broadcast
320
+
321
+ **Errado:**
322
+ ```typescript
323
+ .on('broadcast', { event: 'new_order' }, async () => {
324
+ // PT-BR: re-fetch que pode atingir replica stale
325
+ const { data } = await supabase.from('orders').select() // ❌
326
+ setOrders(data)
327
+ })
328
+ ```
329
+
330
+ **Por quê:** broadcast chega em 10-15ms, mas replication lag tipicamente 50-500ms. SELECT no callback **garantido** vai chegar antes da replica replicar. Bug "intermittent missing data".
331
+
332
+ **Certo:** confiar no `payload.record` enviado pelo server (REGRA #3 + Solução padrão "ler o próprio broadcast").
333
+
334
+ ### Anti-pattern 3: `pg_last_wal_replay_lsn()` sem timeout
335
+
336
+ **Errado:**
337
+ ```sql
338
+ -- PT-BR: loop infinito se replica falhou
339
+ do $$
340
+ begin
341
+ loop
342
+ if pg_last_wal_replay_lsn() >= captured_lsn::pg_lsn then exit; end if;
343
+ perform pg_sleep(0.05);
344
+ end loop;
345
+ end$$;
346
+ ```
347
+
348
+ **Por quê:** se replica desconectou do WAL stream (network partition, disk full), `pg_last_wal_replay_lsn()` nunca alcança o LSN do líder. Query trava indefinidamente, esgota connection pool.
349
+
350
+ **Certo:** timeout 3-5s + fallback explícito para líder (REGRA #5 + função `wait_for_lsn` acima).
351
+
352
+ ### Anti-pattern 4: Cross-partition para conversação causal
353
+
354
+ **Errado:**
355
+ ```sql
356
+ -- PT-BR: messages particionadas por created_at (range temporal)
357
+ create table public.messages (...) partition by range (created_at);
358
+ ```
359
+
360
+ **Por quê:** pergunta e resposta em uma conversação podem cair em partições diferentes (se virada de mês entre as duas). Viola consistent prefix reads — observador vê resposta antes da pergunta.
361
+
362
+ **Certo:** particionar por `conversation_id` (HASH), garante que toda a conversação fica na mesma partição = mesma ordem WAL = consistent prefix.
363
+
364
+ ### Anti-pattern 5: Porta 6543 para `SELECT FOR UPDATE`
365
+
366
+ **Errado:**
367
+ ```typescript
368
+ // PT-BR: tentando lock pessimista via Supavisor transaction mode
369
+ const { data } = await client6543.rpc('lock_order', { id })
370
+ ```
371
+
372
+ **Por quê:** Supavisor 6543 (transaction mode) não preserva sessão entre statements — `SELECT FOR UPDATE` libera o lock na próxima query. Lock vira no-op silencioso.
373
+
374
+ **Certo:** porta 5432 (session mode) para qualquer operação que precisa estado de sessão (locks, prepared statements, `SET LOCAL`, advisory locks).
375
+
376
+ ## Ver também
377
+
378
+ - [`_shared-dados-distribuidos/glossary.md`](../_shared-dados-distribuidos/glossary.md) — termos canônicos `read-after-write consistency`, `monotonic reads`, `consistent prefix reads`, `replication lag`, `leader-follower replication` (Phase 117)
379
+ - [`supabase-realtime/SKILL.md`](../supabase-realtime/SKILL.md) — broadcast com `private:true`, naming `scope:entity:id`, cleanup `removeChannel` (v1.8)
380
+ - [`multi-tenant-performance-scaling/SKILL.md`](../multi-tenant-performance-scaling/SKILL.md) — Supavisor connection string canônica, REGRA #1 porta 6543 (v1.21)
381
+ - [`supabase-database-functions/SKILL.md`](../supabase-database-functions/SKILL.md) — padrões PG functions (security invoker, search_path) usados em `get_current_lsn` e `wait_for_lsn`
382
+ - [Designing Data-Intensive Applications, Martin Kleppmann (O'Reilly 2017)](https://www.oreilly.com/library/view/designing-data-intensive-applications/9781491903063/) — Ch 5 "Problems With Replication Lag" (p. 155-160)
383
+ - [PostgreSQL Documentation — pg_last_wal_replay_lsn](https://www.postgresql.org/docs/current/functions-admin.html#FUNCTIONS-RECOVERY-INFO)
384
+ - [Supabase Read Replicas](https://supabase.com/docs/guides/platform/read-replicas)
385
+ - [Supabase Supavisor 1M Connections](https://supabase.com/blog/supavisor-1-million)