A child now carries zero or more home/away presence patterns instead of
at most one schedule. Migration 9 creates presence_patterns, copies every
presence_schedules row across as a 'home' pattern named "Home schedule"
(cycle, anchor and pattern verbatim), and drops the old table in one
transaction. Migration 1 keeps creating the retired table from a frozen
copy of its DDL; schema.py no longer declares it.
PresenceEngine resolves override -> any covering away pattern (absent)
-> any covering home pattern (present) -> otherwise absent when the child
has a home pattern, else present. The home-pattern default keeps every
migrated child's presence unchanged.
The admin plane's one-per-child presence-schedule routes are replaced by
pattern CRUD: GET/POST /children/{child_id}/presence-patterns and
PATCH/DELETE /presence-patterns/{pattern_id}.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review round 1 (Codex gpt-5.6-terra, LOW): the racing test completed
synchronously and proved nothing. The executor now gates task A's
INSERT — inside its open transaction, after its overlap probe — so
task B queues on the connection lock, re-runs the probe against A's
committed row, and is rejected; exactly one lands. Suite: 990 passed.
PresenceOverridesDao.create now refuses an override that overlaps any
existing override of the SAME child (inclusive boundaries): the check
runs inside the same transaction as the INSERT under the connection
lock, so a racing create cannot slip a conflict between check and
write — a deterministic race test proves exactly one of two concurrent
overlapping creates lands. The ValueError names the conflicting
override's id, its range, and its flag. Round-trip, per-child scope,
and remove-then-recreate tests added. Suite: 990 passed.