Couchside Privacy Policy

Release-candidate status. This policy describes the committed Couchside greenfield composition. No production service is available yet: the surface is served by the committed build and by the QA deployment, and production deployment is a separate, pending step. This document is not yet a statement of legal approval or production publication. It becomes effective only after the owner obtains legal review, configures a user contact channel, and verifies that the deployed production revision serves this exact document at `/privacy`.

What this policy covers

The Couchside software is operated by Couchside Fantasy Sports when it is hosted by the project owner.

Third-party data and trademarks

Couchside references National Basketball Association (NBA) and Women's National Basketball Association (WNBA) game, team, and player data under a third-party data provider's usage terms. Couchside is not affiliated with, endorsed by, or sponsored by the NBA, the WNBA, or any team. Team and league names appear only as plain text, following the repository's likeness/trademark display posture; no official logo, wordmark, or player image is ingested or shown.

The active composition consists of a public progressive-web-app building shell plus retained platform health, account, identity, password, session, and legal surfaces. Couchside Analysis and the previous Couchside Leagues product are quarantined source, not active products: the current application has no League route, job, realtime channel, product descriptor, or feature asset.

This policy covers the data the retained platform can handle and separately explains historical data that an earlier deployment or browser may still hold. A self-hosted operator controls its own deployment, database, logs, provider configuration, and retention, so this policy cannot promise how that operator handles those systems.

Information we handle

Retained account operations can store an account ID, display name, avatar URL, verified email, and the subject and provider for linked identities. Email/password sign-in stores a purpose-built password hash and hash-parameter version. Session, refresh, login-state, pending-link, email verification, and password-reset bearer values are stored as digests; their IDs, timestamps, expiry, consumption, and revocation metadata remain readable. A short-lived OIDC login transaction also stores its provider, nonce, redirect URI, code verifier, and binding metadata while the flow is pending.

An authenticated caller can register a browser push subscription. That stores the push endpoint, the `p256dh` and authentication encryption keys, a device label or user-agent value if supplied, and creation/last-seen timestamps. This retained account API does not mean product notifications are active: the target worker has no League jobs or notification fan-out. Platform scheduler-lock and component-heartbeat rows support health and operations. An authenticated account can retain a list of blocked account IDs through `GET /api/v1/me/blocks`, `PUT /api/v1/me/blocks/{blocked_user_id}`, and `DELETE /api/v1/me/blocks/{blocked_user_id}`. Each stored row contains the blocker and blocked account IDs. Only the blocker reads or changes that list; the blocked account is not notified or given a reverse lookup. Unblock deletes the named relationship, while remaining rows persist until individually removed or an operator applies a later authorized retention control. Migration `0038_account_blocks` owns this retained table. Abuse-control budgets are stored the same way for writes and, since the read limits, for reads: one row per budget, keyed by an irreversible digest of an account or league ID and holding only a remaining allowance and refill timestamps. Those rows name no path, payload, or request detail, cannot be read back to an account, and are deleted once the budget has refilled. The target database also has build-only canonical sport/source-identity and entity tables. They can hold sport structure, readable team or person labels, dated affiliation, games/participants/scores/status history, source provenance, external object identifiers, lifecycle audit, and effective-dated mapping outcomes. Provenance stores a source-record digest rather than a raw provider payload. No active route or worker collects that content in this composition, and no source credential belongs in those tables.

Canonical sport roots, entities, identity assertions, game states, and lifecycle operations are protected by database constraints and append-only history. Unknown or ambiguous source identifiers are quarantined without a canonical ID. Corrections, merges, splits, aliases, and tombstones append audit instead of rewriting the prior observation. There is no active League deletion or purge workflow while this schema remains build-only.

The target schema can retain a single-use invite's opaque ID, SHA-256 token digest, campaign and issuer IDs, timestamps, and revocation or claim state. After claim it also retains the claimant account and created roster IDs. A short continuation retains an opaque ID, SHA-256 binding digest, expiry, and—only after sign-in—the exact bound session/account IDs and its bound, claimed, or abandoned timestamps. Raw invite tokens and raw browser bindings are never stored. Invites expire after seven days and continuations after ten minutes; terminal rows remain as audit because the API has read/insert/update authority but no delete authority.

Couchside Leagues keeps an immutable member-visible activity history for each league. It records a public sequence and time, a fixed action type and summary, bounded public details about the affected league, team, roster move, transaction outcome, or competition bracket, and the acting account ID and role needed to show that account's current public display name. A unique team association may be shown to that league's members. Raw account and source IDs do not appear in the activity API.

Activity is available only to authenticated members of that exact league. Sealed waiver claims and bids, losing claims, trade negotiation, draft queues, sealed picks, invite capabilities, ordinary manager lineups, and chat neither appear in the history nor advance its public revision. Activity entries and internal source links remain with the league as audit history; Couchside Leagues does not currently provide a league-deletion workflow. Before first-user activation, disposable development databases are reset and reseeded rather than reconstructing activity by guessing from private history. Later recovery restores the activity history with its source database records.

Earlier Couchside deployments may have stored Analysis provider data or credentials and League settings, memberships, rosters, transactions, drafts, waivers, notifications, or audit history. The greenfield production composition selects a fresh `sideline_greenfield` database. Its migration guard refuses both legacy migration stamps and an unstamped pre-greenfield schema; it does not convert or expose those records. Replaced databases are abandoned rather than destroyed, so historical databases and backups may still exist for authorized operators even though the active runtime cannot read them. Inaccessibility through the building application is isolation, not verified physical erasure.

