Skip to content

v0.105.1 — A character's owner on a token the GM placed

Shipped 2026-09-21 — built on hotfix/0.105.1, one commit; the merge hash and tag are in the changelog. A hotfix from the first prod session on v0.105.0: a player who owns a character could not pick up the token the GM had placed for it, and the admin's ways around that were shut too.

  • The linked character's owner is in the impersonatable set. GET /api/tokens/impersonatable answered from three sources: the token's owner column (whoever placed it), the token allows, and the character's grant rows. The owner never has a row — that is the one implicit permission resolveCharacterAccess exists to apply — so a player whose character the GM dropped onto the map was in none of the three. The client guard reverted every attempt they made at their own character with "You don't have permission to impersonate…", and an admin impersonating them saw the same notification over a selection that otherwise held. The per-token check (canImpersonateToken) had reached the owner through the permission cascade all along; the list had not. It now resolves each owned linked character through resolveCharacterAccess, so a sheet a campaign locks (players.sheetEditing: gm-only) answers control there rather than edit, as it does everywhere else.
  • Superuser Mode can transfer a character's ownership — including to the admin. POST /api/characters/:id/transfer compared the owner column and nothing else. The mode passes every other check on the character, and the page therefore offered the menu item, and the route refused it with "Only the owner can transfer ownership"; and "Cannot transfer to yourself" was precisely the superuser's case. Now: the owner or the mode may transfer; the target being the current owner is the 400; the previous owner (not the caller) keeps edit; and the new owner is not notified when they did it themselves. The modal offers "Me" when the viewer is not the owner and says who keeps edit access.
  • The Transfer Ownership modal goes through authFetch. It was a bare fetch with the bearer token, which never sent X-Impersonate-User-Id — so an admin impersonating the owner opened it as the owner and submitted it as themselves, and was refused as a non-owner.
  • CI — the pre-deploy snapshot prunes itself. Every push to main took a manual RDS snapshot and nothing ever removed one; manual snapshots never expire and the account is capped at 100 per region, so v0.104.4's deploy died at SnapshotQuotaExceeded with 93 of them. The step now deletes its own older <instance>-pre-deploy-* snapshots (never a hand-made one) down to RDS_PRE_DEPLOY_SNAPSHOTS_KEEP (repo var, default 20) before taking the new one; the 7-day automated backups remain the longer net.

characterOwner.play.test.ts runs the first two against the real routers on the harness: the owner in the set at edit, the GM still in, a member with no grant still out; the superuser's transfer to themselves refused without the mode, accepted with it, the previous owner left at edit.