DSH / Atlas
2026-08-20proposedbug-fix

Quarantine unreadable historical attachments

隔离无法读取的历史附件

An admitted `ImageAttachmentRef` remains in durable history and therefore participates in every later request until compaction replaces it. `AttachmentStore.readImage()` fails with `ATTACHMENT_NOT_FOUND`, `ATTACHMENT_CORRUPT`, or `ATTACHMENT_READ_FAILED` when the referenced object disappears, fails integrity verification, or cannot be read. The unchanged history then makes every later model request fail on the same o

English

Problem

An admitted ImageAttachmentRef remains in durable history and therefore participates in every later request until compaction replaces it. AttachmentStore.readImage() fails with ATTACHMENT_NOT_FOUND, ATTACHMENT_CORRUPT, or ATTACHMENT_READ_FAILED when the referenced object disappears, fails integrity verification, or cannot be read. The unchanged history then makes every later model request fail on the same object, leaving the session unable to continue even though the remaining messages are usable. This is the unavailable-object case left fail-loud by reconstructable requests.

Proposal

A session-backed image-request projection records unreadable references before provider dispatch. ATTACHMENT_NOT_FOUND and ATTACHMENT_CORRUPT immediately append attachment/quarantine; ATTACHMENT_READ_FAILED receives one cancellation-aware read retry and appends the same event with a retryable reason if the retry fails. Cancellation and unclassified failures do not quarantine data.

The quarantine event identifies the attachment and failure class. Projection replaces each quarantined image with deterministic text containing its display name when present, attachment-id prefix, and failure class. Later requests derive the same replacement from the log and skip readImage() for that reference, while the original image block remains in append-only history. A request that discovers and records a quarantine reprojects before calling the provider, so the failed read does not become a terminal model-request attempt.

Explicit recovery calls readImage() and appends attachment/recovered only after digest and metadata verification succeeds. Projection then restores the original image reference. Missing or corrupt bytes are never overwritten automatically, and clearing quarantine without verification is invalid.

The shared request-projection consumer owns this policy. Attachment storage continues to report exact read failures, and provider adapters do not invent independent placeholders or recovery state.

Alternatives considered

  • Keep failing every request. This preserves strict error reporting but makes an otherwise usable durable session permanently unavailable after one storage fault.
  • Delete or rewrite the historical image block. That loses evidence, violates append-only history, and prevents a repaired content-addressed object from restoring the original request.
  • Catch the error independently in each adapter. An unlogged placeholder would make replay depend on which adapter and storage state happened to be present, while duplicated policies would drift.
  • Replace missing or corrupt bytes automatically. The reference names verified immutable content; substituting different bytes under that identity would defeat integrity checking.

Acceptance criteria

  • A missing or corrupt historical image produces one durable quarantine transition and a stable placeholder; later model requests do not read that object or fail because of it.
  • A general read failure is retried once without ignoring cancellation, then follows the retryable quarantine path.
  • Restart and fork reconstruct the same quarantined request from the session log.
  • Recovery restores image projection only after the original reference passes complete read verification.
  • Package tests cover error classification, idempotent quarantine, cancellation, retry, recovery, and nested tool-result images; a keyless runnable snapshot pins the model-visible placeholder and durable events.

Risks

Quarantine and recovery each change the provider prefix once. The implementation must identify the exact failing reference before recording state and must coordinate concurrent requests so duplicate failures produce one effective transition. Auxiliary calls without a live session cannot record recovery state; their failure policy remains explicit implementation scope rather than an adapter fallback.

中文

问题

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

提案

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

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

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

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

考虑过的替代方案

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

接受标准

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

风险

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