# Agent Note: 在提供方限制覆写上下文 diff 基础

Status: implemented

[English](2026-07-30-bounded-overwrite-diff-basis.md) | 中文

## 问题

`dsh-fs-local` 会在 `FsWriteOutcome.before` 中返回完整旧文件，供消费方生成覆写上下文 diff。这个仅用于展示的预读没有上限：大文件覆写可能分配整个旧文件；而仅检查较早的路径 stat 也无法真正实施上限，因为外部进程可以在 stat 与读取之间替换文件或扩大文件。即使旧文件很小，大替换内容也会使上下文 hunk 接近替换内容本身的大小。本改动关闭了 [result-time applied-hunk diff](../../archived/architecture/2026-07-02-result-time-applied-hunk-diffs.md) 中记录的暂缓上限事项。

## 决策

`LocalFileSystem.Config.diffBasisMaxBytes` 是一个不超过运行时 Buffer 分配和字符串解码上限的正安全整数部署配置，默认 10 MiB。只有当 UTF-8 替换内容严格低于该上限，且为生成基础而打开的旧文件最终也低于该上限时，覆写才提供 `before`。旧文件读取会打开文件描述符、检查该描述符，并按可响应取消的分块最多读取配置的字节数；一旦到达边界便返回 `null`。描述符 stat 后发生大小变化时同样返回 `null`，即使最终大小仍低于上限，因为部分前缀会成为错误的 diff 基础。旧内容为二进制或无效 UTF-8 时也返回 `null`；描述符阶段的任何 errno 同样如此——旧文件在调用方预检之后、基础读取打开之前被删除或变得不可读，不能让调用方已经提交的写入失败；只有取消和非 errno 故障会继续向上传播。这些结果都不会阻止原子写入。

本地提供方拥有该决策，因为 `before` 是它提供的可选、尽力而为的基础：当配置的成对上限已使替换内容不合格时，它可以避免获取旧内容。`tool-fs` 继续拥有 diff 计算、保留与展示。该配置独立于 `tool-fs.readStreamMinSize`；读取路由与覆写展示是不同策略，无需共享数值。

`before: null` 要求消费方使用既有的整文件回退。该上限只限制额外获取旧内容的成本，以及上下文内容对是否合格；它不限制调用方持有的替换内容、返回的 `after` 值或消费方的回退渲染。

## 考虑过的替代方案

**保留一个与读取工具流式阈值相等的硬编码阈值。** 否决，因为读取阈值可由部署配置，且归消费方所有。两个同值常量会形成无法强制的一致性耦合，而覆写基础本身也是部署层面的内存与展示选择。

**提供方只限制旧内容一侧，并在 `tool-fs` 中限制新内容 diff。** 否决，因为当提供方配置的成对上限已经排除替换内容时，这仍会获取旧文本；同时会把同一条 `before` 合格规则拆到两个插件中。消费方仍可自由施加额外的输出限制。

**信任初次 `probe()` 的大小，再执行普通整文件读取。** 否决，因为该大小可能在读取前变旧；描述符读取必须对它真正读取的对象实施上限。

**为任意大的内容对流式生成上下文 diff。** 本次缺陷修复不采用，因为当前文件系统 seam 返回完整的 `before`/`after` 字符串，当前 diff 实现也消费这两个字符串。流式 diff 需要独立的跨包协议与展示设计。

## 后果

部署可以调整额外的覆写基础成本，而不改变读取路由。达到或超过排他上限时，覆写仍会成功，并通过整文件回退保持可见，但不再提供上下文 hunk。低于上限时，除调用方的替换内容外，提供方仍可能持有接近 `diffBasisMaxBytes` 的旧文本。对于合格覆写，有上限的描述符读取会增加一次 open/stat/read 序列，同时防止陈旧路径探测把该序列变成无上限分配。
