# plausible — parity & coverage map (Phase 2 Q4.7) ## P2 — Parity port **No recipe-maintainer corpus exists for plausible** (`/srv/recipe-maintainer/recipe-info/` has no `plausible/` entry — confirmed: no `recipe-info/plausible/tests/*.py`). P2 (parity) is therefore **vacuously satisfied** — there are no scripts to port. Coverage is delivered entirely via the generic lifecycle tiers (install/upgrade/backup/restore) + the recipe-aware backup overlay + the recipe-specific functional tests below. ## Lifecycle (generic tiers + recipe-aware overlays — Phase 1d/1e) plausible ships `test_install.py` (generic serving + SPA shell at `/`) and the backup-integrity overlays (`test_backup.py` / `test_restore.py` / `test_upgrade.py` + `ops.py`). Where an overlay is present the generic tier still runs alongside it (Phase-1e HC3 invariant). Readiness probe: `HEALTH_PATH = /api/health`, `HEALTH_OK = (200,)`. plausible's `app` boots before its ClickHouse events DB is ready and `/` 500s during init (then 302s once ready, so `/` cannot distinguish not-ready from ready). The dedicated `/api/health` endpoint returns `200` with `{"clickhouse":"ok","postgres":"ok","sites_cache":"ok"}` **only** once both datastores are reachable — a true readiness gate. `DEPLOY_TIMEOUT` / `HTTP_TIMEOUT` are widened to 1200s to wait out the cold ClickHouse + migrations init. ## P3 — Recipe-specific functional tests - `functional/test_health_check.py` - `test_plausible_root_serves` — GET `/api/health` → 200, proving ClickHouse + postgres + the sites_cache are all up (plausible's self-reported backend readiness; not a Traefik fallback). - `functional/test_event_tracking.py` — **§4.3 prescribed "track a test event, query it back"**, the app's primary object. Both tests register a site row in the metadata postgres (plausible's `sites_cache` drops events for unregistered domains — empirically confirmed), POST to the public `/api/event` ingestion endpoint with a browser User-Agent (plausible drops bot/library UAs), then read the row back out of the ClickHouse `events_v2` table on a poll loop (sites_cache refresh + event write-buffer flush make the first landing non-instantaneous). Real app-state assertions, not 202-ack stand-ins: - `test_pageview_event_roundtrip` — a `pageview` event lands in `events_v2`; asserts the stored `name`/`pathname`/`hostname` match what was sent. - `test_custom_event_roundtrip` — a *custom-named* event (a goal/conversion, plausible's distinctive non-pageview tracking path) lands under that exact name (not coerced to `pageview`). ## P4 — Backup data-integrity (real) `ops.py` seeds an identifiable `ci_marker` row in the metadata postgres (`db` service), `pre_restore` drops it, and the restore tier asserts the row survives backup→restore. plausible's recipe backs up postgres via a real `pg_dump`/`pg_restore` hook (backupbot pre-/post-hooks), so the SQL-level marker restores cleanly — the recipe-aware data-integrity bar (P4), not a "service is up" stand-in. ## Notes / deferrals - Reading events back via plausible's **stats API** (rather than ClickHouse directly) requires a registered user + API key; under `DISABLE_AUTH=true` there is no default user, and creating an API key adds significant setup with no extra signal over the direct ClickHouse read-back (which is the authoritative store). The ClickHouse read-back is the stronger, more direct assertion and is used.