@@ -192,14 +192,22 @@ fn genuine_missed_interval_still_synthesized_after_absorb() {
192192
193193#[ test]
194194fn flap_during_paused_steady_state_synthesized_at_resume ( ) {
195- // Arrange:loop 见暂停(处理过),暂停期内用户恢复又暂停(flap)——
196- // flag 稳态真,seq +1(flap 的 Start 已执行)
195+ // Arrange:loop 见暂停 P1(Pause 事件 @3000,上升沿已处理),随后用户恢复
196+ // +再次暂停都落在同一 <500ms 未采样窗(loop 只见稳态真)——P1 自身区间
197+ // 完成(实测 resume @3500,晚于 Pause 事件时刻 = 恢复沿丢采样)
197198 let v = PauseEdgeView { own_pending : true , pause_processed : true , last_pause_ms : Some ( 3000 ) , ..view ( true , 0 ) } ;
198- // Act:稳态真 delta 1 = 自身完成(absorb 后无漏检 )
199+ // Act:稳态真 delta 1 = own 完成(吸收后 delta_eff=0 )
199200 let ( a1, n1) = plan_edge_observation ( & v, true , 1 , Some ( PauseSource :: Manual ) , Some ( iv ( 3000 , 3500 , PauseSource :: Manual ) ) , 4000 ) ;
200- assert ! ( a1. events. is_empty( ) , "自身完成被吸收,不合成" ) ;
201- assert ! ( !a1. flush_cut) ;
202- // Act:第二个 flap 完成(delta 又 +1——真漏检)
201+ // Assert:该次恢复从未落库 → P2-3 补发合成 Resume(实测恢复沿)+ 边界切断
202+ assert_eq ! ( a1. events. len( ) , 1 ) ;
203+ assert_eq ! ( a1. events[ 0 ] . kind, PlanEventKind :: Resume ) ;
204+ assert_eq ! ( a1. events[ 0 ] . source, PauseSource :: Manual ) ;
205+ assert_eq ! ( a1. events[ 0 ] . moment_ms, 3500 ) ;
206+ assert_eq ! ( a1. missed_count, 0 , "own 完成吸收——非漏检区间,不误报" ) ;
207+ assert ! ( a1. flush_cut, "恢复后采到的内容不得与后续连句——切断边界" ) ;
208+ assert ! ( !n1. own_pending) ;
209+ assert ! ( !n1. pause_processed, "再次暂停的上升沿未处理——防御 flush 重新武装" ) ;
210+ // Act:暂停期再真漏检一个 flap(另一轮恢复+暂停,delta 又 +1——真漏检)
203211 let ( a2, _n2) = plan_edge_observation (
204212 & n1,
205213 true ,
@@ -208,9 +216,12 @@ fn flap_during_paused_steady_state_synthesized_at_resume() {
208216 Some ( iv ( 4200 , 4400 , PauseSource :: Media ) ) ,
209217 4500 ,
210218 ) ;
219+ // Assert:真漏检 flap 仍合成一对(合成 Resume 在前,事件时刻保持非降)
211220 assert_eq ! ( a2. events. len( ) , 2 , "真漏检 flap 合成一对" ) ;
221+ assert_eq ! ( a2. events[ 0 ] . kind, PlanEventKind :: Pause ) ;
212222 assert_eq ! ( a2. events[ 0 ] . moment_ms, 4200 ) ;
213223 assert ! ( a2. flush_cut, "flap 内容在引擎流里连句——恢复边沿需断句" ) ;
224+ assert ! ( a2. events[ 0 ] . moment_ms >= a1. events[ 0 ] . moment_ms, "DB 序单调" ) ;
214225}
215226
216227#[ test]
@@ -284,3 +295,101 @@ fn init_view_snapshots_paused_state_with_pending_absorption() {
284295 assert ! ( !v2. flag) ;
285296 assert ! ( !v2. own_pending) ;
286297}
298+
299+ #[ test]
300+ fn rising_absorbs_own_pending_instead_of_duplicate_synth ( ) {
301+ // Arrange(P2-2):上一可见暂停的恢复抢跑(下降沿 delta 0 已发可见
302+ // Resume,own_pending 保持真),其 Start 完成增量直到下一次可见暂停的
303+ // 上升沿才到达(manual <500ms 往返链形态)——delta 1 全数 = own 增量
304+ let v = PauseEdgeView { own_pending : true , pause_processed : true , last_pause_ms : Some ( 4000 ) , ..view ( false , 0 ) } ;
305+ // Act:上升沿 + delta 1(槽=own 区间的实测时刻)
306+ let ( action, next) = plan_edge_observation (
307+ & v,
308+ true ,
309+ 1 ,
310+ Some ( PauseSource :: Manual ) ,
311+ Some ( iv ( 3000 , 4050 , PauseSource :: Manual ) ) ,
312+ 6000 ,
313+ ) ;
314+ // Assert:own 增量被吸收——不得把本已可见的暂停重复合成为 Pause/Resume
315+ // 对(DB 序倒挂 + missed_count 误报 + 多余 flush);只发可见 Pause
316+ assert_eq ! ( action. events. len( ) , 1 ) ;
317+ assert_eq ! ( action. events[ 0 ] . kind, PlanEventKind :: Pause ) ;
318+ assert_eq ! ( action. events[ 0 ] . moment_ms, 6000 ) ;
319+ assert_eq ! ( action. missed_count, 0 , "own 完成增量吸收——不再误报漏检" ) ;
320+ assert ! ( action. flush_cut, "可见暂停上升沿照常断句" ) ;
321+ assert_eq ! ( next. last_pause_ms, Some ( 6000 ) ) ;
322+ assert ! ( next. own_pending, "新可见暂停的完成增量待吸收" ) ;
323+ assert_eq ! ( next. seq, 1 ) ;
324+ }
325+
326+ #[ test]
327+ fn rising_absorbs_own_pending_then_synths_only_extra_missed ( ) {
328+ // Arrange(P2-2 delta 2 语义):own 完成增量与一个真漏检 flap 的完成
329+ // 增量同落一次上升沿观察——吸收 own +1 后余 1 为真漏检(槽只保最近
330+ // 完成区间 = flap,先于可见暂停补合成对)
331+ let v = PauseEdgeView { own_pending : true , pause_processed : true , last_pause_ms : Some ( 4000 ) , ..view ( false , 0 ) } ;
332+ // Act
333+ let ( action, next) = plan_edge_observation (
334+ & v,
335+ true ,
336+ 2 ,
337+ Some ( PauseSource :: Manual ) ,
338+ Some ( iv ( 5000 , 5200 , PauseSource :: Media ) ) ,
339+ 9000 ,
340+ ) ;
341+ // Assert:合成对(真漏检 flap,时刻取槽)+ 可见 Pause;missed_count=1
342+ assert_eq ! ( action. events. len( ) , 3 ) ;
343+ assert_eq ! ( action. events[ 0 ] . kind, PlanEventKind :: Pause ) ;
344+ assert_eq ! ( action. events[ 0 ] . moment_ms, 5000 ) ;
345+ assert_eq ! ( action. events[ 0 ] . source, PauseSource :: Media ) ;
346+ assert_eq ! ( action. events[ 1 ] . kind, PlanEventKind :: Resume ) ;
347+ assert_eq ! ( action. events[ 1 ] . moment_ms, 5200 ) ;
348+ assert_eq ! ( action. events[ 2 ] . kind, PlanEventKind :: Pause ) ;
349+ assert_eq ! ( action. events[ 2 ] . moment_ms, 9000 ) ;
350+ assert_eq ! ( action. missed_count, 1 ) ;
351+ assert ! ( action. flush_cut) ;
352+ assert ! ( next. pause_processed) ;
353+ assert ! ( next. own_pending) ;
354+ assert_eq ! ( next. last_pause_ms, Some ( 9000 ) ) ;
355+ }
356+
357+ #[ test]
358+ fn steady_true_absorb_of_unseen_recovery_synthesizes_resume_and_cuts ( ) {
359+ // Arrange(P2-3 分支):可见暂停的 Pause 事件 @3000 已发出,随后其物理
360+ // 恢复(resume 实测 @3500,晚于 Pause 事件时刻)+再次暂停都落在同一
361+ // <500ms 未采样窗——恢复沿从未被 loop 采到、从未落库
362+ let v = PauseEdgeView { own_pending : true , pause_processed : true , last_pause_ms : Some ( 3000 ) , ..view ( true , 0 ) } ;
363+ // Act:稳态真 delta 1 = own 完成(吸收后 delta_eff=0)
364+ let ( action, next) = plan_edge_observation ( & v, true , 1 , Some ( PauseSource :: Manual ) , Some ( iv ( 3000 , 3500 , PauseSource :: Media ) ) , 4000 ) ;
365+ // Assert:补发合成 Resume(时刻=实测恢复沿,≥ Pause 事件保单调)+ 边界
366+ // 切断(恢复后采到的内容不得与后续连句)+ 防御 flush 重新武装
367+ assert_eq ! ( action. events. len( ) , 1 ) ;
368+ assert_eq ! ( action. events[ 0 ] . kind, PlanEventKind :: Resume ) ;
369+ assert_eq ! ( action. events[ 0 ] . source, PauseSource :: Media ) ;
370+ assert_eq ! ( action. events[ 0 ] . moment_ms, 3500 ) ;
371+ assert ! ( action. flush_cut) ;
372+ assert_eq ! ( action. missed_count, 0 , "own 恢复非漏检——不误报" ) ;
373+ assert ! ( !next. own_pending) ;
374+ assert ! ( !next. pause_processed, "再次暂停的上升沿未处理——恢复边沿防御 flush 兜底" ) ;
375+ assert_eq ! ( next. seq, 1 ) ;
376+ }
377+
378+ #[ test]
379+ fn steady_true_absorb_without_unseen_recovery_stays_silent ( ) {
380+ // Arrange(P2-3 对照——"无未见恢复沿"):own 完成增量是**更早一次已见
381+ // 恢复**(抢跑下降沿已发可见 Resume)的迟到完成:其 Start 在下一可见
382+ // 暂停期间才执行,完成区间 resume(@5000)不晚于已发出的 Pause 事件
383+ // 时刻(@6000 = 下一可见暂停的上升沿)
384+ let v = PauseEdgeView { own_pending : true , pause_processed : true , last_pause_ms : Some ( 6000 ) , ..view ( true , 0 ) } ;
385+ // Act:稳态真吸收 own 完成(delta_eff=0)
386+ let ( action, next) = plan_edge_observation ( & v, true , 1 , Some ( PauseSource :: Manual ) , Some ( iv ( 4000 , 5000 , PauseSource :: Manual ) ) , 7000 ) ;
387+ // Assert:静默吸收——不补 Resume 不 flush(对应恢复已按可见沿发出,
388+ // 补发即重复);防御 flush 状态保持现状
389+ assert ! ( action. events. is_empty( ) , "已见恢复的迟到完成增量静默吸收" ) ;
390+ assert ! ( !action. flush_cut) ;
391+ assert_eq ! ( action. missed_count, 0 ) ;
392+ assert ! ( !next. own_pending) ;
393+ assert ! ( next. pause_processed, "无未见恢复沿——防御 flush 不重新武装" ) ;
394+ assert_eq ! ( next. seq, 1 ) ;
395+ }
0 commit comments