mirror of
https://github.com/PerpetualSoftware/pad.git
synced 2026-09-24 19:32:10 +00:00
366d4fb7e5
* fix(store): harden account-deletion FK cascade (TASK-1959)
DeleteAccountAtomic could 500 with nothing deleted when a user had rows
referencing them via foreign keys with no ON DELETE action — notably
activities.user_id (the audit/history log, including the session_ip_changed
rows the auth middleware writes on the very request that deletes the
account). The delete-account tests only passed by working around this
(pinning RemoteAddr to loopback, scrubbing activities.user_id).
Audit every table with a FK to users(id) and handle each in the delete
transaction:
- de-identify (UPDATE ... SET NULL) audit/history rows: items
created/modified, comments, comment_reactions, item_links,
item_versions, share_link_views
- delete owned/transient/audit rows: sessions, api_tokens,
workspace_members, sent invitations, password/email tokens, issued
grants, created share links, mcp_audit_log, oauth_connections
- rely on existing ON DELETE CASCADE / SET NULL for item_stars,
user_report_layouts, {collection,item}_grants.user_id,
items.assigned_user_id
Migrations 072 (SQLite) / 050 (Postgres) give activities.user_id an
ON DELETE SET NULL FK so the highest-write-frequency audit table can't
block a delete via a row written concurrently during the request. SQLite
rebuilds the table (022_audit_trail pattern); Postgres never had the FK,
so it is added after an orphan scrub so validation passes.
Remove the test work-arounds now that the cascade holds: deleteAccountReq
deletes from a changed IP so the session-IP-change audit row exercises the
fix, and the partial-delete test injects its post-cancel failure via the
sidecar hook instead of the (now-fixed) FK gap. Add a store-level test that
deletes a fully-populated user atomically.
Closes TASK-1959
Claude-Session: https://claude.ai/code/session_01HxBkAMiFBtCRJ2tKSCt3ST
* fix(store): harden account-deletion FK cascade (TASK-1959)
DeleteAccountAtomic could 500 with nothing deleted when a user had rows
referencing them via foreign keys with no ON DELETE action — notably
activities.user_id (the audit/history log, including the session_ip_changed
rows the auth middleware writes on the very request that deletes the
account). The delete-account tests only passed by working around this
(pinning RemoteAddr to loopback, scrubbing activities.user_id).
Audit every table with a FK to users(id) and handle each in the delete
transaction:
- de-identify (UPDATE ... SET NULL) audit/history rows: items
created/modified, comments, comment_reactions, item_links,
item_versions, share_link_views
- delete owned/transient/audit rows: sessions, api_tokens,
workspace_members, sent invitations, password/email tokens, issued
grants, created share links, mcp_audit_log, oauth_connections
- rely on existing ON DELETE CASCADE / SET NULL for item_stars,
user_report_layouts, {collection,item}_grants.user_id,
items.assigned_user_id
Migrations 072 (SQLite) / 050 (Postgres) give activities.user_id an
ON DELETE SET NULL FK so the highest-write-frequency audit table can't
block a delete via a row written concurrently during the request. SQLite
rebuilds the table (022_audit_trail pattern); Postgres never had the FK,
so it is added after an orphan scrub so validation passes.
Log the account_deleted audit row with an empty user_id (deleted id kept
in metadata): the user row is already gone by then, and the new
activities.user_id FK would otherwise reject the insert and silently drop
the row. This makes the account_deleted event actually recorded on both
dialects.
Remove the test work-arounds now that the cascade holds: deleteAccountReq
deletes from a changed IP so the session-IP-change audit row exercises the
fix, and the partial-delete test injects its post-cancel failure via the
sidecar hook instead of the (now-fixed) FK gap. Add a store-level test that
deletes a fully-populated user atomically.
Closes TASK-1959
Claude-Session: https://claude.ai/code/session_01HxBkAMiFBtCRJ2tKSCt3ST