fix(test): one window predicate, called by the tool and by the fixture #30

Merged
jlxq0 merged 1 commit from fix/window-predicate-shared into main 2026-08-29 00:57:51 +00:00
Owner

Held for your review. Not merging this one.

Reproduced your mutation on my tree before changing anything: days_until > params.days>= at src/mcp.rs:593, 107 passed, 0 failed. The gap is
exactly as you describe.

Why it survived

src/birthday.rs filtered with days_until(b, today) <= days in the fixture
body while the tool compared days_until > params.days inline. Two
implementations of one decision, so the test exercised its own copy and the
tool's was covered by nothing. within(30) == 3 was true of a correct call site
and of a broken one alike.

The predicate

birthday::within_window(&bday, today, days) -> Option<u32> is now the only
place that comparison is written. The tool calls it; the fixture calls it.

mutation result
remaining <= daysremaining < days within_window_includes_the_last_day_and_excludes_the_next and the_thirty_day_window_answers_where_the_fourteen_day_window_is_empty both fail
remaining <= daysremaining <= days + 1 same two fail

Tightening and loosening both bite, so the boundary is pinned from both sides
rather than only against being too strict.

Extraction alone would not have closed it, and this is the part I would not

have done unprompted

After extracting, I put the inline comparison back at the call site,
bypassing the predicate. All 108 tests passed. One predicate is not a
guarantee that one predicate is used.

So days_until is now private. The same bypass no longer compiles:

error[E0603]: function `days_until` is private

Reintroducing the drift now requires re-exporting it, which is a visible change
in a diff rather than a one-character edit. That is a narrowing rather than a
proof: nothing at the unit level can stop someone who changes the visibility
too, and driving the tool handler itself would need a mock CardDAV server and an
rmcp request context, which is a larger piece of work than this defect warrants.

Verification

fmt, clippy -D warnings, 108 tests, cargo deny, all green at ad7d4425.

No behaviour change. The deployed filter is identical to the one v0.1.6
ships, so the tag stands, the running image is untouched, and the live 30-day
answer is still 3. What changed is that the number is now pinned.

Recorded in AGENTS.md as the general form: a test that reimplements a
comparison tests its own copy, and the repair is one predicate both sides call
plus the visibility that stops the second copy coming back.

**Held for your review. Not merging this one.** Reproduced your mutation on my tree before changing anything: `days_until > params.days` → `>=` at `src/mcp.rs:593`, **107 passed, 0 failed**. The gap is exactly as you describe. ## Why it survived `src/birthday.rs` filtered with `days_until(b, today) <= days` in the fixture body while the tool compared `days_until > params.days` inline. Two implementations of one decision, so the test exercised its own copy and the tool's was covered by nothing. `within(30) == 3` was true of a correct call site and of a broken one alike. ## The predicate `birthday::within_window(&bday, today, days) -> Option<u32>` is now the only place that comparison is written. The tool calls it; the fixture calls it. | mutation | result | |---|---| | `remaining <= days` → `remaining < days` | `within_window_includes_the_last_day_and_excludes_the_next` and `the_thirty_day_window_answers_where_the_fourteen_day_window_is_empty` both fail | | `remaining <= days` → `remaining <= days + 1` | same two fail | Tightening and loosening both bite, so the boundary is pinned from both sides rather than only against being too strict. ## Extraction alone would not have closed it, and this is the part I would not ## have done unprompted After extracting, I put the inline comparison **back** at the call site, bypassing the predicate. **All 108 tests passed.** One predicate is not a guarantee that one predicate is used. So `days_until` is now **private**. The same bypass no longer compiles: error[E0603]: function `days_until` is private Reintroducing the drift now requires re-exporting it, which is a visible change in a diff rather than a one-character edit. That is a narrowing rather than a proof: nothing at the unit level can stop someone who changes the visibility too, and driving the tool handler itself would need a mock CardDAV server and an rmcp request context, which is a larger piece of work than this defect warrants. ## Verification fmt, clippy `-D warnings`, **108 tests**, `cargo deny`, all green at `ad7d4425`. **No behaviour change.** The deployed filter is identical to the one `v0.1.6` ships, so the tag stands, the running image is untouched, and the live 30-day answer is still 3. What changed is that the number is now pinned. Recorded in `AGENTS.md` as the general form: a test that reimplements a comparison tests its own copy, and the repair is one predicate both sides call plus the visibility that stops the second copy coming back.
fix(test): one window predicate, called by the tool and by the fixture
All checks were successful
CI / cargo (pull_request) Successful in 52s
CI / docker (pull_request) Successful in 40s
ad7d44250d
The fixture filtered with days_until(b, today) <= days while upcoming_birthdays
compared days_until > params.days inline, so the test exercised its own copy of
the comparison and the tool's was covered by nothing. Flipping the tool's > to
>= drops a birthday exactly days away, turns the live 30-day answer from 3 into
2, and leaves all 107 tests green. Alan found it; reproduced here before fixing.

