Part of epic tracebloc/backend#1142 (Theme D — preflight parity). Sibling of #352.
MED. tb data ingest for image tasks green-lights a dataset the cluster will reject for a structural reason the local walk can already see — an accept-locally-then-fail-after-upload parity gap.
Symptom
A labels.csv whose file column is named image_id (not filename):
tb data ingest <dir> --task image_classification --label-column label --target-size 32x32 --dry-run reports ✔ Dry-run complete — your data and workspace check out.
- The real ingest then fails cluster-side with exit 9: every record logs
ERROR … file_transfer.py:182 | No filename found in record, 20/20 file-transfer failures, 0 records ingested.
Root cause
Two asymmetric preflight paths for "which column names the file":
|
Text tasks |
Image tasks |
| resolver |
matchColumnIndex(header, "filename") |
imageFileColIndex(header) |
| column missing |
hard error ("Add a filename column and re-run") |
falls back to column 0 — reads image_id values as filenames, finds the files, passes |
The ingestor's contract (layout.v1.json) declares requires_filename_column: true for every image task, and the cluster reads each image's file via a case-sensitive record.get("filename") at transfer time (data-ingestors file_transfer.py:179). It accepts exactly one key — literal filename and never aliases another column. So image_id (and data_id) resolve to no key → every row fails at transfer.
The ingestor's own preflight (validate_data) does not catch this — IngestableRecordsValidator passes when the column is absent ("not this validator's error to raise") — so it only surfaces at transfer time. That also blinded the parity harness: imgc-ok's golden is generated from validate_data (accept), the CLI also accepted (via the column-0 fallback), so accept/accept looked green.
Secondary latent bug: imageFileColIndex also accepted data_id as an alias with a comment claiming "a data_id CSV is ingested cleanly by the cluster" — it is not; a data_id-only manifest fails at transfer identically to image_id.
Even the CLI's own parity fixture was wrong: imgc-ok/labels.csv (and every other imgc-* fixture) used image_id,label, so the CLI's canonical happy-path example would not actually ingest. (Text fixtures correctly use filename,label, and text ingests fine.)
Fix (this PR)
- Enforce the filename-column contract up front for the image family (
CheckImageFilenameColumn), mirroring the text family's check — reject in the dry-run with an actionable message before any upload.
- Drop the
data_id false-accept and the column-0 fallback from imageFileColIndex (require literally filename, case-insensitive + trimmed like the ingestor's _match_column).
- Correct all 17
imgc-* parity fixtures to filename, add a new imgc-no-filename-col case that pins the divergence (ingestor preflight accepts, CLI rejects the doomed upload), regenerate goldens against the pin, and fix the unit-test CSVs + help text.
Follow-up (separate issue)
The transfer read is case-sensitive (record.get("filename")) while the ingestor's preflight validator matches case-insensitively, so a header like Filename passes preflight yet fails transfer — the same #340-class asymmetry already fixed for the label column, still open for filename. Best fixed ingestor-side.
Part of epic tracebloc/backend#1142 (Theme D — preflight parity). Sibling of #352.
MED.
tb data ingestfor image tasks green-lights a dataset the cluster will reject for a structural reason the local walk can already see — an accept-locally-then-fail-after-upload parity gap.Symptom
A
labels.csvwhose file column is namedimage_id(notfilename):tb data ingest <dir> --task image_classification --label-column label --target-size 32x32 --dry-runreports✔ Dry-run complete — your data and workspace check out.ERROR … file_transfer.py:182 | No filename found in record, 20/20 file-transfer failures, 0 records ingested.Root cause
Two asymmetric preflight paths for "which column names the file":
matchColumnIndex(header, "filename")imageFileColIndex(header)image_idvalues as filenames, finds the files, passesThe ingestor's contract (
layout.v1.json) declaresrequires_filename_column: truefor every image task, and the cluster reads each image's file via a case-sensitiverecord.get("filename")at transfer time (data-ingestorsfile_transfer.py:179). It accepts exactly one key — literalfilenameand never aliases another column. Soimage_id(anddata_id) resolve to no key → every row fails at transfer.The ingestor's own preflight (
validate_data) does not catch this —IngestableRecordsValidatorpasses when the column is absent ("not this validator's error to raise") — so it only surfaces at transfer time. That also blinded the parity harness:imgc-ok's golden is generated fromvalidate_data(accept), the CLI also accepted (via the column-0 fallback), so accept/accept looked green.Secondary latent bug:
imageFileColIndexalso accepteddata_idas an alias with a comment claiming "adata_idCSV is ingested cleanly by the cluster" — it is not; adata_id-only manifest fails at transfer identically toimage_id.Even the CLI's own parity fixture was wrong:
imgc-ok/labels.csv(and every otherimgc-*fixture) usedimage_id,label, so the CLI's canonical happy-path example would not actually ingest. (Text fixtures correctly usefilename,label, and text ingests fine.)Fix (this PR)
CheckImageFilenameColumn), mirroring the text family's check — reject in the dry-run with an actionable message before any upload.data_idfalse-accept and the column-0 fallback fromimageFileColIndex(require literallyfilename, case-insensitive + trimmed like the ingestor's_match_column).imgc-*parity fixtures tofilename, add a newimgc-no-filename-colcase that pins the divergence (ingestor preflight accepts, CLI rejects the doomed upload), regenerate goldens against the pin, and fix the unit-test CSVs + help text.Follow-up (separate issue)
The transfer read is case-sensitive (
record.get("filename")) while the ingestor's preflight validator matches case-insensitively, so a header likeFilenamepasses preflight yet fails transfer — the same #340-class asymmetry already fixed for the label column, still open forfilename. Best fixed ingestor-side.