ProcessCenter 탐색 어댑터 계약 (ExploreGateway 연동)
상태: 확정 · 구현: server/remoteExplore.ts (POST /api/explore/v1/search) · 테스트: server/remoteExplore.test.ts상대 서비스: ExploreGateway — 포털 탐색 조합 오케스트레이터(ExploreGateway/docs/SERVICE_BOUNDARIES.md, docs/control-plane-contract.md)
1. 방향 (2026-09-13 정정)
초기 작성된 위임 계획(ProcessCenter → 게이트웨이 호출)은 방향이 반대여서 폐기했다. 실제 구조는 다음과 같다.
Portal Control Center ──(control plane catalog·사용자 Scope)──▶ ExploreGateway
│
포털 explore.endpoint 호출 │ (service_token)
▼
ProcessCenter 어댑터
POST /api/explore/v1/search- ExploreGateway: catalog와 Scope의 교집합으로 어댑터를 선택·실행하고 결과를 정규화한다(부분 실패·timeout·
catalogStale포함). - ProcessCenter: 자신의 업무 데이터 검색 API를 어댑터로 제공하고 원본 접근정책을 최종 적용한다.
- 문서 인덱스·RAG·citation은 RAGCenter가 소유한다(ExploreGateway/ProcessCenter가 소유하지 않는다).
2. 엔드포인트
| 메서드 | 경로 | 설명 |
|---|---|---|
| POST | /api/explore/v1/search | ProcessCenter 업무 데이터(프로세스 노드) 검색 |
Catalog 등록 값(예):
{ "portalId": "process-center", "capabilities": ["search"], "explore": { "endpoint": "/api/explore/v1/search", "timeoutMs": 1500, "authMode": "service_token" } }3. 인증·컨텍스트
| 항목 | 규칙 |
|---|---|
| 인증 | Authorization: Bearer <EXPLORE_GATEWAY_API_KEY> — 상수 시간 비교, 미설정·불일치 시 401 explore_gateway_auth_required |
| 사용자 컨텍스트 | x-explore-subject-id 필수(없으면 400 explore_subject_required) — 게이트웨이가 인증한 사용자 식별자 |
| 상관관계 | x-explore-request-id(선택) — 없으면 서버가 생성하며 응답 meta.requestId로 에코 |
| 권한 | 각 어댑터가 자신의 원본 접근정책을 최종 적용한다(게이트웨이는 대체하지 않음) |
4. 요청·응답
요청
{ "query": "휴가 신청", "limit": 20 }query: 1~200자(공백 제거 후), 위반 시 400invalid_explore_querylimit: 1~30 정수(기본 20, 범위 밖은 잘라서 적용)
응답 200
{
"results": [
{ "id": "process:19", "kind": "record", "portalId": "process-center", "portalLabel": "Process Center",
"title": "운영 관리", "summary": "…", "meta": "L6 · L6-000001", "href": "/processes?node=19" }
],
"meta": { "requestId": "…", "subjectId": "a-user" }
}오류
| HTTP | error | 의미 |
|---|---|---|
| 400 | explore_subject_required | x-explore-subject-id 누락 |
| 400 | invalid_explore_query | query 길이 위반 |
| 401 | explore_gateway_auth_required | 서비스 토큰 미설정·불일치 |
| 500 | (express 기본 오류 처리) | 서버 내부 오류 — 게이트웨이는 해당 포털을 unavailablePortals로 기록 |
5. 관련 문서
- 기계 판독 계약:
docs/explore-adapter.openapi.json - ExploreGateway 경계·control plane 계약:
ExploreGateway/docs/SERVICE_BOUNDARIES.md,docs/control-plane-contract.md - 어댑터 온보딩·런타임 준비 상태:
ExploreGateway/docs/adapter-onboarding.md(카탈로그 등록PORTAL_EXPLORE_CATALOG_JSON, 키 주입, 게이트웨이 런타임 미구현 항목) - RAG 경계(RAGCenter 소유):
UiCenter/docs/SERVICE_BOUNDARIES.md,FileStore/docs/ARCHITECTURE.md
6. 개발 스택 연동 (검증 완료, 2026-09-13)
| 항목 | 값 |
|---|---|
| PC 어댑터 토큰 | EXPLORE_GATEWAY_API_KEY=local-dev-explore-gateway-key(게이트웨이 PORTAL_SERVICE_TOKENS_JSON과 동일 값) |
| 게이트웨이 → PC 접근 | 포털 baseUrl이 http://localhost:3000이므로 게이트웨이 컨테이너에서 loopback 전달자 사용 |
| 게이트웨이 → PortalConsole catalog | CONTROL_PLANE_ORIGIN=http://portal-console:3000 + directory:read 포털 API key |
| DS 카탈로그 정책 | PORTAL_EXPLORE_CATALOG_JSON={"process-center":{"capabilities":["search"],"endpoint":"/api/explore/v1/search","timeoutMs":1500}} |
| e2e 결과 | 게이트웨이(3500) 호출 → ProcessCenter 업무 데이터 1건 반환(partial=false, catalogStale=false) |
7. 탐색 UI 연동 준비 상태 (2026-09-13)
공통 UI는 준비 완료 — portal-foundation 0.6.155의 PortalExploreSearch + @hohyun94/portal-operations 0.3.0의 createExploreGatewayClient(PC vendor 반영 완료: hohyun94-portal-foundation-0.6.155.tgz·hohyun94-portal-operations-0.3.0.tgz).
const client = createExploreGatewayClient({ baseUrl: EXPLORE_GATEWAY_URL, getAccessToken: () => <사용자 토큰> });
<PortalExploreSearch onSearch={query => client.search({ query, limit: 20 })} />남은 게이트: 사용자 토큰 전달 방식 ⚠️
- 게이트웨이는 DirectoryService 사용자 토큰(JWT) 만 인증 수단으로 받는다(
aud=directory-user). - ProcessCenter는 로그인 시 DS 흐름에서
accessToken을 받지만 자체 서명 세션 쿠키만 보관하고 DS 토큰을 보관하지 않는다(브라우저도 HttpOnly라 읽을 수 없다). - 따라서 다음 중 하나를 결정해야 한다.
- 서버 프록시 + 위임 토큰(권장) — ✅ 구현·검증 완료: DS가
POST /api/internal/delegation-token(경로 B)을 제공하고, 개발 스택에서 위임 토큰으로 게이트웨이 탐색이 성공하는 것을 확인했다(DirectoryService/docs/delegation-token-contract.md). PC에는 서버 프록시 절차만 추가하면 된다. - DS 토큰 보관 — 로그인 시 받은 사용자 토큰을 사용자별로 안전하게 보관하고 만료 시 재발급(보안·운영 부담).
- (임시) 개발 스택에서만 서버가 단기 토큰을 발급해 검증 — 운영 사용 금지.
- 서버 프록시 + 위임 토큰(권장) — ✅ 구현·검증 완료: DS가
결정·구현 현황(2026-09-13): 1안 채택 · DS 측 D1(서비스 토큰 일반화)·D3(위임 발급, 경로 B)·D2(테넌트/등급 기본값) 구현 완료. PC 측 서버 프록시와 화면 연결도 완료했다(아래).
PC 구현(2026-09-13) ✔
| 구성 | 위치 | 내용 |
|---|---|---|
| 위임 토큰 클라이언트 | server/directoryDelegation.ts | issueDelegationToken({ subject, audience, scope }) — DS /api/internal/delegation-token 호출, 만료 30초 전까지 캐시 |
| 통합 탐색 서비스 | server/exploreGatewaySearch.ts | searchAcrossPortals() — openId(directory:<id>) → subject 변환 → 위임 토큰 → PF createExploreGatewayClient로 게이트웨이 호출, 미설정 시 configured:false |
| 보호된 절차 | server/routers/explore.ts | explore.gatewaySearch(query — 상단 검색창이 입력 중 호출) — 401→UNAUTHORIZED, 그 외→BAD_GATEWAY |
| 화면 | client/src/pages/Home.tsx(상단 검색창) + App.tsx | 별도 /explore 화면은 없앴다 — 옛 주소는 /processes 로 되돌리고, 연합 결과는 상단 검색창이 href 로 연다 |
| 환경 | compose | PC: EXPLORE_GATEWAY_URL·EXPLORE_GATEWAY_AUDIENCE=explore-gateway / 게이트웨이: PORTAL_CONSOLE_JWT_AUDIENCE=explore-gateway |
검증: 단위 테스트 5건(발급·캐시·오류·미설정·subject 변환) + 개발 스택 전체 체인 — 위임 토큰(aud=explore-gateway) 발급 → 게이트웨이 탐색 성공(업무 데이터 반환) · 게이트웨이 헬스 adapters:["process-center"] · PC 200.
참고: PC의 사용자 openId는 directory:<directoryUserId> 형식이므로, 위임 요청의 subject는 접두어를 제거한 값이다.
§14 통합 답변(answer) — 정본은 중앙 RAG 하나다 (2026-10-05)
| 항목 | 상태 |
|---|---|
explore.gatewayAsk(셸 상단 검색의 질문 경로) | 게이트웨이 위임 단일 경로(server/exploreGatewaySearch.ts → 공용 createExploreGateway) |
| 로컬 RAG 폴백 | 제거 — 게이트웨이 장애·무응답은 빈 답(화면은 검색 결과만) 또는 BAD_GATEWAY 오류로 드러난다. 같은 질문에 두 답이 생기지 않게 한다 |
DISABLE_LOCAL_RAG_FALLBACK env | 제거(폴백이 없어져 스위치가 필요 없다) |
explore.answer 절차(로컬 RAG 직접 호출) | 제거 |
PC의 answer provider(POST /api/explore/v1/ask) | 제거 — 카탈로그에서도 process-center 의 answer 능력을 내린다(게이트웨이는 응답 없음으로 처리하고 다음 provider 를 쓴다) |
PC 자체 RAG 기능(trpc.rag.*·/rag 화면) | 유지 — 사내 문서 전용 화면이며 통합 탐색 경로와 분리한다 |
근거: 게이트웨이는 후보 provider 를 순서대로 시도해 첫 답에서 멈춘다. provider 가 둘이면 질문마다 답이 달라질 수 있어 사용자가 어느 답을 믿을지 판단할 수 없다 — 정본을 중앙 RAG(RagService) 하나로 둔다(ExploreGateway/docs/adapter-onboarding.md §3-2).
선행 조건: PC 문서가 RagService 색인에 있어야 한다(위임 scope rag:read·rag:sync·rag:admin + server/rag/ragIdentity.ts 의 uuid5 식별자로 발행). 적재가 끝나기 전 배포라면 PC 문서 근거 답이 나오지 않는다.