@seedgrid/ai-toolkit 0.0.0-stage → 0.3.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.
- package/LICENSE +21 -0
- package/README.md +151 -2
- package/dist/adapters/claude.js +114 -0
- package/dist/adapters/codex.js +108 -0
- package/dist/adapters/index.js +17 -0
- package/dist/adapters/shared.js +142 -0
- package/dist/adapters/types.js +1 -0
- package/dist/cli.js +203 -0
- package/dist/commands/common.js +98 -0
- package/dist/commands/doctor.js +290 -0
- package/dist/commands/init.js +9 -0
- package/dist/commands/install.js +107 -0
- package/dist/commands/remove.js +54 -0
- package/dist/commands/review.js +127 -0
- package/dist/commands/status.js +73 -0
- package/dist/commands/update.js +8 -0
- package/dist/commands/validate.js +27 -0
- package/dist/core/apply.js +181 -0
- package/dist/core/blocks.js +64 -0
- package/dist/core/content.js +528 -0
- package/dist/core/env.js +40 -0
- package/dist/core/frontmatter.js +29 -0
- package/dist/core/hash.js +14 -0
- package/dist/core/inspect.js +51 -0
- package/dist/core/lock.js +124 -0
- package/dist/core/paths.js +65 -0
- package/dist/core/plan.js +242 -0
- package/dist/core/tools.js +62 -0
- package/dist/core/types.js +11 -0
- package/dist/core/updates.js +121 -0
- package/docs/arquitetura.md +80 -0
- package/docs/compatibilidade.md +50 -0
- package/docs/formato.md +155 -0
- package/docs/instalacao.md +105 -0
- package/docs/manutencao.md +115 -0
- package/package.json +43 -4
- package/seedgridKB/SeedGrid/Extensions/Catalogo-de-enums.md +161 -0
- package/seedgridKB/SeedGrid/Extensions/Catalogo-de-propriedades.md +971 -0
- package/seedgridKB/SeedGrid/Extensions/Indice.md +63 -0
- package/seedgridKB/SeedGrid/Extensions/ai-pricing/Configuracao.md +124 -0
- package/seedgridKB/SeedGrid/Extensions/ai-pricing/Enums.md +40 -0
- package/seedgridKB/SeedGrid/Extensions/ai-pricing/Exemplos.md +97 -0
- package/seedgridKB/SeedGrid/Extensions/ai-pricing/Visao-geral.md +114 -0
- package/seedgridKB/SeedGrid/Extensions/apikeys/Configuracao-Rust.md +69 -0
- package/seedgridKB/SeedGrid/Extensions/apikeys/Configuracao.md +88 -0
- package/seedgridKB/SeedGrid/Extensions/apikeys/Enums.md +33 -0
- package/seedgridKB/SeedGrid/Extensions/apikeys/Exemplos.md +121 -0
- package/seedgridKB/SeedGrid/Extensions/apikeys/Rust.md +126 -0
- package/seedgridKB/SeedGrid/Extensions/apikeys/Visao-geral.md +111 -0
- package/seedgridKB/SeedGrid/Extensions/audit/Configuracao-Rust.md +93 -0
- package/seedgridKB/SeedGrid/Extensions/audit/Configuracao.md +127 -0
- package/seedgridKB/SeedGrid/Extensions/audit/Enums.md +66 -0
- package/seedgridKB/SeedGrid/Extensions/audit/Exemplos.md +151 -0
- package/seedgridKB/SeedGrid/Extensions/audit/Rust.md +120 -0
- package/seedgridKB/SeedGrid/Extensions/audit/Visao-geral.md +114 -0
- package/seedgridKB/SeedGrid/Extensions/backup/Configuracao-Rust.md +136 -0
- package/seedgridKB/SeedGrid/Extensions/backup/Configuracao.md +319 -0
- package/seedgridKB/SeedGrid/Extensions/backup/Enums.md +131 -0
- package/seedgridKB/SeedGrid/Extensions/backup/Exemplos.md +210 -0
- package/seedgridKB/SeedGrid/Extensions/backup/Rust.md +135 -0
- package/seedgridKB/SeedGrid/Extensions/backup/Visao-geral.md +150 -0
- package/seedgridKB/SeedGrid/Extensions/billing/Configuracao-Rust.md +86 -0
- package/seedgridKB/SeedGrid/Extensions/billing/Configuracao.md +243 -0
- package/seedgridKB/SeedGrid/Extensions/billing/Enums.md +165 -0
- package/seedgridKB/SeedGrid/Extensions/billing/Exemplos.md +205 -0
- package/seedgridKB/SeedGrid/Extensions/billing/Rust.md +109 -0
- package/seedgridKB/SeedGrid/Extensions/billing/Visao-geral.md +181 -0
- package/seedgridKB/SeedGrid/Extensions/broker/Configuracao-Rust.md +40 -0
- package/seedgridKB/SeedGrid/Extensions/broker/Visao-geral.md +114 -0
- package/seedgridKB/SeedGrid/Extensions/cache/Configuracao-Rust.md +73 -0
- package/seedgridKB/SeedGrid/Extensions/cache/Configuracao.md +112 -0
- package/seedgridKB/SeedGrid/Extensions/cache/Enums.md +36 -0
- package/seedgridKB/SeedGrid/Extensions/cache/Exemplos.md +149 -0
- package/seedgridKB/SeedGrid/Extensions/cache/Rust.md +81 -0
- package/seedgridKB/SeedGrid/Extensions/cache/Visao-geral.md +90 -0
- package/seedgridKB/SeedGrid/Extensions/commons/Configuracao.md +107 -0
- package/seedgridKB/SeedGrid/Extensions/commons/Enums.md +105 -0
- package/seedgridKB/SeedGrid/Extensions/commons/Exemplos.md +141 -0
- package/seedgridKB/SeedGrid/Extensions/commons/Visao-geral.md +99 -0
- package/seedgridKB/SeedGrid/Extensions/exchange/Configuracao.md +122 -0
- package/seedgridKB/SeedGrid/Extensions/exchange/Enums.md +29 -0
- package/seedgridKB/SeedGrid/Extensions/exchange/Exemplos.md +82 -0
- package/seedgridKB/SeedGrid/Extensions/exchange/Visao-geral.md +118 -0
- package/seedgridKB/SeedGrid/Extensions/filemanagement/Configuracao-Rust.md +110 -0
- package/seedgridKB/SeedGrid/Extensions/filemanagement/Configuracao.md +157 -0
- package/seedgridKB/SeedGrid/Extensions/filemanagement/Exemplos.md +157 -0
- package/seedgridKB/SeedGrid/Extensions/filemanagement/Rust.md +70 -0
- package/seedgridKB/SeedGrid/Extensions/filemanagement/Visao-geral.md +88 -0
- package/seedgridKB/SeedGrid/Extensions/login-tenancy/Configuracao-Rust.md +124 -0
- package/seedgridKB/SeedGrid/Extensions/login-tenancy/Configuracao.md +195 -0
- package/seedgridKB/SeedGrid/Extensions/login-tenancy/Enums.md +71 -0
- package/seedgridKB/SeedGrid/Extensions/login-tenancy/Exemplos.md +149 -0
- package/seedgridKB/SeedGrid/Extensions/login-tenancy/Rust.md +139 -0
- package/seedgridKB/SeedGrid/Extensions/login-tenancy/Visao-geral.md +130 -0
- package/seedgridKB/SeedGrid/Extensions/notification/Configuracao.md +141 -0
- package/seedgridKB/SeedGrid/Extensions/notification/Enums.md +52 -0
- package/seedgridKB/SeedGrid/Extensions/notification/Exemplos.md +141 -0
- package/seedgridKB/SeedGrid/Extensions/notification/Visao-geral.md +113 -0
- package/seedgridKB/SeedGrid/Extensions/privacy/Configuracao-Rust.md +67 -0
- package/seedgridKB/SeedGrid/Extensions/privacy/Configuracao.md +94 -0
- package/seedgridKB/SeedGrid/Extensions/privacy/Enums.md +79 -0
- package/seedgridKB/SeedGrid/Extensions/privacy/Exemplos.md +172 -0
- package/seedgridKB/SeedGrid/Extensions/privacy/Rust.md +107 -0
- package/seedgridKB/SeedGrid/Extensions/privacy/Visao-geral.md +90 -0
- package/seedgridKB/SeedGrid/Extensions/realtime/Configuracao-Rust.md +72 -0
- package/seedgridKB/SeedGrid/Extensions/realtime/Configuracao.md +104 -0
- package/seedgridKB/SeedGrid/Extensions/realtime/Exemplos.md +104 -0
- package/seedgridKB/SeedGrid/Extensions/realtime/Rust.md +69 -0
- package/seedgridKB/SeedGrid/Extensions/realtime/Visao-geral.md +105 -0
- package/seedgridKB/SeedGrid/Extensions/resilience/Configuracao-Rust.md +68 -0
- package/seedgridKB/SeedGrid/Extensions/resilience/Configuracao.md +186 -0
- package/seedgridKB/SeedGrid/Extensions/resilience/Enums.md +96 -0
- package/seedgridKB/SeedGrid/Extensions/resilience/Exemplos.md +229 -0
- package/seedgridKB/SeedGrid/Extensions/resilience/Rust.md +80 -0
- package/seedgridKB/SeedGrid/Extensions/resilience/Visao-geral.md +112 -0
- package/seedgridKB/SeedGrid/Extensions/runtime/Configuracao-Rust.md +90 -0
- package/seedgridKB/SeedGrid/Extensions/runtime/Visao-geral.md +114 -0
- package/seedgridKB/SeedGrid/Extensions/security/Configuracao-Rust.md +327 -0
- package/seedgridKB/SeedGrid/Extensions/security/Configuracao.md +510 -0
- package/seedgridKB/SeedGrid/Extensions/security/Enums.md +95 -0
- package/seedgridKB/SeedGrid/Extensions/security/Exemplos.md +218 -0
- package/seedgridKB/SeedGrid/Extensions/security/Rust.md +203 -0
- package/seedgridKB/SeedGrid/Extensions/security/Visao-geral.md +131 -0
- package/seedgridKB/SeedGrid/Extensions/subdomain-tenancy/Configuracao-Rust.md +188 -0
- package/seedgridKB/SeedGrid/Extensions/subdomain-tenancy/Configuracao.md +345 -0
- package/seedgridKB/SeedGrid/Extensions/subdomain-tenancy/Enums.md +117 -0
- package/seedgridKB/SeedGrid/Extensions/subdomain-tenancy/Exemplos.md +205 -0
- package/seedgridKB/SeedGrid/Extensions/subdomain-tenancy/Rust.md +161 -0
- package/seedgridKB/SeedGrid/Extensions/subdomain-tenancy/Visao-geral.md +142 -0
- package/seedgridKB/SeedGrid/Extensions/whatsapp/Configuracao-Rust.md +75 -0
- package/seedgridKB/SeedGrid/Extensions/whatsapp/Configuracao.md +152 -0
- package/seedgridKB/SeedGrid/Extensions/whatsapp/Enums.md +198 -0
- package/seedgridKB/SeedGrid/Extensions/whatsapp/Exemplos.md +173 -0
- package/seedgridKB/SeedGrid/Extensions/whatsapp/Rust.md +83 -0
- package/seedgridKB/SeedGrid/Extensions/whatsapp/Visao-geral.md +99 -0
- package/seedgridKB/SeedGrid/Mapa-dos-repositorios.md +112 -0
- package/seedgridKB/SeedGrid/SeedGrid-components/Indice.md +323 -0
- package/seedgridKB/SeedGrid/SeedGrid-components/buttons/API.md +257 -0
- package/seedgridKB/SeedGrid/SeedGrid-components/buttons/Exemplos.md +307 -0
- package/seedgridKB/SeedGrid/SeedGrid-components/buttons/Visao-geral.md +51 -0
- package/seedgridKB/SeedGrid/SeedGrid-components/datatable/API.md +174 -0
- package/seedgridKB/SeedGrid/SeedGrid-components/datatable/Exemplos.md +236 -0
- package/seedgridKB/SeedGrid/SeedGrid-components/datatable/Visao-geral.md +76 -0
- package/seedgridKB/SeedGrid/SeedGrid-components/digits/API.md +339 -0
- package/seedgridKB/SeedGrid/SeedGrid-components/digits/Exemplos.md +311 -0
- package/seedgridKB/SeedGrid/SeedGrid-components/digits/Visao-geral.md +57 -0
- package/seedgridKB/SeedGrid/SeedGrid-components/editor/API.md +125 -0
- package/seedgridKB/SeedGrid/SeedGrid-components/editor/Exemplos.md +116 -0
- package/seedgridKB/SeedGrid/SeedGrid-components/editor/Visao-geral.md +86 -0
- package/seedgridKB/SeedGrid/SeedGrid-components/feedback-e-exibicao/API.md +490 -0
- package/seedgridKB/SeedGrid/SeedGrid-components/feedback-e-exibicao/Exemplos.md +544 -0
- package/seedgridKB/SeedGrid/SeedGrid-components/feedback-e-exibicao/Visao-geral.md +69 -0
- package/seedgridKB/SeedGrid/SeedGrid-components/fundamentos/API.md +113 -0
- package/seedgridKB/SeedGrid/SeedGrid-components/fundamentos/Exemplos.md +159 -0
- package/seedgridKB/SeedGrid/SeedGrid-components/fundamentos/Visao-geral.md +92 -0
- package/seedgridKB/SeedGrid/SeedGrid-components/gadgets/API.md +516 -0
- package/seedgridKB/SeedGrid/SeedGrid-components/gadgets/Exemplos.md +629 -0
- package/seedgridKB/SeedGrid/SeedGrid-components/gadgets/Visao-geral.md +80 -0
- package/seedgridKB/SeedGrid/SeedGrid-components/inputs-selecao/API.md +615 -0
- package/seedgridKB/SeedGrid/SeedGrid-components/inputs-selecao/Exemplos.md +384 -0
- package/seedgridKB/SeedGrid/SeedGrid-components/inputs-selecao/Visao-geral.md +111 -0
- package/seedgridKB/SeedGrid/SeedGrid-components/inputs-texto/API.md +661 -0
- package/seedgridKB/SeedGrid/SeedGrid-components/inputs-texto/Exemplos.md +312 -0
- package/seedgridKB/SeedGrid/SeedGrid-components/inputs-texto/Visao-geral.md +123 -0
- package/seedgridKB/SeedGrid/SeedGrid-components/layout/API.md +1090 -0
- package/seedgridKB/SeedGrid/SeedGrid-components/layout/Exemplos.md +1172 -0
- package/seedgridKB/SeedGrid/SeedGrid-components/layout/Visao-geral.md +98 -0
- package/seedgridKB/SeedGrid/SeedGrid-components/modulos/Visao-geral.md +98 -0
- package/seedgridKB/SeedGrid/SeedGrid-components/modulos/fe-apikeys.md +118 -0
- package/seedgridKB/SeedGrid/SeedGrid-components/modulos/fe-audit.md +88 -0
- package/seedgridKB/SeedGrid/SeedGrid-components/modulos/fe-backup.md +131 -0
- package/seedgridKB/SeedGrid/SeedGrid-components/modulos/fe-billing-platform.md +132 -0
- package/seedgridKB/SeedGrid/SeedGrid-components/modulos/fe-billing-user.md +120 -0
- package/seedgridKB/SeedGrid/SeedGrid-components/modulos/fe-login-tenancy.md +119 -0
- package/seedgridKB/SeedGrid/SeedGrid-components/modulos/fe-security.md +427 -0
- package/seedgridKB/SeedGrid/SeedGrid-components/modulos/fe-self-enter.md +104 -0
- package/seedgridKB/SeedGrid/SeedGrid-components/modulos/fe-subdomain-tenancy.md +110 -0
- package/seedgridKB/SeedGrid/SeedGrid-components/navegacao-e-overlay/API.md +490 -0
- package/seedgridKB/SeedGrid/SeedGrid-components/navegacao-e-overlay/Exemplos.md +561 -0
- package/seedgridKB/SeedGrid/SeedGrid-components/navegacao-e-overlay/Visao-geral.md +82 -0
- package/seedgridKB/SeedGrid/SeedGrid-components/pacotes/Visao-geral.md +68 -0
- package/seedgridKB/SeedGrid/SeedGrid-components/pacotes/fe-commons.md +61 -0
- package/seedgridKB/SeedGrid/SeedGrid-components/pacotes/fe-core.md +140 -0
- package/seedgridKB/SeedGrid/SeedGrid-components/pacotes/fe-playground.md +112 -0
- package/seedgridKB/SeedGrid/SeedGrid-components/pacotes/fe-theme.md +97 -0
- package/seedgridKB/SeedGrid/SeedGrid-components/providers/API.md +177 -0
- package/seedgridKB/SeedGrid/SeedGrid-components/providers/Exemplos.md +332 -0
- package/seedgridKB/SeedGrid/SeedGrid-components/providers/Visao-geral.md +106 -0
- package/seedgridKB/SeedGrid/SeedGrid-components/showcase.md +70 -0
- package/seedgridKB/SeedGrid/Toolkit/agents/arquiteto.md +53 -0
- package/seedgridKB/SeedGrid/Toolkit/agents/backend-java.md +45 -0
- package/seedgridKB/SeedGrid/Toolkit/agents/backend-rust.md +43 -0
- package/seedgridKB/SeedGrid/Toolkit/agents/frontend-react.md +41 -0
- package/seedgridKB/SeedGrid/Toolkit/agents/qa.md +44 -0
- package/seedgridKB/SeedGrid/Toolkit/agents/revisor.md +40 -0
- package/seedgridKB/SeedGrid/Toolkit/references/fluxo-de-trabalho.md +78 -0
- package/seedgridKB/SeedGrid/Toolkit/references/glossario.md +15 -0
- package/seedgridKB/SeedGrid/Toolkit/references/modos-shared-native.md +27 -0
- package/seedgridKB/SeedGrid/Toolkit/rules/backend-java-seedgrid.md +54 -0
- package/seedgridKB/SeedGrid/Toolkit/rules/coordenacao.md +39 -0
- package/seedgridKB/SeedGrid/Toolkit/rules/formato-entrega.md +29 -0
- package/seedgridKB/SeedGrid/Toolkit/rules/frontend-seedgrid.md +47 -0
- package/seedgridKB/SeedGrid/Toolkit/rules/personalizacao-local.md +22 -0
- package/seedgridKB/SeedGrid/Toolkit/skills/planejar-mudanca/SKILL.md +38 -0
- package/seedgridKB/SeedGrid/Toolkit/skills/revisar-codigo/SKILL.md +33 -0
- package/seedgridKB/SeedGrid/Toolkit/skills/revisar-codigo/references/checklist.md +25 -0
- package/seedgridKB/SeedGrid/Toolkit/skills/seedgrid-backend-crud/SKILL.md +29 -0
- package/seedgridKB/SeedGrid/Toolkit/skills/seedgrid-bump/SKILL.md +26 -0
- package/seedgridKB/SeedGrid/Toolkit/skills/seedgrid-crud/SKILL.md +221 -0
- package/seedgridKB/SeedGrid/Toolkit/skills/seedgrid-crud/agents/openai.yaml +4 -0
- package/seedgridKB/SeedGrid/Toolkit/skills/seedgrid-crud/references/checklist.md +31 -0
- package/seedgridKB/SeedGrid/Toolkit/skills/seedgrid-crud/references/patterns.md +185 -0
- package/seedgridKB/SeedGrid/Toolkit/skills/seedgrid-crud/references/routing.md +66 -0
- package/seedgridKB/SeedGrid/Toolkit/skills/seedgrid-new-screen/SKILL.md +56 -0
- package/seedgridKB/SeedGrid/Toolkit/skills/seedgrid-new-screen/agents/openai.yaml +4 -0
- package/seedgridKB/SeedGrid/Toolkit/skills/seedgrid-new-screen/references/checklist.md +32 -0
- package/seedgridKB/SeedGrid/Toolkit/skills/seedgrid-new-screen/references/steps.md +114 -0
- package/seedgridKB/SeedGrid/Toolkit/skills/seedgrid-review/SKILL.md +61 -0
- package/seedgridKB/SeedGrid/Toolkit/skills/validar-mudanca/SKILL.md +29 -0
- package/seedgridKB/SeedGrid/Toolkit/templates/decisao-arquitetura.md +29 -0
- package/seedgridKB/SeedGrid/Toolkit/templates/especificacao-tarefa.md +32 -0
- package/seedgridKB/SeedGrid/Toolkit/templates/relatorio-validacao.md +30 -0
- package/seedgridKB/SeedGrid/Versoes.md +64 -0
- package/seedgridKB/SeedGrid/Visao-geral.md +35 -0
- package/seedgridKB/manifest.json +142 -0
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
---
|
|
2
|
+
tags: [seedgrid, extension, privacy, rust]
|
|
3
|
+
fonte: rust/seedgrid-rust-crate-privacy
|
|
4
|
+
branch: main
|
|
5
|
+
commit: be738a2d1e
|
|
6
|
+
analisado-em: 2026-10-14
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# privacy — porte Rust (`seedgrid-rust-crate-privacy`)
|
|
10
|
+
|
|
11
|
+
Porte da lib Java `seedgrid-quarkus-lib-privacy`. Lado Java: [[SeedGrid/Extensions/privacy/Visao-geral|Visão geral do privacy (Java)]] · [[SeedGrid/Extensions/privacy/Configuracao|Configuração do privacy (Java)]]. Configuração Rust: [[SeedGrid/Extensions/privacy/Configuracao-Rust|Configuração Rust do privacy]].
|
|
12
|
+
|
|
13
|
+
Base de privacidade LGPD/PII com duas capacidades: (1) **mascaramento** de dados pessoais em JSON/objetos; (2) **criptografia de campo at-rest** AES-256-GCM com **hash de busca** determinístico HMAC-SHA256. Crate puro (sem Axum/DB). **Confirmado** (`src/lib.rs`).
|
|
14
|
+
|
|
15
|
+
## Identificação
|
|
16
|
+
| Item | Valor |
|
|
17
|
+
|---|---|
|
|
18
|
+
| Pacote Cargo | `seedgrid-rust-crate-privacy` (lib `seedgrid_privacy`) |
|
|
19
|
+
| Versão | workspace `2026.10.14` |
|
|
20
|
+
| Features | nenhuma |
|
|
21
|
+
| Dependência SeedGrid | `seedgrid-macros` (derive `PersonalData`) |
|
|
22
|
+
| Consumidores (Cargo.toml) | audit (reexporta `masking`), login-tenancy, security, subdomain-tenancy |
|
|
23
|
+
|
|
24
|
+
## API pública
|
|
25
|
+
| Módulo / símbolo | Papel |
|
|
26
|
+
|---|---|
|
|
27
|
+
| `masking`: `MaskingStrategy` (`Full`, `Format`, `Partial`, `Hash`, `Template`, `Remove`), `MaskRule` (`format`, `hash`, `remove`…), `mask_json`, `PersonalDataResolver`, `PersonalDataSanitizer`, `ResolvedRule`, `masker::{cpf, cnpj, cep}`, `sha256_hex` | mascaramento; caminhos `a.b`, `lista[].campo`; regra repetida segue a ordem de inserção do `LinkedHashMap` Java |
|
|
28
|
+
| `crypto`: `CryptoService`, `AesGcmCryptoService::from_key_spec` / `from_raw_key`, `looks_encrypted`, `VERSION_PREFIX` (`v1`), `CryptoError` | cifra; formato `v1:BASE64(iv):BASE64(ct‖tag)` igual ao Java |
|
|
29
|
+
| `search`: `SearchHashService`, `HmacSha256SearchHashService::from_key_spec`, `SearchNormalization` (`None`, `Canonical`, `Email`), `ValueNormalizer`, `DefaultValueNormalizer`, `SecureSearch`, `EncryptedFieldSearch` | hash de busca |
|
|
30
|
+
| `field`: `CryptoProvider` (estado global), `EncryptedFieldConverter` (`from_provider`, `with_service`), `EncryptedFieldListener`, `SecureFieldsCarrier`, `SecureFieldDescriptor`, `apply_search_hashes_via_provider`, `FieldError`; constantes `ENCRYPTION_ENABLED_PROPERTY`, `CRYPTO_KEY_PROPERTY`, `SEARCH_KEY_PROPERTY` | integração com entidades (sem reflexão/JPA) |
|
|
31
|
+
| `#[derive(PersonalData)]` (de `seedgrid-macros`) | gera `SecureFieldsCarrier` + `encrypt_personal_data`/`decrypt_personal_data` a partir de `#[personal_data(search = "...", normalize = "...")]` |
|
|
32
|
+
|
|
33
|
+
### `CryptoProvider` (global)
|
|
34
|
+
- `is_encryption_enabled()`: override programático (`set_encryption_enabled_for_tests`) vence; senão lê `SEEDGRID_PRIVACY_ENCRYPTION_ENABLED`; padrão `false`.
|
|
35
|
+
- `get_crypto_service()` / `get_search_hash_service()`: o serviço configurado (`configure`, `configure_with_key`, `configure_search_hash_service`, `configure_search_with_key`) vence; senão é construído **sob demanda** a partir de `SEEDGRID_PRIVACY_CRYPTO_KEY` / `SEEDGRID_PRIVACY_SEARCH_KEY` e guardado em cache. Chave inválida → `None`.
|
|
36
|
+
- `get_secure_search()`, `configure_normalizer`, `get_normalizer`, `reset_for_tests`.
|
|
37
|
+
|
|
38
|
+
Fonte: `src/field.rs` → `impl CryptoProvider`. Detalhes em [[SeedGrid/Extensions/privacy/Configuracao-Rust|Configuração Rust]].
|
|
39
|
+
|
|
40
|
+
## Exemplos
|
|
41
|
+
Mascaramento:
|
|
42
|
+
```rust
|
|
43
|
+
use seedgrid_privacy::{mask_json, MaskRule, masker};
|
|
44
|
+
use serde_json::json;
|
|
45
|
+
|
|
46
|
+
let v = json!({ "cpf": "12345678900", "nome": "Joao", "email": "joao@x.com" });
|
|
47
|
+
let out = mask_json(&v, &[
|
|
48
|
+
MaskRule::format("cpf", "***.***.***-**"),
|
|
49
|
+
MaskRule::hash("email"),
|
|
50
|
+
MaskRule::remove("nome"),
|
|
51
|
+
]);
|
|
52
|
+
assert_eq!(masker::cpf(Some("12345678900")), "***.***.***-00");
|
|
53
|
+
```
|
|
54
|
+
Derive (de `tests/personal_data_derive.rs`):
|
|
55
|
+
```rust
|
|
56
|
+
use seedgrid_privacy::{CryptoProvider, PersonalData, SecureFieldsCarrier};
|
|
57
|
+
|
|
58
|
+
#[derive(PersonalData, Default)]
|
|
59
|
+
struct User {
|
|
60
|
+
#[personal_data(search = "email_hash", normalize = "email")]
|
|
61
|
+
email: String,
|
|
62
|
+
email_hash: Option<String>,
|
|
63
|
+
#[personal_data(search = "whats_app_hash")] // normalize padrão = canonical
|
|
64
|
+
whats_app: Option<String>,
|
|
65
|
+
whats_app_hash: Option<String>,
|
|
66
|
+
#[personal_data] // cifra sem gerar hash
|
|
67
|
+
cnpj: Option<String>,
|
|
68
|
+
}
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## Testes
|
|
72
|
+
Unitários em `src/*` (vetores portados do Java, ex.: `SHA-256("abc")`) e `tests/personal_data_derive.rs`. **Não executados.** Sem exemplo em `rust/examples/` (o `seedgrid-login-tenancy-example` usa a crate indiretamente).
|
|
73
|
+
|
|
74
|
+
## Notas de comportamento
|
|
75
|
+
|
|
76
|
+
- **Chave ausente/inválida com cifra ligada**: falha (fail-closed); o Rust devolve `CryptoError::InvalidKey` em vez de gravar em claro (`EncryptedFieldConverter::resolve_for_write`). Chave AES > 32 bytes é rejeitada (sem truncar).
|
|
77
|
+
- As mensagens de erro de chave inválida citam `seedgrid.crypto.key` / `seedgrid.search.key` (`src/crypto.rs`, `src/search.rs` → `decode_key`); a propriedade Java é `seedgrid.privacy.crypto.key`.
|
|
78
|
+
- `PARTIAL` com parâmetro negativo cai no padrão 2; `PARTIAL`/`TEMPLATE` contam caracteres Unicode (Java conta UTF-16).
|
|
79
|
+
- Os enums `CryptoAlgorithm`/`SearchAlgorithm` não existem no Rust.
|
|
80
|
+
|
|
81
|
+
## Enums
|
|
82
|
+
Java: [[SeedGrid/Extensions/privacy/Enums|Enums do privacy (Java)]].
|
|
83
|
+
|
|
84
|
+
| Enum | Arquivo | Java equivalente |
|
|
85
|
+
|---|---|---|
|
|
86
|
+
| `MaskingStrategy` | `src/masking.rs` | [[SeedGrid/Extensions/privacy/Enums#MaskingStrategy\|MaskingStrategy]] |
|
|
87
|
+
| `SearchNormalization` | `src/search.rs` | [[SeedGrid/Extensions/privacy/Enums#SearchNormalization\|SearchNormalization]] |
|
|
88
|
+
| `CryptoError`, `FieldError` | `src/crypto.rs`, `src/field.rs` | exceções |
|
|
89
|
+
|
|
90
|
+
### MaskingStrategy
|
|
91
|
+
Variantes: `Full`, `Format`, `Partial`, `Hash`, `Template`, `Remove` (os mesmos 6 valores do Java). Sem serde; as regras são montadas por código (`MaskRule::format/hash/remove…`).
|
|
92
|
+
|
|
93
|
+
| Valor | Efeito (`mask_value`) |
|
|
94
|
+
|---|---|
|
|
95
|
+
| `Full` | valor fixo = `param` ou `***` |
|
|
96
|
+
| `Format` | `param` como máscara pronta (vazio → `***`) |
|
|
97
|
+
| `Partial` | só os últimos N caracteres visíveis (`param` = N, padrão 2; negativo cai em 2 — o Java estoura) |
|
|
98
|
+
| `Hash` | SHA-256 hex minúsculo |
|
|
99
|
+
| `Template` | `#` revela, `*` mascara, outro char é literal |
|
|
100
|
+
| `Remove` | estrutural: remove o campo (`is_removal()`; `mask_value` devolve `None`) |
|
|
101
|
+
|
|
102
|
+
Conta caracteres Unicode (o Java conta unidades UTF-16).
|
|
103
|
+
|
|
104
|
+
### SearchNormalization
|
|
105
|
+
`None` (padrão), `Canonical`, `Email` — iguais ao Java (`NONE`, `CANONICAL`, `EMAIL`). `None` = valor exato; `Canonical` = trim + NFD sem acentos + minúsculas + só `[a-z0-9]`; `Email` = trim + minúsculas. No derive: `normalize = "none"|"canonical"|"email"` (padrão `canonical`). Sem serde.
|
|
106
|
+
|
|
107
|
+
`CryptoAlgorithm` (`AES_256_GCM`) e `SearchAlgorithm` (`HMAC_SHA256`) do Java **não existem** no Rust (o algoritmo é fixo).
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
---
|
|
2
|
+
tags: [seedgrid, library, privacy]
|
|
3
|
+
fonte: java/seedgrid-quarkus-lib-privacy
|
|
4
|
+
branch: main
|
|
5
|
+
commit: 79f8b03eeb
|
|
6
|
+
analisado-em: 2026-10-14
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# SeedGrid Privacy — Visão geral
|
|
10
|
+
|
|
11
|
+
> [!info] Library, não extension Quarkus
|
|
12
|
+
> `seedgrid-quarkus-lib-privacy` é uma **library Java** (jar único com índice Jandex), **não** uma extension Quarkus com `runtime`/`deployment`. Não há `@ConfigRoot`/`@ConfigMapping`; a config é lida via `ConfigProvider`. Documentada nesta estrutura por uniformidade.
|
|
13
|
+
|
|
14
|
+
Configuração: [[SeedGrid/Extensions/privacy/Configuracao|Configuração do privacy]] · Enums: [[SeedGrid/Extensions/privacy/Enums|Enums do privacy]] · Exemplos: [[SeedGrid/Extensions/privacy/Exemplos|Exemplos do privacy]] · [[SeedGrid/Extensions/privacy/Rust|Porte Rust]]
|
|
15
|
+
|
|
16
|
+
## Objetivo
|
|
17
|
+
|
|
18
|
+
Primitivas de privacidade (LGPD/GDPR) para entidades JPA. **Confirmado** (`README.md`, código):
|
|
19
|
+
1. **Secure Fields** — cifra reversível de campos `String` no banco (AES-256-GCM, formato `v1:base64(iv):base64(ciphertext)`) e hash determinístico HMAC-SHA256 (hex, 64 chars) num campo irmão para busca por igualdade. **Opt-in** por `seedgrid.privacy.encryption.enabled` (padrão desligado → grava valor cru).
|
|
20
|
+
2. **Masking de PII** — redação one-way nas saídas (auditoria, export, e-mail, log, resposta de API) via `@PersonalData`/`@MaskRule` + `PersonalDataSanitizer`, ou valor a valor com `PersonalDataMasker`. Não usa chave e funciona nos dois modos.
|
|
21
|
+
|
|
22
|
+
## Coordenadas e dependências
|
|
23
|
+
|
|
24
|
+
| Item | Valor |
|
|
25
|
+
|---|---|
|
|
26
|
+
| groupId:artifactId | `br.com.seedgrid:seedgrid-privacy` |
|
|
27
|
+
| Versão (pom) | `2026.07.27` |
|
|
28
|
+
| Parent | `br.com.seedgrid:seedgrid-parent:2026.07.02` |
|
|
29
|
+
| Dependências | `quarkus-arc`, `quarkus-hibernate-orm`, `quarkus-jackson`; testes: `quarkus-junit5`, AssertJ, Mockito, PostgreSQL + Testcontainers |
|
|
30
|
+
| Dependências SeedGrid | nenhuma. É usada por `seedgrid-commons` (embeddables `Address`/`Contact`) e por extensions (audit, security, subdomain-tenancy — segundo o README) |
|
|
31
|
+
|
|
32
|
+
Fonte: `java/seedgrid-quarkus-lib-privacy/pom.xml`. **Confirmado.**
|
|
33
|
+
|
|
34
|
+
## Arquitetura
|
|
35
|
+
|
|
36
|
+
```mermaid
|
|
37
|
+
flowchart TB
|
|
38
|
+
subgraph SF["Secure Fields (estático, sem CDI no caminho JPA)"]
|
|
39
|
+
BOOT["CryptoBootstrap<br/>@Singleton @Startup"] -->|"se chaves presentes"| CP["CryptoProvider<br/>(estado estático)"]
|
|
40
|
+
CONV["EncryptedFieldAttributeConverter<br/>@Convert"] --> CP
|
|
41
|
+
LIS["EncryptedFieldListener<br/>@EntityListeners"] --> CP
|
|
42
|
+
SRCH["EncryptedFieldSearch"] --> CP
|
|
43
|
+
CP --> AES["AesGcmCryptoService<br/>(crypto.key)"]
|
|
44
|
+
CP --> HMAC["HmacSha256SearchHashService<br/>(search.key)"]
|
|
45
|
+
end
|
|
46
|
+
subgraph MK["Masking (CDI)"]
|
|
47
|
+
SAN["PersonalDataSanitizer<br/>@ApplicationScoped"] --> RES["PersonalDataResolver<br/>@ApplicationScoped (cache por classe)"]
|
|
48
|
+
MASK["PersonalDataMasker (estático)"] --> STRAT["MaskingStrategy"]
|
|
49
|
+
SAN --> STRAT
|
|
50
|
+
end
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Fluxo do Secure Fields (**Confirmado**):
|
|
54
|
+
- **Escrita** (`EncryptedFieldAttributeConverter#convertToDatabaseColumn`): flag desligado → grava cru; ligado → cifra (valor já `v1:` não é recifrado).
|
|
55
|
+
- **Leitura** (`convertToEntityAttribute`): segue o **dado**, não o flag — valor `v1:...` é decifrado (exige `crypto.key`), valor cru volta como está.
|
|
56
|
+
- **Hash** (`EncryptedFieldListener`, `@PrePersist`/`@PreUpdate`): só se `@EncryptedField.searchField` estiver preenchido e o flag ligado; aplica `SearchNormalization` (`NONE`/`CANONICAL`/`EMAIL`), desce recursivamente em campos `@Embeddable` (profundidade máx. 5); o campo de hash deve ser **irmão** no mesmo objeto, senão `IllegalStateException`.
|
|
57
|
+
- **Busca**: `EncryptedFieldSearch#hashForSearch(valor[, normalização])` — usar a mesma normalização da anotação; `hashForSearch(valor)` usa `CANONICAL`.
|
|
58
|
+
- `CryptoBootstrap` (startup) configura os serviços se as chaves existirem; senão os serviços são criados lazy no primeiro uso.
|
|
59
|
+
|
|
60
|
+
## APIs públicas
|
|
61
|
+
|
|
62
|
+
| Pacote/Símbolo | Papel |
|
|
63
|
+
|---|---|
|
|
64
|
+
| `@EncryptedField(searchField, normalize)` | marca campo `String` sensível (cifra sempre AES-256-GCM e hash sempre HMAC-SHA256) |
|
|
65
|
+
| `EncryptedFieldAttributeConverter` | `AttributeConverter<String,String>` (`@Converter`, aplicar com `@Convert`) |
|
|
66
|
+
| `EncryptedFieldListener` | entity listener (`@EntityListeners`) |
|
|
67
|
+
| `EncryptedFieldSearch` | `hashForSearch(String)`, `hashForSearch(String, SearchNormalization)` |
|
|
68
|
+
| `CryptoProvider` (estático) | `isEncryptionEnabled()`, `looksEncrypted()`, `getCryptoService()`, `getSearchHashService()`, `getSecureSearch()`, `configure*()`, `setEncryptionEnabledForTests()`, `resetForTests()`; constantes dos nomes de propriedade |
|
|
69
|
+
| `CryptoService`, `SearchHashService`, `ValueNormalizer` (+ `AesGcmCryptoService`, `HmacSha256SearchHashService`, `DefaultValueNormalizer`) | interfaces/implementações substituíveis via `CryptoProvider.configure*` |
|
|
70
|
+
| `CryptoProperties` | POJO `cryptoKey`/`searchKey` |
|
|
71
|
+
| `masking.@PersonalData(type, maskWith)` | campo/getter com PII para redação |
|
|
72
|
+
| `masking.@MaskRule(field, type, maskWith, remove)` | regra por caminho (`cpf`, `endereco.cep`, `dependentes[].cpf`), usada por quem chama (ex.: `@Audit`) |
|
|
73
|
+
| `masking.MaskingStrategy` | `FULL`, `FORMAT`, `PARTIAL`, `HASH` (SHA-256), `TEMPLATE`, `REMOVE` |
|
|
74
|
+
| `masking.PersonalDataSanitizer` (bean) | `sanitizeToJson(value, sourceType, rules)`, `sanitizeToJson(value, rules)`, `sanitize(value, type[, rules])` (fail-closed) |
|
|
75
|
+
| `masking.PersonalDataResolver` (bean) | `resolveRules(Class)` |
|
|
76
|
+
| `masking.PersonalDataMasker` (estático) | `mask(value, strategy, param)`, `cpf()`, `cnpj()`, `cep()` |
|
|
77
|
+
|
|
78
|
+
Sem endpoints REST, eventos CDI ou migrations (as colunas cifradas/hash são do host; o exemplo usa `length = 512` para cifrado e `64` para hash).
|
|
79
|
+
|
|
80
|
+
## Testes existentes (não executados)
|
|
81
|
+
|
|
82
|
+
`src/test/java/br/com/seedgrid/privacy/`: `AesGcmCryptoServiceTest`, `HmacSha256SearchHashServiceTest`, `DefaultValueNormalizerTest`, `SearchNormalizationTest`, `EncryptedFieldAttributeConverterTest`, `EncryptedFieldListenerTest`, `EncryptionEnabledFlagTest`, `EncryptedFieldEmbeddableJpaTest` (JPA com `PrivacyTestPersistenceUnitInfo`, PostgreSQL/Testcontainers), `masking/{MaskingStrategy,PersonalDataMasker,PersonalDataResolver,PersonalDataSanitizer}Test`. ~57 anotações `@Test`/`@ParameterizedTest` por grep. Exemplo com testes de integração: `java/examples/seedgrid-privacy-example` (`CustomerEncryptionTest`, `PrivacySchemaBootstrapTest`). **Não executados.**
|
|
83
|
+
|
|
84
|
+
## Limitações
|
|
85
|
+
|
|
86
|
+
- **Validação no boot é parcial**: o `CryptoBootstrap` **não** verifica o flag nem a ausência de chaves; a falha ocorre no primeiro persist/hash/decifra. Por outro lado, uma chave **presente e inválida** (Base64 inválido ou < 32 bytes) lança no `CryptoBootstrap` mesmo com o flag desligado. **Confirmado** pelo código; que a exceção em bean `@Startup` aborta o boot é **Inferência**.
|
|
87
|
+
- `crypto.key` com mais de 32 bytes passa no `KeyDecoder` (mínimo 32) mas o AES só aceita 16/24/32 bytes — falharia no primeiro `encrypt` (**Inferência**, comportamento do JCE). Use exatamente 32 bytes.
|
|
88
|
+
- Estado global estático em `CryptoProvider` (não é bean CDI) — compartilhado por toda a JVM.
|
|
89
|
+
- Para e-mail use `SearchNormalization.EMAIL`; `CANONICAL` remove a pontuação e faria e-mails diferentes colidirem (Javadoc de `SearchNormalization`).
|
|
90
|
+
- Trocar de modo com dados existentes exige migração: desligado→ligado deixa registros antigos crus e sem hash; ligado→desligado exige a chave para ler valores `v1:`.
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
---
|
|
2
|
+
tags: [seedgrid, extension, realtime, rust, configuracao]
|
|
3
|
+
fonte: rust/seedgrid-rust-crate-realtime
|
|
4
|
+
branch: main
|
|
5
|
+
commit: be738a2d1e
|
|
6
|
+
analisado-em: 2026-10-14
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# realtime — Configuração Rust
|
|
10
|
+
|
|
11
|
+
Visão do crate: [[SeedGrid/Extensions/realtime/Rust|Porte Rust do realtime]] · Java: [[SeedGrid/Extensions/realtime/Configuracao|Configuração do realtime (Java)]].
|
|
12
|
+
|
|
13
|
+
## Cabeçalho
|
|
14
|
+
| Item | Valor |
|
|
15
|
+
|---|---|
|
|
16
|
+
| Struct | `RealtimeConfig` — `rust/seedgrid-rust-crate-realtime/src/config.rs` |
|
|
17
|
+
| Mecanismo | `RealtimeConfig::from_env()` (= `from_lookup(std::env::var)`), `RealtimePublisher::from_env()`, ou struct do host |
|
|
18
|
+
| Prefixo Java | `seedgrid.realtime` (`@ConfigMapping` em `java/seedgrid-quarkus-ext-realtime/runtime/.../configs/RealtimeConfig.java`) |
|
|
19
|
+
| Leitura | no boot (`from_env`); o limite por minuto é fixado ao montar `rest::router` |
|
|
20
|
+
|
|
21
|
+
Regra de leitura (`from_lookup`): valor `trim`ado; vazio = ausente; valor que não parseia → padrão.
|
|
22
|
+
|
|
23
|
+
## Variáveis de ambiente
|
|
24
|
+
| Propriedade | Tipo | Valores aceitos | Padrão | Obrigatória | Comportamento | Uso | Relações | Leitura | Origem |
|
|
25
|
+
|---|---|---|---|---|---|---|---|---|---|
|
|
26
|
+
| `SEEDGRID_REALTIME_ENABLED` (Java `seedgrid.realtime.enabled`) | bool | `true` (case-insensitive) liga; qualquer outro valor desliga | `false` | Não | desligado: `connect` responde `{enabled:false}` e avisos são no-op | `RealtimePublisher::new` | exige URL + chave | `from_env()` | `config.rs`; `publisher.rs` → `new` |
|
|
27
|
+
| `SEEDGRID_REALTIME_PULSE_BASE_URL` (Java `seedgrid.realtime.pulse.base-url`) | `Option<String>` | URL | — | Condicional: obrigatória se ligado | ligado sem ela → log de erro e realtime desligado | `PulsePublisher::builder` | `PULSE_PROJECT_KEY` | `from_env()` | `publisher.rs` → `new` |
|
|
28
|
+
| 🔒 `SEEDGRID_REALTIME_PULSE_PROJECT_KEY` (Java `seedgrid.realtime.pulse.project-key`) | `Option<String>` | chave `project` do Pulse | — | Condicional: obrigatória se ligado | idem | idem | `PULSE_BASE_URL` | `from_env()` | idem |
|
|
29
|
+
| `SEEDGRID_REALTIME_TOKEN_TTL` (Java `seedgrid.realtime.token-ttl`) | Duration | ISO `PTnHnMnS`; número = segundos; sufixos via `seedgrid_runtime::parse_duration` (ex.: `5m`, `300s`); `0`/inválido → padrão | `PT5M` (300 s) | Não | janela para **conectar** com o token (`ttl_seconds`, mínimo 1) | `issue_connect_token` | — | `from_env()` | `config.rs` → `parse_duration`; `publisher.rs` |
|
|
30
|
+
| `SEEDGRID_REALTIME_CHANNELS` (Java `seedgrid.realtime.channels`) | `Option<Vec<String>>` | lista separada por vírgula | todos os da conta (`None`) | Não | channels que o token deixa assinar; também devolvidos no JSON | `issue_connect_token`, `connect` | — | `from_env()` | `config.rs`; `rest.rs` |
|
|
31
|
+
| `SEEDGRID_REALTIME_CONNECT_RATE_PER_MINUTE` (Java `seedgrid.realtime.connect-rate-per-minute`) | u32 | inteiro ≥ 0; `0` desliga | `30` | Não | pedidos de token por minuto, por usuário, por instância | `ConnectRateLimiter` em `rest::router` | — | `from_env()`; fixado em `router()` | `config.rs`; `rest.rs`; `rate_limit.rs` |
|
|
32
|
+
|
|
33
|
+
Mapeamento Java ↔ env: **Confirmado** (nomes dos métodos do `RealtimeConfig.java` + regra MicroProfile sem ambiguidade; o teste `reads_the_same_env_as_the_java_extension` usa esses nomes). Padrões iguais ao Java (`false`, `PT5M`, `30`).
|
|
34
|
+
|
|
35
|
+
## Struct `RealtimeConfig`
|
|
36
|
+
| Campo | `Default` |
|
|
37
|
+
|---|---|
|
|
38
|
+
| `enabled` | `false` |
|
|
39
|
+
| `pulse_base_url` | `None` |
|
|
40
|
+
| `pulse_project_key` | `None` |
|
|
41
|
+
| `token_ttl` | 300 s |
|
|
42
|
+
| `channels` | `None` |
|
|
43
|
+
| `connect_rate_per_minute` | `30` |
|
|
44
|
+
|
|
45
|
+
## Features Cargo
|
|
46
|
+
Nenhuma.
|
|
47
|
+
|
|
48
|
+
## Somente testes
|
|
49
|
+
| Variável | Onde |
|
|
50
|
+
|---|---|
|
|
51
|
+
| `REALTIME_E2E_PULSE_URL`, 🔒 `REALTIME_E2E_SERVER_KEY` | `tests/pulse_e2e_it.rs` (`#[ignore]`) |
|
|
52
|
+
|
|
53
|
+
## Configuração mínima
|
|
54
|
+
```dotenv
|
|
55
|
+
SEEDGRID_REALTIME_ENABLED=true
|
|
56
|
+
SEEDGRID_REALTIME_PULSE_BASE_URL=http://<pulse-host>:<porta>
|
|
57
|
+
SEEDGRID_REALTIME_PULSE_PROJECT_KEY=<SEGREDO:pulse-project-key>
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## Configuração completa comentada
|
|
61
|
+
```dotenv
|
|
62
|
+
SEEDGRID_REALTIME_ENABLED=true
|
|
63
|
+
SEEDGRID_REALTIME_PULSE_BASE_URL=http://<pulse-host>:<porta> # endereço visto pelo backend
|
|
64
|
+
SEEDGRID_REALTIME_PULSE_PROJECT_KEY=<SEGREDO:pulse-project-key>
|
|
65
|
+
SEEDGRID_REALTIME_TOKEN_TTL=PT5M # ou 300 / 5m
|
|
66
|
+
SEEDGRID_REALTIME_CHANNELS=backup,report # vazio = todos
|
|
67
|
+
SEEDGRID_REALTIME_CONNECT_RATE_PER_MINUTE=30 # 0 desliga
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## Receitas
|
|
71
|
+
- **Desligado (padrão)**: nada a configurar; o front fica no polling.
|
|
72
|
+
- **Avisos do backup**: `RealtimePublisher::from_env()` e passe-o em `BackupOverrides.user_events` ([[SeedGrid/Extensions/backup/Configuracao-Rust|Configuração Rust do backup]]).
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
---
|
|
2
|
+
tags: [seedgrid, extension, realtime, configuracao]
|
|
3
|
+
fonte: java/seedgrid-quarkus-ext-realtime
|
|
4
|
+
branch: main
|
|
5
|
+
commit: 8f13639b3c
|
|
6
|
+
analisado-em: 2026-10-14
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Realtime (Quarkus) — configuração
|
|
10
|
+
|
|
11
|
+
Voltar: [[SeedGrid/Extensions/realtime/Visao-geral|Visão geral do realtime]] · [[SeedGrid/Extensions/realtime/Exemplos|Exemplos]] · [[SeedGrid/Extensions/realtime/Rust|Porte Rust]]
|
|
12
|
+
|
|
13
|
+
## 1. Cabeçalho
|
|
14
|
+
|
|
15
|
+
| Item | Valor |
|
|
16
|
+
|---|---|
|
|
17
|
+
| Prefixo | `seedgrid.realtime` |
|
|
18
|
+
| Classe de config | `java/seedgrid-quarkus-ext-realtime/runtime/src/main/java/br/com/seedgrid/realtime/configs/RealtimeConfig.java` → `RealtimeConfig` (grupo aninhado `RealtimeConfig.Pulse`) |
|
|
19
|
+
| Mecanismo | SmallRye `@ConfigMapping(prefix = "seedgrid.realtime")` + `@StaticInitSafe`; registrado também por `SeedgridRealtimeProcessor#config()` (`ConfigMappingBuildItem`) |
|
|
20
|
+
| Fase | Runtime (ConfigMapping no módulo runtime). Na prática os valores são lidos uma vez, quando o bean `RealtimePublisher`/`ConnectRateLimiter` é criado. |
|
|
21
|
+
| Outras fontes | Não há `microprofile-config.properties` no runtime nem `@ConfigProperty`/`ConfigProvider` adicionais. |
|
|
22
|
+
|
|
23
|
+
Variáveis de ambiente: a regra MicroProfile se aplica sem ambiguidade a todas as chaves abaixo, e os nomes `SEEDGRID_REALTIME_*` estão confirmados no README e na mensagem de log de `RealtimePublisher#connect`.
|
|
24
|
+
|
|
25
|
+
## 2. Propriedades da extension
|
|
26
|
+
|
|
27
|
+
### Ativação e conexão com o Pulse
|
|
28
|
+
|
|
29
|
+
| Propriedade | Tipo | Valores aceitos | Padrão | Obrigatória | Comportamento | Uso | Relações | Fase | Origem |
|
|
30
|
+
|---|---|---|---|---|---|---|---|---|---|
|
|
31
|
+
| `seedgrid.realtime.enabled` (env `SEEDGRID_REALTIME_ENABLED`) | `boolean` | `true`/`false` | `false` | Não | Liga o realtime. Desligado: `/realtime/connect` responde `200 {enabled:false}`, `publishToUser` é no-op e nada fala com o Pulse. | Liga a integração com o Pulse | Ligado exige `pulse.base-url` e `pulse.project-key`; se faltar algum, o realtime fica desligado e é logado ERROR (a aplicação sobe) | Runtime | Decl.: `RealtimeConfig#enabled()`. Consumo: `RealtimePublisher#connect(RealtimeConfig)` |
|
|
32
|
+
| `seedgrid.realtime.pulse.base-url` (env `SEEDGRID_REALTIME_PULSE_BASE_URL`) | `Optional<String>` | URL do Pulse visível pelo backend (pode ser endereço interno) | — | Condicional: necessária quando `enabled=true` | Valor em branco é tratado como ausente (`filter(!isBlank)`). Passado a `PulsePublisher.builder().baseUrl(...)`. | Endereço do Pulse | Com `enabled=true` e sem valor → realtime desligado + log ERROR | Runtime | Decl.: `RealtimeConfig.Pulse#baseUrl()`. Consumo: `RealtimePublisher#connect` |
|
|
33
|
+
| 🔒 `seedgrid.realtime.pulse.project-key` (env `SEEDGRID_REALTIME_PULSE_PROJECT_KEY`) | `Optional<String>` | chave `project` da conta no Pulse | — | Condicional: necessária quando `enabled=true` | Segredo; fica só no backend. Em branco = ausente. Enviada pelo SDK ao Pulse (header `x-api-key` nos testes). | Autenticar o backend no Pulse | Idem `base-url` | Runtime | Decl.: `RealtimeConfig.Pulse#projectKey()`. Consumo: `RealtimePublisher#connect` |
|
|
34
|
+
|
|
35
|
+
### Token de conexão e limites
|
|
36
|
+
|
|
37
|
+
| Propriedade | Tipo | Valores aceitos | Padrão | Obrigatória | Comportamento | Uso | Relações | Fase | Origem |
|
|
38
|
+
|---|---|---|---|---|---|---|---|---|---|
|
|
39
|
+
| `seedgrid.realtime.token-ttl` (env `SEEDGRID_REALTIME_TOKEN_TTL`) | `Duration` | ISO-8601 (`PT5M`) ou formatos aceitos pelo conversor de `Duration` do SmallRye | `PT5M` | Não | Janela para **conectar**; a conexão aberta segue depois do vencimento. Repassado ao Pulse em `IssueConnectTokenInput` (o teste com `PT2M` confirma o envio de `ttlSeconds: 120`). Sem validação local de faixa. | Validade do token | Só tem efeito com o realtime ligado | Runtime | Decl.: `RealtimeConfig#tokenTtl()`. Consumo: `RealtimePublisher#issueConnectToken` |
|
|
40
|
+
| `seedgrid.realtime.channels` (env `SEEDGRID_REALTIME_CHANNELS`) | `Optional<List<String>>` | lista separada por vírgula | — (ausente = todos os channels da conta) | Não | Restringe os channels que o token deixa assinar; também é devolvido no corpo do `/realtime/connect`. | Escopo de assinatura | — | Runtime | Decl.: `RealtimeConfig#channels()`. Consumo: `RealtimePublisher#issueConnectToken`, `RealtimeController#connect` |
|
|
41
|
+
| `seedgrid.realtime.connect-rate-per-minute` (env `SEEDGRID_REALTIME_CONNECT_RATE_PER_MINUTE`) | `int` | inteiro; `<= 0` desliga | `30` | Não | Pedidos de token por minuto **por usuário**, janela fixa de 60 s, em memória por instância. Excedido → `429` com `Retry-After` (segundos, mínimo 1). | Proteger a cota de emissões da project key | Só é consultado com o realtime ligado (o controller responde `enabled:false` antes) | Runtime | Decl.: `RealtimeConfig#connectRatePerMinute()`. Consumo: `ConnectRateLimiter(RealtimeConfig)` / `#tryAcquire` |
|
|
42
|
+
|
|
43
|
+
Total: **6 propriedades públicas**.
|
|
44
|
+
|
|
45
|
+
## 3. Propriedades de frameworks/bibliotecas
|
|
46
|
+
|
|
47
|
+
A extension não define nem exige propriedades `quarkus.*`, `mp.*` ou `smallrye.*` (não há `microprofile-config.properties` nem `RunTimeConfigurationDefaultBuildItem`). Indiretamente, `@Authenticated` depende do mecanismo de autenticação configurado no host (ex.: JWT do `seedgrid-security`). (Inferência)
|
|
48
|
+
|
|
49
|
+
## 4. Propriedades usadas só em testes
|
|
50
|
+
|
|
51
|
+
Arquivo `runtime/src/test/resources/application.properties` e `FakePulse`:
|
|
52
|
+
|
|
53
|
+
| Propriedade | Valor no teste | Observação |
|
|
54
|
+
|---|---|---|
|
|
55
|
+
| `quarkus.http.test-port` | `0` | porta aleatória |
|
|
56
|
+
| `seedgrid.realtime.enabled` | `true` | `RealtimeDisabledTest` sobrescreve para `false` via `QuarkusTestProfile` |
|
|
57
|
+
| `seedgrid.realtime.pulse.project-key` | `<SEGREDO:pulse-project-key-de-teste>` | valor fictício de teste (não copiado) |
|
|
58
|
+
| `seedgrid.realtime.token-ttl` | `PT2M` | — |
|
|
59
|
+
| `seedgrid.realtime.connect-rate-per-minute` | `1000` | evita 429 nos testes |
|
|
60
|
+
| `seedgrid.realtime.pulse.base-url` | URL do WireMock | injetada por `FakePulse#start()` |
|
|
61
|
+
|
|
62
|
+
## 5. Aliases, obsoletas, renomeações
|
|
63
|
+
|
|
64
|
+
Nenhum alias encontrado. O README declara que a crate Rust usa "mesmo contrato: rota, corpo, config, eventos" — detalhes em [[SeedGrid/Extensions/realtime/Rust|Porte Rust]].
|
|
65
|
+
|
|
66
|
+
## 6. Configuração mínima
|
|
67
|
+
|
|
68
|
+
```properties
|
|
69
|
+
seedgrid.realtime.enabled=true
|
|
70
|
+
seedgrid.realtime.pulse.base-url=<URL-DO-PULSE-VISTA-PELO-BACKEND>
|
|
71
|
+
seedgrid.realtime.pulse.project-key=${SEEDGRID_REALTIME_PULSE_PROJECT_KEY}
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Sem nenhuma configuração a extension fica desligada (padrão `enabled=false`) e o endpoint responde `{enabled:false}`.
|
|
75
|
+
|
|
76
|
+
## 7. Configuração completa comentada
|
|
77
|
+
|
|
78
|
+
```properties
|
|
79
|
+
# Liga o realtime (padrão: false)
|
|
80
|
+
seedgrid.realtime.enabled=true
|
|
81
|
+
# Pulse visto pelo backend (pode ser endereço interno)
|
|
82
|
+
seedgrid.realtime.pulse.base-url=<URL-DO-PULSE>
|
|
83
|
+
# SEGREDO: chave "project" da conta no Pulse. Use variável de ambiente.
|
|
84
|
+
seedgrid.realtime.pulse.project-key=<SEGREDO:pulse-project-key>
|
|
85
|
+
# Janela para conectar (padrão: PT5M)
|
|
86
|
+
seedgrid.realtime.token-ttl=PT5M
|
|
87
|
+
# Channels permitidos no token (ausente = todos os da conta)
|
|
88
|
+
seedgrid.realtime.channels=backup,report
|
|
89
|
+
# Pedidos de token por minuto, por usuário e por instância (padrão: 30; 0 desliga)
|
|
90
|
+
seedgrid.realtime.connect-rate-per-minute=30
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## 8. Receitas
|
|
94
|
+
|
|
95
|
+
- **Desligar em um ambiente**: `SEEDGRID_REALTIME_ENABLED=false` (ou não definir). A tela recebe `{enabled:false}` e fica no polling.
|
|
96
|
+
- **Restringir assinaturas a poucos channels**: `SEEDGRID_REALTIME_CHANNELS=backup,report`.
|
|
97
|
+
- **Remover o limite de emissão** (ex.: testes de carga): `SEEDGRID_REALTIME_CONNECT_RATE_PER_MINUTE=0`.
|
|
98
|
+
- **Pulse em rede interna**: aponte `pulse.base-url` para o endereço interno; o front usa o `connectUrl` devolvido pelo Pulse (não configurado aqui).
|
|
99
|
+
|
|
100
|
+
## 9. Dúvidas e lacunas
|
|
101
|
+
|
|
102
|
+
- O formato aceito de `Duration` além do ISO-8601 depende do conversor do SmallRye/Quarkus (Não confirmado neste repositório).
|
|
103
|
+
- Não há validação local de `token-ttl` (negativo/zero); o comportamento fica a cargo do Pulse (Não confirmado).
|
|
104
|
+
- Com várias réplicas, o limite de `connect-rate-per-minute` é por instância (Inferência).
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
---
|
|
2
|
+
tags: [seedgrid, extension, realtime, exemplos]
|
|
3
|
+
fonte: java/seedgrid-quarkus-ext-realtime
|
|
4
|
+
branch: main
|
|
5
|
+
commit: 8f13639b3c
|
|
6
|
+
analisado-em: 2026-10-14
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Realtime (Quarkus) — exemplos
|
|
10
|
+
|
|
11
|
+
Voltar: [[SeedGrid/Extensions/realtime/Visao-geral|Visão geral do realtime]] · [[SeedGrid/Extensions/realtime/Configuracao|Configuração]]
|
|
12
|
+
|
|
13
|
+
## 1. Dependência
|
|
14
|
+
|
|
15
|
+
Fonte: `README.md`.
|
|
16
|
+
|
|
17
|
+
```xml
|
|
18
|
+
<dependency>
|
|
19
|
+
<groupId>br.com.seedgrid</groupId>
|
|
20
|
+
<artifactId>seedgrid-realtime</artifactId>
|
|
21
|
+
<version>2026.10.08</version>
|
|
22
|
+
</dependency>
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## 2. Avisar um usuário pelo evento neutro (preferido)
|
|
26
|
+
|
|
27
|
+
Fonte: `README.md` e `RealtimeEnabledTest#userEventIsPublishedToThatUser`. O processo não depende desta extension — só do `SgUserEvent` do `seedgrid-commons`. Construtor: `SgUserEvent(userPublicId, channel, event, data)`.
|
|
28
|
+
|
|
29
|
+
```java
|
|
30
|
+
import br.com.seedgrid.commons.events.SgUserEvent;
|
|
31
|
+
import jakarta.enterprise.event.Event;
|
|
32
|
+
import jakarta.inject.Inject;
|
|
33
|
+
import java.util.Map;
|
|
34
|
+
|
|
35
|
+
@Inject Event<SgUserEvent> userEvents;
|
|
36
|
+
|
|
37
|
+
// Dentro da transação que grava o resultado: o aviso sai depois do commit (AFTER_SUCCESS).
|
|
38
|
+
userEvents.fire(new SgUserEvent(userPublicId, "report", "report.finished.success",
|
|
39
|
+
Map.of("reportPublicId", report.getPublicId())));
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
`data` leva apenas identificadores; a tela busca o registro pela API normal.
|
|
43
|
+
|
|
44
|
+
## 3. Avisar diretamente com o `RealtimePublisher`
|
|
45
|
+
|
|
46
|
+
Fonte: assinatura de `RealtimePublisher#publishToUser(String, String, String, Map<String,Object>)`.
|
|
47
|
+
|
|
48
|
+
```java
|
|
49
|
+
import br.com.seedgrid.realtime.services.RealtimePublisher;
|
|
50
|
+
|
|
51
|
+
@Inject RealtimePublisher realtime;
|
|
52
|
+
|
|
53
|
+
// Assíncrono; nunca propaga erro. Desligado = no-op.
|
|
54
|
+
realtime.publishToUser("backup", "backup.finished.success", userPublicId,
|
|
55
|
+
Map.of("exportPublicId", exportPublicId, "status", "DONE"));
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## 4. Front pedindo o token
|
|
59
|
+
|
|
60
|
+
Fonte: `RealtimeController` e tabela do `README.md`.
|
|
61
|
+
|
|
62
|
+
```http
|
|
63
|
+
POST /realtime/connect
|
|
64
|
+
Authorization: Bearer <JWT-DO-USUARIO>
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Respostas possíveis:
|
|
68
|
+
|
|
69
|
+
```json
|
|
70
|
+
{ "enabled": false }
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
```json
|
|
74
|
+
{ "enabled": true, "token": "pct_...", "expiresAt": 1791119100, "connectUrl": "wss://...", "channels": ["backup"] }
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
`401` (sem usuário), `429` com `Retry-After`, `503` (Pulse indisponível → manter polling).
|
|
78
|
+
|
|
79
|
+
## 5. Configuração por ambiente
|
|
80
|
+
|
|
81
|
+
Fonte: `RealtimeConfig` e `README.md`. Detalhes em [[SeedGrid/Extensions/realtime/Configuracao|Configuração]].
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
SEEDGRID_REALTIME_ENABLED=true
|
|
85
|
+
SEEDGRID_REALTIME_PULSE_BASE_URL=<URL-DO-PULSE>
|
|
86
|
+
SEEDGRID_REALTIME_PULSE_PROJECT_KEY=<SEGREDO:pulse-project-key>
|
|
87
|
+
SEEDGRID_REALTIME_TOKEN_TTL=PT5M
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## 6. Testando com Pulse fingido
|
|
91
|
+
|
|
92
|
+
Fonte: `runtime/src/test/java/br/com/seedgrid/realtime/FakePulse.java` — um `QuarkusTestResourceLifecycleManager` que sobe WireMock e devolve `seedgrid.realtime.pulse.base-url` apontando para ele.
|
|
93
|
+
|
|
94
|
+
```java
|
|
95
|
+
@QuarkusTest
|
|
96
|
+
@QuarkusTestResource(FakePulse.class)
|
|
97
|
+
class MeuTeste {
|
|
98
|
+
@Test
|
|
99
|
+
@TestSecurity(user = "7f3a9c1e-0000-4000-8000-000000000001")
|
|
100
|
+
void conecta() {
|
|
101
|
+
given().when().post("/realtime/connect").then().statusCode(200);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
```
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
---
|
|
2
|
+
tags: [seedgrid, extension, realtime, rust]
|
|
3
|
+
fonte: rust/seedgrid-rust-crate-realtime
|
|
4
|
+
branch: main
|
|
5
|
+
commit: be738a2d1e
|
|
6
|
+
analisado-em: 2026-10-14
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# realtime — porte Rust (`seedgrid-rust-crate-realtime`)
|
|
10
|
+
|
|
11
|
+
Porte da extensão Quarkus `seedgrid-quarkus-ext-realtime` ("mesmo env, mesma rota, mesmo JSON"). Lado Java: [[SeedGrid/Extensions/realtime/Visao-geral|Visão geral do realtime (Java)]] · [[SeedGrid/Extensions/realtime/Configuracao|Configuração do realtime (Java)]]. Configuração Rust: [[SeedGrid/Extensions/realtime/Configuracao-Rust|Configuração Rust do realtime]].
|
|
12
|
+
|
|
13
|
+
Avisos em tempo real ao **usuário logado** via SeedGrid Pulse: entrega ao front um token curto de conexão (`POST /realtime/connect`) e publica os `SgUserEvent` da plataforma. **Confirmado** (`src/lib.rs`, README).
|
|
14
|
+
|
|
15
|
+
## Identificação
|
|
16
|
+
| Item | Valor |
|
|
17
|
+
|---|---|
|
|
18
|
+
| Pacote Cargo | `seedgrid-rust-crate-realtime` (lib `seedgrid_realtime`) |
|
|
19
|
+
| Versão | workspace `2026.10.14` |
|
|
20
|
+
| Features | nenhuma |
|
|
21
|
+
| Dependências SeedGrid | `seedgrid-runtime` (eventos `SgUserEvent`/`UserEventSink`, `parse_duration`); `seedgrid-pulse-publisher-rust` (path `../../../seedgrid-products/seedgrid-pulse/components/publishers/seedgrid-pulse-publisher-rust`, **fora** do `seedgrid-platform`) |
|
|
22
|
+
| Dev-deps | `seedgrid-pulse-subscriber-rust` (também fora do repo), `wiremock` |
|
|
23
|
+
|
|
24
|
+
Observação: o crate só compila com o repositório `seedgrid-products` presente ao lado de `seedgrid-platform` (**Confirmado** pelo `Cargo.toml`; o caminho existe nesta máquina em `seedgrid-products/...`).
|
|
25
|
+
|
|
26
|
+
## API pública
|
|
27
|
+
| Símbolo | Papel | Fonte |
|
|
28
|
+
|---|---|---|
|
|
29
|
+
| `RealtimeConfig` (`from_env`, `from_lookup`, `Default`), `config::parse_duration` | config | `src/config.rs` |
|
|
30
|
+
| `RealtimePublisher::new(config)` / `from_env()` | cliente do Pulse; ligado sem URL ou chave → loga erro e fica desligado (a app sobe) | `src/publisher.rs` |
|
|
31
|
+
| `RealtimePublisher::is_enabled`, `config`, `publish_to_user(channel, event, user_public_id, data)`, `issue_connect_token(user_public_id)` | publicação (nunca propaga erro) e emissão de token | `src/publisher.rs` |
|
|
32
|
+
| `impl UserEventSink for RealtimePublisher` | publica `SgUserEvent` numa task própria (par do `@Observes(during = AFTER_SUCCESS)` Java) | `src/publisher.rs` |
|
|
33
|
+
| `IssueError::{Disabled, Pulse}` | erro de emissão | `src/publisher.rs` |
|
|
34
|
+
| `rest::router(publisher, Arc<dyn RealtimeIdentityResolver>)` | rota `POST /realtime/connect` | `src/rest.rs` |
|
|
35
|
+
| trait `RealtimeIdentityResolver` | o host informa o usuário da sessão (ex.: `sub` do JWT) | `src/rest.rs` |
|
|
36
|
+
| `RealtimeConnectResponse` | JSON de resposta (`enabled`, `token`, `expiresAt`, `connectUrl`, `channels`) | `src/rest.rs` |
|
|
37
|
+
| `rate_limit::ConnectRateLimiter` | janela fixa de 1 min por usuário, em memória, por instância | `src/rate_limit.rs` |
|
|
38
|
+
|
|
39
|
+
## Endpoint `POST /realtime/connect`
|
|
40
|
+
| Status | Quando |
|
|
41
|
+
|---|---|
|
|
42
|
+
| 401 | sem usuário (vale mesmo com realtime desligado) |
|
|
43
|
+
| 200 `{enabled:false}` | desligado ou não configurado |
|
|
44
|
+
| 429 + `Retry-After` | limite `connect_rate_per_minute` excedido |
|
|
45
|
+
| 200 `{enabled:true, token, expiresAt, connectUrl, channels}` | token emitido |
|
|
46
|
+
| 503 | Pulse não respondeu/recusou |
|
|
47
|
+
|
|
48
|
+
Fonte: `src/rest.rs` → `connect`.
|
|
49
|
+
|
|
50
|
+
## Exemplo (doc de `src/lib.rs`)
|
|
51
|
+
```rust
|
|
52
|
+
use seedgrid_realtime::RealtimePublisher;
|
|
53
|
+
use seedgrid_runtime::events::UserEventSink;
|
|
54
|
+
|
|
55
|
+
let realtime = RealtimePublisher::from_env();
|
|
56
|
+
let api = seedgrid_realtime::rest::router(realtime.clone(), identity);
|
|
57
|
+
let user_events: Arc<dyn UserEventSink> = Arc::new(realtime.clone());
|
|
58
|
+
// avisos do backup:
|
|
59
|
+
let overrides = BackupOverrides { user_events: Arc::new(realtime), ..Default::default() };
|
|
60
|
+
```
|
|
61
|
+
Não há app em `rust/examples/` para realtime.
|
|
62
|
+
|
|
63
|
+
## Testes
|
|
64
|
+
- Unitários (`src/config.rs`: durações, mesmo env do Java, padrão desligado) e `tests/realtime_it.rs` (router contra Pulse simulado com `wiremock`: 401, 429, 503, desligado, publicação).
|
|
65
|
+
- `tests/pulse_e2e_it.rs` (`#[ignore]`) contra Pulse real com `REALTIME_E2E_PULSE_URL` e `REALTIME_E2E_SERVER_KEY`.
|
|
66
|
+
- **Não executados.**
|
|
67
|
+
|
|
68
|
+
## Enums
|
|
69
|
+
Sem enums de domínio. Enum público de erro: `IssueError` (`src/publisher.rs`) — `Disabled` (realtime desligado; `POST /realtime/connect` responde `{enabled:false}`) e `Pulse(PulseError)` (Pulse recusou ou não respondeu; vira HTTP 503).
|