diff --git a/src/main/java/com/cloudhandson/vpdbackoffice/service/McpSseService.java b/src/main/java/com/cloudhandson/vpdbackoffice/service/McpSseService.java index e650245..e51fa46 100644 --- a/src/main/java/com/cloudhandson/vpdbackoffice/service/McpSseService.java +++ b/src/main/java/com/cloudhandson/vpdbackoffice/service/McpSseService.java @@ -103,7 +103,7 @@ public class McpSseService { if (isVectorTool(tool)) { ObjectNode embedding = objectMapper.createObjectNode(); embedding.put("type", "array"); - embedding.put("description", "외부 임베딩 모델이 만든 검색 벡터. 이 예제 fixture는 4차원입니다."); + embedding.put("description", "외부 임베딩 모델이 만든 검색 벡터. 개발 환경에서는 4차원 벡터를 사용합니다."); ObjectNode items = objectMapper.createObjectNode(); items.put("type", "number"); embedding.set("items", items); diff --git a/src/main/java/com/cloudhandson/vpdbackoffice/service/OpenAiCompatibleClient.java b/src/main/java/com/cloudhandson/vpdbackoffice/service/OpenAiCompatibleClient.java index 210f05e..5c28017 100644 --- a/src/main/java/com/cloudhandson/vpdbackoffice/service/OpenAiCompatibleClient.java +++ b/src/main/java/com/cloudhandson/vpdbackoffice/service/OpenAiCompatibleClient.java @@ -64,7 +64,7 @@ public class OpenAiCompatibleClient { public List embedding(String input) { if (!embeddingConfigured()) { - throw new AppException("AI 임베딩 설정이 없습니다. BACKOFFICE_AI_EMBEDDING_MODEL을 설정하거나 데모 임베딩을 선택하세요."); + throw new AppException("AI 임베딩 설정이 없습니다. BACKOFFICE_AI_EMBEDDING_MODEL을 설정하거나 로컬 임베딩(개발용)을 선택하세요."); } BackofficeProperties.Ai ai = properties.ai(); diff --git a/src/main/java/com/cloudhandson/vpdbackoffice/service/ProtectedObjectService.java b/src/main/java/com/cloudhandson/vpdbackoffice/service/ProtectedObjectService.java index f3d0ec2..cbe851a 100644 --- a/src/main/java/com/cloudhandson/vpdbackoffice/service/ProtectedObjectService.java +++ b/src/main/java/com/cloudhandson/vpdbackoffice/service/ProtectedObjectService.java @@ -138,7 +138,7 @@ public class ProtectedObjectService { defaultOrdsPath(normalizedOwner, normalizedObjectName), String.join(",", columns), "", - "DB 객체를 한 개의 ORDS 조회 Handler로 노출하는 예제 대상" + "DB 객체를 한 개의 ORDS 조회 Handler로 노출하는 기본 구성" ); long objectId = mapper.nextObjectId(); mapper.insertObject(objectId, command); diff --git a/src/main/java/com/cloudhandson/vpdbackoffice/service/VectorKnowledgeService.java b/src/main/java/com/cloudhandson/vpdbackoffice/service/VectorKnowledgeService.java index 24ceac7..0646d20 100644 --- a/src/main/java/com/cloudhandson/vpdbackoffice/service/VectorKnowledgeService.java +++ b/src/main/java/com/cloudhandson/vpdbackoffice/service/VectorKnowledgeService.java @@ -65,7 +65,7 @@ public class VectorKnowledgeService { chunks, tags, registered, - embeddingClient.embeddingConfigured() ? embeddingClient.embeddingModelName() : "DEMO-4D" + embeddingClient.embeddingConfigured() ? embeddingClient.embeddingModelName() : "로컬 임베딩(개발용)" ); } @@ -120,7 +120,7 @@ public class VectorKnowledgeService { "CB_VECTOR_SEARCH_DOCUMENTS 보호 객체가 없습니다. 28_agent_ords_vector_tag_vpd_setup.sql을 먼저 실행하세요.")); String mode = normalizeMode(embeddingMode); String requestBody = "{\"embedding\":" + json(embed(normalizedQuery, mode)) + "}"; - IssuedToken temporary = tokenService.issueTemporaryToken(userId, "벡터 지식자료 검색 임시 실행"); + IssuedToken temporary = tokenService.issueTemporaryToken(userId, "지식 검색 검증 세션"); try { ProbeResult probe = ordsProbeService.runProbe(new ProbeCommand( temporary.keyId(), vectorObject.objectId(), temporary.plainToken(), normalizeLimit(limit), requestBody)); @@ -223,16 +223,16 @@ public class VectorKnowledgeService { private String normalizeMode(String mode) { String normalized = mode == null ? DEMO_MODE : mode.trim().toUpperCase(Locale.ROOT); if (!DEMO_MODE.equals(normalized) && !AI_MODE.equals(normalized)) { - throw new AppException("임베딩 방식은 DEMO 또는 AI만 사용할 수 있습니다."); + throw new AppException("임베딩 방식은 로컬 임베딩 또는 AI 임베딩만 사용할 수 있습니다."); } if (AI_MODE.equals(normalized) && !embeddingClient.embeddingConfigured()) { - throw new AppException("AI 임베딩이 설정되지 않았습니다. 설정에서 embedding model/API key를 추가하거나 DEMO 임베딩을 선택하세요."); + throw new AppException("AI 임베딩이 설정되지 않았습니다. 설정에서 embedding model/API key를 추가하거나 로컬 임베딩(개발용)을 선택하세요."); } return normalized; } private String embeddingModel(String mode) { - return AI_MODE.equals(mode) ? embeddingClient.embeddingModelName() : "DEMO-4D"; + return AI_MODE.equals(mode) ? embeddingClient.embeddingModelName() : "로컬 임베딩(개발용)"; } private String json(List values) { diff --git a/src/main/resources/templates/dashboard.html b/src/main/resources/templates/dashboard.html index 1bb6d24..73a1587 100644 --- a/src/main/resources/templates/dashboard.html +++ b/src/main/resources/templates/dashboard.html @@ -7,7 +7,7 @@
권한을 정하면 DB가 그대로 지킵니다

누가 어떤 데이터를 볼 수 있는지 설계하고, 실제 결과까지 확인하세요.

-

권한 설계 → DB 보호 연결 → 토큰 발급 → 실제 결과 확인

+

권한 설계 → DB 보호 연결 → 검증 세션 → 실제 결과 확인

전체 흐름 설명 보기

사용자·그룹·역할에 권한을 연결하면 Oracle VPD가 요청할 때마다 그 규칙을 읽어 허용된 행만 반환합니다. 별도 SQL 필터를 만드는 일은 예외적인 고급 작업입니다.

@@ -40,9 +40,9 @@ 3
-

3. 토큰 발급

+

3. 검증 세션

검증할 사용자를 선택해 토큰을 발급하고, 한 번만 보이는 원문을 복사합니다.

- 테스트 토큰 발급하기 → + 검증 세션 발급하기 →
diff --git a/src/main/resources/templates/fragments/layout.html b/src/main/resources/templates/fragments/layout.html index 77a4663..f30ace3 100644 --- a/src/main/resources/templates/fragments/layout.html +++ b/src/main/resources/templates/fragments/layout.html @@ -29,7 +29,7 @@ @@ -38,11 +38,11 @@
@@ -76,8 +76,8 @@
3 · IDENTITY - 토큰 발급 -

검증할 사용자를 나타내는 일회성 원문 토큰을 준비합니다.

+ 검증 세션 +

확인할 사용자를 나타내는 일회성 토큰을 준비합니다.

diff --git a/src/main/resources/templates/fragments/ords-handler-source.html b/src/main/resources/templates/fragments/ords-handler-source.html index e12fe26..b22b757 100644 --- a/src/main/resources/templates/fragments/ords-handler-source.html +++ b/src/main/resources/templates/fragments/ords-handler-source.html @@ -8,7 +8,7 @@ ADMIN.TABLE_NAME / 기본 ORDS Handler PL/SQL -

이 소스는 VPD context를 설정한 뒤 한 테이블을 SELECT하는 시작 예제입니다. 실제 수정·저장은 ORDS 핸들러 화면에서 parsing schema 권한으로 진행하세요.

+

이 소스는 VPD context를 설정한 뒤 한 테이블을 SELECT하는 기본 구성입니다. 실제 수정·저장은 ORDS 핸들러 화면에서 parsing schema 권한으로 진행하세요.

source
diff --git a/src/main/resources/templates/fragments/vector-search-result.html b/src/main/resources/templates/fragments/vector-search-result.html index 9ab5a35..1bf1aed 100644 --- a/src/main/resources/templates/fragments/vector-search-result.html +++ b/src/main/resources/templates/fragments/vector-search-result.html @@ -2,7 +2,7 @@
-
임베딩DEMO-4D
+
임베딩로컬 임베딩(개발용)
검색 결과0건
@@ -14,11 +14,11 @@ - - - - - + + + + + diff --git a/src/main/resources/templates/mcp-chatbot.html b/src/main/resources/templates/mcp-chatbot.html index a5a1a33..2fd5b17 100644 --- a/src/main/resources/templates/mcp-chatbot.html +++ b/src/main/resources/templates/mcp-chatbot.html @@ -21,10 +21,10 @@
질문 실행 -

예시를 선택하거나 질문을 직접 입력하세요.

+

추천 질문을 선택하거나 질문을 직접 입력하세요.

- 토큰과 조회 결과 설명 보기 -

Bearer Token 원문을 붙여 넣거나 사용자를 선택해 10분 임시 토큰으로 실제 ORDS/VPD 결과를 조회할 수 있습니다.

+ 세션과 조회 결과 설명 보기 +

Bearer Token 원문을 붙여 넣거나 사용자를 선택해 10분 검증 세션으로 실제 ORDS/VPD 결과를 조회할 수 있습니다.

@@ -47,12 +47,12 @@ placeholder="비우면 라우팅만 확인합니다.">
-
Java MCP client 실행 결과가 여기에 표시됩니다.
+
Java MCP client 확인 결과가 여기에 표시됩니다.
diff --git a/src/main/resources/templates/mcp-reasoning.html b/src/main/resources/templates/mcp-reasoning.html index cac4ce1..0038fdf 100644 --- a/src/main/resources/templates/mcp-reasoning.html +++ b/src/main/resources/templates/mcp-reasoning.html @@ -31,22 +31,22 @@
- 기본 Handler 예제와 수정 가이드 보기 + 기본 Handler 구성과 수정 가이드 보기

cb_ords_handler_pkg.set_vpd_context(:auth_header)로 토큰의 사용자·역할 컨텍스트를 넣고, 선택한 한 테이블에 SELECT ... FROM OWNER.TABLE을 실행한 뒤 JSON으로 반환합니다. 이것은 유일한 사용 방식이 아니라 시작점이며, Handler 소스와 ORDS 메타데이터에서 수정할 수 있습니다. 컬럼 민감도·마스킹은 이 화면에서 다루지 않고 권한 관리의 원문 표시 허용 컬럼에서 별도로 설정합니다.

diff --git a/src/main/resources/templates/probe.html b/src/main/resources/templates/probe.html index 7b7be41..76219e0 100644 --- a/src/main/resources/templates/probe.html +++ b/src/main/resources/templates/probe.html @@ -25,7 +25,7 @@

토큰은 사용자를 찾는 열쇠입니다. 서버가 직접 역할과 그룹 상속 역할을 합치고, VPD가 저장된 행·열 규칙을 적용한 결과를 보여줍니다.

- 테스트 토큰이 없나요? 먼저 발급하기 + 검증 세션이 없나요? 먼저 발급하기
@@ -41,15 +41,15 @@ 1. 발급받은 토큰 원문 - 원문은 DB에 저장되지 않습니다. 아래 임시 토큰 사용자를 선택하면 이 입력은 무시되고 실행 중 발급·완료 즉시 회수됩니다. 둘 다 비우면 invalid token 결과를 확인할 수 있습니다. + 원문은 DB에 저장되지 않습니다. 아래 검증 세션 사용자를 선택하면 이 입력은 무시되고 실행 중 발급·완료 즉시 폐기됩니다. 둘 다 비우면 invalid token 결과를 확인할 수 있습니다.
diff --git a/src/main/resources/templates/vector-knowledge.html b/src/main/resources/templates/vector-knowledge.html index 0deb38c..9de34b8 100644 --- a/src/main/resources/templates/vector-knowledge.html +++ b/src/main/resources/templates/vector-knowledge.html @@ -1,15 +1,15 @@ - +
-

벡터 지식자료 데모

-

문서를 청크로 나누고 임베딩·기술 태그를 저장한 뒤, 사용자 권한에 맞는 청크만 검색합니다.

+

지식 검색 관리

+

지식자료를 등록하고 접근 정책에 따라 검색 결과를 제공합니다.

- 전체 시나리오 설명 보기 -

문서 본문을 청크로 나누고 각 청크를 벡터로 변환합니다. 청크마다 기술 태그를 붙이면 권한 규칙의 TAG 조건이 같은 태그가 붙은 행만 허용합니다. 마지막 검색은 Bearer Token으로 VPD를 통과한 청크만 벡터 거리순으로 반환합니다.

+ 검색 정책과 처리 방식 +

문서는 검색 단위로 나누고 각 단위를 임베딩으로 변환합니다. 기술 태그는 접근 정책의 TAG 조건으로 사용되며, 검색 시 DB 보호 정책이 허용된 지식자료만 남깁니다. Bearer Token은 검증 세션에만 사용되고 검색이 끝나면 폐기됩니다.

@@ -25,19 +25,19 @@
- 1 · INGEST -

문서 등록 → 청킹 → 임베딩 → 태그 저장

-

같은 문서 ID로 다시 저장하면 기존 청크를 교체합니다.

+ 1 · CONTENT +

지식자료 등록

+

같은 문서 ID로 저장하면 최신 내용으로 갱신됩니다.

- 0 chunks + 0개 검색 단위
- 청킹과 임베딩 방식 보기 + 분할·임베딩 설정 보기
  • 문단을 우선 묶고 길이가 길면 문장·공백 기준으로 나눕니다.
  • -
  • DEMO-4D는 외부 API 없이 동일한 4차원 예제를 재현하는 모드입니다. 의미 검색 품질을 제공하는 실제 임베딩은 아닙니다.
  • +
  • 로컬 임베딩(개발용)은 외부 API 없이 검색 흐름을 확인하는 4차원 처리 방식입니다. 운영 검색 품질을 위한 모델은 아닙니다.
  • AIBACKOFFICE_AI_EMBEDDING_MODEL과 API key가 설정된 OpenAI 호환 /v1/embeddings를 사용합니다.
  • -
  • 실제 운영에서는 문서 내용과 같은 임베딩 모델로 검색어도 임베딩해야 합니다.
  • +
  • 운영 환경에서는 자료 등록과 검색에 같은 임베딩 모델과 차원을 사용해야 합니다.
@@ -60,13 +60,13 @@ 태그는 대문자로 정규화됩니다. 예: SPRING_BOOT, ORACLE_VPD @@ -75,37 +75,37 @@ - +
document - 저장 완료 + 저장 완료
- 2 · AUTHORIZE -

태그 권한 설정

-

태그 권한은 기존 권한 관리에서 역할별 행 규칙으로 저장합니다.

+ 2 · ACCESS POLICY +

접근 정책 설정

+

역할별 기술 태그 접근 규칙은 권한 관리에서 관리합니다.

권한 관리 열기
-

예: 백엔드 역할

+

백엔드 지식 접근 정책

ALLOW TAG SPRING_BOOT
ALLOW TAG ORACLE_VPD

- 두 태그 중 하나가 붙은 청크를 허용합니다. + 두 태그 중 하나가 붙은 검색 단위를 허용합니다.
-

예: ORDS 제외 역할

+

ORDS 제외 정책

ALLOW TAG SPRING_BOOT
DENY TAG ORDS

- 허용 후보 중 ORDS 태그 청크를 다시 제외합니다. + 허용 후보 중 ORDS 태그 검색 단위를 다시 제외합니다.
- VPD predicate가 계산하는 식 보기 + 접근 정책이 적용되는 방식
(ALLOW TAG A OR ALLOW TAG B)
 AND NOT (DENY TAG C OR DENY TAG D)

허용 태그가 없거나 토큰 컨텍스트가 없으면 1=0으로 닫힙니다. 태그 권한은 행 접근 기준이고, 본문·임베딩 표시 보호는 컬럼 정책으로 별도 관리합니다.

@@ -116,25 +116,25 @@ AND NOT (DENY TAG C OR DENY TAG D)
3 · SEARCH -

권한 적용 벡터 검색

-

선택한 사용자에게 임시 토큰을 발급하고 검색 완료 즉시 회수합니다.

+

권한 기반 지식 검색

+

검색 권한 주체에 대한 검증용 세션을 사용하고 완료 후 폐기합니다.

Vector ORDS + th:text="${summary != null && summary.vectorObjectRegistered()} ? '검색 API 연결됨' : '검색 API 설정 필요'">검색 API
- 검색 실행 순서 보기 + 검색 처리 방식 보기
  1. 검색어를 같은 임베딩 방식으로 벡터화합니다.
  2. 임시 Bearer Token으로 전용 ORDS Handler를 호출합니다.
  3. -
  4. VPD가 TECH_TAG 권한에 맞지 않는 청크를 먼저 제거합니다.
  5. -
  6. 남은 청크를 벡터 거리순으로 반환합니다.
  7. +
  8. VPD가 TECH_TAG 권한에 맞지 않는 검색 단위를 먼저 제거합니다.
  9. +
  10. 남은 검색 단위를 관련도 순으로 반환합니다.
- +
-
검색하면 선택한 사용자에게 허용된 청크만 여기에 표시됩니다.
+
검색 권한에 따라 허용된 지식자료만 여기에 표시됩니다.
- 현재 저장 현황 보기 + 저장 현황 보기
문서0
-
청크0
+
검색 단위0
태그 연결0
diff --git a/src/main/resources/templates/vpd-filter-policies.html b/src/main/resources/templates/vpd-filter-policies.html index f8d6674..8d8704d 100644 --- a/src/main/resources/templates/vpd-filter-policies.html +++ b/src/main/resources/templates/vpd-filter-policies.html @@ -88,7 +88,7 @@
diff --git a/src/test/java/com/cloudhandson/vpdbackoffice/web/GuidedFlowTemplateTest.java b/src/test/java/com/cloudhandson/vpdbackoffice/web/GuidedFlowTemplateTest.java index 7f66fa6..d888fa2 100644 --- a/src/test/java/com/cloudhandson/vpdbackoffice/web/GuidedFlowTemplateTest.java +++ b/src/test/java/com/cloudhandson/vpdbackoffice/web/GuidedFlowTemplateTest.java @@ -16,7 +16,7 @@ class GuidedFlowTemplateTest { assertThat(html) .contains("1. 권한 설계") .contains("2. DB 보호 연결") - .contains("3. 토큰 발급") + .contains("3. 검증 세션") .contains("4. 결과 확인"); } @@ -27,7 +27,7 @@ class GuidedFlowTemplateTest { assertThat(probe) .contains("name=\"bearerToken\"") - .contains("임시 토큰 사용자") + .contains("검증 세션 사용자") .doesNotContain("name=\"tokenKeyId\""); assertThat(result) .contains("적용된 사용자와 권한") @@ -58,7 +58,7 @@ class GuidedFlowTemplateTest { assertThat(html) .contains("누가 어떤 행을 볼 수 있는지는 권한 규칙과 VPD에서 결정") - .contains("벡터 지식자료 검색 흐름") + .contains("지식 검색 연결 흐름") .contains("VPD context 설정과 기본 SELECT") .contains("전용 벡터 Handler 사용") .contains("원문 표시 허용 컬럼") @@ -103,8 +103,8 @@ class GuidedFlowTemplateTest { String client = template("mcp-client-demo.html"); String sse = template("mcp-sse.html"); - assertThat(chatbot).contains("임시 토큰 사용자").contains("태그 벡터 검색"); - assertThat(reasoning).contains("MCP tool을 고르고").contains("임시 토큰 사용자").contains("MCP Tool"); + assertThat(chatbot).contains("검증 세션 사용자").contains("태그 벡터 검색"); + assertThat(reasoning).contains("MCP tool을 고르고").contains("검증 세션 사용자").contains("MCP Tool"); assertThat(client).contains("tool 선택과 호출은 reasoning 결과").doesNotContain("Context Path"); assertThat(sse).contains("Instruction / parameter mapping").contains("bearerToken"); } @@ -115,10 +115,11 @@ class GuidedFlowTemplateTest { String result = template("fragments/vector-search-result.html"); assertThat(vector) - .contains("문서 등록 → 청킹 → 임베딩 → 태그 저장") + .contains("지식자료 등록") .contains("ALLOW TAG SPRING_BOOT") .contains("DENY TAG ORDS") - .contains("권한 적용 벡터 검색") + .contains("권한 기반 지식 검색") + .contains("로컬 임베딩(개발용)") .contains("/vector-knowledge/ingest") .contains("/vector-knowledge/search"); assertThat(result)
ChunkDocumentTitleTECH_TAGScore검색 단위 ID자료 ID제목기술 태그관련도 본문