Skip to content

fix(guard,explain): survive duplicate index labels and unhashable cells - #452

Merged
JohnnyWilson16 merged 1 commit into
mainfrom
fix/guard-explain-edge
Sep 16, 2026
Merged

JohnnyWilson16 merged 1 commit into
mainfrom
fix/guard-explain-edge

Conversation

@kevincostner17

Copy link
Copy Markdown
Contributor

Summary

Two crashes on frames that fd.clean and fd.profile otherwise accept, both found by fuzzing.

Root cause

Behaviour change

  • Guard, duplicate labels: when either index has repeated labels, the surviving values are checked positionally instead of by label. Row-level steps only ever drop rows, so every surviving value must still appear, in order, in the original column.
    • Still reported: a rewritten cell, reordered rows, gained rows, a dtype change and a dropped column.
    • Two missing values count as equal, as before.
    • A rewrite that happens to leave the values a valid in-order subsequence is the one case this check cannot distinguish. It is strictly better than the previous behaviour, which could not verify these frames at all.
  • Guard, unique labels: unchanged, still aligned by index label.
  • explain_clean: unhashable cells are counted by their text form, so [1], [1], {"k": 2} counts as 2 distinct values. Hashable columns are untouched.

Default-output changes

None for frames that already worked.

  • Frames with a duplicate index and a protected column now return the cleaned frame where they used to raise ProtectedColumnError. The guard action in the report is recorded as verified rather than violated.
  • explain_clean on a frame with list or dict cells now returns a report with a nunique count where it used to raise.
  • Unique-index frames and hashable columns produce byte-identical output.

Tests

  • tests/test_guard_protected.py:
    • the repro frame (duplicate labels, a protected column, drop_empty_rows) cleans and matches the unprotected result
    • _series_identical on a duplicate index still reports a rewritten cell, reordered rows and gained rows
    • two missing values compare equal, and a missing-to-value change is still reported
  • tests/test_explain.py: explain_clean on a frame with list and dict cells returns a report, counts 2 distinct payloads and still counts the null.

Verification

The protected-column guard compared its snapshot with
before.loc[after.index]. Repeated index labels make that reindex multiply
rows instead of selecting them, so once any row was dropped the comparison
could never match and fd.clean raised ProtectedColumnError on a column it
had not touched - telling the user to report an executor bug. Context
policies and mutable=False were therefore unusable on any frame with a
non-unique index. When either index has repeated labels the guard now
checks positionally: row-level steps only drop rows, so every surviving
value must still appear, in order, in the original column. Rewritten
cells, reordered rows and gained rows are still reported, and two missing
values still count as equal.

explain_clean counted distinct values with nunique(), which hashes every
value, so a list or dict cell raised TypeError although fd.clean and
fd.profile accept the same frame. Unhashable cells are now counted by
their text form, as profile already does.

Closes #449
Closes #450
@coderabbitai

coderabbitai Bot commented Sep 16, 2026

Copy link
Copy Markdown
Contributor

Important

  • 🔍 Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Advanced

Run ID: a20f3f88-c74f-4c75-ae87-0efa9bbef542


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions

Copy link
Copy Markdown

FreshData benchmark report — performance

  • freshdata: ?
  • python: ?
  • platform: ?
fixture n_rows n_cols p50 s p95 s peak MB repair % false-repair % preserve % trust monotonic export %

Authored-code reduction (Metric 6)

@JohnnyWilson16
JohnnyWilson16 merged commit e5bac8a into main Sep 16, 2026
22 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants