engines: [no_silent_loss, no_corruption_on_escape]
scenarios:
- id: timestamp_pre_1970
what: "a pre-1970 sub-second timestamp renders its real value, never an empty cell — euclidean division so the negative sub-second remainder stays in range (round-7 HIGH; Parquet kept it, CSV dropped it). True oracle: hard-coded expected string, not the recomputed self-oracle that hid the bug."
no_silent_loss: { test: pre_1970_subsecond_timestamp_is_not_dropped_to_an_empty_cell }
no_corruption_on_escape: { na: "an ISO-8601 timestamp is `[0-9-T:.]` — no delimiter/quote can appear, so there is nothing to escape" }
- id: timestamp_naive_vs_instant
what: "a naive TIMESTAMP renders bare while an instant TIMESTAMPTZ (Timestamp(_, Some('UTC'))) gets a trailing `Z` — CSV kept the naive-vs-instant distinction rivet preserves everywhere else (Parquet isAdjustedToUTC, target types). Without the marker BOTH render identically, and a consumer loading the instant on a NON-UTC session reads the UTC wall-clock as local and shifts every value (the session-state class on the CSV encode leg). True oracle: hard-coded expected strings (bare vs `…Z`)."
no_silent_loss: { test: timestamp_marks_instant_utc_but_leaves_naive_bare }
no_corruption_on_escape: { na: "an ISO-8601 timestamp plus `Z` is `[0-9-T:.Z]` — no delimiter/quote can appear, nothing to escape" }
- id: negative_time_signed_duration
what: "MySQL TIME is a SIGNED duration (-838:59:59..838:59:59); the batch decoder emits negative micros. The old truncating `/`+`%` put a minus on EVERY field (`-1:-30:00`, `00:00:00.-00001`) — corrupt in every reader. One leading sign + magnitude formatting; a >24h duration still prints in full. True oracle: hard-coded expected strings."
no_silent_loss: { test: negative_time_renders_one_leading_sign_not_a_minus_per_field }
no_corruption_on_escape: { na: "an HH:MM:SS.ffffff duration (± prefix) is `[-0-9:.]` — no delimiter/quote can appear, nothing to escape" }
- id: float_non_finite
what: "NaN / Inf / -Inf emit an explicit literal, never an empty cell that reads back as NULL."
no_silent_loss: { test: float_special_values_emit_literals_not_empty }
no_corruption_on_escape: { na: "a float literal is `[0-9.eE+-]`/`NaN`/`inf` — no CSV metacharacter, nothing to escape" }
- id: null_vs_empty
what: "a NULL cell writes the empty field (the CSV convention) — pinned so a future change can't collide NULL with an empty string silently."
no_silent_loss: { test: null_value_writes_empty_string }
no_corruption_on_escape: { na: "the empty field has no content to escape" }
- id: binary_hex
what: "binary / bytea renders as lowercase hex (round-trippable), never lossy or as raw bytes that could contain a delimiter."
no_silent_loss: { test: binary_is_written_as_hex }
no_corruption_on_escape: { na: "hex output is `[0-9a-f]` only — it can never contain a comma/quote/newline, so no escaping is needed" }
- id: unsupported_type_fails_loud
what: "a type CSV cannot represent (nested list/struct/map) FAILS LOUD at writer construction, never a silently-skipped or mangled column."
no_silent_loss: { test: csv_rejects_array_columns_loudly }
no_corruption_on_escape: { na: "the run bails before any row is written — there is no cell to escape" }
- id: data_cell_delimiters
what: "a comma / double-quote / LF / CR inside a text cell is RFC-4180 quoted (quotes doubled) so it never splits or shifts the row — verified end-to-end against an INDEPENDENT DuckDB re-read, not the writer's own rendering."
no_silent_loss: { na: "the value is present in the cell; the risk here is column MISALIGNMENT (escaping), covered by the sibling column" }
no_corruption_on_escape: { test: batch_csv_export_complete_and_escaped_via_duckdb }
- id: header_column_name
what: "a column NAME containing a comma/quote (a curated-query alias `AS \"Amount, USD\"`) is RFC-4180 quoted in the header exactly like a data cell — round-7 caught the header emitted as a raw join(\",\"), splitting it off the data columns."
no_silent_loss: { na: "the header is metadata, not a data row; a mis-escaped header misaligns readers (corruption), it does not drop a value" }
no_corruption_on_escape: { test: csv_header_quotes_a_column_name_with_a_comma_or_quote }
- id: formula_injection
what: "a cell whose value begins with =/+/-/@ (spreadsheet formula triggers) is written verbatim — RFC-4180-faithful output."
no_silent_loss: { na: "the value is written faithfully and completely — no loss" }
no_corruption_on_escape: { na: "faithful RFC-4180 output is CORRECT, not corruption: a leading =/+/-/@ is not injection into rivet's own SQL/command/URI surface, only a downstream-spreadsheet concern (round-7 deferred by-design; documented spreadsheet-unsafe rather than silently prefixed, which would corrupt the real data)" }