@seedgrid/ai-toolkit 0.3.1 → 0.3.3

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 (67) hide show
  1. package/README.md +3 -3
  2. package/dist/adapters/shared.js +12 -7
  3. package/dist/cli.js +1 -1
  4. package/dist/commands/review.js +2 -2
  5. package/dist/core/plan.js +5 -0
  6. package/docs/arquitetura.md +2 -2
  7. package/docs/compatibilidade.md +1 -1
  8. package/docs/formato.md +4 -4
  9. package/package.json +1 -1
  10. package/seedgridKB/SeedGrid/Extensions/Catalogo-de-propriedades.md +3 -2
  11. package/seedgridKB/SeedGrid/Extensions/Indice.md +1 -0
  12. package/seedgridKB/SeedGrid/Extensions/ai-pricing/Configuracao.md +11 -18
  13. package/seedgridKB/SeedGrid/Extensions/ai-pricing/Exemplos.md +2 -2
  14. package/seedgridKB/SeedGrid/Extensions/ai-pricing/Visao-geral.md +2 -2
  15. package/seedgridKB/SeedGrid/Extensions/business-days/Configuracao.md +36 -0
  16. package/seedgridKB/SeedGrid/Extensions/business-days/Exemplos.md +37 -0
  17. package/seedgridKB/SeedGrid/Extensions/business-days/Visao-geral.md +48 -0
  18. package/seedgridKB/SeedGrid/Extensions/exchange/Configuracao.md +10 -16
  19. package/seedgridKB/SeedGrid/Extensions/exchange/Exemplos.md +2 -2
  20. package/seedgridKB/SeedGrid/Extensions/exchange/Visao-geral.md +2 -2
  21. package/seedgridKB/SeedGrid/Extensions/login-tenancy/Configuracao.md +4 -5
  22. package/seedgridKB/SeedGrid/Extensions/login-tenancy/Visao-geral.md +1 -1
  23. package/seedgridKB/SeedGrid/Extensions/subdomain-tenancy/Configuracao.md +3 -2
  24. package/seedgridKB/SeedGrid/Extensions/subdomain-tenancy/Visao-geral.md +2 -2
  25. package/seedgridKB/SeedGrid/Mapa-dos-repositorios.md +9 -0
  26. package/seedgridKB/SeedGrid/Produtos/Pulse/Autenticacao-e-chaves.md +89 -0
  27. package/seedgridKB/SeedGrid/Produtos/Pulse/Exemplos.md +44 -0
  28. package/seedgridKB/SeedGrid/Produtos/Pulse/Protocolo-e-API.md +191 -0
  29. package/seedgridKB/SeedGrid/Produtos/Pulse/SDKs.md +287 -0
  30. package/seedgridKB/SeedGrid/Produtos/Pulse/Servidor/Configuracao.md +128 -0
  31. package/seedgridKB/SeedGrid/Produtos/Pulse/Servidor/Instalacao.md +105 -0
  32. package/seedgridKB/SeedGrid/Produtos/Pulse/Servidor/Operacao.md +86 -0
  33. package/seedgridKB/SeedGrid/Produtos/Pulse/Servidor/Providers.md +121 -0
  34. package/seedgridKB/SeedGrid/Produtos/Pulse/UI/Configuracao.md +61 -0
  35. package/seedgridKB/SeedGrid/Produtos/Pulse/UI/Visao-geral.md +74 -0
  36. package/seedgridKB/SeedGrid/Produtos/Pulse/Visao-geral.md +96 -0
  37. package/seedgridKB/SeedGrid/Toolkit/agents/arquiteto.md +1 -1
  38. package/seedgridKB/SeedGrid/Toolkit/agents/backend-java.md +2 -2
  39. package/seedgridKB/SeedGrid/Toolkit/agents/backend-rust.md +1 -1
  40. package/seedgridKB/SeedGrid/Toolkit/agents/frontend-react.md +1 -1
  41. package/seedgridKB/SeedGrid/Toolkit/agents/qa.md +2 -2
  42. package/seedgridKB/SeedGrid/Toolkit/agents/revisor.md +2 -2
  43. package/seedgridKB/SeedGrid/Toolkit/references/fluxo-de-trabalho.md +3 -3
  44. package/seedgridKB/SeedGrid/Toolkit/rules/backend-java-seedgrid.md +27 -3
  45. package/seedgridKB/SeedGrid/Toolkit/rules/coordenacao.md +1 -1
  46. package/seedgridKB/SeedGrid/Toolkit/rules/documentos.md +26 -0
  47. package/seedgridKB/SeedGrid/Toolkit/rules/frontend-seedgrid.md +2 -2
  48. package/seedgridKB/SeedGrid/Toolkit/skills/{seedgrid-bump → sg-bump}/SKILL.md +1 -1
  49. package/seedgridKB/SeedGrid/Toolkit/skills/{revisar-codigo → sg-code-review}/SKILL.md +2 -2
  50. package/seedgridKB/SeedGrid/Toolkit/skills/{seedgrid-backend-crud → sg-crud-back}/SKILL.md +4 -4
  51. package/seedgridKB/SeedGrid/Toolkit/skills/{seedgrid-crud → sg-crud-front}/SKILL.md +5 -5
  52. package/seedgridKB/SeedGrid/Toolkit/skills/{seedgrid-crud → sg-crud-front}/agents/openai.yaml +1 -1
  53. package/seedgridKB/SeedGrid/Toolkit/skills/{seedgrid-crud/references/routing.md → sg-crud-front/references/crud-front-routing.md} +1 -1
  54. package/seedgridKB/SeedGrid/Toolkit/skills/{seedgrid-new-screen → sg-new-screen}/SKILL.md +3 -3
  55. package/seedgridKB/SeedGrid/Toolkit/skills/sg-new-screen/agents/openai.yaml +4 -0
  56. package/seedgridKB/SeedGrid/Toolkit/skills/sg-new-skill/SKILL.md +50 -0
  57. package/seedgridKB/SeedGrid/Toolkit/skills/{planejar-mudanca → sg-plan-change}/SKILL.md +1 -1
  58. package/seedgridKB/SeedGrid/Toolkit/skills/{seedgrid-review → sg-review}/SKILL.md +1 -1
  59. package/seedgridKB/SeedGrid/Toolkit/skills/{validar-mudanca → sg-validate-change}/SKILL.md +1 -1
  60. package/seedgridKB/SeedGrid/Visao-geral.md +7 -0
  61. package/seedgridKB/manifest.json +32 -23
  62. package/seedgridKB/SeedGrid/Toolkit/skills/seedgrid-new-screen/agents/openai.yaml +0 -4
  63. /package/seedgridKB/SeedGrid/Toolkit/skills/{revisar-codigo/references/checklist.md → sg-code-review/references/code-review-checklist.md} +0 -0
  64. /package/seedgridKB/SeedGrid/Toolkit/skills/{seedgrid-crud/references/checklist.md → sg-crud-front/references/crud-front-checklist.md} +0 -0
  65. /package/seedgridKB/SeedGrid/Toolkit/skills/{seedgrid-crud/references/patterns.md → sg-crud-front/references/crud-front-patterns.md} +0 -0
  66. /package/seedgridKB/SeedGrid/Toolkit/skills/{seedgrid-new-screen/references/checklist.md → sg-new-screen/references/new-screen-checklist.md} +0 -0
  67. /package/seedgridKB/SeedGrid/Toolkit/skills/{seedgrid-new-screen/references/steps.md → sg-new-screen/references/new-screen-steps.md} +0 -0
