Skip to content

인입 세 경로(Wodup·InBody·Motra)의 실패를 "어느 단계·어느 줄·왜·원문은 남는가"로, 같은 파일 재시도와 신규 재인입을 구분, 큰 파일 비용을 숫자로 — v0.18.0 I01 (2026-09-10)

  • 기간: 2026-09-10 (세션 2개 — 첫 세션 5f069000… 이 분석·Phase 1~3 과 Phase 4 적용 도중 사용량 한도로 중단, 세션 20466fdd… 이 이어받아 Phase 4~6. 오너 지시 "물어보지 말고 Phase 끝까지 완주"). 계획 ID I01 / Phase 3 Step 2([리팩터링 3-2]).
  • 랜딩: PR #1498 (Phase 1~5 한 PR, base release/v0.18.0, 릴리스 큐 merge:request) — release/v0.18.0 병합 55e9fae8(2026-09-10 02:44 UTC · 11:44 KST, 큐 run 34430574867). 마이그레이션 없음. 엣지 함수 _shared/wodup-normalizer.ts·_shared/wodup-import-worker.ts·_shared/wodup-import-errors.ts 변경(엣지 배포는 승격 뒤 각 환경 Deploy 가 한다). release 통합 / staging 확인 / Production 배포는 서로 다른 상태: release 통합까지, staging·Production 은 미반영(§5). 적용 릴리스 v0.18.0 예정.
  • 설계서: 없음 — 분석·Phase 계획·"예상 효과·개선사항" 표는 이슈 #1415 댓글(2026-09-10).
  • 정본: 인입 공통 계약 src/react/features/import/importContract.ts(ImportIssue·ImportPreflightError·경로 등록표 IMPORT_PATHS·한도 IMPORT_LIMITS·A11 화면 상태 ImportJobView·worker 실패 코드 표 WODUP_WORKER_ERROR_CODES), 화면 상태 변환 features/import/wodupImportView.ts·motraImportView.ts, 파서 services/wodupJsonlUpload.ts·services/inbodyImport.ts, 코덱 domains/codecs/motraImportCodec.ts, 서버 supabase/functions/_shared/wodup-import-errors.ts(worker 실패 코드·정규화 실패/경고 코드 정본)·wodup-normalizer.ts. 문서: 이 기록, 인입 파이프라인 §경로별 책임, 자원 예산 §3-5, 성능 기준선 §6.
  • 도구: scripts/performance/import/wodup-large-file.mjs(npm run perf:import -- --sessions N) — seed 로 결정적 Wodup 파일을 만들어 브라우저 사전검사·SHA-256·서버 정규화의 시간·이벤트 루프 최대 정지·힙 최고점을 잰다. 결과 JSON 은 scripts/performance/evidence/(gitignore, 규격은 evidence/schema.json).
  • 게이트: tests/react/importContract.test.mjs(경로 등록표의 소유 모듈 실존·worker 코드 표 일치·worker 가 실제로 던지는 코드 전수), motraImportContract(Motra typed 오류·죽은 파서 사본 부재), wodupNormalizerIssues(정규화 typed 오류·경고·worker 동봉), importReplayFixtures(fixture 결정성·manifest 대조), resourceContract(한도 근거·실측값), 기존 wodupJsonlUpload·wodupImportRunner·inbodyImport·importRepositoryDestinations·readImportContractHardCut 재조준. npm run check 전체(§5). ci:precheck-local: ci:precheck-local precheck · static 통과 · unit-1 통과 1695 passed / 0 failed / 29 conditional skip · unit-2 통과 1837 passed / 0 failed / 9 conditional skip · database 통과 db reset(마이그레이션 전체 적용): pass · schema.sql 스냅샷 --check: pass · pgTAP: pass 136파일/2625 assert · 동시 저장 세대·영수증: pass · dirty 범위·세대 병합: pass · 큰 이력 계산 격리·동시 CRUD·통계 수렴: pass · 7분 29초 (2026-09-10 02:41 UTC · 11:41 KST, head 7ea8dad7).
  • 버그리포트: 없음(수리 건 아님).
  • 계약: ImportPort 결과에 WodupImportRunOutcome(job / already_imported / cancelled / detached)·errorCode·statsPending, InBody 저장소 결과 {kind: imported | already_imported, workspace, rowCount}(contracts/ports/importDto.ts·writes.ts). worker error_code 표는 _shared/wodup-import-errors.ts 가 정본이고 클라이언트 표와 테스트로 일치.

Phase 현황

Phase내용상태
Phase 1인입 공통 계약(이슈·단계·원문 가용성·재시도 정책)·경로 등록표·A11 화면 상태·worker 실패 코드 표 + 일치 테스트d49b08dd
Phase 2Wodup 클라이언트: 줄 번호가 붙은 typed 사전검사, 500줄마다 양보 + 취소, 전체 파일 SHA-256 봉인, 같은 파일 재시도/신규 재인입 구분, D09 error_code → typed issuea7b35f3c
Phase 3InBody: 건너뛴 행·중복·시간 표기를 결과에 싣고, 이미 넣은 파일을 성공으로 위장하지 않음c0812df7
Phase 4Motra 코덱 typed 오류·통계 대기, 앱의 죽은 Motra 파서 사본 제거4eac83a5
Phase 5서버 normalizer typed 오류·경고 코드, 큰 파일 계측, replay fixture7ea8dad7
Phase 6문서(이 기록·인입 파이프라인·자원 예산·성능 기준선·총괄) → Precheck → PR → Merge Check✅ release/v0.18.0 병합 55e9fae8

1. 배경

유저 A 가 내 정보 화면에서 Wodup 파일을 올린다. 파일 2번째 줄이 JSON 이 아니면 브라우저 사전검사는 "2번째 줄을 JSON 으로 못 읽음"까지 알아내지만, 화면은 그 문구를 버리고 "파일을 가져오지 못했어요"만 보여줬다. 같은 파일을 한 번 더 올리면 새 배치가 만들어져 서버가 끝까지 처리한 뒤에야 "이미 가져온 기록"으로 실패했다. 유저 B 가 InBody CSV 를 올리면 날짜·체중을 못 읽은 행은 조용히 빠졌고, 같은 파일을 두 번 올려도 "N개 가져왔어요"라고 성공처럼 말했다. 관리자가 Motra 기록을 대리 인입할 때 서버가 "이미 넣은 세션"(22023)·"권한 없음"(42501)으로 거부하면 앱 쪽 코덱은 그 오류를 구분하지 못했다.

