  note server_version=16.15 (Ubuntu 16.15-0ubuntu0.24.04.1)

CASE S1 :: 正常路径：3000 行，v1/v2 写流量持续期间完成 扩张→双写→回填→校验→切读→停写旧列→收缩
  note P1 expand committed on attempt 1
  ok   P1 加列与触发器在同一事务提交 (expected=P1-expanded, actual=P1-expanded)
  note writes v1=1241 v2=579 inserts=86 backfill batches=13 backfill rows_updated=2266
  ok   业务写入全程无错误 (expected=0, actual=0)
  ok   新旧两版代码都在迁移期间写入
  ok   P4 校验：新列无空值 (expected=0, actual=0)
  ok   P4 校验：新旧列逐行一致 (expected=0, actual=0)
  ok   每行新列等于该行最后一次已提交业务写入（回填未覆盖新值） (expected=0, actual=0)
  ok   SET NOT NULL 由已验证 CHECK 证明，未扫描全表
  ok   priority_level 已为 NOT NULL (expected=NO, actual=NO)
  ok   切读：抽样 429 行按旧列与按新列读出相同结果 (expected=0, actual=0)
  ok   v3 只写新列无错误 (expected=0, actual=0)
  ok   v3 只写新列时，触发器仍维护旧列 (expected=0, actual=0)
  ok   v3 写入后逐行核对通过 (expected=0, actual=0)
  ok   不可逆点之前：回退到 v1 的守卫放行（old column authoritative-compatible）
  ok   P7 收缩：触发器与旧列已删除 (expected=P7-contracted, actual=P7-contracted)
  ok   v3 读写不受收缩影响 (expected=urgent, actual=urgent)
  ok   收缩后 v1 读取旧列失败（undefined_column） (expected=42703, actual=42703)
  ok   收缩后回退守卫拒绝：old column dropped at P7-contracted

CASE S2 :: 锁队列：长事务持 ACCESS SHARE，ALTER TABLE 排队等 ACCESS EXCLUSIVE，后来的 SELECT 排在它后面
  ok   长事务持有 AccessShareLock (expected=[AccessShareLock], actual=[AccessShareLock])
  ok   ALTER TABLE 在等待 AccessExclusiveLock（granted=false） (expected=relation:AccessExclusiveLock, actual=relation:AccessExclusiveLock)
  ok   ALTER 被长事务阻塞（hard block） (expected=[long-reader], actual=[long-reader])
  ok   后来的普通 SELECT 也在等待 AccessShareLock (expected=relation:AccessShareLock, actual=relation:AccessShareLock)
  ok   pg_blocking_pids 显示 SELECT 被排队中的 ALTER 阻塞（soft block），而不是被长事务直接阻塞 (expected=[ddl], actual=[ddl])
  ok   长事务不结束，ALTER 与 SELECT 都无法完成
  ok   长事务提交后 ALTER 完成 (expected=ok, actual=ok)
  ok   随后 SELECT 完成 (expected=100, actual=100)

CASE S3 :: lock_timeout：DDL 拿不到锁时以 55P03 快速失败，不再挡住普通查询；释放后重试成功
  ok   lock_timeout 到期，ALTER 失败（lock_not_available） (expected=55P03, actual=55P03)
  ok   DDL 放弃后，普通 SELECT 在长事务仍未结束时完成 (expected=100, actual=100)
  ok   此时长事务仍在进行 (expected=idle in transaction, actual=idle in transaction)
  ok   前两次尝试均为 55P03 (expected=[1:55P03, 2:55P03], actual=[1:55P03, 2:55P03])
  ok   第三次尝试成功 (expected=3, actual=3)
  ok   列已加上 (expected=1, actual=1)

CASE S4 :: ADD COLUMN：无默认值/非易变默认值不重写表；易变默认值重写表
  ok   ADD COLUMN 持有 AccessExclusiveLock（直到事务结束）
  ok   无默认值：无 rewriting table 消息
  ok   无默认值：relfilenode 不变
  ok   常量默认值：无 rewriting table 消息
  ok   常量默认值：relfilenode 不变
  ok   常量默认值记录在 pg_attribute.atthasmissing/attmissingval (expected=true, actual=true)
  ok   既有行读到默认值 7 (expected=5000, actual=5000)
  ok   易变默认值 clock_timestamp()：出现 rewriting table 消息
  ok   易变默认值：relfilenode 改变（表被重写）

CASE S5 :: CHECK ... NOT VALID：不扫描既有行，但约束新写入；VALIDATE 只持 SHARE UPDATE EXCLUSIVE
  ok   NOT VALID：没有 verifying table 消息，既有 10 个空值未被检查
  ok   convalidated=false (expected=false, actual=false)
  ok   新插入的空值被拒绝（check_violation） (expected=23514, actual=23514)
  ok   边界：更新一条尚未修复的旧行的其他列，也因该行违反约束而失败 (expected=23514, actual=23514)
  ok   存在空值时 VALIDATE 失败 (expected=23514, actual=23514)
  ok   VALIDATE 事务持有 ShareUpdateExclusiveLock (expected=[ShareUpdateExclusiveLock], actual=[ShareUpdateExclusiveLock])
  ok   VALIDATE 未提交期间，另一会话的 UPDATE 不被阻塞 (expected=1, actual=1)
  ok   convalidated=true (expected=true, actual=true)

