Skip to content

Founding Beta invites — Production Readiness

FieldValue
Date2026-08-17
StatusGo for production after companion CI green + staging smoke
ScopeInvite-led Founding Beta: Central Accept & send invite, public register with invite_token, expired self-serve resend, settings
Branchfeature/founding-beta-invite
CompanionsBackend · Frontend · Docs · marketing /beta/ resend

Companion docs: Founding Beta · Central API v1 · Authentication · Deployment authentication · Upgrade · Playwright · Changelog


Executive summary

Founding Beta access is invite-led while public registration stays off:

StepSurface
ApplyMarketing /beta/POST /public/beta-applications
QualifyCentral Beta Applications → Accept & send invite
ActivateSPA /#/register?invite=TOKENPOST /public/register-workspace with invite_token
ExpiredPublic resend (non-enumerating; requires a prior invite) or Central Resend invite

Does not redesign auth, settings hierarchy, or Sanctum SPA login (platform freeze). Marketing origins must not be added to SANCTUM_STATEFUL_DOMAINS.

Go / No-Go: Go — audit residuals H1–H3, M2, M4, L1, and L5 remediated.

GateResult
Hashed invite tokens (SHA-256); raw token only in invite response + encrypted queued mailPass
Email binding on activatePass
Single-use via activated_at / hasActiveInvite() (hash retained for preview)Pass
Public resend requires prior invite (hasBeenInvited)Pass
issueInvite refuses activated applicationsPass
Invite TTL max 90 days (UI, API validation, runtime clamp)Pass
Registration bypass only with valid invite when registration closedPass
Invalid invite + open registration falls back to normal signupPass
Central invite gated by beta-applications.updatePass
CSRF except api/central/v1/public/*; Bearer SPA login unchangedPass
Invite columns not mass-assignable (service forceFill only)Pass
Pest FoundingBetaInviteTest + BetaApplicationTestPass
Playwright test:e2e:beta-applicationsPass
Docs product / API / auth / upgrade / changelog / PlaywrightPass

Security summary

ControlStatus
Token: Str::random(64) → store hash('sha256'); unique indexPass
Resource omits hash; $hidden = ['invite_token']Pass
Preview: invalid / expired / activated without leaking raw tokenPass
Activate: case-insensitive email match + active invitePass
Central Gate::authorize('update') on invitePass
Public resend non-enumerating; no mint for Accepted-never-invitedPass
Queued invite notification implements ShouldBeEncryptedPass
Invite link uses HashRouter fragment (FrontendUrl::foundingBetaInvite)Pass

Remediations (closed)

IDItemResolution
H1Public resend minted first invite for Accepted-onlyRemediatedhasBeenInvited() required
H2TTL UI/API 365 vs clamp 90Remediated — max 90 everywhere
H3issueInvite on activated appsRemediated — 422 validation
M2Pest gapsRemediated — extended FoundingBetaInviteTest
M4Plain token in queue payloadRemediatedShouldBeEncrypted
L1Invite columns $fillableRemediated — removed; factory withInvite uses forceFill
L5Invalid invite + open registrationRemediated — fall back to normal register (email-mismatch still 422)

Accepted residual

IDItemNotes
M3Playwright skips public resend UI (throttle 5/min)Covered by Pest; e2e covers Central resend + expired UI
L3Preview returns email for known tokensAcceptable while token stays secret
L4Resend can rotate pending invites (5/min)Intended self-serve recovery

Intentional

  • Keep hashed token after activation so preview reports activated=true (.ai/rules/central.md).
  • founding_beta_enabled controls CTA visibility only, not invite validity.

Test evidence

SuiteResultNotes
Pest FoundingBetaInviteTestPassIssue/hash/preview/resend, H1/H3, TTL max, authz, activated reuse, open-reg fallback
Pest BetaApplicationTestPassCRUD + permission gates
Playwright npm run test:e2e:beta-applicationsPassOne Central admin session funnel

Staging / production smoke

  1. php artisan migrate --force — migration 2026_08_16_232506_add_founding_beta_invites_to_beta_applications_table (+ settings updateOrInsert).
  2. Confirm FRONTEND_URL points at the production SPA (invite links).
  3. Set founding_beta_apply_url (absolute URL) or rely on MARKETING_URL + /beta/.
  4. CORS: allow marketing origin for public apply/resend — do not add it to SANCTUM_STATEFUL_DOMAINS.
  5. Queue: queue:work --queue=emails (or include emails in the worker list); APP_KEY required for encrypted notification payloads.
  6. Central Settings: registration_enabled=false, founding_beta_enabled=true, invite TTL 1–90 days.
  7. Smoke: apply → Accept & send invite → inbox/link → register → reopen link → “already activated”.
  8. Smoke: Accepted via status Save alone → public resend does not email a new invite; use Accept & send invite.
  9. Smoke expired: expire invite → expired UI → public or Central resend → new link registers.

Official documentation for the EloSync SaaS Platform.