fix: publish schedules after creation watermark

This commit is contained in:
lda
2026-09-09 18:39:00 +07:00 Verified
parent 86a52adda5
commit 39993c9d5e
2 changed files with 33 additions and 4 deletions
+13 -4
View File
@@ -21,7 +21,7 @@ from wf_scheduling.history import FileScheduleHistoryRecorder, HistoryEntry
from wf_scheduling.models import OccurrenceRecord, Schedule
from wf_scheduling.occurrences import occurrence_id
from wf_scheduling.prepare import PreparationRejected, SchedulePreparer
from wf_scheduling.store import StaleScheduleRevisionError
from wf_scheduling.store import ScheduleExistsError, StaleScheduleRevisionError
from .models import (
JsonProjector,
@@ -207,11 +207,20 @@ class WorkflowScheduleApi:
}
)
self._validate_definition(schedule)
stored = store.create_schedule(schedule)
# Creation never backfills time before the revision: the consumed
# watermark starts at creation so catch-up only ever covers
# downtime after this revision, never pre-creation instants.
# watermark starts at creation so catch-up only ever covers downtime
# after this revision, never pre-creation instants. Check the id
# before this write so a duplicate create cannot overwrite the
# existing schedule's watermark; publishing the schedule last keeps
# a failed watermark write invisible to the poller.
try:
store.get_schedule(schedule_id)
except KeyError:
pass
else:
raise ScheduleExistsError(f"schedule id already exists: {schedule_id!r}")
store.save_consumed(schedule_id, now)
stored = store.create_schedule(schedule)
return _PROJECT_SCHEDULE(stored.model_dump(mode="json"))
async def get_schedule(self, *, schedule_id: str) -> ScheduleResult:
+20
View File
@@ -528,6 +528,26 @@ async def test_create_initializes_consumed_no_backfill(tmp_path: Path) -> None:
assert consumed is not None and consumed >= created_at
async def test_create_watermark_failure_does_not_publish_schedule(
tmp_path: Path,
) -> None:
"""A failed creation watermark cannot expose a backfillable schedule."""
api, sched_store, _ = _fault_harness(tmp_path / "torn_create")
sched_store.armed = True
with pytest.raises(OSError, match="injected consumed failure"):
await api.create_schedule(
schedule_id="s",
deployment_id="dep.personal",
trigger=_cron(),
misfire="latest",
)
with pytest.raises(KeyError):
sched_store.get_schedule("s")
assert sched_store.get_consumed("s") is None
async def test_update_watermark_failure_leaves_revision(tmp_path: Path) -> None:
api, sched_store, _ = _fault_harness(tmp_path / "torn_update")