Some checks are pending
Veza CI / Backend (Go) (push) Waiting to run
Veza CI / Frontend (Web) (push) Waiting to run
Veza CI / Rust (Stream Server) (push) Waiting to run
Veza CI / Notify on failure (push) Blocked by required conditions
E2E Playwright / e2e (full) (push) Waiting to run
Security Scan / Secret Scanning (gitleaks) (push) Waiting to run
The soft-launch report doc (SOFT_LAUNCH_BETA_2026.md) had the
narrative — cohort table, email body inline, monitoring list,
acceptance gate. But the operational pieces were notes-to-self :
"add migration if missing", "Typeform to-do", "schema TBD". The
operator was supposed to assemble them on the day, which on a soft-
launch day is the worst possible time.
Added the missing 6 pieces so the day-of work is "tick boxes",
not "build the tooling" :
* migrations/990_beta_invites.sql — schema with code (16-char
base32-ish), email, cohort label, used_at, expires_at + 30d
default, sent_by FK with ON DELETE SET NULL. Three indexes :
unique on code (signup-path lookup), cohort (post-launch
attribution report), partial expires_at WHERE used_at IS NULL
(cleanup cron).
* scripts/soft-launch/validate-cohort.sh — sanity check on the
operator's CSV : header form, malformed emails, duplicates,
cohort distribution (≥50 total / ≥5 creators / ≥3 distinct
labels), optional collision check against existing users.
Exit codes 0 / 1 (block) / 2 (warn-but-proceed). Hard checks
block, soft checks let the operator override with FORCE=1.
* scripts/soft-launch/send-invitations.sh — split-phase :
step 1 (default) inserts beta_invites rows + renders one .eml
per recipient under scripts/soft-launch/out-<date>/
step 2 (SEND=1) dispatches via $SEND_CMD (msmtp by default)
so the operator can review the rendered emls before sending
100 emails. Per-recipient transactional INSERT so a partial
failure doesn't poison the table. Failed inserts logged with
the offending email so the operator can rerun on the subset.
* templates/email/beta_invite.eml.template — proper MIME multipart
(text + HTML) eml ready for sendmail-compatible piping. French
copy aligned with the éthique brand (no FOMO, no urgency
manipulation, no "limited spots" framing).
* scripts/soft-launch/monitor-checks.sh — polls the 6 acceptance-
gate signals defined in SOFT_LAUNCH_BETA_2026.md §"Acceptance
gate" : testers signed up, Sentry P1 events, status page,
synthetic parcours, k6 nightly age, HIGH issues. Each gate
independently emits ✅ / 🔴 / ⚪ (last for "couldn't check").
Verdict on stdout. LOOP=1 keeps polling every CHECK_INTERVAL
seconds. Designed for cron + tmux, not for an interactive UI.
* docs/SOFT_LAUNCH_BETA_2026_CHECKLIST.md — pre-flight gate that
must reach 100% green before the first invitation goes out.
T-72h section (database, cohort, email infra, redemption path,
monitoring, comms), D-day section (last-hour, send, hour-1,
every-4h), 18:00 UTC decision call section. Linked back to the
bigger SOFT_LAUNCH_BETA_2026.md so the operator can navigate
between the "what" (report) and the "how / has-everything-
been-checked" (this checklist) without losing context.
What still requires the operator on the day :
- Build the cohort CSV (curate emails from real sources)
- Create the Typeform feedback form ; paste its URL into the
eml template once known
- Configure msmtp / sendmail ($SEND_CMD)
- Press the send button
- Show up at 18:00 UTC for the decision call
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
65 lines
3.2 KiB
SQL
65 lines
3.2 KiB
SQL
-- 990_beta_invites.sql
|
|
-- v1.0.10 polish (Cluster 3.4) — soft-launch beta cohort tracking.
|
|
--
|
|
-- Records each individual invitation sent for the v2.0.0 soft-launch
|
|
-- bêta. Tracks (a) the invite code used in the registration link,
|
|
-- (b) when the recipient redeemed it (NULL until redemption), and
|
|
-- (c) which cohort segment (creator / listener / community-member /
|
|
-- press) the recipient belongs to so the post-launch report can
|
|
-- attribute feedback by audience.
|
|
--
|
|
-- The associated email template + send script live at
|
|
-- scripts/soft-launch/send-invitations.sh and reference this table
|
|
-- via INSERT … RETURNING code.
|
|
--
|
|
-- Privacy : the email column is the only PII here ; no behavioural
|
|
-- data is stored. used_at is the redemption signal. After v2.0.0
|
|
-- public launch, run the cleanup migration in 991 (TBD) to anonymise
|
|
-- the email column for invites that haven't been redeemed in 30+ days.
|
|
|
|
CREATE TABLE IF NOT EXISTS public.beta_invites (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
-- The invite code is what the recipient pastes into the signup
|
|
-- form. 16 random characters from a base32 alphabet (no 0/1/I/L
|
|
-- to avoid eyestrain). Generated by send-invitations.sh.
|
|
code VARCHAR(32) NOT NULL UNIQUE,
|
|
email VARCHAR(320) NOT NULL,
|
|
-- Free-text label so the cohort generator can carry whatever
|
|
-- segmentation the operator wants (e.g. "creator-vinyl-pressing",
|
|
-- "listener-jazz-mailing-list", "press-pitchfork"). Index below
|
|
-- is for the post-launch report grouping.
|
|
cohort VARCHAR(64) NOT NULL,
|
|
-- NULL until the recipient signs up. Set by the auth handler
|
|
-- when /auth/register is hit with a valid invite code.
|
|
used_at TIMESTAMPTZ,
|
|
-- Hard expiry so unredeemed invites can't accumulate forever.
|
|
-- Default 30 days from creation ; soft-launch is short-window.
|
|
expires_at TIMESTAMPTZ NOT NULL DEFAULT (NOW() + INTERVAL '30 days'),
|
|
-- Operator who sent the invite — useful when reconciling "who
|
|
-- gave their friend a code" during the audit.
|
|
sent_by UUID REFERENCES public.users(id) ON DELETE SET NULL,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
);
|
|
|
|
COMMENT ON TABLE public.beta_invites IS
|
|
'v2.0.0 soft-launch beta invitation tracking. v1.0.10 Cluster 3.4.';
|
|
COMMENT ON COLUMN public.beta_invites.code IS
|
|
'16-char base32 invite code (no 0/1/I/L). Pasted into signup form.';
|
|
COMMENT ON COLUMN public.beta_invites.cohort IS
|
|
'Free-text cohort label (creator-* / listener-* / press-* / etc.).';
|
|
COMMENT ON COLUMN public.beta_invites.used_at IS
|
|
'Redemption timestamp. NULL means the invite is still pending.';
|
|
|
|
-- Lookup by code (signup path) — every /auth/register call reads it.
|
|
CREATE UNIQUE INDEX IF NOT EXISTS idx_beta_invites_code
|
|
ON public.beta_invites(code);
|
|
|
|
-- Cohort grouping for the post-launch attribution query.
|
|
CREATE INDEX IF NOT EXISTS idx_beta_invites_cohort
|
|
ON public.beta_invites(cohort);
|
|
|
|
-- Pending-invitations sweep — cron job that expires unused invites
|
|
-- after expires_at. Partial index keeps it small.
|
|
CREATE INDEX IF NOT EXISTS idx_beta_invites_pending_expiry
|
|
ON public.beta_invites(expires_at)
|
|
WHERE used_at IS NULL;
|