forlogic-core 2.2.6 → 2.2.8

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 (70) hide show
  1. package/.note/memory/features/import/attachment-idempotency-registry.md +8 -8
  2. package/.note/memory/features/import/attachment-strategy.md +30 -30
  3. package/.note/memory/patterns/admin-i18n-policy.md +20 -20
  4. package/.note/memory/patterns/alias-url-resolution.md +69 -69
  5. package/.note/memory/patterns/doc-sync-rule.md +35 -35
  6. package/.note/memory/patterns/documentation-standard.md +17 -17
  7. package/.note/memory/patterns/dynamic-supabase-config.md +4 -4
  8. package/.note/memory/patterns/environment-detection-logic.md +35 -35
  9. package/.note/memory/patterns/i18n-architecture.md +3 -3
  10. package/README.md +120 -120
  11. package/dist/README.md +1079 -0
  12. package/dist/bin/bootstrap.js +40 -0
  13. package/dist/bin/pull-docs.js +186 -0
  14. package/dist/components/ui/color-picker.d.ts +2 -0
  15. package/dist/components/ui/combo-tree.d.ts +3 -1
  16. package/dist/components/ui/combobox.d.ts +2 -1
  17. package/dist/components/ui/icon-picker.d.ts +2 -0
  18. package/dist/components/ui/select.d.ts +9 -2
  19. package/dist/docs/KNOWLEDGE.md +109 -0
  20. package/dist/index.css +1 -1
  21. package/dist/index.css.map +1 -1
  22. package/dist/index.esm.js +1 -1
  23. package/dist/index.js +1 -1
  24. package/docs/PUBLISH.md +168 -168
  25. package/docs/STORAGE_BUCKETS.md +456 -456
  26. package/docs/SUPABASE_SECRETS.md +122 -122
  27. package/docs/WORKSPACE_KNOWLEDGE.md +154 -154
  28. package/docs/design-system/buttons-actions.md +130 -130
  29. package/docs/design-system/charts-dashboards.md +301 -340
  30. package/docs/design-system/crud.md +114 -174
  31. package/docs/design-system/data-display.md +106 -106
  32. package/docs/design-system/dialogs.md +212 -212
  33. package/docs/design-system/domain.md +329 -319
  34. package/docs/design-system/examples.md +275 -275
  35. package/docs/design-system/foundation.md +1 -1
  36. package/docs/design-system/inputs.md +137 -132
  37. package/docs/design-system/layout.md +154 -202
  38. package/docs/design-system/navigation.md +331 -272
  39. package/docs/design-system/notifications-feedback.md +34 -34
  40. package/docs/design-system/patterns/README.md +53 -53
  41. package/docs/design-system/patterns/action-button.md +22 -22
  42. package/docs/design-system/patterns/alertdialog-deletion.md +46 -46
  43. package/docs/design-system/patterns/baseform-custom-fields.md +59 -59
  44. package/docs/design-system/patterns/baseform-usage.md +42 -42
  45. package/docs/design-system/patterns/body-content-scroll.md +56 -56
  46. package/docs/design-system/patterns/combo-tree.md +23 -23
  47. package/docs/design-system/patterns/components-registry.md +17 -17
  48. package/docs/design-system/patterns/core-providers.md +41 -41
  49. package/docs/design-system/patterns/crud-bulk-actions.md +12 -12
  50. package/docs/design-system/patterns/crud-config-props.md +16 -16
  51. package/docs/design-system/patterns/crud-defaults.md +17 -17
  52. package/docs/design-system/patterns/crud-toolbar.md +28 -28
  53. package/docs/design-system/patterns/delete-confirmation.md +40 -40
  54. package/docs/design-system/patterns/dialog-body-scroll.md +26 -26
  55. package/docs/design-system/patterns/dialog-structure.md +32 -32
  56. package/docs/design-system/patterns/dialog-variants.md +41 -41
  57. package/docs/design-system/patterns/feature-flags.md +81 -81
  58. package/docs/design-system/patterns/header-metadata.md +57 -57
  59. package/docs/design-system/patterns/i18n-setup.md +117 -117
  60. package/docs/design-system/patterns/pagination.md +27 -27
  61. package/docs/design-system/patterns/single-scroll.md +39 -39
  62. package/docs/design-system/patterns/vite-tailwind-setup.md +49 -49
  63. package/docs/design-system/platform.md +18 -18
  64. package/docs/design-system/selectors.md +296 -260
  65. package/docs/design-system/tables-grids.md +38 -95
  66. package/package.json +152 -152
  67. package/dist/assets/docs-BEwTKYu3.css +0 -1
  68. package/dist/assets/docs-Bgpz6ETN.js +0 -10752
  69. package/dist/assets/index-SqMwTzMJ.js +0 -97
  70. package/dist/index.html +0 -34
