Plataforma de Integração X-Adm — Arquitetura-Alvo¶
Status: Rascunho · Responsável: Gustavo Madruga · Atualizado em: 2026-08-11
Rascunho — rev. 3 (2026-07-06): estreitamento do hub + diagramas didáticos (fluxo, rede, ER, eventos). O hub deixa de ser "porta única de ingress" e passa a ser SÓ a cópia fiel do X-Adm (nas duas direções) + o connector do PowerSync; o banco por cliente vira o barramento; tudo que não é a cópia do X-Adm é um vertical standalone (porta própria) ou um reator (consome o banco, empurra pra fora), fino via xadm-commons. Reverte D12 (XLS fica standalone, não migra pra trás do hub), remove o gateway cadastrável e o dispatch do hub (D9/D10), e fixa o packaging de reator (microserviço fino, não FaaS, não plugin-no-hub). Ainda não é decisão publicada. Quando estabilizar, migra para o repo central (xadm/documentacao, "constituição") e um decision record espelha no integrador/docs/decisoes/ no início da implementação.
Rev. 4 (2026-08-11) — sincronização com o construído. A referência de reator (proc-thoms / webstorm-ecom) foi para produção provando um padrão diferente do previsto, e as libs da casa foram extraídas. Este doc foi atualizado para refletir o que existe: os reatores consomem por poll (@Scheduled) + webhook poke sobre o Postgres compartilhado (não PowerSync — descartado, junto com LISTEN/NOTIFY, na decisão 0003 do webstorm-ecom); a biblioteca é o xadm-commons multi-módulo (8 libs focadas), não o bi-commons único que a visão original previa — e sem base de reator nem bridge Kotlin PsBridge (a engine de reator é escrita à mão por app); o egress do thoms usa a tabela webstorm_ecom_request com chave cod_prod e idempotência derivada do watermark updated_at (não thoms_envio/EAN). A visão (hub estreito = cópia fiel, os 4 shapes, owner-writes, banco-barramento) permanece — mudou a mecânica de consumo dos reatores e o formato da biblioteca.
Escopo: reorganização de todas as integrações do ecossistema X-Adm — o integrador, os apps -xls, e os dois requisitos novos (PIED e thoms). Este documento define o alvo (north-star) e o roadmap de migração; não é código.
- Problema As integrações do X-Adm nasceram por cliente e por necessidade, e hoje vivem em formatos diferentes:
O integrador recebe eventos do X-Adm em tempo real (push HTTP) e mantém uma réplica na nuvem — código único, igual para todos os clientes. Os apps -xls (bi-transporte-xls, bi-comercial-xls) processam planilhas — um app por cliente, com o plumbing duplicado e a lógica de relatório hardcoded. Chegam dois requisitos com direções novas: PIED (dados de uma fonte externa entram no X-Adm) e thoms (dados do X-Adm saem para um e-commerce). À medida que as necessidades divergem por cliente, aparece a tensão central:
Motor genérico (compartilhado, estável) × lógica divergente (isolada, muda em ritmos diferentes).
Os dois erros a evitar:
(a) enfiar lógica divergente no motor compartilhado → "pra atualizar um cliente, redeploya todo mundo" (o medo que motivou este documento); (b) duplicar o motor por cliente → a dívida atual dos -xls (~42 classes de plumbing repetidas). O reframe que este documento adota: a variável de organização não é o cliente — é a forma (shape) do fluxo de dados.
Princípio-mestre desta revisão: o hub é a cópia fiel do banco do X-Adm — o caminho oficial pra extrair uma cópia do ERP e pra devolver dados a ele pelo mesmo banco. Qualquer coisa diferente disso tem o seu próprio caminho (um vertical com porta própria, ou um reator que consome o banco). O banco por cliente é o barramento; o erro (b) é evitado não por centralizar no hub, mas pela biblioteca comum (xadm-commons) que deixa cada vertical fino.
- Estado atual (mapa) 2.1 Como os dados do X-Adm chegam à nuvem hoje O X-Adm (ERP legado, on-prem) empurra os dados — não é pull.
Push HTTP → PUT/DELETE /api/v1/xadm no integrador. Body JSON com campo Origem (PNOTAI, PBLDI, PPEDAMX, PNOTAD, PPEDIDO, PCSTPROD, PREQUIS) + arrays por tabela. PUT = upsert, DELETE = soft delete. Aplicação numa única transação, serializada por lock global (ordem FIFO, evita deadlock intra-XADM). Contrato legado: responde sempre HTTP 200 (erro vai no corpo + Sentry). Batch XLSX → apps -xls. Recepção assíncrona (valida magic bytes ZIP, SHA-256 para dedupe, grava no storage Garage, status PENDENTE, responde 202) + processamento em background (Apache POI em modo SAX → merge diff-aware no Postgres). Ambos aterrissam num Postgres por cliente, replicado por PowerSync (1 deploy por cliente) para os apps Flutter.
2.2 Modelo de dados
integrador: 14 tabelas de negócio (contratos, estoque, fones, itens, itenslote, itensped, itenspedamx, itpedamxdi, loteest, municipio, notacompl, propriedades, tbpcoest, veiculos), + infra (request = auditoria, push_enviada, pabast_). Schema via Flyway; soft delete (deleted); PK UUID v7.
Espelho do ERP em duas direções: fluxo de SAÍDA (X-Adm → Integrador → PowerSync → apps; produtor = X-Adm, Chave já preenchida; ex. Sul Plata) e fluxo de ENTRADA (PIED → Integrador → PowerSync → X-Adm; produtor = fonte externa, chave natural xPed/CodigoAlt/CgcCpf; mais colunas das mesmas tabelas + fones). Princípio: toda tabela que VOLTA ao X-Adm (entrada) carrega CodRetorno + MsgRetorno, que o X-Adm preenche no write-back (POST /api/v1/powersync) junto com as Chave que gera; em tabelas usadas só na saída essas colunas ficam sempre null. O produtor (int-pied) reconcilia lendo GET /api/v1/xadm/retorno/{tabela} (status derivado: 006=confirmado, 0XX=pendente, 1XX/9XX=rejeitado). Ver decisão 0013 e modelagem.md.
Entrada vs saída de nota não são tabelas separadas: é o 4º caractere da chave_nota (P = entrada, R = saída/venda) — convenção do ERP. O app navarro (sulplata / onpetrotrading) calcula saldo de BL e vendas sobre notacompl + itens a partir dela. A integração reflete e documenta essa convenção do ERP (ver D0).
-xls: tabelas bi_ (sincronizam via PowerSync) e xls_* (metadados do processador, não sincronizam), em banco próprio por cliente.
2.3 Multi-cliente hoje
1 deploy por cliente (integracao.
Fora de escopo desta reorg: etc/database-sync (migração pontual PG→PG), etc/watchdog (monitor de proxy Coolify).
- Os 4 shapes de integração A organização gira em torno da forma do fluxo, não do cliente:
Push X-Adm em tempo real — X-Adm empurra eventos. Genérico, igual para todos. Entra no hub → réplica. (hoje: integrador) Batch de arquivo (XLSX/CSV) com parsing específico — o X-Adm exporta relatório, alguém faz upload, um parser específico do relatório transforma. Divergente por cliente. Vertical standalone com porta própria. (hoje: apps -xls) Pull/webhook de API externa → formato X-Adm (ENTRADA) — puxa/recebe de uma fonte externa, transforma para o formato do X-Adm, e entrega de volta ao ERP on-prem. Vertical standalone com porta própria. (novo: PIED; POC pronta em poc-pied-simples) Push para API externa (SAÍDA) — lê a réplica no banco, transforma, e faz POST numa API de terceiro. Reator (consome o banco). Divergente por cliente, mas leve. (novo: thoms → e-commerce) Shapes 1 e 2 são saída de dados do X-Adm (o X-Adm é a fonte). Shape 3 é entrada no X-Adm (o X-Adm é o destino). Shape 4 é saída, mas o destino é uma API externa em vez de um app.
Só o shape 1 (a cópia fiel do X-Adm) entra pelo hub. Os shapes 2 e 3 entram pela porta do PRÓPRIO vertical. O shape 4 não tem porta de entrada — é um reator que consome o banco.
3.1 O contrato de transporte X-Adm ↔ nuvem (padrão único por direção) Ficou definido um padrão único de transporte entre o X-Adm e a nuvem, um por direção. O que muda de uma integração para outra é só o processamento e o destino, nunca o transporte.
Saída (X-Adm → nuvem) — dois modos, conforme a natureza do dado:
Eventos de domínio em tempo real (nota emitida, BL liberado, pedido…): o X-Adm faz o push HTTP direto ao hub, sem delay (é o /api/v1/xadm de hoje). São eventos pequenos que exigem imediatismo — inclusive os que disparam push FCM. Viram a cópia fiel (réplica) no hub.
Dados em arquivo / lote: o X-Adm grava numa pasta de envio local (outbox); um .jar agente observa a pasta (ex.: a cada 1 min) e envia à nuvem. O agente roteia por TIPO (config, não código): dado bruto do ERP que vira a cópia fiel (ex.: produto/estoque tabular) → o hub (int.
O X-Adm fica desacoplado do destino: quem se preocupa com as integrações externas é a nuvem — é lá que vive a inteligência de "como e para onde entregar".
Entrada (fonte externa → X-Adm) — "espelho + PowerSync":
A nuvem recebe os dados de uma integração externa (ex.: PIED) na porta do vertical, processa e grava no formato de tabela que o X-Adm espera — um espelho (tabelas sem prefixo, com máquina de status por linha) no banco do cliente. Um cliente Java do lado do X-Adm conecta via PowerSync (connector do hub), lê as linhas PENDENTE, importa no ERP chamando o zimrtmu.exe (runtime do ZIM) e escreve o status de volta (PROCESSANDO/IMPORTADO/ERRO). Mesmo padrão para qualquer fonte de entrada — muda a integração externa e o processamento, não o contrato com o X-Adm. (Ver D6/D7.)
- Arquitetura-alvo
Uma Plataforma de Integração X-Adm: um hub estreito (a cópia fiel do X-Adm) por cliente + verticais standalone por fonte/relatório (porta própria) + reatores que consomem o banco + Postgres por cliente (o barramento) + PowerSync bidirecional (do hub).
X-Adm (ERP legado, on-prem)outbox + agente .jar │ push HTTP (tempo real) ▲ cliente Java (PowerSync) (~1 min, roteia p/tipo)│ + arquivos roteados │ lê PENDENTE, importa, ▼ │ escreve status (entrada) ┌──────────────────────────────┐ ┌───────────────────────────────────────────┐ │ HUB · int.
│ │ VERTICAIS (porta PRÓPRIA, finos) │ │ push X-Adm → RÉPLICA │ │ excel. — recebe XLS, parse, bi_ │ │ connector PowerSync │ │ pied. — webhook+poll, espelho │ │ write-back (nome/status ERP) │ │ (recepção/dedupe/envelope via xadm-commons) │ │ dono da CÓPIA FIEL do X-Adm │ └──────────────────────┬────────────────────┘ └──────────────┬───────────────┘ │ owner-writes │ owner-writes │ ▼ ▼ ┌─────────────────────────────────────────────────────────────────────────────┐ │ POSTGRES POR CLIENTE — O BARRAMENTO ⇄ PowerSync (do hub) │ │ réplica X-Adm · bi_ · estado-desejado (PENDENTE) · status dos reatores │ └───────┬───────────────────────────────┬─────────────────────────┬───────────┘ PowerSync PowerSync poll + webhook poke ▼ ▼ ▼ apps Flutter cliente Java ERP REATORES (consomem o banco, (bi-*, navarro_app) (importa no X-Adm) empurram pra fora: proc-thoms→ecom) (FCM: fica no integrador por ora — D8)
Diagrama do alvo — fluxo de dados de ponta a ponta (Mermaid):
flowchart TB
subgraph ONPREM["🏢 X-Adm — on-prem"]
direction TB
XADM["X-Adm<br/>(ERP ZIM)"]
AGENTE["Agente .jar<br/>(outbox, ~1 min)"]
PSC["Cliente Java + PsBridge<br/>chama zimrtmu.exe"]
end
subgraph CLOUD["☁️ Nuvem — 1 stack por cliente"]
direction TB
HUB["HUB · int.cliente<br/>cópia fiel do X-Adm<br/>+ connector PowerSync"]
EXCEL["excel.cliente<br/>XLS → bi_*"]
PIEDV["pied.cliente<br/>webhook + poll → espelho"]
REAT["Reator · proc-thoms<br/>consome → empurra"]
PG[("Postgres do cliente<br/>— O BARRAMENTO —")]
PS(["PowerSync"])
end
subgraph EXT["🌐 Externo"]
direction TB
PIEDAPI["API PIED"]
ECOM["E-commerce<br/>(Webstorm)"]
end
APP["📱 Apps Flutter<br/>bi-*, navarro_app"]
XADM -->|"① push REST JSON (tempo real)"| HUB
XADM -.->|"arquivos"| AGENTE
AGENTE -->|"XLS (anexo)"| EXCEL
AGENTE -->|"dado bruto ERP"| HUB
PIEDAPI <-->|"webhook / poll"| PIEDV
HUB -->|"réplica"| PG
EXCEL -->|"bi_*"| PG
PIEDV -->|"estado-desejado"| PG
PG <--> PS
HUB -.->|"connector"| PS
PS ==>|"② sync"| APP
APP -.->|"write-back (uploadData)"| HUB
HUB -.->|"FCM push (por ora no integrador)"| APP
PG ==>|"③ poll + webhook poke"| REAT
REAT -->|"POST egress"| ECOM
PS <-->|"④ PENDENTE + status"| PSC
PSC -->|"zimrtmu.exe importa"| XADM
classDef hub fill:#064490,color:#fff,stroke:#042c5e;
classDef vert fill:#2a5b97,color:#fff,stroke:#042c5e;
classDef reat fill:#8b5e00,color:#fff,stroke:#5e3f00;
classDef data fill:#5f6b78,color:#fff,stroke:#3a434d;
class HUB hub;
class EXCEL,PIEDV vert;
class REAT reat;
class PG,PS data;
Os quatro caminhos numerados: ① a cópia sai do X-Adm e chega no hub por REST JSON (push tempo real) — e dado bruto/relatório via o agente; ② os apps recebem por PowerSync (e devolvem write-back pelo connector do hub); ③ os reatores consomem o banco e empurram pra fora (ecom; o FCM fica no integrador por ora); ④ o que vai pro X-Adm desce por PowerSync até o cliente Java, que chama o zimrtmu.exe pra gravar no ERP e devolve o status.
Leitura: só a cópia fiel do X-Adm entra pelo hub (push HTTP + dado bruto do agente). Relatório e fonte externa entram pela porta do próprio vertical. O Postgres por cliente é o barramento: cada peça escreve as SUAS tabelas (owner-writes) e o hub é o connector do PowerSync que serve os apps e o cliente Java do ERP (entrada); os reatores leem o banco direto por poll+poke e empurram pra fora. O FCM segue no integrador por ora (D8).
4.1 Os tipos de app (taxonomia) Toda a organização se apoia em distinguir os tipos de app, com donos e responsabilidades diferentes.
Tipo O que é Exemplos (alvo) Deploy
Hub A cópia fiel do X-Adm por cliente: recebe o push do X-Adm (réplica), é o connector do PowerSync (serve os apps e o cliente do ERP; aplica write-backs), é dono da réplica + do request de auditoria. (Os reatores NÃO passam pelo connector — leem o banco por poll+poke.) Genérico, fino, NÃO é porta de ingress de nada além do X-Adm. int.@Scheduled) + webhook poke, reage, é dono da sua tabela de status/auditoria, e empurra pra fora (API de terceiro, push). Interno (sem porta pública). proc-thoms (→ e-commerce, em prod). FCM (navarro): fica no integrador por ora (D8). 1 por destino/reação
App de cliente O que o usuário final usa. Consome o banco via PowerSync; pode fazer write-back de campos editáveis (uploadData → connector do hub). Não é integração. navarro_app (sulplata/onpetrotrading), bi-transporte, bi-comercial 1 por app de cliente
E uma natureza que NÃO é app: a biblioteca da casa (xadm-commons, multi-módulo) com o plumbing genérico — hoje: auth (xadm-seguranca), infra web/RFC-7807/health (xadm-comum-web), outbox M2M (xadm-mensageria), utilitários (xadm-comum-util), storage S3/Garage (xadm-comum-storage), fixtures de teste (xadm-comum-teste), admin/reset PowerSync (xadm-comum-powersync) e o seam de ingestão XLS (xadm-ingestion-core). Usada pelo hub, verticais e reatores — é o que os torna finos. É o pilar do desenho (D11): sem ela, cada standalone reintroduziria o erro (b). Nota: a "base de reator (consumidor PowerSync + idempotência + status)" da visão original não foi construída — o reator consome por poll+poke e é escrito à mão (D8).
A fronteira nuclear: o hub é a cópia fiel do X-Adm e o connector do PowerSync; o vertical é a face para o mundo externo (recebe na SUA porta) e dono do seu domínio; o reator consome o banco e empurra pra fora; o app de cliente consome. Cada tabela tem um dono (owner-writes, D4); o banco compartilhado é o ponto de encontro.
Diagrama de camadas (código — o corte 75/25):
flowchart TB
COMMONS["xadm-commons — BIBLIOTECA multi-módulo, não é app: auth · web/RFC-7807 · mensageria · util · storage S3 · fixtures teste · admin/reset PowerSync · seam ingestão XLS (SPI). NB: base de reator NÃO construída — reator é hand-rolled (D8)"]
HUB["HUB · int.cliente — cópia fiel do X-Adm + connector PS + write-back (genérico)"]
EXCEL["excel.cliente (vertical) — ~25% próprio: parse abas 10/20/30, defesa veicular, chaves naturais, schema bi_*, sua porta/console"]
REAT["proc-thoms (reator) — ~pouco: declara tabelas, mapeia (EAN), tabela de status, POST egress"]
PG[("Postgres do cliente — barramento")]
APP["bi-transporte / bi-comercial / navarro_app (Flutter) — via PowerSync"]
HUB -.->|"depende"| COMMONS
EXCEL -.->|"depende"| COMMONS
REAT -.->|"depende"| COMMONS
HUB -->|"réplica + write-back"| PG
EXCEL -->|"bi_*"| PG
PG -->|"poll + poke"| REAT
REAT -->|"egress"| PG
PG -->|"PowerSync"| APP
Leitura: xadm-commons é a fundação (~75%) que hub, verticais e reatores compilam; o hub é a cópia + o connector; o vertical tem só o seu ~25% + a sua porta; o reator é ainda mais fino (declara o que ouve + a reação). Ninguém duplica plumbing.
4.2 Fronteiras e propriedade de dados Cada cliente tem um Postgres (o barramento), com um dono por tabela (owner-writes, D4) e vários leitores. Quem escreve o quê:
Hub (int.
Exemplo trabalhado — vantroba (hoje → alvo):
Hoje Alvo Hub não existe — o -xls acumula tudo nasce int.vantroba.xadm.biz ESTREITO: só a cópia fiel do X-Adm (réplica do push) + connector PowerSync + write-back Vertical bi-transporte-xls (Micronaut, ~75% boilerplate) segue como excel.vantroba (standalone, porta PRÓPRIA); NÃO migra pra trás do hub (D12→B); emagrece extraindo o plumbing pro xadm-commons; segue dono das suas tabelas (bi_faturamento/bi_movimento) + Flyway próprio App de cliente bi-transporte (Flutter) lê via PowerSync igual, sem mudança
Convenção de nomes de tabela (= propriedade do dado):
Prefixo Significa Sincroniza p/ apps? Exemplos
(sem prefixo) Dado do ERP — é, ou vai virar, dado do X-Adm Sim réplicas do X-Adm (itens, notacompl…); produto/estoque do thoms; estado-desejado do PIED que o cliente Java importa
webstorm_ecom_request (append-only, chave cod_prod CHAR(11); idempotência derivada do watermark updated_at, não uma coluna PENDENTE/ENVIADO/ERRO). EAN (ean13) é só correlação, não a chave.
4.3 Componentes de runtime Hub (genérico, 1 por cliente). Evolução ESTREITADA do integrador atual. NÃO é porta de ingress geral. Recebe: (1) o push HTTP do X-Adm em tempo real, e (2) via agente, o dado bruto do ERP que vira a cópia fiel (ex.: produto/estoque). Aplica na réplica, mantém o request de auditoria, é o connector do PowerSync (serve apps, cliente do ERP e reatores; aplica write-back). Não parseia relatório, não recebe webhook de fonte externa, não despacha nada, não conhece as tabelas dos verticais/reatores (owner-writes, D4).
Verticais de integração (divergente, deploy independente, finos via xadm-commons, PORTA PRÓPRIA). A face para o mundo externo que RECEBE:
Processadores de relatório (excel.
Reatores (consomem o banco, empurram pra fora; sem porta pública). Reagem a um dado no banco do cliente e empurram pra fora. Packaging FIXADO pelo proc-thoms construído (D8): microserviço Micronaut fino que consome o Postgres compartilhado por poll (@Scheduled) + webhook poke (PowerSync/NOTIFY descartados, decisão 0003 do webstorm-ecom), é dono da sua tabela de status/auditoria (idempotência por watermark), e chama o externo. NÃO é FaaS (sem infra nova; não há problema de escala — um punhado de reatores) e NÃO é plugin dentro do hub (isso reconstruiria o monólito). A engine é escrita à mão no app (não há base de reator compartilhada). proc-thoms é a referência que prova o padrão — em produção. O push FCM (hoje no integrador, específico do navarro_app) PERMANECE no integrador por ora — extração para reator deferida (D8).
Postgres por cliente (o barramento, compartilhado pelas peças daquele cliente). Réplica, bi_*, estado-desejado e status de reator entram no mesmo banco. Sem dono único de schema: o hub roda o Flyway da réplica; cada vertical/reator roda o seu (histórico separado). Um dono por tabela (D4).
PowerSync por cliente (transporte bidirecional, connector no hub). Além do sync X-Adm→apps, serve o cliente Java do ERP (entrada). Os reatores NÃO usam PowerSync — leem o banco por poll+poke. Write-back sempre pelo connector do hub (uploadData → POST int.
4.4 Varredura de fronteiras (o que vai onde) Regra de bolso para classificar qualquer peça:
É a cópia fiel do X-Adm (a réplica do push, ou dado bruto do ERP que vira réplica) ou o connector do PowerSync → hub. Recebe de fora (relatório, webhook/poll de fonte externa) e escreve suas tabelas → vertical com porta própria. Não recebe de fora, consome o banco e empurra pra fora → reator. Consome o resultado para um usuário → app de cliente.
O que está no integrador hoje → destino no alvo:
Peça atual Classificação Destino XadmController (push /api/v1/xadm) cópia fiel hub Recepção/apply na réplica + request (auditoria) cópia fiel hub Connector PowerSync + /lastchange + write-back connector hub Push FCM (contexto BL/venda, payload) reator (específico do navarro_app) fica no integrador POR ORA (extração deferida — D8) Views CRUD JTE por tabela, export XLSX, /debug operacional da réplica hub (a console da cópia fiel; login via SSO) Login Google/Firebase das views identidade plano auth; SSO nas portas APIs REST /api/v1/* por tabela da réplica hub (não mexer agora)
Reconforto: quase tudo do integrador é a cópia fiel do X-Adm e vira o hub estreito; o único resíduo específico de app é o push FCM, que fica onde está por ora.
E o bi-transporte-xls (rico) → alvo (medido: ~75% genérico / ~25% específico):
Peça do -xls Classificação Destino
Dedupe SHA-256, storage 3-modos, envelope de status, motor de upsert, motor SAX, auth 2-camadas, reset PowerSync, bi_configuracao, 100% das telas operacionais genérico (~75%) — idêntico ao bi-comercial-xls xadm-commons (biblioteca)
Parse (abas 10/20/30), regras (defesa veicular, seq_dentro_grupo), chaves naturais, schema (bi_faturamento/bi_movimento), a SUA porta/console específico (~25%) segue no vertical excel.
Apps de cliente → sua fronteira:
App Lê Escreve Fronteira navarro_app (sulplata/onpetrotrading) notas/BL via PowerSync nomes curtos via uploadData→hub consumidor + write-back; o push é do integrador (por ora), não do app bi-transporte (vantroba), bi-comercial (onpetro) bi_* via PowerSync nada (read-only) consumidor puro Nenhum app de cliente escreve no banco direto nem faz integração — sempre via hub (write-back) ou lendo via PowerSync.
4.5 Diagramas didáticos (rede, dados, eventos) A mesma arquitetura por três ângulos: a REDE (o que é público/interno/on-prem e quem fala com quem), os DADOS (o modelo no banco do cliente) e os EVENTOS (a ordem temporal de cada fluxo).
Rede — o que roda onde e quem fala com quem
flowchart TB
subgraph ONPREM["🏢 On-prem — LAN do cliente"]
XADM["X-Adm (ERP ZIM)"]
AG["Agente .jar"]
PSC["Cliente Java<br/>+ zimrtmu.exe"]
end
subgraph COOLIFY["☁️ Coolify + Traefik — HTTPS (Let's Encrypt)"]
subgraph PUB["Portas públicas — Traefik + SSO (login Google)"]
HUB["int.cliente.xadm.biz"]
EXCEL["excel.cliente.xadm.biz"]
PIEDV["pied.cliente.xadm.biz"]
end
subgraph INTERNOS["Internos — sem domínio público"]
REAT["proc-thoms"]
PS["PowerSync"]
PG[("Postgres do cliente")]
end
end
subgraph EXTNET["🌐 Internet"]
PIEDAPI["API PIED"]
ECOM["Webstorm"]
FCM["Firebase / FCM"]
end
APP["📱 Apps (mobile / web)"]
XADM -->|"HTTPS ↑ REST"| HUB
AG -->|"HTTPS ↑"| HUB
AG -->|"HTTPS ↑"| EXCEL
PSC <-->|"HTTPS ↕ PowerSync"| PS
PIEDAPI -->|"webhook ↓"| PIEDV
PIEDV -->|"poll ↑"| PIEDAPI
REAT -->|"HTTPS ↑"| ECOM
HUB -->|"HTTPS ↑"| FCM
APP <-->|"HTTPS ↕"| HUB
APP <-->|"HTTPS ↕ sync"| PS
HUB --- PG
EXCEL --- PG
PIEDV --- PG
REAT --- PG
PS --- PG
Leitura: o X-Adm (on-prem) só faz conexões de SAÍDA para a nuvem (HTTPS) — nenhuma porta aberta no cliente. Na nuvem, o Traefik expõe só as portas públicas (hub + verticais), com SSO na frente; reatores, PowerSync e Postgres são internos (sem domínio). As integrações externas (PIED, Webstorm, FCM) falam pela borda.
Dados — o modelo no banco do cliente (o barramento)
erDiagram
NOTACOMPL ||--o{ ITENS : "chave_nota"
NOTACOMPL ||--o{ BI_FATURAMENTO : "derivado"
NOTACOMPL {
uuid id PK
string chave_nota "4o char P=entrada R=venda"
boolean deleted
}
ITENS {
uuid id PK
string chave_nota FK
}
VEICULOS {
uuid id PK
string placa
}
BI_FATURAMENTO {
uuid id PK
string periodo
numeric valor
}
ESTADO_DESEJADO_PEDIDO {
uuid id PK
string status
string msg_erro
}
REQUEST {
uuid id PK
string origem
string status
}
WEBSTORM_ECOM_REQUEST {
string cod_prod
boolean sucesso
timestamp updated_at_enviado
}
Legenda de propriedade (owner-writes, D4): réplica sem prefixo (NOTACOMPL, ITENS, VEICULOS…) + REQUEST = hub · BI_ (derivado) + XLS_ = excel.cliente · ESTADO_DESEJADO_ (sem prefixo) + PIED_ (landing/normalizado) = pied.cliente · WEBSTORM_ECOM_REQUEST (auditoria por cod_prod) = proc-thoms. Convenção do ERP (D0): entrada vs saída de nota é o 4º char da chave_nota (P/R), não tabelas separadas.
Eventos — a ordem temporal de cada fluxo
Saída (tempo real) + reator de egress:
sequenceDiagram
autonumber
participant X as X-Adm
participant H as Hub
participant PG as Postgres
participant PS as PowerSync
participant A as App
participant R as Reator
participant E as E-commerce
X->>H: push REST JSON (nota)
H->>PG: upsert réplica (1 transação)
H-->>X: 200 (erro vai no corpo)
PG-->>PS: replica change
PS->>A: sync (nota nova)
H-->>R: webhook poke (opcional, baixa latência)
R->>PG: sweep @Scheduled: SELECT pendentes (watermark updated_at)
R->>E: POST egress (lote 10, teto 200)
R->>PG: webstorm_ecom_request (sucesso, updated_at_enviado)
Entrada (fonte externa → X-Adm, via zimrtmu.exe):
sequenceDiagram
autonumber
participant P as API PIED
participant V as pied.cliente
participant PG as Postgres
participant PS as PowerSync
participant C as Cliente Java (ERP)
participant Z as zimrtmu.exe
participant X as X-Adm
P->>V: webhook (ou V faz poll)
V->>PG: estado-desejado (PENDENTE)
PG-->>PS: change
PS->>C: linha PENDENTE
C->>Z: importar
Z->>X: grava no ERP ZIM
C->>PS: status = IMPORTADO
PS->>PG: write-back status
Excel (anexo → dashboard):
sequenceDiagram
autonumber
participant U as Agente / Upload
participant EX as excel.cliente
participant PG as Postgres
participant PS as PowerSync
participant A as Dashboard
U->>EX: XLS (anexo)
EX->>EX: dedupe SHA-256 + parse SAX
EX->>PG: bi_* (upsert diff-aware)
PG-->>PS: change
PS->>A: sync (bi_*)
- Decisões de projeto Decisões já tomadas, registradas com o porquê (viram decision records na implementação).
D0 — A integração espelha o banco do ERP, sempre Desenho do ERP manda, integração obedece. O ideal é refletir as tabelas e colunas do X-Adm tal como são. A integração é espelho: não reinterpreta nem remodela. Quando o ERP usa uma convenção não óbvia, a integração a documenta e a segue fielmente. Exemplo: entrada vs saída de nota é o 4º char da chave_nota (P/R), não tabelas separadas. É fronteira de responsabilidade: modelagem é do ERP; integração é espelho.
D1 — Organizar por shape do fluxo, não por cliente A lógica genérica (cópia fiel, transporte) é a mesma independente do cliente; a lógica divergente (parsing, mapeamento) muda por relatório/fonte. Separar as duas é o que resolve a tensão (a)/(b) da seção 1.
D2 — Hub = cópia fiel do X-Adm (in/out) + connector PowerSync, 1 deploy por cliente (NÃO é porta de ingress) O hub faz exatamente uma coisa: mantém a cópia fiel do banco do X-Adm (réplica do push; dado bruto do ERP), é o connector do PowerSync (serve apps, cliente do ERP e reatores; aplica write-back) e é dono da réplica + request. Ingress de qualquer coisa que NÃO seja a cópia fiel (relatório XLS, fonte externa) entra pela porta do PRÓPRIO vertical, não pelo hub. Mantém a propriedade boa do integrador (código idêntico, diferença só em config; blast radius por cliente) e dá ao hub uma responsabilidade única e nítida. "Hub estreito" ≠ monólito de ingress.
D3 — Lógica divergente em verticais/reatores standalone, deploy independente Isola a lógica volátil: atualizar o parser da vantroba (excel.vantroba) ou o mapeamento do thoms não redeploya o hub nem outros clientes. O plumbing (recepção/dedupe/status/UI/upsert) sai dos serviços para o xadm-commons (biblioteca) — não para o hub — então cada serviço fica fino (~25% de código próprio) sem duplicar infra. Prova empírica: hoje bi-transporte-xls e bi-comercial-xls já têm ~75% de classes idênticas.
D4 — Owner-writes: um dono por tabela, no banco compartilhado (o barramento) Cada tabela tem exatamente um dono — quem roda o DDL (Flyway) e escreve. O hub é dono da réplica + request. Cada vertical é dono das suas tabelas (bi_*, estado-desejado). Cada reator é dono da sua tabela de status. xadm-commons carrega o motor de escrita PowerSync-safe (upsert diff-aware, soft-delete, dedupe) — a disciplina é compartilhada por código, não por centralizar num hub. O ponto de integração é o banco, não um dispatcher.
D5 — Réplicas novas no Postgres do cliente PIED (pedido/cliente/produto) e thoms (produto/estoque) entram no Postgres do respectivo cliente, reaproveitando a topologia atual (um banco por cliente, compartilhado). O isolamento entre clientes é preservado; o acoplamento fica só entre peças do mesmo cliente (via o barramento).
D6 — Entrada no X-Adm (shape 3) via PowerSync bidirecional + máquina de status por linha O resultado do processamento fica em tabelas de estado-desejado na nuvem, cada linha com status PENDENTE → PROCESSANDO → IMPORTADO → ERRO (+ msg_erro). Um programa Java do lado do ERP conecta via PowerSync (connector do hub), lê PENDENTE, importa no X-Adm chamando o zimrtmu.exe (o runtime do ZIM que grava no ERP) e escreve o status de volta. Não cria canal de entrada novo no ERP — reusa o PowerSync que já roda. O poc-ps-java fica como POC/base do cliente PowerSync (consumo em Java exige a bridge Kotlin PsBridge.kt — a mesma que o xadm-commons usa nos reatores). Simetria: essa máquina de status por-linha é a mesma ideia da tabela request e generaliza para qualquer entrada futura.
D7 — Transporte X-Adm padronizado (por direção)
Saída — eventos em tempo real: push HTTP direto ao hub (o /api/v1/xadm atual permanece). Saída — arquivo/lote: X-Adm grava na outbox (JSON/XLS) → agente .jar roteia por tipo (config): dado bruto do ERP → hub; relatório → o vertical dono (excel.
D8 — Efeitos específicos de app são REATORES, fora do hub; packaging = microserviço fino
Reação/entrega específica (push, egress para API de terceiro) NÃO mora no hub — é um reator. Packaging fixado pelo proc-thoms construído: microserviço Micronaut FINO que consome o Postgres compartilhado por poll (@Scheduled, sweep ~60s) + um webhook poke HTTP para baixar a latência (near-real-time nos writes do X-Adm), é dono da sua tabela de status/auditoria (idempotência derivada de um watermark updated_at), reage e empurra pra fora. PowerSync e LISTEN/NOTIFY foram avaliados e descartados (decisão 0003 do webstorm-ecom): PowerSync existe para alcançar um consumidor sem acesso ao banco — mas o reator vive no mesmo banco; NOTIFY não persiste e exigiria o sweep de qualquer jeito. Padrão "Outbox = o próprio banco" (poll é o piso garantido; o poke é otimização de latência, não garantia). NÃO vive dentro do hub (plugin-no-hub = o monólito de volta, o análogo do "registro de telas" rejeitado no D12) e NÃO é FaaS (sem infra nova; sem problema de escala — um punhado de reatores; o "imposto de app" do D13 é uniforme e mais barato que levantar um FaaS). Não há base de reator compartilhada hoje: a engine (sweep, single-flight, leitura da réplica, mapper, egress em lote, auditoria) é escrita à mão no app — "fino" vem do banco fazer de fila, não de uma lib. O hub fica uniforme (só a cópia) pra TODO cliente; "tem push/egress" é composição explícita (deployar o reator), não config no motor genérico. FCM (navarro) e egress (thoms) são o mesmo padrão.
Sequência: proc-thoms foi o PRIMEIRO reator construído e a REFERÊNCIA — está em produção (webstorm.thoms.xadm.biz), com lote/breaker/retry/pacing configuráveis. O push FCM PERMANECE no integrador por ora (funciona hoje; a extração é purista, não funcional) — deferida (REGRA Nº 3). Gatilho pra GENERALIZAR num notificador único (regra/payload viram config): um 2º app precisar de push — antes disso, um reator por reação, com a regra em código. Aprendizado da referência: se algum dia sair uma "base de reator" compartilhada, ela padroniza sweep+poke+watermark, não um consumidor PowerSync.
D9 — Ingestão externa entra pela porta do PRÓPRIO serviço (não há gateway no hub) Cada vertical que recebe de fora (webhook/poll de fonte externa, arquivo) faz a própria recepção — auth de transporte (token do cadastro do serviço), dedupe, envelope de status — na SUA porta, via xadm-commons. Não há gateway cadastrável no hub (o hub não é porta de ingress). Webhook = fast path (tempo real); poll = reconciliação; idempotência por chave natural cobre a sobreposição — tudo dentro do serviço. Adicionar uma fonte = 1 serviço novo (vertical), sem tocar no hub.
D10 — Sem registro de serviços nem dispatch: o barramento é o banco Não há contrato de dispatch hub→serviço nem registro de serviços no hub. O ponto de integração é o banco compartilhado (owner-writes, D4): cada vertical escreve as suas tabelas; reatores consomem o banco via PowerSync; o hub não conhece as peças. UI: cada vertical tem a sua console (na sua porta, para a sua ingestão/domínio); o hub tem a sua (saúde da réplica/push); SSO (plano auth) unifica o login das portas. Custo aceito: mais de uma console por cliente — mitigado pelo SSO. Sem framework de plugin de UI.
D11 — a biblioteca da casa re-acopla; a política desacopla (o PILAR)
A lib da casa reintroduz acoplamento em compile-time: um breaking change toca o hub e TODOS os serviços que a consomem — o medo (a) voltando pela porta da biblioteca. Como AGORA cada vertical é standalone e fino graças a ela, a biblioteca é o pilar do desenho (é o que impede o erro (b)). Realidade construída (ADR 0019 do xadm-commons): não é o bi-commons único que a visão previa, e sim o xadm-commons multi-módulo — libs focadas, cada uma versionada e publicada por conta própria, o consumidor pina só o que usa. Módulos hoje: xadm-seguranca (auth), xadm-comum-web (RFC-7807/health/versão/Sentry), xadm-mensageria (outbox M2M + tela), xadm-comum-util (checksum/conversão), xadm-comum-storage (S3/Garage XLS), xadm-comum-teste (fixtures Testcontainers/ArchUnit), xadm-comum-powersync (admin/reset do PowerSync — não consumo), xadm-ingestion-core (seam SPI de ingestão XLS — o SAX/upsert são SPI do app, não da lib). Governança: SemVer por-módulo, tag <módulo>-vX.Y.Z, o release.yml publica só o módulo taggeado; distribuição via Maven registry do Forgejo (fonte.xadm.biz) + CI de release; consumidor pina a versão (nunca SNAPSHOT em produção); ZERO conhecimento de domínio, guardado por ArchUnit. Não existe (nem planejado no xadm-commons) uma base de reator / bridge Kotlin PsBridge — o reator é escrito à mão (D8); o upsert PowerSync-safe/envelope da visão original também não viraram módulo.
D12 — XLS segue vertical standalone (excel.
D13 — Todo hub/vertical/reator é um app da plataforma X-Adm
Cada peça (nova ou migrada) segue a governança da casa: repo próprio, checklist app-novo.md, docs/app.json (app_id; toolchain ∈ matriz-baseline da fábrica de CI), workflows docs/ci/release, /health OBRIGATÓRIO (constituição, Engenharia), GlitchTip via Central de Apps (constituição, Central de Apps), skills instaladas. Custo declarado ("imposto de app"): cada serviço paga o bootstrap completo — uniforme, proporcional para integrações de verdade, e é o que mantém a plataforma auditável (e mais barato que um runtime FaaS — D8). Transição de DNS: int.
- Como cada shape encaixa no alvo Shape 1 (push X-Adm): o hub recebe e aplica direto na réplica — a cópia fiel. É o integrador atual, estreitado (só isso + connector + write-back).
Shape 2 (XLSX/CSV): o vertical excel.
Shape 3 (PIED, entrada): vertical integracao/maxsul/pied com a PORTA PRÓPRIA e dois adaptadores de entrada para a mesma transformação: Webhook (tempo real): a PIED chama a porta do PRÓPRIO vertical (pied.maxsul.xadm.biz/webhook); o vertical recebe (auth de transporte, dedupe, status via xadm-commons), verifica assinatura e parseia. Poll REST (reconciliação): o vertical pola a API PIED direto (safety net / backfill). Ambos convergem no mesmo transform; o vertical grava as tabelas de estado-desejado (é dono delas) no banco do maxsul; o cliente Java no ERP importa e o status volta pelo connector do hub. Idempotência por chave natural torna a sobreposição webhook×poll segura. (Ver D6/D9.)
Shape 4 (thoms, saída): reator proc-thoms (em produção, webstorm.thoms.xadm.biz). O X-Adm grava JSON de produto/estoque na outbox; o agente envia para o hub (int.thoms.xadm.biz) — é dado bruto do ERP, vira a cópia fiel (réplica produto/estoque, sem prefixo, zero lógica thoms no hub). O proc-thoms (reator) consome a réplica por poll (@Scheduled, sweep ~60s) + webhook poke (não PowerSync), mapeia por cod_prod (EAN só correlação; só variante existente), e chama a API do parceiro (Webstorm, POST em lotes de 10, teto 200); mantém a SUA tabela webstorm_ecom_request (append-only, chave cod_prod, idempotência por watermark updated_at) — ecom offline/erro → o registro não avança o watermark e o próximo sweep reenvia (retry 2×, breaker em 3 falhas). É a referência do padrão de reator (D8).
Write-back de apps (bidirecional pelo lado do cliente): transversal ao shape 1 — o app mobile grava campos editáveis (nomes curtos) via uploadData → connector do hub. É o que faz sulplata/onpetrotrading bidirecionais. O hub trata como escrita nas tabelas que possui (a réplica), com a regra de que esses campos nunca são sobrescritos pelo push do X-Adm.
Push FCM (efeito específico do navarro_app): permanece no integrador POR ORA (D8). No alvo, é um reator como o proc-thoms — consome os eventos (PNOTAI/PBLDI) do banco e empurra o FCM. A extração é deferida até o proc-thoms provar o padrão de reator.
-
Detalhes de segundo nível (defaults propostos — abertos a veto) Recepção nos verticais (não no hub): cada vertical que recebe de fora faz dedupe + envelope de status via xadm-commons, na sua porta. Reusa o modelo de 2 fases dos -xls de hoje (recebe 202 → processa → status). Payload podre não bloqueia a fila; a UI do vertical permite REPLAY. O contrato exato entra na spec de cada vertical. Consumo nos reatores: poll (
@Scheduled, sweep ~60s) + webhook poke sobre o Postgres compartilhado (PowerSync/NOTIFY descartados — decisão 0003 dowebstorm-ecom). O reator lê a réplica por uma query com watermark (updated_at> último envio com sucesso); a tabela de status/auditoria própria garante idempotência (não reenvia a mesma versão) + retry com backoff + circuit-breaker. Cair/voltar re-sincroniza pelo próprio watermark; destino externo offline → o registro não avança o watermark e o próximo sweep reenvia. Números da referência (proc-thoms): lote 10 (teto 200), pacing 1s, breaker em 3 falhas, retry 2× no cliente. Auth: o write-back app→hub é por JWT (JWKS do auth). A ingestão externa de cada vertical autentica o transporte da fonte (token do serviço, comparação em tempo constante, fail-closed — padrão engenharia/seguranca). Não há canal hub↔serviço a autenticar — não existe dispatch. Orçamento de conexões por cliente: o Postgres é COMPARTILHADO — pools Hikari deliberadamente pequenos (hub 10, vertical/reator 5, minimum-idle 2). Subir pool é decisão de operação medida. Schema/Flyway: um dono por tabela (D4). Cada peça roda o seu Flyway (histórico separado, como o -xls já faz). Ownership disjunto no banco compartilhado, já em produção hoje. auth / authui ficam fora da reorg — plano de identidade compartilhado (JWKS que o PowerSync valida, admin, SSO das portas). Ortogonais. Custo do PowerSync-em-Java (só o cliente do ERP, shape 3 — os reatores NÃO usam PowerSync): o consumo em Java do lado do X-Adm exige a bridge Kotlin (PsBridge.kt, ~190 linhas, basepoc-ps-java) + cuidados de config (streams edition: 3, callback sempre-200, JWKS inline > jwks_uri). Ficar de olho no repo do PowerSync — deve sair uma API Java pura que elimina a bridge. Ainda não há módulo da casa que encapsule essa bridge (oxadm-comum-powersynccobre só admin/reset, não consumo). -
Roadmap de migração (incremental, sem big-bang) Ordenado do menor risco/maior aprendizado para o maior. Progresso (2026-08): os passos 1–3 já saíram em boa parte —
xadm-commonspublicado (8 módulos), proc-thoms em produção, int-pied capturando da API PIED real; o mecanismo de reator emergiu como poll+poke (não PowerSync). Segue o plano com o estado anotado: -
[feito, com correção de rota] Extrair a biblioteca da casa do plumbing duplicado. Saiu como
xadm-commonsmulti-módulo (não obi-commonsúnico da visão original), Maven registry do Forgejo + CI de release (D11). A "base de reator (consumidor PowerSync)" prevista NÃO foi construída — o reator provou-se hand-rolled sobre poll+poke, então não há o que extrair aí ainda. - [feito, em prod] thoms (reator proc-thoms). Exercitou o shape 4 (egress) + transporte de saída (outbox+agente → hub → réplica) + o consumo — que ficou poll+poke, não PowerSync (decisão 0003 do
webstorm-ecom). É a REFERÊNCIA do padrão de reator (D8): saiu fino, mas o "fino" vem do banco-fila, não de lib. - [em curso] PIED (novo vertical pied.
). Versão complexa/Caminho 2 — shape 3 inteiro (porta própria: webhook + poll → estado-desejado + status → cliente Java no ERP). Captura da API PIED real validada; Fase 2 (push ao ERP) atrás de gate manual. O poc-pied-simples é referência do Caminho 1; o poc-ps-java é a base entregue aos devs do ERP. - Evoluir o integrador → hub ESTREITO: manter o push X-Adm + connector PowerSync + write-back; remover/não-migrar o que não é a cópia fiel. O push FCM PERMANECE no integrador por ora (D8) — sua extração para reator é um passo posterior, disparado quando o padrão de reator (passo 2) estiver provado.
- (removido) ~~Migrar os -xls pra trás do hub~~ — os -xls ficam como estão (standalone excel.
, D12→B). Em vez disso: emagrecê-los oportunisticamente, extraindo o plumbing pro xadm-commons, SEM mudar a porta. Dependências do lado do X-Adm (time do ERP): o agente .jar (outbox → nuvem, roteia por tipo) e o cliente PowerSync (espelho → ERP) são genéricos e entregues pelo time do X-Adm. poc-ps-java é a base do cliente PowerSync.
Cada passo é entregável sozinho e não bloqueia os apps existentes.
- Questões — status
Resolvidas
Hub estreito: o hub é só a cópia fiel do X-Adm (in/out) + connector PowerSync; NÃO é porta de ingress. Ingress não-xadm é do vertical. (D2)
Packaging dos reatores: microserviço Micronaut fino que consome por poll (
@Scheduled) + webhook poke — PowerSync e NOTIFY descartados; não FaaS, não plugin-no-hub; engine escrita à mão (não há base de reator). proc-thoms em produção prova. (D8,webstorm-ecomADR 0003) Gatilho dos reatores (mecânica fina): DECIDIDO no build da referência — poll (sweep ~60s) como piso garantido + webhook poke como camada de latência; "banco compartilhado = a fila". NOTIFY/PowerSync avaliados e rejeitados. (D8) Idempotência / chaves naturais por fonte: VALIDADO contra a API PIED real (produto=product_code, cliente=documento, pedido=code; saída X-Adm=CgcCpf/CodigoAlt/xPed/(xPed,CodigoAlt); thoms=cod_prod, EAN só correlação). A lição "a doc da fonte mentiu sobre o filtro de delta" confirmou-se (apiSentnão existe;lastUpdateAftersó vale em pedidos) e está tratada: full-rescan de produto/cliente por rodada, janela sobreposta em pedido, upsert idempotente + reconciliação. (4.2, int-pied ADR 0001) Biblioteca da casa — mecânica de release: FEITO —xadm-commonsmulti-módulo, publicação via Maven registry do Forgejo + CI de release, SemVer por-módulo (tag<mod>-vX.Y.Z). (D11, ADR 0019) FCM: permanece no integrador por ora; extração deferida até valer a pena um 2º reator/notificador. (D8) XLS: segue standalone (excel.), não migra pra trás do hub. (D12→B) Egress do thoms (estado de envio): tabela própria do reator ( webstorm_ecom_request, chavecod_prod, idempotência por watermarkupdated_at), NÃO coluna na réplica, NÃOthoms_envio/EAN. (4.2) Transporte X-Adm ↔ nuvem: padronizado (saída = push tempo real / outbox+agente roteado; entrada = espelho + PowerSync). (D7) Nomenclatura das tabelas: sem prefixo = dado do ERP (sincroniza);= derivado (sincroniza); = interno (não sincroniza). (4.2)
Ainda abertas
Multi-tenancy: fica só para o futuro. D2 fixa 1-deploy-por-cliente; revisitar apenas se o número de clientes explodir.
NFRs por shape: sendo fechados por-app como previsto (proc-thoms e int-pied já têm os seus — lote/pacing/breaker; poll 6h/full-rescan). Falta só consolidar a nota geral (volumetria, SLA de entrega do thoms, retenção do storage) quando cada spec estabilizar.
Dados pessoais na réplica (LGPD): CPF/CNPJ trafega confirmadamente (int-pied usa documento como chave). Stub aberto em Dados pessoais (LGPD) — a definir; a política (retenção, acesso, logs, quem acessa as consoles via SSO) é do usuário/jurídico.
Base de reator compartilhada: NÃO existe nem está planejada no xadm-commons; cada reator é hand-rolled. Decidir se vale extrair (sweep+poke+watermark) quando surgir o 2º reator — não antes (evitar abstração prematura sobre 1 exemplo).
- Glossário
Hub: a cópia fiel do banco do X-Adm por cliente (int.
.xadm.biz) — recebe o push do X-Adm (réplica) e dado bruto do ERP, é o connector do PowerSync (apps + cliente do ERP) e aplica os write-backs. NÃO é porta de ingress de relatório/fonte externa. O integrador estreitado. Vertical de integração: app standalone com PORTA PRÓPRIA que recebe de fora (relatório/arquivo, webhook/poll), faz o específico e é dono das suas tabelas no banco do cliente (owner-writes). Fino via xadm-commons. Ex.: excel. , pied. . Reator: app standalone fino que NÃO recebe de fora — consome o banco do cliente por poll ( @Scheduled) + webhook poke (não PowerSync), é dono da sua tabela de status/auditoria (idempotência por watermark), e empurra pra fora (API de terceiro, push). Ex.: proc-thoms (em prod). FCM (navarro) será um reator, mas fica no integrador por ora (D8). Barramento: o Postgres por cliente — onde todas as peças daquele cliente escrevem (owner-writes) e leem. É o ponto de integração, no lugar de um dispatcher no hub. xadm-commons: a biblioteca da casa, multi-módulo (8 libs focadas:xadm-seguranca,xadm-comum-web,xadm-mensageria,xadm-comum-util,xadm-comum-storage,xadm-comum-teste,xadm-comum-powersync[admin/reset],xadm-ingestion-core[seam SPI]) — plumbing genérico, ZERO domínio, cada consumidor pina o que usa. Publicada no Maven registry do Forgejo (SemVer por-módulo). Pilar do desenho (D11). NÃO é um app. Não inclui base de reator nem bridge PowerSync Kotlin. Owner-writes: invariante de que cada tabela tem um dono (roda o Flyway e escreve). Hub = réplica + request; vertical = suas tabelas; reator = sua tabela de status. Estado-desejado (sem prefixo): tabelas na nuvem que representam o que deveria estar no X-Adm; consumidas pelo cliente Java do ERP via PowerSync, com máquina de status por linha. São dado do ERP → sem prefixo (4.2). zimrtmu.exe: o runtime do ZIM chamado pelo cliente Java do lado do ERP para gravar de fato no X-Adm as linhas PENDENTE do estado-desejado (o "importa no ERP" do shape 3). Shape: a forma do fluxo de dados (1 push X-Adm, 2 batch arquivo, 3 entrada→ERP, 4 push→API externa). A unidade de organização. PowerSync bidirecional: o mesmo PowerSync (connector no hub) serve saída (X-Adm→apps) e entrada (cloud→ERP via cliente Java + write-back). Os reatores NÃO usam PowerSync — consomem o banco por poll+poke. App de cliente: app que o usuário final usa (mobile, BI); consome via PowerSync. Não é integração.