Skip to content

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

All triggers

Specification body

Manifesto de build + postura

1. O problema que resolve

A Stack tinha dois eixos desconectados:

  1. Qual variante entregarsurface × target × form factor (specs/variants/taxonomy.kmd). Um Kroma view(state) já roda em desktop, android, web-canvas e headless trocando só o Backend (stack-RFC-021); "quais targets buildar" é uma matriz de build.
  2. Como renderizar a webcanvas vs 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 surface já significa modalidade de UI na taxonomia (web/mobile/cli). A RFC-022/036 usa "surface" para tela/área do produto. Este manifesto usa screen para o segundo sentido — ver taxonomy.kmd §3.4 (desambiguação).

3. Invariantes (duros — o lint rejeita)

  • R1 — nunca canvas + html-ssr na 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 spike kroma#211).
  • R2 — tela marcada public = true (ou que precise de SEO / preview de link / degradação sem-JS) DEVE ser html-*. 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-ssr só é selecionável quando o renderer existir (kroma#219, hoje blocked-by o widget kit). Até lá o lint aceita a declaração mas o build a rejeita — falhar alto é melhor que servir uma tela vazia.
  • R5 — variants só aceita combinações válidas da matriz taxonomy.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.

PosturaDefault de strategyRacional
high-security-internalcanvasoperadores conhecidos, dado sensível; C12/C17 valem o custo
public-self-servicehtml-ssrprecisa 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 daquele surface × target (o Kroma troca só o Backend);
  • para a surface web → um bundle por tela conforme a strategy: canvas ⇒ build wasm32 + wasm-bindgen (o gate kroma#208 já protege esse caminho); html-templ ⇒ o pipeline templ/HTMX do Jet; html-ssr ⇒ (quando kroma#219 shippar) 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