funifier-mcp 0.3.20 → 0.3.22

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 (45) hide show
  1. package/.cursor/rules/funifier.mdc +1 -0
  2. package/.github/copilot-instructions.md +1 -0
  3. package/AGENTS.md +1 -0
  4. package/CHANGELOG.md +31 -0
  5. package/datasource-funifier-docs/.coverage.json +22 -6
  6. package/datasource-funifier-docs/.validation.json +87 -11
  7. package/datasource-funifier-docs/knowledge/guides/database-access.md +3 -0
  8. package/datasource-funifier-docs/knowledge/guides/explain-diagnostics.md +170 -0
  9. package/datasource-funifier-docs/knowledge/guides/server-utils.md +718 -0
  10. package/datasource-funifier-docs/knowledge/index.md +4 -0
  11. package/datasource-funifier-docs/knowledge/modules/custom-object.md +12 -7
  12. package/datasource-funifier-docs/knowledge/modules/database.md +48 -5
  13. package/dist/mcp/bundle.js +131 -108
  14. package/dist/mcp/tools/database.d.ts.map +1 -1
  15. package/dist/mcp/tools/database.js +14 -2
  16. package/dist/mcp/tools/database.js.map +1 -1
  17. package/dist/mcp/tools/database.test.js +16 -0
  18. package/dist/mcp/tools/database.test.js.map +1 -1
  19. package/dist/mcp/tools/db-explain.d.ts +4 -0
  20. package/dist/mcp/tools/db-explain.d.ts.map +1 -0
  21. package/dist/mcp/tools/db-explain.js +187 -0
  22. package/dist/mcp/tools/db-explain.js.map +1 -0
  23. package/dist/mcp/tools/db-explain.test.d.ts +2 -0
  24. package/dist/mcp/tools/db-explain.test.d.ts.map +1 -0
  25. package/dist/mcp/tools/db-explain.test.js +142 -0
  26. package/dist/mcp/tools/db-explain.test.js.map +1 -0
  27. package/dist/mcp/tools/index.d.ts.map +1 -1
  28. package/dist/mcp/tools/index.js +2 -0
  29. package/dist/mcp/tools/index.js.map +1 -1
  30. package/dist/mcp/tools/list-tools.d.ts.map +1 -1
  31. package/dist/mcp/tools/list-tools.js +8 -0
  32. package/dist/mcp/tools/list-tools.js.map +1 -1
  33. package/package.json +1 -1
  34. package/skills/funifier/SKILL.md +1 -0
  35. package/skills/funifier/references/create-aggregate.md +10 -5
  36. package/skills/funifier/references/create-audit.md +0 -8
  37. package/skills/funifier/references/create-custom-object.md +0 -6
  38. package/skills/funifier/references/date-handling.md +0 -6
  39. package/skills/funifier/references/debug.md +2 -1
  40. package/skills/funifier/references/help.md +0 -6
  41. package/skills/funifier/references/manage-indexes.md +0 -6
  42. package/skills/funifier/references/query-aggregate.md +2 -6
  43. package/skills/funifier/references/server-utils.md +94 -0
  44. package/datasource-funifier-docs/.search-index.json +0 -59689
  45. package/datasource-funifier-docs/.skills-map.json +0 -150
@@ -103,9 +103,11 @@ A Funifier funciona como um **backend de aplicações**. Independentemente do ti
103
103
  | Triggers | `guides/triggers-guide.md` | Guia completo de triggers | Referência de eventos, entidades, managers e exemplos de código Java |
104
104
  | Trigger Examples | `guides/trigger-examples.md` | Exemplos de produção de triggers | Código Groovy pronto para padrões reais: atualizar coleção relacionada, criar achievement, inicializar jogador, normalizar/rejeitar `__c`, processar lote, registrar ação via `track` |
105
105
  | Database Access | `guides/database-access.md` | Acesso ao banco de dados | Referência de acesso via API REST e via código Java (Jongo) |
