Catalisa.
Building blocks/DecisãoProdução

Decision Engine

Regras de negócio em tabelas DMN, versionadas e alteradas sem deploy

25
Endpoints
6
Entidades
1
Provedores
Tenant
Escopo
3005
Porta

A sua política de crédito para de ser um `if` escondido no código e vira uma tabela versionada que o analista de risco lê, revisa e publica. Mudar a regra deixa de ser um deploy e passa a ser uma publicação.

Para quem é
  • Fintechs e financeiras cuja política de crédito muda mais rápido que o ciclo de release do time
  • Seguradoras e operadoras de saúde com tabelas de aceitação e precificação que mudam por norma
  • Times de produto que precisam provar a um auditor qual regra rodou em qual data
Substitui
  • Licença de um BRMS comercial (IBM ODM, FICO Blaze Advisor) para o caso de tabelas de decisão
  • Operação própria de um cluster Drools ou Camunda só para avaliar regras
  • Blocos de `if/else` de política de negócio espalhados pelo código da aplicação
O que não é
  • Um orquestrador de esteira — ele não busca dado em bureau nem chama outro serviço (isso é o Decision Platform)
  • Um motor de workflow ou BPM com tarefas humanas, timers e compensação
  • Um modelo estatístico ou de machine learning; ele executa regras determinísticas que você escreve

01Resumo executivo

O Decision Engine guarda as regras de negócio da sua empresa como tabelas de decisão em vez de código. Você descreve a política — "score acima de 700 e renda acima de 5 mil aprova até 20 mil" — como uma tabela com linhas e colunas, versiona essa tabela, publica a versão e chama uma API para obter a resposta.

Na prática isso muda quem manda na regra. Hoje, mudar um limite de crédito costuma significar abrir um chamado, esperar um desenvolvedor, esperar um code review e esperar a próxima janela de deploy. Com o Decision Engine, o analista de risco altera a tabela, cria a versão 1.4.0, publica, e a próxima requisição já usa a regra nova — sem que uma linha do seu aplicativo mude.

Está em produção desde março de 2026, é usado pelo Decision Platform como motor de avaliação e pelo Feature Flags para resolver pertencimento a segmentos.

AtributoValor
Identificadordecision-engine
CategoriaDecisão
EscopoTenant (exige organizationId no token)
Porta (standalone)3005
Path alias@decision-engine
Prefixo HTTP/decision-engine
Schema no bancodecisions
StatusProdução desde 2026-03
Depende dePostgreSQL, runner DMN (DMN_ENGINE_URL), IAM

02O problemanegócio

O cenário. Uma financeira tem uma política de crédito. Essa política é um conjunto de faixas: score, renda, comprometimento, produto, canal. Ela muda o tempo todo — por decisão comercial, por pressão de inadimplência, por mudança de norma. E ela precisa ser explicável, porque alguém um dia vai perguntar por que aquele cliente foi negado.

O que trava hoje.

  • A regra vive no código, então quem a muda é o desenvolvedor. O analista de risco, que é quem entende a política, não consegue nem ler o if aninhado que a implementa. A informação passa por tradução, e tradução perde coisa.
  • Mudar a regra exige deploy. Um ajuste de faixa entra na fila de release junto com refatoração de banco e correção de bug. O tempo entre decidir e valer não é técnico, é de processo — e é medido em semanas.
  • Não dá para dizer qual regra rodou. Seis meses depois, com dez deploys no meio, responder "qual era a política no dia 12 de março" significa arqueologia de Git. Se o cálculo mudou de lugar, nem isso resolve.
  • Testar a política nova é caro. Sem separação entre a regra e o aplicativo, comparar a política nova contra a antiga exige subir dois ambientes.
  • Adotar um BRMS comercial troca um problema por outro. Motores corporativos resolvem a governança, mas trazem licença por cotação, ciclo de implantação longo e um formato de regra que só roda dentro deles.

O custo de não resolver. O custo direto é a política que continua errada enquanto espera a fila de deploy — cada dia de atraso em apertar um critério de risco é carteira originada com a regra antiga. O custo indireto é a auditoria: sem log de qual versão avaliou qual proposta, a resposta ao regulador vira reconstrução manual. E há um custo regulatório concreto, porque o art. 20 da LGPD garante ao titular o direito de solicitar revisão de decisões tomadas unicamente com base em tratamento automatizado, incluindo as que definem perfil de crédito — e revisar exige saber o que rodou.


03Proposta de valornegócio

AntesDepois
A regra é um if que só o desenvolvedor lêA regra é uma tabela DMN que o analista de risco lê
Mudar a política entra na fila de deployMudar a política é POST .../versions + POST .../publish
"Qual regra rodou naquele dia?" é arqueologia de GitToda execução grava versão, entrada, saída e tempo
A regra nova substitui a antiga e someVersões coexistem: rascunho, publicada e arquivada
Trocar de motor significa reescrever tudoO XML é DMN da OMG e roda em qualquer motor conforme

A regra sai do código. Uma tabela de decisão tem entradas, saídas e linhas. Quem escreve política de crédito já pensa assim — a tabela é o formato natural, não uma tradução dele.

Publicar é o gatilho, não o deploy. Uma versão nasce DRAFT e não executa. Ela só passa a valer quando alguém com a permissão DECISION_VERSIONS_PUBLISH a publica. A separação entre escrever e valer é o controle que uma política de crédito exige.

Toda execução deixa rastro. Cada chamada grava um ExecutionLog com a entrada, a saída, o tempo em milissegundos, quem executou e — o que mais importa — qual versão foi usada. Responder "por que este cliente foi negado" vira uma consulta.

O DMN inválido não entra. Ao criar uma versão, o serviço valida o XML contra o runner DMN antes de gravar. Você descobre o erro na hora de subir a regra, não na primeira proposta real.

O formato é aberto. DMN é um padrão da OMG, na versão 1.5 desde agosto de 2024. A tabela que você escreve aqui é um XML padronizado — ele sai daqui inteiro e roda em outro motor conforme. Isso não é detalhe técnico: é a diferença entre depender de um fornecedor e depender de um formato.


04Casos de uso reaisnegócio

Caso 1 — Uma financeira aperta o critério de risco na sexta e a regra vale na sexta Cenário ilustrativo

Contexto. Financeira de crédito pessoal com originação digital, cerca de 40 mil propostas por mês. A política de aprovação tem 6 entradas (score de bureau, renda declarada, renda estimada, comprometimento, produto, canal) e 30 linhas.

A dor. A política morava numa classe de serviço com condicionais aninhadas. Toda mudança de faixa passava por chamado, desenvolvimento, revisão e janela de deploy — na prática, de dez a quinze dias úteis entre o comitê de risco decidir e a regra valer. Quando a inadimplência de uma safra subia, a financeira originava mais duas semanas com o critério que já sabia estar errado.

