pi_async_fs 0.1.0

Runtime-agnostic asynchronous filesystem contracts for local and remote storage
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
// 全文件原地覆盖失败时的目标状态证据。
//
// 覆盖写同时涉及“输入 buffer 处理了多少”和“目标文件现在能被证明
// 处于什么状态”两个正交维度。[`crate::TransferProgress`] 只表达前者;
// [`OverwriteTargetEvidence`] 表达后者,以避免调用方把“已取回原 buffer”
// 或“处理了零字节”误解成“目标文件一定没有变”。

use core::cmp::Ordering;
use core::fmt;
use core::hash::{Hash, Hasher};

use pi_result::Error;

use crate::TransferProgress;

// 一次全文件原地覆盖普通错误完成时,对目标内容状态的可证明事实。
//
// # 作用与使用位置
//
// 本类型将进入以后单独冻结的覆盖错误恢复载体,并最终由
// `FileIo` 的全文件覆盖方法在普通错误结果中返回。它描述的是 adapter
// 在错误完成点能够证明的目标文件逻辑内容和长度,不是对后端内部步骤
// 的枚举,也不是一个持续更新的实时文件快照。
//
// 证据的时间边界是本次操作仍持有对应稳定文件身份租约时的普通错误
// 完成点。租约释放后,另一个进程、绕过本库的句柄或后续受管操作仍可以
// 再次改变文件;因此调用方不能把本值当成永久锁定的当前状态。
//
// # 与传输进度的关系
//
// [`crate::TransferProgress`] 表达输入连续前缀在本次覆盖流程中的处理进度;
// 本类型表达目标文件状态证据。二者不能互相推导。例如,先缩短目标
// 再写入的 adapter 可能同时报告 `TransferProgress::Exact { bytes: 0 }`
// 和 [`Self::ExactInputPrefix`] 的零字节前缀:没有处理任何输入字节,但目标
// 已经被明确变为空文件。
//
// 本值不证明普通写管道已排空、文件已执行强刷新、目录项已持久化,
// 或远端对象已达到强一致完成点。这些保证必须由成功合同或显式强刷新
// 接口单独提供。
//
// # 重试边界
//
// 本类型不提供 `is_retry_safe` 之类的便利判断。是否可以重试还取决于错误
// 原因、调用方要求的幂等级别、协调范围和错误完成后是否存在外部修改。
// 只有 [`Self::Unchanged`] 能单独证明本次调用没有改变目标内容;调用方仍须
// 确认具体错误允许重试。
//
// # 基本能力与演进
//
// 本类型刻意不实现 `Clone`、`Copy` 或 `Default`。它实现调试、显示、判等、
// 全序与哈希,以支持诊断和确定性索引;排序只比较证据标签及字段,不表示
// 证据强度、操作先后或恢复优先级。`Send` 与 `Sync` 由字段自动获得,不使用
// 手写 `unsafe impl`。
//
// 本类型不承诺稳定 ABI、整数判别值、序列化格式或可反向解析的文本协议。
// `#[non_exhaustive]` 强制库外调用方保留一个保守匹配分支;后续可以添加更精确
// 的可移植证据,但不能改弱现有变体的语义。
#[non_exhaustive]
/// 全文件覆盖失败时,对目标内容状态的可证明事实。
///
/// 证据只描述本次操作普通错误完成点的目标状态,不是后续实时快照,也不
/// 提供持久化保证。调用方应与传输进度和错误原因共同决定恢复策略。
pub enum OverwriteTargetEvidence {
    // 能证明本次覆盖没有改变目标文件的逻辑内容和长度。
    //
    // 这个结论以本次操作获得排他租约后、任何目标修改之前的状态为
    // 参照。它不承诺文件时间、后端统计、临时上传对象、锁或其它非内容
    // 副作用也一定没有变化。
    /// 能证明本次覆盖没有改变目标的逻辑内容和长度。
    Unchanged,

