fix: rollover-safe today reads under lock, INSERT INTO regex, midnight-safe dynamic dates

- upsert re-checks 'today' after acquiring the connection lock (call
  date can go stale while queued across midnight); delete cutoff
  clamps to the lock-time date too
- mutation regex now matches INSERT [OR x] INTO completion_events;
  test_schema.py's raw-SQL CHECK-constraint helper exempted narrowly
  via INSERT-strip, guard file self-scanned
- NOW replaced by _now_stamp() (today 08:00 UTC, computed at call
  time); occurred-at test derives its query date from the stamp so it
  is midnight-safe
- new rollover-contention test: a lock holder delays A's upsert across
  a shifted clock; the in-lock re-check rejects the now-past date
This commit is contained in:
Joshua King
2026-09-14 08:24:17 -04:00
parent e48123e488
commit 6e1f22e18a
2 changed files with 309 additions and 105 deletions
+22 -2
View File
@@ -183,6 +183,11 @@ class TaskInstancesDao:
child deletion cannot slip between check and insert).
"""
_validate_date(due_date, "due_date")
# Fail fast on obviously-past dates BEFORE queuing on the
# connection lock (a nice early error); the AUTHORITATIVE
# check runs after the lock is acquired, because a caller can
# wait on the lock across midnight — a date that was "today"
# at call time may be "yesterday" by the time the INSERT runs.
today = datetime.date.today().isoformat()
if due_date < today:
raise ValueError(
@@ -191,6 +196,16 @@ class TaskInstancesDao:
)
async with _connection_lock(self._database):
async with self._database.transaction():
# Re-read "today" under the lock: this is the date the
# insert actually executes on, so the no-past rule
# holds across midnight rollovers.
today = datetime.date.today().isoformat()
if due_date < today:
raise ValueError(
f"instances are never generated in the past: "
f"due_date {due_date!r} is before today "
f"{today!r}"
)
definition = await self._database.fetch_one(
"SELECT child_id FROM task_definitions WHERE id = ?",
(definition_id,),
@@ -313,10 +328,15 @@ class TaskInstancesDao:
of its date. Returns the number deleted.
"""
_validate_date(cutoff_date, "cutoff_date")
today = datetime.date.today().isoformat()
effective_cutoff = max(cutoff_date, today)
async with _connection_lock(self._database):
async with self._database.transaction():
# "today" is read under the lock: the DELETE actually
# executes on this date, so a caller queued across
# midnight cannot delete instances that became past
# while it waited (backdating is clamped to the
# execution date, not the call date).
today = datetime.date.today().isoformat()
effective_cutoff = max(cutoff_date, today)
result = await self._database.execute(
"DELETE FROM task_instances WHERE definition_id = ? "
"AND due_date >= ? AND NOT EXISTS ("