# 为什么需要修正层：可运行的反例

这个文件用一段可复现的对比回答评委最关心的问题：**"窗口聚合已经有 Flink 等成熟方案，为什么还要做一个新项目？"**

## 场景

同一设备先上报 `at=0, value=20` 和 `at=16, value=35`，`gap=10`，它们是两个会话。网络恢复后补传 `at=8, value=12`，桥接两个会话；随后发现该记录有误并撤回。

## 两种实现的实际输出

运行：

```sh
node scripts/compare_naive.mjs
```

实测输出（2026-10-23，本仓库 `dist/engine.js`）：

| 结果 | 增量聚合（不重建会话） | TideWeave（重建连通分量） |
| --- | --- | --- |
| 窗口 | `[0,26) members=[left,right] count=2 sum=55 min=12 max=35` | `[0,10) members=[left] count=1 sum=20 min=20 max=20`<br>`[16,26) members=[right] count=1 sum=35 min=35 max=35` |
| 窗口数 | 1 | 2 |
| 撤回命令输出 | 无（只改数字） | 3 条变更（1 delete + 2 upsert） |

## 错在哪里

"增量聚合"是很多手写实现的自然做法：插入时合并会话，撤回时只把数值减回去。撤回后它给出：

- **边界仍是一个窗口** `[0,26)`，但原始事实其实是两个会话；
- **count=2**，而撤回后只剩 2 条事实分属两个窗口，不是 1 个窗口的 2 条；
- **sum=55**，而正确答案是 20 与 35 两个独立窗口；
- **min=12 / max=35**，`min=12` 来自已被撤回的事件，是过期值；
- **下游无法察觉**：没有变更日志、没有 before-image、没有版本校验。

这些错误不会报错，只会静默地把错账交给下游。真实系统里，这表现为"对账差异"、"看板数字回退后再也回不来"、"补传一次数据后指标永久偏移"。

## TideWeave 做了什么

撤回不是一个减法，而是一次**原子状态变更**：

1. 从事件索引中删除 `bridge`；
2. 对受影响的 key 重新计算会话连通分量，得到两个原始会话；
3. 对比修正前后，生成 1 条 `delete`（旧的合并窗口，带 before-image）和 2 条 `upsert`（两个重建窗口）；
4. 下游 `View` 校验版本连续、before-image 与行身份，缺一即整笔拒绝。

对同一个输入，Flink 的 retract 流会把"如何重新划分窗口边界与成员"留给下游；TideWeave 由引擎完成并输出可核对的变更。这不是"又一个窗口库"，而是**窗口层之上的修正语义**。

## 这不是数据库的活儿吗？

对已入库的事实做更正，当然可以是一条 UPDATE。但本场景里更正不是一次性写库，而是**持续到达的输入流**：迟到与乱序由水位线和 allowed lateness 语义化处理；撤回后窗口边界与成员按连通分量重建；每次修正输出成员级 before/after，版本与旧值可被下游校验；同一命令序列可逐字节重放。

具体落到三个场合才有意义：

1. **根本没有数据库**——设备网关、边缘控制器、桌面工具、CI 测试夹具。现场只有一个进程，但迟到合并、撤回拆分、窗口边界仍要算对。
2. **更正以流的形式反复到达**——广告无效流量撤回、退款回冲、订单撤改。没有"一次性修完"的时刻，乱序且会重复；照命令日志重放，用 UPDATE 会二次扣减。
3. **必须交代"改了什么"**——对账、审计、风控。UPDATE 只留行级 after（或 binlog），给不出成员级 before/after，也给不出"这两个会话本该拆开"的拓扑结论。

用数据库完成同样的事，需要物化视图、触发器、自研撤回流与版本校验，事件时间语义分散在应用里，而且 UPDATE 本身不可重放，要幂等还得另建命令去重层。TideWeave 把这些做成库的一部分。

**反过来说，以下情况不必用它**：只要日终出一张报表、允许全量重算的，数仓更省事；要集群吞吐与多租户隔离的，用 Flink；要跨机共享状态与高可用的，本项目是单进程嵌入，不合适。把边界说清楚比把适用范围说大更容易验收。

## 边界

- 这个对比只证明"只做数值增减"会错，不证明所有增量实现都错；如果下游自己重建拓扑，也能得到正确结果，但那正是本项目要替你做的事。
- TideWeave 不宣称发明会话窗口；它宣称的是把"撤回 → 重建 → 终结保护 → 下游校验"做成一个可嵌入、可回放的整体。
- 场景数据是合成的，脚本可复跑；`examples/session-correction.json` 是同一场景的完整 JSON 版本。