A solução com o BB. A política vira uma decisão politica-credito-pessoal dentro do projeto credito. Cada revisão do comitê vira uma versão em semver: POST /decision-engine/api/v1/decisions/:decisionId/versions cria a 2.3.0 em DRAFT, o time de risco a valida contra uma amostra pela rota POST /decision-engine/api/v1/versions/:versionId/execute — que executa uma versão específica sem afetar quem está em produção — e POST /decision-engine/api/v1/versions/:versionId/publish faz ela valer. O aplicativo continua chamando POST /decision-engine/api/v1/decisions/:decisionId/execute, sempre sem saber que versão está no ar.

O resultado. O intervalo entre decidir e valer cai de semanas para o tempo de escrever a tabela e revisar. E o ExecutionLog responde, para qualquer proposta, com qual versão ela foi avaliada.

Caso 2 — Uma auditoria pergunta por uma negativa de dezoito meses atrás Cenário ilustrativo

Contexto. Operação de crédito consignado que precisa responder a uma reclamação formal sobre negativa de proposta.

A dor. A política tinha mudado sete vezes desde então. Reconstruir a regra vigente naquela data exigia achar o commit certo, entender o que mais tinha mudado junto e confiar que o comportamento em produção era mesmo o do código. Nenhuma dessas três etapas dá uma resposta que se sustenta num processo.

A solução com o BB. GET /decision-engine/api/v1/execution-logs?filter[decisionId]=...&filter[from]=...&filter[to]=... devolve as execuções da janela. Cada log traz o versionId usado, a entrada exata e a saída. GET /decision-engine/api/v1/versions/:versionId devolve o XML DMN daquela versão — a tabela que de fato avaliou aquela proposta, não uma reconstrução dela.

O resultado. A resposta deixa de ser uma reconstrução e passa a ser um registro. E como a tabela DMN é legível, ela entra no processo como anexo em vez de como código-fonte.

Caso 3 — Uma seguradora troca regra de aceitação por produto sem multiplicar código Cenário ilustrativo

Contexto. Seguradora com 12 produtos, cada um com sua tabela de aceitação e seus critérios de recusa automática.

A dor. Cada produto novo significava mais um ramo de condicional no mesmo serviço. O acoplamento crescia, e mudar a regra do produto A quebrava o produto B com uma frequência desconfortável.

A solução com o BB. Cada produto vira uma Decision própria dentro do projeto aceitacao, com sua chave (aceitacao-vida, aceitacao-auto). As DecisionTag marcam quais são as tabelas reguladas e quais são comerciais, e GET /decision-engine/api/v1/decisions?filter[tagId]=... lista por corte. O aplicativo resolve a chave do produto e chama a decisão correspondente.

O resultado. Regras de produtos diferentes deixam de compartilhar código. Produto novo é um POST, não uma refatoração.

Caso 4 — Por que um padrão aberto importa na hora de trocar de fornecedor Referência de mercado

Contexto. O DMN é mantido pela OMG, o mesmo consórcio do BPMN e do UML, e está na versão 1.5 desde agosto de 2024. A especificação define uma notação gráfica, um modelo de tabela de decisão e a linguagem de expressão FEEL, com representação em XML pensada explicitamente para intercâmbio entre organizações e ferramentas.

A dor do mercado. Boa parte dos motores de regras de mercado usa formato proprietário. O GoRules, por exemplo, é tecnicamente muito bom e tem motor open source em nove linguagens, mas o modelo de decisão nativo dele é próprio. Sair de um formato proprietário significa reescrever a política inteira, e é exatamente por isso que a migração de BRMS raramente acontece mesmo quando o contrato incomoda.

Como a Catalisa endereça. O que o Decision Engine armazena em DecisionVersion.content é o XML DMN cru. Ele entra por API e sai por API pela rota GET /decision-engine/api/v1/versions/:versionId, que devolve o conteúdo completo. Uma decisão de sair da Catalisa custa o tempo de baixar os XMLs, não o de reescrever a política.

O resultado. O custo de troca é uma escolha nossa, não uma consequência de arquitetura. Isso é defensável em due diligence de um jeito que "temos API de exportação" não é.


05Mercado e diferenciaisnegócio

Panorama. O mercado de motores de regras se divide em três faixas. Na de cima estão as suítes corporativas — IBM ODM, FICO Blaze Advisor — que entregam governança completa, simulação e ferramenta de autoria para o analista, com preço por cotação e ciclo de implantação de projeto. No meio estão os motores open source operáveis — Camunda e Drools, este último hoje sob a Apache Software Foundation com licença Apache 2.0 — que resolvem a execução e transferem a operação, o versionamento e o multi-tenant para você. Embaixo estão as bibliotecas — json-rules-engine, GoRules — que embarcam no seu processo e não têm opinião sobre governança.

O Decision Engine não tenta ser o motor mais rápido nem o modelador mais bonito. Ele pega um motor DMN maduro e coloca em volta dele o que falta para uso em produção regulada: versão, publicação, log por execução, isolamento por organização e autorização — tudo com o mesmo token dos outros 31 building blocks.

CritérioCatalisa Decision EngineCamundaDrools (Apache KIE)IBM ODMjson-rules-engine
Formato da regraDMN (XML da OMG)DMN (XML da OMG)DRL e DMNProprietário e DMNJSON próprio
PreçoPrecificação em definiçãoCotação (tabela não publica valores)Licença zero, você operaCotação, não públicoGratuito (ISC)
Versionamento e publicaçãoNativo, com semver e estadosVia deploy de definiçãoVocê constróiNativoNão existe
Log por execuçãoNativo, com versão usadaVia histórico da plataformaVocê constróiNativoNão existe
Multi-tenantNativo, por organizationId do tokenVocê modelaVocê constróiVocê modelaNão existe
Autorização por permissãoNativa, vocabulário compartilhadoExternaExternaNativaNão existe
Modelador visual de tabelaNão (ver §15)Sim, referência de mercadoParcialSimNão
Simulação e teste em loteNão (ver §15)SimParcialSimNão
Você opera o motorNãoSim, salvo SaaSSimSimN/A

Nossos diferenciais

  1. A governança vem junto, e não em volta. Versão, publicação, arquivamento e log de execução são endpoints do produto. Quem adota Camunda ou Drools recebe um avaliador de regras excelente e ainda precisa construir essas quatro coisas — que são justamente as que a auditoria pede.
  2. O tenant é do token, não do modelo de dados. Toda rota exige organizationId no JWT e todo repositório filtra por ele. Num motor genérico, separar clientes é modelagem sua, e modelagem sua é onde vaza.
  3. O formato é padrão, então a saída é barata. A regra é XML DMN da OMG, extraível por API. Isso é difícil de copiar não por ser tecnicamente complexo, mas porque o modelo de negócio da maioria dos concorrentes depende do custo de troca.
  4. O motor é substituível por trás. O DmnProxyService fala com um runner DMN por HTTP através de uma interface de três operações — executar, validar e checar saúde. Trocar o runner não toca nenhuma rota, nenhum dado e nenhum cliente.

