Telafina Manual do desenvolvedor telafina.com.br

Manual do desenvolvedor

Tudo para colocar a Telafina num produto e operá-la: da instalação ao adaptador, da primeira medição à vigia de produção.

versão 4.0.0-alpha.1contrato do adaptador 1runtime Node 24+navegadores Chromium e WebKit

01Visão geral

A Telafina abre o produto num navegador real e mede cada célula: uma rota, vista por uma persona, num aparelho, com um valor de eixo (escala de texto, tema, idioma). Em cada célula ela roda as sondas de layout, acessibilidade, console e rede, clica nos controles da tela sem deixar nada ser gravado e, no fim, percorre as jornadas de várias telas.

Três ideias explicam quase todo o comportamento:

  • O produto mora no seu repositório. Tudo o que é específico dele fica num adaptador em qa/telafina/. A Telafina não sabe nada de nenhum produto fora do adaptador.
  • Só entra no veredito o que se confirma. Um achado novo acima de menor é medido de novo, duas vezes, na mesma versão do código. Se não reaparecer, fica na faixa transitório, fora do veredito.
  • Decisões compartilhadas passam por merge. Nível de autonomia, isenções, regras silenciadas e tetos de gasto são arquivos no repositório do produto, e só valem depois de mesclados no branch padrão.

02Requisitos

O quêDetalhe
Node24 ou mais novo. O adaptador é TypeScript lido direto pelo Node, sem transpilador.
GitAcesso ao repositório do produto e ao da Telafina (privado).
NavegadoresChromium e WebKit do Playwright, instalados pelo telafina setup.
MemóriaUm navegador por vez, além do ambiente de desenvolvimento do produto. O env check avisa quando falta.
Claude Code (opcional)Para instalar como plugin e para o ciclo de correção. Codex CLI é o motor alternativo.
Correção autônoma (opcional)bubblewrap e socat no Linux, exigidos pela sandbox do motor.
Contrato da API (opcional)uv, para rodar o Schemathesis.
Android (opcional)adb e um aparelho ou emulador. Nunca num servidor.

03Instalação

Como plugin do Claude Code

Traz de uma vez a linha de comando, o servidor MCP e a skill.

claude plugin marketplace add lemuelcs/telafina
claude plugin install telafina@telafina

Um repositório de produto pode pré-registrar o marketplace, para o editor oferecer a instalação a quem abrir o projeto. Em .claude/settings.json do produto:

{
  "extraKnownMarketplaces": {
    "telafina": { "source": { "source": "github", "repo": "lemuelcs/telafina" } }
  },
  "enabledPlugins": { "telafina@telafina": true }
}

Por npm, sem Claude Code

npm install -g git+ssh://[email protected]/lemuelcs/telafina#v4.0.0-alpha.1

Fixe sempre uma tag. Atualizar é um ato explícito: troque a tag e instale de novo.

Preparar o ambiente

Uma vez por máquina ou servidor:

telafina setup --name meu-servidor   # dependências, Chromium, WebKit, ~/.telafina/environment.json
telafina env check                   # runtime, navegadores, memória, disco, repositório, motores, sandbox, uv, adb, e-mail

O setup pode ser repetido sem efeito colateral e desliga a telemetria do Playwright. A Telafina não envia dados de uso a lugar nenhum.

E-mail de alerta

Os alertas saem só por e-mail, por SMTP ou pela API do Resend, escolhido por ambiente em ~/.telafina/environment.json:

"mail": {
  "transport": "smtp",                      // ou "resend"
  "credentialsFile": "/root/.telafina/credentials/mail.env",
  "to": "[email protected]"
}

O arquivo de credenciais fica fora de qualquer repositório, com permissão só para o dono, e aceita estes nomes:

# SMTP (465 com TLS implícito, ou 587 com STARTTLS)
SMTP_HOST=mail.empresa.com.br
SMTP_PORT=465
SMTP_SECURE=true
[email protected]
SMTP_PASS=...
MAIL_FROM=Telafina <[email protected]>

# ou Resend
RESEND_API_KEY=...
EMAIL_FROM=Telafina <[email protected]>

