R1 cleanup: collAnon and dead code gone, docs current, every peer's scenario in one sweep
Build / Build (push) Successful in 1m7s

Starts the completion program (docs/ROADMAP.md, "Owner decisions on finishing everything", 2026-10-17).

- Dead code: the commented-out collAnon blocks (discussions, seeding, admin user lists, HTTP/3, the old signature
  middleware), the empty PrivaPubWorkersConfiguration, PublicAvatarUsersService and UserPolicyType, unused helpers,
  the second IPasswordHasher registration, the PasswordGenerator package, SignUpAsync/LoginAsync's unused invitation
  parameters. "Starting PrivaPub"; a signed-in login is sent to "/", not to "/discussions".
- Stored fields nothing read: RootUser.Contacts and ResetPasswordToken*, the local Avatar.AvatarServer,
  EmailRecovery.RecoveryCode/RequestIP/CreationDate, MongoSettings.LogsDatabase, and AppConfiguration's
  upload/discussion keys, RequiresFirstTimeSetup and Version (old documents still load: MongoDB.Entities ignores extra
  elements). DmPost's indexes are no longer made. 49 collAnon keys leave FieldsNameResource in every language.
- /clientapi/data/current-version answers the build's ref. UsersIds is a list that can't throw, and the admin routes
  leave the administrator out without mutating the request.
- deploy.yml checks SecureMode on a name no persona can have, and the browser redirect on the directory's first local
  persona: @thepra is the administrator's own now.
- Town: decePub's e2e remnants (expect, client.jsonl, the decePub column) and town/tests go; the dead gaps/load
  commands too. Mobilizon no longer keeps an event for a deleted test.
- tools/pasture/sweep.sh runs every peer's scenario, batch by batch, as CLAUDE.md asks before a change to what
  PrivaPub sends.
- Docs: CLAUDE.md's repo map, route table (every frozen route) and stale lines; ROADMAP's status (setup, v1.25.0,
  v1.26.0, the program C), the 2026-10-17 decisions, and dated notes where earlier text no longer holds; FEDERATION.md's
  FEP list (7888, f228, 7628, ae0c and 8967 supported) and the deploy's persona.

Verified: dotnet build PrivaPub.sln -c Release (0 errors, no new warnings); bash -n on the touched scripts; py_compile
on the town; the pasture's gts, mastodon and lemmy scenarios on this build.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WpVCoDeM1qUTRp4fXQdfof
This commit is contained in:
thepraandClaude Opus 5.5 committed 2026-10-17 02:56:57 +02:00
1 parent 6727e1817a
commit ff5e635dda
71 files changed
+238 -4767

No files matched your search

