온보딩·조직 연결 · 라이브
초대 온보딩 · 셀프 org 생성 · 조직 연합까지 라이브 배포 · app.aisuit.dev

멀티테넌트 org 온보딩
사람이 AI슈트에 들어오는 방식

사람이 AI슈트에 들어오는 전 경로가 구현·라이브 배포됐습니다 — 관리자 초대 온보딩(1회용 초대 링크로 직원 활성화), 공개 셀프서비스 org 생성(누구나 org를 만들고 첫 owner가 됨), 그리고 소유자 합의하 조직 연합(org 간 동의 기반 링크)까지 모두 실제로 동작합니다. org는 안으로(중첩 팀), 밖으로(연합 링크) 확장됩니다. 남은 건 한 신원이 여러 org를 오가는 계정⟂멤버십 하나뿐입니다. (신뢰 코어와 콘솔은 형제 페이지 /spec·/console.)

온보딩 v0.1 → 라이브 상태: 구현 · 라이브 배포 배포: app.aisuit.dev 잔여: 계정⟂멤버십
읽는 분께 — 이 문서는 원래 설계·논의 문서였고, 이제 그 내용의 대부분이 라이브입니다 — 코드가 우선입니다. 신뢰 코어(권한·승인·감사·MCP 게이트)와 콘솔은 형제 페이지 /spec·/console에서 다룹니다. 본 페이지가 계획서와 어긋나면 계획서 원문(multitenant-org-onboarding-plan-v0.1)이 우선합니다.
현재 위치 & 상태 2026-07-02 · 라이브 배포
초대 온보딩 라이브 SSO 자동가입 제거 라이브 셀프 org 생성 라이브 조직 연합 라이브 계정⟂멤버십 이후
이 페이지가 담은 온보딩 모델은 이제 구현·라이브입니다 — 초대 온보딩·SSO 자동가입 제거·셀프서비스 org 생성·조직 연합·offboarding까지 모두 app.aisuit.dev에 배포됐습니다. 계정⟂멤버십(한 신원이 여러 org를 오감)만 남았습니다.
초대 온보딩이메일+팀+역할로 구성원 추가 → 1회용 초대 링크 → 직원 수락·활성화 · 전 수명주기 audit
SSO 도메인 자동가입 제거멤버십은 완전히 초대 게이트 — 알 수 없는 계정 로그인은 "초대 필요"로 거부
셀프서비스 org 생성공개 /signup → 새 org + 첫 owner · IP throttle · 한 계정 = 한 org
조직 연합owner 승인·읽기 전용 거버넌스 롤업·owner 전용 콘솔 화면 · 테넌트 벽(불변식8) 유지
운영 수명주기(offboarding)kill-switch·재활성화·in-flight 잔여 정리(세션·MCP 토큰 폐기)·org 소유권 이전
초대 온보딩·셀프 org·조직 연합 라이브(app.aisuit.dev) · 잔여 = 계정⟂멤버십. 조직 연합 상세는 federation-design-v0.1.md · 신뢰 코어는 코어 설계 →, 콘솔은 콘솔 시스템 →
00 · 전체 그림 온보딩 §1

사람이 AI슈트에 들어오는 모델 — 이제 현실

온보딩의 핵심은 네 가지로 요약됩니다. 이 전체 모델은 합의된 방향이었고, 이제 구현·라이브입니다 — 초대 온보딩·셀프서비스 org 생성·조직 연합이 모두 실제로 동작합니다.

아이디어 1

누구나 org를 만들 수 있다

로그인한 사람은 누구나 org를 만들고 그 org의 owner가 됩니다. org의 "규모"(팀·파트·본부·기업)는 강제된 위계가 아니라 라벨일 뿐입니다. (이 셀프서비스 생성은 이제 공개 /signup으로 라이브)

아이디어 2

두 가지 진입 역할

org 생성자/owner vs 초대된 직원. 모르는 계정이 그냥 로그인하면 "초대 필요"로 거부되고 org 생성으로 안내되며, 기존 org에 자동으로 합류하지 않습니다.

