db=files35 store=/tmp/b3-35-files ttl=PT1H grace=PT10M part=65536

CASE U1 :: 正常路径：乱序上传 3 块 → 完成 → 提交元数据 → 处理 → 发布 → 读取
  ok   新会话状态 (expected=INIT, actual=INIT)
  ok   上传分块 3 (expected=STORED, actual=STORED)
  ok   上传分块 1 (expected=STORED, actual=STORED)
  ok   上传分块 2 (expected=STORED, actual=STORED)
  ok   收到分块后状态 (expected=UPLOADING, actual=UPLOADING)
  ok   整体摘要等于声明值 (expected=33c08d50da76591fabe687cdba9e28d5f0fd938d6cd2ed0a90198b69e1b31017, actual=33c08d50da76591fabe687cdba9e28d5f0fd938d6cd2ed0a90198b69e1b31017)
  ok   blob 位于内容地址 blobs/sha256/33c08d50da76…
  ok   tmp 目录无残留 (expected=0, actual=0)
  ok   完成后会话状态 (expected=COMPLETED, actual=COMPLETED)
  ok   提交后文件状态 (expected=PROCESSING, actual=PROCESSING)
  ok   提交后会话状态 (expected=COMMITTED, actual=COMMITTED)
  ok   PROCESSING 时读取被拒绝 (expected=NOT_READY, actual=NOT_READY)
  ok   首次领取尝试号 (expected=1, actual=1)
  ok   发布成功
  ok   发布后文件状态 (expected=PUBLISHED, actual=PUBLISHED)
  ok   读取内容摘要 (expected=33c08d50da76591fabe687cdba9e28d5f0fd938d6cd2ed0a90198b69e1b31017, actual=33c08d50da76591fabe687cdba9e28d5f0fd938d6cd2ed0a90198b69e1b31017)
  ok   派生文件解压后与原文件一致
  ok   发布事件条数 (expected=1, actual=1)
  note file=file-0001 blob=33c08d50da76591f derived=derived/33c08d50da76591fabe687cdba9e28d5f0fd938d6cd2ed0a90198b69e1b31017.gz

CASE U2 :: 分块边界：重复同内容、同序号异内容、摘要不符、越界、缺块完成、重复完成与提交
  ok   分块 1 首次上传 (expected=STORED, actual=STORED)
  ok   分块 1 同内容重传 (expected=DUPLICATE, actual=DUPLICATE)
  ok   分块 1 不同内容 (expected=PART_CONFLICT, actual=PART_CONFLICT)
  ok   冲突后暂存分块未被覆盖 (expected=1435ff162342df07623e5dd604f6ea7e3b0302d3467dbda30e9d5547ebd2f153, actual=1435ff162342df07623e5dd604f6ea7e3b0302d3467dbda30e9d5547ebd2f153)
  ok   分块 2 摘要与内容不符 (expected=CHECKSUM_MISMATCH, actual=CHECKSUM_MISMATCH)
  ok   摘要不符的分块未登记 (expected=0, actual=0)
  ok   分块序号 4 越界 (expected=PART_OUT_OF_RANGE, actual=PART_OUT_OF_RANGE)
  ok   分块序号 0 越界 (expected=PART_OUT_OF_RANGE, actual=PART_OUT_OF_RANGE)
  ok   缺分块 2 时完成被拒绝 (expected=MISSING_PARTS, actual=MISSING_PARTS)
  ok   拒绝后仍可继续上传 (expected=UPLOADING, actual=UPLOADING)
  ok   拒绝后无 blob、无临时文件 (expected=0, actual=0)
  ok   重复调用 complete 返回同一摘要 (expected=8049c7255267a43f452ba58db940ef1c9d5f0fa0389c76b455766176d0e94be6, actual=8049c7255267a43f452ba58db940ef1c9d5f0fa0389c76b455766176d0e94be6)
  ok   完成后不再接受分块 (expected=SESSION_CLOSED, actual=SESSION_CLOSED)
  ok   重复提交元数据返回同一 file_id (expected=file-0001, actual=file-0001)
  ok   file_object 只有一行 (expected=1, actual=1)
  ok   处理任务只有一行 (expected=1, actual=1)

CASE U3 :: 整体摘要与声明不符：完成被拒绝，会话中止，不产生 blob
  ok   complete 返回摘要不符 (expected=DIGEST_MISMATCH, actual=DIGEST_MISMATCH)
  ok   会话被中止 (expected=ABORTED, actual=ABORTED)
  ok   关闭原因 (expected=DIGEST_MISMATCH, actual=DIGEST_MISMATCH)
  ok   无 blob、无临时文件 (expected=0, actual=0)

