Skip to main content

Overview

This runbook covers the manual process for provisioning a new broker tenant on PensionPortal.ai. Each broker firm operates as an isolated tenant — they can only access their own employers, pension schemes, and member data. Tenant provisioning is currently a manual admin operation. A self-service broker onboarding flow is planned but not yet implemented.
This runbook is intended for PensionPortal.ai engineering staff and SuperAdmin operators. Do not share this document with broker firms.

1. Tenant Model Overview

In PensionPortal.ai, a “tenant” maps to one broker firm. The data hierarchy under a tenant is:
Broker (tenant)
  └── Employers (companies the broker serves)
        └── Pension Schemes
              └── Members
                    └── Contributions, KFH records, compliance documents
Tenant isolation is enforced at the service layer via ActorContext — every query is filtered by brokerId. A broker user cannot read, write, or enumerate any data belonging to a different broker. See the Multi-Tenancy Architecture for the full isolation model.

2. Pre-Provisioning Checklist

Complete all items before beginning the provisioning steps:
  • Data Processing Agreement (DPA) signed with the broker firm
  • Broker firm legal name and Companies Registration Office (CRO) number confirmed
  • Primary contact email address confirmed (this becomes the BrokerAdmin login)
  • Primary contact full name confirmed
  • Agreed billing plan confirmed (and payment method set up if applicable)
  • Subdomain or white-label domain requirements noted (future — not yet implemented)
  • Broker firm has been briefed on GDPR responsibilities as a data controller
Do not provision a tenant without a signed DPA. PensionPortal.ai processes personal data on behalf of the broker. Processing without a DPA in place violates GDPR Art. 28.

3. Provisioning Steps

Step 1 — Connect to the production database

# Option A: psql direct connection (requires DATABASE_URL_UNPOOLED from Vercel env)
psql $DATABASE_URL_UNPOOLED

# Option B: Drizzle Studio (browser UI — easier for non-SQL workflows)
npm run db:studio
# Opens at https://local.drizzle.studio

Step 2 — Generate a broker ID

The broker is identified by a brokerId UUID that is stored on all associated users, employers, schemes, and members.
-- Generate a new UUID to use as the brokerId
SELECT gen_random_uuid();
-- Example output: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890'
-- Copy this value — you will use it in Steps 3 and 6
Record the generated brokerId in the provisioning log for this broker. You will need it throughout this procedure and for future support requests. A dedicated broker admin UI at /admin/brokers is a planned roadmap item. Until implemented, the brokerId is the primary identifier for all support queries.
There is currently no separate brokers table — the brokerId is a bare UUID propagated across the users, employers, schemes, and related tables.

Step 3 — Create the BrokerAdmin user account

Generate a secure temporary password:
# Generate a random temporary password
node -e "console.log(require('crypto').randomBytes(16).toString('hex'))"
# Output: a 32-character hex string — use this as the temporary password

# Generate a bcrypt hash of the temporary password (work factor 12)
node -e "const bcrypt = require('bcryptjs'); bcrypt.hash('PASTE_TEMP_PASSWORD_HERE', 12).then(h => console.log(h))"
Insert the user record:
INSERT INTO users (
  id,
  email,
  password_hash,
  role,
  broker_id,
  full_name,
  created_at,
  updated_at
) VALUES (
  gen_random_uuid(),
  'admin@brokerfirm.ie',            -- replace with actual broker contact email
  '$2b$12$...paste_hash_here...',   -- bcrypt hash from command above
  'BrokerAdmin',
  'a1b2c3d4-e5f6-7890-abcd-ef1234567890',  -- replace with brokerId from Step 2
  'Firstname Lastname',             -- replace with actual full name
  NOW(),
  NOW()
);

-- Confirm the insert
SELECT id, email, role, broker_id, full_name, created_at
FROM users
WHERE email = 'admin@brokerfirm.ie';
Never reuse temporary passwords across tenants. Generate a unique temporary password for each new broker provisioning. The broker must change this password on first login. Note: A forced first-login password change flow is a planned roadmap item. Until implemented, instruct the broker verbally or in the secure credential delivery message to change their password immediately.

