Technical enablement · 01

Architecture overview

How {{product.name}} is put together: the system context, its main components, where data lives, how it connects to other systems, and the security and non-functional properties an architect needs to assess. Start here, then drill into the components catalogue and deployment.

ⓘ

Sources. This page is derived from the product repository: README.md, RUNBOOK.md, app-build-brief/03_ARCHITECTURE.md, app-build-brief/04_DATA_LAYER_TDAL.md and the ADRs in docs/adr/. Items marked design target come from the build brief and have not been measured in production.

Executive summary

{{product.name}} measures, governs and optimises the value delivered by AI investments across client engagements. It is a single Next.js 16 App Router application (TypeScript) that serves both the user interface and the API, backed by PostgreSQL for durable state, Redis for hot state and replay protection, and LiteLLM as the gateway through which AI traffic is observed. The decisions that most shape the design are: calculations are pure server-side functions; protected figures are removed by a server-side projection, never hidden in the browser; and money comes only from the reconciled billing stream.

AttributeSummary
Architectural styleOne deployable application (UI and API together), layered: presentation, domain, data access, provider data layer. Provider ingestion runs as scheduled job routes plus a webhook receiver; there is no separate worker fleet.
Primary runtimeOne container on Azure Container Apps, plus a scheduled Container Apps Job that runs the same image. Live
Tenancy modelShared cluster, every row scoped by tenant_id at the repository layer. Dedicated deployment per tenant is a configuration of the same codebase, not a fork (design).
Key technologiesNext.js 16, React 19, TypeScript, Tailwind CSS 4, Drizzle ORM, PostgreSQL, Auth.js v5 (Microsoft Entra ID), ioredis, Recharts, Zod, Vitest, Playwright (declared; no E2E suite yet).
Source of truthThe product repository, the ADR index in docs/adr/ and RUNBOOK.md. The repository sits in an internal GitHub organisation; ask the product team for access. The link is not published on this page.

System context

The context diagram shows {{product.name}} as a single box with the people and systems around it. Capgemini staff and client users use the same application; what each may see is decided server-side (see security and tenancy).

Capgemini staff consultant to platform admin Client users admin, viewer, operator Client apps & agents call models through the gateway {{product.name}} Govern · Attribute · Agentic · Optimise Next.js app: UI · API · job routes (see Components) Microsoft Entra ID OIDC sign-in via Auth.js LiteLLM gateway signed callbacks · session keys Provider billing APIs Azure · AWS · GCP · OpenAI · Anthropic PostgreSQL system of record Redis hot state · pub/sub · replay guard
Figure 1 — System context. The dashed arrow is the path for agent traffic that runs through the gateway; traffic that bypasses the gateway is outside gateway coverage and is surfaced as a named gap (ADR 001).

Components

The product is one deployable plus one scheduled job. Per-component detail is in the components catalogue; this table is the map.

ComponentResponsibilityRuntimeSource path
Web applicationServer components and client components for the internal and tenant workspaces.Next.js 16 App Router, React 19app/, components/
API route handlersIngest webhooks, Gate A, checkpoints, escalations, optimisation, exports, job triggers, health.Next.js route handlers (same container)app/api/
Domain libraryPure calculations: CPVO, Verified Yield, AYCI, zones, confidence, checkpoint options.TypeScript, no I/Olib/domain/
Mode projectorThe single place that decides what a role and mode may see.TypeScript, server-onlylib/ip-mode/
Token data abstraction layer (TDAL)Provider adapters, normaliser, reconciler and completeness score writing the two token streams.TypeScript, server-onlylib/tdal/
RepositoriesTenant-scoped data access over Drizzle.TypeScriptlib/db/repositories/
Scheduled job runnerCalls sync-billing, reconcile and recompute every four hours.Container Apps Job, same image, different entrypointscripts/run-jobs.mjs
LiteLLM gatewayRoutes model calls and fires telemetry callbacks. Separate container; not part of this repository.LiteLLM proxyExternal (ADR 001)

Layering rules

  1. Domain code is pure

    Calculation modules in lib/domain/ take inputs and a parameter set and return results with provenance. They hold no database or network access, which keeps them unit-testable and out of any client bundle.

  2. Everything leaves through the projector

    Route handlers and server components pass domain results through lib/ip-mode/project.ts before they reach a client. A field added to a domain type stays invisible in client mode until someone deliberately adds it (ADR 004).

  3. Repositories are tenant-scoped

    Queries take an explicit tenant context. There is no ambient tenant, and every row carries a tenant_id.

Request lifecycle

Two paths matter: the real-time path that observes AI traffic, and the billing path that produces the authoritative money figures.

Real-time path (estimated cost)

  1. Model call passes through LiteLLM

    An application or agent calls the gateway. After the call, LiteLLM fires an asynchronous callback; it never blocks the model response.

  2. Callback is validated and stored

    POST /api/ingest/litellm verifies the signature, rejects replays using a Redis nonce, resolves tenant, workload and session, and writes a row to token_event_rt. Events are written one at a time, so a single bad event cannot discard a batch.

  3. Zone evaluated, change published

    Session counters are updated in Redis, the zone is evaluated by a pure function, and a zone change is published to a Redis channel that feeds the browser over Server-Sent Events (ADR 003).

