OxagenDocs
Specs & Plans

GDPR Data Erasure SOP

Archived spec & plan — status: partially shipped (audited 2026-07-03).

Status: Partially shipped — verified against the codebase on 2026-07-03 by an automated audit.

The GDPR Data Erasure SOP spec has partial implementation. The request-handling infrastructure (contract, API, MCP, UI, database, immediate session revocation, audit logging) is shipped. However, the CLI tool mentioned in section 6 was deleted during a refactor, and the Inngest hard-delete cascade function explicitly refuses to complete erasures because the full cascade (OXA-1721) is not yet implemented—the system intentionally fails loudly to avoid falsely claiming erasure while data remains.

Implementation evidence

  • /Users/macanderson/Workspaces/oxagen-platform/packages/oxagen/src/contracts/privacy.data.erase.ts — contract definition with api/mcp/agent surfaces
  • /Users/macanderson/Workspaces/oxagen-platform/packages/database/atlas/migrations/20260611233016_initial_schema.sql — database table privacy.privacy_erasure_requests
  • /Users/macanderson/Workspaces/oxagen-platform/apps/api/src/routes/v1/privacy.data.erase.ts — API endpoint implementation
  • /Users/macanderson/Workspaces/oxagen-platform/apps/mcp/src/tools/privacy.data.erase.ts — MCP tool
  • /Users/macanderson/Workspaces/oxagen-platform/packages/handlers/src/privacy.data.erase.ts — handler with session revocation
  • /Users/macanderson/Workspaces/oxagen-platform/apps/app/src/app/account/privacy/privacy-panel.tsx — user-scope UI
  • /Users/macanderson/Workspaces/oxagen-platform/apps/app/src/app/[orgSlug]/settings/privacy/org-privacy-panel.tsx — org-scope UI
  • /Users/macanderson/Workspaces/oxagen-platform/packages/inngest-functions/src/functions/privacy.erasure.execute.ts — Inngest event handler (incomplete; throws NonRetriableError)
  • /Users/macanderson/Workspaces/oxagen-platform/docs/capabilities/privacy.data.erase.md — capability documentation

Known gaps at time of archive

  • CLI command oxagen privacy erase (section 6 of SOP; was deleted in commit 6239d4e1)
  • Full hard-delete cascade in Inngest function (conversations, messages, api_keys, generated_assets; pending OXA-1721)
  • Actual completion of erasure requests (function marks as failed instead of completed)

Source documents (archived verbatim below)

  • docs/specs/gdpr/sop-data-erasure.md

Document — sop-data-erasure.md

Standard Operating Procedure: GDPR Data Erasure (Article 17)

Document ID: SOP-GDPR-001
Version: 1.0
Effective date: 2026-06-09
Owner: Oxagen Privacy Team
Review cadence: Annually, or after any material change to data processing systems


1. Purpose

This SOP defines the procedure Oxagen follows to fulfill GDPR Article 17 erasure requests ("right to be forgotten"). It covers:

  • Self-service erasure initiated by a data subject through the product
  • Operator-initiated erasure via API, MCP, or CLI
  • Support-desk erasure requests received outside the product
  • Evidence collection for GDPR audit responses

2. Scope

This procedure applies to:

  • User-scope erasure — an individual user requesting deletion of their own account and personal data
  • Org-scope erasure — an organization Owner requesting deletion of the entire organization and all associated data

Both paths are covered by the privacy.data.erase contract (see docs/capabilities/privacy.data.erase.md).


ObligationOur approach
Response within 30 days (GDPR Art. 12.3)Immediate session revocation; hard-delete within the configurable grace period (default 30 days; set PRIVACY_ERASURE_GRACE_DAYS).
Notify data processorsprivacy/erasure.execute Inngest event is the signal — subscribe downstream processors via webhook.
Retain audit trailsecurity.security_events records the privacy.erasure_requested event permanently (immutable append-only table). The audit record is retained even after data erasure.
Cannot fulfill if legal holdNot yet implemented — contact privacy@oxagen.ai to freeze an erasure request. Track in Linear.

4. Self-service erasure (data subject)