O env check abre a conexão e autentica sem enviar nada. Uma entrega que falha fica registrada como não entregue e é tentada de novo na próxima execução; ela nunca derruba a medição.

04Primeiros passos

  1. Crie o adaptador em qa/telafina/telafina.config.ts no repositório do produto (veja o exemplo).
  2. Rode a checagem de conformidade. Ela entra com cada persona e lista o que está presente, faltando ou falhando, sem mudar nada:
    telafina check --product loja --repo .
  3. Meça uma rota. A resposta vem em até um minuto:
    telafina measure --route /pedidos
  4. Leia o que apareceu:
    telafina findings
    telafina finding 1f88a924
  5. Corrija o código e confirme a correção:
    telafina verify 1f88a924
  6. Quando o adaptador estiver estável, meça o produto inteiro, num terminal:
    telafina measure --all

Dentro do repositório do produto, --product e --repo podem ser omitidos: a Telafina acha o adaptador a partir da pasta atual.

05O adaptador

O adaptador é um módulo TypeScript no repositório do produto. Ele exporta defineProduct({...}), que é validado ao carregar: um campo desconhecido ou de forma errada é recusado com o nome do campo e a forma esperada. Use só sintaxe TypeScript que o Node sabe apagar (sem enum e sem namespace) e nenhum caminho absoluto.

qa/telafina/
├── telafina.config.ts     # o adaptador
├── journeys/              # jornadas de várias telas
│   └── index.ts
└── state/                 # estado compartilhado: só vale depois do merge
    ├── level.json
    ├── waivers.json
    ├── muted.json
    ├── classes.json
    ├── ceilings.json
    ├── risk.json
    ├── escapes.json
    ├── pending.json
    └── watch.json

Exemplo completo

import { defineProduct, readJsxRoutes, readCredential } from 'telafina';
import { journeys } from './journeys/index.ts';

const url = process.env.TELAFINA_LOJA_URL ?? 'https://dev.loja.com.br';

export default defineProduct({
  contract: 1,
  id: 'loja',
  language: 'pt-BR',
  targets: {
    dev: {
      url,
      landing: '/painel',
      health: [
        { name: 'spa', url: '/', expect: { status: 200 } },
        { name: 'api', url: '/api/health', expect: { status: 200, jsonPath: '$.status', equals: 'ok' } },
      ],
    },
  },
  personas: [
    { id: 'anon', anonymous: true },
    { id: 'cliente', role: 'CUSTOMER' },
    { id: 'gestor', role: 'ADMIN' },
  ],
  roles: ['CUSTOMER', 'ADMIN'],
  devices: ['iphone-se', 'iphone-15', 'galaxy-s23', 'ipad'],
  axes: [{ id: 'scale', values: [1, 1.3], confirm: 'root-font-scale' }],
  forbidden: {
    'pt-BR': {
      controls: ['excluir', 'cancelar pedido', 'publicar'],
      endsSession: ['^sair$'],
      dismiss: ['fechar', 'agora não'],
    },
  },
  expectedNon2xx: [
    { method: 'GET', path: '/api/me', status: 401, persona: 'anon', reason: 'visitante sem sessão' },
  ],
  payments: { requests: [{ method: 'POST', path: '/api/checkout' }], origins: ['checkout.provedor.com'] },
  gates: ['npm run typecheck', 'npm test'],
  protectedPaths: ['prisma/migrations', '.github'],
  version: { paths: ['src', 'package.json', 'package-lock.json'] },
  hooks: {
    listRoutes: async () => readJsxRoutes('src/App.tsx', { guardMarkers: ['RequireAuth'] }),
    signIn: async (ctx, persona, target) => {
      const email = readCredential({ env: `LOJA_QA_${persona.id.toUpperCase()}_EMAIL` });
      const senha = readCredential({ env: `LOJA_QA_${persona.id.toUpperCase()}_PASSWORD` });
      const page = await ctx.newPage();
      await page.goto(`${target.url}/entrar`);
      await page.getByLabel('E-mail').fill(email ?? '');
      await page.getByLabel('Senha').fill(senha ?? '');
      await page.getByRole('button', { name: 'Entrar' }).click();
      await page.waitForURL('**/painel');
      await page.close();
    },
    journeys: async () => journeys,
  },
});

