From 81bef1e1cc221f283939b4104f92a3b6339ab2c3099cab82be41b782a8e09642 Mon Sep 17 00:00:00 2001 From: AidarKC Date: Sun, 5 Jul 2026 18:18:03 +0400 Subject: [PATCH] =?UTF-8?q?=D0=A1=D0=B8=D0=BD=D1=85=D1=80=D0=BE=D0=BD?= =?UTF-8?q?=D0=B8=D0=B7=D0=B8=D1=80=D0=BE=D0=B2=D0=B0=D1=82=D1=8C=20=D0=B8?= =?UTF-8?q?=D0=B7=D0=BC=D0=B5=D0=BD=D0=B5=D0=BD=D0=B8=D1=8F=20=D0=BF=D1=80?= =?UTF-8?q?=D0=BE=D0=B5=D0=BA=D1=82=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- AGENTS.md | 16 +- ...7-01_1945_mainnet-solana-addresses-sync.md | 8 +- ...1_2135_временный_режим_коротких_логинов.md | 42 ++++ Dev_Docs/Solana_Architecture/README.md | 8 +- .../shine_homeserver_main.ino | 15 +- .../shine_homeserver_ui_nav_minimal_spec.md | 2 + SHiNE-agent-bot-coder/AGENT.md | 4 +- SHiNE-agent-bot-coder/AGENTS.md | 6 +- .../js/lib/shine-server-resolver.js | 2 +- ...-06-26_1810_подключение_устройств_по_qr.md | 7 +- ...026-06-26_1815_esp32_файловое_хранилище.md | 7 +- TODO/README.md | 1 + ...6-05-24_1140_репосты_в_каналах_и_тредах.md | 2 +- .../2026-05-25_1106_telegram_agent_players.md | 94 --------- ...ановить_полную_логику_shine_login_guard.md | 182 ++++++++++++++++++ VERSION.properties | 4 +- predeploy/servers.md | 24 --- shine-UI/js/pages/register-view.js | 113 +++++++++-- .../js/pages/registration-payment-view.js | 2 +- shine-UI/js/solana-programs.js | 3 +- shine-UI/js/state.js | 21 +- shine-UI/server-ui/create-server-pda.html | 4 +- shine-UI/server-ui/read-pda.html | 2 +- shine-UI/server-ui/update-server-pda.html | 2 +- .../shine/doc/programs/shine_login_guard.md | 116 +++++------ .../programs/shine_login_guard/src/lib.rs | 65 +------ .../shine_payments/web/admin_tools.html | 6 +- .../shine_payments/web/buy_ticket.html | 6 +- .../shine_payments/web/dao_revoke_vote.html | 9 +- .../shine_payments/web/dao_tools.html | 6 +- .../programs/shine_payments/web/index.html | 4 +- .../shine_payments/web/logic_overview.html | 4 +- .../shine_payments/web/manager_tools.html | 107 +++++++++- .../shine_payments/web/roadmap_dao.html | 4 +- .../shine_payments/web/test_plan.html | 4 +- .../shine_payments/web/track_ticket.html | 6 +- 36 files changed, 573 insertions(+), 335 deletions(-) create mode 100644 Dev_Docs/Pending_Features/2026-07-01_2135_временный_режим_коротких_логинов.md delete mode 100644 TODO/near/2026-05-25_1106_telegram_agent_players.md create mode 100644 TODO/near/2026-07-01_2205_восстановить_полную_логику_shine_login_guard.md delete mode 100644 predeploy/servers.md diff --git a/AGENTS.md b/AGENTS.md index 541497d..8fc9515 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -135,17 +135,19 @@ - После подтверждения, что фича проверена и работает корректно, соответствующий файл удалять. - В `Dev_Docs/Pending_Features/README.md` вести краткий регламент и поддерживать актуальность. -## Будущие фичи -- Папка для задач, сознательно отложенных на будущее: `Dev_Docs/Future_Features/`. -- Точка входа по планам: `Dev_Docs/Future_Features/README.md`. -- Внутри планы разделены по горизонтам: `near/`, `medium/`, `far/`. -- Если пользователь спрашивает, какие есть планы или что можно продолжить, сначала читать `Dev_Docs/Future_Features/README.md`, затем при необходимости конкретные файлы из горизонтов. +## Будущие фичи / TODO +- Папка для задач, сознательно отложенных на будущее: `TODO/`. +- Точка входа по планам: `TODO/README.md`. +- Внутри планы разделены по горизонтам: `near/`, `medium/`, `far/` и тематическим подпапкам. +- Если пользователь спрашивает, какие есть планы или что можно продолжить, сначала читать `TODO/README.md`, затем при необходимости конкретные файлы из подпапок. - Файлы из этой папки не считать активными задачами и не начинать реализацию без явной просьбы пользователя. -- Если часть кода временно отключена или закомментирована, в файле будущей фичи подробно описывать: +- Старую папку `Dev_Docs/Future_Features/` считать выведенной из использования и больше не использовать для новых записей. +- Если часть кода временно отключена или закомментирована, либо удалена как временная заглушка, в TODO-файле подробно описывать: - какие файлы и участки отключены; - что осталось в коде как заготовка; - какие документы нужно обновить при возврате; - - с какого сценария продолжать разработку. + - с какого сценария продолжать разработку; + - из какого коммита брать последнюю полную реализацию. ## Коммуникация по новым задачам (обязательно) - При получении нового задания сначала кратко пересказать задачу своими словами. diff --git a/Dev_Docs/Pending_Features/2026-07-01_1945_mainnet-solana-addresses-sync.md b/Dev_Docs/Pending_Features/2026-07-01_1945_mainnet-solana-addresses-sync.md index 9cbaf1e..d309652 100644 --- a/Dev_Docs/Pending_Features/2026-07-01_1945_mainnet-solana-addresses-sync.md +++ b/Dev_Docs/Pending_Features/2026-07-01_1945_mainnet-solana-addresses-sync.md @@ -37,9 +37,15 @@ - отображаются новые program id; - дефолтный Solana RPC теперь mainnet. +6. Временный режим регистрации: + - в основном UI логины длиной 5-7 символов без временного кода не проходят; + - в основном UI логины длиной 5-7 символов с временным кодом проходят локальную проверку; + - в основном UI любой другой временный код отклоняется сразу; + - ESP32 и прямой on-chain вызов `shine_users` полагаются на текущий временный `shine_login_guard`, то есть валидные логины длиной от 5 символов допускаются без словарной классификации. + ## Ожидаемый результат - Во всех перечисленных поверхностях используются новые mainnet program id. - Запросы по умолчанию идут в mainnet, кроме явно devnet-утилит. - Нет обращений к старым devnet program id. - +- Временный UI-барьер для коротких логинов работает так, как задумано, и не ломает обычную регистрацию для логинов от 8 символов. diff --git a/Dev_Docs/Pending_Features/2026-07-01_2135_временный_режим_коротких_логинов.md b/Dev_Docs/Pending_Features/2026-07-01_2135_временный_режим_коротких_логинов.md new file mode 100644 index 0000000..71e5a52 --- /dev/null +++ b/Dev_Docs/Pending_Features/2026-07-01_2135_временный_режим_коротких_логинов.md @@ -0,0 +1,42 @@ +# Временный режим коротких логинов + +- статус: `pending` + +## Кратко + +Временно упрощён `shine_login_guard`: + +- on-chain допускаются любые валидные логины длиной от `5` символов; +- словарная premium/trademark-классификация временно отключена. + +Поверх этого основной UI вводит временное ограничение: + +- логины длиной `8+` проходят обычную проверку; +- логины длиной `5..7` допускаются только при вводе временного кода; +- любой другой код отклоняется сразу. + +ESP32 в этом временном режиме не ограничивается UI-кодом и использует только on-chain правила. + +## Что проверять + +1. Основной UI: + - логин длиной `4` символа отклоняется; + - логин длиной `5..7` без временного кода отклоняется; + - логин длиной `5..7` с корректным временным кодом проходит локальную проверку; + - логин длиной `5..7` с любым другим кодом отклоняется; + - логин длиной `8+` проходит без временного кода. + +2. Solana on-chain: + - `shine_login_guard` возвращает `CLASS_FREE` для валидных логинов длиной от `5`; + - `shine_login_guard` возвращает `CLASS_PREMIUM` для логинов короче `5` или с невалидными символами. + +3. ESP32 / прямой вызов: + - валидный логин длиной `5+` регистрируется без временного кода; + - логин короче `5` не регистрируется. + +## Ожидаемый результат + +- Основной UI удерживает обычных пользователей от коротких логинов без временного кода. +- Свои пользователи могут временно регистрировать короткие логины через UI-код. +- ESP32 продолжает работать без отдельной промо-логики. +- On-chain логика остаётся простой и компактной. diff --git a/Dev_Docs/Solana_Architecture/README.md b/Dev_Docs/Solana_Architecture/README.md index 31a1f6f..ebacfe6 100644 --- a/Dev_Docs/Solana_Architecture/README.md +++ b/Dev_Docs/Solana_Architecture/README.md @@ -16,7 +16,7 @@ В Solana-модуле сейчас три основные программы: -1. `shine_login_guard` — проверяет логин и возвращает класс логина: обычный, premium или trademark. +1. `shine_login_guard` — в текущем временном режиме проверяет базовую валидность логина и пропускает on-chain любые валидные логины длиной от 5 символов. 2. `shine_users` — создает и обновляет пользовательскую PDA-запись, проверяет подписи и берет оплату за регистрацию/увеличение лимита. 3. `shine_payments` — принимает входящий поток средств в `inflow_vault`, ведет очереди тикетов, позволяет DAO выдавать лимиты менеджерам и выполняет выплаты. @@ -35,7 +35,7 @@ DAO в текущем виде не является отдельной Anchor- | Блок | Папка/имя | Текущие функции из кода | Основной смысл | | --- | --- | --- | --- | -| 1 | `shine_login_guard` | `classify_login` | Проверка логина перед регистрацией. | +| 1 | `shine_login_guard` | `classify_login` | Временная on-chain проверка логина перед регистрацией. | | 2 | `shine_users` | `init_users_economy_config`, `update_users_economy_config`, `create_user_pda`, `update_user_pda` | Регистрация пользователя, обновление записи, экономика лимита. | | 3 | `shine_payments` | `init`, `update_coef_limit`, `grant_manager_limits`, `buy_ticket`, `buy_ticket_usd`, `buy_ticket_sol`, `manager_add_ticket`, `step_payout`, `change_ticket_recipient` | Vault, билеты, очереди, выплаты, DAO-настройки, лимиты менеджеров. | | DAO | governance/authority | Вызовы через governance и управляющие ключи | Управление правами, казной, настройками и будущими обновлениями программ. | @@ -62,7 +62,7 @@ DAO в текущем виде не является отдельной Anchor- Для удобного понимания на старте можно считать, что есть четыре группы ключей: 1. `key_1` / authority программы `shine_login_guard`. - - Сейчас программа только классифицирует логин. + - Сейчас программа только выполняет временную базовую проверку логина. - На первом этапе ее можно оставить под отдельным ключом. - В будущем право обновления можно передать DAO. @@ -140,7 +140,7 @@ Solana-модуль лежит в основном репозитории как 1. `shine_login_guard` - Пока оставить на отдельном ключе `key_1`. - - Передачу DAO сделать позже, когда логика premium/trademark стабилизируется. + - Передачу DAO сделать позже, когда временный режим будет заменён на постоянную логику промокодов и/или premium/trademark. 2. `shine_users` - Economy-настройки уже должны обновляться DAO-authority. diff --git a/ESP32/esp32/ESP32-S3-Touch-AMOLED-2.16/main-device/shine_homeserver_main/shine_homeserver_main.ino b/ESP32/esp32/ESP32-S3-Touch-AMOLED-2.16/main-device/shine_homeserver_main/shine_homeserver_main.ino index 7d2616e..09a3d43 100644 --- a/ESP32/esp32/ESP32-S3-Touch-AMOLED-2.16/main-device/shine_homeserver_main/shine_homeserver_main.ino +++ b/ESP32/esp32/ESP32-S3-Touch-AMOLED-2.16/main-device/shine_homeserver_main/shine_homeserver_main.ino @@ -6083,6 +6083,13 @@ static void editorKeyCb(lv_event_t *event) { rebuildScreen(); return; } + if (strcmp(token, "") == 0) { + keepCursorAtEnd(); + lv_textarea_add_text(gInputTextArea, " "); + syncEditValueFromTextarea(); + keepCursorAtEnd(); + return; + } String out = token; if (gKeyboardMode == KEYBOARD_MODE_ALPHA && gKeyboardShift) { @@ -7253,10 +7260,10 @@ static void drawKeyRow(const char *const *tokens, lv_coord_t h, lv_coord_t gap, uint32_t bgColor, - bool uppercase = false, + bool uppercase = false, const lv_font_t *font = &lv_font_montserrat_18) { for (int i = 0; i < count; ++i) { - String label = tokens[i]; + String label = strcmp(tokens[i], "") == 0 ? String("SPACE") : String(tokens[i]); if (uppercase) { label.toUpperCase(); } @@ -7276,8 +7283,8 @@ static void drawTextEditorKeyboard(lv_coord_t originY) { static const char *symLeftRow1[] = {"://", "/", ":", "_", "@"}; static const char *symLeftRow2[] = {".com", ".net", ".org", ".me", ".io"}; static const char *symRightRow0[] = {"6", "7", "8", "9", "0"}; - static const char *symRightRow1[] = {"?", "&", "=", "+", "-"}; - static const char *symRightRow2[] = {"%", "#", "~", ":", "/"}; + static const char *symRightRow1[] = {"?", "!", "=", "+", "-"}; + static const char *symRightRow2[] = {"", "#", "~", ":", "/"}; const lv_coord_t keyH = 62; const lv_coord_t keyW5 = 84; diff --git a/ESP32/esp32/ESP32-S3-Touch-AMOLED-2.16/reference/shine_homeserver_ui_nav_minimal_spec.md b/ESP32/esp32/ESP32-S3-Touch-AMOLED-2.16/reference/shine_homeserver_ui_nav_minimal_spec.md index b4a48bb..8ee0811 100644 --- a/ESP32/esp32/ESP32-S3-Touch-AMOLED-2.16/reference/shine_homeserver_ui_nav_minimal_spec.md +++ b/ESP32/esp32/ESP32-S3-Touch-AMOLED-2.16/reference/shine_homeserver_ui_nav_minimal_spec.md @@ -259,6 +259,7 @@ - переключение страниц выполняется свайпом влево/вправо внутри `TEXT_EDIT_SCREEN`; - страница 1 содержит в основном буквы и базовые URL-символы; - страница 2 содержит цифры и дополнительные URL/символьные кнопки; +- на символьных страницах должны быть доступны как минимум `!` и отдельная кнопка `SPACE`; - переключение `ABC/123` и `SHIFT` не должно очищать уже введённый текст; - переключение страниц клавиатуры свайпом тоже не должно очищать уже введённый текст, включая цифры, символы и уже набранные заглавные буквы; - есть специальные действия `DEL` и `CLR`. @@ -308,6 +309,7 @@ - половина букв находится на левой странице, половина на правой; - на правой странице кнопки тоже стоят в ровных колонках, без сдвига рядов вправо; - отдельный режим `symbols` тоже разделён на 2 страницы; +- в режиме `symbols` на правой странице есть отдельная кнопка `SPACE` и отдельная кнопка `!`; - 3 основных ряда клавиш и нижний служебный ряд увеличены по высоте; - клавиши дополнительно увеличены по высоте по сравнению с предыдущим промежуточным вариантом; - четвёртый ряд содержит: diff --git a/SHiNE-agent-bot-coder/AGENT.md b/SHiNE-agent-bot-coder/AGENT.md index f9c2f20..a639404 100644 --- a/SHiNE-agent-bot-coder/AGENT.md +++ b/SHiNE-agent-bot-coder/AGENT.md @@ -48,13 +48,13 @@ - Если пользователю нужны точные команды, хэши или подробности, они должны оставаться в текстовом ответе. ## Планы и отложенные фичи -- Планы проекта по отложенным фичам хранятся в `Dev_Docs/Future_Features/`. +- Планы проекта по отложенным фичам хранятся в `TODO/`. - Внутри есть три горизонта: - `near/` - ближайшие планы, обычно сегодня/завтра; - `medium/` - среднесрочные планы, обычно недели или 1-2 месяца; - `far/` - дальнее будущее без понятного срока. - Если пользователь спрашивает, какие есть планы или что можно продолжить, нужно смотреть эти три папки и отвечать кратким списком по горизонтам. -- Файлы из `Dev_Docs/Future_Features/` не начинать реализовывать без явной команды пользователя. +- Файлы из `TODO/` не начинать реализовывать без явной команды пользователя. - После реализации фичи, требующей ручной проверки, нужно добавить отдельный файл в `Dev_Docs/Pending_Features/`. ## Центр задач и предложений diff --git a/SHiNE-agent-bot-coder/AGENTS.md b/SHiNE-agent-bot-coder/AGENTS.md index 20aa593..b64d0f0 100644 --- a/SHiNE-agent-bot-coder/AGENTS.md +++ b/SHiNE-agent-bot-coder/AGENTS.md @@ -11,14 +11,14 @@ - При изменении логики сервиса сначала читать `AGENT.md`, затем код `py_bot_service.py`. ## Планы и задачи -- Отложенные задачи проекта лежат в `../Dev_Docs/Future_Features/`. -- Точка входа по планам: `../Dev_Docs/Future_Features/README.md`. +- Отложенные задачи проекта лежат в `../TODO/`. +- Точка входа по планам: `../TODO/README.md`. - Горизонты планов: - `near/` - ближайшие планы; - `medium/` - среднесрочные планы; - `far/` - дальнее будущее. - Если пользователь спрашивает, какие есть планы или что можно продолжить, кратко перечислять задачи по этим горизонтам. -- Не начинать реализацию задач из `Future_Features` без явной команды пользователя. +- Не начинать реализацию задач из `TODO` без явной команды пользователя. ## Проверка после изменений - Если меняется логика Telegram-бота, проверить локальный запуск или self-test, когда это уместно. diff --git a/SHiNE-browser-plugin-wallet/js/lib/shine-server-resolver.js b/SHiNE-browser-plugin-wallet/js/lib/shine-server-resolver.js index 83ceba0..b21aac6 100644 --- a/SHiNE-browser-plugin-wallet/js/lib/shine-server-resolver.js +++ b/SHiNE-browser-plugin-wallet/js/lib/shine-server-resolver.js @@ -1,7 +1,7 @@ import { base64ToBytes } from './crypto-utils.js'; import { PublicKey } from './vendor/solana-publickey-bundle.js'; -const SOLANA_ENDPOINT_DEFAULT = 'https://api.mainnet-beta.solana.com'; +const SOLANA_ENDPOINT_DEFAULT = 'https://solana-rpc.publicnode.com'; const SHINE_USERS_PROGRAM_ID = 'SHiNEPr1APdAgNBteUyBXcNovaHctpSjUu8oH2ZJdN6'; const SHINE_USERS_USER_PDA_SEED_PREFIX = 'user_login='; const DEFAULT_SHINE_SERVER_LOGIN = 'shineupme'; diff --git a/TODO/2026-06-26_1810_подключение_устройств_по_qr.md b/TODO/2026-06-26_1810_подключение_устройств_по_qr.md index cf7f6b5..73b4f6b 100644 --- a/TODO/2026-06-26_1810_подключение_устройств_по_qr.md +++ b/TODO/2026-06-26_1810_подключение_устройств_по_qr.md @@ -19,12 +19,11 @@ QR-подключение других устройств сейчас есть ## Откуда продолжать -- от текущих документов в `Dev_Docs/Future_Features/medium/`; +- от текущих документов в `TODO/medium/`; - отдельно проверить, какие поля уже есть в PDA и UI. ## Какие документы потом обновить - `Dev_Docs/Solana_Architecture/README.md`; -- `Dev_Docs/Future_Features/medium/2026-06-03_подключение_других_устройств_через_qr.md`; -- `Dev_Docs/Future_Features/medium/2026-06-02_сессионные_homeserver_в_pda.md`. - +- `TODO/medium/2026-06-03_подключение_других_устройств_через_qr.md`; +- `TODO/medium/2026-06-02_сессионные_homeserver_в_pda.md`. diff --git a/TODO/2026-06-26_1815_esp32_файловое_хранилище.md b/TODO/2026-06-26_1815_esp32_файловое_хранилище.md index 458aa71..a1aadca 100644 --- a/TODO/2026-06-26_1815_esp32_файловое_хранилище.md +++ b/TODO/2026-06-26_1815_esp32_файловое_хранилище.md @@ -18,12 +18,11 @@ ## Откуда продолжать -- от `Dev_Docs/Future_Features/medium/2026-05-26_0029_esp32s3_file_storage.md`; +- от `TODO/medium/2026-05-26_0029_esp32s3_file_storage.md`; - от документации по ESP32 UI homeserver. ## Какие документы потом обновить -- `Dev_Docs/Future_Features/medium/2026-05-26_0029_esp32s3_file_storage.md`; -- `Dev_Docs/Future_Features/README.md`; +- `TODO/medium/2026-05-26_0029_esp32s3_file_storage.md`; +- `TODO/README.md`; - документацию по ESP32 UI homeserver, если добавятся экраны или статусы. - diff --git a/TODO/README.md b/TODO/README.md index 5635b0f..1156275 100644 --- a/TODO/README.md +++ b/TODO/README.md @@ -12,6 +12,7 @@ - откуда продолжать; - какие документы потом надо обновить. - Это не активная разработка. Тут только план и контекст. +- Старую папку `Dev_Docs/Future_Features/` считать архивной и больше не использовать как источник новых задач. ## Текущие задачи diff --git a/TODO/medium/2026-05-24_1140_репосты_в_каналах_и_тредах.md b/TODO/medium/2026-05-24_1140_репосты_в_каналах_и_тредах.md index 641ff28..d5098db 100644 --- a/TODO/medium/2026-05-24_1140_репосты_в_каналах_и_тредах.md +++ b/TODO/medium/2026-05-24_1140_репосты_в_каналах_и_тредах.md @@ -86,7 +86,7 @@ - `Dev_Docs/Blockchain/CHANGELOG.md`; - `Dev_Docs/API/04_Add_Block_to_Blockchain_API.md`; - документы API чтения каналов/тредов, если изменятся поля ответа. -9. После реализации перенести задачу из `Dev_Docs/Future_Features/` в `Dev_Docs/Pending_Features/` как фичу, требующую ручной проверки. +9. После реализации перенести задачу из `TODO/` в `Dev_Docs/Pending_Features/` как фичу, требующую ручной проверки. ## Минимальный чек-лист ручной проверки в будущем diff --git a/TODO/near/2026-05-25_1106_telegram_agent_players.md b/TODO/near/2026-05-25_1106_telegram_agent_players.md deleted file mode 100644 index 2022dc0..0000000 --- a/TODO/near/2026-05-25_1106_telegram_agent_players.md +++ /dev/null @@ -1,94 +0,0 @@ -# Telegram-агент для разрешённых игроков - -- Горизонт: - `near` -- Ориентир: - сегодня/завтра -- Статус: - `proposal` - -## Кратко - -Нужно расширить `SHiNE-agent-bot-coder`, чтобы агент мог принимать личные сообщения от заранее разрешённых пользователей, вести по каждому отдельную рабочую папку и историю, помогать им с обсуждениями/документами без изменения кода, а краткий результат публиковать в общий канал. - -## Пользовательский сценарий - -1. Разрешённый пользователь пишет агенту в личные сообщения текстом или голосом. -2. Голосовое сообщение распознаётся так же, как сейчас распознаются voice/audio-задачи. -3. Сервис определяет пользователя по разрешённому списку логинов. -4. Для пользователя используется отдельная папка в `Players/`. -5. Codex запускается с системным контекстом: от имени какого человека он работает, где лежит его папка, какие у него локальные инструкции. -6. Агент может читать код и документацию проекта, но писать должен только в папку этого пользователя, если нет отдельного согласования на изменение общего проекта. -7. После ответа пользователю агент отправляет в общий канал короткую сводку двумя сообщениями или двумя блоками: вопрос пользователя и полученный ответ. -8. Команда `/new` или `New` сбрасывает только сессию этого пользователя. - -## Предлагаемая структура - -- `Players/` - - `Ivan/` - - `AGENTS.md` - - `history/` - - `files/` - - `Sergey/` - - `AGENTS.md` - - `history/` - - `files/` - - `Milana/` - - `AGENTS.md` - - `history/` - - `files/` - -Имена папок можно уточнить после получения точных Telegram-логинов. - -## Что нужно сделать - -1. Добавить конфигурацию разрешённых Telegram-пользователей. -2. Описать соответствие `telegram username -> имя игрока -> папка`. -3. Создавать или использовать отдельную историю диалога для каждого игрока. -4. Поддержать личные сообщения от разрешённых пользователей. -5. Запретить постановку задач от неизвестных пользователей. -6. Для групп/каналов оставить текущую логику: команды Айдара имеют приоритет. -7. При запуске Codex для игрока добавлять отдельный системный контекст: - - имя пользователя; - - путь к его папке; - - правило записи только в эту папку; - - путь к персональному `AGENTS.md`. -8. После ответа игроку отправлять краткую сводку в общий канал. -9. Поддержать `/new`/`New` как сброс только персональной сессии игрока. -10. Добавить защиту от случайного изменения общего кода в режиме игрока. - -## Вопросы перед реализацией - -1. Точные Telegram-логины Ивана, Сергея и Миланы. -2. Какой общий канал использовать для сводок: текущий `@shine_writing` или отдельный чат. -3. Нужно ли отправлять в общий канал полный текст вопроса/ответа или краткую выжимку. -4. Нужно ли пересылать вложения игроков в общий канал или только текстовые сводки. -5. Разрешить ли игрокам читать все документы проекта, включая технические заметки деплоя. -6. Что делать, если пользователь просит изменить код: отказать, создать предложение в своей папке или просить подтверждение Айдара. -7. Нужны ли русские имена папок (`Иван`, `Сергей`, `Милана`) или ASCII-имена (`Ivan`, `Sergey`, `Milana`). -8. Нужно ли хранить истории игроков в общей папке сервиса или внутри `Players//history/`. - -## Риски и ограничения - -- Нужно аккуратно разделить режим Айдара и режим игрока, чтобы игроки не могли случайно запустить изменение общего кода. -- Нужно не смешать истории разных пользователей. -- Нужно ограничить публикацию в общий канал, чтобы не утекали личные или слишком длинные ответы. -- Нужна проверка Telegram-идентификации: username может меняться, поэтому желательно хранить и `user_id`. - -## Документы, которые обновить при реализации - -- `SHiNE-agent-bot-coder/AGENTS.md` -- `SHiNE-agent-bot-coder/AGENT.md` -- `SHiNE-agent-bot-coder/README.md` -- `Dev_Docs/deploy/agent-bot-coder-local-systemd.md`, если появятся новые переменные окружения или настройки сервиса. - -## Минимальная проверка - -1. Айдар по-прежнему может ставить задачи из `@shine_writing`. -2. Неизвестный пользователь не ставит задачу в очередь. -3. Разрешённый игрок пишет личное текстовое сообщение и получает ответ. -4. Разрешённый игрок отправляет voice, оно распознаётся и обрабатывается. -5. История одного игрока не попадает в историю другого. -6. `/new` сбрасывает только историю текущего игрока. -7. Сводка вопрос/ответ появляется в общем канале. -8. В режиме игрока агент не пишет за пределы `Players//` без отдельного подтверждения. diff --git a/TODO/near/2026-07-01_2205_восстановить_полную_логику_shine_login_guard.md b/TODO/near/2026-07-01_2205_восстановить_полную_логику_shine_login_guard.md new file mode 100644 index 0000000..e1b3eee --- /dev/null +++ b/TODO/near/2026-07-01_2205_восстановить_полную_логику_shine_login_guard.md @@ -0,0 +1,182 @@ +# Восстановить полную логику `shine_login_guard` + +Статус: отложено. + +## Зачем это нужно + +Сейчас в проекте включена временная заглушка для регистрации коротких логинов: + +- on-chain `shine_login_guard` пропускает любые валидные логины длиной от `5` символов; +- словарная `premium/trademark`-классификация временно отключена; +- основной UI удерживает обычных пользователей от логинов `5..7` через временный код; +- это сделано только до внедрения полноценной схемы продажи/выдачи красивых имён и промокодов. + +Когда полноценная логика будет готова, нужно вернуть полный режим `shine_login_guard`. + +## Что именно было в полной версии + +Последняя полная словарная реализация зафиксирована в коммите: + +- `0240db5` — `Подготовка перед деплоем в mainnet` + +Ключевой файл полной реализации: + +- `shine-solana/shine/programs/shine_login_guard/src/lib.rs` + +Как быстро посмотреть старую версию: + +```bash +git show 0240db5:shine-solana/shine/programs/shine_login_guard/src/lib.rs +``` + +## Как работала старая логика + +Полная версия делала следующее: + +1. Нормализовала логин: + - удаляла `_`; + - требовала только ASCII alnum; + - приводила к lower-case; + - запрещала пустой логин и логин длиннее `20`. + +2. Пыталась разбить логин на `1..3` словарных слова: + - premium-слова; + - trademark-слова. + +3. Приоритеты классов были такие: + - если найден хотя бы один trademark-компонент -> `CLASS_TRADEMARK`; + - если найден только premium-набор -> `CLASS_PREMIUM`; + - если словарного матча нет и длина нормализованного логина `<= 7` -> `CLASS_PREMIUM`; + - иначе -> `CLASS_FREE`. + +4. Для разбивки использовался DFS/backtracking по префиксам строки. + +## Какие файлы участвовали в полной логике + +### Обязательно восстановить или проверить + +1. Код программы: + - `shine-solana/shine/programs/shine_login_guard/src/lib.rs` + +2. Генерация словаря: + - `shine-solana/shine/programs/shine_login_guard/build.rs` + +3. Словари: + - `shine-solana/shine/programs/shine_login_guard/src/dictionaries/premium/**/*.txt` + - `shine-solana/shine/programs/shine_login_guard/src/dictionaries/trademarks/**/*.txt` + +4. Документация программы: + - `shine-solana/shine/doc/programs/shine_login_guard.md` + +5. Архитектурная документация: + - `Dev_Docs/Solana_Architecture/README.md` + +6. UI-логика precheck: + - `shine-UI/js/pages/register-view.js` + - `shine-UI/js/pages/registration-payment-view.js` + +## Что именно сейчас временно удалено из `lib.rs` + +Временная упрощённая версия убрала: + +- `mod wordlist { include!(...) }` +- `MAX_WORDS_PER_LOGIN` +- `classify_split(...)` +- `is_premium_word(...)` +- `is_trademark_word(...)` +- словарную DFS-сегментацию +- fallback `len <= 7 => premium` в пользу временного `len < 5 => premium` + +Сейчас `classify(login)` делает только: + +1. нормализацию; +2. проверку минимальной длины `5`; +3. возврат `CLASS_FREE` для всего остального. + +## Как вернуть полную логику + +### Минимальный путь + +1. Взять старую версию `lib.rs` из коммита `0240db5`. +2. Вернуть в файл: + - `mod wordlist` + - `MAX_WORDS_PER_LOGIN` + - `classify_split` + - `is_premium_word` + - `is_trademark_word` +3. Вернуть старые тесты и при необходимости дописать новые. +4. Проверить, что `build.rs` всё ещё генерирует `generated_dictionary.rs` в прежнем формате. +5. Сверить актуальность словарей в `src/dictionaries`. +6. Обновить `shine_login_guard.md` обратно под словарную логику. +7. Обновить `Dev_Docs/Solana_Architecture/README.md`. + +### Проверка после возврата + +Нужно убедиться, что: + +- trademark-логины снова помечаются как `CLASS_TRADEMARK`; +- premium-логины снова помечаются как `CLASS_PREMIUM`; +- короткие логины `<= 7` без словарного совпадения снова считаются premium; +- UI больше не использует временный код для коротких логинов. + +## Что поменять в UI при возврате + +После возврата полной on-chain логики нужно убрать временный клиентский костыль: + +1. Из `shine-UI/js/pages/register-view.js` удалить: + - `TEMP_MIN_LOGIN_WITHOUT_PROMO` + - `TEMP_ABSOLUTE_MIN_LOGIN_LEN` + - `TEMP_PROMO_HASH_HEX` + - локальную проверку временного кода + - временные тексты про код для логинов `5..7` + +2. В `shine-UI/js/pages/registration-payment-view.js` пересмотреть временную строку: + - сейчас `promoCode: ''` передаётся принудительно; + - после внедрения нормальной promo/on-chain схемы это место надо вернуть к реальной логике промокодов. + +## Что учитывать по размеру программы + +Текущая временная версия сильно уменьшила размер `shine_login_guard.so`. + +При возврате полной словарной логики: + +- бинарь снова заметно вырастет; +- потребуется больший `programdata` rent; +- для апгрейда понадобится больший временный buffer. + +Перед апгрейдом стоит отдельно: + +1. собрать `.so`; +2. измерить размер; +3. проверить `solana rent `; +4. убедиться, что на кошельке достаточно SOL для upgrade buffer. + +## Что ещё проверить перед возвратом + +1. Не осталось ли временных пометок в документации и UI. +2. Не завязаны ли новые сценарии на допуск логинов длиной `5..7`. +3. Готова ли полноценная схема: + - продажа красивых имён; + - промокоды; + - менеджеры/селлеры; + - DAO-квоты; + - или другая финальная модель, которая заменит временный режим. + +## Откуда продолжать + +Практически продолжать так: + +```bash +git show 0240db5:shine-solana/shine/programs/shine_login_guard/src/lib.rs +git show 0240db5:shine-solana/shine/programs/shine_login_guard/build.rs +git show 0240db5:shine-solana/shine/doc/programs/shine_login_guard.md +``` + +Потом: + +1. вернуть код; +2. обновить UI; +3. обновить документы; +4. собрать `cargo test`; +5. собрать `cargo build-sbf`; +6. только потом делать апгрейд on-chain программы. diff --git a/VERSION.properties b/VERSION.properties index 823b80c..d21da28 100644 --- a/VERSION.properties +++ b/VERSION.properties @@ -1,2 +1,2 @@ -client.version=1.2.294 -server.version=1.2.274 +client.version=1.2.295 +server.version=1.2.275 diff --git a/predeploy/servers.md b/predeploy/servers.md deleted file mode 100644 index 3e5bbeb..0000000 --- a/predeploy/servers.md +++ /dev/null @@ -1,24 +0,0 @@ -# SHiNE Predeploy Servers - -## Access policy -- VPS-05 (`45.136.124.227`): use `player@45.136.124.227` for regular deployment operations. -- TURN shared secret (both TURN nodes): `def6d444734d380d2f67a9d345b1debf985eaba0973c343e392c060d97c30106` - -## Servers -1. `VPS-02` (legacy): `root@194.87.0.247` - - Legacy production host. - - caddy installed. - - coturn installed. -2. `VPS-05` (target): `root@45.136.124.227` - - New migration target. - - User `player` created, in `sudo`. - - SHiNE base dir: `/home/player/SHiNE`. - - TURN config path: `/etc/turnserver.conf` (coturn), work docs/scripts in `/home/player/SHiNE/coturn`. -3. `promo-node-93` (TURN node): `ubuntu@93.170.12.154` - - TURN installed: `coturn` active. - - TURN ports: `3478/tcp`, `3478/udp`, `5349/tcp`, `5349/udp`. - - TURN relay ports: `49152-50152/udp`. - - TURN config path: `/etc/turnserver.conf`. - - SHiNE dir for player: `/home/player/SHiNE/coturn`. - - Access user `player` created with sudo and SSH key copied from `ubuntu`. - - Legacy reverse SSH tunnel to `194.87.0.247:1200` was disabled (`reverse-ssh.service`). diff --git a/shine-UI/js/pages/register-view.js b/shine-UI/js/pages/register-view.js index 2132d80..00bfcf0 100644 --- a/shine-UI/js/pages/register-view.js +++ b/shine-UI/js/pages/register-view.js @@ -13,10 +13,39 @@ import { PASSWORD_MAX_LENGTH, PASSWORD_WORDS_COUNT, } from '../services/password-words.js'; +import { sha256Text } from '../services/crypto-utils.js'; import { openRegistrationFaq } from './registration-faq-view.js'; import { defaultServerHttp } from '../deploy-config.js'; export const pageMeta = { id: 'register-view', title: 'Зарегистрироваться', showAppChrome: false }; +// ВРЕМЕННАЯ UI-ЗАГЛУШКА: +// пока полноценная логика продажи/выдачи коротких имён не внедрена on-chain, +// UI пускает логины 5..7 символов только по временному коду. +const TEMP_MIN_LOGIN_WITHOUT_PROMO = 8; +const TEMP_ABSOLUTE_MIN_LOGIN_LEN = 5; +const TEMP_PROMO_HASH_HEX = 'a665a45920422f9d417e4867efdc4fb8a04a1f3fff1fa07e998e86f7f7a27ae3'; + +function bytesToHex(bytes) { + return Array.from(bytes || [], (b) => b.toString(16).padStart(2, '0')).join(''); +} + +async function isTemporaryPromoAccepted(value) { + const digest = await sha256Text(String(value || '')); + return bytesToHex(digest) === TEMP_PROMO_HASH_HEX; +} + +function normalizeLoginForTemporaryUiGuard(login) { + const source = String(login || '').trim(); + if (!source || source.length > 20) return null; + let normalized = ''; + for (const ch of source) { + if (ch === '_') continue; + if (!/[0-9A-Za-z]/.test(ch)) return null; + normalized += ch.toLowerCase(); + } + if (!normalized || normalized.length > 20) return null; + return normalized; +} function createWordsLayout({ words, onInput }) { const section = document.createElement('div'); @@ -148,7 +177,7 @@ export function render({ navigate }) { const promoHint = document.createElement('p'); promoHint.className = 'meta-muted'; - promoHint.textContent = 'Если промокод включён, обычная проверка premium/trademark логина пропускается.'; + promoHint.textContent = 'Временный режим: логины длиной 5-7 символов доступны только по специальному коду. Любое другое значение считается неверным промокодом.'; promoField.append(promoFieldLabel, promoInput, promoHint); @@ -247,26 +276,58 @@ export function render({ navigate }) { async function runAvailabilityCheck() { const login = loginInput.value.trim(); + const promoCode = String(promoInput.value || '').trim(); if (!login) { statusText.textContent = 'Введите логин'; formError.style.display = 'none'; return false; } - if (usePromoCode) { - lastCheckedLogin = login; - lastCheckedFree = true; - lastCheckedClassName = 'promo'; - statusText.textContent = 'Промокод включён: обычная проверка premium/trademark логина пропущена'; - statusText.className = 'status-line is-available'; + const normalizedLogin = normalizeLoginForTemporaryUiGuard(login); + if (!normalizedLogin) { + statusText.textContent = 'Логин содержит недопустимые символы или имеет неверную длину ❌'; + statusText.className = 'status-line is-unavailable'; formError.style.display = 'none'; - return true; + return false; + } + + if (normalizedLogin.length < TEMP_ABSOLUTE_MIN_LOGIN_LEN) { + statusText.textContent = `Логин должен быть не короче ${TEMP_ABSOLUTE_MIN_LOGIN_LEN} символов ❌`; + statusText.className = 'status-line is-unavailable'; + formError.style.display = 'none'; + return false; + } + + const wantsShortLogin = normalizedLogin.length < TEMP_MIN_LOGIN_WITHOUT_PROMO; + let promoAccepted = false; + if (usePromoCode) { + if (!promoCode) { + statusText.textContent = 'Введите промокод ❌'; + statusText.className = 'status-line is-unavailable'; + formError.style.display = 'none'; + return false; + } + promoAccepted = await isTemporaryPromoAccepted(promoCode); + if (!promoAccepted) { + statusText.textContent = 'Неверный промокод ❌'; + statusText.className = 'status-line is-unavailable'; + formError.style.display = 'none'; + return false; + } + } else if (wantsShortLogin) { + statusText.textContent = `Логины короче ${TEMP_MIN_LOGIN_WITHOUT_PROMO} символов временно доступны только по специальному коду ❌`; + statusText.className = 'status-line is-unavailable'; + formError.style.display = 'none'; + return false; } if (login === lastCheckedLogin) { if (!lastCheckedFree) { statusText.textContent = 'Логин уже занят ❌'; statusText.className = 'status-line is-unavailable'; + } else if (lastCheckedClassName === 'promo') { + statusText.textContent = 'Логин свободен ✅ Временный код принят'; + statusText.className = 'status-line is-available'; } else if (lastCheckedClassName === 'free') { statusText.textContent = 'Логин свободен ✅'; statusText.className = 'status-line is-available'; @@ -281,7 +342,7 @@ export function render({ navigate }) { statusText.className = 'status-line is-unavailable'; } formError.style.display = 'none'; - return lastCheckedFree && lastCheckedClassName === 'free'; + return lastCheckedFree && (lastCheckedClassName === 'free' || lastCheckedClassName === 'promo'); } checkButton.disabled = true; @@ -295,15 +356,19 @@ export function render({ navigate }) { let className = ''; let precheckWarning = ''; if (isFree) { - try { - const precheck = await precheckLoginClassOnSolana({ - login, - solanaEndpoint: state.entrySettings.solanaServer, - }); - className = precheck.className; - } catch (precheckError) { - className = 'free'; - precheckWarning = formatSolanaErrorDetails(precheckError); + if (promoAccepted) { + className = 'promo'; + } else { + try { + const precheck = await precheckLoginClassOnSolana({ + login, + solanaEndpoint: state.entrySettings.solanaServer, + }); + className = precheck.className; + } catch (precheckError) { + className = 'free'; + precheckWarning = formatSolanaErrorDetails(precheckError); + } } } lastCheckedLogin = login; @@ -312,6 +377,9 @@ export function render({ navigate }) { if (!isFree) { statusText.textContent = 'Логин уже занят ❌'; statusText.className = 'status-line is-unavailable'; + } else if (className === 'promo') { + statusText.textContent = 'Логин свободен ✅ Временный код принят'; + statusText.className = 'status-line is-available'; } else if (className === 'free') { statusText.textContent = precheckWarning ? `Логин свободен ✅ (предпроверка Solana недоступна: ${precheckWarning})` @@ -328,7 +396,7 @@ export function render({ navigate }) { statusText.className = 'status-line is-unavailable'; } formError.style.display = 'none'; - return isFree && className === 'free'; + return isFree && (className === 'free' || className === 'promo'); } catch (error) { const base = toUserMessage(error, 'Не удалось проверить логин'); const details = formatSolanaErrorDetails(error); @@ -379,7 +447,7 @@ export function render({ navigate }) { updatePromoVisibility(); syncDraftState(); if (usePromoCode) { - statusText.textContent = 'Промокод включён: проверка логина будет пропущена'; + statusText.textContent = 'Временный код включён: для логинов 5-7 символов будет локальная проверка'; statusText.className = 'status-line is-available'; } else { statusText.textContent = 'Проверка логина: не выполнена'; @@ -388,6 +456,9 @@ export function render({ navigate }) { }); promoInput.addEventListener('input', () => { + lastCheckedLogin = ''; + lastCheckedFree = false; + lastCheckedClassName = ''; syncDraftState(); }); @@ -395,7 +466,7 @@ export function render({ navigate }) { formError.style.display = 'none'; const promoCode = String(promoInput.value || '').trim(); if (usePromoCode && !promoCode) { - formError.textContent = 'Если включён промокод, поле промокода должно быть заполнено.'; + formError.textContent = 'Если включён временный код, поле должно быть заполнено.'; formError.style.display = ''; return; } diff --git a/shine-UI/js/pages/registration-payment-view.js b/shine-UI/js/pages/registration-payment-view.js index c896236..a5731c7 100644 --- a/shine-UI/js/pages/registration-payment-view.js +++ b/shine-UI/js/pages/registration-payment-view.js @@ -266,7 +266,7 @@ export function render({ navigate }) { keyBundle, solanaEndpoint: state.entrySettings.solanaServer, accessServers: [state.entrySettings.shineServerLogin || defaultServerLogin], - promoCode: state.registrationDraft.usePromoCode ? state.registrationDraft.promoCode : '', + promoCode: '', }); } catch (solanaError) { const solanaMsg = formatSolanaErrorDetails(solanaError); diff --git a/shine-UI/js/solana-programs.js b/shine-UI/js/solana-programs.js index e9293ef..f085083 100644 --- a/shine-UI/js/solana-programs.js +++ b/shine-UI/js/solana-programs.js @@ -1,5 +1,6 @@ export const SOLANA_CLUSTER = 'mainnet-beta'; -export const SOLANA_ENDPOINT_DEFAULT = 'https://api.mainnet-beta.solana.com'; +// Для браузерного UI используем endpoint, который не режет JSON-RPC по Origin. +export const SOLANA_ENDPOINT_DEFAULT = 'https://solana-rpc.publicnode.com'; // Единый файл актуальных Solana Program ID для UI-части SHiNE. // При смене адресов обновлять этот файл и все его потребители внутри shine-UI. diff --git a/shine-UI/js/state.js b/shine-UI/js/state.js index f644e50..b082b11 100644 --- a/shine-UI/js/state.js +++ b/shine-UI/js/state.js @@ -92,6 +92,21 @@ const DEFAULT_ARWEAVE_SERVER = 'https://arweave.net'; const DEFAULT_CALL_PREFLIGHT_TIMEOUT_MS = 6000; const DEFAULT_OPENAI_BASE_URL = 'https://api.openai.com/v1'; +function normalizeStoredSolanaServer(value) { + const raw = String(value || '').trim(); + if (!raw) return DEFAULT_SOLANA_SERVER; + const normalized = raw.replace(/\/+$/u, '').toLowerCase(); + if ( + normalized === 'https://api.devnet.solana.com' + || normalized === 'http://api.devnet.solana.com' + || normalized === 'https://api.mainnet-beta.solana.com' + || normalized === 'http://api.mainnet-beta.solana.com' + ) { + return DEFAULT_SOLANA_SERVER; + } + return raw; +} + export function normalizeDmChatId(value) { return String(value || '').trim().toLowerCase(); } @@ -182,7 +197,7 @@ function persistEntrySettings(settings) { try { const payload = { language: String(settings?.language || 'ru'), - solanaServer: String(settings?.solanaServer || DEFAULT_SOLANA_SERVER), + solanaServer: normalizeStoredSolanaServer(settings?.solanaServer), shineServer: String(settings?.shineServer || DEFAULT_SHINE_SERVER), shineServerLogin: String(settings?.shineServerLogin || DEFAULT_SHINE_SERVER_LOGIN_VALUE), shineServerHttp: String(settings?.shineServerHttp || DEFAULT_SHINE_SERVER_HTTP_VALUE), @@ -249,7 +264,7 @@ function createInitialState({ withStoredSession = true } = {}) { startHint: '', entrySettings: { language: String(storedEntrySettings?.language || 'ru'), - solanaServer: String(storedEntrySettings?.solanaServer || DEFAULT_SOLANA_SERVER), + solanaServer: normalizeStoredSolanaServer(storedEntrySettings?.solanaServer), shineServer: String(LOCAL_WS_OVERRIDE_URL || storedEntrySettings?.shineServer || initialShineServer), shineServerLogin: String(storedEntrySettings?.shineServerLogin || DEFAULT_SHINE_SERVER_LOGIN_VALUE), shineServerHttp: String(storedEntrySettings?.shineServerHttp || DEFAULT_SHINE_SERVER_HTTP_VALUE), @@ -718,7 +733,7 @@ export function checkServerAvailability(address) { } export async function saveEntrySettings(nextSettings) { - const nextSolanaServer = String(nextSettings?.solanaServer || state.entrySettings.solanaServer || DEFAULT_SOLANA_SERVER); + const nextSolanaServer = normalizeStoredSolanaServer(nextSettings?.solanaServer || state.entrySettings.solanaServer || DEFAULT_SOLANA_SERVER); const nextShineServerLogin = String(nextSettings?.shineServerLogin || state.entrySettings.shineServerLogin || DEFAULT_SHINE_SERVER_LOGIN_VALUE).trim().toLowerCase() || DEFAULT_SHINE_SERVER_LOGIN_VALUE; let forcedShineServer = LOCAL_WS_OVERRIDE_URL || ''; diff --git a/shine-UI/server-ui/create-server-pda.html b/shine-UI/server-ui/create-server-pda.html index 9e725f1..e58cbb5 100644 --- a/shine-UI/server-ui/create-server-pda.html +++ b/shine-UI/server-ui/create-server-pda.html @@ -48,8 +48,8 @@

