cleanup phases 6

This commit is contained in:
hamid
2026-08-02 18:48:32 +03:30
parent e2db97392a
commit 51e86a1e5f
239 changed files with 118 additions and 70 deletions
@@ -0,0 +1,78 @@
# Refinement Phase 0 — Local end-to-end bring-up & the integration seam — Report (2026-07-12)
## What was built
Removed the three hard integration blockers so the client and server actually talk on one machine, and
proved it with one real authenticated round-trip. **Plumbing only — no business logic, money path, auth
crypto, or envelope shape changed.**
- **CORS (§3.1).** New `Baya.WebFramework/ServiceConfiguration/CorsServiceExtension.cs`
`AddCorsPolicies(configuration)` builds the named policy `BalinyaarWebClient` from
`Cors:AllowedOrigins` (string array), defaulting to `http://localhost:3000` when unset. Allows exactly the
four headers the client sends (`Authorization`, `Content-Type`, `Accept-Language`, `Idempotency-Key`) +
`AllowAnyMethod()`; **no `AllowCredentials()`** (the client uses a bearer header, not a cookie). Registered
in the service chain and `app.UseCors(CorsServiceExtension.PolicyName)` placed **after `UseRouting()` and
before `UseRateLimiter()`** so pre-flight `OPTIONS` is answered before the limiter/auth run.
- **Local database story (§3.2).** `server/docker-compose.yml` rewritten to a single-purpose SQL Server 2022
(Developer edition) on `localhost:1433` with a **dev-only** `MSSQL_SA_PASSWORD` + a named volume +
healthcheck. (The old compose referenced an unbuildable `bobby-baya` app image and exposed `1435`.) The
committed `appsettings.json` / `appsettings.Development.json` connection strings (`SqlServer` + `logDb`) are
now **non-working placeholders** (`Password=SET_VIA_USER_SECRETS_OR_ENV`); the real local value comes from
`dotnet user-secrets`. Added `<UserSecretsId>baya-web-api</UserSecretsId>` to the API csproj (it was
missing — user-secrets weren't actually wired before).
- **Development-only OTP retrieval (§3.3).** `GET /api/v1/dev/last_otp/{phone}` (`DevController`) returns the
most recent OTP for a phone so a browser / e2e flow can log in without an SMS gateway. Backed by a new
Development-only `DevOtpStore` + `DevCapturingSmsSender` (an `ISmsSender` decorator that captures the code
then delegates to the log-only `LoggingSmsSender`), wired **only** in Development by
`AddDevelopmentOtpCapture()`. The endpoint **404s in every non-Development environment** and the capture is
not even registered there — two independent guarantees it can't leak a code. It does **not** weaken the
`otp` rate-limit or the per-phone resend window.
- **Client env & runbook (§3.4).** Verified `client/.env.development` already has
`NEXT_PUBLIC_API_URL = https://localhost:5002` (matches the API's bound URL) — **no client code changed, no
mock flag flipped.** Wrote `dev/post-phase/refinement/RUNBOOK.md` — the copy-pasteable "run the whole app
locally" procedure (dev-cert trust, compose, user-secrets, both run commands, OTP-from-logs / dev-endpoint,
DevTools verification, troubleshooting).
## What is now testable (and exactly how)
- **Automated (in the suite, +11 tests → 369 total):** 3 new `Baya.Test.Api` integration tests
(`CorsAndDevBringUpTests`): a pre-flight `OPTIONS /api/v1/auth/request_otp` from `http://localhost:3000`
reflects `Access-Control-Allow-Origin: http://localhost:3000`; the same from a foreign origin gets **no**
allow-origin header; `GET /api/v1/dev/last_otp/...` returns **404** in the (non-Development) test host. Plus
8 `Baya.Test.Foundation` unit tests (`DevOtpBringUpTests`) covering the store's capture/latest/
spelling-insensitive lookup + the decorator's capture-and-still-delegate behaviour.
- **Manual (the §7 proof):** follow `RUNBOOK.md``docker compose up -d`, set the user-secret, `dotnet run`,
`npm run dev`, open `/fa/login`, request an OTP, read the code from the server console (or
`GET /api/v1/dev/last_otp/{phone}`), submit → land on the customer home with **200s** on
`/auth/request_otp`, `/auth/verify_otp`, `/api/v1/me` and **no CORS error**. Negative check: remove
`app.UseCors(...)` → the same flow fails with a CORS error.
## What is mocked / waiting on a real service
- No new seam. The existing **`ISmsSender`** (`LoggingSmsSender`) stays the interim OTP channel (logs the
code); its `mocks-registry.md` row is updated to note the Development-only `DevCapturingSmsSender` +
`/dev/last_otp` affordance. Real SMS is [Refinement Phase 8](refinement-phase-8-external-rails.md).
- The `DevOtpStore` / `DevCapturingSmsSender` / `/dev/last_otp` endpoint are a **Development-only dev
affordance, not a seam** (per the phase §4) — superseded by real SMS in Phase 8.
## Contracts
- **None produced.** This phase ships plumbing (CORS middleware) + a Development-only diagnostic endpoint the
frontend does not consume as a contract, so no `dev/contracts/domains/*.md` was written and the
`swagger.v1.json` snapshot was **not** regenerated (the only new path is the dev-only helper Phase 8
removes — regenerating would add churn for a path no client binds to). Auth remains the one real domain
the frontend consumes, unchanged.
## Docs updated
- `server/CLAUDE.md` "Startup wiring" — added `AddCorsPolicies(config)` to the registration list, the
Development-only `AddDevelopmentOtpCapture()` note, and `CORS` in the pipeline order (after routing, before
the rate limiter). Project map — noted the Development-only `Dev` controller.
- `dev/post-phase/refinement/RUNBOOK.md` — new local-run runbook.
- `dev/shared-working-context/reports/mocks-registry.md``ISmsSender` row updated.
## Follow-ups for later phases
- **Phase 1** — local-dev demo seed (nurses/variants/search rows) so discovery/booking aren't empty on the
real path.
- **Phase 4** — flip the 21 `USE_*_MOCK` flags (this phase changed none).
- **Phase 5** — rotate the leaked remote `sa` credentials + the dev-grade `IdentitySettings` keys (still
committed as dev placeholders here); set real production `Cors:AllowedOrigins`.
- **Phase 8** — real SMS gateway; removes the `/dev/last_otp` helper + the `DevCapturingSmsSender` decorator.
- Note for deployed envs: `Cors:AllowedOrigins` must list the real web origin(s); an empty array falls back
to the localhost dev origin (safe — a real user's origin won't match, so cross-origin is effectively denied
until configured).