CASE U4 :: 8 个线程并发上传同一序号：同内容只登记一次；两种内容只有一种胜出
  ok   同内容：STORED 恰好 1 次 (expected=1, actual=1)
  ok   同内容：其余 7 次 DUPLICATE (expected=7, actual=7)
  ok   同内容：登记行数 (expected=1, actual=1)
  note 两种内容并发结果 [DUPLICATE, PART_CONFLICT, STORED, PART_CONFLICT, DUPLICATE, PART_CONFLICT, DUPLICATE, PART_CONFLICT]
  ok   异内容：STORED 恰好 1 次 (expected=1, actual=1)
  ok   异内容：与胜者相同的另外 3 次为 DUPLICATE (expected=3, actual=3)
  ok   异内容：另一种内容的 4 次为 PART_CONFLICT (expected=4, actual=4)
  ok   暂存文件内容与登记摘要一致 (expected=7b1b4a6d865d9c5ab9068c92a94a82174e5d0419b698aab949343580886cc709, actual=7b1b4a6d865d9c5ab9068c92a94a82174e5d0419b698aab949343580886cc709)
  ok   无临时文件残留 (expected=0, actual=0)

CASE U5 :: 两个会话上传相同内容：共用一个 blob，回收不删除被引用的 blob
  ok   两次完成得到同一摘要 (expected=995856b77bc91c22bc0cdd50b99754ab19a7259440834fb855e73c46592e7725, actual=995856b77bc91c22bc0cdd50b99754ab19a7259440834fb855e73c46592e7725)
  ok   blob 目录只有一个文件 (expected=1, actual=1)
  note reclaim expired=0 stagingRemoved=2 blobsRemoved=0 blobsKept=1 tmpRemoved=0
  ok   被两条 file_object 引用的 blob 未删除 (expected=0, actual=0)
  ok   两个已提交会话的暂存分块被清理 (expected=2, actual=2)

CASE U6 :: 中止：之后的分块被拒绝，暂存分块由回收清理
  ok   中止后上传被拒绝 (expected=SESSION_CLOSED, actual=SESSION_CLOSED)
  ok   中止后完成被拒绝 (expected=SESSION_CLOSED, actual=SESSION_CLOSED)
  ok   被拒绝的分块没有留下临时文件 (expected=0, actual=0)
  note reclaim expired=0 stagingRemoved=1 blobsRemoved=0 blobsKept=0 tmpRemoved=0
  ok   暂存目录被删除 (expected=1, actual=1)
  ok   staging/up-0001 不存在

CASE M1 :: 元数据被拒绝：会话保持 COMPLETED，blob 受保护，重试提交成功
  ok   非法文件名被拒绝 (expected=METADATA_INVALID, actual=METADATA_INVALID)
  ok   会话仍为 COMPLETED (expected=COMPLETED, actual=COMPLETED)
  note reclaim(宽限期后、TTL 内) expired=0 stagingRemoved=0 blobsRemoved=0 blobsKept=1 tmpRemoved=0
  ok   COMPLETED 会话引用的 blob 未被删除
  ok   重试提交成功 (expected=PROCESSING, actual=PROCESSING)

CASE M2 :: 元数据事务在提交前失败且客户端不再重试：TTL 后会话过期，blob 被回收
  ok   注入故障 (expected=IllegalStateException, actual=IllegalStateException)
  ok   file_object 已回滚 (expected=0, actual=0)
  ok   会话仍为 COMPLETED (expected=COMPLETED, actual=COMPLETED)
  note reclaim(TTL 内) expired=0 stagingRemoved=0 blobsRemoved=0 blobsKept=1 tmpRemoved=0
  ok   TTL 内不删除 (expected=0, actual=0)
  note reclaim(TTL + 宽限期后) expired=1 stagingRemoved=1 blobsRemoved=1 blobsKept=0 tmpRemoved=0
  ok   会话被标记过期 (expected=1, actual=1)
  ok   会话状态 (expected=EXPIRED, actual=EXPIRED)
  ok   孤儿 blob 被删除 (expected=1, actual=1)
  ok   blob 文件不存在
  ok   暂存分块已删除
  ok   过期后不能再提交 (expected=SESSION_NOT_COMPLETED, actual=SESSION_NOT_COMPLETED)

CASE NC3 :: 负向对照：回收只看宽限期、不查引用
  ok   对照组（查引用）：0 个 blob 被删 (expected=0, actual=0)
  note reclaim(不查引用) expired=0 stagingRemoved=0 blobsRemoved=2 blobsKept=0 tmpRemoved=0
  ok   negative control exposes: PROCESSING 文件的 blob 被删除
  ok   negative control exposes: TTL 内 COMPLETED 会话的 blob 被删除
  ok   negative control exposes: 元数据提交成功但处理任务找不到 blob（file file-0002）

