Regras de Processo Válido
Este documento define o catálogo normativo de regras de validade estrutural e semântica para definições de
processo Kikwiflow (documentos JSON compostos por um mapa de nós de fluxo, flowNodes, e um ponto de entrada,
defaultStartPoint). Cada regra recebe um identificador estável no formato KIKWI-NNN, um título, uma
descrição normativa e os metadados necessários para implementação automatizada.
Este catálogo é a fonte de verdade para:
- Linters de modelagem (validação em tempo de edição, antes do deploy).
- Validadores de deploy (validação em tempo de publicação da definição de processo).
- Agentes e ferramentas de análise estática que precisem determinar, de forma determinística, se um JSON de
processo é válido.
Cada regra é independente e pode ser implementada isoladamente. O escopo deste documento é limitado à validação
de schema e de referências semânticas dentro da própria definição de processo — ele não descreve o
comportamento do motor de execução, não lista exceções lançadas em runtime e não explica por que uma regra
existe. Para o comportamento funcional de cada tipo de nó, consulte
Modelo de Processo e Nós de Fluxo.
Convenções
Severidade
| Valor | Significado |
|---|
| Bloqueante | A definição de processo que viola esta regra não deve ser considerada válida. Um validador de deploy deve rejeitá-la; um linter deve exibi-la como erro. |
| Aviso | A definição de processo que viola esta regra pode ser aceita, mas o comportamento resultante é potencialmente não intencional. Um linter deve exibi-la como aviso; um validador de deploy pode aceitar a publicação. |
Status
| Valor | Significado |
|---|
| Existente | A violação desta regra já impede o deploy da definição de processo na implementação atual do validador de deploy do motor. |
| Sugerida | A violação desta regra não é detectada em nenhuma camada de validação hoje. É uma proposta de regra a ser implementada por um linter e/ou incorporada ao validador de deploy. |
| Rejeitada | A regra foi avaliada e descartada — ou a redação estava semanticamente errada (contradiz o comportamento real e correto do motor), ou implementá-la exigiria travessia de grafo com risco inaceitável de falso-positivo. Mantida no catálogo só como registro, não deve ser implementada como está. |
Convenções de referência
flowNodes refere-se ao mapa de nós da definição de processo, indexado por identificador de nó.
outgoing refere-se à lista de sequence flows de saída de um nó, cada uma contendo, entre outros campos,
targetNodeId, expectedAnswer, isDefault e handlesNull.
boundaryEventIds refere-se à lista de identificadores de eventos de borda (timers, tratadores de erro)
anexados a um nó.
- "Nó pai" refere-se ao nó de fluxo ao qual um evento de borda está funcionalmente anexado por meio de
boundaryEventIds.
A. Regras estruturais gerais do grafo
Aplicam-se a qualquer definição de processo, independentemente dos tipos de nó presentes.
| ID | Título | Descrição | Severidade | Status |
|---|
| KIKWI-001 | Integridade referencial das sequence flows | Todo targetNodeId declarado em uma sequence flow de qualquer nó deve referenciar o identificador de um nó existente em flowNodes. | Bloqueante | Existente |
| KIKWI-002 | Consistência entre chave e identificador do nó | A chave usada para indexar um nó em flowNodes deve ser idêntica ao valor do campo id declarado dentro desse nó. | Bloqueante | Sugerida (corrigida na raiz — ver nota) |
| KIKWI-003 | Ponto de entrada declarado e existente | O campo defaultStartPoint da definição de processo deve estar preenchido e referenciar o identificador de um nó existente em flowNodes. | Bloqueante | Existente |
| KIKWI-004 | Tipo do ponto de entrada | O nó referenciado por defaultStartPoint deve ser do tipo DEFAULT_START_EVENT. | Aviso | Sugerida |
| KIKWI-005 | Alcançabilidade de todos os nós | Todo nó declarado em flowNodes deve ser alcançável a partir de defaultStartPoint, por meio de uma sequence flow, de uma referência em boundaryEventIds de um nó alcançável, ou de um targetJoinId de um PARALLEL_GATEWAY alcançável. | Aviso | Sugerida |
| KIKWI-006 | Terminalidade explícita | Um nó sem nenhuma sequence flow em outgoing só é válido se for do tipo DEFAULT_END_EVENT, JOIN_GATEWAY ou BOUNDARY_ERROR_HANDLER. | Aviso | Sugerida |
| KIKWI-007 | Saída única fora de gateways de decisão e divisão | Todo nó que não seja do tipo EXCLUSIVE_GATEWAY ou PARALLEL_GATEWAY deve declarar no máximo uma sequence flow em outgoing. | Aviso | Sugerida |
Nota sobre KIKWI-002: em vez de implementar esta regra como validação, a causa raiz foi corrigida — o
motor (kikwi-core) passa a usar exclusivamente a chave do mapa flowNodes para todo bookkeeping de
execução (gravação de taskDefinitionId, resolução de boundary events, etc.), nunca mais o campo id
interno do nó. A dualidade que esta regra policiava deixou de existir como risco funcional — divergir
id/chave ainda é uma inconsistência de legibilidade a evitar, mas não quebra mais a execução.
B. DEFAULT_START_EVENT
| ID | Título | Descrição | Severidade | Status |
|---|
| KIKWI-008 | Presença obrigatória do evento de início | Toda definição de processo deve conter ao menos um nó do tipo DEFAULT_START_EVENT. | Bloqueante | Sugerida |
| KIKWI-009 | Saída obrigatória do evento de início | Todo nó DEFAULT_START_EVENT deve declarar exatamente uma sequence flow em outgoing. | Bloqueante | Existente |
C. DEFAULT_END_EVENT
| ID | Título | Descrição | Severidade | Status |
|---|
| KIKWI-010 | Ausência de saída no evento de fim | Um nó DEFAULT_END_EVENT não deve declarar nenhuma sequence flow em outgoing. | Aviso | Sugerida |
D. EXECUTABLE_TASK
| ID | Título | Descrição | Severidade | Status |
|---|
| KIKWI-011 | Executor obrigatório | Todo nó EXECUTABLE_TASK deve declarar o campo executor com um valor não nulo e não vazio. | Bloqueante | Existente |
| KIKWI-012 | Executor resolvível | O valor declarado em executor deve corresponder a um componente registrado do tipo executor de tarefa, disponível no ambiente de execução do processo. | Bloqueante | Existente |
E. EXTERNAL_TASK
| ID | Título | Descrição | Severidade | Status |
|---|
| KIKWI-013 | Ausência do campo executor | Um nó EXTERNAL_TASK não deve declarar o campo executor — esse tipo de nó não é associado a um executor de tarefa. | Aviso | Sugerida |
F. EXCLUSIVE_GATEWAY
| ID | Título | Descrição | Severidade | Status |
|---|
| KIKWI-014 | Tipo de provedor de resposta obrigatório | Todo EXCLUSIVE_GATEWAY deve declarar providerType com um dos valores reconhecidos: BEAN ou VARIABLE. | Bloqueante | Existente |
| KIKWI-015 | Bean do provedor de resposta preenchido | Se providerType for BEAN, o campo providerBean deve estar preenchido. | Bloqueante | Existente |
| KIKWI-016 | Bean do provedor de resposta resolvível | Se providerType for BEAN, o valor de providerBean deve corresponder a um componente registrado do tipo provedor de resposta, disponível no ambiente de execução. | Bloqueante | Existente |
| KIKWI-017 | Variável do provedor de resposta preenchida | Se providerType for VARIABLE, o campo providerVariable deve estar preenchido. | Bloqueante | Existente |
| KIKWI-018 | Unicidade da sequence flow padrão | Um EXCLUSIVE_GATEWAY deve declarar no máximo uma sequence flow de saída com isDefault: true. | Bloqueante | Existente |
| KIKWI-019 | Unicidade da sequence flow de valor nulo | Um EXCLUSIVE_GATEWAY deve declarar no máximo uma sequence flow de saída com handlesNull: true. | Bloqueante | Existente |
| KIKWI-020 | Unicidade de resposta esperada | As sequence flows de saída de um mesmo EXCLUSIVE_GATEWAY não devem repetir o mesmo valor de expectedAnswer entre si. | Bloqueante | Existente |
| KIKWI-021 | Saída obrigatória do gateway de decisão | Todo EXCLUSIVE_GATEWAY deve declarar ao menos uma sequence flow em outgoing. | Bloqueante | Existente |
| KIKWI-022 | Cobertura de resposta por sequence flow padrão | Recomenda-se que todo EXCLUSIVE_GATEWAY declare uma sequence flow com isDefault: true, cobrindo respostas não previstas explicitamente pelas demais sequence flows. | Aviso | Sugerida |
| KIKWI-023 | Tratamento de resposta nula | Recomenda-se que todo EXCLUSIVE_GATEWAY cujo provedor de resposta possa legitimamente retornar um valor nulo declare uma sequence flow com handlesNull: true. | Aviso | Sugerida |
G. PARALLEL_GATEWAY e JOIN_GATEWAY
| ID | Título | Descrição | Severidade | Status |
|---|
| KIKWI-024 | Join de destino obrigatório | Todo PARALLEL_GATEWAY deve declarar o campo targetJoinId. | Bloqueante | Existente |
| KIKWI-025 | Join de destino existente | O valor de targetJoinId deve referenciar o identificador de um nó existente em flowNodes. | Bloqueante | Existente |
| KIKWI-026 | Tipo do join de destino | O nó referenciado por targetJoinId deve ser do tipo JOIN_GATEWAY. | Bloqueante | Existente |
| KIKWI-027 | Pluralidade de ramos | Todo PARALLEL_GATEWAY deve declarar ao menos duas sequence flows em outgoing. | Aviso | Sugerida |
KIKWI-028 | Alcance exclusivo do join por divisão paralela | Rejeitada nesta revisão — a redação original está errada. Dizia que nenhuma sequence flow de um nó que não seja PARALLEL_GATEWAY deveria declarar um JOIN_GATEWAY como targetNodeId. Na prática, esse é o mecanismo normal e correto de um ramo sinalizar sua própria conclusão: uma EXECUTABLE_TASK/EXTERNAL_TASK dentro de um ramo aberto por PARALLEL_GATEWAY aponta sua própria outgoing direto para o JOIN_GATEWAY de destino o tempo todo (ver parallel-gateway-fan-out-join.json em kikwi-core-tests, testado desde antes desta revisão) — ContinuationService.generateNextTasksWithContext trata isso no branch genérico (registerBranchConclusion), não é um caso degenerado. Tentar implementar esta regra literalmente rejeita esse padrão real (confirmado rodando a suíte de testes: quebrou 3 fixtures válidos). O que de fato distingue "join alcançado corretamente" de "join alcançado por engano" não é o tipo do nó de origem — é se a execução está dentro de um contexto de ramo ativo (branchId/joinTaskId não-nulos), uma propriedade de caminho de execução, não estrutural, e que exigiria a mesma travessia de grafo arriscada que KIKWI-029 (com o mesmo risco de falso-positivo) para verificar em deploy-time. Não reintroduzir sem resolver esse problema primeiro. | — | Rejeitada |
| KIKWI-029 | Convergência dos ramos para o join declarado | Todo ramo aberto por um PARALLEL_GATEWAY deve convergir para o JOIN_GATEWAY declarado em targetJoinId, seja atingindo-o diretamente, seja terminando em um DEFAULT_END_EVENT dentro do próprio ramo. | Aviso | Sugerida |
H. Timers de borda (BOUNDARY_INTERRUPTIVE_TIMER, BOUNDARY_NON_INTERRUPTIVE_TIMER)
| ID | Título | Descrição | Severidade | Status |
|---|
| KIKWI-030 | Vínculo funcional obrigatório | Todo timer de borda deve estar referenciado em boundaryEventIds de um nó do tipo EXECUTABLE_TASK ou EXTERNAL_TASK. | Bloqueante | Sugerida (mesma categoria de "nó órfão" de KIKWI-005 — deliberadamente fora desta rodada) |
| KIKWI-031 | Referência de evento de borda existente | Todo identificador declarado em boundaryEventIds de um nó deve corresponder a um nó existente em flowNodes. | Bloqueante | Existente (agora em todo host — EXTERNAL_TASK foi o último a ganhar allowlist) |
| KIKWI-032 | Tipo de evento de borda reconhecido | Todo identificador declarado em boundaryEventIds deve corresponder a um nó de um dos tipos de evento de borda reconhecidos (BOUNDARY_INTERRUPTIVE_TIMER, BOUNDARY_NON_INTERRUPTIVE_TIMER, BOUNDARY_ERROR_HANDLER). | Bloqueante | Existente (idem) |
| KIKWI-033 | Tipo de provedor do timer interruptivo | Todo BOUNDARY_INTERRUPTIVE_TIMER deve declarar providerType com um dos valores reconhecidos: STATIC, VARIABLE ou BEAN. | Bloqueante | Existente |
| KIKWI-034 | Valor estático do timer preenchido | Se providerType for STATIC, o campo staticValue deve estar preenchido. | Bloqueante | Existente |
| KIKWI-035 | Variável do timer preenchida | Se providerType for VARIABLE, o campo providerVariable deve estar preenchido. | Bloqueante | Existente |
| KIKWI-036 | Bean do timer preenchido | Se providerType for BEAN, o campo providerBean deve estar preenchido. | Bloqueante | Existente |
| KIKWI-037 | Política de agendamento obrigatória | Todo BOUNDARY_NON_INTERRUPTIVE_TIMER deve declarar o campo schedulePolicy. | Bloqueante | Existente |
| KIKWI-038 | Tipo de política de agendamento reconhecido | schedulePolicy.type deve ser RATE_DURATION ou FIXED_DATES. | Bloqueante | Existente — mas não mais como checagem: ScheduleType só tem esses dois valores agora (CRON foi removido do enum), então isso é uma invariante de compilação, não algo que DeployValidator precisa verificar. DeployValidator ainda rejeita schedulePolicy.type nulo. |
| KIKWI-039 | Expressão de taxa preenchida | Se schedulePolicy.type for RATE_DURATION, o campo expression deve estar preenchido. | Bloqueante | Existente |
| KIKWI-040 | Datas fixas preenchidas | Se schedulePolicy.type for FIXED_DATES, o campo fixedDates deve conter ao menos uma data. | Bloqueante | Existente |
| KIKWI-053 | Contagem de ocorrências positiva | Se schedulePolicy.maxOccurrences estiver preenchido, deve ser um inteiro >= 1 — 0 ou negativo nunca agenda nenhum ciclo, o que provavelmente é um erro de modelagem e não a intenção do autor. | Aviso | Sugerida |
| KIKWI-048 | Nó pai restrito a EXTERNAL_TASK para timer interruptivo | Um BOUNDARY_INTERRUPTIVE_TIMER só deve estar referenciado em boundaryEventIds de um nó do tipo EXTERNAL_TASK — nunca EXECUTABLE_TASK. Não se aplica a BOUNDARY_NON_INTERRUPTIVE_TIMER, que não cancela o nó pai. | Bloqueante | Existente |
Nota: KIKWI-033–036 valem hoje só para BOUNDARY_INTERRUPTIVE_TIMER (o timer de borda) — os mesmos
campos em TIMER_TASK (o timer de fluxo principal) continuam sem validação de deploy (tier B), deliberado
nesta rodada por não estar no escopo original revisado com o time.
I. BOUNDARY_ERROR_HANDLER
| ID | Título | Descrição | Severidade | Status |
|---|
| KIKWI-041 | Tipo de nó pai restrito a EXECUTABLE_TASK | Um BOUNDARY_ERROR_HANDLER só deve estar referenciado em boundaryEventIds de um nó do tipo EXECUTABLE_TASK. | Bloqueante | Existente (agora também em EXTERNAL_TASK, que antes não tinha nenhuma validação de boundaryEventIds) |
| KIKWI-042 | Unicidade de código de erro por nó pai | Handlers de erro anexados ao mesmo nó pai não devem repetir o mesmo valor de errorCode, e no máximo um handler sem errorCode (curinga) é permitido por nó pai. | Bloqueante | Existente |
J. RetryPolicy (aplicável a EXECUTABLE_TASK)
| ID | Título | Descrição | Severidade | Status |
|---|
| KIKWI-043 | Tentativas máximas explícitas | Se retryPolicy estiver presente, o campo maxRetries deve estar explicitamente presente e ser maior ou igual a 1. | Bloqueante | Existente |
| KIKWI-044 | Estratégia de retry reconhecida | Se retryPolicy estiver presente, o campo strategy deve ser LINEAR ou EXPONENTIAL_BACKOFF. | Bloqueante | Existente |
| KIKWI-045 | Intervalo inicial do backoff exponencial | Se retryPolicy.strategy for EXPONENTIAL_BACKOFF, o campo initialInterval deve estar preenchido. | Bloqueante | Existente |
| KIKWI-046 | Intervalos da estratégia linear | Recomenda-se que, se retryPolicy.strategy for LINEAR, o campo intervals seja declarado com ao menos um elemento. | Aviso | Sugerida |
K. CALL_ACTIVITY_COORDINATOR
| ID | Título | Descrição | Severidade | Status |
|---|
| KIKWI-047 | calledElement obrigatório | calledElement não pode ser nulo/vazio. | Bloqueante | Existente |
| KIKWI-052 | elementVariable requer collectionVariable | elementVariable só é válido quando collectionVariable também está presente. | Bloqueante | Existente |
collectionVariable resolver para uma List em runtime não é validado em deploy-time — o motor não tem
mecanismo de declaração de tipo de variável de processo. Ver
CALL_ACTIVITY_COORDINATOR — Subprocessos.
L. BOUNDARY_INTERRUPTIVE_CATCH_EVENT
| ID | Título | Descrição | Severidade | Status |
|---|
| KIKWI-049 | Nó pai restrito a EXTERNAL_TASK ou TIMER_TASK | Um BOUNDARY_INTERRUPTIVE_CATCH_EVENT só deve estar referenciado em boundaryEventIds de um nó do tipo EXTERNAL_TASK ou TIMER_TASK — nunca EXECUTABLE_TASK. Um EXECUTABLE_TASK executa seu handler de forma síncrona, sem fronteira transacional onde o motor possa interromper com segurança antes de um efeito colateral real já ter acontecido. | Bloqueante | Existente — correção de catálogo: o código sempre aceitou TIMER_TASK como host válido também, além de EXTERNAL_TASK; o título/descrição desta linha só citava EXTERNAL_TASK, sem relação com o trabalho desta rodada. |
| KIKWI-050 | Tipo de provedor obrigatório | Todo BOUNDARY_INTERRUPTIVE_CATCH_EVENT deve declarar providerType com um dos valores reconhecidos: STATIC, VARIABLE, BEAN ou TEMPLATE. | Bloqueante | Existente — correção de catálogo: já era validado via validateCorrelationKeySource (compartilhado com EVENT_CATCHER/EVENT_THROWER) antes desta rodada; o catálogo estava com status desatualizado e faltava TEMPLATE como 4º valor reconhecido. |
| KIKWI-051 | Ausência de catchType/matchPolicy | Um BOUNDARY_INTERRUPTIVE_CATCH_EVENT não deve declarar catchType nem matchPolicy — ao contrário de EVENT_CATCHER, um evento de borda sempre resolve exatamente 1 chave. | Aviso | Sugerida |
M. EVENT_CATCHER
Seções inteiras (M, N, O) adicionadas nesta revisão — este tipo de nó já era validado em código desde antes,
mas nunca ganhou entrada no catálogo (escrito antes de EVENT_CATCHER/EVENT_THROWER/TIMER_TASK existirem
como tipos implantáveis). Nenhuma regra nova de validador foi implementada aqui — só o catálogo estava
incompleto.
| ID | Título | Descrição | Severidade | Status |
|---|
| KIKWI-054 | Tipo de provedor obrigatório | Todo EVENT_CATCHER deve declarar providerType com um dos valores reconhecidos: STATIC, VARIABLE, BEAN ou TEMPLATE. | Bloqueante | Existente |
| KIKWI-055 | Campo do provedor preenchido | Conforme providerType: staticKey (STATIC), providerVariable (VARIABLE), providerBean resolvível a um CorrelationKeysProvider (BEAN), ou correlationTemplates não-vazio (TEMPLATE). | Bloqueante | Existente |
| KIKWI-056 | GROUP incompatível com STATIC | catchType: GROUP não pode combinar com providerType: STATIC — STATIC sempre resolve exatamente 1 chave, incompatível com o scatter-gather de GROUP. | Bloqueante | Existente |
| KIKWI-057 | boundaryEventIds restrito a timers | Um EVENT_CATCHER.boundaryEventIds só pode referenciar BOUNDARY_INTERRUPTIVE_TIMER ou BOUNDARY_NON_INTERRUPTIVE_TIMER — nem BOUNDARY_ERROR_HANDLER, nem BOUNDARY_INTERRUPTIVE_CATCH_EVENT (ainda não suportado como boundary de outro EVENT_CATCHER). | Bloqueante | Existente |
N. EVENT_THROWER
| ID | Título | Descrição | Severidade | Status |
|---|
| KIKWI-058 | Tipo de provedor obrigatório + campo correspondente | Mesma validação condicional de KIKWI-054/055, aplicada a EVENT_THROWER — sem catchType/matchPolicy (não existem nesse tipo de nó; sempre resolve exatamente 1 chave, política v1 é falhar se ninguém estiver esperando). | Bloqueante | Existente |
O. TIMER_TASK
| ID | Título | Descrição | Severidade | Status |
|---|
| KIKWI-059 | boundaryEventIds restrito a timers e catch event | Um TIMER_TASK.boundaryEventIds só pode referenciar BOUNDARY_INTERRUPTIVE_TIMER, BOUNDARY_NON_INTERRUPTIVE_TIMER ou BOUNDARY_INTERRUPTIVE_CATCH_EVENT — nunca BOUNDARY_ERROR_HANDLER (não há handler síncrono nenhum pra um TIMER_TASK resolver via try/catch). | Bloqueante | Existente |
TIMER_TASK.providerType/staticValue/providerVariable/providerBean (os mesmos campos de
BOUNDARY_INTERRUPTIVE_TIMER, seção H) permanecem tier B — não validados em deploy, só falham quando o timer é
efetivamente instanciado. Ver a nota ao final da seção H.
Ver também