아이디어 3

초대된 직원은 절대 자가 가입하지 않는다

admin이 구성원(이메일+팀+역할)을 추가하면 시스템이 1회용 first-auth 링크를 만들고, admin이 안전한 채널로 공유하면, 직원이 인증(Google 또는 비밀번호)으로 교환해야 비로소 활성화됩니다.

아이디어 4

org는 두 방향으로 확장된다

한 org 안에서는 중첩 팀으로, 독립적으로 소유된 org들 사이에서는 동의 기반 연합 링크로 — 각 org를 소유한 계정의 승인으로만 연결됩니다. (조직 연합도 이제 라이브)

읽는 순서이 페이지는 위 모델 전체를 설명하며, 03~06의 초대 온보딩과 07의 셀프서비스 org 생성·조직 연합이 모두 라이브입니다. 남은 이후 목표는 계정⟂멤버십 하나뿐입니다.
01 · 확정된 결정 온보딩 §2

구현된 설계 결정

아래는 합의되어 잠긴 설계 결정들이며, 그대로 유지되어 이제 모두 구현·라이브입니다.

확정 · 구현됨

한 계정 = 한 org (불변식8 유지)

현재 사용자는 한 org에 묶여 있고(불변식8 그대로 유지), 시스템은 그 단일 org 모델로 라이브입니다. 계정⟂멤버십 분리org 스위처는 유일한 이후 목표입니다.

확정 · 구현됨

org "규모" = 라벨

org 규모는 강제 위계가 아니라 라벨입니다. org 내부 위계는 중첩 팀으로, org 간 연결은 연합 링크로 표현합니다.

확정 · 구현됨

수락은 소셜 + 비밀번호 둘 다

직원 수락은 Google과 비밀번호를 모두 지원하되, 비밀번호 경로는 강화하고 초대된 org가 require_sso면 비활성(Google 전용)입니다.

확정 · 제거됨

SSO 도메인 자동 프로비저닝 제거

도메인으로 자동 합류시키던 기존 동작을 제거했습니다. 직원은 언제나 초대로만 들어오며, 도메인 allowlist는 hd 힌트로 강등됐습니다.

확정 · 라이브

오픈 org 생성 — 이제 라이브

가입을 마친 누구나 org를 만들고 첫 owner가 되는 셀프서비스 생성이 공개 /signup으로 라이브입니다. 모르는 로그인은 "초대 필요"로 거부됩니다.

확정 · 라이브

연합은 동의로만

한 org가 다른 org를 연결하려면 그 org를 소유한 계정의 승인이 반드시 필요합니다. 조직 연합은 이제 라이브입니다.

확정 · 구현됨

초대 링크 = 1회 표시 bearer

초대 링크는 admin에게 1회만 표시되어 직접 공유합니다(자동 이메일 발송 없음). 따라서 링크 자체가 bearer 자격증명이라, 토큰을 그 현실에 맞게 설계했습니다(05 참조).

02 · 초대 온보딩 온보딩 §3

초대 온보딩 슬라이스 — 구현·라이브

합의된 흐름을 실제로 만든 슬라이스이며, 이제 라이브입니다. 원래 강조점은 대부분이 신규였다는 것 — 이전 코드엔 구성원을 만드는 기능 자체가 없었습니다(기존 멤버 서비스는 읽기/수정만, User 행을 쓰는 곳은 시드와 제거 대상 SSO 자동 프로비저닝뿐이었습니다). 그래서 단순 "연결"이 아니라 새로 도입된 쓰기 표면이며, 이제 그것이 라이브로 동작합니다.

  • "구성원 추가" admin 서비스 — 이메일+팀+역할로 pending User 행 + 초대를 삽입하고, 이메일 유일성 규칙을 처리합니다.
  • admin API + "구성원 추가" 프론트 폼 — 구성원 화면에서 동작합니다.
  • 1회용 초대 토큰 + 1회 표시 first-auth 링크 — admin이 복사해 공유하고, pending 구성원은 "초대됨" 배지 + 재발급/취소를 노출합니다.
  • 공개 /invite/<token> 수락 화면 + 토큰-게이트 set-password 엔드포인트 + Google 수락 바인딩.
  • 전체 수명주기 audit — 초대 생성·취소·재발급·수락·거부를 모두 기록합니다.
  • 제거됨: SSO 도메인 자동 프로비저닝 — 자동 합류로 회귀 금지.
