Chapter 6 · Foundations
What you are building
Meridian specified precisely enough to implement: the actors, the objects, the money flow, and the five things it deliberately does not do.
7 min read
The example carried through all 150 chapters. It is specified here in enough detail that every later decision has something to be right or wrong about.
The product in one paragraph
Meridian is a platform where independent software vendors sell seat-based licences to business customers. A vendor signs up, completes identity verification, publishes a catalogue of plans, and invites or is found by business customers who buy seats. Meridian charges 5% of every transaction. Vendors are paid directly by their own customers; Meridian never holds a vendor's money.
The actors
| Actor | What they are | Where they live |
|---|---|---|
| Vendor | A tenant. A company selling software licences. | tenants |
| Vendor staff | A person with a role at a vendor. May work with several vendors. | users + memberships |
| Customer | A business buying seats from one vendor. Belongs to that vendor. | customers, scoped by tenant_id |
| Customer admin | A person at the customer who manages their seats. | A separate identity surface |
| Platform staff | Meridian's own support and finance. | platform_users — deliberately a different table |
That last row matters and is easy to get wrong. Platform staff are not members of a tenant with elevated permissions; they are a distinct kind of identity, so that no role granted inside a tenant can ever escalate into platform access. Chapter 108 depends on this separation existing from the start.
The objects
tenant (vendor) ├── members staff with roles: owner, admin, billing, support ├── connect account the vendor's payment account, verification state ├── plans what they sell: name, price, billing interval, seat model ├── customers the businesses buying from them │ └── subscriptions a customer on a plan, with a seat quantity │ ├── seats who occupies each one │ └── invoices what was charged and when └── entitlements what a subscription actually unlocks
Every object below tenant carries tenant_id. Not only the top-level ones — seats and invoices too, even though they are reachable through a parent that already has it. Chapter 25 explains why the redundant column is correct here rather than sloppy, and it is one of the few places this series argues for denormalisation.
The money flow
Note the order of the last two steps. The entitlement is granted when the webhook confirms payment, not when the checkout form is submitted. That ordering is the subject of Section 9, and getting it the other way round is the most common way a platform gives away free access.
The requirements, stated testably
A specification that cannot fail a test is a brochure. These are the ones the series is built to satisfy:
| # | Requirement | How it is verified |
|---|---|---|
| R1 | No request returns a row belonging to another tenant | An isolation test that queries every table as tenant A and asserts zero tenant-B rows (Ch. 38) |
| R2 | A user belonging to two vendors sees exactly one vendor's data per session | Session fixture with a dual-member user (Ch. 48) |
| R3 | Meridian's balance never holds vendor funds | Every charge asserts stripeAccount is set (Ch. 66) |
| R4 | A duplicate webhook never grants a seat twice | Replay the same event id and assert one grant (Ch. 116) |
| R5 | A seat change mid-cycle bills the correct prorated amount | Fixture clock, assert the invoice total (Ch. 84) |
| R6 | Entitlements match the paid subscription state | A drift check that compares both systems (Ch. 96) |
| R7 | A deleted tenant's rows are actually gone | Deletion test that greps every table (Ch. 148) |
These seven are what "done" means. Each one has a chapter that implements it and a test that fails without it.
What Meridian deliberately does not do
Naming the exclusions keeps the series finite and honest.
- No marketplace discovery. Customers arrive at a vendor by invitation or link. A searchable cross-vendor catalogue is a different product with the opposite isolation requirement — see Chapter 4.
- No usage-based billing. Seats only. Metered billing is a worthwhile subject and a different one; mixing it in would double Section 7 and teach neither well.
- No white-labelled custom domains per tenant. Subdomains, yes. Full custom domains with per-tenant certificates are an infrastructure chapter pretending to be a product feature.
- No multi-currency at launch. One currency keeps the proration arithmetic legible. Chapter 150 says what changes when you add a second.
- No in-product messaging between vendors and customers. It would be the largest source of cross-tenant leaks in the product and contributes nothing to the subject.
A worked example earns its keep by being small enough to hold in your head and real enough to break. Every exclusion above was cut because it added surface without adding a lesson.
The stack
Stated once, so later chapters can be concrete rather than hedged:
| Layer | Choice | Why it matters here |
|---|---|---|
| Database | Postgres | Row-level security is the second isolation layer |
| Access | A typed query builder | Lets the tenant scope be enforced in the type system |
| Runtime | TypeScript on the server | One language across the request path and the webhook handlers |
| Payments | Stripe Connect | Shape B from Chapter 5 |
| Tests | A real Postgres instance, not a mock | RLS cannot be tested against a fake |
That last row is not a preference. Isolation policies live in the database, so a test suite that mocks the database tests nothing about isolation.
Key points
- Meridian: vendors sell seat-based licences to business customers; the platform takes 5% and never holds vendor funds.
- Platform staff are a separate identity table, not privileged tenant members, so no in-tenant role can escalate to platform access.
- Every object below the tenant carries
tenant_id, including the ones reachable through a parent that already has it. - Entitlements are granted on the payment webhook, not on form submission. Reversing that order is how platforms give away free access.
- Seven testable requirements define "done": isolation, dual membership, no platform-held funds, webhook idempotency, correct proration, no entitlement drift, real deletion.
- Five deliberate exclusions: marketplace discovery, usage billing, custom domains, multi-currency, in-product messaging.
- Tests run against real Postgres, because row-level security cannot be verified against a mock.
Next: The smallest version worth shipping — what to build in the first week, and what to refuse to build.