| … | … | @@ -29,7 +29,7 @@ OLD (3+ Autobase types, hand-crafted): |
| 29 | 29 | NEW (1 Autobase type, all keet-core): |
| 30 | 30 | Room ← Autobase (keet-core) ✓ works great |
| 31 | 31 | Registry ← Room (it's just a room) ✓ reuses keet-core |
| 32 | | Profile ← Room (it's just a room) ✓ reuses keet-core |
| 32 | DM ← Room (RoomType=2) ✓ already in keet-core |
| 33 | 33 | Everything ← Room config + messages ✓ already exists |
| 34 | 34 | Graph ← Local computation ✓ no storage needed |
| 35 | 35 | ``` |
| … | … | @@ -109,46 +109,72 @@ room.config = { |
| 109 | 109 | - Chat history = listing history (searchable, versioned, auditable) |
| 110 | 110 | - No new Autobase. No writer key management. No view corruption. |
| 111 | 111 | |
| 112 | | ### Profile Room |
| 112 | ### DMs as Social Graph Edges |
| 113 | 113 | |
| 114 | | Each user has a personal room that IS their public profile. They control it. Others follow by joining. |
| 114 | keet-core already has DMs. They're `RoomType=2` — rooms with exactly 2 members, blind-pairing delivery, request/accept flow, read receipts. We don't need to build DMs. We need to use them for discovery. |
| 115 | 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 | | } |
| 116 | **A DM is both a conversation AND a social connection.** Room shares posted in DMs are how you discover each other's rooms. Each share is a separate editable message — update the invite token by editing, remove by deleting. |
| 117 | |
| 118 | **keet-core DM API (already implemented):** |
| 124 | 119 | |
| 125 | | // Sharing a room = posting a structured message: |
| 120 | | Route | What it does | |
| 121 | |-------|-------------| |
| 122 | | `dm.request(roomId, memberId, message)` | Send DM request via blind pairing | |
| 123 | | `dm.accept(roomId)` | Accept incoming DM request | |
| 124 | | `dm.reject(roomId)` | Reject incoming DM request | |
| 125 | | `dm.list()` | List accepted DMs (status=1) | |
| 126 | | `dm.requests(status)` | List pending requests (status=3) | |
| 127 | | `dm.fromContact(memberId)` | DM from contact info directly | |
| 128 | |
| 129 | **Room sharing in DMs — each share is one editable message:** |
| 130 | |
| 131 | ```javascript |
| 132 | // Share a room = post a structured message in the DM: |
| 126 | 133 | { |
| 127 | 134 | type: "room-share", |
| 128 | 135 | roomId: "def456...", |
| 129 | 136 | title: "Hypercore Dev Chat", |
| 130 | 137 | topics: ["hypercore"], |
| 131 | | inviteToken: "abc...", // optional — only if room is open |
| 138 | inviteToken: "abc...", |
| 132 | 139 | sharedAt: 1708402400000 |
| 133 | 140 | } |
| 141 | |
| 142 | // Update the invite token = edit that message |
| 143 | // Unshare = delete that message |
| 144 | // List shared rooms = load DM history, filter by type: "room-share" |
| 134 | 145 | ``` |
| 135 | 146 | |
| 136 | 147 | **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) |
| 148 | 1. Alice clicks Bob's name in a shared room → "Message" |
| 149 | 2. DM request sent via blind pairing (keet-core handles this) |
| 150 | 3. Bob accepts the DM request |
| 151 | 4. They can chat normally — AND share rooms as structured messages |
| 152 | 5. Alice sees Bob's shared rooms as joinable cards in the DM |
| 153 | 6. She joins one → meets Carol → DMs Carol → discovers more rooms |
| 154 | 7. Her local MI computation includes rooms shared via all her DMs |
| 155 | |
| 156 | **Why DMs beat profile rooms:** |
| 157 | |
| 158 | | Property | Profile Room | DM | |
| 159 | |----------|-------------|-----| |
| 160 | | Followers see each other | Yes (privacy leak) | No | |
| 161 | | Per-person sharing | No (everyone sees same) | Yes (share different rooms with different people) | |
| 162 | | New room type needed | Yes ("profile") | No (DMs already exist in keet-core) | |
| 163 | | New concept for users | Yes ("follow") | No ("message" — already understood) | |
| 164 | | Spam protection | Manual (invite-only) | Built-in (request/accept flow) | |
| 165 | | Chat + sharing | Awkward | Natural (DMs have chat by design) | |
| 166 | | Selective sharing | Impossible | Yes (share rust rooms with devs, gaming rooms with gamers) | |
| 167 | | Already implemented | No | Yes (6 API methods in keet-core) | |
| 168 | |
| 169 | **Privacy properties:** |
| 170 | - DM contacts don't know about each other (no shared member list) |
| 171 | - You control what you share per-relationship |
| 172 | - No broadcast — you think about who you're sharing with |
| 173 | - Request/accept prevents drive-by following (no spam) |
| 174 | - The client can offer "share to..." with a multi-DM contact picker for convenience |
| 175 | |
| 176 | **Sharing with multiple contacts:** |
| 177 | The client provides a "Share Room" action: pick a room, pick contacts from your DM list, post the room-share message to each selected DM. This is explicit, intentional sharing — not broadcasting to an audience you can't see. |
| 152 | 178 | |
| 153 | 179 | ## Room Metadata Vocabulary |
| 154 | 180 | |
| … | … | @@ -187,7 +213,7 @@ Enables: DHT discovery, invite flow, privacy control. |
| 187 | 213 | ### Type Metadata |
| 188 | 214 | |
| 189 | 215 | ``` |
| 190 | | type : string — "chat" | "registry" | "profile" | "wiki" | "board" |
| 216 | type : string — "chat" | "registry" | "dm" (RoomType=2, set by keet-core) |
| 191 | 217 | ``` |
| 192 | 218 | |
| 193 | 219 | Enables: room type conventions, UI behavior switching. |
| … | … | @@ -201,11 +227,16 @@ For each room you're a member of: |
| 201 | 227 | - room.config (topics, type, related rooms, bridges) |
| 202 | 228 | - room.members (member IDs) |
| 203 | 229 | |
| 204 | | For each profile room you follow: |
| 205 | | - room-share messages (rooms the person has shared) |
| 230 | For each DM you have: |
| 231 | - room-share messages (rooms this contact has shared with you) |
| 232 | - The DM itself is a social edge (you know this person) |
| 206 | 233 | ``` |
| 207 | 234 | |
| 208 | | All of this data is already on your machine. keet-core replicated it when you joined the rooms. |
| 235 | All of this data is already on your machine. keet-core replicated it when you joined the rooms and accepted the DMs. Three signal types feed the graph: |
| 236 | |
| 237 | - **Direct** — rooms you're actually in (strongest signal) |
| 238 | - **One-hop** — rooms your DM contacts shared with you (medium signal) |
| 239 | - **Two-hop** — rooms shared by people in rooms your contacts shared (weak, optional) |
| 209 | 240 | |
| 210 | 241 | ### Computation (local, in-memory) |
| 211 | 242 | |
| … | … | @@ -285,6 +316,22 @@ The computation maps exactly to the OpenCog language learning pipeline: |
| 285 | 316 | (ConceptNode "room:abc123") |
| 286 | 317 | (ConceptNode "platform:irc"))) |
| 287 | 318 | |
| 319 | ;; DM relationship — a social edge (RoomType=2, already in keet-core) |
| 320 | (EvaluationLink |
| 321 | (PredicateNode "has-dm") |
| 322 | (ListLink |
| 323 | (ConceptNode "member:alice0") |
| 324 | (ConceptNode "member:bob0"))) |
| 325 | |
| 326 | ;; Room shared via DM — one-hop discovery |
| 327 | (EvaluationLink |
| 328 | (PredicateNode "shared-via-dm") |
| 329 | (ListLink |
| 330 | (ConceptNode "member:bob0") |
| 331 | (ConceptNode "room:def456") |
| 332 | (ConceptNode "member:alice0"))) |
| 333 | ;; "bob shared room def456 with alice" |
| 334 | |
| 288 | 335 | ;; COMPUTED ATOMS (local, ephemeral): |
| 289 | 336 | |
| 290 | 337 | ;; Co-occurrence with MI value |
| … | … | @@ -334,13 +381,13 @@ The hierarchy exists in the computation, not in stored data structures. Each use |
| 334 | 381 | |
| 335 | 382 | ### How It Works With Rooms (organic, bottom-up) |
| 336 | 383 | |
| 337 | | **Path 1: Through people (social traversal)** |
| 384 | **Path 1: Through people (DM-based social traversal)** |
| 338 | 385 | ``` |
| 339 | 386 | 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 |
| 387 | 2. You click Bob's name → "Message" |
| 388 | 3. DM request sent (blind pairing), Bob accepts |
| 389 | 4. Bob shares rooms with you as editable messages in the DM |
| 390 | 5. You join one → meet Carol → DM Carol → discover more rooms |
| 344 | 391 | 6. Your local MI computation clusters everything automatically |
| 345 | 392 | ``` |
| 346 | 393 | |
| … | … | @@ -373,7 +420,7 @@ The hierarchy exists in the computation, not in stored data structures. Each use |
| 373 | 420 | 5. Optionally: create a registry room for it → others can browse it too |
| 374 | 421 | ``` |
| 375 | 422 | |
| 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. |
| 423 | 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 rooms shared via DMs. The user doesn't need to know which mechanism discovered which group. |
| 377 | 424 | |
| 378 | 425 | ## What Changes in Code |
| 379 | 426 | |
| … | … | @@ -386,20 +433,21 @@ index.js discovery plumbing — config, eager init, heartbeat (~170 lines) |
| 386 | 433 | router.js discovery routes — register, unregister, list, etc. (~200 lines) |
| 387 | 434 | ``` |
| 388 | 435 | |
| 389 | | **Add (~480 lines):** |
| 436 | **Add (~440 lines):** |
| 390 | 437 | ``` |
| 391 | 438 | 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 |
| 439 | lib/dm-shares.js (~60) — Post/read room-share messages in DM rooms |
| 393 | 440 | lib/local-graph.js (~200) — MI computation on local membership data |
| 394 | 441 | lib/topic-announce.js (~60) — DHT announce/search for discoverable rooms |
| 395 | | router.js new routes (~80) — registry.*, profile.*, graph.*, topic.* routes |
| 442 | router.js new routes (~40) — registry.*, graph.*, topic.* routes |
| 443 | (DM routes already exist: dm.request, dm.accept, etc.) |
| 396 | 444 | ``` |
| 397 | 445 | |
| 398 | | **Net: ~870 removed, ~480 added. Fewer lines AND simpler code.** |
| 446 | **Net: ~870 removed, ~440 added. Fewer lines AND simpler code.** |
| 399 | 447 | |
| 400 | | The 480 new lines are trivial compared to the 870 they replace: |
| 448 | The 440 new lines are trivial compared to the 870 they replace: |
| 401 | 449 | - `registry-room.js` calls existing room/chat APIs |
| 402 | | - `profile-room.js` calls existing room/identity APIs |
| 450 | - `dm-shares.js` calls existing DM + chat APIs (DM infrastructure already built) |
| 403 | 451 | - `local-graph.js` is pure math on data already in memory |
| 404 | 452 | - `topic-announce.js` is a thin DHT wrapper |
| 405 | 453 | - No Autobase lifecycle management, no writer keys, no view corruption handling |
| … | … | @@ -419,9 +467,12 @@ group_bar.gd (446 lines, same size, different logic) |
| 419 | 467 | |
| 420 | 468 | **Add:** |
| 421 | 469 | ``` |
| 422 | | keet_profile.gd (~100 lines) |
| 423 | | — Follow/unfollow users, read shared rooms from profile rooms |
| 424 | | — "Follow" button on member popup |
| 470 | keet_dm.gd (~120 lines) |
| 471 | — DM lifecycle: create, list, accept/reject requests |
| 472 | — Room sharing: post/edit/delete room-share messages in DMs |
| 473 | — Read shared rooms from DM history |
| 474 | — "Message" button on member popup |
| 475 | — "Share Room" action with multi-contact picker |
| 425 | 476 | |
| 426 | 477 | keet_graph.gd (~150 lines) |
| 427 | 478 | — Request MI computation results from daemon |
| … | … | @@ -430,9 +481,10 @@ keet_graph.gd (~150 lines) |
| 430 | 481 | |
| 431 | 482 | **Modify:** |
| 432 | 483 | ``` |
| 433 | | keet_service.gd — add profile_mgr, graph_mgr (2 lines) |
| 434 | | member_panel.gd — add "Follow" button to member context (20 lines) |
| 484 | keet_service.gd — add dm_mgr, graph_mgr (2 lines) |
| 485 | member_panel.gd — add "Message" button to member context (20 lines) |
| 435 | 486 | chat_panel.gd — detect bridge message prefix, show badge (30 lines) |
| 487 | room_list_panel.gd — show DM rooms in a "Messages" section (40 lines) |
| 436 | 488 | ``` |
| 437 | 489 | |
| 438 | 490 | ### index.js (launcher) |
| … | … | @@ -503,13 +555,13 @@ Each step is independently deployable. The existing app works at every stage. |
| 503 | 555 | |
| 504 | 556 | Set `topics`, `discoverable`, `type` on rooms via `setConfig`. No Autobase changes. No UI changes. Just establishing conventions. |
| 505 | 557 | |
| 506 | | ### Step 2: Add profile rooms |
| 558 | ### Step 2: Add DM support to godot-keet |
| 507 | 559 | |
| 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. |
| 560 | keet-core already has DMs (6 API methods). Add `keet_dm.gd` to wrap them. Add "Message" button to member panel. Show DM rooms in room list. Add room-share message type for sharing rooms in DMs. This is ~120 lines of GDScript + ~60 lines of JS. |
| 509 | 561 | |
| 510 | 562 | ### Step 3: Add local graph computation |
| 511 | 563 | |
| 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. |
| 564 | Compute MI from membership data on a timer (every 60 seconds, or on room join/leave). Include rooms shared via DMs as one-hop data. Feed results to group bar for reordering. ~200 lines of JS, ~150 lines of GDScript. No Autobase changes. |
| 513 | 565 | |
| 514 | 566 | ### Step 4: Add registry rooms |
| 515 | 567 | |
| … | … | @@ -536,7 +588,7 @@ Bridges set `bridgedTo` in room config when they connect. Chat panel detects bri |
| 536 | 588 | - **SOCIAL-GRAPH-NAVIGATION.md** — The math is correct. MI, MST, clustering, sheaf theory. All stays. |
| 537 | 589 | - **COMMUNITY-EMERGENCE.md** — The sociology is correct. Phase transitions, nucleation, three sovereignties. All stays. |
| 538 | 590 | - **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). |
| 591 | - **ROADMAP.md** — The phased evolution is correct. The Hub wrapping keet-daemon is correct. The specific phases change (drop board panel, inline wiki, DMs instead of profile rooms). |
| 540 | 592 | - **ARCHITECTURE.md** — The fracture principle is correct. Independent projects composing into a system. |
| 541 | 593 | |
| 542 | 594 | ## What the Existing Docs Got Wrong |
| … | … | @@ -549,20 +601,22 @@ Bridges set `bridgedTo` in room config when they connect. Chat panel detects bri |
| 549 | 601 | |
| 550 | 602 | ## Gaps Still Open |
| 551 | 603 | |
| 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. |
| 604 | 1. **DM initiation requires shared room** — `dm.request` needs a `roomId` where both members are present. You can't DM someone you've never been in a room with. This is correct for privacy but means cold-contact discovery requires at least one shared room, registry room, or out-of-band exchange. `dm.fromContact` may work without a shared room (needs testing). |
| 553 | 605 | |
| 554 | 606 | 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 | 607 | |
| 556 | 608 | 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 | 609 | |
| 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). |
| 610 | 4. **DM room presentation** — DMs need to appear distinctly from group rooms in the room list. Either a separate "Messages" section in the room list, or a filter/tab. keet-core marks them as `RoomType=2` so filtering is straightforward. |
| 559 | 611 | |
| 560 | 612 | 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 | 613 | |
| 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. |
| 614 | 6. **Cross-platform identity linking** — COMMUNITY-EMERGENCE.md discusses it, ROADMAP.md Phase 6 plans it, but no spec exists. Likely: identity links are room-share messages in DMs — "I am also alice on IRC Libera" — signed by both identities. The DM is the trust channel. Implementation TBD. |
| 563 | 615 | |
| 564 | 616 | 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 | 617 | |
| 618 | 8. **Room-share message format** — Need a convention for structured messages. keet-core messages are text strings. Structured data must be JSON-encoded in the text field, with client-side parsing. Alternatively, messages may support metadata/attachments that could carry structured payloads. Needs investigation of what keet-core message fields support. |
| 619 | |
| 566 | 620 | ## The Principle |
| 567 | 621 | |
| 568 | 622 | The community already exists. The rooms already exist. The people already exist. |