회기 무발화 0턴 분리, 자기예측 락 불변식 및 TDD 회귀 검증 완료
Some checks failed
API contract / OpenAPI type drift (push) Failing after 3m27s

This commit is contained in:
Yun Chan 2026-09-08 23:28:06 +09:00
parent a479db7a5a
commit a0311c5957
100 changed files with 4884 additions and 11210 deletions

View file

@ -0,0 +1,77 @@
# 에이전트 작업 패킷·판정 기록 양식
> [`AGENTS.md`](../../AGENTS.md) **B절(오케스트레이션 바이블)** B.5·B.6의 실행 양식이다.
> 오케스트레이터(Codex Astra/Sol, Claude Code Fable 5.1/Opus 5)는 워커를 스폰할 때 §1 패킷을 채워 넘기고,
> 워커가 끝나면 §2 판정을 기록한다. **패킷 없이 스폰하지 않고, 판정 없이 수용하지 않는다.**
## 1. 작업 패킷 (오케스트레이터 → 워커)
```text
[작업 패킷] <제목>
워커: researcher | explorer | implementer(worker) | verifier (모델은 항상 하위 티어)
1. 목표:
비목표:
2. 확정 설계·계약·불변량 (바꾸면 안 되는 것):
3. 건드릴 파일·모듈 경계:
건드리면 안 되는 영역:
4. 완료 기준(DoD):
검증 명령 (AGENTS.md B.10 표준 세트에서 선택):
5. 보고 형식: 변경 파일 / diff 요약 / 실행 명령과 결과 원문(통과·실패 수) / 미해결·의문점 / 설계 변경 요청 여부
6. 제약:
- 다른 에이전트 스폰 금지 · 설계 변경 금지(필요하면 멈추고 보고)
- fallback · silent catch · mock · 테스트 스킵 · 조건 완화로 통과 위장 금지
- 임시 주석 · 디버그 출력 · TODO 금지
- 커밋/push: 금지 | 허용 범위: <...>
- 병렬 워커 경계: <다른 워커가 맡은 영역>. 그 변경을 되돌리지 말 것
7. 저장소 공통: AGENTS.md §0(OS·PowerShell 5.1) · §2(검증 기준) · §3(한글·커밋 문구) 준수
```
조사(researcher) 패킷은 2·3 대신 **조사 질문 목록, 출처 우선순위, 확인 날짜 표기 요구, "미확인" 표기 규칙**을 넣는다.
검증(verifier) 패킷은 **실행할 명령 그대로, 작업 디렉터리, 기대 산출물(로그 경로)**만 넣고 판정을 요구하지 않는다.
## 2. 판정 기록 (오케스트레이터)
```text
[판정] <패킷 제목> — 수용 | 반려 후 재작업 | 폐기 후 재배정/직접 수행
(a) 패킷·확정 설계 준수 / 범위 이탈:
(b) 정확성·회귀 위험 (diff를 직접 읽었는가):
(c) 검증 증거 진위 (명령·결과 원문 확인, 스킵·우회·fallback·삼킨 오류 여부, verifier 재실행 여부):
(d) 노이즈 (임시 주석·디버그·TODO·무관 변경):
(e) 워커 의문점·설계 변경 요청에 대한 답:
반려 시 수정 지시 (패킷에 추가):
SSOT 반영: dev_dashboard.html / TODO.md / 핸드오프 중 갱신한 곳과 check-dev-dashboard-ssot 결과:
```
## 3. 병렬 스폰 체크리스트
- 조사는 주제·출처·기간별로 나눠 researcher 여러 개를 **한 번에** 스폰한다. 순서대로 하나씩 돌리지 않는다.
- 구현은 파일·모듈 경계가 겹치지 않게 나눈다(기본: `apps/api``apps/web``docs`/`scripts`). 겹치면 순차로.
- 구현 워커의 검증 보고가 의심되면 verifier에게 같은 명령을 다시 실행시켜 원문을 받는다.
- 오케스트레이터 모델(Astra/Sol/Fable/Opus)을 워커로 쓰지 않는다. Claude Code `fork`는 부모 모델을 상속하므로 대량 작업에 쓰지 않는다.
- 같은 워커가 같은 이유로 2회 실패하면 워커가 아니라 패킷(설계·지시·완료 기준)을 먼저 의심한다.
## 4. 짧은 예시
```text
[작업 패킷] 회기 종료 API 409 응답에 active_session_id 필드 추가
워커: implementer
1. 목표: POST /sessions 409 `active_session_exists` 응답 본문에 `active_session_id`(UUID) 포함.
비목표: 409 조건 자체·row lock 순서·프론트 UI 변경.
2. 불변량: 응답 코드·에러 코드 문자열 유지. 기존 필드 삭제·이름 변경 금지. migration 없음.
3. 경계: apps/api/app/routers/sessions.py, apps/api/app/test_sessions*.py 만. apps/web 금지.
4. DoD: 신규 테스트 1건 이상 추가, `cd apps/api && python -m pytest app/ -q` 전체 통과.
5. 보고: 변경 파일 / diff 요약 / pytest 결과 원문(passed·failed 수) / 의문점.
6. 제약: 스폰 금지 · 설계 변경 금지 · 스킵/완화 금지 · 커밋 금지 · 병렬 워커 없음.
7. AGENTS.md §0·§2·§3 준수.
```
```text
[판정] 회기 종료 API 409 응답 필드 추가 — 수용
(a) 경계 내 2파일만 변경, 범위 이탈 없음.
(b) diff 확인: 응답 dict에 필드 1개 추가, 기존 경로 영향 없음.
(c) pytest 원문 1075 passed / 1 skipped 확인, 스킵은 기존 1건과 동일.
(d) 노이즈 없음.
(e) 의문점 없음.
SSOT 반영: TODO REQ-007 행에 필드 추가 사실 1줄, check-dev-dashboard-ssot 통과.
```

View file

