# SEC-009 account export and deletion lifecycle

Updated: 2026-08-15

Status: `IMPLEMENTED — NOT VERIFIED`. Generated-output ownership, readable export, and the dark-launched deletion lifecycle are implemented and locally verified. Production activation remains blocked by retention and restore policy.

## Owner inventory boundary

The authoritative inventory must include Identity-safe account/profile fields and roles; companies, opportunities, applications and all workspace children; correspondence/events/attachments; Career Profile and versions/children; CV variants/versions/artifacts/extraction runs; AI notes/interactions/operations/notifications; email drafts/send metadata; provider connection metadata; rules; sessions/trusted-device metadata; and owned files. It must exclude password/security hashes, TOTP/recovery/token hashes, OAuth tokens, IMAP passwords, data-protection keys and global settings.

## Checkpoint 1 — owner-scoped generated files

- `AppPaths.GetOwnerStorageKey` provides one opaque SHA-256 owner directory key.
- CV PDF exports now write under `CvExports/<owner-key>/<yyyyMMdd>/<uuid>.pdf`. The friendly renderer filename remains the download name, while the stored UUID prevents collisions and unsafe path influence.
- Daily exports now write under `exports/<owner-key>/daily_export_<yyyyMMdd>.json` with the existing atomic temporary-file move.
- The PDF exporter receives the authenticated/public-variant owner explicitly from every controller, including anonymous public download after slug ownership resolution.
- Retention prunes both legacy top-level date directories and new owner/date directories. Unknown folders remain untouched.

No existing generated file is moved or guessed. Legacy shared-date outputs stay a separately reviewed rollout concern because they cannot be attributed safely.

## Checkpoint 2 — complete readable export

- Authenticated `POST /api/export/account` requires the current local session to have been created within the last 15 minutes and is limited to two requests per user per hour.
- One service owns both the authoritative row inventory and file inventory. It queries with explicit owner predicates and `IgnoreQueryFilters`, so soft-deleted applications remain portable and an absent/requestless tenant scope cannot silently empty the export.
- The ZIP contains readable account, company, opportunity, application, correspondence, event, attachment, Career, CV, workspace, AI operation, notification, settings/provider and security-metadata JSON categories.
- Owned attachment, CV upload, avatar, generated-CV and daily-export bytes are included only after managed-root/reparse-point checks. Missing or unsafe files produce manifest warnings rather than cross-root reads.
- `manifest.json` records schema version, generated time, category/item counts, byte sizes and SHA-256 checksums for every included entry. `README.txt` explains formats, exclusions and retention limits.
- Password/security/concurrency hashes, TOTP secrets, recovery/trusted-device hashes, session IDs, provider access/refresh tokens, IMAP passwords, operation leases, email payload hashes, global settings and data-protection keys are never serialized.
- The Settings Backup tab presents the readable export separately from the application-key-encrypted operational backup and explains recent sign-in without weakening the API rule.
- Temporary ZIPs live under an opaque owner root and are opened with delete-on-close when returned by the controller.

## Checkpoint 3 â€” disabled, retryable deletion lifecycle

- Additive Identity status plus durable request/file-ledger tables track request, stage, retry, file checksum, row count, warnings, and sanitized failure state. SQLite is EF-generated; the MariaDB migration uses explicit bounded types and its generated script was reviewed.
- `AccountLifecycle:DeletionEnabled` is explicitly `false` by default. Both self-service and admin requests fail safely while disabled; the old admin Identity-only delete path has been removed.
- A valid request immediately marks the account pending, rotates its security stamp, revokes sessions and trusted devices, unpublishes public CVs, cancels queued work, and requests cancellation of running work. Pending users cannot sign in, complete 2FA, or reuse an existing local session.
- Self-service requires an exact server-provided `DELETE <email>` phrase and a session created within 15 minutes. Last-administrator protection remains enforced. Admin deletion uses the same coordinator and exact-email confirmation header.
- One managed-root inventory covers attachments, CV artifacts, file-backed avatars, generated CVs, daily exports, and previously generated account-export ZIPs. Files move to same-volume quarantine markers before any database delete; partial file failure restores them and leaves rows untouched.
- Database deletion is explicit and transactional across all owned application, Career, CV, correspondence, provider-credential, queue/notification, security, and Identity rows. Request/file ledgers survive for retry and audit. Commit-acknowledgement ambiguity leaves files quarantined and replays deletion instead of risking data resurrection.
- Purge clears backend in-memory caches, removes quarantined files, and calls the authenticated AI-sidecar maintenance endpoint before it can write a minimal pseudonymous tombstone. A sidecar failure leaves the durable request at `purging_files` for retry rather than falsely completing. Invalid ledger records fail closed.
- Production Compose maps the tombstone root to the separate `jobtracker_deletion_tombstones` named volume and exposes only the disabled-by-default `ACCOUNT_DELETION_ENABLED` switch. This is repository configuration evidence, not proof that the volume exists or is protected on production.
- Startup stages restored identities matching tombstones before readiness, and the background reconciler resumes all durable non-completed requests even while new deletion requests remain disabled.
- Settings explains the disabled production gate; when enabled it uses the reusable prompt dialog and exact phrase. Admin user deletion supplies the matching account email.

## Verification

- Owner-storage focused CV/export/controller/background tests: 77/77.
- Account lifecycle focused backend/API tests: 6/6. The new failure test proves a sidecar outage prevents completion/tombstone creation and a later retry succeeds. The broader lifecycle/export/auth/admin slice remains 21/21 from the prior checkpoint.
- Full backend: 658/658.
- AI sidecar: 23/23, including authenticated cache purge; cache access is lock-protected.
- Production Compose configuration: valid with a distinct tombstone volume and deletion disabled by default.
- Frontend export/Settings/admin tests: 8/8; full frontend 58 suites/237 tests.
- Backend build: pass, zero warnings/errors.
- Optimized frontend build/TypeScript: pass.
- EF model parity: no pending model changes. MariaDB migration script generation: pass with bounded Identity/lifecycle schema and indexes.
- Chromium: full disposable startup/application suite 9/9; fresh Free account receives a real ZIP response with a `PK` signature and readable-export success state.
- `git diff --check`: pass aside from line-ending notices.

## Remaining external/production work

1. Decide backup, audit/security-log, quarantine, and tombstone retention plus any legal-hold obligations.
2. Deploy and protect the separately configured tombstone volume, then rehearse a pre-deletion backup restore with tombstone replay.
3. Rehearse the implemented sidecar cache purge across container restart and define remote provider-revocation semantics using a disposable synthetic account.
4. Only then enable admin deletion, observe it, and separately approve self-service activation.

Production retention, legal hold and restored-backup decisions remain recorded in `BLOCKERS.md`.
