# Collectives architecture — participation, rooms, subcollectives, diffusion > **Status — P1 shipped (implementation note).** The three scope pillars of P1 are in: > `groups.parent_id`/`groups.kind`/`group_rooms` (migration `030_collectives.sql`), > the participation-manual landing page at the app root (`apps/web/src/app/page.tsx`, copy in > `packages/core/src/participation-manual.ts`), and the provisioner port + Matrix implementation > (`apps/api/src/collectives/`). Join-by-invite is unchanged: an invite token claims workspace > membership exactly as before; `GET /collectives-public/for-invite/:token` adds names-only > collective labelling to the join card. Room addresses in the UI, directory, and the UI/state > re-parenting surface remain P2+. Status: design for build (phase 1 dispatch in flight) Date: 2026-10-12 Decides: landing flow, collective model, room matrix, provisioning, diffusion ## Product shape `app.occupymedia.cc` lands on the **participation manual** (Spanish-first, English toggle) with three paths: 1. **Join an existing collective** — invite link (`/join/[token]`, exists). 2. **Start a collective** — creates the collective + its room set + onboarding. 3. **Solo user** — account + onboarding room (channels + guide bot explains how to plug in); can join or start a collective later. ## Account tiers, signup, membership (amended 2026-10-12) **Self-serve accounts are now part of the product.** The site lets anyone create an account without belonging to any collective. Posture reconciliation: the homeserver keeps registration token-gated; the **Hub mediates account creation** (the Hub holds the registration capability and provisions the Matrix account server-side after its own signup checks). Direct registration on chat.occupymedia.cc stays closed — the Hub is the only door, and it can add rate-limiting / verification later without touching the homeserver. - **Solo account**: gets the platform's storage (R2-backed files space) and PeerTube access — the full media-maker toolkit, no collective. - **Membership is many-to-many**: one media maker can belong to several collectives at once (`group_members` already models this); solo users can be invited into collectives later, or request to join. - **Account is the unit of identity**; collective membership is a set, not a home. ## Contribution policy (per collective, disclosed at join time) Every collective defines what happens to members' contributions: - Default and stated plainly at join: **deleting your account does NOT delete your contributions.** Content already submitted to a collective stays in its library/archive, attributed, under the collective's retention rules. - Collectives may override (e.g., "contributions are removed on request", "contributions are anonymized after 90 days") — the policy is a field on the collective, shown on the join screen and in the collective's settings. - Enforcement is schema-level: contributions must not `ON DELETE CASCADE` from the user row. User deletion detaches attribution per policy; it never silently rips content out of a collective archive. ## Telegram access to open rooms Open rooms bridge to Telegram (mautrix-telegram, already scaffolded in the chat CT's compose): a collective's open room can attach a Telegram group so outsiders participate from Telegram with no Matrix account at all — same bridge pattern as Matrix federation for open rooms, different door. Bridged rooms are verified for encryption state (never treated as plaintext by assumption); archiving follows the same open-room rules. ## Collective model (maps to what exists) A **collective is a group** in the Hub's group tree — `groups.parent_id` becomes self-referencing (top-level groups = collectives, children = subcollectives, recursive). This rides the existing groups/RBAC/files machinery (`group_files`, `group_members`, `chat_media_archive` are all keyed workspace+group) instead of fighting workspace isolation with cross-tenant writes. Workspace-per-collective stays available later if isolation demands it. - Root group = collective. Children = subcollectives ("report to the top one"). - Files: each collective/subcollective keeps its own group folder in the files app (already how `group_file_shares` is laid out: `/`). - Chat: room set per collective (below), bound to the group via the existing `ROOM_MAPPINGS`-style immutable mapping. ## Rooms per collective **Collective creation includes the Open (public) room by default — a visible toggle lets the founder opt out.** A collective that opts out is fully private from day one; one that keeps it gets the outsider door (federated Matrix + optional Telegram bridge, below). | Room | Visibility | Encryption | Federation | Purpose | |---|---|---|---|---| | Open | Public, listed | **Never** (bot-readable) | **Yes** | Outsiders (any Matrix server) talk to the collective | | Closed (production) | Invite-only | **E2EE default** | No | Internal production | | Onboarding | Invite-only, per user | No | No | Guide bot posts the participation manual, ES/EN | Open rooms are **federated**: outsiders join from their own Matrix accounts on any server — no accounts minted on ours for outsiders (registration stays token-gated; outsiders never get local credentials). Closed rooms are local-only + E2EE: ciphertext on the server, nothing readable by seizure. Archive mirroring applies to Open rooms only (bot-readable by definition); closed-room material stays in the room (members' clients = the backup) until the phase-B encrypted-export flow gives admins self-service copies. ## Provisioning A **provisioner bot** account (distinct from the archive bot; both crypto-blind) creates the room set on collective creation: - creates Open room (public, federated, `history_visibility: shared`), sets power levels (collective admins = PL 100, members = PL 0 + post), posts the room address into the Hub group record - creates Closed room (invite-only, **`m.room.encryption` state set at creation** — the bot sets ciphertext it can never read, invites the founding members) - creates the founder's Onboarding room + posts the manual sequence (ES first) - records everything in the Hub DB (group ↔ room mapping table — extends the existing archive mapping pattern) Encrypted-by-default is thus **structural**: every production room is born encrypted; the only unencrypted rooms are Open (by product definition) and Onboarding (bot-posted guide, disposable). ## Diffusion (push content upward) Subcollectives **push** content to the parent's diffusion queue; the parent **releases**. Maps onto the existing publishing pipeline (drafts → approvals → schedule → dispatch): - New table `diffusion_submissions`: id, from_group_id, to_group_id (parent), draft_id or file_ref, submitted_by, status (`submitted|approved|released|rejected`), approver, timestamps. - Submission = the existing approval request, cross-group: the parent collective's approvers see it in their Approvals queue with origin labeling ("from subcollective X"). - Release = the existing publish dispatch. No new publish machinery. Recursive trees compose naturally: a sub-subcollective pushes to its parent; that parent can push its own (and forwarded) content further up. ## Participation manual Spanish-first static page at the app root (`/`), English toggle. Structure: what this platform is → the three paths → room etiquette (open vs closed) → safety model summary (E2EE, token-gated accounts, redaction behavior) → how to get help (onboarding room). Source content: the Acampada 26S kit copy where applicable; written new where not. Lives in the app (i18n dictionaries), not a PDF. ## Build phases 1. **P1 (dispatched)**: `groups.parent_id` migration + collective create/join APIs + landing page (manual + three paths) + provisioner bot (room set + onboarding rooms) + onboarding guide content (ES/EN). 2. **P2**: files UI shows the collective tree; open/closed room addresses surfaced per collective; Element spaces mapping (one Element space per collective: open + closed rooms together). 3. **P3**: diffusion submissions (push/approve/release across the tree) with origin labeling in Approvals. 4. **P4**: phase-B encrypted export bundles (in flight separately) extended to per-collective admin keys. ## Security posture recap - Outsiders: federated into Open rooms only; zero local credentials. - Production rooms: E2EE at birth; server stores ciphertext. - Onboarding rooms: disposable, bot-posted guide only. - Seizure: E2EE ciphertext + admin-held export keys (phase B) reconstruct the archive; Open-room content was public by definition.