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.
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 |
|---|---|
| Node | 24 ou mais novo. O adaptador é TypeScript lido direto pelo Node, sem transpilador. |
| Git | Acesso ao repositório do produto e ao da Telafina (privado). |
| Navegadores | Chromium e WebKit do Playwright, instalados pelo telafina setup. |
| Memória | Um 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
- Crie o adaptador em
qa/telafina/telafina.config.tsno repositório do produto (veja o exemplo). - 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 . - Meça uma rota. A resposta vem em até um minuto:
telafina measure --route /pedidos - Leia o que apareceu:
telafina findings telafina finding 1f88a924 - Corrija o código e confirme a correção:
telafina verify 1f88a924 - 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
| Campo | O que declara |
|---|---|
contract | Versão do contrato. Esta Telafina atende o contrato 1; outro número é recusado, dizendo qual lado atualizar. |
id | Identidade do produto: minúsculas, dígitos e hífen, 2 a 31 caracteres. |
language | pt-BR ou en. Idioma do relatório e das listas de ações proibidas. |
targets | Ambientes 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). |
personas | Tipos de usuário: id, anonymous, role, tenant e canary (a conta que a vigia usa em produção). |
roles, qaTenants | Papéis que existem e inquilinos de QA. Persona com papel desconhecido reprova a checagem; persona fora de um inquilino de QA nunca entra. |
devices | Aparelhos 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. |
axes | Eixos 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. |
forbidden | Por 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. |
readOnlyRequests | Pedidos que não são GET mas só leem (ex.: busca por POST). São os únicos liberados durante a varredura. |
expectedNon2xx | Respostas de erro esperadas, com o motivo. Não viram achado. |
thirdPartyOrigins | Origens de terceiros. Seus erros de console ficam separados dos do produto. |
payments | Pedidos e origens de pagamento. Nem a varredura nem as jornadas os deixam passar. |
gates | Comandos que uma correção precisa passar (typecheck, testes, lint). |
protectedPaths | Caminhos que uma correção nunca toca. Qualquer mudança neles rejeita a tentativa. |
ruleLimits | Ajuste das regras: minTapTarget (24–96 px), hardMinTapTarget (16–48), minFontSize (8–24), minNavLabelSize (8–20). |
version | Caminhos que o alvo serve. A versão de cada achado é o commit mais um hash das mudanças não commitadas nesses caminhos. |
neverCapture | Rotas cuja tela nunca é capturada porque mostra um segredo. /prefixo* casa um prefixo. |
declaredRoutes | Rotas listadas à mão, para o modo reduzido sem acesso ao repositório. |
timeLimitMinutes | Limite de uma medição completa (padrão 240). O que ficar de fora é relatado como lacuna. |
cleanupCeilings | Teto de linhas que uma limpeza remove por tipo (padrão 500, e 500 no total). |
performance, security, api, android | Liga as classes adicionais. |
watch | A vigia de produção: intervalo, alvo e nomes das credenciais da conta canária. |
apiPrefix | Prefixo 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.
| Gancho | Assinatura | O que habilita |
|---|---|---|
listRoutes | () → RouteEntry[] | O inventário de rotas. Sem ele, só declaredRoutes. |
resolveParam | (rota, persona) → resolução | Rotas com parâmetro, como /pedidos/:id. |
signIn | (ctx, persona, alvo) | Personas autenticadas. Cookies, localStorage e sessionStorage viram o estado da persona. |
isSessionAlive | (ctx) → boolean | Detectar 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 | () → string | Versã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) → plano | Remover o que as jornadas criaram, contando antes de apagar. |
integrityProbe | () → contagens | Provar, 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çalhos | A 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 ctx | Para quê |
|---|---|
page, context | A 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. |
env | Configuraçõ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:
| Modo | Quando | O que acontece |
|---|---|---|
isolated-copy | O adaptador tem o gancho isolation | As jornadas gravam numa cópia do banco criada para a execução e descartada no fim, termine como terminar. |
signed-data | Sem isolamento, com cleanup | As jornadas gravam no ambiente compartilhado, só com personas de QA; a limpeza remove exatamente o que foi declarado com ctx.created. |
read-only | Varredura de controles e vigia | Nenhum 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
| Escopo | Comando | Comportamento |
|---|---|---|
| Rotas | --route /pedidos,/conta | Responde em até 60 s com os achados. Aceita --axis theme=dark. |
| Área | --area /admin | Todas as rotas com esse prefixo. |
| Jornada | --journey pedido-ate-o-pagamento | Uma jornada só. |
| Produto inteiro | --all | Exige 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.
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
| Arquivo | Para quem |
|---|---|
report.md | Pessoas: relatório executivo no idioma do adaptador. |
findings.json | Programas: inventário completo, todas as faixas. |
findings.sarif | GitHub e afins: só a faixa veredito. |
junit.xml | CI: um caso por assinatura. |
evidence/ | Recorte do elemento de cada achado; captura da tela só acima de menor. |
run.json | Estado 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.
| Arquivo | Conteúdo |
|---|---|
level.json | Degrau de autonomia do produto. |
waivers.json | Isenções, com motivo e validade. |
muted.json | Regras silenciadas no produto. |
classes.json | Classes adicionais admitidas no veredito. |
ceilings.json | Tetos de gasto por ambiente (padrão US$ 30 por execução e US$ 100 por mês). |
risk.json | Mapa de risco das rotas, que prioriza a ordem de medição. |
escapes.json | Defeitos que escaparam e por quê. |
pending.json | Decisões esperando você. |
watch.json | O único ambiente autorizado a rodar a vigia. |
15Autonomia e correção
Degrau (level.json) | O que a Telafina faz |
|---|---|
measure | Mede e relata. Não toca no código. Todo produto começa aqui. |
supervised-fix | Corrige numa cópia de trabalho própria e mede de novo; você revisa cada correção. |
unattended-pr | Corrige, 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:
- Cria uma worktree Git em
~/.telafina/products/<id>/worktrees/<execução>, no branchtelafina/fix-<execução>. - O gancho
verificationTargetsobe um alvo servindo essa cópia. Sem ele, o ciclo fica indisponível. - Roda os
gatesantes de começar e depois de cada tentativa. - 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.
- Qualquer mudança em
protectedPathsrejeita a tentativa. - 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.
| Classe | Como ligar | O que mede |
|---|---|---|
| Desempenho | performance: {} e um alvo productionLike | LCP 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ásica | security: {} | 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 API | api: { spec } e o gancho apiAuth | Schemathesis contra a descrição OpenAPI. Operações que gravam só rodam numa cópia isolada; fora dela, só GET. |
| Android | android: { package } e o gancho nativePath | Instala 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.
| Ferramenta | Faz |
|---|---|
telafina_list_products | Lista os produtos deste ambiente. |
telafina_start_run | Inicia uma medição e devolve a identidade. O produto inteiro pede confirmação. |
telafina_get_run | Progresso e achados até agora. |
telafina_cancel_run | Cancela; repetir não faz nada. |
telafina_list_findings | Achados por prioridade, paginados. |
telafina_get_finding | Um achado completo. |
telafina_compare_runs | Compara duas execuções. |
telafina_verify_finding | Mede 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.
| Comando | Faz |
|---|---|
setup [--name] | Instala dependências e navegadores, grava environment.json, roda o env check. |
env check | Confere runtime, navegadores, memória, disco, repositório, motores, sandbox, uv, adb e e-mail. |
products | Produtos deste ambiente. |
portfolio | Todos os produtos de relance: degrau, última medição completa, bloqueios, pendências, isenções, silenciamentos, vigia, gasto. |
check | Conformidade do adaptador; entra com cada persona; não muda nada. |
measure | Mede rotas, uma área, uma jornada ou o produto inteiro. |
status | Estado de uma execução, ou as execuções do produto. |
resume · cancel | Retoma ou cancela uma execução. |
findings · finding | Lista achados, ou lê um. |
verify | Mede um achado de novo. |
compare | Compara duas execuções. |
fix | Ciclo de correção (só no terminal). |
decide | Propõe sua decisão sobre uma pendência. |
waive | Propõe uma isenção por assinatura ou por origem de terceiros. |
unmute | Propõe tirar o silêncio de uma regra. |
classes admit · classes withdraw | Propõe admitir ou retirar uma classe do veredito. |
escape add | Registra um defeito que escapou. |
gc | Aplica a retenção. |
isolation refresh | Atualiza o modelo da cópia isolada. |
watch install · run · status · uninstall | Vigia de produção. |
android run | Caminho 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ódigo | Significado |
|---|---|
0 | Concluído, nada na faixa veredito. |
1 | Concluído, com achados no veredito. |
2 | Recusado: faltou uma condição (alvo, confirmação, teto, adaptador inválido). |
3 | Falhou. |
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
| Sintoma | Causa e saída |
|---|---|
adapter_invalid | Um campo do adaptador está errado ou não existe. A mensagem nomeia o campo e a forma esperada. |
contract_mismatch | Adaptador e Telafina estão em contratos diferentes. A mensagem diz qual dos dois atualizar. |
Persona FAILING no check | A entrada falhou: conta inexistente, senha trocada, troca de senha pendente ou credencial fora do lugar. |
| Alvo sem saúde | Uma sonda de health não respondeu o esperado. Um 404 do proxy quase sempre é container parado. |
Execução termina incomplete com células stale | A versão servida mudou no meio: merge, pull ou hot reload. Meça numa versão fixa. |
Execução interrupted | O processo ou o navegador morreu (falta de memória, reinício). telafina resume <execução>. |
measure --all recusado | Nã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 recusada | Espaço em disco abaixo do tamanho do modelo mais 10 GB, ou modelo desatualizado. Libere disco ou rode isolation refresh. |
| Nenhuma rota encontrada | O 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 chega | telafina env check mostra se o transporte autentica; watch status mostra os alertas não entregues. |