Esteiras e processos
central.process.json declara workflow, transições, bloqueios por etapa, bindings cross-model, proteção de exclusão e invariantes atômicas — executados pelo backend.
O arquivo opcional backend/central.process.json declara capacidades de processo que pertencem ao Core, não à implementação local. Ele é carregado depois do domínio: toda model e campo citados precisam existir em central.domain.json.
O runtime não executa JavaScript, eval, nomes de funções ou módulos informados no JSON. Expressões usam a mesma AST fechada das regras de domínio.
Contrato mínimo
{
"schemaVersion": 1,
"models": {
"Pagamento": {
"workflow": {},
"bindings": [],
"deleteProtection": [],
"atomicInvariants": []
}
}
}
Exemplo: esteira de tickets
{
"schemaVersion": 1, "name": "Exemplo de tickets",
"models": [{
"name": "Ticket", "singular": "ticket", "basePath": "/tickets",
"crud": { "enabled": true, "permissions": { "read": "tickets.read", "write": "tickets.write" } },
"fields": {
"titulo": { "kind": "string", "required": true },
"etapa": { "kind": "enum", "values": ["Aberto", "Concluido"], "default": "Aberto" }
}
}]
}
{
"schemaVersion": 1,
"models": {
"Ticket": { "workflow": {
"stageField": "etapa", "initialStages": ["Aberto"], "defaultStage": "Aberto",
"transitions": [{ "from": "Aberto", "to": "Concluido" }]
} }
}
}
Mover de Aberto para Concluído funciona; voltar para Aberto é recusado pelo backend — mesmo por PATCH direto. A UI correspondente está em Esteiras (CorePipeline).
Workflow e transições
{
"workflow": {
"stageField": "etapa",
"initialStages": ["Solicitado"],
"defaultStage": "Solicitado",
"transitions": [
{ "from": "Solicitado", "to": "Aprovado" },
{
"from": "Aprovado",
"to": "Aguardando NF",
"when": { "op": "eq", "args": [{ "field": "aprovadoFinanceiro" }, { "value": true }] }
}
],
"lockedFieldsByStage": {
"Enviado para pagamento": ["valor", "projetoId", "projetoItemId"],
"Pagamento Ok": ["valor", "projetoId", "projetoItemId"]
},
"lockedMessage": "Os dados de negócio ficam bloqueados nesta etapa.",
"onEnter": [
{ "stage": "Enviado para pagamento", "set": { "statusTrabalho": "Trabalhando", "statusPagamento": "Pendente" } }
],
"automaticTransitions": [
{
"when": { "op": "eq", "args": [{ "field": "pagamentoLiquidado" }, { "value": true }] },
"to": "Pagamento Ok",
"set": { "statusTrabalho": "Trabalhando" }
}
]
}
}
- O backend compara o valor anterior e o novo; reenviar a mesma etapa não cria transição.
- Uma mudança manual precisa existir em
transitionse satisfazerwhen. lockedFieldsByStageé aplicado sobre a etapa anterior e impede alterações mesmo porPUT/PATCHdiretos.onEnterproduz alterações confiáveis do Core.automaticTransitionsroda no servidor depois dos bindings, sem depender do frontend.
A UI pode declarar botões transition e setField, mas é apenas uma projeção: a autoridade é o backend.
Bindings cross-model
Bindings preenchem campos derivados a partir de outra model ou de registros relacionados, resolvidos em lote e avaliados na ordem declarada — uma expressão pode consumir lookups e agregações anteriores.
Lookup
{
"field": "percentualFeeAplicado",
"kind": "lookup",
"sourceModel": "Projeto",
"localField": "projetoId",
"sourceField": "percentualFee",
"watchFields": ["percentualFee"],
"recalculate": "async",
"default": 0
}
Quando Projeto.percentualFee muda, o Core encontra os dependentes e agenda um recálculo assíncrono com uma leitura por binding e um bulkWrite — em vez de save() item a item na requisição do usuário.
Agregação
{
"field": "pagamentoTotalPlanejado",
"kind": "aggregate",
"sourceModel": "Pagamento",
"foreignField": "projetoItemId",
"operator": "sum",
"sourceField": "valor",
"match": { "canceladoNaCentral": { "neq": true } },
"default": 0
}
Operadores: sum, count, min, max. Filtros aceitam igualdade direta ou { "eq" }, { "neq" }, { "in": [] } e { "nin": [] }.
Expressão derivada
{
"field": "pagamentoValorPendente",
"kind": "expression",
"precision": 2,
"expression": {
"op": "max",
"args": [{ "value": 0 }, { "op": "subtract", "args": [{ "field": "contratacaoTotal" }, { "field": "pagamentoTotalPago" }] }]
}
}
Além dos operadores do domínio, processos aceitam if (condição, verdadeiro, falso), concat e formatCurrency (valor, moeda e locale opcionais).
Recálculo imediato e assíncrono
recalculate |
Comportamento | Use quando |
|---|---|---|
immediate (padrão) |
Dependentes atualizados no mesmo ciclo da mutação | O pai precisa refletir a alteração antes da resposta |
async |
Recálculo em fila persistida, em lote | Um pai com muitos filhos (ex.: percentuais de um Projeto) |
A fila async é persistida em oon_process_jobs, com lease de 60 s e retomada por outra réplica, trava por alvo, deduplicação e até 5 tentativas com backoff. drainProcessJobs() existe para testes determinísticos.
Proteção de exclusão
{
"deleteProtection": [
{ "sourceModel": "Pagamento", "foreignField": "projetoItemId", "message": "Não é possível excluir o item porque existem pagamentos vinculados." }
]
}
A verificação ocorre no CRUD oficial antes da exclusão — sem sobrescrever métodos do Mongoose.
Invariante financeira atômica
{
"atomicInvariants": [
{
"name": "pagamentos-limitados-ao-contratado",
"kind": "relatedSumLteParentField",
"parentModel": "ProjetoItem",
"parentLocalField": "projetoItemId",
"sourceField": "valor",
"parentField": "contratacaoTotal",
"match": { "canceladoNaCentral": { "neq": true } },
"tolerance": 0.01,
"code": "PAGAMENTO_ACIMA_CONTRATADO",
"message": "A soma {total} não pode ultrapassar o valor contratado {limit}."
}
]
}
O Core executa a mutação em transação MongoDB e incrementa uma versão interna no pai antes de somar. Duas inclusões simultâneas disputam a mesma escrita: uma conclui, a outra recebe conflito transitório, é repetida, refaz a soma e é aceita ou rejeitada.
Alterações reais
O contexto de defineValidation inclui changedFields: somente campos cujo valor final difere do anterior, incluindo derivados pelo Core. Isso evita efeitos colaterais disparados por round-trips sem alteração real.
Fronteira recomendada
| Pertence ao Core (process manifest) | Permanece na Central |
|---|---|
| Transições e bloqueios de etapa | Fórmula comercial específica |
| Status operacionais recorrentes | Tipos de responsáveis do negócio |
| Dependências e recálculos cross-model | Regras fiscais |
| Agregações relacionadas | Integrações e mapeamentos |
| Proteção de exclusão | Mensagens e condições próprias |
| Invariantes concorrentes, lote/assíncrono | — |
APIs públicas
const {
validateProcessManifest, registerProcessManifest, loadProcessManifest,
prepareProcessMutation, resolveProcessBindings, assertReferencePolicies,
assertAtomicInvariants, assertDeleteAllowed, recalculateDependents,
drainProcessJobs, ProcessManifestError,
} = require("@oondemand/oon-core-back");