← Post-phase server audit

# Runtime services — deployment topology

Generated from the canonical Markdown — do not hand-edit. Audit date 2026-07-10.

Audit date: 2026-07-10 · Derivation: every entry below is justified from the code — the DI seam that depends on it, the config key that names it, or the package/startup wiring that talks to it. Nothing is invented; "not needed" claims are backed by the absence of the package/code. Make-it-real steps live in `dev/shared-working-context/reports/mocks-registry.md` (row references below).

The shape in one sentence: today the API binary talks to exactly one external system — SQL Server (app DB + log DB); everything else (18 seams) is an in-process mock, so "deployment" today is one container + one database — and the table below is the roadmap of what must exist as each seam goes real.

## Service inventory

# | Service | Purpose | Depends via (seam / config) | MVP? | Registry row
--- | --- | --- | --- | --- | ---
1 | SQL Server (app DB `Baya`) | System of record — 12 schemas (`usr ops geo catalog verif search booking payments payouts reviews messaging partner`) | EF Core; `ConnectionStrings:SqlServer` | Required now | —
2 | SQL Server (log DB `Baya_Logs`) | Serilog sink in deployed envs (Warning+, auto-created `log.LogEvents`) | `ConnectionStrings:logDb` | Required now (deployed) | —
3 | Reverse proxy / TLS (nginx·caddy·traefik) | TLS termination, HTTP/1.1+2, forwarded headers | Kestrel config; JWE bearer | Required now | —
4 | Prometheus (+ Grafana) | Scrapes `/metrics`; health forwarded to gauges | `UseMetricServer` + OTel exporter | Recommended now | —
5 | Redis | `ICacheService` + `IDistributedLock` (money-path mutex) | `Seams:*` (keys TBD; none today) | Before >1 API instance | rows 14, 42
6 | MinIO / S3 / ArvanCloud | `IObjectStorage` — verification docs, avatars (REQ-006), invoice PDFs | `Seams:ObjectStorage:*` | Before real verification | row 13
7 | Job scheduler (Hangfire/Quartz, in-app on SQL) | The deferred crons: payout batch, expiry scan, no-show, Moadian poll | hosted services (no interface exists) | Before unattended ops | row 26
8 | SMS gateway (Kavenegar·Ghasedak·SMS.ir) | `ISmsSender` — OTP delivery (login is impossible without it) | `Seams:Sms:*` (to be added) | Launch-critical | row 12
9 | PSP / IPG + Shaparak (ZarinPal·Sadad·Vandar·Jibit) | `IPaymentProvider` + `IWebhookVerifier` + `ISettlementSplitProvider` (تسهیم) | encrypted `payment_gateways.config_json` | Real payments | rows 39–41
10 | BNPL providers (SnappPay·Digipay) | `IBnplProvider` / `IBnplProviderResolver` / `ICurrencyNormalizer` | `Seams:Bnpl:*`, `Seams:Currency:*`, gateway config | Optional at launch | rows 46–47
11 | Bank-transfer rail (Jibit·Vandar·Sadad payout API → PAYA/SATNA) | `IBankTransferProvider` — weekly nurse payouts | `Seams:BankTransfer:*` | Real payouts | row 23
12 | سامانه مودیان (tax e-invoicing) | `IMoadianClient` — legal invoice registration | `Seams:Moadian:*` + signing cert | Legal — soon after launch | row 45
13 | KYC bridge vendor (Finnotech-class) | `IShahkarVerifier` + `IIdentityKycProvider` + `IBankAccountOwnershipVerifier` | `Seams:Shahkar:*`, `Seams:IdentityKyc:*`, `Seams:BankOwnership:*` | Real verification + payout gate | rows 27, 28, 30
14 | Geocoding (Neshan) | `IGeocoder` — address → coordinates (EVV distance) | `Seams:Geocoding:*` | With real EVV | row 31
15 | Review-moderation classifier (LLM/API) | `IReviewModerationService` — AI pre-screen | `Seams:ReviewModeration:*` | Optional (human queue is default) | row 33
16 | MoH / INO / eNamad | `ICredentialVerifier` + `ILicenseVerificationService` — manual admin process; no public B2B API exists | `Seams:LicenseVerification:*` | Manual = the MVP design | rows 29, 50
17 | Elasticsearch | `INurseSearch` alt backend + outbox feeder | `Search:Backend` (non-`sql` throws today) | Not MVP — SQL search is real | rows 38, 43

