{"id":"publisher-tenant-subdomain-contract","status":"live","updatedAt":"2026-05-25","parentIssue":221,"issue":222,"routes":{"accountSetup":"/account/setup","accountSourceData":"/account/source-data","anonymousPlayground":"/playground","anonymousPlaygroundSourceData":"/playground/source-data","anonymousPlaygroundApi":"/api/playground/anonymous-workspace","anonymousPlaygroundClaimApi":"/api/playground/claim","anonymousPlaygroundCleanupApi":"/api/playground/cleanup","freeBuildWorkspaceApi":"/api/account/publisher/free-build-workspace","reserveSubdomainApi":"/api/account/publisher/subdomain","customDomainApi":"/api/account/publisher/custom-domain"},"tables":[{"name":"publisher_plan_entitlements","purpose":"Paid-plan gate checked before a publisher can reserve a Bumpgrade subdomain.","publicSafeFields":["status","source","plan_slug","starts_at","ends_at"],"privateFields":["owner_user_id","owner_email"]},{"name":"publisher_tenants","purpose":"One publisher workspace with owner identity, private Free Build or paid plan status, default subdomain, and primary hostname.","publicSafeFields":["id","status","plan_status","default_subdomain","primary_hostname","source_issue_number"],"privateFields":["owner_user_id","owner_email"]},{"name":"publisher_subdomain_reservations","purpose":"Unique default `*.bumpgrade.com` hostname reservation with idempotency and audit correlation.","publicSafeFields":["subdomain","full_hostname","status","source_issue_number"],"privateFields":["owner_user_id","owner_email","idempotency_key"]},{"name":"publisher_custom_domains","purpose":"Existing-domain onboarding with deterministic DNS instructions, verification state, SSL state, idempotency, and redaction.","publicSafeFields":["status","dns_record_type","dns_record_name","dns_record_value","dns_last_checked_at","dns_verified_at","ssl_status","source_issue_number"],"privateFields":["tenant_id","owner_user_id","owner_email","domain_name","normalized_domain","idempotency_key"]},{"name":"publisher_tenant_audit_events","purpose":"Append-only tenant setup evidence for Free Build creation, subdomain reservation, and domain/custom-domain changes.","publicSafeFields":["event_kind","summary","created_at"],"privateFields":["actor_user_id","actor_email","metadata_json"]},{"name":"anonymous_playground_workspaces","purpose":"Browser-scoped launch playground progress for visitors who have not signed up yet; expired rows can be marked expired and cleared by owner or scheduled cleanup.","publicSafeFields":["id","status","revision","expires_at","source_issue_number"],"privateFields":["recovery_token_sha256","offer_name","audience","launch_goal","product_format","price_point","lead_magnet","checkout_plan","delivery_plan","follow_up_plan","source_url","selected_importer_slug","claimed_by_user_id","claimed_tenant_id","metadata_json"]},{"name":"anonymous_playground_audit_events","purpose":"Append-only save, claim, and cleanup evidence for browser-scoped playground progress.","publicSafeFields":["event_kind","created_at"],"privateFields":["workspace_id","idempotency_key","metadata_json"]}],"freeBuildPolicy":{"status":"live","issue":473,"parentIssue":466,"signedInWorkspaceLive":true,"anonymousPlaygroundLive":true,"anonymousSaveRateLimitLive":true,"anonymousScheduledCleanupLive":true,"confirmationText":"Create my private Free Build workspace","privateBuildPlanStatus":"free_build","route":"/api/account/publisher/free-build-workspace","anonymousPlayground":{"status":"live","route":"/playground","sourceDataRoute":"/playground/source-data","saveApiRoute":"/api/playground/anonymous-workspace","claimApiRoute":"/api/playground/claim","cleanupApiRoute":"/api/playground/cleanup","claimCreatesPrivateDraftFunnel":true,"structuredBuilderFieldsLive":true,"saveRateLimit":{"scope":"browser_recovery_workspace","windowSeconds":600,"maxSavesPerWindow":12,"responseCodeWhenLimited":429,"responseCode":"ANONYMOUS_PLAYGROUND_SAVE_RATE_LIMITED","rawIpStored":false,"rawUserAgentStored":false,"recoveryTokenHashExposed":false},"claimMergePolicy":{"mode":"additive_private_draft","emailVerifiedAccountRequired":true,"createsFreeBuildWorkspaceWhenMissing":true,"reusesExistingFreeBuildWorkspaceWhenPresent":true,"createsAdditionalPrivateDraftFunnel":true,"createsPrivateClaimRecords":true,"overwritesExistingFreeBuildWorkspace":false,"overwritesExistingDrafts":false,"overwritesAnonymousDraftFields":false,"publicSourceDataExposesPrivateDraftContent":false,"paidGoLiveRequired":true,"idempotencyScope":"anonymous_workspace_id_and_user_id"},"cleanupControlsLive":true,"scheduledCleanupLive":true,"scheduledCleanupCron":"17 9 * * *","scheduledCleanupActor":"cloudflare_cron","retentionDays":30,"draftSourceDataRoute":"/funnels/source-data","cookieMaxAgeDays":30},"whatWorksToday":["Logged-out visitors can save structured launch context in a browser-scoped playground before signup.","Returning from the same browser reloads saved playground progress while the recovery cookie is present.","Verified signed-in users can attach the saved playground to a private Free Build workspace, private launch draft, and private offer/product/audience/importer-review records.","If the signed-in user already has a Free Build workspace, claiming the playground reuses that workspace and adds a private draft without replacing existing workspace work.","Owner cleanup and daily Cloudflare Cron cleanup can expire old anonymous recovery, clear anonymous draft fields, replace the recovery token hash, and preserve private claimed records.","Verified signed-in users can create a private Free Build workspace before payment.","The workspace is persisted in publisher_tenants with plan_status=free_build.","Idempotent replays return the same workspace instead of duplicating tenant rows.","Claimed playground launch drafts preserve offer, audience, product, opt-in, checkout, delivery, follow-up, and migration-starting-point notes in private funnel_drafts, funnel_draft_steps, anonymous_playground_claim_records, and funnel_audit_events evidence."],"paidGoLiveGates":["Bumpgrade subdomain reservation","Custom-domain onboarding","Public publishing","Live checkout and payment collection","Buyer or subscriber sends","Fulfillment and protected access"],"redaction":"Public source data describes the Free Build and anonymous playground contracts only. Private owner identity, raw playground draft fields, expired playground content, cleanup actors, private funnel content, private claim-record content, recovery cookies, token hashes, idempotency keys, and audit metadata stay out of public source-data."},"subdomainPolicy":{"defaultDomain":"bumpgrade.com","paidPlanRequired":true,"emailVerificationRequired":true,"allowedPattern":"lowercase letters, numbers, and hyphens; 3-63 characters; cannot start or end with hyphen","reservedNames":["account","accounts","admin","api","app","assets","auth","billing","blog","bumpgrade","cdn","codex","compare","developers","docs","email","features","help","login","m","mail","pricing","resources","roadmap","root","signup","static","status","support","www"]},"crossSubdomainAuth":{"status":"configured","issue":224,"cookieDomain":"bumpgrade.com","trustedOriginPattern":"https://*.bumpgrade.com","trustedOrigins":["https://bumpgrade.com","https://www.bumpgrade.com","https://*.bumpgrade.com","http://localhost:*","http://127.0.0.1:*"],"crossSubDomainCookiesEnabled":true,"bumpgradeHostedSubdomainsShareLogin":true,"goal":"One Better Auth identity session applies across bumpgrade.com and paid publisher subdomains such as a.bumpgrade.com and b.bumpgrade.com.","tenantIsolation":"The shared session proves identity only. Every publisher-site read or write must still resolve the requested hostname to a tenant and enforce tenant-scoped entitlements before returning private data.","localTestBoundary":"Localhost cannot prove browser cookie sharing for bumpgrade.com. Tests assert the production Better Auth cookie-domain and trusted-origin contract, then production smoke reads this source-data route after deploy."},"customerAuthPolicy":{"status":"configured","issue":224,"sharedIdentityProvider":"https://bumpgrade.com","appliesTo":["bumpgrade.com","*.bumpgrade.com"],"endUserPromise":"Customers using Bumpgrade-hosted publisher sites should not need a second login when moving between paid publisher subdomains on bumpgrade.com.","publisherSiteRule":"A shared identity session is not shared data access. Every request still resolves the hostname to the publisher tenant and checks checkout, entitlement, or membership state before returning customer content.","customDomains":{"canShareBumpgradeCookieDirectly":false,"behavior":"Customer-owned custom domains cannot receive a bumpgrade.com browser cookie directly. Bumpgrade should use a central bumpgrade.com login handoff and return URL for identity, then enforce tenant-scoped access on the custom domain.","launchCopy":"Existing-domain DNS onboarding is live. Custom-domain customer login uses the Bumpgrade account handoff instead of promising raw cookie sharing across unrelated domains."},"adminBoundary":"Owner/admin sessions remain allowlisted and owner-gated; shared publisher-site identity must not grant admin access."},"customDomainPolicy":{"status":"live","issue":223,"domainRequirement":"Bring an existing domain you already own; Bumpgrade does not sell or register domains today.","paidPlanRequired":true,"emailVerificationRequired":true,"defaultBumpgradeHostnameRequiredFirst":true,"dnsInstruction":{"recordType":"CNAME","recordName":"the publisher-owned hostname, for example www.example.com","recordValue":"custom-domains.bumpgrade.com","expectedValue":"custom-domains.bumpgrade.com"},"statuses":["pending_dns","dns_verified","ssl_pending","active","failed","disabled"],"redaction":"Public source data exposes policy, routes, and DNS instruction shape; private customer domain rows require authenticated publisher context."},"domainPurchasePolicy":{"status":"not_offered_yet","issue":225,"currentLaunchAnswer":"No. Bumpgrade does not sell, register, renew, or transfer domains today. Use a paid Bumpgrade subdomain or connect a domain you already own.","whatWorksToday":["Paid publishers can reserve a default *.bumpgrade.com hostname.","Paid publishers can connect an existing custom domain with Bumpgrade DNS instructions and verification state."],"notClaimed":["Domain search or availability checks.","Domain registration, transfer, renewal, privacy, contact, or refund handling.","Registrar pricing, supported TLD inventory, or registrar-of-record status."],"futurePath":"If Bumpgrade adds registration later, it needs a registrar/provider decision, availability checks, purchase and renewal terms, contact/privacy handling, payment/refund policy, and provider failure states before any public CTA claims domains can be bought through Bumpgrade."},"notIncludedYet":["Buying, registering, renewing, or transferring domains through Bumpgrade.","Publisher site editor parity for arbitrary pages on the reserved hostname.","Logged-out anonymous workspace recovery.","Raw browser-cookie sharing across unrelated custom domains."]}