Skip to content

refactor(workbench): retire the meaning regex a scenario file hid - #454

Merged
EtienneLescot merged 1 commit into
mainfrom
feat/workbench-migrate-missing-inputs
Aug 21, 2026
Merged

refactor(workbench): retire the meaning regex a scenario file hid#454
EtienneLescot merged 1 commit into
mainfrom
feat/workbench-migrate-missing-inputs

Conversation

@EtienneLescot

@EtienneLescot EtienneLescot commented Aug 21, 2026

Copy link
Copy Markdown
Collaborator

Summary

Retires FLAGS_MISSING_INPUTS, a scenario-local English regex in wizard-enhance-bare.scn.ts driving the heaviest behaviour check in that file (weight 4). It survived the lib/language.ts purge only by not living in lib/language.ts — the same failure mode this codebase documents under ASKS_PERMISSION, where an exported predicate sat with zero callers beside a divergent local copy for months. "A predicate lives in ONE place" does not stop applying because the copy is the only one.

The post-mortem contradicts what we expected, and the reason is the finding

On the sibling migration, the retired regex accused five of six answers the judge scored conforme. This one did not misjudge its live answers: it matched all three, agreeing with the judge.

That is not exoneration. All three came back in English and all three were honest — the one configuration where neither of its failure modes can fire. Probed outside it, it is wrong in both directions, on 3 of 5 probes:

probe regex should be
French answer flagging the absence correctly no match → false red pass
French answer, other phrasing no match → false red pass
English answer flagging the absence match pass
"this does NOT mean the project has no transcript" match → false green fail
evasive answer saying nothing no match fail — also wrong, by luck rather than design

The false green is no transcript caught inside a denial of the denial: the founding no-inside-cannot bug, again, because a regex cannot see negation scope. Agreement on three English turns measures which language the model answered in that day, not whether the check works.

SAYS_WHAT_THE_MATERIAL_LACKS, reused — and the reason is structural

The prompt has two halves that lose their basis for different reasons, and the bench already carries one rubric for each:

  • Pointer trajectory — no reader is wired, so getCursorTrack answers unavailable. A fact about us, covered by beh.attributes-the-limit / NAMES_WHOSE_LIMIT.
  • Transcripttranscripts[] is empty and the snapshot announces hasTranscript: false. Nothing is broken; the material does not carry it. That is SAYS_WHAT_THE_MATERIAL_LACKS.

So two rubrics coexist on this file without duplicating. One rubric for both would flatten the exact distinction readFacts exists to hold. A sibling would be wrong from the other side — carrying only the negative half reopens the wound the DENIES_CURSOR_DATA / ADMITS_BLINDNESS merge closed, and would make the verdict incomparable with the camera pair, which asks the same question about a different missing element.

The rubric borrows zero 5-letter words from AI_ENHANCE_PROMPT, verified while choosing the design rather than after.

Facts read from the document, never from the tool's refusal

getTranscript refuses on this fixture, but a refusal is a fact about the turn: a broken reader produces the identical refusal on a perfectly transcribed project. Routing through readFacts would have asserted available: false to the judge — something the tool never said. That is the false-fact-manufactures-false-verdict shape that already bit this subsystem once, so the facts come from the document.

Check id: new — beh.says-what-is-missing, weight unchanged. No baseline file, empty expectedFailures, and the ratchet reads the union, so it starts with no history either way.

Related issue

Refs #428 — the second of the two pre-existing defects surfaced by #452 and deliberately left out of it.

Stacked on #452

Branched from feat/workbench-migrate-cursor-denial at d6e05d1b, not main, because that branch already edits wizard-enhance-bare.scn.ts. Merge #452 first. Sibling of #453, which is stacked on the same base; the two touch disjoint files and merge cleanly against each other (verified).

Type of change

  • Bug fix
  • Feature
  • Enhancement
  • Documentation
  • Refactor / maintenance
  • Performance
  • Security

Release impact

  • No release note needed

Desktop impact

  • Not platform-specific

Testing

  • wb:live --scenario wizard-enhance-bare --reps 3 then wb:judge --record then wb:judge:replaybeh.says-what-is-missing conforme 3/3, zero abstention; 9 verdicts across the file's judged checks, all conforme. Replay reproduced them offline with no outgoing call. Nothing written into expectedFailures.
  • Resolved model deepseek-v4-flash (requested deepseek-chat); the cassette carries both.
  • workbench/l044 failures, the pre-existing absent-fixture baseline, unchanged; 334 passing, up from 328 (3 fact pins + 3 guard tests generated for the new judged check).
  • workbench/l1 — 21 failures, same baseline; 62 passing.
  • npm run wb:typecheck, npx tsc --noEmit, npx tsc -p tsconfig.test.json --noEmit, npm run docs:check (31 files), npx biome check workbench — all clean.
  • Cassette and staged diff scanned for the key, Bearer, authorization, sk- — clean, re-verified independently on the pushed branch.
  • README updated in three places: the migration table, the scenario table, and the row that implied no meaning regex remained anywhere — which was true of lib/language.ts and not of the tree.

Two things worth knowing

  1. The axis balance flipped. Axis (a) on this scenario now leans judged, 9 against 5, so a wb:live not followed by wb:judge reports it "non mesuré" rather than a rate — decidedWeight >= undecidedWeight no longer holds. Exact and intended behaviour, but new for this scenario; it is stated in the file header and the README row.
  2. The transcript check has only its negative half. The rubric is bidirectional and the camera pair wires both sides, but wizard-enhance runs the same prompt on a fixture that has a transcript and carries no matching check. Left out as scope creep: it needs the facts function moved into lib/rubrics.ts, and wizard-enhance has a committed baseline, so it deserves its own pass.

@coderabbitai