Explicitly not needed (verified absent from `server/Directory.Packages.props` and code): message broker (no RabbitMQ/Kafka), Redis today (no `StackExchange.Redis`), Hangfire/Quartz today, Elasticsearch client (`Elastic.Clients.*`), any cloud SDK, any payment/SMS vendor SDK. The only externally-pointing package beyond SQL Server is `Serilog.Sinks.Elasticsearch` — its wiring is commented out (`Baya.Infrastructure.CrossCutting/Logging/LoggingConfiguration.cs:58-70`).

## Dependency graph

Solid edges are live today; dashed edges are behind a mocked seam (the arrow exists in code, the wire does not). One line per node below the graph.

Mermaid source (canonical, in the .md)

    flowchart LR
      subgraph fe[Frontend]
        WEB["Next.js client"]
      end
      subgraph host["API host — Baya.Web.Api (:5002)"]
        API["ASP.NET Core API\nREST /api/v1 · /metrics · /HealthCheck"]
        JOBS["In-proc interval jobs\n(→ Hangfire/Quartz later)"]
      end
      subgraph data[Data & platform infra]
        SQL[("SQL Server 'Baya'\n12 schemas · migrations on boot")]
        LOG[("SQL Server 'Baya_Logs'\nSerilog sink, Warning+")]
        REDIS[("Redis — cache + dist. lock\n(in-proc today)")]
        S3[("MinIO / S3 — object storage\n(local disk today)")]
      end
      subgraph money["Money rails (all mocked today)"]
        PSP["PSP / IPG + Shaparak\ncapture · webhook · تسهیم"]
        BNPL["BNPL — SnappPay / Digipay"]
        BANK["PAYA / SATNA payout rail"]
        MOAD["سامانه مودیان e-invoicing"]
      end
      subgraph trust["Trust & identity rails (all mocked today)"]
        SMS["SMS gateway (OTP)"]
        KYC["Shahkar · e-KYC · استعلام شبا"]
        GEOC["Neshan geocoding"]
        MODAI["Review-moderation classifier"]
        MANUAL["MoH / INO / eNamad\n(manual admin review)"]
      end
      subgraph obs[Observability]
        PROM["Prometheus (+ Grafana)"]
      end
      WEB -->|HTTPS/JSON| API
      API --> SQL
      API --> LOG
      JOBS --- API
      API -.-> REDIS
      API -.-> S3
      API -.-> SMS
      API <-.-> PSP
      API <-.-> BNPL
      API -.-> BANK
      API -.-> MOAD
      API -.-> KYC
      API -.-> GEOC
      API -.-> MODAI
      MANUAL -.- API
      PROM -->|scrape /metrics| API

- Next.js client — the only API consumer; reads `NEXT_PUBLIC_API_URL` (root `CLAUDE.md`).
- API — single ASP.NET Core host; all seams resolve in-process today.
- In-proc jobs — the two `BackgroundService` sweeps (booking-request expiry 1 min, notification retention 24 h); the scheduler upgrade re-homes them (plan §4.1).
- SQL Server `Baya` — system of record; migrations + seeding run on every non-Testing boot.
- SQL Server `Baya_Logs` — deployed-env Serilog sink (auto-creates DB/table).
- Redis — target for `ICacheService`/`IDistributedLock`; nothing speaks Redis yet.
- MinIO/S3 — target for `IObjectStorage`; local disk + `file://` URLs today.
- PSP/IPG — card capture, callback signatures, تسهیم settlement split.
- BNPL — provider-financed installments; settle/revert callbacks.
- PAYA/SATNA — weekly nurse payout batches + async reconciliation.
- مودیان — legal e-invoice registration (pending→registered poll).
- KYC vendor — Shahkar phone↔NID, identity+liveness, Sheba ownership (payout gate).
- Neshan — geocoding for address coordinates / EVV distance.
- Moderation classifier — optional AI pre-screen; human moderation is the default gate.
- MoH/INO/eNamad — human verification workflows, by design (no API exists).
- Prometheus — scrapes `/metrics`; health check results forwarded as gauges.

