Workspace W2 Rollout — 다중 워크스페이스 멤버십 운영
Epic #1362 종결 후의
실제 운영 절차. 한 사용자가 finance-invest (금융 인텔리전스) +
energy-kogas (한국가스공사) 두 워크스페이스에 동시 소속하고 UI
드롭다운에서 자유롭게 전환.
W1 단일 워크스페이스 인프라 (workspace-isolation.md) 의 후속 — DB·FK·Keycloak group sync 는 무변경, JWT 다중 멤버십 + UI 전환 흐름만 추가.
토폴로지
Keycloak realm: gend
├── /tenants/finance-invest ← 기존 /tenants/internal 에서 rename
│ ├── ttagu99@gmail.com
│ ├── analyst-alice@company.com
│ └── ... (기존 사용자 전원 자동 잔류 — group rename 만)
│
└── /tenants/energy-kogas ← 신규
├── ttagu99@gmail.com ← 동시 멤버십 (W2 핵심)
└── kogas-analyst@company.com
데이터 격리 매트릭스
| 테이블 그룹 | finance-invest 사용자 | energy-kogas 사용자 | 정책 |
|---|---|---|---|
intel_articles, intel_ohlcv_* (Fact) | ✅ | ✅ | NULL 공유 |
intel_briefings, intel_risk_reports | finance 만 | ❌ | NOT NULL 격리 |
onto_* instance, KOGAS dashboards | ❌ | kogas 만 | NOT NULL 격리 |
query_history, saved_queries, data_marts | finance 만 | kogas 만 | M2 FK 전파 |
시드 절차
Pre-flight 점검
# 1) AKS prod context 확인
kubectl config use-context aks-genos-prod
kubectl get pods -n gend | grep gend-api
# 2) workspace_id IS NULL row 수 (격리 의도 어긋남 사전 진단)
kubectl exec -n gend deploy/postgresql -- psql -U gend -c "
SELECT 'intel_briefings' AS t, COUNT(*) FROM intel_briefings WHERE workspace_id IS NULL
UNION ALL
SELECT 'query_history', COUNT(*) FROM query_history WHERE workspace_id IS NULL
UNION ALL
SELECT 'saved_queries', COUNT(*) FROM saved_queries WHERE workspace_id IS NULL
UNION ALL
SELECT 'data_marts', COUNT(*) FROM data_marts WHERE workspace_id IS NULL;"
시드 실행
# admin JWT 발급 (Keycloak)
export GEND_ADMIN_TOKEN=$(curl -s -X POST \
https://gend.genon.ai/auth/realms/gend/protocol/openid-connect/token \
-d "grant_type=password" \
-d "client_id=gend-ui" \
-d "username=admin" \
-d "password=$ADMIN_PASS" \
-d "scope=openid" \
| jq -r .access_token)
# 사용자 본인 Keycloak sub 조회
export GEND_W2_ADMIN_USER_ID=$(curl -s \
https://gend.genon.ai/auth/admin/realms/gend/users?username=ttagu99 \
-H "Authorization: Bearer $GEND_ADMIN_TOKEN" \
| jq -r '.[0].id')
# 시드 실행 (idempotent — 여러 번 실행해도 안전)
GEND_API_BASE=https://gend.genon.ai \
python3 scripts/seed_w2_workspaces.py
Post-flight 검증
# 1) Workspace row 2건 (finance-invest + energy-kogas)
kubectl exec -n gend deploy/postgresql -- psql -U gend -c \
"SELECT id, slug, name FROM workspaces;"
# 2) Keycloak group rename + 신규 group 확인
curl https://gend.genon.ai/auth/admin/realms/gend/groups \
-H "Authorization: Bearer $GEND_ADMIN_TOKEN" \
| jq '.[] | select(.name == "tenants") | .subGroups[] | {name, path}'
# 3) JWT 재로그인 후 멤버십 확인
TOKEN=$(curl ... password grant ...)
echo $TOKEN | cut -d. -f2 | base64 -d | jq '.groups, .workspace_slugs'
UI 동작 검증
- 로그아웃 → 재로그인 (JWT 갱신 필수)
- TopBar WorkspaceSwitcher 드롭다운에 2개 항목 노출 확인
- 클릭으로 전환 → toast
... 으로 전환했습니다→ 모든 데이터 화면 자동 재조회 /intel/briefings와/dashboards/kogas/lng두 페이지에서 격리 가시 확인
롤백 절차
internal → finance-invest rename 을 되돌려야 할 경우:
# Step 2 워크플로우로 slug 역방향 변경
FINANCE_ID=$(kubectl exec -n gend deploy/postgresql -- psql -U gend -t -c \
"SELECT id FROM workspaces WHERE slug='finance-invest';" | tr -d ' ')
curl -X PUT https://gend.genon.ai/api/v1/workspaces/$FINANCE_ID \
-H "Authorization: Bearer $GEND_ADMIN_TOKEN" \
-d '{"slug":"internal","name":"사내"}'
UUID 보존이라 FK 영향 0건. energy-kogas 는 status=deleted 로 전환:
curl -X PUT https://gend.genon.ai/api/v1/workspaces/$KOGAS_ID \
-d '{"status":"deleted"}' ...
알려진 제약 (W3 에서 해소)
- 위임 트리 (본사 → 지사 → 협력사) 는 별도 Epic #1149
- 워크스페이스 별 audit chain 분리 (W3 GA)
- 워크스페이스 전환 시 토큰 자체 갱신 (현재는 헤더만, Keycloak token-exchange 는 미사용)