Политика хранения и удаления данных (EU/US/IL)
1. Принцип
⇄ S56·W4 (решение владельца 2026-10-06): сроков у данных клиента теперь ДВА, и оба — 30 дней от ПОСЛЕДНЕГО ДЕЙСТВИЯ, с одним расписанием напоминаний. Заменены прежние таймеры (1) «модель — 30 дней от обучения», (2) «кадры — 30 дней после выдачи, только у выданных заказов» и (4) «исходные фото — 30 дней с загрузки». Отсечка при замене модели (3) — прежняя. ⟦ДЛЯ ЮРИСТА: формулировки этого раздела наши, по изменённому факту; сверить с текстом согласия на биометрию — его новая редакция biometric-2026-10-06-eu-us-il говорит то же⟧- (1) Личные данные — Модель личности (soul_id у внешнего провайдера) + Исходные фотографии (у нас:
uploads/+ уменьшенная копияanalysis/) + анкета (рост, размер одежды, тип фигуры, пол) ⟦исправлено 2026-10-08, сверка канона B-TXT-01; юрист смотрит после⟧: 30 дней после последнего действия с данными — загрузка фото, правка анкеты, оплата съёмки, сообщение в переписке, «Готово» после догрузки; со стороны студии — выдача заказа и просьба клиенту. Просто войти в кабинет — не действие. Пока у клиента есть заказ, где следующий шаг за студией (оплачен, в работе, на приёмке, правка, открытая просьба о правке), срок стоит: модель и фото нужны производству. По истечении — удаление у провайдера + отвязка у нас с подтверждением удаления (лог + проброс запроса провайдеру), удаление фото и анкеты. Учётная запись и список заказов остаются. - (2) Данные заказа — Результаты (готовые кадры) и переписка по заказу: 30 дней после последнего действия по заказу: выдан — от выдачи (открыта просьба о правке — срок стоит; выдан повторно — новый отсчёт); ждём фото от клиента — от просьбы оператора; отменён — от отмены; черновик без оплаты — от последнего сообщения (кадров у черновика нет). Оплачен / в работе / на приёмке / правка — не удаляем: следующий шаг за студией, клиент заплатил и ждёт. Новое сообщение в переписке сдвигает отсчёт. Затем удаление из хранилища + инвалидация CDN + лог; сам заказ и оплата остаются. ⇄ S60·W3 (решение владельца 2026-10-18, ADR-0019 уточнение «вечер»): съёмка по БЛОГЕРСКОМУ коду с действующим согласием на рекламу — полгода (180 дней) от той же точки отсчёта вместо 30 дней; «Продлить» продлевает кадры и разрешение вместе. Отзыв согласия (кабинет, бот /ads — по всем съёмкам) возвращает кадры в обычный срок: 30 дней от отзыва, не позже уже назначенной даты. «Удалить сейчас» у такой съёмки — удаление И отзыв. Через полгода кадры удаляются, согласие кончается. Касается ТОЛЬКО кадров и переписки заказа: срок (1) личных данных блогера согласие не трогает. ⟦ДЛЯ ЮРИСТА (Ю-9): срок 6 месяцев для рекламных кадров — проверить как политику хранения⟧
- Напоминания — одни для обоих сроков: за 7 дней, за 3 дня и утром в день удаления, одним сообщением со списком (◧ бот / ◨ email) и плашкой в кабинете, видной с 7 дней. Удаление — в конце дня удаления, час не обещается; между сообщением дня удаления и удалением не меньше 12 часов. У каждой строки — «Продлить на 30 дней» (к дате удаления, без ограничения числа продлений; клик фиксируется как согласие) и «Удалить сейчас» (с подтверждением, необратимо). Срок, начавшийся или возобновившийся позже, чем за неделю до даты, ставится не ближе 7 дней — чтобы три напоминания успели.
- (3) Отсечка при ЗАМЕНЕ Модели личности — при обучении новой soul_id прежняя отвязывается и удаляется по истечении окна восстановления ~5 дней (на случай ошибочного удаления).
- Хранение сверх срока — только явное продление владельцем или законное основание (§5).
2. Что удаляется (объекты хранения)
✅ СТРОКА ПРО ИСХОДНЫЕ ФОТО ПРИВЕДЕНА К НОВОМУ ФАКТУ 2026-08-30 (решение владельца, S36-W2). Срок у исходных фотографий ТЕПЕРЬ ЕСТЬ: 30 дней с загрузки (таймер-4, §1). 🗄️ Предыстория, чтобы читатель понимал, почему абзац такой длинный. Сутками раньше здесь стояло «у нас — по сроку хранения», и это обещало то, чего система не делала: замер S35-W6 перечислил ВСЕ пути удаления объектов вapps/api— привязанное к заказу фото не удалял по возрасту ни один, а метёлкаPhotoGcServiceберёт только СИРОТ (её «возраст» — грейс 2 часа, защита загрузки в полёте). Промежуточным шагом владелец выбрал привести ДОКУМЕНТ к поведению (сужение обещания), и только потом назвал число. ⚠️ Итоговую юридическую формулировку по-прежнему смотрит юрист — здесь приведён ФАКТ, а не утверждённая редакция.
| Категория | Где живёт | Удаляется |
|---|---|---|
| Исходные фото (загрузка пользователя) | R2/S3 (uploads/ + analysis/ — уменьшенная копия для разбора, несёт то же лицо; presigned — S1); копия передана внешнему провайдеру для обучения | да, срок личных данных — 30 дней после последнего действия (§1 (1); ⇄ S56·W4, было «30 дней с загрузки»). Гасятся ОБА префикса разом: оригинал и производная копия. Плюс — кнопкой «Удалить сейчас», по требованию клиента (снял фото в наборе), при удалении аккаунта (§7, полный каскад) и метёлка ОСИРОТЕВШИХ объектов |
| Модель личности (soul_id) — биометрическая модель | у внешнего провайдера (Higgsfield); у нас только идентификатор externalSoulId | да, срок личных данных (§1 (1)) / продление / «Удалить сейчас»; проброс запроса на удаление провайдеру + подтверждение |
| Анкета (рост, размер, фигура, пол) и разбор фото (этничность, причёска, телосложение) — и их копии: снимок анкеты в заказе, состояние диалога бота, тексты промптов заказа (в каждом — этнический токен, рост, пол) | Postgres (users, souls.analysis, orders.anketaSnapshot, bot_sessions, order_prompts, order_prompt_versions) | да, срок личных данных — 30 дней после последнего действия (§1 (1)), вместе с моделью и исходными фото; у промптов стирается текст, заказ (сумма, даты, статус) остаётся (⇄ S57·W3, решение владельца 2026-10-13). При удалении аккаунта — полностью (§7) |
| Результаты (сгенерированные изображения) | R2/S3 — `results/`, `redress/` И `delivered/` (переодетые кадры — те, что уходят клиенту; delivered/ — копия выданного комплекта, S35-W5) + CDN, выдача только presigned | да, срок данных заказа — 30 дней после последнего действия по заказу (§1 (2); ⇄ S56·W4: у ВСЕХ заказов по границе, а не только у выданных), все префиксы с `retention: 'delivery'` в реестре apps/api/src/s3/client-data-namespaces.ts — перечень живёт там, а не здесь |
| Прежняя soul_id при замене | у провайдера + идентификатор у нас | да, по истечении окна ~5 дней (таймер 3) |
| Промежуточные артефакты генерации | ⟦РЕШЕНИЕ: кэш/темп ИИ-провайдеров — уточнить у Наташи (N1) и провайдеров⟧ | да / по договору с провайдером |
| Записи заказов | Postgres (orders, S1) | при удалении аккаунта — удаляются полностью (§7). При обычном жизненном цикле — ⟦срок по налоговому/бухгалтерскому праву IL — юрист⟧ |
| Платёжные записи | Postgres (payments) | при удалении аккаунта — НЕ удаляются, обезличиваются (отвязка от userId/orderId; сумма/валюта/дата/txn остаются), основание GDPR ст.17(3)(b) + налоговый ретеншн (§7) |
| Журнал согласий | Postgres (consents) | при удалении аккаунта — записи удаляются, но PII-free снимок (тип/версия/дата) сохраняется в записи об удалении как proof-of-consent (§7) |
| Переписка по заказу (чат клиент↔студия) | Postgres (order_messages) | да, тот же срок данных заказа, что у Результатов (§1 (2); решение владельца продукта 2026-08-20, требование R-10; ⇄ S56·W4 — у всех заказов по границе, у черновика — от последнего сообщения). Реализовано: OrderRetentionService.purgeOrderData. При удалении аккаунта — удаляется полностью (§7), счётчик стёртых сообщений попадает в запись об удалении |
| Лог фактов удаления (deletion receipt) | Postgres (deletion_audits) | хранится (доказуемость соблюдения политики), PII/биометрии не содержит: хэш id + счётчики + снимок согласий |
⚠️ Удаление должно затрагивать все копии: объект в R2/S3, кэш CDN (инвалидация), любые темп-файлы у ИИ-провайдеров. Удаление только из БД при живом S3-объекте — недостаточно.
3. Срок, точка отсчёта, продление
3.1. Сроки (§0h, ⇄ S56·W4 2026-10-06): 30 дней после последнего действия — у личных данных (модель, исходные фото, анкета) и у данных заказа (кадры, переписка); точки отсчёта — §1. ~5 дней — окно восстановления при замене soul_id.
3.2. Точка старта 30 дней — последнее действие (решение владельца 2026-10-06, §1). ⟦ДЛЯ ЮРИСТА: прежде здесь стояла открытая развилка «от обучения / от выдачи / от последней активности»⟧
3.3. Продление — опт-ин кликом из напоминания или из кабинета: +30 дней к дате удаления, без ограничения числа продлений (решение владельца 2026-10-06); продлевает строку, у которой нажато, — личные данные или данные конкретного заказа; факт продления фиксируется как согласие. 3.3a. «Удалить сейчас» — у той же строки, после подтверждения; необратимо; учётная запись, заказы и оплаты остаются. Если над съёмкой в этот момент идёт работа студии, удаление выполняется сразу после окончания текущего этапа (новые этапы по заказу не запускаются), и клиент получает сообщение, когда данные удалены (решение владельца 2026-10-08, ADR-0017 п.9). Подписка V2 — отдельный будущий режим постоянного хранения (вне MVP).
3.4. Скидка на повторную сессию (Phase 0) сама по себе хранение не продлевает.
4. Требования к авто-очистке (для реализации, НЕ в этом окне)
Формулируем как требования к будущей задаче (E4/E9/E10; инструмент канона — планировщик retention, слой 4 Phase 1):
- R-1. Автоматический регламентный процесс (джобы планировщика поверх workflow-движка/cron), удаляющий объекты старше срока — без ручного участия оператора. ⇄ S56·W4 (решение владельца 2026-10-06, кликом): пока у клиента есть заказ, где следующий шаг за студией (оплачен, в работе, на приёмке, правка, открытая просьба о правке), срок личных данных СТОИТ — иначе двойник удалялся бы посреди оплаченной съёмки. Прежнее уточнение S36-W2 («таймер-4 не делает исключения для незавершённого заказа») этим решением заменено. «Ждём фото от клиента» паузы не даёт — там следующий шаг за клиентом.
- R-2. Единый источник «возраста» объекта (метка времени загрузки/выдачи в БД или тег объекта S3).
- R-3. ✅ ИСПОЛНЕНО (2026-08-20). Каскад: при удалении — снять объект в R2/S3, инвалидировать CDN, отметить в БД. До этой даты инвалидация была ЛОГ-ЗАГЛУШКОЙ («кэш сойдёт по TTL»). Реализация —
CdnService.invalidatePrefix(apps/api/src/s3/cdn.service.ts), включается переменнойCLOUDFRONT_DISTRIBUTION_ID. ⚠️ Честно о сегодняшнем состоянии: кэша перед хранилищем сейчас НЕТ — личные объекты (результаты и входные фото) отдаются presigned-ссылкой прямо из S3,CDN_BASE_URLв проде не задан. То есть требование закрыто НА БУДУЩЕЕ: в день, когда CloudFront поставят перед бакетом, каскад не окажется молча неполным. Пока distribution id не задан, а кэш обнаружен, каждый проход пишет об этом ошибкой в лог — «нечего инвалидировать» и «инвалидация сломалась» не должны выглядеть одинаково. - R-3a. Для Модели личности (soul_id): проброс запроса на удаление внешнему провайдеру + получение и хранение подтверждения удаления (§0h: доказательство удаления у суб-обработчика).
- R-4. Досрочное удаление по запросу пользователя (отзыв согласия — см. согласие на биометрию, §7; удаление аккаунта — §5a): тот же каскад, вне ожидания срока. Для удаления аккаунта — сразу после подтверждения, без окна отмены (§5a).
- R-5. Идемпотентность и логирование факта удаления (что/когда удалено) — для доказуемости соблюдения политики (BIPA/GDPR accountability); сам лог не должен содержать биометрию.
- R-6. Обработка ошибок удаления (ретраи, алерт), чтобы «зависшие» объекты не хранились дольше срока.
- R-7. ✅ ИСПОЛНЕНО (S56·W4, 2026-10-06). Напоминание за 7 дней, за 3 дня и утром в день удаления — одно сообщение со списком на человека (◧ бот / ◨ email) и плашка в кабинете, с кнопками «Продлить на 30 дней» и «Удалить сейчас» у каждой строки; при молчании — удаление в конце дня (R-3a для модели). Реализация —
RetentionWorker(apps/api/src/notifications/retention-worker.service.ts). До S56 здесь стояло одно напоминание «за 3–5 дней» и только о модели. - R-8. ✅ ИСПОЛНЕНО (2026-08-30, S36-W2). Предупреждение о сроке хранения Результатов при выдаче. Здесь стояло «НЕ исполнено на 2026-08-29»: напоминание-джоба (R-7) считает срок от
Soul.expiresAt, то есть стережёт таймер-1, а у Результатов предупреждения не было ни при выдаче, ни по таймеру. Что сделано: сообщение о выдаче в ОБОИХ каналах (◧ телеграм · ◨ письмо) называет ДАТУ, до которой доступны кадры. Дата считается изorder.deliveredAt— из ТОГО ЖЕ значения, которое задаёт якорь таймера-2, поэтому обещанная человеку дата совпадает с той, по которой кадры реально сотрут. Текст и формат даты — общие для каналов (framesStoredUntilNote,@fromframe/types), иначе бот и письмо назвали бы один день разными словами. ⚠️ Датой, а не «до 30 дней»: срок в днях человеку нечем проверить, дату — можно. - R-10. ✅ ИСПОЛНЕНО (2026-08-20). Переписка по заказу (
order_messages, S32) живёт 30 дней после выдачи (⇄ S36 2026-08-29, было 90) — тот же таймер-2, что у Результатов: она содержит свободный текст клиента и обсуждает те самые кадры, поэтому переживать их не должна. Срок выбран владельцем продукта из двух вариантов; принятая цена названа прямо: переписка исчезает вместе с кадрами, к которым относится, и отдельно её сохранить нельзя. Реализация —SoulLifecycleWorker.purgeOldMessages(apps/api/src/soul/soul-lifecycle.worker.ts), проход по заказамDELIVEREDстарше срока, у которых переписка ещё есть. ⚠️ Отсчёт — от `order.updatedAt` терминального заказа, как и у кадров: отдельного поля «дата выдачи» в схеме нет. Поэтому переписка чистится РАНЬШЕ кадров внутри одного прохода — очистка кадров сама двигаетupdatedAt. Разбор — в док-блокеtick(). ⚠️ До этой даты срок здесь стоял как ТРЕБОВАНИЕ к будущей реализации, а не как обещание: политика публична, и объявленный срок, которого никто не исполняет, — ещё одно невыполненное обещание. Теперь исполнение есть, и умолчание стало бы хуже объявления. - R-9. Отсечка при замене soul_id (§0h таймер-3): прежняя модель помечается на удаление с окном восстановления ~5 дней, по истечении — каскад R-3/R-3a. В БД:
Soul.replacedAt/purgeAfter(F1). - Механизм: планировщик-джобы (build/reuse поверх workflow-движка) — каскадит БД/CDN и покрывает реминдеры/продление/досрочный отзыв; S3 Lifecycle — опциональная подстраховка на уровне бакета. ⟦РЕШЕНИЕ: включать ли Lifecycle-подстраховку — при реализации E9 — владелец продукта + инфра⟧
5. Законные исключения из авто-удаления
Хранение сверх срока допустимо только при основании, например:
- активный спор/претензия/возврат (до разрешения);
- требования закона (налоговый/бухгалтерский учёт — юрисдикция IL-юрлица; учёт продаж на стороне Lemon Squeezy как Merchant of Record). При удалении аккаунта это исключение реализовано как обезличивание платёжных записей (§7): сохраняется финансовая запись без связи с личностью. ⟦РЕШЕНИЕ: перечень исключений и их сроки по EU/US/IL — юрист⟧
Исключение касается обезличенных платёжных записей, не биометрии: биометрия (фото + soul_id) удаляется в любом случае. Записи заказов при удалении аккаунта удаляются полностью — по налоговому основанию сохраняется только обезличенный payment.5a. Удаление аккаунта — «право на забвение» (реализовано, spec 002)
Отдельно от трёх retention-таймеров (§1) действует явный запрос на удаление аккаунта:
- Триггер: ◨ Пользователь из веб-ЛК; оператор поддержки — тем же сервисом. Единая личность — удаление затрагивает оба канала (бот + веб).
- Стирание сразу, без окна отмены: после подтверждения аккаунт блокируется (403 на защищённых ручках) и сразу начинается необратимый purge; отмены нет. Если в этот момент идёт шаг съёмки (платный вызов провайдеру уже отправлен), ему дают завершиться (минуты), новые шаги не запускаются, затем стирается всё.
- Purge: S3 — ВСЕ неймспейсы клиентских данных по реестру
apps/api/src/s3/client-data-namespaces.ts(uploads/·analysis/·results/·redress/·delivered/·souls/), soul_id у провайдера (пробросdeleteSoul+ подтверждение, идемпотентно; провал внешнего вызова → эскалация без потери записи), БД одной транзакцией: обезличиваниеpayments→ удалениеconsents/orders/souls/orderPhoto/otp/sessions→ tombstoneUser(PII=null,deletedAt) → записьdeletion_audits(хэш id + счётчики + PII-free снимок согласий). - Что переживает purge: обезличенные платежи (§5, налог), deletion receipt (доказательство), снимок согласий в receipt (proof-of-consent, BIPA/GDPR accountability). Всё — без PII. ⟦НА РЕВЬЮ ЮРИСТУ ПЕРЕД ПРОДОМ: отсутствие окна отмены при удалении аккаунта; полнота списка стираемых PII-сущностей; достаточность обезличивания платежей и снимка согласий⟧
6. Ответственность и пересмотр
6.1. Владелец политики — оператор данных (⟦РЕШЕНИЕ: IL-юрлицо/DPO — владелец продукта⟧).
6.2. Политика пересматривается при изменении инфраструктуры хранения или юрисдикций.
6.3. Политика публикуется (публичный retention-график — требование BIPA-подобных законов).
Открытые решения (сводка)
- DPA с внешним провайдером обучения (Higgsfield): гарантии не-использования для своих целей, запрет передачи третьим лицам, удаление по запросу + подтверждение; механизм трансгранич. передачи биометрии.
- Судьба темп-артефактов у ИИ-провайдеров генерации.
- Отдельность отсечки 5 дней при замене soul_id vs поглощение 30-дневным сроком — подтвердить у юриста.
- Срок хранения заказов/платёжных метаданных (налоговое право IL + MoR-учёт) vs биометрии.
- Lifecycle-подстраховка поверх планировщика — при реализации E9.
- Перечень законных исключений по EU/US/IL.