## Per-service notes

### 1–2 · SQL Server (`Baya` + `Baya_Logs`)

- Evidence: `UseSqlServer` at `server/src/Infrastructure/Baya.Infrastructure.Persistence/ServiceConfiguration/ServiceCollectionExtensions.cs:41`; sink at `…CrossCutting/Logging/LoggingConfiguration.cs:44-45` (schema `log`, auto-create); the 12 schemas via per-entity `ToTable(name, schema)` (e.g. `PaymentsConfig/LedgerEntryConfig.cs:18`, `PayoutsConfig/NursePayoutConfig.cs:19`).
- Default: `mcr.microsoft.com/mssql/server:2022-latest` (Developer for dev; licensed edition in prod). Both DBs fit one instance; `Baya_Logs` can move later.
- Config: `ConnectionStrings:SqlServer`, `ConnectionStrings:logDb` — rotate + externalize first (plan §1.1; live `sa` credentials are committed today).
- Health/readiness: the app's only health check (`/HealthCheck`, `Monitoring/Configurations/HealthCheckConfigurations.cs:17`); `logDb` has none (plan §7.2). Boot runs `MigrateAsync` + 3 seeders (`Program.cs:99-104`) → the login needs DDL rights and concurrent multi-node boot races (plan §4.3).

### 3 · Reverse proxy / TLS

- Evidence of need: JWE bearer auth (`RequireHttpsMetadata` must be true in prod — `Identity/ServiceConfiguration/ServiceCollectionExtension.cs:139`); Kestrel `EndpointDefaults=Http2` breaks non-TLS HTTP/1.1 (`appsettings.json:29-33`, plan §1.5); the rate limiter partitions on `RemoteIpAddress` with no ForwardedHeaders middleware (`WebFramework/ServiceConfiguration/RateLimitingServiceExtension.cs:69`, plan §1.6) — the proxy must pass `X-Forwarded-For` and the app must be taught to honor it.
- Default: caddy 2 / nginx 1.27; terminate TLS, h2 to clients, HTTP/1.1 (or h2c) upstream once §1.5 lands.

### 4 · Prometheus (+ Grafana)

- Evidence: `/metrics` via prometheus-net `UseMetricServer` + OTel `AddPrometheusExporter` (two stacks — consolidate, plan §7.1) at `Monitoring/Configurations/PrometheusMetricsConfigurations.cs:11` and `OpenTelemetryConfigurations.cs:21`; health forwarded (`HealthCheckConfigurations.cs:18`).
- Default: `prom/prometheus:v2.53` + `grafana/grafana:11`. No tracing backend exists yet (metrics-only); an OTLP collector becomes relevant with plan §7.1.

### 5 · Redis

- Evidence of the gap: `MemoryCacheService` and `InProcessDistributedLock` (`CrossCutting/Seams/MemoryCacheService.cs:11`, `InProcessDistributedLock.cs:14`) — single-process only; the money-path lock convention `booking:{id}:payment|refund` is already in the handlers.
- Default: `redis:7-alpine` (AOF on). Required the moment a second API instance runs (shared cache invalidation generation-tokens + cross-instance money mutex). Config keys to be introduced with the swap (plan §4.2); none exist today.

### 6 · MinIO / S3 / ArvanCloud

