| … | … | @@ -0,0 +1,572 @@ |
| 1 | # Rooms Are the Computer |
| 2 | |
| 3 | A bottom-up architecture where rooms are the only data structure. Everything else — groups, registries, profiles, graphs, governance — is either room config, room messages, or local computation on room data. |
| 4 | |
| 5 | This document supersedes the Autobase-per-level approach in AUTO-DISCOVERY-SPEC.md and simplifies the ROADMAP.md architecture. The conceptual frameworks in SOCIAL-GRAPH-NAVIGATION.md and COMMUNITY-EMERGENCE.md remain valid — this document changes HOW they're implemented, not WHAT they describe. |
| 6 | |
| 7 | ## The Insight |
| 8 | |
| 9 | keet-core already gives you a battle-tested, multi-writer, replicated, conflict-resolving Autobase for every room. Admins can write config. Members can post messages. Roles manage permissions. Invites manage access. This is the hardest distributed systems problem — and it's already solved. |
| 10 | |
| 11 | Every time we create a hand-crafted Autobase above the room level (discovery registry, meta-registry, topic index), we're reimplementing worse versions of what keet-core already provides. Each additional Autobase multiplies: |
| 12 | |
| 13 | - Writer management complexity |
| 14 | - Replication overhead |
| 15 | - View corruption risk |
| 16 | - Recovery procedures |
| 17 | - Code surface area |
| 18 | |
| 19 | **The fix: stop creating Autobases. Use rooms.** |
| 20 | |
| 21 | ## The Architecture |
| 22 | |
| 23 | ``` |
| 24 | OLD (3+ Autobase types, hand-crafted): |
| 25 | Room ← Autobase (keet-core) ✓ works great |
| 26 | Registry ← Autobase (hand-crafted) ✗ complex, error-prone |
| 27 | Meta-registry ← Autobase (hand-crafted) ✗ even worse |
| 28 | |
| 29 | NEW (1 Autobase type, all keet-core): |
| 30 | Room ← Autobase (keet-core) ✓ works great |
| 31 | Registry ← Room (it's just a room) ✓ reuses keet-core |
| 32 | Profile ← Room (it's just a room) ✓ reuses keet-core |
| 33 | Everything ← Room config + messages ✓ already exists |
| 34 | Graph ← Local computation ✓ no storage needed |
| 35 | ``` |
| 36 | |
| 37 | ### What a Room Already Provides |
| 38 | |
| 39 | | Capability | How | Multi-writer? | |
| 40 | |-----------|-----|--------------| |
| 41 | | Arbitrary key-value metadata | `setConfig(roomId, key, value)` | Yes (any admin) | |
| 42 | | Structured data exchange | Messages with JSON payloads | Yes (any member) | |
| 43 | | Access control | Roles: admin, moderator, peer, read-only | Yes (admin manages) | |
| 44 | | Identity | Member profiles: name, avatar, username | Yes (each member) | |
| 45 | | Replication | Hyperswarm + Autobase | Automatic | |
| 46 | | Persistence | RocksDB | Automatic | |
| 47 | | Invites | `createInvitation(roomId)` | Yes (admin) | |
| 48 | | Real-time updates | Push events via subscription | Automatic | |
| 49 | |
| 50 | Every feature that a "discovery registry Autobase" provides is already available through room primitives. The registry was a room with extra steps. |
| 51 | |
| 52 | ## Room Types (Convention, Not Code) |
| 53 | |
| 54 | A room's purpose is declared by a config key. No new Autobase schemas. No new data structures. Just conventions about what `room.config.type` means. |
| 55 | |
| 56 | ### Chat Room (default) |
| 57 | |
| 58 | ```javascript |
| 59 | room.config = { |
| 60 | type: "chat", // or absent — default behavior |
| 61 | title: "Rust P2P Dev", |
| 62 | description: "Building distributed systems in Rust", |
| 63 | topics: ["rust", "p2p", "systems"], |
| 64 | discoverable: true, // opt-in to DHT topic announcement |
| 65 | bridgedTo: [ // set by bridge bots when they connect |
| 66 | { platform: "irc", network: "libera", channel: "#rust-p2p" } |
| 67 | ], |
| 68 | relatedRooms: ["roomId1", "roomId2"] // admin-curated links |
| 69 | } |
| 70 | ``` |
| 71 | |
| 72 | ### Registry Room |
| 73 | |
| 74 | A room whose purpose is listing other rooms. Members post structured messages as room listings. The room's membership and roles control who can list rooms. |
| 75 | |
| 76 | ```javascript |
| 77 | room.config = { |
| 78 | type: "registry", |
| 79 | title: "Berlin P2P Meetup", |
| 80 | description: "Rooms for the Berlin Hypercore community", |
| 81 | topics: ["berlin", "p2p", "meetup"], |
| 82 | avatar_small: "base64...", // group icon |
| 83 | avatar_large: "base64..." |
| 84 | } |
| 85 | |
| 86 | // Listing a room = posting a structured message: |
| 87 | { |
| 88 | type: "room-listing", |
| 89 | roomId: "abc123...", |
| 90 | title: "Hypercore Study Group", |
| 91 | description: "Weekly deep-dives into Hypercore internals", |
| 92 | topics: ["hypercore", "study-group"], |
| 93 | inviteToken: "xyz...", |
| 94 | listedBy: "member:alice0" |
| 95 | } |
| 96 | |
| 97 | // Unlisting = posting a removal: |
| 98 | { |
| 99 | type: "room-unlisting", |
| 100 | roomId: "abc123...", |
| 101 | unlistedBy: "member:alice0" |
| 102 | } |
| 103 | ``` |
| 104 | |
| 105 | **Why this works:** |
| 106 | - Room roles = registry roles (admin can curate, members can browse) |
| 107 | - Room bans = registry bans (ban a spammer from listing) |
| 108 | - Room invites = registry invites (share access to the registry) |
| 109 | - Chat history = listing history (searchable, versioned, auditable) |
| 110 | - No new Autobase. No writer key management. No view corruption. |
| 111 | |
| 112 | ### Profile Room |
| 113 | |
| 114 | Each user has a personal room that IS their public profile. They control it. Others follow by joining. |
| 115 | |
| 116 | ```javascript |
| 117 | room.config = { |
| 118 | type: "profile", |
| 119 | title: "alice0's profile", |
| 120 | description: "Rust developer, Berlin", |
| 121 | topics: ["rust", "berlin", "p2p"], |
| 122 | publicRooms: true // signals that this profile shares room listings |
| 123 | } |
| 124 | |
| 125 | // Sharing a room = posting a structured message: |
| 126 | { |
| 127 | type: "room-share", |
| 128 | roomId: "def456...", |
| 129 | title: "Hypercore Dev Chat", |
| 130 | topics: ["hypercore"], |
| 131 | inviteToken: "abc...", // optional — only if room is open |
| 132 | sharedAt: 1708402400000 |
| 133 | } |
| 134 | ``` |
| 135 | |
| 136 | **UX flow:** |
| 137 | 1. Alice clicks Bob's name in any shared room |
| 138 | 2. She sees his member info (already works) |
| 139 | 3. New: "Follow" button appears |
| 140 | 4. Follow = join Bob's profile room (invite auto-created, or discoverable) |
| 141 | 5. In Bob's profile room, she sees his shared rooms as messages |
| 142 | 6. She joins any that interest her via posted invite tokens |
| 143 | 7. Her local MI computation now has data about Bob's rooms |
| 144 | 8. Unfollow = leave the profile room |
| 145 | |
| 146 | **Why this solves user discovery:** |
| 147 | - keet-core cannot store arbitrary fields on identity profiles (fixed schema) |
| 148 | - Profile rooms work around this using existing room primitives |
| 149 | - "Follow" is just room membership — no new concept |
| 150 | - The profile room IS the user's public presence |
| 151 | - Auto-created during identity setup (one extra `createRoom` call) |
| 152 | |
| 153 | ## Room Metadata Vocabulary |
| 154 | |
| 155 | These are conventions for `setConfig` keys. No code changes to keet-core needed. Any room can use any of these. |
| 156 | |
| 157 | ### Content Metadata |
| 158 | |
| 159 | ``` |
| 160 | topics : string[] — ["rust", "p2p", "gaming"] |
| 161 | language : string — "en", "de", "jp" |
| 162 | category : string — "development", "social", "gaming", "music" |
| 163 | ``` |
| 164 | |
| 165 | Enables: content-based clustering, DHT topic discovery, search. |
| 166 | |
| 167 | ### Structural Metadata |
| 168 | |
| 169 | ``` |
| 170 | relatedRooms : string[] — ["roomId1", "roomId2"] (admin-curated links) |
| 171 | parentRoom : string — "roomIdX" (thread/sub-room relationship) |
| 172 | bridgedTo : object[] — [{platform, network, channel}] (set by bridge bots) |
| 173 | ``` |
| 174 | |
| 175 | Enables: explicit graph edges, hierarchy, cross-platform topology. |
| 176 | |
| 177 | ### Discovery Metadata |
| 178 | |
| 179 | ``` |
| 180 | discoverable : "true" — opt-in to DHT topic announcement |
| 181 | invitePolicy : string — "open" | "invite-only" | "approval" |
| 182 | publicDescription : string — what non-members see (may differ from description) |
| 183 | ``` |
| 184 | |
| 185 | Enables: DHT discovery, invite flow, privacy control. |
| 186 | |
| 187 | ### Type Metadata |
| 188 | |
| 189 | ``` |
| 190 | type : string — "chat" | "registry" | "profile" | "wiki" | "board" |
| 191 | ``` |
| 192 | |
| 193 | Enables: room type conventions, UI behavior switching. |
| 194 | |
| 195 | ## The Graph: Local Computation, Not Shared State |
| 196 | |
| 197 | ### Input (from rooms you're in) |
| 198 | |
| 199 | ``` |
| 200 | For each room you're a member of: |
| 201 | - room.config (topics, type, related rooms, bridges) |
| 202 | - room.members (member IDs) |
| 203 | |
| 204 | For each profile room you follow: |
| 205 | - room-share messages (rooms the person has shared) |
| 206 | ``` |
| 207 | |
| 208 | All of this data is already on your machine. keet-core replicated it when you joined the rooms. |
| 209 | |
| 210 | ### Computation (local, in-memory) |
| 211 | |
| 212 | **Step 1: Build co-occurrence matrix** |
| 213 | |
| 214 | For every pair of rooms (A, B), count how many members appear in both: |
| 215 | |
| 216 | ``` |
| 217 | members_A = {alice, bob, carol} |
| 218 | members_B = {bob, carol, dave} |
| 219 | shared(A, B) = {bob, carol} → count = 2 |
| 220 | ``` |
| 221 | |
| 222 | **Step 2: Compute MI** |
| 223 | |
| 224 | ``` |
| 225 | MI(A, B) = log2( P(A,B) / (P(A) × P(B)) ) |
| 226 | |
| 227 | P(A,B) = shared(A,B) / total_member_room_pairs |
| 228 | P(A) = |members_A| / total_members |
| 229 | P(B) = |members_B| / total_members |
| 230 | ``` |
| 231 | |
| 232 | High MI = rooms share more members than chance predicts = structurally connected. |
| 233 | |
| 234 | **Step 3: Cluster** |
| 235 | |
| 236 | Build maximum spanning tree from MI scores. Cut edges below threshold. Connected components = groups. |
| 237 | |
| 238 | **Step 4: Extract room archetypes (optional)** |
| 239 | |
| 240 | Rooms with similar connection patterns (similar MI profiles) are the same "type" even if they have different topics. A "social hub" room connects broadly. A "focused dev" room connects narrowly. A "bridge portal" connects to platforms. The connector pattern reveals the role. |
| 241 | |
| 242 | ### Output (used by UI) |
| 243 | |
| 244 | ```javascript |
| 245 | { |
| 246 | clusters: [ |
| 247 | { id: 0, rooms: ["abc", "def", "ghi"], label: "Rust P2P", topics: ["rust", "p2p"] }, |
| 248 | { id: 1, rooms: ["jkl", "mno"], label: "Berlin Social", topics: ["berlin"] } |
| 249 | ], |
| 250 | edges: [ |
| 251 | { from: "abc", to: "def", mi: 3.2 }, |
| 252 | { from: "abc", to: "ghi", mi: 2.8 } |
| 253 | ], |
| 254 | suggestions: [ |
| 255 | { room: "pqr", reason: "3 shared members with your Rust cluster", via: "member:bob0" } |
| 256 | ] |
| 257 | } |
| 258 | ``` |
| 259 | |
| 260 | This powers: group bar ordering, room list sorting, graph view, "suggested rooms." |
| 261 | |
| 262 | ### In AtomSpace Terms |
| 263 | |
| 264 | The computation maps exactly to the OpenCog language learning pipeline: |
| 265 | |
| 266 | ```scheme |
| 267 | ;; INPUT ATOMS (from keet-core room data): |
| 268 | |
| 269 | ;; Membership — the raw observation (like a word in a sentence) |
| 270 | (MemberLink |
| 271 | (ConceptNode "member:alice0") |
| 272 | (ConceptNode "room:abc123")) |
| 273 | |
| 274 | ;; Room metadata (from room config) |
| 275 | (EvaluationLink |
| 276 | (PredicateNode "has-topic") |
| 277 | (ListLink |
| 278 | (ConceptNode "room:abc123") |
| 279 | (ConceptNode "topic:rust"))) |
| 280 | |
| 281 | ;; Bridge connection (from room config, set by bridge bot) |
| 282 | (EvaluationLink |
| 283 | (PredicateNode "bridged-to") |
| 284 | (ListLink |
| 285 | (ConceptNode "room:abc123") |
| 286 | (ConceptNode "platform:irc"))) |
| 287 | |
| 288 | ;; COMPUTED ATOMS (local, ephemeral): |
| 289 | |
| 290 | ;; Co-occurrence with MI value |
| 291 | (EvaluationLink |
| 292 | (PredicateNode "room-pair-mi") |
| 293 | (ListLink |
| 294 | (ConceptNode "room:abc123") |
| 295 | (ConceptNode "room:def456"))) |
| 296 | ;; FloatValue 3.2 attached as MI score |
| 297 | ;; FloatValue 7 attached as shared member count |
| 298 | |
| 299 | ;; Cluster membership (from MST + threshold cut) |
| 300 | (MemberLink |
| 301 | (ConceptNode "room:abc123") |
| 302 | (ConceptNode "cluster:0")) |
| 303 | |
| 304 | ;; Room archetype / Section (connection grammar) |
| 305 | (Section |
| 306 | (ConceptNode "room:abc123") |
| 307 | (ConnectorSeq |
| 308 | (Connector (ConceptNode "topic:rust") (ConnectorDir "+")) |
| 309 | (Connector (ConceptNode "topic:p2p") (ConnectorDir "+")) |
| 310 | (Connector (ConceptNode "platform:irc") (ConnectorDir "+")))) |
| 311 | ``` |
| 312 | |
| 313 | **The pipeline is recursive.** The same computation that clusters rooms into groups can cluster groups into meta-groups. Same algorithm, same atoms, different scale. One piece of code, N levels of hierarchy. |
| 314 | |
| 315 | ```scheme |
| 316 | ;; Level 0: rooms → groups (MI on shared members) |
| 317 | ;; Level 1: groups → meta-groups (MI on shared rooms between groups) |
| 318 | ;; Level 2: meta-groups → communities (MI on shared groups across platforms) |
| 319 | ``` |
| 320 | |
| 321 | The hierarchy exists in the computation, not in stored data structures. Each user sees their own computed hierarchy from their own local data. Different users may compute different clusters — that's privacy, not a bug. |
| 322 | |
| 323 | ## Discovery Without Global Registries |
| 324 | |
| 325 | ### How It Works Now (manual, top-down) |
| 326 | |
| 327 | ``` |
| 328 | 1. Someone creates a registry Autobase |
| 329 | 2. They share the 64-char hex key out-of-band |
| 330 | 3. You paste the key into godot-keet |
| 331 | 4. Your node initializes the Autobase, replicates it |
| 332 | 5. You browse rooms listed in the registry |
| 333 | ``` |
| 334 | |
| 335 | ### How It Works With Rooms (organic, bottom-up) |
| 336 | |
| 337 | **Path 1: Through people (social traversal)** |
| 338 | ``` |
| 339 | 1. You're in room X with Bob |
| 340 | 2. You click Bob's name → "Follow" |
| 341 | 3. You join Bob's profile room |
| 342 | 4. You see rooms Bob has shared |
| 343 | 5. You join one → meet Carol → follow Carol → discover more rooms |
| 344 | 6. Your local MI computation clusters everything automatically |
| 345 | ``` |
| 346 | |
| 347 | **Path 2: Through topics (DHT discovery)** |
| 348 | ``` |
| 349 | 1. Your room has topics: ["rust", "p2p"] |
| 350 | 2. Room config has discoverable: "true" |
| 351 | 3. keet-daemon announces on hash("hyper-social:topic:rust") |
| 352 | 4. Someone else searches for "rust" on the DHT |
| 353 | 5. They find your room's announcement (title, description, invite info) |
| 354 | 6. They join → the graph grows |
| 355 | ``` |
| 356 | |
| 357 | **Path 3: Through registries (curated directories — still rooms)** |
| 358 | ``` |
| 359 | 1. Someone creates a registry room for "Berlin P2P Meetup" |
| 360 | 2. They share the room invite (not a 64-char hex key — a normal invite) |
| 361 | 3. You join the registry room |
| 362 | 4. You browse room listings posted as messages |
| 363 | 5. You join rooms that interest you |
| 364 | 6. The registry room shows up as a group in your group bar |
| 365 | ``` |
| 366 | |
| 367 | **Path 4: Through computation (emergent groups)** |
| 368 | ``` |
| 369 | 1. You're in 5 rooms that share 80% of their members |
| 370 | 2. Your local MI computation detects this cluster |
| 371 | 3. Group bar shows a "?" icon: "5 rooms with high overlap — name this group?" |
| 372 | 4. You name it → it becomes a local group label |
| 373 | 5. Optionally: create a registry room for it → others can browse it too |
| 374 | ``` |
| 375 | |
| 376 | All four paths feed the same local graph. The group bar shows the result: a mix of named groups (from registry rooms you've joined), computed clusters (from MI), and followed profiles (from profile rooms). The user doesn't need to know which mechanism discovered which group. |
| 377 | |
| 378 | ## What Changes in Code |
| 379 | |
| 380 | ### keet-daemon |
| 381 | |
| 382 | **Remove (~870 lines):** |
| 383 | ``` |
| 384 | lib/room-discovery.js — Autobase registry class (~500 lines) |
| 385 | index.js discovery plumbing — config, eager init, heartbeat (~170 lines) |
| 386 | router.js discovery routes — register, unregister, list, etc. (~200 lines) |
| 387 | ``` |
| 388 | |
| 389 | **Add (~480 lines):** |
| 390 | ``` |
| 391 | lib/registry-room.js (~80) — Post/read room-listing messages in registry rooms |
| 392 | lib/profile-room.js (~60) — Create profile room on identity setup, post room-shares |
| 393 | lib/local-graph.js (~200) — MI computation on local membership data |
| 394 | lib/topic-announce.js (~60) — DHT announce/search for discoverable rooms |
| 395 | router.js new routes (~80) — registry.*, profile.*, graph.*, topic.* routes |
| 396 | ``` |
| 397 | |
| 398 | **Net: ~870 removed, ~480 added. Fewer lines AND simpler code.** |
| 399 | |
| 400 | The 480 new lines are trivial compared to the 870 they replace: |
| 401 | - `registry-room.js` calls existing room/chat APIs |
| 402 | - `profile-room.js` calls existing room/identity APIs |
| 403 | - `local-graph.js` is pure math on data already in memory |
| 404 | - `topic-announce.js` is a thin DHT wrapper |
| 405 | - No Autobase lifecycle management, no writer keys, no view corruption handling |
| 406 | |
| 407 | ### godot-keet |
| 408 | |
| 409 | **Simplify:** |
| 410 | ``` |
| 411 | keet_discovery.gd (419→~200 lines) |
| 412 | — Remove: Autobase registry management, writer keys, admin roles |
| 413 | — Keep: list rooms, search, register/unregister (now calls room APIs) |
| 414 | |
| 415 | group_bar.gd (446 lines, same size, different logic) |
| 416 | — Before: fetch registries from discovery config |
| 417 | — After: show computed clusters + joined registry rooms + followed profiles |
| 418 | ``` |
| 419 | |
| 420 | **Add:** |
| 421 | ``` |
| 422 | keet_profile.gd (~100 lines) |
| 423 | — Follow/unfollow users, read shared rooms from profile rooms |
| 424 | — "Follow" button on member popup |
| 425 | |
| 426 | keet_graph.gd (~150 lines) |
| 427 | — Request MI computation results from daemon |
| 428 | — Feed data to group_bar and (later) graph view |
| 429 | ``` |
| 430 | |
| 431 | **Modify:** |
| 432 | ``` |
| 433 | keet_service.gd — add profile_mgr, graph_mgr (2 lines) |
| 434 | member_panel.gd — add "Follow" button to member context (20 lines) |
| 435 | chat_panel.gd — detect bridge message prefix, show badge (30 lines) |
| 436 | ``` |
| 437 | |
| 438 | ### index.js (launcher) |
| 439 | |
| 440 | **One line changes:** |
| 441 | ```javascript |
| 442 | // Before: |
| 443 | import { startDaemon } from 'keet-daemon' |
| 444 | // After: |
| 445 | import { startHub } from 'hyper-social-hub' |
| 446 | // (startHub wraps startDaemon + adds hub routes) |
| 447 | ``` |
| 448 | |
| 449 | Or: keet-daemon absorbs the hub routes directly (no separate package needed for Phase 1). The routes are thin enough to add to the existing router. |
| 450 | |
| 451 | ## UI Philosophy |
| 452 | |
| 453 | From the research and this conversation, the UI principles: |
| 454 | |
| 455 | ### Sparse by Default |
| 456 | |
| 457 | A UI that would be better if it wasn't there. Messages, names, avatars ARE the interface. Typography and spacing do the work. No decoration that isn't information. |
| 458 | |
| 459 | ### Contextual, Not Comprehensive |
| 460 | |
| 461 | The interface is a different tool depending on what you're doing: |
| 462 | - **Chatting**: message stream + input, everything else fades |
| 463 | - **Navigating**: group bar + room list, chat dims |
| 464 | - **Discovering**: profile rooms, registry rooms, topic search |
| 465 | - **Managing**: room settings, bridge config, moderation |
| 466 | |
| 467 | Show only what's relevant to the current mode. Fade, don't remove — maintaining spatial memory. |
| 468 | |
| 469 | ### Ambient State, Not Badges |
| 470 | |
| 471 | Room activity as subtle glow or pulse on group bar icons, not red circles with numbers. Unread state as color shift, not notification count. The emotional weight of a number ("47 unread") creates anxiety. A warm glow says "there's life here" without pressure. |
| 472 | |
| 473 | ### Three Levels Max |
| 474 | |
| 475 | Group rail (60px) + room list + active content. That's it. Deeper navigation through drilldown (click into a room, click into a thread), not through more panels. The group bar shows computed clusters alongside named groups — no distinction in the UI between "groups someone created" and "groups the math found." |
| 476 | |
| 477 | ### The Board Panel Is Dead |
| 478 | |
| 479 | Kanban boards don't serve this context. If task tracking ever matters, it's a room-level addon someone builds later, not a core feature. Removing it from the roadmap. |
| 480 | |
| 481 | ### Wiki Is Inline |
| 482 | |
| 483 | A wiki page is a pinned, editable message — not a separate panel. You're discussing something, someone says "let's capture this," and the message becomes a wiki entry. Other people edit it in-place. The page list is a filtered view of chat history showing only wiki-type messages. No tab switching. The wiki lives in the conversation that created it. |
| 484 | |
| 485 | ```javascript |
| 486 | // A wiki entry is just a message with a type field: |
| 487 | { |
| 488 | type: "wiki", |
| 489 | title: "How to set up Hyperswarm", |
| 490 | content: "## Step 1\n\nInstall the package...", |
| 491 | editedBy: "member:alice0", |
| 492 | editedAt: 1708402400000 |
| 493 | } |
| 494 | ``` |
| 495 | |
| 496 | Renders inline as an expandable card. Click to read. Edit button for admins/mods. Version history = previous messages with the same title. |
| 497 | |
| 498 | ## Migration Path |
| 499 | |
| 500 | Each step is independently deployable. The existing app works at every stage. |
| 501 | |
| 502 | ### Step 1: Add room metadata conventions |
| 503 | |
| 504 | Set `topics`, `discoverable`, `type` on rooms via `setConfig`. No Autobase changes. No UI changes. Just establishing conventions. |
| 505 | |
| 506 | ### Step 2: Add profile rooms |
| 507 | |
| 508 | Create a profile room during identity setup. Add "Follow" button to member popups. Post room-share messages when user opts to share a room publicly. This is ~160 lines of new code total. |
| 509 | |
| 510 | ### Step 3: Add local graph computation |
| 511 | |
| 512 | Compute MI from membership data on a timer (every 60 seconds, or on room join/leave). Feed results to group bar for reordering. ~200 lines of JS, ~150 lines of GDScript. No Autobase changes. |
| 513 | |
| 514 | ### Step 4: Add registry rooms |
| 515 | |
| 516 | Create a new room type "registry." Post room-listing messages instead of Autobase entries. The UI for browsing registries stays identical — it's just reading messages instead of Autobase records. ~80 lines of JS, ~100 lines of GDScript changes. |
| 517 | |
| 518 | ### Step 5: Add DHT topic discovery |
| 519 | |
| 520 | Rooms with `discoverable: "true"` announce on topic-hashed DHT keys. Search by topic. ~60 lines of JS, ~80 lines of GDScript. No Autobase. |
| 521 | |
| 522 | ### Step 6: Remove RoomDiscovery Autobase |
| 523 | |
| 524 | Once registry rooms are stable and profile rooms handle user discovery, remove the hand-crafted Autobase code. ~870 lines deleted. The existing discovery registries migrate to registry rooms (one-time conversion). |
| 525 | |
| 526 | ### Step 7: Add bridge awareness |
| 527 | |
| 528 | Bridges set `bridgedTo` in room config when they connect. Chat panel detects bridge message prefixes and shows platform badges. ~30 lines in chat_panel.gd. |
| 529 | |
| 530 | ### Step 8: Add graph view (optional) |
| 531 | |
| 532 | 2D force-directed visualization of the computed graph. Rooms as circles, MI as edges, clusters as colors. Click to enter room. This is the visual expression of what the group bar already uses internally. ~400 lines of GDScript. Pure UI — no new data infrastructure. |
| 533 | |
| 534 | ## What the Existing Docs Got Right |
| 535 | |
| 536 | - **SOCIAL-GRAPH-NAVIGATION.md** — The math is correct. MI, MST, clustering, sheaf theory. All stays. |
| 537 | - **COMMUNITY-EMERGENCE.md** — The sociology is correct. Phase transitions, nucleation, three sovereignties. All stays. |
| 538 | - **AUTO-DISCOVERY.md** — The layered approach is correct (Layers 0-4). The implementation changes from "new Autobases" to "room conventions + local computation." |
| 539 | - **ROADMAP.md** — The phased evolution is correct. The Hub wrapping keet-daemon is correct. The specific phases change (drop board panel, inline wiki, add profile rooms). |
| 540 | - **ARCHITECTURE.md** — The fracture principle is correct. Independent projects composing into a system. |
| 541 | |
| 542 | ## What the Existing Docs Got Wrong |
| 543 | |
| 544 | - **AUTO-DISCOVERY-SPEC.md** — Proposed new Autobases (topic-discovery, mi-engine as stored state). Wrong tool. Replace with rooms + local computation. This document supersedes it. |
| 545 | - **ROADMAP.md Phase 5** — Board panel doesn't serve this context. Drop it. |
| 546 | - **ROADMAP.md Phase 5** — Wiki panel as separate tab. Wrong UX. Wiki is inline in chat. |
| 547 | - **ROADMAP.md Phase 1** — Hub-daemon as separate package may be unnecessary. The routes are thin enough to add to keet-daemon's router directly. Decide during implementation. |
| 548 | - **AUTO-DISCOVERY-SPEC.md** — Proposed ~1,400 LOC of changes. Actual changes are ~480 LOC of additions (after removing ~870 LOC of Autobase code). Net simpler. |
| 549 | |
| 550 | ## Gaps Still Open |
| 551 | |
| 552 | 1. **Profile room discoverability** — How does Alice find Bob's profile room if they aren't in a shared room yet? Answer: she can't, and that's correct. Discovery happens through shared rooms, not through a global directory. This IS the privacy model. |
| 553 | |
| 554 | 2. **Registry room migration** — Existing discovery registries (Global, testy) need to be migrated to registry rooms. One-time conversion: read all entries from the Autobase, create a room, post them as messages. The Autobase discovery key becomes the room's invite. |
| 555 | |
| 556 | 3. **Bridge bot identity** — When a bridge connects to a room and sets `bridgedTo` in config, it needs admin access. Bridge bots should be added as room members with moderator role (not admin — they shouldn't be able to delete the room). |
| 557 | |
| 558 | 4. **Profile room spam** — If "Follow" is just joining a room, and the profile room is discoverable, bots could spam it. Mitigation: profile rooms default to invite-only. Following requires a mutual connection (you're both in at least one room). |
| 559 | |
| 560 | 5. **MI computation cost** — For users in 100+ rooms with 1000+ total members, the pairwise MI computation is O(n² rooms × m members). Benchmark needed. May need sparse representation or incremental updates. |
| 561 | |
| 562 | 6. **Cross-platform identity linking** — COMMUNITY-EMERGENCE.md discusses it, ROADMAP.md Phase 6 plans it, but no spec exists. Likely: identity links are messages in profile rooms — "I am also alice on IRC Libera" — cryptographically signed by both identities. Implementation TBD. |
| 563 | |
| 564 | 7. **Moderation service** — COMMUNITY-EMERGENCE.md describes moderation as a subscribable label service (Blacksky pattern). No implementation spec. Likely Phase 8. Could be: a room whose messages are moderation labels, subscribable by other rooms. |
| 565 | |
| 566 | ## The Principle |
| 567 | |
| 568 | The community already exists. The rooms already exist. The people already exist. |
| 569 | |
| 570 | Rooms are the computer. Everything else is either room config, room messages, or local math on room data. One Autobase type (managed by keet-core). One computation (MI pipeline). One UI pattern (sparse, contextual, ambient). |
| 571 | |
| 572 | The structure was always there. The tools just need to see it. |