Skip to content

Architecture — dot-cOS

System Architecture

graph TB
    subgraph Clients
        FE[dot-cos-frontend\ncos.dotevolve.net]
        AD[dot-cos-admin-dashboard\ncos-admin.dotevolve.net]
    end

    subgraph Gateway
        GW[dot-cos-api-gateway\ncos-api.dotevolve.net\nNode.js / Express]
    end

    subgraph Services
        WS[dot-cos-workflow-service\ncos-workflow.dotevolve.net\nNode.js / Prisma]
    end

    subgraph Data
        PG[(Supabase PostgreSQL\nWorkflow Data)]
        QST[QStash\nAsync messaging]
        REDIS[(Redis\nRate Limiting)]
    end

    subgraph Platform
        PA[dot-portal-api\nPortal API]
        SB[Supabase Auth]
        CR[(Central Registry\nSupabase PostgreSQL)]
    end

    FE -->|Bearer JWT| GW
    AD -->|Bearer JWT| GW
    GW -->|supabase.auth.getUser| SB
    GW -->|x-tenant-id header| WS
    WS -->|tenantId scoped| PG
    WS -->|events| QST
    WS -->|tenant status| CR
    AD -->|SSO| GW

API Gateway — Tenant Resolution

The gateway extracts activeTenantId from JWT claims — no HTTP call to the workflow service:

flowchart TD
    A[Request + Bearer JWT] --> B[requireAuth middleware]
    B --> C[supabase.auth.getUser token]
    C --> D{Valid?}
    D -->|No| E[403 Forbidden]
    D -->|Yes| F[Attach user.app_metadata to req.user]
    F --> G[resolveTenant middleware]
    G --> H{activeTenantId in app_metadata?}
    H -->|No, protected route| I[401 Unauthorized]
    H -->|No, public route| J[next - no tenantId]
    H -->|Yes| K[req.tenantId = activeTenantId]
    K --> L[Set x-tenant-id header]
    L --> M[Proxy to downstream service]

Workflow Service — Tenant Resolution

flowchart TD
    A[Request + x-tenant-id header] --> B[resolveTenant middleware]
    B --> C{x-tenant-id present?}
    C -->|No| D[401 Unauthorized]
    C -->|Yes| E[Query Central Registry\nfor tenant status]
    E --> F{Status?}
    F -->|suspended| G[403 Forbidden]
    F -->|pending_payment| H[402 Payment Required]
    F -->|not found| I[401 Unauthorized]
    F -->|active| J[req.tenantId = tenantId]
    J --> K[All Prisma queries\nwhere: tenantId]

Data Flow — Workflow Creation

sequenceDiagram
    participant FE as Frontend
    participant GW as API Gateway
    participant WS as Workflow Service
    participant PG as PostgreSQL
    participant QST as QStash

    FE->>GW: POST /api/v1/workflows (Bearer JWT)
    GW->>GW: Validate JWT, extract activeTenantId
    GW->>WS: POST /api/v1/workflows (x-tenant-id: tenantId)
    WS->>WS: Validate tenant status
    WS->>PG: WorkflowInstance.create({ tenantId, templateId, entityId })
    PG-->>WS: Created instance
    WS->>QST: Publish WORKFLOW_CREATED event
    WS-->>GW: 201 Created
    GW-->>FE: 201 Created

Prisma Schema Changes (Central Portal Migration)

Change Before After
Tenant.subscriptionPlan String @default("FREE") Removed — read from Central Registry
User.password String // Hashed Removed — Supabase Auth
User.supabaseId Not present String? @unique
Tenant resolution HTTP call to /api/v1/me → DB lookup JWT claim extraction via x-tenant-id header
tenantCache In-memory email→tenantId Map Removed entirely

Infrastructure

dot-cOS services
  ├── Supabase PostgreSQL    (workflow data — Prisma ORM)
  ├── Supabase Auth          (JWT validation)
  ├── Central Registry       (tenant status checks)
  ├── QStash (Upstash)       (async HTTP messaging)
  ├── Redis                  (rate limiting)
  └── OpenAI GPT-4o          (IDP — document extraction)

Local Development

# Start all infrastructure
docker compose up -d   # Postgres, Redis (see service docs for QStash env)

# Run workflow service
cd dot-cos-workflow-service
npm run dev

# Run API gateway
cd dot-cos-api-gateway
npm run dev

See dot-cos/docker-compose.yml for the full local stack definition.