[feat] 대화 생성 API, ACL
name: Feature Request
labels: 'Type : Feature'
기능 요약
대화 생성, 조회 API와 PostgreSQL RLS 기반 공유 범위(ACL)를 추가한다.
상세 설명
대화 API
- 대화 생성
POST /api/conversations- 팀 귀속(
teamId)은 선택. 공유 상태는 항상 PRIVATE으로 시작 - 귀속은 생성 후 변경할 수 없다 (DB 트리거로 고정)
- 팀 귀속(
- 내 대화 목록
GET /api/conversations-
updated_at내림차순
-
- 대화 + 메시지 이력
GET /api/conversations/{conversationId}- 메시지는
seq순서로 복원 - 볼 수 없으면 404 (403이 아니다 — 존재 여부를 알리지 않음)
- 메시지는
- 공유 상태 전환
PATCH /api/conversations/{conversationId}/visibility- 소유자만 가능 (아니면 403
NOT_CONVERSATION_OWNER) - PRIVATE(나만) / TEAM(팀 전체)
- TEAM은 팀에 귀속된 대화만 가능 (아니면 400
CONVERSATION_NOT_TEAM_SCOPED)
- 소유자만 가능 (아니면 403
- 검색 폭 변경
PATCH /api/conversations/{conversationId}/retrieval-scope- OWN ⊂ CONVERSATION_TEAM ⊂ ALL (기본값)
- 권한 기능이 아니라 RAG 검색 폭이다. 권한과 교집합으로 적용된다
RLS 기반 접근 제어 (acl 패키지)
- 권한 판정을 애플리케이션이 아니라 DB 정책(RLS)이 한다
-
conversations·messages에 RLS + FORCE ROW LEVEL SECURITY - 정책:
conv_select,conv_insert,conv_update,msg_select,msg_insert -
acl_scope생성 컬럼(compute_acl_scope)과 인덱스로 판정
-
- 요청마다 범위를 계산해 커넥션에 주입 (캐싱하지 않음 - 팀 제거 즉시 차단되어야 함)
-
ScopeResolver→ScopeContext(GUC 5개) →AclContextInjector -
AclContextAspect가 트랜잭션 진입 직후 주입.AclTransactionConfig로 트랜잭션을 바깥(order 0), 애스펙트를 안쪽(order 100)에 두어set_config가 버려지지 않게 한다 -
AclScope는 DB의compute_acl_scope()와 문자열이 일치해야 한다 -
AclContextOverride는 인증이 없는 경로(시드·테스트 픽스처) 전용
-
- 개별 공유(
share):ShareGrant,Permission,ResourceType- visibility와 독립이며 합집합. 회수는 soft(
revokedAt)이고 재부여는 같은 행을 되돌린다
- visibility와 독립이며 합집합. 회수는 soft(
스키마 적용 순서
-
schema.sql(ddl-auto 이전) - ddl-auto 테이블 생성
-
SchemaPostProcessor(@Order(100)) →post-schema.sql: 생성 컬럼·트리거·RLS·정책 -
DevDataSeeder(@Order(200)): 사용자·팀 생성 후AclContextOverride.runAs로 대화 시드- RLS 대상 테이블 쓰기는
SeedWriter로 분리 (애스펙트가 메서드 진입 시 돌기 때문)
- RLS 대상 테이블 쓰기는
sequenceDiagram
autonumber
participant C as 클라이언트
participant TX as 트랜잭션 어드바이저<br/>(order 0 · 바깥)
participant AS as AclContextAspect<br/>(order 100 · 안쪽)
participant SR as ScopeResolver
participant IN as AclContextInjector
participant DB as PostgreSQL
C->>TX: @Transactional 서비스 호출
TX->>DB: BEGIN
Note over TX,AS: 순서가 뒤집히면 set_config 가<br/>트랜잭션 밖에서 돌아 조용히 버려진다
TX->>AS: 트랜잭션 안에서 실행
AS->>SR: resolve()
SR->>DB: team_members 조회 (내 팀)
SR->>DB: share_grants 조회 (대화·문서 Grant)
SR-->>AS: ScopeContext (GUC 5개)
AS->>IN: injectCurrent()
IN->>DB: set_config(app.*, LOCAL=true) × 5
AS->>DB: 실제 쿼리
DB-->>AS: RLS 정책이 걸러낸 행만
TX->>DB: COMMIT (GUC 도 함께 사라짐)
오류 코드 추가
-
CONVERSATION_NOT_FOUND(404),NOT_CONVERSATION_OWNER(403),CONVERSATION_NOT_TEAM_SCOPED(400),NOT_TEAM_MEMBER(403)