Skip to content

feat(msgr): 팀 메신저 스키마(P0-S1) — 조직·멤버·초대·좌석·채널·메시지·크루·결재 미러·감사 + RLS·Realtime·is_pro 좌석 OR - #419

Open
beyondworks wants to merge 3 commits into
mainfrom
feat/msgr-schema
Open

Conversation

@beyondworks

Copy link
Copy Markdown
Owner

무엇

아르고 전용 팀 메신저(설계 정본: 루트 MESSENGER-DESIGN.md, 미추적)의 P0 첫 스토리. 새 마이그레이션 supabase/migrations/20260903120000_msgr.sql 하나로 msgr_* 테이블 11개·RLS·security definer 멤버십 헬퍼·트리거·Realtime private topic 인가·Storage 버킷 msgr 정책을 얹고, is_pro()에 "활성 Team 좌석 보유" OR 조건을 붙인다(좌석 ⊇ 개인 Pro). 기존 개인 스키마(entitlements·account_keys·device_keys…)와 텔레그램·슬랙 경로는 무변경.

현재 제품은 1인 사장 구조라 조직·멤버·초대·역할·좌석·감사 개념이 없다. 메신저 앱(별도 apps/messenger)과 크루 브리지가 공유할 서버측 정본이 먼저 있어야 한다.

드릴 실측으로 잡은 결함 3건(설계에 없던 것)

  • insert … returning은 select 정책도 검사한다 — 생성자를 멤버로 넣는 AFTER 트리거보다 먼저라, 조직 select에 owner_user_id = auth.uid()·채널 select에 created_by 조건이 없으면 생성 자체가 실패.
  • STABLE 함수는 호출 문장의 스냅샷을 써서 방금 넣은 행을 못 본다 — 채널 select 정책은 msgr_can_read_channel(id) 대신 행 컬럼으로.
  • channels 정책 ↔ channel_members for all 정책 순환("infinite recursion") — msgr_is_channel_user() definer 헬퍼로 절단.

검증

  • npm run test:pg(initdb 드릴, Docker 불필요) — test/msgr-pg-integration.test.mjs 11/11 + 기존 billing 5/5. 드릴 스크립트는 파일마다 별도 DB(node --test 병렬 프로세스 경합 방지).
  • 변이 5종 전부 red 실증: 결재 소유자 검사 제거 → 8번 실패 / is_pro guest 제외 제거 → 5번 / 크루 명의 소유자 검사 제거 → 6번 / 좌석 경계 >=> → 4번 / 제거 멤버 가시 → 4건 실패.
  • npm test 1409/0(skip 16), npm run lint 0.

영향 반경(문맥 행렬)

  • 플랜 축: is_pro() 재정의 — 개인 pro·체험·ends_at 경계는 기존 pg 테스트(billing 5번)가 그대로 초록 = 검증함. 좌석 OR은 msgr 5번.
  • 산출물 축: prod Supabase 미적용(머지 ≠ 반영). 적용은 supabase db push + 서명 프로브 별도 드릴. Storage 버킷 msgr 생성도 별도(정책만 선행).
  • OS·셸·러너 축: 무관(SQL만). CI(맥·윈)는 pg 드릴을 돌리지 않으므로 이 PR의 CI 초록은 lint+단위만 의미한다 — 미검증 아님, 로컬 드릴이 검증.
  • 미검증: 실 Supabase Realtime realtime.send·realtime.messages 정책 실왕복(스텁으로만 검증). P1 S2 격리 E2E에서 닫는다.

🤖 Generated with Claude Code

…ime 방송·is_pro 좌석 OR (pg 드릴 11건)

- supabase/migrations/20260903120000_msgr.sql: msgr_* 11테이블, security definer 멤버십 헬퍼(anon 회수), 트리거(owner 자동 멤버·좌석/채널 한도·org_id 채움·realtime.send 최소 payload·감사), private topic org:<id> 인가, 버킷 msgr 정책, is_pro()에 활성 Team 좌석 OR
- 드릴 실측 3건 반영: insert…returning은 select 정책도 본다(소유자·생성자 조건), STABLE 함수는 자기 행을 못 본다(채널 select는 행 컬럼), channels↔channel_members 정책 순환은 definer 헬퍼로 절단
- test/msgr-pg-integration.test.mjs + helpers/msgr-cases.mjs(역할 경계표 6×8 단일 정본), 드릴 스크립트는 파일마다 별도 DB

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
beyondworks and others added 2 commits September 3, 2026 14:06
…·채널 게이트 advisory lock, client_msg_id 작성자 축, Storage 채널 단위, 초대 update 경로 제거, Realtime 관용 캐스트, 수락 display_name

- CRITICAL-1·2: msgr_lock_cols 트리거(channels org_id/kind/created_by, approvals org/channel/crew/approval_id/action, crews org/owner/ws/slug, members org/user, messages 신원·client_msg_id) — 서비스 문맥은 통과
- HIGH-3: pg_advisory_xact_lock으로 동시 insert 직렬화(좌석·공개 채널 한도), 드릴에 2세션 인터리브 테스트
- HIGH-4: 유니크 인덱스 (channel_id, author_kind, coalesce(crew_id, author_user_id), client_msg_id) — 멤버 선점이 크루 답글을 못 막는다
- HIGH-5: 버킷 msgr select/insert를 2세그먼트(channel_id) 채널 열람·쓰기 판정으로(authenticated 직접 다운로드 경로)
- MEDIUM-6·7·8: realtime recv는 심어 둔 행 가시성으로 검증, denied 기본 정규식 좁힘, 초대 update 정책 제거(수락은 RPC만)
- LOW: realtime topic 캐스트를 msgr_uuid_or_null로, 수락 시 display_name = 이메일 앞부분

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…이드 통과, 초대 FK on delete, 정책 자기 비교(org_id) 수정, 크루 삭제 후 글 보존(CHECK 완화), 채널명 개행 금지

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant