8월 7일까지 워킹트리에만 남아 있던 미커밋 작업을 커밋한다. 여러 사본 폴더(worktree·clone)에 흩어져 있던 중간 스냅샷을 정리하기 전에 원본을 git 이력으로 고정하는 것이 목적이다. - contracts/routes/services: measurement, outcome_trajectory, rupture_repair, deliberate_practice, calibration_transfer, supervision_research, multimodal_alliance, continuous_improvement 계열 신규 모듈과 테스트 - infra/db/init: 07~16 마이그레이션(측정 기반~calibration transfer 실행) - apps/web: 세션 리뷰 카드·관리 화면·E2E 스펙 추가 - docs/ops: G0~G8 라이브 통합·배포·롤백 증거 문서와 evidence JSON/PNG - scripts: smoke·ledger·릴리스 에이전트·NAS 프리뷰 운영 스크립트 engine.public 로그 .bak과 apps/web/test-results 산출물은 커밋에서 제외했다.
8.3 KiB
8.3 KiB
공개 DB 복구 리허설과 전환 게이트 — 2026-08-07
현재 상태
- 2026-08-07 18:26 KST owner 승인에 따라 공개 DB를 복구 named volume으로 전환했다. 공개 API
api-vignette.chanpaca.net은 이 Windows 호스트의127.0.0.1:8001로 들어오며,vignette-dev-db:55432가vignette_recovered_prod_20260807을 사용한다. - 18:30 KST 공개 경로 재검증 결과 owner 기준
app_user=84,sessions=30,turns=705, Google 계정 16, Google 소유 회기 30, orphan session 0이다. local/public health는 모두environment=prod,db=true,engine=true,engine_mode=claude_cli이고 OpenAPI path는 119개다. /auth/config의 Google OAuth는 configured, dev login은 disabled이며, 미인증/auth/me는 401, Google login start는accounts.google.com으로 302다. 공개 로그인 화면도 실제 브라우저에서 렌더됐다.- 복구 후보에는 유효한
auth_session을 복사하지 않았으므로 모든 사용자는 Google 재로그인이 필요하다. 계정·회기 원장은 복구됐지만 기존 로그인 쿠키가 계속 유효하다는 의미는 아니다. - 전환 직전 DB는
vignette-dev-db-pre-recovery-20260807-182642로 중지 보존했다. 자동 rollback을 검증한 앞선 실패 후보 2개도 중지 보존했으며, 어떤 원본 container/volume도 삭제하지 않았다. VignettePublicRuntimeWatchdog를 다시 활성화했고 18:30 KST check-only에서 engine, API, web, cloudflared, public API가 모두 healthy였다.- active recovered DB와 cutover 스크립트는
restart=unless-stopped를 사용해 Docker 재기동 뒤에도 같은 named volume을 자동 재연결한다. 이 변경은 실행 중 container를 재시작하지 않고 적용했다. - 전환 전 DB는 2026-08-06 20:36 KST에 만들어진 익명 PGDATA였다. owner 기준
app_user=706,sessions=448,turns=204이지만, 대부분은 개발/E2E fixture였다. - RLS context 없이 app role로 조회한
sessions=0,turns=0은 삭제 증거가 아니다. owner 조회에는 행이 있고pg_stat_user_tables.n_tup_del=0, 보존 로그의 대상 테이블 DELETE/TRUNCATE/DROP도 0건이다. - 운영에 가장 가까운 분리 원본은 detached Docker volume
08413cd7…cceba8이다. 원본은 읽기 전용으로 조사했으며app_user=84, Google account 16, Google-owned sessions 29, turns 705, feedback 2,895가 있다. 마지막 사용자 갱신 흔적은 새 DB 생성보다 약 14시간 40분 앞선 2026-08-06 05:55 KST다. - 현재 DB에는 이 원본에 없는 Google 회기 1개가 2026-08-07 07:55 KST에 생성돼 있다. 테스트라고 단정하지 않고 current-only delta로 보존한다. 이 회기는 종료 상태이며 턴 0, Alliance Pulse 1, measurement 9, self-assessment 1, state 1, summary 1, model run 2를 가진다.
복구 가능성 증거
- 사고 전 후보 dump:
D:\workspace\vignette-backups\vignette-before-test-data-cleanup-20260715-095617.dump - 현재 공개 대상 보존 dump:
D:\workspace\vignette-recovery\current-public-target-20260807-161953.dump- SHA-256
ceaf52230a8cd5dfc408c27728c894ca7290cc47d7e9a9d82e0e18e7239db145
- SHA-256
- 옛 원본 보존 dump:
D:\workspace\vignette-recovery\recovered-original-20260807-161953.dump- SHA-256
9e5f2185f146a31a322dbfc18b2fa1c3bfef64e951d58b8ff8d4aef0dc26a94a
- SHA-256
- 격리 복구 후보:
- container
vignette-recovered-prod-candidate - named volume
vignette_recovered_prod_20260807 - localhost
55433 - detached 원본과 current-only Google 회기 1개를 transaction merge
- migrations 01~16 적용, 최신 07·16
ON_ERROR_STOP단일 transaction 재적용 통과 - owner 무결성: users 84, sessions 30, turns 705, Google sessions 30
- orphan·external ID 중복·case/session_no 중복·시간 역전 0
- 같은 source 재실행
already-applied, 추가 삽입 0 - learner RLS simulation: 기대 session 6, app role 관측 session 6
- post-merge dump:
D:\workspace\vignette-recovery\candidate-post-delta-20260807-172059.dump - SHA-256
d24eaa753526dff77d889ed2a48477d4df4bc2b854878543f89f4db6f6502e32 - 비식별 merge manifest:
docs/ops/evidence/recovery-delta-merge-manifest-2026-08-07.json
- container
백업과 복원 리허설
scripts/backup-vignette-db.ps1은 running container와 readiness를 먼저 확인하고, custom-format dump를 container 내부에서pg_restore --list로 검증한 뒤에만.partial을 최종 dump로 원자 이동한다.- 게시 manifest는 container ID, dump 크기, SHA-256, 검증 방식을 포함하고 DB 본문·비밀은 포함하지 않는다.
- 2026-08-07 현재 public-target 백업:
D:\workspace\vignette-backups\vignette-dev-db-vignette-20260807-080647Z.dump- SHA-256
c39fb23ad4845275ccf1fbd659a18bc57da768d70fa16ad8ed917551bc089cf3 - 외부 포트 없는 일회용 DB에
pg_restore --exit-on-error성공 - 129 tables, 9,646 rows, users 706, sessions 448, turns 204
- session→user/turn→session orphan 0, invalid index 0
- temporary container/volume cleanup 뒤 잔여 0
- SHA-256
- 전환 직전 public-target 보존 dump:
D:\workspace\vignette-backups\vignette-dev-db-vignette-20260807-091057Z.dump- SHA-256
6b84d5c8fd82edd1c40f407e4b5d28e5c32ea6c00c953aaeb2ff5551f4e8af1f
- SHA-256
- engine 설정을 현재 공개 런타임과 맞춘 최종 복구 DB 보존 dump:
D:\workspace\vignette-recovery\vignette-recovered-prod-candidate-vignette-20260807-092630Z.dump- SHA-256
d7bcc396972482ea7691b3d25f923f92bb4e05c3a54fdc7d50954144a48868ed
- SHA-256
- 공개 전환 후 active recovered DB 보존 dump:
D:\workspace\vignette-backups\vignette-dev-db-vignette-20260807-095511Z.dump- SHA-256
660695c9b3590198de35d6027b70b2df8ffb5a684ccd1572415b8b2a3865bb88 - custom archive TOC 검증과 원자 게시 manifest 생성 완료
- SHA-256
- 예약 실행, 실패 알림, NAS/off-host 암호화 복제, restore drill 주기와 보존 정책은 별도 운영 게이트다.
확인된 재발 메커니즘과 패치
기존 scripts/dev-up.ps1은 vignette-dev-db가 실행 중이 아니면 컨테이너를 제거하고 PGDATA mount 없는
새 컨테이너를 만들었다. 기존 익명 volume은 삭제되지 않지만 서비스가 새 빈 volume을 보게 되는 형태가
현재 결과와 일치한다. 보존된 Docker event가 없어 실행 actor를 단정하지는 않는다.
재발 방지 패치는 다음 계약으로 바꿨다.
- 정지된 기존 DB는 제거하지 않고
docker start를 우선한다. - 새 DB는 고정 named volume
vignette-dev-db-pgdata를 명시한다. - 일반
dev-up에는 container/volume 삭제 경로가 없다. - 시작·readiness 실패 시 container와 volume을 보존하고 fail closed한다.
- focused pytest 5 passed, Ruff, Windows PowerShell 5.1 AST parser, diff check를 통과했다.
전환 결과와 남은 확인
- current-only Google 회기 1개와 직접 provenance 행을 격리 후보에 UUID remap하여 transaction merge한다. 기존 후보 계정/profile은 덮어쓰지 않고 auth session은 복사하지 않았다.
- 병합 후보의 계정·회기·턴·orphan·RLS·constraint·멱등 replay를 다시 고정한다.
- post-merge dump의 disposable clone에서 G4/G5 DB-backed 브라우저 폐루프를 확인한다.
desktop 2/2·mobile 2/2, route mock 0, G4/G5 실제 POST·read-model reload·멱등 재확인,
UUID 노출·마이크 호출·중복 원장 0을 확인했다. 증거 SHA는
7c2336bf…7cc93이다. - 윤찬의 공개 DB 전환 승인을 받는다.
- 전환 직전 현재 public-target dump SHA를 다시 확인한다.
- watchdog을 짧게 정지하고 현재 container를 보존 이름으로 바꾼 뒤, recovered named volume을
vignette-dev-db:55432에 연결한다. - API 8001 재기동 후 public health/OpenAPI/auth/OAuth start를 확인한다.
- 실제 사용자가 Google 재로그인한 뒤 본인 계정과 과거 소유 회기 목록이 보이는지 확인한다. 이 브라우저에는 기존 로그인 상태가 없어 계정 가장 없이 사용자 확인으로 남겼다.
- 실패 시 기존 container로 되돌리는 자동 rollback 경로를 두 차례 실증했다. 최종 전환은 통과했다.
공개 전환 뒤에도 원본·전환 직전 container와 모든 dump/volume은 사용자 확인과 안정화가 끝날 때까지 삭제하지 않는다.