A05(#1406)가 인입 서버 호출을 자기 저장소·코덱으로 옮기고 D09(#1404)가 Wodup worker 를 임대·재시도·원자 완료가 있는 durable 소비자로 바꾼 뒤, 남은 것은 파서·정규화·identity·부분 실패의 표현이었다. 이 트랙은 그 표현을 세 경로가 공유하는 계약 한 벌로 모으고, 큰 파일 비용을 G04 기준선의 "인입 미측정" 자리에 숫자로 채운다.

2. 문제 제기

세 경로가 실패를 각자 throw new Error("문구")·return null 로 말했다

"어느 단계에서, 어느 줄/행이, 왜, 원문은 남는가"를 담는 공통 모양이 없었다. 화면은 문구를 버리고 일반 안내로 바꿨고(Wodup 사전검사), 건너뛴 행·이미 넣은 파일 같은 부분 결과는 성공으로 흡수됐다(InBody). Motra 코덱은 서버 오류 코드를 통째로 넘겼다.

재시도와 신규 재인입의 구분이 서버 게이트에만 있었다

브라우저는 파일 identity(해시)를 만들지 않아 같은 파일을 다시 올리면 새 배치를 만들었다. D09 worker 가 나중에 해시를 봉인하므로 "같은 파일이 진행 중인가·이미 끝났는가"를 올리기 전에 알 수 없었다. 취소·업로드 중단 상태도 없어 폴링을 멈추면 서버는 계속 도는데 화면은 그냥 끝났다.

서버 정규화가 값을 조용히 추정했다

ISO 가 아닌 날짜("June 20, 2026")는 new Date() 로 해석해 시간대 가정이 들어갔고, 숫자가 아닌 세트 값("60kg")은 소리 없이 null 이 됐다. 정규화 실패는 Error("Line 2: invalid JSON …") 문자열뿐이라 worker 의 normalization_failed 뒤에 코드·줄 번호가 남지 않았다.

큰 파일 비용이 숫자로 없었다

G04 기준선은 인입을 "근사만(저장 문 연속 호출) — I01 이 잰다"로 남겼고, 자원 계약의 parseMemoryBytesunmeasured 였다. 사전검사가 표본 1만 줄을 동기 루프로 파싱해 UI 를 얼마나 막는지도 몰랐다.

앱 저장소에 호출자 0인 Motra 파서 사본이 있었다

#1463(v0.17.7)이 관리자 화면을 Barbelic-docs/admin 으로 옮긴 뒤 motraNormalizedUpload.ts·motraXlsxBlocks.ts 는 테스트와 자원 계약만 참조하는 죽은 사본이었다.

3. 해결 방안

원칙

오너 결정 항목 없음(§26). 이슈 계획의 전제대로 진행: 판정 권위는 서버 게이트(one-shot·해시 대조)에 두고 브라우저는 같은 판정을 미리 안내만 한다. D09 worker runtime(임대·재시도·완료 판단)은 손대지 않고 normalizer 의 결과 모양만 바꾼다. DB 변경 없음. CASE-055(e2e)가 실패 토스트 문구·관측 수를 정확히 대조하므로 서버 실패 문구는 종전 그대로 두고 코드별 문구는 A11 화면 상태(ImportJobView.issue)에만 싣는다.

접근

내용판정
A. 공통 typed 계약 + 경로별 적용features/import/importContract.ts 한 벌(단계·이슈 코드·위치·원문 가용성·재시도 정책·A11 화면 상태·무효화 대상)을 두고 세 경로의 파서·러너·코덱이 그 모양으로 결과를 낸다. 파일 해시로 재시도(같은 배치에 붙기)와 신규 재인입(거부)을 브라우저에서 구분채택 — 다음에 경로가 늘어도 같은 계약을 채우면 된다
B. 경로별 문구·토스트만 손질각 catch 에서 문구를 골라 보여준다기각 — 부분 결과·재시도 구분·취소는 해결 안 됨(땜질)
C. 서버에 사전검사 RPC 추가브라우저 검사를 서버로 옮긴다기각 — 검사 대상이 브라우저의 파일이라 마이그레이션·엣지 배포 비용 대비 이득 없음

4. 적용한 내용

Phase 1 — 인입 공통 계약 (d49b08dd)

  • src/react/features/import/importContract.ts: ImportIssue(source·stage·code·message·location(줄/행/필드)·rawAvailable·retryable), ImportPreflightError, 사전검사 코드 표 IMPORT_PREFLIGHT_CODES, 경로 등록표 IMPORT_PATHS(세 경로 × 단계 file→parse→validate→identity→upload→canonical→stats 의 소유 모듈·원문 가용성·재시도/재인입 정책·S10 복구 지원·통계 시점), 한도 IMPORT_LIMITS, A11 화면 상태 ImportJobView(phase 9종·progress·outcome·issue·serverContinues·statsPending·invalidates).
  • supabase/functions/_shared/wodup-import-errors.ts: worker 가 배치 행 error_code 에 남기는 코드 18종 표(retryable·stage·httpStatus). 클라이언트 표 WODUP_WORKER_ERROR_CODES 와 키·retryable·stage 일치를 테스트가 대조하고, worker 소스에서 실제로 던지는 코드를 전수 추출해 표에 있는지 확인한다.

Phase 2 — Wodup 클라이언트 (a7b35f3c)

  • wodupJsonlUpload.ts: 사전검사 실패가 코드·단계·줄 번호를 실은 ImportPreflightError 로(문구는 종전과 같음). 500줄(IMPORT_LIMITS.wodup.yieldEveryLines)마다 이벤트 루프에 양보하고 AbortSignal 로 취소(file_read_aborted). 64MB 이하 파일은 브라우저가 전체 SHA-256 을 계산해 배치 행에 봉인(fileHashScope: full / skipped_size / unavailable) — D09 worker 의 해시 대조가 실제로 동작한다.
  • importRepository.findWodupImportBatchByFileHash port 추가. 러너(wodupImportRunner)는 같은 해시의 배치를 찾아 완료됨 → already_imported 거부(서버까지 안 감)·진행 중 → 그 배치에 붙기·실패 → 새 배치. 결과는 WodupImportRunOutcome(job / already_imported / cancelled(serverContinues) / detached).
  • DTO 에 D09 error_codeerrorCode; features/import/wodupImportView.ts 가 잡 DTO 를 ImportJobView 로 그린다(describeWodupWorkerError 로 코드별 문구). appController 는 typed 문구를 그대로 보여준다.

Phase 3 — InBody (c0812df7)

  • inbodyImport.ts: inspectInbodyRows 가 건너뛴 행(행 번호·이유 date_unreadable/weight_unreadable)·중복 수·시간 표기 집계(텍스트/엑셀 일련번호/Date 셀)를 결과에 싣는다. 헤더 없음·범위 밖 값은 행·필드가 붙은 typed 오류. 시간 표기가 섞이면 validate_time_zone_mixed 경고(값은 바꾸지 않는다 — source_refmeasuredAt 을 포함해 identity 가 흔들리므로 경고로만).
  • profileRepository.importInbodyBodyMetrics 결과 {kind: "imported" | "already_imported", workspace, rowCount} — 같은 파일을 다시 올리면 "이미 가져온 파일이에요"(프로필 컨트롤러 문구 분기).

Phase 4 — Motra 코덱·죽은 파서 사본 제거 (4eac83a5)

  • motraImportCodec.motraImportIssue: RPC 오류를 typed issue 로 — 22023 import_already_applied(이미 넣은 세션, 제거 절차 뒤에만 재인입)·22023 판정표 slug 가 카탈로그에 없음(identity_decision_invalid)·22023 행 검사(validate_row_shape)·42501 not_authorized·23505 identity_session_ref_duplicate; 모르는 모양은 null(호출자가 원래 오류를 그대로 다룬다). 결과 DTO 에 statsPending(stats_mode !== "inline"). features/import/motraImportView.ts 가 결과·오류를 ImportJobView 로.
  • 앱의 motraNormalizedUpload.ts·motraXlsxBlocks.ts + 테스트 2개 제거(정본은 Barbelic-docs/admin). 자원 계약의 인입 한도 근거를 IMPORT_LIMITS 로, 아키텍처 장부(coverage-inventory)의 삭제 파일 참조 정리.

Phase 5 — 서버 normalizer·계측·fixture (7ea8dad7)

  • _shared/wodup-import-errors.ts: 정규화 실패 코드 5종(line_invalid_json·line_source_mismatch·session_exercise_limit·exercise_set_limit·session_set_limit)·경고 접두 11종(기존 8 + date_parsed_non_iso·date_unparseable·set_value_not_numericWodupNormalizeError(code·line·sessionId).
  • _shared/wodup-normalizer.ts: throw new Error 0 → 전부 WodupNormalizeError(세션 한도 오류는 normalizeWodupRow 가 줄 번호를 붙인다). 조용한 강제 변환을 경고로: ISO 아닌 날짜를 Date 로 해석하면 date_parsed_non_iso:<원문>, 못 읽으면 date_unparseable, 세트의 숫자 필드에 값이 있는데 숫자가 아니면 set_value_not_numeric:<필드>:<세트 id>(값은 null 그대로 — 0 이나 다른 단위로 추정하지 않는다). worker 는 normalization_failed/second_pass_normalization_failed 실패 상세에 normalize_code·line 을 동봉(문구·error_code 는 종전 그대로).
  • 계측 scripts/performance/import/wodup-large-file.mjs(§5 표), 자원 계약 clientImportparseMemoryBytes(measured)·preflightMaxSyncBlockMs·workerNormalizeHeapPeakBytes 추가.
  • replay fixture tests/fixtures/import/: wodup-raw.jsonl(정상 세션·엉성한 세션(비ISO 날짜·"60kg"·"five")·종목 행) → wodup-normalized.jsonl(정규화 결과, 바이트 단위 결정성 검사), inbody.csv(유효 3·건너뜀 2·중복 1), motra-normalized.json(pgTAP 과 같은 3세션 페이로드), manifest.json(경로별 identity·원문 가용성·복구 방식·기대 결과 — IMPORT_PATHS 와 대조).

주요 결정과 그 근거

  • 판정 권위는 서버, 브라우저는 미리 안내. 파일 해시 대조·one-shot 게이트는 서버가 하고, 브라우저는 같은 해시의 완료 배치를 찾으면 서버까지 가지 않고 already_imported 를 안내한다. 서버 게이트를 우회하는 경로는 없다.
  • 정규화 경고는 늘리되 값은 바꾸지 않는다. 조용한 추정을 없애는 대신 값을 고치지 않고(정본 불변, §23) 경고 코드로만 드러낸다. warning_count 가 커질 수 있다.
  • 서버 실패 문구는 그대로. CASE-055 가 문구·관측 수를 대조한다. 코드별 문구는 ImportJobView.issue 에만 있고 화면 반영은 A11 몫.
  • 정규화 코드 표는 서버 파일이 정본. 클라이언트에 같은 표를 복제하지 않았다(소비자가 아직 없다 — A11 이 errorMessage 의 "Line N:" 과 normalize_code 를 쓸 때 옮긴다).

작업 중 드러난 것

  • 첫 세션은 Phase 4 적용 도중 사용량 한도로 끊겼고 워크트리에 미커밋 변경이 남았다. 이어받은 세션이 검토 후 마무리했다(인수인계 댓글·이슈 스레드).
  • 이어받은 세션에서 백그라운드 npm run checkgit stash 를 같은 워크트리에서 겹쳐 돌려 git 인덱스가 비었다(파일은 온전). git reset 으로 복구하고 검사를 다시 돌렸다 — 같은 워크트리에서 검사 중에는 git 작업을 하지 않는다.
  • 아키텍처 장부 검사(check-coverage-inventory)는 기준 release 에서도 같은 항목(미분류 문서 5·크론 진입점 barbelic-stats-cron-history-purge)으로 실패하는 기존 상태다 — 이 트랙 범위 밖.
  • 계측 첫 실행에서 사전검사 최대 정지 123ms·정규화 632ms 가 나왔으나 다른 세션의 CPU 부하 중이었고, 3회 반복에서는 47ms·0.94초로 안정됐다. 같은 PC 의 반복 최댓값을 기록했다.
  • 사전검사의 최대 정지는 줄 수가 아니라 표본 문자열(최대 32MB)을 줄로 쪼개는 한 번에서 나온다 — 500줄 양보로는 그 한 번이 줄지 않는다. 표본 한도가 상한을 정하므로 그대로 두었다.
  • 정규화는 파일 전체를 한 문자열로 들고 산출도 한 문자열로 만든다. 1만 세션 파일의 힙 최고점 226MB 는 Edge 함수 메모리 한도 256MB 의 88% — 줄 단위 스트리밍은 D09 runtime(청크 staging)과 같이 설계해야 하므로 후속으로 남겼다(자원 계약 note).
  • 이슈 본문의 소유 경계에 적힌 src/react/controllers/motraImportController.ts 는 #1463 이관으로 앱에 없다. Motra typed 결과의 앱 쪽 소비자는 저장소 port 와 A11 뿐이다.

5. 적용 결과

항목전 → 후
Wodup 사전검사 실패 표현Error("문구")ImportPreflightError(코드 15종·단계·줄 번호), 화면 문구 일반 안내 1종 → 코드별 문구(줄 번호 포함)
같은 파일(완료됨) 재업로드 시 새 배치 생성1(서버가 끝까지 처리 뒤 실패) → 0(브라우저가 해시로 찾아 already_imported) · 진행 중이면 그 배치에 붙음 · 실패했으면 새 배치
브라우저가 계산하는 파일 해시없음(worker 가 나중에 봉인) → 64MB 이하 전체 SHA-256 을 배치 행에 봉인(초과는 skipped_size)
취소·업로드 중단상태 없음 → cancelled(serverContinues 로 서버 계속 여부)·detached(폴링 상한)
D09 error_code클라이언트가 읽지 않음 → DTO errorCodeImportJobView.issue(코드 18종 문구), worker 표와 일치 테스트
InBody 건너뛴 행·중복·이미 넣은 파일조용히 버림·"N개 가져왔어요" → 행 번호·이유·중복 수·already_imported
Motra 서버 오류코드 통째로 전달 → typed issue 5종 + statsPending
서버 정규화 실패Error 문자열 → WodupNormalizeError 5종(코드·줄 번호), worker 실패 상세에 동봉
서버 정규화의 조용한 추정무음 → 경고 3종(date_parsed_non_iso·date_unparseable·set_value_not_numeric), 값은 불변
앱 저장소의 죽은 Motra 파서 사본파일 2 + 테스트 2 → 0 (−791줄)
자원 계약 clientImport.parseMemoryBytesunmeasured → 54,945,496 bytes(measured) + preflightMaxSyncBlockMs 47 + workerNormalizeHeapPeakBytes 225,966,552
새 테스트importContractmotraImportContractwodupNormalizerIssuesimportReplayFixtures 4 + 기존 파일에 사전검사·러너·InBody 검사 10건
로컬 게이트npm run check 3,570 중 3,532 통과·0 실패·38 건너뜀(DB 조건) · check:unused 통과
화면 동작폴링 간격·상한·서버 실패 문구·CASE-055 관측 불변. 시각 변경 0
release 통합 / staging / Productionrelease/v0.18.0 병합 55e9fae8(2026-09-10 02:44 UTC · 11:44 KST, 큐 run 34430574867) · staging 미반영 · Production 미반영

큰 파일 실측(Node 24, 이 PC, --expose-gc, 3회 반복 최댓값 — 브라우저가 아니라 Node 이벤트 루프로 잰 값):

파일사전검사이벤트 루프 최대 정지사전검사 힙 최고전체 SHA-256서버 정규화(동기)정규화 힙 최고
2,000세션 · 2,008줄 · 4.9MB44~66ms12~21ms12.5MB4ms0.20~0.22초52MB
10,000세션 · 10,008줄 · 24.3MB(표본 1만 줄 한도)0.29~0.33초39~47ms55MB20ms0.92~0.94초226MB(Edge 256MB 의 88%)

G04 예산에는 UI 정지 기준이 없어 충족 여부 대신 실측값을 남긴다. Edge 함수 CPU 한도 2초 안(0.94초), 메모리 한도는 1만 세션 파일에서 88%.

미검증: 실제 브라우저의 메인 스레드 정지·메모리(e2e CASE-055 는 시간만 본다), staging·Production 에서의 엣지 함수 동작(승격 뒤 Deploy).

6. 이번 개선으로 향상된 것

잘못된 파일이 어디서 왜 막혔는지 유저가 안다

유저 A 의 Wodup 파일 2번째 줄이 깨져 있으면 "2번째 줄을 JSON 으로 읽지 못했어요"가 그대로 보인다. InBody 파일은 "3개 가져옴·2행 건너뜀(6행 측정일, 7행 체중)"처럼 부분 결과를 말한다.

같은 파일을 다시 올려도 서버가 헛돌지 않는다

완료된 파일은 올리기 전에 "이미 가져온 파일"로 안내하고, 진행 중이던 배치에는 다시 붙는다. 실패했던 파일만 새 배치를 만든다.

서버가 추정한 값이 숨지 않는다

정규화가 날짜·숫자를 추정하거나 버릴 때마다 경고 코드가 남아 R03 복구·운영이 원문과 대조할 수 있다. 값은 바꾸지 않는다.

큰 파일 비용이 숫자로 남았다

npm run perf:import 한 번으로 같은 파일을 다시 잴 수 있고, 자원 계약이 Edge 한도와 나란히 읽힌다.

구조적으로 남는 것

  • 인입 계약 1벌(features/import/importContract.ts)과 경로 등록표 — 소유 모듈이 실제 파일인지 테스트가 지킨다.
  • worker 실패 코드 표·정규화 실패/경고 코드 표(_shared/wodup-import-errors.ts) — 클라이언트 표 일치·worker 실제 코드 전수 테스트.
  • replay fixture 3종 + manifest — 파서가 바뀌면 fixture 결정성 테스트가 먼저 깨진다.
  • 큰 파일 계측 스크립트와 증거 규격.

남은 것

  • A11 인계: ImportJobView(phase·progress·outcome·issue·serverContinues·statsPending·invalidates)를 그대로 화면 상태로 쓴다. Wodup 은 wodupImportJobView(job)·러너 결과 종류, InBody 는 저장소 결과 kind, Motra 는 motraImportJobViewFrom/motraImportFailureViewFrom. 코드별 문구를 화면에 실을 때 CASE-055 의 문구 대조를 함께 갱신한다. 정규화 실패의 normalize_code·줄 번호는 지금 worker 실패 상세(로그)에만 있다 — 배치 행에 싣는 것은 D09 와 상의.
  • S10/R03 인계: tests/fixtures/import/manifest.json 이 경로별 원문 가용성·복구 방식·identity 의 정본이다(Wodup 만 import_replay, InBody 는 재업로드, Motra 는 변환기 재실행).
  • R04 인계: npm run perf:import -- --sessions N 으로 같은 파일을 재측정한다. 브라우저 실측은 e2e 에서 따로.
  • D09 후속: 정규화의 줄 단위 스트리밍(1만 세션 파일 힙 226MB → 세션 수와 무관하게). 청크 staging 과 같이 설계.
  • Barbelic-docs/admin: 관리자 쪽 Motra 파서 사본(admin/src/react/services/motraNormalizedUpload.ts)은 이 PR 범위 밖 — 같은 ImportIssue 계약을 적용하는 것은 후속.
  • staging 승격(full CI 1회)·QA·Production 은 릴리스 담당 절차(v0.18.0)로 진행하며 이 문서 §5 의 상태 표를 그때 갱신한다.