vignette/docs/ops/handoff-goal-production-2026-08-29.md

21 KiB
Raw Blame History

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, gateway 68 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_quarantine effect 0, 증거 없는 release 승인 disabled→reject effect 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-gate 15/15와 session-layout 8/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로 사용했다.

  1. D:\workspace\vignette\apps\api\uploads
  2. D:\workspace\vignette-public-runtime-bf5f7352\apps\api\uploads
  3. D:\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가 손상 파일을 정상 이미지처럼 공개하는 문제와 데이터 복구 정책은 별도로 닫아야 한다.
  • 익일 첫 결정 게이트는 다음 둘 중 하나다.
    1. 권장: 손상 bytes와 DB reference를 보존하고, manifest에 decode 상태를 결속해 손상 객체는 public serve하지 않으며 UI fallback을 사용한다. 이후 원본 복구 또는 소유자 승인 기반 정리를 별도 수행한다.
    2. 엄격: 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-gate 15/15와 session-layout 8/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, detached dba9b75a
  • current scheduled tasks VignettePublicRuntime, VignettePublicRuntimeWatchdog는 마지막 확인 시 enabled/Ready이며 여전히 old dba9b75a runtime을 가리킨다.
  • current Pages production은 deployment 0c60261e, source 5bf89ff였다. UI 후보는 아직 미배포다.

8. 현재 변경 파일 경계

API 소유 범위:

  • apps/api/app/config.py
  • apps/api/app/db.py
  • apps/api/app/main.py
  • apps/api/app/routes/users.py
  • apps/api/app/upload_runtime.py
  • apps/api/app/upload_storage.py
  • apps/api/app/test_upload_storage_contract.py
  • apps/api/app/test_engine_health_contract.py
  • scripts/validate-public-runtime-upload-manifest.py

runtime/bootstrap 핵심 범위:

  • scripts/initialize-public-runtime-upload-root.py
  • scripts/initialize-public-runtime-upload-root.ps1
  • scripts/bootstrap-legacy-public-runtime-upload-root.ps1
  • scripts/validate-public-runtime-offline-quiescence.py
  • scripts/probe-public-runtime-database-identity.py
  • scripts/probe-public-runtime-upload-root.py
  • scripts/public_runtime_database_identity.py
  • scripts/public-runtime-upload-root.ps1
  • scripts/public-runtime-task-maintenance.ps1
  • scripts/public-runtime-task-definition-cutover.ps1
  • scripts/start-public-runtime.ps1
  • scripts/boot-public-runtime.ps1
  • scripts/watch-public-runtime.ps1
  • scripts/install-public-runtime-task.ps1
  • scripts/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.py
  • scripts/test_public_runtime_task_definition_cutover.py

파일 전체를 자동 stage하지 말고 각 diff에 선행 사용자 변경이 섞였는지 다시 확인한다.

9. 익일 재개 순서

9.1 현재 truth 재확인

  1. Windows/PowerShell 판, 현재 경로, Git HEAD/worktree/status를 다시 확인한다.
  2. 이 문서와 docs/dev_dashboard.html, docs/ops/backlog-2026-06-26.md, docs/ops/public-runtime-watchdog.md를 읽는다.
  3. production/API/task state는 문서만 믿지 말고 read-only로 다시 확인한다.
  4. 세 source root union을 다시 계산해 93 / 9d7031... / 52,973 bytes, DB 8 refs / 70926c... / private audio 0과 일치하는지 확인한다.
  5. 아바타 decode audit도 재실행해 DB refs valid 2 / invalid 6 / missing 0이 유지되는지 확인한다.

9.2 코드 blocker 해소

  1. PreservedInventory.total_size_bytes를 initializer privacy-safe result, manifest, bootstrap cutover receipt, task-recovery receipt, final passed receipt까지 끝까지 결속한다.
  2. exact expected total 52,973을 CLI 인자와 tests에서 pin한다. count 93만으로 same-count substitution을 허용하지 않는다.
  3. 6개 손상 DB-ref에 대한 정책을 소유자와 결정한다. 어떤 경우에도 원본 bytes/DB reference를 승인 없이 삭제하지 않는다.
  4. 권장 정책을 택하면 decode-valid preserved object만 immutable public cache로 제공하고, invalid object는 private forensic 보존 + public 404/fallback 처리하며 privacy-safe 손상 count를 health/receipt에 기록한다.
  5. 새 stable upload root가 비어 있거나 exact expected set임을 cutover 전후에 증명한다. UUID형 pre-existing extra를 무조건 허용하지 않는다.
  6. 문서·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/webnpm run typecheck, npm run build
  • e2e/layout-visual-gate.spec.ts 15/15
  • e2e/session-layout.spec.ts 8/8
  • e2e/image-resilience.spec.ts
  • e2e/tabs-behavior.spec.ts
  • Google auth/onboarding/admin/profile avatar 실제 브라우저 E2E

9.4 좁은 커밋과 clean candidate

  1. shared dirty master에서 이번 runtime 파일만 line-by-line 검토해 좁게 stage한다.
  2. author는 Yun Chan <yunchan@twentyoz.kr>, 한글의 짧은 커밋 메시지를 사용한다.
  3. git add . 금지.
  4. master ac9b7026 이후 runtime commit을 만들고, 새 clean release worktree/branch를 만든다.
  5. UI 커밋 35a62fda, a73b9eff를 순서대로 cherry-pick한다.
  6. 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.dev
  • https://ef48c0ae.vignette-b1q.pages.dev
  • https://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 /uploads directory를 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=52973 end-to-end 결속부터 마무리하고, 전체 통합 GREEN 전에는 stage/push/deploy/runtime/task/DB를 건드리지 마. clean candidate가 준비되면 SHA와 승인 범위를 먼저 보여줘.