██████╗ ███████╗██╗ ██╗
██╔══██╗██╔════╝██║ ██║
██████╔╝███████╗███████║
██╔═══╝ ╚════██║██╔══██║
██║ ███████║██║ ██║
╚═╝ ╚══════╝╚═╝ ╚═╝
ProStaff Harness - Execução Verificável e Auditoria de Evidências
╔══════════════════════════════════════════════════════════════════════════════╗
║ PSH - Harness de execução para agentes de código ║
╠══════════════════════════════════════════════════════════════════════════════╣
║ Portões de qualidade que consomem evidência produzida pelo núcleo, nunca ║
║ número informado pelo agente. ║
║ ║
║ Cobertura, teste, lint e segurança viram registro assinado por hash da ║
║ árvore que foi verificada. ║
║ ║
║ v0.3.0 · memória entre sessões · adapter Claude Code · 524 testes ║
╚══════════════════════════════════════════════════════════════════════════════╝
Um agente de código que reporta a própria nota não está sendo avaliado.
Está se autodeclarando aprovado.
O padrão aparece sempre da mesma forma:
agente: "rodei os testes, cobertura 87%, pode avançar de fase"
harness: portão aprovado
Ninguém rodou nada.
O número veio do modelo.
E mesmo quando o teste roda de verdade, nada impede continuar editando o código depois e seguir usando aquele resultado.
O psh fecha esses dois buracos.
O portão lê exclusivamente registros de evidência que o próprio núcleo produziu.
Cada registro carrega o hash da árvore de arquivos que estava no disco no momento da verificação.
Editou depois, a evidência vence.
$ psh advance --coverage 99
psh: portão não aceita métrica vinda do chamador: [coverage].
Valor de portão só vem de registro de evidência produzido por 'psh verify'.
$ echo $?
5
A recusa é explícita de propósito.
Tratada como "flag desconhecida", o caminho nunca apareceria em teste e ninguém saberia se a garantia existe.
▶ Funcionalidades (clique para expandir)
┌─────────────────────────────────────────────────────────────────────────────┐
│ [■] Evidence Engine - o núcleo executa o verificador, o agente nunca │
│ [■] Frescor por hash - editou arquivo observado, a evidência vence │
│ [■] Trilha encadeada - JSONL com prev_hash, detecta remoção e edição │
│ [■] Âncora externa - pega reescrita coordenada da cadeia inteira │
│ [■] Workflow Engine - fases, portões e protocolo de falha por classe │
│ [■] Contrato validado - JSON Schema + integridade referencial no load │
│ [■] Override rastreado - passed-with-override permanente, nunca passed │
│ [■] Extratores nativos - lcov, Cobertura, SimpleCov, go cover, JSON Ptr │
│ [■] Exit code manda - texto extrai detalhe, nunca decide veredito │
│ [■] Adapter CI - headless, JSON, código de saída estável │
│ [■] psh doctor - diagnóstico fora do runtime, colável em issue │
│ [■] Binário único - bun build --compile, sem runtime instalado │
│ [■] Boundary Engine - allowlist por agente, aplicada pelo kernel │
│ [■] Memory Engine - página em disco, índice FTS5, frescor por hash │
│ [■] psh handoff - retomada montada do estado, não de resumo │
│ [■] Consolidação - a sessão vira página, cada linha com a origem │
│ [■] Adapter Claude Code - cinco hooks, contrato lido do binário instalado │
└─────────────────────────────────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────┐
│ 01 · Plataformas │
│ 02 · Instalação │
│ 03 · Primeiros passos │
│ 04 · Como um portão decide │
│ 05 · Contrato de workflow │
│ 06 · Códigos de saída │
│ 07 · Integração com CI │
│ 08 · O que o psh não faz │
│ 09 · Desenvolvimento │
│ 10 · Roadmap │
└──────────────────────────────────────────────────────┘
O núcleo (workflow, evidência, auditoria) roda onde o Bun roda.
O que depende de plataforma é como a fronteira de escrita é aplicada.
| Plataforma | Fronteira | Estado |
|---|---|---|
| Linux | mount pelo kernel, via bubblewrap e Landlock | testado no CI e contra o binário real |
| macOS | mount pelo kernel, via seatbelt | testado no CI, sem o sandbox instalado |
| Windows via WSL2 | igual ao Linux | testado no CI, em WSL2 de verdade |
| Windows nativo | nenhuma | não suportado |
Windows exige WSL2.
Não é preguiça de portar: a fronteira precisa de namespace de usuário e de Landlock, que são construções do kernel Linux.
O CI roda a suíte dentro de um WSL2 real, e mede o que aquele kernel oferece antes de rodar qualquer teste:
kernel: Linux 6.18.33.2-microsoft-standard-WSL2
landlock: 101 símbolos em kallsyms
bwrap: bubblewrap 0.9.0
bwrap real: funciona
Ou seja, o WSL2 tem as primitivas necessárias.
A linha da tabela acima é medição, não suposição.
Fora do WSL2 o psh cai no modo degradado, que detecta e reverte escrita
fora da fronteira em vez de impedir, e isso é uma garantia mais fraca.
O modo aparece no psh status, no psh doctor e dentro de cada registro de
evidência, nunca em silêncio.
Sem sandbox instalado, em qualquer plataforma, o comportamento é o mesmo modo degradado declarado.
Dá para trabalhar assim, mas quem impede a escrita passa a ser um snapshot, não o kernel.
Requer Bun 1.3 ou superior para compilar.
git clone https://github.com/Bulletdev/ProStaff-Harness.git
cd ProStaff-Harness/psh
bun install
bun run build # gera dist/psh, binário únicoIsolamento de execução é opcional e usa o ai-jail 1.19 ou superior.
Sem ele o psh roda em modo degradado.
O modo é declarado no psh status, no psh doctor e dentro de cada
registro de evidência, nunca silencioso.
psh init --profile lean # detecta a stack, mostra o plano, pede confirmação
psh verify # o NÚCLEO roda os verificadores do portão atual
psh status # fase, tentativa, portão, sandbox, trilha
psh advance # avalia o portão e decide a transição
psh audit verify # integridade da trilha encadeada
psh audit reanchor # decisão humana registrada quando trilha e âncora divergem
psh doctor # diagnóstico completo
psh remember "<fato>" # fixa o que não pode ser perdido entre sessões
psh memory search <termo> # busca na memória do projeto
psh handoff # bloco de retomada, pronto para a próxima sessão
psh adapter claude-code install # registra os cinco hooks em .claude/
psh adapter claude-code contract # confere o contrato contra o binário instaladopsh init é não destrutivo.
Mostra o plano, faz backup do que sobrescrever e só escreve dentro de
.harness/.
Use --dry-run para ver o plano sem aplicar nada.
$ psh status
perfil lean
fase phase.5.build - Build + Quality
tentativa 2 (retries 1/2)
status in-progress
sandbox ai-jail 1.19.2 operante
fronteira mount (2 agentes)
trilha íntegra (47 entradas, 0 problemas)
portão all-of: REPROVADO
ok verifier-status:tests observado 0 esperado exit 0
NÃO verifier:coverage observado 78.4 esperado min 85
78.4 abaixo do mínimo 85
psh verify -> núcleo executa o verificador dentro do sandbox
-> extrai o valor do relatório, ou usa o código de saída
-> calcula o hash da árvore dos paths observados
-> grava .harness/evidence/<fase>/<tentativa>/<verificador>.json
psh advance -> lê APENAS registros de evidência
-> recalcula o hash da árvore agora
-> divergiu = evidência obsoleta, nomeando o arquivo que mudou
-> compara com o threshold do contrato
-> grava evento, encadeia na trilha, atualiza state.json
O que isso impede, na prática:
| Tentativa | Resultado |
|---|---|
| Passar a métrica por argumento | recusa nomeada, saída 5 |
| Avançar sem ter verificado | reprova por "não verificado" |
| Verificar e continuar editando | reprova por evidência obsoleta, com o arquivo citado |
| Verificador que falhou ao rodar | reprova, e a métrica do relatório não é aproveitada |
| Suíte morta por timeout ou sinal | falha, nunca zero |
| Apagar uma linha da trilha | psh audit verify acusa, saída 4 |
| Reescrever a trilha inteira relinkada | a âncora fora do arquivo acusa |
| Escrever depois de adulterar a trilha | recusado antes da escrita, para o estrago não sumir |
Fases, portões e verificadores ficam em .harness/workflow.json.
O arquivo é validado contra JSON Schema no carregamento.
Contrato inválido é falha fatal, nunca aviso.
{
"_type": "psh-workflow",
"version": 1,
"profile": "lean",
"verifiers": [
{
"id": "coverage",
"run": ["npm", "run", "test:coverage"],
"extract": { "kind": "lcov", "file": "coverage/lcov.info", "metric": "lines.pct" },
"watch": ["src/**", "tests/**", "package.json"],
"timeout_s": 900
}
],
"phases": [
{
"id": "phase.5.build",
"name": "Build + Quality",
"terminal": false,
"next": ["phase.6.ux-gate"],
"gate": {
"type": "all-of",
"checks": [{ "kind": "verifier", "verifier": "coverage", "min": 85 }],
"on_fail": {
"action": "rework",
"loopback_to": "phase.5.build",
"message": "cobertura abaixo do mínimo"
}
},
"on_failure": { "class": "quality", "max_auto_retries": 2 }
}
]
}O threshold mora em um lugar só: no check do portão, nunca no verificador.
O contrato não tem onde declarar o mesmo número duas vezes com valores diferentes.
Fase terminal é declarada com "terminal": true, nunca inferida de um next
vazio.
Um next que aponta para fase inexistente derruba o carregamento, em vez de
virar erro em runtime quando já é tarde.
| Código | Significado |
|---|---|
0 |
sucesso |
1 |
falha genérica: uso incorreto, verificador com erro |
2 |
portão reprovado |
3 |
contrato inválido |
4 |
cadeia de auditoria comprometida |
5 |
métrica forjada recusada |
6 |
projeto sem .harness/ |
- name: portões de qualidade
run: psh adapter ci --jsonO adapter verifica, avalia o portão e decide a transição em uma chamada, sem TTY e sem interação.
Ele não oferece --force.
Override é ato humano com confirmação, e CI não tem humano para confirmar.
{
"_type": "psh-ci-report",
"phase": "phase.5.build",
"sandbox_mode": "ai-jail",
"boundary": { "mode": "mount", "agents": 2, "detail": "escrita restrita pelo kernel via ai-jail" },
"verify": { "ran": [{ "verifier": "coverage", "status": "ok", "value": 87.4 }] },
"gate": { "passed": true },
"advance": { "decision": "advanced", "to": "phase.6.ux-gate" },
"audit_ok": true
}--gate-only avalia sem mexer no estado.
--skip-verify reaproveita evidência existente em vez de reverificar.
Esta seção existe porque um harness que promete garantia que não tem é pior que não ter harness nenhum.
-
Sem sandbox, não impede: detecta e reverte.
A fronteira só é aplicada pelo kernel quando há um sandbox operante.
Fora disso o modo é
degraded: snapshot antes, comparação depois, reversão do que saiu da fronteira.A diferença aparece no
psh status, nopsh doctore em cada registro de evidência. -
O mount não expressa arquivo novo em diretório gravável fora do escopo.
A raiz do projeto permanece gravável, então uma entrada criada ali durante a corrida escapa do kernel.
É o snapshot que fecha esse resíduo, e por isso ele continua ligado também no modo enjaulado.
-
Detecção de comando destrutivo não é proteção.
rm -rf,git reset --harde afins geram alerta na trilha, nunca bloqueio.Casamento por texto erra nos dois sentidos, e tratar isso como controle criaria confiança que o mecanismo não sustenta.
-
A memória não é fonte canônica.
A faixa em
.harness/memory/é transitória e fica fora do repositório. O que precisa sobreviver com garantia sai dela porpsh memory promotee vira arquivo versionado, que entra em revisão como qualquer outro. -
A página de sessão não é narrativa escrita por modelo.
Ela é montada da trilha, com o número da entrada em cada linha. A reescrita como narrativa que o R5.2 pede é chamada de modelo e depende do Maestro, que é a v0.4.
-
O adapter não expõe as tools do núcleo por MCP.
O agente fala com o harness por linha de comando. O servidor MCP é marco posterior.
-
A atribuição de quem fez o quê é melhor esforço dentro da jaula.
psh execmarca a sessão com o id do agente, mas o agente roda com ambiente próprio e pode apagar a marca antes de chamar opsh. O que ele não apaga é a entradacommand.execda mesma execução na trilha, e é por ela que a correlação fecha. -
Não gerencia modelo nem custo.
Maestro e contabilidade de token são marcos posteriores.
-
O verificador não declara toolchain nem credencial.
As flags da jaula são fixas e o
envdo verificador é mapa literal, sem interpolação. Toolchain instalado sob$HOMEsome lá dentro, e passar uma credencial exigiria escrever a chave em texto puro num arquivo versionado.No primeiro teste de campo isso custou vendorizar o binário do node dentro da árvore, e só funcionou porque o promptfoo lê o
.envdo diretório de trabalho por conta própria. -
Cinco achados do primeiro teste de campo seguem abertos.
Estão em
DEVDOCS/CAMPO-01-multilingo.md, com repro e gravidade. Nenhum deles produz valor de portão errado, que foi o critério para publicar a v0.3.0 com eles em aberto em vez de segurar a versão.
cd psh
bun run check # typecheck + verificação estática + testes com cobertura
bun test # 524 testes
bun run build # binário único┌──────────────────────────────────────────────────────────────────────────┐
│ psh/src/workflow fases, portões, protocolo de falha, estado │
│ psh/src/evidence execução de verificador, extratores, frescor │
│ psh/src/gate avaliação de portão sobre evidência │
│ psh/src/audit trilha encadeada por hash │
│ psh/src/memory página, índice FTS5, handoff, consolidação │
│ psh/src/adapters ci (headless) e claude-code (cinco hooks) │
│ psh/schemas contratos de dados versionados │
└──────────────────────────────────────────────────────────────────────────┘
Regras da suíte de testes:
-
Todo arquivo em
tests/roda por glob, nunca por lista enumerada.Teste que não roda é pior que teste ausente, porque cria confiança.
-
Nenhum teste escreve no diretório de trabalho nem toca no Git da árvore real.
-
Cobertura de linha acima de 85% em
audite emevidence, acima de 70% no resto, com todo arquivo desrc/entrando na medição.
| Versão | Escopo | Estado |
|---|---|---|
| 0.1 | Núcleo verificável: workflow, evidência, auditoria, CLI, adapter CI | entregue |
| 0.2 | Motor de fronteira, integração com ai-jail, modo degradado, suíte adversarial | entregue |
| 0.3 | Adapter Claude Code e memória entre sessões | entregue |
| 0.4 | Roteamento de modelo, contabilidade de token e custo | planejado |
| 0.5 | Adapter OpenCode, perfis por stack | planejado |
| 1.0 | Endurecimento, binários assinados, matriz de CI completa | planejado |
AGPL-3.0, a mesma do prostaff-api.
O ai-jail é GPL-3.0 e entra como dependência externa invocada como
processo, nunca linkada: o psh monta um argv e executa o binário.
Não há obra derivada, e as duas licenças convivem.
- Fabio Akita (@akitaonrails) - autor do ai-jail, consumido aqui como dependência externa para isolamento de execução.
Parte do ecossistema ProStaff