    // 能证明目标的全部逻辑内容精确等于原始输入的连续前缀。
    /// 能证明目标全部逻辑内容精确等于原始输入的连续前缀。
    ExactInputPrefix {
        // 目标文件的精确逻辑长度,也是从原始输入起点计算的前缀字节数。
        //
        // 本值必须不大于本次原始输入视图的长度。`bytes == 0` 表示目标
        // 已被精确证明为空文件;`bytes == input.len()` 表示所需逻辑内容
        // 已经全部形成,但普通错误仍可来自后续完成步骤,且不证明持久性。
        /// 目标精确逻辑长度,也是原始输入前缀的字节数。
        bytes: usize,
    },

    // 能证明至少一个修改目标内容或长度的步骤已经发生,但无法证明精确结果。
    //
    // 目标可能为空、只包含新前缀、已形成完整新内容,或处于后端可能产生的
    // 其它状态。即使字节恰好与原内容相同,只要已经跨过修改步骤且精确结果
    // 不可证明,adapter 也应使用本变体,而不是 [`Self::Unchanged`]。
    /// 能证明目标修改已经开始,但无法证明精确内容和长度。
    MutationStarted,

    // 无法证明目标是否被改变,也无法给出与原始输入的精确关系。
    //
    // 常见来源包括取消与后台完成竞态、系统调用结果不确定,以及远端提交
    // 响应丢失。调用方不得把本变体当作“零字节已处理”或“目标未变”。
    /// 无法证明目标是否改变或与原始输入的精确关系。
    Unknown,
}

impl fmt::Debug for OverwriteTargetEvidence {
    // 以包含变体名称和公开字段名称的开发者格式显示证据。
    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::Unchanged => formatter.write_str("Unchanged"),
            Self::ExactInputPrefix { bytes } => formatter
                .debug_struct("ExactInputPrefix")
                .field("bytes", bytes)
                .finish(),
            Self::MutationStarted => formatter.write_str("MutationStarted"),
            Self::Unknown => formatter.write_str("Unknown"),
        }
    }
}

impl fmt::Display for OverwriteTargetEvidence {
    // 以面向日志的人类可读摘要显示证据,不承诺可反向解析。
    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::Unchanged => formatter.write_str("target unchanged"),
            Self::ExactInputPrefix { bytes } => {
                write!(formatter, "target is exact input prefix of {bytes} bytes")
            }
            Self::MutationStarted => formatter.write_str("target mutation started"),
            Self::Unknown => formatter.write_str("target state unknown"),
        }
    }
}

impl PartialEq for OverwriteTargetEvidence {
    // 按证据变体及 `bytes` 字段判等。
    fn eq(&self, other: &Self) -> bool {
        match (self, other) {
            (Self::Unchanged, Self::Unchanged)
            | (Self::MutationStarted, Self::MutationStarted)
            | (Self::Unknown, Self::Unknown) => true,
            (
                Self::ExactInputPrefix { bytes: left },
                Self::ExactInputPrefix { bytes: right },
            ) => left == right,
            _ => false,
        }
    }
}

impl Eq for OverwriteTargetEvidence {}

impl PartialOrd for OverwriteTargetEvidence {
    // 返回与 [`Ord`] 一致的证据标签与字段全序。
    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
        Some(self.cmp(other))
    }
}

impl Ord for OverwriteTargetEvidence {
    // 按变体声明顺序及字段比较,不表达证据强度或恢复优先级。
    fn cmp(&self, other: &Self) -> Ordering {
        fn rank(evidence: &OverwriteTargetEvidence) -> u8 {
            match evidence {
                OverwriteTargetEvidence::Unchanged => 0,
                OverwriteTargetEvidence::ExactInputPrefix { .. } => 1,
                OverwriteTargetEvidence::MutationStarted => 2,
                OverwriteTargetEvidence::Unknown => 3,
            }
        }

        rank(self).cmp(&rank(other)).then_with(|| match (self, other) {
            (
                Self::ExactInputPrefix { bytes: left },
                Self::ExactInputPrefix { bytes: right },
            ) => left.cmp(right),
            _ => Ordering::Equal,
        })
    }
}

impl Hash for OverwriteTargetEvidence {
    // 按与判等一致的证据变体及字段向调用方哈希器写入状态。
    fn hash<H>(&self, state: &mut H)
    where
        H: Hasher,
    {
        match self {
            Self::Unchanged => 0_u8.hash(state),
            Self::ExactInputPrefix { bytes } => {
                1_u8.hash(state);
                bytes.hash(state);
            }
            Self::MutationStarted => 2_u8.hash(state),
            Self::Unknown => 3_u8.hash(state),
        }
    }
}

