
CASE C1 :: 缓存替身契约：add / gets+cas / 仅当版本更大 / 带版本墓碑 / TTL 边界
  ok   键不存在时 cas 返回 NOT_FOUND（未命中时 cas 无从比较） (expected=NOT_FOUND, actual=NOT_FOUND)
  ok   键不存在时 add (expected=STORED, actual=STORED)
  ok   键已存在时 add 不覆盖，即使版本更大 (expected=NOT_STORED, actual=NOT_STORED)
  ok   gets 得到的 cas 唯一值未变时 cas 成功 (expected=STORED, actual=STORED)
  ok   用旧 cas 唯一值再写返回 EXISTS (expected=EXISTS, actual=EXISTS)
  ok   仅当版本更大：v1 不能覆盖 v2 (expected=REJECTED, actual=REJECTED)
  ok   仅当版本更大：同版本不覆盖值条目 (expected=REJECTED, actual=REJECTED)
  ok   仅当版本更大：v3 覆盖 v2 (expected=STORED, actual=STORED)
  ok   失效版本不高于现有值版本时忽略（重复失效不删新值） (expected=IGNORED, actual=IGNORED)
  ok   失效 v4 写入墓碑 (expected=STORED, actual=STORED)
  ok   更旧的失效不会调低墓碑版本 (expected=IGNORED, actual=IGNORED)
  ok   墓碑 v4 拒绝 v3 回填 (expected=REJECTED, actual=REJECTED)
  ok   add 把墓碑当作已存在 (expected=NOT_STORED, actual=NOT_STORED)
  ok   墓碑 v4 接受同版本 v4 回填 (expected=STORED, actual=STORED)
  ok   t=TTL-1 仍可读 (expected=VALUE(v4,p4), actual=VALUE(v4,p4))
  ok   t=TTL 过期（now >= expiresAt 视为不存在） (expected=MISS, actual=MISS)

CASE C2 :: 正常路径（版本条件写 + 带版本墓碑）：未命中回填 → 命中 → 更新 → 回填新版本
  ok   首次读：未命中，读库 v1，回填 STORED
  ok   再次读命中 v1
  ok   写入后数据库版本 (expected=2, actual=2)
  ok   提交后缓存为 v2 墓碑 (expected=TOMB(v2), actual=TOMB(v2))
  ok   墓碑视为未命中，读库 v2 回填 STORED
  ok   缓存与数据库一致 (expected=VALUE(v2,p2), actual=VALUE(v2,p2))

CASE C3 :: 负向对照：add 回填 + delete 失效。慢读者在写者提交并删除后回填 v1
  note 事件：t=0 读者未命中 → 读库 v1 → 停住；t=0 写者提交 v2 → delete；t=5 读者 add v1 = STORED
  ok   数据库已是 v2 (expected=2, actual=2)
  ok   negative control exposes: 删除之后的 add 成功，缓存存入 v1
  ok   negative control exposes: t=104（回填后 TTL-1）缓存仍返回 v1
  ok   t=105 旧值随 TTL 过期 (expected=MISS, actual=MISS)
  ok   过期后读库 v2 回填
  note 旧值可见区间 [0, 105)：提交时刻 0 到 回填时刻 5 + TTL 100

CASE C4 :: 负向对照：仅当版本更大写入 + 普通 delete。删除后键不存在，v1 被当作“更新”写入
  note 事件：读者读库 v1 → 写者提交 v2 → delete → 读者 putIfNewer(v1) = STORED
  ok   negative control exposes: 缓存存入 v1，而数据库为 v2

CASE C5 :: 修复：仅当版本更大写入 + 带版本墓碑。同一交错下慢读者的 v1 回填被拒绝
  note 事件：读者读库 v1 → 写者提交 v2 → 墓碑 v2 → t=5 读者 putIfNewer(v1) = REJECTED
  ok   v1 回填被墓碑 v2 拒绝 (expected=REJECTED, actual=REJECTED)
  ok   缓存仍为墓碑 v2 (expected=TOMB(v2), actual=TOMB(v2))
  ok   下一次读回填 v2

CASE C6 :: 边界：两个写者的失效乱序到达（先 v3 后 v2），慢读者持 v1、另一读者持 v2
  ok   W3 的失效先到：墓碑 v3 (expected=STORED, actual=STORED)
  ok   W2 的失效后到：被忽略，墓碑不降级 (expected=IGNORED, actual=IGNORED)
  note 事件：A 读 v1；W2 提交 v2；B 读 v2；W3 提交 v3；失效 v3 到达；失效 v2 到达；A 回填 v1；B 回填 v2
  ok   A 的 v1 回填被拒绝 (expected=REJECTED, actual=REJECTED)
  ok   B 的 v2 回填被拒绝（v2 已被 v3 取代） (expected=REJECTED, actual=REJECTED)
  ok   缓存保持墓碑 v3 (expected=TOMB(v3), actual=TOMB(v3))
  ok   后续读回填 v3

CASE C7 :: 失效边界：墓碑 TTL=30 短于读者停顿 31，墓碑过期后 v1 回填重新成功
  note 事件：t=0 读者读 v1；t=0 写者提交 v2、墓碑 v2（到期 t=30）；t=31 读者 putIfNewer(v1) = STORED
  ok   negative control exposes: 墓碑过期后保护消失，缓存重新存入 v1（数据库 v2）

