Outcome and invariant
Keep demonstrated duplicate record identities as a typed refusal when a segment stage ends in an incomplete record header, record payload or seal. This is a separate T-13.2 / KEEP-RECOVERY-010 finding under #131: appending incomplete bytes must not turn previously demonstrated corruption into authority to discard evidence.
Verified main failure
On main 6051abb25a9fd33ae7ee0de5614514b709a4d82a, construct a canonical segment header followed by two exact copies of the one-zero record. The header occupies 64 bytes; each record occupies 145 bytes. With no tail, the public classifier correctly refuses DuplicateRecordIdentity at indexes 0/1 and offsets 64/209.
Append any of these tails and the same classifier instead admits truncation:
- One byte of a next record header:
TailHeader { record_index: 2, offset: 354, required: 112, observed: 1 }.
- A canonical record header plus partial payload:
Record { record_index: 2, offset: 354, expected: 145, observed: 120 }.
- The 16-byte seal magic:
Seal { offset: 354, required: 128, observed: 16 }.
The copied-Docker public probe fingerprints each exact corrupted stage, admits its bytes, assesses it, and successfully obtains plan_recovery_stage_discard for all three. It compiles and fails at known duplicate corruption escaped as discard plans. No filesystem removal was executed; the demonstrated failure is authorization, not an assertion that data was actually deleted.
recovery_segment_classifier.rs validates the identity index only in admit_reusable; recognized partial seals return earlier, and truncated record/header errors return through classify_cursor_error without checking already-complete records.
Acceptance
- Every truncation-return path preserves duplicate refusal for preceding complete admitted records, with exact identity/index/offset diagnostics.
- Preserve canonical incomplete-stage classifications, existing corrupt-framing diagnoses, record/resource limits, and bounded allocation; do not silently repair duplicates.
- Public classifier and fingerprint-bound assessment/planning regressions fail on the unfixed revision and pass with the correction. Include the no-tail control and all three demonstrated tail classes, plus a bounded generated record/tail sweep.
- Complete stage and reusable-prefix duplicate handling remain correct. Define diagnostic precedence where a tail also contradicts framing, without hiding the corruption or emitting a discardable state.
- Replay the class through the existing recovery fuzz path when available; retain counterexamples. Do not substitute seed counts or source-text tests for runtime evidence.
- Update current requirements/evidence, finish independent exact-head review and required checks.
Scope and prerequisites
One coherent duplicate-invariant correction, branched from origin/main. No prerequisite on #171/#172: that PR fixes a different incomplete-seal fixed-framing contract, and these failures exist with canonical partial seal magic. Shared classifier code alone is not a dependency; integration must preserve both fixes. No new namespace, on-disk format, complete-future-prefix feasibility campaign, or reopening of #99's separate v2 incomplete-retention-stage disposition decision. The safe merged state refuses known duplicates before discard planning while preserving noncontradictory existing recovery outcomes.
Outcome and invariant
Keep demonstrated duplicate record identities as a typed refusal when a segment stage ends in an incomplete record header, record payload or seal. This is a separate T-13.2 / KEEP-RECOVERY-010 finding under #131: appending incomplete bytes must not turn previously demonstrated corruption into authority to discard evidence.
Verified main failure
On main
6051abb25a9fd33ae7ee0de5614514b709a4d82a, construct a canonical segment header followed by two exact copies of the one-zero record. The header occupies 64 bytes; each record occupies 145 bytes. With no tail, the public classifier correctly refusesDuplicateRecordIdentityat indexes 0/1 and offsets 64/209.Append any of these tails and the same classifier instead admits truncation:
TailHeader { record_index: 2, offset: 354, required: 112, observed: 1 }.Record { record_index: 2, offset: 354, expected: 145, observed: 120 }.Seal { offset: 354, required: 128, observed: 16 }.The copied-Docker public probe fingerprints each exact corrupted stage, admits its bytes, assesses it, and successfully obtains
plan_recovery_stage_discardfor all three. It compiles and fails atknown duplicate corruption escaped as discard plans. No filesystem removal was executed; the demonstrated failure is authorization, not an assertion that data was actually deleted.recovery_segment_classifier.rsvalidates the identity index only inadmit_reusable; recognized partial seals return earlier, and truncated record/header errors return throughclassify_cursor_errorwithout checking already-complete records.Acceptance
Scope and prerequisites
One coherent duplicate-invariant correction, branched from origin/main. No prerequisite on #171/#172: that PR fixes a different incomplete-seal fixed-framing contract, and these failures exist with canonical partial seal magic. Shared classifier code alone is not a dependency; integration must preserve both fixes. No new namespace, on-disk format, complete-future-prefix feasibility campaign, or reopening of #99's separate v2 incomplete-retention-stage disposition decision. The safe merged state refuses known duplicates before discard planning while preserving noncontradictory existing recovery outcomes.