@@ -39,14 +39,17 @@ def test_within_grace_still_expects_yesterday(self):
3939
4040
4141class TestStageReports :
42- def _make_reports (self , tmp_path , date = "2026-06-10" , skip = (), tiny = ()):
42+ def _make_reports (self , tmp_path , date = None , skip = (), tiny = ()):
43+ # Each stage's report is dated at ITS OWN expected slot date (slot-aware),
44+ # so the helper stays correct regardless of per-stage schedules.
4345 for stage , (slot , ext ) in STAGES .items ():
4446 if stage in skip :
4547 continue
4648 d = tmp_path / stage
4749 d .mkdir (exist_ok = True )
50+ dt = date or expected_report_date (NOW , slot )
4851 content = "x" * 10 if stage in tiny else "x" * 500
49- (d / f"{ date } .{ ext } " ).write_text (content )
52+ (d / f"{ dt } .{ ext } " ).write_text (content )
5053
5154 def test_all_present_no_alerts (self , tmp_path ):
5255 self ._make_reports (tmp_path )
@@ -57,7 +60,8 @@ def test_missing_report_alerts(self, tmp_path):
5760 alerts = check_stage_reports (tmp_path , NOW )
5861 assert len (alerts ) == 1
5962 assert "implementation" in alerts [0 ]
60- assert "2026-06-10" in alerts [0 ]
63+ exp = expected_report_date (NOW , STAGES ["implementation" ][0 ])
64+ assert exp in alerts [0 ]
6165
6266 def test_trivially_small_report_alerts (self , tmp_path ):
6367 self ._make_reports (tmp_path , tiny = ("analysis" ,))
@@ -80,9 +84,14 @@ def _jobs_file(self, tmp_path, name, *, status="ok", last_run="2026-06-10T22:01:
8084
8185 def test_missing_report_quiet_when_job_ran_clean (self , tmp_path ):
8286 # implementation report missing, but its cron job ran ok at/after the
83- # 22:00 slot for the expected date (2026-06-10) → idle clean cycle, no alert.
87+ # slot for the expected date → idle clean cycle, no alert. Slot-aware.
88+ slot = STAGES ["implementation" ][0 ]
89+ exp = expected_report_date (NOW , slot )
8490 self ._make_reports (tmp_path , skip = ("implementation" ,))
85- jf = self ._jobs_file (tmp_path , "evolution-implementation" )
91+ jf = self ._jobs_file (
92+ tmp_path , "evolution-implementation" ,
93+ last_run = f"{ exp } T{ slot :02d} :01:00" ,
94+ )
8695 assert check_stage_reports (tmp_path , NOW , jf ) == []
8796
8897 def test_missing_report_alerts_when_job_errored (self , tmp_path ):
@@ -301,11 +310,14 @@ def test_extension_matches_output_file(self):
301310 def test_slot_hour_matches_schedule (self ):
302311 for stage , (slot , _ext ) in STAGES .items ():
303312 spec = (self .CRON_DIR / f"{ stage } .yaml" ).read_text ()
304- m = re .search (r'^schedule:\s*"(\d+)\s+(\d+)\s' , spec , re .M )
305- assert m , f"{ stage } .yaml has no parsable daily schedule"
306- assert int (m .group (2 )) == slot , (
307- f"watchdog STAGES says '{ stage } ' runs at { slot :02d} :00, "
308- f"but { stage } .yaml schedules hour { m .group (2 )} "
313+ # Hour field may be a single hour ("21") or a multi-slot list
314+ # ("1,5,9,13,17,21"); STAGES mirrors the FIRST slot.
315+ m = re .search (r'^schedule:\s*"(\d+)\s+([\d,]+)\s' , spec , re .M )
316+ assert m , f"{ stage } .yaml has no parsable schedule"
317+ first_hour = int (m .group (2 ).split ("," )[0 ])
318+ assert first_hour == slot , (
319+ f"watchdog STAGES says '{ stage } ' first slot is { slot :02d} :00, "
320+ f"but { stage } .yaml's first scheduled hour is { first_hour } "
309321 )
310322
311323
0 commit comments