# T5DE community design Status: draft for review (2026-10-29). Decisions marked **Decided** were agreed in discussion; the rest are proposals. Open questions are at the end. ## Goals - **Community products**: original work that IMVU's shop doesn't allow, hosted by community-run catalogs, worn and placed in T5DE without IMVU ever seeing it. - **One account per person** on a central hub, linked to any number of IMVU accounts. - **T5DE-hosted rooms**: decorated scenes and their chat, hosted by the hub. - **Social extras**: an opt-in T5DE badge, opt-in T5DE presence, creator pages. - **Moderation from one place**: the hub's website. - **Performance** on every backend. - **Free first**, with payments possible later without reworking the data. Non-goals: re-hosting IMVU's products (original work only), changing what IMVU or non-T5DE users see (they see IMVU's defaults), and replacing IMVU's servers for anything else (for now). ## Overview ``` ┌──────────── user's PC ─────────────┐ │ T5DE client ── patch ── proxy ─────┼──► IMVU (unchanged; never sees community items) └────────────────────────────┬───┬───┘ │ └──► catalogs (many, community-run) ──► S3/R2 storage ▼ ┌──────────── hub (T5DE-run) ─────────────┐ │ API · realtime gateway · rooms · jobs │──► IMVU public API (linking, age checks) │ website & console · Postgres · NATS · R2│ └─────────────────────────────────────────┘ ``` Repositories (**Decided**): | Repository | What | | --- | --- | | `t5de` | Client patches; gains removing community items from what IMVU sees, and the new settings. | | `t5de-proxy` | The local proxy; gains the product resolver, IMQ router, hub and catalog clients. | | `t5de-hub` | The hub: API, realtime gateway, rooms, jobs, website and moderation console (one Go program). | | `t5de-catalog` | The reference catalog: API and a small admin page. Anyone can run one. | | `t5de-api` | Shared definitions: OpenAPI specs, WebSocket messages, token formats, and Go types generated from them. | ## What the client already does (found 2026-10-29) From an experiment with the proxy presenting a made-up product ID as an alias of a real product (t5de-proxy's local `spike-alias` branch): - **Products load from whatever the answers say.** Before using a product the client calls `authorizeProducts2` (XML-RPC: may this user use it, where are its files, their checksum) and product-info endpoints, then downloads the files from the addresses given. When the proxy answered for an ID IMVU doesn't know, the client loaded and rendered the product. - **IMVU signs requests.** `X-imvu-auth` is md5(user ID + session security key + body), with the query string added for some calls. A changed request is rejected ("invalid authorization data"). The proxy sees the key in the login response and could re-sign, but the design avoids sending IMVU anything changed on community products' behalf: the proxy answers those requests itself, and splits requests that mix both. - **Outfit changes are chat commands.** Wearing or removing sends plain-text `*use ` / `*remove ` messages over IMQ (the home screen is a chat session too), and IMVU relays them to everyone in the session. - **IMVU didn't keep an unknown ID** in the account's saved outfit after it was worn. - **The proxy holds the IMVU session key.** It never leaves the user's machine: not to the hub, not to catalogs. IMVU's public user API (`api.imvu.com/user/user-`) has `is_ageverified`, `is_ap`, `is_adult` (self-reported age, not verified) and `tagline`. ## Client side ### Patch (`t5de`) - Removes community product IDs from outgoing `*use` / `*remove` and outfit saves, so IMVU and non-T5DE users see defaults. Tells the proxy the full outfit through a local endpoint only the proxy answers. - Settings: T5DE badge, T5DE presence, adult content (each opt-in), hub sign-in. ### Proxy (`t5de-proxy`) - **IMVU passthrough**: as today. - **Product resolver**: community products are named (global product ID, revision) everywhere outside the machine. The client only understands 32-bit integers (XML-RPC), so the proxy maps each to a local ID in a reserved range near the top (about 1.9-2.1 billion), stored per install. It answers the client's permission, product-info and file requests for them, and splits mixed requests (the IMVU part forwarded unchanged). - **File cache**: files named by sha256, kept on disk; worn items keep working when a catalog is down. - **IMQ router**: chat for T5DE-hosted rooms goes to the hub, the rest to IMVU. Adds other T5DE users' community items to incoming `*use` messages, so the client's own code loads them. - **Hub client**: one WebSocket for presence, room channels and hosted rooms; device-code sign-in, refresh token encrypted for the Windows user (DPAPI). - **Local pages**: browsing and account pages shown in the client's built-in browser. - **Creator live preview**: loads a product from a local folder and reloads it as files change. ## Identity (**Decided**) - A hub **account is a person**. It links to any number of **IMVU accounts**, up to a cap stored per account (default 10), which moderators can raise on request. - **Linking**: the hub gives a one-time code, the user puts it in their IMVU tagline, the hub reads it from IMVU's public API. The user can remove it afterwards. **The most recent proof wins**: linking an account already linked elsewhere moves it (accounts get sold or given away), and the old link ends. - **Sign-in**: device-code flow (approve in a browser). Tokens are short-lived Ed25519 JWTs with key IDs; keys rotate and are published as a JWKS. - **Alts stay private**: tokens for catalogs and rooms carry an ID per linked IMVU account (pairwise), never the person's ID, so other people and catalogs can't connect a person's accounts. Only the person and moderators can. - **Creators** have a public creator profile, separate from (and not revealing) their IMVU accounts. - **Switching accounts in the client**: the proxy sees which IMVU account logs in and tells the hub. Not linked: offer to link. Linked to another hub account: no community features that session. - **Bans** apply to the person, so all linked accounts. ## Age (**Decided**: IMVU's verification) - Adult content requires the **IMVU account in use** to be age verified (`is_ageverified`) or to have Access Pass (`is_ap`; **Decided**: it counts), checked by the hub itself (never reported by the proxy, which anyone can change): at linking and at most daily after, cached. Revocations show up at the next check. - Per IMVU account, not per person, so one verified account doesn't unlock others a minor might use. - Enforced at three points: catalogs (adult files need an adult token), the hub (adult items left out of room channels for viewers who aren't verified and opted in), and the viewer's proxy. - Self-reported `is_adult` doesn't count. ## Products and catalogs - **The hub is the registry.** Products have **global IDs** owned by their creator. A **revision** is a file list (path, sha256, size), signed by the hub on the creator's behalf (creator-held keys later). Revisions never change. - **Catalogs host and curate**: their own rules, ratings ceiling and community. A catalog stores files and tells the hub which revisions it hosts. The same revision on several catalogs is mirrored, not duplicated: uploading a file list identical to an existing revision links to it. Variants link with **derived from** (credit, and the original creator's derive permission). - **Outfits and rooms refer to products and revisions** (see Revisions), so any catalog hosting a revision can serve it. - **Files** are named by hash, immutable, served from object storage or a CDN; never through Go. - **Ratings**: general and adult, as IMVU. Catalogs declare the highest rating they host. - **Uploads** go from the creator's browser (or the proxy) straight to the catalog's API with a hub token, so large files never pass through the hub. The catalog validates them: formats parsed, size limits, nothing executable, thumbnails generated, extra texture maps accepted (see Better looks). - **Format**: IMVU's own product files, as the client loads them, so creators keep using IMVU's tools. ### Revisions (**Decided**: public history) A **revision** is the product itself: its files. IMVU has revisions too (its file addresses carry a revision number, `productdata///`) but hardly shows them; T5DE shows them openly. - **Numbered** 1, 2, 3... per product. Uploading changed files makes a new revision; editing info doesn't. - **History on every product's page**, for everyone: each revision's number and date, the creator's notes (what changed, required when publishing), a preview image, the files added, removed or changed (with sizes), its rating, and which catalogs host it. Any two revisions can be compared. - **Which revision people get** (**Decided**): always the **latest**, everywhere, by default. A person can opt in, per product, to **stay on** a revision they prefer: a setting on the product's page, kept per person (all their IMVU accounts), and only for what they wear. Rooms always use the latest revision of their furniture. Outfits and scenes store just the product; the proxy picks the revision when loading (the pin if there is one, else the latest), and what a wearer broadcasts in a room is always a concrete revision, so everyone sees the same one. - **Old revisions stay available**, so outfits and rooms keep working. A creator can **retract** a broken revision: it's marked retracted in the history, and anyone following or staying on it moves to the nearest revision that isn't. Only moderation removes a revision outright (shown as "removed by moderation", with the files gone). - **Derived from** points to a specific revision of the original. - Moderators see the same history, plus who uploaded each revision and from where. ### Product info (**Decided**: editable) - A product's **files** come in revisions that never change; its **info** belongs to the product and its creator can edit it at any time on the hub's website: name, description, icon, tags, category. Stored at the hub, so an edit shows everywhere (every catalog, every client) without re-uploading anything. - **Icon**: an image uploaded to the hub, re-encoded there (decoded and saved again as PNG or WebP at fixed sizes, which drops anything hidden in the file) and served from R2. - **Description**: HTML, as on IMVU, made safe by an **allowlist** (in Go, bluemonday or equivalent), never a blocklist: - Tags: text formatting (`b`, `i`, `u`, `s`, `em`, `strong`, `small`, `big`, `sub`, `sup`, `font` with `color` and `size`, `span`, `div`, `p`, `br`, `hr`, `center`), lists, simple tables, headings, `a`, `img`. - Styles: only a short list of properties (color, background color, font size, weight and style, text alignment and decoration) with checked values; no `url()`, no positioning, nothing that can cover the page. - Links: `http`/`https` only, with `rel="nofollow ugc noopener noreferrer"`, opening outside the page. - Images: `https` only, fetched through the hub's image proxy (re-encoded, size-limited), so viewers' addresses don't reach outside servers and pages don't mix in insecure content. - Everything else is removed: scripts, event attributes (`onclick`, ...), `iframe`, `object`, `embed`, `form`, `style` and `link` elements, `javascript:` and `data:` addresses, comments. - Size limits on the description. - **Cleaned twice**: when saved (the stored HTML is already safe) and again when shown, by whatever shows it: the hub's website (with a strict Content-Security-Policy that allows no inline scripts) and the proxy. - **In the client, the proxy is the last line**: IMVU's pages run in Gecko 1.9.2 with chrome privileges (they can call into the client's Python), so a script there could do far more than on a website. The proxy cleans descriptions again with the same allowlist before handing them to the client, and gives the client text-only fields (name, tags) as escaped text. - **History and moderation**: every edit is kept as a revision; moderators see the history and can revert or lock a product's info. Changing the rating to *adult* is immediate; changing it to *general* is reviewed first. ### Catalogs (`t5de-catalog`) - Go program, SQLite or Postgres, S3-compatible storage, docker-compose for operators. - **API**: catalog and search, revisions, files by hash, uploads, reports, takedowns. - **No user-facing site** (**Decided**): users, creators and operators use the hub's website. Operators' rights come from hub tokens ("operator of catalog X"); the catalog enforces them itself, so operators stay in charge of their server. - **Small admin page** for what must work without the hub: first setup, storage, keys, registering with the hub, health, emergency takedown. - General-audience files are public (cacheable, and catalogs learn less about who wears what); adult files need a token. - Operators can build their own branded site on the public API. ### Joining the hub and access (**Decided**) - **Any catalog can ask to join** the hub. It's hidden until a hub moderator approves it. - To join, a catalog must give **its own takedown and legal contact**. It stores the files, so the hub forwards takedown notices and reports about its products to that contact (and can still block a product network-wide). - Two settings per catalog: - **Visibility**: *public* (listed in the hub's directory and search) or *private* (not listed; found through an invitation or link). - **Membership**: *open* (any hub user can use it), *approval* (users ask, the catalog's operators approve) or *invite only*. Any combination works: a public catalog can still require approval; a private one can be open to anyone who has the link. - **Seeing versus wearing** (**Decided**): anyone can *see* any catalog's products on someone else (subject to their own age settings); getting and wearing them needs membership where the catalog requires it. ## Outfits 1. The user puts on a community item. The client shows it (proxy answers for it); the patch sends IMVU the outfit without it. 2. The proxy sends the full outfit to the hub, which keeps the current outfit per linked IMVU account and relays it on the room channel. 3. Other T5DE users' proxies add the items to that user's incoming `*use`, filtered by their own age settings. Non-T5DE users see the default. Saved outfits are per IMVU account (avatars differ). The library (products added, favorites, followed creators) is per person, shared across their accounts. ## Rooms (**Decided**: both) - **Decorations over IMVU rooms**: community furniture placed in an IMVU room, seen only by T5DE users. Placements travel on the room channel like outfits. - **T5DE-hosted rooms**: the room lives on the hub: scene, furniture placement, owner, moderators, permissions, bans, chat, seats, presence. The proxy routes that room's IMQ traffic to the hub, which speaks the subset of IMQ the client needs (subscribe, state changes, mounts, messages) and the room-joining XML-RPC calls, so the client treats it as an ordinary room. Needs our own definitions of those messages, written from observation. - **Chat retention** (**Decided**): not stored by default. When someone reports a conversation, the recent messages around the report are kept as evidence for 30 days, then deleted. - Room shells and furniture are ordinary products. ## Presence and badge (**Decided**: both opt-in, per IMVU account) - **Badge**: other T5DE users see that this account uses T5DE. - **Presence**: friends see "online in T5DE". The proxy asks the hub about the user's IMVU friends; the hub doesn't store friend lists, but it sees them when asked (stated in the privacy text). ## Better looks Revisions can carry extra texture maps (normal, roughness, ...) beside the standard ones. The proxy hands them to T5DE's renderer, which uses them when drawing the item; IMVU products and other clients get the standard textures. Hub and catalogs only store and label the files; the work is in t5de-renderer. ## Creator pages Profile, products across all catalogs, follows, and release notifications (over the realtime gateway). ## Moderation and policy - **One policy at the hub**, binding every catalog to stay listed: original work only; no logos, characters or real people's likenesses; zero tolerance for sexualized depictions of minors, with the legal reporting duties that come with it; a takedown process and contact. - **Levers**: delist a catalog; block a product (global ID, and its file hashes to catch re-uploads) network-wide, pushed to every proxy; ban a person; close a hosted room; mute or ban in rooms. - **Console** on the hub's website: report queue, live views (rooms, reports) over server-sent events, actions, audit log. Catalog operators get their catalog's queue there too. ## Payments later (**Decided**: free first) Built in from the start so payments can be added without reworking data: getting a product creates an **ownership record** (source "free", price 0); products have a price (always 0); creators have an empty payout field. A payment service plugs in later. ## Hub (`t5de-hub`) One Go program in modules (split into services only if load requires it): - **Identity**: accounts, linked IMVU accounts, link caps, device-code sign-in, tokens, JWKS. - **IMVU checks**: background jobs reading IMVU's public API (tagline when linking; `is_ageverified` when linking and at most daily). Called rarely and cached: IMVU could block the hub's address, and nothing but linking may depend on IMVU being up. - **Registry**: products, revisions, ratings, derivations, catalogs and what they host, search (Postgres full-text first; Meilisearch when needed). - **Realtime gateway** (WebSocket): presence, badge, room channels, hosted rooms. Live room state in memory on the node owning the room; NATS passes it between nodes once there's more than one. - **Rooms**: hosted rooms' stored state. - **Moderation**: reports, actions, blocklist, audit log. - **Website and console** (**Decided**: in this repo): browse catalogs, products, creators, rooms, news; manage accounts (links, privacy, data export and deletion); catalog operators' screens; moderation console. **Decided**: server-rendered. Proposed: templ (Go templates) and htmx, built into the program: fast, cacheable at Cloudflare, indexable, no JS build. How it looks comes from its CSS (a small design system of our own), not from rendering on the server; small scripts where a page needs richer interaction (image galleries, drag and drop). ### Data (sketch) ``` person(id, link_cap, settings, banned_at) imvu_link(person, imvu_id, pairwise_id, linked_at, age_verified, age_checked_at, badge, presence) creator(id, person, name, page) catalog(id, url, operators, max_rating, status, signing_key) product(id, creator, rating, slot, derived_from, price) revision(product, number, manifest_sha256, signature, notes, created_at, status, extra_maps) hosting(catalog, revision) ownership(person, product, source, price) pin(person, product, revision) outfit(imvu_link, items[product]) room(id, owner, scene, furniture[], roles, bans) report(...), moderation_action(...), audit(...) ``` ## Infrastructure - **Hub**: two small VMs or containers behind Cloudflare (already in front of Gitea; supports WebSockets), managed Postgres with point-in-time backups, R2 for the hub's own assets and T5DE's example catalog (no egress fees). Jobs in Postgres (River). NATS when the gateway has more than one node. - **Deploys**: Gitea CI builds container images and runs migrations; OpenTofu once there are more than a couple of machines. - **Monitoring**: Bugsink (already used for crash reports) for server errors; Prometheus and Grafana (or Grafana Cloud) for metrics; a status page. - **Limits**: rate limits at Cloudflare and per account; upload limits at catalogs. ## Performance Go throughout. Product files never pass through Go (hash-named, from a CDN or object storage). pgx with sqlc (plain SQL, no ORM) and indexes designed up front. Public pages cached at Cloudflare. Live rooms in memory on their node. The proxy streams and adds no waiting. ## Failure - **Hub down**: community items fall back to IMVU's defaults; everything IMVU keeps working; hosted rooms are unavailable. - **Catalog down**: its items come from the proxy's cache or another catalog hosting the same revision, else defaults. - **IMVU's API down**: linking and age checks wait; existing links and cached checks keep working. ## Privacy - IMVU session keys never leave the user's machine. - Linked accounts are private (pairwise IDs). - The hub sees room presence and, for presence, the friends asked about; it stores neither beyond what's needed. - Catalogs see addresses of those fetching adult files (with a token) and of uploaders. - All stated in the setup and on the website. ## Phases 1. Spec (`t5de-api`); local product packs, removing community items from what IMVU sees, creator live preview. No servers. 2. Hub identity: linking, age checks, link caps, badge, presence; website basics. 3. Registry and catalogs: creator pages, community outfits visible to T5DE users, moderation console. 4. Decorations over IMVU rooms. 5. T5DE-hosted rooms. 6. Better looks (renderer work; can run alongside 3 or later). 7. Later: payments. ## Open questions - The takedown contact and legal entity for the hub itself. - Hosted rooms: how much of IMQ and the room-joining calls the client needs (see docs/imq-rooms.md).