Produto
Nesta página (7 seções)
Situação do LeIA em 13/09/2026
Retrato congelado do dia do evento. Este inventário descreve o produto como ele estava em 13/09/2026 e não é atualizado depois disso. O que aparece aqui como verificado, pendente ou ausente vale para aquela data, não para hoje: parte das pendências já foi corrigida. Para o estado atual, leia o
CHANGELOG.mde o código.
Inventário verificado em produção e no código no dia 13/09/2026, entre 12h e 16h30 (horário de Brasília).
Fontes: app publicado em https://leia-snowy.vercel.app, serviço em
https://llm-service-production-4278.up.railway.app, repositório repos/leia em HEAD e9b7eb2
(a build em produção passou de dev para 589da62 no meio da revisão). Capturas de tela e logs da
revisão ficaram em /tmp/leia-review/screens/, /tmp/leia-review/flows/ e /tmp/leia-review/*.json
(fora do repositório). Nenhum segredo foi lido ou copiado para este arquivo.
Regra de leitura: "verificado" significa observado em produção com captura de tela ou resposta HTTP; "código" significa que a rota ou estado existe no código, mas não foi exercitado em produção.
1. O que temos
| Funcionalidade | Onde roda | Situação |
|---|---|---|
| Landing pública com "Como funciona", "Para quem", "Por que confiar" e link para a demo | Next.js na Vercel (apps/) | Verificado |
Cadastro e login de cidadã e advogado (Bearer em localStorage, leia:auth) | Web (apps/) + serviço (apps/) | Verificado |
| Upload de PDF por advogado (gera link da cliente) ou por cidadã (vai direto para a explicação) | Web (apps/) + serviço (leia/) | Verificado, pipeline de 70 a 108 s |
Pipeline de 16 nós (T1 a T14 + END) no Groq openai/: extração, memória, sínteses, humanização, questões | Serviço Railway (apps/, protocolo_pdf.json) | Verificado, saída não determinística (ver pendências) |
| Jornada da cidadã: boas-vindas, um ponto por vez, "Ouvir" (Web Speech), "Ver trecho original" quando existe | Web (apps/) | Verificado |
| Gaveta "Tenho uma dúvida" com resposta em streaming (SSE) e recusa fora do documento | Web (ChatSheet.tsx) + serviço POST / | Verificado |
| Encaminhar dúvida ao advogado e resposta do advogado no painel | Web (ChatSheet.tsx, TaskDetailView.tsx) + serviço POST /, .../ | Verificado |
Conferência de entendimento: 6 perguntas de múltipla escolha, aprovação por QUIZ_PASS_RATIO | Web (Journey.tsx) + serviço POST / (core/) | Verificado (com gabarito trivial, ver pendências) |
| Comprovante com QR, código SHA-256 e carimbo público OpenTimestamps (.ots) | Web (Receipt.tsx) + serviço (leia/) | Verificado, .ots de 237 bytes baixado |
Verificação pública / com JSON canônico e prova | Web (Verify.tsx) + serviço GET / | Verificado, sha256(canonical) == payloadHash |
| Documento com marcações e inferências por classe (quem é quem, datas, fatos, fundamentos, pedidos) | Web (DocumentView.tsx) + serviço GET / | Verificado |
| Painel do advogado (lista, detalhe, etapas, rodadas, dúvidas) e painel da cidadã ("Meus documentos") | Web (Panel.tsx, TaskDetailView.tsx) + serviço GET / | Verificado |
| Vínculo automático da cidadã logada à tarefa ao abrir o link | Web (Journey.tsx:37-41) + serviço POST / | Verificado |
| PWA: manifest, service worker, banner "Nova versão do LeIA disponível." | Web (public/, UpdatePrompt.tsx) | Verificado |
| Painel interno legado do serviço (login por cookie, dashboard, chat de bastidores, resumo estruturado) | Serviço Railway (main.py, app_gestao.py, templates/) | Parcial: rotas respondem, fluxos não exercitados |
Página HTML própria do serviço / e PDF assinado | Serviço Railway (templates/, core/) | Responde, mas expõe gabarito (ver pendências) |
| Rate limit por IP (30/min) nas rotas públicas e de conta | Serviço (leia/) | Verificado (429 com Retry-After) |
Testes do serviço (tests_leia.py, tests_v3.py) | Local, venv | 18 passed em 3,5 s |
2. Telas
Todas as telas foram verificadas a 390 px e 1280 px sem overflow horizontal, lang="pt-BR", sem
exceção JS não tratada (/tmp/leia-review/events-*.json). Data de verificação: 13/09/2026.
| Rota | Quem usa | O que faz | Estados | Verificado em produção (13/09/2026) |
|---|---|---|---|---|
/ | Público | Apresenta o produto; links "Ver um exemplo" (/), /, /; rodapé com licença e versão | Estático; variante logado com "Meu painel" e "Sair" | Sim: 01-landing-390/, 17-landing-logado-390.png; rodapé "LeIA v0.4.0 · 589da62" |
/ | Cidadã e advogado | Abas "Entrar" e "Criar conta" (papel, OAB opcional); ?modo=cadastro; ?next= filtrado por safeNext | Campos vazios, 401, 409, já logado, carregando; 403 e erro genérico só no código | Sim: 02-entrar-*.png, 20-entrar-erro-409-390.png, 17-entrar-ja-logado-390.png |
/ | Advogado e cidadã | Nome + PDF, POST /; advogado vê link da cliente, cidadã vai para / | Anônimo redireciona para /; sem arquivo; escolhido; enviado; erro genérico | Sim: 03-enviar-anon-redirect-390.png, 12-enviar-*.png, 21-enviar-cidada-390.png, 16-enviar-invalido-resultado-390.png; redirecionamento da cidadã visto no fluxo F1.2 (flows/) |
/ (advogado) | Advogado | Lista GET / com chip de status, rodada, dúvidas abertas, "Copiar link da cliente", "Ver detalhes"; repolling 4 s | Preparando, Pronto para a cliente, Entendimento registrado; loading; erro genérico; vazio não observado | Sim: 10-painel-advogado-*.png, 14-painel-com-preparando-390.png, 31-*, 33-*; flows/ |
/ (cidadã) | Cidadã | "Meus documentos": Continuar, Ver comprovante (só se última tentativa aprovada), Ver detalhes | Vazio; com documento aprovado; após rodada reprovada perde "Ver comprovante" | Sim: 20-painel-cidada-vazio-*.png, 29-painel-cidada-*.png, 36-painel-cidada-apos-rodada2-390.png |
/ (advogado) | Advogado | GET /: link da cliente, etapas, respostas por rodada, dúvidas com textarea "Responder" | Preparando, pronto, com dúvida, respondida, duas rodadas, 404, anônimo redireciona; "falhou" não observado | Sim: 11-*, 14-painel-detalhe-preparando-390.png, 15-*, 32-*, 33-*, 37-*, 17-painel-detalhe-404-390.png |
/ (cidadã) | Cidadã | Mesma rota sem link da cliente nem textarea; mostra resposta do advogado | Aprovado com dúvida aguardando; com resposta; duas rodadas | Sim: 30-*, 34-*, 36-painel-detalhe-cidada-rodada2-390.png; flows/ |
/ (carregando e erro) | Cidadã | GET /; "Carregando sua explicação." | Qualquer erro, inclusive 404, mostra "Estamos tentando de novo." e refaz a cada 4 s | Sim: 09-t-naoexiste-390.png (loop de 404) |
/ (espera) | Cidadã | "Estamos preparando a explicação do seu documento", "Etapa atual: {tipo bruto}", repolling 8 s | Único estado | Sim: 13-t-espera-*.png, flows/; troca sozinha para boas-vindas ao terminar |
/ (falhou) | Cidadã | "Não deu certo desta vez" | Único estado | Não: nenhuma tarefa chegou a falhou (PDF inválido é recusado com 400 no upload) |
/ (boas-vindas) | Cidadã | Título com nome do documento, quem está falando, "Ouvir", "Você não assina nada aqui.", "Começar a explicação"; cidadã logada é vinculada | Anônimo; logada e vinculada; reabrir após aprovação vai direto ao resultado | Sim: 04-t-welcome-*.png, 22-t-welcome-cidada-390.png, flows/ |
/ (ponto n de N) | Cidadã | Explicação em parágrafos, "Ouvir explicação", "Ver trecho original" (só quando há trecho), "Entendi, próximo", "Ver o documento com as marcações", "Tenho uma dúvida" | Ponto 1, intermediário com trecho, último | Sim: 05-t-topic*.png, 23-t-topic1-cidada-390.png, flows/; trecho em 1 de 6 pontos (demo) e 0 ou 1 de 6 nas tarefas novas |
/ (gaveta de dúvida) | Cidadã | Dialog inferior com 3 exemplos, input, resposta em streaming, "Enviar esta dúvida para o advogado" quando tem_advogado | Aberta; respondida; fora do documento; enviada ao advogado; fechada por Esc; falha de envio só no código | Sim: 06-t-duvida-*.png, 24-t-duvida-*.png, flows/, f2-12-citizen-duvida-enviada.png |
/ (conferindo k de K) | Cidadã | Enunciado, alternativas A a D, "Ouvir a pergunta", "Próxima pergunta", "Enviar minhas respostas"; respostas em localStorage leia:{hash} | Sem escolha; escolhida; última; erro de envio só no código | Sim: 07-t-question*.png, flows/; envio real só nas tarefas criadas na revisão |
/ (resultado aprovado) | Cidadã | Chip "Entendimento registrado", "O que você viu", "Ver meu comprovante", "Rever a explicação" | Único estado | Sim: 26-t-resultado-aprovado-*.png, flows/ |
/ (resultado "Vamos ver de novo") | Cidadã | Chip, lista dos enunciados errados sem nota, "Ler a explicação de novo" | Único estado | Sim: 35-t-resultado-ver-de-novo-*.png (rodada 2, 0 de 6) |
/ | Cidadã e advogado | GET /: chips conferidas/não localizadas, sínteses com lastro, texto com marcas, cartões por classe | Loading; 404; 409 "ainda sendo preparada" só no código | Sim: 08-t-documento-*.png, 09-t-naoexiste-documento-390.png, flows/, f2-23-lawyer-documento.png |
/ | Cidadã | GET {API}/ a cada 30 s: chip, data, tentativa, QR, código, "Salvar comprovante", "Conferir o registro" | Registrado; 404; "Carimbo pendente", texto de demo e impressão não verificados | Sim: 27-comprovante-*.png, 09-comprovante-naoexiste-390.png, flows/ |
/ | Público, auditor | Chip "registrado em", código SHA-256, "Baixar prova (.ots)", JSON canônico, "Como conferir por conta própria" | Registrado; 404; "carimbo pendente" não verificado | Sim: 28-verify-*.png, 09-verify-naoexiste-390.png, flows/; .ots baixado por curl (237 bytes) |
| Rota inexistente | Público | 404 padrão do Next, em inglês, sem layout | Único estado | Sim: 09-rota-inexistente-390.png |
| Banner "Nova versão do LeIA disponível." | Todos | Registra /, checa a cada 60 s, mostra versão e "Atualizar" / "Agora não" | Aparece quando há SW em espera | Sim, durante deploy no meio da revisão: 09-rota-inexistente-390.png, 08-t-documento-390.png, flows/ |
3. API do serviço
Base: https://llm-service-production-4278.up.railway.app. Rotas verificadas com curl em 13/09/2026.
"Bearer" é o token de POST /api/auth/login; "cookie" é a sessão sessao do painel interno.
Rotas do painel interno aceitam Bearer também (core/auth.py).
3.1 Rotas consumidas pelo app (v3)
| Rota | Auth | Quem chama | Situação |
|---|---|---|---|
POST / | Nenhuma, rate limit | apps/ | Verificado: 200, 403 para papel fornecedor, 409 duplicado, 422 sem @ |
POST / | Nenhuma, rate limit | auth.ts:47 | Verificado: 200 {token, usuario}, 401 JSON, 429 após o 30.o pedido limitado por IP. Rotaciona o token anterior |
GET / | Bearer | auth.ts:64 (nunca chamado por tela) | Verificado: 200, 401 sem token, 401 após logout |
POST / | Bearer | auth.ts:57 | Verificado: {ok:true} |
GET / | Bearer | lib/ | Verificado: chaves iguais a docs/; link_cliente aponta para a Vercel |
POST / | Bearer, rate limit | api.ts:128-134 | Verificado: 201 em ~1 s, 400 "Este arquivo não é um PDF." para PDF inválido |
GET / | Bearer | api.ts:135-138 | Verificado: 200, 404 desconhecido, 403 sem permissão |
POST / | Bearer | api.ts:139-142 | Verificado no fluxo F2.8 (duvida_respondida por=2) |
GET / | Nenhuma | api.ts:37-40 | Verificado: 200 sem correta/justificativa; 404 Link inválido ou expirado; devolve últimos 8 eventos brutos (ver pendências) |
POST / | Nenhuma, rate limit | api.ts:42-47 | Verificado nas tarefas da revisão (6/6 e 0/6); 404 hash inválido; devolve correta das erradas (ver pendências) |
POST / | Nenhuma, rate limit | api.ts:50-74 | Verificado: SSE, TTFB 0,34 s, total 1,27 s; recusa fora do documento |
POST / | Nenhuma, rate limit | api.ts:143-146 | Verificado: 200, 409 sem advogado, 429 com balde esgotado |
POST / | Bearer (cidadã) | api.ts:147-150 | Verificado: 200, 403 para advogado, 409 vinculada a outra conta |
GET / | Nenhuma | api.ts:160-163 | Verificado: 200 (demo 18/18, novas 2/2 e 22/24), 404; 409 antes de pronta só no código |
GET / | Nenhuma | api.ts:76-80 | Verificado: 200 com payload, canonical, payloadHash, otsPresent; 404 |
GET / | Nenhuma | api.ts:82 | Verificado: 200, 237 bytes |
GET / | Nenhuma | Página do serviço e links | Verificado só o 404 |
3.2 Rotas do painel interno e da página própria do serviço
| Rota | Auth | Quem chama | Situação |
|---|---|---|---|
GET /, POST /, POST / | Nenhuma / cookie | templates/, dashboard.html; healthcheck do Railway em / | Verificado: 200, 303 com cookie HttpOnly; SameSite=lax; Secure; POST / sem rate limit |
GET /, GET / | Cookie ou Bearer | Painel interno | Verificado: 303 sem credencial, 200 com Bearer |
GET/ | Cookie ou Bearer | index.html:2420 | GET verificado; POST sobrescreve protocolo.json no diretório do código (código) |
GET / | Cookie ou Bearer | Ninguém | Código |
GET /, POST / | Cookie ou Bearer | index.html:2291, 2299 | GET verificado; histórico global compartilhado por todos os usuários |
POST / | Cookie ou Bearer | index.html:2112 | Código (Groq) |
GET / | Nenhuma | Templates | Verificado |
GET /, /, / | Nenhuma | Ninguém | Verificado: 404 (DOCS_ENABLED desligado) |
POST /, GET /, GET / | Cookie ou Bearer | index.html:1806, 1828, 1816 | Código |
GET /, POST / | Cookie ou Bearer | index.html:2319, 2354 | GET verificado |
GET/ | Cookie ou Bearer | tarefa_nova.html:61 | Código; sem rate limit |
GET / | Cookie ou Bearer | Dashboard | Código; HTML inclui gabarito para o remetente |
POST /, POST / | Cookie ou Bearer | dashboard.html:293, tarefa_detalhe.html:152, 157, 208 | Código |
GET / | Cookie ou Bearer | Ninguém | Código; meta.json expõe id, e-mail e nome do remetente |
GET / | Cookie ou Bearer | Ninguém | Verificado: 303 sem cookie, 200 com Bearer |
GET / (HTML do serviço) | Nenhuma | cliente_view.html | Verificado: 200 com 6 data-correta/justificativa no DOM; grava cliente_abriu {ip, ua} a cada abertura |
GET /, GET / | Nenhuma / cookie ou Bearer | cliente_view.html:303, tarefa_detalhe.html:163 | Verificado 403 sem tentativa aprovada; PDF inclui IP e user agent (core/) |
Comportamentos transversais verificados: CORS libera https://leia-snowy.vercel.app e
http://localhost:3000 (preflight de https://evil.example recebe 400 sem allow-origin); rate
limit de 30 req/min por IP num único balde compartilhado por quiz, chat, dúvida, cadastro, login e
POST /api/tarefas; X-Forwarded-For falso não contorna o limite.
4. Infraestrutura
| Item | Valor |
|---|---|
| App (Next.js 15, PWA) | https://leia-snowy.vercel.app, projeto Vercel no scope pessoal citado em apps/; deploy com vercel deploy --prod (README:46) |
Serviço (FastAPI, uvicorn --proxy-headers) | https://llm-service-production-4278.up.railway.app, projeto Railway llm-service; railway.toml (healthcheck /), Dockerfile (DATA_DIR=/) |
| Repositório | git remote origin = github.com/deegalabs/leia (responde 404: privado); tag única token-economy/; HEAD e9b7eb2 |
| Modelo | Groq openai/ (apps/); PIPELINE_TEMPERATURE padrão 0.0 no código, valor efetivo no Railway não lido |
| Carimbo público | OpenTimestamps (OTS_ENABLED), prova em /; nenhuma ancoragem em blockchain (nenhum ANCHOR_*/AMOY em apps/) |
| Banco | SQLite (DB_PATH ou DATABASE_URL) em DATA_DIR; tabelas de usuários (um session_token por usuário), tarefas, tentativas, dúvidas (core/) |
| Arquivos por tarefa | WORKSPACE_DIR/: meta.json, log.jsonl, T*.json, questoes.json; sessões do painel em workspace/; contexto global em contexto_persistente.json |
| Volume | DATA_DIR=/ no Dockerfile:7; existência e montagem do volume Railway não verificadas nesta revisão (variáveis do Railway não foram lidas de propósito) |
Variáveis do serviço (nomes, todas em apps/) | GROQ_API_KEY, GROQ_MODEL, ADMIN_EMAIL, ADMIN_PASSWORD, ADMIN_NAME, ADVOGADO_SIGNUP, DATA_DIR, DB_PATH, WORKSPACE_DIR, DATABASE_URL, PIPELINE_CONCURRENCY, PIPELINE_TEMPERATURE, PIPELINE_MAX_TOKENS, PDF_PROTOCOL_FILE, PERSISTENT_CONTEXT_FILE, RATE_LIMIT_PER_MINUTE, RATE_LIMIT_PROXY_HOPS, CORS_ORIGINS, CLIENT_APP_URL, BASE_URL, OTS_ENABLED, SESSION_COOKIE_SECURE, LOG_LEVEL, QUIZ_PASS_RATIO, ATTEMPT_HASH_PREFIX, CITIZEN_CHAT_TEMPERATURE, CITIZEN_CHAT_MAX_TOKENS, CITIZEN_CHAT_MEMORY_CHARS, RESUMO_ESTRUTURADO_API_BASE, JURISPRUDENCIA_API_BASE, EXTERNAL_*, BRAND_NAME, DOCS_ENABLED, MAX_UPLOAD_MB, PORT (só no comando uvicorn); API_KEY legado só em comentário; LEIA_FIXTURE só no mock |
| Estado observado em produção | CLIENT_APP_URL aponta para a Vercel; CORS_ORIGINS inclui Vercel e localhost:3000; DOCS_ENABLED desligado; SESSION_COOKIE_SECURE ligado; RATE_LIMIT_PER_MINUTE coerente com 30; OTS_ENABLED ligado |
| Variáveis do app | SERVICE_URL (Railway, sem NEXT_PUBLIC_), NEXT_PUBLIC_DEMO_HASH (0SVal1OCE4IHpiRHo-rsNA), NODE_ENV; GIT_COMMIT_SHA em build (fallback dev, next.config.ts:7) |
| Mock interno do app | apps/ e apps/; na Vercel / responde 404 porque o app aponta para o Railway |
| Demo intacta | / (tarefa 1, "Contrato de honorários advocatícios (exemplo)", status pronta, 1 dúvida aberta não respondida, sem tentativa) |
| Tarefas criadas na revisão | ids 2, 3 e 4 (AUHYNFsnGOhI0QziN1NZEw, 5e5xqKOQNM2GBGw9bx86iw, illjOdH9CowLew-2_PZKJQ), contas de teste @example.com e @leia.local |
5. Pontas soltas confirmadas
Ordem: alta, media, baixa. "Onde" cita arquivo:linha no repositório ou URL. Itens duplicados entre as frentes de verificação (telas, API, fluxos, documentação) foram unidos.
| Sev. | Título | Onde | Como corrigir |
|---|---|---|---|
| alta | Pipeline inventa fatos sobre o contrato e o quiz certifica a invenção (D1) | GET /: tópicos "Carlos", "Ana", "A Justiça já decidiu... Carlos ainda recorre", síntese "fase de recurso (apelação)", "Art. 85 CPC"; texto do PDF só cita CONTRATANTE/CONTRATADA. Causa: apps/ (T13 "história para uma criança"), app_gestao.py:986-988 (chat trata todo documento como "o processo dele"), leia/ (trecho só com score >= 3) | Reescrever T13/T14 para explicar só o que está no texto, sem personagens nem fase processual; exigir trecho_verbatim por tópico e descartar tópico sem âncora; trocar o enquadramento do system prompt do chat por "este documento"; validar com o PDF de exemplo antes do pitch |
| alta | Gabarito é sempre a alternativa A | protocolo_pdf.json:173 (T14 pede "a correta é LITERALMENTE o que a história contou"), core/ (salva como veio), core/ (compara índice fixo); tarefas 2 e 3: 6/6 com A, 0/6 com B; demo segue o mesmo padrão | Embaralhar alternativas no serviço ao salvar questoes.json (seed derivada do hash) e guardar o índice correto; opcionalmente pedir ao modelo posição variada |
| alta | JSON público expõe IP e user agent de quem abriu o documento | leia/ devolve os 8 últimos eventos de log.jsonl sem filtro; app_gestao.py:890-892 grava cliente_abriu {ip, ua} a cada abertura de / do serviço; o hash da demo está na landing (apps/) | Filtrar eventos por lista de tipos do pipeline (como /, app_gestao.py:324-331) e remover ip/ua; avaliar não gravar IP/UA; mesmo cuidado em GET / e no PDF assinado (core/, core/) |
| alta | Resposta do quiz devolve o gabarito das questões erradas | core/ monta erros[] com correta, justificativa, alternativas; app_gestao.py:959 devolve sem filtro em rota pública | Devolver só {id, area, enunciado, escolhida} como em docs/; a UI já não usa o resto |
| media | Quase nenhum ponto traz o trecho original (sourceQuote) | leia/ (casamento heurístico, score >= 3); apps/; landing promete "Toda explicação mostra o trecho literal" (page.tsx:13); demo 1 de 6, tarefa 3: 0 de 6, tarefa 2: 1 de 6 | Fazer o modelo devolver o trecho literal por tópico e conferir por substring exata no texto extraído; tópico sem trecho não vai para a cidadã; ou ajustar a promessa da landing |
| media | Mesmo PDF gera saídas diferentes e uma rodada quase vazia foi entregue como "pronta" | Tarefa 4 (illjOdH9CowLew-2_PZKJQ): 2 inferências, sínteses vazias, ponto 1 "não há registro de nenhum fato"; demo 18 e tarefa 2 com 24 para o mesmo texto de 1600 chars; core/ marca pronta sem checagem; landing promete "mesmo documento, mesma explicação" (page.tsx:15) | Porta de qualidade antes de pronta (mínimo de fatos, tópicos com trecho, sínteses não vazias), senão falhou com reprocessamento; fixar seed/temperatura e confirmar PIPELINE_TEMPERATURE no Railway; em demo ao vivo usar a tarefa fixa |
| media | Payload canônico com documentSha256 e summarySha256 vazios e salt derivado do próprio hash | leia/ nunca passa pdf_sha256/resumo_sha256; leia/ usa ""; salt = hash_imutavel[:40] (api_citizen.py:169) publicado em / (registry.py:163-165); contraria docs/ | Calcular SHA-256 do PDF e do resumo exibido, salt aleatório guardado só no comprovante; regenerar payload |
| media | Datas e horas do painel 3 h adiantadas | core/ (utcnow sem fuso), leia/, core/ emitem ISO sem Z; apps/ interpreta como hora local; "16:02" no painel contra "13:02" no comprovante | Emitir timestamps com Z (ou datetime.now(timezone.utc)) no serviço, ou tratar string sem fuso como UTC no formatDateTime |
| media | Nomes internos do pipeline expostos à cidadã e ao advogado | Journey.tsx:93 ("Etapa atual: task start (T1_IDENTIFICADOR_PARTES, 2 de 16)"); TaskDetailView.tsx:22-23 e messages/ só mapeiam tipos do mock; serviço emite task_start, task_done, pipeline_done, tentativa, carimbo_publico, cliente_abriu, duvida_enviada | Mapear os tipos reais e os ids T1..T14 para frases em pt-BR simples; esconder o que não tiver tradução |
| media | Link inválido fica em "Estamos tentando de novo" para sempre | Journey.tsx:54-58 trata 404 como erro genérico com retry a cada 4 s; serviço responde Link inválido ou expirado (api_citizen.py:86-89); Receipt, Verify e DocumentView já distinguem 404 | Tratar 404 como "Este link não existe ou expirou" sem retry; manter retry só para erros de rede/5xx |
| media | Sessão revogada vira erro genérico; segundo login derruba o primeiro aparelho | Um session_token por usuário, rotacionado a cada login (core/, leia/); Panel.tsx:29, TaskDetailView.tsx:34,131 e Session.tsx:27-33 não tratam 401 nem chamam me() | No app: em 401 limpar leia:auth e redirecionar para /; no serviço: permitir vários tokens por usuário ou avisar no login |
| media | Respostas do chat com listas em itálico, travessões e recusa em jargão | app_gestao.py:986-996 (prompt sem regras de formato); apps/ (regex trata * item como itálico); pt-BR.json:82 (c3.notInDocument) não é usado; ChatSheet.tsx:14 tem texto próprio | Instruir "sem markdown, sem travessão, frases curtas" no prompt; renderizar listas; padronizar a recusa com a frase prevista e oferecer o encaminhamento ao advogado |
| media | PDF inválido vira "Deu um problema do nosso lado, não foi você." | Serviço responde 400 "Este arquivo não é um PDF." (app_gestao.py:94-95); lib/ descarta o corpo; Upload.tsx:40 mapeia todo erro para systemError | Propagar detail dos 4xx e mostrar mensagem específica; checar magic %PDF no cliente antes do envio |
| media | Cartão da cidadã perde "Ver comprovante" após rodada reprovada posterior | Serviço aceita nova rodada depois de assinada sem reverter status (app_gestao.py:912-951); listagem expõe só ultima_tentativa (api_tasks.py:90-101); Panel.tsx:81 vs TaskDetailView.tsx:47 | Expor melhor_tentativa ou comprovante_token na listagem; ou bloquear nova rodada após aprovação |
| media | Documento com marcações: cartões vazios, botões de lastro pequenos com nomes internos e sem âncora, página muito longa | DocumentView.tsx:69-84 (classe com 0 itens ainda renderiza), :53 (refs identificacao[0], T7_SINTESE_FATOS sem min-h, refs T7..T10 sem mark-* no DOM), :62 (texto inteiro em mono) | Ocultar classes vazias; rotular lastro com texto humano e só quando houver âncora; alvo de toque 44 px; texto em fonte normal e recolhido por padrão |
| media | Página HTML própria do serviço / embute o gabarito | templates/ (data-correta), :268 (justificativa); rota app_gestao.py:864-906; docs/ afirma o contrário | Remover os atributos e corrigir no servidor, ou desligar a rota HTML legada em produção (o app não a usa) |
| media | Rate limit único por IP para todas as rotas limitadas; POST / limitado sem documentação | leia/; rotas em app_gestao.py:912,968, api_citizen.py:122, api_auth.py:46,64, api_tasks.py:106; apps/ e docs/ desatualizados; sala de auditoria atrás de um NAT esgota o balde junto | Baldes por rota (chat mais folgado, login/cadastro mais restrito); documentar; considerar chave por usuário para rotas Bearer |
| media | POST / (form) sem rate limit | app_gestao.py:149-161 sem Depends(rate_limit); mesmo PBKDF2 de core/; 6 tentativas erradas responderam 303 com o balde esgotado | Adicionar Depends(rate_limit) ou remover o login por formulário |
| media | Rotas sem chamador expostas atrás do cookie/Bearer; artefato/ expõe e-mail do remetente; submit externo sem auth | main.py:586; app_gestao.py:440, 468, 535, 553, 813-838, 843; removidos | Remover ou desligar por flag antes da auditoria; se mantiver artefato, filtrar meta.json; questões ao dono do serviço em docs/ |
| media | Entregas 2, 3 e 4 sem tag, sem CHANGELOG e sem MANIFEST; prazo da Entrega 4 (dom 10h30) já passou | git tag: só token-economy/; CHANGELOG.md:16-29 "A preencher"; nenhum evidence/; README.md:26-30 lista entregas como "planejada"/"em construção"; política em docs/ e docs/ | Rodar scripts/ (árvore está limpa), preencher CHANGELOG, criar MANIFEST.md em evidence/ e atualizar a tabela do README |
| media | evidence/ quase vazia e testes internos sem resultado | evidence/, 02-internal-tests, 03-external-tests, 05-slides, extras com 0 arquivos (pastas vazias nem existem no remoto: README.md:25 e docs/ viram links quebrados); evidence/ só com 20 PNG; docs/ com 0 de 13 casos preenchidos e casos T03, T08-T10 que pressupõem rubrica de resposta aberta inexistente | Subir canvas, prints e planilhas com MANIFEST.md; preencher INTERNAL-TESTS com os resultados desta revisão (as capturas em / servem) e alinhar os casos ao quiz de múltipla escolha |
| media | Prompts que rodam não estão registrados em prompts/ (D3) | prompts/ prevê catálogo inexistente; prompts/ difere do apps/ que roda (ids, tokens, seed, parallel); system prompt do chat inline em app_gestao.py:986-996; prompts/ sem entrada de 13/09 apesar de 31 commits | Copiar protocolo_pdf.json para prompts/ (ou apontar o README para o arquivo real), extrair o prompt do chat para arquivo versionado (CITIZEN_CHAT_PROMPT_FILE já é citado na doc mas não lido), atualizar o build-log |
| media | Documentação e contratos defasados em relação ao produto | docs/ (rota "a acrescentar" que existe, campos explicacao/clausula, SSE sem done, mock que não reproduz v3); docs/ (coluna "pendente" já feita, / exposto, 303 vs 401); docs/, MVP.md, USE-CASES.md, ARCHITECTURE.md, SCALING.md, AUDIT-GUIDE.md, INTEGRATION-PLAN.md descrevem rotas /, /, /, login Google, Polygon Amoy, modelos Claude/GPT que nunca existiram; .env.example da raiz com 8 chaves não lidas; docs/ expõe caminhos locais e repositório privado | Marcar SERVICE-V2-MAP e design como históricos; reescrever LLM-API-CONTRACT a partir de API-V3-CONTRACT.md (este está correto); corrigir AUDIT-GUIDE (nome do PDF, OTS em vez de testnet, 14 tarefas + END); apagar .env.example da raiz; remover caminhos locais de REUSE.md |
| baixa | Página 404 padrão do Next em inglês | apps/ sem not-found.tsx | Criar not-found.tsx com cabeçalho e link para / |
| baixa | Plural fixo "1 dúvidas abertas" | messages/, Panel.tsx:69 (fmt sem plural) | Duas chaves (singular/plural) ou Intl.PluralRules |
| baixa | Emoji nos títulos vindos do serviço; "Ouvir" recebe o título bruto | protocolo_pdf.json:161 exige cabeçalhos com emoji; api_citizen.py:71-79 copia o cabeçalho; Journey.tsx:148 limpa no h1 mas :150 monta o texto do TTS sem cleanTitle | Tirar emoji do prompt e limpar também no serviço; usar cleanTitle no texto falado |
| baixa | Status enviada aceito pelo serviço mas não pelo app | leia/, api_tasks.py:24, app_gestao.py:874 vs apps/; nenhum caminho grava enviada hoje | Remover o valor de READY_STATUSES ou adicionar em status.ts |
| baixa | Identidade de build inconsistente e banner cobre a barra inferior | vercel deploy --prod sem --build-env GIT_COMMIT_SHA cai no fallback dev (next.config.ts:7, scripts/); link do rodapé aponta para github.com/deegalabs/leia/commit/589da62 (404, repositório privado) e para o pai do commit publicado; a 390 px o toast (UpdatePrompt.tsx:36, bottom-3 z-50) cobre "Conferir o registro" no comprovante | Passar GIT_COMMIT_SHA no deploy (documentar no README); esconder o link do commit enquanto o repositório for privado; posicionar o toast acima da BottomActionBar |
| baixa | .env.example do serviço com padrões inseguros e duas lacunas | ADMIN_PASSWORD=trocar123, SESSION_COOKIE_SECURE=false, ADVOGADO_SIGNUP=true (fallback "true" em api_auth.py:43: qualquer pessoa cria conta de advogado e consome cota Groq); API_KEY só em comentário; LEIA_FIXTURE ausente | Padrões seguros no exemplo; decidir ADVOGADO_SIGNUP=false em produção ou exigir OAB validada |
| baixa | Arquivos indevidos versionados | apps/ (hunk já aplicado), contexto_persistente.json (estado de runtime, []), core/ (0 bytes, removido), protocolo_jurisprudencia.json (sem referência); entram na imagem via COPY . . | git rm, acrescentar ao .gitignore e .dockerignore |
| baixa | Estados existentes no código e não verificados em produção | Journey.tsx:82-85 (falhou); Receipt.tsx:28,36 e Verify.tsx:31,39 (carimbo pendente; ramo "demo" é inalcançável com o serviço real); ChatSheet.tsx:74 (falha ao encaminhar dúvida); AuthForm.tsx:36 (403); Upload.tsx:34 (não PDF por extensão); Receipt.tsx:44 (impressão); download do registro.json | Exercitar num ambiente de teste com OTS_ENABLED=false e um PDF que faça o pipeline falhar; remover o ramo "demo" morto |
6. Descartadas na verificação
- Botão flutuante "Tenho uma dúvida" cobre "Rever a explicação" no celular: pela geometria de
Journey.tsx:101eui.tsx:9-10,30o FAB (96 a 148 px do rodapé) sobrepõe a borda do botão primário, não do secundário; não reproduzido visualmente. Vale conferir a sobreposição com "Próxima pergunta". - Primeiro cadastro de cidadã falhou com erro genérico (15:49:47Z): não reproduzido em 6 tentativas; causa provável é
AuthForm.tsx:36mostrar a mensagem genérica para 422 (senha curta), 429 ou falha de rede; logs do Railway inacessíveis (MCP sem login). - "Copiar link da cliente" não confirmou "Copiado": limitação do Chrome headless sem permissão de clipboard; com permissão,
ui.tsx:73-79copia o link e troca o rótulo por 2 s. - Suíte de testes não rodou no python do sistema: rodou numa venv de scratch, 18 passed (77 avisos de
utcnowdepreciado); limitação de ambiente, não defeito. - Árvore do repositório mudou durante a revisão (commit
e9b7eb2trocouPwaRegister.tsxporUpdatePrompt.tsx): observação de processo; árvore atual limpa e consistente.
7. Como testar em 5 minutos
- Abrir https://leia-snowy.vercel.app no celular ou a 390 px. Clicar em "Ver um exemplo": é a demo
fixa
/t/0SVal1OCE4IHpiRHo-rsNA. Não enviar o quiz nem encaminhar dúvida nela se quiser mantê-la intacta para o pitch. - Na demo: "Começar a explicação", passar pelos 6 pontos (o ponto 3 tem "Ver trecho original"), abrir "Tenho uma dúvida" e perguntar "Quanto eu pago se perder?" (resposta com valores do contrato) e depois "Qual é a capital da França?" (recusa).
- Criar uma conta de advogado em
/entrar?modo=cadastro, ir em/enviare subirexamples/contrato-honorarios-exemplo.pdf. Esperar 1 a 2 minutos na tela de espera (atualiza sozinha). Copiar o link da cliente. - Em outra janela anônima, criar uma conta de cidadã, abrir o link (a tarefa fica vinculada), ler os
pontos, encaminhar uma dúvida ao advogado, responder as 6 perguntas (hoje a alternativa A acerta
todas) e abrir "Ver meu comprovante" e "Conferir o registro". Baixar a prova
.ots. - Voltar à janela do advogado: em
/painelo cartão mostra "Rodada 1: 6 de 6" e "1 dúvidas abertas"; em "Ver detalhes" responder a dúvida. Na janela da cidadã,/painel> "Ver detalhes" mostra a resposta. - Conferir por conta própria:
curl -s "$API/verify/<attempt>?format=json" | python3 -c "import json,sys,hashlib; d=json.load(sys.stdin); print(hashlib.sha256(d['canonical'].encode()).hexdigest()==d['payloadHash'])"comAPI=https://llm-service-production-4278.up.railway.app. - Testes do serviço:
cd apps/llm-service && python -m venv .venv && . .venv/bin/activate && pip install -r requirements.txt pytest && python -m pytest -q tests_leia.py tests_v3.py(18 passed).
Atenção ao rate limit: 30 requisições por minuto por IP num único balde; várias pessoas atrás do mesmo Wi-Fi contam juntas.
Fonte: docs/STATUS.md (abre no GitHub)