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,273 @@
1
+ ---
2
+ id: schema-modeling-decisions
3
+ domain: data
4
+ agents: [data-engineer]
5
+ when: "ao modelar o schema de uma aplicação"
6
+ ---
7
+
8
+ # Decisões de modelagem de schema — normalizar, chaves, relacionamentos e árvores
9
+
10
+ A maioria dos schemas ruins não nasce de ignorância: nasce de defaults aplicados sem critério.
11
+ Coluna `id` serial em toda tabela porque o ORM gera; lista separada por vírgula porque "é só um
12
+ campo"; `parent_id` numa árvore porque era o primeiro padrão que apareceu. Bill Karwin catalogou
13
+ esses defaults como **antipatterns** — cada um tem uma cara reconhecível e uma correção concreta.
14
+ Este pack é a árvore de decisão de modelagem: quando normalizar, qual chave usar, como traduzir
15
+ cardinalidade em FK, e como modelar hierarquia sem cair no antipattern. A régua-mãe (Karwin):
16
+ **"A Primary Key is a constraint, not a data type"** — e o mesmo vale para todo o schema. Você
17
+ escolhe a estrutura pela restrição que ela garante, não pelo que o framework gera por padrão.
18
+
19
+ ## O problema (os tells de um schema gerado, não desenhado)
20
+
21
+ Reconheça para evitar. Se o schema tem 3+ destes, ele foi gerado, não modelado:
22
+
23
+ 1. **Jaywalking** — múltiplos valores numa coluna separados por vírgula (`tags = "sql,db,perf"`,
24
+ `account_ids = "12,34,56"`). Viola a 1NF. Karwin chama de *jaywalking* porque é "atravessar fora
25
+ da faixa" para evitar criar a tabela de interseção (*intersection*).
26
+ 2. **Multicolumn Attributes** — `tag1, tag2, tag3` em colunas separadas. Mesmo problema da
27
+ Jaywalking, só que na horizontal: como você consulta "todas as linhas com a tag X" sem repetir
28
+ `WHERE tag1=X OR tag2=X OR tag3=X`?
29
+ 3. **ID Required** — coluna `id` serial em *toda* tabela por reflexo, inclusive onde uma chave
30
+ natural ou composta seria melhor (sobretudo em tabelas de interseção).
31
+ 4. **Keyless Entry** — relacionamentos sem `FOREIGN KEY`. A integridade vira responsabilidade da
32
+ aplicação, e qualquer script externo (migração, import, job) gera linhas órfãs.
33
+ 5. **Naive Trees** — hierarquia modelada só com `parent_id` (adjacency list) sem pensar no padrão
34
+ de consulta. "Pegar todos os descendentes" vira N joins ou recursão na aplicação.
35
+ 6. **EAV (Entity-Attribute-Value)** — tabela genérica `entity_id | attribute_name | value` para
36
+ "schema flexível". Você perde tipo, `NOT NULL`, FK e qualquer consulta sã.
37
+ 7. **Polymorphic Associations** — uma FK que aponta para "várias tabelas" via coluna
38
+ discriminadora (`commentable_id` + `commentable_type`). SQL não suporta FK assim → sem
39
+ integridade referencial.
40
+
41
+ ## O conhecimento / os princípios
42
+
43
+ ### 1. Normalizar é o default. Desnormalizar é uma exceção justificada.
44
+
45
+ Normalize até a 3NF como ponto de partida. As três primeiras formas normais resolvem os
46
+ antipatterns mais comuns:
47
+
48
+ | Forma | Regra (Karwin) | Antipattern que elimina |
49
+ |---|---|---|
50
+ | **1NF** | Sem colunas repetidas nem valores separados por vírgula numa célula. Cada célula = um valor atômico. | Jaywalking, Multicolumn Attributes |
51
+ | **2NF** | Com chave composta, nenhuma coluna pode depender de só *parte* da chave. | Redundância em tabelas de junção |
52
+ | **3NF** | Proíbe armazenar dado não relacionado à PK e duplicado em outro lugar. | Anomalias de update |
53
+
54
+ > Karwin: ir além de 3NF/BCNF para 4NF+ é raramente necessário e custa muitos joins. 3NF é o alvo
55
+ > prático para a maioria das aplicações.
56
+
57
+ **Desnormalize só com critério explícito** (não "por performance" no chute). Critérios válidos:
58
+
59
+ - **Read-heavy comprovado** — a tabela é lida ordens de magnitude mais do que escrita, e o join
60
+ está medido como gargalo (não suposto).
61
+ - **Dado histórico/snapshot** — você *quer* congelar o valor no momento (preço da `order_item` na
62
+ hora da compra, não o preço atual do produto). Aqui "duplicar" o preço não é desnormalização — é
63
+ o modelo correto, porque a semântica é "valor naquele instante".
64
+ - **Agregado materializado** — `COUNT`/`SUM` pré-calculado (ex.: `post.comments_count`) com
65
+ trigger ou job mantendo a consistência.
66
+
67
+ Regra: se você não consegue nomear **qual escrita vai manter o dado redundante consistente**, não
68
+ desnormalize. A redundância sem dono é bug esperando acontecer.
69
+
70
+ ### 2. Jaywalking / Multicolumn → tabela de interseção (1NF)
71
+
72
+ O tell:
73
+
74
+ ```sql
75
+ -- ANTIPATTERN (Jaywalking): N:N escondido numa string
76
+ CREATE TABLE products (
77
+ product_id SERIAL PRIMARY KEY,
78
+ account_ids VARCHAR(100) -- "12,34,56"
79
+ );
80
+ -- "Quem é dono do produto 5?" → WHERE account_ids LIKE '%5%' (pega 15, 25, 51...)
81
+ -- COUNT/SUM/JOIN/FK: impossíveis. Limite de tamanho da coluna: bomba-relógio.
82
+ ```
83
+
84
+ A correção é sempre a **tabela de interseção**, com a chave composta dos dois lados como PK
85
+ (que também elimina duplicatas de graça):
86
+
87
+ ```sql
88
+ CREATE TABLE contacts (
89
+ product_id INT NOT NULL REFERENCES products(product_id),
90
+ account_id INT NOT NULL REFERENCES accounts(account_id),
91
+ PRIMARY KEY (product_id, account_id) -- chave composta natural, sem 'id' serial
92
+ );
93
+ ```
94
+
95
+ O mesmo vale para `tag1, tag2, tag3` (Multicolumn): vira `post_tags(post_id, tag_id)`.
96
+
97
+ ### 3. PK: surrogate (pseudokey) vs. natural vs. composta
98
+
99
+ Karwin: o antipattern **ID Required** não é "usar `id` serial" — é usar `id` serial *sem pensar*,
100
+ em toda tabela. A decisão real:
101
+
102
+ ```sql
103
+ -- Tabela de interseção: chave COMPOSTA NATURAL é melhor que 'id' serial.
104
+ -- Garante unicidade do par (impede duplicata) sem índice extra.
105
+ PRIMARY KEY (product_id, account_id)
106
+
107
+ -- Entidade de domínio com identificador natural confiável e estável:
108
+ CREATE TABLE countries (
109
+ iso_code CHAR(2) PRIMARY KEY, -- 'BR', 'US' — único, estável, significativo
110
+ name VARCHAR(80) NOT NULL
111
+ );
112
+
113
+ -- Entidade sem chave natural estável (usuário, pedido): surrogate é legítimo.
114
+ CREATE TABLE orders (
115
+ order_id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY
116
+ );
117
+ ```
118
+
119
+ Critérios de Karwin para **natural/composta**:
120
+ - O atributo é **genuinamente único** (verifique nos dados reais, não na esperança).
121
+ - É **estável** (não muda — CPF muda? email muda? então não é boa PK).
122
+ - Carrega **significado de domínio** que simplifica queries.
123
+
124
+ Critérios para **surrogate**:
125
+ - Não existe chave natural estável, OU a natural é larga/composta e referenciada por muitas FKs.
126
+ - A imutabilidade da PK importa (surrogate nunca muda, mesmo que o "negócio" mude).
127
+
128
+ > Nota de naming: Karwin recomenda nome descritivo (`comment_id`) para permitir `JOIN ... USING
129
+ > (comment_id)`. Equipes que abstraem o front com convenção `id` constante discordam — escolha uma
130
+ > convenção e mantenha. O antipattern é não decidir.
131
+
132
+ ### 4. Cardinalidade → FK (e sempre declare a FK)
133
+
134
+ Traduza a cardinalidade do domínio em estrutura. E declare `FOREIGN KEY` — **Keyless Entry** é
135
+ antipattern: sem a constraint, a integridade vira "torcer para a aplicação acertar", e qualquer
136
+ import/script externo gera órfãos.
137
+
138
+ | Cardinalidade | Onde mora a FK | Forma |
139
+ |---|---|---|
140
+ | **1:N** (um cliente, N pedidos) | Na tabela do lado "N" | `orders.customer_id REFERENCES customers` |
141
+ | **N:N** (produtos ↔ contas) | Tabela de interseção | `contacts(product_id, account_id)` com PK composta |
142
+ | **1:1** (user ↔ profile) | FK + `UNIQUE` (ou PK compartilhada) | `profiles.user_id UNIQUE REFERENCES users` |
143
+ | **0..1** (opcional) | FK `NULL`-able | `employees.manager_id NULL REFERENCES employees` |
144
+
145
+ Escolha a ação referencial de propósito (Karwin):
146
+
147
+ ```sql
148
+ order_id BIGINT REFERENCES orders(order_id)
149
+ ON DELETE CASCADE -- itens somem com o pedido (parte fraca da composição)
150
+ -- ON DELETE RESTRICT -- impede apagar o pai se há filhos (default seguro)
151
+ -- ON DELETE SET NULL -- desvincula sem apagar (ex.: manager saiu, subordinado fica)
152
+ ```
153
+
154
+ ### 5. Hierarquias → escolha o modelo pelo padrão de leitura/escrita (não use só `parent_id`)
155
+
156
+ **Naive Trees** = usar só adjacency list sem perguntar como a árvore é consultada. Os quatro
157
+ modelos de Karwin, com critério de escolha:
158
+
159
+ **a) Adjacency List** (`parent_id`) — o default honesto.
160
+
161
+ ```sql
162
+ CREATE TABLE comments (
163
+ comment_id SERIAL PRIMARY KEY,
164
+ parent_id INT REFERENCES comments(comment_id),
165
+ body TEXT
166
+ );
167
+ -- Mover subárvore = 1 UPDATE no parent_id. Inserir = trivial.
168
+ -- "Todos os descendentes" precisa de recursão (PostgreSQL 8.2+):
169
+ WITH RECURSIVE tree AS (
170
+ SELECT comment_id, parent_id FROM comments WHERE comment_id = 4
171
+ UNION ALL
172
+ SELECT c.comment_id, c.parent_id
173
+ FROM comments c JOIN tree t ON c.parent_id = t.comment_id
174
+ )
175
+ SELECT * FROM tree;
176
+ ```
177
+ Use quando: o banco suporta `WITH RECURSIVE`, inserts/moves são frequentes, e a profundidade das
178
+ leituras é moderada. Em Postgres moderno, é a escolha default razoável.
179
+
180
+ **b) Path Enumeration** (caminho materializado) — leitura por prefixo.
181
+
182
+ ```sql
183
+ ALTER TABLE comments ADD COLUMN path VARCHAR(255); -- '4/5/8/'
184
+ -- Todos os descendentes de 4 (sem recursão):
185
+ SELECT * FROM comments WHERE path LIKE '4/%';
186
+ ```
187
+ Use quando: leitura é dominante e por subárvore. Cuidado: mover uma subárvore exige reescrever o
188
+ `path` de todos os descendentes, e não há FK garantindo que o caminho aponta para nós reais.
189
+
190
+ **c) Nested Sets** (`left`/`right`) — leitura de subárvore ultrarrápida, escrita cara.
191
+
192
+ Cada nó guarda dois números (left, right); descendentes têm `left/right` *entre* os do ancestral.
193
+ Karwin: rápido para consultar subárvores, mas **inserir/mover é complexo** — quase todos os nós
194
+ precisam ter left/right renumerados. Use só quando a árvore é praticamente read-only (catálogo
195
+ estável) e você lê subárvores inteiras o tempo todo.
196
+
197
+ **d) Closure Table** — tabela separada com *uma linha por par ancestral→descendente*. O equilíbrio
198
+ de Karwin: rápido em todas as operações, ao custo de mais armazenamento.
199
+
200
+ ```sql
201
+ CREATE TABLE comment_tree (
202
+ ancestor INT NOT NULL REFERENCES comments(comment_id),
203
+ descendant INT NOT NULL REFERENCES comments(comment_id),
204
+ depth INT NOT NULL,
205
+ PRIMARY KEY (ancestor, descendant)
206
+ );
207
+ -- Inserir nó: linha self (depth 0) + uma linha para cada ancestral do pai.
208
+ INSERT INTO comment_tree (ancestor, descendant, depth)
209
+ SELECT ancestor, :new_id, depth + 1
210
+ FROM comment_tree WHERE descendant = :parent_id
211
+ UNION ALL SELECT :new_id, :new_id, 0;
212
+ -- Todos os descendentes: SELECT descendant FROM comment_tree WHERE ancestor = :id;
213
+ -- Linha de comando ascendente: SELECT ancestor FROM comment_tree WHERE descendant = :id ORDER BY depth DESC;
214
+ ```
215
+ Use quando: você precisa de consultas rápidas de ancestral *e* descendente, suporta atributos na
216
+ aresta (ex.: data de vigência), e as escritas são moderadas. É a escolha mais versátil do livro.
217
+
218
+ **e) `ltree` (PostgreSQL)** — path enumeration nativo, com operadores de árvore.
219
+
220
+ ```sql
221
+ CREATE EXTENSION ltree;
222
+ ALTER TABLE comments ADD COLUMN path ltree; -- 'root.4.5.8'
223
+ CREATE INDEX ON comments USING GIST (path);
224
+ SELECT * FROM comments WHERE path <@ '4'; -- todos os descendentes de 4
225
+ ```
226
+ Use quando: está em Postgres e quer materialized path "de fábrica". Limitações: o índice GIST tem
227
+ limite de tamanho de label (árvores muito profundas sofrem), e mover subárvore exige reescrever o
228
+ `path` de todos os descendentes.
229
+
230
+ ## Checklist (passe antes de aprovar o schema)
231
+
232
+ - [ ] Alguma coluna guarda lista separada por vírgula ou `campo1/campo2/campo3`? (Jaywalking /
233
+ Multicolumn → tabela de interseção)
234
+ - [ ] Toda relação tem `FOREIGN KEY` declarada com ação `ON DELETE`/`ON UPDATE` escolhida de
235
+ propósito? (Keyless Entry)
236
+ - [ ] Cada tabela de interseção usa **chave composta** dos dois lados como PK, não um `id` serial
237
+ desnecessário? (ID Required)
238
+ - [ ] Onde existe chave natural estável e única, ela foi considerada como PK em vez de surrogate
239
+ automático?
240
+ - [ ] Existe alguma FK "polimórfica" (`*_id` + `*_type`)? Se sim, refatorada para arco exclusivo ou
241
+ interseções por tipo?
242
+ - [ ] Existe tabela genérica `entity/attribute/value`? Se sim, substituída por colunas tipadas,
243
+ tabela por subtipo, ou JSON tipado?
244
+ - [ ] A hierarquia foi modelada pelo padrão de leitura/escrita real, não só `parent_id` por reflexo?
245
+ - [ ] Toda redundância (dado duplicado) tem um dono explícito — a escrita que a mantém consistente?
246
+
247
+ ## Tabela de decisão "use X quando Y"
248
+
249
+ | Use X | Quando Y |
250
+ |---|---|
251
+ | **Normalizar até 3NF** | Default. Sempre comece aqui. |
252
+ | **Desnormalizar** | Read-heavy *medido*, snapshot histórico, ou agregado materializado **com dono de consistência** |
253
+ | **Tabela de interseção** (PK composta) | Cardinalidade N:N — sempre, nunca lista CSV nem `tag1/tag2/tag3` |
254
+ | **PK surrogate** (`IDENTITY`/serial) | Sem chave natural estável; natural muito larga; imutabilidade da PK importa |
255
+ | **PK natural** | Atributo único, estável e significativo (`iso_code`, código de referência) |
256
+ | **PK composta** | Tabela de interseção (os dois FKs juntos) |
257
+ | **FK `ON DELETE CASCADE`** | Filho é parte fraca da composição (item do pedido some com o pedido) |
258
+ | **FK `ON DELETE RESTRICT`** | Default seguro: impede apagar pai com filhos |
259
+ | **FK `ON DELETE SET NULL`** | Vínculo opcional que pode existir sozinho (subordinado sem gestor) |
260
+ | **Adjacency List** (`parent_id`) | Postgres com `WITH RECURSIVE`; inserts/moves frequentes; profundidade moderada |
261
+ | **Path Enumeration / `ltree`** | Leitura por subárvore dominante; árvore não muito profunda; moves raros |
262
+ | **Nested Sets** | Árvore quase read-only; leitura de subárvore inteira é o caso crítico |
263
+ | **Closure Table** | Precisa de queries rápidas de ancestral E descendente; atributos na aresta; escrita moderada |
264
+ | **Colunas tipadas / tabela por subtipo** | Em vez de EAV — quando os atributos são conhecidos |
265
+ | **JSON/JSONB tipado** | Schema genuinamente dinâmico — em vez de EAV, mantendo o resto relacional |
266
+ | **Arco exclusivo** (parent table compartilhada) | Em vez de FK polimórfica — quando precisa de FK real |
267
+ | **Interseções por tipo** | Em vez de FK polimórfica — quando a 1:1 não precisa ser forçada |
268
+
269
+ ---
270
+
271
+ Fonte: Bill Karwin, *SQL Antipatterns: Avoiding the Pitfalls of Database Programming* (Pragmatic
272
+ Bookshelf) — capítulos Jaywalking, Multicolumn Attributes, Naive Trees, ID Required, Keyless Entry,
273
+ Entity-Attribute-Value, Polymorphic Associations; e a coletânea *awesome-database-design*.
@@ -0,0 +1,316 @@
1
+ ---
2
+ id: supabase-rls-patterns
3
+ domain: data
4
+ agents: [data-engineer]
5
+ when: "ao implementar segurança de dados com Row Level Security no Supabase/Postgres"
6
+ ---
7
+
8
+ # Supabase RLS — segurança no banco, não na aplicação
9
+
10
+ ## O problema
11
+
12
+ Com Supabase, qualquer cliente com a `anon` key fala direto com o Postgres via PostgREST. Não existe
13
+ "backend que filtra antes". Se você confia no `.eq('user_id', userId)` do cliente para isolar dados, a
14
+ segurança é uma sugestão: qualquer um troca o `userId` no DevTools e lê a tabela inteira. **A fronteira
15
+ de autorização é o banco.** RLS é o mecanismo que faz o próprio Postgres decidir, linha a linha, o que
16
+ cada `role`/usuário pode `select`/`insert`/`update`/`delete`.
17
+
18
+ Os tells de RLS feito por quem não conhece:
19
+
20
+ 1. **Tabela com RLS desabilitado** exposta no schema `public` — vazamento total via API.
21
+ 2. **`enable row level security` sem nenhuma policy** — ninguém lê nada (RLS nega por padrão), e o dev
22
+ "conserta" desabilitando RLS em vez de escrever a policy.
23
+ 3. **`auth.uid()` chamado direto na policy** (sem `(select …)`) — reavaliado **por linha**, derruba a
24
+ query de <1ms para segundos numa tabela de 100k linhas.
25
+ 4. **Policy sem `to authenticated`** — roda o predicado caro até para `anon`, que nunca deveria passar.
26
+ 5. **Policy baseada em `user_metadata`** — claim que o **próprio usuário edita**; vira escalonamento de
27
+ privilégio (o atacante se promove a admin).
28
+ 6. **`update` sem `select` correspondente** — o update "não funciona" e ninguém entende por quê.
29
+ 7. **Multi-tenant filtrando `tenant_id` só na app**, sem coluna `tenant_id` na policy nem índice — um
30
+ tenant lê dados do outro, e quando você adiciona a policy, a query fica lenta sem índice.
31
+
32
+ ## O conhecimento / os princípios
33
+
34
+ ### 1. Ligue RLS e conceda os grants — RLS nega por padrão
35
+
36
+ RLS só protege se estiver habilitado, e habilitar sem policy bloqueia tudo. Toda tabela em `public`
37
+ deve ter RLS ligado:
38
+
39
+ ```sql
40
+ alter table public.todos enable row level security;
41
+ ```
42
+
43
+ Os `grant` definem *quais operações o role pode tentar*; a policy define *quais linhas*. Os dois são
44
+ necessários:
45
+
46
+ ```sql
47
+ grant select on public.todos to anon;
48
+ grant select, insert, update, delete on public.todos to authenticated;
49
+ grant select, insert, update, delete on public.todos to service_role;
50
+ ```
51
+
52
+ > Sem policy, ninguém (exceto `service_role`/`bypassrls`) lê nada. A resposta certa para "ninguém lê" é
53
+ > **escrever a policy**, nunca desabilitar RLS.
54
+
55
+ ### 2. USING vs WITH CHECK — eles não são intercambiáveis
56
+
57
+ - **`using`** filtra as linhas **existentes** que a query pode ver/afetar (read-side).
58
+ - **`with check`** valida as linhas **novas/modificadas** que estão sendo gravadas (write-side).
59
+
60
+ Se você só define `using` num insert/update, não há validação do que entra. Regra por operação (direto
61
+ da doc do Supabase):
62
+
63
+ | Operação | `using` | `with check` |
64
+ |---|---|---|
65
+ | `select` | sempre | nunca |
66
+ | `insert` | nunca | sempre |
67
+ | `update` | quase sempre | sempre |
68
+ | `delete` | sempre | nunca |
69
+
70
+ Postgres **não aceita múltiplas operações num único `for`** — escreva **uma policy por operação**:
71
+
72
+ ```sql
73
+ -- SELECT: usa USING
74
+ create policy "Individuals can view their own todos."
75
+ on public.todos for select
76
+ to authenticated
77
+ using ( (select auth.uid()) = user_id );
78
+
79
+ -- INSERT: usa WITH CHECK (impede gravar linha de outro usuário)
80
+ create policy "Users can create their own todos."
81
+ on public.todos for insert
82
+ to authenticated
83
+ with check ( (select auth.uid()) = user_id );
84
+
85
+ -- UPDATE: USING (quais linhas pode tocar) + WITH CHECK (no que pode virar)
86
+ create policy "Users can update their own todos."
87
+ on public.todos for update
88
+ to authenticated
89
+ using ( (select auth.uid()) = user_id )
90
+ with check ( (select auth.uid()) = user_id );
91
+
92
+ -- DELETE: usa USING
93
+ create policy "Users can delete their own todos."
94
+ on public.todos for delete
95
+ to authenticated
96
+ using ( (select auth.uid()) = user_id );
97
+ ```
98
+
99
+ > **Armadilha:** `update` precisa de uma policy de `select` correspondente, senão o update "não
100
+ > funciona". O Postgres precisa enxergar a linha (`select`) para então atualizá-la.
101
+
102
+ ### 3. Sempre especifique o role com `to`
103
+
104
+ Sem `to`, a policy é avaliada para **todos os roles**, inclusive `anon` — que paga o custo de um
105
+ predicado que nunca vai passar. Com `to authenticated`, a avaliação para cedo para visitantes.
106
+
107
+ Ordem das cláusulas: `on <tabela>` → `for <operação>` → `to <roles>` → `using/with check`.
108
+
109
+ ```sql
110
+ -- leitura pública explícita
111
+ create policy "Public profiles are visible to everyone."
112
+ on public.profiles for select
113
+ to anon, authenticated
114
+ using ( true );
115
+ ```
116
+
117
+ Benchmark da doc (tabela 100k linhas): adicionar `to authenticated` numa query feita por `anon` cai de
118
+ **170ms para <0,1ms** — porque a policy nem roda.
119
+
120
+ ### 4. auth.uid() e auth.jwt() — e o claim que você NÃO pode confiar
121
+
122
+ - `auth.uid()` → `uuid` do usuário autenticado (vem do `sub` do JWT).
123
+ - `auth.jwt()` → claims completos do token.
124
+
125
+ Dentro do JWT existem dois objetos de metadata, e a diferença é de segurança, não de estilo:
126
+
127
+ | Claim | Origem | Confiável em RLS? |
128
+ |---|---|---|
129
+ | `app_metadata` (`raw_app_meta_data`) | gravado pelo backend/admin | **SIM** |
130
+ | `user_metadata` (`raw_user_meta_data`) | **editável pelo próprio usuário** | **NÃO — nunca** |
131
+
132
+ Usar `user_metadata` para autorização (ex.: `role`, `is_admin`, `tenant_id`) é uma vulnerabilidade: o
133
+ usuário muda o próprio claim e escala privilégio. Use sempre `app_metadata`:
134
+
135
+ ```sql
136
+ -- pertencimento a time via app_metadata (seguro)
137
+ create policy "User is in team"
138
+ on public.my_table for select
139
+ to authenticated
140
+ using ( team_id in (select auth.jwt() -> 'app_metadata' -> 'teams') );
141
+ ```
142
+
143
+ Enforçar MFA (assurance level) com policy **restritiva** (restritiva = AND com as permissivas):
144
+
145
+ ```sql
146
+ create policy "Restrict updates to MFA users."
147
+ on public.profiles
148
+ as restrictive
149
+ for update
150
+ to authenticated
151
+ using ( (select auth.jwt()->>'aal') = 'aal2' );
152
+ ```
153
+
154
+ Prefira `permissive` (default, combinam por OR) na maioria dos casos; use `restrictive` só quando
155
+ precisa de uma condição que se soma a **todas** as outras (como o gate de MFA acima).
156
+
157
+ ### 5. Multi-tenant: `tenant_id` na policy + índice obrigatório
158
+
159
+ Isolamento de tenant tem que viver na policy, com o `tenant_id` derivado do JWT (via `app_metadata`),
160
+ não passado pelo cliente:
161
+
162
+ ```sql
163
+ -- tenant_id vem do app_metadata; cliente não consegue forjar
164
+ create policy "Tenant isolation - select"
165
+ on public.invoices for select
166
+ to authenticated
167
+ using ( tenant_id = ((select auth.jwt()) -> 'app_metadata' ->> 'tenant_id')::uuid );
168
+
169
+ -- ÍNDICE é parte da policy, não opcional
170
+ create index idx_invoices_tenant_id on public.invoices using btree (tenant_id);
171
+ ```
172
+
173
+ Toda coluna comparada na policy (`user_id`, `tenant_id`) precisa de índice. Benchmark da doc: índice em
174
+ `user_id` numa tabela de 100k linhas leva a query de **171ms para <0,1ms** (>100x).
175
+
176
+ ### 6. A armadilha de performance nº 1: envolva funções em `(select …)`
177
+
178
+ Chamar `auth.uid()`, `auth.jwt()` ou qualquer função direto na policy faz o Postgres reavaliá-la **por
179
+ linha**. Envolver em `(select …)` dispara um **initPlan** que **cacheia o resultado uma vez por
180
+ statement** (válido porque o resultado não muda entre linhas):
181
+
182
+ ```sql
183
+ -- LENTO: auth.uid() reavaliado por linha
184
+ using ( auth.uid() = user_id )
185
+
186
+ -- RÁPIDO: (select …) cacheia via initPlan
187
+ using ( (select auth.uid()) = user_id )
188
+ ```
189
+
190
+ Vale para funções suas também:
191
+
192
+ ```sql
193
+ -- de:
194
+ using ( is_admin() or auth.uid() = user_id )
195
+ -- para:
196
+ using ( (select is_admin()) or (select auth.uid()) = user_id )
197
+ ```
198
+
199
+ Números reais da doc (tabela 100k linhas, antes → depois):
200
+
201
+ | Cenário | Antes | Depois |
202
+ |---|---|---|
203
+ | `auth.uid()=user_id` → `(select auth.uid())` | 179ms | 9ms |
204
+ | `is_admin()` com join → `(select is_admin())` | 11.000ms | 7ms |
205
+ | `has_role()=role` → `(select has_role())` | 178.000ms | 12ms |
206
+ | `team_id = any(...)` → versão envolvida | 173.000ms | 16ms |
207
+
208
+ ### 7. SECURITY DEFINER para quebrar recursão e otimizar join
209
+
210
+ Quando a policy precisa consultar **outra tabela** que **também tem RLS** (ex.: tabela de papéis/times),
211
+ você cai em recursão ou em joins caros. A solução é uma função `security definer` — ela roda com os
212
+ privilégios de quem **definiu** (dono), **ignorando RLS** dentro dela, então a policy a chama sem
213
+ recursão:
214
+
215
+ ```sql
216
+ create or replace function public.user_teams()
217
+ returns int[] as $$
218
+ begin
219
+ return array( select team_id from team_user where auth.uid() = user_id );
220
+ end;
221
+ $$ language plpgsql security definer;
222
+ ```
223
+
224
+ Na policy, envolva o retorno em `array(select …)` para o cache do initPlan:
225
+
226
+ ```sql
227
+ -- 1M linhas: =any(user_teams()) sem cache → timeout (>120s)
228
+ -- com array(select …) → 170ms; com índice → 2ms
229
+ using ( team_id = any( array(select public.user_teams()) ) );
230
+ ```
231
+
232
+ > `security definer` = roda como o dono (ignora RLS interno). `security invoker` = roda como quem chama
233
+ > (respeita RLS). Funções de autorização usadas em policies são `security definer` **de propósito** —
234
+ > mas blinde-as: `search_path` fixo e lógica mínima, porque elas furam o RLS por design.
235
+
236
+ Para **views** (Postgres 15+), o default seguro é `security_invoker` para a view respeitar a RLS das
237
+ tabelas-base:
238
+
239
+ ```sql
240
+ create view public.my_view
241
+ with (security_invoker = true)
242
+ as select ... ;
243
+ ```
244
+
245
+ ### 8. Inverta o join em vez de fazer a tabela-fonte cruzar a alvo
246
+
247
+ Joins na policy que partem da tabela protegida para a tabela de associação são caros. Inverta: filtre na
248
+ tabela de associação e compare a coluna local.
249
+
250
+ ```sql
251
+ -- LENTO (9.000ms): join source → target
252
+ using ( auth.uid() in (select user_id from team_user where team_user.team_id = invoices.team_id) )
253
+
254
+ -- RÁPIDO (20ms): inverte — filtra na associação, compara coluna local
255
+ using ( team_id in (select team_id from team_user where user_id = (select auth.uid())) )
256
+ ```
257
+
258
+ ### 9. Nunca confie no filtro do cliente — mas adicione-o por performance
259
+
260
+ O `.eq('user_id', userId)` no cliente **não é segurança** (RLS é). Mas adicioná-lo ajuda o planner:
261
+ combinar o filtro explícito com a RLS levou a query de **171ms para 9ms** na doc, porque o índice é
262
+ usado melhor. Regra: RLS garante a segurança; o `.eq()` é otimização redundante, não substituto.
263
+
264
+ ### 10. Bypass consciente: `service_role` e `bypassrls`
265
+
266
+ A `service_role` key e roles com `bypassrls` **ignoram toda RLS**. Use no servidor (jobs, webhooks,
267
+ migrações), **jamais no browser**. Expor a `service_role` no front é equivalente a desligar a RLS.
268
+
269
+ ```sql
270
+ alter role "background_worker" with bypassrls;
271
+ ```
272
+
273
+ ## Checklist
274
+
275
+ Antes de marcar uma tabela como segura — qualquer "não" é um vazamento ou um gargalo:
276
+
277
+ - [ ] `enable row level security` está ligado em **toda** tabela do schema `public`?
278
+ - [ ] Existe uma policy por operação (`select`/`insert`/`update`/`delete`) onde aquela operação é
279
+ permitida — e não só `select`?
280
+ - [ ] `insert`/`update` usam `with check` (não só `using`)?
281
+ - [ ] `update` tem uma policy de `select` correspondente?
282
+ - [ ] Toda policy especifica `to authenticated` (ou o role correto), nunca implícito?
283
+ - [ ] Autorização usa `app_metadata` / `auth.uid()` — **nunca** `user_metadata`?
284
+ - [ ] Toda chamada de `auth.uid()`/`auth.jwt()`/função está envolvida em `(select …)`?
285
+ - [ ] Toda coluna comparada na policy (`user_id`, `tenant_id`) tem **índice** btree?
286
+ - [ ] Multi-tenant: `tenant_id` vem do JWT (`app_metadata`), não do payload do cliente?
287
+ - [ ] Consultas a outras tabelas com RLS usam `security definer` (com `search_path` fixo) para evitar
288
+ recursão?
289
+ - [ ] Views usam `with (security_invoker = true)` (PG 15+)?
290
+ - [ ] A `service_role` key existe **apenas** no servidor, nunca no bundle do cliente?
291
+
292
+ ## Tabela de decisão "use X quando Y"
293
+
294
+ | Use… | Quando… |
295
+ |---|---|
296
+ | `using` | A condição decide quais **linhas existentes** ler/afetar (`select`, `delete`, lado-read do `update`) |
297
+ | `with check` | A condição valida **linhas novas/modificadas** sendo gravadas (`insert`, lado-write do `update`) |
298
+ | `(select auth.uid())` | **Sempre** — em qualquer policy que chame função; cacheia via initPlan (per-statement, não per-row) |
299
+ | `auth.uid() = user_id` | Isolamento simples por dono da linha (uma tabela, sem times/tenants) |
300
+ | `app_metadata` no `auth.jwt()` | Autorização por role/tenant/time — claim controlado pelo backend, não forjável |
301
+ | `user_metadata` | **Nunca** em autorização (usuário edita); só para preferências de UI sem efeito de segurança |
302
+ | Policy `permissive` (default) | Caso geral — múltiplas policies combinam por **OR** (acesso se qualquer uma passar) |
303
+ | Policy `restrictive` | Gate que deve valer junto com **todas** as outras por **AND** (ex.: exigir `aal2`/MFA) |
304
+ | `to authenticated` | Sempre que a regra só vale para logados — evita rodar predicado caro para `anon` |
305
+ | Índice btree na coluna | **Sempre** que a coluna aparece na policy (`user_id`, `tenant_id`) — ganho >100x |
306
+ | `security definer` | A policy consulta **outra tabela com RLS** (papéis/times/tenants) e há risco de recursão/join caro |
307
+ | `security invoker` (default em funções) | A função deve **respeitar** a RLS de quem a chama — e em **views** (`security_invoker = true`) |
308
+ | `array(select user_teams())` | Função retorna conjunto usado com `= any(...)` — força cache e evita timeout em tabelas grandes |
309
+ | Inverter o join (filtrar na tabela de associação) | A policy faria a tabela protegida cruzar (`join`) a tabela de associação — inverta para usar índice |
310
+ | `service_role` / `bypassrls` | Operação de servidor confiável (jobs, webhooks, migração) — **nunca** exposto ao cliente |
311
+
312
+ ---
313
+
314
+ **Fonte:** Supabase Docs — [Row Level Security](https://supabase.com/docs/guides/database/postgres/row-level-security),
315
+ [RLS Performance and Best Practices](https://supabase.com/docs/guides/troubleshooting/rls-performance-and-best-practices-Z5Jjwv),
316
+ [AI Prompt: Create RLS policies](https://supabase.com/docs/guides/getting-started/ai-prompts/database-rls-policies).