Watch
0
0
Fork
You've already forked hyperhive
0

matrix-mcp: auto-join only on accounts that live on the swarm homeserver

This commit is contained in:
damocles 2026-11-07 13:54:56 +01:00 • committed by mara
commit 513530ae35
2 changed files with 53 additions and 10 deletions

View file

@ -35,10 +35,12 @@ is what an agent reads at call time. They fall into four groups:
`join_room`. `resolve_invite` accepts or rejects a pending invite and is the
path for invites; `join_room` is for a public room the agent wasn't invited to.
`list_invites` gives the inviter and the topic, so an agent can judge an
invite before it answers. The daemon joins an invite from a user of the
agent's own homeserver by itself, with an info log line and no todo. An invite
from any other homeserver stays a todo that names the inviter as an external
matrix user, and the agent decides. If an automatic join fails, the invite
invite before it answers. On an account that lives on the swarm
homeserver, the daemon joins an invite from a user of that same server by itself,
with an info log line and no todo. Every other invite stays a todo that names the
inviter as an external matrix user, and the agent decides: that covers invites from
other homeservers and every invite to a linked account on a public server such as
matrix.org. If an automatic join fails, the invite
falls back to a todo too.
- **Receipts:** `mark_read` advances the read receipt.

View file

@ -130,7 +130,7 @@ pub async fn sweep_invites<S: BuildHasher>(
// from the unread sweep's `<room>` key) so the two never collide; the
// harness coalesces the wake iff the summary is new or changed, so
// re-upserting an unchanged invite every tick is a cheap no-op.
let own_server = client.user_id().map(|u| u.server_name().to_owned());
let own_server = swarm_server(client);
for room in &current {
let room_id = room.room_id().to_owned();
let inviter = room
@ -139,8 +139,8 @@ pub async fn sweep_invites<S: BuildHasher>(
.ok()
.and_then(|d| d.inviter)
.map(|m| m.user_id().to_owned());
// An invite from the swarm's own homeserver is trusted: join it
// without waking the agent. A failed join falls through to the todo
// An invite from a user of the swarm's own homeserver, to an account
// that lives on it, is trusted: join it without waking the agent. A failed join falls through to the todo
// below, so the invite is never lost.
if is_local(own_server.as_deref(), inviter.as_deref()) {
match room.join().await {
@ -179,8 +179,25 @@ pub async fn sweep_invites<S: BuildHasher>(
}
}
/// Whether `inviter` is a user of the agent's own homeserver. No known
/// inviter or no known own server counts as external.
/// The swarm homeserver's server name when `client` is an account on it, else
/// `None`. The swarm homeserver is the daemon-wide `HIVE_MATRIX_URL`; a linked
/// account on another server (matrix.org, catgirl.cloud, ...) is not on it, so
/// its same-server inviters are strangers, not swarm members.
fn swarm_server(client: &Client) -> Option<matrix_sdk::ruma::OwnedServerName> {
let swarm = reqwest::Url::parse(&crate::paths::homeserver_url()?).ok()?;
same_homeserver(&swarm, &client.homeserver())
.then(|| client.user_id().map(|u| u.server_name().to_owned()))
.flatten()
}
/// Whether two homeserver URLs point at the same host and port.
fn same_homeserver(a: &reqwest::Url, b: &reqwest::Url) -> bool {
a.host_str() == b.host_str() && a.port_or_known_default() == b.port_or_known_default()
}
/// Whether `inviter` is a user of the swarm homeserver `own` (the account's
/// server when the account is on it, else `None`). No known inviter or no
/// swarm server counts as external.
fn is_local(own: Option<&ServerName>, inviter: Option<&UserId>) -> bool {
matches!((own, inviter), (Some(own), Some(u)) if u.server_name() == own)
}
@ -197,7 +214,7 @@ pub(crate) fn invite_key(room_id: &matrix_sdk::ruma::RoomId) -> String {
mod invite_tests {
use matrix_sdk::ruma::{server_name, user_id};
use super::is_local;
use super::{is_local, same_homeserver};
#[test]
fn only_an_inviter_on_the_own_homeserver_is_local() {
@ -216,4 +233,28 @@ mod invite_tests {
assert!(!is_local(Some(own), None));
assert!(!is_local(None, Some(user_id!("@mara:pr1ma.darkest.space"))));
}
#[test]
fn an_account_is_on_the_swarm_homeserver_only_by_host_and_port() {
let u = |s: &str| reqwest::Url::parse(s).expect("url");
let swarm = u("https://matrix.pr1ma.darkest.space");
assert!(same_homeserver(
&swarm,
&u("https://matrix.pr1ma.darkest.space/")
));
assert!(same_homeserver(
&swarm,
&u("https://matrix.pr1ma.darkest.space:443")
));
// A public server is a different host, and a different port is a
// different server.
assert!(!same_homeserver(&swarm, &u("https://catgirl.cloud")));
assert!(!same_homeserver(
&swarm,
&u("https://matrix.pr1ma.darkest.space:8448")
));
// An external account gets no swarm server, so even its own
// same-server inviter is not local.
assert!(!is_local(None, Some(user_id!("@x:catgirl.cloud"))));
}
}