<!-- source: openspec/changes/add-e2e-smoke-suite/verification.md -->

<!-- source: openspec/changes/add-e2e-smoke-suite/verification.md -->

---
audience:
  - executor
---
# Verification — add-e2e-smoke-suite

> Companion to proposal.md / design.md / tasks.md. Lists the deterministic
> acceptance assertions Forge runs before opening the PR.

## Spec alignment

"Done" at the capability-spec level means three things. First, `packages/e2e/` exists as a private workspace package (`@rapoport-studio/e2e`, `private: true`) with the module layout in `design.md § Module layout` — playwright config, global setup, six spec files across `tests/web/` and `tests/studio/`, and a README documenting the operator runbook. Second, `.github/workflows/e2e-prod.yml` exists with both a `schedule` trigger (`0 6,18 * * *`) and `workflow_dispatch`, wired to the `production-e2e` environment, with the Telegram-on-failure step in place. Third, `pnpm --filter @rapoport-studio/e2e typecheck` returns 0 — the package compiles standalone.

The change is complete at the spec level when a reader can clone the repo, set the documented secrets, manually trigger `workflow_dispatch`, and watch six green tests run against production within a 3-minute wall-clock budget. The archive-time amendments to `current/platform.md` and `current/auth.md` (per `tasks.md § Phase 6`) are part of the archive step, not this PR.

## Hallucination risks

MUST appear:

- `packages/e2e/package.json` containing `"private": true` and `"name": "@rapoport-studio/e2e"`.
- `packages/e2e/playwright.config.ts` referencing `globalSetup` from `./global-setup.ts`.
- `packages/e2e/global-setup.ts` calling `supabase.auth.admin.generateLink` (not `signInWithMagicLink`, not `signInWithPassword`).
- `packages/e2e/tests/web/apex.spec.ts`, `services.spec.ts`, `intake.spec.ts` — three files.
- `packages/e2e/tests/studio/login.spec.ts`, `workspace.spec.ts`, `spec-smoke.spec.ts` — three files.
- `packages/e2e/README.md` containing the operator runbook + weekly cleanup SQL.
- `.github/workflows/e2e-prod.yml` containing the `schedule: '0 6,18 * * *'` cron and `workflow_dispatch:` trigger.
- `.github/workflows/e2e-prod.yml` containing `environment: production-e2e`.
- `.github/workflows/e2e-prod.yml` containing the `if: failure()` Telegram POST step referencing `TELEGRAM_BOT_TOKEN` and `TELEGRAM_OPERATOR_CHAT_ID`.

MUST NOT appear:

- No new files under `apps/web/` or `apps/studio/` — tests are a **black-box** consumer of the deployed apps. (`packages/e2e/` is the only home.)
- No `NEXT_PUBLIC_E2E_MODE`, `E2E_BYPASS`, or any other auth-bypass env var or branch in `apps/studio/src/**` or `packages/auth/src/**`. The auth flow is exercised, not bypassed (per D-E2E-4).
- No imports of `@rapoport-studio/db`, `@rapoport-studio/auth`, `@rapoport-studio/ui`, or any other `@rapoport-studio/*` package from `packages/e2e/**` source files (per `design.md § Boundaries`). Type-only `import type` from `@rapoport-studio/db` is the single allowed exception if a test typeguards a Supabase row shape.
- No edits to `openspec/current/**/*.md` in this PR — capability-spec amendments land at archive (per `AGENTS.md § What gets amended where`).
- No edits to `packages/auth/src/proxy.ts` to add an e2e-user short-circuit — `platform_owner: true` is set on the user's `app_metadata`, not in proxy code.
- No checked-in `playwright/.auth/storage.json` — must be in `packages/e2e/.gitignore`.
- No second Playwright project beyond `chromium` (per D-E2E-5).
- No Cloudflare Worker cron file (e.g. `apps/web/src/cron/e2e.ts`) — scheduling lives in GitHub Actions per D-E2E-2.

## Pattern references

- `.github/workflows/publish-forge.yml` — workflow shape (Node setup, pnpm filter, run step). `.github/workflows/e2e-prod.yml` should mirror its structure.
- `packages/auth/package.json`, `packages/state/package.json`, `packages/config/package.json` — `private: true` workspace-package shape that `packages/e2e/package.json` should mirror.
- `packages/auth/src/__tests__/proxy.test.ts` — file-naming and Supabase-mock pattern; not directly applicable (this is Playwright not vitest) but the import shape for `@supabase/supabase-js` is the same.
- `apps/studio/src/app/auth/confirm/route.ts` — the production endpoint that `global-setup.ts` drives Chromium through; do not duplicate its logic, just drive it.
- `openspec/archive/2026-05-25-add-marketing-funnel-instrumentation/design.md § 7` — Telegram POST shape precedent; the `if: failure()` step should mirror the curl + `parse_mode=Markdown` pattern.
- `openspec/changes/linear-as-agent-control-plane-openspec/tasks.md § Phase 3` — references `apps/studio/e2e/openspec-linear-sync.spec.ts` shape; the broader convention this change establishes the home for.
- `packages/config/src/studio.ts` — `cookieDomain` default `.rapoport.studio` is why D-E2E-3 targets production rather than preview.

