Closes the Feature 16 feature-review P2 (Maestro task a232c4f5-3a09-4c00-bf7c-1476092be01f).
The sweep guarded its announcement with a read-then-set watermark inside a per-process asyncio lock, so two processes sharing one SQLite file (the HA integration rollover sweep and the API scheduler) could both read the old watermark and both announce the same missed batch.
MetaStateDao.claim: a single conditional upsert (INSERT ... ON CONFLICT DO UPDATE ... WHERE value IS NOT excluded.value) whose rowcount says whether THIS caller changed the row — atomic at the database level, not just within one process. get/set intact.
run_missed_sweep: builds events optimistically, then gates the announcement on the claim; the loser returns []. The claim happens only after the events are built, so a failure before it leaves the watermark unadvanced (a later run retries). Same [watermark, today) window, same payload shape, in-process lock kept.
Tests: two independent NestQuestDatabase wrappers (the two-process shape) racing one seeded database yield exactly one announcer; same-night rerun announces nothing; post-downtime run announces the accumulated window once.
Done-condition: a test proves two concurrent claimants cannot both announce the same missed batch (tests/test_core_sweep.py::test_two_process_shape_race_exactly_one_announces).
Closes the Feature 16 feature-review P2 (Maestro task a232c4f5-3a09-4c00-bf7c-1476092be01f).
The sweep guarded its announcement with a read-then-set watermark inside a per-process asyncio lock, so two processes sharing one SQLite file (the HA integration rollover sweep and the API scheduler) could both read the old watermark and both announce the same missed batch.
- `MetaStateDao.claim`: a single conditional upsert (`INSERT ... ON CONFLICT DO UPDATE ... WHERE value IS NOT excluded.value`) whose rowcount says whether THIS caller changed the row — atomic at the database level, not just within one process. `get`/`set` intact.
- `run_missed_sweep`: builds events optimistically, then gates the announcement on the claim; the loser returns []. The claim happens only after the events are built, so a failure before it leaves the watermark unadvanced (a later run retries). Same `[watermark, today)` window, same payload shape, in-process lock kept.
- Tests: two independent `NestQuestDatabase` wrappers (the two-process shape) racing one seeded database yield exactly one announcer; same-night rerun announces nothing; post-downtime run announces the accumulated window once.
Done-condition: a test proves two concurrent claimants cannot both announce the same missed batch (tests/test_core_sweep.py::test_two_process_shape_race_exactly_one_announces).
The sweep guarded its announcement with a read-then-set watermark
inside a per-process asyncio lock, so two processes sharing one SQLite
file (the HA integration's rollover sweep and the API scheduler) could
both read the old watermark and both announce the same missed batch.
Add MetaStateDao.claim: a single conditional upsert
(INSERT ... ON CONFLICT DO UPDATE ... WHERE value IS NOT excluded.value)
whose rowcount says whether THIS caller changed the row — the
read-compare-write is one SQLite statement, atomic at the database
level, not just within one process. run_missed_sweep now builds the
events optimistically and gates the announcement on that claim: the
caller that advances the watermark returns the events, a loser returns
[]. The claim happens only after the events are built, so a failure
before it leaves the watermark unadvanced and a later run retries;
get/set are intact and the in-process sweep lock still serialises
same-process callers.
Tests prove two independent NestQuestDatabase wrappers (the
two-process shape) racing one seeded database yield exactly one
announcer, plus the unchanged same-night no-op and post-downtime
window-once semantics.
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.
Closes the Feature 16 feature-review P2 (Maestro task a232c4f5-3a09-4c00-bf7c-1476092be01f).
The sweep guarded its announcement with a read-then-set watermark inside a per-process asyncio lock, so two processes sharing one SQLite file (the HA integration rollover sweep and the API scheduler) could both read the old watermark and both announce the same missed batch.
MetaStateDao.claim: a single conditional upsert (INSERT ... ON CONFLICT DO UPDATE ... WHERE value IS NOT excluded.value) whose rowcount says whether THIS caller changed the row — atomic at the database level, not just within one process.get/setintact.run_missed_sweep: builds events optimistically, then gates the announcement on the claim; the loser returns []. The claim happens only after the events are built, so a failure before it leaves the watermark unadvanced (a later run retries). Same[watermark, today)window, same payload shape, in-process lock kept.NestQuestDatabasewrappers (the two-process shape) racing one seeded database yield exactly one announcer; same-night rerun announces nothing; post-downtime run announces the accumulated window once.Done-condition: a test proves two concurrent claimants cannot both announce the same missed batch (tests/test_core_sweep.py::test_two_process_shape_race_exactly_one_announces).
Independent Claude Sonnet 5 review completed for this exact commit.