21 KiB
Vignette 전체 개선 목표 · 정식 배포 핸드오프
작성 시각: 2026-08-29 21:18 KST
작업 루트: D:\workspace\vignette
Goal ID: 01a04217-f73b-7303-b597-401fa7f5d290
Goal: 엑셀 파일의 모든 내용을 마친다
0. 2026-08-30 재개 후 현행 상태
이 절이 아래 2026-08-29 종료 스냅샷보다 우선한다. 현재 이어받기 Goal ID는
01a04dd0-93ef-7d02-a9cb-40682fd0988a다.
preserved_total_size_bytes=52,973과 decode 3/90, 현재 DB 참조 decode 2/6을 initializer→manifest v3→ bootstrap/cutover/task-recovery/final receipt→API health까지 결속했다.- manifest 이후 생성된 UUID형 아바타도 URL 기록·현재 DB 참조·GET/HEAD에서 3MB 제한, Pillow full decode, 확장자-format 일치를 다시 검사한다. 검증한 동일 bytes를 응답해 검사 뒤 재오픈 경쟁을 없앴고, 사후 손상은 current invalid/health fallback과 public 404로 닫는다. 원본 bytes와 DB URL은 삭제하지 않았다.
- API 전체
1074 passed / 1 skipped, gateway68 passed, runtime/bootstrap 통합154 tests OK, web typecheck/build, 이미지+사람 게이트 route E2E가 통과했다. SSOT checker와 scoped diff check도 통과했다. - Codex 내장 브라우저의 격리 local stateful fixture에서 8.5초 뒤 overlay 0·heading 정상, content
keep_quarantineeffect 0, 증거 없는 release 승인 disabled→rejecteffect 0, 증거 4종 promote 승인 lifecycle effect 정확히 1을 확인했다. 이는 실DB/public proof를 대신하지 않는다. - clean 통합 브랜치는
YunChan/goal-production-20260830이며 UI 공통화 두 커밋 위에 runtime·usage·G8·auth· 관리자 UI·온보딩 계정 전환을 좁은 커밋으로 결합했다. push·Pages 배포·public runtime/task 변경은 아직 없다. - 풀스택
layout-visual-gate15/15와session-layout8/8은 Docker Desktop이 꺼져 있고 활성 public watchdog이 daemon 기동 즉시 기존 runtime 복구를 시도할 수 있어 승인 대기다. 승인 시 watchdog을 일시 중지·비활성화하고 고유 DB/container/volume과 56432/58000/55173만 사용한 뒤 exact cleanup, Docker 종료, watchdog 원상복구를 수행한다. - 최종 외부 게이트는 여전히 C001 적격 외부 임상 검수와 REQ-008 실제 Windows 재부팅 smoke다. 둘 다 추정 증거로 닫지 않는다.
1. 이전 세션 종료 결정과 당시 결론
사용자가 세션 장기화를 이유로 상세 핸드오프 후 익일 재개를 지시했다. 21:15 KST부터 모든 에이전트의 새 편집을 중단했고, 로컬 stage·commit·push·Cloudflare Pages 배포·라이브 DB 변경·API/tunnel 재시작·예약 작업 변경·PC 재부팅은 수행하지 않았다.
당시 소스는 배포 가능 GREEN이 아니었다. 특히 종료 직전 실제 이미지 디코딩 감사를 추가로 수행한 결과, 연결된 운영 DB가 참조하는 아바타 8개 중 6개가 깨진 동일 PNG payload라는 사실을 확인했다. 파일 존재와 해시만 보존하면 사용자가 신고한 깨진 이미지가 그대로 남았다. 이 결함은 위 2026-08-30 후보에서 public 404/fallback으로 닫았지만 아직 정식 배포 전이다.
Goal은 완료 처리하지 않았다. 기술 구현·정식 배포·실제 재부팅 증명과 별개로 C001 적격 외부 임상 검수도 여전히 인간 게이트다.
2. 완료된 엑셀 산출물
- 산출물:
D:\workspace\vignette\outputs\01a04217-f73b-7303-b597-401fa7f5d290\Vignette_개선관리_완료.xlsx - SHA256:
c832547f30ae0664e54b302c8e9f62cc157f31022ad158d17803d8cac988d8bd - 크기: 2,751,365 bytes
- 검증: 6 sheets, 11 requirements, 38 formulas, formula error 0, inspection files 18
- 상태 집계: 완료 9, 검토 2, 보류 0
- 남은 두 검토 항목:
- C001: 적격 외부 임상 검수 입력과 서명 증빙
- REQ-008: 실제 PC 재부팅 뒤 예약 작업 기반 자동복구와 공개 smoke
- C001 셀 상태:
G23=pending-valid,G24=PACKAGE_MATCH,B24=pending_external_review,J3=검토; 외부 검수 입력 46칸은 의도적으로 비워 두었다. - 임상 검수 내용을 추정하거나 가짜로 작성하면 안 된다.
3. UI 작업 상태
깨끗한 UI 후보 워크트리는 아래와 같다.
- 경로:
D:\workspace\vignette-ui-image-release-20260829 - 브랜치:
YunChan/ui-image-resilience-release-20260829 - HEAD:
a73b9efff77e3c575e32976bbe4f1ed404e103f0 - 관련 커밋:
35a62fda탭 구조와 이미지 복구를 공통화a73b9eff분석 탭과 축어록 계층을 정돈
- 워크트리 상태: clean
- 로컬 미리보기:
http://127.0.0.1:5188
반영된 브라우저 코멘트:
- 학습 대시보드의 불필요한 안쪽 컨테이너 스타일 정리
- 교수자 요약 카드 상단 간격 분리
- 관리자 사용자 탭의 의미 없는 외곽 컨테이너 제거 및 공통 탭 컴포넌트화
- Topbar 프로필 이미지 실패 시 깨진 이미지 아이콘 대신 안전한 fallback 표시
- 학습자 상세 분석의 4개 탭이 한 줄을 유지하도록 수정
- 회기 축어록 내담자 발화의 불필요한 테두리 제거
- Google 로그인 단일 진입 UX 정리
이 UI는 로컬 내장 브라우저에서 시각 확인했지만 production에는 배포하지 않았다. 당시 내장 브라우저에는 로컬 분석/축어록 탭과 production 관리자 탭이 열려 있었다. 익일에는 탭 존재를 가정하지 말고 새로 열어 확인한다.
4. 운영 데이터와 업로드 보존 감사
4.1 정확한 소스 경계
아래 세 root의 profile-avatars만 source allowlist로 사용했다.
D:\workspace\vignette\apps\api\uploadsD:\workspace\vignette-public-runtime-bf5f7352\apps\api\uploadsD:\workspace\vignette-public-runtime-dba9b75a3887\apps\api\uploads
결과:
- union object count: 93
- union inventory SHA256:
9d703126f78d4fc8330408835d76a7d680276240dc578d6fc9ca420c2f25e6aa - union total bytes: 52,973
- maximum object bytes: 28,208
- same-name content conflict: 0
- invalid/nested/reparse entry: 0
- strict server-generated UUID-token filename shape: 93/93
- DB references found in union: 8/8
연결 DB의 개인정보 없는 결속값:
- database target SHA256:
81fe4a2844b7340f21396931fa18580e24358f857a08cc60540ddf8a4f8789b5 - reference count: 8
- unique referenced objects: 8
- reference-set SHA256:
70926cf36ceb2375dd6c471bc59a38138460d8e4895ad1f2bddbcf1a49210d2b - active private multimodal audio: 0
4.2 종료 직전 발견한 손상 이미지
Pillow 12.2.0의 실제 decode/verify와 확장자-format 일치를 파일명·경로·사용자 ID·이메일·URL을 출력하지 않고 검사했다.
- 전체 93개: 정상 decode 3, 실패 90
- 정상 3개: JPEG 2개, PNG 1개; 크기 225×225, 512×512, 1×1
- 운영 DB 참조 8개: 정상 2, 실패 6, missing 0
- 정상 참조 2개: JPEG, 225×225 및 512×512
- 실패 참조 6개: 모두 70 bytes, PNG signature는 있으나 full decode 실패
- 실패 6개는 동일한 content 한 종류다.
근거 파일: docs/ops/evidence/avatar-decode-audit-2026-08-29.json
이 결과의 의미:
- “93개를 덮어쓰기 없이 복사했다”만으로는 깨진 이미지 문제가 해결되지 않는다.
- 6개 손상 payload와 해당 DB reference를 승인 없이 삭제·초기화하면 안 된다.
- 원본을 찾을 수 있으면 복구하고, 찾을 수 없으면 손상 bytes는 private forensic 보존하되 public static 응답은 404/fallback으로 보내는 정책이 권장된다.
- 현재 UI 후보의
ResilientImage가 시각적 fallback은 제공하지만, backend가 손상 파일을 정상 이미지처럼 공개하는 문제와 데이터 복구 정책은 별도로 닫아야 한다. - 익일 첫 결정 게이트는 다음 둘 중 하나다.
- 권장: 손상 bytes와 DB reference를 보존하고, manifest에 decode 상태를 결속해 손상 객체는 public serve하지 않으며 UI fallback을 사용한다. 이후 원본 복구 또는 소유자 승인 기반 정리를 별도 수행한다.
- 엄격: 6개 원본을 복구할 때까지 API cutover 자체를 fail-closed로 막는다.
5. 업로드·DB·재부팅 복구 코드 상태
2026-08-29 dirty master에서 시작한 다음 안전 계약은 현재 clean 통합 브랜치에 좁은 커밋으로 결합돼 있다.
- exact 3-root union + caller-pinned count/inventory digest
- source copy 전후 재스캔과 create-only copy
- DB reference 8/8 보존 확인
- manifest v3의 privacy-safe path/content hash, total bytes, decode 상태와 DB target binding
- 새 API가 자기 pool의 repeatable-read snapshot으로 DB target과 현재 avatar refs를 DDL 전에 검증
- 모든 신규 physical DB connection이 target digest를 재검증
- production Uvicorn
--workers 1고정 - upload/PATCH/onboarding write lease와 freeze drain
- unrelated profile PATCH가 stale avatar URL을 되살리지 못하도록 수정
- 신규 업로드는 UUID(user id)+random token 이름, create-only hard-link publish
- static 공개 범위는 decode-valid manifest-preserved path 또는 full decode를 재통과한 strict runtime-generated filename으로 제한
- preserved bytes immutable memory cache와 신규 업로드 single-read response로 per-request 전체 hash DoS와 disk reopen TOCTOU 제거
- boot/watchdog task를 새 정의로 disabled 설치 → exact action 계약 확인 → 둘을 함께 enable
- 두 번째 task 설치/enable 실패 시 두 task 모두 disabled로 보상
- exact root task path
\결속
재개 후 위 미완료 연결은 해소했다. initializer manifest/result와 bootstrap의 cutover/task-recovery/final passed receipt가
모두 preserved_total_size_bytes=52,973과 decode proof를 교차 검증한다. 남은 것은 승인된 격리 풀스택 E2E와 정식
배포·public browser proof이지 manifest 생산자/소비자 계약 불일치가 아니다.
6. 마지막 검증 결과
2026-08-30 재개 후 보고:
- 전체 API:
1074 passed / 1 skipped - gateway:
68 passed - runtime/bootstrap 결합:
154 tests OK - API runtime focused:
31 passed - web typecheck/build: PASS
- 이미지 복구+사람 게이트 focused browser E2E: PASS
- Codex 내장 브라우저 local stateful 사람 게이트: overlay 0, 보류/반려 effect 0, 승인 effect 1
- SSOT checker와 scoped
git diff --check: PASS
주의:
- full API·web build/typecheck·route/internal-browser proof는 현재 후보 기준이다.
- 실제 Postgres를 쓰는
layout-visual-gate15/15와session-layout8/8, production browser proof는 아직 없다. - Python 3.12/3.14의
tempfile.TemporaryDirectory가 현재 sandbox ACL과 충돌해 생성 직후 접근 거부를 냈다. 동일 테스트는 Python 3.11에서 정상 통과했다. 익일 테스트는py -3.11또는C:\Users\encep\AppData\Local\Programs\Python\Python311\python.exe를 사용한다. D:\workspace\vignette\tmp아래 접근 거부 임시 디렉터리들은 테스트 환경 잔재다. 광범위 재귀 삭제하지 말고, 필요 시 exact path와 ACL을 확인한 뒤 별도로 정리한다.
7. 이전 세션 Git·워크트리 기준선
2026-08-29 21:15 KST 기준:
- shared checkout:
D:\workspace\vignette - branch:
master - HEAD:
ac9b7026881139780938f4c4f2b89a235b0a0c08 - HEAD tree:
b07cb4dd6b9b33b650b59b24fc1bc4b8bf2b48f8 origin/master보다 20 commits ahead- shared checkout은 사용자 작업과 이번 작업이 섞인 큰 dirty tree다.
git status --untracked-files=all은 접근 거부 tmp를 포함해 547 entries를 셌다. git add .,git commit -a, whole-tree copy는 금지한다.
관련 worktree:
- UI clean candidate:
D:\workspace\vignette-ui-image-release-20260829,a73b9eff - old runtime-storage candidate:
D:\workspace\vignette-runtime-storage-release-20260829,dba9b75a, dirty; 현행 source of truth로 사용하지 않는다. - current public runtime:
D:\workspace\vignette-public-runtime-dba9b75a3887, detacheddba9b75a - current scheduled tasks
VignettePublicRuntime,VignettePublicRuntimeWatchdog는 마지막 확인 시 enabled/Ready이며 여전히 olddba9b75aruntime을 가리킨다. - current Pages production은 deployment
0c60261e, source5bf89ff였다. UI 후보는 아직 미배포다.
8. 현재 변경 파일 경계
API 소유 범위:
apps/api/app/config.pyapps/api/app/db.pyapps/api/app/main.pyapps/api/app/routes/users.pyapps/api/app/upload_runtime.pyapps/api/app/upload_storage.pyapps/api/app/test_upload_storage_contract.pyapps/api/app/test_engine_health_contract.pyscripts/validate-public-runtime-upload-manifest.py
runtime/bootstrap 핵심 범위:
scripts/initialize-public-runtime-upload-root.pyscripts/initialize-public-runtime-upload-root.ps1scripts/bootstrap-legacy-public-runtime-upload-root.ps1scripts/validate-public-runtime-offline-quiescence.pyscripts/probe-public-runtime-database-identity.pyscripts/probe-public-runtime-upload-root.pyscripts/public_runtime_database_identity.pyscripts/public-runtime-upload-root.ps1scripts/public-runtime-task-maintenance.ps1scripts/public-runtime-task-definition-cutover.ps1scripts/start-public-runtime.ps1scripts/boot-public-runtime.ps1scripts/watch-public-runtime.ps1scripts/install-public-runtime-task.ps1scripts/register-boot-task.ps1- 관련 focused tests 10개
docs/ops/public-runtime-watchdog.md- 관련 architecture/local-development/testing 가이드와
docs/dev_dashboard.html
마지막 직접 수정된 task tests:
scripts/test_public_runtime_upload_root.pyscripts/test_public_runtime_task_definition_cutover.py
파일 전체를 자동 stage하지 말고 각 diff에 선행 사용자 변경이 섞였는지 다시 확인한다.
9. 익일 재개 순서
9.1 현재 truth 재확인
- Windows/PowerShell 판, 현재 경로, Git HEAD/worktree/status를 다시 확인한다.
- 이 문서와
docs/dev_dashboard.html,docs/ops/backlog-2026-06-26.md,docs/ops/public-runtime-watchdog.md를 읽는다. - production/API/task state는 문서만 믿지 말고 read-only로 다시 확인한다.
- 세 source root union을 다시 계산해
93 / 9d7031... / 52,973 bytes, DB8 refs / 70926c... / private audio 0과 일치하는지 확인한다. - 아바타 decode audit도 재실행해
DB refs valid 2 / invalid 6 / missing 0이 유지되는지 확인한다.
9.2 코드 blocker 해소
PreservedInventory.total_size_bytes를 initializer privacy-safe result, manifest, bootstrap cutover receipt, task-recovery receipt, final passed receipt까지 끝까지 결속한다.- exact expected total
52,973을 CLI 인자와 tests에서 pin한다. count 93만으로 same-count substitution을 허용하지 않는다. - 6개 손상 DB-ref에 대한 정책을 소유자와 결정한다. 어떤 경우에도 원본 bytes/DB reference를 승인 없이 삭제하지 않는다.
- 권장 정책을 택하면 decode-valid preserved object만 immutable public cache로 제공하고, invalid object는 private forensic 보존 + public 404/fallback 처리하며 privacy-safe 손상 count를 health/receipt에 기록한다.
- 새 stable upload root가 비어 있거나 exact expected set임을 cutover 전후에 증명한다. UUID형 pre-existing extra를 무조건 허용하지 않는다.
- 문서·SSOT·얇은 backlog를 실제 계약과 일치시킨다.
9.3 통합 테스트
Python 3.11로 최소 아래를 한 번에 다시 실행한다.
py -3.11 -B -X utf8 -m unittest `
apps.api.app.test_upload_storage_contract `
apps.api.app.test_engine_health_contract `
scripts.test_initialize_public_runtime_upload_root `
scripts.test_legacy_public_runtime_upload_bootstrap `
scripts.test_public_runtime_environment_handoff `
scripts.test_public_runtime_listener_pid_probe `
scripts.test_public_runtime_task_definition_cutover `
scripts.test_public_runtime_task_maintenance `
scripts.test_public_runtime_upload_release_safety `
scripts.test_public_runtime_upload_root `
scripts.test_public_runtime_watchdog_provenance `
scripts.test_start_public_runtime_contract -v
추가 검증:
- Windows PowerShell 5.1 AST parse for all changed
.ps1 - Python compile for all new/changed
.py - scoped
git diff --check - API의 전체 관련 test suite
- 깨끗한 통합 후보에서
apps/web의npm run typecheck,npm run build e2e/layout-visual-gate.spec.ts15/15e2e/session-layout.spec.ts8/8e2e/image-resilience.spec.tse2e/tabs-behavior.spec.ts- Google auth/onboarding/admin/profile avatar 실제 브라우저 E2E
9.4 좁은 커밋과 clean candidate
- shared dirty master에서 이번 runtime 파일만 line-by-line 검토해 좁게 stage한다.
- author는
Yun Chan <yunchan@twentyoz.kr>, 한글의 짧은 커밋 메시지를 사용한다. git add .금지.- master
ac9b7026이후 runtime commit을 만들고, 새 clean release worktree/branch를 만든다. - UI 커밋
35a62fda,a73b9eff를 순서대로 cherry-pick한다. - clean candidate SHA/tree, clean status, 테스트 결과를 고정한다.
9.5 사용자 승인 후에만 정식 전환
후보가 GREEN일 때 사용자에게 아래 범위를 정확히 제시하고 승인받는다.
후보 커밋
<sha>를 원격에 push하고 Cloudflare Pages production과 이 PC의 public API/tunnel·두 예약 작업을 새 detached runtime으로 전환해도 돼? 기존 API/tunnel은 약 1분 재시작되고, 검증된 upload inventory는 덮어쓰기 없이 새 영구 root에 보존돼.
승인 전 금지:
git push- Cloudflare Pages production deploy
- public API/cloudflared stop/restart
- scheduled task reinstall/retarget/enable 변경
- DB avatar URL 수정
- stable upload root 생성/복사
승인 후에도 Pages 자산 보존은 stale default script를 그대로 쓰지 않는다. 최소 다음 실제 production 세대의 immutable asset graph를 explicit origins로 보존한다.
https://0c60261e.vignette-b1q.pages.devhttps://ef48c0ae.vignette-b1q.pages.devhttps://1f1ddf18.vignette-b1q.pages.dev
배포 뒤에는 HTTP 200만 보지 않는다. custom domain의 신규 index/asset hash·MIME·신규 UI marker·구버전 marker 부재, API health manifest/DB/freeze proof, Google 로그인, super account role/onboarding, 데이터/아바타 fallback을 Codex 내장 브라우저로 보여준다.
9.6 실제 재부팅 게이트
production 전환과 browser smoke가 끝난 뒤에만 아래 문구로 명시 승인받는다.
지금 이 PC를 재부팅해도 돼. 저장하지 않은 작업은 없고, encep 계정으로 로그인한 뒤 REQ-008 자동복구 smoke까지 계속 진행해.
재부팅 뒤에는 먼저 task를 수동 실행하지 않는다. 로그인 후 자동으로 API/tunnel/tasks가 복구되는지 관찰하고, 공개 health·Google 로그인·데이터·이미지·새 runtime SHA/task action을 증명한다.
10. C001 외부 임상 검수 게이트
C001은 코드·UI·운영 배포로 대신할 수 없다. 적격 검수자의 실제 입력, 자격/역할, 검토 시각, 대상 버전/패키지 결속, 승인 또는 수정 요청을 받아 workbook의 지정 셀에 반영해야 한다. 검수자가 없으면 최종 Goal은 기술 완료 / 외부 검수 대기로 정확히 남긴다.
11. 절대 하지 말 것
- 손상 아바타 6개의 DB URL이나 파일을 승인 없이 삭제·초기화하지 않는다.
- 90개 decode-invalid legacy payload를 정상 이미지로 간주하지 않는다.
- 한 개 source root만 복사해 8개 DB ref를 복구했다고 주장하지 않는다.
- old runtime root 2개만 보고 데이터가 온전하다고 판단하지 않는다.
- general
/uploadsdirectory를 static mount하지 않는다. - production Uvicorn worker를 2개 이상 띄우지 않는다.
- reset receipt 없이 initial nonzero refs → current zero를 정상으로 받아들이지 않는다.
- 예약 작업을 새 정의로 교체한 뒤 검증 전에 enable하지 않는다.
- 실제 재부팅 전 task를 수동 실행해 자동복구 증거를 오염시키지 않는다.
- 외부 임상 검수 내용을 만들어내지 않는다.
- dirty tree에서 전체 stage/commit/copy하지 않는다.
- Python 3.12/3.14 tempfile ACL 오류를 제품 테스트 실패와 혼동해 같은 방식으로 반복하지 않는다.
12. 재개 프롬프트
다음 세션에서 아래처럼 시작하면 된다.
docs/ops/handoff-goal-production-2026-08-29.md를 먼저 읽고, live/Git/DB/avatar decode truth를 read-only로 재검증해. 손상 DB-ref 6개의 보존·fallback 정책과preserved_total_size_bytes=52973end-to-end 결속부터 마무리하고, 전체 통합 GREEN 전에는 stage/push/deploy/runtime/task/DB를 건드리지 마. clean candidate가 준비되면 SHA와 승인 범위를 먼저 보여줘.