From f7d5bb2d35bbbd47729ee984f9fc26c46d7bd021 Mon Sep 17 00:00:00 2001 From: Tyler Skelton Date: Wed, 7 Oct 2026 20:17:05 -0700 Subject: [PATCH 1/3] Exact tag: "verified" needs a check that compares the output with an exact value On the 2026-10-07 mmhd lane (MiniMax M3 worker, Haiku 5.5 judge) cron-next was labelled verified on four judge checks that all tested properties: 8 lines, each minute a multiple of 15, sorted, unique; "every date is the 13th or a Friday"; two runs agree; "starts with 2024-06-1". The grader asked for */15 from 2025-01-01 00:07 and got 00:45, 01:00, 01:45 instead of 00:15, 00:30, 00:45. Wrong output with the right shape passes every property check. src/tiers.ts assertsExact tags a check "exact" (beside "value") when it compares the work's output with an exact expected value: equality with a literal or a table of literal cases, diff against literal text, grep -x of a literal line, `is None` on literal input, a specific non-zero exit/HTTP status, a worked-out value found in a program's output on literal input, or an oracle built from the input files. Counts, sizes, types, modulo, prefixes, membership, formats, one constant across every item, two unknowns, checks that cannot fail, and patterns tried on the check's own lines are properties. tierOf now earns "verified" only from a passing independent exact check; passing independent value checks that are all properties give "passed-checks" with the reason "passed checks that test properties only". The post-work audit drops such a check with V-property-only. The reference writer's check compares against two references and is tagged exact. Prompts: the drafter now asks, for each functional requirement, at least one check that compares the output on a concrete input with an exact expected output worked out from the task text, with its source quoted or derived in a new `expect` field, plus edge and near-miss inputs (boundaries, empty input, 1.2.3.4.5 for an IP rule, both sides of an either/or rule). The old line that told drafters to test properties instead of working values out is gone. The critic sees `expect` and is told a derivation is not a guess. The audit prompt asks for exact comparisons and edge inputs, drops its count example, and takes `expect` too. Replayed over 2026-10-07 (bench/local/exact_replay.py): of the 81 verifieds the base rule grants, every grader-wrong one (2/2) is removed and 56 of 79 grader-right ones (71%) are kept. --- src/criteria.ts | 53 ++- src/post-audit.ts | 25 +- src/reference.ts | 5 +- src/tiers.ts | 518 +++++++++++++++++++++++++++- test/acp-seal-gate.test.ts | 2 +- test/discriminating-checks.test.ts | 6 +- test/ended-with-verdict.test.ts | 2 +- test/exact-values.test.ts | 199 +++++++++++ test/harness.test.ts | 2 +- test/hidden-checks-withheld.test.ts | 2 +- test/independent-checks.test.ts | 6 +- test/judgment.test.ts | 2 +- test/late-criteria.test.ts | 2 +- test/lazy-seal.test.ts | 2 +- test/post-work-audit.test.ts | 7 +- test/prework-screen.test.ts | 2 +- test/reference.test.ts | 2 +- test/reveal-default.test.ts | 2 +- test/review-advisory.test.ts | 4 +- test/sharper-checks.test.ts | 4 +- test/tiers.test.ts | 18 +- 21 files changed, 797 insertions(+), 68 deletions(-) create mode 100644 test/exact-values.test.ts diff --git a/src/criteria.ts b/src/criteria.ts index 7a47c9b..b06772c 100644 --- a/src/criteria.ts +++ b/src/criteria.ts @@ -44,7 +44,12 @@ import type { CheckAuthor } from "./types.js"; * appears, it compiles) rather than running the deliverable. Kept, but such a * check alone cannot carry a "verified" once the ones that ran it are retired. */ -export type DraftedCheck = { name: string; run: string; surface?: true }; +/** + * A drafted check. `expect` is where its expected output comes from: the task + * words that state it, or the steps that derive it from a rule the task states. + * Shown to the critic; it never runs. + */ +export type DraftedCheck = { name: string; run: string; surface?: true; expect?: string }; export type Draft = { checks: DraftedCheck[]; notes: string[]; @@ -71,6 +76,8 @@ export const CRITERIA_MAX_NAME = 40; */ export const CRITERIA_MAX_RUN = 1_000; export const CRITERIA_MAX_NOTE = 200; +/** The longest `expect` (where a check's expected output comes from) kept on a drafted check. */ +export const CRITERIA_MAX_EXPECT = 300; /** * Shape a renderer-supplied payload into checks the engine may run. @@ -100,6 +107,7 @@ export function sanitizeCriteria(raw: unknown): Draft { name: c.name.trim().slice(0, CRITERIA_MAX_NAME), run: c.run.trim(), ...(c.surface === true ? { surface: true as const } : {}), + ...(typeof c.expect === "string" && c.expect.trim() ? { expect: c.expect.trim().slice(0, CRITERIA_MAX_EXPECT) } : {}), })) : []; const notes: string[] = Array.isArray(o.notes) @@ -500,13 +508,13 @@ export function drafterInputsHash(d: DrafterInputs): string { return h.digest("hex").slice(0, 16); } -const SYSTEM = [ +export const DRAFT_SYSTEM = [ "You draft acceptance criteria for one coding task. You are not doing the task", "and you will not judge whether it was done — a person approves what you write", "and it is sealed before the work starts.", "", "Return JSON only, matching:", - ' {"checks":[{"name":"kebab-name","run":"shell command"}],"notes":["sentence"]}', + ' {"checks":[{"name":"kebab-name","run":"shell command","expect":"where the expected output comes from"}],"notes":["sentence"]}', "", "checks are commands that MUST already work in this project. Prefer the scripts", "listed below verbatim, optionally narrowed (npm test -- ). Never invent", @@ -525,16 +533,22 @@ const SYSTEM = [ "present. A task that says accuracy above 0.62 gets a check of accuracy above 0.62.", "At least one check must run the thing and test its result; a check that only", "asks whether a file exists, a name appears, or output parses shows nothing.", + "For each thing the task says the work must do, at least one check runs it on a", + "concrete input and compares what it produces with the EXACT expected output:", + "[ \"$(python3 tool.py 3 4)\" = \"7\" ], assert f('a b') == 'a-b', a diff against a", + "heredoc, or grep -qx of a whole expected line. Work the expected output out from", + "the task text only: a value it states, or one you get by applying a rule it states", + "to an input your check picks, step by step. Put where it comes from in the check's", + "\"expect\": the task words quoted, or the derivation (\"*/15 from 00:07: 00:15,", + "00:30, 00:45\"). A property (the line count, sorted, each minute a multiple of 15,", + "a prefix, two runs agreeing) holds for wrong output too: it may come with an exact", + "check, never instead of one. Pick inputs at the edges: a boundary, empty input, a", + "value just outside a rule (1.2.3.4.5 for an IP rule), a start that is not on a step,", + "and each side of an either/or rule.", "When the right answer cannot be known before the work (a fitted value, a model's", "accuracy on held-out data, the fastest query), check the closest thing the task", "provides instead: the stated threshold on the data that IS here (accuracy >= 0.62", - "on the provided test split), the fit's error against the measured points, the new", - "query's time against the original's. Never hard-code an answer you had to guess.", - "Never write an expected value you worked out yourself — a date, a total, a count,", - "an output line the task does not state. Your own arithmetic is where checks go", - "wrong. Expected values come only from the task text; otherwise test what the task", - "states as a property: the stated number of lines, each strictly after the start,", - "the stated format, totals that add up, the same answer from two equivalent inputs.", + "on the provided test split). Never hard-code an answer you had to guess.", "Checks must leave nothing behind: build and write into $(mktemp -d), never into", "a directory the task names.", "Every path is relative to the working directory shown below. Never write an", @@ -545,7 +559,7 @@ const SYSTEM = [ "feels. They are recorded on the receipt as stated intent and never reported as", "verified. Do not write a note that pretends to be a check.", "", - "Two or three checks and at most two notes. Fewer is better. If the task needs", + "Two to four checks and at most two notes. If the task needs", "no criterion beyond the project's own bar, return empty lists.", ].join("\n"); @@ -751,7 +765,7 @@ export async function draftCriteria(opts: { baseUrl: opts.baseUrl, apiKey: opts.apiKey, model: opts.model, - system: SYSTEM, + system: DRAFT_SYSTEM, prompt: prompted, cwd: askCwd, what: "drafting criteria", @@ -772,7 +786,7 @@ export async function draftCriteria(opts: { baseUrl: opts.baseUrl, apiKey: opts.apiKey, model: opts.model, - system: SYSTEM, + system: DRAFT_SYSTEM, prompt: `${prompted}\n\nYour last reply could not be parsed as JSON. Reply with the JSON object only.`, cwd: askCwd, what: "drafting criteria", @@ -797,7 +811,7 @@ export async function draftCriteria(opts: { baseUrl: opts.baseUrl, apiKey: opts.apiKey, model: opts.model, - system: SYSTEM, + system: DRAFT_SYSTEM, prompt: context, cwd: askCwd, what: "drafting criteria", @@ -850,7 +864,8 @@ export const CRITIC_SYSTEM = [ " only have guessed (a model name, a count, a result the work must find out).", ' "surface" — it only looks: a file exists, a word appears in source, it compiles, --help works.', ' "runs" — it runs the deliverable on an input and checks what it produces.', - "For invents and guesses, quote the task words the check gets wrong, verbatim.", + "For invents and guesses, quote the task words the check gets wrong, verbatim. A value", + "derived step by step from a rule the task states, shown in the check's expect, is not a guess.", "", "Then list hard requirements no check would catch if the work got them wrong. A hard", "requirement is a concrete fact a shell command could test on this machine, right now:", @@ -869,6 +884,12 @@ export const CRITIC_SYSTEM = [ ' "uncovered":["verbatim task words"], "requirements":["verbatim task words"]}', ].join("\n"); +/** What the critic reads: the task, the project view, and each check with where its expected output comes from. */ +export function criticPrompt(task: string, view: string | undefined, checks: readonly DraftedCheck[]): string { + const line = (c: DraftedCheck) => `- ${c.name}: ${c.run}${c.expect ? `\n expect: ${c.expect}` : ""}`; + return `TASK TEXT:\n${task}\n\n${view ? `${view}\n\n` : ""}CHECKS:\n${checks.map(line).join("\n")}`; +} + export type Critique = { kept: DraftedCheck[]; dropped: { name: string; verdict: "invents" | "guesses"; quote: string }[]; @@ -1132,7 +1153,7 @@ async function critiqued( apiKey: opts.apiKey, model: opts.model, system: CRITIC_SYSTEM, - prompt: `TASK TEXT:\n${opts.task}\n\n${view ? `${view}\n\n` : ""}CHECKS:\n${d.checks.map((c) => `- ${c.name}: ${c.run}`).join("\n")}`, + prompt: criticPrompt(opts.task, view, d.checks), cwd: opts.askCwd ?? opts.cwd, what: "reviewing the drafted checks", fetchFn: opts.fetchFn, diff --git a/src/post-audit.ts b/src/post-audit.ts index 65fecef..c1f0bd0 100644 --- a/src/post-audit.ts +++ b/src/post-audit.ts @@ -41,7 +41,7 @@ import { parseLenient } from "./lenient-json.js"; import { interfaceView, isCode, type Changed } from "./interface-view.js"; import { bashPath, runCommand } from "./run.js"; import { copyTree } from "./scratch.js"; -import { assertsValue } from "./tiers.js"; +import { assertsExact, assertsValue } from "./tiers.js"; export const AUDIT_MAX_CHECKS = 4; export const AUDIT_MIN_QUOTE = 8; @@ -54,32 +54,39 @@ export const AUDIT_SYSTEM = [ "it added or changed, their signatures, how to run them, and the project listing.", "", "Write 2 to 4 shell checks. Each runs the deliverable (a script, a function, a query) on an input", - "and compares what it produces with an expected value, exiting non-zero on a mismatch. Almost", + "and compares what it produces with the EXACT expected output, exiting non-zero on a mismatch:", + "equality with a literal, a diff against a heredoc, or grep -x of a whole expected line. A count,", + "an order, a format, a prefix or two runs agreeing is a property wrong output also has: such a", + "check is dropped. Each requirement the task states gets at least one exact check. Almost", "every task states something checkable: an output for a given input, a format, a count, a rule", "(\"blank lines are ignored\", \"sorted by total, highest first\"), a file it must write.", "Return an empty list only when the task states nothing a command could test.", "", "The expected value must come from the task text: either stated outright, or the result of", "applying a rule the task states to a small input your check builds itself (write it under", - "$(mktemp -d)). For each check give `quote`: the task words that state that value or rule,", + "$(mktemp -d)). Pick inputs at the edges: a boundary, empty input, a value just outside a rule", + "(1.2.3.4.5 for an IP rule), a start that is not on a step, and each side of an either/or rule.", + "For each check give `quote`: the task words that state that value or rule,", "copied character for character from the task text (a short span is best, 5 to 20 words).", "A quote that is not word for word in the task, even a close paraphrase, drops the check. Never", - "use a value you would only know by running the work or by guessing.", + "use a value you would only know by running the work or by guessing. When the value is derived,", + "give the steps in `expect` (\"*/15 from 00:07: 00:15, 00:30, 00:45\").", "", "Rules: one line each, under 500 characters, run under bash from the project root on a", "throwaway copy. A check that only prints is useless: end every check in a comparison, e.g.", " [ \"$(python3 tool.py 3 4)\" = \"7\" ]", " python3 -c \"import json; d=json.load(open('out.json')); assert d['total'] == 12, d\"", - " [ \"$(grep -c '^ERROR' report.txt)\" -eq 3 ]", + " python3 tool.py 'x y' | diff - <(printf 'x-y\\n')", "A wrong result must make the check exit non-zero: no `|| true`, no `|| echo`, no", "printing PASS/FAIL with exit 0. Do not write into the project; scratch files go under", "$(mktemp -d). Use the file names, flags and signatures shown; do not invent others.", "", "Reply with JSON only:", - '{"checks":[{"name":"kebab-name","run":"shell command","quote":"verbatim task words"}]}', + '{"checks":[{"name":"kebab-name","run":"shell command","quote":"verbatim task words","expect":"how the expected output follows"}]}', ].join("\n"); -export type AuditCheck = { name: string; run: string; quote: string }; +/** `expect`: how the expected output follows from the quote, when it is derived rather than stated. */ +export type AuditCheck = { name: string; run: string; quote: string; expect?: string }; export type AuditDrop = { name: string; run: string; why: string }; /** The judge's reply as checks, with every ungrounded or malformed one dropped and said. */ @@ -116,6 +123,7 @@ export function parseAuditChecks(reply: string, task: string): { kept: AuditChec let name = String(o.name ?? "").trim().replace(/\s+/g, "-").slice(0, CRITERIA_MAX_NAME) || `audit-${kept.length + dropped.length + 1}`; const run = String(o.run ?? "").trim(); const quote = String(o.quote ?? "").trim(); + const expect = typeof o.expect === "string" ? o.expect.trim().slice(0, 300) : ""; while (seen.has(name)) name = `${name}-2`; seen.add(name); if (!run) dropped.push({ name, run, why: "no command" }); @@ -124,7 +132,7 @@ export function parseAuditChecks(reply: string, task: string): { kept: AuditChec else if (quote.replace(/\s+/g, " ").length < AUDIT_MIN_QUOTE || !quotedIn(quote, task)) dropped.push({ name, run, why: `its quote is not in the task text: "${quote.slice(0, 80)}"` }); else if (kept.length >= AUDIT_MAX_CHECKS) dropped.push({ name, run, why: `over ${AUDIT_MAX_CHECKS} checks` }); - else kept.push({ name, run, quote }); + else kept.push({ name, run, quote, ...(expect ? { expect } : {}) }); } return { kept, dropped }; } @@ -508,6 +516,7 @@ export async function auditGates( const never = cannotFail(c.run); if (never) return no("L16-cannot-fail", never); if (!assertsValue(c.run)) return no("V-no-value", "it does not assert an expected value"); + if (!assertsExact(c.run)) return no("V-property-only", "it tests a property of the output (a count, an order, a format, membership, two runs agreeing), not an exact expected value"); const work = await runOnCopy(ctx.workDir, c.run, { timeoutMs: ctx.timeoutMs, signal: ctx.signal }); if (work.printedFail) return no("L16-printed-fail", "it printed FAIL and still exited 0 on the work", { work: work.status }); if (work.status !== "pass") return no("A-fails-on-work", `it does not pass on the work (${work.status}): ${work.output.split("\n").slice(-1)[0]?.slice(0, 160) ?? ""}`, { work: work.status }); diff --git a/src/reference.ts b/src/reference.ts index 579989d..cf57565 100644 --- a/src/reference.ts +++ b/src/reference.ts @@ -486,8 +486,9 @@ async function writeAndTry(opts: Ask, prompt: string): Promise return assertsBehaviour(run); } -/** The tags a drafted check carries: surface from the critic, value from the command. */ +// ---------------------------------------------------------------- the exact tag + +/** + * Does this command compare the deliverable's output with an EXACT expected + * value, or only test a property of it? + * + * The 2026-10-07 lanes earned a wrong "verified" on cron-next with four + * passing judge checks that all tested properties: 8 lines, each minute a + * multiple of 15, sorted, unique; "every date is the 13th or a Friday"; two + * runs of the worker agree; "starts with 2024-06-1". Output that skips every + * other occurrence has all of those properties. An exact check would not + * have passed: from 00:07, `*\/15` gives 00:15 first, and the work gave 00:45. + * + * Exact, any one of these: + * 1. An equality (`==`, `===`, `-eq`, `=` in a test bracket, a failing `if + * ... != ...` / `!==`, `assertEqual`/`strictEqual`/`deepEqual`) between + * what the work produced and a literal: a number, a quoted string, a + * list/dict/set of literals, true/false/null, `"$(printf '...')"`, or a + * name bound to one (a table of cases). Asserted: under `assert`, `if ...: + * exit/raise/throw`, `exit(0 if ...)`, a test bracket, or `jq -e`. + * 2. `diff`/`cmp` against a heredoc, a here-string, `<(printf ...)` / + * `<(echo ...)`, or an expected file (the tier's pre-work record still + * has to vouch for the file: `valueUnproven`). + * 3. `grep -x` (or a `^...$` pattern) of a literal line, with no regex + * wildcards unless `-F`, over the work's output or a file it wrote. + * 4. ` is None`, asserted. + * 5. A specific worked-out value with a digit in it found in what a program + * printed on literal input: `nextrun.py '0 9 * * *' '2023-01-01 08:00' 1 + * | grep -q '2023-01-01 09:00'` (containsValue). + * 6. An oracle: the work's output equal to a value the check built from the + * task's input files without running anything (oracleEquality). + * 7. An exit or HTTP status other than 0/200 the task names (`rc=$?; [ $rc + * -eq 2 ]`), not read only on the path where the command failed. + * Forms 5 and 6 narrow the rule as first measured (literal equality, diff, + * grep -x only): over the 2026-10-07 lanes that kept 48 of 79 grader-right + * verifieds the base rule grants, under the two-thirds bar; with them, 56 of + * 79, and every grader-wrong verified is still removed + * (bench/local/exact_replay.py). + * Property, never exact, whatever the literal: + * - counts and sizes: `len()`, `.length`, `wc`, `grep -c`, `count`, `sum()`, + * jq `length`; + * - types and shapes: `type()`, `typeof`, `isinstance`, `keys`; + * - modulo (`% 15 == 0`), prefixes/suffixes and slices, membership in a + * file, a format pattern, ordering; + * - exit status 0 and HTTP 200: they say only that it ran; + * - the same constant compared across every item (`all(...)`, `any(...)`, + * a comprehension, a `for` loop, `find -exec`, `xargs`, jq `.[]`): each + * item having the property is still a property; + * - two unknowns compared (`[ "$a" = "$b" ]`, `L == sorted(set(L))`, two + * runs of the work agreeing); + * - a command that cannot fail (`... || echo fail`, `...; true`, `print(x == 1)`); + * - a pattern the work wrote, tried on lines the check chose (`re.search(p, ...)`). + * A constant compared inside a loop over a literal table of cases + * (`for inp, want in CASES: assert f(inp) == want`) is exact: `want` changes + * with the input. + * + * `literalOnly` turns forms 5 and 6 off: the rule as first measured, for + * bench/local/exact_replay.py. + */ +export function assertsExact(run: string, opts: { literalOnly?: boolean } = {}): boolean { + // A pattern the work wrote, tried on lines the check chose: the judge's own examples + // missed the grader's near-miss (see "Not widened" under assertsValue). + if (cannotFail(run) || PATTERN_DELIVERABLE.test(run)) return false; + const names = exactNames(run); + const narrow = !opts.literalOnly; + if (exactEquality(run, names, narrow) || exactHelper(run, names) || exactDiff(run) || exactGrepLine(run) || exactNone(run, names) || (narrow && containsValue(run))) return true; + // `bash -c '