Skip to content

test(reconcile): assert the move cutoff's behaviour, not only its value (#673) - #708

Draft
willhea wants to merge 1 commit into
developfrom
worktree-issue673-move-cutoff-boundary
Draft

test(reconcile): assert the move cutoff's behaviour, not only its value (#673)#708
willhea wants to merge 1 commit into
developfrom
worktree-issue673-move-cutoff-boundary

Conversation

@willhea

@willhea willhea commented Aug 25, 2026

Copy link
Copy Markdown
Collaborator

Closes #673, via options 1 and 2.

The problem

DeltaTrack decides whether a provision moved or was deleted and replaced by comparing word similarity against MOVE_THRESHOLD = 0.6. One test asserted the constant is 0.6. Ten behaviour tests checked pairs, but the nearest sat at 0.6667 (11.1% clear) and both low-side cases scored 0.0000 — zero word overlap, so each would pass for any cutoff in (0, 1].

The number was pinned. The behaviour the number exists to produce was not.

The near-threshold population, re-derived

#673 assumed real near-boundary corpus pairs existed but had not checked. Re-measured here by scoring the unmatched population of all 27 committed manifest pairs through production's own round-1 → round-2 handoff, with a probe bound below 0.6 so the region either side is visible rather than truncated at it:

Band Pairs
exactly 0.6 12
[0.58, 0.60) 38
[0.60, 0.62) 36

So the fixtures are real, not composed to hit a number. That matters because a fixture written to reach a similarity encodes the author's belief about the scorer and then tests the belief. Both are quoted from named bills so a reader can re-measure rather than trust.

What was added

1. test_a_pair_at_exactly_the_cutoff_is_moved — two short-title provisions from 118-hr-4366 measuring exactly 0.6, where >= MOVE_THRESHOLD is True and > MOVE_THRESHOLD is False. It is the only fixture that can see the direction of a comparison MOVE_THRESHOLD's own comment describes as "at or above".

2. test_a_pair_just_below_the_cutoff_stays_separate — two effective-date provisions from 119-hr-1 at 0.5957, which is 0.0043 below the cutoff against the existing 0.0000.

3. test_the_audited_pair_keeps_its_classification_split — see below, because it is deliberately not what option 2 literally proposes.

Option 2, re-aimed

Reading the existing gate closely: assert actual == expected already asserts the summary counts, and the failure message already prints moved: 163 -> 161. So "assert the counts as well as the digest" was substantially already done, and building it again would have been inert.

The real gap is the one #673's own framing points at: every value the digest gate compares against lives in a regenerable file, and its remedy line says to regenerate.

The figures in the new test are literals in the test source. UPDATE_BASELINE=1 cannot move them; only a person editing that file can, which appears in a diff and can be argued with in review. Detection is shared with the digest gate — attribution and remedy are not.

The full split is pinned rather than moved alone, because a lost move is conserved rather than destroyed: it becomes one removal plus one addition.

Verification

Each mutation applied and reverted, tree confirmed clean afterwards.

Mutation 2 (short-circuit prunes 2% too aggressively; MOVE_THRESHOLD untouched) reddens the at-the-cutoff case. Before this change it left the entire fast tier green.

A note on orientation, since it cost a cycle: the effect #673 describes needs < threshold * 1.02. Multiplying the other operand prunes less aggressively, and the boundary test correctly stayed green under that — which looked like a failed fix until the bound values were measured. The chosen fixture's quick_ratio is exactly 0.6, so it sits inside the window the correct mutation prunes; 10 of the 12 exact-0.6 corpus pairs do.

Mutation 2, then regenerating the baseline — the result that justifies test 3:

digest gate (27 assertions) new audited-pair test
mutation applied 4 fail fails
mutation applied, UPDATE_BASELINE=1 run all 27 pass still fails

The digest gate goes green after exactly the remedy its own failure message recommends, while the behavioural regression is still present. The new test is the sole survivor.

Mutation 1 (constant 0.6 → 0.45) still reddens the existing constant pin, so the additions do not replace coverage that already works. It also reddens both new cases, and the split is deliberate: the at-the-cutoff one fails on its precondition saying the fixture needs re-measuring, and the below-cutoff one fails on the behaviour saying a pair below MOVE_THRESHOLD was reconciled as a move.

Gates. Fast suite 2011 passed / 4 skipped / 15 xfailed; the three affected modules 69 passed / 1 skipped; ruff check and ruff format --check clean.

