같이 읽은 라이선스를 모두에게 적용하면 권한이 섞인다
공동 작업의 보이스 목록은 요청자가 사용을 허용받은 소유자들의 라이선스를 확인한다. 소유자가 여러 명이면 같은 목록 요청 안에서 단건 판정을 반복한다. 조회를 합쳐 라이선스 후보를 한 번에 읽을 수 있지만, 그 결과에서 라이선스 하나를 고른 뒤 모든 소유자에게 적용하면 다른 사람의 권한이 넘어갈 수 있다.
실제로 배치 판정 테스트에는 A와 B가 각자의 라이선스를 가진 조건이 있다. B의 라이선스가 더 최근에 시작했다고 해서 A에게도 B의 라이선스가 적용돼서는 안 된다. 이는 테스트로 구성한 반례이며, 운영에서 권한 유출이 발생했다는 기록은 아니다. 변경은 DB 조회를 묶고 판정은 사용자마다 유지했다.
넓게 읽어도 각 사용자의 값으로 다시 걸러야 한다
배치 함수는 사용자들의 로그인·역할·신분 그룹 값을 중복 없는 합집합으로 만들어 대상 항목을 조회한다. 읽은 결과에는 다른 사용자의 항목도 섞여 있다. 단건 함수와 같은 resolveFromItems를 각 사용자에게 호출해, 항목의 대상 유형과 그 사용자의 값이 함께 맞는 것만 후보에 넣는다. 다음은 그 매칭 조건이다.
const matches =
(item.targetType === "ROLE" && item.value === String(user.role)) ||
(item.targetType === "USER_GROUP" &&
!!user.userGroupId && item.value === user.userGroupId) ||
(item.targetType === "USER" && item.value === user.userLogin);
if (!matches) continue; 값만 비교하면 역할 번호와 다른 유형의 값이 우연히 같은 경우를 구분하지 못한다. USER의 기준 키도 내부 사용자 ID가 아니라 로그인이다. 대상 등록은 사용자가 처음 접속하기 전에도 가능해야 해서 로그인으로 매칭하고, 반환 Map은 호출자가 소유자 행과 연결할 수 있도록 내부 사용자 ID를 키로 쓴다.
사용자별로 남은 후보에서 활성 기간과 우선순위에 따라 라이선스를 고르고, 선택된 라이선스 안에서도 그 사용자에게 맞은 대상들의 기능 권한을 합성한다. 조회용 합집합이 권한용 합집합으로 그대로 바뀌는 자리를 두지 않은 것이다. 별도의 배치 전용 판정 코드를 만들지 않아 단건과 배치의 규칙이 갈라질 가능성도 줄였다.
| 단계 | 함께 처리하는 범위 | 사용자마다 유지하는 조건 |
|---|---|---|
| 대상 항목 조회 | 여러 사용자의 매칭 값 | 다른 사람 항목도 읽힐 수 있음 |
| 라이선스 후보 구성 | 동일한 판정 함수 사용 | 자신의 targetType·value만 통과 |
| 기능 권한 합성 | 같은 합성 규칙 사용 | 선택된 라이선스의 자신의 대상만 합성 |
초대받은 모든 소유자가 보이스 판정 대상은 아니다
보이스 사용 판정은 먼저 요청자의 협업 관계 중 voiceAllowed: true인 것만 읽는다. 과목이 삭제되지 않았고 과목별 협업 보이스 스위치도 켜져 있어야 한다. 여기서 소유자 ID를 중복 제거하고, 남은 소유자가 없으면 라이선스 조회를 하지 않는다. 사이트 협업 정책이 꺼져 있어도 판정을 끝낸다.
그다음 남은 소유자 행만 읽어 배치 해석한다. 최종 결과에는 보이스 클론 기능이 열리고 생성이 차단되지 않은 소유자만 남긴다. 만료된 라이선스의 기능 값이 남아 있더라도 생성 차단 여부를 함께 확인한다. 초대 관계가 있다는 사실, 보이스 사용을 허용했다는 사실, 지금 소유자가 그 기능을 사용할 수 있다는 사실을 각각 통과해야 한다.
현재 배치 해석에는 후속 변경으로 검증용 계정 지정 조회가 추가돼 있다. 사용자별 지정이 있으면 그 지정의 라이선스 해석이 일반 대상 매칭보다 우선한다. 따라서 최신 함수는 배치당 검증용 계정 조회 한 번과 대상 항목 조회 한 번을 수행한다. 초기 변경에서 말하는 “한 번”은 대상 항목 조회를 묶었다는 뜻이다. 협업 관계와 소유자 조회까지 포함한 전체 보이스 권한 경로가 SQL 한 개라는 의미는 아니다.
캐시 만료를 기다리지 않고 다음 요청에서 다시 판정한다
결과를 캐시하면 반복 조회를 더 줄일 수 있지만, 허용 해제와 라이선스 변경을 모든 변경 경로에서 무효화해야 한다. 이 변경은 캐시를 추가하지 않고 요청마다 필요한 값을 다시 읽는 방식을 유지했다. 캐시 만료 때문에 해제된 권한이 다음 요청에 남는 지연을 만들지 않기 위한 선택이다. 이미 진행 중인 외부 작업까지 동시에 중단한다는 보장은 아니다.
단위 테스트는 대상 항목 조회가 배치당 한 번인지, 두 사용자 값이 함께 전달되는지, A와 B의 결과가 섞이지 않는지, 매칭 없는 사용자가 거부되는지, 단건과 배치의 라이선스 ID·모드가 같은지 확인한다. 협업 테스트는 허용된 소유자만 전달하는지와 배치 호출 자체를 확인한다. 이 테스트는 조회·판정 계약을 확인하며, 실제 DB 부하를 측정하지 않는다.
기존 단건 호출도 병렬로 실행되므로 소유자 수만큼 응답 시간이 늘어났다고 말할 수는 없다. 줄어든 것은 반복 DB 요청이고, 사용자별 매칭 계산은 남는다. AI Commons에서 배치 권한 조회가 유지해야 할 조건은 단건과 같은 사용자에게 같은 판정을 돌려주는 것이다.