From 10f365ef9f04a8d9084ccd41067808cb3d6bdc90 Mon Sep 17 00:00:00 2001 From: Joshua Date: Mon, 21 Sep 2026 13:10:57 -0400 Subject: [PATCH 1/3] Add the re-imagined architecture document --- docs/ARCHITECTURE.md | 203 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 203 insertions(+) create mode 100644 docs/ARCHITECTURE.md diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md new file mode 100644 index 0000000..054b20e --- /dev/null +++ b/docs/ARCHITECTURE.md @@ -0,0 +1,203 @@ +# NestQuest Architecture + +Status: approved direction (2026-09-21). This document supersedes the +single-process design implied by Features 01–14, in which the Home Assistant +integration owned the database and the parent admin surface was planned as a +Lovelace card inside Home Assistant. + +## 1. Why this changed + +The original design put everything inside the Home Assistant integration: the +SQLite database, the business logic, the kid panel cards, and (planned) the +parent admin card. Three problems surfaced: + +1. **The panel needed hand configuration.** Standing up the kid panel required + hand-writing card YAML (`child_order`, `quest_log_path`, `board_path`) and + hand-creating a Lovelace dashboard. A fresh install rendered nothing. +2. **The admin surface fought Home Assistant.** The admin design + (`design/ADMIN-SPEC.md`) is drawn at 390×844 — it is a phone app. Trapping it + inside a Lovelace card meant fighting HA's UI constraints for a mobile-first + experience. +3. **Data was locked to one process.** A standalone admin web app cannot reach a + SQLite file inside Home Assistant's config directory. + +The re-imagined design keeps the panel in Home Assistant (TouchHub loads an HA +dashboard), moves data ownership to a standalone API service, and promotes the +admin surface to a standalone mobile-first PWA. + +## 2. Target architecture + +``` + DAKboard TouchHub + │ (loads an HA dashboard URL) + ▼ + HA box API box + ┌───────────────────────────────┐ ┌───────────────────────────────────┐ + │ Lovelace panel dashboard │ │ reverse proxy (TLS) │ + │ ├ nestquest-party-board-card │ service │ ├ /api/v1/panel/* LAN-only │ + │ └ nestquest-quest-log-card │ token │ └ /api/v1/admin/* public │ + │ │ ─────────▶ │ FastAPI (imports core/) │ + │ NestQuest integration │ │ └ SQLite (owned here) │ + │ (thin client, no database) │ ◀───────── │ Authentik (OIDC) │ + │ sensors + HA bus events │ SSE └───────────────────────────────────┘ + └───────────────────────────────┘ ▲ + PWA ───────────┘ + https://nestquest.cubecraftlabs.com +``` + +One owner of the data (the API). Two clients (the HA integration, the PWA). + +## 3. Data ownership + +**The API service is the only writer.** The HA integration never opens the +database; it reads a snapshot and posts completions. This removes the +two-writer problem entirely. + +- **Storage:** SQLite on the API box. Single household, trivial concurrency, + and the existing schema, migrations, and DAO tests are already SQLite. Postgres + buys nothing at this scale. +- **Schema:** unchanged. The cutover from the current HA-hosted database is a + file copy — same schema, same migration version. +- **Backups:** continuous replication (e.g. Litestream) plus a proven restore. + A configured-but-untested backup is not a backup. + +## 4. Authentication: two planes + +The kid panel and the admin PWA have opposite trust levels, so they use +separate credentials and separate network exposure. + +### Panel plane (machine) + +- **Credential:** a static service token. +- **Exposure:** `/api/v1/panel/*` is reachable **only from the LAN**. +- **Grants:** read the snapshot, and the single open write — complete a quest as + `actor: panel` with the tapped `actor_child_id`. +- **Refused:** admin routes reject the service token outright, not merely as a + non-admin. The internet cannot reach these routes at all. + +This preserves the original fail-closed boundary: the kiosk can complete a +quest and nothing else. + +### Admin plane (human) + +- **Credential:** an Authentik OIDC JWT (authorization code + PKCE). +- **Exposure:** `/api/v1/admin/*` is internet-facing behind Authentik. +- **Grants:** everything a parent can do — definitions, assignment, presence, + children, un-complete, history, export, settings, regenerate. +- **Gate:** the API validates the JWT (issuer, audience, JWKS signature, expiry) + and requires membership in the `nestquest-admins` group. The Authentik group + is the single source of truth for "who is a parent", replacing the + integration-side admin allowlist. + +## 5. Components + +### `core/` — domain library + +The extracted domain layer: schema, migrations, database access, DAOs, the pure +recurrence and presence engines, materialization, completion, event payload +construction, and the children/definitions/allowlist business layers. **No +Home Assistant imports.** Consumed by both the API and (during transition) the +integration. This is where the ~1500 existing tests live after extraction. + +### `api/` — NestQuest API + +A FastAPI service importing `core/`. Owns the database. Serves the panel and +admin planes, validates Authentik tokens, and publishes a transition event +stream (SSE) that the integration consumes. No business logic in route handlers. + +### `custom_components/nestquest/` — HA integration (client) + +A thin client. Polls the snapshot and builds the existing sensors; subscribes to +the SSE stream and re-fires the four HA bus events; proxies the panel completion. +Holds a last-good snapshot cache so a brief API outage does not blank the panel. +Owns no database and no business logic. + +### `panel/` — Lovelace cards and dashboard strategy + +The two kid-facing Lit cards plus a dashboard strategy that generates the panel +views at render time. Cards auto-discover children, so no `child_order` is +required; the strategy computes navigation paths, so `quest_log_path` and +`board_path` are not required. + +### `admin/` — Admin PWA + +A React progressive web app implementing `design/ADMIN-SPEC.md`, authenticated +via Authentik OIDC, installable on a phone. The only admin surface. + +## 6. Zero-configuration panel + +A fresh install should show the panel with no YAML: + +1. **Auto-discovery.** The party-board card reads an ordered child list from a + household entity attribute instead of a configured `child_order`. +2. **Dashboard strategy.** A Lovelace strategy generates a party-board view and + one quest-log view per child, wiring navigation between them, so paths are + computed rather than configured. Explicit configuration still wins if a user + supplies it. +3. **Auto-registration.** The integration registers the dashboard on first + setup, so there is no manual dashboard step. + +## 7. Home Assistant surface preserved + +The re-imagining must not break what already works: + +- **Sensors:** the per-child and household sensor entity_ids and the per-child + instance attribute payload shape are unchanged. The panel cards and the four + blueprints depend on them. +- **Events:** the four HA bus events (`nestquest_quest_completed`, + `nestquest_quest_uncompleted`, `nestquest_quest_missed`, + `nestquest_child_day_complete`) keep firing with their documented payloads. + They are now driven by the API's transition stream rather than by local + service handlers. +- **Blueprints:** the morning summary, afternoon reminder, end-of-day report, and + day-complete celebration continue to work with no change. + +## 8. Events without an HA token on the API box + +The API publishes transitions on an SSE stream; the integration subscribes and +re-fires the HA bus events locally. This deliberately avoids storing a Home +Assistant long-lived token on the API box, which would grant full HA access. + +## 9. Migration phases + +Mapped to the Maestro features: + +| Phase | Feature | Outcome | +|---|---|---| +| 1 | 15 — Domain Core Extraction | `core/` runs with no HA imports; one implementation of the rules | +| 2 | 16 — NestQuest API Service | Panel and admin planes served with two-plane auth | +| 3 | 17 — API Deployment and Identity | TLS, Authentik, backups, network separation | +| 4 | 18 — HA Integration as API Client | Integration becomes a client; entity/event surface preserved | +| 5 | 19 — Admin PWA | Mobile-first React PWA replaces the admin card | +| 6 | 20 — Zero-Config Panel Dashboard | Panel installs itself; navigation and animations match the mockups | + +Cutover: copy the existing SQLite file to the API box (same schema). + +## 10. Locked decisions + +- **API on its own box** — separate from Home Assistant. +- **Authentik** (`https://auth.cubecraftlabs.com`) for admin identity; a + `nestquest-admins` group is the admin source of truth. +- **Single household** — no tenant column, no multi-household auth. +- **React** for the admin PWA. +- **Drop the HA admin services** — the PWA is the only admin surface. The panel + completion path stays. +- **SQLite on the API box** initially; Postgres only if multi-household ever + becomes real. + +## 11. Preserved vs. changed + +**Preserved:** the tested Python domain layer, the panel card designs and +tokens, the sensor and event contract, the completion model, the append-only +event log, the panel's fail-closed "complete only" boundary. + +**Changed:** data ownership moves to the API; the integration becomes a client; +the admin card becomes a PWA; panel cards become config-free with a +strategy-generated dashboard. + +**New:** the API service, Authentik-gated admin auth, the PWA, and a +last-good-snapshot cache in the integration. + +**Cost:** two new deployables (the API and the PWA) and one new box. Accepted +knowingly in exchange for a self-installing panel, an admin surface that is not +fighting HA's UI, and a data layer with real auth in front of it. From 0ed35ab85a62b4001577eead363b497158fc6599 Mon Sep 17 00:00:00 2001 From: Joshua Date: Wed, 23 Sep 2026 14:29:12 -0400 Subject: [PATCH 2/3] release: advance version to 0.6.0 and add the re-imagined architecture release notes --- RELEASE_NOTES.md | 146 ++++++++++++++++++---- custom_components/nestquest/manifest.json | 2 +- pyproject.toml | 2 +- uv.lock | 2 +- 4 files changed, 128 insertions(+), 24 deletions(-) diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 5bd20da..3c82296 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -1,27 +1,131 @@ # NestQuest Release Notes -## Unreleased +## Version 0.6.0 — 2026-09-23 -- **Storage layer extraction (task d0b691d8):** the SQLite storage layer - (schema, migrations, db, store, const, recurrence, DAOs) is bundled - under `custom_components/nestquest/core/`, with the integration's - original module names preserved as re-export shims. -- **Logger namespace change (breaking for logging configuration):** the - migrations runner now logs under - `custom_components.nestquest.core.migrations` instead of - `custom_components.nestquest.migrations`. Users who tuned the old - logger name (e.g. in `logger.yaml` or `configuration.yaml`) must - update the entry to keep that level, as the old name no longer - receives migration log output. -- **Additional logger namespace moves:** the children registry and the - admin allowlist business layers now log under - `custom_components.nestquest.core.children` and - `custom_components.nestquest.core.admin_allowlist` respectively - (moved with the engines/business-layer extraction, task 510c1f78). - The parent `custom_components.nestquest` logger still catches both, - so users who tune the parent rather than the leaf names are - unaffected; only users who pinned a leaf logger name by its old - spelling need to update. +### Scope + +Sixth release of NestQuest, promoting the re-imagined architecture +(Features 15, 16, 18 and 20) and the foreground development-orchestration +tooling from `dev` onto 0.5.0. This is a major architectural change: the +Home Assistant integration becomes a thin client of a standalone NestQuest +API service that owns the database. + +- **Feature 15 — Domain Core Extraction:** all domain code (schema, + migrations, database access, the DAOs, the pure recurrence/presence + engines, materialization, completion, the SSE-free event payload + builders, the panel snapshot builder, the children/definitions/allowlist + business layers, and an explicit settings object) now lives in a + Home-Assistant-free package bundled inside the integration at + `custom_components/nestquest/core/`. The SQLite schema and migration + versions are unchanged, so an existing `nestquest.db` opens without + migration. Covered by a no-Home-Assistant end-to-end smoke test. + (Tasks d0b691d8, 510c1f78, 4dd8f870, 628473ad.) +- **Feature 16 — NestQuest API Service:** a standalone FastAPI service + (`api/`) that imports `core/` and is the only writer of the database. + Panel plane (`/api/v1/panel/*`) is authenticated by a static service + token: household snapshot and an idempotent complete route. Admin plane + (`/api/v1/admin/*`): children, quest definitions, presence + schedule/override, uncomplete, regenerate, history query + CSV export, + and settings — authenticated by an Authentik OIDC JWT (issuer, audience, + JWKS signature and expiry) plus membership in `nestquest-admins`, with + the panel service token rejected outright. A server-sent-events stream + publishes the quest-completed, quest-uncompleted, quest-missed and + child-day-complete transitions; the missed sweep runs in the API with a + cross-process-safe watermark. `/health` and the OpenAPI document are + served. (Tasks 042dfc5c, ae7e90ae, 1860ef76, 4d7bae00, 44c93cfd, + e70e6bfb, 0f2afb29, e4845baa, b586a2f1, e4a9d31b, da0226b3, 2d2dda53, + 2b3de7e5, 9047a97b, 9a118da9, 058c7b69, a232c4f5.) +- **Feature 18 — HA Integration as API Client:** the integration no longer + owns a database. It polls `GET /api/v1/panel/snapshot` and builds the + existing sensors (entity ids and per-child instance payload shape + unchanged), subscribes to the SSE stream and re-fires the four HA bus + events, proxies `nestquest.complete_quest` to the panel complete route, + and caches the last-good snapshot so a brief API outage does not blank + the panel. The HA admin services and the service permission gate are + removed; only the panel completion path remains. (Tasks 37d5e806, + 6e70d166, b1ed9c45, 47c7c90e, d25b875f, d04f5bf3, ad67fc44.) +- **Feature 20 — Zero-Config Panel Dashboard:** the panel installs with no + hand-written card YAML and no manual dashboard creation. The cards + auto-discover children from a household entity attribute (ordered by the + API's `sort_order`), a Lovelace dashboard strategy generates the + party-board view and one quest-log view per child at render time with + computed navigation paths, and the integration registers the dashboard + on first setup. Navigation and the panel transitions match the design + mockups. (Tasks 1b6d91d2, c859af23, c2d4dd8e, 107b8fbe, 50067c1f, + fc69c940, 67775be9.) +- **Development orchestration tooling:** durable OpenCode controller and + role launchers, the foreground terminal orchestrator, the independent + review/approval gates, and the automatic release-preparation policy. + +### Requirements + +- Minimum Home Assistant version: **2024.6.0**. +- A running NestQuest API service, reachable at the base URL and panel + service token configured in the integration options (see `api/README.md` + and `docs/ARCHITECTURE.md`). Feature 17 — the deployment (TLS reverse + proxy, Authentik, network separation, backups) — is **not** part of this + release and must be completed before the integration is functional. + +### Breaking changes + +- **The HA integration no longer owns a database.** It reads exclusively + from the NestQuest API. The local `nestquest.db` is no longer opened, + migrated or written by the integration (the file is left untouched on + disk). Without a reachable API the entities become unavailable after the + configured staleness threshold; the panel serves the last-good snapshot + in the meantime. +- **HA admin services are removed** (`uncomplete`, quest-definition + create/update/set-active, presence pattern/override, CSV export, + `manage_child`, `regenerate`) and the HA-side permission gate is gone. + Admin operations move to the API, and — once Feature 19 lands — the + admin PWA. Any automation that called an admin service must be + repointed at the API. +- **Logger namespaces moved** under `custom_components.nestquest.core.*`: + the migrations runner now logs under + `custom_components.nestquest.core.migrations` (previously + `custom_components.nestquest.migrations`), and the children registry and + admin allowlist log under `custom_components.nestquest.core.children` and + `custom_components.nestquest.core.admin_allowlist`. The parent + `custom_components.nestquest` logger still catches all three; only users + who pinned a leaf logger name by its old spelling must update. +- **The panel installs with no configuration.** `child_order`, + `quest_log_path` and `board_path` are no longer required (an explicit + value still wins). The integration registers a `custom:nestquest-party` + dashboard on setup; point TouchHub at it. +- **Schema is unchanged at version 7** — no migration runs on upgrade. + +### Known Limitations + +- **Feature 17 (API deployment and identity) is not in this release.** The + API is containerized (`Dockerfile`, `deploy/compose.yaml`, + `api/README.md`) but there is no deployed TLS reverse proxy, no Authentik + application/provider/`nestquest-admins` group, no proven network + separation of the panel routes, and no backup/restore. Until that + deployment is completed the integration has nothing to talk to. +- **Feature 19 (admin PWA) is not in this release.** There is no admin UI + in 0.6.0; admin operations are reachable only through the API directly. +- Moving the household database from Home Assistant to the API box is a + manual file copy (the schema is unchanged). +- Single household; single API service. + +### Rollback + +Schema version is unchanged (7), so no migration is involved in either +direction. Nevertheless **back up `nestquest.db` (plus `-wal`/`-shm`) +before upgrading**: 0.6.0 never touches it, and a downgrade to 0.5.0 +resumes using exactly that file. + +**HACS installs:** + +1. Downgrade via HACS (HACS > Integrations > NestQuest > Redownload > 0.5.0). +2. Restart Home Assistant. + +**Manual installs:** + +1. Replace `custom_components/nestquest/` with the 0.5.0 tree. +2. Restart Home Assistant. + +**Repository operators:** git-revert the 0.6.0 release merge on `main`. ## Version 0.5.0 — 2026-09-18 diff --git a/custom_components/nestquest/manifest.json b/custom_components/nestquest/manifest.json index c4fe61d..4f5d553 100644 --- a/custom_components/nestquest/manifest.json +++ b/custom_components/nestquest/manifest.json @@ -15,5 +15,5 @@ "iot_class": "local_polling", "issue_tracker": "https://github.com/Maniacs-Mansion/NestQuest/issues", "requirements": [], - "version": "0.5.0" + "version": "0.6.0" } diff --git a/pyproject.toml b/pyproject.toml index f1779bc..88eaee7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "nestquest" -version = "0.5.0" +version = "0.6.0" description = "NestQuest Home Assistant Integration" readme = "README.md" requires-python = ">=3.11" diff --git a/uv.lock b/uv.lock index 711e68e..f3a2c88 100644 --- a/uv.lock +++ b/uv.lock @@ -373,7 +373,7 @@ wheels = [ [[package]] name = "nestquest" -version = "0.5.0" +version = "0.6.0" source = { virtual = "." } [package.dev-dependencies] From 8676bdc6ad3f3c626bff364bea2cb602588be92b Mon Sep 17 00:00:00 2001 From: Joshua Date: Wed, 23 Sep 2026 14:35:01 -0400 Subject: [PATCH 3/3] release: correct the 0.6.0 notes - no containerization artifacts ship in this release --- RELEASE_NOTES.md | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 3c82296..6504e5f 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -61,10 +61,12 @@ API service that owns the database. - Minimum Home Assistant version: **2024.6.0**. - A running NestQuest API service, reachable at the base URL and panel - service token configured in the integration options (see `api/README.md` - and `docs/ARCHITECTURE.md`). Feature 17 — the deployment (TLS reverse - proxy, Authentik, network separation, backups) — is **not** part of this - release and must be completed before the integration is functional. + service token configured in the integration options. The API service + source ships in `api/`; there is no container image, compose file or + deployment runbook in this release, and Feature 17 — the deployment + (TLS reverse proxy, Authentik, network separation, backups) — is **not** + part of this release and must be completed before the integration is + functional. See `docs/ARCHITECTURE.md` for the target design. ### Breaking changes @@ -97,11 +99,13 @@ API service that owns the database. ### Known Limitations - **Feature 17 (API deployment and identity) is not in this release.** The - API is containerized (`Dockerfile`, `deploy/compose.yaml`, - `api/README.md`) but there is no deployed TLS reverse proxy, no Authentik - application/provider/`nestquest-admins` group, no proven network - separation of the panel routes, and no backup/restore. Until that - deployment is completed the integration has nothing to talk to. + API service source ships in `api/`, but no container image, compose + file, TLS reverse proxy, Authentik application/provider/`nestquest-admins` + group, proven network separation of the panel routes, or backup/restore + is included. Feature 17's containerization groundwork was completed on + its own feature branch, which is **not** merged into `dev` and therefore + is not part of this release. Until that deployment is completed the + integration has nothing to talk to. - **Feature 19 (admin PWA) is not in this release.** There is no admin UI in 0.6.0; admin operations are reachable only through the API directly. - Moving the household database from Home Assistant to the API box is a