307 lines
21 KiB
Markdown
307 lines
21 KiB
Markdown
# Runtime services — deployment topology
|
||
|
||
**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.
|
||
|
||
> **Refinement-phase-8 update (2026-07-13):** services **8–14** and **16** below now have a **real HTTP adapter
|
||
> shipped behind their seam**, config-selected by a per-rail `Seams:*:Provider` selector (mock stays the default).
|
||
> "Depends via" now points at a real client, not just a planned one — provisioning the vendor account + credential
|
||
> and flipping the selector turns each on, no code change. What is still genuinely absent (no adapter): **Redis**
|
||
> (5), **Elasticsearch** (17), and the LLM **review-moderation classifier** (15, optional). MoH/INO/eNamad (16)
|
||
> stay **manual by design**. See the mocks-registry refinement-phase-8 banner for the provider tokens per rail.
|
||
>
|
||
> **Refinement-phase-9 update (2026-07-13) — observability (service 4):** the two overlapping metric stacks were
|
||
> consolidated onto **one OpenTelemetry stack** — prometheus-net was removed; metrics are scraped at `/metrics` via
|
||
> the OTel Prometheus exporter, and **distributed tracing** (ASP.NET Core + EF Core) was added, exporting **OTLP
|
||
> only when `OpenTelemetry:Otlp:Endpoint` is configured** (an MVP with Prometheus alone is unchanged). A request's
|
||
> `ApiResult.requestId` is its W3C trace id (support ↔ trace 1:1). Health checks split into `/healthz/live`
|
||
> (process) vs `/healthz/ready` (app DB + log DB [deployed] + an object-storage write probe); `/HealthCheck` stays
|
||
> as the aggregate. An **OTLP collector** (Grafana Tempo / Jaeger / OTEL Collector) becomes the optional new
|
||
> observability service when trace export is turned on. Elasticsearch (17) is still deferred.
|
||
|
||
## 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) · optional **OTLP collector** | Scrapes `/metrics` (one OTel stack, refinement-phase-9); traces export OTLP when configured | OTel metrics + tracing; `OpenTelemetry:Otlp:Endpoint` | **Recommended now** (collector optional) | — |
|
||
| 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** — in-process, SQL only (refinement-phase-7 **done**) | The recurring crons: booking-expiry, notification-retention, credential-expiry scan, no-show sweep, weekly payout-batch generation (Moadian/refund-settlement poll = Phase 8) | `RecurringJobSchedulerHostedService` + `IRecurringJob`s | **Done for single instance** (no new infra) | 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
|
||
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 (refinement-phase-9 — §7.2 landed):** split `/healthz/live` (process) vs `/healthz/ready`
|
||
(app DB + `logDb` [deployed] + object-storage write probe); `/HealthCheck` stays as the aggregate
|
||
(`Monitoring/Configurations/HealthCheckConfigurations.cs`). 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) · optional OTLP collector
|
||
|
||
- **Evidence (refinement-phase-9 — §7.1 landed):** **one** OpenTelemetry stack — metrics scraped at `/metrics` via
|
||
`UseOpenTelemetryPrometheusScrapingEndpoint()` (`Monitoring/Configurations/PrometheusMetricsConfigurations.cs`),
|
||
plus `WithTracing` (ASP.NET Core + EF Core) exporting **OTLP only when `OpenTelemetry:Otlp:Endpoint` is set**
|
||
(`OpenTelemetryConfigurations.cs`). The duplicate prometheus-net stack (`UseMetricServer`/`UseHttpMetrics`/
|
||
`ForwardToPrometheus` + packages) was removed. A request's `ApiResult.requestId` is its W3C trace id.
|
||
- **Default:** `prom/prometheus:v2.53` + `grafana/grafana:11`. A **tracing backend / OTLP collector** (Grafana
|
||
Tempo · Jaeger · OpenTelemetry Collector) is the optional new service — set `OpenTelemetry:Otlp:Endpoint` at it to
|
||
turn trace + metric export on; Prometheus-scrape-only is an acceptable MVP.
|
||
|
||
### 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 (refinement-phase-7).** DDL is a separate deploy step — `dotnet run -- migrate` applies
|
||
migrations + idempotent seeders then exits. **Development** boot still migrates + seeds (incl. the
|
||
Development-only sandbox gateway + demo world) for convenience; **deployed** boot only *checks* the schema is
|
||
current (`EnsureSchemaUpToDateAsync`, fail-fast on a pending migration) and seeds roles/break-glass admin. So
|
||
multi-instance boots no longer race on DDL and the runtime login needs no permanent DDL rights (plan §1.3/§1.4
|
||
also landed — no committed `admin`/`qw123321`; sandbox gateway is Development-only).
|
||
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:** mixed `Http1AndHttp2` Kestrel (refinement-phase-5), so gRPC shares the listener via ALPN;
|
||
gRPC **reflection is Development-only** (refinement-phase-9 §7.5); TLS required for JWE sanity.
|
||
4. **Single-instance constraints today:** in-memory cache, in-proc money lock, the in-proc recurring-job
|
||
scheduler (refinement-phase-7 — its per-tick lock is that same in-proc seam), per-instance rate-limit buckets.
|
||
§4.3 (migrations split from boot) **landed**; scaling past one instance still requires §4.2 (Redis for the
|
||
shared cache + the cross-instance lock the scheduler/money path use) first — the DB uniques keep money
|
||
*correct* either way, but locks/cache/limits/scheduler-de-dup silently degrade.
|
||
5. **Logs (refinement-phase-9 — §7.3 landed):** deployed envs write **Information+** to `Baya_Logs` (framework
|
||
categories held at Warning); **no PII/secrets** (the OTP code is no longer logged in any env). The dead
|
||
Elasticsearch sink + package were removed; log-table retention is an ops/DBA task (or ship logs to the OTLP
|
||
collector). 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.
|