Skip to content
bitzorcas
中EN

Guide

GDPR erasure and owner orchestration

Understand cooling-off, the current Identity anonymization scope, transition gaps, legal holds, and a production owner protocol.

Last updated

The right to erasure is not a database-wide Delete. A production implementation distinguishes deletion, anonymization, statutory retention, immutable financial or security evidence, and external processor notification. It also records a provable outcome for each step.

1. Current routes

RouteCurrent behavior
POST /api/privacy/erasurecreate CoolingOff with a UTC deadline 30 days later
DELETE /api/privacy/erasure/{id}owner cancels during CoolingOff
POST /api/privacy/erasure/{id}/executeoperator rejects or anonymizes the Identity user

Create and cancel evaluate gdpr.own-data.create/delete. Execute evaluates privacy.erasure.delete. These differ from catalog value privacy.erasure.manage.

2. Cooling-off

CreateErasure writes CoolingOff(1) directly and sets CoolingOffEndsAt = now + 30 days. The SmartEnum has Submitted(0), but creation does not use it.

Create a request and display its deadline
var response = await http.PostAsJsonAsync(
"/api/privacy/erasure",
new { requestId = stableRequestId },
cancellationToken);
var request = await response.Content
.ReadFromJsonAsync<DataSubjectRequestSummary>(cancellationToken);
// The current summary does not expose CoolingOffEndsAt.
// A client cannot display the exact cancellation deadline from this response.

No background job executes or escalates an expired request. There is no deadline queue or operator reminder.

3. Transitions are not closed

ExecuteErasure rejects only Completed and Cancelled. It checks the deadline only when status is CoolingOff. Submitted, Executing, Rejected, and a wrong request type can reach anonymization.

createowner canceloperator reasondeadline elapsedidentity update succeedsCURRENTLY POSSIBLECURRENTLY POSSIBLECURRENTLY POSSIBLE

CoolingOff

Cancelled

Rejected

Executing

Completed

Submitted

A central transition guard must validate request type, source status, deadline, and expected row version.

4. Request-type confusion is release-blocking

SAR and erasure share SysDataSubjectRequest and an integer StatusCode. Execute and cancel load by ID without checking RequestType:

  • SAR Submitted(0) is erasure Submitted and can bypass cooling-off;
  • SAR Processing(1) is erasure CoolingOff and can be cancelled;
  • cross-type creation with the same RequestId can return the wrong row.

Hiding the wrong action in a UI is not a security control.

5. Actual erasure scope

GdprStore.AnonymizeUserAsync verifies that the request tenant equals Effective Tenant and invokes Identity’s IIdentityPersonalDataEraser. The implementation updates only SysUser:

FieldUpdate
UserName / DisplayNameanon-{digest}
Emailanon-{digest}@erased.local
normalized fieldscorresponding anonymous values
Phoneempty string
AvatarUrlnull
PasswordHashERASED
SecurityStampnew GUID
IsDeletedtrue

The digest is the first 24 hexadecimal characters of SHA256(tenantId:userId) and has no server secret. It is stable and useful for uniqueness, but a party that knows the IDs can recompute it.

6. Data not covered

The port does not orchestrate:

  • RefreshToken, UserSession, UserToken, ExternalLogin, or FIDO2 credentials;
  • PasswordHistory, UserDevice, LoginLog, or organization relations;
  • Files, Documents, Comments, Chat, Tickets, Billing, and other business owners;
  • search, caches, backups, analytics, or external processors;
  • other SysUser fields such as PhoneHash, confirmation flags, last login, organizational IDs, and ScimExternalId.

Changing SecurityStamp may invalidate some tokens. It is not explicit revocation of all credentials and sessions. Soft deletion is not physical destruction.

7. Transaction behavior

The command runs inside TransactionPipelineBehavior:

  1. set DSR to Executing;
  2. update the Identity user through the persistence abstraction;
  3. set DSR to Completed;
  4. commit after a successful Result.

With one database and one UoW adapter, these writes can commit atomically. Object storage, search, and external processors cannot join that local transaction. Future orchestration needs durable steps, Outbox, idempotent consumers, and recovery.

There is no Legal Hold or Retention Decision in current source. GDPR should not guess whether an invoice, order, or security record can be removed. Each owner returns a typed disposition.

Target owner-controlled erasure outcome
public interface IDataErasureContributor
{
// OwnerCode must remain stable after an execution plan is frozen.
string OwnerCode { get; }
// The owner chooses deletion, anonymization, or lawful retention.
Task<ErasureOutcome> ExecuteAsync(
VerifiedSubject subject,
ErasureExecutionContext context,
CancellationToken cancellationToken);
}
public sealed record ErasureOutcome(
ErasureDisposition Disposition, // Deleted, Anonymized, or Retained
int AffectedRecords,
string EvidenceHash,
string? RetentionReasonCode,
DateTimeOffset? RetainUntil,
bool Retryable);

RetentionReasonCode should reference a governed policy registry, not store free-form legal advice.

noyes

Subject re-proofing

Legal hold and retention preflight

Approval and cooling-off

Freeze owner set and versions

Execute per owner
idempotency + checkpoint

Every required owner
in terminal state?

Partial / retry / escalate

CompletedWithEvidence

Notify external processors

Completion means every required owner in the frozen set has a terminal outcome. It cannot mean merely that the last call did not throw.

10. Idempotency and recovery

A step key can include TenantId + ErasureRequestId + OwnerCode + ContractVersion. Persist:

  • attempt, start, and completion times;
  • a non-PII subject reference;
  • owner contract version;
  • disposition, affected count, and evidence hash;
  • retryability, next attempt, and error code;
  • operator, approval, and correlation.

Retries must not reopen Rejected or Cancelled requests and must re-check a newly introduced legal hold.

11. Test contract

The existing parity test proves that both ORMs soft-delete the Identity user and produce an @erased.local email. Missing coverage includes:

  1. SAR IDs rejected by every erasure handler;
  2. Rejected, Executing, and Submitted cannot execute;
  3. the exact cooling-off equality boundary;
  4. uniform cross-tenant ID behavior;
  5. policy when the Identity user does not exist;
  6. credential and session handling;
  7. owner partial failure, retry, and legal hold;
  8. transaction and Outbox consistency;
  9. deterministic pseudonym re-identification risk;
  10. backup and downstream processor expiry evidence.

12. Source review

Terminal window
# Inspect the exact fields changed by the current anonymizer.
sed -n '1,180p' \
src/Platform/Identity/BitzOrcas.Identity.Infrastructure/Identity/IdentityPersonalDataEraser.cs
# Prove the absence or presence of type guards and owner orchestration.
rg -n "RequestType|IDataErasureContributor|LegalHold" \
src/Platform/Gdpr/BitzOrcas.Platform.Gdpr.Application/Commands
# Inventory Identity records that still reference UserId.
rg -n "UserId" src/Platform/Identity -g '*.cs'

Back to GDPR overview

100%

Scroll or use controls to zoom · drag when enlarged · double-click for 100% / 200%