Founders Promo System — Architecture Reconciliation
Status: Accepted — operator dispatch decision 2026-05-20 UTC (T-3 to v1 launch 2026-05-23 UTC)
Owner: software-architect
Date: 2026-05-20 UTC
Parent cards: #204 (epic), #207, #208, #209, #210, #214
Sub-card slate under review: #231, #232, #233, #234, #235, #236, #237, #238, #239, #240, #241
Related ADR produced by this doc: 0102
Refs: #204
1. Context
The Founders promo card slate was filed on 2026-04-23/24. Between filing and today (2026-05-20), several architectural decisions landed that create drift against the filed cards. This document:
- States the current architectural facts as of 2026-05-20 UTC.
- Maps each of the 14 cards to a disposition (KEEP / REWRITE / SUPERSEDE / SPLIT).
- Identifies the dependency order across the slate.
- Names the minimum viable v1 subset for 2026-05-23 UTC and the post-launch soak set.
- Identifies open architectural questions that block sub-cards from being claimed.
2. Invariants (restated for this system)
All platform invariants apply. Founders-promo-specific:
- No stored credentials. Trial records, referral records, and billing records contain no API key or broker token. ADR-0002 CI grep covers
backend_v2/wholesale. - GDPR by default. All three
founder_*tables cascade onusers.idDELETE. After 30-day cooling, PII fields (referred_user_id,ip_hash) are hard-nulled (not deleted — row survives for audit).audit_logrows: 7-year retention (trade-adjacent entitlement). - Audit trail for every state change. Every status transition and every bonus grant writes an
audit_logrow. No money- or entitlement-affecting state changes are unlogged. - No deletion on downgrade. When a trial lapses, history rows are filtered at the API layer, not deleted. Named test assertions (
test_lapse_does_not_delete_*) are a required part of the card acceptance criteria. - Paper-first gating. Founders receive Pro-tier paper trading only during the trial window. Live-trading graduation is governed by MBT's paper-first gate. This engine does not touch live-trading paths.
- Kill switch.
FLAG_FOUNDERS_PROMO=offandFOUNDERS_PROMO_SCHEDULER_DISABLED=1are both required. The scheduler kill switch must not require a redeploy. - No stored credentials in Stripe webhook handlers.
STRIPE_WEBHOOK_SECRETis sourced from env/Infisical only.
3. Current architectural facts (post-drift corrections)
3.1 Pricing: $29/mo flat, no free trial
Decision locked 2026-05-19 UTC (project_pricing_tiers_locked):
- Founders pay $29/mo immediately at signup. There is no free trial period. The "$29 flat for 6 months" offer means a paying subscription at the Founders discount rate.
- There is no "3 months free paper trading" or "14-day referred trial." The cards were filed under the original model where Founders received a free time window. That model is retired.
- The 6-month period is a pricing-lock window for $29/mo, not a free window.
- After 6 months, Founders roll to standard pricing (Pro $39/mo or Pro+ $79/mo) or a documented renewal discount (not yet defined).
Impact on the card slate: This is the largest single change. The FounderTrialService as specified in the cards — with initial_days, accrued_days_feedback, accrued_days_referrals, expires_at, and the warning ladder — describes a free trial engine. That engine no longer maps to the product. See §5 for dispositions.
3.2 Scheduler: Raptor uses APScheduler, not Celery
Current state: Raptor (backend_v2/) uses APScheduler for background jobs. The Celery + Redis stack referenced in ADR-0016 and all 206-series cards was chosen in April as a future stack for MBT. As of 2026-05-20, Celery and Redis are not deployed on any Raptor dyno. There is no founders.daily_sweep Celery task registered anywhere in the codebase.
The flag-reconciler design (flag-reconciler-bidirectional-sync-2026-05-13.md) explicitly shows APScheduler as the console-side scheduler. Both Raptor and Console run APScheduler.
Decision (see ADR-0102): The Founders daily sweep — if it still exists post-pricing reconciliation — uses APScheduler, not Celery beat. ADR-0016 is superseded for the Founders use case.
Impact: #231 and #233 require Celery task registration to be replaced with APScheduler job registration.
3.3 Stripe webhooks: Queue is the owner
Decision locked 2026-05-12 UTC (project_queue_owns_customer_timeline):
Queue (C++) is the authoritative billing service. The Queue Stripe webhook design (queue-stripe-webhook-design-2026-05-14.md) defines Queue as the sole receiver of customer.subscription.created and related billing events.
The Founders promo cards (#236, #240) specify a Stripe webhook handler in Raptor (backend_v2/). As of 2026-05-14, the Stripe webhook endpoint lives in Queue's billing layer (POST /api/v1/billing/webhook). Queue already dispatches customer.subscription.created via a pipeline that includes mirror fan-out to Raptor's billing_subscription_mirror table.
Impact: Cards #236 and #240 must be re-homed. The Stripe customer.subscription.created branch for Founders bonus/conversion does not belong in Raptor. It belongs as an additional handler branch in Queue's existing webhook pipeline, or as a post-webhook callback that Queue emits to Raptor's internal API after confirming a Founders-tier paid conversion.
The cleanest seam: Queue's webhook pipeline calls POST /api/internal/raptor/founders/paid-conversion on Raptor (internal endpoint, not public) after a Founders-tier subscription is confirmed. Raptor's FounderTrialService handles the bonus/transition logic. This keeps Queue's billing pipeline clean and keeps Founders trial logic in Raptor where the trial schema lives.
3.4 Queue owns customer source-of-truth
Decision locked 2026-05-12 UTC (project_queue_owns_customer_timeline):
Any new "read this customer's profile" feature reads from Queue, not Raptor. The founder_trial schema, however, lives in Raptor's Postgres (it is entitlement metadata, not customer master). The trial data can be surfaced to Queue consumers via a read-through endpoint or by mirroring trial status into Queue's customer record post-conversion.
Impact: The admin console views for Founders cohort (#212, referenced in cards but not in the 14-card slate) must read from Queue for customer master and call Raptor for trial detail. This is a design constraint for any future admin-view card but does not block the cards in the current slate.
3.5 Raptor connects via RAPTOR_APP_DATABASE_URL (ADR-0099)
Raptor's application runtime connects as the raptor_app restricted Postgres role. All schema migrations for Founders tables must be Alembic migrations (owner role, DATABASE_URL). Application code must not use raw DDL in service methods.
New migrations must include -- POSTGRES-ONLY sentinel for PL/pgSQL blocks per the feedback_postgres_only_migration_sentinel rule.
3.6 iOS Founders flow goes through Apple IAP
Decision locked 2026-05-18 UTC (project_ios_billing_iap):
iOS Founders pay through Apple IAP (StoreKit 2). The Stripe webhook handler for Founders conversion (the hook in Queue) applies to web-origin subscriptions only. iOS subscriptions are confirmed via Apple's webhook to a separate handler. The 14 cards in the current slate make no mention of iOS. This gap must be flagged as an open question (see §8).
4. Reconciliation: card-by-card dispositions
4.1 Epic-level cards
| Card | Title | Disposition | Rationale |
|---|---|---|---|
| #204 | Founders Promo epic | REWRITE | Epic AC references "3 months free paper trading." The product model is now $29/mo paid immediately. Epic AC must be rewritten to match the new offer. Sub-card links are also stale. |
| #207 | Referral links + conversion attribution + +time | REWRITE | Core referral mechanic still applies, but the bonus model (referrer gets +3 months of free time) must become referrer gets a defined benefit in the new pricing model. Locked decision: what does referral earn under a paid-subscription model? Open question (see §8.Q1). Schema and ReferralService logic are sound if the bonus model is clarified. |
| #208 | Feedback form + ops approval + +time | REWRITE | The "+30 days free" feedback bonus does not exist in the pay-immediately model. The feedback form and ops-approval flow have independent value (product feedback collection). The bonus mechanism must be redefined or removed. Open question §8.Q2. |
| #209 | Expiration warning emails at 30/14/7/1 days | SUPERSEDE | The warning email cadence is designed for a free trial countdown. Under the new model, Founders are on a 6-month paid subscription lock. The email cadence that applies is the standard billing renewal cadence (Stripe handles renewal reminders) plus a "Founders pricing expires in 30/14/7/1 days, you will roll to standard pricing" notification. New card needed with that framing. |
| #210 | Grace window + paid-tier transition | SUPERSEDE | The grace window design assumes a free trial that must be paid to continue. Under the new model, the Founder is already paid. The relevant transition is: after 6 months, the $29 lock expires and Stripe rolls the subscription to the standard-tier price. There is no grace window for a billing-active subscriber. A separate card may be needed for the pricing-lock expiry notification. The FounderAccessMiddleware (sub-cards #239) still makes sense for enforcing access restrictions on lapsed/non-paying Founders, but the state machine driver changes entirely. |
| #214 | PostHog instrumentation | KEEP | The instrumentation requirements (Founder cohort properties, funnels, retention) are largely independent of the pricing model change. The is_founder, founder_status, founder_cohort properties map to the new model if founder_status is redefined to reflect payment state rather than trial state. Copy must be updated to remove references to days_remaining on the identify call — replace with months_until_price_lock_expiry. Minor rewrite in the AC; the engineering scope is unchanged. |
4.2 Founders trial engine (206 series)
| Card | Title | Disposition | Rationale |
|---|---|---|---|
| #231 | Schema migration + FounderTrialService skeleton | REWRITE | The founder_trial schema must be significantly reconceived. The initial_days, accrued_days_*, expires_at columns represent a free-time model. The new model needs: founders_pricing_lock_start, founders_pricing_lock_end (6 months from start), stripe_subscription_id, ios_transaction_id (for Apple IAP), status (enum: active_founders_pricing |
| #232 | initialize(), get_status(), compute_days_remaining(), add_bonus() |
REWRITE | Most of these methods change substantially. compute_days_remaining() becomes compute_months_until_price_lock_expiry(). add_bonus() is removed or repurposed if the bonus model changes (see §8.Q1, Q2). initialize() remains but sets founders_pricing_lock_end = started_at + 180 days. |
| #233 | Celery daily sweep | REWRITE | Celery → APScheduler. The sweep still makes sense: scan for Founders whose founders_pricing_lock_end is approaching and emit pricing-lock-expiry warning notifications. The warning ladder (30/14/7/1 days) still applies, but it is a "your Founders pricing expires" warning, not a "your free trial expires" warning. |
| #234 | Integration tests: full lifecycle | REWRITE | All test scenarios reference the free-trial model. Must be rewritten to test the new paid-subscription + pricing-lock lifecycle. |
4.3 Referral service (207 series)
| Card | Title | Disposition | Rationale |
|---|---|---|---|
| #235 | Referral schema, slug generation, click/attribution | KEEP | The schema (founder_referral_links, founder_referral_conversions), slug algorithm, click recording, attribution cookie flow, and GDPR gate are all model-independent. These can ship as-is. The only link to the pricing model is in generate_link() being gated by FLAG_FOUNDERS_REFERRAL and in the attribution being triggered at signup — both still apply. |
| #236 | Stripe webhook: paid-conversion bonus grant | REWRITE | Must be re-homed from Raptor to a Queue→Raptor internal callback (see §3.3). The abuse detection logic (same-email, same-payment-fingerprint) is sound and should be preserved. The bonus quantum must be confirmed against the new model (see §8.Q1). |
| #237 | Integration tests: full referral flow | REWRITE | Depends on #236 re-homing. GDPR field-clearing test is still valid. |
4.4 Grace window (210 series)
| Card | Title | Disposition | Rationale |
|---|---|---|---|
| #238 | compute_grace_end(), grace-entry sweep, banner API |
SUPERSEDE | The grace-window concept (free-trial-expired-but-not-yet-blocked) does not apply to a paid subscriber. The banner API endpoint GET /api/founders/trial/banner remains conceptually useful but should be repurposed to serve founders_pricing_lock_expiry info rather than free-trial-expiry info. File new card scoped to the pricing-lock expiry banner. Close #238 with reference to new card. |
| #239 | FounderAccessMiddleware: grace/lapsed enforcement |
REWRITE | Access enforcement at lapse (blocked paper trades, 90-day history filter) still applies if a Founder's paid subscription lapses or cancels. The middleware is still needed; the trigger changes from "free trial expired" to "subscription cancelled/payment failed." The named no-delete assertions must be preserved. |
| #240 | Stripe webhook: grace-to-converted_to_paid | SUPERSEDE | In the new model, there is no "grace window to paid" transition — the Founder is already paid. The customer.subscription.created event is handled by Queue (§3.3). The relevant transitions for Raptor are: subscription cancelled (→ lose Founders access enforcement) and subscription continued past 6 months (→ price lock expires, move to standard pricing). File new card for the pricing-lock expiry transition. Close #240. |
| #241 | Integration tests: grace window lifecycle | SUPERSEDE | Follows #238 and #240. The test scenarios are not applicable to the new model. File new card for pricing-lock-expiry lifecycle tests. Close #241. |
5. Revised data model (sketch for REWRITE cards)
The following replaces the founder_trial schema from founders-trial-engine.md:
founder_subscription
id TEXT PK -- uuid v4
user_id TEXT NOT NULL UNIQUE FK -> users.id ON DELETE CASCADE
cohort TEXT NOT NULL -- 'direct_signup' | 'referred'
stripe_subscription_id TEXT NULL UNIQUE -- for web-origin subscriptions
ios_original_transaction_id TEXT NULL UNIQUE -- for Apple IAP subscriptions; NULL for web
billing_origin TEXT NOT NULL -- 'stripe' | 'apple_iap'
founders_pricing_lock_start TIMESTAMP NOT NULL -- UTC; when $29/mo lock began
founders_pricing_lock_end TIMESTAMP NOT NULL -- UTC; = lock_start + 180 days
status TEXT NOT NULL -- see status enum below
referrer_founder_id TEXT NULL FK -> founder_subscription.id ON DELETE SET NULL
cancelled_at TIMESTAMP NULL
pricing_lock_expired_at TIMESTAMP NULL
created_at TIMESTAMP NOT NULL
updated_at TIMESTAMP NOT NULL
CHECK (cohort IN ('direct_signup', 'referred'))
CHECK (billing_origin IN ('stripe', 'apple_iap'))
CHECK (status IN (
'active', -- Founders pricing lock active, subscription paid
'pricing_lock_expiry_30d', -- warning: lock expires in <= 30 days
'pricing_lock_expiry_14d',
'pricing_lock_expiry_7d',
'pricing_lock_expiry_1d',
'pricing_lock_expired', -- 6-month window passed; subscription continues at standard price
'cancelled' -- subscription cancelled (paper trades blocked; data retained)
))
No initial_days / accrued_days_* / expires_at columns. Those belong to the retired free-trial model.
The referral bonus (if retained post operator decision on §8.Q1) and the feedback bonus (if retained post §8.Q2) must be redefined in terms of a founder_subscription benefit rather than added free time.
6. Dependency order
Layer 0 — Schema foundation
#231 (REWRITE) — founder_subscription migration + FounderTrialService skeleton
└─ depends on: [ADR-0102](https://internal-docs.raxx.app/architecture/adr/0102-founders-promo-scheduler-apscheduler.html) (APScheduler decision), §8.Q1+Q2 operator decisions
Layer 1 — Core service logic
#232 (REWRITE) — FounderTrialService initialize(), get_status(), compute_lock_expiry()
└─ depends on: #231
Layer 2 — Scheduler + referral schema
#233 (REWRITE) — APScheduler daily sweep: pricing-lock warning transitions
└─ depends on: #232
#235 (KEEP) — Referral schema + slug generation + attribution flow
└─ depends on: #231 (referrer_founder_id FK target exists)
Layer 3 — Webhook handlers + access enforcement
#236 (REWRITE) — Queue→Raptor callback handler: paid-conversion referral bonus
└─ depends on: #235, #232 (add_bonus if retained)
#239 (REWRITE) — FounderAccessMiddleware: cancellation enforcement
└─ depends on: #232
Layer 4 — Email dispatch
New card replacing #209 — pricing-lock expiry warning emails
└─ depends on: #233
Layer 5 — Integration tests
#234 (REWRITE) — full lifecycle integration tests
└─ depends on: #231, #232, #233
#237 (REWRITE) — referral integration tests
└─ depends on: #235, #236
New card replacing #241 — pricing-lock lifecycle tests
└─ depends on: #239
Layer 6 — Analytics
#214 (KEEP, minor copy rewrite) — PostHog instrumentation
└─ depends on: #231 (status enum finalized)
7. Minimum viable v1 subset (must ship by 2026-05-23 UTC)
Honest assessment: The full Founders promo system — even the pre-pricing-model-change design — cannot ship in 3 days with the pricing model drift unresolved. Two operator decisions (§8.Q1, Q2) are blocking claims on the REWRITE cards. However, a scoped v1 that enables Founders to sign up and pay is achievable if the operator decisions come in within 2026-05-20 UTC.
V1 gate (must-ship for launch)
| Card | Disposition | Why v1 |
|---|---|---|
| #235 | KEEP | Referral attribution cookie + slug generation enables the referral CTA from day 1. Link is generated on Founders signup. No Stripe dependency. |
| #231 (rewritten) | REWRITE → ship | Schema is the foundation for everything. Migration is additive, fast to land. |
| #232 (rewritten) | REWRITE → ship | initialize() and get_status() are called at signup. Needed for Founders to be provisioned on day 1. |
V1 gate — only if operator decisions land by 2026-05-20 UTC
| Card | Disposition | Blocker |
|---|---|---|
| #233 (rewritten) | REWRITE → target v1 | Operator must confirm warning email cadence for pricing-lock expiry. No Celery dependency once rewritten to APScheduler. |
| #239 (rewritten) | REWRITE → target v1 | Access enforcement at cancellation is a v1 safety net. Blocker is clarifying what "cancelled" state means in the new model. |
Post-launch soak (first 30 days post 2026-05-23 UTC)
| Card | Notes |
|---|---|
| #236 (rewritten) | Queue→Raptor referral bonus callback. Requires QC-3 (#2446) to be stable first. QC-3 is in the Queue cutover epic which has its own timeline. |
| #237 (rewritten) | Integration tests for referral. Follow #236. |
| #208 (rewritten) | Feedback form. Operator decision Q2 required. Non-blocking for v1 launch. |
| #234 (rewritten) | Full lifecycle integration tests. Follow #231/#232/#233. |
| New card replacing #209 | Pricing-lock expiry warning emails. Follow #233. |
| New card replacing #238 | Banner API for pricing-lock expiry. |
| New card replacing #241 | Integration tests for pricing-lock lifecycle. |
| #214 (keep) | PostHog instrumentation. Works post-launch; not a day-1 gate. |
| #204 (rewrite) | Epic AC rewrite. Can land any time. |
| #207 (rewrite) | Parent design card update to match new referral model. |
| #210 (rewrite) | Parent design card update. Low urgency; superseded sub-cards close first. |
Cards to close without replacement: #238, #240, #241, #209 (superseded by new cards filed in the post-launch wave).
8. Open questions — operator decisions required
These block sub-card claims. No feature-dev work begins on blocked cards until the operator responds.
Q1 — What does a referral earn in the pay-immediately model?
Context: Cards #207, #235, #236, #237 assume the referrer earns +90 days of free time. That model is retired. Options under the new model:
- A: Referrer earns a one-month Founders pricing extension (i.e., their 6-month lock extends to 7 months — still $29/mo).
- B: Referrer earns a one-time account credit applied to their next Stripe invoice.
- C: Referral bonus is retired entirely for v1. Referral links still exist for attribution tracking only.
- D: Something else (operator specifies).
Decision needed by: 2026-05-21 UTC (or referral sub-cards ship v1 attribution-only with bonus deferred to post-launch).
Default if no decision: Proceed with Option C (attribution-only). The schema and ReferralService (#235) ship; the process_paid_conversion bonus-grant path in #236 is deferred. This is the lowest-risk v1 posture.
Q2 — Does the feedback bonus survive in the new model?
Context: #208 specifies "+30 days free time" for approved feedback. That bonus is retired. Options:
- A: Feedback form ships as a product-feedback collection tool only (no reward tied to it).
- B: Approved feedback earns a one-month Founders pricing extension (same as referral Q1 Option A).
- C: Approved feedback earns a one-time account credit.
- D: Feedback form is deferred entirely to post-launch.
Decision needed by: 2026-05-21 UTC.
Default if no decision: Option D (defer). Feedback form is not a day-1 v1 gate.
Q3 — iOS referral and pricing-lock: same logic, separate webhook handler?
Context: iOS Founders pay via Apple IAP. Apple sends server-to-server notifications (S2S V2) to a separate endpoint, not to the Stripe webhook. Cards #236 and #240 address Stripe only. Options:
- A: Apple IAP handler mirrors the Stripe handler's Founders logic via the same Queue→Raptor internal callback interface. QC-3 Queue webhook team also implements the Apple S2S handler.
- B: iOS Founders are excluded from referral bonus for v1. Attribution still tracked; bonus deferred.
- C: iOS Founders pricing-lock expiry is managed client-side via StoreKit subscription expiry signals only.
Decision needed by: 2026-05-22 UTC.
Q4 — Pricing-lock expiry email copy: who owns authoring?
Context: The replacement for #209 needs "your Founders pricing expires in N days" copy. Per #209's risk section, no email copy ships without Kristerpher's review. Is this copy already drafted, or does the email card wait for copy authoring before it can be claimed?
Q5 — Attorney review gate on referral incentives
Context: founders-referral-service.md §1 notes: "No user-facing referral copy ships without attorney review." Matthew Crosby (IP counsel, engaged) has not been asked about the referral incentive disclosure requirements. Is this review in progress? If not, referral-facing copy is gated until review completes. The schema + backend cards (#235, #236) can ship behind a flag; the user-facing copy cannot.
9. Security + GDPR checklist
| Question | Answer |
|---|---|
| What PII does this collect? | user_id (FK to PII in users). billing_origin discriminator. stripe_subscription_id, ios_original_transaction_id (pseudonymous payment identifiers). No email, name, or payment card data stored here. |
| Retention period? | founder_subscription: lifetime of account; cascade-deleted 30 days after account erasure. audit_log rows for Founders actions: 7 years. |
| Deletion on DSR? | ON DELETE CASCADE on users.id. 30-day cooling per ADR-0003. |
| Audit logging? | Every status transition writes audit_log. Action namespaces: founder.trial.init, founder.trial.status_transition. No raw PII in context field. |
| Stored credential risk? | None. No API key, token, or secret. ADR-0002 CI grep applies. |
| Breach response? | Covered by ADR-0003 breach pipeline. Entitlement metadata (non-financial) is low-risk. |
| Where are secrets? | FLAG_FOUNDERS_PROMO, FOUNDERS_PROMO_SCHEDULER_DISABLED — env vars only. STRIPE_WEBHOOK_SECRET — Infisical /Raxx/Queue/Billing/Stripe/ (Queue-side, not Raptor). |
| Kill switch? | FOUNDERS_PROMO_SCHEDULER_DISABLED=1 halts sweep. FLAG_FOUNDERS_PROMO=off gates initialization. |
| iOS PII surface? | ios_original_transaction_id is Apple's anonymous identifier. Apple's platform terms govern its use. Not a raw PII field. |
10. Rollout plan
| Phase | What ships | Gate |
|---|---|---|
| v1 (by 2026-05-23 UTC) | founder_subscription schema migration; FounderTrialService.initialize(), get_status(); FLAG_FOUNDERS_PROMO=off; #235 referral attribution; FounderAccessMiddleware (lapse/cancel enforcement behind flag) |
Schema merged before 2026-05-22 UTC. Flag stays off until QA verified on staging. |
| Post-launch week 1 | APScheduler pricing-lock warning sweep; pricing-lock expiry warning emails (pending copy + Q4); referral bonus (pending Q1); FLAG_FOUNDERS_PROMO=on on prod |
Operator flips flag after first real Founders signup confirmed on staging. |
| Post-launch weeks 2–4 | Referral integration tests; feedback form (pending Q2); PostHog instrumentation; admin cohort views | Follow dependency chain in §6. |
| Post-launch (QC-3 dependency) | Queue→Raptor paid-conversion callback for referral bonus (#236 rewritten) | QC-3 (#2446) must be stable on prod. |
11. Superseded ADRs
| ADR | Status after this reconciliation |
|---|---|
| ADR-0016 — Founders Trial: Celery beat for daily sweep | Superseded by ADR-0102. Celery is not deployed on Raptor. APScheduler is the correct scheduler. |
The ADR-0016 file should be annotated with a "Status: Superseded by ADR-0102" header.