범위 경계이 슬라이스 자체는 단일 org를 유지합니다(계정당 한 org만 아직). 다만 셀프서비스 org 생성과 조직 연합은 이제 별도로 라이브입니다(07 참조).
왜 "신규"였는가이전에 User 행을 쓰는 유일한 경로는 시드와, 제거된 SSO 자동 프로비저닝뿐이었습니다. 즉 admin이 사람을 만들어내는 첫 번째 쓰기 표면이 새로 도입됐고, 그래서 04의 이메일 유일성·팀 저장 결정이 함께 실렸습니다.
03 · 초대 · 수락 흐름 온보딩 §5

초대(admin)와 수락(직원), 두 흐름

온보딩은 두 단계로 나뉩니다 — admin이 사람을 초대하고, 직원이 링크를 수락해 활성화됩니다. 아래는 구현된 흐름입니다(라이브).

초대admin이 구성원을 추가

STEP 1구성원 추가admin이 이메일 + 팀 + 역할 입력
STEP 2pending 생성pending User 행 + 초대 토큰 생성(유일성 규칙은 04)
STEP 3링크 1회 표시https://app.aisuit.dev/invite/<token>를 admin에게 1회만 노출
STEP 4안전 채널 공유admin이 신뢰 채널로 직접 전달(자동 이메일 없음)
STEP 5목록 표시pending + 재발급/취소 노출
STEP 6auditinvite.created 기록

수락직원이 링크를 교환 — 두 분기

공개 /invite/<token> 화면에서 토큰을 검증(존재·pending·미만료, 아니면 균일 not-found)한 뒤, 둘 중 하나로 수락합니다.

(a) Google

소셜 신원 바인딩

/invite에서 단명 httponly 초대 쿠키를 설정하고(+서명된 OAuth state에 초대 참조를 실어), SSO로 redirect합니다. callback은 그 특정 초대를 로드해 pending+미만료를 확인하고, 이메일 일치 AND org 일치를 확인한 뒤 원자적으로 소비하고, 초대된 pending 사용자에 소셜 신원을 바인딩합니다.

(b) 비밀번호

토큰-게이트 set-password

토큰-게이트 set-password 엔드포인트로 POST합니다. 링크 자체가 bearer이므로 강화합니다 — 수락자가 초대 이메일을 재확인하고, 토큰은 1회 + 짧은 만료 + 높은 엔트로피, 그리고 초대된 org가 require_sso이 분기 자체를 제공하지 않습니다(Google 전용).

핵심 — 수락은 다른 계정 로그인이 아니다Google 수락 중에는 "이메일이 일치하는 기존 활성 계정으로 그냥 로그인"하는 fallthrough를 금지합니다. 수락은 초대된 사용자를 활성화하는 것이지, 다른 계정에 로그인하는 것이 아닙니다.
두 분기의 공통 결과어느 쪽이든 → status='active', 토큰 소비, 세션 시작 → 직원 워크스페이스로 진입. audit: invite.redeemed(method) 또는 invite.redeem_denied(reason).
04 · 데이터 모델 온보딩 §4

무엇을 어떻게 담는가 (구현됨)

대부분은 기존 구조 위에 얹혔습니다. users.status는 이미 존재해 마이그레이션 없이 그대로 쓰고, 초대를 담는 테이블이 새로 추가됐습니다 — 아래는 모두 실제 라이브 DB에 존재하며 additive로 마이그레이션된 상태입니다.

