Skip to content

fix(semantic): a replayed repair needs corroboration in the frame it lands on - #489

Merged
kevincostner17 merged 1 commit into
mainfrom
fix/memory-replay-uncorroborated
Sep 20, 2026
Merged

kevincostner17 merged 1 commit into
mainfrom
fix/memory-replay-uncorroborated

Conversation

@kevincostner17

Copy link
Copy Markdown
Contributor

Summary

A repair learned on one dataset was replayed onto another whose column meant something different, and applied automatically:

A: segment = M/F      ->  learns  M -> male
B: segment = S/M/L    ->  becomes S/male/L/male
                          status=automatic, human_review=False, confidence 0.95

Clothing sizes silently rewritten as genders. This is the trap the brief names directly: "Dataset A: M -> Male. Dataset B: M -> Medium. The library must not blindly replay Dataset A's decision into Dataset B."

Found by the Phase 16 lane (#481) and reproduced independently before fixing.

Root cause

The conflict machinery already existed and worked — _conflict_proposal turns a deterministic-vs-memory clash into a high-risk unsafe_ambiguous record that is never auto-applied. But it only fires on disagreement.

On B the CategorySynonymExpert correctly abstains (the column is not gender-like), and an abstention is not a disagreement:

det_list = det_by_key.get(key)
if not det_list:
    merged.append(mem_p)     # no deterministic proposal at all
    continue

Every other branch was careful — agreement kept the higher confidence, disagreement produced unsafe_ambiguous, flag-vs-repair was resolved deliberately. Only total silence had no handling, and silence is exactly what happens when the evidence behind a learned repair is absent from the new frame.

The stored signatures would not have caught it: both columns profile as role="categorical", semantic_type=None, free_text=False.

The fix

A replayed repair whose issue type is context-dependentcategory_synonym, boolean_synonym, reference_value, where the same token means different things in different columns — is demoted to a review-required suggestion when nothing in this frame corroborates it.

Context-free repairs (encoding_repair, format_alignment, numeric_format, …) are untouched: they mean the same thing in every column, so demoting them would cost the feature without buying any safety.

before after
sizes + gender memory S/male/L/male, auto-applied S/M/L kept — suggested / risk=high / human_review=True
genders + gender memory auto-applied auto-applied — the feature still works

Provenance survives the demotion, which matters more for a held decision than an applied one: memory_influenced, model_id ending :memory, backend="memory", evidence kinds memory_replay and a new memory_uncorroborated note carrying the reason.

Note for reviewers

This flips five tests that #481 pinned as current behaviour when it found the defect. They were tripwires for the fix and the fix tripped them; each now asserts the repaired behaviour with its history kept in the docstring.

Two findings in that file stay pinned because they remain true: freshdata_version is written and never read, and the SQLite store overwrites on a dataset_id collision.

This is the third pinned-defect collision in this programme (after #485 and #488) — worth grepping DEFECT / FINDING (S before landing any behaviour change.

Verification

  • 5 new tests; 4 fail on main, the one that passes being the feature-still-works control.
  • Full suite py3.12: 7098 passed, 20 skipped, 0 failed, coverage 94.94%.
  • ruff check . clean repo-wide.

…lands on

A repair learned on one dataset was replayed onto another whose column meant
something different, and applied automatically:

    A: segment = M/F      ->  learns  M -> male
    B: segment = S/M/L    ->  becomes S/male/L/male   (status=automatic,
                                                       human_review=False,
                                                       confidence 0.95)

Clothing sizes silently rewritten as genders.

The conflict machinery already existed and worked: _conflict_proposal turns a
deterministic-vs-memory clash into a high-risk unsafe_ambiguous record that is
never auto-applied. But it only fires on disagreement. On B the
CategorySynonymExpert correctly abstains -- the column is not gender-like --
and an abstention is not a disagreement, so _merge_proposals passed the
replayed repair through carrying the full confidence it earned elsewhere:

    det_list = det_by_key.get(key)
    if not det_list:
        merged.append(mem_p)     # no deterministic proposal at all
        continue

Every other branch was careful. Only total silence had no handling, and silence
is exactly what happens when the evidence behind a learned repair is absent.

The stored column_signature/value_signature would not have caught it: both
columns profile as role="categorical", semantic_type=None, free_text=False.

A replayed repair whose issue type is context-dependent -- category_synonym,
boolean_synonym, reference_value, where the same token means different things
in different columns -- is now demoted to a review-required suggestion when
nothing in this frame corroborates it. Context-free repairs (encoding_repair,
format_alignment, numeric_format and the rest) are untouched: they mean the
same thing in every column, so demoting them would cost the feature without
buying any safety.

    sizes + gender memory    S/M/L kept, suggested / risk=high / human_review
    genders + gender memory  still auto-applied -- the feature still works

Provenance survives the demotion, which matters more for a held decision than
an applied one: memory_influenced, model_id ending :memory, backend="memory",
evidence kinds memory_replay and a new memory_uncorroborated note carrying the
reason.

4 of the 5 new tests fail on main; the one that passes is the
feature-still-works control.

This also flips five tests that #481 pinned as current behaviour when it found
the defect. They were tripwires for the fix and the fix tripped them; each now
asserts the repaired behaviour with its history kept in the docstring. Two
findings in that file stay pinned because they remain true: freshdata_version
is written and never read, and the SQLite store overwrites on a dataset_id
collision.

Full suite 7098 passed / 0 failed, coverage 94.94%; ruff clean repo-wide.
@coderabbitai

coderabbitai Bot commented Sep 20, 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: 4aba5d8b-c60b-43c1-b517-687ba2ca9edc


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)

@kevincostner17
kevincostner17 merged commit 44ff836 into main Sep 20, 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

Development

Successfully merging this pull request may close these issues.

1 participant