06Campos do adaptador

CampoO que declara
contractVersão do contrato. Esta Telafina atende o contrato 1; outro número é recusado, dizendo qual lado atualizar.
idIdentidade do produto: minúsculas, dígitos e hífen, 2 a 31 caracteres.
languagept-BR ou en. Idioma do relatório e das listas de ações proibidas.
targetsAmbientes medidos, por nome. Cada um tem url, health, landing, stable (build que não muda durante a execução), productionLike (para desempenho) e production (só a vigia chega a ele).
personasTipos de usuário: id, anonymous, role, tenant e canary (a conta que a vigia usa em produção).
roles, qaTenantsPapéis que existem e inquilinos de QA. Persona com papel desconhecido reprova a checagem; persona fora de um inquilino de QA nunca entra.
devicesAparelhos do catálogo (iphone-se, iphone-15, iphone-15-pro-max, galaxy-s23, pixel-7, ipad, ipad-paisagem) ou um perfil próprio com motor, viewport, escala, user agent e áreas seguras.
axesEixos medidos por célula, como escala de texto, tema e idioma. confirm diz como conferir que o eixo pegou: root-font-scale, data-theme, color-scheme ou lang. Por padrão cobre as 15 rotas de maior risco.
forbiddenPor idioma: nomes de controles que nunca são clicados (controls), exceções seguras (safe), ids, classes, controles que encerram a sessão (endsSession) e os que fecham sobreposições (dismiss). Tudo em expressões regulares.
readOnlyRequestsPedidos que não são GET mas só leem (ex.: busca por POST). São os únicos liberados durante a varredura.
expectedNon2xxRespostas de erro esperadas, com o motivo. Não viram achado.
thirdPartyOriginsOrigens de terceiros. Seus erros de console ficam separados dos do produto.
paymentsPedidos e origens de pagamento. Nem a varredura nem as jornadas os deixam passar.
gatesComandos que uma correção precisa passar (typecheck, testes, lint).
protectedPathsCaminhos que uma correção nunca toca. Qualquer mudança neles rejeita a tentativa.
ruleLimitsAjuste das regras: minTapTarget (24–96 px), hardMinTapTarget (16–48), minFontSize (8–24), minNavLabelSize (8–20).
versionCaminhos que o alvo serve. A versão de cada achado é o commit mais um hash das mudanças não commitadas nesses caminhos.
neverCaptureRotas cuja tela nunca é capturada porque mostra um segredo. /prefixo* casa um prefixo.
declaredRoutesRotas listadas à mão, para o modo reduzido sem acesso ao repositório.
timeLimitMinutesLimite de uma medição completa (padrão 240). O que ficar de fora é relatado como lacuna.
cleanupCeilingsTeto de linhas que uma limpeza remove por tipo (padrão 500, e 500 no total).
performance, security, api, androidLiga as classes adicionais.
watchA vigia de produção: intervalo, alvo e nomes das credenciais da conta canária.
apiPrefixPrefixo da API do produto (padrão /api/).

07Ganchos

A parte de comportamento do adaptador. Todos são opcionais. Um gancho ausente desliga capacidades com nome, e o telafina check diz quais.

GanchoAssinaturaO que habilita
listRoutes() → RouteEntry[]O inventário de rotas. Sem ele, só declaredRoutes.
resolveParam(rota, persona) → resoluçãoRotas com parâmetro, como /pedidos/:id.
signIn(ctx, persona, alvo)Personas autenticadas. Cookies, localStorage e sessionStorage viram o estado da persona.
isSessionAlive(ctx) → booleanDetectar sessão que caiu no meio da execução e entrar de novo.
seedPersonas(alvo)Criar ou reparar as contas de QA antes de medir.
version() → stringVersão própria, quando o alvo não serve um checkout (ex.: hash de um build).
applyAxis(ctx, {axis, value})Aplicar um valor de eixo na célula.
journeys() → Journey[]Jornadas.
cleanup(criados, modo) → planoRemover o que as jornadas criaram, contando antes de apagar.
integrityProbe() → contagensProvar, depois da varredura, que nada de QA mudou.
isolation{create, destroy, refresh?}Cópia isolada do banco por execução.
verificationTarget{start(worktree), stop()}O ciclo de correção: sobe um alvo que serve a cópia de trabalho.
apiAuth(persona) → cabeçalhosA classe de contrato da API.
nativePath() → {steps}O caminho nativo no Android.

