Kanon expressions v0.4
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-gosobre a deliberação/k-arch; registrycomponent-names.md). Kanon (gr. kanōn, régua/regra) é carga, não portador: vive dentro do.kmd(frontmatter hoje; fenced blockskanon em versões futuras) e é avaliada exclusivamente pelo motor dokoder-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-exceededacima 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.kmdv0.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/reposervem okoder-spec-audit notices— o análogo por-turno doapplicable: um broadcast-alert declaraapplies_when(Kanon) sobre o foco da sessão, e o motor computa a relevância (universais semapplies_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 viakanon.EvalWith(expr, facts, *FactDomain)(Namesexatos +Prefixesabertos, 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 doservices/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ão —EvalWithé aditivo eEvalsegue 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 quandoqualquer chave Kanon (
applies_when) está presente; ausência = doc inválido (kanon-unsupported-versionna avaliação).- Avaliador v0.4 aceita
0.0–0.4; rejeita0.5+e1.x(forward-compatexplícita, nunca tácita).
- Documentos sem chaves Kanon não declaram
kanon:— a linguagem é opt-in.