Step 4 — Communicate credentials securely

Send the following via a secure channel only. Do not send credentials over plain email. Acceptable channels:
  • 1Password Secure Share link (expires after first view)
  • Bitwarden Send (single-use, time-limited)
  • Encrypted email (if broker has PGP key on file)
Message template:
Your PensionPortal.ai account has been created.

Login URL:          https://app.pensionportal.ai
Email:              [broker contact email]
Temporary password: [temporary password]

IMPORTANT: Change your password immediately after first login.
Navigate to: Account Settings → Change Password

Getting started guide: https://docs.pensionportal.ai/quickstart

If you have any issues logging in, contact: engineering@pensionportal.ai

Step 5 — Verify access

Log in to the portal as the new BrokerAdmin and confirm all of the following: Isolation checks — CRITICAL:
  • The BrokerAdmin can see zero employers initially (clean slate)
  • The BrokerAdmin cannot navigate to or view any other broker’s employers, schemes, or members
  • Attempting to access another tenant’s resource returns a 403 or empty result, not their data
Functionality checks:
  • BrokerAdmin can create a new employer
  • BrokerAdmin can create a pension scheme under that employer
  • AI Compliance Assistant responds to queries
  • Email notification received when a test member is enrolled (confirms Resend integration)
  • Audit log records all test actions (check via SuperAdmin audit view or direct DB query)
-- Verify audit log entries were created during verification
SELECT action, entity_type, timestamp
FROM audit_logs
WHERE actor_id = (SELECT id FROM users WHERE email = 'admin@brokerfirm.ie')
ORDER BY timestamp DESC
LIMIT 20;

Step 6 — Log provisioning in the audit trail

INSERT INTO audit_logs (
  actor_id,
  action,
  entity_type,
  entity_id,
  notes,
  timestamp
) VALUES (
  (SELECT id FROM users WHERE role = 'SuperAdmin' LIMIT 1),  -- use your SuperAdmin user ID
  'TENANT_PROVISIONED',
  'broker',
  'a1b2c3d4-e5f6-7890-abcd-ef1234567890',  -- brokerId from Step 2
  'New broker onboarded: [Broker Firm Legal Name] — DPA signed [date] — primary contact: [email]',
  NOW()
);

4. Failure and Rollback Plan

If the provisioning procedure fails or must be abandoned mid-way: Step 1 — Identify the last successful step by checking what records exist in the database. Step 2 — Remove partially-created records:
-- Remove the partially-created user (within the last hour as a safety guard)
DELETE FROM users
WHERE broker_id = 'a1b2c3d4-e5f6-7890-abcd-ef1234567890'
  AND created_at > NOW() - INTERVAL '1 hour';

-- Remove any employers created during testing (if Step 5 was partially completed)
DELETE FROM employers
WHERE broker_id = 'a1b2c3d4-e5f6-7890-abcd-ef1234567890'
  AND created_at > NOW() - INTERVAL '1 hour';
Step 3 — Log the rollback:
INSERT INTO audit_logs (
  actor_id,
  action,
  entity_type,
  entity_id,
  notes,
  timestamp
) VALUES (
  (SELECT id FROM users WHERE role = 'SuperAdmin' LIMIT 1),
  'TENANT_PROVISIONING_ROLLED_BACK',
  'broker',
  'a1b2c3d4-e5f6-7890-abcd-ef1234567890',
  'Provisioning failed at step [N] — rolled back. Reason: [describe failure]',
  NOW()
);
Step 4 — Diagnose the failure, then retry from Step 1 with a new brokerId.

5. Post-Provisioning Verification Checklist

