diff --git a/tradein-mvp/backend/app/core/auth.py b/tradein-mvp/backend/app/core/auth.py
index 0dbafb3a..06f896b6 100644
--- a/tradein-mvp/backend/app/core/auth.py
+++ b/tradein-mvp/backend/app/core/auth.py
@@ -50,20 +50,14 @@ class UserScope(TypedDict):
# Account → brand mapping (#657 brand-by-account)
# ---------------------------------------------------------------------------
#
-# Брендирование «Практика» (#657 brand-by-account) применяется ТОЛЬКО под
-# аккаунтом `praktika`. Резолвится в /me как UserScope.brand, а при генерации
-# trade-in PDF — по `created_by` оценки (анти-spoofing #7, бренд не хранится).
-# Остальные юзеры (вкл. `kopylov`) → None (стандартный generic UI/PDF). Slug
-# должен существовать в таблице `brands` (см. services/brand.py) — иначе
-# useBrand fallback на generic.
-#
-# `kopylov` ранее мапился на бренд `praktika`, но это приводило к утечке лого
-# «Практики» в отчёты, созданные под `kopylov`, не тем клиентам (инцидент:
-# отчёт для Брусники с лого Практики) — маппинг удалён. NB: kopylov/praktika
-# остаются в роли `pilot` в roles.yaml — это РОЛЬ, не бренд, не трогаем.
-_USERNAME_BRAND: dict[str, str] = {
- "praktika": "praktika",
-}
+# White-label бренд «Практика» снят целиком (бизнес-решение 2026-06-19): строка
+# удалена из таблицы `brands` (миграция 121), SVG-логотип убран из фронта.
+# Маппинг аккаунт→бренд теперь пустой — все юзеры резолвятся в None (generic
+# UI/PDF). Механизм сохранён для будущих white-label клиентов: добавить slug
+# сюда + строку в `brands` (см. services/brand.py). NB: аккаунты praktika/
+# kopylov остаются пилот-логинами (роль `pilot` в roles.yaml) — это РОЛЬ, не
+# бренд, не трогаем.
+_USERNAME_BRAND: dict[str, str] = {}
def get_brand_for_user(username: str) -> str | None:
diff --git a/tradein-mvp/backend/data/sql/121_remove_brand_praktika.sql b/tradein-mvp/backend/data/sql/121_remove_brand_praktika.sql
new file mode 100644
index 00000000..4235cb1b
--- /dev/null
+++ b/tradein-mvp/backend/data/sql/121_remove_brand_praktika.sql
@@ -0,0 +1,9 @@
+-- 121_remove_brand_praktika.sql
+-- Полное удаление white-label бренда «Практика» (бизнес-решение 2026-06-19).
+-- Бренд снят целиком: эта миграция убирает строку из brands; аккаунт-маппинг
+-- praktika/kopylov удалён в app/core/auth.py; SVG-логотип удалён из фронта.
+-- Идемпотентно: DELETE по PK slug. На fresh-build удаляет строку, засеянную 002
+-- и обновлённую 084/099 (эта миграция с бОльшим номером — выполняется последней).
+BEGIN;
+DELETE FROM brands WHERE slug = 'praktika';
+COMMIT;
diff --git a/tradein-mvp/backend/tests/test_me_brand.py b/tradein-mvp/backend/tests/test_me_brand.py
index f6866334..1c932bd9 100644
--- a/tradein-mvp/backend/tests/test_me_brand.py
+++ b/tradein-mvp/backend/tests/test_me_brand.py
@@ -1,7 +1,8 @@
"""Tests for #657 brand-by-account — UserScope.brand resolution.
-Pure-function tests against app.core.auth: brand slug привязывается к аккаунту
-(ТОЛЬКО praktika → "praktika"), остальные юзеры — включая kopylov — → None.
+Pure-function tests against app.core.auth. White-label бренд «Практика» снят
+целиком (бизнес-решение 2026-06-19): маппинг аккаунт→бренд пустой, поэтому ВСЕ
+юзеры (включая praktika/kopylov) резолвятся в None (generic UI/PDF).
get_user_scope прокидывает brand в scope, который /me отдаёт фронту.
Без БД и сети: читается реальный auth/roles.yaml (ancestor-walk внутри репо).
@@ -28,10 +29,9 @@ from app.core.auth import get_brand_for_user, get_user_scope # noqa: E402
@pytest.mark.parametrize(
("username", "expected"),
[
- ("praktika", "praktika"),
- # kopylov больше НЕ брендируется как Практика (#657 brand leak fix):
- # бренд резолвится по created_by при генерации PDF, поэтому утечка лого
- # Практики в отчёты под kopylov исправляется тем, что kopylov → generic.
+ # «Практика» снята целиком (2026-06-19): praktika/kopylov больше не
+ # брендируются — маппинг пустой, все аккаунты → generic UI/PDF.
+ ("praktika", None),
("kopylov", None),
("admin", None),
("user1", None),
@@ -43,15 +43,16 @@ def test_get_brand_for_user(username: str, expected: str | None) -> None:
def test_user_scope_brand_none_for_kopylov() -> None:
- # Регресс-гард на #657 brand leak: kopylov-оценки рендерят generic PDF.
+ # Регресс-гард: бренд снят, kopylov-оценки рендерят generic PDF.
scope = get_user_scope("kopylov")
assert scope["brand"] is None
assert scope["username"] == "kopylov"
-def test_user_scope_carries_brand_for_praktika() -> None:
+def test_user_scope_brand_none_for_praktika() -> None:
+ # Бренд «Практика» удалён — praktika-аккаунт теперь generic.
scope = get_user_scope("praktika")
- assert scope["brand"] == "praktika"
+ assert scope["brand"] is None
assert scope["username"] == "praktika"
diff --git a/tradein-mvp/frontend/public/brands/praktika.svg b/tradein-mvp/frontend/public/brands/praktika.svg
deleted file mode 100644
index a452e484..00000000
--- a/tradein-mvp/frontend/public/brands/praktika.svg
+++ /dev/null
@@ -1 +0,0 @@
-
diff --git a/tradein-mvp/frontend/src/components/trade-in/Topbar.tsx b/tradein-mvp/frontend/src/components/trade-in/Topbar.tsx
index 486415c3..c0c444e6 100644
--- a/tradein-mvp/frontend/src/components/trade-in/Topbar.tsx
+++ b/tradein-mvp/frontend/src/components/trade-in/Topbar.tsx
@@ -118,7 +118,7 @@ export function Topbar({ active }: TopbarProps) {
{brand ? (
/* logo_url из бренд-конфига → safeUrl (блок javascript:/data: схем, #766).
Брендовый логотип — полноразмерный wordmark (содержит название), поэтому
- отдельный brand-product текст НЕ рендерим, иначе дубль («ПРАКТИКА» ×2).
+ отдельный brand-product текст НЕ рендерим, иначе дубль названия бренда ×2.
Невалидный/пустой URL → fallback TI-марка + текстовое название бренда. */
safeUrl(brand.logo_url) ? (
` — демо/превью, только если у аккаунта нет бренда.
- * (praktika/kopylov логинятся → свой бренд автоматически из me.brand, без ?brand=.)
* Если ни один не задан — нет бренда (generic UI), хук возвращает {data: null}.
*
* Mirror паттерна useMe.ts: TanStack Query, staleTime/gcTime Infinity