Skip to content

저장 라우팅 단계 — 포털별 파일 보관은 FileStore가 소유한다 ​

작성: 2026-09-15 · 상태: 계약 확정 + 콘솔 표면 구현됨 — FileStore의 실제 구현과 라우트로 검증했고, 콘솔 "파일 보관" 데스크(PortalConsole server/fileStore.ts · client/src/components/FileStoreDesk.tsx · docs/FILE_STORE_DESK.md)가 이 계약을 프록시한다. 관련 문서: PLATFORM_VISION.md §6 · SERVICE_BOUNDARIES.md · SEARCH_SCOPE.md(§6 공유 스토리지 경로 규약) · ONTOLOGY_SCOPE.md(§6 정본·색인) · DECISION_LOG.md

1. 왜 지금 필요한가 ​

앞 단계는 무엇을 어디에 두는가를 정하지 않은 채 진행됐다.

표면현재 보관문제
문서 라이브러리(플랫폼 생성 문서)콘솔 앱의 단일 오브젝트 버킷(platform-docs/ 접두) · 미설정 시 로컬 파일시스템콘솔에 스토리지 환경변수가 없으면 로컬 디스크로 저장되고 컨테이너 재배포 시 사라진다
포털 업무 파일(첨부·미디어)FileStore(포털별 정책)어떤 백엔드·보존·허용 형식인지 콘솔에서 보이지 않는다
사내 정책·비정형 문서FileStore색인 연결 전

문서 라이브러리 보관 문제의 실측 근거와 복구 절차는 PortalConsole docs/DOCUMENT_LIBRARY_STORAGE.md에 있다. 이 문서는 그 상위 규칙 — 포털별 비정형 파일의 저장 라우팅 소유자와 콘솔 표면 — 을 고정한다.

2. 결정 ​

항목결정
소유FileStore — 백엔드(R2·S3·S3 호환·로컬 볼륨) 등록, 소비자(포털)별 저장 정책, 보존·암호화 요구·RAG 발행 허용, 객체 키 규약
운영 표면PortalConsole 관리자 메뉴 — 목록·등록·수정·진단만 제공하고 서버 간 호출로 프록시한다(브라우저는 FileStore에 직접 붙지 않는다)
자격증명 소유FileStore 서버 환경변수. 콘솔은 secret 이름(secretRef)만 표시하고 Access Key·Secret Key는 저장·표시하지 않는다
FileStore 콘솔계속 유지 — 계정·권한(admin:manage)·정리(cleanup)·감사처럼 권한 게이트가 걸린 작업은 FileStore 자신의 /admin 화면이 담당
신설 금지새 저장 서비스·새 버킷 체계를 만들지 않는다. 이미 구현된 모델을 그대로 쓴다

이 결정은 PLATFORM_VISION.md의 원칙 두 개를 그대로 따른다 — 서비스 분리 · 표면 통합(실행은 FileStore, 운영 표면은 콘솔 한 곳)과 플랫폼은 업무 파일의 내용을 판단하지 않는다(보관·검증·색인 발행만).

3. 검증 — FileStore에 이미 있는 것 (2026-09-15 실제 소스) ​

구성위치내용
저장 백엔드 모델src/domain.ts storageBackendSchemaid(소문자 슬러그 3~64) · provider(CLOUDFLARE_R2·AMAZON_S3·S3_COMPATIBLE·LOCAL_VOLUME) · label · secretRef · endpoint · bucket · localRoot · region · enabled
소비자(포털) 정책src/domain.ts consumerStoragePolicySchemaid(uuid) · consumerId(포털 ID) · backendId · pathPrefix · maxBytes · allowedContentTypes · retentionDays · encryptionRequired · ragPublishAllowed · readEnabled · writeEnabled · enabled
객체 키 규약canonicalObjectKey{pathPrefix}/{consumerId}/{assetId}/{fileName} — 포털·자산 단위로 분리되고 파일명은 정규화 후 120자로 절단
라우팅src/storage.ts createStorageprovider가 객체 저장소면 S3 호환 어댑터, LOCAL_VOLUME이면 로컬 볼륨 어댑터. 정책의 backendId가 백엔드를 고른다
secret 해석src/storage.tssecretRef는 환경변수 이름 접두다 — ${secretRef}_ACCESS_KEY_ID · ${secretRef}_SECRET_ACCESS_KEY를 FileStore 서버 환경에서 읽는다. DB에는 이름만 저장된다
정책 규칙createAssetRecord · canPublishToRag쓰기 가능 여부, 최대 크기, 허용 MIME, 보존 기한, RAG 발행 허용은 모두 정책이 판정한다
RAG 발행 계약RagPublishEventassetId · consumerId · sourceDocumentId · sourceVersion · contentType · classification · organizationIds · storageLocator · checksum · sizeBytes
로컬 볼륨 위치자LocalVolumeStorage.getReadUrllocal://{key} 형태의 내부 불투명 locator다(브라우저 접근용 URL이 아니다 — 서버가 권한 확인 후 스트리밍)

