G8 실제 rollback 증명 종료와 비-secure origin 회기 리뷰 크래시 수정
G8 마지막 게이트인 receipt-bound 실제 image rollback을 격리 NAS vignette-preview-20260807 에서 실행해 종료했다. Gate6 계약 정정: 감사 대상 current API 이미지가 com.docker.compose.project/service/version image label 을 갖고 있어 "helper 의 compose label 0개" 계약은 감사되지 않은 다른 이미지를 쓰지 않는 한 성립하지 않는다. 계약을 key 부재가 아니라 소속(membership) 으로 바꿔 launch-nas-preview-g8-helpers.py 에 구현했다. image 상속 label 을 baseline 으로 읽고 container 의 모든 compose label 이 baseline 과 같거나 선언된 격리 override 인지 검사하며, 최종 project 는 target 이 아니고 service 는 api/web/db/proxy 가 아니어야 한다. docker run argv 에 target label 을 주입하면 fake-runner 테스트가 먼저 깨진다 (37/37). 실행 결과: - rollback-old receipt nas-g8-723eeef22eab05e63e3fafb0 -> 79ec../c530.. - restore-current receipt nas-g8-2738846cf2cf4fbe8ce0fc26 -> 52e0../6fdb.. - release gate/approval 각 2회 멱등, audit.ci_lifecycle_event rollback/executed 2, audit.ci_human_approval_event authorize_rollback 2, silent auto-promotion 0 - HMAC journal 6-record 체인 검증, health 3/3, OpenAPI 126, auth 401, Web 200 - helper 0, listener 0, 비밀 env 파기. down/volume rm/prune 미실행, 공개 런타임 미접촉 - 계획했던 Windows SSH 터널은 NAS sshd 가 direct-tcpip 를 거부해 사용할 수 없어 sshd 설정 변경 대신 같은 격리 계약의 NAS-side probe 컨테이너로 실행했다 비-secure origin 크래시 수정: 배포된 NAS 프리뷰(평문 HTTP, 비-localhost)에 회기 스펙을 돌려 24건 실패를 확인했고 원인은 하나였다. crypto.randomUUID 는 secure context 전용인데 제품 코드 18곳이 fallback 없이 호출했고 RuptureRepairCard 는 렌더 시점 호출이라 회기 리뷰 라우트 전체가 error boundary 로 떨어졌다. 릴리스 게이트 108/108 은 localhost 후보 스택에서만 돌아 이 경로를 밟은 적이 없다. src/lib/uuid.ts 의 randomUuid() 로 통일하고 fallback 도 crypto.getRandomValues 를 우선 사용해 idempotency key 의 예측 불가능성을 유지했다. 회귀는 insecure-context-uuid.spec.ts 6/6 으로 고정했다(직접 호출 0건 검사 포함). 이 수정은 아직 NAS 에 배포하지 않았다. 검증: API 898, gateway 58, executor 28, probe 11, helper launcher 37, release agent 21, ruff clean, web api-types/typecheck/build, SSOT FAIL 0, SSOT unit 5/5, dashboard E2E 10/10, 학생 폐루프 실 DB 브라우저 4/4(일회용 클론), crypto 수정 후 기존 스펙 회귀 70/70, 복원된 NAS 실제 브라우저 SSE->DB 리뷰 PASS. 부수 발견(열린 항목): 공개 API 가 engine=false 로 degraded 인데 워치독이 이를 감지하지 못한다. engine 판정이 게이트웨이 /health 의 ok 만 보고 claude readiness probe 를 돌리지 않기 때문이다. 같은 .env 와 같은 CLI 로 새 게이트웨이를 다른 포트에 띄우면 즉시 ready 이므로 상주 프로세스의 세션만 죽은 형태다. TODO A절과 대시보드에 기록했다. 이 커밋은 파일 단위로 담겼다. 위 파일들에는 이전 세션의 미커밋 G0~G8 작업이 함께 들어 있으며, hunk 를 쪼개면 대시보드/체커/TODO 정합성이 깨져 SSOT 체커가 실패한다.
This commit is contained in:
parent
76d0b9ae9b
commit
93dd8f82d7
22 changed files with 10057 additions and 473 deletions
542
docs/HANDOFF.md
542
docs/HANDOFF.md
|
|
@ -1,315 +1,287 @@
|
|||
# Vignette Handoff
|
||||
# Vignette G0~G8 완료 핸드오프
|
||||
|
||||
> Updated: 2026-07-31 KST. 새 세션은 이 문서와 `docs/DESIGN_CONCEPT.md`를 먼저 읽고 이어가면 된다.
|
||||
> Updated: 2026-08-07 KST (G8 실제 rollback 종료 반영)
|
||||
> Workspace: `D:\workspace\vignette`
|
||||
> 이 문서는 다음 에이전트가 **G0~G8 아홉 목표 중 마지막으로 남은 G7 외부 게이트를 닫기 위한** 실행 기준이다.
|
||||
|
||||
## 현재 상태
|
||||
## 0. 현재 상태 한 줄 요약
|
||||
|
||||
- Workspace: `D:\workspace\vignette`
|
||||
- 로컬 웹 기본 포트: `http://localhost:5173`
|
||||
- 로컬 API 기본 포트: `http://127.0.0.1:8000`
|
||||
- 로컬 engine gateway 기본 포트: `http://127.0.0.1:9099`
|
||||
- 공개 웹: `https://vignette.chanpaca.net`
|
||||
- 공개 API: `https://api-vignette.chanpaca.net`
|
||||
- 최신 앱 배포 소스: commit `12bdc594970be8fbd8727800c720d14d7d5b4e25` (`master`, origin push 완료). 사용자 제공 1536×1024 세션 시안 기반의 326px 컨텍스트 레일, 중앙 인물 무대·자막, 357px 라이브 코칭 레일, 116px 제어바와 투명 WebP 식물 장식을 포함한다.
|
||||
- 최신 Cloudflare Pages production deploy: 2026-07-31 final HEAD-linked deploy `https://f9b06388.vignette-b1q.pages.dev`(deploy metadata commit `32c0b97d`), branch `main`, custom domain entry `assets/index-CzevzoM7.js`. 세션 청크 `Session-BWMFTqgE.js`·`Session-D7DZVnh0.css`와 `session-botanical/leaf-1.webp`가 커스텀 도메인에서 올바른 MIME으로 200을 반환한다. 공개 로그인 실렌더, Google 버튼 2개 enabled, boot diagnostic 0, 브라우저 warning/error 0을 확인했다.
|
||||
- 최신 학습 세션 런타임: 실시간 내담자 응답은 `VIGNETTE_LIVE_CLIENT_PROVIDER=claude_cli` 전용 lane으로 evaluator와 분리하고, 응답 저장·SSE done 뒤 fast-loop 평가는 백그라운드 처리한다. 입력창은 생성·TTS 중에도 초안 작성과 보존이 가능하다. 로컬 `-UseHiggsVoice`는 설치된 `higgs-audio-v3-tts-4b`를 P1에 사용하지만 연구/비상업 라이선스 때문에 dev 전용이며, 공개 `/voice/health`는 `openai / gpt-4o-mini-tts`로 유지한다.
|
||||
- 관리자 AI 엔진 capability: 공급자 6종을 드롭다운으로 선택하고 설치·인증 상태에 따라 live 모델과 추론 강도만 선택한다. 공개 인증 E2E에서 Codex 7개 모델의 기본 `gpt-5.6-terra / medium`, Agy 11개 모델의 기본 `gemini-3.6-flash-high / high`를 확인했으며 설정은 저장하지 않았다. Anthropic API는 키가 없어 fail-closed unavailable이다.
|
||||
- Google OAuth 허용 이메일 도메인: `hs.ac.kr`, `twentyoz.kr`
|
||||
- 최신 백엔드 회귀: API `432 passed`, engine gateway `44 passed`; 프론트 typecheck·API type drift·production build와 세션 stream 1/1, voice draft/TTS 1/1 focused E2E가 통과했다.
|
||||
- X1 재귀학습 export 1차: `scripts/export-recursive-dataset.py` 기본 read-only dry-run, `--write-dataset` 명시 시에만 `ds.*` write, approved export는 steward/legal/IAA gate 없으면 거부.
|
||||
- C1 사례개념화 워크시트 2차: `SessionReviewResponse.caseWorksheet`와 리뷰 화면 편집 카드가 축어록 근거 기반 초안을 제공한다. 학습자 저장본은 `PUT /sessions/{id}/review/worksheet`로 `app.case_worksheet`에 저장되고, 이후 `GET /review`는 `saved_by_learner` 저장본을 자동 초안보다 우선 반환한다. 검증: focused backend 25 passed, API typecheck/build, 로컬 API smoke `saved_by_learner:local smoke saved worksheet`. 임상 루브릭, AI 추출/채점, 교수자 검수는 후속.
|
||||
- C3 이론모드 1차: `theory_mode`가 세션·평가·생성 프롬프트까지 흐르고, `build_turn_messages`는 인간중심/CBT/통합 프레이밍을 엔진 메시지에 넣는다. CBT 체인·이론부합 루브릭·명시적 선택 UI는 후속.
|
||||
- H2 평가 정규화: 턴 평가의 대안발화도 `app.alternative_utterance`에 적재하고 리뷰 hydrate 시 `alternative_utterances`로 복원한다.
|
||||
- X2 예산 경고/저비용 평가 라우팅: `ADMIN_USAGE_BUDGET_USD`가 0보다 크면 `/admin/usage`가 budget 상태(ok/warn/exceeded)를 반환하고 `/admin`이 예산 배너를 표시한다. `EVALUATOR_FAST_MODEL`/`EVALUATOR_DEEP_MODEL`을 설정하면 fast/deep 평가 호출만 해당 모델 override로 gateway에 전달한다. 비우면 기존 gateway default 라우팅을 유지한다.
|
||||
- 2026-06-28 11:17 KST: `/admin/usage` prod 503 원인은 provider/model breakdown 쿼리의 `ORDER BY tokens_in + tokens_out`가 집계 alias가 아니라 원본 컬럼 참조로 해석된 PostgreSQL `GroupingError`였다. `SUM(tokens_in)+SUM(tokens_out)` 집계식 정렬로 수정했고, public API 프로세스 `127.0.0.1:8001`의 인증 HTTP smoke에서 `source=database`, `durable=true`, 200을 확인했다.
|
||||
- H4 LLM call audit + 동의 게이트: 상담 생성(generate/stream)과 fast/deep 평가의 외부 LLM 호출 직후 `audit.llm_call_log`에 provider/model/token/cost/inference_geo/latency metadata만 적재한다. prompt/completion 본문은 저장하지 않는다. 로컬 dev-login 실제 `/turn` smoke에서 `audit.llm_call_log`가 8→11로 3행 증가했다. 이번 패스에서 `app_user.consent_at` 기반 learner 동의 수락/철회 API와 세션 시작/voice dev persona 시작 하드게이트를 추가했다. 한국어 이름/기관 NER와 guardian/legal 서명 evidence는 후속.
|
||||
- RAG warm 동시성: E2E가 여러 세션을 빠르게 만들 때 BGE-M3 embedder가 동시에 지연 로드되어 `tqdm` lock 예외와 API health/dev-login timeout이 반복됐다. `rag.py` embedder load/encode를 process-wide `RLock`으로 직렬화하고, `sessions.py` warm task를 semaphore 1개로 제한했다. 최신 검증: `layout-visual-gate + session-layout` 11 passed.
|
||||
- 계약 SSOT 4차: `apps/web/src/lib/api.ts`의 수기 DTO 중 auth, persona, admin/user/engine, review leaf, teacher safety/growth leaf, learner sessions, session review/worksheet aggregate, teacher dashboard aggregate에 이어 `SessionStartResponse`, `SessionDetailResponse`, `SessionDetailTurn`까지 `apps/web/src/lib/api.gen.ts`의 `ApiSchema` alias로 전환했다. 백엔드 세션 응답 stage는 `StageLabel` enum(`라포|탐색|개입|정리`)으로 고정했고, generated optional/default 차이는 화면 form state, notification default helper, 배열 렌더링 fallback으로 흡수한다. `.github/workflows/api-contract.yml`은 PR/master push에서 `npm run check:api-types`로 FastAPI OpenAPI와 generated type drift를 막는다. 검증: `npm run check:api-types`, `npm run typecheck`, `npm run build`, `python -m pytest app/test_session_turn_persistence.py -q` 16 passed, learner/session-review/teacher/layout focused E2E 32 passed.
|
||||
- 턴 런타임 리팩터 2차: `turn_runtime.finalize_completed_turn`으로 REST submit/stream/voice WS의 `record_completed_turn` + `record_safety_event` 호출쌍을 공통화했다. gateway `/v1/stream` route 레벨에서 `token/done/error` SSE 프레임 contract tests를 추가했다. 검증: focused backend 40 passed.
|
||||
- Phase3 artifact checker 강화: `scripts/check-phase3-artifacts.py`가 CSV enum, KPI report 필수 field, approved export의 PII pass·κ/ICC·withdrawn exclusion·consent scope·file sha256을 검증한다. `app/test_phase3_artifact_checker.py` 5 tests 추가. 실제 파일럿 evidence와 steward/legal/IAA gate는 그대로 외부 의존이다.
|
||||
- 세션 종료 UX/dark theme SSOT 정리: 드래그형 `SlideToEnd`를 명시 확인 다이얼로그로 교체하고, Topbar/Settings theme 토글을 `lib/theme.ts` 단일 경로로 통합했다. 저장값이 없으면 dark를 기본으로 두고 앱 부팅 시 theme를 먼저 적용해 초기 flash를 줄인다. API 기본 preference `system`은 Settings에서 light로 오해하지 않고 현재 초기 테마를 따른다. 회기 리뷰·설정 포함 주요 페이지의 흰 섹션 잔재도 dark 작업면으로 맞췄다. 검증: `npm run typecheck`, `npm run build`, `npx playwright test e2e/layout-visual-gate.spec.ts e2e/settings.spec.ts e2e/session-review.spec.ts --project=chromium-desktop --project=chromium-mobile --project=chromium-single-run --workers=1` 22 passed.
|
||||
- 아바타 렌더 기준: 사용자 피드백에 따라 서연 P1과 P4~P7의 생성 이미지/PSD 래스터 파츠 연결을 전부 끊고, 모든 실제 화면과 dev 미리보기에서 기존 SVG 도형 기반 파라미터 리그만 렌더링한다. `RasterBust`와 래스터 원본·파츠는 재검토용으로 보존하지만 앱에서 로드하지 않는다. active session 데스크톱 스테이지의 `상태 라벨 / 아바타 / 현재 상태` 3행 구조는 유지하며, 사용자 제공 수채화 잎 5장은 인물 파츠가 아닌 독립 배경 장식으로만 사용한다.
|
||||
- M3 인증 claim 1차: Google/SAML/dev-login이 설정 기반 cohort map과 SAML cohort claim을 `cohort_ids`로 넘기고, 관리 사용자 `external_id`는 provider subject 기반(`google:`/`saml:`/`dev:`)으로 저장한다. 운영 SAML 서명검증·기관 claim schema·deprovisioning audit은 후속.
|
||||
- 페르소나 저작/P4~P7 적재 2차: `data/personas/P4.json`~`P7.json`을 저장소 관리 `PersonaCard`로 읽어 `materialize_seed_personas()`와 seed fallback catalog에 포함했다. 검증: `python -m pytest app/test_persona_review.py -q` 22 passed, 로컬 dev DB materialize 결과 `approved_codes=P1,P2,P3,P4,P5,P6,P7`, 인증된 로컬 `GET /personas`도 `P1..P7` 반환.
|
||||
- Google OAuth 진단 1차: provider callback error는 `access_denied`/`provider_error`로 분리하고, 로그인 화면은 실패 reason code를 함께 표시한다. 실제 Google 계정 완료 proof는 아직 owner 로그인/storageState가 필요하다.
|
||||
- 관리자 기본 진입 경로와 blank diagnostics: `initialPathForUser()`가 pending/onboarding/admin-entitled landing을 소유한다. 관리자 콘솔 접근권이 있는 계정은 primary role이 learner/teacher여도 `/` 또는 로그인 완료 뒤 `/admin`으로 들어간다. `App.tsx`는 pathname 변경마다 document scroll을 0으로 복원해 긴 관리자 화면에서 짧은 `/admin/access`로 이동해도 sticky shell만 보이는 빈 화면을 만들지 않는다. 최초 `/auth/me`는 요청별 5초 상한과 짧은 3회 재시도를 사용하며, 재부팅 중 연결 실패는 로그아웃/권한 상실로 오인하지 않고 `AuthRestoreGate`에서 같은 세션으로 다시 연결한다. 설정 기반 슈퍼 관리자/관리자 entitlement는 로그인과 세션 복원 시 `app.app_user.admin_access`에도 영속화해 재기동 뒤 설정 판정만 의존하지 않는다. OAuth upsert의 nullable 관리자 플래그는 PostgreSQL `boolean`으로 명시 캐스팅하며 non-dev 저장 실패는 원래 DB 예외를 로그에 남기고 503으로 닫는다. primary role 화면의 사이드바에도 `운영 콘솔` 진입점을 유지해 학습자/교수자 작업면으로 이동한 뒤 관리자 페이지가 사라지지 않는다. 관리자 화면은 `/admin/users`와 `/admin/tickets` payload 계약 위반을 안전하게 정규화하고 `관리자 데이터 진단`으로 깨진 필드/path/asset을 표시한다. 앱 route-level error boundary도 렌더 예외를 full white page 대신 진단 화면으로 대체하며, 0-height main도 진단 패널로 노출한다. `index.html`에는 React/module 실행 자체가 실패해도 3.5초 뒤 `Vignette 화면 진단`을 body에 직접 띄우는 boot watchdog이 있고, 같은 진단을 `/client-diagnostics`로 보내 API 로그에 남긴다. 2026-07-30 Cloudflare Pages production `1332ccbe`는 새 엔트리 `index-D4Twx0A1.js`와 직전 3세대 자산을 함께 게시했다. 실제 Google 슈퍼 관리자 사용자 행에 연결한 15분 임시 세션으로 관리자 5경로, primary learner 화면의 운영 콘솔 복귀, restored-tab 복원을 공개 사이트에서 4 passed로 확인했고 임시 세션은 즉시 삭제했다. 이어 관리자 권한 영속화 SQL의 타입 추론 회귀를 수정해 API PID `40992`로 재시작했고, 같은 실제 사용자 행으로 신규 세션 생성→공개 `/auth/me` 200(`admin_access=true`, `super_admin=true`, approved)까지 재검증한 뒤 세션을 폐기했다.
|
||||
- 2026-07-30 관리자 실제 빈 본문 후속: 실제 스크롤 소유자는 document가 아니라 `.vg-main`이므로 `AppShell`이 mount·pathname 변경·`pageshow`마다 내부 스크롤을 직접 0으로 복원한다. 이전 E2E는 `window.scrollY`만 조작해 잘못된 대상을 통과시켰고, 정확한 `.vg-main.scrollTop=900` 재현은 수정 전 2 failed·수정 후 desktop/mobile 8 passed였다. 진단 visible node도 `.vg-main` viewport 교차를 요구한다. Cloudflare Pages production `f323a41d`는 `index-Dun9umCY.js`와 현재 포함 직전 4세대 자산을 서빙한다. 실제 Google 슈퍼 관리자 사용자 행 기반 공개 5경로·learner→운영 콘솔·내부 scrollTop 복원 4 passed와 관리자 홈 픽셀 캡처를 직접 확인하고 임시 세션을 폐기했다. API PID는 `40992`, 공개 health는 prod·db=true·engine=true다.
|
||||
- 2026-07-30 관리자 광고 차단기 blank 근본 수정: 실제 사용자 캡처와 같은 셸-only 화면은 권한/API/스크롤이 아니라 공식 EasyList의 `.ad-root`·`.ad-section` cosmetic rule이 관리자 `ad-*` 클래스를 광고로 오인한 결과였다. 같은 `display:none!important` 규칙을 주입해 첨부 화면을 그대로 RED 재현한 뒤 관리자 DOM/CSS 487곳을 `vgops-*`로 교체했다. 루트/진단은 `data-vignette-admin-*` marker를 쓰고 HTML watchdog은 marker의 실제 computed visibility까지 검사한다. 로컬 EasyList 회귀 1/1, admin guards 12/12, admin full-sweep 20/20, admin layout 3/3, session-layout 8/8, typecheck가 통과했다. production `0a339b77` 배포 뒤 실제 Google 슈퍼 관리자 사용자 행의 임시 세션에서 같은 EasyList 규칙을 켠 채 운영 홈·AI 운영·사용자·권한·티켓 5경로가 모두 visible, legacy `ad-*` class 0, boot/admin diagnostic 0, 브라우저 오류 0임을 확인하고 세션을 즉시 폐기했다.
|
||||
- 같은 장애의 재유입 차단: `apps/web/scripts/check-cosmetic-filter-safety.mjs`가 프로덕션 소스 81개와 `index.html`을 검사하고 `ad-*`/`ads-*`/`advert*`/`sponsor*` UI namespace를 발견하면 `npm run build`와 `npm run lint`를 실패시킨다. 탐지기 자체의 RED/GREEN fixture도 매 실행에 포함된다. 로컬 EasyList E2E는 첫 watchdog 판정 뒤에도 실제 root/heading이 보이고 legacy class가 0인지 검사하며, 공개 E2E는 같은 필터 아래 관리자 5경로를 모두 검증하도록 승격했다. 이 gate를 통과한 격리 빌드를 production `1b3b6d5b`로 다시 게시했고 custom/preview entry 일치, Admin JS SHA-256 일치, `vgops-root` 존재, `ad-root` 부재를 공개 응답 바이트로 확인했다.
|
||||
- `frontenddesign` 스킬은 현재 세션의 사용 가능 스킬 목록에 없었다. 대신 `docs/DESIGN_CONCEPT.md`를 SSOT로 사용했다.
|
||||
- 공개 API 터널은 현재 `C:\Users\encep\.cloudflared\vignette-config.yml`에서 `http://127.0.0.1:8001`을 본다.
|
||||
- 공개용 API 프로세스는 `127.0.0.1:8001`에서 `ENVIRONMENT=prod`로 떠 있다. 로컬 개발 API `127.0.0.1:8000`과 Tailnet 개발 API `127.0.0.1:8010`도 `ENVIRONMENT=dev`로 떠 있다.
|
||||
- 2026-06-27 05:34:32 UTC 모바일 502는 cloudflared 로그상 `127.0.0.1:8001` origin connection refused와 일치한다. 현재 public API는 복구됐고, watchdog 기본 검사에서 아직 DNS가 없는 `api-vnet.18ka.net`을 제외해 향후 설치 시 불필요한 restart loop를 막았다. 2026-07-03 KST 재확인 대상: `https://vignette.chanpaca.net/admin/users?deploy=b67fd5d4` 200 with `assets/index-R5KK7hZI.js` + `assets/index-DzdAkRsn.css` and `vignette-boot-diagnostic` HTML, `https://api-vignette.chanpaca.net/health` prod/db/engine true, public unauth `GET /personas` 401.
|
||||
- `GET /personas`는 이제 인증 필요다. 검증 당시 로컬 `http://127.0.0.1:8000/personas`와 현재 공개 `https://api-vignette.chanpaca.net/personas` 모두 비로그인 401 확인 완료.
|
||||
- 로컬 웹이 `http://127.0.0.1:5175`처럼 다른 Vite 포트로 떠도 로그인은 `로컬 테스트 계정으로 계속` 버튼을 사용한다. Google OAuth 버튼은 로컬에서는 disabled로 둔다. 현재 OAuth callback이 공개 API로 돌아가기 때문에 로컬 Google OAuth는 로컬 세션에 붙을 수 없다.
|
||||
- 현재 확인용 프로세스는 `127.0.0.1:9099` engine gateway, `127.0.0.1:8001` prod public API, `127.0.0.1:8000` dev API, `127.0.0.1:8010` Tailnet dev API, `127.0.0.1:5173` Vite dev web, `127.0.0.1:5174` public preview, `cloudflared` tunnel 1개다. Tailnet URL은 `https://alpaca-home.taile93291.ts.net/login`이다. stale Vite allowedHosts로 403이 났던 상태는 재검증 시 `login` 200, `/api/health` dev/db/engine true, `/api/auth/config` 200이었다. `alpaca-home.taile93291.ts.net`는 Vite 기본 `allowedHosts`에도 포함했다.
|
||||
- `scripts/dev-up.ps1 -NoGateway -NoWeb`는 이제 gateway/web stale 정리를 건너뛰고 지정 `-ApiPort`의 API만 재기동한다. 검증 당시 8000 API-only 재기동 후 8001/8010/5173/9099/20241 listener가 보존됐다.
|
||||
- Docker `vignette-dev-db`는 실행 중이고 DB는 accepting connections다. 단, 기존 컨테이너라 healthcheck가 없고 `POSTGRES_USER=vignette` 기반이다. `vignette_app` role은 NOBYPASSRLS로 존재하지만 현재 API startup DDL이 owner 권한을 요구하므로 런타임 app-role 전환은 마이그레이션 owner/런타임 role 분리 후 진행한다.
|
||||
- 공개 런타임 재기동 스크립트:
|
||||
- start: `powershell -NoProfile -ExecutionPolicy Bypass -File scripts\start-public-runtime.ps1`
|
||||
- stop: `powershell -NoProfile -ExecutionPolicy Bypass -File scripts\stop-public-runtime.ps1`
|
||||
**G0~G6·G8은 internal DONE + 실제 runtime 증거로 종료됐다. 남은 것은 G7 외부 4-artifact 게이트 하나이며,
|
||||
이건 코드로 풀 수 없고 사용자가 제공해야 하는 운영 자원·동의가 있어야 진행된다.**
|
||||
|
||||
## 이번 세션 완료
|
||||
| Goal | 판정 | 완료 증거 |
|
||||
|---|---|---|
|
||||
| G0 Measurement Foundation | DONE | provenance census 29/29, unknown/missing/orphan/null/total violations 0 |
|
||||
| G1 Alliance Core | DONE | prompt 1.2 read-skew fix, prediction 24/24 ready, direction 9/9, error 0, recall 94.4%, precision 73.9% |
|
||||
| G2 Outcome Trajectory | DONE | benchmark accuracy/early-warning recall 1.0, false alert 0, 실 DB 10 sessions/30 measurements |
|
||||
| G3 Rupture & Repair | DONE | macro-F1 1.0, status 1.0, gaming 0, missed→partial→resolved 실 DB/API |
|
||||
| G4 Deliberate Practice | DONE | completed-session observer, actual API/DB attempts, teacher correction, returned-practice browser 폐루프 |
|
||||
| G5 Calibration & Transfer | DONE | actual transfer execution, independent/model-run/instrument/evidence provenance, DB/browser idempotency |
|
||||
| G6 Supervision & Research | DONE | safety event metadata-only projection, safety priority 1 > deterioration 2, raw detail 0 |
|
||||
| G7 Multimodal Alliance | **internal DONE · external GATE** | consent/withdrawal, synthetic soak 59/59, public unauth WSS 1008, focused 119 PASS |
|
||||
| G8 Continuous Improvement | **DONE** | 격리 NAS 실제 image rollback 2회 + executed receipt 2건 (아래 §3) |
|
||||
|
||||
- 남아 있던 E2E seed persona 결합을 제거했다.
|
||||
- `session-persistence`, `teacher`, `voice` 스펙은 `/api/personas`에서 실제 DB persona를 가져와 사용한다.
|
||||
- `voice-success`만 DB-offline fixture라 `ALLOW_SEED_PERSONA_FALLBACK=true`와 `P1`을 의도적으로 유지하고 주석을 남겼다.
|
||||
- 운영 설정 fail-closed를 강화했다.
|
||||
- `ENVIRONMENT != dev`에서 dev login, seed fallback, 기본 session secret, OAuth 누락, localhost frontend/CORS를 설정 검증에서 거부한다.
|
||||
- API lifespan은 DB 초기화 실패 시 dev에서만 degraded fallback을 허용하고, staging/prod에서는 시작 실패로 둔다.
|
||||
- `SettingsConfigDict(populate_by_name=True)`를 추가해 테스트/코드에서 field name 기반 설정 생성이 가능하게 했다.
|
||||
- `infra/docker-compose.yml`을 깨끗한 ASCII compose로 재작성했다.
|
||||
- `ENVIRONMENT`, Google OAuth client id/secret, `OAUTH_REDIRECT_URI`, `CORS_ORIGINS`, `OPENAI_BASE_URL`을 API 컨테이너에 명시 주입한다.
|
||||
- 기존 손상된 줄 때문에 `OPENAI_API_KEY`가 주석/중복으로 처리되던 문제를 제거했다.
|
||||
- 기본 `FRONTEND_BASE_URL`은 `https://vignette.chanpaca.net`로 수정했다.
|
||||
- `infra/.env.example`에 운영 필수 값을 보강했다.
|
||||
- `ENVIRONMENT=prod`, `OAUTH_GOOGLE_CLIENT_ID`, `OAUTH_GOOGLE_CLIENT_SECRET`
|
||||
- 기본 CORS에서 localhost 제거. 로컬 개발은 주석대로 `ENVIRONMENT=dev`와 localhost CORS를 별도 추가.
|
||||
- admin UI 안정화.
|
||||
- health card의 긴 engine readiness JSON이 모바일 폭을 밀어내지 않도록 줄바꿈 처리.
|
||||
- backend admin health는 동적인 raw JSON detail 대신 안정적인 `Engine readiness failed` 문구를 내려준다.
|
||||
- 전역 engine config를 바꾸는 `db-persistence` E2E는 `@single-run`으로 분리했다.
|
||||
- 사용자 생성 후 UI가 서버 목록 reload를 끝내기 전에 카드를 찾던 E2E 타이밍을 안정화했다. 생성한 이메일로 검색한 뒤 실제 서버 응답 카드만 검증한다.
|
||||
- 사용자 관리 UI는 DB 사용자 저장소가 durable하지 않으면 생성/수정/비활성화를 막는다.
|
||||
- AI 운영 라벨은 `Claude CLI 게이트웨이`, `Anthropic API`, `OpenAI 호환`, `Solar`처럼 실제 어댑터명으로 표시한다.
|
||||
- 설정의 AI 운영 패널은 `/admin/health` 기반 응답 생성 상태를 함께 보여주되, health 지연이 설정 폼 로딩을 막지 않도록 분리했다.
|
||||
- 비개발 환경에서 `admin_engine_config` singleton row가 없으면 runtime default로 내려가지 않고 fail-closed 한다.
|
||||
- 비개발 DB 장애 상태는 `임시 기록`이 아니라 저장소 중단으로 표시한다.
|
||||
- public Google OAuth smoke 문서를 보강했다.
|
||||
- `E2E_PUBLIC_AUTH=1`에서는 로컬 Vite 서버를 띄우지 않고 공개 웹을 대상으로 한다.
|
||||
- storageState는 인증 쿠키를 포함하므로 `node_modules/.tmp` 아래 민감 파일로 취급한다.
|
||||
- API session TTL은 현재 8시간이다.
|
||||
- 공개 API prod 전환을 완료했다.
|
||||
- `cloudflared` ingress를 `127.0.0.1:8000`에서 `127.0.0.1:8001`로 변경했다.
|
||||
- `https://api-vignette.chanpaca.net/health`가 `environment:"prod"`, `db:true`, `engine:true`를 반환한다.
|
||||
- public production-safe Playwright 게이트가 통과한다.
|
||||
- 공개 런타임 start/stop 스크립트를 추가했다.
|
||||
- `scripts/start-public-runtime.ps1`는 prod-safe env override로 API 8001을 띄우고 health를 검증한다.
|
||||
- 기본 실행 시 `cloudflared`도 vignette config 기준으로 재시작한다.
|
||||
- `scripts/stop-public-runtime.ps1`는 8001 API를 정리한다. `-StopCloudflared`를 붙이면 터널도 같이 정리한다.
|
||||
- 공개 API 보안 경계를 보강했다.
|
||||
- `apps/api/app/routes/personas.py`에 `CurrentPrincipal`을 붙여 persona catalog를 authenticated-only로 전환했다.
|
||||
- 로컬 E2E `auth.spec.ts`에 비로그인 `/api/personas => 401` 회귀를 추가했다.
|
||||
- public readiness gate는 빈 API request context로 비로그인 `/personas => 401`을 확인한다. storageState가 붙은 request fixture를 쓰지 않도록 처리했다.
|
||||
- 공개 웹 API base fallback을 보강했다.
|
||||
- `apps/web/src/lib/api.ts`는 `VITE_API_BASE`가 없고 host가 `vignette.chanpaca.net` 또는 `*.pages.dev`이면 `https://api-vignette.chanpaca.net`을 기본 API origin으로 사용한다.
|
||||
- 이 변경으로 public web의 `/api/auth/config`가 SPA HTML로 떨어져 Google 버튼이 disabled 되는 문제를 피한다.
|
||||
- 로컬 로그인 UX를 보강했다.
|
||||
- `apps/web/src/pages/Login.tsx`는 로컬 origin에서 OAuth redirect URI가 공개 API이면 Google 버튼을 disabled 처리하고 `로컬 테스트 계정으로 로그인` 안내를 보여준다.
|
||||
- `auth.spec.ts`에 로컬 테스트 계정 로그인 후 `/learn` redirect 회귀를 추가했다.
|
||||
- 학습자 홈 기록 UX를 보강했다.
|
||||
- `apps/web/src/pages/LearnerHome.tsx`는 세션이 0개인 사용자에게도 실제 서버 세션 0개를 기반으로 `기존 회기` 빈 상태와 0 카운트를 보여준다.
|
||||
- 기록이 있을 때만 `has-session-records` 압축 레이아웃을 쓰도록 분리했다. 0회 사용자 모바일에서도 선택 내담자 정보, 새 회기 CTA, 기록 빈 상태가 문서 스크롤 없이 보인다.
|
||||
- `apps/web/e2e/readiness.spec.ts`는 fake 12회/최근 8회 부재뿐 아니라 서버 기반 0회 빈 상태를 기대하도록 갱신했다.
|
||||
- history의 반복 CTA는 `다시 시도` 대신 `다시 연습`으로 바꿔 기록 회고와 새 회기 시작 의미를 분리했다.
|
||||
- 직접 세션 URL의 persona 우회 경로를 막았다.
|
||||
- `LearnerHome`에서만 막던 `degraded` 또는 non-DB persona 차단을 `Session` 직접 진입 경로에도 적용했다.
|
||||
- `/learn/session/:personaCode`로 직접 들어와도 DB 원본 persona가 아니면 `회기 시작` 버튼이 비활성화된다.
|
||||
- desktop/mobile E2E에 degraded `seed_fallback` persona route mock 회귀를 추가했다.
|
||||
- active session dense viewport를 보정했다.
|
||||
- `apps/web/src/pages/session/session.css`에서 380px 이하/낮은 화면의 stage padding과 avatar 크기를 줄여 stage 내부 clipping을 없앴다.
|
||||
- `session-layout.spec.ts` dense viewport desktop/mobile 회귀와 전체 Playwright에서 확인했다.
|
||||
- active session 텍스트 턴 저장/표시 흐름을 실제 SSE 계약에 맞췄다.
|
||||
- 프론트 `sessionApi.stream`은 실제 백엔드 계약인 `POST /sessions/{id}/stream`을 fetch stream으로 소비한다.
|
||||
- `/stream` 요청이 거절되거나 SSE가 열린 뒤 error 이벤트로 끝나면 학습자 발화와 부분 내담자 응답을 transcript에 남기지 않고 입력값을 복원한다.
|
||||
- 내담자 자막은 로컬 typewriter가 아니라 서버 token 이벤트로만 쌓는다.
|
||||
- 백엔드 `/turn`과 `/stream`은 엔진 성공 전 learner turn/state를 append하지 않도록 순서를 바꿨고, `app.test_session_turn_persistence` 회귀로 검증했다.
|
||||
- 음성 회기 실패 턴 저장/표시도 같은 기준으로 맞췄다.
|
||||
- voice route는 엔진 응답 생성 성공 후에만 learner/client turn을 저장한다.
|
||||
- 프론트는 음성 transcript를 pending으로 표시하고, 엔진 실패 시 pending transcript를 제거한다.
|
||||
- 음성 degraded reason의 내부 `seed_fallback`/runtime store 표현은 사용자용 한국어 상태 문구로 바꿨다.
|
||||
- admin health의 dev DB fallback 문구를 `임시 기록`이 아니라 `비영구 런타임 기록`으로 정리했다.
|
||||
- Live2D demo fallback과 샘플 자산을 제거했다.
|
||||
- Mao/Haru 샘플 모델과 Pixi/Cubism 런타임은 공개 배포물에서 제거했다.
|
||||
- 세션 화면은 SVG persona avatar만 렌더링한다.
|
||||
- Cloudflare edge에 남은 기존 `/live2d/*` 캐시를 막기 위해 Pages Function `apps/web/functions/live2d/[[path]].js`가 404를 반환한다.
|
||||
대시보드 status count는 **33 done / 2 doing / 0 planned**다.
|
||||
|
||||
## 검증 결과
|
||||
## 1. 작업 시작 전 반드시 읽을 파일
|
||||
|
||||
- Web typecheck:
|
||||
- `cd apps/web; fnm env --use-on-cd | Out-String | Invoke-Expression; fnm use 22.22.3; npm run typecheck`
|
||||
- Passed
|
||||
- Backend unit/compile:
|
||||
- `cd apps/api; C:\Users\encep\AppData\Local\Programs\Python\Python311\python.exe -m unittest app.test_runtime_policy app.test_session_turn_persistence engine_gateway.test_gateway_model`
|
||||
- `20 tests OK`
|
||||
- `python -m compileall app engine_gateway`
|
||||
- Passed
|
||||
- Web production build:
|
||||
- `cd apps/web; fnm use 22.22.3; npm run build`
|
||||
- Passed
|
||||
- Design/theme verification:
|
||||
- `docs/design-verification/full-pages/01..07-*-{desktop,tablet,mobile}.png`
|
||||
- 7개 주요 화면 x 3뷰포트 = 21장 캡처 갱신. 자동 검사 결과: horizontal overflow 0, 큰 흰 CSS 배경면 0. 세션 active는 `seoyeon-live2d-psd-v2` 래스터 로드, 현재 상태 배지와 아바타 겹침 없음.
|
||||
- `npx playwright test e2e/layout-visual-gate.spec.ts e2e/session-layout.spec.ts e2e/avatar-expression.spec.ts e2e/session-review.spec.ts e2e/settings.spec.ts --project=chromium-desktop --project=chromium-mobile --project=chromium-single-run --workers=1`
|
||||
- `36 passed`
|
||||
- Docker compose config:
|
||||
- With dummy required env vars, `docker compose -f infra\docker-compose.yml --env-file infra\.env.example config --quiet`
|
||||
- Passed
|
||||
- Focused Playwright after fixes:
|
||||
- `npx playwright test e2e/admin.spec.ts e2e/db-persistence.spec.ts e2e/voice-success.spec.ts --project=chromium-desktop --project=chromium-mobile --project=chromium-single-run`
|
||||
- `10 passed`
|
||||
- Admin manage-users focused rerun:
|
||||
- `PLAYWRIGHT_BASE_URL=http://127.0.0.1:5175 PLAYWRIGHT_SKIP_WEB_SERVER=1 npx playwright test e2e/admin.spec.ts --project=chromium-desktop --project=chromium-mobile --grep "manage real server-known users"`
|
||||
- `2 passed`
|
||||
- Full Playwright:
|
||||
- `cd apps/web; PLAYWRIGHT_HOST=127.0.0.1 npx playwright test`
|
||||
- `72 passed` after public API/cloudflared was restored
|
||||
- 참고: tunnel을 내린 직후 첫 전체 실행은 public login gate 2개만 `Cloudflare 1033`으로 실패하고 나머지 `70 passed`였다. `scripts\start-public-runtime.ps1`로 prod public API와 cloudflared를 복구한 뒤 전체 재실행에서 `72 passed`.
|
||||
- Public auth project discovery:
|
||||
- `E2E_PUBLIC_AUTH=1 npx playwright test --list --project=chromium-public-auth`
|
||||
- `2 tests`: public production-safe config + real OAuth `/turn`
|
||||
- Public production-safe gate:
|
||||
- `E2E_PUBLIC_AUTH=1 npx playwright test e2e/public-auth-turn.spec.ts --project=chromium-public-auth --grep "production-safe"`
|
||||
- `1 passed` on rerun
|
||||
- Admin/settings focused E2E:
|
||||
- `npx playwright test e2e/admin.spec.ts e2e/settings.spec.ts --project=chromium-desktop --project=chromium-mobile --project=chromium-single-run`
|
||||
- Latest focused reruns: `admin.spec.ts` `8 passed`, `settings.spec.ts` `9 passed`
|
||||
- Auth/readiness focused E2E:
|
||||
- `npx playwright test e2e/auth.spec.ts e2e/readiness.spec.ts --project=chromium-desktop --project=chromium-mobile`
|
||||
- `14 passed`
|
||||
- Persona auth boundary:
|
||||
- local unauth `http://127.0.0.1:8000/personas` -> 401
|
||||
- public unauth `https://api-vignette.chanpaca.net/personas` -> 401
|
||||
- Public runtime script:
|
||||
- `powershell -NoProfile -ExecutionPolicy Bypass -File scripts\start-public-runtime.ps1`
|
||||
- API 8001 health and public `https://api-vignette.chanpaca.net/health` both returned `environment:"prod"`, `db:true`, `engine:true`
|
||||
- Public web deployment:
|
||||
- `wrangler pages deploy dist --project-name vignette --branch main --commit-dirty=true`
|
||||
- 2026-07-03 manual deployment, preview `https://8f6aba1c.vignette-b1q.pages.dev`
|
||||
- `https://vignette.chanpaca.net/admin/users?deploy=b67fd5d4` serves `assets/index-R5KK7hZI.js`, `assets/index-DzdAkRsn.css`, and boot diagnostic HTML
|
||||
- `https://vignette.chanpaca.net/live2d/mao/Mao.model3.json`, Haru model, and Cubism core routes return 404
|
||||
- Public login screen:
|
||||
- `npx playwright test e2e/auth.spec.ts --project=chromium-desktop --grep "public login"`
|
||||
- `1 passed`
|
||||
- OAuth state is now HMAC-signed and bound to a HttpOnly state cookie; fake callback with state cookie now reaches `oauth=token_exchange_failed` instead of `oauth=invalid_state`, provider callback error redirects to `oauth=access_denied`, and callback failures log non-secret reason/status details in `api.public.err.log`.
|
||||
- Login UI maps known OAuth/SAML failure reasons and displays `오류 코드: ...`; auth focused E2E is now 7 passed.
|
||||
- Public runtime watchdog:
|
||||
- `powershell -NoProfile -ExecutionPolicy Bypass -File scripts\watch-public-runtime.ps1 -CheckOnly`
|
||||
- healthy: `engine, api, web-preview, cloudflared, public-api`
|
||||
- `api-vnet.18ka.net` is not part of the default watchdog checks while DNS is not live; add it later with `-AdditionalPublicHealthUrls`.
|
||||
- 2026-07-30부터 엔진 readiness와 관리자·인증 제어면을 분리한다. 엔진 단독 장애는 API·웹·터널 재시작을 유발하지 않는다. 실운영에서 API PID `36824` 유지, 예약 작업 `LastTaskResult=0`, failcount `0`을 확인했다.
|
||||
- API 코드 배포는 `start-public-runtime.ps1 -ForceApiRestart -SkipEngineRestart -SkipWebRestart -SkipCloudflaredRestart`로 API만 교체한다. 2026-07-30 관리자 entitlement 수정 배포 뒤 public `/health`는 `prod/db/engine=true`다.
|
||||
- Local login on `127.0.0.1:5175`:
|
||||
- `PLAYWRIGHT_BASE_URL=http://127.0.0.1:5175 PLAYWRIGHT_SKIP_WEB_SERVER=1 npx playwright test e2e/auth.spec.ts --project=chromium-desktop`
|
||||
- `6 passed`
|
||||
- `PLAYWRIGHT_BASE_URL=http://127.0.0.1:5175 PLAYWRIGHT_SKIP_WEB_SERVER=1 npx playwright test e2e/auth.spec.ts --project=chromium-mobile`
|
||||
- `6 passed`
|
||||
- focused local dev-login redirect: `1 passed`
|
||||
- Learner home/history focused verification:
|
||||
- `cd apps/web; npm run typecheck`
|
||||
- Passed
|
||||
- `PLAYWRIGHT_BASE_URL=http://127.0.0.1:5175 PLAYWRIGHT_SKIP_WEB_SERVER=1 npx playwright test e2e/learner.spec.ts e2e/readiness.spec.ts --project=chromium-desktop --project=chromium-mobile`
|
||||
- `14 passed`
|
||||
- Visual audit screenshots: `apps/web/node_modules/.tmp/ui-audit/learn-empty-desktop.png`, `apps/web/node_modules/.tmp/ui-audit/learn-empty-mobile-compact.png`
|
||||
- Screenshot metrics confirmed desktop/mobile document overflow `x:0, y:0`, empty history visible, and real stats area present.
|
||||
- Session dense viewport verification:
|
||||
- `PLAYWRIGHT_BASE_URL=http://127.0.0.1:5175 PLAYWRIGHT_SKIP_WEB_SERVER=1 npx playwright test e2e/session-layout.spec.ts --project=chromium-desktop --project=chromium-mobile`
|
||||
- `8 passed`
|
||||
- Latest voice/session focused E2E:
|
||||
- `PLAYWRIGHT_BASE_URL=http://127.0.0.1:5175 PLAYWRIGHT_SKIP_WEB_SERVER=1 npx playwright test e2e/voice.spec.ts e2e/voice-success.spec.ts e2e/session-layout.spec.ts --project=chromium-desktop --project=chromium-mobile --project=chromium-single-run`
|
||||
- `15 passed`
|
||||
- Latest learner/session/settings/admin focused E2E:
|
||||
- `PLAYWRIGHT_BASE_URL=http://127.0.0.1:5175 PLAYWRIGHT_SKIP_WEB_SERVER=1 npx playwright test e2e/learner.spec.ts e2e/session-layout.spec.ts e2e/settings.spec.ts e2e/admin.spec.ts --project=chromium-desktop --project=chromium-mobile --project=chromium-single-run`
|
||||
- `37 passed`
|
||||
1. `AGENTS.md`
|
||||
2. `docs/README.md`
|
||||
3. `docs/HANDOFF.md` — 현재 문서
|
||||
4. `docs/dev_dashboard.html` — 상태 SSOT
|
||||
5. `docs/TODO.md` — 열린 작업 SSOT
|
||||
6. `docs/ops/backlog-2026-06-26.md`
|
||||
7. `docs/ops/outcome-os-g7-external-proof-readiness-2026-08-07.md` — **다음 작업의 핵심**
|
||||
8. `docs/ops/nas-preview-g8-rollback-proof-runbook.md`
|
||||
9. `docs/ops/nas-preview-deployment-evidence-2026-08-07.md`
|
||||
10. `docs/ops/public-db-recovery-rehearsal-2026-08-07.md`
|
||||
|
||||
## 아직 못 끝낸 것
|
||||
`docs/archive/`는 현재 상태 근거로 읽지 않는다.
|
||||
|
||||
- 공개 Google OAuth 실제 `/turn` proof는 아직 없다.
|
||||
- 이유: `E2E_PUBLIC_STORAGE_STATE=apps/web/node_modules/.tmp/public-auth.json`가 아직 확보되지 않았다.
|
||||
- 사용자가 Playwright codegen 브라우저에서 Google 로그인 후 storageState를 저장해야 한다.
|
||||
- 2026-06-26 15:18 KST 기준 `apps/web/node_modules/.tmp/public-auth.json`은 없다. codegen 창/프로세스가 남아 있으면 로그인 완료 후 창을 닫아 저장을 완료하면 된다.
|
||||
- 공개 API `environment:"dev"` 문제는 이번 세션에서 해결했다. cloudflared가 prod API인 `127.0.0.1:8001`을 보도록 변경했고 public gate로 확인했다.
|
||||
- Google Console 설정은 브라우저 로그인 세션이 필요하다. 막히면 사용자가 로그인해 주기로 했다.
|
||||
- 현재 worktree에는 이번 디자인 패스의 아바타/세션 화면 변경이 남아 있다(`Session.tsx`, `session.css`, `apps/web/public/avatar/seoyeon-live2d-psd-v2/`, `avatar-expression.spec.ts`, 관련 문서). 과거 임시 크롭 `apps/web/public/avatar/p1-concept/`는 P1 기본값으로 쓰지 않는다. API 쪽 변경(`apps/api/app/persona_repository.py`, `apps/api/app/test_persona_review.py`)은 이번 디자인 패스 범위에서 수정하지 않았으므로 별도 변경으로 취급하고 되돌리지 말 것.
|
||||
## 2. 절대 보존 경계
|
||||
|
||||
## 다음 세션 우선순위
|
||||
- Windows 11 + PowerShell 환경이다. 실행 전 OS·셸·도구 경로를 다시 확인한다.
|
||||
- 명령은 PowerShell 5.1 호환 문법을 기본으로 작성한다. NAS 원격 스크립트는 **UTF-8 base64 전달 방식**을 쓴다.
|
||||
(`$()`가 PowerShell에서 먼저 확장되는 것을 막는다.)
|
||||
- worktree는 큰 규모로 dirty하다. `git reset --hard`, `git checkout --`, 대규모 자동 포맷, 임의 삭제 금지.
|
||||
- Git commit/stage/push는 사용자가 요청하기 전에는 하지 않는다.
|
||||
- 공개 복구 DB, 원본 volume, pre-recovery container, dump, NAS named volume, 이전 API/Web 이미지를 삭제하지 않는다.
|
||||
- **로컬 포트 8001(공개 API) · 5173(공개 Web) · 55432(공개 DB) · 9099/9100(엔진)은 공개 런타임이다.**
|
||||
`scripts/dev-up.ps1`은 8000/5173 리스너를 정리하므로 공개 런타임이 떠 있는 동안 실행하지 않는다.
|
||||
- 비밀값, 이메일, cookie, 사용자 UUID가 포함된 raw response, `.env` 내용은 로그·증거·채팅에 출력하지 않는다.
|
||||
|
||||
1. Google OAuth Console을 확인한다.
|
||||
- Authorized redirect URI: `https://api-vignette.chanpaca.net/auth/callback`
|
||||
- Authorized JavaScript origin: `https://vignette.chanpaca.net`
|
||||
- 앱/도메인 설정은 `hs.ac.kr`, `twentyoz.kr` 계정만 서버 claim 검증으로 허용한다. Google Console의 authorized domains와 이메일 도메인 제한은 다른 개념이다.
|
||||
2. storageState를 캡처하고 public `/turn` smoke를 실행한다.
|
||||
- `cd D:\workspace\vignette\apps\web`
|
||||
- `npx playwright codegen https://vignette.chanpaca.net/login --save-storage=.\node_modules\.tmp\public-auth.json`
|
||||
- `$env:E2E_PUBLIC_AUTH="1"`
|
||||
- `$env:E2E_PUBLIC_STORAGE_STATE=".\node_modules\.tmp\public-auth.json"`
|
||||
- `.\node_modules\.bin\playwright.cmd test e2e/public-auth-turn.spec.ts --project=chromium-public-auth`
|
||||
3. UI/UX는 계속 `docs/DESIGN_CONCEPT.md`를 따른다.
|
||||
- learner home은 빈 사용자에게 fake 통계/기록을 보여주지 않는다. 0회 상태는 실제 서버 기록 0개로 `기존 회기` 빈 상태를 보여주는 현재 구현을 유지한다.
|
||||
- active learning session은 전체 화면, 문서 스크롤 없음이 원칙이다.
|
||||
- 실제 session history는 `기존 회기`, `이어하기`, `기록/리뷰`, `다시 연습`, `새 회기 시작` 흐름으로 유지한다.
|
||||
4. 다음 배포 전 `admin/engine-config`를 다시 확인한다.
|
||||
- 의도된 현재값이 `claude_cli / http://127.0.0.1:9099 / gateway-default`인지 확인.
|
||||
- E2E가 engine config를 건드리므로 배포 전 실제 운영값을 반드시 재확인한다.
|
||||
## 3. G8 — 종료됨 (2026-08-07 21:04~21:12 KST)
|
||||
|
||||
## 로컬 실행 명령
|
||||
### Gate6 계약 정정 (성립 불가능했던 이전 계약을 코드로 교체)
|
||||
|
||||
Engine gateway:
|
||||
감사 대상 current API 이미지 `sha256:52e0e816…8b2d`가 `com.docker.compose.project=vignette-preview-20260807`,
|
||||
`service=api`, `version=2.20.1` **image label**을 갖고 있다. 따라서 "helper의 `com.docker.compose.*` key 0개"는
|
||||
감사되지 않은 다른 이미지를 쓰지 않는 한 성립하지 않는다. 계약을 **key 부재 → 소속(membership)** 으로 바꿔
|
||||
구현했다.
|
||||
|
||||
- 코드: `scripts/launch-nas-preview-g8-helpers.py`
|
||||
- 테스트: `scripts/test_launch_nas_preview_g8_helpers.py` — **37/37**
|
||||
- 계약: image 상속 label을 baseline으로 읽고 container의 모든 compose label이 baseline과 같거나 선언된 격리
|
||||
override인지 검사 · 최종 project ≠ target · 최종 service ∉ {api,web,db,proxy} · argv에 target project/service
|
||||
label 미주입(fake-runner) · exact container name/ID/`docker inspect` 증거 보존
|
||||
- 런북: `docs/ops/nas-preview-g8-rollback-proof-runbook.md`
|
||||
|
||||
### 실제 실행 결과
|
||||
|
||||
| plan | receipt | 활성화된 API/Web 이미지 |
|
||||
|---|---|---|
|
||||
| `rollback-old` | `nas-g8-723eeef22eab05e63e3fafb0` | `79ec…4450` / `c530…2f28` |
|
||||
| `restore-current` | `nas-g8-2738846cf2cf4fbe8ce0fc26` | `52e0…8b2d` / `6fdb…f215` |
|
||||
|
||||
- release gate·approval 각각 2회 멱등, lifecycle `executed`, artifact/approval/receipt binding, control-plane 분리 검증
|
||||
- DB: `audit.ci_lifecycle_event` `rollback/executed` 2 · `audit.ci_human_approval_event` `authorize_rollback` 2 ·
|
||||
`app.ci_release_gate` 2(전부 `pending_human_approval`, silent auto-promotion 0) · `ci_gate_artifact` 8
|
||||
- HMAC journal 6 records, `previous_hash` 체인 전수 PASS, SHA256 `a5594feb…0690`
|
||||
- 최종 상태: health 3/3, OpenAPI 126, auth 401, Web 200, helper 0, listener 0, 비밀 env 파기
|
||||
- **계획 이탈:** Windows SSH 터널 `18018→8018`은 NAS sshd가 `administratively prohibited`로 direct-tcpip를 거부해
|
||||
쓸 수 없었다. **sshd 설정은 바꾸지 않았고**, 같은 격리 계약의 NAS-side probe 컨테이너에서 loopback으로
|
||||
실행해 control-plane(8018)/preview(8088) origin 분리는 그대로 유지했다. 다음 에이전트도 NAS에서
|
||||
포트 포워딩을 기대하지 말 것.
|
||||
- 기계 판독 증거: `docs/ops/evidence/nas-preview-g8-actual-rollback-2026-08-07.json`
|
||||
|
||||
## 4. 공개 서비스 복구 상태 — 이미 완료, 다시 망가뜨리지 말 것
|
||||
|
||||
공개 계정·회기 DB 복구는 2026-08-07 18:26 KST에 완료됐다.
|
||||
|
||||
- Public Web `https://vignette.chanpaca.net` · Public API `https://api-vignette.chanpaca.net` (origin `127.0.0.1:8001`)
|
||||
- active DB container `vignette-dev-db`, volume `vignette_recovered_prod_20260807`, restart `unless-stopped`
|
||||
- 복구 집계: users 84, sessions 30, turns 705, Google users 16, orphan sessions 0
|
||||
- health `environment=prod`, `db=true`, `engine=true`, `engine_mode=claude_cli`, dev-login disabled, unauth `/auth/me` 401
|
||||
- 공개 OpenAPI 119 paths · watchdog `VignettePublicRuntimeWatchdog` 5분 주기
|
||||
|
||||
보안상 과거 `auth_session`은 복원하지 않았다. 사용자는 Google 재로그인이 필요하다.
|
||||
|
||||
보존물: pre-recovery container `vignette-dev-db-pre-recovery-20260807-182642`,
|
||||
pre-cutover dump `…20260807-091057Z.dump` SHA256 `6b84d5c8…af1f`,
|
||||
recovered dump `…20260807-092630Z.dump` SHA256 `d7bcc396…68ed`,
|
||||
post-cutover dump `…20260807-095511Z.dump` SHA256 `660695c8…65b88`.
|
||||
|
||||
공개 DB에는 current dirty source를 배포하지 않는다.
|
||||
|
||||
## 5. current source NAS 배포 — 완료
|
||||
|
||||
- URL `http://100.116.83.60:8088` · project `vignette-preview-20260807` · remote root `/volume1/docker/vignette-preview-20260807`
|
||||
- active release SHA `6030a677af7e87cbfabc422b553d108d53414fd3c446548734a13b036d35c611`
|
||||
- API `sha256:52e0e816…8b2d` / Web `sha256:6fdbb646…f215` (previous: `79ec731f…0450` / `c530213f…62f28`)
|
||||
- DB volume `vignette-preview-20260807_pgdata` · pre-proof dump `vignette-preview-g8-preproof-20260807T110610Z.dump`
|
||||
SHA256 `92ed4736…1570f`
|
||||
- candidate browser E2E 108/108(634.578s), postdeploy SSE→DB review PASS
|
||||
- **G8 rollback/restore 이후 재검증:** 실제 브라우저 SSE→DB review 재실행 PASS(35.0s)
|
||||
|
||||
배포 중 첫 108 gate가 103/5로 fail-closed된 이력(G5 fixture drift, StrictMode retry fixture, `shell.css` manifest 누락)은
|
||||
지우지 않는다.
|
||||
|
||||
### ⚠ 배포된 SHA는 비-secure origin 결함을 갖고 있다 (수정은 소스에만 있음)
|
||||
|
||||
배포 후 NAS 프리뷰(`http://100.116.83.60:8088`, 평문 HTTP·비-localhost)에 전체 회기 스펙을 돌려보니 24건이
|
||||
실패했고, 원인은 단 하나였다. 페이지 스냅샷의 실제 예외는 `crypto.randomUUID is not a function`이다.
|
||||
이 API는 **secure context(HTTPS 또는 localhost)에서만** 노출되는데 제품 코드 18곳이 fallback 없이 직접
|
||||
호출했고, `RuptureRepairCard.tsx`는 렌더 시점(`useRef`)에 호출해 회기 리뷰 라우트 전체가 error boundary로
|
||||
떨어졌다. 릴리스 게이트의 108/108은 **localhost 후보 스택**(secure context)에서 돌았기 때문에 이 경로를
|
||||
한 번도 밟지 않았다.
|
||||
|
||||
- 수정: `apps/web/src/lib/uuid.ts`의 `randomUuid()`로 통일. fallback도 `crypto.getRandomValues`를 우선
|
||||
사용해 idempotency key의 예측 불가능성을 유지하고, Web Crypto가 아예 없을 때만 `Math.random`으로 내려간다.
|
||||
- 회귀: `apps/web/e2e/insecure-context-uuid.spec.ts` **6/6** (직접 호출 0건 검사 포함), typecheck·build 통과.
|
||||
- **아직 NAS에 배포하지 않았다.** 배포된 SHA `6030a677…c611`은 여전히 결함 빌드다. 다음 배포 때
|
||||
release agent로 승격하고, 승격 뒤에는 NAS origin에서 회기 스펙을 다시 돌려 24건이 사라지는지 확인한다.
|
||||
- 교훈: candidate gate를 localhost에서만 돌리면 secure-context 전용 API 결함을 못 잡는다. 배포 대상과
|
||||
같은 scheme/host 형태에서 최소 한 번은 회기 리뷰 라우트를 열어봐야 한다.
|
||||
|
||||
## 6. **다음 에이전트의 유일한 남은 목표 — G7 외부 종료 Gate**
|
||||
|
||||
G7 내부 구현은 완료됐고 `scripts/check-g7-external-proof.py`는 현재 의도적으로 exit 1이다.
|
||||
네 artifact를 **같은 public host, 겹치는 50분 시간창**으로 수집해야 한다.
|
||||
|
||||
```powershell
|
||||
cd D:\workspace\vignette\apps\api
|
||||
C:\Users\encep\AppData\Local\Programs\Python\Python311\python.exe -m uvicorn engine_gateway.gateway:app --host 127.0.0.1 --port 9099
|
||||
& $py -X utf8 -B scripts/check-g7-external-proof.py `
|
||||
--voice-soak <soak.json> --runtime <runtime.json> `
|
||||
--topology <topology.json> --human-voice-gain <pack.json>
|
||||
```
|
||||
|
||||
API:
|
||||
1. `scripts/soak-public-voice-websocket.py` v4 — 운영 Deepgram/OpenAI TTS, authenticated public WSS,
|
||||
**명시 동의 물리 마이크** 50분 양방향 `passed`. `--confirm-physical-capture` 없이는 장치 열거·캡처를 하지 않는다.
|
||||
2. `scripts/capture-g7-runtime-evidence.py` — 같은 시간창의 관리자 endpoint worker/Uvicorn queue high-water.
|
||||
3. `scripts/capture-g7-topology-evidence.py` — 같은 host·exact image의 50분 cgroup/proc/Docker/TCP high-water.
|
||||
4. 독립 blind human voice-gain pack — 최소 30명 / 50회기 / 150 paired axis / blind evaluator 2인 /
|
||||
ICC(A,1) ≥ 0.75 · κ ≥ 0.70 · gain ≥ 0.01 · participant-cluster bootstrap 10,000회 95% CI lower > 0.
|
||||
|
||||
준비 상세는 `docs/ops/outcome-os-g7-external-proof-readiness-2026-08-07.md`.
|
||||
|
||||
**사용자에게 받아야 하는 것 (코드로 대체 불가):**
|
||||
|
||||
- 운영 Deepgram key·quota 사용 권한
|
||||
- authenticated learner/admin session 또는 안전한 `storageState` 생성 협조
|
||||
- 물리 마이크 50분 실행에 대한 **명시적 동의**
|
||||
- 실제 참가자·독립 평가자 운영 승인
|
||||
|
||||
합성 label이나 무동의 mic probe로 대체하지 않는다. `check-g7-external-proof.py` exit 0 전에는 G7 메인 상태를
|
||||
DONE으로 바꾸지 않는다.
|
||||
|
||||
## 7. 학생 자기주도 학습 폐루프
|
||||
|
||||
```text
|
||||
학습자 홈 추천 → 새 회기/사전 설정 → 실제 Session → 종료 리뷰 → G4 처방 또는 G5 전이 의도
|
||||
→ 별도 재연습 회기 → completed-session observer → 실제 G4 attempt / G5 transfer execution
|
||||
→ read-model reload → before/after·진행도·멱등 재확인
|
||||
```
|
||||
|
||||
실제 DB-backed browser 증거: `apps/web/e2e/returned-practice-db-closed-loop.spec.ts` +
|
||||
`apps/web/e2e/harness/prepare-returned-practice-db.py`, desktop 2/2 + mobile 2/2, route mock 0,
|
||||
visible raw UUID 0, getUserMedia/enumerateDevices 0, horizontal overflow 0.
|
||||
|
||||
이 harness는 **disposable clone**(전용 DB container + 전용 API 포트 + 전용 vite 포트)에서만 실행한다.
|
||||
공개 런타임 포트를 재사용하지 않는다. `--api-base-url`, `--database-url`, `--practice-internal-token`,
|
||||
`--transfer-internal-token`이 필요하고 API health의 `db`·`engine`이 모두 true여야 한다.
|
||||
|
||||
2026-08-07 재실행 절차(그대로 재현 가능):
|
||||
|
||||
1. `docker run -d --name vignette-g8-e2e-db-<날짜> -p 127.0.0.1:55439:5432` + `infra/db/init` 마운트,
|
||||
`POSTGRES_USER=vignette_owner` / `APP_DB_USER=vignette_app`. `app.ci_regression_dag_node`가 생기면 준비 완료.
|
||||
2. 별도 엔진 게이트웨이를 **새 포트**(예: 9199)에 띄운다. 상주 게이트웨이의 claude 세션이 죽어 있으면
|
||||
`engine=false`가 되므로 공개용 9099를 재사용하지 않는다.
|
||||
3. uvicorn API를 8021에, vite를 5199에 띄우고 `ENGINE_URL`을 2번 게이트웨이로 지정한다.
|
||||
4. harness 실행 → `E2E_RETURNED_PRACTICE_DB_CLOSED_LOOP=1` + `E2E_RETURNED_PRACTICE_FIXTURE`로 spec 실행.
|
||||
5. 결과 기준: desktop 2/2 + mobile 2/2 = **4 passed**. 끝나면 컨테이너·프로세스를 모두 정리한다.
|
||||
|
||||
G4/G5 핵심 production bug 수정은 보존한다.
|
||||
|
||||
- G4 runtime SQL `digest(...)` → `app.digest(...)`
|
||||
- G5 JSONB bind는 `json.dumps` string이 아니라 dict/list object 전달
|
||||
- route는 nested Pydantic suite를 `body.model_dump()` dict로 깨지 않고 typed object로 전달
|
||||
|
||||
## 8. 최종 검증 명령과 현재 기준선
|
||||
|
||||
```powershell
|
||||
cd D:\workspace\vignette\apps\api
|
||||
$env:ENGINE_URL="http://127.0.0.1:9099"
|
||||
$env:ENGINE_MODE="claude_cli"
|
||||
$env:ENVIRONMENT="dev"
|
||||
$env:AUTH_DEV_LOGIN_ENABLED="true"
|
||||
C:\Users\encep\AppData\Local\Programs\Python\Python311\python.exe -m uvicorn app.main:app --host 127.0.0.1 --port 8000
|
||||
$py = 'C:\Users\encep\AppData\Local\Programs\Python\Python311\python.exe'
|
||||
$ruff = 'C:\Users\encep\AppData\Local\hermes\hermes-agent\venv\Scripts\ruff.exe'
|
||||
```
|
||||
|
||||
Public API process:
|
||||
### Backend full — 기준 API **898 passed**, gateway **58 passed**
|
||||
|
||||
```powershell
|
||||
cd D:\workspace\vignette
|
||||
powershell -NoProfile -ExecutionPolicy Bypass -File scripts\start-public-runtime.ps1
|
||||
Set-Location D:\workspace\vignette\apps\api
|
||||
& $py -X utf8 -B -m pytest -p no:cacheprovider app -q
|
||||
& $py -X utf8 -B -m pytest -p no:cacheprovider engine_gateway -q
|
||||
```
|
||||
|
||||
Web:
|
||||
### G8 executor/probe/helper/release governance — 기준 28 / 11 / 37 / 21
|
||||
|
||||
```powershell
|
||||
cd D:\workspace\vignette\apps\web
|
||||
fnm env --use-on-cd | Out-String | Invoke-Expression
|
||||
fnm use 22.22.3
|
||||
npm run dev -- --host localhost --port 5173
|
||||
Set-Location D:\workspace\vignette
|
||||
& $py -X utf8 -B -m unittest scripts/test_serve_nas_preview_rollback_executor.py
|
||||
& $py -X utf8 -B -m unittest scripts/test_probe_nas_preview_g8_rollback.py
|
||||
& $py -X utf8 -B -m unittest scripts/test_launch_nas_preview_g8_helpers.py
|
||||
& $py -X utf8 -B -m pytest -p no:cacheprovider scripts/test_outcome_os_release_agent.py -q
|
||||
& $ruff check scripts/serve-nas-preview-rollback-executor.py scripts/test_serve_nas_preview_rollback_executor.py scripts/probe-nas-preview-g8-rollback.py scripts/test_probe_nas_preview_g8_rollback.py scripts/launch-nas-preview-g8-helpers.py scripts/test_launch_nas_preview_g8_helpers.py scripts/run-outcome-os-release-agent.py scripts/test_outcome_os_release_agent.py
|
||||
```
|
||||
|
||||
## 최근 주요 변경 파일
|
||||
### Web contract/type/build
|
||||
|
||||
- `apps/web/e2e/support.ts`
|
||||
- `apps/web/e2e/auth.spec.ts`
|
||||
- `apps/web/e2e/learner.spec.ts`
|
||||
- `apps/web/e2e/session-layout.spec.ts`
|
||||
- `apps/web/e2e/session-review.spec.ts`
|
||||
- `apps/web/e2e/session-persistence.spec.ts`
|
||||
- `apps/web/e2e/teacher.spec.ts`
|
||||
- `apps/web/e2e/voice.spec.ts`
|
||||
- `apps/web/e2e/voice-success.spec.ts`
|
||||
- `apps/web/e2e/public-auth-turn.spec.ts`
|
||||
- `apps/web/e2e/db-persistence.spec.ts`
|
||||
- `apps/web/e2e/README.md`
|
||||
- `apps/web/playwright.config.ts`
|
||||
- `apps/web/src/pages/Admin.tsx`
|
||||
- `apps/web/src/pages/Login.tsx`
|
||||
- `apps/web/src/pages/Session.tsx`
|
||||
- `apps/web/src/pages/Settings.tsx`
|
||||
- `apps/web/src/pages/session/session.css`
|
||||
- `apps/web/src/components/avatar/ClientAvatar.tsx`
|
||||
- `apps/web/src/components/avatar/persona.ts`
|
||||
- `apps/api/app/config.py`
|
||||
- `apps/api/app/main.py`
|
||||
- `apps/api/app/routes/admin.py`
|
||||
- `apps/api/app/routes/personas.py`
|
||||
- `apps/api/app/routes/users.py`
|
||||
- `apps/api/app/routes/voice.py`
|
||||
- `apps/api/app/test_session_turn_persistence.py`
|
||||
- `apps/api/app/test_runtime_policy.py`
|
||||
- `infra/docker-compose.yml`
|
||||
- `infra/.env.example`
|
||||
- `docs/HANDOFF.md`
|
||||
- `docs/dev_dashboard.html`
|
||||
- `apps/web/src/lib/api.ts`
|
||||
```powershell
|
||||
Set-Location D:\workspace\vignette\apps\web
|
||||
npm run check:api-types
|
||||
npm run typecheck
|
||||
npm run build
|
||||
```
|
||||
|
||||
### Dashboard SSOT + E2E — 기준 SSOT PASS(FAIL 0), unit 5/5, dashboard 10/10
|
||||
|
||||
```powershell
|
||||
Set-Location D:\workspace\vignette
|
||||
& $py -X utf8 -B scripts/check-dev-dashboard-ssot.py
|
||||
& $py -X utf8 -B -m pytest -p no:cacheprovider scripts/test_dev_dashboard_ssot.py -q
|
||||
|
||||
Set-Location D:\workspace\vignette\apps\web
|
||||
.\node_modules\.bin\playwright.cmd test e2e/dev-dashboard.spec.ts --project=chromium-desktop --project=chromium-mobile --workers=1 --reporter=line
|
||||
```
|
||||
|
||||
status count를 바꾸면 `scripts/check-dev-dashboard-ssot.py`의 `EXPECTED_STATUS_COUNTS`와
|
||||
`apps/web/e2e/dev-dashboard.spec.ts`의 `metrics.done`/`metrics.doing`을 **함께** 갱신한다.
|
||||
|
||||
### 격리 NAS 런타임 회기 E2E
|
||||
|
||||
```powershell
|
||||
Set-Location D:\workspace\vignette\apps\web
|
||||
$env:PLAYWRIGHT_BASE_URL='http://100.116.83.60:8088'
|
||||
$env:PLAYWRIGHT_SKIP_WEB_SERVER='1'
|
||||
node.exe .\node_modules\@playwright\test\cli.js test e2e/session-layout.spec.ts e2e/session-persistence.spec.ts `
|
||||
e2e/self-directed-learning-loop.spec.ts e2e/alliance-pulse.spec.ts e2e/outcome-trajectory.spec.ts `
|
||||
e2e/rupture-repair.spec.ts e2e/deliberate-practice.spec.ts e2e/calibration-transfer.spec.ts `
|
||||
e2e/supervision-research.spec.ts e2e/multimodal-alliance.spec.ts e2e/continuous-improvement-admin.spec.ts `
|
||||
--project=chromium-desktop --project=chromium-mobile --project=chromium-single-run --workers=1 --reporter=line
|
||||
```
|
||||
|
||||
전체 Playwright inventory는 610 tests / 44 files다. 실행 환경/API/DB를 정확히 맞추지 않고 fixture failure를
|
||||
제품 failure로 오인하지 않는다. 같은 blocker가 두 번 반복되면 전체 재시도 대신 원인·증거·수정 계획을 먼저 보고한다.
|
||||
|
||||
## 9. 핵심 변경 파일
|
||||
|
||||
- `scripts/launch-nas-preview-g8-helpers.py` · `scripts/test_launch_nas_preview_g8_helpers.py` (신규, Gate6 계약)
|
||||
- `scripts/serve-nas-preview-rollback-executor.py` · `scripts/test_serve_nas_preview_rollback_executor.py`
|
||||
- `scripts/probe-nas-preview-g8-rollback.py` · `scripts/test_probe_nas_preview_g8_rollback.py`
|
||||
- `scripts/run-outcome-os-release-agent.py` · `scripts/test_outcome_os_release_agent.py`
|
||||
- `scripts/check-dev-dashboard-ssot.py` · `scripts/test_dev_dashboard_ssot.py`
|
||||
- `scripts/check-g7-external-proof.py` · `scripts/soak-public-voice-websocket.py` ·
|
||||
`scripts/capture-g7-runtime-evidence.py` · `scripts/capture-g7-topology-evidence.py`
|
||||
- `apps/web/src/lib/uuid.ts` · `apps/web/e2e/insecure-context-uuid.spec.ts` (신규, 비-secure origin 결함 수정)
|
||||
- `apps/web/e2e/dev-dashboard.spec.ts` · `apps/web/e2e/returned-practice-db-closed-loop.spec.ts` ·
|
||||
`apps/web/e2e/harness/prepare-returned-practice-db.py`
|
||||
- `docs/dev_dashboard.html` · `docs/TODO.md` · `docs/README.md` · `docs/ops/backlog-2026-06-26.md`
|
||||
- `docs/ops/nas-preview-g8-rollback-proof-runbook.md` ·
|
||||
`docs/ops/evidence/nas-preview-g8-actual-rollback-2026-08-07.json` ·
|
||||
`docs/ops/nas-preview-deployment-evidence-2026-08-07.md`
|
||||
|
||||
## 10. 최종 원칙
|
||||
|
||||
- source-only PASS를 runtime DONE으로 부르지 않는다.
|
||||
- health만 보고 배포 완료라고 하지 않는다. auth, OpenAPI, assets, browser SSE→DB review를 함께 본다.
|
||||
- synthetic control plane을 실제 NAS rollback으로 과장하지 않는다.
|
||||
- G7 물리 마이크·사람 평가를 무동의/합성 데이터로 대체하지 않는다.
|
||||
- current/previous images와 DB backup을 확인하기 전 destructive operation을 실행하지 않는다.
|
||||
- 성공보다 정직한 fail-closed 증거가 우선이다.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue