ES /docs

download floorplan image failed

Fix Plan: download floorplan image failed (장기 개선 / 재발 방지)

RCA ### 장기 개선 (재발 방지) 섹션의 두 축을 반영한다.

  1. Agent 공통 sanitize 유틸(safeFileName) 을 @agents/utils 에 신설하고, 외부 값(서버 응답 file_extension, meta.name) 을 로컬 파일 경로 조립에 사용하는 refinement/preprocessor 두 hotspot 에 적용해 슬래시/공백/제어문자로 인한 ENOENT 재발을 차단.
  2. Filesystem 관련 exception 로깅 컨벤션을 원본 Error 를 그대로 넘기는 방식으로 통일(팀 표준 = fix logger 가 Error 를 native 로 처리) — refinement-agent 의 downloadImage catch 블록에 적용해 code/path/stack 이 유지되도록 한다.

Changes#

cupixworks: applications/agents/packages/utils/src/cputils.ts#

  • What: CPUtils 클래스에 safeFileName(input: string | undefined): string static 메서드 추가. 규칙:
    • undefined/빈 문자열 → 빈 문자열 반환 (caller 가 확장자 없는 fallback 을 처리)
    • path.basename(input) 로 상위 경로 제거 → 슬래시/역슬래시/제어문자/공백을 _ 로 치환 (/[\\/\x00-\x1f\s]/g)
    • .. 로 시작하는 경우 leading ._ 로 치환
    • 최대 길이 제한(예: 64자) 로 truncate
  • Why: RCA 근본 원인 — file_extensionrvt-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:
    1. setDefaultLocalPaths 에서 originalImageFileExt 를 파일명에 붙이기 전에 CPUtils.safeFileName(originalImageFileExt) 로 정규화. 결과가 원본과 다르면 warn 로그로 기록해 서버 데이터 이상을 관측 가능하게 함.
    2. downloadImage catch 절에서 JSON.stringify(error) 대신 원본 error 를 splat 인자로 logger 에 그대로 넘기고, 실패 컨텍스트(localImageFilePath, floorplan id) 도 함께 포함. 팀 표준(fix logger Error 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 downloadImage catch 절에서 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 파일에 safeFileName describe block 추가.
  • Model 파일들(cpfloorplan.ts) 은 별도 spec 이 없음 — 기존 관행에 따라 lint/tsc 통과만 검증.