Design: collectives architecture (participation landing, room matrix, subcollectives, diffusion)

This commit is contained in:
glass beadian 2026-10-12 09:42:16 +00:00
parent e6a78a0805
commit 2ee5c55de1

View file

@ -0,0 +1,112 @@
# Collectives architecture — participation, rooms, subcollectives, diffusion
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.
## 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: `<ws>/<group>`).
- Chat: room set per collective (below), bound to the group via the existing
`ROOM_MAPPINGS`-style immutable mapping.
## Rooms per collective
| 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.