Summary
uofa adversarial analyze silently emits empty per-COU recall columns on every committed Phase 2 corpus, because batch_manifest.json records spec_path as a generation-time absolute path that no longer resolves.
This is the same defect class as the out_dir staleness fixed in 548224d, in the second of the two paths that manifest records.
Evidence
spec_path values in the committed batch manifests point at the generator's temp directory:
$ python -c "import json,os; d=json.load(open('dev/build/adversarial/phase2/holdout-2026-04-29-v0513/batch_manifest.json')); \
ps=[s['spec_path'] for s in d['perSpecResults']]; print(ps[0]); print(sum(map(os.path.exists,ps)), '/', len(ps))"
/tmp/holdout_specs/ce/w-al-01.yaml
0 / 39
classifier.py (~line 384) uses it to derive the per-COU baseline key:
try:
spec_path = Path(per_spec["spec_path"])
spec_obj = load_spec(spec_path)
baseline_key = _detect_baseline_key(str(spec_obj.base_cou))
except Exception:
pass
The bare except: pass means the miss degrades baseline_key to None rather than raising, so the run completes normally and the columns come out blank.
Confirmed on a fresh analyze run of the v0.5.13 holdout (post-548224d1, so per-spec resolution now works and rows are produced):
| column |
distinct values |
recall_morrison_cou1 |
[''] |
recall_morrison_cou2 |
[''] |
recall_min_per_cou |
[''] |
recall_cou_disparity |
[''] |
cou_dependent_flag |
[''] |
All other columns populate correctly.
Impact
- Per-COU recall (the D1 per-COU coverage-delta columns) cannot be derived from any committed corpus.
- The failure is quiet: the run succeeds, the CSV has the right shape, and only the values are missing.
- Anything that quotes a per-COU figure sourced from these corpora needs re-checking against a run where
baseline_key actually resolves.
Suggested fix
Same shape as 548224d — derive rather than trust the recorded path:
- Resolve the spec by
spec_id against the repo's own dev/specs/ tree, falling back to the recorded spec_path when it resolves.
- Alternatively, read
base_cou from the per-spec manifest.json if it is recorded there (needs checking; it may not be), which avoids re-loading the spec YAML entirely.
- Narrow the bare
except: pass so a resolution failure is at least visible — either a warning naming the spec, or a single summary line noting that per-COU columns were not derivable for N specs.
Point 3 is worth doing regardless of which resolution strategy wins: a column that is silently empty is harder to notice than one that is loudly absent.
Notes
- Related: 548224d fixed the sibling
out_dir staleness by re-anchoring on the batch directory the caller passes via --in. That approach can't go stale on a future rename because it derives the location instead of recording it; the same reasoning applies here.
- The committed corpora are frozen artifacts, so the fix belongs in the analyzer, not in the recorded manifests.
- Not yet verified: whether the M5 corpus (
dev/build/adversarial/phase2/2026-04-26, 381 specs) behaves identically. Resolution of spec_path fails there too (paths have the same shape), but a full analyze run over it was not performed.
Summary
uofa adversarial analyzesilently emits empty per-COU recall columns on every committed Phase 2 corpus, becausebatch_manifest.jsonrecordsspec_pathas a generation-time absolute path that no longer resolves.This is the same defect class as the
out_dirstaleness fixed in 548224d, in the second of the two paths that manifest records.Evidence
spec_pathvalues in the committed batch manifests point at the generator's temp directory:classifier.py(~line 384) uses it to derive the per-COU baseline key:The bare
except: passmeans the miss degradesbaseline_keytoNonerather than raising, so the run completes normally and the columns come out blank.Confirmed on a fresh analyze run of the v0.5.13 holdout (post-548224d1, so per-spec resolution now works and rows are produced):
recall_morrison_cou1['']recall_morrison_cou2['']recall_min_per_cou['']recall_cou_disparity['']cou_dependent_flag['']All other columns populate correctly.
Impact
baseline_keyactually resolves.Suggested fix
Same shape as 548224d — derive rather than trust the recorded path:
spec_idagainst the repo's owndev/specs/tree, falling back to the recordedspec_pathwhen it resolves.base_coufrom the per-specmanifest.jsonif it is recorded there (needs checking; it may not be), which avoids re-loading the spec YAML entirely.except: passso a resolution failure is at least visible — either a warning naming the spec, or a single summary line noting that per-COU columns were not derivable for N specs.Point 3 is worth doing regardless of which resolution strategy wins: a column that is silently empty is harder to notice than one that is loudly absent.
Notes
out_dirstaleness by re-anchoring on the batch directory the caller passes via--in. That approach can't go stale on a future rename because it derives the location instead of recording it; the same reasoning applies here.dev/build/adversarial/phase2/2026-04-26, 381 specs) behaves identically. Resolution ofspec_pathfails there too (paths have the same shape), but a full analyze run over it was not performed.