Kanon expressions v0.4

mandatory

Kanon é a linguagem de expressões de regra do governance plane da Koder Stack (stack-RFC-015): nomeada, versionada, NÃO-Turing-completa, com EBNF e corpus de conformidade próprios. v0.1 cobre `applies_when` (predicados de aplicabilidade); v0.2 quantificadores estruturais (forall/exists + `has`); v0.3 composição (`refines:` + reticulado RFC-2119); v0.4 binding cláusula→oráculo (`kanon_bindings:` — verify_by/gate/fp_budget/autofix). Transporte oficial: frontmatter de `.kmd`. O avaliador canônico é a lib compartilhada `engines/lang/kanon` (extraída de koder-tools/internal 2026-06-27 — RFC-020 §5 / reuse-first); o koder-spec-audit a consome (RFC-015 R4).

Document format reference: meta/docs/stack/policies/document-format.kmd

Nome ratificado pelo owner em 2026-06-11 (/k-go sobre a deliberação /k-arch; registry component-names.md). Kanon (gr. kanōn, régua/regra) é carga, não portador: vive dentro do .kmd (frontmatter hoje; fenced blocks kanon em versões futuras) e é avaliada exclusivamente pelo motor do koder-spec-audit`. Extração para arquivo próprio é movimento de transporte, nunca de semântica (seam D9 — RFC-015 §2).

1. Princípios normativos (herdados da RFC-015)

  • R1 — Total e decidível. Sem recursão de usuário, sem iteração ilimitada.

    Toda expressão termina; profundidade de aninhamento de parênteses ≤ 32 (erro kanon-depth-exceeded acima disso).

  • R2 — A válvula de escape é Go. O que não couber nesta gramática vira

    checker Go nomeado. Estender a gramática = bump de versão + revisão da RFC-015. Nunca "só mais um operador" sem processo.

  • R3 — Versionada. Todo documento portando Kanon declara kanon: "<maj.min>"

    no frontmatter. Avaliador rejeita major desconhecido ou minor maior que o suportado com erro nomeado kanon-unsupported-version — nunca silêncio.

  • R5 — O corpus é o contrato. §5 é executável: o avaliador DEVE passar

    todos os casos (espelhados em internal/kanon/kanon_test.go). Feature nova entra com casos positivos E negativos aqui antes do motor.

2. Construtos da v0.1

v0.1 — applies_when: predicado de aplicabilidade de uma spec/policy a uma ação/alvo. Chave de frontmatter:

kanon: "0.1"
applies_when: path ~ "**/landing
kodix"

v0.3 — composição (refines:): documento declara no frontmatter o pai que refina; o avaliador (koder-spec-audit refines) PROVA o invariante policies-RFC-003 sobre as cláusulas Requirement (RFC-013): só TIGHTEN/ADD, nunca LOOSEN. Reticulado de obrigação (semântica da linguagem): MAY(1) < SHOULD(2) < SHALL(3); SHALL NOT tem força de SHALL. Ausência de cláusula no filho = herança (nunca violação); cláusula sem keyword RFC-2119 não compara (malformação é território do kmd lint). Grafo validado: pai inexistente (kanon-refines-missing), ciclo (kanon-refines-cycle, cadeia ≤32), e id de cláusula duplicado entre docs NÃO-relacionados (kanon-duplicate-clause, warning de contradição).

kanon: "0.3"
refines: meta/docs/stack/policies/observability-first.kmd

v0.4 — binding cláusula→oráculo (kanon_bindings:): o vínculo cláusula↔verificação vira DADO do documento — koder-spec-audit bindings valida e (com --exec) executa:

kanon: "0.4"
kanon_bindings:
  req-auth:
    verify_by: "koder-spec-audit landing-responsive --module {path}"
    gate: block          # advisory | warn | block
    fp_budget: "2/30d"   # OBRIGATÓRIO quando gate: block (RFC-014 R3)
    autofix: ""

Reticulado de gates (semântica da linguagem): advisory(1) < warn(2) < block(3); através de refines: o gate só APERTA (kanon-gate-loosen). Findings de validação: kanon-binding-orphan (id inexistente no doc), kanon-bad-gate, kanon-block-without-budget (R3), kanon-binding-no-oracle, kanon-unbound-shall (SHALL sem binding — warning de cobertura). No --exec, {path} é substituído pelo path repo-relativo do documento; oráculo não-zero em binding block reprova a execução.

3. Gramática (EBNF, normativa)

toplevel    = quantexpr | expr ;
quantexpr   = ( "forall" | "exists" ) , "module" ,
              [ "where" , expr ] , ":" , expr ;
expr        = or_expr ;
or_expr     = and_expr , { "or" , and_expr } ;
and_expr    = unary , { "and" , unary } ;
unary       = "not" , unary
            | primary ;
primary     = "(" , expr , ")"
            | has_pred
            | comparison ;
has_pred    = "has" , literal ;
comparison  = fact , operator , literal ;
fact        = "path" | "surface" | "kind" | "domain" | "area" ;
operator    = "==" | "!=" | "~" ;
literal     = '"' , { any character except '"' } , '"' ;
  • Palavras-chave (and, or, not, forall, exists, module, where,

    has) e fatos são case-sensitive, minúsculos.

  • Literais são sempre entre aspas duplas. Sem escapes na v0.1 (um literal

    não contém ").

  • Precedência: not > and > or. Parênteses agrupam.

4. Semântica

Fatos. O chamador fornece um mapa fato→valor (strings):

Fato Significado Exemplo
path path repo-relativo (separador /) do alvo da ação products/dev/kvs/landing/index.html
surface superfície da ação web, mobile, desktop, cli, tv, tui
kind natureza do alvo product, area, sector, institutional, catalog, spec, package
domain domínio L1 (derivável do path pelo chamador) products
area área L2 (derivável do path) dev
component (v0.5) slug do componente em foco na sessão (domínio notices) koda, kvs, kdb
repo (v0.5) repo em foco na sessão (domínio notices) koder

Domínio notices (v0.5, koder-tools#053). Os fatos component/repo servem o koder-spec-audit notices — o análogo por-turno do applicable: um broadcast-alert declara applies_when (Kanon) sobre o foco da sessão, e o motor computa a relevância (universais sem applies_when + os que casam), substituindo "a IA lê os ~70 notices todo turno". Ausência de foco ⇒ sem base pra filtrar ⇒ todos relevantes (fail-open; nunca esconde um lock).

Domínio de fatos parametrizável (API de embedding). A tabela acima é o vocabulário do governance plane — o domínio default de kanon.Eval. Um consumidor que embarca o avaliador noutro domínio fornece o seu vocabulário via kanon.EvalWith(expr, facts, *FactDomain) (Names exatos + Prefixes abertos, ex.: arg_). A garantia anti-typo (kanon-unknown-fact) vale então por domínio, não global: um fato fora do domínio do chamador é erro alto, e os fatos de um domínio não vazam pra outro. Primeiro consumidor: a política de autorização §5 do services/ai/agents (stack-RFC-020), domínio {tool, tier, provenance, arg_*} (provenance ∈ {owner, world} = taint instrução×dados do §5.1). Isto é transporte/embedding, não gramática (seam D9): o EBNF (§3) é idêntico, logo não há bump de versãoEvalWith é aditivo e Eval segue usando o domínio de governança (backward-compatible).

Operadores. == igualdade exata · != desigualdade · ~ glob match sobre o valor do fato: * casa dentro de um segmento (não atravessa /), ** atravessa segmentos, ? um caractere. Glob ancora a string inteira.

Fato ausente (chamador não forneceu, ex.: surface desconhecida): == e ~ avaliam false; != avalia true. (Racional: ausência não pode satisfazer uma igualdade; e "não é X" é verdade do desconhecido.)

Quantificadores (v0.2). Domínio module = diretório que contém koder.toml (mesma definição do projectlayout). Pra cada módulo, os fatos são path (repo-relativo do diretório), domain, area; has "<rel>" testa a existência do path relativo dentro do módulo. where filtra o domínio; o corpo é a obrigação. forall reprova com a lista de violadores (path de cada módulo filtrado que falha o corpo); exists passa se ≥1 módulo satisfaz. has fora de quantificador e quantificador dentro de applies_when são erros nomeados (contextos não se misturam).

Erros nomeados (avaliação nunca falha em silêncio): kanon-parse-error · kanon-unknown-fact · kanon-unsupported-version · kanon-depth-exceeded.

5. Corpus de conformidade (normativo, executável)

Fatos-base do corpus: path=products/dev/kvs/landing/index.html, surface=web, kind=product, domain=products, area=dev. (sem surface) = mesmo mapa sem a chave surface.

# Expressão Fatos Resultado
C01 `path ~ "*landing

