basquetWi + New ticket

New ticket

Sub-ticket of PLUTO-448 approveAccessRequest silently FORKS identity for a soft-deleted existing user (data-integrity bug; surfaced restoring Augusto Montoni df340604, a PLUTO-147 dormancy-swept stub). Two coupled gaps: (1) the existing-user probe (admin-actions.ts ~2168) filters deletedAt IS NULL, so a soft-deleted stub is invisible and the flow falls through to the net-new INSERT branch; (2) uq_users_emailcanonical is PARTIAL (WHERE deletedAt IS NULL, migration 005) and there is NO unique on legajo/numeroDocumento, so the fall-through INSERT does NOT raise 23505 — it creates a SECOND active users row (new uuid), orphaning the real soft-deleted identity (legajo/comisión assignment/role/history buried). A bare new stub logs in; real identity stays buried. BONUS gap: the existing-ACTIVE-user branch (~2172) marks the request Aprobado but sends NO sendAccessRequestApproved email. FIX: extend existing-user detection to also match soft-deleted rows and RESTORE in-txn (clear deletedAt/deletedBy/deleteReason, isActive=true, re-assert alumno role, mark Aprobado, fire sendAccessRequestApproved) under the archive sentinel; + add the missing email to the active-user branch. This kills the dormancy-swept-stub-reappears recurrence (every PLUTO-147-swept student who returns hits this). GATING: touches user-create + email path -> audit pre-impl design-ping + pre-push diff review + post-push PTD. NOTE: users has NO restoredBy/restoredAt cols (fraud-tables only). · pluto