feat: center DDS demo on sales knowledge authorization

This commit is contained in:
devmrko
2026-06-30 14:21:39 +09:00
parent 188b398dcb
commit 04cca64f10
9 changed files with 270 additions and 55 deletions

View File

@@ -1,13 +1,13 @@
# 설계서: 기술 태그 기반 벡터 지식자료 검색과 VPD 연결
> **상태**: 제품형 운영 흐름 구현 · DDS 병행 검증 완료 · 객체별 Data Grant predicate 적용 · 운영 확장 항목 별도
> **추적성**: Redmine #565, #566 · 구현 커밋 `a5e70bc` · 기준 구현: `28_agent_ords_vector_tag_vpd_setup.sql`, `29_agent_ords_vector_search_ords.sql`, `32_dds_vector_tag_setup.sql`, `34_dds_token_data_grant_common_auth.sql`
> **추적성**: Redmine #565, #566 · 구현 커밋 `a5e70bc` · 기준 구현: `28_agent_ords_vector_tag_vpd_setup.sql`, `29_agent_ords_vector_search_ords.sql`, `32_dds_vector_tag_setup.sql`, `34_dds_token_data_grant_common_auth.sql`, `36_dds_sales_knowledge_scenario.sql`
## 1. 한 문장으로 이해하기
문서를 잘게 나누고(청킹) 숫자 벡터로 바꾼 뒤(벡터화) 각 조각에 `SPRING_BOOT`, `ORACLE_VPD` 같은 기술 태그를 붙인다. 사용자의 권한에는 “이 태그를 볼 수 있음”을 등록하고, ORDS 검색은 권한에 맞는 조각만 검색 결과에 남긴다.
문서를 잘게 나누고(청킹) 숫자 벡터로 바꾼 뒤(벡터화) 각 조각에 `SALES`, `HR`, `FINANCE` 같은 업무 접근 분류값을 붙인다. 사용자의 권한에는 “이 값을 볼 수 있음”을 등록하고, 검색은 권한에 맞는 조각만 검색 결과에 남긴다.
이 기능에서 태그는 문서의 분류표이고, VPD는 그 분류표를 이용해 DB가 실제로 보여 줄 행을 제한하는 장치다. 화면에서 검색 조건을 넣는 것만으로는 우회할 수 없도록 DB 안에서 마지막 필터를 적용한다.
이 기능에서 `TECH_TAG`는 문서 청크에 저장된 업무 분류 컬럼 값이고, VPD/DDS는 그 값을 이용해 DB가 실제로 보여 줄 행을 제한하는 장치다. 화면에서 검색 조건을 넣는 것만으로는 우회할 수 없도록 DB 안에서 마지막 필터를 적용한다.
## 2. 큰 흐름 (일반 사용자가 보는 순서)
@@ -51,11 +51,11 @@
- 같은 `ALLOW` 권한에 태그를 여러 개 넣으면 OR이다.
```text
ALLOW TAG SPRING_BOOT
ALLOW TAG ORACLE_VPD
ALLOW TAG SALES
ALLOW TAG INTERNAL
결과: TECH_TAG 목록에 SPRING_BOOT가 포함됨
OR TECH_TAG 목록에 ORACLE_VPD가 포함됨
결과: TECH_TAG 목록에 SALES가 포함됨
OR TECH_TAG 목록에 INTERNAL이 포함됨
```
- `DENY` 태그도 내부적으로 OR로 묶은 뒤 허용 결과에서 뺀다.
@@ -74,21 +74,21 @@ AND NOT (거부 태그 C OR 거부 태그 D)
| 청크 | 태그 |
|---|---|
| Spring Boot VPD 시작하기 | `SPRING_BOOT`, `ORDS` |
| Oracle VPD 정책 연결 | `ORACLE_VPD`, `ORDS` |
| MCP 도구 권한 설계 | `MCP` |
| 세일즈 파이프라인 운영 가이드 | `SALES`, `INTERNAL` |
| 인사 채용 운영 기준 | `HR`, `INTERNAL` |
| 재무 마감 체크리스트 | `FINANCE`, `INTERNAL` |
역할을 다음처럼 만든다.
1. `KNOWLEDGE_BACKEND`: `ALLOW TAG SPRING_BOOT`, `ALLOW TAG ORACLE_VPD`
2. `KNOWLEDGE_MCP`: `ALLOW TAG MCP`
3. `KNOWLEDGE_NO_ORDS`: `ALLOW TAG SPRING_BOOT`, `ALLOW TAG ORACLE_VPD`, `DENY TAG ORDS`
1. `SALES_KNOWLEDGE_ROLE`: `ALLOW TAG SALES`
2. `HR_KNOWLEDGE_ROLE`: `ALLOW TAG HR`
3. `KNOWLEDGE_INTERNAL`: `ALLOW TAG INTERNAL`, 필요 시 `DENY TAG FINANCE`
그러면:
- `KNOWLEDGE_BACKEND` 사용자는 첫 번째와 두 번째 청크 본다.
- `KNOWLEDGE_MCP` 사용자는 세 번째 청크만 본다.
- `KNOWLEDGE_NO_ORDS` 사용자는 `ORDS` 태그가 붙은 첫 번째·두 번째 청크가 거부되어 결과가 없다.
- `agent_sales` 사용자는 `SALES` 값이 붙은 세일즈 청크 본다.
- `agent_hr` 사용자는 `HR` 값이 붙은 청크만 본다.
- `KNOWLEDGE_INTERNAL` 사용자는 `INTERNAL` 값이 붙은 청크를 보되, `FINANCE` 거부 규칙이 있으면 재무 청크는 제외된다.
- 태그 권한이 전혀 없는 사용자는 결과가 없다.
## 5. ORDS API 계약
@@ -114,10 +114,10 @@ Content-Type: application/json
"chunk_id": 28001,
"document_id": "knowledge-001",
"chunk_no": 1,
"title": "Spring Boot VPD 시작하기",
"title": "세일즈 파이프라인 운영 가이드",
"chunk_text": "...",
"source_uri": "kb://security/spring-boot-vpd",
"tech_tag": "ORDS,SPRING_BOOT",
"source_uri": "kb://business/sales/pipeline",
"tech_tag": "INTERNAL,SALES",
"score": 0.0123
}
]
@@ -137,6 +137,8 @@ Content-Type: application/json
5. `29_agent_ords_vector_search_ords.sql``cb-agent-vector/search` ORDS 모듈/핸들러 (CB_ORDS로 실행)
6. backoffice `/permissions`에서 역할별 `특정 기술 태그` 규칙을 저장
7. `30_agent_ords_vector_tag_vpd_test.sql` — OR와 DENY 우선순위 확인
8. DDS 기본 경로: `34_dds_token_data_grant_common_auth.sql` — 단일 기술 사용자·객체별 Data Grant predicate
9. 세일즈 시나리오: `36_dds_sales_knowledge_scenario.sql``agent_sales` + `TECH_TAG=SALES` + 단기 데모 토큰
backoffice의 `/objects` 화면은 이 객체에 일반 Handler를 생성하지 않도록 막는다. 일반 Handler를 사용하면 벡터 컬럼을 그대로 반환할 수 있기 때문에, 임베딩을 응답에서 제외하고 벡터 거리 검색을 하는 전용 Handler만 허용한다.
@@ -160,11 +162,11 @@ backoffice의 `/objects` 화면은 이 객체에 일반 Handler를 생성하지
## 7. 운영 가이드라인
- 태그는 자유 문장보다 대문자·언더스코어 형태의 안정적인 ID로 관리한다. 예: `SPRING_BOOT`, `ORACLE_VPD`, `INTERNAL_ONLY`.
- 값은 자유 문장보다 대문자·언더스코어 형태의 안정적인 ID로 관리한다. 예: `SALES`, `HR`, `FINANCE`, `INTERNAL`.
- 현재 예시의 태그 ID 허용 문자는 영문·숫자·`_`·`-`다. 이 범위를 벗어난 값은 UI와 VPD 함수에서 모두 무시해 권한이 넓어지지 않게 한다.
- 동의어를 무분별하게 만들지 말고 태그 사전을 먼저 정한다. `SPRING`, `SPRINGBOOT`, `SPRING_BOOT`을 모두 별개로 만들면 권한 누락이 생긴다.
- 문서 청크에 최소 한 개의 기술 태그를 부여한다. 태그가 없는 청크는 이 뷰에 노출되지 않는다.
- 태그를 수정하면 권한 결과가 즉시 바뀐다. 임베딩을 다시 만들 필요가 없는 태그 변경과, 내용이 바뀌어 재임베딩해야 하는 변경을 구분한다.
- 동의어를 무분별하게 만들지 말고 분류값 사전을 먼저 정한다. `SALES`, `SALES_TEAM`, `SELLING`을 모두 별개로 만들면 권한 누락이 생긴다.
- 문서 청크에 최소 한 개의 업무 분류값을 부여한다. 값이 없는 청크는 이 뷰에 노출되지 않는다.
- 분류값을 수정하면 권한 결과가 즉시 바뀐다. 임베딩을 다시 만들 필요가 없는 변경과, 내용이 바뀌어 재임베딩해야 하는 변경을 구분한다.
- `TECH_TAG`는 행 접근용이고, 컬럼 민감도/마스킹은 별도 정책이다. “컬럼을 PUBLIC으로 표시했다”는 사실이 행 조회 권한을 부여하지 않는다.
- 벡터 차원은 ingestion 모델과 테이블 데이터가 일치해야 한다. 차원이 다르면 ORDS가 4xx/5xx 오류를 반환하므로 모델 버전을 함께 기록한다.
@@ -203,6 +205,7 @@ Bearer token
| `agent_hr` | `28001`, `28002` (2건) |
| `agent_fin_self` | `28002` (1건) |
| `agent_all` | `28001`, `28002`, `28003` (3건) |
| `agent_sales` | `28004` (1건, `TECH_TAG=SALES`) |
이 방식의 의미는 “DDS가 토큰 문자열을 Data Grant 문법의 인자로 받는다”가 아니다. `DATA GRANT``ON` 대상 뷰와 저장된 SQL predicate를 선언하고, 토큰 함수가 그 predicate가 참조할 Context를 만든다. 따라서 현재 제품형 기본 시나리오는 **단일 DDS 기술 사용자 + 객체별 DATA GRANT predicate + 공통 업무 권한**이다. 여러 보호 뷰/테이블에는 각각 Data Grant를 만들고, 각 predicate에서 논리 권한 대상과 행 규칙을 매핑한다. 드라이버가 SQL 실행 전에 `EndUserSecurityContext`를 전달하는 순수 DDS Context 방식은 별도 확장 경계이며, 그 경우에는 `ORA_END_USER_CONTEXT`를 참조하는 일반 `DATA GRANT`를 설계한다.