@@ -1,456 +1,456 @@
1
- # Supabase Storage — Inventário e mapa de consumidores
2
-
3
- > **Projeto Supabase:** `ccjfvpnndclajkleyqkc` (qualiex-db, prod)
4
- > **Snapshot:** 2026-05-04
5
- > **Objetivo:** mapear todos os buckets, quem os consome, suas RLS atuais e os riscos para guiar o próximo ciclo de hardening.
6
- > **Escopo:** apenas documentação — nenhuma policy, bucket ou código foi alterado.
7
-
8
- ---
9
-
10
- ## 1. Visão geral
11
-
12
- 15 buckets ativos. Tamanho total ≈ 332 GB (dominado por `content-videos`).
13
-
14
- | # | Bucket | Público | Objetos | Tamanho | MIME limit | File size limit |
15
- |---|---|---|---:|---:|---|---|
16
- | 1 | `career-banners` | ✅ | 4 | 349 kB | — | — |
17
- | 2 | `certificates` | ✅ | 2.470 | 982 MB | — | — |
18
- | 3 | `content-files` | ✅ | 5.474 | 2,7 GB | lista ampla | 2 GB |
19
- | 4 | `content-videos` | ✅ | 1.929 | 326 GB | só vídeo | 3 GB |
20
- | 5 | `contracts` | 🔒 | 151 | 47 MB | — | — |
21
- | 6 | `imports` | 🔒 | 3 | 569 kB | — | — |
22
- | 7 | `knowledge-files` | 🔒 | 8 | 24 MB | — | — |
23
- | 8 | `library-assets` | ✅ | 10 | 105 kB | — | — |
24
- | 9 | `pdi-uploads` | 🔒 | 8 | 14 MB | — | — |
25
- | 10 | `performance` | 🔒 | 76 | 19 MB | — | — |
26
- | 11 | `resumes` | 🔒 | 745 | 162 MB | pdf/doc/docx | 10 MB |
27
- | 12 | `thumbnails` | ✅ | 1.158 | 585 MB | — | — |
28
- | 13 | `trainings` | 🔒 | 27 | 13 MB | — | — |
29
- | 14 | `university-assets` | ✅ | 284 | 308 MB | — | — |
30
- | 15 | `user-uploads` | 🔒 | 1.001 | 1,5 GB | — | — |
31
-
32
- > ℹ️ A coluna **Público** indica `storage.buckets.public`. Buckets públicos liberam download anônimo via `/storage/v1/object/public/<bucket>/<path>` e, sem policy explícita, também permitem `LIST` anônimo.
33
-
34
- ### Changelog
35
-
36
- | Data | Mudança |
37
- |------|---------|
38
- | 2026-05-04 | `performance` tornado privado com 4 policies (SELECT/INSERT/UPDATE/DELETE) escopadas por alias. `performance-files` removido (deprecado). Snapshot atualizado. |
39
- | 2026-04-29 | Snapshot inicial. `library-assets` INSERT anônimo removido. `thumbnails` rollback para policies permissivas. |
40
-
41
- ---
42
-
43
- ## 2. Matriz Bucket × Projeto
44
-
45
- Projetos do ecossistema (Lovable):
46
-
47
- - [Admin / Configurações Core](/projects/9dc9be11-bf85-4561-b36a-8d8f35fdbc06) — `qualiex-admin` (este; lib `forlogic-core`)
48
- - [Documentos](/projects/196d59d6-6e5c-4e49-b064-a24e57874298)
49
- - [Configurações](/projects/b0970ba3-5c2a-4b7d-8d3b-5b2162b2d285)
50
- - [OKR](/projects/53776a8a-ab33-494d-8b92-74341fd3d133)
51
- - [Cockpit](/projects/e6853fb4-67f9-4776-b427-7768a9136f10)
52
- - [Plano de Controle](/projects/d4712bc3-bc32-40a6-874c-401cf773c55c)
53
- - [Colaboradores](/projects/37cdf18f-3d54-4af2-a02b-9f7e2d94e654)
54
- - [Educação](/projects/075796dc-6ed4-43d3-92e3-3ab7f6314db6)
55
- - [Matriz de Foco](/projects/e0a4fc74-f1c6-4466-8a9c-dcb31f03fb20)
56
- - [Treinamentos](/projects/e02280e1-3f95-4114-8d83-80d3e8ced304)
57
- - [Competências](/projects/ffc892d4-d7b4-46cd-883d-01ffeb99b2f4)
58
- - [PDI](/projects/7269db93-bd03-4b7d-842a-cd74404d2606)
59
- - [Desempenho](/projects/7c73eab6-bf0a-4cb5-93b3-e1809d1363d7)
60
- - [Pulso](/projects/f49c7856-0bf8-4aa7-be0b-a6c5c5bf3ca1)
61
-
62
- | Bucket | Admin | Educação | Colaboradores | Treinamentos | Matriz Foco | PDI | Desempenho | Cockpit | Documentos |
63
- |---|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|
64
- | `library-assets` | ✅ | | | | | | | | |
65
- | `imports` | ✅ | | | | | | | | |
66
- | `thumbnails` | | ✅ | | | | | | | |
67
- | `university-assets` | | ✅ | | | | | | | |
68
- | `certificates` | | ✅ | | 🔎 read | | | | | |
69
- | `content-files` | | ✅ | | | | | | | |
70
- | `content-videos` | | ✅ | | | | | | | |
71
- | `career-banners` | | | ✅ | | | | | | |
72
- | `resumes` | | | ✅ | | | | | (policy) | |
73
- | `contracts` | | | ✅ | | | | | | |
74
- | `user-uploads` | | | ✅ (interviews) | ✅ (training-requests, trainings, imported-evidence) | ✅ (evidence-images, evidence-attachments) | | | | |
75
- | `pdi-uploads` | | | | | | ✅ (evidences) | | | |
76
- | `trainings` | | | | ✅ | | | | | |
77
- | `performance` | | | | | | | ✅ (1:1) | | |
78
- | `knowledge-files` | | | | | | | | | ❓ |
79
-
80
- Legenda: ✅ uso confirmado em código · 🔎 só leitura · (policy) tem RLS mas sem código consumidor encontrado · ❓ a confirmar com o time.
81
-
82
- ---
83
-
84
- ## 3. Detalhamento por bucket
85
-
86
- ### 3.1 `library-assets` — ✅ corrigido
87
-
88
- - **Finalidade:** logos, favicons e marca branca da lib (Qualiex / Saber).
89
- - **Consumidor:** [Admin](/projects/9dc9be11-bf85-4561-b36a-8d8f35fdbc06) — `lib/assets/index.ts`, `lib/setup/favicon.ts`. Consumido como leitura pública por todos os projetos via URL pública.
90
- - **Estrutura:** raiz do bucket (`logo-qualiex-white.svg`, `favicon.png`, etc.).
91
- - **Visibilidade:** público.
92
- - **RLS atuais:** **nenhuma policy de write** no `storage.objects` para esse bucket. SELECT continua público pelo flag de bucket público. INSERT/UPDATE/DELETE bloqueados para qualquer role (apenas service_role contorna RLS).
93
- - **Mudança recente:** a antiga policy `Allow public upload to library assets` (INSERT anônimo sem condição) foi **removida**.
94
- - **Riscos:**
95
- - 🟢 Upload anônimo eliminado.
96
- - 🟡 `LIST` anônimo (default de bucket público) ainda expõe inventário, mas como são logos é baixo impacto.
97
- - **Recomendação:** se precisar permitir upload via app, adicionar policy `TO authenticated` + checagem de admin. Caso contrário, manter como está (gerenciado via service_role/console).
98
-
99
- ---
100
-
101
- ### 3.2 `imports` — ok, com ressalvas
102
-
103
- - **Finalidade:** arquivos Excel/CSV submetidos pelo wizard de importação.
104
- - **Consumidor:** [Admin](/projects/9dc9be11-bf85-4561-b36a-8d8f35fdbc06) — `src/imports/wizard/services/importJobService.ts`.
105
- - **Estrutura:** `{alias}/{timestamp}_{filename}`.
106
- - **Visibilidade:** privado.
107
- - **RLS atuais:**
108
- - `imports_bucket_select` — SELECT, role `public`, `auth.role() = 'authenticated'` + `foldername[1] = jwt.alias`.
109
- - `imports_bucket_insert` — INSERT, role `public`, `auth.role() = 'authenticated'` + `foldername[1] = jwt.alias`.
110
- - `imports_bucket_update` — UPDATE, role `public`, `auth.role() = 'authenticated'` + `foldername[1] = jwt.alias`.
111
- - `imports_bucket_delete` — DELETE, role `public`, `auth.role() = 'authenticated'` + `foldername[1] = jwt.alias`.
112
- - **Riscos:**
113
- - 🟡 Policies estão atribuídas ao role `public` — funcional, mas inconsistente com o padrão `TO authenticated`.
114
- - **Recomendação:** converter as policies para `TO authenticated` (mais explícito e linter-friendly).
115
-
116
- ---
117
-
118
- ### 3.3 `thumbnails` — ⚠️ sem scoping por alias
119
-
120
- - **Finalidade:** miniaturas/capas de cursos e conteúdos.
121
- - **Consumidor:** [Educação](/projects/075796dc-6ed4-43d3-92e3-3ab7f6314db6) — `src/modules/contents/hooks/useImageUpload.ts` (default bucket).
122
- - **Estrutura atual:** sem prefixo obrigatório (uploads vão na raiz).
123
- - **Visibilidade:** público.
124
- - **RLS atuais:**
125
- - `Users can upload thumbnails` — INSERT `TO authenticated`, sem scoping por alias.
126
- - `Users can update thumbnails` — UPDATE `TO authenticated`, sem scoping por alias.
127
- - `Users can delete thumbnails` — DELETE `TO authenticated`, sem scoping por alias.
128
- - **Histórico:**
129
- - Hardening anterior (`thumbnails_auth_insert/update/delete` exigindo `foldername[1] = jwt.alias`) quebrou os uploads do projeto Educação, que escreve direto na raiz do bucket.
130
- - Em 29/04/2026 fizemos rollback para o estado anterior (3 policies permissivas para `authenticated`).
131
- - **Riscos:**
132
- - 🔴 Sem isolamento multi-tenant: qualquer usuário autenticado pode sobrescrever/apagar arquivos de qualquer tenant.
133
- - 🟡 `LIST` público (bucket público) — inventário visível.
134
- - **Próximo passo (P1):** ajustar `src/modules/contents/hooks/useImageUpload.ts` para gravar em `{alias}/...` e então reaplicar as policies com scoping por alias.
135
-
136
- ---
137
-
138
- ### 3.4 `university-assets` — média
139
-
140
- - **Finalidade:** imagens da plataforma de cursos (banners, ilustrações, IA-docs).
141
- - **Consumidor:** [Educação](/projects/075796dc-6ed4-43d3-92e3-3ab7f6314db6) — `src/hooks/useImageUpload.ts`, `src/modules/contents/services/aiDocsService.ts`.
142
- - **Estrutura:** `{alias}/...` em parte das chamadas (não obrigatório).
143
- - **Visibilidade:** público.
144
- - **RLS atuais:**
145
- - `university_assets_auth_insert` — INSERT `TO authenticated`, `jwt.alias IS NOT NULL`. Sem scoping por pasta.
146
- - `university_assets_auth_update` — UPDATE `TO authenticated`, `jwt.alias IS NOT NULL`. Sem scoping por pasta.
147
- - `university_assets_auth_delete` — DELETE `TO authenticated`, `jwt.alias IS NOT NULL`. Sem scoping por pasta.
148
- - **Riscos:**
149
- - 🟠 Qualquer usuário autenticado de qualquer tenant pode sobrescrever/excluir arquivos de qualquer outro tenant.
150
- - 🟡 `LIST` público.
151
- - **Recomendação:** adicionar scoping `(storage.foldername(name))[1] = jwt.alias` no INSERT/UPDATE/DELETE.
152
-
153
- ---
154
-
155
- ### 3.5 `certificates` — média
156
-
157
- - **Finalidade:** PDFs/imagens de certificados emitidos para alunos.
158
- - **Consumidores:**
159
- - [Educação](/projects/075796dc-6ed4-43d3-92e3-3ab7f6314db6) — `src/hooks/useUserCertificateUpload.ts`, `src/hooks/useBulkCertificateUpload.ts` (uploads usam **signed URLs** com TTL 1 ano).
160
- - [Treinamentos](/projects/e02280e1-3f95-4114-8d83-80d3e8ced304) — `src/training/utils/evidenceUrlResolver.ts` (leitura via `getPublicUrl`).
161
- - **Estrutura:** sem padrão por alias (arquivos no root).
162
- - **Visibilidade:** público (mesmo o código gerando signed URLs — ou seja, a privacidade pretendida não é real).
163
- - **RLS atuais:**
164
- - `Authenticated users can upload certificates` — INSERT `TO authenticated`, sem scoping.
165
- - `Authenticated users can update certificates` — UPDATE `TO authenticated`, sem scoping.
166
- - `Authenticated users can delete certificates` — DELETE `TO authenticated`, sem scoping.
167
- - **Riscos:**
168
- - 🔴 Certificados são **PII** (nome do aluno, curso, datas). Bucket público + sem scoping = qualquer URL adivinhada/descoberta vaza dados pessoais.
169
- - 🟠 `LIST` anônimo expõe inventário com nomes de arquivo.
170
- - **Recomendação:** **tornar privado** e migrar Treinamentos para usar signed URLs (já é o padrão de Educação). Adicionar scoping por alias.
171
-
172
- ---
173
-
174
- ### 3.6 `content-files` — média/alta
175
-
176
- - **Finalidade:** materiais de cursos (apostilas, planilhas, pacotes SCORM).
177
- - **Consumidor:** [Educação](/projects/075796dc-6ed4-43d3-92e3-3ab7f6314db6) — `src/modules/contents/hooks/useFileUpload.ts`, `docs/SCORM_IMPLEMENTATION.md`.
178
- - **Estrutura:** `{packageFolder}/...` (SCORM); demais sem padrão claro.
179
- - **Visibilidade:** público.
180
- - **RLS atuais:**
181
- - `Authenticated users can upload content-files` — INSERT `TO authenticated`, sem scoping.
182
- - `Authenticated users can update content-files` — UPDATE `TO authenticated`, sem scoping.
183
- - `Authenticated users can delete content-files` — DELETE `TO authenticated`, sem scoping.
184
- - **Riscos:**
185
- - 🟠 Materiais de treinamento de um cliente podem estar acessíveis publicamente para quem tiver a URL/listar o bucket.
186
- - 🟡 `LIST` público.
187
- - **Recomendação:** scoping por `alias` e avaliar deixar privado com signed URLs (impacta player de cursos — exige mudança no front).
188
-
189
- ---
190
-
191
- ### 3.7 `content-videos` — ok (mais maduro)
192
-
193
- - **Finalidade:** vídeos das aulas de Educação.
194
- - **Consumidor:** [Educação](/projects/075796dc-6ed4-43d3-92e3-3ab7f6314db6) — `src/contexts/UploadQueueContext.tsx`, `src/modules/contents/hooks/useVideoUpload.ts`.
195
- - **Estrutura:** `{alias}/{filename}`.
196
- - **Visibilidade:** público.
197
- - **RLS atuais:**
198
- - `content_videos_auth_insert` — INSERT `TO authenticated`, `foldername[1] = jwt.alias`.
199
- - `content_videos_auth_update` — UPDATE `TO authenticated`, `foldername[1] = jwt.alias`.
200
- - `content_videos_auth_delete` — DELETE `TO authenticated`, `foldername[1] = jwt.alias`.
201
- - **Riscos:**
202
- - 🟠 Vídeos premium acessíveis por URL pública direta.
203
- - 🟡 `LIST` público (alto volume — 326 GB).
204
- - **Recomendação:** considerar privado + signed URLs (igual a Vimeo/Mux). Custo: rever player e CDN.
205
-
206
- ---
207
-
208
- ### 3.8 `career-banners` — baixa
209
-
210
- - **Finalidade:** banners da página pública de carreiras.
211
- - **Consumidor:** [Colaboradores](/projects/37cdf18f-3d54-4af2-a02b-9f7e2d94e654).
212
- - **Estrutura:** raiz.
213
- - **Visibilidade:** público.
214
- - **RLS atuais:**
215
- - `career_banners_upload` — INSERT, role `public`, `auth.role() = 'authenticated'`.
216
- - `career_banners_delete` — DELETE, role `public`, `auth.role() = 'authenticated'`.
217
- - **Riscos:**
218
- - 🟡 Sem scoping por alias — qualquer admin de qualquer tenant pode deletar banners de outro.
219
- - 🟡 Policies em role `public` em vez de `TO authenticated`.
220
- - **Recomendação:** scoping por alias se for multi-tenant. Padronizar para `TO authenticated`.
221
-
222
- ---
223
-
224
- ### 3.9 `resumes` — média
225
-
226
- - **Finalidade:** currículos de candidatos (RH).
227
- - **Consumidores:**
228
- - [Colaboradores](/projects/37cdf18f-3d54-4af2-a02b-9f7e2d94e654) — fluxo de RH.
229
- - [Cockpit](/projects/e6853fb4-67f9-4776-b427-7768a9136f10) — migração `20260330141432` cria policy escopada por `auth.uid()`.
230
- - **Estrutura:** `{alias}/{filename}` (Colaboradores) e `{auth.uid()}/...` (Cockpit) — **dois esquemas convivendo**.
231
- - **Visibilidade:** privado.
232
- - **RLS atuais:**
233
- - `resumes_alias_select` — SELECT `TO authenticated`, `foldername[1] = jwt.alias`.
234
- - `resumes_auth_upload` — INSERT `TO authenticated`, `foldername[1] = jwt.alias` + extensões `pdf|doc|docx|jpg|jpeg|png`.
235
- - **⚠️ Incongruência MIME:** o bucket aceita apenas `pdf/doc/docx` (configuração `allowed_mime_types`), mas a policy `resumes_auth_upload` lista também `jpg|jpeg|png`. Na prática o bucket rejeitaria imagens pelo MIME antes da policy ser avaliada, mas a policy está desatualizada.
236
- - **Riscos:**
237
- - 🟠 Há duas convenções de path conflitantes (alias vs uid). Pode haver arquivos "órfãos" que não casam com nenhuma policy.
238
- - 🟠 Não há policy explícita de UPDATE/DELETE listada.
239
- - **Recomendação:** alinhar uma única convenção (`{alias}/{auth.uid()}/{file}` cobre os dois) e adicionar UPDATE/DELETE. Remover extensões de imagem da policy para casar com o bucket.
240
-
241
- ---
242
-
243
- ### 3.10 `contracts` — ok
244
-
245
- - **Finalidade:** templates DOCX e contratos gerados.
246
- - **Consumidor:** [Colaboradores](/projects/37cdf18f-3d54-4af2-a02b-9f7e2d94e654) — `src/contracts/contractService.ts`, edge function `contract-processor`.
247
- - **Estrutura:** `{templates|generated}/{alias}/{filename}`.
248
- - **Visibilidade:** privado.
249
- - **RLS atuais:**
250
- - `contracts_select` — SELECT, role `public`, `foldername[1] IN ('templates','generated')` + `foldername[2] = jwt.alias`.
251
- - `contracts_insert` — INSERT, role `public`, mesma condição.
252
- - `contracts_update` — UPDATE, role `public`, mesma condição.
253
- - `contracts_delete` — DELETE `TO authenticated`, mesma condição.
254
- - **Riscos:**
255
- - 🟢 Bem segregado por alias.
256
- - 🟡 SELECT/INSERT/UPDATE em role `public` (funcional mas inconsistente).
257
- - **Recomendação:** padronizar para `TO authenticated`.
258
-
259
- ---
260
-
261
- ### 3.11 `user-uploads` — 🚨 alta complexidade
262
-
263
- Bucket multi-projeto, segregado por subpasta. **Usado por 3 projetos** (Colaboradores, Treinamentos, Matriz de Foco).
264
-
265
- - **Visibilidade:** privado.
266
- - **Estrutura por consumidor:**
267
-
268
- | Subpasta raiz | Projeto | Uso |
269
- |---|---|---|
270
- | `interviews/{alias}/...` | Colaboradores | Áudios/anexos de entrevistas |
271
- | `training-requests/{alias}/...` | Treinamentos | Anexos de solicitações de treinamento |
272
- | `imported-evidence/{alias}/...` | Treinamentos | Evidências importadas em massa |
273
- | `trainings/.../{alias}/...` | Treinamentos | Evidências de treinamento (alias na 3ª pasta) |
274
- | `evidence-images/...` | Matriz de Foco | Imagens coladas no rich-text editor |
275
- | `evidence-attachments/{evidenceId}/...` | Matriz de Foco | Anexos de evidências |
276
-
277
- - **RLS atuais (05/05/2026):**
278
-
279
- | Policy | Cmd | Role | Scoping |
280
- |--------|-----|------|---------|
281
- | `interview_scoped_select` | SELECT | `public` | `foldername[1] = 'interviews'` + `foldername[2] = jwt.alias` |
282
- | `authenticated_interview_upload` | INSERT | `public` | `auth.role() = 'authenticated'` — **sem scoping por subpasta/alias** ⚠️ |
283
- | `training_requests_attachments_select` | SELECT | `public` | `foldername[1] = 'training-requests'` + `foldername[2] = jwt.alias` |
284
- | `training_requests_attachments_insert` | INSERT | `public` | `foldername[1] = 'training-requests'` + `foldername[2] = jwt.alias` |
285
- | `training_requests_attachments_delete` | DELETE | `public` | `foldername[1] = 'training-requests'` + `foldername[2] = jwt.alias` |
286
- | `imported_evidence_select` | SELECT | `public` | `foldername[1] = 'imported-evidence'` + `foldername[2] = jwt.alias` |
287
- | `imported_evidence_insert` | INSERT | `public` | `foldername[1] = 'imported-evidence'` + `foldername[2] = jwt.alias` |
288
- | `evidence_uploads_select` | SELECT | `authenticated` | `foldername[1] IN ('evidence-images','evidence-attachments')` + `jwt.alias IS NOT NULL` — **sem checagem de alias** ⚠️ |
289
- | `evidence_uploads_delete` | DELETE | `authenticated` | `foldername[1] IN ('evidence-images','evidence-attachments')` + `jwt.alias IS NOT NULL` — **sem checagem de alias** ⚠️ |
290
- | `Authenticated users can update user-uploads` | UPDATE | `authenticated` | `foldername[2] = jwt.alias` (cobre `*/{alias}/...`, mas não `evidence-images/` nem `evidences/`) |
291
-
292
- - **Riscos:**
293
- - 🔴 `authenticated_interview_upload` continua permitindo que qualquer authenticated grave em qualquer subpasta do bucket (cross-tenant + cross-projeto).
294
- - 🔴 `evidence_uploads_select/delete` não checam alias — qualquer authenticated lê/deleta evidências da Matriz de Foco de outros tenants.
295
- - 🟠 Padrão de path inconsistente entre projetos (uns usam alias na 1ª pasta, outros na 3ª, outros não usam).
296
-
297
- - **Recomendação:**
298
- - Padronizar para `{projeto}/{alias}/...`.
299
- - Substituir `authenticated_interview_upload` por INSERT por subpasta + alias.
300
- - Adicionar checagem de alias em `evidence_uploads_select/delete` (Matriz de Foco).
301
-
302
- ---
303
-
304
- ### 3.12 `trainings` — ok
305
-
306
- - **Finalidade:** evidências de treinamentos (PDFs, fotos).
307
- - **Consumidor:** [Treinamentos](/projects/e02280e1-3f95-4114-8d83-80d3e8ced304) — `LeaderResolveModal`, `LeaderPendingSelectModal`.
308
- - **Estrutura:** `.../.../{alias}/...` (alias na 3ª pasta — esquisito).
309
- - **Visibilidade:** privado.
310
- - **RLS atuais:**
311
- - `trainings_bucket_select` — SELECT, role `public`, `foldername[2] = jwt.alias`.
312
- - `trainings_bucket_insert` — INSERT, role `public`, `foldername[2] = jwt.alias`.
313
- - **Riscos:**
314
- - 🟡 Discrepância: a policy usa pasta `[2]`, mas o resolver de evidências em `evidenceUrlResolver.ts` aceita o bucket. Validar se o path real bate.
315
- - 🟠 Sem policy explícita de UPDATE/DELETE.
316
- - 🟡 Policies em role `public` (funcional mas inconsistente).
317
- - **Recomendação:** confirmar convenção de path, adicionar UPDATE/DELETE escopados e padronizar para `TO authenticated`.
318
-
319
- ---
320
-
321
- ### 3.13 `performance` — ✅ corrigido (05/2026)
322
-
323
- - **Finalidade:** anexos do módulo de **One-on-One** dentro de Desempenho — evidências de tarefas e relatórios.
324
- - **Consumidor:** [Desempenho](/projects/7c73eab6-bf0a-4cb5-93b3-e1809d1363d7)
325
- - `src/one-on-ones/services/evidenceService.ts` → pasta `evidences/`
326
- - `src/one-on-ones/services/taskReportService.ts` → pasta `task-reports/`
327
- - **Estrutura:** `{alias}/evidences/...` e `{alias}/task-reports/...`.
328
- - **Visibilidade:** 🔒 **privado** (alterado de público para privado).
329
- - **RLS atuais (pós-hardening):**
330
- - `performance_authenticated_select` — SELECT `TO authenticated`, `jwt.alias = foldername[1]`.
331
- - `performance_authenticated_insert` — INSERT, role `public`, `jwt.alias = foldername[1]`.
332
- - `performance_authenticated_update` — UPDATE, role `public`, `jwt.alias = foldername[1]`.
333
- - `performance_authenticated_delete` — DELETE, role `public`, `jwt.alias = foldername[1]`.
334
- - **Mudança recente:** bucket tornado privado + 4 policies com scoping por alias na 1ª pasta. Resolve os riscos P0 apontados anteriormente.
335
- - **Riscos:**
336
- - 🟢 Isolamento multi-tenant via alias — resolvido.
337
- - 🟡 INSERT/UPDATE/DELETE em role `public` (funcional mas inconsistente com o padrão `TO authenticated`).
338
- - 🟡 O código de Desempenho pode precisar migrar de `getPublicUrl` para `createSignedUrl` (bucket agora privado).
339
- - **Recomendação:** padronizar policies para `TO authenticated`. Confirmar que o front de Desempenho já usa signed URLs.
340
-
341
- ---
342
-
343
- ### 3.14 `knowledge-files` — ❓ a confirmar
344
-
345
- - **Finalidade provável:** base de conhecimento para IA / chatbot.
346
- - **Consumidor:** sem uso direto encontrado em nenhum dos 14 projetos. 8 objetos, último em março/2026.
347
- - **Estrutura:** `{alias}/...`.
348
- - **Visibilidade:** privado.
349
- - **RLS atuais:**
350
- - `knowledge_files_select` — SELECT, role `public`, `auth.role() = 'authenticated'` + `foldername[1] = jwt.alias`.
351
- - `knowledge_files_upload` — INSERT, role `public`, `auth.role() = 'authenticated'` + `foldername[1] = jwt.alias`.
352
- - **Riscos:**
353
- - 🟡 Sem UPDATE/DELETE explícitos.
354
- - 🟡 Provavelmente consumido por uma edge function (não por front) — confirmar.
355
- - 🟡 Policies em role `public` (funcional mas inconsistente).
356
- - **Recomendação:** identificar consumidor e validar fluxo antes de mexer.
357
-
358
- ---
359
-
360
- ### 3.15 `pdi-uploads` — ✅ novo (05/2026)
361
-
362
- - **Finalidade:** evidências de ações nos Planos de Desenvolvimento Individual (PDI).
363
- - **Consumidor:** exclusivamente [PDI](/projects/7269db93-bd03-4b7d-842a-cd74404d2606)
364
- - `src/modules/plans/utils/evidenceStorage.ts` — helper centralizado (`uploadEvidence`, `resolveEvidenceUrl`, `getEvidenceSignedUrl`)
365
- - `src/modules/plans/components/tracking/EvidenceModal.tsx` — upload de evidências no acompanhamento
366
- - `src/modules/plans/components/ActionManagementModal.tsx` — upload/edição de evidências em ações
367
- - **Estrutura:** `{alias}/evidences/{timestamp}_{sanitized_filename}`.
368
- - **Visibilidade:** 🔒 **privado** — acesso via signed URLs (1h de validade).
369
- - **Limites:** sem restrição de MIME type ou tamanho no bucket (validação no front: 10 MB max).
370
- - **RLS atuais (3 policies, todas `TO authenticated` com scoping por alias):**
371
- - `pdi_uploads_select` — SELECT, `bucket_id = 'pdi-uploads' AND foldername[1] = jwt.alias`.
372
- - `pdi_uploads_insert` — INSERT, mesma condição em `WITH CHECK`.
373
- - `pdi_uploads_update` — UPDATE, mesma condição em `USING` e `WITH CHECK`.
374
- - **Sem policy de DELETE** — alinhado com a regra de soft delete do projeto (evidências são desvinculadas no registro, não apagadas do storage).
375
- - **Origem:** bucket criado durante migração de evidências do PDI, que antes usava `user-uploads/evidences/`. Migração concluída em 05/2026 (ver `docs/pdi-storage-migration-plan.md`).
376
- - **Riscos:**
377
- - 🟢 Isolamento multi-tenant via alias — correto desde a criação.
378
- - 🟢 Todas as policies usam `TO authenticated` (padrão correto).
379
- - 🟢 Signed URLs no front (bucket privado desde o início).
380
- - **Recomendação:** nenhuma ação necessária — bucket exemplar.
381
-
382
- ---
383
-
384
- ## 4. Padrões de policies (síntese)
385
-
386
- ### 4.1 Boas práticas observadas
387
-
388
- | Padrão | Onde aparece | Recomendar generalizar |
389
- |---|---|---|
390
- | `(storage.foldername(name))[1] = ((SELECT auth.jwt()) ->> 'alias')` | content-videos, contracts, imports, knowledge-files, training-requests, imported-evidence, resumes, performance, **pdi-uploads** | ✅ sim — virar padrão |
391
- | `TO authenticated` explícito | thumbnails, certificates, content-files, university-assets, content-videos, resumes, evidence_uploads, **pdi-uploads** | ✅ sim — converter as policies em role `public` |
392
- | `(SELECT auth.jwt())` (e não `auth.jwt()`) | a maioria das policies novas | ✅ obrigatório (linter) |
393
- | Bucket privado + signed URLs | certificates (Educação), contracts, performance, **pdi-uploads** | ✅ aplicar a thumbnails, content-files, content-videos |
394
-
395
- ### 4.2 Policies com role `public` (debt técnico)
396
-
397
- As seguintes policies usam `roles: {public}` com `auth.role() = 'authenticated'` no body em vez do mais correto `TO authenticated`. Funcionam, mas geram warnings no linter e são inconsistentes com o padrão:
398
-
399
- | Bucket | Policies afetadas |
400
- |--------|-------------------|
401
- | `career-banners` | `career_banners_upload`, `career_banners_delete` |
402
- | `contracts` | `contracts_select`, `contracts_insert`, `contracts_update` |
403
- | `imports` | `imports_bucket_select`, `imports_bucket_insert`, `imports_bucket_update`, `imports_bucket_delete` |
404
- | `knowledge-files` | `knowledge_files_select`, `knowledge_files_upload` |
405
- | `performance` | `performance_authenticated_insert`, `performance_authenticated_update`, `performance_authenticated_delete` |
406
- | `trainings` | `trainings_bucket_select`, `trainings_bucket_insert` |
407
- | `user-uploads` | `authenticated_interview_upload`, `interview_scoped_select`, `training_requests_*`, `imported_evidence_*` |
408
-
409
- **Recomendação:** migrar todas para `TO authenticated` em uma migration batch (P2 — sem mudança funcional).
410
-
411
- ---
412
-
413
- ## 5. Itens em aberto (a confirmar com o time)
414
-
415
- 1. **`knowledge-files`**: quem consome? É edge function?
416
- 2. **`performance`**: confirmar que o front de Desempenho já migrou de `getPublicUrl` para signed URLs após o bucket ser tornado privado.
417
- 3. **`certificates` precisa ser público?** O código de Educação já gera signed URLs, então não.
418
- 4. **`content-videos` privado?** Avaliar custo (player) vs benefício (proteger PII e propriedade intelectual).
419
- 5. **`user-uploads` para Matriz de Foco**: `evidence_uploads_select/delete` não checam alias — risco cross-tenant confirmado. (PDI já migrou para `pdi-uploads`).
420
- 6. **`resumes`**: policy permite `jpg/jpeg/png` mas bucket só aceita `pdf/doc/docx` — limpar policy.
421
-
422
- ---
423
-
424
- ## 6. Próximos passos sugeridos (priorizados)
425
-
426
- > Cada item é uma migração isolada — abrir uma issue/PR separada por bucket.
427
-
428
- | Prioridade | Bucket | Ação |
429
- |---|---|---|
430
- | ✅ done | `library-assets` | INSERT anônimo removido (sem policies de write). |
431
- | ✅ done | `thumbnails` | INSERT/UPDATE/DELETE `TO authenticated` (sem scoping — pendente ajuste no front). |
432
- | ✅ done | `performance` | Tornado privado + 4 policies com scoping por alias. |
433
- | ✅ done | `performance-files` | Bucket deprecado e removido. |
434
- | ✅ done | `pdi-uploads` | Bucket criado (privado) + 3 policies `TO authenticated` com scoping por alias. Migração de `user-uploads` concluída. |
435
- | 🔴 P0 | `user-uploads` | Substituir `authenticated_interview_upload` (INSERT genérico) por INSERT por subpasta + alias; adicionar checagem de alias em `evidence_uploads_select/delete` (Matriz de Foco). |
436
- | 🔴 P0 | `certificates` | Tornar privado + migrar Treinamentos para signed URLs. |
437
- | 🟠 P1 | `thumbnails` | Ajustar front de Educação para gravar em `{alias}/...` e reaplicar scoping por alias. |
438
- | 🟠 P1 | `university-assets`, `content-files` | Adicionar scoping por alias em UPDATE/DELETE/INSERT. |
439
- | 🟠 P1 | `resumes` | Unificar convenção de path (alias + uid), adicionar UPDATE/DELETE, remover extensões de imagem da policy. |
440
- | 🟡 P2 | Múltiplos | Padronizar ~20 policies de role `public` para `TO authenticated` (limpeza, sem mudança funcional). Ver seção 4.2. |
441
- | 🟡 P2 | `contracts`, `imports` | Padronizar `TO authenticated` (limpeza). |
442
- | 🟡 P2 | `career-banners` | Scoping por alias se for multi-tenant. |
443
- | 🟡 P2 | `content-videos` | Avaliar privado + signed URLs (impacto no player). |
444
-
445
- ---
446
-
447
- ## 7. Como manter este documento
448
-
449
- - Atualizar a cada migração que toque `storage.objects` ou `storage.buckets`.
450
- - Refresh trimestral dos números (objetos / tamanho) via:
451
- ```sql
452
- SELECT bucket_id, COUNT(*) AS objects,
453
- pg_size_pretty(SUM((metadata->>'size')::bigint)) AS total_size
454
- FROM storage.objects GROUP BY bucket_id ORDER BY bucket_id;
455
- ```
456
- - Quando um novo projeto consumir um bucket existente, adicioná-lo à matriz da seção 2 e à seção 3 do bucket.
1
+ # Supabase Storage — Inventário e mapa de consumidores
2
+
3
+ > **Projeto Supabase:** `ccjfvpnndclajkleyqkc` (qualiex-db, prod)
4
+ > **Snapshot:** 2026-05-04
5
+ > **Objetivo:** mapear todos os buckets, quem os consome, suas RLS atuais e os riscos para guiar o próximo ciclo de hardening.
6
+ > **Escopo:** apenas documentação — nenhuma policy, bucket ou código foi alterado.
7
+
8
+ ---
9
+
10
+ ## 1. Visão geral
11
+
12
+ 15 buckets ativos. Tamanho total ≈ 332 GB (dominado por `content-videos`).
13
+
14
+ | # | Bucket | Público | Objetos | Tamanho | MIME limit | File size limit |
15
+ |---|---|---|---:|---:|---|---|
16
+ | 1 | `career-banners` | ✅ | 4 | 349 kB | — | — |
17
+ | 2 | `certificates` | ✅ | 2.470 | 982 MB | — | — |
18
+ | 3 | `content-files` | ✅ | 5.474 | 2,7 GB | lista ampla | 2 GB |
19
+ | 4 | `content-videos` | ✅ | 1.929 | 326 GB | só vídeo | 3 GB |
20
+ | 5 | `contracts` | 🔒 | 151 | 47 MB | — | — |
21
+ | 6 | `imports` | 🔒 | 3 | 569 kB | — | — |
22
+ | 7 | `knowledge-files` | 🔒 | 8 | 24 MB | — | — |
23
+ | 8 | `library-assets` | ✅ | 10 | 105 kB | — | — |
24
+ | 9 | `pdi-uploads` | 🔒 | 8 | 14 MB | — | — |
25
+ | 10 | `performance` | 🔒 | 76 | 19 MB | — | — |
26
+ | 11 | `resumes` | 🔒 | 745 | 162 MB | pdf/doc/docx | 10 MB |
27
+ | 12 | `thumbnails` | ✅ | 1.158 | 585 MB | — | — |
28
+ | 13 | `trainings` | 🔒 | 27 | 13 MB | — | — |
29
+ | 14 | `university-assets` | ✅ | 284 | 308 MB | — | — |
30
+ | 15 | `user-uploads` | 🔒 | 1.001 | 1,5 GB | — | — |
31
+
32
+ > ℹ️ A coluna **Público** indica `storage.buckets.public`. Buckets públicos liberam download anônimo via `/storage/v1/object/public/<bucket>/<path>` e, sem policy explícita, também permitem `LIST` anônimo.
33
+
34
+ ### Changelog
35
+
36
+ | Data | Mudança |
37
+ |------|---------|
38
+ | 2026-05-04 | `performance` tornado privado com 4 policies (SELECT/INSERT/UPDATE/DELETE) escopadas por alias. `performance-files` removido (deprecado). Snapshot atualizado. |
39
+ | 2026-04-29 | Snapshot inicial. `library-assets` INSERT anônimo removido. `thumbnails` rollback para policies permissivas. |
40
+
41
+ ---
42
+
43
+ ## 2. Matriz Bucket × Projeto
44
+
45
+ Projetos do ecossistema (Lovable):
46
+
47
+ - [Admin / Configurações Core](/projects/9dc9be11-bf85-4561-b36a-8d8f35fdbc06) — `qualiex-admin` (este; lib `forlogic-core`)
48
+ - [Documentos](/projects/196d59d6-6e5c-4e49-b064-a24e57874298)
49
+ - [Configurações](/projects/b0970ba3-5c2a-4b7d-8d3b-5b2162b2d285)
50
+ - [OKR](/projects/53776a8a-ab33-494d-8b92-74341fd3d133)
51
+ - [Cockpit](/projects/e6853fb4-67f9-4776-b427-7768a9136f10)
52
+ - [Plano de Controle](/projects/d4712bc3-bc32-40a6-874c-401cf773c55c)
53
+ - [Colaboradores](/projects/37cdf18f-3d54-4af2-a02b-9f7e2d94e654)
54
+ - [Educação](/projects/075796dc-6ed4-43d3-92e3-3ab7f6314db6)
55
+ - [Matriz de Foco](/projects/e0a4fc74-f1c6-4466-8a9c-dcb31f03fb20)
56
+ - [Treinamentos](/projects/e02280e1-3f95-4114-8d83-80d3e8ced304)
57
+ - [Competências](/projects/ffc892d4-d7b4-46cd-883d-01ffeb99b2f4)
58
+ - [PDI](/projects/7269db93-bd03-4b7d-842a-cd74404d2606)
59
+ - [Desempenho](/projects/7c73eab6-bf0a-4cb5-93b3-e1809d1363d7)
60
+ - [Pulso](/projects/f49c7856-0bf8-4aa7-be0b-a6c5c5bf3ca1)
61
+
62
+ | Bucket | Admin | Educação | Colaboradores | Treinamentos | Matriz Foco | PDI | Desempenho | Cockpit | Documentos |
63
+ |---|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|
64
+ | `library-assets` | ✅ | | | | | | | | |
65
+ | `imports` | ✅ | | | | | | | | |
66
+ | `thumbnails` | | ✅ | | | | | | | |
67
+ | `university-assets` | | ✅ | | | | | | | |
68
+ | `certificates` | | ✅ | | 🔎 read | | | | | |
69
+ | `content-files` | | ✅ | | | | | | | |
70
+ | `content-videos` | | ✅ | | | | | | | |
71
+ | `career-banners` | | | ✅ | | | | | | |
72
+ | `resumes` | | | ✅ | | | | | (policy) | |
73
+ | `contracts` | | | ✅ | | | | | | |
74
+ | `user-uploads` | | | ✅ (interviews) | ✅ (training-requests, trainings, imported-evidence) | ✅ (evidence-images, evidence-attachments) | | | | |
75
+ | `pdi-uploads` | | | | | | ✅ (evidences) | | | |
76
+ | `trainings` | | | | ✅ | | | | | |
77
+ | `performance` | | | | | | | ✅ (1:1) | | |
78
+ | `knowledge-files` | | | | | | | | | ❓ |
79
+
80
+ Legenda: ✅ uso confirmado em código · 🔎 só leitura · (policy) tem RLS mas sem código consumidor encontrado · ❓ a confirmar com o time.
81
+
82
+ ---
83
+
84
+ ## 3. Detalhamento por bucket
85
+
86
+ ### 3.1 `library-assets` — ✅ corrigido
87
+
88
+ - **Finalidade:** logos, favicons e marca branca da lib (Qualiex / Saber).
89
+ - **Consumidor:** [Admin](/projects/9dc9be11-bf85-4561-b36a-8d8f35fdbc06) — `lib/assets/index.ts`, `lib/setup/favicon.ts`. Consumido como leitura pública por todos os projetos via URL pública.
90
+ - **Estrutura:** raiz do bucket (`logo-qualiex-white.svg`, `favicon.png`, etc.).
91
+ - **Visibilidade:** público.
92
+ - **RLS atuais:** **nenhuma policy de write** no `storage.objects` para esse bucket. SELECT continua público pelo flag de bucket público. INSERT/UPDATE/DELETE bloqueados para qualquer role (apenas service_role contorna RLS).
93
+ - **Mudança recente:** a antiga policy `Allow public upload to library assets` (INSERT anônimo sem condição) foi **removida**.
94
+ - **Riscos:**
95
+ - 🟢 Upload anônimo eliminado.
96
+ - 🟡 `LIST` anônimo (default de bucket público) ainda expõe inventário, mas como são logos é baixo impacto.
97
+ - **Recomendação:** se precisar permitir upload via app, adicionar policy `TO authenticated` + checagem de admin. Caso contrário, manter como está (gerenciado via service_role/console).
98
+
99
+ ---
100
+
101
+ ### 3.2 `imports` — ok, com ressalvas
102
+
103
+ - **Finalidade:** arquivos Excel/CSV submetidos pelo wizard de importação.
104
+ - **Consumidor:** [Admin](/projects/9dc9be11-bf85-4561-b36a-8d8f35fdbc06) — `src/imports/wizard/services/importJobService.ts`.
105
+ - **Estrutura:** `{alias}/{timestamp}_{filename}`.
106
+ - **Visibilidade:** privado.
107
+ - **RLS atuais:**
108
+ - `imports_bucket_select` — SELECT, role `public`, `auth.role() = 'authenticated'` + `foldername[1] = jwt.alias`.
109
+ - `imports_bucket_insert` — INSERT, role `public`, `auth.role() = 'authenticated'` + `foldername[1] = jwt.alias`.
110
+ - `imports_bucket_update` — UPDATE, role `public`, `auth.role() = 'authenticated'` + `foldername[1] = jwt.alias`.
111
+ - `imports_bucket_delete` — DELETE, role `public`, `auth.role() = 'authenticated'` + `foldername[1] = jwt.alias`.
112
+ - **Riscos:**
113
+ - 🟡 Policies estão atribuídas ao role `public` — funcional, mas inconsistente com o padrão `TO authenticated`.
114
+ - **Recomendação:** converter as policies para `TO authenticated` (mais explícito e linter-friendly).
115
+
116
+ ---
117
+
118
+ ### 3.3 `thumbnails` — ⚠️ sem scoping por alias
119
+
120
+ - **Finalidade:** miniaturas/capas de cursos e conteúdos.
121
+ - **Consumidor:** [Educação](/projects/075796dc-6ed4-43d3-92e3-3ab7f6314db6) — `src/modules/contents/hooks/useImageUpload.ts` (default bucket).
122
+ - **Estrutura atual:** sem prefixo obrigatório (uploads vão na raiz).
123
+ - **Visibilidade:** público.
124
+ - **RLS atuais:**
125
+ - `Users can upload thumbnails` — INSERT `TO authenticated`, sem scoping por alias.
126
+ - `Users can update thumbnails` — UPDATE `TO authenticated`, sem scoping por alias.
127
+ - `Users can delete thumbnails` — DELETE `TO authenticated`, sem scoping por alias.
128
+ - **Histórico:**
129
+ - Hardening anterior (`thumbnails_auth_insert/update/delete` exigindo `foldername[1] = jwt.alias`) quebrou os uploads do projeto Educação, que escreve direto na raiz do bucket.
130
+ - Em 29/04/2026 fizemos rollback para o estado anterior (3 policies permissivas para `authenticated`).
131
+ - **Riscos:**
132
+ - 🔴 Sem isolamento multi-tenant: qualquer usuário autenticado pode sobrescrever/apagar arquivos de qualquer tenant.
133
+ - 🟡 `LIST` público (bucket público) — inventário visível.
134
+ - **Próximo passo (P1):** ajustar `src/modules/contents/hooks/useImageUpload.ts` para gravar em `{alias}/...` e então reaplicar as policies com scoping por alias.
135
+
136
+ ---
137
+
138
+ ### 3.4 `university-assets` — média
139
+
140
+ - **Finalidade:** imagens da plataforma de cursos (banners, ilustrações, IA-docs).
141
+ - **Consumidor:** [Educação](/projects/075796dc-6ed4-43d3-92e3-3ab7f6314db6) — `src/hooks/useImageUpload.ts`, `src/modules/contents/services/aiDocsService.ts`.
142
+ - **Estrutura:** `{alias}/...` em parte das chamadas (não obrigatório).
143
+ - **Visibilidade:** público.
144
+ - **RLS atuais:**
145
+ - `university_assets_auth_insert` — INSERT `TO authenticated`, `jwt.alias IS NOT NULL`. Sem scoping por pasta.
146
+ - `university_assets_auth_update` — UPDATE `TO authenticated`, `jwt.alias IS NOT NULL`. Sem scoping por pasta.
147
+ - `university_assets_auth_delete` — DELETE `TO authenticated`, `jwt.alias IS NOT NULL`. Sem scoping por pasta.
148
+ - **Riscos:**
149
+ - 🟠 Qualquer usuário autenticado de qualquer tenant pode sobrescrever/excluir arquivos de qualquer outro tenant.
150
+ - 🟡 `LIST` público.
151
+ - **Recomendação:** adicionar scoping `(storage.foldername(name))[1] = jwt.alias` no INSERT/UPDATE/DELETE.
152
+
153
+ ---
154
+
155
+ ### 3.5 `certificates` — média
156
+
157
+ - **Finalidade:** PDFs/imagens de certificados emitidos para alunos.
158
+ - **Consumidores:**
159
+ - [Educação](/projects/075796dc-6ed4-43d3-92e3-3ab7f6314db6) — `src/hooks/useUserCertificateUpload.ts`, `src/hooks/useBulkCertificateUpload.ts` (uploads usam **signed URLs** com TTL 1 ano).
160
+ - [Treinamentos](/projects/e02280e1-3f95-4114-8d83-80d3e8ced304) — `src/training/utils/evidenceUrlResolver.ts` (leitura via `getPublicUrl`).
161
+ - **Estrutura:** sem padrão por alias (arquivos no root).
162
+ - **Visibilidade:** público (mesmo o código gerando signed URLs — ou seja, a privacidade pretendida não é real).
163
+ - **RLS atuais:**
164
+ - `Authenticated users can upload certificates` — INSERT `TO authenticated`, sem scoping.
165
+ - `Authenticated users can update certificates` — UPDATE `TO authenticated`, sem scoping.
166
+ - `Authenticated users can delete certificates` — DELETE `TO authenticated`, sem scoping.
167
+ - **Riscos:**
168
+ - 🔴 Certificados são **PII** (nome do aluno, curso, datas). Bucket público + sem scoping = qualquer URL adivinhada/descoberta vaza dados pessoais.
169
+ - 🟠 `LIST` anônimo expõe inventário com nomes de arquivo.
170
+ - **Recomendação:** **tornar privado** e migrar Treinamentos para usar signed URLs (já é o padrão de Educação). Adicionar scoping por alias.
171
+
172
+ ---
173
+
174
+ ### 3.6 `content-files` — média/alta
175
+
176
+ - **Finalidade:** materiais de cursos (apostilas, planilhas, pacotes SCORM).
177
+ - **Consumidor:** [Educação](/projects/075796dc-6ed4-43d3-92e3-3ab7f6314db6) — `src/modules/contents/hooks/useFileUpload.ts`, `docs/SCORM_IMPLEMENTATION.md`.
178
+ - **Estrutura:** `{packageFolder}/...` (SCORM); demais sem padrão claro.
179
+ - **Visibilidade:** público.
180
+ - **RLS atuais:**
181
+ - `Authenticated users can upload content-files` — INSERT `TO authenticated`, sem scoping.
182
+ - `Authenticated users can update content-files` — UPDATE `TO authenticated`, sem scoping.
183
+ - `Authenticated users can delete content-files` — DELETE `TO authenticated`, sem scoping.
184
+ - **Riscos:**
185
+ - 🟠 Materiais de treinamento de um cliente podem estar acessíveis publicamente para quem tiver a URL/listar o bucket.
186
+ - 🟡 `LIST` público.
187
+ - **Recomendação:** scoping por `alias` e avaliar deixar privado com signed URLs (impacta player de cursos — exige mudança no front).
188
+
189
+ ---
190
+
191
+ ### 3.7 `content-videos` — ok (mais maduro)
192
+
193
+ - **Finalidade:** vídeos das aulas de Educação.
194
+ - **Consumidor:** [Educação](/projects/075796dc-6ed4-43d3-92e3-3ab7f6314db6) — `src/contexts/UploadQueueContext.tsx`, `src/modules/contents/hooks/useVideoUpload.ts`.
195
+ - **Estrutura:** `{alias}/{filename}`.
196
+ - **Visibilidade:** público.
197
+ - **RLS atuais:**
198
+ - `content_videos_auth_insert` — INSERT `TO authenticated`, `foldername[1] = jwt.alias`.
199
+ - `content_videos_auth_update` — UPDATE `TO authenticated`, `foldername[1] = jwt.alias`.
200
+ - `content_videos_auth_delete` — DELETE `TO authenticated`, `foldername[1] = jwt.alias`.
201
+ - **Riscos:**
202
+ - 🟠 Vídeos premium acessíveis por URL pública direta.
203
+ - 🟡 `LIST` público (alto volume — 326 GB).
204
+ - **Recomendação:** considerar privado + signed URLs (igual a Vimeo/Mux). Custo: rever player e CDN.
205
+
206
+ ---
207
+
208
+ ### 3.8 `career-banners` — baixa
209
+
210
+ - **Finalidade:** banners da página pública de carreiras.
211
+ - **Consumidor:** [Colaboradores](/projects/37cdf18f-3d54-4af2-a02b-9f7e2d94e654).
212
+ - **Estrutura:** raiz.
213
+ - **Visibilidade:** público.
214
+ - **RLS atuais:**
215
+ - `career_banners_upload` — INSERT, role `public`, `auth.role() = 'authenticated'`.
216
+ - `career_banners_delete` — DELETE, role `public`, `auth.role() = 'authenticated'`.
217
+ - **Riscos:**
218
+ - 🟡 Sem scoping por alias — qualquer admin de qualquer tenant pode deletar banners de outro.
219
+ - 🟡 Policies em role `public` em vez de `TO authenticated`.
220
+ - **Recomendação:** scoping por alias se for multi-tenant. Padronizar para `TO authenticated`.
221
+
222
+ ---
223
+
224
+ ### 3.9 `resumes` — média
225
+
226
+ - **Finalidade:** currículos de candidatos (RH).
227
+ - **Consumidores:**
228
+ - [Colaboradores](/projects/37cdf18f-3d54-4af2-a02b-9f7e2d94e654) — fluxo de RH.
229
+ - [Cockpit](/projects/e6853fb4-67f9-4776-b427-7768a9136f10) — migração `20260330141432` cria policy escopada por `auth.uid()`.
230
+ - **Estrutura:** `{alias}/{filename}` (Colaboradores) e `{auth.uid()}/...` (Cockpit) — **dois esquemas convivendo**.
231
+ - **Visibilidade:** privado.
232
+ - **RLS atuais:**
233
+ - `resumes_alias_select` — SELECT `TO authenticated`, `foldername[1] = jwt.alias`.
234
+ - `resumes_auth_upload` — INSERT `TO authenticated`, `foldername[1] = jwt.alias` + extensões `pdf|doc|docx|jpg|jpeg|png`.
235
+ - **⚠️ Incongruência MIME:** o bucket aceita apenas `pdf/doc/docx` (configuração `allowed_mime_types`), mas a policy `resumes_auth_upload` lista também `jpg|jpeg|png`. Na prática o bucket rejeitaria imagens pelo MIME antes da policy ser avaliada, mas a policy está desatualizada.
236
+ - **Riscos:**
237
+ - 🟠 Há duas convenções de path conflitantes (alias vs uid). Pode haver arquivos "órfãos" que não casam com nenhuma policy.
238
+ - 🟠 Não há policy explícita de UPDATE/DELETE listada.
239
+ - **Recomendação:** alinhar uma única convenção (`{alias}/{auth.uid()}/{file}` cobre os dois) e adicionar UPDATE/DELETE. Remover extensões de imagem da policy para casar com o bucket.
240
+
241
+ ---
242
+
243
+ ### 3.10 `contracts` — ok
244
+
245
+ - **Finalidade:** templates DOCX e contratos gerados.
246
+ - **Consumidor:** [Colaboradores](/projects/37cdf18f-3d54-4af2-a02b-9f7e2d94e654) — `src/contracts/contractService.ts`, edge function `contract-processor`.
247
+ - **Estrutura:** `{templates|generated}/{alias}/{filename}`.
248
+ - **Visibilidade:** privado.
249
+ - **RLS atuais:**
250
+ - `contracts_select` — SELECT, role `public`, `foldername[1] IN ('templates','generated')` + `foldername[2] = jwt.alias`.
251
+ - `contracts_insert` — INSERT, role `public`, mesma condição.
252
+ - `contracts_update` — UPDATE, role `public`, mesma condição.
253
+ - `contracts_delete` — DELETE `TO authenticated`, mesma condição.
254
+ - **Riscos:**
255
+ - 🟢 Bem segregado por alias.
256
+ - 🟡 SELECT/INSERT/UPDATE em role `public` (funcional mas inconsistente).
257
+ - **Recomendação:** padronizar para `TO authenticated`.
258
+
259
+ ---
260
+
261
+ ### 3.11 `user-uploads` — 🚨 alta complexidade
262
+
263
+ Bucket multi-projeto, segregado por subpasta. **Usado por 3 projetos** (Colaboradores, Treinamentos, Matriz de Foco).
264
+
265
+ - **Visibilidade:** privado.
266
+ - **Estrutura por consumidor:**
267
+
268
+ | Subpasta raiz | Projeto | Uso |
269
+ |---|---|---|
270
+ | `interviews/{alias}/...` | Colaboradores | Áudios/anexos de entrevistas |
271
+ | `training-requests/{alias}/...` | Treinamentos | Anexos de solicitações de treinamento |
272
+ | `imported-evidence/{alias}/...` | Treinamentos | Evidências importadas em massa |
273
+ | `trainings/.../{alias}/...` | Treinamentos | Evidências de treinamento (alias na 3ª pasta) |
274
+ | `evidence-images/...` | Matriz de Foco | Imagens coladas no rich-text editor |
275
+ | `evidence-attachments/{evidenceId}/...` | Matriz de Foco | Anexos de evidências |
276
+
277
+ - **RLS atuais (05/05/2026):**
278
+
279
+ | Policy | Cmd | Role | Scoping |
280
+ |--------|-----|------|---------|
281
+ | `interview_scoped_select` | SELECT | `public` | `foldername[1] = 'interviews'` + `foldername[2] = jwt.alias` |
282
+ | `authenticated_interview_upload` | INSERT | `public` | `auth.role() = 'authenticated'` — **sem scoping por subpasta/alias** ⚠️ |
283
+ | `training_requests_attachments_select` | SELECT | `public` | `foldername[1] = 'training-requests'` + `foldername[2] = jwt.alias` |
284
+ | `training_requests_attachments_insert` | INSERT | `public` | `foldername[1] = 'training-requests'` + `foldername[2] = jwt.alias` |
285
+ | `training_requests_attachments_delete` | DELETE | `public` | `foldername[1] = 'training-requests'` + `foldername[2] = jwt.alias` |
286
+ | `imported_evidence_select` | SELECT | `public` | `foldername[1] = 'imported-evidence'` + `foldername[2] = jwt.alias` |
287
+ | `imported_evidence_insert` | INSERT | `public` | `foldername[1] = 'imported-evidence'` + `foldername[2] = jwt.alias` |
288
+ | `evidence_uploads_select` | SELECT | `authenticated` | `foldername[1] IN ('evidence-images','evidence-attachments')` + `jwt.alias IS NOT NULL` — **sem checagem de alias** ⚠️ |
289
+ | `evidence_uploads_delete` | DELETE | `authenticated` | `foldername[1] IN ('evidence-images','evidence-attachments')` + `jwt.alias IS NOT NULL` — **sem checagem de alias** ⚠️ |
290
+ | `Authenticated users can update user-uploads` | UPDATE | `authenticated` | `foldername[2] = jwt.alias` (cobre `*/{alias}/...`, mas não `evidence-images/` nem `evidences/`) |
291
+
292
+ - **Riscos:**
293
+ - 🔴 `authenticated_interview_upload` continua permitindo que qualquer authenticated grave em qualquer subpasta do bucket (cross-tenant + cross-projeto).
294
+ - 🔴 `evidence_uploads_select/delete` não checam alias — qualquer authenticated lê/deleta evidências da Matriz de Foco de outros tenants.
295
+ - 🟠 Padrão de path inconsistente entre projetos (uns usam alias na 1ª pasta, outros na 3ª, outros não usam).
296
+
297
+ - **Recomendação:**
298
+ - Padronizar para `{projeto}/{alias}/...`.
299
+ - Substituir `authenticated_interview_upload` por INSERT por subpasta + alias.
300
+ - Adicionar checagem de alias em `evidence_uploads_select/delete` (Matriz de Foco).
301
+
302
+ ---
303
+
304
+ ### 3.12 `trainings` — ok
305
+
306
+ - **Finalidade:** evidências de treinamentos (PDFs, fotos).
307
+ - **Consumidor:** [Treinamentos](/projects/e02280e1-3f95-4114-8d83-80d3e8ced304) — `LeaderResolveModal`, `LeaderPendingSelectModal`.
308
+ - **Estrutura:** `.../.../{alias}/...` (alias na 3ª pasta — esquisito).
309
+ - **Visibilidade:** privado.
310
+ - **RLS atuais:**
311
+ - `trainings_bucket_select` — SELECT, role `public`, `foldername[2] = jwt.alias`.
312
+ - `trainings_bucket_insert` — INSERT, role `public`, `foldername[2] = jwt.alias`.
313
+ - **Riscos:**
314
+ - 🟡 Discrepância: a policy usa pasta `[2]`, mas o resolver de evidências em `evidenceUrlResolver.ts` aceita o bucket. Validar se o path real bate.
315
+ - 🟠 Sem policy explícita de UPDATE/DELETE.
316
+ - 🟡 Policies em role `public` (funcional mas inconsistente).
317
+ - **Recomendação:** confirmar convenção de path, adicionar UPDATE/DELETE escopados e padronizar para `TO authenticated`.
318
+
319
+ ---
320
+
321
+ ### 3.13 `performance` — ✅ corrigido (05/2026)
322
+
323
+ - **Finalidade:** anexos do módulo de **One-on-One** dentro de Desempenho — evidências de tarefas e relatórios.
324
+ - **Consumidor:** [Desempenho](/projects/7c73eab6-bf0a-4cb5-93b3-e1809d1363d7)
325
+ - `src/one-on-ones/services/evidenceService.ts` → pasta `evidences/`
326
+ - `src/one-on-ones/services/taskReportService.ts` → pasta `task-reports/`
327
+ - **Estrutura:** `{alias}/evidences/...` e `{alias}/task-reports/...`.
328
+ - **Visibilidade:** 🔒 **privado** (alterado de público para privado).
329
+ - **RLS atuais (pós-hardening):**
330
+ - `performance_authenticated_select` — SELECT `TO authenticated`, `jwt.alias = foldername[1]`.
331
+ - `performance_authenticated_insert` — INSERT, role `public`, `jwt.alias = foldername[1]`.
332
+ - `performance_authenticated_update` — UPDATE, role `public`, `jwt.alias = foldername[1]`.
333
+ - `performance_authenticated_delete` — DELETE, role `public`, `jwt.alias = foldername[1]`.
334
+ - **Mudança recente:** bucket tornado privado + 4 policies com scoping por alias na 1ª pasta. Resolve os riscos P0 apontados anteriormente.
335
+ - **Riscos:**
336
+ - 🟢 Isolamento multi-tenant via alias — resolvido.
337
+ - 🟡 INSERT/UPDATE/DELETE em role `public` (funcional mas inconsistente com o padrão `TO authenticated`).
338
+ - 🟡 O código de Desempenho pode precisar migrar de `getPublicUrl` para `createSignedUrl` (bucket agora privado).
339
+ - **Recomendação:** padronizar policies para `TO authenticated`. Confirmar que o front de Desempenho já usa signed URLs.
340
+
341
+ ---
342
+
343
+ ### 3.14 `knowledge-files` — ❓ a confirmar
344
+
345
+ - **Finalidade provável:** base de conhecimento para IA / chatbot.
346
+ - **Consumidor:** sem uso direto encontrado em nenhum dos 14 projetos. 8 objetos, último em março/2026.
347
+ - **Estrutura:** `{alias}/...`.
348
+ - **Visibilidade:** privado.
349
+ - **RLS atuais:**
350
+ - `knowledge_files_select` — SELECT, role `public`, `auth.role() = 'authenticated'` + `foldername[1] = jwt.alias`.
351
+ - `knowledge_files_upload` — INSERT, role `public`, `auth.role() = 'authenticated'` + `foldername[1] = jwt.alias`.
352
+ - **Riscos:**
353
+ - 🟡 Sem UPDATE/DELETE explícitos.
354
+ - 🟡 Provavelmente consumido por uma edge function (não por front) — confirmar.
355
+ - 🟡 Policies em role `public` (funcional mas inconsistente).
356
+ - **Recomendação:** identificar consumidor e validar fluxo antes de mexer.
357
+
358
+ ---
359
+
360
+ ### 3.15 `pdi-uploads` — ✅ novo (05/2026)
361
+
362
+ - **Finalidade:** evidências de ações nos Planos de Desenvolvimento Individual (PDI).
363
+ - **Consumidor:** exclusivamente [PDI](/projects/7269db93-bd03-4b7d-842a-cd74404d2606)
364
+ - `src/modules/plans/utils/evidenceStorage.ts` — helper centralizado (`uploadEvidence`, `resolveEvidenceUrl`, `getEvidenceSignedUrl`)
365
+ - `src/modules/plans/components/tracking/EvidenceModal.tsx` — upload de evidências no acompanhamento
366
+ - `src/modules/plans/components/ActionManagementModal.tsx` — upload/edição de evidências em ações
367
+ - **Estrutura:** `{alias}/evidences/{timestamp}_{sanitized_filename}`.
368
+ - **Visibilidade:** 🔒 **privado** — acesso via signed URLs (1h de validade).
369
+ - **Limites:** sem restrição de MIME type ou tamanho no bucket (validação no front: 10 MB max).
370
+ - **RLS atuais (3 policies, todas `TO authenticated` com scoping por alias):**
371
+ - `pdi_uploads_select` — SELECT, `bucket_id = 'pdi-uploads' AND foldername[1] = jwt.alias`.
372
+ - `pdi_uploads_insert` — INSERT, mesma condição em `WITH CHECK`.
373
+ - `pdi_uploads_update` — UPDATE, mesma condição em `USING` e `WITH CHECK`.
374
+ - **Sem policy de DELETE** — alinhado com a regra de soft delete do projeto (evidências são desvinculadas no registro, não apagadas do storage).
375
+ - **Origem:** bucket criado durante migração de evidências do PDI, que antes usava `user-uploads/evidences/`. Migração concluída em 05/2026 (ver `docs/pdi-storage-migration-plan.md`).
376
+ - **Riscos:**
377
+ - 🟢 Isolamento multi-tenant via alias — correto desde a criação.
378
+ - 🟢 Todas as policies usam `TO authenticated` (padrão correto).
379
+ - 🟢 Signed URLs no front (bucket privado desde o início).
380
+ - **Recomendação:** nenhuma ação necessária — bucket exemplar.
381
+
382
+ ---
383
+
384
+ ## 4. Padrões de policies (síntese)
385
+
386
+ ### 4.1 Boas práticas observadas
387
+
388
+ | Padrão | Onde aparece | Recomendar generalizar |
389
+ |---|---|---|
390
+ | `(storage.foldername(name))[1] = ((SELECT auth.jwt()) ->> 'alias')` | content-videos, contracts, imports, knowledge-files, training-requests, imported-evidence, resumes, performance, **pdi-uploads** | ✅ sim — virar padrão |
391
+ | `TO authenticated` explícito | thumbnails, certificates, content-files, university-assets, content-videos, resumes, evidence_uploads, **pdi-uploads** | ✅ sim — converter as policies em role `public` |
392
+ | `(SELECT auth.jwt())` (e não `auth.jwt()`) | a maioria das policies novas | ✅ obrigatório (linter) |
393
+ | Bucket privado + signed URLs | certificates (Educação), contracts, performance, **pdi-uploads** | ✅ aplicar a thumbnails, content-files, content-videos |
394
+
395
+ ### 4.2 Policies com role `public` (debt técnico)
396
+
397
+ As seguintes policies usam `roles: {public}` com `auth.role() = 'authenticated'` no body em vez do mais correto `TO authenticated`. Funcionam, mas geram warnings no linter e são inconsistentes com o padrão:
398
+
399
+ | Bucket | Policies afetadas |
400
+ |--------|-------------------|
401
+ | `career-banners` | `career_banners_upload`, `career_banners_delete` |
402
+ | `contracts` | `contracts_select`, `contracts_insert`, `contracts_update` |
403
+ | `imports` | `imports_bucket_select`, `imports_bucket_insert`, `imports_bucket_update`, `imports_bucket_delete` |
404
+ | `knowledge-files` | `knowledge_files_select`, `knowledge_files_upload` |
405
+ | `performance` | `performance_authenticated_insert`, `performance_authenticated_update`, `performance_authenticated_delete` |
406
+ | `trainings` | `trainings_bucket_select`, `trainings_bucket_insert` |
407
+ | `user-uploads` | `authenticated_interview_upload`, `interview_scoped_select`, `training_requests_*`, `imported_evidence_*` |
408
+
409
+ **Recomendação:** migrar todas para `TO authenticated` em uma migration batch (P2 — sem mudança funcional).
410
+
411
+ ---
412
+
413
+ ## 5. Itens em aberto (a confirmar com o time)
414
+
415
+ 1. **`knowledge-files`**: quem consome? É edge function?
416
+ 2. **`performance`**: confirmar que o front de Desempenho já migrou de `getPublicUrl` para signed URLs após o bucket ser tornado privado.
417
+ 3. **`certificates` precisa ser público?** O código de Educação já gera signed URLs, então não.
418
+ 4. **`content-videos` privado?** Avaliar custo (player) vs benefício (proteger PII e propriedade intelectual).
419
+ 5. **`user-uploads` para Matriz de Foco**: `evidence_uploads_select/delete` não checam alias — risco cross-tenant confirmado. (PDI já migrou para `pdi-uploads`).
420
+ 6. **`resumes`**: policy permite `jpg/jpeg/png` mas bucket só aceita `pdf/doc/docx` — limpar policy.
421
+
422
+ ---
423
+
424
+ ## 6. Próximos passos sugeridos (priorizados)
425
+
426
+ > Cada item é uma migração isolada — abrir uma issue/PR separada por bucket.
427
+
428
+ | Prioridade | Bucket | Ação |
429
+ |---|---|---|
430
+ | ✅ done | `library-assets` | INSERT anônimo removido (sem policies de write). |
431
+ | ✅ done | `thumbnails` | INSERT/UPDATE/DELETE `TO authenticated` (sem scoping — pendente ajuste no front). |
432
+ | ✅ done | `performance` | Tornado privado + 4 policies com scoping por alias. |
433
+ | ✅ done | `performance-files` | Bucket deprecado e removido. |
434
+ | ✅ done | `pdi-uploads` | Bucket criado (privado) + 3 policies `TO authenticated` com scoping por alias. Migração de `user-uploads` concluída. |
435
+ | 🔴 P0 | `user-uploads` | Substituir `authenticated_interview_upload` (INSERT genérico) por INSERT por subpasta + alias; adicionar checagem de alias em `evidence_uploads_select/delete` (Matriz de Foco). |
436
+ | 🔴 P0 | `certificates` | Tornar privado + migrar Treinamentos para signed URLs. |
437
+ | 🟠 P1 | `thumbnails` | Ajustar front de Educação para gravar em `{alias}/...` e reaplicar scoping por alias. |
438
+ | 🟠 P1 | `university-assets`, `content-files` | Adicionar scoping por alias em UPDATE/DELETE/INSERT. |
439
+ | 🟠 P1 | `resumes` | Unificar convenção de path (alias + uid), adicionar UPDATE/DELETE, remover extensões de imagem da policy. |
440
+ | 🟡 P2 | Múltiplos | Padronizar ~20 policies de role `public` para `TO authenticated` (limpeza, sem mudança funcional). Ver seção 4.2. |
441
+ | 🟡 P2 | `contracts`, `imports` | Padronizar `TO authenticated` (limpeza). |
442
+ | 🟡 P2 | `career-banners` | Scoping por alias se for multi-tenant. |
443
+ | 🟡 P2 | `content-videos` | Avaliar privado + signed URLs (impacto no player). |
444
+
445
+ ---
446
+
447
+ ## 7. Como manter este documento
448
+
449
+ - Atualizar a cada migração que toque `storage.objects` ou `storage.buckets`.
450
+ - Refresh trimestral dos números (objetos / tamanho) via:
451
+ ```sql
452
+ SELECT bucket_id, COUNT(*) AS objects,
453
+ pg_size_pretty(SUM((metadata->>'size')::bigint)) AS total_size
454
+ FROM storage.objects GROUP BY bucket_id ORDER BY bucket_id;
455
+ ```
456
+ - Quando um novo projeto consumir um bucket existente, adicioná-lo à matriz da seção 2 e à seção 3 do bucket.