Variantes — Build + Posture Manifest
variants specs/variants/build-manifest.kmd
Define o **manifesto de build+postura**: o artefato onde um deployment declara (a) QUAIS variantes construir/entregar — o eixo surface × target × form factor de `specs/variants/taxonomy.kmd` — e (b) para a surface `web`, QUAL render strategy cada tela usa (`canvas` / `html-ssr` / `html-templ`, ratificado em `stack-RFC-022` §4). Os defaults por tela vêm da postura de segurança do deployment (`stack-RFC-036`), porque escolher `canvas` é escolher o controle C12. Codifica os invariantes duros (nunca canvas+ssr na mesma tela; tela pública = html-*) e como o CI consome o manifesto para produzir a matriz declarada.
When this spec applies
Primary triggers
- Escolher canvas vs HTML para uma tela de um app web Koder
All triggers
- Declarar quais variantes (targets/plataformas) um deployment ou cliente recebe
- Escolher canvas vs HTML para uma tela de um app web Koder
- Configurar a postura de segurança de uma surface web (C12 opacidade vs SEO)
- Montar a matriz de build/CI de um componente com múltiplas variantes
Specification body
Manifesto de build + postura
1. O problema que resolve
A Stack tinha dois eixos desconectados:
- Qual variante entregar —
surface × target × form factor(specs/variants/taxonomy.kmd). Um Kromaview(state)já roda em desktop, android, web-canvas e headless trocando só oBackend(stack-RFC-021); "quais targets buildar" é uma matriz de build. - Como renderizar a web —
canvasvs HTML, que é o toggle do controle C12 (UI sem-DOM) da postura de segurança (stack-RFC-036): opacidade-a-máquinas de um lado, SEO/interop do outro (stack-RFC-022§4).
Não havia um artefato único onde um deployment declarasse os dois. Este é.
2. Forma
Declarativo, versionado, por deployment/tenant (RLS por tenant quando persistido).
Home canônica: bloco no koder.toml do Sector (ou .kmd irmão para deployments de cliente).
[deployment "saude-publica-macae"]
posture = "high-security-internal" # perfil RFC-036: liga o piso (mTLS + invariantes + anti-replay)
# e define o DEFAULT de render strategy das telas
# Eixo 1 — variantes a construir/entregar (taxonomy.kmd §3–§4)
variants = [
"web-browser", # <sector>-web-browser
"desktop-linux",
"mobile-android",
] # ios omitido: target de fronteira (RFC-019 §7), sem Backend ainda
# Eixo 2 — render strategy POR TELA (só para a surface `web`; RFC-022 §4.2)
[web.screen."operator-dashboard"]
strategy = "canvas" # opacidade desejável: PHI + anti-scraping (C12) + watermark/DLP (C17)
[web.screen."public-status"]
strategy = "html-ssr" # transparência exigida: SEO / preview / sem-JS
public = true
[web.screen."sign-in"]
strategy = "html-templ" # form de credencial: password-manager / autofill / IME (veto 2)
"tela", não "surface". O termo
surfacejá significa modalidade de UI na taxonomia (web/mobile/cli). A RFC-022/036 usa "surface" para tela/área do produto. Este manifesto usascreenpara o segundo sentido — vertaxonomy.kmd§3.4 (desambiguação).
3. Invariantes (duros — o lint rejeita)
- R1 — nunca
canvas+html-ssrna mesma tela. Emitir a árvore SSR reabre exatamente a superfície de scraping que o canvas fechou: a árvore semântica é legível por máquina. As duas estratégias são mutuamente exclusivas por tela, por construção (validado pelo spikekroma#211). - R2 — tela marcada
public = true(ou que precise de SEO / preview de link / degradação sem-JS) DEVE serhtml-*. Canvas é opaco a crawler por construção (RFC-022 §5).strategy = "canvas"numa tela pública é erro de lint, não escolha. - R3 — tela de credencial / digitação pesada DEVE ser
html-templ. Perde-se password-manager, autofill e IME no canvas — e o canvas não adiciona segurança ali (auth já é protegida por mTLS/step-up, piso tier-1 da RFC-036). Veto 2 da RFC-022 §4.1. - R4 —
html-ssrsó é selecionável quando o renderer existir (kroma#219, hojeblocked-byo widget kit). Até lá o lint aceita a declaração mas o build a rejeita — falhar alto é melhor que servir uma tela vazia. - R5 —
variantssó aceita combinações válidas da matriztaxonomy.kmd§4.
4. Defaults vêm da postura (não se duplica política)
O posture referencia um perfil da RFC-036 — que é o SSOT da metade de segurança. O manifesto
não redefine controles; ele só (a) herda o default de render strategy e (b) permite override
por tela.
| Postura | Default de strategy | Racional |
|---|---|---|
high-security-internal | canvas | operadores conhecidos, dado sensível; C12/C17 valem o custo |
public-self-service | html-ssr | precisa ser achável/linkável; opacidade seria contraproducente |
Overrides por tela sempre vencem o default — e os invariantes R1–R3 vencem os overrides.
5. Consumo pelo CI
O CI lê o manifesto e produz exatamente a matriz declarada:
- para cada
variant→ o job de build daquelesurface × target(o Kroma troca só oBackend); - para a surface
web→ um bundle por tela conforme astrategy:canvas⇒ build wasm32 +wasm-bindgen(o gatekroma#208já protege esse caminho);html-templ⇒ o pipeline templ/HTMX do Jet;html-ssr⇒ (quandokroma#219shippar) o emissor SSR sobre a árvore RFC-026.
Auditoria de cobertura: /k-parity compara o manifesto declarado contra as variantes efetivamente
produzidas.
6. O que este manifesto NÃO é
- Não é um perímetro de segurança. Declarar
strategy = "canvas"liga um controle de defense-in-depth (C12 = "obscurity com custo maior, não barreira criptográfica" — RFC-036). O piso real (RLS + invariantes, mTLS, anti-replay) é surface-agnóstico e protege HTML e canvas igualmente. Um manifesto não substitui o backend validar. - Não é o eixo de naming. A variante continua
<sector>-<surface>-<target>(taxonomy.kmd§5); a strategy é atributo, não identidade.
References
specs/variants/taxonomy.kmdrfcs/stack-RFC-022-kroma-web-backend.kmdrfcs/stack-RFC-036-secure-surface-and-tenant-security-alerting.kmdrfcs/stack-RFC-019-koder-ui-render-strategy.kmdpolicies/web-server.kmd