ADR-0084 — Runner macOS no Mac do fundador para o compile-check nativo
22/09/2026 · aceito (decisão do fundador, em viagem, por chat)
docs/adr/ADR-0084-runner-macos-no-mac-do-fundador.md
ADR-0084 — Runner macOS no Mac do fundador para o compile-check nativo
Data: 2026-09-22 · Status: aceito (decisão do fundador, em viagem, por chat) · Revisão obrigatória: 2026-09-24 (quinta), troca de usuário.
Contexto
O check required mobile-compile rodava inteiro em macos-14, runner hospedado e pago pelo GitHub. Na prática ele decidia pular em 27 das últimas 30 execuções: o diff só toca apps/jep/ios ou apps/jep/android uma vez a cada ~90 dias, e o que ele compila é uma sonda (xcodebuild -list, gradlew tasks), não um build.
Em 22/09 a cobrança da conta GitHub falhou e o job passou a morrer em 4 s sem log ("recent account payments have failed or your spending limit needs to be increased"). Como é required, nenhum PR conseguia mergear — inclusive PRs de documentação (#56, #57). A fila inteira ficou refém de um minuto de macOS que ninguém ia usar.
Todos os outros 25 jobs do repo já rodam em runners self-hosted no Hetzner (missão "CI no Hetzner", 03/09). O mobile-compile era o único que dependia de runner pago, e o comentário no próprio workflow já registrava isso como bloqueio conhecido.
Decisão
- Partir o job em três:
decidir(Linux self-hosted, todo PR, computa o diff com a política fail-closed existente),compilar(macOS self-hosted, só quandodecidirmanda) emobile-compile(Linux,if: always(), o nome required, que lê os dois resultados porscripts/ci/mobile-compile-veredito.she reprova qualquer combinação incoerente). O gate não é afrouxado: mesmo predicado, mesma sonda, mesmo nome required. Muda só a máquina que decide. - O runner macOS é o Mac do fundador (Apple Silicon, Xcode 26.6, JDK 21, Android SDK já instalados), registrado como
tynna-mac-1com a labeltynna-mac, serviçolaunchd. Minuto self-hosted é gratuito: depois disto nenhum job do repo usa runner hospedado, e a cobrança do GitHub deixa de conseguir travar a esteira. - Somente o job
compilarpode usartynna-mac.tests/standards/mobile-compile-split.test.tstrava isso: sedecidirou o veredito dependessem do Mac, todo PR ficaria em fila quando o Mac estivesse desligado. Com a separação, um Mac desligado só atrasa o PR raro que toca shell nativo — etimeout-minutes: 30transforma a espera em vermelho, não em silêncio.
Risco aceito, com prazo
O runner foi instalado sob a conta pessoal do fundador (mlermen, admin), como LaunchAgent, porque criar um usuário dedicado exige sudo com senha e o fundador está longe do Mac até quinta. O twin não tem nem pede a senha.
O que isso significa, sem eufemismo: qualquer job que rode em tynna-mac executa código de PR com acesso ao Keychain do fundador (chave do Paperclip, tokens OAuth do Google e do Telegram — o security find-generic-password responde sem senha), aos arquivos da conta e à sessão gráfica. Um PR escrito por agente, ou um prompt injection num PR, teria essa porta. É a mesma classe de risco que o PR #48 fechou no Hetzner (runner fora de docker, sudo, wheel).
Mitigações em vigor até quinta:
- O repo é privado e não recebe PR de fork; todo PR vem do twin, do Mergify ou do fundador.
- Só um job usa a label, e ele só dispara quando o diff toca
apps/*/ios|android— hoje, nenhum PR aberto faz isso. tests/standards/mobile-compile-split.test.tsreprova qualquer outro job comtynna-mac.
O que não está mitigado: um PR pode editar o próprio workflow e rodar um job arbitrário em tynna-mac. O ciso-l2 revisa mudanças em .github/workflows/**, mas com veredito de agente, não bloqueio mecânico. Por isso o prazo.
Quinta (2026-09-24), no Mac, um comando: sudo bash scripts/ops/criar-runner-mac.sh. Cria o usuário padrão tynna-runner (sem admin, Keychain vazio), instala o runner como LaunchDaemon sob ele, e imprime os passos para remover o runner da conta mlermen. Card no Paperclip com a pendência. Se em 2026-10-01 o runner ainda estiver sob mlermen, este ADR está violado e o job compilar deve voltar a macos-14 (com a cobrança resolvida), não continuar assim.
Consequências
- A fila volta a mergear sem depender de cobrança; #56 e #57 destravam com o merge deste PR.
- Um PR que toque shell nativo enquanto o Mac estiver desligado fica vermelho por timeout em 30 min, com mensagem clara; re-run quando o Mac voltar.
- O Mac vira parte da infraestrutura: entra no runbook de credenciais (quinta) e o
_diag/do runner é onde se olha quando o job não inicia. config/escopos.ymlcontinua sem omobile-compileemjobs_pesados, de propósito: a decisão dele vive no script, e mexer no contrato disparariaroda_tudo. O comentário lá que fala em "macos-14 pago" fica desatualizado até o próximo PR que toque o contrato por outro motivo.
Alternativas descartadas
- Resolver só a cobrança. Resolve hoje e volta a travar na próxima falha de cartão. A separação vale por si; a cobrança fica como fallback.
- EAS Build. Troca minuto do GitHub por cota de outro serviço, para uma sonda que não é build.
- Remover o check dos required. Seria "aceitar risco contra um gate" (G2) — e desnecessário, já que a separação mantém o gate.
- Runner como
mlermenem caráter permanente. Recusado acima; é o que o prazo existe para impedir.
Só leitura. Toda mudança nestes documentos é pull request.