08Rotas

O pacote traz dois leitores para o gancho listRoutes:

readJsxRoutes(arquivo, opções)

Lê rotas do React Router escritas em JSX, sem executar nada. Junta caminhos relativos aninhados e marca como protegida a rota que está dentro de um elemento de guardMarkers (padrão RequireAuth, ProtectedRoute, PrivateRoute). roleOverrides atribui papéis por prefixo, para produtos que checam o papel dentro da página.

readJsxRoutes('src/App.tsx', {
  guardMarkers: ['RequireAuth'],
  roleOverrides: [{ prefix: '/admin', role: 'ADMIN' }],
})

readRouteModules(arquivos)

Importa módulos que exportam listas de { path, index, children }, trocando por vazio o que não é rota (páginas, componentes).

listRoutes: () => readRouteModules(['web/src/routes/gestao-routes.tsx', 'web/src/routes/cliente-routes.tsx'])

Um leitor que não acha nenhuma rota, ou não consegue ler o arquivo, recusa a execução. Uma lista vazia nunca passa por produto sem telas.

09Jornadas

Uma jornada percorre várias telas como um usuário. Ela declara a persona, se grava dados e, se gravar, o que criou, para que a verificação e a limpeza toquem só nisso.

import type { Journey } from 'telafina';
import { createdInThisRun } from 'telafina';

export const journeys: Journey[] = [
  {
    id: 'pedido-ate-o-pagamento',
    persona: 'cliente',
    writes: true,
    async run(ctx) {
      ctx.step('abre o catálogo');
      await ctx.page.goto(`${ctx.target.url}/catalogo`);
      ctx.step('adiciona o primeiro item');
      await ctx.page.getByRole('button', { name: 'Adicionar' }).first().click();
      const id = await ctx.page.getByTestId('carrinho-id').textContent();
      ctx.created('cart', id!);
      ctx.step('segue até a tela de pagamento');
      await ctx.page.getByRole('link', { name: 'Finalizar' }).click();
      ctx.reachedPayment();   // para aqui: nada é enviado ao provedor
    },
    async assert(created) {
      const id = createdInThisRun(created, 'cart', created.get('cart')![0]!);
      // consulte o banco ou a API e lance erro se o carrinho não estiver lá
    },
  },
];
No contexto ctxPara quê
page, contextA página e o contexto do Playwright, da persona e do aparelho da jornada.
step(texto)Marca o passo atual. Uma falha diz em qual passo parou.
created(tipo, id)Declara um dado criado. Só ele chega à verificação e à limpeza.
report(regra, detalhe)Registra um achado observado durante a jornada.
as(persona)Outra página, com outra persona, em sessão própria (ex.: quem convida e quem aceita).
reachedPayment()Encerra a jornada na tela de pagamento, sem enviar nada.
envConfigurações da cópia isolada, quando a execução tem uma.

Outros campos: writesThrough (screens ou api), device, watch (roda também na vigia de produção, e então precisa ser somente-leitura) e irreversible (ações irreversíveis, que só podem mirar dados que a própria execução criou). O pacote também exporta ajudas para diálogos: expectDialogOpens, expectDialogOffers e expectOverlayCloses.

10Dados e isolamento

Cada execução roda em um de três modos de dados:

ModoQuandoO que acontece
isolated-copyO adaptador tem o gancho isolationAs jornadas gravam numa cópia do banco criada para a execução e descartada no fim, termine como terminar.
signed-dataSem isolamento, com cleanupAs jornadas gravam no ambiente compartilhado, só com personas de QA; a limpeza remove exatamente o que foi declarado com ctx.created.
read-onlyVarredura de controles e vigiaNenhum pedido que grave passa pela rede.

Cópia isolada em PostgreSQL

import { postgresTemplateIsolation } from 'telafina';

