Skip to content

System Admin 재설계 — 개발 계획 (v0) ​

2026-08-20 작성. feat/system-admin-redesign. 짝 문서: design-system-admin-statutory-monitor.md(이 재설계 위에 얹을 첫 소비 기능), project_account_layer(meta account — 인증 토대).

1. 목표 — 세 축 ​

현재 tools/system-admin은 단일 DB에 raw pg 직접 쿼리 + 환경변수(IP allowlist + 단일 JWT) 구조다. 이를 다음으로 전환한다.

#축현재목표
①데이터 접근server/db.ts의 raw pg (TypeORM/코어 로직 미사용)API 서버 경유 — 각 엔드포인트의 tRPC 시스템 프로시저 호출 (코어 로직 재사용)
②인증환경변수 IP allowlist + 단일 시스템 JWT (server/auth.ts)시스템 계정 인증 — accounts.platformRole(meta account) 기반, 감사 주체 식별 가능
③토폴로지한 DB(한 조직군)멀티 엔드포인트 — 엔터프라이즈 클라이언트마다 독립 API+DB+인증 DB, 한 콘솔에서 전환·순회

핵심 통찰: ③ 때문에 system-admin은 어떤 단일 DB에도 직접 붙을 수 없다. N개의 독립 배포(API+DB)를 HTTP로 호출하는 중앙 오케스트레이터가 되어야 한다. 따라서 ①은 "TypeORM으로 바꾸기"가 아니라 "HTTP API 클라이언트로 바꾸기"다.

그리고 인증도 엔드포인트마다 독립이다 — §2.1이 이 재설계의 최난도 핵심.

2. 목표 아키텍처 ​

                    ┌───────────────────────────┐
   운영자 로그인 ──▶ │  system-admin (중앙 콘솔)   │
                    │  · 엔드포인트 레지스트리     │
                    │  · 운영자 세션(시스템 계정)  │
                    │  · fan-out 오케스트레이터    │
                    │  · 중앙 릴리스/감사 기록(선택)│
                    └───────────┬───────────────┘
              per-endpoint 시스템 계정 토큰으로 호출
        ┌───────────────┬───────┴───────┬───────────────┐
        ▼               ▼               ▼               ▼
   API+DB (ent A)   API+DB (ent B)   API+DB (ent C)   ...
   systemProcedure  systemProcedure  systemProcedure
   (전 org 집계 = 자기 DB 안에서)
  • 각 API 서버(packages/api): 시스템 스코프 프로시저(systemProcedure)를 노출. 자기 DB 안에서 전 조직 집계/조직 상세를 코어 로직 재사용으로 계산.
  • system-admin 서버: raw pg(db.ts) 제거 → ⑴ 엔드포인트 레지스트리(소규모 로컬 저장) + ⑵ 엔드포인트별 세션 매니저(§2.1) + ⑶ fan-out 유틸(부분 실패 허용·타임아웃·버전 미지원 구분). 화면은 그대로 Alpine 탭.

2.1 인증 모델 — 본인 계정 · 전 org 접근 시스템관리자 계정 (★ 핵심, 2026-08-20 정정) ​

결정(사용자): 기존 인증 시스템을 그대로 쓴다. 인증자는 서비스 계정이 아니라 자기 계정(본인 계정)으로 접근한다. 시스템관리자 계정 = 한 엔드포인트 안에서 모든 organization을 방문할 수 있는 Account이며, 특정 org의 권한은 그 org의 grant/role을 받아 부여된다. 본인 계정으로 접근하므로 감사 주체가 자연히 사람으로 남는다(이전 초안의 "서비스 계정 + 신원 전달" 긴장은 폐기).

현재 인증 시스템 (코드 재확인, 2026-08-20) ​

조직 접근은 전부 명시적·조직당이다. "전 org 접근" 능력은 아직 없다.

경로무엇스코프
org 세션 (resolveUser, JWT userId)ACTIVE Employee 로드 → 그 조직 권한조직 1개 (멤버십)
owner 암묵 grant (resolveOwnerGrantUser)ownerAccountId == account.id 검증 → 전권자기 소유 조직 1개
grant 세션 (resolveGrantUser, JWT grantId)organization_grants 행의 스코프/impersonationgrant가 지정한 조직 1개
platformRole(OPERATOR/SUPPORT)grant 발급·회수 + audit 조회그 자체로 org 접근 없음 — 들어가려면 명시적 grant 필요

→ 빌드해야 할 능력(gap): "시스템관리자 계정은 grant 행 없이도 임의의 org에 진입" — 즉 owner 암묵 grant(자기 조직)를 전 org로 일반화한 것. resolveOwnerGrantUser 패턴을 그대로 확장한다(Phase 0).

목표 모델 — 계층 확정 (2026-08-20) ​

결정: 벤더 발급 유지 + 시스템관리자는 전 org를 스스로 전권 제어(고객사 요청 루프 없음). org 동의는 계약 약관(시스템 강제 없음 — 2026-07 결정 유지). 두 계층으로 나뉜다.

  1. 시스템관리자 = OPERATOR (전 org 전권, 내재) — platformRole == OPERATOR면 그 엔드포인트의 어느 org든 열거·진입 + 전권(owner 동형). 진입 시 JWT {accountId, organizationId, systemGrant:true} → resolveSystemGrantUser가 platformRole만으로 SUPER_ADMIN 상당 세션을 합성. per-org grant 행 불필요 — 매번 발급·승인 없이 즉시 제어(사용자: "그 권한까지 제어 가능해야, 매번 고객사 요청 불가"). 감사는 본인 계정으로 전량 기록되므로 통제는 사후 audit로.
  2. SUPPORT = per-org 스코프 grant (OPERATOR가 발급) — 제한된 지원 인력은 전권이 아니라 organization_grants(roleCode/grantedPermissions/impersonation)를 OPERATOR가 벤더-사이드로 발급받아 특정 org만 스코프 접근. 이게 Phase B에서 이미 구현된 부분. (org 관리자 승인 경로는 두지 않음 — 발급 권한은 벤더.)

즉 최상위 시스템관리자는 계정 속성(OPERATOR)만으로 전 org 전권, 하위 지원 계층은 발급받은 grant로 스코프 접근. platformRole 두 값이 그대로 이 두 계층이다.

멀티 엔드포인트에서의 세션 (독립 realm은 그대로) ​

각 엔터프라이즈 배포는 여전히 독립 인증 realm(자체 accounts·JWT secret·auth_tokens)이라 공유 IdP는 없다. 따라서:

  • 운영자의 시스템관리자 계정은 엔드포인트마다 존재(같은 이메일이라도 각 DB의 별도 account row). system-admin은 엔드포인트별로 운영자 본인 자격증명으로 로그인해 세션을 얻고, 그 세션으로 그 엔드포인트의 전 org에 접근한다.
  • system-admin은 엔드포인트별 세션을 동시에 보유·관리한다(세션 매니저): 엔드포인트 키별 토큰 캐시·독립 만료·401 시 해당 엔드포인트만 재로그인. 한 엔드포인트 세션이 다른 엔드포인트에 영향 없음.
  • 자격증명 저장: 운영자 본인 자격증명을 콘솔에서 엔드포인트별로 1회 입력(또는 리프레시 토큰 저장)해 볼트에 암호화 저장(encryptedColumn 재사용). 서비스 계정이 아니므로 이 볼트는 "운영자 개인의 엔드포인트별 세션 자격"이다. (저장 정책·SSO 여부는 §5-2.)
  • 감사: 각 엔드포인트 audit_log의 주체 = 운영자 본인 시스템관리자 계정. 별도 신원 전달 불필요.