- Evidence: `LocalDiskObjectStorage` writes under a temp root and returns `file://` URLs (`CrossCutting/Seams/LocalDiskObjectStorage.cs:20,56`); consumers: b6 verification documents (signed-URL upload flow), future avatars (REQ-006), invoice `PdfStorageKey` (`InvoicesConfig/InvoiceConfig.cs` — `pdf_storage_key` column).
- Default: `minio/minio:latest` (S3-compatible; ArvanCloud object storage is the Iran-hosted option).
- Config: `Seams:ObjectStorage:RootPath` today → bucket/endpoint/keys with the swap. Presigned PUT/GET with expiry is the contract the frontend already codes against.

### 7 · Job scheduler

- Evidence: two `PeriodicTimer` hosted services only (`Persistence/ServiceConfiguration/ServiceCollectionExtensions.cs:63,67`); the payout/expiry/no-show/ Moadian crons are admin-manual with seeded-but-unread cadence keys (plan §4.1). No `IJobScheduler` interface exists — the registry name is aspirational.
- Default: Hangfire on the existing SQL Server (no new container) — dashboard behind admin auth; or Quartz with SQL persistence. Not a separate service to "spin up", but it changes the SQL footprint (schema) and ops (dashboard, retries).

### 8 · SMS gateway

- Evidence: `LoggingSmsSender` logs OTPs instead of sending (`CrossCutting/Seams/LoggingSmsSender.cs:16`) — no real user can log in; per-phone resend window + `otp` rate policy already enforced upstream.
- Default: Kavenegar / Ghasedak / SMS.ir (SaaS — API key, no container). Keys to add: `Seams:Sms:{ApiKey,SenderLine,BaseUrl}` (registry row 12). Template/pattern OTP send for deliverability.

### 9 · PSP / IPG + Shaparak (تسهیم)

- Evidence: `MockPaymentProvider` (instant success, `VerifyAsync` echoes the expected amount — `CrossCutting/Seams/MockPaymentProvider.cs:24`), `MockWebhookVerifier` (marker-based "signature" — `MockWebhookVerifier.cs:21`), `MockSettlementSplitProvider` (`MockSettlementSplitProvider.cs:13`). Real merchant credentials belong in the encrypted `payment_gateways.config_json` (seeded sandbox row: `Persistence/ServiceCollectionExtensions.cs:109-117` — environment-gate it, plan §1.4).
- Default: SaaS (ZarinPal / Sadad / Vandar / Jibit) — needs merchant + terminal registration and تسهیم (settlement-split) setup to registered IBANs; webhook endpoint is already public (`POST webhooks/payments/{provider}`) with upsert-first idempotency in place.

### 10 · BNPL providers

- Evidence: `MockBnplProvider` drives the full verb set + state machine (`CrossCutting/Seams/MockBnplProvider.cs:17-53`); callback endpoint `WebhooksBnplController` (signed, rate-limited). `Seams:Bnpl:*` + `Seams:Currency:TomanToIrrMultiplier` (`SeamOptions.cs:98-120,79`).
- Default: SnappPay and/or Digipay (SaaS, OAuth). Do plan §2.1 first — the revert-clearing path is currently unreachable, so real BNPL refunds would strand ledger state.

### 11 · Bank-transfer rail (PAYA/SATNA)

- Evidence: `MockBankTransferProvider` settles every instruction instantly (`CrossCutting/Seams/MockBankTransferProvider.cs:18-31`); the handler already chooses PAYA vs SATNA by `payout_satna_threshold_irr` (`platform_configs` seed row 22) and the irreversibility backstops (unconditional `UNIQUE(booking_id)` link, forward-only payout machine) are in place.
- Default: Jibit / Vandar / Sadad payout API (SaaS). Needs the source settlement account + the async `submitted → paid/failed` reconciliation callback the mock collapses (registry row 23, steps 3–4).

### 12 · سامانه مودیان

