15

I wanted to describe a multi-tenant app in plain English and get back a schema that respects tenancy at the database layer, not just in the app code where one forgotten where org_id = leaks everyone's data.

The naive prompt gave me tables with no foreign keys, text for everything, and zero row-level security. The version below forces real constraints, sane types, and an RLS policy per table, plus the up/down migration. It caught a missing unique constraint I would have shipped.

Does anyone gate the model from using text when a domain or enum is clearly correct?

THE PROMPT
You are a database engineer. From the requirements below, design a PostgreSQL schema for a multi-tenant app where every row belongs to an organization.

OUTPUT in this order:
1. An ERD as a mermaid `erDiagram`.
2. The DDL. Rules: every table has an `org_id` FK to organizations; use `uuid` PKs with `gen_random_uuid()`; use `timestamptz` not `timestamp`; use enums or CHECK constraints instead of free `text` for finite value sets; add NOT NULL and UNIQUE where the domain implies them; name every constraint explicitly.
3. Row-level security: `ENABLE ROW LEVEL SECURITY` on every tenant table plus a policy that restricts rows to the current org via `current_setting('app.current_org')`. No table with org data may be left without a policy.
4. Indexes: add an index for every FK and for every column you'd filter or sort on based on the requirements.
5. A forward migration and a matching reversible down migration.

CONSTRAINTS: no `text` where an enum/domain fits; no nullable FKs unless the relationship is genuinely optional (justify each). End with a checklist confirming every table has: a PK, org_id FK, RLS enabled, an RLS policy, and FK indexes.

REQUIREMENTS:
{REQUIREMENTS}
the `timestamptz not timestamp` line is such a small thing that has burned me across timezones more than once. adding it to my default schema prompt.mira_dev 2 months ago
add a comment

1 Answer

10

This is close to how I do it. The RLS-per-table checklist is the part that saves you, because the failure mode is silent, you don't notice the unprotected table until it's a breach. One thing I add: make it also emit a tiny SQL test that sets app.current_org to org A, inserts a row, switches to org B, and asserts the row is invisible. If the model can't make that pass, the policy is wrong.

THE PROMPT
Append a verification script: SET app.current_org to a test org, insert, switch orgs, and SELECT to prove the row is not visible cross-tenant. It must return zero rows for the other org.

Your Answer