Technical breakdown
How the platform actually works.
A section-by-section description of the components running today. No projections, no comparisons — the parts that are committed to the repository and observable in production.
Runtime architecture
The platform runs as a Node.js 22 backend (ESM, TypeScript sources compiled to plain JS at build time) fronted by a Next.js dashboard. The backend serves the HTTP API and a separate BullMQ worker consumes background jobs from Redis.
PostgreSQL holds tenant data — fleet, rentals, reservations, availability, insights, usage events, and the append-only audit table. A local SQLite database retains the legacy analytics read path.
The AI layer chains providers: Anthropic Claude as the primary target with OpenAI and Gemini as fallbacks. Selection happens per request in the provider module.
Every deployable image is built by Jenkins on push to main and rolled out by ArgoCD from the gitops manifests. The backend and the ingest cron are two separate images with two separate Jenkins jobs.
- Backend Dockerfile: Dockerfile
- Ingest cron Dockerfile: cron/Dockerfile
- Backend Jenkins pipeline: Jenkinsfile
Hash-chained audit ledger
Audit events are written to saas.audit_events. Each row stores the SHA-256 hash of the previous row concatenated with its own canonical payload, so any tampering with an earlier row invalidates every hash that follows.
The first row of every tenant chain is a sentinel with a fixed predecessor hash. Insertion is done inside a SECURITY DEFINER function that reads the previous hash and computes the next one atomically.
- row_hash = sha256(prev_hash || canonical_payload)
- canonical_payload is a stable JSON serialisation of the event
- chain is per-tenant — cross-tenant reads are blocked by RLS
Chain verification endpoint
The backend exposes POST /api/v1/public/trust/verify-chain. Given a tenant slug, it walks the tenant's chain, recomputes every hash, and returns a snapshot that records how many events were verified, the first and last hashes, and the timestamp at which the snapshot was generated.
The endpoint is public but read-only and rate-limited. The Trust page consumes it to render live proof; the same shape is documented here so external verifiers can call it themselves.
- Request: { tenant_slug: string }
- Response: { snapshot: { events_verified, first_hash, last_hash, snapshot_generated_at, api_version } }
- api_version is frozen at v1 (LP5.a Sprint 1)
Release Certificate v1
Every release ends with a Release Certificate JSON committed under change-safety/. The certificate records the sprint, the commit SHA, the outcome of each Change Safety gate, any recorded exceptions, and the sign-off.
The schema is frozen at v1. Downstream tooling reads the certificate to know which gate ran, what its verdict was, and where the evidence lives.
- sprint — sprint identifier
- commit_sha — deployed commit
- gates — { replay, snapshot_diff, journey, manifest } verdicts
- exceptions — recorded overrides with charter reference
- signed_off_by — approver identity
- evidence_ref — path to the evidence bundle
Evidence bundle shape
Each release stores its artefacts under change-safety/evidence/<track>/<milestone>/. The bundle carries a manifest.json listing every file and its checksum, and a status.txt written in plain sentences.
The bundle is the source of truth for a release. If a claim is not backed by a file inside the bundle, it is not part of the release record.
- manifest.json — [{ path, sha256, bytes }] over the bundle contents
- status.txt — human-readable outcome, one sentence per gate
- screenshots/ — visual regressions captured at gate time
- lighthouse/ — performance runs when the gate requires them
Tenant isolation
Every tenant row carries a tenant_id column. PostgreSQL row-level security policies attach app.current_tenant to each query so callers only see rows that belong to their own tenant.
Cross-tenant reads are permitted only from a SECURITY DEFINER function that is explicitly annotated as cross-tenant and is called from a small, audited set of system_admin surfaces.
JWT claims resolve the tenant. The query string is never trusted for tenant selection.
saas.audit_events
The saas.audit_events table records every state-changing event flowing through the API. Each event carries the tenant, the actor, the event type, a canonical payload, and the pair of hashes that anchor it to the chain described above.
Event types are drawn from the AuditEventType enum; OpenAPI x-audit-event annotations on each route declare which events it emits, and a test guards against drift between the annotations and the enum.
- tenant_id, actor_id, event_type
- payload (canonical JSON)
- prev_hash, row_hash
- created_at
Decision platform (today)
The decision platform is the layer that turns detected patterns into recorded outcomes. It is composed of three pieces that already exist in the repository:
Rules describe conditions over KPI series. Actions are typed operations that a rule may propose. The execution ledger records which rule fired, which action was selected, and what the resulting event was — with a link back into the audit chain.
- Rule table — declarative conditions over the KPI layer
- Action registry — typed catalogue of operations a rule may propose
- Execution ledger — append-only record of rule → action → outcome
Trust vs. Engineering
/trust is the customer-facing surface. It is designed to be read by someone deciding whether the platform can be trusted with their data: it links to a live hash-chain verifier, an example Release Certificate, and a short incident library.
/engineering — this page — is the technical description of the same components. It exists so an engineer can read how the pieces fit together without having to reason from the customer surface.
Both pages describe the same system. Neither page advertises functionality that is not present in the repository today.
Scope of this page
This page describes components that are committed to the repository and observable in production today. It contains no benchmarks, no comparisons to other systems, and no forward-looking statements. Where a claim is specific, a relative path to the committed artefact is cited.