3. 재사용 토대 (이미 있음) ​

  • accounts.platformRole(OPERATOR/SUPPORT) + AccountPrincipal.platformRole — 축 ②의 계정 속성. (meta account Phase B)
  • operatorProcedure / platform.router 패턴 — 시스템 계정 게이트의 선례. systemProcedure는 이걸 "org 비종속 + 전 조직 스코프"로 일반화한 형태.
  • audit_logs(migration 1864) + recordOperatorAction — 축 ②의 감사 주체 기록에 재사용.
  • 즉 ②의 절반은 이미 구현됨. 남은 것은 "org에 묶이지 않는 systemProcedure 타입"과 "각 엔드포인트가 시스템 계정 토큰을 발급/신뢰하는 방식".

4. 개발 단계 ​

Phase 0 — 시스템관리자 계정: 전 org 전권 접근 + systemProcedure (API) · 토대 ​

  • 전 org 진입 능력(gap): resolveOwnerGrantUser(자기 조직 1개)를 일반화한 resolveSystemGrantUser 추가 — platformRole == OPERATOR면 임의의 org에 전권(owner 동형) 진입. {accountId, organizationId, systemGrant:true} JWT. 진입 API: account.enterOrganizationAsSystemAdmin(organizationId)(OPERATOR 게이트, org 열거 포함). per-org grant 불필요. (SUPPORT 계층의 스코프 접근은 기존 grant 경로 그대로.)
  • systemProcedure 신설(packages/api/src/trpc.ts): 전 조직 집계(health)·특정 org 상세(audit) 둘 다 지원하는 org-비종속 시스템 스코프. platformRole 필수. 멀티테넌시 예외를 여기서 명시적으로 못 박는다(토큰 위조 아닌 계정 속성으로 판정).
  • read/write 구분 축을 처음부터(§5-3): 최소 프로시저 단위, 가능하면 platformRole별.
  • 감사: 전 org 진입 세션은 이미 actingVia(meta account) 흐름을 타 audit_log에 본인 계정으로 기록됨 — Phase C 인프라 재사용. systemProcedure 호출도 동일 훅.
  • 검증: 시스템관리자 계정으로 임의 org 진입 OK(본인 계정 감사행) / 일반 계정은 거부 / systemProcedure 전 org 집계 OK.

Phase 1 — 엔드포인트 레지스트리 + 다중 세션 매니저 + fan-out · 재사용 공통 (★ 인증 난제) ​

  • 레지스트리: 이름·baseURL·환경(prod/stg)·서버버전 + 엔드포인트별 운영자 본인 자격증명(암호화, §2.1). 저장 위치 §5-1.
  • 세션 매니저(§2.1): 엔드포인트별 본인 계정 독립 로그인 → 토큰 캐시(엔드포인트 키) → 만료 추적·선제 갱신 → 401 시 해당 엔드포인트만 재인증. 엔드포인트 간 세션 격리가 불변식.
  • fan-out 유틸: 등록된 전 엔드포인트 순회 호출(각자 자기 토큰 부착) → 결과 병합(성공/실패/미지원(구버전) 3분류), 타임아웃, 재시도. 법정공제뿐 아니라 모든 헬스류가 재사용하므로 공통 유틸로.
  • 검증: 2개 로컬 엔드포인트(같은 API 두 포트, 서로 다른 JWT secret으로 realm 분리 재현) → 각각 독립 로그인·세션 보유 확인 / 한쪽 토큰 만료·재인증이 다른 쪽 무영향 / 한쪽 다운 시 부분 실패 정상 처리.