View File

@@ -2,12 +2,26 @@
> **상태**: VPD·DDS 병행 검증 완료, 토큰 기반 객체별 Data Grant 적용 완료
> **추적성**: Redmine #565, #566 · DDS 구현 커밋 `1e48864`, `a5e70bc`
> **기준 구현**: `sql/adb/32_dds_vector_tag_setup.sql`, `sql/adb/34_dds_token_data_grant_common_auth.sql`, `dds-backoffice/src/main/java/com/cloudhandson/ddsbackoffice/service/DdsGrantPublisher.java`, `dds-backoffice/src/main/java/com/cloudhandson/ddsbackoffice/service/DdsVectorKnowledgeService.java`
> **기준 구현**: `sql/adb/32_dds_vector_tag_setup.sql`, `sql/adb/34_dds_token_data_grant_common_auth.sql`, `sql/adb/36_dds_sales_knowledge_scenario.sql`, `dds-backoffice/src/main/java/com/cloudhandson/ddsbackoffice/service/DdsGrantPublisher.java`, `dds-backoffice/src/main/java/com/cloudhandson/ddsbackoffice/service/DdsVectorKnowledgeService.java`
## 목적
VPD와 Oracle Deep Data Security(DDS)를 별도 인스턴스로 운영하면서도, 고객의 업무 권한을 정의하는 공통 테이블을 기준으로 같은 검색 시나리오를 비교한다. 고객마다 DDS 계정을 만드는 것이 목표가 아니다. 현재 기본 데모는 하나의 DDS 기술 사용자 세션에서 Bearer 토큰으로 Context를 만들고, 객체별 Data Grant predicate가 공통 권한 테이블을 읽어 결과를 제한한다.
## 주 시나리오: 세일즈 사용자의 지식 검색
PG/MySQL 원본 매트릭스는 DDS의 객체·행 Grant 동작을 설명하기 위한 보조 예제다. 제품 흐름의 주인공은 지식 검색이다.
```text
세일즈 사용자 agent_sales
→ Bearer 토큰으로 사용자 식별
→ SALES_KNOWLEDGE_ROLE의 ALLOW TAG SALES 확인
→ 벡터 청크의 TECH_TAG 컬럼 값 검사
→ TECH_TAG에 SALES가 포함된 지식만 검색 결과에 포함
```
문서 청킹·임베딩 단계에서 청크마다 `TECH_TAG` 값을 저장한다. 이 값은 단순한 화면용 태그가 아니라, DDS가 보호 VIEW의 행을 판단하는 업무 분류값이다. 따라서 `agent_sales` 토큰으로 검색하면 세일즈 파이프라인 가이드처럼 `TECH_TAG=SALES`인 청크만 반환되고 HR·FINANCE 등 다른 값의 청크는 벡터 유사도가 높아도 반환되지 않는다.
## 인스턴스 경계
| 구분 | VPD Demo | DDS Demo |
@@ -23,7 +37,7 @@ VPD와 Oracle Deep Data Security(DDS)를 별도 인스턴스로 운영하면서
## 동일하게 비교할 수 있는 것
- 사용자별 원본 접근 범위
- 업무 분류값별 지식 접근 범위
- 행 단위 허용/차단
- 권한 없는 사용자의 default deny
- 보호 VIEW 우회 시도 차단
@@ -75,6 +89,7 @@ WHERE EXISTS (허용된 TAG 중 하나가 청크 TAG와 일치)
| `agent_hr` | `SPRING_BOOT` OR `ORACLE_VPD` | `28001`, `28002` (2건) |
| `agent_fin_self` | `ORACLE_VPD` | `28002` (1건) |
| `agent_all` | 전체 허용 역할 | `28001`, `28002`, `28003` (3건) |
| `agent_sales` | `SALES` | `28004` (1건) |
토큰 행, 임시 함수, 임시 권한은 검증 직후 삭제했다. 이 결과는 “DDS 사용자 수 = 고객 사용자 수”가 아니라 “DDS 기술 사용자 1개 + 고객 권한 테이블” 모델이 동작함을 보여 준다.