Skip to content

Kanon expressions v0.4

kanon specs/kanon/expressions.kmd

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).

When this spec applies

All triggers

Specification body

Spec — Kanon expressions v0.5

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 stack-manifest.kmd). 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/**" and kind == "product"

v0.2 — regra estrutural (quantificador): obrigação sobre o grafo de módulos, avaliada por koder-spec-audit structural:

forall module where domain == "products": has "README.md" or has "README.kmd"
exists module: path ~ "**/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):

FatoSignificadoExemplo
pathpath repo-relativo (separador /) do alvo da açãoproducts/dev/kvs/landing/index.html
surfacesuperfície da açãoweb, mobile, desktop, cli, tv, tui
kindnatureza do alvoproduct, area, sector, institutional, catalog, spec, package
domaindomí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ãoFatosResultado
C01path ~ "**/landing/**"basetrue
C02path ~ "**/backend/**"basefalse
C03surface == "web"basetrue
C04surface == "web"(sem surface)false
C05surface != "tv"(sem surface)true
C06path ~ "**/landing/**" and kind == "product"basetrue
C07path ~ "**/landing/**" and kind == "area"basefalse
C08kind == "area" or kind == "product"basetrue
C09not kind == "area"basetrue
C10not (kind == "product" or surface == "tv")basefalse
C11domain == "products" and area == "dev"basetrue
C12path ~ "products/*/kvs/**"basetrue
C13path ~ "products/*"basefalse (um * não atravessa /)
C14path ~ "**/index.htm?"basetrue
C15banana == "x"baseerro kanon-unknown-fact
C16kind == productbaseerro kanon-parse-error (literal sem aspas)
C17kind ==baseerro kanon-parse-error
C18(33 parênteses aninhados)baseerro 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ãoResultado
C19forall module: has "README.md" or has "README.kmd"fail; violators = [products/dev/b]
C20forall module where domain == "products": has "README.md"fail; violators = [products/dev/b]
C21exists module: has "README.kmd"pass
C22exists module: has "LICENSE"fail
C23has "README.md" (em applies_when/Eval)erro kanon-parse-error (has exige quantificador)
C24forall 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 declaraResultado
C25req-b SHALL, req-c SHOULD (tighten)sem violação
C26req-z SHALL (add)sem violação
C27req-a SHOULDkanon-loosen em req-a
C28req-b MAYkanon-loosen em req-b
C29(nada) ou req-a sem keywordsem violação (herança / kmd lint)

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

#BindingsResultado
C30req-a verify_by+block+fp_budgetsem findings
C31req-a block SEM fp_budgetkanon-block-without-budget
C32req-z (órfão) + gate "hard"kanon-binding-orphan + kanon-bad-gate
C33nenhum bindingkanon-unbound-shall em req-a (warning); req-b não acusa
C34refines: pai block → filho warn no mesmo idkanon-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.

References