Phase 2 — 기존 raw-pg 라우트를 API-경유로 점진 이관 (진행 중) ​

  • server/db.ts(raw pg) 소비처를 하나씩 API 클라이언트로 교체. 한 번에 다 바꾸지 않고 라우트별로.
  • 순서 권고(위험 낮은 read부터): organizations 목록/상세 → attendance 뷰어 → roles/admins → provision(write, 신중) → secom-sync/integrations/sheet-templates.
  • 각 이관마다 대응 systemProcedure를 API에 추가(코어 로직 재사용). 화면 계약(응답 shape)은 유지해 UI 변경 최소화.
  • platform-accounts 탭(platformRole 부여)은 특수: 이건 "각 엔드포인트 DB의 accounts"를 다루므로, 멀티 엔드포인트에선 "어느 엔드포인트의 계정인가" 축이 붙는다 — 이관 시 이 점 반영.

진행 현황 (2026-08-20)

  • ✅ 공통 인프라 + 읽기 라우트 6개 이관·검증:
    • 공통: orchestrator/runtime.ts(레지스트리+세션 싱글턴, dev 부트스트랩 SYSADMIN_ENDPOINT_CREDS) · orchestrator/org-scoped.ts(callOrgScoped — per-org 단일 엔드포인트 호출 헬퍼) · fanout.ts의 endpointStatuses 헬퍼.
    • fan-out(크로스-org): organizations 목록 → platform.systemOrganizations / sheet-templates → systemSheetTemplates / national-holidays → systemHolidayYears.
    • per-org(단일 엔드포인트 라우팅, ?endpoint=): organizations/:id 상세 → systemOrganizationDetail / :orgId/admins → systemOrgAdmins / :orgId/roles → systemOrgRoles.
    • API systemProcedure는 전부 TypeORM 재사용(raw SQL 복제 없음). 응답 계약 유지 + 엔드포인트 차원 부가.
    • 라이브: 목록 162 · 상세 6역할/1관리자 · 시트템플릿 23 · 공휴일 3년 · admins 1 · roles(EMPLOYEE 32권한).
  • ✅ 읽기 배치 2 이관·검증 (2026-08-20): callOrgScoped를 임의 input 수용으로 일반화 후 —
    • per-org: roles/:orgId/permissions → systemPermissions(109권한/35그룹) · attendance/:orgId → systemOrgAttendance(필터·페이지네이션, BESTRH 6908건·page2·ISO checkIn 검증)
    • fan-out: secom-sync → systemSecomSyncStates(엔티티 없어 API raw query 단일화)
  • ✅ 읽기 배치 3 이관·검증 (2026-08-20): integrations 자격증명 목록 → systemIntegrationCredentials(fan-out). 복호화·마스킹을 API-side에서 처리 — 원문 시크릿은 API 밖으로 나가지 않고 AK_liv...7890 형태 마스킹만 전달. 라이브: 시드 1건 삽입해 마스킹 round-trip + provider 필터 + 잘못된 provider 400 검증 후 정리.
  • ✅ 쓰기 배치 1 이관·검증 (2026-08-20): 조직 쓰기 3종 → API-경유 mutation.
    • PUT /:id/status → systemSetOrganizationStatus · PUT /:id → systemUpdateOrganization · DELETE /:id → systemDeleteOrganization(조직+직원+owner 계정 소프트삭제·토큰 무효화·confirmName 검사를 트랜잭션으로 재현).
    • 감사 패턴 확립: 각 쓰기가 recordOperatorAction(actor=운영자, 대상 org, redact input)으로 append-only 기록. system-admin은 callOrgScopedMutation 헬퍼로 400/403/404 원 상태코드 보존.
    • 라이브: 스크래치 조직+직원 1건으로 status→SUSPENDED / name·phone 수정 / confirmName 불일치 400 / 삭제(deletedEmployees=1) + 소프트삭제 캐스케이드 + audit_logs 3건(actorKind=OPERATOR·ok=true) 검증 후 정리.
  • ✅ 쓰기 배치 2 이관·검증 (2026-08-20): secom-sync 4종(create/toggle/reset-cursor/delete) → API-경유 mutation. secom_sync_state(엔티티 없음)는 API 내부 raw query, toggle/reset/delete는 대상 행 organizationId 조회 후 감사. 감사 공통 헬퍼 recordSystemWrite 추가. callOrgScopedMutation을 4xx(400/403/404/409) 원 상태코드 보존으로 일반화. 라이브: 전 라이프사이클+중복409+없는키404+audit_logs 4건 후 정리.
  • ✅ 쓰기 배치 3 이관·검증 (2026-08-20): integrations 5종(create/update/webhook-secret generate·revoke/delete) → API-경유 mutation. 암호화 키를 API만 보유 — sysadmin은 시크릿 원문(TLS)만 보내고 API가 암호화 저장, sysadmin에서 필드 암복호 로직 완전 제거. 응답은 항상 마스킹(웹훅 생성 시에만 새 원문 1회). 라이브: create시 DB에 ciphertext(v1:…) 저장 확인 + 전 라이프사이클 + audit_logs 5건(create input apiKey=[REDACTED]) 후 정리.
  • ✅ 쓰기 배치 4 이관·검증 (2026-08-21): org-less 플랫폼 레벨 쓰기 3종 → API-경유. 결정 (a) 채택: audit_logs.organizationId를 nullable 로 완화(마이그레이션 1886)하고 organizationId=null 로 감사 — platformRole 부여 같은 민감 행위도 감사에 남는다.
    • sheet-templates(category/isActive) → systemPatchSheetTemplate · platform-accounts(platformRole 부여·해제, 읽기 계정검색도 함께 이관) → systemPlatformAccounts/systemSetPlatformRole · national-holidays(sync) → systemSyncNationalHolidays(코어 syncNationalHolidays 재사용 — raw-pg diff 규칙 복제 제거).
    • recordOperatorAction/recordSystemWrite의 organizationId 를 string|null로 확장. AuditLog 엔티티도 nullable.
    • 라이브(마이그레이션 후): SUPPORT 부여·해제 / 템플릿 category 변경(DB 확인) / 공휴일 2025 sync(fetched 19) + audit_logs 5건 전부 organizationId=NULL·OPERATOR 후 정리.
  • ✅ 쓰기 배치 5 이관·검증 (2026-08-21): roles 3종(create/update/delete + role_permissions 트랜잭션) · admins 3종(create/update/delete + accounts·employees 트랜잭션) → API-경유 mutation.
    • 역할: 중복 코드 409·permissionCodes 전체 교체·SUPER_ADMIN 삭제 금지·소프트삭제 시 권한링크 정리. 관리자: 비밀번호 원문(TLS)→API bcrypt 해시(감사 [REDACTED]), Account 생성→employees.accountId 연결, 비번 변경은 accounts 갱신, 마지막 관리자 삭제 방지.
    • TypeORM gotcha 발견·교정: manager.query는 UPDATE…RETURNING을 [rows, affectedCount]로 반환(INSERT…RETURNING은 rows 직접) — 라이브 테스트에서 잡음.
    • 라이브: 역할 전 라이프사이클(권한교체·isActive 토글 시 권한 보존) / 관리자 전 라이프사이클(신규·변경 비번 로그인 실증) + audit_logs 11건 후 정리.
  • ✅ 쓰기 배치 6 이관·검증 (2026-08-21) — 쓰기 이관 완료: provision 3종 + preview → API-경유. raw SQL 복제본을 버리고 provisionWorkspace(조직 생성의 단일 진입점) 재사용 — 서비스 주석의 '후속 통합 대상' 이행. 법정공제 요율 시드·알림 기본값·SUPER_ADMIN 배정까지 가입 경로와 동일.
    • 서비스 확장(하위호환): owner.employeeNumber(미지정 시 자동) · organization.linkOwnerAccount(콘솔 createOwnerAccount 의미) · legalName/medicalField/timezone.
    • seed-defaults 는 seedRolesAndPermissions(정본) 재사용 — 기존 역할 권한을 카탈로그로 동기화(기존 콘솔의 add-missing에서 의미 통일, 주의). seed-item-codes 는 콘솔 확장 카탈로그 유지(skip-if-exists). DEFAULT_PERMISSIONS/ROLES를 @hereby/database 에서 export.
    • national-holidays 연도별 목록(마지막 raw-pg 읽기)도 이관.
    • 라이브: preview(106/6) / WT_PROV 프로비저닝(조직·관리자·관리팀·SUPER_ADMIN·항목코드·statutory 10, 원문 비번 로그인 OK) / 중복 409 / seed 2종 / 공휴일 목록 19건 + audit 3건 후 정리.
  • Phase 2 이관 완료. 읽기 전부 + 쓰기 배치 1~6 전부 API-경유, 전 쓰기 감사됨. routes 디렉터리의 db.js 참조 0 — 남은 raw pg 는 db.ts 자체와 서버 부팅부(search_path 설정)뿐이라 Phase 3 의 db.ts 제거가 기계적 작업이 됐다.

