sched: entry-level history cursor so tied occurrence entries paginate exactly once (B4)

This commit is contained in:
lda
2026-09-09 17:08:27 +07:00 Verified
parent c1c9500e1b
commit b3302eb197
5 changed files with 298 additions and 23 deletions
+89
View File
@@ -222,3 +222,92 @@ def test_deployment_revision_increments_on_save(tmp_path: Path) -> None:
)
)
assert artifacts.get_deployment("dep-1").revision == 2
def _tied_entry(
kind: str,
checkpoint_id: str | None,
created_minute: int,
) -> OccurrenceRecord:
"""One history entry for the shared 12:00 occurrence of schedule a."""
return OccurrenceRecord(
schedule_id="a",
occurrence_id="a|2026-09-08T12:00:00+00:00",
kind=kind, # type: ignore[arg-type]
resolved_at=datetime(2026, 9, 8, 12, 0, tzinfo=UTC),
run_id="run-1",
revision=1,
checkpoint_id=checkpoint_id,
created_at=datetime(2026, 9, 8, 12, created_minute, tzinfo=UTC),
)
def _traverse(store: FileScheduleStore, limit: int) -> list[dict]:
"""Walk every page to the end, returning all rows in visit order."""
rows: list[dict] = []
cursor: str | None = None
while True:
page = store.list_occurrences("a", cursor=cursor, limit=limit)
rows.extend(page["occurrences"]) # type: ignore[arg-type]
cursor = page["next_cursor"] # type: ignore[assignment]
if cursor is None:
assert page["total"] == len(rows)
return rows
def test_history_pagination_visits_every_tied_entry_once(tmp_path: Path) -> None:
"""Admission + repeated interruptions + completion share one cursor tie.
Every stored entry must appear exactly once across small-page
traversal (B4): the cursor carries the entry ordinal, not just the
shared ``(resolved_at, occurrence_id)`` tie.
"""
store = FileScheduleStore(tmp_path)
store.create_schedule(_schedule("a"))
store.append_history(_tied_entry("admitted", None, 0))
store.append_history(_tied_entry("interrupted", "run-1.000001", 5))
store.append_history(_tied_entry("interrupted", "run-1.000002", 9))
store.append_history(_tied_entry("completed", "run-1.000003", 14))
rows = _traverse(store, limit=1)
assert [(row["kind"], row["checkpoint_id"]) for row in rows] == [
("admitted", None),
("interrupted", "run-1.000001"),
("interrupted", "run-1.000002"),
("completed", "run-1.000003"),
]
first = store.list_occurrences("a", limit=1)
assert first["next_cursor"] is not None
assert len(str(first["next_cursor"]).split("|")) >= 3
def test_history_pagination_legacy_rows_keep_file_order(tmp_path: Path) -> None:
"""Rows persisted before the entry ordinal order by file position.
Real persisted data without ``seq`` must still traverse exactly once;
pre-ordinal two-part cursors stay accepted and resume after the tied
group exactly as they did before (no duplicates, no crash).
"""
import json
store = FileScheduleStore(tmp_path)
store.create_schedule(_schedule("a"))
history_path = tmp_path / "schedules" / "a" / "history.json"
history_path.parent.mkdir(parents=True, exist_ok=True)
legacy = [
_tied_entry("admitted", None, 0).model_dump(mode="json"),
_tied_entry("interrupted", "run-1.000001", 5).model_dump(mode="json"),
]
for item in legacy:
del item["seq"]
history_path.write_text(json.dumps(legacy), encoding="utf-8")
rows = _traverse(store, limit=1)
assert [row["kind"] for row in rows] == ["admitted", "interrupted"]
legacy_cursor = "2026-09-08T12:00:00+00:00|a|2026-09-08T12:00:00+00:00"
resumed = store.list_occurrences("a", cursor=legacy_cursor, limit=1)
assert resumed["occurrences"] == []
assert resumed["next_cursor"] is None
with pytest.raises(ValueError):
store.list_occurrences("a", cursor="a|b|c|d", limit=1)