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.
- 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
- 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
- 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.
| Atributo | Valor |
|---|---|
| Identificador | decision-engine |
| Categoria | Decisão |
| Escopo | Tenant (exige organizationId no token) |
| Porta (standalone) | 3005 |
| Path alias | @decision-engine |
| Prefixo HTTP | /decision-engine |
| Schema no banco | decisions |
| Status | Produção desde 2026-03 |
| Depende de | PostgreSQL, 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
ifaninhado 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
| Antes | Depois |
|---|---|
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 deploy | Mudar a política é POST .../versions + POST .../publish |
| "Qual regra rodou naquele dia?" é arqueologia de Git | Toda execução grava versão, entrada, saída e tempo |
| A regra nova substitui a antiga e some | Versões coexistem: rascunho, publicada e arquivada |
| Trocar de motor significa reescrever tudo | O 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ério | Catalisa Decision Engine | Camunda | Drools (Apache KIE) | IBM ODM | json-rules-engine |
|---|---|---|---|---|---|
| Formato da regra | DMN (XML da OMG) | DMN (XML da OMG) | DRL e DMN | Proprietário e DMN | JSON próprio |
| Preço | Precificação em definição | Cotação (tabela não publica valores) | Licença zero, você opera | Cotação, não público | Gratuito (ISC) |
| Versionamento e publicação | Nativo, com semver e estados | Via deploy de definição | Você constrói | Nativo | Não existe |
| Log por execução | Nativo, com versão usada | Via histórico da plataforma | Você constrói | Nativo | Não existe |
| Multi-tenant | Nativo, por organizationId do token | Você modela | Você constrói | Você modela | Não existe |
| Autorização por permissão | Nativa, vocabulário compartilhado | Externa | Externa | Nativa | Não existe |
| Modelador visual de tabela | Não (ver §15) | Sim, referência de mercado | Parcial | Sim | Não |
| Simulação e teste em lote | Não (ver §15) | Sim | Parcial | Sim | Não |
| Você opera o motor | Não | Sim, salvo SaaS | Sim | Sim | N/A |
Nossos diferenciais
- 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.
- O tenant é do token, não do modelo de dados. Toda rota exige
organizationIdno JWT e todo repositório filtra por ele. Num motor genérico, separar clientes é modelagem sua, e modelagem sua é onde vaza. - 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.
- O motor é substituível por trás. O
DmnProxyServicefala 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.
| Driver | Por que ele importa |
|---|---|
| Execuções por mês | É a chamada ao runner DMN, o custo variável real |
| Decisões publicadas | Proxy do tamanho da operação de política |
| Retenção do log de execução | Cada 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 Engine | Camunda Self-Managed | Drools (Apache KIE) | IBM ODM | |
|---|---|---|---|---|
| Licença | Precificação em definição | Cotação — a tabela pública não publica valores | Zero (Apache 2.0) | Cotação, não público |
| Quem opera o motor | Catalisa | Você | Você | Você ou parceiro |
| Versionamento e log | Incluído | Você constrói | Você constrói | Incluído |
| Multi-tenant | Incluído | Você modela | Você constrói | Você 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,validateDmnehealthCheck. O runner é a imagemdecision-engine-runner, construída a partir de um repositório próprio (catalisaio/decision-engine-runner) e publicada no GHCR pelo workflowbuild-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.createVersionchamavalidateDmnantes 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
ACTIVEexecuta. UmaDecisionnasceDRAFTe precisa ser promovida aACTIVEporPATCH. É 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
| Termo | Significa |
|---|---|
| DMN | Decision Model and Notation, padrão da OMG para modelar decisões. A regra é um XML. |
| FEEL | Friendly Enough Expression Language, a linguagem de expressão do DMN. É o que você escreve numa célula: >= 18, "adult". |
| Tabela de decisão | O <decisionTable> do DMN: colunas de entrada, colunas de saída, uma linha por regra. |
| Hit policy | Como o motor resolve quando mais de uma linha casa. FIRST devolve a primeira; há outras no padrão. |
| Project | Agrupamento de decisões. Existe para organizar, não para isolar — o isolamento é por organização. |
| Decision | A decisão de negócio, com chave estável (politica-credito-pessoal). É o que o seu aplicativo referencia. |
| DecisionVersion | Uma versão do XML DMN daquela decisão, em semver. É o que de fato executa. |
| Tag | Rótulo colorido por organização, para cortar o catálogo de decisões. |
| ExecutionLog | O registro de uma execução: entrada, saída, tempo, quem executou e qual versão. |
Modelo de dados — schema decisions no PostgreSQL.
| Modelo Prisma | Tabela | Propósito | Campos-chave |
|---|---|---|---|
DecisionProject | decisions.decision_projects | Agrupa decisões | key único por organização, ownerId, deletedAt |
Decision | decisions.decisions | A decisão de negócio | key único por organização, projectId, status, deletedAt |
DecisionVersion | decisions.decision_versions | O XML DMN versionado | content (texto), version único por decisão, status, publishedAt |
DecisionTag | decisions.decision_tags | Rótulo por organização | name único por organização, color (hex) |
DecisionTagLink | decisions.decision_tag_links | Liga decisão e tag | Chave composta (decisionId, tagId) |
ExecutionLog | decisions.execution_logs | Rastro de execução | decisionId, versionId, input, output, executionTimeMs, executedBy |
Enumerações
| Enum | Valores |
|---|---|
DecisionStatus | DRAFT · ACTIVE · ARCHIVED |
VersionStatus | DRAFT · 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étodo | Rota | Descrição | Permissão |
|---|---|---|---|
POST | /decision-engine/api/v1/projects | Cria projeto | DECISION_PROJECTS_CREATE |
GET | /decision-engine/api/v1/projects | Lista projetos, paginado | DECISION_PROJECTS_READ |
GET | /decision-engine/api/v1/projects/:projectId | Busca projeto | DECISION_PROJECTS_READ |
PATCH | /decision-engine/api/v1/projects/:projectId | Atualiza nome e descrição | DECISION_PROJECTS_UPDATE |
DELETE | /decision-engine/api/v1/projects/:projectId | Exclusão lógica. Responde 204 | DECISION_PROJECTS_DELETE |
Decisões — /decision-engine/api/v1/decisions
| Método | Rota | Descrição | Permissão |
|---|---|---|---|
POST | /decision-engine/api/v1/decisions | Cria decisão em DRAFT | DECISION_VERSIONS_CREATE |
GET | /decision-engine/api/v1/decisions | Lista decisões, paginado | DECISION_VERSIONS_READ |
GET | /decision-engine/api/v1/decisions/:decisionId | Busca decisão | DECISION_VERSIONS_READ |
PATCH | /decision-engine/api/v1/decisions/:decisionId | Atualiza nome, descrição, status e tags | DECISION_VERSIONS_CREATE |
DELETE | /decision-engine/api/v1/decisions/:decisionId | Exclusão lógica. Responde 204 | DECISION_PROJECTS_DELETE |
POST | /decision-engine/api/v1/decisions/:decisionId/execute | Executa a última versão publicada | DECISION_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_CREATEe que excluir exigeDECISION_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étodo | Rota | Descrição | Permissão |
|---|---|---|---|
POST | /decision-engine/api/v1/decisions/:decisionId/versions | Cria versão. Valida o DMN antes de gravar | DECISION_VERSIONS_CREATE |
GET | /decision-engine/api/v1/decisions/:decisionId/versions | Lista versões da decisão | DECISION_VERSIONS_READ |
GET | /decision-engine/api/v1/versions/:versionId | Busca versão. Devolve o XML DMN em content | DECISION_VERSIONS_READ |
POST | /decision-engine/api/v1/versions/:versionId/publish | Publica a versão | DECISION_VERSIONS_PUBLISH |
POST | /decision-engine/api/v1/versions/:versionId/archive | Arquiva a versão | DECISION_VERSIONS_CREATE |
POST | /decision-engine/api/v1/versions/:versionId/execute | Executa esta versão específica | DECISION_EXECUTE |
Filtro de listagem: filter[status] (DRAFT, PUBLISHED, ARCHIVED).
Tags — /decision-engine/api/v1/tags
| Método | Rota | Descrição | Permissão |
|---|---|---|---|
POST | /decision-engine/api/v1/tags | Cria tag | DECISION_TAGS_MANAGE |
GET | /decision-engine/api/v1/tags | Lista tags, paginado | DECISION_TAGS_MANAGE |
GET | /decision-engine/api/v1/tags/:tagId | Busca tag | DECISION_TAGS_MANAGE |
PATCH | /decision-engine/api/v1/tags/:tagId | Atualiza nome e cor | DECISION_TAGS_MANAGE |
DELETE | /decision-engine/api/v1/tags/:tagId | Remove tag. Responde 204 | DECISION_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étodo | Rota | Descrição | Permissão |
|---|---|---|---|
GET | /decision-engine/api/v1/execution-logs | Lista todos os logs da organização | DECISION_LOGS_READ |
GET | /decision-engine/api/v1/execution-logs/:logId | Busca um log | DECISION_LOGS_READ |
GET | /decision-engine/api/v1/decisions/:decisionId/execution-logs | Logs de uma decisão | DECISION_LOGS_READ |
Filtros: filter[decisionId], filter[versionId] (UUID), filter[from] e filter[to] (data ISO-8601).
O
executionLogsRouterestá montado em dois pontos (/api/v1/execution-logse/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étodo | Rota | Descrição |
|---|---|---|
GET | /decision-engine/health | Sonda 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>"
}
| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
version | string | Sim | Semver estrito, no formato \d+\.\d+\.\d+. Único por decisão. |
content | string | Sim | O 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
| Status | Quando |
|---|---|
400 | version fora de semver, content vazio, ou DMN inválido segundo o runner |
403 | Token sem organizationId ou sem DECISION_VERSIONS_CREATE |
404 | A decisão não existe nesta organização |
409 | Já 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 } }
| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
input | object | Sim | Mapa 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
| Status | Quando |
|---|---|
400 | Decision is not active — a decisão não está ACTIVE |
400 | No published version available — nenhuma versão publicada |
400 | DMN execution failed: ... — o runner recusou o XML ou a entrada |
403 | Token sem organizationId ou sem DECISION_EXECUTE |
404 | A 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
| Status | Quando |
|---|---|
400 | Only published versions can be executed — a versão está DRAFT ou ARCHIVED |
403 | A versão pertence a outra organização, ou falta DECISION_EXECUTE |
404 | A 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>>= 18</text></inputEntry>
<outputEntry id="oe1"><text>"adult"</text></outputEntry>
</rule>
<rule id="rule2">
<inputEntry id="ie2"><text>< 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
PUBLISHEDao mesmo tempo. O motor usa a mais recente. O jeito de voltar atrás é arquivar a nova, não republicar a antiga — republicar retorna409, porque ela já está publicada. - Semver é obrigatório e imutável.
1.1é rejeitado; precisa ser1.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:
- Modele a tabela no Camunda Modeler (gratuito para desenho) ou em qualquer editor DMN.
- Exporte o
.dmn. - 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?
| Pergunta | Se 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 block | Como se relaciona | Obrigatório |
|---|---|---|
| IAM | Emite o token; todas as rotas exigem organizationId e permissão | Sim |
| Decision Platform | Usa este motor como avaliador; lê a última versão publicada da decisão apontada pela config | Não |
| Feature Flags | Resolve pertencimento a segmento avaliando DMN pela facade DecisionEngineFacadeToken | Não |
| Pricing Engine | Complementar: a decisão diz se aprova, a precificação diz a que preço | Não |
| Calculations Engine | Complementar: amortização, IOF e CET depois que a decisão aprovou | Não |
| Audit Trail | Registra quem publicou qual versão; o ExecutionLog registra o que rodou | Não |
| Webhooks Engine | Pode entregar os eventos decision-engine.* a sistemas externos | Nã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ável | Descrição | Obrigatória | Padrão |
|---|---|---|---|
DMN_ENGINE_URL | URL do runner DMN. O serviço não avalia nada sem ela. | Sim (na prática) | http://localhost:8080 |
DATABASE_URL | PostgreSQL. O schema é decisions. | Sim | — |
JWT_SECRET | Segredo HS256 do IAM, mínimo 44 caracteres | Sim | — |
PORT | Porta no modo standalone | Não | 3000 (mapeada para 3005) |
DEPLOYMENT_MODE | monolith ou standalone | Não | monolith |
MODULE_SELF | Identificação do serviço nos health checks | Não | decision-engine |
Dependências de infraestrutura
| Dependência | Para quê |
|---|---|
| PostgreSQL | Schema decisions — projetos, decisões, versões, tags, logs |
| Runner DMN | Avaliação das tabelas. Imagem decision-engine-runner, exposta em 8081:8080 no compose local |
| IAM | Emissão e verificação do token |
O Redis não é dependência deste building block.
Limites
| Limite | Valor |
|---|---|
| Nome de projeto, decisão | 255 caracteres |
Chave (key) de projeto e decisão | 100 caracteres, ^[a-z0-9-]+$, única por organização |
| Descrição | 1000 caracteres |
| Nome de tag | 100 caracteres, único por organização |
| Cor de tag | Hexadecimal de 6 dígitos, #RRGGBB |
| Formato da versão | Semver estrito \d+\.\d+\.\d+, único por decisão |
| Tamanho do XML DMN | Sem limite na aplicação; limitado pelo corpo HTTP e pela coluna text |
| Página padrão / máxima | 20 / 100 itens |
Catálogo de erros
| Status | Código | Significa | O que fazer |
|---|---|---|---|
400 | VALIDATION | Corpo reprovado no Zod (semver, chave, cor, UUID) | Confira o formato contra §9 |
400 | VALIDATION | DMN execution failed: ... | O runner recusou o XML ou a entrada. Valide o DMN e confira se as chaves do input casam com os inputExpression |
400 | VALIDATION | Invalid DMN content na criação de versão | O XML não passou na validação. Abra no Camunda Modeler |
400 | VALIDATION | Decision is not active | PATCH na decisão com {"status":"ACTIVE"} |
400 | VALIDATION | No published version available | Publique uma versão |
400 | VALIDATION | Only published versions can be executed | Publique a versão, ou use a rota da decisão |
403 | — | Organization context required | Autentique informando a organização |
403 | FORBIDDEN | Permissão faltando, ou o recurso é de outra organização | Confira a permissão exata na tabela de §9 |
404 | NOT_FOUND | Recurso inexistente, de outra organização ou excluído logicamente | Confira o ID |
409 | CONFLICT | key já existe, ou versão já existe, ou já publicada/arquivada | Escolha outra chave, ou confira o estado atual |
500 | INTERNAL | Failed to publish event | O barramento de eventos falhou. A escrita principal já aconteceu |
Observabilidade.
GET /decision-engine/healthresponde 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/healthdo próprio runner (DMN_ENGINE_URL).- O
DmnProxyServiceloga cada execução em níveldebugcom tempo edecisionId, e cada falha emerrorcom 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ção | Impacto | Situação |
|---|---|---|
Execução com falha não gera ExecutionLog | A 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 DMN | O 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 lote | Nã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ção | Nã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 anterior | Vá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ção | execution_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 banco | A 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 recurso | Criar 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 usadas | Existem no vocabulário do IAM, mas nenhuma rota deste building block as exige. | Vocabulário à frente do código |
executionLogsRouter montado em dois prefixos | Os mesmos handlers respondem sob caminhos extras além dos três documentados. | Use apenas as rotas de §9 |
| Sem métricas nativas de negócio | Distribuiçã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ção | Entradas 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
A esteira que busca os dados, aplica a política e devolve o veredito
Feature FlagsLigue e desligue funcionalidade em produção sem novo deploy, por segmento de cliente
Pricing EngineTaxa por faixa de risco, com tarifas, comissões e seguros no mesmo cálculo
IAMIdentidade, organizações e permissões para todo o catálogo de building blocks