Skip to content

adversarial analyze: per-COU recall columns are silently empty on committed corpora (stale spec_path) #67

Description

@cloudronin

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:

  1. Resolve the spec by spec_id against the repo's own dev/specs/ tree, falling back to the recorded spec_path when it resolves.
  2. 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.
  3. 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions