# Tenant visibility exposure policy

## 목적

`hanmac-family` 하위 조직의 `config.visibility` 값이 호출 위치별로 어디까지 노출되는지 정리한다. 현재 정책에서 완전 공개 조직도는 없다고 본다. 따라서 `public`은 인터넷 전체 공개가 아니라 인증/공유 경계 안에서의 기본 노출값이다.

## Visibility 값

| 값 | 의미 | 기본값 여부 |
| --- | --- | --- |
| `public` | 인증된 사용자 화면과 통제된 공유 경계에서 기본 노출 가능한 조직. 현재 정책상 인터넷 완전 공개를 의미하지 않음 | `visibility`가 없거나 빈 값이면 `public` |
| `internal` | Baron 로그인 세션 또는 M2M API Key를 가진 내부/연동 경계에는 노출하지만, 외부 공유 경계에는 노출하지 않는 조직 | 명시 설정 필요 |
| `private` | 기본 조직도 노출 대상에서 제외하는 조직. 해당 조직의 하위 조직도 함께 제외 | 명시 설정 필요 |

`visibility`와 `orgUnitType` 설정은 현재 `hanmac-family` descendant tenant에만 허용된다. `hanmac-family` root 자체에는 org config를 두지 않는 정책이다.

## 호출 위치별 노출 기준

| 호출 위치 | 인증/권한 경계 | `public` | `internal` | `private` |
| --- | --- | --- | --- | --- |
| OrgFront 일반 조직도 `/chart` | 로그인 세션, backend `/api/v1/admin/tenants` 기반 | 노출 | 노출 | 권한 없는 사용자는 미노출 |
| OrgFront embed picker `/embed/picker` | 로그인 세션 또는 picker 자동 로그인 경로 | 노출 | 노출 | 권한 없는 사용자는 미노출 |
| 공유 링크 `/api/v1/public/orgchart?token=...` | share token. 완전 공개가 아니라 통제된 공유 경계 | 노출 | 미노출 | 미노출 |
| M2M 조직 Context JSON API `/api/v1/integrations/org-context` | API Key + `org-context:read` | 노출 | 노출 | 미노출 |
| AdminFront 테넌트 관리 `/api/v1/admin/tenants` | 사용자 role/Keto 관리 경계 | 노출 | 노출 | `super_admin`, 해당 private subtree owner/admin/manage 가능 사용자, 또는 명시적 private 조회 권한자만 노출 |
| AdminFront CSV export | 사용자 role/Keto 관리 경계 | 노출 | 노출 | `/api/v1/admin/tenants`와 동일 |
| AdminFront CSV import | 권한 있는 관리자 작업 | 입력값 검증 대상 | 입력값 검증 대상 | 입력값 검증 대상 |

## 핵심 판단

`internal`은 현재 “특정 조직 권한자에게만 보이는 조직”이 아니다. 로그인된 OrgFront 사용자가 backend에서 해당 tenant family를 받을 수 있는 경우, OrgFront 기본 조직도와 picker에는 `internal` 조직이 기본 노출된다.

다만 `internal`은 공개 공유 링크에서는 제외된다. 외부 M2M JSON API에서는 API Key 자체가 신뢰 경계이므로 `internal`을 포함한다.

`private`은 기본적으로 일반 조직도, picker, 공유 링크, M2M JSON API에서 제외된다. 또한 `private` 조직 아래의 descendant도 함께 제외된다. AdminFront 테넌트 관리와 CSV export에서도 권한 없는 사용자에게는 제외된다.

`private` 노출이 허용되는 사용자는 다음 중 하나다.

- `super_admin`
- 해당 private 조직 또는 ancestor를 `ManageableTenants`로 가진 owner/admin/manage 가능 사용자
- Keto/ReBAC에서 해당 private 조직에 `view_private`, `view_private_descendants`, `view`, `manage` 중 하나를 가진 사용자
- ancestor tenant에 `view_private_descendants`를 가진 사용자

## 구현 근거

- backend `tenantVisibility`는 `internal`, `private`만 별도 값으로 인정하고 나머지는 `public`으로 처리한다.
- backend `filterPublicTenants`는 `internal`, `private`, 그리고 그 descendant를 공개 공유 링크 노출에서 제외한다.
- backend `filterOrgContextSubtree`는 `private`과 그 descendant만 M2M 조직 Context에서 제외한다. 따라서 `internal`은 포함된다.
- backend `/api/v1/admin/tenants`와 CSV export는 사용자 profile과 Keto 권한을 기준으로 private root 및 descendant를 필터링한다.
- OrgFront `filterTenantsByVisibility(..., "internal")`은 `private`만 제외한다. 일반 `/chart`와 `/embed/picker`는 backend에서 이미 권한 필터링된 tenant 목록 위에서 이 기준을 사용한다.
- OrgFront `filterTenantsByVisibility(..., "public")`은 `internal`, `private`를 제외한다. share token 기반 공개 조직도가 이 기준을 사용한다.

## 회색지대

현재 이름만 보면 `public`이 인터넷 완전 공개라는 의미로 해석될 수 있지만, 정책상 완전 공개 조직도는 없고 `public`은 기본 노출값이다.

현재 이름만 보면 `internal`이 “조직 내부 구성원 또는 특정 권한자만”이라는 의미로 해석될 수 있지만, 구현은 “공유 링크에는 숨기고, 로그인/M2M 경계에는 노출”이다.

특정 권한자에게만 보이는 조직은 `private`과 Keto/ReBAC 권한으로 표현한다. `internal`을 제한 공개 권한 모델로 확장하려면 별도 정책 변경이 필요하다.
