System architecture

The parts of Catalyst Platform, where each one runs, what it is built with and how they talk to each other. One rule shapes all of it: information that identifies a business or a person is stored only in the database and the file store, never in Cloudflare's queues, workflows or caches. In prod, the one deployed environment, the database and the books as read from Xero are in South Africa; reports, packs and the audit copies stay in the EU until the move to Cape Town.

Overview

Cloudflare runs all of the code and the background work, and stores nothing that identifies anyone. Every record and file sits in PostgreSQL and an S3-compatible file store. In prod the database and the books as read are in Amazon Web Services (AWS) in Cape Town (the region called af-south-1), and the other files are in Cloudflare R2 in the EU. Moving those to Cape Town changes configuration only; the code stays the same. The practice's client businesses are called SMMEs here.

Worker
A small program that Cloudflare runs on demand. Each part of Catalyst Platform is its own Worker.
Service Binding
A private, direct call from one Worker to another inside Cloudflare (a remote procedure call, or RPC). It has no internet address, so nobody outside can call it.
Queue
A to-do list between two Workers: one leaves a short message, the other picks it up and retries if something fails.
Workflow
A long-running job, such as a sync that takes many steps, which survives restarts and resumes where it left off.
Durable Object
A tiny single-copy counter or lock, used here for rate limits and safe token refresh.
Hyperdrive
Cloudflare's pooled connection from the Workers to the database. Its caching is switched off.
Outside the platform Browsers Practice users, Catalyst staff SimplePay Payroll Xero Accounting Service providers, advisers e.g. Aurora Cloudflare · runs the code · stores nothing that identifies anyone Dashboard The website Gateway + Identity Sign-in, roles, routing SimplePay connector Built, not deployed Xero connector Sync, tidy the books Analysis Health, checks, signals Report Drafts, review, PDF Audit Daily chain check Service provider distribution Consent, service provider library Workflows long sync jobs Durable Objects rate limits, locks used by the connectors; they hold references and counters only RPC Service Binding RPC · private calls between Workers, no internet address Queue references only HTTPS, at the platform's own address OAuth 2.0, not yet granted by SimplePay OAuth 2.0 read-only HTTPS API Agreed information only, after the SMME's consent, sent by Catalyst staff Hyperdrive · SQL TLS, certificate checked, no cache S3 API, signed (SigV4) files in and out Keys KMS after the move Report → PDF in Cloudflare today PostgreSQL 17 Today: Amazon RDS, Cape Town After the move: private network File store Today: S3 Cape Town, R2 EU After the move: S3, Cape Town Keys Today: Worker secrets After the move: KMS PDF rendering Today: Browser Rendering After the move: Fargate Every record, file and key that identifies anyone · today: Cape Town and EU · after the move: AWS Cape Town (af-south-1)
Solid boxes and lines are the everyday path. Dashed lines are not in use yet: the funding and advice path has no delivery channel built, and SimplePay has not granted access. Every connection to the data stores is encrypted or signed; nothing in Cloudflare keeps a copy of what passes through.

Today and after the move to Cape Town

Catalyst agreed in October 2026 that the platform need not run fully in South Africa at first, to keep its cost low. Prod, the one deployed environment, therefore keeps some files in Cloudflare R2 in the EU and its main keys as Worker secrets; its database and the books as read are already in Cape Town. The written confirmation is still to be filed. Prod holds real businesses' data; until 30 October 2026 its database was in the EU, with Neon in Frankfurt. The code is the same in both set-ups. Each capability is chosen by configuration:

Capability Prod today After the move to Cape Town
Database PostgreSQL 17 on Amazon RDS in Cape Town, reached through Hyperdrive over TLS. PostgreSQL 17 on Amazon RDS in Cape Town, with no public address, reached through Hyperdrive over a private Cloudflare Tunnel.
File store Amazon S3 in Cape Town for the books as read, each practice's files under its own key. Cloudflare R2 in the EU jurisdiction for reports, packs and audit copies, used through its S3 interface. Buckets are private and each Worker has its own token for its own buckets. Amazon S3 in Cape Town, one IAM user per Worker, limited to its own buckets.
Keys The Xero token key and the signing keys held as Worker secrets, never reused. Each practice's key for its raw files is in AWS KMS in Cape Town. AWS KMS in Cape Town. The keys never leave KMS.
PDF rendering Cloudflare Browser Rendering. A Chromium renderer on AWS Fargate in Cape Town, with no internet access.
Where the Workers run Placed in Cape Town, next to the database, so each page needs fewer slow round trips. Static files still come from the data centre nearest the user. The same.
Web address test.platform.catalystholdings.global, which is an address, not a test system. The gateway has no public address. The same address. Changing it means every user registers their passkey again, so it needs a decision of its own.

Finishing the move. Apply the rest of the AWS set-up (already written in Terraform), copy the files in R2 across, move the keys into KMS (or have each practice reconnect Xero), switch to the Cape Town PDF renderer, and change each Worker's settings. No application code changes. One database change then makes service provider files held in Cape Town a requirement again.

