# --- 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