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-runantes de cobrança real - ✅ Compare
payload_match: truecom split UI - ✅ Registre
rule_snapshot_hashna evidência de Fechamento - ❌ Não editar matriz
ativa: criar nova versão - ❌ Não ignorar
warnings[]no response
Relacionado
- Spec:
docs/kroz-rule-engine-spec.md - Webhooks: trigger settlement
- Auditoria e prova:
seal_hash - Referência de API