The components

Every service is written in TypeScript 6 and runs as a Cloudflare Worker (deployed with Wrangler 4). They share the same foundations: Zod 4 checks every message and request, Drizzle ORM 0.45 with postgres.js 3.4 talks to the database, aws4fetch 1 signs requests to the file store and to AWS, and Vitest 4 runs the tests. The table lists only what each part adds. Under each name is its Worker in Cloudflare, with -pilot after it today (-prod after the rename): catalyst-platform-web-pilot, for example.

Component What it does Built with Runs on Talks to, and how
On Cloudflare
Dashboardcatalyst-platform-web The website practices and Catalyst staff use, rendered on the server. React 19, React Router 7, Vite 8, Ant Design 6 Worker, at the platform's own address Gateway only, by Service Binding RPC. It never touches the database or the file store.
Gatewaycatalyst-platform-gateway Checks who is asking and what their role allows, then passes the request on. Holds no business logic. Remembers a signed-in session for 5 seconds for reads, so one page checks it once; every action checks it afresh. Plain TypeScript; Cloudflare's rate limiter for sign-in Worker with no public address, reached only through the dashboard Every service by RPC; the database through Hyperdrive, only to record audit events.
Identitycatalyst-platform-identity Users, practices, roles, sign-in sessions, passkeys, invitations, in-app notices and each practice's logo (stored in the database). Passwords hashed with argon2id (@noble/hashes 2); passkeys with SimpleWebAuthn 14 Worker The database through Hyperdrive; called by the gateway and analysis.
Xero connectorcatalyst-platform-xero-connector Connects an SMME's Xero, syncs it every night, keeps the raw copy and tidies it into one standard ledger. Hono 4; a typed Xero client of its own Worker, with a Workflow, two Durable Objects and queues Xero by OAuth 2.0; the file store by signed S3 requests; the database through Hyperdrive; analysis by queue.
SimplePay connectorcatalyst-platform-simplepay-connector Brings in payroll the same way. Employee ids are replaced by coded tokens before anything is stored. Built and tested against a stand-in SimplePay on a developer's machine; not deployed, and nothing calls it yet. It cannot connect until SimplePay grants third-party access. Same as the Xero connector Worker, with a Workflow, a Durable Object and a queue SimplePay by OAuth 2.0, once granted; the file store by signed S3 requests; the database through Hyperdrive.
Analysiscatalyst-platform-analysis Works out each month's figures, health score and checks, and keeps the rule library of checks. Also works out the signals that service providers' rules can read. Signals are never scored and never appear in a report. Hono 4; @platform/ratios, @platform/triggers Worker, woken by a queue Reads the tidied ledger from the Xero connector by RPC; identity by RPC; the database through Hyperdrive, with a separate login for the rule library.
Reportcatalyst-platform-report Builds the draft report, runs review and approval, and issues the PDF with the practice's logo. Hono 4; @platform/report, @platform/report-workflow, @platform/narrative Worker Analysis by RPC; the PDF renderer; the file store by signed S3 requests; service provider distribution by queue. Optional AI wording would go to Anthropic through Cloudflare AI Gateway, with no names or identifiers in it. It is switched off in prod, where the report uses template text.
Service provider distributioncatalyst-platform-datahousecatalyst-platform-vas Keeps the SMME's consent record and the anonymised record of each SMME (the datahouse), tests each service provider's criteria and opportunity rules, and builds a file holding only what the SMME agreed to share. Its service provider library is where Catalyst Platform staff set up each service provider. Two Workers (datahouse and service provider service); Hono 4; @platform/service-provider-spec, eligibility, consent, pack, offers Workers, one woken by a queue Analysis by RPC; the database through two separate connections (the second only for re-identifying named data); the file store by signed S3 requests. Files are built and checked, but no delivery channel to a service provider is built yet.
Auditcatalyst-platform-audit Every night checks that the audit trail is unbroken and writes a signed copy of it to locked storage. @platform/audit Worker on a daily schedule The database through Hyperdrive, read only; the file store by signed S3 requests.
Data stores (today: AWS Cape Town, and R2 in the EU; after the move: AWS Cape Town)
Database The system of record: practices, users, ledgers, findings, reports, consents, logos and the audit trail. Its structure starts from one baseline migration (26 October 2026); later changes are added as new migrations. PostgreSQL 17 on Amazon RDS. After the move: encrypted with a key in KMS Cape Town. After the move: private network, no public address Only through Hyperdrive. Today over TLS; after the move over an outbound-only Cloudflare Tunnel.
File storage Raw copies of the books, issued report PDFs, service provider and adviser files, and audit copies. Today: Amazon S3 for the raw copies, Cloudflare R2 private buckets for the rest. After the move: Amazon S3, one encryption key per bucket. Audit copies locked for 7 years in both Today: raw copies in Cape Town, the rest in the EU. After the move: Cape Town Signed (SigV4) HTTPS from the Workers that need each bucket, and no others.
Keys Protect connection tokens, coded tokens and the audit signature. Today: Worker secrets, and AWS KMS for the raw copies. After the move: AWS KMS Today: with the Workers; KMS in Cape Town. After the move: Cape Town KMS by signed (SigV4) HTTPS; the keys themselves never leave KMS.
PDF renderer Turns an approved report into a PDF. Today: Cloudflare Browser Rendering. After the move: Node.js, Playwright 1.63 with Chromium, in a container Today: Cloudflare. After the move: AWS Fargate in Cape Town, internal only, no internet access Called by the report service; after the move over the private tunnel.
Shared packages (published to GitHub Packages, released together under one version)
@platform/core Common basics: results, ids, money in cents, and a logger that writes reference numbers only. Published as @catalyst-global-ltd/catalyst-platform-core, -contracts, -db, -audit and -testing. For now the services link them straight from the aicfo-core repository, so every service builds against the same code and agrees on every message and table.
@platform/contracts The agreed shape of every RPC call and queue message, and the rule that background steps pass references, never data.
@platform/db The database tables, every change to their structure, row-level security and per-practice access.
@platform/audit Writes audit events in the same step as the change, and checks the chain.
@platform/testing Test data factories, used in tests only.

