Hubzilla in the pasture

Hubzilla 11.4.1 with its pubcrawl addon (peers/hubzilla.sh): the hlhd image on the shared MySQL, a cron sidecar, hzuser
and hzfriend made through Hubzilla's own PHP as public channels that speak ActivityPub. scenarios/hubzilla.sh, 20
checks: follows, posts, comments, likes, edits and deletions both ways, and a third channel's comment that the thread's
owner passes on, which PrivaPub takes on its FEP-8b32 proof. Known gap G-0010 (upstream): Hubzilla 11 keeps a follower
after its Undo{Follow}.

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-06 09:59:49 +02:00
1 parent 9d0b5fac3e
commit b39eadffeb
7 files changed
+262

No files matched your search

+8
View File
@@ -607,6 +607,14 @@ tools/pasture/run.sh down # removes e
announced by the other), that alice's public post goes to the relays (and through both to Mastodon: Activity-Relay's announced by the other), that alice's public post goes to the relays (and through both to Mastodon: Activity-Relay's
forward on its FEP-8b32 proof, after the scenario makes Mastodon read alice's keys anew) and nothing less public, and forward on its FEP-8b32 proof, after the scenario makes Mastodon read alice's keys anew) and nothing less public, and
has Mastodon leave again, so the town sees no relayed posts. 16 checks. has Mastodon leave again, so the town sees no relayed posts. 16 checks.
- **Hubzilla (11.4.1, `peers/hubzilla.sh`):** the hlhd image (php-fpm and nginx on 8080) on the shared MySQL
(database `hubzilla`, its schema loaded from the image), `.htconfig.php` written to `.state/hubzilla` and mounted, a
cron sidecar running `Zotlabs/Daemon/Master.php Cron` each minute. Its addons `pubcrawl` (ActivityPub) and `statistics`
(NodeInfo) are installed with `util/addons`; hzuser (the hub's administrator) and hzfriend are made through its own PHP
(`hz_eval`), as accounts written the way its registration queue would and channels with the role `public` (the older
role names grant connections nothing), the ActivityPub app installed as each channel. Its API (`api/z/1.0`) posts and
edits with HTTP Basic (email and password); liking and dropping go through its web pages signed in (`hz_web`).
`scenarios/hubzilla.sh`, 20 checks and one known gap (G-0010, its unfollow).
- **Smithereen (1.0.3):** its image on the shared MySQL (database `smithereen`, its schema from the image's commit), - **Smithereen (1.0.3):** its image on the shared MySQL (database `smithereen`, its schema from the image's commit),
with imgproxy and a file server behind Caddy as `smithereen.test` (`/i` and `/s`), trusting the CA through a JDK with imgproxy and a file server behind Caddy as `smithereen.test` (`/i` and `/s`), trusting the CA through a JDK
store with it added (`JAVA_TOOL_OPTIONS`). MySQL takes its stored functions only with store with it added (`JAVA_TOOL_OPTIONS`). MySQL takes its stored functions only with
+1
View File
@@ -44,6 +44,7 @@ and every run starting clean, with signed fetches required (as privapub.thepra.d
- **Ghost 6.67** with its ActivityPub service 1.2.14 - **Ghost 6.67** with its ActivityPub service 1.2.14
- **BookWyrm 0.9.3** - **BookWyrm 0.9.3**
- **Vernissage 1.43.0** - **Vernissage 1.43.0**
- **Hubzilla 11.4.1** with its pubcrawl addon
- **Activity-Relay 2.0.9** and **aode-relay 0.3.129**, as relays PrivaPub reads from - **Activity-Relay 2.0.9** and **aode-relay 0.3.129**, as relays PrivaPub reads from
- in the town only (a seeded community checked server by server): **Hollo 0.9.19**, **Iceshrimp.NET 2026.1.2-beta**, - in the town only (a seeded community checked server by server): **Hollo 0.9.19**, **Iceshrimp.NET 2026.1.2-beta**,
**Pleroma 2.10.2** **Pleroma 2.10.2**
+21
View File
@@ -854,6 +854,27 @@ without a port, before it gives out its OAuth client.
never as the relay's boost), and nobody's home; a persona's public post goes to the relays, nothing less public, and never as the relay's boost), and nobody's home; a persona's public post goes to the relays, nothing less public, and
reaches Mastodon through both (forwarded on its proof, and announced). reaches Mastodon through both (forwarded on its proof, and announced).
### Hubzilla 11.4.1 (pubcrawl)
- **Channels speak ActivityPub only with the "Activitypub Protocol" app** installed for them (the pubcrawl addon on
the hub first). Accounts on Hubzilla 11 come only from its registration queue; the pasture writes the account row as
that would (salt, `whirlpool(salt . password)`).
- **A channel's role decides what its connections may do.** Hubzilla 11 grants what a role's `perms_connect` lists only
for the roles `public`, `personal`, `group` and `custom`; the older names its role list still shows (`social`,
`social_restricted`, ...) grant a new connection nothing, so it drops everything that connection sends ("store: no
permission") while answering 200.
- **Threads are its owner's:** a comment by another channel in a thread goes to the owner, who passes it on to the
thread's audience as the commenter's own Create, with the commenter's FEP-8b32 proof, and adds each activity to the
thread's `context` (FEP-171b, `Add{Create}`). PrivaPub takes the forwarded Create on its proof; the `Add` is dropped,
the Create having said it already.
- **Signs everything with a proof** (FEP-8b32) and verifies HTTP signatures; NodeInfo comes from its `statistics`
addon.
- **An unfollow changes nothing there** (G-0010): `Activity::unfollow` deletes the setting `system.their_perms`, which
Hubzilla 11 keeps one permission per row under `their_perms`, so the follower stays.
- **Pasture evidence (2026-10-06, `tools/pasture/scenarios/hubzilla.sh`):** 20 checks pass and one gap is expected
(G-0010): follows both ways, posts both ways, comments both ways, a third channel's comment passed on by the thread's
owner, likes both ways, edits and deletions both ways, statistics.
### Smithereen 1.0.3 ### Smithereen 1.0.3
- **Walls:** a post is a `Note`; one written on someone else's wall carries the wall (`sm:wall`, FEP-400e) as its - **Walls:** a post is a `Note`; one written on someone else's wall carries the wall (`sm:wall`, FEP-400e) as its
+5
View File
@@ -108,6 +108,11 @@ nodebb.test {
reverse_proxy pasture-nodebb:4567 reverse_proxy pasture-nodebb:4567
} }
hubzilla.test {
tls internal
reverse_proxy pasture-hubzilla:8080
}
vernissage.test { vernissage.test {
tls internal tls internal
reverse_proxy pasture-vernissage:8080 reverse_proxy pasture-vernissage:8080
+125
View File
@@ -0,0 +1,125 @@
# Hubzilla 11.4.1: channels with nomadic identity, speaking its own Zot6 and, through its pubcrawl addon, ActivityPub.
# The hlhd image (php-fpm and nginx on 8080) on the shared MySQL (database hubzilla, its schema from the image), its
# configuration written here and mounted as .htconfig.php, its store in a volume. Its queue runs in processes the web
# container spawns and in a cron sidecar (Zotlabs/Daemon/Master.php Cron each minute). It trusts Caddy's CA through the
# bundle mounted as its system store. Accounts and channels are made through its own PHP (util/ and cli_startup), and a
# channel speaks ActivityPub only once the "Activitypub Protocol" app is installed for it. Its API (api/z/1.0) takes
# HTTP Basic authentication by the account's email and password.
HUBZILLA_IMAGE=${HUBZILLA_IMAGE:-docker.io/hlhd/hubzilla:v11.4.1}
HUBZILLA_PASSWORD=Hubzilla-Pasture-1
. "$here/peers/shared.sh"
hz_php() { podman exec -u www-data -w /var/www/html pasture-hubzilla php "$@"; }
# hz_eval <php>: PHP run inside Hubzilla, its CLI start done (the database, the configuration, the classes)
hz_eval() { podman exec -i -u www-data -w /var/www/html pasture-hubzilla php -r "require_once('include/cli_startup.php'); cli_startup(); $1"; }
hubzilla_config() {
cat <<EOF
<?php
\$db_host = 'mysql';
\$db_port = 0;
\$db_user = 'pasture';
\$db_pass = 'pasture';
\$db_data = 'hubzilla';
\$db_type = 0;
App::\$config['system']['timezone'] = 'UTC';
App::\$config['system']['baseurl'] = 'https://hubzilla.test';
App::\$config['system']['sitename'] = 'Pasture Hubzilla';
App::\$config['system']['location_hash'] = '$(openssl rand -hex 32)';
App::\$config['system']['transport_security_header'] = 1;
App::\$config['system']['content_security_policy'] = 1;
App::\$config['system']['ssl_cookie_protection'] = 1;
App::\$config['system']['register_policy'] = REGISTER_OPEN;
App::\$config['system']['register_text'] = '';
App::\$config['system']['admin_email'] = 'admin@hubzilla.test';
App::\$config['system']['verify_email'] = 0;
App::\$config['system']['php_path'] = 'php';
App::\$config['system']['directory_mode'] = DIRECTORY_MODE_STANDALONE;
App::\$config['system']['theme'] = 'redbasic';
EOF
}
hubzilla_up() {
shared_mysql_up
mysql_db hubzilla
mkdir -p "$here/.state/hubzilla"
[ -s "$here/.state/hubzilla/htconfig.php" ] || hubzilla_config > "$here/.state/hubzilla/htconfig.php"
podman volume exists pasture-hubzilla-store || podman volume create --label pasture=1 pasture-hubzilla-store >/dev/null
podman run -d --replace --name pasture-hubzilla --network $net --label pasture=1 \
-v "$here/.state/hubzilla/htconfig.php:/var/www/html/.htconfig.php:z,ro" -v pasture-hubzilla-store:/var/www/html/store \
-v "$ca/bundle.pem:/etc/ssl/certs/ca-certificates.crt:z,ro" "$HUBZILLA_IMAGE" >/dev/null
podman exec -u root pasture-hubzilla sh -c 'mkdir -p /var/www/html/store/[data]/smarty3 && chown -R www-data /var/www/html/store'
# its schema, once
if [ "$(podman exec pasture-mysql mysql -upasture -ppasture -N hubzilla -e "show tables like 'account'" 2>/dev/null)" != "account" ]; then
podman exec pasture-hubzilla cat /var/www/html/install/schema_mysql.sql | podman exec -i pasture-mysql mysql -upasture -ppasture hubzilla 2>/dev/null
fi
for _ in $(seq 1 60); do
site hubzilla.test -s -o /dev/null -w '%{http_code}' https://hubzilla.test:6443/.well-known/nodeinfo 2>/dev/null | grep -q 200 && break
sleep 2
done
podman run -d --replace --name pasture-hubzilla-cron --network $net --label pasture=1 --volumes-from pasture-hubzilla \
-u www-data -w /var/www/html --entrypoint sh "$HUBZILLA_IMAGE" -c 'while true; do php Zotlabs/Daemon/Master.php Cron; sleep 60; done' >/dev/null
hubzilla_settle
echo "hubzilla: https://hubzilla.test:6443"
}
# ActivityPub and NodeInfo on, hzuser and hzfriend
hubzilla_settle() {
hz_php util/addons install pubcrawl >/dev/null 2>&1 || true
# NodeInfo is the statistics addon's
hz_php util/addons install statistics >/dev/null 2>&1 || true
# (the first account is the hub's administrator, as Hubzilla asks)
hubzilla_user hzuser 4096
hubzilla_user hzfriend
}
# hubzilla_user <nick> [roles]: an account (<nick>@hubzilla.test, the pasture's password; Hubzilla 11 makes accounts
# only from its registration queue, so the row is written as that would, the password salted and hashed with whirlpool)
# with a public channel of that name that speaks ActivityPub and takes followers without asking. Its role is "public":
# Hubzilla 11 grants a connection what a role's perms_connect lists only for public, personal, group and custom, and the
# older names its role list still offers ("social") grant nothing, so nobody could post to it
hubzilla_user() {
hz_eval "
require_once('include/account.php'); require_once('include/channel.php');
use Zotlabs\Lib\Apps;
\$email = '$1@hubzilla.test';
\$r = q(\"select account_id from account where account_email = '%s'\", dbesc(\$email));
if (! \$r) {
\$salt = random_string(32);
q(\"insert into account (account_parent, account_salt, account_password, account_email, account_language, account_created, account_flags,
account_roles, account_level, account_expires, account_service_class) values (0, '%s', '%s', '%s', 'en', '%s', 0, %d, 5, '%s', '')\",
dbesc(\$salt), dbesc(hash('whirlpool', \$salt . '$HUBZILLA_PASSWORD')), dbesc(\$email), dbesc(datetime_convert()), intval(${2:-0}),
dbesc(DBA::\$dba->get_null_date()));
\$r = q(\"select account_id from account where account_email = '%s'\", dbesc(\$email));
}
\$account_id = \$r[0]['account_id'];
q('update account set account_flags = 0 where account_id = %d', intval(\$account_id));
\$c = channelx_by_nick('$1');
if (! \$c) {
\$x = create_identity(['account_id' => \$account_id, 'nickname' => '$1', 'name' => '$1', 'permissions_role' => 'public', 'publish' => 1]);
if (! \$x['success']) { echo 'channel: ' . \$x['message'] . PHP_EOL; exit(1); }
\$c = channelx_by_nick('$1');
}
// (its apps list only what a signed-in channel may have: the import runs as the channel, its session set as
// local_channel() reads it)
session_id('cli' . \$c['channel_id']);
\$_SESSION = ['authenticated' => 1, 'uid' => \$c['channel_id'], 'account_id' => \$c['channel_account_id']];
App::\$channel = \$c;
Apps::import_system_apps();
if (! Apps::addon_app_installed(\$c['channel_id'], 'pubcrawl'))
Apps::app_install(\$c['channel_id'], 'Activitypub Protocol');
echo 'ok ' . \$c['channel_id'] . PHP_EOL;
"
}
# hz_web <nick> <curl args...>: a request to Hubzilla's web pages as <nick>, signed in through its login form (what its
# API has no route for: liking, editing, dropping)
hz_web() {
local nick=$1 jar="$here/.state/hubzilla/$1.cookies"; shift
if ! site hubzilla.test -s -b "$jar" https://hubzilla.test:6443/network 2>/dev/null | grep -qi logout; then
rm -f "$jar"
site hubzilla.test -s -o /dev/null -c "$jar" -X POST https://hubzilla.test:6443/login -d auth-params=login \
-d "main_login_username=$nick@hubzilla.test" --data-urlencode "main_login_password=$HUBZILLA_PASSWORD"
fi
site hubzilla.test -s -b "$jar" -c "$jar" "$@"
}
+88
View File
@@ -0,0 +1,88 @@
# Hubzilla 11.4.1 (pubcrawl, its ActivityPub addon): follows both ways; posts both ways; comments both ways; likes both
# ways; a comment by hzfriend in hzuser's thread, which Hubzilla passes on to alice as the thread's owner; edits and
# deletions both ways; the unfollow; statistics. Hubzilla's API (api/z/1.0) posts, edits and reads; what it has no route
# for is done through its own PHP (connecting) or its web pages signed in (liking, dropping; peers/hubzilla.sh), and
# what it holds is read from its MySQL (database hubzilla, the item table by mid).
. "$here/peers/hubzilla.sh"
HZ=https://hubzilla.test:6443
# hzc <nick> <curl args...>: Hubzilla's API as <nick>
hzc() { local nick=$1; shift; site hubzilla.test -s -u "$nick@hubzilla.test:$HUBZILLA_PASSWORD" "$@"; }
hz_sql() { podman exec pasture-mysql mysql -upasture -ppasture hubzilla -Nse "$1" 2>/dev/null; }
# hz_item <mid> [nick]: the id of the item Hubzilla holds under that ActivityPub id in a channel (hzuser's)
hz_item() { hz_sql "select id from item where mid = '$1' and uid = (select channel_id from channel where channel_address = '${2:-hzuser}') and item_deleted = 0 limit 1"; }
hz_connect() { hz_eval "use Zotlabs\\Lib\\Connect; \$c = channelx_by_nick('$1'); \$r = Connect::connect(\$c, '$2'); echo \$r['success'] ? 'connected' : \$r['message'];"; }
p_home_id() { curl -s -H "$PH" "$P/api/v1/timelines/home?limit=40" | j "print(next((o['id'] for o in ((s.get('reblog') or s) for s in d) if '$1' in (o['content'] or '')), ''))"; }
p_status() { curl -s -H "$PH" "$P/api/v1/statuses/$1" | j "print(d.get('$2'))"; }
p_replies_have() { [ "$(curl -s -H "$PH" "$P/api/v1/statuses/$1/context" | j "print(any('$2' in s['content'] for s in d['descendants']))")" = "True" ]; }
echo "hubzilla"
[ -n "$(hzc hzuser "$HZ/api/z/1.0/verify" | j "print(d.get('channel_address') or '')")" ] && ok "Hubzilla's API as hzuser" \
|| { ko "Hubzilla's API ($(hzc hzuser "$HZ/api/z/1.0/verify" | head -c 200))"; return 1; }
PT=$(privapub_token alice_hubzilla)
PH="Authorization: Bearer $PT"
[ -n "$PT" ] && ok "PrivaPub token for alice_hubzilla" || { ko "PrivaPub token for alice_hubzilla"; return 1; }
alice_uri="$(curl -s -H "$PH" "$P/api/v1/accounts/verify_credentials" | j "print(d['url'])" | sed 's|/@|/peasants/|')"
run=$(date +%s)
echo " follows"
hz_on_p=$(curl -s -H "$PH" "$P/api/v2/search?q=hzuser@hubzilla.test&resolve=true&type=accounts" | j "print(d['accounts'][0]['id'])")
[ -n "$hz_on_p" ] && ok "PrivaPub resolves hzuser" || ko "PrivaPub cannot resolve hzuser"
# (a Hubzilla made anew keeps its addresses: alice follows again, so both follows below are new)
curl -s -o /dev/null -X POST -H "$PH" "$P/api/v1/accounts/$hz_on_p/unfollow"
hz_connect hzuser "$alice_uri" >/dev/null
until_true 60 '[ "$(curl -s -H "$PH" "$P/api/v1/accounts/verify_credentials" | j "print(d[\"followers_count\"])")" -ge 1 ]' \
&& ok "hzuser follows alice" || ko "Hubzilla's follow never reached alice"
curl -s -o /dev/null -X POST -H "$PH" "$P/api/v1/accounts/$hz_on_p/follow"
until_true 60 '[ "$(curl -s -H "$PH" "$P/api/v1/accounts/relationships?id[]=$hz_on_p" | j "print(d[0][\"following\"])")" = "True" ]' \
&& ok "alice follows hzuser (Accept arrived)" || ko "Hubzilla's Accept never arrived"
# (hzfriend connects to hzuser now: a channel comments only on what reached it, and only once the other side has let it)
hz_connect hzfriend hzuser@hubzilla.test >/dev/null
echo " posts"
hzc hzuser -o /dev/null -X POST "$HZ/api/z/1.0/item/update" --data-urlencode "body=a Hubzilla post $run"
until_true 60 '[ -n "$(p_home_id "a Hubzilla post $run")" ]' && ok "hzuser's post reaches alice's home" || ko "hzuser's post never reached alice"
hz_post=$(p_home_id "a Hubzilla post $run")
hz_post_uri=$(p_status "$hz_post" uri)
p_post=$(curl -s -X POST -H "$PH" "$P/api/v1/statuses" -d "status=a PrivaPub post for Hubzilla $run&visibility=public")
p_post_id=$(echo "$p_post" | j "print(d['id'])"); p_post_uri=$(echo "$p_post" | j "print(d['uri'])")
until_true 60 '[ -n "$(hz_item "$p_post_uri")" ]' && ok "alice's post reaches hzuser" || ko "alice's post never reached Hubzilla"
echo " comments"
curl -s -o /dev/null -X POST -H "$PH" "$P/api/v1/statuses" -d "status=@hzuser@hubzilla.test a PrivaPub comment $run&in_reply_to_id=$hz_post&visibility=public"
until_true 60 '[ "$(hz_sql "select count(*) from item where body like '"'%a PrivaPub comment $run%'"' and thr_parent = '"'$hz_post_uri'"'")" -ge 1 ]' \
&& ok "alice's comment threads under hzuser's post" || ko "alice's comment never reached Hubzilla"
hzc hzuser -o /dev/null -X POST "$HZ/api/z/1.0/item/update" --data-urlencode "body=a Hubzilla comment $run" -d "parent=$(hz_item "$p_post_uri")"
until_true 60 'p_replies_have "$p_post_id" "a Hubzilla comment $run"' && ok "hzuser's comment threads under alice's post" || ko "hzuser's comment missing on PrivaPub"
# hzfriend, who alice does not follow, comments in hzuser's thread: Hubzilla, the thread's owner, passes it on to alice
until_true 30 '[ -n "$(hz_item "$hz_post_uri" hzfriend)" ]' >/dev/null
hzc hzfriend -o /dev/null -X POST "$HZ/api/z/1.0/item/update" --data-urlencode "body=a word from hzfriend $run" -d "parent=$(hz_item "$hz_post_uri" hzfriend)"
until_true 60 'p_replies_have "$hz_post" "a word from hzfriend $run"' \
&& ok "hzfriend's comment in hzuser's thread reaches alice, passed on" || ko "hzfriend's comment never reached alice"
echo " likes"
curl -s -o /dev/null -X POST -H "$PH" "$P/api/v1/statuses/$hz_post/favourite"
until_true 60 '[ "$(hz_sql "select count(*) from item where verb = '"'Like'"' and thr_parent = '"'$hz_post_uri'"' and item_deleted = 0")" -ge 1 ]' \
&& ok "alice's like lands on Hubzilla" || ko "alice's like never reached Hubzilla"
hz_web hzuser -o /dev/null "$HZ/like/$(hz_item "$p_post_uri")?verb=like"
until_true 60 '[ "$(p_status "$p_post_id" favourites_count)" = "1" ]' && ok "hzuser's like counts on PrivaPub" || ko "hzuser's like never counted"
echo " edits and deletions"
hzc hzuser -o /dev/null -X POST "$HZ/api/z/1.0/item/update" -d "post_id=$(hz_item "$hz_post_uri")" --data-urlencode "body=a Hubzilla post $run, edited"
until_true 60 'p_status "$hz_post" content | grep -q "edited"' && ok "hzuser's edit reaches PrivaPub" || ko "hzuser's edit never reached PrivaPub"
curl -s -o /dev/null -X PUT -H "$PH" "$P/api/v1/statuses/$p_post_id" -d "status=a PrivaPub post for Hubzilla $run, edited"
until_true 60 '[ "$(hz_sql "select count(*) from item where mid = '"'$p_post_uri'"' and body like '"'%edited%'"'")" -ge 1 ]' \
&& ok "alice's edit reaches Hubzilla" || ko "alice's edit never reached Hubzilla"
hz_web hzuser -o /dev/null "$HZ/item/drop/$(hz_item "$hz_post_uri")"
until_true 60 '[ "$(curl -s -o /dev/null -w "%{http_code}" -H "$PH" "$P/api/v1/statuses/$hz_post")" = "404" ]' \
&& ok "hzuser's deletion reaches PrivaPub" || ko "hzuser's deleted post still on PrivaPub"
curl -s -o /dev/null -X DELETE -H "$PH" "$P/api/v1/statuses/$p_post_id"
until_true 60 '[ -z "$(hz_item "$p_post_uri")" ]' && ok "alice's deletion reaches Hubzilla" || ko "alice's deleted post still on Hubzilla"
echo " unfollow"
curl -s -o /dev/null -X POST -H "$PH" "$P/api/v1/accounts/$hz_on_p/unfollow"
# (Hubzilla takes the Undo, verified, but its unfollow deletes a setting Hubzilla 11 no longer keeps: G-0010)
until_true 20 '[ "$(hz_sql "select count(*) from abconfig where xchan = '"'$alice_uri'"' and cat = '"'their_perms'"' and v = '"'1'"' and chan = (select channel_id from channel where channel_address = '"'hzuser'"')")" = "0" ]' \
&& ok "alice's unfollow reaches Hubzilla" || xf "Hubzilla keeps alice following hzuser after her Undo{Follow} (G-0010)"
echo " statistics"
stats_check hubzilla.test hubzilla
+14
View File
@@ -137,5 +137,19 @@
"status": "closed", "status": "closed",
"note": "Closed by the owner's decision of 2026-10-06: a persona's actor names its wall (sm:wall, FEP-400e), so Smithereen sends PrivaPub what is written on its users' walls (Add{Note}, shown to their followers here as on that wall), and its users may write on a persona's wall when they follow it. Checked live: Smithereen 1.0.3 scenario, 40 checks.", "note": "Closed by the owner's decision of 2026-10-06: a persona's actor names its wall (sm:wall, FEP-400e), so Smithereen sends PrivaPub what is written on its users' walls (Add{Note}, shown to their followers here as on that wall), and its users may write on a persona's wall when they follow it. Checked live: Smithereen 1.0.3 scenario, 40 checks.",
"closed": "2026-10-06" "closed": "2026-10-06"
},
{
"id": "G-0010",
"title": "Hubzilla keeps an ActivityPub follower after its Undo{Follow}: the follower's permissions stay granted",
"match": {
"feature": "unfollow",
"observer": "hubzilla"
},
"kind": "peer",
"phase": "upstream",
"code": "hubzilla 11.4.1 Zotlabs/Lib/Activity.php unfollow(): AbConfig::Delete($channel, $xchan, 'system', 'their_perms'), while Hubzilla 11 keeps a connection's permissions one per row under the category their_perms",
"opened": "2026-10-06",
"status": "open",
"note": "The Undo is verified and handled; nothing changes. PrivaPub drops what Hubzilla goes on sending, as from an account no persona follows."
} }
] ]