kroz docs

Rule Engine

Contrato parametrizado → payload BaaS: resolver, gatilhos, settlement e prova criptográfica.

Visão geral

O Rule Engine resolve qual versão de regra aplicar dado um evento, compila alocações em baas_split[] e registra execução com snapshot_hash.

Princípio: a regra é dados + versão + hash. A execução é determinística dado (evento, versão ativa).

Nome comercial: "O contrato vira regra executável". Não usar "smart contract".

Spec completa: docs/kroz-rule-engine-spec.md (repo FÁBRICA)


Arquitetura

flowchart LR
    subgraph config [Configuração]
        C[contracts]
        SM[split_matrices]
        RT[rule_triggers]
    end
    subgraph runtime [Runtime]
        DE[domain_events]
        RR[rule_resolver]
        SE[split_executions]
    end
    subgraph io [I/O]
        API[POST /api/rules-dry-run]
        WH[Webhook payment.settled]
        BAAS[baas_split payload]
    end
    C --> SM
    SM --> RT
    API --> RR
    WH --> DE --> RR
    RR --> SE
    RR --> BAAS

API: Dry-run de regra

POST /api/rules-dry-run

Preview sem persistir split_executions. Usado pela UI e integrações.

curl -s -X POST https://app.kroz.co/api/rules-dry-run \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -H "Origin: https://app.kroz.co" \
  -d '{
    "tenantSlug": "incorp-piloto",
    "contractReference": "PS-0421",
    "amount": 50000,
    "billingType": "PIX",
    "installmentNumber": 3,
    "event_type": "payment.create",
    "split": [
      { "walletId": "wallet_terrenista_demo", "percentualValue": 18 },
      { "walletId": "wallet_vendas_demo", "percentualValue": 10 }
    ]
  }'
Campo Obrigatório Descrição
contractReference ✅ ex.: PS-0421
amount ou value ✅ > 0
tenantSlug ✅* *Ou KROZ_DEMO_MODE=true
billingType PIX, BOLETO
installmentNumber Ativa gatilho 3ª parcela
event_type Default payment.create
split Para payload_match comparison

Response 200

{
  "ok": true,
  "dryRun": true,
  "preview": true,
  "contract_id": "uuid",
  "contract_reference": "PS-0421",
  "tenant_slug": "incorp-piloto",
  "matrix_status": "ativa",
  "rule_version_id": "uuid",
  "rule_version": 1,
  "rule_snapshot_hash": "sha256...",
  "trigger_matched": "default",
  "trigger_effects": [],
  "resolved_allocations": [
    { "role": "landbank", "percent": 18, "walletId": "..." },
    { "role": "sales", "percent": 10, "walletId": "..." }
  ],
  "baas_split": [
    { "walletId": "...", "percentualValue": 18 },
    { "walletId": "...", "percentualValue": 10 }
  ],
  "payload_match": true,
  "warnings": [],
  "baas_payload": {
    "billingType": "PIX",
    "value": 50000,
    "split": [ "..."]
  }
}

Erros

Status Mensagem
400 contractReference é obrigatório.
400 Informe amount maior que zero para resolver a regra.
400 Campo obrigatório: tenantSlug. Em ambiente piloto, defina KROZ_DEMO_MODE=true.
404 Contrato não encontrado. / Nenhuma matriz ativa para este contrato.
405 Use POST para dry-run de regra.

Gatilhos (rule_triggers)

trigger_type Uso
payment_settled Default: liquidação confirmada
installment_due Vencimento da parcela
installment_number_gte ex.: vendas a partir da 3ª
construction_milestone_gte Marco de obra (fase 1)
amount_gte Multa/juros
manual_approval Human-in-the-loop
default Fallback

Config exemplo: 3ª parcela

{
  "trigger_type": "installment_number_gte",
  "trigger_config": { "min_installment": 3, "apply_to": ["alloc_sales"] }
}

Escopo: contract_id (unidade) ou development_id (obra) ou tenant default.


Alocações (receivers jsonb)

{
  "allocations": [
    {
      "id": "alloc_landbank",
      "role": "landbank",
      "receiver_id": "uuid",
      "wallet_id": "wallet_terrenista_demo",
      "basis": "percent",
      "value": 18,
      "priority": 20
    }
  ],
  "issuer_retention": { "role": "spe", "basis": "remainder" }
}
basis Cálculo
percent % do valor líquido
fixed Valor fixo por evento
remainder SPE emissora

Roles reconhecidos: landbank, terrenista, corretor, vendas, sales, comissao, spe


Settlement via webhook

Não é HTTP público: disparado internamente por _domainEventProcessor.js:

PAYMENT_RECEIVED → processPaymentSettledEvent()
  → domain_events (event_type: "payment.settled")
  → resolveRuleExecution(event_type: "payment.settled")
  → split_executions (status: confirmed | blocked)

Idempotência

payment.settled:{eventId}: retry webhook seguro.

Bloqueio

compareSplits(): plano vs liquidação real diverge → split_executions.status: "blocked"

Audit + alerta operacional.


Entidades (Supabase)

Tabela Função
contracts Ancora comercial
split_matrices Matriz versionada (evolui → rule_versions)
rule_triggers Gatilhos por contrato/obra
domain_events Fila normalizada
split_executions Registro por liquidação
audit_events seal_hash, trilha
payment_operations Cobrança + status
reconciliation_suggestions Divergência pós-webhook

Integração com adaptador BaaS

Pipeline:

resolveRuleExecution()
  → baas_split[]
  → payments/create-with-split (dryRun ou real)
  → webhook PAYMENT_SPLIT_DONE
  → split_executions confirmed

UI Rateio chama dry-run client-side; backend usa /api/rules-dry-run quando Supabase configurado.

Ver Matriz de rateio · Adaptadores BaaS


Versionamento

Regra Comportamento
Versão publicada Imutável
Correção Nova versão + superseded_by
snapshot_hash SHA-256 do JSON canônico
Só ativa entra no resolver Status check

Campos roadmap em split_matrices:

rule_code, effective_from, effective_until,
snapshot_hash, approved_by, approved_at, superseded_by

Boas práticas

  • ✅ Sempre /api/rules-dry-run antes de cobrança real
  • ✅ Compare payload_match: true com split UI
  • ✅ Registre rule_snapshot_hash na evidência de Fechamento
  • ❌ Não editar matriz ativa: criar nova versão
  • ❌ Não ignorar warnings[] no response

Relacionado