recovery: fresh-eyes audit fixes (handlers + Pass-N engine + sweep)

Handlers (section_recover.rs):
- Bisect expand loops now honor ctx.halted() (were deadline-only, so a
  Stop could hang up to 60s vacuuming a readable island).
- read_span: explicit Transport arm so a bus-abort read isn't counted as
  unproductive grinding; debug_assert the sector-aligned span invariant.
- Scoreboard rank: an attempted-but-zero-time handler (e.g. returned Halted
  on its first check) now ranks BOTTOM, not top — it no longer crowds out
  proven performers.
- Document the wedge tier-size coupling + new regression test that a
  2-handler (tier-1) chain still catches a wedge via cross-section streak.

Pass-N engine (patch.rs):
- Rebuild PatchOutcome stats AFTER the post-read re-verify downgrade flush
  (was snapshotting before it, over-reporting bytes_good / recovered and
  risking a 'perfect rip' verdict on an imperfect one).
- Progress 'recovered' composes the still-bad set to MATCH work_total
  (subtract NonTried, add Unreadable) so the bar can't pin at 0 on a
  partially-swept disc or run backward on the Unreadable→NonTrimmed relabel.
- Remove dead work_done field; rewrite the stale 'adaptive batching' comment
  to describe the handler chain and mark block_sectors/full_recovery as
  informational-only.

Sweep (disc/mod.rs):
- Saturating arithmetic at the damage-jump position math (honor the
  read_error side's documented defence-in-depth guarantee).

Deferred (noted, need focused passes): fast_capture re-introduction,
Pass-1 halt-misclassified-as-jump, bytes_good display inflation, the
always-zero blocks_* telemetry, Pass-1 jump-on-first-error policy.
This commit is contained in:
Matthew Jackson
2026-07-01 09:14:32 -07:00
parent ceaa1da369
commit 2dd98c3e32
3 changed files with 136 additions and 28 deletions
+9 -1
View File
@@ -3505,7 +3505,15 @@ impl Disc {
);
}
let jump_pos = (pos + block_bytes + sectors * 2048).min(region_end);
// Saturating throughout — the read_error side
// computes the sector count with saturating_mul as
// "defence in depth"; honor the same guarantee at
// the consuming multiply/add so a pathological jump
// distance can't wrap.
let jump_pos = pos
.saturating_add(block_bytes)
.saturating_add(sectors.saturating_mul(2048))
.min(region_end);
let gap_start = pos + block_bytes;
let gap_bytes = jump_pos.saturating_sub(gap_start);
if gap_bytes > 0 {