nexus-core-v3 3.0.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 (232) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +134 -0
  3. package/agents/README.md +133 -0
  4. package/agents/_protocol.md +107 -0
  5. package/agents/analyst.md +138 -0
  6. package/agents/architect.md +146 -0
  7. package/agents/data-engineer.md +170 -0
  8. package/agents/dev.md +134 -0
  9. package/agents/devops.md +141 -0
  10. package/agents/nexus-master.md +147 -0
  11. package/agents/pm.md +133 -0
  12. package/agents/po.md +138 -0
  13. package/agents/qa.md +192 -0
  14. package/agents/sm.md +122 -0
  15. package/agents/squad-creator.md +121 -0
  16. package/agents/ux-design-expert.md +165 -0
  17. package/artifact-manifest.json +903 -0
  18. package/bin/nexus.mjs +37 -0
  19. package/checklists/README.md +49 -0
  20. package/checklists/architect-checklist.md +47 -0
  21. package/checklists/change-checklist.md +61 -0
  22. package/checklists/db-predeploy-checklist.md +57 -0
  23. package/checklists/design-quality-checklist.md +57 -0
  24. package/checklists/discovery-checklist.md +36 -0
  25. package/checklists/foundation-checklist.md +39 -0
  26. package/checklists/launch-checklist.md +39 -0
  27. package/checklists/pm-checklist.md +48 -0
  28. package/checklists/po-master-checklist.md +64 -0
  29. package/checklists/reality-check-checklist.md +49 -0
  30. package/checklists/story-dod-checklist.md +52 -0
  31. package/checklists/story-draft-checklist.md +36 -0
  32. package/dist/bin/dashboard.html +279 -0
  33. package/dist/bin/nexus.mjs +20008 -0
  34. package/dist/constitution.yaml +76 -0
  35. package/knowledge/README.md +57 -0
  36. package/knowledge/architecture/architectural-styles-map.md +182 -0
  37. package/knowledge/architecture/design-patterns-gof.md +192 -0
  38. package/knowledge/architecture/distributed-patterns-cheatsheet.md +201 -0
  39. package/knowledge/architecture/saas-subscription-blueprint.md +355 -0
  40. package/knowledge/architecture/system-design-tradeoffs.md +231 -0
  41. package/knowledge/architecture/t3-fullstack-typesafe-stack.md +273 -0
  42. package/knowledge/copy/landing-copy-that-converts.md +168 -0
  43. package/knowledge/data/postgres-indexing-and-tuning.md +263 -0
  44. package/knowledge/data/schema-modeling-decisions.md +273 -0
  45. package/knowledge/data/supabase-rls-patterns.md +316 -0
  46. package/knowledge/data/zero-downtime-migrations.md +308 -0
  47. package/knowledge/devops/cicd-pipeline-best-practices.md +318 -0
  48. package/knowledge/devops/production-dockerfile.md +283 -0
  49. package/knowledge/devops/twelve-factor-app.md +398 -0
  50. package/knowledge/engineering/clean-code-principles.md +429 -0
  51. package/knowledge/engineering/effective-code-review.md +204 -0
  52. package/knowledge/engineering/testing-strategy-beyond-unit.md +307 -0
  53. package/knowledge/governance/risk-matrix.md +56 -0
  54. package/knowledge/integration/mcp-server-selection-matrix.md +235 -0
  55. package/knowledge/marketing/copy-que-converte.md +43 -0
  56. package/knowledge/marketing/funil-e-jornada.md +36 -0
  57. package/knowledge/negocios/proposta-vencedora.md +38 -0
  58. package/knowledge/negocios/roi-e-unit-economics.md +46 -0
  59. package/knowledge/pipeline/1-descobrir.md +26 -0
  60. package/knowledge/pipeline/2-estrategizar.md +26 -0
  61. package/knowledge/pipeline/3-estruturar.md +27 -0
  62. package/knowledge/pipeline/4-construir.md +27 -0
  63. package/knowledge/pipeline/5-endurecer.md +28 -0
  64. package/knowledge/pipeline/6-lancar.md +27 -0
  65. package/knowledge/pipeline/7-operar.md +27 -0
  66. package/knowledge/security/lgpd-conformidade-basica.md +35 -0
  67. package/knowledge/security/owasp-secure-coding-gates.md +220 -0
  68. package/knowledge/security/owasp-top10-threat-assessment.md +287 -0
  69. package/knowledge/security/threat-modeling-stride.md +34 -0
  70. package/knowledge/web-craft/a11y-audit-checklist.md +251 -0
  71. package/knowledge/web-craft/accessible-component-patterns.md +383 -0
  72. package/knowledge/web-craft/anti-ai-look.md +114 -0
  73. package/knowledge/web-craft/design-system-from-code.md +195 -0
  74. package/knowledge/web-craft/intrinsic-css-layout.md +420 -0
  75. package/knowledge/web-craft/style-cloning.md +185 -0
  76. package/knowledge/web-craft/visual-polish-review.md +183 -0
  77. package/package.json +55 -0
  78. package/runbooks/campanha-de-conteudo.md +36 -0
  79. package/runbooks/feature-em-projeto-existente.md +37 -0
  80. package/runbooks/mvp-startup.md +38 -0
  81. package/runbooks/resposta-a-incidente.md +37 -0
  82. package/squads/exemplo-conteudo/agents/editor-chefe.md +48 -0
  83. package/squads/exemplo-conteudo/agents/pesquisador.md +44 -0
  84. package/squads/exemplo-conteudo/agents/redator.md +45 -0
  85. package/squads/exemplo-conteudo/knowledge/estilo-editorial.md +21 -0
  86. package/squads/exemplo-conteudo/squad.yaml +19 -0
  87. package/squads/exemplo-conteudo/tasks/pesquisar-fontes.md +26 -0
  88. package/squads/exemplo-conteudo/tasks/planejar-pauta.md +27 -0
  89. package/squads/exemplo-conteudo/tasks/redigir-artigo.md +26 -0
  90. package/squads/exemplo-conteudo/tasks/revisar-artigo.md +27 -0
  91. package/squads/marketing/agents/analista.md +56 -0
  92. package/squads/marketing/agents/chefe-marketing.md +65 -0
  93. package/squads/marketing/agents/conteudo.md +55 -0
  94. package/squads/marketing/agents/copy.md +55 -0
  95. package/squads/marketing/agents/growth.md +56 -0
  96. package/squads/marketing/agents/social.md +55 -0
  97. package/squads/marketing/squad.yaml +17 -0
  98. package/squads/marketing/tasks/aprovar-campanha.md +43 -0
  99. package/squads/negocios/agents/chefe-negocios.md +65 -0
  100. package/squads/negocios/agents/financas-roi.md +55 -0
  101. package/squads/negocios/agents/suporte.md +55 -0
  102. package/squads/negocios/agents/vendas-proposta.md +56 -0
  103. package/squads/negocios/squad.yaml +17 -0
  104. package/squads/negocios/tasks/aprovar-proposta.md +40 -0
  105. package/squads/security/agents/appsec-reviewer.md +59 -0
  106. package/squads/security/agents/chefe-seguranca.md +65 -0
  107. package/squads/security/agents/compliance-auditor.md +60 -0
  108. package/squads/security/agents/threat-modeler.md +60 -0
  109. package/squads/security/squad.yaml +20 -0
  110. package/squads/security/tasks/aprovar-gate-seguranca.md +42 -0
  111. package/squads/security/tasks/emitir-parecer-conformidade.md +42 -0
  112. package/tasks/README.md +72 -0
  113. package/tasks/accessibility-wcag-checklist.md +69 -0
  114. package/tasks/advanced-elicitation.md +42 -0
  115. package/tasks/analyze-performance.md +54 -0
  116. package/tasks/analyze-project-structure.md +59 -0
  117. package/tasks/apply-qa-fixes.md +57 -0
  118. package/tasks/architect-analyze-impact.md +62 -0
  119. package/tasks/archive-squad.md +52 -0
  120. package/tasks/audit-codebase.md +53 -0
  121. package/tasks/build-component.md +61 -0
  122. package/tasks/calculate-roi.md +63 -0
  123. package/tasks/ci-cd-configuration.md +51 -0
  124. package/tasks/collect-visual-evidence.md +62 -0
  125. package/tasks/compose-molecule.md +57 -0
  126. package/tasks/consolidate-patterns.md +54 -0
  127. package/tasks/create-brownfield-prd.md +54 -0
  128. package/tasks/create-competitor-analysis.md +42 -0
  129. package/tasks/create-deep-research-prompt.md +62 -0
  130. package/tasks/create-doc.md +62 -0
  131. package/tasks/create-epic.md +49 -0
  132. package/tasks/create-front-end-spec.md +56 -0
  133. package/tasks/create-migration-plan.md +57 -0
  134. package/tasks/create-next-story.md +66 -0
  135. package/tasks/create-prd.md +53 -0
  136. package/tasks/create-project-brief.md +47 -0
  137. package/tasks/create-rls-policies.md +59 -0
  138. package/tasks/create-schema.md +57 -0
  139. package/tasks/create-service.md +55 -0
  140. package/tasks/create-squad.md +100 -0
  141. package/tasks/create-suite.md +62 -0
  142. package/tasks/db-apply-migration.md +56 -0
  143. package/tasks/db-domain-modeling.md +57 -0
  144. package/tasks/db-dry-run.md +50 -0
  145. package/tasks/db-env-check.md +57 -0
  146. package/tasks/db-load-csv.md +54 -0
  147. package/tasks/db-policy-apply.md +58 -0
  148. package/tasks/db-rollback.md +51 -0
  149. package/tasks/db-run-sql.md +61 -0
  150. package/tasks/db-seed.md +52 -0
  151. package/tasks/db-smoke-test.md +51 -0
  152. package/tasks/db-snapshot.md +48 -0
  153. package/tasks/db-verify-order.md +49 -0
  154. package/tasks/deliberate.md +46 -0
  155. package/tasks/design-indexes.md +59 -0
  156. package/tasks/dev-develop-story.md +61 -0
  157. package/tasks/document-project.md +59 -0
  158. package/tasks/execute-checklist.md +57 -0
  159. package/tasks/execute-epic-plan.md +52 -0
  160. package/tasks/execute-subtask.md +51 -0
  161. package/tasks/extend-pattern.md +63 -0
  162. package/tasks/extend-squad.md +60 -0
  163. package/tasks/extract-patterns.md +64 -0
  164. package/tasks/extract-tokens.md +59 -0
  165. package/tasks/facilitate-brainstorming-session.md +42 -0
  166. package/tasks/generate-ai-frontend-prompt.md +57 -0
  167. package/tasks/generate-documentation.md +60 -0
  168. package/tasks/generate-migration-strategy.md +57 -0
  169. package/tasks/generate-shock-report.md +56 -0
  170. package/tasks/mcp-management.md +66 -0
  171. package/tasks/orchestrate.md +50 -0
  172. package/tasks/perform-market-research.md +42 -0
  173. package/tasks/plan-create-context.md +57 -0
  174. package/tasks/plan-create-implementation.md +58 -0
  175. package/tasks/po-close-story.md +60 -0
  176. package/tasks/po-manage-story-backlog.md +59 -0
  177. package/tasks/po-pull-story.md +60 -0
  178. package/tasks/po-sync-story.md +59 -0
  179. package/tasks/pr-automation.md +50 -0
  180. package/tasks/pre-push-quality-gate.md +54 -0
  181. package/tasks/push.md +53 -0
  182. package/tasks/qa-browser-console-check.md +52 -0
  183. package/tasks/qa-create-fix-request.md +58 -0
  184. package/tasks/qa-evidence-requirements.md +55 -0
  185. package/tasks/qa-false-positive-detection.md +55 -0
  186. package/tasks/qa-fix-issues.md +55 -0
  187. package/tasks/qa-gate.md +53 -0
  188. package/tasks/qa-migration-validation.md +58 -0
  189. package/tasks/qa-nfr-assess.md +45 -0
  190. package/tasks/qa-review-story.md +56 -0
  191. package/tasks/qa-risk-profile.md +45 -0
  192. package/tasks/qa-security-checklist.md +64 -0
  193. package/tasks/qa-test-design.md +47 -0
  194. package/tasks/qa-trace-requirements.md +48 -0
  195. package/tasks/release-management.md +53 -0
  196. package/tasks/repository-cleanup.md +61 -0
  197. package/tasks/route.md +44 -0
  198. package/tasks/run-tests.md +50 -0
  199. package/tasks/security-audit.md +54 -0
  200. package/tasks/setup-database.md +60 -0
  201. package/tasks/setup-design-system.md +60 -0
  202. package/tasks/shard-doc.md +60 -0
  203. package/tasks/spec-assess-complexity.md +55 -0
  204. package/tasks/spec-critique.md +64 -0
  205. package/tasks/spec-gather-requirements.md +48 -0
  206. package/tasks/spec-research-dependencies.md +42 -0
  207. package/tasks/spec-write-spec.md +50 -0
  208. package/tasks/test-as-user.md +52 -0
  209. package/tasks/ux-create-wireframe.md +54 -0
  210. package/tasks/ux-user-research.md +55 -0
  211. package/tasks/validate-next-story.md +61 -0
  212. package/tasks/validate-squad.md +55 -0
  213. package/tasks/verify-subtask.md +52 -0
  214. package/tasks/version-management.md +45 -0
  215. package/templates/README.md +47 -0
  216. package/templates/architecture-tmpl.md +115 -0
  217. package/templates/competitor-analysis-tmpl.md +87 -0
  218. package/templates/epic-tmpl.md +83 -0
  219. package/templates/front-end-spec-tmpl.md +110 -0
  220. package/templates/market-research-tmpl.md +98 -0
  221. package/templates/migration-plan-tmpl.md +92 -0
  222. package/templates/prd-tmpl.md +95 -0
  223. package/templates/project-brief-tmpl.md +100 -0
  224. package/templates/qa-verdict-tmpl.md +73 -0
  225. package/templates/rls-policies-tmpl.md +93 -0
  226. package/templates/schema-design-tmpl.md +107 -0
  227. package/templates/spec-tmpl.md +88 -0
  228. package/templates/squad/agent-dna-tmpl.md +72 -0
  229. package/templates/squad/chief-dna-tmpl.md +98 -0
  230. package/templates/squad/squad-task-tmpl.md +50 -0
  231. package/templates/squad/squad-yaml-tmpl.md +47 -0
  232. package/templates/story-tmpl.md +63 -0
