Add LIFECYCLE tests that wire an entry via make_entry(options=...) and
call async_setup_entry, covering the from_options_resilient fallback
production actually uses (P2): a fully malformed options dict
(horizon_days=True, day_rollover_time='99:99',
morning_summary_time='25:00') succeeds with the day-rollover listener
at the default 00:00 and the backfill over DEFAULT_HORIZON_DAYS; and a
bad sibling (horizon_days=5, morning_summary_time='25:00') keeps
horizon_days == 5. Swapping from_options_resilient for from_options in
setup now fails these tests.
Reword comments/tests describing review history rather than the code:
__init__.py setup comment, test_core_settings P2-3/P2-1/P2-5 markers,
the moved from_options horizon test, and the AST scanner docstrings.
Test hygiene: move freeze_nestquest_clock into tests/conftest.py and
use it from test_core_settings and test_timezones; simplify CORE_PKG to
a plain import plus Path(mod.__file__).parent; move
test_from_options_horizon_validates_and_defaults into test_core_settings.
1575 passed.
The harness tracks entities per entry so a platform unload removes
exactly that entry's entities; lifecycle tests prove deactivated
children drop out on reload, reactivation restores the original
unique_ids, new children appear without disturbing existing ones,
and the restart equivalent restores live states.
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.
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.
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.
Review round 4 (Codex gpt-5.6-terra, HIGH): relying on the flow
context alone can leave first setup ownerless. seed_setup_admin gains
an owner_ids tier: when no persisted copy and no context user exist,
every HA owner account is seeded (owners can already do everything in
HA — no privilege escalation). __init__ resolves owners via
hass.auth.async_get_users on both setup paths; conftest's hass gains
the auth surface. Tests: owner fallback seeds only owners, context
user still wins over owners, owner_ids argument validation. Suite:
910 passed.
Review rounds 1-2 (Codex gpt-5.6-terra) found two HIGHs against real HA:
- entry.context is not a durable record — the config flow now persists
the completing HA user into entry data (CONF_ADMIN_USER_IDS) and the
seed precedence becomes: options copy (the current list) first, then
the config flow's entry-data copy, then the context user as a last
resort. Config-flow tests cover the persisted user and the
context-free path; a seed-precedence test covers options > data.
- the hand-rolled closure could not serialize as an HA multi-select.
The harness now mocks homeassistant.helpers.config_validation with a
faithful multi_select, and the flow uses cv.multi_select exactly as
real HA code does. Suite: 904 passed.
- admin_allowlist.py: list_admin_ids / set_admin_ids (atomic sync of
the allowlist inside one transaction under the connection lock;
refuses empty targets so the last admin can never be removed and
the allowlist can never be emptied — fail closed) and
seed_setup_admin (seeds ONLY an empty allowlist: stored entry-data
copy first — disaster recovery for a lost database — else the HA
user who completed the config flow from the entry context;
non-empty lists are never resurrected).
- __init__.py: both setup paths (first setup and the reopened-record
path) seed after migrations; a failed seed closes the database and
re-raises through the existing error handling.
- options_flow.py: the admin picker is a multi-select of EXISTING HA
users (hass.auth.async_get_users), defaulted to the current
database allowlist, membership-checked at the schema level and
backed up on the raw-input path; empty selections are refused
(no_admins), unknown ids rejected (invalid_admin); the selection is
applied to the DB when the runtime record is reachable and always
stored on the entry as the recovery copy. Submissions without the
key (legacy raw payloads) keep the three-key behaviour untouched.
- const.CONF_ADMIN_USER_IDS; strings + translations for the field and
the two errors; conftest entries gain a context dict (HA's entry
context carries the flow user).
- Tests: 20 allowlist business tests (round-trips, refusal matrix,
seed precedence and narrowed-list persistence), 5 picker flow tests,
3 setup-seeding lifecycle tests. Suite: 901 passed.