birthday::within_window is now the only place that comparison is written, and
both sides call it. Tightening it to < and loosening it to <= days + 1 each
redden the boundary test and the live-pair fixture.

Extraction alone does not close it, since a call site can compare inline beside
the predicate and stay green. days_until is therefore private: bypassing the
predicate now fails to compile with E0603 rather than passing quietly, and
re-exporting it is a visible change rather than a one-character one.

No behaviour change, so v0.1.6 stands and the running image is unaffected.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Author
Owner

108 passed at ad7d442 in my own run, and all three checks behave as described.

baseline                        108 passed, 0 failed
remaining <= days + 1           FAIL  within_window_includes_the_last_day_and_excludes_the_next
                                FAIL  the_thirty_day_window_answers_where_the_fourteen_day_window_is_empty
inline bypass at the call site  error[E0603]: function `days_until` is private

The loosening reds the live-pair fixture as well as the boundary test, which is what makes it a pin on the number the acceptance turns on rather than only on the helper.

And the bypass now fails to compile rather than passing quietly. That is the whole of what the extraction bought once you found extraction alone did not close it.

The finding is worth more than the fix and it is yours

You extracted, put the inline comparison back, and 108 passed. I would have shipped the extraction believing it closed. One predicate is not a guarantee that one predicate is used, and nothing in the extracted form makes a bypass visible.

Calling the privacy a narrowing rather than a proof is the right register. Someone changing the visibility gets there again, and it is a visible change rather than a one-character one, which is the property being bought.

Declining to drive the tool handler is right too. A mock CardDAV server and an rmcp context to pin one comparison is a cost that buys a second thing to be wrong about.

No behaviour change

v0.1.6 stands, the image is untouched, the live 30-day answer is still 3. What changed is that the 3 is now pinned.

Merging.

**108 passed at `ad7d442` in my own run, and all three checks behave as described.** baseline 108 passed, 0 failed remaining <= days + 1 FAIL within_window_includes_the_last_day_and_excludes_the_next FAIL the_thirty_day_window_answers_where_the_fourteen_day_window_is_empty inline bypass at the call site error[E0603]: function `days_until` is private **The loosening reds the live-pair fixture as well as the boundary test**, which is what makes it a pin on the number the acceptance turns on rather than only on the helper. **And the bypass now fails to compile rather than passing quietly.** That is the whole of what the extraction bought once you found extraction alone did not close it. ## The finding is worth more than the fix and it is yours **You extracted, put the inline comparison back, and 108 passed.** I would have shipped the extraction believing it closed. **One predicate is not a guarantee that one predicate is used**, and nothing in the extracted form makes a bypass visible. **Calling the privacy a narrowing rather than a proof is the right register.** Someone changing the visibility gets there again, and it is a visible change rather than a one-character one, which is the property being bought. **Declining to drive the tool handler is right too.** A mock CardDAV server and an rmcp context to pin one comparison is a cost that buys a second thing to be wrong about. ## No behaviour change `v0.1.6` stands, the image is untouched, the live 30-day answer is still 3. **What changed is that the 3 is now pinned.** Merging.
jlxq0 merged commit ba79bdb142 into main 2026-08-29 00:57:51 +00:00
jlxq0 deleted branch fix/window-predicate-shared 2026-08-29 00:57:51 +00:00
Sign in to join this conversation.
No reviewers
No labels
waiting-on-julian
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
jlxq0/carddav-mcp!30
No description provided.