콘텐츠로 이동

ADR-0053: 고객 컨텍스트 제거 — MTS 위젯 직접 호출, Exposure Log 은퇴

  • 상태: 승인됨 (2026-08-17 — 구현 착수로 승인 전환, ALPHA-991 PR ②) — "바뀌지 않는 것"의 응답 형상·204 수렴 문면은 대체됨 → ADR-0054 (경로·면책 문구 동반·serving_scope 판정·캐시 방향은 불변, 2026-08-24)
  • 날짜: 2026-08-17
  • 관련: ALPHA-991 · ADR-0010(하이브리드 온프렘 — 노출 이력 항목 부분 대체) · ADR-0013(Exposure Log — 이 ADR 이 대체) · ADR-0031(Publication 리네이밍 — 호출자 문면 부분 대체) · ADR-0035(위젯 빌드 산출물 — 런타임 경로 문면 부분 대체) · ADR-0051(BYOC 배포 토폴로지) · ALPHA-501(요청 메트릭)

맥락

두 가지가 확정됐다(사용자 결정 2026-08-17): 개인화 기능을 두지 않는다, 그리고 고객 단위 노출 이력(민원/감사 재현) 요건을 유지하지 않는다. 둘째는 첫째에서 자동으로 따라오지 않는다 — ADR-0013 의 고객 해시는 개인화가 아니라 감사 재현을 위한 장치였다. 따라서 이 ADR 은 "개인화가 없으니 해시도 불요"라는 추론이 아니라, 감사 요건 자체를 유지하지 않는다는 제품 결정을 입력으로 받는다. 그 위에서 서빙 경로의 두 전제가 무너진다.

첫째, 고객 식별의 존재 이유가 사라진다. X-Customer-Hash(증권사 백엔드가 생성하는 고객 해시, ADR-0013)의 유일한 소비처가 고객 단위 노출 이력이었다. 요건이 사라지면 이 헤더는 필수 검증(400)만 남은 빈 계약이다.

둘째, 증권사 백엔드 경유 호출 전제가 풀린다. 지금 계약 (docs/contracts/publication-api.md)은 "증권사 백엔드가 호출하는 조회 표면"이고, ADR-0035 는 런타임 경로를 "위젯 → 증권사 백엔드/API GW → Publication API"로 문면 확정했다. 경유의 이유는 ① 고객 해시를 생성할 주체가 백엔드뿐이고 ② 원본 고객 ID 비수취 경계를 서버 간 호출로 지키기 위함이었다. ①이 사라지면 ②는 "받을 고객 데이터 자체가 없음"으로 더 강하게 성립하므로, 경유를 강제할 근거가 없다.

배포 형태가 증권사 클라우드 계정(BYOC, ADR-0051)으로 확정되어 있으므로 MTS 위젯(빌드 산출물, ADR-0035)이 publication-api 를 직접 호출해도 트래픽은 증권사 통제 표면 안에서 끝난다 — ADR-0010 의 경계는 벤더/증권사이지 증권사 내부의 호출 토폴로지가 아니다. 데모는 현행 계약대로 중계 형상이다(위젯 → mock-broker /api/broker/ai-analysis → publication-api, CloudFront 는 mock-broker 를 프록시) — mock-broker 의 고객 해시가 형식 요건만 채우는 껍데기 값이라는 사실이 이 전환의 방증이고, 직접 호출 형상 자체는 이 ADR 구현에서 데모 중계 층을 걷어내며 처음 성립한다.

한편 관측 축은 이미 분리되어 있다 — serving_request_metric(ALPHA-501)은 요청 수·상태·에러 코드를 기록하는 관측 원장이고, 설계 시점부터 "고객 식별자· 문구를 싣지 않는다"를 규약으로 갖는다. 고객 축이 사라진 뒤 서빙 경로에 남는 기록 요구는 이 관측 축이 전부다.