Quando escolher o concorrente. Se o seu analista de negócio precisa desenhar a tabela numa interface gráfica, o Camunda Modeler é hoje a melhor ferramenta de autoria DMN do mercado e o Decision Engine não tem editor visual (§15) — o caminho honesto é modelar no Camunda Modeler e subir o XML aqui, mas se o seu time não aceita esse passo, use a plataforma deles inteira. Se você precisa de simulação em lote, comparação A/B entre políticas e um ambiente de teste governado com aprovação formal, IBM ODM e FICO Blaze Advisor entregam isso há duas décadas e nós não entregamos. Se a sua regra é simples, roda dentro de um único serviço Node e não precisa de versionamento nem auditoria, o json-rules-engine é gratuito, tem 17 kB e resolve em uma tarde — adotar um building block inteiro seria excesso. E se você já opera JVM e tem time para isso, o Drools sob a Apache 2.0 não custa licença nenhuma.


06Modelo de cobrança e ROInegócio

Unidade de cobrança. Precificação em definição. Quando definida, a unidade natural é a execução de decisão — é o que o cliente entende, é o que escala com o uso e é o que aparece no ExecutionLog.

O que dispara custo.

DriverPor que ele importa
Execuções por mêsÉ a chamada ao runner DMN, o custo variável real
Decisões publicadasProxy do tamanho da operação de política
Retenção do log de execuçãoCada execução grava uma linha com entrada e saída em JSON; auditoria longa custa armazenamento

Comparação de custo. Cenário: operação com 500 mil execuções de decisão por mês e 20 decisões publicadas.

Catalisa Decision EngineCamunda Self-ManagedDrools (Apache KIE)IBM ODM
LicençaPrecificação em definiçãoCotação — a tabela pública não publica valoresZero (Apache 2.0)Cotação, não público
Quem opera o motorCatalisaVocêVocêVocê ou parceiro
Versionamento e logIncluídoVocê constróiVocê constróiIncluído
Multi-tenantIncluídoVocê modelaVocê constróiVocê modela

Comparação estruturada em 2026-08-16 a partir das páginas públicas dos fornecedores. Nenhum dos concorrentes comerciais publica preço — Camunda e IBM vendem por cotação. Não estimamos valores que não conseguimos verificar. Consulte cada fornecedor na data da sua análise.

ROI. A conta que fecha não é a de licença — o Drools é gratuito e vai ganhar de qualquer coisa nessa linha. O retorno está em dois lugares. O primeiro é o que você não constrói: versionamento, publicação, log por execução e multi-tenant sobre um motor cru são um projeto de meses, e é um projeto que precisa ficar certo porque ele é o que a auditoria olha. O segundo é o intervalo entre decidir e valer: numa carteira que origina 40 mil propostas por mês, cada semana de atraso em apertar um critério de risco é originação inteira com a regra que o comitê já decidiu abandonar. Esse número é seu e você sabe calculá-lo melhor que nós.


07Arquitetura

                          HTTP  (Bearer JWT do IAM)
                                    │
  ┌─────────────────────────────────┴────────────────────────────────────────┐
  │ Hono app   basePath('/decision-engine')                                   │
  │                                                                           │
  │  /api/v1/projects         projectsRouter        5 rotas                   │
  │  /api/v1/decisions        decisionsRouter       6 rotas                   │
  │  /api/v1                  versionsRouter        6 rotas                   │
  │  /api/v1/tags             tagsRouter            5 rotas                   │
  │  /api/v1/execution-logs   executionLogsRouter   3 rotas                   │
  │  /health                  sonda simples                                   │
  │                                                                           │
  │  Toda rota: authMiddleware → requirePermission(P) → requireOrganization    │
  └─────────────────────────────────┬────────────────────────────────────────┘
                                    │  Zod parse → ResultAsync<T, AppError>
  ┌─────────────────────────────────┴────────────────────────────────────────┐
  │ services/                                                                 │
  │   ProjectService        agrupamento de decisões                           │
  │   DecisionService       ciclo de vida + execute (usa a última PUBLISHED)   │
  │   VersionService        semver, publish, archive, execute de versão fixa   │
  │   TagService            rótulos por organização                           │
  │   ExecutionLogService   consulta do rastro                                │
  │   DmnProxyService       ÚNICA porta de saída para o runner DMN            │
  └────────────┬──────────────────────────────────────┬──────────────────────┘
               │                                      │  HTTP
  ┌────────────┴──────────────────┐   ┌───────────────┴──────────────────────┐
  │ repositories/ (Prisma)        │   │ runner DMN  ($DMN_ENGINE_URL)        │
  │   PostgreSQL, schema          │   │   POST /api/v1/execute               │
  │   "decisions"                 │   │   GET  /health                       │
  └───────────────────────────────┘   │   imagem decision-engine-runner      │
                                      │   (Camunda DMN, repositório à parte) │
                                      └──────────────────────────────────────┘

O caminho de uma execução

  POST /decision-engine/api/v1/decisions/:id/execute   { "input": { "age": 25 } }
        │
        ├─ 1. Decision existe nesta organização?         não → 404
        ├─ 2. Decision.status == ACTIVE?                 não → 400
        ├─ 3. Existe DecisionVersion PUBLISHED?          não → 400
        │       └─ pega a MAIS RECENTE publicada
        ├─ 4. DmnProxyService.execute(version.content, input)
        │       └─ POST $DMN_ENGINE_URL/api/v1/execute
        ├─ 5. grava ExecutionLog (entrada, saída, trace, tempo, quem)
        ├─ 6. publica evento decision-engine.decision.executed
        └─ 200 { output, trace, executionTimeMs, versionUsed }

Decisões não óbvias.

  • O motor DMN é um processo separado, alcançado por HTTP. O DmnProxyService é a única porta de saída, com três operações: execute, validateDmn e healthCheck. O runner é a imagem decision-engine-runner, construída a partir de um repositório próprio (catalisaio/decision-engine-runner) e publicada no GHCR pelo workflow build-dmn-engine.yml. O motivo é isolamento de tecnologia: a avaliação DMN madura vive na JVM, o resto da plataforma é Bun e TypeScript, e essa fronteira mantém as duas coisas independentes. O custo aceito é uma chamada de rede por execução.
  • O runner não conhece organização, e por isso não recebe nada persistido. Cada execução envia o XML DMN inteiro no corpo da requisição, junto com a entrada. O runner é stateless por construção — não há deploy de modelo, não há catálogo dentro dele, não há estado para vazar entre clientes. O custo é trafegar o XML a cada chamada; o ganho é que o isolamento entre tenants nunca depende do runner.
  • A validação do DMN acontece na criação da versão, não na execução. VersionService.createVersion chama validateDmn antes de gravar. Um XML quebrado falha para quem está subindo a regra, e não para o cliente final na primeira proposta. O trade-off é uma ida ao runner a cada versão criada, o que é irrelevante na frequência em que isso acontece.
  • Executar uma decisão usa sempre a última versão publicada; executar uma versão usa aquela versão. São dois endpoints com propósitos diferentes. POST /decisions/:id/execute é o caminho de produção — o chamador não sabe nem quer saber a versão. POST /versions/:id/execute é o caminho de teste — você escolhe a versão de propósito, inclusive uma que acabou de publicar, para comparar contra a anterior.
  • Só decisão ACTIVE executa. Uma Decision nasce DRAFT e precisa ser promovida a ACTIVE por PATCH. É um segundo interruptor, além da publicação da versão: escrever a tabela, publicar a versão e ligar a decisão são três atos deliberados. Isso irrita na primeira integração e evita o acidente na centésima.
  • Exclusão é lógica. Projetos, decisões, versões e tags usam deletedAt. O log de execução aponta para a versão, e a versão precisa sobreviver à remoção para que a auditoria continue reproduzível.

