mirror of
https://github.com/prosolis/gogobee.git
synced 2026-07-15 00:32:40 +00:00
Pete holds the chips; we hold the euros and are the only one who can move them. This is the loop that turns an escrow row on games.parodia.dev into a real balance change here. It is a poll because Pete cannot call us and isn't going to be able to. Every step is keyed on the escrow guid, because every step can be interrupted in the worst possible place: die after DebitIdem and before the verdict is queued, and Pete re-offers the row, we claim it again, the debit replays as a no-op and reports the same answer. The player is charged once. That is the only property here that really matters, and there is a test that kills us three times to prove it. The verdict rides the queue that already carries adventure facts — it wants the same durability, backoff and parking, so the row now says where it's going rather than the queue growing a twin. Flush sends it at once instead of after a 15s sender tick, because there's a person at the other end watching a spinner. First GET gogobee has ever made to Pete.
101 lines
5.8 KiB
Markdown
101 lines
5.8 KiB
Markdown
# GogoBee → Appservice Auth Migration (multi-session)
|
|
|
|
Goal: move TwinBee's Matrix auth from the **MAS OAuth 2.0 device grant** (working
|
|
stopgap) to the **full appservice transaction/push model** — the MAS-durable end
|
|
state: no human login, no MFA, no consent, `as_token` never expires, scales at
|
|
485+ rooms. The device grant stays behind `AUTH_MODE=masdevice` as instant
|
|
rollback.
|
|
|
|
Why the *previous* appservice attempt failed: it was a hybrid (as_token + `/sync`),
|
|
and Synapse forbids appservice-namespace users from `/sync`. This migration
|
|
replaces `/sync` with Synapse→bot transaction push + MSC3202/MSC2409 E2EE
|
|
extensions, which is the supported path in mautrix v0.28.1.
|
|
|
|
Keep this doc working-tree only — do NOT commit (see feedback_dont_commit_plan_mds).
|
|
|
|
## Key facts established (investigation, 2026-07-03)
|
|
|
|
- **HEAD 20d1f92 is MISLABELED**: commit msg says "device grant" but it only
|
|
deleted registration.yaml.example; committed client.go is still the FAILED
|
|
appservice+/sync hybrid. The real device-grant (masauth.go + client.go rewrite)
|
|
is UNCOMMITTED working tree — that's what runs in prod. Backed up to scratchpad.
|
|
- mautrix v0.28.1 fully supports appservice E2EE: transaction carries
|
|
`to_device`/`device_lists`/`device_one_time_keys_count` (stable + msc2409/msc3202
|
|
unstable field names); `CreateDeviceMSC4190`; cryptohelper `MSC4190=true` mints
|
|
the device (no /login). HEAD's old appservice code already had auth+crypto
|
|
correct — only the /sync event loop was wrong.
|
|
- To-device processing gates on `Registration.EphemeralEvents` (yaml
|
|
`receive_ephemeral: true`) — `appservice/http.go:133`.
|
|
- Encrypted rooms: must feed `m.room.encryption`/`m.room.member` to the client's
|
|
StateStore via `mautrix.UpdateStateStore(...)` + `mach.HandleMemberEvent`, or the
|
|
bot sends plaintext into encrypted rooms / can't share keys.
|
|
- Both hosts SSH-reachable: `reala@parodia.dev` (Synapse/MAS/Authentik docker),
|
|
`reala@192.168.1.212` (millenia, bot in screen). Retired registration at
|
|
`/home/reala/matrix/compose/synapse/gogobee-registration.yaml.retired-20260703-223425`.
|
|
|
|
## Decisions (user, 2026-07-03)
|
|
|
|
- Cutover: **AUTH_MODE flag** (keep device grant as rollback), not full replace.
|
|
- Synapse changes: **I apply + restart, confirm right before restart**.
|
|
|
|
## Session 1 — bot-side code (local, no prod risk) ✅ DONE (build+vet green)
|
|
|
|
- [x] `Config`: AuthMode + appservice fields (RegistrationPath, ListenHost/Port,
|
|
HomeserverDomain).
|
|
- [x] `internal/bot/session.go`: `Session` unifying both modes (OnEventType/Run/
|
|
Stop); `NewSession(cfg)` dispatches on AuthMode; masdevice path wraps existing
|
|
device-grant NewClient + DefaultSyncer + sync loop (verbatim).
|
|
- [x] `internal/bot/appservice.go`: LoadRegistration → CreateFull → BotClient;
|
|
cryptohelper MSC4190 device create; ep.OnOTK/OnDeviceList + 9 crypto to-device
|
|
types → crypto machine; EventEncrypted decrypt+redispatch; StateStore
|
|
population (encryption + members) via lazy per-room resolveRoom + UpdateStateStore
|
|
on member/encryption state; HTTP listener via as.Start() in goroutine.
|
|
- [x] `main.go`: NewSession + sess.OnEventType + sess.Run; env AUTH_MODE,
|
|
AS_REGISTRATION, AS_LISTEN_HOST, AS_LISTEN_PORT, HOMESERVER_DOMAIN; envInt helper.
|
|
- [x] `.env.example` documented; go.mod tidied (zerolog now direct dep).
|
|
- [x] `go build`/`go vet` green (CGO=1 -tags goolm).
|
|
|
|
New files: internal/bot/session.go, internal/bot/appservice.go. NOT yet committed.
|
|
|
|
### Known correctness risks to verify in Session 3
|
|
- **resolveRoom is lazy on inbound encrypted events only.** Scheduled/broadcast
|
|
posts into an encrypted room the bot hasn't received from yet would send
|
|
plaintext (StateStore unresolved). Mitigation for S3: prewarm the configured
|
|
rooms (BROADCAST_ROOMS, GAMES_ROOM, ESTEEMED_ROOM, MOD_ADMIN_ROOM, MINIFLUX_*)
|
|
at startup, or resolve all joined rooms once. Interactive `!` commands are
|
|
reply-driven so lazy resolve covers them.
|
|
- Whether crypto machine successfully shares keys after ReplaceCachedMembers +
|
|
device fetch (FetchKeys) — the make-or-break, only testable against live Synapse.
|
|
- MSC4190 device creation must succeed against MAS-fronted Synapse (untested end
|
|
to end; HEAD's old code used the same path but was never verified past whoami).
|
|
|
|
## Session 2 — Synapse infra on parodia (touches live homeserver)
|
|
|
|
- [ ] Restore gogobee-registration.yaml: set `url:` = bot tailnet addr, add
|
|
`receive_ephemeral: true`, `org.matrix.msc3202: true`, keep
|
|
`io.element.msc4190: true`, `rate_limited: false`.
|
|
- [ ] homeserver.yaml `experimental_features`: msc2409_to_device_messages_enabled,
|
|
msc3202_device_masquerading, msc3202_transaction_extensions,
|
|
msc4190_device_management. Wire `app_service_config_files`.
|
|
- [ ] Determine bot's headscale tailnet address on parodia (Synapse must reach the
|
|
bot's ListenPort). Confirm routing.
|
|
- [ ] Backup homeserver.yaml + registration; **confirm before restart**; restart
|
|
matrix-synapse; check it comes back for all users.
|
|
|
|
## Session 3 — cutover + E2EE verification on millenia
|
|
|
|
- [ ] Copy registration to bot host; set AUTH_MODE=appservice + AS_* env.
|
|
- [ ] rsync source + build in place (CGO=1 -tags goolm), restart screen session.
|
|
Remove empty local data/gogobee.db first (empty_local_db_wipes_prod).
|
|
- [ ] Verify: transactions arrive (listener logs), whoami OK, MSC4190 device made,
|
|
bot responds to `!` command in a PLAINTEXT room.
|
|
- [ ] Verify E2EE: bot decrypts an incoming encrypted-room message AND its reply
|
|
decrypts for a human client (encrypt-out works). This is the make-or-break.
|
|
- [ ] If broken: AUTH_MODE=masdevice, restart → back on device grant. Then triage.
|
|
|
|
## Cleanup (after proven)
|
|
|
|
- Prune stale @twinbee devices, orphan MAS client, retire masauth.go or leave
|
|
behind the flag. Fix HEAD mislabel (the eventual commit should actually contain
|
|
device-grant + appservice, not the stale hybrid).
|