PortalConsole Control Plane 계약
목적
ExploreGateway는 PortalConsole를 업무 데이터를 제공하는 서비스가 아니라 포털 카탈로그와 실행 정책을 제공하는 Control Plane으로 참조한다. 실제 검색·문서·RAG 데이터는 각 도메인 서비스가 소유한다.
엔드포인트
GET /api/directory/v1/control-plane/catalog
Authorization: Bearer <ExploreGateway portal API key>호출 주체는 서버 간 API Key로만 인증한다. 브라우저나 사용자 JWT에서 이 계약을 직접 호출하지 않는다. API Key에는 기존 directory:read scope를 사용하되, 향후 전용 scope가 필요해지면 control-plane:read로 확장할 수 있다.
응답 계약
{
"data": {
"contractVersion": "1",
"service": "portal-control-center",
"generatedAt": "2026-08-29T00:00:00.000Z",
"dataVersion": 42,
"portals": [
{
"portalId": "process-center",
"portalName": "Process Center",
"baseUrl": "https://process.example.com",
"enabled": true,
"capabilities": ["search", "answer"],
"explore": {
"endpoint": "/api/explore/v1/search",
"answerEndpoint": "/api/explore/v1/ask",
"timeoutMs": 1500,
"authMode": "service_token"
}
}
]
},
"meta": {
"requestId": "a-request-id",
"generatedAt": "2026-08-29T00:00:00.000Z"
}
}책임 경계
| 필드 | 소유자 | 설명 |
|---|---|---|
portalId, portalName, baseUrl | PortalConsole | 등록된 포털 식별·표시·접속 메타데이터 |
enabled | PortalConsole | 운영자가 활성화한 포털만 Gateway가 선택 |
capabilities | PortalConsole 정책 | 포털이 제공하는 탐색 기능의 선언 |
explore.endpoint | 포털 운영 계약 | Gateway가 호출할 검색 API 경로 |
explore.answerEndpoint | 포털 운영 계약 | Gateway가 호출할 답변 API 경로(없으면 endpoint로 폴백) |
timeoutMs | Control Plane 정책 | 포털별 최대 대기 시간 |
| 실제 검색 결과 | 각 포털 | 원본 업무 데이터 및 포털 내부 권한 집행 |
| RAG 답변·citation | RagService | 근거 수집·답변 생성·출처 반환 |
오류와 하위 호환성
Gateway는 알 수 없는 capability를 무시하고, contractVersion이 지원 범위 밖이면 해당 카탈로그를 사용하지 않는다. 특정 포털의 endpoint가 실패하면 다른 포털 결과를 유지하고 unavailablePortals에 기록한다. Catalog 조회 자체가 실패한 경우에는 캐시된 마지막 정상 카탈로그를 짧은 TTL 동안 사용할 수 있지만, 만료된 카탈로그로 신규 포털을 호출해서는 안 된다.
보안 원칙
PortalConsole는 포털 접근 정책을 제공하지만, Gateway는 클라이언트가 전달한 포털 목록을 신뢰하지 않는다. 사용자 scope와 catalog의 교집합을 사용하고, 포털 어댑터는 원본 서비스에서 최종 권한을 재검증한다. baseUrl과 endpoint는 등록·검증된 값만 허용하며, 임의 URL을 사용자 입력으로 받지 않는다.
Registry 선택 규칙
ExploreGateway는 다음 조건을 모두 만족하는 경우에만 어댑터를 실행한다.
- 카탈로그의
enabled가true이다. - 요청 capability가 포털의
capabilities에 포함된다. - 사용자 Scope에 포털 ID가 포함된다.
- 해당 포털 ID에 대응하는 Gateway 어댑터가 등록되어 있다.
카탈로그에는 등록되어 있지만 어댑터가 아직 배포되지 않은 포털은 unavailablePortals로 반환한다. 이 상태는 전체 요청을 실패시키지 않으며 partial: true로 표시한다.
답변(answer) 실행
answer capability가 켜진 포털(예: RagService — 경계표에서 RAG 답변·citation 소유)은 POST <explore.answerEndpoint> 로 질문을 받아 근거 있는 답과 출처를 돌려준다. 게이트웨이는 POST /api/explore/v1/ask 로 이를 노출한다.
- 요청:
{ "question": string(2~2000), "limit"?: number(1~10) }— 사용자 토큰(DirectoryService) 검증은search와 동일 - 헤더:
Authorization: Bearer <게이트웨이 서비스 토큰>(게이트웨이 인증) +x-explore-subject-id+x-explore-user-token(게이트웨이가 검증한 사용자 원본 토큰). RLS(조직·보안등급)는 provider 가 이 사용자 토큰으로 직접 강제한다 — subject id 만으로는 클레임을 알 수 없기 때문이다. - 응답(provider):
{ "answer": string, "citations"?: [{ title, href? }] } - 게이트웨이 응답:
{ answer: { answer, citations?, providerId, providerLabel } | null, partial, unavailablePortals, requestId, catalogStale? } - 실행 규칙:
answer를 구현하고 catalog 에서answercapability가 켜진 어댑터만 후보로 삼아 순서대로 시도하고 첫 답에서 멈춘다(비용). 어느 provider 도 답하지 못하면answer: null.
검색(find)과 답변(ask)은 분리된 경로다 — 화면은 입력 중에는 search, 사용자가 명시적으로 제출할 때만 ask 를 호출한다(LLM 비용·지연, 그리고 "명시적 요청 시에만 RAG" 원칙).
카탈로그는 기본 30초 동안 메모리 캐시한다. Control Plane 조회가 실패하면 마지막 정상 카탈로그를 최대 5분까지 stale 상태로 사용할 수 있다. 이 시간이 지나면 신규 요청은 실패시켜 오래된 정책으로 포털을 호출하지 않도록 한다. 실행 결과에는 catalogStale 상태를 포함해 운영자가 정책 최신화 여부를 확인할 수 있게 한다.