# 팀 · 공유 작업공간 (0.8)

상태: feat/complete-core-20261030 구현, 기본 팀 SQL은 제품 Supabase에 적용되어 있으며 세부 권한 SQL과 Worker 배포는 대기 중이다. Cloudflare/Supabase 설정은 STANDALONE-0.8.md를 따른다. 로컬 npm start 개발 계정은 실제 팀 인증 경로가 아니다.

## 권한 계약

| 기능 | 소유자 | 편집자 | 조회 전용 |
|---|---|---|---|
| 업무 자료·보고서 읽기/내보내기 | 가능 | 가능 | 가능 |
| 업무 자료·파일 변경 | 가능 | 가능 | 서버와 RLS에서 거절 |
| 외부 서비스 연결·비밀 설정 | 가능 | 불가 | 불가 |
| 개인 고객 포털 주소 생성/해제 | 가능 | 불가 | 불가 |
| 팀원 초대·역할·접근 변경 | 자기 소유 작업공간만 | 불가 | 불가 |
| 접근 가능한 작업공간 전환 | 가능 | 가능 | 가능 |
| 다른 팀에서 탈퇴 | 해당 없음 | 가능 | 가능 |

기존 전체 접근 멤버는 표의 역할을 따른다. 소유자는 팀원별로 세부 권한을 적용하고 다음 항목을 정할 수 있다.

- 열어 줄 모듈은 고객, 영업 기회, 예약, 청구서 중에서 고른다.
- 고객 목록을 직접 선택한 담당 고객으로 제한할 수 있다. 연결된 영업 기회·예약·청구서에도 같은 제한을 적용한다. 고객을 하나도 지정하지 않으면 목록은 비어 있다.
- 고객 이름은 항상 표시한다. 이메일·전화번호·회사·태그·유입 경로는 선택한 항목만 표시한다.
- 편집자에게 기존 고객 수정을 허용할 수 있다. 허용된 고객과 공개한 필드만 수정할 수 있다. 영업 기회·예약·청구서는 읽기 전용이다.
- 팀원은 개인 작업공간을 따로 관리할 수 있다. Agency 역할, 권한 템플릿, 다른 업무 모듈의 세부 규칙은 이후 범위다.

## 사용 순서

1. /team.html에서 팀 표시 이름을 저장한다. 소유자 본인의 관리 영역임을 명시한다.
2. 팀원 이메일과 편집자/조회 전용 역할을 골라 개인 초대 주소를 만든다. URL은 한 번만 표시되며 DB에는 SHA-256만 보관한다. 자동 이메일은 보내지 않는다.
3. 초대받은 사람은 자신의 이메일 확인이 끝난 Supabase 계정으로 로그인한 뒤 주소를 열고 수락한다. 다른 이메일·미확인 계정·만료/해제 주소는 거절한다. 7일 만료이며 같은 이메일의 미수락 이전 초대는 새 초대 생성 시 해제한다.
4. 접근 가능한 작업공간에서 열기를 누른다. 선택 쿠키는 host 전용 Secure/HttpOnly/SameSite=Lax이다. 초대 토큰은 URL fragment로만 전달하고 수락 후 지운다. 로그인 이동 중에는 해당 fragment만 sessionStorage에 잠시 보관한다.
5. 소유자는 **팀원별 세부 권한**에서 모듈과 필드를 체크하고 필요하면 담당 고객을 지정한다. **전체 접근으로 복원**은 역할의 기존 전체 범위를 다시 허용한다.
6. 역할 저장/권한 해제/재허용과 본인 탈퇴를 즉시 확인한다. 해제된 회원은 이전 수락 주소로 권한을 되살릴 수 없다. 만료 전이라도 새 초대 또는 소유자의 재허용이 필요하다.

## 서버와 데이터베이스

- 매 요청 Supabase 인증의 실제 actor와 선택 workspace owner를 분리한다. 헤더는 선택 요청일 뿐 인증 근거가 아니다. 목록/데이터/파일 변경 때 실제 접근을 다시 검사한다.
- 일반 업무/파일은 실제 사용자 JWT와 RLS를 사용한다. 세부 권한 팀원은 원본 업무·파일 RLS에서 차단한다. 전용 Worker 경로만 허용된 항목을 투영한다. 수정은 회원 행을 잠그고 역할·세부 권한을 재확인한 뒤 조건부 저장한다. 다른 제품 테이블·버킷은 변경하지 않는다.
- 팀 프로필·회원·초대·감사 테이블은 editable workspace JSON과 분리하고 직접 authenticated/anon 권한을 철회했다. 제품 한정 SECURITY DEFINER RPC는 고정 빈 search_path, auth.uid, 소유자 검증, 확인된 이메일을 사용한다. anon 실행 권한은 없다.
- 비밀 설정과 고객 포털 주소 테이블은 계속 소유자 전용이다. 편집자가 업무 JSON 안의 역할 필드를 바꿔도 ACL이 바뀌지 않는다.
- 브라우저 초안 캐시는 actor+workspace로 나눈다. 조회 전용으로 강등되면 예전 편집자 초안을 자동 전송하지 않는다. 해제된 선택 쿠키는 본인 작업공간으로 복귀하며 명시적인 해제 workspace 헤더는 403이다. 로그아웃은 선택 쿠키도 지운다.
- 이미 다운로드하거나 내보낸 자료까지 원격 삭제하는 기능은 아니다. 실시간 화면의 기존 내용은 다음 요청/새로고침까지 남을 수 있으나 새 서버 접근은 차단한다.
- 권한 변경 감사 이력은 소유자에게 최근 100개를 보여 준다. 회원/초대 목록은 현재 전체 목록이며 대규모 조직 페이지 분할은 후속 범위다.

## 검증

실제 SQL 테스트: 다른 이메일/미확인/만료/재사용 차단, RLS 읽기/쓰기/파일과 비밀 격리, 소유자 위조 거절, 탈퇴·해제, MCP viewer 실행 차단, cache 분리. UI 회귀 시험: busy 렌더 전 역할 선택값 캡처. 브라우저: 초대 수락→조회 전용 버튼 차단→편집 전환→고객 저장/새로고침→소유자 화면 재조회→권한 해제→본인 빈 작업공간 복귀.

테스트 인증/Storage 공급자는 대역이다. 실제 Supabase 이메일 계정 둘로 가입/로그인/초대 및 클라우드 Storage 공유 E2E는 새 Worker 배포 후 별도 확인해야 한다. 화면 증거는 receipts/ui-0.8/에 있으며 합성 자료만 담는다.
