# ERP Core의 AWS RDB 전환 설계

작성일: 2026-09-09 · 상태: 구현 전 초안  
사용자 최신 방향: **Supabase를 사용하지 않고 AWS 기반 RDB를 우선한다.**  
검토 코드: `wishket-aidp/erp-core@098fd338665de3f1a39ceb889a724f00079ddd91`  
현재 작업: 원본 코드 정적 조사와 문서 작성. AWS 리소스 생성, 설치, 인증 설정, SQL 실행, 코드 수정은 수행하지 않았다.

AWS RDB 위에 CMSLAB 제품·채널·SCM 도메인을 구축하고 ERP Core의 UI·화면 계약·권한 해석 로직을 재사용하는 방향이다. RDS PostgreSQL을 우선 검토 후보로 둔다. 기존 SQL이 PostgreSQL의 UUID·enum·partial index·JSONB·RLS·함수를 쓰므로 이식 변경량을 줄일 수 있다는 판단이다. **DB 엔진/버전, 인증 제공자, AWS 컴퓨팅 서비스는 아직 확정하지 않는다.**

## 1. 확정 방향과 미확정 결정

| 항목 | 현재 상태 | 다음 결정에 필요한 정보 |
|---|---|---|
| Supabase 사용 | 신규 구축에서 제외하는 사용자 방향 | 원본의 Supabase 코드는 비교·참고 자료로만 유지 |
| 업무 RDB | AWS 기반 우선 | RDS PostgreSQL 후보. 기존 AWS 표준·운영 담당자·엔진·예산 확인 |
| 로그인 | 미확정 | 회사 기존 SSO/IdP, 초대/퇴사 처리, MFA 정책. Cognito는 후보이며 채택 확정 아님 |
| 웹 배포 | 미확정 | Next.js 런타임, 사내망/인터넷 노출, 운영 인력. ECS/EC2/기타 중 운영 방식으로 결정 |
| 파일 보관 | S3 후보 | 원본 Excel·출력 파일 보존, 권한, 리전, 보존기간 |
| 배치 실행 | worker/스케줄러 경계만 설계 | 하루 단위 수집과 큰 파일 변환 처리시간을 확인해 서비스 선택 |
| ORM/SQL 도구 | 미확정 | 기존 팀 표준 및 트랜잭션·타입·migration 도구 지원 범위 |
| 더존/WMS | 파일 입출력 우선, 읽기 어댑터 추가 | 실제 조회권한, 문서·코드·증분 기준, 업로더와 승인 책임 |