// 全文件原地覆盖普通错误的诊断、恢复资产和副作用证据载体。
//
// # 作用与使用位置
//
// 以后单独冻结的全文件覆盖方法会按值接收写入 buffer。成功时消费
// 输入;Future 正常轮询到错误完成时,本类型会把以下四项不可分割的信息
// 一次性交还调用方:
//
// 1. 统一的 [`pi_result::Error`] 诊断报告;
// 2. 调用时传入的同一个原始 `B`;
// 3. 输入连续前缀的 [`TransferProgress`];
// 4. 目标文件的 [`OverwriteTargetEvidence`]。
//
// 现有 [`crate::BufferFailure`] 只表达写入源的错误、恢复和进度,适合严格尾部
// 追加。覆盖可能在没有处理任何输入字节时已经清空目标,因此需要本专用
// 载体;它不给 `BufferFailure` 添加只对覆盖有意义的可选字段,也不通过
// 嵌套载体迫使调用方穿过两层接口才能完成恢复。
//
// # 原始 buffer 的身份
//
// `B` 在类型定义处不带任何约束,因此本载体可以保存 owned buffer、共享
// 所有权 buffer,或具有调用期生命周期的引用值。“同一个原始 `B`”不只意味着
// 字节相等;adapter 还必须按写入 buffer 合同恢复 allocation、容量、引用身份、
// `Cow` 变体、映射 guard、池租约和该类型承诺保持的其它状态。返还原值不会
// 回滚已有文件副作用。
//
// # 成功、普通错误与取消
//
// - 成功路径不产生本值,也不返还 `B`;
// - Future 正常完成为错误时,adapter 必须先停止对脱离载体的访问,再恢复
//   原始 `B`、进度与目标证据;
// - 调用方直接丢弃 Future 时没有返回本值的同步通道。取消后的内存存活、
//   后台完成、租约保持和最终释放必须由覆盖操作 Future 的合同单独规定。
//
// # 重试与交叉不变量
//
// `progress` 和 `target_evidence` 必须同时真实,但不必具有相同的字节数。
// 例如,目标已清空可以与零输入进度同时成立;后端也可能已处理全部输入,
// 但仍无法证明目标提交结果。任何有字节数的进度或精确前缀证据都不得超过
// 本次原始输入视图的长度。库不会根据本载体自动重试覆盖,调用方也必须
// 同时检查错误可重试性、目标证据和外部协调状态。
//
// # 基本能力与线程语义
//
// 字段对库外保持私有,后续通过逐项冻结的借用访问器和一次性拆解方法
// 提供受控访问。本类型不实现 `Clone`、`Copy`、`Default`、判等、排序或哈希:
// 错误报告的诊断帧不具有适合公共合同的稳定值比较语义,而原始 buffer 也是一次性
// 恢复资产。
//
// `Debug` 和 `Display` 不要求 `B` 实现格式化 trait,也不显示 buffer 内容。
// 本类型不实现 [`std::error::Error`],因为它不是一个可以随意共享、复制或丢弃恢复
// 资产的普通错误对象。`Send` 与 `Sync` 只由 `B` 及其它字段条件性自动获得,
// 不使用手写 `unsafe impl`,也不强制 `B: 'static`。
/// 全文件覆盖失败时的统一诊断、原始缓冲区、进度和目标证据。
///
/// 普通失败返还与调用时同一逻辑值的 `B`,方便调用方检查和恢复;这不表示
/// 目标未改变或可以盲目重试。释放本类型不会回滚文件或执行额外 I/O。
pub struct OverwriteFailure<B> {
    pub(crate) error: Error,
    pub(crate) buffer: B,
    pub(crate) progress: TransferProgress,
    pub(crate) target_evidence: OverwriteTargetEvidence,
}