106
+ | Explain / Diagnóstico | `guides/explain-diagnostics.md` | Plano de consulta real (`explain`) do MongoDB | Diagnosticar query lenta / timeout de `DELETE`: obter `winningPlan`, `IXSCAN` vs `COLLSCAN`, `totalDocsExamined` via scheduler (`runCommand explain`), já que o endpoint REST só executa a query. Inclui alternativa `$indexStats`/`$collStats` sem scheduler |
106
107
  | Java Managers | `guides/java-managers.md` | Referência de managers Java | Métodos disponíveis em cada manager (PlayerManager, ActionManager, etc.) para uso em triggers, schedulers e public endpoints |
107
108
  | Java Entities | `guides/java-entities.md` | Referência de entidades Java | Campos e tipos dos objetos (Player, Achievement, ActionLog, etc.) e mapeamento para coleções MongoDB |
108
109
  | Java Libraries | `guides/java-libraries.md` | Bibliotecas e utilitários Java | JsonUtil, Guid, DateUtil, Unirest, EmailBuilder, Jongo — métodos e exemplos de uso |
110
+ | Server-side Utils | `guides/server-utils.md` | Classes utilitárias do pacote `com.funifier.engine.util` | CpfUtil, AesCrypt, BCrypt, PgpCrypt, OtpAuthUtil, DiffUtil, ExpressionUtils, MustacheUtils, StringUtil, FormatterUtil, ParserUtil, HttpUtil, UrlUtil, ExcelUtil, PaginationUtil, FileUtil — use quando precisar validar CPF, criptografar campo, renderizar template, comparar objetos, gerar Excel ou fazer HTTP em código server-side |
109
111
  | Permission Audit | `guides/permission-audit.md` | Auditoria de permissões de API | Cross-check código↔Funifier: manifest de chamadas → missing scopes, excess tokens, danger findings; gramática de scope (SecurityFilter), keyword `database`, auth context, remediação |
110
112
 
111
113
  ---
@@ -123,4 +125,6 @@ Para **relatórios e queries avançadas**, consulte `guides/aggregates.md` — d
123
125
 
124
126
  Para **lógica customizada com código**, consulte `guides/triggers-guide.md` (conceitos e referência de eventos) e `guides/trigger-examples.md` (exemplos Groovy prontos), além de `guides/database-access.md`.
125
127
 
128
+ Para **diagnosticar queries lentas ou timeout de `DELETE`** (ver se bate no índice), consulte `guides/explain-diagnostics.md` — obtém o plano real (`explain`) do MongoDB via scheduler, já que o endpoint REST só executa a query.
129
+
126
130
  Para **referência completa de recursos Java** (managers, entidades, bibliotecas), consulte `guides/java-managers.md`, `guides/java-entities.md` e `guides/java-libraries.md`.
@@ -137,6 +137,8 @@ flowchart TD
137
137
 
138
138
  > **Armadilha crítica (confirmada no código):** se `q` casa com **mais de 20.000** documentos, o delete **não executa** e retorna **200 OK** sem erro. O comentário no código diz "menor que 1000" (`DatabaseRest.java:466`), mas a constante real é `20_000` (`:469`) — o comentário está **desatualizado**.
139
139
 
140
+ > **Segunda armadilha — TIMEOUT (independente do no-op):** mesmo **abaixo** de 20.000, o delete é **síncrono num único request HTTP** e faz **3 varreduras** na coleção (`count` → `distinct(_id)` materializando **todos** os ids em memória → `remove`). Casar **milhares** de docs (ex.: ~10.000) sobre campo **não indexado** estoura o **timeout do request**. → Apague em **lotes de 1.000 por chamada, sobre campo indexado** (o número 1.000 é a intenção original do comentário do código); particione o `q` (o DELETE não aceita `$limit`) e repita até zerar. Se houver delete-trigger/audit na coleção, o lote seguro cai abaixo de 1.000. Ver §5.6 e §10.
141
+
140
142
  #### Fluxograma — delete
141
143
 
