insigz docs
Engineering

System Architecture

One-page mental model of how the whole platform fits together, the request lifecycle, and the service boundaries.

One-page mental model of how the whole platform fits together, the request lifecycle, and the service boundaries. Read this before the deeper files.

The two planes

                          ┌─────────────────────────────────────────────┐
                          │            CONTROL PLANE (owner)             │
                          │   control-plane service  +  insigz Control   │
                          │   provisioning orchestrator · billing ·      │
                          │   fleet observability · audit · break-glass  │
                          └───────────────┬─────────────────────────────┘
                    drives Terraform +    │  reads AGGREGATE metrics only
                    insigzctl (no tenant  │  (never raw tenant data,
                    DB credentials)       │   except logged break-glass)
        ┌─────────────────────────────────┼─────────────────────────────────┐
        ▼                                 ▼                                 ▼
┌──────────────┐                 ┌──────────────┐                 ┌──────────────┐
│  TENANT A    │                 │  TENANT B    │                 │  TENANT C    │   … (silo per customer)
│ Cloud Run    │                 │ Cloud Run    │                 │ Cloud Run    │
│  api + web   │                 │  api + web   │                 │  api + web   │
│ Cloud SQL PG │                 │ Cloud SQL PG │                 │ Cloud SQL PG │
│ Mongo Atlas  │                 │ Mongo Atlas  │                 │ Mongo Atlas  │
│ GCS bucket   │                 │ GCS bucket   │                 │ GCS bucket   │
│ Secret/KMS   │                 │ Secret/KMS   │                 │ Secret/KMS   │
└──────────────┘                 └──────────────┘                 └──────────────┘
   DATA PLANE                       DATA PLANE                       DATA PLANE

The control plane spans all tenants; the data planes are isolated from each other. The control plane holds no tenant database credentials — it provisions infra and reads telemetry, and the only path into tenant data is an explicit, logged, time-boxed break-glass (product doc §17).

Tenant service topology

Each tenant runs one logical api service (Cloud Run) fronting four stores:

        client (web / control-web)
              │  HTTPS, JWT (tenant-scoped)

    ┌───────────────────────┐
    │   api  (Cloud Run)     │   stateless; tenant context from JWT + host
    │  ┌──────────────────┐  │
    │  │ tenant-context mw │  │  resolves tenant, sets PG session GUCs, picks Mongo DB
    │  ├──────────────────┤  │
    │  │ authz (RBAC/ABAC) │  │  policy decision before any handler
    │  ├──────────────────┤  │
    │  │ domain services   │  │  canonical CRUD · cases · reports · agents · RAG
    │  └──────────────────┘  │
    └───┬────────┬───────┬───┘
        │        │       │
   ┌────▼──┐  ┌──▼────┐ ┌▼──────────┐  ┌───────────┐
   │  PG    │  │ Mongo │ │   GCS     │  │  Redis    │
   │canonical│  │ docs  │ │  files    │  │ cache/q   │
   │ + RLS  │  │ +RAG  │ │           │  │           │
   └────────┘  └───────┘ └───────────┘  └───────────┘

        │ LISTEN/NOTIFY → WebSocket fan-out (realtime; see backend/61)

Channel taxonomy (aligns 61): realtime channels are generally t:<tid>:entity:<id> / t:<tid>:aoi:<box>; the session:<id> channel is wargame-specific. A notify is a cache-invalidation hint, never data.

Resolution timing (aligns 21): in the write lifecycle below, deterministic resolution runs inline in the ingest transaction; probabilistic resolution + event derivation run async via the outbox worker.

Request lifecycle (a read, end to end)

  1. Ingress. Client calls https://<tenant>.insigz.app/api/... with a JWT. Cloud Run routes to that tenant's service (custom domain or subdomain mapping).
  2. Tenant context. Middleware resolves the tenant from the verified JWT claim (tid) and host, then opens a short transaction and sets Postgres session variables with SET LOCAL (canonical — see 82): SET LOCAL app.tenant_id = …; SET LOCAL app.user_id = …; SET LOCAL app.role = …; SET LOCAL app.clearance = …; SET LOCAL app.cell = …;. SET LOCAL scopes to the transaction so pooled connections never leak context. These drive row-level security.
  3. Authorization. The policy engine (packages/auth) makes an allow/deny decision for (subject, action, resource) before the handler runs — the subject may be a tenant user or a break-glass operator-in-tenant (41). UI gates mirror this but are never the source of truth (see 41).
  4. Domain logic. The handler queries Postgres (canonical) and/or Mongo (documents/RAG). RLS guarantees the query can only see this tenant's rows even if a filter is forgotten.
  5. AI path (if a chat/agent call). The handler calls the RAG context assembler (packages/rag) → retrieves grounded observations + documents → builds the Claude prompt with tool definitions → streams the response → validates citations before returning (see ai/31, 32).
  6. Response + audit. Result returns; any mutation writes an append-only audit row. Mutations that change world-state also NOTIFY the realtime channel.

Write lifecycle (ingestion, the spine)

source feed ──► ingestor (Cloud Run job / Pub-Sub consumer)
                  │  fetch → normalize → geolocate → SIGN → write

        Postgres: insert Observation (canonical, signed, bitemporal)

                  ├─► resolution: attach to / create Entity (deterministic + probabilistic)
                  ├─► event derivation: fuse Observations → Event
                  ├─► Mongo: upsert raw payload doc + (if textual) RAG chunks + embeddings
                  └─► NOTIFY session_<id> (realtime cache-invalidation hint)

Ingestion is the only writer of Observations; everything downstream (events, cases, the analyst's grounding) inherits from it. See data/20.

Service inventory

ServicePlaneResponsibility
apidatatenant REST API; canonical CRUD, cases/reports, chat/agents, RAG
ingestorsdataper-source fetch/normalize/sign/write jobs (Cloud Run Jobs or Pub/Sub consumers)
realtimedataLISTEN → WebSocket fan-out (can live in api or split out)
control-planecontroltenant lifecycle, provisioning orchestration, billing, fleet telemetry, audit
insigzctlbothCLI for migrate/seed/register/suspend/decommission (invoked by control plane + ops)

Why single-tenant silos (and the cost)

Strong isolation (own DB, own secrets, own RLS) is the product's core promise for regulated buyers; it gives the smallest blast radius and the easiest compliance story. The cost is operational: provisioning is real infrastructure and ops scales with tenant count — which is exactly why the control plane + automated provisioning (infra/51) is mandatory, not optional.

Gaps to close

  • Decide whether realtime and ingestors run inside api or as separate Cloud Run services/jobs.
  • Confirm one Mongo Atlas project per tenant vs. one project with per-tenant databases (cost vs. isolation; see architecture/11).
  • Define the tenant routing mechanism: subdomain vs. custom domain vs. path; and how the JWT tid is minted per IdP.