download floorplan image failed
Fix Plan: download floorplan image failed (장기 개선 / 재발 방지)
RCA ### 장기 개선 (재발 방지) 섹션의 두 축을 반영한다.
- Agent 공통 sanitize 유틸(
safeFileName) 을@agents/utils에 신설하고, 외부 값(서버 응답file_extension,meta.name) 을 로컬 파일 경로 조립에 사용하는 refinement/preprocessor 두 hotspot 에 적용해 슬래시/공백/제어문자로 인한ENOENT재발을 차단. - Filesystem 관련 exception 로깅 컨벤션을 원본 Error 를 그대로 넘기는 방식으로 통일(팀 표준 = fix logger 가 Error 를 native 로 처리) — refinement-agent 의
downloadImagecatch 블록에 적용해code/path/stack이 유지되도록 한다.
Changes#
cupixworks: applications/agents/packages/utils/src/cputils.ts#
- What:
CPUtils클래스에safeFileName(input: string | undefined): stringstatic 메서드 추가. 규칙:undefined/빈 문자열 → 빈 문자열 반환 (caller 가 확장자 없는 fallback 을 처리)path.basename(input)로 상위 경로 제거 → 슬래시/역슬래시/제어문자/공백을_로 치환 (/[\\/\x00-\x1f\s]/g)..로 시작하는 경우 leading.을_로 치환- 최대 길이 제한(예: 64자) 로 truncate
- Why: RCA 근본 원인 —
file_extension이rvt-site/lvl 1처럼 슬래시를 포함할 때path.join이 하위 디렉터리로 해석해ENOENT. Agent 어디서든 재사용 가능한 단일 진입점을 제공해 hotspot 재발을 원천 차단. - Lines: cputils.ts 하단에 static 메서드 1개 추가 (~15 lines)
cupixworks: applications/agents/packages/utils/src/cputils.spec.ts#
- What:
safeFileName에 대한 vitest describe block 추가. 커버 케이스:- 정상 확장자(
png,jpg) 는 그대로 통과 - 슬래시 포함(
rvt-site/lvl 1) →_치환 (rvt-site_lvl_1) - 역슬래시, 제어문자, 여러 공백
undefined/빈 문자열 → 빈 문자열- path traversal 시도(
../etc/passwd) → 상위 경로 제거
- 정상 확장자(
- Why: 재발 방지 유틸이므로 회귀 테스트 필수.
- Lines: spec 파일 끝에 describe 1개 추가 (~30 lines)
cupixworks: applications/agents/packages/cupix-capture-refinement-agent/src/model/cpfloorplan.ts#
- What:
setDefaultLocalPaths에서originalImageFileExt를 파일명에 붙이기 전에CPUtils.safeFileName(originalImageFileExt)로 정규화. 결과가 원본과 다르면 warn 로그로 기록해 서버 데이터 이상을 관측 가능하게 함.downloadImagecatch 절에서JSON.stringify(error)대신 원본 error 를 splat 인자로 logger 에 그대로 넘기고, 실패 컨텍스트(localImageFilePath, floorplan id) 도 함께 포함. 팀 표준(fix loggerError native 처리) 을 따름.
- Why: RCA 확정 원인 지점. 두 개선 축이 만나는 파일이며, sanitize + observability 를 동시에 반영해 재발 시 원인 파악이 즉시 가능하도록 함.
- Lines: cpfloorplan.ts:35-55 (sanitize 호출 + warn), 57-75 (catch 로깅)
cupixworks: applications/agents/packages/cupix-capture-preprocessor-agent/src/model/cpfloorplan.ts#
- What:
getBimFloorplanFilePath에서this._extension을 파일명에 concat 하기 전에CPUtils.safeFileName으로 정규화하고, 결과가 원본과 다르면 warn 로그를 남긴다. 정규화 후 값이 빈 문자열이면 기존 동작(확장자 없이 파일명) 을 유지. - Why: refinement-agent 와 동일한 hotspot 패턴이 preprocessor-agent 에도 존재. 감사 결과 확인된 두 번째 지점 — 같은 sanitize 유틸을 적용해 일관성 확보.
- Lines: cpfloorplan.ts:59-69
Acceptance Criteria#
-
CPUtils.safeFileName이@agents/utils에서 export 되고 vitest 단위 테스트가 통과한다. -
safeFileName('rvt-site/lvl 1')결과에 슬래시/공백이 남아있지 않다. -
grep -n "path.join.*file_extension" applications/agents/packages/**/model/cpfloorplan.ts결과에서safeFileName을 거치지 않는 concatenation 이 0건. - refinement-agent
downloadImagecatch 절에서JSON.stringify(error)가 제거되고 error 객체가 logger 에 직접 전달된다. - 두 hotspot 파일의 sanitize 우회 결과가 warn 로그로 관측 가능하다 (
sanitized/normalized등의 문자열 grep 가능). - TypeScript 컴파일(
nx run) 및 eslint(변경 파일만) 통과.
Tests#
- 기존 테스트:
applications/agents/packages/utils/src/cputils.spec.ts기존 케이스 유지. - 신규 테스트: 같은 spec 파일에
safeFileNamedescribe block 추가. - Model 파일들(
cpfloorplan.ts) 은 별도 spec 이 없음 — 기존 관행에 따라 lint/tsc 통과만 검증.