ADR-008 — Tenant retention override (Workspace + Keycloak group)
| 항목 | 값 |
|---|---|
| Status | Proposed (2026-05-25 — #982 design step) |
| Date | 2026-05-25 |
| Decider | GenD 코어팀 + 컴플라이언스 owner |
| Related Issue | #982 (P3, 멀티 테넌트 도입 후 단계) |
| Related Epic | #1018 (Workspace Isolation) — prerequisite |
| Related ADR | ADR-006 (Ontology Layered Packaging L4 = Workspace) |
본 ADR 의 한 줄 결정: 테넌트별 retention 기본값을 Workspace (#1018) 와 결합하여
tenant_retention_default테이블로 분리한다. Industry 글로벌 default → Workspace override → 최종 RetentionPolicy override 의 3-단계 fallback 으로 동작한다.
컨텍스트
PR #970 (#957) 로 Industry 별 retention 템플릿이 dialog 시작점으로 제공되지만, 멀티 테넌트 환경에서는 같은 산업이라도 테넌트(법인/부서/계약) 별로 보존 기간·action 이 다를 수 있다. 현재는 정책 생성 시 사용자가 매번 override 해야 하고, 테넌트 단위 "기본 정책" 개념이 없다.
#982 는 이를 P3 작업으로 등록했으나 본문 명시 prerequisite (멀티 테넌트 로드맵 확정 / Keycloak group 정책 합의 / 설계 세션) 가 모두 별도 트랙. #1018 (Workspace Isolation Epic) 이 M1 Step 5 까지 머지 완료된 시점에서 design 단계의 ADR 로 정착시켜 둔다 — 실제 구현은 #1018 M2 cross-cutting FK propagation 이 끝난 뒤로 미룬다.
결정
1) 데이터 모델
tenant_retention_default 테이블 신설:
class TenantRetentionDefault(Base):
__tablename__ = "tenant_retention_default"
__table_args__ = (
UniqueConstraint(
"workspace_id", "industry",
name="uq_tenant_retention_default_per_industry",
),
CheckConstraint(
"default_action IN ('delete', 'archive', 'anonymize')",
name="ck_tenant_retention_action",
),
CheckConstraint(
"recommended_days >= 1",
name="ck_tenant_retention_days_positive",
),
)
id: Mapped[uuid.UUID]
workspace_id: Mapped[uuid.UUID] # FK workspaces.id ON DELETE CASCADE
industry: Mapped[str] # finance, healthcare, public, retail, manufacturing
recommended_days: Mapped[int]
default_action: Mapped[str] # delete | archive | anonymize
rationale: Mapped[str | None] # 컴플라이언스 근거 (사람용)
created_by: Mapped[str]
created_at: Mapped[datetime]
updated_at: Mapped[datetime]
Keycloak group 메타데이터 저장 대신 별도 테이블 채택 사유:
- audit / RBAC: PG row 가
created_by와updated_at을 가져 변경 이력 감사 가능. - 성능: Keycloak group attribute 는 토큰 발급 시점 가져오므로 retention dialog 매 호출마다 API roundtrip 발생 — PG cache 가 단순.
- 일관성: 기존
RetentionPolicy가 PG 에 있어 같은 transaction 안에서 조회 가능.
2) Fallback 체인
GET /api/v1/retention/templates?industry={ind} 응답 우선순위:
1. caller 의 active workspace 의 tenant_retention_default(workspace_id, industry)
2. 글로벌 INDUSTRY 템플릿 (#970 PR 의 _INDUSTRY_TEMPLATES dict)
3. (없으면 응답 본문이 비어 있음 — caller 가 수동 입력)
dialog 의 Industry Select 상단에 "테넌트 기본 (workspace)" 옵션 신설 (1) 이 있을 때만 표시. (1) 미설정 시에는 옵션 없음 — 단일 테넌트 사용 시 기존 흐름과 100% 호환.
3) Workspace ↔ Industry 매핑
workspaces.industry 컬럼 추가 (단일 industry per workspace 가정):
class Workspace(Base):
# ... existing fields
industry: Mapped[str | None] # finance, healthcare, public, retail, manufacturing
tenant_retention_default.industry 는 workspaces.industry 와 별개로 두 — workspace 가 multi-industry 운영 (예: 금융 + 헬스케어 데이터 모두 보유) 일 경우 industry 별로 별도 row 생성 가능.
4) UI
apps/ui/src/components/admin/RetentionPage.tsx 의 Industry Select 위에 "테넌트 기본" 액션 신설.
- 클릭 시 현 workspace 의
tenant_retention_default가 있으면 prefill, 없으면 "이 workspace 의 기본값으로 등록" 모달 (admin 권한 한정). - 모달에서 등록 후
GET /api/v1/retention/templates응답에 자동 반영.
5) API
GET /api/v1/retention/templates?industry=finance
→ workspace default → industry default 순서로 응답
POST /api/v1/retention/tenant-defaults
body: { industry, recommended_days, default_action, rationale }
→ caller 의 active workspace 에 등록 (admin only)
PUT /api/v1/retention/tenant-defaults/{id}
DELETE /api/v1/retention/tenant-defaults/{id}
영향
- 영향 자식 이슈: #982 (본 ADR 의 구현), #1018 (Workspace Isolation 의 cross-cutting 적용 대상에 포함).
- 운영: 멀티 테넌트 도입 후 정식 운영 단계에서 활성화. 현 prod (단일 테넌트) 는 기존 industry 글로벌 default 만 사용 → 호환 유지.
- 회귀 가드:
tenant_retention_default모델 ORM 회귀 가드 + retention 라우터의 fallback 우선순위 회귀 가드 (Industry global default 가 사라지지 않게). - 컴플라이언스:
rationale컬럼은 감사 시 "왜 이 보존 기간을 정했는가" 를 사람이 읽을 수 있는 형태로 남긴다. CREDIT_INFO_ACT / EFT_ACT 검사 시 증빙.
비목표
- 본 ADR 은 design plan 의 단일 진실의 원천. 실제 구현 PR (#982) 은 #1018 M2 cross-cutting FK 가 끝난 뒤 별도 트랙으로 진행.
- workspace-가-아닌 organization / business unit 단위 (Workspace 하위 부서) retention override 는 본 ADR 범위 밖. 필요 시 ADR-009 로 확장.
- 동일 industry 내 PIPA / GDPR / SOC2 별 retention 매트릭스는 본 ADR 범위 밖 —
tenant_retention_default는 industry 단위 best-effort default 만 제공.
재검토 트리거
- 다중 industry workspace 비중 증가 →
tenant_retention_default의 unique key 를(workspace_id, industry, framework)로 확장 검토. - Workspace 폐쇄 (suspended → deleted) 시 retention default row 처리 정책 결정 필요 (cascade vs orphan).
- 컴플라이언스 owner 검토로
rationale형식 표준화 (자유 텍스트 vs 정의된 카테고리).