Deletion deserves a receipt

Account erasure became a durable cross-store workflow whose evidence proved what ran without storing the removed payload again.

The first deletion job returned 200 OK before it deleted anything.

The endpoint accepted a synthetic account ID, placed a message on a queue, and responded with { deleted: true }. A worker later removed the main account row. It did not know about analytics events, saved drafts, preview identities, derived indexes, cached exports, or backup restoration.

The response was fast, simple, and false.

Changing it to { deletionQueued: true } would improve the wording and leave the deeper problem. An erasure request is not one database call. It is a durable workflow across stores with different APIs, failure modes, and legitimate exceptions. The system needs to know what it discovered, which steps completed, what remains, and whether retry is safe.

It also needs evidence after the data is gone.

That requirement creates a trap. A deletion log that copies the account, event properties, paths, and values can recreate the very record it claims to remove. Q2F8 needed a receipt proving scope and outcome without becoming a shadow archive.

The request created an identity, not a claim

The revised endpoint accepted a request and returned a stable workflow identity:

{
  "requestId": "erase_01c…",
  "state": "accepted",
  "requestedAt": "2018-09-18T14:02:19Z"
}

accepted meant the request had been durably recorded. It did not mean discovery, deletion, backup handling, or verification had finished.

Repeated submissions for the same synthetic subject and policy scope joined the active workflow rather than creating parallel erasures. A completed request could return its prior receipt unless a later collection period created new eligible data, in which case a new request referenced the previous one.

The request stored subject reference, policy version, requested scope, origin, and authentication evidence appropriate to the experiment. It did not snapshot all current account data.

Identity made progress queryable and retryable. The API could stop using transport success as a statement about world state.

The first truthful response was smaller than the original: the system had promised only to begin accountable work.

Discovery preceded deletion

Q2F8 registered every store that could hold subject-linked data:

account profile fixture
event purpose stores
saved publication drafts
preview identities
search projection
export cache
preference and admission receipts
backup tombstone registry

Each store implemented a discovery contract returning opaque record groups, applicable policy, deletability, and any exception. Discovery did not return payload values to the orchestrator.

store: measurement-events
group: partition-2018-09 / subject-key-7b…
count: 12
action: delete
adapterVersion: 3

The orchestrator created an immutable plan from the discovered groups and current registry generation. If a store registered after planning but before completion, verification detected the changed generation and required discovery there before the workflow could claim complete.

This addressed a common weakness in “delete everywhere” code: everywhere is usually a hard-coded list that silently becomes incomplete as architecture changes.

The store registry made scope explicit and testable.

Store adapters owned local semantics

The orchestrator knew workflow state. It did not know how to erase every database, index, or cache safely.

Each adapter defined:

discover(subjectRef)
delete(groupRef, operationId)
verifyAbsent(groupRef)
describeException(groupRef)

The profile adapter deleted relational rows in a transaction. The analytics adapter used purpose partitions and admission identities. The search adapter removed documents and waited for an index generation. The export cache invalidated objects by opaque key. The backup adapter wrote a deletion tombstone for restore-time replay rather than rewriting every archive immediately.

The contract distinguished logical absence from physical cleanup where necessary. A cache could stop serving data before object deletion propagated. The receipt named both states.

Adapters had narrow credentials. The analytics adapter could delete by registered subject mapping but could not browse arbitrary payloads. The orchestrator received counts and receipt identities, not the data being removed.

Local ownership prevented one universal deletion query from requiring unrestricted access across the whole experiment.

Steps had stable operation identities

A worker can lose its response after a store completes deletion. Retrying a destructive call without identity can produce errors, inconsistent counts, or unintended broader effects.

Each plan step received a stable operation ID derived from erasure request, store, group, and adapter version:

delete:erase_01c:measurement-events:partition-2018-09:adapter-v3

The adapter stored or derived an outcome by that identity. Repeating the same operation returned the previous receipt when possible. A “not found” result was not automatically treated as prior success; it needed discovery identity or a verification path showing the target scope was absent.

The workflow states were:

planned -> executing -> effect-known -> verified
                   -> failed-retryable
                   -> outcome-unknown
                   -> exception-review

outcome-unknown mattered. A timeout did not prove failure, and issuing a broader second delete could be unsafe. The orchestrator paused that step and asked the adapter to reconcile through operation identity or absence verification.

Deletion inherited the same reliable-effect discipline that X8B6 had taught for financial operations.

The receipt recorded evidence, not content

A store deletion receipt contained:

erasure request ID
store and adapter version
opaque group identity
operation ID
policy authority
started and completed times
records or objects affected as a count
verification method and result
exception code, if any
receipt digest and predecessor

It did not contain names, email addresses, draft titles, event properties, document bodies, or deleted rows serialized for audit.

Opaque group identities were derived so they remained useful for deduplication and investigation without serving as reversible subject identifiers outside controlled mapping. Receipt access was narrower than ordinary job logs and followed its own retention policy.

Counts helped detect incomplete or unexpectedly broad work. They were not proof by themselves. Verification recorded the query or index condition used to establish absence under the same group scope.

The receipt answered “which authorized operation ran against which registered store, and how was absence checked?” It did not answer “what was every deleted value?”

Evidence became a compact account of effect rather than a duplicate dataset.

Verification used independent reads

Trusting the delete method's affected-row count would let an adapter assert its own success. Q2F8 followed deletion with an independent absence check through the store's normal read or discovery path.

For relational data, verification queried by the subject mapping and policy scope. For search, it waited until the deletion operation's generation was visible and then queried the index. For cache, it used the same public lookup route the application would use. For object storage, it checked key absence or deletion marker under bounded consistency rules.

Verification had a deadline and could return unknown. Eventual propagation did not become immediate success. The workflow could say:

primary rows deleted
search projection pending
cache unavailable for verification

This prevented one subsystem's completion from being summarized as account-wide deletion.

The independent read still relied on the same underlying store and could share defects. Synthetic canaries and registry-wide tests added another evidence layer. Perfect proof of nonexistence is difficult; the project stated the bounded paths it had verified.

Deletion receipts became credible by naming their verification limits.

Exceptions were first-class states

Not every record followed the same deletion rule. Some narrow receipts needed temporary retention to demonstrate preference or deletion workflow state in the experiment. An active security investigation fixture could place a hold on a specific synthetic group. Backup copies followed an expiring archive policy rather than live rewrite.

The first system marked any skipped store as success with a log note. The revised workflow required a structured exception:

exception type
policy authority and version
scope
reason category
review or expiry time
access restriction
next action

An exception could not be generic “required for compliance.” The software could enforce a declared exception but could not determine whether the legal basis was correct; this personal project used synthetic policy decisions and kept that limitation visible.

The user-facing status named that some evidence remained, at what category and until when, without exposing sensitive detail. An expired exception returned the workflow to active deletion.

Complete meant every registered group was verified absent or covered by a current explicit exception. “Best effort” was not a hidden success state.

Deleting every preference receipt would make it impossible to explain why past optional events had been admitted or when withdrawal became effective. Keeping subject-linked receipts forever would create another indefinite identity store.

Q2F8 separated event payload identity from minimal policy evidence. After account erasure, the receipt store removed direct subject mapping and retained only what the synthetic policy required for a bounded period: receipt chain identity, policy version, categories, effective times, and erasure request reference.

The mapping transformation had its own receipt and verification. The retained evidence could not be used through normal application paths to reconstruct the deleted profile.

This was closer to pseudonymization than guaranteed anonymization, so the project did not relabel it as anonymous. Access and retention obligations remained.

The design demonstrated a difficult balance rather than claiming universal correctness. Audit need is not a license to preserve every original identifier. Erasure is not an excuse to destroy the only evidence of whether a policy workflow ran.

The minimal evidence set had to justify itself just as event data did.

A background integration could recreate account-linked data after discovery but before verification. Deletion would finish against the original plan and leave the new record.

Accepting the erasure request moved the synthetic subject into an erasure-pending state at the admission boundaries. Optional collection stopped. Application mutations requiring the account were rejected or redirected into the closure flow. Store adapters checked the erasure generation before creating subject-linked records.

This was not a global database lock. It was a policy state every registered write boundary had to enforce.

Verification performed a final discovery under the current registry and erasure generation. If new eligible records appeared, the plan appended deterministic steps before completion. Repeated recreation attempts surfaced as integration errors.

The account identity remained only as long as needed to prevent resurrection and coordinate deletion. Completion then removed or transformed that control record according to policy.

Erasure required constraining future writes, not only deleting past ones.

Backups received tombstones and restore gates

Live stores could delete promptly. Encrypted backups were retained for a finite disaster-recovery window and were not rewritten per request.

The workflow created a tombstone with subject mapping digest, affected store classes, erasure receipt, and expiry. Backups remained inaccessible to normal application and analytics paths. A restore environment had to replay unexpired tombstones and current retention deadlines before it could serve traffic.

The restore test proved the sequence:

restore backup containing synthetic account
load deletion tombstones
re-run registered store adapters
verify absence
only then mark restored environment eligible

The erasure status distinguished live completion from maximum backup persistence. It did not say every byte vanished instantly.

Tombstones themselves had a lifecycle. After all backups capable of containing the subject expired and verification completed, the subject-specific tombstone could be removed or reduced to non-identifying aggregate workflow evidence.

A backup is not exempt data because it is inconvenient to query. It needs policy, access restriction, expiry, and a recovery path that does not resurrect erased state.