isolation: postgresTemplateIsolation({
  product: 'loja',
  cluster: { urlFile: '/root/.secrets/loja-pg-admin.url' },  // usuário com CREATEDB
  template: 'loja_telafina_tpl',
  source: 'loja',
}),

A cada execução, uma cópia telafina_loja_<execução> é clonada do modelo com STRATEGY = FILE_COPY e apagada no fim. Cópias órfãs são apagadas no início da próxima. A cópia é recusada quando o tamanho do modelo mais 10 GB de reserva passa do espaço livre em disco. O modelo só é atualizado por um comando explícito, que se recusa enquanto houver alguém conectado ao banco de origem:

telafina isolation refresh

11Credenciais

Senhas de personas, chaves e endereços de banco nunca ficam no repositório. readCredential lê de uma variável de ambiente ou de um arquivo, e recusa um arquivo que esteja dentro de qualquer repositório Git:

readCredential({ env: 'LOJA_QA_CLIENTE_PASSWORD' })
readCredential({ file: '/root/.config/telafina/credentials/loja/cliente.password' })

Credenciais nunca aparecem na saída de um comando, numa ferramenta do MCP, num relatório ou no prompt do motor de correção. Valores com cara de segredo encontrados no ambiente são mascarados nos registros.

12Medir

EscopoComandoComportamento
Rotas--route /pedidos,/contaResponde em até 60 s com os achados. Aceita --axis theme=dark.
Área--area /adminTodas as rotas com esse prefixo.
Jornada--journey pedido-ate-o-pagamentoUma jornada só.
Produto inteiro--allExige você num terminal, com confirmação digitada. Roda em segundo plano.

Filtros valem em qualquer escopo: --persona cliente e --device iphone-se. --time-limit muda o limite em minutos; --target escolhe o alvo (produção é sempre recusada).

Execuções longas

Uma medição maior roda como processo destacado. O comando devolve na hora a identidade da execução (<produto>-<data UTC>-<4 hex>), uma estimativa e o caminho do relatório. Ela continua mesmo que a sessão feche.

telafina status loja-20260928T220414Z-da8d   # progresso e achados por gravidade até agora
telafina status --product loja               # as execuções do produto
telafina measure --all --wait                # espera terminar
telafina resume loja-20260928T220414Z-da8d   # retoma do primeiro grupo que não terminou
telafina cancel loja-20260928T220414Z-da8d   # encerra; o que já mediu fica

Uma execução cujo processo morreu, por exemplo com o navegador derrubado por falta de memória, fica interrompida e pode ser retomada. Uma execução por produto e por ambiente: a segunda espera ou é recusada.

Saúde e versão

Antes de cada grupo de células, as sondas de health precisam responder exatamente o esperado. Qualquer outra coisa, inclusive o 404 de um proxy na frente de um container parado, conta como fora do ar. A Telafina tenta de novo a cada 30 s por até 10 minutos e não gera achado num alvo doente.

A versão do código é lida no início e depois de cada grupo. Se mudar no meio (um merge, um hot reload), as medições seguintes ficam velhas e a execução termina incompleta. Para medir o produto inteiro, sirva uma versão fixa.

Modo reduzido

Para medir um ambiente de outra máquina, sem o repositório: telas públicas e somente leitura, pelas rotas de declaredRoutes.

telafina check --reduced --target-url https://dev.loja.com.br
telafina measure --reduced --target-url https://dev.loja.com.br --route /

13Ler resultados

Gravidade

blocker impede o uso; major atrapalha de verdade; minor é defeito sem impacto grande. Só achados acima de menor passam pela confirmação.

Faixas

Todo achado cai numa faixa. Só a faixa veredito conta para o resultado da execução e para o código de saída.

verdictconfirmado e dentro das regras: conta
pendingespera uma decisão sua
waivedisento, por prazo, com motivo
mutedregra silenciada no produto
class-not-admittedde uma classe ainda fora do veredito
stalemedido depois que a versão mudou
transientnão reapareceu nas duas confirmações
withheldretido: evidência mostraria um segredo
observationinformativo, sem regra
telafina findings                                   # execução mais recente, por prioridade, 20 por página
telafina findings --severity major --route /pedidos
telafina findings --band transient --cursor <próxima>
telafina finding 1f88a924 --detailed                # regra, elemento, esperado, observado, evidência, comando
telafina verify 1f88a924                            # corrigido, ainda reproduz, inconclusivo ou removido de propósito
telafina compare <execA> <execB>                     # corrigidos, novos, persistentes, células incomparáveis

