테마
Rollback Policy
현재 v3 에서는 통합 해제(unmerge) 기능을 일반 사용자 API 또는 운영자 콘솔로 제공하지 않습니다. forensic 복구가 필요한 incident 의 경우 운영자가 DB 레벨에서 직접 처리합니다.
이는 사용자 데이터의 일관성과 보안을 보호하기 위한 의도된 설계입니다. 아래는 그 근거와, 정말로 복구가 필요한 사고 상황에서 운영자가 따르는 절차를 정리한 문서입니다.
왜 사용자용 unmerge 버튼을 두지 않았나
1. 통합 이후 새로 쌓인 데이터의 소속이 모호해짐
A → B 통합이 일어난 뒤 RP 들은 user.merged 를 받고, A 의 도메인 데이터를 B 의 view 로 합쳐 단일 사용자에게 보여줍니다. 시간이 지나면:
- B 는 A 의 데이터를 보면서 새 작업(주문, 게시물, 결제) 을 만들게 됩니다.
- 그 새 작업이 "통합 이전 A 의 맥락" 인지 "통합 이후 B 의 맥락" 인지 시스템이 자동으로 판별할 방법이 없습니다.
- 만약 unmerge 한다면 이 새 작업들이 어느 쪽에 귀속되어야 하는지 결정해야 하는데, 사용자도 운영자도 일관된 답을 줄 수 없습니다.
결과적으로 자동 unmerge 는 "데이터를 보존했다" 가 아니라 "데이터의 의미가 흐려졌다" 로 끝날 수 있어, 사용자에게 더 큰 혼란을 줄 위험이 있습니다.
2. RP 마다 통합을 흡수하는 방식이 다름
- 어떤 RP 는 두 사용자의 데이터를 한쪽으로 hard merge (합치고 다른쪽 삭제) 합니다.
- 어떤 RP 는 soft link (외래키만 갱신, 양쪽 row 유지) 만 합니다.
- 일부 RP 는 통합 시점에 결제/주문 같은 후속 작업을 함께 수행합니다 (예: 두 무료 trial 의 합산 한도 조정).
logi 단에서 unmerge 를 일괄 제공한다고 해도, 실제 복구는 각 RP 의 협조에 의존합니다. 한 RP 만 처리를 누락해도 사용자가 보는 데이터가 RP 간에 어긋나는 상황이 생깁니다. 이 위험을 사용자에게 떠넘기지 않기 위해, 복구가 필요할 때는 운영자가 영향 범위를 명시적으로 파악한 뒤 incident 로 다루는 쪽을 택했습니다.
3. 보안 측면에서도 양방향 통합은 공격면이 넓음
unmerge 를 사용자에게 그대로 열어두면 다음과 같은 시나리오가 가능해집니다:
- A 계정을 잠시 탈취한 공격자가 A 를 B 로 흡수 → B 권한 획득 후 unmerge 로 흔적 일부 삭제.
- T3 OTP 를 통과한 공격자가 흡수와 unmerge 를 반복해 audit log 를 부풀리고 실제 사고를 묻어버림.
identity_links row 와 audit log 를 영구히 남기는 것이, 사용자가 자신의 계정에 무슨 일이 있었는지 나중에라도 확인할 수 있게 해주는 더 안전한 선택입니다.
4. "분리하고 싶다" 라는 요청의 실제 원인은 대개 다른 곳에 있음
실무에서 "통합을 취소해 달라" 는 요청이 들어오면, 대부분은 더 적절한 처리 경로가 있는 다른 종류의 사고입니다:
- 잘못된 계정에 OTP 를 입력했어요 — T3 가 양쪽 본인 확인을 요구하기 때문에 실제로는 매우 드물지만, 만약 발생했다면 사회공학 공격으로 분류해 별도 incident 로 다룹니다.
- 다른 사람의 계정을 잘못 흡수했어요 — 이는 계정 탈취/공유/도용에 해당하며, GDPR 또는 개인정보보호법 차원의 incident response 가 우선합니다.
- 두 계정을 다시 따로 쓰고 싶어요 — 이 경우 새 계정을 만들고 RP 에 데이터 이전을 요청하는 편이 더 안전하고 의미가 명확합니다. logi 가 unmerge 를 흉내내는 것보다, RP 의 정식 데이터 export 가 사용자에게도 투명합니다.
Forensic 복구 절차
진짜로 unmerge 가 필요한 사고 (예: 잘못된 user 가 흡수됨이 확인된 incident) 가 발생하면, 운영자는 다음을 수행합니다. 이는 운영자 매뉴얼이지 사용자 API 가 아닙니다.
- Incident 선언 —
logi_merge_failures_total이 아닌 별도 incident ticket. 영향 범위 파악 (어느 RP 가 영향을 받는지). - identity_links row 삭제 — DB 직접 작업. 같은 transaction 안에서 audit log 에
merge_reversed이벤트 INSERT. - 흡수된 user 의 자격증명 재발급 — 모든 RP 의
oauth_access_grants가 revoked 상태이므로 사용자가 다시 SSO 로 로그인해야 함. logi 가 자동으로 grant 를 살리지 않음. - RP 통보 —
user.merge_reversed이벤트 emission. (참고: 이 이벤트 타입은 현재 webhook outbox 에 등록되어 있지 않습니다. 운영자가 수동으로 emit 하거나, 영향받은 RP 와 별도 채널로 조율.) - Post-mortem — incident report 작성. 통합이 어떻게 잘못 트리거되었는지 (T1/T2 라면 정상 동작이라 거의 불가능 — T3 라면 OTP 가 어떻게 잘못된 곳에 도착했는지).
운영자가 사용할 SQL (참고용)
sql
BEGIN;
-- 1. 대상 row 확인
SELECT * FROM identity_links
WHERE primary_user_id = :survivor_id AND linked_user_id = :merged_id;
-- 2. audit 기록
INSERT INTO authentication_audits (event_type, user_id, event_data, created_at)
VALUES ('merge_reversed', :survivor_id,
jsonb_build_object('linked_user_id', :merged_id, 'reason', :incident_ticket),
NOW());
-- 3. 통합 row 제거
DELETE FROM identity_links
WHERE primary_user_id = :survivor_id AND linked_user_id = :merged_id;
-- 4. 흡수된 user 의 active 상태 회복 (필요시)
UPDATE users SET deleted_at = NULL WHERE id = :merged_id AND deleted_at IS NOT NULL;
COMMIT;⚠️ canonical resolver 의 60초 캐시 때문에 commit 후 즉시 RP 가 옛 canonical 을 볼 수 있습니다. CanonicalResolver.invalidate(:survivor_id) 와 invalidate(:merged_id) 를 호출하거나 캐시 backend 를 flush 해야 합니다.
향후 계획
unmerge 의 데이터 의미를 안전하게 정의할 수 있는 추가 메타데이터 (예: per-RP merge cursor, 통합 이후 새로 생성된 row 의 명시적 마킹) 는 v4 이후의 검토 대상입니다. 현재 v3 에서는 통합을 한 방향으로만 다루는 단순한 모델을 채택하고 있으며, 이를 통해 사용자가 자신의 계정 이력을 명확하게 추적할 수 있도록 보장합니다.