# Backend Phase 0 — Foundation, cross-cutting seams & starter cleanup > **Mission:** turn the inherited starter skeleton into a clean Balinyaar foundation. Remove the demo > scaffolding, stand up the **REST API surface** the marketplace needs (the baseline is gRPC-only today), > wire the missing cross-cutting plumbing (rate limiting, request logging, current-user + audit-field > stamping, PII encryption), and define every **mock-able external dependency as a DI seam** with a > faithful in-memory implementation so later phases just plug real providers in. No domain tables yet — > this phase makes the next fifteen phases possible and consistent. > > **Track:** backend · **Depends on:** nothing (first phase) · **Unlocks:** every backend phase > **Before you start, read [`../_shared/agent-operating-rules.md`](../_shared/agent-operating-rules.md).** It is not optional. --- ## 1. Context — where this sits This is the very first build phase. The server (`server/`, .NET 10, Clean Architecture, CQRS via **`martinothamar/Mediator`** — *not* MediatR) already ships a working spine you must **keep and build on**, and some demo scaffolding you must **remove**. **What already exists (do not rebuild) — confirmed in the codebase:** - ASP.NET Core Identity + **JWE/JWT** + **phone-OTP (passwordless TOTP)** auth, the dynamic-permission RBAC system, the CQRS pipeline (`ValidateCommandBehavior`, `MetricsBehaviour`), `OperationResult`, Mapster, FluentValidation, Serilog, OpenTelemetry/prometheus, health checks, the `BaseController` + the full MVC filter/versioning/Swagger stack, and `Baya.Tests.Setup` (in-memory SQLite). - The 12-project Clean-Arch solution `Baya.sln`. Identity tables map to the **`usr`** schema. **What is starter scaffolding you will remove in this phase:** - The `Order` demo end-to-end: `Domain/Entities/Order/Order.cs`, `Application/Features/Order/**`, `Contracts/Persistence/IOrderRepository.cs`, `Persistence/Repositories/OrderRepository.cs`, `Persistence/Configuration/OrderConfig/`, the `User.Orders` nav, `IUnitOfWork.OrderRepository`, `OrderGrpcServices` + `OrderGrpcServiceModels.proto` and its wiring. - The three old migrations + snapshot (`Migrations/2021…`, `2022…`, `2023…`, namespace `Persistence.Migrations`) — they predate the marketplace and will be regenerated as the new baseline in **backend-phase-1**. **Known gaps you will close here:** no HTTP controllers exist (REST surface must be created); `LoggingBehavior<,>` is defined but never registered; **no rate limiting** is wired despite `CONVENTIONS.md` §11; OTP delivery is stubbed (handled in b2). ## 2. Required reading (do this first) - [`../_shared/agent-operating-rules.md`](../_shared/agent-operating-rules.md) and [`../_shared/backend-conventions-checklist.md`](../_shared/backend-conventions-checklist.md). - [`server/CLAUDE.md`](../../../server/CLAUDE.md) — *Startup wiring*, *Project map*, *Identity & auth*, *Persistence*; and [`server/CONVENTIONS.md`](../../../server/CONVENTIONS.md) — §1 routing, §4 controllers, §6 persistence (audit fields, soft delete), §9 logging, §11 security (rate limiting), §12 service registration. - [`product/overview/platform-summary.md`](../../../product/overview/platform-summary.md) — the four ground truths (no cash custody, 10% VAT, full-upfront BNPL, weekly payout) and the **IRR-Rials-always** rule that shapes the money types you scaffold here. - [`../../contracts/conventions/api-conventions.md`](../../contracts/conventions/api-conventions.md) and [`money-and-types.md`](../../contracts/conventions/money-and-types.md) — the envelope/format your REST surface must honour. - Read the actual code you'll touch: `Baya.Web.Api/Program.cs`, `Baya.Application/Common/*Behavior*.cs`, `Baya.WebFramework/BaseController/*`, `Baya.Infrastructure.Persistence/ApplicationDbContext.cs`, and each project's `ServiceConfiguration/` extension. ## 3. Scope — build this ### 3.1 Remove the starter scaffolding Delete the `Order` feature/entity/repository/config/gRPC/proto and the three old migrations listed in §1. Remove every reference (nav property, `IUnitOfWork` member, DI wiring, proto compile). The solution must build clean afterwards. Do **not** remove Identity/auth/observability — those stay. ### 3.2 Stand up the REST surface The marketplace is REST/JSON. Create the first versioned controller(s) under `Baya.Web.Api/Controllers/V1/` following `CONVENTIONS.md` §4 exactly (sealed, `BaseController`, inject `ISender`, `[controller]`/`[action]` tokens, `base.OperationResult(...)`, `[Display(Description=...)]`). A minimal **health/ping** or **reference** controller is enough to prove the pipeline end-to-end through Swagger; later phases add the real controllers. Confirm `MapControllers()` now serves real routes and Swagger renders them. ### 3.3 Close the wiring gaps - **Register `LoggingBehavior<,>`** in the Application pipeline (alongside the existing behaviors), in the correct order, so every request is structurally logged (no PII — `CONVENTIONS.md` §9). - **Rate limiting** (`CONVENTIONS.md` §11): add `AddRateLimiter` via a `ServiceConfiguration/` extension and `UseRateLimiter()` **before** `UseAuthentication()` in `Program.cs`. Define named policies (a per-IP fixed-window/token-bucket baseline) ready for auth/OTP/refund/payout endpoints to apply later. ### 3.4 Current-user + audit-field stamping - Add **`ICurrentUser`** (Application contract) wrapping the HTTP context (user id, roles), registered **Scoped**, with a null-object for non-HTTP contexts (jobs/tests). - Add a **SaveChanges interceptor** (or extend the existing `SavingChanges` hook) that stamps `CreatedAt/ModifiedAt` and `CreatedById/ModifiedById` from `ICurrentUser` on a shared base/interface, per `CONVENTIONS.md` §6. Define the audit-capable base type (extend the existing `BaseEntity`/ `ITimeModification` rather than inventing a parallel one). The `audit_logs` *table* and the append-only change log come in **b1** — here you build the *field-stamping* plumbing the interceptor needs and leave a clean extension point for b1 to add log-row writing. ### 3.5 Define the cross-cutting seams (interfaces + mocks + DI) Create these **Application-layer interfaces** with real-shaped signatures, an **Infrastructure mock implementation** each, and **DI registration** via a `ServiceConfiguration/` extension (selected by config so a real impl swaps in later). Keep amounts as IRR `long`. This phase **introduces** these seams; later phases reuse them. - **`IDateTimeProvider`** — `DateTimeOffset UtcNow` (so time is testable; no `DateTime.Now` in handlers). - **`IFieldEncryptor`** — `string Encrypt(string)` / `string Decrypt(string)` (+ a deterministic `Hash(string)` for lookups like `iban_hash`). Mock = local symmetric key from config; **never** log plaintext PII. This is what every encrypted PII column will use. - **`ICacheService`** — typed get/set/remove with TTL and a `GetOrCreateAsync`. Mock = in-memory (`IMemoryCache`-backed). The config accessor (b1) and read-heavy queries cache through this; Redis later. - **`IObjectStorage`** — presigned/streamed put/get/delete keyed by an opaque storage key, returning a retrievable URL. Mock = local-disk/in-memory store under a scratch path. MinIO/S3 later. - **`INotificationDispatcher`** — `DispatchAsync(notification)` with a channel concept (in-app now; SMS/push later). Mock = no-op/log; the real in-app `notifications` write lands in b15. Define the seam now so emitting domains (booking, payments) can depend on it. > The **OTP/SMS** seam (`ISmsSender`) is introduced in **b2** (with the auth REST surface), and the > **search/payment/bnpl/bank/vendor** seams in their phases — do not pre-build those here; just leave the > registry rows. (Listed in [mocks-registry.md](../../shared-working-context/reports/mocks-registry.md).) ### 3.6 Money & convention guardrails Add (or confirm) the small shared helpers the money path will rely on: IRR is `long`/`BIGINT` everywhere; if you add a money value object keep it integer-only with no float path. Document the rule in `server/CONVENTIONS.md` if not already explicit. ## 4. Mocks & seams in this phase | Seam | Mock behaviour | Registry | | --- | --- | --- | | `IDateTimeProvider` | returns real UTC now (deterministic override in tests) | n/a (not external) | | `IFieldEncryptor` | local symmetric key from config; passthrough-but-reversible; deterministic hash | update row | | `ICacheService` | in-memory `IMemoryCache` | update row | | `IObjectStorage` | local-disk/in-memory blob store | update row | | `INotificationDispatcher` | log/no-op (in-app write arrives in b15) | update row | Record each in [`mocks-registry.md`](../../shared-working-context/reports/mocks-registry.md) (seam, file, what's faked, config keys, how to make real, status 🟡). ## 5. Critical rules you must not get wrong - **Don't break the working spine.** Identity, JWE auth, dynamic permissions, the CQRS behaviors, and observability must still work after cleanup. Run the existing tests. - **Seams live in Application, implementations in Infrastructure.** Never reference Infrastructure from Application/Domain. Register via `ServiceConfiguration/` extensions called from `Program.cs` — no inline DI in `Program.cs` (`CONVENTIONS.md` §12). - **Mock = real interface, fake body.** No `if (mock)` branches in handlers; selection is by registration. - **`IFieldEncryptor` never leaks plaintext** into logs, exceptions, or non-`AsNoTracking` query projections of PII. - **Audit-field stamping is infrastructure, not handler code** — handlers never set `CreatedById` etc. ## 6. Definition of Done The shared [definition-of-done.md](../_shared/definition-of-done.md), plus: - [ ] `Order` + the three old migrations are gone; `dotnet build Baya.sln` is clean (zero new warnings); `dotnet test Baya.sln` passes (existing identity tests still green). - [ ] At least one real REST controller is reachable through Swagger and returns the `OperationResult` envelope. - [ ] `LoggingBehavior` registered; rate limiter wired (policies defined, `UseRateLimiter` placed before auth). - [ ] `ICurrentUser` + audit-field interceptor in place; the five seams (§3.5) registered with mocks. - [ ] The **Project map** in `server/CLAUDE.md` is updated (Order removed; seams/cross-cutting noted); `CONVENTIONS.md` notes the IRR-`BIGINT` money rule if it wasn't explicit. ## 7. How to test (what a human can verify after this phase) - `dotnet build Baya.sln` and `dotnet test Baya.sln` — both succeed. - `dotnet run` the API → open `/swagger` → the new REST controller appears and its endpoint returns a 200 in the standard envelope; the Order endpoints are gone. - Hit the rate-limited test endpoint past its limit → `429`. - A unit test proves `IFieldEncryptor.Decrypt(Encrypt(x)) == x` and the audit interceptor stamps `CreatedAt`/`CreatedById` on a save (use the SQLite test context). ## 8. Hand off & document (close the phase) - **Docs:** update `server/CLAUDE.md` *Project map* (+ a one-line note on the new seams and where they're registered). Update `CONVENTIONS.md` only if you established a new rule. - **Contracts:** no domain API yet — but publish the first `swagger.json` snapshot ([openapi/README.md](../../contracts/openapi/README.md)) so the frontend's f0 can wire its type pipeline against the envelope shape. - **Handoff & report:** write `shared-working-context/backend/handoff/after-backend-phase-0.md` (the spine is clean, REST works, seams exist, what f0/b1 can rely on), append to `backend/STATUS.md`, write `reports/backend-phase-0-report.md`, and update `reports/mocks-registry.md` (the five rows → 🟡). - **Memory:** save a `project` memory note for any non-obvious decision (e.g. how the seams are selected by config, the audit interceptor design) with a `MEMORY.md` pointer.