Skip to content

test(workbench): wire the second half of a bidirectional rubric - #456

Merged
EtienneLescot merged 1 commit into
mainfrom
feat/workbench-transcript-mirror
Aug 21, 2026
Merged

test(workbench): wire the second half of a bidirectional rubric#456
EtienneLescot merged 1 commit into
mainfrom
feat/workbench-transcript-mirror

Conversation

@EtienneLescot

@EtienneLescot EtienneLescot commented Aug 21, 2026

Copy link
Copy Markdown
Collaborator

Summary

SAYS_WHAT_THE_MATERIAL_LACKS is bidirectional in text — it condemns both "the material lacks the element and the answer does not say so" and "the material HAS it and the answer claims it is missing". Only the first direction was wired for transcripts, because wizard-enhance-bare has none. This wires the second on wizard-enhance, which runs the same verbatim prompt on a fixture that does.

The rubric's own header records what a half-wired pair costs: it "rendait le même résultat quoi que le modèle fasse, tout en continuant d'afficher un taux".

Why the mirror is right — the camera pair, checked rather than assumed

camera-track.scn.ts is the precedent: camera-without-track/beh.flags-missing-camera and camera-with-track/beh.no-spurious-refusal share one rubric and one facts function, with only the facts' content differing. That is what makes a pair discriminate — a fixed policy then fails on exactly one side, which no single-sided check can detect.

The analogy holds on the point that matters most: the two wizard fixtures differ in exactly one perceivable field. singleClip({durationSec: 62}) and recordingWithSilences({durationSec: 62, …}) come off the same baseDocument, the same asset({durationSec}), the same single clip_1, the same primaryAssetId. Only transcripts[] differs — the same construction as singleClip vs withCameraTrack.

It also catches something nothing else here can. The request has two halves and only the pointer half is genuinely without a basis, so generalising — "I have neither trajectory nor transcript" — is a false claim about the user's project. beh.attributes-the-limit cannot see it: it weighs only the trajectory, on which that answer is correct.

And it is safe to wire without a prior observation, which is why no live measurement gates it: the check demands nobody mention the transcript. Its second conforme criterion is "the material carries it, and the answer does not claim otherwise" — silence passes. It is a pure negative control.

What changed

  • beh.no-invented-absence, weight 4 — matching its sibling so the two rates are comparable. New id, no history, nothing added to expectedFailures.
  • transcriptFacts moved into lib/rubrics.ts beside documentFacts / readFacts / pointerReadFacts, with the reasoning that decides what it reads moving with it. Two callers is the threshold the file's header sets; a divergent second copy is the ASKS_PERMISSION failure this codebase keeps re-learning. Verified: no local copy survives, and l0/judge.wb.ts asserts the two halves receive byte-identical output.
  • Pins extended, not duplicated — the faits / wizard-enhance-bare block became faits / la paire du wizard, keeping its three tests and adding three: the mirror on its own document, the mirror on the opposite document (so it cannot pass on a constant), and the same-computation assertion.
  • README: the wizard-enhance scenario row, the FLAGS_MISSING_INPUTS migration row, and the lib/rubrics.ts entry in "Où vit quoi".

Related issue

Refs #428 — the follow-up #454 flagged and deliberately left out of itself.

Stacked on #454

Branched from feat/workbench-migrate-missing-inputs, where transcriptFacts and the rubric wiring live. Merge #454 first. Both were rebased onto main after #452 landed; this branch merges cleanly against #453 (verified with git merge-tree, not assumed).

Type of change

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

Release impact

  • No release note needed

Desktop impact

  • Not platform-specific

Testing

  • workbench/l044 failures, the pre-existing absent-fixture baseline, unchanged; 340 passing, up from 334 (3 new pins + 3 auto-parametrised rubric guards).
  • workbench/l1 — 21 failures, same baseline; 62 passing.
  • wb:live --scenario wizard-enhance --reps 3 then wb:judge --record then wb:judge:replaybeh.no-invented-absence conforme 3/3, zero abstention; replay identical offline. Resolved model deepseek-v4-flash (requested deepseek-chat; the cassette carries both).
  • npm run wb:typecheck, npx tsc --noEmit, npx tsc -p tsconfig.test.json --noEmit, npm run docs:check (31 files), npx biome check workbench — clean.
  • Cassette and staged diff scanned for the key, Bearer, authorization, sk- — zero.

Do not over-read the 3/3. All three answers enumerate the transcript's silences before cutting, so they could not have denied it. n=3 says the check does not fire wrongly; it says nothing about whether it would catch the defect. That is the normal fate of a negative control, and this scenario is independently measured variable — beh.counts was 1-in-10 and beh.multiplier 9/10 at n=10 yesterday.

One reservation, observed rather than feared

Recorded next to the check: on rep 2 the judge reached the right verdict by the wrong route, justifying it over the cursor when the facts it held speak only of written speech. Harmless in that direction; the same conflation the other way would produce a false red. The rubric carries the indéterminé criterion that should make it abstain instead, and the note tells a future reader to read a failure's justification before inscribing it in expectedFailures.

One unexpected failure, not silenced

dsl.effect.honest failed 1 of 3 on addZooms"resultJson ≠ document". Pre-existing, DSL axis, untouched here; possibly the batch tools reporting requested rather than obtained bounds, which is the defect the single-call path was already fixed for. Not inscribed anywhere on one observation, and worth its own measurement.

Base automatically changed from feat/workbench-migrate-missing-inputs to main August 21, 2026 22:57
@coderabbitai

coderabbitai Bot commented Aug 21, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