+3 -4
View File
@@ -1,5 +1,5 @@
"""docs/INTEROP-BACKLOG.md from the latest town results, the bash scenarios and decePub's e2e cells: new failures (to fix
or to record in gaps.json), known gaps by phase, known gaps now passing (close them), and the scenarios' failures.
"""docs/INTEROP-BACKLOG.md from the latest town results and the bash scenarios: new failures (to fix or to record in
gaps.json), known gaps by phase, known gaps now passing (close them), and the scenarios' failures.
town.sh backlog [run] [--write]"""
import json
@@ -27,9 +27,8 @@ def main(args):
with open(os.path.join(run_dir, "results.json")) as f:
result = json.load(f)
gaps = {g["id"]: g for g in check.load_gaps()}
client = check.classify(check.latest_client([dict(c) for c in load_jsonl(os.path.join(seed.OUT, "client.jsonl"))]), list(gaps.values()))
scenarios = load_jsonl(os.path.join(seed.OUT, "scenarios.jsonl"))
checks = result["checks"] + client
checks = result["checks"]
new = defaultdict(list)
known = defaultdict(list)
-15
View File
@@ -478,21 +478,6 @@ def canary_hits(platform, canaries):
return sorted(c for c in canaries if c in dump)
def latest_client(lines):
"""decePub's e2e cells (out/client.jsonl, appended by every run) as each cell's latest run left them: the rows from
within an hour of the cell's newest, the newest per test. A run that picked other accounts leaves no stale failure."""
from datetime import datetime, timedelta
at = lambda c: datetime.fromisoformat(c["t"].replace("Z", "+00:00"))
newest = {}
for c in lines:
newest[c["cell"]] = max(newest.get(c["cell"], at(c)), at(c))
latest = {}
for c in sorted(lines, key=at):
if at(c) >= newest[c["cell"]] - timedelta(hours=1):
latest[(c["cell"], c.get("ref"))] = c
return list(latest.values())
def load_gaps():
try:
with open(os.path.join(HERE, "gaps.json")) as f:
-63
View File
@@ -1,63 +0,0 @@
"""What the town's objects should look like to a reader, for decePub's e2e tests: for every object PrivaPub holds and some
town persona may see, its ref, URI, PrivaPub id, origin, kind, visibility, the text marker, content warning, media alt
texts, poll options, quoted and parent URIs, and the personas that may see it.
town.sh expect [run] [--persona <username>]"""
import json
import sys
import check
import seed
from dialects import driver
def edited_cw(world, ref, args):
"""The warning an object shows now: an edit made before plans kept warnings replaced the post without one."""
edit = next((s for s in world.planner.steps if s["verb"] == "edit" and s["args"]["target"] == ref), None)
if edit is None or ref not in world.edited:
return args.get("cw")
return edit["args"].get("cw")
def objects(world):
steps = {s["ref"]: s for s in world.planner.steps if s.get("ref") and s["verb"] == "post"}
held = driver("privapub").stored([o["uri"] for o in world.objects.values()])
personas = [k for k in world.planner.accounts if k.startswith("privapub/")]
out = []
for ref, o in sorted(world.objects.items(), key=lambda kv: int(kv[0][1:])):
row = held.get(o["uri"])
if not row or not row.exists or row.deleted or ref in world.deleted:
continue
recipients = world.recipients(o)
readers = [p.split("/")[1] for p in personas
if p == o["author"] or p in recipients or o["visibility"] in ("public", "unlisted", "community")]
if not readers:
continue
args = steps[ref]["args"]
out.append({
"ref": ref, "uri": o["uri"], "id": row.local_id, "origin": o["origin"], "author": o["author"],
"kind": o["kind"], "visibility": o["visibility"], "marker": f"({ref})", "cw": edited_cw(world, ref, args),
"alts": [m["alt"] for m in args.get("media", [])], "poll": (args.get("poll") or {}).get("options"),
"quote": world.objects.get(o.get("quote") or "", {}).get("uri"),
"parent": world.objects.get(o.get("parent") or "", {}).get("uri"),
"edited": ref in world.edited, "title": args.get("title"), "readers": readers,
"followers_of_author": sorted(p.split("/")[1] for p in world.follows.get(o["author"], set()) if p.startswith("privapub/")),
})
return out
def main(args):
name = next((a for a in args if not a.startswith("--") and not a.startswith("privapub/")), "village")
persona = None
if "--persona" in args:
persona = args[args.index("--persona") + 1].split("/")[-1]
world = check.World(seed.run_dir(name))
items = objects(world)
if persona:
items = [i for i in items if persona in i["readers"]]
print(json.dumps(items, ensure_ascii=False))
return 0
if __name__ == "__main__":
sys.exit(main(sys.argv[1:]))
+1 -1
View File
@@ -75,7 +75,7 @@
"code": "misskey-dev/misskey packages/backend/src/core/NoteDeleteService.ts: only repliesCount is decremented on delete",
"opened": "2026-10-05",
"status": "open",
"note": "the renote row is gone after our Undo (checked in its database); renoteCount stays up. Sharkey decrements it."
"note": "the renote row is gone after our Undo (checked in its database); renoteCount stays up. Sharkey decrements it. Its match named decePub's e2e cells (client.action.boost), deleted 2026-10-17, so it matches nothing until a town cell checks Misskey's renote count."
},
{
"id": "G-0006",
+1 -1
View File
@@ -1,4 +1,4 @@
"""The plan: a fake community drawn from a spec and its seed, the same on every run (tests/test_gen.py pins its hash).
"""The plan: a fake community drawn from a spec and its seed, the same on every run.
A plan is a list of steps, each {n, phase, round, actor, verb, args, ref}. Accounts are named "<platform>/<username>",
objects by a ref ("p12"); the seeder turns refs into URIs as it goes. Phases run in order:
+2 -15
View File
@@ -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 = [("client", "decePub"), ("deliver", "Delivery"), ("relay", "Replies passed on"), ("confine", "Confinement"),
GROUPS = [("deliver", "Delivery"), ("relay", "Replies passed on"), ("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)] + \
[o for o in ("*", "decepub") if any(k.split("|")[2] == o for k in cells)]
[o for o in ("*",) 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">',
@@ -169,19 +169,6 @@ def main(args):
if os.path.exists(path):
with open(path) as f:
scenarios = [json.loads(line) for line in f if line.strip()]
# a client's cells, when any, 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()]
checks = check.classify(check.latest_client(lines), 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:
Whitespace-only changes.
-79
View File
@@ -1,79 +0,0 @@
"""The town's own logic, with no pasture: python3 -m unittest discover -s tools/pasture/town/tests"""
import os
import sys
import unittest
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
import check # noqa: E402
import gen # noqa: E402
from core.rng import Rng # noqa: E402
class RngTests(unittest.TestCase):
def test_the_same_seed_and_name_give_the_same_stream(self):
a, b = Rng(7, "x"), Rng(7, "x")
self.assertEqual([a.next64() for _ in range(5)], [b.next64() for _ in range(5)])
def test_substreams_do_not_disturb_each_other(self):
root = Rng(7)
before = Rng(7).sub("graph").next64()
root.sub("content").next64()
self.assertEqual(before, root.sub("graph").next64())
def test_below_stays_in_range(self):
r = Rng(1)
self.assertTrue(all(0 <= r.below(3) < 3 for _ in range(1000)))
class PlanTests(unittest.TestCase):
def test_a_spec_always_plans_the_same(self):
spec = gen.load("village")
self.assertEqual(gen.digest(gen.plan(spec)), gen.digest(gen.plan(spec)))
def test_every_post_names_its_marker_and_every_reply_a_parent_planned_before_it(self):
planner = gen.Planner(gen.load("village"))
steps = planner.plan()
made = set()
for s in steps:
if s["verb"] != "post":
continue
self.assertIn(f"({s['ref']})", s["args"]["text"])
if s["args"].get("reply_to"):
self.assertIn(s["args"]["reply_to"], made)
made.add(s["ref"])
def test_lemmy_accounts_never_follow_people_and_platforms_get_only_what_they_can_do(self):
steps = gen.plan(gen.load("village"))
for s in steps:
platform = s["actor"].split("/")[0]
if s["verb"] == "follow":
self.assertNotEqual("lemmy", platform)
if s["verb"] in ("react", "edit", "vote", "boost"):
self.assertIn({"react": "react", "edit": "edit", "vote": "vote", "boost": "boost"}[s["verb"]], gen.CAPS[platform])
def test_a_stored_plan_rebuilds_the_same_world(self):
planner = gen.Planner(gen.load("village"))
planner.plan()
stored = gen.Stored(gen.snapshot(planner))
self.assertEqual(planner.objects.keys(), stored.objects.keys())
self.assertEqual(planner.objects["p1"]["mentions"], stored.objects["p1"]["mentions"])
class GapTests(unittest.TestCase):
def test_a_known_gap_turns_failures_into_xfail_and_passes_into_xpass(self):
gaps = [{"id": "G-9", "match": {"feature": r"count\.like\..*", "observer": "privapub"}, "status": "open"}]
checks = check.classify([
{"feature": "count.like.followers", "observer": "privapub", "ok": False},
{"feature": "count.like.public", "observer": "privapub", "ok": True},
{"feature": "count.like.public", "observer": "gts", "ok": False},
], gaps)
self.assertEqual(["xfail", "xpass", "fail"], [c["status"] for c in checks])
def test_a_closed_gap_matches_nothing(self):
gaps = [{"id": "G-9", "match": {"feature": ".*"}, "status": "closed"}]
self.assertEqual("fail", check.classify([{"feature": "x", "ok": False}], gaps)[0]["status"])
if __name__ == "__main__":
unittest.main()
+1 -1
View File
@@ -59,7 +59,7 @@ def main(argv):
s = state.session(args[0], args[1])
print(json.dumps(driver(args[0]).seen(s, args[2:])))
return 0
if cmd in ("plan", "seed", "check", "report", "backlog", "gaps", "expect", "load"):
if cmd in ("plan", "seed", "check", "report", "backlog"):
module = __import__(cmd if cmd not in ("plan",) else "gen")
return module.main(args)
print(f"unknown command {cmd}", file=sys.stderr)