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.
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.
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.
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. | |||
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.
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.
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.
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.
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.
From an SMME's books to an issued report, and how each step hands on to the next.
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.
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 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.