Files
SocialPub/tools/pasture/town/town.py
T
thepraandClaude Opus 5.5 bdc8be4508 A restore on the live pasture: its own scenario, and two fixes it found
tools/pasture/scenarios/restore.sh backs the pasture up from the administrator's endpoint, then alice_restore deletes a
post and makes another, mastouser follows her, she blocks bob_restore and carol_restore is made; the restore, asked for
from the endpoint, keeps every protective act (19 checks). town.sh renew <peer> signs a peer's town accounts in again,
since a restore ends every session.

It found that a backup listed media files already missing when it was made, so verifying it failed and the restore was
refused: a manifest now lists only the files it holds (refusals are cut to five lines). And a local post made after the
backup now comes back as a deleted row, as a deletion leaves it, so it answers 410 and its id is never given again;
DeletedObject holds remote tombstones only.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LsXgEaXee4GCU1hwYgPJXw
2026-10-07 12:24:23 +02:00

71 lines
2.9 KiB
Python

"""The town's command line. Run through tools/pasture/town.sh.
selftest <peer>... each driver against its own server: accounts, posts, likes, replies, polls, edits,
deletes, and that its database view agrees with its API
drive <peer> <user> <verb> '<json>' one action as one town account, printed as JSON
stored <peer> <uri>... what a peer holds of each object (from its database, never fetching)
seen <peer> <user> <uri>... what one account can see of each object
renew <peer>... its accounts signed in again (after a PrivaPub restore ends every session)
plan <spec> the deterministic plan of a spec, printed as JSON lines
seed <spec> replays a spec's plan against the pasture, writing out/<run>/ledger.jsonl
check [run] sweeps every peer for what the ledger expects
report [run...] out/<run>/report.html; several runs (a village and pairs) in one matrix
backlog [run] [--write] docs/INTEROP-BACKLOG.md from the latest results
"""
import json
import os
import sys
HERE = os.path.dirname(os.path.abspath(__file__))
sys.path.insert(0, HERE)
from dialects import DRIVERS, driver # noqa: E402
from dialects.base import Account, PostSpec # noqa: E402
import state # noqa: E402
def main(argv):
if not argv or argv[0] in ("-h", "--help", "help"):
print(__doc__)
return 0
cmd, args = argv[0], argv[1:]
if cmd == "selftest":
import selftest
return selftest.run(args or [p for p in DRIVERS])
if cmd == "drive":
peer, user, verb = args[0], args[1], args[2]
params = json.loads(args[3]) if len(args) > 3 else {}
s = state.session(peer, user)
d = driver(peer)
if verb == "post":
made = d.post(s, PostSpec(**params))
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, ensure_ascii=False))
return 0
if cmd == "stored":
rows = driver(args[0]).stored(args[1:])
print(json.dumps({u: {k: v for k, v in r.__dict__.items() if k != "raw"} for u, r in rows.items()}, default=str))
return 0
if cmd == "renew":
# a peer's town accounts signed in again (a PrivaPub restore ends every session): new tokens, nothing else
for platform in args:
stored = state.sessions(platform)
state.remember(driver(platform).provision([s.account for s in stored]))
print(f"{platform}: {len(stored)} accounts signed in again")
return 0
if cmd == "seen":
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"):
module = __import__(cmd if cmd not in ("plan",) else "gen")
return module.main(args)
print(f"unknown command {cmd}", file=sys.stderr)
return 2
if __name__ == "__main__":
sys.exit(main(sys.argv[1:]) or 0)