CASE P1a :: 多进程：改名进 blob 目录之后、提交之前 kill -9，再执行回收
  ok   子进程到达注入点：AT after-rename pid=1777186
  note kill -9 1777186 rc=0
  ok   子进程被 SIGKILL 终止（退出码 128+9） (expected=137, actual=137)
  ok   blob 已在内容地址
  ok   blob 内容完整 (expected=e2fd28383e6ab442d7346b7bdf847272d9d26f4c8e25bc8baa9fdb43bc00aa06, actual=e2fd28383e6ab442d7346b7bdf847272d9d26f4c8e25bc8baa9fdb43bc00aa06)
  ok   数据库事务随连接断开回滚，会话仍为 UPLOADING (expected=UPLOADING, actual=UPLOADING)
  ok   无 file_object (expected=0, actual=0)
  note reclaim(宽限期后、TTL 内) expired=0 stagingRemoved=0 blobsRemoved=0 blobsKept=1 tmpRemoved=0
  ok   未结束会话声明了该摘要，blob 受保护 (expected=0, actual=0)
  note reclaim(TTL + 宽限期后) expired=1 stagingRemoved=1 blobsRemoved=1 blobsKept=0 tmpRemoved=0
  ok   会话过期 (expected=EXPIRED, actual=EXPIRED)
  ok   孤儿 blob 被删除 (expected=1, actual=1)
  ok   暂存分块已删除
  ok   blob 与 tmp 目录为空 (expected=0, actual=0)

CASE P1b :: 多进程：同一崩溃点后客户端重试 complete，走去重分支完成
  ok   子进程到达注入点：AT after-rename pid=1777222
  note kill -9 1777222 rc=0
  ok   子进程被 SIGKILL 终止（退出码 128+9） (expected=137, actual=137)
  ok   重试 complete 成功 (expected=c00900afd774ca8bc0cd86a79f2da37f867cadbb855e35ab95a2086f1d07ae43, actual=c00900afd774ca8bc0cd86a79f2da37f867cadbb855e35ab95a2086f1d07ae43)
  ok   会话 COMPLETED (expected=COMPLETED, actual=COMPLETED)
  ok   blob 仍只有一个 (expected=1, actual=1)
  ok   重试删除了自己的临时文件 (expected=0, actual=0)
  note reclaim expired=0 stagingRemoved=1 blobsRemoved=0 blobsKept=1 tmpRemoved=0
  ok   已提交文件的 blob 保留 (expected=0, actual=0)
  ok   文件进入处理 (expected=PROCESSING, actual=PROCESSING)

CASE P1c :: 多进程：临时文件 fsync 之后、改名之前 kill -9；临时文件在宽限期后回收
  ok   子进程到达注入点：AT after-fsync pid=1777254
  note kill -9 1777254 rc=0
  ok   子进程被 SIGKILL 终止（退出码 128+9） (expected=137, actual=137)
  ok   内容地址上没有任何文件 (expected=0, actual=0)
  ok   留下一个组装中的临时文件 (expected=1, actual=1)
  ok   宽限期内不删临时文件 (expected=0, actual=0)
  note reclaim(宽限期后) expired=0 stagingRemoved=0 blobsRemoved=0 blobsKept=0 tmpRemoved=1
  ok   临时文件被删除 (expected=1, actual=1)
  ok   会话在 TTL 内仍可重试 (expected=UPLOADING, actual=UPLOADING)
  ok   重试 complete 成功 (expected=76ed810a26a1d89f56edcf8aec730c81d9de2452d2fed9ee5d6009790e405a49, actual=76ed810a26a1d89f56edcf8aec730c81d9de2452d2fed9ee5d6009790e405a49)

CASE P1d :: 多进程：崩溃后客户端中止会话；无引用的 blob 在宽限期内保留，期满删除
  ok   子进程到达注入点：AT after-rename pid=1777285
  note kill -9 1777285 rc=0
  ok   子进程被 SIGKILL 终止（退出码 128+9） (expected=137, actual=137)
  note reclaim(立即) expired=0 stagingRemoved=1 blobsRemoved=0 blobsKept=1 tmpRemoved=0
  ok   宽限期内无引用 blob 仍保留 (expected=0, actual=0)
  note reclaim(宽限期后) expired=0 stagingRemoved=0 blobsRemoved=1 blobsKept=0 tmpRemoved=0
  ok   期满后删除 (expected=1, actual=1)
  ok   blob 不存在