대상설계비고
users.statusinvited | active | disabled 사용이미 존재 — 마이그레이션 없음
member_invitations조직 · pending 사용자 · 이메일 · 역할 · 팀(nullable) · 초대자 · token_hash(sha256 1회·≥32바이트 엔트로피) · 만료 · 수락시각 · 상태 · 생성시각실재 · raw 토큰은 링크에만, 저장은 해시
새 write 표면이건 단순 추가가 아니라 새 write 표면입니다 — admin이 주도하는 첫 User INSERT가 여기서 생깁니다. 그래서 아래 두 가지를 함께 결정·구현했습니다.
결정 · 구현됨

이메일 유일성

같은 org+이메일로 초대하면 먼저 조회합니다 — 활성이면 "이미 구성원입니다"로 거부(깔끔한 메시지), pending이면 그 행을 재사용하고 새 토큰만 발급(중복 INSERT 금지), disabled면 재활성 경로로 처리합니다.

결정 · 구현됨

영속 사용자↔팀 연결

팀은 사람의 속성이므로 users.team_id를 추가해 확정했습니다(이전엔 팀 연결이 agent 배정에만 있어 pending 사용자를 담을 수 없었습니다). 이제 라이브 DB에 존재합니다.

멀티-org용 테이블도 이제 실재조직 owner·규모(organizations.owner_user_id·scale), 연합 링크(org_links·org_link_grants) 테이블도 이제 실재합니다 — 셀프서비스 org 생성과 조직 연합이 라이브이기 때문입니다. 아직 남은 것은 계정⟂멤버십 분리뿐입니다.
05 · 보안 온보딩 §6 · 불변식8 · 불변식9

초대 온보딩의 보안 약속

초대 링크가 곧 자격증명이라는 사실이 보안 설계 전체를 끌고 갑니다. 아래는 적용된 방어선입니다(라이브에서 모두 강제됩니다).

  • 초대 토큰 = bearer 자격증명 — 1회 · ≥32바이트 엔트로피 · 짧은 만료 · 저장은 sha256 해시 · raw는 링크에만 · 미로깅 · HTTPS만 · 취소/재발급 지원.
  • 소셜 바인딩 OAuth 왕복 — 초대를 redirect 전반에 스레딩합니다(서명 state + 단명 쿠키). callback이 그 특정 초대에 바인딩해 이메일/org 일치를 확인하고 원자적으로 소비하며, 기존 계정 fallthrough를 금지합니다 — 이것이 핵심 수정입니다.
  • 비밀번호 경로가 약한 고리 — 링크가 인증 요소이자 계정 주장이므로, 이메일 재확인 + 1회/짧은 만료/높은 엔트로피 + require_sso면 Google 전용 강제로 보강합니다.
  • audit 필수invite.created / revoked / reissued / redeemed / redeem_denied를 모두 기록하되, raw 토큰은 절대 미기록.
  • 수락 전 접근 0 — pending 사용자는 세션·권한이 없습니다. 역할/팀은 초대 때 admin이 지정하며 도메인 추론 금지.
  • 테넌트 격리(불변식8) — 초대/토큰/pending은 org-스코프이고, cross-org/무효 토큰은 균일 not-found로 응답합니다.
비밀번호 경로는 솔직히 약한 고리이메일 왕복 코드가 없으면 비밀번호 수락은 결국 "신뢰 채널 bearer 링크"입니다. 그래서 admin이 신뢰할 수 있는 채널로 공유해야 하고, require_sso org에서는 비밀번호 분기를 아예 제공하지 않습니다.
도메인 자동 프로비저닝 제거가 실제로 없앤 것제거된 것은 ① 자동 INSERT(도메인으로 사용자를 자동 생성하던 경로), 그리고 ② 셀프서비스 org 생성을 위해 반드시 정리해야 했던 도메인 기반 기존-사용자 매칭입니다. 도메인 allowlist는 hd 힌트로 강등됐고, 둘 다 정리되어 자동 합류로의 회귀가 사라졌습니다.
06 · 운영 수명주기 온보딩 §7