Monolito vs. standalone. Em monolito, o container TypeDI resolve os serviços por chamada direta. Em standalone — o modo usado em produção — o serviço sobe na porta 3005 com MODULE_SELF=decision-engine. Em qualquer um dos modos o runner DMN é sempre alcançado por HTTP: o DMN_ENGINE_URL é obrigatório nos dois. Outros building blocks que só precisam avaliar um DMN sem persistir nada usam a facade DecisionEngineFacadeToken, que tem implementação local e remota.


08Conceitos e modelo de dados

Glossário

TermoSignifica
DMNDecision Model and Notation, padrão da OMG para modelar decisões. A regra é um XML.
FEELFriendly Enough Expression Language, a linguagem de expressão do DMN. É o que você escreve numa célula: >= 18, "adult".
Tabela de decisãoO <decisionTable> do DMN: colunas de entrada, colunas de saída, uma linha por regra.
Hit policyComo o motor resolve quando mais de uma linha casa. FIRST devolve a primeira; há outras no padrão.
ProjectAgrupamento de decisões. Existe para organizar, não para isolar — o isolamento é por organização.
DecisionA decisão de negócio, com chave estável (politica-credito-pessoal). É o que o seu aplicativo referencia.
DecisionVersionUma versão do XML DMN daquela decisão, em semver. É o que de fato executa.
TagRótulo colorido por organização, para cortar o catálogo de decisões.
ExecutionLogO registro de uma execução: entrada, saída, tempo, quem executou e qual versão.

Modelo de dados — schema decisions no PostgreSQL.

Modelo PrismaTabelaPropósitoCampos-chave
DecisionProjectdecisions.decision_projectsAgrupa decisõeskey único por organização, ownerId, deletedAt
Decisiondecisions.decisionsA decisão de negóciokey único por organização, projectId, status, deletedAt
DecisionVersiondecisions.decision_versionsO XML DMN versionadocontent (texto), version único por decisão, status, publishedAt
DecisionTagdecisions.decision_tagsRótulo por organizaçãoname único por organização, color (hex)
DecisionTagLinkdecisions.decision_tag_linksLiga decisão e tagChave composta (decisionId, tagId)
ExecutionLogdecisions.execution_logsRastro de execuçãodecisionId, versionId, input, output, executionTimeMs, executedBy

Enumerações

EnumValores
DecisionStatusDRAFT · ACTIVE · ARCHIVED
VersionStatusDRAFT · PUBLISHED · ARCHIVED

Ciclo de vida de uma decisão e de suas versões

  Decision                              DecisionVersion
  ────────                              ───────────────

   criação                               POST .../versions
      │                                        │
      ▼                                        ▼
  ┌───────┐                              ┌─────────┐
  │ DRAFT │                              │  DRAFT  │  não executa
  └───┬───┘                              └────┬────┘
      │ PATCH status=ACTIVE                   │ POST /versions/:id/publish
      ▼                                       ▼
  ┌────────┐   executa ────────────▶     ┌───────────┐
  │ ACTIVE │                             │ PUBLISHED │  a mais recente é
  └───┬────┘                             └─────┬─────┘  a que responde por
      │ PATCH status=ARCHIVED                  │        /decisions/:id/execute
      ▼                                        │ POST /versions/:id/archive
  ┌──────────┐                                 ▼
  │ ARCHIVED │                            ┌──────────┐
  └──────────┘                            │ ARCHIVED │  não executa mais
                                          └──────────┘

  Para uma proposta ser avaliada, PRECISA de:
    Decision.status == ACTIVE   E   pelo menos uma DecisionVersion PUBLISHED.
    Falta um dos dois → 400, não 404. É a causa nº 1 de dúvida na integração.

  Publicar não despublica a anterior: várias versões podem estar PUBLISHED ao
  mesmo tempo, e o motor usa a mais recente. Para tirar uma do ar, arquive-a.

09Referência da API

Prefixo: /decision-engine. Em staging, a base é https://decision-engine.bb.stg.catalisa.app.

Todas as rotas abaixo exigem: authMiddleware (Bearer JWT do IAM), o requirePermission indicado na coluna, e requireOrganization — token sem organizationId recebe 403 antes de a regra de negócio rodar.

Os corpos aceitam duas formas: JSON:API ({"data":{"attributes":{...}}}) ou o objeto direto ({...}). O código faz body.data?.attributes ?? body.

Projetos — /decision-engine/api/v1/projects

MétodoRotaDescriçãoPermissão
POST/decision-engine/api/v1/projectsCria projetoDECISION_PROJECTS_CREATE
GET/decision-engine/api/v1/projectsLista projetos, paginadoDECISION_PROJECTS_READ
GET/decision-engine/api/v1/projects/:projectIdBusca projetoDECISION_PROJECTS_READ
PATCH/decision-engine/api/v1/projects/:projectIdAtualiza nome e descriçãoDECISION_PROJECTS_UPDATE
DELETE/decision-engine/api/v1/projects/:projectIdExclusão lógica. Responde 204DECISION_PROJECTS_DELETE

Decisões — /decision-engine/api/v1/decisions

MétodoRotaDescriçãoPermissão
POST/decision-engine/api/v1/decisionsCria decisão em DRAFTDECISION_VERSIONS_CREATE
GET/decision-engine/api/v1/decisionsLista decisões, paginadoDECISION_VERSIONS_READ
GET/decision-engine/api/v1/decisions/:decisionIdBusca decisãoDECISION_VERSIONS_READ
PATCH/decision-engine/api/v1/decisions/:decisionIdAtualiza nome, descrição, status e tagsDECISION_VERSIONS_CREATE
DELETE/decision-engine/api/v1/decisions/:decisionIdExclusão lógica. Responde 204DECISION_PROJECTS_DELETE
POST/decision-engine/api/v1/decisions/:decisionId/executeExecuta a última versão publicadaDECISION_EXECUTE

As permissões desta tabela estão transcritas do código, não normalizadas. Repare que criar e atualizar decisão exigem DECISION_VERSIONS_CREATE e que excluir exige DECISION_PROJECTS_DELETE — não há permissão dedicada a decisões. Conceda os papéis com base nesta tabela, não no nome que a permissão sugere.

Filtros de GET /decisions: filter[projectId] (UUID), filter[status] (DRAFT, ACTIVE, ARCHIVED), filter[tagId] (UUID).

Versões — /decision-engine/api/v1

