fix: reconcile admitted schedule candidates

This commit is contained in:
lda
2026-09-09 22:18:23 +07:00 Verified
parent 595a1fe827
commit 80b26bff4e
3 changed files with 116 additions and 3 deletions
+62
View File
@@ -38,6 +38,8 @@ from wf_core import InterruptRequest, RunState, RunStatus
from wf_platform import CapabilitySource
from wf_scheduling.history import FileScheduleHistoryRecorder, HistoryEntry
from wf_scheduling.models import PendingCandidate
from wf_scheduling.ownership import SchedulerOwnership
from wf_scheduling.recovery import recover
from wf_scheduling.store import (
FileScheduleStore,
ScheduleExistsError,
@@ -787,6 +789,66 @@ async def test_occurrences_pending_synthesis_first_page_only(tmp_path: Path) ->
assert all(row["kind"] != "pending" for row in plain["occurrences"])
async def test_admitted_occurrence_replaces_retained_candidate_after_recovery(
tmp_path: Path,
) -> None:
"""A torn candidate clear cannot expose an admitted occurrence as pending."""
root = tmp_path / "admitted_candidate"
api, sched_store, run_store, artifact_store, _ = _harness(root)
await api.create_schedule(
schedule_id="s", deployment_id="dep.personal", trigger=_cron()
)
intended = ts(2026, 9, 8, 13, 0)
admission = persist_admission(
store=run_store,
run_id=run_store.allocate_run_id(),
environment=_env(artifact_store),
resolved_input={},
max_steps=None,
scheduled_at=intended,
schedule_id="s",
schedule_revision=1,
)
FileScheduleHistoryRecorder(sched_store).record(
HistoryEntry(
schedule_id="s",
kind="admitted",
resolved_at=intended,
run_id=admission.id,
revision=1,
reason="rev=1",
created_at=intended,
)
)
# Fault injection: admission/history are durable, but candidate clearing
# was lost at the following persistence boundary.
sched_store.save_candidate(
PendingCandidate(schedule_id="s", intended_at=intended, revision=1),
schedule_id="s",
)
before_recovery = await api.list_schedule_occurrences(schedule_id="s", limit=1)
assert before_recovery["total"] == 1
assert before_recovery["occurrences"][0]["kind"] == "admitted"
ownership = SchedulerOwnership(root, owner="test").acquire()
try:
recover(
schedule_store=sched_store,
run_store=run_store,
now=intended,
ownership=ownership,
)
finally:
ownership.release()
assert sched_store.get_candidate("s") is None
after_recovery = await api.list_schedule_occurrences(schedule_id="s", limit=1)
assert after_recovery["total"] == 1
assert after_recovery["occurrences"][0]["kind"] == "admitted"
assert after_recovery["occurrences"][0]["run_id"] == admission.id
async def test_occurrences_pending_limit_one_traverses_every_row(
tmp_path: Path,
) -> None: