Town: decePub's cells in the report, a Hollo pair, kinder waits

The report merges decePub's end-to-end cells (out/client.jsonl, the
latest result of each test) into the matrix under the observer
"decepub", and names its run without the workstation's paths. A second
spec, hollo-pair, seeds PrivaPub and Hollo alone (258 of 261 checks pass;
the three left found the emoji variation selector bug fixed in
PrivaPub), and Hollo accounts may join circles. The seeder waits up to
90 s for a follow request, two of a sender's retries after a refused
first delivery (Hollo answers its first concurrent deliveries 500 while
it creates its tables). town.sh drive prints its JSON unescaped.

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 01:24:17 +02:00
1 parent f70ec59359
commit 4b6d261739
5 files changed
+46 -7

No files matched your search

+1 -1
View File
@@ -192,7 +192,7 @@ class Planner:
# circles: members are local siblings' neighbours and remote followers of the owner # circles: members are local siblings' neighbours and remote followers of the owner
for ref, grp in sorted(self.groups.items()): for ref, grp in sorted(self.groups.items()):
if grp["kind"] == "circle": if grp["kind"] == "circle":
pool = sorted(k for k in micro if k != grp["owner"] and k.split("/")[0] in ("privapub", "gts", "mastodon", "akkoma")) pool = sorted(k for k in micro if k != grp["owner"] and k.split("/")[0] in ("privapub", "gts", "mastodon", "akkoma", "hollo"))
for member in rng.sample(pool, min(len(pool), self.spec.get("circleMembers", 4))): for member in rng.sample(pool, min(len(pool), self.spec.get("circleMembers", 4))):
grp["members"].add(member) grp["members"].add(member)
self.step("graph", member, "join", {"group": ref}) self.step("graph", member, "join", {"group": ref})
+19 -3
View File
@@ -12,7 +12,7 @@ import gen
DIRS = ["in", "out", "via", "control", "local"] DIRS = ["in", "out", "via", "control", "local"]
DIR_LABEL = {"in": "into PrivaPub", "out": "from PrivaPub", "via": "through PrivaPub", "control": "peer to peer", DIR_LABEL = {"in": "into PrivaPub", "out": "from PrivaPub", "via": "through PrivaPub", "control": "peer to peer",
"local": "on its own server"} "local": "on its own server"}
GROUPS = [("deliver", "Delivery"), ("confine", "Confinement"), ("see", "Seen by the audience"), GROUPS = [("client", "decePub"), ("deliver", "Delivery"), ("confine", "Confinement"), ("see", "Seen by the audience"),
("hide", "Hidden from outsiders"), ("count", "Counts"), ("thread", "Threads"), ("edit", "Edits"), ("hide", "Hidden from outsiders"), ("count", "Counts"), ("thread", "Threads"), ("edit", "Edits"),
("delete", "Deletes"), ("graph", "Follows"), ("privacy", "Privacy")] ("delete", "Deletes"), ("graph", "Follows"), ("privacy", "Privacy")]
STATUS_ORDER = ["fail", "xpass", "xfail", "pass"] STATUS_ORDER = ["fail", "xpass", "xfail", "pass"]
@@ -84,7 +84,7 @@ def render(result, scenarios, title):
cells = result["cells"] cells = result["cells"]
totals = Counter(result["totals"]) totals = Counter(result["totals"])
observers = [p for p in gen.HOSTS if any(k.split("|")[2] == p for k in cells)] + \ observers = [p for p in gen.HOSTS if any(k.split("|")[2] == p for k in cells)] + \
(["*"] if any(k.split("|")[2] == "*" for k in cells) else []) [o for o in ("*", "decepub") if any(k.split("|")[2] == o for k in cells)]
features = sorted({k.split("|")[0] for k in cells}) features = sorted({k.split("|")[0] for k in cells})
out = [f"<title>{html.escape(title)}</title>", out = [f"<title>{html.escape(title)}</title>",
'<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400;600&family=IBM+Plex+Sans:wght@400;600&display=swap">', '<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400;600&family=IBM+Plex+Sans:wght@400;600&display=swap">',
@@ -93,7 +93,7 @@ def render(result, scenarios, title):
f"<p>Every object the town's accounts made, checked on every server it should and should not have reached, " f"<p>Every object the town's accounts made, checked on every server it should and should not have reached, "
f"through each server's database and API. A cell's squares are the directions it was checked in: " f"through each server's database and API. A cell's squares are the directions it was checked in: "
f"into PrivaPub, from PrivaPub, through PrivaPub, peer to peer, and on the author's own server.</p>" f"into PrivaPub, from PrivaPub, through PrivaPub, peer to peer, and on the author's own server.</p>"
f"<div class=\"meta\">{html.escape(result['run'])} · checked {html.escape(result['when'])}</div></header>") f"<div class=\"meta\">{html.escape(os.path.basename(result['run']))} · checked {html.escape(result['when'])}</div></header>")
out.append('<section class="totals" aria-label="Totals">' + "".join( out.append('<section class="totals" aria-label="Totals">' + "".join(
f'<div class="total"><i class="m s-{s}"></i><b>{totals.get(s, 0)}</b><span>{s}</span></div>' for s in STATUS_ORDER) + "</section>") f'<div class="total"><i class="m s-{s}"></i><b>{totals.get(s, 0)}</b><span>{s}</span></div>' for s in STATUS_ORDER) + "</section>")
out.append('<div class="legend"><span><i class="s-pass"></i> pass</span><span><i class="s-fail"></i> fail</span>' out.append('<div class="legend"><span><i class="s-pass"></i> pass</span><span><i class="s-fail"></i> fail</span>'
@@ -149,6 +149,22 @@ def main(args):
if os.path.exists(path): if os.path.exists(path):
with open(path) as f: with open(path) as f:
scenarios = [json.loads(line) for line in f if line.strip()] scenarios = [json.loads(line) for line in f if line.strip()]
# decePub's e2e cells (tests/e2e) join the servers' in the same matrix, under the observer "decepub"
client = os.path.join(seed.OUT, "client.jsonl")
if os.path.exists(client):
import check
with open(client) as f:
lines = [json.loads(line) for line in f if line.strip()]
latest = {}
for c in lines:
latest[(c["cell"], c.get("ref"))] = c # the latest result of each test
checks = check.classify(list(latest.values()), check.load_gaps())
for cell, v in check.summarise(checks).items():
result["cells"][cell] = v
from collections import Counter
totals = Counter(result["totals"])
totals.update(Counter(c["status"] for c in checks))
result["totals"] = totals
page = render(result, scenarios, "PrivaPub Town Matrix") page = render(result, scenarios, "PrivaPub Town Matrix")
target = os.path.join(run_dir, "report.html") target = os.path.join(run_dir, "report.html")
with open(target, "w") as f: with open(target, "w") as f:
+3 -2
View File
@@ -203,10 +203,11 @@ class Seeder:
def _wait_pending(self, s): def _wait_pending(self, s):
platform = s["actor"].split("/")[0] platform = s["actor"].split("/")[0]
d, sess, follower = driver(platform), self.session(s["actor"]), self.acct(s["args"]["target"]) d, sess, follower = driver(platform), self.session(s["actor"]), self.acct(s["args"]["target"])
for _ in range(30): # a refused first delivery is retried on the sender's backoff (16 s, then 31 s): wait out two of them
for _ in range(45):
if follower.lower() in (p.lower() for p in d.pending(sess)): if follower.lower() in (p.lower() for p in d.pending(sess)):
return d, sess, follower return d, sess, follower
time.sleep(1) time.sleep(2)
raise TimeoutError(f"{follower}'s follow request never reached {s['actor']}") raise TimeoutError(f"{follower}'s follow request never reached {s['actor']}")
def v_accept(self, s): def v_accept(self, s):
+22
View File
@@ -0,0 +1,22 @@
{
"schema": "pasture-town/1",
"name": "hollo-pair",
"seed": 20261005,
"peers": {
"privapub": {"roots": 1, "personas": [2, 2], "circles": 1, "communities": 0},
"hollo": {"accounts": 3}
},
"profiles": {"locked": 0.2, "bot": 0.0, "fields": [0, 2], "avatar": 0.9, "header": 0.3, "bioWords": [5, 12],
"langs": {"en": 80, "it": 20}},
"graph": {"follows": [2, 4], "mutual": 0.6, "pending": 0.0, "rejected": 0.0},
"circleMembers": 2,
"content": {
"posts": 24,
"mix": {"text": 40, "cw": 10, "tags": 10, "mention": 10, "image": 10, "poll": 10, "quote": 10},
"visibility": {"public": 50, "unlisted": 10, "followers": 20, "direct": 10, "circle": 10},
"replyRounds": 2, "replies": 0.5, "deeperReplies": 0.4
},
"interactions": {"likes": [0, 3], "boosts": [0, 1], "react": 0.4, "vote": 0.8, "bookmark": 0.1},
"mutations": {"edit": 0.15, "delete": 0.05, "blocks": 0, "mutes": 1, "reports": 0},
"time": {"roundSettleSeconds": 15, "graphSettleSeconds": 20, "settleSeconds": 30, "deadlineSeconds": 120}
}
+1 -1
View File
@@ -41,7 +41,7 @@ def main(argv):
print(json.dumps(made.__dict__)) print(json.dumps(made.__dict__))
else: else:
result = getattr(d, verb)(s, **params) result = getattr(d, verb)(s, **params)
print(json.dumps(result if not hasattr(result, "__dict__") else result.__dict__, default=str)) print(json.dumps(result if not hasattr(result, "__dict__") else result.__dict__, default=str, ensure_ascii=False))
return 0 return 0
if cmd == "stored": if cmd == "stored":
rows = driver(args[0]).stored(args[1:]) rows = driver(args[0]).stored(args[1:])