CASE NC4 :: 负向对照（多进程）：直接写到内容地址，写到一半 kill -9
  ok   子进程写完第一块
  note kill -9 1777316 rc=0
  ok   子进程被 SIGKILL 终止 (expected=137, actual=137)
  note 内容地址文件大小 65536，声明大小 150000
  ok   negative control exposes: 内容地址上的文件与地址摘要不符
  ok   正确实现的 complete 复核摘要后用完整文件替换 (expected=3cda7216bef42850699ac27d76a52f19febb6a49d5c87326d7a01232361ae0cd, actual=3cda7216bef42850699ac27d76a52f19febb6a49d5c87326d7a01232361ae0cd)
  ok   替换后内容与地址一致 (expected=3cda7216bef42850699ac27d76a52f19febb6a49d5c87326d7a01232361ae0cd, actual=3cda7216bef42850699ac27d76a52f19febb6a49d5c87326d7a01232361ae0cd)

CASE P2 :: 多进程：worker-a 领取后被 SIGSTOP，租约过期 worker-b 重复执行；只有一次发布
  ok   worker-a 停在发布之前：ARTIFACT worker=worker-a file=file-0001 attempt=1 sha=1bb6590a76615a53f95d19376c591d79d62ae5ab14eeb0f749ce42cd3e0360d4
  note kill -STOP 1777348 rc=0
  ok   /proc 显示 worker-a 处于停止状态 (expected=T, actual=T)
  ok   worker-a 的租约已过期
  ok   worker-b 正常退出 (expected=0, actual=0)
  note worker-b 输出 [ARTIFACT worker=worker-b file=file-0001 attempt=2 sha=1bb6590a76615a53f95d19376c591d79d62ae5ab14eeb0f749ce42cd3e0360d4, PUBLISHED worker=worker-b attempt=2]
  ok   worker-b 以尝试号 2 发布
  note kill -CONT 1777348 rc=0
  ok   worker-a 正常退出 (expected=0, actual=0)
  ok   worker-a 的迟到发布被拒绝 (expected=REJECTED worker=worker-a attempt=1, actual=REJECTED worker=worker-a attempt=1)
  ok   发布事件恰好 1 条 (expected=1, actual=1)
  ok   事件来自 worker-b (expected=worker-b, actual=worker-b)
  ok   published_attempt (expected=2, actual=2)
  ok   任务状态 (expected=DONE, actual=DONE)
  ok   两个 worker 的派生文件摘要相同（确定性变换） (expected=1bb6590a76615a53f95d19376c591d79d62ae5ab14eeb0f749ce42cd3e0360d4, actual=1bb6590a76615a53f95d19376c591d79d62ae5ab14eeb0f749ce42cd3e0360d4)
  ok   磁盘上派生文件与登记摘要一致 (expected=1bb6590a76615a53f95d19376c591d79d62ae5ab14eeb0f749ce42cd3e0360d4, actual=1bb6590a76615a53f95d19376c591d79d62ae5ab14eeb0f749ce42cd3e0360d4)

CASE P2b :: 多进程：两个 worker 同时启动抢同一任务
  ok   worker-a 退出 (expected=0, actual=0)
  ok   worker-b 退出 (expected=0, actual=0)
  note 输出 [NO_JOB worker=worker-a, ARTIFACT worker=worker-b file=file-0001 attempt=1 sha=319eb0e445e4c8a88b3857271bbe32391a40e40691eac309e0251fecf65ed385, PUBLISHED worker=worker-b attempt=1]
  ok   恰好一个 PUBLISHED (expected=1, actual=1)
  ok   另一个没有领到任务 (expected=1, actual=1)
  ok   发布事件 1 条 (expected=1, actual=1)

CASE NC1 :: 负向对照：发布只按 file_id 更新，不检查状态与尝试号
  ok   租约过期后 worker-b 领到尝试号 2 (expected=2, actual=2)
  note naive 事件数=2 最终 published_attempt=1
  ok   negative control exposes: 同一文件发布事件 2 条
  ok   negative control exposes: 过期尝试覆盖了较新的发布记录

CASE P3 :: 多进程：独立读取进程在 PROCESSING 时被拒绝，发布后可读
  ok   blob 已在磁盘上（拒绝来自状态检查）
  note reader 输出 [READ REJECTED code=NOT_READY detail=NOT_READY: PROCESSING]
  ok   PROCESSING 时读取被拒绝
  ok   上传中的会话没有可读文件：[READ REJECTED code=NOT_FOUND detail=NOT_FOUND: file-0002]
  note reader 输出 [READ OK bytes=150000 sha=47f591739a9ea0a95b610c58496004bc5dd50acb25f40a7d8a2514aa14da1340]
  ok   发布后读取成功且摘要一致

CASE NC2 :: 负向对照：按内容地址直接读取，不检查文件状态
  ok   对照组：正式读取接口拒绝 (expected=NOT_READY, actual=NOT_READY)
  ok   negative control exposes: PROCESSING 文件被读出 150000 字节

SUMMARY passed=137 failed=0
