Skip to content

친구 스코프 데이터 v2 — 레벨 0kg·임의 보드·피드 1개에서 정본 3종 공급까지 (2026-08-25)

  • 기간: 2026-08-25 (세션 1 — 데이터 v1과 같은 세션, 오너 보고 원문: "레벨이랑 바벨리지가 안나와" · "클린풀이랑 Bottom to TOp Front squat은 왜 나오는거야? 포즈 스쿼트도" · "봉천곰돌이 페이지 들어가봤더니 피드탭에 피드가 1개밖에 없어" — 이슈 #803)
  • 랜딩: PR #807(Phase 1~4, ea68ac97) + PR #811(Phase 5-1 탭바 게이트) + PR #813(Phase 5-2 도크 판 레벨) + PR #816(Phase 5-3·5-4 피드 본인-글 교정+도크 세로 칼럼, 7124e49e) — 마이그레이션 20260821790000 Production push·프로브 검증 — 마이그레이션 20260821780000_following_scope_data_v2 1건(Production db push+권한 프로브 검증 완료 — v1의 push 누락 교훈 이행), Vercel 배포 확인(CI pass)
  • 설계서: 없음 — Phase 계획·예상 효과·개선사항 표는 이슈 #803 본문
  • 정본: friendScopeStore.ts 슬라이스 6종(정체·탭·report·pr·dashboard·favorites·calendar·feed) · following_* allowlist 6례(볼륨·달력·PR 개요·홈 대시보드·즐겨찾기·프로필 피드 — 게이트는 전부 profile_feed_can_view_v1 한 곳)
  • 도구: 없음
  • 게이트: pgTAP following_scope_data_v2.test.sql 15(payload 동일성·피드=대상 본인 세션만·클레임 복원·게이트) · friendScopeStore.test.mjs 10 · CI migration-smoke(브라우저 저니 포함) green
  • 버그리포트: bug-029-20260826.md — 피드 가시성 유출(Phase 5-3). 그 외 항목은 이 문서와 이슈 코멘트가 정본
  • 계약: 없음(신설 절 없음 — 데이터 공급 확장)

Phase 현황

Phase내용상태
Phase 1서버 래퍼 3종(대시보드·즐겨찾기·프로필 피드)+pgTAP 15✅ PR #807 (ea68ac97)
Phase 2friendScopeStore 슬라이스 3→6(report는 리포트 탭 lazy 전환)✅ 〃
Phase 3랜딩(레벨·누적 바벨리지·즐겨찾기 보드)·피드 탭(전체+페이지네이션) 배선✅ 〃
Phase 4CI·머지·Production db push+프로브 검증✅ 3함수 권한 확인
Phase 5오너 실기기 재확인 1차 — 탭바 번쩍임 보고✅ 5-1로 수리
Phase 5-1lazy 탭 스켈레톤 구간에 본 탭바 노출 → 숨김 게이트를 판(.fscope) 존재 기반으로✅ PR #811
Phase 5-2도크를 판 레벨(Suspense 밖) 상주로 — lazy 첫 렌더 1프레임 서스펜션에도 도크 유지. #811 배포 누락 발견·해소(docs 푸시 auto-cancel+경로 필터 조합, 새 번들 실측)✅ PR #813 (e6a0fa93)
Phase 5-3피드 가시성 누출 교정(보안) — 스왑 원본(get_profile_feed)이 집계(480000~)라 대상의 팔로잉 제3자 글이 노출 → 발견 즉시 Production revoke로 차단, 서버측 본인-글 필터+페이지 보정 루프+대상 author 부착으로 재발행(20260821790000), pgTAP 상호 팔로우 구성으로 비공허 재작성(17)✅ PR #816 (7124e49e)
Phase 5-4일지 탭 도크 세로 칼럼 — .screen:has(.sj-fullcal…)>div 채움 규칙이 판 레벨 도크를 잡음 → 도크를 .screen 밖 판 직속으로(정적 프로브 재현·실측 row/54px)✅ 〃
Phase 6오너 실기기 재확인(레벨·보드·피드 본인 글·도크 레이아웃)✅ 오너 확인 2026-08-26 ("오케이 잘 되었어")

1. 배경

데이터 v1이 친구 랜딩에 PR 개요를 공급했지만 오너 실기기 확인에서 네 가지가 남았다: 레벨·누적 바벨리지 0 고정, 보드에 0kg 종목(클린 풀·Bottom to Top Front Squat) 혼입, 보드 선정 기준 자체(즐겨찾기가 아닌 기록값 상위), 친구 피드가 1개만 표시.

2. 문제 제기

레벨·누적 바벨리지는 볼륨 개요가 아니라 HOME_DASHBOARD 소산이다

통계 중앙화 원칙(레벨/XP는 DB 정책 profile_summary.volume_level만, 클라 재계산 금지)에 따라 buildVolumePropsFromOverviewHOME_DASHBOARD.profile_summary가 없으면 레벨을 null로 둔다 — 친구용 공급 RPC 부재.

보드 선정(추정 1RM 포함)과 표시(측정 1RM만)가 어긋났다

v1의 "보유 기록 상위 6"은 추정 1RM·랩 기록도 "기록 있음"으로 세는데 보드 행 표시는 측정 1RM만 보여줘 0kg 행이 떴다. 근본적으로는 이 보드의 의미가 내 홈에서 "즐겨찾기한 주요 종목"인데 친구 즐겨찾기가 비노출이라 임의 대체였던 것.

친구 피드는 내 피드 캐시의 교집합이었다

v1은 내 피드에 적재된 페이지에서 작성자 필터 — 첫 페이지에 그 친구 글이 1개면 1개만 보인다. 전용 공급이 없었다.

3. 해결 방안

원칙 (오너 결정, 2026-08-25)

  • D1: 보드 = 친구의 즐겨찾기(내 홈과 동일 의미) — 즐겨찾기 팔로워 공개 승인(철회 시 래퍼 함수 1개 drop).
  • 기존 원칙 유지: following_* allowlist(표면당 공개 결정+pgTAP), 가시성 게이트 한 곳.

접근

대안판정
래퍼 3종 추가(allowlist 4~6례)채택 — 대시보드·즐겨찾기는 표준 패턴. 핵심 발견: get_profile_feed 엔진은 본인 세션만 읽는 구조라 클레임 스왑이 곧 "그 사람 글 전체+커서"(제3자 유출 없음 — pgTAP이 잠금)
집계 피드(get_following_feed) 클레임 스왑금지 — 그 친구가 팔로우한 제3자 글이 새어 나옴(가시성은 관계별)
레벨 클라 재계산기각 — 통계 중앙화 원칙 위반
보드 폴백 유지(기록값 상위)부분 채택 — 즐겨찾기 빈 경우만, 측정 1RM(kg)로 한정(0kg 행 소멸)

4. 적용한 내용

Phase 1 — 서버 (#807, 20260821780000)

get_following_home_dashboard_v1(스왑)·get_following_exercise_favorites_v1(payload 빌더가 user id를 받아 스왑 불필요 — stable 직접 호출)·get_following_profile_feed_v1(스왑, 커서 4인자 전달). pgTAP 15.

Phase 2 — 스토어

슬라이스 dashboard·favorites·feed 추가, report는 리포트 탭 lazy로 전환(랜딩 필요 데이터가 dashboard로 이동 — 열기 시 RPC는 pr·dashboard·favorites 3회 병렬 유지). feed = 첫 페이지 lazy+커서 loadMore(append)·hasMore 게이트. 전 슬라이스 토큰 폐기·AbortError 무기록.

Phase 3 — 배선

랜딩 투영 입력에 HOME_DASHBOARD 추가(레벨·누적 바벨리지), favoriteIds = 친구 즐겨찾기(빈 경우 측정 1RM(kg) 상위 6). 피드 탭 = 전용 슬라이스(전체·onLoadMore·당겨서 새로고침=첫 페이지 재적재), 반응·조정 로컬 병합은 내 피드와 같은 파이프라인.

작업 중 드러난 것 (pgTAP 4수정 — 다음 세션 주의)

  • sessions 시드는 source·source_ref NOT NULL — 기존 테스트 문법('barbelic', 'pgtap:...') 복사.
  • 온보딩이 기본 즐겨찾기 7개를 자동 시드한다 — 즐겨찾기 개수 고정 단언 금지(포함 검사로).
  • brid_* 함수는 authenticated 실행 권한이 없다 — 시드 파생값은 superuser 단계에서 temp 테이블에 붙잡고 grant select 까지(superuser가 만든 temp 테이블은 role 전환 뒤 안 읽힌다).
  • 화면이 렌더하는 요소에 :has() 게이트를 걸면 lazy 전환의 폴백 구간에 게이트가 풀린다 — 본 탭바 숨김이 도크(.fsb) 존재 기반이라 리포트 탭 첫 진입 스켈레톤 동안 본 탭바가 번쩍였다(오너 보고). 스코프 수명과 일치해야 하는 게이트는 컨테이너 소유 요소(.fscope)에 건다(#811).
  • React.lazy는 모듈이 워밍돼 있어도 첫 렌더에 1프레임 서스펜드한다 — 스코프 수명 UI(도크)는 Suspense 밖 판 레벨에 상주시켜야 끊기지 않는다(#813). 화면 내부 렌더 경로는 friendNav 미공급으로 잠재우고 이중 렌더 금지 단언을 상주.
  • Vercel 배포 함정 재확인: UI 머지 직후 docs-only 커밋을 푸시하면 큐의 앱 빌드가 auto-cancel되고 docs 커밋은 경로 필터로 스킵돼 앱이 조용히 구 번들에 머문다(#811이 이렇게 누락) — UI 머지 후 앱 배포 success 확인 전에는 docs 머지 금지, 배포 후 새 번들 마커 실측(mobileRoot-*.css)으로 닫는다.
  • schema.sql은 append 구조 — 함수 의미는 "마지막 정의"로 판단할 것: get_profile_feed를 앞부분(base) 정의만 읽고 본인-글 전용으로 오판해 스왑 래퍼가 가시성 누출을 냈다(Phase 5-3). 함수 판단은 grep | tail(마지막 재정의)+함수 comment로. 잠그는 pgTAP은 누출이 실제로 일어나는 구성(대상이 제3자를 팔로우)으로 짜야 공허 통과가 없다.
  • .screen:has(화면) > div 채움 규칙은 .screen 직계에 넣는 모든 요소를 오염시킨다 — 판 레벨 상주 요소는 .screen 밖(판 직속)에 둔다. 구조 CSS 의심 증상은 빌드 CSS+실 DOM의 정적 프로브(vite preview + probe.html)로 몇 분 만에 재현·특정된다(Phase 5-4에서 실증).

5. 적용 결과

항목결과
친구 레벨·누적 바벨리지0kg 고정 → DB 정책 값(profile_summary) 표시
보드 의미기록값 상위 임의 6(0kg 혼입) → 친구 즐겨찾기 순서(폴백=측정 1RM(kg) 상위 — 0kg 행 소멸)
친구 피드내 피드 캐시 교집합(1개 사례) → 전체+무한 스크롤+당겨서 새로고침
following_* 표면3 → 6 (게이트는 계속 profile_feed_can_view_v1 한 곳)
테스트1991 → 1995 · pgTAP +15 · CI migration-smoke green
Productiondb push 완료+프로브 검증(3함수 authenticated ✓·anon 차단 ✓) — v1 누락 교훈 이행
실기기 확인레벨·바벨리지·즐겨찾기 보드·피드(본인 글만)·도크 레이아웃 — 오너 실기기 확인 완료(2026-08-26)

6. 이번 개선으로 향상된 것

친구 페이지의 세 표면이 전부 "그 사람의 정본"이 됐다

레벨·XP는 DB 정책 값, 보드는 본인이 등록한 주요 종목, 피드는 본인 글 전체 — 임의 대체가 남아 있지 않다.

구조적으로 남는 것

  • following_* allowlist 6례 — "새 친구 표면 = 래퍼 하나(공개 결정)+pgTAP 하나" 관례 정착. 프로필 피드 엔진의 "본인 세션만" 구조 확인은 이후 피드 계열 확장의 판단 기준.
  • pgTAP 시드 함정 3종(위 4절)이 기록으로 남아 다음 DB 테스트가 같은 곳에서 안 넘어진다.

남은 것

  • 친구가 즐겨찾기·기록 모두 없는 경우의 1인칭 빈 상태 카피 2건(디자인 측 후속 — v1 기록에 등재).