How the parts talk to each other

Service Bindings

Workers call each other directly inside Cloudflare, as if calling a function. Of the Workers, only the dashboard can be reached from the internet. For now the database can be reached too: since 2 November 2026 it has a public address that admits only Cloudflare's network, a stopgap until the private tunnel works again. Its passwords and encryption still apply. The gateway and every other service have no address of their own, so the gateway's checks can't be bypassed. Each call and each answer is checked against an agreed shape.

Queues and Workflows

Background work is handed on by queue: sync to tidy-up, tidy-up to analysis, an issued report to service provider distribution. Messages and Workflow steps carry references only, such as a run number and a file name, never the figures. Failed work is retried up to five times, then parked in a dead-letter queue, where it is recorded and visible and can be sent again.

Database access

Workers reach PostgreSQL through Hyperdrive with caching switched off: today over TLS, after the move to Cape Town over a private tunnel. Each service signs in as its own database user with only the rights it needs. Row-level security lets a query see only its own practice's rows. Every change writes its audit event in the same transaction, so a change that can't be audited doesn't happen.

Files and keys

Workers read and write the file store through the S3 interface, with requests signed by AWS's standard method (SigV4), whether the store is S3 or R2. Each Worker has its own narrow permission, limited to its own buckets. Most keys are Worker secrets today; after the move each Worker asks KMS in Cape Town.

Outside services

Xero: the practice signs in to Xero and grants read-only access (OAuth 2.0); Catalyst Platform never writes to Xero. A Durable Object per organisation keeps calls inside Xero's limits (60 a minute, 5,000 a day, 5 at once) and waits when Xero asks it to. SimplePay: the same pattern for payroll, once SimplePay grants access. Service providers: would receive only agreed information, sent by Catalyst staff; how it reaches them is still to be agreed and built.

The nightly data journey

From an SMME's books to an issued report, and how each step hands on to the next.

Every figure is stored in the database or the file store; Cloudflare passes on references only 1 Sync Xero connector Workflow, nightly at 02:00 2 Raw landing Books as read, in the file store 3 Normalise Tidy ledger, PostgreSQL 4 Analyse Health score, checks, findings, signals SigV4 HTTPS files written Queue references only Queue references only Service Binding RPC · when the accountant opens the month in the dashboard 5 Report draft Built from the stored findings 6 Accountant review Findings and report approved by a person 7 Issue PDF made and kept in the file store Dashboard via gateway Approve via gateway Queue references only Service provider and adviser check Optional; nothing is shared without consent
Every step runs again safely if it is interrupted. Steps 1 to 4 run on their own each night; from step 5 on, the accountant is in control. Today Cloudflare Browser Rendering makes the PDF in step 7; after the move a renderer in Cape Town makes it, reached over the private tunnel.

Security and residency, in brief

Environments and deployment

Local

Each developer runs every Worker on their own machine with Wrangler, plus PostgreSQL 17 and an S3 stand-in in Docker. Made-up data and Xero's Demo Company only.

Prod

The one deployed environment, for real practices, with its own database, buckets and keys. First deployed on 14 October 2026, then emptied and started again from the baseline migration on 26 October 2026. It holds real businesses' data, in the EU until its database moved to Cape Town on 30 October 2026. By the product owner's decision of 10 November 2026, one person may approve their own rule and service provider changes in prod, and the record shows the approval was the author's alone. There is no staging or other deployed environment.

The move to Cape Town (planned)

The same Workers, with the remaining data stores, the keys and the PDF renderer in AWS Cape Town. This moves prod; it is not a new environment. The AWS set-up is written in Terraform but not applied yet. The move is due before a second practice joins, and Catalyst is still to decide on it.