Ponto de partida
Oito decisões
Duas camadas de regra: base e complementar. A base é a alçada financeira — uma só vence, escolhida por prioridade. As complementares empilham: todas que casam entram na cadeia. Controles de compra são ortogonais — alçada, dono do centro de custo, aprovador de categoria, Jurídico, LGPD, Qualidade — e um motor de regra única força o produto cartesiano dessas dimensões em fluxos configurados à mão.
O motor não sabe o que é um pedido. Ele conhece um assunto com valor, empresa, requisitante e um conjunto de linhas com nó de centro de custo e nó de categoria. ApprovalProcess é entidade de configuração — mesmo padrão do ItemType — e é o que faz requisição, cotação, contrato e alteração bancária percorrerem o mesmo código. Desde 04/09/2026 vale para toda aprovação humana do Nexio: cadastro e homologação de fornecedor, alteração bancária, curadoria de item, exercício e remanejamento de verba são instâncias deste motor. O motor decide; o módulo dono do documento executa o efeito.
Cadeia congelada na submissão; pessoas resolvidas na ativação do passo. O snapshot completo deixa a tarefa apontando para quem saiu da empresa; a resolução tardia total impede mostrar a cadeia ao requisitante no momento em que ele submete. O híbrido entrega as duas coisas — e é para onde Oracle e Coupa convergiram.
O passo tem escopo: documento, linha ou fatia de rateio. É o que permite avaliar a alçada sobre os R$ 40 mil que couberam à área, e não sobre os R$ 400 mil do pedido inteiro. O Protheus dispara pelo total; Coupa e Ariba tratam rateio como dado contábil sem efeito no fluxo.
Paralelo é dentro do nível, não entre níveis. Cada nível tem um modo: qualquer um, todos ou quórum de N — e em todos eles uma rejeição encerra a instância. Entre níveis é sempre sequencial. A v1.0 definia o oposto, e nunca leu o campo — trocar a semântica ainda é de graça, e esta é a que o mercado inteiro usa.
Nada é aprovado por omissão. SLA vencido notifica ou escalona; nunca aprova. Aprovação automática existe como tipo de nível declarado — "abaixo de R$ 500 com fornecedor homologado" — e jamais como consequência de tempo, nem como chave que deixe o requisitante decidir o próprio pedido. Aprovar por decurso de prazo é a funcionalidade que destrói a validade jurídica de toda a trilha.
A alçada mora na pessoa, dentro do nó. CostCenterAssignment.approvalLimit. O nível do tipo "sobe até quem tenha alçada" caminha a árvore de centro de custo até encontrar quem cubra o valor. Trezentos centros de custo passam a ser trezentas linhas de vínculo, não trezentos fluxos — e é o modelo que o cliente vindo do Protheus já tem na cabeça.
Toda decisão é evento, e a instância é reconstruível a partir deles. ApprovalEvent é append-only, com autor, papel pelo qual agiu, delegação em vigor e o passo alcançado. O estado do agregado é projeção, não fonte. Desde v2.17 a decisão tem esquema: tabela de campos, enum fechado de ações, ordenação por sequence e a invariante de imutabilidade escrita — até então esta era a única entidade do acervo declarada fonte da verdade sem nenhum campo definido. Sem isso, "por que este pedido foi aprovado" só tem resposta na cabeça de quem estava lá.
Fundamentos
Sete princípios
1. Configuração e execução são agregados diferentes
Editar uma política não muda documento que não foi reavaliado. A instância guarda o id e a versão de cada política que a formou, e a versão é imutável: alterar uma política publica uma versão nova e aposenta a anterior, que continua respondendo pelas instâncias vivas. É a regra RN-F08 da spec de junho, elevada de detalhe a invariante.
Reescrito em v2.17, e a diferença importa. A formulação antiga — "não pode mudar um documento em andamento" — não convivia com a RN-AP-20, que manda reavaliar quando muda a categoria justamente porque isso "pode acionar controle complementar que não existia". O que a invariante protege é o documento parado: nenhuma edição de política mexe numa cadeia sozinha. A reavaliação é o único caminho pelo qual configuração nova entra numa instância viva, ela é sempre disparada por uma mudança no documento, e recasa contra as políticas vigentes na data da reavaliação (RN-AP-47). Congelar as versões antigas na reavaliação seria pior: a devolução reenviada é o caminho mais comum do produto, e o documento voltaria em regra que o cliente já corrigiu.
2. Quem aprova nunca é quem pede
O requisitante é excluído de toda tarefa da própria instância — inclusive quando é ele o responsável pelo centro de custo. Nesse caso o motor sobe um nível na árvore; se a subida esgotar, cai no aprovador de reserva do processo. Recusar silenciosamente ou deixar o próprio aprovar são os dois erros que auditoria encontra primeiro. Não há parâmetro que desligue esta regra: allowSelfApproval não existe. Tenant em que o requisitante é o único aprovador possível recebe a recusa do princípio 4 — e resolve com um segundo aprovador ou com o aprovador de reserva, não com exceção.
3. Ambiguidade é erro de configuração, nunca desempate silencioso
Duas políticas base com a mesma prioridade e critérios que se sobrepõem são recusadas no momento de publicar, com 409 — e, se ainda assim duas casarem o mesmo documento em execução, a submissão é recusada com 422, nunca desempatada em silêncio. A garantia tem duas camadas: a verificação completa na aplicação, na mesma transação da publicação e serializada por bloqueio de linha na cadeia de ancestrais do nó, e uma guarda parcial no banco, quando o banco a oferecer. O que fica proibido é o que a implementação anterior fazia: varredura em memória, sem lock, que perde a corrida de dois POST simultâneos.
4. Um passo sem aprovador possível é recusa explícita
Não existe passo pulado por falta de gente. Se a resolução devolve vazio depois do rollup, da delegação e do grupo, o motor tenta o aprovador de reserva do processo; se também não houver, a submissão é recusada com 422 nomeando o passo. Pular é como um pedido de R$ 2 milhões passa sem que ninguém perceba.
5. A cadeia é mostrada antes de ser percorrida
O requisitante vê a cadeia inteira ao submeter, e qualquer pessoa com permissão pode simular sem criar documento. Nenhum ERP brasileiro pesquisado tem simulador; no Oracle ele existe atrás de privilégio especial. Aqui a lógica de seleção já é a mesma consulta — expor é quase de graça, e é o que transforma configuração de aprovação de adivinhação em engenharia.
6. O motor consome contratos, não caminha em árvores
Centro de custo publica path, responsáveis efetivos, alçada acumulada e empresa. Catálogo publica path de categoria. Orçamento publica saldo. O motor combina — e por isso pode ser testado sem nenhum dos três.
7. Toda aprovação humana é uma instância do motor
Onde uma segunda pessoa decide sobre o pedido de outra, existe um ApprovalProcess — sem exceção por módulo. O módulo dono do documento submete, guarda o resultado e aplica o efeito (carência bancária, movimento do razão, estado do item); o motor resolve quem, em que ordem, em quanto tempo e com que alçada. O motor decide; o módulo executa. Atos unilaterais com motivo — bloquear fornecedor, promover manualmente, revogar anexo, substituir cotação, forçar duplicata, expurgar arquivo — não são aprovação: quem age é quem decide, e o que os protege é permissão, motivo codificado e trilha. Regra de 04/09/2026, que fecha o achado A8 da contra-análise: até a v2.6 o cadastro e a homologação de fornecedor e a curadoria de item tinham rota de aprovação própria, e a alteração bancária tinha um caminho no motor e outro no módulo.
Modelo
Mapa de entidades
Configuração — o que o cliente parametriza
| Entidade | Cardinalidade | O que é |
|---|---|---|
| ApprovalProcess | configuração · por tenant | Qual documento é aprovável. Define o valor avaliado, os critérios disponíveis, as ações permitidas, o aprovador de reserva, o uso de motivo da rejeição e da devolução, as flags que o assunto expõe e se nasce com política padrão semeada. Dez tipos-semente: REQUISITION, QUOTATION, PURCHASE_ORDER, CONTRACT, SUPPLIER_REGISTRATION, SUPPLIER, BANK_ACCOUNT_CHANGE, ITEM, BUDGET, BUDGET_CHANGE. |
| ApprovalPolicy | N por processo · versionada | A regra. kind = Base ou Supplemental, prioridade, critérios, vigência e níveis. Versão imutável. |
| ApprovalCriteria | value object da política | Quinze critérios: faixa de valor, nó de centro de custo, nó de categoria, nó de conta contábil, tipo de item, destinação, empresa, estabelecimento, UF de entrega, fornecedor, flags do assunto, estado do orçamento, desvio do menor preço, faixa de risco do fornecedor e categoria de homologação. Critério ausente casa tudo. |
| ApprovalLevel | 1..N por política | Ordem, tipo de aprovador, alvo, modo dentro do nível, SLA próprio, valor de ativação e rótulo exibido — e o alvo do escalonamento (escalationTargetType · escalationTargetRef), exigido quando o processo escala e este nível não caminha árvore. Novo em v2.10 |
| ApprovalGroup | N por tenant | Pool nomeado de usuários — "Comitê de Investimentos", "Jurídico". Referenciado por nível; o modo do nível decide se basta um. |
| ApprovalGroupMember | N por grupo · dentro do agregado | Quem está no grupo, com vigência — mesma disciplina do vínculo de centro de custo. A resolução do nível lê os membros vigentes na ativação do passo, e não na submissão: um comitê que muda de composição em outubro decide em outubro com quem está nele. Não é agregado próprio — nasce e morre com o grupo, e por isso a contagem de agregados não muda. Novo em v2.9 |
| CostCenterAssignment | do Centro de Custo | Onde a alçada da pessoa mora (approvalLimit). O motor lê; não escreve. |
| ApprovalDelegation | N por usuário · uma vigente por processo | Ausência temporária. Uma linha por processo — scope nulo significa "todos" e conflita com qualquer outra —, cada uma com seu delegado, seu teto de valor e seu período, de fim obrigatório. Novo em v2.10 |
| ApprovalProcessSettings | 1 por tenant × processo | Os parâmetros que variam por processo: dedupe, tolerância de reavaliação, SLA padrão, comportamento de escalonamento, aprovador de reserva e uso de motivo. Chamava-se ApprovalSettings até v2.16, e foi renomeada quando nasceu a configuração de tenant — cada nome passou a declarar a própria chave. Não há chave de autoaprovação — ver RN-AP-08. |
| ApprovalSettings | 1 por tenant | Nova em v2.16. Os parâmetros do motor que não variam por processo: o limiar de materialidade do rateio (RN-AP-15), o teto de duração da delegação (RN-AP-36) e o expediente com a lista de dias não úteis (RN-AP-13). Uma linha por tenant, criada no provisionamento com os padrões do produto. Fecha a pendência "configuração por tenant do Nexio" do lado do fluxo, no padrão do CurrencySettings. |
Execução — o que o motor escreve
| Entidade | Cardinalidade | O que é |
|---|---|---|
| ApprovalRequest | aggregate root · 1 por documento vivo | A instância. subjectType + subjectId, empresa, requisitante, valor avaliado, moeda, estado, e o snapshot das políticas que a formaram com suas versões. Mais três colunas em v2.17: roundNumber (int, a rodada corrente — sobe na devolução reenviada e na reabertura, RN-AP-43), version (int, o token de concorrência da raiz por onde toda decisão serializa, RN-AP-44) e budgetRequirementUnmet (RN-AP-42). É dona de anexo desde 09/09/2026 (ownerType = ApprovalRequest): da evidência que o aprovador junta depois da submissão — parecer, cotação de terceiro, autorização por fora —, já que o documento em aprovação não aceita mais alteração; o documento que sustenta a proposta antes da decisão continua sendo do agregado que a propôs. CanWrite é o aprovador pendente da rodada corrente, e mais ninguém. As onze respostas do descritor estão declaradas na página do Anexo, em O dono polimórfico — este módulo implementa, não redecide. |
| ApprovalStep | 1..N por instância | Um nível resolvido. Ordem, origem (qual política e nível), escopo, modo, SLA, estado — e a marca authorityExceeded quando a alçada esgotou. |
| ApprovalAssignment | 1..N por passo | A tarefa de uma pessoa. Origem da atribuição, estado, decisão, comentário, momento. É a entidade que a spec de junho não tinha — e sem ela não existe fila endereçável, aprovação por papel, delegação registrada nem paralelo dentro do nível. |
| ApprovalEvent | append-only · dentro do agregado da instância | Toda transição, com autor, ação, alvo e metadados. Fonte da verdade da trilha. Ganhou esquema em v2.17: tabela de campos, enum fechado de ações, chave de ordenação (approvalRequestId, sequence) e a invariante de imutabilidade — ver Instância, passo e tarefa (RN-AP-48). É ele que a GET /approvals sustenta, e é dele que sai o relatório de aprovação que o Motivo delega a este módulo. |
| ApprovalToken | opcional · fase 2 | Link assinado para aprovar por e-mail sem sessão. De uso único, com expiração, vinculado a um ApprovalAssignment. |
Os três níveis da execução não são zelo arquitetural. Instância sem passo não sabe onde está; passo sem tarefa não sabe de quem está esperando. Colapsar tarefa em passo — que é o desenho da spec de junho — obriga a resolver "quem pode agir" em toda leitura de fila, transforma a consulta do aprovador numa varredura, e não deixa onde registrar que Ana agiu como substituta de Bruno na delegação nº 12.
Mecânica
Da submissão à decisão
Configuração
Processos aprováveis
ApprovalProcess é o que torna o motor genérico. Cada processo declara o que significa "valor" naquele documento, quais critérios fazem sentido e o que acontece quando a cadeia termina.
| Processo | Valor avaliado | Critérios que fazem sentido | Efeito da aprovação | Política padrão semeada |
|---|---|---|---|---|
| REQUISITION | Total estimado, por fatia de rateio | valor, centro de custo, categoria, conta contábil, tipo de item, destinação, orçamento | Libera para cotação ou para pedido | — · o tenant configura (RN-AP-03) |
| QUOTATION | Valor da proposta escolhida | valor, categoria, fornecedor, desvio do menor preço | Libera a emissão do pedido | — |
| PURCHASE_ORDER | Total do pedido, por fatia | valor, centro de custo, categoria, conta contábil, fornecedor, empresa, orçamento | Autoriza o envio ao fornecedor | — |
| CONTRACT | Valor total comprometido | valor, categoria, fornecedor. Prazo e cláusula de exclusividade: a definir na spec do Contract | Coloca o contrato em vigência | — |
| SUPPLIER_REGISTRATION | — | fornecedor (tipo de pessoa é flag do assunto) | Cadastro passa a Approved; o relacionamento continua Prospect | PermissionHolder(approve:suppliers) · Any |
| SUPPLIER | — | categoria de homologação, faixa de risco, fornecedor (sancionado) | Grava o resultado da homologação e promove a SpendAuthorized | PermissionHolder(manage:supplier-qualifications) · Any; recomendada: Panel de quatro grupos — jurídico, financeiro, técnico, qualidade |
| BANK_ACCOUNT_CHANGE | Média mensal de compras ao fornecedor nos últimos 12 meses; zero para fornecedor novo | valor, fornecedor, flags do assunto (titular ≠ fornecedor, chave PIX não verificada) | Aplica a proposta ao registro vivo e inicia a carência | PermissionHolder(approve:supplier-banks) · Any, mais um nível Role de controladoria quando a flag titular ≠ fornecedor estiver presente; mais um degrau incondicional de Quorum(2) sobre a mesma permissão, sem activationMinValue — quatro olhos, e um "não" basta. O degrau é incondicional de propósito (v2.17): o valor avaliado deste processo é a média mensal de compras, e enquanto PurchaseOrder e Invoice não existirem — que é o horizonte do MVP — todo fornecedor avalia zero. Uma política recomendada por faixa ("Quorum(2) acima de R$ 100 mil") nunca casaria, toda troca de conta cairia no PermissionHolder · Any — uma assinatura só — e o controle nasceria degradado funcionando, que é o pior modo de falha que existe. O cálculo da média fica como gancho nomeado: quem o produz é o módulo do documento de compra, sobre o valor aprovado, na moeda funcional da empresa, e até ele existir o degrau não depende dele. Não escala e não é delegável (allowEscalation e delegable falsos na semente): o decisor deste processo não muda por decurso de prazo nem por férias |
| ITEM | — | categoria, tipo de item, flags do assunto (regulado, dado pessoal, crítico) | Item passa a Approved; a rejeição grava o motivo no item | PermissionHolder(approve:catalog-items) · Any |
| BUDGET | Total orçado do exercício | valor, empresa | Publica o exercício e arquiva a versão substituída | — |
| BUDGET_CHANGE | Delta da verba | valor, centro de custo, conta contábil | Aplica o remanejamento | — |
Quatro desses processos não têm valor, e é por isso que eles precisam existir na lista. Cadastro e homologação de fornecedor, alteração de dado bancário e curadoria de item são aprovações sem dinheiro — e a alteração bancária é o controle antifraude mais importante de um sistema de compras, porque é o vetor de fraude mais explorado no Brasil. Um motor amarrado a "faixa de valor" não consegue aprová-las, e o produto termina com dois motores. Foi o que aconteceu até a v2.6: Fornecedor e Item tinham rotas de aprovação próprias, e a alteração bancária tinha um caminho aqui e outro lá. O princípio 7 fecha isso.
A alteração bancária tem valor, só não é o da proposta. O valor avaliado é a média mensal de compras ao fornecedor nos últimos doze meses — é o que separa a troca de conta de quem recebe R$ 10 mil por ano da de quem recebe R$ 10 milhões, e é o "valor médio histórico" que a tabela prometia sem ter como ler. Fornecedor novo avalia zero, e é a política do tenant que decide se zero merece um nível a mais.
Política padrão semeada: o dia 1 se comporta como antes. Todo processo sem valor nasce com a política de reserva já publicada — isDefault, critérios vazios, um nível PermissionHolder apontando para a permissão que autorizava o clique na rota antiga (approve:suppliers, manage:supplier-qualifications, approve:supplier-banks, approve:catalog-items), modo Any. Quem aprovava ontem aprova hoje, agora pelo motor, com fila, SLA, delegação e trilha — com uma diferença deliberada: quem cadastrou não aprova (RN-AP-08), que é a separação de funções que auditoria de cadastro de fornecedor exige. Tenant em que o solicitante é o único detentor da permissão cai na RN-AP-09; é configuração de onboarding — segundo detentor ou fallbackApproverId do processo —, não exceção, e o painel de administração avisa "processo com um único aprovador possível". O tenant edita a política quando quiser alçada; nunca fica sem ela. Os processos com dinheiro continuam exigindo configuração para ativar (RN-AP-03), porque alçada financeira não tem padrão sensato.
O que o motor não decide. Bloquear e desbloquear fornecedor, promoção manual a SpendAuthorized, revogar anexo, substituir cotação, forçar item sobre duplicata, expurgar arquivo e desligar legal hold são atos de uma pessoa só, com permissão, motivo codificado e trilha. Não há pedido de um e decisão de outro, logo não há instância. Um destes virar aprovação é decisão de spec, não de política. A importação de itens com autoApprove é carga, não aprovação: cria o item já aprovado sem instância, com a permissão e a trilha da importação.
Configuração
Política e critérios
| Campo | Tipo | Regra |
|---|---|---|
| processId | Guid | A qual processo esta regra se aplica. Obrigatório. |
| kind | enum | Base ou Supplemental. Decide se a política concorre ou empilha. Imutável depois de publicada. |
| priority | int | Só tem efeito em Base: menor número vence. Em Supplemental, ordena a posição na cadeia. |
| isDefault | bool | Exatamente uma política Base por processo é a de reserva, com critérios vazios. É a RN-CF03 da spec de junho, que o agregado nunca teve. |
| criteria | value object | Ver abaixo. Critério ausente casa tudo. |
| levels | 1..N | Ordenados. Uma política sem nível é recusada na publicação. |
| version | int | Imutável. Editar publica versão nova e aposenta a anterior, que segue respondendo pelas instâncias vivas. |
| validFrom / validTo | date / date? | Vigência da política. Fora dela, a política não é candidata — e o histórico continua legível. |
| fallbackApproverId | Guid? | Usado quando um nível resolve vazio. Se nulo, herda o do processo. |
Critérios disponíveis
| Critério | Operador | Fonte |
|---|---|---|
| Faixa de valor | intervalo semiaberto [min, max) | Valor avaliado do processo, na moeda funcional da empresa do documento |
| Centro de custo | nó, valendo para a subárvore | Prefixo de path. Sub-áreas criadas depois passam a ser cobertas sem tocar na regra |
| Categoria | nó, valendo para a subárvore | Prefixo de path do CategoryNode, pela mesma mecânica |
| Tipo de item | em lista | ItemType das linhas |
| Destinação | igualdade | predominantUse — CAPEX ou OPEX, sem depender do centro de custo |
| Empresa | em lista | companyId do documento, derivado do estabelecimento |
| Estabelecimento | em lista | establishmentId do documento. Novo. É o recorte para obra, filial em regime especial ou CD com alçada própria |
| Fornecedor | em lista · ou estado | Fornecedor da linha, ou o fato de ele ser Prospect, sancionado ou com documento vencido |
| Flags do assunto | presença | Cada processo declara as flags que o seu assunto expõe (subjectFlags): no catálogo, regulado, com dado pessoal, crítico; na alteração bancária, titular ≠ fornecedor e chave PIX não verificada; no cadastro de fornecedor, o tipo de pessoa. Nunca nós inventados na taxonomia. Até a v2.6 chamava-se "flag do item" |
| Conta contábil | nó, valendo para a subárvore | Prefixo de path do GLAccount da fatia — obrigatória na submissão, RN-CC-29 — e, no BUDGET_CHANGE, da linha de destino. Terceira árvore, mesma mecânica. Novo em v2.7 |
| Faixa de risco do fornecedor | em lista | riskTier da homologação em curso (SUPPLIER) ou da vigente para a categoria da linha (demais processos). Novo em v2.7 |
| Categoria de homologação | nó, valendo para a subárvore | scopeCategoryIds da homologação, por prefixo de path do CategoryNode. Só no processo SUPPLIER — o critério "categoria" lê as linhas do documento, e uma homologação não tem linhas. Novo em v2.7 |
| Orçamento | estado | Ok, Warning, Exceeded, Uncovered. Os quatro desde v2.17 — são os quatro que evaluate:budget-states devolve, e listar três deixava o motor sem tratamento para o sinal que mais bloqueia (RN-AP-53) |
| UF de entrega | em lista | deliveryStateCode das linhas. Novo. Entrega interestadual muda alíquota, DIFAL e substituição tributária — e muitas empresas querem que ela passe por quem responde pelo fiscal antes de virar pedido |
| Desvio do menor preço | percentual | Só no processo de cotação |
UF de entrega é critério de linha avaliado sobre o documento. Um documento pode ter linhas entregando em UFs diferentes, então o critério casa quando qualquer linha satisfaz — mesma mecânica já usada em centro de custo e categoria, e pela mesma razão: uma regra que só dispara quando o documento inteiro satisfaz é uma regra que nunca dispara. O recorte de o que aquele nível aprova continua sendo do scopeMode do passo: Document manda o documento inteiro para o fiscal, Line manda só as linhas interestaduais. Sem isso escrito, cada implementação escolhe uma.
Política de empresa é critério, não coluna. Com multiempresa especificada em Empresa e Estabelecimento, a tentação é dar um companyId à própria política. Não é preciso e seria pior: o critério Empresa em lista já faz esse recorte, e faz melhor — uma política Base do grupo inteiro convive com uma Supplemental de uma empresa só, e a cadeia final é a união das duas. Uma coluna de escopo criaria um segundo mecanismo concorrente para a mesma pergunta.
Aprovador precisa de escopo na empresa do documento — e isso é garantido antes, não aqui. Desde 08/09/2026 o vínculo de centro de custo só é criado para quem alcança a empresa do nó (RN-CC-30), de modo que quem o motor resolve já enxerga o documento. Em passo de escopo Slice a empresa que vale é a da fatia (CostAllocation.companyId), não a do documento — na v1 as duas coincidem sempre, e a distinção existe porque na compra centralizada o documento é da matriz e o rateio sai de estabelecimento da filial. Escrito em 09/09/2026, junto com a coluna. Isso vale para os tipos que caminham a árvore; para os que não passam por vínculo nenhum — SpecificUser, Role, PermissionHolder, Panel, ApprovalGroup, a delegação e o próprio reserva — a guarda equivalente é na publicação da política, e está na RN-AP-40. O que sobra para o motor é defesa em profundidade, para o caso em que o escopo foi revogado depois do vínculo: o nível é tratado como resolvido vazio, cai no fallbackApproverId — e, desde a v2.12, não em silêncio. Ver RN-AP-39.
Base concorre; complementar empilha
Uma requisição de R$ 80 mil de software com dado pessoal para o centro de custo /TI/INFRA/ resolve uma política base — a faixa de R$ 50 mil a R$ 100 mil — e duas complementares — "software com dado pessoal exige o DPO" e "categoria TI exige o arquiteto". A cadeia final é a união ordenada e deduplicada das três. Com motor de regra única, essa mesma combinação exigiria uma política escrita à mão para cada cruzamento de faixa × categoria × flag.
Especificidade não desempata — de propósito
Entre duas políticas base que casam, vence a de menor prioridade numérica, e ponto. Desempate por "regra mais específica" parece elegante e produz configurações que ninguém consegue prever: qual é mais específica, a que amarra centro de custo ou a que amarra faixa de valor? A prioridade explícita é feia e é auditável.
Sobreposição entre bases é recusada ao publicar — guarda parcial no banco, verificação completa na aplicação
Duas políticas base com a mesma prioridade e critérios que se cruzam nunca coexistem. Até a v2.5 esta spec dizia que a garantia vivia inteira no banco, numa restrição de exclusão sobre (tenant =, processo =, prioridade =, faixa de valor &&, nó de CC =, nó de categoria =). Não é verdade, por três razões: igualdade não colide quando o nó é nulo — e nulo é o caso normal, o da política de reserva e o de toda regra "só por valor"; /TI/ e /TI/INFRA/ se sobrepõem por rollup, e igualdade não enxerga; e os outros critérios (empresa, estabelecimento, tipo de item, destinação, fornecedor, flags, UF, orçamento, desvio) e a vigência ficam de fora, o que recusa como sobrepostas políticas de empresas diferentes ou de janelas disjuntas.
A regra passa a ter duas camadas. Na aplicação, sempre. Ao publicar uma versão de política base, o serviço carrega as bases ativas do mesmo tenant, processo e prioridade cuja vigência cruza a nova e recusa com 409 APPROVAL_POLICY_OVERLAP_CONFLICT se alguma tiver interseção não vazia em todos os critérios — nulo casa tudo, nó casa por prefixo nos dois sentidos, faixa e vigência por interseção. A verificação corre na mesma transação da publicação, serializada por bloqueio de linha (SELECT … FOR UPDATE ou o equivalente do banco) na cadeia de ancestrais do nó de centro de custo — a mecânica que o Centro de Custo já usa para a cascata —, para que dois POST simultâneos não passem os dois. Publicar versão nova aposenta a anterior na mesma transação, antes de verificar, para não colidir consigo mesma. No banco, quando ele oferecer. Uma restrição de exclusão sobre a mesma tupla, com sentinela no lugar do nulo (COALESCE(costCenterId, '0000…')) para que a reserva também seja protegida, continua valendo como segunda guarda — parcial, e a spec não depende dela.
Em execução, se duas bases de mesma prioridade casarem o mesmo documento — dado que escapou da verificação, ou política publicada antes desta regra —, a submissão é recusada com 422 APPROVAL_POLICY_AMBIGUOUS_UNPROCESSABLE, nomeando as duas políticas. É o princípio 3 aplicado até o fim. E GET /approval-policies/{id}/validate passa a listar sobreposição como inconsistência, para que a tela avise antes de publicar.
Depende do banco. A restrição de exclusão com operador de faixa (EXCLUDE … btree_gist, no Postgres) e a sintaxe exata do bloqueio de linha são específicas do banco, e a arquitetura ainda não o fixou. O que esta spec garante não depende disso: a verificação na aplicação, o erro de execução e o /validate. A guarda no banco entra quando o banco for escolhido, na sintaxe dele — decisão registrada no mapa de domínio. Regra de 03/09/2026.
A cobertura por subárvore vale para os descendentes futuros
A política guarda o nó, nunca a lista expandida de folhas. Uma sub-área criada no mês seguinte já nasce coberta. O override no descendente existe: uma política amarrada em /TI/INFRA/ com prioridade menor vence a de /TI/ para tudo que estiver abaixo de infraestrutura.
Configuração
Níveis e aprovadores
| Campo | Tipo | Regra |
|---|---|---|
| order | int | Sequencial dentro da política. Entre níveis é sempre sequencial. |
| approverType | enum | Ver a tabela seguinte. Determina a estratégia de resolução. |
| target | Guid? / string? | Usuário, papel, grupo, permissão, e-mail externo, distância de subida ou lista de alvos (Panel), conforme o tipo. |
| mode | enum | Any · All · Quorum(n). É aqui que vive o paralelo. Em todos, uma rejeição encerra a instância. |
| label | string(80)? | "Aprovação Gerencial". O que a tela mostra; se ausente, o motor deriva do tipo. |
| maxApprovalHours | int? | SLA do nível. Nulo herda o da política, depois o do processo. |
| activationMinValue | decimal? | Abaixo deste valor o nível não entra na cadeia. É como se declara "diretor só entra acima de R$ 100 mil" sem criar outra política. |
| scopeMode | enum | Document · Line · Slice. Um nível de alçada de centro de custo é normalmente por fatia; um de Jurídico é sempre do documento; um de fiscal é por linha — só as linhas que casam o critério geram passo (RN-AP-46). Line entrou no enum em v2.17: era usado em três lugares do documento e não existia aqui, o que tornava inconfigurável o padrão "interestadual passa pelo fiscal". |
Tipos de aprovador
| Tipo | Resolve para | Fase |
|---|---|---|
| SpecificUser | Um usuário fixo. Simples e frágil: é o tipo que quebra quando a pessoa sai. | MVP |
| Role | Todos os usuários ativos com o papel, no tenant. O modo do nível decide se basta um. | MVP |
| CostCenterOwner | Os effectiveOwners do nó da fatia, com rollup. O tipo mais usado na prática. | MVP |
| CostCenterOwnerUpToAmount | Sobe a árvore de centro de custo até encontrar quem tenha approvalLimit que cubra o valor da fatia. É a alçada acumulada da decisão 7. | MVP |
| CostCenterOwnerAtDistance(n) | O responsável n níveis acima do nó da fatia. É o "gestor do gestor" sem depender de hierarquia de pessoas. | MVP |
| ApprovalGroup | Os membros ativos do grupo. Comitês. | MVP |
| PermissionHolder | Todos os usuários ativos com a permissão, no tenant. É o alvo das políticas padrão semeadas: no dia 1, quem aprovava pela rota do módulo continua aprovando, agora pelo motor. Novo em v2.7 | MVP |
| Panel | Uma lista de alvos — papel, grupo ou permissão — e o nível só fecha com uma decisão de cada alvo. É o paralelo dentro do nível que a Decisão 5 permite, na forma que a homologação por área exige: jurídico, financeiro, técnico e qualidade ao mesmo tempo, um de cada. Modo fixo em All sobre os alvos; a primeira rejeição encerra. Novo em v2.7 | MVP |
| CategoryOwner | O responsável declarado no nó de categoria, com rollup. Exige um vínculo equivalente no catálogo. | fase 2 |
| RequesterManager | O gestor direto do requisitante, na hierarquia de pessoas do GrydAuth. | fase 2 |
| ExternalEmail | Um e-mail sem conta, via ApprovalToken. Sócio, conselheiro, cliente final em obra. | fase 2 |
| Auto | Aprova sozinho quando a condição do nível é satisfeita. Sempre declarado, nunca por decurso de prazo. | fase 2 |
Modo dentro do nível, com precisão. Any: a primeira decisão fecha o nível — e as demais tarefas viram Obsolete, não desaparecem, porque a trilha precisa mostrar quem mais estava na fila. All: o nível só fecha quando todos decidem; uma rejeição encerra a instância imediatamente. Quorum(n): fecha em n aprovações; qualquer rejeição encerra, como nos outros modos. É o "quatro olhos" — duas pessoas do financeiro numa troca de conta bancária. A semântica de comitê de investimento, em que um voto contrário não derruba a proposta e a instância só encerra quando n fica inalcançável, é modo próprio de fase 2, Vote(n), restrito aos processos que o produto declarar (BUDGET, BUDGET_CHANGE, CONTRACT) — nunca uma releitura do Quorum, nunca em BANK_ACCOUNT_CHANGE. Revisado em v2.8.
Contrato vigente · revisão consolidada
Alçada explícita e autorização por empresa
A alçada financeira é o vínculo vigente de responsabilidade: CostCenterAssignment.approvalLimit com approvalLimitCurrency, ou hasUnlimitedAuthority = true. Limite ausente não cobre valor algum. Não somar limites de pessoas distintas.
- Filtrar pelo teto de permissões da plataforma, concessão da operação na empresa do documento, vigência, segregação de funções e escopo do passo.
- Converter o valor avaliado para a moeda do limite usando o snapshot da submissão/revisão; ausência de cotação recusa a submissão.
- Procurar responsáveis do nó e subir a árvore até encontrar autoridade suficiente. Dedupe que agrupa fatias reavalia a alçada sobre o valor agregado.
- Esgotada a árvore, procurar o reserva explicitamente habilitado para a empresa, operação e valor, com limite ou autoridade ilimitada cadastrada. Ser reserva ou Owner da raiz não concede alçada.
- Sem cobertura, recusar a submissão com
APPROVAL_AUTHORITY_UNAVAILABLE_UNPROCESSABLE. Se a insuficiência surgir ao ativar um passo posterior, preservar votos já válidos e devolver a instância por configuração pendente. Avisar a controladoria por registro durável.
authorityExceeded registra a falha de resolução, sem autorizar sua superação. O simulador mostra a falta de cobertura; não exige autoridade ilimitada quando um limite finito já atende ao escopo. Delegação, reatribuição e escalada passam pelas mesmas verificações de empresa, operação, valor e impedimento de autoaprovação.
Configuração
Grupos e delegação
Pool nomeado de usuários, com membros que entram e saem com vigência — mesma disciplina do vínculo de centro de custo. Um grupo referenciado por política ativa não pode ficar vazio: a remoção do último membro é 409, com a lista de políticas que o usam. É o comitê que aprova sem que ninguém precise saber os nomes ao configurar.
Herdada da v1.0: delegante, delegado, período, encerramento automático por rotina diária e registro em auditoria com delegante, substituto e referência à delegação. Ganha escopo — scope, um processo por linha, nulo = todos — e teto de valor. A regra "no máximo uma ativa por pessoa" virou uma vigente por delegante e por processo em 08/09/2026 (v2.10): com dez processos, a regra antiga tornava impossível o que a própria spec dava como exemplo — delegar o pedido ao Bruno até R$ 50 mil, o item à Carla e não delegar a alteração bancária. Três linhas, três delegados, três tetos.
Uma delegação vigente por delegante e por processo, e com fim
A chave é (delegante, scope, período) sem sobreposição — scope nulo lido como "todos": verificada na aplicação, na mesma transação, com guarda de exclusão por interseção no banco quando ele existir (o banco ainda não está definido) — a mesma disciplina da sobreposição de políticas e do membro de grupo. Sobreposição é APPROVAL_DELEGATION_OVERLAP_CONFLICT · 409. validTo é obrigatório e não passa de maxDelegationDays (padrão 90, renovável): delegação sem fim é autoridade paralela permanente, que ninguém revisa e toda auditoria de compras encontra.
A tarefa delegada carrega a alçada do titular, nunca a do delegado
O passo já foi resolvido com o approvalLimit de quem responde pelo nó; o delegado apenas age. O teto da delegação só reduz — acima dele a tarefa fica com o titular. Um substituto com alçada maior aprovando o que o titular não poderia é o furo silencioso mais comum deste mecanismo, e aqui ele não existe por construção.
Processo pode não ser delegável
delegable por processo, false na semente do BANK_ACCOUNT_CHANGE: se a troca de conta do fornecedor exige quem responde pelo controle, ela não é coberta por quem está cobrindo férias. Criar delegação que alcance um processo não delegável é APPROVAL_DELEGATION_NOT_ALLOWED_UNPROCESSABLE · 422; a delegação de escopo nulo simplesmente não o alcança. Novo em v2.10
Delegação redireciona a tarefa; não substitui o aprovador
A ApprovalAssignment continua registrando o titular como aprovador de direito e o delegado como quem agiu, com o id da delegação. É por isso que a tarefa precisa ser entidade: não há onde guardar esse par num campo do passo.
Delegação vigente é lida na ativação do passo, não na submissão
Uma delegação que começa amanhã tem de valer para o passo que ativa amanhã, mesmo que a instância tenha sido criada hoje. É consequência direta da decisão 3.
Ativar a delegação transfere também o que já está na fila
Novo em v2.17. A regra acima cobre o passo que ativa amanhã e calava sobre o presente — e o presente é justamente o que a pessoa deixou aberto ao sair de férias. Quando a delegação entra em vigor, as tarefas Pending do titular que caem no seu escopo passam ao delegado: a ApprovalAssignment continua a mesma linha, com o titular como aprovador de direito e o delegado como quem age, e cada transferência grava approval-assignment.delegated com o id da delegação. O SLA não reinicia — o relógio é do passo, e zerá-lo faria a delegação virar o jeito mais fácil de comprar prazo. Acima do teto de valor da delegação a tarefa fica com o titular (RN-AP-37), e o fim da delegação faz o caminho de volta pelas que continuarem abertas. É o que Ariba, Coupa e Oracle fazem, e sem isso a delegação só serve para quem se lembra de criá-la antes de a fila encher (RN-AP-49).
Cadeia de delegação não encadeia
Se A delega para B e B delega para C, a tarefa de A vai para B — e para. Encadear produz caminhos que ninguém consegue prever e é o vetor clássico de escapar da alçada em três saltos. B pode recusar a tarefa (POST …/decline): ela volta para A, com evento e aviso.
Execução
Instância, passo e tarefa
| Entidade | Estados | Transição terminal |
|---|---|---|
| ApprovalRequest | InApproval · Approved · Rejected · Returned · Cancelled | Revisto em v2.17. Rejected e Cancelled são finais. Approved é terminal condicional: tem duas saídas declaradas — Approved → InApproval por revaluate e Approved → Cancelled por cancel —, cada uma com evento de reabertura e roundNumber incrementado na instância (RN-AP-43). Não nasce estado novo: Reopened seria um estado que só diz "já esteve aprovado", e isso é o que a rodada conta. Returned volta ao requisitante e a instância continua viva. Não há Draft — o rascunho é do documento, e a instância só nasce quando o módulo dono chama POST /approvals; nem Expired — nada vence por decurso (RN-AP-11) e o escalonamento não encerra passo nenhum. Os dois estavam no enum sem nada que os produzisse. Corrigido em v2.9 |
| ApprovalStep | Pending · Active · Approved · Rejected · Skipped · Obsolete | Skipped só por activationMinValue ou por dedupe — nunca por falta de aprovador. Obsolete é o que a reavaliação produz. |
| ApprovalAssignment | Pending · Approved · Rejected · Returned · Obsolete · Reassigned | Obsolete quando outra tarefa do mesmo nível já fechou o passo em modo Any. A linha permanece: a trilha mostra quem mais estava na fila. |
| Campo | Onde | O que guarda |
|---|---|---|
| policyId · policyVersion · levelOrder | passo | De onde este passo veio. Permite explicar a cadeia meses depois, com a política já editada três vezes. |
| scope · scopeRef · scopeAmount | passo | Document, Line ou Slice, com o id do alvo e o valor sobre o qual a alçada foi avaliada. |
| mode · quorum · slaDueAt | passo | Modo do nível, quórum exigido e o vencimento calculado na ativação. |
authorityExceeded | passo | Marca resolução sem alçada suficiente. Impede a decisão financeira até cobertura explícita; não autoriza Owners da raiz por omissão. |
| resolvedOutOfScope | passo | bool, gravado na ativação. Verdadeiro quando o alvo do nível resolveu para quem não alcança a empresa do documento e o passo caiu no reserva. Escopo revogado depois do vínculo é o único caminho que deveria produzi-lo (RN-AP-39). Novo em v2.12 |
| resolvedEmptyOnActivation | passo | bool, gravado na ativação. Verdadeiro quando o passo resolveu zero pessoas por passagem do tempo — vigência de membro de grupo vencida, papel sem detentor, Owner que deixou de ser vigente — e caiu no aprovador de reserva. É o terceiro irmão de authorityExceeded e resolvedOutOfScope (RN-AP-45). Novo em v2.17 |
| roundNumber | instância e passo | int. A instância guarda a rodada corrente; cada passo guarda a rodada em que nasceu. É o que responde "quantas vezes este pedido rodou e o que mudou entre as rodadas" depois que a reabertura passou a existir (RN-AP-43). Novo em v2.17 |
| version | instância | int. Token de concorrência da raiz: toda decisão passa expectedVersion e a divergência é 409. Não existe token no passo nem na tarefa — a serialização é uma só, na raiz (RN-AP-44). Novo em v2.17 |
| approverUserId | tarefa | Quem é o aprovador de direito. |
| actingUserId | tarefa | Quem de fato decidiu. Igual ao anterior, salvo delegação ou reatribuição. |
| origin · originRef | tarefa | Direct, ByRole, ByGroup, ByDelegation, ByEscalation, ByReassignment — com o id da delegação, do grupo ou do papel. |
| decision · reasonCodeId · comment · decidedAt | tarefa | A decisão, o motivo codificado, o comentário e o momento. Em rejeição e devolução os dois são obrigatórios: o código classifica para o relatório, o comentário explica o caso para quem vai corrigir. A tarefa grava também reasonCode e reasonLabel congelados — ver Motivo. |
documentRevisionId · relevantContentHash · authorizedAmount · authorizedCurrency · evidenceRefs | decisão / evento | Revisão e conteúdo autorizado, teto de valor, moeda e versões imutáveis das evidências. Cada preservação referencia a decisão original e registra prova de equivalência. |
| Campo | Tipo | O que guarda |
|---|---|---|
| id · tenantId | Guid · Guid | Identidade e tenant, como em toda entidade do produto. |
| approvalRequestId | Guid | Sempre preenchido, inclusive nos eventos de passo e de tarefa. É a chave de agrupamento da trilha. |
| sequence | long | Monotônico dentro da instância, alocado na mesma transação que incrementa o version da raiz (RN-AP-44). A chave de ordenação é (approvalRequestId, sequence) — nunca occurredAt, que empata em decisão em lote e em fatias paralelas fechando juntas. |
| roundNumber | int | A rodada em que o fato aconteceu (RN-AP-43). É o que separa "o comitê aprovou" da primeira rodada do da terceira. |
| stepId · assignmentId | Guid? · Guid? | Nulos nos eventos de instância; o de tarefa carrega os dois. |
| action | enum | Enum fechado — a tabela seguinte. Acrescentar valor é migração, como em todo enum do produto. |
| outcome | string? | O desfecho, onde a ação o admite: Approved · Rejected · Returned · Cancelled · ApprovedWithProviso. É o que permite uma ação só para "decidiu" sem perder o quê. |
| actorUserId · actorType | Guid? · enum | Quem agiu e por qual tipo de aprovador (Role, CostCenterOwner, PermissionHolder…). actorUserId nulo é o motor — ativação de passo, aprovação pela política, vencimento de SLA —, e é o único caso de evento sem pessoa. |
| onBehalfOfUserId · delegationId | Guid? · Guid? | O aprovador de direito e a delegação em vigor, quando quem agiu não é quem responde (RN-AP-17). |
| policyId · policyVersion · levelOrder | Guid? · int? · int? | De onde veio o passo que produziu o evento. É o que explica a cadeia com a política já editada três vezes. |
| scope · scopeRef · scopeAmount · scopeCurrency | enum? · Guid? · decimal? · char(3)? | O escopo sobre o qual se decidiu e o valor avaliado, na moeda em que foi avaliado. |
| exchangeRate · quoteUnit · rateDate | decimal? · int? · date? | A prova da comparação de alçada (RN-AP-52): a taxa congelada na submissão, o fator de cotação com que ela foi publicada e a data dela, gravados no evento da decisão. O fator entrou em 09/09/2026, pela mesma razão que o pôs no movimento do razão: taxa sem fator não reconstitui a conta em moeda cotada por 100 — o iene comparado com o fator errado erra por duas ordens de grandeza, e a trilha guardaria a prova de uma conta que ninguém consegue refazer. Sem isso, "por que este valor coube nesta alçada" não tem resposta depois que a cotação mudou. |
| reasonCodeId · reasonCode · reasonLabel · comment | Guid? · string? · string? · text? | O motivo codificado com rótulo congelado e o comentário. Obrigatórios juntos em toda ação que não seja aprovar (RN-AP-19). |
| occurredAt | timestamptz | Quando. Serve para medir, não para ordenar. |
| metadata | documento semiestruturado | O que é específico da ação — o alvo da reatribuição, o que mudou na reavaliação, o passo que a escalada acrescentou. Nunca o lugar de um campo pelo qual a consulta filtra: o que a GET /approvals filtra é coluna. |
| Alvo | Ações |
|---|---|
| Instância | approval-request.submitted · chain_resolved · decided · returned · resubmitted · revaluated · reopened · approved_by_policy · budget_requirement_unmet |
| Passo | approval-step.activated · closed · skipped · obsoleted · escalated · authority_exceeded · resolved_out_of_scope · resolved_empty_on_activation |
| Tarefa | approval-assignment.created · decided · info_requested · info_answered · reassigned · declined · delegated · obsoleted |
Imutável quer dizer sem rota de escrita. ApprovalEvent é append-only: não tem PUT, não tem DELETE, não tem soft delete — a exclusão lógica que o resto do produto herda da plataforma não se aplica aqui, e essa exceção é declarada, não esquecida. Corrigir um evento é gravar outro que o corrija, com metadata apontando o sequence corrigido. É a mesma disciplina do razão orçamentário, e pela mesma razão: uma trilha que pode ser editada não prova nada (RN-AP-48).
Por que só agora. A decisão 8 declarava o evento fonte da verdade desde a v2.0, e o BudgetEntry foi obrigado a ser "reconstruível" citando este precedente — que não existia por escrito. Era a única entidade do acervo sem tabela de campos.
Uma instância viva por documento. Reenviar um documento devolvido reaproveita a mesma ApprovalRequest, com uma nova rodada de passos e os anteriores marcados como Obsolete. Criar instância nova a cada reenvio parece mais limpo e quebra a pergunta "quantas vezes este pedido rodou e o que mudou entre as rodadas" — que é a métrica que expõe política mal configurada.
Mecânica
Congelar e resolver
| Momento | O que é fixado | Por quê |
|---|---|---|
| Submissão | Políticas aplicáveis e suas versões, valor avaliado, taxa de câmbio, rateio, limiar de materialidade efetivo, sequência de passos com tipo, escopo, modo e SLA | Editar a política amanhã não pode mudar a cadeia de um documento que já está circulando. É o que torna a trilha defensável. |
| Ativação do passo | Os aprovadores concretos, a delegação vigente, os membros do grupo e o vencimento do SLA | Um responsável que mudou entre a submissão e a chegada do passo tem de ser o novo. Congelar pessoas na submissão produz tarefas para quem já saiu. |
| Nunca | Quem já decidiu | Decisão tomada é evento. Nenhuma reavaliação, edição de política ou troca de responsável apaga uma assinatura. |
Dedupe: a mesma pessoa não assina duas vezes seguidas
Se Ana é responsável pelo centro de custo e membro do comitê, e os dois passos são consecutivos, ela aprova uma vez e o segundo passo é marcado Skipped com o motivo. Configurável por processo — há empresas que exigem a dupla assinatura de propósito, e há auditoria que a proíbe. O que não pode existir é o comportamento implícito.
O requisitante é removido de toda tarefa da própria instância
Antes de tudo, e sem chave que desligue. Se a remoção esvaziar o passo, o motor sobe um nível nos tipos de árvore e cai no aprovador de reserva nos demais; se esgotar, a submissão é recusada com 422, e a mensagem diz qual passo ficou sem ninguém e que o requisitante era o único aprovador possível — não "erro ao aprovar".
Resolução vazia nunca vira passo pulado
É a diferença entre um sistema que controla e um que parece controlar. Ordem exata: tipo do nível → rollup na árvore → delegação vigente → aprovador de reserva da política → aprovador de reserva do processo → recusa explícita.
Na submissão sem elegível, recusar com erro de configuração. Na ativação posterior, procurar reserva explicitamente habilitado; se também faltar, preservar o voto anterior e devolver a instância com motivo (RN-AP-45).
Cadeia inteira pulada é aprovação pela política, com nome
Quando todos os níveis têm activationMinValue acima do valor avaliado, a instância termina Approved sem tarefa e grava approval-request.approved_by_policy, com a política e a versão. É a única aprovação sem pessoa do MVP, e é declarada: aparece no simulador antes de submeter, e o /validate avisa que a política tem faixa sem decisor humano. Novo em v2.8.
Mecânica
Rateio e escopo do passo
Um documento tem N linhas; cada linha tem de 1 a N fatias de rateio, cada fatia com seu centro de custo e seu valor. O motor avalia as regras contra as linhas e fatias e decide sobre o documento inteiro — que é o padrão SAP, onde documentos são "aprovados na íntegra".
| # | Etapa | Detalhe |
|---|---|---|
| 1 | Agrega as fatias por centro de custo | Cinco linhas apontando para /TI/INFRA/ viram uma fatia agregada. Sem isso, o responsável recebe cinco tarefas do mesmo pedido. |
| 2 | Aplica a materialidade | Fatia abaixo de allocationMaterialityPercent e de allocationMaterialityAmount — o que for maior — é absorvida pela maior fatia para efeito de roteamento, e só. Continua contabilizada integralmente. O limiar é lido uma vez aqui e congela com a cadeia (RN-AP-06). Materialidade não é regra de orçamento: o orçamento sempre vê a decomposição integral, e Block numa fatia absorvida recusa a submissão do mesmo jeito (RN-AP-15). |
| 3 | Gera passos por escopo | Níveis com scopeMode = Slice geram um passo por fatia material, em paralelo entre si. Níveis com scopeMode = Line geram um passo por linha que casa o critério do nível, com scopeRef = id da linha e scopeAmount = valor da linha — a materialidade não se aplica (RN-AP-46). Níveis de documento geram um passo só. |
| 4 | Deduplica aprovadores | Se a mesma pessoa responde por duas fatias, ela recebe uma tarefa que cobre as duas, com o valor somado no scopeAmount. Em escopo Line a dedupe é por (pessoa, linha), não por (pessoa, fatia): quem responde pelo fiscal de duas linhas interestaduais recebe duas tarefas, porque o que ele examina é a linha. |
| 5 | Ordena | Passos de fatia do mesmo nível são paralelos entre si; os níveis seguem sequenciais. O documento avança quando todos os passos do nível fecham. |
Agrupar fatias pode reduzir tarefas, mas exige reavaliar a alçada sobre o valor agrupado e preservar cada responsável e origem na trilha. Ganho de produtividade e comparação com concorrentes são hipóteses a validar em piloto.
Um dono só, e é o tenant — decidido em 08/09/2026 (v2.15). O limiar vive na configuração de tenant deste documento e não varia por processo. Duas razões, e a primeira é de fato: só dois dos dez processos-semente têm fatia — REQUISITION e PURCHASE_ORDER —, e eles carregam o mesmo rateio em dois momentos, de modo que limiares diferentes fariam o pedido reabrir cadeia para fatias que a requisição já tinha dispensado. Nos outros oito não há o que dispensar: a alteração bancária avalia a média de compras, o exercício avalia o total orçado, e cadastro, homologação e curadoria não têm valor nenhum. A segunda razão é de controle: materialidade é dispensa — subir o número remove aprovadores —, e com herança por processo a tela do tenant mostraria 5% enquanto um processo rodasse a 30%. Com um valor só, a garantia da RN-AP-15 é uma consulta e uma linha de trilha. É a mesma escolha que tirou o allowSelfApproval em vez de lhe dar padrão: aperta-se a regra, não o controle. Se um dia aparecer o caso real de variar por processo, dois campos anuláveis no ApprovalSettings herdando deste valor são adição não-quebrante — o que não se desfaz é um número que já divergiu em produção.
Execução
Ações do aprovador
| Ação | Efeito | Comentário |
|---|---|---|
| Aprovar | Fecha a tarefa. O passo fecha conforme o modo; a instância avança para o próximo nível. | opcional |
| Rejeitar | Encerra a instância. O documento volta ao requisitante como rejeitado e não pode ser reenviado sem alteração. | obrigatório |
| Devolver para ajuste | Devolve ao requisitante sem encerrar. Os passos da rodada viram Obsolete; o reenvio abre uma rodada nova na mesma instância. | obrigatório |
| Solicitar informação | Pergunta ao requisitante sem tirar o documento da fila. O SLA pausa enquanto aguarda — senão o aprovador é penalizado por uma resposta que não depende dele. | obrigatório |
| Reatribuir | Passa a tarefa a outra pessoa elegível, uma vez, sem alterar o passo. Exige permissão própria e gera evento com origem e destino. | obrigatório |
| Aprovar com ressalva | Aprova e registra uma condição a cumprir — "mediante apresentação da ART". Não bloqueia o avanço; fica visível no documento e no recebimento. | obrigatório |
Devolver não é rejeitar, e essa distinção é o que faz o SLA significar algo
Sem "devolver", todo pedido com um erro de digitação é rejeitado e reenviado como novo — e a métrica de aprovação passa a medir qualidade de digitação em vez de decisão. É a ação mais usada em qualquer sistema que a oferece, e a primeira que falta em quem não a modelou.
Aprovação em lote existe; decisão em lote não
A tela pode aprovar dez tarefas numa ação, e o motor grava dez eventos, um por tarefa, cada um com seu timestamp. Um evento de lote pareceria uma decisão só e destruiria a rastreabilidade individual que a auditoria pede.
Contrato vigente · revisão consolidada
Revisões e validade da decisão
| Alteração | Efeito |
|---|---|
| Aumento de valor | Reavaliar além da tolerância. Dentro dela, preservar assinatura somente se o novo valor ainda couber no teto explicitamente autorizado; tolerância não amplia autorização. |
| Redução de valor | Pode preservar assinatura se todo o restante do conteúdo relevante e o escopo forem equivalentes. |
| Fornecedor, oferta, quantidade, unidade, especificação, rateio, empresa, prazo ou condição contratual | Reavaliar quando muda o conteúdo relevante, mesmo com valor total igual ou menor. Mudança de fornecedor é sempre material. |
| Política ou evidência usada na decisão | Reavaliação usa política vigente; assinatura de outra versão de política não é reaproveitada. Revogação de evidência material exige revisão, preservando histórico. |
| Correção apenas editorial | Registrar a diferença e demonstrar que o hash do conteúdo relevante permanece igual. |
Cada decisão grava documentRevisionId, relevantContentHash, authorizedAmount, moeda, escopo, policyId, policyVersion, nível e referências imutáveis das evidências. Ao preservar uma assinatura, manter sua revisão original e registrar a prova de equivalência com a revisão nova. Existir na cadeia nova não basta. A nova decisão exige autorização vigente; revogação de acesso não apaga assinatura histórica.
Pedido emitido mantém versão efetiva. A alteração é uma revisão candidata na mesma trilha de rodadas; enquanto circula, entregas e obrigações continuam ligadas à versão efetiva. Aprovação e emissão da alteração aplicam apenas o delta financeiro e quantitativo, após nova avaliação sob trava. Rejeitar a alteração mantém o pedido anterior. Cancelamento é ato de negócio separado, respeitando parcelas recebidas e obrigações exigíveis.
Antes da primeira emissão, uma devolução de requisição pode liberar ou recompor a reserva conforme seu desfecho documentado. Essa regra não se aplica indiscriminadamente a um pedido já emitido. roundNumber identifica rodadas da instância; documentRevisionId identifica conteúdo, não apenas tentativas.
Execução
SLA e escalonamento
O vencimento é calculado na ativação do passo, em horas úteis: o relógio só corre nos dias da semana marcados como úteis, dentro do expediente do tenant, e pula as datas da lista de dias não úteis. Contar em horas corridas faz todo passo ativado na sexta vencer no domingo, e o escalonamento vira ruído semanal.
Decisão de 08/09/2026 (v2.14). Não existe BusinessCalendar. O expediente e a lista nonWorkingDates são parâmetros do tenant, e o produto semeia a lista com os feriados nacionais — que são algoritmo, não cadastro: as datas fixas mais as derivadas da Páscoa, geráveis anos à frente sem ninguém digitar. O tenant acrescenta o feriado municipal e o recesso, ou tira o que não vale ali. Entidade com ciclo de vida próprio só se pagaria com um segundo consumidor, e não há: prazo de cotação e prazo de entrega contam em dias corridos, como o resto do produto. Três limites, registrados agora para não voltarem como defeito: a lista é do tenant, não do estabelecimento — filial em outro município acrescenta a data à lista de todos ou convive com ela —, não existe meio expediente, e errar uma data adianta um lembrete, nunca uma decisão (RN-AP-11).
Definido por processo, em escalationBehavior: Notify (lembrete ao aprovador e ao requisitante), Escalate (acrescenta uma tarefa, mantendo a original viva) e None. Não existe AutoApprove. O processo diz se escala; o nível diz para quem — só ele sabe a própria forma de resolução. NotifyManager saiu do enum em v2.9: avisar "o gestor" exige a árvore de subordinação do GrydAuth, que é fase 2 — a mesma dependência que mantém RequesterManager fora do MVP. Quem precisa do gestor hoje escala pela árvore de centro de custo, que é a hierarquia que o produto tem.
Decisão de 08/09/2026 (v2.10). Escalate não tem mais destino implícito. Em nível que caminha árvore (CostCenterOwner, CostCenterOwnerUpToAmount, CostCenterOwnerAtDistance(n)) continua sendo o responsável do nó pai — a hierarquia existe e o simulador a desenha. Em Role, SpecificUser, ApprovalGroup, PermissionHolder e Panel não existe "nível acima", e o nível precisa declarar escalationTarget, no mesmo vocabulário do próprio nível. Sem alvo, a política não publica: APPROVAL_ESCALATION_TARGET_REQUIRED_UNPROCESSABLE. O motor não inventa aprovador em nenhum outro ponto desta spec, e não passa a inventar no vencimento — cair no aprovador de reserva transformaria um nome só no aprovador universal de tudo o que atrasa, e bastaria deixar o relógio correr para chegar nele.
allowEscalation por processo, false na semente do BANK_ACCOUNT_CHANGE: no vencimento, só lembrete. Escalonar é ganho de produtividade; num controle antifraude é superfície de ataque — quem quisesse outro decisor bastaria não decidir até o prazo virar. Mesmo raciocínio do gate 9, que tirou a dissidência do Quorum em vez de afrouxar a RN-AP-10. Publicar política com Escalate em processo que não escala é APPROVAL_ESCALATION_NOT_ALLOWED_UNPROCESSABLE. Novo em v2.10
Revisto em v2.17. Em Any, a tarefa original continua pendente e a escalada acrescenta um caminho: as duas fecham o passo, e o evento diz qual chegou primeiro. Em All, Quorum(n) e Panel a escalada substitui a tarefa do titular — que vai a Obsolete —, porque acrescentar um caminho ali é reduzir o número de pessoas distintas que o modo exige: num Quorum(2) de quatro olhos, o titular atrasa, a escalada entra, e o quórum fecha com uma assinatura de gente que decidiu e outra de quem entrou por decurso de prazo. O número de pessoas distintas exigido pelo modo não muda nunca — é o que o modo significa (RN-AP-50).
E o alvo da escalada passa pelas mesmas guardas do alvo original: RN-AP-08 (o requisitante nunca decide a própria instância), alçada — o escalado precisa cobrir o scopeAmount do passo, ou o passo herda a marca authorityExceeded — e escopo de empresa (RN-AP-39/40). Um alvo de escalonamento que escapasse dessas três seria a porta que a RN-AP-08 fecha na entrada e deixaria aberta no vencimento.
A pergunta avisa o requisitante (nexio.approval-assignment.info_requested) e pausa o SLA do passo; a resposta entra por POST /approvals/{id}/answer-info, rota do requisitante, avisa quem perguntou (nexio.approval-assignment.info_answered) e retoma o relógio. Os dois avisos desde 08/09/2026: até então só a volta tinha slug, e a ida não — o documento parava esperando uma resposta que ninguém tinha sido avisado de dar. O tempo pausado fica registrado, para que o indicador separe "aprovador lento" de "requisitante lento".
Contrato vigente · revisão consolidada
Conclusão financeira na mesma transação
O motor chama explicitamente o contrato do processo. Sob a mesma conexão e transação, o dono do documento revalida orçamento, autorização e revisão, e responde com resultado tipado. Em sucesso, transiciona o documento e grava o efeito financeiro correspondente: Commitment na requisição; Obligation na emissão do pedido ou ativação do mínimo contratual; apenas delta na alteração efetiva.
Insuficiência na última decisão: persistir o voto, retornar documento e instância a Returned e registrar a notificação durável, sem Approved ou novo lançamento financeiro. Retornar ReturnedForBudget com motivos e valores. Falha técnica reverte voto e efeitos; repetição usa a mesma identidade da operação. O registro do voto não é conclusão financeira.
O orçamento devolve NotControlled, Ok, Warning, Exceeded ou Uncovered, origem da política e limites afetados. Block ou Uncovered impedem novo compromisso. RequireApproval exige cobertura Supplemental efetivamente concluída para o escopo excedido; se a avaliação final descobrir exigência nova, devolver para nova rodada. Materialidade dispensa roteamento, nunca consumo ou controle.
Eventos em memória são despachados após o commit produtor; reações obrigatórias, avisos de pendência e integrações têm registro durável. Cancelar uma revisão candidata não cancela a obrigação efetiva. Contratos completos em Contratos transacionais.
Diferencial
Simulador
POST /approvals:simulate recebe um documento hipotético — valor, empresa, linhas com centro de custo e categoria, requisitante — e devolve a cadeia que ele produziria, sem criar nada — passo a passo, com quem resolve em cada um, as marcas de exceção onde a cadeia não sai como o desenho previa — authorityExceeded, resolvedOutOfScope — e o limiar de materialidade efetivo com as fatias que ele dispensou, para que "por que esta área não aparece na cadeia?" tenha resposta antes de alguém perguntar (novo em v2.15). Três usos, e o terceiro é o que ninguém oferece:
Testar uma política antes de publicar, inclusive numa versão em rascunho. Configuração de aprovação é o lugar do produto onde o erro só aparece quando um pedido trava, semanas depois.
A mesma consulta alimenta a prévia mostrada na submissão: "este pedido passará por Ana, depois pelo comitê". Expectativa correta é metade das reclamações de fluxo de aprovação.
Reexecutar uma instância antiga contra a configuração da época e comparar com o que de fato aconteceu. Como a instância guarda as versões das políticas, a comparação é exata — e é a resposta a "prove que o controle funcionava em março".
Na prática
Exemplos de fluxo
Tudo o que um tenant consegue montar é combinação das peças definidas acima: um ApprovalProcess, a política Base que casa o documento, as Supplemental que empilham, e níveis com tipo, modo, escopo, valor de ativação e prazo. Esta seção existe para o time ver as combinações com números — um exemplo por processo-semente, uma requisição acompanhada do começo ao fim, e os padrões que se repetem entre processos. Nenhum exemplo usa recurso que não esteja nas seções anteriores; onde um parece pedir algo novo, a resposta está em "o que não dá para montar", no fim. Novo em v2.8.
As peças e o que cada uma muda
| Peça | Opções | O que muda no fluxo |
|---|---|---|
| Política | Base — concorre por prioridade, uma vence · Supplemental — empilha, todas que casam entram | A base decide a espinha da cadeia; cada complementar acrescenta níveis na posição que a prioridade dela dá. "OU" entre condições é duas complementares. |
| Critérios | Quinze, em conjunção; ausente casa tudo; nós de centro de custo, categoria e conta contábil valem por subárvore | Quem casa. Faixa de valor e nó são os mais usados; flags do assunto, estado do orçamento, fornecedor e UF de entrega são os que acionam controle extra. |
| Tipo do nível | SpecificUser · Role · PermissionHolder · ApprovalGroup · Panel · CostCenterOwner · CostCenterOwnerUpToAmount · CostCenterOwnerAtDistance(n) | Quem resolve — e se a resolução caminha a árvore de centro de custo ou lê uma lista. |
| Modo | Any · All · Quorum(n); Panel é sempre All sobre os alvos | Quantas decisões fecham o nível. Em todos, uma rejeição encerra a instância. |
| Escopo | Document · Line · Slice | Sobre o que a alçada é avaliada e quantos passos o nível gera: um por fatia material, em paralelo, ou um só para o documento. |
| Ativação | activationMinValue | Abaixo do valor o nível não entra — "diretor só acima de R$ 100 mil" sem escrever outra política. |
| Alçada | CostCenterAssignment.approvalLimit, lida por CostCenterOwnerUpToAmount | Sobe a árvore até quem cubra a fatia; esgotada a subida, aprova o topo com authorityExceeded marcado e aviso à controladoria. |
| Prazo | maxApprovalHours por nível, política ou processo · escalationBehavior só por processo · escalationTarget no nível | O prazo herda de baixo para cima; a resposta ao atraso, não. escalationBehavior tem um dono só — o processo, em ApprovalSettings —, para que o que acontece num vencimento não dependa de qual política casou; o para quem mora no nível, porque depende do tipo dele. Lembrete, tarefa declarada ou nada. Nunca aprovação. |
| Ausência | ApprovalDelegation, uma vigente por processo, com teto de valor e fim obrigatório | Para quem a tarefa vai enquanto o titular está fora — e o que continua com o titular: o que passa do teto, e todo processo marcado delegable = false. |
| Ações | aprovar · aprovar com ressalva · devolver · pedir informação · reatribuir · rejeitar | Devolver abre rodada nova na mesma instância; rejeitar encerra; pedir informação pausa o SLA. |
| Mudança | revaluationTolerancePercent · revaluationToleranceAmount | Quando a edição de um documento aprovado refaz a cadeia — e o que é preservado. |
Um exemplo por processo
Nomes, valores e centros de custo são inventados; a mecânica é a da spec. "Cadeia" é o que o simulador mostra ao requisitante antes de submeter.
REQUISITION · alçada por fatia, diretor só acima de R$ 100 mil, DPO por complementar
Configuração. Base "até R$ 50 mil" (prioridade 10): nível 1 CostCenterOwner · Any · Slice. Base "de R$ 50 mil a R$ 500 mil" (prioridade 20): nível 1 CostCenterOwnerUpToAmount · Any · Slice; nível 2 Role(Diretor de Compras) · Any · Document com activationMinValue R$ 100 mil. Supplemental "categoria /TI/SOFTWARE/ com a flag dado pessoal": um nível SpecificUser(DPO) · Document. Materialidade do rateio, que é do tenant e vale para todos os processos: 10%.
Documento. Ana, de /TI/INFRA/, requisita R$ 80 mil em licenças que tratam dado pessoal, rateados 75% em /TI/INFRA/ (R$ 60 mil) e 25% em /MARKETING/ (R$ 20 mil). Bruno é Owner de /TI/INFRA/ com limite de R$ 50 mil; Carla é Owner de /TI/ com limite de R$ 150 mil; Diego é Owner de /MARKETING/ com limite de R$ 50 mil.
Cadeia. Casam a base de R$ 50–500 mil e a complementar. Nível 1, por fatia, em paralelo: a fatia de TI sobe de Bruno (R$ 50 mil não cobre R$ 60 mil) para Carla; a fatia de Marketing fica com Diego. Nível 2 não ativa — R$ 80 mil está abaixo de R$ 100 mil — e o passo nasce Skipped. Depois, o passo do documento: o DPO. Ana vê "Carla e Diego em paralelo, depois o DPO" antes de submeter. Com R$ 120 mil, o diretor entraria entre os gestores e o DPO; se Ana fosse ela própria a Owner de /TI/INFRA/, seria removida e a fatia subiria para Carla do mesmo jeito.
QUOTATION · quem explica por que não escolheu a mais barata
Configuração. Base de reserva (critérios vazios): nível 1 Role(Gerente de Compras) · Any · Document. Supplemental "desvio do menor preço acima de 5%": nível Role(Diretor de Suprimentos) · Any. Supplemental "fornecedor da proposta em estado Prospect": nível PermissionHolder(manage:supplier-qualifications) · Any.
Documento. O comprador fecha a cotação escolhendo a proposta da Beta Serviços, 8% acima da menor — e a Beta ainda é Prospect.
Cadeia. Gerente de compras, depois o diretor de suprimentos, depois quem homologa. Escolhendo a menor proposta de um fornecedor SpendAuthorized, só o gerente. A justificativa de "não escolhi a mais barata" vira um nível a mais, não um campo de texto que ninguém lê.
PURCHASE_ORDER · alçada acumulada, verba estourada e comitê por quórum
Configuração. Base "acima de R$ 200 mil" (prioridade 30): nível 1 CostCenterOwnerUpToAmount · Any · Slice; nível 2 ApprovalGroup(Comitê de Compras) · Quorum(2) · Document, quatro membros. Supplemental "estado do orçamento = Exceeded": nível Role(Controller) · Any · Document, com prioridade que o posiciona antes do comitê. Supplemental "fornecedor sancionado": nível Role(Compliance) · Any.
Documento. Pedido de R$ 420 mil, 90% em /OPERACOES/MANUTENCAO/ (R$ 378 mil) e 10% em /OPERACOES/LOGISTICA/ (R$ 42 mil). O Owner de manutenção tem limite de R$ 100 mil; o de /OPERACOES/, R$ 300 mil; Fernanda, CFO, é Owner da raiz com hasUnlimitedAuthority. O orçamento devolve Exceeded para a fatia de manutenção; a overBudgetPolicy do nó é RequireApproval, que é exatamente a complementar acima.
Cadeia. A fatia de manutenção sobe dois níveis e chega a Fernanda num passo só — cada subida não é um passo; a de logística fica com o Owner local. Depois o Controller, depois o comitê: duas aprovações de quatro fecham, um "não" encerra. Variante de alçada estourada: se Fernanda tivesse limite de R$ 250 mil e ninguém na raiz tivesse autoridade ilimitada, a fatia de R$ 378 mil iria a ela mesmo assim, com authorityExceeded marcado na ativação, a tarefa mostrando a marca e nexio.approval-step.authority_exceeded chegando à controladoria enquanto a tarefa está aberta — e o /validate teria apontado a raiz antes de a política ser publicada.
CONTRACT · jurídico sempre, comitê e CEO acima de R$ 1 milhão
Configuração. Base "até R$ 1 milhão" (prioridade 20): Role(Jurídico) · Any · Document, depois Role(Diretor de Suprimentos) · Any. Base "acima de R$ 1 milhão" (prioridade 10): Role(Jurídico) · Any, depois ApprovalGroup(Comitê de Contratos) · Quorum(2) · Document, depois SpecificUser(CEO). Supplemental "categoria /SERVICOS/TI/": SpecificUser(CISO).
Documento. Contrato de 36 meses com a Beta para monitoramento de rede, R$ 1,2 milhão comprometidos, categoria /SERVICOS/TI/MONITORAMENTO/.
Cadeia. Jurídico, CISO (a complementar entra onde a prioridade dela manda), comitê com dois de quatro, CEO. Prazo e cláusula de exclusividade ainda não são critérios — a spec do Contract decide; até lá, contrato longo se recorta pelo valor total comprometido. É um dos três processos em que a votação com dissidência tolerada (Vote(n), fase 2) poderá ser ligada; hoje, no comitê, um voto contrário encerra.
SUPPLIER_REGISTRATION · quem cadastrou não aprova
Configuração. A semente: PermissionHolder(approve:suppliers) · Any · Document. Supplemental "flag tipo de pessoa = física": nível Role(Controladoria) · Any.
Documento. Ana, compradora com approve:suppliers, cadastra a Beta Serviços ME. Bruno e Carla também têm a permissão.
Cadeia. Ana é removida (RN-AP-08); Bruno e Carla recebem a tarefa, e o primeiro que decidir fecha. Faltou certidão? Bruno devolve, com motivo e comentário; Ana completa e reenvia na mesma instância. Fornecedor sancionado? Rejeita, e o motivo vai no uso SupplierReject. Tenant pequeno: se Ana fosse a única com a permissão, a submissão seria recusada com 422 nomeando o passo — é o onboarding que dá a permissão a mais alguém ou define o fallbackApproverId, nunca uma exceção à regra.
SUPPLIER · quatro áreas ao mesmo tempo, num nível só
Configuração. A semente é PermissionHolder(manage:supplier-qualifications) · Any; o tenant a substitui por Panel[Role(Jurídico), ApprovalGroup(Financeiro), Role(Engenharia), Role(Qualidade)] · Document. Supplemental "faixa de risco = alta": nível Role(Diretor de Suprimentos) · Any.
Documento. Homologação da Beta para /SERVICOS/ENGENHARIA/, com riskTier alto.
Cadeia. Um passo com quatro tarefas em paralelo, uma decisão de cada área; a primeira rejeição encerra. Fechado o painel, o diretor. Aprovada, a homologação grava o resultado e promove a Beta a SpendAuthorized na categoria. Quatro áreas ao mesmo tempo é o paralelo dentro do nível da Decisão 5 — não são quatro níveis.
BANK_ACCOUNT_CHANGE · quatro olhos, e um "não" basta
Configuração. A semente é PermissionHolder(approve:supplier-banks) · Any, mais controladoria quando o titular não bate. A recomendada: Base "média mensal a partir de R$ 100 mil": PermissionHolder(approve:supplier-banks) · Quorum(2) · Document. Supplemental "flag chave PIX não verificada": Role(Tesouraria) · Any. Supplemental "flag titular ≠ fornecedor": Role(Controladoria) · Any.
Documento. A Beta, com média de R$ 300 mil por mês nos últimos doze meses, pede troca de conta com chave PIX aleatória ainda não resolvida. Eduardo, de compras, registra a proposta; seis pessoas têm approve:supplier-banks, ele entre elas.
Cadeia. Eduardo é removido; cinco tarefas, duas aprovações fecham, um "não" encerra; depois a Tesouraria. Aprovada, o Fornecedor aplica a proposta e inicia a carência de 24 a 72 horas — e nexio.supplier-bank-account.change_requested já tinha ido ao contato financeiro antigo na abertura. É por este processo que Quorum não tolera dissidência: uma pessoa do financeiro desconfiar tem de bastar. Vote(n) nunca entra aqui.
ITEM · curadoria com controle extra por flag e por categoria
Configuração. A semente: PermissionHolder(approve:catalog-items) · Any. Supplemental "flag regulado": Role(Segurança do Trabalho) · Any. Supplemental "categoria /QUIMICOS/": Role(Engenharia de Processo) · Any.
Documento. Ana cria "Ácido sulfúrico 98% · tambor 200 L" em /QUIMICOS/ACIDOS/, com a flag regulado.
Cadeia. Aprovador do catálogo, Segurança, Engenharia — a ordem entre as complementares é a prioridade delas. Rejeição grava o motivo ItemReject no item e avisa a criadora (nexio.item.rejected, além do decided). Importação em lote com autoApprove não passa por aqui: é carga, com a permissão e a trilha da importação.
BUDGET · o exercício passa pela diretoria inteira
Configuração. Base de reserva: Role(Controller) · Any · Document, depois ApprovalGroup(Diretoria) · All · Document, depois SpecificUser(CEO). Supplemental "empresa = filial em regime especial": SpecificUser(Diretor da filial).
Documento. Exercício 2027 da Empresa Alfa, R$ 48 milhões orçados.
Cadeia. Controller; os cinco diretores, todos; CEO. Um "não" de qualquer diretor encerra — e o exercício volta como rejeitado, com motivo. Aprovado, o exercício é publicado e a versão anterior, arquivada. "Três de cinco bastam mesmo com dois contra" é o Vote(n) de fase 2, e este é um dos processos onde ele será permitido.
BUDGET_CHANGE · remanejamento por valor e por conta de destino
Configuração. Base "delta até R$ 50 mil" (prioridade 10): Role(Analista de Orçamento) · Any · Document. Base "acima de R$ 50 mil" (prioridade 20): Role(Analista de Orçamento) · Any, depois Role(Controller) · Any. Supplemental "conta contábil de destino em /1/ (investimento)": SpecificUser(CFO).
Documento. Remanejar R$ 150 mil de /MARKETING/EVENTOS/ para /MARKETING/DIGITAL/, conta de destino 4.1.2 (despesa).
Cadeia. Analista, depois Controller. Se a conta de destino fosse de investimento, o CFO entraria depois do Controller. O critério de conta contábil lê a linha de destino, que é a que recebe verba — é para isso que ele existe no remanejamento.
Uma requisição do começo ao fim
A requisição de Ana, do primeiro exemplo, acompanhada momento a momento — com o que o motor grava e o que a Notificação envia.
| # | Momento | O que o motor faz | Trilha e aviso |
|---|---|---|---|
| 1 | Ana submete | A Requisição chama POST /approvals no processo REQUISITION. O casador roda uma vez: base de R$ 50–500 mil (versão 3) e a complementar do DPO (versão 1). A cadeia é congelada com valor, taxa e rateio no snapshot; o nível do diretor nasce Skipped. | Evento de submissão com as políticas e versões. Nada é enviado ainda. |
| 2 | Nível 1 ativa | O resolvedor lê os Owners vigentes e as delegações de hoje: Carla para a fatia de TI, Diego para a de Marketing. Vencimento calculado em horas úteis. | nexio.approval-assignment.assigned para Carla e para Diego. |
| 3 | Carla aprova; Diego não decide em 48 h úteis | A fatia de TI fecha. O SLA de Diego vence; o processo tem escalationBehavior = Notify. | nexio.approval-assignment.reminded para Diego e para Ana. |
| 4 | Diego devolve para ajuste | Motivo ApprovalReturn e comentário ("falta a cotação anexa"). A instância vai a Returned, viva; os passos da rodada viram Obsolete, inclusive o que Carla aprovou. | nexio.approval-request.returned para Ana. |
| 5 | Ana anexa e reenvia | Rodada 2 na mesma ApprovalRequest. O casador roda de novo; a cadeia é a mesma. Carla e Diego recebem tarefas novas — devolver reinicia a rodada, e a assinatura da rodada 1 fica na trilha, não na cadeia. | assigned outra vez para os dois. |
| 6 | Os dois aprovam | Todos os passos de fatia do nível 1 fecharam; o passo do documento ativa. | assigned para o DPO. |
| 7 | O DPO aprova com ressalva | "Mediante DPA assinado". A instância termina Approved; a ressalva fica visível no documento e no recebimento. O motor emite o evento de empenho com as fatias e os valores; quem grava o Commitment é o Orçamento. | nexio.approval-request.decided (outcome Approved) para Ana. |
| 8 | Semana seguinte, Ana edita para R$ 95 mil | Aumento de 18,75% com tolerância de 10%: reavalia. A cadeia nova é igual — mesmas fatias, mesmas pessoas, diretor ainda abaixo de R$ 100 mil. As aprovações são preservadas; o evento registra o que mudou. | Evento de reavaliação. Nenhuma tarefa nova. |
| 9 | Ana edita para R$ 130 mil | Reavalia: o nível do diretor ativa. Carla, Diego e o DPO continuam válidos com o mesmo escopo e a mesma política; entra uma tarefa nova. | assigned para o Diretor de Compras. |
Padrões que se repetem
| Padrão | Como se monta | Onde aparece |
|---|---|---|
| Quatro olhos | Um nível Quorum(2) sobre PermissionHolder ou Role. Duas pessoas fecham; um "não" encerra | Alteração bancária, cadastro de fornecedor pessoa física, item crítico |
| Comitê | ApprovalGroup com All ou Quorum(n); os membros mudam sem editar a política | Pedido acima da faixa, contrato, exercício orçamentário |
| Por área, em paralelo | Panel com um alvo por área — uma decisão de cada, no mesmo nível | Homologação de fornecedor, item regulado que exige várias engenharias |
| Alçada progressiva | CostCenterOwnerUpToAmount · Slice; o limite mora no vínculo, não na política | Requisição e pedido — o padrão de quem vem do Protheus |
| Gestor do gestor | CostCenterOwnerAtDistance(1) depois de CostCenterOwner, um nível por assinatura exigida | Empresas que querem os dois níveis intermediários assinando |
| Diretor só acima de X | activationMinValue no nível, dentro da mesma política | Qualquer processo com valor |
| Controle extra por condição | Uma Supplemental por condição — flag, categoria, fornecedor, UF, conta — cada uma com o nível que acrescenta | DPO, Segurança, fiscal, compliance |
| Exceção por subárvore | Base amarrada no nó filho com prioridade menor que a do pai | /TI/INFRA/ com alçada própria dentro de /TI/ |
| Verba estourada exige o Controller | Supplemental com critério estado do orçamento = Exceeded; é o que RequireApproval significa | Requisição, pedido, remanejamento |
| Interestadual passa pelo fiscal | Supplemental com critério UF de entrega e nível Role(Fiscal) · Line — só as linhas de fora da UF | Requisição e pedido com entrega em outra UF |
| Fornecedor ainda não homologado | Supplemental com critério fornecedor em estado Prospect | Cotação e pedido |
| Uma política para o grupo, uma por empresa | Base sem critério de empresa mais Supplemental com critério empresa ou estabelecimento em lista | Multiempresa, filial em regime especial, obra |
| Férias com teto, por processo | uma ApprovalDelegation por processo, cada uma com delegado, teto e fim; acima do teto a tarefa fica com o titular, e o vencimento faz o que o processo mandar | Qualquer aprovador |
| Compra pequena sem ninguém | Todos os níveis da política com activationMinValue acima do valor: a instância termina Approved com o evento approval-request.approved_by_policy; o /validate avisa que a política tem faixa sem decisor humano | Requisição de baixo valor com fornecedor homologado — declarado na política, visível no simulador |
O que não dá para montar — e o que usar no lugar
- "Dois níveis ao mesmo tempo." Níveis são sequenciais. O paralelo é dentro do nível:
Panelpara áreas diferentes,Slicepara fatias diferentes. - "Deixar o requisitante aprovar quando não há mais ninguém." Não existe chave. O tenant dá a permissão a uma segunda pessoa ou define o
fallbackApproverId; ausência de aprovador legítima é declarada na política, comactivationMinValuehoje e o nívelAutona fase 2. - "Aprovar sozinho depois de X dias." SLA vencido lembra, chama o gestor ou cria tarefa no nível acima. Nunca aprova.
- "Aprovar metade das linhas." A decisão é do documento inteiro. Quem quer separar, separa antes de submeter.
- "Comitê onde um voto contrário não derruba."
Vote(n)— fecha emn, encerra só quandonfica inalcançável —, restrito aBUDGET,BUDGET_CHANGEeCONTRACT. Nunca em processo de controle antifraude. - Políticas mais elaboradas de encaminhamento por falta de autoridade podem evoluir; a v1 já impede conclusão sem autoridade explicitamente concedida.
- "Condição A ou B numa política só." Critérios são conjunção. Duas complementares, uma por condição, produzem o mesmo nível — e o motor deduplica.
- "Aprovar pelo e-mail, sem entrar."
ApprovalTokenestá modelado e desligado por parâmetro.
Configuração
Parametrizações
O que o cliente ajusta sem código, e em que nível cada coisa vive. Parâmetro ausente herda o nível acima.
| Parâmetro | Tipo | Padrão | Efeito |
|---|---|---|---|
| workWeek | DayOfWeek[] | seg · ter · qua · qui · sex | Os dias que contam como úteis. Tirar o fim de semana resolve cinco sétimos do erro de contagem. Novo em v2.14 |
| workdayStart · workdayEnd | time · time | 09:00 · 18:00 | A janela diária em que o relógio corre. Não existe meio expediente: o dia é útil inteiro ou não é útil. Novo em v2.14 |
| nonWorkingDates | date[] | feriados nacionais, semeados | As datas que o relógio pula. Semeada pelo produto e editável pelo tenant, que acrescenta feriado municipal e recesso. Substitui o businessCalendarId em v2.14 |
| allocationMaterialityPercent | numeric(9,6) | 5,00 | Fatia abaixo disso não gera passo próprio. Um valor por tenant, e o dono é este documento — o Centro de Custo cita, não redeclara (RN-AP-15 · RN-CC-24). Revisto em v2.15 |
allocationMaterialityAmount · allocationMaterialityCurrency | numeric(21,4) · char(3) | 1.000,00 · BRL | Moeda explícita do limiar, inclusive em tenant multimoeda. Converter usando snapshot e registrar limiar efetivo. Vale o maior entre percentual e valor. |
| maxDelegationDays | int | 90 | Duração máxima de uma delegação. validTo é obrigatório e não passa disso; renovar é criar outra, com trilha. Novo em v2.10 |
A casa saiu em v2.16, e com ela a pendência do lado do fluxo. Estes parâmetros são a entidade ApprovalSettings — uma linha por tenant, criada no provisionamento com os padrões acima, com rotas e permissão próprias. TenantSettings não existe na plataforma, e Company e Establishment não eram o lugar: são parâmetros do tenant, não da empresa. Padrão do CurrencySettings — entidade nomeada por assunto, nunca um JSON genérico nem tabela-gaveta, que é o que a spec de Empresa recusou por escrito. costCenterMaxDepth saiu desta tabela: nunca foi parâmetro de aprovação — quem valida profundidade de árvore é o Centro de Custo, e é lá que ele mora, como gancho declarado.
| Parâmetro | Tipo | Padrão | Efeito |
|---|---|---|---|
| fallbackApproverId | Guid | — | Último recurso quando toda resolução esvazia. Obrigatório para ativar o processo |
| rejectReasonUsage · returnReasonUsage | ReasonCodeUsage | ApprovalReject · ApprovalReturn | O uso de motivo que a rejeição e a devolução gravam. ITEM usa ItemReject; SUPPLIER_REGISTRATION e SUPPLIER usam SupplierReject; os demais, o padrão. Do produto, não do tenant |
| subjectFlags | string[] | por processo | As flags que o assunto expõe ao critério "flags do assunto". Do produto, não do tenant |
| seededDefaultPolicy | bool | true nos processos sem valor | A política de reserva vem publicada pelo produto, com um nível PermissionHolder. O tenant edita; nunca fica sem |
| deduplicateConsecutive | bool | true | Mesma pessoa em níveis consecutivos assina uma vez |
| revaluationTolerancePercent | numeric(9,6) | 0,00 | Aumento tolerado sem reavaliar a cadeia |
revaluationToleranceAmount · revaluationToleranceCurrency | numeric(21,4) · char(3) | 0,00 · BRL | Moeda explícita. Vale o maior limiar após conversão; nunca amplia authorizedAmount nem dispensa mudança material. |
| defaultSlaHours | int | 48 | SLA herdado por política e nível |
| escalationBehavior | enum | Notify | None · Notify · Escalate. Vive aqui e em nenhum outro nível — política e nível herdam o do processo |
| allowEscalation | bool | true | false na semente do BANK_ACCOUNT_CHANGE. Desligado, o vencimento só lembra — e política com Escalate não publica. Novo em v2.10 |
| delegable | bool | true | false na semente do BANK_ACCOUNT_CHANGE. Nenhuma delegação alcança um processo não delegável, nem a de escopo nulo. Novo em v2.10 |
| requireCommentOnApprove | bool | false | Rejeição e devolução já exigem sempre |
| allowReassign | bool | true | Permite passar a tarefa a outro elegível, uma vez |
| allowBatchApproval | bool | true | Aprovar várias tarefas numa ação — sempre um evento por tarefa |
| emailApproval | bool | false | Liga o ApprovalToken. Fase 2 |
| Parâmetro | Nível | Efeito |
|---|---|---|
| priority · isDefault · kind | política | Como a política concorre ou empilha |
| criteria | política | Quinze critérios; ausência casa tudo |
| validFrom · validTo | política | Vigência da regra |
| fallbackApproverId | política | Sobrepõe o do processo |
| approverType · target | nível | Estratégia de resolução |
| mode · quorum | nível | Any · All · Quorum(n) |
| scopeMode | nível | Document, Line ou Slice (RN-AP-46). Revisto em v2.17 |
| activationMinValue | nível | Abaixo disso o nível não entra |
| maxApprovalHours | nível | SLA próprio; nulo herda |
| escalationTargetType · escalationTargetRef | nível | Para quem a escalada vai. Obrigatório quando o processo escala e o nível não caminha árvore; proibido quando caminha — lá o alvo é o nó pai. Novo em v2.10 |
| label | nível | O nome que o aprovador vê |
Camada API
Endpoints
| Rota | Permissão | O que faz |
|---|---|---|
| GET /approval-processes | read:approval-processes | Processos e seus parâmetros |
| PUT /approval-processes/{id} | manage:approval-processes | Parâmetros do processo e aprovador de reserva |
| GET /approval-settings | read:approval-settings | Nova em v2.16. A linha do tenant: materialidade, teto de delegação, expediente e dias não úteis |
| PUT /approval-settings | manage:approval-settings | Nova em v2.16. Devolve o recurso, pela convenção de sucesso. Não afeta instância viva: o que a cadeia usou está congelado desde a submissão (RN-AP-06) |
| GET /approval-policies | read:approval-policies | Filtra por processo, tipo, vigência e nó |
| POST /approval-policies | manage:approval-policies | Cria em rascunho. Sobreposição entre bases é 409 |
| POST /approval-policies/{id}/publish | manage:approval-policies | Publica versão nova e aposenta a anterior |
| GET /approval-policies/{id}/validate | read:approval-policies | Inconsistências: nó sem responsável, grupo vazio, papel sem membro, nível sem alvo, alvo que não alcança nenhuma empresa coberta pela política (RN-AP-39), sobreposição com outra base de mesma prioridade, raiz de centro de custo sem Owner com hasUnlimitedAuthority em política com nível CostCenterOwnerUpToAmount, limite preenchido sem approvalLimitCurrency, faixa em que todos os níveis ficam abaixo do activationMinValue, nível com escalonamento sem alvo declarado, política que escala em processo que não escala e — em política Supplemental com critério estado do orçamento — os nós de centro de custo em RequireApproval que ficariam sem cobertura se ela for publicada com outro critério ou aposentada (RN-AP-41) |
| GET /approval-groups · POST · PUT | manage:approval-groups | Comitês e seus membros com vigência |
| GET /approval-delegations · POST | manage:approval-delegations · :self | Herdado da v1.0, mais escopo e teto |
| POST /approvals:simulate | read:approval-policies | A cadeia de um documento hipotético, sem criar nada |
| Rota | Permissão | O que faz |
|---|---|---|
| POST /approvals | interna | Submete um assunto. Chamada pelo módulo dono do documento, nunca pela tela |
| GET /approvals | audit:approvals | Nova em v2.17. A consulta transversal. Instâncias de qualquer documento do tenant, com filtro por documento (subjectType + subjectId), processo, estado, período, pessoa (requisitante, aprovador de direito ou quem agiu), rodada e marca de exceção — authorityExceeded, resolvedOutOfScope, resolvedEmptyOnActivation, budgetRequirementUnmet. É a rota que faltava para a permissão audit:approvals significar alguma coisa, e é ela que sustenta as duas promessas sem dono: o relatório de por que os pedidos voltam (motivo × processo × período, que o Motivo delega explicitamente a este módulo) e a pergunta de auditoria que não é sobre um documento conhecido. Paginada e ordenada por submissão; a trilha de cada uma continua em /events |
| GET /approvals/{id} | read:approvals | A instância com passos, tarefas e a cadeia projetada até o fim |
| GET /approvals/inbox | act:approvals | A fila. Tarefas pendentes do usuário, incluindo as recebidas por delegação, com filtro por processo, valor e vencimento |
| POST /approvals/{id}/assignments/{aid}/approve | act:approvals | Aprova. Aceita ?withProviso= para ressalva |
| POST /approvals/{id}/assignments/{aid}/reject | act:approvals | Rejeita. Comentário obrigatório |
| POST /approvals/{id}/assignments/{aid}/return | act:approvals | Devolve para ajuste. Comentário obrigatório |
| POST /approvals/{id}/assignments/{aid}/request-info | act:approvals | Pergunta ao requisitante. Pausa o SLA e avisa quem tem que responder (nexio.approval-assignment.info_requested) |
| POST /approvals/{id}/answer-info | read:approvals · só o requisitante | A resposta à pergunta aberta. Retoma o SLA do passo, entra na trilha e avisa quem perguntou. Não decide nada e não muda a cadeia |
| POST /approvals/{id}/assignments/{aid}/decline | act:approvals | O delegado recusa a tarefa recebida. Ela volta ao titular — que recebe nexio.approval-assignment.assigned —, o evento registra a recusa com o id da delegação, e o documento continua sem decisão. Não existe para tarefa própria: quem é o aprovador de direito decide ou reatribui |
| POST /approvals/{id}/assignments/{aid}/reassign | reassign:approvals | Passa a outro elegível, uma vez |
| POST /approvals/batch-approve | act:approvals | N tarefas numa chamada, N eventos |
| POST /approvals/{id}/cancel | interna | Cancelamento vindo do documento. Libera empenho |
| POST /approvals/{id}/revaluate | interna | Disparado pela alteração do documento. Preserva o que continua válido |
| GET /approvals/{id}/events | read:approvals | A trilha completa, ordenada |
Contrato
Contrato de erros
Mesma convenção do resto do produto: o status vem do sufixo do código, nunca da mensagem.
| Código | HTTP | Quando |
|---|---|---|
| APPROVAL_POLICY_OVERLAP_CONFLICT | 409 | Duas políticas base com a mesma prioridade e critérios sobrepostos. Verificado na aplicação ao publicar, na mesma transação; guarda parcial no banco quando ele oferecer |
| APPROVAL_POLICY_IN_USE_CONFLICT | 409 | Desativar política com instância viva. A mensagem carrega a contagem |
| APPROVAL_GROUP_EMPTY_CONFLICT | 409 | Remover o último membro de grupo usado por política ativa |
| APPROVAL_ASSIGNMENT_ALREADY_DECIDED_CONFLICT | 409 | Duas decisões simultâneas na mesma tarefa. A segunda perde e é informada |
| APPROVAL_STEP_NOT_ACTIVE_CONFLICT | 409 | Decisão sobre passo que já fechou — tela desatualizada |
| APPROVAL_REQUEST_VERSION_CONFLICT | 409 | Novo em v2.17. expectedVersion diferente do version corrente da instância: outra decisão da mesma instância commitou primeiro. A segunda relê e decide de novo — a resposta traz o estado atual, não um erro genérico (RN-AP-44) |
| APPROVAL_NO_APPROVER_UNPROCESSABLE | 422 | Um passo resolveu vazio depois de todo o encadeamento. A mensagem nomeia o passo |
| APPROVAL_SELF_APPROVAL_UNPROCESSABLE | 422 | Requisitante tentando decidir a própria instância. Defesa em profundidade: a RN-AP-08 já o removeu de toda tarefa; este erro pega reatribuição, delegação ou token que o traga de volta |
| APPROVAL_NO_POLICY_UNPROCESSABLE | 422 | Nenhuma política base casou e o processo não tem política de reserva |
| APPROVAL_POLICY_AMBIGUOUS_UNPROCESSABLE | 422 | Novo em v2.6. Duas políticas base de mesma prioridade casaram o mesmo documento na submissão. A mensagem nomeia as duas. Nunca desempate silencioso |
| BUDGET_EXCEEDED_UNPROCESSABLE | 422 | Repassado do orçamento sem reembrulho, no mesmo padrão dos erros do GrydFiles. Política Block resolvida no nó e valor acima de disponível mais tolerância; traz available e wouldExceedBy por fatia. Substitui o antigo APPROVAL_BUDGET_EXCEEDED_UNPROCESSABLE_UNPROCESSABLE, que existia quando o motor decidia sozinho |
| BUDGET_UNCOVERED_ALLOCATION_UNPROCESSABLE | 422 | Novo em v2.18. Fatia lançada contra dimensão orçamentária que não existe no plano — sinal Uncovered. Também repassado do orçamento sem reembrulho, e é outro código: BUDGET_EXCEEDED_UNPROCESSABLE diz "estourou o disponível" e admite tolerância e política; este diz "não há verba cadastrada para esta conta neste centro de custo" e não admite nenhuma das duas. A mensagem nomeia centro de custo, conta e período, e sugere a linha coringa (RN-AP-53) |
| APPROVAL_DELEGATION_CHAIN_UNPROCESSABLE | 422 | Delegar para quem já delegou — cadeia não encadeia |
| APPROVAL_ESCALATION_TARGET_REQUIRED_UNPROCESSABLE | 422 | Novo em v2.10. Publicar política com escalonamento ligado e nível que não caminha árvore sem escalationTarget |
| APPROVAL_ESCALATION_NOT_ALLOWED_UNPROCESSABLE | 422 | Novo em v2.10. Escalonamento em processo com allowEscalation = false — o antifraude não muda de decisor por decurso |
| APPROVAL_DELEGATION_OVERLAP_CONFLICT | 409 | Novo em v2.10. Duas delegações vigentes do mesmo delegante para o mesmo processo, ou uma de escopo nulo cruzando qualquer outra. A mensagem nomeia a que já existe |
| APPROVAL_DELEGATION_PERIOD_UNPROCESSABLE | 422 | Novo em v2.10. Delegação sem validTo ou acima de maxDelegationDays |
| APPROVAL_SETTINGS_WORK_WEEK_EMPTY_UNPROCESSABLE | 422 | Novo em v2.16. Semana útil sem nenhum dia — o SLA nunca venceria, e nada pode ficar pendente para sempre |
| APPROVAL_SETTINGS_WORKDAY_WINDOW_UNPROCESSABLE | 422 | Novo em v2.16. workdayStart maior ou igual a workdayEnd |
| APPROVAL_SETTINGS_MATERIALITY_UNPROCESSABLE | 422 | Novo em v2.16. Percentual fora de 0–100 ou valor negativo. Zero nos dois é válido e significa "nenhuma fatia é dispensada" |
| APPROVAL_LEVEL_TARGET_OUT_OF_COMPANY_SCOPE_UNPROCESSABLE | 422 | Novo em v2.12. Publicar política com nível cujo alvo resolvido não alcança todas as empresas que ela alcança, ou com fallbackApproverId sem escopo irrestrito (RN-AP-40) |
| APPROVAL_DELEGATION_OUT_OF_COMPANY_SCOPE_UNPROCESSABLE | 422 | Novo em v2.12. Delegado cujo conjunto de empresas não contém o do delegante — a tarefa chegaria a quem toma 404 ao abri-la (RN-AP-40) |
| APPROVAL_DELEGATION_NOT_ALLOWED_UNPROCESSABLE | 422 | Novo em v2.10. Delegação que alcançaria processo com delegable = false |
| APPROVAL_REASSIGN_TARGET_UNPROCESSABLE | 422 | Destino da reatribuição não é elegível para o passo |
| APPROVAL_POLICY_NO_LEVEL_UNPROCESSABLE | 422 | Publicar política sem nível |
| APPROVAL_PROCESS_NO_FALLBACK_UNPROCESSABLE | 422 | Ativar processo sem aprovador de reserva. Não se aplica aos processos com política padrão semeada: neles a reserva é o próprio nível PermissionHolder, e permissão sem ninguém cai na RN-AP-09 |
| APPROVAL_COMMENT_REQUIRED | 400 | Rejeição, devolução, ressalva, reatribuição ou pedido de informação sem comentário |
| APPROVAL_LIMIT_CURRENCY_NOT_CONVERTIBLE_UNPROCESSABLE | 422 | Novo em v2.17. Submeter documento cuja moeda não tem par de conversão vigente contra a moeda de um approvalLimit que a cadeia precisa avaliar. Repassado da Moeda sem reembrulho, no mesmo padrão do BUDGET_EXCEEDED_UNPROCESSABLE; a mensagem nomeia o par e o vínculo. Comparar sem par seria comparar números de moedas diferentes no ponto em que a alçada decide (RN-AP-52). Corrigido na validação cruzada de 09/09/2026: este documento nomeava CURRENCY_PAIR_NOT_CONFIGURED_UNPROCESSABLE, que na Moeda significa outra coisa — conversão pedida num sentido sem par cadastrado, em qualquer chamada. Repassar sem reembrulho exige repassar o código, e o desta recusa é o específico |
| REASON_CODE_REQUIRED_UNPROCESSABLE | 422 | Rejeição ou devolução sem reasonCodeId. Repassado do catálogo de Motivo sem reembrulho, como os erros do GrydFiles |
| APPROVAL_NOT_FOUND | 404 | Instância, passo ou tarefa inexistente neste tenant |
APPROVAL_AUTHORITY_UNAVAILABLE_UNPROCESSABLE | 422 | Submissão sem responsável com autorização e alçada suficientes. |
APPROVAL_BUDGET_REQUIREMENT_UNMET_UNPROCESSABLE | 422 | RequireApproval sem cobertura complementar aplicável. |
Autorização
Permissões
| Permissão | Público | Cobre |
|---|---|---|
| read:approval-processes · :manage | NexioAdmin | Processos e parâmetros por processo |
| read:approval-settings · :manage | Admin, NexioAdmin | Nova em v2.16. A configuração de tenant do motor. Separada da de processo porque muda o comportamento de todos os processos de uma vez |
| read:approval-policies | Admin, Controller | Ler políticas, validar e simular |
| manage:approval-policies | NexioAdmin | Criar, editar e publicar política |
| manage:approval-groups | NexioAdmin | Comitês e membros |
| manage:approval-delegations | NexioAdmin | Qualquer delegação do tenant |
| manage-own:approval-delegations | Aprovador | A própria delegação |
| read:approvals | Requisitante, Aprovador, Auditoria | Instância, cadeia e trilha dos documentos a que a pessoa tem acesso |
| act:approvals | Aprovador | Decidir sobre tarefas atribuídas a si. Não é guarda-chuva: a tarefa precisa ser sua |
| reassign:approvals | Aprovador sênior, Admin | Passar tarefa a outro elegível |
| audit:approvals | Auditoria | Ler qualquer instância do tenant, inclusive de documentos fora do alcance da pessoa. Ganhou rota em v2.17 — GET /approvals, a consulta transversal com filtro. Até então era uma permissão sem endpoint que a exercesse |
A separação entre act:approvals e reassign:approvals é deliberada: quem aprova não necessariamente pode escolher quem aprova no lugar dele — que é como uma alçada é contornada sem violar nenhuma permissão.
Rastreabilidade
Regras de negócio
Uma política base vence; complementares empilham
A cadeia final é a união ordenada e deduplicada da base com todas as complementares que casaram.
Prioridade desempata bases; especificidade não
Menor número vence. Sobreposição com a mesma prioridade é recusada ao publicar — verificada na aplicação, na mesma transação, com guarda parcial no banco quando ele oferecer. Se ainda assim duas casarem em execução, a submissão responde 422 APPROVAL_POLICY_AMBIGUOUS_UNPROCESSABLE.
Processo exige política base de reserva e fallbackApproverId explicitamente habilitado para suas empresas, operações e limites. Configuração sem cobertura não ativa; a execução revalida vigência e autoridade.
Critério de árvore guarda o nó e vale por rollup
Centro de custo e categoria, por prefixo de path. Descendentes criados depois já nascem cobertos; override no descendente é uma política de prioridade menor.
Política é versionada e a versão é imutável
Editar publica versão nova. Instâncias vivas continuam na versão que as formou.
A cadeia é congelada na submissão
Passos, escopos, modos, SLA, valor avaliado, taxa de câmbio, rateio e o limiar de materialidade efetivo. Nada disso muda sem reavaliação explícita — mexer na configuração no meio do caminho não reembaralha aprovadores de instância viva. O limiar entrou na lista em v2.15, pela mesma razão da taxa de câmbio: resolve-se uma vez, na submissão, e o que a cadeia usou fica no snapshot.
Os aprovadores são resolvidos na ativação do passo
Responsáveis, membros de grupo e delegação vigente são lidos na hora em que o passo ativa, não na submissão.
O requisitante nunca decide a própria instância
Removido de toda tarefa antes de qualquer outra regra, e não há chave que desligue isso — allowSelfApproval não existe. Se o passo esvaziar, sobe um nível nos tipos de árvore ou cai no reserva nos demais; se esgotar, 422 nomeando o passo e dizendo que o requisitante era o único aprovador possível. Vale para todo processo, inclusive os do Fornecedor e do Item: é esta regra — e não uma checagem no domínio — que garante "aprovador ≠ solicitante" na alteração bancária (RN-FOR-12). APPROVAL_SELF_APPROVAL_UNPROCESSABLE fica como defesa em profundidade na decisão. Revisada em v2.8, 06/09/2026.
Passo sem aprovador é recusa na submissão e desfecho declarado na ativação
422 nomeando o passo, na submissão. Só activationMinValue e dedupe produzem passo pulado. Revista em v2.17: a guarda de grupo ou vínculo vazio passa a valer também para expiração por vigência — e, quando o esvaziamento acontece na ativação, o desfecho é o da RN-AP-45, não o 422.
Paralelo é dentro do nível; entre níveis é sequencial
Any, All, Quorum(n) ou Panel. Rejeição encerra a instância em todos os modos — em Quorum(n), fecha em n aprovações e a primeira rejeição encerra. Votação com dissidência tolerada é modo próprio de fase 2, Vote(n), restrito aos processos que o produto declarar (BUDGET, BUDGET_CHANGE, CONTRACT); nunca uma releitura do Quorum, nunca em BANK_ACCOUNT_CHANGE. Revisada em v2.8, 06/09/2026.
Nada é aprovado por decurso de prazo
SLA vencido notifica ou escalona. AutoApprove não existe como comportamento de escalonamento.
Escalonar acrescenta caminho, não substitui
Em Any. A tarefa original continua viva, ambas fecham o passo, e o evento registra qual chegou primeiro. Revista em v2.17: em All, Quorum(n) e Panel a escalada substitui a tarefa do titular em vez de somar-se a ela — ver RN-AP-50.
SLA conta em horas úteis e pausa em pedido de informação
Revista em v2.14. Hora útil é hora dentro do expediente do tenant, em dia da semana marcado como útil e fora da lista nonWorkingDates, semeada com os feriados nacionais e editável pelo tenant. Não há calendário por estabelecimento: a lista é uma só. O tempo pausado é registrado separadamente.
Regras são avaliadas por linha e fatia; a decisão é do documento inteiro
Fatias do mesmo centro de custo são agregadas antes de gerar passo.
Alçada é avaliada sobre a fatia, acima do limiar de materialidade
Fatia imaterial é absorvida pela maior para efeito de fluxo e continua contabilizada integralmente. Esta é a regra única do produto para materialidade, e o limiar é um só por tenant — allocationMaterialityPercent e allocationMaterialityAmount, o que for maior —, resolvido na submissão e congelado com a cadeia (RN-AP-06). O Centro de Custo cita esta regra (RN-CC-24) em vez de redeclará-la; nenhuma outra página a redeclara. Revista em v2.15. Revista de novo em v2.17: materialidade é regra de roteamento, e só. O orçamento sempre vê a decomposição integral — a fatia absorvida continua sendo consumida na linha dela —, e Block numa fatia absorvida recusa a submissão do mesmo jeito. Sem isso haveria duas decomposições do mesmo rateio: a alçada nunca veria os 3% de /MARKETING/ e o orçamento devolveria Exceeded naquela fatia, de modo que ou a submissão é recusada por uma fatia que o motor decidiu ignorar, ou a marca da RN-AP-42 dispara no funcionamento normal — e controladoria que vê marca todo dia aprende a ignorá-la.
Alçada não soma entre pessoas; acumula subindo a árvore
Esgotada a subida, aprova o topo; o passo marca authorityExceeded na ativação, com evento na trilha e aviso à controladoria antes da decisão (RN-AP-31).
Delegação redireciona a tarefa e não encadeia
Titular e substituto ficam ambos na tarefa, com o id da delegação. Delegação de delegação é 422.
Rejeitar encerra; devolver mantém a instância viva
O reenvio abre rodada nova na mesma instância, com os passos anteriores marcados como obsoletos.
Comentário é obrigatório em toda ação que não seja aprovar
Rejeitar, devolver, pedir informação, reatribuir e aprovar com ressalva.
Fornecedor, conteúdo relevante, quantidade, unidade, escopo, condições e evidências materiais exigem reavaliação, mesmo sem aumento de valor. Tolerância numérica não autoriza ultrapassar o valor assinado.
Exigir equivalência de conteúdo relevante, escopo, evidências, policyId, policyVersion e nível; novo valor deve caber no authorizedAmount e moeda registrados. Preservar decisão original e registrar a equivalência, sem atribuir ao signatário conteúdo que não autorizou.
Aprovação em lote gera um evento por tarefa
Nunca um evento de lote.
Toda transição é evento, e o estado é projeção
Append-only, com autor, papel pelo qual agiu, delegação em vigor e passo alcançado.
Duas decisões simultâneas na mesma tarefa: a segunda é 409
Concorrência otimista na tarefa. A segunda pessoa é informada de quem decidiu e quando, não de um erro genérico. Revista em v2.17: esta regra continua valendo para a mesma tarefa, mas deixou de ser a única trava — o que decide o avanço do passo é sempre uma leitura agregada de outras tarefas, e essa serializa na raiz pela RN-AP-44.
A aprovação final emite evento de empenho; o cancelamento emite o inverso
O motor não conhece verba — quem cria e libera o Commitment é o módulo de orçamento.
A cadeia é consultável antes de existir
O simulador roda a mesma seleção, sem criar nada, inclusive contra política em rascunho e contra a configuração de uma data passada.
Quem decide bloqueio por verba é o orçamento, não o motor
O motor consulta evaluate:budget-states e obedece à overBudgetPolicy resolvida por nó, com a tolerância que o orçamento aplica. Não existe parâmetro de bloqueio no motor: dois lugares decidindo a mesma coisa divergem no primeiro documento com fatias em centros de custo diferentes — e quem erraria é o motor, que não conhece herança de política nem folga. É o princípio 6 desta spec aplicado a si mesma: o motor consome contratos, não caminha em árvores. Revista em v2.17: os sinais que o motor trata são quatro — Ok, Warning, Exceeded e Uncovered —, e Uncovered bloqueia sempre (RN-AP-53).
Toda aprovação humana é uma instância do motor
Onde uma segunda pessoa decide sobre o pedido de outra, o módulo dono do documento chama POST /approvals e aplica o efeito quando a instância termina. Nenhum módulo tem rota própria de aprovar ou rejeitar. Atos unilaterais com motivo (bloqueio, promoção manual, revogação, substituição, escape de duplicata, expurgo) não são aprovação. Princípio 7, 04/09/2026.
Processo sem valor nasce com política padrão semeada
A política de reserva vem publicada pelo produto, com um nível PermissionHolder na permissão que autorizava o clique na rota antiga. O tenant edita, nunca apaga a última. Processo com dinheiro continua exigindo configuração para ativar (RN-AP-03). Dia 1 igual ao de ontem, com uma diferença deliberada: quem cadastrou não aprova (RN-AP-08). Tenant em que o solicitante é o único detentor da permissão cai na RN-AP-09 — configuração de onboarding (segundo detentor ou fallbackApproverId do processo), não exceção; o painel de administração avisa "processo com um único aprovador possível". Revisada em v2.8, 06/09/2026.
O uso de motivo da rejeição é declarado pelo processo
rejectReasonUsage e returnReasonUsage vêm do produto, por processo. Código e comentário continuam obrigatórios juntos em toda rejeição e devolução (RN-AP-19) — inclusive onde a rota antiga do módulo exigia só o código.
Esgotar a árvore registra authorityExceeded, mas não concede autoridade à raiz. Só reserva com concessão e limite explícitos pode decidir. Sem ele, recusa na submissão ou Returned por configuração na ativação.
Cadeia inteira pulada termina aprovada pela política, com evento próprio
Todos os níveis abaixo do activationMinValue: a instância termina Approved sem tarefa e grava approval-request.approved_by_policy com a política e a versão. É a única aprovação sem pessoa do MVP — declarada na política, visível no simulador; /validate avisa a faixa sem decisor humano. Novo em v2.8, 06/09/2026.
Membro de grupo entra e sai com vigência, e o nível lê a vigente na ativação
ApprovalGroupMember guarda usuário, grupo e período. Dois períodos do mesmo usuário no mesmo grupo não se sobrepõem — verificado na aplicação, na mesma transação, com guarda de exclusão por interseção no banco quando ele oferecer (o banco ainda não está definido). Grupo referenciado por política ativa não pode ficar sem membro vigente: APPROVAL_GROUP_EMPTY_CONFLICT. Novo em v2.9, 08/09/2026.
Toda promessa do motor tem rota: a resposta ao pedido de informação e a recusa do delegado
O SLA que "retoma na resposta" precisa de POST /approvals/{id}/answer-info, e só o requisitante da instância a chama; a recusa que "devolve ao titular" precisa de POST /approvals/{id}/assignments/{aid}/decline, e só o delegado a chama. Nenhuma das duas decide o documento, as duas geram evento. Novo em v2.9, 08/09/2026.
Escalonamento tem alvo declarado, e processo de controle não escala
Em nível que caminha árvore, Escalate é o responsável do nó pai. Em qualquer outro tipo, o nível declara escalationTargetType e escalationTargetRef, verificados no /validate e na publicação (APPROVAL_ESCALATION_TARGET_REQUIRED_UNPROCESSABLE). O alvo entra na cadeia projetada pelo simulador: escalar nunca amplia o conjunto de aprovadores além do que foi mostrado antes. Processo com allowEscalation = false — semente do BANK_ACCOUNT_CHANGE — recusa Escalate na publicação e no vencimento apenas lembra. Novo em v2.10, 08/09/2026.
Uma delegação vigente por delegante e por processo, com fim obrigatório
Chave (delegante, scope, período) sem sobreposição, com scope nulo lido como "todos os processos"; verificação na aplicação, na mesma transação, e guarda no banco quando ele oferecer exclusão por interseção. validTo obrigatório, limitado a maxDelegationDays (padrão 90). Substitui a regra "no máximo uma ativa por pessoa", que não convivia com dez processos. Novo em v2.10, 08/09/2026.
A delegação move a tarefa dentro da alçada do titular, e não alcança processo não delegável
O passo foi resolvido com o approvalLimit de quem responde pelo nó; o delegado age em nome dele e o teto da delegação só reduz. Acima do teto, a tarefa fica com o titular. Processo com delegable = false não é alcançado por nenhuma delegação, nem pela de escopo nulo. A trilha continua gravando aprovador de direito, quem agiu e o id da delegação (RN-AP-17). Novo em v2.10, 08/09/2026.
Serializar a raiz, registrar voto e chamar explicitamente o dono do processo na mesma transação. Sucesso efetiva estado e lançamento. Insuficiência orçamentária esperada devolve ReturnedForBudget, preserva voto e devolve documento/instância sem Approved ou novo compromisso; falha técnica reverte tudo.
Revalidar concessão efetiva por empresa e operação na ativação e na decisão. Alvo revogado não atua; reserva passa pelas mesmas guardas. Sem elegível, recusar submissão ou devolver por configuração, com histórico e aviso durável.
Exigir teto da plataforma, UserCompanyScope e UserCompanyRole com a permissão da operação na empresa. Publicação valida cobertura em todas as empresas do critério; reserva precisa da mesma cobertura e alçada. Delegação declara empresas e operações, só vale na interseção autorizada e nunca amplia valor ou escopo.
Validate identifica nós RequireApproval sem Supplemental correspondente, inclusive por expiração ou aposentadoria. Troca de configuração pode gerar pendência administrativa; nenhuma compra conclui financeiramente com a exigência não atendida.
budgetRequirementUnmet registra a falha e gera aviso durável à controladoria. Ausência de cobertura Supplemental válida impede concluir aprovação financeira. Na submissão, APPROVAL_BUDGET_REQUIREMENT_UNMET_UNPROCESSABLE; durante a execução, Returned com motivo e votos preservados.
Reavaliação incrementa roundNumber e registra a revisão candidata. Pedido emitido mantém effectiveRevisionId e sua obrigação durante a aprovação da alteração. Só a alteração efetiva aplica delta; sua rejeição não libera a obrigação anterior.
Toda decisão serializa na raiz da instância
Nova em v2.17, 09/09/2026. ApprovalRequest tem version (int) e toda decisão passa expectedVersion; divergência é APPROVAL_REQUEST_VERSION_CONFLICT · 409, e a resposta traz o estado atual para que a pessoa releia e decida de novo. O fechamento do passo e a ativação do seguinte acontecem dentro da mesma transação da decisão — a mesma em que a RN-AP-38 grava o efeito.
Por que a raiz e não a tarefa. A RN-AP-24 já protegia duas decisões na mesma tarefa, mas o que decide o avanço é sempre uma leitura agregada de outras tarefas: contar aprovações em Quorum(n), fechar All, marcar as demais Obsolete em Any, ver que todas as fatias do nível fecharam. Quorum(2) com três pessoas decidindo ao mesmo tempo: duas transações leem uma aprovação, ambas gravam duas, ambas fecham o passo, ambas invocam o contrato de desfecho — e a verba é empenhada duas vezes. Simétrico: duas fatias paralelas ativam o nível seguinte duas vezes, e uma rejeição concorrente com a última aprovação deixa a instância Rejected com o efeito de aprovação aplicado. Um token por tarefa não pega nenhum dos três, porque em nenhum deles duas transações escrevem a mesma tarefa.
O sequence do ApprovalEvent é alocado nesta mesma transação, que é o que faz a trilha ter ordem total dentro da instância (RN-AP-48). O custo é o esperado: as decisões de uma instância viram fila. É o preço certo — a publicação de política já paga o equivalente com bloqueio de linha, e uma instância recebe unidades de decisão por dia, não milhares.
Registrar resolvedEmptyOnActivation e procurar reserva habilitado para empresa, operação e valor. Sem elegível, devolver a instância e documento por configuração pendente na transação do voto anterior, sem perdê-lo e sem deixar fila sem responsável. Avisos de expiração são duráveis e auxiliares.
Escopo Line: um passo por linha que casa, sem materialidade
Nova em v2.17, 09/09/2026. ApprovalScopeMode tem três valores — Document, Line e Slice. Um nível com scopeMode = Line gera um passo por linha do documento que casa o critério do nível, com scopeRef = id da linha e scopeAmount = valor da linha; os passos de linha do mesmo nível são paralelos entre si, como os de fatia. A materialidade não se aplica — ela dispensa fatia, e o que faz uma linha entrar aqui não é o valor dela, é o critério: uma linha interestadual de R$ 300 passa pelo fiscal exatamente como uma de R$ 300 mil. A dedupe é por (pessoa, linha), não por (pessoa, fatia): quem responde pelo fiscal de duas linhas recebe duas tarefas, porque o que ele examina é a linha, uma de cada vez.
Por que o valor já era usado e o enum não o tinha. O campo do passo declarava Document · Line · Slice, o padrão recomendado do documento era Role(Fiscal) · Line — só as linhas de fora da UF — e o campo do nível só oferecia Document · Slice: não havia caminho de configuração que gerasse um passo Line. O efeito era que o padrão "interestadual passa pelo fiscal", um dos diferenciais brasileiros anunciados, era inconfigurável. Escrever a regra custa três linhas; o valor no enum já estava pago.
A rodada nova recasa políticas vigentes e registra versões original e corrente. Preservar assinatura exige também policyVersion idêntica, conteúdo relevante equivalente, mesmo nível e escopo e valor dentro do autorizado (RN-AP-21).
A trilha é imutável, ordenada por sequence e de ações fechadas
Nova em v2.17, 09/09/2026. ApprovalEvent tem tabela de campos, enum fechado de ações e chave de ordenação (approvalRequestId, sequence), com o sequence monotônico dentro da instância e alocado na transação da decisão (RN-AP-44). Ordenar por occurredAt é proibido: aprovação em lote grava N eventos no mesmo instante, e fatias paralelas fecham juntas — o empate é a regra, não a exceção.
Imutável quer dizer sem rota de escrita. Não há PUT, não há DELETE e não há soft delete — a exclusão lógica que o resto do produto herda da plataforma não se aplica aqui, e a exceção é declarada. Corrigir um evento é gravar outro que o corrija, apontando no metadata o sequence corrigido. Mesma disciplina do razão orçamentário, mesma razão: trilha editável não prova nada, e a promessa "aprovação tem prova quando a auditoria pergunta quem autorizou" precisa ser verificável, não afirmada.
O que é coluna e o que é metadata: tudo por que a GET /approvals filtra é coluna. O metadata guarda o que é específico da ação — o alvo da reatribuição, o que mudou na reavaliação, qual passo a escalada acrescentou. Pôr ali um campo de filtro é como o esquema se perde de novo.
Ativar delegação transfere as tarefas já abertas do titular
Nova em v2.17, 09/09/2026. A entrada em vigor de uma ApprovalDelegation passa as tarefas Pending do titular que caem no seu escopo para o delegado: a ApprovalAssignment continua sendo a mesma linha — titular como aprovador de direito, delegado como quem age, id da delegação registrado (RN-AP-17) —, e cada transferência grava approval-assignment.delegated. O SLA não reinicia: o relógio é do passo, e zerá-lo faria da delegação o jeito mais barato de comprar prazo. Tarefa acima do teto de valor da delegação fica com o titular (RN-AP-37), e o encerramento da delegação faz o caminho de volta pelas que ainda estiverem abertas.
Por quê. A regra anterior cobria "o passo que ativa amanhã" e calava sobre o presente — e o presente é exatamente o que a pessoa deixou aberto ao sair de férias, com o SLA correndo. Ariba, Coupa e Oracle transferem a fila aberta na ativação. Sem isso, a delegação só funciona para quem se lembra de criá-la antes de a fila encher, que é a hora em que ninguém se lembra.
Any acrescenta caminho; All, Quorum e Panel substituem a tarefa do titular sem reduzir pessoas distintas exigidas. Escalado precisa da mesma autorização empresarial, alçada e segregação. Falta de elegível segue RN-AP-45.
approvalLimit nulo não cobre valor; hasUnlimitedAuthority=true concede autoridade ilimitada por ato de cadastro. Esgotada a árvore sem cobertura, reserva também precisa de autoridade suficiente. Não exigir pessoa ilimitada quando limites finitos já atendem ao escopo.
A alçada compara na moeda do limite, com a taxa congelada na submissão
Nova em v2.17, 09/09/2026. approvalLimit anda com approvalLimitCurrency, obrigatória quando o limite está preenchido — inclusive no nó corporativo, que não tem empresa. A comparação converte o valor da fatia ou da linha para a moeda do limite, usando a taxa congelada na submissão do documento, e o ApprovalEvent da decisão grava a exchangeRate e a rateDate usadas. Submeter documento cuja moeda não tem par de conversão vigente contra a moeda de um limite que a cadeia precisa avaliar é recusa declarada — APPROVAL_LIMIT_CURRENCY_NOT_CONVERTIBLE_UNPROCESSABLE, repassado da Moeda sem reembrulho —, nunca comparação silenciosa. O código genérico de conversão sem par cadastrado é outro, e não é este.
Multimoeda vale desde a v1 (decisão de 09/09/2026): a functionalCurrency da empresa aceita qualquer ISO 4217, e a restrição "BRL na v1" saiu da spec de Moeda. Com Alfa em BRL e Beta em USD, o limite do nó corporativo comparado com um valor em USD sem conversão declarada é uma comparação entre moedas diferentes exatamente no ponto em que a alçada decide — e quatro páginas discordavam sobre se isso podia acontecer.
A taxa é congelada uma vez, e o razão herda. A conversão acontece na submissão e é o único momento de conversão na vida do documento; todo movimento do razão originado por ele herda exchangeRate e rateDate do documento, e a resolução própria de taxa do Orçamento vale só para movimento sem documento de origem. O Commitment é gravado dias depois da submissão, e as duas specs congelavam em momentos diferentes: com resolução no movimento, a verba consumiria um número diferente do que foi aprovado.
Uncovered e Block impedem novos compromissos; RequireApproval exige cobertura concluída. Recebimento/aceite ocorrido pode ser registrado com pendência, sem concluir nova autorização por esse caminho. Orçamento é a fonte da decisão.
Limites
Fora de escopo
- Aprovação por e-mail —
ApprovalTokenestá modelado e desligado por parâmetro. - Aprovador externo sem conta — depende do token acima.
- Hierarquia de pessoas —
RequesterManagere o escalonamentoNotifyManagerexigem a árvore de subordinação no GrydAuth. Enquanto ela não existir, nenhum dos dois está no enum: opção que ninguém consegue executar é dívida, não configuração. - Responsável por categoria — exige um vínculo no
CategoryNodeequivalente ao do centro de custo. - Condições compostas — hoje os critérios são conjunção.
OUse faz com duas políticas complementares. - Votação com dissidência tolerada —
Vote(n), modo de fase 2 restrito aBUDGET,BUDGET_CHANGEeCONTRACT; nunca em processo de controle antifraude, nunca como releitura doQuorum. - Políticas mais elaboradas de encaminhamento por falta de autoridade podem evoluir; a v1 já impede conclusão sem autoridade explicitamente concedida.
- Editor de fluxo em diagrama. Política com critérios e níveis ordenados cobre o que compras precisa; um motor de BPMN traz desvio, subprocesso e evento temporal — e uma classe de bug que ninguém depura em produção.
- Aprovação parcial de documento. Aprovar 6 das 10 linhas parece útil e transforma um pedido em dois, quebrando negociação, rateio e recebimento. Se o requisitante quer separar, ele separa antes.
- Assinatura eletrônica com certificado. Trilha auditável não é assinatura ICP-Brasil. Se o cliente precisar, é integração, não motor.
- Autoaprovação — por decurso de prazo, por chave ou por exceção. Em nenhum processo, por nenhum parâmetro. O que existe é ausência declarada de aprovador:
activationMinValueno MVP e o nívelAutona fase 2, sempre na política, visíveis no simulador e na trilha. - Aprovação fora do motor. Nenhum módulo do Nexio tem rota própria de aprovar; o que parece exceção é ato unilateral com motivo, e está listado em "O que o motor não decide". Se um novo ato precisar de segunda pessoa, ele entra na semente — não ganha rota.
Pendência que fica registrada: o processo REQUISITION avalia valor estimado, e o PURCHASE_ORDER avalia o valor negociado, que costuma ser menor. Falta decidir se um pedido cujo valor ficou abaixo do já aprovado na requisição precisa de nova cadeia. A recomendação é não precisar, com um parâmetro por processo para os clientes que exigem a dupla aprovação — mas isso depende de a Requisição existir, e volta como decisão daquela spec.