Przejdź do treści

How it works

Secruna is a multi-tenant SaaS. Customer organisations sign in, connect the systems where their AI lives, and the platform discovers, classifies, and produces evidence packs against the regulatory frameworks they subscribe to.

Architecture at a glance

flowchart LR
    Browser[Customer browser] --> CF[CloudFront / Azure Front Door]
    CF --> FE[Frontend - Next.js]
    CF --> MK[Marketing - Next.js]
    FE --> API[cp-api - FastAPI]
    API --> PG[(Postgres + RLS)]
    API --> ARQ[(Redis / arq queue)]
    ARQ --> DW[Discovery worker]
    DW --> CN[Connectors: AWS / Azure / GCP / GitHub / surveying tools]
    DW --> PG
    API --> WH[Webhooks fan-out]
    WH --> SIEM[SIEM / Slack / Teams]

There are four runtime services:

  • frontend — the customer dashboard (Next.js, App Router).
  • marketing — the public site at secruna.com (Next.js, mostly static).
  • cp-api — the control-plane API (FastAPI, Pydantic v2). Owns the database, the rule loader, and the webhook fan-out.
  • discovery-worker — a Container Apps Job that drains the arq queue every two minutes, talks to connectors, and writes artifacts back to Postgres.

Postgres is the system of record. Redis (with arq) is the job queue. There are no message buses, no event streams, no microservices — deliberately. The whole platform is small enough that a single engineer can hold it in their head.

Multi-tenant isolation

Tenants are isolated by row-level security. Every customer table has a tenant_id column and an RLS policy that compares it to a session variable app.current_tenant_path of type ltree. The variable is set at the start of every request from the authenticated session token; the session token is itself bound to a tenant at issue time.

If a developer forgets to set the session variable, the policy returns zero rows — failing closed rather than open. There is no cross-tenant aggregation surface anywhere in the API.

Multi-framework loader (Plan 96)

The rule book is split per framework:

rule_book/
  v1/
    eu-ai-act/    19 rules
    rics/         5 rules
    uk-defence-ai-playbook/   6 rules
    ds-05-138/    in flight

At process start the loader walks every directory, parses the YAML, validates against the rule schema, and indexes by framework slug. A tenant only ever sees rules from frameworks listed in its subscription record (next section).

Per-framework subscriptions (Plan 103)

Each tenant has a framework_subscriptions row that lists the frameworks they have bought. The dashboard, the rule evaluator, and the navigation all read from this. A surveying firm sees the RICS pack; a defence supplier sees the AI Playbook and 05-138 packs; an EU-headquartered fintech might subscribe to the EU AI Act only.

The picker is also surfaced in onboarding, so the first thing a new tenant does is choose what they're regulated under.

Audit-everything

Every mutation in the API is wrapped by an audit middleware that writes a row to audit_log with:

  • who — the authenticated user id and tenant
  • what — the entity type, id, and a JSON diff of the change
  • when — UTC timestamp
  • provenance — IP, user agent, request id, and a hash chain back to the previous row

The audit log is queryable by any org admin through the dashboard, exportable as CSV, and (separately) streamed via webhook to the customer's SIEM if they configure one. There is no API surface that mutates state without writing audit rows.