Files
member-console/test/mockshot-seed.sql
T
cgalo5758 56e65b669c Read invoices back from Stripe and archive the change
Stripe delivers webhook events in the endpoint's API version. Under a
version later than the 2025-02-24.acacia stripe-go v81 pins, the
payload-shaped invoice handlers projected every invoice with no
subscription link and no lines, and every payment with no payment
intent or method.

- Invoice events read only the invoice id and run ReconcileInvoice,
  which reads the invoice, its lines and its payment intent's charges
  back at the pinned version and converges the core rows under a
  per-invoice lock, in any event order. A payment row is one attempt,
  keyed by its Stripe charge (Stripe store migration 00005).
- Two clocks: created_at is when the console wrote a row; invoices gain
  issued_at and payments paid_at (core migration 00022, backfilled),
  and every invoice and payment list, the activity feed and the
  composite's latest invoice read those.
- An hourly and at-boot invoice sweep reads back invoices never read,
  open invoices once a day, and invoices of dead-lettered events; the
  provider page states when it last finished. The first run is the
  repair of invoices projected so far.
- The API version skew is logged once per version, and
  payment_method.attached links payments recorded before it.
- A restricted key now needs Invoices read and Charges read.
- scripts/db-reader-role.sql maintains console_reader, a login role
  that reads the console's tables without personal data, documented in
  docs/database-management.md and checked against a golden column list
  by TestConsoleReaderColumns. The demo snapshot keeps owners and
  privileges so the role's checks hold on it.
- The demo seed and test fixtures carry issue and paid times.
  status/design-feedback.md gains four upstream notes, one written when
  #60 was closed.

OpenSpec change stripe-invoice-reconcile is archived and its deltas
synced into openspec/specs/. Follow-ups are #150, #151 and #152.
2026-09-28 18:42:29 -05:00

460 lines
23 KiB
PL/PgSQL

-- SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Commercial
-- SPDX-FileCopyrightText: 2025-2026 Christian Galo
-- 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:<org key>`, 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;
-- The rule goes in through core.commit_rule_change, the only write path to
-- core.entitlement_set_rules since migration 00018 revoked the DML. That
-- function asserts the exclusive materialization rendezvous, so this
-- transaction takes the advisory lock first. No pool carries the set yet, so
-- the act row it files enumerates nothing.
SELECT pg_advisory_xact_lock(hashtextextended('entitlement_materialization', 0));
SELECT core.commit_rule_change(
es.set_id, 'rule_added', NULL,
jsonb_build_object(
'rule_type', 'limit',
'resource_key', 'fedwiki_sites',
'resource_value', 5,
'resource_per_unit', FALSE,
'stacking_policy', 'additive',
'description', 'Up to 5 sites.'
),
'system', NULL, NULL, NULL, NULL, NOW(), 200)
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, issued over the last two days
-- for the timeline, which places an invoice at its issue time and a payment
-- when it succeeded (stripe-invoice-reconcile D17). created_at is left to
-- the database: it is when the row was written.
INSERT INTO core.invoices (billing_account_id, status, amount_due, amount_paid, currency,
period_start, period_end, due_date, issued_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, issued_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, paid_at)
SELECT i.invoice_id, i.billing_account_id, i.amount_paid, 'usd', 'succeeded',
i.paid_at
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;