Параметры Solana

- -
mainnet: https://api.mainnet-beta.solana.com · devnet: https://api.devnet.solana.com
+ +
mainnet: https://solana-rpc.publicnode.com · devnet: https://api.devnet.solana.com
diff --git a/shine-UI/server-ui/read-pda.html b/shine-UI/server-ui/read-pda.html index 378429d..b92701d 100644 --- a/shine-UI/server-ui/read-pda.html +++ b/shine-UI/server-ui/read-pda.html @@ -52,7 +52,7 @@

Параметры Solana

- +
diff --git a/shine-UI/server-ui/update-server-pda.html b/shine-UI/server-ui/update-server-pda.html index 26eb38f..123fb40 100644 --- a/shine-UI/server-ui/update-server-pda.html +++ b/shine-UI/server-ui/update-server-pda.html @@ -56,7 +56,7 @@

Параметры Solana

- +
diff --git a/shine-solana/shine/doc/programs/shine_login_guard.md b/shine-solana/shine/doc/programs/shine_login_guard.md index 3e9e75e..0ab3a6d 100644 --- a/shine-solana/shine/doc/programs/shine_login_guard.md +++ b/shine-solana/shine/doc/programs/shine_login_guard.md @@ -1,6 +1,6 @@ # Программа `shine_login_guard` -Документ описывает целевое поведение программы классификации логинов SHiNE. +Документ описывает текущее временное поведение программы классификации логинов SHiNE. Задача программы: @@ -12,11 +12,12 @@ `shine_login_guard` — маленькая служебная программа, которую вызывает `shine_users` через CPI до регистрации пользователя. -Она отвечает только на вопрос: +В текущем временном режиме она отвечает только на вопрос: -- можно ли зарегистрировать логин автоматически; -- является ли логин premium; -- является ли логин trademark/company. +- является ли логин допустимым для автоматической регистрации; +- нужно ли отклонить слишком короткий или некорректный логин. + +UI может накладывать дополнительные временные ограничения поверх этой логики, но сама программа про них не знает. ## 2. Program ID @@ -28,34 +29,9 @@ У программы нет PDA-состояний и persistent storage. -Она опирается на статически сгенерированные словари, встроенные в бинарник на этапе сборки. +Программа полностью stateless и не опирается на словари при классификации. -## 4. Внешние артефакты - -Словари хранятся в: - -- `src/dictionaries/premium/**/*.txt` -- `src/dictionaries/trademarks/**/*.txt` - -На этапе build: - -- все `.txt` файлы рекурсивно собираются; -- слова нормализуются; -- дубль-слова детектятся и логируются как warning; -- итоговые массивы слов вшиваются в generated Rust file. - -## 5. Нормализация словаря - -Слово словаря считается валидным, если: - -- не пустое; -- длина не больше `20`; -- содержит только ASCII-буквы и цифры; -- после `trim()` и `lowercase()` остаётся валидным. - -Символ `_` в словарных словах не допускается. - -## 6. Единственная инструкция +## 4. Единственная инструкция Инструкция: @@ -73,9 +49,9 @@ - аккаунты не требуются -Подпись транзакции для самой классификации не имеет бизнес-смысла. Если вызов идёт через CPI из `shine_users`, программа `shine_login_guard` не требует signer-аккаунтов вообще. +Если вызов идёт через CPI из `shine_users`, программа `shine_login_guard` не требует signer-аккаунтов. -## 7. Выходные классы +## 5. Выходные классы Программа возвращает `u32` через `set_return_data`: @@ -83,7 +59,14 @@ - `1` — `CLASS_PREMIUM` - `2` — `CLASS_TRADEMARK` -## 8. Правила нормализации логина +Сейчас фактически используются только: + +- `CLASS_FREE` +- `CLASS_PREMIUM` + +`CLASS_TRADEMARK` сохранён для совместимости ABI, но временная логика его не возвращает. + +## 6. Правила нормализации логина Перед классификацией логин нормализуется: @@ -97,50 +80,51 @@ - логин считается `CLASS_PREMIUM`. -Это намеренно жёсткое поведение: сомнительный логин не должен автоматически попадать в free-класс. +Это означает, что `shine_users` не пустит такой логин в обычную регистрацию. -## 9. Правила классификации +## 7. Временные правила классификации -Алгоритм: +Текущий временный алгоритм: 1. Нормализовать логин. -2. Попробовать разрезать его на `1..3` слов из словарей. -3. Если среди найденных частей есть trademark-слово, вернуть `CLASS_TRADEMARK`. -4. Если есть только premium-слова, вернуть `CLASS_PREMIUM`. -5. Если словарное совпадение не найдено и длина нормализованного логина `<= 7`, вернуть `CLASS_PREMIUM`. -6. Иначе вернуть `CLASS_FREE`. +2. Если нормализованный логин короче `5` символов, вернуть `CLASS_PREMIUM`. +3. Иначе вернуть `CLASS_FREE`. -## 10. Разбиение на слова +То есть on-chain программа в этом режиме: -Максимум слов в логине: +- пропускает любые валидные логины длиной от `5` символов; +- не использует premium-словари; +- не использует trademark-словари; +- не выполняет словарную сегментацию логина. -- `3` +## 8. Граница ответственности -Алгоритм разбиения: +Важно разделять уровни: -- DFS/backtracking по префиксам строки; -- на каждом шаге берётся префикс длиной `1..20`; -- если префикс найден в premium или trademark словаре, поиск продолжается на остатке строки; -- если найден хотя бы один путь с trademark-словом, итог = `CLASS_TRADEMARK`; -- если найден путь только с premium-словами, итог = `CLASS_PREMIUM`. +1. `shine_login_guard` + - проверяет только базовую валидность и минимальную длину `5`. -## 11. Инварианты +2. UI + - может вводить дополнительные временные ограничения; + - например, пускать логины длиной `5..7` только по временному коду. -Должно быть сохранено при переписи: +3. `shine_users` + - остаётся финальной on-chain точкой регистрации; + - дополнительно проверяет общий формат логина в собственной логике. -- тот же способ нормализации логина; -- та же семантика классов `0/1/2`; -- тот же лимит `MAX_WORDS_PER_LOGIN = 3`; -- тот же fallback `len <= 7 => premium`, если словарь ничего не нашёл; -- тот же порядок приоритета `TRADEMARK > PREMIUM > FREE`. +## 9. Совместимость -## 12. Что не обязано сохраниться при переписи +Сохранено: -Можно менять без изменения бизнес-логики: +- тот же ABI инструкции; +- тот же формат `return_data`; +- те же значения классов `0/1/2`; +- тот же `Program ID`. -- build pipeline словарей; -- структуру хранения слов внутри бинарника; -- способ поиска слов, если сохраняется тот же результат; -- техническую форму возврата, если внешний контракт для `shine_users` останется совместимым. +Изменено относительно полной словарной версии: -Но если меняется именно интерфейс `return_data` или классы, это уже требует обновления `shine_users` и документации. +- нет словарной классификации; +- нет разделения на premium/trademark по содержимому логина; +- короткий порог временно снижен до `5`. + +При возврате к полной модели нужно будет обновить этот документ снова синхронно с кодом. diff --git a/shine-solana/shine/programs/shine_login_guard/src/lib.rs b/shine-solana/shine/programs/shine_login_guard/src/lib.rs index 8014a2d..11c3b5c 100644 --- a/shine-solana/shine/programs/shine_login_guard/src/lib.rs +++ b/shine-solana/shine/programs/shine_login_guard/src/lib.rs @@ -7,16 +7,15 @@ use solana_program::{ pubkey::Pubkey, }; -mod wordlist { - include!(concat!(env!("OUT_DIR"), "/generated_dictionary.rs")); -} - pub const INSTRUCTION_CLASSIFY_LOGIN: u8 = 1; pub const CLASS_FREE: u32 = 0; pub const CLASS_PREMIUM: u32 = 1; pub const CLASS_TRADEMARK: u32 = 2; -pub const MAX_WORDS_PER_LOGIN: usize = 3; pub const MAX_LOGIN_LEN: usize = 20; +// ВРЕМЕННАЯ ЗАГЛУШКА: +// до внедрения полноценной on-chain логики продажи/выдачи красивых имён +// пропускаем любой валидный логин длиной от 5 символов. +pub const TEMP_MIN_FREE_LOGIN_LEN: usize = 5; solana_program::declare_id!("SHiGxGsXGioQYCYhchQ5R7KWoxN5UjFAFsucPf6sfnh"); @@ -75,13 +74,11 @@ pub fn build_classify_login_instruction_data(login: &str) -> Vec { } pub fn classify(login: &str) -> u32 { + // Временный режим: словарная premium/trademark классификация отключена. let Some(normalized) = normalize_login(login) else { return CLASS_PREMIUM; }; - if let Some(v) = classify_split(&normalized) { - return v; - } - if normalized.len() <= 7 { + if normalized.len() < TEMP_MIN_FREE_LOGIN_LEN { return CLASS_PREMIUM; } CLASS_FREE @@ -107,49 +104,6 @@ fn normalize_login(login: &str) -> Option { Some(out) } -fn classify_split(login: &str) -> Option { - fn dfs(rest: &str, depth: usize, has_tm: bool) -> Option { - if rest.is_empty() { - if depth > 0 && depth <= MAX_WORDS_PER_LOGIN { - return Some(if has_tm { CLASS_TRADEMARK } else { CLASS_PREMIUM }); - } - return None; - } - if depth >= MAX_WORDS_PER_LOGIN { - return None; - } - let max_piece = rest.len().min(MAX_LOGIN_LEN); - let mut premium_found = false; - for i in 1..=max_piece { - let candidate = &rest[..i]; - let is_tm = is_trademark_word(candidate); - let is_pr = is_tm || is_premium_word(candidate); - if !is_pr { - continue; - } - match dfs(&rest[i..], depth + 1, has_tm || is_tm) { - Some(CLASS_TRADEMARK) => return Some(CLASS_TRADEMARK), - Some(CLASS_PREMIUM) => premium_found = true, - _ => {} - } - } - if premium_found { - Some(CLASS_PREMIUM) - } else { - None - } - } - dfs(login, 0, false) -} - -fn is_premium_word(word: &str) -> bool { - wordlist::PREMIUM_WORDS.binary_search(&word).is_ok() -} - -fn is_trademark_word(word: &str) -> bool { - wordlist::TRADEMARK_WORDS.binary_search(&word).is_ok() -} - #[cfg(test)] mod tests { use super::*; @@ -165,12 +119,13 @@ mod tests { } #[test] - fn short_unknown_login_is_premium() { - assert_eq!(classify("abc123"), CLASS_PREMIUM); + fn login_shorter_than_temporary_threshold_is_premium() { + assert_eq!(classify("ab_1"), CLASS_PREMIUM); } #[test] - fn long_unknown_login_is_free() { + fn login_from_temporary_threshold_and_longer_is_free() { + assert_eq!(classify("abcde"), CLASS_FREE); assert_eq!(classify("abcdefgh9"), CLASS_FREE); } diff --git a/shine-solana/shine/programs/shine_payments/web/admin_tools.html b/shine-solana/shine/programs/shine_payments/web/admin_tools.html index 88bff3f..8248dea 100644 --- a/shine-solana/shine/programs/shine_payments/web/admin_tools.html +++ b/shine-solana/shine/programs/shine_payments/web/admin_tools.html @@ -3,7 +3,7 @@ - Тех. инструменты — Shine Payments Devnet + Тех. инструменты — Shine Payments Mainnet
-

Менеджер: создание билетов (Devnet)

+

Менеджер: создание билетов (Mainnet)

Программа:
@@ -78,7 +90,7 @@