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(rootCLAUDE.md). - API — single ASP.NET Core host; all seams resolve in-process today.
- In-proc jobs — the two
BackgroundServicesweeps (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:
UseSqlServeratserver/src/Infrastructure/Baya.Infrastructure.Persistence/ServiceConfiguration/ServiceCollectionExtensions.cs:41; sink at…CrossCutting/Logging/LoggingConfiguration.cs:44-45(schemalog, auto-create); the 12 schemas via per-entityToTable(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_Logscan move later. - Config:
ConnectionStrings:SqlServer,ConnectionStrings:logDb— rotate + externalize first (plan §1.1; livesacredentials are committed today). - Health/readiness: the app's only health check (
/HealthCheck,Monitoring/Configurations/HealthCheckConfigurations.cs:17);logDbhas none (plan §7.2). Boot runsMigrateAsync+ 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 (
RequireHttpsMetadatamust be true in prod —Identity/ServiceConfiguration/ServiceCollectionExtension.cs:139); KestrelEndpointDefaults=Http2breaks non-TLS HTTP/1.1 (appsettings.json:29-33, plan §1.5); the rate limiter partitions onRemoteIpAddresswith no ForwardedHeaders middleware (WebFramework/ServiceConfiguration/RateLimitingServiceExtension.cs:69, plan §1.6) — the proxy must passX-Forwarded-Forand 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:
/metricsvia prometheus-netUseMetricServer+ OTelAddPrometheusExporter(two stacks — consolidate, plan §7.1) atMonitoring/Configurations/PrometheusMetricsConfigurations.cs:11andOpenTelemetryConfigurations.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:
MemoryCacheServiceandInProcessDistributedLock(CrossCutting/Seams/MemoryCacheService.cs:11,InProcessDistributedLock.cs:14) — single-process only; the money-path lock conventionbooking:{id}:payment|refundis 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:
LocalDiskObjectStoragewrites under a temp root and returnsfile://URLs (CrossCutting/Seams/LocalDiskObjectStorage.cs:20,56); consumers: b6 verification documents (signed-URL upload flow), future avatars (REQ-006), invoicePdfStorageKey(InvoicesConfig/InvoiceConfig.cs—pdf_storage_keycolumn). - Default:
minio/minio:latest(S3-compatible; ArvanCloud object storage is the Iran-hosted option). - Config:
Seams:ObjectStorage:RootPathtoday → bucket/endpoint/keys with the swap. Presigned PUT/GET with expiry is the contract the frontend already codes against.
7 · Job scheduler
- Evidence: two
PeriodicTimerhosted 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). NoIJobSchedulerinterface 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:
LoggingSmsSenderlogs OTPs instead of sending (CrossCutting/Seams/LoggingSmsSender.cs:16) — no real user can log in; per-phone resend window +otprate 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,VerifyAsyncechoes 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 encryptedpayment_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:
MockBnplProviderdrives the full verb set + state machine (CrossCutting/Seams/MockBnplProvider.cs:17-53); callback endpointWebhooksBnplController(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:
MockBankTransferProvidersettles every instruction instantly (CrossCutting/Seams/MockBankTransferProvider.cs:18-31); the handler already chooses PAYA vs SATNA bypayout_satna_threshold_irr(platform_configsseed row 22) and the irreversibility backstops (unconditionalUNIQUE(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/failedreconciliation callback the mock collapses (registry row 23, steps 3–4).
12 · سامانه مودیان
- Evidence:
MockMoadianClientleaves invoicespendingforever (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 persistexternal_response_jsonand 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:
MockGeocoderjitters ±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 inappsettings.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:*). HumanModerateReviewCommandretains 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:
MockCredentialVerifieralways returnsRequiresManualReview(MockCredentialVerifier.cs:18);MockLicenseVerificationServicelikewise (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:
SqlNurseSearchis the real MVP backend;Search:Backend≠sqlthrows 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)
- Boot = migrate + seed. Every non-Testing start applies EF migrations and seeds roles, the
admin/qw123321user, 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. - Environment files:
appsettings.json≡appsettings.Development.json(byte-identical); no Production/Staging file exists. All non-secret env differences ride on ~14Seams:*groups whose defaults live in code (SeamOptions.cs), not in config files. - HTTP posture: HTTP/2-only Kestrel default (plan §1.5), gRPC plugin + reflection always on (plan §7.5), TLS required for JWE sanity.
- 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.
- Logs: deployed envs write Warning+ to
Baya_Logsonly (Information dropped — plan §7.3); dev writes console +logs/log.json. - Client: the Next.js app needs
NEXT_PUBLIC_API_URLpointing at the proxy; wire casing camelCase; snake_case routes.