tools/pasture/town/ (run through tools/pasture/town.sh) seeds a fake
community across every running peer and checks that all of them, and
PrivaPub, agree on what happened:
- drivers per platform on four dialect bases (Mastodon API, Misskey API,
Lemmy API, PrivaPub with /clientapi), each with a selftest against
its own server; what a server holds is read from its database, never
by making it fetch;
- a deterministic generator (specs/village.json: 23 accounts on seven
servers, roots with several personas, circles and communities, a
cross-server follow graph, posts of every kind and visibility, reply
rounds, likes, boosts, reactions, votes, edits, deletes, blocks,
mutes and a report) and a seeder that keeps a ledger of what happened;
- a sweep that expects delivery and confinement per server, what each
account sees, counts, threads, edits, deletes, follows and privacy
rows (sibling keys, published days, canary root credentials in every
peer's database, located posts that never leave), with what the peers
do on purpose modelled (Misskey drops orphan replies, Lemmy keeps only
community content, edits go to the post's own audience);
- known gaps (gaps.json) turn failures into xfail and passes into xpass;
a self-contained report.html, and docs/INTEROP-BACKLOG.md.
The pasture moves to a public-looking subnet (peers with no private
address switch can join), takes PASTURE_PORT when 6971 is in use, adds
peers to a running pasture (run.sh add, Caddy recreated with its CA
kept), removes its volumes on down, writes every scenario check to
out/scenarios.jsonl, serves decePub as decepub.test for its e2e tests,
lifts GoToSocial's and Lemmy's own rate limits, trusts Caddy in
Mastodon (TRUSTED_PROXY_IP) and gains Hollo (Fedify), whose one login
owning several accounts is the nearest peer to PrivaPub's personas.
The first village found the four PrivaPub bugs fixed in the commits
before this one; the second run, on the fixed server, passes 2319 checks
with 11 failures left, all between peers or from Lemmy's send worker,
which the seeder now warms up first. ROADMAP records the owner's
decisions of 2026-10-04 (the town, and P9 back from the cut list).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LsXgEaXee4GCU1hwYgPJXw
peers/lemmy.sh runs the Lemmy 1.0.0-beta.2 backend on the shared Postgres. It trusts
Caddy's CA through SSL_CERT_FILE and reaches the pasture through
DANGER_FEDERATION_ALLOW_LOCAL_IP. Lemmy 0.19 cannot join: its rustls trusts only its
bundled roots. LEMMY_LOG sets RUST_LOG.
scenarios/lemmy.sh drives Lemmy through its v4 API. 20 checks pass, three runs in a row:
- communities both ways;
- a titled thread each way, and alice's mention of a Lemmy community becoming a thread
there;
- the Lemmy community's announce reaching alice's home;
- comments both ways and alice's like as an upvote;
- private messages both ways;
- statistics.
Two expected failures: votes, which Lemmy sends only through the community as
Announce{Like|Dislike}, and a moderator's removal. Both belong to P7.
What it showed, in docs/INTEROP.md:
- Lemmy 1.0 keeps its user's thread in a remote community pending until the community
announces it back. It clears the flag before answering that echo 400 ("Object is not
remote"). Leaving the author's server out of the Announce, as tried here, left every
such thread pending, so GroupDistributor now says why the echo stays.
- Every bare Announce{object} is answered 400, as Lemmy answers its own compatibility
Announce(Page).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ELjqpznMFMNrJoJUj6K5p2