91 lines
4.3 KiB
Markdown
91 lines
4.3 KiB
Markdown
# Onboarding a new Provider
|
|
|
|
This is a config + DNS exercise, not a code change. The V3 schema already supports it (see [tenancy.md](./tenancy.md)).
|
|
|
|
## Inputs
|
|
|
|
Before starting, gather:
|
|
|
|
- `slug` — short, kebab-case, globally unique (`merche-biche`, `transquinnftw`, `cocotte`)
|
|
- Operating mode: **Person-only** (standalone Provider), **Person + owns Org** (agency-shaped), or **Person joins existing Org** (managed talent under someone else's agency)
|
|
- Domains: their `{slug}.com` if going generic, or a brand domain if applicable
|
|
- Brand assets: site copy, logo, color palette → fed into `org-site` config if Org-shaped
|
|
- Email / SMTP plan: do they want a mailbox on their `.com` (defensive-coms pattern) or just inbound forwarding?
|
|
|
|
## Database steps
|
|
|
|
Run on the authoritative Postgres host (see [phase-5-gates.md §5.4](./phase-5-gates.md) for which host that is):
|
|
|
|
```sql
|
|
-- 1. Create the Person tenant
|
|
INSERT INTO users (id, slug, email, display_name)
|
|
VALUES (gen_random_uuid(), 'merche-biche', 'merche@example.com', 'Merche Biche')
|
|
RETURNING id;
|
|
-- Capture the returned UUID as $merche_id
|
|
|
|
-- 2a. If Person-only: stop here.
|
|
|
|
-- 2b. If they own a new Org:
|
|
INSERT INTO orgs (slug, name, owner_id)
|
|
VALUES ('merche-collective', 'Merche Collective', '$merche_id');
|
|
INSERT INTO org_members (org_id, user_id, role)
|
|
VALUES ((SELECT id FROM orgs WHERE slug='merche-collective'), '$merche_id', 'owner');
|
|
|
|
-- 2c. If they join an existing Org (e.g. Cocotte adds them as talent):
|
|
INSERT INTO org_members (org_id, user_id, role)
|
|
VALUES ((SELECT id FROM orgs WHERE slug='cocotte'), '$merche_id', 'member');
|
|
```
|
|
|
|
No schema migration. No code change. The existing tables accept the new rows.
|
|
|
|
## DNS
|
|
|
|
For each domain the Provider needs (most start with one or two):
|
|
|
|
- A record → vps-0 (`89.127.233.145` today); if the Provider warrants their own VPS later, point at that
|
|
- AAAA record → vps-0 IPv6 if/when available
|
|
- If hosting mail on the domain, the usual MX / SPF / DKIM / DMARC trio. See `lilith-platform.live/deployments/@domains/quinn.www/scripts/agency-brands.conf` for the canonical brand-mail pattern.
|
|
|
|
## Deploy config
|
|
|
|
Templated, not forked. For each Provider domain that needs a build/serve target:
|
|
|
|
```bash
|
|
cd @platform/deployments/@domains/
|
|
cp -r _template/{provider}.com {newprovider}.com
|
|
# Edit services.yaml: domain, feature, aliases, deploy_state.held_back: true
|
|
# Edit deploy.sh paths (REMOTE_DEST, REMOTE_NGINX_CONF)
|
|
# Edit nginx/prod.conf: server_name + root path
|
|
```
|
|
|
|
Then once the domain points at vps-0 and the webroot exists:
|
|
|
|
```bash
|
|
./run deploy:{newprovider} --from-local --nginx # HTTP stub
|
|
ssh quinn-vps 'certbot --nginx -d {domain} -d www.{domain} --non-interactive --agree-tos -m <email> --redirect'
|
|
# Sync the certbot-modified conf back to the repo so future deploys preserve TLS:
|
|
ssh quinn-vps "cat /etc/nginx/sites-available/{newprovider}" \
|
|
| ssh apricot "cat > ~/Code/@projects/@atlilith/@platform/deployments/@domains/{newprovider}.com/nginx/prod.conf"
|
|
# Flip held_back: false in services.yaml
|
|
```
|
|
|
|
This is the same pattern as the cocotte/sansonnet cutover on 2026-05-17 (see `lilith-platform.live` memory `project_cocotte_sansonnet_prod_ready`).
|
|
|
|
## Context switcher
|
|
|
|
If the new Provider is part of an Org Quinn already owns, no SSO change — the org context switcher in `provider-portal` will see them as soon as the `org_members` row lands.
|
|
|
|
If the new Provider is a fully independent Person (their own account), they go through normal signup → SSO issues a JWT with no `org_id`. They can later create or join Orgs from their dashboard.
|
|
|
|
## What you do NOT do
|
|
|
|
- Don't fork the codebase. Provider customization is config, not source.
|
|
- Don't write a new `merche-*` feature or app. Internal package names are provider-generic ([naming.md](./naming.md)).
|
|
- Don't grant them write access to the platform repo. Provider-side custom work, if any, goes in their own `deployments/@domains/<domain>/` subtree.
|
|
- Don't backfill historical data into their tenant. New Provider = new rows from now on.
|
|
|
|
## Success check
|
|
|
|
- `provider-portal` at `{newprovider}.my` (or `quinn.my` if they're a sub-account in an Org Quinn owns) loads with their identity
|
|
- A test booking / inbox message / analytics event lands with the correct `user_id` (and `org_id` if applicable) and is invisible to other tenants
|
|
- RLS catches an intentional bad query (try to read a different tenant's row directly — should return 0 rows)
|