Sindh IT Portal — Facilitation DeskSpecification documents
Englishاردوسنڌي
← All documents

Technical Architecture

The end-to-end engineering architecture of the Sindh IT Portal — Facilitation Desk (SITP): components, layers, data, AI, security, deployment, and operations.

Field Value
Doc ID 15
Status Draft
Owner S&ITD / MAAHIR
Languages EN (master) · UR · SD
Related docs 03-non-functional-requirements/en.md, 04-functional-requirements/en.md, /specs/en/06-ticket-workflow/, 09-ai-ocr-architecture/en.md, 16-security-and-compliance/en.md, 18-deployment-and-hosting/en.md

1. Architecture Overview & Principles

The SITP is engineered as an API-first, modular monolith with a pluggable AI/OCR layer, multilingual by construction (EN/UR/Sindhi), role-scoped at every layer, feature-flagged end-to-end, and observability-first. The reference tenets below govern every downstream design decision in this document.

Architectural principles

# Principle What it means in SITP
1 Modular monolith A single NestJS deployment composed of strongly-bounded modules (Auth, Tickets, Org/RBAC, Files, Notifications, AI-bridge, Comms, Analytics, Integrations, …). One process, one deployable, clearly separable seams so any module can be extracted to a microservice later.
2 API-first Every capability is reachable over a versioned REST API; the Next.js portal, the future PWA/React Native app, the partner webhooks, and Metabase all consume the same API. No business logic in the UI.
3 Pluggable AI/OCR Every LLM and OCR call goes through a stable interface (AIClient, OCRClient, TranscriptionClient). Engines (Azure OpenAI / Google / AWS / self-hosted Llama+Qwen via Ollama+vLLM; Tesseract / Google Doc AI / Azure Doc Intelligence / AWS Textract) are selected per-feature and per data-sensitivity.
4 Multilingual & RTL-native EN/UR/Sindhi treated as first-class. Locale stored per user, propagated in JWT, applied to templates, search analysis, calendars (Gregorian + Hijri), and AI translation.
5 Role-scoped (RBAC + ABAC) Every API route is guarded; data queries are scoped by role and (for confidential/VIP tickets) by attribute. No "god queries".
6 Feature-flagged Every capability is toggleable by the Super Admin per department and per environment. Code ships dark; flags gate code paths at runtime.
7 Observability-first Logs (Loki), metrics (Grafana), traces (OpenTelemetry), errors (Sentry) and uptime are wired from day one — not bolted on.
8 Security-by-design Encryption at rest and in transit, least privilege, append-only audit, step-up auth, secrets vault, WAF, rate limiting, AV scan on every upload.
9 Data-residency-aware Sovereign data (CNIC, NADRA lookups, sensitive tickets) can be routed to on-prem AI/storage; cloud engines are only used for data classes the policy permits.
10 12-factor / clean architecture Config in environment, stateless processes, disposable containers, logs to stdout, admin tasks as one-off processes. Domain logic isolated from frameworks, DB drivers, and external SDKs.

The architecture favors a small number of well-operated components over a sprawl of microservices: one app DB, one cache, one search, one object store, one identity provider, one queue, one BI tool. This matches the team size, the single-host starting point on Server4Sale, and the provincial government's appetite for operational simplicity. The seams (interfaces, queues, modules) are designed so that scale-out, multi-tenant, or microservice extraction is a refactor, not a rewrite.


2. High-Level Architecture Diagram

The portal is delivered as two public front-ends (a Next.js full portal and a Docusaurus docs site) that sit behind an nginx reverse proxy. The proxy terminates TLS, serves the static docs build directly, and forwards /api to the NestJS API gateway, the long-lived WebSocket gateway, the Python FastAPI AI service, the embedded Metabase, and (in the admin plane) Keycloak. The API gateway is the single entry point to all stateful backends: MariaDB 10.11 (system of record), Redis (cache + sessions + queues), Meilisearch (multilingual search), MinIO (S3-compatible object storage, fronted by ClamAV), and BullMQ workers that offload OCR/AI/email/SMS/WhatsApp. External integrations — NADRA, SECP, FBR, SRB, PSEB, NITB e-Office, Mailjet, SMS gateways, WhatsApp Business API, Zoom/Meet/Teams — are reached only through adapter modules in the integrations layer.