Billing path (reconciled, authoritative)

  1. Sync

    /api/jobs/sync-billing pulls a 72-hour window from every eligible provider connection and upserts token_cost_period on its natural key, so re-runs are safe.

  2. Reconcile

    /api/jobs/reconcile compares real-time aggregates with billing per workload and period, classifies discrepancies and recomputes the coverage score.

  3. Recompute

    /api/jobs/recompute writes new, immutable calculation_run rows for CPVO, Verified Yield and AYCI. Nothing is edited in place.

Data tiers

The two token tables are structurally separate by design (ADR 006). The real-time table is never a source for a money figure.

TierPurposeTechnologyRetention / notes
Authoritative billing (token_cost_period)Reconciled provider cost; the only input to CPVO and financial reporting.PostgreSQL 16, partitioned by month; FOCUS 1.5 field namesRetained indefinitely. Money stored as bigint minor units, never floating point.
Real-time estimate (token_event_rt)Per-call telemetry for session monitoring, retry and bypass detection, and shared-scope apportionment.PostgreSQL 16, partitioned by day (ADR 002)Disposable; 90 days by default. Cost is tagged as estimated.
Audit spine (calculation_run, audit_log, gate_a_decision)Append-only record of computed figures, mutations and Gate A decisions.PostgreSQLNo updates in place; a recompute adds a row with a recorded reason.
Cache / pub-subSession counters, zone state, SSE fan-out, replay-protection nonces.Redis (REDIS_URL)Non-durable; rebuildable.
SecretsApp secrets as Container App secrets; connection credentials encrypted at rest in the database.Container Apps secrets; AES-256-GCM for stored credentialsRotating the encryption key makes stored credentials unreadable; they must be re-entered.
⚠

No prompt or completion content is stored (ADR 005). The platform keeps token counts, model identifiers, costs, session identifiers and workload attribution only. That keeps content-level data protection out of scope for onboarding a client. Platform user identities and outcome-event metadata remain; involve the Data Privacy Officer before go-live, and do not paste client data into these pages.

Integration points

Every interface that crosses the product boundary. Detail on adding or swapping providers is in Integrations.