결정

  1. MTS 위젯이 publication-api 를 직접 호출한다. "증권사 백엔드가 호출하는 조회 표면" 전제를 계약에서 걷어낸다. 서빙 응답은 게시분(Published)·비개인화 공통 콘텐츠·읽기 전용이라 단말 직접 노출에 적합하고, 제공 범위 차단은 지금처럼 204 수렴으로 증권사 단말에 제외 사실을 드러내지 않는다.
  2. 고객 식별을 폐지한다. X-Customer-Hash 헤더·MISSING_CUSTOMER_HASH (SERV4001)·서비스 관통 파라미터를 제거한다. "원본 고객 ID 를 받는 표면을 만들지 않는다"는 원칙은 유지되되, 근거가 "해시로만 받는다"에서 "고객 식별 자체를 받지 않는다"로 강해진다.
  3. X-Channel 도 함께 폐지한다(SERV4002·SERV4003 포함). 직접 호출에서 채널은 클라이언트 자가 신고 값이라 감사 근거가 못 되고, 위젯 단일 채널이라 구분 정보량도 없다. 채널 구분이 실제로 필요해지는 시점(HTS 등 제2 채널 실물)에 신뢰 가능한 공급 방식과 함께 재도입한다.
  4. Exposure Log 를 은퇴시키고 기록 요구는 serving_request_metric 이 흡수한다. ADR-0013 의 "조회=노출 기록, 민원 시점 상태 완전 재현" 요건을 폐기한다 — 고객 단위 재현은 고객 식별 없이는 원리적으로 불가능하고, 남는 관측 요구(어느 경로가 얼마나 호출되고 어떤 상태로 끝났는가)는 요청 메트릭이 이미 전담한다. 메트릭 스키마는 확장하지 않는다 — 노출 문구 스냅샷·티커 축을 옮겨 싣는 것은 폐기한 감사 요건을 관측 원장에 되살리는 일이다. ExposureLogRecorder·ExposureLog·exposure_log 테이블을 제거한다 (코드 은퇴 선행 → 수축 마이그레이션의 확장수축 순서, 적용분 수정 금지).
  5. 서빙 표면은 인증 없는 공개 읽기 표면임을 명시적으로 결정한다. 고객 식별을 받지 않으므로 요청 단위 인증으로 보호할 개인 자산이 응답에 없고, 콘텐츠 자체의 공개 등급도 판단한다 — 응답은 전 고객 동일하게 노출될 것을 전제로 게시·검수된 비개인화 투자 참고 정보다(Published 만 서빙·serving_scope 차단은 유지). 남는 위험은 무단 열거·재배포와 트래픽 증폭인데, 이는 콘텐츠의 상업적 이용 권한 문제라 요청 인증이 아니라 증권사-벤더 계약과 노출면 통제의 일로 분류한다 — 의도적 트레이드오프이며, 열거를 막아야 하는 요구가 실물이 되면 단말 무관 토큰(위젯 임베드 키) 층을 그때 결정한다.

진입 형상의 기본형은 위젯 배포 도메인과 동일 오리진의 경로 프록시다 (설명 조회 경로 → publication-api — 데모 CloudFront 프록시와 동형). 이 형상에서 CORS 는 불요하고, rate limit·WAF 등 완화 통제는 그 프록시(증권사 엣지) 소관으로 계약 문서에 책임 경계를 명시한다(벤더 불관여 원칙 — 해시 생성 규칙·salt 가 증권사 영역이었던 것과 같은 결) — 인증 없는 표면이므로 호출 주체 판별·차단은 어느 층도 약속하지 않으며, 이는 위의 열거 위험 수용과 같은 트레이드오프다. 프록시는 쿠키·Authorization 등 인증 헤더를 오리진에 전달하지 않는다(strip) — 위젯 도메인의 MTS 세션 토큰이 publication-api 액세스 로그에 흘러들면 고객 식별 비수취 결정이 뒤로 무너진다. 이 strip 요건은 계약 문서에 명시한다. publication-api 의 CORS 설정(허용 오리진 = 위젯 도메인, 테넌트별 설정값)은 별도 API 호스트를 여는 테넌트에만 성립 요건으로 추가된다 — 어느 형상이든 CORS 는 방어 수단이 아니다. ADR-0051 의 단계 3 internal ALB 와는 충돌하지 않는다 — 공개 진입은 위젯 도메인 프록시 한 곳이고 internal ALB 는 그 뒤 내부 라우팅이다.

