# 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
- `custom/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).
- `custom/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.