Caveat: measured in a worktree lacking the gitignored fetched corpus, which reports 4 skips where the main checkout reports 1. None relate to this change. The corpus figures above come from the committed manifest, which that gap does not touch.

Redundancy, flagged rather than acted on

The house rule says to check whether a new test makes an older one redundant. Two candidates, which I have not removed because deleting coverage was not part of the ask:

  • test_dead_zone_pair_becomes_moved (0.6667) is now largely dominated. Any threshold raise that reddens it also reddens the at-the-cutoff case, which fires strictly earlier. It still carries the 0.4 → 0.7 → 0.6 history in its docstring, which is the main reason to keep it.
  • test_below_threshold_unchanged and test_low_similarity_stays_separate both sit at 0.0000 and are redundant with each other before this change; the new below-cutoff case dominates both.

Happy to drop any of these in a follow-up if you agree.

Still open from #673

  • Whether SIMILARITY_THRESHOLD (0.4, the round-1 correspondence cutoff) has the same gap. This change measured the move cutoff only.
  • Whether round 1 has an equivalent gap. Its structure was read, not mutation-tested.

…ue (#673)

The move/no-move decision compares word similarity against MOVE_THRESHOLD (0.6).
A test pinned the constant; nothing tested a pair near it. The nearest case sat
at 0.6667, 11.1% clear on the high side, and both low-side cases scored 0.0000 --
zero word overlap, so each would pass for any cutoff in (0, 1]. The number was
pinned and the behaviour the number exists to produce was not.

Three tests, all against REAL corpus text rather than fixtures composed to hit a
number. The near-threshold population was re-derived here by scoring the unmatched
population of all 27 committed manifest pairs through production's own round-1 ->
round-2 handoff: 12 pairs land on exactly 0.6, 38 in [0.58, 0.60), 36 in
[0.60, 0.62). A fixture written to reach a similarity encodes the author's belief
about the scorer and then tests the belief; these are quoted from named bills so a
reader can re-measure.

  - test_a_pair_at_exactly_the_cutoff_is_moved. Two short-title provisions from
    118-hr-4366 measuring exactly 0.6, where `>= MOVE_THRESHOLD` is True and
    `> MOVE_THRESHOLD` is False. It is the only fixture that can see the direction
    of a comparison MOVE_THRESHOLD's own comment describes as "at or above".
  - test_a_pair_just_below_the_cutoff_stays_separate. Two effective-date
    provisions from 119-hr-1 at 0.5957, 0.0043 below the cutoff against the
    existing 0.0000.
  - test_the_audited_pair_keeps_its_classification_split, in
    test_canonical_baseline.py.

That third one is deliberately NOT what #673's option 2 literally proposes. The
digest gate already asserts the summary counts and already prints the moved delta
on failure, so "assert the counts too" was substantially already done. The real
gap is that every value it compares against lives in a regenerable file and its
own remedy line says to regenerate. The figures in the new test are literals in
the test source: UPDATE_BASELINE=1 cannot move them, only a person editing this
file can, which appears in a diff and can be argued with. Detection is shared with
the digest gate; attribution and remedy are not. The full split is pinned rather
than `moved` alone, because a lost move is conserved as one removal plus one
addition.

Verification, each mutation applied and reverted, tree confirmed clean after:

  - Mutation 2 (multiply the short-circuit bound so pruning is 2% too aggressive,
    leaving MOVE_THRESHOLD at 0.6) reddens the at-the-cutoff case. Before this
    change it left the entire fast tier green. Note the orientation: the effect
    #673 describes needs `< threshold * 1.02`; multiplying the other operand
    prunes LESS aggressively and the boundary test correctly stays green under it.
  - Mutation 2, then regenerating the baseline, is the result that justifies the
    third test. All 27 digest assertions go GREEN -- silenced by exactly the remedy
    their own failure message recommends -- while the regression is still present
    and test_the_audited_pair_keeps_its_classification_split remains the sole
    failure.
  - Mutation 1 (constant 0.6 -> 0.45) still reddens the existing constant pin, so
    the additions do not replace coverage that already works. It also reddens both
    new cases: the at-the-cutoff one on its precondition, saying the fixture needs
    re-measuring, and the below-cutoff one on the behaviour, saying a pair below
    MOVE_THRESHOLD was reconciled as a move.

Fast suite 2011 passed / 4 skipped / 15 xfailed; the three affected modules 69
passed / 1 skipped; ruff clean.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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.

A shift in which provisions count as moved passes every test named for that behavior, leaving only a digest to notice

1 participant