SHA256
Compare commits
4
Commits
| Author | SHA256 | Date | |
|---|---|---|---|
|
|
553a1f1e27 | ||
|
|
940dc855cf | ||
|
|
9e6ff07136 | ||
|
|
f095182673 |
-17
@@ -76,21 +76,10 @@ shine-solana/shine/scripts/**/*.env
|
|||||||
shine-solana/shine/scripts/**/TEMP_*.md
|
shine-solana/shine/scripts/**/TEMP_*.md
|
||||||
|
|
||||||
# Локальные артефакты и внешние материалы ESP32-подпроекта
|
# Локальные артефакты и внешние материалы ESP32-подпроекта
|
||||||
ESP32/esp32-config-tool/
|
|
||||||
ESP32/**/.git/
|
ESP32/**/.git/
|
||||||
ESP32/**/.idea/
|
ESP32/**/.idea/
|
||||||
ESP32-wallet/.idea/
|
|
||||||
ESP32/**/.arduino-build/
|
ESP32/**/.arduino-build/
|
||||||
ESP32/**/official-demo/
|
ESP32/**/official-demo/
|
||||||
!ESP32/esp32/ESP32-S3-Touch-AMOLED-2.16/official-demo/
|
|
||||||
ESP32/esp32/ESP32-S3-Touch-AMOLED-2.16/official-demo/**
|
|
||||||
!ESP32/esp32/ESP32-S3-Touch-AMOLED-2.16/official-demo/examples/
|
|
||||||
ESP32/esp32/ESP32-S3-Touch-AMOLED-2.16/official-demo/examples/**
|
|
||||||
!ESP32/esp32/ESP32-S3-Touch-AMOLED-2.16/official-demo/examples/Arduino-v3.3.5/
|
|
||||||
ESP32/esp32/ESP32-S3-Touch-AMOLED-2.16/official-demo/examples/Arduino-v3.3.5/**
|
|
||||||
!ESP32/esp32/ESP32-S3-Touch-AMOLED-2.16/official-demo/examples/Arduino-v3.3.5/libraries/
|
|
||||||
ESP32/esp32/ESP32-S3-Touch-AMOLED-2.16/official-demo/examples/Arduino-v3.3.5/libraries/**
|
|
||||||
!ESP32/esp32/ESP32-S3-Touch-AMOLED-2.16/official-demo/examples/Arduino-v3.3.5/libraries/lv_conf.h
|
|
||||||
ESP32/**/original-firmware/*.bin
|
ESP32/**/original-firmware/*.bin
|
||||||
ESP32/**/original-firmware/*.bin.sha256
|
ESP32/**/original-firmware/*.bin.sha256
|
||||||
ESP32/**/*.elf
|
ESP32/**/*.elf
|
||||||
@@ -104,9 +93,3 @@ ESP32/**/*.a
|
|||||||
# Полные серверные бэкапы (тяжёлые архивы, не коммитим)
|
# Полные серверные бэкапы (тяжёлые архивы, не коммитим)
|
||||||
server-backup/archive/**
|
server-backup/archive/**
|
||||||
!server-backup/archive/.gitkeep
|
!server-backup/archive/.gitkeep
|
||||||
|
|
||||||
# Локальная дев-обвязка Claude (дев-сервер shine-UI, сессии, планы) — не коммитим
|
|
||||||
.claude/
|
|
||||||
# Рабочие бэкапы/превью-ассеты UI — не для репозитория
|
|
||||||
*.bak.png
|
|
||||||
shine-UI/assets/navbar_preview.png
|
|
||||||
|
|||||||
Generated
+3
-1
@@ -2,5 +2,7 @@
|
|||||||
<project version="4">
|
<project version="4">
|
||||||
<component name="VcsDirectoryMappings">
|
<component name="VcsDirectoryMappings">
|
||||||
<mapping directory="" vcs="Git" />
|
<mapping directory="" vcs="Git" />
|
||||||
|
<mapping directory="$PROJECT_DIR$/ESP32/esp32/ESP32-S3-Touch-AMOLED-2.16/official-demo" vcs="Git" />
|
||||||
|
<mapping directory="$PROJECT_DIR$/tools/understand-anything-lab/upstream" vcs="Git" />
|
||||||
</component>
|
</component>
|
||||||
</project>
|
</project>
|
||||||
|
|||||||
@@ -24,12 +24,12 @@
|
|||||||
- Подробные служебные правила Telegram-обработчика, его очередь, история, systemd-запуск и особенности ответов описывать в `SHiNE-agent-bot-coder/AGENT.md`.
|
- Подробные служебные правила Telegram-обработчика, его очередь, история, systemd-запуск и особенности ответов описывать в `SHiNE-agent-bot-coder/AGENT.md`.
|
||||||
- Если в сообщениях пользователя встречается «агент MD» или похожая формулировка про файл инструкций Codex, считать, что имеется в виду автоматически читаемый `AGENTS.md`.
|
- Если в сообщениях пользователя встречается «агент MD» или похожая формулировка про файл инструкций Codex, считать, что имеется в виду автоматически читаемый `AGENTS.md`.
|
||||||
|
|
||||||
## ESP32 UI homeserver
|
## ESP32 UI сабсервера
|
||||||
- Для UI-скетча устройства `ESP32-S3-Touch-AMOLED-2.16` документ-спецификация и Arduino-скетч должны всегда оставаться синхронными.
|
- Для UI-скетча устройства `ESP32-S3-Touch-AMOLED-2.16` документ-спецификация и Arduino-скетч должны всегда оставаться синхронными.
|
||||||
- Актуальный документ по экранной логике, состояниям, кнопкам, полям, статусам и ограничениям UI считать источником истины для скетча.
|
- Актуальный документ по экранной логике, состояниям, кнопкам, полям, статусам и ограничениям UI считать источником истины для скетча.
|
||||||
- При изменении документа обязательно в том же наборе изменений приводить в соответствие скетч.
|
- При изменении документа обязательно в том же наборе изменений приводить в соответствие скетч.
|
||||||
- При изменении скетча обязательно в том же наборе изменений обновлять документ, если поменялись экраны, тексты, переходы, статусы, кнопки, поля или поведение.
|
- При изменении скетча обязательно в том же наборе изменений обновлять документ, если поменялись экраны, тексты, переходы, статусы, кнопки, поля или поведение.
|
||||||
- Для нового ESP32 UI-прототипа homeserver использовать русский язык как основной и отдельно следить, чтобы текст реально отображался на устройстве, а не только логически присутствовал в коде.
|
- Для нового ESP32 UI-прототипа сабсервера использовать русский язык как основной и отдельно следить, чтобы текст реально отображался на устройстве, а не только логически присутствовал в коде.
|
||||||
|
|
||||||
## Solana-модуль
|
## Solana-модуль
|
||||||
- В проекте есть отдельный Solana/Anchor-модуль в папке `shine-solana/shine/`.
|
- В проекте есть отдельный Solana/Anchor-модуль в папке `shine-solana/shine/`.
|
||||||
@@ -56,12 +56,9 @@
|
|||||||
- Перед каждым `AddBlock` обязательно проверять/актуализировать текущее состояние вершины блокчейна (`last global number/hash`) и использовать его при формировании блока.
|
- Перед каждым `AddBlock` обязательно проверять/актуализировать текущее состояние вершины блокчейна (`last global number/hash`) и использовать его при формировании блока.
|
||||||
|
|
||||||
## Документация личных сообщений (DM)
|
## Документация личных сообщений (DM)
|
||||||
- Актуальная документация по логике личных сообщений находится в `Dev_Docs/Personal_Messages/Протокол_DM_v1.md`.
|
- Актуальная документация по логике личных сообщений находится в `Dev_Docs/Personal_Messages/README.md`.
|
||||||
- Точный байтовый формат DM находится в `Dev_Docs/Personal_Messages/Формат_DM_v1.md`.
|
- При любом изменении кода, связанного с личными сообщениями (формат подписанного DM-блока, типы DM-сообщений, правила доставки/ACK/read-receipt, роутинг по сессиям, UI-логика чатов), обязательно обновлять `Dev_Docs/Personal_Messages/README.md`.
|
||||||
- При любом изменении кода, связанного с личными сообщениями (формат подписанного DM-блока, типы DM-сообщений, правила доставки/ACK/read-receipt, роутинг по сессиям, UI-логика чатов), обязательно обновлять оба документа:
|
- Логика личных сообщений в коде должна всегда соответствовать `Dev_Docs/Personal_Messages/README.md`.
|
||||||
- `Dev_Docs/Personal_Messages/Протокол_DM_v1.md`
|
|
||||||
- `Dev_Docs/Personal_Messages/Формат_DM_v1.md`
|
|
||||||
- Логика личных сообщений в коде должна всегда соответствовать этим документам.
|
|
||||||
- Документ по личным сообщениям обязан поддерживаться в актуальном состоянии.
|
- Документ по личным сообщениям обязан поддерживаться в актуальном состоянии.
|
||||||
|
|
||||||
## Документация API сервера
|
## Документация API сервера
|
||||||
@@ -71,11 +68,10 @@
|
|||||||
- Без явного подтверждения пользователя формат серверного API не менять; допускается только приведение документации в соответствие уже существующему коду.
|
- Без явного подтверждения пользователя формат серверного API не менять; допускается только приведение документации в соответствие уже существующему коду.
|
||||||
- Если добавляется новая операция `op`, нужно обновить общий список операций в `Dev_Docs/API/09_Operations_Index.md` или создать его, если файла ещё нет.
|
- Если добавляется новая операция `op`, нужно обновить общий список операций в `Dev_Docs/API/09_Operations_Index.md` или создать его, если файла ещё нет.
|
||||||
|
|
||||||
## Документация Figma
|
## Известная проблема (временная пометка)
|
||||||
- Актуальная документация по переносу экранов SHiNE в Figma и обратному переносу из Figma в код находится в `Dev_Docs/Figma/`.
|
- Мнения по связям пользователя (`known_person`, `shine_confirmed`, `shine_seen`) в UI могут отображаться нестабильно.
|
||||||
- Точка входа: `Dev_Docs/Figma/README.md`.
|
- Требуется отдельная доработка и финальная проверка end-to-end: запись мнения в блокчейн → обновление `connections_state` → ответ `GetUserConnectionsGraph` → отображение в UI.
|
||||||
- Подробный рабочий регламент: `Dev_Docs/Figma/TRANSFER_UI_SCREENS.md`.
|
- До фикса считать эту часть функционала незавершённой и обязательно перепроверять вручную после каждого деплоя.
|
||||||
- Для экранов регистрации, входа и других чувствительных UI-flow по умолчанию переносить экраны в Figma по одному, а не пачкой, если пользователь отдельно не подтвердил иной способ.
|
|
||||||
|
|
||||||
## Версионирование
|
## Версионирование
|
||||||
- Единый файл версий проекта: `VERSION.properties` (в корне репозитория).
|
- Единый файл версий проекта: `VERSION.properties` (в корне репозитория).
|
||||||
@@ -86,25 +82,15 @@
|
|||||||
- Обычные коммиты делать стандартным `git commit`; переменная `$GITEA_TOKEN` для коммитов не нужна и не используется.
|
- Обычные коммиты делать стандартным `git commit`; переменная `$GITEA_TOKEN` для коммитов не нужна и не используется.
|
||||||
|
|
||||||
## Deploy
|
## Deploy
|
||||||
- Все документы и заметки по деплою хранить в папке `Deploy/`.
|
- Все документы и заметки по деплою хранить в папке `Dev_Docs/deploy/`.
|
||||||
- Production-хост SHiNE: `player@shineup.me` (`178.208.64.62`).
|
- Базовый целевой хост для деплоя по умолчанию: `player@93.170.12.154` (`shineup.me`).
|
||||||
- Второй production-хост SHiNE: `player@193.8.215.70` (`server2.shineup.me`).
|
|
||||||
- Базовый путь на сервере для SHiNE: `/home/player` (проекты SHiNE размещать в `/home/player/SHiNE/...`).
|
- Базовый путь на сервере для SHiNE: `/home/player` (проекты SHiNE размещать в `/home/player/SHiNE/...`).
|
||||||
- По возможности все справки, комментарии и примечания в конфигах/документах писать на русском языке.
|
- По возможности все справки, комментарии и примечания в конфигах/документах писать на русском языке.
|
||||||
- Для операций `git push` при необходимости использовать токен из переменной окружения `$GITEA_TOKEN`.
|
- Для операций `git push` при необходимости использовать токен из переменной окружения `$GITEA_TOKEN`.
|
||||||
- Любые изменения и любой деплой на production `shineup.me` выполнять только после отдельного явного подтверждения пользователя.
|
- Для серверного деплоя использовать один gradle task: `./gradlew deployServer`.
|
||||||
- Если пользователь пишет просто `задеплой` без уточнения production/test, по умолчанию деплоить на `server2.shineup.me`.
|
- Для UI деплоя использовать один gradle task: `./gradlew deployUI`.
|
||||||
- Default server deploy: `./gradlew deployServer` или `./gradlew deployServerTest2`.
|
|
||||||
- Default UI deploy: `./gradlew deployUI` или `./gradlew deployUITest2`.
|
|
||||||
- Production server deploy: `./gradlew deployServerProduction`.
|
|
||||||
- Production UI deploy: `./gradlew deployUIProduction`.
|
|
||||||
- Для локального запуска использовать `./gradlew startLocal` (или `startLocalWithBuild`).
|
- Для локального запуска использовать `./gradlew startLocal` (или `startLocalWithBuild`).
|
||||||
- Сначала предлагать локальную проверку, а деплой на сервер выполнять по запросу пользователя.
|
- Сначала предлагать локальную проверку, а деплой на сервер выполнять по запросу пользователя.
|
||||||
- Для временной бесплатной загрузки аватаров в Arweave секретный JWK нельзя хранить в git и нельзя прописывать в репозиторный `application.properties`.
|
|
||||||
- Для продовой настройки тестового Arweave-кошелька JWK-файл нужно хранить только на сервере, например: `/home/player/SHiNE/secrets/test-free-avatar-wallet.json`.
|
|
||||||
- Для этой временной фичи на проде должны быть заданы параметры `test.freeAvatar.walletJwkPath` и `test.freeAvatar.walletAddress` через серверный override-конфиг/секреты на хосте.
|
|
||||||
- После изменения продовых значений `test.freeAvatar.*` нужно заново выполнить серверный деплой или перезапуск сервера, чтобы настройки были перечитаны приложением.
|
|
||||||
- При таких изменениях в git допускается коммитить только документацию и код чтения настроек, но не сам JWK, не содержимое секрета и не реальные приватные ключи.
|
|
||||||
|
|
||||||
## Логи звонков (установка соединения)
|
## Логи звонков (установка соединения)
|
||||||
- Специальный поток диагностики установки звонков идёт через `CallDeliveryReport` (клиент → сервер).
|
- Специальный поток диагностики установки звонков идёт через `CallDeliveryReport` (клиент → сервер).
|
||||||
@@ -136,19 +122,17 @@
|
|||||||
- После подтверждения, что фича проверена и работает корректно, соответствующий файл удалять.
|
- После подтверждения, что фича проверена и работает корректно, соответствующий файл удалять.
|
||||||
- В `Dev_Docs/Pending_Features/README.md` вести краткий регламент и поддерживать актуальность.
|
- В `Dev_Docs/Pending_Features/README.md` вести краткий регламент и поддерживать актуальность.
|
||||||
|
|
||||||
## Будущие фичи / TODO
|
## Будущие фичи
|
||||||
- Папка для задач, сознательно отложенных на будущее: `TODO/`.
|
- Папка для задач, сознательно отложенных на будущее: `Dev_Docs/Future_Features/`.
|
||||||
- Точка входа по планам: `TODO/README.md`.
|
- Точка входа по планам: `Dev_Docs/Future_Features/README.md`.
|
||||||
- Внутри планы разделены по горизонтам: `near/`, `medium/`, `far/` и тематическим подпапкам.
|
- Внутри планы разделены по горизонтам: `near/`, `medium/`, `far/`.
|
||||||
- Если пользователь спрашивает, какие есть планы или что можно продолжить, сначала читать `TODO/README.md`, затем при необходимости конкретные файлы из подпапок.
|
- Если пользователь спрашивает, какие есть планы или что можно продолжить, сначала читать `Dev_Docs/Future_Features/README.md`, затем при необходимости конкретные файлы из горизонтов.
|
||||||
- Файлы из этой папки не считать активными задачами и не начинать реализацию без явной просьбы пользователя.
|
- Файлы из этой папки не считать активными задачами и не начинать реализацию без явной просьбы пользователя.
|
||||||
- Старую папку `Dev_Docs/Future_Features/` считать выведенной из использования и больше не использовать для новых записей.
|
- Если часть кода временно отключена или закомментирована, в файле будущей фичи подробно описывать:
|
||||||
- Если часть кода временно отключена или закомментирована, либо удалена как временная заглушка, в TODO-файле подробно описывать:
|
|
||||||
- какие файлы и участки отключены;
|
- какие файлы и участки отключены;
|
||||||
- что осталось в коде как заготовка;
|
- что осталось в коде как заготовка;
|
||||||
- какие документы нужно обновить при возврате;
|
- какие документы нужно обновить при возврате;
|
||||||
- с какого сценария продолжать разработку;
|
- с какого сценария продолжать разработку.
|
||||||
- из какого коммита брать последнюю полную реализацию.
|
|
||||||
|
|
||||||
## Коммуникация по новым задачам (обязательно)
|
## Коммуникация по новым задачам (обязательно)
|
||||||
- При получении нового задания сначала кратко пересказать задачу своими словами.
|
- При получении нового задания сначала кратко пересказать задачу своими словами.
|
||||||
|
|||||||
@@ -0,0 +1,255 @@
|
|||||||
|
# DAO_запуск
|
||||||
|
|
||||||
|
Рабочий документ по тому, что ещё нужно сделать для первого запуска DAO-сценария SHiNE.
|
||||||
|
|
||||||
|
Логика документа:
|
||||||
|
|
||||||
|
- `этап1` — то, без чего нельзя считать сценарий первого запуска собранным даже в тестовом виде;
|
||||||
|
- `этап2` — то, что полезно и, вероятно, потребуется дальше, но это можно делать после старта `этап1` или параллельно без блокировки первого результата.
|
||||||
|
|
||||||
|
Базовая среда первого прохода:
|
||||||
|
|
||||||
|
- сеть `Solana devnet`;
|
||||||
|
- модель синхронизации: `server-to-server`;
|
||||||
|
- `Solana + Arweave` используются как якорь и архив;
|
||||||
|
- DAO понимается как стандартный governance/smart-contract контур, который управляет отдельными программами SHiNE, приносящими деньги.
|
||||||
|
|
||||||
|
## Краткий вывод
|
||||||
|
|
||||||
|
Для первого запуска DAO в тестовом виде текущего списка в целом хватает, но только если понимать запуск как:
|
||||||
|
|
||||||
|
- можно развернуть и проверить базовый DAO-контур;
|
||||||
|
- можно зарегистрировать пользователей и ключевые сущности;
|
||||||
|
- можно провести тестовую покупку билета через smart contract;
|
||||||
|
- можно завести тестовый денежный поток в программы, управляемые DAO;
|
||||||
|
- можно проверить опорную межсерверную синхронизацию и фиксацию состояния в архивный слой.
|
||||||
|
|
||||||
|
Если же под "запуском" понимать уже полностью устойчивую production-схему с ротацией ключей, восстановлением любого сервера из архива, железными устройствами подписи и полным циклом администрирования, то текущий список нужно будет ещё расширять.
|
||||||
|
|
||||||
|
## Этап1
|
||||||
|
|
||||||
|
Цель этапа: собрать минимально жизнеспособный DAO-сценарий в `devnet`, который можно пройти руками от регистрации до базовой экономики и проверки архитектуры.
|
||||||
|
|
||||||
|
### 1. Переписать и стабилизировать регистрацию пользователей без Anchor
|
||||||
|
|
||||||
|
Что сделать:
|
||||||
|
|
||||||
|
- довести `shine_users` в чистом Rust/Solana SDK до рабочего и проверенного состояния;
|
||||||
|
- убедиться, что `shine_login_guard` и связанный сценарий регистрации совместимы с новым ABI;
|
||||||
|
- проверить создание и чтение `user_pda`;
|
||||||
|
- проверить update пользовательской записи и связанные экономические параметры;
|
||||||
|
- синхронизировать сервер, UI и lazy-import с новым форматом и seed'ами.
|
||||||
|
|
||||||
|
Почему это в `этап1`:
|
||||||
|
|
||||||
|
- без стабильной пользовательской регистрации дальше нельзя строить ни DAO-сценарий, ни привязку устройств, ни платёжные сценарии.
|
||||||
|
|
||||||
|
### 2. Проверить полный сценарий регистрации и базовой Solana-интеграции
|
||||||
|
|
||||||
|
Что сделать:
|
||||||
|
|
||||||
|
- руками прогнать регистрацию нового пользователя;
|
||||||
|
- руками прогнать создание и update server PDA там, где это требуется текущему сценарию;
|
||||||
|
- убедиться, что сервер читает новые PDA без anchor-зависимостей и без старых discriminator'ов;
|
||||||
|
- зафиксировать, какие именно части сценария уже подтверждены руками, а какие ещё нет.
|
||||||
|
|
||||||
|
Почему это в `этап1`:
|
||||||
|
|
||||||
|
- сейчас в проекте уже есть признаки перехода на pure Rust, но без ручной проверки это нельзя считать завершённым.
|
||||||
|
|
||||||
|
### 3. Создать стандартный DAO smart contract / governance-контур
|
||||||
|
|
||||||
|
Что сделать:
|
||||||
|
|
||||||
|
- определить и реализовать стандартный DAO-контур, который будет управлять программами SHiNE;
|
||||||
|
- зафиксировать, какие права сразу передаются DAO, а какие временно остаются на отдельных ключах;
|
||||||
|
- подготовить тестовую DAO-структуру в `devnet`.
|
||||||
|
|
||||||
|
Минимум для первого запуска:
|
||||||
|
|
||||||
|
- DAO существует как управляемая сущность;
|
||||||
|
- DAO может владеть или контролировать ключевые права управления денежными программами;
|
||||||
|
- есть понятный путь, как DAO влияет на доходные программы SHiNE.
|
||||||
|
|
||||||
|
Почему это в `этап1`:
|
||||||
|
|
||||||
|
- без этого "DAO-запуск" будет только запуском отдельных Solana-программ, но не запуском управляемой DAO-системы.
|
||||||
|
|
||||||
|
### 4. Доработать смарт-контракт выплат с третьей очередью
|
||||||
|
|
||||||
|
Что сделать:
|
||||||
|
|
||||||
|
- добавить в `shine_payments` третью очередь, о которой уже принято решение;
|
||||||
|
- проверить совместимость с текущей моделью тикетов, выплат и DAO-управления;
|
||||||
|
- убедиться, что логика очередей соответствует ожидаемой экономике проекта.
|
||||||
|
|
||||||
|
Почему это в `этап1`:
|
||||||
|
|
||||||
|
- по текущей постановке это нужно именно для сценария регистрации DAO и дальнейшей экономики.
|
||||||
|
|
||||||
|
### 5. Сделать UI для покупки билетов и просмотра очереди
|
||||||
|
|
||||||
|
Что сделать:
|
||||||
|
|
||||||
|
- добавить UI-сценарий покупки билетов через smart contract;
|
||||||
|
- показать пользователю, сколько перед ним человек в очереди;
|
||||||
|
- убедиться, что UI отражает актуальное состояние контрактной логики, а не локальные предположения.
|
||||||
|
|
||||||
|
Почему это в `этап1`:
|
||||||
|
|
||||||
|
- покупка билетов у тебя обозначена как часть DAO-сценария, а не как побочная функция;
|
||||||
|
- без UI можно тестировать контракт вручную, но нельзя считать сценарий запуска достаточно собранным для нормальной проверки.
|
||||||
|
|
||||||
|
### 6. Реализовать базовую синхронизацию серверов
|
||||||
|
|
||||||
|
Что сделать:
|
||||||
|
|
||||||
|
- сделать обмен состоянием между серверами по модели `server-to-server`;
|
||||||
|
- определить минимальный набор данных, который обязан синхронизироваться;
|
||||||
|
- предусмотреть фиксацию синхронизированного состояния в `Arweave`, а `Solana` использовать как якорь и ссылочный слой;
|
||||||
|
- описать, какой сервер считается источником истины в спорных случаях или как решается конфликт.
|
||||||
|
|
||||||
|
Почему это в `этап1`:
|
||||||
|
|
||||||
|
- без межсерверной синхронизации трудно обосновать архитектуру сети как воспроизводимую и переносимую;
|
||||||
|
- это напрямую связано с идеей, что любой сможет поднять свой сервер.
|
||||||
|
|
||||||
|
### 7. Подготовить базовый сценарий архивирования и восстановления
|
||||||
|
|
||||||
|
Что сделать:
|
||||||
|
|
||||||
|
- описать и частично реализовать схему: серверы синхронизируются между собой, архив состояния уходит в `Arweave`, ссылка/якорь фиксируется через `Solana`;
|
||||||
|
- определить минимальный сценарий восстановления блоков или состояния из архивного слоя;
|
||||||
|
- подтвердить, что новый сервер может получить достаточно данных для старта.
|
||||||
|
|
||||||
|
Почему это в `этап1`:
|
||||||
|
|
||||||
|
- это один из ключевых признаков независимой и воспроизводимой DAO-инфраструктуры.
|
||||||
|
|
||||||
|
## Этап2
|
||||||
|
|
||||||
|
Цель этапа: усилить безопасность, автономность и удобство системы после того, как минимальный DAO-сценарий уже запустился и проверен в `devnet`.
|
||||||
|
|
||||||
|
### 1. Смена ключей цифровой подписи
|
||||||
|
|
||||||
|
Что сделать:
|
||||||
|
|
||||||
|
- продумать и реализовать смену `root key`, `device key`, `blockchain key`;
|
||||||
|
- описать ограничения, кто и в каком сценарии может менять каждый тип ключа;
|
||||||
|
- продумать, как не потерять доступ и как обновлять доверие к новым ключам.
|
||||||
|
|
||||||
|
Почему это в `этап2`:
|
||||||
|
|
||||||
|
- для production это очень важно;
|
||||||
|
- для первого тестового запуска можно временно использовать фиксированный набор ключей.
|
||||||
|
|
||||||
|
### 2. Полная повторная перепроверка всех сценариев
|
||||||
|
|
||||||
|
Что сделать:
|
||||||
|
|
||||||
|
- повторно прогнать регистрацию, DAO, выплаты, билеты, синхронизацию и архивирование после стабилизации `этап1`;
|
||||||
|
- оформить итоговый чек-лист ручной проверки;
|
||||||
|
- отдельно проверить пограничные сценарии и восстановление после ошибок.
|
||||||
|
|
||||||
|
Почему это в `этап2`:
|
||||||
|
|
||||||
|
- это обязательный шаг перед переходом от "собрали" к "доверяем".
|
||||||
|
|
||||||
|
### 3. Устройство на ESP32 как сабсервер с ключами
|
||||||
|
|
||||||
|
Что сделать:
|
||||||
|
|
||||||
|
- дописать прошивку, чтобы устройство могло выступать сабсервером с ключами;
|
||||||
|
- дать ему возможность регистрироваться и подключаться к серверу;
|
||||||
|
- определить, какие операции устройство подписывает и где хранит ключевой материал.
|
||||||
|
|
||||||
|
Почему это в `этап2`:
|
||||||
|
|
||||||
|
- это очень сильное развитие архитектуры, но оно не должно блокировать первый DAO-запуск.
|
||||||
|
|
||||||
|
### 4. Логин и подпись через коробочки / устройства
|
||||||
|
|
||||||
|
Что сделать:
|
||||||
|
|
||||||
|
- реализовать сценарий входа через устройство или хотя бы сценарий подписи сообщений и ключей через устройство;
|
||||||
|
- определить, как это встраивается в регистрацию DAO и подтверждение действий;
|
||||||
|
- проверить, можно ли через это безопасно регистрировать DAO или подписывать критичные команды.
|
||||||
|
|
||||||
|
Почему это в `этап2`:
|
||||||
|
|
||||||
|
- это следующий уровень безопасности и UX, но не минимальный блокер первого старта.
|
||||||
|
|
||||||
|
### 5. Создание тестового DAO с использованием устройств подписи
|
||||||
|
|
||||||
|
Что сделать:
|
||||||
|
|
||||||
|
- после готовности устройств собрать тестовый DAO-сценарий уже с аппаратным участием;
|
||||||
|
- проверить, где устройство достаточно, а где всё ещё нужен обычный кошелёк или управляющий ключ.
|
||||||
|
|
||||||
|
Почему это в `этап2`:
|
||||||
|
|
||||||
|
- это проверка усиленной модели, а не базового старта.
|
||||||
|
|
||||||
|
### 6. Расписание синхронизации серверов
|
||||||
|
|
||||||
|
Что сделать:
|
||||||
|
|
||||||
|
- определить периодичность и правила фоновой синхронизации;
|
||||||
|
- продумать ручной и автоматический режим;
|
||||||
|
- решить, как часто публиковать архивные снимки и якоря.
|
||||||
|
|
||||||
|
Почему это в `этап2`:
|
||||||
|
|
||||||
|
- сначала важнее добиться самой работающей синхронизации, а потом уже делать её регулярной и автономной.
|
||||||
|
|
||||||
|
### 7. Полное восстановление блоков из Solana/Arweave
|
||||||
|
|
||||||
|
Что сделать:
|
||||||
|
|
||||||
|
- довести процедуру восстановления до сценария "любой может поднять свой сервер";
|
||||||
|
- определить минимальный bootstrap-набор;
|
||||||
|
- проверить восстановление на чистом окружении.
|
||||||
|
|
||||||
|
Почему это в `этап2`:
|
||||||
|
|
||||||
|
- для концепции сети это критично, но как полноценная задача обычно идёт после появления базового архива и первичной синхронизации.
|
||||||
|
|
||||||
|
## Что блокирует первый запуск сильнее всего
|
||||||
|
|
||||||
|
Если расставить приоритет внутри `этап1`, то самый жёсткий порядок сейчас выглядит так:
|
||||||
|
|
||||||
|
1. pure Rust регистрация пользователей и ручная проверка сценария;
|
||||||
|
2. DAO/gov-контур и его права управления;
|
||||||
|
3. доработка выплат с третьей очередью;
|
||||||
|
4. покупка билетов через smart contract и UI-проверка очереди;
|
||||||
|
5. межсерверная синхронизация;
|
||||||
|
6. архивирование в `Arweave` с якорем в `Solana`;
|
||||||
|
7. минимальное восстановление состояния новым сервером.
|
||||||
|
|
||||||
|
## Что уже частично похоже на готовое
|
||||||
|
|
||||||
|
По текущим документам и следам в проекте уже видно, что:
|
||||||
|
|
||||||
|
- переход `shine_users` и `shine_login_guard` на pure Rust уже начат и в значительной степени сделан;
|
||||||
|
- архитектура DAO, `shine_users` и `shine_payments` уже описана;
|
||||||
|
- часть Solana-структуры и PDA-форматов уже формализована;
|
||||||
|
- тема ESP32 уже отдельно присутствует в проекте как направление.
|
||||||
|
|
||||||
|
Это хорошо, потому что документ получается не "с нуля", а как сборка того, что уже назрело в коде и планах.
|
||||||
|
|
||||||
|
## Вопросы, которые всё ещё стоит уточнить
|
||||||
|
|
||||||
|
1. Какой именно стандарт DAO планируется использовать в первом проходе: готовый governance-стек Solana или собственная минимальная обвязка вокруг управляющих кошельков?
|
||||||
|
2. Третья очередь в `shine_payments` уже точно определена по смыслу, или пока есть только решение "она нужна", но без финальной экономики?
|
||||||
|
3. Что именно считается единицей синхронизации между серверами: блоки SHiNE, агрегированные снапшоты, PDA-состояния, или смесь этих вариантов?
|
||||||
|
4. Нужен ли для `этап1` уже полноценный автоматический recovery нового сервера, или достаточно доказать это в полу-ручном сценарии?
|
||||||
|
5. Покупка билетов должна в первом проходе работать только через web/UI, или также нужен отдельный сценарий из серверного UI или скриптов?
|
||||||
|
|
||||||
|
## Рекомендуемый следующий практический шаг
|
||||||
|
|
||||||
|
Если идти без распыления, то следующим рабочим фокусом стоит считать:
|
||||||
|
|
||||||
|
1. закрыть ручную проверку pure Rust регистрации;
|
||||||
|
2. после этого формализовать минимальный DAO-контур;
|
||||||
|
3. затем переходить к третьей очереди выплат и к UI покупки билетов;
|
||||||
|
4. после этого делать синхронизацию, архив и восстановление.
|
||||||
@@ -0,0 +1,92 @@
|
|||||||
|
# DEBUG: тестирование сетевого соединения между двумя клиентами
|
||||||
|
|
||||||
|
Документ описывает временный debug-контур для проверки WebRTC соединения между двумя активными WS-сессиями.
|
||||||
|
|
||||||
|
## 1) Подготовка
|
||||||
|
|
||||||
|
|
||||||
|
0. Убедись, что в `application.properties` включен параметр:
|
||||||
|
`debug.tempApi.enabled=true`
|
||||||
|
1. Создай файл `.debug-token` в корне проекта на основе `debug-token.example`.
|
||||||
|
2. В `.debug-token` должна быть одна строка: секретный токен.
|
||||||
|
3. Перезапусти сервер.
|
||||||
|
|
||||||
|
## 2) API debug
|
||||||
|
|
||||||
|
Базовый заголовок для всех запросов:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
-H "Authorization: Bearer <YOUR_DEBUG_TOKEN>"
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.1 Получить список живых клиентов
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -s \
|
||||||
|
-H "Authorization: Bearer <YOUR_DEBUG_TOKEN>" \
|
||||||
|
http://localhost:7070/debug/ws/clients | jq
|
||||||
|
```
|
||||||
|
|
||||||
|
Ответ содержит `sessionId`, `login`, `ip`, `userAgent`, и клиентскую информацию.
|
||||||
|
|
||||||
|
### 2.2 Запустить debug-соединение между двумя сессиями
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -s -X POST \
|
||||||
|
-H "Authorization: Bearer <YOUR_DEBUG_TOKEN>" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{
|
||||||
|
"initiatorSessionId": "SESSION_ID_A",
|
||||||
|
"responderSessionId": "SESSION_ID_B",
|
||||||
|
"clearDebugLog": false
|
||||||
|
}' \
|
||||||
|
http://localhost:7070/debug/ws/connect | jq
|
||||||
|
```
|
||||||
|
|
||||||
|
В ответе придёт `runId`. Его используй для фильтра логов.
|
||||||
|
|
||||||
|
### 2.3 Читать последние N debug-логов
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -s \
|
||||||
|
-H "Authorization: Bearer <YOUR_DEBUG_TOKEN>" \
|
||||||
|
"http://localhost:7070/debug/ws/logs?limit=200&runId=<RUN_ID>" | jq
|
||||||
|
```
|
||||||
|
|
||||||
|
## 3) Операционный сценарий “Codex + пользователь”
|
||||||
|
|
||||||
|
1. Codex поднимает сервер и сообщает пользователю ссылку на UI.
|
||||||
|
2. Codex пишет пользователю: **«Запусти двух клиентов и скажи “продолжай”»**.
|
||||||
|
3. Пользователь запускает два клиента (лучше под разными логинами).
|
||||||
|
4. Пользователь пишет: **«продолжай»**.
|
||||||
|
5. Codex:
|
||||||
|
- вызывает `/debug/ws/clients`,
|
||||||
|
- выбирает 2 сессии,
|
||||||
|
- вызывает `/debug/ws/connect`,
|
||||||
|
- получает `runId`,
|
||||||
|
- читает `/debug/ws/logs?runId=...` и сообщает прогресс.
|
||||||
|
6. Если соединение не удалось:
|
||||||
|
- Codex сообщает ошибки по логам,
|
||||||
|
- при необходимости просит перезапустить 2 клиента,
|
||||||
|
- повторяет запуск debug-run.
|
||||||
|
|
||||||
|
## 4) Какие сообщения считать успехом
|
||||||
|
|
||||||
|
- `peer_connection_connected`
|
||||||
|
- `debug_connection_success`
|
||||||
|
- `signal_sent_200/210/220` без ошибок
|
||||||
|
|
||||||
|
## 5) Что говорить пользователю в ходе прогона (через «колонку»/чат)
|
||||||
|
|
||||||
|
Рекомендуемые фразы:
|
||||||
|
|
||||||
|
- «Сервер запущен. Запусти двух клиентов и напиши “продолжай”.»
|
||||||
|
- «Вижу 2 активные сессии, запускаю тест соединения.»
|
||||||
|
- «Тест запущен, runId=... Сейчас проверяю логи.»
|
||||||
|
- «Соединение установлено / не установлено. Ниже причины и следующий шаг.»
|
||||||
|
|
||||||
|
## 6) Ограничения
|
||||||
|
|
||||||
|
- Механизм временный, не для production-эксплуатации.
|
||||||
|
- Доступ к debug API имеет любой, кто знает токен.
|
||||||
|
- Рекомендуется тестить между разными логинами.
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
0. ПЕРЕДЕЛАТЬ ВСЁ НА НОВЫЙ ФОРМАТ!!
|
||||||
|
|
||||||
|
ВЫНЕСТИ ЭТИ ТРИ ВЕЩИ В ОБЩИЙ ПАРСЕР
|
||||||
|
* [2] type - тип соощения
|
||||||
|
* [2] Sиbtype - субтип сообщения
|
||||||
|
* [2] version - версия формата соощения
|
||||||
|
|
||||||
|
А ОСТАЛЬНОЕ В РЕАЛИЗАЦИЮ
|
||||||
|
|
||||||
|
|
||||||
|
ПЕРЕДЕЛАЕМ БД
|
||||||
|
|
||||||
|
1. СДЕЛАЕМ ЛИНИЮ ТОЛЬКО ДЛЯ ТЕХ ТИПОВ КОМУ НАДО (ЛАЙКАМ И ОТВЕТАМ НЕ НАДО)
|
||||||
|
(НОМЕР СООБЩЕНИЯ В ЛИНИИ ХРАНИТЬ В БЛОКАХ ВРОДЕ И НЕ НАДО ТЕМ БОЛЕЕ ЕГО ПОТОМ ПЕРЕПРОВЕРЯТЬ ВСЁ РАВНО)
|
||||||
|
А МОЖЕТ И НАДО ТК КАК ПО ОДНОМУ БЛОКУ ( ИЛИ ЧАСТИ БЛОКОВ ПОНЯТЬ КАКАЯ ЭТО ЧАСТЬ ПЕРЕПИСКИ - ВЕДЬ ГЛОБАЛ НОМЕР ВООБЩЕ НЕ ПОКАЗАТЕЛЬ)
|
||||||
|
|
||||||
|
В БД ПОМЕЧАТЬ ЧТО БЛОК ИЗ ЭТОЙ ЛИНИИ (ДЛЯ БЫСТРОГО ПОИСКА)
|
||||||
|
|
||||||
|
А УНИКАЛЬНЫЙ НОМЕР ЛИНИИ ЭТО ПО СУТИ НОМЕР СООБЩЕНИЯ СОЗДАВШЕГО ЛИНИЮ КАНАЛ (НУ И ФОРМАТ СООБЩЕНИЯ НАЧАЛА ЛИНИИ - КАНАЛА)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
3. СООТВЕТСТВЕННО удалить НАПИСАТЬ/ПЕРОВЕРИТЬ НОРМАЛЬНЫЙ SubscriptionsDAO - ТК СТАРЫЙ РАБОТАЛ НО НА ДРУГОМ ФОРМАТЕ И ТИПО КРИВО
|
||||||
|
|
||||||
|
и дальше:
|
||||||
|
ЗДЕЛАТЬ ТРИ ЗАПРОСА:
|
||||||
|
СПИСОК КАНАЛОВ НА КОГО ПОДПИСАН И ПО СКОЛЬКО СООБЩЕНИЙ И ПОСЛДНИЙ ТЕКСТ
|
||||||
|
ДОДЕЛАТЬ И СВЯЗ ПОДПИСАН УЖЕ НЕ ТОЛЬКО НА ЧЕЛА НО И НА КАНАЛ. (И ПОЛУЧАЕТСЯ ЕСТЬ ОБЩИЙ КАНАЛЛ ПОСТОВ (НО НЕКОТОРЫЕ ПОСТЫ В НИКУДА-
|
||||||
|
А НЕКОТОРЫЕ ПОСТЫ ОБЪЯВЛЕНИЕ КАНАЛА
|
||||||
|
|
||||||
|
СПИСОК СООБЩЕНИЙ В КАНАЛЕ
|
||||||
|
|
||||||
|
ОПСИСАНИЕ ОДНОГО СОООБЩЕНИЯ (С ИСТОРИЕЙ ДО НАЧАЛА ВЕТКИ И СО ВСЕМИ ОТВЕТАМИ НА НЕГО)
|
||||||
|
|
||||||
|
(НУ И В БУДУЩЕМ четвёртый ИСТОРИЮ сообщения ПО ЕДИТУ)
|
||||||
|
|
||||||
|
|
||||||
|
И ПОМЯТКА
|
||||||
|
ВСЕГДА СЧИТАЕМ ПО ПОСЛЕДНЕМУ БЛОКЧЕЙНУ ДОСТУПНОМУ ПОЛЬЗОВАТЕЛЮ
|
||||||
|
ХОТЯ ССЫЛКА ПО НОМЕРУ БЛОКЧЕЙНА КУДА ДОБАВИЛИ
|
||||||
|
|
||||||
|
ЛАЙКИ И ОТВЕТЫ ПИШЕМ НА НОМЕР СООБЩЕНИЯ ЕДИТА
|
||||||
|
(СЧИТАЕМ ТРИГЕРОМ И НА ОРИГИНАЛЬНЫЙ СУМАРНОЕ И ОТДЕЛЬНО НА НЕГО, И НА КАЖДЫЙ ЕДИТ ОТДЕЛЬНО)
|
||||||
|
|
||||||
|
ОТВЕТЫ ПОКАЗЫВАЕМ ВСЕ ВРАЗ
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
Сделать возможность убрать свой лайк. (пока не надо а сложность что надо больше проверок) - хотя можно и без проверки, просто за двойной лайк или за снятие двойное лайка. Будет двойное проникновение :)) тому кто изменил код клиента и убрал проверку на клиенте - и блокчейн заблокируется и всё.
|
||||||
|
поэтому просто на каждую реакцию добавиться убрать эту ракцию .
|
||||||
|
- это просто
|
||||||
|
|
||||||
|
сделатьпотом что бы в солану_юзерс хранилось имя текущего блокчейна пользователя. Что бы потом можно было грузить именно актуальный ТО ЕСТЬ потом можно будет менять блокченый!
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
сделать сессион пасворд тоже ключём подписи устройства!!
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
Перечень библиотек и их краткое описание
|
||||||
|
|
||||||
|
shine-server-log
|
||||||
|
Статический “сиренный” метод для максимально заметного критического лога администратору
|
||||||
|
|
||||||
|
shine-server-config
|
||||||
|
Минимальный конфиг-лоадер, который один раз читает application.properties и даёт доступ к параметрам.
|
||||||
|
|
||||||
|
shine-server-geo
|
||||||
|
Утилиты, которые вытаскивают IP/язык/UA из Jetty WebSocket и (опционально) резолвят гео по IP с кэшем в БД.
|
||||||
|
|
||||||
|
shine-server-crypto
|
||||||
|
Базовые крипто-утилиты для SHA-256 и Ed25519 (BouncyCastle) + проверка подписи/хэша для .bch сущностей и маленький self-test.
|
||||||
|
|
||||||
|
shine-server-bd
|
||||||
|
Библиотека реалезующая всю работу с БД:
|
||||||
|
|
||||||
|
shine-server-blockchain
|
||||||
|
Библиотека, которая задаёт единый бинарный формат блоков (RAW+signature+hash), парсит/валидирует “тело” блока по type/version, и проверяет целостность/подпись цепочки через SHA-256 + Ed25519 с привязкой к login и предыдущим хэшам.
|
||||||
|
|
||||||
|
shine-server-protocol
|
||||||
|
Библиотека JSON-протокол поверх WebSocket для взаимодействия с клиентами.
|
||||||
@@ -8,7 +8,7 @@ shine.db.SqliteDbController — один вход в БД: читает db.path,
|
|||||||
shine.db.DatabaseInitializer — разовая сборка схемы (таблицы + индексы).
|
shine.db.DatabaseInitializer — разовая сборка схемы (таблицы + индексы).
|
||||||
|
|
||||||
|
|
||||||
shine.db.entities.* — POJO-модели строк таблиц (без логики, только поля/геттеры/сеттеры + иногда удобные методы вроде getClientKeyByte()).
|
shine.db.entities.* — POJO-модели строк таблиц (без логики, только поля/геттеры/сеттеры + иногда удобные методы вроде getDeviceKeyByte()).
|
||||||
shine.db.dao.* — DAO по таблицам: ActiveSessionsDAO, SolanaUsersDAO, UserParamsDAO, IpGeoCacheDAO, BlockchainStateDAO, BlocksDAO; плюс “сервисные” DAO:
|
shine.db.dao.* — DAO по таблицам: ActiveSessionsDAO, SolanaUsersDAO, UserParamsDAO, IpGeoCacheDAO, BlockchainStateDAO, BlocksDAO; плюс “сервисные” DAO:
|
||||||
|
|
||||||
UserCreateDAO — атомарная регистрация пользователя в транзакции (BEGIN IMMEDIATE + rollback/commit).
|
UserCreateDAO — атомарная регистрация пользователя в транзакции (BEGIN IMMEDIATE + rollback/commit).
|
||||||
@@ -0,0 +1,209 @@
|
|||||||
|
SHiNE — структура БД (актуальная версия)
|
||||||
|
|
||||||
|
Перечень таблиц и назначение
|
||||||
|
|
||||||
|
solana_users
|
||||||
|
Справочник пользователей: логин + ключ устройства + (опционально) Solana-ключ.
|
||||||
|
Базовая таблица, используется как FK почти везде.
|
||||||
|
|
||||||
|
active_sessions
|
||||||
|
Активные сессии авторизации/работы клиента: секреты, тайминги, WebPush-данные, IP и информация о клиенте.
|
||||||
|
|
||||||
|
users_params
|
||||||
|
Хранилище актуальных параметров пользователя.
|
||||||
|
Для каждой пары (login, param) хранится только самая новая версия по time_ms.
|
||||||
|
|
||||||
|
ip_geo_cache
|
||||||
|
Кеш геолокации по IP для снижения нагрузки на внешние сервисы.
|
||||||
|
|
||||||
|
blockchain_state
|
||||||
|
Агрегированное состояние блокчейна по blockchain_name:
|
||||||
|
лимиты, текущий размер, последний глобальный блок и состояние линий 0..7.
|
||||||
|
|
||||||
|
blocks
|
||||||
|
Журнал всех блоков и сообщений.
|
||||||
|
Содержит историю событий: тексты, реакции, ответы, связи.
|
||||||
|
PRIMARY KEY намеренно отсутствует.
|
||||||
|
|
||||||
|
connections_state ⭐
|
||||||
|
Актуальное состояние связей между пользователями
|
||||||
|
(друг / контакт / подписка).
|
||||||
|
Обновляется автоматически на основе событий из blocks.
|
||||||
|
|
||||||
|
message_stats ⭐
|
||||||
|
Агрегированные счётчики лайков и ответов на конкретные сообщения.
|
||||||
|
Поддерживается триггерами из blocks.
|
||||||
|
|
||||||
|
Таблицы подробно
|
||||||
|
|
||||||
|
solana_users
|
||||||
|
login — TEXT PK — уникальный логин пользователя
|
||||||
|
device_key — TEXT NOT NULL — публичный ключ устройства (Base64(32) / HEX(64))
|
||||||
|
solana_key — TEXT NULL — публичный ключ Solana-аккаунта
|
||||||
|
|
||||||
|
active_sessions
|
||||||
|
session_id — TEXT PK — идентификатор сессии
|
||||||
|
login — TEXT NOT NULL, FK → solana_users(login)
|
||||||
|
session_pwd — TEXT NOT NULL — секрет сессии
|
||||||
|
storage_pwd — TEXT NOT NULL — секрет storage
|
||||||
|
session_created_at_ms — INTEGER NOT NULL
|
||||||
|
last_authirificated_at_ms — INTEGER NOT NULL
|
||||||
|
push_endpoint — TEXT NULL
|
||||||
|
push_p256dh_key — TEXT NULL
|
||||||
|
push_auth_key — TEXT NULL
|
||||||
|
client_ip — TEXT NULL
|
||||||
|
client_info_from_client — TEXT NULL
|
||||||
|
client_info_from_request — TEXT NULL
|
||||||
|
user_language — TEXT NULL
|
||||||
|
|
||||||
|
users_params
|
||||||
|
login — TEXT NOT NULL, FK → solana_users(login)
|
||||||
|
param — TEXT NOT NULL
|
||||||
|
time_ms — INTEGER NOT NULL
|
||||||
|
value — TEXT NOT NULL
|
||||||
|
device_key — TEXT NULL
|
||||||
|
signature — TEXT NULL
|
||||||
|
|
||||||
|
Ограничение:
|
||||||
|
UNIQUE(login, param)
|
||||||
|
|
||||||
|
Логика:
|
||||||
|
обновление принимается только если excluded.time_ms > users_params.time_ms
|
||||||
|
|
||||||
|
ip_geo_cache
|
||||||
|
ip — TEXT PK
|
||||||
|
geo — TEXT NULL
|
||||||
|
updated_at_ms — INTEGER NOT NULL
|
||||||
|
|
||||||
|
blockchain_state
|
||||||
|
blockchain_name — TEXT PK
|
||||||
|
login — TEXT NOT NULL, FK → solana_users(login)
|
||||||
|
blockchain_key — TEXT NOT NULL
|
||||||
|
size_limit — INTEGER NOT NULL
|
||||||
|
file_size_bytes — INTEGER NOT NULL
|
||||||
|
last_global_number — INTEGER NOT NULL (-1 = genesis)
|
||||||
|
last_global_hash — TEXT NOT NULL
|
||||||
|
updated_at_ms — INTEGER NOT NULL
|
||||||
|
|
||||||
|
Линии 0..7:
|
||||||
|
для каждой линии:
|
||||||
|
lineX_last_number
|
||||||
|
lineX_last_hash
|
||||||
|
|
||||||
|
blocks
|
||||||
|
login — TEXT NOT NULL
|
||||||
|
bch_name — TEXT NOT NULL
|
||||||
|
block_global_number — INTEGER NOT NULL
|
||||||
|
block_global_pre_hash — TEXT NOT NULL
|
||||||
|
block_line_index — INTEGER NOT NULL
|
||||||
|
block_line_number — INTEGER NOT NULL
|
||||||
|
block_line_pre_hash — TEXT NOT NULL
|
||||||
|
msg_type — INTEGER NOT NULL
|
||||||
|
msg_sub_type — INTEGER NOT NULL
|
||||||
|
block_bytes — BLOB NULL
|
||||||
|
|
||||||
|
Ссылка на другой блок (nullable):
|
||||||
|
to_login
|
||||||
|
to_bch_name
|
||||||
|
to_block_global_number
|
||||||
|
to_block_hash
|
||||||
|
|
||||||
|
connections_state ⭐
|
||||||
|
Текущее агрегированное состояние связей.
|
||||||
|
|
||||||
|
login — TEXT NOT NULL
|
||||||
|
rel_type — INTEGER NOT NULL
|
||||||
|
10 = FRIEND
|
||||||
|
20 = CONTACT
|
||||||
|
30 = FOLLOW
|
||||||
|
to_login — TEXT NOT NULL
|
||||||
|
to_bch_name — TEXT NOT NULL
|
||||||
|
to_block_global_number — INTEGER NULL
|
||||||
|
to_block_hash — TEXT NULL
|
||||||
|
|
||||||
|
Ограничение:
|
||||||
|
UNIQUE(login, rel_type, to_login)
|
||||||
|
|
||||||
|
message_stats ⭐
|
||||||
|
Счётчики активности по целевому сообщению.
|
||||||
|
|
||||||
|
to_login — TEXT NOT NULL
|
||||||
|
to_bch_name — TEXT NOT NULL
|
||||||
|
to_block_global_number — INTEGER NOT NULL
|
||||||
|
to_block_hash — TEXT NOT NULL
|
||||||
|
likes_count — INTEGER NOT NULL DEFAULT 0
|
||||||
|
replies_count — INTEGER NOT NULL DEFAULT 0
|
||||||
|
|
||||||
|
UNIQUE:
|
||||||
|
(to_login, to_bch_name, to_block_global_number, to_block_hash)
|
||||||
|
|
||||||
|
Триггеры БД (полная логика)
|
||||||
|
|
||||||
|
3.1 Связи пользователей
|
||||||
|
trg_blocks_connection_state_ai
|
||||||
|
AFTER INSERT ON blocks
|
||||||
|
|
||||||
|
Условие:
|
||||||
|
msg_type = 3 (connection)
|
||||||
|
|
||||||
|
Добавление / обновление связи
|
||||||
|
msg_sub_type IN (10,20,30)
|
||||||
|
выполняется UPSERT в connections_state
|
||||||
|
|
||||||
|
Удаление связи
|
||||||
|
msg_sub_type IN (11,21,31)
|
||||||
|
удаляется соответствующая связь:
|
||||||
|
11 → 10
|
||||||
|
21 → 20
|
||||||
|
31 → 30
|
||||||
|
|
||||||
|
Итог:
|
||||||
|
blocks — журнал событий
|
||||||
|
connections_state — всегда актуальное состояние
|
||||||
|
|
||||||
|
3.2 Подсчёт лайков ⭐
|
||||||
|
trg_blocks_message_stats_like_ai
|
||||||
|
AFTER INSERT ON blocks
|
||||||
|
|
||||||
|
Условие:
|
||||||
|
msg_type = 2 (reaction)
|
||||||
|
msg_sub_type = 1 (like)
|
||||||
|
|
||||||
|
Действие:
|
||||||
|
определяется цель по to_bch_name, to_block_global_number, to_block_hash
|
||||||
|
to_login вычисляется как
|
||||||
|
substr(to_bch_name, 1, length(to_bch_name) - 3)
|
||||||
|
выполняется UPSERT в message_stats
|
||||||
|
likes_count += 1
|
||||||
|
|
||||||
|
3.3 Подсчёт ответов ⭐
|
||||||
|
trg_blocks_message_stats_reply_ai
|
||||||
|
AFTER INSERT ON blocks
|
||||||
|
|
||||||
|
Условие:
|
||||||
|
msg_type = 1 (text)
|
||||||
|
msg_sub_type = 2 (reply)
|
||||||
|
|
||||||
|
Действие:
|
||||||
|
цель определяется аналогично лайкам
|
||||||
|
выполняется UPSERT в message_stats
|
||||||
|
replies_count += 1
|
||||||
|
|
||||||
|
Индексы (смысл)
|
||||||
|
|
||||||
|
idx_solana_users_login — поиск пользователя
|
||||||
|
idx_active_sessions_login — сессии пользователя
|
||||||
|
idx_users_params_login — параметры пользователя
|
||||||
|
idx_ip_geo_cache_updated_at — чистка кеша
|
||||||
|
idx_blockchain_state_login — блокчейны пользователя
|
||||||
|
idx_blockchain_state_updated_at — обслуживание
|
||||||
|
idx_blocks_chain_global — чтение цепочки
|
||||||
|
idx_blocks_to_target — реакции / ответы
|
||||||
|
idx_message_stats_target — быстрый доступ к счётчикам
|
||||||
|
|
||||||
|
Итоговая модель мышления
|
||||||
|
|
||||||
|
blocks — неизменяемый журнал событий
|
||||||
|
connections_state — проекция связей
|
||||||
|
message_stats — проекция активности
|
||||||
|
всё вычисляется детерминированно через триггеры
|
||||||
@@ -0,0 +1,75 @@
|
|||||||
|
# Протокол звонков (MVP)
|
||||||
|
|
||||||
|
Версия: browser-to-browser, runtime-only signaling.
|
||||||
|
|
||||||
|
## Цели
|
||||||
|
- Технические сообщения звонка не сохраняются в БД direct_messages.
|
||||||
|
- Первый INVITE рассылается всем активным сессиям получателя и дублируется web push.
|
||||||
|
- Последующие сигналы идут только в конкретную sessionId и не дублируются в push.
|
||||||
|
|
||||||
|
## Операции API
|
||||||
|
|
||||||
|
### 1) CallInviteBroadcast
|
||||||
|
Отправляет общий вызов пользователю.
|
||||||
|
|
||||||
|
Запрос payload:
|
||||||
|
- `toLogin: string`
|
||||||
|
- `callId: string`
|
||||||
|
- `type: 100` (INVITE)
|
||||||
|
|
||||||
|
Поведение сервера:
|
||||||
|
- Рассылает `IncomingCallInvite` во все активные WS-сессии `toLogin`.
|
||||||
|
- В payload события передаёт:
|
||||||
|
- `fromLogin`
|
||||||
|
- `fromSessionId` (session инициатора)
|
||||||
|
- `toLogin`
|
||||||
|
- `callId`
|
||||||
|
- `type=100`
|
||||||
|
- `timeMs`
|
||||||
|
- Отправляет web push уведомление о входящем вызове.
|
||||||
|
|
||||||
|
Ответ payload:
|
||||||
|
- `callId`
|
||||||
|
- `deliveredWsSessions`
|
||||||
|
- `deliveredFcmSessions`
|
||||||
|
|
||||||
|
### 2) CallSignalToSession
|
||||||
|
Отправляет технический сигнал в конкретную сессию.
|
||||||
|
|
||||||
|
Запрос payload:
|
||||||
|
- `toLogin: string`
|
||||||
|
- `targetSessionId: string`
|
||||||
|
- `callId: string`
|
||||||
|
- `type: int`
|
||||||
|
- `data: string` (для SDP/ICE/служебных строк)
|
||||||
|
|
||||||
|
Поведение сервера:
|
||||||
|
- Ищет только `targetSessionId`.
|
||||||
|
- Проверяет, что сессия принадлежит `toLogin`.
|
||||||
|
- Отправляет `IncomingCallSignal` только в эту сессию.
|
||||||
|
- В БД ничего не сохраняет.
|
||||||
|
- Push не отправляет.
|
||||||
|
|
||||||
|
Ответ payload:
|
||||||
|
- `delivered: boolean`
|
||||||
|
|
||||||
|
## Коды type
|
||||||
|
- `100` INVITE
|
||||||
|
- `110` RINGING
|
||||||
|
- `120` ACCEPT
|
||||||
|
- `130` DECLINE_BUSY
|
||||||
|
- `140` TIMEOUT
|
||||||
|
- `150` HANGUP
|
||||||
|
- `200` OFFER
|
||||||
|
- `210` ANSWER
|
||||||
|
- `220` ICE
|
||||||
|
|
||||||
|
## Правила UI/логики
|
||||||
|
- Если уже есть активный звонок и пришел новый INVITE -> автоответ `DECLINE_BUSY` без UI.
|
||||||
|
- После ACCEPT `callId` остаётся во всех OFFER/ANSWER/ICE сообщениях до конца звонка.
|
||||||
|
- При параллельных звонках A<->B допускается детерминированное правило, кто создаёт OFFER.
|
||||||
|
|
||||||
|
## Тайминги MVP
|
||||||
|
- Ожидание подтверждения/реакции после INVITE: до 5с (у инициатора).
|
||||||
|
- Ожидание принятия у входящего звонка: 20с.
|
||||||
|
- Общий лимит ожидания до соединения: 22с.
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
|
||||||
|
|
||||||
|
Дальше делать:
|
||||||
|
Описание форматов.
|
||||||
|
Запросы клиент-сервер.
|
||||||
|
Промт на клиента.
|
||||||
|
|
||||||
|
---
|
||||||
|
Потом в сервак дописать синхронизацию серверов.
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
# SHiNE Deployment Servers Inventory
|
||||||
|
|
||||||
|
## Scope
|
||||||
|
This folder contains all deployment-related notes and server records for SHiNE.
|
||||||
|
|
||||||
|
## Legacy Production Server
|
||||||
|
- Name: `VPS-02` (legacy)
|
||||||
|
- Access: `root@194.87.0.247`
|
||||||
|
- Current role: old production server
|
||||||
|
- Confirmed services:
|
||||||
|
- `coturn` is installed and active (`systemd: active/running`)
|
||||||
|
- `caddy` is installed (reported by project context; verify version on host if needed)
|
||||||
|
- TURN configuration observed on host:
|
||||||
|
- `listening-port=3478`
|
||||||
|
- `external-ip=194.87.0.247`
|
||||||
|
- `relay-ip=194.87.0.247`
|
||||||
|
- auth mode: `use-auth-secret` + `static-auth-secret`
|
||||||
|
- SHiNE deployment note:
|
||||||
|
- This host is used as current/legacy runtime for SHiNE.
|
||||||
|
- Gradle-based deployment is used in this project (see repository deploy tasks and scripts).
|
||||||
|
|
||||||
|
## Target Production Server (Migration)
|
||||||
|
- Name: `VPS-05` (new)
|
||||||
|
- Access: `root@45.136.124.227`
|
||||||
|
- Planned role: new primary production server for gradual migration
|
||||||
|
- Baseline setup done:
|
||||||
|
- `ripgrep` installed
|
||||||
|
- user `player` created
|
||||||
|
- user `player` added to `sudo` group
|
||||||
|
- deployment directory created: `/home/player/SHiNE`
|
||||||
|
- Rule:
|
||||||
|
- All SHiNE-related runtime files and deployments on VPS-05 should be placed under `/home/player/SHiNE`.
|
||||||
|
|
||||||
|
## Additional TURN Node
|
||||||
|
- Name: `promo-node-93`
|
||||||
|
- Access: `ubuntu@93.170.12.154` (and `player` user for SHiNE operations)
|
||||||
|
- Role: additional TURN node for SHiNE calls
|
||||||
|
- TURN setup:
|
||||||
|
- `coturn` installed and active
|
||||||
|
- `listening-port=3478`
|
||||||
|
- `tls-listening-port=5349`
|
||||||
|
- `use-auth-secret` + shared `static-auth-secret`
|
||||||
|
- relay UDP port range: `49152-50152`
|
||||||
|
- Runtime files:
|
||||||
|
- `/etc/turnserver.conf`
|
||||||
|
- `/home/player/SHiNE/coturn/turnserver.conf`
|
||||||
|
- Cleanup done:
|
||||||
|
- Disabled old reverse SSH tunnel (`reverse-ssh.service`) that exposed `0.0.0.0:1200 -> localhost:22` to `194.87.0.247`.
|
||||||
|
|
||||||
|
## Next Migration Steps (recommended)
|
||||||
|
1. Install and configure runtime dependencies (JDK, Caddy, DB, TURN if required).
|
||||||
|
2. Mirror SHiNE deployment process from VPS-02 using existing Gradle deployment flow.
|
||||||
|
3. Move traffic gradually and validate logs/metrics before final cutover.
|
||||||
@@ -1,93 +0,0 @@
|
|||||||
# Production-серверы SHiNE
|
|
||||||
|
|
||||||
## Короткий ответ
|
|
||||||
|
|
||||||
По текущим данным репозитория у SHiNE описаны **два production-контура**:
|
|
||||||
|
|
||||||
- `player@shineup.me`
|
|
||||||
- домен `shineup.me`
|
|
||||||
- IP `178.208.64.62`
|
|
||||||
|
|
||||||
и
|
|
||||||
|
|
||||||
- `player@193.8.215.70`
|
|
||||||
- домен `server2.shineup.me`
|
|
||||||
- IP `193.8.215.70`
|
|
||||||
|
|
||||||
## 1. Основной production-хост
|
|
||||||
|
|
||||||
- SSH: `player@shineup.me`
|
|
||||||
- домен: `shineup.me`
|
|
||||||
- IP: `178.208.64.62`
|
|
||||||
- пользователь: `player`
|
|
||||||
- базовый путь: `/home/player`
|
|
||||||
|
|
||||||
Основные каталоги:
|
|
||||||
|
|
||||||
- проект SHiNE: `/home/player/SHiNE`
|
|
||||||
- серверный jar: `/home/player/SHiNE/shine-server/shine-server.jar`
|
|
||||||
- UI: `/home/player/SHiNE/shine-ui`
|
|
||||||
- данные: `/home/player/SHiNE/shine-server/data/`
|
|
||||||
- логи: `/home/player/SHiNE/shine-server/logs/app.log`
|
|
||||||
|
|
||||||
Сервисы:
|
|
||||||
|
|
||||||
- `shine-server.service`
|
|
||||||
- `caddy.service`
|
|
||||||
|
|
||||||
Caddy:
|
|
||||||
|
|
||||||
- активный конфиг: `/etc/caddy/Caddyfile`
|
|
||||||
- UI root: `/home/player/SHiNE/shine-ui`
|
|
||||||
- `/ws` проксируется на `127.0.0.1:7070`
|
|
||||||
|
|
||||||
Deploy:
|
|
||||||
|
|
||||||
- `./gradlew deployServerProduction`
|
|
||||||
- `./gradlew deployUIProduction`
|
|
||||||
|
|
||||||
Правило:
|
|
||||||
|
|
||||||
- любые изменения на `shineup.me` делать только после отдельного подтверждения пользователя.
|
|
||||||
|
|
||||||
## 2. Второй production-сервер
|
|
||||||
|
|
||||||
- SSH: `player@193.8.215.70`
|
|
||||||
- домен: `server2.shineup.me`
|
|
||||||
- IP: `193.8.215.70`
|
|
||||||
- пользователь: `player`
|
|
||||||
- базовый путь: `/home/player`
|
|
||||||
|
|
||||||
Роль:
|
|
||||||
|
|
||||||
- второй production-контур SHiNE;
|
|
||||||
- использовать как production-сервер, несмотря на исторические имена deploy-задач
|
|
||||||
`deployServerTest2` / `deployUITest2`.
|
|
||||||
|
|
||||||
Основные каталоги:
|
|
||||||
|
|
||||||
- проект SHiNE: `/home/player/SHiNE`
|
|
||||||
- серверный jar: `/home/player/SHiNE/shine-server/shine-server.jar`
|
|
||||||
- UI: `/home/player/SHiNE/shine-ui`
|
|
||||||
- данные: `/home/player/SHiNE/shine-server/data/`
|
|
||||||
- логи: `/home/player/SHiNE/shine-server/logs/app.log`
|
|
||||||
|
|
||||||
## 3. Связанные публичные production-публикации на том же хосте
|
|
||||||
|
|
||||||
На этом же production-хосте есть отдельная публикация для `shine_payments`:
|
|
||||||
|
|
||||||
- каталог: `/home/player/sites/test-solana-tickets.shineup.me`
|
|
||||||
- домены:
|
|
||||||
- `https://test-solana-tickets.shineup.me`
|
|
||||||
- `https://test-solana-tickets.shiningpeople.ru`
|
|
||||||
|
|
||||||
Это не второй production-хост SHiNE, а отдельный сайт на том же сервере.
|
|
||||||
|
|
||||||
## 4. Какие серверы не считать production
|
|
||||||
|
|
||||||
Не production:
|
|
||||||
|
|
||||||
- `t1.shineup.me`
|
|
||||||
- `t2.shineup.me`
|
|
||||||
- `t3.shineup.me`
|
|
||||||
- `t4.shineup.me`
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
# Deploy
|
|
||||||
|
|
||||||
Подробности о том, где что задеплоено в SHiNE, нужно искать в папке `Deploy/`.
|
|
||||||
|
|
||||||
Эта папка служит краткой картой окружений:
|
|
||||||
|
|
||||||
- [TEST_SERVERS.md](/home/ai/work/SHiNE/SHiNE-server-sha256/Deploy/TEST_SERVERS.md) — тестовые стенды;
|
|
||||||
- [PRODUCTION_SERVERS.md](/home/ai/work/SHiNE/SHiNE-server-sha256/Deploy/PRODUCTION_SERVERS.md) — production-контур и связанные публичные публикации.
|
|
||||||
|
|
||||||
Ниже краткая сводка.
|
|
||||||
|
|
||||||
## Основные публичные контуры
|
|
||||||
|
|
||||||
- Production SHiNE:
|
|
||||||
- `player@shineup.me`
|
|
||||||
- домен `shineup.me`
|
|
||||||
- IP `178.208.64.62`
|
|
||||||
- Второй production SHiNE:
|
|
||||||
- `player@193.8.215.70`
|
|
||||||
- домен `server2.shineup.me`
|
|
||||||
- IP `193.8.215.70`
|
|
||||||
## Отдельный quad-devnet стенд
|
|
||||||
|
|
||||||
На отдельном VPS `178.208.90.249` подняты 4 независимых test/devnet-инстанса:
|
|
||||||
|
|
||||||
- `t1.shineup.me`
|
|
||||||
- `t2.shineup.me`
|
|
||||||
- `t3.shineup.me`
|
|
||||||
- `t4.shineup.me`
|
|
||||||
|
|
||||||
## Важно
|
|
||||||
|
|
||||||
- Production-контура SHiNE сейчас два: `shineup.me` и `server2.shineup.me`.
|
|
||||||
- `t1..t4.shineup.me` — это отдельные тестовые/devnet-контуры, не production.
|
|
||||||
- Любые изменения на `shineup.me` делать только после отдельного подтверждения пользователя.
|
|
||||||
@@ -1,124 +0,0 @@
|
|||||||
# Тестовые серверы SHiNE
|
|
||||||
|
|
||||||
Этот файл описывает тестовые стенды, которые сейчас фигурируют в проекте.
|
|
||||||
|
|
||||||
## 1. Исторический `test2`, теперь второй production-сервер
|
|
||||||
|
|
||||||
- SSH: `player@193.8.215.70`
|
|
||||||
- Домен: `server2.shineup.me`
|
|
||||||
- IP: `193.8.215.70`
|
|
||||||
- Назначение: второй production-контур SHiNE
|
|
||||||
|
|
||||||
Структура:
|
|
||||||
|
|
||||||
- каталог SHiNE: `/home/player/SHiNE`
|
|
||||||
- сервер: `/home/player/SHiNE/shine-server/shine-server.jar`
|
|
||||||
- UI: `/home/player/SHiNE/shine-ui`
|
|
||||||
- данные: `/home/player/SHiNE/shine-server/data/`
|
|
||||||
- логи: `/home/player/SHiNE/shine-server/logs/app.log`
|
|
||||||
|
|
||||||
Сервисы:
|
|
||||||
|
|
||||||
- `shine-server.service`
|
|
||||||
- `caddy.service`
|
|
||||||
|
|
||||||
Deploy:
|
|
||||||
|
|
||||||
- `./gradlew deployServer`
|
|
||||||
- `./gradlew deployServerTest2`
|
|
||||||
- `./gradlew deployUI`
|
|
||||||
- `./gradlew deployUITest2`
|
|
||||||
|
|
||||||
Примечания:
|
|
||||||
|
|
||||||
- этот хост больше не считать test-контуром;
|
|
||||||
- исторические имена deploy-задач `deployServerTest2` / `deployUITest2` сохранены, но сам хост считать production;
|
|
||||||
- задача `deployUITest2` по умолчанию выкладывает UI на `server2.shineup.me`, а не на `t2.shineup.me`;
|
|
||||||
- при описании окружений перечислять его как второй production-сервер.
|
|
||||||
|
|
||||||
## 2. Отдельный quad-devnet стенд `t1..t4`
|
|
||||||
|
|
||||||
- VPS: `178.208.90.249`
|
|
||||||
- пользователь: `player`
|
|
||||||
- назначение: 4 независимых SHiNE-инстанса на Solana `devnet`
|
|
||||||
|
|
||||||
Домены и логины:
|
|
||||||
|
|
||||||
- `server_t1` -> `https://t1.shineup.me`
|
|
||||||
- `server_t2` -> `https://t2.shineup.me`
|
|
||||||
- `server_t3` -> `https://t3.shineup.me`
|
|
||||||
- `server_t4` -> `https://t4.shineup.me`
|
|
||||||
|
|
||||||
Каталоги:
|
|
||||||
|
|
||||||
- `/home/player/t1/server`
|
|
||||||
- `/home/player/t1/UI`
|
|
||||||
- `/home/player/t2/server`
|
|
||||||
- `/home/player/t2/UI`
|
|
||||||
- `/home/player/t3/server`
|
|
||||||
- `/home/player/t3/UI`
|
|
||||||
- `/home/player/t4/server`
|
|
||||||
- `/home/player/t4/UI`
|
|
||||||
|
|
||||||
Подробная памятка на самом VPS:
|
|
||||||
|
|
||||||
- `/home/player/Agents.md`
|
|
||||||
|
|
||||||
Порты и systemd:
|
|
||||||
|
|
||||||
- `t1` -> `7101` -> `shine-t1.service`
|
|
||||||
- `t2` -> `7102` -> `shine-t2.service`
|
|
||||||
- `t3` -> `7103` -> `shine-t3.service`
|
|
||||||
- `t4` -> `7104` -> `shine-t4.service`
|
|
||||||
|
|
||||||
Что важно по конфигу каждого инстанса:
|
|
||||||
|
|
||||||
- отдельный `/home/player/tX/server/application.properties`
|
|
||||||
- `server.port=710X`
|
|
||||||
- `server.SHiNE.login=server_tX`
|
|
||||||
- `db.path=data/shine.sqlite`
|
|
||||||
- `solana.cluster=devnet`
|
|
||||||
- `solana.rpcUrl=https://api.devnet.solana.com`
|
|
||||||
- `server.ui.indexPath=/home/player/tX/UI/index.html`
|
|
||||||
- `server.info.url=https://tX.shineup.me`
|
|
||||||
|
|
||||||
UI каждого инстанса:
|
|
||||||
|
|
||||||
- живёт в отдельной копии `shine-UI`;
|
|
||||||
- использует свой `js/deploy-config.js`;
|
|
||||||
- по умолчанию смотрит именно на свой `tX.shineup.me`.
|
|
||||||
|
|
||||||
Caddy на стенде:
|
|
||||||
|
|
||||||
- конфиг: `/etc/caddy/Caddyfile`
|
|
||||||
- статика: `/home/player/tX/UI`
|
|
||||||
- `/ws` проксируется на `127.0.0.1:710X`
|
|
||||||
|
|
||||||
Operational-нюанс:
|
|
||||||
|
|
||||||
- при одновременных рестартах возможны `HTTP 429` от `api.devnet.solana.com`;
|
|
||||||
- поэтому сервисы `shine-t1..shine-t4` лучше перезапускать по одному, с паузой.
|
|
||||||
|
|
||||||
## 3. Что проверять первым делом
|
|
||||||
|
|
||||||
Для любого test-контура полезны такие быстрые проверки:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
curl -I https://server2.shineup.me
|
|
||||||
curl -I https://t1.shineup.me
|
|
||||||
curl -I https://t2.shineup.me
|
|
||||||
curl -I https://t3.shineup.me
|
|
||||||
curl -I https://t4.shineup.me
|
|
||||||
```
|
|
||||||
|
|
||||||
Для quad-devnet VPS:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
sudo systemctl --no-pager --full status caddy shine-t1 shine-t2 shine-t3 shine-t4
|
|
||||||
```
|
|
||||||
|
|
||||||
Для второго production-контура:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
sudo systemctl --no-pager --full status shine-server caddy
|
|
||||||
```
|
|
||||||
@@ -35,7 +35,7 @@
|
|||||||
"blockchainName": "anya-001",
|
"blockchainName": "anya-001",
|
||||||
"solanaKey": "BASE64_32_PUBLIC_KEY",
|
"solanaKey": "BASE64_32_PUBLIC_KEY",
|
||||||
"blockchainKey": "BASE64_32_PUBLIC_KEY",
|
"blockchainKey": "BASE64_32_PUBLIC_KEY",
|
||||||
"clientKey": "BASE64_32_PUBLIC_KEY",
|
"deviceKey": "BASE64_32_PUBLIC_KEY",
|
||||||
"bchLimit": 1000000
|
"bchLimit": 1000000
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -71,8 +71,7 @@
|
|||||||
Важно:
|
Важно:
|
||||||
|
|
||||||
- это server-side existence-check;
|
- это server-side existence-check;
|
||||||
- если пользователя нет в локальной БД, сервер сразу пытается lazy-import из Solana PDA;
|
- если пользователя нет в локальной БД, он может быть импортирован при авторизации из Solana PDA.
|
||||||
- поэтому `GetUser` можно использовать как актуальный способ получить `clientKey` и базовые поля пользователя перед E2EE DM.
|
|
||||||
|
|
||||||
### Запрос
|
### Запрос
|
||||||
|
|
||||||
@@ -100,7 +99,7 @@
|
|||||||
"blockchainName": "anya-001",
|
"blockchainName": "anya-001",
|
||||||
"solanaKey": "BASE64_32_PUBLIC_KEY",
|
"solanaKey": "BASE64_32_PUBLIC_KEY",
|
||||||
"blockchainKey": "BASE64_32_PUBLIC_KEY",
|
"blockchainKey": "BASE64_32_PUBLIC_KEY",
|
||||||
"clientKey": "BASE64_32_PUBLIC_KEY",
|
"deviceKey": "BASE64_32_PUBLIC_KEY",
|
||||||
"serverLastGlobalNumber": 128,
|
"serverLastGlobalNumber": 128,
|
||||||
"serverLastGlobalHash": "4f...ab",
|
"serverLastGlobalHash": "4f...ab",
|
||||||
"serverBlockchainSizeBytes": 45212,
|
"serverBlockchainSizeBytes": 45212,
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
Этот файл описывает именно этапы авторизации клиента, то есть как создать новую сессию и как войти в уже существующую.
|
Этот файл описывает именно этапы авторизации клиента, то есть как создать новую сессию и как войти в уже существующую.
|
||||||
|
|
||||||
Здесь четыре базовых метода обычной авторизации:
|
Здесь четыре метода:
|
||||||
|
|
||||||
- `AuthChallenge`
|
- `AuthChallenge`
|
||||||
- `CreateAuthSession`
|
- `CreateAuthSession`
|
||||||
@@ -11,41 +11,14 @@
|
|||||||
|
|
||||||
Логика раздела такая:
|
Логика раздела такая:
|
||||||
|
|
||||||
- сначала клиент либо начинает создание новой сессии через `clientKey`;
|
- сначала клиент либо начинает создание новой сессии через `deviceKey`;
|
||||||
- либо начинает вход в уже созданную сессию через `sessionKey`;
|
- либо начинает вход в уже созданную сессию через `sessionKey`;
|
||||||
- сервер на первом шаге выдаёт challenge/nonce;
|
- сервер на первом шаге выдаёт challenge/nonce;
|
||||||
- на втором шаге клиент присылает подписанный ответ;
|
- на втором шаге клиент присылает подписанный ответ;
|
||||||
- сервер сверяет актуальные публичные ключи и только потом проверяет подпись.
|
- сервер сверяет актуальные публичные ключи и только потом проверяет подпись.
|
||||||
|
|
||||||
Новые поля этого раздела:
|
|
||||||
|
|
||||||
- `sessionType` — числовой код типа сессии;
|
|
||||||
- `clientPlatform` — свободная строка платформы клиента.
|
|
||||||
|
|
||||||
Текущие поддерживаемые коды `sessionType`:
|
|
||||||
|
|
||||||
- `1` — обычный клиент;
|
|
||||||
- `50` — кошелёк;
|
|
||||||
- `100` — homeserver.
|
|
||||||
|
|
||||||
Правило проверки `sessionType`:
|
|
||||||
|
|
||||||
1. если в `Solana PDA` нет записи для `sessionKey`, сервер принимает `sessionType`, присланный клиентом;
|
|
||||||
2. если запись в `PDA` есть, `sessionType` в запросе должен совпадать с `session_type` из `PDA`;
|
|
||||||
3. при несовпадении сервер возвращает `460 / SESSION_TYPE_MISMATCH`.
|
|
||||||
|
|
||||||
Ниже в документе сначала описан сценарий, а потом зафиксированы точные форматы запросов и ответов.
|
Ниже в документе сначала описан сценарий, а потом зафиксированы точные форматы запросов и ответов.
|
||||||
|
|
||||||
Отдельно появился новый серверный сценарий pairing через доверенный homeserver/ESP. Он не заменяет обычный вход и описан в:
|
|
||||||
|
|
||||||
- `Dev_Docs/Протоколы/ESP_Pairing_и_режимы_подключения.md`
|
|
||||||
|
|
||||||
Кратко:
|
|
||||||
|
|
||||||
- `AuthChallenge/CreateAuthSession` и `SessionChallenge/SessionLogin` остаются каноническими потоками обычной авторизации;
|
|
||||||
- pairing через ESP идёт отдельными `op` и только подготавливает безопасное добавление новой сессии;
|
|
||||||
- решение об одобрении pairing принимает любая уже авторизованная доверенная сессия пользователя.
|
|
||||||
|
|
||||||
## 1. Поток авторизации
|
## 1. Поток авторизации
|
||||||
|
|
||||||
Поддерживаются два сценария:
|
Поддерживаются два сценария:
|
||||||
@@ -55,7 +28,7 @@
|
|||||||
2. Вход в существующую сессию:
|
2. Вход в существующую сессию:
|
||||||
`SessionChallenge` -> `SessionLogin`
|
`SessionChallenge` -> `SessionLogin`
|
||||||
|
|
||||||
`clientKey` используется для создания новой сессии.
|
`deviceKey` используется для создания новой сессии.
|
||||||
|
|
||||||
`sessionKey` используется для входа в уже созданную сессию.
|
`sessionKey` используется для входа в уже созданную сессию.
|
||||||
|
|
||||||
@@ -119,10 +92,8 @@ ed25519/BASE64_PUBLIC_KEY
|
|||||||
"storagePwd": "BASE64_OR_APP_SPECIFIC_SECRET",
|
"storagePwd": "BASE64_OR_APP_SPECIFIC_SECRET",
|
||||||
"timeMs": 1774600000123,
|
"timeMs": 1774600000123,
|
||||||
"authNonce": "nonce",
|
"authNonce": "nonce",
|
||||||
"clientKey": "BASE64_DEVICE_PUBLIC_KEY",
|
"deviceKey": "BASE64_DEVICE_PUBLIC_KEY",
|
||||||
"signatureB64": "BASE64_SIGNATURE",
|
"signatureB64": "BASE64_SIGNATURE",
|
||||||
"sessionType": 1,
|
|
||||||
"clientPlatform": "Web",
|
|
||||||
"clientInfo": "Android 15; Pixel 9"
|
"clientInfo": "Android 15; Pixel 9"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -138,15 +109,15 @@ AUTH_CREATE_SESSION:{login}:{sessionKey}:{storagePwd}:{timeMs}:{authNonce}
|
|||||||
|
|
||||||
Перед проверкой подписи сервер должен:
|
Перед проверкой подписи сервер должен:
|
||||||
|
|
||||||
1. взять актуальный `solana_users.client_key`;
|
1. взять актуальный `solana_users.device_key`;
|
||||||
2. сравнить его с `payload.clientKey`;
|
2. сравнить его с `payload.deviceKey`;
|
||||||
3. только потом проверять подпись.
|
3. только потом проверять подпись.
|
||||||
|
|
||||||
Если `clientKey` не совпадает, сервер возвращает ошибку `DEVICE_KEY_NOT_ACTUAL`.
|
Если ключ не совпадает, сервер возвращает ошибку `DEVICE_KEY_NOT_ACTUAL`.
|
||||||
|
|
||||||
На будущее:
|
На будущее:
|
||||||
|
|
||||||
- для ротации `client_key` желательно добавить перепроверку через Solana.
|
- для ротации `device_key` желательно добавить перепроверку через Solana.
|
||||||
|
|
||||||
### Успешный ответ
|
### Успешный ответ
|
||||||
|
|
||||||
@@ -172,18 +143,16 @@ AUTH_CREATE_SESSION:{login}:{sessionKey}:{storagePwd}:{timeMs}:{authNonce}
|
|||||||
- `501 / NO_LOGIN` — у пользователя на сервере не заполнен `login`.
|
- `501 / NO_LOGIN` — у пользователя на сервере не заполнен `login`.
|
||||||
- `400 / EMPTY_STORAGE_PWD` — пустой `storagePwd`.
|
- `400 / EMPTY_STORAGE_PWD` — пустой `storagePwd`.
|
||||||
- `400 / EMPTY_SESSION_KEY` — пустой `sessionKey`.
|
- `400 / EMPTY_SESSION_KEY` — пустой `sessionKey`.
|
||||||
- `422 / UNSUPPORTED_KEY_ALGORITHM` — префикс алгоритма в `sessionKey` или `clientKey` не поддерживается текущим сервером.
|
- `422 / UNSUPPORTED_KEY_ALGORITHM` — префикс алгоритма в `sessionKey` или `deviceKey` не поддерживается текущим сервером.
|
||||||
- `400 / BAD_BASE64` — неверный Base64 в `sessionKey`, `clientKey` или `signatureB64`.
|
- `400 / BAD_BASE64` — неверный Base64 в `sessionKey`, `deviceKey` или `signatureB64`.
|
||||||
- `400 / EMPTY_SIGNATURE` — пустая подпись.
|
- `400 / EMPTY_SIGNATURE` — пустая подпись.
|
||||||
- `400 / TIME_SKEW` — время клиента отличается от серверного больше допустимого окна.
|
- `400 / TIME_SKEW` — время клиента отличается от серверного больше допустимого окна.
|
||||||
- `400 / NO_DEVICE_KEY` — у пользователя в БД отсутствует `clientKey`.
|
- `400 / NO_DEVICE_KEY` — у пользователя в БД отсутствует `deviceKey`.
|
||||||
- `400 / EMPTY_AUTH_NONCE` — пустой `authNonce`.
|
- `400 / EMPTY_AUTH_NONCE` — пустой `authNonce`.
|
||||||
- `400 / AUTH_NONCE_MISMATCH` — `authNonce` не соответствует значению из `AuthChallenge`.
|
- `400 / AUTH_NONCE_MISMATCH` — `authNonce` не соответствует значению из `AuthChallenge`.
|
||||||
- `400 / EMPTY_DEVICE_KEY` — в запросе не передан `clientKey`.
|
- `400 / EMPTY_DEVICE_KEY` — в запросе не передан `deviceKey`.
|
||||||
- `422 / DEVICE_KEY_NOT_ACTUAL` — `clientKey` не совпадает с актуальной версией на сервере.
|
- `422 / DEVICE_KEY_NOT_ACTUAL` — `deviceKey` не совпадает с актуальной версией на сервере.
|
||||||
- `422 / BAD_SIGNATURE` — подпись не прошла проверку.
|
- `422 / BAD_SIGNATURE` — подпись не прошла проверку.
|
||||||
- `460 / SESSION_TYPE_MISMATCH` — `sessionType` не совпадает с типом сессии, уже опубликованным для этого `sessionKey` в Solana PDA.
|
|
||||||
- `501 / SESSION_TYPE_PDA_CHECK_FAILED` — сервер не смог проверить `sessionType` по Solana PDA.
|
|
||||||
- `501 / DB_ERROR_SESSION_CREATE` — ошибка БД при создании записи активной сессии.
|
- `501 / DB_ERROR_SESSION_CREATE` — ошибка БД при создании записи активной сессии.
|
||||||
- `500 / INTERNAL_ERROR` — непредвиденная внутренняя ошибка сервера.
|
- `500 / INTERNAL_ERROR` — непредвиденная внутренняя ошибка сервера.
|
||||||
|
|
||||||
@@ -239,8 +208,6 @@ AUTH_CREATE_SESSION:{login}:{sessionKey}:{storagePwd}:{timeMs}:{authNonce}
|
|||||||
"sessionKey": "ed25519/BASE64_PUBLIC_KEY",
|
"sessionKey": "ed25519/BASE64_PUBLIC_KEY",
|
||||||
"timeMs": 1774600010456,
|
"timeMs": 1774600010456,
|
||||||
"signatureB64": "BASE64_SIGNATURE",
|
"signatureB64": "BASE64_SIGNATURE",
|
||||||
"sessionType": 1,
|
|
||||||
"clientPlatform": "Web",
|
|
||||||
"clientInfo": "Android 15; Pixel 9"
|
"clientInfo": "Android 15; Pixel 9"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -291,40 +258,12 @@ SESSION_LOGIN:{sessionId}:{timeMs}:{nonce}
|
|||||||
- `422 / UNSUPPORTED_KEY_ALGORITHM` — префикс алгоритма в `sessionKey` не поддерживается текущим сервером.
|
- `422 / UNSUPPORTED_KEY_ALGORITHM` — префикс алгоритма в `sessionKey` не поддерживается текущим сервером.
|
||||||
- `400 / BAD_BASE64` — неверный Base64 в `sessionKey` или `signatureB64`.
|
- `400 / BAD_BASE64` — неверный Base64 в `sessionKey` или `signatureB64`.
|
||||||
- `422 / BAD_SIGNATURE` — подпись не прошла проверку.
|
- `422 / BAD_SIGNATURE` — подпись не прошла проверку.
|
||||||
- `460 / SESSION_TYPE_MISMATCH` — `sessionType` не совпадает с типом сессии, уже опубликованным для этого `sessionKey` в Solana PDA.
|
|
||||||
- `501 / SESSION_TYPE_PDA_CHECK_FAILED` — сервер не смог проверить `sessionType` по Solana PDA.
|
|
||||||
- `501 / DB_ERROR_USER_LOOKUP` — ошибка БД при чтении пользователя для этой сессии.
|
- `501 / DB_ERROR_USER_LOOKUP` — ошибка БД при чтении пользователя для этой сессии.
|
||||||
- `422 / USER_NOT_FOUND_FOR_SESSION` — пользователь, которому принадлежит сессия, не найден.
|
- `422 / USER_NOT_FOUND_FOR_SESSION` — пользователь, которому принадлежит сессия, не найден.
|
||||||
- `500 / INTERNAL_ERROR` — непредвиденная внутренняя ошибка сервера.
|
- `500 / INTERNAL_ERROR` — непредвиденная внутренняя ошибка сервера.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 6. Pairing через homeserver/ESP
|
|
||||||
|
|
||||||
Новые `op`, относящиеся к этому сценарию:
|
|
||||||
|
|
||||||
- `GetTrustedDeviceLoginSettings`
|
|
||||||
- `UpsertTrustedDeviceLoginSettings`
|
|
||||||
- `StartTrustedDeviceLogin`
|
|
||||||
- `ListTrustedDeviceLoginRequests`
|
|
||||||
- `ApproveTrustedDeviceLogin`
|
|
||||||
- `RejectTrustedDeviceLogin`
|
|
||||||
- `CancelTrustedDeviceLogin`
|
|
||||||
- `GetTrustedDeviceLoginStatus`
|
|
||||||
|
|
||||||
В этом потоке:
|
|
||||||
|
|
||||||
- новое устройство не владеет `clientKey` и не проходит обычный `CreateAuthSession`;
|
|
||||||
- пароль проверяется сервером только как фильтр;
|
|
||||||
- решение об одобрении принимает уже авторизованная доверенная сессия пользователя;
|
|
||||||
- сервер не расшифровывает `encryptedPayload` и не становится источником приватных ключей.
|
|
||||||
|
|
||||||
Точные форматы этих операций см. в `03_Session_Management_API.md` и в протокольном документе:
|
|
||||||
|
|
||||||
- `Dev_Docs/Протоколы/ESP_Pairing_и_режимы_подключения.md`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 6. Пример ошибки
|
## 6. Пример ошибки
|
||||||
|
|
||||||
```json
|
```json
|
||||||
@@ -0,0 +1,136 @@
|
|||||||
|
# API для разработчиков: Управление сессиями
|
||||||
|
|
||||||
|
Этот файл описывает методы, которые используются уже после успешной авторизации пользователя в сессию.
|
||||||
|
|
||||||
|
Здесь два метода:
|
||||||
|
|
||||||
|
- `ListSessions` — получить список активных сессий пользователя;
|
||||||
|
- `CloseActiveSession` — закрыть одну из активных сессий.
|
||||||
|
|
||||||
|
Логика раздела такая:
|
||||||
|
|
||||||
|
- сначала пользователь проходит `SessionLogin`;
|
||||||
|
- после этого сервер считает соединение авторизованным;
|
||||||
|
- уже в этом состоянии клиент может читать список сессий и управлять ими.
|
||||||
|
|
||||||
|
То есть это не этап создания или входа в сессию, а этап последующего контроля уже существующих активных сессий.
|
||||||
|
|
||||||
|
## 1. `ListSessions`
|
||||||
|
|
||||||
|
Доступно только после успешного `SessionLogin`.
|
||||||
|
|
||||||
|
### Запрос
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"op": "ListSessions",
|
||||||
|
"requestId": "list-001",
|
||||||
|
"payload": {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Успешный ответ
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"op": "ListSessions",
|
||||||
|
"requestId": "list-001",
|
||||||
|
"status": 200,
|
||||||
|
"ok": true,
|
||||||
|
"payload": {
|
||||||
|
"sessions": [
|
||||||
|
{
|
||||||
|
"sessionId": "sess_7c5e5c4b",
|
||||||
|
"clientInfoFromClient": "Android 15; Pixel 9",
|
||||||
|
"clientInfoFromRequest": "UA=Java-http-client/17.0.18; remote=127.0.0.1",
|
||||||
|
"geo": "RU/Moscow",
|
||||||
|
"lastAuthenticatedAtMs": 1774600010500
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Специфические коды ошибок `ListSessions`
|
||||||
|
|
||||||
|
- `422 / NOT_AUTHENTICATED` — запрос доступен только после успешного `SessionLogin`.
|
||||||
|
- `501 / DB_ERROR_LIST_SESSIONS` — ошибка БД при чтении списка активных сессий.
|
||||||
|
- `500 / INTERNAL_ERROR` — непредвиденная внутренняя ошибка сервера.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. `CloseActiveSession`
|
||||||
|
|
||||||
|
Доступно только после успешного `SessionLogin`.
|
||||||
|
|
||||||
|
### Запрос
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"op": "CloseActiveSession",
|
||||||
|
"requestId": "close-001",
|
||||||
|
"payload": {
|
||||||
|
"sessionId": "sess_7c5e5c4b"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Успешный ответ
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"op": "CloseActiveSession",
|
||||||
|
"requestId": "close-001",
|
||||||
|
"status": 200,
|
||||||
|
"ok": true,
|
||||||
|
"payload": {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Специфические коды ошибок `CloseActiveSession`
|
||||||
|
|
||||||
|
- `422 / NOT_AUTHENTICATED` — запрос доступен только после успешного `SessionLogin`.
|
||||||
|
- `400 / NO_SESSION_TO_CLOSE` — сервер не смог определить, какую сессию нужно закрыть.
|
||||||
|
- `501 / DB_ERROR` — ошибка БД при поиске сессии или её удалении.
|
||||||
|
- `422 / SESSION_NOT_FOUND` — целевая сессия не найдена.
|
||||||
|
- `422 / SESSION_OF_ANOTHER_USER` — нельзя закрывать сессию другого пользователя.
|
||||||
|
- `500 / INTERNAL_ERROR` — непредвиденная внутренняя ошибка сервера.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Пример ошибки
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"op": "CloseActiveSession",
|
||||||
|
"requestId": "close-001",
|
||||||
|
"status": 403,
|
||||||
|
"ok": false,
|
||||||
|
"error": "NOT_AUTHENTICATED",
|
||||||
|
"message": "Операция доступна только для авторизованных пользователей",
|
||||||
|
"payload": {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## 4. Формат `sessionId`
|
||||||
|
|
||||||
|
Текущее серверное значение `sessionId` генерируется как:
|
||||||
|
|
||||||
|
- случайные **32 байта** (`SecureRandom`),
|
||||||
|
- кодирование в **стандартный Base64 RFC 4648** (алфавит `A-Z a-z 0-9 + /`),
|
||||||
|
- **без padding** `=`.
|
||||||
|
|
||||||
|
Практически это строка длиной около **43 символов** (для 32 байт без `=`).
|
||||||
|
|
||||||
|
Пример реального формата:
|
||||||
|
|
||||||
|
```
|
||||||
|
K9v3nQ4u8jYk0a2p7cD4mLx1zR0sT5wV6bN8eH3fQ1M
|
||||||
|
```
|
||||||
|
|
||||||
|
Важно: это **не человеко-читаемое имя**, а непрозрачный идентификатор.
|
||||||
|
Нужно передавать его как есть, без нормализации регистра и без URL-экранирования внутри JSON.
|
||||||
+2
-65
@@ -1,11 +1,6 @@
|
|||||||
# API для разработчиков: 04 — Запись и чтение блока блокчейна
|
# API для разработчиков: 04 — Добавление блока в блокчейн (AddBlock)
|
||||||
|
|
||||||
Документ описывает **текущий рабочий формат** сетевых вызовов:
|
Документ описывает **текущий рабочий формат** сетевого вызова `AddBlock`, который используется для записи **любого** блока в блокчейн пользователя.
|
||||||
|
|
||||||
- `AddBlock` — запись любого блока в блокчейн пользователя;
|
|
||||||
- `GetBlockchainBlock` — публичное чтение одного конкретного блока по имени цепочки и номеру.
|
|
||||||
|
|
||||||
`GetBlockchainBlock` нужен в том числе для межсерверной синхронизации и для открытого чтения публичного блокчейна по одному блоку.
|
|
||||||
|
|
||||||
> Важный принцип: на уровне JSON API сейчас есть **один универсальный метод** записи — `AddBlock`.
|
> Важный принцип: на уровне JSON API сейчас есть **один универсальный метод** записи — `AddBlock`.
|
||||||
> Конкретный смысл записи задаётся типом самого бинарного блока (`type/subType/version` в заголовке блока).
|
> Конкретный смысл записи задаётся типом самого бинарного блока (`type/subType/version` в заголовке блока).
|
||||||
@@ -86,7 +81,6 @@
|
|||||||
- `bad_signature`, `signature_verify_failed`
|
- `bad_signature`, `signature_verify_failed`
|
||||||
- `prev_line_block_not_found`, `bad_prev_line_hash`
|
- `prev_line_block_not_found`, `bad_prev_line_hash`
|
||||||
- `limit_exceeded`
|
- `limit_exceeded`
|
||||||
- `chain_resync_in_progress` — цепочка временно заблокирована полным resync
|
|
||||||
- `repost_disabled` — репосты временно отключены до будущей реализации
|
- `repost_disabled` — репосты временно отключены до будущей реализации
|
||||||
- `internal_error`
|
- `internal_error`
|
||||||
|
|
||||||
@@ -180,60 +174,3 @@
|
|||||||
- сейчас нет серверной ACL-политики чтения параметров (в MVP их может читать любой клиент, который знает `login`);
|
- сейчас нет серверной ACL-политики чтения параметров (в MVP их может читать любой клиент, который знает `login`);
|
||||||
- нет валидации формата значений для конкретных ключей (телефон, URL и т.д. проверяются только на стороне клиента);
|
- нет валидации формата значений для конкретных ключей (телефон, URL и т.д. проверяются только на стороне клиента);
|
||||||
- нет отдельного индекса/поиска по этим полям — только точечное чтение и listing по `login`.
|
- нет отдельного индекса/поиска по этим полям — только точечное чтение и listing по `login`.
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 9. `GetBlockchainBlock`
|
|
||||||
|
|
||||||
### Назначение
|
|
||||||
|
|
||||||
Публичное чтение одного конкретного блока из цепочки.
|
|
||||||
|
|
||||||
Нужно для:
|
|
||||||
|
|
||||||
- открытого чтения блокчейна по одному блоку;
|
|
||||||
- межсерверной синхронизации;
|
|
||||||
- восстановления/докачки отсутствующего хвоста цепочки.
|
|
||||||
|
|
||||||
### JSON формат запроса
|
|
||||||
|
|
||||||
`op = "GetBlockchainBlock"`.
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"op": "GetBlockchainBlock",
|
|
||||||
"requestId": "req-2001",
|
|
||||||
"payload": {
|
|
||||||
"blockchainName": "alice-001",
|
|
||||||
"blockNumber": 12
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Поля `payload`:
|
|
||||||
|
|
||||||
- `blockchainName` — обязательно, формат `login-NNN`.
|
|
||||||
- `blockNumber` — обязательно, номер блока в цепочке, `>= 0`.
|
|
||||||
|
|
||||||
### Успешный ответ
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"op": "GetBlockchainBlock",
|
|
||||||
"requestId": "req-2001",
|
|
||||||
"status": 200,
|
|
||||||
"ok": true,
|
|
||||||
"payload": {
|
|
||||||
"blockchainName": "alice-001",
|
|
||||||
"blockNumber": 12,
|
|
||||||
"blockHash": "9f0eaabbccddeeff00112233445566778899aabbccddeeff0011223344556677",
|
|
||||||
"blockBytesB64": "AAAB..."
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Ошибки
|
|
||||||
|
|
||||||
- `400 / BAD_FIELDS` — некорректные `blockchainName` или `blockNumber`.
|
|
||||||
- `404 / BLOCK_NOT_FOUND` — такого блока нет.
|
|
||||||
- `500 / INTERNAL_ERROR` — внутренняя ошибка сервера.
|
|
||||||
@@ -0,0 +1,333 @@
|
|||||||
|
# API для разработчиков: Технические запросы
|
||||||
|
|
||||||
|
Этот файл описывает технические WebSocket-запросы, которые нужны для служебной работы клиента с сервером. Часть операций доступна без авторизации, часть требует успешной авторизованной сессии.
|
||||||
|
|
||||||
|
Сейчас здесь шесть методов:
|
||||||
|
|
||||||
|
- `Ping` — keep-alive запрос для поддержания живого WebSocket-соединения;
|
||||||
|
- `GetServerInfo` — запрос базовой публичной информации о сервере для выбора узла в децентрализованной сети;
|
||||||
|
- `GetCallIceConfig` — выдача STUN/TURN конфигурации для звонков;
|
||||||
|
- `ClientErrorLog` — отправка клиентской ошибки в серверный лог;
|
||||||
|
- `ClientDebugLog` — отправка клиентского debug-события в серверный буфер;
|
||||||
|
- `CallDeliveryReport` — диагностический отчёт клиента о доставке/установке звонка.
|
||||||
|
|
||||||
|
Логика раздела такая:
|
||||||
|
|
||||||
|
- `Ping` нужен для регулярной проверки, что соединение всё ещё живо;
|
||||||
|
- `GetServerInfo` нужен до авторизации и до работы с данными, чтобы клиент понял, что сервер доступен, и показал пользователю краткую карточку этого узла.
|
||||||
|
|
||||||
|
Ниже сначала описаны назначение методов, затем точные форматы запросов и ответов.
|
||||||
|
|
||||||
|
## 1. `Ping`
|
||||||
|
|
||||||
|
### Назначение
|
||||||
|
|
||||||
|
Служебный keep-alive запрос.
|
||||||
|
|
||||||
|
Клиент может отправлять его периодически, чтобы:
|
||||||
|
|
||||||
|
- поддерживать активное WebSocket-соединение;
|
||||||
|
- понимать, что сервер отвечает;
|
||||||
|
- при необходимости получать текущее серверное время.
|
||||||
|
|
||||||
|
### Запрос
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"op": "Ping",
|
||||||
|
"requestId": "ping-001",
|
||||||
|
"payload": {
|
||||||
|
"ts": 1774700000123
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Поле `ts` в запросе необязательно для логики сервера. Сервер его не валидирует и не использует для принятия решения.
|
||||||
|
|
||||||
|
### Успешный ответ
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"op": "Ping",
|
||||||
|
"requestId": "ping-001",
|
||||||
|
"status": 200,
|
||||||
|
"ok": true,
|
||||||
|
"payload": {
|
||||||
|
"ts": 1774700000456
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Специфические коды ошибок `Ping`
|
||||||
|
|
||||||
|
- У `Ping` нет специальных прикладных ошибок.
|
||||||
|
- Если произойдёт непредвиденная проблема, сервер вернёт общую ошибку из раздела `00`, обычно `500 / INTERNAL_ERROR`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. `GetServerInfo`
|
||||||
|
|
||||||
|
### Назначение
|
||||||
|
|
||||||
|
Запрос публичной информации о сервере.
|
||||||
|
|
||||||
|
Он нужен клиенту для выбора сервера в децентрализованной сети. По этому запросу клиент может:
|
||||||
|
|
||||||
|
- проверить, что сервер вообще доступен;
|
||||||
|
- показать URL и версию сервера;
|
||||||
|
- показать физический регион или адрес размещения;
|
||||||
|
- показать описание сервера;
|
||||||
|
- показать поле `origin` как комментарий о природе этого узла;
|
||||||
|
- показать дополнительную текстовую информацию.
|
||||||
|
|
||||||
|
Этот запрос доступен без авторизации.
|
||||||
|
|
||||||
|
### Источник данных
|
||||||
|
|
||||||
|
- `version` берётся из Gradle build и подставляется в `application.properties`;
|
||||||
|
- остальные поля читаются из настроек сервера;
|
||||||
|
- если значение в конфиге не задано, сервер возвращает пустую строку.
|
||||||
|
|
||||||
|
### Запрос
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"op": "GetServerInfo",
|
||||||
|
"requestId": "srv-001",
|
||||||
|
"payload": {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Успешный ответ
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"op": "GetServerInfo",
|
||||||
|
"requestId": "srv-001",
|
||||||
|
"status": 200,
|
||||||
|
"ok": true,
|
||||||
|
"payload": {
|
||||||
|
"url": "wss://node.example.org/ws",
|
||||||
|
"version": "1.0",
|
||||||
|
"physicalRegion": "Грузия, Тбилиси",
|
||||||
|
"description": "Public community SHiNE node",
|
||||||
|
"origin": "Community-operated node",
|
||||||
|
"extraInfo": "IPv4 + IPv6; test federation enabled"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Поля ответа
|
||||||
|
|
||||||
|
- `url` — публичный URL сервера.
|
||||||
|
- `version` — версия сервера из Gradle build.
|
||||||
|
- `physicalRegion` — физический регион или адрес размещения сервера.
|
||||||
|
- `description` — человекочитаемое описание сервера.
|
||||||
|
- `origin` — комментарий о том, какой это сервер.
|
||||||
|
- `extraInfo` — любая дополнительная информация о сервере.
|
||||||
|
|
||||||
|
### Специфические коды ошибок `GetServerInfo`
|
||||||
|
|
||||||
|
- У `GetServerInfo` нет специальных прикладных ошибок при штатной работе.
|
||||||
|
- Если произойдёт непредвиденная проблема, сервер вернёт общую ошибку из раздела `00`, обычно `500 / INTERNAL_ERROR`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. `GetCallIceConfig`
|
||||||
|
|
||||||
|
Доступно только после успешной авторизации.
|
||||||
|
|
||||||
|
### Запрос
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"op": "GetCallIceConfig",
|
||||||
|
"requestId": "ice-001",
|
||||||
|
"payload": {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Успешный ответ
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"op": "GetCallIceConfig",
|
||||||
|
"requestId": "ice-001",
|
||||||
|
"status": 200,
|
||||||
|
"ok": true,
|
||||||
|
"payload": {
|
||||||
|
"stunUrls": ["stun:stun.example.org:3478"],
|
||||||
|
"turnUrls": ["turn:turn.example.org:3478?transport=udp"],
|
||||||
|
"turnUsername": "user",
|
||||||
|
"turnPassword": "password",
|
||||||
|
"turnServers": [
|
||||||
|
{
|
||||||
|
"id": "primary",
|
||||||
|
"urls": ["turn:turn.example.org:3478?transport=udp"],
|
||||||
|
"username": "user",
|
||||||
|
"password": "password"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"turnEnabled": true,
|
||||||
|
"generatedAtMs": 1774700000123,
|
||||||
|
"expiresAtMs": 1774700300123,
|
||||||
|
"ttlSec": 300
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Специфические коды ошибок `GetCallIceConfig`
|
||||||
|
|
||||||
|
- `422 / NOT_AUTHENTICATED` — требуется авторизация.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. `ClientErrorLog`
|
||||||
|
|
||||||
|
### Запрос
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"op": "ClientErrorLog",
|
||||||
|
"requestId": "err-001",
|
||||||
|
"payload": {
|
||||||
|
"kind": "global_error",
|
||||||
|
"message": "TypeError: failed",
|
||||||
|
"stack": "...",
|
||||||
|
"sourceUrl": "https://shineup.me/app.js",
|
||||||
|
"lineNumber": 10,
|
||||||
|
"columnNumber": 20,
|
||||||
|
"route": "#/channel-view/own-0",
|
||||||
|
"href": "https://shineup.me/#/channel-view/own-0",
|
||||||
|
"userAgent": "...",
|
||||||
|
"clientTs": 1774700000123,
|
||||||
|
"requestOp": "GetChannelMessages",
|
||||||
|
"requestIdRef": "GetChannelMessages-123",
|
||||||
|
"contextJson": "{\"screen\":\"channels\"}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Успешный ответ
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"op": "ClientErrorLog",
|
||||||
|
"requestId": "err-001",
|
||||||
|
"status": 200,
|
||||||
|
"ok": true,
|
||||||
|
"payload": {
|
||||||
|
"serverTs": 1774700000456,
|
||||||
|
"accepted": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Специфические коды ошибок `ClientErrorLog`
|
||||||
|
|
||||||
|
- `400 / BAD_FIELDS` — обязательные поля ошибки не заполнены.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. `ClientDebugLog`
|
||||||
|
|
||||||
|
### Запрос
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"op": "ClientDebugLog",
|
||||||
|
"requestId": "dbg-001",
|
||||||
|
"payload": {
|
||||||
|
"runId": "ui-run-1",
|
||||||
|
"level": "info",
|
||||||
|
"message": "opened channels tab",
|
||||||
|
"details": "{\"route\":\"#/channels\"}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Успешный ответ
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"op": "ClientDebugLog",
|
||||||
|
"requestId": "dbg-001",
|
||||||
|
"status": 200,
|
||||||
|
"ok": true,
|
||||||
|
"payload": {
|
||||||
|
"accepted": true,
|
||||||
|
"serverTs": 1774700000456
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Специфические коды ошибок `ClientDebugLog`
|
||||||
|
|
||||||
|
- `400 / BAD_FIELDS` — поле `message` не заполнено.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. `CallDeliveryReport`
|
||||||
|
|
||||||
|
### Запрос
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"op": "CallDeliveryReport",
|
||||||
|
"requestId": "call-report-001",
|
||||||
|
"payload": {
|
||||||
|
"type": "outgoing_failed",
|
||||||
|
"value": "{\"reason\":\"ice_failed\",\"callId\":\"call-1\"}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Успешный ответ
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"op": "CallDeliveryReport",
|
||||||
|
"requestId": "call-report-001",
|
||||||
|
"status": 200,
|
||||||
|
"ok": true,
|
||||||
|
"payload": {
|
||||||
|
"serverTs": 1774700000456,
|
||||||
|
"accepted": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Специфические коды ошибок `CallDeliveryReport`
|
||||||
|
|
||||||
|
- `400 / BAD_FIELDS` — поле `type` не заполнено.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. Короткое резюме
|
||||||
|
|
||||||
|
- `Ping` нужен для keep-alive и проверки, что WebSocket-соединение живо.
|
||||||
|
- `GetServerInfo` нужен для выбора сервера в сети и показа публичной информации об узле.
|
||||||
|
- `GetCallIceConfig` нужен для WebRTC-звонков и требует авторизации.
|
||||||
|
- `ClientErrorLog`, `ClientDebugLog`, `CallDeliveryReport` используются для диагностики клиента и звонков.
|
||||||
|
|
||||||
|
|
||||||
|
## 8. Прямое техническое сообщение в конкретную сессию
|
||||||
|
|
||||||
|
На текущий момент в публичном JSON API этого документа **нет отдельного RPC** для отправки произвольного технического сообщения в конкретную сессию пользователя (по `sessionId`).
|
||||||
|
|
||||||
|
Что уже есть в системе:
|
||||||
|
|
||||||
|
- сервер хранит `sessionId` активной сессии;
|
||||||
|
- есть `ListSessions`, чтобы клиент получил список sessionId своего пользователя;
|
||||||
|
- у сервера есть внутренний реестр активных WS-подключений по `sessionId`.
|
||||||
|
|
||||||
|
Чего не хватает для полноценной фичи «direct tech message by sessionId»:
|
||||||
|
|
||||||
|
1. отдельная API-операция (например, `SendSessionTechMessage`);
|
||||||
|
2. правило авторизации (кто имеет право писать в чужую/свою сессию);
|
||||||
|
3. унифицированный формат payload и события доставки;
|
||||||
|
4. коды ошибок (`SESSION_OFFLINE`, `SESSION_NOT_FOUND`, `FORBIDDEN` и т.п.).
|
||||||
|
|
||||||
|
Итог: как инфраструктурная база это почти готово, но нужен отдельный RPC-слой и политика доступа.
|
||||||
@@ -15,29 +15,15 @@
|
|||||||
| `AddUser` | `01_User_Registration_API.md` | отключено (`410 / ADD_USER_DISABLED`) |
|
| `AddUser` | `01_User_Registration_API.md` | отключено (`410 / ADD_USER_DISABLED`) |
|
||||||
| `GetUser` | `01_User_Registration_API.md` | чтение/проверка пользователя + server-состояние его блокчейна |
|
| `GetUser` | `01_User_Registration_API.md` | чтение/проверка пользователя + server-состояние его блокчейна |
|
||||||
| `SearchUsers` | `01_User_Registration_API.md` | поиск логинов по префиксу |
|
| `SearchUsers` | `01_User_Registration_API.md` | поиск логинов по префиксу |
|
||||||
| `TestGetFreeAvatarQuota` | `14_Test_Free_Avatar_Upload_API.md` | временный тестовый просмотр остатка бесплатных загрузок аватара |
|
|
||||||
| `TestUploadFreeAvatar` | `14_Test_Free_Avatar_Upload_API.md` | временная тестовая бесплатная загрузка маленького аватара в Arweave |
|
|
||||||
| `AuthChallenge` | `02_Authentication_API.md` | challenge для создания новой сессии |
|
| `AuthChallenge` | `02_Authentication_API.md` | challenge для создания новой сессии |
|
||||||
| `CreateAuthSession` | `02_Authentication_API.md` | создание новой авторизованной сессии |
|
| `CreateAuthSession` | `02_Authentication_API.md` | создание новой авторизованной сессии |
|
||||||
| `SessionChallenge` | `02_Authentication_API.md` | challenge для входа в существующую сессию |
|
| `SessionChallenge` | `02_Authentication_API.md` | challenge для входа в существующую сессию |
|
||||||
| `SessionLogin` | `02_Authentication_API.md` | вход в существующую сессию |
|
| `SessionLogin` | `02_Authentication_API.md` | вход в существующую сессию |
|
||||||
| `GetTrustedDeviceLoginSettings` | `03_Session_Management_API.md` | чтение текущего режима входа через доверенное устройство |
|
|
||||||
| `UpsertTrustedDeviceLoginSettings` | `03_Session_Management_API.md` | включение/обновление pairing-настроек доверенной сессией |
|
|
||||||
| `StartTrustedDeviceLogin` | `03_Session_Management_API.md` | создание pairing-заявки для нового устройства |
|
|
||||||
| `ListTrustedDeviceLoginRequests` | `03_Session_Management_API.md` | список активных pairing-заявок для доверенной сессии |
|
|
||||||
| `ApproveTrustedDeviceLogin` | `03_Session_Management_API.md` | подтверждение pairing-заявки доверенной сессией |
|
|
||||||
| `RejectTrustedDeviceLogin` | `03_Session_Management_API.md` | отклонение pairing-заявки доверенной сессией |
|
|
||||||
| `CancelTrustedDeviceLogin` | `03_Session_Management_API.md` | отмена pairing-заявки со стороны нового ожидающего устройства |
|
|
||||||
| `GetTrustedDeviceLoginStatus` | `03_Session_Management_API.md` | чтение статуса и результата pairing-заявки |
|
|
||||||
| `ListSessions` | `03_Session_Management_API.md` | список активных сессий |
|
| `ListSessions` | `03_Session_Management_API.md` | список активных сессий |
|
||||||
| `CloseActiveSession` | `03_Session_Management_API.md` | закрытие активной сессии |
|
| `CloseActiveSession` | `03_Session_Management_API.md` | закрытие активной сессии |
|
||||||
| `AddBlock` | `04_Add_Block_to_Blockchain_API.md` | добавление блока в блокчейн |
|
| `AddBlock` | `04_Add_Block_to_Blockchain_API.md` | добавление блока в блокчейн |
|
||||||
| `GetBlockchainBlock` | `04_Add_Block_to_Blockchain_API.md` | чтение одного блока блокчейна |
|
|
||||||
| `Ping` | `05_Technical_Requests_API.md` | keep-alive |
|
| `Ping` | `05_Technical_Requests_API.md` | keep-alive |
|
||||||
| `GetServerInfo` | `05_Technical_Requests_API.md` | публичная информация о сервере |
|
| `GetServerInfo` | `05_Technical_Requests_API.md` | публичная информация о сервере |
|
||||||
| `ListBlockchainHeads` | `05_Technical_Requests_API.md` | список heads всех локальных блокчейнов |
|
|
||||||
| `GetSyncUserProfile` | `05_Technical_Requests_API.md` | межсерверный профиль пользователя для синхронизации |
|
|
||||||
| `SendSignal` | `05_Technical_Requests_API.md` | общий межсессионный технический сигнал в одну или все сессии пользователя |
|
|
||||||
| `GetCallIceConfig` | `05_Technical_Requests_API.md` | STUN/TURN конфигурация звонков |
|
| `GetCallIceConfig` | `05_Technical_Requests_API.md` | STUN/TURN конфигурация звонков |
|
||||||
| `ClientErrorLog` | `05_Technical_Requests_API.md` | логирование клиентской ошибки |
|
| `ClientErrorLog` | `05_Technical_Requests_API.md` | логирование клиентской ошибки |
|
||||||
| `ClientDebugLog` | `05_Technical_Requests_API.md` | клиентский debug-лог |
|
| `ClientDebugLog` | `05_Technical_Requests_API.md` | клиентский debug-лог |
|
||||||
@@ -57,11 +43,10 @@
|
|||||||
| `AddCloseFriend` | `11_Connections_API.md` | добавить близкого друга |
|
| `AddCloseFriend` | `11_Connections_API.md` | добавить близкого друга |
|
||||||
| `UpsertPushToken` | `12_Direct_Messages_Push_Calls_API.md` | регистрация WebPush-токена |
|
| `UpsertPushToken` | `12_Direct_Messages_Push_Calls_API.md` | регистрация WebPush-токена |
|
||||||
| `SendTestWebPush` | `12_Direct_Messages_Push_Calls_API.md` | тестовая push-доставка |
|
| `SendTestWebPush` | `12_Direct_Messages_Push_Calls_API.md` | тестовая push-доставка |
|
||||||
|
| `SendDirectMessage` | `12_Direct_Messages_Push_Calls_API.md` | отправка подписанного DM-пакета |
|
||||||
| `SendMessagePair` | `12_Direct_Messages_Push_Calls_API.md` | отправка пары входящий/исходящий DM |
|
| `SendMessagePair` | `12_Direct_Messages_Push_Calls_API.md` | отправка пары входящий/исходящий DM |
|
||||||
| `ReceiveOutcomingMessage` | `12_Direct_Messages_Push_Calls_API.md` | алиас `SendMessagePair` |
|
| `ReceiveOutcomingMessage` | `12_Direct_Messages_Push_Calls_API.md` | алиас `SendMessagePair` |
|
||||||
| `ReceiveIncomingMessage` | `12_Direct_Messages_Push_Calls_API.md` | прием входящего DM-блока |
|
| `ReceiveIncomingMessage` | `12_Direct_Messages_Push_Calls_API.md` | прием входящего DM-блока |
|
||||||
| `DeleteMessage` | `12_Direct_Messages_Push_Calls_API.md` | tombstone одного личного сообщения у обеих сторон |
|
|
||||||
| `DeleteConversation` | `12_Direct_Messages_Push_Calls_API.md` | tombstone удаления истории переписки |
|
|
||||||
| `AckSessionDelivery` | `12_Direct_Messages_Push_Calls_API.md` | подтверждение доставки в сессию |
|
| `AckSessionDelivery` | `12_Direct_Messages_Push_Calls_API.md` | подтверждение доставки в сессию |
|
||||||
| `CallInviteBroadcast` | `12_Direct_Messages_Push_Calls_API.md` | broadcast приглашения к звонку |
|
| `CallInviteBroadcast` | `12_Direct_Messages_Push_Calls_API.md` | broadcast приглашения к звонку |
|
||||||
| `CallSignalToSession` | `12_Direct_Messages_Push_Calls_API.md` | сигнал звонка в конкретную сессию |
|
| `CallSignalToSession` | `12_Direct_Messages_Push_Calls_API.md` | сигнал звонка в конкретную сессию |
|
||||||
@@ -69,7 +54,5 @@
|
|||||||
## Важные замечания
|
## Важные замечания
|
||||||
|
|
||||||
- `ReceiveOutcomingMessage` сейчас зарегистрирован как алиас того же handler/request-класса, что и `SendMessagePair`.
|
- `ReceiveOutcomingMessage` сейчас зарегистрирован как алиас того же handler/request-класса, что и `SendMessagePair`.
|
||||||
- Legacy-операция `SendDirectMessage` больше не зарегистрирована и не должна использоваться для DM v1.
|
|
||||||
- Отдельных HTTP endpoints для DM-файлов сейчас нет.
|
|
||||||
- Классы `Net_MarkChannelMessagesSeen_*` существуют в коде, но операция `MarkChannelMessagesSeen` не зарегистрирована в `JsonHandlerRegistry`, поэтому в публичный список API не входит.
|
- Классы `Net_MarkChannelMessagesSeen_*` существуют в коде, но операция `MarkChannelMessagesSeen` не зарегистрирована в `JsonHandlerRegistry`, поэтому в публичный список API не входит.
|
||||||
- HTTP debug endpoints из `src/main/java/server/debug/` не входят в этот индекс WebSocket `op`; они описаны отдельно в `13_HTTP_Debug_API.md`.
|
- HTTP debug endpoints из `src/main/java/server/debug/` не входят в этот индекс WebSocket `op`; они описаны отдельно в `13_HTTP_Debug_API.md`.
|
||||||
@@ -21,7 +21,7 @@
|
|||||||
"param": "display_name",
|
"param": "display_name",
|
||||||
"time_ms": 1774700000123,
|
"time_ms": 1774700000123,
|
||||||
"value": "Alice",
|
"value": "Alice",
|
||||||
"client_key": "BASE64_DEVICE_PUBLIC_KEY",
|
"device_key": "BASE64_DEVICE_PUBLIC_KEY",
|
||||||
"signature": "BASE64_SIGNATURE"
|
"signature": "BASE64_SIGNATURE"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -76,7 +76,7 @@
|
|||||||
"param": "display_name",
|
"param": "display_name",
|
||||||
"time_ms": 1774700000123,
|
"time_ms": 1774700000123,
|
||||||
"value": "Alice",
|
"value": "Alice",
|
||||||
"client_key": "BASE64_DEVICE_PUBLIC_KEY",
|
"device_key": "BASE64_DEVICE_PUBLIC_KEY",
|
||||||
"signature": "BASE64_SIGNATURE"
|
"signature": "BASE64_SIGNATURE"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -116,7 +116,7 @@
|
|||||||
"param": "display_name",
|
"param": "display_name",
|
||||||
"time_ms": 1774700000123,
|
"time_ms": 1774700000123,
|
||||||
"value": "Alice",
|
"value": "Alice",
|
||||||
"client_key": "BASE64_DEVICE_PUBLIC_KEY",
|
"device_key": "BASE64_DEVICE_PUBLIC_KEY",
|
||||||
"signature": "BASE64_SIGNATURE"
|
"signature": "BASE64_SIGNATURE"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -126,4 +126,4 @@
|
|||||||
|
|
||||||
## Примечание
|
## Примечание
|
||||||
|
|
||||||
Имена JSON-полей `time_ms` и `client_key` сейчас соответствуют Java-модели ответа/запроса и должны передаваться именно в таком виде.
|
Имена JSON-полей `time_ms` и `device_key` сейчас соответствуют Java-модели ответа/запроса и должны передаваться именно в таком виде.
|
||||||
@@ -0,0 +1,306 @@
|
|||||||
|
# API для разработчиков: DM, push и сигналы звонков
|
||||||
|
|
||||||
|
Документ описывает WebSocket-операции для подписанных личных сообщений, WebPush и realtime-сигналов звонков.
|
||||||
|
|
||||||
|
Логика личных сообщений дополнительно описана в `Dev_Docs/Personal_Messages/README.md`; этот файл фиксирует именно публичные `op`, поля запросов и поля ответов.
|
||||||
|
|
||||||
|
## 1. `UpsertPushToken`
|
||||||
|
|
||||||
|
Требует авторизации.
|
||||||
|
|
||||||
|
### Запрос
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"op": "UpsertPushToken",
|
||||||
|
"requestId": "push-upsert-001",
|
||||||
|
"payload": {
|
||||||
|
"sessionId": "SESSION_ID",
|
||||||
|
"endpoint": "https://push.example/...",
|
||||||
|
"p256dhKey": "BASE64",
|
||||||
|
"authKey": "BASE64",
|
||||||
|
"platform": "web",
|
||||||
|
"userAgent": "Mozilla/5.0 ..."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Успешный ответ
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"op": "UpsertPushToken",
|
||||||
|
"requestId": "push-upsert-001",
|
||||||
|
"status": 200,
|
||||||
|
"ok": true,
|
||||||
|
"payload": {
|
||||||
|
"tokenId": "token-1",
|
||||||
|
"updatedAtMs": 1774700000123
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. `SendTestWebPush`
|
||||||
|
|
||||||
|
Требует авторизации. Если `login` передан, он должен совпадать с логином текущей сессии.
|
||||||
|
|
||||||
|
### Запрос
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"op": "SendTestWebPush",
|
||||||
|
"requestId": "push-test-001",
|
||||||
|
"payload": {
|
||||||
|
"login": "alice",
|
||||||
|
"sessionId": "SESSION_ID",
|
||||||
|
"title": "Test",
|
||||||
|
"text": "Push body"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Успешный ответ
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"op": "SendTestWebPush",
|
||||||
|
"requestId": "push-test-001",
|
||||||
|
"status": 200,
|
||||||
|
"ok": true,
|
||||||
|
"payload": {
|
||||||
|
"targetLogin": "alice",
|
||||||
|
"attemptedSessions": 1,
|
||||||
|
"sessionsWithPushConfig": 1,
|
||||||
|
"delivered": 1,
|
||||||
|
"failed": 0,
|
||||||
|
"sentAtMs": 1774700000123
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. `SendDirectMessage`
|
||||||
|
|
||||||
|
Отправляет один подписанный DM-пакет.
|
||||||
|
|
||||||
|
### Запрос
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"op": "SendDirectMessage",
|
||||||
|
"requestId": "dm-001",
|
||||||
|
"payload": {
|
||||||
|
"blobB64": "BASE64_SIGNED_DM_PACKET"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Успешный ответ
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"op": "SendDirectMessage",
|
||||||
|
"requestId": "dm-001",
|
||||||
|
"status": 200,
|
||||||
|
"ok": true,
|
||||||
|
"payload": {
|
||||||
|
"messageId": "dm-1",
|
||||||
|
"deliveredWsSessions": 1,
|
||||||
|
"deliveredWebPushSessions": 0,
|
||||||
|
"sessionNotFound": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. `SendMessagePair` и `ReceiveOutcomingMessage`
|
||||||
|
|
||||||
|
`ReceiveOutcomingMessage` сейчас является алиасом `SendMessagePair` и использует тот же request/handler.
|
||||||
|
|
||||||
|
### Запрос
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"op": "SendMessagePair",
|
||||||
|
"requestId": "dm-pair-001",
|
||||||
|
"payload": {
|
||||||
|
"incomingBlobB64": "BASE64_INCOMING_SIGNED_BLOCK",
|
||||||
|
"outgoingBlobB64": "BASE64_OUTGOING_SIGNED_BLOCK"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Успешный ответ
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"op": "SendMessagePair",
|
||||||
|
"requestId": "dm-pair-001",
|
||||||
|
"status": 200,
|
||||||
|
"ok": true,
|
||||||
|
"payload": {
|
||||||
|
"baseKey": "base-key",
|
||||||
|
"incomingKey": "incoming-key",
|
||||||
|
"outgoingKey": "outgoing-key",
|
||||||
|
"deliveredWsSessions": 1,
|
||||||
|
"deliveredWebPushSessions": 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. `ReceiveIncomingMessage`
|
||||||
|
|
||||||
|
Принимает входящий подписанный DM-блок.
|
||||||
|
|
||||||
|
### Запрос
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"op": "ReceiveIncomingMessage",
|
||||||
|
"requestId": "dm-in-001",
|
||||||
|
"payload": {
|
||||||
|
"incomingBlobB64": "BASE64_INCOMING_SIGNED_BLOCK"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Успешный ответ
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"op": "ReceiveIncomingMessage",
|
||||||
|
"requestId": "dm-in-001",
|
||||||
|
"status": 200,
|
||||||
|
"ok": true,
|
||||||
|
"payload": {
|
||||||
|
"messageKey": "incoming-key",
|
||||||
|
"baseKey": "base-key",
|
||||||
|
"deliveredWsSessions": 1,
|
||||||
|
"deliveredWebPushSessions": 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. `AckSessionDelivery`
|
||||||
|
|
||||||
|
Требует авторизации. Подтверждает доставку сообщения в текущую сессию.
|
||||||
|
|
||||||
|
### Запрос
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"op": "AckSessionDelivery",
|
||||||
|
"requestId": "ack-001",
|
||||||
|
"payload": {
|
||||||
|
"messageKey": "incoming-key"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Успешный ответ
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"op": "AckSessionDelivery",
|
||||||
|
"requestId": "ack-001",
|
||||||
|
"status": 200,
|
||||||
|
"ok": true,
|
||||||
|
"payload": {
|
||||||
|
"messageKey": "incoming-key"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. `CallInviteBroadcast`
|
||||||
|
|
||||||
|
Требует авторизации. Отправляет приглашение к звонку на активные сессии пользователя `toLogin`.
|
||||||
|
|
||||||
|
### Запрос
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"op": "CallInviteBroadcast",
|
||||||
|
"requestId": "call-invite-001",
|
||||||
|
"payload": {
|
||||||
|
"toLogin": "bob",
|
||||||
|
"callId": "call-1",
|
||||||
|
"type": 100
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Успешный ответ
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"op": "CallInviteBroadcast",
|
||||||
|
"requestId": "call-invite-001",
|
||||||
|
"status": 200,
|
||||||
|
"ok": true,
|
||||||
|
"payload": {
|
||||||
|
"callId": "call-1",
|
||||||
|
"deliveredWsSessions": 1,
|
||||||
|
"deliveredFcmSessions": 0,
|
||||||
|
"deliveredWebPushSessions": 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. `CallSignalToSession`
|
||||||
|
|
||||||
|
Требует авторизации. Отправляет сигнал звонка в конкретную сессию получателя.
|
||||||
|
|
||||||
|
### Запрос
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"op": "CallSignalToSession",
|
||||||
|
"requestId": "call-signal-001",
|
||||||
|
"payload": {
|
||||||
|
"toLogin": "bob",
|
||||||
|
"targetSessionId": "SESSION_ID",
|
||||||
|
"callId": "call-1",
|
||||||
|
"type": 101,
|
||||||
|
"data": "{\"sdp\":\"...\"}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Успешный ответ
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"op": "CallSignalToSession",
|
||||||
|
"requestId": "call-signal-001",
|
||||||
|
"status": 200,
|
||||||
|
"ok": true,
|
||||||
|
"payload": {
|
||||||
|
"delivered": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Если целевая сессия не найдена или доставка не удалась, сервер может вернуть `404`.
|
||||||
|
|
||||||
|
## Типовые ошибки
|
||||||
|
|
||||||
|
- `422 / NOT_AUTHENTICATED` — требуется авторизация.
|
||||||
|
- `400 / BAD_FIELDS` — не заполнены обязательные поля.
|
||||||
|
- `404 / USER_NOT_FOUND` — пользователь не найден.
|
||||||
|
- `404 / SESSION_NOT_FOUND` — сессия не найдена.
|
||||||
|
- `422 / BAD_SIGNATURE` — подпись DM не прошла проверку.
|
||||||
|
- `422 / BAD_DEVICE_KEY` — некорректный device key отправителя.
|
||||||
|
- `422 / BAD_TIME_WINDOW` — время подписанного сообщения вне допустимого окна.
|
||||||
|
- `422 / REPLAY` — повторное сообщение заблокировано.
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
# История изменений документации блокчейна
|
||||||
|
|
||||||
|
## 2026-05-24 11:40:00 +0300
|
||||||
|
- Базовый коммит-ориентир: `abdce05`.
|
||||||
|
- `TEXT_REPOST (subType=30)` оставлен как зарезервированный формат, но новые блоки репоста временно отключены на уровне `AddBlock`.
|
||||||
|
- В `11_TEXT_Blocks.md` зафиксировано, что запись `TEXT_REPOST` временно не используется до будущей реализации.
|
||||||
|
- В `Dev_Docs/API/04_Add_Block_to_Blockchain_API.md` добавлен код отказа `repost_disabled`.
|
||||||
|
|
||||||
|
## 2026-05-21 19:05:00 +0300
|
||||||
|
- Базовый коммит-ориентир: `5344c42`.
|
||||||
|
- Добавлен новый TEXT-подтип `TEXT_REPOST (subType=30)`:
|
||||||
|
- обновлён перечень типов в `11_TEXT_Blocks.md`;
|
||||||
|
- обновлена быстрая карта типов в `00_Blockchain_Formats_and_Block_Types.md`.
|
||||||
|
- Уточнено API-описание поддержанных подтипов в `Dev_Docs/API/04_Add_Block_to_Blockchain_API.md`.
|
||||||
|
- В документе `Dev_Docs/API/08_MCP_Чтение_и_дозапись_персонального_публичного_чата.md` зафиксировано, что чтение канала учитывает `TEXT_POST` и `TEXT_REPOST`.
|
||||||
|
|
||||||
|
## 2026-05-20 11:34:17 +0300
|
||||||
|
- Базовый коммит-ориентир: `a53444b`.
|
||||||
|
- В `13_CONNECTION_Blocks.md` добавлены новые CONNECTION подтипы:
|
||||||
|
- `60/61` — `known_person / unknown_person` (знаю этого человека);
|
||||||
|
- `70/71` — `shine_confirmed / shine_unconfirmed` (точно уверен, что сияющий);
|
||||||
|
- `74/75` — `shine_seen / shine_unseen` (мало знаком, но видел сияющим).
|
||||||
|
- Обновлён список CONNECTION-подтипов в `Dev_Docs/API/04_Add_Block_to_Blockchain_API.md`.
|
||||||
|
|
||||||
|
## 2026-05-19 20:30:21 +0300
|
||||||
|
- Базовый коммит-ориентир: `7986184`.
|
||||||
|
- Уточнён документ `11_TEXT_Blocks.md`: для `TEXT_EDIT_POST` и `TEXT_EDIT_REPLY` зафиксировано, что `textLen=0` допустим и трактуется как логическое удаление сообщения.
|
||||||
|
- Явно закреплено, что отдельного `DELETE`-подтипа нет, удаление выполняется edit-блоком.
|
||||||
|
|
||||||
|
## 2026-05-19 00:22:46 +0300
|
||||||
|
- Базовый коммит-ориентир: `c27da63a3e65`.
|
||||||
|
- Актуализирован `README.md` как точка входа для MVP-документации по протоколу.
|
||||||
|
- В документации явно зафиксировано, что `channelType=100` и `channelType=200` присутствуют в формате, но пока не используются в UI.
|
||||||
|
- Актуализирован перечень REACTION-подтипов: добавлен `REACTION_UNLIKE (subType=2)`.
|
||||||
|
- Актуализирован перечень CONNECTION-подтипов: добавлены `SPOUSE/PARENT/CHILD/SIBLING` и обратные операции.
|
||||||
|
- В документ `02_Blockchain_Kinds_and_Lines.md` добавлены фактические серверные правила валидации line-полей.
|
||||||
|
- Обновлён корневой `AGENTS.md`: формат блокчейна менять только после явного подтверждения пользователя и с предварительным предупреждением.
|
||||||
|
|
||||||
|
## 2026-05-13 00:02:32 +0300
|
||||||
|
- Базовый коммит-ориентир: `f63f40f1eb2f`.
|
||||||
|
- Добавлен текущий формат `CreateChannelBody` с полями `channelType (2 байта)` и `channelTypeVersion (2 байта)`.
|
||||||
|
- Зафиксированы типы каналов: `0=stories`, `1=public`, `100=personal`, `200=group`.
|
||||||
|
- Серверная уникальность имени канала изменена на `owner + type + name(slug)`.
|
||||||
|
- Root-канал `0` переименован в `stories` на уровне API-чтения.
|
||||||
|
- Для персонального канала (`type=100`) включена сборка парного потока при чтении (`A->B` + `B->A`, если существует).
|
||||||
|
- Добавлена поддержка командного префикса `/.` и команды `/.desc` для актуализации описания канала при чтении.
|
||||||
|
- Зафиксированы команды `/.add` и `/.remove` для каналов `type=200` (зарезервировано под расширение участниками).
|
||||||
|
- В `AGENTS.md` добавлено обязательное правило актуализации документации в `Dev_Docs/Blockchain/`.
|
||||||
@@ -24,16 +24,10 @@
|
|||||||
10. [CHANGELOG.md](./CHANGELOG.md)
|
10. [CHANGELOG.md](./CHANGELOG.md)
|
||||||
Журнал изменений документации.
|
Журнал изменений документации.
|
||||||
|
|
||||||
## Смежная документация
|
|
||||||
- [../ИТХ/README.md](../ИТХ/README.md) — ежедневное закрытие блокчейна (ИТХ): краткий обзор.
|
|
||||||
- [../ИТХ/Спецификация_ИТХ_v1.md](../ИТХ/Спецификация_ИТХ_v1.md) — точная спецификация чекпоинтов (Arweave/Solana/канал закрытий).
|
|
||||||
- [sync-between-servers.md](./sync-between-servers.md) — живая межсерверная синхронизация блоков и DM.
|
|
||||||
|
|
||||||
## Важные ограничения MVP
|
## Важные ограничения MVP
|
||||||
- Каналы `type=100` и `type=200` присутствуют в формате, но сейчас не используются в UI.
|
- Каналы `type=100` и `type=200` присутствуют в формате, но сейчас не используются в UI.
|
||||||
- Поддерживаемый рабочий сценарий UI на текущем этапе: `stories (type=0)` и `public (type=1)`.
|
- Поддерживаемый рабочий сценарий UI на текущем этапе: `stories (type=0)` и `public (type=1)`.
|
||||||
|
|
||||||
## Обязательное сопровождение
|
## Обязательное сопровождение
|
||||||
- При любом изменении формата/правил блокчейна в коде документы этого каталога обновляются в том же наборе изменений.
|
- При любом изменении формата/правил блокчейна в коде документы этого каталога обновляются в том же наборе изменений.
|
||||||
- Обычный `AddBlock` сейчас пишет через `<blockchainName>.tmp_bch`, `<blockchainName>.write_check` и `<blockchainName>.write_pending`; эта схема и `BlockchainTmpRecoveryOnStartup` должны быть описаны в актуальной документации по синхронизации и recovery.
|
|
||||||
- Каждое обновление документов фиксируется в `CHANGELOG.md` с датой/временем и хэшем коммита-основания.
|
- Каждое обновление документов фиксируется в `CHANGELOG.md` с датой/временем и хэшем коммита-основания.
|
||||||
@@ -0,0 +1,100 @@
|
|||||||
|
# Синхронизация блоков и DM между серверами SHiNE
|
||||||
|
|
||||||
|
Документ описывает архитектуру и протокол синхронизации данных между партнёрскими серверами SHiNE.
|
||||||
|
|
||||||
|
## 1. Зачем нужна синхронизация
|
||||||
|
|
||||||
|
Пользователи SHiNE могут быть «приписаны» к разным серверам.
|
||||||
|
Когда пользователь A (на сервере X) пишет пользователю B (на сервере Y):
|
||||||
|
|
||||||
|
1. Сервер X принимает сообщение;
|
||||||
|
2. Сервер X должен переслать DM-блок серверу Y;
|
||||||
|
3. Сервер Y сохраняет блок и доставляет в активные сессии пользователя B.
|
||||||
|
|
||||||
|
Аналогично, блоки пользовательского блокчейна (записи `AddBlock`) должны синхронизироваться,
|
||||||
|
чтобы любой партнёрский сервер мог отдать полную историю пользователя.
|
||||||
|
|
||||||
|
## 2. Список серверов синхронизации (`sync_servers`)
|
||||||
|
|
||||||
|
Каждый сервер регистрирует в своей Solana PDA список `sync_servers` —
|
||||||
|
логины SHiNE-аккаунтов партнёрских серверов, с которыми он синхронизируется.
|
||||||
|
|
||||||
|
- Список хранится в блоке `ServerProfileBlock` внутри `user_pda` сервера.
|
||||||
|
- Адрес каждого партнёрского сервера читается из его PDA на Solana.
|
||||||
|
- Синхронизация двусторонняя: оба сервера должны иметь друг друга в `sync_servers`.
|
||||||
|
|
||||||
|
## 3. Что синхронизируется
|
||||||
|
|
||||||
|
### 3.1 Личные сообщения (DM)
|
||||||
|
|
||||||
|
- Все DM-блоки форматов типов `1/2` (текст) и `3/4` (read-receipt).
|
||||||
|
- Сервер-отправитель: при получении пары блоков от клиента перенаправляет их серверу получателя.
|
||||||
|
- Сервер-получатель: сохраняет блоки в `signed_messages_v2`, доставляет в активные сессии.
|
||||||
|
- Дедупликация по уникальному `message_key = from|to|timeMs|nonce|type`.
|
||||||
|
|
||||||
|
### 3.2 Блоки пользовательского блокчейна
|
||||||
|
|
||||||
|
- Все блоки `AddBlock` пользователей, зарегистрированных на сервере или синхронизирующихся через него.
|
||||||
|
- Синхронизируются в обе стороны между всеми партнёрами из `sync_servers`.
|
||||||
|
- Порядок блоков сохраняется (по глобальному номеру блока и хэшу).
|
||||||
|
- Дедупликация по глобальному номеру блока и хэшу.
|
||||||
|
|
||||||
|
## 4. Протокол синхронизации (целевой, не реализован)
|
||||||
|
|
||||||
|
### 4.1 Межсерверное соединение
|
||||||
|
|
||||||
|
- Серверы устанавливают постоянное WebSocket-соединение друг с другом.
|
||||||
|
- Адрес партнёра определяется по `server_address` из его Solana PDA.
|
||||||
|
- Аутентификация: подпись Ed25519 корневым ключом сервера (`root_key` из PDA).
|
||||||
|
- При разрыве — переподключение с экспоненциальным backoff.
|
||||||
|
|
||||||
|
### 4.2 Доставка новых данных (push)
|
||||||
|
|
||||||
|
- При получении нового блока или DM сервер немедленно пушит его всем подключённым партнёрам.
|
||||||
|
- Партнёр подтверждает приём (ACK). Без ACK — повтор с backoff.
|
||||||
|
|
||||||
|
### 4.3 Начальная синхронизация (backfill)
|
||||||
|
|
||||||
|
- При первом подключении к партнёру серверы обмениваются «курсорами» состояния:
|
||||||
|
последний глобальный номер блока, последний известный DM-ключ.
|
||||||
|
- Сервер с более полной историей досылает недостающее партнёру.
|
||||||
|
|
||||||
|
### 4.4 Разрешение конфликтов
|
||||||
|
|
||||||
|
- Блоки пользовательского блокчейна: порядок определяется глобальным номером блока.
|
||||||
|
Конфликтующие ветки (fork) разрешаются по правилам `AddBlock` (см. `Dev_Docs/Blockchain/README.md`).
|
||||||
|
- DM: конфликтов нет, `message_key` уникален.
|
||||||
|
|
||||||
|
## 5. Маршрутизация DM между серверами
|
||||||
|
|
||||||
|
При отправке DM от пользователя A к пользователю B:
|
||||||
|
|
||||||
|
1. Клиент A отправляет пару блоков на свой сервер X.
|
||||||
|
2. Сервер X определяет, на каком сервере зарегистрирован пользователь B.
|
||||||
|
- Сначала проверяет локально (если B зарегистрирован на X).
|
||||||
|
- Иначе читает PDA пользователя B из Solana и смотрит `access_servers`.
|
||||||
|
- Выбирает первый доступный сервер из `access_servers` и перенаправляет туда DM.
|
||||||
|
3. Сервер Y (из `access_servers` B) сохраняет и доставляет блоки.
|
||||||
|
|
||||||
|
Кэш адресов серверов: обновляется раз в сессию (при ошибке соединения).
|
||||||
|
|
||||||
|
## 6. Безопасность
|
||||||
|
|
||||||
|
- Все блоки подписаны ключами пользователя на клиенте — сервер не может подделать содержимое.
|
||||||
|
- Серверы не расшифровывают DM-контент (шифрование — задача следующего этапа).
|
||||||
|
- При синхронизации каждый блок проходит валидацию подписи на принимающем сервере.
|
||||||
|
|
||||||
|
## 7. Статус реализации
|
||||||
|
|
||||||
|
| Компонент | Статус |
|
||||||
|
|-----------|--------|
|
||||||
|
| Регистрация серверной PDA в Solana | ✅ Реализовано |
|
||||||
|
| Чтение `sync_servers` из PDA | Нужна реализация |
|
||||||
|
| Межсерверный WebSocket-канал | Нужна реализация |
|
||||||
|
| Push новых DM партнёрам | Нужна реализация |
|
||||||
|
| Push блоков блокчейна партнёрам | Нужна реализация |
|
||||||
|
| Backfill при первом подключении | Нужна реализация |
|
||||||
|
| Маршрутизация DM через access_servers | Нужна реализация (заглушка) |
|
||||||
|
|
||||||
|
Текущая версия сервера работает без межсерверной синхронизации.
|
||||||
|
Синхронизация — задача следующего этапа разработки.
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
# Будущие фичи
|
||||||
|
|
||||||
|
Эта папка хранит задачи, которые сознательно отложены и сейчас не должны попадать в активную разработку или ручную проверку без отдельной команды пользователя.
|
||||||
|
|
||||||
|
## Горизонты планирования
|
||||||
|
|
||||||
|
- `near/` - ближайшие планы: задачи, к которым можно вернуться сегодня или завтра.
|
||||||
|
- `medium/` - среднесрочные планы: задачи на ближайшие недели или 1-2 месяца.
|
||||||
|
- `far/` - дальнее будущее: идеи без понятного срока возврата.
|
||||||
|
|
||||||
|
Если пользователь спрашивает, какие есть планы, агент должен смотреть эти три папки и кратко перечислять задачи по горизонтам.
|
||||||
|
|
||||||
|
## Как использовать
|
||||||
|
|
||||||
|
1. Каждая будущая фича описывается отдельным markdown-файлом в одном из горизонтов.
|
||||||
|
2. В файле нужно фиксировать:
|
||||||
|
- зачем нужна фича;
|
||||||
|
- к какому сроку или горизонту она относится;
|
||||||
|
- что нужно сделать;
|
||||||
|
- какие вопросы нужно уточнить перед реализацией;
|
||||||
|
- что уже было сделано в коде, если фича частично реализована;
|
||||||
|
- что временно отключено или закомментировано, если применимо;
|
||||||
|
- какие документы нужно обновить при возврате к задаче;
|
||||||
|
- с какого места продолжать разработку.
|
||||||
|
3. Агент не должен начинать реализацию файлов из этой папки без явной просьбы пользователя.
|
||||||
|
|
||||||
|
## Текущие планы
|
||||||
|
|
||||||
|
### Ближайшие
|
||||||
|
|
||||||
|
- `near/2026-05-25_1106_telegram_agent_players.md` - разрешённые пользователи Telegram для агента, отдельные папки игроков, персональные истории и публикация краткого вопроса/ответа в общий канал.
|
||||||
|
- `near/2026-05-25_1106_wallet_topup_solana_arweave.md` - пополнение Solana и Arweave через внешний сервис покупки с подсказкой и копированием адреса.
|
||||||
|
|
||||||
|
### Среднесрочные
|
||||||
|
|
||||||
|
- `medium/2026-05-24_1140_репосты_в_каналах_и_тредах.md` - репосты в каналах и тредах.
|
||||||
|
- `medium/2026-05-25_1106_shine_balance_wallet.md` - кошелёк и пополнение баланса сияния через блокчейн.
|
||||||
|
- `medium/2026-05-26_0029_esp32s3_file_storage.md` - ESP32S3 как личное файловое хранилище SHiNE для файлов переписок и вложений.
|
||||||
|
- `medium/2026-06-03_подключение_других_устройств_через_qr.md` - довести подключение других устройств через QR: сейчас заготовка есть, но сценарий работает нестабильно и его нужно будет отдельно доделать.
|
||||||
|
- `medium/2026-06-02_сессионные_саб_серверы_в_pda.md` - несколько саб-серверов пользователя как типизированные сессии в PDA с версией записи.
|
||||||
|
|
||||||
|
### DAO-запуск
|
||||||
|
|
||||||
|
- `dao_запуск/2026-06-05_esp32_hardware_wallet_device_session.md` - ESP32 как аппаратный кошелёк: постоянная device-сессия на сервере, подтверждение операций на экране, делегированные сессии для браузера/телефона.
|
||||||
|
|
||||||
|
### Дальнее будущее
|
||||||
|
|
||||||
|
- Сейчас задач нет.
|
||||||
+1
-1
@@ -22,7 +22,7 @@ ESP32 становится аппаратным HSM (hardware security module):
|
|||||||
### ESP32 (основная работа)
|
### ESP32 (основная работа)
|
||||||
- [ ] Инициализация WiFi (SSID/пароль в NVS)
|
- [ ] Инициализация WiFi (SSID/пароль в NVS)
|
||||||
- [ ] WebSocket-клиент (`WebSocketsClient`) — постоянное соединение с сервером
|
- [ ] WebSocket-клиент (`WebSocketsClient`) — постоянное соединение с сервером
|
||||||
- [ ] Авторизация на сервере: `AuthChallenge` → `CreateAuthSession` через `clientKey` (уже есть в NVS), сохранить `sessionId` в NVS
|
- [ ] Авторизация на сервере: `AuthChallenge` → `CreateAuthSession` через `deviceKey` (уже есть в NVS), сохранить `sessionId` в NVS
|
||||||
- [ ] Обработчик входящих WebSocket-событий: JSON-парсинг, диспетчер по типу
|
- [ ] Обработчик входящих WebSocket-событий: JSON-парсинг, диспетчер по типу
|
||||||
- [ ] Новые UI-экраны: «Разрешить сессию?» и «Подписать?» с кнопками Да/Нет
|
- [ ] Новые UI-экраны: «Разрешить сессию?» и «Подписать?» с кнопками Да/Нет
|
||||||
- [ ] Расширенное хранилище ключей в NVS (произвольные именованные ключи сверх базовых трёх)
|
- [ ] Расширенное хранилище ключей в NVS (произвольные именованные ключи сверх базовых трёх)
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
# Дальнее будущее
|
||||||
|
|
||||||
|
Сейчас в этом горизонте нет активных идей.
|
||||||
|
|
||||||
|
Сюда переносить задачи, у которых нет понятного срока возврата и которые не нужно учитывать в ближайшем или среднесрочном планировании.
|
||||||
+1
-1
@@ -86,7 +86,7 @@
|
|||||||
- `Dev_Docs/Blockchain/CHANGELOG.md`;
|
- `Dev_Docs/Blockchain/CHANGELOG.md`;
|
||||||
- `Dev_Docs/API/04_Add_Block_to_Blockchain_API.md`;
|
- `Dev_Docs/API/04_Add_Block_to_Blockchain_API.md`;
|
||||||
- документы API чтения каналов/тредов, если изменятся поля ответа.
|
- документы API чтения каналов/тредов, если изменятся поля ответа.
|
||||||
9. После реализации перенести задачу из `TODO/` в `Dev_Docs/Pending_Features/` как фичу, требующую ручной проверки.
|
9. После реализации перенести задачу из `Dev_Docs/Future_Features/` в `Dev_Docs/Pending_Features/` как фичу, требующую ручной проверки.
|
||||||
|
|
||||||
## Минимальный чек-лист ручной проверки в будущем
|
## Минимальный чек-лист ручной проверки в будущем
|
||||||
|
|
||||||
+1
-1
@@ -35,7 +35,7 @@
|
|||||||
## Документы, которые нужно обновить при возврате
|
## Документы, которые нужно обновить при возврате
|
||||||
|
|
||||||
- `Dev_Docs/Keys/README.md`
|
- `Dev_Docs/Keys/README.md`
|
||||||
- `Dev_Docs/Personal_Messages/Протокол_DM_v1.md`
|
- `Dev_Docs/Personal_Messages/README.md`
|
||||||
- `Dev_Docs/API/`
|
- `Dev_Docs/API/`
|
||||||
- `Dev_Docs/Blockchain/`, если появятся новые блоки или команды для файлов.
|
- `Dev_Docs/Blockchain/`, если появятся новые блоки или команды для файлов.
|
||||||
|
|
||||||
+11
-11
@@ -1,4 +1,4 @@
|
|||||||
# Сессионные homeserver-ы в PDA пользователя
|
# Сессионные саб-серверы в PDA пользователя
|
||||||
|
|
||||||
- Статус:
|
- Статус:
|
||||||
`future`
|
`future`
|
||||||
@@ -10,15 +10,15 @@
|
|||||||
после завершения первого этапа по пользовательским сессиям
|
после завершения первого этапа по пользовательским сессиям
|
||||||
|
|
||||||
- Основание:
|
- Основание:
|
||||||
Идея зафиксирована после обсуждения архитектуры пользовательских сессий и внутренних homeserver-ов. Сейчас задача сознательно отложена: сначала нужно аккуратно ввести базовую модель сессий, а затем возвращаться к расширенной серверной роли.
|
Идея зафиксирована после обсуждения архитектуры пользовательских сессий и внутренних саб-серверов. Сейчас задача сознательно отложена: сначала нужно аккуратно ввести базовую модель сессий, а затем возвращаться к расширенной серверной роли.
|
||||||
|
|
||||||
## Зачем нужна фича
|
## Зачем нужна фича
|
||||||
|
|
||||||
У одного пользователя может быть несколько доверенных внутренних homeserver-ов, и каждый из них должен жить как отдельная пользовательская сессия, а не как отдельная особая сущность вне общей модели.
|
У одного пользователя может быть несколько доверенных внутренних саб-серверов, и каждый из них должен жить как отдельная пользовательская сессия, а не как отдельная особая сущность вне общей модели.
|
||||||
|
|
||||||
Это нужно, чтобы:
|
Это нужно, чтобы:
|
||||||
|
|
||||||
- хранить несколько homeserver-ов у одного пользователя одновременно;
|
- хранить несколько саб-серверов у одного пользователя одновременно;
|
||||||
- различать обычные клиентские сессии и серверные сессии по явному типу;
|
- различать обычные клиентские сессии и серверные сессии по явному типу;
|
||||||
- дать расширяемый формат записи с версией;
|
- дать расширяемый формат записи с версией;
|
||||||
- использовать единый подход для DM, звонков и внутренних команд между сессиями.
|
- использовать единый подход для DM, звонков и внутренних команд между сессиями.
|
||||||
@@ -35,18 +35,18 @@
|
|||||||
Предварительные значения:
|
Предварительные значения:
|
||||||
|
|
||||||
- тип `1` - обычная пользовательская сессия;
|
- тип `1` - обычная пользовательская сессия;
|
||||||
- тип `100` - homeserver пользователя;
|
- тип `100` - саб-сервер пользователя;
|
||||||
- версия `1` - первая рабочая версия формата записи сессии.
|
- версия `1` - первая рабочая версия формата записи сессии.
|
||||||
|
|
||||||
На текущем этапе под это уже зарезервирован отдельный блок `SessionsBlock` с `block_type = 55`, а `TrustedStateBlock` остаётся на `50`.
|
На текущем этапе под это уже зарезервирован отдельный блок `SessionsBlock` с `block_type = 55`, а `TrustedStateBlock` остаётся на `50`.
|
||||||
|
|
||||||
Важно: homeserver-ов у одного пользователя может быть несколько.
|
Важно: саб-серверов у одного пользователя может быть несколько.
|
||||||
|
|
||||||
## Архитектурный принцип
|
## Архитектурный принцип
|
||||||
|
|
||||||
Внутренний протокол взаимодействия должен оставаться транспортным.
|
Внутренний протокол взаимодействия должен оставаться транспортным.
|
||||||
|
|
||||||
То есть SHiNE-сервер не должен разбирать прикладной смысл внутренней нагрузки homeserver-а, а должен:
|
То есть SHiNE-сервер не должен разбирать прикладной смысл внутренней нагрузки саб-сервера, а должен:
|
||||||
|
|
||||||
- доставлять сообщения между сессиями;
|
- доставлять сообщения между сессиями;
|
||||||
- доставлять сигналы звонков между сессиями;
|
- доставлять сигналы звонков между сессиями;
|
||||||
@@ -60,7 +60,7 @@
|
|||||||
- Вызов звонка уже рассылается по нескольким активным сессиям пользователя.
|
- Вызов звонка уже рассылается по нескольким активным сессиям пользователя.
|
||||||
- Сигналы звонка уже адресуются конкретной сессии, а stop-сигналы дублируются на остальные сессии того же пользователя.
|
- Сигналы звонка уже адресуются конкретной сессии, а stop-сигналы дублируются на остальные сессии того же пользователя.
|
||||||
|
|
||||||
Иными словами, текущая серверная логика ближе к модели "сервер доставляет между сессиями", чем к модели "сервер понимает внутренний протокол homeserver-а".
|
Иными словами, текущая серверная логика ближе к модели "сервер доставляет между сессиями", чем к модели "сервер понимает внутренний протокол саб-сервера".
|
||||||
|
|
||||||
## Что нужно сделать при возврате к задаче
|
## Что нужно сделать при возврате к задаче
|
||||||
|
|
||||||
@@ -77,7 +77,7 @@
|
|||||||
- правила удаления и обновления записи;
|
- правила удаления и обновления записи;
|
||||||
- правила ротации `sessionPubKey`.
|
- правила ротации `sessionPubKey`.
|
||||||
6. Продумать, как UI и сервер будут отличать тип `1` и тип `100`.
|
6. Продумать, как UI и сервер будут отличать тип `1` и тип `100`.
|
||||||
7. Определить, какие внутренние сообщения homeserver-а останутся полностью прозрачными для SHiNE-сервера, а какие потребуют только технической маршрутизации.
|
7. Определить, какие внутренние сообщения саб-сервера останутся полностью прозрачными для SHiNE-сервера, а какие потребуют только технической маршрутизации.
|
||||||
8. Добавить API/операции чтения и обновления списка сессий, если для этого не хватит существующих механизмов.
|
8. Добавить API/операции чтения и обновления списка сессий, если для этого не хватит существующих механизмов.
|
||||||
9. После реализации обязательно обновить документацию.
|
9. После реализации обязательно обновить документацию.
|
||||||
|
|
||||||
@@ -87,7 +87,7 @@
|
|||||||
- `Dev_Docs/Solana_Architecture/README.md`
|
- `Dev_Docs/Solana_Architecture/README.md`
|
||||||
- `Dev_Docs/Инициализация_Solana_регистрации/README.md`
|
- `Dev_Docs/Инициализация_Solana_регистрации/README.md`
|
||||||
- `Dev_Docs/Keys/README.md`
|
- `Dev_Docs/Keys/README.md`
|
||||||
- `Dev_Docs/Personal_Messages/Протокол_DM_v1.md`, если изменится адресация DM по типам сессий
|
- `Dev_Docs/Personal_Messages/README.md`, если изменится адресация DM по типам сессий
|
||||||
- `Dev_Docs/API/`, если появятся новые серверные операции или изменятся ответы
|
- `Dev_Docs/API/`, если появятся новые серверные операции или изменятся ответы
|
||||||
|
|
||||||
## Что пока не делать
|
## Что пока не делать
|
||||||
@@ -101,5 +101,5 @@
|
|||||||
Продолжать после завершения первой части:
|
Продолжать после завершения первой части:
|
||||||
|
|
||||||
1. описать минимальный формат записи пользовательской сессии;
|
1. описать минимальный формат записи пользовательской сессии;
|
||||||
2. отдельно решить, живут ли homeserver-ы в том же списке, что и обычные сессии;
|
2. отдельно решить, живут ли саб-серверы в том же списке, что и обычные сессии;
|
||||||
3. затем уже проектировать операции регистрации, обновления и отключения таких сессий.
|
3. затем уже проектировать операции регистрации, обновления и отключения таких сессий.
|
||||||
@@ -0,0 +1,94 @@
|
|||||||
|
# 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/<name>/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/<name>/` без отдельного подтверждения.
|
||||||
@@ -8,9 +8,9 @@
|
|||||||
|
|
||||||
В SHiNE у пользователя есть несколько уровней ключей:
|
В SHiNE у пользователя есть несколько уровней ключей:
|
||||||
|
|
||||||
- `root key` - главный (master) ключ пользователя: тот, кто им владеет, управляет пользовательской PDA в Solana и может заменить все остальные ключи. Это не пополняемый кошелёк (комиссии платит `client key`).
|
- `root key` - главный корневой ключ пользователя, он же основной Solana-ключ.
|
||||||
- `blockchain key` - ключ записи в персональный SHiNE-блокчейн пользователя.
|
- `blockchain key` - ключ записи в персональный SHiNE-блокчейн пользователя.
|
||||||
- `client key` - общий ключ пользовательских устройств для повседневной работы, звонков, DM и мелких платежей.
|
- `device key` - общий ключ пользовательских устройств для повседневной работы, звонков, DM и мелких платежей.
|
||||||
- `session key` - ключ конкретной сессии или конкретного устройства для авторизации на сервере.
|
- `session key` - ключ конкретной сессии или конкретного устройства для авторизации на сервере.
|
||||||
|
|
||||||
Главная идея: самые важные ключи можно держать на доверенном серверном или аппаратном устройстве, а обычные клиентские устройства получают только ключи, нужные для текущей работы.
|
Главная идея: самые важные ключи можно держать на доверенном серверном или аппаратном устройстве, а обычные клиентские устройства получают только ключи, нужные для текущей работы.
|
||||||
@@ -28,9 +28,9 @@
|
|||||||
- управление остальными ключами;
|
- управление остальными ключами;
|
||||||
- подтверждение операций, которые должны иметь максимальный уровень доверия.
|
- подтверждение операций, которые должны иметь максимальный уровень доверия.
|
||||||
|
|
||||||
`root key` — это **главный (master) ключ** в следующем смысле: зная `root key`, можно управлять пользовательской PDA-записью в Solana (`create_user_pda` / `update_user_pda`) и тем самым **заменить все остальные ключи** пользователя (device, blockchain, homeserver). Поэтому компрометация `root key` равносильна компрометации всей личности пользователя.
|
В текущей модели `root key` совпадает по смыслу с главным Solana-ключом пользователя.
|
||||||
|
|
||||||
Важно не путать авторитет и кошелёк: `root key` — это авторитет над PDA-записью, а **SOL-комиссии за create/update платит `client key`** (он же fee payer и адрес для пополнения). Подробнее о том, какой ключ за что отвечает на Solana, — в `Dev_Docs/Keys/DERIVATION.md`, §3.
|
На `root key` могут храниться значимые средства, если пользователь сознательно выбирает такую модель. Для мелких текущих расходов предпочтительнее использовать `device key`.
|
||||||
|
|
||||||
## `blockchain key`
|
## `blockchain key`
|
||||||
|
|
||||||
@@ -50,9 +50,9 @@
|
|||||||
|
|
||||||
Рабочая логика по умолчанию должна использовать последнюю актуальную ветку. Старые ветки остаются читаемыми и показывают историю смены ключей.
|
Рабочая логика по умолчанию должна использовать последнюю актуальную ветку. Старые ветки остаются читаемыми и показывают историю смены ключей.
|
||||||
|
|
||||||
## `client key`
|
## `device key`
|
||||||
|
|
||||||
`client key` - общий ключ, который знают доверенные устройства пользователя.
|
`device key` - общий ключ, который знают доверенные устройства пользователя.
|
||||||
|
|
||||||
Назначение:
|
Назначение:
|
||||||
|
|
||||||
@@ -63,11 +63,11 @@
|
|||||||
- derivation Arweave-кошелька;
|
- derivation Arweave-кошелька;
|
||||||
- оплата или подготовка добавления данных в Arweave-кошелек по отдельному протоколу.
|
- оплата или подготовка добавления данных в Arweave-кошелек по отдельному протоколу.
|
||||||
|
|
||||||
Arweave-кошелёк должен выводиться из `client key` по протоколу:
|
Arweave-кошелёк должен выводиться из `device key` по протоколу:
|
||||||
|
|
||||||
- `Dev_Docs/Протоколы/SHINE_ARWEAVE_DERIVATION_V1.md`
|
- `Dev_Docs/Протоколы/SHINE_ARWEAVE_DERIVATION_V1.md`
|
||||||
|
|
||||||
Если пользователь теряет только `client key`, в худшем случае ломается повседневная переписка и доступ конкретных устройств к ежедневным операциям. `root key` и `blockchain key` при правильной архитектуре остаются отдельно защищёнными.
|
Если пользователь теряет только `device key`, в худшем случае ломается повседневная переписка и доступ конкретных устройств к ежедневным операциям. `root key` и `blockchain key` при правильной архитектуре остаются отдельно защищёнными.
|
||||||
|
|
||||||
## `session key`
|
## `session key`
|
||||||
|
|
||||||
@@ -83,7 +83,7 @@ Arweave-кошелёк должен выводиться из `client key` по
|
|||||||
- авторизация сессии на сервере;
|
- авторизация сессии на сервере;
|
||||||
- привязка устройства к пользователю;
|
- привязка устройства к пользователю;
|
||||||
- подтверждение запросов от конкретной сессии;
|
- подтверждение запросов от конкретной сессии;
|
||||||
- доступ к зашифрованному `client key` после успешной авторизации.
|
- доступ к зашифрованному `device key` после успешной авторизации.
|
||||||
|
|
||||||
Одна и та же сессия может быть пригодна для подключения к нескольким серверам пользователя, если архитектура конкретного пользователя это допускает.
|
Одна и та же сессия может быть пригодна для подключения к нескольким серверам пользователя, если архитектура конкретного пользователя это допускает.
|
||||||
|
|
||||||
@@ -108,14 +108,14 @@ Arweave-кошелёк должен выводиться из `client key` по
|
|||||||
Обычное устройство обычно имеет:
|
Обычное устройство обычно имеет:
|
||||||
|
|
||||||
- собственный `session key`;
|
- собственный `session key`;
|
||||||
- зашифрованный `client key`, который открывается после авторизации;
|
- зашифрованный `device key`, который открывается после авторизации;
|
||||||
- доступ к DM, звонкам и обычным пользовательским операциям.
|
- доступ к DM, звонкам и обычным пользовательским операциям.
|
||||||
|
|
||||||
Доверенное серверное или аппаратное устройство может иметь:
|
Доверенное серверное или аппаратное устройство может иметь:
|
||||||
|
|
||||||
- `root key`;
|
- `root key`;
|
||||||
- `blockchain key`;
|
- `blockchain key`;
|
||||||
- `client key`;
|
- `device key`;
|
||||||
- собственный `session key`.
|
- собственный `session key`.
|
||||||
|
|
||||||
Такая сессия может подписывать операции повышенной важности по запросам пользователя.
|
Такая сессия может подписывать операции повышенной важности по запросам пользователя.
|
||||||
@@ -139,7 +139,7 @@ Self-message - это сообщение пользователя самому
|
|||||||
|
|
||||||
Входящее сообщение может быть зашифровано:
|
Входящее сообщение может быть зашифровано:
|
||||||
|
|
||||||
- `client key`;
|
- `device key`;
|
||||||
- `session key`;
|
- `session key`;
|
||||||
- отдельным ключом конкретного чата;
|
- отдельным ключом конкретного чата;
|
||||||
- другим ключом, который уже известен клиенту.
|
- другим ключом, который уже известен клиенту.
|
||||||
@@ -158,13 +158,11 @@ Self-message - это сообщение пользователя самому
|
|||||||
|
|
||||||
## Связанные документы
|
## Связанные документы
|
||||||
|
|
||||||
- `Dev_Docs/Keys/DERIVATION.md` - **источник истины по конкретной деривации** секрета и ключей (формулы Argon2id, `base64|suffix→SHA-256→Ed25519`, суффиксы `root.key`/`bch.key`/`client.key`/`homeserver.key:<имя>`, Solana-ключ, ссылки на код).
|
- `Dev_Docs/Personal_Messages/README.md` - текущая документация личных сообщений.
|
||||||
- `Dev_Docs/Personal_Messages/Протокол_DM_v1.md` - текущая логическая документация личных сообщений.
|
|
||||||
- `Dev_Docs/Personal_Messages/Формат_DM_v1.md` - точный байтовый формат личных сообщений.
|
|
||||||
- `Dev_Docs/Blockchain/README.md` - точка входа по форматам SHiNE-блокчейна.
|
- `Dev_Docs/Blockchain/README.md` - точка входа по форматам SHiNE-блокчейна.
|
||||||
- `Dev_Docs/Solana_Architecture/README.md` - архитектура Solana-программ, PDA-счетов, DAO и движения средств.
|
- `Dev_Docs/Solana_Architecture/README.md` - архитектура Solana-программ, PDA-счетов, DAO и движения средств.
|
||||||
- `Dev_Docs/Инициализация_Solana_регистрации/README.md` - деплой и первичная инициализация Solana-регистрации.
|
- `Dev_Docs/Инициализация_Solana_регистрации/README.md` - деплой и первичная инициализация Solana-регистрации.
|
||||||
- `Dev_Docs/Протоколы/SHINE_ARWEAVE_DERIVATION_V1.md` - derivation Arweave-кошелька из `client key`.
|
- `Dev_Docs/Протоколы/SHINE_ARWEAVE_DERIVATION_V1.md` - derivation Arweave-кошелька из `device key`.
|
||||||
|
|
||||||
## Что нужно уточнить перед реализацией
|
## Что нужно уточнить перед реализацией
|
||||||
|
|
||||||
@@ -173,5 +171,5 @@ Self-message - это сообщение пользователя самому
|
|||||||
- какие операции требуют `root key`, а какие достаточно подписывать `blockchain key`;
|
- какие операции требуют `root key`, а какие достаточно подписывать `blockchain key`;
|
||||||
- формат self-message-команд;
|
- формат self-message-команд;
|
||||||
- порядок перебора ключей при расшифровке входящих сообщений;
|
- порядок перебора ключей при расшифровке входящих сообщений;
|
||||||
- правила ротации `client key` и восстановления доступа после потери устройства;
|
- правила ротации `device key` и восстановления доступа после потери устройства;
|
||||||
- какие типы серверных и аппаратных сессий нужны в первой реализации.
|
- какие типы серверных и аппаратных сессий нужны в первой реализации.
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
# Кошелёк: лимит/закрепление блокчейна Сияния
|
||||||
|
|
||||||
|
- статус: `pending`
|
||||||
|
|
||||||
|
## Кратко что сделано
|
||||||
|
|
||||||
|
- На экране `Кошелёк -> Блокчейн Сияния` добавлены 2 слоя данных:
|
||||||
|
- фактическое состояние цепочки на сервере (`кол-во блоков`, `размер`, `крайний блок`, `hash`, `размер крайнего блока`);
|
||||||
|
- закреплённое состояние в Solana PDA (`лимит`, `использовано`, `остаток`, `крайний блок`, `hash`).
|
||||||
|
- Добавлены действия:
|
||||||
|
- `Закрепить в Solana` — обновляет PDA до текущего состояния серверной цепочки;
|
||||||
|
- `Увеличить лимит` — увеличивает `paid_limit_bytes` в PDA с учётом цены из economy PDA.
|
||||||
|
- Если `rootKey`/`blockchainKey` не сохранены локально, экран запрашивает пароль, восстанавливает ключи через стандартную derivation-логику и предлагает сохранить их в зашифрованный контейнер.
|
||||||
|
|
||||||
|
## Что проверять вручную
|
||||||
|
|
||||||
|
1. Открыть `Кошелёк -> Блокчейн Сияния` под авторизованным пользователем.
|
||||||
|
2. Проверить, что в блоке "Фактическое состояние на сервере" отображаются:
|
||||||
|
- число блоков;
|
||||||
|
- размер цепочки;
|
||||||
|
- номер/хэш крайнего блока;
|
||||||
|
- размер крайнего блока.
|
||||||
|
3. Проверить, что в блоке "Закреплено в Solana" отображаются:
|
||||||
|
- лимит;
|
||||||
|
- израсходовано;
|
||||||
|
- остаток;
|
||||||
|
- номер/хэш крайнего закреплённого блока.
|
||||||
|
4. Нажать `Закрепить в Solana` и убедиться, что:
|
||||||
|
- приходит успешная транзакция;
|
||||||
|
- после обновления Solana-показатели подтягиваются до серверных (или максимально близко по актуальному состоянию).
|
||||||
|
5. Нажать `Увеличить лимит`, ввести значение кратное шагу, подтвердить списание и проверить:
|
||||||
|
- лимит увеличился;
|
||||||
|
- отображение цены/списания соответствует economy PDA.
|
||||||
|
6. Повторить пункты 4-5 в сценарии, когда `rootKey`/`blockchainKey` не сохранены, и проверить:
|
||||||
|
- появляется запрос пароля;
|
||||||
|
- после ввода пароля операции выполняются;
|
||||||
|
- предложение сохранить ключи показывается.
|
||||||
|
|
||||||
|
## Ожидаемый результат
|
||||||
|
|
||||||
|
- Экран корректно разделяет "фактическое состояние на сервере" и "закреплённое в Solana".
|
||||||
|
- Обе операции (`Закрепить в Solana`, `Увеличить лимит`) выполняются без ошибок при валидных данных.
|
||||||
|
- Восстановление ключей через пароль работает, а без нужных ключей операция не выполняется молча.
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
# Озвучивание ответов агента
|
||||||
|
|
||||||
|
## Что сделано
|
||||||
|
|
||||||
|
В локальный Telegram-бот-сервис агента-кодера добавлены персональные настройки озвучивания финальных ответов:
|
||||||
|
|
||||||
|
- `/voice_on` включает озвучивание для текущего Telegram-пользователя;
|
||||||
|
- `/voice_off` выключает озвучивание для текущего Telegram-пользователя;
|
||||||
|
- `/voice_status` показывает текущее состояние;
|
||||||
|
- если озвучивание включено, после текстового финального ответа сервис генерирует voice-файл через OpenAI TTS и отправляет его в Telegram;
|
||||||
|
- длинные ответы делятся на несколько фрагментов озвучки.
|
||||||
|
|
||||||
|
## Что проверять
|
||||||
|
|
||||||
|
1. Перезапустить `shine-agent-bot-coder`.
|
||||||
|
2. Отправить `/voice_status` и убедиться, что по умолчанию озвучивание выключено.
|
||||||
|
3. Отправить `/voice_on`.
|
||||||
|
4. Дать простую задачу агенту и проверить, что пришёл полный текстовый ответ и voice-файл с тем же ответом.
|
||||||
|
5. Отправить `/voice_off`.
|
||||||
|
6. Дать ещё одну простую задачу и проверить, что приходит только текст.
|
||||||
|
7. При возможности проверить второго whitelist-пользователя: его настройка должна быть независимой.
|
||||||
|
|
||||||
|
## Ожидаемый результат
|
||||||
|
|
||||||
|
Настройка хранится персонально по username и сохраняется после перезапуска сервиса. При включённой настройке Telegram получает текстовый ответ и дополнительное voice-сообщение с озвучкой. При выключенной настройке поведение остаётся прежним.
|
||||||
|
|
||||||
|
## Статус
|
||||||
|
|
||||||
|
pending
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
# Голосовая адаптация ответов Telegram-бота
|
||||||
|
|
||||||
|
## Краткое описание
|
||||||
|
Добавлены персональные настройки голосовых ответов и адаптации текста перед озвучкой. Если голосовые ответы включены, сервис перед TTS может отдельно прогонять финальный текст через OpenAI-модель и отправлять более короткую голосовую версию в исходный чат, личный чат пользователя и общий чат `@shine_writing`, если эти чаты доступны и отличаются.
|
||||||
|
|
||||||
|
## Что проверить
|
||||||
|
- Команды `/voice_on`, `/voice_off`, `/voice_status` для конкретного пользователя.
|
||||||
|
- Команды `/voice_rewrite_on`, `/voice_rewrite_off`, `/voice_rewrite_status` для конкретного пользователя.
|
||||||
|
- Команда `/status` показывает очередь, голосовые ответы и адаптацию текста перед озвучкой.
|
||||||
|
- При включённых голосовых ответах после задачи приходит текстовый ответ и voice-ответ.
|
||||||
|
- При включённой адаптации voice-ответ короче и без длинных технических строк.
|
||||||
|
- При задаче из личного чата voice дополнительно появляется в общем чате `@shine_writing`.
|
||||||
|
- При задаче из общего чата voice дополнительно появляется в личном чате пользователя, если сервис уже знает его личный chat_id.
|
||||||
|
|
||||||
|
## Ожидаемый результат
|
||||||
|
Текстовый ответ остаётся полным. Голосовая версия приходит отдельно, звучит короче и естественнее, а персональные настройки одного пользователя не меняют поведение других пользователей.
|
||||||
|
|
||||||
|
## Статус
|
||||||
|
pending
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
# Эксперимент Understand Anything
|
||||||
|
|
||||||
|
## Краткое описание
|
||||||
|
|
||||||
|
Добавлена изолированная лаборатория для проверки `Lum1104/Understand-Anything` без подключения к сборке, деплою и рабочему коду SHiNE.
|
||||||
|
|
||||||
|
## Что проверять
|
||||||
|
|
||||||
|
- Установить Node.js 22+ и pnpm 10+.
|
||||||
|
- Запустить `./tools/understand-anything-lab/install_codex_skills.sh`.
|
||||||
|
- Перезапустить Codex-сессию.
|
||||||
|
- Выполнить `/understand --language ru` в корне проекта.
|
||||||
|
- После генерации выполнить `/understand-dashboard` и проверить, что граф открывается и помогает ориентироваться по серверным, UI, Solana и агентским папкам.
|
||||||
|
|
||||||
|
## Ожидаемый результат
|
||||||
|
|
||||||
|
- В проекте появляется локальная папка `.understand-anything/` с графом знаний.
|
||||||
|
- Dashboard открывается и показывает интерактивный граф проекта.
|
||||||
|
- Основные процессы сборки и деплоя SHiNE не меняются.
|
||||||
|
|
||||||
|
## Статус
|
||||||
|
|
||||||
|
`pending`
|
||||||
|
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
# Центр задач Telegram-агента
|
||||||
|
|
||||||
|
## Краткое описание
|
||||||
|
|
||||||
|
Добавлена первая версия центра задач и предложений внутри `SHiNE-agent-bot-coder`.
|
||||||
|
|
||||||
|
Бот хранит задачи и предложения в JSON-файле данных сервиса, умеет показывать список через `/tasks`, создавать задачи для игроков по фразе Айдара, принимать предложения игроков по префиксу `предложение:`, менять статусы и добавлять короткие напоминания после ответов.
|
||||||
|
|
||||||
|
## Что проверять
|
||||||
|
|
||||||
|
- Айдар пишет `/tasks` и видит текущий список задач и предложений без уже закрытого предложения от Димы.
|
||||||
|
- Айдар пишет `поставь задачу Милане: проверить описание SHiNE` и задача появляется в списке Миланы.
|
||||||
|
- Милана пишет `/tasks` и видит назначенную задачу.
|
||||||
|
- Игрок пишет `предложение: ...`, после чего предложение появляется у Айдара.
|
||||||
|
- Айдар меняет статус фразами вида `одобрить TC-XXXX`, `доработать TC-XXXX`, `закрыть TC-XXXX`, где `TC-XXXX` - ID существующей задачи или предложения.
|
||||||
|
- После обычного ответа бота Айдару или игроку появляется короткое напоминание, если у пользователя есть активные задачи.
|
||||||
|
|
||||||
|
## Ожидаемый результат
|
||||||
|
|
||||||
|
Задачи и предложения сохраняются между перезапусками сервиса, статусы меняются корректно, напоминания не мешают основному ответу Codex.
|
||||||
|
|
||||||
|
## Статус
|
||||||
|
|
||||||
|
pending
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
# Рестарты и voice-настройки Telegram-агента
|
||||||
|
|
||||||
|
## Краткое описание
|
||||||
|
|
||||||
|
Добавлена первая версия безопасного рестарта Telegram-агента:
|
||||||
|
|
||||||
|
- `/restart` и `/restart_service` ставят отложенный рестарт после текущей задачи и до взятия следующей;
|
||||||
|
- `/restart_hard`, `/restart_now`, `/restart_force` выполняют жёсткий рестарт сразу;
|
||||||
|
- команды рестарта доступны только Айдару;
|
||||||
|
- voice-ответы включены по умолчанию для новых пользователей;
|
||||||
|
- адаптация текста перед озвучкой стала ближе к исходному ответу и не должна менять смысл;
|
||||||
|
- скрыты отдельные команды статуса voice-функций из справки, состояние показывается через `/status`.
|
||||||
|
|
||||||
|
## Что проверить
|
||||||
|
|
||||||
|
1. Отправить `/restart` во время активной задачи игрока или Айдара.
|
||||||
|
2. Убедиться, что активная задача завершается, после чего сервис перезапускается до следующей задачи.
|
||||||
|
3. Отправить `/restart_hard` и убедиться, что сервис перезапускается сразу.
|
||||||
|
4. Проверить, что игрок не может выполнить команды рестарта.
|
||||||
|
5. Проверить `/status`: он показывает очередь и состояния голосовых функций.
|
||||||
|
6. Проверить нового пользователя: voice-ответы должны быть включены по умолчанию.
|
||||||
|
7. Проверить текстовый запрос пользователя с включённым voice: после текстового ответа должен прийти voice-файл.
|
||||||
|
8. Проверить, что адаптированная озвучка не превращается в другой ответ, а только убирает длинные технические строки.
|
||||||
|
|
||||||
|
## Ожидаемый результат
|
||||||
|
|
||||||
|
Сервис можно обновлять без потери текущей задачи через отложенный рестарт. Жёсткий рестарт остаётся аварийной командой Айдара. Voice-ответы работают для текстовых и голосовых запросов, а голосовая версия остаётся близкой к текстовой.
|
||||||
|
|
||||||
|
## Статус
|
||||||
|
|
||||||
|
pending
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
# Кнопки вкладки «Каналы»
|
||||||
|
|
||||||
|
## Что сделано
|
||||||
|
|
||||||
|
Доработана верхняя панель вкладки «Каналы»:
|
||||||
|
- при открытии нижней кнопкой «Каналы» показывается режим «Все каналы»;
|
||||||
|
- в режиме «Все каналы» справа доступны кнопка «Мои каналы» и иконка поиска канала;
|
||||||
|
- в режиме «Мои каналы» доступен переход обратно во «Все каналы», а справа показывается плюсик создания канала.
|
||||||
|
|
||||||
|
## Что проверить
|
||||||
|
|
||||||
|
1. Открыть вкладку «Каналы» через нижнюю навигацию.
|
||||||
|
2. Убедиться, что открыт режим «Все каналы», а плюсик создания канала не отображается.
|
||||||
|
3. Нажать иконку поиска в режиме «Все каналы».
|
||||||
|
4. Убедиться, что открывается текущий сценарий поиска каналов.
|
||||||
|
5. Нажать «Мои каналы».
|
||||||
|
6. Убедиться, что справа появился плюсик создания канала.
|
||||||
|
7. Нажать «Все каналы» или стрелку назад и проверить возврат к режиму «Все каналы».
|
||||||
|
|
||||||
|
## Ожидаемый результат
|
||||||
|
|
||||||
|
Кнопки верхней панели соответствуют активному режиму: поиск в «Все каналы», создание только в «Мои каналы».
|
||||||
|
|
||||||
|
## Статус
|
||||||
|
|
||||||
|
pending
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
# Длинные voice/audio в Telegram-боте агента
|
||||||
|
|
||||||
|
- краткое описание фичи:
|
||||||
|
Бот теперь умеет обрабатывать длинные voice/audio аккуратнее: учитывает лимит Telegram Bot API на скачивание слишком больших файлов, поддерживает альтернативный `TELEGRAM_API_BASE_URL` для локального `telegram-bot-api`, локально пережимает длинное аудио через `ffmpeg`, режет на куски и отправляет их в OpenAI transcription последовательно.
|
||||||
|
- что именно проверять:
|
||||||
|
1. Короткий `voice` по-прежнему распознаётся без заметной задержки.
|
||||||
|
2. Длинный `audio/voice`, который помещается в скачивание Telegram, успешно пережимается, режется на части и даёт цельную расшифровку.
|
||||||
|
3. Очень большой файл через обычный `https://api.telegram.org` даёт понятное сообщение про лимит Telegram.
|
||||||
|
4. После переключения на локальный `telegram-bot-api` такой же большой файл начинает скачиваться и распознаваться.
|
||||||
|
- ожидаемый результат:
|
||||||
|
Бот не падает на длинных аудио, даёт либо расшифровку, либо понятное объяснение, какой именно лимит мешает и что нужно включить.
|
||||||
|
- статус:
|
||||||
|
pending
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
# Диагностика больших voice/audio в Telegram-боте
|
||||||
|
|
||||||
|
- краткое описание фичи:
|
||||||
|
- Бот при большом voice/audio больше не отказывается заранее по метаданным Telegram. Теперь он сначала сообщает, что пробует скачать файл, затем отдельно сообщает об успешном скачивании и только после этого переходит к подготовке аудио и распознаванию через OpenAI.
|
||||||
|
- что именно проверять:
|
||||||
|
- Отправить в бота большой `voice` или `audio`, который раньше попадал под ранний отказ.
|
||||||
|
- Проверить, что сначала приходит сообщение о попытке скачать большой файл.
|
||||||
|
- Проверить два сценария:
|
||||||
|
- скачивание удалось: бот пишет об успешной загрузке и продолжает распознавание;
|
||||||
|
- скачивание не удалось: бот пишет именно о неудачном скачивании из Telegram, без ложной привязки к ошибке OpenAI.
|
||||||
|
- ожидаемый результат:
|
||||||
|
- Пользователь видит понятную поэтапную диагностику: попытка скачивания, результат скачивания и только потом следующий этап обработки.
|
||||||
|
- статус:
|
||||||
|
- pending
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
# Перенос server UI в shine-UI
|
||||||
|
|
||||||
|
- краткое описание фичи:
|
||||||
|
Веб-панель управления серверной Solana PDA перенесена в `shine-UI/` как отдельные страницы.
|
||||||
|
Новая точка входа: `shine-UI/server-ui.html`.
|
||||||
|
Общая логика работы с PDA вынесена в единый модуль `shine-UI/js/services/shine-user-pda-service.js`.
|
||||||
|
|
||||||
|
- что именно проверять:
|
||||||
|
1. Открытие `shine-UI/server-ui.html` и переходы на страницы создания и обновления PDA.
|
||||||
|
2. Генерацию ключей из логина и пароля на странице создания.
|
||||||
|
3. Ручной ввод base58-ключей и регистрацию серверного PDA.
|
||||||
|
4. Загрузку существующей серверной PDA на странице обновления.
|
||||||
|
5. Обновление `server_address` и `sync_servers` только по `root + device` без blockchain-ключа.
|
||||||
|
6. Корректное чтение нового формата `ServerProfileBlock` через общий PDA-модуль.
|
||||||
|
7. То, что актуальной точкой входа остаётся `shine-UI/server-ui.html`.
|
||||||
|
|
||||||
|
- ожидаемый результат:
|
||||||
|
1. Новые страницы открываются без JS-ошибок.
|
||||||
|
2. Создание серверной PDA проходит через общий модуль и пишет актуальный формат.
|
||||||
|
3. Обновление серверной PDA переиспользует существующую подпись LastBlockState и не требует blockchain-ключ.
|
||||||
|
4. Клиентский UI не ломается после перевода общего PDA-слоя на новый формат.
|
||||||
|
|
||||||
|
- статус:
|
||||||
|
pending
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
# Кнопка настройки сервера и DEVNET topup
|
||||||
|
|
||||||
|
- краткое описание фичи:
|
||||||
|
На экране `entry-settings-view` добавлена кнопка `Настроить свой сервер`, открывающая `server-ui.html` в новой вкладке.
|
||||||
|
На страницах серверного UI добавлена кнопка открытия `devnet-topup-view` в новой вкладке с автоматической передачей `wallet` из device-адреса.
|
||||||
|
|
||||||
|
- что именно проверять:
|
||||||
|
1. На странице настроек входа есть кнопка `Настроить свой сервер`.
|
||||||
|
2. Кнопка открывает `shine-UI/server-ui.html` в новой вкладке.
|
||||||
|
3. На страницах `create-server-pda.html` и `update-server-pda.html` есть кнопка `Открыть пополнение DEVNET`.
|
||||||
|
4. Если device public key заполнен, новая вкладка открывает `devnet-topup-view?wallet=...` с правильным адресом.
|
||||||
|
5. Если device-адрес не введён, серверный UI показывает понятную ошибку и не открывает пустую ссылку.
|
||||||
|
|
||||||
|
- ожидаемый результат:
|
||||||
|
1. Переход в серверный UI с клиентской страницы настроек работает.
|
||||||
|
2. Пополнение devnet из серверного UI открывается сразу на нужный адрес.
|
||||||
|
3. Основной клиентский UI и серверные страницы не получают JS-ошибок при загрузке.
|
||||||
|
|
||||||
|
- статус:
|
||||||
|
pending
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
# Фикс DEVNET topup и автоподстановки пароля
|
||||||
|
|
||||||
|
- статус: pending
|
||||||
|
- кратко: исправлена ширина экрана `devnet-topup-view` после успешного пополнения и отключена нежелательная автоподстановка пароля в server UI и на экранах входа/регистрации.
|
||||||
|
|
||||||
|
## Что проверять
|
||||||
|
- Открыть страницу пополнения DEVNET, выполнить пополнение и убедиться, что после появления `Signature` экран не расширяется по ширине.
|
||||||
|
- Проверить, что кнопки на странице пополнения остаются аккуратными и не разъезжаются.
|
||||||
|
- Открыть `server-ui/update-server-pda.html`, загрузить PDA и убедиться, что поле пароля остаётся пустым.
|
||||||
|
- Проверить обычные экраны входа и регистрации: поле пароля не должно самопроизвольно заполняться длинной строкой.
|
||||||
|
|
||||||
|
## Ожидаемый результат
|
||||||
|
- Длинная transaction signature переносится по строкам внутри прежней ширины экрана.
|
||||||
|
- Кнопки сохраняют компактный mobile-first layout.
|
||||||
|
- Поля пароля пустые, пока пользователь сам ничего не вводил.
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
# Диагностика ключей server PDA и баланс device
|
||||||
|
|
||||||
|
- статус: pending
|
||||||
|
- кратко: на странице обновления server PDA добавлена сверка ожидаемых ключей с уже загруженной PDA, предупреждение о неверном пароле, кнопка показа баланса device-аккаунта и уточнение, что create/update оплачиваются с deviceKey.
|
||||||
|
|
||||||
|
## Что проверять
|
||||||
|
- На `update-server-pda.html` загрузить существующую PDA и убедиться, что видны ожидаемые `root/blockchain/device` public key.
|
||||||
|
- Ввести правильный пароль и нажать `Сгенерировать`: должно появиться сообщение, что ключи совпадают.
|
||||||
|
- Ввести неверный пароль и нажать `Сгенерировать`: должно появиться сообщение, что ключи не совпали и пароль, вероятно, неверный.
|
||||||
|
- На `create-server-pda.html` и `update-server-pda.html` нажать `Показать / обновить баланс device` и убедиться, что баланс читается по текущему `devPub`.
|
||||||
|
- Повторить `update_user_pda` после увеличения `heap frame` и проверить, ушла ли ошибка `memory allocation failed`.
|
||||||
|
|
||||||
|
## Ожидаемый результат
|
||||||
|
- Пользователь видит, какие именно public key должны получиться для загруженной PDA.
|
||||||
|
- Ошибка неправильного пароля выявляется до отправки транзакции.
|
||||||
|
- Баланс device-кошелька читается прямо со страницы.
|
||||||
|
- Если проблема `OOM` была только в размере heap frame/compute budget клиента, `update_user_pda` начинает проходить.
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
# Lazy-import Solana PDA: актуальный формат
|
||||||
|
|
||||||
|
- Краткое описание:
|
||||||
|
Серверный Java lazy-import пользователя из `shine_users` обновлён под актуальный формат `user_pda`. Убран RPC-фильтр по размеру PDA, добавлен разбор нового `ServerProfileBlock` (`block_type = 30`) без сохранения server-only полей в `solana_users`.
|
||||||
|
- Что проверять:
|
||||||
|
1. Взять логин пользователя, который существует в Solana PDA, но отсутствует в локальной таблице `solana_users`.
|
||||||
|
2. Выполнить вход этим логином через сервер.
|
||||||
|
3. Убедиться, что lazy-import подтянул пользователя из Solana.
|
||||||
|
4. Убедиться, что запись в `solana_users` создана с полями `login`, `blockchain_name`, `solana_key`, `blockchain_key`, `device_key`.
|
||||||
|
5. Убедиться, что отсутствие/наличие server-полей в PDA не ломает импорт.
|
||||||
|
- Ожидаемый результат:
|
||||||
|
1. Пользователь успешно находится и импортируется из Solana PDA независимо от фактического размера PDA.
|
||||||
|
2. Новый `ServerProfileBlock` не ломает парсер.
|
||||||
|
3. В БД не появляются лишние server-only поля.
|
||||||
|
- Статус: `pending`
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
# Pure Rust `shine_users` и `shine_login_guard`
|
||||||
|
|
||||||
|
Статус: `pending`
|
||||||
|
|
||||||
|
## Что сделано
|
||||||
|
|
||||||
|
- `shine_login_guard` переписан без Anchor на чистый Rust/Solana SDK.
|
||||||
|
- `shine_users` переписан без Anchor на чистый Rust/Solana SDK.
|
||||||
|
- Для `shine_users` введён новый instruction ABI без Anchor discriminator'ов.
|
||||||
|
- Для `shine_users` используются новые seed'ы:
|
||||||
|
- `user_login=` для `user_pda`
|
||||||
|
- `shine_users_economy_config` для economy PDA
|
||||||
|
- Формат блоков PDA синхронизирован:
|
||||||
|
- `SessionsBlock = 50`
|
||||||
|
- `TrustedStateBlock = 70`
|
||||||
|
- UI JS-модуль и Java lazy-import обновлены под новые seeds/ABI/коды блоков.
|
||||||
|
|
||||||
|
## Что проверить руками
|
||||||
|
|
||||||
|
1. В обычном UI выполнить регистрацию нового пользователя в Solana.
|
||||||
|
2. Проверить, что после регистрации читается новая `user_pda`.
|
||||||
|
3. В server UI выполнить создание server PDA.
|
||||||
|
4. В server UI выполнить update server PDA.
|
||||||
|
5. Проверить, что после update растёт `record_number`.
|
||||||
|
6. Проверить, что lazy-import на сервере читает новый формат PDA без ошибок.
|
||||||
|
7. Проверить, что старые Anchor discriminator'ы больше нигде не требуются.
|
||||||
|
|
||||||
|
## Ожидаемый результат
|
||||||
|
|
||||||
|
- Регистрация и update работают на новых чисто-rust программах.
|
||||||
|
- UI не использует старый Anchor ABI.
|
||||||
|
- Серверный Java parser читает новый формат PDA.
|
||||||
|
- Ошибок `out of memory` и anchor-specific падений больше нет.
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
# ESP32 Argon2/UI совместимость и экран результата
|
||||||
|
|
||||||
|
- краткое описание фичи:
|
||||||
|
выравнивание derivation на `ESP32` с текущим `UI` по нормализации логина, совместимости `master secret`/`root.key`/`bch.key`/`dev.key`, а также правки экрана результата и progress bar.
|
||||||
|
|
||||||
|
- что именно проверять:
|
||||||
|
1. На `UI` и `ESP32` ввести один и тот же логин в разном регистре, например `Anya24`, и один и тот же непустой пароль.
|
||||||
|
2. Убедиться, что после нормализации логина на `ESP32` и `UI` получаются одинаковые:
|
||||||
|
`master secret`, `root`, `blockchain`, `device` в `Base58`.
|
||||||
|
3. Проверить режим пустого пароля:
|
||||||
|
`UI` и `ESP32` должны выдать одинаковые ключи в legacy-режиме.
|
||||||
|
4. Проверить, что пустой логин на `ESP32` не запускает расчёт и показывает сообщение об ошибке.
|
||||||
|
5. Проверить progress bar:
|
||||||
|
при непустом пароле полоса должна быть видна и двигаться.
|
||||||
|
6. Проверить экран результата:
|
||||||
|
сначала `Login`, затем `Password`, затем `Master secret` и ключи;
|
||||||
|
свайп вверх/вниз должен прокручивать длинный результат без артефактов.
|
||||||
|
|
||||||
|
- ожидаемый результат:
|
||||||
|
`ESP32` и `UI` считают одинаковый `master secret` и одинаковые ключи для одинаковых входных данных;
|
||||||
|
progress bar виден;
|
||||||
|
экран результата читаемый и корректно прокручивается.
|
||||||
|
|
||||||
|
- статус:
|
||||||
|
pending
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
## Краткое описание
|
||||||
|
В `SHiNE-agent-bot-coder` для личных чатов добавлен режим одного редактируемого статусного сообщения. Бот принимает запрос, обновляет это сообщение по этапам обработки и в конце превращает его в финальный текстовый ответ. При длинном ответе допускается ещё одно дополнительное текстовое сообщение с продолжением. Голосовой ответ остаётся отдельным сообщением.
|
||||||
|
|
||||||
|
## Что проверять
|
||||||
|
1. Отправить в личный чат короткий текстовый запрос и убедиться, что бот не шлёт цепочку промежуточных сообщений, а редактирует одно сообщение до финального ответа.
|
||||||
|
2. Отправить в личный чат `voice` или `audio` и убедиться, что в том же сообщении последовательно видны этапы распознавания и выполнения.
|
||||||
|
3. Проверить длинный ответ, который не помещается в один Telegram message: должно получиться не больше двух текстовых сообщений.
|
||||||
|
4. Проверить, что `voice`-ответ приходит отдельным новым сообщением после текстового.
|
||||||
|
5. Проверить, что в `@shine_writing` по-прежнему логируются только итоговые `вопрос -> ответ`, без промежуточных статусов.
|
||||||
|
|
||||||
|
## Ожидаемый результат
|
||||||
|
- В личке основная переписка стала чище: промежуточные этапы живут в одном редактируемом сообщении.
|
||||||
|
- При длинном ответе бот не разбрасывает ответ на много сообщений.
|
||||||
|
- Канал `@shine_writing` работает по старой схеме без лишнего шума.
|
||||||
|
|
||||||
|
## Статус
|
||||||
|
`pending`
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
## Краткое описание
|
||||||
|
В локальный Telegram-бот `SHiNE-agent-bot-coder` добавлена команда `/settings`, которая сразу показывает текущие персональные настройки пользователя и список доступных команд для их изменения. В `/help` оставлена только ссылка на `/settings` без перечисления самих команд настроек. Также добавлен переключатель режима ответа в личке: один редактируемый статус или отдельные сообщения по этапам.
|
||||||
|
|
||||||
|
## Что проверять
|
||||||
|
1. Отправить `/help` и убедиться, что в справке есть `/settings`, но нет списка команд `/voice_*` и `/single_message_*`.
|
||||||
|
2. Отправить `/settings` и проверить, что бот показывает текущие значения:
|
||||||
|
- озвучивание финальных ответов;
|
||||||
|
- адаптацию текста перед озвучкой;
|
||||||
|
- режим одного редактируемого сообщения в личке.
|
||||||
|
3. По очереди переключить:
|
||||||
|
- `/voice_on` и `/voice_off`;
|
||||||
|
- `/voice_rewrite_on` и `/voice_rewrite_off`;
|
||||||
|
- `/single_message_on` и `/single_message_off`.
|
||||||
|
4. После каждого переключения снова вызвать `/settings` и убедиться, что статус изменился и сохранился.
|
||||||
|
5. При `/single_message_on` отправить обычный запрос в личку и проверить, что бот ведёт его через одно редактируемое сообщение.
|
||||||
|
6. При `/single_message_off` отправить обычный запрос в личку и проверить, что бот снова шлёт отдельные сообщения по этапам и отдельный финальный ответ.
|
||||||
|
|
||||||
|
## Ожидаемый результат
|
||||||
|
- `/settings` стал основной точкой входа для пользовательских настроек.
|
||||||
|
- `/help` стал короче и не дублирует список команд настроек.
|
||||||
|
- Режим ответа в личке реально переключается персонально для пользователя и сохраняется после перезапуска сервиса.
|
||||||
|
|
||||||
|
## Статус
|
||||||
|
`pending`
|
||||||
@@ -0,0 +1,210 @@
|
|||||||
|
# Shine Payments: e2e после переписи без Anchor и добавления Q3
|
||||||
|
|
||||||
|
## Краткое описание
|
||||||
|
|
||||||
|
Нужно вручную и через вспомогательные CLI-проверки подтвердить, что программа `shine_payments` после:
|
||||||
|
|
||||||
|
- переписи на чистый `solana_program`;
|
||||||
|
- отказа от `programs/common`;
|
||||||
|
- добавления очереди `Q3`;
|
||||||
|
- обновления HTML UI;
|
||||||
|
|
||||||
|
корректно работает на devnet с новым `program id`.
|
||||||
|
|
||||||
|
Отличие от финального боевого сценария:
|
||||||
|
|
||||||
|
- вместо DAO-механики используется обычный кошелёк `FUc28vNixp7F3nnkpGVt6nuJbgvJ4429v4B5wS52Df6P`, которому даны права DAO на изменение коэффициента и выдачу лимитов менеджеру.
|
||||||
|
|
||||||
|
## Что именно проверять
|
||||||
|
|
||||||
|
### 1. Подготовка окружения
|
||||||
|
|
||||||
|
Проверить и зафиксировать:
|
||||||
|
|
||||||
|
- новый keypair программы `shine_payments`;
|
||||||
|
- новый `program id`;
|
||||||
|
- обновление `program id` в HTML UI и связанных настройках;
|
||||||
|
- наличие deploy authority, которой можно закрыть старый buffer/programdata, если это технически доступно;
|
||||||
|
- адреса тестовых кошельков:
|
||||||
|
- DAO/базовый кошелёк;
|
||||||
|
- менеджер;
|
||||||
|
- покупатель 1;
|
||||||
|
- покупатель 2;
|
||||||
|
- получатели выплат.
|
||||||
|
|
||||||
|
### 2. Очистка/смена старой программы
|
||||||
|
|
||||||
|
Проверить один из сценариев:
|
||||||
|
|
||||||
|
- если возможно, закрыть старый `program buffer/programdata` текущими ключами;
|
||||||
|
- если закрытие невозможно или нецелесообразно, зафиксировать это и продолжить с новым `program id`.
|
||||||
|
|
||||||
|
Отдельно проверить, что старые PDA предыдущей версии не используются новой программой.
|
||||||
|
|
||||||
|
### 3. Деплой и init новой программы
|
||||||
|
|
||||||
|
Проверить:
|
||||||
|
|
||||||
|
- `cargo build-sbf` проходит;
|
||||||
|
- новая программа деплоится на devnet;
|
||||||
|
- `init` выполняется один раз на пустых PDA;
|
||||||
|
- после `init` читаются:
|
||||||
|
- `config`;
|
||||||
|
- `coef_limit`;
|
||||||
|
- `queues`;
|
||||||
|
- `inflow_vault`.
|
||||||
|
|
||||||
|
Сразу после `init` запросить состояние очередей и зафиксировать, что:
|
||||||
|
|
||||||
|
- `Q1`, `Q2`, `Q3` пустые;
|
||||||
|
- `tickets_total = 0`;
|
||||||
|
- `tickets_paid = 0`;
|
||||||
|
- все суммы равны `0`.
|
||||||
|
|
||||||
|
### 4. Проверка покупки билета
|
||||||
|
|
||||||
|
На минимальных суммах проверить:
|
||||||
|
|
||||||
|
1. покупку через `buy_ticket_usd`;
|
||||||
|
2. покупку через `buy_ticket_sol`;
|
||||||
|
3. при необходимости ещё один вызов базового `buy_ticket`.
|
||||||
|
|
||||||
|
После каждой покупки:
|
||||||
|
|
||||||
|
- запросить состояние `Q1`;
|
||||||
|
- убедиться, что создался следующий ticket;
|
||||||
|
- проверить рост:
|
||||||
|
- `q1_tickets_total`;
|
||||||
|
- `q1_sum_total_usd_cents`;
|
||||||
|
- убедиться, что деньги покупки ушли в `dao_wallet`, а не в `inflow_vault`.
|
||||||
|
|
||||||
|
### 5. Проверка DAO-управления
|
||||||
|
|
||||||
|
Проверить:
|
||||||
|
|
||||||
|
1. изменение коэффициента через `update_coef_limit`;
|
||||||
|
2. повторный запрос `coef_limit` и подтверждение нового значения;
|
||||||
|
3. выдачу менеджеру прав через `grant_manager_limits`:
|
||||||
|
- отдельно под `Q1`;
|
||||||
|
- отдельно под `Q2`;
|
||||||
|
- отдельно под `Q3`.
|
||||||
|
|
||||||
|
После выдачи лимитов:
|
||||||
|
|
||||||
|
- считать `manager_allowance_pda`;
|
||||||
|
- убедиться, что лимиты записаны отдельно по трём очередям.
|
||||||
|
|
||||||
|
### 6. Проверка manager_add_ticket
|
||||||
|
|
||||||
|
На минимальных суммах создать менеджерские тикеты:
|
||||||
|
|
||||||
|
1. один ticket в `Q1`;
|
||||||
|
2. один ticket в `Q2`;
|
||||||
|
3. один ticket в `Q3`.
|
||||||
|
|
||||||
|
После каждого добавления:
|
||||||
|
|
||||||
|
- запросить состояние очередей;
|
||||||
|
- проверить рост счётчиков и сумм именно у нужной очереди;
|
||||||
|
- проверить уменьшение соответствующего manager allowance.
|
||||||
|
|
||||||
|
### 7. Проверка приоритета очередей
|
||||||
|
|
||||||
|
Подтвердить очередность `step_payout`:
|
||||||
|
|
||||||
|
1. сначала выплачивается `Q1`;
|
||||||
|
2. затем `Q2`;
|
||||||
|
3. затем `Q3`.
|
||||||
|
|
||||||
|
Для этого:
|
||||||
|
|
||||||
|
- между шагами регулярно читать `queues`;
|
||||||
|
- фиксировать, какой именно ticket был следующим к выплате;
|
||||||
|
- убедиться, что при наличии pending в `Q1` программа не уходит в `Q2` или `Q3`.
|
||||||
|
|
||||||
|
### 8. Проверка частичных выплат
|
||||||
|
|
||||||
|
Перед выплатами пополнять `inflow_vault` только минимально достаточными суммами.
|
||||||
|
|
||||||
|
Нужно проверить:
|
||||||
|
|
||||||
|
1. частичную серию выплат, когда часть тикетов ещё остаётся pending;
|
||||||
|
2. дополнительную покупку билета в промежутке между выплатами;
|
||||||
|
3. повторную проверку приоритета после появления нового билета в `Q1`.
|
||||||
|
|
||||||
|
После каждого `step_payout`:
|
||||||
|
|
||||||
|
- запрашивать состояние очередей;
|
||||||
|
- проверять:
|
||||||
|
- рост `tickets_paid`;
|
||||||
|
- рост `sum_paid_usd_cents`;
|
||||||
|
- `is_paid = true` у погашенного ticket;
|
||||||
|
- правильный DAO multiplier:
|
||||||
|
- `Q1 -> 1x`;
|
||||||
|
- `Q2 -> 2x`;
|
||||||
|
- `Q3 -> 3x`.
|
||||||
|
|
||||||
|
### 9. Проверка финального добора
|
||||||
|
|
||||||
|
После частичных выплат:
|
||||||
|
|
||||||
|
- купить ещё один билет;
|
||||||
|
- допополнить `inflow_vault`;
|
||||||
|
- выполнить оставшиеся `step_payout` до полного погашения всех трёх очередей.
|
||||||
|
|
||||||
|
В конце:
|
||||||
|
|
||||||
|
- все pending ticket должны отсутствовать;
|
||||||
|
- все суммы paid должны совпасть с total по каждой очереди;
|
||||||
|
- если вызвать `step_payout` на пустых очередях, доступный остаток `inflow_vault` должен уйти в `dao_wallet`.
|
||||||
|
|
||||||
|
### 10. Финальный возврат лампортов
|
||||||
|
|
||||||
|
После завершения теста вернуть все доступные остатки, которые можно вернуть текущими полномочиями, на базовый кошелёк:
|
||||||
|
|
||||||
|
- `FUc28vNixp7F3nnkpGVt6nuJbgvJ4429v4B5wS52Df6P`
|
||||||
|
|
||||||
|
Отдельно зафиксировать:
|
||||||
|
|
||||||
|
- что именно удалось вернуть;
|
||||||
|
- что именно нельзя вернуть без специальной инструкции закрытия или без deploy authority.
|
||||||
|
|
||||||
|
## Ожидаемый результат
|
||||||
|
|
||||||
|
- `buy_ticket_usd` и `buy_ticket_sol` создают ticket без ошибок чтения state;
|
||||||
|
- `Q3` работает наравне с `Q2`, но с третьим приоритетом;
|
||||||
|
- DAO может менять коэффициент и выдавать лимиты;
|
||||||
|
- менеджер может создавать билеты во все три очереди;
|
||||||
|
- `step_payout` соблюдает порядок `Q1 -> Q2 -> Q3`;
|
||||||
|
- DAO-множитель на выплатах равен `1x/2x/3x` для `Q1/Q2/Q3`;
|
||||||
|
- HTML UI и on-chain программа используют один и тот же актуальный `program id`;
|
||||||
|
- остатки средств после теста по максимуму возвращены на базовый DAO-кошелёк.
|
||||||
|
|
||||||
|
## Статус
|
||||||
|
|
||||||
|
- `done`
|
||||||
|
|
||||||
|
## Итог выполнения
|
||||||
|
|
||||||
|
- новый `shine_payments` задеплоен в devnet с `program id`:
|
||||||
|
- `c4yTa4JT9EtQDCBX9LmWFK6T2gp4JGsuymFbom2EudW`
|
||||||
|
- старый `shine_payments`:
|
||||||
|
- `m48pWRGWrMj3TEHjuU4zsp5Gju4e7ZaPovk8RcVt7kR`
|
||||||
|
- закрыт, лампорты возвращены на базовый DAO-кошелёк
|
||||||
|
- HTML UI переведён на новый `program id`
|
||||||
|
- подтверждены:
|
||||||
|
- `init`
|
||||||
|
- `buy_ticket_usd`
|
||||||
|
- `buy_ticket_sol`
|
||||||
|
- `grant_manager_limits`
|
||||||
|
- `manager_add_ticket` для `Q1/Q2/Q3`
|
||||||
|
- `change_ticket_recipient`
|
||||||
|
- `update_coef_limit`
|
||||||
|
- `step_payout` по порядку `Q1 -> Q2 -> Q3`
|
||||||
|
- повторный возврат приоритета в `Q1` после новой покупки
|
||||||
|
- итоговые агрегаты очередей:
|
||||||
|
- `Q1 total=4, paid=4, sum_total=780, sum_paid=780`
|
||||||
|
- `Q2 total=1, paid=1, sum_total=60, sum_paid=60`
|
||||||
|
- `Q3 total=1, paid=1, sum_total=70, sum_paid=70`
|
||||||
|
- временные тестовые кошельки собраны обратно в базовый DAO-кошелёк
|
||||||
|
- в `inflow_vault` остался только rent-минимум PDA
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
# Клиентская Solana-регистрация после ухода от Anchor
|
||||||
|
|
||||||
|
## Краткое описание
|
||||||
|
|
||||||
|
Исправлен рассинхрон обычного клиентского UI с no-Anchor ABI программ:
|
||||||
|
|
||||||
|
- `shine_login_guard`
|
||||||
|
- `shine_users`
|
||||||
|
|
||||||
|
Исправлены клиентские вызовы:
|
||||||
|
|
||||||
|
1. Solana-предпроверка логина в обычном UI.
|
||||||
|
2. `init_users_economy_config` в обычном UI.
|
||||||
|
|
||||||
|
## Что проверять
|
||||||
|
|
||||||
|
1. На странице регистрации проверка свободного логина не выдаёт `InvalidInstructionData`.
|
||||||
|
2. Для свободного обычного логина отображается корректный статус без fallback-предупреждения про недоступную Solana-предпроверку.
|
||||||
|
3. Регистрация пользователя через обычный UI проходит до конца.
|
||||||
|
4. Страница `Solana: init регистрации` в обычном UI отправляет корректную транзакцию и не падает из-за старого Anchor discriminator.
|
||||||
|
|
||||||
|
## Ожидаемый результат
|
||||||
|
|
||||||
|
1. `shine_login_guard` принимает клиентский precheck.
|
||||||
|
2. `init_users_economy_config` из обычного UI совместим с текущей программой `shine_users`.
|
||||||
|
3. Обычный клиентский UI ведёт себя так же, как серверный UI, там где используется общий no-Anchor путь.
|
||||||
|
|
||||||
|
## Статус
|
||||||
|
|
||||||
|
- `pending`
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
# ESP32 UI-прототип сабсервера SHiNE
|
||||||
|
|
||||||
|
- краткое описание фичи:
|
||||||
|
для `Waveshare ESP32-S3-Touch-AMOLED-2.16` добавлен новый интерактивный UI-скетч сабсервера `SHiNE` с хранением данных в `NVS`, настройками `Wi-Fi`, настройками серверов, кошельком, экраном `QR/URI`, живой Solana-регистрацией и экраном входящих запросов. Логика PIN в коде сохранена, но вход по PIN во временной сборке отключён, чтобы не блокировать проверку остальных экранов. В текущей версии `Wi-Fi` подключается реально, адреса `API/RPC/WS` проверяются реально, баланс кошелька читается из `Solana RPC`, а регистрация отправляет `create_user_pda` в `shine_users`.
|
||||||
|
|
||||||
|
- что именно проверять:
|
||||||
|
1. Прошить режим `subserver-ui` и дождаться открытия главного экрана без PIN.
|
||||||
|
2. Проверить, что текст в заголовках, кнопках и статусах отображается читаемо; в текущей временной сборке допускается ASCII-транслитерация русского текста.
|
||||||
|
3. Открыть `Настройки` и убедиться, что показывается пометка о временно отключённом входе по PIN.
|
||||||
|
4. Открыть `Подключение -> Wi-Fi`, ввести `SSID` и пароль, нажать `Проверить`, дождаться реального подключения, затем перезагрузить устройство и проверить, что значения сохранились.
|
||||||
|
5. Открыть `Подключение -> Серверы`, проверить или изменить `API/RPC/WS`, нажать `Проверить` и убедиться, что показываются реальные статусы доступности, затем перезагрузить устройство и проверить сохранение значений.
|
||||||
|
6. Открыть `Аккаунт`, ввести логин, имя сабсервера и нажать `Сгенерировать`; проверить, что появились секрет и адрес кошелька, а после перезагрузки они не исчезают.
|
||||||
|
7. Открыть `Кошелёк`, нажать `Проверить` и убедиться, что баланс реально читается из `Solana RPC`; затем открыть `QR и URI` и проверить, что QR-код отрисовывается и сканируется как `solana:`-ссылка.
|
||||||
|
8. При необходимости отдельно проверить тестовые кнопки `+/- SOL`: они меняют локальный баланс для UX-сценариев, но после следующей реальной RPC-проверки баланс должен вернуться к сетевому значению.
|
||||||
|
9. Вернуться на главный экран и проверить, что до выполнения всех условий кнопка регистрации недоступна, а после выполнения становится доступной.
|
||||||
|
10. Выполнить регистрацию и убедиться, что статус меняется на `Сабсервер активен`, онлайн-статус становится активным, а на экране появляются краткие отпечатки `PDA/TX`.
|
||||||
|
11. После регистрации проверить через `Solana`/UI проекта, что `user_pda` для этого логина реально создана и соответствует `device`-адресу устройства.
|
||||||
|
12. Открыть `Запросы`, поочерёдно открыть оба демонстрационных запроса и проверить, что кнопки `Разрешить` и `Отклонить` меняют их статус.
|
||||||
|
13. При необходимости открыть `Настройки -> Сменить PIN` и убедиться, что новый PIN сохраняется, хотя вход по PIN временно не используется на старте.
|
||||||
|
14. Выполнить `Полный сброс` и убедиться, что все поля, секрет, баланс, онлайн и регистрация очищаются.
|
||||||
|
|
||||||
|
- ожидаемый результат:
|
||||||
|
новый `ESP32`-скетч стабильно запускается, показывает читаемый интерфейс хотя бы в ASCII-транслитерации, сохраняет данные во внутренней памяти устройства, реально подключается к `Wi-Fi`, реально проверяет `API/RPC/WS`, реально читает баланс из `Solana RPC`, рисует рабочий `QR` для `solana:`-URI и позволяет вручную пройти полный сценарий on-chain регистрации сабсервера.
|
||||||
|
|
||||||
|
- статус:
|
||||||
|
pending
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
# ESP32 авто-прошивка shine_subserver_ui
|
||||||
|
|
||||||
|
- краткое описание фичи:
|
||||||
|
добавлен исполняемый скрипт `flash_shine_subserver_ui.sh`, который автоматически ищет USB-порт `ESP32` и запускает заливку прошивки `shine_subserver_ui` без ручного указания `PORT`.
|
||||||
|
- что именно проверять:
|
||||||
|
1. Подключить плату `ESP32` по USB.
|
||||||
|
2. Перейти в папку `ESP32/esp32/ESP32-S3-Touch-AMOLED-2.16/test-device/`.
|
||||||
|
3. Запустить `./flash_shine_subserver_ui.sh`.
|
||||||
|
4. Убедиться, что скрипт сам показывает найденный порт и успешно запускает compile/upload.
|
||||||
|
- ожидаемый результат:
|
||||||
|
скрипт без ручного ввода порта находит `ESP32`, печатает найденный `/dev/ttyACM*` или `/dev/ttyUSB*` и заливает `shine_subserver_ui`.
|
||||||
|
- статус:
|
||||||
|
pending
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
# ESP32 тест рендера текста
|
||||||
|
|
||||||
|
- краткое описание фичи:
|
||||||
|
добавлен отдельный диагностический скетч `text_render_test`, который показывает один экран с несколькими вариантами вывода текста: встроенный шрифт `Arduino_GFX`, `U8g2` ASCII, `U8g2` кириллица и кнопки с подписями. Скрипт нужен для изоляции проблемы, когда на экране видны только цветные кнопки и блоки, но не видно ни одной буквы.
|
||||||
|
- что именно проверять:
|
||||||
|
1. Прошить режим `text-test`.
|
||||||
|
2. Проверить, виден ли заголовок `TEXT TEST 123`.
|
||||||
|
3. Проверить, видны ли строки `A`, `B`, `C`, `D`.
|
||||||
|
4. Проверить, видны ли подписи на трёх нижних кнопках: `BTN 1`, `abc123`, `Русский`.
|
||||||
|
5. Сравнить, какой из способов вывода реально отображается, а какой нет.
|
||||||
|
- ожидаемый результат:
|
||||||
|
хотя бы один вариант вывода текста становится видим на экране, что позволяет локализовать проблему до конкретного шрифта или способа рендера.
|
||||||
|
- статус:
|
||||||
|
pending
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
# ESP32 PIN-клавиатура: подписи кнопок
|
||||||
|
|
||||||
|
- краткое описание фичи:
|
||||||
|
в UI-скетче `shine_subserver_ui` изменена отрисовка подписей кнопок. Вместо малого шрифта теперь используется более стабильный шрифт с явным центрированием текста внутри кнопок, чтобы на экране ввода PIN и других экранах не пропадали цифры и надписи.
|
||||||
|
- что именно проверять:
|
||||||
|
1. Включить устройство и дождаться экрана ввода PIN.
|
||||||
|
2. Убедиться, что на всех серых кнопках видны цифры `0-9`, `Отмена` и `OK`.
|
||||||
|
3. Открыть другие экраны с кнопками (`Главный экран`, `Wi-Fi`, `Серверы`, `Настройки`) и убедиться, что подписи отображаются и не уезжают за границы кнопок.
|
||||||
|
- ожидаемый результат:
|
||||||
|
подписи кнопок стабильно видны сразу после старта, текст визуально центрирован, пустых серых кнопок без цифр и названий нет.
|
||||||
|
- статус:
|
||||||
|
pending
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
# ESP32 папка тестовых скетчей
|
||||||
|
|
||||||
|
- краткое описание фичи:
|
||||||
|
добавлена отдельная папка `test_sketches/` с изолированными диагностическими скетчами для экрана `ESP32-S3-Touch-AMOLED-2.16`: тест рендера текста через `Arduino_GFX`, тест геометрии кнопок и минимальный тест `LVGL`.
|
||||||
|
- что именно проверять:
|
||||||
|
1. Запустить `./burn.sh gfx-text-test` и убедиться, что прошивается тест текста из новой папки.
|
||||||
|
2. Запустить `./burn.sh gfx-layout-test` и проверить нижние ряды кнопок.
|
||||||
|
3. Запустить `./burn.sh lvgl-basic-test` и проверить, что `LVGL` показывает текст и кнопки.
|
||||||
|
4. Убедиться, что новая папка не мешает сборке `subserver-ui`.
|
||||||
|
- ожидаемый результат:
|
||||||
|
тестовые скетчи лежат отдельно от основного UI, шьются отдельными режимами и позволяют быстро проверять разные гипотезы по экрану без правок в `shine_subserver_ui`.
|
||||||
|
- статус:
|
||||||
|
pending
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
# ESP32 LVGL interaction test
|
||||||
|
|
||||||
|
- краткое описание фичи:
|
||||||
|
добавлен отдельный скетч `lvgl_interaction_test` на `LVGL`: экран с 9 кнопками, touch-вводом и нижней статусной строкой. При нажатии на кнопку на экране и в `Serial` показывается, какая именно кнопка нажата и сколько нажатий уже было.
|
||||||
|
- что именно проверять:
|
||||||
|
1. Прошить режим `lvgl-interaction-test`.
|
||||||
|
2. Убедиться, что виден заголовок, подзаголовок, 9 кнопок и нижняя статусная панель.
|
||||||
|
3. Поочерёдно нажать разные кнопки.
|
||||||
|
4. Проверить, что нижняя строка меняется на `Pressed: <button> (#N)`.
|
||||||
|
5. Проверить, что touch устойчиво работает по всей сетке кнопок.
|
||||||
|
- ожидаемый результат:
|
||||||
|
`LVGL` стабильно рисует плотный экран с множеством кнопок, а нажатия корректно обрабатываются и визуально подтверждаются без глюков позиционирования.
|
||||||
|
- статус:
|
||||||
|
pending
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
# ESP32 LVGL touch debug test
|
||||||
|
|
||||||
|
- краткое описание фичи:
|
||||||
|
добавлен отдельный диагностический скетч `lvgl_touch_debug_test`, который одновременно показывает сырые координаты touch, маркер точки касания и одну большую кнопку `LVGL`. Он нужен, чтобы отделить проблему raw-touch от проблемы доставки событий в `LVGL`.
|
||||||
|
- что именно проверять:
|
||||||
|
1. Прошить режим `lvgl-touch-debug-test`.
|
||||||
|
2. Коснуться экрана в разных местах.
|
||||||
|
3. Проверить, меняется ли текст `RAW pressed` и координаты `x/y`.
|
||||||
|
4. Проверить, появляется ли розовый маркер точки касания.
|
||||||
|
5. Проверить, срабатывает ли большая кнопка `Tap Here` и меняется ли строка `LVGL button clicked`.
|
||||||
|
- ожидаемый результат:
|
||||||
|
становится ясно, работает ли сам touch-драйвер, правильно ли приходят координаты и доходит ли нажатие до кнопки `LVGL`.
|
||||||
|
- статус:
|
||||||
|
pending
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
# ESP32 LVGL official based test
|
||||||
|
|
||||||
|
- краткое описание фичи:
|
||||||
|
добавлен отдельный скетч `lvgl_official_based_test`, который строится на максимально близкой к официальному `05_LVGL_Widgets` инициализации `display + touch + LVGL`, но вместо официального demo рисует наш компактный экран с кнопками и статусом нажатия.
|
||||||
|
- что именно проверять:
|
||||||
|
1. Прошить режим `lvgl-official-based-test`.
|
||||||
|
2. Убедиться, что экран отображается без артефактов по краям.
|
||||||
|
3. Нажать разные кнопки и проверить, меняется ли нижняя строка `Pressed: ...`.
|
||||||
|
4. Проверить, идут ли координаты touch в `Serial`.
|
||||||
|
- ожидаемый результат:
|
||||||
|
если официальный каркас инициализации действительно является рабочей базой, то на этом тесте должны заработать и touch, и кнопки, и исчезнуть визуальные артефакты, которые были в наших самодельных `LVGL`-тестах.
|
||||||
|
- статус:
|
||||||
|
pending
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
# LVGL Russian font test
|
||||||
|
|
||||||
|
- Краткое описание: тест кастомного `LVGL`-шрифта с кириллицей на базе рабочего `LVGL + subserver touch` контура.
|
||||||
|
- Что проверять:
|
||||||
|
- на экране видны русские заголовки и подписи без транслита;
|
||||||
|
- отображаются буквы `Ё/ё`;
|
||||||
|
- видны кнопки `Статус`, `Подключение`, `Кошелёк`, `Запросы`, `Настройки`, `Регистрация`, `Разрешить`, `Отклонить`, `Назад`;
|
||||||
|
- длинная кнопка `Проверка переноса русского текста` отображается читаемо;
|
||||||
|
- строка `Нажато:` меняется при клике;
|
||||||
|
- строка `Касание:` меняется при касании.
|
||||||
|
- Ожидаемый результат: кириллица стабильно отображается на `LVGL`-экране и не ломает touch.
|
||||||
|
- Статус: pending
|
||||||
@@ -0,0 +1,104 @@
|
|||||||
|
# ESP32 nav minimal test
|
||||||
|
|
||||||
|
- Краткое описание: минимальный UI-прототип для сабсервера на базе `LVGL + subserver touch`, с Wi-Fi flow, серверными адресами и общим экраном редактирования текста.
|
||||||
|
- Что проверять:
|
||||||
|
- стартует экран `HOME`;
|
||||||
|
- на `HOME` видны реальное значение сабсервера или `subserver not set`, реальное значение логина или `login not set`, при отсутствии секрета строка `secret not set`, а также `STATUS`, верхний правый блок с процентом батареи, иконкой батареи и индикатором Wi-Fi, кнопка баланса, строка `SHiNE: ...`, кнопка `SETTINGS` уменьшенной ширины у правого края и нижняя подпись `SHiNE subserver (v.0.18)`;
|
||||||
|
- справа от строки логина виден индикатор статуса Solana-аккаунта:
|
||||||
|
- зелёный, если ключи совпали;
|
||||||
|
- красный, если mismatch;
|
||||||
|
- белый контур, если пользователь не найден;
|
||||||
|
- если статус не зелёный, рядом выводится краткое текстовое пояснение;
|
||||||
|
- строка Wi-Fi на `HOME` корректно показывает одно из состояний:
|
||||||
|
- `Wi-Fi (not configured) not configured`
|
||||||
|
- `Wi-Fi (<saved_ssid>) disconnected`
|
||||||
|
- `Wi-Fi (<current_ssid>) connected`
|
||||||
|
- строка `SHiNE:` корректно показывает одно из состояний:
|
||||||
|
- `connected`
|
||||||
|
- `account not configured`
|
||||||
|
- `unavailable`
|
||||||
|
- пока открыт `HOME`, статус сам обновляется без перехода на другие экраны;
|
||||||
|
- баланс обновляется кнопкой по нажатию;
|
||||||
|
- если логин зарегистрирован и секрет/сабсервер заданы, устройство:
|
||||||
|
- читает `user_pda` через Solana RPC;
|
||||||
|
- сверяет `root`, `blockchain`, `device` и `subserver` session type `100`;
|
||||||
|
- поднимает WebSocket-сессию с сервером SHiNE;
|
||||||
|
- шлёт `Ping` раз в минуту;
|
||||||
|
- кнопка `SETTINGS` открывает `SETTINGS_MENU`;
|
||||||
|
- свайп влево на `HOME` открывает `SETTINGS_MENU`;
|
||||||
|
- если пользователь не найден в Solana PDA, слева снизу появляется `REGISTER ACCOUNT`;
|
||||||
|
- `REGISTER ACCOUNT` открывает экран-заглушку;
|
||||||
|
- в `SETTINGS_MENU` сначала видны только `Wi-Fi` и `Server`;
|
||||||
|
- обе видимые карточки меню одного цвета;
|
||||||
|
- свайп вверх показывает `Server` и `Account`;
|
||||||
|
- свайп вниз возвращает `Wi-Fi` и `Server`;
|
||||||
|
- свайп вправо из `SETTINGS_MENU` возвращает на `HOME`;
|
||||||
|
- нажатие `Wi-Fi` открывает `WIFI_SCREEN`;
|
||||||
|
- `SELECT NETWORK` запускает скан;
|
||||||
|
- после скана показывается список доступных SSID;
|
||||||
|
- выбор SSID открывает общий экран редактирования текста для пароля;
|
||||||
|
- если для этого SSID пароль уже сохранялся раньше, он автоматически подставляется в редактор;
|
||||||
|
- если затем ввести пароль для другого SSID, пароль первой сети не теряется;
|
||||||
|
- одновременно хранится до `8` паролей для разных SSID;
|
||||||
|
- на этом экране видно старое значение, курсор стоит в конце;
|
||||||
|
- две верхние служебные строки над полем ввода отсутствуют;
|
||||||
|
- при вводе пароля Wi-Fi текст показывается открыто, без точек;
|
||||||
|
- большая клавиатура реально видна на экране и занимает большую часть высоты;
|
||||||
|
- буквы разбиты на 2 страницы;
|
||||||
|
- режим символов тоже разбит на 2 страницы;
|
||||||
|
- на правой странице кнопки стоят в ровных вертикальных колонках;
|
||||||
|
- свайп влево/вправо на экране ввода переключает страницы клавиатуры;
|
||||||
|
- при этом свайп страниц клавиатуры срабатывает только из нижней клавиатурной зоны, а не из верхней части экрана;
|
||||||
|
- при переключении `ABC/123` и `SHIFT` уже введённый текст не пропадает;
|
||||||
|
- при свайпе между левой и правой половиной клавиатуры уже введённый текст тоже не пропадает, в том числе для цифр, символов и заглавных букв;
|
||||||
|
- визуальный курсор в поле ввода не показывается;
|
||||||
|
- новые символы всегда дописываются только в конец строки;
|
||||||
|
- основные 3 ряда клавиш и нижний служебный ряд стали выше;
|
||||||
|
- внизу остаётся отдельная тёмная полоса с версией `SHiNE subserver (v.0.18)`, а рамка клавиатурного блока заканчивается выше неё;
|
||||||
|
- одно непрерывное касание вызывает не более одного действия кнопки;
|
||||||
|
- скольжение пальцем по клавиатуре не нажимает подряд несколько клавиш;
|
||||||
|
- медленный свайп по экрану не должен превращаться в случайное нажатие кнопки;
|
||||||
|
- `ABC/123`, `SHIFT`, `DEL`, `SAVE`, `CANCEL` работают;
|
||||||
|
- при успехе SSID и пароль сохраняются, а `HOME` показывает `Wi-Fi connected`;
|
||||||
|
- если после подключения ко второй сети снова выбрать первую, её старый пароль уже подставлен и достаточно нажать `SAVE`;
|
||||||
|
- при ошибке показывается `Connection failed`;
|
||||||
|
- `CLEAR SAVED WI-FI` очищает сохранённые настройки;
|
||||||
|
- если сеть была ранее успешно сохранена, после потери связи устройство автоматически пытается переподключиться;
|
||||||
|
- первые повторные попытки идут раз в `10` секунд, а после долгого отсутствия связи интервал увеличивается до `30` секунд;
|
||||||
|
- нажатие `Server` открывает `SERVER_SCREEN`;
|
||||||
|
- в `SERVER_SCREEN` видны и редактируются два значения:
|
||||||
|
- `https://api.devnet.solana.com`
|
||||||
|
- `https://shineup.me`
|
||||||
|
- нажатие `SOLANA RPC` открывает общий экран редактирования;
|
||||||
|
- нажатие `SHINE SERVER` открывает общий экран редактирования;
|
||||||
|
- после `SAVE` новые адреса сохраняются в NVS;
|
||||||
|
- нажатие `Account` открывает `ACCOUNT_SCREEN`;
|
||||||
|
- `ACCOUNT_SCREEN` показывает 3 кнопки:
|
||||||
|
- `Login (<value|not set>)`
|
||||||
|
- `Subserver (<value|not set>)`
|
||||||
|
- `Secret (<*****|not set>)`
|
||||||
|
- `Login` открывает общий экран редактирования и сохраняется в NVS;
|
||||||
|
- `Subserver` открывает промежуточный экран с `USE SUBSERVER1` и `EDIT MANUALLY`;
|
||||||
|
- `USE SUBSERVER1` возвращает стандартное значение `subserver1`;
|
||||||
|
- `EDIT MANUALLY` открывает общий экран редактирования и сохраняет значение в NVS;
|
||||||
|
- `Secret` теперь открывает меню секрета с показом секрета, ручным вводом и генерацией;
|
||||||
|
- в `SHOW SECRET` показывается прокручиваемый список всех ключей:
|
||||||
|
- `Secret (base58)`
|
||||||
|
- `Root key (base58)`
|
||||||
|
- `Root key priv (base58)`
|
||||||
|
- `Blockchain key (base58)`
|
||||||
|
- `Blockchain key priv (base58)`
|
||||||
|
- `Device key (base58)`
|
||||||
|
- `Device key priv (base58)`
|
||||||
|
- `Subserver key (base58)`
|
||||||
|
- `Subserver key priv (base58)`
|
||||||
|
- значения ключей показываются полными строками увеличенным шрифтом;
|
||||||
|
- при смене `login` сохранённый секрет сбрасывается в `not set`;
|
||||||
|
- во время генерации секрета есть `CANCEL` и подтверждение остановки;
|
||||||
|
- при отмене генерации старый секрет, если он был, не должен теряться;
|
||||||
|
- свайп вправо из внутренних экранов возвращает в `SETTINGS_MENU`;
|
||||||
|
- свайп вправо из `ACCOUNT_SUBSERVER_SCREEN` и `ACCOUNT_SECRET_SCREEN` возвращает в `ACCOUNT_SCREEN`;
|
||||||
|
- если во время реального свайпа палец проходит по кнопке, это не должно открывать кнопку как обычный `click`.
|
||||||
|
- Ожидаемый результат: новый скетч даёт чистый навигационный каркас и уже умеет настраивать Wi-Fi и серверные адреса на самой ESP32.
|
||||||
|
- Дополнительно ожидается: `HOME` уже показывает реальный Solana/WS-статус сабсервера, а отсутствие пользователя в Solana заметно сразу без перехода в настройки.
|
||||||
|
- Статус: pending
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
# Deeplink ссылки профиля и связей
|
||||||
|
|
||||||
|
- краткое описание:
|
||||||
|
Исправлена загрузка UI по прямым ссылкам вида `https://shineup.me/shine.<login>` и `https://shineup.me/shine.<login>/links` через добавление корневого `<base href="/">` в основной `index.html`.
|
||||||
|
- что проверять:
|
||||||
|
1. Открыть прямую ссылку на профиль в новой вкладке: `https://shineup.me/shine.<login>`.
|
||||||
|
2. Открыть прямую ссылку на связи в новой вкладке: `https://shineup.me/shine.<login>/links`.
|
||||||
|
3. Повторить оба сценария в состоянии гостя.
|
||||||
|
4. Повторить оба сценария в состоянии, когда в браузере залогинен другой пользователь.
|
||||||
|
- ожидаемый результат:
|
||||||
|
1. Страница загружается напрямую без поломки ассетов и без ухода на неверный экран.
|
||||||
|
2. Открывается профиль/связи именно пользователя из URL.
|
||||||
|
3. Для гостя экран открывается в read-only режиме.
|
||||||
|
4. Для залогиненного другого пользователя URL не подменяется на текущую сессию.
|
||||||
|
- статус:
|
||||||
|
pending
|
||||||
@@ -0,0 +1,269 @@
|
|||||||
|
# Личные сообщения (DM): как это устроено
|
||||||
|
|
||||||
|
## Коротко (для быстрого понимания)
|
||||||
|
|
||||||
|
Личные сообщения в SHiNE сейчас работают как пара **подписанных клиентом блоков** в формате `SHiNE_dm2`:
|
||||||
|
|
||||||
|
- тип `1` — входящее сообщение для собеседника;
|
||||||
|
- тип `2` — исходящая копия того же сообщения для автора.
|
||||||
|
|
||||||
|
Оба блока отправляются вместе одной операцией (`SendMessagePair` / `ReceiveOutcomingMessage`) и либо сохраняются оба, либо не сохраняются вовсе.
|
||||||
|
Дальше сервер доставляет их по активным сессиям целевого логина событием `SignedMessageArrived`, а клиент подтверждает доставку на конкретную сессию через `AckSessionDelivery`.
|
||||||
|
|
||||||
|
Подтверждение прочтения также идёт парой блоков:
|
||||||
|
|
||||||
|
- тип `3` — «прочитано» для исходящего сообщения автора;
|
||||||
|
- тип `4` — зеркальная копия для второй стороны.
|
||||||
|
|
||||||
|
UI чата строится на этих типах: текстовые сообщения (1/2), read-receipt (3/4), непрочитанные, галочки и история.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Подробно
|
||||||
|
|
||||||
|
## 1) Общая схема потока
|
||||||
|
|
||||||
|
1. Клиент формирует текст сообщения и строит **2 подписанных блока** (`type=1` и `type=2`) с одинаковыми `fromLogin/toLogin/timeMs/nonce`.
|
||||||
|
2. Клиент отправляет оба блока в одном RPC: `SendMessagePair` (алиас: `ReceiveOutcomingMessage`).
|
||||||
|
3. Сервер:
|
||||||
|
- парсит оба блока;
|
||||||
|
- валидирует пару;
|
||||||
|
- проверяет существование `from/to` пользователей и подписи;
|
||||||
|
- атомарно сохраняет пару в `signed_messages_v2`.
|
||||||
|
4. Сервер доставляет блоки в активные сессии целевого логина событием `SignedMessageArrived`.
|
||||||
|
5. Клиент, получив событие, кладёт сообщение в локальный чат и отправляет `AckSessionDelivery(messageKey)`.
|
||||||
|
6. При открытии чата клиент отправляет read-receipt (пара `type=3/4`) для непрочитанных входящих.
|
||||||
|
|
||||||
|
## 2) Формат signed DM-блока (`SHiNE_dm2`)
|
||||||
|
|
||||||
|
Префикс: `SHiNE_dm2` (ASCII).
|
||||||
|
|
||||||
|
Далее поля (big-endian):
|
||||||
|
|
||||||
|
1. `toLoginLen` (`u8`) + `toLogin` (ASCII, 1..60);
|
||||||
|
2. `fromLoginLen` (`u8`) + `fromLogin` (ASCII, 1..60);
|
||||||
|
3. `timeMs` (`u64`);
|
||||||
|
4. `nonce` (`u32`);
|
||||||
|
5. `messageType` (`u16`);
|
||||||
|
6. `payloadLen` (`u16`);
|
||||||
|
7. `payloadBytes` (`1..4096`);
|
||||||
|
8. `signature` (`64 bytes`, Ed25519).
|
||||||
|
|
||||||
|
Ограничения:
|
||||||
|
|
||||||
|
- полный пакет: до `8192` байт;
|
||||||
|
- `messageType` сейчас допустим только `1..4`.
|
||||||
|
|
||||||
|
## 3) Типы DM-сообщений
|
||||||
|
|
||||||
|
- `1` (`TYPE_INCOMING_TEXT`) — входящий текст для получателя.
|
||||||
|
- `2` (`TYPE_OUTGOING_COPY`) — исходящая копия в истории автора.
|
||||||
|
- `3` (`TYPE_READ_INCOMING`) — read-receipt (входящий тип для пары квитанции).
|
||||||
|
- `4` (`TYPE_READ_OUTGOING_COPY`) — зеркальная копия read-receipt.
|
||||||
|
|
||||||
|
Правило пары:
|
||||||
|
|
||||||
|
- первый блок должен быть нечётным (`1` или `3`);
|
||||||
|
- второй должен быть ровно `+1` (`2` или `4`);
|
||||||
|
- ключевые поля пары совпадают: `toLogin/fromLogin/timeMs/nonce`.
|
||||||
|
|
||||||
|
## 4) Ключи сообщений
|
||||||
|
|
||||||
|
- `baseKey = from|to|timeMs|nonce`
|
||||||
|
- `messageKey = baseKey|messageType`
|
||||||
|
|
||||||
|
Эти ключи используются:
|
||||||
|
|
||||||
|
- для дедупликации;
|
||||||
|
- для связи read-receipt с исходным сообщением;
|
||||||
|
- для ACK доставки по сессии.
|
||||||
|
|
||||||
|
## 5) RPC и события
|
||||||
|
|
||||||
|
## `SendMessagePair` (алиас `ReceiveOutcomingMessage`)
|
||||||
|
|
||||||
|
Запрос:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"op": "SendMessagePair",
|
||||||
|
"requestId": "req-1",
|
||||||
|
"payload": {
|
||||||
|
"incomingBlobB64": "<base64 signed block type 1 or 3>",
|
||||||
|
"outgoingBlobB64": "<base64 signed block type 2 or 4>"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Успешный ответ:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"op": "SendMessagePair",
|
||||||
|
"requestId": "req-1",
|
||||||
|
"status": 200,
|
||||||
|
"ok": true,
|
||||||
|
"payload": {
|
||||||
|
"baseKey": "from|to|time|nonce",
|
||||||
|
"incomingKey": "from|to|time|nonce|1",
|
||||||
|
"outgoingKey": "from|to|time|nonce|2",
|
||||||
|
"deliveredWsSessions": 2,
|
||||||
|
"deliveredWebPushSessions": 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## `SignedMessageArrived` (server event)
|
||||||
|
|
||||||
|
Событие в сессию получателя содержит:
|
||||||
|
|
||||||
|
- `messageKey`, `baseKey`;
|
||||||
|
- `fromLogin`, `toLogin`, `targetLogin`;
|
||||||
|
- `messageType`, `timeMs`, `nonce`;
|
||||||
|
- `blobB64`;
|
||||||
|
- `backlog` (признак догрузки из очереди).
|
||||||
|
|
||||||
|
## `AckSessionDelivery`
|
||||||
|
|
||||||
|
Запрос:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"op": "AckSessionDelivery",
|
||||||
|
"requestId": "ack-1",
|
||||||
|
"payload": {
|
||||||
|
"messageKey": "from|to|time|nonce|1"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Ответ: `status=200`, echo `messageKey`.
|
||||||
|
|
||||||
|
## 6) Хранение на сервере (SQLite)
|
||||||
|
|
||||||
|
Основные таблицы:
|
||||||
|
|
||||||
|
1. `signed_messages_v2` — сами DM-блоки типов `1/2/3/4`:
|
||||||
|
- `message_key` (PK),
|
||||||
|
- `base_key`,
|
||||||
|
- `target_login`,
|
||||||
|
- `from_login`, `to_login`,
|
||||||
|
- `time_ms`, `nonce`, `message_type`,
|
||||||
|
- `raw_block`,
|
||||||
|
- `source_api`, `origin_session_id`,
|
||||||
|
- `receipt_ref_base_key`, `receipt_ref_type`.
|
||||||
|
2. `signed_message_session_delivery` — доставка по сессиям:
|
||||||
|
- составной PK `(message_key, session_id)`,
|
||||||
|
- `delivered` (0/1),
|
||||||
|
- `delivered_at_ms`, `created_at_ms`.
|
||||||
|
|
||||||
|
Примечание: историческая таблица `signed_direct_messages_history` в БД присутствует как legacy-слой, но текущий рабочий поток DM v2 опирается на `signed_messages_v2` + `signed_message_session_delivery`.
|
||||||
|
|
||||||
|
## 7) Доставка и backlog
|
||||||
|
|
||||||
|
- При сохранении пары сервер пытается сразу доставить в онлайн-сессии.
|
||||||
|
- Для офлайн/недоступных сессий остаётся pending-запись доставки в таблице `signed_message_session_delivery`.
|
||||||
|
- При подключении сессии сервер автоматически вызывает `dispatchPendingForSession`:
|
||||||
|
- для новой сессии регистрирует все существующие сообщения адресата как «недоставленные»;
|
||||||
|
- отправляет **все** pending через WebSocket событием `SignedMessageArrived(backlog=true)`;
|
||||||
|
- лимита на количество сообщений нет — передаётся вся история без ограничений.
|
||||||
|
- Клиент дедублирует входящие через `knownMessageKeys`: если `messageKey` уже есть локально — игнорирует.
|
||||||
|
- После получения клиент отправляет `AckSessionDelivery`, чтобы отметить `delivered=1` в таблице доставки.
|
||||||
|
|
||||||
|
## 8) Read-receipt логика
|
||||||
|
|
||||||
|
Когда клиент открывает чат:
|
||||||
|
|
||||||
|
1. ищет входящие `messageType=1` без `readReceiptSent`;
|
||||||
|
2. для каждого отправляет read-receipt как пару `type=3/4`;
|
||||||
|
3. после успешной отправки помечает `readReceiptSent`.
|
||||||
|
|
||||||
|
Сервер для read-receipt хранит ссылку на исходное сообщение:
|
||||||
|
|
||||||
|
- `receipt_ref_base_key`;
|
||||||
|
- `receipt_ref_type`.
|
||||||
|
|
||||||
|
Есть уникальность, чтобы не плодить дубликаты receipt на один и тот же `baseKey` для одного `target_login`.
|
||||||
|
|
||||||
|
## 9) Логика UI-клиента
|
||||||
|
|
||||||
|
### Хранилище сообщений
|
||||||
|
|
||||||
|
- In-memory: `state.chats[chatId]` — массив сообщений по каждому диалогу.
|
||||||
|
- Персистентно: IndexedDB база `shine-ui-messages-v1`, object store `messages`, ключ `messageKey`.
|
||||||
|
- `chatId` для `type=1` — `fromLogin`, для `type=2` — `toLogin`.
|
||||||
|
|
||||||
|
### Жизненный цикл при старте/подключении
|
||||||
|
|
||||||
|
1. `hydrateMessagesFromStore()` — читает все сообщения из IndexedDB в `state.chats` (до WebSocket-соединения).
|
||||||
|
2. После установки WebSocket-сессии сервер присылает backlog (`SignedMessageArrived(backlog=true)`) для всех недоставленных сообщений.
|
||||||
|
3. Клиент дедублирует через `knownMessageKeys` — уже имеющиеся в IndexedDB игнорируются.
|
||||||
|
4. Новые сообщения в реальном времени приходят теми же WebSocket-событиями.
|
||||||
|
|
||||||
|
### Очистка при выходе и смене пользователя
|
||||||
|
|
||||||
|
- При любом логауте (`terminateCurrentSession`) IndexedDB с сообщениями **удаляется полностью**.
|
||||||
|
- При входе нового пользователя через QR — IndexedDB удаляется явно до вызова `terminateCurrentSession`.
|
||||||
|
- При входе нового пользователя через логин/пароль — IndexedDB удаляется в `registration-keys-view.js` прямо перед `authorizeSession()`.
|
||||||
|
- Это гарантирует: при любом способе входа старые сообщения предыдущего пользователя не попадут к следующему.
|
||||||
|
|
||||||
|
### UI-поведение
|
||||||
|
|
||||||
|
- непрочитанные считаются по `from='in' && unread=true`;
|
||||||
|
- доставка/прочтение исходящих:
|
||||||
|
- `firstTick` — сообщение принято сервером,
|
||||||
|
- `secondTick` — пришло подтверждение прочтения;
|
||||||
|
- при открытии диалога UI автопрокручивает ленту в самый низ;
|
||||||
|
- после отправки нового сообщения UI сразу прокручивает ленту вниз.
|
||||||
|
|
||||||
|
## 10) Синхронизация личных сообщений между серверами
|
||||||
|
|
||||||
|
Когда пользователи зарегистрированы на разных серверах SHiNE, серверы должны синхронизировать DM между собой.
|
||||||
|
|
||||||
|
### Общий принцип
|
||||||
|
|
||||||
|
- Сервер A получает DM-блок, адресованный пользователю на сервере B.
|
||||||
|
- Сервер A пересылает этот блок серверу B (межсерверный relay).
|
||||||
|
- Сервер B сохраняет блок и доставляет его в активные сессии получателя.
|
||||||
|
- Серверы, между которыми идёт синхронизация, задаются списком `sync_servers` в PDA пользователя-сервера.
|
||||||
|
|
||||||
|
### Что синхронизируется
|
||||||
|
|
||||||
|
- Все DM-блоки типов `1/2` (текстовые сообщения) и `3/4` (read-receipt).
|
||||||
|
- Синхронизация двусторонняя: оба сервера должны уметь принимать и пересылать блоки.
|
||||||
|
|
||||||
|
### Идемпотентность
|
||||||
|
|
||||||
|
- Блоки имеют уникальный `message_key` (`from|to|timeMs|nonce|type`).
|
||||||
|
- Повторная доставка одного и того же блока безопасна — дедупликация происходит по `message_key`.
|
||||||
|
|
||||||
|
### Статус реализации
|
||||||
|
|
||||||
|
Межсерверная синхронизация DM **пока не реализована**. Текущая версия работает только в рамках одного сервера. Это задача для следующего этапа.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 11) Инварианты (обязательно соблюдать при доработках)
|
||||||
|
|
||||||
|
1. Пара блоков (1/2 или 3/4) должна оставаться атомарной.
|
||||||
|
2. `messageKey`/`baseKey` формат должен быть совместим с текущей логикой дедупликации и receipt.
|
||||||
|
3. Доставка должна оставаться **по сессиям** с явным `AckSessionDelivery`.
|
||||||
|
4. Read-receipt не должен отправляться многократно на один и тот же `baseKey`.
|
||||||
|
5. Любые изменения DM-логики в коде должны сразу отражаться в этом документе.
|
||||||
|
|
||||||
|
## 12) Ключевые файлы реализации
|
||||||
|
|
||||||
|
- UI:
|
||||||
|
- `shine-UI/js/services/auth-service.js`
|
||||||
|
- `shine-UI/js/app.js`
|
||||||
|
- `shine-UI/js/state.js`
|
||||||
|
- `shine-UI/js/pages/chat-view.js`
|
||||||
|
- Сервер:
|
||||||
|
- `shine-server-net-protocol/.../messages/SignedMessageBlock.java`
|
||||||
|
- `shine-server-net-protocol/.../messages/SignedMessagesCore.java`
|
||||||
|
- `shine-server-net-protocol/.../messages/Net_SendMessagePair_Handler.java`
|
||||||
|
- `shine-server-net-protocol/.../messages/SignedMessagesRealtime.java`
|
||||||
|
- `shine-server-net-protocol/.../messages/Net_AckSessionDelivery_Handler.java`
|
||||||
|
- БД:
|
||||||
|
- `shine-server-db/src/main/java/shine/db/DatabaseInitializer.java`
|
||||||
|
- `shine-server-db/src/main/java/shine/db/dao/SignedMessagesV2DAO.java`
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
# TODO: доработка персональных сообщений для агентов
|
||||||
|
|
||||||
|
Статус: отложено.
|
||||||
|
|
||||||
|
## Что хотели сделать
|
||||||
|
|
||||||
|
Добавить упрощённую маршрутизацию персональных сообщений через служебную инструкцию в начале текстового payload (внутри подписанного DM-блока), чтобы:
|
||||||
|
|
||||||
|
- отличать сообщения человеку от сообщений агенту;
|
||||||
|
- отличать сообщения от человека и от агента;
|
||||||
|
- скрывать в обычном UI сообщения, адресованные агенту (`target=agent`);
|
||||||
|
- поддержать сценарий «сообщения самому себе между своими клиентами/устройствами», где один клиент/агент пишет другому в рамках одного логина.
|
||||||
|
|
||||||
|
## Базовая идея формата (черновик)
|
||||||
|
|
||||||
|
Пример префикса:
|
||||||
|
|
||||||
|
```text
|
||||||
|
@shine:pm:v1 {"target":"agent","agentId":"assistant","author":"human"}
|
||||||
|
Текст сообщения...
|
||||||
|
```
|
||||||
|
|
||||||
|
Пример ответа агента:
|
||||||
|
|
||||||
|
```text
|
||||||
|
@shine:pm:v1 {"target":"user","author":"agent","agentId":"assistant","agentLabel":"My Bot"}
|
||||||
|
Ответ агента...
|
||||||
|
```
|
||||||
|
|
||||||
|
## Почему отложено
|
||||||
|
|
||||||
|
- нужно отдельно согласовать финальный формат инструкции;
|
||||||
|
- нужно определить строгие правила UI-фильтрации и fallback;
|
||||||
|
- нужно определить, нужен ли позднее отдельный серверный роутинг для agent-сессий.
|
||||||
|
|
||||||
|
## Что сделать при возвращении к задаче
|
||||||
|
|
||||||
|
1. Зафиксировать окончательный формат префикса и JSON-полей.
|
||||||
|
2. Описать правила парсинга/валидации (включая битые/неполные префиксы).
|
||||||
|
3. Добавить UI-логику показа/скрытия agent-сообщений.
|
||||||
|
4. Добавить маркировку «ответ агента» в диалоге.
|
||||||
|
5. Продумать режим self-chat (между своими клиентами/агентом) в рамках одного логина.
|
||||||
@@ -12,9 +12,8 @@
|
|||||||
|
|
||||||
- логин пользователя;
|
- логин пользователя;
|
||||||
- неизменяемые параметры создания записи;
|
- неизменяемые параметры создания записи;
|
||||||
- публичный recovery-ключ пользователя;
|
|
||||||
- корневой публичный ключ пользователя;
|
- корневой публичный ключ пользователя;
|
||||||
- клиентский публичный ключ пользователя;
|
- ключ устройства;
|
||||||
- данные одного или нескольких пользовательских блокчейнов SHiNE;
|
- данные одного или нескольких пользовательских блокчейнов SHiNE;
|
||||||
- серверные данные пользователя, если пользователь выступает сервером;
|
- серверные данные пользователя, если пользователь выступает сервером;
|
||||||
- серверы доступа пользователя;
|
- серверы доступа пользователя;
|
||||||
@@ -35,9 +34,9 @@
|
|||||||
|
|
||||||
## 2.1. Кто оплачивает create/update PDA
|
## 2.1. Кто оплачивает create/update PDA
|
||||||
|
|
||||||
- Инструкции `create_user_pda` и `update_user_pda` оплачиваются с `client_key`.
|
- Инструкции `create_user_pda` и `update_user_pda` оплачиваются с `device_key`.
|
||||||
- `root_key` используется для подписи unsigned части записи через Ed25519 instruction и не является fee payer.
|
- `root_key` используется для подписи unsigned части записи через Ed25519 instruction и не является fee payer.
|
||||||
- Для server PDA это правило то же самое: пополнять SOL нужно на адрес `client_key`.
|
- Для server PDA это правило то же самое: пополнять SOL нужно на адрес `device_key`.
|
||||||
|
|
||||||
## 3. Общие правила кодирования
|
## 3. Общие правила кодирования
|
||||||
|
|
||||||
@@ -86,44 +85,25 @@ UserPdaRecordV1
|
|||||||
|
|
||||||
| block_type | Блок | Назначение |
|
| block_type | Блок | Назначение |
|
||||||
|------------|------|------------|
|
|------------|------|------------|
|
||||||
| `0` | `RecoveryKeyBlock` | Ключ восстановления пользователя. |
|
|
||||||
| `1` | `RootKeyBlock` | Корневой ключ пользователя. |
|
| `1` | `RootKeyBlock` | Корневой ключ пользователя. |
|
||||||
| `2` | `ClientKeyBlock` | Клиентский ключ пользователя. |
|
| `2` | `DeviceKeyBlock` | Ключ устройства пользователя. |
|
||||||
| `3` | `BlockchainRegistryBlock` | Один или несколько блокчейнов пользователя. |
|
| `3` | `BlockchainRegistryBlock` | Один или несколько блокчейнов пользователя. |
|
||||||
| `30` | `ServerProfileBlock` | Серверные данные пользователя. |
|
| `30` | `ServerProfileBlock` | Серверные данные пользователя. |
|
||||||
| `40` | `AccessServersBlock` | Серверы доступа/relay. |
|
| `40` | `AccessServersBlock` | Серверы доступа/relay. |
|
||||||
| `50` | `SessionsBlock` | Опубликованные пользовательские сессии и homeserver-ы. |
|
| `50` | `SessionsBlock` | Опубликованные пользовательские сессии и саб-серверы. |
|
||||||
| `70` | `TrustedStateBlock` | Счетчик trusted-связей. |
|
| `70` | `TrustedStateBlock` | Счетчик trusted-связей. |
|
||||||
| `255` | `ReservedBlock` | Зарезервировано, пока не используется. |
|
| `255` | `ReservedBlock` | Зарезервировано, пока не используется. |
|
||||||
|
|
||||||
Правила:
|
Правила:
|
||||||
|
|
||||||
- неизвестный `block_type` в `format_major = 1` считается ошибкой;
|
- неизвестный `block_type` в `format_major = 1` считается ошибкой;
|
||||||
- обязательные блоки: `RecoveryKeyBlock`, `RootKeyBlock`, `ClientKeyBlock`, `BlockchainRegistryBlock`;
|
- обязательные блоки: `RootKeyBlock`, `DeviceKeyBlock`, `BlockchainRegistryBlock`;
|
||||||
- необязательные блоки: `ServerProfileBlock`, `AccessServersBlock`, `SessionsBlock`, `TrustedStateBlock`;
|
- необязательные блоки: `ServerProfileBlock`, `AccessServersBlock`, `SessionsBlock`, `TrustedStateBlock`;
|
||||||
- каждый обязательный блок должен встречаться ровно один раз;
|
- каждый обязательный блок должен встречаться ровно один раз;
|
||||||
- порядок блоков в записи фиксируется для простоты проверки:
|
- порядок блоков в записи фиксируется для простоты проверки:
|
||||||
`RecoveryKey`, `RootKey`, `ClientKey`, `BlockchainRegistry`, `ServerProfile`, `AccessServers`, `Sessions`, `TrustedState`.
|
`RootKey`, `DeviceKey`, `BlockchainRegistry`, `ServerProfile`, `AccessServers`, `Sessions`, `TrustedState`.
|
||||||
|
|
||||||
## 6. RecoveryKeyBlock
|
## 6. RootKeyBlock
|
||||||
|
|
||||||
Recovery-ключ нужен для будущих сценариев восстановления и ротации остальных ключей. В текущей версии он только публикуется в записи и не меняется через обычный `update_user_pda`.
|
|
||||||
|
|
||||||
```text
|
|
||||||
RecoveryKeyBlock
|
|
||||||
- block_type: u8 = 0
|
|
||||||
- block_version: u8 = 0
|
|
||||||
- recovery_key: [u8; 32]
|
|
||||||
```
|
|
||||||
|
|
||||||
Правила:
|
|
||||||
|
|
||||||
- при создании задается публичный recovery-ключ пользователя;
|
|
||||||
- при обновлении `recovery_key` должен совпадать с предыдущей записью;
|
|
||||||
- приватный `recovery.key` в PDA не хранится;
|
|
||||||
- отдельная ротация recovery-ключа будет отдельным форматом/сценарием в будущем.
|
|
||||||
|
|
||||||
## 7. RootKeyBlock
|
|
||||||
|
|
||||||
Смена `root_key` пока не проектируется и не реализуется. Блок фиксирует только стадию `0`.
|
Смена `root_key` пока не проектируется и не реализуется. Блок фиксирует только стадию `0`.
|
||||||
|
|
||||||
@@ -140,24 +120,24 @@ RootKeyBlock
|
|||||||
- при обновлении `root_key` должен совпадать с предыдущей записью;
|
- при обновлении `root_key` должен совпадать с предыдущей записью;
|
||||||
- ротация root-key будет отдельным форматом/сценарием в будущем.
|
- ротация root-key будет отдельным форматом/сценарием в будущем.
|
||||||
|
|
||||||
## 8. ClientKeyBlock
|
## 7. DeviceKeyBlock
|
||||||
|
|
||||||
Смена `client_key` пока также не проектируется как отдельная ротация. В версии `0` хранится один клиентский ключ пользователя.
|
Смена `device_key` пока также не проектируется как отдельная ротация. В версии `0` хранится один ключ устройства.
|
||||||
|
|
||||||
```text
|
```text
|
||||||
ClientKeyBlock
|
DeviceKeyBlock
|
||||||
- block_type: u8 = 2
|
- block_type: u8 = 2
|
||||||
- block_version: u8 = 0
|
- block_version: u8 = 0
|
||||||
- client_key: [u8; 32]
|
- device_key: [u8; 32]
|
||||||
```
|
```
|
||||||
|
|
||||||
Правила:
|
Правила:
|
||||||
|
|
||||||
- при создании задается текущий клиентский публичный ключ пользователя;
|
- при создании задается текущий публичный ключ устройства;
|
||||||
- при обновлении `client_key` должен совпадать с предыдущей записью;
|
- при обновлении ключ устройства может быть обновлен только если это отдельно разрешено бизнес-логикой инструкции;
|
||||||
- история устройств и несколько клиентских ключей в этом формате не хранятся.
|
- история устройств и несколько устройств в этом формате не хранятся.
|
||||||
|
|
||||||
## 9. BlockchainRegistryBlock
|
## 8. BlockchainRegistryBlock
|
||||||
|
|
||||||
Блок хранит данные пользовательских блокчейнов SHiNE. Сейчас используется один блокчейн, но структура сразу сделана как список.
|
Блок хранит данные пользовательских блокчейнов SHiNE. Сейчас используется один блокчейн, но структура сразу сделана как список.
|
||||||
|
|
||||||
@@ -175,7 +155,7 @@ BlockchainRegistryBlock
|
|||||||
- в будущем можно увеличить количество записей без изменения смысла `BlockchainRecord`;
|
- в будущем можно увеличить количество записей без изменения смысла `BlockchainRecord`;
|
||||||
- каждый `BlockchainRecord` описывает один пользовательский SHiNE-блокчейн.
|
- каждый `BlockchainRecord` описывает один пользовательский SHiNE-блокчейн.
|
||||||
|
|
||||||
## 10. BlockchainRecord
|
## 9. BlockchainRecord
|
||||||
|
|
||||||
```text
|
```text
|
||||||
BlockchainRecord
|
BlockchainRecord
|
||||||
@@ -211,7 +191,7 @@ BlockchainRecord
|
|||||||
|
|
||||||
Arweave `tx_id` - обычное поле внутри записи конкретного блокчейна. Solana-программа не проверяет, что такой Arweave transaction действительно существует и содержит корректные данные; это ответственность клиента/сервера/пользователя.
|
Arweave `tx_id` - обычное поле внутри записи конкретного блокчейна. Solana-программа не проверяет, что такой Arweave transaction действительно существует и содержит корректные данные; это ответственность клиента/сервера/пользователя.
|
||||||
|
|
||||||
## 11. Правила обновления BlockchainRecord
|
## 10. Правила обновления BlockchainRecord
|
||||||
|
|
||||||
При обновлении записи:
|
При обновлении записи:
|
||||||
|
|
||||||
@@ -249,7 +229,7 @@ last_block_signature = Ed25519(blockchain_public_key, message)
|
|||||||
|
|
||||||
Причина проверки подписи `LastBlockState`: `root_key` управляет Solana-записью пользователя, а `blockchain_public_key` подтверждает состояние конкретного пользовательского блокчейна. Подписывается не голый хэш, а связка логина, имени блокчейна, номера последнего блока, хэша последнего блока и занятого размера.
|
Причина проверки подписи `LastBlockState`: `root_key` управляет Solana-записью пользователя, а `blockchain_public_key` подтверждает состояние конкретного пользовательского блокчейна. Подписывается не голый хэш, а связка логина, имени блокчейна, номера последнего блока, хэша последнего блока и занятого размера.
|
||||||
|
|
||||||
## 12. ServerProfileBlock
|
## 11. ServerProfileBlock
|
||||||
|
|
||||||
Блок присутствует, если пользователь выступает сервером.
|
Блок присутствует, если пользователь выступает сервером.
|
||||||
|
|
||||||
@@ -273,9 +253,9 @@ ServerProfileBlock
|
|||||||
- `address_format_version` — версия формата адреса, сейчас `0`;
|
- `address_format_version` — версия формата адреса, сейчас `0`;
|
||||||
- `sync_servers_count` максимум `32`;
|
- `sync_servers_count` максимум `32`;
|
||||||
- `server_address` - строковый адрес сервера в соответствии с `address_format_type`;
|
- `server_address` - строковый адрес сервера в соответствии с `address_format_type`;
|
||||||
- `sync_servers` - логины SHiNE-пользователей, зарегистрированных как серверы, с которыми этот сервер синхронизирует серверные данные и пользовательские блокчейны SHiNE. Это server-to-server список партнёров самого серверного узла, а не список серверов доставки личных сообщений для обычного пользователя. Solana-программа не обязана проверять, что эти логины действительно зарегистрированы как серверы.
|
- `sync_servers` - логины SHiNE-пользователей, зарегистрированных как серверы, с которыми этот сервер синхронизирует блокчейн и личные сообщения. Solana-программа не обязана проверять, что эти логины действительно зарегистрированы как серверы.
|
||||||
|
|
||||||
## 13. AccessServersBlock
|
## 12. AccessServersBlock
|
||||||
|
|
||||||
Блок хранит серверы доступа/relay для пользователя.
|
Блок хранит серверы доступа/relay для пользователя.
|
||||||
|
|
||||||
@@ -291,10 +271,10 @@ AccessServersBlock
|
|||||||
|
|
||||||
- блок может отсутствовать, если серверы доступа не заданы;
|
- блок может отсутствовать, если серверы доступа не заданы;
|
||||||
- список может обновляться при изменении маршрутизации пользователя;
|
- список может обновляться при изменении маршрутизации пользователя;
|
||||||
- `access_servers` - логины пользователей системы, используемых как серверы доступа/relay для конкретного пользователя. Через этот список клиентская и серверная логика SHiNE может маршрутизировать доставку личных сообщений, доступ к сессиям и другие пользовательские операции. Solana-программа не обязана проверять, что эти логины действительно зарегистрированы как серверы;
|
- `access_servers` - логины пользователей системы, используемых как серверы доступа/relay. Solana-программа не обязана проверять, что эти логины действительно зарегистрированы как серверы;
|
||||||
- точная семантика выбора сервера доступа определяется клиентской/серверной логикой SHiNE.
|
- точная семантика выбора сервера доступа определяется клиентской/серверной логикой SHiNE.
|
||||||
|
|
||||||
## 14. SessionsBlock
|
## 13. SessionsBlock
|
||||||
|
|
||||||
Блок хранит опубликованные пользовательские сессии. На текущем этапе регистрация пользователя не добавляет туда записи автоматически, поэтому стандартный create/update продолжает работать с пустым списком.
|
Блок хранит опубликованные пользовательские сессии. На текущем этапе регистрация пользователя не добавляет туда записи автоматически, поэтому стандартный create/update продолжает работать с пустым списком.
|
||||||
|
|
||||||
@@ -329,8 +309,7 @@ SessionRecord
|
|||||||
| Значение | Смысл |
|
| Значение | Смысл |
|
||||||
|----------|-------|
|
|----------|-------|
|
||||||
| `1` | Обычная пользовательская сессия. |
|
| `1` | Обычная пользовательская сессия. |
|
||||||
| `50` | Кошелёк пользователя. |
|
| `100` | Саб-сервер пользователя. |
|
||||||
| `100` | Homeserver пользователя. |
|
|
||||||
|
|
||||||
Правила:
|
Правила:
|
||||||
|
|
||||||
@@ -341,7 +320,7 @@ SessionRecord
|
|||||||
- внутри одного блока должны быть уникальны и `session_name`, и `session_pub_key`;
|
- внутри одного блока должны быть уникальны и `session_name`, и `session_pub_key`;
|
||||||
- на текущем этапе UI и регистрация не обязаны добавлять туда записи автоматически.
|
- на текущем этапе UI и регистрация не обязаны добавлять туда записи автоматически.
|
||||||
|
|
||||||
## 15. TrustedStateBlock
|
## 14. TrustedStateBlock
|
||||||
|
|
||||||
Пока trusted-логика не реализована полностью, поэтому блок хранит только счетчик.
|
Пока trusted-логика не реализована полностью, поэтому блок хранит только счетчик.
|
||||||
|
|
||||||
@@ -354,7 +333,7 @@ TrustedStateBlock
|
|||||||
|
|
||||||
Пока блок с доверенными лицами не реализуется, потому что полный формат trusted-логики еще не составлен. В будущем trusted-связи, очереди, таймеры и подтверждения должны быть вынесены в отдельный формат.
|
Пока блок с доверенными лицами не реализуется, потому что полный формат trusted-логики еще не составлен. В будущем trusted-связи, очереди, таймеры и подтверждения должны быть вынесены в отдельный формат.
|
||||||
|
|
||||||
## 16. Подпись user_pda
|
## 15. Подпись user_pda
|
||||||
|
|
||||||
Подписывается не вся PDA целиком, а unsigned-часть записи:
|
Подписывается не вся PDA целиком, а unsigned-часть записи:
|
||||||
|
|
||||||
@@ -375,7 +354,7 @@ Solana-программа проверяет подпись через встр
|
|||||||
|
|
||||||
Смену формата подписи сейчас не трогаем.
|
Смену формата подписи сейчас не трогаем.
|
||||||
|
|
||||||
## 17. Регистрация пользователя
|
## 16. Регистрация пользователя
|
||||||
|
|
||||||
При регистрации:
|
При регистрации:
|
||||||
|
|
||||||
@@ -393,12 +372,12 @@ Solana-программа проверяет подпись через встр
|
|||||||
- если покупается дополнительный лимит, пользователь платит комиссию за этот лимит;
|
- если покупается дополнительный лимит, пользователь платит комиссию за этот лимит;
|
||||||
- вся unsigned-часть записи подписана `root_key`.
|
- вся unsigned-часть записи подписана `root_key`.
|
||||||
|
|
||||||
## 18. Обновление пользователя
|
## 17. Обновление пользователя
|
||||||
|
|
||||||
При обновлении:
|
При обновлении:
|
||||||
|
|
||||||
- PDA должна существовать;
|
- PDA должна существовать;
|
||||||
- `login`, `created_at_ms`, `recovery_key`, `root_key`, `client_key` не меняются;
|
- `login`, `created_at_ms`, `root_key` не меняются;
|
||||||
- `record_number = previous_record_number + 1`;
|
- `record_number = previous_record_number + 1`;
|
||||||
- `prev_record_hash` равен хэшу unsigned-части предыдущей записи;
|
- `prev_record_hash` равен хэшу unsigned-части предыдущей записи;
|
||||||
- `updated_at_ms` обновляется;
|
- `updated_at_ms` обновляется;
|
||||||
@@ -408,7 +387,7 @@ Solana-программа проверяет подпись через встр
|
|||||||
- при увеличении оплаченного лимита пользователь доплачивает комиссию;
|
- при увеличении оплаченного лимита пользователь доплачивает комиссию;
|
||||||
- Arweave `tx_id` может быть пустым или обновленным, но его содержимое Solana не валидирует.
|
- Arweave `tx_id` может быть пустым или обновленным, но его содержимое Solana не валидирует.
|
||||||
|
|
||||||
## 19. Отличия от старого линейного формата
|
## 18. Отличия от старого линейного формата
|
||||||
|
|
||||||
Старый формат после `login` хранил поля линейно:
|
Старый формат после `login` хранил поля линейно:
|
||||||
|
|
||||||
@@ -416,8 +395,8 @@ Solana-программа проверяет подпись через встр
|
|||||||
- `root_key`;
|
- `root_key`;
|
||||||
- `blockchain_key_status`;
|
- `blockchain_key_status`;
|
||||||
- `blockchain_key`;
|
- `blockchain_key`;
|
||||||
- `client_key_status`;
|
- `device_key_status`;
|
||||||
- `client_key`;
|
- `device_key`;
|
||||||
- `chain_number`;
|
- `chain_number`;
|
||||||
- `balance`;
|
- `balance`;
|
||||||
- серверные поля;
|
- серверные поля;
|
||||||
@@ -428,54 +407,17 @@ Solana-программа проверяет подпись через встр
|
|||||||
|
|
||||||
Новый целевой формат сохраняет первые 9 фиксированных полей как заголовок, но дальше переходит на типизированные блоки:
|
Новый целевой формат сохраняет первые 9 фиксированных полей как заголовок, но дальше переходит на типизированные блоки:
|
||||||
|
|
||||||
- recovery-ключ становится отдельным обязательным блоком;
|
|
||||||
- ключи становятся отдельными блоками;
|
- ключи становятся отдельными блоками;
|
||||||
- данные блокчейна становятся расширенным блоком со своим публичным ключом, лимитом, занятым размером, вершиной цепочки и Arweave `tx_id`;
|
- данные блокчейна становятся расширенным блоком со своим публичным ключом, лимитом, занятым размером, вершиной цепочки и Arweave `tx_id`;
|
||||||
- серверные данные и access-серверы отделяются от данных блокчейна;
|
- серверные данные и access-серверы отделяются от данных блокчейна;
|
||||||
- расширение формата делается добавлением новых версий блоков или новых `block_type`, а не вставкой полей в середину линейной записи.
|
- расширение формата делается добавлением новых версий блоков или новых `block_type`, а не вставкой полей в середину линейной записи.
|
||||||
|
|
||||||
## 20. Деривация ключей из master secret
|
## 18. Что пока не входит в формат
|
||||||
|
|
||||||
Сама Solana-программа не вычисляет ключи из секрета и не хранит приватные ключи. Но текущая согласованная клиентская схема деривации для публичной версии формата фиксируется здесь как reference для UI/ESP32/внешних клиентов.
|
|
||||||
|
|
||||||
Базовая формула:
|
|
||||||
|
|
||||||
```text
|
|
||||||
seed = SHA-256("SHiNE-key" || 0x00 || master_secret32 || 0x00 || suffix_utf8)
|
|
||||||
```
|
|
||||||
|
|
||||||
Где:
|
|
||||||
|
|
||||||
- `master_secret32` — 32-байтовый master secret пользователя;
|
|
||||||
- `suffix_utf8` — строка назначения ключа.
|
|
||||||
|
|
||||||
Согласованные suffix:
|
|
||||||
|
|
||||||
```text
|
|
||||||
"recovery.key"
|
|
||||||
"root.key"
|
|
||||||
"blockchain.key"
|
|
||||||
"client.key"
|
|
||||||
```
|
|
||||||
|
|
||||||
Соответствие:
|
|
||||||
|
|
||||||
```text
|
|
||||||
recovery.seed = SHA-256("SHiNE-key" || 0x00 || master_secret32 || 0x00 || "recovery.key")
|
|
||||||
root.seed = SHA-256("SHiNE-key" || 0x00 || master_secret32 || 0x00 || "root.key")
|
|
||||||
blockchain.seed = SHA-256("SHiNE-key" || 0x00 || master_secret32 || 0x00 || "blockchain.key")
|
|
||||||
client.seed = SHA-256("SHiNE-key" || 0x00 || master_secret32 || 0x00 || "client.key")
|
|
||||||
```
|
|
||||||
|
|
||||||
Далее каждая строка `seed` интерпретируется off-chain как `seed32` для отдельной пары Ed25519.
|
|
||||||
|
|
||||||
## 21. Что пока не входит в формат
|
|
||||||
|
|
||||||
Пока не проектируем:
|
Пока не проектируем:
|
||||||
|
|
||||||
- ротацию `recovery_key`;
|
|
||||||
- ротацию `root_key`;
|
- ротацию `root_key`;
|
||||||
- сложную ротацию `client_key`;
|
- сложную ротацию `device_key`;
|
||||||
- ротацию `blockchain_public_key`;
|
- ротацию `blockchain_public_key`;
|
||||||
- проверку содержимого Arweave transaction;
|
- проверку содержимого Arweave transaction;
|
||||||
- хранение полной истории пользовательского блокчейна внутри Solana;
|
- хранение полной истории пользовательского блокчейна внутри Solana;
|
||||||
@@ -16,7 +16,7 @@
|
|||||||
|
|
||||||
В Solana-модуле сейчас три основные программы:
|
В Solana-модуле сейчас три основные программы:
|
||||||
|
|
||||||
1. `shine_login_guard` — в текущем временном режиме проверяет базовую валидность логина и пропускает on-chain любые валидные логины длиной от 5 символов.
|
1. `shine_login_guard` — проверяет логин и возвращает класс логина: обычный, premium или trademark.
|
||||||
2. `shine_users` — создает и обновляет пользовательскую PDA-запись, проверяет подписи и берет оплату за регистрацию/увеличение лимита.
|
2. `shine_users` — создает и обновляет пользовательскую PDA-запись, проверяет подписи и берет оплату за регистрацию/увеличение лимита.
|
||||||
3. `shine_payments` — принимает входящий поток средств в `inflow_vault`, ведет очереди тикетов, позволяет DAO выдавать лимиты менеджерам и выполняет выплаты.
|
3. `shine_payments` — принимает входящий поток средств в `inflow_vault`, ведет очереди тикетов, позволяет DAO выдавать лимиты менеджерам и выполняет выплаты.
|
||||||
|
|
||||||
@@ -35,7 +35,7 @@ DAO в текущем виде не является отдельной Anchor-
|
|||||||
|
|
||||||
| Блок | Папка/имя | Текущие функции из кода | Основной смысл |
|
| Блок | Папка/имя | Текущие функции из кода | Основной смысл |
|
||||||
| --- | --- | --- | --- |
|
| --- | --- | --- | --- |
|
||||||
| 1 | `shine_login_guard` | `classify_login` | Временная on-chain проверка логина перед регистрацией. |
|
| 1 | `shine_login_guard` | `classify_login` | Проверка логина перед регистрацией. |
|
||||||
| 2 | `shine_users` | `init_users_economy_config`, `update_users_economy_config`, `create_user_pda`, `update_user_pda` | Регистрация пользователя, обновление записи, экономика лимита. |
|
| 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-настройки, лимиты менеджеров. |
|
| 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 и управляющие ключи | Управление правами, казной, настройками и будущими обновлениями программ. |
|
| DAO | governance/authority | Вызовы через governance и управляющие ключи | Управление правами, казной, настройками и будущими обновлениями программ. |
|
||||||
@@ -46,9 +46,9 @@ DAO в текущем виде не является отдельной Anchor-
|
|||||||
|
|
||||||
| Программа | Program ID |
|
| Программа | Program ID |
|
||||||
| --- | --- |
|
| --- | --- |
|
||||||
| `shine_login_guard` | `SHiGxGsXGioQYCYhchQ5R7KWoxN5UjFAFsucPf6sfnh` |
|
| `shine_login_guard` | `3xkopA7cXagxzMFrKdv3NCBfV6BKiRJCk69kr27M2sRo` |
|
||||||
| `shine_users` | `SHiNEPr1APdAgNBteUyBXcNovaHctpSjUu8oH2ZJdN6` |
|
| `shine_users` | `FZS1YctoeEhCkZ5VTjsysUFAXR8CqxYztcLboXcg2Rpm` |
|
||||||
| `shine_payments` | `SHiPmXbM9Fs9khzRUW3TGKsS2W84aqaXTxs3ZkajW9v` |
|
| `shine_payments` | `c4yTa4JT9EtQDCBX9LmWFK6T2gp4JGsuymFbom2EudW` |
|
||||||
|
|
||||||
Если эти адреса меняются, нужно синхронно обновить:
|
Если эти адреса меняются, нужно синхронно обновить:
|
||||||
|
|
||||||
@@ -62,7 +62,7 @@ DAO в текущем виде не является отдельной Anchor-
|
|||||||
Для удобного понимания на старте можно считать, что есть четыре группы ключей:
|
Для удобного понимания на старте можно считать, что есть четыре группы ключей:
|
||||||
|
|
||||||
1. `key_1` / authority программы `shine_login_guard`.
|
1. `key_1` / authority программы `shine_login_guard`.
|
||||||
- Сейчас программа только выполняет временную базовую проверку логина.
|
- Сейчас программа только классифицирует логин.
|
||||||
- На первом этапе ее можно оставить под отдельным ключом.
|
- На первом этапе ее можно оставить под отдельным ключом.
|
||||||
- В будущем право обновления можно передать DAO.
|
- В будущем право обновления можно передать DAO.
|
||||||
|
|
||||||
@@ -140,7 +140,7 @@ Solana-модуль лежит в основном репозитории как
|
|||||||
|
|
||||||
1. `shine_login_guard`
|
1. `shine_login_guard`
|
||||||
- Пока оставить на отдельном ключе `key_1`.
|
- Пока оставить на отдельном ключе `key_1`.
|
||||||
- Передачу DAO сделать позже, когда временный режим будет заменён на постоянную логику промокодов и/или premium/trademark.
|
- Передачу DAO сделать позже, когда логика premium/trademark стабилизируется.
|
||||||
|
|
||||||
2. `shine_users`
|
2. `shine_users`
|
||||||
- Economy-настройки уже должны обновляться DAO-authority.
|
- Economy-настройки уже должны обновляться DAO-authority.
|
||||||
+6
-6
@@ -19,18 +19,18 @@
|
|||||||
5. `DAO_TREASURY_WALLET` / `dao_wallet` — казна DAO.
|
5. `DAO_TREASURY_WALLET` / `dao_wallet` — казна DAO.
|
||||||
6. `manager_wallet` — кошелек менеджера, которому DAO выдает лимиты на создание тикетов.
|
6. `manager_wallet` — кошелек менеджера, которому DAO выдает лимиты на создание тикетов.
|
||||||
7. `user root_key` — корневой ключ пользователя для подписи пользовательской записи.
|
7. `user root_key` — корневой ключ пользователя для подписи пользовательской записи.
|
||||||
8. `user client_key` — ключ устройства пользователя.
|
8. `user device_key` — ключ устройства пользователя.
|
||||||
9. `server_key` — ключ сервера пользователя, если пользователь является сервером.
|
9. `server_key` — ключ сервера пользователя, если пользователь является сервером.
|
||||||
|
|
||||||
Текущие адреса из `programs/common/src/deploy_config.rs`:
|
Текущие адреса из `programs/common/src/deploy_config.rs`:
|
||||||
|
|
||||||
| Роль | Адрес |
|
| Роль | Адрес |
|
||||||
| --- | --- |
|
| --- | --- |
|
||||||
| `SHINE_LOGIN_GUARD_PROGRAM_ID` | `SHiGxGsXGioQYCYhchQ5R7KWoxN5UjFAFsucPf6sfnh` |
|
| `SHINE_LOGIN_GUARD_PROGRAM_ID` | `3xkopA7cXagxzMFrKdv3NCBfV6BKiRJCk69kr27M2sRo` |
|
||||||
| `SHINE_USERS_PROGRAM_ID` | `SHiNEPr1APdAgNBteUyBXcNovaHctpSjUu8oH2ZJdN6` |
|
| `SHINE_USERS_PROGRAM_ID` | `FZS1YctoeEhCkZ5VTjsysUFAXR8CqxYztcLboXcg2Rpm` |
|
||||||
| `SHINE_PAYMENTS_PROGRAM_ID` | `SHiPmXbM9Fs9khzRUW3TGKsS2W84aqaXTxs3ZkajW9v` |
|
| `SHINE_PAYMENTS_PROGRAM_ID` | `c4yTa4JT9EtQDCBX9LmWFK6T2gp4JGsuymFbom2EudW` |
|
||||||
| `DAO_AUTHORITY` | `aiShm43fZjm3YkMs22sYL1bpXaL3bVxv7SSraPHzVgq` |
|
| `DAO_AUTHORITY` | `FUc28vNixp7F3nnkpGVt6nuJbgvJ4429v4B5wS52Df6P` |
|
||||||
| `DAO_TREASURY_WALLET` | `aiShm43fZjm3YkMs22sYL1bpXaL3bVxv7SSraPHzVgq` |
|
| `DAO_TREASURY_WALLET` | `FUc28vNixp7F3nnkpGVt6nuJbgvJ4429v4B5wS52Df6P` |
|
||||||
|
|
||||||
## Постоянные PDA
|
## Постоянные PDA
|
||||||
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user