- Evidence: `MockMoadianClient` leaves invoices `pending` forever (`CrossCutting/Seams/MockMoadianClient.cs:21`); no reconciliation job exists (plan §6.5). VAT-on-commission + sequential invoice numbers are already correct server-side.
- Default: government SaaS — enrollment (memory/economic code) + a signing certificate; the certificate is a deploy-time secret.

### 13 · KYC bridge (Shahkar / e-KYC / استعلام شبا)

- Evidence: three deterministic mocks with magic-value failure cases (`MockShahkarVerifier.cs:26-37`, `MockIdentityKycProvider.cs:25`, `MockBankAccountOwnershipVerifier.cs:26`); handlers already persist `external_response_json` and treat shared-SIM as a handled state. The Sheba-ownership result gates first payouts (`matched_national_id`).
- Default: one Finnotech-class bridge covers all three استعلام‌ها (SaaS; API keys under `Seams:{Shahkar,IdentityKyc,BankOwnership}:*`).

### 14 · Geocoding (Neshan)

- Evidence: `MockGeocoder` jitters ±5 km around 8 hardcoded centroids (`MockGeocoder.cs:52`) — EVV distance checks are noise until this (and/or REQ-008's user pin) is real. `Seams:Geocoding:*` is one of only three seam sections present in `appsettings.json` (`:22-26`).
- Default: Neshan (Iran coverage; SaaS API key), rate-limit/retry per registry row 31.

### 15 · Review moderation

- Evidence: keyword-list mock; clean text stays in the human queue by default (`MockReviewModerationService.cs:23`; `Seams:ReviewModeration:*`). Human `ModerateReviewCommand` retains decision authority — so this can stay mocked indefinitely at low volume.
- Default: any text-moderation API / LLM endpoint when review volume outgrows the human queue.

### 16 · MoH / INO / eNamad — a process, not a service

- Evidence: `MockCredentialVerifier` always returns `RequiresManualReview` (`MockCredentialVerifier.cs:18`); `MockLicenseVerificationService` likewise (`MockLicenseVerificationService.cs:26`). The registry itself records no public B2B API exists — the admin review queue is the real implementation. Provision: admin staffing + the f15 console.

### 17 · Elasticsearch — deliberately later

- Evidence: `SqlNurseSearch` is the real MVP backend; `Search:Backend` ≠ `sql` throws at startup (`Persistence/ServiceConfiguration/ServiceCollectionExtensions.cs:74-78`); no ES client package.
- Default when needed: `elasticsearch:8.x` + the outbox feeder (registry rows 38/43; plan §8.1).

## Deployment notes (from the code, not aspiration)

1. Boot = migrate + seed. Every non-Testing start applies EF migrations and seeds roles, the `admin`/`qw123321` user, and an active sandbox ZarinPal gateway (`Program.cs:99-104`). Until plan §1.3/§1.4/§4.3 land: single-instance start-up, DDL-privileged login, and clean up the seeded credentials per environment.
2. Environment files: `appsettings.json` ≡ `appsettings.Development.json` (byte-identical); no Production/Staging file exists. All non-secret env differences ride on ~14 `Seams:*` groups whose defaults live in code (`SeamOptions.cs`), not in config files.
3. HTTP posture: HTTP/2-only Kestrel default (plan §1.5), gRPC plugin + reflection always on (plan §7.5), TLS required for JWE sanity.
4. Single-instance constraints today: in-memory cache, in-proc money lock, in-proc sweeps, per-instance rate-limit buckets. Scaling past one instance requires plan §4.2 (Redis) + §4.3 (migrations) first — the DB uniques keep money correct either way, but locks/cache/limits silently degrade.
5. Logs: deployed envs write Warning+ to `Baya_Logs` only (Information dropped — plan §7.3); dev writes console + `logs/log.json`.
6. Client: the Next.js app needs `NEXT_PUBLIC_API_URL` pointing at the proxy; wire casing camelCase; snake_case routes.