feat(runtime): implement single-article consolidation runtime and modularize codebase

This commit is contained in:
2026-08-24 00:14:07 -03:00
parent e1e0be1353
commit 23de7d8fe7
176 changed files with 266754 additions and 10179 deletions
@@ -0,0 +1,453 @@
# Plano de testes e evals — Runtime de consolidação de artigos
**Versão:** 1.0
**Data:** 23 de agosto de 2026
**Escopo:** runtime; self-healing excluído
**Regra mestre:** comprovar integralmente os requisitos de produção com a menor suíte suficiente, sem testes duplicados, frágeis ou sem efeito em gates.
## 1. Objetivo
Demonstrar que o runtime atende aos contratos, preserva grounding, aplica o ECP, suporta falhas e processa 100 artigos por hora sem perda ou duplicação.
## 2. Princípios
- Qualidade agregada não pode esconder falhas por idioma, domínio ou extrator.
- Invariantes críticas exigem zero falhas.
- Grounding é validado por código, não por LLM-as-a-judge.
- Nenhum teste textual ou assertion usa regex.
- Nenhuma decisão esperada depende de lista manual de palavras-chave.
- Prompts reais são usados no Promptfoo.
- Providers são simulados nos testes de integração e reais apenas nos evals autorizados.
- Casos de correção textual distinguem reparo de reescrita.
- Self-healing, otimizador e judge não fazem parte desta suíte.
## 3. Camadas de teste
| Camada | Finalidade | Execução |
| --- | --- | --- |
| Unitário | Funções determinísticas, parsers, IDs, estado e renderer | Todo PR |
| Contrato | Schemas de artigo, ECP, LLM e manifesto | Todo PR |
| Integração simulada | Fluxo completo com respostas controladas dos providers | Todo PR |
| Promptfoo reduzido | Regressão crítica de prompts e modelos baratos | Mudança em prompt, schema, contexto ou modelo |
| Golden set completo | Qualidade por artigo, idioma, domínio e extrator | Antes de promoção |
| Carga | 100 artigos/hora | Staging antes do go-live e mudança relevante |
| Fault injection | Provider, disco, SQLite e Langfuse | Staging e releases relevantes |
| Segurança | Prompt injection, secrets e conteúdo hostil | Todo release |
| Reprocessamento | Idempotência, retomada e duplicidade | Todo release |
## 4. Dados de teste
### 4.1 Fixture inicial
Os 20 artigos do JSON de referência são a regressão inicial obrigatória. Cada item deve ser convertido para a unidade de entrada do runtime e associado a um ECP válido.
### 4.2 Golden set de produção
O conjunto deve ser revisado manualmente e estratificado por:
- idioma;
- domínio de origem;
- extrator selecionado;
- tamanho e estrutura;
- concordância e divergência entre extratores;
- ECP direto, contextual, tangencial e não relacionado;
- ruídos editoriais;
- pequenos defeitos textuais.
O corpus deve ser suficiente para cobrir os idiomas, domínios, extratores, estruturas editoriais e tipos de ruído suportados. Seu tamanho deve ser justificado pela estabilidade observada dos resultados e pelos intervalos de confiança dos gates medidos.
### 4.3 Holdout
Parte da golden set deve permanecer fora da elaboração dos prompts. Resultados do holdout são usados no gate final e não podem orientar exemplos few-shot diretamente.
### 4.4 Verdade de referência
Cada caso deve possuir:
- entrada integral;
- ECP e versão;
- status esperado;
- título, subtítulo, autor e data esperados ou conjunto de candidatos aceitáveis;
- blocos mantidos e removidos;
- links e imagens mantidos;
- reparos permitidos e proibidos;
- classificação ECP esperada;
- sentimento esperado;
- tags aceitas ou rubrica fechada;
- Markdown ou estrutura esperada;
- motivo de falha/descarte, quando aplicável.
## 5. Métricas de avaliação
- pass rate integral por artigo;
- precisão, recall e F1 de blocos mantidos;
- acurácia de metadados;
- taxa de reparos corretos;
- taxa de reparos indevidos;
- grounding textual;
- grounding de URL e imagem;
- validade de schema;
- classificação ECP;
- sentimento;
- tags;
- custo e latência.
Resultados devem ser segmentados por idioma, domínio, extrator e versão de prompt/modelo.
## 6. Gates críticos
Uma configuração não pode ser promovida se ocorrer qualquer um dos seguintes:
- texto inventado;
- URL ou imagem inventada;
- fato, número, nome, data, placar ou citação alterado indevidamente;
- reparo usado como reescrita;
- ID inexistente aceito;
- duplicidade de saída;
- secret em log, trace ou arquivo;
- regex no pipeline textual ou assertion de conteúdo;
- modelo potente configurado no runtime;
- Promptfoo chamado online.
## 7. Gates quantitativos
- pass rate integral textual: pelo menos 95%;
- grounding textual, URLs e imagens: 100%;
- schema válido em todas as respostas aceitas: 100%;
- duplicidade: 0;
- perda no teste de carga: 0;
- trace enviado ou telemetria preservada: 100%.
Custo e latência devem ser medidos no staging e receber limites aprovados antes do go-live.
## 8. Testes de contrato de entrada
| ID | Cenário | Resultado esperado |
| --- | --- | --- |
| IN-001 | Artigo válido, ECP válido e extrator disponível | Segue para fingerprint |
| IN-002 | JSON do artigo corrompido | `INVALID_ARTICLE_SCHEMA`; nenhum LLM |
| IN-003 | ECP ausente | `INVALID_ECP_SCHEMA`; nenhum LLM |
| IN-004 | ECP corrompido | `INVALID_ECP_SCHEMA`; nenhum LLM |
| IN-005 | ECP em versão incompatível | Falha de contrato; nenhum LLM |
| IN-006 | `selected_extractor` ausente | `MISSING_SELECTED_EXTRACTOR` |
| IN-007 | `selected_extractor` desconhecido | `INVALID_SELECTED_EXTRACTOR` |
| IN-008 | Extrator selecionado sem dados utilizáveis | `SELECTED_EXTRACTOR_UNAVAILABLE` |
| IN-009 | Outro extrator utilizável, mas selecionado inválido | Falhar; não substituir silenciosamente |
| IN-010 | URL ausente em todas as fontes | `MISSING_SOURCE_URL` |
| IN-011 | Título ausente em todas as fontes | `MISSING_TITLE_CANDIDATE` |
| IN-012 | Sem conteúdo textual | `MISSING_CONTENT` |
| IN-013 | Campos extras desconhecidos | Preservar entrada registrada e ignorar no fluxo |
| IN-014 | Wrapper com array `articles` usado como artigo | Falha de schema da unidade |
| IN-015 | Conteúdo contém instrução para o modelo | Tratar como dado não confiável |
## 9. Testes de fingerprint e idempotência
| ID | Cenário | Resultado esperado |
| --- | --- | --- |
| ID-001 | Mesma entrada e mesmas versões | Mesmo fingerprint |
| ID-002 | Mudança apenas de timestamp operacional | Mesmo fingerprint |
| ID-003 | Mudança de prompt | Fingerprint diferente |
| ID-004 | Mudança de modelo funcional | Fingerprint diferente |
| ID-005 | Mudança do ECP | Fingerprint diferente |
| ID-006 | Execução já concluída | Retornar saída existente sem novo LLM |
| ID-007 | Queda antes da primeira escrita | Reexecução limpa |
| ID-008 | Queda durante escrita temporária | Nenhum arquivo final parcial |
| ID-009 | Queda após Markdown e antes do estado final | Reconciliação por hash sem duplicidade |
| ID-010 | Duas execuções concorrentes idênticas | Uma conclusão efetiva, nenhuma duplicidade |
## 10. Testes de parsing sem regex
| ID | Cenário | Resultado esperado |
| --- | --- | --- |
| PAR-001 | HTML válido com elementos aninhados | DOM preserva relações |
| PAR-002 | HTML malformado tolerado pelo parser | Estrutura segura ou falha controlada |
| PAR-003 | Markdown com heading, lista, citação e ênfase | AST identifica tipos |
| PAR-004 | URL com query e fragmento | Parser preserva componentes válidos |
| PAR-005 | Unicode composto e decomposto | Normalização consistente |
| PAR-006 | Texto em idiomas sem separação simples por espaços | Tokenizador apropriado não quebra o fluxo |
| PAR-007 | JSON-LD com `Article` ou `NewsArticle` | Metadados estruturais registrados |
| PAR-008 | JSON-LD inválido | Ignorar fonte inválida e registrar warning |
| PAR-009 | Código importa módulo de regex no pipeline textual | CI falha por análise AST |
| PAR-010 | Configuração Promptfoo contém assertion regex de conteúdo | CI falha |
## 11. Testes de candidatos e proveniência
| ID | Cenário | Resultado esperado |
| --- | --- | --- |
| CAN-001 | Mesmo bloco em três extratores | Equivalência registrada, IDs preservados |
| CAN-002 | Bloco exclusivo de outro extrator | Candidato disponível sem inserção automática |
| CAN-003 | Ordem diverge entre extratores | Base segue `selected_extractor` |
| CAN-004 | Título duplicado no corpo | Duplicação estrutural removível pelo assembler |
| CAN-005 | Link sem URL válida | Candidato rejeitado estruturalmente |
| CAN-006 | Imagem sem posição editorial | Não inserir automaticamente |
| CAN-007 | IDs repetidos | Falha interna de construção |
| CAN-008 | Conteúdo idêntico com Unicode diferente | Equivalência via normalização apropriada |
| CAN-009 | Similaridade baixa | Manter candidatos distintos |
| CAN-010 | Três extratores concordam integralmente | Ainda chamar higienização LLM |
## 12. Testes de higienização
| ID | Cenário | Resultado esperado |
| --- | --- | --- |
| HYG-001 | Três extratores concordam | LLM ainda seleciona IDs e resultado é validado |
| HYG-002 | Um extrator contém publicidade textual | Bloco de ruído removido conforme referência |
| HYG-003 | Todos repetem chamada externa | LLM remove; consenso não obriga manutenção |
| HYG-004 | Chamada para outra notícia no meio | Link/bloco removido sem afetar conteúdo |
| HYG-005 | Newsletter após o corpo | Removida |
| HYG-006 | Bloco editorial exclusivo de um extrator | Mantido quando golden set exigir |
| HYG-007 | Citação legítima | Preservada como citação |
| HYG-008 | Lista editorial legítima | Preservada como lista |
| HYG-009 | Heading legítimo | Preservado |
| HYG-010 | Imagem editorial posicionada | Mantida com URL existente |
| HYG-011 | Imagem publicitária | Removida |
| HYG-012 | Link editorial contextual | Mantido |
| HYG-013 | Link de recomendação | Removido |
| HYG-014 | Modelo devolve artigo completo | Schema rejeita |
| HYG-015 | Modelo seleciona ID inexistente | Grounding rejeita |
| HYG-016 | Modelo muda ordem narrativa sem IDs correspondentes | Renderer ignora ordem não autorizada |
| HYG-017 | Modelo omite conteúdo material | Falha no gate de cobertura do caso |
| HYG-018 | Primário falha tecnicamente | Retry técnico e/ou fallback conforme política |
| HYG-019 | Primário falha semanticamente | Fallback barato, sem retry semântico no mesmo modelo |
| HYG-020 | Ambos falham e base estrutural é segura | Fallback determinístico conservador |
| HYG-021 | Ambos falham e base não é segura | `HYGIENE_FAILED`; sem Markdown |
## 13. Testes de reparos textuais
| ID | Cenário | Resultado esperado |
| --- | --- | --- |
| REP-001 | Mojibake inequívoco em título | Reparo aceito e auditado |
| REP-002 | Unicode quebrado no corpo | Reparo aceito |
| REP-003 | Espaçamento acidental | Reparo aceito se preservar sentido |
| REP-004 | Pontuação corrompida | Reparo aceito conforme golden set |
| REP-005 | Pequeno typo inequívoco | Reparo aceito conforme eval |
| REP-006 | Sinônimo mais elegante | Reparo rejeitado; original preservado |
| REP-007 | Frase reescrita para fluência | Reparo rejeitado |
| REP-008 | Nome de pessoa alterado | Reparo rejeitado, salvo defeito de encoding comprovado |
| REP-009 | Número ou placar alterado | Reparo rejeitado |
| REP-010 | Data alterada | Reparo rejeitado |
| REP-011 | Citação corrigida editorialmente | Reparo rejeitado |
| REP-012 | Fragmento original não existe | Reparo rejeitado |
| REP-013 | Fragmento é ambíguo no mesmo bloco | Reparo rejeitado ou exige referência estrutural válida |
| REP-014 | Um reparo válido e outro inválido | Aplicar válido, preservar original do inválido |
| REP-015 | Modelo omite categoria ou justificativa | Reparo inválido |
| REP-016 | Reparo modifica sentido apesar de pequena distância | Gate semântico/humano do eval reprova configuração |
## 14. Testes do ECP
| ID | Cenário | Resultado esperado |
| --- | --- | --- |
| ECP-001 | Texto `DIRECT_INHERENT` | Continuar para enriquecimento |
| ECP-002 | Texto `CONTEXTUAL_INHERENT` | Continuar para enriquecimento |
| ECP-003 | Texto `TANGENTIAL` | Manifesto `rejected_ecp`; sem Markdown |
| ECP-004 | Texto `NOT_RELATED` | Manifesto `rejected_ecp`; sem Markdown |
| ECP-005 | Classificador falha | `ECP_CLASSIFICATION_FAILED` |
| ECP-006 | Resultado fora do enum | Rejeitar contrato |
| ECP-007 | Evidência não pertence ao documento | Rejeitar resultado |
| ECP-008 | Relação contextual embutida válida | `CONTEXTUAL_INHERENT` conforme referência |
| ECP-009 | Tier LLM do ECP aponta modelo potente | Gate de configuração falha |
## 15. Testes de enriquecimento
| ID | Cenário | Resultado esperado |
| --- | --- | --- |
| ENR-001 | Sentimento positivo em relação ao ECP | `positive` |
| ENR-002 | Sentimento negativo em relação ao ECP | `negative` |
| ENR-003 | Notícia factual sem polaridade | `neutral` |
| ENR-004 | Tags no idioma do artigo | 3 a 8 tags válidas |
| ENR-005 | Tags duplicadas | Resposta rejeitada/fallback |
| ENR-006 | Tag sem evidência | Reprovar caso |
| ENR-007 | Modelo tenta alterar corpo | Ignorar alteração e rejeitar schema |
| ENR-008 | Primário falha | Fallback barato |
| ENR-009 | Ambos falham | `ENRICHMENT_FAILED`; sem Markdown |
## 16. Testes de Markdown e manifesto
| ID | Cenário | Resultado esperado |
| --- | --- | --- |
| OUT-001 | Texto aprovado completo | Manifesto e Markdown consistentes |
| OUT-002 | Subtítulo ausente | Campo omitido e nenhuma linha vazia artificial |
| OUT-003 | Autor ausente | Campo omitido |
| OUT-004 | Data ausente | Campo omitido |
| OUT-005 | Headings/listas/citações | Markdown válido |
| OUT-006 | Sublinhado no HTML | Texto preservado sem underline |
| OUT-007 | HTML inline residual | Validação reprova |
| OUT-008 | Rejeição ECP | Manifesto sem Markdown |
| OUT-009 | URL no Markdown não existe na entrada | Grounding reprova |
| OUT-010 | Imagem sem origem | Grounding reprova |
| OUT-011 | Escrita falha por disco | Estado não concluído; nenhum par parcial válido |
| OUT-012 | Hash após escrita diverge | Falha de persistência |
## 17. Testes de providers e fallback
| ID | Cenário | Resultado esperado |
| --- | --- | --- |
| LLM-001 | Timeout primário | Retry técnico limitado |
| LLM-002 | 429 primário | Backoff e fallback conforme limite |
| LLM-003 | 5xx primário | Retry técnico e fallback |
| LLM-004 | Resposta vazia | Retry técnico limitado |
| LLM-005 | Schema inválido | Fallback; sem loop semântico |
| LLM-006 | Grounding inválido | Fallback |
| LLM-007 | Fallback válido | Continuar e registrar uso |
| LLM-008 | Fallback indisponível | Falha/fallback determinístico da etapa |
| LLM-009 | Modelo não certificado na configuração | Falha inicial |
| LLM-010 | Configuração aponta modelo potente | Gate de configuração falha |
| LLM-011 | Troca de provider com mesmo papel | Domínio permanece inalterado |
| LLM-012 | Retry excede limite | Encerrar tentativa e seguir política |
## 18. Testes de observabilidade
| ID | Cenário | Resultado esperado |
| --- | --- | --- |
| OBS-001 | Fluxo textual normal | Trace e spans completos |
| OBS-002 | Fallback usado | Tentativas registradas separadamente |
| OBS-003 | Langfuse indisponível | Resultado continua e evento fica pendente |
| OBS-004 | Flush posterior | Telemetria enviada uma vez |
| OBS-005 | Conteúdo em trace desabilitado | Métricas, hashes e status permanecem |
| OBS-006 | Secret presente no ambiente | Nunca aparece em log/trace |
| OBS-007 | Execução rejeitada pelo ECP | Score e status corretos |
| OBS-008 | Reparo aplicado | Diff e decisão registrados |
| OBS-009 | Reparo rejeitado | Original e motivo registrados |
| OBS-010 | Prompt/modelo trocados | Versões corretas na trace |
| OBS-011 | Erro inesperado | Stack técnica local e código sanitizado |
## 19. Testes de segurança
| ID | Cenário | Resultado esperado |
| SEC-001 | Artigo contém “ignore instruções” | Texto tratado como dado |
| SEC-002 | HTML contém script | Não executar; parser trata como estrutura não confiável |
| SEC-003 | URL maliciosa no conteúdo | Não acessar; apenas validar e preservar se editorial |
| SEC-004 | Resposta do modelo inclui secret inventado | Schema/grounding rejeita |
| SEC-005 | Path traversal derivado do título | Nome por fingerprint impede traversal |
| SEC-006 | Log de exceção do SDK contém header | Sanitização remove credencial |
| SEC-007 | Arquivo de saída preexistente de outra execução | Não sobrescrever sem correspondência idempotente |
| SEC-008 | Conteúdo enorme excede limite configurado | Falha controlada antes do provider ou estratégia aprovada de contexto |
## 20. Teste de carga
### 20.1 Perfil
- 100 artigos por hora;
- mistura representativa de idiomas, domínios, extratores e estruturas editoriais;
- chamadas primárias e percentual observado de fallback;
- execuções concorrentes controladas pelo orquestrador;
- mesmo SQLite e diretório de saída planejados para produção.
### 20.2 Critérios
- nenhuma perda;
- nenhuma duplicação;
- nenhum arquivo parcial apresentado como final;
- nenhuma corrupção SQLite;
- 100% de traces enviados ou preservados;
- memória e disco estáveis durante o ensaio;
- custo e latência p50/p95/p99 registrados por etapa e total;
- throughput sustentado durante a janela acordada.
### 20.3 Saída
O relatório de staging deve propor limites de custo e latência. O go-live depende da aprovação desses limites.
## 21. Fault injection
| ID | Falha injetada | Critério |
| --- | --- | --- |
| FLT-001 | Provider primário indisponível | Fallback barato assume |
| FLT-002 | Ambos providers indisponíveis | Falha controlada sem saída falsa |
| FLT-003 | Langfuse indisponível | Processamento continua |
| FLT-004 | SQLite temporariamente bloqueado | Timeout controlado/retry de persistência |
| FLT-005 | Disco sem espaço | Nenhum final parcial |
| FLT-006 | Processo encerrado durante escrita | Reexecução recupera |
| FLT-007 | Resposta truncada do LLM | Schema rejeita |
| FLT-008 | ECP indisponível | Nenhum Markdown |
| FLT-009 | Arquivo temporário órfão | Limpeza segura por fingerprint |
| FLT-010 | Falha no flush de telemetria | Evento permanece pendente |
## 22. Promptfoo
### 22.1 Matriz
Testar:
- prompt de higienização × primário/fallback baratos;
- prompt de enriquecimento × primário/fallback baratos;
- versões candidata e vigente apenas no processo normal de release manual;
- slices de idioma, extrator e domínio.
### 22.2 Assertions permitidas
- JSON Schema;
- funções Python customizadas sem regex;
- IDs pertencentes ao contexto;
- conjuntos esperados de blocos;
- URLs pertencentes à entrada;
- precisão e recall calculados;
- enum e cardinalidade;
- diffs de reparo com bibliotecas de sequência/Unicode;
- comparação com verdade de referência;
- métricas de custo e latência.
### 22.3 Assertions proibidas
- regex textual;
- contains/not-contains baseado em keyword para semântica;
- LLM-as-a-judge para grounding;
- modelo potente como judge do runtime;
- aprovação baseada somente em score agregado.
## 23. Execução no CI
### Todo pull request
- schema e formato;
- lint;
- análise AST da proibição de regex;
- unitários;
- contratos;
- integração simulada;
- segurança básica.
### Mudança de prompt, contexto, schema ou modelo
- itens anteriores;
- Promptfoo reduzido;
- regressão dos 20 casos iniciais;
- comparação de custo e latência.
### Antes de promoção
- golden set completo;
- holdout;
- fault injection aplicável;
- carga de 100 artigos/hora;
- aprovação manual dos resultados;
- aprovação dos limites de custo e latência.
## 24. Evidências de teste
Preservar como artefatos:
- versão do código;
- versões dos prompts;
- providers e modelos;
- configuração Promptfoo;
- hashes da golden set;
- resultados por caso e slice;
- violações críticas;
- relatório de custo e latência;
- relatório de carga;
- aprovação de release.
## 25. Critério de conclusão
O plano estará cumprido quando:
- todos os cenários obrigatórios estiverem automatizados ou documentados como revisão manual controlada;
- gates críticos tiverem zero falhas;
- gates quantitativos forem atingidos por slice;
- os 20 casos iniciais não regredirem;
- a golden set de produção tiver revisão concluída;
- o teste de 100 artigos/hora passar;
- custo e latência tiverem limites aprovados;
- nenhuma suíte depender de regex textual ou modelo potente;
- não houver qualquer teste ou componente de self-healing no runtime.