feat(account): journal detached credentials to the retention log

On unlink (RemoveIdentity, reason=unlink) and email change (replaceEmailIdentity,
reason=change) write the outgoing credential to retained_identities before removing
the live identities row — so the legal dossier survives while the (kind, external_id)
frees for reuse. Same transaction, so the dossier and live state cannot diverge.
Integration tests cover both reasons.
This commit is contained in:
Ilia Denisov
2026-07-03 11:52:48 +02:00
parent 710ab06333
commit 1598646021
4 changed files with 165 additions and 3 deletions
+11 -3
View File
@@ -31,21 +31,29 @@ func (s *Store) RemoveIdentity(ctx context.Context, accountID uuid.UUID, kind st
if err != nil {
return err
}
hasKind, others := false, 0
var toRetain []Identity
others := 0
for _, id := range ids {
if id.Kind == kind {
hasKind = true
toRetain = append(toRetain, id)
} else {
others++
}
}
if !hasKind {
if len(toRetain) == 0 {
return ErrNotFound
}
if others == 0 {
return ErrLastIdentity
}
return withTx(ctx, s.db, func(tx *sql.Tx) error {
// Journal the detached credential before removing it, so the legal dossier
// survives while the identity frees for reuse (see retention.go).
for _, id := range toRetain {
if err := retainIdentityTx(ctx, tx, accountID, id.Kind, id.ExternalID, id.Confirmed, id.CreatedAt, retainUnlink); err != nil {
return err
}
}
delID := table.Identities.DELETE().WHERE(
table.Identities.AccountID.EQ(postgres.UUID(accountID)).
AND(table.Identities.Kind.EQ(postgres.String(kind))),