CASE C8 :: 租约令牌：未命中领令牌，delete 作废令牌；并发未命中者不回填
  ok   租约期内的第二个未命中者读库但不回填
  note 事件：A 领令牌 → 读 v1 → 停住；B 未命中（令牌已发出）读库不回填；写者提交 v2 → delete 作废令牌；A 持旧令牌回填 = REJECTED
  ok   被作废的令牌回填失败 (expected=REJECTED, actual=REJECTED)
  ok   缓存仍为空 (expected=MISS, actual=MISS)
  ok   新未命中者领新令牌，回填 v2

CASE C9 :: 负向对照：延迟双删。回填落在第二次删除之前被修复，落在之后仍留旧值
  note (a) 读者读 v1；写者提交 v2 → delete#1 → 读者 add v1 = STORED → delete#2
  ok   (a) 第二次删除清掉了 v1 (expected=MISS, actual=MISS)
  note (b) 读者读 v1；写者提交 v2 → delete#1 → delete#2；读者 add v1 = STORED
  ok   negative control exposes: (b) 第二次删除早于回填，v1 留在缓存直到 TTL

CASE C10 :: 真实并发：8 线程 × 300 次操作、4 个键、约 20% 写，读者在读库与回填之间随机停 0–2ms
  note VERSION_TOMBSTONE：写=452 回填成功=245 回填被拒=480 静止后旧值条目=0
  ok   静止后每个值条目的版本都等于数据库版本 (expected=0, actual=0)
  note ADD_DELETE（同种子，交错不可重复）：写=452 回填成功=285 未写入=235 静止后旧值条目=0（本次计数，不作断言）

CASE C11 :: 多进程：写者 JVM 同事务写 outbox，提交后 kill -9；数据库 immediate 重启；补删任务幂等修复
  ok   (a) 写者进程正常退出（输出：COMMITTED 2 / INVALIDATED STORED） (expected=0, actual=0)
  ok   (a) 写者经 TCP 写入墓碑 v2 (expected=TOMB(v2), actual=TOMB(v2))
  ok   (a) outbox 行仍会被补删任务处理一次（重复失效被忽略） (expected=1, actual=1)
  ok   (b) 预热：缓存存入 v1 (expected=STORED, actual=STORED)
  note (b) 子进程 pid=2339098 输出 "COMMITTED 2" 后收到 kill -9，退出码 137
  ok   (b) 子进程被 SIGKILL 终止 (expected=137, actual=137)
  ok   (b) 提交已持久：数据库 v2 (expected=2, actual=2)
  ok   (b) 缓存仍是 v1：失效没有发出 (expected=VALUE(v1,p1), actual=VALUE(v1,p1))
  ok   pg.sh restart-immediate 退出码（输出：pg b3-25 restarted after immediate stop） (expected=0, actual=0)
  ok   (b) immediate 重启后数据库仍为 v2 (expected=2, actual=2)
  ok   (b) immediate 重启后 outbox 待处理行仍在 (expected=1, actual=1)
  note (b) 补删第 1 次：claimed=1 marked=0 error=crashed after send, before mark
  ok   (b) 失效已送达缓存 (expected=TOMB(v2), actual=TOMB(v2))
  ok   (b) 标记前崩溃，行仍待处理 (expected=1, actual=1)
  ok   (b) 读者回填 v2
  ok   (b) 补删第 2 次：重发并标记完成 (expected=1, actual=1)
  ok   (b) 重发的失效 v2 没有删掉已回填的 v2 (expected=VALUE(v2,p2), actual=VALUE(v2,p2))
  ok   (b) 无待处理行 (expected=0, actual=0)

CASE C12 :: 负向对照：写者不写 outbox，提交后 kill -9
  note 子进程输出 "COMMITTED 2" 后 kill -9，退出码 137
  ok   补删任务没有可处理的行 (expected=0, actual=0)
  ok   negative control exposes: t=TTL-1 缓存仍返回 v1，数据库为 v2；只能等 TTL 或巡检

CASE C13 :: 恢复：补删时缓存不可达，行保持待处理；缓存恢复后补删成功
  note 连接已关闭的端口：claimed=1 marked=0 error=cache unreachable: ConnectException
  ok   发送失败，不标记完成
  ok   行仍待处理 (expected=1, actual=1)
  ok   缓存不可达期间旧值仍在 (expected=VALUE(v1,p1), actual=VALUE(v1,p1))
  ok   换到可达的缓存后补删完成 (expected=1, actual=1)
  ok   缓存写入墓碑 v2 (expected=TOMB(v2), actual=TOMB(v2))

CASE C14 :: 补删任务并发：两个领取者用 FOR UPDATE SKIP LOCKED 处理 20 行
  ok   待处理 20 行 (expected=20, actual=20)
  ok   全部处理完 (expected=0, actual=0)
  ok   标记完成 20 次 (expected=20, actual=20)
  ok   失效命令发送 20 次（无行被两个领取者同时处理） (expected=20, actual=20)
  ok   缓存中 20 个 v2 墓碑 (expected=20, actual=20)

CASE C15 :: 巡检：比较缓存版本与数据库版本，发现旧值后用数据库版本修复
  note 第 1 轮：checked=2 stale=1 repaired=1
  ok   发现并修复 1 个旧值
  ok   第 2 轮无旧值（checked=1）
  ok   修复后读者回填 v2
  note 巡检读到 v2 后写者提交 v3 并写墓碑 v3：stale=1 repaired=0 缓存=TOMB(v3)
  ok   巡检的 v2 修复被忽略
  ok   缓存保持墓碑 v3，没有被回退 (expected=TOMB(v3), actual=TOMB(v3))

SUMMARY passed=78 failed=0
