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:
1 parent
f3e8cce2ed
commit
7b0377c85f
19 files changed
+309
-63
No files matched your search
@@ -95,13 +95,13 @@ LBT=$(privapub_token bob_lemmy19)
|
||||
curl -s -o /dev/null -X POST -H "Authorization: Bearer $LBT" $P/api/v1/statuses -d "status=@lemmyuser@lemmy19.test a word from bob $run&visibility=direct"
|
||||
until_true 45 'lm GET "private_message/list?limit=50" | grep -q "a word from bob $run"' && ok "bob's DM to lemmyuser goes as a ChatMessage too" || ko "bob's DM never reached Lemmy 0.19"
|
||||
# (0.19 takes a private message only as a ChatMessage and answers a direct Note 400: a first message to someone who
|
||||
# never wrote to anyone here is a Note, G-0008. 1.0 takes a Note)
|
||||
# never wrote to anyone here goes as a ChatMessage because NodeInfo names Lemmy before 1.0, owner decision 2026-10-05)
|
||||
quiet="quiet$run"
|
||||
QT=$(site lemmy19.test -s -X POST "$LM/api/v3/user/register" -H 'Content-Type: application/json' \
|
||||
-d "{\"username\":\"$quiet\",\"password\":\"Lemmy-Pasture-Pass-1\",\"password_verify\":\"Lemmy-Pasture-Pass-1\",\"show_nsfw\":false}" | j "print(d['jwt'])")
|
||||
curl -s -o /dev/null -X POST -H "$LAH" $P/api/v1/statuses -d "status=@$quiet@lemmy19.test a first word from PrivaPub $run&visibility=direct"
|
||||
until_true 30 'site lemmy19.test -s "$LM/api/v3/private_message/list?limit=50" -H "Authorization: Bearer $QT" | grep -q "a first word from PrivaPub $run"' \
|
||||
&& ok "alice's first DM to someone there arrives" || xf "alice's first DM to someone there never arrives, a Note Lemmy 0.19 refuses (G-0008)"
|
||||
&& ok "alice's first DM to someone there arrives as a ChatMessage" || ko "alice's first DM to someone there never arrived"
|
||||
|
||||
echo " moderation"
|
||||
p_locked() { curl -s -H "$LAH" "$P/api/v1/statuses/$lm_cats_on_p" | j "print((d.get('privapub') or {}).get('locked'))"; }
|
||||
|
||||
@@ -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'))"; }
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
# Account moves into PrivaPub's view: an account alice follows on GoToSocial moves to a new account there. The new one
|
||||
# names the old among its aliases (/api/v1/accounts/alias), the old moves (/api/v1/accounts/move), GoToSocial sends its
|
||||
# Move to the old one's followers, and PrivaPub shows the old account moved to the new (`moved`). Needs the gts peer.
|
||||
# Move to the old one's followers, PrivaPub shows the old account moved to the new (`moved`) and moves alice's follow
|
||||
# there. Needs the gts peer.
|
||||
G=https://gts.test:6443
|
||||
gcurl() { curl -k --resolve gts.test:6443:127.0.0.1 "$@"; }
|
||||
MOVE_PASSWORD='Gts-Pasture-Pass-1!'
|
||||
@@ -44,5 +45,14 @@ moved=$(gcurl -s -o /dev/null -w '%{http_code}' -X POST -H "Authorization: Beare
|
||||
[ "$moved" = "202" ] || [ "$moved" = "200" ] && ok "the old account moves on GoToSocial" || ko "GoToSocial refused the move ($moved)"
|
||||
until_true 60 '[ "$(curl -s -H "$PH" "$P/api/v1/accounts/$old_on_p" | j "print((d.get(\"moved\") or {}).get(\"acct\"))")" = "$new@gts.test" ]' \
|
||||
&& ok "PrivaPub shows the old account moved to the new one" || ko "PrivaPub does not show the move ($(curl -s -H "$PH" "$P/api/v1/accounts/$old_on_p" | j "print(d.get('moved'))"))"
|
||||
[ "$(curl -s -H "$PH" "$P/api/v1/accounts/relationships?id[]=$old_on_p" | j "print(d[0]['following'])")" = "True" ] \
|
||||
&& ok "and alice still follows the old account: following the new one is hers to do" || ko "alice's follow of the old account changed"
|
||||
# as Mastodon does it (owner decision 2026-10-05): alice's follow moves to the new account, which GoToSocial makes
|
||||
# locked, so it arrives as a request the new account approves
|
||||
new_on_p=$(curl -s -H "$PH" "$P/api/v1/accounts/$old_on_p" | j "print((d.get('moved') or {}).get('id'))")
|
||||
until_true 45 '[ "$(curl -s -H "$PH" "$P/api/v1/accounts/relationships?id[]=$new_on_p" | j "print(d[0][\"following\"] or d[0][\"requested\"])")" = "True" ]' \
|
||||
&& ok "PrivaPub moves alice's follow to the new account" || ko "alice does not follow the new account"
|
||||
[ "$(curl -s -H "$PH" "$P/api/v1/accounts/relationships?id[]=$old_on_p" | j "print(d[0]['following'] or d[0]['requested'])")" = "False" ] \
|
||||
&& ok "and no longer follows the old one" || ko "alice still follows the old account"
|
||||
until_true 30 '[ -n "$(gcurl -s -H "Authorization: Bearer $NT" "$G/api/v1/follow_requests" | j "print(d[0][\"id\"])")" ]'
|
||||
gcurl -s -o /dev/null -X POST -H "Authorization: Bearer $NT" "$G/api/v1/follow_requests/$(gcurl -s -H "Authorization: Bearer $NT" "$G/api/v1/follow_requests" | j "print(d[0]['id'])")/authorize"
|
||||
until_true 45 '[ "$(curl -s -H "$PH" "$P/api/v1/accounts/relationships?id[]=$new_on_p" | j "print(d[0][\"following\"])")" = "True" ]' \
|
||||
&& ok "the new account lets alice in" || ko "the new account never accepted alice"
|
||||
@@ -119,7 +119,8 @@
|
||||
"phase": "P3",
|
||||
"code": "lemmy 0.19.20 crates/apub/src/protocol/objects/chat_message.rs: ChatMessageType has ChatMessage alone, so a Create{Note} to a person answers 400 (Lemmy 1.0 takes a Note); mbin 1.10.1 src/Service/ActivityPub/ActivityPubContent.php getVisibility(): a Note neither public nor to the author's followers throws 'PM: not implemented'",
|
||||
"opened": "2026-10-05",
|
||||
"status": "open",
|
||||
"note": "Since 2026-10-05 a direct message to someone who writes to us as ChatMessages goes out as one (Post.AsChatMessage), so answers arrive. A first message to someone who never wrote to anyone here is still a Note: neither server's actors say what they take, and PrivaPub never decides by a server's software name, so that waits for the owner. Pleroma and Akkoma file a ChatMessage as a chat apart from direct messages, so sending both would show them twice. Mbin's API never starts a conversation with an account elsewhere."
|
||||
"status": "closed",
|
||||
"note": "Closed by the owner's decision of 2026-10-05: a direct message to one account goes as a ChatMessage when the account writes to us that way, or when its server's NodeInfo names Lemmy before 1.0 or Mbin (the one place PrivaPub decides by software). Checked live: Lemmy 0.19's first message and Mbin's thread both ways.",
|
||||
"closed": "2026-10-05"
|
||||
}
|
||||
]
|
||||
Reference in new issue
Block a user