142
144
  ```mermaid
@@ -507,8 +509,8 @@ Operam sobre o **banco de sistema** (`SystemFactory.getInstance()`), não o do t
507
509
  ### 5.5 Convenção `__c`
508
510
  Não é validada no CRUD. Única exigência: `out` do `aggregate` deve terminar em `__c`. O framework usa internamente `csv_config__c`, `csv_log__c`, `csv_format__c`, `find_cache_<id>__c`, `email_code_verifier_log__c`.
509
511
 
510
- ### 5.6 Limite de delete (20.000)
511
- Filtro que casa > 20.000 docs → no-op silencioso com 200. (Comentário do código diz "1000", mas o valor real é 20.000.)
512
+ ### 5.6 Limite de delete (20.000) e timeout
513
+ **Duas armadilhas independentes** (ver §2.4): filtro que casa **> 20.000** docs → **no-op silencioso** com 200 (comentário do código diz "1000", mas o valor real é 20.000); filtro que casa **milhares ≤ 20.000** → **timeout** (operação síncrona: 3 varreduras + materializa todos os ids num único request). Mitigação: apague em **lotes de 1.000 sobre campo indexado** (§10).
512
514
 
513
515
  ### 5.7 Assimetria de encriptação — risco silencioso
514
516
  Encriptação na escrita só ocorre se `checkReadEncryptedValues()` for verdadeiro. Esse método retorna `true` por **dois caminhos** (`AuthBean.java:221-226`): scope `read_encrypted_field_values` **ou** permissão de sistema `api/encrypted_field_values/read`. Se **nenhum** estiver presente, o dado é salvo em **plaintext** na coleção configurada, **sem aviso**. Resultado: a mesma coleção pode acumular documentos encriptados e em plaintext, dependendo de qual token escreveu. Além disso, falhas internas de `AesCrypt.encryptFields` (ex.: JSONPath inválido) são **engolidas** (`printStackTrace`) → o campo permanece plaintext silenciosamente.
@@ -562,6 +564,7 @@ Toda operação resolve `FrontController.getInstance(authBean.getApiKey()).getMa
562
564
  - **PATCH parcial:** PUT é sempre replace via `save()`.
563
565
  - **`PUT /{collection}/operations`:** **código morto** comentado em `DatabaseRest.java:393-447` (operações `$inc`/`$set`/`upsert`/`multi`). Implementado, porém **nunca exposto**.
564
566
  - **Delete > 20.000 docs:** no-op silencioso (200).
567
+ - **Delete de milhares de docs (≤ 20.000):** timeout HTTP — síncrono, 3 varreduras + materializa todos os ids num único request. Apague em lotes de 1.000 sobre campo indexado (§5.6, §10).
565
568
  - **Auditoria no bulk:** inexistente.
566
569
  - **Encriptação no bulk e no aggregate:** inexistente (campos não são encriptados/decriptados nesses caminhos).
567
570
  - **`aggregate_test` não oculta `password`** da coleção `player` (diferente de `/aggregate`).
@@ -625,6 +628,7 @@ GET /v3/database/cars__c/index # índices da coleção
625
628
  |---|---|---|
626
629
  | Resposta vazia com 200/201 | Token sem scope `database` ou nome de coleção curto demais | Conferir `scope` e o limiar de chars do endpoint (§5.1) |
627
630
  | DELETE retorna 200 mas nada some | Filtro casa > 20.000 docs | `GET /count?q=...` antes; paginar o filtro |
631
+ | DELETE dá timeout / 504 / conexão cai | Filtro casa milhares de docs (≤ 20.000): síncrono, 3 varreduras + materializa ids | Apague em lotes de 1.000 sobre campo indexado (§5.6, §10); se houver delete-trigger/audit, lotes menores |
628
632
  | PUT criou doc novo em vez de atualizar | `_id` ausente no body | Incluir `_id` |
629
633
  | Campo volta como string ilegível | Token de leitura sem capacidade de cripto → ciphertext retornado | Usar token com `read_encrypted_field_values` |
