← Security & Compliance Documentation

Trust Center

Customer data deletion record

Theora Customer Data Deletion Procedure: automated nightly retention via delete_expired_queries plus a 30-day on-request deletion workflow, with Cloud Logging audit trail.

Customer Data Deletion Record

FieldValue
Document titleTheora Customer Data Deletion Procedure
Version1.0
Effective date29 May 2026
Owner / Authorprivacy@theorahq.com (Privacy Lead)
Approved byEngineering Lead
Review cadenceAnnually, or on material change to the deletion workflow
Companion policyTheora Data Privacy Policy at /privacy/ (must be attached alongside this procedure as Vanta evidence)

Companion policy notice: This procedure document must be submitted together with the Theora Data Privacy Policy (public URL /privacy/). When no on-request deletions occurred during the audit window, the policy + procedure pair is the acceptable evidence for this Vanta test.

0. Document control history

VersionDateAuthorChange
1.029 May 2026privacy@theorahq.comInitial published version.

This document describes how Theora (Django 4.2 on Cloud Run, Cloud SQL Postgres, GCS media, Firebase Auth) deletes customer data both on an automated retention schedule and in response to user-initiated Data Deletion Requests. It serves as the SOC 2 evidence artifact when no on-request deletions occurred during the inaugural reporting period.

1. Automated retention and deletion

Theora ships a Django management command, delete_expired_queries, that runs nightly via Cloud Scheduler against the Cloud Run job/service in tactile-stack-491017-c0 (us-central1). It connects to Cloud SQL Postgres through the betaapp-connector Serverless VPC Access connector. Retention windows currently enforced:

  • Delivered analyses - hard-deleted 90 days after the analysis was delivered to the user.
  • Abandoned analyses - hard-deleted 180 days after the last write, covering drafts that never reached delivery.
  • Competitor sessions - hard-deleted 90 days after session close.

A 24-hour safety guard excludes any row whose updated_at is within the last day; this prevents a misconfigured clock or a backfill from wiping freshly captured queries. The job runs inside a single transaction per model and cascades through the Django ORM so dependent rows (line items, tool calls, intermediate artifacts) are removed atomically.

The relevant code path is core/management/commands/delete_expired_queries.py, and the scheduling and exception context are recorded in docs/soc2/inventory-of-resources.md and docs/soc2/risk-register.md.

2. On-request deletion (Data Subject Request)

Users exercise their deletion rights by emailing privacy@theorahq.com, the address advertised on the /privacy/ page. The workflow is:

  1. Intake - the privacy mailbox owner files an internal ticket and starts the 30-day target SLA clock.
  2. Identity verification - the requester is matched to a Firebase Identity Platform UID by confirming the request originates from the registered email on the account, and (where ambiguous) by a signed-in confirmation click. This avoids deleting another user's data on behalf of an impersonator.
  3. Database deletion - an operator runs a parameterised deletion against Cloud SQL (Django ORM .delete() on the User aggregate root, cascading to analyses, queries, sessions, and audit children).
  4. Media purge - any GCS objects the user uploaded are listed by user-id prefix and deleted from the media bucket. The bucket has Object Versioning disabled for user-uploaded prefixes so deletion is final.
  5. Confirmation - the operator sends a confirmation email to the original requester and writes an internal audit-log entry tying the request ID to the deleted user ID, row counts, and file counts.

The 30-day SLA is more conservative than the timelines in our Privacy Policy and gives room for identity-verification round-trips.

3. Audit trail

Every deletion - automated and on-request - emits a structured Cloud Logging entry through the standard Django logging chain. Sensitive fields are stripped by core.logging.RedactingFilter before egress. Each entry includes:

  • request_id - the privacy ticket ID, or auto- for the nightly job.
  • user_id - Firebase UID (not email).
  • rows_deleted - per-model counts returned by the ORM.
  • files_deleted - GCS object count for the media purge step.
  • operator - system for the nightly job, or the operator's email for on-request deletions.

Cloud Logging retention is the GCP default for the _Default log bucket; deletion events are also captured by VPC Flow Logs at the network layer and by Cloud SQL audit logs at the database layer, giving three independent observation points.

4. References

  • Public Privacy Policy: /privacy/ on theorahq.com, including the "Your data deletion rights" section that names privacy@theorahq.com and the 30-day SLA.
  • docs/soc2/inventory-of-resources.md - lists Cloud SQL, GCS media bucket, and Cloud Scheduler entries that participate in deletion.
  • docs/soc2/risk-register.md - records the residual risk of partial deletion (e.g. log retention) and the compensating controls.
  • docs/soc2/vendor-list.md - lists Google Cloud and Firebase as the sub-processors that hold customer data subject to this procedure.

5. Vanta evidence applicability

If no Data Deletion Request was received during the audit window, this procedure document plus the public Privacy Policy is the acceptable evidence and the test should be marked applicable on that basis. The test can also be supported (and is stronger if so) by a Cloud Logging screenshot of the nightly delete_expired_queries run showing a non-zero rows_deleted count - that artifact demonstrates the procedure is actually executing in production rather than merely documented. If at least one on-request deletion did occur, the redacted audit-log entry for that request is the primary evidence and this document is supporting context.