CASE S6 :: SET NOT NULL：已验证 CHECK 可证明无空值时跳过全表扫描；NOT VALID 的 CHECK 不算
  ok   SET NOT NULL 持有 AccessExclusiveLock
  ok   已验证 CHECK：出现“existing constraints ... sufficient to prove”
  ok   已验证 CHECK：没有 verifying table（未扫描）
  ok   负向对照：只有 NOT VALID 的 CHECK 时仍扫描全表（verifying table）
  ok   无约束且存在空值：扫描发现空值，SET NOT NULL 失败（not_null_violation） (expected=23502, actual=23502)

CASE S7 :: CREATE INDEX CONCURRENTLY 失败后留下 INVALID 索引；IF NOT EXISTS 重试会掩盖它；删除后重建恢复
  ok   重复值导致 CIC 失败（unique_violation） (expected=23505, actual=23505)
  ok   失败后索引仍在目录中，pg_index.indisvalid=false (expected=false, actual=false)
  ok   INVALID 索引不被查询使用
  ok   negative control exposes: 用 IF NOT EXISTS 重试“成功”返回，但索引仍是 INVALID
  ok   边界：CIC 不能在事务块内执行（active_sql_transaction） (expected=25001, actual=25001)
  ok   恢复：DROP INDEX CONCURRENTLY、修复重复值、重建后 indisvalid=true (expected=true, actual=true)
  ok   重建后的索引被查询使用

CASE S8 :: 回填与新代码写入交错：无条件写回覆盖新值；条件更新保留新值
  note 目标行 id=3，种子 priority=1（low）；并发写入把它改为 high
  note 无条件写回行数=10
  ok   negative control exposes: 读—算—无条件写回把新写入的 high 覆盖为 low
  ok   negative control exposes: 被覆盖后新旧两列仍一致（mismatch=0），列间比对查不出这次丢失
  ok   条件写回：10 行中跳过已有新值的 1 行 (expected=9, actual=9)
  ok   条件写回保留 high (expected=high, actual=high)
  ok   回填 UPDATE 在目标行上等待业务事务结束 (expected=transactionid:ShareLock, actual=transactionid:ShareLock)
  ok   阻塞者是未提交的业务事务
  ok   回填批次提交
  ok   READ COMMITTED 下 WHERE priority_level IS NULL 在新版本行上重新求值，跳过该行 (expected=9, actual=9)
  ok   新值保留 (expected=high, actual=high)
  ok   negative control exposes: 无条件 SET priority_level = lvl_of(priority) 把 urgent 改写为 high

CASE S9 :: 旧代码只写旧列：无触发器时新列落后；有兼容触发器时保持一致
  ok   回填完成时新列无空值 (expected=0, actual=0)
  ok   negative control exposes: v1 更新后新列仍是旧值（mismatch=1）
  ok   negative control exposes: v1 新插入的行新列为空（null=1）
  ok   触发器同步后 mismatch=0 (expected=0, actual=0)
  ok   v1 插入的行由触发器填入新列 (expected=0, actual=0)
  ok   id 21：priority 2 → normal (expected=normal, actual=normal)
  ok   v3 只写新列 low，v1 读到 priority=1 (expected=1, actual=1)

CASE S10 :: 不可逆点：urgent 写入之后，回退到 v1 不再无损，守卫拒绝
  ok   越过之前，数据库约束拒绝 urgent (expected=23514, actual=23514)
  ok   越过之前回退守卫放行
  ok   已越过但尚无 urgent：守卫拒绝回退（no-return point crossed）
  ok   无 urgent 行时可以撤回越过动作，守卫重新放行
  ok   存在 urgent 行时撤回失败：重新加约束的校验扫描发现违规 (expected=23514, actual=23514)
  ok   守卫拒绝回退，报告 1 行旧格式无法表达
  ok   阶段保持 P6-no-return (expected=P6-no-return, actual=P6-no-return)
  ok   negative control exposes: v1 把 urgent 读成 priority=3（high），语义已丢失
  ok   negative control exposes: v1 改优先级再改回 3 后，新列变为 high，urgent 永久丢失

CASE S11 :: 回填进程在批次事务中被 kill -9：未提交批次回滚，断点不前进；重启后续跑，新值不被覆盖
  note worker: IN_TX batch=1
  note worker: COMMITTED batch=1
  note worker: IN_TX batch=2
  note worker: COMMITTED batch=2
  note worker: IN_TX batch=3
  note worker: COMMITTED batch=3
  note worker: IN_TX batch=4
  ok   kill 前，回填会话停在第 4 批事务内（已 UPDATE、未提交） (expected=idle in transaction, actual=idle in transaction)
  note worker exit=137 last_id at kill=600
  ok   服务端清理了被杀进程的会话 (expected=0, actual=0)
  ok   断点未前进：被中断批次整体回滚 (expected=600, actual=600)
  ok   被中断批次的未触碰行仍为空（158 行）
  ok   重启的回填进程正常结束 (expected=0, actual=0)
  note worker2: DONE batches=17
  ok   业务写入无错误 (expected=0, actual=0)
  ok   断点到达 max_id (expected=4000, actual=4000)
  ok   新列无空值 (expected=0, actual=0)
  ok   新旧列一致 (expected=0, actual=0)
  ok   每行等于最后一次已提交业务写入 (expected=0, actual=0)

SUMMARY passed=96 failed=0
