# Agent Note: 隔离无法读取的历史附件

Status: proposed

[English](2026-08-20-attachment-read-quarantine.md) | 中文

## 问题

已接纳的 `ImageAttachmentRef` 会留在持久历史中，因此在被压缩替换前都会参与之后的每次请求。引用对象丢失、完整性校验失败或无法读取时，`AttachmentStore.readImage()` 会返回 `ATTACHMENT_NOT_FOUND`、`ATTACHMENT_CORRUPT` 或 `ATTACHMENT_READ_FAILED`。未变化的历史随后会让之后每次模型请求在同一对象上失败，使会话无法继续，即使其余消息仍可使用。这是[可重建请求](../../implemented/architecture/2026-07-05-reconstructable-requests.zh.md)保留为明确失败的对象不可用情况。

## 提案

由会话支撑的图片请求投影在分派给提供方之前记录无法读取的引用。`ATTACHMENT_NOT_FOUND` 和 `ATTACHMENT_CORRUPT` 立即追加 `attachment/quarantine`；`ATTACHMENT_READ_FAILED` 先执行一次服从取消信号的读取重试，重试仍失败时追加同一事件并标记为可重试原因。取消和未分类失败不会隔离数据。

隔离事件标识附件和失败类别。投影把每张已隔离图片替换为确定性文本，包含可用时的显示名称、附件 ID 前缀和失败类别。之后的请求从日志派生相同替换结果，并跳过该引用的 `readImage()`，原始图片块仍留在仅追加历史中。请求发现并记录隔离后，会在调用提供方前重新投影，因此读取失败不会成为终止性的模型请求尝试。

显式恢复会调用 `readImage()`，且仅在内容摘要和元数据校验成功后追加 `attachment/recovered`。投影随后恢复原始图片引用。系统绝不会自动覆盖丢失或损坏的字节，也不允许未经验证就清除隔离。

共享请求投影消费方拥有这项策略。附件存储继续报告准确的读取失败，提供方适配器不会各自生成占位或恢复状态。

## 考虑过的替代方案

- **让每次请求继续失败。** 这保留了严格错误报告，但一次存储故障会让其他部分仍可使用的持久会话永久不可用。
- **删除或重写历史图片块。** 这会丢失证据、违反仅追加历史，并使修复后的内容寻址对象无法恢复原始请求。
- **由每个适配器分别捕获错误。** 未记录的占位会让回放取决于当时存在的适配器和存储状态，重复策略也会发生偏差。
- **自动替换丢失或损坏的字节。** 引用标识经过验证的不可变内容；在该身份下替换成其他字节会破坏完整性校验。

## 接受标准

- 缺失或损坏的历史图片产生一次持久隔离转换和稳定占位；之后的模型请求不再读取该对象，也不会因它失败。
- 一般读取失败会在服从取消信号的前提下重试一次，随后进入可重试隔离路径。
- 重启和 fork 后会从会话日志重建相同的隔离请求。
- 仅在原始引用通过完整读取校验后，恢复操作才恢复图片投影。
- 包测试覆盖错误分类、幂等隔离、取消、重试、恢复和嵌套工具结果图片；一个无需密钥的可运行快照钉住模型可见占位和持久事件。

## 风险

隔离和恢复各会改变一次提供方前缀。实现必须在记录状态前识别准确的失败引用，并协调并发请求，使重复失败只产生一次有效转换。没有活跃会话的辅助调用无法记录恢复状态；它们的失败策略属于明确的实现范围，不能退回到适配器自行处理。