Complete this checklist and retain a copy in the provisioning log:
CheckResultNotes
BrokerAdmin can log in successfullyPass / Fail
BrokerAdmin cannot see other tenants’ dataPass / Fail
BrokerAdmin can create an employerPass / Fail
BrokerAdmin can create a scheme under that employerPass / Fail
BrokerAdmin can enrol a memberPass / Fail
AI Compliance Assistant responds to queriesPass / Fail
Email notifications workingPass / Fail
Audit log records all test actionsPass / Fail
Provisioning logged in audit trailPass / Fail
Temporary test data cleaned upPass / Fail
If any check fails, do not hand the account over to the broker. Investigate, resolve, and re-run the full verification before proceeding.

6. Off-Boarding (Broker Departure)

When a broker firm ends their contract with PensionPortal.ai, follow this procedure to fulfil GDPR data return and erasure obligations.
Pension scheme compliance records (scheme documents, contribution histories, KFH filings) are subject to a statutory 7-year retention period under Irish pensions legislation. You must NOT delete scheme-level records — only anonymise the personal data of individual members. Confirm retention obligations with legal before proceeding.
Step 1 — Export all broker data:
# Generate a full data export for the broker
# Export scheme and member data scoped to this brokerId
pg_dump $DATABASE_URL_UNPOOLED \
  --format=custom \
  --no-owner \
  --no-acl \
  --file="broker-export-$(date +%Y%m%d).dump"

# Planned: A SuperAdmin export endpoint at /api/admin/export/[brokerId] is a roadmap item.
# Until implemented, export manually via pg_dump as above, then filter by brokerId
# when delivering the export to the broker.
Step 2 — Deliver export to broker via secure channel (1Password Secure Share or encrypted email). Step 3 — Anonymise all member PII following the GDPR Art. 17 procedure in Backup & Restore Runbook:
-- Anonymise all members belonging to this broker's schemes
-- WARNING: Irreversible. Confirm legal hold status before executing.
UPDATE members SET
  first_name            = 'DELETED',
  last_name             = 'DELETED',
  email                 = NULL,
  phone                 = NULL,
  pps_number_encrypted  = NULL,
  date_of_birth         = NULL,
  updated_at            = NOW()
WHERE scheme_id IN (
  SELECT s.id FROM schemes s
  JOIN employers e ON e.id = s.employer_id
  WHERE e.broker_id = 'a1b2c3d4-e5f6-7890-abcd-ef1234567890'
);
Step 4 — Disable all broker users:
UPDATE users
SET role = 'Disabled', updated_at = NOW()
WHERE broker_id = 'a1b2c3d4-e5f6-7890-abcd-ef1234567890';
Step 5 — Retain scheme compliance records (do not delete — statutory retention applies):
-- Read-only verification that records are retained
SELECT COUNT(*) AS schemes_retained
FROM schemes s
JOIN employers e ON e.id = s.employer_id
WHERE e.broker_id = 'a1b2c3d4-e5f6-7890-abcd-ef1234567890';
Step 6 — Log off-boarding in audit trail:
INSERT INTO audit_logs (
  actor_id,
  action,
  entity_type,
  entity_id,
  notes,
  timestamp
) VALUES (
  (SELECT id FROM users WHERE role = 'SuperAdmin' LIMIT 1),
  'TENANT_OFFBOARDED',
  'broker',
  'a1b2c3d4-e5f6-7890-abcd-ef1234567890',
  'Broker offboarded: [Broker Firm Legal Name]. Data exported [date], PII anonymised, users disabled. Scheme records retained under 7-year statutory requirement.',
  NOW()
);
Post-off-boarding checklist:
  • Data export delivered to broker and receipt confirmed
  • All member PII anonymised (names, emails, phone numbers, PPS numbers, dates of birth)
  • All broker user accounts set to Disabled role (cannot log in)
  • Scheme compliance records confirmed retained (statutory 7-year hold)
  • Off-boarding logged in audit trail
  • DPO notified for GDPR Art. 30 records of processing update
  • Billing subscription cancelled