The installed building PWA caches only a fixed list of public shell files: HTML, CSS, JavaScript, the manifest, and icons. It stores the signed-in Couchside user ID under one namespaced `localStorage` key solely to detect sign-out, revocation, or an account change across tabs. A same-tab invite/protected return stores only that classification in `sessionStorage`, never the destination or its capability. Every API request and the privacy, sign-in, email-verification, and password-reset paths bypass service-worker caching. Sensitive landing responses are also served with `Cache-Control: no-store` and `Referrer-Policy: no-referrer`.

Retained sign-in flows use session, CSRF, login-binding, password-form-binding, and short-lived invite-continuation cookies. Session and binding cookies are HttpOnly. Hosted binding cookies are Secure and host-only; the CSRF cookie is intentionally readable by the shell so it can send the matching header, and the server also enforces its same-site request policy. Verification and reset pages read a token from the URL, immediately remove the query string from the visible history entry, and send the token only after the user selects the confirm or reset action. The replacement invite client synchronously replaces a token-bearing fragment with fixed `#/invite` before any asynchronous work, then transmits the token once to receive an opaque binding. It never writes the token to browser storage, Cache Storage, rendered HTML, logs, or another URL. The binding contains no session/account ID and is cleared on bind, failure, claim, or logout.

The return classification is removed when consumed, and the principal marker is removed on sign-out or detected revocation. Principal changes also clear Couchside-owned private or obsolete caches and the browser push subscription while retaining the current public offline shell and unrelated same-origin caches. Browser history synchronization or the message containing an invite is outside Couchside's control, even though Couchside replaces its active history entry immediately.

On a first retained-platform session, sign-out, account change, revocation, or expiry, the shell deletes known current and historical Leagues Cache Storage families, historical Leagues sync metadata, the retired development-identity key, and the browser's current push subscription. It preserves the current public shell, Analysis, and unrelated caches. A normal sign-out first asks the retained account API to delete the matching server push-subscription row, then requires the browser endpoint to unsubscribe, and only then asks the server to revoke the session. If either push-cleanup step fails ambiguously, the shell keeps the account visibly signed in and offers a retry while it still has authorization to recover. If principal-boundary browser cleanup fails, the shell does not accept a new principal and instructs the user to clear site data.

Other old Couchside localStorage, cookies, or browser-history entries can remain on a device or with a browser-sync provider. The active runtime cannot clear unknown browser data, other devices, or synced history. Users must use their browser's site-data and history controls for those copies.

How we use and share information

When an operator configures an OIDC provider and a user selects it, the sign-in flow exchanges authorization, token, redirect, and identity-claim data with that provider over HTTPS in QA and production. An unconfigured provider is absent from the available-provider response.

Retained account actions send verification, password-reset, sign-up security, and password-change messages through Postmark when both transactional-email settings are configured. Without that configuration, the null sender sends nothing. The recipient address, message body, and single-use link are then subject to Postmark's own processing and retention. Error and request context is sent to Sentry only when Sentry is configured. Hosting, database, logging, and identity providers process the ordinary data needed to supply those retained operations. If a future retained account surface loads a stored third-party avatar URL, the viewer's browser can disclose normal request metadata, such as IP address and user agent, to that host.

The target release performs no fantasy-provider or basketball-statistics-provider sync, League notification fan-out, or product web-push delivery. We do not sell personal information or use it for targeted advertising in the code represented here.

Diagnostics, security, and retention

The service records operational diagnostics such as request method, path without its query string, status, duration, and request ID. Configured cloud/load-balancer and error-reporting services may also process request or error context needed to operate and secure the platform. Do not put sensitive information in URLs or other free-text inputs.

The service also reports aggregate performance measurements — how long requests took, how long they waited for a database connection, how much work and how many bytes they cost, and how many were refused by an abuse budget. These carry no account, league, team, channel or request identifier of any kind: each measurement is grouped only by a fixed category such as the kind of read it was and whether it was served or refused.

Production infrastructure-as-code configures 30-day log retention, 30 retained database backups, and seven days of database transaction-log retention. These rolling settings do not physically erase an abandoned historical database, and they are not a promise that every provider copy disappears on one schedule. Account and identity records remain while needed for sign-in, security, audit, or applicable obligations; the repository does not establish a shorter universal retention period for every active record type.

Deletion and choices

You can sign out, revoke your active sessions, remove a password when another sign-in method remains, delete a registered push subscription, and remove an account from your own block list through the retained account APIs. Those controls revoke access or remove the named credential/subscription. They do not erase an entire account, all security history, infrastructure logs or backups, abandoned historical databases, or browser data on other devices.

The active composition has no account-erasure workflow and no active League deletion route or purge worker. Historical product data is inaccessible to the target runtime but may remain until an authorized operator disposes of it under an approved retention process. Before production release, the owner must add an operational contact route and a legally reviewed process for privacy requests, including any required access, correction, and deletion requests.

Changes and launch gate

We will update this policy before deploying a material change to the data flows described above. The repository's privacy-policy check validates the inventory's required flow categories and code evidence, requires one matching policy section for every flow, and rejects a stale generated HTML artifact. That check cannot verify a cloud provider's live configuration, physical deletion, or legal compliance. Production publication, owner verification, and legal approval remain explicit release gates.