impl<B> OverwriteFailure<B> {
    // 组合一个已经停止后端访问并完成原始 buffer 恢复的普通覆盖错误。
    //
    // # 参数顺序与所有权
    //
    // 参数顺序固定为“诊断报告、原始 buffer、输入进度、目标证据”,与以后
    // 单独冻结的一次性拆解返回顺序保持一致。前三项与
    // [`crate::BufferFailure::new`] 保持相同心智模型,最后一项是全文件覆盖专有
    // 的目标状态证据。
    //
    // 本方法按值取得全部参数并把它们移入载体;不克隆错误、buffer 或证据。
    // `B` 没有泛型约束,可以是 owned 值或带调用期生命周期的引用值。
    //
    // # 交叉不变量与 adapter 责任
    //
    // 本方法无法也不尝试统一取得 `B` 的字节视图,因而不验证进度或
    // [`OverwriteTargetEvidence::ExactInputPrefix`] 的字节数是否超过原始输入长度,
    // 也不重新查询目标文件来验证证据。生产本值的 `FileIo` adapter 必须在调用
    // 前保证:
    //
    // - 所有有限字节数都不超过本次权威输入视图的长度;
    // - `progress` 和 `target_evidence` 分别真实表达输入与目标两个维度;
    // - 后端、工作线程或远端服务已经停止访问脱离载体,原始 `buffer` 可以
    //   安全交还调用方。
    //
    // 违反这些规则是 adapter 合同错误。本构造方法本身不依据这些值执行不安全
    // 内存操作,因此不准确的证据不会单独造成 Rust 内存安全意义上的未定义行为;
    // 它仍可以误导恢复决策,所以必须视为严重实现缺陷。
    //
    // # 副作用、panic 与性能
    //
    // 本方法只组合字段,不执行文件或网络 I/O,不查询、修改或重试目标,不触发
    // 日志,也不访问全局协调状态。它无锁、无分配,计划为 O(1) 字段移动;
    // 合法输入不应 panic。由于方法消费一次性所有权,“重复调用”不是可观察的
    // 幂等操作,但构造本身没有载体之外的副作用。
    #[must_use]
    /// 按“错误、缓冲区、进度、目标证据”的顺序构造失败值。
    ///
    /// `progress` 与 `target_evidence` 必须分别反映真实可证明事实;本构造器
    /// 不通过再次访问文件来校验它们。
    pub fn new(
        error: Error,
        buffer: B,
        progress: TransferProgress,
        target_evidence: OverwriteTargetEvidence,
    ) -> Self {
        Self {
            error,
            buffer,
            progress,
            target_evidence,
        }
    }

    // 共享借用本次普通覆盖错误的统一 `pi_result` 诊断报告。
    //
    // # 返回值与所有权
    //
    // 返回引用的生命周期严格绑定到 `self` 的本次共享借用。本方法不消费
    // [`OverwriteFailure`],因此调用方查看诊断后仍可借用进度和目标证据,或者在
    // 借用结束后一次性拆解载体并取回原始 buffer。
    //
    // 本接口只提供共享引用。它不克隆报告,不暴露可变引用,不附加 context 或
    // attachment,也不会把报告单独移出而留下缺少诊断的恢复资产。需要对报告
    // 取得所有权时,调用方必须使用以后单独冻结的完整拆解接口,同时处理
    // buffer、进度和目标证据。
    //
    // # 副作用、幂等性与成本
    //
    // 本方法不修改报告或载体,不触发日志,不执行 I/O,不访问后端或全局
    // 协调状态。在借用规则允许时重复调用会返回同一个报告引用,属于无分配、无锁、
    // 无外部副作用的 O(1) 操作;合法调用不应 panic。它不对 `B` 增加任何
    // trait 或生命周期约束。
    #[must_use]
    /// 返回失败诊断的共享引用。
    pub fn error(&self) -> &Error {
        &self.error
    }

