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:
1 parent
f70ec59359
commit
4b6d261739
5 files changed
+46
-7
No files matched your search
@@ -192,7 +192,7 @@ class Planner:
|
||||
# circles: members are local siblings' neighbours and remote followers of the owner
|
||||
for ref, grp in sorted(self.groups.items()):
|
||||
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))):
|
||||
grp["members"].add(member)
|
||||
self.step("graph", member, "join", {"group": ref})
|
||||
|
||||
@@ -12,7 +12,7 @@ import gen
|
||||
DIRS = ["in", "out", "via", "control", "local"]
|
||||
DIR_LABEL = {"in": "into PrivaPub", "out": "from PrivaPub", "via": "through PrivaPub", "control": "peer to peer",
|
||||
"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"),
|
||||
("delete", "Deletes"), ("graph", "Follows"), ("privacy", "Privacy")]
|
||||
STATUS_ORDER = ["fail", "xpass", "xfail", "pass"]
|
||||
@@ -84,7 +84,7 @@ def render(result, scenarios, title):
|
||||
cells = result["cells"]
|
||||
totals = Counter(result["totals"])
|
||||
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})
|
||||
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">',
|
||||
@@ -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"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"<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(
|
||||
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>'
|
||||
@@ -149,6 +149,22 @@ def main(args):
|
||||
if os.path.exists(path):
|
||||
with open(path) as f:
|
||||
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")
|
||||
target = os.path.join(run_dir, "report.html")
|
||||
with open(target, "w") as f:
|
||||
|
||||
@@ -203,10 +203,11 @@ class Seeder:
|
||||
def _wait_pending(self, s):
|
||||
platform = s["actor"].split("/")[0]
|
||||
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)):
|
||||
return d, sess, follower
|
||||
time.sleep(1)
|
||||
time.sleep(2)
|
||||
raise TimeoutError(f"{follower}'s follow request never reached {s['actor']}")
|
||||
|
||||
def v_accept(self, s):
|
||||
|
||||
@@ -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}
|
||||
}
|
||||
@@ -41,7 +41,7 @@ def main(argv):
|
||||
print(json.dumps(made.__dict__))
|
||||
else:
|
||||
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
|
||||
if cmd == "stored":
|
||||
rows = driver(args[0]).stored(args[1:])
|
||||
|
||||
Reference in new issue
Block a user