630
634
  | Coleção com mix encriptado/plaintext | Algum writer escreveu sem capacidade de cripto | Garantir capacidade em **todos** os writers (§5.7) |
@@ -687,15 +691,16 @@ PUT /v3/database/products__c
687
691
  ```
688
692
  **Resultado:** cria documento NOVO (`_id` gerado). O original não é atualizado. **Sempre** envie `_id` no PUT — e lembre que campos omitidos serão removidos (replace, não patch).
689
693
 
690
- ### 10.5 Anti-pattern — DELETE em volume sem checar count
694
+ ### 10.5 Anti-pattern — DELETE em volume sem fatiar
691
695
  ```
692
696
  DELETE /v3/database/logs__c?q=year:2022
693
697
  ```
694
- Se houver > 20.000 docs de 2022, retorna **200 sem deletar nada**. Cheque antes:
698
+ **Duas armadilhas** (ver §2.4): se houver **> 20.000** docs de 2022, retorna **200 sem deletar nada** (no-op); se houver **milhares ≤ 20.000**, **dá timeout** (síncrono: 3 varreduras + materializa todos os ids num único request). Cheque antes e **fatie em lotes de ≤ 1.000 sobre campo indexado**:
695
699
  ```
696
- GET /v3/database/count?collection=logs__c&q=year:2022
700
+ GET /v3/database/count?collection=logs__c&q=year:2022,month:1 # confirme ≤ 1.000
701
+ DELETE /v3/database/logs__c?q=year:2022,month:1 # repita por mês/dia até zerar
697
702
  ```
698
- Se > 20.000, fatie o filtro (ex.: `year:2022,month:{$lte:6}`) ou use `/drop` (com cautela sem scope).
703
+ O DELETE REST **não aceita `$limit`** — "lote" = montar `q` que case ≤ 1.000 por chamada (janelas de data, prefixos de `_id`) e repetir. O número 1.000 vem do comentário original do código. Filtre sempre por **campo indexado** (senão as 3 varreduras continuam O(coleção)). Se a coleção inteira pode ir embora → `/drop` (cautela: sem scope). Para volume massivo sem o timeout do REST, mova a deleção para **trigger/scheduler/public endpoint** (`remove` em laço server-side).
699
704
 
700
705
  ### 10.6 Anti-pattern — confiar em encriptação sem a capacidade
701
706
  Escrever em coleção com `crypt_object_field` configurado usando token **sem** `read_encrypted_field_values` (nem permissão de sistema) salva os campos em **plaintext silenciosamente**. Garanta a capacidade em todos os writers.
@@ -708,7 +713,7 @@ Escrever em coleção com `crypt_object_field` configurado usando token **sem**
708
713
  - [ ] Nome da coleção respeita o limiar de chars do endpoint usado (§5.1): `> 3` para POST/PUT/bulk/aggregate_test, `> 2` para aggregate, `> 0` para GET-list/delete/count/index.
709
714
  - [ ] `_id` incluído no PUT para atualizar (e ciência de que PUT é replace completo).
710
715
  - [ ] Sufixo `__c` no destino `out` do aggregate.
711
- - [ ] `GET /count` executado antes de DELETE para garantir filtro com20.000 docs.
716
+ - [ ] `GET /count` executado antes de DELETE: filtro casa **≤ 20.000** (acima = no-op) **e cada chamada 1.000 sobre campo indexado** (acima = risco de timeout — fatie em lotes; §10.5).
712
717
  - [ ] Triggers cadastradas com `entity: '<colecao>'` se há comportamento automático esperado — e ciência de que **exceção no script não aborta** a operação.
713
718
  - [ ] `Audit` **ativo** em `{entity, event, type}` se auditoria for necessária (e token com user/player; bulk não audita).
714
719
  - [ ] Índices criados (`POST /{collection}/index`) para campos de filtro frequente.
@@ -93,6 +93,16 @@ Síncrono. Idêntico ao POST exceto:
93
93
 
94
94
  **PUT é full replace, não PATCH:** campos ausentes no corpo são removidos do documento. Como `_id` é gerado quando ausente (passo [3]), um PUT sem `_id` **cria** um documento novo a cada chamada.
95
95
 
96
+ **Edição parcial segura (read-modify-write):** não existe merge — enviar `{_id, campoX}` para "mudar só o campoX" **apaga** todos os demais campos. Para editar um único campo de um documento existente, faça o ciclo completo:
97
+
98
+ ```
99
+ 1. query → busque o documento por _id (funifier_database action=query, query={"_id": "<id>"})
100
+ 2. edite → altere o campo desejado no objeto retornado (mantendo todos os outros campos)
101
+ 3. update → reenvie o DOCUMENTO COMPLETO (funifier_database action=update, data=<doc inteiro>)
102
+ ```
103
+
104
+ O agente é quem faz o read-modify-write, explicitamente — a tool `funifier_database action=update` não relê nem mescla. O mesmo vale para os módulos que usam `POST` como upsert (`player`, `point`, `level`, `mystery`): sempre reenvie o objeto completo.
105
+
96
106
  ### 2.4 Pipeline — `delete()` (DELETE por filtro)
97
107
 
98
108
  ```
