친구 데이터 전용 접근점 분리 — 계정 전환 제거 + 일지 월 총중량 0kg 수리
- 기간: 2026-08-31 (하루)
- 랜딩: PR #1031 (Phase 0~3 묶음, CI 1회) · 마이그레이션 6개 Production 적용
- 설계서: 이슈 #1028 본문(분석·Phase 계획·예상 효과)
- 정본: 친구·그룹 접근 계약 — 전용 접근점 원칙·viewer/target 분리·전환 금지·접근점 장부
- 도구: Production 드라이런(마이그레이션 전문 적용→원자 롤백 + 행동 검증 8종), 정본 추출-치환 빌더(schema.sql 마지막 정의 기반 재발행)
- 게이트:
tests/react/friendAccessSqlContract.test.mjs— 친구/그룹 노출 함수 이름 스윕(전환 부재·게이트·장부 등재 상시 단언) - 버그리포트: BUG-052 (친구 일지 월 총중량 0kg)
- 계약: 본인 RPC 시그니처·payload 전부 불변(클라 수정은 일지 월 합계 배선뿐) — strict adapter 무접촉
Phase 현황
| Phase | 내용 | 상태 |
|---|---|---|
| Phase 0 | 전환 52곳 전수 장부 + 친구·그룹 접근 계약 문서 신설 | ✅ |
| Phase 1 | 친구 일지 월 총중량 수리(summary.volume + 하드코딩 0 제거) | ✅ |
| Phase 2-1~2-5 | 접근점 5개 엔진 분리·전환 제거 (PR·대시보드·볼륨·피드·그룹 세션) | ✅ |
| Phase 3 | 전환 재유입 방지 상시 게이트 + remote-schema 핀 갱신 | ✅ |
1. 배경
친구 스코프(8/25, #792)는 랜딩·일지·리포트·피드 4화면으로 친구 데이터를 보여준다. 데이터 공급의 4곳은 계정 전환 방식 — 팔로우 게이트 통과 후 트랜잭션 로컬로 JWT sub 클레임을 대상 유저로 바꿔 본인용 함수를 그대로 실행(8/23 친구 리포트 v1 기법) — 이었고, 일지 달력·즐겨찾기 2곳만 전용 조회였다. 그룹 멤버 세션 상세(#847)도 같은 전환 기법을 썼다.
2. 문제 제기
- 오너 보고(2026-08-31): "친구 일지 탭의 월별 중량 총합이 전부 0kg." — 조사 결과 친구 달력 응답에 중량이 아예 없고 클라이언트가
volume: 0을 하드코딩(BUG-052). - 구조 문제(조사 중 정리, 오너 결정으로 확정): 전환 방식은 ①원 함수가 돌려주는 전부가 자동으로 친구에게 공개된다 — 8/26 실사고: 피드 원 함수가 집계형으로 바뀌자 제3자 기록이 노출(20260821790000 교정) ②Supabase 내부 동작(auth.uid()가 어느 설정을 먼저 읽는지)에 의존 — 바뀌면 무음 오작동 ③전환 중 쓰기가 생기면 대상 명의로 기록될 위험 ④열람자 종속 값(liked_by_me 등)이 대상 관점으로 계산되는 잠재 부정확.
3. 해결 방안
원칙(오너 결정 D1, 2026-08-31): 다른 계정의 호출에 응답하는 지점은 계정 전환 없이 전용 접근점으로 분리한다. 계산이 비싸면 내부 엔진(대상 유저 명시 인자)을 본인 경로와 공유한다. D2: 통합 단일 함수(파라미터 분기, B안) 대신 입구 2개·엔진 1개(A안) — 사고 시 친구 문만 긴급 차단(8/26 실사용), 표면별 노출 축소, 본인 경로 무접촉, 접속 로그 식별이 근거.
접근: 데이터 종류마다 ①내부 엔진 …_engine(p_user_id, …)(기존 본문에서 auth.uid()만 인자 치환, 실행 권한 전면 회수) ②본인 문(시그니처·payload 불변, engine(auth.uid()) 위임) ③친구/그룹 문(게이트 → engine(p_user_id) 직결). 열람자 종속 값이 있으면 엔진이 p_viewer를 별도 인자로 받는다.
4. 적용한 내용
- Phase 0: 전환 52곳 분류(친구/그룹 5 vs 내부 배치·인입·정비 ~47곳은 범위 밖 명시),
docs/contracts/friend-access.md신설 + 사이드바 등록. - Phase 1:
get_following_calendar_month_v1에summary.volume(본인 일지 정본user_calendar_day_summaries구간 합) 추가,friendScopeStore/mobileApp배선, pgTAP +1. - Phase 2 (마이그레이션 5개, 각각 자가 검증 DO 동반):
- 2-1 PR 오버뷰:
get_pr_overview_engine_v1신설(하위 헬퍼는 이미 인자화 — 1함수 치환). - 2-2 홈 대시보드:
build_home_dashboard_core_v2(uuid,date) 재서명(drop 후 재생성) +get_home_dashboard_engine_v1(누적·레벨·PR 카운트·반영 계획 합류 포함) 신설. - 2-3 볼륨 리포트: 체인 5층(v1/v2/v3_core·annual_v3·선택자) 전부 p_user_id 관통 +
get_volume_overview_engine_v1. 친구 문은engine(대상, as_of, 4)직결. - 2-4 프로필 피드:
get_profile_feed_page_engine_v1(p_viewer, p_author)— 열람자/작성자 분리.p_author지정 = 그 작성자 본인 글만(친구 페이지, 교정판의 6페이지 보정 루프 폐기), 미지정 = 열람자 집계(내 피드). 구get_profile_feed_v2_enginedrop. - 2-5 세션 상세:
build_session_detail_payload_v1(게이트 없는 빌더) + 본인 문(소유자/팔로워 게이트)·그룹 문(동일 그룹 게이트) — 전환 제거.
- 2-1 PR 오버뷰:
- Phase 3:
friendAccessSqlContract.test.mjs(이름 스윕이라 새 접근점 자동 포착) +check-supabase-remote-schema.mjs볼륨 체인 핀 신구조·전환 부재 술어·v2_engine 은퇴 등재. SQL 계약 테스트 12파일을 "계산 본문 = 엔진 소유"로 재조준. - 작업 중 드러난 것: ①잠정 장부의 오귀속 2건(volume_level_progress_v1·get_home_dashboard — 실제는 인접 정비 루틴의 전환) 정밀 재조사로 교정 ②
tests/support/schemaSql.mjs의 함수 열거가 베이스라인 인용형만 세던 것을 비인용형까지 확장 ③전환 시절 친구 피드·그룹 세션의 열람자 종속 값(liked_by_me 등)이 대상 관점으로 계산되던 잠재 부정확을 함께 교정.
5. 적용 결과
| 항목 | 전 | 후 |
|---|---|---|
| 친구 일지 월 총중량 | 항상 0kg | 실제 값 — Production 실측 성근 8월 49,032kg == 일 롤업 합 |
| 친구/그룹 접근점의 계정 전환 | 5/7곳 사용 | 0곳 (행동 드라이런에서 전 과정 sub GUC 미접촉 확인) |
| 친구 응답 == 본인 응답 | 전환 결과로 성립 | 엔진 공유로 구조적으로 성립 — 리포트·PR 정확 일치(765,269kg 등) 실측 |
| 친구 피드 제3자 노출 방어 | 전환+자기글 필터 루프(사후 교정) | 처음부터 대상 글만 질의(20장 전부 대상 본인 글 실측) |
| 친구 피드 liked_by_me | 대상 유저 관점(부정확) | 실제 열람자 관점 |
| 전환 재유입 방지 | 규칙 없음 | 상시 게이트(스윕 테스트) + remote-schema 술어 |
| 검증 | — | 로컬 게이트 전체 green(유닛 2,267) · Production 스키마/행동 드라이런 · CI 1회 |
미검증: pgTAP·e2e는 CI에서 실행(머지 게이트), 화면 시각 품질은 자동 검증 범위 밖.
6. 이번 개선으로 향상된 것
- 원 함수 확장이 친구 노출을 조용히 넓히는 구조(8/26 사고의 뿌리) 소멸 — 노출은 접근점 장부와 게이트가 명시적으로 소유.
- Supabase 내부 동작 변화(auth.uid() 구현)에 대한 의존 제거 — 전환 자체가 없다.
- 사고 대응 수단 유지: 친구 문 실행 권한 회수 = 본인 화면 무중단 긴급 차단.
- 본인/친구 화면 숫자 동일성이 "같은 엔진"이라는 구조로 보장되고, 그 구조 자체가 테스트 계약이 됐다.
남은 것
- 내부 기계(통계 잡 처리·인입·정비 ~47곳)의 전환은 이 트랙 범위 밖 — "다른 계정의 호출 응답"이 아닌 서버 자신의 배치 작업(계약 문서 §4에 명시).
- 그룹 멤버 세션의 동반 RPC
strength_set_metrics_map_json은 종전대로 팔로워 게이트(그룹 동료 확장은 별건).