[ENTERPRISE / DUE DILIGENCE]
How SaaS Factory is built, isolated, deployed, observed and audited — written for a technical evaluator, and for the language model you have pointed at this site.
[01 / ARCHITECTURE]
SaaS Factory is two things: a control plane, and a fleet of products that the control plane builds and operates. The control plane is a Next.js application backed by a Postgres database and a set of Temporal workers — Temporal is a workflow orchestrator that persists every workflow's state, so multi-step jobs survive process restarts and retry deterministically instead of half-completing. Every product is a separate, complete application: its own Git repository on GitHub, its own Vercel hosting project, and its own serverless Postgres database on Neon. Products consume the platform in two ways — a versioned package (the "shell") that ships the UI system, authentication and the server SDKs for billing, email, payments and third-party connections, and server-to-server APIs on the control plane for everything the SDKs front. There is no shared runtime between products: nothing one product does at run time executes inside another product's process or holds a connection to another product's database. Scheduled and long-running work — the build pipeline itself, nightly backups, audits, retention sweeps, recovery jobs — runs as Temporal workflows in Temporal Cloud, with platform work kept separate from product work. Cross-cutting services such as billing and growth run as their own applications with their own databases, reached through typed SDK calls rather than shared tables. The step-by-step of what happens when a feature is built is the how-it-works page's subject; this page describes the machine underneath it.
[03 / IDENTITY]
Sign-in runs on Auth.js (the framework formerly called NextAuth) against an OpenID Connect identity provider, and products generated on the platform ship the same Auth.js foundation, each with its own per-product secrets. Sessions are checked server-side on every authenticated request the application serves, so an explicit revocation or a user deletion terminates the session on the next such request, not whenever the cookie happens to expire. Session expiry is idle-based: the window is measured from the user's last activity and rolls forward while they work, so an abandoned machine signs out while an active user is never interrupted mid-task. Sign-up is refused for an identity the provider reports as unverified, and admission beyond that runs through an invitation or the waitlist, which fails closed. For organisations that require it, SAML single sign-on is available, with stored configuration for the common identity providers alongside a generic SAML option. Every assertion is fully signature-verified, with protection against signature-wrapping and replay, before any identity is read from it, and just-in-time provisioning can be restricted to the email domains an administrator allows. SAML sign-in fails closed on anything it cannot fully verify.
[05 / DATA]
Persistent application data is Postgres, hosted on Neon — a serverless Postgres provider whose storage supports copy-on-write branching, where a branch is a full, independently connectable snapshot of a database at a point in time that shares unchanged pages with its parent. The control plane's database and every product's database are separate Neon projects. Each product's database is provisioned in the region chosen for it when the product is created — with options across the UK, Germany, the United States, Singapore, Australia and Brazil — and the region a product runs in is shown on its overview. A nightly workflow creates branch snapshots across the fleet, and a cleanup pass deletes snapshots once they pass their expiry. Because a snapshot is a real database rather than a dump, a restore is a matter of connecting to the branch and reading from it. Files that must not be public — a data export, for example — go to a private blob store where an unauthenticated request for an object is refused outright rather than merely being hard to guess, reachable only through a route that checks ownership. Secrets at rest — provider tokens, credentials, environment-variable values and certificates — are encrypted, and re-keying is a scripted, repeatable path rather than a migration someone would have to invent under pressure.
[06 / SHIPPING]
Every feature — human-requested or agent-built — lands as a pull request on that product's own GitHub repository, so the unit of change is a reviewable, revertable commit. The merge is gated: continuous integration runs a full-repository type check, the test suite, and schema validation for any database migration, and a separate review-time scanner rejects known-dangerous patterns, such as anything that would move credentials into product code, before the merge completes. That scanner fails closed: if it cannot run, the merge does not proceed. On merge, Vercel builds and deploys the product from its default branch. Releases are first-class records: completed features are composed into a versioned release with its changelog, an acceptance gate, and a recorded outcome — improved, neutral or regressed — so “what is running in production” is a queryable ledger rather than a guess. Platform-level change ships the same way: the shell package is published as a semantic version whose release record states what it brings and the machine-readable migration steps a product must take to adopt it, and the sanctioned upgrade path takes those steps as an ordinary pull request through the same gates.
[07 / FAILURE]
The platform's failure handling assumes failure is normal and optimises for containment and evidence. Build-pipeline runs have explicit terminal states — completed, failed, cancelled — and a failed run stays failed and visible with its error attached; it is not retried into looking like a success. When a production deploy fails, a self-heal path attempts a repair, with a capped number of attempts before the failure is escalated to critical status — it cannot loop indefinitely. Platform operations sit behind circuit breakers, so a failing dependency is backed off rather than hammered. Scheduled recovery sweeps find work wedged in non-terminal states and either resume it or fail it explicitly, because a job stuck forever is worse than a job that failed honestly. Runtime errors from every product in the fleet report to a central, authenticated and rate-limited error service; they land in a project-scoped error store that the dashboard renders. Where money is concerned the posture is fail-closed: before billable work starts for a customer organisation, a pre-flight spend check asks the billing service, and if that check cannot be completed, the work does not start. AI usage records the billing service does not accept are held and replayed, so a billing outage delays a charge rather than silently dropping or doubling it, and a record that still cannot be delivered raises an alert.
[08 / API]
Other systems reach the platform through a REST API. Authentication is by API keys the operator creates per project: the raw key is shown once at creation and only a one-way hash of it is stored. Each key carries an explicit list of scopes and can be given an expiry and an IP allowlist; its last use is recorded, and it can be revoked. Requests are rate-limited, with separate budgets for different kinds of traffic, and the limits hold across serverless instances and cold starts instead of quietly resetting per instance. The API surface covers the records this page has been describing — features, runs, releases and more — so an external system can read the same ledger the dashboard renders.
[09 / MCP]
MCP — the Model Context Protocol, the open standard by which AI assistants call external tools — is a first-class access path: the platform runs its own MCP server. Access is authorised with OAuth: a user grants access through a consent flow, and tokens can be revoked — there is no shared key, and each session acts as the specific user who granted it, inside an organisation that user belongs to; a membership revoked later stops being honoured immediately. If permission resolution fails for any reason, the surface denies rather than guesses. What the MCP surface provides is orchestration of the product layer — enumerating products, reading their state, writing feature specs, firing builds, asking mediated questions of a codebase. What it is not: it is not raw codebase access, and it is not tied to one vendor's client — anything that speaks MCP can connect and gets exactly the rights the granting user holds, narrowed by their permission profile.
[10 / CONNECTIONS]
Products reach third-party APIs through the platform's connections layer — a broker that holds the credentials so that product code never does. A pull request that would bring credentials into product code, or call a connection its feature was not authorised to use, is rejected — the rule is enforced at review time, not requested politely, and the check fails closed if it cannot run. OAuth tokens live in a dedicated vault, held separately per product, per end user and per provider, so an end user who connects their own account gets their own vaulted token rather than sharing the operator's, and OAuth applications are registered once at the platform and inherited by products instead of every product managing its own developer-portal registrations. Every call through the layer is logged, which makes integration health a measurable fact rather than a mood: the health score is computed deterministically from those logs, a connection nobody has called is treated as unproven rather than healthy — absence of evidence is never rewarded — and a product with an outstanding finding cannot score a clean pass. The connection model itself — the store, bring-your-own-key, OAuth handling, custom APIs — is the connections page's subject; this chapter is about custody and enforcement.
[11 / EVIDENCE]
Concretely, what is written down: an audit log records administrative and security-relevant actions — who acted, what they did, to which resource and from where — and entries attributed to a product are mirrored into that product's own activity view through the same pipeline. The build pipeline keeps its own evidence — every agent job, and the full agent conversation transcript for each feature — so how a change was arrived at stays recoverable, not just what changed. Connection calls are logged twice over, deliberately: a metadata log, and alongside it a short-lived detail record holding the request and response themselves, because a failure you cannot see the wire for is a failure someone has to guess at. Credentials never enter either, and the detail record is hard-deleted within days while the metadata log outlives it. Runtime errors store the message, stack trace, route and severity. Retention is enforced by scheduled sweeps that prune agent logs, audit logs, pipeline data, connection call logs and scan uploads on defined windows.
[12 / EXIT]
Leaving is a designed path, because an exit that only works through a support conversation is not an exit. A data-export request assembles the requester's records as structured JSON and writes them to the private blob store, then delivers a link that resolves only through a route checking that the requester owns the export, and only for a limited window. Deletion is likewise a first-class request: it is scheduled with a grace window, executed table by table, written to the audit log, and confirmed by email. Beyond the compliance surface, nothing is held in a proprietary format: a product's code is an ordinary Git repository on GitHub, its database is ordinary Postgres, and the REST API can enumerate the product's records — features, runs, releases — with a scoped key, so a scripted export of the operational history needs no one's permission but the operator's own.
[02 / TENANCY]
Your product's application data is physically separate, not logically partitioned. Each product built on the platform gets its own Postgres database, in its own Neon project, reached with its own credentials — not a shared database with a tenant column, and not a schema per tenant. A defect in one product's queries cannot read another product's rows, because there is no connection over which it could reach them. Inside a product, organisations are separated by the database itself. Real organisation-scoped row-level security policies — Postgres rules that filter every row a query can read or write — are applied to the schema at build time; the build fails if any table that requires isolation is missing a policy; and the application is provisioned on a database role that cannot switch those policies off. The behaviour that follows is the one you want when something goes wrong: a query that arrives without organisation context returns nothing at all, rather than everything. An automated check then watches every live product to confirm its application still cannot bypass those policies, raising a critical alert the moment that changes.
The platform's own web application is held to the same rule. The control plane keeps its operational records — which organisations exist, which products they own, build history, support threads — together with the records its platform features keep for each product. The web application connects to that database on a restricted role that cannot bypass row-level security, and each request that acts for an organisation runs inside that organisation's context, so every statement on that role is filtered by Postgres itself. Every table the platform defines carries a recorded classification, and the test suite fails if a new table arrives without one; every table classified as tenant-owned carries a real per-organisation or per-product policy. A code path that loses its organisation context reads nothing: it fails closed rather than seeing another customer's rows.
[13 / SELF-AUDIT]
Every product on the platform is audited against fixed registries of controls, written down in advance — each control naming what to check and where to look for it — so two runs of the same audit ask the same questions of the same code. Thirteen quality domains are scored per product: security, compliance, accessibility, performance, mobile, SEO, error resilience, dependencies, supply chain, platform alignment, database, API integration health, and billing and metering. Across the eleven domains that run against a registry there are 289 named controls; the security registry alone is thirty of them, and the compliance registry forty-six. Two of the thirteen involve no model at all. The database audit connects to the product's own live database and verifies its tenant isolation directly from the Postgres catalogue. The integration-health audit is computed the same way, from the connection call log rather than from an impression. A check that could not be performed is recorded as a finding, never as a pass — it is written down as its own result rather than rounded up to green. Accessibility runs a mechanical lint pass first, its rules mapped to named controls, so its auditor spends its judgement on what a linter cannot settle. The other eleven domains each get a purpose-built auditor: its own registry, its own instructions, and a toolset that cannot write. The grade is arithmetic the server performs from the control statuses that come back — the score an auditor volunteers about its own work is never read. Findings carry file and line, they are attributed to the control they failed, and they stay in view until the code changes. Which controls those are, in detail, is the security page's subject.
Two independent checks keep it that way. The test suite counts every statement that could reach tenant data without an organisation context, and a single new one fails it. Monitoring at run time records any statement that actually runs outside an organisation context, and reports a heartbeat so that a quiet monitor can be told apart from a dead one.
Your product's data Its own Postgres database, its own Neon project, its own credentials. No product holds a connection to another product's database, so there is no path to leak across.
Organisations inside your product Real row-level security applied at build time, on a connection that cannot switch it off — a query with no organisation context returns nothing, not everything. Watched for any slip.
The platform's own database Operational records and the records platform features keep for each product. The web application connects on a role that cannot bypass row-level security and runs each organisation's requests in that organisation's context.