    // 共享借用已恢复但尚未被取回的原始 buffer 值。
    //
    // # 返回值与原始身份
    //
    // 返回值是 `&B`,而不是统一转换后的字节切片。这保留 owned、借用、共享
    // 所有权、`Cow`、映射句柄或池化载体的具体类型身份,调用方可以在本次
    // 借用期内使用 `B` 自身已提供的只读能力。本方法不要求 `B` 实现
    // `AsRef<[u8]>`、`Debug`、`Clone` 或其它 trait。
    //
    // adapter 在创建 [`OverwriteFailure`] 之前必须已经停止对脱离载体的访问并恢复
    // 该原始 `B`。本访问器不会再次脱离、恢复、克隆或复制 buffer,也不证明
    // 已有文件副作用被回滚。
    //
    // # 为什么只提供共享借用
    //
    // [`TransferProgress`] 与 [`OverwriteTargetEvidence::ExactInputPrefix`] 都相对于调用时的
    // 原始输入内容建立。如果在拆解载体前通过 `&mut B` 修改 buffer,这些证据
    // 就可能被错误解读为对新内容的描述。因此本类型不提供 `buffer_mut()`,也不提供
    // 会单独消费 buffer 并丢弃其它恢复信息的便利接口。需要修改或重新提交时,
    // 调用方必须先使用以后单独冻结的完整拆解接口;拆解后的证据仍只描述
    // 修改前的原始请求。
    //
    // # 副作用、幂等性与成本
    //
    // 本方法不修改 buffer 或载体,不执行文件或网络 I/O,不访问后端、映射登记
    // 或全局协调状态。在借用规则允许时重复调用返回同一原始值的共享引用,
    // 属于无分配、无锁、无外部副作用的 O(1) 操作;合法调用不应 panic。
    #[must_use]
    /// 返回原始缓冲区的共享引用。
    pub fn buffer(&self) -> &B {
        &self.buffer
    }

    // 共享借用本次覆盖对权威输入连续前缀的可证明处理进度。
    //
    // # 返回值的语义边界
    //
    // 返回引用只描述一个输入维度:从调用时原始 buffer 视图起点开始,adapter
    // 能够可靠证明已处理的连续前缀。具体“处理”完成点由以后冻结的全文件
    // 覆盖方法合同定义,不得由 adapter 随意改成仅复制到临时缓冲区的字节数。
    //
    // 本值不描述目标文件状态。`TransferProgress::Exact { bytes: 0 }` 可以与
    // `OverwriteTargetEvidence::ExactInputPrefix { bytes: 0 }` 同时存在:没有处理任何
    // 输入字节,但目标已经被清空。反过来,完整的精确输入进度也不证明目标
    // 提交成功、管道已排空或内容已持久化。调用方必须把本值与
    // [`Self::target_evidence`] 和 [`Self::error`] 的结果共同解释。
    //
    // # 所有权与不变量
    //
    // 本方法返回共享引用,不消费或隐式复制 [`TransferProgress`],也不提供
    // 可变访问。这保证进度在完整拆解前不会被改造成与原始 buffer 或目标
    // 证据不一致的组合。返回引用只代表错误完成点已记录的事实,不会随后续
    // 文件或后端变化而自动更新。
    //
    // # 副作用、幂等性与成本
    //
    // 本方法不重新查询文件、后端或任务状态,不修改载体,不触发日志或 I/O。
    // 在借用规则允许时重复调用会返回同一进度引用,属于无分配、无锁、无外部
    // 副作用的 O(1) 操作;合法调用不应 panic,也不对 `B` 增加任何约束。
    #[must_use]
    /// 返回可证明传输进度的共享引用。
    pub fn progress(&self) -> &TransferProgress {
        &self.progress
    }

    // 共享借用普通错误完成点已记录的目标文件状态证据。
    //
    // # 返回值与时间边界
    //
    // 返回的是 adapter 在本次覆盖仍持有对应稳定文件身份租约时,对普通
    // 错误完成点能够证明的目标逻辑内容和长度事实。本方法名为
    // `target_evidence` 而不是 `target_state`,因为它不是一个持续更新的实时快照。
    // 错误完成并释放租约后,后续受管操作、其它进程或绕过本库的句柄仍可以
    // 再次改变文件。
    //
    // 本访问器不重新读取文件、查询远端对象、检查映射登记或取得新的协调
    // 租约。因此它不会因为访问时间更晚就自动提供更新证据。
    //
    // # 联合解释与重试限制
    //
    // 调用方必须将返回值与 [`Self::progress`] 和 [`Self::error`] 共同解释:
    //
    // - [`OverwriteTargetEvidence::Unchanged`] 只证明本次覆盖没有改变目标内容与长度;
    // - [`OverwriteTargetEvidence::ExactInputPrefix`] 证明目标与原始输入前缀的精确关系;
    // - [`OverwriteTargetEvidence::MutationStarted`] 证明至少一个目标修改步骤已发生,
    //   但不证明精确结果;
    // - [`OverwriteTargetEvidence::Unknown`] 不能解释为目标未变。
    //
    // 本类型故意不根据证据提供 `is_retry_safe()`。即使证据为 `Unchanged`,能否
    // 重试仍取决于错误原因、外部修改、协调范围和调用方要求的幂等级别。
    //
    // # 所有权、副作用与成本
    //
    // 本方法返回共享引用,不消费、隐式复制或暴露可变的 [`OverwriteTargetEvidence`],
    // 也不修改载体。它不执行 I/O、不触发日志、不访问全局状态;在借用规则
    // 允许时重复调用会返回同一证据引用,属于无分配、无锁、无外部副作用的
    // O(1) 操作。合法调用不应 panic,也不对 `B` 增加任何约束。
    #[must_use]
    /// 返回目标内容证据的共享引用。
    pub fn target_evidence(&self) -> &OverwriteTargetEvidence {
        &self.target_evidence
    }