MétodoRotaDescriçãoPermissão
POST/decision-engine/api/v1/decisions/:decisionId/versionsCria versão. Valida o DMN antes de gravarDECISION_VERSIONS_CREATE
GET/decision-engine/api/v1/decisions/:decisionId/versionsLista versões da decisãoDECISION_VERSIONS_READ
GET/decision-engine/api/v1/versions/:versionIdBusca versão. Devolve o XML DMN em contentDECISION_VERSIONS_READ
POST/decision-engine/api/v1/versions/:versionId/publishPublica a versãoDECISION_VERSIONS_PUBLISH
POST/decision-engine/api/v1/versions/:versionId/archiveArquiva a versãoDECISION_VERSIONS_CREATE
POST/decision-engine/api/v1/versions/:versionId/executeExecuta esta versão específicaDECISION_EXECUTE

Filtro de listagem: filter[status] (DRAFT, PUBLISHED, ARCHIVED).

Tags — /decision-engine/api/v1/tags

MétodoRotaDescriçãoPermissão
POST/decision-engine/api/v1/tagsCria tagDECISION_TAGS_MANAGE
GET/decision-engine/api/v1/tagsLista tags, paginadoDECISION_TAGS_MANAGE
GET/decision-engine/api/v1/tags/:tagIdBusca tagDECISION_TAGS_MANAGE
PATCH/decision-engine/api/v1/tags/:tagIdAtualiza nome e corDECISION_TAGS_MANAGE
DELETE/decision-engine/api/v1/tags/:tagIdRemove tag. Responde 204DECISION_TAGS_MANAGE

Não há permissão de leitura separada para tags: ler exige a mesma DECISION_TAGS_MANAGE que escrever.

Logs de execução

MétodoRotaDescriçãoPermissão
GET/decision-engine/api/v1/execution-logsLista todos os logs da organizaçãoDECISION_LOGS_READ
GET/decision-engine/api/v1/execution-logs/:logIdBusca um logDECISION_LOGS_READ
GET/decision-engine/api/v1/decisions/:decisionId/execution-logsLogs de uma decisãoDECISION_LOGS_READ

Filtros: filter[decisionId], filter[versionId] (UUID), filter[from] e filter[to] (data ISO-8601).

O executionLogsRouter está montado em dois pontos (/api/v1/execution-logs e /api/v1), o que faz caminhos equivalentes responderem sob outros prefixos. Use apenas as três rotas da tabela — são as que a documentação sustenta.

Saúde

MétodoRotaDescrição
GET/decision-engine/healthSonda de vida do serviço, com versão do build. Pública.

POST /decision-engine/api/v1/decisions/:decisionId/versions

Cria uma nova versão da decisão. O XML DMN é validado contra o runner antes de ser gravado.

Request

{
  "version": "1.0.0",
  "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?><definitions ...>...</definitions>"
}
CampoTipoObrigatórioDescrição
versionstringSimSemver estrito, no formato \d+\.\d+\.\d+. Único por decisão.
contentstringSimO XML DMN completo. Mínimo 1 caractere; validado no runner.

Resposta 201

{
  "data": {
    "type": "decision-versions",
    "id": "8f1c...",
    "links": { "self": "/api/v1/versions/8f1c..." },
    "attributes": {
      "decisionId": "3a2b...",
      "version": "1.0.0",
      "status": "DRAFT",
      "publishedAt": null
    }
  }
}

Erros

StatusQuando
400version fora de semver, content vazio, ou DMN inválido segundo o runner
403Token sem organizationId ou sem DECISION_VERSIONS_CREATE
404A decisão não existe nesta organização
409Já existe uma versão com esse número nesta decisão

POST /decision-engine/api/v1/decisions/:decisionId/execute

O endpoint de produção. Usa a versão publicada mais recente, sem que o chamador precise saber qual é.

Request

{ "input": { "age": 25, "score": 730, "renda": 5200 } }
CampoTipoObrigatórioDescrição
inputobjectSimMapa livre de variáveis. As chaves precisam casar com os inputExpression da tabela DMN.

Resposta 200

{
  "output": { "result": "adult" },
  "trace": [
    {
      "decisionId": "age_check",
      "decisionName": "Age Check",
      "outcome": { "result": "adult" },
      "rulesEvaluated": [
        { "ruleId": "rule1", "triggered": true, "conditions": [">= 18"], "outcome": {} }
      ]
    }
  ],
  "executionTimeMs": 34,
  "versionUsed": "1.0.0"
}

O trace é a explicação da decisão: quais regras foram avaliadas e qual disparou. É o que responde "por que este cliente foi negado" — guarde-o.

Erros

StatusQuando
400Decision is not active — a decisão não está ACTIVE
400No published version available — nenhuma versão publicada
400DMN execution failed: ... — o runner recusou o XML ou a entrada
403Token sem organizationId ou sem DECISION_EXECUTE
404A decisão não existe nesta organização

Execução que falha não gera ExecutionLog. O registro só é gravado depois que o runner responde com sucesso. Para monitorar taxa de erro, use o log da aplicação e não a tabela — ver §15.


POST /decision-engine/api/v1/versions/:versionId/execute

O endpoint de teste. Executa a versão que você indicar, desde que ela esteja PUBLISHED. Mesmo corpo do anterior.

Resposta 200

{ "output": { "result": "adult" }, "executionTimeMs": 31 }

Repare que esta rota não devolve trace nem versionUsed — você já sabe a versão, e a explicação não é retornada aqui. Se precisa do trace, use a rota da decisão.

Erros

StatusQuando
400Only published versions can be executed — a versão está DRAFT ou ARCHIVED
403A versão pertence a outra organização, ou falta DECISION_EXECUTE
404A versão não existe

10Início rápido

Do zero à primeira decisão avaliada. Os comandos usam staging e a tabela DMN de exemplo é a mesma de scripts/test-dmn-staging.ts.

1. Autenticar no IAM

TOKEN=$(curl -s -X POST https://iam.bb.stg.catalisa.app/iam/api/v1/users/login \
  -H "Content-Type: application/json" \
  -d '{
    "email": "admin@test.com",
    "password": "password123",
    "organizationId": "b0000000-0000-0000-0000-000000000001"
  }' | jq -r .accessToken)

BASE=https://decision-engine.bb.stg.catalisa.app/decision-engine/api/v1

2. Criar o projeto

PROJECT_ID=$(curl -s -X POST "$BASE/projects" \
  -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  -d '{"name":"Crédito","key":"credito","description":"Políticas de crédito"}' \
  | jq -r '.data.id')

A key precisa casar com ^[a-z0-9-]+$ e é única na sua organização.

3. Criar a decisão

DECISION_ID=$(curl -s -X POST "$BASE/decisions" \
  -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  -d "{\"projectId\":\"$PROJECT_ID\",\"name\":\"Checagem de idade\",\"key\":\"checagem-idade\"}" \
  | jq -r '.data.id')

Ela nasce DRAFT. Ainda não executa.

4. Criar a versão com a tabela DMN

