Назначение: определить жизненный цикл профиля, его формы на диске и при хранении и протокол синхронизации. Это
модель, проверенная в поле отгруженными антидетект-продуктами (<uuid>.zip + <uuid>.sig, частичная локальная
материализация) и формализованная здесь (ADR-0004).
1. Две формы профиля
- Рабочая форма (runtime form): живой
user-data-dir, с которым может работать Chromium (cookies, localStorage, IndexedDB…). - Форма для хранения и синхронизации: Profile Blob =
manifest + payload:- payload =
user-data-dir→ детерминированный архив (tar/zip) → сжатие → шифрование (AEAD). <uuid>.blob(шифртекст) +<uuid>.sig(подпись над шифртекстом и манифестом).- манифест (открытый, с защитой целостности): uuid, вектор версий, размер, версия ядра, открывавшего профиль последним, id отпечатка, createdAt, deviceId, contentHash.
- payload =
Мосты между двумя формами: materialize (блоб → папка) и pack (папка → блоб). Это единственные разрешённые преобразования.
2. Конечный автомат жизненного цикла
create / import
│
┌─────▼──────┐ pull(blob) ┌──────────────┐ materialize ┌───────────────┐
│ CLOUD_ONLY │────────────────►│ LOCAL_CACHED │────────────►│ MATERIALIZED │
└─────▲──────┘ └──────▲───────┘ └──────┬────────┘
│ evict │ pack │ start
│ │ ▼
┌───────┴────────┐ upload ┌──────┴───────┐ stop ┌──────────────┐
│ SYNCING_UPLOAD │◄───────────────│ DIRTY │◄────────────│ RUNNING │
└────────────────┘ └──────────────┘ └──────────────┘
remote newer │ ▲
▼ │ resolve
┌────────────────┐ ┌──────┴──────┐ lock held elsewhere
│ SYNCING_DOWNLOAD│ │ CONFLICT │◄────── LOCKED_REMOTE
└────────────────┘ └─────────────┘
Определения состояний:
CLOUD_ONLY— блоб существует только в облаке; локально ничего нет.LOCAL_CACHED— блоб есть локально, не распакован.MATERIALIZED— живая папка существует, профиль не запущен.RUNNING— ядро работает с папкой (монопольно; синхронизация не может её трогать).DIRTY— остановлен с локальными изменениями, ещё не загруженными.SYNCING_UPLOAD/DOWNLOAD— идёт передача.CONFLICT— локальная и удалённая версии разошлись (обе изменились после общей базы).LOCKED_REMOTE— блокировку запуска/правки держит другое устройство.
3. Шифрование и подпись (zero-knowledge, несколько KEK — ADR-0008)
- Иерархия конвертов: полезная нагрузка каждого профиля шифруется AEAD случайным ключом данных профиля (DEK). DEK независимо оборачивается несколькими KEK — любой из них его разворачивает. Сервер хранит только шифртекст и копии обёрнутого DEK → сервер не может расшифровать ни на одном пути.
- Факторы KEK: пароль (Argon2id) + обязательный Recovery Key (офлайн-набор на экстренный случай); необязательные passkey (WebAuthn PRF) и связка ключей ОС; командные профили добавляют KEK ключа организации (обёртка открытым ключом) для восстановления с помощью администратора — по-прежнему zero-knowledge для сервера.
- AEAD (AES-256-GCM или XChaCha20-Poly1305) для полезной нагрузки; подпись (Ed25519) над манифестом и шифртекстом —
чтобы обнаружить подмену и повреждение (
.sig). - Восстановление: потеряли один фактор → восстановите через другой. Потеряли все факторы → потеряли профили (честный предел сквозного шифрования). Никакого депонирования на сервере. Подробности: KEY_RECOVERY_COMPARISON.md, SECURITY_POLICY §4.
4. Протокол синхронизации
- Блокировка:
sync.lock.acquire(id)перед загрузкой или правкой. Блокировки выдаются в аренду (TTL) и помечены владельцем. - Векторы версий: каждый блоб несёт
{deviceId: counter}; сравнение показывает, перемотка это вперёд или расхождение. - Загрузка: pack → шифрование → подпись → multipart-загрузка в объектное хранилище → фиксация манифеста в базе метаданных.
- Скачивание: получить блоб → проверить подпись → расшифровать → материализовать по требованию.
- Конфликт: если удалённая версия ушла дальше локальной базы, пока локальная в DIRTY →
CONFLICT. Разрешение по умолчанию: побеждает последний записавший, под защитой блокировки; CONFLICT сохраняет оба блоба (a/b) для ручного выбора. - Вытеснение: LOCAL_CACHED/MATERIALIZED могут вытесняться в CLOUD_ONLY при нехватке места на диске (LRU), но никогда — в DIRTY/RUNNING.
5. Гарантия local-first
Без облачного аккаунта работает всё: профили живут как локальные блобы и папки, жизненный цикл — без состояний SYNCING/LOCKED_REMOTE. Облако — добавочный слой и никогда не зависимость (ARCHITECTURE.md §1.3).
6. Инварианты (их соблюдает Profile Lifecycle, в обход — никогда)
- Профиль в
RUNNINGникогда не упаковывается, не загружается и не вытесняется. - Синхронизация никогда не правит живой
user-data-dir(DO_NOT_DO.md). - Materialize/pack — единственные преобразования папка↔блоб.
- Каждый переход состояния порождает событие
profile.state_changedи (для опасных) запись аудита. - Materialized = единственное окно открытого текста. Пока профиль в
MATERIALIZED/RUNNING,user-data-dirлежит на диске в открытом виде (он нужен Chromium). Приpack/вытеснении папка надёжно стирается; окно держится минимальным. (Остаточный риск при компрометации машины: THREAT_MODEL §4.) materializeникогда не отдаёт сырой путь удалённым клиентам. Путь получает только локальный интерфейс / контекст владельца; клиенты CLI/MCP/скриптов получают непрозрачный дескриптор, а любая выдача сырого пути — это power mode + аудит: иначе путь обошёл бы единую границу управления.
Открытые вопросы
Детерминизм архивов между ОС, частичная/дельта-синхронизация против полного блоба, TTL блокировки и политика перехвата, UX конфликтов, варианты восстановления ключей (социальное/депонирование?) → OPEN_QUESTIONS.md.