From efaa505059231e4cd898d6825d79f4a7002d90ca Mon Sep 17 00:00:00 2001 From: "Claude (agent-headless-import)" Date: Thu, 24 Sep 2026 22:12:02 -0700 Subject: [PATCH] test(import): a folder of only header-only files kept no record and refuses (improvise-8jk6.6) After merging PR #314, a header-only file's seeded File proposal means the category check alone no longer refuses an import that kept no record. nothing_to_import does, and this pins it. The docs now name from_merge (switched to from_csv in the merge) among the CSV entry points. Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_01VdfEGXA7aJ6ftMsCc9PJze --- context/code/formula-and-io.md | 16 +++++++++++++--- context/test-evidence/formula-and-io.md | 11 +++++++++++ crates/improvise-io/src/import/wizard.rs | 10 ++++++++++ 3 files changed, 34 insertions(+), 3 deletions(-) diff --git a/context/code/formula-and-io.md b/context/code/formula-and-io.md index d51dfcaf..321ca347 100644 --- a/context/code/formula-and-io.md +++ b/context/code/formula-and-io.md @@ -670,8 +670,10 @@ This is not caution for its own sake. When the parser first changed, eleven tests in four files failed. Every one handed parser output to `ImportPipeline::new`, the JSON constructor, which would read every column as text and propose `Revenue` as a Category. Production had the same shape at all -three CSV entry points: `main.rs`'s single file and merge, and -`ImportJsonHeadless`. Now that mistake does not compile. Two holes remain, and +three CSV entry points: `main.rs`'s single file, `ImportPipeline::from_merge` +(the merge path, from PR #314), and `ImportJsonHeadless`. `from_merge` merged +in with `Self::new` and now calls `Self::from_csv`. Now that mistake does not +compile. Two holes remain, and both take a deliberate step. `ImportPipeline::records` is still a public `Vec`, and a test helper once copied it out of a CSV pipeline into a JSON one. And `CsvRecords` derefs to a slice that `.to_vec()` will copy. @@ -708,6 +710,14 @@ records were skipped as not JSON objects`, or `the source has no records`. The wizard asks it first on `ReviewProposals`, where another array path can still be chosen. +One case meets PR #314's "a header-only file is still a file". A folder whose +CSVs are **all** header-only has no records, only sources. Built from +`from_merge`'s seeded proposals alone, it would save `File` items, empty +columns and one empty records view per file. It is refused here, because it +kept no record. A header-only file *beside* files with rows is unaffected, +since some record was kept. Whether an all-header-only folder should instead +save its structure is a question for the user on the PR. + **A partial skip stays a warning.** Some records kept and some skipped still builds, exits 0, and warns with a count: `Skipped 1 of 3 records that were not JSON objects`. The argument: a non-object in an array of records is not a @@ -717,7 +727,7 @@ trailing `null` or a stray scalar in a hand-edited file. Every record that Making it fatal would fail every import that carries one stray element. Making it a distinct non-zero exit that still writes gives a contract where `import … && next` stops even though the file was written. That is a question -for the user (PR #TBD), and `an_import_that_skips_some_records_warns_with_a_count_and_still_writes` +put to the user on the PR that fixed 8jk6.6, and `an_import_that_skips_some_records_warns_with_a_count_and_still_writes` pins the current answer so that changing it is a decision. ### A folder of CSVs is one sparse model, with a view per file (improvise-lpzu) diff --git a/context/test-evidence/formula-and-io.md b/context/test-evidence/formula-and-io.md index 0e5ced9e..11b6a9a0 100644 --- a/context/test-evidence/formula-and-io.md +++ b/context/test-evidence/formula-and-io.md @@ -1894,3 +1894,14 @@ improvise-8jk6.12 (4). A `region` of `" "` is kept as a record with no predates this PR (improvise-ych), but the sparse-record rule now rests on it: without it the value becomes an item named `" "`. Rival F4 (the reviewer's fault) fails it. + +### a_folder_of_only_header_only_files_keeps_no_record_and_refuses + +improvise-8jk6.6 meeting PR #314's rule that a header-only file is still a +file. Written after merging `main` at `c91cc055`, where the two rules first +met. The file seeds give the pipeline a `File` Category proposal even with no +records. So without `nothing_to_import` the build succeeds and saves `File` +items and empty views: the category check alone cannot refuse. The `setup` +assertion proves that seeded proposal exists, so the test is not passing only +because there was no category. **R7**, the check removed from the build, fails +it. Whether this case should refuse is put to the user on the PR. diff --git a/crates/improvise-io/src/import/wizard.rs b/crates/improvise-io/src/import/wizard.rs index eebc0c1b..f8bd66a9 100644 --- a/crates/improvise-io/src/import/wizard.rs +++ b/crates/improvise-io/src/import/wizard.rs @@ -3338,6 +3338,16 @@ mod tests { assert_eq!(wb.model.get_cell(&key), Some(&CellValue::Number(5.0))); } + /// improvise-8jk6.6 meets lpzu's header-only rule: a folder of nothing but + /// headers kept no record. Evidence: `context/test-evidence/formula-and-io.md`. + #[test] + fn a_folder_of_only_header_only_files_keeps_no_record_and_refuses() { + let p = merged_pipeline(&[("m/a.csv", "entity,x\n"), ("m/b.csv", "entity,y\n")]); + assert!(p.proposals.iter().any(|f| f.field == "File"), "setup"); + let msg = p.build_model().expect_err("refuses").to_string(); + assert_eq!(msg, "Nothing to import: the source has no records"); + } + /// improvise-8jk6.6: the wizard asks on the review step, where another /// path can still be chosen. Evidence: the test-evidence doc. #[test]