# Native Iceberg v2 delete experiments

Requires Java 17 and uv. Extract the archive, then run from its root (fish shell):

```fish
set -x SPARK_LOCAL_IP 127.0.0.1
uv run reproduce.py --source observations.csv --output . --verify
```

Set JAVA_HOME to your Java 17 installation if needed. First run downloads the pinned
Python dependencies, Apache Iceberg Java 1.9.2 Spark runtime and DuckDB iceberg extension.
The extension binary served for DuckDB 1.4.4 is installed by DuckDB, not vendored here.

The verifier hashes every original artifact. It then makes a temporary copy and relocates
the captured absolute file paths in metadata JSON, Avro manifests and position-delete
Parquet. Relocated manifest lengths/file sizes are updated. This derivative is necessary
for a portable Iceberg v2 local table: the original downloaded files remain unchanged.

Spark reads all ten snapshots without partition filters. Every field is compared with
the captured result and the source's eighteen original rows. It also checks a filtered
ID-only projection and a temperature-only projection requiring an internal equality key.
DuckDB compares complete rows and the warm predicate. For the station-partitioned branch,
it scans North and South explicitly and combines them; unrestricted DuckDB 1.4.4 and 1.5.2
incorrectly removed the North key for a South-only delete in this fixture. Spark's unrestricted
scan and the Iceberg specification establish the partition-scoped answer. No claim is made
that unrestricted DuckDB scans pass that case. DuckDB retains predicate/key columns to avoid
the equality-delete projection defect tracked at duckdb/duckdb-iceberg#940.

To generate fresh independent tables (fresh must not exist):

```fish
uv run reproduce.py --source observations.csv --output fresh
```

Files are written with PyArrow 25.0.1 and stable Parquet field IDs, then committed using the
real Apache Iceberg Java API. Spark SQL did not produce these delete files. File names,
snapshot IDs and timestamps will change. Full row assertions remain fixed.

Four alternate histories start with IDs 1-18: position delete then replacement append;
equality delete plus same-commit replacement; same-commit position delete; and wrong-then-right
partition-scoped equality delete. Each branch has one partition specification. These are
not v3 deletion vectors, concurrent writers, delete compaction or a general Iceberg reader.

proof.json includes all original manifest entries and inherited sequences. The browser
reads the original Parquet/Avro/JSON independently and derives row masks from those records.
