{ "schema": "vignette.avatar-image-resilience-recovery.v1", "evidence_id": "AVATAR-IMAGE-RESILIENCE-RECOVERY-2026-08-29", "captured_at": "2026-08-29T18:00:42+09:00", "privacy": { "raw_user_uuid_recorded": false, "raw_filename_recorded": false, "email_recorded": false, "resource_identity": "SHA-256 식별자만 기록" }, "incident": { "root_cause": { "runtime_mode": "Python uvicorn 직접 실행 프로세스", "process_working_directory": "/apps/api", "user_upload_dir_environment": "미설정", "effective_user_upload_dir": "cwd 기준 상대경로 uploads", "effective_storage_location": "/apps/api/uploads", "docker_named_upload_volume_used_by_public_api": false, "failure_mechanism": "DB 경로는 승격 뒤에도 남았지만 업로드 파일은 이전 detached release root에 머물렀다. 새로 승격된 Python 직접 실행 런타임이 같은 상대 업로드 경로를 서로 다른 작업 디렉터리에서 해석해 파일을 찾지 못했다." }, "initial_public_failure": { "phase": "복구 전", "http_status": 404, "response_content_type": "application/json", "browser_img_complete": true, "browser_img_natural_width": 0, "browser_img_natural_height": 0 }, "problem_object": { "path_sha256": "484dcee08b007741872ac48b49421fd45bf5a60b273e7a64c5b9a66dac85138a", "bytes": 28208, "content_sha256": "c5d06061bfa635d65f8285df242c5c9820dadcffcd785ed3f9b8619be24212c7" } }, "recovery": { "source_release_commit_prefix": "bf5f7352cee0", "current_release_commit_prefix": "dba9b75a3887", "objects": [ { "object_key": "recovered_object_1", "bytes": 18395, "content_sha256": "854403aed367db2d459e482a5e980fd22a846738b6034068cd7b4e80bbb7f34d" }, { "object_key": "problem_object", "bytes": 28208, "content_sha256": "c5d06061bfa635d65f8285df242c5c9820dadcffcd785ed3f9b8619be24212c7" } ], "object_count": 2, "total_bytes": 46603, "source_and_current_content_hashes_match": true, "current_release_storage_path_kind": "release root 내부의 일반 디렉터리", "current_release_git_dirty_paths": 0, "final_public_probe": { "http_status": 200, "content_type": "image/jpeg", "bytes": 28208, "content_sha256": "c5d06061bfa635d65f8285df242c5c9820dadcffcd785ed3f9b8619be24212c7", "matches_recovered_problem_object": true }, "authenticated_browser_dom": { "route": "/admin/users", "image_complete": true, "natural_width": 512, "natural_height": 512, "computed_display": "block", "visible": true, "raw_source_recorded": false } }, "ui_resilience": { "files": [ "apps/web/src/components/avatar/ResilientImage.tsx", "apps/web/src/components/shell/Topbar.tsx", "apps/web/src/pages/Settings.tsx", "apps/web/src/pages/Onboarding.tsx", "apps/web/src/lib/auth.tsx", "apps/web/e2e/image-resilience.spec.ts", "apps/web/e2e/uc-settings-consent.spec.ts" ], "covered_states": [ "유효한 외부 HTTPS 이미지", "API 상대경로 이미지", "빈 소스", "허용하지 않는 비웹 scheme", "HTTP 404", "디코딩 또는 로드 실패", "지연 응답 중 0x0 비노출 로딩 상태", "업로드 뒤 소스 교체", "접근 가능한 이니셜 fallback" ], "verification": { "typecheck": { "command": "npm run typecheck", "result": "passed" }, "production_build": { "command": "npm run build", "result": "passed", "vite_modules_transformed": 148, "deterministic_runs": 2, "dist_file_count": 601, "dist_manifest_sha256": "35c48fbf844efd0514282b76937bbef3b1f173992313c7d377363d71f0806d89" }, "design_ssot": { "command": "npm run check:design-ssot", "result": "passed" }, "focused_e2e": { "command": "npx playwright test e2e/image-resilience.spec.ts e2e/uc-settings-consent.spec.ts --project=chromium-desktop --project=chromium-mobile", "result": "40/40 passed" }, "layout_and_tabs_e2e": "17/17 passed", "session_layout_e2e": "8/8 passed", "scoped_diff_check": "passed" }, "release_candidate": { "base_commit": "5bf89ff4ee971940882695d27f153e1460df295e", "commit": "35a62fda90d3ba56707acfeb3fb94090d3949f8e", "tree": "d8634141289b6a0007954671ece5fb7dba9c2881", "author": "Yun Chan", "clean": true, "protected_api_or_generated_paths_changed": 0, "remote_push_and_pages_deploy": "pending_explicit_external_write_approval" } }, "stable_upload_contract": { "status": "pending", "current_recovery_scope": "현재 release root에 대한 시점 복구", "remaining_risk": "USER_UPLOAD_DIR가 cwd 상대경로인 동안 이후 승격이나 다른 release root 재시작에서 업로드가 다시 고립될 수 있다.", "required_follow_up": "사용자 업로드를 release root 밖의 절대 영속 상태 디렉터리로 옮기고, 모든 부팅·watchdog 진입점에서 USER_UPLOAD_DIR를 주입·검증한 뒤 inventory, 콘텐츠 해시 검증, 중복 제거, rollback, 공개 재시작 증거를 포함해 마이그레이션한다." } }