Files
Ben Stull 83eafe72ee feat(e2e): deployed-environment E2E harness + gated test-auth (v0.52.0)
The §9 pipeline's PPE+E2E stage was unreachable: e2e/metadata.spec.js was
bound to Tier-1-only scaffolding (docker-seeded faceted collection,
SQLite-injected owner, Mailpit OTC sink). This makes the same suite run
against a deployed host.

- backend: POST /auth/test/login — fail-closed, secret-gated, single-
  identity owner test-login (404 unless E2E_TEST_AUTH_SECRET +
  E2E_TEST_AUTH_EMAIL both set; constant-time compare; loud startup warn).
  6 vertical tests; backend 665 green. Documented in backend/.env.example.
- e2e/lib/auth.js: branch on E2E_TEST_AUTH_SECRET (deployed test-login vs
  local Mailpit OTC); OWNER_EMAIL from E2E_OWNER_EMAIL. Spec unchanged so
  the localhost Tier-1 path keeps working.
- testing/seed-ppe.sh: seed a dedicated, prod-untouching PPE registry +
  content repo (faceted bdd collection) — real OHM content never touched.
- docs/design/2026-06-07-deployed-env-e2e-harness.md; CHANGELOG; VERSION
  + frontend/package.json -> 0.52.0.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 23:35:53 -07:00

146 lines
6.2 KiB
Bash