    // 一次性拆解诊断报告、原始 buffer、输入进度和目标证据。
    //
    // # 返回值顺序与所有权
    //
    // 返回四元组的顺序固定为“诊断报告、原始 buffer、输入进度、目标证据”,与
    // [`Self::new`] 的参数顺序完全一致。本方法消费 `self` 并按值移出全部四个字段,
    // 不克隆错误、buffer、进度或证据。因此恢复资产只能被安全地从同一载体
    // 移出一次。
    //
    // 本类型不另行提供只消费错误或只消费 buffer 的部分拆解方法。四元组让
    // 调用方在同一个所有权交接点显式看到全部恢复信息,不再引入一个只包装字段的
    // 浅层 `OverwriteFailureParts<B>` 类型。
    //
    // # 证据在拆解后的含义
    //
    // 拆解不会让进度或目标证据改为对 buffer 当前可变内容的实时描述。它们仍然
    // 只表达覆盖调用时的原始输入,以及本次普通错误完成点已记录的事实。如果
    // 调用方随后修改已取回的 `B`,必须仍把这两项证据解释为对修改前请求的
    // 描述。拆解本身不证明覆盖已回滚,也不会消除 [`OverwriteTargetEvidence::Unknown`]
    // 或 [`OverwriteTargetEvidence::MutationStarted`] 表达的不确定性。
    //
    // # 副作用、panic 与成本
    //
    // 本方法不重试覆盖,不回滚或刷新文件,不查询后端,不访问全局协调状态,
    // 也不触发日志或 I/O。它计划为无分配、无锁的 O(1) 字段移动;合法调用
    // 不应 panic。由于 `self` 被消费,同一载体无法重复调用本方法,因而不存在
    // 针对同一恢复资产的幂等重复拆解语义。
    #[must_use]
    /// 消费失败值并按构造顺序返回全部字段。
    pub fn into_parts(
        self,
    ) -> (
        Error,
        B,
        TransferProgress,
        OverwriteTargetEvidence,
    ) {
        (
            self.error,
            self.buffer,
            self.progress,
            self.target_evidence,
        )
    }
}

impl<B> fmt::Debug for OverwriteFailure<B> {
    // 显示错误报告、传输进度、目标证据、`B` 的类型名称和脱敏占位符。
    //
    // 该实现不要求 `B: Debug`,不得输出原始文件内容、备用容量、指针、引用
    // 计数、池槽位或原生句柄。格式化不执行文件或网络 I/O,也不消费恢复资产。
    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
        formatter
            .debug_struct("OverwriteFailure")
            .field("error", &self.error)
            .field("buffer_type", &core::any::type_name::<B>())
            .field("buffer", &"<redacted>")
            .field("progress", &self.progress)
            .field("target_evidence", &self.target_evidence)
            .finish()
    }
}

impl<B> fmt::Display for OverwriteFailure<B> {
    // 显示错误报告、进度和目标证据的面向人类摘要。
    //
    // 输出故意省略 `B` 的类型和全部内容,不能用来恢复、解析、序列化或判断
    // buffer 身份。格式化失败只通过 `fmt::Error` 返回,不改变任何所有权。
    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(
            formatter,
            "{}; progress: {}; target: {}",
            self.error, self.progress, self.target_evidence
        )
    }
}