Pular para o conteúdo

ADR-003 — Stack Pivot: NATS JetStream + SurrealDB + DBOS

ADR-003 — Stack Pivot: NATS JetStream + SurrealDB + DBOS

Seção intitulada “ADR-003 — Stack Pivot: NATS JetStream + SurrealDB + DBOS”
CampoValor
StatusProposed (2026-06-09)
Data2026-06-09
Versaov0.1
Substitui
RelacionadoADR-001 — Domain Kernel + Emmett, ADR-002 — MCP-First Surface

O que esta errado com o stack atual (PG + BullMQ + Drizzle)

Seção intitulada “O que esta errado com o stack atual (PG + BullMQ + Drizzle)”

canna-br v0.2.x roda Emmett sobre PostgreSQL como event store, BullMQ sobre Redis para jobs assincronos, e Drizzle ORM para read models em PG. Esta combinacao funciona, mas apresenta tres tensoes crescentes:

1. Event store PG nao e event bus

O event store PG do Emmett e transacional e correto para append de streams, mas nao serve como barramento de eventos para fan-out para multiplos consumidores (projecoes, SNGPC adapter, audit log, notificacoes). Hoje isso e simulado via polling ou BullMQ, gerando acoplamento e latencia.

2. BullMQ nao oferece durabilidade de saga

SNGPC requer envio de XML assinado com confirmacao. Se o worker Redis cair apos envio mas antes de confirmacao, o estado do saga se perde. BullMQ tem retry, mas nao tem durabilidade de estado de workflow multi-step com compensacao. Bugs nesse ponto sao risco regulatorio direto.

3. Drizzle read models sao SQL flat — sem graph

A chain of custody canna-br tem natureza de grafo: associacao → membro → prescricao → lote → dispensacao → SNGPC. Queries de auditoria regulatoria cruzam 4-5 joins. Drizzle resolve, mas com impedance mismatch crescente. Graph-native seria mais expressivo para relatorios regulatorios e para o MCP tool trace_dispensation_chain.

  • NATS JetStream: event bus persistente com replay, consumer groups, e subject-based routing. Emmett pode publicar eventos para subjects NATS no mesmo append transacional (via outbox pattern ou adapter nativo). Multiplos consumers (projecoes, SNGPC, audit) subscrevem independentemente com acknowledgement.
  • SurrealDB: banco multi-model (document + graph + relacional) com LIVE queries (websocket push quando dado muda). Read models de graph (trace chain) ficam nativos. MCP tools de consulta eliminam SQL multi-join.
  • DBOS: runtime de workflows duravais sobre Postgres proprio. Substitui BullMQ para sagas SNGPC e jobs regulatorios. Estado de workflow persistido — crash-safe por design.

canna-br como plataforma agentica (ADR-002 MCP-first) se beneficia diretamente: agentes MCP consultam SurrealDB via LIVE query para estado em tempo real, disparam workflows DBOS para acoes regulatorias, e recebem eventos NATS para notificacoes proativas. O stack pivot e pre-requisito para a visao agentica completa.

Migrar os quatro componentes de infraestrutura conforme mapeamento abaixo. O kernel de dominio Emmett (ADR-001) nao muda — a decisao e apenas na camada de persistencia e mensageria.

Componente atualResponsabilidadeSubstituido porRacional
Emmett PG event storeAppend de streams de eventos, optimistic concurrencyNATS JetStream via emmett-nats adapterEvent bus + store unificado; replay nativo; consumer groups para fan-out
BullMQ + RedisJobs assincronos, retry, sagas SNGPCDBOS WorkflowsDurabilidade de saga crash-safe; estado persistido em Postgres DBOS
Drizzle ORM + PG read modelsRead models SQL, queries de auditoriaSurrealDBGraph-native para chain of custody; LIVE queries para MCP tools; elimina joins multi-tabela
Polling / eventos internosFan-out de eventos para multiplos consumersNATS subjects nativosSubject-based routing; acknowledgement por consumer; replay configuravel

Emmett continua como kernel de domain logic (packages/event-store). A interface EventStore do Emmett e preservada — apenas a implementacao de backend muda de PG para NATS. packages/domain permanece zero-infra, zero-NATS.

Nao existe adapter oficial Emmett → NATS. O escopo de construcao e:

packages/emmett-nats/
src/
NatsEventStore.ts # implementa EventStore interface do Emmett
NatsStreamAppender.ts # append com optimistic concurrency via sequence number NATS
NatsProjectionRunner.ts # consumer durable com checkpointing
tests/
concurrency.test.ts # gate: duas dispensacoes concorrentes → apenas uma passa

Mecanismo de optimistic concurrency no NATS: usar expectedLastMsgId ou expectedLastSeq na publish do JetStream subject. Se a sequencia esperada nao corresponde, a publish retorna erro — equivalente ao expectedVersion do Emmett PG. Isso replica o gate de concorrencia do spike v0.2.0 em NATS.

Tamanho estimado: M (medio) — ~3-5 dias de implementacao + testes de concorrencia.