Phase 3 — 인증 전환 마무리 + 환경변수 인증 제거 ✅ (2026-08-21 완료) ​

  • ✅ 콘솔 로그인 운영자 본인 계정 기반 전환: env 단일 계정(SYSADMIN_USERNAME/PASSWORD) 제거. 로그인 = 대상 엔드포인트(미지정 시 첫 엔드포인트) account.login 검증 + platform.systemWhoAmI(신설 systemProcedure)로 OPERATOR 확인 → 콘솔 JWT 발급 + 그 엔드포인트 세션 자동 등록. IP allowlist 는 심층 방어로 유지. 로그인 화면은 이메일 기반으로 전환.
  • ✅ Endpoints 탭 신설: 엔드포인트 목록 + 상태(?probe=true → systemWhoAmI 라이브 확인: ok/인증필요/연결실패) + 엔드포인트별 자격증명 등록(연결)·해제 UI. §2.1의 "운영자가 콘솔에서 엔드포인트별 본인 자격증명 입력"이 이것으로 구현됨(인메모리 — 영속 vault 는 §5-2 대기).
  • ✅ 검증-후-등록 원칙: verifyOperatorCredential 헬퍼가 SessionManager 밖에서 로그인+OPERATOR 를 검증하고 성공 시에만 setCredentials. 실패한 로그인/재등록 시도가 기존 등록 세션을 파괴하던 버그를 라이브 테스트에서 발견·교정.
  • ✅ raw pg 완전 제거: db.ts 삭제, search_path 부팅 코드 제거 — system-admin 은 순수 API 클라이언트(DB_* env 불필요).
  • 라이브 검증: env 계정 401 / OPERATOR 로그인→자동 연결(orgs 162) / 일반회원 403 / 틀린 비번 401 / 실패 시도 후 세션 보존 / probe·해제·재연결 전부 정상. UI 빌드 + DS 게이트(icons·modals·dialogs·spinners) 통과.
  • ✅ 최초 OPERATOR 부트스트랩 (2026-08-21 후속): 계정 기반 전환으로 생긴 닭-달걀 공백(부여 UI 가 OPERATOR 로그인 뒤에 있음)을 API 부팅 시 env 부트스트랩으로 해소 — OPERATOR_BOOTSTRAP_EMAIL/PASSWORD 가 있고 OPERATOR 가 전무할 때만 계정 생성(Grafana 패턴, 멱등·재부여 경로 차단, 기존 이메일이면 role 만 부여). meta-account §7 "콘솔 수동 부여"는 2번째 스태프부터의 규칙으로 유지. 라이브: 존재 시 skip / 전무 시 생성→그 계정으로 콘솔 로그인 검증.

