ghostguild-org/docs/LAUNCH_READINESS.md
2026-05-19 13:26:05 +01:00

13 KiB
Raw Blame History

Launch Readiness

Status as of 2026-05-18. Cutover has not happened yet. Code is on local main; deploy steps below still need to execute.

Pre-cutover deploy checklist is the live content on this page. Everything else (post-launch work, bylaws decoupling, deferred features, simplify follow-ups, a11y) lives in BACKLOG.md. Completed launch-blocker items are archived — see ~/.claude/projects/-Users-jennie-Sites-ghostguild-org/memory/project_launch_readiness_archive.md.


Launch shape (2026-05-18)

The launch decision: site live with events ASAP, applications open immediately, Slack invitations sent later in waves.

  • Anyone can hit the site, see events, buy a ticket (members and guests both supported on main).
  • Anyone can join — /join (anonymous) and /accept-invite (waitlist pre-registrants) both render the same SignupFlowOverlay and call the same Helcim signup path. New members become active immediately on payment; slackInvited=false until an admin marks them in a wave.
  • The entire waitlist is invited to apply at launch via the pre-registrant invitation tool. They go through the same flow as anonymous signups, just with email pre-filled and a token-bound pre-reg.

Open decisions that gate the launch comms — see Open decisions below.


Current state

  • All launch code is on local main: Helcim plan consolidation, contribution-amount redesign + migration script, cadence UX unification, receipts Phase 1, and feature/guest-event-accounts (merged in e96d493). Not pushed — site is not deployed yet.
  • Helcim plan consolidation migration ran against prod 2026-04-18 (Monthly plan id 50302, Annual plan id 50303).
  • Contribution-amount migration has NOT yet been run against prod.
  • Receipts Phase 1 code is shipped; remaining work is deploy-time only (see Deploy checklist).
  • cancel-subscription correctly keeps status active per ratified bylaws (Fix #9 in this doc; the stale B1 entry in BACKLOG was marked done 2026-05-18).

P0 — Must fix before launch

None outstanding.


P1 — Strongly preferred before launch

None outstanding.


Deploy checklist

Applies when the app is deployed to Dokploy on Hetzner. Build is via the in-repo Dockerfile (node:20-alpine, runs node .output/server/index.mjs on port 3000); Dokploy autodetects it. Traefik (Dokploy's reverse proxy) handles SSL; oidc-provider.ts:194 and the rate-limit middleware already trust X-Forwarded-Proto / X-Forwarded-For.

One-time host setup

  • Provision the Dokploy app pointing at this repo. Build context: repo root. Default Dockerfile. Container port: 3000.
  • Set env vars in the Dokploy UI (full list below). The validate-env.js Nitro plugin fails fast at boot if MONGODB_URI / JWT_SECRET / RESEND_API_KEY / HELCIM_API_TOKEN are missing — container refuses to start, so misconfig surfaces immediately in logs.
  • BASE_URL must exactly match the public origin (e.g. https://ghostguild.org, no trailing slash). The /api/helcim/customer origin check at server/api/helcim/customer.post.js:11-15 does exact-match comparison against the Origin header — if BASE_URL is wrong or unset, signup 403s.
  • NODE_ENV=production must be set. Without it: Secure cookie flag, HSTS, and CSP all silently no-op.
  • Add a Dokploy Scheduled Task for daily reconciliation. Command:
    curl -fsS -X POST "$BASE_URL/api/internal/reconcile-payments" -H "X-Reconcile-Token: $NUXT_RECONCILE_TOKEN"
    
    Schedule: 0 4 * * * (or any time of day). The Nitro route does the heavy lifting (Mongo iteration, Helcim API, retries) — the scheduler just wakes it up.

Cutover

  • Push local main to origin/main.
  • Run node scripts/migrate-contribution-amount.cjs --apply against prod Mongo BEFORE the new code serves traffic. Idempotent; dry-run on local counted 34 members. Requires MONGODB_URI in env. The script writes contributionAmount (Number) derived from existing contributionTier (String) on every Member doc; the old field is left intact for a window.
  • Set NUXT_HELCIM_MONTHLY_PLAN_ID=50302 in Dokploy env.
  • Set NUXT_HELCIM_ANNUAL_PLAN_ID=50303 in Dokploy env.
  • Set NUXT_RECONCILE_TOKEN to any 32+ char random string. Shared secret between the Dokploy scheduled task and /api/internal/reconcile-payments.
  • Deploy.
  • Run node scripts/reconcile-helcim-payments.mjs --apply against prod Mongo AFTER the new code serves traffic to backfill Payment records for pre-existing members. Idempotent (unique helcimTransactionId); the daily Dokploy cron picks it up from there.
  • Prod audit for pre-fix series-pass bypass registrations. Fixed in f34b062 + 4e1888a (2026-04-20). Before that, child events of pass-only series (tickets.requiresSeriesTicket=true && tickets.allowIndividualEventTickets=false) accepted drop-in registrations from non-pass-holders. For every such series, list its child-event registrations where the registrant is not in the parent series' pass-holder list, filter to registeredAt < 2026-04-20, and decide per-case: grandfather (keep + notify), refund + unregister, or silently unregister. Local Mongo was scrubbed of 2 such rows on 2026-04-20; prod was intentionally untouched.
  • Helcim dashboard: disable the default payment-confirmation email for plans 50302 + 50303. We send our own CRA-safe confirmation via Resend (server/emails/paymentConfirmation.js) triggered from upsertPaymentFromHelcim; leaving Helcim's default on = duplicate emails.
  • Run one real test charge against the deployed app and verify (a) a Payment doc in Mongo with amount, paymentType, status: 'success', and (b) exactly one CRA-compliant confirmation email (charity name + "not an official donation receipt" disclaimer; no banned assertive phrasing).
  • Rotate HELCIM_API_TOKEN in the Helcim merchant portal and update the Dokploy env var. The token was previously exposed in window.__NUXT__ payload until commit 208638e.
  • Trigger the daily reconcile task once manually in Dokploy to confirm scheduled task + token are wired correctly. Expect a [reconcile] done {...} log line.

Activation (after Cutover passes)

The site is deployed but not yet public. These are the steps that flip the switch.

  • Disable the coming-soon gate. Set NUXT_PUBLIC_COMING_SOON=false (or remove the var) in Dokploy and redeploy. The gate lives in app/middleware/coming-soon.global.js:4 and is purely env-driven. Verify /, /about, /events, /board all render without a redirect when logged out.
  • Publish first event(s). Confirm at least one event or series is live and visible publicly. Walk through the guest ticket-purchase flow end-to-end (anonymous → buy ticket → registered → confirmation email).
  • Pre-flight real-money signup test on prod. Have one trusted person (ideally outside the immediate build team) go through /join from scratch: choose a small contribution, pay, receive welcome email, land on dashboard, see "Slack coming" note. This catches end-to-end issues that no internal test reproduces.
  • Send waitlist invitation batch via the pre-registrant admin tool. Decide cadence first (see Open decisions). Smoke-test by inviting yourself or one friend first; only fan out once that round-trip is clean.

Open decisions before launch comms

These do not block deploy but need answers before the waitlist invite goes out. Each carries a small amount of work depending on the answer.

  • Apply-framing decision. Today's CTAs say "Join Ghost Guild" / "Become a member"; there is no "Apply" copy in the codebase. Both /join and /accept-invite use the same SignupFlowOverlay, so the mechanical flow is single-source. Pick one:
    • A (no code work). Keep "Join" everywhere on-site; use "apply" only in external comms (waitlist email, social, etc.).
    • B (small code work). Rename to "Apply" across CTAs + page copy. Touches app/pages/index.vue:11, app/pages/about.vue:86, app/pages/join.vue:5,109,111,301, app/components/LoginModal.vue:66, and at least the waitlist invite + welcome email copy. Likely ~30 min of search-and-replace + screenshot review.
  • First Slack wave date. A publicly-stated date or cadence rule (e.g. "end of each month"). Used in three places: waitlist invite email, welcome email, dashboard "Slack coming" note. Without this, every new member emails support asking when Slack is coming.
  • Non-member event CTA — ticket-first or membership-first? Event pages render to anonymous visitors with both paths viable. Pick which one is primary: "Buy ticket" lowers friction, "Apply for membership" protects the funnel. Write the CTA copy once and use consistently across events.
  • Receipts for guest ticket purchases. Phase 1 receipts cover membership payments only. Guest ticket buyers will get no CRA-compliant receipt at launch. Options: (a) ship a basic transactional receipt for tickets pre-launch, (b) accept the gap until Phase 2 (build JuneOct 2026, live Jan 2027).
  • Waitlist invite cadence. Single blast vs staggered (e.g., 50/day over 4 days). Trade-off is Day-1 support load — a stagger gives you time to catch real issues from early batches before the rest of the list hits.

Items from BACKLOG.md that materially affect the launch-window experience. None are deploy blockers, but each shows up to real users:

  • /api/auth/member returns slackInvited. Without this, the dashboard "Slack coming" note shows for every active member regardless of state. Highest-priority of the wave-Slack bugs because every new member sees the broken case.
  • Admin members-list row reactivity on "Mark as Slack invited" — admin has to manually reload after clicking. Hits operators, not members, but operators are us.
  • /board color-contrast fix (.block-label, .slack-handle#746a58 on #e8dfc8 → 4.01:1, needs ≥4.5:1). Single CSS-var change, currently the only red item in e2e/a11y.spec.js.
  • Spec vs UI mismatch on wave language. docs/specs/wave-based-slack-onboarding-tests.md §7.5 says "no wave/cohort/batch language" but shipped copy uses "monthly onboarding waves." Pick a side and align before launch comms go out.

Env vars required in Dokploy (reference):

  • NODE_ENV=production
  • BASE_URL (exact public origin, no trailing slash)
  • MONGODB_URI
  • JWT_SECRET (or NUXT_JWT_SECRET — the NUXT_ variant wins)
  • RESEND_API_KEY
  • HELCIM_API_TOKEN
  • NUXT_HELCIM_MONTHLY_PLAN_ID=50302
  • NUXT_HELCIM_ANNUAL_PLAN_ID=50303
  • NUXT_PUBLIC_HELCIM_PORTAL_URL
  • NUXT_RECONCILE_TOKEN (32+ char random string)
  • SLACK_BOT_TOKEN
  • OIDC_COOKIE_SECRET

Fixed 2026-04-25

Day-of-launch security and correctness audit. All commit shas TBD until Phase 5.

CRITICAL (security)

  • Fix #1HELCIM_API_TOKEN removed from public runtime config + dead useHelcim.js deleted. Token must be rotated post-deploy (was previously exposed via window.__NUXT__).
  • Fix #2/api/helcim/customer gated with origin check + per-IP/email rate limit + magic-link email verification (replaces unauthenticated setAuthCookie).
  • Fix #3/api/events/[id]/payment deleted (dead code with auth bypass). processHelcimPayment stub + eventPaymentSchema removed.
  • Fix #4/api/helcim/initialize-payment re-derives ticket amount server-side via calculateTicketPrice; new series_ticket metadata type.
  • Fix #5/api/helcim/customer upgrades existing status:guest members in place rather than rejecting with 409.

HIGH (correctness)

  • Fix #6 — Recurring reconciliation: Netlify scheduled function calls /api/internal/reconcile-payments daily. Requires NUXT_RECONCILE_TOKEN env var.
  • Fix #7validateBeforeSave: false added to event subdoc saves (waitlist endpoints) to dodge legacy location validators.
  • Fix #8 — Series-pass purchase always creates a guest Member when caller is unauthenticated, mirroring event-ticket flow.
  • Fix #9cancel-subscription leaves status active (per ratified bylaws); adds lastCancelledAt audit field.
  • Fix #10/api/auth/verify uses validateBody with .strict() Zod schema.
  • Fix #11 — Added 8 vitest cases for cancel-subscription.post.js (was uncovered).

Side-quests

  • Visual audit Phase 4 changes (events/series surface)
  • Per-fix branch verification: see docs/superpowers/specs/2026-04-25-fix-*.md

Manual browser tests still needed

None outstanding. All launch-blocking flows verified via local dev or cloudflared tunnel with real Helcim test card + real email (see archive for the full log). The one remaining browser verification is the staging test charge bundled into the Deploy checklist above.


Post-launch & deferred work

Bylaws decoupling, post-launch a11y, ASVS Phase 4, deferred features, simplify-pass follow-ups, known gotchas, wave-Slack pilot follow-ups — everything that isn't a deploy step has moved to BACKLOG.md.