Heavy-Work Host Isolation — heavy / long / destabilizing work (test, build, compile, package, publish/CI, ISO, benchmark, simulation) runs on dedicated VMs, not the developer laptop
Toda execução **pesada** — teste, build/compilação, packaging, publicação/release (pipeline de CI), ISO, benchmark, simulação ou qualquer cenário que possa travar/consumir excessivamente o host (CPU, RAM, swap, GPU, IO, kernel) — acontece em uma **VM dedicada** hospedada em `s.khost1`, **nunca diretamente na máquina do desenvolvedor** (laptop, workstation). As VMs são compartilhadas entre IAs e humanos — para evitar interferência cruzada, cada VM aceita **uma única sessão de execução por vez** (mecânica de lock per-VM, análoga ao `koder-lock` de componentes). VMs cobrem 3 famílias de targets (linux / windows / android) através de qualquer tecnologia de virtualização (LXC, Docker, QEMU/KVM, emulador Android, etc.). As VMs **incus-backed** (Linux) são criadas via `klease create --ttl` (`infra/net/lease`) e reclamadas por TTL pelo `koder-leased` — ver §R2; isto fecha o problema da VM órfã. QEMU/KVM (Windows/Android) e Docker seguem provisionamento direto até o backend kbox do lease cobri-los.
Renomeada de
test-host-isolationem 2026-05-24 (o nome "test" era estreito demais; cobre todo trabalho pesado, incl. buildcompilação publicaçãoCI). Refs antigas migram via `projectskoder-stack#163`.
A máquina onde o desenvolvedor (humano + IA) escreve código não é a mesma máquina onde os testes rodam. Esta separação tem duas razões:
- Estabilidade da sessão. Builds e testes pesados (Koda self-host,
ISOs, fuzzers, emuladores, stress, soak) regularmente travam o laptop — OOM-killer mata a IDE, swap explode, kernel panics, GPU freezing. Cada incidente custa 10–30 min de reset + perda de contexto de sessão de IA.
- Isolamento entre IAs. O monorepo Koder é editado por múltiplas
sessões de IA simultâneas (Claude, Codex, Gemini, etc.). Quando duas sessões rodam o mesmo emulador / a mesma VM / o mesmo Wayland compositor headless ao mesmo tempo, os outputs colidem silenciosamente (eventos de input intercalados, telas misturadas, arquivos sobrescritos, ADB device 0 disputado). A mecânica de lock per-VM, descrita aqui, impede isso.
Rules
R1 — Toda execução pesada roda em VM remoto
Escopo (importante): "pesado" não é só teste. Cobre toda a família — teste, build/compilação, packaging, publicação/release (pipeline de CI), ISO, benchmark, simulação/emulação. O nome antigo desta policy (
test-host-isolation) era estreito demais; o princípio sempre foi isolamento de host pra qualquer trabalho pesadolongoque desestabiliza a workstation.
Roda em VM hospedada em s.khost1 (qualquer tecnologia de virtualização, ver §R2) toda execução que case com pelo menos um dos critérios abaixo:
| Critério | Exemplo |
|---|---|
make build / make build-safe / make kasm de Koda |
bootstrap stage1 ~7 min, ~2.7 GB RSS nasm + ~600 MB koder = swap pressure |
| Suíte de testes de qualquer componente em modo "full" | /k-test <módulo>, cargo test, go test ./..., flutter test --coverage, pytest -x |
| Test gráfico em modo headless (Chrome headless, Wayland sintético, Xvfb) | flutter test integration_test --device-id chrome, Playwright/Selenium headless |
| Test que sobe emulador Android | qualquer flutter test --device-id emulator-*, /k-reinstall <slug> --device android em fixture |
| Stress / soak / fuzz / chaos | /k-test-gen-{stress,soak,fuzz,chaos}, criterion benches, property-based tests com seeds grandes |
| Build de ISO / image / pacote pesado | lb build do infra/linux/kodix, kpkg pack, .deb/.AppImage gigantes |
| Pipeline de release / CI (act_runner job) | build Flutter multi-plataforma (flutter build linux/apk/web), packaging (fpmappimagetool), orquestração WinRM, checkout do monorepo, publicação HubFlow. O runner não roda no laptop — vive no pool em s.khost1 (infra-RFC-002 + infra/ci#001#002). Caso pioneer: `k-reinstall kterm` v1.32.3 2026-05-24 — checkout de 1,81 GB + builds saturaram o laptop |
| Qualquer execução que historicamente já travou esta máquina | listada em meta/context/test-host-isolation/known-hangs.md (catálogo crescente) |
Exceções permitidas no laptop, sem necessidade de VM:
- Edição de código (LSP, formatters, linters)
- Build incremental / single-file de componente leve (<10s wall, <500MB RSS)
- Teste unitário isolado em código puro (sem subprocess pesado)
- Smoke "hello world" pós-edição (round-trip <30s, <500MB RSS)
- Inspeçãoscripting via
grepfindgit loggh/incus list - Comandos que conversam com o VM remoto (
ssh khost1 ...,incus exec ...) mas não rodam carga aqui
R2 — Catálogo de VMs de teste em s.khost1
Toda VM de teste é provisionada em s.khost1 (177.93.107.211, EVEO Vivver LAN, ver meta/context/infrastructure/servers.md §s.khost1).
Convenção de nomes: s.khost1.<env>-<target>[-<slug>] onde <env> é um dos códigos canônicos da policies/environments.kmd (dev / stg / prd). Test VMs vivem em dev env por definição — outros envs (stg/prd) só fazem sentido pra cenários explícitos de teste-contra-staging ou teste-de-fumaça em produção. Containers de produção da Stack (s.khost1.flow, s.khost1.id, s.khost1.aivoice, …) não levam env prefix porque já são prd por convenção pré-existente.
| Target | Tecnologia preferida | Nomenclatura | Notas |
|---|---|---|---|
| Linux x86_64 | LXC via incus |
s.khost1.dev-linux-<slug> |
Ubuntu 24.04 ou Debian 13 base; share /srv/koder-monorepo via virtiofs / 9p / NFS |
| Linux ARM64 | LXC via incus (com --type=virtual-machine se cross-arch) |
s.khost1.dev-linux-arm64-<slug> |
Pra Koder Kodix ARM, kasm ARM, paridade variantes |
| Windows 10/11 | QEMU/KVM | s.khost1.dev-win10 / s.khost1.dev-win11 |
WinRM 5985 + RDP 3389; sucessor do ~/temp/win10-vm local que demora >10min pra subir |
| Android | Emulador AOSP (sistema-imagem system-images;android-NN;google_apis;x86_64) sob QEMU/KVM, ou container redroid (Docker) |
s.khost1.dev-android-<api> (ex.: dev-android-34) |
ADB exposto via incus proxy na porta 5555; um emulador por VM (evita ADB device 0 disputa) |
| Container leve (linux user-space only) | Docker / Podman | s.khost1.dev-docker-<image> |
Pra testes que não precisam de kernel customizado |
Provisioning canônico — lease (LEASE-001/006). As VMs de teste incus-backed (Linux x86_64 / ARM64 — as duas primeiras linhas da tabela) nascem via
klease create(CLI doinfra/net/lease), não porincus launchdireto:klease create --image <perfil> --ttl 4h --label project=<módulo> # ... trabalhar; em execuções longas renovar enquanto usa: klease renew <id> --ttl 4h # ao terminar (ou deixar o TTL expirar): klease release <id>O daemon
koder-leased(host-level ems.khost1) renova o lease enquanto a sessão o renova e reapa a VM por TTL quando a sessão morre sem renovar — é o reclaim de VM órfã que originou o componente (a saturaçãoload 36por VMs ad-hoc nunca reclamadas).klease list/klease info <id>/klease why <id>inspecionam o estado; o reaper exportakoder_lease_*(scrape peloinfra/observe). Garantia no-false-reap atestada (soak ativo, gateno_false_reap,infra/net/lease/docs/gates/).Escopo do reaper (Phase 0 = backend incus). O lease só gere os alvos provisionados por
incus. Windows (QEMU/KVM), emulador Android (QEMUKVM ou redroidDocker) e Docker/Podman ficam fora do reaper por ora — seguem provisionamento direto + o lock per-VM de §R3/§R4. O lease os alcança quando o backend kbox (LEASE-003, Phase 2) e o microVM scale-to-zero (LEASE-004, Phase 3) shiparem.
incus launchdireto vira exceção documentada (ex.:koder-leasedindisponível) — nesse caso, limpar a VM manualmente ao terminar.
VM ≠ servidor de produção. Containers de produção (
s.khost1.id,s.khost1.flow,s.khost1.hub, etc., listados eminfrastructure/servers.md §Containers ativos) não são VMs de teste. Não rodar teste neles a menos que seja um teste de integração contra produção, declarado explicitamente.
R3 — Lock per-VM: uma sessão de IA por vez
Cada VM de teste aceita uma única sessão de execução por vez, independentemente de qual IA (Claude, Codex, Gemini, etc.) ou humano está pilotando. O mecanismo de lock é análogo ao lock per-componente (koder-lock / meta/context/notices/active/lock-<slug>.md) mas em um diretório paralelo dedicado a VMs.
Diretório de locks de VM: meta/context/notices/vm-active/ (separado do vm-active/ vs active/ para não confundir locks de componente com locks de VM — uma sessão pode ter os dois ao mesmo tempo: lock no componente sendo editado + lock no VM onde os testes rodam).
Arquivo de lock: meta/context/notices/vm-active/lock-<vm-id>.md
Frontmatter obrigatório:
---
vm: s.khost1.dev-linux-<slug> # ID canônico da VM (env prefix canônico — ver policies/environments.kmd)
session: <ai-session-tag> # ex.: claude-2026-05-19-rfc019, codex-2026-05-19-id
ai: claude | codex | gemini | <other> | human
started: <iso-8601-utc> # ex.: 2026-05-19T22:30:00Z
reason: |
<uma frase: que teste/operação está rodando + ticket referenciado>
heartbeat: <iso-8601-utc> # opcional; updateado por sessões longas
ttl_minutes: 60 # opcional; default 120 se omitido
---O conteúdo do arquivo é livre — vale repetir comandos exatos que estão rodando, output snapshots, etc., para outras sessões inspecionarem.
R4 — Fluxo de aquisição / liberação de lock de VM
Antes de rodar qualquer carga numa VM listada em §R2:
- Check. Ler
meta/context/notices/vm-active/lock-<vm-id>.md.- Não existe → pode prosseguir (ir pra §R4.2)
- Existe + não é minha sessão → outra sessão de IA/humano está
usando essa VM. Não invadir. Opções:
- (a) Aguardar (se urgente: pollar a cada 30s o arquivo até sumir)
- (b) Usar outra VM (provisionar uma nova se a alvo for genérica
o suficiente — ex.:
dev-linux-koda-1vsdev-linux-koda-2) - (c) Sinalizar pro usuário e parar — explicitamente reportar
qual sessão está ocupando, há quanto tempo, e que ticket
- Existe + é minha sessão → seguir adiante (lock já meu)
- Existe + sessão expirada (mais que
ttl_minutessem updatedo
heartbeat, OUstartedhá mais de 24h e nenhumheartbeat) → considerar lock órfão; arquivar (mover prameta/context/notices/vm-active-archive/lock-<vm-id>-expired-<date>.md) com nota explicando, e prosseguir pra §R4.2
- Place. Escrever o arquivo de lock com o frontmatter de §R3.
Commitar (pode ser commit independente ou junto com o trabalho subsequente — escolher o que reduzir churn).
- Use. Executar os testes/builds no VM.
- Para execuções >10min, atualizar
heartbeatem intervalos<
ttl_minutes/3para evitar staleness.
- Para execuções >10min, atualizar
- Archive. Ao concluir, mover o arquivo pra
meta/context/notices/vm-active-archive/lock-<vm-id>-<YYYY-MM-DD>.md. Não deletar — histórico fica como audit trail.
Implementação preferida: comando
koder-lock vm <subcmd>análogo aokoder-lockde componentes. Tracking emdev/koder-tools— até shipar, o fluxo manual acima vale.
R5 — Testes gráficos em modo headless NÃO são exceção
Mesmo em modo headless, testes gráficos consomem recursos de GPU virtual / framebuffer / Wayland compositor / ADB / emulador. Roda em VM:
- Flutter integration tests headless
- Chrome headless + PlaywrightSeleniumPuppeteer
- Xvfb / Wayland compositor sintético (Weston/labwc) em test runners
- Android emulator (qualquer API level)
- iOS simulator (futuro — quando shippar, mesmo princípio em
s.khost1via UTM ou similar)
Para emuladores Android, a regra de §R3 (uma sessão por VM) é especialmente importante: ADB device 0 é serializado pelo emulador. Duas sessões emitindo adb shell ao mesmo emulador intercalam comandos.
R6 — Onde ficam os dados de teste
Os repositórios da Stack vivem em ~/dev/koder/ no laptop. Para os VMs lerem o código, há 3 estratégias por ordem de preferência:
- Bind mount via incus (LXC compartilha o diretório
real-time):
incus config device add <vm> monorepo disk source=/home/koder/dev/koder path=/home/koder/dev/koder readonly=true - NFS export do laptop pro
s.khost1(read-only) - Git fetch dentro do VM (clone
https://flow.koder.dev/Koder/koder.git) — mais lento, mas elimina dependência de o laptop estar ligado
Para artefatos de build / outputs / logs, escrever em path local da VM (ex.: /tmp/, /srv/test-out/). Não escrever de volta no monorepo exceto via PR/commit consciente.
R7 — Conflito entre lock per-componente e lock per-VM
Os dois locks são ortogonais. Uma sessão típica de IA que edita engines/lang/koda e roda testes em dev-linux-koda mantém:
meta/context/notices/active/lock-engines-lang-koda.md(componente)meta/context/notices/vm-active/lock-s.khost1.dev-linux-koda.md(VM)
Liberar na ordem reversa da aquisição (LIFO): VM primeiro, depois componente. Garante que se outra sessão pegou o componente entre liberação e fim, ela ainda vê os artefatos da build prévia consistentes.
R8 — Onde criar a primeira VM (bootstrap)
Caminho canônico hoje é o lease (§R2):
klease create --ttl 4h --label project=<módulo>, com reclaim por TTL. O blocoincus launchabaixo é o que okleaseautomatiza por baixo (e o fallback de bootstrap quando okoder-leasedestá indisponível); o device do monorepo entra via perfil de imagem ouincus config device addna instância do lease.
Para Koda especificamente — o primeiro consumidor desta policy — provisionar s.khost1.dev-linux-koda:
# Em s.khost1, root:
incus launch images:debian/13 dev-linux-koda
incus config device add dev-linux-koda monorepo disk \
source=/home/koder/dev/koder \
path=/home/koder/dev/koder \
readonly=false
incus exec dev-linux-koda -- apt update
incus exec dev-linux-koda -- apt install -y nasm gcc binutils make git
# Run from inside:
incus exec dev-linux-koda -- bash -c \
'cd /home/koder/dev/koder/engines/lang/koda && make build-safe'Detalhes finais (NFS vs bind, network, snapshots para reset rápido) ficam no runbook
meta/context/runbooks/test-vm-provisioning.mdque a primeira sessão que provisionar deve escrever.
R9 — Carve-out: hardware ausente no datacenter
A regra geral §R1 é "trabalho pesado roda em VM no DC, nunca no laptop." Excepcionalmente, quando o trabalho pesado depende de hardware que o DC (s.khost1 hoje) não tem e o laptop tem esse hardware, a execução no laptop é autorizada.
O princípio é "DC primeiro; laptop só se o DC fisicamente não pode." Não é frequência, duração, nem ergonomia — é capacidade. Se o DC adquirir o hardware no futuro, o trabalho retorna pra lá sem necessidade de re-ratificar este carve-out.
Critérios cumulativos (TODOS obrigatórios)
- O workload falha ou degrada inviavelmente quando rodado no
hardware disponível no DC (e o motivo é uma capacidade ausente — não preguiça de provisionar, não otimização de custo, não conveniência).
- O laptop tem a capacidade exigida (driver carregado,
versão suficiente, recursos físicos adequados).
- A combinação "DC-não-tem + laptop-tem" está *egistrada e
atualizada*em
registries/hardware-readiness.md— qualquer workload novo precisa de uma linha (ou de cobertura por uma linha existente) antes de rodar.
Procedimento
- Conferir que a row aplicável está em
hardware-readiness.md§"Tickets cobertos pelo carve-out".
- Place lock per-component (`koder-lock place slug --reason ticket
running carve-out`) — mesmo lock que cobriria a VM remota se o DC tivesse o hardware.
- Executar; capturar evidência (logs, gráficos, baselines, screenshots
de
chrome://gpuquando aplicável). - Persistir resultados no destino canônico do workload
(
registries/perf-baseline.md, manifest, ticket, etc.). - Arquivar o lock.
Reversibilidade
O DC adquiriu o hardware? Reroutear o workload pro DC na mesma janela em que hardware-readiness.md flipa a row pra active no DC. O carve-out deixa de aplicar automaticamente — não precisa editar esta policy nem o ticket.
Limites estruturais (carve-out NÃO autoriza)
- Workloads em que o DC tem o hardware mas a IA preferiu o
laptop ("é mais rápido", "tenho lock aqui", "evito tar-pipe") — isso continua violação direta de §R1.
- Workloads em que o hardware do laptop não atende ao requisito
do workload (capacidade insuficiente, driver errado, versão velha) — o critério (2) acima reprova; o workload espera procurement.
- Substituir a auditoria: continua sendo
koder-spec-audit test-host+ walks de PR — workloads cobertos pelo carve-out aparecem com o registry-row referenciado no commit message.
Por que existe
A Stack hoje tem 1 host (s.khost1) com Tesla T4 (datacenter-class, sem display, sem GL consumer) e um laptop com RTX 4070 Mobile (consumer-class com GL real). Há workloads — benchmarks gráficos Chrome (JetStreamMotionMark), iteração visual de WaylandCEF render paths — que só rodam onde tem GL consumer. Esperar procurement de GPU consumer pro DC trava esses workloads indefinidamente sem justificativa estrutural: o hardware necessário já existe na rede; está só no lado errado.
O carve-out reconhece essa anomalia sem fingir que não existe e sem abrir a policy pra usos não-justificados (volta-redonda nos critérios 1+2+3). Quando o DC adquirir GPU consumer (procurement ratificado em hardware-readiness.md row gpu-consumer), o carve-out naturalmente esvazia.
R10 — Perf gates ruidosos: pinar CPU/NUMA, não confiar no host nu
Um benchmark/throughput-gate que roda numa VM compartilhada de s.khost1 (load alto, 6+ sessões) tem ratio confiável só se ambos os lados do A/B compartilharem os mesmos cores + memória NUMA-local — afinidade, não host nu (medido: baseline do self-compile da koda variou 17 % run-a-run sem pin). Use o modo --pin "<cpulist>" [--membind <node>] do gate (mecânica: systemd-run --scope -p AllowedCPUs=… -p AllowedMemoryNodes=… -p MemoryMax=…, auto-liberado na saída do scope) — afinidade pina nossos procs, não evicta vizinhos. Exclusividade real de core só via reserved-compute-pool do Kodix (infra/linux/kodix#069) consumido por kbox run --reserve-cpus (infra/net/box#151); até lá, --pin é o interim e o futuro --reserve será o modo exclusivo. Referência de implementação: engines/lang/koda/scripts/verify-gc-change.sh --pin (koda#808).
Tests (validação da policy)
| ID | Verificação |
|---|---|
| T1 | Audit walk encontra commits recentes que rodaram make build / make kasm no laptop sem lock de VM ativo. PASS = zero ocorrências; FAIL = listar PRs/commits violadores |
| T2 | Audit walk encontra arquivos em meta/context/notices/vm-active/ com started há > ttl_minutes e sem heartbeat — locks órfãos |
| T3 | Dois locks distintos em vm-active/ pra mesma VM — PASS = nunca |
| T4 | infrastructure/servers.md §Containers ativos lista as VMs declaradas em §R2 que já estão provisionadas — sincronia entre policy + registro |
| T5 | meta/context/runbooks/test-vm-provisioning.md existe e cobre os 4 targets (linux x86, linux arm, windows, android) |
| T6 | Toda execução pesada rodada no laptop dentro do carve-out §R9 referencia em commit-msg a row aplicável de registries/hardware-readiness.md + lock arquivado. PASS = zero execuções de carve-out sem a referência; FAIL = listar commits |
Trigger phrase mapping
Padrões que indicam violação iminente da policy:
make build/make build-safe/make kasmrodando em~/dev/koder/engines/lang/kodano laptop → REROUTE pra VMflutter test --device-id chromeno laptop → REROUTE pra VMemulator -avd ...no laptop → REROUTE pra VMqemu-system-...no laptop pra Windows VM local → MIGRAR pras.khost1.dev-win10lb build(ISO Koder Kodix) no laptop → REROUTE pra VM ous.khost1direto
Phase-current note
Fase atual — aceleração: o catálogo de VMs (§R2) está em construção. A regra obriga que antes de rodar o teste, a IA provisione a VM se ainda não existir, escreva o runbook em
meta/context/runbooks/test-vm-provisioning.md, e só então rode. O custo de provisionar a primeira VM é amortizado por todas as sessões subsequentes.
Histórico
- 2026-05-19 — policy ratificada após sequência de incidentes
(#770#773#774) onde builds de Koda travaram o laptop com OOM-killer + swap saturado + segfaults durante GC mark walker, custando 30+ minutos por incidente. Owner direcionou: "todos os testes da koda devem ser realizados em uma vm de testes que deve ser criada no s.khost1". Esta policy generaliza pra Stack-wide.