Skip to content

Architecture

A single-page summary of what the install actually wires up. For a deeper engineering treatment, see the internal docs under docs-internal/ in the source tree.

ARIS runs as an isolated customer instance in the customer's own environment. It is not a multi-tenant SaaS deployment; all local state belongs to that installed instance.

Components

flowchart LR
    O([Operator]) -->|HTTPS| W[web<br/>Next.js + NextAuth]
    H[Host collector<br/>aris-collector] -->|mTLS gRPC ingest| C
    H -->|HTTPS mTLS heartbeat| C
    W -->|OIDC| G[Google<br/>accounts.google.com]
    W -->|signIn lookup| D[(Postgres)]
    W -->|bridge JWT| C[core<br/>aris-server]
    C -->|reads| D
    C -->|writes ingest ledger| D
    C -->|directory sync| GA[Google<br/>admin.googleapis.com]
Component What it is Where state lives
web Next.js 15 app. Hosts the operator UI and the NextAuth Google OIDC flow. Stateless. NextAuth session is a signed cookie.
core Go binary (aris-server). Runs DB migrations, the directory sync scheduler, the v1 API, and optionally the collector ingest gRPC listener. All state in Postgres.
collector Go binary (aris-collector) installed on observed hosts. Captures local AI-tool signals, buffers them in an encrypted local queue, and forwards to core. Local encrypted queue and audit log under the collector state directory.
db Postgres 16. Holds identity tables plus collector ingest/projection tables. The aris-db Docker volume.

Authentication path

  1. Operator hits the web UI; the middleware redirects to /login.
  2. NextAuth bounces the browser to Google for OIDC. Google redirects back to /api/auth/callback/google with an ID token.
  3. Web's signIn callback queries Postgres directly: (deployment namespace, issuer="https://accounts.google.com", sub) → person.user_id. The current schema column is named tenant_id, but it is a per-instance namespace, not a SaaS tenant boundary. If the row is missing or person.status != 'active', sign-in is rejected with a tailored error code (UnknownIdentity, PersonInactive, EmailUnverified).
  4. On success, NextAuth issues a session cookie. The cookie carries userId so subsequent requests don't re-query the DB on every page load.
  5. When the UI calls a protected core endpoint, web mints a fresh bridge token — a 5-minute HS256 JWT with sub=user_id, aud=core, and email — and forwards it as the bearer.

Authorisation path

Scopes are not carried in the session or the bridge token. Core derives them on every request from the user's current Workspace group membership:

  • aris-admin@<domain> member → [read, audit, admin]
  • aris-auditor@<domain> member → [read, audit]
  • otherwise → [read]

Group membership is refreshed by the directory-sync scheduler on the ARIS_DIRECTORY_SYNC_INTERVAL cadence (default 1h). Removing a user from a group takes effect on the next sync run, not at the next request.

Directory sync

Core runs a three-pass reconciler against Google's Admin Directory API once per ARIS_DIRECTORY_SYNC_INTERVAL:

  1. Upsert every Workspace user as (person, authentication_identity) rows.
  2. Reconcile employment state (manager, group memberships, org unit) and person status (active / deactivated).
  3. Resolve any unresolved staged identities by email match.

The hard guarantee: a transport failure during fetch aborts the run before any write. A transient Google outage cannot mark every user as departed. This is the load-bearing security property the rest of the platform relies on.

Collector ingest path

Collectors connect outbound to core over gRPC. The current production ingest path uses mutual TLS:

  1. Core presents the ingest server certificate configured by ARIS_INGEST_SERVER_CERT_FILE / ARIS_INGEST_SERVER_KEY_FILE.
  2. The collector verifies that certificate against forwarder.mtls.server_ca_path and forwarder.mtls.server_name.
  3. The collector presents its client certificate.
  4. Core verifies the client certificate against ARIS_INGEST_CLIENT_CA_FILE.
  5. Core derives the collector host_id from the certificate's single DNS or URI SAN identity and rejects envelopes that claim a different host.
  6. When ARIS_INGEST_REQUIRE_ENROLLMENT=true, core also checks active collector certificate and identity state before accepting the stream, then rechecks long-lived streams before later frames.

Accepted envelopes are written to the ingest ledger first. Projection into sessions, processes, and configuration facts runs after receipt so the ingest stream stays durable and retry-friendly.

For broad fleet deployment, the target model is bounded bootstrap enrollment into per-device mTLS identity, followed by automatic renewal and active revocation. The initial admin enrollment API, enrollment command, renewal command, automatic renewal scheduler, heartbeat API, core routes, and opt-in ingest-side active certificate/identity authorization exist as MVP primitives, but polished fleet-health UI and operator-facing revocation UI are still incomplete; treat manually provisioned collector certificates as the evaluation and small-pilot path until the full workflow is available.

Enrolled collectors can also send HTTPS mTLS heartbeats to core. Depending on whether ARIS_ENROLLMENT_CLIENT_CERT_HEADER is configured, heartbeats use either direct client certificates observed in r.TLS or the same trusted proxy client-cert header used by the renewal endpoint, and include a proof signature from the corresponding private key. They report collector version, config hashes, queue depth, source state, renewal status, and last envelope time. Core stores those in Postgres and derives fleet states such as healthy, stale, revoked, certificate-expiring, queue-at-risk, unsupported-version, and config-invalid.

Release tooling now includes aris-release-manifest, which emits the checksum and digest manifest for published images, host packages, and SBOM files. It computes local artifact/SBOM SHA-256 values and requires image references to use immutable SemVer tags pinned as image:version@sha256:<digest>; signing and SBOM generation are separate release steps.

What ARIS does NOT do

  • ARIS never writes to Google. Read-only scopes only.
  • ARIS does not phone home or push telemetry to Ryora.
  • ARIS does not proxy AI traffic. Collectors observe local AI-tool signals from hosts you opt into and forward records to core.

See also