결론: 포털별 저장소 라우팅은 FileStore에 이미 구현돼 있다 — 정책(consumerStoragePolicy)이 consumerId ↔ backendId 매핑이므로 포털마다 다른 백엔드(예: 업무 파일은 R2, 프로필 사진은 로컬 볼륨)를 쓸 수 있고, 업로드는 요청의 mappingId로 정책을 찾아 그 정책의 backendId로 스토리지를 만든다(AssetService.resolveMapping). 없는 것은 콘솔 쪽 운영 표면·백엔드별 자격증명 입력과 RAG 쪽 연결이다(자격증명은 2026-10-11 화면 입력 → 봉투 암호화 저장으로 정리했다).

4. 관리 API 계약 (실제 경로 · 2026-09-15 확인) ​

모든 경로의 접두는 /api/file-store/v1이다.

기능메서드 · 경로인증비고
설정 스냅샷(백엔드 + 정책)GET /admin/configuration관리자providers 목록·secret 정책 문구, 백엔드별 credentialsConfigured(화면 등록)·secretRefConfigured(실제 해석 가능)·missingCredentialNames(비어 있는 환경변수 이름)·connectionTest(마지막 연결 테스트 상태·시각·stale) 포함
백엔드 검증POST /admin/backends/validate관리자스키마·자격증명 해석 확인(본문 credentials가 있으면 그 값, 없으면 저장된 값 → secretRef). LOCAL_VOLUME은 root 읽기·쓰기 권한까지 확인
백엔드 연결 실측POST /admin/backends/test-connection관리자Cloudflare R2 전용 — HeadBucket으로 bucket 권한 확인(자격증명 해석은 validate와 같다)
백엔드 등록(신규)POST /admin/backends관리자ID 는 서버가 채번(이름 로마자 slug → provider → 중복 시 -2) — 화면은 ID 를 입력받지 않는다
백엔드 등록·수정PUT /admin/backends/:backendId관리자id는 본문이 아니라 경로가 정본(수정 전용). 본문 credentials가 있으면 백엔드별 자격증명을 봉투 암호화 저장(값은 응답에 없음)
백엔드 자격증명 등록·회전PUT /admin/backends/:backendId/credentials관리자write-only — 응답은 credentialsConfigured만
백엔드 자격증명 삭제·상태DELETE · GET /admin/backends/:backendId/credentials관리자값은 내려보내지 않고 등록 여부만
백엔드 삭제DELETE /admin/backends/:backendId관리자204 — 저장된 자격증명도 함께 지운다
정책 등록·수정PUT /admin/consumer-policies/:policyId관리자policyId는 uuid, 본문의 consumerId(또는 portalId)가 포털
정책 삭제DELETE /admin/consumer-policies/:policyId관리자204
정책 조회(서버 간)GET /consumer-policies서비스 토큰포털 어댑터가 자기 정책을 읽는 경로
포털 R2 자격증명 전달POST /admin/directory/r2-credentials관리자콘솔이 소유한 포털별 R2 bundle 등록·배포와 연결
자산 목록·상세·감사GET /admin/assets · /admin/assets/:assetId · /admin/audit-logs관리자portalId(정책의 consumerId)로 필터
요약(개수·용량)GET /admin/summary관리자사용량 뷰의 원천
세션 확인·로그인GET /admin/session · GET /auth/local/status · POST /auth/local/login—로그인 응답은 Set-Cookie만 반환하고 본문에 토큰을 주지 않는다

인증 3경로 — 콘솔이 쓸 수 있는 것 ​

authenticateAdmin은 다음 순서로 통과시킨다.

  1. 로컬 관리자 세션 — 쿠키 또는 Authorization: Bearer <세션 토큰>
  2. DirectoryService 세션 — 중앙 로그인 공유 세션
  3. 레거시 관리자 토큰 — x-filestore-admin-token: ${FILE_STORE_ADMIN_TOKEN}

주의: requireAdminPermission이 걸린 라우트(GET·POST /admin/accounts, /admin/cleanup)는 로컬 관리자 세션과 역할 권한(admin:manage·storage:write …)을 요구한다. 즉 2·3번 경로로는 호출할 수 없다 → 이 라우트들은 콘솔이 아니라 FileStore /admin 화면이 담당한다(§2).

반대로 자산 API와 GET /consumer-policies는 authenticateService — Authorization: Bearer ${FILE_STORE_SERVICE_TOKEN}이다.

콘솔에 필요한 환경변수(서버 전용, 브라우저 비노출):

이름용도
FILE_STORE_BASE_URL서버 간 호출 주소
FILE_STORE_ADMIN_TOKEN백엔드·정책 등록·수정·진단
FILE_STORE_SERVICE_TOKEN자산·정책 조회