flowchart LR subgraph Clients["Clients"] WEB["Next.js Full Portal<br/>(App Router + TS)"] DOC["Docusaurus Docs Site<br/>(static, /docs/)"] MOB["PWA → React Native (phase 4)"] PARTNER["Partner / 3rd-party consumers<br/>(REST + webhooks)"] end subgraph Edge["Edge / Reverse Proxy"] NX["nginx<br/>TLS · WAF · static docs · routing"] end subgraph App["Application plane"] GW["NestJS API Gateway<br/>(modular monolith · REST)"] WS["WebSocket Gateway<br/>(Socket.IO / Centrifugo)"] AI["FastAPI AI Service<br/>(Python · pluggable engines)"] WQ["BullMQ Workers<br/>(OCR · AI · email · SMS · WA · exports)"] FF["Feature-Flag Service<br/>(config + cache + audit)"] end subgraph Identity["Identity"] KC["Keycloak<br/>(OIDC · 2FA · SSO)"] end subgraph Data["Stateful backends"] DB[("MariaDB 10.11<br/>system of record")] RD[("Redis<br/>cache · sessions · pub-sub")] MS[("Meilisearch<br/>multilingual search")] MN[("MinIO / S3<br/>encrypted blobs")] CLAM["ClamAV<br/>(AV scan pipeline)"] end subgraph BI["Analytics plane"] MB["Metabase<br/>(embedded · self-hosted)"] end subgraph Ext["External integrations"] NADRA["NADRA (CNIC)"] SECP["SECP"] FBR["FBR / NTN"] SRB["SRB"] PSEB["PSEB"] EOFC["NITB e-Office"] MJ["Mailjet (email)"] SMS["SMS gateway<br/>(Jazz / Telenor)"] WA["WhatsApp Business API"] VID["Zoom / Meet / Teams"] end WEB --> NX DOC --> NX MOB --> NX PARTNER --> NX NX --> DOC NX --> GW NX --> WS NX --> AI NX --> KC NX --> MB GW --> DB GW --> RD GW --> MS GW --> MN GW --> RD GW --> WQ GW --> FF WS <--> RD AI --> MN AI --> RD WQ --> MN MN --> CLAM WQ --> MS MB --> DB GW --> NADRA GW --> SECP GW --> FBR GW --> SRB GW --> PSEB GW --> EOFC WQ --> MJ WQ --> SMS WQ --> WA GW --> VID

The diagram groups components into planes — Clients, Edge, Application, Identity, Stateful backends, Analytics, and External integrations — to make the trust and dependency boundaries explicit. The NestJS API gateway is the only component that talks to every stateful backend and to most external integrations; workers talk to storage, search, and outbound notification channels; the AI service talks only to object storage and Redis (for its own caching/job state) and is invoked by the gateway and by workers, never directly by clients. The WebSocket gateway shares Redis with the API for pub-sub fan-out so live updates produced by the API or by workers are pushed to the correct connected sessions.


3. Component Catalog