A identidade de um achado é estável entre execuções: a mesma falha, na mesma regra e no mesmo elemento, tem o mesmo identificador.

O que cada execução grava

ArquivoPara quem
report.mdPessoas: relatório executivo no idioma do adaptador.
findings.jsonProgramas: inventário completo, todas as faixas.
findings.sarifGitHub e afins: só a faixa veredito.
junit.xmlCI: um caso por assinatura.
evidence/Recorte do elemento de cada achado; captura da tela só acima de menor.
run.jsonEstado da execução, grupo a grupo.

Retenção, aplicada no fim de cada execução e por telafina gc: evidências por 30 dias, relatórios por 12 meses, medições de rota única por 7 dias, até 5 GB por produto.

14Estado compartilhado

O que precisa ser igual em todo ambiente mora em qa/telafina/state/ no repositório do produto. A Telafina lê esse estado do branch padrão remoto, como estava no último fetch, nunca da cópia local. Uma mudança só vale depois de revisada e mesclada.

Os comandos abaixo não mudam nada direto: cada um abre a proposta num branch próprio, para você revisar.

telafina waive 1f88a924 --reason "texto legal exige 11 px" --expires 2027-01-31
telafina waive --origin accounts.google.com --reason "erros do login do Google" --expires 2026-12-31
telafina decide 8ac74e19 --accept --reason "aceito até o redesenho"
telafina decide 8ac74e19 --fix "aumentar a área do botão" --reason "afeta o checkout"
telafina unmute controle-morto --reason "falsos positivos corrigidos"
telafina classes admit performance --reason "build de homologação estável"
telafina escape add --route /conta --description "botão some no iPhone SE" --reason outside-matrix

Isenções expiram em no máximo 180 dias. Um defeito que escapou precisa de exatamente um motivo: outside-matrix, no-rule, misclassified ou deprioritised.

ArquivoConteúdo
level.jsonDegrau de autonomia do produto.
waivers.jsonIsenções, com motivo e validade.
muted.jsonRegras silenciadas no produto.
classes.jsonClasses adicionais admitidas no veredito.
ceilings.jsonTetos de gasto por ambiente (padrão US$ 30 por execução e US$ 100 por mês).
risk.jsonMapa de risco das rotas, que prioriza a ordem de medição.
escapes.jsonDefeitos que escaparam e por quê.
pending.jsonDecisões esperando você.
watch.jsonO único ambiente autorizado a rodar a vigia.

15Autonomia e correção

Degrau (level.json)O que a Telafina faz
measureMede e relata. Não toca no código. Todo produto começa aqui.
supervised-fixCorrige numa cópia de trabalho própria e mede de novo; você revisa cada correção.
unattended-prCorrige, mede e abre o pull request sozinha. Nunca mescla.
telafina fix                       # só no terminal; segue o degrau do produto
telafina fix --ceiling-usd 45      # sobe o teto só desta execução (você no terminal)

Como funciona o ciclo:

  1. Cria uma worktree Git em ~/.telafina/products/<id>/worktrees/<execução>, no branch telafina/fix-<execução>.
  2. O gancho verificationTarget sobe um alvo servindo essa cópia. Sem ele, o ciclo fica indisponível.
  3. Roda os gates antes de começar e depois de cada tentativa.
  4. O motor (Claude Code, ou Codex como alternativo) trabalha dentro da sandbox, com rede limitada ao alvo de verificação, ao Git, ao provedor de IA e ao registro de pacotes.
  5. Qualquer mudança em protectedPaths rejeita a tentativa.
  6. O achado é medido de novo no alvo de verificação. Só passa o que ficou corrigido.

Teto de gasto. Antes de cada chamada de IA, a Telafina confere o teto da execução e o do mês, por produto e por ambiente. No Claude Code, o que sobra vira --max-budget-usd, então o próprio motor não passa do limite. Todo gasto vai para ~/.telafina/products/<id>/ledger.jsonl.