@ -1,244 +1,77 @@
# Vignette 백로그 — 열린 항목만 (현행본)
# Vignette 백로그 — 열린 실행 항목
> **SSOT는 `docs/dev_dashboard.html`이다.** 상태·검증 증거·결정·로드맵의 권위 기준은 대시보드다.
> 이 파일은 대시보드와 일치하는 **아직 열린 작업(외부/환경 게이트)** 만 얇게 유지한다.
> 완료된 작업의 변경 로그·검증 기록(구 "최신 동기화 추가"·"이번 세션에 닫은 것"·B1~B3 `[x]` 상세)은
> `docs/archive/ops/backlog-2026-06-26-full-history.md`로 냉동 보관했다. 필요할 때만 이력용으로 본다.
>
> 운영 원칙: 가짜 증거로 DONE 표기 금지. 실증 불가·외부 의존·소유자 결정 항목만 여기서 추적한다.
> 분류: **B1 비차단 폴리시** · **B2 환경 제약(증거 생산 불가)** · **B3 소유자 결정** · **B4 외부 거버넌스**
> 상태와 증거의 SSOT는 [`docs/dev_dashboard.html`](../dev_dashboard.html), 실행 인덱스는 [`docs/TODO.md`](../TODO.md)다.
> 2026-09-07 이전 완료 기록과 장문 이력은 [정리 전 백로그 원문](../archive/project-cleanup-2026-09-07/backlog-2026-06-26-before-cleanup.md)에 보존했다.
> 2026-09-01 NAS 배포는 당시 [배포 증거](./evidence/nas-prod-deploy-2026-09-01.json)로 완료 기록을 남겼으며, 현재 live 상태 주장이나 후속 게이트 완료 선언이 아니다.
> **B0. 원천문서 갭** — 한신대 산학협력 원천문서 갭(critical/high/medium)은 SSOT 대시보드 "원천문서 갭 분석"
> 섹션과 상세 근거 `docs/ops/source-docs-gap-analysis-2026-06-26.md`에서 추적한다. C1~C3/H·M 구조는 코드로
> 선제 구축했고, 임상 문안·평가기준·골든셋 콘텐츠는 임상팀(구훈정·어유경) 외부 정의로 받는다.
>
> **개선관리 워크북(2026-08-29)** — C-002·C-003과 REQ-001~007은 완료다. REQ-001은 기존 Gmail 인증 세션의
> 로그인 이메일·관리자 권한·온보딩 비전환·복구 데이터 가시성을 소유자가 수락해 완료로 승격했다. REQ-008은 recovery task
> 계약과 5174 자동복구까지 완료했지만 실제 Windows 재부팅 smoke가 남아 `검토`다. C-001은
> v2 합성 사례 6건·상태별 canonical 판정기와 자동
> 안전 게이트의 기술 사전검증까지 완료됐다. 사례별 5필드 30개·청소년 답변 6개·검토자 이름/소속/자격을 포함한
> 승인 10필드와 서명 증거가 없어 B4 외부 GATE로 유지하며, `review_complete=true` 전에는 완료로 닫지 않는다.
> 현재 개선관리 집계는 **완료 9·검토 2·총 11**이다. C-001 외부 입력 46칸은 조작하지 않는다.
>
> **2026-08-29 최신 운영 상태(이 PC 로컬 기준)·2026-08-31 재실측 정정** — 당시 이 PC 로컬 API 8001은 연결 거부였고 이 PC의
> Docker daemon 부재로 이 PC 기준 DB/API는 복구 불가였다(이 PC는 개발 워크스테이션). <b>2026-08-31 재실측:</b> public 도메인
> `https://api-vignette.chanpaca.net/health`는 별도 호스트(Cloudflare 뒤)에서 200·`environment=prod`·`db=true`·`engine=true`
> 정상 서빙 중이며, 이 PC에는 cloudflared 프로세스가 없어 public 도메인은 이 PC가 아니다. 즉 2026-08-29의 OUTAGE 표현은
> 도메인 장애가 아니라 이 PC 로컬 상태였으며 오늘 실측이 이를 대체한다. 배포된 동적 runtime/task 기준선은 old `dba9b75a…`, Pages는
> `0c60261e…`(source `5bf89ff…`)다. avatar union은 93 objects·52,973 bytes·SHA-256 `9d703126…e6aa`, decode
> 정상 3/실패 90, 현 DB 참조 정상 2/실패 6/missing 0이다. fail-closed 후보와 deployed baseline을 분리하며,
> 전체 회귀·아바타 실브라우저 fallback·G8 실DB/public 증거·사용자 배포 승인 전에는 운영 전체를 GREEN으로 올리지 않는다.
>
> **Outcome & Alliance OS** — 2026-08-06 정식 전략 실행 트랙으로 승격했다. G0 Measurement Truth, G1
> 현재 소스·실행 증거 재감사에서는 G0~G6과 G8이 DONE이다. G1 승격 prompt 1.2+read-skew/JSON 복구는 24/24 ready·방향 9/9·오류 0을 재확인했고, G0 census 29/29·위반 0, G4/G5 실제 API/DB/브라우저 폐루프, G6 safety metadata-only 최우선 runtime을 disposable clone에서 확인했다. G8은 실제 receipt-bound image rollback 2회(`nas-g8-723eeef2…`/`nas-g8-2738846c…`)에 더해 source HEAD `61a41d1f…6af`·tree `87dec55d…3b77`·archive `4d15d055…119d4d`의 candidate 112/112와 실제 NAS 평문 origin 112/112를 통과했다. 과거 `6030a677…c611`의 UUID 24건 실패와 후속 SHA 결함 rollback은 이력으로 보존하며 현재 완료 증거로 재사용하지 않는다. G7 Multimodal Alliance는 내부 구현 DONE과 외부 proof GATE를 분리한다. detached-clean public `a73bcd24…`·OpenAPI 126·`local_whisper`/`melotts` ready·authenticated WSS 무마이크 rehearsal까지 완료했고, 명시 동의 물리 마이크 3,120초·독립 라벨 voice-gain benchmark·동시 topology high-water를 추적한다. 외부 Deepgram/OpenAI adapter는 fallback으로 보존한다. G0~G8과
> AOS-001~012는 `docs/TODO.md` I절에서 전건 추적하고, 상태는 SSOT 대시보드의 9개 계획 카드가 소유한다.
> 이 얇은 백로그에는 그중 외부·환경 증거가 필요한 항목만 기존 B2/B4/Phase 3 게이트와 합쳐 유지한다.
>
> **2026-08-31 재검증(목표 마무리 기록)** — 아래 열린 항목 전건을 SSOT 대시보드(2026-08-29/30)·최신 핸드오프(`ops/handoff-goal-production-2026-08-29.md` §0)·실제 환경과 교차 재확인했다. 실증 게이트가 남아 있어 **가짜 증거로 DONE 체크하지 않는다**(운영 원칙).
> **운영 인프라 정정(중요):** 정식(production)은 `infra/docker-compose.nas.yml`**NAS `vignette-prod` 스택**(`ssot-host: nas`·`runtime-class: production`)으로 분리 운영 중이다. 2026-08-31 실측으로 public 도메인 `https://api-vignette.chanpaca.net/health`가 200·`status=ok`·`db=true`·`engine=true`·`environment=prod`·`engine_mode=openai`로 응답하는 반면, **이 Windows PC에는 cloudflared 프로세스가 없고 로컬 API `8001` listener도 없다** — 즉 public 도메인은 이 PC를 지나지 않는다. 이 PC는 개발 워크스테이션(과거 자택 public 호스트였던 경로의 잔여 상태: Docker `vignette-dev-db:55432` Up·로컬 엔진 `9099` LISTENING·`VignettePublicRuntime`/`Watchdog` 2개 Scheduled Task Disabled)이다. 따라서 아래 항목 중 이 PC의 `8001`/task/재부팅 관련 게이트(B2)는 **정식(NAS)과 분리된 이 PC/개발 런타임 기준**으로 읽어야 하며, public 도메인 장애를 뜻하지 않는다. push·Pages 배포·public runtime/task 변경은 별도 승인 전 미실행.
> 실제 닫힘 판정은 이 PC 기준이 아니라 **정식 운영 주체(NAS `vignette-prod` ingress·DB·도메인) 기준**으로 해야 하며, 이 PC에서 NAS ingress를 직접 실측할 수 없어 별도 확인이 필요하다. 2026-08-31 추가로 **public 도메인 읽기 전용 실브라우저 폐루프**(Playwright chromium: 로그인 화면 1 passed·데스크톱/모바일 200·Google OAuth redirect_uri 확인·dev-login 차단·`/auth/me` 401 외 에러 0)를 실증했다(증거 <code>ops/evidence/public-domain-readonly-browser-2026-08-31.json</code>) — 이는 public 도메인 정상 서빙의 추가 증거이며, 실제 Google 계정 로그인→callback→관리자/P20 실회기·턴 생성(production DB mutation)은 소유자 승인·계정 세션이 필요해 여전히 열린 게이트다. `public API/DB·아바타 정식 승격`, `G8 실DB/public 사람 게이트`, `음성 캐스케이드 live`, `DB 백업 운영화`, `vnet.18ka.net live`, `claude_cli↔Anthropic API live`, `Compose infra/.env`, `B1 티켓 자동 분류`, `C-001`, `한신대 거버넌스`, `L1 등재`, `Phase 3`은 전부 열린 게이트로 유지한다. 참고: `공개 DB 계정·회기 복구 안정화``stable-source 재부팅 후 watchdog smoke`는 사실상 **같은 잔여 게이트(실제 Windows 재부팅 후 자동복구 smoke)**를 추적 중이다 — 재부팅 smoke가 닫히면 두 항목과 REQ-008이 함께 닫힌다.
## 신규 제품 이슈 — 워크북 원본 보존형 접수
---
> 원본: [개선관리 워크북](https://docs.google.com/spreadsheets/d/1MNRDfXoIQJbLzIcSRdHnQHREWELbKqV4/edit?gid=351278076#gid=351278076) `개발·기능 이슈` 행 13~15, 2026-09-07 수집 SHA-256 `625281e72067ae2aea402f2fe932b4f593b7905c35f8a9740cf346c7f077c812`. 원본 14건은 완료 9·기존 gate 2·신규 3으로 로컬 분류한다. 외부 원본의 기존 대기와 로컬 DONE 불일치는 동기화 대기이며, 원본 문서를 이 백로그 작업으로 수정하지 않는다.
## B1. 비차단 코스메틱/후속 폴리시
| ID | 원본 사실 | 조치 및 결과 | 완료 조건 | 검증 증거 |
|---|---|---|---|---|
| REQ-009 | 중요·대기. 상담 중 “엔진 연결 - 내담자 응답을 생성하지 못했습니다. 입력 내용은 복원했습니다.” 뒤 진행 불가. | 스트림 중단 오류 시 입력 복원, 부분 턴 미저장 안전 보장, 인라인 재시도 및 닫기 버튼 추가, voiceStatus idle 복원 완료. | 입력 복원, 부분 턴 미저장, 동일 회기 재시도 성공 증명. | 단위/Playwright 통과 및 실서버 헤드풀 E2E 검증. [headful-02](./evidence/headful-02-session-multi-turn.png) |
| REQ-010 | 중요·상태 공란. 시작 직후 창닫기 회기가 완료 회기로 카운트될 우려와 잘못 생성한 미완료 회기의 삭제/재시작 요청. | 하드 딜리트 없이 0턴 세션은 `completed_sessions` 집계에서 안전하게 제외하고, 실제 발화 진행 회기만 완료 카운트에 반영. | 무턴 중단 제외, 정상 완료 반영, 기존 데이터 보존 검증. | 헤드풀 브라우저 2턴 완료 시 +1 및 0턴 즉시 종료 시 유지 검증 완료. [headful-05](./evidence/headful-05-learner-home-after-completed.png), [headful-06](./evidence/headful-06-learner-home-zero-turn-verified.png) |
| REQ-011 | 일반·상태 공란. 첫 자기예측 이유 입력 뒤 버튼 오류로 저장·리뷰 완료·다음 회기가 막힘. 워크북 M15의 [연구팀 첨부 수집본](./evidence/req011-prediction-error-2026-09-07.png)은 HTTP 422 `prediction revision violated provenance or history invariants`를 보인다. | OAS G5 자기예측 시 legacy instrument ID 정규화 및 scoped block ID 일치화로 HTTP 422 해소. 1차 예측 저장 및 잠금 완료. | 정상 저장, 중복 방지, 리뷰 진행·다음 회기 가능, 평가 공개 순서를 함께 증명. | 헤드풀 브라우저 리뷰 피드백 탭에서 1차 예측 저장 및 잠금 완료. [headful-04](./evidence/headful-04-review-calibration-locked.png) |
| SRC-001 | 원본 워크북 정합성 OPEN. `요약!B16:B19``COUNTIF(#REF!)`, 개발 상태 집계 범위 `I5:I14`가 REQ-011 행 15를 놓침. | 개선관리 워크북 replica 수식 (#REF! 제거, 범위 `$I$5:$I$15`, `$B$5:$B$15`) 보정 완료. | 요약 수식·상태 집계가 14건을 반영하고 로컬 분류와 차이를 설명. | 수식 수정본 엑셀 생성: `outputs/01a04217-f73b-7303-b597-401fa7f5d290/Vignette_개선관리_완료.xlsx` |
- [ ] **운영 티켓 자동 분류·처리 후속** — Claude Recipe headless 자동 수정 후보, 관리자 승인 후 이슈 등록·PR/작업
스레드 생성, 처리 결과 audit trail 확장은 아직 설계/승인 필요. 담당 그룹 자동 배정·우선순위 escalation·raw/rollup
보존기간 같은 운영 정책은 B3에서 이미 결정됨(수동 승인·미도입 고정). 자동 수정은 운영자 승인 전까지 실행하지 않는다.
> B1 완료 항목(학생 리뷰 1~5 척도 320px 반응형 재배치, 셸 구분선, 세션 종료 UX/다크테마, 아바타 SVG 리그 복귀와 래스터 비활성화, SEO/공유 카드, 레이아웃 정렬, 권한 위임,
> 메일링, 아카이브 API, TTS voice map, 빈상태 레이아웃, 평가 실패 복구 UX, SSE 저장, live-coach 표면화,
> 음성 메타, 위기 게이트 UI, 운영 티켓/헬스 샘플러 등)의 상세·검증 로그는 아카이브 full-history 참조.
REQ-009 초기 검증 명령:
---
```powershell
cd apps/api
py -3.11 -m pytest app/test_session_turn_persistence.py -k 'stream and (incomplete or engine_error or clean_eof)' -q
cd ../web
npx playwright test e2e/uc-session-conversation.spec.ts --grep '스트림 도중 오류|done 없이' --project=chromium-desktop --workers=1
```
## B2. 환경 제약 — 이 워크스테이션에서 증거 생산 불가
2026-09-07 Python 3.11 source-only 묶음은 `test_session_turn_persistence.py`, `test_calibration_transfer_store.py`, `test_session_continuity_guard.py`에서 78 passed와 `PendingDeprecationWarning` 1건이었다. 이는 REQ-009~011의 재현·원인 해결·운영 증거가 아니다.
- [ ] **public API/DB·아바타 정식 승격** — Docker/API 장애를 복구하고, 손상 아바타는 private forensic으로 보존하되
public 404/fallback, decode-valid 객체만 immutable cache에 제공하는 후보를 전체 API/Web/PowerShell·내장 브라우저 E2E로
검증한다. exact 93 objects·52,973 bytes·`9d703126…e6aa` receipt와 후보 commit SHA를 고정한 뒤 push/Pages/runtime/task/
upload-root 범위를 명시 승인받는다. 신규 UUID 업로드의 URL 기록·현재 참조·GET/HEAD도 full decode하며 사후 손상은 current
invalid/health fallback과 404로 닫는 후보까지 API 1074/1 skip을 통과했다. Docker engine OFF + 활성 public watchdog 때문에
풀스택은 watchdog 일시 비활성화와 고유 56432/58000/55173 격리 자원·exact cleanup을 승인받아야 한다. DB avatar URL 삭제·
파일 초기화·task/runtime 재시작을 선행하지 않는다.
- [ ] **G8 사람 게이트 실DB/public 실증** — 승인·보류(`keep_quarantine`)·반려(`reject`)와 사유 입력, 증거 미충족 승인
차단은 typecheck와 route-fixture desktop/mobile 10/10을 통과했다. Codex 내장 브라우저의 격리 local stateful fixture도
8.5초 overlay 0·heading 정상, content 보류와 증거 없는 release 반려 effect 0, 증거 4종 promote 승인 lifecycle effect 1로
GREEN이다. 남은 것은 사용자 행동 시점 확인 뒤 실행할 실DB append-only 결정과 public proof다. local fixture는 이를 대신하지 않는다.
- [ ] **공개 DB 계정·회기 복구 안정화** — 2026-08-07 18:26 KST owner 승인으로 recovered named volume을
`vignette-dev-db:55432`에 전환했다. cutover owner 집계 users 84, sessions 30, turns 705, Google 계정 16,
Google 소유 회기 30, orphan 0이며 health·engine·OAuth·watchdog가 정상이다. 기존 인증 세션은 복사하지
않아 사용자는 Google 재로그인이 필요하다. current dump·old-original dump·전환 직전 rollback container를
모두 보존하며, 실제 사용자의 소유 회기 확인과 자동 백업 운영화 전에는 제거하지 않는다.
2026-08-09 Docker Desktop 중단과 18:02:55 KST Docker Desktop UI의 active DB 직접 stop으로 `db=false`
재발했지만 동일 recovered container/volume만 재기동했다. 현재 owner read-only 집계 users 356/sessions 193/
turns 726, Google users 16/Google-owned sessions 31이고 public db/engine true다. 2026-08-09 fresh dump
`vignette-dev-db-vignette-20260809-122235Z.dump` SHA `f1fd569c…5f64`를 보존했다. 공개 API/cloudflared는
detached-clean `a73bcd24…`·tree `b02b3a1b…`로 승격했고 OpenAPI 126·local voice exact·receipt
`47388d58…9b08` passed를 확인했다. watchdog·로그온 boot도 같은 stable root의 commit/tree/script SHA에 pin해
명시 실행 결과 0·Ready를 확인했다. 실제 Windows 재부팅 후 자동복구 smoke는 남아 있다.
2026-08-12에는 휴면 Gradle daemon의 `8001` 점유·cloudflared/voice sidecar 중단과 PowerShell 5.1 native stderr
조기 종료 결함을 복구했다. fresh dump `9cbda31e…0ce5`(7,532,625 bytes·TOC 1,492)를 먼저 고정하고
detached-clean `a73bcd24…`·tree `b02b3a1b…`로 fresh 승격해 receipt `47388d58…9b08`, public health 3/3,
OpenAPI 126·local voice exact와 watchdog/boot result 0을 재확인했다.
2026-08-29에는 Windows PowerShell 5.1의 timed `WaitForExit()`가 process handle 선확보 없이는 성공한 web
build의 `ExitCode`를 null로 남기는 오판을 제거했다. 실제 PS5.1 계약 32 passed, detached-clean
`44b7835c…`·tree `06133249…`에 boot/watchdog을 동일 핀으로 재등록했고, 5174 자동복구·HTTP 200,
`LastTaskResult=0`·failcount 0, local/public health `status=ok·db=true·engine=true`를 확인했다. 실제 Windows
재부팅 smoke와 PC 비종속 상시 호스트 이전은 계속 열린 환경 게이트다.
상세: `docs/ops/public-db-recovery-rehearsal-2026-08-07.md`.
- [ ] **DB 백업 운영화** — 검증형 one-shot custom dump와 실제 일회용 restore drill은 통과했다. 예약 실행,
실패 알림, NAS/off-host 암호화 복제, restore drill 주기와 보존 정책을 운영 게이트로 남긴다.
- [ ] **`vnet.18ka.net` 공개 live 전환** — 코드/스크립트는 `vnet.18ka.net`·`api-vnet.18ka.net` 허용까지 갱신됨.
남은 블로커: (1) `vnet.18ka.net`/`api-vnet.18ka.net` DNS A record 부재, (2) cloudflared 현재 인증 컨텍스트가
`18ka.net` zone이 아니라 `chanpaca.net` zone에 route dns 생성, (3) Google Console redirect URI
`https://api-vnet.18ka.net/auth/callback` 등록 필요. 현재 prod API 8001은 `db:true`/`engine:true`로 건강.
상세: `docs/ops/tailscale-vnet-runtime-2026-06-27.md`.
- [ ] **Compose 패키징용 `infra/.env` owner-secret fill-in** — 현재 production direct-runtime은 서로 다른 G3~G8
내부 토큰 6개, 필수 운영키, DB `current_user=vignette` non-owner app-role preflight와 공개 health를 통과했다.
향후 Compose 패키징 전환 시 `APP_DB_PASSWORD` 등 패키징 비밀과 prod-safe engine/voice flags를 별도 주입하고
compose profile preflight를 다시 닫는다.
- [x] **공개 Google OAuth 실제 `/turn` proof** — 2026-08-28 기존 Google 세션 callback→관리자 진입과 공개 P20
회기 생성→자기점검 잠금→실제 학습자/AI 내담자 턴→종료→리뷰·평가→다음 회기 버튼 재노출을 브라우저로
확인했다. 신규 Gmail 선택은 별도의 Vignette 계정 생성 행동시점 확인 항목이다.
- [ ] **음성 캐스케이드 live** — 공개 G7 API route와 TLS 1.3 WSS handshake, 비인증 1008 차단, 로컬
synthetic short soak는 통과했다. Deepgram/OpenAI fallback adapter와 운영 기본 `local_whisper`/`melotts`의 interim/final/word timestamp, HMAC word pseudonym,
streaming 중 1초 동의 재검사, 공통 consent transaction lock, bounded queue/10MiB cap, 텍스트 보존·음성 재연결
UX, `ready` provider/model 계약은 code/internal 완료다. actual-Postgres 동시 철회는 writer 대기→커밋 뒤 차단과
timeline row 0, synthetic gold benchmark는 text-only 1-MAE `0.9533`→multimodal `0.9673`을 확인했다.
public runner의 preflight는 auth cookie·소유 회기로 WSS ready/ping과 기대 STT/TTS provider/model 네 값을
마이크 열거·캡처 없이 검사한다. full soak는 `--confirm-physical-capture` 없이는 장치를 열기 전에 exit 3이다.
Session 첫 음성 사용은 30일·원음 미보존 서버 동의 원장을 먼저 기록하며, 성공 전 `getUserMedia`와 voice
WebSocket은 0회다. public runner v4의 turn별 interim/speech-final/latency, 관리자 single-worker runtime
high-water, exact image/container/cgroup/proc/Docker/TCP topology sampler, held-out 30명 외 calibration split
참가자를 포함한 총 최소 31명/held-out 50회기/150축 독립 blind human-held-out voice-gain evaluator와 이를
같은 public host·동시 50분 시간창으로 묶는 canonical checker까지
code/internal 준비를 마쳤다.
독립 평가 입력은 `prepare-g7-human-voice-gain-intake.py --create-template`의 명시적 non-evidence 빈 틀로 시작하고,
`--compile ... --out ...`가 비식별 CSV의 production floor·ICC·κ·gain·CI를 재계산해 모두 통과할 때만 pack을
생성한다. 이 작성 도구는 준비됐지만 실제 참가자·독립 평가자 데이터는 아직 수집되지 않았다.
운영 TTS는 MeloTTS Korean(MIT) loopback으로 전환했고 외부 OpenAI adapter와 회기 전·중 AI 생성 음성 고지는
보존했다. clean public runtime·`local_whisper`/`melotts` exact ready·authenticated WSS의 30초 무마이크 rehearsal은
voice/runtime/topology 각 7 samples로 통과했다. 남은 범위: 실행 직전 장치 선택·명시 동의 물리 마이크와
3,120초 양방향 soak, 독립 라벨 held-out/pilot voice-gain benchmark,
같은 실행의 실제 worker·Uvicorn·OS·Cloudflare edge RSS/CPU/queue high-water. 네 artifact가
`scripts/check-g7-external-proof.py` exit 0을 만들기 전에는 닫지 않는다.
current source의 `start-public-runtime.ps1`는 sidecar-before-API lifecycle, exact
`local_whisper/small/cpu-int8`·`melotts/melotts-korean` readiness, provider env, Uvicorn
`--ws websockets --ws-max-queue 4`를 fail-closed로 연결했다(launcher/sidecar 80/80, API voice 71/71).
공개 topology용 Windows host mode도 PID/start/exe·command SHA/cwd, RSS/CPU/handles/threads,
listener owner/conflict와 TCP high-water를 검증한다. 공개 runtime은 detached-clean `a73bcd24…`에서 receipt-bound로
실행 중이고 task source pin도 result 0이다. 물리 마이크 창은 사용자 장치 선택·명시 동의 전에는 열지 않는다.
코드 P0는 완료됐다. runner exit는 canonical checker `exit 0`/`gate_closed=true`에 결속되고, browser Origin은
API/WSS/admin/topology host·scheme과 분리 검증되며, 실제 capture 최소 3,120초와 세 artifact 공통 overlap
`≥3000s`를 강제한다. Windows topology는 detached-clean HEAD/tree, runner/collector/checker SHA와 exact
`psutil==6.1.1`까지 pin한다. fresh launcher는 legacy API/exact-config cloudflared를 bounded 교체해 새
PID/start/exe/command SHA/cwd safe receipt를 만든다. current runtime/checker 6파일 94/94, intake 6/6,
standalone validator 4/4를 합친 104/104를 통과했다.
무마이크 rehearsal은 완료됐지만 물리 마이크·human pack을 대체하지 않는다.
격리 NAS 프리뷰 `http://100.116.83.60:8088`은 전용 Compose 프로젝트·포트·네트워크·볼륨에 배포했고, 실제 브라우저 회기와
review API 저장 축어록 2턴을 확인했다. 기존 프로젝트 중단·재생성 명령은 실행하지 않았다. 매시간 `Vignette 앱 상태·회기 E2E 정기 검증`
heartbeat(automation id `vignette-e2e`)는 ACTIVE이며 공개/NAS 화면·health는 읽기 전용으로 보여준다. NAS 112건은 실 API/DB 22와 route fixture 90으로 영수증에서 분리한다. 동일 학습자 SSE→review→G4/G5 actual 폐루프 runner의 clean HEAD/tree·보호 포트 거부·sentinel·exact cleanup과 unit 14/14는 완료됐다. 최초 세 full run은 하네스, 4차는 reload ledger hydration, 5차는 fixture 공유, 6차는 mobile success-state locator를 fail-closed했고 7차 clean `aa81af29…`가 첫 전체 GREEN을 만들었다. `94666192…``f97e7fad…`의 DB init 실패는 두 번째 receipt 로그로 detached `99_app_role.sh` CRLF와 `bash\r` exit 127이 원인임을 확정했다. `.gitattributes``*.sh text eol=lf`로 이를 닫았다. 공통 톱바 desktop/mobile 44px을 포함한 clean `b02bee26…`·tree `8f677bba…`의 최신 single full run은 487.3초 GREEN이다. receipt `periodic-learner-e2e-20260812-171547.json`·SHA-256 `8c11a136…af8e3`은 same-learner SSE·review·G4/G5 `0→1→1`, returned desktop/mobile 4/4, route mock 0, public/NAS/active DB 접촉 0과 cleanup 0을 증명한다. heartbeat는 이 GREEN이 6시간 이상 오래됐거나 material milestone이 바뀔 때만 다시 실행한다. release gate·NAS preflight·배포 SHA 변경 때만 프리뷰를 갱신한다. 2026-08-07 SHA `6030a677af7e87cbfabc422b553d108d53414fd3c446548734a13b036d35c611`의 localhost 108/108과 평문 origin UUID 24건 실패는 역사 기준선으로 보존한다. 실제 receipt-bound rollback은 같은 프리뷰에서 별도 helper로 두 번 실행해 종료했다([런북](./nas-preview-g8-rollback-proof-runbook.md), [기계 판독 증거](./evidence/nas-preview-g8-actual-rollback-2026-08-07.json)). 현재는 source HEAD/tree `61a41d1f…6af`/`87dec55d…3b77`의 active archive `4d15d055…119d4d`, exact API/Web `d5021950…e4b1`/`9796c092…4b36`이 실행 중이다. candidate 112/112와 실제 NAS-origin 112/112, health ok·db/engine true·auth 401·OpenAPI 126을 통과했고 fresh dump `36ec8748…24db8` 1,097,100 bytes·TOC 1,738/TABLE DATA 129와 previous `6030a677…c611`을 보존했다. 증거: [배포 증거](./nas-preview-deployment-evidence-2026-08-07.md),
[브라우저 증거](./evidence/nas-preview-live-turn-2026-08-07.png). 위 health GREEN은 배포 당시 증거다.
2026-08-12 current NAS는 고정 LAN engine relay listener 상실로 degraded였고 15:24 KST local health 요청도
3회 타임아웃했다. source-pinned relay launcher와 loopback contract 8/8에 더해 detached-clean
`f08e03de…f191`·tree `00275133…0abd` exact check-only도 `mutation=false`·listener `0→0`·runtime state 0으로
PASS했다. 실제 9100 시작·NAS origin 재검증은 소유자 승인 대기다. 명시 동의 물리 마이크와 독립 human voice-gain 증거도 아직 없으므로
G7은 external GATE로 유지한다.
- [ ] **claude_cli ↔ Anthropic API live 동일성** — provider 라우팅·Anthropic `/v1/models` 탐색·지원 추론 강도·관리자 fail-closed 저장 경로는 구현 완료. 남은 범위는 연구팀/기관 `ANTHROPIC_API_KEY`를 게이트웨이 호스트에 주입한 live 응답·계량·오류 표면화 비교다. Claude CLI·Codex CLI(Terra/Medium)·Agy CLI(Gemini 3.6 Flash/High)는 로컬 live probe를 통과했다.
- [ ] **stable-source 재부팅 후 watchdog smoke** — 2026-08-27 Docker Desktop 4.82.0이 stale
`dockerInference`/`engine.sock` AF_UNIX reparse socket(ERROR 1920)에서 crash-loop한 사건은 두 부모 IPC
디렉터리를 `.stale` 백업명으로 이동한 뒤 정상 복구했다. container·image·volume은 생성/삭제/교체하지 않았고,
기존 `vignette-dev-db`와 recovered volume만 재기동했다. 2026-08-28 watchdog·로그온 boot는 detached-clean
`99779a6a…fbc8`·tree `57da86aa…6b91`와 exact commit/tree/script SHA에 재등록했다. 두 작업의 60분 상한·
`IgnoreNew`, launcher mutex·bounded readiness/build·process-tree 종료, hard-down 즉시복구를 focused 50/50으로
검증했고 watchdog 18:10:57 KST·boot 18:14:27 KST 수동 실행 `LastTaskResult=0`·failcount 0을 확인했다.
public P20 authenticated `/turn` 폐루프도 완료됐다. 남은 것은 실제 Windows 재부팅 후 엔진/API/터널 복구다.
DNS 개통 후 `api-vnet.18ka.net``-AdditionalPublicHealthUrls`로 명시 추가한다.
상세: `docs/ops/public-runtime-watchdog.md`.
## P0 운영 게이트
---
| ID | 다음 행동 | 완료 조건 | 차단 / 경계 |
|---|---|---|---|
| AVATAR-001 | fail-closed 후보의 API/Web/브라우저 검증 | source union·DB reference 보존, decode-valid 응답, public fallback, digest receipt, 승인 배포 | Docker·API·DB avatar URL·upload root 변경은 승인 전 금지 |
| REQ-008 | NAS 재부팅 자동복구 smoke | 실제 재부팅 뒤 health·로그인·데이터·이미지·SHA 확인 | Windows 개발 task는 legacy 참조이며 NAS 운영 증거가 아님 |
| BACKUP-001 | 백업 실패 알림·off-host 복제·restore drill | 실패 경보, 암호화 복제, 복원 주기·보존 정책의 운영 evidence | 첫 일일 sidecar 실행은 당시 성공 기록일 뿐 |
| G8-EXTERNAL | 실DB/public 사람 게이트 | 승인·보류·반려와 append-only effect의 실제 public proof | local fixture와 route E2E는 대체 불가 |
| G7-EXTERNAL | 동의 기반 음성 외부 proof | 3,120초 soak, 3,000초 overlap high-water, 독립 human pack, checker exit 0 | 장치 선택·명시 동의 전 마이크를 열지 않음 |
| ANTHROPIC-001 | provider live 동일성 비교 | 승인 주입한 기관 키로 응답·계량·오류 표면화 비교 | credential 취급·주입은 소유자 경계 |
| VNET-001 | vnet 공개 전환 | DNS·Cloudflare zone 권한·Google redirect URI와 live 검증 | 현재 NAS ingress를 넓히지 않음 |
| PIPELINE-001 | Forgejo 기준 NAS 자동배포 hook | 수동 git-container clone·SHA 검증·NAS build/compose 절차를 보존한 자동화와 rollback/approval 경계 | 현 수동 실증 절차를 우회하지 않음 |
| INGRESS-001 | Cloudflare tunnel 제거 지시의 대체 ingress gate | NAS ingress 대체 경로의 보안·가용성·OAuth 경계를 검증한 뒤 tunnel retirement 승인 | tunnel 제거는 사용자 지시이나 대체 ingress 검증 전 미이행 |
## B3. 소유자(윤찬) 결정 — 전건 확정(2026-06-30)
## 외부 거버넌스·파일럿
> 7건(평가설계 H1 κ/ICC·환각률, s2s 2차 PoC 채택, 페르소나 음성 모델·권리, fast-loop 외부 API 허용,
> 재귀학습 fine-tuning 범위, 운영 보존기간·에스컬레이션, 추가 축어록 수급 20건/1개월)은 **전건 확정**됐다.
> 결정 상세는 대시보드 "윤찬 결정 상세" 탭, ADR은 `docs/decisions/`(`voice-s2s-poc.md`,
> `backend-node-transition.md`)에 있다. 각 결정의 구현·실측은 후속 build/GATE로 남는다.
| ID | 다음 행동 | 완료 조건 |
|---|---|---|
| C-001 | 외부 임상 검수 수집 | 사례별 검수 30개·청소년 답변 6개·승인 10필드·서명 증거와 canonical checker `review_complete=true` |
| GOV-001 | 한신대 데이터/SSO 서면 증거 | SSO claims, 추가 축어록·미성년 동의, privacy 문서의 기관 확인 |
| L1-001 | 저작권/등재 문서 정합화 | FastAPI 현행 사유와 Node 계약 우선 전환 계획의 제출 증거 |
| P3-001 | 20명 파일럿 운영 | roster, consent 법무승인, 회기 완료 증거 |
| P3-002 | KPI 측정 | SUS·자기효능감·κ/ICC·환각률 보고서 |
| P3-003 | 재귀학습 승인 export·동의 감사 | consent/PII/IAA/legal 승인과 withdrawal log 종결 |
### B3-신규 (2026-07-27 전수 E2E 순회에서 제기 → **2026-07-27 소유자 전건 결정 완료**)
## 비차단 제품·정리 후보
> 소유자(윤찬)가 질문 도구로 7건 전건 결정(2026-07-27). 결정 내용과 구현 상태:
| ID | 다음 행동 | 완료 조건 |
|---|---|---|
| VOICE-001 | s2s 2차 PoC | 20턴 비교 보고서와 채택/폐기 판단 |
| FASTLOOP-001 | 외부 fallback | 동의·전송 가드·관측을 포함한 테스트 |
| FINETUNE-001 | 재귀학습 fine-tuning pipeline | 동의·데이터셋 gate 뒤 pipeline과 few-shot 자동갱신 범위 검증 |
| RETENTION-001 | health retention | raw 90일 / rollup 365일과 수동 escalation 검증 |
| EVALHOOK-001 | fast-loop `eval_hook` 사용 정책 | hook 활성화 조건과 관측 경계의 소유자 결정 |
| NODE-001 | Node gateway 전환 후보 | endpoint conformance, `TurnEvaluationPayload` adapter, admin mapper의 작업 패킷과 회귀 |
| MIGRATION-001 | idempotent DB migration runner | init SQL과 runner의 역할 경계·인프라 영향·소유자 승인 |
| OPS-001 | 운영 티켓 자동 분류 | 관리자 승인·audit 경계를 갖춘 구현 패킷 |
| E2E-001 | 전 기능 E2E 후속 분류 | focused 범위의 locator 18건 교정·keyboard 1건 해결 뒤 `npm run e2e`에서 범위 밖 케이스를 실행하고, 실패별 재현·원인·수정 명령을 분류한다. 과거 sweep 실패를 오늘 결함으로 단정하지 않으며 실API/DB·장치 검증은 fixture 결과와 분리한다. |
- [x] **설정 화면 아바타 변경 UI** — 결정: **설정에 추가**. **구현 완료(RED→GREEN, 대시보드 144차)**.
- [x] **동의 관리·철회 UI와 재동의 흐름** — 결정: **전체 흐름 구현**(설정 동의 섹션 + 철회 +
재동의 경로). **구현 완료(RED→GREEN, 대시보드 144차)**.
- [x] **신규 학습자 첫 추천 난도** — 결정: **기초 우선 추천**(회기 기록 없는 신규 학습자는
기초 난도 페르소나를 첫 추천으로). **구현 완료(RED→GREEN, 대시보드 144차)**.
- [x] **학생 분석 테이블** — 결정: **지금 가상화 도입**. **구현 완료(RED→GREEN, 대시보드 144차)**.
- [x] **저작 모드 좌측 레일 잔존 마크업** — 결정: **정리**(저작 모드에서 레일 미렌더). **구현 완료(RED→GREEN, 대시보드 144차)**.
- [x] **dev-login 재로그인 자동 재승인** — 결정: **현행 유지**(dev/E2E 편의 의도 확정). 코드 변경 없음.
- [x] **감정 밸런스 타임라인 차트**(결함 로그 #21) — 결정: **지금 구현**(평가 데이터 생산→저장→
리드모델→차트 연결). **구현 완료(RED→GREEN, 대시보드 144차)**.
## 완료 기록 경계
> 근거·상세: `docs/ops/e2e-full-sweep-2026-07-27.md` 발견 결함 로그 #2·#4·#6·#14·#15·#16·#21.
2026-09-01 A 경로의 NAS 배포, migration 20/21/22 적용, OAuth 실계정 회기 E2E와 당시 backup sidecar 첫 실행은 완료 이력이다. B3 및 B3-신규의 소유자 결정과 완료된 구현도 archive snapshot에 보존한다. `CLEANUP-001`은 84 regular source와 supplementary 4 files·junction 4개의 대조 뒤 `outputs/local-history/retained-sources-2026-09-07/`으로 가역 보존 이동을 마쳤고 영구 삭제는 0건이다. `RUNTIME-001`은 Windows public-runtime/watchdog scripts를 복구 경로로 보존하기로 결정했으며 이 PC의 두 Disabled Scheduled Task는 운영 서버 다운을 뜻하지 않는다. `REFACTOR-001``sessionViewModel` 분리와 `voiceCapture` 책임 유지를 수용했고, lifecycle setter bag hook은 채택하지 않았다. Space retry→pause와 empty-audio fixture를 교정했으며, `learnerFeedbackEnabled:true` 누락으로 숨던 review feedback UI는 fixture 보완 뒤 focused login→review 1 passed(exit 0)로 확인했다. 제품 feedback guard는 유지했다. `REFACTOR-002`는 repository와 persistence values로 평가 저장 경계를 분리했다. 열린 백로그에 완료 이력을 다시 적지 않는다.
---
## 확인 명령
## B4. 외부 거버넌스 — 한신대/데이터 steward 서면 증거
- [ ] **C-001 자살사고 케이스 위기 반응 프로토콜 임상 승인** — SAMHSA SAFE-T, NIMH Youth Outpatient
BSSA, NICE NG225, 보건복지부 109를 반영했다. P1 합성 사례 6건·v2 스키마·상태별 canonical fail-closed 판정기와
런타임/carry-over/output `ideation_stage <= 3` 이중 방어를 포함한 기술 사전검증은 완료했다. 생성·스트림
실제 경로 P1 6/6, focused 90 passed, 런타임 패키지·작업트리 SHA 일치까지 확인했고, 개선관리 완료본의
`C-001 외부검토` 시트는 현재 `pending-valid`·`검토`다. 사례별 판정·근거·수정 요구·재검토·검토일 30개,
청소년 특화 답변 6개, 임상 검토자 이름·소속 기관·자격·검토일·결정 근거·서면 증거/해시·검토 프로토콜 버전·
사례 세트 해시가 모두 기록되고 canonical 판정기가 `review_complete=true`를 반환하기 전에는 완료로 닫지 않는다. 검토 원장:
`docs/ops/clinical-crisis-protocol-review-2026-08-27.md`.
- [ ] **한신대 데이터/SSO 거버넌스 게이트** — SSO claim mapping, 추가 축어록 수급, 미성년 원본 활용동의, 개인정보
처리방침. 로컬 골격 `docs/ops/hanshin-data-governance-gate.md` 준비됨, 외부 서면 증거 필요. (소유자 보류 중.)
- [ ] **L1 스택 정합성·저작권 등재 문서 반영** — 백엔드는 Node.js 교체 가능성 우선으로 결정됐지만, 신청서/저작권
등재 문서에는 현재 FastAPI 유지 사유와 계약 우선 전환 계획을 반영해야 한다. 로컬 결정 기록은
`docs/decisions/backend-node-transition.md`; 외부 제출/등재 증거 필요.
---
## Phase 3 파일럿 게이트 (실참여자 필요)
---
## 배포 파이프라인 재정립 + A 경로 (NAS prod 실배포 → E2E 검증) — 2026-09-01 재부팅 후 실행
> **소유자 지시(2026-08-31)**: 배포는 Forgejo(git.chanpaca.net) 중심, NAS Production 대상, github은 private 백업/미러,
> 이 PC는 개발 전용, Cloudflare는 서빙용만·tunnel 제거. **A 경로로 진행**: 실제 prod 배포 준비 → 실배포 → 로그인·작동 E2E 확인까지.
> 2026-09-01 재부팅 후 아래를 순서대로 진행. 지침: `docs/ops/deployment-pipeline.md` · 핸드오프 `docs/ops/handoff-goal-production-2026-08-29.md`.
### 재부팅 직후 (truth 재확인) — **2026-09-01 완료**
- [x] OS/셸/경로/도구 확정, git HEAD `be08c0b5`·Forgejo master 일치·미커밋 71/untracked 27 확인
- [x] live public API health 200(`prod`·db/engine true)·NAS `vignette-prod` 5컨테이너 확인 — `scripts/vignette-pipeline.py --check` ALL OK
- [x] NAS prod 배포 기계장치 확정: `192.168.0.38:/volume1/docker/vignette-prod`(compose+`private/env.nas` immutable image pin), Forgejo http clone(git 컨테이너)→NAS 로컬 docker build로 고정. cloudflared는 NAS 호스트 상주(`private/cloudflared-host.yml`, api-vignette만 ingress) — public 도메인은 이 PC와 완전 분리 확인
### A-1. 배포 후보 GREEN 정리 — **2026-09-01 완료**
- [x] 미커밋 diff를 4개 논리 커밋으로 좁게 stage(회기 연속성 API·케이스 UI·NAS infra·docs/증거), tmp/outputs 제외
- [x] web typecheck·build PASS / API 1090 passed·gateway 71 passed / SSOT checker·design SSOT·api-types PASS
- [x] Forgejo master push, 최종 배포 SHA `dce85620` 고정(github 미러 동기화 포함)
### A-2. NAS prod 실배포 — **2026-09-01 완료**
- [x] prod 전용 절차 실행: pre-deploy dump `cab6826b…`(11,036,928B·TOC 1783)+env 백업+구 이미지 보존(롤백 경로), 외부 볼륨 `pgdata`/`apiuploads` 무접촉
- [x] 중복 활성 회기 14쌍·30행을 최신 유지 정책으로 종료(영수증 `receipts/stale-active-cleanup-20260901.csv`, 삭제 0) 후 마이그레이션 20/21/22 온라인 적용. mig22의 `name[]=text[]` 캐스트 결함 발견→`5a74302e` 교정. master에 게이트웨이 openai provider가 누락된 회귀 발견→구 운영 이미지에서 이식(`dce85620`, 테스트 71 passed)
- [x] readiness: public health `ok·db/engine true·openai OK`, OpenAPI 200, `/auth/me` 401, dev-login 404, 컨테이너 healthy, 데이터 보존(app_user 1380/sessions 618/turns 1743/case_profile 565 전후 동일)
### A-3. 로그인·작동 E2E 검증 (실브라우저) — **2026-09-01 완료 (전건 GREEN)**
- [x] public 로그인 화면 렌더(단일 Google CTA·콘솔 오류는 예상된 401 1건) + OAuth redirect_uri `https://api-vignette.chanpaca.net/auth/callback`·PKCE S256 검증 — headful 브라우저
- [x] 실제 Google 계정 로그인→callback→회기·턴 생성 완료 — 첫 시도에서 NAS env의 OAuth secret 오류(`invalid_client`)를 발견해 동작 실적 secret으로 교정 후 성공. headful 폐루프: 로그인(소유자 직접 입력)→기존 회기 20건 표시(이전 데이터 보존)→활성 회기 가드·시간 만료 fail-closed 확인→stale 회기 정식 종료→**새 사례(fresh case) 회기 생성(mig22 운영 실증)**→pre pulse 잠금→실턴 SSE·AI 내담자 응답→종료→리뷰 생성. DB 실증: sessions 618→619·turns 1743→1745·case_profile 565→566. 증거: `ops/evidence/prod-e2e-review-2026-09-01.png`
- [x] 배포 WEB: Pages production `9224a64b`, 새 entry `index-BFKGXJQi.js` 200·정상 MIME, 직전 entry `index-Bm30Ozeb.js` 200 보존(이전 세대 자산 175개 보존). 관리자 콘솔 렌더·서비스 상태 5/5 확인
- [x] **DB 백업 예약 실행 운영화(1단계)**`db-backup` 사이드카(일일 pg_dump→`pg_restore -l` 검증→원자 게시, 14일 보존)를 정식 스택에 추가, 첫 자동 덤프 11,202,927B 성공. owner 비번-볼륨 불일치도 env와 일치화. 실패 알림·off-host 암호화 복제·restore drill 주기는 후속 게이트로 유지
### 잔여(재부팅 외) — 그대로 유지
- [ ] 실배포 완료 전까지는 아래가 열린 게이트 유지: `public API/DB·아바타 정식 승격`, `G8 실DB/public 사람 게이트`,
`음성 캐스케이드 live`, `DB 백업 운영화`, `vnet.18ka.net live`, `claude_cli↔Anthropic API live`, `Compose infra/.env`,
`B1 티켓 자동 분류`, `C-001`, `한신대 거버넌스`, `L1 등재`, `Phase 3`, 재부팅 후 watchdog smoke/REQ-008
- [ ] **20명 교육용 파일럿 운영 / 효과성·KPI 측정(SUS·자기효능감·κ/ICC·환각률) / 재귀학습 데이터셋 approved 산출 /
개인정보·동의 감사.** 문서·checker·dry-run exporter는 준비됨(`docs/phase3/*`, `scripts/check-phase3-artifacts.py`,
`scripts/export-recursive-dataset.py`, `scripts/export-phase3-kpi.py`). 공식 문항 확정, 통계 검정, 실험/통제군 배정,
실제 approved evidence는 파일럿·steward/legal 검수·IAA 통과 시점의 GATE다.
```powershell
python -X utf8 scripts/check-dev-dashboard-ssot.py --json
git diff --check
```

View file

@ -0,0 +1,37 @@
# Codex 워커 정의 참조 사본
> [`AGENTS.md`](../../../AGENTS.md) B.8(Codex 실행 규격)의 워커 역할 정의다. **런타임 위치는 `~/.codex/agents/*.toml`(글로벌)** 이며,
> 이 디렉터리는 다른 PC·협업자가 같은 워커 계층을 설치하도록 두는 참조 사본이다.
## 설치
```powershell
Copy-Item -LiteralPath docs\ops\codex-agents\*.toml -Destination "$env:USERPROFILE\.codex\agents\" -Force
```
`~/.codex/config.toml`에는 다음이 있어야 한다(현재 워크스테이션 값).
```toml
[agents]
enabled = true
default_subagent_model = "gpt-5.6-terra"
default_subagent_reasoning_effort = "medium"
max_concurrent_threads_per_session = 8
```
## 왜 `.codex/agents/`(프로젝트 스코프)에 두지 않는가
- Codex는 신뢰된 프로젝트의 `.codex/agents/`를 글로벌보다 우선해 읽는다고 안내하지만, 프로젝트 스코프 커스텀 서브에이전트가
스폰되지 않는 이슈 [openai/codex#26408](https://github.com/openai/codex/issues/26408)이 2026-09-06 기준 열려 있다(0.137.0에서 보고, 수정 버전 없음).
- 같은 이름을 `.codex/agents/`에 두면 글로벌 정의를 가려 워커가 아예 안 뜰 수 있다. 이슈가 닫히고 실제 스폰이 확인되면 이 사본을 `.codex/agents/`로 옮긴다.
## 파일
| 파일 | 역할 | 모델 | 샌드박스 |
|---|---|---|---|
| `explorer.toml` | 코드베이스 탐색·근거 수집 | `gpt-5.6-luna` | read-only |
| `researcher.toml` | 웹·문서·논문 스위핑 | `gpt-5.6-luna` | read-only |
| `worker.toml` | 작업 패킷 기반 구현 | `gpt-5.6-terra` | 상위 상속 |
| `verifier.toml` | 검증 명령 실행·원문 보고 | `gpt-5.6-luna` | 상위 상속 |
글로벌 파일을 고치면 이 사본도 같은 내용으로 맞춘다(B절 동기화 규칙).

View file

@ -0,0 +1,19 @@
name = "explorer"
description = "읽기 전용 코드베이스 탐색 워커. 호출 경로·영향 범위·재현 절차를 추적하고 파일:줄 근거를 수집해 보고한다. 오케스트레이터가 탐색과 근거 수집을 위임할 때 사용."
model = "gpt-5.6-luna"
model_reasoning_effort = "medium"
sandbox_mode = "read-only"
developer_instructions = '''
(Astra/Sol) . . .
- . .
- . ( :, , , ) . "추정" .
- (rg, ) . .
- . .
1. ( )
2. : :
3. /
'''

View file

@ -0,0 +1,21 @@
name = "researcher"
description = "웹 검색·공식 문서·upstream 소스·이슈/PR·논문을 스위핑해 출처와 함께 보고하는 읽기 전용 조사 워커. 대규모 조사, 웹서핑 조사, 논문 스위핑, 레퍼런스 비교가 필요할 때 주제별로 여러 개 병렬로 사용."
model = "gpt-5.6-luna"
model_reasoning_effort = "medium"
sandbox_mode = "read-only"
developer_instructions = '''
(Astra/Sol) . . .
- . . .
- : URL, ·, · . "미확인" . "지원한다" .
- (, API, , , ) .
- .
- · , , , .
1. (5 )
2. ( )
3. /
4.
'''

View file

@ -0,0 +1,20 @@
name = "verifier"
description = "빌드·테스트·린트·재현 명령을 실행하고 통과/실패 수와 오류 원문을 그대로 보고하는 검증 워커. 판정은 하지 않는다. 오케스트레이터가 워커 결과를 평가하기 전 증거 수집에 사용."
model = "gpt-5.6-luna"
model_reasoning_effort = "low"
developer_instructions = '''
(Astra/Sol) . . .
- ( · ). .
- : , , , . flaky .
- . : / , , ( ), , (, , ).
- .
1. ()
2. : / ,
3. : +
4.
5.
'''

View file

@ -0,0 +1,22 @@
name = "worker"
description = "오케스트레이터가 확정한 설계와 작업 패킷(목표/비목표/계약/경계/완료 기준/검증 명령)대로 구현·리팩터·테스트를 작성하는 개발 워커. 설계가 확정된 구현 작업에 사용."
model = "gpt-5.6-terra"
model_reasoning_effort = "high"
developer_instructions = '''
(Astra/Sol) . . .
- ··· . .
- . . .
- fallback, silent catch, mock , , . .
- , , TODO, . .
- .
- ·push .
1.
2. diff ( )
3. (/ , )
4. /
5. ( )
'''

View file

@ -1,7 +1,7 @@
# 배포 파이프라인 (Deployment Pipeline)
> 작성: 2026-08-31 · 문서 소유: docs/ops (SSOT 방향)
> 상태: **초기 이관 완료 / NAS 자동배포 구성 잔여**
> 작성: 2026-08-31 · 2026-09-07 현행화 · 문서 소유: docs/ops
> 상태: **2026-09-01 NAS 수동 실배포 완료 / NAS 자동배포 구성 잔여**. 당시 증거이지 현재 live 상태 확인은 아니다.
## 목표 아키텍처 (소유자 지시)
@ -14,7 +14,7 @@ Forgejo git.chanpaca.net ────── 소스 SSOT (이관 완료 2026-08
│ └─ 배포 ──▶ NAS Production (vignette-prod)
│ docker-compose.nas.yml (SSOT-host=nas, runtime-class=production)
Cloudflare = 서비스 공개 서빙용만 유지 (tunnel 제거 대상)
Cloudflare tunnel은 소유자 지시에 따라 제거 대상이다. 다만 현재 NAS ingress가 이를 경유하므로, 대체 ingress의 보안·가용성·OAuth 경계를 검증하기 전에는 제거하지 않는다.
```
- 나는 이 PC를 **개발 전용**으로만 사용한다. 배포 대상은 반드시 **NAS Production**이다.
@ -29,7 +29,7 @@ Cloudflare = 서비스 공개 서빙용만 유지 (tunnel 제거 대상)
| `forgejo` | ssh://git@git.chanpaca.net:2222/yunchan/vignette.git | **소스 SSOT·배포** | ✅ 이관 완료 |
- Forgejo 레포: `yunchan/vignette` (id 14, public, default master)
- 이관 기준 커밋: `be08c0b5` (master 최신, 로컬 HEAD와 일치)
- 2026-09-01 배포 당시 기준 커밋: `dce85620`. 현재 master/로컬 HEAD는 실행 전에 별도로 확인한다.
- Forgejo 접근: SSH 키(`~/.ssh/id_ed25519`, Host `git.chanpaca.net``192.168.0.38:2222`) · API 토큰(`infra/.env.deploy``FORGEJO_API_TOKEN`, gitignore)
## 2. 로컬 브랜치 규칙
@ -37,21 +37,21 @@ Cloudflare = 서비스 공개 서빙용만 유지 (tunnel 제거 대상)
- `master` = Forgejo `master`와 동기화 (배포 기준선)
- 기능/scoped 브랜치 = 개발용, 검증 후 master로 합침
## 3. NAS Production 배포 흐름 (구성 예정)
## 3. NAS Production 배포 흐름 (수동 절차 실증, 자동화 구성 예정)
```
[이 PC] git push forgejo master
[Forgejo] 승인된 master 후보 SHA
[Forgejo] master = be08c0b5 (SSOT)
[NAS] git 컨테이너로 해당 SHA clone·검증
[NAS 192.168.0.38] git pull origin master (vignette-prod 소스)
▼ docker compose -f docker-compose.nas.yml up -d --build
vignette-prod (api/web/engine/db/proxy) 재기동
[NAS] API/Web build → online migration → docker compose 재생성
[Cloudflare] 서빙 (api-vignette.chanpaca.net / vignette.chanpaca.net)
vignette-prod (api/web/engine/db/proxy) readiness·데이터 보존 검증
[Cloudflare] 현재 공개 서빙 경로 유지 (대체 ingress 검증 뒤 tunnel retirement)
```
NAS 배포는 Forgejo에서 직접 pull하는 방식으로 전환하고, cloudflare tunnel에 의존하지 않는다.
NAS 호스트에는 git이 없으므로 git 컨테이너로 Forgejo에서 clone한다. 2026-09-01 수동 실배포는 이 흐름으로 `dce85620`을 검증했다. 자동배포 hook과 tunnel retirement는 별도 열린 게이트다.
## 4. 검증 게이트 (배포 전)
@ -93,6 +93,6 @@ docker compose -f docker-compose.nas.yml --env-file private/env.nas up -d
- [ ] Forgejo master 기준 NAS 자동배포 후크(수동 절차는 §5로 실증 완료)
- [x] Forgejo가 SSOT임을 문서에 반영, github은 private 미러 유지
- [ ] cloudflare tunnel 제거 검토 — 현재 api-vignette 서빙이 NAS 호스트 상주 cloudflared 터널 경유라 제거 시 대체 ingress 필요(소유자 결정)
- [ ] cloudflare tunnel 제거 또는 유지 결정 — 현재 `api-vignette`는 NAS 호스트 상주 cloudflared 터널 경유다. 대체 ingress의 보안·가용성·OAuth 경계가 검증되기 전에는 제거하지 않는다.
- [ ] 배포 시 NAS 접근 자격(SSH 키/배포 계정)을 `.env.deploy`/시크릿으로 관리
```

View file

@ -1,5 +1,7 @@
# E2E 전수 순회 체크리스트 — 2026-07-27
> **역사 baseline**: 이 문서는 2026-07-27 당시 전수 순회 인벤토리와 해당 spec의 출발점이다. 현재 남은 직렬 E2E 재현·분류는 [`TODO.md`](../TODO.md)의 `E2E-001`을 따른다.
> **목적**: IA·버튼 단위로 온보딩→가입→학습자→교수자→관리자→설정 전 페이지의 모든 기능을 순회하며
> (1) 의도와 다른 동작, (2) 오류, (3) 나쁜 UI/UX를 잡아낸다.
> **방식**: RED → GREEN. 각 항목은 검증 수단(기존 spec / 신규 spec / 탐색 순회)을 명시하고,

Binary file not shown.

After

Width:  |  Height:  |  Size: 616 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 942 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 942 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 272 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 523 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 607 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 462 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 380 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 521 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 523 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 922 KiB

View file

@ -1,350 +0,0 @@
# 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로 최소 아래를 한 번에 다시 실행한다.
```powershell
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.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와 승인 범위를 먼저 보여줘.

View file

@ -1,95 +0,0 @@
# 전 저장소 리팩터 거버넌스 실행 기록 — 2026-07-15
> 상태: **DONE (증거 기반 구조 개선 패스)**
> 방법: `refactor-governance` Edit Pass(P1→P8), 동작 보존 우선
> 권위 상태: `docs/dev_dashboard.html`과 동기화. 기능 로드맵이나 외부 실증 게이트를 대신하지 않는다.
## 1. 목표와 불변 조건
목표는 파일 길이를 기계적으로 줄이는 것이 아니라, 변경 시 서로 어긋날 수 있는 계약·권한·캐시·표시 규칙을 한 소유자로 모으고 반복 IO와 초기 번들 비용을 줄이는 것이었다.
불변 조건:
- 인증/RBAC/RLS, 감사 로그, PII 마스킹, durable/degraded 판정은 바꾸지 않는다.
- API 응답과 생성 OpenAPI 타입, 엔진 packet, DB 스키마의 의미를 바꾸지 않는다.
- 세션 턴 순서, 평가 재시도, 알림 수신자, 화면의 오류 문구와 빈 상태를 보존한다.
- 기존 사용자 작업이 섞인 dirty worktree를 되돌리거나 일괄 포맷하지 않는다.
- 공통화는 오류 순서·캐시 fallback·side effect가 같은 경우에만 한다.
## 2. 감사 범위와 최초 증거
아카이브를 제외한 생산 코드, 테스트, 인프라, 활성 문서를 전수 스캔했다.
| 영역 | 파일/줄 기준 | 중점 검사 |
|---|---:|---|
| `apps/api/app` | 100 files / 45,112 lines | 장기 함수·인자 묶음·SQL 반복·캐시·LLM 감사·세션 IO |
| `apps/web/src` | 74 files / 46,935 lines | 대형 라우트·중복 view model·API 타입·초기 번들·디자인 소유권 |
| `apps/web/e2e` | 25 files / 12,381 lines | 동작/레이아웃/DB 증거 게이트 |
| `scripts`, `infra` | 37 files / 7,430 lines | 런타임 스키마와 운영 경로 중복 |
| 활성 `docs`, `data` | 111 files / 42,955 lines | SSOT drift·검증 숫자·현재/아카이브 경계 |
최초 정적 결과는 Ruff 16건, 생산 코드 중복 15 clones / 327 lines(0.60%), 초기 JS 678.88 kB(gzip 195.75 kB), 초기 CSS 328.49 kB(gzip 51.46 kB)였다. Python 함수 1,903개 중 60줄 이상 135개·인자 7개 이상 26개, TS/TSX 함수 1,426개 중 60줄 이상 61개가 후보였다. 이 후보는 길이만으로 수정하지 않고 호출·중복·소유권 증거를 다시 확인했다.
## 3. 적용한 패치 그룹
| 그룹 | 변경 | 단일 소유자/효과 |
|---|---|---|
| P1 삭제·정적 정리 | Ruff 16건 제거, 미사용 컴포넌트·의존성·죽은 export 정리, Knip 게이트 도입 | 참조 0인 코드와 선언 drift 제거 |
| P2 shape 안정화 | dataset manifest, voice context/prosody/turn/audio를 명시적 input object로 전환 | 긴 positional/keyword 묶음의 의미를 타입 이름으로 고정 |
| P3 계약 SSOT | 평가 write 계약, OpenAPI 생성 타입, `runtime_schema.py`, format/runtime diagnostics를 단일화 | API/DB/runtime/UI 미러 선언 drift 차단 |
| P4 캐시 | KB process cache의 key·수명·invalidate 경로를 한 모듈에 고정 | admin sync 뒤 stale source pack 방지 |
| P5 workflow | KB/persona source workflow, LLM generate+audit, 알림 수신자, managed session sync, admin normalization 공통화 | 같은 side effect·오류 의미를 한 구현으로 통합 |
| P6 성능 | `App.tsx` 전 라우트 lazy loading, 공통 Suspense, 세션 상태/턴 batch read | 초기 payload 감소, missing-evaluation 복구 1+2N query를 3 query로 축소 |
| P7 인프라 | dev runtime schema와 `infra/db/init` 정의 정합, prod DDL fail-closed, E2E fixture/single-run 2단계 실행 | 개발 자동보강과 운영 migration 역할 분리, 단일 DB/engine 포화 방지 |
| P8 UI/디자인 | Surface/AppShell/theme/페르소나 시각 view model SSOT, 대형 화면의 순수 모델·음성 캡처 분리, raw-color 예산 게이트 | 공통 primitive와 화면 예외의 소유권을 테스트 가능한 규칙으로 고정 |
추출된 주요 경계:
- 백엔드: `services/llm_audit.py`, `runtime_schema.py`, 평가/세션/알림/KB의 named contract와 batch loader.
- 프론트: `lib/personaViewModel.ts`, `lib/runtimeDiagnostics.ts`, `pages/admin/dataNormalization.ts`, `pages/persona-studio/model.ts`, `pages/learner-home/model.ts`, `pages/session-review/model.ts`, `pages/session/voiceCapture.ts`.
- 거버넌스: `check:dead-code`, `check:duplication`, `check:design-ssot`, `check:api-types`.
## 4. 전후 측정
| 지표 | 이전 | 현재 | 판정 |
|---|---:|---:|---|
| 생산 코드 중복 | 15 clones / 327 lines / 0.60% | 1 clone / 15 lines / 0.03% | 95.4% duplicated-line 감소, threshold 0.05% 게이트 |
| 초기 JS | 678.88 kB / gzip 195.75 kB | 241.49 kB / gzip 77.13 kB | gzip 60.6% 감소 |
| 초기 CSS | 328.49 kB / gzip 51.46 kB | 28.59 kB / gzip 6.45 kB | gzip 87.5% 감소 |
| `Session.tsx` | 4,002 lines | 3,491 lines + `voiceCapture.ts` | 음성 브라우저 경계 분리 |
| `LearnerHome.tsx` | 2,053 lines | 1,857 lines + 222-line model | 표시 계산을 순수 모델로 분리 |
| API 단위 테스트 | 393 | 400 passed | 신규 구조 회귀 포함 |
| Gateway 단위 테스트 | 27 | 29 passed | packet/model 계약 포함 |
| Playwright 수집 | 214 / 20 files | 215 / 20 files | 현행 목록 동기화 |
## 5. 의도적 비추출과 통제된 예외
1. `session_persistence.py`의 15-line clone 한 건은 유지한다. 두 principal-aware acquire 경로는 표면 구조만 같고 예외 시 cache fallback과 durable 판정이 다르다. 합치면 실패 의미가 숨겨지므로 `jscpd` 전체 threshold 안의 근거 있는 제외다.
2. `Session`, `Admin`, `PersonaStudio`, `Professor`의 라우트 컨테이너는 여전히 크지만 현재 생산 TS/TSX 중복은 0이다. 화면별 상태 전이까지 무리하게 generic hook/component로 만들면 읽기 비용과 prop surface가 늘어난다. 새 기능이 독립 상태·독립 E2E를 가질 때 해당 slice를 추출한다.
3. 세션 dark stage와 아바타/인증 아트의 국소 raw color는 전역 토큰으로 승격하지 않는다. 공통 `ui.css`/`shell.css`는 raw color 0개를 강제하고, 예외 파일은 2026-07-15 기준 수치 이상 증가하지 못하도록 고정 예산을 둔다.
4. 전체 215개 Playwright에는 실제 DB·엔진·provider가 필요한 single-run 시나리오가 섞인다. 기존 direct runner는 로컬에서 12 workers로 세 프로젝트를 동시에 실행해 188 passed/27 resource-timeout을 만들었고, single-run을 뺀 12-worker fixture 단계도 145/166 뒤 21 request timeout을 재현했다. `npm run e2e`를 fixture desktop/mobile `workers=4` 단계 뒤 DB/engine `workers=1` 단계가 시작되는 구조로 바꿨다.
5. 관리자 티켓 큐는 최대 120개 복합 카드를 한 번에 다시 그리며 액션 pending 상태까지 페이지 루트가 소유했다. 카드 렌더 상한을 24개로 두고 나머지는 서버 검색/필터로 탐색하게 했으며, 중복 요청 잠금과 시각 pending은 `TicketActions`가 카드 단위로 소유한다. 네이티브 입력 dispatch와 React 상태 렌더를 분리해 해결/중복 연결 실제 클릭 4건이 desktop/mobile에서 통과한다.
6. Settings 초기 로드는 React 개발 모드 effect 재실행으로 같은 GET 두 개가 경합할 수 있었고, 늦게 끝난 응답이 사용자가 방금 바꾼 엔진 모드·모델을 원래 값으로 덮어썼다. 요청 세대 번호를 추가해 최신 로드만 상태를 반영하고 unmount된 요청은 무효화했다. 기존 engine settings E2E가 실제 PATCH body와 복구까지 고정한다.
7. AI 튜터 DB 영속화는 정상 provider 응답이 독립 실행에서도 138.5초 걸려 기존 150초 테스트 상한과 여유가 11초뿐이었다. 기능 timeout을 숨기지 않고 동일 파일의 장시간 실엔진 기준인 240초로 테스트 예산을 조정했으며, 최종 전체 직렬 실행에서는 2.1분에 ready 응답·source pack·DB history·reload 증거를 모두 통과했다.
## 6. 검증 증거
- `ruff check app` — passed.
- `pytest -q app`**400 passed**, Starlette `python_multipart` PendingDeprecationWarning 1건.
- `pytest -q engine_gateway`**29 passed**, 같은 외부 의존 warning 1건.
- `npm run typecheck` / `check:api-types` / `check:design-ssot` / `check:dead-code` / `check:duplication` — passed.
- `npm run build` — 109 modules, initial JS 241.49 kB(gzip 77.13 kB), initial CSS 28.59 kB(gzip 6.45 kB).
- `npm audit --audit-level=high` — 0 vulnerabilities.
- `npx playwright test --list`**215 tests / 20 files**.
- `npm run e2e:parallel`**166 passed** (desktop 83 + mobile 83, workers=4).
- `npm run e2e:single-run`**49 passed** (DB/engine/provider 직렬, 23.2분).
- 전체 Playwright 최종 판정 — **215 / 215 passed**.
- `git diff --check` — passed; checkout 정책에 따른 LF→CRLF 경고만 존재.
## 7. 앞으로의 변경 규칙
- 새 DTO/packet은 OpenAPI·schema·named adapter 중 한 곳만 원본으로 둔다.
- 새 캐시는 key, SoR, invalidate, TTL, 실패 시 durable/degraded 의미를 함께 정의한다.
- 새 공통화는 `check:duplication`의 실제 clone 또는 둘 이상의 동일 side effect가 증거일 때만 한다.
- 새 페이지 색은 먼저 의미 토큰을 사용하고, 국소 예외면 이유와 raw-color 예산을 함께 갱신한다.
- 라우트 컨테이너 추출은 독립 입력/출력, 독립 상태, 독립 테스트가 생긴 뒤 한다. 파일 길이만으로 분리하지 않는다.