## Acceptance

### Phase 1 — package skeleton

- Done when: [ -f packages/e2e/package.json ]
- Done when: grep -c '"private": true' packages/e2e/package.json returns 1
- Done when: grep -c '"@rapoport-studio/e2e"' packages/e2e/package.json returns 1
- Done when: [ -f packages/e2e/playwright.config.ts ]
- Done when: [ -f packages/e2e/tsconfig.json ]
- Done when: [ -f packages/e2e/.gitignore ]
- Done when: [ -f packages/e2e/README.md ]
- Done when: grep -c 'playwright/.auth' packages/e2e/.gitignore returns 1

### Phase 2 — auth fixture + global setup

- Done when: [ -f packages/e2e/src/fixtures/supabase.ts ]
- Done when: [ -f packages/e2e/src/fixtures/auth.ts ]
- Done when: [ -f packages/e2e/global-setup.ts ]
- Done when: grep -c 'auth.admin.generateLink' packages/e2e/src/fixtures/auth.ts returns 1
- Done when: grep -c 'storageState' packages/e2e/global-setup.ts returns 1

### Phase 3 — smoke tests

- Done when: [ -f packages/e2e/tests/web/apex.spec.ts ]
- Done when: [ -f packages/e2e/tests/web/services.spec.ts ]
- Done when: [ -f packages/e2e/tests/web/intake.spec.ts ]
- Done when: [ -f packages/e2e/tests/studio/login.spec.ts ]
- Done when: [ -f packages/e2e/tests/studio/workspace.spec.ts ]
- Done when: [ -f packages/e2e/tests/studio/spec-smoke.spec.ts ]
- Done when: grep -c 'e2e: true' packages/e2e/tests/web/intake.spec.ts returns 1

### Phase 4 — GitHub Actions workflow

- Done when: [ -f .github/workflows/e2e-prod.yml ]
- Done when: grep -c "cron: '0 6,18 \* \* \*'" .github/workflows/e2e-prod.yml returns 1
- Done when: grep -c 'workflow_dispatch:' .github/workflows/e2e-prod.yml returns 1
- Done when: grep -c 'environment: production-e2e' .github/workflows/e2e-prod.yml returns 1
- Done when: grep -c 'playwright install chromium' .github/workflows/e2e-prod.yml returns 1

### Phase 5 — Telegram alert wiring

- Done when: grep -c 'TELEGRAM_BOT_TOKEN' .github/workflows/e2e-prod.yml returns 1
- Done when: grep -c 'TELEGRAM_OPERATOR_CHAT_ID' .github/workflows/e2e-prod.yml returns 1
- Done when: grep -c 'if: failure()' .github/workflows/e2e-prod.yml returns 2

### Phase 7 — verification

- Done when: pnpm install --frozen-lockfile
- Done when: pnpm --filter @rapoport-studio/e2e typecheck

### Reviewer sign-off

- Reviewer confirms: D-E2E-1 package location at `packages/e2e/` (not `apps/`, not `tools/`); README explicitly documents the "not a library" departure.
- Reviewer confirms: D-E2E-2 scheduling lives in GitHub Actions (not Cloudflare Worker cron); workflow has both `schedule` and `workflow_dispatch` triggers.
- Reviewer confirms: D-E2E-4 auth strategy uses `auth.admin.generateLink` only; no `NEXT_PUBLIC_E2E_MODE` or middleware bypass introduced anywhere in `apps/` or `packages/auth/`.
- Reviewer confirms: D-E2E-9 every write-performing test tags the row with `metadata.e2e = true`; `intake.spec.ts` is the only writer in v1 and carries the tag.
- Reviewer confirms: no edits to `openspec/current/**/*.md` — archive amendments are deferred per `tasks.md § Phase 6`.

### Mandatory boilerplate gates

- Done when: gh pr list --head <branch-for-this-change> returns ≥1
- Done when: jq -r '.outcome' ~/.forge/state/<RAP-NN>.json returns 'completed-with-pr'