단, 요청당 DB 쓰기가 남는 한 남용 통제는 공개 토폴로지의 전제 조건이다 (선택적 위임이 아니다). exposure_log INSERT 는 소멸하지만 serving_request_metric 은 /api/** 전 요청을 — 미매칭 경로·실패 응답까지 — 행으로 적재하고 보존 상한이 없어, 비인증 표면에서는 임의 클라이언트가 무한 쓰기를 유발할 수 있다. 따라서 ① 엣지 rate limit 은 계약 문서에서 "권고"가 아니라 공개 노출의 필수 전제로 명시하고, ② publication-api 자체도 엣지 구성 품질에 생사를 걸지 않도록 메트릭 기록의 자체 상한(미매칭 경로 미기록·보존/집계 정책 중 최소 하나)을 구현 티켓 범위에 넣는다 — 구체 방식은 구현에서 정한다.

이 결정으로 바뀌지 않는 것: 서빙 표면 자체(GET /api/v1/explanations/{etf_ticker} — 경로·응답 형상·204 수렴·면책 문구 동반), 제공 범위(serving_scope) 판정, 게시분 캐시 방향(ALPHA-433 — 직접 호출로 요청량이 단말 수에 비례하게 되어 오히려 전제가 강해진다), 하이브리드 플레인 분리와 데이터 비반출·Pull 단방향(ADR-0010 — 단 같은 ADR 의 "노출 이력을 온프렘으로 이동" 항목은 노출 이력 축 자체가 소멸하므로 이 ADR 이 부분 대체한다), Cloud 의 비개인화 원칙(원래부터 개인 데이터가 없던 플레인이라 무변경).

대안

  • 경유 전제를 유지하고 헤더만 optional 로 — 호출 주체가 증권사 백엔드로 남는 한 위젯→백엔드→publication-api 의 중계 구현을 증권사에 요구하게 된다. 개인화가 없는데 중계가 하는 일은 헤더 부착뿐이라 납품 협의 표면만 늘린다. 배제.
  • exposure_log 를 고객 축만 빼고 유지(문구 스냅샷·티커·시각) — 감사 요건 없이 남는 것은 "요청당 텍스트 스냅샷 적재"라는 쓰기 비용뿐이다. 이 INSERT 는 200 응답이 수행하는 동기 쓰기 2건(exposure_log·serving_request_metric)의 한 축이다 — 요건이 사라진 쓰기를 성능 비용을 내며 유지할 이유가 없다. 노출 문구 재현이 다시 요건이 되면 그때 게시 이력(publication)+정책 이력(policy_version)의 역산이 아니라 스냅샷 재도입을 정면으로 결정한다. 배제.
  • 채널(X-Channel)만 존치 — 자가 신고 값의 통계는 엣지(CloudFront·ALB) 로그로도 얻는다. 검증 로직·에러 코드 2종을 유지할 근거가 안 된다. 배제.
  • 위젯 전용 BFF(backend-for-frontend) 신설 — 직접 노출이 불안하면 세울 수 있는 층이나, 지금 요구(CORS·읽기 전용 조회)에 대해 모듈 하나가 통째로 과잉이다(온프렘 반입 컴포넌트 최소화, ADR-0051 결정 6과 같은 계열). 배제.

결과

  • 얻는 것: 증권사 측 납품 요구사항에서 "고객 해시 생성·중계 구현"이 통째로 빠진다 — 위젯 임베드 + 엣지 프록시 구성만 남는다. 서빙 hot path 의 요청당 INSERT 1건(exposure_log)이 사라진다. 사문화된 인접 티켓 2건(ALPHA-778 해시 평문 로그 제거·ALPHA-775 면책 문구 스냅샷)이 함께 종결된다.
  • 잃는 것/트레이드오프: 고객 단위 축이 통째로 사라진다 — 요청에 고객 식별자가 없으므로 "그 고객이 접속했는가"조차 어느 로그로도 귀속할 수 없다 (엣지 접속 로그는 IP·경로뿐). 노출 시점의 "요청 ↔ 실제 응답 문구" 결합도 사라진다 — exposure_log 의 summary_snapshot 이 그 유일한 기록이었다. 다만 문구 변경 이력 자체는 남는다: publication 의 문구는 in-place 갱신이지만 운영자 정정은 append-only console_action_log 에 전후 문구가 기록되므로, 민원 대응은 "게시·정정 이력 + 정책(policy_version) 이력"의 시점 재구성 수준으로 낮아진다(특정 고객에게 무엇이 나갔는가는 재구성 불가). 이 강등은 감사 요건을 유지하지 않는다는 제품 결정의 직접 귀결이며, 요건이 되살아나면 스냅샷 재도입을 정면으로 재결정한다. API 가 단말에 직접 노출되어 엣지 보호가 증권사 구성 품질에 의존한다(계약 문서로 경계만 명시). 채널 축 관측이 사라진다(재도입 조건 명시로 상쇄).
  • 따라오는 의무: ~~ADR-0013 대체됨 표기, ADR-0010(노출 이력 항목)·ADR-0031 (호출자 문면 + 노출 기록·exposure 컴포넌트 소유 조항)·ADR-0035(런타임 경로 문면 + "계약 영향 없음·새 데이터 표면 없음" 결과 조항)·ADR-0038(ExposureLog = 유일 가변 JPA 엔티티 문면) 부분 대체 표기, README 색인~~(승인 전환과 함께 이행 — ALPHA-991 PR ②), ~~ADR-0033 부분 대체 표기~~(데모 재배선 ALPHA-992 에서 이행), ~~사문화 티켓 2건(ALPHA-778·775) 종결~~(취소 처리 완료). 문서 정합 — docs/domain/exposure-log.md· data-residency.md(고객별 노출 이력 서술)·docs/context.md·state-machine.md 등 호출 경로·노출 기록을 문면으로 갖는 문서 전수는 구현 PR 의 docs-sync 게이트가 이 ADR 을 기준으로 쓸어낸다. 계약 문서(publication-api.md·openapi.yaml· README) 재작성은 구현 PR 과 동행. 구현 순서 — ① 코드 은퇴+호출자 정리 (demo 위젯의 mock-broker 중계 제거는 proxy-site terraform 변경과 한 단위다 — 현행은 전 경로 단일 mock-broker 오리진이므로, 설명 조회 경로만 별도 behavior 로 publication-api 에 분리하고 정적 자산·시세·차트(/api/broker/ quotes·chart)는 mock-broker 에 남긴다. 오리진을 통째로 바꾸면 데모 화면 전체가 깨진다. publication-api 는 현행 compose 에서 호스트 포트 미공개 (내부망 전용)라 호스트 포트 공개 + CloudFront 전용 SG 제한을 한 단위의 신뢰경계 변경으로 함께 배선해야 한다 — behavior 만 추가하면 오리진 연결이 없어 502 다. 인증 헤더 strip 동반, loadtest·compose 예시 동반) ② 수축 마이그레이션(exposure_log drop)+dbml 재생성.