# 输入读取预算与流式审计边界

## 已实现：CLI 有界读取

CLI 原来先用 `readFile` 读取整份文件，之后才进入核心检查。文件再大也会先占用相应内存。现在当前输入和比较基线都使用 64 KiB 分块文件流；读取器从 MoonBit 的 `input_limit_utf16()` 获取预算，不维护另一份硬编码上限。

读取器最多保留 **1,048,577 个 UTF-16 单位**，然后退出读取并关闭流。核心仍只接受 1,048,576 个单位，因此多出的一个单位必定触发解码前的超限拒绝。没有为了凑入预算而把合法前缀伪装成完整文件，也没有绕开核心的行数、token 或区间预算。

使用方法和输出协议不变：

```sh
node cli.mjs examples/preprocessed.jsonl --jsonl --summary
node cli.mjs examples/pretraining.json --baseline examples/pretraining-boundary.json --summary
```

- 恰好达到核心上限的完整字符串仍可审计；其内容是否通过由核心决定。
- 超限返回原有 `{ok:false,error}`，无 report/comparison，退出码 2。`--out` 仍会写出这个错误结果，`--summary` 仍在标准错误流显示输入错误。
- 路径不存在或读取中途失败时沿用 I/O 错误行为：退出码 2、标准错误流提示，不写入或覆盖 `--out`。
- UTF-8 在跨块边界连续解码，中文和 emoji 不会按字节切坏。损坏或不完整 UTF-8 沿用 Node 原有替换字符行为，本次没有引入严格编码校验。1 MiB UTF-8 文件不等于 1,048,576 个 UTF-16 单位；网页文件选择器的 1 MiB 限制不变。
- 最后一个超限单位可能是代理对的一半，但该字符串只用于触发核心长度拒绝，不能进入解码或生成通过报告。

实现依据为 Node 的 [setEncoding](https://nodejs.org/api/stream.html#readablesetencodingencoding) 和[异步迭代读取](https://nodejs.org/api/stream.html#readablesymbolasynciterator)。测试覆盖逐字节拆分的中文/emoji、尾部损坏编码、超限输入源的提前关闭、读取失败、精确上限和 JSON/JSONL/比较两侧超限。

这里约束的是应用保留的输入文本长度。Node/操作系统可能预读额外块，拼接字符串、JSON 解析、核心报告和输出仍占内存；没有宣称整体内存峰值等于输入上限，也没有测量吞吐量或内存改善百分比。当前不会等待无限大输入结束，但对从不发送数据也不结束的输入尚无读取超时。

## 尚未实现：逐行与跨批次审计

当前仍把预算内文本拼接完整后交给 MoonBit；没有逐行提交审计、无限数据集支持或增量报告。后续实现应满足以下约束，不能只把旧接口在多个批次上反复调用就称为全量审计：

| 问题 | 必须保留的语义 |
| --- | --- |
| 物理行号 | 空行、CRLF、跨字节块分隔符都纳入同一个文件级行号；缺省 ID 仍为 `row-N`，不能每批重新从 1 开始。最后一行没有换行符也要正确处理。 |
| 重复 ID | 显式 ID、自动生成 ID、不同批次之间必须共同检查，包括显式 `row-N` 与自动 ID 冲突。批内唯一不等于全文件唯一。 |
| ID 索引大小 | 内存 Set 随样本数增长；大规模支持需要有界限制或磁盘索引/外部排序。概率过滤器不能单独证明唯一性。 |
| 资源预算 | 文件总样本数、总 token 和诊断预算不得按批次重置。提高总量上限应单独设计并测试，不能通过分批绕过现有限制。 |
| 无效或截断尾部 | 损坏行、读取失败、取消和资源耗尽必须使全文件结果保持错误或未完成，不能保留之前批次的绿色通过作为最终结论。 |
| 报告大小 | 当前报告含每个 token 的状态和来源，即使输入流式处理，汇总完整报告仍会增长；需要明确的增量输出格式和最终完成标记。 |
| 原子交付 | 在全文件读完、ID/预算校验完成前，只能展示进度或临时结果；写出最终报告时须区分完成、取消和失败。 |

上述是设计约束，并未新增报告字段或改变 v1 格式。实现时应先加入跨批次重复 ID、自动 ID 冲突、末行损坏、取消及全局预算回归用例，再发布对应接口。审计规则和最终状态依然由 MoonBit 决定。
