Skip to content

Commit 0b24cdd

Browse files
committed
Sort scheduler reconciliation by UTC timestamp
1 parent 5d2cea3 commit 0b24cdd

2 files changed

Lines changed: 65 additions & 3 deletions

File tree

tests/test_scheduler_failure_state_reconciliation.py

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -566,6 +566,48 @@ def test_unknown_terminal_status_does_not_count_as_success(tmp_path: Path) -> No
566566
assert entry["recommendation"] == "replace"
567567

568568

569+
def test_runtime_ledger_and_outcomes_sort_by_normalized_timestamp(
570+
tmp_path: Path,
571+
) -> None:
572+
ledger_path = tmp_path / "runtime" / "ledgers" / "workspace-a.runtime-ledger.jsonl"
573+
append_ledger_events(
574+
ledger_path,
575+
[
576+
build_success_event(
577+
workspace_id="workspace-a",
578+
run_id="success-run",
579+
occurred_at="2026-06-02T00:00:00Z",
580+
),
581+
build_failure_event(
582+
workspace_id="workspace-a",
583+
run_id="failure-run",
584+
occurred_at="2026-06-01T23:30:00-01:00",
585+
message="later failure",
586+
),
587+
],
588+
)
589+
590+
events = scheduler_reconciliation.read_runtime_ledger(ledger_path, workspace_id="workspace-a")
591+
assert [event["run_id"] for event in events] == ["success-run", "failure-run"]
592+
593+
outcomes = scheduler_reconciliation.summarize_run_outcomes(events)
594+
assert [outcome.run_id for outcome in outcomes] == ["success-run", "failure-run"]
595+
596+
derived, reasons, _ = scheduler_reconciliation.derive_failure_state(
597+
current_failure_state=None,
598+
run_budget=None,
599+
retry_policy=None,
600+
events=events,
601+
)
602+
assert derived == {
603+
"status": "retryable",
604+
"attempt_count": 1,
605+
"last_failure_at": "2026-06-01T23:30:00-01:00",
606+
"last_failure_reason": "later failure",
607+
}
608+
assert reasons == ["1 consecutive terminal runtime failure(s) since the last success"]
609+
610+
569611
def test_reconciliation_keeps_current_state_without_terminal_runs(tmp_path: Path) -> None:
570612
workspace_root = tmp_path / "workspace"
571613
workspace_root.mkdir()

tools/common/scheduler_failure_reconciliation.py

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,10 @@ def parse_timestamp(raw_value: str, *, label: str) -> datetime:
4242
return parsed.astimezone(UTC)
4343

4444

45+
def occurrence_sort_key(raw_value: str, *, label: str, tie_breaker: str) -> tuple[datetime, str]:
46+
return parse_timestamp(raw_value, label=label), tie_breaker
47+
48+
4549
def read_runtime_ledger(path: Path, *, workspace_id: str) -> list[dict[str, Any]]:
4650
if not path.exists():
4751
return []
@@ -64,7 +68,13 @@ def read_runtime_ledger(path: Path, *, workspace_id: str) -> list[dict[str, Any]
6468
)
6569
parse_timestamp(occurred_at, label=f"{path}:{line_number}:occurred_at")
6670
events.append(payload)
67-
events.sort(key=lambda event: (event["occurred_at"], str(event.get("event_id", ""))))
71+
events.sort(
72+
key=lambda event: occurrence_sort_key(
73+
str(event["occurred_at"]),
74+
label=f"{path}:occurred_at",
75+
tie_breaker=str(event.get("event_id", "")),
76+
)
77+
)
6878
return events
6979

7080

@@ -84,7 +94,11 @@ def summarize_run_outcomes(events: list[dict[str, Any]]) -> list[RunOutcome]:
8494
if not terminal_events:
8595
continue
8696
terminal_events.sort(
87-
key=lambda event: (event["occurred_at"], str(event.get("event_id", "")))
97+
key=lambda event: occurrence_sort_key(
98+
str(event["occurred_at"]),
99+
label="terminal runtime-ledger occurred_at",
100+
tie_breaker=str(event.get("event_id", "")),
101+
)
88102
)
89103
last_event = terminal_events[-1]
90104
event_type = last_event.get("event_type")
@@ -129,7 +143,13 @@ def summarize_run_outcomes(events: list[dict[str, Any]]) -> list[RunOutcome]:
129143
)
130144
)
131145

132-
outcomes.sort(key=lambda outcome: (outcome.occurred_at, outcome.run_id))
146+
outcomes.sort(
147+
key=lambda outcome: occurrence_sort_key(
148+
outcome.occurred_at,
149+
label="runtime-ledger outcome occurred_at",
150+
tie_breaker=outcome.run_id,
151+
)
152+
)
133153
return outcomes
134154

135155

0 commit comments

Comments
 (0)