/

CrowdVision · source-available, not open source · © 2026 Nicolò Ghignatti

Architecture Overview

CrowdVision is a distributed, event-driven microservices system. The frontend is fully separated from the backend, and the backend services are isolated from one another, so the platform can scale parts independently, tolerate partial failure, and enforce strict data boundaries.

This page gives the whole-system picture — the components and the two deployment models. Inter-service communication and the real-time data path are covered in Communication & Data Flow; the cluster specifics in Deployment & Kubernetes; the Istio mesh migration in Service Mesh Architecture.


Component Landscape

Libraries

Shared code embedded directly into other services — not independently deployed, no Dockerfile of their own.

LibraryRoleStack
auth-contractsShared Kernel: the StandardClaims/Membership token shape and the role-weight ladder (roles.json). Consumed by auth-middleware, auth-policy, claims-gateway, tenancy.Go module
auth-middlewareTwo verification strategies: RequireAuthentication (full JWT verify against JWKS) and RequireMeshClaims (trusts the mesh-injected x-gateway-claims header). Consumed by claims-gateway and tenancy (the only Go HTTP services).Go module
auth-policyCedar authorization bundle (schema.cedarschema, policy.cedar, fixtures/conformance.json) embedded and evaluated locally by each language: cedar-go (Go, tenancy), cedar-policy crate via include_str! at compile time (Rust, digital-twin), cedarpy at runtime (Python, agent). One shared bundle, three independent embeddings — not a package import, a sibling directory each build copies in.Go module + Rust/Python bindings

Services

Overview diagram

Independently deployable, each with its own container image.

ComponentRoleStack
KeycloakIdentity provider for every tier — authentication, credentials, MFA, brokered/BYO-IdP login. See Identity & Tenancy Architecture.Ops (Keycloak 26)
claims-gatewayVerifies the IdP token, resolves tenancy, mints the internal token every service trustsGo
tenancyDomains & memberships — who belongs to which organization, with what roleGo / PostgreSQL
registryOrganization signup & lifecycle (control plane; never deployed in a cell)Go / PostgreSQL
provisionerReconciles pending organizations into running tenancy (pooled tier today). No external route — internal reconcile loop only.Go
digital-twinBuilding Management — the spatial modelRust / Axum / MongoDB
telemetryTelemetry Ingestion — readings & thresholdsRust / Axum / PostgreSQL + TimescaleDB
dashboardTelemetry Distribution — per-building filteringRust / Axum / MongoDB
notificationAlerting — push deliveryRust / Axum / MongoDB
socketReal-time transport to the browserRust / Axum / socketioxide
chatPersists multi-turn chat sessions and orchestrates calls to agentNode.js / MongoDB
agentAssistant — maintained separately; see its referencePython / PostgreSQL (pgvector)
clientThe single-page applicationVue 3 / Vite
GatewaySingle entry point, routes by URL prefixCaddy (compose) / Istio Gateway API (k8s)
Service MeshKubernetes only: Istio in ambient mode — ztunnel gives every pod L4 mTLS, edge RequestAuthentication verifies the JWT once, AuthorizationPolicy enforces coarse authZ. Docker-compose’s equivalent is Caddy’s forward_auth against claims-gateway’s /verify. See Service Mesh Architecture.Istio (ambient)
BrokerPublish/subscribe backbone for telemetry and alertsRedis
DatastoresOne database per stateful serviceMongoDB (chat, digital-twin, dashboard, notification — all Rust), PostgreSQL (telemetry — with TimescaleDB; agent, tenancy, registry, Keycloak)

The agent runs as part of the system but sits outside this guide’s microservice set; it appears here for completeness only.


Module View

Build-time dependency graph — what needs what to compile. Not the same as request-time calls between services, see Communication & Data Flow.

Module view diagram

Deployment Model: Docker Compose (Local Development)

All incoming external traffic enters the system through a single Caddy reverse-proxy gateway exposed on port 80. Caddy strips the service prefixes from the URLs and proxies the requests to the appropriate target containers on the internal Docker bridge network. For protected routes, Caddy’s forward_auth calls claims-gateway’s /verify and copies the resulting X-Gateway-Claims header onto the request — the docker-compose equivalent of Istio’s RequestAuthentication + outputPayloadToHeader in the Kubernetes model below.

Docker Compose deployment diagram

Network Segregation & Database Isolation

Each stateful microservice is attached to two networks:

This is the intended database-per-service boundary: no service is meant to reach another service’s database directly, only through an API call or an event.


Deployment Model: Kubernetes (Production / Staging)

In Kubernetes mode, Caddy is replaced by an Istio Gateway (Gateway API, gatewayClassName: istio) fronting an Istio ambient mesh: a per-node ztunnel DaemonSet gives every pod L4 mTLS and workload identity with no sidecar and no pod restarts, PeerAuthentication enforces STRICT mTLS across the crowdvision namespace, and a RequestAuthentication on the gateway verifies the claims-gateway-issued JWT exactly once — the validated payload is injected as a single x-gateway-claims header via outputPayloadToHeader, and every downstream service decodes that header instead of re-verifying the JWT itself. The full phased migration is documented in Service Mesh Architecture.

Kubernetes deployment diagram

A key difference in the Kubernetes model is how the Sensor Simulator integrates. Because the simulator is an external Docker container (not a cluster workload), it reaches the cluster through the load balancer’s public port, just like a browser. See the External Sensor Simulator page. The cluster internals — StatefulSets, Secrets, Gateway API routes — are detailed in Deployment & Kubernetes; the mesh internals (ztunnel, RequestAuthentication, AuthorizationPolicy, Cedar) in Service Mesh Architecture.

agent is deliberately excluded from the mesh’s AuthorizationPolicy-enforced JWT gate: it also serves a local-dev evaluation bypass token that never holds a real gateway JWT, so it keeps checking for the mesh claims header first and falls back to its own token verification in-process.


Routing Table

The routing table is identical across both deployment models. Only the component enforcing it differs (Caddy vs Istio Gateway API).

External Path PrefixInternal Target ServicePurpose
/gateway/claims-gatewayToken exchange, /me, logout, JWKS.
/tenancy/tenancyDomains, subdomains, memberships, invite codes.
/twin/digital-twinBuilding models, room CRUD, spatial data.
/notification/notificationProcessing sensor events, triggering Web Push alerts.
/telemetry/telemetryIngesting time-series sensor readings (people count, temperature). /telemetry/ingest is exempt from the auth gate — it is device/simulator-facing and carries no user token, authenticating instead with an HMAC X-Signature over the raw body that telemetry verifies itself.
/chat/chatPersisted multi-turn chat sessions, forwarding to agent.
/agent/agentTool-calling assistant for live building data and documentation search. Not gated by the mesh/gateway JWT check — see the exclusion note above.
/contracts/dashboardPer-building telemetry filtering preferences. Not gated by the auth check.
/socket.io/socketMaintaining persistent WebSocket connections to the frontend.
/clientServes the Vue 3 SPA. Catch-all, must be defined last.

Dev Mode

Developer Mode Only: when running just stack dev, Caddy also exposes database GUIs at /twin-db-gui/*, /telemetry-db-gui/*, /notification-db-gui/*, /dashboard-db-gui/*, and /chat-db-gui/*. These are not present in Kubernetes deployments for security reasons.