16Classes adicionais

Elas começam fora do veredito: os achados aparecem, mas não contam até você admitir a classe com telafina classes admit.

ClasseComo ligarO que mede
Desempenhoperformance: {} e um alvo productionLikeLCP e CLS: mediana de três visitas válidas por rota, num aparelho de referência. Padrões 2,5 s e 0,1; ajustáveis por rota.
Segurança básicasecurity: {}Leitura passiva das respostas já visitadas: HSTS, CSP, nosniff, proteção contra frames, cookies Secure/HttpOnly/SameSite, credenciais em URL. Não envia nenhum pedido.
Contrato da APIapi: { spec } e o gancho apiAuthSchemathesis contra a descrição OpenAPI. Operações que gravam só rodam numa cópia isolada; fora dela, só GET.
Androidandroid: { package } e o gancho nativePathInstala o APK, abre o app, entra na WebView e roda as mesmas sondas de layout e acessibilidade. Lê travamentos no logcat.
telafina android run --build app-release.apk --serial emulator-5554

O Android roda só com aparelho ou emulador conectado, nunca num servidor. iOS não faz parte desta versão.

17Vigia de produção

A única coisa que a Telafina faz em produção: de hora em hora, por padrão, percorre as jornadas marcadas com watch: true, como a persona canary, com a rede em somente leitura. Se uma jornada começa a falhar, você recebe um e-mail; se volta a passar, recebe outro.

telafina watch install        # grava uma linha marcada no crontab, só no ambiente de watch.json
telafina watch status         # última execução, jornadas, falha atual, alertas não entregues
telafina watch run --now      # roda uma vez agora (você no terminal)
telafina watch uninstall      # remove só as linhas marcadas

No adaptador: watch: { intervalMinutes: 60, credentialKeys: ['LOJA_CANARIO_EMAIL', 'LOJA_CANARIO_PASSWORD'] } e um alvo com production: true. O intervalo mínimo é 5 minutos. A linha fica entre os comentários # telafina-watch:<produto> begin e end; nenhuma outra linha do crontab é tocada.

18No editor (MCP e skill)

Instalado como plugin, o servidor MCP telafina-mcp roda local, por stdio, e expõe oito ferramentas sobre o mesmo núcleo da linha de comando.

FerramentaFaz
telafina_list_productsLista os produtos deste ambiente.
telafina_start_runInicia uma medição e devolve a identidade. O produto inteiro pede confirmação.
telafina_get_runProgresso e achados até agora.
telafina_cancel_runCancela; repetir não faz nada.
telafina_list_findingsAchados por prioridade, paginados.
telafina_get_findingUm achado completo.
telafina_compare_runsCompara duas execuções.
telafina_verify_findingMede um achado de novo depois da correção.

O fluxo típico dentro do editor: a sessão de código mede a rota que acabou de mudar, lê o achado, corrige o código e chama telafina_verify_finding. O ciclo de correção autônoma não é exposto pelo MCP.

Respostas cabem em 4.000 tokens por padrão (até 20.000 com response_format: "detailed"); o que for maior vai para o disco, com o caminho na resposta. Texto vindo de páginas chega marcado como não confiável, em untrustedPageText, cortado em 500 caracteres. A skill telafina ensina à sessão quando medir e como confirmar uma correção.

19Atos do dono

A Telafina não tem contas de usuário. Quem autoriza é definido pelo tipo de ato:

  • Mudanças de estado (nível, isenções, silenciamentos, classes, tetos, vigia) só valem depois de mescladas. A revisão no GitHub é a autorização.
  • Atos imediatos (medir o produto inteiro, subir o teto de uma execução, rodar a vigia agora) exigem uma pessoa: um terminal interativo com confirmação digitada, ou a confirmação do MCP respondida por você no editor. Sem nenhum dos dois, o comando é recusado e mostra o comando de terminal a usar.

Cada ato fica registrado em ~/.telafina/audit.jsonl, com o caminho que o autorizou.

20Referência de comandos

Todo comando aceita --product, --repo, --json e --help.