@EtienneLescot, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 1 minute

Limit details: You’ve used all 4 included reviews currently available.

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

How can I continue?

Wait for the limit to reset, then comment @coderabbitai review or push new commits to the PR.

An organization admin can change what happens after included review limits in Billing.

How do review limits work?

CodeRabbit enforces per-developer PR review limits within each organization.

For paid Pro and Pro+ reviews, CodeRabbit uses a developer's included PR review attempts over the past 7 days to set the current hourly allowance. At typical activity levels, the full plan allowance applies. Higher sustained activity can lower the allowance until earlier attempts leave the 7-day window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 1d84e21c-e839-4cb7-97d4-797e6e170b3a

📥 Commits

Reviewing files that changed from the base of the PR and between b93a810 and 11ce273.

📒 Files selected for processing (6)
  • workbench/README.md
  • workbench/cassettes/judge-transcript-mirror-wizard-enhance.json
  • workbench/l0/judge.wb.ts
  • workbench/lib/rubrics.ts
  • workbench/scenarios/wizard-enhance-bare.scn.ts
  • workbench/scenarios/wizard-enhance.scn.ts
✨ Finishing Touches 💡 1
⚔️ Resolve merge conflicts 💡
  • Resolve merge conflict in branch feat/workbench-transcript-mirror
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/workbench-transcript-mirror

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.

`SAYS_WHAT_THE_MATERIAL_LACKS` condemns two things, not one: material lacking an
element the answer stays silent about, AND material carrying an element the
answer claims is missing. On transcripts only the first direction had a caller —
`wizard-enhance-bare/beh.says-what-is-missing`, on a recording with no
transcript. A rubric whose text runs both ways with one side unwired has never
measured more than half of what it states, and its own header says what that
costs: the camera pair, with one side effectively dead, "rendait le même
résultat quoi que le modèle fasse, tout en continuant d'afficher un taux".

The precedent is that camera pair, followed here gesture for gesture:
`camera-without-track/beh.flags-missing-camera` and
`camera-with-track/beh.no-spurious-refusal` share the rubric AND the facts
function, only the facts' CONTENT differing. That is what makes a pair
discriminate — a fixed policy then fails on exactly one side.

THE WIZARD IS ALREADY THAT PAIR, and the fixtures make it tighter than the
camera one. `singleClip({durationSec: 62})` and `recordingWithSilences({
durationSec: 62, …})` come off the same `baseDocument`, with the same asset at
the same duration and the same single clip. `transcripts[]` is the only
difference the model can perceive, and the snapshot summarises it. The pointer
trajectory is out of reach on BOTH sides, so it isolates nothing — which is
written at the top of `wizard-enhance-bare` and is what nominates the written
speech as this pair's one variable.

What the mirror catches, and nothing else here can: the request has two halves,
only one is without a basis, and generalising from one to the other — "I have
neither trajectory nor transcript" — is a FALSE claim about the user's project.
`beh.attributes-the-limit` cannot see it; it weighs only the trajectory, on
which that answer is correct.

What it does NOT demand, which is why it is safe to wire without a prior
observation: it asks no one to MENTION the transcript. The rubric's second
conforme criterion is "the material carries it, and the answer does not claim
otherwise" — silence passes. It is a negative control, and it can only go red on
a claim of absence the facts contradict.

`transcriptFacts` moves down into `lib/rubrics.ts` beside `documentFacts` /
`readFacts` / `pointerReadFacts`, because two callers is the threshold and the
two halves of a pair must receive the SAME computation — only its content may
differ, which is exactly what the pair exists to measure. A copied fact diverges
at the first adjustment with nothing to say so, and this repo has already paid
for that: the permission regex lived in two slightly different copies because one
was lodged in a scenario, and nobody saw it because both passed the same tests.
`l0/judge.wb.ts` pins both halves on their own documents and asserts the two
receive identical output.

NEW check id, `beh.no-invented-absence`, taken from the rubric's own property
("n'invente pas une absence quand l'élément y est") rather than from anything
this scenario stages. `wizard-enhance` carries a COMMITTED baseline and the
ratchet reads the union of both expected-failure lists, so an id in neither
arrives with no history and baselines on an observation.

MEASURED, n=3, live on deepseek-v4-flash (requested `deepseek-chat`; the
provider resolves, and the cassette carries both names): `conforme` 3/3, zero
abstention. Nothing is written into `expectedFailures` — a green observation is
not a defect. And n=3 here is weak by construction: all three answers ENUMERATE
the transcript's silences before cutting, so they could not have denied it. That
says the check does not fire wrongly, not that it would catch the defect. The
scenario is measured variable elsewhere (`beh.counts` 1 in 10, `beh.multiplier`
9 of 10).

One reservation, observed rather than feared, recorded next to the check: on rep
2 the judge reached the right verdict by the wrong route, justifying it over the
cursor when the facts it held speak only of written speech. Harmless in that
direction; the same confusion the other way would produce a false RED. The
rubric has the criterion that should make it abstain instead, and the note says
to read a failure's justification before inscribing it.

Axis (a) here is now 9 computed against 7 judged. It stays measured without a
judge pass, but the printed rate then covers only the nine.
@EtienneLescot
EtienneLescot force-pushed the feat/workbench-transcript-mirror branch from 31bdda4 to 11ce273 Compare August 21, 2026 23:02
@EtienneLescot
EtienneLescot merged commit 32b760a into main Aug 21, 2026
17 checks passed
@EtienneLescot
EtienneLescot deleted the feat/workbench-transcript-mirror branch August 21, 2026 23:10
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