# Phase 15 — Turn off developer/debug mode on the live site

**Blocker:** blockers.md §B.2. Do this deliberately, on its own, right before any real stranger is let near
the site — it touches deploy config and secrets handling, not app logic, and has real prerequisites.
**Depends on:** nothing code-wise, but bundle with credential rotation (blockers.md §B.1) when you do it —
currently deferred per your own call, revisit before real users arrive.

---

**Root cause.** The exposure (`GET api/v1/dev/last_otp/{phone}`,
unauthenticated by design, guarded only by `environment.IsDevelopment()`) is correctly implemented in code
— the entire gap is that the deployed `docker-compose.yml` sets `ASPNETCORE_ENVIRONMENT: Development`,
**deliberately and already documented** (a comment in the compose file and a full section in `DEPLOY.md`
spell out why and what depends on it).

This is **not** a one-line flip — five real prerequisites, already documented in `DEPLOY.md`, need to land
together:

1. Create `server/src/API/Baya.Web.Api/appsettings.Production.json` with real (non-`not-for-production`
   sentinel) `IdentitySettings:SecretKey`/`Encryptkey` — `StartupSecretsGuard` rejects the sentinel outside
   Development, so a bare env-var flip with the current file would refuse to boot.
   **`Seams:FieldEncryption:Key`/`:HashKey` must be copied byte-identical** from the current Development
   value — never regenerated (root `CLAUDE.md` rule 6 — this key decrypts all existing PII).
2. Set `ASPNETCORE_ENVIRONMENT: Production` in `docker-compose.yml`.
3. Switch migrations from boot-time auto-apply (Development-only branch) to the documented one-shot
   (`... migrate`) deploy step — and confirm the demo/lifecycle seed data the shared DB currently relies on
   survives the transition, since those seeders stop running automatically post-flip.
4. Swap `Seams:Sms:Provider` from `telegram` (a broadcast-to-a-fixed-chat-list relay, fine for trusted
   internal testers, not for strangers) to `kavenegar` with a real API key — flipping only the environment
   variable closes the `/dev/last_otp` leak but leaves OTPs going out over the Telegram relay instead of to
   the real phone.
5. Bundle with credential rotation (blockers.md §B.1) when you do it — the same config file carries the DB
   password alongside the encryption keys.

**Flag:** whether to keep the Telegram relay live in parallel with `kavenegar` for internal/test accounts
post-launch, or retire it entirely, is a product/ops call, not answered by the code.

---

## Not phases — noted here so they aren't lost, but no code to write

- **§B.1 (rotate committed credentials)** — per your call, skipped for now (private repo, single developer).
  Revisit before any real user touches the deployed site; bundle with step 5 above when you do.
- **§B.3 (legal review of Terms/Privacy)** — not a code task; needs an actual legal review.
- **§B.4 (مودیان e-invoicing)** — per your call, not built now. When you pick it up: the invoice data and tax
  math are already correct; the only missing piece is the actual government-facing registration call, and it
  needs a chosen provider/gateway before any adapter code makes sense. Add a note to forgotten-features.md
  saying exactly that, rather than building a speculative seam with nothing real to point it at.
- **§B.5 (e-namad certification)** — a business/paperwork step, not a code change; also gates the real
  ZarinPal card-payment path ([07-card-payment-redirect.md](07-card-payment-redirect.md)) from ever going
  fully live, independent of any code fix.
- **§B.6 (real bank-transfer payout rail)** — per your call, stays mocked. Resolved by
  [11-nurse-payouts.md](11-nurse-payouts.md) — the seam already exists server-side; only the client UI in
  front of it was missing.
