side-quest: cross-user XP leaderboard with public-key-verified score submissions #1
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Proposal for a public leaderboard on top of the side-quest XP plugin. Today the ledger (
~/.claude/side-quest/xp.json) is one person across many machines, synced over MQTT. A leaderboard means multiple people, which needs a trust model — nothing stops someone editing theirxp.jsonto999999999.Verification: sign submissions, verify against the user's public key
Each player has a keypair. A score submission is a canonical JSON payload —
{player, total_xp, level, quests_completed, epoch, ts}— with a detached signature. The leaderboard verifies the signature against that player's registered public key before accepting the row.Key trust root — reuse Forgejo. Every player already has SSH keys on git.trtmn.io, and Forgejo publishes them at
https://git.trtmn.io/<user>.keys. No separate key registry to build: the leaderboard fetches<user>.keys(cached) and verifies against it. Standardize on raw ed25519 detached signatures over the canonical payload rather than thessh-keygen -Y signarmored envelope — ed25519 verify is in WebCrypto, and parsing the OpenSSH pubkey wire format for thessh-ed25519line is ~20 lines, versus implementing the full SSHSIG format.What this does and does not stop: a signature proves the submission came from the key owner — nobody can forge your score. It does not stop the key owner inflating their own score. Closing that needs the ledger to be an append-only, independently-replayable event log (it partly is already — the MQTT event stream), which is a larger scope. Recommend shipping signature verification first and treating self-inflation as out of scope for v1 (social trust among a small group).
Where to host — options considered
Audience decision: fully public.
POST /submitwith the signed payload -> Worker fetches/caches<user>.keys, verifies ed25519, upserts into D1.GET /renders the board (HTML) or serves JSON for a static Pages frontend. Public URL, free tier, no server to run, rate-limitable, single trusted verifier, fits the existing Cloudflare-heavy setup. Cost: a new Worker + D1 binding, ~150 lines.xp.sh leaderboard-submitopens/updates a PR addingscores/<player>.json+.sig; an Action verifies every sig against<user>.keyson merge and regeneratesLEADERBOARD.md. No new infra, git history is the audit trail. But: needs push/PR access per player (or a bot token), git.trtmn.io sits behind Cloudflare Access so the rendered page is not truly public without a bypass, and a PR per session is heavyweight for a number that changes constantly.sidequest/leaderboard/<player>with the signed payload; a clancy-side subscriber verifies and writes a static board to R2/Pages. Reuses the transport that already exists, near-real-time. But the broker is Tailscale-only and unauthenticated — fine for a private board, but a public one still needs the separate public renderer, so this is option 1's verifier plus an extra hop.Recommendation: option 1 (Worker + D1), raw ed25519 signatures, pubkeys from Forgejo
<user>.keys. Submission via a newxp.sh leaderboard-submitthat signs with~/.ssh/id_ed25519and does one HTTPS POST — wired into the existing Stop hook (debounced) or run periodically by the sync daemon.Open questions
Scope
New Worker + D1 (or chosen host), an
xp.sh leaderboard-submitsubcommand + ed25519 signing, key-fetch/verify logic, board rendering, and docs. Cross-cutting (plugin + new infra).Filed from a
/side-questworking session; see side-quest 2.5.0 for the current ledger/sync design.