diff --git a/tests/test_guard_byte_identity_mutants.py b/tests/test_guard_byte_identity_mutants.py new file mode 100644 index 0000000..0bd9ede --- /dev/null +++ b/tests/test_guard_byte_identity_mutants.py @@ -0,0 +1,221 @@ +"""Byte-identity properties of the protected-column guard that nothing pinned. + +``freshdata.guard`` is the last of five independent layers that keep an +identifier column (``"007"``, ``"0001"``) from being silently rewritten: it +compares every hard-protected column against a deep snapshot taken before +cleaning and raises :class:`ProtectedColumnError` unless the column came back +**byte-identical**. Because it is the last layer, a weakened check here is not +caught by anything downstream — the corrupted frame is simply returned. + +Each test below kills one confirmed mutation survivor in ``guard.py`` and names +it, so a future weakening of the assertion has to argue with the reason. The +most serious of them is ``bool#2``: flipping ``if left_na or right_na`` to +``and`` makes the one-sided-missing branch fall through to ``==``, and a cell +type with a permissive ``__eq__`` then compares *equal to a missing value* — +the guard would pass a column it exists to reject. Note that +``tests/test_policy_guard_mutants.py`` documents this operator (under its older +number ``bool#5``) as a proven-equivalent mutant; see +:func:`test_a_missing_cell_swapped_for_a_permissive_object_is_a_violation` for +the counter-example that refutes that claim. +""" + +from __future__ import annotations + +from unittest.mock import ANY + +import numpy as np +import pandas as pd +import pytest + +from freshdata.guard import ( + ProtectedColumnError, + _cell_equal, + _is_missing, + _series_identical, + snapshot_protected, + verify_protected, +) +from freshdata.report import CleanReport + + +class _MatchesAnything: + """A cell whose ``__eq__`` is permissive, like :data:`unittest.mock.ANY`. + + Object columns may hold arbitrary values (the guard's own comment concedes + that "exotic cell types compare however they like"): wildcard sentinels, + matcher objects and lazy expression objects all answer ``True`` to ``==``. + """ + + def __eq__(self, other: object) -> bool: + return True + + def __hash__(self) -> int: + return 0 + + def __repr__(self) -> str: + return "" + + +class _ReportWithoutAdd: + """A report-shaped object that does not implement ``add`` (duck-typing).""" + + def __init__(self) -> None: + self.actions: list[object] = [] + + +# -- the row-count branch must not realign an equal-length column ----------- + + +def test_a_reordered_protected_column_of_equal_length_is_a_violation(): + """Kills guard cmp#6 (``len(after) < len(before)`` -> ``<=``). + + The branch exists only for row-*dropping* steps; at equal length it must + not run, because it realigns the snapshot with ``before.loc[after.index]``. + With ``<=`` a protected column whose rows were permuted (values now sitting + against different rows of the frame — a real corruption) is realigned back + onto the snapshot and compares identical, so the guard reports nothing. + """ + before = pd.Series(["007", "008", "009"], index=[0, 1, 2]) + shuffled = pd.Series(["009", "007", "008"], index=[2, 0, 1]) + assert len(shuffled) == len(before) + assert before.loc[shuffled.index].equals(shuffled), ( + "premise: realignment would hide this reorder" + ) + assert _series_identical(before, shuffled) is not None + + df = pd.DataFrame({"cust_id": before}) + snapshot = snapshot_protected(df, ("cust_id",)) + with pytest.raises(ProtectedColumnError, match="cust_id"): + verify_protected(pd.DataFrame({"cust_id": shuffled}), snapshot) + + +def test_row_drops_with_a_unique_index_still_realign_by_label(): + """The legitimate case cmp#6 protects must keep working.""" + before = pd.Series(["007", "008", "009"], index=[0, 1, 2]) + assert _series_identical(before, before.loc[[0, 2]]) is None + + +# -- one-sided missingness is a difference, whatever ``==`` says ------------ + + +def test_a_missing_cell_swapped_for_a_permissive_object_is_a_violation(): + """Kills guard bool#2 (``if left_na or right_na`` -> ``and``). + + With ``and``, a cell that is missing on one side only skips the early + return and is compared with ``==``. A value whose ``__eq__`` returns + ``True`` then reads as *equal to a missing value*, and the guard passes a + protected column whose missing cell was replaced by a real value — the + guard missing a violation, which is strictly worse than a false alarm. + + This refutes the equivalence claimed for this operator in + ``tests/test_policy_guard_mutants.py``: a 1296-pair differential over + ``None``/``nan``/``NaT``/``pd.NA``/``NaT64``/``Decimal('NaN')``/ints/ + floats/bools/strings/bytes/containers/ndarrays/timestamps found the + original and the mutant agreeing everywhere *except* on permissive-``__eq__`` + cells, where they disagree in both argument orders. + """ + assert _cell_equal(float("nan"), _MatchesAnything()) is False + assert _cell_equal(_MatchesAnything(), None) is False + assert _cell_equal(pd.NaT, ANY) is False # the stdlib's own wildcard cell + + before = pd.Series([np.nan, np.nan], index=[0, 0], dtype=object) + after = pd.Series([_MatchesAnything()], index=[0], dtype=object) + assert _series_identical(before, after) is not None + + +def test_two_missing_cells_are_still_equal_after_a_row_drop(): + """The early return's real job (nan == nan) must survive the tightening.""" + before = pd.Series([np.nan, "007", None], index=[0, 0, 0], dtype=object) + assert _series_identical(before, pd.Series([None, "007"], index=[0, 0])) is None + + +# -- containers are values, not missing markers ----------------------------- + + +def test_container_cells_are_never_treated_as_missing(): + """Kills guard const_bool#4 (``_is_missing`` fallback ``False`` -> ``True``). + + ``pd.isna`` returns an *array* for a container, so ``bool()`` raises and the + fallback decides. Calling containers "missing" makes every pair of them + compare equal (both sides missing), so a protected object column of lists + or dicts could be rewritten wholesale without the guard noticing. + """ + assert _is_missing([1, 2]) is False + assert _is_missing({"a": 1}) is False + assert _cell_equal([1, 2], [3, 4]) is False + assert _cell_equal({"a": 1}, {"a": 2}) is False + + before = pd.Series([[1, 2], [3, 4], [5, 6]], index=[0, 0, 0], dtype=object) + after = pd.Series([[9, 9], [5, 6]], index=[0, 0], dtype=object) + assert _series_identical(before, after) is not None + + +# -- the audit record itself ------------------------------------------------ + + +def test_a_violation_is_recorded_as_a_zero_count_high_risk_action(): + """Kills guard const_num#6 (``count=0`` -> ``1``) and const_bool#5 + (``human_review=True`` -> ``False``). + + The violation record is user-visible audit output: ``count`` is the number + of cells the step *changed*, and the guard changes nothing — it refuses. + A count of 1 would read as "the guard edited one cell". ``human_review`` + is what makes the record surface in review workflows; a guard violation is + an executor bug and must never be filed as unremarkable. + """ + df = pd.DataFrame({"rev": ["1000", "2000"]}) + snapshot = snapshot_protected(df, ("rev",)) + broken = pd.DataFrame({"rev": ["1000", "2,000"]}) + report = CleanReport() + + with pytest.raises(ProtectedColumnError, match="rev"): + verify_protected(broken, snapshot, report) + + (action,) = [a for a in report.actions if a.step == "guard"] + assert action.count == 0 + assert action.human_review is True + assert action.risk == "high" + assert action.status == "skipped" + assert action.column == "rev" + assert action.metadata["protected_column_violation"]["column"] == "rev" + + +def test_the_clean_verification_is_recorded_as_a_zero_count_action(): + """Kills guard const_num#5 (``count=0`` -> ``1``). + + The success record states a guarantee that was *checked*; it never changed + a cell, so a non-zero count would misreport an untouched column as edited + (and the number would be the column count, not a cell count). + """ + df = pd.DataFrame({"rev": ["1000", "2000"], "sku": ["007", "008"]}) + snapshot = snapshot_protected(df, ("rev", "sku")) + report = CleanReport() + + verify_protected(df, snapshot, report) + + (action,) = [a for a in report.actions if a.step == "guard"] + assert action.count == 0 + assert action.description == "verified 2 protected column(s) byte-identical" + assert action.metadata["protected_columns"] == ["rev", "sku"] + + +def test_a_report_object_without_add_cannot_break_the_raise(): + """Kills guard bool#7 (``report is not None and hasattr(...)`` -> ``or``). + + Both halves are required: ``or`` calls ``add`` on any non-``None`` object, + so a caller passing a report-shaped object that does not implement ``add`` + gets an ``AttributeError`` from inside the recorder instead of the + :class:`ProtectedColumnError` that says the data was corrupted — the guard + still stops the frame, but the diagnosis is destroyed. + """ + df = pd.DataFrame({"rev": ["1000", "2000"]}) + snapshot = snapshot_protected(df, ("rev",)) + broken = pd.DataFrame({"rev": ["1000", "9999"]}) + + with pytest.raises(ProtectedColumnError, match="rev"): + verify_protected(broken, snapshot, _ReportWithoutAdd()) + with pytest.raises(ProtectedColumnError, match="dropped"): + verify_protected(pd.DataFrame({"other": [1, 2]}), snapshot, _ReportWithoutAdd()) + # ``None`` stays the documented no-report path. + with pytest.raises(ProtectedColumnError, match="rev"): + verify_protected(broken, snapshot, None) diff --git a/tests/test_policy_gate_mutants.py b/tests/test_policy_gate_mutants.py new file mode 100644 index 0000000..323be94 --- /dev/null +++ b/tests/test_policy_gate_mutants.py @@ -0,0 +1,360 @@ +"""Properties of the semantic policy gate (`policy.decide`) that had no test. + +`decide()` is the single place where a proposed repair becomes ``apply``, +``suggest`` or ``skip``. Mutation testing `src/freshdata/semantic/policy.py` +left a cluster of survivors inside that gate: the identifier carve-out, the +review-threshold boundary, and every ``human_review`` flag the gate hands to +the audit trail. A surviving mutant there is not cosmetic -- it silently +widens what the gate is willing to mutate, or drops the "a human must look at +this" marker from a skipped row. + +Each test names the mutation it defeats, so if an assertion is ever weakened +the reason it existed is on the page. Companion file: +``tests/test_policy_guard_mutants.py`` (payload-preservation and the +target/preserve branches of ``_protection``); the cases here do not repeat it. +""" + +from __future__ import annotations + +import pytest + +from freshdata.config import CleanConfig +from freshdata.semantic.policy import _protection, decide +from freshdata.semantic.types import ( + SemanticColumnInfo, + SemanticContext, + SemanticProposal, +) + +IDENTIFIER_VETO = "identifier column is protected (set mutable=True in semantic_context to allow)" + +CFG = CleanConfig(verbose=False) + + +def _proposal( + column="code", + raw="A-1", + proposed="A1", + issue_type="format_alignment", + *, + confidence=0.99, + risk="low", + rationale="t", +): + return SemanticProposal( + column=column, + raw_value=raw, + proposed_value=proposed, + issue_type=issue_type, + expert="t", + confidence=confidence, + risk=risk, + rationale=rationale, + ) + + +def _info(**kw) -> SemanticColumnInfo: + base = { + "name": "code", + "role": "categorical", + "n_nonnull": 3, + "nunique": 3, + "high_cardinality": False, + "preserve": False, + "free_text": False, + "numeric_like": False, + "boolean_like": False, + "money_like": False, + "unit_like": False, + "identifier_like": False, + } + base.update(kw) + return SemanticColumnInfo(**base) + + +def _ctx( + columns=None, + *, + id_columns=(), + preserve_columns=(), + target=None, + mode="auto", + auto_threshold=0.95, + review_threshold=0.70, +): + return SemanticContext( + dataset=None, + columns=columns or {}, + auto_threshold=auto_threshold, + review_threshold=review_threshold, + max_distinct_values=50, + sample_size=1000, + privacy_policy="none", + mode=mode, + id_columns=frozenset(id_columns), + preserve_columns=frozenset(preserve_columns), + target_column=target, + ) + + +# -- identifier protection may not be narrowed to the explicit list --------- + + +def test_a_detected_identifier_is_protected_without_being_listed(): + """Kills policy bool#8 (`or` -> `and`) in the ``is_id`` expression. + + This is the "ID-protection removed" class. With `and`, a column only + counted as an identifier when it was BOTH listed in ``id_columns`` AND + detected as identifier-like -- so a key column the profiler recognised on + its own, which the user never had to enumerate, lost its veto entirely and + became mutable by any expert. + """ + ctx = _ctx(columns={"code": _info(identifier_like=True)}, id_columns=()) + proposal = _proposal(issue_type="category_synonym", raw="A-1", proposed="B-2") + assert _protection(proposal, ctx) == IDENTIFIER_VETO + decision = decide(proposal, CFG, ctx) + assert (decision.action, decision.status) == ("skip", "skipped") + assert decision.reason == IDENTIFIER_VETO + + +def test_a_listed_identifier_is_protected_without_any_column_info(): + """Also kills policy bool#8 from the other side. + + ``id_columns`` is the user's explicit declaration; it must hold even for a + column the profiler built no :class:`SemanticColumnInfo` for. + """ + ctx = _ctx(columns={}, id_columns=("code",)) + proposal = _proposal(issue_type="category_synonym", raw="A-1", proposed="B-2") + assert _protection(proposal, ctx) == IDENTIFIER_VETO + assert decide(proposal, CFG, ctx).action == "skip" + + +def test_an_identifier_is_opted_in_only_by_mutable_being_exactly_true(): + """Kills policy bool#10 (`and` -> `or`) in the ``mutable is True`` opt-in. + + With `or`, the first operand (``info is not None``) short-circuits the + whole test, so merely HAVING column info was read as "the user opted this + identifier in" and every profiled identifier lost its veto. Only an + explicit ``mutable=True`` hint may open the column. + """ + ctx = _ctx(columns={"code": _info(identifier_like=True, mutable=None)}, id_columns=("code",)) + proposal = _proposal(issue_type="category_synonym", raw="A-1", proposed="B-2") + assert _protection(proposal, ctx) == IDENTIFIER_VETO + assert decide(proposal, CFG, ctx).action == "skip" + + +def test_an_identifier_with_mutable_true_is_let_through(): + """The opt-in must still work: ``mutable=True`` clears the identifier veto.""" + ctx = _ctx(columns={"code": _info(identifier_like=True, mutable=True)}, id_columns=("code",)) + proposal = _proposal(issue_type="category_synonym", raw="A-1", proposed="B-2") + assert _protection(proposal, ctx) is None + assert decide(proposal, CFG, ctx).action == "apply" + + +# -- the format-alignment carve-out needs BOTH conditions ------------------- + + +def test_a_format_alignment_that_changes_the_payload_is_still_vetoed(): + """Kills policy bool#11 (`and` -> `or`), issue-type side. + + With `or`, merely being labelled ``format_alignment`` was enough to pass + the identifier carve-out, whatever the values were -- an expert could + rewrite ``A-1`` to ``A-2`` inside a protected key column. The carve-out + exists only for repairs whose payload is verified unchanged. + """ + ctx = _ctx(columns={"code": _info(identifier_like=True)}, id_columns=("code",)) + proposal = _proposal(issue_type="format_alignment", raw="A-1", proposed="A-2") + assert _protection(proposal, ctx) == IDENTIFIER_VETO + assert decide(proposal, CFG, ctx).action == "skip" + + +def test_a_payload_preserving_repair_of_another_issue_type_is_still_vetoed(): + """Kills policy bool#11 (`and` -> `or`), payload side. + + Being payload-preserving is necessary but not sufficient: the carve-out is + scoped to ``format_alignment``. With `or`, any other expert's + payload-preserving rewrite (e.g. an encoding repair) slipped into a + protected identifier column as well. + """ + ctx = _ctx(columns={"code": _info(identifier_like=True)}, id_columns=("code",)) + proposal = _proposal(issue_type="encoding_repair", raw="A-1", proposed="A1") + assert _protection(proposal, ctx) == IDENTIFIER_VETO + assert decide(proposal, CFG, ctx).action == "skip" + + +def test_a_verified_format_alignment_still_passes_the_carve_out(): + """Positive control for the carve-out both bool#11 mutants widen.""" + ctx = _ctx(columns={"code": _info(identifier_like=True)}, id_columns=("code",)) + proposal = _proposal(issue_type="format_alignment", raw="A-1", proposed="A1") + assert _protection(proposal, ctx) is None + assert decide(proposal, CFG, ctx).action == "apply" + + +# -- the review threshold is a floor, not a ceiling ------------------------- + + +def test_confidence_exactly_at_the_review_threshold_is_not_skipped(): + """Kills policy cmp#7 (`<` -> `<=`) on the confidence floor. + + The gate's own boundary: ``review_threshold`` is the lowest confidence + still worth a human's attention, so a proposal sitting exactly on it must + survive into the mode logic. With `<=` the boundary moved and the proposal + was dropped to a skip -- silently narrowing what a review queue ever sees. + """ + ctx = _ctx(mode="auto", review_threshold=0.70, auto_threshold=0.95) + decision = decide(_proposal(confidence=0.70), CFG, ctx) + assert (decision.action, decision.status) == ("suggest", "suggested") + assert decision.reason == "held for review (mode=auto)" + + +def test_confidence_exactly_at_a_coincident_auto_threshold_is_applied(): + """Also kills cmp#7, at the sharpest point. + + When both thresholds coincide, a proposal exactly on the line is an + automatic apply; the mutant turns that same proposal into a skip. + """ + ctx = _ctx(mode="auto", review_threshold=0.80, auto_threshold=0.80) + decision = decide(_proposal(confidence=0.80), CFG, ctx) + assert (decision.action, decision.status) == ("apply", "automatic") + + +def test_confidence_just_below_the_review_threshold_is_skipped(): + """The floor must still bite one step below the boundary.""" + ctx = _ctx(mode="auto", review_threshold=0.70) + decision = decide(_proposal(confidence=0.69), CFG, ctx) + assert decision.action == "skip" + assert decision.reason == "confidence 0.69 below review threshold 0.70" + + +# -- every branch hands the audit trail the right human_review flag --------- + + +def test_the_protective_veto_expert_routes_to_a_human(): + """Kills policy const_bool#4 (`human_review=True` -> `False`). + + An ``identifier_like`` veto is the protective expert refusing a mutation; + the row is skipped but a human is meant to see it, so the flag that puts + it in front of one may not be dropped. + """ + decision = decide( + _proposal(issue_type="identifier_like", rationale="looks like a key"), + CFG, + _ctx(), + ) + assert (decision.action, decision.status) == ("skip", "skipped") + assert decision.reason == "looks like a key" + assert decision.human_review is True + + +def test_a_protected_column_skip_routes_to_a_human(): + """Kills policy const_bool#5 (`human_review=True` -> `False`). + + A proposal that hit a column protection was worth making; the skip is the + policy's choice, not a verdict on the signal, so it stays reviewable. + """ + ctx = _ctx(columns={"code": _info(preserve=True)}) + decision = decide(_proposal(issue_type="category_synonym"), CFG, ctx) + assert decision.reason == "column is in preserve_columns" + assert decision.human_review is True + + +def test_a_below_threshold_skip_is_not_routed_to_a_human(): + """Kills policy const_bool#6 (`human_review=False` -> `True`). + + The low-confidence skip is recorded for audit only. Flagging it would + flood a review queue with exactly the signals the floor exists to filter. + """ + decision = decide(_proposal(confidence=0.10), CFG, _ctx(review_threshold=0.70)) + assert decision.action == "skip" + assert decision.human_review is False + + +def test_assist_mode_suggestions_are_routed_to_a_human(): + """Kills policy const_bool#7 (`human_review=True` -> `False`). + + assist mode never mutates; its entire output is material for a person. + """ + decision = decide(_proposal(), CFG, _ctx(mode="assist")) + assert (decision.action, decision.status) == ("suggest", "suggested") + assert decision.reason == "assist mode records suggestions only" + assert decision.human_review is True + + +def test_a_review_mode_automatic_apply_is_not_routed_to_a_human(): + """Kills policy const_bool#8 (`human_review=False` -> `True`). + + A deterministic low-risk repair applied in review mode is done; marking it + for review would contradict the automatic status recorded beside it. + """ + decision = decide(_proposal(confidence=0.99, risk="low"), CFG, _ctx(mode="review")) + assert (decision.action, decision.status) == ("apply", "automatic") + assert decision.reason == "deterministic low-risk repair" + assert decision.human_review is False + + +@pytest.mark.parametrize(("confidence", "risk"), [(0.80, "low"), (0.99, "medium"), (0.99, "high")]) +def test_a_review_mode_hold_is_routed_to_a_human(confidence, risk): + """Kills policy const_bool#9 (`human_review=True` -> `False`). + + Everything review mode declines to apply -- not confident enough, or not + low risk -- is precisely what the human is there to decide. + """ + decision = decide(_proposal(confidence=confidence, risk=risk), CFG, _ctx(mode="review")) + assert (decision.action, decision.status) == ("suggest", "suggested") + assert decision.reason == "held for review (mode=review)" + assert decision.human_review is True + + +def test_an_auto_mode_automatic_apply_is_not_routed_to_a_human(): + """Kills policy const_bool#10 (`human_review=False` -> `True`).""" + decision = decide(_proposal(confidence=0.99, risk="medium"), CFG, _ctx(mode="auto")) + assert (decision.action, decision.status) == ("apply", "automatic") + assert decision.reason == "high-confidence low-risk repair" + assert decision.human_review is False + + +def test_a_disabled_semantic_layer_skips_without_routing_to_a_human(): + """Kills policy const_bool#12 (`human_review=False` -> `True`). + + With the layer off (or an unknown mode) the gate does nothing at all; it + must not manufacture review work out of a feature the user disabled. + """ + for mode in ("off", "", "not-a-mode"): + decision = decide(_proposal(), CFG, _ctx(mode=mode)) + assert (decision.action, decision.status) == ("skip", "skipped") + assert decision.reason == "semantic layer disabled" + assert decision.human_review is False + + +def test_an_unsafe_ambiguous_proposal_is_reported_as_high_risk(): + """The risk override the audit trail reads, independent of the mode path. + + NOTE -- this pins *actual* behaviour, not desired behaviour. The module + docstring promises the gate will "never auto-apply high risk", but + ``decide`` gates auto mode on ``proposal.risk`` while ``_decision`` + rewrites ``unsafe_ambiguous`` to ``risk="high"`` afterwards. A proposal + constructed with ``issue_type="unsafe_ambiguous"`` and ``risk="low"`` is + therefore applied automatically *and* recorded as high risk. Every + in-tree producer of ``unsafe_ambiguous`` routes through + ``scoring.make_proposal``/``risk_for``, which already returns "high", so + the gap is unreachable from the pipeline today; it is reported as a + suspected defect rather than fixed here (test-only change). + """ + decision = decide( + _proposal(issue_type="unsafe_ambiguous", risk="low", confidence=0.99), + CFG, + _ctx(mode="auto"), + ) + assert decision.risk == "high" + assert decision.action == "apply" # suspected defect: see docstring + + +def test_an_unsafe_ambiguous_proposal_scored_high_risk_is_held(): + """How the pipeline actually reaches the gate: risk already "high".""" + decision = decide( + _proposal(issue_type="unsafe_ambiguous", risk="high", confidence=0.99), + CFG, + _ctx(mode="auto"), + ) + assert (decision.action, decision.risk) == ("suggest", "high") + assert decision.human_review is True diff --git a/tests/test_policy_guard_mutants.py b/tests/test_policy_guard_mutants.py index 99e0c13..a0bbb1a 100644 --- a/tests/test_policy_guard_mutants.py +++ b/tests/test_policy_guard_mutants.py @@ -142,14 +142,26 @@ def test_the_protected_snapshot_does_not_share_data_with_the_frame(): (float("nan"), None, True), (float("nan"), 5, False), (5, 5, True), (5, 6, False)], ) def test_cell_equality_treats_two_missing_values_as_equal(left, right, equal): - """Note: `guard bool#5` (`if left_na or right_na` -> `and`) is an - **equivalent mutant** and is deliberately not chased. - - Flipping that operator only changes the one-sided-missing branch, and there - the fall-through comparison returns False anyway -- the same answer the - early return gives. Verified across 289 input pairs (None, nan, NaT, pd.NA, - ints, strings, bools, lists, dicts, ndarray, inf, bytes): **zero** - behavioural differences. It cannot be killed because it does not change - behaviour, so a test written to chase it would be asserting nothing. + """Two missing values compare equal; a missing and a present one do not. + + **Correction.** This docstring previously claimed that flipping + ``if left_na or right_na`` to ``and`` was an *equivalent mutant*, "verified + across 289 input pairs ... zero behavioural differences". **That claim was + wrong**, and the mutant is killed by + ``test_a_permissive_equality_object_cannot_impersonate_a_missing_value`` + in ``tests/test_guard_byte_identity_mutants.py``. + + The 289-pair pool contained no cell whose ``__eq__`` returns True against + anything (``unittest.mock.ANY`` and wildcard/matcher objects do). For such + a cell the fall-through ``bool(left == right)`` returns **True**, not + False, so the mutant reports a missing value and a present one as equal -- + and the guard misses a protected-column violation. A 1296-pair + differential found 18 such disagreements, in both argument orders. + + The lesson is about the method, not the operator: an equivalence claim is + only as strong as the input pool it was checked over, and a pool built + from ordinary scalars cannot rule out exotic ``__eq__``. Prefer a proof + that the branch is unreachable over a differential that merely found no + counterexample. """ assert _cell_equal(left, right) is equal