Component Purpose Technology Notes
Next.js Portal Public site, citizen/company rep UX, ticketing, KB, dashboards, internal comms, document generation Next.js (App Router) + TypeScript + Tailwind + shadcn/ui Server components for SEO + initial render; client islands for real-time and heavy interaction. Hosted by nginx (Node runtime behind the proxy).
Docusaurus Docs Site Public-facing product documentation (/docs/) in EN/UR/Sindhi with RTL and Hijri-aware content Docusaurus (TypeScript) Statically built; served directly by nginx at baseUrl: /docs/. No backend dependency.
nginx Reverse Proxy TLS termination, WAF (modsecurity/OWASP CRS), static docs hosting, path routing, gzip/brotli, rate limiting, request sizing nginx Single public ingress on the host. Routes /docs/ to static build; /api/ to NestJS; /ws/ to WebSocket gateway; /auth/ to Keycloak; /bi/ to Metabase.
NestJS API All business logic: auth, tickets, org/RBAC, files, notifications, comms, integrations, analytics aggregates, feature flags NestJS (TypeScript) + REST Modular monolith. 12-factor. Uses Prisma (recommended — see §5) over the MariaDB driver.
MariaDB System of record: users, orgs, tickets, MoMs, audit, RBAC, KB, content, configuration MariaDB 10.11.14 NOT PostgreSQL. Verified installed and running on the server. utf8mb4 + utf8mb4_unicode_520_ci for Urdu/Sindhi correctness.
Redis Cache, session store, BullMQ queues, WebSocket pub-sub adapter, rate-limit counters, feature-flag cache Redis 7 Persistence enabled (AOF) for durable queues.
Meilisearch Multilingual full-text search over tickets, KB articles, officials, documents, and chat Meilisearch Chosen specifically because it handles Urdu and Sindhi tokenization well, where MariaDB full-text is weaker.
MinIO + ClamAV S3-compatible object storage for uploads and generated documents; AV scanning on every upload MinIO + ClamAV (clamd) Encryption at rest; versioned buckets; presigned time-limited URLs. ClamAV runs as a sidecar service invoked from a BullMQ worker.
Keycloak Identity provider: OIDC, 2FA (TOTP/SMS), step-up auth, SSO for government staff, company auth Keycloak (self-hosted) Realm-per-actor-class option. Federates government staff via OIDC.
FastAPI AI Service Pluggable AI/OCR engine abstraction, PII redaction, on-prem fallback, audit of every AI call Python + FastAPI Wraps AIClient, OCRClient, TranscriptionClient interfaces. Hosts adapters for Azure OpenAI / Google / AWS / self-hosted Llama+Qwen via Ollama+vLLM; Tesseract / Google Doc AI / Azure Doc Intelligence / AWS Textract.
WebSocket Gateway Live ticket updates, presence/typing, 3-tier internal comms, live dashboards Socket.IO or Centrifugo Scaled horizontally via the Redis pub-sub adapter. Authenticated against Keycloak tokens.
BullMQ Workers Offload long-running work: OCR, AI calls, email/SMS/WhatsApp fan-out, PDF/Excel exports, scheduled digests, escalation timers Node + BullMQ on Redis Separate worker pools per job class (so OCR cannot starve notifications).
Metabase Ad-hoc analytics and embedded dashboards for internal roles Metabase (self-hosted) Connects read-only to a replica/schema of MariaDB. Embedded via signed URLs for selected dashboards.
Mailjet Transactional and inbound email (notifications + replies-to-ticket) Mailjet (SMTP + API + inbound parse) Multilingual templates rendered server-side; inbound webhook parses replies into ticket messages.
SMS Gateway OTP, ticket notifications, escalations Jazz / Telenor bulk SMS Chosen for Pakistan domestic delivery; failover between two providers configurable.
WhatsApp Business API Two-way chat with companies, notifications, inbound-to-ticket WhatsApp Business Cloud / BSP Templated message approval handled out-of-band.
Video Providers Hybrid TRI/hearing virtual meetings Zoom / Google Meet / Teams Provider selected per meeting; join links minted and attached to the ticket.
Feature-Flag Service Central, audited runtime toggles per capability, department, and environment NestJS module over MariaDB + Redis cache Every capability is a flag (see §12).
Observability Stack Logs, metrics, traces, errors, uptime Loki (logs) · Grafana (metrics/dashboards) · OpenTelemetry (traces) · Sentry (errors) · status page Single Grafana front-end over Loki + OTel + MariaDB exporter.
CI/CD Lint, typecheck, test, build, migrate, deploy GitHub Actions or GitLab CI Environments: devstagingprod. Migrations run as a gated step.

4. Application Layers (NestJS)

The NestJS deployment is organized as a clean-architecture modular monolith: each module owns its domain models, DTOs, services, controllers, and persistence, and exposes only typed interfaces to other modules. Cross-cutting concerns (validation, auth, logging, tracing, feature flags) live in shared infrastructure that no domain module bypasses.