@@ -0,0 +1,287 @@
1
+ ---
2
+ tags: [seedgrid, produto, pulse, sdk, publisher, subscriber]
3
+ fonte: seedgrid-products/seedgrid-pulse/components
4
+ branch: main
5
+ commit: 9dcbdad
6
+ analisado-em: 2026-10-16
7
+ ---
8
+
9
+ # Pulse — SDKs
10
+
11
+ Dois lados independentes, cada um com sua credencial e seu transporte. Copie/instale **só o lado que precisa**. Voltar: [[SeedGrid/Produtos/Pulse/Visao-geral|Visão geral]] · exemplos prontos: [[SeedGrid/Produtos/Pulse/Exemplos|Exemplos]].
12
+
13
+ | | **Publisher** | **Subscriber** |
14
+ |---|---|---|
15
+ | Papel | publicar mensagens e emitir token curto | conectar, assinar, receber, `ack`/`result` |
16
+ | Roda em | backend confiável (API, worker, job) | navegador, app, agente |
17
+ | Credencial | chave **`project`** (`x-api-key`) 🔒 | token curto (`getToken`) **ou** chave `client` (agente) |
18
+ | Transporte | REST `POST /api/v1/messages` | WebSocket (reconecta sozinho) |
19
+ | Regra de ouro | a chave **nunca** vai ao cliente | **nenhuma** chave no navegador/app |
20
+
21
+ ## Publishers (`components/publishers/`)
22
+
23
+ | Linguagem | Pacote / coordenada | Versão (manifesto) | Entrada |
24
+ |---|---|---|---|
25
+ | Java 17+ | `br.com.seedgrid:seedgrid-pulse-publisher` (Maven Central) | `2026.10.0` | `PulsePublisher.builder().baseUrl().projectKey().build()` |
26
+ | Next.js (server-only) | `@seedgrid/pulse-publisher-next` | `2026.09.0` | `createPulsePublisher({ baseUrl, projectKey })` |
27
+ | Dart | `seedgrid_pulse_publisher` | `2026.9.1` | `PulsePublisher.create(baseUrl:, projectKey:)` |
28
+ | PHP | `seedgrid/pulse-publisher` (Composer) | — | `HttpPulsePublisher::create(baseUrl:, projectKey:)` |
29
+ | Rust | `seedgrid-pulse-publisher-rust` | `0.2.0` | `PulsePublisher::builder(url, key).build()?` |
30
+ | Delphi | fonte em `src/` (XE6+/FPC) | — | `NewPulsePublisher(url, key)` |
31
+
32
+ Todos oferecem: `publish(...)` com helpers de destino (`toUser`, `toGroup`, `toAccount`, e grupo+usuário), `deliveryMode`, `deliveryPolicy`, `metadata` e `issueConnectToken(...)`.
33
+
34
+ Java:
35
+
36
+ ```java
37
+ PulsePublisher publisher = PulsePublisher.builder()
38
+ .baseUrl(System.getenv("PULSE_BASE_URL"))
39
+ .projectKey(System.getenv("PULSE_PROJECT_KEY"))
40
+ .build();
41
+
42
+ publisher.publish(PublishMessageInput.toUser(
43
+ "orders", "order.updated", "usr_7f3a9c1e", Map.of("orderId", "o-1")));
44
+
45
+ // rota autenticada do SEU backend, para o navegador:
46
+ ConnectToken token = publisher.issueConnectToken(
47
+ new IssueConnectTokenInput(sessao.publicId(), Duration.ofMinutes(5), List.of("orders")));
48
+ ```
49
+
50
+ Next.js:
51
+
52
+ ```ts
53
+ import "server-only";
54
+ import { createPulsePublisher, message } from "@seedgrid/pulse-publisher-next";
55
+
56
+ export const pulse = createPulsePublisher({
57
+ baseUrl: process.env.PULSE_BASE_URL!,
58
+ projectKey: process.env.PULSE_PROJECT_KEY!, // nunca NEXT_PUBLIC_*
59
+ });
60
+
61
+ await pulse.publish(message.toUser("orders", "order.updated", usuario.publicId, { orderId }));
62
+ const token = await pulse.issueConnectToken({ subject: usuario.publicId, ttlSeconds: 300 });
63
+ ```
64
+
65
+ PHP:
66
+
67
+ ```php
68
+ $publisher = HttpPulsePublisher::create(baseUrl: getenv('PULSE_BASE_URL'), projectKey: getenv('PULSE_PROJECT_KEY'));
69
+ $publisher->publish(PublishMessageInput::toUser('orders', 'order.updated', 'usr_7f3a9c1e', ['orderId' => 'o-1']));
70
+ ```
71
+
72
+ Dart:
73
+
74
+ ```dart
75
+ final publisher = PulsePublisher.create(
76
+ baseUrl: Platform.environment['PULSE_BASE_URL']!,
77
+ projectKey: Platform.environment['PULSE_PROJECT_KEY']!,
78
+ );
79
+ final result = await publisher.publish(
80
+ PublishMessageInput.toUser('orders', 'order.updated', 'usr_7f3a9c1e', {'orderId': 'o-1'}),
81
+ );
82
+ print(result.messageId);
83
+ publisher.close();
84
+ ```
85
+
86
+ Rust:
87
+
88
+ ```rust
89
+ let publisher = PulsePublisher::builder(base_url, project_key).build()?;
90
+ let result = publisher
91
+ .publish(PublishMessageInput::to_user("orders", "order.updated", "usr_7f3a9c1e", json!({ "orderId": "o-1" })))
92
+ .await?;
93
+ println!("{}", result.message_id);
94
+ ```
95
+
96
+ Delphi:
97
+
98
+ ```pascal
99
+ Config := TPulsePublisherConfig.Create(GetEnvironmentVariable('PULSE_BASE_URL'), GetEnvironmentVariable('PULSE_PROJECT_KEY'));
100
+ Publisher := TPulsePublisher.Create(Config, TIndyPulseHttpTransport.Create); // Delphi moderno: transporte NetHttp
101
+ R := Publisher.Publish(TPublishMessageInput.ToUser('orders', 'order.updated', 'usr_7f3a9c1e', '{"orderId":"o-1"}'));
102
+ Writeln(R.MessageId);
103
+ ```
104
+
105
+ Variáveis recomendadas no backend: `PULSE_BASE_URL`, `PULSE_PROJECT_KEY` 🔒. Os exemplos completos de cada SDK ficam em `examples/` (ou `example/`) da pasta do pacote.
106
+
107
+ ### Publicar com modo e política de entrega
108
+
109
+ `deliveryMode` escolhe o ciclo de vida da mensagem e `deliveryPolicy` diz **como insistir** (tentativas, validade, espaçamento). Os dois são opcionais e independentes; o significado campo a campo está em [[SeedGrid/Produtos/Pulse/Protocolo-e-API#deliveryPolicy: campo a campo|Protocolo e API]]. Omitidos, vale `notification` e os padrões do servidor. Um job que o consumidor precisa **executar** usa `processed` (único modo com `result`, retry de execução e DLQ):
110
+
111
+ Java:
112
+
113
+ ```java
114
+ publisher.publish(PublishMessageInput.builder()
115
+ .channel("render").event("render.requested").toUser("agent:render-node-7")
116
+ .data(Map.of("jobId", "job_9f2"))
117
+ .deliveryMode(DeliveryMode.PROCESSED)
118
+ .deliveryPolicy(DeliveryPolicy.builder()
119
+ .maxAttempts(5).ttlSeconds(300).resultTimeoutSeconds(120)
120
+ .retryBaseMs(200).retryMaxMs(2000)
121
+ .retryStrategy(RetryStrategy.EXPONENTIAL)
122
+ .build())
123
+ .build());
124
+ ```
125
+
126
+ Next.js:
127
+
128
+ ```ts
129
+ await pulse.publish({
130
+ channel: "render",
131
+ event: "render.requested",
132
+ target: { user: "agent:render-node-7" },
133
+ data: { jobId: "job_9f2" },
134
+ deliveryMode: "processed",
135
+ deliveryPolicy: { maxAttempts: 5, ttlSeconds: 300, resultTimeoutSeconds: 120, retryBaseMs: 200, retryMaxMs: 2000, retryStrategy: "exponential" },
136
+ metadata: { correlationId: "report-4471", source: "report-api" },
137
+ });
138
+ ```
139
+
140
+ PHP:
141
+
142
+ ```php
143
+ $input = PublishMessageInput::toUser('render', 'render.requested', 'agent:render-node-7', ['jobId' => 'job_9f2'])
144
+ ->withDeliveryMode(DeliveryMode::Processed)
145
+ ->withDeliveryPolicy(new DeliveryPolicy(
146
+ maxAttempts: 5, ttlSeconds: 300, retryBaseMs: 200, retryMaxMs: 2000,
147
+ retryStrategy: RetryStrategy::Exponential,
148
+ resultTimeoutSeconds: 120, // último parâmetro do construtor
149
+ ))
150
+ ->withMetadata(new Metadata(correlationId: 'report-4471', source: 'report-api'));
151
+ $publisher->publish($input);
152
+ ```
153
+
154
+ Dart:
155
+
156
+ ```dart
157
+ await publisher.publish(
158
+ PublishMessageInput.toUser('render', 'render.requested', 'agent:render-node-7', {'jobId': 'job_9f2'})
159
+ .withDeliveryMode(DeliveryMode.processed)
160
+ .withDeliveryPolicy(const DeliveryPolicy(
161
+ maxAttempts: 5, ttlSeconds: 300, retryBaseMs: 200, retryMaxMs: 2000,
162
+ retryStrategy: RetryStrategy.exponential,
163
+ resultTimeoutSeconds: 120,
164
+ ))
165
+ .withCorrelationId('report-4471'),
166
+ );
167
+ ```
168
+
169
+ Rust:
170
+
171
+ ```rust
172
+ publisher.publish(
173
+ PublishMessageInput::to_user("render", "render.requested", "agent:render-node-7", json!({ "jobId": "job_9f2" }))
174
+ .with_delivery_mode(DeliveryMode::Processed)
175
+ .with_delivery_policy(DeliveryPolicy {
176
+ max_attempts: Some(5),
177
+ ttl_seconds: Some(300),
178
+ result_timeout_seconds: Some(120),
179
+ retry_base_ms: Some(200),
180
+ retry_max_ms: Some(2000),
181
+ retry_strategy: Some(RetryStrategy::Exponential),
182
+ }),
183
+ ).await?;
184
+ ```
185
+
186
+ Delphi (campo `0`/`rsUnset` = omitir):
187
+
188
+ ```pascal
189
+ Policy := TDeliveryPolicy.Empty;
190
+ Policy.MaxAttempts := 5;
191
+ Policy.TtlSeconds := 300;
192
+ Policy.ResultTimeoutSeconds := 120;
193
+ Policy.RetryBaseMs := 200;
194
+ Policy.RetryMaxMs := 2000;
195
+ Policy.RetryStrategy := rsExponential;
196
+ R := Publisher.Publish(TPublishMessageInput.ToUser('render', 'render.requested', 'agent:render-node-7', '{"jobId":"job_9f2"}')
197
+ .WithDeliveryMode(dmProcessed)
198
+ .WithDeliveryPolicy(Policy));
199
+ ```
200
+
201
+ Valores inválidos (`0`, `retryMaxMs < retryBaseMs`, estratégia desconhecida) voltam como **422** `INVALID_MESSAGE_REQUEST` e viram a exceção de validação do SDK (por exemplo `PulseValidationException`). Valores acima dos tetos do servidor **não** dão erro: são reduzidos ao teto.
202
+
203
+ `resultTimeoutSeconds` (SDKs a partir do Pulse 0.3.0) é o prazo, em segundos, para o consumidor devolver o `result` depois do `ack`; só vale com `processed`. Omitido, vale o padrão do servidor (`0` = sem prazo). Cuidado: um prazo curto reentrega a tarefa ainda em execução, então use um valor maior que o pior caso e deduplique por `messageId`. Os SDKs não validam o valor (o servidor responde 422 para `0`). Notas por linguagem: PHP recebe o parâmetro **no fim** do construtor de `DeliveryPolicy`; no Rust, literais de `DeliveryPolicy` precisam do campo (ou `..Default::default()`); no Delphi, `ResultTimeoutSeconds = 0` omite o campo; no Java o construtor antigo continua existindo. Semântica completa: [[SeedGrid/Produtos/Pulse/Protocolo-e-API#Prazo de execução (resultTimeoutSeconds)|Prazo de execução]].
204
+
205
+ ## Subscribers (`components/subscribers/`)
206
+
207
+ | Plataforma | Pacote | Versão | Camada de framework |
208
+ |---|---|---|---|
209
+ | React / JS | `@seedgrid/pulse-subscriber-react` (npm) | `3.0.0` | hook `usePulseSubscriber` + core `PulseSubscriberClient` |
210
+ | Angular 17+ | `@seedgrid/pulse-subscriber-angular` | `2.0.0` | `PulseSubscriberService` (Observables) + `providePulseSubscriber()` |
211
+ | Flutter / Dart | `seedgrid_pulse_subscriber` | `2.0.0` | streams `states`, `messages` |
212
+ | Rust | `seedgrid-pulse-subscriber-rust` | `0.2.0` | callbacks `on_message`, `on_state_change` |
213
+ | Delphi | fonte em `src/` (Indy) | — | `IPulseSubscriber` (`NewPulseSubscriberIndy`) |
214
+
215
+ Capacidades comuns: autenticação **só no handshake**; reconexão automática (backoff com jitter); **replay** do backlog ao reconectar (a assinatura é reenviada); `ack` (recebimento) e `reportResult` (execução); deduplicação por `messageId` opcional; `auth-error` ao receber `4403` (sem reconectar).
216
+
217
+ > **Paridade (Confirmado — comparação dos SDKs em 2026-10):** `unsubscribe`, listas `subscribedChannels/Groups` e `autoAck` existem em React, Flutter e Delphi. O **Angular ainda não os tem** (guarda só a última assinatura); o exemplo Angular contorna isso.
218
+
219
+ ### React
220
+
221
+ ```tsx
222
+ const { messages, connectionState, ack } = usePulseSubscriber({
223
+ getToken: async () => (await fetch("/api/realtime/connect", { method: "POST" })).json(),
224
+ subscription: { channels: ["orders"], groups: ["tenant:15"] },
225
+ });
226
+ ```
227
+
228
+ ### Angular
229
+
230
+ ```ts
231
+ providePulseSubscriber(() => {
232
+ const http = inject(HttpClient);
233
+ return {
234
+ getToken: () => firstValueFrom(http.post<PulseConnectToken>("/api/realtime/connect", {})),
235
+ subscription: { channels: ["orders"] },
236
+ };
237
+ });
238
+ // componente: inject(PulseSubscriberService).messages$ / state$ / ack({ messageId })
239
+ ```
240
+
241
+ ### Flutter
242
+
243
+ ```dart
244
+ final subscriber = PulseSubscriber();
245
+ subscriber.messages.listen((m) async { if (m.requiresAck) await subscriber.ack(AckInput(m.messageId)); });
246
+ await subscriber.connect(ConnectInput(getToken: () async => PulseConnectToken.fromJson(await meuBackend.post('/api/realtime/connect'))));
247
+ await subscriber.subscribe(const SubscribeInput(channels: ['orders']));
248
+ ```
249
+
250
+ ### Rust (agente, chave `client`)
251
+
252
+ ```rust
253
+ let subscriber = PulseSubscriber::new();
254
+ subscriber.on_message(|m| println!("{} em {}", m.event, m.channel));
255
+ subscriber.connect(ConnectInput::new("http://localhost:18080", "<chave client>")).await?;
256
+ subscriber.subscribe(SubscribeInput::channels(["orders"])).await?;
257
+ ```
258
+
259
+ ### Delphi
260
+
261
+ ```pascal
262
+ FSub := NewPulseSubscriberIndy;
263
+ FSub.UseAutoAck(True);
264
+ FSub.OnMessage(App.OnMsg);
265
+ FSub.Connect(TConnectInput.CreateWithToken(App.BuscarToken)); // ou TConnectInput.Create(url, chaveClient)
266
+ FSub.Subscribe(TSubscribeInput.ForChannels(['orders']));
267
+ ```
268
+
269
+ ## Fluxo de ponta a ponta (navegador)
270
+
271
+ ```text
272
+ navegador ──(sessão)──▶ POST /api/realtime/connect [rota do SEU backend]
273
+ │ issueConnectToken(subject = usuário da sessão) [chave project]
274
+ ▼
275
+ navegador ◀── { token, expiresAt, connectUrl }
276
+ navegador ──WebSocket (access_token=pct_…)──▶ Pulse ──▶ message / ack / result
277
+ ```
278
+
279
+ ## Ack × result
280
+
281
+ - `ack` confirma o **recebimento**; `result` informa o **resultado de execução** (`success`/`retryable`/`permanent`).
282
+ - Só mensagens `processed` esperam `result`; em `notification`/`acknowledged` o servidor o recusa com `result_not_applicable`. Com `autoAck`/auto-ack nos SDKs que o têm, o SDK envia `ack` e, para `processed`, `result:success` depois que o seu handler retorna sem erro.
283
+ - Entrega é at-least-once: **deduplique por `messageId`**.
284
+
285
+ ## Instalação a partir do fonte
286
+
287
+ Nem todos os SDKs estão em registro público (Confirmado nos commits: Java em Maven Central, React no npm). Para os demais use dependência por caminho/cópia do fonte, como fazem os [[SeedGrid/Produtos/Pulse/Exemplos|exemplos]] (ex.: Flutter com `path: sdk/subscriber`). Cada SDK traz `INSTALLATION.md`, `CONFIGURATION.md`, `SECURITY.md`, `TROUBLESHOOTING.md` e `TESTING.md`.
@@ -0,0 +1,128 @@
1
+ ---
2
+ tags: [seedgrid, produto, pulse, servidor, configuracao, variaveis]
3
+ fonte: seedgrid-products/seedgrid-pulse/backend (src/config.rs, env.md)
4
+ branch: main
5
+ commit: 9dcbdad
6
+ analisado-em: 2026-10-16
7
+ ---
8
+
9
+ # Pulse (servidor) — variáveis de ambiente
10
+
11
+ Todas as variáveis do **servidor**. As da UI estão em [[SeedGrid/Produtos/Pulse/UI/Configuracao|Configuração da UI]]. Arquitetura atual: **login-tenancy, banco único** — a separação entre clientes é lógica, por `company_id`. Sem `POSTGRES_URL` o servidor sobe em modo memória/dev com a plataforma desabilitada.
12
+
13
+ Convenção: 🔒 = segredo (nunca em repositório/log); valores de exemplo são placeholders, **não** padrões.
14
+
15
+ ## Runtime (HTTP, WebSocket, CORS)
16
+
17
+ | Variável | Padrão | Descrição |
18
+ |---|---|---|
19
+ | `HTTP_HOST` | `127.0.0.1` | IP de bind. Em container use `0.0.0.0`. |
20
+ | `HTTP_PORT` | `8080` | Porta HTTP. |
21
+ | `WS_PATH` | `/ws` | Path do WebSocket legado (o endpoint do protocolo é `/api/v1/connect`). |
22
+ | `CORS_ALLOWED_ORIGINS` | vazio | Lista separada por vírgula. Vazio = nenhuma origem liberada. |
23
+ | `WS_HEARTBEAT_INTERVAL_SECONDS` | `30` | Heartbeat do WebSocket. |
24
+ | `WS_IDLE_TIMEOUT_SECONDS` | `90` | Fecha conexão ociosa. |
25
+
26
+ ## Credenciais do Pulse 🔒
27
+
28
+ | Variável | Padrão | Descrição |
29
+ |---|---|---|
30
+ | `SEEDGRID_PULSE_SERVER_KEY` | — | Chave **administrativa** (contas/chaves, métricas) em `x-api-key`. **Não publica** (403). Só em ENV; fica só com a operação do Pulse. |
31
+ | `SEEDGRID_PULSE_CONNECT_TOKEN_SECRET` | — | Segredo HMAC-SHA256 que assina o token curto de conexão. Sem ele, `POST /api/v1/connect-tokens` responde 503. |
32
+ | `SEEDGRID_PULSE_CONNECT_TOKEN_PREVIOUS_SECRET` | — | Segredo anterior, aceito só na verificação (rotação sem derrubar tokens válidos). |
33
+ | `SEEDGRID_PULSE_CONNECT_TOKEN_TTL_SECS` | `300` | Validade do token quando o pedido não informa `ttlSeconds`. É a janela para **conectar**. |
34
+ | `SEEDGRID_PULSE_CONNECT_TOKEN_MAX_TTL_SECS` | `900` | Teto: pedido acima → 422. |
35
+ | `SEEDGRID_PULSE_CONNECT_TOKEN_RATE_PER_KEY` | `600` | Emissões/min por chave `project`; `0` desliga. Por instância. |
36
+ | `SEEDGRID_PULSE_CONNECT_TOKEN_RATE_PER_SUBJECT` | `30` | Emissões/min por `subject` na mesma chave; `0` desliga. Por instância. |
37
+ | `SEEDGRID_PULSE_PUBLIC_WS_URL` | — | Endereço **público** do WebSocket devolvido em `connectUrl`. Ausente = campo omitido. |
38
+
39
+ Quem usa cada credencial: [[SeedGrid/Produtos/Pulse/Autenticacao-e-chaves|Autenticação e chaves]].
40
+
41
+ ## Persistência e mensageria
42
+
43
+ | Variável | Padrão | Descrição |
44
+ |---|---|---|
45
+ | `POSTGRES_URL` | — | **Interruptor da plataforma.** Sem ela, security/login-tenancy/audit/apikeys ficam desligados. 🔒 |
46
+ | `POSTGRES_RUN_MIGRATIONS` | `true` | Roda migrations na subida. |
47
+ | `DELIVERY_STORE_BACKEND` | `memory` | `memory` \| `postgres`. Em produção, `postgres`. |
48
+ | `BROKER_PROVIDER` | `memory` | `memory` \| `redis` \| `nats` \| `rabbitmq` (fallback `EVENT_BUS_BACKEND`). Valor desconhecido cai em `memory`. Detalhes e variáveis por provider: [[SeedGrid/Produtos/Pulse/Servidor/Providers|Providers]]. |
49
+ | `PRESENCE_BACKEND` | `memory` | `memory` \| `redis`. Multi-réplica ⇒ `redis`. |
50
+ | `BROKER_REDIS_URL` | — | URL Redis-compatível (fallback `REDIS_URL`). 🔒 |
51
+ | `BROKER_REDIS_CHANNEL` | `seedgrid:pulse:events` | Canal pub/sub (fallback `REDIS_PUBSUB_CHANNEL`). |
52
+ | `BROKER_NATS_URL` | — | URL do NATS (`nats://host:4222`; fallback `NATS_URL`). Obrigatória com `nats`. 🔒 |
53
+ | `BROKER_NATS_SUBJECT` | `seedgrid.pulse.events` | Subject do pub/sub. |
54
+ | `BROKER_RABBITMQ_URL` | — | URL AMQP (fallbacks `RABBITMQ_URL`, `AMQP_URL`). Obrigatória com `rabbitmq`. 🔒 |
55
+ | `BROKER_RABBITMQ_EXCHANGE` | `seedgrid.pulse.events` | Exchange fanout. |
56
+
57
+ As URLs Redis-compatíveis servem para Valkey e Dragonfly sem mudar mais nada; ver [[SeedGrid/Produtos/Pulse/Servidor/Providers|Providers]].
58
+
59
+ ## Entrega (retry, TTL, limpeza)
60
+
61
+ Os pares `*_CAP` são o **teto** que uma configuração por mensagem pode pedir; omitidos, assumem o valor base. Todos (exceto o intervalo de limpeza) são editáveis em runtime em **Configurações**.
62
+
63
+ | Variável | Padrão | Descrição |
64
+ |---|---|---|
65
+ | `DELIVERY_RETRY_STRATEGY` | `exponential` | `exponential` (qualquer outro valor = `fixed`: atraso constante de `DELIVERY_RETRY_BASE_MS`). |
66
+ | `DELIVERY_RETRY_BASE_MS` | `300` | Backoff base. |
67
+ | `DELIVERY_RETRY_MAX_MS` | `5000` | Backoff máximo. |
68
+ | `DELIVERY_MAX_ATTEMPTS` | `20` | Tentativas de execução por mensagem (modo `processed`). |
69
+ | `DELIVERY_TTL_SECONDS` | `86400` | TTL da mensagem (1 dia). |
70
+ | `DELIVERY_RESULT_TIMEOUT_SECONDS` | `0` (sem prazo) | **Prazo de execução** padrão (modo `processed`): segundos entre o `ack` e o `result`. `0` desliga. Estourado, vale como `retryable` com motivo `result_timeout`. Editável em Configurações, sem reinício. |
71
+ | `DELIVERY_RESULT_TIMEOUT_SECONDS_CAP` | `86400` (ou o padrão, se maior) | Teto do prazo que o publisher pode pedir; `0` **proíbe** o prazo de execução. |
72
+ | `DELIVERY_RETRY_BASE_MS_CAP` / `DELIVERY_RETRY_MAX_MS_CAP` / `DELIVERY_MAX_ATTEMPTS_CAP` / `DELIVERY_TTL_SECONDS_CAP` | = valor base | Tetos para o override por mensagem. |
73
+ | `DELIVERY_CLEANUP_INTERVAL_SECONDS` | `60` | Intervalo da varredura de retenção (`DELIVERY_CLEANUP_RETENTION_DAYS`) e da limpeza automática da lixeira. **Não** controla a expiração por TTL, que tem verificador próprio (a cada `ceil(DELIVERY_RETRY_BASE_MS/1000)` s, mínimo 1 s). Exige reinício. |
74
+ | `DELIVERY_CLEANUP_RETENTION_DAYS` | `0` (nunca apaga) | **Dias** que uma mensagem em estado final (`completed`/`dead_letter`/`expired`) é mantida. `0` desliga a varredura: o histórico é prova. Obsoleta: `DELIVERY_CLEANUP_RETENTION_SECONDS` (lida só se a de dias não existir). |
75
+ | `DELIVERY_TRASH_AUTO_CLEAN_DAYS` | `0` (só manual) | Dias na lixeira antes da limpeza automática. Cada limpeza vai ao audit como `system:pulse`. |
76
+
77
+ Detalhes e as regras de lixeira: [[SeedGrid/Produtos/Pulse/Servidor/Operacao|Operação]].
78
+
79
+ ## Security (login, JWT, RBAC)
80
+
81
+ Habilitado com `POSTGRES_URL` **e** as chaves RSA. Nomes alinhados à extension Java `security` ([[SeedGrid/Extensions/security/Configuracao|Configuração do security]]).
82
+
83
+ | Variável | Descrição |
84
+ |---|---|
85
+ | `SECURITY_PRIVATE_KEY_LOCATION` / `SECURITY_PUBLIC_KEY_LOCATION` | Caminhos do par RSA (primários). 🔒 |
86
+ | `SECURITY_JWT_PRIVATE_KEY_PEM` / `SECURITY_JWT_PUBLIC_KEY_PEM` | PEM inline (secundário). 🔒 |
87
+ | `SECURITY_JWT_ISSUER` / `SECURITY_JWT_KID` | Issuer e `kid` do JWT. |
88
+ | `SECURITY_JWT_TTL` | Validade do access token, ISO-8601 (`PT8H`). |
89
+ | `SECURITY_REFRESH_TOKEN_TTL` | Validade do refresh token (`P30D`). |
90
+ | `SECURITY_PASSWORD_MIN_SIZE` / `_EXPIRATION_DAYS` / `_MAX_HISTORY` | Política de senha. |
91
+ | `SECURITY_ENCRYPTION_KEY` | AES-256 (base64) da cripto de campo; obrigatória se usar TOTP. 🔒 |
92
+ | `SECURITY_FRONTEND_BASE_URL` | Base da UI nos links de e-mail. |
93
+ | `SEEDGRID_SECURITY_FIRST_ADMIN_USER_EMAIL` / `_NAME` / `_INITIAL_PASSWORD` | Bootstrap do primeiro admin. 🔒 |
94
+ | `SEEDGRID_SECURITY_DEFAULT_COMPANY_CNPJ` / `_CORPORATE_NAME` / `_TRADE_NAME` | Company da plataforma (razão social e fantasia são obrigatórias no bootstrap). |
95
+
96
+ ### 2FA
97
+
98
+ Desligado por padrão. `SEEDGRID_SECURITY_2F_ENABLED=true` liga; `_ALLOWED_METHODS` (`EMAIL,TOTP`), `_DEFAULT_METHOD` (`TOTP`), `_EMAIL_CODE_TTL`, `_SESSION_TTL`, `_VERIFY_MAX_ATTEMPTS` (5), `_TOTP_ISSUER` (`SeedGrid`; vale trocar para `SeedGrid Pulse`), `_TOTP_DIGITS` (6), `_TOTP_PERIOD` (30), `_TOTP_WINDOW` (1), `_BACKUP_CODES_COUNT` (10). WhatsApp: `SEEDGRID_SECURITY_2F_WHATSAPP_CHANNEL_ID` mais um provider em `SEEDGRID_WHATSAPP_API` (`evolution`/`cloud`) com credenciais ([[SeedGrid/Extensions/whatsapp/Configuracao|whatsapp]]).
99
+
100
+ ### E-mail (SMTP)
101
+
102
+ `SECURITY_MAILER_USERNAME` e `_PASSWORD` ligam o SMTP real (🔒); `_HOST` (`smtp.gmail.com`), `_PORT` (587), `_START_TLS` (`REQUIRED`), `_FROM`. **Sem usuário e senha o e-mail só vai para o log** — confirmação, reset de senha e 2FA por e-mail não chegam.
103
+
104
+ ## Login-tenancy
105
+
106
+ `SEEDGRID_LOGIN_TENANCY_APP_CODE` (`seedgrid-app`; use `seedgrid-pulse`), `_APP_NAME`, `_DATABASE_MIGRATE_AT_START` (`true`), `_DATABASE_BASELINE_ON_MIGRATE` (`true`), `_FOUNDATION_STATUS_ENABLED`, `_FOUNDATION_HEALTH_ENABLED`, `_BOOTSTRAP_ENABLED`, `_BOOTSTRAP_ADMIN_ENABLED`. Não existe flag "enabled": a dependência ativa o baseline. `SEEDGRID_SHOW_DEV_MESSAGE` (`false`) inclui detalhe interno nos erros — **nunca em produção**. Ver [[SeedGrid/Extensions/login-tenancy/Configuracao|login-tenancy]].
107
+
108
+ ## Audit
109
+
110
+ `SEEDGRID_AUDIT_ENABLED` (`true`), `AUDIT_STRATEGY` (`JPA` = Postgres \| `MONGODB`), `SEEDGRID_AUDIT_DB_*` só para banco de auditoria separado, `SEEDGRID_AUDIT_MONGODB_URI`/`_DATABASE`. No deploy padrão as migrations de audit rodam no mesmo banco. A leitura é escopada por `company_id` via headers internos sobrescritos pelo JWT — não os repasse de proxy. Ver [[SeedGrid/Extensions/audit/Configuracao|audit]].
111
+
112
+ ## API keys
113
+
114
+ `SEEDGRID_APIKEYS_PREFIX_LENGTH` (`8`), `SEEDGRID_APIKEYS_KEY_BYTES` (`32`). Ver [[SeedGrid/Extensions/apikeys/Configuracao|apikeys]].
115
+
116
+ ## Observabilidade
117
+
118
+ `RUST_LOG` (ex.: `info`). Métricas: `GET /metrics` (JSON) e `GET /metrics/prometheus`; Prometheus e Grafana vêm no compose.
119
+
120
+ ## Variáveis removidas (não configure)
121
+
122
+ As de multitenancy por subdomínio (`DOMAIN_SUFFIX`, `SEEDGRID_MULTITENANCY_*`, `PRODUCT_PREFIX`, `MULTITENANCY_MAILER_*`) foram eliminadas e são ignoradas. Backup (`BACKUP_*`) e billing (`SEEDGRID_BILLING_*`) não se aplicam ao Pulse hoje.
123
+
124
+ ## Pegadinhas
125
+
126
+ - A chave de servidor nunca vai para produto nenhum nem para o serviço da UI.
127
+ - Variável definida no EasyPanel **vence** o padrão de código: ao mudar um padrão, confira o ambiente.
128
+ - `0` em retenção/limpeza significa **desligado** (não "apagar tudo").
@@ -0,0 +1,105 @@
1
+ ---
2
+ tags: [seedgrid, produto, pulse, servidor, instalacao, deploy]
3
+ fonte: seedgrid-products/seedgrid-pulse/backend
4
+ branch: main
5
+ commit: 9dcbdad
6
+ analisado-em: 2026-10-16
7
+ ---
8
+
9
+ # Pulse (servidor) — instalação e execução
10
+
11
+ O **servidor Pulse** é o backend Rust (Axum): API REST, WebSocket, entrega, retry, DLQ, presença e módulos de plataforma. A UI é outro componente: [[SeedGrid/Produtos/Pulse/UI/Visao-geral|Pulse UI]].
12
+
13
+ Voltar: [[SeedGrid/Produtos/Pulse/Visao-geral|Visão geral]] · variáveis: [[SeedGrid/Produtos/Pulse/Servidor/Configuracao|Configuração do servidor]] · brokers: [[SeedGrid/Produtos/Pulse/Servidor/Providers|Providers (Redis, Valkey, Dragonfly, NATS, RabbitMQ)]]
14
+
15
+ ## Modos de execução
16
+
17
+ | Modo | Quando | Persistência |
18
+ |---|---|---|
19
+ | **Memória** (sem `POSTGRES_URL`) | desenvolvimento rápido, testes | nenhuma; plataforma (login, audit, apikeys) **desabilitada** — só o núcleo realtime |
20
+ | **Postgres** | produção de instância única | mensagens, contas, usuários, audit |
21
+ | **Distribuído** | várias réplicas | Postgres + broker pub/sub + presença em Redis |
22
+
23
+ Exemplos de `env` (Confirmado — `backend/README.md`):
24
+
25
+ ```env
26
+ # simples
27
+ BROKER_PROVIDER=memory
28
+ PRESENCE_BACKEND=memory
29
+ DELIVERY_STORE_BACKEND=memory
30
+
31
+ # com Postgres
32
+ DELIVERY_STORE_BACKEND=postgres
33
+ POSTGRES_URL=postgres://<user>:<senha>@<host>:5432/<db>
34
+
35
+ # distribuído
36
+ BROKER_PROVIDER=redis
37
+ PRESENCE_BACKEND=redis
38
+ DELIVERY_STORE_BACKEND=postgres
39
+ BROKER_REDIS_URL=redis://<host>:6379/0
40
+ ```
41
+
42
+ ## Subir com Docker/Podman Compose
43
+
44
+ Em `backend/`:
45
+
46
+ ```bash
47
+ docker compose up -d --build
48
+ ```
49
+
50
+ Sobe `postgres`, `redis`, `seedgrid-pulse`, `prometheus`, `grafana` e `seedgrid-pulse-ui`. Portas locais (Confirmado — `docker-compose.yml`): API `18080`, Prometheus `19090`, Grafana `13001`, UI `13000`.
51
+
52
+ O compose de desenvolvimento traz valores de exemplo (chave de servidor, segredo do token, admin inicial). **Troque todos** em qualquer ambiente real. 🔒
53
+
54
+ ## Rodar a partir do fonte
55
+
56
+ Pré-requisitos: Rust estável recente (edition 2024) e o repositório de crates da plataforma clonado ao lado (path dependencies).
57
+
58
+ ```text
59
+ <raiz>/
60
+ ├─ seedgrid-platform/rust/ # crates seedgrid-rust-crate-*
61
+ └─ seedgrid-products/seedgrid-pulse/ # este repo
62
+ ```
63
+
64
+ O prefixo `../../../seedgrid-platform/rust/` está em `backend/Cargo.toml`; ajuste-o se o layout for outro.
65
+
66
+ ```bash
67
+ cd backend
68
+ docker compose up -d postgres redis
69
+ cargo run
70
+ ```
71
+
72
+ O build não precisa de banco (não há `sqlx::query!` verificada em compilação).
73
+
74
+ ## Primeiro acesso
75
+
76
+ Com `POSTGRES_URL` e o par RSA do JWT, o boot roda as migrations e cria a company da plataforma e o **primeiro admin** a partir de `SEEDGRID_SECURITY_FIRST_ADMIN_USER_*` e `SEEDGRID_SECURITY_DEFAULT_COMPANY_*` ([[SeedGrid/Produtos/Pulse/Servidor/Configuracao|Configuração]]). Entre na UI com esse admin, crie a **conta** do seu produto e as **chaves** (Contas → conta → Chaves → Nova chave). O segredo da chave aparece **uma única vez**.
77
+
78
+ Par RSA do JWT (exemplo):
79
+
80
+ ```bash
81
+ openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:2048 -out private.pem
82
+ openssl rsa -pubout -in private.pem -out public.pem
83
+ ```
84
+
85
+ Aponte `SECURITY_PRIVATE_KEY_LOCATION` / `SECURITY_PUBLIC_KEY_LOCATION` (ou use PEM inline).
86
+
87
+ ## Imagem e deploy
88
+
89
+ - Imagem no GHCR: `ghcr.io/seedgrid/seedgrid-pulse-backend` (Confirmado — `.github/workflows/deploy.yml`).
90
+ - `push` em `main` dispara o workflow, que detecta o que mudou, publica a imagem e chama o webhook do EasyPanel (secret `EASYPANEL_DEPLOY_URL_PULSE_BACKEND`). Commit só de documentação **não** redeploya.
91
+ - O container escuta `8080`; use `HTTP_HOST=0.0.0.0` em container.
92
+ - Probes: liveness `GET /health`, readiness `GET /ready` (200/503 com o estado de store, broker e presença).
93
+ - Migrations rodam no boot (`POSTGRES_RUN_MIGRATIONS`, padrão `true`).
94
+
95
+ ## Checklist de produção
96
+
97
+ - [ ] `POSTGRES_URL` + `DELIVERY_STORE_BACKEND=postgres`.
98
+ - [ ] Mais de uma réplica ⇒ broker pub/sub (`redis`, ou `nats`/`rabbitmq` — ver [[SeedGrid/Produtos/Pulse/Servidor/Providers|Providers]]) **e** `PRESENCE_BACKEND=redis`.
99
+ - [ ] `SEEDGRID_PULSE_SERVER_KEY` e `SEEDGRID_PULSE_CONNECT_TOKEN_SECRET` longos e aleatórios, só em ENV. 🔒
100
+ - [ ] `SEEDGRID_PULSE_PUBLIC_WS_URL` com o endereço público do WebSocket (`wss://…`), se há clientes de navegador.
101
+ - [ ] `CORS_ALLOWED_ORIGINS` com a origem da UI.
102
+ - [ ] Par RSA do JWT próprio; admin inicial trocado.
103
+ - [ ] `SEEDGRID_SHOW_DEV_MESSAGE` desligado (padrão).
104
+ - [ ] Proxy reverso **não** repassa `x-seedgrid-company-id` / `x-seedgrid-all-companies` vindos do cliente.
105
+ - [ ] Retenção do histórico decidida (padrão: nunca apagar) — [[SeedGrid/Produtos/Pulse/Servidor/Operacao|Operação]].
@@ -0,0 +1,86 @@
1
+ ---
2
+ tags: [seedgrid, produto, pulse, servidor, operacao, lixeira, retencao]
3
+ fonte: seedgrid-products/seedgrid-pulse/backend (docs/LIXEIRA-mensagens-e-contas.md, README.md)
4
+ branch: main
5
+ commit: 9dcbdad
6
+ analisado-em: 2026-10-16
7
+ ---
8
+
9
+ # Pulse (servidor) — operação
10
+
11
+ Lixeira, retenção, auditoria, observabilidade e escala. Voltar: [[SeedGrid/Produtos/Pulse/Visao-geral|Visão geral]] · variáveis: [[SeedGrid/Produtos/Pulse/Servidor/Configuracao|Configuração]].
12
+
13
+ ## Princípios
14
+
15
+ - O **histórico de entregas é prova**: por padrão **nunca é apagado sozinho**.
16
+ - Apagar é **sempre ação humana**, com permission própria, confirmação na UI e **audit** (quem, o quê, quantos).
17
+ - A chave de servidor **não apaga nada** (401): toda exclusão tem um autor.
18
+
19
+ ## Lixeira de mensagens
20
+
21
+ - **Só mensagens `pending`** podem ir para a lixeira (mensagens entregues/processadas são histórico; só saem com a exclusão da conta).
22
+ - Na UI: **Mensagens**, filtro de status **"Na lixeira"**. Mover (`PULSE_MESSAGE_DELETE`), restaurar (volta a `pending`) e limpar de vez (`PULSE_MESSAGE_PURGE`, apaga também as entregas por destinatário).
23
+ - API: `POST /api/v1/messages/trash` · `/trash/restore` · `/trash/purge`, lote de até **1000** ids (`400 BATCH_TOO_LARGE`), resposta `{requested, affected, skipped}`.
24
+ - Mensagem na lixeira não é entregue, não expira por TTL e some das listas padrão.
25
+ - **Descartar** (tela Pendências, `PULSE_BACKLOG_DISCARD`) e **Lixeira** coexistem como ações distintas; só a lixeira tem restaurar e limpar de vez.
26
+ - Tarefas `delivered` sem `result` e sem prazo de execução (as anteriores ao Pulse 0.3.0, ou quando `resultTimeoutSeconds` não foi usado) só saem por **descarte**; veja [[SeedGrid/Produtos/Pulse/Protocolo-e-API#Prazo de execução (resultTimeoutSeconds)|Prazo de execução]].
27
+
28
+ ## Lixeira de contas
29
+
30
+ - A lixeirinha da lista de contas pede confirmação e leva a conta para `pending_delete` (`PULSE_ACCOUNT_DELETE`). A conta **deixa de autenticar** e suas conexões são derrubadas.
31
+ - **Lixeira de contas** (menu): restaurar (volta ao status anterior) ou **apagar definitivamente** (`PULSE_ACCOUNT_PURGE`), que remove a conta, suas chaves e **todas as suas mensagens** em cascata. O diálogo mostra antes quantas chaves e mensagens saem (`GET /accounts/{id}/deletion-summary`).
32
+ - O apagar definitivo **não é uma transação única**: mensagens primeiro, conta por último. Se falhar no meio, a conta continua na lixeira e dá para repetir.
33
+
34
+ ## Retenção e limpeza automática
35
+
36
+ | Variável | Padrão | Efeito |
37
+ |---|---|---|
38
+ | `DELIVERY_CLEANUP_RETENTION_DAYS` | `0` | Dias que uma mensagem **final** (`completed`/`dead_letter`/`expired`) é mantida. `0` = **nunca apaga**. |
39
+ | `DELIVERY_TRASH_AUTO_CLEAN_DAYS` | `0` | Dias na lixeira antes de apagar sozinho. `0` = só manual. Cada limpeza vai ao audit como `system:pulse`. |
40
+
41
+ As duas **não se sobrepõem**: a primeira atua no histórico (estados finais), a segunda na lixeira (`trashed`). Mensagens `pending` e `delivered` nunca são limpas pela retenção. A obsoleta `DELIVERY_CLEANUP_RETENTION_SECONDS` só vale se a de dias não existir. Ambas são editáveis em **Configurações**, sem redeploy.
42
+
43
+ > **Mudança de comportamento (2026-10-15):** o padrão era 7 dias e `0` apagava tudo. Ambientes que definem a variável explicitamente (EasyPanel) mantêm o valor definido.
44
+
45
+ Sem consumidor, uma `pending` não fica para sempre: o TTL (`DELIVERY_TTL_SECONDS`, 1 dia) a expira.
46
+
47
+ ## Ordem das listas e paginação
48
+
49
+ As telas listam **da mais recente para a mais antiga** (quem acabou de publicar vê a sua primeira): contas por `created_at`, mensagens por `published_at` e entregas por destinatário por `delivered_at`, todas **com desempate por id**. A paginação é 1-based (`page`, `pageSize`; a Auditoria usa `page`/`size` e o backend dela é base 0, convertido pela UI). Até o Pulse 0.2.1 a lista de contas ordenava sem desempate, e contas criadas no mesmo segundo repetiam ou sumiam entre páginas (corrigido no 0.2.2). O **replay** e as pendências devidas continuam em ordem cronológica (do mais antigo ao mais novo), porque entregar na ordem de publicação é o comportamento certo (Confirmado em `delivery/repo.rs`).
50
+
51
+ ## Auditoria
52
+
53
+ Toda operação destrutiva (mover para lixeira, restaurar, limpar; contas e mensagens) grava em `audit_log` o autor (claim `sub` do JWT, e-mail na descrição), a ação e as contagens, **somente se algo mudou**. Visível em **Segurança → Auditoria** (escopo por `company_id`).
54
+
55
+ ## Observabilidade
56
+
57
+ | O quê | Onde |
58
+ |---|---|
59
+ | Liveness / readiness | `GET /health` · `GET /ready` (200 `READY` / 503 `NOT_READY`, com store, broker e presença) |
60
+ | Métricas | `GET /metrics` (JSON) · `GET /metrics/prometheus` |
61
+ | Dashboards | Prometheus + Grafana no compose (`SeedGrid Pulse Overview`) |
62
+ | Telas | dashboard, conexões, backlog, DLQ, mensagens, entregas por destinatário |
63
+
64
+ Métricas úteis: `publish_requests_total`, `delivery_attempts_total`, `delivery_success_total`, `delivery_dead_letter_total`, `delivery_expired_total`, `delivery_result_timeout_total` (prazo de execução estourado), `ack_total`, `ws_connections_opened_total`/`_closed_total`, latências `publish_to_deliver_*` e `deliver_to_ack_*`.
65
+
66
+ ## Escala (várias réplicas)
67
+
68
+ - Estado durável no **Postgres** (`DELIVERY_STORE_BACKEND=postgres`).
69
+ - Entrega entre instâncias via **broker pub/sub** (`BROKER_PROVIDER`) e presença em Redis (`PRESENCE_BACKEND=redis`): ver [[SeedGrid/Produtos/Pulse/Servidor/Providers|Providers]].
70
+ - Revogação de credencial atravessa o broker (tópico `pulse.revocations`) e derruba as conexões em **todas** as instâncias.
71
+ - Limites de emissão de token são **por instância**: com N réplicas o efetivo é até N vezes.
72
+
73
+ ## Diagnóstico rápido
74
+
75
+ | Sintoma | Verifique |
76
+ |---|---|
77
+ | Publicou e ninguém recebeu | `accountId`/conta ativa; `channel` e `event` batem com o `subscribe`; `user` do destino = `subject` da conexão; entrega fica `pending` até alguém conectar |
78
+ | 403 ao publicar | está usando chave `client` ou a chave de servidor; use a `project` |
79
+ | WebSocket 401 | credencial ausente/revogada; token `pct_` vencido antes de conectar |
80
+ | Conexão fecha com `4403` | chave/conta revogada: não reconecte com a mesma credencial |
81
+ | `connect-tokens` 503 | falta `SEEDGRID_PULSE_CONNECT_TOKEN_SECRET` |
82
+ | E-mail/2FA por e-mail não chega | `SECURITY_MAILER_USERNAME`/`_PASSWORD` ausentes (só loga) |
83
+ | Histórico "sumiu" | retenção definida no ambiente (`DELIVERY_CLEANUP_RETENTION_*`) |
84
+ | Réplica B não recebe o que A publicou | broker `memory` em produção multi-réplica |
85
+
86
+ Testes e CI: `cargo test` (unitários e e2e); com Docker/Podman, `cargo test redis_ -- --ignored`, `postgres_ -- --ignored` e `--test trash_integration -- --ignored --test-threads=1` sobem Postgres/Redis via testcontainers.