5. 콘솔 표면 설계 — "파일 보관(FileStore)" 데스크 ​

탭내용호출
저장 백엔드백엔드 목록(provider·bucket·endpoint·자격증명 설정됨/미설정·활성 여부), 등록(ID 는 입력받지 않는다 — FileStore 가 이름에서 채번)·수정·비활성, 자격증명 입력·회전·삭제, R2 연결 실측, 비활성화 = enabled:falseGET /admin/configuration · POST /admin/backends · POST /admin/backends/validate · POST /admin/backends/test-connection · PUT·DELETE /admin/backends/:id · PUT·DELETE /admin/backends/:id/credentials
포털별 정책포털 × 백엔드 라우팅, pathPrefix, 허용 형식, 최대 크기, 보존 기한, ragPublishAllowed, read/write 허용PUT·DELETE /admin/consumer-policies/:id
진단콘솔이 이미 저장하는 포털별 FileStore 연결(fileStoreConsumerId·fileStoreMappingId·fileStoreBaseUrl)과 FileStore 실제 정책의 불일치 표시 — 매핑 ID가 없거나 정책이 비활성인 포털을 찾아 준다콘솔 포털 설정 + FileStore GET /consumer-policies

구현 위치(2026-09-15): PortalConsole server/fileStore.ts(접속·호출·진단 대조) · server/routers.ts fileStore(프록시) · client/src/components/FileStoreDesk.tsx(탭 3개) · 테스트 server/fileStore.test.ts · 운영 문서 docs/FILE_STORE_DESK.md.

표시 규칙: 자격증명 값(Access Key·Secret Key, OAuth Client ID·Secret)은 어떤 경우에도 콘솔 DB·브라우저·응답·로그에 남기지 않는다 — 화면은 설정됨/미설정(필요하면 secretRef 이름과 비어 있는 환경변수 이름)만 보여 준다. 값은 폼 입력 → 콘솔 서버 통과 → FileStore가 백엔드별 봉투 암호화 저장(FILE_STORE_ENVELOPE_MASTER_KEY)이고, 회전은 재입력, 정리는 자격증명 삭제다(ModelGateway 키 화면·문서 라이브러리 보관 진단과 같은 규칙을 값까지 확장).

위험 작업 안내: (a) LOCAL_VOLUME은 백엔드 컨테이너에 볼륨 마운트가 없으면 재배포 시 파일이 사라진다(문서 라이브러리에서 실제로 발생) → 화면에 경고를 고정한다. (b) 백엔드 삭제 전에 그 backendId를 쓰는 정책이 있는지 확인해 함께 보여 준다. (c) ragPublishAllowed를 켜면 그 포털 자산이 색인 대상이 된다 — 색인 연결(§6) 전에는 켜지 않는다.

6. 남은 것 ​

  1. 공유 스토리지 경로 규약(FileStore → RagService) — SEARCH_SCOPE.md §6이 남긴 항목. RagService 색인 입력은 파일 경로 기반(BatchDocumentJob{path, metadata})이고 FileStore의 로컬 위치자는 local:// 불투명 locator다. 후보: (a) 공유 볼륨 마운트 (b) FileStore가 읽기 URL을 발급하고 RagService가 회수 (c) FileStore가 push. 미결정 — 결정 전까지 ragPublishAllowed는 끈 상태로 둔다.
  2. RagPublisher 실연결 — FileStore의 발행 포트는 현재 로그 스텁이다(asset.rag.publish 이벤트를 서버 로그로만 남긴다). 실제 전달 매체를 위 1번과 함께 정한다.
  3. 색인 재발행 시점 — 승인 즉시 vs 배치. ONTOLOGY_SCOPE.md §7과 같은 질문이다.
  4. 문서 라이브러리 보관 이관 여부 — 플랫폼 생성 문서를 콘솔 자체 버킷(platform-docs/)에 둘지, FileStore 소비자(platform-docs consumer)로 옮길지. 옮기면 문서도 같은 정책·감사·RAG 경로를 탄다.
  5. 콘솔 자체 스토리지 진단의 일반화 — 문서 라이브러리 진단(PortalConsole docs/DOCUMENT_LIBRARY_STORAGE.md)을 백엔드 진단으로 확장.

7. 하지 않는 것 ​

  • 콘솔이 파일 본문·저장 자격증명·버킷을 소유하지 않는다 ❌
  • 새 저장 서비스·새 버킷 체계를 만들지 않는다 ❌ (이미 구현된 FileStore 모델을 쓴다)
  • 파일 내용의 업무적 판단·업무 로직 실행 ❌ (보관·검증·색인 발행까지만 — 비전 §2)
  • FileStore의 권한 게이트 작업(계정·권한·cleanup)을 콘솔로 복제 ❌