@@ -113,6 +123,20 @@ Síncrono. Idêntico ao POST exceto:
113
123
  >
114
124
  > O comentário no código diz "menor que 1000", mas o limite real codificado é `<= 20_000`.
115
125
 
126
+ > ⚠️ **TIMEOUT — segunda armadilha (independente do no-op):** mesmo **abaixo** de 20.000, o `delete()` é **síncrono e pesado num único request HTTP**. Para `1 ≤ total ≤ 20.000` ele faz, em sequência, **três varreduras na coleção** mais a materialização de **todos** os `_id`s casados em memória:
127
+ >
128
+ > | Passo | Operação | Custo |
129
+ > |---|---|---|
130
+ > | 1 | `count("{q}")` | varredura (scan #1) |
131
+ > | 2 | `distinct("_id").query("{q}")` | varredura (scan #2) **+ materializa todos os N `_id`s** num `List<String>` |
132
+ > | 3 | `executeStrict(before_delete, ids)` | itera sobre `ids` (O(1) só sem delete-trigger registrado) |
133
+ > | 4 | `remove("{q}")` | varredura (scan #3) + N deleções |
134
+ > | 5 | `execute(after_delete, ids)` + `auditManager.log(..., ids)` | itera sobre `ids` |
135
+ >
136
+ > Para N na casa dos **milhares** (ex.: ~10.000) sobre um campo de filtro **não indexado**, cada varredura é O(tamanho da coleção); a soma das três + materialização **estoura o timeout do request/gateway**. Ou seja, há **duas cliffs distintas**: `> 20.000` = **no-op silencioso** (correção); milhares `≤ 20.000` = **timeout** (performance). Mitigação: §10.5 (lotes de **1.000** sobre **campo indexado**).
137
+ >
138
+ > ⚠️ Se a coleção tiver **delete-trigger** registrado ou **audit** configurado, os passos 3/5 rodam **por `_id`** — o custo multiplica e o lote seguro cai abaixo de 1.000.
139
+
116
140
  #### Fluxo de exclusão — `delete()`
117
141
 
118
142
  ```mermaid
@@ -363,7 +387,7 @@ Diferenças do REST padrão: é **full replace** (campos ausentes somem); sem `_
363
387
  | Validação | `collection > 0` E `q.trim().length() >= 3` |
364
388
  | Retorno | `200` body null (sempre) |
365
389
 
366
- Comportamento real (ver 2.4): `q` < 3 chars → no-op silencioso; **`total > 20.000` → no-op silencioso** (não apaga, não dispara trigger, não audita). A validação de 3 chars é só de comprimento — `q=a:1` (3 chars) apaga tudo onde `a==1`.
390
+ Comportamento real (ver 2.4): `q` < 3 chars → no-op silencioso; **`total > 20.000` → no-op silencioso** (não apaga, não dispara trigger, não audita). A validação de 3 chars é só de comprimento — `q=a:1` (3 chars) apaga tudo onde `a==1`. **E mesmo abaixo de 20.000**, casar **milhares** de docs faz a operação síncrona (count + distinct/materializa ids + remove = 3 varreduras) **estourar o timeout** — apague em **lotes de 1.000 sobre campo indexado** (§2.4, §10.5).
367
391
 
368
392
  ### 4.6 `POST /v3/database/{collection}/bulk`
369
393
 
@@ -576,6 +600,7 @@ Não há transação multi-documento. Bulk e aggregate-`out` gravam item a item;
576
600
 
577
601
  - **`PUT /{collection}/operations`** (update com `$inc`/`$set`/multi/upsert) — **comentado no código**, inexistente em runtime.
578
602
  - **DELETE acima de 20.000 documentos** — **no-op silencioso** (não apaga nada; ver 2.4).
603
+ - **DELETE de milhares de documentos (≤ 20.000)** — **timeout HTTP**: operação síncrona com 3 varreduras + materialização de todos os `_id`s num único request (ver 2.4). Apague em **lotes de 1.000 sobre campo indexado** (§10.5).
579
604
  - **Ordenação no GET list** — sem `$sort`; use `/aggregate`.
580
605
  - **Decriptação no GET list e no aggregate** — só GET-by-id decripta.
581
606
  - **Audit em bulk** — bulk não gera audit log.
@@ -656,6 +681,7 @@ GET /v3/database/{collection}/index # índices de uma coleção
656
681
  | GET list retorna `[]` com `q` complexo | Erro de query engolido por `getPageResult` |
657
682
  | Aggregate retorna 500 | Sintaxe de pipeline inválida (`getPageResultThrowsException`) |
658
683
  | **DELETE não apaga nada** | `q` < 3 chars, **ou filtro casa > 20.000 docs (no-op)**, ou nenhum match |
684
+ | **DELETE dá timeout / 504 / conexão cai** | Filtro casa **milhares de docs (≤ 20.000)**: operação síncrona (3 varreduras + materializa ids) estoura o timeout. → Apague em **lotes de 1.000 sobre campo indexado** (§10.5); se houver delete-trigger/audit, lotes menores |
659
685
  | Bulk async: dados somem após restart | Fila em memória, sem persistência |
660
686
  | Campo criptografado vem `null` na leitura | Foi gravado em texto puro por token sem `read_encrypted_field_values`, ou secret trocada |
661
687
  | Senha do player apagada após PUT | Token com `read_encrypted_player_password` e `password` não enviado |
@@ -663,7 +689,7 @@ GET /v3/database/{collection}/index # índices de uma coleção
663
689
  ### 9.3 Queries úteis (mongosh / via aggregate)
664
690
 
665
691
  ```javascript
666
- // quantos documentos um DELETE tentaria apagar (cheque o limite de 20.000!)
692
+ // quantos documentos um DELETE tentaria apagar (>20.000 = no-op; milhares = timeout — apague em lotes de 1.000)
667
693
  db.minha__c.countDocuments({ status: "active" })
668
694
 
669
695
  // ver índices reais de uma coleção
@@ -687,7 +713,7 @@ POST /v3/database/action_log/aggregate
687
713
 
688
714
  1. O token tem o scope correto? (`database`; e `read_encrypted_*` se aplicável)
689
715
  2. O nome da coleção respeita o tamanho mínimo do endpoint (ver 5.2)?
690
- 3. No DELETE: o filtro casa **entre 1 e 20.000** documentos?
716
+ 3. No DELETE: o filtro casa **entre 1 e 20.000** documentos (acima = no-op)? E **cada chamada** casa **≤ 1.000** sobre **campo indexado** (acima = risco de timeout)?
691
717
  4. Há `CryptObjectField` + `CryptTenantSecret` ativa coerentes com o que foi gravado?
692
718
  5. A fila async está drenando (`/queue/info`)?
693
719
 
@@ -780,7 +806,24 @@ Sem `_id`, cada PUT **cria** um documento novo (ObjectId gerado). Sempre inclua
780
806
  DELETE /v3/database/logs__c?q=ttl:1
781
807
  ```
782
808
 
783
- Se o filtro casar **mais de 20.000** documentos, **nada é apagado** (no-op silencioso, 200). Apague em lotes (`q` que limite a ≤ 20.000 por chamada) ou use `drop` se a coleção inteira puder ir embora.
809
+ Um filtro amplo cai numa de **duas armadilhas** (ver §2.4):
810
+
811
+ - **> 20.000 docs → no-op silencioso** (200, nada apagado).
812
+ - **Milhares ≤ 20.000 → timeout HTTP**: o `delete()` é síncrono e faz 3 varreduras na coleção + materializa todos os `_id`s em memória, tudo num único request.
813
+
814
+ **Apague em lotes de 1.000 documentos por chamada, sobre um campo de filtro indexado.** O número 1.000 vem do próprio comentário do código (`DatabaseRest.delete`: *"…se o número de registros for menor que 1000…"*) — é a intenção original de lote seguro, não um limite da plataforma; ajuste conforme a latência observada.
815
+
816
+ > ⚠️ O `DELETE` REST **não aceita `$limit`/`max_results`** — não dá para "apagar 1.000 por vez" sem particionar o filtro. "Lote" aqui = montar `q` que **case ≤ 1.000** por chamada (janelas de data, prefixos de `_id`, faixas de uma chave) e **repetir** até zerar. Cada chamada deve filtrar por **campo indexado** — senão as 3 varreduras continuam O(coleção inteira) e o timeout volta.
817
+
818
+ ```http
819
+ # ✅ Em lotes, por janela de tempo (campo `time` indexado), repetindo até zerar:
820
+ GET /v3/database/logs__c/count?q=ttl:1,time:{$lt:1704067200000} # confirme ≤ 1.000
821
+ DELETE /v3/database/logs__c?q=ttl:1,time:{$lt:1704067200000}
822
+ ```
823
+
824
+ **Alternativas:**
825
+ - Se a coleção **inteira** pode ir embora → `DELETE /v3/database/logs__c/drop` (uma operação, sem as 3 varreduras; ⚠️ não checa scope — §8.2).
826
+ - Para exclusão massiva sem o timeout do REST → mova a deleção para **trigger/scheduler/public endpoint** (`manager.getJongoConnection().getCollection(...).remove(...)` em laço server-side, fora do request HTTP).
784
827
 
785
828
  **NÃO FAÇA — ler dados sensíveis por GET list ou aggregate esperando decriptação:**
786
829
 
@@ -800,7 +843,7 @@ O valor vai em **texto puro** e a leitura futura (com o scope) retorna `null`. U
800
843
 
801
844
  - [ ] Token tem o scope `database` (lembre: a checagem é por **substring**).
802
845
  - [ ] Coleções customizadas usam sufixo `__c`; `out` de aggregate **deve** terminar em `__c`.
803
- - [ ] DELETE: o filtro `q` tem ≥ 3 chars **e** casa **no máximo 20.000** documentos (acima disso é no-op).
846
+ - [ ] DELETE: o filtro `q` tem ≥ 3 chars **e** casa **no máximo 20.000** documentos (acima disso é no-op) **e cada chamada casa ≤ 1.000 sobre campo indexado** (acima disso, risco de timeout — apague em lotes; §10.5).
804
847
  - [ ] Para preservar `password` em `player`: garanta o mesmo nível de permissão (`read_encrypted_player_password`) ao ler e gravar.
805
848
  - [ ] Para campos criptografados: ler **e** gravar com `read_encrypted_field_values`; `CryptObjectField` (tenant) e `CryptTenantSecret` ativa (sistema) coerentes.
806
849
  - [ ] `drop`, `collections` (tenant) e `queue/*` não checam scope — proteja esses tokens de clientes não confiáveis.