온보딩만 하고 끝내지 않는다

온보딩만 만들고 offboarding을 못 하면 구멍이 됩니다. 그래서 offboarding 수명주기까지 함께 구현·라이브입니다 — kill-switch·재활성화·in-flight 잔여 정리·org 소유권 이전이 모두 동작합니다.

offboarding · 구현됨

비활성화 kill-switch + 재활성화

admin offboard 액션이 사용자를 disabled로 전이하고, 그 사람의 사람 세션 + 활성 MCP 토큰을 폐기합니다(in-flight 잔여 정리). disabled 사용자는 재활성화로 되돌릴 수 있습니다.

소유권 · 구현됨

org 소유권 이전

owner를 offboard하면 org가 owner 없이 남아 교착될 수 있습니다. 그래서 org 소유권 이전을 구현해 offboarding 교착에서 벗어날 수 있게 했습니다. 미수락(만료) 초대는 status='invited'로 걸러 활성 목록에서 제외합니다.

07 · 조직 연합 · 이후 목표 온보딩 §8 · federation-design-v0.1

조직 연합은 라이브, 남은 목표는 하나

한때 "이후 목표"였던 셋 중 셀프서비스 org 생성과 조직 연합은 이제 라이브입니다. 유일하게 남은 이후 목표는 계정⟂멤버십이며, 라이브 시스템은 그 전까지 한 계정 = 한 org를 유지합니다.

라이브

셀프서비스 org 생성

공개 /signup → 새 org + 첫 owner(생성자=owner). 로그인 화면에 진입 링크가 있고, IP throttle로 자동 org 남발을 차단합니다. 계정⟂멤버십 전까지 한 계정 = 한 org.

라이브

동의 기반 org 연합

org owner가 다른 org를 각 owner의 승인으로 연결합니다(org_links + consent FSM). "링크가 무엇을 grant하나"는 읽기 전용 거버넌스 롤업 먼저로 확정됐고, 이것이 유일하게 승인된 스코프·감사되는 cross-org 다리입니다 — 테넌트 벽(불변식8)은 그대로. owner 전용 "조직 연결" 콘솔 화면도 라이브입니다.

이후 목표 · DEFERRED

계정⟂멤버십 신원

한 신원 → 여러 멤버십 + org 스위처. 진짜 비용은 컬럼이 아니라, SSO 진입점을 "provider가 org를 고른다"에서 "검증된 신원 → 멤버십 선택"으로 re-key하는 것입니다. 그래서 수락 시 issuer+sub를 저장해 나중에 re-key할 수 있게 해뒀습니다. signup은 그 전까지 계정당 두 번째 org 생성을 거부합니다.

연합 상세는 별도 문서조직 연합의 상세 설계·구현은 federation-design-v0.1.md에서 다룹니다. 남은 계정⟂멤버십만 아직 목표이며, 라이브 설계는 그 길을 막지 않도록 수락 시 issuer+sub를 이미 저장해 둡니다.
08 · 논의 항목 온보딩 §9

결정된 질문과 아직 열린 질문

아래 질문들 대부분은 이미 결정·구현됐습니다(녹색 확정). 나머지는 계정⟂멤버십과 함께 아직 열려 있는 항목입니다.

확정 · 구현됨 아직 열림
1require_sso org에서 수락은 Google 전용(비밀번호 분기 없음).확정
2Google 수락 시 이메일 불일치 = hard-reject.확정
3영속 사용자↔팀 저장 = users.team_id.확정
4연합 링크가 grant하는 것 = 읽기 전용 거버넌스 롤업 먼저.확정
5테넌트별 provider 목록 스코핑 — 방문자에게 어느 org의 소셜 버튼을 보이느냐.열림
6전역 "Google로 가입"(글로벌 소셜 signup).열림
7생성 전 이메일 검증(email-existence oracle 완전 차단).열림
8계정⟂멤버십 re-key — 한 신원이 여러 org를 오가도록 SSO 진입점 재설계.열림