4.1 User initiates erasure via product

  1. User navigates to Account → Privacy (URL: /account/privacy).
  2. User clicks "Delete my account", reads the confirmation warning, and confirms.
  3. System:
    • Revokes all active sessions immediately.
    • Inserts auth.privacy_erasure_requests row with status = 'queued', scope = 'user', scheduledAt = now() + PRIVACY_ERASURE_GRACE_DAYS.
    • Emits privacy.erasure_requested security event.
    • Dispatches privacy/erasure.execute Inngest event with sendAt: scheduledAt.
  4. User is signed out and redirected to /login.
  5. After the grace period, privacy.erasure-execute Inngest function executes the hard-delete cascade.

4.2 Cancellation during grace period

If the user contacts privacy@oxagen.ai within the grace period:

  1. Support engineer verifies identity via the original account email.
  2. In the Inngest dashboard, cancel the pending privacy/erasure.execute event.
  3. Update auth.privacy_erasure_requests row: status = 'cancelled'.
  4. Reinstate the user account (re-enable deleted_at = null if soft-deleted).
  5. Notify the user by email.

5. Org-scope erasure

  1. Org Owner navigates to {org} → Settings → Privacy.
  2. Owner clicks "Delete organization" and confirms.
  3. Same pipeline as §4.1 but with scope = 'org':
    • All member sessions revoked.
    • privacy.org_erasure_requested security event emitted.
  4. After grace period, the Inngest function cascades deletion across all org data.

6. Support-desk erasure requests

When a request is received by email or via another channel:

  1. Verify identity. Confirm the requester is the data subject or their authorized representative. For account holders: verify via the email address on file. For org-scope: verify Owner role.
  2. Check for active account. Query auth.users for the email. If account exists, proceed to step 3. If deleted/anonymised already, provide confirmation.
  3. Initiate erasure via CLI:
    # User scope
    oxagen privacy erase --scope user --yes
    # Org scope (requires org Owner API key)
    oxagen privacy erase --scope org --org-id <uuid> --yes
    Or via API:
    curl -X POST https://api.oxagen.sh/v1/{org}/{ws}/privacy/erase \
      -H "Authorization: Bearer <api_key>" \
      -H "Content-Type: application/json" \
      -d '{"scope": "user", "confirm": true}'
  4. Record the request. The privacy.erasure_requested security event is auto-generated. Note the requestId in the support ticket for traceability.
  5. Notify the data subject. Email: "We have received your erasure request. Your data will be permanently deleted on {effectiveAt}."

7. Downstream systems

The privacy/erasure.execute Inngest event is the official signal for all downstream processors. Each subscribed system must handle this event and confirm deletion. Current integrations:

SystemHookup status
PostHog (analytics)Subscribe via webhook — pending
Linear (internal tickets)Manual anonymisation by support — pending automation
Stripe (billing)Customer data retained per Stripe legal obligations; PII anonymised in Postgres only
Vercel Blob (file storage)Cascade delete on generated_assets rows triggers storage cleanup — pending

8. Verification and audit evidence

For GDPR audit responses, produce the following evidence:

  1. Erasure request record:
    SELECT * FROM auth.privacy_erasure_requests WHERE public_id = '<requestId>';
  2. Security event (immutable):
    SELECT * FROM security.security_events
    WHERE event_type IN ('privacy.erasure_requested', 'privacy.org_erasure_requested')
    AND subject_id = '<userId>';
  3. Completion record:
    SELECT status, completed_at FROM auth.privacy_erasure_requests WHERE public_id = '<requestId>';
    -- status = 'completed', completed_at is set
  4. User anonymisation:
    SELECT id, name, email FROM auth.users WHERE id = '<userId>';
    -- name = 'Deleted User', email = '<uuid>@deleted.invalid'

9. Data retention exceptions

The following data is retained after erasure per legal obligation:

DataReasonRetention period
security.security_events rowsAudit integrity; immutable append-only7 years
Stripe billing recordsFinancial obligation under applicable law7 years
auth.privacy_erasure_requests rowProof of erasure fulfillment3 years

10. Escalation

ScenarioOwnerAction
Data subject disputes erasure was not completedPrivacy TeamRun §8 verification; re-trigger if needed
Erasure fails in Inngest (3 retries exhausted)EngineeringCheck Inngest dashboard; manually re-trigger or escalate to on-call
Regulator inquiry / GDPR auditLegal + Privacy TeamCompile §8 evidence, produce this SOP
Data subject requests erasure but legal hold appliesLegalBlock Inngest event; notify data subject of exemption with legal basis

11. Review log

DateChangeAuthor
2026-06-09Initial versionOxagen Engineering

On this page