# --- Gitea ---
# Base URL of the Gitea instance the app speaks to.
GITEA_URL=http://localhost:3000
# The bot service account that performs every Git operation per §1.
# Provision a real Gitea user, generate a personal access token with
# repo and admin (or at minimum: repo, write:repository) scopes, and
# put the token here. The bot is the only Git writer.
GITEA_BOT_USER=rfc-bot
GITEA_BOT_TOKEN=
# The Gitea org or user that owns every RFC repo the bot will create on
# graduation.
GITEA_ORG=wiggleverse
# §22.2 — the project registry repo (REQUIRED). The framework reads
# `projects.yaml` at its root to learn which projects exist. The repo name is
# the deployment's choice; the app fails to start if this is unset.
REGISTRY_REPO=registry
# §22.13 — optional id for the bootstrap/default project. Reserved for the
# Plan B re-stamp; leave unset in Plan A (the default project id stays
# `default`). When set, it must match an `id` in projects.yaml.
# DEFAULT_PROJECT_ID=ohm
# --- OAuth (Gitea) ---
# In Gitea: Site Administration → Applications → Add OAuth2 Application.
# Redirect URI: {APP_URL}/auth/callback
OAUTH_CLIENT_ID=
OAUTH_CLIENT_SECRET=
# --- App ---
APP_URL=http://localhost:8000
SECRET_KEY=change-me-to-a-long-random-string
DATABASE_PATH=data/rfc-app.db
# Per §1: owner zero. The Gitea login that gets the owner role on
# first sign-in.
OWNER_GITEA_LOGIN=ben
# Webhook signature secret. Gitea sends X-Gitea-Signature as the
# HMAC-SHA256 of the body using this secret. Per §4.1 the webhook is
# one of two cache writers; signing keeps spurious writes out.
GITEA_WEBHOOK_SECRET=change-me-to-a-shared-secret
# --- LLM providers (carryover §18) ---
# Comma-separated list of provider keys to enable. Per the §19.2
# per-RFC-model topic, this is app-wide until that topic lands.
ENABLED_MODELS=claude
# ANTHROPIC_API_KEY also powers the §9.1 propose-RFC tag suggestions
# (roadmap #27) — that surface always uses Claude Haiku for cost,
# independent of ENABLED_MODELS. With no key set, tag suggestions are
# simply unavailable (the modal hides the row); the rest of the app is
# unaffected.
ANTHROPIC_API_KEY=
GOOGLE_API_KEY=
OPENAI_API_KEY=
# --- Tag suggestions (§9.1 / roadmap #27) ---
# Per-user rate limit on the suggest-tags endpoint (cost backstop; the
# modal debounces and the endpoint is contributor-gated). Optional —
# these defaults apply when unset.
TAG_SUGGEST_RATE_MAX=30
TAG_SUGGEST_RATE_WINDOW_SECONDS=60
# --- Email (§15.4) ---
# Leave SMTP_HOST unset to use the stdout fallback — the integration
# tests rely on it, and a dev environment without a real SMTP provider
# still sees envelope traces in the logs. Set the rest to wire a real
# provider (Postmark, Mailgun, SES, etc.).
SMTP_HOST=
SMTP_PORT=587
SMTP_USER=
SMTP_PASSWORD=
SMTP_STARTTLS=1
# Single non-spoofing envelope identity per §15.9 — every notification
# email goes out from the same address regardless of which user's
# gesture produced it. Configure both the SPF and DKIM records for
# this address with the chosen SMTP provider.
EMAIL_FROM=notifications@wiggleverse.local
EMAIL_FROM_NAME=Wiggleverse
# §15.4 bundle threshold: when a user's quiet-hours release queue is
# at least this big, the flush goes out as one bundled "Activity while
# you were away" email instead of individual sends.
EMAIL_BUNDLE_THRESHOLD=5
# Set to 0 to suppress every outbound email (the inbox and SSE still
# work — only the email channel turns off).
EMAIL_ENABLED=1
# --- Email-bounce webhook (§15.4 + §19.2-settled) ---
# When set, `/api/webhooks/email-bounce` requires the same value in
# the `X-Webhook-Secret` header. Pick a long random string and
# configure the SMTP provider's webhook to inject it. When unset,
# the webhook stays unauthenticated for dev — the v1 contract.
WEBHOOK_EMAIL_BOUNCE_SECRET=
# --- §12 hygiene cadence (Slice 8) ---
# How often the hygiene scheduler sweeps for the 30/90-day boundaries.
# Production default is hourly; tests override to seconds via the same
# env var.
HYGIENE_TICK_SECONDS=3600
# --- v0.7.0: email + one-time-code sign-in (§6.2) ---
# How long a one-time code stays valid after issuance. Re-requesting
# invalidates the prior code immediately regardless of TTL.
OTC_TTL_MINUTES=10
# Per-email cooldown between successive /auth/otc/request calls. The
# endpoint returns HTTP 429 when the cooldown blocks a request (the
# loud-failure shape so the abuse path is visible). Set to 0 to
# disable the cooldown — useful for tests but never in production.
OTC_REQUEST_COOLDOWN_SECONDS=60
# --- v0.12.0: CloudFlare Turnstile gate on OTC dispatch (§6.2, item #10) ---
# Provision a Turnstile site at dash.cloudflare.com → Turnstile → Add
# site. The site key (public) goes in `frontend/.env` as
# VITE_TURNSTILE_SITE_KEY. The secret key (private) goes here and is
# what the backend POSTs to /siteverify alongside the user's response
# token. Leave both unset for dev/test paths; the gate stays open when
# the secret is absent AND TURNSTILE_REQUIRED=false (the default).
CLOUDFLARE_TURNSTILE_SECRET=
# When `true`, /auth/otc/request fails closed (HTTP 500 "auth
# misconfigured") if CLOUDFLARE_TURNSTILE_SECRET is unset. When `false`
# (the default), a missing secret skips verification — useful in dev
# and during the pre-rollout window when the operator hasn't wired
# the secret yet. Flip to `true` once the secret is wired so a future
# config drift surfaces as a loud 500 rather than a silent abuse-
# defense disablement.
TURNSTILE_REQUIRED=false
# --- Deployed-environment E2E test auth (v0.52.0) ---
# DANGER: NEVER set these on a production deployment. Together they
# enable `POST /auth/test/login`, which mints an authenticated OWNER
# session for the one configured email without any OTC/email round trip
# — it exists only to run the Playwright E2E suite against a deployed
# pre-prod (PPE) host that has no Mailpit sink. The route is fail-closed:
# it returns 404 unless BOTH vars below are set, requires the caller to
# present E2E_TEST_AUTH_SECRET in the `X-Test-Auth-Secret` header
# (constant-time compare), and only ever mints the single configured
# email (any other → 403). Leave BOTH unset everywhere except PPE.
# E2E_TEST_AUTH_EMAIL=e2e-owner@example.test
# E2E_TEST_AUTH_SECRET= # a Secret Manager ref on real deployments; never a literal here