정식 승격 상태판과 증거를 동기화

This commit is contained in:
Yun Chan 2026-08-30 00:06:45 +09:00
parent 34aff65cb0
commit be08c0b573
16 changed files with 1160 additions and 63 deletions

View file

@ -0,0 +1,134 @@
{
"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": "<release-root>/apps/api",
"user_upload_dir_environment": "미설정",
"effective_user_upload_dir": "cwd 기준 상대경로 uploads",
"effective_storage_location": "<release-root>/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, 공개 재시작 증거를 포함해 마이그레이션한다."
}
}