-- mockshot-seed.sql — reversible sample data at deployment scale, for -- screenshots and demos (first used for the README operator-overview shot). -- -- Identity: the database generates every ID (Decision 30), and this seed -- addresses the rows it owns by `key` (grammar `^[a-z][a-z0-9_]*$`). -- Organizations are keyed `mockshot_0001` .. `mockshot_2842` (one per mock -- member) and `mockshot_t0001` .. `mockshot_t0038` (the teams). Each mock -- person's user row carries the external reference `mockshot:`, so a -- person is found by its OIDC subject and never by its email address, its -- display name, or a marker column. The catalog rows are keyed -- `mockshot_hosting` (entitlement set), `mockshot_starter_hosting` and its -- four siblings (products), and `monthly` / `yearly` within each product -- (prices). Everything else — pools, workspaces, assignments, accounts, -- subscriptions, items, invoices, payments, grants, provisions — is reached -- through its organization or its catalog parent, so it needs no key of its -- own. mockshot-nuke.sql reverses the seed from exactly those three -- predicates: the `mockshot_` organization key prefix, the `mockshot:` -- subject prefix, and the catalog keys. -- -- Re-running the seed inserts nothing: each keyed insert conflicts on its -- key, and each keyless insert is guarded by a NOT EXISTS on the parent that -- already carries the child. -- -- Run against a test stack (port/creds from bootstrap-stack.sh output): -- psql -h localhost -p $POSTGRES_PORT -U member_console -d member_console \ -- -v ON_ERROR_STOP=1 -f test/mockshot-seed.sql -- Reverse with mockshot-nuke.sql the same way. Nuke BEFORE restarting the -- app: the boot-time entitlement materializer must never reconcile the mock -- provisions into real pool entitlements. -- -- Shape follows the app's own mechanics: signup mints a personal org per -- member (backdated over ~19 months of joins) with its default pool, -- workspace, and default-plan grant; team orgs are created by long-standing -- members; upgrading to a paid subscription ends the free default's delivery -- (ladder exclusion); subscriptions carry priced items so monthly recurring -- money is real. -- -- Targets (existing → after): People 5→2,847 (~148 joined in 30 days), -- Team orgs 0→38, Subscriptions 0→214 (205 active + 9 trialing, MRR -- $2,160.00), Open invoices 0→9 ($372.00 outstanding), Delivering operator -- grants →19 with ≈2,669 signup defaults in the caption, Catalog -- products 10→15. BEGIN; -- 2,842 users, joined over ~19 months (i*5h + jitter). The subject carries -- the organization key the member's personal org gets below, which is how -- every later statement recovers the member's index without an ID of its own. INSERT INTO core.users (oidc_subject, created_at) SELECT 'mockshot:mockshot_' || lpad(i::text, 4, '0'), now() - (i * 5 || ' hours')::interval - (random() * interval '55 minutes') FROM generate_series(1, 2842) AS i ON CONFLICT (oidc_subject) DO NOTHING; -- The member index, its organization key, and its join date, read back from -- the rows themselves so a second run sees exactly what the first created. -- `i` orders members by join date: member 1 is the newest, 2842 the oldest. CREATE TEMP TABLE mock_members ON COMMIT DROP AS SELECT split_part(u.oidc_subject, '_', 2)::int AS i, 'mockshot_' || split_part(u.oidc_subject, '_', 2) AS org_key, u.user_id, u.created_at AS joined_at FROM core.users u WHERE u.oidc_subject LIKE 'mockshot:%'; INSERT INTO core.persons (user_id, display_name, primary_email, primary_email_verified, created_at) SELECT m.user_id, -- Stride 7 is coprime to 20, so consecutive members vary in both names -- and (first, last) pairs stay unique across lcm(21,20)=420 indices. (ARRAY['Ada','Basil','Corin','Dara','Emil','Freya','Gus','Hana','Iris','Jonas','Kira', 'Lior','Mara','Nils','Odile','Piotr','Quinn','Rosa','Sana','Theo','Uma'])[1 + (m.i % 21)] || ' ' || (ARRAY['Aldana','Brook','Calder','Dove','Ellis','Fen','Grange','Hale','Ingram','Jarvis', 'Keel','Lark','Marsh','North','Orr','Penn','Quill','Reyes','Sorrel','Vale'])[1 + ((m.i * 7) % 20)], -- Realistic data, never a predicate: nothing in this file or in the nuke -- selects on an address. 'mock-p' || m.i || '@example.test', true, m.joined_at + interval '1 minute' FROM mock_members m WHERE NOT EXISTS (SELECT 1 FROM core.persons p WHERE p.user_id = m.user_id); -- One personal org per mock person, keyed with the member's own key and timed -- the way signup mints them (two minutes after the person row). INSERT INTO core.organizations (name, key, org_type, owner_person_id, created_at) SELECT p.display_name || '''s Organization', m.org_key, 'personal', p.person_id, m.joined_at + interval '2 minutes' FROM mock_members m JOIN core.persons p ON p.user_id = m.user_id ON CONFLICT (key) DO NOTHING; -- The team org type (design taxonomy: personal / team / enterprise — -- design/organization/architecture.md §2). Org types are vocabulary whose -- primary key is the type itself, so there is nothing to key; the nuke -- removes it only if no organization is left using it. INSERT INTO core.org_types (org_type, display_name, description, is_active) VALUES ('team', 'Team', 'An organization more than one person works in.', true) ON CONFLICT (org_type) DO NOTHING; -- 38 team orgs created by long-standing members (the oldest signups), each -- 10-70 days after its owner joined. INSERT INTO core.organizations (name, key, org_type, owner_person_id, created_at) SELECT CASE WHEN k <= 4 THEN (ARRAY['Cedar Print Collective','Prairie Signal Cooperative', 'Harbor Works Guild','Juniper Commons'])[k] ELSE (ARRAY['Cedar','Prairie','Harbor','Juniper','Granite','Meadow','Larkspur','Alder'])[1 + ((k - 5) % 8)] || ' ' || (ARRAY['Collective','Cooperative','Commons','Works','Guild','Studio','Assembly','Exchange'])[1 + ((k - 5) / 8)] END, 'mockshot_t' || lpad(k::text, 4, '0'), 'team', p.person_id, m.joined_at + interval '10 days' + (random() * interval '60 days') FROM generate_series(1, 38) AS k JOIN mock_members m ON m.i = 2843 - k JOIN core.persons p ON p.user_id = m.user_id ON CONFLICT (key) DO NOTHING; -- Every org gets the default pool and workspace signup mints for it -- (provisioning.CreateWorkspaceWithPrimaryAssignment), so mock provisions -- land in the org's own pool instead of borrowing somebody else's. The pool -- is the one row the system keys by design, `default`; the workspace carries -- no key, and the assignment is the pair, so neither does it. INSERT INTO core.resource_pools (org_id, name, key, pool_type, is_auto_managed, created_at) SELECT o.org_id, 'Default', 'default', 'default', TRUE, o.created_at FROM core.organizations o WHERE o.key LIKE 'mockshot\_%' ON CONFLICT (org_id, key) DO NOTHING; INSERT INTO core.workspaces (org_id, name, created_at) SELECT o.org_id, 'Default', o.created_at FROM core.organizations o WHERE o.key LIKE 'mockshot\_%' AND NOT EXISTS (SELECT 1 FROM core.workspaces w WHERE w.org_id = o.org_id); INSERT INTO core.pool_assignments (pool_id, workspace_id, is_primary, created_at) SELECT rp.pool_id, w.workspace_id, TRUE, o.created_at FROM core.organizations o JOIN core.resource_pools rp ON rp.org_id = o.org_id AND rp.key = 'default' JOIN core.workspaces w ON w.org_id = o.org_id WHERE o.key LIKE 'mockshot\_%' AND NOT EXISTS ( SELECT 1 FROM core.pool_assignments pa WHERE pa.pool_id = rp.pool_id AND pa.workspace_id = w.workspace_id ); -- Billing accounts: every team + the 176 most recent personal orgs. INSERT INTO core.accounts (org_id, name, created_at) SELECT o.org_id, o.name || ' billing', o.created_at FROM ( SELECT org_id, name, created_at FROM core.organizations WHERE key LIKE 'mockshot\_%' ORDER BY (key LIKE 'mockshot\_t%') DESC, created_at DESC LIMIT 214 ) o WHERE NOT EXISTS (SELECT 1 FROM core.accounts a WHERE a.org_id = o.org_id); -- 214 live subscriptions: teams first (all active), then individuals; -- the last 9 are trialing. INSERT INTO core.subscriptions (billing_account_id, status, current_period_start, current_period_end) SELECT billing_account_id, CASE WHEN rn <= 205 THEN 'active' ELSE 'trialing' END, date_trunc('month', now()), date_trunc('month', now()) + interval '1 month' FROM ( SELECT a.billing_account_id, row_number() OVER (ORDER BY (o.key LIKE 'mockshot\_t%') DESC, a.billing_account_id) AS rn FROM core.accounts a JOIN core.organizations o ON o.org_id = a.org_id WHERE o.key LIKE 'mockshot\_%' AND NOT EXISTS (SELECT 1 FROM core.subscriptions s WHERE s.billing_account_id = a.billing_account_id) ) accts; -- The entitlement the mock plans confer. One set, one numeric rule, so the -- mock provisions carry a real entitlement set instead of borrowing one. INSERT INTO core.entitlement_sets (name, key, description, is_active) VALUES ('Hosting baseline', 'mockshot_hosting', 'Entitlements every hosting plan confers.', TRUE) ON CONFLICT (key) DO NOTHING; INSERT INTO core.entitlement_set_rules (set_id, rule_type, resource_key, resource_value, resource_per_unit, stacking_policy, description) SELECT es.set_id, 'limit', 'fedwiki_sites', 5, FALSE, 'additive', 'Up to 5 sites.' FROM core.entitlement_sets es WHERE es.key = 'mockshot_hosting' AND NOT EXISTS ( SELECT 1 FROM core.entitlement_set_rules r WHERE r.set_id = es.set_id AND r.resource_key = 'fedwiki_sites' ); -- Five mock products with deployment-plausible names (before the prices, -- items, and grants that reference them). Published so grant events print -- them. INSERT INTO core.products (name, key, description, display_category, entitlement_set_id, lifecycle_status) SELECT v.name, v.key, v.description, v.display_category, CASE WHEN v.confers THEN es.set_id END, 'published' FROM (VALUES ('Starter Hosting', 'mockshot_starter_hosting', 'Default plan for new members', NULL, TRUE), ('Standard Hosting', 'mockshot_standard_hosting', 'Paid hosting tier', NULL, TRUE), ('Sustainer Hosting', 'mockshot_sustainer_hosting', 'Supporter hosting tier', NULL, TRUE), ('Custom Domain', 'mockshot_custom_domain', 'Serve a site on your own domain', 'addon', FALSE), ('Storage Boost', 'mockshot_storage_boost', 'Additional media storage', 'addon', FALSE) ) AS v(name, key, description, display_category, confers) CROSS JOIN (SELECT set_id FROM core.entitlement_sets WHERE key = 'mockshot_hosting') es ON CONFLICT (key) DO NOTHING; -- Prices for the paid tiers, keyed within their product: Standard $9/mo -- (default), Sustainer $18/mo (default) with an $180/yr alternative — the -- yearly price exercises the one-twelfth normalization on the overview's -- money headline. INSERT INTO core.prices (product_id, key, currency, unit_amount, recurring_interval, is_default) SELECT p.product_id, v.key, 'usd', v.unit_amount, v.recurring_interval, v.is_default FROM (VALUES ('mockshot_standard_hosting', 'monthly', 900, 'month', TRUE), ('mockshot_sustainer_hosting', 'monthly', 1800, 'month', TRUE), ('mockshot_sustainer_hosting', 'yearly', 18000, 'year', FALSE) ) AS v(product_key, key, unit_amount, recurring_interval, is_default) JOIN core.products p ON p.key = v.product_key ON CONFLICT (product_id, key) DO NOTHING; -- Subscription items: teams ride Sustainer (every 4th on the yearly price), -- individuals ride Standard monthly. The team's number comes out of its own -- key, the only arithmetic the rest of the file needs. INSERT INTO core.subscription_items (subscription_id, product_id, price_id, quantity) SELECT s.subscription_id, pr.product_id, pr.price_id, 1 FROM core.subscriptions s JOIN core.accounts a ON a.billing_account_id = s.billing_account_id JOIN core.organizations o ON o.org_id = a.org_id JOIN LATERAL ( SELECT p.price_id, p.product_id FROM core.prices p JOIN core.products pd ON pd.product_id = p.product_id WHERE CASE WHEN o.key LIKE 'mockshot\_t%' THEN pd.key = 'mockshot_sustainer_hosting' AND p.key = CASE WHEN split_part(o.key, '_t', 2)::int % 4 = 0 THEN 'yearly' ELSE 'monthly' END ELSE pd.key = 'mockshot_standard_hosting' AND p.key = 'monthly' END LIMIT 1 ) pr ON true WHERE o.key LIKE 'mockshot\_%' AND NOT EXISTS ( SELECT 1 FROM core.subscription_items si WHERE si.subscription_id = s.subscription_id ); -- The signup default-plan grant for every mock org, stamped minutes after -- the org itself (system-authored: reason 'default' ⟺ no granting person, -- per chk_grants_default_iff_system_authored). INSERT INTO core.grants (product_id, granted_to_org_id, granted_by_person_id, grant_reason, description, valid_from, created_at) SELECT pd.product_id, o.org_id, NULL, 'default', 'Plan issued at signup', o.created_at + interval '2 minutes', o.created_at + interval '2 minutes' FROM core.organizations o CROSS JOIN (SELECT product_id FROM core.products WHERE key = 'mockshot_starter_hosting') pd WHERE o.key LIKE 'mockshot\_%' AND NOT EXISTS ( SELECT 1 FROM core.grants g WHERE g.granted_to_org_id = o.org_id AND g.grant_reason = 'default' ); -- Each default grant delivers in its own org's pool. INSERT INTO core.pool_provisions (pool_id, grant_id, status, entitlement_set_id, product_id, activated_at) SELECT rp.pool_id, g.grant_id, 'active', es.set_id, g.product_id, g.created_at FROM core.grants g JOIN core.organizations o ON o.org_id = g.granted_to_org_id AND o.key LIKE 'mockshot\_%' JOIN core.resource_pools rp ON rp.org_id = o.org_id AND rp.key = 'default' CROSS JOIN (SELECT set_id FROM core.entitlement_sets WHERE key = 'mockshot_hosting') es WHERE g.grant_reason = 'default' AND NOT EXISTS (SELECT 1 FROM core.pool_provisions pp WHERE pp.grant_id = g.grant_id); -- Auto-provisioning: every live mock subscription delivers its plan, so a -- paying org is never provision-less after its free default ends below. INSERT INTO core.pool_provisions (pool_id, subscription_id, status, entitlement_set_id, product_id) SELECT rp.pool_id, s.subscription_id, 'active', es.set_id, si.product_id FROM core.subscriptions s JOIN core.accounts a ON a.billing_account_id = s.billing_account_id JOIN core.organizations o ON o.org_id = a.org_id AND o.key LIKE 'mockshot\_%' JOIN core.resource_pools rp ON rp.org_id = o.org_id AND rp.key = 'default' JOIN core.subscription_items si ON si.subscription_id = s.subscription_id CROSS JOIN (SELECT set_id FROM core.entitlement_sets WHERE key = 'mockshot_hosting') es WHERE NOT EXISTS ( SELECT 1 FROM core.pool_provisions pp WHERE pp.subscription_id = s.subscription_id AND pp.product_id = si.product_id ); -- Eighteen operator-issued extras (upgrades, promos) over the last ~3 days, -- on the 18 newest members' orgs. The operator is a real console person, -- never one of the mock members: mock members are exactly the users whose -- subject carries the mockshot prefix. INSERT INTO core.grants (product_id, granted_to_org_id, granted_by_person_id, grant_reason, description, valid_from, created_at) SELECT pd.product_id, o.org_id, op.person_id, CASE WHEN m.i % 2 = 0 THEN 'manual' ELSE 'promotional' END, 'Issued by an operator', now() - (m.i * interval '4 hours') - interval '20 minutes', now() - (m.i * interval '4 hours') - interval '20 minutes' FROM mock_members m JOIN core.organizations o ON o.key = m.org_key JOIN core.products pd ON pd.key = CASE WHEN m.i % 4 = 1 THEN 'mockshot_standard_hosting' WHEN m.i % 4 = 2 THEN 'mockshot_sustainer_hosting' WHEN m.i % 4 = 3 THEN 'mockshot_custom_domain' ELSE 'mockshot_storage_boost' END CROSS JOIN LATERAL ( SELECT p.person_id FROM core.persons p JOIN core.users u ON u.user_id = p.user_id WHERE u.oidc_subject NOT LIKE 'mockshot:%' ORDER BY (p.display_name ILIKE 'alice%') DESC, p.created_at LIMIT 1 ) op WHERE m.i <= 18 AND NOT EXISTS ( SELECT 1 FROM core.grants g WHERE g.granted_to_org_id = o.org_id AND g.grant_reason <> 'default' ); INSERT INTO core.pool_provisions (pool_id, grant_id, status, entitlement_set_id, product_id, activated_at) SELECT rp.pool_id, g.grant_id, 'active', es.set_id, g.product_id, g.created_at FROM core.grants g JOIN core.organizations o ON o.org_id = g.granted_to_org_id AND o.key LIKE 'mockshot\_%' JOIN core.resource_pools rp ON rp.org_id = o.org_id AND rp.key = 'default' CROSS JOIN (SELECT set_id FROM core.entitlement_sets WHERE key = 'mockshot_hosting') es WHERE g.grant_reason <> 'default' AND NOT EXISTS (SELECT 1 FROM core.pool_provisions pp WHERE pp.grant_id = g.grant_id); -- Invoices, one per billing account across the first fifteen teams -- (invoice-numbers change, data model v16.2): the number is the platform's -- own, per billing account, a zero-padded four-digit decimal starting at -- 0001 — and since every account here holds exactly one seeded invoice, -- every one of the fifteen is numbered 0001. Each account's -- next_invoice_number moves to 2 below, past the number this seed assigned. -- -- 9 open invoices, on teams t0001..t0009, staggered over the last two days -- for the timeline. INSERT INTO core.invoices (billing_account_id, status, amount_due, amount_paid, currency, period_start, period_end, due_date, created_at, invoice_number) SELECT a.billing_account_id, 'open', (ARRAY[2400, 4800, 1200, 9600, 2400, 7200, 4800, 1200, 3600])[n], 0, 'usd', now() - interval '1 month', now(), now() + interval '14 days', now() - (n * interval '5 hours'), '0001' FROM generate_series(1, 9) AS n JOIN core.organizations o ON o.key = 'mockshot_t' || lpad(n::text, 4, '0') JOIN core.accounts a ON a.org_id = o.org_id WHERE NOT EXISTS ( SELECT 1 FROM core.invoices i WHERE i.billing_account_id = a.billing_account_id ); -- 6 recently paid invoices, on teams t0010..t0015, each settled by a -- succeeded payment two minutes after issue — the timeline's -- payment_received rows. INSERT INTO core.invoices (billing_account_id, status, amount_due, amount_paid, currency, period_start, period_end, due_date, paid_at, created_at, invoice_number) SELECT a.billing_account_id, 'paid', (ARRAY[2400, 4800, 2400, 9600, 4800, 2400])[n], (ARRAY[2400, 4800, 2400, 9600, 4800, 2400])[n], 'usd', now() - interval '1 month', now(), now() + interval '14 days', now() - (n * interval '11 hours') + interval '2 minutes', now() - (n * interval '11 hours'), '0001' FROM generate_series(1, 6) AS n JOIN core.organizations o ON o.key = 'mockshot_t' || lpad((n + 9)::text, 4, '0') JOIN core.accounts a ON a.org_id = o.org_id WHERE NOT EXISTS ( SELECT 1 FROM core.invoices i WHERE i.billing_account_id = a.billing_account_id ); INSERT INTO core.payments (invoice_id, billing_account_id, amount, currency, status, created_at) SELECT i.invoice_id, i.billing_account_id, i.amount_paid, 'usd', 'succeeded', i.created_at + interval '2 minutes' FROM core.invoices i JOIN core.accounts a ON a.billing_account_id = i.billing_account_id JOIN core.organizations o ON o.org_id = a.org_id AND o.key LIKE 'mockshot\_t%' WHERE i.status = 'paid' AND NOT EXISTS (SELECT 1 FROM core.payments pm WHERE pm.invoice_id = i.invoice_id); -- Each of the fifteen accounts just numbered one invoice 0001; its counter -- moves to 2. The `<> 2` guard makes a second run of this seed a no-op here -- as well as everywhere above. UPDATE core.accounts a SET next_invoice_number = 2 FROM core.organizations o WHERE o.org_id = a.org_id AND o.key LIKE 'mockshot\_t%' AND split_part(o.key, '_t', 2)::int <= 15 AND a.next_invoice_number <> 2; -- Upgrading ends the free default's delivery: the ladder table's exclusion -- admits one active provision per pool per ladder, so each subscribed org's -- default-grant provision ended when its paid subscription provisioned. The -- grant itself stays 'active' in the ledger — exactly the ledger-vs-delivery -- distinction the overview tile is built around. UPDATE core.pool_provisions pp SET status = 'ended', ended_at = pp.activated_at + interval '40 minutes' FROM core.grants g JOIN core.organizations o ON o.org_id = g.granted_to_org_id JOIN core.accounts a ON a.org_id = g.granted_to_org_id JOIN core.subscriptions s ON s.billing_account_id = a.billing_account_id WHERE pp.grant_id = g.grant_id AND g.grant_reason = 'default' AND o.key LIKE 'mockshot\_%' AND pp.status <> 'ended'; COMMIT; -- Post-seed tallies SELECT (SELECT count(*) FROM core.persons WHERE status = 'active') AS people, (SELECT count(*) FROM core.persons WHERE status = 'active' AND created_at > now() - interval '30 days') AS joined_30d, (SELECT count(*) FROM core.organizations o JOIN core.org_types t ON t.org_type = o.org_type WHERE o.status = 'active' AND o.org_type <> 'personal' AND NOT t.is_reserved) AS team_orgs, (SELECT count(*) FROM core.subscriptions WHERE status = 'active') AS active_subs, (SELECT count(*) FROM core.subscriptions WHERE status = 'trialing') AS trialing_subs, (SELECT sum(CASE p.recurring_interval WHEN 'month' THEN p.unit_amount * si.quantity WHEN 'year' THEN (p.unit_amount * si.quantity) / 12 ELSE 0 END) FROM core.subscriptions s JOIN core.subscription_items si ON si.subscription_id = s.subscription_id JOIN core.prices p ON p.price_id = si.price_id WHERE s.status = 'active') AS mrr_cents, (SELECT count(*) FROM core.invoices WHERE status = 'open') AS open_invoices, (SELECT count(DISTINCT g.grant_id) FROM core.grants g JOIN core.pool_provisions pp ON pp.grant_id = g.grant_id WHERE pp.status = 'active' AND g.grant_reason <> 'default') AS operator_grants, (SELECT count(DISTINCT g.grant_id) FROM core.grants g JOIN core.pool_provisions pp ON pp.grant_id = g.grant_id WHERE pp.status = 'active' AND g.grant_reason = 'default') AS default_grants;