@luanpdd/kit-mcp 1.35.0 → 1.36.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 (117) hide show
  1. package/bin/cli.js +2 -2
  2. package/bin/mcp.js +6 -6
  3. package/bin/ui.js +74 -74
  4. package/gates/ai-prompt-stability.md +120 -120
  5. package/gates/budget-description.md +68 -68
  6. package/gates/confidence.md +29 -29
  7. package/gates/dependency-check.md +33 -33
  8. package/gates/dept-cycle-prevention.md +179 -179
  9. package/gates/golden-signals-coverage.md +133 -133
  10. package/gates/legacy-refactor-safety.md +178 -178
  11. package/gates/multi-tenant-rls-coverage.md +102 -102
  12. package/gates/no-personal-uuid.md +72 -72
  13. package/gates/obs-agents-mcp-supabase.md +86 -86
  14. package/gates/obs-skills-frontmatter.md +76 -76
  15. package/gates/observability-coverage.md +151 -151
  16. package/gates/omm-no-regression.md +83 -83
  17. package/gates/postmortem-template-required.md +127 -127
  18. package/gates/prr-checklist-coverage.md +128 -128
  19. package/gates/regression.md +32 -32
  20. package/gates/release-pipeline-policy.md +132 -132
  21. package/gates/secrets-scan.md +33 -33
  22. package/gates/service-role-not-in-user-facing.md +113 -113
  23. package/gates/skill-must-include.md +71 -71
  24. package/gates/sync-idempotent.md +62 -62
  25. package/gates/verify-phase-goal.md +34 -34
  26. package/kit/agents/designer-ui.md +216 -216
  27. package/kit/agents/workflow-generator.md +537 -167
  28. package/kit/commands/adicionar-backlog.md +1 -1
  29. package/kit/commands/adicionar-fase.md +1 -1
  30. package/kit/commands/adicionar-tarefa.md +1 -1
  31. package/kit/commands/auditar-observabilidade.md +103 -103
  32. package/kit/commands/auditar-toil.md +129 -129
  33. package/kit/commands/caracterizar-prompt.md +195 -195
  34. package/kit/commands/criar-workflow.md +158 -158
  35. package/kit/commands/definir-perfil.md +1 -1
  36. package/kit/commands/definir-slo.md +108 -108
  37. package/kit/commands/fio.md +1 -1
  38. package/kit/commands/golden-signals.md +142 -142
  39. package/kit/commands/instrumentar-fase.md +200 -200
  40. package/kit/commands/investigar-producao.md +162 -162
  41. package/kit/commands/observabilidade.md +118 -118
  42. package/kit/commands/postmortem.md +179 -179
  43. package/kit/commands/prr.md +205 -205
  44. package/kit/commands/publicar-rapido.md +207 -207
  45. package/kit/commands/risk-budget.md +220 -220
  46. package/kit/commands/sre.md +230 -230
  47. package/kit/file-manifest.json +424 -424
  48. package/kit/framework/references/output-style.md +22 -22
  49. package/kit/hooks/post-apply-migration.js +199 -199
  50. package/kit/hooks/sidecar-tool-publisher.js +210 -210
  51. package/kit/skills/_shared-dados-distribuidos/glossary.md +224 -224
  52. package/kit/skills/_shared-legacy/glossary.md +389 -389
  53. package/kit/skills/_shared-multi-tenant/glossary.md +186 -186
  54. package/kit/skills/_shared-observability/glossary.md +396 -396
  55. package/kit/skills/_shared-sre/glossary.md +712 -712
  56. package/kit/skills/_shared-supabase/glossary.md +234 -234
  57. package/kit/skills/blameless-postmortems/SKILL.md +340 -340
  58. package/kit/skills/burn-rate-alerting/SKILL.md +258 -258
  59. package/kit/skills/cascading-failures/SKILL.md +311 -311
  60. package/kit/skills/core-analysis-loop/SKILL.md +352 -352
  61. package/kit/skills/distributed-tracing/SKILL.md +362 -362
  62. package/kit/skills/dynamic-workflow-authoring/SKILL.md +327 -223
  63. package/kit/skills/eliminating-toil/SKILL.md +243 -243
  64. package/kit/skills/event-based-slos/SKILL.md +296 -296
  65. package/kit/skills/four-golden-signals/SKILL.md +314 -314
  66. package/kit/skills/hermetic-builds/SKILL.md +323 -323
  67. package/kit/skills/legacy-monster-methods/SKILL.md +444 -444
  68. package/kit/skills/llm-as-dependency/SKILL.md +436 -436
  69. package/kit/skills/load-shedding-graceful-degradation/SKILL.md +396 -396
  70. package/kit/skills/observability-driven-development/SKILL.md +315 -315
  71. package/kit/skills/observability-maturity-model/SKILL.md +222 -222
  72. package/kit/skills/opentelemetry-standard/SKILL.md +351 -351
  73. package/kit/skills/production-readiness-review/SKILL.md +305 -305
  74. package/kit/skills/release-engineering/SKILL.md +367 -367
  75. package/kit/skills/retry-strategies/SKILL.md +372 -372
  76. package/kit/skills/sre-risk-management/SKILL.md +221 -221
  77. package/kit/skills/structured-events/SKILL.md +265 -265
  78. package/kit/skills/supabase-cron-queues/SKILL.md +275 -275
  79. package/kit/skills/supabase-database-functions/SKILL.md +332 -332
  80. package/kit/skills/supabase-declarative-schema/SKILL.md +183 -183
  81. package/kit/skills/supabase-pgvector-rag/SKILL.md +253 -253
  82. package/kit/skills/supabase-postgres-style/SKILL.md +138 -138
  83. package/kit/skills/supabase-storage/SKILL.md +234 -234
  84. package/kit/skills/telemetry-pipelines/SKILL.md +259 -259
  85. package/kit/skills/telemetry-sampling/SKILL.md +256 -256
  86. package/kit/skills/ui-anti-padroes-ia/SKILL.md +261 -261
  87. package/kit/skills/ui-contexto-produto/SKILL.md +248 -248
  88. package/kit/skills/ui-cor-estrategia/SKILL.md +213 -213
  89. package/kit/skills/ui-critica-auditoria/SKILL.md +260 -260
  90. package/kit/skills/ui-motion-funcional/SKILL.md +264 -264
  91. package/kit/skills/ui-ritmo-espacial/SKILL.md +259 -259
  92. package/kit/skills/ui-tipografia/SKILL.md +211 -211
  93. package/package.json +1 -1
  94. package/src/cli/index.js +1114 -1114
  95. package/src/cli/render.js +194 -194
  96. package/src/cli/upgrade-check.js +135 -135
  97. package/src/core/error-redaction.js +76 -76
  98. package/src/core/failures.js +153 -153
  99. package/src/core/gate-runner.js +205 -205
  100. package/src/core/gates.js +82 -82
  101. package/src/core/logger.js +170 -170
  102. package/src/core/manifest-verify.js +174 -174
  103. package/src/core/metrics.js +268 -268
  104. package/src/core/notify.js +60 -60
  105. package/src/core/path-safety.js +141 -141
  106. package/src/core/replays.js +120 -120
  107. package/src/core/ui.js +185 -185
  108. package/src/mcp-server/install.js +149 -149
  109. package/src/mcp-server/roots.js +124 -124
  110. package/src/ui/auto-spawn.js +113 -113
  111. package/src/ui/browser.js +78 -78
  112. package/src/ui/client.js +130 -130
  113. package/src/ui/events.js +65 -65
  114. package/src/ui/lockfile.js +191 -191
  115. package/src/ui/port.js +67 -67
  116. package/src/ui/server.js +547 -547
  117. package/src/ui/wrapper.js +129 -129
