# Backend conventions checklist (quick reference)
The authoritative rules are [`server/CLAUDE.md`](../../../server/CLAUDE.md) and
[`server/CONVENTIONS.md`](../../../server/CONVENTIONS.md). This is a tick-list to keep handy while you
build. If anything here seems to conflict with those files, **those files win**.
## Architecture
- [ ] Clean Architecture respected: Domain → Application → Infrastructure → API; **dependencies point
inward**. Domain references nothing; Application references only Domain; Infrastructure/API
implement Application contracts. Never reference Infrastructure/API from Domain/Application.
- [ ] New cross-layer dependency or project/folder ⇒ update the **Project map** in `server/CLAUDE.md`.
- [ ] New infrastructure is registered via a `ServiceConfiguration/` extension method called from
`Program.cs` — never inline in `Program.cs`.
## CQRS / features
- [ ] Feature lives in `Baya.Application/Features//{Commands|Queries}//`.
- [ ] Requests are `record`; handlers are `internal sealed`; one handler per request.
- [ ] **Never throw for expected failures** — return `OperationResult.SuccessResult/FailureResult/NotFoundResult`.
- [ ] Contracts the handler needs are interfaces in `Application/Contracts/`, implemented in Infrastructure.
- [ ] Input-bearing commands have a FluentValidation validator (picked up by `ValidateCommandBehavior`).
## Persistence (EF Core)
- [ ] Read queries use `AsNoTracking()` **and** project with `.Select(...)` to a DTO — never hydrate
entities to map them. Mapping (Mapster) happens in the handler after the query.
- [ ] Every unbounded list is **paginated** (`Skip`/`Take`); no unbounded `ToListAsync()`.
- [ ] Access the DB via `IUnitOfWork` in handlers; commit once per command (`CommitAsync`).
- [ ] One `IEntityTypeConfiguration` per entity in `Persistence/Configuration/Config/`.
- [ ] Soft-deletable entities declare a global query filter (`!IsDeleted` / `deleted_at IS NULL`).
- [ ] Audit fields (`CreatedAt/ModifiedAt/CreatedById/ModifiedById`) are set by the SaveChanges
interceptor via `ICurrentUser` — handlers don't pass them.
- [ ] Encrypted PII columns (phone, national_id, IBAN, addresses, clinical notes) go through the field
encryptor seam — never stored or logged in plaintext.
## Performance, caching, money, idempotency
- [ ] Read-heavy/config/reference data is cached behind the cache seam with sensible invalidation;
`platform_configs` values are read through the typed config accessor (cached), not hardcoded.
- [ ] **Money is IRR `BIGINT` — no floats, anywhere.** Toman conversion happens only inside a provider
adapter at its boundary. The three booking amounts always satisfy
`gross = commission + payout`.
- [ ] Money-path writes are **idempotent** (webhook dedup on the unique external-event key; filtered
unique on succeeded transaction; forward-only state machines) and, where the phase says so, guarded
by a Redis **distributed lock** — with the DB constraint as the authoritative backstop.
- [ ] `ledger_entries` is append-only and balanced (Σdebit = Σcredit per `transaction_group_id`).
## API surface
- [ ] Controllers are `sealed`, inherit `BaseController`, inject `ISender`, return
`base.OperationResult(...)` — never `Ok()/BadRequest()/NotFound()` directly. (Note: the baseline
has **no** controllers yet — REST is introduced in an early phase; follow that pattern thereafter.)
- [ ] Routes use `[controller]`/`[action]` tokens (snake_case transformer); pass `CancellationToken`.
- [ ] Authorize with the narrowest fitting policy; auth/OTP/refund/payout-sensitive endpoints are
rate-limited.
- [ ] The endpoint's contract is published to `dev/contracts/` (see operating-rules §6).
## Quality
- [ ] `async`/`await` all the way; `CancellationToken` threaded through; no `.Result`/`.Wait()`/`async void`.
- [ ] Package versions only in `Directory.Packages.props`.
- [ ] Handler unit tests (NSubstitute) + at least one `WebApplicationFactory` integration test per new
feature area (happy path, 401, validation 400).
- [ ] `dotnet build` zero new warnings; `dotnet test` green; no dead code.