Files
baya-monorepo/dev/post-phase/server/runtime-services.html
T
2026-07-10 11:49:55 +03:30

223 lines
36 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Runtime services — deployment topology</title>
<style>
:root{
--bg:#faf9f6; --fg:#26221c; --muted:#6d675e; --panel:#ffffff; --border:#ddd7cc;
--accent:#0e7a63; --accent-soft:#e4f2ee; --code-bg:#f1ede5; --th-bg:#efeadf;
--warn:#a04b12; color-scheme: light dark;
}
@media (prefers-color-scheme: dark){
:root{
--bg:#191714; --fg:#e8e3da; --muted:#a29a8d; --panel:#211e1a; --border:#3a352d;
--accent:#4fc3a8; --accent-soft:#1e3630; --code-bg:#2a261f; --th-bg:#2d2921;
--warn:#e09355;
}
}
*{box-sizing:border-box}
body{margin:0;background:var(--bg);color:var(--fg);
font:16px/1.62 ui-sans-serif,system-ui,"Segoe UI",Roboto,"Vazirmatn",sans-serif;}
main{max-width:72rem;margin:0 auto;padding:2.5rem 1.5rem 5rem;}
h1{font-size:1.75rem;line-height:1.25;margin:.2rem 0 1rem;}
h2{font-size:1.35rem;margin:2.4rem 0 .7rem;padding-top:1rem;border-top:1px solid var(--border);}
h3{font-size:1.08rem;margin:1.8rem 0 .5rem;color:var(--accent);}
p{margin:.6rem 0;}
a{color:var(--accent);text-decoration:none;} a:hover{text-decoration:underline;}
code{background:var(--code-bg);border-radius:4px;padding:.1em .35em;
font:.86em ui-monospace,"Cascadia Code",Consolas,monospace;overflow-wrap:anywhere;}
pre{background:var(--code-bg);border:1px solid var(--border);border-radius:8px;
padding: .9rem 1rem;overflow-x:auto;}
pre code{background:none;padding:0;}
hr{border:none;border-top:1px solid var(--border);margin:2rem 0;}
.tblwrap{overflow-x:auto;margin:1rem 0;border:1px solid var(--border);border-radius:8px;}
table{border-collapse:collapse;width:100%;font-size:.92rem;}
th{background:var(--th-bg);text-align:start;position:sticky;top:0;}
th,td{border-bottom:1px solid var(--border);padding:.5rem .7rem;vertical-align:top;}
td:not(:last-child),th:not(:last-child){border-inline-end:1px solid var(--border);}
tbody tr:last-child td{border-bottom:none;}
ul,ol{margin:.6rem 0;padding-inline-start:1.5rem;}
li{margin:.35rem 0;}
li>code:first-child{font-weight:600;}
nav.toc{background:var(--panel);border:1px solid var(--border);border-radius:10px;
padding:1rem 1.3rem;margin:1.4rem 0 2rem;font-size:.92rem;}
nav.toc strong{display:block;margin-bottom:.4rem;}
nav.toc ul{margin:.2rem 0;padding-inline-start:1.1rem;list-style:none;}
nav.toc>ul{padding-inline-start:0;}
nav.toc li{margin:.2rem 0;}
nav.toc .l3{padding-inline-start:1.1rem;font-size:.88em;color:var(--muted);}
nav.toc .l3 a{color:var(--muted);}
.crumbs{font-size:.85rem;color:var(--muted);margin-bottom:.3rem;}
.crumbs a{color:var(--muted);}
.stamp{font-size:.85rem;color:var(--muted);margin:-.4rem 0 1rem;}
figure.diagram{margin:1.5rem 0;padding:1rem;background:var(--panel);
border:1px solid var(--border);border-radius:10px;overflow-x:auto;}
figure.diagram svg{display:block;min-width:900px;width:100%;height:auto;}
details.src{margin:.6rem 0 1.6rem;font-size:.85rem;color:var(--muted);}
details.src summary{cursor:pointer;}
svg text{fill:var(--fg);font:13px ui-sans-serif,system-ui,"Segoe UI",sans-serif;}
svg .t2{font-size:11px;fill:var(--muted);}
svg .grp-title{font-size:12px;font-weight:600;fill:var(--muted);letter-spacing:.04em;}
svg .box{fill:var(--panel);stroke:var(--fg);stroke-opacity:.55;rx:8;}
svg .box.live{stroke:var(--accent);stroke-opacity:1;stroke-width:1.6;}
svg .box.mock{stroke-dasharray:5 4;}
svg .grp{fill:none;stroke:var(--border);stroke-width:1.2;rx:12;}
svg .edge{fill:none;stroke-width:1.7;}
svg .edge.real{stroke:var(--accent);}
svg .edge.mock{stroke:var(--muted);stroke-dasharray:6 4;}
svg .lbl{font-size:10.5px;fill:var(--muted);}
svg .arr-real{fill:var(--accent);} svg .arr-mock{fill:var(--muted);}
</style>
</head>
<body><main>
<div class="crumbs"><a href="index.html">← Post-phase server audit</a></div>
<h1 id="runtime-services-deployment-topology">Runtime services — deployment topology</h1><p class="stamp">Generated from the canonical Markdown — do not hand-edit. Audit date 2026-07-10.</p><nav class="toc"><strong>Contents</strong><ul><li class="l2"><a href="#service-inventory">Service inventory</a></li><li class="l2"><a href="#dependency-graph">Dependency graph</a></li><li class="l2"><a href="#per-service-notes">Per-service notes</a></li><li class="l3"><a href="#1-2-sql-server-baya-baya-logs">12 · SQL Server (<code>Baya</code> + <code>Baya_Logs</code>)</a></li><li class="l3"><a href="#3-reverse-proxy-tls">3 · Reverse proxy / TLS</a></li><li class="l3"><a href="#4-prometheus-grafana">4 · Prometheus (+ Grafana)</a></li><li class="l3"><a href="#5-redis">5 · Redis</a></li><li class="l3"><a href="#6-minio-s3-arvancloud">6 · MinIO / S3 / ArvanCloud</a></li><li class="l3"><a href="#7-job-scheduler">7 · Job scheduler</a></li><li class="l3"><a href="#8-sms-gateway">8 · SMS gateway</a></li><li class="l3"><a href="#9-psp-ipg-shaparak-تسهیم">9 · PSP / IPG + Shaparak (تسهیم)</a></li><li class="l3"><a href="#10-bnpl-providers">10 · BNPL providers</a></li><li class="l3"><a href="#11-bank-transfer-rail-paya-satna">11 · Bank-transfer rail (PAYA/SATNA)</a></li><li class="l3"><a href="#12-سامانه-مودیان">12 · سامانه مودیان</a></li><li class="l3"><a href="#13-kyc-bridge-shahkar-e-kyc-استعلام-شبا">13 · KYC bridge (Shahkar / e-KYC / استعلام شبا)</a></li><li class="l3"><a href="#14-geocoding-neshan">14 · Geocoding (Neshan)</a></li><li class="l3"><a href="#15-review-moderation">15 · Review moderation</a></li><li class="l3"><a href="#16-moh-ino-enamad-a-process-not-a-service">16 · MoH / INO / eNamad — a process, not a service</a></li><li class="l3"><a href="#17-elasticsearch-deliberately-later">17 · Elasticsearch — deliberately later</a></li><li class="l2"><a href="#deployment-notes-from-the-code-not-aspiration">Deployment notes (from the code, not aspiration)</a></li></ul></nav>
<p><strong>Audit date:</strong> 2026-07-10 · <strong>Derivation:</strong> 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 <code>dev/shared-working-context/reports/mocks-registry.md</code> (row references below).</p>
<p><strong>The shape in one sentence:</strong> today the API binary talks to exactly <strong>one external system — SQL Server</strong> (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.</p>
<h2 id="service-inventory">Service inventory</h2>
<div class="tblwrap"><table><thead><tr><th>#</th><th>Service</th><th>Purpose</th><th>Depends via (seam / config)</th><th>MVP?</th><th>Registry row</th></tr></thead><tbody><tr><td>1</td><td><strong>SQL Server</strong> (app DB <code>Baya</code>)</td><td>System of record — 12 schemas (<code>usr ops geo catalog verif search booking payments payouts reviews messaging partner</code>)</td><td>EF Core; <code>ConnectionStrings:SqlServer</code></td><td><strong>Required now</strong></td><td></td></tr><tr><td>2</td><td><strong>SQL Server</strong> (log DB <code>Baya_Logs</code>)</td><td>Serilog sink in deployed envs (Warning+, auto-created <code>log.LogEvents</code>)</td><td><code>ConnectionStrings:logDb</code></td><td><strong>Required now</strong> (deployed)</td><td></td></tr><tr><td>3</td><td><strong>Reverse proxy / TLS</strong> (nginx·caddy·traefik)</td><td>TLS termination, HTTP/1.1+2, forwarded headers</td><td>Kestrel config; JWE bearer</td><td><strong>Required now</strong></td><td></td></tr><tr><td>4</td><td><strong>Prometheus</strong> (+ Grafana)</td><td>Scrapes <code>/metrics</code>; health forwarded to gauges</td><td><code>UseMetricServer</code> + OTel exporter</td><td><strong>Recommended now</strong></td><td></td></tr><tr><td>5</td><td><strong>Redis</strong></td><td><code>ICacheService</code> + <code>IDistributedLock</code> (money-path mutex)</td><td><code>Seams:*</code> (keys TBD; none today)</td><td>Before &gt;1 API instance</td><td>rows 14, 42</td></tr><tr><td>6</td><td><strong>MinIO / S3 / ArvanCloud</strong></td><td><code>IObjectStorage</code> — verification docs, avatars (REQ-006), invoice PDFs</td><td><code>Seams:ObjectStorage:*</code></td><td>Before real verification</td><td>row 13</td></tr><tr><td>7</td><td><strong>Job scheduler</strong> (Hangfire/Quartz, in-app on SQL)</td><td>The deferred crons: payout batch, expiry scan, no-show, Moadian poll</td><td>hosted services (no interface exists)</td><td>Before unattended ops</td><td>row 26</td></tr><tr><td>8</td><td><strong>SMS gateway</strong> (Kavenegar·Ghasedak·SMS.ir)</td><td><code>ISmsSender</code> — OTP delivery (login is impossible without it)</td><td><code>Seams:Sms:*</code> (to be added)</td><td><strong>Launch-critical</strong></td><td>row 12</td></tr><tr><td>9</td><td><strong>PSP / IPG + Shaparak</strong> (ZarinPal·Sadad·Vandar·Jibit)</td><td><code>IPaymentProvider</code> + <code>IWebhookVerifier</code> + <code>ISettlementSplitProvider</code> (تسهیم)</td><td>encrypted <code>payment_gateways.config_json</code></td><td>Real payments</td><td>rows 3941</td></tr><tr><td>10</td><td><strong>BNPL providers</strong> (SnappPay·Digipay)</td><td><code>IBnplProvider</code> / <code>IBnplProviderResolver</code> / <code>ICurrencyNormalizer</code></td><td><code>Seams:Bnpl:*</code>, <code>Seams:Currency:*</code>, gateway config</td><td>Optional at launch</td><td>rows 4647</td></tr><tr><td>11</td><td><strong>Bank-transfer rail</strong> (Jibit·Vandar·Sadad payout API → PAYA/SATNA)</td><td><code>IBankTransferProvider</code> — weekly nurse payouts</td><td><code>Seams:BankTransfer:*</code></td><td>Real payouts</td><td>row 23</td></tr><tr><td>12</td><td><strong>سامانه مودیان</strong> (tax e-invoicing)</td><td><code>IMoadianClient</code> — legal invoice registration</td><td><code>Seams:Moadian:*</code> + signing cert</td><td>Legal — soon after launch</td><td>row 45</td></tr><tr><td>13</td><td><strong>KYC bridge vendor</strong> (Finnotech-class)</td><td><code>IShahkarVerifier</code> + <code>IIdentityKycProvider</code> + <code>IBankAccountOwnershipVerifier</code></td><td><code>Seams:Shahkar:*</code>, <code>Seams:IdentityKyc:*</code>, <code>Seams:BankOwnership:*</code></td><td>Real verification + payout gate</td><td>rows 27, 28, 30</td></tr><tr><td>14</td><td><strong>Geocoding</strong> (Neshan)</td><td><code>IGeocoder</code> — address → coordinates (EVV distance)</td><td><code>Seams:Geocoding:*</code></td><td>With real EVV</td><td>row 31</td></tr><tr><td>15</td><td><strong>Review-moderation classifier</strong> (LLM/API)</td><td><code>IReviewModerationService</code> — AI pre-screen</td><td><code>Seams:ReviewModeration:*</code></td><td>Optional (human queue is default)</td><td>row 33</td></tr><tr><td>16</td><td><strong>MoH / INO / eNamad</strong></td><td><code>ICredentialVerifier</code> + <code>ILicenseVerificationService</code><strong>manual admin process; no public B2B API exists</strong></td><td><code>Seams:LicenseVerification:*</code></td><td>Manual = the MVP design</td><td>rows 29, 50</td></tr><tr><td>17</td><td><strong>Elasticsearch</strong></td><td><code>INurseSearch</code> alt backend + outbox feeder</td><td><code>Search:Backend</code> (non-<code>sql</code> throws today)</td><td><strong>Not MVP</strong> — SQL search is real</td><td>rows 38, 43</td></tr></tbody></table></div>
<p><strong>Explicitly not needed</strong> (verified absent from <code>server/Directory.Packages.props</code> and code): message broker (no RabbitMQ/Kafka), Redis today (no <code>StackExchange.Redis</code>), Hangfire/Quartz today, Elasticsearch client (<code>Elastic.Clients.*</code>), any cloud SDK, any payment/SMS vendor SDK. The only externally-pointing package beyond SQL Server is <code>Serilog.Sinks.Elasticsearch</code> — its wiring is commented out (<code>Baya.Infrastructure.CrossCutting/Logging/LoggingConfiguration.cs:58-70</code>).</p>
<hr>
<h2 id="dependency-graph">Dependency graph</h2>
<p>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.</p>
<figure class="diagram"><svg viewBox="0 0 1160 830" role="img" aria-label="Balinyaar runtime dependency graph">
<defs>
<marker id="arr-real" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse"><path class="arr-real" d="M0,0 L10,5 L0,10 z"/></marker>
<marker id="arr-mock" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse"><path class="arr-mock" d="M0,0 L10,5 L0,10 z"/></marker>
</defs>
<rect class="box live" x="20" y="150" width="210" height="62" rx="8"/><text x="125" y="178" text-anchor="middle" font-weight="600">Next.js client</text><text class="t2" x="125" y="195" text-anchor="middle">HTTPS · JSON (camelCase)</text>
<rect class="box live" x="20" y="430" width="210" height="62" rx="8"/><text x="125" y="458" text-anchor="middle" font-weight="600">Prometheus (+ Grafana)</text><text class="t2" x="125" y="475" text-anchor="middle">scrapes /metrics + health</text>
<rect class="grp" x="290" y="90" width="270" height="330" rx="12"/>
<text class="grp-title" x="425" y="114" text-anchor="middle">API HOST · Baya.Web.Api (:5002)</text>
<rect class="box live" x="310" y="132" width="230" height="78" rx="8"/><text x="425" y="150" text-anchor="middle" font-weight="600">ASP.NET Core API</text><text class="t2" x="425" y="166" text-anchor="middle">REST /api/v1 · Swagger</text><text class="t2" x="425" y="180" text-anchor="middle">/metrics · /HealthCheck · gRPC</text>
<rect class="box live" x="310" y="250" width="230" height="62" rx="8"/><text x="425" y="278" text-anchor="middle" font-weight="600">In-proc interval jobs</text><text class="t2" x="425" y="295" text-anchor="middle">expiry 1min · retention 24h</text>
<path class="edge real" d="M 425 250 L 425 210"/>
<text class="t2" x="425" y="345" text-anchor="middle">18 vendor seams resolve</text>
<text class="t2" x="425" y="361" text-anchor="middle">in-process (mocks) today —</text>
<text class="t2" x="425" y="377" text-anchor="middle">dashed edges = the wire to build</text>
<rect class="grp" x="640" y="20" width="500" height="216" rx="12"/>
<text class="grp-title" x="660" y="44">DATA &amp; PLATFORM INFRA</text>
<rect class="box live" x="660" y="58" width="225" height="62" rx="8"/><text x="772.5" y="86" text-anchor="middle" font-weight="600">SQL Server «Baya»</text><text class="t2" x="772.5" y="103" text-anchor="middle">12 schemas · system of record</text>
<rect class="box live" x="900" y="58" width="220" height="62" rx="8"/><text x="1010" y="86" text-anchor="middle" font-weight="600">SQL Server «Baya_Logs»</text><text class="t2" x="1010" y="103" text-anchor="middle">Serilog sink (deployed envs)</text>
<rect class="box mock" x="660" y="146" width="225" height="62" rx="8"/><text x="772.5" y="174" text-anchor="middle" font-weight="600">Redis</text><text class="t2" x="772.5" y="191" text-anchor="middle">cache + dist. lock — in-proc now</text>
<rect class="box mock" x="900" y="146" width="220" height="62" rx="8"/><text x="1010" y="174" text-anchor="middle" font-weight="600">MinIO / S3</text><text class="t2" x="1010" y="191" text-anchor="middle">object storage — local disk now</text>
<rect class="grp" x="640" y="260" width="500" height="216" rx="12"/>
<text class="grp-title" x="660" y="284">MONEY RAILS (ALL MOCKED TODAY)</text>
<rect class="box mock" x="660" y="298" width="225" height="62" rx="8"/><text x="772.5" y="326" text-anchor="middle" font-weight="600">PSP / IPG + Shaparak</text><text class="t2" x="772.5" y="343" text-anchor="middle">capture · webhook · تسهیم</text>
<rect class="box mock" x="900" y="298" width="220" height="62" rx="8"/><text x="1010" y="326" text-anchor="middle" font-weight="600">BNPL</text><text class="t2" x="1010" y="343" text-anchor="middle">SnappPay / Digipay</text>
<rect class="box mock" x="660" y="386" width="225" height="62" rx="8"/><text x="772.5" y="414" text-anchor="middle" font-weight="600">PAYA / SATNA rail</text><text class="t2" x="772.5" y="431" text-anchor="middle">weekly nurse payouts</text>
<rect class="box mock" x="900" y="386" width="220" height="62" rx="8"/><text x="1010" y="414" text-anchor="middle" font-weight="600">سامانه مودیان</text><text class="t2" x="1010" y="431" text-anchor="middle">tax e-invoicing</text>
<rect class="grp" x="640" y="500" width="500" height="300" rx="12"/>
<text class="grp-title" x="660" y="524">TRUST &amp; IDENTITY RAILS (ALL MOCKED TODAY)</text>
<rect class="box mock" x="660" y="538" width="225" height="62" rx="8"/><text x="772.5" y="566" text-anchor="middle" font-weight="600">SMS gateway (OTP)</text><text class="t2" x="772.5" y="583" text-anchor="middle">launch-critical — logs only now</text>
<rect class="box mock" x="900" y="538" width="220" height="62" rx="8"/><text x="1010" y="566" text-anchor="middle" font-weight="600">KYC bridge</text><text class="t2" x="1010" y="583" text-anchor="middle">Shahkar · e-KYC · استعلام شبا</text>
<rect class="box mock" x="660" y="626" width="225" height="62" rx="8"/><text x="772.5" y="654" text-anchor="middle" font-weight="600">Neshan geocoding</text><text class="t2" x="772.5" y="671" text-anchor="middle">EVV distance accuracy</text>
<rect class="box mock" x="900" y="626" width="220" height="62" rx="8"/><text x="1010" y="654" text-anchor="middle" font-weight="600">Review moderation</text><text class="t2" x="1010" y="671" text-anchor="middle">optional — human queue default</text>
<rect class="box mock" x="660" y="714" width="460" height="62" rx="8"/><text x="890" y="742" text-anchor="middle" font-weight="600">MoH / INO / eNamad</text><text class="t2" x="890" y="759" text-anchor="middle">manual admin review — no public API exists</text>
<path class="edge real" d="M 230 181 C 270 181, 270 171, 310 171" marker-end="url(#arr-real)"/><text class="lbl" x="270" y="170" text-anchor="middle">HTTPS/JSON</text>
<path class="edge real" d="M 230 455 C 260 455, 260 400, 290 400" marker-end="url(#arr-real)"/><text class="lbl" x="260" y="419.5" text-anchor="middle">scrape</text>
<path class="edge real" d="M 560 130 C 600 130, 600 100, 640 100" marker-end="url(#arr-real)"/><text class="lbl" x="600" y="99" text-anchor="middle">EF Core · Serilog</text>
<path class="edge mock" d="M 560 170 C 600 170, 600 185, 640 185" marker-end="url(#arr-mock)"/><text class="lbl" x="600" y="193.5" text-anchor="middle">cache · lock · files</text>
<path class="edge mock" d="M 560 245 C 600 245, 600 320, 640 320" marker-end="url(#arr-mock)"/><text class="lbl" x="600" y="268.5" text-anchor="middle">capture · payouts · invoices</text>
<path class="edge mock" d="M 640 395 C 600 395, 600 290, 560 290" marker-end="url(#arr-mock)"/><text class="lbl" x="600" y="356.5" text-anchor="middle">webhooks / callbacks</text>
<path class="edge mock" d="M 560 335 C 600 335, 600 610, 640 610" marker-end="url(#arr-mock)"/><text class="lbl" x="600" y="466.5" text-anchor="middle">OTP · استعلام · geocode · moderation</text>
<rect class="grp" x="20" y="600" width="210" height="130" rx="12"/>
<text class="grp-title" x="125" y="624" text-anchor="middle">LEGEND</text>
<path class="edge real" d="M 40 646 L 90 646"/><text class="t2" x="100" y="650">live today</text>
<path class="edge mock" d="M 40 674 L 90 674"/><text class="t2" x="100" y="678">mocked seam — to build</text>
<rect class="box mock" x="40" y="694" width="50" height="20" rx="6"/><text class="t2" x="100" y="708">service to provision</text>
</svg></figure>
<details class="src"><summary>Mermaid source (canonical, in the .md)</summary><pre><code>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 &amp; 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 &amp; 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 --&gt;|HTTPS/JSON| API
API --&gt; SQL
API --&gt; LOG
JOBS --- API
API -.-&gt; REDIS
API -.-&gt; S3
API -.-&gt; SMS
API &lt;-.-&gt; PSP
API &lt;-.-&gt; BNPL
API -.-&gt; BANK
API -.-&gt; MOAD
API -.-&gt; KYC
API -.-&gt; GEOC
API -.-&gt; MODAI
MANUAL -.- API
PROM --&gt;|scrape /metrics| API</code></pre></details>
<ul><li><strong>Next.js client</strong> — the only API consumer; reads <code>NEXT_PUBLIC_API_URL</code> (root <code>CLAUDE.md</code>).</li><li><strong>API</strong> — single ASP.NET Core host; all seams resolve in-process today.</li><li><strong>In-proc jobs</strong> — the two <code>BackgroundService</code> sweeps (booking-request expiry 1 min, notification retention 24 h); the scheduler upgrade re-homes them (plan §4.1).</li><li><strong>SQL Server <code>Baya</code></strong> — system of record; migrations + seeding run on every non-Testing boot.</li><li><strong>SQL Server <code>Baya_Logs</code></strong> — deployed-env Serilog sink (auto-creates DB/table).</li><li><strong>Redis</strong> — target for <code>ICacheService</code>/<code>IDistributedLock</code>; nothing speaks Redis yet.</li><li><strong>MinIO/S3</strong> — target for <code>IObjectStorage</code>; local disk + <code>file://</code> URLs today.</li><li><strong>PSP/IPG</strong> — card capture, callback signatures, تسهیم settlement split.</li><li><strong>BNPL</strong> — provider-financed installments; settle/revert callbacks.</li><li><strong>PAYA/SATNA</strong> — weekly nurse payout batches + async reconciliation.</li><li><strong>مودیان</strong> — legal e-invoice registration (pending→registered poll).</li><li><strong>KYC vendor</strong> — Shahkar phone↔NID, identity+liveness, Sheba ownership (payout gate).</li><li><strong>Neshan</strong> — geocoding for address coordinates / EVV distance.</li><li><strong>Moderation classifier</strong> — optional AI pre-screen; human moderation is the default gate.</li><li><strong>MoH/INO/eNamad</strong> — human verification workflows, by design (no API exists).</li><li><strong>Prometheus</strong> — scrapes <code>/metrics</code>; health check results forwarded as gauges.</li></ul>
<hr>
<h2 id="per-service-notes">Per-service notes</h2>
<h3 id="1-2-sql-server-baya-baya-logs">12 · SQL Server (<code>Baya</code> + <code>Baya_Logs</code>)</h3>
<ul><li><strong>Evidence:</strong> <code>UseSqlServer</code> at <code>server/src/Infrastructure/Baya.Infrastructure.Persistence/ServiceConfiguration/ServiceCollectionExtensions.cs:41</code>; sink at <code>…CrossCutting/Logging/LoggingConfiguration.cs:44-45</code> (schema <code>log</code>, auto-create); the 12 schemas via per-entity <code>ToTable(name, schema)</code> (e.g. <code>PaymentsConfig/LedgerEntryConfig.cs:18</code>, <code>PayoutsConfig/NursePayoutConfig.cs:19</code>).</li><li><strong>Default:</strong> <code>mcr.microsoft.com/mssql/server:2022-latest</code> (Developer for dev; licensed edition in prod). Both DBs fit one instance; <code>Baya_Logs</code> can move later.</li><li><strong>Config:</strong> <code>ConnectionStrings:SqlServer</code>, <code>ConnectionStrings:logDb</code><strong>rotate + externalize first</strong> (plan §1.1; live <code>sa</code> credentials are committed today).</li><li><strong>Health/readiness:</strong> the app's only health check (<code>/HealthCheck</code>, <code>Monitoring/Configurations/HealthCheckConfigurations.cs:17</code>); <code>logDb</code> has none (plan §7.2). Boot runs <code>MigrateAsync</code> + 3 seeders (<code>Program.cs:99-104</code>) → the login needs DDL rights and concurrent multi-node boot races (plan §4.3).</li></ul>
<h3 id="3-reverse-proxy-tls">3 · Reverse proxy / TLS</h3>
<ul><li><strong>Evidence of need:</strong> JWE bearer auth (<code>RequireHttpsMetadata</code> must be true in prod — <code>Identity/ServiceConfiguration/ServiceCollectionExtension.cs:139</code>); Kestrel <code>EndpointDefaults=Http2</code> breaks non-TLS HTTP/1.1 (<code>appsettings.json:29-33</code>, plan §1.5); the rate limiter partitions on <code>RemoteIpAddress</code> with <strong>no ForwardedHeaders middleware</strong> (<code>WebFramework/ServiceConfiguration/RateLimitingServiceExtension.cs:69</code>, plan §1.6) — the proxy must pass <code>X-Forwarded-For</code> <em>and</em> the app must be taught to honor it.</li><li><strong>Default:</strong> caddy 2 / nginx 1.27; terminate TLS, h2 to clients, HTTP/1.1 (or h2c) upstream once §1.5 lands.</li></ul>
<h3 id="4-prometheus-grafana">4 · Prometheus (+ Grafana)</h3>
<ul><li><strong>Evidence:</strong> <code>/metrics</code> via prometheus-net <code>UseMetricServer</code> + OTel <code>AddPrometheusExporter</code> (two stacks — consolidate, plan §7.1) at <code>Monitoring/Configurations/PrometheusMetricsConfigurations.cs:11</code> and <code>OpenTelemetryConfigurations.cs:21</code>; health forwarded (<code>HealthCheckConfigurations.cs:18</code>).</li><li><strong>Default:</strong> <code>prom/prometheus:v2.53</code> + <code>grafana/grafana:11</code>. No tracing backend exists yet (metrics-only); an OTLP collector becomes relevant with plan §7.1.</li></ul>
<h3 id="5-redis">5 · Redis</h3>
<ul><li><strong>Evidence of the gap:</strong> <code>MemoryCacheService</code> and <code>InProcessDistributedLock</code> (<code>CrossCutting/Seams/MemoryCacheService.cs:11</code>, <code>InProcessDistributedLock.cs:14</code>) — single-process only; the money-path lock convention <code>booking:{id}:payment|refund</code> is already in the handlers.</li><li><strong>Default:</strong> <code>redis:7-alpine</code> (AOF on). <strong>Required the moment a second API instance runs</strong> (shared cache invalidation generation-tokens + cross-instance money mutex). Config keys to be introduced with the swap (plan §4.2); none exist today.</li></ul>
<h3 id="6-minio-s3-arvancloud">6 · MinIO / S3 / ArvanCloud</h3>
<ul><li><strong>Evidence:</strong> <code>LocalDiskObjectStorage</code> writes under a temp root and returns <code>file://</code> URLs (<code>CrossCutting/Seams/LocalDiskObjectStorage.cs:20,56</code>); consumers: b6 verification documents (signed-URL upload flow), future avatars (REQ-006), invoice <code>PdfStorageKey</code> (<code>InvoicesConfig/InvoiceConfig.cs</code><code>pdf_storage_key</code> column).</li><li><strong>Default:</strong> <code>minio/minio:latest</code> (S3-compatible; ArvanCloud object storage is the Iran-hosted option).</li><li><strong>Config:</strong> <code>Seams:ObjectStorage:RootPath</code> today → bucket/endpoint/keys with the swap. Presigned PUT/GET with expiry is the contract the frontend already codes against.</li></ul>
<h3 id="7-job-scheduler">7 · Job scheduler</h3>
<ul><li><strong>Evidence:</strong> two <code>PeriodicTimer</code> hosted services only (<code>Persistence/ServiceConfiguration/ServiceCollectionExtensions.cs:63,67</code>); the payout/expiry/no-show/ Moadian crons are admin-manual with seeded-but-unread cadence keys (plan §4.1). <strong>No <code>IJobScheduler</code> interface exists</strong> — the registry name is aspirational.</li><li><strong>Default:</strong> 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).</li></ul>
<h3 id="8-sms-gateway">8 · SMS gateway</h3>
<ul><li><strong>Evidence:</strong> <code>LoggingSmsSender</code> logs OTPs instead of sending (<code>CrossCutting/Seams/LoggingSmsSender.cs:16</code>) — <strong>no real user can log in</strong>; per-phone resend window + <code>otp</code> rate policy already enforced upstream.</li><li><strong>Default:</strong> Kavenegar / Ghasedak / SMS.ir (SaaS — API key, no container). Keys to add: <code>Seams:Sms:{ApiKey,SenderLine,BaseUrl}</code> (registry row 12). Template/pattern OTP send for deliverability.</li></ul>
<h3 id="9-psp-ipg-shaparak-تسهیم">9 · PSP / IPG + Shaparak (تسهیم)</h3>
<ul><li><strong>Evidence:</strong> <code>MockPaymentProvider</code> (instant success, <code>VerifyAsync</code> echoes the expected amount — <code>CrossCutting/Seams/MockPaymentProvider.cs:24</code>), <code>MockWebhookVerifier</code> (marker-based "signature" — <code>MockWebhookVerifier.cs:21</code>), <code>MockSettlementSplitProvider</code> (<code>MockSettlementSplitProvider.cs:13</code>). Real merchant credentials belong in the <strong>encrypted</strong> <code>payment_gateways.config_json</code> (seeded sandbox row: <code>Persistence/ServiceCollectionExtensions.cs:109-117</code> — environment-gate it, plan §1.4).</li><li><strong>Default:</strong> SaaS (ZarinPal / Sadad / Vandar / Jibit) — needs merchant + terminal registration and تسهیم (settlement-split) setup to registered IBANs; webhook endpoint is already public (<code>POST webhooks/payments/{provider}</code>) with upsert-first idempotency in place.</li></ul>
<h3 id="10-bnpl-providers">10 · BNPL providers</h3>
<ul><li><strong>Evidence:</strong> <code>MockBnplProvider</code> drives the full verb set + state machine (<code>CrossCutting/Seams/MockBnplProvider.cs:17-53</code>); callback endpoint <code>WebhooksBnplController</code> (signed, rate-limited). <code>Seams:Bnpl:*</code> + <code>Seams:Currency:TomanToIrrMultiplier</code> (<code>SeamOptions.cs:98-120,79</code>).</li><li><strong>Default:</strong> SnappPay and/or Digipay (SaaS, OAuth). <strong>Do plan §2.1 first</strong> — the revert-clearing path is currently unreachable, so real BNPL refunds would strand ledger state.</li></ul>
<h3 id="11-bank-transfer-rail-paya-satna">11 · Bank-transfer rail (PAYA/SATNA)</h3>
<ul><li><strong>Evidence:</strong> <code>MockBankTransferProvider</code> settles every instruction instantly (<code>CrossCutting/Seams/MockBankTransferProvider.cs:18-31</code>); the handler already chooses PAYA vs SATNA by <code>payout_satna_threshold_irr</code> (<code>platform_configs</code> seed row 22) and the irreversibility backstops (unconditional <code>UNIQUE(booking_id)</code> link, forward-only payout machine) are in place.</li><li><strong>Default:</strong> Jibit / Vandar / Sadad payout API (SaaS). Needs the source settlement account + the async <code>submitted → paid/failed</code> reconciliation callback the mock collapses (registry row 23, steps 34).</li></ul>
<h3 id="12-سامانه-مودیان">12 · سامانه مودیان</h3>
<ul><li><strong>Evidence:</strong> <code>MockMoadianClient</code> leaves invoices <code>pending</code> forever (<code>CrossCutting/Seams/MockMoadianClient.cs:21</code>); no reconciliation job exists (plan §6.5). VAT-on-commission + sequential invoice numbers are already correct server-side.</li><li><strong>Default:</strong> government SaaS — enrollment (memory/economic code) + a signing certificate; the certificate is a deploy-time secret.</li></ul>
<h3 id="13-kyc-bridge-shahkar-e-kyc-استعلام-شبا">13 · KYC bridge (Shahkar / e-KYC / استعلام شبا)</h3>
<ul><li><strong>Evidence:</strong> three deterministic mocks with magic-value failure cases (<code>MockShahkarVerifier.cs:26-37</code>, <code>MockIdentityKycProvider.cs:25</code>, <code>MockBankAccountOwnershipVerifier.cs:26</code>); handlers already persist <code>external_response_json</code> and treat shared-SIM as a handled state. The Sheba-ownership result gates first payouts (<code>matched_national_id</code>).</li><li><strong>Default:</strong> one Finnotech-class bridge covers all three استعلام‌ها (SaaS; API keys under <code>Seams:{Shahkar,IdentityKyc,BankOwnership}:*</code>).</li></ul>
<h3 id="14-geocoding-neshan">14 · Geocoding (Neshan)</h3>
<ul><li><strong>Evidence:</strong> <code>MockGeocoder</code> jitters ±5 km around 8 hardcoded centroids (<code>MockGeocoder.cs:52</code>) — EVV distance checks are noise until this (and/or REQ-008's user pin) is real. <code>Seams:Geocoding:*</code> is one of only three seam sections present in <code>appsettings.json</code> (<code>:22-26</code>).</li><li><strong>Default:</strong> Neshan (Iran coverage; SaaS API key), rate-limit/retry per registry row 31.</li></ul>
<h3 id="15-review-moderation">15 · Review moderation</h3>
<ul><li><strong>Evidence:</strong> keyword-list mock; clean text stays in the human queue by default (<code>MockReviewModerationService.cs:23</code>; <code>Seams:ReviewModeration:*</code>). Human <code>ModerateReviewCommand</code> retains decision authority — so this can stay mocked indefinitely at low volume.</li><li><strong>Default:</strong> any text-moderation API / LLM endpoint when review volume outgrows the human queue.</li></ul>
<h3 id="16-moh-ino-enamad-a-process-not-a-service">16 · MoH / INO / eNamad — a process, not a service</h3>
<ul><li><strong>Evidence:</strong> <code>MockCredentialVerifier</code> always returns <code>RequiresManualReview</code> (<code>MockCredentialVerifier.cs:18</code>); <code>MockLicenseVerificationService</code> likewise (<code>MockLicenseVerificationService.cs:26</code>). The registry itself records <strong>no public B2B API exists</strong> — the admin review queue <em>is</em> the real implementation. Provision: admin staffing + the f15 console.</li></ul>
<h3 id="17-elasticsearch-deliberately-later">17 · Elasticsearch — deliberately later</h3>
<ul><li><strong>Evidence:</strong> <code>SqlNurseSearch</code> is the real MVP backend; <code>Search:Backend</code><code>sql</code> throws at startup (<code>Persistence/ServiceConfiguration/ServiceCollectionExtensions.cs:74-78</code>); no ES client package.</li><li><strong>Default when needed:</strong> <code>elasticsearch:8.x</code> + the outbox feeder (registry rows 38/43; plan §8.1).</li></ul>
<hr>
<h2 id="deployment-notes-from-the-code-not-aspiration">Deployment notes (from the code, not aspiration)</h2>
<ol><li><strong>Boot = migrate + seed.</strong> Every non-Testing start applies EF migrations and seeds roles, the <code>admin</code>/<code>qw123321</code> user, and an <strong>active sandbox ZarinPal gateway</strong> (<code>Program.cs:99-104</code>). Until plan §1.3/§1.4/§4.3 land: single-instance start-up, DDL-privileged login, and clean up the seeded credentials per environment.</li><li><strong>Environment files:</strong> <code>appsettings.json</code><code>appsettings.Development.json</code> (byte-identical); <strong>no Production/Staging file exists.</strong> All non-secret env differences ride on ~14 <code>Seams:*</code> groups whose defaults live in code (<code>SeamOptions.cs</code>), not in config files.</li><li><strong>HTTP posture:</strong> HTTP/2-only Kestrel default (plan §1.5), gRPC plugin + reflection always on (plan §7.5), TLS required for JWE sanity.</li><li><strong>Single-instance constraints today:</strong> 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 <em>correct</em> either way, but locks/cache/limits silently degrade.</li><li><strong>Logs:</strong> deployed envs write Warning+ to <code>Baya_Logs</code> only (Information dropped — plan §7.3); dev writes console + <code>logs/log.json</code>.</li><li><strong>Client:</strong> the Next.js app needs <code>NEXT_PUBLIC_API_URL</code> pointing at the proxy; wire casing camelCase; snake_case routes.</li></ol>
</main></body></html>