HO Demo Seed
The HO tenant (T0ADQ6UAVKR) is the primary sandbox for VERA development and smoke testing. A daily reseed keeps it in a known, clean state so each session starts from a predictable baseline.
What the daily reseed does
lib/demo-seed.js runs automatically at 8:50 AM ET every weekday (10 minutes before the 9 AM morning briefing fires). It:
- Regenerates TSVs from
generate-dummy-data/generate-v2.js(fresh random hours/budgets each run; names are stable). - Acquires a Postgres advisory lock to prevent concurrent runs.
- Wipes all mutable tables for the HO tenant in FK-safe order:
proposal_liabilities→proposal_roles→proposals(these must go first —proposal_liabilities.vendor_user_idhard-FKsuserswith noON DELETEclause, so any surviving row blocks the users wipe and rolls back the entire seed), thenconversations,time_entries,budgets,pending_pto,unresolved_items,liabilities,rate_history,project_tasks,projects(children then parents),users,tasks,clients,disciplines,company_calendar. - Re-inserts from TSVs in dependency order (disciplines → clients → tasks → users → projects → …).
- Re-appends the three preserved real users with collision-safe IDs.
- Seeds anchor data for the preserved users: Test Client, four anchor projects, time entries, envelopes, rate history, PTO requests, unresolved items, and a vendor scenario.
Scheduled by initDemoSeed(TENANTS) called at boot in index.js.
Manual reseed
DM VERA !test seed as an owner on the HO tenant. VERA replies with a summary of tables wiped and rows inserted.
If HO state gets messy mid-day, !test seed is the fastest recovery path.
Preserved real users
These three users are never wiped — their rows are read before the wipe and re-appended after.
| Slack ID | Name | Role |
|---|---|---|
U0AEJHN3MNV | Tom Hart | Owner |
U0AKQ2EDWAU | MTH | Manager |
U0AJTN12DPY | UTH | User |
The generator produces a Tom Hart row with a fake Slack ID (U0000000001). That row is discarded during the merge; the real Tom Hart row (with the real Slack ID) takes its place.
The cast of characters
Generated users (15)
These users are produced by generate-v2.js on every reseed. Names are stable; budgets and hours vary.
| ID | Name | Role | Type | Notes |
|---|---|---|---|---|
| 1 | Tom Hart | Owner | FTE | Placeholder — replaced by preserved Tom Hart |
| 2 | Rachel Kim | Owner | FTE | Second owner |
| 3 | Sarah Chen | Manager | FTE | PM archetype: consistent under-budget pace |
| 4 | Mike Johnson | Manager | FTE | PM archetype: aggressive pace, often over |
| 5 | Alex Kim | User | FTE | |
| 6 | Nina Foster | User | FTE | |
| 7 | Priya Patel | User | FTE | |
| 8 | Lena Zhao | User | FTE | |
| 9 | Chris Taylor | User | Contractor | 20 h/wk |
| 10 | Marcus Webb | User | Contractor | 20 h/wk |
| 11 | Jamie Park | User | Contractor | 20 h/wk |
| 12 | David Okonkwo | User | Contractor | 20 h/wk |
| 13 | Amy Walsh | User | Contractor | 20 h/wk |
| 14 | Talia Rodriguez | User | Vendor | Sells deliverables; logs no time |
| 15 | Jordan Reed | User | Vendor | Sells deliverables; logs no time |
Plus the three preserved real users (Tom Hart at real Slack ID, MTH, UTH) appended after the generated set.
Disciplines (6)
Six disciplines are seeded, each carrying a default_task_id that maps 1:1 to the corresponding task. This ensures every client time entry is categorised in the By-discipline segmentation view with zero Unmapped rows.
| ID | Name | Default task |
|---|---|---|
| 1 | Design | Design (task 2) |
| 2 | Development | Development (task 3) |
| 3 | Strategy | Strategy (task 1) |
| 4 | Copywriting | Copywriting (task 4) |
| 5 | Project Management | Project Management (task 5) |
| 6 | QA & Review | QA & Review (task 6) |
Task 7 (Admin) is internal-only and intentionally has no discipline — internal projects are excluded from segmentation.
Clients (11)
| Name | Notes |
|---|---|
| Internal | Required infrastructure — always id=1 |
| Meridian Health | Retainer client |
| Harbor Logistics | Retainer client |
| Bloom Retail | Fixed Price client |
| Pulse Analytics | T&M client |
| FreshFarm | Fixed Price client |
| CityCore | T&M client |
| Nexus Media | Fixed Price client |
| SkyBridge | T&M + Fixed Price client |
| Apex Fintech | Retainer client |
| Future Forward Fund | Pro Bono client |
Generated projects (canonical shapes)
| Name | Type | PM | Notes |
|---|---|---|---|
| Admin / Internal Meetings / New Business Development | Internal | Tom (id 1) | Overhead |
| PTO / Sick / Leave | Internal | — | Protected time-off projects |
| Meridian Health Support Retainer | Retainer | Sarah Chen | 13+ completed periods + active Apr 2026 |
| Harbor Logistics Platform Retainer | Retainer | Mike Johnson | Pacing over budget (demo alert trigger) |
| Apex Fintech Creative Retainer | Retainer | Sarah Chen | |
| Bloom Retail Brand Refresh | Fixed Price | Sarah Chen | |
| FreshFarm Brand Identity | Fixed Price | Sarah Chen | |
| Nexus Media Campaign Suite | Fixed Price | Mike Johnson | |
| SkyBridge Annual Report | Fixed Price | Mike Johnson | |
| Future Forward Fund Impact | Pro Bono | Sarah Chen | |
| CityCore App Development | T&M | Sarah Chen | Vendor liability (Talia Rodriguez, Design) — drives the By-discipline make-vs-buy face-off |
| Pulse Analytics Dashboard | T&M | Mike Johnson | Vendor liability (Jordan Reed, Copywriting) — drives the By-discipline make-vs-buy face-off |
| SkyBridge Website Redesign | T&M | Mike Johnson | |
| Meridian Health Digital Support | T&M | Sarah Chen | Backbone engagement — spans the full 12-month window so the Monthly P&L never falls to $0 (the frozen retainers are stripped at seed) |
| Harbor Logistics Platform Support | T&M | Mike Johnson | Backbone engagement — full-window billable base, neutral delivery position |
The three retainers above are generated but stripped at seed time (Retainer/Agile creation is frozen, #962), so they do not appear in the live demo. Their continuous monthly revenue is replaced by the two full-window T&M backbone engagements, whose logged hours are spread evenly across the window (not front-loaded) so every month shows believable, non-zero revenue.
Preserved-user anchor projects
Created by seedPreservedUserData after the bulk insert. These always exist post-reseed regardless of what the generator produces.
| Name | Type | Purpose |
|---|---|---|
| Test Client | Client record | Parent for anchor projects |
| Test – Acme Retainer | Retainer | MTH PM; three burn states (Owner <50%, User healthy) |
| Test – Blue Website (FP) | Fixed Price | MTH PM; User over budget; vendor scenario |
| Test – Agile Sprint | Agile | MTH PM; User at warning burn |
| Test – Pro Bono Clinic | Pro Bono | Zero-cost project |
| Test – Internal Overhead | Internal | Shared overhead project for all preserved users |
Verifying a reseed worked
Run these in Supabase SQL Editor after !test seed:
sql
-- Confirm test cruft (Foo* projects) is gone
SELECT id, name FROM projects
WHERE tenant_id = 'e0c22d94-f990-5cd3-99b7-a4e2a2228147'
AND name LIKE 'Foo%';
-- Expect 0 rows
-- Confirm cast is restored
SELECT COUNT(*) AS user_count FROM users
WHERE tenant_id = 'e0c22d94-f990-5cd3-99b7-a4e2a2228147'
AND status = 'active';
-- Expect ≥ 18 (15 generated + 3 preserved)
-- Confirm anchor projects exist
SELECT id, name FROM projects
WHERE tenant_id = 'e0c22d94-f990-5cd3-99b7-a4e2a2228147'
AND name LIKE 'Test – %';
-- Expect 4–5 rows (Acme Retainer, Blue Website FP, Agile Sprint, Pro Bono Clinic, Internal Overhead)Safeguards
seedDemoTenantreturns{ success: false }immediately iftenant.id !== 'T0ADQ6UAVKR'.!test seedinindex.jscheckstenant.id !== DEMO_TENANT_IDbefore calling the seed function.- Advisory lock prevents concurrent runs (cron + manual).
- Every DELETE includes
WHERE tenant_id = <HO UUID>as belt-and-suspenders beyond RLS.
Monitoring
Every successful seed run records a seed.daily event, visible on /status as "Demo seed (HO)". It follows the same weekday-only cadence as the morning/evening reminder checks — an amber reading early Monday morning (before the 8:50 AM ET run) is expected and self-heals once that run completes, not a failure to investigate.
Recovery if reseed fails
If !test seed fails mid-run, the database may be in a partially-wiped state. Re-run !test seed immediately — the wipe+insert is atomic (single transaction), so a failed run leaves the previous state intact or rolls back cleanly.
If state is unrecoverable, escalate to Tom for direct SQL intervention.