Moves carry follows over; first DMs to Lemmy 0.19 and Mbin go as ChatMessage

Two owner decisions of 2026-10-05, both recorded in ROADMAP:
- After a verified Move the personas following the old account follow the new one, in the same lists, and a mute or
  block of the old account carries over, as Mastodon does it.
- A direct message to one account on a server whose NodeInfo names Lemmy before 1.0 or Mbin goes as a ChatMessage,
  the one place PrivaPub decides by a server's software (invariant 17). G-0008 is closed.

Mbin addresses its private messages to the recipient's profile page, so a Create addressed to a persona's /@name now
reaches the persona. Checked live: moves 8/8, Lemmy 0.19 30/30, Mbin 26/26 with messages both ways. The software
theory runs alone, since every test's peer shares 127.0.0.1.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LsXgEaXee4GCU1hwYgPJXw
This commit is contained in:
thepraandClaude Opus 5.5 committed 2026-10-05 20:57:43 +02:00
1 parent f3e8cce2ed
commit 7b0377c85f
19 files changed
+309 -63

No files matched your search

+13 -5
View File
@@ -1,6 +1,6 @@
# Mbin 1.10.1: the threadiverse in Symfony. Magazines both ways (FEP-1b12), threads with titles, a Note addressed to a
# magazine as one of its microblog posts, comments both ways, favourites (Like) and upvotes (Announce) both ways (Mbin
# sends no downvote), a direct message (G-0008), a moderator's lock and removal, the unfollow, statistics. Mbin is driven
# sends no downvote), private messages both ways, a moderator's lock and removal, the unfollow, statistics. Mbin is driven
# through its API as mbuser, with the token peers/mbin_token.py got through the authorization-code flow.
MB=https://mbin.test:6443
MT=$(cat "$here/.state/mbin.token" 2>/dev/null)
@@ -20,6 +20,11 @@ mb_resolve() { mb GET "search/v2?q=$(python3 -c 'import sys, urllib.parse; print
p_home_has() { curl -s -H "$PH" "$P/api/v1/timelines/home?limit=40" | j "print(next(((s.get('reblog') or s)['id'] for s in d if '$1' in ((s.get('reblog') or s)['content'] or '') or '$1' in (((s.get('reblog') or s).get('privapub') or {}).get('title') or '')), ''))"; }
echo "mbin"
# Mbin's access token lasts an hour: a stale one is replaced through the same authorization-code flow
if [ "$(mb GET users/me | j "print(d['username'])")" != "mbuser" ]; then
python3 "$here/peers/mbin_token.py" Mbin-Pasture-Pass-1 "$here/.state/mbin/client.json" > "$here/.state/mbin.token" 2>"$here/.state/mbin/token.log"
MT=$(cat "$here/.state/mbin.token" 2>/dev/null)
fi
[ -n "$MT" ] && [ "$(mb GET users/me | j "print(d['username'])")" = "mbuser" ] && ok "Mbin token for mbuser" || { ko "Mbin token"; return 1; }
PT=$(privapub_token alice_mbin)
PH="Authorization: Bearer $PT"
@@ -88,11 +93,14 @@ until_true 45 '[ "$(mb GET "entry/$books_thread" | j "print(d[\"uv\"])")" = "1"
&& ok "alice's boost is an upvote on Mbin" || ko "alice's boost not counted on Mbin ($(mb GET "entry/$books_thread" | j "print(d[\"favourites\"], d[\"uv\"])"))"
echo " private messages"
# Mbin takes a private message only as a ChatMessage, and drops alice's direct Note ("PM: not implemented", G-0008); its
# API never starts a conversation with an account elsewhere, so nothing comes the other way through it
# Mbin takes a private message only as a ChatMessage, which PrivaPub sends it since NodeInfo names Mbin (owner decision
# 2026-10-05); its API never starts a conversation with an account elsewhere, so mbuser answers in the thread alice began
curl -s -o /dev/null -X POST -H "$PH" $P/api/v1/statuses -d "status=@mbuser@mbin.test a secret from PrivaPub $run&visibility=direct"
until_true 30 '[ "$(mb_sql "select count(*) from message where body like '"'%a secret from PrivaPub $run%'"'")" = "1" ]' \
&& ok "alice's DM arrives as an Mbin private message" || xf "alice's DM never reaches Mbin, which takes private messages only as ChatMessage (G-0008)"
until_true 45 '[ "$(mb_sql "select count(*) from message where body like '"'%a secret from PrivaPub $run%'"'")" = "1" ]' \
&& ok "alice's DM arrives as an Mbin private message" || ko "alice's DM never reached Mbin"
thread=$(mb_sql "select thread_id from message where body like '%a secret from PrivaPub $run%' limit 1")
mb POST "messages/thread/$thread/reply" "{\"body\":\"an Mbin answer $run\"}" >/dev/null
until_true 45 'curl -s -H "$PH" "$P/api/v1/conversations" | grep -q "an Mbin answer $run"' && ok "mbuser's answer arrives as a DM" || ko "Mbin's answer never reached alice"
echo " moderation"
p_locked() { curl -s -H "$PH" "$P/api/v1/statuses/$books_thread_on_p" | j "print((d.get('privapub') or {}).get('locked'))"; }