You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Follow-up to the audit #387 asked for and #390 never did catalog-wide ("Worth auditing whether other hex assets built by the same step have the same defect"). Surfaced again while building #506.
The defect
The vector H3 polyfill emitted one row per (feature, cell, geometry part) instead of per (feature, cell), so any MultiPolygon whose parts share a cell produced byte-identical duplicate rows. COUNT(*), SUM(...), and SUM(h3_cell_area(...)) over such a hex asset all over-count.
This is already fixed upstream — boettiger-lab/datasets#150, PR #158, fix(#150): de-duplicate (feature, cell) rows in vector hex output, merged 2026-07-12 (Pass 2 now writes SELECT DISTINCT *). There is no tool bug to file and nothing to change in cng-datasets. The remaining problem is purely stale artifacts: every vector hex built before 2026-07-12 still carries the duplication.
Verified, not inferred
Ran the tool at :latest on the cluster with the exact production parameters of the conserved-areas hex job (--chunk-size 1000 --intermediate-chunk-size 10 --resolution 10 --parent-resolutions 9,8,0), chunk 4 (1,000 features):
rows
duplicate (feature, cell) rows
Tool :latest, same params, same input
26,752
0
Published data for those same 1,000 features
26,889
137
Minimal reproducer of the mechanism — _cng_fid = 4661, "City Hall Park", a 2-part MultiPolygon of 9 total vertices covering 0.7 acres, both parts inside one res-10 cell. Pass 1 emits 2 rows ("Processing 2 rows in 2 batches"); the post-fix Pass 2 collapses them to 1. The published asset has 2.
Confirmed affected
Collection
Hex built
rows
duplicate (feature, cell) rows
ca30x30-conserved-areas-terrestrial-2025
2026-06-18
13,340,094
70,806 (0.53%)
padus-4-1/fee
2026-02-15
609,968,495
160,293 (0.026%)
ca30x30-ecoregion
rebuilt 2026-08-05
25,596,931
0 — fixed by the #507 res-10 rebuild on the post-fix tool
Worst single case in conserved-areas: 221 copies of one unit on a single res-10 cell (Black Mountain Ranch Open Space, 1,492 parts → 86 copies on one cell). Duplication scales with part count, so parcel/BLM-style multipart inventories are the exposed class; single-part polygons are unaffected.
Why it matters beyond a row count
_cng_fid is documented as the universal per-feature dedup key, and the hex assets STAC states "one row per (feature, h10) pair". Neither holds on a pre-fix build, so the guidance in AGENTS.md ("dedup by _cng_fid") does **not** rescue a consumer here — COUNT(DISTINCT _cng_fid)is fine, butCOUNT(*)and any per-cellSUMare inflated, andaudit-feature-dup.py --key _cng_fid` reports the asset as clean because it audits upstream (axis-2) duplication, not build artifacts.
The #506hex-weights build dedups (cell, unit) explicitly before aggregating, so the published per-cell GAP fractions are correct and this issue does not block them. But it silently corrupted the issue`s own premise: the reported "max total weight 222" was 221 duplicate rows, not 222 overlapping units (real max: 16).
Proposed scope
Enumerate. For every vector hex asset in the catalog, read the h0=*/data_0.parquet mtime (rclone metadata listing, not a scan). Anything ≥ 2026-07-12 is clean by construction; only earlier builds are suspect.
Measure each suspect: COUNT(*) - COUNT(DISTINCT (h<native>, _cng_fid)) via the duckdb-geo MCP.
Remediate each affected asset with a SELECT DISTINCT * rewrite to a staging prefix → validate row delta + unchanged COUNT(DISTINCT h<native>) → purge-verify-empty → flip. This is the fix(#387,#389): dedup ca30x30 ecoregion hex + exact h3_cell_area area recipe #390ecoregion-dedup.yaml pattern; it is far cheaper than a full re-hex and is provably equivalent, because the duplicates are byte-identical. A full re-hex is only needed if an asset also predates another polyfill fix (e.g. the transmeridian split, datasets#145, 2026-07-10 — relevant to global layers).
Verifyrows == COUNT(DISTINCT (h<native>, _cng_fid)) afterwards, and consider adding that as a verify-stac.py data-backed check so a stale build cannot pass the gate silently.
REQUIRED: audit everything published before the gate existed
Scope amendment (2026-08-05). This issue is not complete when the known-affected assets are
fixed — it requires a systematic audit of all data predating the CI gate, because the two
defect classes found so far were both invisible for the same structural reason, not by chance:
The gate is PR-triggered on changed catalog/** YAMLs. That is deliberate — gate at
creation, not a periodic sweep — but the corollary is that any collection published before a
check existed is never retro-verified unless someone happens to touch its recipe. An
S3-only STAC edit triggers nothing.
So the pre-gate cohort has never been evaluated by the current rule set at all. Two confirmed
consequences already: the duplicate rows in this issue, and #511 (WDPA NO_TAKE = 'All ' with a
trailing space, which silently empties WHERE NO_TAKE = 'All').
Required work: run the current verify-stac.py (full, data-backed) against every collection
published before the gate — not just the row-uniqueness check — and triage every HARD finding.
Expect false positives from checks meeting old data for the first time: two were already found
(NULL-native hex columns fabricating 77,991,738 WDPA duplicates; a legitimate None category
discarded as a missing-value token, #511). Fix the checks as well as the data — a check that
misfires on the pre-gate cohort will misfire on new datasets too.
Acceptance for this half: every collection has been run against the current verifier at least
once, with each HARD finding either remediated or explicitly dismissed with a reason.
Acceptance: every vector hex asset in the catalog satisfies COUNT(*) == COUNT(DISTINCT (h<native>, _cng_fid)), except where a genuine multi-feature-per-cell case is documented on the asset.
Follow-up to the audit #387 asked for and #390 never did catalog-wide ("Worth auditing whether other hex assets built by the same step have the same defect"). Surfaced again while building #506.
The defect
The vector H3 polyfill emitted one row per (feature, cell, geometry part) instead of per (feature, cell), so any MultiPolygon whose parts share a cell produced byte-identical duplicate rows.
COUNT(*),SUM(...), andSUM(h3_cell_area(...))over such a hex asset all over-count.This is already fixed upstream —
boettiger-lab/datasets#150, PR #158,fix(#150): de-duplicate (feature, cell) rows in vector hex output, merged 2026-07-12 (Pass 2 now writesSELECT DISTINCT *). There is no tool bug to file and nothing to change incng-datasets. The remaining problem is purely stale artifacts: every vector hex built before 2026-07-12 still carries the duplication.Verified, not inferred
Ran the tool at
:lateston the cluster with the exact production parameters of the conserved-areas hex job (--chunk-size 1000 --intermediate-chunk-size 10 --resolution 10 --parent-resolutions 9,8,0), chunk 4 (1,000 features)::latest, same params, same inputMinimal reproducer of the mechanism —
_cng_fid = 4661, "City Hall Park", a 2-part MultiPolygon of 9 total vertices covering 0.7 acres, both parts inside one res-10 cell. Pass 1 emits 2 rows ("Processing 2 rows in 2 batches"); the post-fix Pass 2 collapses them to 1. The published asset has 2.Confirmed affected
ca30x30-conserved-areas-terrestrial-2025padus-4-1/feeca30x30-ecoregionWorst single case in conserved-areas: 221 copies of one unit on a single res-10 cell (
Black Mountain Ranch Open Space, 1,492 parts → 86 copies on one cell). Duplication scales with part count, so parcel/BLM-style multipart inventories are the exposed class; single-part polygons are unaffected.Why it matters beyond a row count
_cng_fidis documented as the universal per-feature dedup key, and the hex assets STAC states "one row per (feature, h10) pair". Neither holds on a pre-fix build, so the guidance in AGENTS.md ("dedup by_cng_fid") does **not** rescue a consumer here —COUNT(DISTINCT _cng_fid)is fine, butCOUNT(*)and any per-cellSUMare inflated, andaudit-feature-dup.py --key _cng_fid` reports the asset as clean because it audits upstream (axis-2) duplication, not build artifacts.The #506
hex-weightsbuild dedups(cell, unit)explicitly before aggregating, so the published per-cell GAP fractions are correct and this issue does not block them. But it silently corrupted the issue`s own premise: the reported "max total weight 222" was 221 duplicate rows, not 222 overlapping units (real max: 16).Proposed scope
h0=*/data_0.parquetmtime (rclone metadata listing, not a scan). Anything ≥ 2026-07-12 is clean by construction; only earlier builds are suspect.COUNT(*) - COUNT(DISTINCT (h<native>, _cng_fid))via the duckdb-geo MCP.SELECT DISTINCT *rewrite to a staging prefix → validate row delta + unchangedCOUNT(DISTINCT h<native>)→ purge-verify-empty → flip. This is the fix(#387,#389): dedup ca30x30 ecoregion hex + exact h3_cell_area area recipe #390ecoregion-dedup.yamlpattern; it is far cheaper than a full re-hex and is provably equivalent, because the duplicates are byte-identical. A full re-hex is only needed if an asset also predates another polyfill fix (e.g. the transmeridian split, datasets#145, 2026-07-10 — relevant to global layers).rows == COUNT(DISTINCT (h<native>, _cng_fid))afterwards, and consider adding that as averify-stac.pydata-backed check so a stale build cannot pass the gate silently.REQUIRED: audit everything published before the gate existed
Scope amendment (2026-08-05). This issue is not complete when the known-affected assets are
fixed — it requires a systematic audit of all data predating the CI gate, because the two
defect classes found so far were both invisible for the same structural reason, not by chance:
verify-stac.pyand its CI gate landed 2026-06-24 (Enforce STAC correctness via a verify-stac CI gate on the recipe PR (replace per-defect-class manual audits) #302/verify-stac.py: enforce the STAC-correctness check suite via CI (#302) #306); individual checks landedlater still (the
values-vs-DISTINCT check 2026-06-26, this issue's row-uniqueness check2026-08-05).
catalog/**YAMLs. That is deliberate — gate atcreation, not a periodic sweep — but the corollary is that any collection published before a
check existed is never retro-verified unless someone happens to touch its recipe. An
S3-only STAC edit triggers nothing.
So the pre-gate cohort has never been evaluated by the current rule set at all. Two confirmed
consequences already: the duplicate rows in this issue, and #511 (WDPA
NO_TAKE = 'All 'with atrailing space, which silently empties
WHERE NO_TAKE = 'All').Required work: run the current
verify-stac.py(full, data-backed) against every collectionpublished before the gate — not just the row-uniqueness check — and triage every HARD finding.
Expect false positives from checks meeting old data for the first time: two were already found
(NULL-native hex columns fabricating 77,991,738 WDPA duplicates; a legitimate
Nonecategorydiscarded as a missing-value token, #511). Fix the checks as well as the data — a check that
misfires on the pre-gate cohort will misfire on new datasets too.
Acceptance for this half: every collection has been run against the current verifier at least
once, with each HARD finding either remediated or explicitly dismissed with a reason.
Acceptance: every vector hex asset in the catalog satisfies
COUNT(*) == COUNT(DISTINCT (h<native>, _cng_fid)), except where a genuine multi-feature-per-cell case is documented on the asset.