Lower the username floor to 3 characters on the admin-only write path
(account/channel set-username), separate from the 5-character self-service
minimum, since a deliberately short operator handle isn't the squatting the
higher floor guards against. Channel admin username assignment previously
had no length/format validation at all; this adds it.
Also adds three reference docs: the client's deep-link (tg://, owpg://)
surface, an OpenAPI 3.1 spec for both admin HTTP APIs (built from source,
not the stale docs/admin-panel-api.en.md), and a starter reserved-username
blocklist.
Adds a narrower spam sanction alongside the existing account freeze: a
restricted account keeps every existing membership and conversation, but
cannot join new channels/groups (public join or invite link) and cannot
start a new conversation with a non-contact. Reachable both as a standalone
admin action and as a decision on a reported user's moderation case, with
the same idempotent-supersession and appeal wiring freeze already has.
UpdateUsernameAdmin already skipped the reserved-word check in the
availability lookup, but UserStore.UpdateUsername's own write path
(replacePeerUsernameTx / CollectibleUsernameStore.SetEditableUsername)
enforces the same operator blocklist a second time, independently and
unconditionally. That second check is what was still rejecting an admin
handing out a word they'd deliberately reserved, with "username occupied".
Add UpdateUsernameAdmin/SetEditableUsernameAdmin bypass variants down the
write path (postgres and memory) and route users.Service's actual write
through them when the availability check was already bypassed.
Add users.Service.UpdateUsernameAdmin, which skips the
config.ReservedUsernames block that self-service UpdateUsername enforces.
Operators need to be able to hand a reserved word to a specific account
even though regular users can't claim it themselves.
redisstore.userBaseValue has no Deleted/DeletedAt/Status field, so caching a
deleted user silently reset Deleted back to false (and Status to the zero
UserStatusUnknown) on every round trip. That never self-healed: each later
cache miss reloaded the correctly tombstoned DB row and immediately
re-corrupted it on write, so once anyone looked a deleted account up, it kept
showing a blank name with "last seen recently" instead of "Deleted Account".
Keep deleted users off the base cache entirely so lookups always hit the
authoritative store, and stop presence overlay from touching a Deleted user's
Status at all as defense in depth.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Keep bot credentials out of durable command results, fail bot deletion closed when session revocation fails, reject invalid scam/fake states at every write boundary, and make direct collectible grants a single replayable PostgreSQL aggregate.
Also lock admin gift sender/message limits and add regression coverage for rollback, replay, moderation constraints, and credential redaction.
Admin console additions (Layer 228):
- Give Gifts: dedicated tab with sorted Lottie/TGS gift picker + inline form; grant any catalog gift to a user/channel from 777000 (no charge)
- Upgraded/collectible delivery: mint a unique gift with admin-selected model/pattern/backdrop and custom number, or random/auto (DB FK + UNIQUE(gift_id,num) enforce invariants)
- SCAM/FAKE flags for users/channels (migration 0136) with configurable profile warning (TELESRV_SCAM_WARNING/TELESRV_FAKE_WARNING)
- Support toggle, force channel settings incl. gigagroup (migration 0137), username management, cosmetic color/emoji-status
- Emoji admin tab (custom emoji list + document IDs + Lottie/TGS preview)
- Bot management; soft UI / dark theme
Wired through Router -> admin.Service -> adminapi -> BFF -> React panel (en/zh/ru).