Module map

Module Responsibility Key collaborators
Auth Login, OIDC handshake with Keycloak, 2FA, step-up auth, session/JWT issuance and refresh, RBAC resolution Keycloak, Redis
Tickets Ticket lifecycle (New → Triaged → Assigned → InProgress → Resolved → Closed/Reopened/Appealed), SLA, escalation, sub-tasks, merge/split, link, watchers/CC, bulk ops, draft & save-later, resolution-proof gate Org, Files, Notifications, AI-bridge, Comms, Analytics, Meilisearch
Org/RBAC Nested departments (Dept → Section → Staff), DG/Secretary oversight, company reps (Primary/Admin/Filer/Viewer/Notify), granular overrides, account lifecycle automation Auth, Files, Analytics
Files Upload, AV scan orchestration, encryption, preview, versioning, presigned download links MinIO, ClamAV (via workers), Audit
Notifications Fan-out to email/SMS/WhatsApp/in-app, templating (multilingual), preference center, digests, two-way inbound parse Mailjet, SMS, WA, workers
AI-bridge Thin client to the FastAPI AI service; applies feature flags + data-sensitivity policy before calling FastAPI AI service, Feature-Flag service
Comms 3-tier internal comms: ticket-scoped threads, org-wide inbox, channel-based chat WebSocket gateway, Meilisearch
Analytics Aggregates, materialized views/cubes, public transparency dashboard, GIS/district heatmap MariaDB, Metabase, Redis (live push)
Integrations Adapter pattern for NADRA/SECP/FBR/SRB/PSEB/e-Office; inbound/outbound webhooks; consumer REST API All external systems, Audit
FeatureFlags Read/write toggles; cache invalidation; audit of changes MariaDB, Redis, Audit
Audit Append-only audit log of every state-changing action MariaDB
I18n Locale resolution, locale propagation, Gregorian+Hijri formatting, AI translation passthrough FastAPI AI service
Health/Observability /health, /ready, /metrics, OTel spans, structured logs OTel, Loki

Cross-cutting mechanics


5. Database

5.1 Choice: MariaDB 10.11 (not PostgreSQL)

The system of record is MariaDB 10.11.14, already installed and running on the production host. This is a deliberate, locked decision (see _context.md §3). PostgreSQL is not used anywhere in the stack. The rationale:

5.2 ORM recommendation: Prisma