ComandoFaz
setup [--name]Instala dependências e navegadores, grava environment.json, roda o env check.
env checkConfere runtime, navegadores, memória, disco, repositório, motores, sandbox, uv, adb e e-mail.
productsProdutos deste ambiente.
portfolioTodos os produtos de relance: degrau, última medição completa, bloqueios, pendências, isenções, silenciamentos, vigia, gasto.
checkConformidade do adaptador; entra com cada persona; não muda nada.
measureMede rotas, uma área, uma jornada ou o produto inteiro.
statusEstado de uma execução, ou as execuções do produto.
resume · cancelRetoma ou cancela uma execução.
findings · findingLista achados, ou lê um.
verifyMede um achado de novo.
compareCompara duas execuções.
fixCiclo de correção (só no terminal).
decidePropõe sua decisão sobre uma pendência.
waivePropõe uma isenção por assinatura ou por origem de terceiros.
unmutePropõe tirar o silêncio de uma regra.
classes admit · classes withdrawPropõe admitir ou retirar uma classe do veredito.
escape addRegistra um defeito que escapou.
gcAplica a retenção.
isolation refreshAtualiza o modelo da cópia isolada.
watch install · run · status · uninstallVigia de produção.
android runCaminho nativo no Android.

21Saída e códigos

Por padrão a saída é para pessoas. Com --json, o resultado sai em JSON no stdout e o progresso em linhas JSON no stderr, próprio para scripts e CI.

CódigoSignificado
0Concluído, nada na faixa veredito.
1Concluído, com achados no veredito.
2Recusado: faltou uma condição (alvo, confirmação, teto, adaptador inválido).
3Falhou.

Uma recusa sempre diz o que faltou e como resolver. Exemplo num CI:

telafina measure --route /,/pedidos --json > resultado.json
case $? in
  0) echo "sem achados" ;;
  1) echo "achados no veredito"; exit 1 ;;
  *) echo "medição não concluída"; exit 2 ;;
esac

22Arquivos e pastas

~/.telafina/                         # TELAFINA_HOME muda o lugar
├── environment.json                 # nome deste ambiente, e-mail
├── audit.jsonl                      # atos do dono e como foram autorizados
├── credentials/                     # credenciais de e-mail (fora de repositórios)
├── runtime/                         # dependências e navegadores instalados pelo setup
├── watch/<produto>.json · .jsonl    # estado e histórico da vigia (90 dias)
└── products/<produto>/
    ├── lock                         # uma execução por produto neste ambiente
    ├── ledger.jsonl                 # gasto de IA, chamada a chamada
    ├── worktrees/<execução>/        # cópias de trabalho da correção (7 dias)
    └── runs/<execução>/
        ├── run.json
        ├── report.md
        ├── findings.json · findings.sarif · junit.xml
        └── evidence/

23Solução de problemas

SintomaCausa e saída
adapter_invalidUm campo do adaptador está errado ou não existe. A mensagem nomeia o campo e a forma esperada.
contract_mismatchAdaptador e Telafina estão em contratos diferentes. A mensagem diz qual dos dois atualizar.
Persona FAILING no checkA entrada falhou: conta inexistente, senha trocada, troca de senha pendente ou credencial fora do lugar.
Alvo sem saúdeUma sonda de health não respondeu o esperado. Um 404 do proxy quase sempre é container parado.
Execução termina incomplete com células staleA versão servida mudou no meio: merge, pull ou hot reload. Meça numa versão fixa.
Execução interruptedO processo ou o navegador morreu (falta de memória, reinício). telafina resume <execução>.
measure --all recusadoNão havia terminal interativo nem confirmação do MCP. Rode num terminal.
Achado some na confirmaçãoÉ transitório: uma corrida de carregamento, não um defeito estável. Fica fora do veredito, de propósito.
Cópia isolada recusadaEspaço em disco abaixo do tamanho do modelo mais 10 GB, ou modelo desatualizado. Libere disco ou rode isolation refresh.
Nenhuma rota encontradaO leitor não achou o arquivo do router ou não reconheceu o formato. Confira o caminho, ou devolva uma lista estática em listRoutes.
E-mail não chegatelafina env check mostra se o transporte autentica; watch status mostra os alertas não entregues.