# Программа `shine_login_guard`

Документ описывает целевое поведение программы классификации логинов SHiNE.

Задача программы:

- по логину определить класс регистрации;
- вернуть результат в `return_data`;
- не хранить PDA и не вести собственное состояние.

## 1. Назначение

`shine_login_guard` — маленькая служебная программа, которую вызывает `shine_users` через CPI до регистрации пользователя.

Она отвечает только на вопрос:

- можно ли зарегистрировать логин автоматически;
- является ли логин premium;
- является ли логин trademark/company.

## 2. Program ID

Текущий program id:

- `3xkopA7cXagxzMFrKdv3NCBfV6BKiRJCk69kr27M2sRo`

## 3. Состояние

У программы нет PDA-состояний и persistent storage.

Она опирается на статически сгенерированные словари, встроенные в бинарник на этапе сборки.

## 4. Внешние артефакты

Словари хранятся в:

- `src/dictionaries/premium/**/*.txt`
- `src/dictionaries/trademarks/**/*.txt`

На этапе build:

- все `.txt` файлы рекурсивно собираются;
- слова нормализуются;
- дубль-слова детектятся и логируются как warning;
- итоговые массивы слов вшиваются в generated Rust file.

## 5. Нормализация словаря

Слово словаря считается валидным, если:

- не пустое;
- длина не больше `20`;
- содержит только ASCII-буквы и цифры;
- после `trim()` и `lowercase()` остаётся валидным.

Символ `_` в словарных словах не допускается.

## 6. Единственная инструкция

Инструкция:

- `classify_login(login: String)`

Бинарный ABI инструкции:

```text
- tag: u8 = 1
- login_len: u32 LE
- login_bytes[login_len]: UTF-8
```

Аккаунты:

- аккаунты не требуются

Подпись транзакции для самой классификации не имеет бизнес-смысла. Если вызов идёт через CPI из `shine_users`, программа `shine_login_guard` не требует signer-аккаунтов вообще.

## 7. Выходные классы

Программа возвращает `u32` через `set_return_data`:

- `0` — `CLASS_FREE`
- `1` — `CLASS_PREMIUM`
- `2` — `CLASS_TRADEMARK`

## 8. Правила нормализации логина

Перед классификацией логин нормализуется:

- пустой логин запрещён;
- длина исходного логина не больше `20`;
- символ `_` удаляется;
- остальные символы обязаны быть ASCII alnum;
- буквы приводятся к нижнему регистру.

Если нормализация не удалась:

- логин считается `CLASS_PREMIUM`.

Это намеренно жёсткое поведение: сомнительный логин не должен автоматически попадать в free-класс.

## 9. Правила классификации

Алгоритм:

1. Нормализовать логин.
2. Попробовать разрезать его на `1..3` слов из словарей.
3. Если среди найденных частей есть trademark-слово, вернуть `CLASS_TRADEMARK`.
4. Если есть только premium-слова, вернуть `CLASS_PREMIUM`.
5. Если словарное совпадение не найдено и длина нормализованного логина `<= 7`, вернуть `CLASS_PREMIUM`.
6. Иначе вернуть `CLASS_FREE`.

## 10. Разбиение на слова

Максимум слов в логине:

- `3`

Алгоритм разбиения:

- DFS/backtracking по префиксам строки;
- на каждом шаге берётся префикс длиной `1..20`;
- если префикс найден в premium или trademark словаре, поиск продолжается на остатке строки;
- если найден хотя бы один путь с trademark-словом, итог = `CLASS_TRADEMARK`;
- если найден путь только с premium-словами, итог = `CLASS_PREMIUM`.

## 11. Инварианты

Должно быть сохранено при переписи:

- тот же способ нормализации логина;
- та же семантика классов `0/1/2`;
- тот же лимит `MAX_WORDS_PER_LOGIN = 3`;
- тот же fallback `len <= 7 => premium`, если словарь ничего не нашёл;
- тот же порядок приоритета `TRADEMARK > PREMIUM > FREE`.

## 12. Что не обязано сохраниться при переписи

Можно менять без изменения бизнес-логики:

- build pipeline словарей;
- структуру хранения слов внутри бинарника;
- способ поиска слов, если сохраняется тот же результат;
- техническую форму возврата, если внешний контракт для `shine_users` останется совместимым.

Но если меняется именно интерфейс `return_data` или классы, это уже требует обновления `shine_users` и документации.