@@ -0,0 +1,201 @@
1
+ ---
2
+ id: distributed-patterns-cheatsheet
3
+ domain: architecture
4
+ agents: [architect]
5
+ when: "ao escolher protocolos, comunicação e padrões de resiliência num sistema distribuído"
6
+ ---
7
+
8
+ # Distributed Patterns — cheatsheet de decisão "use X quando Y"
9
+
10
+ ## O problema
11
+
12
+ A maioria das arquiteturas distribuídas erra não por falta de conhecimento, mas por **default não justificado**: escolhe-se REST porque é o que se conhece, retry porque "é seguro", blue-green porque soa robusto — sem amarrar a escolha ao critério que a justifica. O resultado é previsível: gRPC interno que ninguém consegue debugar no browser, retries que amplificam um incidente em vez de absorvê-lo, rate limiter que deixa passar o dobro do limite na virada da janela, deploy que custa 2x sem precisar.
13
+
14
+ Este pack é uma **régua de decisão**. Cada item tem o **critério de escolha** (o "quando Y") e o **trade-off** que você está aceitando. Se você não consegue nomear o Y, não escolha o X — o default está te traindo.
15
+
16
+ ## Os princípios / o conhecimento
17
+
18
+ ### 1. Estilo de API — REST vs GraphQL vs gRPC vs WebSocket
19
+
20
+ A confusão comum é tratá-los como concorrentes. **São complementos.** O mapa mental sólido: REST para API pública, gRPC para serviço interno, GraphQL para necessidade de dados orientada ao cliente, WebSocket para tempo real bidirecional.
21
+
22
+ | Estilo | Transporte / formato | Use quando | Trade-off que você aceita |
23
+ |---|---|---|---|
24
+ | **REST** | HTTP/1.1 + JSON | API pública, integração externa, recursos CRUD, quando cache de infra (CDN/proxy) importa | Over/under-fetching; múltiplos round-trips para dados relacionados |
25
+ | **GraphQL** | HTTP + schema/query | Cliente decide o shape dos dados; agrega N fontes num gateway; payloads onde se pega só o necessário | Cache HTTP fraco (POST único); complexidade de servidor; risco de query cara N+1 |
26
+ | **gRPC** | HTTP/2 + Protobuf (binário, IDL) | Comunicação serviço-a-serviço interna, baixa latência, contratos fortes, streaming | **Não roda no browser** (XHR não fala HTTP/2 puro); binário não é human-readable; sem cache HTTP nativo |
27
+ | **WebSocket** | TCP full-duplex, conexão longa | Tempo real bidirecional (chat, presença, dados ao vivo) entre muitos clientes e o servidor | Conexão persistente consome recurso do servidor; escala horizontal exige sticky session / estado distribuído |
28
+
29
+ **Números reais para calibrar:**
30
+ - gRPC é tipicamente **5 a 10x mais rápido que REST** em benchmarks — vem do HTTP/2 (multiplexing de várias requests numa conexão) + Protobuf binário.
31
+ - WebSocket ganha em **latência para mensagens pequenas e frequentes** porque elimina o handshake repetido — exatamente onde REST sangra.
32
+
33
+ **Tells de escolha errada:**
34
+ - gRPC exposto direto ao browser → precisa de gRPC-Web/proxy; sinal de que REST ou GraphQL era o certo na borda.
35
+ - WebSocket para request-response pontual → você pagou conexão persistente por nada.
36
+ - GraphQL numa API com 3 endpoints estáveis → complexidade sem retorno; REST resolvia.
37
+
38
+ ### 2. API Gateway — a borda única
39
+
40
+ O gateway é o ponto de entrada que faz o que cada serviço não deve repetir: **autenticação/autorização, rate limiting, roteamento, agregação de respostas, terminação TLS, observabilidade**. Use quando há múltiplos serviços atrás de uma fachada e você quer cross-cutting concerns num lugar só.
41
+
42
+ | Responsabilidade | Por que no gateway, não no serviço |
43
+ |---|---|
44
+ | AuthN / AuthZ | Não duplicar verificação de token em N serviços |
45
+ | Rate limiting | Proteger o backend inteiro de um cliente abusivo |
46
+ | Roteamento / versionamento | Desacoplar URL pública da topologia interna |
47
+ | Agregação | Compor resposta de vários serviços (BFF) numa chamada |
48
+ | TLS termination / observabilidade | Ponto único de métricas, logs, tracing |
49
+
50
+ **Trade-off:** o gateway é um **single point of failure** e ponto de latência — exige HA e cuidado para não virar monolito disfarçado. Não coloque lógica de negócio nele.
51
+
52
+ ### 3. Síncrono vs Assíncrono
53
+
54
+ | Critério | Síncrono (request-response) | Assíncrono (mensagem/evento) |
55
+ |---|---|---|
56
+ | Acoplamento temporal | Forte — chamador espera | Fraco — fire-and-forget |
57
+ | Use quando | Precisa da resposta agora para continuar (consulta, validação) | Pode processar depois; desacoplar produtor e consumidor; absorver picos |
58
+ | Falha do downstream | Propaga imediatamente ao chamador | Absorvida pela fila; reprocessa depois |
59
+ | Trade-off | Cascata de latência e de falha entre serviços | Complexidade: eventual consistency, ordenação, idempotência obrigatória |
60
+
61
+ Regra prática: **toda cadeia síncrona longa é uma cascata de falha esperando acontecer.** Se a operação não precisa do resultado em linha, torne-a assíncrona.
62
+
63
+ ### 4. Message Queue vs Event Streaming
64
+
65
+ Ambos são assíncronos, mas resolvem coisas diferentes. Confundir os dois é um erro clássico.
66
+
67
+ | Dimensão | Message Queue (ex.: RabbitMQ, SQS) | Event Streaming (ex.: Kafka) |
68
+ |---|---|---|
69
+ | Modelo | Fila: mensagem **consumida e removida** | Log append-only: evento **retido e re-lido** |
70
+ | Consumidores | Tipicamente 1 consumidor por mensagem (work queue) | N consumidores independentes, cada um com seu offset |
71
+ | Replay | Não (mensagem some após ack) | Sim (re-processa do offset, retém por tempo/tamanho) |
72
+ | Ordem | Por fila | Por partição |
73
+ | Use quando | Distribuir tarefas/jobs, desacoplar work, balancear carga | Pipeline de eventos, event sourcing, múltiplos consumidores, auditoria/replay, alto throughput |
74
+ | Trade-off | Sem histórico; difícil ter múltiplas views do mesmo evento | Mais operacional (partições, offsets, retenção); ordem só dentro da partição |
75
+
76
+ **Por que Kafka é rápido (para citar):** escrita sequencial em disco (append-only), zero-copy no envio ao consumidor, e batching — evita o custo de I/O aleatório.
77
+
78
+ ### 5. Idempotência
79
+
80
+ Idempotência é a **pré-condição de qualquer retry seguro**. Operação idempotente = executá-la N vezes tem o mesmo efeito de executá-la uma vez. Sem isso, retry vira cobrança duplicada.
81
+
82
+ **Top casos onde aplicar idempotência:**
83
+ 1. **Pagamentos / cobrança** — retry não pode cobrar duas vezes.
84
+ 2. **Criação de recurso** (POST que cria pedido) — request duplicado não cria dois pedidos.
85
+ 3. **Consumo de mensagem** (at-least-once delivery) — a mesma mensagem pode chegar 2x.
86
+ 4. **Webhooks** — provedores reenviam em caso de timeout.
87
+ 5. **Retries automáticos** entre serviços.
88
+ 6. **Operações disparadas por usuário** (double-click no "Comprar").
89
+
90
+ **Como implementar:** **idempotency key** — o cliente envia um ID único (header `Idempotency-Key`); o servidor guarda o resultado da primeira execução e, em requests repetidos com a mesma key, retorna o resultado cacheado em vez de re-executar.
91
+
92
+ ### 6. Retries — estratégias
93
+
94
+ Retry resolve **falha transitória** (timeout momentâneo, blip de rede). Não resolve serviço caído — para isso é circuit breaker.
95
+
96
+ | Estratégia | Comportamento | Risco |
97
+ |---|---|---|
98
+ | **Retry imediato** | Tenta de novo na hora | Pode martelar serviço já estressado |
99
+ | **Intervalo fixo** | Espera X ms entre tentativas | Retries sincronizados de N clientes = thundering herd |
100
+ | **Exponential backoff** | Atraso cresce a cada tentativa (1s, 2s, 4s, 8s…) | Reduz pressão, mas ainda sincroniza se todos começam juntos |
101
+ | **Backoff + jitter** | Backoff exponencial **com aleatoriedade** no intervalo | **Recomendado** — espalha os retries, evita retry storm / thundering herd |
102
+
103
+ **Regras invioláveis do retry:**
104
+ - **Limite o número** de tentativas (bounded) — retry infinito amplifica incidente.
105
+ - **Só retentar operação idempotente** — senão duplica efeito colateral.
106
+ - **Instrumente** — sem métrica, retries escondem o incidente em vez de absorvê-lo.
107
+ - **Só para transitório** — se a falha é persistente, retry só piora.
108
+
109
+ ### 7. Resiliência — Circuit Breaker
110
+
111
+ O circuit breaker protege contra **falha não-transitória** (serviço down): para de chamar o downstream para não desperdiçar recurso e dar fôlego pra ele se recuperar. Três estados:
112
+
113
+ | Estado | Comportamento | Transição |
114
+ |---|---|---|
115
+ | **CLOSED** | Operação normal; todas as requests passam | Falhas acima do threshold → OPEN |
116
+ | **OPEN** | Bloqueia tudo; **falha imediata** sem chamada de rede | Após timeout configurado (tipicamente 30s a poucos minutos) → HALF-OPEN |
117
+ | **HALF-OPEN** | Deixa passar **1 ou poucas** requests de teste | Sucesso → CLOSED; falha → volta a OPEN e reinicia o timer |
118
+
119
+ **Circuit breaker vs Retry — quando cada um:**
120
+
121
+ | | Retry | Circuit Breaker |
122
+ |---|---|---|
123
+ | Trata | Falha **transitória** (blip momentâneo) | Falha **persistente** (serviço provavelmente down) |
124
+ | Efeito | Tenta de novo | Para de tentar para não piorar |
125
+ | Juntos | Retry **dentro** do CLOSED; o breaker corta o retry quando vira OPEN | Complementares, não substitutos |
126
+
127
+ Combine com **bulkhead** (isolar thread pools para que um downstream lento não consuma todas as threads) e **timeout** (toda chamada remota tem prazo).
128
+
129
+ ### 8. Resiliência — Rate Limiting (os 5 algoritmos)
130
+
131
+ Escolher o algoritmo errado custa caro: ou você deixa passar burst que derruba o backend (boundary exploit), ou gasta memória demais para precisão que não precisa.
132
+
133
+ | Algoritmo | Como funciona | Use quando | Trade-off |
134
+ |---|---|---|---|
135
+ | **Token Bucket** | Bucket enche tokens a taxa fixa; cada request consome 1; bucket = capacidade de burst | **Default para API pública** — modela burst separado da taxa sustentada; permite picos controlados reais | Precisa afinar tamanho do bucket vs taxa de refill |
136
+ | **Leaky Bucket** | Fila drena a taxa **constante**; request entra na fila, rejeitado se cheia | Saída suave e constante, independente do burst de entrada (proteger downstream que odeia picos) | Enfileira → adiciona latência; descarta na fila cheia |
137
+ | **Fixed Window Counter** | Conta requests por janela fixa (ex.: por minuto) | Simplicidade máxima; throttle de login, limites básicos, serviço interno onde aproximação serve | **Boundary exploit**: na virada da janela pode passar até 2x o limite |
138
+ | **Sliding Window Log** | Guarda timestamp **exato** de cada request recente | Precisão máxima e auditoria — pagamento, autenticação, endpoint que exige contagem exata | Maior custo de memória e operação (guarda cada timestamp) |
139
+ | **Sliding Window Counter** | Aproxima a janela deslizante ponderando janela atual + anterior | **Melhor compromisso em escala distribuída** — reduz o boundary burst com custo baixo, alto throughput | Aproximação (não tão exato quanto o log), mas bom o suficiente |
140
+
141
+ **Regra de bolso:** API pública → **token bucket**. Precisa de exatidão/auditoria → **sliding window log**. Escala distribuída com eficiência → **sliding window counter**. Saída precisa ser perfeitamente suave → **leaky bucket**. Só simplicidade e tolera burst de borda → **fixed window**.
142
+
143
+ ### 9. Estratégias de Deploy
144
+
145
+ | Estratégia | Como | Downtime | Rollback | Risco | Custo infra | Complexidade | Use quando |
146
+ |---|---|---|---|---|---|---|---|
147
+ | **Rolling** | Substitui instâncias aos poucos | Não | **Lento** | Médio | 1x–1.25x | Baixa | Bug fixes de rotina; default barato |
148
+ | **Blue-Green** | Dois ambientes idênticos; troca **todo** o tráfego de uma vez | Não | **Instantâneo** | Baixo | **2x** | Média | Patch crítico de segurança; release que exige rollback imediato |
149
+ | **Canary** | Roteia % pequeno (ex.: 25%) pro novo, observa, então expande | Não | **Rápido** | Baixo | 1x–1.1x | **Alta** | Release de feature grande; quer validar com tráfego real antes do full rollout |
150
+ | **A/B Testing** | Variantes para medir comportamento (experimento, não mitigação) | Não | Rápido | **Maior** (sem rollback por erro/latência automático) | 1x–1.1x | Média | Experimento de produto; ambas versões já estáveis |
151
+ | **Shadow** | Espelha tráfego real pro novo **sem retornar resposta ao usuário** | Não | N/A | Nenhum (usuário não afetado) | 2x | Alta | Validar código novo com workload real sem impacto no usuário |
152
+
153
+ **Feature flags são ortogonais:** desacoplam **deploy de release**. Você faz deploy do código com qualquer estratégia, mas mantém a feature desligada e liga via config — gradualmente, sem novo deploy.
154
+
155
+ **Critério de escolha resumido:** bug fix rotineiro → rolling; feature grande → canary; patch crítico que precisa de reversão instantânea → blue-green; quer medir comportamento → A/B; quer testar sob carga real sem risco → shadow.
156
+
157
+ ## Checklist
158
+
159
+ Antes de fechar a arquitetura, responda — cada "não" é um default não justificado a revisar:
160
+
161
+ - [ ] Para cada API, eu sei nomear **por que** REST/GraphQL/gRPC/WebSocket e não o outro?
162
+ - [ ] Tem gRPC que precisa ser consumido pelo browser? (vai precisar de proxy/gRPC-Web)
163
+ - [ ] Cross-cutting concerns (auth, rate limit, TLS) estão no **gateway**, não duplicados nos serviços?
164
+ - [ ] As cadeias síncronas longas foram revisadas — o que pode ser assíncrono virou assíncrono?
165
+ - [ ] A escolha fila vs streaming bate com a necessidade de **replay / múltiplos consumidores**?
166
+ - [ ] Toda operação retentável é **idempotente** (idempotency key onde há efeito colateral)?
167
+ - [ ] Os retries têm **limite**, usam **backoff + jitter**, e são **só para falha transitória**?
168
+ - [ ] Chamadas a downstream têm **timeout + circuit breaker** (e bulkhead onde compartilham pool)?
169
+ - [ ] O algoritmo de rate limiting foi escolhido pelo critério (token bucket default; log p/ exatidão; counter p/ escala)?
170
+ - [ ] A estratégia de deploy bate com o **custo de rollback** aceitável (instantâneo → blue-green; gradual → canary)?
171
+ - [ ] Feature flags separam **deploy de release** onde faz sentido?
172
+
173
+ ## Tabela de decisão "use X quando Y"
174
+
175
+ | Decisão (X) | Escolha quando (Y) | Trade-off aceito |
176
+ |---|---|---|
177
+ | **REST** | API pública/externa, CRUD, cache de CDN importa | Over/under-fetching, round-trips |
178
+ | **GraphQL** | Cliente dita o shape; agrega N fontes | Cache HTTP fraco, risco N+1 |
179
+ | **gRPC** | Serviço↔serviço interno, baixa latência, contrato forte | Sem browser nativo, binário, sem cache HTTP |
180
+ | **WebSocket** | Tempo real bidirecional, muitas conexões vivas | Recurso por conexão, sticky sessions na escala |
181
+ | **API Gateway** | N serviços atrás de uma borda com cross-cutting concerns | SPOF + latência; exige HA |
182
+ | **Assíncrono** | Não precisa da resposta em linha; absorver picos | Eventual consistency, idempotência obrigatória |
183
+ | **Message Queue** | Distribuir jobs, 1 consumidor por mensagem | Sem replay/histórico |
184
+ | **Event Streaming** | Múltiplos consumidores, replay, auditoria, alto throughput | Ops de partição/offset/retenção |
185
+ | **Idempotency key** | Qualquer operação com efeito colateral que pode ser retentada | Estado extra para guardar resultados |
186
+ | **Backoff + jitter** | Retry de falha transitória sob concorrência | Latência adicional na recuperação |
187
+ | **Circuit Breaker** | Downstream provavelmente down (falha persistente) | Falha rápida temporária enquanto OPEN |
188
+ | **Token Bucket** | Rate limit de API pública com burst real | Afinar bucket vs refill |
189
+ | **Sliding Window Log** | Rate limit com exatidão/auditoria (pagamento, auth) | Custo de memória por timestamp |
190
+ | **Sliding Window Counter** | Rate limit em escala distribuída eficiente | Aproximação (não exato) |
191
+ | **Leaky Bucket** | Saída precisa ser constante/suave pro downstream | Latência de fila; descarte na fila cheia |
192
+ | **Fixed Window** | Throttle simples, tolera burst de borda | Boundary exploit (até 2x na virada) |
193
+ | **Rolling deploy** | Bug fix de rotina, custo baixo | Rollback lento |
194
+ | **Blue-Green** | Patch crítico, rollback instantâneo necessário | Custo de infra 2x |
195
+ | **Canary** | Feature grande, validar com tráfego real | Alta complexidade de pipeline |
196
+ | **Shadow** | Testar sob carga real sem risco ao usuário | Custo 2x, sem resposta ao usuário |
197
+ | **Feature flags** | Separar deploy de release; rollout gradual por config | Gestão de flags / débito se não limpar |
198
+
199
+ ---
200
+
201
+ > **Fonte de referência:** ByteByteGo — *system-design-101* (github.com/ByteByteGoHq/system-design-101), seções "SOAP vs REST vs GraphQL vs RPC", "What is gRPC?", "API Gateway 101", "Types of Message Queues", "Kafka 101 / Why is Kafka Fast?", "Top 6 Cases to Apply Idempotency", "Retry Strategies for System Failures", "Resiliency Patterns", "Top 5 Most-Used Deployment Strategies". Números e critérios consolidados com fontes públicas de engenharia (rate limiting, circuit breaker, deployment trade-offs).
@@ -0,0 +1,355 @@
1
+ ---
2
+ id: saas-subscription-blueprint
3
+ domain: architecture
4
+ agents: [architect]
5
+ when: "ao construir um SaaS que cobra assinatura (do zero ao faturamento)"
6
+ ---
7
+
8
+ # SaaS de assinatura — do zero ao primeiro faturamento
9
+
10
+ Blueprint extraído do `nextjs/saas-starter` (Vercel) — o starter oficial com Next.js (App Router),
11
+ Postgres, Drizzle, Stripe e shadcn/ui. Tudo aqui é **grounded no código real** do template, não em
12
+ "boas práticas" abstratas. O objetivo é dar ao arquiteto o caminho concreto: schema, fluxo de cobrança,
13
+ auth, RBAC e os pontos onde o starter falha de propósito (e você não pode copiar cego).
14
+
15
+ ## O problema
16
+
17
+ "Fazer um SaaS" parece um épico, mas o caminho comercial mínimo é estreito e conhecido: **alguém se
18
+ cadastra, vira dono de um team, escolhe um plano, paga no Stripe, e o estado da assinatura volta pro seu
19
+ banco via webhook.** A maioria das tentativas erra em um de quatro lugares:
20
+
21
+ 1. **Modelam billing no usuário, não no team.** Aí quando o cliente quer convidar um colega, o plano não
22
+ acompanha — assinatura é por *conta de cobrança* (team), não por pessoa.
23
+ 2. **Confiam no redirect de sucesso do Checkout pra liberar acesso.** O redirect é cosmético e burlável;
24
+ a fonte de verdade do estado da assinatura é **o webhook**, não a URL de retorno.
25
+ 3. **Reinventam o portal de billing** (trocar cartão, cancelar, fazer upgrade) — quando o Stripe Customer
26
+ Portal já faz isso hospedado e PCI-compliant.
27
+ 4. **Esquecem o RBAC.** Qualquer membro consegue remover o dono ou convidar gente. O próprio starter tem
28
+ esse buraco — é o tell número 1 de SaaS copiado de template.
29
+
30
+ Este pack resolve os quatro.
31
+
32
+ ## O conhecimento
33
+
34
+ ### 1. Modelagem: a unidade de cobrança é o `team`, não o `user`
35
+
36
+ O schema real do starter (Drizzle / Postgres). Note onde os campos do Stripe moram — **em `teams`**:
37
+
38
+ ```ts
39
+ // users — identidade e auth
40
+ users {
41
+ id serial PK
42
+ name varchar(100)
43
+ email varchar(255) NOT NULL UNIQUE
44
+ passwordHash text NOT NULL
45
+ role varchar(20) NOT NULL DEFAULT 'member' // role GLOBAL, não de team
46
+ createdAt timestamp NOT NULL DEFAULT now()
47
+ updatedAt timestamp NOT NULL DEFAULT now()
48
+ deletedAt timestamp // soft delete
49
+ }
50
+
51
+ // teams — a CONTA DE COBRANÇA. Stripe vive aqui.
52
+ teams {
53
+ id serial PK
54
+ name varchar(100) NOT NULL
55
+ createdAt timestamp NOT NULL DEFAULT now()
56
+ updatedAt timestamp NOT NULL DEFAULT now()
57
+ stripeCustomerId text UNIQUE // 1 customer Stripe por team
58
+ stripeSubscriptionId text UNIQUE
59
+ stripeProductId text
60
+ planName varchar(50)
61
+ subscriptionStatus varchar(20) // espelho do status do Stripe
62
+ }
63
+
64
+ // teamMembers — a relação N:N + a role DE TEAM (esta é a que importa pro RBAC)
65
+ teamMembers {
66
+ id serial PK
67
+ userId integer NOT NULL FK -> users.id
68
+ teamId integer NOT NULL FK -> teams.id
69
+ role varchar(50) NOT NULL // 'owner' | 'member'
70
+ joinedAt timestamp NOT NULL DEFAULT now()
71
+ }
72
+
73
+ // invitations — convite pendente por e-mail, aceito no signup
74
+ invitations {
75
+ id serial PK
76
+ teamId integer NOT NULL FK -> teams.id
77
+ email varchar(255) NOT NULL
78
+ role varchar(50) NOT NULL
79
+ invitedBy integer NOT NULL FK -> users.id
80
+ invitedAt timestamp NOT NULL DEFAULT now()
81
+ status varchar(20) NOT NULL DEFAULT 'pending' // pending | accepted
82
+ }
83
+
84
+ // activityLogs — trilha de auditoria append-only
85
+ activityLogs {
86
+ id serial PK
87
+ teamId integer NOT NULL FK -> teams.id
88
+ userId integer FK -> users.id // nullable (eventos do sistema)
89
+ action text NOT NULL // valor do enum ActivityType
90
+ timestamp timestamp NOT NULL DEFAULT now()
91
+ ipAddress varchar(45) // 45 = caber IPv6
92
+ }
93
+ ```
94
+
95
+ **Decisão concreta a internalizar:** existem **duas roles** e elas não são a mesma coisa.
96
+ `users.role` é global (útil pra super-admin do produto). `teamMembers.role` é por team (`owner`/`member`)
97
+ — **é esta que governa quem pode convidar, remover e gerenciar billing.** Confundir as duas é como o RBAC
98
+ quebra silenciosamente.
99
+
100
+ O enum de auditoria (`ActivityType`), com os 10 eventos que o starter rastreia:
101
+
102
+ ```
103
+ SIGN_UP · SIGN_IN · SIGN_OUT · UPDATE_PASSWORD · UPDATE_ACCOUNT · DELETE_ACCOUNT
104
+ CREATE_TEAM · INVITE_TEAM_MEMBER · REMOVE_TEAM_MEMBER · ACCEPT_INVITATION
105
+ ```
106
+
107
+ ### 2. Cobrança: Checkout (hospedado) → webhook (fonte de verdade) → Customer Portal
108
+
109
+ O fluxo tem três peças, e cada uma tem uma responsabilidade que não se mistura.
110
+
111
+ **a) `createCheckoutSession` — manda o cliente pro Checkout hospedado do Stripe.** Parâmetros reais:
112
+
113
+ ```ts
114
+ stripe.checkout.sessions.create({
115
+ mode: 'subscription', // assinatura, não pagamento único
116
+ payment_method_types: ['card'],
117
+ line_items: [{ price: priceId, quantity: 1 }],
118
+ subscription_data: { trial_period_days: 14 }, // trial sem cartão preso na hora
119
+ allow_promotion_codes: true, // cupom na própria tela do Stripe
120
+ success_url: `${baseUrl}/api/stripe/checkout?session_id={CHECKOUT_SESSION_ID}`,
121
+ cancel_url: `${baseUrl}/pricing`,
122
+ // ...client_reference_id / customer = vínculo com o team
123
+ })
124
+ ```
125
+ Se o usuário não está logado, o starter **redireciona pro sign-up antes** — não dá pra comprar anônimo
126
+ porque a assinatura precisa de um team pra ancorar.
127
+
128
+ **b) Webhook `POST /api/stripe/webhook` — a ÚNICA fonte de verdade do estado da assinatura.**
129
+ Verificação de assinatura obrigatória (senão qualquer um forja um POST e ganha plano premium):
130
+
131
+ ```ts
132
+ const payload = await request.text(); // RAW body, não parseado
133
+ const signature = request.headers.get('stripe-signature') as string;
134
+ let event: Stripe.Event;
135
+ try {
136
+ event = stripe.webhooks.constructEvent(
137
+ payload, signature, process.env.STRIPE_WEBHOOK_SECRET!
138
+ );
139
+ } catch (err) {
140
+ return NextResponse.json(
141
+ { error: 'Webhook signature verification failed.' }, { status: 400 }
142
+ );
143
+ }
144
+
145
+ switch (event.type) {
146
+ case 'customer.subscription.updated':
147
+ case 'customer.subscription.deleted':
148
+ await handleSubscriptionChange(event.data.object as Stripe.Subscription);
149
+ break;
150
+ // outros eventos: loga e ignora (não falha)
151
+ }
152
+ return NextResponse.json({ received: true }); // 200 sempre que processou
153
+ ```
154
+
155
+ `handleSubscriptionChange` traduz o status do Stripe pro seu banco:
156
+
157
+ | Status do Stripe | O que grava em `teams` |
158
+ |---|---|
159
+ | `active` ou `trialing` | preenche `stripeSubscriptionId`, `stripeProductId`, `planName`, `subscriptionStatus` |
160
+ | `canceled` ou `unpaid` | **zera** (`null`) `stripeSubscriptionId`, `stripeProductId`, `planName` |
161
+
162
+ **Por que isto importa:** o redirect de `success_url` é só UX. O acesso premium do cliente deve ser
163
+ decidido lendo `teams.subscriptionStatus` — que **só** é escrito pelo webhook. Em dev, o webhook chega via
164
+ `stripe listen --forward-to localhost:3000/api/stripe/webhook` (o CLI te dá o `whsec_...` pro
165
+ `STRIPE_WEBHOOK_SECRET`).
166
+
167
+ **c) `createCustomerPortalSession` — NÃO construa telas de billing.** O starter cria uma configuração de
168
+ portal hospedado do Stripe com:
169
+ - upgrade/downgrade de plano (troca de price, quantidade, cupom);
170
+ - atualização de método de pagamento;
171
+ - cancelamento com **motivo rastreado** (5 opções: `too_expensive`, `missing_features`,
172
+ `switched_service`, `unused`, `other`);
173
+ - proração habilitada.
174
+
175
+ Você manda o cliente pro portal e ele volta; o webhook `customer.subscription.updated` sincroniza o
176
+ resultado. Zero tela de cartão no seu código → zero escopo PCI no seu lado.
177
+
178
+ Leitura de catálogo (pra montar a pricing page a partir do Stripe, não hardcoded):
179
+ `getStripePrices()` retorna `{ id, productId, unitAmount, currency, interval, trialPeriodDays }`;
180
+ `getStripeProducts()` retorna `{ id, name, description, defaultPriceId }`.
181
+
182
+ ### 3. Auth: JWT em cookie HttpOnly + middleware que renova a sessão
183
+
184
+ Sem provider externo de auth. Sessão é um JWT assinado (`jose`, `HS256`) guardado num cookie:
185
+
186
+ ```ts
187
+ // session.ts
188
+ const SALT_ROUNDS = 10; // bcryptjs
189
+ hashPassword(pw) -> bcrypt hash
190
+ comparePasswords(pw, h) -> bool
191
+
192
+ signToken(payload) -> new SignJWT(payload)
193
+ .setProtectedHeader({ alg: 'HS256' })
194
+ .setIssuedAt()
195
+ .setExpirationTime('1 day from now')
196
+ .sign(key) // key = process.env.AUTH_SECRET
197
+ verifyToken(token) -> jwtVerify(token, key) // valida HS256
198
+
199
+ // cookie de sessão:
200
+ cookies().set('session', token, {
201
+ httpOnly: true, // JS do browser NÃO lê -> mitiga XSS roubar token
202
+ secure: true,
203
+ sameSite: 'lax', // mitiga CSRF mantendo navegação top-level
204
+ expires: <24h>,
205
+ })
206
+ ```
207
+
208
+ O **middleware** protege rotas e **renova o token a cada GET** (sessão deslizante de 24h):
209
+
210
+ ```ts
211
+ // middleware.ts
212
+ const protectedRoutes = '/dashboard';
213
+
214
+ const sessionCookie = request.cookies.get('session');
215
+ const isProtectedRoute = pathname.startsWith(protectedRoutes);
216
+
217
+ if (isProtectedRoute && !sessionCookie) {
218
+ return NextResponse.redirect(new URL('/sign-in', request.url));
219
+ }
220
+
221
+ // em GET com sessão válida: re-assina com nova expiração (+24h) e regrava o cookie
222
+ // se verifyToken falhar: deleta o cookie e redireciona protegidas pro /sign-in
223
+
224
+ export const config = {
225
+ matcher: ['/((?!api|_next/static|_next/image|favicon.ico).*)'],
226
+ runtime: 'nodejs', // jose precisa de Node, não Edge runtime
227
+ };
228
+ ```
229
+
230
+ **Decisão concreta:** o middleware faz só o *gate grosso* (tem cookie? rota protegida?). A autorização
231
+ fina (este usuário pode fazer *esta* ação?) **não** mora no middleware — mora na Server Action. Misturar os
232
+ dois é o erro clássico.
233
+
234
+ ### 4. Server Actions: validação + autenticação como wrappers compostos
235
+
236
+ O starter encapsula o boilerplate de toda action em três wrappers. Use sempre o mais restritivo que serve:
237
+
238
+ ```ts
239
+ type ActionState = { error?: string; success?: string; [key: string]: any };
240
+
241
+ // 1. valida FormData contra um schema zod
242
+ validatedAction(schema, async (data, formData) => { ... })
243
+
244
+ // 2. valida + EXIGE usuário autenticado (injeta `user`)
245
+ validatedActionWithUser(schema, async (data, formData, user) => { ... })
246
+
247
+ // 3. exige user + carrega o team (injeta `team`, redireciona se não logado)
248
+ withTeam(async (formData, team) => { ... })
249
+ ```
250
+
251
+ Padrão de validação real (`zod` + `safeParse`, devolve a 1ª mensagem de erro):
252
+
253
+ ```ts
254
+ const result = schema.safeParse(Object.fromEntries(formData));
255
+ if (!result.success) return { error: result.error.errors[0].message };
256
+ ```
257
+
258
+ ### 5. O buraco de RBAC que você NÃO pode copiar
259
+
260
+ Auditando as actions reais: `removeTeamMember` e `inviteTeamMember` usam `validatedActionWithUser` e
261
+ checam **apenas se o caller pertence a um team** — **não checam se ele é `owner`.** Isso significa que,
262
+ no template como está, **qualquer `member` pode remover qualquer pessoa (inclusive o dono) ou convidar
263
+ gente.** É um tell de SaaS feito de template cru.
264
+
265
+ O fix concreto que o arquiteto deve impor antes de qualquer mutação sensível:
266
+
267
+ ```ts
268
+ // dentro da action, depois de carregar a membership do caller:
269
+ const callerMembership = await getTeamMembership(user.id, teamId);
270
+ if (callerMembership?.role !== 'owner') {
271
+ return { error: 'Apenas o dono do time pode gerenciar membros.' };
272
+ }
273
+ ```
274
+
275
+ E toda ação sensível (convidar, remover, mudar plano) deve gravar em `activityLogs` via `logActivity`:
276
+
277
+ ```ts
278
+ async function logActivity(teamId, userId, type: ActivityType, ipAddress?) {
279
+ if (teamId == null) return;
280
+ await db.insert(activityLogs).values({
281
+ teamId, userId, action: type, ipAddress: ipAddress ?? '',
282
+ });
283
+ }
284
+ ```
285
+
286
+ ### 6. O fluxo de convite (signup que aceita invite)
287
+
288
+ Convite é por e-mail com `status: 'pending'`. No signup, se vier `inviteId`, a action procura o convite
289
+ **casando id + email + status pending**, e só então herda a role do convite e marca `accepted`:
290
+
291
+ ```ts
292
+ const [invitation] = await db.select().from(invitations).where(and(
293
+ eq(invitations.id, parseInt(inviteId)),
294
+ eq(invitations.email, email),
295
+ eq(invitations.status, 'pending'),
296
+ )).limit(1);
297
+
298
+ if (invitation) {
299
+ userRole = invitation.role; // herda role do convite
300
+ await db.update(invitations).set({ status: 'accepted' })
301
+ .where(eq(invitations.id, invitation.id));
302
+ } else {
303
+ // sem convite válido -> cria team novo e vira 'owner'
304
+ }
305
+ ```
306
+ Sem convite válido, o usuário vira `owner` de um team novo. O casamento por e-mail impede aceitar convite
307
+ de outra pessoa.
308
+
309
+ ## Checklist
310
+
311
+ - [ ] Os campos `stripeCustomerId / stripeSubscriptionId / planName / subscriptionStatus` estão em
312
+ **`teams`**, não em `users`?
313
+ - [ ] O acesso premium é decidido lendo `teams.subscriptionStatus` (escrito pelo webhook) e **não** pelo
314
+ redirect de sucesso do Checkout?
315
+ - [ ] O webhook verifica a assinatura com `stripe.webhooks.constructEvent` usando o **raw body** e
316
+ `STRIPE_WEBHOOK_SECRET`, retornando 400 se falhar?
317
+ - [ ] O webhook trata `customer.subscription.updated` **e** `customer.subscription.deleted`, e
318
+ **zera** os campos no `canceled`/`unpaid`?
319
+ - [ ] Trocar cartão / cancelar / upgrade vão pro **Customer Portal** hospedado (zero tela de cartão no
320
+ seu código)?
321
+ - [ ] O cookie de sessão é `httpOnly: true`, `secure: true`, `sameSite: 'lax'`, e o JWT é `HS256` com
322
+ `AUTH_SECRET`?
323
+ - [ ] O middleware renova o token nos GETs (sessão deslizante) e usa `runtime: 'nodejs'`?
324
+ - [ ] A autorização **fina** mora na Server Action (não no middleware), via wrappers
325
+ `validatedActionWithUser` / `withTeam`?
326
+ - [ ] Convidar / remover / mudar plano checam `teamMembers.role === 'owner'` **antes** de mutar?
327
+ (o starter NÃO faz — é o buraco a fechar)
328
+ - [ ] Toda ação sensível grava `activityLogs` com `ActivityType` e IP?
329
+ - [ ] Convite casa **id + email + status pending** antes de herdar a role?
330
+ - [ ] Em dev, o webhook chega via `stripe listen --forward-to .../api/stripe/webhook` com `whsec_`?
331
+
332
+ ## Tabela de decisão
333
+
334
+ | Você precisa de... | Faça assim (grounded no starter) | Não faça |
335
+ |---|---|---|
336
+ | Unidade de cobrança | Campos Stripe em `teams`; 1 `stripeCustomerId` por team | Billing por usuário individual |
337
+ | Liberar acesso premium | Ler `teams.subscriptionStatus` (sync do webhook) | Confiar no `success_url` do Checkout |
338
+ | Coletar pagamento | Stripe Checkout hospedado (`mode: 'subscription'`) | Formulário de cartão próprio (vira escopo PCI) |
339
+ | Trocar cartão / cancelar / upgrade | Stripe Customer Portal hospedado | Construir telas de billing do zero |
340
+ | Saber o estado real da assinatura | Webhook com `constructEvent` + `STRIPE_WEBHOOK_SECRET` | Polling da API ou estado no cliente |
341
+ | Trial | `subscription_data: { trial_period_days: 14 }` | Lógica de trial própria no seu banco |
342
+ | Sessão | JWT `HS256` (jose) em cookie `httpOnly`+`secure`+`sameSite:lax` | Token em `localStorage` (XSS lê) |
343
+ | Gate de rota | Middleware: tem cookie? rota protegida? renova no GET | Checar permissão fina no middleware |
344
+ | Permissão fina (quem pode o quê) | Na Server Action, cheque `teamMembers.role === 'owner'` | Assumir que membership == permissão |
345
+ | Validar input de action | `validatedAction*` + `zod.safeParse(Object.fromEntries(formData))` | Validação manual espalhada |
346
+ | Auditoria | `logActivity` append-only em `activityLogs` com `ActivityType` | Sem trilha (impossível investigar abuso) |
347
+ | Convite de membro | `invitations` por e-mail, `pending` → `accepted` casando id+email | Adicionar membro direto sem aceite |
348
+ | Runtime do middleware | `runtime: 'nodejs'` (jose não roda em Edge) | Edge runtime com `jose`/crypto Node |
349
+
350
+ ---
351
+
352
+ **Fonte:** [`nextjs/saas-starter`](https://github.com/nextjs/saas-starter) (Vercel) — Next.js App Router,
353
+ Postgres, Drizzle, Stripe, shadcn/ui. Schema, fluxo de billing, auth, middleware e actions extraídos do
354
+ código real do template. O buraco de RBAC em `removeTeamMember`/`inviteTeamMember` é uma característica
355
+ conhecida do starter — está aqui de propósito, como armadilha a corrigir.