Hilo B2B Dashboard — System Architecture
Pragmatic single-monolith architecture for the B2B dashboard. Next.js for app + API. Postgres for data. Azure Blob WORM for tamper-evident audit logs. Compliance baked into the request path, not bolted on. No microservices, no MySQL, no Python — every piece exists for a reason.
Interaction: drag to pan · scroll to zoom · double-click to reset · fullscreen button per diagram.
0. Components — what each thing is, why we have it
Every component in the build, before any diagrams. Struck-through cards = explicitly excluded with reasoning.
Azure App Service · HTTPS Ingress
Azure-managed ingress. TLS termination, Azure-issued cert (auto-renewed), HTTP→HTTPS redirect, custom domain (e.g. partners.hilo.com), health probes. The single public entry point for the whole system.
Next.js 14+ Monolith
The whole application. Frontend pages, API routes, middleware stack, agent endpoints — all in one TypeScript codebase. Renders the dashboard SPA, handles auth callbacks, runs the RBAC + audit middleware on every API call, returns JSON. Stateless — scales horizontally on App Service.
Azure Database for PostgreSQL — Flexible Server
Single source of truth. Postgres 16. One schema for B2B. Holds every business entity: orgs, users, patients, device_assignments, patient_consent, plus the in-DB audit_log partner. Zone-Redundant HA. 35-day point-in-time restore. TDE (transparent data encryption) on by default.
Azure Blob Storage · Immutable (WORM)
The other half of the audit trail. Every audited event written to Postgres is also streamed to Blob Storage with an Immutability Policy applied — Write Once, Read Many. Once a blob is written, it cannot be modified or deleted by anyone — not Hilo ops, not the DBA, not the cloud admin — for the configured retention period (6 years). Regulators get read-only SAS tokens to verify logs independently.
audit_log is for fast app queries. The WORM blob is the legal record. Even if Hilo's app database is fully compromised or maliciously edited, the Blob WORM copy survives unchanged — cryptographically anchored by Azure's storage immutability service. This is the architectural difference between "logs we wrote" and "logs anyone can verify."Microsoft Entra External ID (Azure AD B2C)
Managed OIDC identity provider. Handles login, MFA (TOTP / SMS), password reset, account lockout, session management, social login if needed. Issues JWTs containing sub, org_id, roles[], and permissions[] claims. Next.js middleware verifies the JWT signature on every request — no DB lookup needed.
Azure VNet + Private Endpoints
Postgres and Blob Storage both have private IPs only — no public internet reachability. The Next.js App Service connects via VNet integration. NSG rules deny all inbound to the data subnet except from the app subnet. Database credentials never traverse the public internet.
Azure Key Vault
Zero secrets in code, env vars, or container images. Stores: Postgres connection string, Entra External ID client secret, Blob Storage SAS keys, SendGrid API key, signing keys for JWT verification, encryption keys for at-rest field-level crypto. Next.js accesses via Managed Identity — no passwords to rotate, nothing to commit.
.env files are how breaches happen. Managed Identity removes the bootstrap-credential problem entirely — the App Service authenticates to Key Vault via its Azure identity. Every secret access is logged.Email · SendGrid or Postmark
Sends patient consent emails, partner invitation emails, password reset (or delegated to Entra), notifications. Bounce + delivery tracking required for GDPR-defensible consent — we need to be able to prove an email was delivered when the patient clicked accept.
Azure Application Insights
Auto-instruments Next.js requests, DB queries, external API calls. Distributed tracing — one user click gets one trace ID across middleware, Postgres, Blob, and email send. Metrics dashboards: p99 latency, error rate, DB connection pool utilization. Alerts to email + Teams.
Sentry · Error tracking
Captures every uncaught exception with stack trace + user context + last 5 user actions. When something breaks in production, Sentry surfaces the root cause in seconds — not hours of log searching.
MySQL
The existing B2C backend uses MySQL for accounts. The new B2B build does not — we use Postgres for everything.
MongoDB
The existing B2C backend uses MongoDB for measurement data. We don't.
Python backend service
An earlier proposal had Flask alongside Next.js for "data processing."
Five-service split (auth · resolver · physiological · etc.)
The existing B2C backend uses 5 Spring Boot microservices. We don't replicate that.
1. System Architecture Overview — everything at once
The full map. Browser top-left, identity provider top-center, Azure region holds the whole stack. Two write paths to data: Next.js writes to Postgres, audit middleware additionally writes immutable copies to Blob WORM. No path bypasses the middleware stack.
Reading the diagram: The doctor's browser is the only public ingress. App Service terminates TLS. Next.js middleware runs in this order on every request: authenticate → authorize → scope → audit. Postgres holds business data and a fast-query audit log. Azure Blob WORM holds the immutable, regulator-verifiable copy of every audit event. Identity is fully delegated to Entra External ID — Hilo never stores a password.
2. A Request's Journey — what happens when a doctor clicks "view patient"
An HCP clicks on a patient in the dashboard. Walk through every middleware layer the request passes through, what each one verifies, and what gets written where.
Why the middleware order matters: authenticate first (no identity = stop). Then authorize (does this role have read:patients permission). Then scope (filter by org_id and assignment list — even an authorized user can only see their own slice). Then audit (we log before returning data, so a server crash mid-response still leaves a trace). The query never runs without all four passing.
3. RBAC + Tenant Scoping — how a clinic only sees its own patients
Every B2B customer is a tenant. Inside each tenant, users have roles. The architecture enforces both: tenant isolation (an HCP at Clinic A cannot see Clinic B's patients) and role-based scope (an HCP user only sees their assigned patients; an HCP admin sees all org patients).
Why enforce at the database layer too: the Next.js middleware filters every query by org_id and assignment list. But we add Postgres Row-Level Security as a second layer. If a future bug in the app forgets to scope a query, RLS rejects it at the DB. Defense in depth — same patient data, two independent enforcement layers, both have to fail for cross-tenant leakage to occur.
4. Tamper-Evident Audit Logging — why regulators can trust this
Every audited action is dual-written. The Postgres copy is for fast app-side queries (e.g. "show me everything user X did this week"). The Azure Blob WORM copy is the legal record — immutable, retention-locked, separately accessible to auditors.
The trust model in plain English: Hilo can read the WORM blob. Hilo cannot modify or delete it. Azure's Storage Immutability service enforces this at the platform layer — not the app, not Hilo's IAM policy. Regulators get a read-only SAS token that lets them list and download blobs directly from Azure, bypassing Hilo entirely. If the Postgres audit log and the WORM archive ever disagree, the WORM archive is the authoritative answer — and only Hilo's app can have tampered with the Postgres copy.
| Property | Postgres audit_log | Blob WORM archive |
|---|---|---|
| Purpose | Fast app queries · activity timeline UI | Legal record · regulator verification |
| Modifiable by Hilo? | Theoretically yes (DB admin) | No — Azure platform-enforced |
| Retention | App-managed, can be backfilled | 6 years, retention lock applied at write time |
| Auditor access | Through Hilo's app, requires trust | Direct read-only SAS, no Hilo middleman |
| Tamper detection | None — a clever DBA could rewrite history | Blob hashes verify integrity; immutability rejects writes |
5. GDPR + HIPAA Compliance Flows — consent, residency, right to erasure
Three compliance flows are wired into the system architecture, not added later: explicit consent (Article 9 special category data), data residency (EU patients stay in EU regions), and right to erasure (Article 17). Each one is enforced in middleware, so no API path can skip it.
Reading the flow: a patient grants consent → consent middleware records it (Postgres + WORM). When an HCP later queries that patient's data, the scope middleware first checks the live consent record. If consent is revoked, the query returns nothing — even though the row still exists in Postgres. Right to erasure is a soft delete with a scheduled hard-delete job; audit logs are preserved (regulators need them) but every consumer query now treats the patient as gone.
6. Device Data Flows — HCP-managed vs patient-managed
The two device-ownership models look like the same dashboard view but are completely different data flows. The HCP-managed flow is buildable for the May 28th milestone. The patient-managed flow needs a B2C → B2B data bridge that depends on the mobile app team.
Time of measurement vs time of sync: HCP-managed bands buffer days of data and only sync when the patient returns to the clinic. The dashboard shows both timestamps — "last reading 5 days ago, last synced 1 hour ago" — so a doctor never reads stale-but-unsynced data as a clinical event.
7. Deployment + Operations — from git push to production
One Next.js container. One Postgres database. One Blob Storage account. CI/CD via GitHub Actions. Staging slot for safe pre-prod verification. Zero-downtime deploys via App Service slot swap.
Why slot swap: the new build deploys into the staging slot, runs smoke tests against a copy of prod traffic, and only swaps in if healthy. If the new version is broken, swap back instantly — no downtime, no panic. Database migrations are always backward-compatible (add columns, never drop) so rollbacks don't break the schema.
8. Open Architecture Decisions
Decisions that need Hilo input before code is written. Not for the dev team to make alone.
| # | Decision | Owner | Blocks |
|---|---|---|---|
| 1 | B2C → B2B data access mechanism (direct DB read · read replica · internal API) | Hilo tech (Matti) | Patient-managed flow |
| 2 | Identity provider · Entra External ID vs Auth0 | Hilo tech leadership | Auth integration |
| 3 | HIPAA applicability (US customers? EU only?) | Hilo legal | Compliance scope |
| 4 | Patient consent flow wording & legal basis | Hilo legal | Permission management UX |
| 5 | Azure region(s) — single EU region or EU + US for residency | Hilo tech | Infra provisioning |
| 6 | BP categorization thresholds (ESH 2023 / AHA 2017 / Hilo-defined) | Hilo product | Patient view rendering |
| 7 | Cross-team capacity for patient-managed flow (mobile app team) | Hilo engineering | Whether patient-managed is in MVP |