Data Retention and Deletion Policy (EU/US/IL)
1. The principle
⇄ S56·W4 (owner's decision of 2026-10-06): the client's data now has TWO periods, both 30 days from the LAST ACTION, with one reminder schedule. This replaces the earlier timers (1) "the model — 30 days from training", (2) "frames — 30 days after delivery, delivered orders only" and (4) "source photos — 30 days from upload". The cut-off on model replacement (3) is unchanged. ⟦ДЛЯ ЮРИСТА: формулировки этого раздела наши, по изменённому факту; сверить с текстом согласия на биометрию — его новая редакция biometric-2026-10-06-eu-us-il говорит то же⟧- (1) Personal data — the Likeness model (soul_id at the external provider) + the Source photographs (with us:
uploads/+ the reduced copyanalysis/) + the profile (height, clothing size, build, gender) ⟦исправлено 2026-10-08, сверка канона B-TXT-01; юрист смотрит после⟧: 30 days after the last action with the data — uploading photos, editing the profile, paying for a shoot, a message in the order chat, "Done" after re-uploading; on the studio's side — delivering an order and a request to the client. Merely opening the account is not an action. While the client has an order on which the next step is the studio's (paid, in production, in review, a revision, an open revision request), the period is paused: production needs the model and photos. On expiry — deletion at the provider + unlinking at our end with confirmation of deletion (a log entry + the request forwarded to the provider), deletion of the photos and the profile. The account and the list of orders remain. - (2) Order data — the Results (finished frames) and the order correspondence: 30 days after the last action on the order: delivered — from delivery (an open revision request pauses the period; delivered again — a new count); awaiting the client's photos — from the operator's request; cancelled — from cancellation; an unpaid draft — from the last message (a draft has no frames). Paid / in production / in review / revision — not deleted: the next step is the studio's, the client has paid and is waiting. A new message in the chat moves the count forward. Then deletion from storage + CDN invalidation + a log; the order itself and the payment remain. ⇄ S60·W3 (owner's decision 2026-10-18, ADR-0019 "evening" refinement): a shoot under a CREATOR code with an active permission for ads — six months (180 days) from the same starting point instead of 30 days; "Extend" extends the frames and the permission together. Withdrawing the permission (account, bot /ads — for all shoots) returns the frames to the regular period: 30 days from withdrawal, never later than the date already set. "Delete now" on such a shoot deletes AND withdraws. After six months the frames are deleted and the permission ends. Applies ONLY to the order's frames and correspondence: the personal-data period (1) of the creator is not affected. ⟦FOR THE LAWYER (Ю-9): the 6-month period for ad frames — to be checked as a retention policy⟧
- Reminders — the same for both periods: 7 days and 3 days before, and on the morning of the deletion day, as one message with a list (◧ bot / ◨ email) and a notice in the account, visible from 7 days. Deletion happens at the end of the deletion day; no exact hour is promised; at least 12 hours pass between the deletion-day message and the deletion. Each line has "Extend for 30 days" (added to the deletion date, with no limit on the number of extensions; the click is recorded as a consent) and "Delete now" (with confirmation, irreversible). A period that starts or resumes less than a week before its date is set no closer than 7 days, so that all three reminders can go out.
- (3) The cut-off when a Likeness model is REPLACED — when a new soul_id is trained, the previous one is unlinked and deleted once a recovery window of about 5 days has passed (in case of an erroneous deletion).
- Storage beyond the period is possible only through an explicit extension by the owner or on a lawful basis (§5).
2. What is deleted (storage objects)
✅ THE LINE ABOUT SOURCE PHOTOS WAS BROUGHT INTO LINE WITH THE NEW FACT ON 2026-08-30 (owner's decision, S36-W2). Source photographs now DO have a period: 30 days from upload (timer 4, §1). 🗄️ The background, so that the reader understands why this paragraph is long. A day earlier this said "with us — per the retention period", and that promised something the system did not do: the S35-W6 measurement enumerated ALL object deletion paths inapps/api— not one of them deleted a photo attached to an order by age, and thePhotoGcServicesweeper only picks up ORPHANS (its "age" is a 2-hour grace period protecting uploads in flight). As an interim step the owner chose to bring the DOCUMENT into line with the behaviour (narrowing the promise), and only then named a number. ⚠️ The final legal wording is still for the lawyer to review — what is stated here is the FACT, not an approved edition.
| Category | Where it lives | Deleted |
|---|---|---|
| Source photos (user upload) | R2/S3 (uploads/ + analysis/ — the reduced copy used for analysis, which carries the same face; presigned — S1); a copy is transferred to the external provider for training | yes, the personal-data period — 30 days after the last action (§1 (1); ⇄ S56·W4, previously "30 days from upload"). BOTH prefixes are purged at once: the original and the derived copy. Plus — via "Delete now", at the client's request (a photo removed from the set), on account deletion (§7, the full cascade) and the sweeper for ORPHANED objects |
| The Likeness model (soul_id) — the biometric model | at the external provider (Higgsfield); we hold only the externalSoulId identifier | yes, the personal-data period (§1 (1)) / extension / "Delete now"; the deletion request is forwarded to the provider and confirmed |
| Results (generated images) | R2/S3 — `results/`, `redress/` AND `delivered/` (the redressed frames — the ones that go to the client; delivered/ — a copy of the delivered set, S35-W5) + CDN, delivered only via presigned links | yes, the order-data period — 30 days after the last action on the order (§1 (2); ⇄ S56·W4: for ALL orders per the boundary, not only delivered ones), all prefixes with `retention: 'delivery'` in the registry apps/api/src/s3/client-data-namespaces.ts — the list lives there, not here |
| The previous soul_id after a replacement | at the provider + the identifier with us | yes, once the window of about 5 days has passed (timer 3) |
| Intermediate generation artefacts | ⟦РЕШЕНИЕ: кэш/темп ИИ-провайдеров — уточнить у Наташи (N1) и провайдеров⟧ | yes / per the contract with the provider |
| Order records | Postgres (orders, S1) | on account deletion — deleted in full (§7). During the ordinary lifecycle — ⟦срок по налоговому/бухгалтерскому праву IL — юрист⟧ |
| Payment records | Postgres (payments) | on account deletion — NOT deleted, de-identified (unlinked from userId/orderId; the amount / currency / date / txn remain), on the basis of GDPR Art. 17(3)(b) + tax retention (§7) |
| The consent log | Postgres (consents) | on account deletion — the records are deleted, but a PII-free snapshot (type / version / date) is retained in the deletion record as proof of consent (§7) |
| Order correspondence (client ↔ studio chat) | Postgres (order_messages) | yes, the same order-data period as the Results (§1 (2); product owner's decision of 2026-08-20, requirement R-10; ⇄ S56·W4 — for all orders per the boundary, for a draft — from the last message). Implemented: OrderRetentionService.purgeOrderData. On account deletion — deleted in full (§7), and the count of erased messages goes into the deletion record |
| The log of deletion facts (deletion receipt) | Postgres (deletion_audits) | retained (to demonstrate compliance with the policy); contains no PII and no biometrics: a hash of the id + counters + a snapshot of the consents |
⚠️ Deletion must reach every copy: the object in R2/S3, the CDN cache (invalidation), and any temporary files at the AI providers. Deleting only from the database while the S3 object is still alive is not enough.
3. Period, starting point, extension
3.1. The periods (§0h, ⇄ S56·W4 2026-10-06): 30 days after the last action — for personal data (the model, source photos, profile) and for order data (frames, correspondence); the starting points are in §1. About 5 days — the recovery window when a soul_id is replaced.
3.2. The starting point of the 30 days is the last action (owner's decision of 2026-10-06, §1). ⟦ДЛЯ ЮРИСТА: прежде здесь стояла открытая развилка «от обучения / от выдачи / от последней активности»⟧
3.3. Extension is opt-in, by a click from the reminder or from the account: +30 days added to the deletion date, with no limit on the number of extensions (owner's decision of 2026-10-06); it extends the line on which it was clicked — personal data or a particular order's data; the extension is recorded as a consent. 3.3a. "Delete now" — on the same line, after confirmation; irreversible; the account, orders and payments remain. A V2 subscription is a separate future mode of permanent storage (outside the MVP).
3.4. The discount on a repeat session (Phase 0) does not by itself extend storage.
4. Requirements for automatic clean-up (for implementation, NOT in this window)
Stated as requirements for a future task (E4/E9/E10; the canonical tool is the retention scheduler, layer 4 of Phase 1):
- R-1. An automatic scheduled process (scheduler jobs on top of the workflow engine / cron) that deletes objects older than the period — with no manual involvement by an operator. ⇄ S56·W4 (owner's decision of 2026-10-06, by a click): while the client has an order on which the next step is the studio's (paid, in production, in review, a revision, an open revision request), the personal-data period IS PAUSED — otherwise the model would be deleted in the middle of a paid shoot. The earlier S36-W2 clarification ("timer 4 makes no exception for an unfinished order") is replaced by this decision. "Awaiting the client's photos" does not pause the period — the next step is the client's.
- R-2. A single source of an object's "age" (an upload / delivery timestamp in the database or an S3 object tag).
- R-3. ✅ DONE (2026-08-20). The cascade: on deletion, remove the object in R2/S3, invalidate the CDN, and mark it in the database. Before that date the invalidation was a LOG STUB ("the cache will expire by TTL"). Implementation —
CdnService.invalidatePrefix(apps/api/src/s3/cdn.service.ts), enabled by theCLOUDFRONT_DISTRIBUTION_IDvariable. ⚠️ Honestly about the current state: there is currently NO cache in front of the storage — personal objects (results and input photos) are served by a presigned link straight from S3, andCDN_BASE_URLis not set in production. So the requirement is closed FOR THE FUTURE: on the day CloudFront is put in front of the bucket, the cascade will not be silently incomplete. While the distribution id is unset but a cache is detected, every run writes an error to the log — "nothing to invalidate" and "invalidation broke" must not look the same. - R-3a. For the Likeness model (soul_id): forwarding the deletion request to the external provider plus receiving and storing confirmation of the deletion (§0h: proof of deletion at the sub-processor).
- R-4. Early deletion on the user's request (withdrawal of consent — see the biometric consent, §7; account deletion — §5a): the same cascade, without waiting for the period to expire. For account deletion — immediately after confirmation, with no cancellation window (§5a).
- R-5. Idempotency and logging of the fact of deletion (what was deleted and when) — to demonstrate compliance with the policy (BIPA/GDPR accountability); the log itself must not contain biometrics.
- R-6. Handling deletion errors (retries, alerting) so that "stuck" objects are not stored longer than the period.
- R-7. ✅ IMPLEMENTED (S56·W4, 2026-10-06). Reminders 7 days and 3 days before, and on the morning of the deletion day — one message with a list per person (◧ bot / ◨ email) and a notice in the account, with "Extend for 30 days" and "Delete now" on each line; on silence — deletion at the end of the day (R-3a for the model). Implementation —
RetentionWorker(apps/api/src/notifications/retention-worker.service.ts). Before S56 there was a single reminder "3–5 days before", about the model only. - R-8. ✅ DONE (2026-08-30, S36-W2). A warning about the retention period for the Results at the moment of delivery. This used to read "NOT done as of 2026-08-29": the reminder job (R-7) counts from
Soul.expiresAt, i.e. it guards timer 1, whereas the Results had no warning either at delivery or on a timer. What was done: the delivery message in BOTH channels (◧ Telegram · ◨ email) names the DATE until which the frames are available. The date is computed fromorder.deliveredAt— from THE SAME value that anchors timer 2, so the date promised to the person coincides with the date on which the frames are actually erased. The text and the date format are shared between the channels (framesStoredUntilNote,@fromframe/types); otherwise the bot and the email would name the same day in different words. ⚠️ A date rather than "up to 30 days": a person has no way to check a period expressed in days, but they can check a date. - R-10. ✅ DONE (2026-08-20). Order correspondence (
order_messages, S32) lives for 30 days after delivery (⇄ S36 2026-08-29, it used to be 90) — the same timer 2 as the Results: it contains free text written by the client and discusses those very frames, so it must not outlive them. The period was chosen by the product owner from two options, and the accepted cost was stated plainly: the correspondence disappears together with the frames it relates to, and cannot be kept separately. Implementation —SoulLifecycleWorker.purgeOldMessages(apps/api/src/soul/soul-lifecycle.worker.ts), a pass overDELIVEREDorders older than the period that still have correspondence. ⚠️ The count runs from `order.updatedAt` of a terminal order, as it does for the frames: the schema has no separate "delivery date" field. As a result the correspondence is cleaned EARLIER than the frames within one pass — clearing the frames itself movesupdatedAt. The analysis is in the docblock oftick(). ⚠️ Before that date the period stood here as a REQUIREMENT for a future implementation, not as a promise: the policy is public, and an announced period that nobody implements is one more unkept promise. Now the implementation exists, and staying silent would be worse than announcing it. - R-9. The cut-off when a soul_id is replaced (§0h timer 3): the previous model is marked for deletion with a recovery window of about 5 days, after which the R-3/R-3a cascade runs. In the database:
Soul.replacedAt/purgeAfter(F1). - The mechanism: scheduler jobs (build/reuse on top of the workflow engine) — they cascade through the database and the CDN and cover reminders, extensions and early withdrawal; an S3 lifecycle rule is an optional bucket-level backstop. ⟦РЕШЕНИЕ: включать ли Lifecycle-подстраховку — при реализации E9 — владелец продукта + инфра⟧
5. Lawful exceptions to automatic deletion
Storage beyond the period is permitted only on a lawful basis, for example:
- an active dispute, claim or refund (until it is resolved);
- statutory requirements (tax and accounting records — the jurisdiction of the IL legal entity; sales accounting on the Lemon Squeezy side as Merchant of Record). On account deletion this exception is implemented as the de-identification of payment records (§7): the financial record is kept without any link to an identity. ⟦РЕШЕНИЕ: перечень исключений и их сроки по EU/US/IL — юрист⟧
The exception concerns de-identified payment records, not biometrics: biometrics (photos + soul_id) are deleted in every case. Order records are deleted in full on account deletion — only the de-identified payment is retained on tax grounds.5a. Account deletion — "the right to be forgotten" (implemented, spec 002)
Separately from the retention timers in §1, there is an explicit account deletion request:
- Trigger: ◨ the User from the web personal account; a support operator — through the same service. A single identity — the deletion covers both channels (bot + web).
- Immediate erasure, no cancellation window: after confirmation the account is blocked (403 on protected endpoints) and the irreversible purge starts immediately; there is no cancellation. If a shoot step is running at that moment (a paid call has already been sent to the provider), it is allowed to finish (minutes), no new steps start, and then everything is erased.
- Purge: S3 — ALL client-data namespaces per the registry
apps/api/src/s3/client-data-namespaces.ts(uploads/·analysis/·results/·redress/·delivered/·souls/), the soul_id at the provider (forwardingdeleteSoul+ confirmation, idempotently; a failure of the external call → escalation without losing the record), and the database in a single transaction: de-identifypayments→ deleteconsents/orders/souls/orderPhoto/otp/sessions→ tombstone theUser(PII = null,deletedAt) → write adeletion_auditsentry (a hash of the id + counters + a PII-free snapshot of the consents). - What survives the purge: de-identified payments (§5, tax), the deletion receipt (evidence), and the snapshot of the consents inside the receipt (proof of consent, BIPA/GDPR accountability). All of it without PII. ⟦НА РЕВЬЮ ЮРИСТУ ПЕРЕД ПРОДОМ: отсутствие окна отмены при удалении аккаунта; полнота списка стираемых PII-сущностей; достаточность обезличивания платежей и снимка согласий⟧
6. Ownership and review
6.1. The owner of this policy is the data controller (⟦РЕШЕНИЕ: IL-юрлицо/DPO — владелец продукта⟧).
6.2. The policy is reviewed whenever the storage infrastructure or the jurisdictions change.
6.3. The policy is published (a public retention schedule is a requirement of BIPA-style laws).
Open decisions (summary)
- The DPA with the external training provider (Higgsfield): guarantees of non-use for its own purposes, a ban on transfers to third parties, deletion on request + confirmation; the mechanism for cross-border transfer of biometrics.
- The fate of temporary artefacts at the generation AI providers.
- The starting point of the 30 days (from training the soul_id / from delivering the order).
- The length and number of opt-in soul_id extensions.
- Whether the 5-day cut-off on soul_id replacement stands separately or is absorbed by the 30-day period — to be confirmed with the lawyer.
- The retention period for orders / payment metadata (IL tax law + MoR accounting) versus biometrics.
- A lifecycle backstop on top of the scheduler — when E9 is implemented.
- The list of lawful exceptions for EU/US/IL.