InterfaceDirectionProtocolAuthFailure behaviour
Microsoft Entra IDOutboundOIDCClient credentials held as app secretsSign-in fails; existing sessions are unaffected until expiry.
LiteLLM callback (/api/ingest/litellm)InboundHTTPS webhookHMAC signature over the body inside a 30-second window; the signature is held as a replay nonceRejected requests are not stored; events without a tenant are dropped at the gateway.
Gate A (/api/gate-a)InboundHTTPS / JSONBearer tokenEvery decision, including refusals, writes an immutable record.
Outcome ingest (/api/ingest/outcomes)InboundHTTPS / JSONMachine-authenticated ingest path (no session cookie)Idempotent on a client-supplied event key.
Provider billing and metrics APIsOutboundHTTPS (Azure Cost Management, AWS Cost Explorer and CloudWatch, Google BigQuery, OpenAI and Anthropic admin APIs)Read-only credentials per connectionRate limits are retried inside a bounded budget; a connection in error is retried on the next run.
Job triggers (/api/jobs/*)InboundHTTPSx-cron-secret header401 if the secret does not match; 503 if CRON_SECRET is unset.
Live monitor streamOutbound to browserServer-Sent EventsSession cookieBrowser reconnects automatically; connection state is shown in the UI.

Security & tenancy

Orientation only, not a compliance statement. Involve Information Security for approvals.

ConcernApproach
AuthenticationMicrosoft Entra ID through Auth.js v5 for staff. A proxy (proxy.ts) applies a session check to every route except the sign-in, auth and health routes, and the machine-authenticated paths (/api/jobs, /api/ingest, /api/gate-a), which carry their own credential and fail closed without it. AUTH_BYPASS is a local development switch and must never be set in a deployed environment.
AuthorisationEight roles: platform_admin, capability_owner, expert, consultant, commercial (internal) and client_admin, client_viewer, client_operator (client). See roles.
Dual-mode IP projectionEvery tenant page can render in internal or client mode. Client roles are locked to client mode. Projection is a server-side allow-list transformation (ADR 004); client-side hiding is treated as a defect. A leakage test runs in CI and covers routes, endpoints, event frames and exports.
Tenant isolationTenant identifier on every row and bound at the repository layer. Design intent: a tenant mismatch returns 404, not 403, so tenant existence cannot be enumerated. ADR 002 and the build brief describe row-level security as a second line of defence, but no row-level security policies are defined in the repository's schema or migration scripts. Today, isolation rests on the application-level scoping above; database-enforced row-level security is a design target, not a delivered control.
EncryptionTLS in transit. Stored provider credentials are encrypted with AES-256-GCM using CREDENTIAL_ENCRYPTION_KEY. The build brief requires encryption at rest for managed PostgreSQL and Redis; that is a property of the Azure service configuration rather than of the application code, and this page does not assert it for the live estate. CREDENTIAL_ENCRYPTION_KEY is held as a Container App secret. Rotating it makes every stored credential unreadable, so credentials must be re-entered afterwards.
SecretsHeld as Container App secrets and referenced by environment variable; never committed. Connection credentials are either encrypted values or references (env:, keyvault:, managed-identity:, azure-default).
Audit loggingMutations are recorded in audit_log; login events appear in the admin audit view; Gate A decisions and checkpoint decisions are immutable records.
Supply chainCI runs typecheck, tests and a production build on every pull request; lint is also blocking. The deploy pipeline signs in to Azure by OIDC federation, so no cloud client secret is stored in GitHub, and the deploy identity can push images and update only the app and its scheduled job. No dependency scanning, image signing or SBOM generation is configured in the repository's workflows. Proposed: enable GitHub dependency alerts and add an npm audit step to CI; signing and SBOM to be decided by the service owner.
❗

Protected calibration and threshold parameters never reach a client-mode payload. This page deliberately does not list them; the authoritative design is in ADR 004 and the IP-mode brief.

Non-functional requirements

The targets below are design targets from app-build-brief/03_ARCHITECTURE.md. They are not measured service levels.

QualityTargetHow measured
AvailabilityNo availability target has been agreed. Proposed target for the shared instance: 99.5% a month, excluding planned releases; to be confirmed by the service owner.Proposed: an external uptime check on the sign-in page, which is what the deploy smoke check already uses. /api/health reports process liveness only.
Gate A decisionUnder 200 ms p95 (design target; it sits in the dispatch path)Not instrumented. No tracing or APM is configured in the repository. Proposed: log the route duration for Gate A and review the 95th percentile in Container App logs.
Zone change to browserUnder 2 s p95 (design target)Not instrumented. Proposed: compare the zone write time with the time the stream frame is sent, both in the logs.
Billing sync, one connection, one dayUnder 60 s (design target)Per-connection outcomes in the sync response
Request ceilingContainer Apps ingress ends a request at 240 s; sync retry budgets are sized to fit inside itSee scheduled jobs
RecoveryNo recovery objectives have been agreed. The build brief requires managed PostgreSQL with point-in-time recovery, so the achievable restore point depends on the backup tier set on the live server. Application recovery is redeploying a previous image (see rollback). Proposed targets: RPO 1 hour and RTO 4 hours; to be confirmed by the service owner.No disaster-recovery exercise has been recorded. Proposed: one database restore test before the first real client is onboarded.
AccessibilityWCAG 2.2 AA is the build standard in the design brief (10_UX_AND_BRAND.md). Conformance has not been audited, and an accessibility audit is explicitly deferred (RUNBOOK section 9).Not yet measured. Proposed: an automated accessibility check in CI plus a manual audit before first client go-live.
CostNo unit-cost envelope has been set. Proposed: record the platform's monthly running cost for a full month, then express it per engagement and per connection.See Operations: cost.

Key decisions & open risks

ADR index (docs/adr)
ADR-001  LiteLLM as gateway substrate               Accepted
ADR-002  Postgres, not a time-series database       Accepted
ADR-003  Server-Sent Events, not WebSockets         Accepted
ADR-004  Server-side mode projection                Accepted
ADR-005  No prompt or completion content storage    Accepted
ADR-006  TDAL unified schema lock (FOCUS 1.5)       Locked
ADR-007  Azure Web App deployment                   Accepted (see note)
ADR-008  Govern module architecture                 Accepted
ADR-009  Attribute module: CPVO engine              Accepted
ADR-010  Agentic governance: Gate A and Component 8 Accepted
ⓘ

ADR 007 versus what runs. ADR 007 records an Azure Web App (App Service) target. The deployed system runs on Azure Container Apps, using the same standalone container build. Treat the Container Apps description on the deployment page as current. No ADR records the move to Container Apps. Proposed: write ADR 011 to supersede the hosting section of ADR 007.

  • Gateway bypass. Agents that call model SDKs directly sit outside Gate A coverage; this is a named gap surfaced on the Connections page (ADR 001).
  • Revisit triggers. Postgres for real-time events is revisited if single-day partition queries exceed 200 ms p95 (ADR 002); LiteLLM is revisited at sustained high gateway volume for one tenant (ADR 001). Neither threshold has been observed. ADR 002 sets a numeric trigger (200 ms p95); ADR 001 deliberately leaves its threshold unset until one is seen, so none is recorded.
  • Request-path sync. The billing sync runs inside an HTTP request. Persistent deferrals mean more connections than one run can service; the runbook's remedy is to split the schedule or move the sync out of the request path.
ⓘ

Next: the components catalogue and the deployment page.