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,user_attribute_history,projects(children then parents),users,tasks,clients,disciplines,company_calendar. (user_attribute_historywas missing from this list until issue #1860 — itsuser_idis a soft reference with no FK, so its rows silently survived every users wipe and accumulated, orphaned, on the demo tenant.) - 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. Holds a 20h envelope on Meridian Health Digital Support (#1860 item 2 — previously had none, showing as a 0%-committed row on /console/capacity) |
| 2 | Rachel Kim | Owner | FTE | Second owner. Holds a 20h envelope on Harbor Logistics Platform Support (#1860 item 2, same fix as Tom Hart above) |
| 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 | Fixed Price | 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). Fixed Price as of #1860 (was T&M) — shifts the mix without touching any project pinned by name/id in manual-testing.md. Also carries Tom Hart's (id 1) sole envelope (#1860 item 2). |
| Harbor Logistics Platform Support | Fixed Price | Mike Johnson | Backbone engagement — full-window billable base, neutral delivery position. Fixed Price as of #1860 (was T&M). Also carries Rachel Kim's (id 2) sole envelope (#1860 item 2). |
| Test – Front-loaded (planned vs linear) | Fixed Price | Sarah Chen | #1860 item 1 — proves Phase 3 (#1857): a 200h envelope shaped front-loaded (130h across the first half of the schedule) reads on course against the planned baseline while the same numbers read off course against the naive linear baseline. Dates are derived from now on every reseed (never hardcoded) so it keeps discriminating regardless of which day the daily reseed runs. See tests/demo-seed-discriminator.test.js. |
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 backbone engagements, whose logged hours are spread evenly across the window (not front-loaded) so every month shows believable, non-zero revenue.
Proposals (5)
Issue #1861 — the demo tenant had zero proposals (wipeHoTables cleared proposals/proposal_roles/proposal_liabilities on every reseed, but nothing ever inserted them, so /console/proposals was permanently empty). Five proposals now cover every lifecycle stage. Console-only, Slack-unreachable, audit-silent (see docs/decisions/proposals.md) — seeding writes rows directly and posts nothing.
| Name | Status | Notes |
|---|---|---|
| Bloom Retail — Loyalty App | draft | Minimal, fully editable. No deadline, no hours budget — exercises the empty-state Effort-vs-timeline / Allocation cards. |
| SkyBridge — Loyalty Program Build | draft | Richly staffed: three staffing lines (one windowed + shaped, 150h across 10 weeks) plus a vendor liability (Talia Rodriguez). Has a deadline and hours budget, so the Cost breakdown, Proposed P&L, Effort-vs-timeline, and Envelope-budget bar all render. |
| Nexus Media — Q4 Campaign Refresh | won | Convert-to-project button available; Edit shows disabled-but-visible. |
| Apex Fintech — Mobile Redesign | lost | Read-only; Delete stays available. |
| Meridian Health — Digital Support Renewal | converted | converted_project_id points at the real seeded "Meridian Health Digital Support" project (never an invented id — the dead-link guard tests/demo-seed-proposals.test.js enforces this). Edit and Delete both disabled-but-visible; the list row shows a project → link. |
At least one proposal_role carries a window and a shape (the SkyBridge Development line), so the Phase 3 capacity simulation has shaped demand to overlay, not just the pre-#1821 unshaped path.
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)
-- Confirm every proposal lifecycle stage is present (#1861)
SELECT status, COUNT(*) FROM proposals
WHERE tenant_id = 'e0c22d94-f990-5cd3-99b7-a4e2a2228147'
GROUP BY status;
-- Expect 5 rows total: draft (2), won (1), lost (1), converted (1)
-- Confirm the converted proposal's converted_project_id resolves to a real project
SELECT p.id, p.name, p.converted_project_id, pr.name AS project_name
FROM proposals p
LEFT JOIN projects pr
ON pr.tenant_id = p.tenant_id AND pr.id = p.converted_project_id
WHERE p.tenant_id = 'e0c22d94-f990-5cd3-99b7-a4e2a2228147'
AND p.status = 'converted';
-- Expect project_name to be non-null (never a dangling id)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.