@@ -234,6 +234,7 @@ function main() {
234234 if ( files . length === 0 ) throw new Error ( 'No eval cases found; the harness would report a vacuous 0/0.' ) ;
235235
236236 const results = [ ] ;
237+ const validatedResults = [ ] ;
237238 for ( const f of files ) {
238239 const c = JSON . parse ( readFileSync ( join ( caseDir , f ) , 'utf8' ) ) ;
239240 // THE CONTEXT PACK, built per case so the eval measures the pipeline the
@@ -347,7 +348,9 @@ function main() {
347348 // the description, and a diff-only run carries none at all -- scoring
348349 // against text the reviewer never received excludes vocabulary it could
349350 // not have copied, and that reads as a false miss.
350- results . push ( { pr : c . pr , body : c . input . contextPack ?. body ?? null , expected : c . expected , verdict : null , findings : [ ] } ) ;
351+ const failed = { pr : c . pr , body : c . input . contextPack ?. body ?? null , expected : c . expected , verdict : null , findings : [ ] } ;
352+ validatedResults . push ( failed ) ;
353+ results . push ( failed ) ;
351354 continue ;
352355 }
353356 // PARTIAL losses exit 0. DROPPED is one finding refused; CAPPED is the
@@ -364,6 +367,17 @@ function main() {
364367 // the generator alone, which is how you tell "the reviewer missed it" from "the
365368 // judge threw it away".
366369 let parsed = JSON . parse ( readFileSync ( findingsPath , 'utf8' ) ) ;
370+ // Record what survived mechanical validation before the optional judge
371+ // mutates it. Without this, a final miss cannot be attributed to the
372+ // generator/validator or to suppression; live #3609 required manually
373+ // reconstructing that distinction from log fragments.
374+ validatedResults . push ( {
375+ pr : c . pr ,
376+ body : c . input . contextPack ?. body ?? null ,
377+ expected : c . expected ,
378+ verdict : parsed . verdict ,
379+ findings : parsed . findings ?? [ ] ,
380+ } ) ;
367381 // Nothing to judge costs no process. `judge()` short-circuits on an empty
368382 // list anyway, so this only saves a node start -- but four of the fixtures
369383 // expect zero findings and more come back clean in practice.
@@ -411,17 +425,20 @@ function main() {
411425 results . push ( { pr : c . pr , body : c . input . contextPack ?. body ?? null , expected : c . expected , verdict : parsed . verdict , findings : posted } ) ;
412426 }
413427
428+ const validatedScore = score ( validatedResults ) ;
414429 const s = score ( results ) ;
415430 console . log ( `\nRubric: ${ rubric } model: ${ model } ` ) ;
416431 for ( const l of s . lines ) console . log ( l ) ;
417432 // A case whose review never validated contributes zero to recall, and a recall
418433 // number is not readable without knowing how many of those there were.
419434 const noReview = results . filter ( ( r ) => r . verdict === null ) . length ;
420- console . log ( `\n RECALL of known findings: ${ s . recall } ` ) ;
435+ console . log ( `\n VALIDATED recall before judge/cap: ${ validatedScore . recall } ` ) ;
436+ console . log ( ` VALIDATED extra findings: ${ validatedScore . extra } ` ) ;
437+ console . log ( ` POSTED recall after judge/cap: ${ s . recall } ` ) ;
421438 if ( noReview ) {
422439 console . log ( ` ...over ${ results . length } cases, of which ${ noReview } PRODUCED NO USABLE REVIEW and scored zero.` ) ;
423440 }
424- console . log ( ` EXTRA findings (look at these, do not minimise them): ${ s . extra } ` ) ;
441+ console . log ( ` POSTED extra findings (look at these, do not minimise them): ${ s . extra } ` ) ;
425442 console . log ( '\n Compare against the same command on the other rubric. A change that lowers' ) ;
426443 console . log ( ' recall is a regression whatever it does to EXTRA.\n' ) ;
427444 ok = true ;
0 commit comments