cat > /tmp/age-check.dmn <<'DMN'
<?xml version="1.0" encoding="UTF-8"?>
<definitions xmlns="https://www.omg.org/spec/DMN/20191111/MODEL/"
             id="definitions" name="Age Check"
             namespace="http://camunda.org/schema/1.0/dmn">
  <decision id="age_check" name="Age Check">
    <decisionTable id="decisionTable" hitPolicy="FIRST">
      <input id="input1" label="Age">
        <inputExpression id="inputExpression1" typeRef="integer"><text>age</text></inputExpression>
      </input>
      <output id="output1" label="Result" name="result" typeRef="string" />
      <rule id="rule1">
        <inputEntry id="ie1"><text>&gt;= 18</text></inputEntry>
        <outputEntry id="oe1"><text>"adult"</text></outputEntry>
      </rule>
      <rule id="rule2">
        <inputEntry id="ie2"><text>&lt; 18</text></inputEntry>
        <outputEntry id="oe2"><text>"minor"</text></outputEntry>
      </rule>
    </decisionTable>
  </decision>
</definitions>
DMN

VERSION_ID=$(jq -Rs '{version:"1.0.0", content:.}' < /tmp/age-check.dmn \
  | curl -s -X POST "$BASE/decisions/$DECISION_ID/versions" \
      -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" -d @- \
  | jq -r '.data.id')

Se o XML estiver quebrado, você recebe 400 aqui — antes de qualquer proposta real chegar.

5. Publicar a versão e ativar a decisão

curl -s -X POST "$BASE/versions/$VERSION_ID/publish" \
  -H "Authorization: Bearer $TOKEN" | jq '.data.attributes.status'
# "PUBLISHED"

curl -s -X PATCH "$BASE/decisions/$DECISION_ID" \
  -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  -d '{"status":"ACTIVE"}' | jq '.data.attributes.status'
# "ACTIVE"

São dois interruptores separados, e os dois precisam estar ligados.

6. Executar

curl -s -X POST "$BASE/decisions/$DECISION_ID/execute" \
  -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  -d '{"input":{"age":25}}' | jq
{
  "output": { "result": "adult" },
  "trace": [ { "decisionId": "age_check", "decisionName": "Age Check", "...": "..." } ],
  "executionTimeMs": 34,
  "versionUsed": "1.0.0"
}

7. Conferir o rastro

curl -s "$BASE/decisions/$DECISION_ID/execution-logs" \
  -H "Authorization: Bearer $TOKEN" | jq '.data[0].attributes | {versionId, input, executionTimeMs}'

Credenciais de staging, conforme AMBIENTES.md. Nunca use credencial de produção em documentação ou script de exemplo.


11Receitas

Trocar a política sem derrubar quem está integrado

O objetivo é substituir a regra vigente com verificação antes de ela valer.

# 1. Nova versão a partir do DMN revisado — nasce DRAFT, não afeta produção
VERSION_ID=$(jq -Rs '{version:"1.1.0", content:.}' < politica-revisada.dmn \
  | curl -s -X POST "$BASE/decisions/$DECISION_ID/versions" \
      -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" -d @- \
  | jq -r '.data.id')

# 2. Publicar. A partir daqui ela É a mais recente publicada.
curl -s -X POST "$BASE/versions/$VERSION_ID/publish" -H "Authorization: Bearer $TOKEN"

# 3. Conferir contra um caso conhecido, agora com a versão fixada
curl -s -X POST "$BASE/versions/$VERSION_ID/execute" \
  -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  -d '{"input":{"age":25}}' | jq

# 4. Deu errado? Arquive a nova. A anterior volta a ser a mais recente publicada.
curl -s -X POST "$BASE/versions/$VERSION_ID/archive" -H "Authorization: Bearer $TOKEN"

Armadilhas.

  • Publicar já muda produção. Não existe "publicar em homologação". A partir do passo 2 a versão nova está no ar, e a validação do passo 3 é depois. Para validar antes, mantenha uma decisão separada de ensaio ou valide numa organização de staging.
  • Publicar não despublica a anterior. Várias versões podem estar PUBLISHED ao mesmo tempo. O motor usa a mais recente. O jeito de voltar atrás é arquivar a nova, não republicar a antiga — republicar retorna 409, porque ela já está publicada.
  • Semver é obrigatório e imutável. 1.1 é rejeitado; precisa ser 1.1.0. E não há edição de versão: uma versão publicada nunca muda, o que é o ponto.

Descobrir por que uma proposta foi negada

# 1. Achar a execução pelo intervalo de tempo
curl -s "$BASE/decisions/$DECISION_ID/execution-logs?filter[from]=2026-08-01" \
  -H "Authorization: Bearer $TOKEN" | jq '.data[] | {id, versionId: .attributes.versionId}'

# 2. Ler entrada e saída daquela execução
curl -s "$BASE/execution-logs/$LOG_ID" -H "Authorization: Bearer $TOKEN" \
  | jq '.data.attributes | {input, output, executionTimeMs}'

# 3. Recuperar a TABELA que rodou naquele momento
curl -s "$BASE/versions/$VERSION_ID" -H "Authorization: Bearer $TOKEN" | jq -r '.content'

Armadilhas. O output gravado por /decisions/:id/execute tem a forma {"result": ..., "trace": [...]}, enquanto o gravado por /versions/:id/execute guarda a saída direta, sem envelope. Se você consome os logs programaticamente, trate as duas formas. E lembre que execução que falhou não tem log — a ausência de registro não significa que ninguém chamou.

Exportar todas as suas regras

Útil em auditoria, em backup e antes de qualquer conversa sobre migração.

for D in $(curl -s "$BASE/decisions?page[size]=100" -H "Authorization: Bearer $TOKEN" \
             | jq -r '.data[].id'); do
  for V in $(curl -s "$BASE/decisions/$D/versions?filter[status]=PUBLISHED" \
               -H "Authorization: Bearer $TOKEN" | jq -r '.data[].id'); do
    curl -s "$BASE/versions/$V" -H "Authorization: Bearer $TOKEN" \
      | jq -r '.content' > "dmn/${D}_${V}.dmn"
  done
done

Armadilhas. O XML sai íntegro e é DMN padrão — ele abre no Camunda Modeler e roda em qualquer motor conforme. Paginação padrão é 20 itens; passe page[size] até 100.

Modelar a tabela numa interface visual

O Decision Engine não tem editor gráfico (§15). O caminho prático:

  1. Modele a tabela no Camunda Modeler (gratuito para desenho) ou em qualquer editor DMN.
  2. Exporte o .dmn.
  3. Suba o arquivo pela rota de criação de versão, como no início rápido.

Armadilhas. Use o namespace do exemplo (https://www.omg.org/spec/DMN/20191111/MODEL/) — é o que o runner espera. Recursos DMN de nível de conformidade mais alto, como decisões encadeadas dentro do mesmo arquivo, dependem do runner e não estão cobertos por teste nosso: valide criando a versão, porque a validação acontece ali.


12Integração com outros building blocks

A fronteira que mais gera dúvida: Decision Engine ou Decision Platform?

PerguntaSe a resposta é sim
Eu já tenho todos os dados de entrada em mãos?Decision Engine. Você manda o input completo e recebe a resposta.
Preciso que alguém busque dados antes de decidir (bureau, arquivo, fila)?Decision Platform.
Preciso de resposta imediata e nada mais?Decision Engine.
Preciso de execução assíncrona, timeout configurável ou callback?Decision Platform.
Quem escreve e versiona a tabela de regras?Decision Engine. Sempre. A Platform não tem regras próprias.

Em uma frase: o Decision Engine é a regra; o Decision Platform é a esteira que leva os dados até ela. A Platform não substitui o Engine — ela o consome. Toda execução de esteira termina numa tabela DMN que vive aqui.

Building blockComo se relacionaObrigatório
IAMEmite o token; todas as rotas exigem organizationId e permissãoSim
Decision PlatformUsa este motor como avaliador; lê a última versão publicada da decisão apontada pela configNão
Feature FlagsResolve pertencimento a segmento avaliando DMN pela facade DecisionEngineFacadeTokenNão
Pricing EngineComplementar: a decisão diz se aprova, a precificação diz a que preçoNão
Calculations EngineComplementar: amortização, IOF e CET depois que a decisão aprovouNão
Audit TrailRegistra quem publicou qual versão; o ExecutionLog registra o que rodouNão
Webhooks EnginePode entregar os eventos decision-engine.* a sistemas externosNão
                      ┌───────────────────────────────────────────┐
                      │        Decision Engine  (a REGRA)         │
   escreve a tabela   │                                           │
   ────────────────▶  │  Project → Decision → DecisionVersion     │
   (analista de       │                        (XML DMN, semver)  │
    risco)            │                             │             │
                      │                             ▼             │
                      │                     DmnProxyService       │
                      └──────────────┬──────────────┬─────────────┘
                                     │              │ HTTP
        ┌────────────────────────────┘              ▼
        │ consome como avaliador          ┌──────────────────┐
        │                                 │   runner DMN     │
   ┌────┴────────────────┐                │  (Camunda DMN)   │
   │ Decision Platform   │                └──────────────────┘
   │   busca os dados    │
   │   e chama a regra   │      ┌──────────────┐
   └─────────────────────┘      │Feature Flags │ avalia DMN de segmento
                                └──────────────┘   pela facade

   A seta que importa comercialmente: quem escreve a regra é o analista de
   negócio, e não o desenvolvedor. É o mesmo XML em toda a cadeia.

13Configuração e operação

Variáveis de ambiente

VariávelDescriçãoObrigatóriaPadrão
DMN_ENGINE_URLURL do runner DMN. O serviço não avalia nada sem ela.Sim (na prática)http://localhost:8080
DATABASE_URLPostgreSQL. O schema é decisions.Sim
JWT_SECRETSegredo HS256 do IAM, mínimo 44 caracteresSim
PORTPorta no modo standaloneNão3000 (mapeada para 3005)
DEPLOYMENT_MODEmonolith ou standaloneNãomonolith
MODULE_SELFIdentificação do serviço nos health checksNãodecision-engine

Dependências de infraestrutura

DependênciaPara quê
PostgreSQLSchema decisions — projetos, decisões, versões, tags, logs
Runner DMNAvaliação das tabelas. Imagem decision-engine-runner, exposta em 8081:8080 no compose local
IAMEmissão e verificação do token

O Redis não é dependência deste building block.

Limites

LimiteValor
Nome de projeto, decisão255 caracteres
Chave (key) de projeto e decisão100 caracteres, ^[a-z0-9-]+$, única por organização
Descrição1000 caracteres
Nome de tag100 caracteres, único por organização
Cor de tagHexadecimal de 6 dígitos, #RRGGBB
Formato da versãoSemver estrito \d+\.\d+\.\d+, único por decisão
Tamanho do XML DMNSem limite na aplicação; limitado pelo corpo HTTP e pela coluna text
Página padrão / máxima20 / 100 itens

Catálogo de erros

StatusCódigoSignificaO que fazer
400VALIDATIONCorpo reprovado no Zod (semver, chave, cor, UUID)Confira o formato contra §9
400VALIDATIONDMN execution failed: ...O runner recusou o XML ou a entrada. Valide o DMN e confira se as chaves do input casam com os inputExpression
400VALIDATIONInvalid DMN content na criação de versãoO XML não passou na validação. Abra no Camunda Modeler
400VALIDATIONDecision is not activePATCH na decisão com {"status":"ACTIVE"}
400VALIDATIONNo published version availablePublique uma versão
400VALIDATIONOnly published versions can be executedPublique a versão, ou use a rota da decisão
403Organization context requiredAutentique informando a organização
403FORBIDDENPermissão faltando, ou o recurso é de outra organizaçãoConfira a permissão exata na tabela de §9
404NOT_FOUNDRecurso inexistente, de outra organização ou excluído logicamenteConfira o ID
409CONFLICTkey já existe, ou versão já existe, ou já publicada/arquivadaEscolha outra chave, ou confira o estado atual
500INTERNALFailed to publish eventO barramento de eventos falhou. A escrita principal já aconteceu

Observabilidade.

  • GET /decision-engine/health responde com nome e versão do build. É uma sonda de vida do processo — ela não verifica o runner DMN nem o banco. Para saber se o runner está de pé, use o /health do próprio runner (DMN_ENGINE_URL).
  • O DmnProxyService loga cada execução em nível debug com tempo e decisionId, e cada falha em error com a mensagem do runner. É por aí que se monitora latência do motor.
  • A tabela decisions.execution_logs é a fonte de verdade para volume, latência (executionTimeMs) e distribuição de versões em uso. Ela só registra sucesso (§15).
  • Eventos publicados: decision-engine.project.{created,updated,deleted}, decision-engine.decision.{created,updated,deleted,executed}, decision-engine.version.{created,published,archived}, decision-engine.tag.{created,updated,deleted}.

14Segurança e compliance

Isolamento entre tenants. Toda rota aplica o requireOrganization local, que devolve 403 quando o token não traz organizationId. O organizationId nunca é lido do corpo da requisição — ele vem do claim assinado. No acesso a dados, DecisionProject, Decision e DecisionTag carregam a coluna organization_id e os repositórios filtram por ela em toda consulta. DecisionVersion e ExecutionLog não têm a coluna: o isolamento delas é feito por junção com a decisão dona, e as rotas que as acessam comparam version.decision.organizationId com o do token, respondendo 403 quando diverge.

O runner DMN não guarda nada. Cada execução envia o XML e a entrada no corpo da requisição. O runner é stateless: não há modelo implantado, não há catálogo e não há sessão. Isso significa que ele não é um ponto onde dados de um cliente possam vazar para outro — porque ele não retém dados de cliente nenhum.

Dados sensíveis no log de execução. O ExecutionLog grava a entrada completa da decisão em JSON. Numa esteira de crédito isso inclui, tipicamente, CPF, renda e score — dado pessoal sob a LGPD. O conteúdo não é criptografado em coluna, não há política de retenção automática e não há mascaramento (§15). Trate o schema decisions como base que contém dado pessoal: controle de acesso ao banco, criptografia em repouso no volume e um processo de expurgo definido por você.

Explicabilidade da decisão automatizada. O art. 20 da LGPD assegura ao titular o direito de solicitar revisão de decisões tomadas unicamente com base em tratamento automatizado que afetem seus interesses — incluindo, expressamente, as que definem perfil de crédito. O ExecutionLog com versionId, entrada, saída e trace das regras avaliadas é a base factual para atender a esse pedido: ele diz qual tabela rodou, com que dados e qual linha disparou. A conformidade não é automática — o processo de revisão é seu —, mas o registro que ele exige existe.

Autenticação e permissões. Bearer JWT emitido pelo IAM. As permissões usadas são DECISION_PROJECTS_{CREATE,READ,UPDATE,DELETE}, DECISION_VERSIONS_{CREATE,READ,PUBLISH}, DECISION_TAGS_MANAGE, DECISION_LOGS_READ e DECISION_EXECUTE. Separe DECISION_VERSIONS_PUBLISH do resto ao desenhar papéis: publicar é o ato que muda a política em produção, e deve exigir mais gente do que escrever um rascunho.

Exclusão lógica. Projetos, decisões, versões e tags usam deletedAt. A linha permanece para que o log de execução continue apontando para uma versão existente. Atender a um pedido de eliminação sob a LGPD exige processo explícito de expurgo, hoje não automatizado.


15Limitações conhecidas

LimitaçãoImpactoSituação
Execução com falha não gera ExecutionLogA coluna error existe na tabela mas nunca é preenchida: o log só é criado depois do sucesso do runner. Taxa de erro e latência de falha não são observáveis pela tabela.Lacuna conhecida — use o log da aplicação
Sem editor visual de tabela DMNO XML entra por API. Quem desenha usa uma ferramenta externa, como o Camunda Modeler.Por design hoje; interface no roadmap
Sem simulação nem teste em loteNão há endpoint para rodar uma versão contra um conjunto de casos e comparar com a anterior. A alternativa é chamar /versions/:id/execute em laço.Roadmap
Publicar já vale em produçãoNão há ambiente de ensaio dentro do building block, nem estágio entre DRAFT e valer.Por design — use organização de staging
Publicar não despublica a anteriorVárias versões ficam PUBLISHED e o motor usa a mais recente. Voltar atrás é arquivar a nova.Por design; pode surpreender
Sem retenção automática do log de execuçãoexecution_logs cresce indefinidamente, com entrada e saída em JSON. Em volume alto isso é custo de armazenamento e exposição de dado pessoal.Roadmap
/health não verifica o runner nem o bancoA sonda responde 200 com o serviço de pé mesmo com o runner DMN fora. Um orquestrador confiando só nela não detecta a falha.Lacuna conhecida
Permissões não seguem o recursoCriar decisão exige DECISION_VERSIONS_CREATE; excluir decisão exige DECISION_PROJECTS_DELETE; arquivar versão exige DECISION_VERSIONS_CREATE; ler tag exige DECISION_TAGS_MANAGE.Conceda papéis pela tabela de §9
DECISION_VERSIONS_UPDATE e DECISION_VERSIONS_DELETE não são usadasExistem no vocabulário do IAM, mas nenhuma rota deste building block as exige.Vocabulário à frente do código
executionLogsRouter montado em dois prefixosOs mesmos handlers respondem sob caminhos extras além dos três documentados.Use apenas as rotas de §9
Sem métricas nativas de negócioDistribuição de resultados, taxa de aprovação e uso por versão saem de consulta ao banco, não de um endpoint.Roadmap
trace só na rota da decisão/versions/:id/execute devolve apenas output e executionTimeMs.Por design
Sem cache de execuçãoEntradas idênticas repetidas vão ao runner todas as vezes.Por design — regra determinística com dado que muda

16Perguntas frequentes

Qual é a diferença entre o Decision Engine e o Decision Platform, em uma frase?

O Decision Engine é a regra; o Decision Platform é a esteira que busca os dados e leva até a regra. Se você já tem todos os dados de entrada, chame o Engine direto. Se alguém precisa consultar um bureau, ler um arquivo ou puxar de uma fila antes de decidir, é a Platform — e ela vai terminar chamando uma tabela que vive aqui de qualquer jeito.

Preciso saber DMN para usar?

Precisa entender o que é uma tabela de decisão, o que é fácil. Escrever o XML na mão é chato, e não é o caminho recomendado: modele no Camunda Modeler ou em outro editor DMN, exporte o .dmn e suba o arquivo. A recompensa de aprender o formato é que ele é padrão da OMG e a sua regra não fica presa a nenhum fornecedor, incluindo nós.

Se eu publicar uma versão errada, como volto atrás?

Arquivando a versão errada, com POST /versions/:versionId/archive. A anterior volta a ser a mais recente publicada automaticamente. Não tente republicar a antiga — ela já está PUBLISHED e o serviço responde 409.

Por que a minha decisão retorna 400 mesmo com a versão publicada?

Quase sempre porque a Decision está DRAFT. São dois interruptores independentes: a versão precisa estar PUBLISHED e a decisão precisa estar ACTIVE. Faça PATCH /decisions/:decisionId com {"status":"ACTIVE"}.

Como respondo a um pedido de revisão de decisão automatizada sob a LGPD?

Pelo ExecutionLog. Ele guarda a entrada exata, a saída, o trace das regras avaliadas e o versionId. Com o versionId, GET /versions/:versionId devolve o XML DMN que rodou — a tabela em si, legível, não uma reconstrução a partir de código. O registro existe; o processo de revisão humana é responsabilidade sua.

As minhas regras ficam presas na Catalisa?

Não. O que armazenamos em DecisionVersion.content é o XML DMN cru, e GET /versions/:versionId devolve ele inteiro. A receita de exportação em massa está em §11. Sair daqui custa o tempo de baixar os arquivos.

Qual motor DMN roda por trás?

Um runner baseado em Camunda DMN, empacotado como a imagem decision-engine-runner e mantido em repositório próprio. Ele é alcançado por HTTP através do DmnProxyService, com três operações. Isso torna o motor substituível sem impacto nas rotas — e é por isso que a documentação fala em "runner DMN" e não no nome de um fornecedor.

Quanto tempo demora uma execução?

Depende do tamanho da tabela e da latência até o runner, e o número que importa é o seu. Cada resposta traz executionTimeMs e cada ExecutionLog grava o valor, então medir o seu p95 é uma consulta ao banco. Não publicamos número de laboratório porque ele não sobrevive ao seu ambiente.

Dá para encadear decisões, com uma alimentando a outra?

Dentro de um mesmo arquivo DMN, isso depende do nível de conformidade do runner e não está coberto pelos nossos testes — valide criando a versão, já que a validação acontece ali. Entre decisões separadas, o building block não encadeia: quem orquestra sequência é o Decision Platform, e mesmo ele executa uma decisão por config.

Posso usar isto fora de crédito?

Pode, e faz sentido em qualquer lugar onde a regra muda mais rápido que o software: aceitação de seguro, elegibilidade em saúde, aprovação de desconto no varejo, roteamento de atendimento. O building block não sabe o que é crédito — ele avalia tabelas.


Padrão: PADRAO-DOCUMENTACAO.md · Índice: INDEX.md

Building blocks relacionados