Both Prisma and TypeORM are listed as acceptable in the locked stack. Prisma is recommended for the SITP because:

  1. First-class MariaDB/MySQL driver. Prisma's connector targets the MySQL/MariaDB wire protocol natively; no ORM-internal SQL translation surprises.
  2. Type safety end-to-end from a single schema.prisma source of truth, which accelerates a TypeScript-primary team.
  3. Migrations are explicit, reviewable, and CI-gated — important for a government system where schema change is auditable.
  4. Better developer ergonomics for the modular monolith (one generator, one client, schema divided by // <-- module --> comments and prismaSchemaFolder previews), which lowers onboarding friction across MAAHIR engineers.
  5. Raw escape hatch (prisma.$queryRaw) for the few cases that need MariaDB-specific SQL (e.g., RETURNING, materialized-view refresh).

Caveat: Prisma does not yet auto-detect MariaDB server version quirks for some JSON operations; for JSON columns we model them as String (validated JSON) and use MariaDB's JSON_* functions via $queryRaw when needed. The volume of such cases is low.

5.3 Schema strategy

5.4 Character set

5.5 Indexing

5.6 Read replicas (future)

Today SITP runs on a single MariaDB instance. The architecture keeps the option open to add read replicas for: Metabase analytics reads, reporting/exports, and read-heavy public-site queries. The Prisma client will be configured with a read/write split at that point; all writes (and reads inside write transactions) go to the primary, analytics/dashboard reads go to replicas. Replication lag is bounded by a configured threshold; laggy replicas are skipped automatically.

5.7 Backups


6. AI / OCR Architecture

6.1 Engine abstraction (pluggable)

The FastAPI AI service exposes three stable interfaces. Every consumer (NestJS module or BullMQ worker) programs to the interface, never to a vendor SDK.

# Illustrative interface contract (per engine family)
class AIClient(Protocol):
    async def complete(self, req: AIRequest) -> AIResponse: ...
    async def embed(self, req: EmbedRequest) -> EmbedResponse: ...

class OCRClient(Protocol):
    async def extract(self, req: OCRRequest) -> OCRResult: ...

class TranscriptionClient(Protocol):
    async def transcribe(self, req: AudioRequest) -> Transcript: ...

Each interface has multiple concrete adapters. The service selects an adapter per feature and per data-sensitivity class at call time, using (a) the feature-flag service, (b) the data classification of the input, and (c) an environment-level allow-list (so a "sovereign" environment can forbid cloud engines entirely).

Engine family Cloud options On-prem fallback
LLM Azure OpenAI · Google Vertex/Gemini · AWS Bedrock Self-hosted Llama 3 / Qwen via Ollama + vLLM
OCR Google Document AI · Azure Document Intelligence · AWS Textract Tesseract
Transcription (cloud STT per provider) Self-hosted Whisper

6.2 AI request flow

Each AI call follows the same shape: resolve policy → resolve the engine for this feature+sensitivity → redact PII → call → on failure, fall back → log + audit → respond. The written description follows the diagram.

flowchart TD R["Request from NestJS module/worker<br/>(feature + sensitivity class + locale)"] P["Policy & feature-flag check<br/>(allowed engines for this feature+sensitivity)"] SEL["Select engine<br/>(cloud or on-prem)"] RED["PII redaction<br/>(CNIC, phone, email, etc.)"] CALL["Call engine adapter"] FB{"OK?"} FALL["Fallback engine<br/>(on-prem or secondary cloud)"] LOG["Structured log + audit row<br/>(engine · latency · tokens/cost · redactions)"] RESP["Return result to caller"] R --> P P --> SEL SEL --> RED RED --> CALL CALL --> FB FB -- no --> FALL FALL --> LOG FB -- yes --> LOG LOG --> RESP

The flow ensures four invariants: (1) no engine is ever called that the policy disallows for the input's sensitivity class; (2) PII is redacted before the payload leaves the trusted boundary if a cloud engine is selected; (3) any single engine failure is recoverable via a fallback so the user-facing feature stays available; (4) every call — including its engine, latency, cost, and the count of redacted PII tokens — is captured in an immutable audit row for transparency and cost governance.

6.3 The 11 AI capabilities and engine guidance

# Capability Recommended engine class Notes
1 OCR (scanned uploads, MoM images) Cloud Doc AI/Doc Intelligence/Textract for quality; Tesseract on-prem for sensitive/CNIC-bearing docs Output used by MoM extraction and search indexing.
2 Summary Cloud LLM (Azure OpenAI / Gemini) Short context; safe to send after PII redaction.
3 Auto-routing & classification Cloud or on-prem LLM; rule layer first Hybrid: deterministic rules + LLM for ambiguous.
4 Urgency / sentiment Cloud LLM or on-prem Cheap, high-volume; consider on-prem for cost.
5 Draft replies (trilingual) Cloud LLM Must respect locale + tone; never auto-send.
6 Translation EN/UR/Sindhi Cloud LLM with glossary injection Glossary from _glossary.md injected as constraints.
7 Duplicate / similarity detection On-prem embeddings + Meilisearch similarity Embeddings never leave the boundary for confidential tickets.
8 Public chatbot Cloud LLM with retrieval over KB Strict scope: only public KB content; no PII accepted.
9 PII redaction On-prem rule + regex + on-prem NER Runs before any cloud call (see §6.2).
10 Trends / analytics Cloud LLM for narrative summaries on aggregates Input is aggregated/anonymized; raw tickets not sent.
11 MoM action-item extraction Cloud LLM after OCR + redaction Owner/due-date parsing; officer confirms before sub-task creation.

6.4 PII redaction and on-prem fallback

PII redaction runs first in every flow. For inputs whose data class forbids cloud processing (e.g., raw CNIC, NADRA payloads, confidential/VIP tickets), the selector routes the call to the on-prem path (Llama/Qwen + Tesseract/Whisper). The redaction layer outputs both the redacted payload sent downstream and a reversible mapping (kept only in memory for the duration of the call, never persisted) so the final response can be re-hydrated before being returned to the caller. This satisfies the data-residency-aware principle from §1.


7. Real-time Layer

The WebSocket gateway powers four classes of live experience:

  1. Ticket updates — status, assignment, SLA, watcher notifications, file attachments.
  2. Presence and typing — for ticket-scoped threads and internal comms.
  3. 3-tier internal comms — (1) ticket-scoped private threads, (2) org-wide inbox DMs/groups, (3) full Slack-style channels (threads, pins, read receipts).
  4. Live dashboards — analytics push (e.g., open-ticket counts, SLA breach alerts) into role dashboards.

The gateway authenticates connections against Keycloak tokens (verified on connect and on periodic refresh), authorizes each subscription against RBAC and ABAC, and never trusts a client-asserted scope. Scaling: when more than one gateway instance is needed, all instances share a Redis pub-sub adapter so an event produced by any NestJS module or any BullMQ worker on any host reaches the correct connected session no matter which instance it lives on. Backpressure is handled per-connection (drop typing indicators before dropping ticket updates) and the gateway emits OTel spans for connect/disconnect/message so that the Loki/Grafana plane can correlate latency and drops.


8. Search (Meilisearch)

MariaDB full-text is weak for Urdu and Sindhi scripts; Meilisearch owns multilingual search across the portal.


9. Storage & Files


10. Identity & Auth


11. Feature Flag Service

Every capability in the system is toggleable by the Super Admin, per department and per environment.


12. Notifications Pipeline

The pipeline fans a single domain event (e.g., "ticket updated") out to multiple channels per the recipient's preferences.

flowchart LR EVT["Domain event<br/>(ticket/MoM/etc.)"] DISP["Notifications dispatcher<br/>(resolve channels + locale + prefs)"] TPL["Render template<br/>(multilingual · Gregorian+Hijri)"] Q["BullMQ queue<br/>(per channel)"] OUT["Outbound adapters"] MJ["Mailjet (email)"] SMS["SMS"] WA["WhatsApp"] INAPP["In-app (WS push)"] INB["Inbound:<br/>email reply / WA reply"] PARSE["Parse → resolve ticket by ref"] APPEND["Append as ticket message + notify watchers"] EVT --> DISP DISP --> TPL TPL --> Q Q --> OUT OUT --> MJ OUT --> SMS OUT --> WA OUT --> INAPP INB --> PARSE PARSE --> APPEND

A domain event reaches the dispatcher, which resolves the per-recipient channels (email/SMS/WhatsApp/in-app), locale, and preference-center settings (digest vs immediate, quiet hours, per-channel opt-ins). Templates are rendered server-side in the recipient's locale with both Gregorian and Hijri dates. Each message is enqueued to a per-channel BullMQ queue so that a WhatsApp outage cannot block email. Outbound adapters call Mailjet, the SMS gateway, the WhatsApp Business API, and the in-app WebSocket push.

Two-way inbound is a first-class path: inbound email replies (via Mailjet's inbound parse) and inbound WhatsApp replies (via the WhatsApp webhook) are parsed, the ticket is resolved from the reference in the subject/recipient, and the message is appended to the ticket thread, triggering normal watcher notifications. Rate limiting is applied per channel, per recipient, and per tenant to avoid provider throttling and to respect user quiet hours. Digests and preference center let users batch or mute non-urgent notifications.


13. Integrations Layer

The integrations module exposes every external government and third-party system behind an adapter. The rest of the system never imports a vendor SDK directly.

Adapter Direction Notes
NADRA (CNIC verification) Outbound Sovereign data; on-prem or approved-channel only.
SECP (company lookup) Outbound Used during file-first registration.
FBR / NTN Outbound Tax-number verification.
SRB Outbound Sindh tax registration.
PSEB Outbound IT-industry membership.
NITB e-Office Outbound Ticket/MoM → official file movement.
OIDC SSO Bidirectional Government staff federation.
Inbound webhooks Inbound WhatsApp, Mailjet inbound parse, provider callbacks.
Outbound webhooks + REST API Outbound Partner/consumer integrations; documented OpenAPI.

Cross-cutting concerns:


14. Analytics & BI


15. Multilingual / i18n / RTL


16. Security Architecture


17. Deployment & Hosting

Provider: the SITP is hosted on Server4Sale ("Powered by Server4Sale"). The detailed hosting topology is TBD, but the provider is fixed. The verified server environment on the host is:

Item Value
OS Ubuntu 24.04.4 LTS
Node v22.23.1
npm 10.9.8
Database MariaDB 10.11.14 (running)
Web server nginx (running)
Container runtime Docker 29.6.1
Git 2.43.0
Free disk 3.4 TB
Free RAM 243 GB

Static docs site is served by nginx directly at https://sindhitportal.maahir.io/docs/ from the Docusaurus static build output.

flowchart TB INET["Internet<br/>(sindhitportal.maahir.io)"] NX["nginx reverse proxy<br/>TLS · WAF · /docs/ static · /api/ · /ws/ · /auth/ · /bi/"] subgraph Static["Static plane"] DOC["Docusaurus build<br/>(/docs/)"] end subgraph Apps["App containers"] PORTAL["Next.js portal (Node 22)"] API["NestJS API (Node 22)"] WS["WebSocket gateway"] AI["FastAPI AI service (Python)"] WQ["BullMQ workers (Node 22)"] MB["Metabase"] KC["Keycloak"] end subgraph State["Stateful services"] DB[("MariaDB 10.11")] RD[("Redis")] MS[("Meilisearch")] MN[("MinIO + ClamAV")] end INET --> NX NX --> DOC NX --> PORTAL NX --> API NX --> WS NX --> AI NX --> KC NX --> MB API --> DB API --> RD API --> MS API --> MN API --> WQ WS --> RD AI --> MN AI --> RD WQ --> MN WQ --> MS MB --> DB

The reverse proxy is the single ingress. The static plane is the Docusaurus build served straight from disk. The app plane runs as Docker containers — the portal, API, WebSocket gateway, AI service, BullMQ worker pools, Metabase, and Keycloak — each with its own container and resource limits. The stateful plane — MariaDB 10.11, Redis, Meilisearch, MinIO + ClamAV — runs on the host (MariaDB is already installed at the OS level and shared with the containers over the local network).

Orchestration: Docker Compose today, with the option to graduate to Kubernetes when the deployment footprint outgrows a single host. The Compose file is structured so that each service definition maps cleanly to a future Kubernetes manifest (one service per Deploy, clear healthchecks, explicit resource requests/limits, secrets as env-from).


18. Observability


19. CI/CD


20. Scalability & Performance


21. Disaster Recovery (baseline)

Baseline DR is a locked non-functional requirement; detailed runbooks are deferred. The baseline posture:

Detailed runbooks (per-service recovery, contact tree, comms templates) are produced as a separate operational document.


22. Open Questions / TBD

# Item Status
1 Detailed hosting topology on Server4Sale (single host vs multi-host, network zoning, backup host). TBD
2 Exact AI engine selection per feature (cloud vs on-prem) — defaults proposed in §6.3, to be confirmed with data-classification policy. TBD
3 Kubernetes vs Docker Compose graduation trigger (scale, multi-host, HA requirements). TBD
4 Read-replica introduction timing and routing policy. TBD
5 Choice of secrets vault (Vault vs Server4Sale-managed) and key rotation cadence. TBD
6 Final RPO/RTO numbers (§21) once the DR runbook is authored. TBD
7 Meilisearch HA strategy (replica set) when load justifies. TBD
8 WebSocket gateway choice (Socket.IO vs Centrifugo) — both viable; decision to be made against mobile/PWA client needs. TBD

End of document.