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 PLANEThe 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 generallyt:<tid>:entity:<id>/t:<tid>:aoi:<box>; thesession:<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)
- Ingress. Client calls
https://<tenant>.insigz.app/api/...with a JWT. Cloud Run routes to that tenant's service (custom domain or subdomain mapping). - Tenant context. Middleware resolves the tenant from the verified JWT claim (
tid) and host, then opens a short transaction and sets Postgres session variables withSET LOCAL(canonical — see82):SET LOCAL app.tenant_id = …; SET LOCAL app.user_id = …; SET LOCAL app.role = …; SET LOCAL app.clearance = …; SET LOCAL app.cell = …;.SET LOCALscopes to the transaction so pooled connections never leak context. These drive row-level security. - 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 (see41). - 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.
- 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 (seeai/31,32). - Response + audit. Result returns; any mutation writes an append-only audit row. Mutations that change world-state also
NOTIFYthe 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
| Service | Plane | Responsibility |
|---|---|---|
api | data | tenant REST API; canonical CRUD, cases/reports, chat/agents, RAG |
ingestors | data | per-source fetch/normalize/sign/write jobs (Cloud Run Jobs or Pub/Sub consumers) |
realtime | data | LISTEN → WebSocket fan-out (can live in api or split out) |
control-plane | control | tenant lifecycle, provisioning orchestration, billing, fleet telemetry, audit |
insigzctl | both | CLI 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
realtimeandingestorsrun insideapior 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
tidis minted per IdP.
Phase 0 — Discover & Reuse Before Building (PREREQUISITE)
Study the owner's existing estate, harvest what works, reuse deliberately, and re-validate the decisions before writing any code.
Data Stores (the polyglot decision)
What persists where and why; the build starts single-store Postgres with pgvector, with polyglot Mongo as the documented migration target.