Re local prod-like dev mesh and synthetic seed data proposal; Growth seed and event-path feedback
Position
Growth supports the proposed local prod-like mesh and agrees that synthetic, production-shaped fixtures should be the default. Localhost should not use real customer, prospect, payment, lesson, campaign-performance, or operator-note data for ordinary validation. Growth especially needs this boundary because local form submits currently exercise the same lead_intake, attribution, and dispatcher_event surfaces that production reporting reads.
Growth also agrees with the proposed dispatcher proof rule. A local Growth fact is operator-valid only when the producer write, producer-local dispatcher_event, local fanout delivery, Growth inbox dedup, and the expected Growth projection or downstream projection can be tied to the same envelope. A row in growth.dispatcher_event proves durable producer intent, not downstream delivery.
Growth seed needs
Growth needs synthetic fixtures at four grains.
- Campaign and attribution fixtures: fake
campaignrows for web, Meta leadgen, partner/manual capture, and a no-campaign control; fake UTM values; fakecampaign_external_mappingrows for the ad-spend path; and a smallcampaign_spend_dailyset for funnel reporting. - Intake fixtures: fake
lead_intakerows through the real write paths where possible, coveringweb_form,meta_leadgen,manual_capture, an unmatched intake with nullpersonId, a matched intake withpersonId, a qualified-at-capture intake, and anis_test_data = trueintake using fictional NANP phone data. - Event fixtures:
intake.captured@2rows emitted transactionally with the intake write, plus matchingintake_event_correlationandlead_attributionrows. The seed should not fabricate a successful fanout result unless the local fanout actually delivered the envelope. - Subscriber and suppression fixtures: an email-only
subscriberwith a visitor id, a subscriber-promotion candidate that later has a phone-bearing intake, and an acquisition-suppression case produced fromperson.updatedrather than direct table mutation.
For the Growth operator UI, a useful mesh seed should make /app show recent web and Meta captures, an intake.captured v2 signal, at least one Platform-matched row, and at least one qualified-intake row. For /app/performance, it should include spend, raw intake, qualified intake, and campaign rows that can produce non-empty cost-per-qualified-intake output.
Seed needs from other domains
Growth needs Platform to seed fake organization and tenant context, canonical Person fixtures, and identity outputs for the intake chain. The Person set should include an adult prospect, a guardian-plus-minor case, an archived Person, a reactivated Person, and an is_test_data Person. For intake validation, Growth needs Platform to emit or otherwise reconcile intake.matched for selected fake intake.captured envelopes so the Growth row's personId backfill and the warehouse attribution stitch can be verified.
Growth needs Sales to seed lead.created from Growth-originated intake events, plus qualification events that exercise all current Growth qualification projections: lead.reached with outcome = qualified, lead.stage.changed with to_stage = qualified, and lead.qualified with confirmed organization and market fields. Those events should reference the fake Growth intake.captured event id so lead_intake_correlation resolves without a cross-database guess.
Growth does not need Delivery or Coaching seed data for its own core localhost flows, but the full journey example still benefits from their fixtures after Sales and Revenue move the lead toward scheduling. Growth should not read Delivery or Coaching state directly for seed validation.
Growth's paid-channel attribution work will eventually need Revenue payment.received and refund walk-back facts, but this repo's current Growth inbox does not handle payment.received, payment.failed, or refund events. The local mesh should mark those as future attribution scope, not as a current Growth consumer pass.
Growth producer events
The local mesh should verify these Growth-produced events:
intake.captured@2, emitted from the public web-form path, Meta leadgen path, manual capture path, and legacy/manual repair path. The envelope subject is thefsm_form-submission id.intake.amended@1, emitted when an operator correction changes an amendable intake field. Phone and email remain out of scope for this event because they are identity-bearing.
Both should be checked at the producer transaction boundary: the domain row and dispatcher_event row should commit together, with the correlation row present for intake.captured.
Growth consumer events
Growth's current dispatcher webhook inbox handles these event types:
person.updated@1, projected intoacquisition_suppressionwhen status changes to archived, and removed when status changes back to active.lead.created@1, projected intolead_intake_correlationwhen the originating intake event resolves to a Growthintake.captured.lead.reached@1, projected intoqualified_intakeonly whenoutcome = qualified.lead.stage.changed@1, projected intoqualified_intakeonly whento_stage = qualified.lead.qualified@1, projected intoqualified_intakewith confirmed org and market attribution.
The mesh should distinguish this wired-now set from broader contract-relevant surfaces. intake.matched is still canonical for attaching personId to Growth intakes, but the current repo-local Growth inbox switch does not handle it. Platform's current matching worker/backfill path should be represented explicitly if the mesh wants to validate the personId backfill. Likewise, payment.received and refund walk-back are attribution concerns, not current Growth inbox handlers.
Cron routes
Growth should appear in the local mesh on port 3001.
GET or POST /api/ad-spend/meta is the meaningful cron-shaped route currently present in the app. It requires Authorization: Bearer <GROWTH_SPEND_API_TOKEN> or the fallback configured token, and it is safe against synthetic fixtures only when the fixture includes fake campaign_external_mapping rows and the route is run in a dry-run or mocked-Meta mode. The default mesh should not call the live Meta Marketing API from localhost.
POST /api/ad-spend is the safer manual fixture path for local spend data. It uses the same authorization check and can load synthetic rows without a live ad-platform token.
/api/cron/lock-reservations appears in vercel.json, but this Growth checkout does not currently have a matching route under src/app/api. Platform should treat that as a local-mesh inventory issue, not as a valid Growth cron tick, until Growth either restores the route or removes the stale cron entry.
Local setup constraints
The proposed Growth port 3001 is fine, but the repo's npm run dev script does not pin a port. The mesh runner should launch Growth with an explicit port, for example next dev --turbopack -p 3001, or set the equivalent process-level port.
Growth uses Prisma 7 with @prisma/adapter-pg. Runtime queries read DATABASE_URL; Prisma CLI work uses DIRECT_URL when present. The Prisma models are declared under @@schema("growth"), so local/prod separation should be a separate local or explicitly named dev database, not a different schema name inside production.
The env-loading order is a safety constraint. In the current repo, prisma.config.ts loads .env.local, then loads .env with override enabled. If .env contains production DSNs, a developer may believe .env.local protected them while Prisma CLI commands still point at production. The mesh runner should set process env explicitly so local DSNs win, or Growth should patch that loader before the mesh is treated as safe. Standalone scripts also need review because some scripts load .env and .env.local directly.
A fresh local database should run migrations before app start and then verify that the expected tables live under the growth schema. Historical migrations include early unqualified table creation and later growth-qualified alters, so the bootstrap should smoke-check growth.lead_intake, growth.lead_attribution, growth.dispatcher_event, and growth.dispatcher_inbox_dedup rather than assuming an empty database initialized correctly.
Local env should include DISPATCHER_INBOX_SECRET for the inbox, META_APP_SECRET for signed Meta webhook probes or META_WEBHOOK_ALLOW_UNSIGNED_DEV=1 for explicitly unsigned dev probes, META_WEBHOOK_VERIFY_TOKEN for the verification route, and a local spend token such as GROWTH_SPEND_API_TOKEN or CRON_SECRET. INTAKE_CAPTURED_SCHEMA_VERSION should stay at the default 2 unless a test is deliberately exercising a legacy path.
Objections and edge cases
Growth has no objection to synthetic fixtures as the default. The only edge cases that might justify a separate sanitized-snapshot workflow are legacy Airtable parity investigations, historical attribution drift, or ad-platform mapping bugs where the defect depends on old campaign-code shape rather than current contract shape. That workflow should be explicitly gated, opt-in, and stripped of customer names, phone numbers, emails, notes, real lesson schedules, payment details, and live campaign performance snapshots.
References
- Proposal:
2026-05-22-platform-local-dev-mesh-seed-data-proposal - Growth dispatcher inventory:
2026-05-20-growth-dispatcher-freshness-inventory - Growth initiative scope:
2026-05-23-growth-postgres-initiative-scope - Growth domain doc:
coordination/domains/growth.md - Growth producer paths:
growth/src/modules/lead-intakes/repo.ts,growth/src/modules/intake-amendment/service.ts - Growth inbox:
growth/src/modules/dispatcher-inbox/service.ts - Growth cron config:
growth/vercel.json