Iterates present children from the Feature 10 presence sensors, lists
what each owes today from the remaining sensor, omits absent children
entirely. Blueprint inputs (time, notify service, send toggle); the
notify target is the household's choice, never hard-coded. PyYAML and
Jinja2 join the dev-only dependencies for blueprint template tests.
Notify target (empty default, household fills their own service),
three strict HH:MM automation times (08:00/15:00/20:00 — the report
deliberately before the midnight sweep), and four independent enable
toggles, all leniently stored and round-tripped.
The whole watermark read-query-fire-write span runs under a
per-database sweep lock; a racing second sweep re-reads the watermark
inside the lock and announces nothing.
Fires one nestquest_quest_missed event per past-due open instance at
the configured rollover time and at startup, watermark-guarded in the
new nestquest_meta_state table (migration 7, strictly additive).
Read-only over the domain tables; never reports an instance that was
ever completed.
The completed event's was_on_time travels on the CompletionResult
(decided with the write) instead of re-reading latest state a racing
reversal could replace; unload clears coordinator listeners before
the database close.
NestQuestCoordinator.async_refresh holds a per-entry lock so
overlapping service-path refreshes never interleave; the queued pass
always publishes after the in-flight one.
complete_quest and uncomplete_quest force a coordinator refresh when
the call appended, so entity states land the moment the service call
returns; scheduled internals are unaffected.
complete/uncomplete return a CompletionResult str subclass carrying
appended (decided with the write under the lock), so duplicate
concurrent calls fire one transition event; day-complete only
evaluates for an instance due today.
quest_completed and quest_uncompleted fire on actual transitions with
documented payloads; child_day_complete fires when a completion clears
the child's day; quest_missed is defined as Feature 11's contract.
Payloads documented in the README for automation authors.
Household quests due/completed today sum the active children's
snapshots; cycle day reports the first scheduled child's cycle
position (0 when nobody is scheduled), anchor-date arithmetic only.
all_done is on exactly when the child owed quests today and owes none
remaining (a zero-quest day stays off so celebrations never fire on
an empty day); present_today mirrors the presence engine's answer
for the HA-local date.
State carries the earliest open quest's title truncated under the
255-character limit with the full shape in attributes; the documented
"none" sentinel is reported when the day is clear.
A False platform unload now returns False and retains the runtime
record so live entities keep their database. Sensor names and device
labels re-derive from the current snapshot so child renames
propagate; unique_ids stay child-id based.
Four sensors per active child read the coordinator snapshot: quests
due today (carrying the panel payload that omits missed instances plus
the admin payload that includes them, D-009), completed, remaining, and
completion percentage (100 on a zero-quest day). Platform forwarding
wired through the harness's entity registry stand-ins.
The setup except path closes the database; the coordinator created
above it now shuts down first so no listener can fire against the
closed connection. Regression test included.
One refresh pass builds the whole entity snapshot: active children,
today's instances with derived open/done/missed states, per-child
presence via the PresenceEngine (records converted to model objects),
rollup counts, and the custody cycle day. Interval configurable via
the new update_interval option, default five minutes.
permission_gate wraps all registered services at registration time:
admin-only policy reads call.context.user_id against admin_allowlist
is_admin (fail closed on no context); complete_quest stays open.
PresenceEngine.next_present_dates(child_id, start_date, count):
walks forward from the INCLUSIVE start date through is_present, so
overrides and the pattern are honoured identically — this is the
preview the admin Schedule tab renders before a parent saves a
custody pattern. Count must be a positive int; the scan is capped at
two years (MAX_PREVIEW_SCAN_DAYS) and an always-absent child raises
a clear ValueError instead of looping forever. Tests: known two-week
pattern (straight five-day week, mid-week start skipping the absent
week), inclusive start, override blocking the preview start,
schedule-free child, input validation, always-absent error. Suite:
1002 passed.
Review round 1 (Codex gpt-5.6-terra, MEDIUM): the overrides mapping
was shallow-copied, leaving caller-owned lists mutable through
_overrides — a later append would change is_present results. Each
validated list is copied into an immutable tuple before wrapping;
tests prove post-construction appends change nothing and the snapshot
refuses mutation. Suite: 996 passed.
PresenceEngine gains an overrides snapshot (child_id -> list of
PresenceOverride): is_present checks covering overrides BEFORE the
pattern and returns the override's flag as the answer — an absent
override on a normally-present day returns False, a present one on a
normally-absent day returns True, and an override on a child with no
schedule still applies (holidays work for the always-present sibling).
Snapshot validation mirrors the schedules': dict of lists keyed by the
override's own child id, exposed read-only. The DAO layer guarantees
no overlapping overrides per child; a hand-built snapshot that carries
them resolves deterministically to the LATEST start date (documented).
Tests: all three precedence cases, span edges (outside days stay on
the pattern), snapshot rejection matrix, deterministic overlap
tie-break. Suite: 995 passed.
The matrix test's end-before-start probe (expecting the schema's
end>=start IntegrityError) hit the new overlap query first, since an
inverted range lexically 'overlaps' the seeded row. The overlap
check now runs only for well-ordered ranges; invalid ones fall
through to the INSERT and keep the IntegrityError contract. 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.
Review round 1 (Codex gpt-5.6-terra, 2 LOW): engine snapshot keys now
pass _validate_child_id (True/1.0 compare equal to 1 and would be
accepted then behave differently on lookup); the always-present test
now walks all 28 days asserting all three children's expected verdicts
instead of sampling every third day. Suite: 985 passed.