Task: run the missed sweep in the API and publish nestquest_quest_missed on the SSE stream (task 058c7b69-d136-4ca4-854b-770fa9fc4b73).
Done-condition: the API can trigger the nightly missed-quest sweep and every swept instance produces exactly one documented nestquest_quest_missed transition on the live SSE stream — watermark-guarded (same-day rerun silent, post-downtime run sweeps the accumulated window once), admin-gated, with the daily rollover scheduler in the lifespan.
core/sweep.py (new, HA-free): the ONE sweep policy — per-database lock, watermark read, list_missed_unswept(today, watermark), documented payload via the shared events builder, watermark write. Returns (EVENT_QUEST_MISSED, payload) tuples; no bus, no HA imports.
custom_components/nestquest/sweep.py: thin shim over the core — keeps run_missed_sweep(hass, database, *, today=None) -> int, fires each returned event on hass.bus.
POST /api/v1/admin/missed-sweep: ONE require_admin (router-level), ONE clock read, ONE core call; each event published via api.transitions.publish_quest_missed; responds {"fired": N}.
api/scheduler.py (new): daily MissedSweepScheduler in the lifespan — reads day_rollover_time fresh each cycle, publishes on the app publisher, logs+backs off on failure, cancels cleanly on shutdown; injectable clock/sleep.
tests/test_api_missed_sweep.py: trigger over a live uvicorn server with a live SSE stream — one transition per past-due instance with the documented payload fields, today-due instance not announced, same-day rerun silent, post-downtime catch-up once, non-admin 403 / absent 401, scheduler rollover + cancellation test.
Tests: TZ=UTC uv run pytest -q → 1773 passed; TZ=UTC uv run pytest -q tests/test_api_missed_sweep.py → 5 passed.
Task: run the missed sweep in the API and publish `nestquest_quest_missed` on the SSE stream (task 058c7b69-d136-4ca4-854b-770fa9fc4b73).
**Done-condition:** the API can trigger the nightly missed-quest sweep and every swept instance produces exactly one documented `nestquest_quest_missed` transition on the live SSE stream — watermark-guarded (same-day rerun silent, post-downtime run sweeps the accumulated window once), admin-gated, with the daily rollover scheduler in the lifespan.
- **core/sweep.py (new, HA-free):** the ONE sweep policy — per-database lock, watermark read, `list_missed_unswept(today, watermark)`, documented payload via the shared events builder, watermark write. Returns `(EVENT_QUEST_MISSED, payload)` tuples; no bus, no HA imports.
- **custom_components/nestquest/sweep.py:** thin shim over the core — keeps `run_missed_sweep(hass, database, *, today=None) -> int`, fires each returned event on `hass.bus`.
- **POST /api/v1/admin/missed-sweep:** ONE `require_admin` (router-level), ONE clock read, ONE core call; each event published via `api.transitions.publish_quest_missed`; responds `{"fired": N}`.
- **api/scheduler.py (new):** daily `MissedSweepScheduler` in the lifespan — reads `day_rollover_time` fresh each cycle, publishes on the app publisher, logs+backs off on failure, cancels cleanly on shutdown; injectable clock/sleep.
- **tests/test_api_missed_sweep.py:** trigger over a live uvicorn server with a live SSE stream — one transition per past-due instance with the documented payload fields, today-due instance not announced, same-day rerun silent, post-downtime catch-up once, non-admin 403 / absent 401, scheduler rollover + cancellation test.
Tests: `TZ=UTC uv run pytest -q` → 1773 passed; `TZ=UTC uv run pytest -q tests/test_api_missed_sweep.py` → 5 passed.
WHY: The nightly missed-quest sweep only existed on the Home
Assistant plane (the rollover listener fired hass.bus), so the API
service - now the writer of record with its own settings store and
SSE stream - had no way to announce missed quests, and an admin
could not trigger a catch-up run after downtime.
- Extract the sweep policy (watermark read, list_missed_unswept
query, documented payload build, watermark write, per-database
lock) into the HA-free core (core/sweep.py), building each event
through the shared events payload builder so the shape cannot
drift; the integration's sweep.py becomes a thin shim that fires
the returned events on hass.bus, keeping its signature.
- Admin trigger POST /api/v1/admin/missed-sweep: ONE clock read,
ONE core call, each returned event published on the SSE stream
through the existing publish_quest_missed helper; returns the
fired count. Watermark makes a same-day rerun silent and a
post-downtime run sweep the accumulated window once.
- Daily scheduler (api/scheduler.py) wired in the lifespan: reads
day_rollover_time fresh each cycle, runs the sweep at the
rollover, publishes on the app publisher, backs off and logs on
failure, cancels cleanly on shutdown; injectable clock/sleep for
tests.
- Tests: route trigger over a live uvicorn server with a live SSE
stream (one transition per instance with documented fields,
same-day rerun silent, post-downtime catch-up once, non-admin
403 / absent 401) plus the scheduler rollover/cancellation test.
The missed-sweep route re-fetched every instance through
publish_quest_missed AFTER core_sweep.run_missed_sweep had already
committed its idempotency watermark. A concurrent regenerate deleting
an instance — or a transient database error — made that re-fetch raise
ValueError uncaught (a bare 500) and dropped the missed event forever:
the watermark had advanced, so no future sweep re-examines the
instance. The re-fetch also discarded the ONE shared occurred_at
stamp the sweep computed for the whole run, re-stamping each event
individually.
Publish the already-built (event_type, payload) pairs straight to the
app's ONE publisher instead — the same shape api/scheduler.py uses —
leaving no failure window between the watermark and the announcement.
The deterministic concurrent-deletion test wraps the sweep to delete a
swept instance after it built its events: the trigger must still
answer 200 and stream both sweep-built payloads (it 500'd before the
fix), and all events of one run share the run's occurred_at.
Also add the missing trailing newlines to core/sweep.py and
api/scheduler.py (P3).
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Task: run the missed sweep in the API and publish
nestquest_quest_missedon the SSE stream (task 058c7b69-d136-4ca4-854b-770fa9fc4b73).Done-condition: the API can trigger the nightly missed-quest sweep and every swept instance produces exactly one documented
nestquest_quest_missedtransition on the live SSE stream — watermark-guarded (same-day rerun silent, post-downtime run sweeps the accumulated window once), admin-gated, with the daily rollover scheduler in the lifespan.list_missed_unswept(today, watermark), documented payload via the shared events builder, watermark write. Returns(EVENT_QUEST_MISSED, payload)tuples; no bus, no HA imports.run_missed_sweep(hass, database, *, today=None) -> int, fires each returned event onhass.bus.require_admin(router-level), ONE clock read, ONE core call; each event published viaapi.transitions.publish_quest_missed; responds{"fired": N}.MissedSweepSchedulerin the lifespan — readsday_rollover_timefresh each cycle, publishes on the app publisher, logs+backs off on failure, cancels cleanly on shutdown; injectable clock/sleep.Tests:
TZ=UTC uv run pytest -q→ 1773 passed;TZ=UTC uv run pytest -q tests/test_api_missed_sweep.py→ 5 passed.Independent Claude Sonnet 5 review completed for this exact commit.