배경
Nodease의 공식 제품 방향은 외부 고객 대상 SaaS가 아니라 한 회사가 내부적으로 설치·운영하는 AI Workflow/LLMOps 플랫폼이다. 기본 배포 모델에서는 고객 회사마다 별도 Nodease 배포, PostgreSQL, Redis 및 암호화 키 경계를 사용하고, Organization은 해당 설치 안에서 회사 또는 명시적으로 승인된 최상위 내부 격리 단위, Team은 개발·기획·인사 등 내부 편성 단위로 해석한다.
현재 구현은 모든 신규 가입자에게 개인 기본 Organization과 manager membership, Default Team을 자동 생성한다. 또한 조직원 초대 UI/API는 이미 가입한 사용자의 UUID를 요구하지만, 일반 관리자가 UUID를 검색할 user directory가 없고 이메일 발송도 하지 않는다. 이 흐름은 개인 workspace/SaaS 모델에는 가깝지만 기업 내부 설치형 제품의 bootstrap·온보딩 계약과 맞지 않는다.
목표
- 설치 시 회사 Organization과 최초 관리자 계정을 한 번만 안전하게 bootstrap한다.
- 일반 사용자 가입이 개인 관리자 Organization을 자동 생성하지 않도록 foundation 계약을 정렬한다.
- UUID 직접 입력 대신 이메일 검증 기반 조직 초대·가입·수락 흐름을 제공한다.
- 가입 전 사용자와 기존 가입자를 모두 지원하고, 수락 후 팀 편성까지 일관되게 처리한다.
- 물리적 기업별 배포 격리와 애플리케이션의
organization_id 논리 격리 역할을 공식 문서에 명확히 구분한다.
구현 범위
1. 배포 및 Organization 모델 확정
- 기본 운영 모델을
고객 회사별 독립 배포 + 독립 DB/Redis/keyring으로 문서화한다.
Organization을 회사 또는 승인된 최상위 내부 격리 단위로 정의하고 Team과 구분한다.
- 하나의 설치에서 복수 Organization을 허용할 조건(예: 독립 계열사·사업부)과 비목표를 명시한다.
- 기존 active Organization scope와
X-Organization-Id 방어는 논리적 격리와 defense-in-depth로 유지한다.
2. 최초 설치 bootstrap
- 미초기화 설치에서만 실행 가능한 setup/bootstrap 경계를 둔다.
- 최초 회사 Organization, 최초 active manager membership,
Default Team과 관리자 TeamMembership을 하나의 transaction으로 생성한다.
- 동시 bootstrap 요청에서도 정확히 하나만 성공하도록 DB 제약/lock/idempotency를 적용한다.
- bootstrap 완료 후 동일 공개 경로를 재사용해 추가 관리자 Organization을 만들 수 없게 한다.
- bootstrap 실패는 foundation 일부를 남기지 않고 전체 rollback한다.
3. 회원가입 계약 정렬
- 일반 이메일 회원가입과 최초 OAuth 로그인에서 개인 Organization을 자동 생성하지 않는다.
- 유효한 조직 초대가 있는 사용자는 검증된 이메일을 기준으로 초대에 연결한다.
- 초대가 없는 가입자의 허용 상태를 명시적으로 결정한다: 가입 차단, 승인 대기 또는 조직 미소속 제한 계정 중 하나를 공식 계약으로 확정한다.
- 계정 생성, 초대 수락 및 membership 활성화의 transaction·재시도 경계를 정의한다.
4. 이메일 기반 초대
- Organization manager가 이메일, 조직 역할(
member|manager) 및 선택적 초기 Team을 지정해 초대한다.
- 이메일은 정규화하되 계정 존재 여부를 외부에 노출하지 않는 동일한 응답 계약을 사용한다.
- 일회성 opaque invitation token은 원문을 저장·로그·audit하지 않고 verifier hash, 만료, 상태 및 필요한 scope만 저장한다.
- 초대의 만료, 취소, 재발송, 단일 사용, 재사용 차단 및 반복 요청 멱등성을 구현한다.
- 기존 가입자는 앱 내부 알림과 이메일을 받고, 미가입자는 초대 링크에서 가입 후 검증된 동일 이메일로 수락한다.
- 이메일 전송 실패와 membership 생성/활성화를 혼합하지 않도록 durable outbox 또는 명시적인 재전송 가능한 전달 경계를 사용한다.
- 초대 생성·취소·수락·거절·만료를 안전한 metadata로 audit한다.
5. Team 편성
- 초대 시 지정한 Team은 동일 Organization의 active Team인지 서버에서 검증한다.
- TeamMembership은 초대 수락 및 membership 활성화 이후에만 생성한다.
- 수락 시 membership과 초기 Team 배정을 원자적으로 처리하거나 부분 성공 복구 계약을 둔다.
- 현재 UI/DB에만 존재하는
is_auto_add를 실제 수락 경로에 연결하거나, 지원하지 않을 경우 UI와 계약에서 제거·대체한다.
6. 관리자·사용자 UI
- 관리자 초대 화면의 UUID 입력을 이메일, 역할, 초기 Team 선택으로 교체한다.
- 초대 상태, 만료, 재발송, 취소를 관리할 수 있게 한다.
- 사용자는 앱 알림 또는 이메일 링크에서 초대를 확인하고 수락·거절할 수 있다.
- 최초 설치 화면과 일반 로그인/가입 화면을 구분하고 bootstrap 완료 상태를 클라이언트 입력값으로 신뢰하지 않는다.
- Organization switcher는 실제 복수 Organization membership이 있는 경우에만 의미 있게 표시한다.
7. 기존 데이터·seed·운영 전환
- 기존 사용자별 개인 Organization을 자동 병합하지 않는다.
- 기존 설치의 Organization 분류, 관리자 선택, 사용자 membership 및 리소스 이동에 대한 명시적 migration/운영 절차를 마련한다.
- 리소스·권한·credential·audit 귀속 이동은 별도 승인과 dry-run 결과 없이 수행하지 않는다.
- demo/test seed를 한 회사 Organization과 내부 Team/사용자 모델로 정렬한다.
보안 및 불변조건
- 공개 signup/bootstrap 입력으로 Organization ID, manager ID 또는 Team ID를 임의 지정할 수 없어야 한다.
- invitation token 원문, 이메일 원문 전체, credential 및 raw request body를 로그·trace·audit에 남기지 않는다.
- 초대 수락 시 token scope, organization, 대상 이메일, 사용자 이메일 검증 상태, 만료·취소·사용 여부를 다시 확인한다.
- 다른 Organization의 Team이나 role을 초대에 주입할 수 없어야 한다.
- 마지막 active manager 보호와 manager 권한 변경 정책을 유지한다.
- 이메일 전송 endpoint에는 rate limit과 abuse 방어를 적용한다.
- 가입자 존재 여부와 초대 대상 존재 여부를 응답 차이로 열거할 수 없어야 한다.
Acceptance Criteria
- 빈 설치에서 동시 bootstrap 요청을 보내도 회사 Organization과 최초 manager foundation이 정확히 하나 생성된다.
- bootstrap 완료 후 일반 가입자는 개인 Organization의 manager가 되지 않는다.
- 관리자는 사용자 UUID를 알지 못해도 이메일로 조직 초대를 만들 수 있다.
- 기존 가입자와 미가입자 모두 검증된 초대 경로로 같은 회사 Organization에 합류할 수 있다.
- 만료·취소·이미 사용·변조된 token과 이메일 불일치 초대는 fail-closed된다.
- 이메일 전송 실패 후 재시도해도 membership, 알림 또는 이메일이 중복 활성화되지 않는다.
- 초대에 지정한 초기 Team만 수락 후 배정되며 cross-organization·inactive Team은 거부된다.
- 초대 수락, 거절, 취소, 재발송 후 Sidebar/Organization switcher 상태가 새로고침 없이 정합하게 갱신된다.
- 기존 Organization 데이터에 대한 전환은 dry-run과 명시적 관리자 승인 없이는 파괴적으로 실행되지 않는다.
- 관련 공식 문서, ADR, API/UI 계약 및 정상·실패·동시성·재시도 테스트가 함께 갱신된다.
테스트 범위
- bootstrap 정상/중복/동시 요청/중간 실패 rollback
- 초대 없는 signup/OAuth 정책
- 기존 가입자 및 미가입자 이메일 초대
- 이메일 대소문자·공백 정규화와 검증되지 않은 이메일
- token 변조/만료/취소/재사용/동시 수락
- 초대 재발송과 이메일 provider 실패/중복 delivery
- 동일 이메일 반복 초대 멱등성 및 suspended/removed membership 재초대
- cross-organization·inactive Team 주입 및 수락 중 Team 상태 변경
- 마지막 manager 보호
- in-app notification/SSE와 Sidebar 조직 목록 동기화
- 기존 데이터 migration dry-run 및 rollback
비목표
- 여러 고객 회사를 하나의 공유 DB에서 서비스하는 외부 SaaS 과금·구독 모델
- 전사 SSO/OIDC/SCIM 전체 구현. 다만 후속 연동을 막지 않는 port와 계약은 유지한다.
- 승인 없이 기존 Organization과 리소스를 자동 병합하는 작업
관련 이슈
문서 영향
docs/PRD.md
docs/architecture.md
docs/glossary.md
docs/features/auth/
docs/features/organization/
- 필요 시 Accepted ADR 신규 작성
배경
Nodease의 공식 제품 방향은 외부 고객 대상 SaaS가 아니라 한 회사가 내부적으로 설치·운영하는 AI Workflow/LLMOps 플랫폼이다. 기본 배포 모델에서는 고객 회사마다 별도 Nodease 배포, PostgreSQL, Redis 및 암호화 키 경계를 사용하고,
Organization은 해당 설치 안에서 회사 또는 명시적으로 승인된 최상위 내부 격리 단위,Team은 개발·기획·인사 등 내부 편성 단위로 해석한다.현재 구현은 모든 신규 가입자에게 개인 기본 Organization과
managermembership,DefaultTeam을 자동 생성한다. 또한 조직원 초대 UI/API는 이미 가입한 사용자의 UUID를 요구하지만, 일반 관리자가 UUID를 검색할 user directory가 없고 이메일 발송도 하지 않는다. 이 흐름은 개인 workspace/SaaS 모델에는 가깝지만 기업 내부 설치형 제품의 bootstrap·온보딩 계약과 맞지 않는다.목표
organization_id논리 격리 역할을 공식 문서에 명확히 구분한다.구현 범위
1. 배포 및 Organization 모델 확정
고객 회사별 독립 배포 + 독립 DB/Redis/keyring으로 문서화한다.Organization을 회사 또는 승인된 최상위 내부 격리 단위로 정의하고Team과 구분한다.X-Organization-Id방어는 논리적 격리와 defense-in-depth로 유지한다.2. 최초 설치 bootstrap
DefaultTeam과 관리자 TeamMembership을 하나의 transaction으로 생성한다.3. 회원가입 계약 정렬
4. 이메일 기반 초대
member|manager) 및 선택적 초기 Team을 지정해 초대한다.5. Team 편성
is_auto_add를 실제 수락 경로에 연결하거나, 지원하지 않을 경우 UI와 계약에서 제거·대체한다.6. 관리자·사용자 UI
7. 기존 데이터·seed·운영 전환
보안 및 불변조건
Acceptance Criteria
테스트 범위
비목표
관련 이슈
문서 영향
docs/PRD.mddocs/architecture.mddocs/glossary.mddocs/features/auth/docs/features/organization/