WorkspaceCamada afetadaEsforcoDependencia bloqueante
packages/domainNenhuma — zero-infra
packages/event-storeEvent store backend: PG → NATSMemmett-nats adapter
packages/read-modelsSchema + queries: Drizzle/PG → SurrealDBMSurrealDB schema design
packages/app-servicesWiring event store + projecoesMemmett-nats + read-models
apps/mcp-serverMCP tools: SQL queries → SurrealDB LIVEPread-models migrado
apps/apiFastify routes: sem mudanca de logicaPapp-services migrado
apps/workerBullMQ workers → DBOS workflowsMDBOS setup
apps/sngpc-adapterSaga SNGPC: BullMQ → DBOSMapps/worker DBOS
apps/docsADRs, architecture docsP
tooling/vitestTest infra: NATS in-memory para testesPemmett-nats testes
tooling/dockerCompose: adicionar NATS + SurrealDB, remover RedisP
ops/kamalDeploy config: NATS + SurrealDB + DBOS PostgresPADR-004 VPS simulacao

Legenda: P = pequeno (<1 dia), M = medio (2-5 dias).

Total estimado: ~4M + 5P = ~15-20 dias de implementacao efetiva com subagentes paralelos.

  • Sagas SNGPC crash-safe: DBOS garante que cada step do workflow de envio SNGPC e atomico e reiniciavel. Regulatorio sem risco de estado perdido.
  • Graph queries nativas: trace_dispensation_chain MCP tool vira query SurrealDB de 3 linhas em vez de 5 joins SQL. Auditoria regulatoria expressiva.
  • LIVE updates para agentes MCP: SurrealDB LIVE query envia push websocket quando lote ou quota muda — agentes podem reagir proativamente sem polling.
  • Fan-out desacoplado: projecoes, SNGPC, audit log subscrevem NATS independentemente. Falha de projecao nao afeta o fluxo principal de dispensacao.
  • Event replay: NATS JetStream permite replay de eventos a partir de qualquer sequencia — util para rebuild de read models e debugging regulatorio.
  • Fleet agentica: base para multi-tenant SaaS com multiplas associacoes como consumers NATS independentes.

Risco 1 (alto): Optimistic concurrency em NATS e mais complexa

O mecanismo expectedLastSeq do JetStream e menos ergonomico que o expectedVersion do Emmett PG. Testes de concorrencia com subjects de alta contenção precisam ser validados antes do spike gate. Se o adapter nao reprovar o gate de concorrencia (duas dispensacoes concorrentes → apenas uma passa), a migracao nao avanča.

Risco 2 (medio): DBOS requer Postgres proprio

DBOS nao compartilha Postgres com a aplicacao — precisa de instancia dedicada para seu estado interno. Na VPS atual (62.171.145.76) ja saturada com Langfuse + SurrealDB + NATS, somar ~300MB+ de Postgres DBOS nao e viavel sem nova VPS. Bloqueante para simulacao em prod — ver ADR-004.

Risco 3 (medio): Ordenacao de projecoes para acumuladores

MemberQuotaConsumed e LotQuantityDeducted sao projecoes acumuladoras — a ordenacao de processamento importa para consistencia. NATS JetStream garante ordenacao por subject, mas o NatsProjectionRunner precisa implementar checkpointing correto para evitar duplo-processamento em restart. Requer teste de chaos (kill worker mid-projection).

Custo de migracao: ~4M + 5P por workspace conforme matriz acima. Nenhum workspace e big-bang — cada um migra independentemente com feature flag.

O kernel de dominio (ADR-001) permanece intocado. A chain of custody regulatoria — append de DispensationRecorded + MemberQuotaConsumed + LotQuantityDeducted em UM append atomico — e preservada independente do backend de event store.

Nenhum adapter Emmett → NATS JetStream existe publicamente em junho de 2026. Este e o item de maior risco tecnico da migracao.

Gate de acceptacao do adapter:

DADO: dois comandos RecordDispensation concorrentes para o mesmo stream
QUANDO: ambos tentam append simultaneamente
ENTAO: apenas um passa com expectedLastSeq correto
E: o segundo recebe erro de concorrencia
E: o segundo e reavaliado contra estado atualizado
E: se estoque insuficiente, emite LotInsufficientQuantity
E: o estoque nunca fica negativo

Se este gate nao passar, esta ADR vai para Superseded por solucao alternativa.

Prós: sem risco de migracao, stack maduro, Emmett PG ja validado em spike v0.2.0, menos VPS.

Contras: nao resolve o problema de sagas SNGPC crash-safe, nao habilita graph queries para auditoria, nao e event bus para arquitetura agentica multi-consumer. O stack atual e correto para MVP de associacao unica mas nao escala para a visao multi-tenant agentica.

Decisao: alternativa aceitavel como fallback se emmett-nats nao passar o gate de concorrencia. Nesse caso, revisitar DBOS apenas para sagas (sobre PG existente) e Drizzle mantido.

  • ADR-001 — Domain Kernel + Emmett: /adr/0001-domain-kernel-emmett/
  • ADR-002 — MCP-First Surface: /adr/0002-mcp-first-surface/
  • ADR-004 — Simulacao em VPS Dedicada: /adr/0004-simulation-vps/
  • ops-stack/references/surrealdb-patterns — patterns SurrealDB fonsecagabriel
  • ops-stack/references/nats-patterns — patterns NATS fonsecagabriel
  • ops-stack/references/surrealdb-instance — instancia SurrealDB 62.171.145.76
  • ops-stack/references/nats-instance — instancia NATS 62.171.145.76
  • Lane H mapping report — mapeamento completo dos 12 workspaces (contexto coordenador)
  • Lane G license audit — auditoria de licencas (contexto coordenador)
  • NATS JetStream docs — https://docs.nats.io/nats-concepts/jetstream
  • DBOS docs — https://docs.dbos.dev
  • SurrealDB LIVE queries — https://surrealdb.com/docs/surrealql/statements/live