07 · prova
Capacidade e confiabilidade não são a mesma métrica.
pass@k mede se um caso passou ao menos uma vez em k execuções. pass^k mede se passou todas as vezes. O gate de capacidade exige pass@k ≥ 0.90; o gate de regressão exige pass^k = 1.0; --gate both exige os dois. Uma suíte que passa no primeiro e reprova no segundo é instável, e o relatório diz em quanto.
python -m hpp eval run examples/reliable-coding/benchmark-suite.json -k 3 --gate both
python -m hpp benchmark -k 3 --json
Medido: HPP benchmark: pass@k=1.00 · pass^k=1.00 · gate=PASS sobre dez controles (contrato do manifesto, enforcement de política, waves do WorkGraph, colisão de lanes, frescura de monitor, proveniência de contexto, piso de roteamento, gate evento/evidência, determinismo do grafo, atestação de evidência), três execuções cada. O relatório JSON carrega o sha256 da suíte e a plataforma em que rodou. Exit 0 quando o gate passa, 1 quando não.
Uma decisão tomada fora é medida, nunca confiada por padrão
Novo na 2.6.0
O harness não chama modelo. Uma decisão pequena tomada fora dele — por uma regra, uma pessoa, um modelo local ou um modelo de decisão tipada hospedado — pode ser registrada como hpp.decision/v1: sempre advisory; com raise-only ela pode elevar um valor declarado e nunca baixá-lo; abstention e instrument-failure são desfechos que não mudam nada. hpp decide eval roda o decisor que você nomeia como comando contra casos rotulados e reporta cobertura, acerto seletivo, erros confiantes, abstenções e falhas separadamente, com uma curva de cobertura por limiar de confiança. A versão 1 mede só perguntas de escolha.
python -m hpp decide validate decision.json
python -m hpp decide eval examples/typed-decisions/gotcha-family-suite.json --decider-command '["python", "examples/typed-decisions/baseline_decider.py"]'
decide eval SUITE — --decider-command recebe o argv do decisor como array JSON de strings, executado sem shell; omita-o para reexecutar os registros guardados nos casos da suíte, onde um caso sem registro conta como falha de instrumento. --timeout (padrão 10.0) é o número de segundos por caso antes de esse caso contar como falha de instrumento. O gate: --min-selective-accuracy (padrão 0.9), --max-confident-errors (padrão 0; erro confiante é uma resposta errada com confiança igual ou acima do confidence_floor da suíte, 0.6 quando a suíte não define um) e --max-failures (padrão 0). Exit 0 o gate passou · 1 o gate reprovou, ou nada foi decidido e não há acerto a medir · 2 registro, suíte ou argv inválido.
decide validate RECORD — imprime valid e o valor efetivo sobre o qual um consumidor pode agir, com a sua ação (raised, kept, advised ou none). Exit 0 válido · 2 recusado, nomeando a primeira regra quebrada.
Medido num checkout de main: o decisor baseline na suíte de exemplo → 15 casos, 12 decididos, 3 abstenções, 0 falhas de instrumento, cobertura 0.8, acerto seletivo 1.0, every threshold held, exit 0. A mesma suíte sem --decider-command não guarda registros → 15 falhas de instrumento, no decision was made, exit 1. Um registro com "authority": "binding" → hpp: authority must be 'advisory': …, exit 2. O pacote 2.5.8 não tem o comando decide: ali, hpp decide é uma escolha inválida, exit 2.
Veja examples/typed-decisions para o adaptador, seus riscos e como medi-lo antes de confiar nele.
Pacotes de evidência: o exit code de um critério, medido fora do modelo
Novo na 2.6.0
O hpp não dirige navegador e não chama modelo. O hpp evidence run roda o comando-critério que você declara — uma spec ponta a ponta, uma suíte pytest, qualquer script — como argv sem shell, mede o exit code, faz o hash de cada arquivo de artefato que você declarou e escreve .hpp/evidence/<id>-<UTC>.json (hpp.evidence/v1): o comando, o commit-base, o exit code, o veredito, stdout e stderr como contagem de bytes e sha256 (nunca o texto), os artefatos e um auto-hash. O veredito só é passed quando o comando saiu com 0 e cada padrão declarado casou com um arquivo que esta execução escreveu (um arquivo intocado desde antes da execução sai como unchanged e não conta); fora isso é failed, missing-artifacts, timeout ou could-not-start. O hpp evidence verify re-deriva um registro a partir dos arquivos no disco. O auto-hash torna visível um registro editado; ele não é assinatura — quem pode escrever o arquivo pode reescrevê-lo —, então um checker que não pode confiar no maker roda o comando de novo a partir da sua própria lane em vez de se apoiar só no verify.
python -m hpp evidence run --id smoke-page --artifact out/report.html --artifact out/smoke.log -- python examples/evidence/smoke_page.py
python -m hpp evidence verify <record_path>
evidence run --id ID [--artifact GLOB]... -- COMMAND — --artifact é um glob relativo ao workspace, repetível; --timeout (padrão 600.0) é o número de segundos antes de a execução contar como timeout; --out é o diretório do registro, relativo ao workspace (padrão .hpp/evidence); --record-event acrescenta evidence_recorded ao log de eventos quando, e só quando, o pacote passou, e exige ao menos um --artifact. Um argv com cara de segredo, um caminho de artefato ou de --out fora do workspace, um id inválido ou um timeout não positivo são recusados antes de qualquer execução. Exit 0 passou · 1 não passou · 2 recusado, ou o evento não pôde ser acrescentado.
evidence verify RECORD — o record_path que o run imprimiu. Exit 0 valid, registro íntegro de uma execução que passou · 1 not-evidence, registro íntegro de uma execução que não passou · 2 blocked, o registro foi editado ou se contradiz, ou um artefato mudou ou sumiu.
Medido num checkout de main, numa cópia da árvore para que out/ e .hpp/ fossem escritos ali: o demo → passed, exit 0, e o verify do seu registro → valid, exit 0. Com --break depois do script, os dois artefatos foram escritos e o veredito foi failed, exit 1; o verify desse registro → not-evidence, exit 1, e o de uma cópia editada para dizer passed → blocked, the record was edited after it was written, exit 2. O primeiro registro, verificado de novo depois que a segunda execução reescreveu out/ → blocked, 2 artifact(s) changed since the run, exit 2: runners ponta a ponta que limpam o diretório de saída no início de cada execução fazem o mesmo, então dê aos artefatos que precisam continuar verificáveis um diretório por execução. --record-event sem work_started no log → exit 2, evento não acrescentado; depois de event append --type work_started ele levou o loop a evidenced. O pacote 2.5.8 não tem o comando evidence: ali, hpp evidence é uma escolha inválida, exit 2.
Veja examples/evidence para o mesmo padrão em volta de um runner ponta a ponta de verdade.
Régua de recuperação: o recuperador, medido separado da resposta
Novo na 2.6.0
O harness não roda índice e não chama modelo. Um recuperador é um comando que você declara: ele lê {"query": …, "k": …} como JSON no stdin e imprime ids ranqueados, o melhor primeiro. O hpp retrieval eval pontua o top k de cada resposta contra os ids que uma suíte hpp.retrieval-suite/v1 marca como relevantes — hit@k, recall@k, precision@k, MRR e nDCG@k — e conta à parte as falhas de instrumento (não iniciou, timeout, exit diferente de zero, saída que não é JSON, id duplicado): elas nunca são pontuadas, e as médias cobrem só os casos medidos. Sem nenhum caso medido as métricas são nulas e o gate diz por quê, nunca 0%. Se uma boa resposta pode ser gerada a partir do que foi recuperado é outra pergunta, que não se faz aqui.
python -m hpp retrieval eval examples/retrieval/suite.json
python -m hpp retrieval eval examples/retrieval/suite.json --retriever-command '["python", "examples/retrieval/keyword_retriever.py"]'
retrieval eval SUITE — --retriever-command recebe o argv do recuperador como array JSON de strings, executado sem shell; omita-o para reexecutar os resultados guardados nos casos da suíte. -k substitui o corte da suíte. --timeout (padrão 10.0) é o número de segundos por caso antes de esse caso contar como falha de instrumento. O gate: ao menos um caso medido, --min-recall (padrão 0.8) para o recall@k médio e --max-failures (padrão 0). Exit 0 o gate passou · 1 o gate reprovou · 2 suíte ou argumento recusado.
Medido num checkout de main: os dois comandos → 7 casos medidos, 0 falhas de instrumento, recall@3 médio 0.786, hit@3 0.857, MRR 0.786, nDCG@3 0.749, mean recall@k 0.786 < 0.8, exit 1 — o baseline por palavra-chave erra por completo a pergunta parafraseada, um 0 medido, e o gate padrão reprova; com --min-recall 0.75 → every threshold held, exit 0. Um recuperador que sai com 3 em todo caso → 7 falhas de instrumento, métricas nulas, no case was measured (7 instrument failures); there is no recall to measure, exit 1. --retriever-command '"python"' → hpp: --retriever-command must be a JSON array of strings (argv, no shell), exit 2. A suíte é sintética e o baseline foi escrito junto com ela: estes números provam que a régua funciona, não que algum recuperador é bom. O pacote 2.5.8 não tem o comando retrieval: ali ele é uma escolha inválida, exit 2.
Veja examples/retrieval para o contrato do recuperador e o formato da suíte.
Checagem de citações: todo marcador resolve, ou o exit code diz qual não resolve
Novo na 2.6.0
Uma resposta escrita a partir de fontes marca cada afirmação com o id da fonte em que se apoia — [ID:<id>] por padrão. O hpp cite check lê a resposta e a lista de itens de contexto a partir da qual ela foi escrita e reporta, sem modelo: um marcador cujo id não está no contexto (UNKNOWN_ID), um marcador que nomeia um intervalo ou uma lista (RANGE) e um marcador vazio (EMPTY_MARKER) bloqueiam; mais de --max-per-sentence marcadores numa frase (TOO_MANY) e uma frase com número, percentual, valor monetário ou data e nenhum marcador (UNCITED_CLAIM) avisam. Ela prova que cada marcador resolve, não que a fonte citada sustenta a frase: esse julgamento precisa de um leitor. O separador de frases e o detector de números são heurísticas, listadas na docstring de hpp/citations.py.
python -m hpp cite check --text examples/citations/answer.md --context examples/citations/context.json
cite check --text FILE --context FILE — --context é uma lista JSON de itens com id (e o seu text), ou de ids soltos; --max-per-sentence (padrão 4); --marker recebe uma regex com exatamente um grupo de captura, o id. Texto vazio, texto que é só código, texto ou contexto com cara de segredo e uma regex inutilizável são recusados. Exit 0 limpo · 1 aviso (TOO_MANY, UNCITED_CLAIM) · 2 bloqueio (UNKNOWN_ID, RANGE, EMPTY_MARKER) ou entrada recusada.
Medido num checkout de main: a resposta de exemplo → veredito ok, 7 frases, 5 marcadores, 4 dos 5 ids de contexto citados, exit 0. Em cópias dela: [ID:glossary] trocado por [ID:glossary-v2] → UNKNOWN_ID, exit 2; por [ID:runbook-7,sla-2026] → RANGE, exit 2; o marcador removido da frase com 4 engineers → UNCITED_CLAIM, exit 1. E o limite, medido: 42 minutes trocado por 90 minutes, ainda citado à fonte que diz 42 → veredito ok, exit 0. O pacote 2.5.8 não tem o comando cite: ali ele é uma escolha inválida, exit 2.
Veja examples/citations para outra sintaxe de marcador e o formato do relatório.
A atestação amarra uma aprovação a bytes
hpp attest create registra um veredito junto com o hash da spec, o commit-base, um digest do snapshot completo do repositório (rastreado e não rastreado), o maker, o checker e a sessão. Maker e checker têm de ser diferentes, ou o registro é recusado. hpp attest verify re-deriva cada amarração e bloqueia quando qualquer uma se moveu.
python -m hpp attest create --repo . --spec spec.md --output att.json --maker a --checker b --session s1 --verdict approved
python -m hpp attest verify att.json --repo .
Medido num repositório de rascunho: --maker a --checker a → hpp: maker and checker must be different non-empty actors, exit 2. Com atores diferentes o registro sai approved e o verify devolve valid, exit 0; depois de uma linha da spec mudar, o verify devolve blocked com mismatches: spec_sha256, snapshot_digest, exit 2. A atestação exige git.
Veja a matriz de prova e o contrato do benchmark.