Skip to content

DO NOT MERGE: read-only Matrix ingest bot for BattleLog - #38

Draft
benjamsf wants to merge 4 commits into
mainfrom
feat/battlelog-ingest-bot
Draft

DO NOT MERGE: read-only Matrix ingest bot for BattleLog#38
benjamsf wants to merge 4 commits into
mainfrom
feat/battlelog-ingest-bot

Conversation

@benjamsf

@benjamsf benjamsf commented Aug 27, 2026

Copy link
Copy Markdown
Member

Relates to the BattleLog proof-of-concept test. Do not merge — opened so CI publishes a branch-tagged image for a test server.

User-Facing Summary

  • A read-only Matrix ingest bot, joined to the Space and every room in it, so a product's event feed can carry what is said in the deployment's rooms — including end-to-end encrypted ones.
  • New POST /api/v1/interop/add and GET /api/v1/interop/authz hand a registered peer product a bearer token for that bot.
  • The bot is created by shared-secret registration, not admin login-as-user: an admin-minted token has no device_id, and megolm room keys are shared to a device, so a device-less token can never decrypt anything.
  • Rate-limit exempted, since a /sync loop otherwise hits 429.

Why It's Good for the Product (Release Goal)

  • Encrypted rooms become readable to an authorised product without weakening the rooms.
  • The bot's token can only read and write rooms it is in — unlike handing out SYNAPSE_REGISTRATION_SECRET (creates server admins) or the existing matrixrmapi-bot token (a server admin at PL 200 that can deactivate any user).
  • Token is cached, validated before reuse and re-minted when invalid, matching SynapseAdmin.setup()'s existing contract.

benjamsf and others added 4 commits August 25, 2026 14:13
BattleLog wants to turn Matrix room messages into events in its feed. It
needs a Matrix identity to read timelines with, and it must not be handed
anything that can act on the homeserver beyond that.

Adds @battlelog-bot, a plain local user created with the admin
create-or-modify API and given a token with the admin login-as-user API,
so no password is ever set and the registration shared secret is not
involved. The bot is joined to the Space, every standard room and every
child of the Space, so rooms users made themselves are covered too
without anyone inviting it by hand. Neither
SYNAPSE_REGISTRATION_SECRET nor the admin bot's own token is exposed.

Adds POST /api/v1/interop/add (RASENMAEHER-only) and
GET /api/v1/interop/authz (the peer product itself, over mTLS),
following rmmtxauthz. Token and peer registry are files, not process
state, because gunicorn runs several workers and the two calls can land
on different ones.

The standard rooms are all megolm-encrypted, so a consumer without
crypto support sees ciphertext there. That is the caller's problem to
report per room, and the README says so.

Also dedupes the public-room key tuple that existed in both startup.py
and api/usercrud.py, and folds _exempt_bot_from_ratelimit into the new
reusable override_ratelimit.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Reading an end-to-end-encrypted room needs a device. Room keys are shared
client-to-device, so a consumer without one is handed nothing it can
decrypt, no matter which rooms it is in. The admin login-as-user API
mints a device-less token on purpose — verified against a live server,
whoami for that token returns no device_id at all — which made E2EE
impossible for BattleLog by construction.

So the bot is created by shared-secret registration instead, which does
create a device and binds the token to it. register_bot is now
parameterised for non-admin, with the MAC's 4th field the literal
notadmin, per Synapse's reference implementation. The token is stored and
reused: re-registering would mean a new device that lacks every room key
already shared with the old one.

A password login was the obvious alternative and does not work here —
this homeserver offers only m.login.sso, m.login.token and
m.login.application_service, since password_config.enabled is false.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The earlier battlelog-bot account was created with a device-less token,
and Synapse keeps a localpart reserved even after an account is
deactivated — so it cannot be re-registered into something that can hold
encryption keys. A new name is the only way forward, and this one says
what the account is for.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
My own commits left lines past ruff's default 88 columns in the two files I
added or edited. This repo sets no [tool.ruff] section, so the pinned
ruff-format hook uses that default and would rewrite them on anyone's next
commit.

It went unnoticed because prek aborts this repo's run on prettier-ts and
eslint — TypeScript hooks generated into a Python repo's config, which have no
node_modules to run from — before it ever reaches the Python formatter.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant