Appearance
ADR: Data deletion policy v1
Status
Accepted
MHPAY-82 implements the backend service-level hard-delete preflight for unreconciled external posting risk, exact typed confirmation, and safe tombstone metadata. MHPAY-85 adds a conservative HardDeletePurgeWorker that executes only already approved PendingHardDelete Pay-owned records. Public hard-delete API and UI affordances remain out of scope until a separate implementation task adds them.
Date
2026-06-25
Context
MyHoreca Pay stores financial workflow data: bank imports, normalized transactions, counterparty matches, allocation decisions, Syrve/iiko mappings, posting jobs, reconciliation results, anomalies, notifications, evidence, and audit. Deletion must be designed before implementation because destructive operations can break auditability, tenant isolation, reconciliation, and financial investigation.
The product integrates with Syrve/iiko, but Pay deletion policy applies only to Pay-owned data. Hard delete in Pay must never delete, roll back, update, or otherwise mutate data in Syrve/iiko. External systems remain the source of their own records.
This ADR defines the lifecycle for soft delete, restore, pending hard delete, manual hard delete, audit tombstones, unfinished jobs, and the special case where Pay data references an unreconciled external posting.
Decision
MyHoreca Pay accepts a conservative destructive-operation policy:
- Soft delete is the default user-facing delete behavior.
- Soft delete has a restore window of 5 days.
- After 5 days, the record or aggregate moves to
PendingHardDelete. - Restore is allowed only from
SoftDeletedand only within the 5-day window. - Hard delete is manual only.
- Hard delete deletes only Pay data and never mutates Syrve/iiko.
- Delete reason, actor, scope, audit event, and correlation/request id are required for destructive operations.
- Audit tombstone is retained after hard delete.
- Unreconciled external posting requires a dedicated hard-delete preflight, a red warning, and typed confirmation.
- Unfinished jobs must not continue against hard-deleted Pay data and must move to
CancelledByHardDelete.
Deletion lifecycle
| State | Meaning | Allowed next states |
|---|---|---|
Active | Record or aggregate is available for normal workflow. | SoftDeleted |
SoftDeleted | Record or aggregate is hidden/disabled but retained for restore and investigation. | Restored, PendingHardDelete |
Restored | Record or aggregate was restored from SoftDeleted within the 5-day window and returns to active workflow state. | Active, then later SoftDeleted if deleted again |
PendingHardDelete | Restore window expired or an authorized actor manually marked the aggregate for final deletion review. | HardDeleted |
HardDeleted | Pay-owned data was manually hard-deleted after preflight; audit tombstone remains. | Terminal |
CancelledByHardDelete | Unfinished background/outbox/posting work was cancelled because the owning Pay data was hard-deleted. | Terminal for that job |
Lifecycle:
text
Active
-> SoftDeleted
-> Restored -> Active
-> PendingHardDelete
-> HardDeleted
Unfinished job
-> CancelledByHardDeleteRestored is an audit/lifecycle event and may also be represented as the restored aggregate returning to Active. Future implementation must make the restore event queryable for audit.
Permissions
Destructive operations must be scoped by tenant boundaries and must respect the MVP role matrix.
| Role | Soft delete | Restore | Move/initiate hard delete | Confirm hard delete with unreconciled external posting | Scope |
|---|---|---|---|---|---|
MegaAdmin | Allow | Allow | Allow | Allow | Platform-wide, all organizations |
MegaManager | Support/read only by default | Support/read only by default | Deny | Deny | Platform support scope only |
OrganizationAdmin | Allow for own organization, child organizations, and Outlets | Allow within restore window for own organization scope | May request/mark for review only if implementation adds that workflow; final hard delete confirmation remains denied by default | Deny by default | Organization and inherited child organizations |
Accountant | Deny for admin deletes; may cancel own workflow drafts only if a future ADR allows it | Deny | Deny | Deny | Assigned workflow scope |
Viewer | Deny | Deny | Deny | Deny | Read-only assigned scope |
Rules:
MegaAdminis the only MVP role allowed to perform final manual hard delete.MegaManagercan support investigation but cannot approve destructive operations.OrganizationAdmincan soft delete and restore organization-scoped Pay data inside its owned/inherited organization scope, subject to implementation-specific entity rules.OrganizationAdmincannot hard delete data with unreconciled external postings.- Cross-tenant delete or restore attempts must be denied.
- Every destructive action must write audit with actor, role, effective scope, inherited-admin context when relevant, entity type/id, reason, previous state, new state, and correlation/request id.
Restore policy
Restore returns Pay data from SoftDeleted to active workflow state.
Restore rules:
- Restore is allowed only within 5 days after soft delete.
- Restore is impossible after the aggregate reaches
PendingHardDelete. - Restore must be scoped by tenant boundaries and role permissions.
- Restore must write an audit event.
- Restore must not mutate Syrve/iiko.
- Restore must preserve original ownership scope, including
OrganizationId, optionalOutletId, and inherited parent-admin context where relevant. - Restore must not silently restart background jobs unless a future implementation task defines safe restart behavior.
If a restore would re-enable work that depends on stale external state, future implementation must require refresh/revalidation before posting or reconciliation can continue.
Hard delete policy
Hard delete is a manual, privileged, destructive operation. It is not a scheduled automatic purge.
Hard delete rules:
- Hard delete deletes only Pay-owned data.
- Hard delete must never delete, roll back, update, void, or alter Syrve/iiko data.
- Hard delete requires preflight.
- Hard delete requires a delete reason.
- Hard delete requires actor and effective scope.
- Hard delete requires audit tombstone.
- Hard delete requires correlation/request id.
- Hard delete must cancel unfinished jobs before data removal.
- Hard delete must fail closed when preflight cannot determine whether external posting risk exists.
Hard-delete purge worker
HardDeletePurgeWorker is an executor for already approved Pay-owned hard-delete work. It is not an automatic scheduler that decides which records become hard-deletable.
Worker rules:
- reads only records already in
PendingHardDelete; - uses the saved
PendingHardDeleteByUserId, reason, and correlation id as approval metadata; - calls the existing lifecycle service instead of duplicating destructive rules;
- keeps hard-delete preflight, tombstone, unfinished-job cancellation, and typed confirmation semantics intact;
- skips records that still require unreconciled external posting typed confirmation, because no saved approved confirmation path exists for the worker;
- deletes only Pay-owned data and never calls Syrve/iiko write, delete, update, rollback, or void transports;
- is disabled by default unless
HardDeletePurge:Enabled=trueis configured.
Hard delete preflight must check at least:
- aggregate ownership and tenant scope;
- current deletion state;
- restore window status;
- dependent Pay records;
- unfinished background/outbox/posting jobs;
- existing Syrve/iiko posting evidence;
- reconciliation status;
- open Critical anomalies;
- whether an unreconciled external posting exists.
Unreconciled external posting preflight
An unreconciled external posting is any Pay posting or payment write evidence where Pay has reason to believe an external Syrve/iiko write may exist, but Pay reconciliation has not confirmed the expected final state.
If the aggregate has an unreconciled external posting:
- hard delete must require a dedicated preflight result;
- operation UI must show a separate red warning;
- typed confirmation is required;
- confirmation text must explicitly state that Pay data will be deleted but Syrve/iiko data will not be deleted, rolled back, or changed;
- audit must record that the unreconciled external posting preflight was triggered and confirmed;
- hard delete must not run any external Syrve/iiko delete/update/write operation;
- unfinished posting/reconciliation jobs must be cancelled before final deletion.
Typed confirmation phrase:
text
DELETE PAY DATA ONLY - SYRVE IIKO WILL NOT BE CHANGEDThe confirmation phrase is intentionally explicit and English. It must not be shortened in implementation.
Unfinished jobs policy
Unfinished background jobs, outbox jobs, posting jobs, reconciliation jobs, and notification jobs must not continue against hard-deleted Pay data.
Before hard delete:
- preflight must find unfinished jobs that reference the aggregate;
- pending or running jobs must be stopped or marked non-runnable according to implementation capabilities;
- jobs must transition to
CancelledByHardDelete; - cancellation must record reason, actor, scope, owning aggregate id, previous job state, and correlation/request id;
- no external Syrve/iiko write may execute after hard delete preflight begins for that aggregate unless the hard delete is explicitly cancelled and the aggregate is still not hard-deleted.
CancelledByHardDelete is terminal for the job. Retrying a cancelled job requires creating a new job under a restored or newly created Pay aggregate, and only if a future ADR or implementation task defines that behavior.
Audit tombstone
Audit tombstone is retained after hard delete.
Tombstone must include:
- actor id;
- actor role/effective role;
- timestamp;
- organization scope;
- optional Outlet/location scope;
- inherited-admin context when relevant;
- entity type;
- entity id;
- previous deletion state;
- final deletion state;
- delete reason;
- correlation/request id;
- hard delete preflight summary;
- whether unreconciled external posting warning was shown and typed confirmation was accepted.
Tombstone must not include unnecessary sensitive payload data:
- no raw bank lines;
- no full account numbers;
- no secrets;
- no Syrve/iiko credentials;
- no full private customer payloads beyond the minimal identifiers needed for investigation.
UI warning text
General hard delete warning:
text
This action permanently deletes Pay data for this record after the restore window has ended or after manual hard-delete approval. It does not delete, roll back, or change any data in Syrve/iiko. A tombstone audit record will remain. This action cannot be undone.Unreconciled external posting warning:
text
Danger: this Pay record has an unreconciled external posting. Hard delete will remove Pay data only. It will not delete, roll back, void, or change the payment or document state in Syrve/iiko. You must verify Syrve/iiko manually before continuing. To confirm, type: DELETE PAY DATA ONLY - SYRVE IIKO WILL NOT BE CHANGEDThe unreconciled warning must be visually distinct and red in future UI implementation.
Consequences
- Soft delete becomes the default reversible delete behavior.
- Hard delete is deliberately manual and privileged.
- Pay deletion never mutates Syrve/iiko data.
- Unreconciled external posting becomes a high-risk destructive-operation path with dedicated warning, preflight, typed confirmation, and audit.
- Future implementation must model job cancellation explicitly to prevent writes after hard delete.
- Future implementation must keep tombstone data sufficient for investigation while minimizing sensitive payload retention.
Non-goals / Out of scope
- Public hard-delete API endpoints.
- Frontend hard-delete UI components.
- EF migrations.
- Automated hard delete scheduler.
- Syrve/iiko delete, rollback, void, update, or write operations.
- Real bank files, secrets, raw bank lines, full account numbers, or private customer payloads.
- New business role decisions outside this ADR.
Follow-up implementation notes for future AI agents
- Read this ADR before implementing delete, restore, hard delete, archive, purge, job cancellation, or tombstone audit behavior.
- Also read Permission Matrix and Scoped RBAC and Tenancy before implementing role checks.
- Do not implement hard delete as automatic scheduled cleanup unless a future ADR explicitly changes this decision.
- Do not mutate Syrve/iiko from Pay deletion flows.
- Do not store sensitive raw payloads in tombstones.
- If an implementation task conflicts with this ADR, propose an ADR update before changing runtime behavior.