Этап 5: история — экспорт и импорт .bare, устройства, место, пагинация

Экспорт: ключ архива из секрета аккаунта через HKDF, заголовок ровно 65 байт
(magic, версия, соль, 32 сырых байта отпечатка владельца, iv) и он же целиком
AAD шифротекста. Ника владельца в файле нет. Импорт сверяет отпечаток до
расшифровки, сливает идемпотентно по id, а записи peers берёт только для
ников, которых в локальном TOFU ещё нет: архивом доверие к ключу не перебить.

Настройки: устройства с датой и пометкой «это устройство», «занято N МБ»,
кнопка «экспортировать» в подтверждении выхода и в подтверждении входа
под другим ником — долг этапа 1 и обещание ADR-029 закрыты.
Лента: страницы по 50 с подгрузкой вверх без прыжка прокрутки; новая
страница вставляется, а не пересобирает ленту.

ADR-050: импорт не перезаписывает лежащую запись — у своей есть состояние
отправки, которого в архиве нет.
ADR-054: архив — недоверенный ввод. Ревью собрало архивы с ts вне диапазона
Date, мусорным lastId, ником с bidi-переопределением и roomId с обходом пути:
каждый из них навсегда ломал ленту или счётчик. Теперь форма ника, roomId,
id, ts и автора проверяется, а lastId из файла не читается вовсе.
ADR-051, 052, 053: тексты и кнопки, устройства и место, страницы ленты
без виртуализации.

Приёмка: формат сверен побайтно на модулях, скачанных с боевого сервера, —
смещения заголовка, отпечаток сырыми байтами, новые соль и iv на каждый
экспорт, подмена любого байта заголовка ломает расшифровку, чужой секрет
не открывает, семь видов битых файлов отвергнуты.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015DbCjVfTFq4ZFG8juD45YJ
This commit is contained in:
2026-08-23 02:57:51 +03:00
co-authored by Claude Opus 5
parent 8f67f4aa4d
commit 0c878477d2
21 changed files with 1274 additions and 53 deletions
+30
View File
@@ -62,6 +62,8 @@ const ctx = {
},
errorText,
ensureConfig,
devices,
removeDevice,
signUp,
signIn,
changePassword,
@@ -226,6 +228,34 @@ async function storedNick() {
}
}
// --- устройства ---------------------------------------------------------
// devices — устройства аккаунта для настроек (docs/protocol.md,
// «Устройства»). Своё сервер помечает по сессии; заодно сверяем
// с устройством этой вкладки: сессия привязывается к устройству
// в POST /api/devices, и до него current не проставлен (ADR-017).
async function devices() {
const list = await api.devices();
const mine = sync.deviceId();
if (!Array.isArray(list)) {
return [];
}
return list
.filter((item) => item !== null && typeof item === "object" && typeof item.id === "string")
.map((item) => ({
id: item.id,
createdAt: item.createdAt,
current: item.current === true || (mine !== null && item.id === mine),
}));
}
// removeDevice — «удалить» в настройках: очередь, подписка и сессии
// устройства уходят вместе с ним. Своё устройство сюда не приходит —
// его отцепляет «выйти» (ADR-052).
function removeDevice(id) {
return api.removeDevice(id);
}
// --- аккаунт -----------------------------------------------------------
// derive — вывод ключей по числу итераций, пришедшему от сервера. Границы