Skip to content
➜cat gealo/architecture.md
DEPLOYED ·gealo.app
cd ../gealo
TIER_01

Architecture

Tenancy, real-time, and entitlements. The spine the rest of the product sits on top of.

➜TENANCY_MODEL--scope=request

A tenant owns everything: members, projects, policies, billing, audit. A project (workspace) is where the work happens. Every request resolves a tenant from the host header before any business logic runs. The tenant rides the request context through guards, services, queues, and sockets.

The invariants below are the bones. Each one is enforced in code, not just documented. New endpoints inherit the tenant resolver and the membership guard by default. Opting out is an explicit, reviewable change.

➜STACK_TOPOLOGY--tiers=4 --live
data flowing

Tier_01 // edge

// where the tenant is resolved

Browser

Operations console + auth shells. Tenant resolved from host.

Tier_02 // application

// thin controllers · guards enforce tenancy

Nuxt 3 // SSR

Marketing, auth shells, console pages. Tenant context per request.

Socket.IO // realtime

Tenant + project rooms. Presence, typing, notifications.

NestJS // API

Controllers thin, services do the work. Guards enforce tenancy + entitlements.

Tier_03 // services

// business logic · 30+ modules

Auth + RBAC

JWT, refresh rotation, optional 2FA, tenant role guards.

Entitlements

Plans grant a baseline, policies tighten, add-ons expand. Resolved in one service.

Workspace services

Tasks, chat, meetings, HR, docs, integrations, AI, billing.

Background jobs

Tenant-aware queues. Retention, accrual, storage recalc.

Tier_04 // data plane

// every key, query, and prefix is tenant-scoped

PostgreSQL

Primary OLTP. Real migrations in prod. Tenant-scoped queries.

MongoDB

Chat messages and flex-schema documents.

Redis

Cache (tenant-prefixed keys), rate buckets, pub/sub fabric.

S3-compatible

File plane. Presigned URLs after permission checks. Tenant/project key paths.

➜ invariant: tenant_id on every read[no cross-tenant joins]
➜REAL_TIME_TOPOLOGY--fabric=socketio+redis

Real-time is a shared fabric, not a per-feature add-on. The same Socket.IO and Redis pub/sub pipe carries chat, presence, typing, task updates, meeting state, and notifications. Rooms are explicit. Subscriptions are scoped. The handshake validates tenancy before the connection upgrades.

  • [01]

    Tenant rooms

    Used for tenant-wide signals like presence summary, billing notifications, and announcement broadcasts.

  • [02]

    Project rooms

    Used for project-scoped real-time traffic. Task changes, chat messages, meeting state, document presence.

  • [03]

    Presence and typing

    Lightweight ephemeral state in Redis. Eviction is fast. Reconnect is graceful. No zombie indicators left over.

  • [04]

    Backpressure

    Per-tenant rate buckets for socket emits. Loud tenants do not starve quiet ones.

➜ENTITLEMENTS_FLOW--source=single

Plans grant. Policies tighten. Add-ons expand. The entitlements service is the only place that produces an effective cap. The frontend reads it. The guards enforce it. The numbers in the example below are illustrative.

➜ENTITLEMENTS_RESOLUTION--source=single

// plan grants. policy tightens. add-on expands. effective is what the API + UI both honor.

tap a stage to highlight; tap again to reset [invariant: no frontend hardcodes caps]
➜INVARIANTS--count=6
  1. 01

    Tenancy resolves before anything else

    Every controller resolves a tenant from the host header before any business logic runs. The tenant is attached to the request and to every downstream call. No tenant, no work.

  2. 02

    Tenant scope on every read

    Every query carries a tenant filter. The shape of the data model makes cross-tenant joins inconvenient to write. The audit becomes pattern review instead of entity-by-entity proof.

  3. 03

    Cache keys carry the tenant prefix

    No cache entry is shared across tenants. Invalidation events scope to the tenant. Noisy-neighbor cache thrash stays inside its own room.

  4. 04

    Socket rooms are scoped, never global

    Sockets join explicit tenant and project rooms after handshake validation. There is no global broadcast and no cross-tenant message path. Presence, typing, and notifications all use the same fabric.

  5. 05

    One service computes the effective cap

    Plans grant, policies tighten, add-ons expand. The entitlements service produces a single effective answer. Guards read it. Controllers read it. The frontend reads it from the API. No duplicated thresholds.

  6. 06

    Webhooks verify before they mutate

    Every inbound webhook verifies its signature before resolving a tenant. Tenant resolution comes before any write. An unknown installation gets no mutation and an audit log entry.