fix(tests): reuse the approval bytes so a proposal-apply replay cannot drift into a fingerprint conflict #84
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "feat/issue-82-flake"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
Fixes the intermittent test abort/failure in
tests/proposal_apply_tests.rs::every_step_applied_marks_the_proposal_applied_and_replays_under_the_same_keythat causedcargo testto fail-fast and truncate the test suite.Closes #82
Captured Failure
When the test failed,
cargo testaborted with:Under
cargo test's default fail-fast behavior, any single test panic causescargo testto immediately stop executing remaining test suites, truncating the run and leaving a smaller passed count (e.g. 632 instead of the full 782/791) that appeared green to anything merely summingtest resultlines without checking the exit code or suite count.Root Cause
In
tests/proposal_apply_tests.rs:Env::apply_with_keyconstructed approval payloads by callingapproval_body(digest)on each invocation.approval_body(digest)formats the current time intoapproval.reviewed_at: which truncates to integer seconds.src/history/fingerprint.rs::compute_fingerprintby hashing the request body, includingapproval.reviewed_at. Perthe_same_key_with_a_changed_request_is_an_idempotency_conflict, a changed review timestamp represents a distinct semantic approval and is intentionally treated as an idempotency conflict.every_step_applied_marks_the_proposal_applied_and_replays_under_the_same_keyperformed its same-key replay: If the clock ticked over to the next second during the ~50-100ms between the initial apply and the replay,apply_with_keyregeneratedapproval_bodywith a differentreviewed_at. The incoming request fingerprint no longer matched the recorded operation's fingerprint (request_hash != fingerprint), returningidempotency_conflict(HTTP 409,ResponseStatus::Error) instead of the recordedOkoutcome.Fix
Env::apply_with_approval(&self, proposal_id, operation_id, body: Value)to allow caller-controlled approval payloads.Env::apply_with_keydelegate toapply_with_approval(proposal_id, operation_id, approval_body(digest)).every_step_applied_marks_the_proposal_applied_and_replays_under_the_same_key,a_step_that_conflicts_stops_the_apply_without_rolling_anything_back, anda_replayed_apply_without_a_recorded_outcome_reports_its_recorded_progress) to generate the approval body once and reuse the exact identical payload for both the initial apply and the replay call.Verification
left: Error, right: Ok,idempotency_conflict).test result: ok. 1 passed).cargo test --test proposal_apply_tests--no-fail-fast):cargo testcargo fmt --check— clean (exit 0).cargo clippy --all-targets -- -D warnings— clean (exit 0).Verdict: APPROVE
The pull request cleanly fixes the root cause of the intermittent test failure and suite truncation in
tests/proposal_apply_tests.rs. The fix correctly reuses the exact approval bytes across replay calls rather than freezing the clock globally or weakening any assertion, and leaves product code completely untouched.Per-criterion Evaluation
tests/proposal_apply_tests.rs:1035(left: Error, right: Ok,idempotency_conflict).approval_body(digest)generatesreviewed_atwith 1-second precision usingUtc::now(). When initial apply and replay calls straddled a second boundary, the request body differed, producing a different SHA-256 fingerprint incompute_fingerprint. The server correctly answered 409idempotency_conflict, failing the replay assertion. The fix resends the identical approval payload rather than regenerating it.cargo test --test proposal_apply_tests(21 passed, 0 failed on every iteration).cargo test(no--no-fail-fast) reports the full suite total, so a truncated run can no longer be mistaken for a green onecargo testexecutes cleanly to completion with exit code 0, reporting all 798 passed tests across 45 test result lines without early termination.Key Review Focus Areas
"Reuse the exact bytes you sent" vs "freeze the clock globally":
approval_body(digest)continues to generate dynamic timestamps usingUtc::now().to_rfc3339_opts(chrono::SecondsFormat::Secs, true). The clock is not frozen globally.Env::apply_with_approval(&self, proposal_id, operation_id, body: Value)allows callers to provide the exact JSON payload. Replay tests create the approval body once and supply it to both the initial apply and the replay.Integrity of
the_same_key_with_a_changed_request_is_an_idempotency_conflict:env.apply_with_key, then issues a second request with the same key but an explicitly altered timestamp (reviewed_at: "2030-04-08T06:10:00Z").approval_bodywas not made deterministic or frozen to a fixed value, this test continues to genuinely produce a divergent request body, correctly receives HTTP 409idempotency_conflict, and verifies 0 additional calendar writes. Verified passing in isolation.Reconciling the "abort" symptom with the captured "panic":
cargo testrun intermittently, truncating the suite" and hypothesized a process-level abort (exit without assertion).--fail-fast. Whenassert_eq!(replay.status, ResponseStatus::Ok)panicked on 409idempotency_conflict, theproposal_apply_testsbinary exited 101. Cargo immediately aborted execution of subsequent test binaries, truncating the run and leaving an incomplete pass count (e.g. 632 instead of 782+).Appropriate scope and level of the fix:
Env::apply_with_approvaland updated all three replay tests intests/proposal_apply_tests.rs:a_step_that_conflicts_stops_the_apply_without_rolling_anything_backevery_step_applied_marks_the_proposal_applied_and_replays_under_the_same_keya_replayed_apply_without_a_recorded_outcome_reports_its_recorded_progressenv.apply_with_keyorenv.apply(which generates fresh UUIDs per call). No remaining replay tests regenerate payloads mid-test.Diff cleanliness and production safety:
tests/proposal_apply_tests.rs, 20 insertions, 9 deletions).src/were touched.Findings
Verification I Ran
All checks executed from worktree
/Users/yurisasc/Development/Other/life-service/.worktrees/issue-82-flake(headeefb0a3, basemainat255ce17):20-run loop of affected binary:
Full test suite execution (plain
cargo test, default fail-fast):test result: oklines completed. Total passed: 798 passed, 0 failed, 0 ignored.Targeted test of idempotency conflict path:
Clippy check:
Format check:
PR-body Claims
"Total passed: 791, 0 failed, 0 ignored."main@255ce17(which incorporates PR #83's 7 new tests intests/issue45_pagination_tests.rs), the suite now reports 798 passed, 0 failed. The PR author's 791 figure accurately reflected the baseline prior to PR #83."When the test failed, cargo test aborted with: ... assertion left == right failed: REPLAY FAILED: error=Some(ErrorItem { code: "idempotency_conflict" ... })"mainwasassert_eq!(replay.status, ResponseStatus::Ok). The messageREPLAY FAILED: error=...was formatted in the author's reproduction harness to capture payload details on failure. The failure mode and panic are authentic.Anything I Could Not Check
compute_fingerprinthashes the whole body includingapproval.reviewed_at(formatted with second precision), a >= 1.0s delay deterministically triggers the conflict prior to the fix. Analytical verification confirms the mechanism.fix(tests): stop proposal-apply suite aborting on replay under timestamp driftto fix(tests): reuse the approval bytes so a proposal-apply replay cannot drift into a fingerprint conflictcargo testrun intermittently, truncating the suite #82