RDS DB 접속 인증은 DB 사용자/서비스의 연결 문제다. MD가 어떤 채널을 볼 수 있는지, BM이 어떤 원가를 볼 수 있는지는 애플리케이션·DB 정책으로 별도 구현한다. AWS IAM DB 인증이 이 업무 권한을 자동 제공한다고 가정하지 않는다. [AWS RDS 인증 문서](https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/database-authentication.html), [IAM DB connect 권한](https://docs.aws.amazon.com/service-authorization/latest/reference/list_rds-db.html).

## 2. Supabase 의존성 지도

| 경계 | 현재 소스·의존 동작 | AWS 대상 교체/보존 |
|---|---|---|
| 요청 인증·쿠키 | `proxy.ts::proxy`, `scopeCookieLifetime`: Supabase SSR, getClaims, sb-* 쿠키 | 새 IdP/session 검증과 만료/로그인 유지 처리. proxy 외 Server Action/API에서도 신원을 검증 |
| 로그인·로그아웃 | `app/login/actions.ts`, `app/(authed)/_actions/shell.ts`: signInWithPassword/signOut | AuthPort를 통한 로그인/로그아웃. 기존 로그인 화면은 새 인증 흐름에 맞게 조정 |
| 사용자 관리 | `app/_actions/users.ts`: inviteUserByEmail, updateUserById, resetPasswordForEmail | 초대·잠금·비밀번호 초기화가 새 IdP 또는 SSO에서 가능한지 확인하고 adapter 구현 |
| 세션 강제만료 | `lib/users/queries.ts::revokeUserSessions` | 현재 admin API 호출의 인자/실패처리까지 다시 검증. 새 제공자 세션 폐기와 앱 계정 잠금 검사 연결 |
| actor·guard | `lib/auth/context.ts`, `lib/safe-action.ts`: getClaims/getUser + Supabase 조회 | 검증된 외부 신원→내부 app_user→활성 사원→역할/범위. actor를 요청마다 서버에서 구성 |
| 브라우저 인증 이벤트 | `lib/supabase/client.ts`, `components/query-provider.tsx`, `components/erp/shell/shell-providers.tsx`: onAuthStateChange | 새 세션 변화 이벤트에서 캐시·워크스페이스 정리. 이전 사용자 데이터가 다음 로그인에 남지 않게 함 |
| DB 읽기·쓰기 | `lib/*/queries.ts`: SupabaseClient, .from, 관계를 포함한 .select, .eq, upsert, count, 관리 client | PostgreSQL repository로 교체. 필터·join·페이지·총건수·null·soft delete·upsert 의미 보존 |
| DB 신원·FK | 초기 migration의 created_by/updated_by 등→`auth.users(id)` | 외부 IdP에서 독립된 내부 principal 참조로 바꾸고 감사기록 주체 의미 정리 |
| DB 역할·RLS | migration의 anon/authenticated/service_role, auth.uid(), is_sys_admin() | 실제 AWS runtime DB role과 검증된 actor context에 맞춰 정책 신규 작성. 기존 broad SELECT 복제 금지 |
| 타입 생성 | `lib/database.types.ts`, package.json gen:types | 선택한 SQL/ORM 도구의 타입 생성으로 교체. UI용 contracts/Zod는 가능한 유지 |
| migration·seed·테스트 | `supabase/config.toml`, schemas, migrations, seed, pgTAP helpers, CI env | 일반 PostgreSQL migration·합성 fixture·integration test runner로 교체. Supabase CLI를 신규 필수조건으로 두지 않음 |
| Storage·Realtime·RPC | 검토한 app/lib/components에 `.storage`, `.channel`, `postgres_changes`, `.rpc` 직접 호출 미발견 | 기존 대규모 이전 대상으로 계산하지 않음. 신규 파일 입출력은 S3 후보, 상태 갱신은 초기 polling 가능 |

Storage/Realtime/RPC가 미발견이라는 것은 공급 플랫폼의 기능이 없다는 뜻이 아니다. 이번 체크아웃의 실행 코드 범위에서 직접 사용한 호출을 찾지 못했다는 뜻이다. 숨은 연동이 확인되면 이 목록을 갱신한다.

근거: [proxy](../../../../erp-core/proxy.ts), [서버 DB client](../../../../erp-core/lib/supabase/server.ts), [인증 context](../../../../erp-core/lib/auth/context.ts), [사용자 actions](../../../../erp-core/app/_actions/users.ts), [사용자 query](../../../../erp-core/lib/users/queries.ts), [캐시 provider](../../../../erp-core/components/query-provider.tsx), [초기 migration](../../../../erp-core/supabase/migrations/20260625065247_init.sql), [RLS migration](../../../../erp-core/supabase/migrations/20260625065400_rls_triggers_helpers.sql).

## 3. 권장 경계와 데이터 흐름

```mermaid
flowchart LR
  U[CMSLAB 사용자] --> UI[ERP Core 기반 Next.js UI]
  UI --> AUTH[서버 세션 검증 / AuthPort]
  IDP[기존 SSO 또는 새 IdP\n미확정] --> AUTH
  AUTH --> SVC[업무 서비스\n역할·채널·필드·상태 검증]
  SVC --> REPO[Repository / 트랜잭션]
  REPO --> DB[(AWS RDB\nRDS PostgreSQL 후보)]
  SVC --> FILE[파일 접근 서비스]
  FILE --> S3[(원본·출력 파일\nS3 후보)]
  WORKER[수입·대사·출력 worker] --> SVC
  EXT[ERP·WMS·채널\n파일 / 조회 어댑터] --> WORKER
```

브라우저는 DB 자격증명을 받지 않는다. UI는 기존 DataTable/폼/계층 화면을 재사용하고, 서버 action/API가 검증된 actor와 입력을 업무 서비스로 넘긴다. 업무 서비스가 제품 변경 승인·채널 범위·원가 노출·업로드 상태 전이를 결정하고 repository가 SQL/트랜잭션을 수행한다. migration 관리자와 runtime 계정은 분리한다.

AuthPort의 최소 책임은 `getVerifiedPrincipal`, `signOut`, 초대/계정상태 처리, 세션 폐기다. SSO를 채택하면 앱 자체 비밀번호 재설정 기능이 불필요할 수 있으므로 기존 기능을 기계적으로 이전하지 않는다. Cognito user pool은 앱 인증 및 외부 IdP 연결을 제공하는 후보이며 업무 DB를 대신하지 않는다. [Cognito 공식 설명](https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-user-pools.html).

기존 `auth_user_id` UUID에 새 IdP subject를 무조건 넣지 않는다. 내부 `app_user_id`와 `identity_binding(provider/issuer, subject, app_user_id)`를 분리하는 안을 사용한다. 감사 필드는 내부 사용자/서비스 principal을 참조하고, 원천 신원과 이전 ID 매핑을 보존한다. 고객 데이터가 아직 없는 새 구축이면 데모 사용자 이전 자체가 필요하지 않다.

## 4. AWS RDB의 권한 강제지점

### 기본 권고

서버 업무 서비스에서 명시적 허용 권한·대상 채널·허용 필드·상태 전이를 검사한다. DB에서는 제한된 runtime role, 테이블/컬럼 권한, 제약조건을 적용한다. 원가·처방·채널별 제한 원장처럼 경계가 중요한 데이터에는 PostgreSQL RLS를 추가하는 안을 권고한다. 엔진이 PostgreSQL이 아닌 것으로 결정되면 동등한 데이터 접근 경계를 다시 설계한다.

PostgreSQL RLS는 일반 조회/변경을 제한하지만 소유자·BYPASSRLS 역할에는 예외가 있고 TRUNCATE는 대상이 아니다. runtime에 테이블 소유자/관리 권한을 주고 RLS가 보호한다고 가정하면 안 된다. [PostgreSQL RLS 공식 문서](https://www.postgresql.org/docs/current/ddl-rowsecurity.html). RDS의 최초 관리자 계정은 운영 관리용으로 두고 앱에는 최소권한 DB 사용자를 만든다. [AWS RDS 인증 권고](https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/database-authentication.html), [rds_superuser 역할](https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/Appendix.PostgreSQL.CommonDBATasks.Roles.rds_superuser.html).

### 풀링과 actor 전달

단일 서비스 DB 계정으로 RLS를 사용할 경우 트랜잭션 단위의 actor context 전달 방식이 필요하다. 서버가 검증한 내부 사용자/범위를 한 트랜잭션에서 설정하고 같은 커넥션에서 쿼리하도록 한다. 요청 body의 user_id/role_id를 그대로 DB context에 넣지 않는다. context 누락은 거부, commit/rollback/오류 후 풀 재사용은 잔존 context 없이 동작해야 한다.

이 context 값 자체가 SQL injection이나 침해된 runtime에 대한 완전한 보안 경계는 아니다. 파라미터 SQL, 제한 DB role, 제한된 쓰기 함수/필드, 감사로그를 함께 적용한다. 풀링/ORM 선택 후 실제 사용 방식에서 검증하고 범용 `SET`을 무심코 복사하지 않는다.

### 민감정보와 업무 책임

전사 제품 기본정보와 원가·처방 테이블을 분리한다. MD 화면의 응답·검색·export에는 비허용 필드를 포함하지 않는다. UI 컬럼 숨김으로 대체하지 않는다. SCM 업무권한 이관은 요청 검증·승인·파일 생성/등록 확인 권한의 변경이며 DB 관리자 권한 부여가 아니다. 현행 담당자와 이관 후 담당자는 적용일이 있는 책임표로 관리한다.

S3 후보를 사용할 때 파일 metadata와 권한을 업무 DB에서 검증한 후 제한된 object 접근을 발급한다. presigned URL은 이를 가진 사용자가 유효기간 동안 접근할 수 있는 방식이므로 URL 생성 전 대상 채널·파일 버전·행위 권한을 확인한다. 원가 포함 export URL을 일반 제품 조회 응답에 섞지 않는다. [S3 presigned URL 공식 문서](https://docs.aws.amazon.com/AmazonS3/latest/userguide/using-presigned-url.html).

## 5. 작업 순서

1. **의사결정 기록:** PostgreSQL 후보, 인증/세션, 컴퓨팅, migration/SQL 도구, 네트워크/파일 보존을 짧은 ADR로 확정한다. 제품 의미체계 설계와 분리해 진행한다.
2. **코어 재사용 경계 고정:** UI 키트·contracts·resolver의 순수 로직은 유지 대상으로, Supabase adapter·queries·auth·SQL 정책은 교체 대상으로 표시한다. 라이선스/제공 범위 확인은 기존 검토사항으로 유지한다.
3. **새 DB baseline:** 필요한 조직·권한·법인·공통코드 DDL을 정리하고 auth.users FK·Supabase 역할·helper를 제거/대체한다. owner/grants/search_path/확장 지원을 확인한다. 원본 migration을 그대로 RDS에 실행하지 않는다.
4. **인증 한 흐름 완성:** 로그인→내부 사용자 매핑→활성 사원 선택→조회→로그아웃→캐시 정리를 합성 계정으로 검증한다. 잠금/퇴사/역할 변경도 확인한다.
5. **repository 하나 전환:** 법인 또는 공통코드의 목록/상세/변경을 새 DB에 연결한다. 결과 형태·페이지·정렬·총건수·오류 계약을 유지해 UI 교체 범위를 제한한다.
6. **권한 부정 테스트 통과:** 아래 행위들을 API/서비스/DB/파일에서 각각 검증한다. 기존 Supabase 이슈를 수정하는 별도 운영 환경부터 만들 필요는 없다.
7. **CMSLAB 도메인 첫 흐름:** 제품 계층→ITEM→채널 식별자→LOT/샘플 주문→파일 수입/검증/출력. 승인·감사·중복 방지를 같은 흐름에서 구현한다.
8. **비동기·운영화:** 큰 파일 수입/출력, 상태 조회, 재시도·실패 알림, 백업/복구/배포 migration을 구축한다. 외부 ERP 실제 쓰기는 별도 승인된 adapter 단계로 둔다.

## 6. AWS 대상 검증표

| 테스트 | 통과 기준 |
|---|---|
| 신원 위조/만료 | 변조·만료 토큰, 잘못된 issuer/audience, 미등록·잠금 사용자를 Server Action/API에서 차단 |
| 타인 활성 사원 | 본인 user_id와 타인 employee_id 조합, 본인 role_id 재배정을 거부. 앱과 DB 판정 일치 |
| 역할/조직 경계 | 활성 사원 없음·비활성·삭제된 역할·역할 회수 후 권한 없음. 캐시된 권한으로 우회 불가 |
| MD 민감정보 | 일반 조회·필터·export·파일 URL에서 원가/처방 미노출 |
| 채널 간 접근 | URL/body의 채널·행 ID를 다른 채널 것으로 바꿔도 읽기/변경/파일 다운로드 불가 |
| DB context | context 미설정, rollback 후 풀 재사용, 사용자 A→B 연속 요청에서 A 데이터가 B에 남지 않음 |
| 실제 runtime DB role | RLS 우회·테이블 소유·불필요한 GRANT가 없음. migration 계정으로만 테스트하지 않음 |
| 수입 중복 | 같은 파일/동일 행 재입력과 worker 재시도에서 거래 중복 없음. 정정 배치는 이력으로 구분 |
| 요청/승인/등록 | 권한 없는 승인, 중복 등록, 미등록 상태의 완료 전환을 거부. SCM 이관 전후 기준일을 재현 |
| UI 세션 교체 | 사용자 전환/로그아웃 시 React Query·유지된 탭·다운로드 목록의 이전 사용자 데이터 제거 |
| 데이터 의미 | 제품 리뉴얼 전후 출시제품 추적, ITEM·LOT 재고 분리, 주문일 판매추이와 회계기준 분리 |

테스트 체계는 Vitest/Playwright를 유지할 수 있다. DB integration은 선택한 PostgreSQL 실행환경과 migration 도구에 맞게 새 fixture로 구성한다. 기존 pgTAP의 의도를 재사용하되 `auth.uid`, Supabase seed ID, admin-only E2E 전제를 버린다. CI에 DB integration과 역할별 E2E를 추가하고 실행 결과를 기록한다. 현 시점 이 표는 **미실행 검증 계획**이다.

## 7. 설계상 피할 우회

- AWS로 옮기면서 모든 repository에 관리자 DB 계정을 주고 UI만 제한하는 방식.
- Supabase 플랫폼 역할과 auth schema를 의미 검토 없이 복제해 겉으로만 SQL이 실행되게 하는 방식.
- RDS를 더존의 복제 DB처럼 설계하고 제품마스터/채널 키를 더존 컬럼에 종속시키는 방식.
- 출고 파일 생성 성공을 외부 WMS/ERP 등록 성공으로 처리하는 방식.
- 실측 셀아웃이 없는 채널을 주문/출고 데이터로 조용히 메우거나 판매추이를 회계매출로 표기하는 방식.

이 문서는 AWS 선택을 위한 실행 준비 자료다. 구체 AWS 서비스/용량/가격을 확정하거나 계정·리소스를 생성한 결과가 아니다.