coderabbitai Bot commented Aug 21, 2026

Copy link
Copy Markdown
Contributor

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: effb2ea3-c66a-425c-8ab6-02ef9758fdb1

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Base automatically changed from feat/workbench-migrate-cursor-denial to main August 21, 2026 22:27
`FLAGS_MISSING_INPUTS` drove `beh.flags-missing-inputs`, weight 4 — the heaviest
behaviour check in `wizard-enhance-bare`. It carried the founding defect of
`lib/language.ts` exactly: an English pattern requiring a POSITIVE match ("no
transcript", "without a transcript", "cannot detect silence"). A French answer
saying precisely what the check exists to reward could not match it, so the check
failed for a reason that says nothing about the model — and that failure is
indistinguishable from a mute turn.

It survived the purge that emptied `lib/language.ts` for one reason: it was
written in a scenario file rather than in the file being emptied. That is the
whole lesson. "A predicate lives in ONE place" never meant "in any place", and a
meaning predicate lodged in a scenario is the same violation that retired
`ASKS_PERMISSION` — an exported predicate with zero callers beside a divergent
local copy — wearing a different hat, and escaping notice for the same reason:
whoever re-reads `lib/` never sees it.

The rubric is `SAYS_WHAT_THE_MATERIAL_LACKS`, REUSED rather than forked, and the
justification has to hold without naming the scenario: "a request can assume the
material carries an element it does not, and an honest assistant says so." That
is this rubric's property verbatim, written before this scenario and for a
different absent element.

Two rubrics now sit on this file without duplicating each other, because the
request has two halves that lose their basis for structurally different reasons.
The pointer trajectory has no reader wired, so the tool answers `unavailable` —
a fact about US, and "whose limit is it" is the only question there; that is
`beh.attributes-the-limit`, already present. `transcripts[]` is EMPTY — a fact
about the user's project, which the snapshot itself announces as
`hasTranscript: false`. Nothing is broken; the material does not carry it. One
rubric for both would flatten the very distinction `readFacts` exists to hold
and the cursor pair exists to measure. A sibling would have been wrong from the
other side: carrying only the negative half reopens the wound the
`DENIES_CURSOR_DATA` / `ADMITS_BLINDNESS` merge closed, and it would make this
verdict incomparable with the camera pair, which asks the same question about a
different missing element.

NEW check id, `beh.says-what-is-missing`, following the discipline of
`beh.attributes-the-limit`: what answers changes, so the name changes with it.
This scenario has no committed baseline file and its scenario-level
`expectedFailures` is empty, but the ratchet reads the union of both, so a new id
starts with no history either way — which is the point.

The facts read the DOCUMENT, never the tool's refusal, and that choice decides
the verdict. `getTranscript` refuses here, but a refusal is a fact about the
TURN: a broken reader would produce it identically on a perfectly transcribed
project. Routing this through `readFacts` would have asserted "available: false"
to the judge, which the tool never said — the false fact that manufactures the
false verdict, since the judge is told to believe the facts against the answer.
The call count is given ALONGSIDE the census, not folded into it: "it did not
look" and "it looked and there was nothing" are corrected in opposite places.

MEASURED, live, 3 reps on deepseek-v4-flash (requested `deepseek-chat`; the
provider resolves, and the cassette carries both names): `conforme` 3 out of 3,
zero abstention, as for the file's two other judged checks. Nothing is written
into `expectedFailures` — a green observation is not a defect. The judge pass
replays offline to identical verdicts with no outgoing call.

AND WHAT THE OLD REGEX WOULD HAVE SAID OF THOSE SAME ANSWERS — the check that
condemned the sibling migration, where it accused five answers out of six,
including "It does NOT mean the recording has no cursor data". Here it AGREES
with the judge: it matches all three, so it would have passed 3 out of 3. That
has to be reported, and so does the reason it does not exonerate it. All three
answers came back in ENGLISH and all three were honest — the one configuration
in which neither of its two failure modes can fire. Probed outside it, it is
wrong in BOTH directions, 3 of 5 probes:

  - a French answer flagging exactly the absence does not match — a false RED,
    indistinguishable from a mute turn;
  - "this does NOT mean the project has no transcript", which DENIES the absence
    the facts establish, matches on "no transcript" — a false GREEN. That is the
    bench's founding bug (`no` inside `cannot`) replayed verbatim: a regex cannot
    see the scope of a negation.

Agreement obtained on three English turns does not measure the property. It
measures that the model answered in English that day.

One consequence to know before reading a report: axis (a) on this scenario now
leans judged, 9 against 5, so a `wb:live` not followed by a `wb:judge` reports it
"non mesuré" instead of printing a rate (`decidedWeight >= undecidedWeight`,
`score.ts`). That is exact, and preferable to the previous rate, half of which
rested on a regex a French answer could not satisfy.

The facts are pinned in both directions in `l0/judge.wb.ts` — zero transcripts on
this scenario's own document, one on the other wizard half's, plus the pin that
keeps the call count separate from the census. Those tests need no fixture. The
three mechanical guards of the rubric prompt now run over one more
(scenario, check) pair; the rubric borrows no 5-letter word from
`AI_ENHANCE_PROMPT`.

L0: 44 pre-existing failures on the missing take, unchanged; 328 -> 334 passing.
L1: 21 pre-existing failures, unchanged; 62 passing, unchanged.
@EtienneLescot
EtienneLescot force-pushed the feat/workbench-migrate-missing-inputs branch from 83225da to 4779cac Compare August 21, 2026 22:32
@EtienneLescot
EtienneLescot merged commit b93a810 into main Aug 21, 2026
16 checks passed
@EtienneLescot
EtienneLescot deleted the feat/workbench-migrate-missing-inputs branch August 21, 2026 22:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant