Image: AI generated
Motores de regras se apoiam na mesma premissa ha 60 anos. A premissa de que o objeto de verificacao e um “fato (fact)”.
O Drools coloca objetos Java como “facts” na working memory. O Rego trata o input como dados ja verdadeiros. O JSON Schema assume que a estrutura do documento e um dado. Todos partem da mesma suposicao — os dados recebidos sao fatos.
Mas qual e a razao de existir de um motor de regras? E verificar se os dados satisfazem as regras. Chamar de “ja verdadeiro” algo que precisa ser verificado e uma contradicao.
Nao e fato, e afirmacao
O objeto de verificacao nao e um fact, mas sim um claim (afirmacao). E uma declaracao que pode ser verdadeira ou falsa. Sua validade precisa ser julgada por regras.
O JWT ja segue esse principio. Chama sub, exp, iss nao de “facts”, mas de “claims”. Sao afirmacoes do emissor do token. So se pode confiar neles apos verificar a assinatura, confirmar a expiracao e comparar o issuer.
Essa estrutura ja foi estabelecida em 1958.
O modelo de argumentacao de Toulmin
Stephen Toulmin analisou a estrutura da argumentacao em 6 elementos em 1958:
- Claim (afirmacao): O objeto de julgamento. Aquilo cuja veracidade ou falsidade precisa ser verificada.
- Ground (fundamento): Os dados de evidencia usados no julgamento.
- Warrant (garantia): A regra que determina que o fundamento sustenta a afirmacao.
- Backing (respaldo): A justificativa de por que a regra e valida.
- Qualifier (qualificador): O grau de certeza do julgamento.
- Rebuttal (refutacao): Condicoes de excecao em que a afirmacao nao se sustenta.
A logica formal diz “se as premissas sao verdadeiras, a conclusao tambem e”. Toulmin foi diferente. “A afirmacao e sustentada por fundamentos e regras, mas se ha condicoes de excecao, ela e revertida.” Toda argumentacao e refutavel.
Motores de regras estiveram do lado da logica formal por 60 anos. A entrada e um fact, o resultado e allow/deny, excecoes sao mecanismos separados. Toulmin estava do lado oposto. A entrada e um claim, o resultado e um grau (degree), excecoes sao integradas.
O problema era que o livro de Toulmin estava na prateleira de filosofia. Nao era visivel na prateleira de motores de regras. Um elo perdido de 60 anos.
Entao construi um motor de regras
toulmin e a implementacao do modelo de argumentacao de Toulmin como um motor de regras em Go.
Requisitos evoluem
Veja como if-else e toulmin respondem a mesma evolucao.
// Segunda-feira: "Somente usuarios autenticados, bloqueio de IP aplicado, rede interna isenta do bloqueio"
g := toulmin.NewGraph("api:access")
auth := g.Rule(isAuthenticated)
blocked := g.Counter(isIPBlocked)
exempt := g.Except(isInternalIP)
blocked.Attacks(auth)
exempt.Attacks(blocked)
// Terca-feira: "Adicionar Rate limiting"
limited := g.Counter(isRateLimited)
limited.Attacks(auth)
// Quarta-feira: "Usuarios premium isentos do Rate limit"
premium := g.Except(isPremiumUser)
premium.Attacks(limited)
// Quinta-feira: "Durante resposta a incidentes, ate premium e limitado"
incident := g.Counter(isIncidentMode)
incident.Attacks(premium)
Duas linhas adicionadas por dia, sem alteracao no codigo existente. A mesma evolucao com if-else:
// Segunda-feira
if user != nil {
if blockedIPs[ip] {
if strings.HasPrefix(ip, "10.") {
allow = true
}
} else {
allow = true
}
}
// Quinta-feira — 4 niveis de aninhamento, estrutura incompreensivel
if user != nil {
if blockedIPs[ip] {
if strings.HasPrefix(ip, "10.") {
allow = true
}
} else if isRateLimited(ip) {
if isPremium(user) {
if !incidentMode {
allow = true
}
}
} else {
allow = true
}
}
toulmin: 2 linhas por requisito, estrutura invariavel. if-else: reescreve toda a estrutura a cada vez.
Regras sao funcoes Go
func(ctx Context, specs Specs) (bool, any)
ctx= material de julgamento que muda a cada requisicao (usuario, IP, contexto). Acessado viaGet/Set.specs= criterio de julgamento fixado na declaracao do grafo via.With()(limiares, nomes de roles, configuracoes)- Retorno =
(resultado do julgamento, evidencia). Evidencia e um tipo livre por dominio.
func CheckOneFileOneFunc(ctx toulmin.Context, specs toulmin.Specs) (bool, any) {
gf, _ := ctx.Get("file")
f := gf.(*FileGround)
if len(f.Funcs) > 1 {
return true, &Evidence{Got: len(f.Funcs), Expected: 1}
}
return false, nil
}
Nao precisa aprender uma nova linguagem como Rego. Basta escrever funcoes Go. (O port para TypeScript rulecat tambem usa a mesma assinatura — npm install rulecat.)
spec — mesma funcao, criterios de julgamento diferentes
A spec passa o criterio de julgamento da regra ao builder no momento da declaracao. Registrar a mesma funcao com specs diferentes cria regras distintas — sem necessidade de closure factory:
g := toulmin.NewGraph("access")
admin := g.Rule(isInRole).With(&RoleSpec{Role: "admin"}) // ruleID = "isInRole#admin"
editor := g.Rule(isInRole).With(&RoleSpec{Role: "editor"}).Qualifier(0.8)
g := toulmin.NewGraph("line-limit")
strict := g.Rule(CheckLineCount).With(&LineLimit{Max: 100}).Qualifier(0.7)
relaxed := g.Rule(CheckLineCount).With(&LineLimit{Max: 200}).Qualifier(0.5)
relaxed.Attacks(strict)
O valor de spec precisa implementar a interface Spec (SpecName() string·Validate() error) e e validado no momento do registro. Regras que nao precisam de spec omitem o .With() (spec nil).
Excecoes sao declaradas como grafo
Declare as relacoes entre regras com a Graph Builder API e o motor cuida do resto. A funcao e o identificador. Nao e necessario nome em string.
g := toulmin.NewGraph("filefunc")
w := g.Rule(CheckOneFileOneFunc)
d := g.Except(TestFileException)
d.Attacks(w)
ctx := toulmin.NewContext()
ctx.Set("file", file)
results, _ := g.Evaluate(ctx)
A mesma funcao pode ser reutilizada em grafos diferentes com relacoes de derrota diferentes:
strictGraph := toulmin.NewGraph("strict")
strictGraph.Rule(CheckOneFileOneFunc)
// Sem excecao — arquivos de teste tambem nao sao permitidos
lenientGraph := toulmin.NewGraph("lenient")
w := lenientGraph.Rule(CheckOneFileOneFunc)
r1 := lenientGraph.Except(TestFileException)
r2 := lenientGraph.Except(GeneratedFileException).Qualifier(0.8)
r1.Attacks(w)
r2.Attacks(w)
// Arquivos de teste + gerados sao excecao
Rastreamento do fundamento do julgamento
Passando EvalOption{Trace: true}, alem do verdict, rastreia-se quais regras foram ativadas e qual regra derrotou qual. Cada TraceEntry carrega os elementos de Toulmin tal como sao — Name (Claim)·Ground (ctx)·Specs (Backing)·Verdict:
results, _ := g.Evaluate(ctx, toulmin.EvalOption{Trace: true})
// results[0].Verdict: +0.6
// results[0].Trace: [
// {Name: "CheckOneFileOneFunc", Role: "rule", Activated: true, Qualifier: 1.0},
// {Name: "TestFileException", Role: "except", Activated: true, Qualifier: 1.0},
// ]
Quando ha dezenas de regras, uma pessoa consegue ler “por que esse verdict foi gerado”. Passando Duration: true, mede-se tambem o tempo de execucao por regra. Log de auditoria e depuracao ficam embutidos no motor — nao e preciso logging separado.
O julgamento e calculado por uma unica formula
Aplicou-se o h-Categoriser de Amgoud (2013):
raw = w / (1 + Σ raw(attackers))
verdict = 2 × raw - 1
+1.0— violacao confirmada0.0— julgamento inconclusivo-1.0— refutacao confirmada
Quando uma regra dispara, torna-se um warrant. Se uma excecao tambem dispara, torna-se um attacker. A formula calcula a forca de ambos e produz o verdict. E se ha excecao da excecao? Torna-se um attacker do attacker, restaurando a regra original. Principio de compensacao — uma propriedade que somente o h-Categoriser satisfaz.
Regras tem tres niveis de forca
Aplicou-se a classificacao de Nute (1994):
| Forca | Significado | Exemplo |
|---|---|---|
| Strict | Impossivel de neutralizar | “Acesso a admin API sem autenticacao proibido” |
| Defeasible | Pode ser neutralizado por excecao | “Uma funcao por arquivo” |
| Defeater | Bloqueia outras regras sem fazer afirmacao propria | “Arquivos de teste sao excecao” |
Regras Strict rejeitam arestas de ataque. Defeater apenas ataca sem ter seu proprio julgamento. Isso expressa estruturalmente o nivel de imposicao das regras.
Qual a diferenca para o Rego
| Rego | toulmin | |
|---|---|---|
| Escrita de regras | Requer aprender Rego DSL | Funcoes Go |
| Tratamento de excecoes | Padrao default/else manual | Grafo de defeats declarativo |
| Julgamento | Binario allow/deny | Valor continuo [-1, +1] |
| Justificativa da regra | # METADATA (ignorado pelo motor) | spec (parte da estrutura) |
| Forca da regra | Inexistente | strict/defeasible/defeater |
| Tamanho do motor | Dezenas de milhares de linhas | Centenas de linhas |
| Velocidade | Interpretador (parsing→AST→avaliacao) | Chamada direta de funcoes Go |
Rego e amplo — possui ecossistema de integracao com Kubernetes, Terraform, Envoy. toulmin e profundo — possui o que Rego nao tem (defeasibility, qualifier, backing).
O reposicionamento do Qualifier
No modelo original de Toulmin, o Qualifier e anexado ao Claim. “Provavelmente devemos administrar penicilina a este paciente” — e um qualificador modal que expressa o grau de certeza da afirmacao.
O motor toulmin reposicionou o Qualifier do Claim para cada Rule. Em um motor de regras, o claim e apenas o objeto de verificacao. “Este arquivo tem 3 funcoes” — e uma verificacao factual, nao algo a que se atribui grau de certeza. O que determina a qualidade do julgamento e a certeza da regra:
- “Uma funcao por arquivo” — qualifier 1.0 (regra certa)
- “Recomendado menos de 100 linhas” — qualifier 0.7 (regra flexivel)
O qualifier de cada Rule torna-se o peso inicial w(a) do h-Categoriser, e o verdict final substitui o papel que o Qualifier desempenhava no modelo original de Toulmin — o grau de certeza do julgamento.
Validacao empirica: conversao das 22 regras do filefunc para Toulmin
filefunc e uma ferramenta de convencao de estrutura de codigo para desenvolvimento Go nativo para LLM. Todas as 22 regras foram convertidas em warrants Toulmin.
Classificacao por forca
| Strength | Qtd | Proporcao | Exemplo |
|---|---|---|---|
| Strict | 15 | 68% | F1, F2, F3, F4, A1-A3, A6-A16 |
| Defeasible | 4 | 18% | Q1, Q2, Q3, C4 |
| Defeater | 3 | 14% | F5, F6, excecao de arquivos de teste |
A maioria e strict — convencoes de estrutura de codigo inerentemente minimizam excecoes.
Resultados quantitativos
| Projeto | Num. arquivos (antes→depois) | Media LOC/arquivo (antes→depois) | Violacoes SRP resolvidas | Violacoes depth resolvidas |
|---|---|---|---|---|
| filefunc | — (em conformidade desde o inicio) | 25.1 | 0 | 0 |
| yongol | 87→1.260 | 244→25.4 | 66→0 | 148→0 |
| whyso | 12→99 | 147.8→24.4 | 12→0 | 23→0 |
O yongol passou de 87 para 1.260 arquivos. O numero de arquivos explodiu, mas a media de LOC caiu de 244 para 25.4. Todas as 66 violacoes de SRP e 148 violacoes de depth foram reduzidas a zero.
Fundamentacao teorica
Nao ha teoria original. Tudo e pesquisa existente:
| Elemento | Autoria original |
|---|---|
| Estrutura de 6 elementos | Toulmin (1958) |
| strict/defeasible/defeater | Nute (1994) |
| h-Categoriser | Amgoud & Ben-Naim (2013) |
A originalidade esta na descoberta de que eles se conectam. O que existia separadamente por 60 anos em filosofia (Toulmin), logica (Nute) e teoria da argumentacao (Amgoud) se encontra em um unico ponto: o motor de regras de software.
Calculando contratos
O estado de direito funciona nao porque os juizes sao inteligentes. Funciona porque a estrutura impoe o julgamento. Ha regras, excecoes sao declaradas e o veredito e produzido conforme as evidencias.
toulmin transpoe essa estrutura para codigo.
- Warrant = dispositivo legal
- Backing = intencao legislativa
- Strength = norma cogente vs norma dispositiva
- Rebuttal = clausula de excecao
- Claim = caso
- Ground = evidencia
- h-Categoriser = sentenca
Declare o contrato (warrant), declare as excecoes (rebuttal), insira as evidencias (ground) e o julgamento (verdict) e calculado.
Nao e uma pessoa que julga. E a formula que calcula.
Acc(a) = w(a) / (1 + Σ Acc(attackers))
MIT License. github.com/park-jun-woo/toulmin
Changelog
- 2026-06-18: Atualizacao da API — grafo com padrao builder (
Rule/Counter/Except·.With()·.Attacks()), assinatura de regrafunc(ctx Context, specs Specs),Evaluate(ctx, EvalOption{Trace}), backing→spec, port para TypeScript (rulecat) - 2026-03-22: Versão inicial