| … | … | @@ -424,31 +424,35 @@ All four paths feed the same local graph. The group bar shows the result: a mix |
| 424 | 424 | |
| 425 | 425 | ## What Changes in Code |
| 426 | 426 | |
| 427 | See "Revised Code Estimates" in the Resolved Design Decisions section for the complete audit. |
| 428 | |
| 427 | 429 | ### keet-daemon |
| 428 | 430 | |
| 429 | | **Remove (~870 lines):** |
| 431 | **Remove (~1,600 lines):** |
| 430 | 432 | ``` |
| 431 | | lib/room-discovery.js — Autobase registry class (~500 lines) |
| 432 | | index.js discovery plumbing — config, eager init, heartbeat (~170 lines) |
| 433 | | router.js discovery routes — register, unregister, list, etc. (~200 lines) |
| 433 | lib/room-discovery.js — Autobase registry class (783 lines) |
| 434 | lib/room-discovery-apply.js — Autobase state machine (396 lines) |
| 435 | index.js discovery plumbing — config, eager init, heartbeat (170 lines) |
| 436 | router.js discovery routes — 19 RPC endpoints (213 lines) |
| 437 | test/discovery.js — test harness (40+ lines) |
| 434 | 438 | ``` |
| 435 | 439 | |
| 436 | 440 | **Add (~440 lines):** |
| 437 | 441 | ``` |
| 438 | | lib/registry-room.js (~80) — Post/read room-listing messages in registry rooms |
| 439 | | lib/dm-shares.js (~60) — Post/read room-share messages in DM rooms |
| 440 | | lib/local-graph.js (~200) — MI computation on local membership data |
| 442 | lib/registry-room.js (~80) — Post/read _hs:room-listing messages in registry rooms |
| 443 | lib/dm-shares.js (~60) — Post/read _hs:room-share messages in DM rooms |
| 444 | lib/local-graph.js (~200) — MI computation (inverted member index, sparse) |
| 441 | 445 | lib/topic-announce.js (~60) — DHT announce/search for discoverable rooms |
| 442 | 446 | router.js new routes (~40) — registry.*, graph.*, topic.* routes |
| 443 | 447 | (DM routes already exist: dm.request, dm.accept, etc.) |
| 444 | 448 | ``` |
| 445 | 449 | |
| 446 | | **Net: ~870 removed, ~440 added. Fewer lines AND simpler code.** |
| 450 | **Net: ~1,600 removed, ~440 added. 73% reduction.** |
| 447 | 451 | |
| 448 | | The 440 new lines are trivial compared to the 870 they replace: |
| 449 | | - `registry-room.js` calls existing room/chat APIs |
| 452 | The 440 new lines are trivial compared to the 1,600 they replace: |
| 453 | - `registry-room.js` calls existing room/chat APIs to post `_hs` messages |
| 450 | 454 | - `dm-shares.js` calls existing DM + chat APIs (DM infrastructure already built) |
| 451 | | - `local-graph.js` is pure math on data already in memory |
| 455 | - `local-graph.js` is pure math on data already in memory (~5ms for 500 rooms) |
| 452 | 456 | - `topic-announce.js` is a thin DHT wrapper |
| 453 | 457 | - No Autobase lifecycle management, no writer keys, no view corruption handling |
| 454 | 458 | |
| … | … | @@ -457,19 +461,19 @@ The 440 new lines are trivial compared to the 870 they replace: |
| 457 | 461 | **Simplify:** |
| 458 | 462 | ``` |
| 459 | 463 | keet_discovery.gd (419→~200 lines) |
| 460 | | — Remove: Autobase registry management, writer keys, admin roles |
| 464 | — Remove: Autobase registry management, writer keys, admin roles (33 methods → ~12) |
| 461 | 465 | — Keep: list rooms, search, register/unregister (now calls room APIs) |
| 462 | 466 | |
| 463 | 467 | group_bar.gd (446 lines, same size, different logic) |
| 464 | 468 | — Before: fetch registries from discovery config |
| 465 | | — After: show computed clusters + joined registry rooms + followed profiles |
| 469 | — After: show computed clusters + joined registry rooms |
| 466 | 470 | ``` |
| 467 | 471 | |
| 468 | 472 | **Add:** |
| 469 | 473 | ``` |
| 470 | 474 | keet_dm.gd (~120 lines) |
| 471 | 475 | — DM lifecycle: create, list, accept/reject requests |
| 472 | | — Room sharing: post/edit/delete room-share messages in DMs |
| 476 | — Room sharing: post/edit/delete _hs:room-share messages in DMs |
| 473 | 477 | — Read shared rooms from DM history |
| 474 | 478 | — "Message" button on member popup |
| 475 | 479 | — "Share Room" action with multi-contact picker |
| … | … | @@ -484,7 +488,7 @@ keet_graph.gd (~150 lines) |
| 484 | 488 | keet_service.gd — add dm_mgr, graph_mgr (2 lines) |
| 485 | 489 | member_panel.gd — add "Message" button to member context (20 lines) |
| 486 | 490 | 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) |
| 491 | room_list_panel.gd — show DM rooms in "Messages" section (40 lines) |
| 488 | 492 | ``` |
| 489 | 493 | |
| 490 | 494 | ### index.js (launcher) |
| … | … | @@ -535,14 +539,8 @@ Kanban boards don't serve this context. If task tracking ever matters, it's a ro |
| 535 | 539 | 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. |
| 536 | 540 | |
| 537 | 541 | ```javascript |
| 538 | | // A wiki entry is just a message with a type field: |
| 539 | | { |
| 540 | | type: "wiki", |
| 541 | | title: "How to set up Hyperswarm", |
| 542 | | content: "## Step 1\n\nInstall the package...", |
| 543 | | editedBy: "member:alice0", |
| 544 | | editedAt: 1708402400000 |
| 545 | | } |
| 542 | // A wiki entry is just a message with the _hs convention: |
| 543 | {"_hs":"wiki","title":"How to set up Hyperswarm","content":"## Step 1\n\nInstall the package...","editedBy":"member:alice0","editedAt":1708402400000} |
| 546 | 544 | ``` |
| 547 | 545 | |
| 548 | 546 | Renders inline as an expandable card. Click to read. Edit button for admins/mods. Version history = previous messages with the same title. |
| … | … | @@ -573,7 +571,7 @@ Rooms with `discoverable: "true"` announce on topic-hashed DHT keys. Search by t |
| 573 | 571 | |
| 574 | 572 | ### Step 6: Remove RoomDiscovery Autobase |
| 575 | 573 | |
| 576 | | 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). |
| 574 | Once registry rooms are stable and DMs handle user discovery, remove the hand-crafted Autobase code. ~1,600 lines deleted (room-discovery.js, room-discovery-apply.js, router discovery routes, index.js plumbing). The existing discovery registries migrate to registry rooms (one-time conversion script — see Resolved Design Decisions §4). |
| 577 | 575 | |
| 578 | 576 | ### Step 7: Add bridge awareness |
| 579 | 577 | |
| … | … | @@ -597,25 +595,304 @@ Bridges set `bridgedTo` in room config when they connect. Chat panel detects bri |
| 597 | 595 | - **ROADMAP.md Phase 5** — Board panel doesn't serve this context. Drop it. |
| 598 | 596 | - **ROADMAP.md Phase 5** — Wiki panel as separate tab. Wrong UX. Wiki is inline in chat. |
| 599 | 597 | - **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. |
| 600 | | - **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. |
| 598 | - **AUTO-DISCOVERY-SPEC.md** — Proposed ~1,400 LOC of changes. Actual changes are ~540 LOC of additions (after removing ~2,020 LOC of Autobase code including Godot client). 73% net reduction. |
| 599 | |
| 600 | ## Resolved Design Decisions |
| 601 | |
| 602 | ### 1. DM Initiation: Cold Contact IS Possible |
| 603 | |
| 604 | **Finding:** `dm.fromContact(memberId)` works WITHOUT a shared room. Only a memberId is required. |
| 605 | |
| 606 | Two DM initiation paths exist in keet-core: |
| 607 | |
| 608 | | Method | Requires Shared Room | Input | |
| 609 | |--------|---------------------|-------| |
| 610 | | `dm.request(roomId, memberId)` | Yes | roomId + memberId | |
| 611 | | `dm.fromContact(memberId)` | **No** | memberId only | |
| 612 | |
| 613 | Both use blind pairing internally. Both produce a pending DM request that the target must accept. |
| 614 | |
| 615 | **Implication:** Any source of a memberId enables DM initiation — registry rooms, DHT discovery, QR codes, out-of-band sharing. Cold-contact discovery works. The `dm.fromContact` path is used when you learn someone's memberId from a registry room listing or topic DHT result without being in a room together. |
| 616 | |
| 617 | ### 2. Structured Message Format (`_hs` Convention) |
| 618 | |
| 619 | **Finding:** keet-core messages are text-only. No metadata field, no content-type, no structured payload. The `send(roomId, text)` API accepts a string. The `editMessage(roomId, messageId, text)` API accepts a string. Files are a separate system. |
| 620 | |
| 621 | **Solution:** JSON-encoded text with a `_hs` type discriminator. |
| 622 | |
| 623 | ``` |
| 624 | Detection: text.startsWith('{"_hs":') → structured hyper-social message |
| 625 | Fallback: clients that don't understand _hs show raw JSON (functional, ugly) |
| 626 | Rendering: clients that understand _hs render type-specific UI cards |
| 627 | ``` |
| 628 | |
| 629 | **Message types:** |
| 630 | |
| 631 | ```javascript |
| 632 | // Room share (posted in DMs) |
| 633 | {"_hs":"room-share","roomId":"abc123","title":"Rust P2P Dev","topics":["rust","p2p"],"inviteToken":"xyz789","sharedAt":1708402400000} |
| 634 | |
| 635 | // Room listing (posted in registry rooms) |
| 636 | {"_hs":"room-listing","roomId":"abc123","title":"Rust P2P Dev","description":"Building distributed systems in Rust","topics":["rust","p2p"],"inviteToken":"xyz789","listedBy":"member:alice0"} |
| 637 | |
| 638 | // Room unlisting (posted in registry rooms) |
| 639 | {"_hs":"room-unlisting","roomId":"abc123","unlistedBy":"member:alice0"} |
| 640 | |
| 641 | // Wiki entry (posted in any room, pinnable) |
| 642 | {"_hs":"wiki","title":"How to set up Hyperswarm","content":"## Step 1\n\nInstall the package...","editedBy":"member:alice0","editedAt":1708402400000} |
| 643 | ``` |
| 644 | |
| 645 | **Operations map to existing message APIs:** |
| 646 | - Create → `send(roomId, JSON.stringify(payload))` |
| 647 | - Update → `editMessage(roomId, messageId, JSON.stringify(updatedPayload))` |
| 648 | - Remove → `deleteMessage(roomId, messageId)` |
| 649 | - Read → load room messages, filter by `_hs` type |
| 650 | |
| 651 | **Why `_hs` prefix detection:** |
| 652 | - O(1) check: `text.startsWith('{"_hs":')` |
| 653 | - Extremely unlikely to collide with natural messages |
| 654 | - Short (3 chars) to minimize overhead |
| 655 | - Namespace is extensible (add new `_hs` values without protocol changes) |
| 656 | |
| 657 | ### 3. MI Computation Cost: Not a Real Concern |
| 658 | |
| 659 | **Analysis:** The naive O(N² rooms) pairwise approach is unnecessary. Use an inverted index. |
| 660 | |
| 661 | **Sparse approach (inverted member index):** |
| 662 | ``` |
| 663 | Step 1: Build member → rooms map (one pass over all rooms) |
| 664 | alice0 → [room_A, room_C, room_D] |
| 665 | bob0 → [room_A, room_B] |
| 666 | carol0 → [room_A, room_B, room_C] |
| 667 | |
| 668 | Step 2: For each member, increment pair counters for all room combinations |
| 669 | alice0 is in {A,C,D} → pairs (A,C)+=1, (A,D)+=1, (C,D)+=1 |
| 670 | bob0 is in {A,B} → pair (A,B)+=1 |
| 671 | carol0 is in {A,B,C} → pairs (A,B)+=1, (A,C)+=1, (B,C)+=1 |
| 672 | |
| 673 | Step 3: Compute MI for each pair with count > 0 |
| 674 | ``` |
| 675 | |
| 676 | **Complexity:** O(Σ C(r_i, 2)) where r_i = rooms per member i. For a typical user: |
| 677 | - 30 rooms × 50 avg members = 1,500 total memberships |
| 678 | - Average member in 3 rooms → C(3,2) = 3 pair increments per member |
| 679 | - Total: 50 members × 3 = 150 pair operations |
| 680 | - With MI computation: ~150 log2 operations |
| 681 | - **Runtime: < 1ms** |
| 682 | |
| 683 | At scale (power user): |
| 684 | - 500 rooms × 100 avg members = 50,000 memberships |
| 685 | - Average member in 5 rooms → C(5,2) = 10 pair increments per member |
| 686 | - Total: 10,000 unique members × 10 = 100,000 pair operations |
| 687 | - **Runtime: ~5ms in JavaScript** |
| 688 | |
| 689 | **Decision:** Compute on a 60-second timer. No incremental updates needed. No sparse matrix needed. The inverted index makes it trivially fast for any realistic scale. |
| 690 | |
| 691 | ### 4. Registry Room Migration |
| 692 | |
| 693 | **Procedure:** One-time script, run once per existing registry. |
| 694 | |
| 695 | ``` |
| 696 | For each registry in discovery-keys.json: |
| 697 | 1. Read all entries from the Autobase view (list all rooms) |
| 698 | 2. Create a new keet-core room with config: |
| 699 | - type: "registry" |
| 700 | - title: registry.getMeta("name") or "Global" |
| 701 | - description: registry.getMeta("description") |
| 702 | 3. For each registered room in the Autobase: |
| 703 | Post a room-listing message: |
| 704 | {"_hs":"room-listing","roomId":"...","title":"...","description":"...","topics":[...],"inviteToken":"...","listedBy":"member:migrator"} |
| 705 | 4. Share the new room's invite with existing registry admins |
| 706 | 5. Admins join the registry room → they now have admin role there |
| 707 | 6. Old Autobase can be decommissioned after transition period |
| 708 | ``` |
| 709 | |
| 710 | **Migration order:** |
| 711 | 1. Create registry rooms alongside existing Autobases (both run in parallel) |
| 712 | 2. godot-keet updated to read from registry rooms (new code path) |
| 713 | 3. Transition period: both sources available |
| 714 | 4. Remove Autobase code when registry rooms are stable |
| 715 | |
| 716 | **Existing registries:** |
| 717 | - Global (`c5facb91...`) → "Global" registry room |
| 718 | - testy (`adebddd0...`) → "testy" registry room |
| 719 | - Any user-created registries → individual migration |
| 720 | |
| 721 | ### 5. Bridge Bot Identity and Permissions |
| 722 | |
| 723 | **Bridge bots are regular room members.** They join rooms via invite, like any other member. |
| 724 | |
| 725 | **Permission model:** |
| 726 | |
| 727 | | Action | Required Role | Why | |
| 728 | |--------|--------------|-----| |
| 729 | | Post messages (relay chat) | Peer (default) | Any member can chat | |
| 730 | | Set `bridgedTo` in room config | Admin | Only admins can write config | |
| 731 | | Read messages (relay outbound) | Peer (default) | Any member can read | |
| 732 | | Manage members | Not needed | Bridges don't moderate | |
| 733 | |
| 734 | **Problem:** Bridges need admin to set `bridgedTo` config, but admin is too much power. |
| 735 | |
| 736 | **Solution:** Bridge bots DON'T set room config. Instead: |
| 737 | |
| 738 | 1. Bridge bot joins room as regular peer |
| 739 | 2. Bridge bot posts a structured announcement: |
| 740 | ```javascript |
| 741 | {"_hs":"bridge-announce","platform":"irc","network":"libera","channel":"#rust-p2p","botMember":"member:bridge-irc-bot0"} |
| 742 | ``` |
| 743 | 3. Room admin sees the announcement, decides whether to endorse it |
| 744 | 4. Admin sets `bridgedTo` in room config (admin action, not bot action) |
| 745 | 5. Client shows bridge status based on room config (set by admin) NOT bot announcement |
| 601 | 746 | |
| 602 | | ## Gaps Still Open |
| 747 | **Why this is better:** |
| 748 | - Bridge bot has minimal permissions (peer only) |
| 749 | - Room owner explicitly approves bridges |
| 750 | - A malicious bot can't claim to bridge to anywhere |
| 751 | - Admin can remove bridge config without removing the bot |
| 752 | - Multiple bridges per room, each admin-approved |
| 603 | 753 | |
| 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). |
| 754 | **Bridge identity:** Bridge bots register as regular keet users with a naming convention: `bridge-irc-libera0`, `bridge-discord-myserver0`. The `bridge-` prefix signals bot status to the UI. |
| 605 | 755 | |
| 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. |
| 756 | ### 6. DM Room Presentation |
| 607 | 757 | |
| 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). |
| 758 | **DMs appear in a separate "Messages" section in the room list.** |
| 759 | |
| 760 | ``` |
| 761 | GroupBar (left rail): |
| 762 | [Group icons] |
| 763 | [Home icon] ← shows all rooms + messages |
| 764 | |
| 765 | RoomList (when Home selected): |
| 766 | ┌──────────────────┐ |
| 767 | │ Messages (3) │ ← DMs section (RoomType=2), count of unread |
| 768 | │ alice0 │ |
| 769 | │ bob0 │ |
| 770 | │ carol0 │ |
| 771 | ├──────────────────┤ |
| 772 | │ Rooms (12) │ ← Chat rooms section (RoomType=1) |
| 773 | │ Rust P2P Dev │ |
| 774 | │ Hypercore Dev │ |
| 775 | │ ... │ |
| 776 | └──────────────────┘ |
| 777 | |
| 778 | RoomList (when Group selected): |
| 779 | [filtered rooms from that group/cluster] |
| 780 | [no Messages section — DMs are personal, not per-group] |
| 781 | ``` |
| 782 | |
| 783 | **Room-share rendering inside DMs:** |
| 784 | |
| 785 | When viewing a DM conversation, `_hs:room-share` messages render as joinable cards: |
| 786 | |
| 787 | ``` |
| 788 | ┌──────────────────────────────────┐ |
| 789 | │ alice0 shared a room: │ |
| 790 | │ ┌────────────────────────────┐ │ |
| 791 | │ │ 🏠 Rust P2P Dev │ │ |
| 792 | │ │ topics: rust, p2p │ │ |
| 793 | │ │ [Join] │ │ |
| 794 | │ └────────────────────────────┘ │ |
| 795 | │ Yesterday at 3:42 PM │ |
| 796 | └──────────────────────────────────┘ |
| 797 | ``` |
| 609 | 798 | |
| 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. |
| 799 | If already joined, the card shows "Joined" instead of "Join". Click the card title to navigate to the room. |
| 611 | 800 | |
| 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. |
| 801 | **Filtering:** `RoomType=2` is already set by keet-core on DM rooms. The client filters: `rooms.filter(r => r.roomType === 2)` for Messages, `rooms.filter(r => r.roomType !== 2)` for Rooms. |
| 613 | 802 | |
| 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. |
| 803 | ### 7. Cross-Platform Identity Linking (Phase 8) |
| 615 | 804 | |
| 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. |
| 805 | **Identity links are claims posted in DMs, vouched by bridges.** |
| 617 | 806 | |
| 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. |
| 807 | ```javascript |
| 808 | // Alice posts in her DM with Bob: |
| 809 | {"_hs":"identity-claim","platform":"irc","network":"libera","nick":"alice","claimedAt":1708402400000} |
| 810 | |
| 811 | // Bridge bot (in a shared room) can vouch: |
| 812 | {"_hs":"identity-vouch","member":"member:alice0","platform":"irc","network":"libera","nick":"alice","vouchedBy":"member:bridge-irc-libera0","vouchedAt":1708402400000} |
| 813 | ``` |
| 814 | |
| 815 | **Trust model:** |
| 816 | - **Claim only (no vouch):** "Alice says she's alice on IRC." Trust based on DM relationship. |
| 817 | - **Claim + bridge vouch:** "Alice says she's alice on IRC, AND the IRC bridge confirms it." Stronger. |
| 818 | - **Vouch without claim:** Bridge bot automatically announces identity mappings for members it sees on both sides. No user action needed. |
| 819 | |
| 820 | **Storage:** Claims and vouches are just messages. The client collects them locally, builds an identity graph in memory. No new data structure. Same pattern as MI computation — local aggregation of distributed observations. |
| 821 | |
| 822 | **UI:** Member popup shows claimed identities with trust level: |
| 823 | ``` |
| 824 | alice0 |
| 825 | └── alice @ IRC Libera (bridge confirmed ✓) |
| 826 | └── alice_dev @ Discord MyServer (self-claimed) |
| 827 | ``` |
| 828 | |
| 829 | **This is Phase 8.** The `_hs` message convention makes it possible without protocol changes. Implementation is deferred until bridges are running (Phase 2-3). |
| 830 | |
| 831 | ### 8. Moderation Service (Phase 8) |
| 832 | |
| 833 | **Moderation is a room whose messages are labels.** Same room primitive, different convention. |
| 834 | |
| 835 | ```javascript |
| 836 | // Moderation room config: |
| 837 | {"type":"moderation","title":"P2P Safety Labels","description":"Community moderation labels"} |
| 838 | |
| 839 | // Label messages: |
| 840 | {"_hs":"mod-label","target":"room:abc123","label":"spam","reason":"Crypto scam room","labeledBy":"member:moderator0","labeledAt":1708402400000} |
| 841 | {"_hs":"mod-label","target":"member:scammer0","label":"harassment","reason":"Repeated abuse","labeledBy":"member:moderator0","labeledAt":1708402400000} |
| 842 | |
| 843 | // Unlabel: |
| 844 | {"_hs":"mod-unlabel","target":"room:abc123","label":"spam","unlabeledBy":"member:moderator0"} |
| 845 | ``` |
| 846 | |
| 847 | **Subscription model (Blacksky pattern):** |
| 848 | 1. Moderation teams create moderation rooms |
| 849 | 2. Users join moderation rooms to subscribe to their labels |
| 850 | 3. Client reads label messages, builds local label index |
| 851 | 4. Client applies labels: dim or hide rooms/members with negative labels |
| 852 | 5. Labels are advisory — the user chooses which moderation rooms to trust |
| 853 | 6. Multiple moderation services can overlap or conflict — user resolves |
| 854 | |
| 855 | **Why this works:** |
| 856 | - Moderation team = room admins (existing role system) |
| 857 | - Labels = messages (existing message system) |
| 858 | - Subscription = room membership (existing join system) |
| 859 | - No enforcement — the labeling room can't actually ban anyone from another room |
| 860 | - Composable — subscribe to multiple moderation sources, each adds labels |
| 861 | |
| 862 | **This is Phase 8.** The `_hs` message convention makes it possible. Implementation is deferred until the core system (Steps 1-5) is stable. |
| 863 | |
| 864 | ## Revised Code Estimates |
| 865 | |
| 866 | Based on the detailed audit of existing discovery code: |
| 867 | |
| 868 | ### Remove (~2,020 lines) |
| 869 | |
| 870 | | File | Lines | What | |
| 871 | |------|-------|------| |
| 872 | | `room-discovery.js` | 783 | Autobase registry class | |
| 873 | | `room-discovery-apply.js` | 396 | Autobase state machine | |
| 874 | | `router.js` discovery routes | 213 | 19 RPC endpoints | |
| 875 | | `index.js` discovery plumbing | 170 | Config, init, heartbeat | |
| 876 | | `keet_discovery.gd` | 419 | Godot client (33 methods, 13 signals) | |
| 877 | | Tests | 40+ | Discovery test harness | |
| 878 | |
| 879 | Also removes dependency on: `corestore`, `hyperswarm`, `autobase`, `hyperbee`, `protomux`, `compact-encoding` (for discovery only — these may still be used by keet-core itself). |
| 880 | |
| 881 | ### Add (~540 lines) |
| 882 | |
| 883 | | File | Lines | What | |
| 884 | |------|-------|------| |
| 885 | | `registry-room.js` | 80 | Post/read `_hs:room-listing` messages | |
| 886 | | `dm-shares.js` | 60 | Post/read `_hs:room-share` messages in DMs | |
| 887 | | `local-graph.js` | 200 | MI computation (inverted index, sparse) | |
| 888 | | `topic-announce.js` | 60 | DHT announce/search for discoverable rooms | |
| 889 | | `router.js` new routes | 40 | `registry.*`, `graph.*`, `topic.*` | |
| 890 | | `keet_dm.gd` | 120 | DM lifecycle + room sharing | |
| 891 | | `keet_graph.gd` | 150 | Graph data → group bar | |
| 892 | | `keet_discovery.gd` (rewrite) | 200 | Simplified: registry rooms + topic search | |
| 893 | | UI modifications | 100 | Member "Message" button, DM section, bridge badges | |
| 894 | |
| 895 | **Net: ~2,020 removed, ~540 added. 73% reduction in discovery code.** |
| 619 | 896 | |
| 620 | 897 | ## The Principle |
| 621 | 898 | |