Erasure ran through durable state rather than one process. After each transition, the orchestrator stored plan generation, step status, operation identity, and receipts.

If the worker restarted, it resumed from evidence:

  • Planned steps with no attempt could begin.
  • Steps with known receipts proceeded to verification.
  • Retryable failures followed bounded backoff.
  • Unknown outcomes reconciled before new effects.
  • Exceptions waited for review or expiry.

It did not replay completed deletes merely because in-memory state was gone.

Progress was monotonic under the plan generation. Registering a new store created a new discovery generation without rewriting completed receipts. A policy correction appended a decision record.

This durability mattered because deletion is often infrequent. A process path exercised only during account closure should not rely on a perfect uninterrupted run.

The receipt chain served as both audit and recovery state.

User-facing status matched workflow truth

The first interface said “Your account has been deleted” after queueing. The revised status had concrete phases:

Request received
Locating associated data
Removing active data
Verifying removal
Complete
or
Needs attention: named retained exception or unresolved store

The page did not display internal table names or opaque operation detail. It grouped stores into understandable domains such as account profile, saved content, measurement data, and recovery copies.

Completion time, request identity, and any exception window were available. The user did not need to keep the page open; the workflow was durable. For the synthetic experiment, a notification could be captured locally rather than sent to a real address.

If verification was unknown, the interface did not convert delay into success. It also avoided implying that a transient error meant nothing had been deleted.

Product language followed the state machine instead of smoothing it into a binary promise.

Tests interrupted every boundary

The erasure suite generated synthetic subjects with records in every registered store, then injected failures:

  • Worker crash before and after a store effect.
  • Lost adapter response after successful deletion.
  • New store registration during the workflow.
  • Record recreated after initial discovery.
  • Search projection delay.
  • Backup restore before tombstone replay.
  • Expired exception.
  • Adapter version change during an active plan.
  • Receipt write failure after store commit.

The tests asserted durable state, retry identity, absence checks, user-facing status, and no payload leakage into receipts or logs.

A registry completeness test required every subject-linked store fixture to register discovery and deletion behavior. Code review could not add a new store without selecting an erasure policy.

Property-based sequences explored discover, delete, crash, retry, verify, and register operations. The invariant was:

Complete implies every current registered scope is either
verified absent or covered by an unexpired explicit exception.

The suite made a rare workflow ordinary enough to trust.

Monitoring looked for stuck promises

Operational measures included:

age of oldest active erasure request
time in each workflow state
unknown outcomes
expired exceptions
stores failing verification
new writes rejected during erasure
restore environments awaiting tombstone replay
receipt/log payload-safety checks

Counts alone could not reveal a single request stuck for weeks. The oldest request age and state duration carried more meaning.

A synthetic canary created a test subject across all stores, requested erasure, and verified completion on a short schedule. Another restored a backup fixture and checked resurrection prevention.

Alerts linked receipts and operation identities, not deleted content. Investigation access remained scoped.

Monitoring existed because a deletion API can keep accepting requests while its least healthy adapter accumulates unresolved work. Queue depth is not the same as promise completion.

The system observed the lifecycle at the level users had been told it would complete.

The final receipt had a limited claim

The aggregate erasure receipt summarized:

request and policy identity
registry and plan generations
store step receipts and digests
verified completion times
current exceptions, if any
backup maximum persistence window
final workflow state

It did not claim universal deletion from any system that could possibly contain a copy. It named the registered active stores, recovery process, and verification methods within the experiment.

If an unregistered manual export existed, the receipt would be incomplete. The architecture reduced that risk through narrow export capabilities and a registry, but the limit remained part of the design.

The project therefore avoided language such as “all traces permanently erased” unless the evidence actually supported it. verified complete for registered active stores; backup restore gate remains through date was less marketable and more truthful.

Evidence is strongest when its boundary is explicit.

Deletion was a product feature and a distributed effect

The original endpoint treated erasure as a side effect after the account feature. Q2F8 treated it as a first-class workflow with its own identity, state, recovery, interface, and evidence.

The implementation borrowed lessons from earlier projects. X8B6 contributed stable operation identity and unknown outcomes. R7K1 contributed resource discovery and lifecycle ownership. J05N contributed explicit contracts and migration. Q2F8 applied them to the system's obligation to forget.

The final rule was simple to state and difficult to fake:

Do not say deletion is complete until every registered store has produced bounded evidence of absence or a current explicit exception, and do not preserve the deleted content in the evidence.

A receipt cannot prove metaphysical nonexistence. It can make the system accountable for the paths it controls and prevent retries, process crashes, and vague logs from turning a strong promise into guesswork.

Deletion deserves a receipt because the data's absence becomes important state. The receipt exists to explain that state—not to keep a secret copy of what is gone.