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

ActorWhat they areWhere they live
VendorA tenant. A company selling software licences.tenants
Vendor staffA person with a role at a vendor. May work with several vendors.users + memberships
CustomerA business buying seats from one vendor. Belongs to that vendor.customers, scoped by tenant_id
Customer adminA person at the customer who manages their seats.A separate identity surface
Platform staffMeridian'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:

#RequirementHow it is verified
R1No request returns a row belonging to another tenantAn isolation test that queries every table as tenant A and asserts zero tenant-B rows (Ch. 38)
R2A user belonging to two vendors sees exactly one vendor's data per sessionSession fixture with a dual-member user (Ch. 48)
R3Meridian's balance never holds vendor fundsEvery charge asserts stripeAccount is set (Ch. 66)
R4A duplicate webhook never grants a seat twiceReplay the same event id and assert one grant (Ch. 116)
R5A seat change mid-cycle bills the correct prorated amountFixture clock, assert the invoice total (Ch. 84)
R6Entitlements match the paid subscription stateA drift check that compares both systems (Ch. 96)
R7A deleted tenant's rows are actually goneDeletion 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.

  1. 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.
  2. 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.
  3. 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.
  4. No multi-currency at launch. One currency keeps the proration arithmetic legible. Chapter 150 says what changes when you add a second.
  5. 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:

LayerChoiceWhy it matters here
DatabasePostgresRow-level security is the second isolation layer
AccessA typed query builderLets the tenant scope be enforced in the type system
RuntimeTypeScript on the serverOne language across the request path and the webhook handlers
PaymentsStripe ConnectShape B from Chapter 5
TestsA real Postgres instance, not a mockRLS 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.