backend/*` | base | false |

C03 surface == "web" base true
C04 surface == "web" (sem surface) false
C05 surface != "tv" (sem surface) true
C06 `path ~ "*landing

landing/* and kind ="area"` | base | false |

C08 kind == "area" or kind == "product" base true
C09 not kind == "area" base true
C10 not (kind == "product" or surface == "tv") base false
C11 domain == "products" and area == "dev" base true
C12 path ~ "productskvs/**" base true
C13 `path ~ "products

index.htm?"` | base | true |

C15 banana == "x" base erro kanon-unknown-fact
C16 kind == product base erro kanon-parse-error (literal sem aspas)
C17 kind == base erro kanon-parse-error
C18 (33 parênteses aninhados) base erro kanon-depth-exceeded

Corpus estrutural (v0.2). Domínio do corpus: módulos m1 = products/dev/a {README.md}, m2 = products/dev/b {}, m3 = services/ai/c {README.kmd}.

# Expressão Resultado
C19 forall module: has "README.md" or has "README.kmd" fail; violators = [productsdevb]
C20 forall module where domain == "products": has "README.md" fail; violators = [productsdevb]
C21 exists module: has "README.kmd" pass
C22 exists module: has "LICENSE" fail
C23 has "README.md" (em applies_when/Eval) erro kanon-parse-error (has exige quantificador)
C24 forall module has "x" (sem :) erro kanon-parse-error

Corpus de refinamento (v0.3). Pai declara req-a SHALL, req-b SHOULD, req-c MAY.

# Filho declara Resultado
C25 req-b SHALL, req-c SHOULD (tighten) sem violação
C26 req-z SHALL (add) sem violação
C27 req-a SHOULD kanon-loosen em req-a
C28 req-b MAY kanon-loosen em req-b
C29 (nada) ou req-a sem keyword sem violação (herança / kmd lint)

Corpus de bindings (v0.4). Doc declara req-a SHALL, req-b SHOULD.

# Bindings Resultado
C30 req-a verifyby+block+fpbudget sem findings
C31 req-a block SEM fp_budget kanon-block-without-budget
C32 req-z (órfão) + gate "hard" kanon-binding-orphan + kanon-bad-gate
C33 nenhum binding kanon-unbound-shall em req-a (warning); req-b não acusa
C34 refines: pai block → filho warn no mesmo id kanon-gate-loosen

6. Versionamento e transporte

  • kanon: "0.1" no frontmatter do documento portador é OBRIGATÓRIO quando

    qualquer chave Kanon (applies_when) está presente; ausência = doc inválido (kanon-unsupported-version na avaliação).

  • Avaliador v0.4 aceita 0.00.4; rejeita 0.5+ e 1.x (forward-compat

    explícita, nunca tácita).

  • Documentos sem chaves Kanon não declaram kanon: — a linguagem é opt-in.