Always-on
Todo componente Koder é desenhado pra estar **sempre no ar e operante**, em todos os sentidos: usuários em qualquer versão (N, N-1, N-2 dentro da janela suportada) conseguem usar; rollout de nova versão atravessando milhares de servidores por dias não causa quebra em ponta alguma; servidor cai, rede oscila, dependência falha — produto degrada com graça, nunca trava nem perde dado. Maintenance window é anti-padrão. Flag day é anti-padrão. "Atualize todo mundo antes de mudar" é anti-padrão. A inviolabilidade da experiência atravessa **versão, topologia, conectividade e surface**.
Princípio mandatory: a Koder Stack está sempre no ar e operante. Toda decisão de design considera, desde o primeiro commit, que diferentes versões do mesmo componente vão coexistir em produção, que rede e dependências falham, que clientes ficam offline, que rollouts levam dias, e que o usuário não pode perceber nada disso. Disponibilidade não é feature — é piso.
Por que
Alvo da Stack: 100M+ contas ativas, multi-região, multi-surface. Nesse regime, três fatos são inescapáveis:
- Rollout não é instantâneo. Atualizar uma frota de milhares de
servidores + bilhões de clients (mobiledesktopTV/web) leva dias ou semanas. Durante essa janela, N, N-1, e às vezes N-2 coexistem em produção. Nenhuma versão da matriz pode quebrar.
- Falhas são contínuas, não excepcionais. Em qualquer minuto,
algum DC tá com latência elevada, algum link cai, algum hardware morre, alguma dependência rate-limita. "Funciona quando tudo tá ok" é equivalente a "não funciona".
- O usuário não negocia. O usuário com Koder Talk v3.2 no celular
antigo e Koder Flow v4.1 no servidor self-hosted da empresa não tem que saber disso. Ele aperta o botão. Tem que funcionar.
Custo de fazer certo desde o começo: discipline + cobertura de testes. Custo de retrofit: incidentes, suporte 24/7 caro, perda de confiança, churn. Esta policy existe pra que toda IA / engenheiro saiba que disponibilidade é load-bearing igual segurança ou multi-tenancy — não opcional, não "a gente vê depois".
Dimensões — visão geral
| # | Dimensão | O que protege |
|---|---|---|
| §1 | Coexistência de versões N / N-1 / N-2 | Rollout multi-dia atravessa a frota sem quebrar nenhuma combinação |
| §2 | Wire-format estável + forward-compatibility | Cliente novo lê payload velho; cliente velho lê payload novo |
| §3 | Migrações de schema zero-downtime | Schema evolui sem janela, sem flag day, sem coordenação global |
| §4 | Sem janela de manutenção | Deploy nunca derruba; restart nunca é "todo mundo ao mesmo tempo" |
| §5 | Graceful degradation | Dependência caída → feature desabilita com mensagem clara, resto opera |
| §6 | Resumabilidade + tolerância offline | Operação interrompida continua de onde parou; cliente offline guarda fila |
| §7 | Multi-região / sem SPOF | Queda de DC degrada latência, não derruba o produto |
| §8 | Cross-surface compatibility | Mobile v5 conversa com desktop v3; handshake negocia capacidade |
Cada dimensão tem cláusulas normativas (R), templates de teste (T) e gate de release.
§1. Coexistência de versões N, N-1, N-2
R1.1 — Janela suportada
Todo componente declara explicitamente sua janela de versões coexistentes em koder.toml:
[compat]
window_minor_versions = 2 # N, N-1, N-2 devem interoperar
window_major_versions = 1 # major bump é breaking — separado
window_duration_days = 180 # tempo mínimo de suporte a N-1 em produçãoDefault Stack-wide: 2 minor + 1 major + 180 dias. Componentes podem TIGHTEN (mais janela), nunca LOOSEN.
R1.2 — Capability handshake
Toda conexão cliente↔servidor inicia com handshake de capability:
- Cliente envia:
{client_version, supported_features: [...]} - Servidor responde:
{server_version, accepted_features: [...]} - Cliente opera no intersection das features aceitas
Anti-padrão: cliente assume features pelo número de versão do servidor. Versão é informativa; capability é normativo.
R1.3 — Nenhum endpoint exige "estamos todos na mesma versão"
Endpoints, RPCs, protocolos, eventos: todos têm que tolerar contraparte em qualquer versão dentro da janela. Nunca:
- "404 because client is too old" → degrade, não falhe
- "500 because field is missing" → trate como ausente, não como erro
- "Force upgrade" pop-up bloqueante → só permitido após R1.1 expirar
R1.4 — Deprecation com prazo, não com flip
Remover featurecampoendpoint segue 3 fases:
- Annouce (release N): marca deprecated, log warning server-side,
doc no CHANGELOG, ticket de migração consumidores
- Quiet (release N+1, ≥ 90 dias): feature ainda funciona, sem
warning ruidoso; consumidores conhecidos já migraram
- Remove (release N+2, ≥ 180 dias após N): feature sai
Pular fase = violação. Bug fix de segurança que requer remoção imediata vai pro exception path (§ Exceptions).
§2. Wire-format estável + forward-compat
R2.1 — Unknown-field tolerance é mandatório
Toda biblioteca de serialização usada por componente Koder DEVE preservar campos desconhecidos em round-trip:
- Protobuf:
unknown_fieldspreservado (default proto3 ≥ 3.5) - JSON: parser preserva extra keys e re-serializa; nunca
strict: trueem path crítico de wire
- KMD / KVG / KPKG: parser TEM que skipar diretivas desconhecidas (já
é spec — ver
specs/document-format.kmd) - gRPC / RPC custom: idem proto
Anti-padrão: json.Unmarshal em struct sem json:",omitempty" + re-serialização dropando campos do payload original. Cliente N+1 manda campo novo, gateway N strips, servidor N+1 não recebe → bug.
R2.2 — Versão de schema é first-class
Todo payload de wire (RPC, evento, file format) carrega schema version tag no envelope:
{ "v": 3, "kind": "MessageDelivered", "payload": { ... } }Schema bump é semver:
- patch (3.0 → 3.1): só adiciona campos opcionais
- minor (3.x → 4.0): adiciona campos required dentro da janela
(com default sane no parser legado)
- major (3.x → 4.0 breaking): rara; passa por R1.4 deprecation
R2.3 — Enum é extensível
Enums em wire format SEMPRE têm valor unknown = 0 (ou equivalente) e parser trata unknown como graceful degradation:
enum MessageKind {
MESSAGE_KIND_UNSPECIFIED = 0; // forward-compat reserved
MESSAGE_KIND_TEXT = 1;
MESSAGE_KIND_IMAGE = 2;
// future: MESSAGE_KIND_VIDEO = 3 (cliente N entende como UNSPECIFIED)
}Anti-padrão: enum fechado sem fallback → cliente velho recebe valor novo, crash em switch exhaustive.
R2.4 — Forbidden breaking wire changes (sem deprecation)
| Mudança | Status |
|---|---|
| Adicionar campo opcional | ✓ OK qualquer momento |
| Adicionar enum value | ✓ OK qualquer momento |
| Adicionar endpoint novo | ✓ OK qualquer momento |
| Mudar tipo de campo existente | ✗ Major + deprecation |
| Renomear campo | ✗ Adicionar novo + deprecate antigo (R1.4) |
| Remover campo | ✗ Major + deprecation |
| Mudar semântica sem mudar tipo | ✗ Pior caso possível — proibido |
§3. Migrações de schema zero-downtime
Receitas concretas (Postgres/kdb-next, Redis, S3) em
meta/docs/stack/specs/migrations/expand-migrate-contract.kmd. Componentes copy-paste-tweak; não reinventam o padrão.kdb-next em específico: o contrato
EnsureTableé estrito — toda mudança de coluna/índice numa tabela kdb-next é uma migração e quebra o boot do binário se aplicada por acaso. Procedimento de decisão (rename*_v2vsmigrate apply --kind schema-only|stamp-rows), mecânica dokdbctle exemplos:meta/docs/stack/runbooks/kdbnext-schema-evolution.kmd.
R3.1 — Padrão expand → migrate → contract
Toda migração de schema (SQL, kdb, KV namespace, file layout) que muda forma de dado segue 3 releases mínimo:
- Expand (release N): adiciona nova forma; código N escreve em
ambas as formas; lê preferencialmente da nova, fallback pra antiga
- Migrate (entre N e N+1): backfill assíncrono converte rows
antigas pra forma nova (job em background, idempotente, resumível, per-tenant)
- Contract (release N+1, depois de migrate confirmado): código
pára de escrever na forma antiga; opcionalmente dropa coluna/tabela
Validar que N+1 e N coexistem durante o intervalo de rollout.
R3.2 — NOT NULL só depois de backfill
Adicionar NOT NULL a coluna existente é proibido sem 3 passos:
- ADD COLUMN (nullable, com default sane se aplicável)
- Código novo preenche em todo write; backfill async preenche rows
velhos
- Depois de confirmar 100% preenchido → ALTER TO
NOT NULL
Postgres / kdb: ALTER NOT NULL direto em tabela grande TRAVA escritas — é um maintenance window disfarçado. Proibido.
R3.3 — DROP é a coisa mais cara
Dropar coluna, tabela, índice, KV namespace, file blob:
- Stop reading (release N): código não lê mais; ainda escreve
- Stop writing (release N+1): código não toca mais
- Wait ≥ janela R1.1 depois do release N+1 entrar em 100% da
frota
- Drop fisicamente (release N+2 ou migration manual)
Se a janela R1.1 é 180 dias, dropar coluna leva ~6 meses calendar-time. Esse é o preço de always-on.
R3.4 — Online DDL obrigatório
Toda DDL em tabela ativa usa modo online do DB:
- Postgres:
CREATE INDEX CONCURRENTLY,ALTER TABLE ... ADD COLUMN ... DEFAULT ...sem rewrite (Postgres ≥ 11),pg_repackpra reorder - kdb: schema change via versioned descriptor; nunca lock global
- KV: novo namespace + dual-write + cutover
DDL bloqueante em tabela > 10k rows em produção = release block.
R3.5 — Validação de paridade por shadow é o DEFAULT (RFC-011)
Fonte normativa:
meta/docs/stack/rfcs/stack-RFC-011-shadow-parallel-run-validation.kmd(ratificado 2026-06-06; Phase 3 = este flip). Productionizado: o SDKengines/sdk/koder_shadow_kit+ o consumidor kdb-gateway (kdb#784).
Para uma mudança de backend storagetransform com risco de perdacorrupção de dado (nova forma de armazenamento, novo encoding, troca de engine, migração de formato), a validação de que a forma nova reproduz a antiga tem como default o shadow gate, NÃO uma janela de teste agendada:
- A forma nova roda em shadow contra o tráfego de produção (réplica do WAL /
dual-compute), o caminho antigo permanece autoritativo e intacto, e as duas são comparadas continuamente. A forma nova só é promovida quando a paridade é provada (gate
READY: ≥ N comparações limpas sobre ≥ T tempo). - É um processo contínuo e não-bloqueante em background — não serializa o
trabalho do resto da Stack atrás de uma janela datada. Sinais:
shadow_divergence_total{component,divergence_kind}+shadow_reads_total{component}(contratokoder_shadow_kit); qualquer divergência reabre o gate. - No fluxo expand→migrate→contract (R3.1): "migrate confirmado" passa a
significar, por default, shadow gate
READYsobre tráfego real — não uma espera de calendário.
A janela gated permanece a EXCEÇÃO, só para o genuinamente não-shadowável — mudanças com efeito colateral externo (fronteira I3 do RFC-011: e-mails, cobranças, webhooks de terceiros, qualquer write não-idempotente fora do nosso store). Essas mantêm rollout flag-gated / janela. Storage/transform puro não se qualifica para janela: usa shadow.
Thresholds de promoção (N/T) são config por-consumidor com um default da Stack (RFC-011 §10.5). Não confundir com a janela de versões R1.1 nem com o wait de DROP (R3.3) — esses são sobre coexistência de clientes na frota (quem ainda lê o dado), não sobre paridade da forma nova, e seguem inalterados.
§4. Sem janela de manutenção
R4.1 — Deploy é rolling, sempre
Nenhum componente Koder tem caminho legítimo de "stop everything, deploy, start". Toda release suporta:
- Rolling restart: instâncias atualizam uma de cada vez, traffic
drena, healthcheck confirma antes de seguir
- Coexistência durante rollout: instâncias N e N-1 servindo tráfego
ao mesmo tempo, possivelmente por horas
R4.2 — Healthcheck reflete prontidão real
/healthz (ou equivalente) só responde 200 quando:
- Dependências essenciais respondem
- DB pool aquecido
- Cache pré-populado (se aplicável)
- Capability handshake operacional
Anti-padrão: /healthz retorna 200 desde o main() — load balancer manda tráfego pra instância que ainda não conecta no DB → 500 pro usuário.
R4.3 — Graceful shutdown
SIGTERM → drain phase (≥ 30s default, configurable):
- Pára de aceitar conexões novas
- Termina conexões em-flight (com timeout sane)
- Flush de buffers / cache write-back / commit de transações
- Exit clean
Conexão long-lived (WebSocket, gRPC stream, SSE): server manda reconnect hint ao client antes de derrubar; client reconecta em instância nova.
R4.4 — "Maintenance mode" é proibido como default
Página/banner "estamos em manutenção" só é aceitável em incidente declarado (perda de DC, exploit ativo). Nunca como ferramenta de rollout. Toda PR que introduz maintenance toggle em path normal = block.
§5. Graceful degradation
R5.1 — Falha de dependência opcional ≠ falha do produto
Dependência indisponível → feature dependente desabilita com mensagem clara; o resto do produto continua funcional.
Exemplos concretos:
| Dependência cai | Comportamento correto |
|---|---|
| AI gateway lento | Botão "Gerar com IA" mostra spinner + fallback "tente mais tarde"; resto do editor funciona |
| Search index morto | Busca avisa "indisponível"; navegação manual funciona |
| Push notification rejeita | Mensagem chega quando user abre o app (pull funciona) |
| Telemetry endpoint 503 | Buffer local + retry; nunca bloqueia ação do user |
| Auto-update server timeout | App roda na versão atual; tenta de novo depois |
Anti-padrão: erro 500 do gateway de notificação → app trava tela do user.
R5.2 — Dependência essencial: fail-fast + circuit breaker
Dependência sem a qual a feature não pode operar:
- Circuit breaker (típico: 5 falhas em 30s → abre por 60s)
- Erro user-facing claro (
specs/errors/user-facing-messages.kmd) - Telemetria de degradação para oncall
- Re-tenta no fundo; recupera sozinho quando dep volta
R5.3 — Timeout em toda I/O network
context.WithTimeout (Go) / equivalente em qualquer linguagem: nenhuma chamada de rede sem timeout. Default sane (5s pra intra-DC, 30s pra wide-area). Timeout estourar é graceful degradation, não panic.
R5.4 — Retry com backoff exponencial + jitter
Retry sem jitter cria thundering herd depois de incidente. Mandatório:
- Backoff exponencial (base 100ms, factor 2, max 30s)
- Jitter ≥ 25% (decorrelação)
- Cap de retries (5 default)
- Idempotency-key em retry de mutation
§6. Resumabilidade + tolerância offline
R6.1 — Operações longas são resumíveis
Upload, download, sync, import, export, batch processing: cada um tem checkpoint. Conexão cai / processo morre / power cut → operação retoma de onde parou na próxima tentativa.
Protocolo:
- Upload: chunked (chunks ≤ 8 MiB), server confirma cada chunk,
cliente persiste cursor local; resume = "continue do cursor"
- Download: HTTP Range request (
Range: bytes=N-) - Sync: vector clock / lamport timestamp / opaque cursor + tombstones
- Batch: per-item commit + per-tenant checkpoint
R6.2 — Cliente offline é estado normal
Mobile / desktop apps assumem que conectividade é intermitente:
- Toda ação grava local primeiro (SQLite, kdb embarcado, file)
- Fila de outbound operations persistente
- Sync ao reconectar; conflict resolution explícita (last-writer-wins é raramente correto)
- UI mostra estado: synced / pending / failed
- Read continua funcionando 100% offline em features locais
Anti-padrão: app sem conexão mostra tela branca / erro genérico / desabilita botão sem explicação.
R6.3 — Idempotência de mutations
Toda operação que altera estado server-side aceita idempotency key (cliente gera UUID v4 / ULID, repete em retry):
POST /messages
Idempotency-Key: 01HX...
{ "thread_id": ..., "text": "..." }Servidor garante: 2 requests com mesma key → mesmo efeito + mesma resposta. Sem idempotency: retry de cliente offline duplica mensagem quando volta online.
R6.4 — Sessão sobrevive a reconnect
WebSocket / SSE / gRPC stream: cliente reconecta dentro de janela (≥ 5 min default) e recupera state sem re-autenticar.
§7. Multi-região / sem SPOF
R7.1 — Nada que toca usuário fica single-region
Storage de dado de usuário, identity service, gateway de tráfego, DNS: tudo replicado entre ≥ 2 regiões geograficamente distintas.
Single-region OK só pra:
- Componente interno (build farm, CI)
- Sandbox / dev / staging
- Catálogo público read-only (com CDN cache global)
R7.2 — Failover é automático
DC down → traffic re-routa em ≤ 60s sem ação humana:
- DNS com health-check (≤ 30s TTL pra A/AAAA críticos)
- Anycast onde aplicável
- Replicação de DB async cross-DC; failover promove read replica
- Healthcheck em cada região; orchestrator (ou Jet) tira região doente
R7.3 — Read replicas próximas do usuário
Latência percebida é orçamento finito. Reads críticos (homepage, feed, search) servem da região mais próxima do user (geo-DNS ou anycast).
R7.4 — Backups são uma forma de "always-on"
Perda de dado é o oposto extremo de always-on. Backup:
- Snapshot incremental ≥ 1×/hora pra dado mutable
- Off-site (cross-region + cold storage) ≥ 1×/dia
- Restore testado mensalmente em ambiente isolado
- Ver
policies/checkpoint-retention.kmdpra retenção
§8. Cross-surface compatibility
R8.1 — Surfaces conversam por contrato, não por versão
Mobile / desktop / TV / web / CLI do mesmo produto Koder podem estar em versões diferentes do SDK (koder_kit, koder_web_kit, etc.) e do produto. Quando trocam state (sync, IPC, shared session, KMD doc compartilhado), trocam por contrato versionado (R2.2 + R1.2).
R8.2 — Format de documento é forward-compat por construção
KMD, KVG, KPKG, qualquer file format Koder: parser ignora diretivas desconhecidas e preserva no save round-trip. Doc criado em desktop v3 abre em mobile v2 (sem features novas, sem perder bytes); abre em desktop v4 (com features novas) sem upgrade ritual.
R8.3 — IPC entre apps Koder (ver specs/ipc/protocol.kmd)
Protocolo IPC negocia versão no handshake. App receiver descobre que sender tá em versão antiga → entrega só features comuns. Nunca crasha por payload "muito novo" ou "muito velho".
R8.4 — Coverage matrix
Cada produto Koder publica em registries/variant-compat-matrix.md qual combinação de surfaces × versões foi testada. Combinações não testadas dentro da janela R1.1 = bug latente; CI bloqueia release sem coverage mínimo (T8 abaixo).
Templates de teste mandatórios
Receitas concretas (setup, run, assert) pra cada T abaixo em
meta/docs/stack/specs/testing/always-on-recipes.kmd. Componentes copy-paste-tweak; não reinventam.
T1 — Matriz de versões N × N-1
Para cada componente client+server: rodar suite em todas as combinações dentro da janela R1.1.
client v3.2 ↔ server v3.2 ✓
client v3.2 ↔ server v3.1 ✓
client v3.1 ↔ server v3.2 ✓
client v3.0 ↔ server v3.2 ✓ (N-2)
client v3.2 ↔ server v3.0 ✓Implementação: tests/compat/ com docker-compose multi-version ou matrix CI.
T2 — Unknown-field round-trip
Parser recebe payload com campo novo desconhecido → preserva no re-emit. Asserção byte-equal (depois de normalização) em todas as libs de serialização que o componente usa.
T3 — Rolling upgrade simulado
Em ambiente de teste: 3 réplicas, fazer rolling restart com tráfego ativo (load generator), assertir 0 erros 5xx percebidos pelo cliente durante a janela.
T4 — Schema migration em produção-likeness
Migration aplicada em snapshot de prod-like (millions of rows), medir locks, downtime, falha de queries concorrentes. Reject se qualquer query bloqueia > 100ms.
T5 — Chaos test de dependência opcional
Mata dependência opcional (AI gateway, search, notification): produto continua respondendo 200 nas features que não dependem dela; feature dependente degrada conforme R5.1.
T6 — Resumability de upload
Upload de 100 MiB. A 50%: kill -9 cliente. Reabrir cliente, retomar upload, assertir bytes finais iguais ao original (byte-by-byte).
T7 — Offline tolerance de mobile/desktop
Disable network → execute fluxo principal → re-enable network → assert sync completa sem perda nem duplicação.
T8 — Cross-surface compat coverage
Matrix de surfaces (mobile × desktop × web × TV × CLI) × pares de versões. Mínimo: surface mais recente × surface mais antiga da janela.
T9 — Failover regional
Simula queda da region primária; mede tempo até traffic re-roteado; assertir < 60s e zero data loss.
Gate de release
Componente NÃO entra em release pipeline se:
- Janela R1.1 não declarada em
koder.toml - T1 falha em qualquer combinação na janela
- Schema migration sem plano expandmigratecontract documentado
/healthzretorna 200 antes de dep essencial estar pronta (T3 detecta)- Operação longa sem checkpoint (T6 detecta no path crítico)
- I/O network sem timeout (audit estático)
- Cliente sem retry com jitter (audit estático)
- Coverage matrix T8 < 80% da janela
- Sem failover testado (T9) pra componente single-region servindo user data
Severity:
- Error (block release): R1.1, R2.1, R2.3, R2.4, R3.1, R3.2, R4.1, R5.3, R6.3
- Warning (advisory): R5.4, R6.4, R7.3, T8 < 100%
Anti-padrões explícitos
| Anti-pattern | Por que dói | Correto |
|---|---|---|
| "Atualizem todos os clientes antes do dia X" | Flag day — impossível em escala | R1.1 + R1.2 |
if version != current { return 426 Upgrade Required } |
Quebra metade da frota durante rollout | Capability handshake R1.2 |
json.Unmarshal strict mode em wire path |
Drops campos novos | R2.1 |
ALTER TABLE ADD COLUMN NOT NULL DEFAULT now() em tabela ativa |
Lock até backfill, downtime | R3.2 expandmigratecontract |
| Maintenance page "voltamos em 2h" pra rollout | Default = não disponível | R4.4 |
panic em código de produção |
Mata processo, mata user | Recovery + erro user-facing |
| Upload de 1 GB sem chunk | Falha 80% das vezes em conexão móvel | R6.1 chunked + resumable |
| Cron global sem checkpoint per-tenant | Reprocessa tudo após crash | R6.1 + multi-tenant |
| Single-region DB pra dado de user | Queda do DC = perda do usuário | R7.1 |
| App offline mostra tela branca | Sem comunicação = sem confiança | R6.2 |
| Retry sem jitter | Thundering herd | R5.4 |
| Enum exhaustive switch sem default | Crash em valor novo | R2.3 |
Exceptions
Caminho legítimo pra burlar always-on:
- Security fix com remoção imediata necessária — CVE crítico
exige drop de feature antes da janela expirar. Aprovado caso-a-caso pelo owner; documentado em
meta/context/incidents/. - Dev / staging environments — testes destrutivos OK em ambientes
internos.
- Componentes pré-policy (≤ 2026-05-24) — não exige refactor
imediato; próximo refactor substancial aplica. Lista em
meta/docs/stack/registries/always-on-debt.md(a ser criado quando primeira violação for descoberta).
Exception NÃO é:
- "Tô com pressa pra essa release" → no.
- "Esse componente é só interno" → ainda assim, dev experience é always-on.
- "Mas é só um experimento" → experimentos vivem em
Koder/sandbox,fora do gate de release; produção tem o gate.
Conflicts with / composes with
policies/hyperscale-first.kmd(escala): always-on é o eixode disponibilidade; hyperscale é o eixo de throughput. Os dois são ortogonais e ambos mandatórios.
policies/multi-tenant-by-default.kmd: migrações zero-downtimeda §3 aplicam per-tenant; nunca derrubam ou trancam tenants alheios à mudança.
policies/headless-first.kmd: testes T1-T9 acima são headlesspor construção; usa o mesmo SDK de teste canônico.
policies/releases.kmd: rolling deploy + janela R1.1 entramno pipeline.
policies/stack-principles.kmd(Speed > Quality): always-onvence; "ship rápido" não justifica flag day.
specs/variants/taxonomy.kmd: matriz de variantes × versõesé o que T8 cobre.
Migration path
Componentes pré-policy (existentes em 2026-05-24): não exige refactor imediato.
- Próxima release MAJOR de cada componente aplica esta policy
- Novos endpoints / wire formats / schemas em qualquer componente
seguem a policy desde o primeiro commit (mesmo que o resto do componente esteja em débito)
- Débitos catalogados em
meta/docs/stack/registries/always-on-debt.md - Auditoria automática:
koder-spec-audit always-on [--validate|--report|--fix [--dry-run]] [--strict] [--module=<path>](emproducts/dev/koder-tools/, shipped 2026-05-24) - CI gate:
.gitea/workflows/audit-always-on.ymlroda--validateem PRpush que tocakoder.tomlpolicy/auditor; soft posture inicial (warn em MISSING, error em LOOSER); promove a--strictquando os 4 componentes locked landed[compat]
Significance
Sem essa policy, o Stack pode passar todos os outros gates (hyperscale, multi-tenant, security, headless) e ainda assim viver em incidente-modo-default: cada release é uma manhã estressante, cada servidor antigo é uma bomba-relógio, cada cliente desatualizado é um ticket de suporte, cada DC com problema é um post-mortem. A diferença entre "produto Koder" e "produto Koder operável em escala planetária" mora aqui.
A policy é o equivalente de "always use TLS" pra cleartext network: todo mundo sabe; precisa estar escrito formalmente pra ser enforced.