ae7e90ae-84a5-49b6-9381-fb5e9e797cce — Panel plane: service-token auth and snapshot route (Feature 16 — NestQuest API Service)
Done-condition (verbatim)
GET /api/v1/panel/snapshot returns today's full household snapshot for a seeded household: children in sort order, each child's instances in the panel payload shape (missed instances OMITTED), per-child counts, presence, and cycle day. The route requires the panel service token: a request with no token and a request with a wrong token both return 401; a valid token returns 200 with the documented payload shape. The token check is implemented as ONE reusable dependency (e.g. api/dependencies.py require_panel_token) that the later panel routes reuse. Tests cover missing/wrong/valid token and assert the snapshot payload shape.
Changes
api/dependencies.py (new) — require_panel_token, the ONE reusable panel-plane token dependency: reads the configured token off app.state.config, fails closed with the same uniform 401 (constant-time compare, WWW-Authenticate: Bearer) on a missing Authorization header, a malformed header, a wrong token, and an empty configured token. Later panel routes reuse it (the panel router attaches it once, so routes added to the router inherit the check).
api/config.py — ApiConfig gains the required panel_token field read from NESTQUEST_PANEL_TOKEN, with the same fail-fast discipline as NESTQUEST_DB_PATH (unset/empty → ConfigError, no silent default). Still a frozen dataclass.
api/routes_panel.py (new) — GET /api/v1/panel/snapshot: a thin adapter that calls the pure core build_snapshot(database, settings, now) with the live database from app.state.db and one timezone-aware clock read, shapes each child's instances through core instance_payload(instances, include_missed=False) (missed instances OMITTED, D-009), and returns today_iso, cycle_day, and children in sort order (child_id, child_name, present, next_present, due_today/completed_today/remaining_today/completion_pct, panel instances). Response shape documented in the module docstring and the PanelSnapshotResponse/PanelChildPayload/PanelInstancePayload Pydantic models (also emitted in OpenAPI).
api/app.py — stores the resolved config on app.state.config and includes the panel router.
tests/test_api_panel.py (new) — seeds the household through the SAME nestquest_core.* copy the app uses (two-copy caveat respected); asserts 401 for no/wrong/malformed tokens, 200 with the full documented payload for the correct token (children in sort order, per-child counts, presence, cycle_day, next_present), that a past-due open (missed) instance is absent from the panel payload, plus NESTQUEST_PANEL_TOKEN from_env fail-fast coverage.
tests/test_api_health.py — mechanical update only: the two ApiConfig(db_path=...) constructions now pass the required panel_token field; test_from_env_returns_set_value asserts both env values. No assertions weakened.
Test result
uv run pytest -q tests/test_api_panel.py → 10 passed in 0.23s TZ=UTC uv run pytest -q (full suite) → 1599 passed in 6.54s (baseline 1589 + 10 new; all green)
Note: bare uv run pytest -q at this hour (host EDT past 20:00, so the UTC calendar date is already the next day) shows 19 failed, 1580 passed — those exact 19 failures reproduce on the pristine feature branch via git stash -u (1570 passed / 19 failed) and are a pre-existing local-vs-UTC date skew in the environment, unrelated to this change. Under TZ=UTC the full suite is 1599 passed.
## Maestro task
`ae7e90ae-84a5-49b6-9381-fb5e9e797cce` — Panel plane: service-token auth and snapshot route (Feature 16 — NestQuest API Service)
## Done-condition (verbatim)
GET /api/v1/panel/snapshot returns today's full household snapshot for a seeded household: children in sort order, each child's instances in the panel payload shape (missed instances OMITTED), per-child counts, presence, and cycle day. The route requires the panel service token: a request with no token and a request with a wrong token both return 401; a valid token returns 200 with the documented payload shape. The token check is implemented as ONE reusable dependency (e.g. api/dependencies.py require_panel_token) that the later panel routes reuse. Tests cover missing/wrong/valid token and assert the snapshot payload shape.
## Changes
- **api/dependencies.py (new)** — `require_panel_token`, the ONE reusable panel-plane token dependency: reads the configured token off `app.state.config`, fails closed with the same uniform 401 (constant-time compare, `WWW-Authenticate: Bearer`) on a missing Authorization header, a malformed header, a wrong token, and an empty configured token. Later panel routes reuse it (the panel router attaches it once, so routes added to the router inherit the check).
- **api/config.py** — `ApiConfig` gains the required `panel_token` field read from `NESTQUEST_PANEL_TOKEN`, with the same fail-fast discipline as `NESTQUEST_DB_PATH` (unset/empty → `ConfigError`, no silent default). Still a frozen dataclass.
- **api/routes_panel.py (new)** — `GET /api/v1/panel/snapshot`: a thin adapter that calls the pure core `build_snapshot(database, settings, now)` with the live database from `app.state.db` and one timezone-aware clock read, shapes each child's instances through core `instance_payload(instances, include_missed=False)` (missed instances OMITTED, D-009), and returns `today_iso`, `cycle_day`, and children in sort order (`child_id`, `child_name`, `present`, `next_present`, due_today/completed_today/remaining_today/completion_pct, panel instances). Response shape documented in the module docstring and the `PanelSnapshotResponse`/`PanelChildPayload`/`PanelInstancePayload` Pydantic models (also emitted in OpenAPI).
- **api/app.py** — stores the resolved config on `app.state.config` and includes the panel router.
- **api/nestquest_core.py** — adds `core_snapshot` / `core_settings` re-exports (same pattern as `core_db`/`core_migrations`).
- **tests/test_api_panel.py (new)** — seeds the household through the SAME `nestquest_core.*` copy the app uses (two-copy caveat respected); asserts 401 for no/wrong/malformed tokens, 200 with the full documented payload for the correct token (children in sort order, per-child counts, presence, `cycle_day`, `next_present`), that a past-due open (missed) instance is absent from the panel payload, plus `NESTQUEST_PANEL_TOKEN` from_env fail-fast coverage.
- **tests/test_api_health.py** — mechanical update only: the two `ApiConfig(db_path=...)` constructions now pass the required `panel_token` field; `test_from_env_returns_set_value` asserts both env values. No assertions weakened.
## Test result
`uv run pytest -q tests/test_api_panel.py` → `10 passed in 0.23s`
`TZ=UTC uv run pytest -q` (full suite) → `1599 passed in 6.54s` (baseline 1589 + 10 new; all green)
Note: bare `uv run pytest -q` at this hour (host EDT past 20:00, so the UTC calendar date is already the next day) shows `19 failed, 1580 passed` — those exact 19 failures reproduce on the pristine feature branch via `git stash -u` (1570 passed / 19 failed) and are a pre-existing local-vs-UTC date skew in the environment, unrelated to this change. Under `TZ=UTC` the full suite is 1599 passed.
The panel plane (D-012) needs an authenticated read surface the HA
integration polls: GET /api/v1/panel/snapshot. The token check lives in
ONE reusable dependency (api/dependencies.require_panel_token) — missing,
malformed, wrong, and empty-configured credentials all fail closed with
the same 401 (constant-time compare, uniform detail) — and the panel
router attaches it once so every later panel route inherits the check.
ApiConfig gains the required NESTQUEST_PANEL_TOKEN (same fail-fast
discipline as NESTQUEST_DB_PATH: unset/empty is a ConfigError, never a
silent default), create_app stores the resolved config on app.state so
the dependency reads it without module state, and the snapshot handler
stays a thin adapter: it calls the pure core build_snapshot with the
live database and one timezone-aware clock read, shapes each child's
instances through core.instance_payload(include_missed=False) so missed
instances are omitted from the panel payload (D-009), and returns the
shape documented by the PanelSnapshotResponse models in OpenAPI.
Tests (tests/test_api_panel.py) seed the household through the SAME
nestquest_core.* copy the app uses, pin the route's single clock read,
and assert 401 for missing/wrong/malformed tokens, 200 with the full
documented payload (children in sort order, counts, presence,
cycle_day), and that a past-due open (missed) instance never reaches
the payload.
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.
Maestro task
ae7e90ae-84a5-49b6-9381-fb5e9e797cce— Panel plane: service-token auth and snapshot route (Feature 16 — NestQuest API Service)Done-condition (verbatim)
GET /api/v1/panel/snapshot returns today's full household snapshot for a seeded household: children in sort order, each child's instances in the panel payload shape (missed instances OMITTED), per-child counts, presence, and cycle day. The route requires the panel service token: a request with no token and a request with a wrong token both return 401; a valid token returns 200 with the documented payload shape. The token check is implemented as ONE reusable dependency (e.g. api/dependencies.py require_panel_token) that the later panel routes reuse. Tests cover missing/wrong/valid token and assert the snapshot payload shape.
Changes
require_panel_token, the ONE reusable panel-plane token dependency: reads the configured token offapp.state.config, fails closed with the same uniform 401 (constant-time compare,WWW-Authenticate: Bearer) on a missing Authorization header, a malformed header, a wrong token, and an empty configured token. Later panel routes reuse it (the panel router attaches it once, so routes added to the router inherit the check).ApiConfiggains the requiredpanel_tokenfield read fromNESTQUEST_PANEL_TOKEN, with the same fail-fast discipline asNESTQUEST_DB_PATH(unset/empty →ConfigError, no silent default). Still a frozen dataclass.GET /api/v1/panel/snapshot: a thin adapter that calls the pure corebuild_snapshot(database, settings, now)with the live database fromapp.state.dband one timezone-aware clock read, shapes each child's instances through coreinstance_payload(instances, include_missed=False)(missed instances OMITTED, D-009), and returnstoday_iso,cycle_day, and children in sort order (child_id,child_name,present,next_present, due_today/completed_today/remaining_today/completion_pct, panel instances). Response shape documented in the module docstring and thePanelSnapshotResponse/PanelChildPayload/PanelInstancePayloadPydantic models (also emitted in OpenAPI).app.state.configand includes the panel router.core_snapshot/core_settingsre-exports (same pattern ascore_db/core_migrations).nestquest_core.*copy the app uses (two-copy caveat respected); asserts 401 for no/wrong/malformed tokens, 200 with the full documented payload for the correct token (children in sort order, per-child counts, presence,cycle_day,next_present), that a past-due open (missed) instance is absent from the panel payload, plusNESTQUEST_PANEL_TOKENfrom_env fail-fast coverage.ApiConfig(db_path=...)constructions now pass the requiredpanel_tokenfield;test_from_env_returns_set_valueasserts both env values. No assertions weakened.Test result
uv run pytest -q tests/test_api_panel.py→10 passed in 0.23sTZ=UTC uv run pytest -q(full suite) →1599 passed in 6.54s(baseline 1589 + 10 new; all green)Note: bare
uv run pytest -qat this hour (host EDT past 20:00, so the UTC calendar date is already the next day) shows19 failed, 1580 passed— those exact 19 failures reproduce on the pristine feature branch viagit stash -u(1570 passed / 19 failed) and are a pre-existing local-vs-UTC date skew in the environment, unrelated to this change. UnderTZ=UTCthe full suite is 1599 passed.Independent Claude Sonnet 5 review completed for this exact commit.