Phase 4 — 첫 소비 기능: 법정공제 모니터 ​

  • design-system-admin-statutory-monitor.md §4를 이 토대 위에 구현(statutoryHealth 등). 재설계가 이 기능의 선행조건이었으므로 여기서 접합.

5. 결정 필요 (착수 전) ​

  1. 엔드포인트 레지스트리 저장 위치 — system-admin 로컬 소형 DB(신규) vs 설정파일/시크릿 vs 별도 "메타 API". (권고: 소형 로컬 DB — 자격증명·서버버전·중앙 감사 기록을 함께 둘 자리가 필요)
  2. 엔드포인트별 세션·자격증명 (★ 핵심 — §2.1) — 본인 계정 사용 확정. ⑵⑶ 결정됨(2026-08-21, 구현 완료): ⑵ 1회 입력→암호화 파일 볼트 저장(opt-in — SYSADMIN_VAULT_KEY 32바이트 base64 설정 시; 미설정이면 인메모리). 저장소는 로컬 파일(콘솔이 DB 없는 순수 API 클라이언트가 됐으므로; EKS 는 SYSADMIN_VAULT_PATH 로 PV 경로). 키는 API 의 FIELD_ENCRYPTION_KEY 와 반드시 별개 — "암호화 키는 API 만 보유" 원칙 유지. 볼트 복원값이 env dev 시드보다 우선. ⑶ 리프레시 토큰은 저장하지 않음 — 자격증명이 있으면 재로그인이 자동이라 토큰 영속은 공격면만 늘림(세션 토큰은 항상 인메모리). ⑴도 결정·구현됨(2026-08-21): Platform Staff 탭에 엔드포인트 축 추가 — 대상 엔드포인트 셀렉터(기본 = 로그인 엔드포인트) + systemCreatePlatformStaff(계정 생성+역할 부여 한 번에, 중복 409, org-less 감사)로 새 엔드포인트에 벤더 스태프 계정을 최초 프로비저닝. env 부트스트랩(OPERATOR 전무 시 1명) → 콘솔 생성(2번째부터) 2단 구조 완성. §5-2 전체 종결. (SSO/단일 자격증명은 realm 독립이라 불가 — 엔드포인트별 세션이 원칙.)
  3. 전 org 진입 시 기본 스코프 → 결정됨(2026-08-20): OPERATOR(시스템관리자)는 전 org 전권(고객사 요청 루프 없음), SUPPORT는 발급받은 per-org grant로 스코프. grant 발급 권한은 벤더(OPERATOR) 유지, org 동의는 계약 약관. 남은 미세 결정: 더 세분화된 read-only 계층(예: VIEWER)이 필요한지 여부만 후속.
  4. 중앙 릴리스/감사 기록 — 엔드포인트별 릴리스 이력을 system-admin 중앙에 남길지(“어느 서버에 언제 요율 갔나”), 각 엔드포인트 분산에 맡길지. (모니터 문서 §5-6과 정합)
  5. 이관 중 이중 운영 — Phase 2 진행 동안 raw-pg 라우트와 API-경유 라우트가 공존한다. 엔드포인트 1개(현행 단일 배포)를 레지스트리의 기본 엔드포인트로 등록해 기존 동작을 깨지 않고 점진 전환.

6. 리스크 / 원칙 ​

  • 점진 이관: 축 3개를 한 번에 바꾸지 않는다. Phase 0(API 스코프)·Phase 1(fan-out)은 기존 raw-pg와 병존 가능 → 라우트별로 안전하게 교체.
  • 화면 계약 보존: systemProcedure 응답 shape을 기존 라우트 응답에 맞춰, Alpine UI 변경을 마지막으로 미룬다.
  • 멀티테넌시 예외의 명시성: 전 조직 조회는 의도된 예외 — 반드시 계정 속성(platformRole)으로 게이트하고 전량 감사. 토큰 위조로 뚫리지 않게 Phase 0에서 못 박는다.
  • 부분 실패 정상화: 일부 엔드포인트 다운/구버전은 오류가 아니라 상태다. fan-out·UI 모두 3분류(정상/실패/미지원)를 1급 개념으로.