SHA256
Привести документацию и TODO к production-структуре
This commit is contained in:
@@ -36,4 +36,4 @@
|
||||
## Какие документы потом обновить
|
||||
|
||||
- `Deploy/`;
|
||||
- `Dev_Docs/Blockchain/sync-between-servers.md`, если изменится поведение остановки/восстановления.
|
||||
- `docs/Blockchain/sync-between-servers.md`, если изменится поведение остановки/восстановления.
|
||||
|
||||
@@ -24,6 +24,6 @@ QR-подключение других устройств сейчас есть
|
||||
|
||||
## Какие документы потом обновить
|
||||
|
||||
- `Dev_Docs/Solana_Architecture/README.md`;
|
||||
- `docs/Solana_Architecture/README.md`;
|
||||
- `TODO/medium/2026-06-03_подключение_других_устройств_через_qr.md`;
|
||||
- `TODO/medium/2026-06-02_сессионные_homeserver_в_pda.md`.
|
||||
|
||||
+17
-3
@@ -12,16 +12,30 @@
|
||||
- откуда продолжать;
|
||||
- какие документы потом надо обновить.
|
||||
- Это не активная разработка. Тут только план и контекст.
|
||||
- Старую папку `Dev_Docs/Future_Features/` считать архивной и больше не использовать как источник новых задач.
|
||||
- Старую папку `docs/Future_Features/` считать архивной и больше не использовать как источник новых задач.
|
||||
|
||||
## Текущие задачи
|
||||
|
||||
- `2026-06-26_1800_корректное_завершение_за_30с.md` - дать сервису до 30 секунд на корректное завершение опасных операций перед рестартом.
|
||||
- `2026-06-26_1805_межсерверный_ws_и_dm_sync.md` - постоянный server-to-server WebSocket, push новых блоков и DM, ACK и backfill.
|
||||
- `2026-06-26_1810_подключение_устройств_по_qr.md` - довести подключение других устройств по QR и перевести это в нормальные типизированные сессии.
|
||||
- `2026-06-26_1815_esp32_файловое_хранилище.md` - использовать ESP32 как личное файловое хранилище для переписок и вложений.
|
||||
|
||||
## Перенесённые планы из `Dev_Docs/Future_Features/`
|
||||
## Децентрализация
|
||||
|
||||
Текущий production-режим SHiNE считается односерверным. Задачи по нескольким серверам, Arweave и realtime PDA/Solana sync вынесены в `Децентрализация/` и не блокируют выкладку текущей версии на GitHub.
|
||||
|
||||
- `Децентрализация/односерверный_production_режим.md` - границы текущей production-версии с одним сервером.
|
||||
- `Децентрализация/запись_блокчейнов_в_arweave.md` - будущая запись/архивация блокчейнов в Arweave.
|
||||
- `Децентрализация/realtime_pda_solana_sync.md` - будущая онлайн-синхронизация PDA и Solana.
|
||||
- `Децентрализация/межсерверная_передача_сообщений.md` - будущая доставка сообщений между серверами.
|
||||
- `Децентрализация/межсерверные_звонки.md` - будущая маршрутизация звонков между серверами.
|
||||
- `Децентрализация/2026-06-26_1805_межсерверный_ws_и_dm_sync.md` - перенесённый старый план постоянного server-to-server WS и DM sync.
|
||||
|
||||
## Новые фишки которые надо доделать
|
||||
|
||||
- `Новые фишки которые надо доделать/Новая_контентная_модель_блокчейна/` - отложенная новая контентная модель блокчейна, не входящая в текущий односерверный production-релиз.
|
||||
|
||||
## Перенесённые планы из `docs/Future_Features/`
|
||||
|
||||
### near
|
||||
|
||||
|
||||
@@ -104,9 +104,9 @@
|
||||
|
||||
## Какие документы нужно будет обновить при реализации
|
||||
|
||||
- `Dev_Docs/Blockchain/README.md` и связанные файлы, если изменятся типы служебных сообщений или форматы блокчейн-команд.
|
||||
- `Dev_Docs/API/` если изменится публичный серверный API или появятся новые операции.
|
||||
- `Dev_Docs/Personal_Messages/Протокол_DM_v1.md` если часть маршрутизации или подтверждений будет встроена в существующую логику доставки/сессий.
|
||||
- `docs/Blockchain/README.md` и связанные файлы, если изменятся типы служебных сообщений или форматы блокчейн-команд.
|
||||
- `docs/API/` если изменится публичный серверный API или появятся новые операции.
|
||||
- `docs/Personal_Messages/Протокол_DM_v1.md` если часть маршрутизации или подтверждений будет встроена в существующую логику доставки/сессий.
|
||||
- Документацию по homeserver/ESP32, если появится пользовательская или сервисная файловая логика на устройстве.
|
||||
|
||||
## С какого места продолжать позже
|
||||
|
||||
@@ -58,7 +58,7 @@
|
||||
|
||||
## Почему это не лежит в Pending_Features
|
||||
|
||||
`Dev_Docs/Pending_Features/` предназначена для фич, которые уже реализованы и ждут ручной проверки.
|
||||
`docs/Pending_Features/` предназначена для фич, которые уже реализованы и ждут ручной проверки.
|
||||
|
||||
Репосты сейчас не подходят под этот статус: они не должны проверяться как готовая фича, потому что пользовательский сценарий временно закрыт, а серверная запись новых репостов заблокирована. Поэтому старый pending-файл удалён, а задача перенесена сюда как будущая.
|
||||
|
||||
@@ -82,11 +82,11 @@
|
||||
- отображение `targetBlockchainName`, `targetBlockNumber`, `targetBlockHash`.
|
||||
7. Добавить или обновить тесты на успешный репост и отказ некорректных target-полей.
|
||||
8. Обновить документацию:
|
||||
- `Dev_Docs/Blockchain/11_TEXT_Blocks.md`;
|
||||
- `Dev_Docs/Blockchain/CHANGELOG.md`;
|
||||
- `Dev_Docs/API/04_Add_Block_to_Blockchain_API.md`;
|
||||
- `docs/Blockchain/11_TEXT_Blocks.md`;
|
||||
- `docs/Blockchain/CHANGELOG.md`;
|
||||
- `docs/API/04_Add_Block_to_Blockchain_API.md`;
|
||||
- документы API чтения каналов/тредов, если изменятся поля ответа.
|
||||
9. После реализации перенести задачу из `TODO/` в `Dev_Docs/Pending_Features/` как фичу, требующую ручной проверки.
|
||||
9. После реализации перенести задачу из `TODO/` в `docs/Pending_Features/` как фичу, требующую ручной проверки.
|
||||
|
||||
## Минимальный чек-лист ручной проверки в будущем
|
||||
|
||||
|
||||
@@ -47,10 +47,10 @@
|
||||
|
||||
## Документы, которые обновить при реализации
|
||||
|
||||
- `Dev_Docs/Blockchain/`, если появятся или изменятся блоки баланса.
|
||||
- `Dev_Docs/Blockchain/CHANGELOG.md`, если меняется блокчейн-формат.
|
||||
- `Dev_Docs/API/`, если меняется серверный API.
|
||||
- `Dev_Docs/Pending_Features/` - добавить файл ручной проверки после реализации.
|
||||
- `docs/Blockchain/`, если появятся или изменятся блоки баланса.
|
||||
- `docs/Blockchain/CHANGELOG.md`, если меняется блокчейн-формат.
|
||||
- `docs/API/`, если меняется серверный API.
|
||||
- `docs/Pending_Features/` - добавить файл ручной проверки после реализации.
|
||||
- Документацию Solana-регистрации, если баланс будет связан с Solana-модулем.
|
||||
|
||||
## Минимальная проверка в будущем
|
||||
|
||||
@@ -34,10 +34,10 @@
|
||||
|
||||
## Документы, которые нужно обновить при возврате
|
||||
|
||||
- `Dev_Docs/Keys/README.md`
|
||||
- `Dev_Docs/Personal_Messages/Протокол_DM_v1.md`
|
||||
- `Dev_Docs/API/`
|
||||
- `Dev_Docs/Blockchain/`, если появятся новые блоки или команды для файлов.
|
||||
- `docs/Keys/README.md`
|
||||
- `docs/Personal_Messages/Протокол_DM_v1.md`
|
||||
- `docs/API/`
|
||||
- `docs/Blockchain/`, если появятся новые блоки или команды для файлов.
|
||||
|
||||
## С какого места продолжать
|
||||
|
||||
|
||||
@@ -84,11 +84,11 @@
|
||||
## Что нужно обновить при реализации
|
||||
|
||||
- `shine-solana/shine/doc/formats/shine-user-pda-format-v.1.0.md`
|
||||
- `Dev_Docs/Solana_Architecture/README.md`
|
||||
- `Dev_Docs/Инициализация_Solana_регистрации/README.md`
|
||||
- `Dev_Docs/Keys/README.md`
|
||||
- `Dev_Docs/Personal_Messages/Протокол_DM_v1.md`, если изменится адресация DM по типам сессий
|
||||
- `Dev_Docs/API/`, если появятся новые серверные операции или изменятся ответы
|
||||
- `docs/Solana_Architecture/README.md`
|
||||
- `docs/Инициализация_Solana_регистрации/README.md`
|
||||
- `docs/Keys/README.md`
|
||||
- `docs/Personal_Messages/Протокол_DM_v1.md`, если изменится адресация DM по типам сессий
|
||||
- `docs/API/`, если появятся новые серверные операции или изменятся ответы
|
||||
|
||||
## Что пока не делать
|
||||
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
|
||||
## Что обновить при возврате
|
||||
|
||||
- `Dev_Docs/Pending_Features/README.md`
|
||||
- `docs/Pending_Features/README.md`
|
||||
- `shine-UI/js/pages/connect-device-view.js`
|
||||
- `shine-UI/js/pages/device-qr-view.js`
|
||||
- `shine-UI/js/services/qr-key-transfer-service.js`
|
||||
|
||||
@@ -58,8 +58,8 @@
|
||||
## Документы, которые обновить при реализации
|
||||
|
||||
- Документацию UI/кошельков, если такая есть.
|
||||
- `Dev_Docs/Pending_Features/` - добавить файл ручной проверки после реализации.
|
||||
- `Dev_Docs/API/`, только если появится новый серверный API или логирование.
|
||||
- `docs/Pending_Features/` - добавить файл ручной проверки после реализации.
|
||||
- `docs/API/`, только если появится новый серверный API или логирование.
|
||||
|
||||
## Минимальная проверка
|
||||
|
||||
|
||||
@@ -69,7 +69,7 @@ git show 0240db5:shine-solana/shine/programs/shine_login_guard/src/lib.rs
|
||||
- `shine-solana/shine/doc/programs/shine_login_guard.md`
|
||||
|
||||
5. Архитектурная документация:
|
||||
- `Dev_Docs/Solana_Architecture/README.md`
|
||||
- `docs/Solana_Architecture/README.md`
|
||||
|
||||
6. UI-логика precheck:
|
||||
- `shine-UI/js/pages/register-view.js`
|
||||
@@ -108,7 +108,7 @@ git show 0240db5:shine-solana/shine/programs/shine_login_guard/src/lib.rs
|
||||
4. Проверить, что `build.rs` всё ещё генерирует `generated_dictionary.rs` в прежнем формате.
|
||||
5. Сверить актуальность словарей в `src/dictionaries`.
|
||||
6. Обновить `shine_login_guard.md` обратно под словарную логику.
|
||||
7. Обновить `Dev_Docs/Solana_Architecture/README.md`.
|
||||
7. Обновить `docs/Solana_Architecture/README.md`.
|
||||
|
||||
### Проверка после возврата
|
||||
|
||||
|
||||
@@ -28,6 +28,6 @@
|
||||
|
||||
## Какие документы потом обновить
|
||||
|
||||
- `Dev_Docs/Personal_Messages/Протокол_DM_v1.md`, если изменится стратегия миграции старой истории;
|
||||
- `Dev_Docs/Personal_Messages/Формат_DM_v1.md`, если появится отдельное правило совместимости/конвертации;
|
||||
- при необходимости `Dev_Docs/API/12_Direct_Messages_Push_Calls_API.md`, если затронется поведение backlog/доставки.
|
||||
- `docs/Personal_Messages/Протокол_DM_v1.md`, если изменится стратегия миграции старой истории;
|
||||
- `docs/Personal_Messages/Формат_DM_v1.md`, если появится отдельное правило совместимости/конвертации;
|
||||
- при необходимости `docs/API/12_Direct_Messages_Push_Calls_API.md`, если затронется поведение backlog/доставки.
|
||||
|
||||
+7
-5
@@ -2,7 +2,9 @@
|
||||
|
||||
## Зачем
|
||||
|
||||
Сейчас синхронизация между серверами работает в основном как periodic sync и one-shot push. Для нормальной репликации ещё нужен постоянный межсерверный канал:
|
||||
Текущий production-режим SHiNE рассчитан на один основной сервер. Межсерверная синхронизация относится к будущей децентрализации и не должна блокировать выкладку односерверной production-версии.
|
||||
|
||||
Сейчас синхронизация между серверами работает в основном как periodic sync и one-shot push. Для нормальной репликации в будущем ещё нужен постоянный межсерверный канал:
|
||||
|
||||
- живое подключение к партнёру;
|
||||
- push новых блоков;
|
||||
@@ -35,7 +37,7 @@
|
||||
|
||||
## Какие документы потом обновить
|
||||
|
||||
- `Dev_Docs/Blockchain/sync-between-servers.md`;
|
||||
- `Dev_Docs/Personal_Messages/Протокол_DM_v1.md`;
|
||||
- `Dev_Docs/Personal_Messages/Формат_DM_v1.md`;
|
||||
- `Dev_Docs/API/`.
|
||||
- `docs/Blockchain/sync-between-servers.md`;
|
||||
- `docs/Personal_Messages/Протокол_DM_v1.md`;
|
||||
- `docs/Personal_Messages/Формат_DM_v1.md`;
|
||||
- `docs/API/`.
|
||||
@@ -0,0 +1,16 @@
|
||||
# Децентрализация
|
||||
|
||||
Папка для задач, которые нужны для будущего режима с несколькими серверами, Solana/PDA-синхронизацией и внешним хранением данных.
|
||||
|
||||
## Текущий статус
|
||||
|
||||
Сейчас production-режим SHiNE считается односерверным: один сервер обслуживает пользователей, сообщения, звонки и запись данных. Задачи из этой папки не являются блокерами для выкладки текущего репозитория на GitHub и запуска одного production-сервера.
|
||||
|
||||
## Задачи
|
||||
|
||||
- `односерверный_production_режим.md` - зафиксировать границы текущей production-версии.
|
||||
- `запись_блокчейнов_в_arweave.md` - вынести долговременную запись блокчейнов в Arweave.
|
||||
- `realtime_pda_solana_sync.md` - сделать онлайн-синхронизацию PDA/Solana в реальном времени.
|
||||
- `межсерверная_передача_сообщений.md` - реализовать доставку сообщений между серверами.
|
||||
- `межсерверные_звонки.md` - реализовать маршрутизацию звонков между серверами.
|
||||
- `2026-06-26_1805_межсерверный_ws_и_dm_sync.md` - старый план постоянного server-to-server WS и DM sync, перенесённый в контекст децентрализации.
|
||||
@@ -0,0 +1,30 @@
|
||||
# Realtime-синхронизация PDA и Solana
|
||||
|
||||
## Зачем
|
||||
|
||||
В будущем PDA-записи и Solana-состояние должны автоматически и быстро синхронизироваться с серверным состоянием, чтобы данные пользователей, homeserver-сессии и связанные записи не расходились.
|
||||
|
||||
## Что сделать
|
||||
|
||||
1. Определить, какие серверные события должны обновлять PDA.
|
||||
2. Добавить очередь/воркер для надёжной отправки изменений в Solana.
|
||||
3. Добавить периодическую сверку серверного состояния с PDA.
|
||||
4. Добавить обработку ошибок, повторов и конфликтов версий.
|
||||
5. Добавить мониторинг задержек и неуспешных Solana-транзакций.
|
||||
|
||||
## Что учесть
|
||||
|
||||
- Solana/Anchor-модуль находится в `shine-solana/shine/` и ведётся отдельно от основного server/UI deploy.
|
||||
- Перед изменениями внутри Solana-модуля нужно читать `shine-solana/shine/AGENTS.md`.
|
||||
- Основная инструкция по Solana-регистрации находится в `docs/Инициализация_Solana_регистрации/README.md`.
|
||||
- Формат пользовательской PDA-записи описан в `shine-solana/shine/doc/formats/shine-user-pda-format-v.1.0.md`.
|
||||
|
||||
## Документы, которые потом нужно обновить
|
||||
|
||||
- `docs/Инициализация_Solana_регистрации/README.md`;
|
||||
- `docs/Solana_Architecture/README.md`;
|
||||
- `shine-solana/shine/doc/formats/shine-user-pda-format-v.1.0.md`, если меняется формат PDA.
|
||||
|
||||
## Статус
|
||||
|
||||
Отложено до этапа децентрализации.
|
||||
@@ -0,0 +1,97 @@
|
||||
# ИТХ — ежедневное закрытие блокчейна SHiNE (кратко, для людей)
|
||||
|
||||
Статус: дизайн согласован, код не написан.
|
||||
Подробная спецификация для реализации: [Спецификация_ИТХ_v1.md](./Спецификация_ИТХ_v1.md).
|
||||
|
||||
## Что это
|
||||
|
||||
Раз в сутки один из серверов SHiNE («дежурный») делает **закрытие дня**:
|
||||
|
||||
1. Строит меркл-дерево по вершинам всех пользовательских цепочек —
|
||||
его корень называется **ИТХ** (итоговый хэш дня).
|
||||
2. Собирает **бандл** — все новые блоки, появившиеся с прошлого закрытия,
|
||||
с хэшами и подписями пользователей.
|
||||
3. Заливает всё это одним файлом (**чекпоинт**) в Arweave.
|
||||
Каждый чекпоинт ссылается на предыдущий — получается цепочка чекпоинтов.
|
||||
4. Пишет в свой Solana PDA: «крайний чекпоинт — вот такой хэш, лежит по такому Arweave-адресу».
|
||||
5. Публикует пост о закрытии в своём канале закрытий прямо в Сиянии.
|
||||
|
||||
Остальные серверы проверяют чекпоинт, и если всё сходится:
|
||||
|
||||
- обновляют слот в **своём** PDA на этот же чекпоинт («согласен»);
|
||||
- ставят **лайк** на пост дежурного в канале.
|
||||
|
||||
Несогласие = просто ничего не делать (слот остаётся на прошлом чекпоинте).
|
||||
|
||||
## Зачем
|
||||
|
||||
- **Неизменяемость истории.** Прошлые дни зафиксированы в Arweave и Solana —
|
||||
их нельзя переписать даже сговором всех живых серверов.
|
||||
- **Независимая проверяемость.** Любой желающий может скачать цепочку чекпоинтов
|
||||
из Arweave и проверить всю историю, не доверяя ни одному живому серверу.
|
||||
- **Резерв последней надежды.** Даже если все серверы погибнут,
|
||||
история до последнего чекпоинта не потеряна.
|
||||
|
||||
В повседневной работе серверы синхронизируются друг с другом напрямую —
|
||||
Arweave почти не читается, это гарантия, а не рабочая лошадка.
|
||||
|
||||
## Кто дежурит
|
||||
|
||||
График ведёт DAO в специальном PDA: упорядоченный список логинов серверов
|
||||
и дата начала эпохи. Дежурный дня = `dayIndex % количество_серверов`.
|
||||
Обновлять список может только DAO.
|
||||
|
||||
## Расписание суток (UTC)
|
||||
|
||||
| Время | Что происходит |
|
||||
| --- | --- |
|
||||
| День D, 00:00–24:00 | Обычная работа, блоки копятся. |
|
||||
| D+1, 00:00–06:00 | Дежурный закрывает день D: Arweave → PDA → пост в канал. |
|
||||
| D+1, до 24:00 | Остальные проверяют и подтверждают (PDA + лайк). |
|
||||
|
||||
Если дежурный не закрыл (или закрыл неверно) — день просто пропускается:
|
||||
никто ничего не подтверждает, а следующий дежурный включает в свой бандл
|
||||
блоки за все пропущенные дни и ссылается на последний признанный чекпоинт.
|
||||
|
||||
## Как понять, какой чекпоинт «настоящий»
|
||||
|
||||
**Актуальный чекпоинт = последний, на который указывают слоты
|
||||
больше половины серверов из графика.** Кворума on-chain нет —
|
||||
каждый сервер публикует только своё мнение, а наблюдатель считает голоса сам.
|
||||
Сервер, который систематически ошибается, DAO просто исключает из списка.
|
||||
|
||||
Правило работает относительно списка: по умолчанию считают по DAO-списку,
|
||||
но любой наблюдатель вправе считать голоса по **собственному списку доверенных
|
||||
серверов**. Коротко: запись — по общему списку, чтение — по любому.
|
||||
|
||||
## Форки и параллельные группы
|
||||
|
||||
- Пользователь раздвоил свою цепочку до чекпоинта — дежурный возьмёт самую
|
||||
длинную ветку, проигравшая отбросится; пользователь не наказывается.
|
||||
- Ветка против уже зачекпоинченного — побеждает чекпоинт, ветка отбрасывается.
|
||||
- Умудрился дописать противоречащие ветки в архивы **двух разных групп закрытий** —
|
||||
цепочка замораживается навсегда (два неизменных архива слить нельзя),
|
||||
дальше пользователь начинает новую цепочку `login-002`.
|
||||
- Любая другая группа серверов может вести свою параллельную цепочку чекпоинтов
|
||||
над теми же данными — это разрешено by design: подделать блоки они не могут,
|
||||
а независимых «нотариусов» у истории становится больше.
|
||||
|
||||
## Восстановление сервера с нуля
|
||||
|
||||
1. Прочитать DAO PDA → список серверов графика.
|
||||
2. Прочитать их PDA-слоты → последний признанный чекпоинт (>половины голосов).
|
||||
3. Скачать из Arweave цепочку чекпоинтов от генезиса и раскатать бандлы.
|
||||
4. «Хвост» после последнего чекпоинта добрать живой синхронизацией с серверами.
|
||||
|
||||
## Кто платит
|
||||
|
||||
Каждый сервер платит сам: за свои Arweave-загрузки в свои дежурные дни
|
||||
и за свои Solana-транзакции.
|
||||
|
||||
## Связанные документы
|
||||
|
||||
- [Спецификация_ИТХ_v1.md](./Спецификация_ИТХ_v1.md) — точный формат и алгоритмы (для реализации).
|
||||
- `docs/Blockchain/` — формат блоков и цепочек SHiNE.
|
||||
- `docs/Blockchain/sync-between-servers.md` — живая межсерверная синхронизация.
|
||||
- `docs/Solana_Architecture/README.md` — устройство Solana-части.
|
||||
- `shine-solana/shine/doc/formats/shine-user-pda-format-v.1.0.md` — формат `user_pda`.
|
||||
@@ -0,0 +1,398 @@
|
||||
# Спецификация ИТХ v1 — ежедневное закрытие блокчейна SHiNE
|
||||
|
||||
Статус: дизайн согласован пользователем, реализации в коде нет.
|
||||
Краткое описание для людей: [README.md](./README.md).
|
||||
|
||||
Документ рассчитан на то, чтобы по нему можно было написать код без дополнительных
|
||||
уточнений. Все байтовые порядки — big-endian (BE), все хэши — SHA-256 (32 байта),
|
||||
все подписи — Ed25519, все времена — UTC.
|
||||
|
||||
---
|
||||
|
||||
## 1. Термины
|
||||
|
||||
| Термин | Значение |
|
||||
| --- | --- |
|
||||
| **День D** | Календарные сутки UTC `[D 00:00:00; D+1 00:00:00)`. |
|
||||
| **dayIndex** | Порядковый номер суток от эпохи графика: `dayIndex = (началоСуток_D − epochStart) / 86400_000`. У `epochStart` dayIndex = 0. |
|
||||
| **Дежурный** | Сервер, закрывающий день D: `rota.servers[dayIndex mod rota.servers.length]`. |
|
||||
| **Чекпоинт** | Артефакт закрытия дня: контейнер (заголовок + state-список + бандл), одним файлом в Arweave. |
|
||||
| **ИТХ** | Итоговый хэш дня = `stateRoot` — меркл-корень по вершинам всех цепочек. |
|
||||
| **Бандл** | Все блоки, не покрытые предыдущим признанным чекпоинтом. |
|
||||
| **checkpointHash** | SHA-256 канонического preimage заголовка (раздел 5.3). Идентификатор чекпоинта. |
|
||||
| **Признанный чекпоинт** | Последний чекпоинт, на который указывают PDA-слоты больше половины серверов графика. |
|
||||
|
||||
## 2. График дежурств (rota PDA, DAO)
|
||||
|
||||
Отдельный PDA, создаётся и обновляется **только DAO authority**
|
||||
(точная реализация — в Solana-модуле `shine-solana/shine/`, отдельным решением;
|
||||
предлагаемые seeds: `["checkpoint_rota"]`).
|
||||
|
||||
Логическое содержимое:
|
||||
|
||||
```
|
||||
RotaV1
|
||||
- version: u8 = 1
|
||||
- epoch_start_utc_day: u32 // число суток от unix-эпохи (00:00 UTC)
|
||||
- servers_count: u8
|
||||
- servers: [ login_len: u8, login: UTF-8 ] * servers_count // упорядоченный список
|
||||
```
|
||||
|
||||
Правила:
|
||||
|
||||
- Дежурный дня D определяется по состоянию rota PDA на момент `00:00 UTC дня D+1`
|
||||
(момент начала окна закрытия). Изменение списка не пересчитывает прошлые дни.
|
||||
- Вход в график — **по доверию**, не за деньги: DAO ведёт список как временный
|
||||
уровень, пока не сформировано сообщество сияющих; целевое состояние —
|
||||
приём/исключение серверов через голосование сияющих (см. TODO).
|
||||
- При изменении состава серверов будущее распределение сдвигается автоматически —
|
||||
это допустимо, календарь «дата → сервер» не ведётся.
|
||||
|
||||
## 3. Расписание суток (UTC)
|
||||
|
||||
| Окно | Действие |
|
||||
| --- | --- |
|
||||
| `D+1 00:00 – 06:00` | Дежурный строит чекпоинт дня D, заливает в Arweave, обновляет свой PDA-слот (`role=closed`), публикует пост в канале закрытий. |
|
||||
| `D+1 06:00 – 24:00` | Остальные серверы проверяют; при успехе — свой PDA-слот (`role=agreed`) + лайк на пост. Проверять можно и раньше 06:00, как только чекпоинт появился. |
|
||||
|
||||
- Нет валидного чекпоинта дня D к началу следующего окна закрытия → день D **пропущен**:
|
||||
никто не меняет свои слоты; дежурный дня D+1 включает блоки за все непокрытые дни
|
||||
и ссылается `prev`-полями на последний признанный чекпоинт.
|
||||
- Сервер, отстававший несколько дней, догоняет **последовательно**: проверяет цепочку
|
||||
чекпоинтов по одному (каждый — относительно предыдущего) и в конце ставит свой слот
|
||||
на последний проверенный. Опоздавшее согласие допустимо в любой момент.
|
||||
|
||||
## 4. Что попадает в бандл (правило среза)
|
||||
|
||||
- Срез = момент начала сборки чекпоинта дежурным (внутри окна `00:00–06:00`).
|
||||
- В бандл входят все блоки, известные дежурному на момент среза и **не покрытые
|
||||
предыдущим признанным чекпоинтом**: для каждой цепочки `c` — блоки с номера
|
||||
`prevState[c].lastNumber + 1` до локальной вершины; новые цепочки — целиком,
|
||||
с первого блока (номер `0`).
|
||||
- Блоки, созданные уже утром D+1 до среза, допустимо включать — это не ошибка.
|
||||
- Полнота относительно других серверов **не требуется**: блоки, которых у дежурного
|
||||
не было, войдут в следующий чекпоинт. Проверяющие не считают «у меня есть больше»
|
||||
причиной несогласия.
|
||||
- **Выбор ветки (fork-choice дежурного)**: если из вершины `prevState[c]` растут
|
||||
несколько валидных веток (пользователь подписал противоречащие блоки), дежурный
|
||||
берёт самую длинную валидную; при равной длине — ветку с меньшим `hash32` первого
|
||||
расходящегося блока (байтовое сравнение). Ветки, не растущие из `prevState[c]`,
|
||||
кандидатами не являются. Подробнее о форках — раздел 10.
|
||||
|
||||
## 5. Формат чекпоинта (один файл / одна Arweave-транзакция)
|
||||
|
||||
### 5.1. Контейнер
|
||||
|
||||
```
|
||||
magic: 16 байт ASCII "SHiNE-CHECKPOINT" (без завершающего нуля)
|
||||
headerLen: u32 BE — длина headerJson в байтах
|
||||
headerJson: UTF-8 JSON (раздел 5.2)
|
||||
bundle: записи бандла подряд (раздел 5.4), суммарной длиной до конца файла
|
||||
```
|
||||
|
||||
Магик читаемый — открыв файл в hex-редакторе, видно, что это (по образцу
|
||||
`"SQLite format 3\0"`). Версия формата в магик не входит: она в поле `v`
|
||||
заголовка и в первой строке preimage (`SHiNE-CHECKPOINT-V1`).
|
||||
|
||||
Рекомендуемые Arweave-теги: `App-Name: SHiNE`, `Type: checkpoint`,
|
||||
`Day-Index: <dayIndex>` (теги информационные, доверия к ним нет — истина в подписи).
|
||||
|
||||
### 5.2. headerJson
|
||||
|
||||
```json
|
||||
{
|
||||
"v": 1,
|
||||
"dayIndex": 123,
|
||||
"dayDate": "2026-07-12",
|
||||
"closedBy": "serverlogin",
|
||||
"closedAtMs": 1789200000000,
|
||||
"serverPubKeyB58": "…",
|
||||
"prevCheckpointHashHex": "…64 hex… | null",
|
||||
"prevArweaveTxId": "… | null",
|
||||
"stateRootHex": "…64 hex…",
|
||||
"stateCount": 3500,
|
||||
"bundleRootHex": "…64 hex…",
|
||||
"bundleCount": 4200,
|
||||
"state": [
|
||||
{ "name": "alice-001", "lastNumber": 17, "lastHashHex": "…64 hex…" }
|
||||
],
|
||||
"signatureB64": "…"
|
||||
}
|
||||
```
|
||||
|
||||
- `state` отсортирован по `name` **байтово по UTF-8, по возрастанию**, без дублей.
|
||||
- `dayDate` — дата дня D (для читаемости; проверяется соответствие `dayIndex`).
|
||||
- Для генезис-чекпоинта `prevCheckpointHashHex` и `prevArweaveTxId` равны `null`,
|
||||
`prevState` считается пустым, бандл содержит всю историю.
|
||||
- Все hex — в нижнем регистре.
|
||||
|
||||
### 5.3. Канонический preimage, checkpointHash и подпись
|
||||
|
||||
Preimage — UTF-8 строка из 9 строк, разделитель `\n` (без завершающего `\n`;
|
||||
`null`-значения кодируются как `-`):
|
||||
|
||||
```
|
||||
SHiNE-CHECKPOINT-V1
|
||||
<dayIndex>
|
||||
<dayDate>
|
||||
<closedBy>
|
||||
<prevCheckpointHashHex | ->
|
||||
<prevArweaveTxId | ->
|
||||
<stateRootHex>
|
||||
<bundleRootHex>
|
||||
<closedAtMs>
|
||||
```
|
||||
|
||||
- `checkpointHash = SHA-256(preimage)`.
|
||||
- `signatureB64 = Ed25519(preimage)` актуальным клиентским ключом дежурного сервера
|
||||
(ключ из его `user_pda`, действующий на момент закрытия; `serverPubKeyB58` его дублирует).
|
||||
- `checkpointHash` не зависит от форматирования JSON — только от полей preimage.
|
||||
|
||||
### 5.4. Запись бандла
|
||||
|
||||
Записи идут подряд, отсортированы по `(name байтово, blockNumber)` по возрастанию:
|
||||
|
||||
```
|
||||
nameLen: u16 BE
|
||||
name: UTF-8 (blockchainName)
|
||||
blockNumber: u64 BE
|
||||
blockLen: u32 BE
|
||||
blockBytes: полный блок как в хранилище: preimage + sigMarker + signature64
|
||||
```
|
||||
|
||||
`bundleCount` в заголовке = число записей; парсер обязан проверить, что записи
|
||||
заканчиваются ровно на конце файла.
|
||||
|
||||
### 5.5. Меркл-правила (общие для state и бандла)
|
||||
|
||||
- Лист: `SHA-256(0x00 || данные листа)`.
|
||||
- Узел: `SHA-256(0x01 || left32 || right32)`.
|
||||
- При нечётном числе узлов на уровне последний **поднимается без изменения**.
|
||||
- Пустое дерево → 32 нулевых байта.
|
||||
|
||||
Данные листа:
|
||||
|
||||
- **state** (порядок листьев = порядок `state`):
|
||||
`nameBytes || 0x00 || lastNumber u64 BE || lastHash32`.
|
||||
Корень = `stateRoot` = **ИТХ**.
|
||||
- **бандл** (порядок листьев = порядок записей): полные байты записи из 5.4
|
||||
(включая nameLen/name/blockNumber/blockLen). Корень = `bundleRoot`.
|
||||
|
||||
## 6. Слот чекпоинта в PDA сервера
|
||||
|
||||
Новый типизированный блок в `user_pda` сервера (рядом с `ServerProfileBlock (30)`;
|
||||
финальный номер `block_type` и правки `shine-user-pda-format-v.1.0.md` —
|
||||
при реализации Solana-части, отдельным решением). Предложение:
|
||||
|
||||
```
|
||||
CheckpointStateBlock (block_type = 60, block_version = 0)
|
||||
- day_index: u32
|
||||
- checkpoint_hash: [32]
|
||||
- arweave_tx_len: u8, arweave_tx: ASCII
|
||||
- closed_by_len: u8, closed_by: UTF-8
|
||||
- my_role: u8 // 1 = closed (я закрывал), 2 = agreed (я согласен)
|
||||
- updated_at_ms: u64
|
||||
```
|
||||
|
||||
Правила:
|
||||
|
||||
- Слот **один**, перезаписывается на месте; истории в PDA нет
|
||||
(история — цепочка чекпоинтов в Arweave и посты в канале).
|
||||
- Дежурный после заливки ставит свой чекпоинт с `my_role=1`.
|
||||
- Проверивший и согласный сервер ставит тот же `(day_index, checkpoint_hash,
|
||||
arweave_tx, closed_by)` с `my_role=2`.
|
||||
- **Несогласие = не трогать слот.** Отдельной записи «против» нет.
|
||||
|
||||
Правило признания (для наблюдателей и восстановления):
|
||||
|
||||
> Актуальный чекпоинт = последний (максимальный `day_index`), на который указывают
|
||||
> одинаковые `(day_index, checkpoint_hash)` в слотах **больше половины** серверов
|
||||
> из rota PDA. Рекомендация клиентам: до достижения половины считать актуальным
|
||||
> предыдущий признанный.
|
||||
|
||||
Правило признания — правило **чтения**, а не записи:
|
||||
|
||||
- дефолтное множество для подсчёта — список rota PDA;
|
||||
- любой наблюдатель (клиент, сторонний сервер, восстановление) вправе применять то же
|
||||
правило к **собственному списку доверенных серверов**; DAO-список — рекомендация
|
||||
по умолчанию (конфиг сервера: `checkpoint.trust.logins`; пусто = rota PDA);
|
||||
- серверы графика при закрытии и проверке обязаны использовать общий DAO-список,
|
||||
иначе ротация теряет общую точку отсчёта. Коротко: **запись — по общему списку,
|
||||
чтение — по любому**.
|
||||
|
||||
Параллельные группы закрытий допустимы by design: любая другая группа серверов может
|
||||
вести собственную цепочку чекпоинтов над теми же данными (свой генезис, свои PDA-слоты,
|
||||
свои Arweave-транзакции). Группы механически не пересекаются (разные `prev`-цепочки)
|
||||
и не могут подделать содержимое блоков — только заверять историю или не включать её
|
||||
часть. Запрещать это кодом сознательно не планируется; «официальность» DAO-группы
|
||||
держится только на том, что её список — дефолт в клиентах.
|
||||
|
||||
## 7. Канал закрытий в Сиянии
|
||||
|
||||
- У каждого сервера графика — свой публичный канал `type=1`, slug `checkpoints`
|
||||
(создаётся обычным `TECH_CREATE_CHANNEL`).
|
||||
- При закрытии дежурный пишет в свой канал `TEXT_POST`:
|
||||
|
||||
```
|
||||
/.checkpoint <dayIndex> <checkpointHashHex> <arweaveTxId>
|
||||
```
|
||||
|
||||
- Согласие другого сервера — `REACTION_LIKE`, таргетирующий блок этого поста.
|
||||
Отзыв согласия — `REACTION_UNLIKE`. Комментарии не требуются.
|
||||
- Смысл: человекочитаемое зеркало + криптографическая проверяемость через Arweave
|
||||
(пост и лайки подписаны ключами серверов, зарегистрированными в Solana), с лагом в
|
||||
один день (попадут в следующий бандл).
|
||||
- Машинный якорь «здесь и сейчас» — PDA-слоты; канал вторичен.
|
||||
- При реализации обязательно дописать команду `/.checkpoint` в
|
||||
`docs/Blockchain/02_Channel_Commands.md`.
|
||||
|
||||
## 8. Алгоритм дежурного (закрытие дня D)
|
||||
|
||||
1. В окне `D+1 00:00–06:00` убедиться по rota PDA, что дежурный — я.
|
||||
2. Определить предыдущий признанный чекпоинт (свой слот + слоты остальных, раздел 6);
|
||||
загрузить его `state` (локальный кэш или Arweave). Для генезиса — пустой.
|
||||
3. Зафиксировать срез: для каждой цепочки — вершина выбранной ветки
|
||||
`(lastNumber, lastHash)` по правилу fork-choice из раздела 4.
|
||||
4. Собрать бандл по разделу 4, отсортировать по 5.4, посчитать `bundleRoot`.
|
||||
5. Сформировать `state` по срезу, отсортировать, посчитать `stateRoot` (ИТХ).
|
||||
6. Собрать preimage, `checkpointHash`, подписать серверным ключом.
|
||||
7. Залить контейнер одной Arweave-транзакцией (JWK сервера с хоста, вне git),
|
||||
дождаться подтверждения, получить `arweaveTxId`.
|
||||
8. Обновить свой PDA-слот: `(dayIndex, checkpointHash, arweaveTxId, closedBy=я, role=1)`.
|
||||
9. Опубликовать пост `/.checkpoint …` в своём канале закрытий.
|
||||
10. Любая ошибка на шагах 7–9 → повторять до конца окна; не успел — день пропущен,
|
||||
ничего не публиковать частично (PDA-слот и пост ставить только после успешного Arweave).
|
||||
|
||||
## 9. Алгоритм проверяющего
|
||||
|
||||
Вход: обнаружен новый чекпоинт дня D от сервера S (по его PDA-слоту и/или посту).
|
||||
|
||||
1. **График**: `S == rota[dayIndex mod N]` по состоянию rota PDA на `00:00 D+1`;
|
||||
`dayIndex` больше `day_index` последнего признанного локально чекпоинта.
|
||||
2. Скачать Arweave-транзакцию из слота S; проверить `magic`, длины, парсинг.
|
||||
3. Пересчитать preimage → `checkpointHash`; сверить со слотом S (и постом, если есть).
|
||||
4. Проверить `signatureB64` ключом S (актуальный клиентский ключ из `user_pda` S;
|
||||
допускается ключ, действовавший в день закрытия — см. TODO про ротацию).
|
||||
5. `prevCheckpointHashHex`/`prevArweaveTxId` == локально признанный крайний чекпоинт.
|
||||
Иначе — отказ.
|
||||
6. Пересчитать `bundleRoot` по записям; сверить. Проверить сортировку и `bundleCount`.
|
||||
7. **Мостик prev→state** (полнота бандла): для каждой цепочки из `state`
|
||||
блоки бандла образуют непрерывную последовательность номеров и `prevHash`-ссылок
|
||||
от `prevState[c]` (или от блока 0 для новой цепочки) ровно до `state[c]`.
|
||||
Для каждого блока: `hash32 == SHA-256(preimage)`, структура валидна по правилам
|
||||
`docs/Blockchain/01_Common_Block_Format.md`, подпись Ed25519 валидна ключом
|
||||
владельца цепочки, действующим на день закрытия.
|
||||
8. Пересчитать `stateRoot` по `state`; сверить (это и есть проверка ИТХ).
|
||||
9. **Конфликты с локальными данными**: если локально для `(цепочка, номер)` есть блок
|
||||
с другим `hash32` — побеждает версия чекпоинта: локальная ветка отбрасывается
|
||||
(реорганизация), согласие НЕ отклоняется. Отказ по конфликту возможен только когда
|
||||
чекпоинт противоречит уже признанному прошлому — это ловится шагом 7 (мостик не
|
||||
соединится с `prevState`). «У меня есть блоки, которых нет в бандле» — тоже НЕ отказ.
|
||||
10. Импортировать недостающие блоки бандла в локальное хранилище тем же валидационным
|
||||
путём, что и межсерверная синхронизация (`sync-between-servers.md`).
|
||||
11. Успех → обновить свой PDA-слот (`role=2`) и поставить лайк на пост S.
|
||||
Отказ → не делать ничего on-chain; подробно залогировать причину
|
||||
(рекомендуемый префикс лога: `CHECKPOINT_REJECT`).
|
||||
|
||||
Проверка детерминирована: два честных сервера с любым набором локальных блоков приходят
|
||||
к одному вердикту. Проверяется валидность выбора дежурного и стыковка с прошлым
|
||||
чекпоинтом, но не «длиннейшесть» выбранной ветки — её никто, кроме дежурного, проверить
|
||||
не может, и условием согласия она не является.
|
||||
|
||||
## 10. Форки пользовательских цепочек
|
||||
|
||||
Форк = два блока одной цепочки с одинаковым `blockNumber`, разным `hash32` и валидными
|
||||
подписями владельца. Случайно-честно такое не возникает: каждый блок подписывается
|
||||
владельцем и ссылается на `prevHash`. Пара таких блоков — самодостаточная
|
||||
криптографическая улика, проверяемая любым сервером локально, без арбитра и голосования.
|
||||
|
||||
Лесенка разруливания:
|
||||
|
||||
| Ситуация | Арбитр | Исход |
|
||||
| --- | --- | --- |
|
||||
| Ветки разошлись до чекпоинта | Дежурный (длиннейшая + tie-break, раздел 4) | Проигравшая ветка отбрасывается, цепочка живёт |
|
||||
| Локальная ветка против зачекпоинченной в своей группе | Цепочка чекпоинтов | Локальная ветка отбрасывается (шаг 9.9) |
|
||||
| Чекпоинт противоречит признанному прошлому | Проверяющие | Согласий нет, день пропущен |
|
||||
| Противоречие между архивами двух групп закрытий | Никто — улика | Заморозка цепочки |
|
||||
|
||||
Заморозка применяется **только** в последнем случае — когда обе ветки уже зачекпоинчены
|
||||
разными группами закрытий и неизменны в Arweave: слить два неизменных архива невозможно
|
||||
в принципе.
|
||||
|
||||
- Улика: два конфликтующих блока, каждый из которых входит в валидный чекпоинт
|
||||
своей группы.
|
||||
- Сервер, проверивший улику, перестаёт принимать `AddBlock` в эту цепочку
|
||||
(код отказа: `chain_frozen`); улика синхронизируется партнёрам как обычные данные,
|
||||
каждый проверяет и замораживает независимо.
|
||||
- Уже записанная история цепочки остаётся валидной и читаемой навсегда.
|
||||
- Пользователь продолжает с новой цепочкой `<login>-<NNN+1>` (например, `login-002`);
|
||||
`BlockchainRegistryBlock` в `user_pda` уже поддерживает несколько цепочек.
|
||||
Старые связи/подписки указывают на корни старой цепочки — репутационная история
|
||||
не переносится, это осознанная часть последствий.
|
||||
|
||||
Обязательное правило для клиентов (защищает честных пользователей от гонок):
|
||||
повтор отправки блока после таймаута/ошибки — только **байт-в-байт** тем же блоком.
|
||||
Байт-идентичный дубль — дедупликация, а не форк. Пересборка блока с новым `timestamp`
|
||||
на тот же номер создаёт форк на ровном месте.
|
||||
|
||||
## 11. Восстановление сервера с нуля
|
||||
|
||||
1. Прочитать rota PDA → список серверов.
|
||||
2. Прочитать их PDA-слоты → признанный чекпоинт (правило раздела 6).
|
||||
3. От него по `prevArweaveTxId` спуститься до генезиса, затем раскатать бандлы
|
||||
от генезиса вверх, валидируя каждый чекпоинт по разделу 9 (шаги 2–8).
|
||||
4. Хвост после последнего чекпоинта добрать живой синхронизацией с серверами.
|
||||
|
||||
## 12. Стоимость и ключи
|
||||
|
||||
- Каждый сервер платит сам: Arweave — за свои дежурные дни, Solana — за свои слоты.
|
||||
- Arweave JWK сервера хранится только на хосте (например,
|
||||
`/home/player/SHiNE/secrets/…`), путь задаётся конфигом; в git не попадает —
|
||||
по той же схеме, что `test.freeAvatar.walletJwkPath`.
|
||||
|
||||
## 13. Ориентир для реализации на сервере (не нормативно)
|
||||
|
||||
- `CheckpointScheduler` — таймер окон 00:00/06:00 UTC, определение роли по rota PDA.
|
||||
- `CheckpointBuilder` — срез, бандл, меркл, preimage, подпись (разделы 4–5, 8).
|
||||
- `ArweaveCheckpointUploader` — заливка контейнера, ожидание подтверждения.
|
||||
- `CheckpointVerifier` — раздел 9; переиспользовать валидацию блоков из `AddBlock`/sync.
|
||||
- `CheckpointSolanaSlot` — чтение/запись `CheckpointStateBlock`, чтение rota PDA.
|
||||
- `CheckpointChannelPublisher` — пост `/.checkpoint` и лайки через обычный `AddBlock`.
|
||||
- Конфиги (предложение): `checkpoint.enabled`, `checkpoint.arweave.jwkPath`,
|
||||
`checkpoint.rota.pda` (или деривация по seeds), `checkpoint.closeWindowHours=6`.
|
||||
- Локально хранить: `state` последнего признанного чекпоинта + его
|
||||
`(dayIndex, hash, arweaveTxId)` — чтобы не ходить в Arweave на каждое закрытие.
|
||||
|
||||
## 14. TODO / сознательно отложено
|
||||
|
||||
- **История ключей при ротации**: если сервер/пользователь меняет ключ несколько раз
|
||||
за день, для проверки старых подписей нужна история версий ключей. v1 живёт с
|
||||
«ключ, актуальный на день закрытия»; при внедрении ротации — хранить версии.
|
||||
- **Механизм новой цепочки `-002`**: пользовательский flow «продолжить с новой
|
||||
цепочкой» после заморозки (регистрация в `BlockchainRegistryBlock`, UI) пока
|
||||
не реализован — см. раздел 10.
|
||||
- **Детерминированный выбор дежурного**:
|
||||
`дежурный = SHA-256(prevCheckpointHash || dayIndex) mod N` по байтово отсортированным
|
||||
логинам — убирает порядок списка как рычаг управления, DAO остаётся только
|
||||
вход/выход участников.
|
||||
- **Передача ведения графика сообществу**: вход в группу закрытий — по доверию,
|
||||
не за деньги. Сейчас список ведёт DAO — это временный уровень, пока сообщество
|
||||
сияющих не сформировано; в перспективе приём/исключение серверов — через
|
||||
голосование сияющих. Депозитный/платный вход сознательно отвергнут.
|
||||
- **Автопропуск штрафника** в ротации (2 подряд закрытия без единого согласия).
|
||||
- **On-chain кворум** вместо правила чтения «>половины» — если понадобится.
|
||||
- **Сжатие бандла** (gzip) и **чанкование** очень больших бандлов на несколько
|
||||
Arweave-транзакций с манифестом. v1 — один несжатый файл.
|
||||
- Экономия Solana-транзакций: считать согласия только по лайкам канала — возможно
|
||||
после обкатки v1.
|
||||
|
||||
## 15. Связанные документы (обновлять при реализации)
|
||||
|
||||
- `docs/Blockchain/01_Common_Block_Format.md` — формат блока (используется в 5.4, 9.7).
|
||||
- `docs/Blockchain/02_Channel_Commands.md` — дописать `/.checkpoint`.
|
||||
- `docs/Blockchain/sync-between-servers.md` — путь импорта блоков (9.10).
|
||||
- `shine-solana/shine/doc/formats/shine-user-pda-format-v.1.0.md` — дописать
|
||||
`CheckpointStateBlock` и rota PDA при реализации Solana-части.
|
||||
- `docs/API/04_Add_Block_to_Blockchain_API.md` — при реализации дописать код отказа
|
||||
`chain_frozen` (раздел 10).
|
||||
- `docs/Blockchain/CHANGELOG.md` — фиксировать изменения.
|
||||
@@ -0,0 +1,29 @@
|
||||
# Запись блокчейнов в Arweave
|
||||
|
||||
## Зачем
|
||||
|
||||
Для будущей децентрализации нужно долговременное внешнее хранение блокчейнов, чтобы данные не зависели только от одного серверного диска.
|
||||
|
||||
## Что сделать
|
||||
|
||||
1. Определить, какие блокчейны и какие диапазоны блоков записываются в Arweave.
|
||||
2. Зафиксировать формат пачки блоков, метаданных, ссылок и контрольных хэшей.
|
||||
3. Добавить безопасный механизм публикации без хранения приватного JWK в git.
|
||||
4. Добавить проверку уже загруженных диапазонов, чтобы не плодить дубли.
|
||||
5. Описать восстановление блокчейна из Arweave при потере локальных данных.
|
||||
|
||||
## Важные ограничения
|
||||
|
||||
- Любое изменение формата блокчейна требует отдельного предупреждения и явного подтверждения пользователя.
|
||||
- Добавление данных в блокчейн должно выполняться только через `AddBlock`.
|
||||
- Секреты Arweave нельзя хранить в репозитории.
|
||||
|
||||
## Документы, которые потом нужно обновить
|
||||
|
||||
- `docs/Blockchain/README.md`;
|
||||
- `docs/Blockchain/CHANGELOG.md`;
|
||||
- документы deploy/секретов в `Deploy/`, если появятся новые параметры.
|
||||
|
||||
## Статус
|
||||
|
||||
Отложено до этапа децентрализации.
|
||||
@@ -0,0 +1,30 @@
|
||||
# Межсерверная передача сообщений
|
||||
|
||||
## Зачем
|
||||
|
||||
Когда у SHiNE появится несколько серверов, пользователи на разных серверах должны получать личные сообщения без ручной синхронизации и без привязки к одному центральному узлу.
|
||||
|
||||
## Что сделать
|
||||
|
||||
1. Определить протокол server-to-server доставки DM.
|
||||
2. Добавить маршрутизацию получателя по серверу, user id, публичному ключу или PDA.
|
||||
3. Добавить ACK, повторы, дедупликацию и backfill пропущенных сообщений.
|
||||
4. Разделить realtime-доставку и восстановление истории.
|
||||
5. Описать поведение при недоступности удалённого сервера.
|
||||
|
||||
## Что учесть
|
||||
|
||||
- Логика DM должна соответствовать документам в `docs/Personal_Messages/`.
|
||||
- При изменении формата signed DM-блока или правил доставки нужно обновлять протокол и байтовый формат DM.
|
||||
- Если появятся новые server API/WebSocket операции, нужно обновить `docs/API/`.
|
||||
|
||||
## Документы, которые потом нужно обновить
|
||||
|
||||
- `docs/Personal_Messages/Протокол_DM_v1.md`;
|
||||
- `docs/Personal_Messages/Формат_DM_v1.md`;
|
||||
- `docs/API/`;
|
||||
- `docs/API/09_Operations_Index.md`, если добавляются новые `op`.
|
||||
|
||||
## Статус
|
||||
|
||||
Отложено до этапа децентрализации.
|
||||
@@ -0,0 +1,29 @@
|
||||
# Межсерверные звонки
|
||||
|
||||
## Зачем
|
||||
|
||||
В будущем пользователи на разных серверах должны иметь возможность устанавливать звонки так же, как пользователи одного сервера.
|
||||
|
||||
## Что сделать
|
||||
|
||||
1. Определить протокол межсерверной сигнализации звонков.
|
||||
2. Добавить маршрутизацию offer/answer/ICE-кандидатов между серверами.
|
||||
3. Добавить обработку статусов занятости, отказа, таймаута и ошибок маршрута.
|
||||
4. Добавить диагностику доставки сигналов между серверами.
|
||||
5. Проверить совместимость с текущими логами `CallDeliveryReport`.
|
||||
|
||||
## Что учесть
|
||||
|
||||
- Специальная диагностика установки звонков идёт через `CallDeliveryReport`.
|
||||
- На production важно сохранять поля `reason`, `failureStage`, `pcConnectionState`, `pcIceConnectionState`, `routeLabel`, `configuredTurnHosts*`, `reachableTurnHosts*`.
|
||||
- Межсерверные звонки не должны ломать текущий односерверный сценарий.
|
||||
|
||||
## Документы, которые потом нужно обновить
|
||||
|
||||
- `docs/API/`, если добавляются или меняются операции сигнализации;
|
||||
- документы по звонкам/диагностике, если они будут выделены отдельно;
|
||||
- deploy-документы, если появятся новые параметры TURN/server-to-server маршрутизации.
|
||||
|
||||
## Статус
|
||||
|
||||
Отложено до этапа децентрализации.
|
||||
@@ -0,0 +1,23 @@
|
||||
# Односерверный production-режим
|
||||
|
||||
## Зачем
|
||||
|
||||
Перед выкладкой репозитория на GitHub и запуском production нужно явно зафиксировать, что текущая стабильная версия работает как один основной сервер.
|
||||
|
||||
## Что считаем текущей нормой
|
||||
|
||||
- Один production-сервер обслуживает пользователей, сообщения, звонки и серверные данные.
|
||||
- Децентрализованные сценарии не считаются обязательными для первого production-релиза.
|
||||
- Межсерверная доставка сообщений, межсерверные звонки, realtime PDA/Solana sync и запись блокчейнов в Arweave вынесены в отдельные будущие задачи.
|
||||
- Код и документация текущего production не должны создавать ожидание, что несколько серверов уже работают как единая realtime-сеть.
|
||||
|
||||
## Что сделать перед возвратом к децентрализации
|
||||
|
||||
1. Проверить актуальные документы по API, blockchain, DM и deploy.
|
||||
2. Выделить минимальный протокол server-to-server взаимодействия.
|
||||
3. Решить, какие данные остаются локальными, какие реплицируются между серверами, а какие записываются во внешнее долговременное хранилище.
|
||||
4. После изменения API, blockchain-форматов или DM-протокола обновить соответствующие документы по правилам проекта.
|
||||
|
||||
## Статус
|
||||
|
||||
Отложено. Текущий production работает как один сервер.
|
||||
+275
@@ -0,0 +1,275 @@
|
||||
# Новая логика контента в блокчейне SHiNE
|
||||
|
||||
## Зачем это нужно
|
||||
|
||||
Сейчас блокчейн SHiNE хорошо умеет хранить обычные сообщения, ответы, лайки и связи между людьми.
|
||||
|
||||
Новая модель добавляет поверх этого более понятный смысл контента:
|
||||
|
||||
- обычный текст;
|
||||
- упражнение;
|
||||
- услуга / процедура;
|
||||
- курс;
|
||||
- стартовая страница канала (`entrypoint`).
|
||||
|
||||
Это нужно для того, чтобы канал стал не просто лентой постов, а полноценным пространством знаний, практик, услуг и сообществ.
|
||||
|
||||
## Что меняется для людей
|
||||
|
||||
### 1. В канале появятся понятные виды материалов
|
||||
|
||||
Сообщение можно будет создать не только как обычный текст, но и как:
|
||||
|
||||
- упражнение;
|
||||
- услугу / процедуру;
|
||||
- курс;
|
||||
- стартовую страницу канала.
|
||||
|
||||
Смысл в том, что приложение и сервер будут понимать, что это за материал, а не просто показывать любой текст одинаково.
|
||||
|
||||
### 2. У канала будет стартовая страница
|
||||
|
||||
У канала появится отдельное стартовое сообщение `entrypoint`.
|
||||
|
||||
Это не курс и не оглавление, а именно главная точка входа в канал:
|
||||
|
||||
- короткое объяснение, о чём канал;
|
||||
- описание структуры;
|
||||
- ссылки на нужные материалы;
|
||||
- удобное начало для новых людей.
|
||||
|
||||
У канала в каждый момент времени будет только одна актуальная стартовая страница.
|
||||
Если её исправляют, то сохраняется история версий.
|
||||
Если её удаляют, для интерфейса считается, что стартовой страницы у канала сейчас нет.
|
||||
|
||||
### 3. Курс, упражнение и услуга / процедура будут отличаться по смыслу
|
||||
|
||||
Это важно для логики и статистики.
|
||||
|
||||
- `Упражнение` — то, что человек может делать много раз.
|
||||
- `Услуга / процедура` — то, что тоже можно проходить много раз, но обычно с участием другого человека.
|
||||
- `Курс` — то, что можно начать, закончить или бросить.
|
||||
|
||||
За счёт этого сервер сможет честно считать активность, а интерфейс сможет показывать человеку именно те действия, которые подходят к данному типу материала.
|
||||
|
||||
### 4. Появятся статусные действия
|
||||
|
||||
На контент можно будет не только ответить или поставить лайк, но и отметить свой путь:
|
||||
|
||||
- сделал один раз;
|
||||
- заинтересовался и рассматривает;
|
||||
- начал;
|
||||
- закончил / освоил / знаю;
|
||||
- бросил.
|
||||
|
||||
При этом:
|
||||
|
||||
- для упражнений и услуг / процедур будет отдельно считаться, сколько раз человек сделал / прошёл;
|
||||
- для упражнений и курсов будет храниться текущий статус.
|
||||
|
||||
Текущий статус определяется просто:
|
||||
|
||||
- последнее статусное действие и считается актуальным.
|
||||
|
||||
Например:
|
||||
|
||||
- если последнее действие “заинтересовался и рассматривает”, значит человек присматривается, но ещё не начал;
|
||||
- если последнее действие `started`, значит материал сейчас в процессе;
|
||||
- если последнее действие `abandoned`, значит человек бросил;
|
||||
- если последнее действие `completed`, значит для системы он завершил / освоил материал.
|
||||
|
||||
### 5. К действиям можно добавлять живой текст
|
||||
|
||||
Практически любое статусное действие можно будет сопровождать коротким комментарием.
|
||||
|
||||
Например:
|
||||
|
||||
- “Начал изучать, потому что давно хотел разобраться”;
|
||||
- “Бросил, пока нет времени”;
|
||||
- “Прошёл процедуру, стало заметно легче”.
|
||||
|
||||
Это важно, потому что сам блокчейн будет хранить не только формальный статус, но и живую человеческую причину или заметку.
|
||||
|
||||
### 6. Появится подтверждение статуса другими людьми
|
||||
|
||||
Отдельный человек сможет подтвердить чей-то статус.
|
||||
|
||||
Примеры:
|
||||
|
||||
- подтвердить, что человек действительно занимался;
|
||||
- подтвердить, что он реально прошёл услугу;
|
||||
- подтвердить, что он освоил материал.
|
||||
|
||||
Подтверждение — это не замена статуса, а отдельное мнение / свидетельство со стороны.
|
||||
|
||||
### 7. Появится отдельный тип «мнение»
|
||||
|
||||
На любое сообщение можно будет ответить не только обычным ответом, но и специальным типом ответа: `мнение`.
|
||||
|
||||
Это по сути тоже текстовый ответ, но с отдельным смыслом:
|
||||
|
||||
- это отзыв;
|
||||
- это оценка;
|
||||
- это мнение о материале;
|
||||
- это явная метка для будущего анализа нейронками.
|
||||
|
||||
То есть:
|
||||
|
||||
- обычный ответ нужен для разговора;
|
||||
- `мнение` нужно для отзыва, оценки и анализа реакции людей.
|
||||
|
||||
## Что остаётся как раньше
|
||||
|
||||
### Комментарии
|
||||
|
||||
Обычные ответы на сообщения остаются.
|
||||
То есть обсуждение материалов не ломается и не меняется концептуально.
|
||||
|
||||
### Лайки контента
|
||||
|
||||
Лайк на сообщение, курс, упражнение или услугу остаётся обычной реакцией на конкретный блок.
|
||||
|
||||
### Лайк пользователю
|
||||
|
||||
Лайк пользователю не будет считаться реакцией на сообщение.
|
||||
Он относится к графу связей между людьми.
|
||||
|
||||
Это удобно, потому что:
|
||||
|
||||
- лайк человека — это отношение к человеку;
|
||||
- лайк материала — это отношение к контенту.
|
||||
|
||||
## Сообщество вокруг канала
|
||||
|
||||
Канал сможет работать не только как лента, но и как сообщество.
|
||||
|
||||
Для этого появятся простые действия:
|
||||
|
||||
- заявка на вступление;
|
||||
- самостоятельный выход;
|
||||
- принятие;
|
||||
- исключение.
|
||||
|
||||
Сервер сможет понимать:
|
||||
|
||||
- кто только подал заявку;
|
||||
- кто уже принят;
|
||||
- кто вышел;
|
||||
- кто был исключён.
|
||||
|
||||
## Личный канал и лента достижений
|
||||
|
||||
У каждого человека по смыслу появляется два важных пространства:
|
||||
|
||||
- канал его обычных постов;
|
||||
- отдельная лента его тренировок и достижений.
|
||||
|
||||
В обычном канале человек сможет:
|
||||
|
||||
- писать посты;
|
||||
- делиться мыслями;
|
||||
- публиковать материалы;
|
||||
- обсуждать темы как раньше.
|
||||
|
||||
А в ленте достижений будут видны его реальные действия:
|
||||
|
||||
- какие упражнения он делал;
|
||||
- какие услуги / процедуры проходил;
|
||||
- какие курсы его заинтересовали;
|
||||
- какие курсы он начал;
|
||||
- какие курсы он закончил;
|
||||
- что он бросил.
|
||||
|
||||
То есть блокчейн SHiNE сможет хранить не только слова человека, но и его путь, активность и историю практики.
|
||||
|
||||
## Что смогут делать авторы контента
|
||||
|
||||
Создатели контента в своих каналах смогут публиковать не только обычные посты, но и:
|
||||
|
||||
- упражнения;
|
||||
- курсы;
|
||||
- стартовую страницу канала;
|
||||
- услуги / процедуры, которые они оказывают.
|
||||
|
||||
Это превращает канал в сочетание:
|
||||
|
||||
- блога;
|
||||
- базы знаний;
|
||||
- пространства обучения;
|
||||
- каталога услуг и практик.
|
||||
|
||||
## Что увидит человек в интерфейсе
|
||||
|
||||
На специальных сообщениях в UI можно будет показывать отдельные кнопки действий.
|
||||
|
||||
Например:
|
||||
|
||||
- `Выполнил упражнение`
|
||||
- `Прошёл процедуру`
|
||||
- `Заинтересовало`
|
||||
- `Начал курс`
|
||||
- `Закончил курс`
|
||||
|
||||
То есть материал можно будет не просто прочитать, а сразу отметить реальное действие.
|
||||
|
||||
Также при ответе на любое сообщение можно будет выбрать:
|
||||
|
||||
- обычный ответ;
|
||||
- `мнение / отзыв`.
|
||||
|
||||
## Как будет работать лента достижений
|
||||
|
||||
Если кто-то зайдёт в твою ленту достижений, он сможет:
|
||||
|
||||
- прочитать, что ты делал;
|
||||
- оставить мнение / отзыв;
|
||||
- подтвердить, что это действительно было.
|
||||
|
||||
Это даёт основу для мягкой “сертификации” внутри SHiNE.
|
||||
|
||||
Например:
|
||||
|
||||
- человек прошёл курс и получил подтверждения;
|
||||
- человек прошёл процедуру и получил отзыв;
|
||||
- человек регулярно делает упражнения, и это видно в его истории.
|
||||
|
||||
Так постепенно у пользователя появляется не только лента постов, но и лента достижений, подтверждений и репутации.
|
||||
|
||||
## Ссылки внутри SHiNE
|
||||
|
||||
Для переходов между материалами вводятся простые внутренние адреса:
|
||||
|
||||
- обычная ссылка: `SHiNE/alice-001/157`
|
||||
- особополная ссылка: `SHiNE/alice-001/157/ХЭШ`
|
||||
|
||||
Первая форма — основная и каноническая.
|
||||
Вторая нужна там, где хочется добавить ещё и точную проверку по хэшу.
|
||||
|
||||
## Что это даёт в итоге
|
||||
|
||||
После внедрения новая блокчейн-логика позволит:
|
||||
|
||||
- строить каналы как структурированные пространства, а не просто как поток постов;
|
||||
- выделять упражнения, услуги и курсы как отдельные сущности;
|
||||
- показывать стартовую страницу канала;
|
||||
- хранить путь человека по материалу;
|
||||
- хранить отдельную ленту его действий и достижений;
|
||||
- считать активность и статусы;
|
||||
- подтверждать результаты другими людьми;
|
||||
- развивать сообщество вокруг канала.
|
||||
|
||||
И самое важное: всё это можно добавить как расширение уже существующего блокчейна SHiNE, не разрушая старую модель сообщений.
|
||||
|
||||
## Отдельный вопрос для будущего
|
||||
|
||||
Отзывы о людях как о людях — полезная идея, но её стоит дополнительно обдумать.
|
||||
|
||||
Например, на вкладке связей в будущем можно:
|
||||
|
||||
- писать человеку отзыв;
|
||||
- смотреть все отзывы о человеке;
|
||||
- выводить сначала отзывы близких друзей, родственников, друзей и контактов, а уже потом остальные.
|
||||
|
||||
Но этот слой нужно делать осторожно, чтобы он не стал слишком жёстким или неприятным для людей.
|
||||
|
||||
Поэтому отзывы о людях как отдельная социальная механика требуют дополнительного обсуждения и проектирования.
|
||||
+670
@@ -0,0 +1,670 @@
|
||||
# ТЗ: новая контентная модель блокчейна SHiNE
|
||||
|
||||
## Статус документа
|
||||
|
||||
Этот документ описывает предлагаемые новые типы блоков и правила их обработки.
|
||||
|
||||
Цель:
|
||||
|
||||
- добавить новую семантику контента;
|
||||
- не ломать существующие блоки `type=0..4`;
|
||||
- внедрить всё как расширение блокчейна за счёт новых форматов.
|
||||
|
||||
Документ является проектным ТЗ на реализацию в сервере, БД, API чтения и UI.
|
||||
|
||||
## 1. Базовые принципы
|
||||
|
||||
### 1.1. Совместимость
|
||||
|
||||
Старые типы не меняются:
|
||||
|
||||
- `type=0` — TECH
|
||||
- `type=1` — TEXT
|
||||
- `type=2` — REACTION
|
||||
- `type=3` — CONNECTION
|
||||
- `type=4` — USER_PARAM
|
||||
|
||||
Новые сущности и действия добавляются только как новые `type` и новые `body`.
|
||||
|
||||
Это означает:
|
||||
|
||||
- старые блоки продолжают читаться как раньше;
|
||||
- старые `TEXT_POST`, `TEXT_REPLY`, `REACTION_LIKE` и остальные форматы не ломаются;
|
||||
- существующий блокчейн остаётся валидным;
|
||||
- новый функционал появляется только там, где клиент и сервер умеют его понимать.
|
||||
|
||||
### 1.2. Общая стратегия
|
||||
|
||||
Новая модель делится на четыре слоя:
|
||||
|
||||
1. контентные сущности;
|
||||
2. текстовые отзывы и мнения;
|
||||
3. статусные действия пользователей;
|
||||
4. community-события вокруг канала.
|
||||
|
||||
### 1.3. Редактирование и удаление
|
||||
|
||||
Для новых контентных сущностей сохраняется действующий принцип SHiNE:
|
||||
|
||||
- редактирование всегда ссылается на оригинальный блок;
|
||||
- тип сущности edit не меняет;
|
||||
- удаление выполняется через `edit` с пустым текстом;
|
||||
- отдельный `DELETE`-подтип не вводится.
|
||||
|
||||
Это правило особенно важно для:
|
||||
|
||||
- `plain_text`
|
||||
- `exercise`
|
||||
- `service`
|
||||
- `course`
|
||||
- `entrypoint`
|
||||
|
||||
В пользовательских текстах и UI желательно использовать русские названия:
|
||||
|
||||
- обычный текст;
|
||||
- упражнение;
|
||||
- услуга / процедура;
|
||||
- курс;
|
||||
- стартовое сообщение канала.
|
||||
|
||||
## 2. Канонические внутренние ссылки
|
||||
|
||||
В новой модели поддерживаются только две формы внутренней ссылки:
|
||||
|
||||
- каноническая: `SHiNE/<blockchainName>/<blockNumber>`
|
||||
- особополная: `SHiNE/<blockchainName>/<blockNumber>/<blockHash>`
|
||||
|
||||
Примеры:
|
||||
|
||||
- `SHiNE/alice-001/157`
|
||||
- `SHiNE/alice-001/157/abcd1234...`
|
||||
|
||||
Правила:
|
||||
|
||||
- канонической считается именно короткая форма без хэша;
|
||||
- форма с хэшем используется как усиленный вариант для точной проверки;
|
||||
- внутри UI и серверной логики ссылка должна приводиться как минимум к паре:
|
||||
- `blockchainName`
|
||||
- `blockNumber`
|
||||
- если хэш присутствует, он участвует в дополнительной валидации ссылки.
|
||||
|
||||
## 3. Новые контентные сущности
|
||||
|
||||
## 3.1. Новый `type=5` — `CONTENT`
|
||||
|
||||
Назначение:
|
||||
|
||||
- хранение новых смысловых материалов канала;
|
||||
- сохранение линии канала;
|
||||
- поддержка edit-версий и логического удаления.
|
||||
|
||||
### 3.1.1. Подтипы `CONTENT`
|
||||
|
||||
- `subType=10` — `CONTENT_PLAIN`
|
||||
- `subType=11` — `CONTENT_EDIT_PLAIN`
|
||||
- `subType=20` — `CONTENT_EXERCISE`
|
||||
- `subType=21` — `CONTENT_EDIT_EXERCISE`
|
||||
- `subType=30` — `CONTENT_SERVICE`
|
||||
- `subType=31` — `CONTENT_EDIT_SERVICE`
|
||||
- `subType=40` — `CONTENT_COURSE`
|
||||
- `subType=41` — `CONTENT_EDIT_COURSE`
|
||||
- `subType=50` — `CONTENT_ENTRYPOINT`
|
||||
- `subType=51` — `CONTENT_EDIT_ENTRYPOINT`
|
||||
|
||||
### 3.1.2. Семантика подтипов
|
||||
|
||||
- `CONTENT_PLAIN` — обычный текст нового поколения.
|
||||
- `CONTENT_EXERCISE` — упражнение, которое можно выполнять многократно.
|
||||
- `CONTENT_SERVICE` — услуга / процедура, которую можно проходить многократно.
|
||||
- `CONTENT_COURSE` — курс / оглавление.
|
||||
- `CONTENT_ENTRYPOINT` — стартовое сообщение канала.
|
||||
|
||||
### 3.1.3. Почему `entrypoint` отдельный тип
|
||||
|
||||
`entrypoint` не считается курсом.
|
||||
|
||||
Это отдельная сущность, потому что:
|
||||
|
||||
- она описывает вход в канал;
|
||||
- по ней нельзя делать `started / completed / abandoned`;
|
||||
- у канала в каждый момент времени должна быть только одна актуальная стартовая страница.
|
||||
|
||||
### 3.1.4. Ограничение на `entrypoint`
|
||||
|
||||
Для одного канала допускается только один исходный блок `CONTENT_ENTRYPOINT`.
|
||||
|
||||
Правила:
|
||||
|
||||
- если entrypoint уже существует, создать второй нельзя;
|
||||
- изменять можно только через `CONTENT_EDIT_ENTRYPOINT`;
|
||||
- если entrypoint логически удалён, UI должен считать, что стартовой страницы больше нет;
|
||||
- исторический блок при этом остаётся в цепочке.
|
||||
|
||||
### 3.1.5. Формат body для `CONTENT_*`
|
||||
|
||||
Для `version=1` рекомендуется использовать формат, максимально совместимый по логике с текущими `TEXT_POST` / `TEXT_EDIT_POST`.
|
||||
|
||||
#### Создающие блоки
|
||||
|
||||
Для:
|
||||
|
||||
- `CONTENT_PLAIN`
|
||||
- `CONTENT_EXERCISE`
|
||||
- `CONTENT_SERVICE`
|
||||
- `CONTENT_COURSE`
|
||||
- `CONTENT_ENTRYPOINT`
|
||||
|
||||
body:
|
||||
|
||||
```text
|
||||
ContentLineBody_v1
|
||||
- lineCode: int32
|
||||
- prevLineNumber: int32
|
||||
- prevLineHash32: [32]
|
||||
- thisLineNumber: int32
|
||||
- textLenBytes: uint16
|
||||
- text UTF-8
|
||||
```
|
||||
|
||||
#### Edit-блоки
|
||||
|
||||
Для:
|
||||
|
||||
- `CONTENT_EDIT_PLAIN`
|
||||
- `CONTENT_EDIT_EXERCISE`
|
||||
- `CONTENT_EDIT_SERVICE`
|
||||
- `CONTENT_EDIT_COURSE`
|
||||
- `CONTENT_EDIT_ENTRYPOINT`
|
||||
|
||||
body:
|
||||
|
||||
```text
|
||||
ContentEditBody_v1
|
||||
- lineCode: int32
|
||||
- prevLineNumber: int32
|
||||
- prevLineHash32: [32]
|
||||
- thisLineNumber: int32
|
||||
- toBlockGlobalNumber: int32
|
||||
- toBlockHash32: [32]
|
||||
- textLenBytes: uint16
|
||||
- text UTF-8
|
||||
```
|
||||
|
||||
Правила:
|
||||
|
||||
- edit всегда ссылается на оригинальный блок соответствующего типа;
|
||||
- `toBlockchainName` в edit не хранится;
|
||||
- `textLen=0` означает логическое удаление содержимого;
|
||||
- тип исходной сущности edit не меняет.
|
||||
|
||||
### 3.1.6. Что считается комментарием
|
||||
|
||||
Комментарии не требуют нового формата.
|
||||
|
||||
Для обсуждения новых контентных сущностей продолжают использоваться уже существующие:
|
||||
|
||||
- `TEXT_REPLY`
|
||||
- `TEXT_EDIT_REPLY`
|
||||
|
||||
Это позволяет не ломать старую reply-механику и reuse текущую модель тредов.
|
||||
|
||||
## 4. Текстовые отзывы
|
||||
|
||||
## 4.1. Новый `type=6` — `TEXT_RATING`
|
||||
|
||||
Назначение:
|
||||
|
||||
- текстовая оценка / отзыв на объект;
|
||||
- без числовой шкалы;
|
||||
- с возможностью редактирования и логического удаления.
|
||||
|
||||
Смысл `TEXT_RATING`:
|
||||
|
||||
- это текст;
|
||||
- это специальный отзыв / мнение / оценка;
|
||||
- это явный сигнал, что перед нами не просто комментарий, а осмысленный отзыв;
|
||||
- в будущем это поле можно отдельно анализировать нейронками.
|
||||
|
||||
### 4.1.1. Подтипы
|
||||
|
||||
- `subType=10` — `TEXT_RATING_POST`
|
||||
- `subType=11` — `TEXT_RATING_EDIT`
|
||||
|
||||
### 4.1.2. Где разрешён `TEXT_RATING_POST`
|
||||
|
||||
Разрешён на target:
|
||||
|
||||
- `HEADER` пользователя;
|
||||
- контентный блок `type=5`;
|
||||
- при необходимости в будущем — на другие target-блоки по отдельному решению.
|
||||
|
||||
Сейчас в данном ТЗ:
|
||||
|
||||
- отзыв / оценка на пользователя — да;
|
||||
- отзыв / оценка на контент — да;
|
||||
- отзыв / лайк на канал целиком — не вводится, только оставляется как будущая возможность.
|
||||
|
||||
### 4.1.3. Где и как используется `TEXT_RATING_POST`
|
||||
|
||||
`TEXT_RATING_POST` можно создавать:
|
||||
|
||||
- как отзыв на контентный блок;
|
||||
- как отзыв на пользователя через target на `HEADER`;
|
||||
- как специальный ответ вместо обычного комментария.
|
||||
|
||||
Практическое правило для UI:
|
||||
|
||||
- при ответе на любое сообщение пользователь может выбрать:
|
||||
- обычный ответ;
|
||||
- `мнение / отзыв`.
|
||||
|
||||
### 4.1.4. Формат body
|
||||
|
||||
#### Создание
|
||||
|
||||
```text
|
||||
TextRatingBody_v1
|
||||
- toBlockchainNameLen: uint8
|
||||
- toBlockchainName UTF-8
|
||||
- toBlockGlobalNumber: int32
|
||||
- toBlockHash32: [32]
|
||||
- textLenBytes: uint16
|
||||
- text UTF-8
|
||||
```
|
||||
|
||||
#### Редактирование
|
||||
|
||||
```text
|
||||
TextRatingEditBody_v1
|
||||
- toBlockGlobalNumber: int32
|
||||
- toBlockHash32: [32]
|
||||
- textLenBytes: uint16
|
||||
- text UTF-8
|
||||
```
|
||||
|
||||
Правила:
|
||||
|
||||
- edit ссылается на оригинальный `TEXT_RATING_POST`;
|
||||
- пустой текст в edit означает логическое удаление отзыва.
|
||||
|
||||
## 5. Статусные действия и накопительные события
|
||||
|
||||
## 5.1. Новый `type=7` — `STATUS_ACTION`
|
||||
|
||||
Назначение:
|
||||
|
||||
- хранение действий пользователя по отношению к контенту;
|
||||
- вычисление текущего статуса;
|
||||
- накопительный учёт повторных прохождений;
|
||||
- подтверждение статусов другими людьми.
|
||||
|
||||
### 5.1.1. Подтипы
|
||||
|
||||
- `subType=10` — `STATUS_DONE_ONCE`
|
||||
- `subType=20` — `STATUS_INTERESTED`
|
||||
- `subType=30` — `STATUS_STARTED`
|
||||
- `subType=40` — `STATUS_COMPLETED`
|
||||
- `subType=50` — `STATUS_ABANDONED`
|
||||
- `subType=60` — `STATUS_CONFIRMED`
|
||||
|
||||
### 5.1.2. Матрица допустимости по контенту
|
||||
|
||||
`STATUS_DONE_ONCE` разрешён только для:
|
||||
|
||||
- `CONTENT_EXERCISE`
|
||||
- `CONTENT_SERVICE`
|
||||
|
||||
`STATUS_INTERESTED`, `STATUS_STARTED`, `STATUS_COMPLETED`, `STATUS_ABANDONED` разрешены только для:
|
||||
|
||||
- `CONTENT_EXERCISE`
|
||||
- `CONTENT_COURSE`
|
||||
|
||||
`CONTENT_ENTRYPOINT` не поддерживает:
|
||||
|
||||
- `interested`
|
||||
- `started`
|
||||
- `completed`
|
||||
- `abandoned`
|
||||
|
||||
### 5.1.3. Как считать текущее состояние
|
||||
|
||||
Для пары:
|
||||
|
||||
- `actorLogin`
|
||||
- `targetBlock`
|
||||
|
||||
актуальным статусом считается последнее по времени статусное событие из набора:
|
||||
|
||||
- `STATUS_INTERESTED`
|
||||
- `STATUS_STARTED`
|
||||
- `STATUS_COMPLETED`
|
||||
- `STATUS_ABANDONED`
|
||||
|
||||
Следствия:
|
||||
|
||||
- у одного пользователя по одному объекту в каждый момент времени только один актуальный статус;
|
||||
- если последним пришёл `interested`, статус считается “заинтересовался / рассматривает, но ещё не начал”;
|
||||
- если последним пришёл `started`, статус считается “в процессе”;
|
||||
- если последним пришёл `completed`, статус считается “завершён / освоен / знаю”;
|
||||
- если последним пришёл `abandoned`, статус считается “брошен”.
|
||||
|
||||
### 5.1.4. Как считать количество прохождений
|
||||
|
||||
`STATUS_DONE_ONCE` не меняет текущий статус.
|
||||
|
||||
Он считается отдельно как накопительное событие.
|
||||
|
||||
Сервер должен уметь считать:
|
||||
|
||||
- сколько раз пользователь сделал упражнение;
|
||||
- сколько раз пользователь прошёл услугу / процедуру.
|
||||
|
||||
### 5.1.5. Дополнительный текст действия
|
||||
|
||||
Каждое действие `STATUS_*` может содержать дополнительный текст-комментарий.
|
||||
|
||||
Примеры:
|
||||
|
||||
- как именно делал упражнение;
|
||||
- чем заинтересовал курс;
|
||||
- с какими мыслями начал курс;
|
||||
- почему бросил;
|
||||
- что именно подтверждает подтверждающий человек.
|
||||
|
||||
### 5.1.6. Подтверждение статуса
|
||||
|
||||
`STATUS_CONFIRMED` разрешён только на target-статусы:
|
||||
|
||||
- `STATUS_DONE_ONCE`
|
||||
- `STATUS_INTERESTED`
|
||||
- `STATUS_STARTED`
|
||||
- `STATUS_COMPLETED`
|
||||
- `STATUS_ABANDONED`
|
||||
|
||||
Это значит:
|
||||
|
||||
- подтверждение не ставится прямо на курс или упражнение;
|
||||
- подтверждение ставится на конкретный статусный блок другого человека.
|
||||
|
||||
Подтверждение:
|
||||
|
||||
- не меняет основной статус автора;
|
||||
- не меняет счётчик `done_once`;
|
||||
- хранится как отдельное мнение / свидетельство.
|
||||
|
||||
### 5.1.7. Формат body
|
||||
|
||||
Для `STATUS_DONE_ONCE`, `STATUS_INTERESTED`, `STATUS_STARTED`, `STATUS_COMPLETED`, `STATUS_ABANDONED`:
|
||||
|
||||
```text
|
||||
StatusActionBody_v1
|
||||
- toBlockchainNameLen: uint8
|
||||
- toBlockchainName UTF-8
|
||||
- toBlockGlobalNumber: int32
|
||||
- toBlockHash32: [32]
|
||||
- noteLenBytes: uint16
|
||||
- note UTF-8
|
||||
```
|
||||
|
||||
Для `STATUS_CONFIRMED`:
|
||||
|
||||
```text
|
||||
StatusConfirmBody_v1
|
||||
- toBlockchainNameLen: uint8
|
||||
- toBlockchainName UTF-8
|
||||
- toBlockGlobalNumber: int32
|
||||
- toBlockHash32: [32]
|
||||
- noteLenBytes: uint16
|
||||
- note UTF-8
|
||||
```
|
||||
|
||||
На уровне бинарного формата тело можно оставить одинаковым.
|
||||
Различие задаётся `subType` и правилами валидации target.
|
||||
|
||||
## 6. Community-события
|
||||
|
||||
## 6.1. Новый `type=8` — `COMMUNITY_EVENT`
|
||||
|
||||
Назначение:
|
||||
|
||||
- заявки в сообщество;
|
||||
- выход из сообщества;
|
||||
- принятие;
|
||||
- исключение.
|
||||
|
||||
### 6.1.1. Подтипы
|
||||
|
||||
- `subType=10` — `COMMUNITY_JOIN_REQUEST`
|
||||
- `subType=20` — `COMMUNITY_LEAVE`
|
||||
- `subType=30` — `COMMUNITY_ACCEPT`
|
||||
- `subType=40` — `COMMUNITY_REMOVE`
|
||||
|
||||
### 6.1.2. Базовая логика
|
||||
|
||||
`COMMUNITY_JOIN_REQUEST`
|
||||
|
||||
- создаёт пользователь;
|
||||
- target — `CONTENT_ENTRYPOINT` канала;
|
||||
- может содержать текст заявки.
|
||||
|
||||
`COMMUNITY_LEAVE`
|
||||
|
||||
- создаёт сам участник;
|
||||
- target — `CONTENT_ENTRYPOINT` канала;
|
||||
- подтверждение не требуется;
|
||||
- может содержать текст.
|
||||
|
||||
`COMMUNITY_ACCEPT`
|
||||
|
||||
- создаёт владелец канала;
|
||||
- target — конкретный блок `COMMUNITY_JOIN_REQUEST`;
|
||||
- может содержать текст.
|
||||
|
||||
`COMMUNITY_REMOVE`
|
||||
|
||||
- создаёт владелец канала;
|
||||
- target — `CONTENT_ENTRYPOINT` канала;
|
||||
- body дополнительно хранит `subjectLogin`, кого исключили;
|
||||
- может содержать текст.
|
||||
|
||||
### 6.1.3. Текущее членство
|
||||
|
||||
Пользователь считается текущим участником сообщества, если:
|
||||
|
||||
- у него есть хотя бы одно принятие в это сообщество;
|
||||
- после этого принятия нет более позднего:
|
||||
- `COMMUNITY_LEAVE`
|
||||
- `COMMUNITY_REMOVE`
|
||||
|
||||
Заявка сама по себе членство не создаёт.
|
||||
|
||||
### 6.1.4. Формат body
|
||||
|
||||
Для `JOIN_REQUEST` и `LEAVE`:
|
||||
|
||||
```text
|
||||
CommunityActionBody_v1
|
||||
- toBlockchainNameLen: uint8
|
||||
- toBlockchainName UTF-8
|
||||
- toBlockGlobalNumber: int32
|
||||
- toBlockHash32: [32]
|
||||
- noteLenBytes: uint16
|
||||
- note UTF-8
|
||||
```
|
||||
|
||||
Для `ACCEPT`:
|
||||
|
||||
```text
|
||||
CommunityAcceptBody_v1
|
||||
- toBlockchainNameLen: uint8
|
||||
- toBlockchainName UTF-8
|
||||
- toBlockGlobalNumber: int32
|
||||
- toBlockHash32: [32]
|
||||
- noteLenBytes: uint16
|
||||
- note UTF-8
|
||||
```
|
||||
|
||||
Для `REMOVE`:
|
||||
|
||||
```text
|
||||
CommunityRemoveBody_v1
|
||||
- toBlockchainNameLen: uint8
|
||||
- toBlockchainName UTF-8
|
||||
- toBlockGlobalNumber: int32
|
||||
- toBlockHash32: [32]
|
||||
- subjectLoginLen: uint8
|
||||
- subjectLogin ASCII
|
||||
- noteLenBytes: uint16
|
||||
- note UTF-8
|
||||
```
|
||||
|
||||
## 7. Что остаётся на старых типах
|
||||
|
||||
### 7.0. Обычный канал и лента достижений
|
||||
|
||||
На уровне продукта рекомендуется различать:
|
||||
|
||||
- обычный канал постов пользователя;
|
||||
- отдельную ленту его действий и достижений.
|
||||
|
||||
В обычном канале пользователь:
|
||||
|
||||
- пишет посты;
|
||||
- публикует материалы;
|
||||
- общается и обсуждает.
|
||||
|
||||
В ленте достижений видны события:
|
||||
|
||||
- какие упражнения он делал;
|
||||
- какие услуги / процедуры проходил;
|
||||
- какие курсы его заинтересовали;
|
||||
- какие курсы он начал;
|
||||
- какие курсы он завершил;
|
||||
- что он бросил.
|
||||
|
||||
В данном ТЗ эта модель фиксируется как продуктовая логика.
|
||||
Конкретный способ хранения можно реализовать:
|
||||
|
||||
- либо отдельным специальным каналом;
|
||||
- либо отдельным режимом чтения по статусным блокам.
|
||||
|
||||
### 7.1. Лайк пользователю
|
||||
|
||||
Лайк пользователю не вводится как `REACTION`.
|
||||
|
||||
Он остаётся в слое социальных связей:
|
||||
|
||||
- через `CONNECTION`
|
||||
- как будущий отдельный подтип связи
|
||||
|
||||
В этом ТЗ сам новый подтип связи не описывается детально.
|
||||
Нужно только зафиксировать правило:
|
||||
|
||||
- лайк человека относится к графу связей, а не к реакции на блок.
|
||||
|
||||
### 7.2. Лайк контента
|
||||
|
||||
Лайк на:
|
||||
|
||||
- `CONTENT_PLAIN`
|
||||
- `CONTENT_EXERCISE`
|
||||
- `CONTENT_SERVICE`
|
||||
- `CONTENT_COURSE`
|
||||
- `CONTENT_ENTRYPOINT`
|
||||
|
||||
может использовать уже существующий:
|
||||
|
||||
- `REACTION_LIKE`
|
||||
- `REACTION_UNLIKE`
|
||||
|
||||
Отдельный новый формат для лайка контента не нужен.
|
||||
|
||||
### 7.3. Канал целиком
|
||||
|
||||
В текущем ТЗ не вводятся:
|
||||
|
||||
- отзыв на канал целиком;
|
||||
- лайк канала целиком.
|
||||
|
||||
Это оставляется как будущая возможность.
|
||||
|
||||
### 7.4. Отзывы о людях
|
||||
|
||||
Отзывы о человеке как о человеке в текущем ТЗ допустимы через `TEXT_RATING` на `HEADER`.
|
||||
|
||||
Но продуктовую модель их показа нужно отдельно продумать.
|
||||
|
||||
Направление для будущего:
|
||||
|
||||
- просмотр отзывов о человеке на вкладке связей;
|
||||
- приоритетный вывод отзывов от близких друзей, родственников, друзей и контактов;
|
||||
- затем вывод остальных отзывов.
|
||||
|
||||
Эта тема полезна, но требует дополнительной осторожной проработки с точки зрения UX и социальных рисков.
|
||||
|
||||
## 8. Требования к серверу
|
||||
|
||||
Сервер после внедрения должен уметь:
|
||||
|
||||
1. Валидировать новые `type=5..8`.
|
||||
2. Хранить новые блоки без ломки старого чтения.
|
||||
3. Определять текущий статус пользователя по объекту:
|
||||
- `interested`
|
||||
- `started`
|
||||
- `completed`
|
||||
- `abandoned`
|
||||
4. Считать накопительные события `done_once` для:
|
||||
- `exercise`
|
||||
- `service`
|
||||
5. Считать подтверждения статусов.
|
||||
6. Определять единственный актуальный `entrypoint` канала.
|
||||
7. Определять текущее членство в сообществе канала.
|
||||
8. Поддерживать внутренние ссылки вида:
|
||||
- `SHiNE/<blockchainName>/<blockNumber>`
|
||||
- `SHiNE/<blockchainName>/<blockNumber>/<blockHash>`
|
||||
|
||||
## 9. Требования к UI
|
||||
|
||||
UI после внедрения должен уметь:
|
||||
|
||||
1. Показывать разные карточки для:
|
||||
- текста
|
||||
- упражнения
|
||||
- услуги
|
||||
- курса
|
||||
- entrypoint
|
||||
2. Показывать стартовую страницу канала, если `entrypoint` существует.
|
||||
3. Не показывать entrypoint, если он логически удалён.
|
||||
4. Давать человеку только допустимые действия по типу материала.
|
||||
5. Показывать:
|
||||
- текущий статус;
|
||||
- количество `done_once`;
|
||||
- подтверждения статуса.
|
||||
6. Показывать отдельные действия-кнопки на специальных блоках, например:
|
||||
- `Выполнил упражнение`
|
||||
- `Прошёл процедуру`
|
||||
- `Заинтересовало`
|
||||
- `Начал курс`
|
||||
- `Закончил курс`
|
||||
7. При ответе на сообщение давать выбор:
|
||||
- обычный ответ;
|
||||
- `мнение / отзыв`.
|
||||
8. Открывать внутренние ссылки SHiNE.
|
||||
|
||||
## 10. Вывод по совместимости
|
||||
|
||||
Предлагаемая модель реализуема без слома старого блокчейна.
|
||||
|
||||
Причина:
|
||||
|
||||
- старые `type=0..4` не меняются;
|
||||
- новые сущности вводятся только как новые `type=5..8`;
|
||||
- существующие `reply`, `like`, `edit`, `HEADER`, `CREATE_CHANNEL` и `CONNECTION` продолжают работать как раньше;
|
||||
- старые клиенты смогут игнорировать новые типы как неизвестные;
|
||||
- новые клиенты смогут постепенно включать поддержку нового функционала.
|
||||
|
||||
Итог:
|
||||
|
||||
- это расширение формата блокчейна;
|
||||
- это не миграция со сломом старых блоков;
|
||||
- это можно внедрять поэтапно.
|
||||
Reference in New Issue
Block a user