Stage C Release 3 — pre-launch audit HIGH-severity sub-stages C.4 (revenue path, 4 items) + C.6 (auth/billing, 4 items) per docs/plans/2026-05-18-stage-c-high-batch.md (plan token plan-stage-c-high-batch). 8 items shipped.
Added
website/src/app/api/auth/forgot-password/route.ts— server-side rate-limited forgot-password endpoint. 3 requests/email/hour + 10/IP/hour. Page refactored to POST here instead of callingsupabase.auth.resetPasswordForEmaildirectly client-side. P-H026.website/src/app/api/stripe/webhook/health/route.ts— uptime-monitor endpoint that returns 503 whenSTRIPE_WEBHOOK_SECRETorSTRIPE_SECRET_KEYis missing, so external monitors detect misconfiguration before Stripe's retry budget exhausts. P-H017.handleOrderRefunded+handleSubscriptionCancelledinwebsite/src/app/api/lemon-squeezy/webhook/route.ts— refund-and-keep-trial attack closed. Both handlers revokelicense_key_statusand downgrade the linked org toplan='free', plan_status='cancelled'. Per operator policy decision 2026-05-18: subscription cancellation revokes entitlement. P-H016.
Fixed
website/src/app/book/page.tsx:178,196— Bundle and Team tiernotecopy changed from "Auto-renews at $X/yr unless cancelled" to "trial — we do NOT auto-bill; you continue on Free unless you explicitly upgrade with your consent". Closes the marketing-vs-implementation contradiction: pre-fix the copy promised auto-renewal buttierTrialDays()returns 365 with cron downgrade to free with no auto-bill. Welcome/nurture emails already correctly say "Nothing auto-bills" — copy is now consistent. P-H014. Per operator decision 2026-05-18: change copy (not implement auto-renew).website/src/app/api/sso/callback/route.ts:341-360— SAML callback now performs an explicit NameID-domain match againstssoConfig.domain(defense-in-depth; mirrors OIDC at handleOidcCallback line ~407). Pre-fix: the implicit domain match via SELECT WHERE could regress on any future refactor of the lookup pattern. The explicit assertion makes cross-tenant takeover via NameID-with-mismatched-domain structurally impossible. P-H025. Lives UNDER theMASSU_SSO_ENABLED=falseStage B gate; activates when SSO re-enables post-pen-test.website/src/app/api/cron/expire-trials/route.ts:158-170— milestone-email send conditions changed from EXACT-day equality (=== 30,=== 14, etc.) to RANGE checks (>= 30,<= 14 && > 3, etc.). Pre-fix: any Vercel cron miss permanently dropped the affected milestone. With ranges +trial_email_logUNIQUE(org_id, milestone)idempotency, missed cron days catch up on the next run; once-sent never re-sends. P-H028.website/src/app/api/stripe/webhook/route.ts:8-25,46-55— both 500 paths (missing-secret + processing-failure) now emitseverity: 'critical'log lines withaction+consequencemetadata for external alerting. Once @sentry/nextjs ships in 1.10.4 (P-H037),logger.errorwill additionally callSentry.captureExceptionwithout further code changes. P-H017.website/src/app/api/license/activate/route.ts:17-39—hashIp()no longer falls back toLEMON_SQUEEZY_WEBHOOK_SECRET;IP_HASH_PEPPERis REQUIRED and throws on missing. (Already shipped in 1.10.2 P-H023 — reaffirmed here for changelog completeness across the cluster.)website/src/app/api/cron/expire-trials/route.ts:184-218— removed thesupaUntyped as unknown as ...cast hack ontrial_email_log(P-H010 follow-up;trial_email_lognow in generated types as of 1.10.2). The cron now usessupabase.from('trial_email_log').insert(...)directly. Pre-fix the hack was a CR-9 leftover from when the table wasn't in generated types.website/src/tests/integration/api-auth.test.ts:14-25+scripts/massu-security-scanner.sh:169— addedauth/forgot-passwordto PUBLIC_ROUTES allowlist (intentionally unauthenticated — server-side rate-limited). Both allowlists kept in lockstep so the drift-guard parity test stays green.website/src/lib/changelog.ts:40— added"Deferred to Follow-up Sub-Plans"toKNOWN_SECTION_HEADINGSwhitelist so the 1.10.2 changelog parses cleanly.
Verified (no code change)
- P-H015 ebook-attached-to-LS-variant verification — operator INDEPENDENT action; cannot be automated. Operator confirmed before book launch per parent plan operator-action-inventory.
- P-H027
/api/v1/audit?actor=filter uses correctuser_idcolumn (fixed in Stage A P-006 ff7e678; re-verified atapp/api/v1/audit/route.ts:39-40).