@@ -1,234 +1,234 @@
1
- ---
2
- name: supabase-storage
3
- description: Use ao integrar Storage — buckets públicos vs privados, signed URLs com expiration, RLS sobre storage.objects com multi-tenant path, image transforms, TUS uploads.
4
- ---
5
-
6
- # Supabase — Storage
7
-
8
- ## Quando usar
9
-
10
- LLM carrega esta skill quando trabalhar com upload, download, ou serve de arquivos via Supabase Storage. Trigger phrases:
11
-
12
- - "Supabase Storage", "upload de arquivo"
13
- - "signed URL", "createSignedUrl"
14
- - "bucket público vs privado"
15
- - "RLS storage.objects"
16
- - "multi-tenant arquivos"
17
- - "image transforms Supabase"
18
- - "TUS resumable upload"
19
-
20
- ## Regras absolutas
21
-
22
- - **Bucket privado é default em produção** — apenas dados públicos (avatares públicos, marketing) vão em buckets públicos.
23
- - **Bucket público:** URL direta `getPublicUrl()` + servida via CDN (cache).
24
- - **Bucket privado:** apenas `signed URL` (`createSignedUrl()`) com `expiresIn` curto (60s downloads, 3600s imagens).
25
- - **`storage.objects`** — RLS sempre habilitada. Sem RLS, qualquer authenticated lê qualquer bucket privado.
26
- - **`multi-tenant path`** isolation — usar `auth.uid()` (ou `org_id`) como path prefix: `<user_id>/<filename>`. Validar em RLS via `(storage.foldername(name))[1] = (select auth.uid())::text`.
27
- - **Image transformations** apenas em buckets com transformation enabled (Pro plan+). Query params `?width=800&height=600&resize=contain`.
28
- - **Uploads grandes (> 6 MB):** use TUS resumable protocol (`uploadToSignedUrl` + chunked upload).
29
- - **Awareness de egress billing** — bucket público sem cache headers customizados pode disparar custo significativo. Use Smart CDN + TTL adequado.
30
- - **Não overwrite arquivos públicos** com mesmo nome — CDN cache fica stale. Use versionamento (`avatar-v2.jpg`) ou random suffix.
31
-
32
- ## Patterns canônicos
33
-
34
- ### RLS multi-tenant em `storage.objects`
35
-
36
- ```sql
37
- -- PT-BR: usuário só vê arquivos sob seu próprio prefix de path
38
- -- path canônico: <user_id>/<filename> (ex: 550e8400-e29b-41d4-a716-446655440000/avatar.jpg)
39
-
40
- create policy "users_read_own_files"
41
- on storage.objects for select to authenticated
42
- using (
43
- bucket_id = 'private-uploads'
44
- and (storage.foldername(name))[1] = (select auth.uid())::text
45
- );
46
-
47
- create policy "users_insert_own_files"
48
- on storage.objects for insert to authenticated
49
- with check (
50
- bucket_id = 'private-uploads'
51
- and (storage.foldername(name))[1] = (select auth.uid())::text
52
- );
53
-
54
- create policy "users_update_own_files"
55
- on storage.objects for update to authenticated
56
- using (
57
- bucket_id = 'private-uploads'
58
- and (storage.foldername(name))[1] = (select auth.uid())::text
59
- );
60
-
61
- create policy "users_delete_own_files"
62
- on storage.objects for delete to authenticated
63
- using (
64
- bucket_id = 'private-uploads'
65
- and (storage.foldername(name))[1] = (select auth.uid())::text
66
- );
67
- ```
68
-
69
- ### Upload com path multi-tenant
70
-
71
- ```ts
72
- // PT-BR: cliente — path sempre prefixado com user.id
73
- import { createClient } from '@/utils/supabase/client'
74
-
75
- async function uploadAvatar(file: File) {
76
- const supabase = createClient()
77
- const { data: { user } } = await supabase.auth.getUser()
78
- if (!user) throw new Error('not authenticated')
79
-
80
- // PT-BR: path = <user_id>/<filename>
81
- const path = `${user.id}/avatar.jpg`
82
-
83
- const { data, error } = await supabase.storage
84
- .from('private-uploads')
85
- .upload(path, file, {
86
- cacheControl: '3600',
87
- upsert: true, // PT-BR: sobrescreve se mesmo path
88
- })
89
-
90
- if (error) throw error
91
- return data
92
- }
93
- ```
94
-
95
- ### Signed URL — download privado
96
-
97
- ```ts
98
- // PT-BR: signed URL com expiração 1h
99
- const { data, error } = await supabase.storage
100
- .from('private-uploads')
101
- .createSignedUrl(`${userId}/avatar.jpg`, 3600)
102
-
103
- // data.signedUrl pode ser usado em <img src={data.signedUrl}> por 1h
104
- ```
105
-
106
- ### Image transformations (em bucket com transform habilitado)
107
-
108
- ```ts
109
- // PT-BR: signed URL com transformação inline
110
- const { data } = await supabase.storage
111
- .from('private-uploads')
112
- .createSignedUrl(`${userId}/avatar.jpg`, 3600, {
113
- transform: { width: 200, height: 200, resize: 'cover' },
114
- })
115
- ```
116
-
117
- ### Public bucket — getPublicUrl + cache headers
118
-
119
- ```ts
120
- // PT-BR: para bucket PÚBLICO apenas (não funciona em privado)
121
- const { data } = supabase.storage
122
- .from('public-avatars')
123
- .getPublicUrl('hero.jpg')
124
-
125
- // PT-BR: ao upload, set cacheControl alto para reduzir egress
126
- await supabase.storage
127
- .from('public-avatars')
128
- .upload('hero.jpg', file, {
129
- cacheControl: '31536000', // 1 ano — assets imutáveis
130
- upsert: false, // não sobrescrever (versionar via path)
131
- })
132
- ```
133
-
134
- ### TUS resumable upload (arquivos grandes)
135
-
136
- ```ts
137
- // PT-BR: signed upload URL + TUS chunked upload (>6MB)
138
- import * as tus from 'npm:tus-js-client'
139
-
140
- async function uploadLarge(file: File, path: string) {
141
- const supabase = createClient()
142
- const { data, error } = await supabase.storage
143
- .from('private-uploads')
144
- .createSignedUploadUrl(path)
145
- if (error) throw error
146
-
147
- return new Promise((resolve, reject) => {
148
- const upload = new tus.Upload(file, {
149
- endpoint: data.signedUrl,
150
- headers: { authorization: `Bearer ${data.token}` },
151
- chunkSize: 6 * 1024 * 1024, // 6 MB chunks
152
- onError: reject,
153
- onSuccess: () => resolve(upload.url),
154
- })
155
- upload.start()
156
- })
157
- }
158
- ```
159
-
160
- ### Notas de futuro (alpha — não detalhar em produção)
161
-
162
- - **Vector Buckets** e **Analytics Buckets** existem em alpha (2026). Mencione apenas como existência se relevante; pattern canônico ainda mudando — não detalhar.
163
-
164
- ## Anti-patterns
165
-
166
- ### Anti-pattern 1: Path sem prefix de tenant
167
-
168
- **Errado:**
169
- ```ts
170
- await supabase.storage.from('private-uploads').upload('avatar.jpg', file)
171
- ```
172
-
173
- **Por quê:** path global — qualquer user sobrescreve `avatar.jpg`. RLS multi-tenant não consegue isolar.
174
-
175
- **Certo:**
176
- ```ts
177
- const path = `${user.id}/avatar.jpg`
178
- await supabase.storage.from('private-uploads').upload(path, file)
179
- ```
180
-
181
- ### Anti-pattern 2: Bucket privado sem RLS em `storage.objects`
182
-
183
- **Errado:**
184
- ```sql
185
- create bucket 'private-uploads';
186
- -- (esqueceu policies em storage.objects)
187
- ```
188
-
189
- **Por quê:** sem RLS em `storage.objects`, qualquer `authenticated` lê arquivos do bucket — multi-tenancy quebrado.
190
-
191
- **Certo:** ver pattern "RLS multi-tenant" acima — 4 policies separadas (SELECT/INSERT/UPDATE/DELETE).
192
-
193
- ### Anti-pattern 3: `getPublicUrl` em bucket privado
194
-
195
- **Errado:**
196
- ```ts
197
- // PT-BR: bucket-id é privado
198
- const { data } = supabase.storage.from('private-uploads').getPublicUrl('x.jpg')
199
- // data.publicUrl retorna mas o URL não funciona (403)
200
- ```
201
-
202
- **Por quê:** `getPublicUrl` só funciona em buckets marcados public. Em privado, retorna URL que sempre dá 403.
203
-
204
- **Certo:** use `createSignedUrl` com expiration:
205
- ```ts
206
- const { data } = await supabase.storage
207
- .from('private-uploads')
208
- .createSignedUrl('x.jpg', 3600)
209
- // data.signedUrl funciona por 1h
210
- ```
211
-
212
- ### Anti-pattern 4: Overwrite de arquivo público com mesmo path
213
-
214
- **Errado:**
215
- ```ts
216
- // PT-BR: hero.jpg público
217
- await supabase.storage.from('public').upload('hero.jpg', newFile, { upsert: true })
218
- // CDN cache antigo continua servindo old hero.jpg por horas/dias
219
- ```
220
-
221
- **Por quê:** CDN cache pelo path. Overwrite não invalida cache; usuários veem versão antiga.
222
-
223
- **Certo:** versionar ou random suffix:
224
- ```ts
225
- await supabase.storage.from('public').upload(`hero-${version}.jpg`, newFile)
226
- // ou: hero-<sha>.jpg, hero-<timestamp>.jpg
227
- ```
228
-
229
- ## Ver também
230
-
231
- - [supabase-rls-policies](../supabase-rls-policies/SKILL.md) — RLS sobre `storage.objects` + multi-tenant pattern
232
- - [supabase-auth-ssr](../supabase-auth-ssr/SKILL.md) — usuário autenticado obtém `auth.uid()` para path prefix
233
- - [supabase-edge-functions](../supabase-edge-functions/SKILL.md) — Edge Functions podem mediar uploads complexos
234
- - [glossário](../_shared-supabase/glossary.md) — termos PT-BR↔EN
1
+ ---
2
+ name: supabase-storage
3
+ description: Use ao integrar Storage — buckets públicos vs privados, signed URLs com expiration, RLS sobre storage.objects com multi-tenant path, image transforms, TUS uploads.
4
+ ---
5
+
6
+ # Supabase — Storage
7
+
8
+ ## Quando usar
9
+
10
+ LLM carrega esta skill quando trabalhar com upload, download, ou serve de arquivos via Supabase Storage. Trigger phrases:
11
+
12
+ - "Supabase Storage", "upload de arquivo"
13
+ - "signed URL", "createSignedUrl"
14
+ - "bucket público vs privado"
15
+ - "RLS storage.objects"
16
+ - "multi-tenant arquivos"
17
+ - "image transforms Supabase"
18
+ - "TUS resumable upload"
19
+
20
+ ## Regras absolutas
21
+
22
+ - **Bucket privado é default em produção** — apenas dados públicos (avatares públicos, marketing) vão em buckets públicos.
23
+ - **Bucket público:** URL direta `getPublicUrl()` + servida via CDN (cache).
24
+ - **Bucket privado:** apenas `signed URL` (`createSignedUrl()`) com `expiresIn` curto (60s downloads, 3600s imagens).
25
+ - **`storage.objects`** — RLS sempre habilitada. Sem RLS, qualquer authenticated lê qualquer bucket privado.
26
+ - **`multi-tenant path`** isolation — usar `auth.uid()` (ou `org_id`) como path prefix: `<user_id>/<filename>`. Validar em RLS via `(storage.foldername(name))[1] = (select auth.uid())::text`.
27
+ - **Image transformations** apenas em buckets com transformation enabled (Pro plan+). Query params `?width=800&height=600&resize=contain`.
28
+ - **Uploads grandes (> 6 MB):** use TUS resumable protocol (`uploadToSignedUrl` + chunked upload).
29
+ - **Awareness de egress billing** — bucket público sem cache headers customizados pode disparar custo significativo. Use Smart CDN + TTL adequado.
30
+ - **Não overwrite arquivos públicos** com mesmo nome — CDN cache fica stale. Use versionamento (`avatar-v2.jpg`) ou random suffix.
31
+
32
+ ## Patterns canônicos
33
+
34
+ ### RLS multi-tenant em `storage.objects`
35
+
36
+ ```sql
37
+ -- PT-BR: usuário só vê arquivos sob seu próprio prefix de path
38
+ -- path canônico: <user_id>/<filename> (ex: 550e8400-e29b-41d4-a716-446655440000/avatar.jpg)
39
+
40
+ create policy "users_read_own_files"
41
+ on storage.objects for select to authenticated
42
+ using (
43
+ bucket_id = 'private-uploads'
44
+ and (storage.foldername(name))[1] = (select auth.uid())::text
45
+ );
46
+
47
+ create policy "users_insert_own_files"
48
+ on storage.objects for insert to authenticated
49
+ with check (
50
+ bucket_id = 'private-uploads'
51
+ and (storage.foldername(name))[1] = (select auth.uid())::text
52
+ );
53
+
54
+ create policy "users_update_own_files"
55
+ on storage.objects for update to authenticated
56
+ using (
57
+ bucket_id = 'private-uploads'
58
+ and (storage.foldername(name))[1] = (select auth.uid())::text
59
+ );
60
+
61
+ create policy "users_delete_own_files"
62
+ on storage.objects for delete to authenticated
63
+ using (
64
+ bucket_id = 'private-uploads'
65
+ and (storage.foldername(name))[1] = (select auth.uid())::text
66
+ );
67
+ ```
68
+
69
+ ### Upload com path multi-tenant
70
+
71
+ ```ts
72
+ // PT-BR: cliente — path sempre prefixado com user.id
73
+ import { createClient } from '@/utils/supabase/client'
74
+
75
+ async function uploadAvatar(file: File) {
76
+ const supabase = createClient()
77
+ const { data: { user } } = await supabase.auth.getUser()
78
+ if (!user) throw new Error('not authenticated')
79
+
80
+ // PT-BR: path = <user_id>/<filename>
81
+ const path = `${user.id}/avatar.jpg`
82
+
83
+ const { data, error } = await supabase.storage
84
+ .from('private-uploads')
85
+ .upload(path, file, {
86
+ cacheControl: '3600',
87
+ upsert: true, // PT-BR: sobrescreve se mesmo path
88
+ })
89
+
90
+ if (error) throw error
91
+ return data
92
+ }
93
+ ```
94
+
95
+ ### Signed URL — download privado
96
+
97
+ ```ts
98
+ // PT-BR: signed URL com expiração 1h
99
+ const { data, error } = await supabase.storage
100
+ .from('private-uploads')
101
+ .createSignedUrl(`${userId}/avatar.jpg`, 3600)
102
+
103
+ // data.signedUrl pode ser usado em <img src={data.signedUrl}> por 1h
104
+ ```
105
+
106
+ ### Image transformations (em bucket com transform habilitado)
107
+
108
+ ```ts
109
+ // PT-BR: signed URL com transformação inline
110
+ const { data } = await supabase.storage
111
+ .from('private-uploads')
112
+ .createSignedUrl(`${userId}/avatar.jpg`, 3600, {
113
+ transform: { width: 200, height: 200, resize: 'cover' },
114
+ })
115
+ ```
116
+
117
+ ### Public bucket — getPublicUrl + cache headers
118
+
119
+ ```ts
120
+ // PT-BR: para bucket PÚBLICO apenas (não funciona em privado)
121
+ const { data } = supabase.storage
122
+ .from('public-avatars')
123
+ .getPublicUrl('hero.jpg')
124
+
125
+ // PT-BR: ao upload, set cacheControl alto para reduzir egress
126
+ await supabase.storage
127
+ .from('public-avatars')
128
+ .upload('hero.jpg', file, {
129
+ cacheControl: '31536000', // 1 ano — assets imutáveis
130
+ upsert: false, // não sobrescrever (versionar via path)
131
+ })
132
+ ```
133
+
134
+ ### TUS resumable upload (arquivos grandes)
135
+
136
+ ```ts
137
+ // PT-BR: signed upload URL + TUS chunked upload (>6MB)
138
+ import * as tus from 'npm:tus-js-client'
139
+
140
+ async function uploadLarge(file: File, path: string) {
141
+ const supabase = createClient()
142
+ const { data, error } = await supabase.storage
143
+ .from('private-uploads')
144
+ .createSignedUploadUrl(path)
145
+ if (error) throw error
146
+
147
+ return new Promise((resolve, reject) => {
148
+ const upload = new tus.Upload(file, {
149
+ endpoint: data.signedUrl,
150
+ headers: { authorization: `Bearer ${data.token}` },
151
+ chunkSize: 6 * 1024 * 1024, // 6 MB chunks
152
+ onError: reject,
153
+ onSuccess: () => resolve(upload.url),
154
+ })
155
+ upload.start()
156
+ })
157
+ }
158
+ ```
159
+
160
+ ### Notas de futuro (alpha — não detalhar em produção)
161
+
162
+ - **Vector Buckets** e **Analytics Buckets** existem em alpha (2026). Mencione apenas como existência se relevante; pattern canônico ainda mudando — não detalhar.
163
+
164
+ ## Anti-patterns
165
+
166
+ ### Anti-pattern 1: Path sem prefix de tenant
167
+
168
+ **Errado:**
169
+ ```ts
170
+ await supabase.storage.from('private-uploads').upload('avatar.jpg', file)
171
+ ```
172
+
173
+ **Por quê:** path global — qualquer user sobrescreve `avatar.jpg`. RLS multi-tenant não consegue isolar.
174
+
175
+ **Certo:**
176
+ ```ts
177
+ const path = `${user.id}/avatar.jpg`
178
+ await supabase.storage.from('private-uploads').upload(path, file)
179
+ ```
180
+
181
+ ### Anti-pattern 2: Bucket privado sem RLS em `storage.objects`
182
+
183
+ **Errado:**
184
+ ```sql
185
+ create bucket 'private-uploads';
186
+ -- (esqueceu policies em storage.objects)
187
+ ```
188
+
189
+ **Por quê:** sem RLS em `storage.objects`, qualquer `authenticated` lê arquivos do bucket — multi-tenancy quebrado.
190
+
191
+ **Certo:** ver pattern "RLS multi-tenant" acima — 4 policies separadas (SELECT/INSERT/UPDATE/DELETE).
192
+
193
+ ### Anti-pattern 3: `getPublicUrl` em bucket privado
194
+
195
+ **Errado:**
196
+ ```ts
197
+ // PT-BR: bucket-id é privado
198
+ const { data } = supabase.storage.from('private-uploads').getPublicUrl('x.jpg')
199
+ // data.publicUrl retorna mas o URL não funciona (403)
200
+ ```
201
+
202
+ **Por quê:** `getPublicUrl` só funciona em buckets marcados public. Em privado, retorna URL que sempre dá 403.
203
+
204
+ **Certo:** use `createSignedUrl` com expiration:
205
+ ```ts
206
+ const { data } = await supabase.storage
207
+ .from('private-uploads')
208
+ .createSignedUrl('x.jpg', 3600)
209
+ // data.signedUrl funciona por 1h
210
+ ```
211
+
212
+ ### Anti-pattern 4: Overwrite de arquivo público com mesmo path
213
+
214
+ **Errado:**
215
+ ```ts
216
+ // PT-BR: hero.jpg público
217
+ await supabase.storage.from('public').upload('hero.jpg', newFile, { upsert: true })
218
+ // CDN cache antigo continua servindo old hero.jpg por horas/dias
219
+ ```
220
+
221
+ **Por quê:** CDN cache pelo path. Overwrite não invalida cache; usuários veem versão antiga.
222
+
223
+ **Certo:** versionar ou random suffix:
224
+ ```ts
225
+ await supabase.storage.from('public').upload(`hero-${version}.jpg`, newFile)
226
+ // ou: hero-<sha>.jpg, hero-<timestamp>.jpg
227
+ ```
228
+
229
+ ## Ver também
230
+
231
+ - [supabase-rls-policies](../supabase-rls-policies/SKILL.md) — RLS sobre `storage.objects` + multi-tenant pattern
232
+ - [supabase-auth-ssr](../supabase-auth-ssr/SKILL.md) — usuário autenticado obtém `auth.uid()` para path prefix
233
+ - [supabase-edge-functions](../supabase-edge-functions/SKILL.md) — Edge Functions podem mediar uploads complexos
234
+ - [glossário](../_shared-supabase/glossary.md) — termos PT-BR↔EN