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 증거가 우선이다.
|
||||
|
|
|
|||
|
|
@ -28,8 +28,40 @@
|
|||
| [`MEMORY_KNOWLEDGE_PERSONA_DESIGN.md`](./MEMORY_KNOWLEDGE_PERSONA_DESIGN.md) | 메모리·지식·페르소나·3-AI 정보비대칭 통합 아키텍처(스키마 DDL 포함) |
|
||||
| [`taxonomy.md`](./taxonomy.md) | 상담 발화 라벨 분류체계 SoT(기법/내담자상태/슈퍼바이저논평) |
|
||||
| [`DESIGN_CONCEPT.md`](./DESIGN_CONCEPT.md) | 디자인 시스템·색토큰·아바타·세션UX·3역할 대시보드 SSOT |
|
||||
| [`ops/video-c0schc1z71q-detailed-notes-2026-08-06.md`](./ops/video-c0schc1z71q-detailed-notes-2026-08-06.md) | 치료 동맹·성과 측정·균열 복구 영상의 타임스탬프별 상세 내용 기록과 연구 근거 분석 |
|
||||
| [`design-concepts/prompts/vignette-design-prompts-2026-06-27.md`](./design-concepts/prompts/vignette-design-prompts-2026-06-27.md) | 화면 시안 이미지 생성 프롬프트·공통 시각 방향 |
|
||||
|
||||
## 🚀 정식 전략 실행 로드맵
|
||||
|
||||
| 문서 | 역할 |
|
||||
|---|---|
|
||||
| [`ops/vignette-outcome-alliance-os-agentic-plan-2026-08-06.md`](./ops/vignette-outcome-alliance-os-agentic-plan-2026-08-06.md) | 제약에서 출발하지 않는 Outcome & Alliance OS 에이전틱 계획 — G0~G8 정식 승격, 상태는 대시보드·할 일은 TODO I절 |
|
||||
| [`ops/measurement-provenance-inventory-2026-08-06.md`](./ops/measurement-provenance-inventory-2026-08-06.md) | G0 기존 지표 provenance 전수표 — 시뮬레이션·모델추론·자기보고·운영관측 경계와 legacy adapter 계약 |
|
||||
| [`ops/alliance-calibration-report-prompt-1.2.0-2026-08-06.md`](./ops/alliance-calibration-report-prompt-1.2.0-2026-08-06.md) | G1 Alliance Core 합성 gold 보정 보고서 — 9/9 방향 판정, 최종 실패 0, 근거 recall 88.9% |
|
||||
| [`ops/alliance-calibration-report-prompt-1.2.0-readskewfix-2026-08-07.md`](./ops/alliance-calibration-report-prompt-1.2.0-readskewfix-2026-08-07.md) | G1 승격 기준 재보정 — 24/24 ready, 방향 9/9, 오류·재시도 0, evidence recall 94.4%·precision 73.9% |
|
||||
| [`ops/alliance-pulse-live-integration-evidence-2026-08-06.md`](./ops/alliance-pulse-live-integration-evidence-2026-08-06.md) | G1 실 DB/API 통합 증거 — 자기평가 선잠금, reveal 경계, 3관점×3축, 교수자 append-only supersession |
|
||||
| [`ops/outcome-trajectory-live-integration-evidence-2026-08-06.md`](./ops/outcome-trajectory-live-integration-evidence-2026-08-06.md) | G2 실 5회기 통합 증거 — 세 축·누락·safety 분리, 멱등 체크인, role-safe 관계 기억·RLS 및 실제 DB 악화/단발성 noise control |
|
||||
| [`ops/rupture-repair-live-integration-evidence-2026-08-06.md`](./ops/rupture-repair-live-integration-evidence-2026-08-06.md) | G3 파열·수선 통합 증거 — Scenario Director, 실제 learner-turn 자동 감지, fast/deep reconciliation, API/DB·RLS·교수자 정정 |
|
||||
| [`ops/deliberate-practice-live-integration-evidence-2026-08-06.md`](./ops/deliberate-practice-live-integration-evidence-2026-08-06.md) | G4 의도적 수련 통합 증거 — 5종 실행 처방, competency graph, 전이 mastery gate, 실제 API/DB·RLS·교수자 교정 |
|
||||
| [`ops/calibration-transfer-live-integration-evidence-2026-08-06.md`](./ops/calibration-transfer-live-integration-evidence-2026-08-06.md) | G5 자기보정·전이 통합 증거 — 불가역 prediction lock/reveal, calibration error 감소, unseen transfer, 합성 subgroup drift |
|
||||
| [`ops/outcome-os-g4-g5-production-caller-evidence-2026-08-07.md`](./ops/outcome-os-g4-g5-production-caller-evidence-2026-08-07.md) | G4/G5 실제 production caller 증거 — ready 평가·durable turn에서 처방 생성, prediction lock 뒤 failed 독립 관찰, model-run provenance와 replay·비승격 |
|
||||
| [`ops/outcome-os-self-directed-learning-loop-ui-evidence-2026-08-07.md`](./ops/outcome-os-self-directed-learning-loop-ui-evidence-2026-08-07.md) | 학생 자기주도 전체 루프 UI 증거 — 실제 `src` 홈 추천→회기→리뷰→G4 처방 재연습, desktop/mobile 2/2와 9장 시각 QA(390×844·320×568 포함); 로그인 없는 route fixture이며 실 DB 증거와 분리 |
|
||||
| [`ops/supervision-research-live-integration-evidence-2026-08-06.md`](./ops/supervision-research-live-integration-evidence-2026-08-06.md) | G6 감독·연구 OS 통합 증거 — 3클릭 attention queue, metadata-only 불일치, version drift, Phase 3 네 도메인 provenance |
|
||||
| [`ops/outcome-os-g6-ledger-producer-evidence-2026-08-07.md`](./ops/outcome-os-g6-ledger-producer-evidence-2026-08-07.md) | G6 scheduled 원장 파생 증거 — G0~G5 ledger에서 attention·curriculum gap·4-domain manifest를 직접 파생하고 호출자 계산을 제거 |
|
||||
| [`ops/outcome-os-g6-version-comparison-producer-evidence-2026-08-07.md`](./ops/outcome-os-g6-version-comparison-producer-evidence-2026-08-07.md) | G6 자동 version evaluator 실 DB 증거 — repo-approved synthetic baseline/candidate, measurement anchor 12개, drift/subgroup provenance와 0-duplicate replay |
|
||||
| [`ops/multimodal-alliance-live-integration-evidence-2026-08-06.md`](./ops/multimodal-alliance-live-integration-evidence-2026-08-06.md) | G7 멀티모달 동맹 통합 증거 — 단일 오디오 시계, 독립 text/voice 측정과 보정 융합, 역할 경계, 동의 철회·tombstone 삭제 |
|
||||
| [`ops/continuous-improvement-live-integration-evidence-2026-08-06.md`](./ops/continuous-improvement-live-integration-evidence-2026-08-06.md) | G8 지속 개선 통합 증거 — source→독립 red-team→human gate, 4종 증거, append-only 승인·rollback·incident DAG |
|
||||
| [`ops/outcome-os-g8-agentic-worker-evidence-2026-08-07.md`](./ops/outcome-os-g8-agentic-worker-evidence-2026-08-07.md) | G8 실제 agentic worker 코드 증거 — source hash·PII gate, generator, 독립 red-team 2개, variant judge 3개 이상, durable pending payload와 incident→adversarial 환류 |
|
||||
| [`ops/evidence/continuous-improvement-agentic-producer-live-2026-08-07.json`](./ops/evidence/continuous-improvement-agentic-producer-live-2026-08-07.json) | G8 scheduled producer 실증 — 실제 Claude 7 calls, dev PostgreSQL pending human gate, provenance/hash와 0-call replay |
|
||||
| [`ops/outcome-os-g7-wss-soak-evidence-2026-08-07.md`](./ops/outcome-os-g7-wss-soak-evidence-2026-08-07.md) | G7 실제 로컬 `/voice/ws` 전송 short soak — 철회·재연결·overflow 복구와 50분 실행 옵션, 물리 마이크/public WSS 경계 명시 |
|
||||
| [`ops/outcome-os-g7-external-proof-readiness-2026-08-07.md`](./ops/outcome-os-g7-external-proof-readiness-2026-08-07.md) | G7 외부 종료 증거 준비 — 선행 동의 원장, public runner v4, worker/Uvicorn·Linux topology high-water, 독립 human voice-gain과 canonical checker |
|
||||
| [`ops/outcome-os-g7-g8-visual-qa-2026-08-07.md`](./ops/outcome-os-g7-g8-visual-qa-2026-08-07.md) | G7/G8 데스크톱·모바일 시각 QA — G8 승인 차단 이유 상시 노출·키보드 제출, G7 타깃·포커스 수치 회귀 |
|
||||
| [`ops/outcome-os-deploy-preflight-evidence-2026-08-07.md`](./ops/outcome-os-deploy-preflight-evidence-2026-08-07.md) | G0~G8 배포 프리플라이트 — migration sentinel, G3~G8 독립 토큰, Compose fail-closed 전달 증거 |
|
||||
| [`ops/outcome-os-public-deployment-evidence-2026-08-07.md`](./ops/outcome-os-public-deployment-evidence-2026-08-07.md) | G8/AOS-011 공개 종료 증거 — clean release API·Pages 승격, 119-path OpenAPI, auth/OAuth, asset·브라우저·watchdog proof와 G7 잔여 경계 |
|
||||
| [`ops/outcome-os-clean-release-manifest-2026-08-07.json`](./ops/outcome-os-clean-release-manifest-2026-08-07.json) | dirty worktree의 G0~G8 릴리스 분류 manifest — related/mixed/excluded 전건 분류와 hunk-review-required 차단 |
|
||||
| [`ops/outcome-os-release-hunk-map-2026-08-07.json`](./ops/outcome-os-release-hunk-map-2026-08-07.json) | 혼합 파일 4개의 exact hunk SHA·포함/제외/split 결정과 생성형 API 계약 재생성 규칙 |
|
||||
| [`ops/evidence/outcome-os-release-only-2026-08-07.patch`](./ops/evidence/outcome-os-release-only-2026-08-07.patch) | clean HEAD 적용 검사를 통과한 G0~G8 release-only patch — 공식 API 타입 재생성 포함, 공개 배포 증거는 아님 |
|
||||
|
||||
## 🛠 개발·운영 가이드
|
||||
|
||||
| 문서 | 역할 |
|
||||
|
|
@ -46,6 +78,9 @@
|
|||
| 문서 | 역할 |
|
||||
|---|---|
|
||||
| [`ops/public-runtime-watchdog.md`](./ops/public-runtime-watchdog.md) | 공개 런타임 워치독 설치·복구 절차(런북) |
|
||||
| [`ops/nas-preview-g8-rollback-proof-runbook.md`](./ops/nas-preview-g8-rollback-proof-runbook.md) | G8 실제 receipt-bound image rollback 런북 — Gate6 helper 격리 계약 정정(소속 기준), 실행 순서, 금지 사항, 실패 복구 |
|
||||
| [`ops/evidence/nas-preview-g8-actual-rollback-2026-08-07.json`](./ops/evidence/nas-preview-g8-actual-rollback-2026-08-07.json) | G8 실제 rollback/restore 기계 판독 증거 — helper 격리, 두 executed receipt, HMAC journal 체인, DB lifecycle 집계, 정리 상태 |
|
||||
| [`ops/public-db-recovery-rehearsal-2026-08-07.md`](./ops/public-db-recovery-rehearsal-2026-08-07.md) | 공개 DB 데이터 교체 사고의 RLS/volume 포렌식, 이중 dump, 격리 복구·최신 migration/API 리허설과 승인형 cutover/rollback 게이트 |
|
||||
| [`ops/server-migration-runbook-draft.md`](./ops/server-migration-runbook-draft.md) | 서버 이관 런북 초안(자택→학교 정보처) — 이식성 이슈·DB dump/restore·엔진 모드 전환·검증 (초안) |
|
||||
| [`ops/pilot-usage-guide-draft.md`](./ops/pilot-usage-guide-draft.md) | 파일럿 사용 지침 초안(대학원생 ~20명) — 접속·시간 기반 회기·라이브 코칭·유의사항 (초안) |
|
||||
| [`ops/tailscale-vnet-runtime-2026-06-27.md`](./ops/tailscale-vnet-runtime-2026-06-27.md) | Tailnet 런타임 + 공개 vnet 외부 게이트 추적 |
|
||||
|
|
@ -68,6 +103,7 @@
|
|||
|---|---|
|
||||
| [`decisions/backend-node-transition.md`](./decisions/backend-node-transition.md) | 백엔드 언어 방향(FastAPI 유지·Node 계약 우선 전환) |
|
||||
| [`decisions/voice-s2s-poc.md`](./decisions/voice-s2s-poc.md) | 음성 s2s 2차 PoC 채택 판단 기준 |
|
||||
| [`decisions/outcome-alliance-measurement-ledger.md`](./decisions/outcome-alliance-measurement-ledger.md) | Outcome & Alliance append-only 측정 원장, source/perspective 경계, 전진 복구·롤백 결정 |
|
||||
|
||||
## 🧪 Phase 3 파일럿 (forward — 아직 미실행)
|
||||
|
||||
|
|
|
|||
305
docs/TODO.md
305
docs/TODO.md
|
|
@ -13,15 +13,53 @@
|
|||
## A. 배포·공개 런타임 게이트 [환경]
|
||||
> 출처: `ops/backlog-2026-06-26.md` B2 · 대시보드 운영 탭 · `ops/tailscale-vnet-runtime-2026-06-27.md` · `ops/public-runtime-watchdog.md` · `HANDOFF.md`
|
||||
|
||||
- [ ] **공개 DB 계정·회기 복구 안정화** — 2026-08-07 18:26 KST owner 승인으로 recovered named volume을
|
||||
공개 `vignette-dev-db:55432`에 전환했다. public 기준 users 84/sessions 30/turns 705,
|
||||
Google 계정 16/소유 회기 30, orphan 0, health·engine·OAuth·watchdog가 정상이다. 전환 직전 container와
|
||||
dump, 원본 volume을 보존한다. 남은 것은 사용자가 Google 재로그인한 뒤 본인 과거 회기가 보이는지
|
||||
확인하는 일과 자동 백업 운영화다. 기존 인증 세션은 복사하지 않아 재로그인이 필요하다.
|
||||
상세: `ops/public-db-recovery-rehearsal-2026-08-07.md`.
|
||||
- [ ] **자동 DB 백업·정기 복원 리허설** — `scripts/backup-vignette-db.ps1`의 custom dump→TOC 검증→
|
||||
SHA manifest 원자 게시와 현재 public-target 일회용 복원(129 tables/9,646 rows/orphan 0)을 통과했다.
|
||||
남은 범위는 예약 실행, 실패 알림, NAS/off-host 암호화 복제, 주기적 restore drill과 보존 정책이다.
|
||||
- [ ] **공개 Google OAuth 실제 `/turn` proof** — 로그인 계정으로 `storageState` 캡처 → `E2E_PUBLIC_AUTH=1` +
|
||||
`chromium-public-auth` 1회 통과. 실행 명령은 대시보드 "다음 실행 명령"/`HANDOFF.md`. (소유자 보류 중)
|
||||
- [ ] **실배포 `infra/.env` owner-secret + real preflight** — `APP_DB_PASSWORD`, `OAUTH_GOOGLE_CLIENT_ID/SECRET`,
|
||||
`OPENAI_API_KEY`, `SESSION_SECRET`, prod-safe engine/voice flags 주입 후 `check-deploy-preflight.py` DB 모드 + 배포지 health.
|
||||
- [ ] **Compose 패키징용 `infra/.env` owner-secret** — 현재 운영 direct-runtime은 독립 G3~G8 token 주입,
|
||||
non-owner app-role preflight와 공개 health를 통과했다. Compose로 전환할 때만 `APP_DB_PASSWORD` 등 패키징
|
||||
비밀과 prod-safe engine/voice flags를 별도 주입하고 compose profile preflight를 다시 통과한다.
|
||||
- [ ] **`vnet.18ka.net`/`api-vnet.18ka.net` 공개 live** — DNS A record 등록, cloudflared의 `18ka.net` zone 권한,
|
||||
Google redirect URI `https://api-vnet.18ka.net/auth/callback` 등록. (현재 prod API 8001은 health OK)
|
||||
- [x] **비-secure origin에서 회기 리뷰 크래시 수정** — 2026-08-07 격리 NAS 프리뷰
|
||||
`http://100.116.83.60:8088`(평문 HTTP·비-localhost)에서 회기 리뷰 라우트가
|
||||
`crypto.randomUUID is not a function`으로 error boundary에 잡혔다. `crypto.randomUUID`는 secure
|
||||
context에서만 노출되는데 제품 코드 18곳이 fallback 없이 직접 호출했고, 특히
|
||||
`RuptureRepairCard.tsx`는 렌더 시점(`useRef`)에 호출해 화면 전체가 죽었다. 릴리스 게이트는
|
||||
localhost 후보 스택(secure context)에서만 돌아 이 경로를 밟은 적이 없다. `src/lib/uuid.ts`의
|
||||
`randomUuid()`로 통일했고 fallback도 `crypto.getRandomValues`를 우선 사용해 idempotency key의
|
||||
예측 불가능성을 유지한다. 회귀는 `e2e/insecure-context-uuid.spec.ts` 6/6으로 고정했다(직접 호출
|
||||
0건 검사 포함). **주의: 이 수정은 아직 NAS 프리뷰에 배포되지 않았다** — 배포된 SHA
|
||||
`6030a677…c611`은 여전히 결함이 있는 빌드다.
|
||||
- [ ] **워치독 engine readiness 사각지대** — 2026-08-07 21:5x KST 공개 API가 `engine=false`
|
||||
(`engine_detail=empty engine response`)로 degraded인데 워치독은 `lastResult=0`(정상)으로 끝났다.
|
||||
원인은 판정 기준이다. `scripts/watch-public-runtime.ps1:107`의 engine 검사는 게이트웨이
|
||||
`/health`의 `ok==true`만 보는데 이 엔드포인트는 claude readiness probe를 돌리지 않는다.
|
||||
같은 파일 `:112`의 API 검사도 `environment=="prod" && db`만 보고 `engine`은 보지 않는다.
|
||||
실제로 게이트웨이 9099의 `/ready?force=true`는 503이었고(readiness 캐시 TTL 30초라 stale 아님),
|
||||
NAS용 게이트웨이 9100은 정상이었다. **결정적 확인:** 같은 `.env`·같은 `claude` CLI로 새 게이트웨이
|
||||
프로세스를 별도 포트(9199)에 띄우자 `/ready?force=true`가 즉시 `ok=true, detail=OK`를 반환했다.
|
||||
따라서 CLI나 인증 문제가 아니라 16:06부터 상주하던 9099 **프로세스의 claude 세션만 죽은** 형태이고,
|
||||
프로세스가 살아 있으므로 `/health`의 `ok`만 보는 워치독은 이를 감지할 수 없다.
|
||||
조치: (1) engine 판정을 게이트웨이 `/ready`(또는 API health의 `engine` 필드)로 바꾸고 복구 후
|
||||
재검증에도 같은 기준을 적용한다. (2) 9099 재기동으로 공개 API를 복구한다 — 공개 서비스 영향이 있어
|
||||
소유자 승인 후 수행한다.
|
||||
- [ ] **재부팅 후 watchdog smoke** — 실제 Windows 재부팅 후 엔진/API/터널 자동 복구 + public `/turn` 실측.
|
||||
DNS 개통 후 `api-vnet.18ka.net`을 `-AdditionalPublicHealthUrls`로 명시 추가.
|
||||
- [ ] **음성 캐스케이드 live** — 실제 Deepgram interim/final WSS, 물리 마이크, 공개 WSS, 50분 양방향 장시간 실측.
|
||||
- [ ] **음성 캐스케이드 live** — Deepgram streaming adapter와 interim/final·word timestamp, bounded event queue,
|
||||
운영 10MiB 사전 거부와 실제 PostgreSQL 동시 철회 직렬화는 코드/내부 회귀까지 완료했다. 종료에는 운영
|
||||
key·quota를 사용한 live interim/final, 기대 provider/model과 일치하는 authenticated public WSS ready,
|
||||
명시 동의 물리 마이크, 50분 양방향 장시간 실측과 실제 worker/proxy의 RSS·queue high-water가 모두 필요하다.
|
||||
|
||||
> **완료된 격리 검증 경로:** NAS 프리뷰 `http://100.116.83.60:8088`을 전용 Compose 프로젝트·포트·네트워크·볼륨에 배포하고 실제 브라우저 회기와 review API 저장 축어록을 확인했다. current source exact SHA `6030a677af7e87cbfabc422b553d108d53414fd3c446548734a13b036d35c611`은 전체 browser E2E 108/108, postdeploy SSE→DB review, health 3/3, OpenAPI 126·auth 401·G0~G8 route를 통과했다. 기존 프로젝트를 대상으로 한 중단·재생성 명령은 실행하지 않았다. 매일 04:30 KST `Vignette 회기 E2E 정기 검증`(automation id `vignette-e2e`)은 ACTIVE다. 새 기능이 material milestone이고 release-only patch 결정성·manifest checker·canonical clean-index·NAS preflight를 통과하며 배포 SHA가 달라질 때만 같은 격리 프리뷰를 갱신한다. 첫 스케줄 실행 이력은 아직 증거에 포함하지 않았다. 증거: [배포 증거](./ops/nas-preview-deployment-evidence-2026-08-07.md), [current 기계 판독 증거](./ops/evidence/nas-preview-current-deploy-2026-08-07.json), [브라우저 증거](./ops/evidence/nas-preview-live-turn-2026-08-07.png). 이는 G8 실제 receipt-bound rollback이나 미승인 물리 마이크·부재한 Deepgram 운영 키를 대체하지 않는다.
|
||||
- [ ] **claude_cli ↔ Anthropic API live 동일성** — provider 라우팅·모델 탐색·설정 저장 경로는 구현 완료. 남은 게이트는 연구팀/기관 `ANTHROPIC_API_KEY`를 게이트웨이 호스트에 주입한 뒤 같은 프롬프트의 live 응답·계량·오류 표면화를 비교하는 것이다.
|
||||
|
||||
## B. 외부 거버넌스 [외부]
|
||||
|
|
@ -45,9 +83,10 @@
|
|||
|
||||
- [ ] **s2s 2차 비교 PoC 구현·실측** — `decisions/voice-s2s-poc.md` 기준표(전사·안전·감사·지연)로 20턴 비교 리포트 → 최종 채택/폐기.
|
||||
- [ ] **fast-loop 외부 폴백 구현** — 로컬 상주 분류기 우선 + Haiku/Solar 폴백, 전송 가드(데이터주권·로그·동의)·관측.
|
||||
- [ ] **대체 TTS provider 선정·통합** — 상업 라이선스 명확한 provider. 로컬 Higgs 실제 문장 합성은
|
||||
`-UseHiggsVoice` 개발 전용 경로로 완료했으며, 무참조 synthetic seed만 사용한다. 서비스 배포 provider와
|
||||
dev sample/Higgs 경로 분리는 계속 유지한다.
|
||||
- [x] **운영 TTS provider 선정·통합** — 운영 경로는 OpenAI API `gpt-4o-mini-tts`로 고정했다. OpenAI
|
||||
Services Agreement의 고객 애플리케이션·최종 사용자 통합권과 출력 소유 조항을 근거로 기록하고, 공식 TTS
|
||||
가이드가 요구하는 “AI 생성 음성·사람 음성 아님” 고지를 회기 시작 전과 진행 중 UI에 상시 노출한다. 로컬
|
||||
Higgs와 sample provider는 무참조 synthetic seed를 쓰는 개발 전용 경로로 계속 분리한다.
|
||||
- [ ] **재귀학습 fine-tuning 파이프라인** — 동의/데이터셋 게이트 충족 후. few-shot 자동갱신은 선행 가능.
|
||||
- [ ] **헬스 retention 적용·검증** — raw 90일 / rollup 365일. escalation/그룹 자동화는 미도입 고정.
|
||||
- [x] **한신대 실사용 피드백 1차 개선팩** — `ops/hanshin-feedback-improvement-plan-2026-07-03.md`,
|
||||
|
|
@ -134,9 +173,261 @@
|
|||
**남은 것**: 저장소 초대(소유자 직접 실행).
|
||||
- ⏸️ **외부 대기(구현 착수 금지)**: '실패(게임오버)' 정의 / 검수 표준화 항목 / AI 평가 노출 정책 / 페르소나 추가 제작 — 전부 연구팀 몫, 수신 시 반영.
|
||||
|
||||
## I. Outcome & Alliance OS — 정식 승격 로드맵 [에이전틱 구현]
|
||||
|
||||
> **2026-08-06 정식 승격.** 상세 계약·품질 목표·의존성은
|
||||
> `ops/vignette-outcome-alliance-os-agentic-plan-2026-08-06.md`, 상태는 SSOT 대시보드의
|
||||
> `Outcome & Alliance OS` G0~G8 계획 카드가 소유한다. 아래 작업은 사람 개발자 스프린트가 아니라
|
||||
> 에이전트가 RED → 구현 → 통합 → 적대 검증 → runtime proof → SSOT 동기화로 닫는 증거 DAG다.
|
||||
|
||||
### I-0. G0 Measurement Truth Foundation
|
||||
|
||||
- [x] `app.measurement_event`, `app.measurement_instrument`, `audit.model_run` 계약과 provenance enum 구축.
|
||||
- [x] 기존 `rapport_credit`, `alliance_level`, fast/deep 평가, Phase 3 KPI의 source map과 adapter 구축.
|
||||
- [x] `simulated_state`·`model_inferred`·`agent_reported`·`learner_reported`·`human_rated`·
|
||||
`observed_runtime` 층을 섞는 read model 실패 테스트 구축.
|
||||
- [x] goal mismatch, task mismatch, empathic miss, withdrawal, confrontation, successful/failed repair를 포함한
|
||||
첫 benchmark pack 구축.
|
||||
- [x] **G0 종료 GATE 재검증** — post-merge dump의 disposable clone에 migrations 14/15/16을 각각 두 번
|
||||
원자 재적용하고 current source census 29/29 producers, unknown/missing/null/orphan/호환성 위반 0을 확인했다.
|
||||
증거: `ops/evidence/g0-provenance-census-scratch-post-g6-g8-20260807.json`.
|
||||
|
||||
### I-1. G1 Alliance Core
|
||||
|
||||
- [x] goal/task/bond의 내담자·관찰자·수련생 3관점 측정과 회기 전·중·후 pulse 구현.
|
||||
- [x] 현재 `alliance_level`을 legacy simulation signal로 격리하고 새 alliance read model 구축.
|
||||
- [x] 학습자 리뷰의 관점 차이·근거 장면 UI 구현.
|
||||
- [x] 교수자 재평가를 append-only measurement로 구현.
|
||||
- [x] **G1 종료 GATE** — 세 관점 독립 실행·저장, 축별 비가림 UI, 골든 장면 분류·근거 span,
|
||||
평가 실패의 정상 점수 폴백 0건. **증거:** G1 focused backend 43 passed, PostgreSQL RLS/append-only/reveal
|
||||
실원장 smoke, desktop/mobile Alliance Pulse 6/6 및 기존 회기 리뷰 12/12, prompt `1.2.0` 합성 gold 방향
|
||||
정확도 9/9·최종 실패 0·근거 recall 88.9%. 실제 Session 진입 전 pre pulse를 fail-closed로 잠그고 첫 라운드
|
||||
뒤 mid pulse를 비차단형으로 연결했으며, stable ID 재시도·변조 409·교차 회기 404를 통과했다. desktop/mobile
|
||||
pre·mid focused E2E 2/2와 시각 증거 4장을 보존했다. 추가 source API/dev DB 실증은 한 회기의
|
||||
`pre(degraded/insufficient_transcript) → mid(ready) → post(ready)`를 실제 turn UUID·ended_at과 함께 닫고,
|
||||
동일 응답 안의 awaiting/external-event read skew와 degraded terminal DB 제약을 교정했다. 증거:
|
||||
`ops/evidence/alliance-pulse-pre-mid-post-live-2026-08-07.json`. 현재 승격 기준 prompt 1.2+read-skew fix+
|
||||
보수적 trailing-comma JSON 복구의 전체 재보정은 24/24 ready, 기대 방향 9/9, 오류·재시도 0,
|
||||
evidence recall 94.4%·precision 73.9%를 기록했다. human-AI ICC는 내부 구현 종료가 아니라 Phase 3 외부 품질 목표다.
|
||||
|
||||
### I-2. G2 Longitudinal Outcome Trajectory
|
||||
|
||||
- [x] 최소 5회기 사례 arc와 예상 성과 궤적 분포 구현.
|
||||
- [x] safety event와 outcome deterioration의 별도 계약·표시 구현.
|
||||
- [x] 초기 1~5회기 off-track 탐지와 다음 확인 질문 구현.
|
||||
- [x] case memory에 관계 사건·합의·미해결 균열을 role-safe하게 추가.
|
||||
- [x] **G2 종료 GATE** — 5회기 기억·목표·관계 연속성, 악화 benchmark 조기경보 목표,
|
||||
false alert·uncertainty 병기, 합성 궤적의 임상 결과 오표시 0건. **증거:** 4-case×5-session
|
||||
benchmark status 정확도 1.0·false alert 0·critical miss 0, 실제 DB/API 5회기 연속 case와 15개 독립 축,
|
||||
멱등 재시도·변조 409·학습자/코호트 RLS·role-safe 관계 기억 통과, desktop/mobile 체크인·궤적 12/12와
|
||||
G1/기존 리뷰 15/15 회귀 통과. 추가 실제 PostgreSQL producer→store→evaluator smoke는 합성 교육용
|
||||
악화 case의 `on_track→off_track→deteriorating`과 단발성 noise control의
|
||||
`on_track→watch→on_track`·false alert 승격 0건을 실제 10회기·30 measurement·case별 5 revision으로
|
||||
검증했고 learner/teacher 최신 projection이 일치했다. 원점수는 증거 JSON에 복제하지 않았고 임상 주장은 금지한다.
|
||||
|
||||
### I-3. G3 Rupture & Repair Lab
|
||||
|
||||
- [x] rupture/repair 상태머신과 이벤트 원장 구현.
|
||||
- [x] Scenario Director의 자연스러운 균열 주입 구현.
|
||||
- [x] 실시간 감지, 회기 후 장면 재생, 복구 분기 비교 구현.
|
||||
- [x] 후속 발화에서 복구된 fast-loop 경고의 deep-loop reconciliation 구현.
|
||||
- [x] **G3 종료 GATE** — 유형별 탐지·복구 benchmark, missed/partial/resolved E2E, 순응 유도 judge
|
||||
gaming 0건, 문장 암기 전략의 변형 사례 실패 검증. **증거:** 11-case macro-F1 1.0·복구 상태
|
||||
accuracy 1.0, critical miss/judge gaming/암기 false resolution 0, Scenario/G3 107 passed,
|
||||
G0~G8 계약 251 passed, desktop/mobile 리뷰 50/50, 실제 API/DB durable-turn·멱등·RLS·교수자
|
||||
append-only correction·최종 resolved 검증. 기존 수동 write smoke의 missed→partial→resolved와 별도로,
|
||||
실제 `submit_turn`/공용 finalizer callback만 사용한 자동 runtime PostgreSQL smoke에서 3 episode·10 observation·
|
||||
3 reconciliation, 세 상태, learner/teacher read projection, 세션 밖 evidence 참조 0건을 재현했다.
|
||||
|
||||
### I-4. G4 Deliberate Practice Engine
|
||||
|
||||
- [x] 평가 장면을 원자적 practice prescription으로 변환.
|
||||
- [x] replay, branch, constrained response, voice retry, difficulty ladder 구현.
|
||||
- [x] competency graph와 attempt evidence 저장.
|
||||
- [x] 약한 역량과 망각 위험에 따른 다음 연습 자동 선택.
|
||||
- [x] **G4 종료 GATE 재검증** — 모든 코칭 카드의 실행 가능한 재연습 연결, 전후 비교·근거 저장,
|
||||
미지 사례 전 mastery 금지, 점수 보상 해킹 benchmark 통과. **증거:** G4 core/store/API 54 passed,
|
||||
실제 API/DB durable-turn 처방→학습자 시도→교수자 append-only correction, 멱등 retry/변조 409,
|
||||
learner/cohort RLS, reward-claim `practicing`·`mastery_allowed=false`, G4 UI 8/8 및 G1~G4 리뷰 58/58.
|
||||
replay/branch/constrained response/voice retry/difficulty ladder 카드 CTA가 학습자 홈→사전 설정→실제 새 Session으로
|
||||
이어지고 launch intent가 URL과 화면에 유지되는 desktop/mobile 22/22 및 통합 59/59를 통과했다. 추가 실제
|
||||
PostgreSQL production caller smoke는 ready 평가·durable turn 2개에서 G4 submission/card/prescription/snapshot/
|
||||
decision 각 1개를 자동 생성하고 재실행 시 같은 deterministic submission/card replay, episode·attempt·mastery·
|
||||
transfer 승격 0건을 확인했다. 실제 API/DB/엔진 smoke는 prescriptions 2·episodes 6·attempts 6·
|
||||
append-only correction 1·ready observer model-run 2를 통과했다. post-merge disposable clone의 브라우저는
|
||||
route mock 0으로 desktop 2/2·mobile 2/2, G4 POST·read-model reload·before/after·멱등 retry를 확인했고
|
||||
최종 episode/attempt/ready observer 각 1, UUID 노출·마이크 호출·overflow·중복 0이다.
|
||||
증거: `ops/evidence/scratch-browser-returned-practice-closed-loop-evidence-20260807.json`.
|
||||
- [x] **학생 자기주도 전체 루프 UI 계약** — 실제 `src` 학습 홈 추천→P1·목표 선택→텍스트/SSE 회기→
|
||||
종료·리뷰→G4 공감적 반영 처방→키보드 CTA→새 회기 사전 설정·시작까지 desktop/mobile **2/2**,
|
||||
typecheck와 고정 캡처 9장 시각 QA를 통과했다. 홈 CTA는 40px→44px 및 4.5:1 이상 대비로 보정했고,
|
||||
390×844·320×568 활성 회기의 아바타/문구 겹침 0·축어록/입력창 카드 내부 포함과 리뷰→재연습의
|
||||
내부 criterion/counterevidence/UUID/영문 이론키 비노출을 고정했다. 리뷰 1~5 척도는 320px에서
|
||||
3+2 반응형 배열로 전환해 다섯 선택지 44px 이상·내부/페이지 overflow 0을 desktop/mobile 8/8로 고정했다. 이 검증은 모든
|
||||
미소유 `/api/**`를 404로 닫은 로그인 없는 route fixture이며 실제 API/DB 폐루프라고 주장하지 않는다.
|
||||
상세: `docs/ops/outcome-os-self-directed-learning-loop-ui-evidence-2026-08-07.md`.
|
||||
|
||||
### I-5. G5 Calibration Mirror & Transfer
|
||||
|
||||
- [x] 외부평가 공개 전 자기평가 잠금 흐름 구현.
|
||||
- [x] 역량별 calibration error와 신뢰도 구간 계산.
|
||||
- [x] 과신·과소신 맞춤 메타인지 연습 자동 처방.
|
||||
- [x] 인구통계·관계스타일·난이도를 바꾼 transfer suite 구현.
|
||||
- [x] **G5 종료 GATE 재검증** — 자기평가 오염 방지·수정 이력, 반복 연습의 calibration error 감소,
|
||||
새 사례 역량 유지, subgroup 평가 드리프트 리포트. **증거:** 4-case expectation accuracy 1.0,
|
||||
lock 뒤 오염 거부 1, calibration MAE 0.667→0.267, unseen 4/4 전이, 암기 false verification 0,
|
||||
합성 subgroup drift 0.667 탐지. 실제 API/DB durable-turn revision→lock→독립 reveal·멱등/409·RLS,
|
||||
G5 UI 4/4, G1~G5+기존 리뷰 62/62, typecheck/build 통과. transfer CTA는
|
||||
`novelty=unseen_transfer`·suite·trial을 학습자 홈→사전 설정→실제 Session까지 보존하며 desktop/mobile
|
||||
2/2와 실제 세션 통합 2/2를 통과했다. 추가 production caller 실 DB smoke는 prediction lock 전 observation
|
||||
0건, lock callback 뒤 `failed`·`model_inferred`·`independent_observer` 관찰과 turn/session provenance
|
||||
model-run 각 1개를 확인했다. 재실행 중복 0, passed·assessment·transfer suite/trial/승격 0이며
|
||||
learner/teacher projection이 일치한다. 학생 후속 종료 회기에서 actual-execution POST·422 대기·503 재시도·
|
||||
성공 reload·멱등 재확인 UI와 내부 UUID 비노출은 desktop/mobile 16/16으로 연결했다. 실제 API/DB/엔진
|
||||
smoke는 execution·model-inferred·independent observer·instrument/evidence/model-run provenance 각 1과
|
||||
멱등·오류 출처·미종료·타학습자·교차 cohort 차단을 통과했다. 같은 post-merge disposable clone 브라우저는
|
||||
desktop/mobile 4/4로 G5 POST→reload→before/after와 1→1 멱등 재확인을 통과했고 UUID 노출·마이크 호출·
|
||||
중복 원장 0이다. 합성 교육 fixture이고 임상·실제 숙달 주장은 금지한다.
|
||||
|
||||
### I-6. G6 Supervision & Research OS
|
||||
|
||||
- [x] attention queue와 learner drilldown을 새 측정 원장 기반으로 재구축.
|
||||
- [x] 교수자 정정·AI 불일치를 calibration dataset으로 연결.
|
||||
- [x] cohort curriculum gap, model drift, instrument version 비교 구현.
|
||||
- [x] Phase 3 evidence manifest에 alliance/rupture/transfer/calibration 추가.
|
||||
- [x] **G6 종료 GATE 재검증** — 위험·정체·미해결 관계 사건 3클릭 이내, 모든 집계의 원장 drilldown,
|
||||
모델·척도 버전별 재현 리포트, public/teacher/learner 경계·감사 로그 검증. **증거:** 6-signal
|
||||
queue 순서 정확·drilldown 1/상한3, metadata-only dataset raw transcript 0, version drift 탐지,
|
||||
Phase3 네 도메인 전건. 실제 API/DB 멱등/409·AI-view·역할/cohort RLS·audit, version/subgroup/artifact
|
||||
provenance hydrate 통과. 추가 scheduled producer는 G0~G5 실제 원장에서 attention/gap을 파생하고,
|
||||
repo-approved synthetic baseline/candidate와 실제 measurement anchor 12개를 자동 비교해 batch 2·report 1·
|
||||
subgroup 2를 저장했다. replay 중복 0, periodic 6 cohort 성공, drift/provenance hydrate, transcript 0·비임상
|
||||
경계를 확인했다. G6 focused 37 passed, G6 UI desktop/mobile 6/6, typecheck/build 통과. 최신
|
||||
safety-event metadata-only 우선 producer는 post-merge disposable clone의 current source에서 derive 201,
|
||||
`safety_boundary(1) > deterioration(2)`, direct pointer 각 1, source detail canary 1·G6 projection canary 0,
|
||||
raw transcript/임상 주장 0을 통과했다. NAS 승격은 배포 증거이지 internal 종료 조건과 분리한다. 상세:
|
||||
`ops/outcome-os-g6-ledger-producer-evidence-2026-08-07.md`,
|
||||
`ops/outcome-os-g6-version-comparison-producer-evidence-2026-08-07.md`,
|
||||
`ops/evidence/g6-safety-priority-runtime-scratch-20260807.json`.
|
||||
|
||||
### I-7. G7 Multimodal Alliance
|
||||
|
||||
- [x] STT word timestamp, silence, overlap, interruption, prosody feature event 통합.
|
||||
- [x] 텍스트·음성 평가 독립 산출 후 calibrated fusion 구현.
|
||||
- [x] 장면별 오디오 재생과 음성 재연습 구현.
|
||||
- [x] 비언어 특징의 과도한 임상 추론 차단 계약·테스트 구현.
|
||||
- [x] Deepgram streaming STT adapter, interim/final·word timestamp·provider event 계약과 OpenAI batch fallback 구현.
|
||||
- [x] 저장 word token을 deployment `SESSION_SECRET` 기반 HMAC-SHA256 pseudonym으로 전환하고 원문 미보존을 고정.
|
||||
- [x] 열린 streaming 중 동의를 1초마다 재검사해 철회 시 provider 전송·후속 저장을 중단하는 fail-closed 계약 구현.
|
||||
- [x] 음성 단절/실패 뒤 작성 중 텍스트를 보존하고 텍스트 계속하기·키보드 가능한 음성 재연결 recovery UX 구현.
|
||||
- [x] 운영 TTS를 OpenAI API `gpt-4o-mini-tts`로 고정하고 공식 Services Agreement의 앱 통합·출력 소유 근거와
|
||||
최종 사용자 AI 생성 음성 고지를 제품·증거 계약에 반영.
|
||||
- [x] authenticated WSS `ready`에 STT/TTS provider·model을 노출하고, 마이크 열거·캡처 없는 public preflight가
|
||||
네 기대값과의 정확 일치를 검사하도록 구현. full soak만 명시적 `--confirm-physical-capture` 뒤 장치를 연다.
|
||||
- [x] synthetic gold target에 대해 text-only와 calibrated multimodal 출력을 직접 비교하는 1-MAE benchmark를
|
||||
구현하고 `0.9533→0.9673`, 실측 gain `+0.014`(최소 `0.01`)와 gold 변조 반례를 통과했다.
|
||||
- [x] consent mutation·timeline·measurement를 session 공통 PostgreSQL transaction lock으로 직렬화하고 실제
|
||||
개발 DB 동시 철회에서 미커밋 중 writer 대기, 철회 커밋 뒤 저장 차단, timeline row `0`을 확인했다.
|
||||
- [x] 10MiB audio buffer를 append 전에 거부하고 streaming event queue `32`, Deepgram receive queue `16`을
|
||||
고정했다. 단일 프로세스 cap probe는 10MiB high-water·초과 거부·clear 뒤 RSS 기준선 복귀를 확인했다.
|
||||
- [x] 첫 음성 사용은 처리 목적·전송/전사·파생 신호·30일 보존·원음 미보존·철회/삭제·텍스트 대안을 설명하고,
|
||||
서버 동의 원장 성공 전 `getUserMedia`와 voice WebSocket을 모두 0회로 유지한다. 원장 실패와 지연 권한 응답도
|
||||
마이크 track/소켓을 남기지 않으며 숨은 Space 대신 안내 가능한 `Alt+M`을 사용한다.
|
||||
- [x] process-local metadata-only `/admin/voice-runtime`과 Uvicorn `--ws-max-queue 4`를 추가해 worker RSS/CPU/FD,
|
||||
WebSocket·provider session·audio buffer·event queue·overflow/fallback/error high-water를 관측 가능하게 만들었다.
|
||||
- [x] public WSS evidence v4의 turn별 interim/speech-final/latency 계약, 같은 worker를 고정하는 runtime sampler,
|
||||
exact image/container/cgroup/proc/Docker/TCP topology sampler, 독립 blind human-held-out voice-gain 계약과
|
||||
네 artifact를 같은 public host·동시 시간창으로 묶는 `check-g7-external-proof.py`를 구현했다.
|
||||
- [x] G7 내부 hardening exact SHA `a8c27b0aadcb9dacea86c4be5e62e19277b9cc2678ebca51aa02998d2c2d1a0d`를
|
||||
격리 NAS에 승격했다. clean candidate session E2E 24/24, OpenAPI 124, auth 401, G0~G8와 실제 NAS
|
||||
browser SSE→DB review를 통과했고 물리 마이크는 사용하지 않았다.
|
||||
- [ ] **G7 종료 GATE** — 오디오·자막·측정 이벤트 시간 정렬, 물리 마이크·공개 WSS 장시간 E2E,
|
||||
텍스트 단독 대비 음성 이득 benchmark, 음성 보존·삭제·동의 정책 검증. **내부 완료:** 실제 API/DB
|
||||
동의→단일 시계→독립 측정/융합→철회·삭제, actual-Postgres 동시 철회, synthetic gold gain benchmark,
|
||||
bounded queue/10MiB cap RSS probe, Deepgram adapter, HMAC word pseudonym, 1초 동의 재검사, 선행 동의 원장,
|
||||
recovery UX, mic-free expected-provider/model preflight와 외부 증거용 네 artifact checker를 확인했다. 최신
|
||||
격리 synthetic PCM soak는 59/59 연결 종료·성공 발화 55·철회 차단 2·미동의 차단 1·overflow 거부/복구 1/1,
|
||||
focused 119 passed를 통과했다. 공개 G7 route는 TLS 1.3 handshake·비인증 1008·Cloudflare edge와 마이크
|
||||
열거/캡처 0을 확인했다. 증거: `ops/evidence/g7-public-wss-preflight-current-20260807.json`.
|
||||
**남음:** 실제 Deepgram 운영 key·quota와 live interim/final, 기대값과 일치하는 authenticated public WSS ready,
|
||||
사용자의 명시 동의를 받은 물리 마이크, 실행된 50분 양방향 soak, 독립 라벨 held-out/pilot의 실제 음성 이득,
|
||||
실제 worker·Uvicorn·OS·Cloudflare edge를 같은 50분 시간창으로 묶은 RSS/CPU/queue high-water.
|
||||
preflight·synthetic·옵션 제공만으로 종료하지 않는다.
|
||||
|
||||
### I-8. G8 Autonomous Content & Continuous Improvement
|
||||
|
||||
- [x] source pack 기반 새 사례·균열·연습·benchmark 초안 자동 생성.
|
||||
- [x] 안전·정체성·난이도·정답 누수·문화적 편향 독립 적대 검토.
|
||||
- [x] 승인된 콘텐츠만 catalog 승격하고 provenance 보존.
|
||||
- [x] metadata-only 운영 incident를 승인 source로 변환해 새 adversarial benchmark pipeline을 실행.
|
||||
- [x] repo-approved synthetic source를 durable lease/retry job으로 등록하고 API lifespan에서 opt-in 주기 실행.
|
||||
- [x] 운영 측정의 약한 benchmark·drift를 주기적으로 탐색해 위 incident→adversarial 경로를 자동 기동.
|
||||
- [x] 실제 Claude pending payload를 사람 검수하고 승인된 projection만 catalog에 0→1로 소비하는
|
||||
관리자 browser/API/DB E2E. learner/teacher 403·replay 멱등·변조 409·민감 payload 비노출.
|
||||
- [x] release agent의 결정적 patch·manifest·clean candidate·API/Web/회기 gate, 격리 NAS 승격,
|
||||
semantic runtime/browser proof, 이미지 rollback, exact-SHA SSOT handoff를 fail-closed로 구현·독립 감사.
|
||||
- [x] 위 release agent로 변경 SHA `e9b482ccba616a610c1f4862366d44aabae20ee8c82d8ef7f2cf16069f2d6e99`를
|
||||
실제 격리 NAS에 승격하고 postdeploy browser·DB review·rollback snapshot·exact-SHA SSOT handoff를 남겼다.
|
||||
- [x] **G8 종료 GATE 재검증** — source→draft→red-team→benchmark→catalog 재현, 정답 누수·PII·무근거
|
||||
임상 주장 0건, 모델 교체 자동 calibration/rollback, 운영 오류의 재현 테스트·backlog DAG 자동 환류.
|
||||
**현재 코드 증거:** caller가 완성 JSON을 넣는 기존 경계와 별도로 source hash·PII fail-closed→structured
|
||||
generator→서로 결과를 공유하지 않는 red-team 2개→variant generator→독립 judge 3개 이상→durable
|
||||
`draft_payload`·`pending_human_approval` 저장 worker/API를 추가했다. 모델/structured output 실패, PII,
|
||||
answer leakage, 무근거 claim, reward hacking은 store 호출 전 차단하고 같은 submission은 model call 0으로
|
||||
재생한다. repo-approved source의 durable lease/retry scheduler를 lifespan에 opt-in 연결했고 실제
|
||||
`claude_cli/claude-opus-4-8` 7 calls→dev PostgreSQL reviewer 2·variant 3·pass rate 1.0→
|
||||
`pending_human_approval`과 동일 UUID 0-call replay를 확인했다. full API/gateway 858 passed,
|
||||
Ruff·OpenAPI type check·web typecheck 통과. G6 canonical 임계값·subgroup 원장을 재검증하는 default-off
|
||||
drift trigger는 실 DB incident 1·DAG 4·job 1·approval/catalog 0과 동시성·멱등을 통과했다. 기존 실제
|
||||
Claude pending payload는 새 모델 호출 0회로 관리자 browser에서 catalog 0→1·approval 1, 역할 403·
|
||||
replay 멱등·변조 409를 통과했다. release agent는 독립 감사 4건 수정 뒤 14/14와 전체 API 891 passed를
|
||||
통과했다. exact SHA의 NAS runtime은 health·OpenAPI 123·auth 401·G0~G8와 browser SSE→DB review를
|
||||
통과했고 transient 502 rollback/recovery와 최종 readiness 3회째 성공을 기록했다. 상세:
|
||||
`ops/outcome-os-g8-agentic-worker-evidence-2026-08-07.md`. 현재 소스의 HTTP control-plane executor는
|
||||
artifact SHA·idempotency key·receipt/evidence가 일치할 때만 `executed`를 허용하고 미구성은 `requested`,
|
||||
호출/receipt 실패는 `failed`로 남긴다.
|
||||
**2026-08-07 종료:** 격리 NAS `vignette-preview-20260807`에서 실제 receipt-bound image rollback을 두 번
|
||||
실행했다. 별도 executor helper(`127.0.0.1:18149`, host network·read-only rootfs·cap-drop ALL·
|
||||
no-new-privileges·uid1028/gid100·group101)와 별도 lifecycle의 control-plane API(`127.0.0.1:8018`,
|
||||
producer disabled, preview DB `172.22.0.2`의 `vignette_app` role)를 띄우고, `rollback-old` plan은 receipt
|
||||
`nas-g8-723eeef22eab05e63e3fafb0`으로 previous 이미지 `79ec…4450`/`c530…2f28`를, `restore-current` plan은
|
||||
receipt `nas-g8-2738846cf2cf4fbe8ce0fc26`으로 current 이미지 `52e0…8b2d`/`6fdb…f215`를 활성화했다.
|
||||
release gate·approval 각각 2회 멱등, `audit.ci_lifecycle_event` `rollback/executed` 2건과 receipt id 결속,
|
||||
`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 격리는 image가
|
||||
상속하는 `com.docker.compose.*` label을 baseline으로 두고 project/service만 격리 override하는 계약으로
|
||||
검증했다(`scripts/launch-nas-preview-g8-helpers.py`, fake-runner 37/37). 계획했던 Windows SSH 터널은 NAS
|
||||
sshd가 `administratively prohibited`로 거부해 sshd 설정 변경 대신 같은 격리 계약의 NAS-side probe
|
||||
컨테이너로 실행했고 control-plane/preview origin 분리는 유지했다. `down`/`volume rm`/`prune` 미실행,
|
||||
공개 런타임 미접촉, 비밀 env 파기, helper 0·listener 0 복귀. 상세:
|
||||
[런북](./ops/nas-preview-g8-rollback-proof-runbook.md) ·
|
||||
[기계 판독 증거](./ops/evidence/nas-preview-g8-actual-rollback-2026-08-07.json).
|
||||
- [x] current source exact SHA `6030a677…c611`을 격리 NAS에 승격했다. release-only patch 3회 결정성,
|
||||
전체 browser E2E 108/108, postdeploy SSE→DB review 32.437s, health 3/3, OpenAPI 126·auth 401·G0~G8
|
||||
route, named DB volume 보존을 확인했다. 이는 runtime promotion 증거이며 실제 rollback receipt와 구분한다.
|
||||
|
||||
### I-DAG. 첫 에이전트 실행팩 AOS-001~012
|
||||
|
||||
- [x] **AOS-001** 기존 지표 provenance inventory — 선행 없음. 종료: 모든 현재 점수의 source map.
|
||||
- [x] **AOS-002** `measurement_event` ADR/schema/DDL — 선행 AOS-001. 종료: contract + rollback + RLS tests.
|
||||
- [x] **AOS-003** benchmark case schema와 첫 8개 장면 — 선행 AOS-001. 종료: deterministic fixtures.
|
||||
- [x] **AOS-004** legacy rapport/alliance adapter — 선행 AOS-002. 종료: 기존 데이터 무손실 read model.
|
||||
- [x] **AOS-005** goal/task/bond 3관점 structured schema — 선행 AOS-002/003. 종료: gold contract tests.
|
||||
- [x] **AOS-006** session measurement API — 선행 AOS-005. 종료: API/DB idempotency E2E.
|
||||
- [x] **AOS-007** 학습자 self-assessment lock — 선행 AOS-002. 종료: reveal-order E2E.
|
||||
- [x] **AOS-008** Alliance Pulse 리뷰 UI — 선행 AOS-006/007. 종료: desktop/mobile visual proof.
|
||||
- [x] **AOS-009** 교수자 measurement drilldown — 선행 AOS-006. 종료: role/RLS/audit E2E.
|
||||
- [x] **AOS-010** measurement drift/calibration job — 선행 AOS-003/005. 종료: version comparison report.
|
||||
- [x] **AOS-011** public/deploy proof manifest — 공개 API 119 paths, G1~G8 route, 새 Pages asset, auth/OAuth proof.
|
||||
- [x] **AOS-012** SSOT/TODO 동기화 — dashboard/TODO/backlog/release manifest 동기화와 checker green.
|
||||
|
||||
---
|
||||
|
||||
## 지금 임계경로 (한 줄)
|
||||
납품(공개 데모)의 핵심 블로커는 **A(공개 `/turn` proof + 배포 secrets)**. 그 다음이 **B(한신대 거버넌스)**와
|
||||
**C(Phase 3 파일럿)** — C는 2026-07-13 회의로 **7월 말~8월 초 파일럿 일정 확정**, 그 전제로 **H의 P1(회기 종료 시간 기반)**이
|
||||
새 구현 게이트다. D~G는 품질·확장 작업으로 게이트가 아니다. 상세 상태·검증 증거는 SSOT 대시보드가 소유한다.
|
||||
새 구현 게이트다. **I(Outcome & Alliance OS)는 현재 감사 기준 G0~G6이 internal DONE이다.
|
||||
G8은 receipt-bound 실제 NAS rollback proof가 남았다. G7은 내부 구현 DONE과 외부 proof GATE를 분리하며,
|
||||
Deepgram live·auth public WSS·명시 동의 mic·
|
||||
50분 soak·독립 라벨 benchmark·운영 resource high-water가 남았다.** D~G는
|
||||
품질·확장 작업으로 게이트가 아니다. 상세 상태·검증 증거는 SSOT 대시보드가 소유한다.
|
||||
|
|
|
|||
|
|
@ -50,7 +50,7 @@
|
|||
.pulse-main h1{margin-top:9px;}
|
||||
.pulse-state{margin:13px 0 0;color:var(--ink-2);font-size:13.5px;max-width:640px;}
|
||||
.pulse-state b{color:var(--ink);}
|
||||
.phase-rail{display:grid;grid-template-columns:repeat(5,1fr);gap:7px;margin-top:18px;}
|
||||
.phase-rail{display:grid;grid-template-columns:repeat(6,1fr);gap:7px;margin-top:18px;}
|
||||
.pr-seg{position:relative;}
|
||||
.pr-bar{height:7px;border-radius:999px;background:var(--paper-3);overflow:hidden;}
|
||||
.pr-fill{height:100%;border-radius:999px;background:linear-gradient(90deg,var(--accent),var(--accent-deep));}
|
||||
|
|
@ -201,7 +201,7 @@
|
|||
.fold>summary .fmeta{margin-left:auto;font:650 11px/1 var(--mono);color:var(--ink-3);}
|
||||
.fold-inner{padding:6px 18px 20px;border-top:1px solid var(--hair);}
|
||||
.grid{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:14px;}
|
||||
.roadmap{display:grid;grid-template-columns:repeat(5,minmax(0,1fr));gap:11px;margin-top:6px;}
|
||||
.roadmap{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:11px;margin-top:6px;}
|
||||
.phase-card{min-width:0;background:var(--paper);border:1px solid var(--hair);border-radius:12px;padding:14px;}
|
||||
.phase-card.current{border-color:color-mix(in srgb,var(--accent) 42%,var(--hair));background:linear-gradient(180deg,var(--accent-tint),var(--surface) 58%);}
|
||||
.phase-card.next{border-color:color-mix(in srgb,var(--warn) 35%,var(--hair));}
|
||||
|
|
@ -272,7 +272,7 @@
|
|||
<div class="brand"><span class="mark" aria-hidden="true"></span><span>Vignette 커맨드센터<small>팀장 리뷰 · dev status</small></span></div>
|
||||
<div class="head-right">
|
||||
<a class="owner-pill" href="#owner"><span class="d" aria-hidden="true"></span>내 차례 <b id="pill-owner">0</b></a>
|
||||
<span class="stamp">2026-07-30 KST</span>
|
||||
<span class="stamp">2026-08-07 KST</span>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
|
|
@ -282,15 +282,16 @@
|
|||
<div class="pulse-main">
|
||||
<span class="eyebrow">project pulse</span>
|
||||
<h1>지금 위치: <em>Phase 0 제품 데모는 public /turn proof가 핵심 블로커</em>.<br><em>Phase 1 MVP 코어 루프</em>는 로컬 DB-backed 실증 완료.</h1>
|
||||
<p class="pulse-state">코어(저항·반응 조절, 상태머신, RAG, 이론모드, 가드레일, 평가 JSON Schema)는 <b>잘 정렬</b>됐고, 학습자 홈·교수자 리뷰·공개 웹 로그인·공개 API/Tailnet 기본 스모크도 통과했다. 2026-06-30 레드팀 중 public API Cloudflare 530/error code 1033을 발견했지만, Docker Desktop/DB 재기동 후 public health 200과 비인증 <code>/personas</code> 401로 복구를 확인했다. 단, 공개 Google OAuth 실제 <code>/turn</code>, vnet DNS/zone/OAuth redirect, 실배포 <code>infra/.env</code>, 기관 결정, 물리 마이크·public WSS 같은 게이트는 별도로 남아 있다. 이 판은 <b>검증된 내부 완료</b>와 <b>외부 증거가 필요한 항목</b>을 분리해서 보여준다.</p>
|
||||
<p class="pulse-state"><b>긴급 운영 상태:</b> 2026-08-07 18:26 KST 공개 DB를 복구 named volume으로 전환했다. public owner 집계 users 84·sessions 30·turns 705, Google 계정 16·소유 회기 30, orphan 0이며 health·engine·OAuth·로그인 화면·watchdog가 정상이다. 보안상 기존 auth session은 복사하지 않아 모든 사용자는 Google 재로그인이 필요하고, 전환 직전 container·dump와 원본 volume은 삭제하지 않았다. <b>개발 상태:</b> G0 provenance census 29/29·위반 0과 G6 safety metadata-only 우선 runtime은 disposable clone에서 통과했다. G4·G5 실제 API/DB/browser 폐루프와 current exact SHA <code>6030a677…c611</code>의 NAS 승격은 완료됐고 전체 browser E2E 108/108·postdeploy SSE→DB review·health 3/3·OpenAPI 126을 통과했다. G8은 격리 NAS에서 실제 image rollback 2회를 executed receipt(<code>nas-g8-723eeef2…</code>/<code>nas-g8-2738846c…</code>)로 실행해 DONE이며, G7은 Deepgram live·인증 WSS·명시 동의 물리 마이크 50분·독립 라벨 voice-gain과 resource high-water 전까지 외부 GATE다.</p>
|
||||
<div class="phase-rail" id="phase-rail" aria-label="Phase 진행 추정"></div>
|
||||
<div class="mstrip" aria-label="검증 지표">
|
||||
<span class="mchip good">Full E2E 기준선 <b>158</b></span>
|
||||
<span class="mchip good">Node conformance OK · gateway <b>29</b> · backend <b>400</b></span>
|
||||
<span class="mchip good">Layout 시각게이트 <b>9/9</b> pass</span>
|
||||
<span class="mchip warn">Full E2E 현재 수집 <b>604</b> · 전체 GREEN 대기</span>
|
||||
<span class="mchip good">현재 전체 회귀 · gateway <b>58</b> · backend <b>898</b></span>
|
||||
<span class="mchip good">Outcome clean E2E <b>126</b> · visual <b>15/15</b></span>
|
||||
<span class="mchip good">Alliance G1 baseline 1.2 <b>9/9</b> · 내부 DONE</span>
|
||||
<span class="mchip">Tailnet <b>이전 확인 OK</b></span>
|
||||
<span class="mchip good">Public API <b>recovered OK</b></span>
|
||||
<span class="mchip crit">배포지 secrets <b>BLOCK</b></span>
|
||||
<span class="mchip good">Public API <b>119 paths · G1~G8</b></span>
|
||||
<span class="mchip good">Direct-runtime preflight <b>APP ROLE OK</b></span>
|
||||
<span class="mchip crit">critical 3 <b>1차 완료·gate</b></span>
|
||||
<span class="mchip">Decision <b>P0 조건부</b></span>
|
||||
</div>
|
||||
|
|
@ -608,6 +609,31 @@
|
|||
<p class="dg-note">155차 적용(2026-07-31): 세션 텍스트 입력이 AI 생성·TTS 종료까지 잠기고 답변 표시 뒤에도 evaluator가 끝날 때까지 다음 전송이 약 24초 묶이던 학습 UX를 실제 브라우저와 gateway/API 계량으로 분해했다. textarea는 회기 종료·일시정지에서만 잠기며 생성·음성 준비·재생 중에는 다음 질문 초안을 계속 작성·보존하고, 내담자 응답 저장 직후에는 음성 재생과 무관하게 다음 전송을 허용한다. 관리자 DB의 Agy/Gemini 설정은 evaluator/review에 유지하되 실시간 client 역할은 <code>VIGNETTE_LIVE_CLIENT_PROVIDER=claude_cli</code> 전용 lane으로 분리해 회기별 <code>claude -p</code> 프로세스를 재사용한다. stream learner/client turn과 결정론 상태를 먼저 durable 저장하고 SSE <code>done</code>을 보낸 뒤 fast-loop 평가는 백그라운드에서 같은 learner turn normalized row에 붙이며 평가 기반 코칭 충전도 한 번만 적용한다. Agy fallback도 <code>stream-json</code> delta를 실제 SSE token으로 전달한다. 로컬 음성은 <code>-UseHiggsVoice</code>로 설치된 <code>higgs-audio-v3-tts-4b</code>를 loopback 상주시켜 P1 실제 답변을 24kHz WAV로 합성하며 저장소의 무참조 synthetic seed만 reference로 허용하고 non-dev는 fail-closed한다. <b>실증</b>: 변경 전 full API 완료 24.17/24.57초, 변경 후 실제 UI 첫 응답·전송 해제 9.58초와 상주 연속 턴 6.96초, 생성 중 초안 enabled/보존, TTS 중 보내기 enabled. Higgs full API 362,924 bytes/7.56초 WAV, provider/model 헤더 확인. backend 432/432, gateway 44/44, typecheck·API types·build, session stream 1/1, voice skip/draft 1/1 통과. <b>배포·공개 실증</b>: commit <code>c7883434</code>를 origin/master에 push하고 Cloudflare Pages production <code>505155c4-01f3-4eac-ad59-c2d323a0bb51</code>로 게시했다. custom/preview 모두 <code>index-BU5JjRnR.js</code>를 JavaScript 200으로 서빙하고 최근 5개 배포의 의존 자산을 보존한다. 공개 API는 prod·db=true·engine=true, auth config Google configured/dev login disabled, 비인증 personas·voice speech 401, Google 시작 302다. 실제 공개 로그인 1280×720 렌더에서 버튼 2개 enabled, 브라우저 warning/error 0을 확인했다. 공개 음성은 라이선스 경계상 <code>openai / gpt-4o-mini-tts</code>를 유지한다.</p>
|
||||
<p class="dg-note">156차 적용(2026-07-31): 활성 상담 세션이 전역 테마와 무관하게 <code>.sx-page--active</code>에서 다크 팔레트를 다시 정의해, 라이트 설정에서도 화면은 다크인데 공통 <code>Surface inset</code>인 AI 코치만 밝게 뜨는 혼합 테마 결함을 수정했다. 세션 전용 <code>--bg-surface</code>/<code>--text-*</code>/<code>--accent</code> 덮어쓰기를 제거하고 전역 <code>data-theme</code>·<code>tokens.css</code>·공통 <code>Surface</code>를 그대로 사용해 라이트는 종이·글래스, 다크는 차콜·세이지로 함께 전환한다. 우측 레일은 <b>AI 코치 → 현재 신호 → 음성·AI 응답 상태</b> 순으로 재배치하고 의미 없는 equalizer 막대와 중복 화면 모드 행을 제거했다. 700px 이하 높이에서는 발화 예시·보조 흐름을 먼저 접고 코치 판단과 근거/이력 액션을 보존하며, 기존 사각지대였던 881~1180px에서도 코칭 모드에 AI 코치 카드만 전폭으로 남긴다. <b>검증</b>: <code>npm run check:design-ssot</code>, typecheck, build 통과, 세션 전수 desktop/mobile <code>30 passed / 2 skipped</code>, session-layout <code>8/8</code>, 7폭 layout-visual-gate <code>15/15</code>, 라이트 1366×640 코치·1024×640 압축 코치 및 다크 1440/1024/390 캡처 직접 확인. 로컬 작업트리 기준이며 배포 전이다.</p>
|
||||
<p class="dg-note">157차 적용(2026-07-31): 사용자 제공 1536×1024 활성 세션 시안을 실제 세션 화면에 반영했다. 데스크톱을 <b>좌측 단일 컨텍스트 레일 326px / 중앙 인물 무대+자막 / 우측 단일 라이브 코칭 레일 357px / 하단 116px 제어바</b>로 재구성하고, 상태 신호 모드에서도 AI 코치의 개입 시점·남은 기회·코칭 포인트를 미리 이해할 수 있게 했다. 제공된 수채화 잎 5장은 BEN2로 투명 분리한 뒤 600×800 이하 알파 WebP로 최적화해 합계 약 158KB로 줄였고, 좌우 캔버스와 아바타 뒤에 장식 계층으로 연결했다. 새 E2E는 1408px 상단/본문, 1468px 제어바, 326/357px 레일, WebP 응답과 computed background 연결을 1536×1024에서 고정한다. 1366×768 이하에서는 스테이지를 압축해 자막이 주 작업영역을 잃지 않게 했다. <b>검증</b>: <code>npm run check:design-ssot</code>, typecheck, build 통과, 세션 전수 desktop/mobile <code>32 passed / 2 skipped</code>, session-layout <code>8/8</code>, 7폭 layout-visual-gate <code>15/15</code>, 1536×1024 라이트 캡처 직접 확인. 앱 commit <code>12bdc594</code>를 Cloudflare Pages production에 배포하고 배포 증거 commit <code>32c0b97d</code>를 연동한 최종 production <code>f9b06388</code>까지 완료했다. 커스텀 도메인의 <code>index-CzevzoM7.js</code>, <code>Session-BWMFTqgE.js</code>, <code>Session-D7DZVnh0.css</code>, <code>leaf-1.webp</code>가 각각 올바른 JavaScript/CSS/WebP MIME으로 200을 반환했다. 공개 로그인 실렌더·브라우저 warning/error 0, API <code>prod/db/engine=true</code>, 비로그인 personas 401, Google OAuth 시작 302, watchdog 5항목 healthy도 확인했다.</p>
|
||||
<p class="dg-note">158차 적용(2026-07-31): 관리자 <b>Provider·모델별 사용 원장</b>에서 Claude CLI만 비용이 잡히고 Agy/Gemini가 0달러로 보이던 원인을 수정했다. Claude CLI는 SDK가 반환한 호출별 비용 추정값을 우선하고, 비용이 없는 Agy/Gemini·Codex·Claude API는 버전 고정 공식 참조단가로 입력·캐시 입력·출력 토큰을 환산한다. 기존 DB에 비용 0으로 저장된 행도 <code>/admin/usage</code> 조회 시 보정하며, 공식 단가가 등록되지 않은 모델은 0달러 대신 <code>미산정</code>으로 표시한다. 응답과 리포트는 <code>recorded_cost_usd</code>, <code>estimated_cost_usd</code>, <code>cost_basis</code>, 단가 라벨·출처를 분리한다. 공개 DB-backed 30일 원장에서 <code>gemini-3.6-flash-high</code> 5호출·입력 35,703·출력 1,129가 <code>$0.062022</code>로 보정되고 총비용에도 합산되는 것을 인증 요청으로 확인했다. <b>검증</b>: backend/gateway focused <code>66 passed</code>, API type generation/check, typecheck, build, 관리자 desktop/mobile focused E2E <code>2 passed</code>, 7폭 focused layout visual gate <code>1 passed</code>. 공개 API 프로세스를 재시작하고 Cloudflare Pages production <code>11b3e11f</code>에 배포했으며, 커스텀 도메인의 <code>AdminAi-BfxMUlzj.js</code>와 <code>AdminAi-XHkda_d4.css</code>가 올바른 MIME으로 200을 반환한다.</p>
|
||||
<p class="dg-note">159차 적용(2026-07-31): Claude CLI result에 실제 <code>usage</code>/<code>modelUsage</code>가 있는데도 게이트웨이의 generate·SSE done 응답이 <code>tokens_in=0</code>, <code>tokens_out=0</code>을 하드코딩해 DB 원장에 0을 저장하던 근본 원인을 수정했다. <code>modelUsage</code>/<code>model_usage</code>가 있으면 메인 호출과 서브에이전트를 모델별로 합산하고, 없을 때 최상위 <code>usage</code>로 폴백한다. 입력 원장은 비캐시 입력 + cache read + cache creation 합계다. 과거 미수집 행은 토큰을 비용으로 역산하지 않고 <code>token_unmetered_turns</code>로 분리해 화면에 <code>미계량</code>으로 표시하며, Claude <code>total_cost_usd</code>는 청구 실비가 아니라 SDK 추정치이므로 <code>provider_estimate</code>/<code>SDK 추정</code>으로 바로잡았다. <b>검증</b>: backend 전체 <code>436 passed</code>, gateway 전체 <code>45 passed</code>, API type generation/check, typecheck, build, 관리자 desktop/mobile <code>2 passed</code>, 7폭 focused layout gate <code>1 passed</code>. 신규 live Opus generate는 입력 <code>31,918</code>·출력 <code>4</code>·비용 추정 <code>$0.124862</code>, Haiku SSE done은 입력 <code>30,450</code>·출력 <code>159</code>·비용 추정 <code>$0.061685</code>를 반환했다. 운영 DB 30일 원장의 과거 Claude <code>194</code>건은 <code>token_unmetered_turns=194</code>, <code>cost_basis=provider_estimate</code>로 확인했다. 공개 런타임을 새 gateway/API로 교체하고 Cloudflare Pages production <code>10c2bfc3</code>에 배포했다. 커스텀 도메인의 <code>index-BDlbUpT7.js</code>, <code>AdminAi-r9AkYKAl.js</code>, <code>AdminAi-D7JtoYC_.css</code>는 올바른 MIME으로 200이며, 실제 관리자 임시 세션의 공개 브라우저에서 <code>미계량</code>·<code>SDK 추정</code>·토큰 커버리지와 Codex/Agy catalog를 확인하고 세션·storage state를 즉시 폐기했다.</p>
|
||||
<p class="dg-note">160차 적용(2026-07-31): 159차 이전 Claude 토큰 0/0 행을 비용에서 역산하지 않고 실제 로컬 Claude 실행 JSONL로 제한 백필했다. JSONL <code>3,908</code>개의 assistant usage record <code>3,848</code>개를 본문 출력 없이 읽고, 정규화 응답 SHA-256과 DB 생성 시각 창(-30초~+180초)이 모두 맞으며 후보가 정확히 1개인 경우만 허용했다. 운영 전체 Claude <code>442</code>건 중 <code>169</code>건을 실제 입력 <code>5,179,999</code>·출력 <code>46,910</code> 토큰으로 복구했고, 후보 없음 <code>273</code>건은 계속 <code>미계량</code>, 모호한 후보는 <code>0</code>건이다. apply는 <code>--expected-matches 169</code>와 행별 <code>UPDATE 1</code> 가드를 통과했으며 적용 후 dry-run의 추가 exact match는 <code>0</code>건이다. 확인 시점 30일 창은 Claude <code>183</code>건 중 계량 <code>125</code>·미계량 <code>58</code>건이었다. <b>검증</b>: backend 전체 <code>439 passed</code>, gateway 전체 <code>45 passed</code>, 공개 관리자 브라우저 <code>1 passed</code>에서 Claude 실제 토큰과 부분 계량 <code>125/183회</code>, 잔여 미계량 설명, 브라우저 오류 0을 확인하고 임시 인증 세션·storage state를 폐기했다. DB-only 백필이므로 추가 웹 배포 없이 공개 UI가 live API 결과를 즉시 반영한다.</p>
|
||||
<p class="dg-note">161차 적용(2026-08-06): Outcome & Alliance OS <b>G1 Alliance Core</b>를 증거 게이트로 닫았다. 수련생 자기평가를 먼저 append-only로 잠근 뒤 가상내담자와 독립 관찰자를 별도 모델 실행·세션으로 평가하며, 공개 전에는 외부 관점을 숨기고 terminal 상태에서만 3관점 × goal/task/bond 9개 축을 함께 보여준다. 교수자 재평정은 근거 장면·메모를 강제하고 이전 평정을 supersede하는 append-only 원장으로 남긴다. 학습자 리뷰는 축별 총점 없이 관점 차이·근거 장면·provenance를 데스크톱 표와 모바일 카드로 제공한다. <b>검증:</b> focused backend <code>40 passed</code>, 실 PostgreSQL RLS·상태전이·재시도 model-run·교수자 supersession smoke, Alliance Pulse desktop/mobile <code>6/6</code>와 기존 회기 리뷰 <code>12/12</code>. prompt <code>1.2.0</code> 합성 gold는 방향 정확도 <code>9/9</code>, 최종 실패 <code>0</code>, 근거 recall <code>88.9%</code>이며 실패한 첫 시도 2건도 provenance로 보존됐다. G2 종단 성과 궤적을 병렬 구현 중이다.</p>
|
||||
<p class="dg-note">162차 적용(2026-08-06): Outcome & Alliance OS <b>G2 Longitudinal Outcome Trajectory</b>를 증거 게이트로 닫고 G3를 BUILD로 승격했다. 교육용 합성 5회기 기대분포와 고통 부담·일상 기능·학습 참여 세 축을 총점 없이 추적하며, 누락은 무점수, safety는 별도 참조, 관계 합의·미해결 균열은 역할별 문장으로 분리한다. 학습자 리뷰에는 1~5회기 타임라인, 축별 상태·불확실성·근거·반대근거·다음 질문과 최신 회기 자기보고 체크인을 연결했다. <b>검증:</b> 4-case×5-session benchmark status 정확도 <code>1.0</code>, false alert·critical miss <code>0</code>; 실제 DB/API 한 case S1~S5·15개 관측, 동일 제출 멱등성, 변경 payload 409, 학습자 소유권·교수자 cohort·교차 cohort 차단, role-safe 관계 기억 통과. G2 desktop/mobile <code>12/12</code>, G1+기존 리뷰 <code>15/15</code>, G2/G3 통합 backend <code>90 passed</code>다.</p>
|
||||
<p class="dg-note">163차 적용(2026-08-06): Outcome & Alliance OS <b>G3 Rupture & Repair Lab</b>을 증거 게이트로 닫고 G4를 BUILD로 승격했다. 9종 파열 상태머신·append-only 관측/재조정/안전참조 원장, 텍스트·SSE·음성 공통 durable-turn 감지, 후속 발화의 missed/partial/resolved reconciliation, 역할별 리뷰·교수자 supersession, 4~6턴 희소 Scenario Director와 prompt/SSE 내부 라벨 누출 차단을 연결했다. <b>검증:</b> 11-case benchmark 유형 macro-F1 <code>1.0</code>, 복구 상태 정확도 <code>1.0</code>, critical miss·judge gaming·암기문구 false resolution <code>0</code>; Scenario/G3 집중 <code>107 passed</code>, G0~G8 통합 계약 <code>251 passed</code>, 리뷰 desktop/mobile <code>50/50</code>. 실제 DB/API 회기에서 durable turn UUID 2개, 내부 토큰 write, 멱등 retry/변조 409, learner/cohort RLS, 교수자 append-only correction과 최종 <code>resolved</code>를 확인했다.</p>
|
||||
<p class="dg-note">164차 적용(2026-08-06): Outcome & Alliance OS <b>G4 Deliberate Practice Engine</b>을 증거 게이트로 닫고 G5를 BUILD로 승격했다. 평가 장면을 관찰 가능한 원자 행동과 replay·branch·constrained response·voice retry·difficulty ladder 5종 prescription으로 연결하고, competency graph의 약점·망각 위험에 따라 다음 연습을 고른다. 익숙한 장면 반복이나 자기 성공 주장만으로 mastery를 열지 않고 미지 사례 전이를 요구한다. <b>검증:</b> G4 core/store/API <code>54 passed</code>, G4 리뷰 desktop/mobile <code>8/8</code>, G1~G4+기존 리뷰 <code>58/58</code>. 실제 DB/API 회기에서 durable turn 근거 처방→학습자 시도→교수자 append-only correction, 안정 멱등 retry/변조 409, learner/cohort RLS, 보상 해킹 차단, 총점·XP 없음과 비임상 경계를 확인했다.</p>
|
||||
<p class="dg-note">165차 적용(2026-08-06): Outcome & Alliance OS <b>G5 Calibration Mirror & Transfer</b>를 증거 게이트로 닫고 G6를 BUILD로 승격했다. 외부평가 전 prediction revision과 불가역 lock, 독립 runtime observation reveal, 역량별 calibration error·95% 구간·과신/과소신 처방, 네 변주 축 unseen transfer와 암기 문구 차단, 합성 subgroup drift를 역할 안전 원장/UI에 연결했다. <b>검증:</b> G5 core/store/API <code>41 passed</code>, 4-case expectation accuracy <code>1.0</code>, 보정 MAE <code>0.667→0.267</code>, unseen transfer 4/4, 암기 false verification 0, drift gap 0.667. 실제 DB/API revision→lock→reveal·멱등/변조 409·post-lock 422·RLS, G5 UI 4/4와 G1~G5 리뷰 <code>62/62</code>, typecheck/build를 통과했다.</p>
|
||||
<p class="dg-note">166차 적용(2026-08-06): Outcome & Alliance OS <b>G6 Supervision & Research OS</b>를 증거 게이트로 닫고 G7을 BUILD로 승격했다. 위험·정체·미해결 관계 사건 attention queue와 3클릭 원장 drilldown, metadata-only 교수자/AI 불일치 dataset·audit, curriculum gap, model/prompt/instrument/subgroup drift, Phase 3 네 도메인 artifact provenance를 역할 안전 원장/UI에 연결했다. <b>검증:</b> G6 core/store/schema <code>28 passed</code>, 6-signal queue 순서 정확, raw transcript row 0, drift expected/actual 일치, manifest 네 도메인 전건. 실제 DB/API 멱등/409·AI-view·역할/cohort RLS·audit, version/subgroup/artifact hydrate 통과. G6 UI desktop/mobile <code>6/6</code>, typecheck/build를 통과했다.</p>
|
||||
<p class="dg-note">167차 적용(2026-08-06→07): Outcome & Alliance OS <b>G7 Multimodal Alliance</b>의 내부 제품 범위를 완성했다. 동의 preflight, 한 오디오 시계의 익명 STT word·silence/overlap/interruption/prosody, 독립 text/voice 측정과 calibrated no-gain fusion, 비임상 상호작용 신호 계약, 원음/metadata 역할 경계, 철회와 audio/derived tombstone을 실제 API/DB 원장에 연결했다. 회기 리뷰에는 시간축·독립 측정·보존 경계와 음성 재연습 CTA를 제공한다. <b>검증:</b> backend G0~G8 묶음 <code>393 passed</code>, 실제 API 멱등/변조409·RLS·융합 <code>[true,false,false]</code>·삭제, G7 desktop/mobile <code>8/8</code>, session review <code>24/24</code>, layout <code>15/15 + 8/8</code>, G1~G8 최종 UI 묶음 <code>84/84</code>. 물리 마이크·공개 WSS·50분 장시간 실증은 B2 외부 게이트로 남아 있다.</p>
|
||||
<p class="dg-note">168차 적용(2026-08-06→07): Outcome & Alliance OS <b>G8 Autonomous Content & Continuous Improvement</b>의 내부 제품 범위를 완성했다. source→draft→독립 red-team→benchmark→human catalog gate, 모델·릴리스별 baseline/threshold/provenance/rollback 증거 rail, append-only 승인·monitor·rollback lifecycle, 운영 incident의 4-node 회귀 DAG를 관리자 릴리스 제어석과 실제 API/DB에 연결했다. 자동 게시 버튼은 없으며 human gate 전에는 효과가 없다. <b>검증:</b> G8 UI desktop/mobile <code>8/8</code>, 기존 admin <code>40/40</code>, 실제 API 멱등/변조409·401/403·4종 artifact·lifecycle 6·DAG 4/4, 금지 데이터 0. 현재 공개 OpenAPI에는 G7/G8 route가 각각 <code>0</code>개여서 실제 배포·public proof를 DONE으로 표시하지 않는다.</p>
|
||||
<p class="dg-note">169차 적용(2026-08-07): G7/G8 외부 승격 전 로컬 릴리스 준비를 강화했다. 실제 loopback <code>/voice/ws</code> short soak는 8.015초 동안 연결 <code>83/83</code>·성공 발화 79·철회/재연결/overflow 복구를 통과했고 API/gateway 전체 <code>805 passed</code>지만, 물리 마이크·public WSS·실행된 50분 증거로 확대하지 않는다. G8 시각 QA는 승인 차단 이유를 상시 문구·<code>aria-describedby</code>·semantic form으로 고쳐 desktop/mobile <code>16/16</code>과 build를 통과했다. 배포 프리플라이트는 G3~G8 독립 32자 토큰과 G0~G8 migration sentinel을 fail-closed로 검사한다. 당시 199파일 release-only patch는 clean HEAD <code>git apply --check</code>를 통과했으나 후속 병렬 편집으로 manifest와 달라져 무효화했으며, 재분류·결정적 재빌드 전에는 배포에 사용할 수 없다. 공개 OpenAPI의 G7/G8 경로는 재확인 시점에도 각각 0개라 두 카드 상태는 BUILD로 유지한다.</p>
|
||||
<p class="dg-note">170차 적용(2026-08-07): 병렬 개발 결과를 학생 자기주도 수련의 실제 실행 경로로 통합했다. G1은 회기 전 pre pulse를 fail-closed로, 첫 라운드 뒤 mid pulse를 비차단형으로 연결했고 desktop/mobile focused <code>2/2</code>와 시각 증거 4장을 보존했다. G4/G5 카드의 replay·branch·제약 응답·음성 재시도·난이도 사다리·미지 전이는 학습자 홈→사전 설정→실제 새 Session까지 typed launch intent를 유지하며 관련 UI <code>22/22</code>와 통합 묶음 <code>59/59</code>를 통과했다. 현행 API 기준 learner/session/layout 묶음은 <code>37/37</code>, API/gateway는 <code>767 + 45 passed</code>, typecheck/build/API 계약도 통과했다. dirty 250개는 Outcome 관련 228·혼합 4·보존 제외 18로 전건 분류했고, 혼합 문서 exact hunk와 clean-source API 타입 재생성으로 만든 223파일 release-only patch를 두 번 동일하게 조립해 clean HEAD와 canonical clean index 적용 검사를 통과했다. 바이너리 시각 증거 4장은 manifest에는 관련 증거로 등록하되 텍스트 patch와 분리했다. G7 물리 마이크·공개 WSS·50분 실측과 G8 실제 배포·public proof는 여전히 BUILD다.</p>
|
||||
<p class="dg-note">171차 적용(2026-08-07): G8 agentic worker를 기본 비활성 opt-in scheduler로 연결했다. repo-approved 비식별 source는 immutable <code>app.ci_agentic_job</code>, <code>FOR UPDATE SKIP LOCKED</code> lease, retry 상태로 관리하며 source usage·classification·본문 hash·PII를 engine 전에 재검증한다. 실제 <code>claude_cli/claude-opus-4-8</code>에서 generator 1·독립 reviewer 2·variant generator 1·독립 judge 3, 총 7 calls를 실행해 dev PostgreSQL reviewer 2·variant 3·pass rate 1.0·<code>pending_human_approval</code>을 저장했다. 동일 UUID replay는 model call 0, human approval/catalog row는 0, clinical claim은 false다. engine timeout·answer leakage·red-team finding 실패도 pipeline 저장 0건으로 증명했다. 전체 API/gateway <code>858 passed</code>, Ruff·API type check·web typecheck·SSOT checker를 통과했다. 운영 drift trigger·사람 payload 검수 E2E·release-agent 폐루프 전까지 G8은 BUILD다.</p>
|
||||
<p class="dg-note">171차 적용(2026-08-07): G7 voice code/internal 경계를 Deepgram streaming adapter와 운영 전환용 fail-closed 계약까지 확장했다. interim/final·word timestamp·provider event를 처리하고, word 원문 대신 deployment secret 기반 HMAC pseudonym만 저장한다. 열린 stream은 동의를 1초마다 재검사해 철회 시 abort하며, Session은 음성 단절 뒤 작성 중 텍스트와 텍스트 계속하기·키보드 재연결을 보존한다. WSS <code>ready</code>는 실제 STT/TTS provider/model을 반환하고 public runner v3의 mic-free preflight가 네 기대값을 exact match한다. full soak는 명시적 확인 전 장치를 열거하지 않는다. 운영 TTS는 OpenAI API <code>gpt-4o-mini-tts</code>로 고정하고 공식 앱 통합·출력 소유 근거와 회기 전·중 AI 생성 음성 고지를 반영했다. <b>검증:</b> voice/G7 backend <code>73 passed</code>, public runner <code>12 passed</code>, disclosure desktop/mobile E2E, SSOT checker green. 실제 Deepgram key·quota live, expected provider/model authenticated public ready, 명시 동의 물리 마이크와 50분 soak가 없어 G7은 계속 BUILD다.</p>
|
||||
<p class="dg-note">172차 적용(2026-08-07): G7 voice hardening을 235파일 release-only patch <code>3197805058e3…2bc</code>로 clean worktree에서 재조립·검증한 뒤 공개 API 8001과 Cloudflare Pages production <code>6d7e89d5</code>에 재배포했다. API는 새 clean 경로에서 <code>prod/db/engine=true</code>, OpenAPI 119 paths, 인증 401·Google OAuth 302를 유지하고 <code>/voice/health</code>가 STT <code>openai/gpt-4o-transcribe</code>·TTS <code>openai/gpt-4o-mini-tts</code>를 반환한다. 공개 WSS 비인증 차단은 <code>1008</code>, custom/immutable web은 새 <code>index-DZZoYvZq.js</code>와 AI 생성 음성 고지 포함 Session 청크를 올바른 MIME으로 제공하며, watchdog도 새 clean 경로로 재등록해 <code>LastTaskResult=0</code>을 확인했다. 운영 Deepgram key가 없어 streaming provider는 아직 활성화하지 않았고 authenticated ready·명시 동의 물리 마이크·50분 soak도 미실행이므로 G7은 계속 BUILD다.</p>
|
||||
<p class="dg-note">173차 적용(2026-08-07): G7 완료감사에서 내부 과대주장을 바로잡았다. benchmark는 calibration 점수 재진술 대신 synthetic gold target에 대한 text-only와 calibrated multimodal의 <code>1-MAE 0.9533→0.9673</code>, gain <code>+0.014</code>(최소 <code>0.01</code>)를 직접 계산하고 gold 변조 반례를 통과한다. consent mutation·timeline·measurement는 session 공통 PostgreSQL transaction lock으로 직렬화했으며 실제 개발 DB 동시 철회에서 미커밋 writer 대기, 커밋 뒤 저장 차단, timeline row <code>0</code>을 확인했다. route는 10MiB append 전 거부, transcript event queue <code>32</code>, Deepgram receive queue <code>16</code>을 사용하고 3초 loopback에서 연결 <code>61/61</code>·발화 <code>57</code>·overflow 복구·10MiB RSS probe를 통과했다. 다만 synthetic label은 독립 임상/파일럿 효과 증거가 아니고 단일 프로세스 probe는 실제 worker·Uvicorn·OS·Cloudflare high-water를 대체하지 않는다.</p>
|
||||
<p class="dg-note">174차 적용(2026-08-07): 학생 자기주도 수련의 <b>홈 추천→목표 선택→텍스트/SSE 회기→종료·리뷰→G4 처방→새 회기 재연습</b>을 실제 <code>src</code> 제품 화면 하나의 상태 흐름으로 묶었다. desktop/mobile <code>2/2</code>, typecheck와 고정 캡처 9장 직접 QA를 통과했다. 홈 핵심 CTA는 44px·4.5:1 이상 대비로 보정했고, 390×844·320×568 활성 회기의 아바타/문구 겹침 0·축어록/입력창 카드 내부 포함을 고정했다. 리뷰→재연습의 내부 criterion/counterevidence/UUID/영문 이론키는 사용자용 한국어로 치환했다. 이 브라우저 증거는 미소유 <code>/api/**</code>를 404로 닫은 로그인 없는 route fixture이며 실제 DB 폐루프가 아니다. 실 PostgreSQL production caller 증거와 UI 소비 증거를 분리해 기록한다. 리뷰 1~5 척도는 320px에서 3+2로 재배치해 다섯 선택지 44px 이상·내부/페이지 overflow 0과 desktop/mobile <code>8/8</code>을 확인했다. 상세: <code>ops/outcome-os-self-directed-learning-loop-ui-evidence-2026-08-07.md</code>.</p>
|
||||
<p class="dg-note">175차 적용(2026-08-07): <b>격리 NAS 프리뷰</b>를 <code>http://100.116.83.60:8088</code>에 배포하고 실제 브라우저 회기와 review API의 저장 축어록 2턴을 확인했다. 전용 Compose 프로젝트·포트·네트워크·볼륨만 대상으로 배포했으며 기존 프로젝트 중단·재생성 명령은 실행하지 않았다. 매일 04:30 KST <b>Vignette 회기 E2E 정기 검증</b>(automation id <code>vignette-e2e</code>)을 ACTIVE로 구성했다. 새 기능이 material milestone이고 release-only patch 결정성·manifest·clean-index·NAS preflight가 모두 통과하며 배포 SHA가 달라질 때만 같은 격리 프리뷰를 승격한다. 실행 이력은 첫 스케줄 뒤 별도 증거로 남긴다. 증거: <a href="./ops/nas-preview-deployment-evidence-2026-08-07.md">배포 증거</a>, <a href="./ops/evidence/nas-preview-live-turn-2026-08-07.png">브라우저 증거</a>. 이 프리뷰와 자동화는 G7 종료 증거가 아니다. 물리 마이크 캡처는 미승인이고 Deepgram 운영 키가 없어 G7은 계속 <b>BUILD</b>다.</p>
|
||||
<p class="dg-note">176차 적용(2026-08-07): G8의 세 내부 결손을 병렬 구현·독립 감사로 닫았다. G6 drift 원장의 canonical 임계값·subgroup 근거를 대조하는 default-off trigger는 실 DB incident 1·DAG 4·job 1, 멱등·동시성·approval/catalog 0을 통과했고 문자열만 믿던 fail-open을 RED로 수정했다. 기존 실제 Claude pending 후보는 새 모델 호출 0회로 격리 8011/5181 관리자 browser에서 catalog 0→1·approval 1, learner/teacher 403·replay 멱등·변조 409·민감 projection 비노출을 확인했다. 별도 실엔진 후보의 answer leakage는 422·저장 0으로 차단했다. release agent는 material+changed SHA·patch 2회 결정성·manifest/clean candidate·API/Web/회기 gate·NAS snapshot/semantic runtime/browser·rollback·exact-SHA SSOT handoff를 구현했고, 독립 감사에서 active-state drift·경로 탈출·secret archive·병렬 volume 충돌 네 결함을 수정했다. 전체 API/gateway <code>891 passed</code>, release governance <code>35/35</code>, 관리자·자기주도·회기 desktop/mobile <code>26/26</code>, API type·typecheck·build를 통과했다. 변경 SHA의 실제 격리 NAS 승격 전까지 G8은 <b>BUILD</b>다.</p>
|
||||
<p class="dg-note">177차 적용(2026-08-07): G8 release-only SHA <code>e9b482ccba61…e99</code>를 격리 NAS <code>vignette-preview-20260807</code>에 실제 승격했다. Windows clean candidate의 <code>bash\r</code> DB init 실패, legacy env 경로 오판, 교체 직후 readiness 502를 각각 fail-closed RED로 재현하고 LF 정규화·snapshot env 추적/preflight·180초 bounded readiness로 수정했다. 선행 502 실행은 기존 이미지로 자동 rollback하고 health·OpenAPI 122·auth 401·이미지 ID를 재검증했다. 최종 실행은 API/Web 새 이미지, health ok·db/engine true, OpenAPI 123·auth 401·G0~G8, readiness 3회, 실제 postdeploy browser SSE→DB-backed review를 통과했으며 물리 마이크는 사용하지 않았다. release-agent <code>20/20</code>, final session E2E <code>559.812s</code>, browser review <code>26.921s</code>. exact SHA를 TODO·백로그·manifest와 동기화해 G8은 <b>DONE</b>이다. G7 외부 증거는 그대로 BUILD다.</p>
|
||||
<p class="dg-note">178차 적용(2026-08-07): G7을 가짜 외부 증거로 닫을 수 없도록 제품 동의와 종료 checker를 연결했다. Session은 처리 목적·전송/전사·침묵/속도/끼어듦 파생·30일 보존·원음 미보존·철회/삭제·텍스트 대안을 설명하고, 서버 consent ledger 성공 전 <code>getUserMedia=0</code>·voice WebSocket <code>0</code>을 유지한다. 원장 지연·실패와 늦은 권한 응답도 capture를 남기지 않으며 Space를 제거하고 <code>Alt+M</code>을 안내한다. public runner v4는 turn별 interim ≥1·speech-final=1과 latency를, 관리자 runtime sampler는 worker/Uvicorn queue high-water를, Linux sampler는 exact image/container/cgroup/proc/Docker/TCP high-water를 기록한다. 별도 human contract는 synthetic/raw material을 거부하고 30명/50회기/150축·blind 2인·ICC/κ·10,000 cluster bootstrap을 재계산한다. 네 artifact는 같은 public host·겹치는 50분 시간창이어야 canonical checker를 통과한다. <b>검증:</b> API/G7 <code>92 passed</code>, evidence runner/checker·release gate <code>31 passed</code>, voice UI <code>2/2</code>, typecheck·API type·Ruff·py_compile. 실제 마이크·운영 Deepgram·실제 참가자는 사용하지 않아 G7은 계속 <b>BUILD</b>다.</p>
|
||||
<p class="dg-note">179차 적용(2026-08-07): G7 내부 hardening release-only SHA <code>a8c27b0aadcb9dacea86c4be5e62e19277b9cc2678ebca51aa02998d2c2d1a0d</code>를 격리 NAS <code>vignette-preview-20260807</code>에 승격했다. 첫 release-agent 실행은 새 동의 모달을 따르지 않던 DB-backed mic fixture 1건을 23/24에서 fail-closed로 차단하고 원격을 변경하지 않았다. fixture를 마이크→동의 원장→synthetic capture 순서로 고친 뒤 focused <code>1/1</code>, clean candidate session E2E <code>24/24</code>, API 전체, API contract·typecheck·build를 통과했다. 최종 API/Web 이미지 <code>79ec…4450</code>/<code>c530…2f28</code>, health ok·db/engine true, OpenAPI 124·auth 401·G0~G8, readiness 3회, NAS browser SSE→DB review를 확인했다. 물리 마이크는 사용하지 않았고 G7 외부 four-artifact gate는 계속 <b>BUILD</b>다.</p>
|
||||
<p class="dg-note">180차 적용(2026-08-07): current source release-only SHA <code>6030a677af7e…c611</code>를 격리 NAS <code>vignette-preview-20260807</code>에 승격했다. 최초 전체 gate는 108개 중 5개를 fixture drift 2종과 runtime-critical <code>shell.css</code> 분류 누락으로 차단해 원격을 바꾸지 않았고, G5→G4 무호출·StrictMode retry·learner 모바일 4열 packaging을 focused 6/6과 manifest 회귀로 고정했다. 최종 candidate browser E2E <code>108/108</code>, postdeploy 실제 SSE→DB review <code>32.437s</code>, health <code>3/3</code>, OpenAPI 126·auth 401·G0~G8 route를 통과했다. API/Web 이미지는 <code>52e0…8b2d</code>/<code>6fdb…f215</code>이며 named DB volume과 배포 전 dump를 보존했다. 이 승격은 current runtime 증거이고 G8 receipt-bound 실제 image rollback은 별도 GATE다.</p>
|
||||
<p class="dg-note">181차 적용(2026-08-07): <b>G8의 마지막 게이트인 실제 receipt-bound image rollback을 격리 NAS에서 실행</b>해 DONE으로 승격했다. 먼저 성립 불가능한 Gate6 계약을 정정했다. 감사 대상 current API 이미지 자체가 <code>com.docker.compose.project=vignette-preview-20260807</code>·<code>service=api</code> image label을 갖고 있어 "helper의 compose label 0개"는 감사되지 않은 다른 이미지를 쓰지 않는 한 성립하지 않는다. 그래서 계약을 <b>key 부재가 아니라 소속</b>으로 바꿔 <code>scripts/launch-nas-preview-g8-helpers.py</code>에 고정했다. image 상속 label을 baseline으로 읽고, container의 모든 compose label이 baseline과 같거나 선언된 격리 override인지 검사하며, 최종 project는 <code>vignette-g8-helper-*</code>·service는 <code>g8-rollback-*</code>이어야 하고, <code>docker run</code> argv에 target project/service label을 주입하면 fake-runner 테스트가 먼저 깨진다(<code>37/37</code>). 실행은 executor <code>127.0.0.1:18149</code>(host network·read-only rootfs·cap-drop ALL·no-new-privileges·uid1028/gid100·group101)와 별도 lifecycle의 control-plane API <code>127.0.0.1:8018</code>로 구성했다. 계획했던 Windows SSH 터널은 NAS sshd가 <code>administratively prohibited</code>로 direct-tcpip를 거부해 사용할 수 없었고, sshd 설정을 바꾸는 대신 같은 격리 계약의 NAS-side probe 컨테이너에서 실행해 control-plane(8018)·preview(8088) origin 분리를 그대로 유지했다. 결과는 <code>rollback-old</code> receipt <code>nas-g8-723eeef2…</code>, <code>restore-current</code> receipt <code>nas-g8-2738846c…</code>로 실제 이미지가 <code>52e0/6fdb</code>→<code>79ec/c530</code>→<code>52e0/6fdb</code>로 왕복했고, release gate·approval 2회 멱등, DB <code>audit.ci_lifecycle_event</code> <code>rollback/executed</code> 2건과 receipt id 결속, silent auto-promotion 0, HMAC journal 6-record 체인 검증, health 3/3·OpenAPI 126·auth 401·Web 200, helper 0·listener 0 복귀와 비밀 env 파기를 확인했다. <code>down</code>·<code>volume rm</code>·<code>prune</code>은 실행하지 않았고 공개 런타임은 건드리지 않았다. G7은 Deepgram live·인증 WSS·명시 동의 물리 마이크 50분·독립 라벨 human pack이 남아 <b>외부 GATE</b>로 유지한다.</p>
|
||||
<div class="dg-principles" aria-label="디자인 생성 가드레일">
|
||||
<div><b>래스터만 사용</b><span>이미지 생성 도구 산출물은 PNG 기반 시안이다. SVG·벡터·와이어프레임·로고 시트로 해석하지 않는다.</span></div>
|
||||
<div><b>기능 우선</b><span>메인 라우트의 실제 액션과 정보 구조를 먼저 반영한다. 장식은 기능을 가리지 않는 수준에서만 쓴다.</span></div>
|
||||
|
|
@ -789,9 +815,13 @@
|
|||
<div class="track-group">
|
||||
<div class="tg-head"><h3>코드품질 · 로컬 개발 스택</h3><span class="tg-note">리팩토링 연구 + 1-커맨드 dev 스택 (2026-06-26~27). code-quality-research-2026-06-26.md · local-development.md</span></div>
|
||||
<div class="scards">
|
||||
<article class="scard" data-status="doing" data-cat="인프라·스택·RAG" data-owner="0">
|
||||
<button class="scard-head" aria-expanded="false"><span class="chip c-doing">DB RECOVERY · PUBLIC LIVE</span><span class="scard-mid"><span class="scard-title">공개 계정·회기 복구와 무손실 전환</span><span class="scard-sum">복구 named volume을 공개 55432에 전환했다. 데이터·auth·watchdog는 정상이며 사용자 재로그인 후 과거 소유 회기 확인만 남았다.</span></span><span class="caret" aria-hidden="true"></span></button>
|
||||
<div class="scard-body"><div class="kv k-good"><b>공개 전환</b><p>2026-08-07 18:26 KST owner 승인으로 <code>vignette_recovered_prod_20260807</code>을 <code>vignette-dev-db:55432</code>에 연결했다. public owner 집계 users 84·sessions 30·turns 705, Google 계정 16·소유 회기 30, orphan 0이다. local/public health는 prod·DB true·engine true·claude_cli, OpenAPI 119, Google OAuth 302, 공개 로그인 화면 렌더와 watchdog check-only를 통과했다.</p></div><div class="kv"><b>보존·rollback</b><p>전환 직전 DB는 <code>vignette-dev-db-pre-recovery-20260807-182642</code>로 중지 보존했고 exact dump SHA는 <code>6b84d5c8…8af1f</code>다. 원본 volume, post-merge dump <code>d24eaa75…2e32</code>, 최종 recovered dump <code>d7bcc396…68ed</code>, 자동 rollback을 실증한 실패 후보 2개도 삭제하지 않았다.</p></div><div class="kv k-warn"><b>사용자 확인</b><p>보안상 기존 <code>auth_session</code>은 복사하지 않아 모두 Google 재로그인이 필요하다. 계정·회기 원장은 복구돼 있다. 실제 사용자가 재로그인한 뒤 과거 소유 회기 목록이 보이는지 확인할 때까지 이 카드는 안정화 중으로 유지한다. 상세: <code>ops/public-db-recovery-rehearsal-2026-08-07.md</code>.</p></div></div>
|
||||
</article>
|
||||
<article class="scard" data-status="done" data-cat="인프라·스택·RAG" data-owner="0">
|
||||
<button class="scard-head" aria-expanded="false"><span class="chip c-done">DEV STACK</span><span class="scard-mid"><span class="scard-title">로컬 개발 스택 1-커맨드 기동 — <code>scripts/dev-up.ps1</code></span><span class="scard-sum">게이트웨이9099+API8000+웹5173+Docker DB 보장. 3포트 health OK + 실 AI 턴.</span></span><span class="caret" aria-hidden="true"></span></button>
|
||||
<div class="scard-body"><div class="kv"><b>현재</b><p>게이트웨이(9099 claude_cli)+API(8000, DB 미가용 시 in-memory degraded)+웹(5173)을 커맨드라인 기준 정리 후 결정론적 기동(<code>--reload</code> 워처 불안정 회피, uvicorn 있는 python 자동 해석). Docker가 사용 가능하면 <code>vignette-dev-db</code> healthcheck/pg_isready/role safety를 점검하고, compose <code>infra\.env</code> 필수값 누락도 경고한다. <code>-NoGateway</code>/<code>-NoWeb</code>는 해당 컴포넌트 정리도 건너뛰고, API 정리는 지정 <code>-ApiPort</code>만 대상으로 한다. dev-login + seed 페르소나. 로그 <code>.devlogs/</code>(gitignore). <b>검증</b>: 3포트 health OK + 실제 AI 턴 생성·회기리뷰 동작, API-only 재기동 후 8001/8010/5173/9099/20241 보존.</p></div><div class="kv k-warn"><b>비고</b><p>AI 턴은 로컬 claude CLI 사용(사용량 발생). 진입점 http://localhost:5173. <code>dev-down.ps1</code>은 기본적으로 DB 컨테이너를 보존하고, <code>-Db</code> 지정 시 함께 중지한다. 기존 <code>vignette-dev-db</code>는 accepting connections이나 healthcheck가 없고, <code>vignette_app</code>은 NOBYPASSRLS로 존재한다. 이번 패키징 smoke에서 fresh compose DB는 owner/app role 분리 상태로 API healthy까지 통과했고, 기존 dev DB의 운영 role 전환은 별도 owner migration 확인 후 진행한다. <b>주의(무DB 한정)</b>: DB 없이 degraded면 페르소나가 <code>source:seed_fallback·degraded:true</code>라 프론트 <code>isUsablePersona</code>가 막아 "회기 시작" 버튼이 비활성 → UI 세션 시작엔 실제 Postgres(<code>127.0.0.1:55432</code>)가 필요.</p></div></div>
|
||||
<button class="scard-head" aria-expanded="false"><span class="chip c-done">DEV STACK · DB 보존 패치</span><span class="scard-mid"><span class="scard-title">로컬 개발 스택 1-커맨드 기동 — <code>scripts/dev-up.ps1</code></span><span class="scard-sum">정지 DB는 start 우선, 신규 PGDATA는 고정 named volume, 일반 dev-up의 DB 삭제 경로는 0.</span></span><span class="caret" aria-hidden="true"></span></button>
|
||||
<div class="scard-body"><div class="kv"><b>현재</b><p>게이트웨이(9099 claude_cli)+API(8000)+웹(5173)을 결정론적으로 기동한다. DB는 이름만 같은 새 container로 바꾸지 않는다. 기존 container가 stopped이면 <code>docker start</code>와 readiness를 먼저 수행하고, 그 외 비정상 상태·시작 실패·readiness timeout은 container/volume을 보존한 채 fail closed한다. 신규 DB만 <code>vignette-dev-db-pgdata</code> named volume을 사용하며 일반 경로에는 container rm·volume rm/prune·compose down -v가 없다.</p></div><div class="kv k-good"><b>검증</b><p><code>scripts/test_dev_up_db_safety.py</code> 5 passed, Ruff, Windows PowerShell 5.1 AST parser, diff check 통과. 실행 중인 DB container ID·생성 시각·PGDATA volume은 테스트 전후 동일했다.</p></div></div>
|
||||
</article>
|
||||
<article class="scard" data-status="done" data-cat="인프라·스택·RAG" data-owner="0">
|
||||
<button class="scard-head" aria-expanded="false"><span class="chip c-done">VOICE 로컬 작동</span><span class="scard-mid"><span class="scard-title">음성 채팅 무음 해결 — provider + 브라우저 재생 경계 보강</span><span class="scard-sum">OPENAI_API_KEY 설정 확인, Web Audio unlock 재생, local live STT/TTS WS smoke 통과.</span></span><span class="caret" aria-hidden="true"></span></button>
|
||||
|
|
@ -823,6 +853,49 @@
|
|||
</article>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- TRACK 4: OUTCOME & ALLIANCE OS -->
|
||||
<div class="track-group" id="outcome-alliance-os">
|
||||
<div class="tg-head"><h3>Outcome & Alliance OS · 정식 승격 로드맵</h3><span class="tg-note">제약이 아닌 증거 게이트로 진행 · 사람 개발자 계획 없음 · 2026-08-06 승격</span></div>
|
||||
<div class="scards">
|
||||
<article class="scard" data-status="done" data-cat="Outcome OS·측정" data-owner="0">
|
||||
<button class="scard-head" aria-expanded="false"><span class="chip c-done">G0 DONE · INTERNAL</span><span class="scard-mid"><span class="scard-title">측정 진실 기반 — Measurement Truth Foundation</span><span class="scard-sum">G5 실제 수행 원장까지 포함한 current producer census 29/29와 provenance 위반 0을 고정했다.</span></span><span class="caret" aria-hidden="true"></span></button>
|
||||
<div class="scard-body"><div class="kv"><b>구현</b><p><code>app.measurement_event</code>·<code>app.measurement_instrument</code>·<code>audit.model_run</code>과 6종 provenance, Python→JSON Schema→TypeScript 계약, 기존 rapport/alliance/fast/deep/Phase 3 adapter, 혼합층 집계 차단, 8개 deterministic benchmark 장면을 구축했다. <b>AOS-001~004 완료</b>.</p></div><div class="kv k-good"><b>종료 증거</b><p>post-merge dump disposable clone에 migrations 14/15/16을 각각 두 번 원자 재적용했다. current source census는 producer 29/29, unknown/missing table·null instrument/source/perspective·orphan instrument/model-run/evidence·호환성 위반 0이다. 기존 계약/schema·live append-only/RLS 증거도 유지한다. 상세: <code>ops/evidence/g0-provenance-census-scratch-post-g6-g8-20260807.json</code>, <code>ops/evidence/measurement-migration-rehearsal-2026-08-07.json</code>.</p></div></div>
|
||||
</article>
|
||||
<article class="scard" data-status="done" data-cat="Outcome OS·동맹" data-owner="0">
|
||||
<button class="scard-head" aria-expanded="false"><span class="chip c-done">G1 DONE · EXTERNAL ICC 분리</span><span class="scard-mid"><span class="scard-title">Alliance Core — goal/task/bond 다관점 측정</span><span class="scard-sum">승격 prompt 1.2는 24/24 ready·방향 9/9·오류 0으로 재검증했고 human-AI ICC는 외부 파일럿 품질 목표로 분리한다.</span></span><span class="caret" aria-hidden="true"></span></button>
|
||||
<div class="scard-body"><div class="kv"><b>구현</b><p>회기 pulse, legacy alliance 격리, 자기평가 선잠금, 독립 모델 실행·재시도, 학습자 관점차 리뷰, 교수자 append-only 재평가를 구축했다. 실제 Session은 첫 발화 전 pre를 fail-closed로 잠그고 첫 왕복 뒤 흐름을 막지 않는 mid 30초 점검을 제공한다. <b>AOS-005~010 완료</b>.</p></div><div class="kv k-good"><b>종료 증거</b><p>focused backend 43 passed, live PostgreSQL reveal/RLS/audit/supersession smoke, desktop/mobile Alliance Pulse 6/6와 기존 회기 리뷰 12/12 통과. 동일 payload stable ID, 변경 payload 409, 타 학습자·교차 cohort 404와 pre/mid desktop·mobile 2/2·고정 캡처 4장을 추가 검증했다. 최신 source API/dev DB는 한 회기의 <code>pre(degraded/insufficient_transcript) → mid(ready) → post(ready)</code>를 실제 turn UUID·ended_at으로 닫고, awaiting 응답의 external-event read skew와 degraded terminal DB 제약을 교정했다. prompt 1.2.0 합성 gold 방향 정확도 9/9, 최종 실패 0, 근거 recall 88.9%. 상세: <code>ops/evidence/alliance-pulse-pre-mid-post-live-2026-08-07.json</code>, <code>ops/outcome-os-g1-g4-g5-g7-self-directed-integration-evidence-2026-08-07.md</code>.</p></div></div>
|
||||
</article>
|
||||
<article class="scard" data-status="done" data-cat="Outcome OS·종단" data-owner="0">
|
||||
<button class="scard-head" aria-expanded="false"><span class="chip c-done">G2 DONE · INTERNAL</span><span class="scard-mid"><span class="scard-title">Longitudinal Outcome Trajectory — 종단 성과 궤적</span><span class="scard-sum">5회기 예상 궤적·악화·관계 사건을 세 개 독립 축과 장면 근거로 조기에 추적한다.</span></span><span class="caret" aria-hidden="true"></span></button>
|
||||
<div class="scard-body"><div class="kv"><b>구현</b><p>교육용 합성 5회기 arc, 세 축 outcome 원장·append-only revision, 누락·오류 무점수, safety 분리, early warning·다음 질문, 역할별 관계 기억, 학습자 체크인과 desktop/mobile 타임라인을 구축했다.</p></div><div class="kv k-good"><b>종료 증거</b><p>4-case×5-session benchmark status 정확도 1.0·false alert/critical miss 0. 기존 실제 DB/API on-track case의 S1~S5·관측 15개·멱등/409·RLS·role-safe 기억에 더해, production measurement producer→store→evaluator를 통과하는 실제 PostgreSQL 두 case·10회기·30개 측정으로 악화 <code>on_track→off_track→deteriorating</code>과 단발성 noise <code>on_track→watch→on_track</code>·alert 승격 0건을 검증했다. case별 revision 5개와 learner/teacher projection 일치, 합성·비임상 표시를 보존했다. G2 focused 40 passed, 전체 API 855 passed, G2 UI 12/12, G1+기존 리뷰 15/15. 상세: <code>ops/outcome-trajectory-live-integration-evidence-2026-08-06.md</code>, <code>ops/evidence/outcome-trajectory-alerts-live-2026-08-07.json</code>.</p></div></div>
|
||||
</article>
|
||||
<article class="scard" data-status="done" data-cat="Outcome OS·균열" data-owner="0">
|
||||
<button class="scard-head" aria-expanded="false"><span class="chip c-done">G3 DONE · INTERNAL</span><span class="scard-mid"><span class="scard-title">Rupture & Repair Lab — 관계 균열과 복구</span><span class="scard-sum">균열을 자연스럽게 만들고 탐지·복구·재연습의 전 과정을 장면 단위로 검증한다.</span></span><span class="caret" aria-hidden="true"></span></button>
|
||||
<div class="scard-body"><div class="kv"><b>구현</b><p>9종 rupture/repair 상태머신·append-only 원장, Scenario Director, 텍스트·SSE·음성 공통 finalizer 감지 경계, 회기 후 근거 장면·복구 분기, fast/deep reconciliation, 교수자 supersession을 구축했다.</p></div><div class="kv k-good"><b>종료 증거</b><p>11-case benchmark macro-F1 1.0·복구 상태 accuracy 1.0, critical miss·judge gaming·암기문구 false resolution 0. Scenario/G3 107 passed, 통합 계약 251 passed, 리뷰 desktop/mobile 50/50. 기존 수동 write API 계약과 별도로 실제 텍스트 <code>submit_turn</code>·공용 finalizer callback만 사용한 PostgreSQL smoke가 missed·partial·resolved 3 episode, 10개 append-only observation, 3개 reconciliation, learner/teacher projection, 세션 밖 evidence 참조 0건을 통과했다. SSE·음성 개별 transport 실 DB smoke는 이 증거 범위가 아니다. 상세: <code>ops/rupture-repair-live-integration-evidence-2026-08-06.md</code>, <code>ops/evidence/rupture-runtime-auto-live-2026-08-07.json</code>.</p></div></div>
|
||||
</article>
|
||||
<article class="scard" data-status="done" data-cat="Outcome OS·수련" data-owner="0">
|
||||
<button class="scard-head" aria-expanded="false"><span class="chip c-done">G4 DONE · INTERNAL</span><span class="scard-mid"><span class="scard-title">Deliberate Practice Engine — 의도적 수련 엔진</span><span class="scard-sum">실제 API/DB/엔진과 route-mock 0 브라우저에서 처방→후속 회기→observer 폐루프를 닫았다.</span></span><span class="caret" aria-hidden="true"></span></button>
|
||||
<div class="scard-body"><div class="kv"><b>구현</b><p>원자적 practice prescription, 5개 연습 모드, competency graph·attempt evidence, 약점/망각 우선 선택, append-only PostgreSQL/API·RLS·내부 토큰, 학습자/교수자 리뷰 UI를 연결했다. ready 회기말 평가 저장 뒤 production <code>session_learning_producer</code>가 durable turn intent deviation을 실제 처방으로 변환한다. 학습자 CTA는 처방·원본 회기·성공 기준을 학습 홈→시작 전 브리핑→실제 새 세션 URL까지 보존한다.</p></div><div class="kv k-good"><b>종료 증거</b><p>실제 API/DB/엔진 smoke는 prescriptions 2·episodes 6·attempts 6·append-only correction 1·ready observer model-run 2를 통과했다. post-merge disposable clone의 실제 브라우저는 route mock 0으로 desktop 2/2·mobile 2/2, returned 카드 우선·G4 POST·read-model reload·before/after·멱등 retry를 확인했고 최종 episode/attempt/ready observer 각 1, 중복·UUID 노출·마이크 호출·overflow 0이다. 전체 API 현재 898 passed. 상세: <code>ops/evidence/scratch-browser-returned-practice-closed-loop-evidence-20260807.json</code>, <code>ops/outcome-os-g4-g5-production-caller-evidence-2026-08-07.md</code>.</p></div></div>
|
||||
</article>
|
||||
<article class="scard" data-status="done" data-cat="Outcome OS·보정" data-owner="0">
|
||||
<button class="scard-head" aria-expanded="false"><span class="chip c-done">G5 DONE · INTERNAL</span><span class="scard-mid"><span class="scard-title">Calibration Mirror & Transfer — 자기보정과 전이</span><span class="scard-sum">학생 actual execution을 실제 API/DB/엔진과 브라우저에서 독립 observer·provenance까지 닫았다.</span></span><span class="caret" aria-hidden="true"></span></button>
|
||||
<div class="scard-body"><div class="kv"><b>구현</b><p>prediction revision/lock/reveal, 독립 수행 관측, calibration error·95% interval·메타인지 처방, 미지 transfer suite·암기 차단·합성 drift 원장/API/RLS와 역할 안전 UI를 연결했다. production lock callback은 ready 평가와 같은 competency의 durable turn evidence가 있을 때만 failed 독립 관찰을 파생하며 pass·mastery·transfer는 자동 추론하지 않는다. 실패한 unseen trial과 자기보정 처방을 학습 홈과 실제 새 세션까지 보존한다.</p></div><div class="kv k-good"><b>종료 증거</b><p>실제 API/DB/엔진 smoke는 execution·model-inferred·independent observer·instrument v1·durable evidence·distinct model-run을 각각 1로 고정하고 멱등 및 오류 출처·미종료·타학습자·교차 cohort 차단을 통과했다. post-merge disposable clone 브라우저는 route mock 0으로 desktop/mobile 4/4, actual POST→read reload→before/after와 1→1 멱등 재확인을 통과했고 UUID 노출·마이크 호출·중복 원장 0이다. 4-case benchmark와 lock/reveal/RLS 계약도 유지한다. 상세: <code>ops/evidence/scratch-browser-returned-practice-closed-loop-evidence-20260807.json</code>, <code>ops/calibration-transfer-live-integration-evidence-2026-08-06.md</code>.</p></div></div>
|
||||
</article>
|
||||
<article class="scard" data-status="done" data-cat="Outcome OS·슈퍼비전" data-owner="0">
|
||||
<button class="scard-head" aria-expanded="false"><span class="chip c-done">G6 DONE · INTERNAL</span><span class="scard-mid"><span class="scard-title">Supervision & Research OS — 교수자·연구 운영체계</span><span class="scard-sum">current safety event를 raw detail 없이 최우선 queue와 직접 원장 pointer로 실증했다.</span></span><span class="caret" aria-hidden="true"></span></button>
|
||||
<div class="scard-body"><div class="kv"><b>구현</b><p>append-only attention queue·원장 pointer, metadata-only 교수자/AI 불일치 dataset·audit, cohort gap, version/subgroup drift, 4-domain Phase 3 manifest와 역할 안전 교수자·연구 UX를 연결했다. scheduled producer가 G0~G5 실제 원장에서 attention/gap을 파생하고 repo-approved synthetic version comparison을 별도 research 트랜잭션으로 실행한다.</p></div><div class="kv k-good"><b>종료 증거</b><p>기존 6-signal queue·3-click·네 도메인 manifest·실제 API/DB/RLS/audit·version/subgroup 증거에 더해 post-merge disposable clone의 current source에서 safety event와 deterioration 경쟁 신호를 실제 derive했다. 결과는 <code>safety_boundary(1) > deterioration(2)</code>, direct pointer 각 1, source detail canary 1·G6 projection canary 0, raw transcript/임상 주장 0이고 replay가 멱등이다. 상세: <code>ops/evidence/g6-safety-priority-runtime-scratch-20260807.json</code>, <code>ops/outcome-os-g6-version-comparison-producer-evidence-2026-08-07.md</code>.</p></div></div>
|
||||
</article>
|
||||
<article class="scard" data-status="doing" data-cat="Outcome OS·음성" data-owner="0">
|
||||
<button class="scard-head" aria-expanded="false"><span class="chip c-doing">G7 GATE · INTERNAL DONE · EXTERNAL PROOF</span><span class="scard-mid"><span class="scard-title">Multimodal Alliance — 음성·비언어 동맹 신호</span><span class="scard-sum">내부 안전·복구·용량 계약은 닫았고 실제 provider·동의 마이크·사람 평가 증거를 기다린다.</span></span><span class="caret" aria-hidden="true"></span></button>
|
||||
<div class="scard-body"><div class="kv k-good"><b>내부 구현 DONE</b><p>consent→단일 clock→독립 text/voice/fusion→철회·tombstone, streaming interim/final·word timestamp·provider event, HMAC word pseudonym, 열린 stream의 1초 동의 재검사·abort, 텍스트 보존·음성 재연결 UX를 연결했다. 격리 synthetic PCM soak는 59/59 연결 종료·성공 발화 55·철회 차단 2·미동의 차단 1·overflow 거부/복구 1/1·10MiB 선초과 거부를 통과했고, G7 focused 119 passed·Ruff·compile을 통과했다. 공개 WSS preflight는 TLS 1.3/Cloudflare 연결과 비인증 1008 차단, 마이크 열거·캡처 0을 확인했다. 증거: <code>ops/evidence/g7-public-wss-preflight-current-20260807.json</code>.</p></div><div class="kv k-warn"><b>외부 종료 GATE</b><p>실제 Deepgram 운영 key·quota와 live interim/final, 기대값과 일치하는 authenticated public WSS ready, 실행 직전 명시 동의 물리 마이크 50분 양방향 soak, 같은 public host·겹치는 시간창의 worker/Uvicorn queue와 exact image·cgroup/proc/Docker/TCP high-water, 30명/50회기/150축 독립 라벨 held-out voice-gain이 필요하다. 네 artifact가 <code>scripts/check-g7-external-proof.py</code> exit 0을 만들기 전에는 메인 상태를 DONE으로 바꾸지 않는다. 현재 canonical checker exit 1은 이 외부 증거 부재를 정직하게 차단한 결과다.</p></div></div>
|
||||
</article>
|
||||
<article class="scard" data-status="done" data-cat="Outcome OS·에이전틱" data-owner="0">
|
||||
<button class="scard-head" aria-expanded="false"><span class="chip c-done">G8 DONE</span><span class="scard-mid"><span class="scard-title">Autonomous Content & Continuous Improvement</span><span class="scard-sum">receipt-bound rollback executor가 격리 NAS에서 실제 image rollback과 restore를 두 번의 executed receipt로 실행했다.</span></span><span class="caret" aria-hidden="true"></span></button>
|
||||
<div class="scard-body"><div class="kv"><b>agentic worker + 운영 환류 + human gate</b><p>승인 source의 실제 hash·PII를 engine 호출 전에 검사하고 structured generator 1개, 결과를 공유하지 않는 red-team 2개, variant generator와 variant별 독립 judge 3개 이상을 실행한다. 정답 누수·PII·unknown source claim·open finding·안전/보상 해킹·모델/structured 실패는 store 호출 전에 차단한다. repo-approved source는 기본 비활성 scheduler가 durable lease/retry job으로 관리한다. 실제 <code>claude_cli/claude-opus-4-8</code> 7 calls와 dev PostgreSQL reviewer 2·variant 3·pass rate 1.0을 거쳐 synthetic payload를 <code>pending_human_approval</code>로 저장했다. G6 canonical drift trigger는 metadata-only incident→DAG→job을 원자 enqueue하고, 실제 pending 후보의 관리자 검수는 catalog 0→1·approval 1과 역할 403·멱등/409·민감정보 비노출을 통과했다. 별도 누수 후보는 422·저장 0이다. 상세: <code>ops/outcome-os-g8-agentic-worker-evidence-2026-08-07.md</code>.</p></div><div class="kv k-good"><b>current NAS runtime</b><p>release-only SHA <code>6030a677…c611</code>를 전용 NAS Compose 프로젝트에 승격했다. API/Web 이미지 <code>52e0…8b2d</code>/<code>6fdb…f215</code>, health 3/3·db/engine true, OpenAPI 126·auth 401·G0~G8, browser E2E 108/108과 실제 SSE→DB-backed review를 확인했다. named DB volume과 predeploy dump를 보존했다.</p></div><div class="kv k-good"><b>실제 rollback receipt DONE</b><p>runtime+release_gate 전용 executor는 승인·idempotency·artifact·Compose/env/resolved-policy SHA와 exact API/Web image를 일회성 manifest에 결속하고 HMAC journal·crash recovery·previous-image 복구를 고정한다. 2026-08-07 격리 NAS에서 별도 loopback control-plane API(<code>127.0.0.1:8018</code>)와 별도 executor(<code>127.0.0.1:18149</code>)로 <b>실제 image rollback 2회</b>를 실행했다. <code>rollback-old</code>는 receipt <code>nas-g8-723eeef2…</code>로 previous 이미지 <code>79ec…4450</code>/<code>c530…2f28</code>를, <code>restore-current</code>는 receipt <code>nas-g8-2738846c…</code>로 current 이미지 <code>52e0…8b2d</code>/<code>6fdb…f215</code>를 활성화했다. release gate·approval 각각 2회 멱등, lifecycle <code>rollback/executed</code> 2건, 승인 <code>authorize_rollback</code> 2건, silent auto-promotion 0, HMAC journal 6-record 체인 검증, health 3/3·OpenAPI 126·auth 401·Web 200을 확인했다. helper는 image 상속 compose label을 baseline으로 두고 project/service를 격리 override하는 계약으로 검증했다(<code>launch-nas-preview-g8-helpers.py</code>, 37/37). 상세: <a href="./ops/nas-preview-g8-rollback-proof-runbook.md">런북</a> · <a href="./ops/evidence/nas-preview-g8-actual-rollback-2026-08-07.json">기계 판독 증거</a>.</p></div></div>
|
||||
</article>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ░░ DEEP / FOLDED REFERENCE ░░ -->
|
||||
|
|
@ -835,7 +908,7 @@
|
|||
</div>
|
||||
|
||||
<details class="fold" id="fold-roadmap">
|
||||
<summary><span class="fcaret" aria-hidden="true"></span>전체 개발 로드맵 (5단계)<span class="fmeta">MASTERPLAN + REVISIONS</span></summary>
|
||||
<summary><span class="fcaret" aria-hidden="true"></span>전체 개발 로드맵 (기존 5단계 + Outcome OS)<span class="fmeta">MASTERPLAN + OUTCOME & ALLIANCE OS</span></summary>
|
||||
<div class="fold-inner">
|
||||
<div class="roadmap">
|
||||
<article class="phase-card current"><div class="phase-top"><span class="phase-id">Phase 0</span><span class="tag doing">closing</span></div><h3>기반 정렬 / 운영 안전장치</h3><p>인프라, 인증 경계, fail-closed, 공개 배포, 회귀 테스트 안정화.</p><ul class="phase-list"><li><b>상태:</b> 대부분 완료, 공개 <code>/turn</code> proof만 남음</li><li><b>검증:</b> build + backend 178 기준선 + Playwright 113 기준선, Node/backend focused 120</li><li><b>운영:</b> prod API 8001, engine 9099, tunnel 1개</li></ul></article>
|
||||
|
|
@ -843,6 +916,7 @@
|
|||
<article class="phase-card"><div class="phase-top"><span class="phase-id">Phase 2a</span><span class="tag info">planned</span></div><h3>3역할 / OAuth / 페르소나</h3><p>학습자, 교수자, 관리자 권한 분리와 페르소나 확장.</p><ul class="phase-list"><li><b>DoD:</b> 타인 session 직접 접근 403</li><li><b>범위:</b> OAuth BFF, RBAC, 교수 검수 루프</li><li><b>주의:</b> visible_to와 인간 권한은 별도 레이어</li></ul></article>
|
||||
<article class="phase-card"><div class="phase-top"><span class="phase-id">Phase 2b</span><span class="tag info">planned</span></div><h3>음성 캐스케이드</h3><p>STT → 엔진 → TTS 왕복, WSS/터널 안정성, barge-in 로깅.</p><ul class="phase-list"><li><b>DoD:</b> 50분 WSS 끊김 실측</li><li><b>범위:</b> Deepgram STT, OpenAI TTS, voice preset</li><li><b>리스크:</b> 실시간 레이턴시는 실측 전 완료 표기 금지</li></ul></article>
|
||||
<article class="phase-card"><div class="phase-top"><span class="phase-id">Phase 3</span><span class="tag info">planned</span></div><h3>교육용 파일럿 / 효과성</h3><p>수련생 파일럿, 사전사후 효과 측정, 재귀학습 데이터셋 산출.</p><ul class="phase-list"><li><b>DoD:</b> 20명 완주 + KPI 충족</li><li><b>측정:</b> SUS, 자기효능감, κ/ICC, 환각률</li><li><b>정정:</b> IRB 게이트가 아니라 데이터 활용동의/개인정보 게이트</li></ul></article>
|
||||
<article class="phase-card next"><div class="phase-top"><span class="phase-id">Outcome OS</span><span class="tag doing">G7 external proof</span></div><h3>성과·동맹·수련 폐루프</h3><p>측정 → 이상 탐지 → 원인 가설 → 재연습 → 전이 검증 → 숙련도 갱신.</p><ul class="phase-list"><li><b>상태:</b> G0~G6 internal DONE · G8 실제 NAS rollback receipt DONE · G7 외부 voice/resource/human 대기</li><li><b>완료 DAG:</b> AOS-001~012</li><li><b>운영:</b> current source NAS SHA <code>6030a677…c611</code> 승격 완료 · G8 loopback control-plane rollback/restore receipt 2건 · G7 canonical four-artifact checker 준비</li></ul></article>
|
||||
</div>
|
||||
<div class="source-note">정정 기준: <code>MASTERPLAN</code> 원본의 IRB 신청/승인 게이트는 <code>MASTERPLAN_REVISIONS</code> 기준으로 제거한다. Phase 2는 2a(3역할/OAuth/페르소나)와 2b(음성 캐스케이드)로 분리하고, Phase 1 DoD에는 얇은 음성 왕복 1턴 PoC를 포함한다.</div>
|
||||
</div>
|
||||
|
|
@ -929,7 +1003,7 @@
|
|||
<div class="task-row"><div><span class="task-status s-done">DONE</span></div><div><b>OpenAI TTS voice preset 추상화</b><p>P1/P2/P3 persona voice preset, invalid preset fallback, speed clamp, instructions payload 계약을 회귀화했고, <code>app.persona_voice_map</code>을 live <code>/voice/ws</code> TTS 선택에 연결했다. 명시 preset은 DB map보다 우선하고, OpenAI가 아닌 provider row는 기존 fallback으로 안전하게 흡수한다.</p></div><div><b>산출물</b><p><code>resolve_voice</code>, <code>resolve_voice_from_map</code>, <code>build_tts_payload</code>, <code>get_session_voice_map</code>, seed voice map row, dev runtime schema guard</p></div><div><b>검증</b><p><code>pytest app/test_runtime_policy.py app/test_persona_review.py app/test_voice_service.py app/test_voice_ws.py -q</code> 75 passed; full API 178 passed + gateway 11 passed.</p></div></div>
|
||||
<div class="task-row"><div><span class="task-status s-done">POC DONE</span></div><div><b>P1 서연 무참조 synthetic voice 샘플</b><p>Higgs v3 로컬 런타임에서 실존 reference voice 없이 synthetic seed를 먼저 만들고, 그 seed만 재참조해 우울·피로·불안·라포·회복감 5개 변주를 생성했다. 개발용 <code>VIGNETTE_VOICE_POC_SAMPLE_TTS</code> 플래그로 <code>/voice/ws</code> P1 sample TTS provider에도 연결했다.</p></div><div><b>산출물</b><p><code>docs/archive/voice-art/bimec-persona-voice-poc-2026-06-27.md</code>, <code>docs/voice-art/p1-seoyeon-higgs-v3-20260627/manifest.json</code>, <code>apps/api/app/services/voice.py</code></p></div><div><b>검증</b><p>mp3/wav 6개(seed+5) 생성, 24kHz, 7.72~11.68초. live <code>/voice/ws</code> text_turn smoke: provider <code>p1-sample-poc</code>, binary 20 chunks/78,573 bytes. 샘플 provider는 TTS override만 담당하고 마이크/STT는 OpenAI 키가 필요하다. 프로덕션 탑재는 모델 라이선스·권리정책 결정 전 금지.</p></div></div>
|
||||
<div class="task-row"><div><span class="task-status s-done">LOCAL DONE</span></div><div><b>barge-in / paralinguistic logging</b><p>음성 learner turn에 audio_ref, silence_ms, speech_rate, barge_in을 nullable 메타로 저장하고, 회기 리뷰에서 침묵·발화 속도·끼어듦·음성 입력 비언어 칩으로 표시한다. 2차로 <code>provider_events</code> JSONB 보존 슬롯을 추가해 control/STT provider 이벤트를 allowlist·size limit 후 저장하고, 3차로 내부 taxonomy <code>event_type</code>/<code>category</code>를 붙인다. 4차로 인증된 회기 리뷰에는 한숨·울음·웃음·호흡·운율·배경소음 같은 정규화 이벤트만 label/detail 칩으로 제한 노출한다. 실제 Session 마이크 UI는 browser voice activity/trailing silence 메타를 <code>audio_end</code>에 싣고, readiness는 음성 메타 5개 컬럼 전체를 확인한다. raw transcript/text/provider/source/raw type은 응답에서 제외하고, 공개 공유 카드는 축어록과 provider raw를 계속 싣지 않는다.</p></div><div><b>산출물</b><p><code>app.turns.provider_events</code>, <code>TurnRecord.provider_events</code>, voice route 이벤트 sanitizing/taxonomy, <code>ReviewNonverbalEvent</code>, <code>SessionReview</code> 칩 UI, browser voice metadata capture, voice metadata readiness guard</p></div><div><b>검증</b><p><code>python -X utf8 -m pytest -p no:cacheprovider app/test_runtime_policy.py app/test_session_turn_persistence.py app/test_voice_ws.py app/test_voice_service.py -q</code> 77 passed; provider_events DB insert placeholder/args, WS raw key drop, STT+control event merge, internal taxonomy, client turn 미혼입, review 파생 칩 raw provider 미노출, 침묵 칩 중복 방지, 5-column readiness 검증. <code>PLAYWRIGHT_PORT=5197 npx playwright test e2e/voice-success.spec.ts --project=chromium-single-run --workers=1</code> 2 passed, <code>PLAYWRIGHT_PORT=5199 npx playwright test e2e/session-persistence.spec.ts --project=chromium-single-run --workers=1</code> 5 passed, <code>npm run typecheck</code>.</p></div></div>
|
||||
<div class="task-row"><div><span class="task-status s-plan">GATE</span></div><div><b>공개·실기기 음성 장시간 실측</b><p>남은 범위는 실제 Deepgram interim/final WSS, 물리 마이크, 공개 WSS, 50분 양방향 장시간 실측, provider 기반 한숨·울음 감지다. 로컬 OpenAI STT/TTS smoke와 synthetic mic E2E는 이 실측을 대체하지 않는다.</p></div><div><b>산출물</b><p>public WSS smoke log, physical mic capture log, 50분 soak evidence, provider paralinguistic event sample</p></div><div><b>검증</b><p>실제 공개/실기기 환경에서만 완료 가능. 현재는 B2 음성 캐스케이드 live gate로 추적한다.</p></div></div>
|
||||
<div class="task-row"><div><span class="task-status s-plan">GATE</span></div><div><b>공개·실기기 음성 장시간 실측</b><p>Deepgram adapter, HMAC word pseudonym, 1초 동의 재검사, 공통 consent transaction lock, bounded queue/10MiB cap, recovery UX와 provider/model ready 계약은 내부 완료다. 남은 범위는 실제 Deepgram key·quota live, 기대 provider/model authenticated public WSS, 명시 동의 물리 마이크, 50분 양방향 soak, 독립 라벨 voice-gain benchmark, 실제 운영 topology resource high-water와 provider 기반 한숨·울음 표본이다. mic-free preflight와 synthetic E2E/label은 이를 대체하지 않는다.</p></div><div><b>산출물</b><p>expected provider/model public WSS ready log, Deepgram interim/final 표본, explicit-consent physical mic capture log, 50분 soak evidence, independently labeled voice-gain report, worker/proxy resource high-water, provider paralinguistic event sample</p></div><div><b>검증</b><p>실제 공개/실기기/외부 provider 환경에서만 완료 가능. 현재는 B2 음성 캐스케이드 live gate로 추적한다.</p></div></div>
|
||||
<div class="task-row"><div><span class="task-status s-done">DECIDED</span></div><div><b>s2s 2차 PoC 판단</b><p>소유자 결정(2026-06-30): s2s 2차 PoC 유지. 캐스케이드 1차를 운영 기준으로 두고 <code>voice-s2s-poc.md</code> 비교 PoC를 계속 연다. 최종 채택/폐기는 비교 리포트 이후 재판단한다. 채택 여부 자체는 확정됐고, 비교 PoC 구현·실측이 후속 build로 남는다.</p></div><div><b>산출물</b><p><code>docs/decisions/voice-s2s-poc.md</code></p></div><div><b>검증</b><p>transcript, safety, audit, latency, failure, privacy 기준 명시. 실제 비교 실측은 후속.</p></div></div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -970,13 +1044,19 @@
|
|||
<tbody>
|
||||
<tr><td>Web typecheck</td><td><code>npm run typecheck</code></td><td>Passed</td></tr>
|
||||
<tr><td>Design SSOT / auth visual</td><td><code>npm run check:design-ssot</code> / <code>npx playwright test e2e/auth-visual.spec.ts --project=chromium-single-run --reporter=line</code> / <code>npx playwright test e2e/layout-visual-gate.spec.ts --project=chromium-single-run --reporter=line</code></td><td>SSOT checker passed; login/onboarding light-dark desktop-mobile 1 passed; 14 core screens × 7 widths visual gate 14 passed.</td></tr>
|
||||
<tr><td>Full Playwright E2E baseline</td><td><code>npm run e2e:parallel</code> / <code>npm run e2e:single-run</code> / <code>npm run e2e:list</code></td><td>현재 수집 기준 215 tests / 20 files. fixture desktop/mobile 166/166 passed(2.5분), DB/engine/provider 직렬 49/49 passed(23.2분), 합계 215/215 passed.</td></tr>
|
||||
<tr><td>Full Playwright E2E baseline</td><td><code>npm run e2e:parallel</code> / <code>npm run e2e:single-run</code> / <code>npm run e2e:list</code></td><td>2026-08-07 현재 수집은 <b>610 tests / 44 files</b>다. 현 작업트리의 610개 전체 GREEN은 아직 검증 전이며, 2026-07-15의 fixture desktop/mobile 166/166 + DB/engine/provider 직렬 49/49 = 215/215는 과거 기준선으로만 보존한다.</td></tr>
|
||||
<tr><td>Refactor governance P1~P8</td><td><code>ruff check app</code> / <code>pytest -q app</code> / <code>pytest -q engine_gateway</code> / <code>npm run typecheck</code> / <code>npm run check:api-types</code> / <code>npm run check:design-ssot</code> / <code>npm run check:dead-code</code> / <code>npm run check:duplication</code> / <code>npm run build</code> / <code>npm audit --audit-level=high</code> / full Playwright</td><td>Backend 400 passed, gateway 29 passed, web gates/build/audit passed, vulnerabilities 0, production duplication 1 clone/15 lines/0.03%, Playwright 215/215 passed. 상세 근거는 <code>ops/refactor-governance-2026-07-15.md</code>.</td></tr>
|
||||
<tr><td>API typegen SSOT</td><td><code>npm run check:api-types</code></td><td>Passed; FastAPI OpenAPI → <code>src/lib/api.gen.ts</code> stale check</td></tr>
|
||||
<tr><td>Outcome & Alliance OS G0</td><td><code>py -3.11 -X utf8 -m pytest -p no:cacheprovider apps/api/app/test_measurement_contract.py apps/api/app/test_runtime_schema_ssot.py -q</code> / <code>scripts/check-measurement-ledger.sql</code> / measurement·API contract checks / web typecheck / DB-backed <code>session-persistence</code> focused E2E 3종</td><td>G0 contract/schema 11 passed, 기존 backend 100 passed, auth 39 passed. Python→JSON Schema→TypeScript→PostgreSQL enum·필수필드 계약이 일치하고 8개 deterministic benchmark가 검증됐다. Live PostgreSQL에서 learner/client/evaluator 가시 행 1/1/2, 교차 누수 0, append-only guard 2를 확인했다. 학습자 턴→교수자 대시보드, 워크시트 검수, 종료 deep 평가→durable 리뷰 E2E는 각각 1 passed. G0/AOS-001~004 완료.</td></tr>
|
||||
<tr><td>Outcome & Alliance OS G1</td><td><code>pytest test_alliance_measurement.py test_alliance_calibration.py test_measurement_routes.py test_runtime_schema_ssot.py test_measurement_contract.py</code> / <code>scripts/smoke-alliance-pulse-api.py</code> / <code>alliance-pulse.spec.ts</code> / <code>session-review.spec.ts</code> / prompt 1.2 calibration compare</td><td>backend 40 passed, live DB에서 202→awaiting_agents→ready·공개 전 외부 관점 비노출·3관점×3축·교수자 재평정 supersession·attempt별 model-run provenance를 확인했다. desktop/mobile Alliance Pulse 6/6와 기존 회기 리뷰 12/12 통과. 합성 gold 방향 9/9, 최종 실패 0, 근거 recall 88.9%. G1/AOS-005~010 완료.</td></tr>
|
||||
<tr><td>Outcome & Alliance OS G2</td><td><code>pytest test_outcome_trajectory.py test_outcome_trajectory_schema.py test_outcome_trajectory_store.py</code> / <code>scripts/smoke-outcome-trajectory-api.py</code> / <code>scripts/smoke-outcome-trajectory-alerts.py</code> / <code>outcome-trajectory.spec.ts</code></td><td>focused 40 passed. 기존 실제 DB/API on-track 5회기·멱등/409·RLS에 더해 합성 교육 fixture 두 개가 production measurement producer→store→evaluator를 통과했다. 실제 10회기·30개 측정·case별 5 revision에서 악화는 S3 off_track·S4~S5 deteriorating, 단발성 noise는 S3 watch 뒤 회복해 alert 승격 0건이었다. 최신 observation 30개가 원본 측정 30개를 모두 참조하고 learner/teacher projection이 일치한다. 결과 JSON은 source score를 담지 않으며 임상 주장을 금지한다.</td></tr>
|
||||
<tr><td>Outcome & Alliance OS G3</td><td><code>pytest test_rupture_scenario_director.py test_rupture_runtime.py test_rupture_repair.py test_rupture_repair_store.py</code> / <code>scripts/smoke-rupture-repair-api.py</code> / <code>scripts/smoke-rupture-runtime-auto.py</code> / <code>rupture-repair.spec.ts</code> / G1~G3 review regression</td><td>benchmark macro-F1 1.0·상태 accuracy 1.0·critical miss/judge gaming/암기 false resolution 0. 최신 G3 focused 70 passed; 누적 Scenario/G3 107 passed, G0~G8 계약 251 passed, desktop/mobile review 50/50. 수동 write API는 durable UUID·인증·멱등·409·RLS·교수자 correction을 검증했고, 별도 자동 runtime smoke는 실제 텍스트 learner turn/finalizer callback으로 3 episode·10 observations·3 reconciliations, missed/partial/resolved, learner/teacher projection, evidence 참조 무결성을 PostgreSQL에서 검증했다.</td></tr>
|
||||
<tr><td>Outcome & Alliance OS G4/G5 caller</td><td><code>pytest test_session_learning_producer.py test_deliberate_practice.py test_deliberate_practice_store.py test_calibration_transfer.py test_calibration_transfer_store.py</code> / <code>scripts/smoke-session-learning-producer.py</code></td><td>focused 100 passed, 전체 API 855 passed. 실제 종료 회기·durable turn 2개·ready 평가에서 production caller가 G4 처방 원장 5종을 각 1개 생성하고 deterministic replay한다. G5는 prediction lock 전 observation 0, lock callback 뒤 failed 독립 관찰과 evaluator model-run 각 1개이며 replay 중복 0이다. learner/teacher projection이 일치하고 mastery/pass/assessment/transfer 자동 승격은 전부 0건이다. 합성 교육 fixture이며 임상·실제 숙달 주장이 아니다.</td></tr>
|
||||
<tr><td>학생 자기주도 전체 루프 UI</td><td><code>npx playwright test e2e/self-directed-learning-loop.spec.ts --project=chromium-desktop --project=chromium-mobile --workers=1 --reporter=dot</code> / <code>npx playwright test e2e/alliance-pulse.spec.ts --project=chromium-desktop --project=chromium-mobile --workers=2</code> / <code>npm run typecheck</code> / 고정 캡처 직접 QA</td><td>실제 <code>src</code> 홈 추천→목표 선택→텍스트/SSE 회기→종료·리뷰→G4 처방 키보드 CTA→새 회기 재연습 시작이 desktop/mobile 2/2, typecheck를 통과했다. 페이지 overflow 0, 홈 CTA 44px·4.5:1 이상 대비, typed launch intent와 replay 모드 보존, 내부 criterion/counterevidence/UUID 비노출을 확인했다. 390×844·320×568 활성 회기는 아바타/문구 겹침 0과 축어록/입력창 내부 포함을 통과했다. 320px 리뷰 1~5 척도는 다섯 선택지 44px 이상·내부/페이지 overflow 0이고 전체 desktop/mobile 8/8이다. 모든 미소유 API는 fixture 404이며 실제 로그인/API/DB 증거가 아니다.</td></tr>
|
||||
<tr><td>Contract SSOT aggregate DTO</td><td><code>py -3.11 -X utf8 -m pytest -p no:cacheprovider app/test_evaluation_persistence.py app/test_evaluator_model_routing.py app/test_teacher_dashboard.py app/test_rbac_idor.py app/test_session_turn_persistence.py -q</code> / <code>npm run check:api-types</code> / <code>npm run typecheck</code> / <code>npm run build</code></td><td>50 backend passed; learner sessions, session review/worksheet, teacher dashboard, session start/detail DTOs use generated <code>ApiSchema</code> aliases with UI fallback. Stage responses are OpenAPI enum unions, including review phase key/label, reached phase, evaluation summary/trigger responses, and teacher session/growth stage. <code>.github/workflows/api-contract.yml</code> runs <code>npm run check:api-types</code> on API/Web contract changes.</td></tr>
|
||||
<tr><td>SEO/GEO share cards</td><td><code>pytest app/test_session_share.py app/test_session_turn_persistence.py -q</code> / <code>npm run generate:api-types</code> / <code>npm run typecheck</code></td><td>21 passed; session share creates hashed-token public unfurl payload without raw transcript, revoked token returns 404, OpenAPI generated share DTOs, review screen share button typechecks. Static <code>robots.txt</code>/<code>sitemap.xml</code>/<code>llms.txt</code> added.</td></tr>
|
||||
<tr><td>Backend pytest baseline</td><td><code>pytest -q app</code></td><td>2026-07-15 전체 실행 400 passed. 외부 Starlette 의존성의 <code>python_multipart</code> PendingDeprecationWarning 1건만 존재한다.</td></tr>
|
||||
<tr><td>X2 evaluator routing/cache/cost trend</td><td><code>python -B -m pytest -p no:cacheprovider app/test_usage_report.py app/test_evaluator_model_routing.py app/test_runtime_policy.py app/test_admin_ops.py -q</code> / <code>scripts/report-ai-usage.py</code> sample CLI / prior web gates</td><td>42 backend passed + CLI sample OK; fast/deep evaluator model override, semantic cache hit/miss, admin usage budget/fail-closed, cache hit-rate observability, daily cost trend, and provider/model cost report schema covered. Prior API typecheck/build/admin E2E evidence remains valid because the API response shape was not changed.</td></tr>
|
||||
<tr><td>Backend pytest baseline</td><td><code>pytest -q app</code></td><td>2026-07-31 전체 실행 439 passed. 외부 Starlette 의존성의 <code>python_multipart</code> PendingDeprecationWarning 1건만 존재한다.</td></tr>
|
||||
<tr><td>X2 evaluator routing/cache/cost trend</td><td><code>python -X utf8 -m pytest -p no:cacheprovider app/test_llm_pricing.py app/test_admin_ops.py app/test_usage_report.py engine_gateway/test_provider_registry.py engine_gateway/test_gateway_model.py -q</code> / API type generation/check / web typecheck/build / admin focused E2E·layout visual gate / authenticated public API smoke</td><td>최신 backend 439 passed, gateway 45 passed, desktop/mobile E2E 2 passed, 7폭 focused visual gate 1 passed. Claude CLI SDK 비용 추정값과 Agy/Gemini·Codex·Claude API 공식 참조단가를 분리하고, 기존 0달러 행의 조회 시 보정·단가 미등록 모델의 <code>미산정</code> 표시·예산 합산·리포트 경고까지 고정했다. Claude 토큰은 전체 agent tree와 캐시 입력을 포함한다. 과거 0/0 Claude 442건은 로컬 JSONL의 실제 usage와 유일 일치한 169건만 백필했고, 273건은 <code>token_unmetered_turns</code>로 남겼다. 인증된 공개 관리자 화면은 확인 시점 30일 Claude 183건 중 계량 125·미계량 58건을 표시했다.</td></tr>
|
||||
<tr><td>H3 raw source isolation</td><td><code>python -B -m pytest -p no:cacheprovider app/test_persona_review.py app/test_live_coach_sources.py -q</code></td><td>38 passed; persona source registration records raw hash-only artifacts separately from sanitized evaluator-only RAG chunks, and <code>rag.index_document()</code> rejects <code>sensitivity=3</code>/raw-marker chunks before DB access.</td></tr>
|
||||
<tr><td>H3 persona itemized authoring UI</td><td><code>npm run typecheck</code> / <code>npm run build</code> / <code>npm run check:api-types</code> / <code>npx playwright test e2e/teacher.spec.ts --project=chromium-single-run --workers=1</code> / <code>npx playwright test e2e/layout-visual-gate.spec.ts --project=chromium-single-run --workers=1</code> / <code>npx playwright test e2e/session-layout.spec.ts --project=chromium-desktop --project=chromium-mobile --workers=1</code></td><td>Typecheck/build/API type drift check passed; teacher PersonaStudio focused 8 passed including structured list payload and labeled prompt preview without raw JSON; visual gate 9 passed with the prompt tab opened; session-layout desktop/mobile 8 passed.</td></tr>
|
||||
<tr><td>H2 session review 3-column workbench</td><td><code>npm run typecheck</code> / <code>npm run build</code> / <code>npx playwright test e2e/session-review.spec.ts --project=chromium-desktop --workers=1</code> / <code>npx playwright test e2e/layout-visual-gate.spec.ts --project=chromium-single-run --workers=1</code></td><td>Typecheck/build passed; 3 focused review E2E passed; 9 visual gates passed with dark-theme assertion. Filled learner review uses overview/flow, transcript, and evaluation rail as a 3-column desktop workbench with worksheet below; empty review remains 2 columns or less to avoid sparse gaps.</td></tr>
|
||||
|
|
@ -1005,7 +1085,7 @@
|
|||
<tr><td>OAuth/SAML admin landing</td><td><code>C:\Users\encep\AppData\Local\Programs\Python\Python311\python.exe -X utf8 -m pytest -p no:cacheprovider app/test_auth_providers.py -q</code> / public runtime restart</td><td>36 passed; OAuth/SAML callback now normalizes generic saved <code>next</code> paths such as <code>/learn</code> and <code>/teach</code> to <code>/admin</code> for admin-entitled users while preserving deep links such as <code>/learn/session/...</code>. Live proof after commits <code>998acb44</code>/<code>6b6241f4</code> and latest boot-diagnostic deploy <code>b67fd5d4</code>: local/public <code>/health</code> returned <code>prod</code>, <code>db=true</code>, <code>engine=true</code>; custom domain now serves <code>assets/index-R5KK7hZI.js</code>/<code>assets/index-DzdAkRsn.css</code> plus boot diagnostic HTML; unauthenticated <code>/personas</code> remains <code>401</code>.</td></tr>
|
||||
<tr><td>Session read-model DB readiness</td><td><code>python scripts\check-deploy-preflight.py --skip-db --env-file infra\.env.example --allow-placeholder-secrets</code> / <code>https://api-vignette.chanpaca.net/health</code></td><td>2026-06-29 prod 503 원인은 운영 DB의 <code>app.turns.provider_events</code> 컬럼 누락이었다. 운영 DB hotfix 후 <code>/teacher/dashboard</code> code path는 <code>source=database</code>로 복구됐다. 현재 <code>db.healthcheck()</code>, runtime table readiness, deploy preflight DB mode는 <code>app.turns</code> 음성 메타 컬럼 5개(<code>audio_ref</code>, <code>silence_ms</code>, <code>speech_rate</code>, <code>barge_in</code>, <code>provider_events</code>), <code>app.session_review_status</code> worksheet 컬럼, <code>app.safety_events</code> 필수 컬럼을 함께 검증한다. 2026-06-30에는 Docker Desktop/DB 중단으로 public API 530/error code 1033이 재발했지만, Docker Desktop/DB 재기동 뒤 public health가 <code>environment=prod</code>, <code>db=true</code>, <code>engine=true</code>로 복구됐다.</td></tr>
|
||||
<tr><td>Admin usage persistence</td><td><code>python -m pytest app/test_admin_ops.py app/test_runtime_policy.py -q</code> / authenticated local public-API smoke</td><td>26 passed; <code>/admin/usage</code> returns 200 with <code>source=database</code>, <code>durable=true</code>. Cloudflare blocked raw Python public smoke with 1010, so app-level HTTP was verified against <code>127.0.0.1:8001</code> using the same prod process.</td></tr>
|
||||
<tr><td>Admin AI operations subpage</td><td><code>npx playwright test e2e/admin.spec.ts --project=chromium-desktop --project=chromium-mobile --workers=1 --grep="admin route guards"</code> / <code>npx playwright test e2e/layout-visual-gate.spec.ts --project=chromium-single-run --workers=1</code> / <code>npx playwright test e2e/session-layout.spec.ts --project=chromium-desktop --project=chromium-mobile --workers=1</code></td><td><code>/admin/ai</code>를 관리자 좌측 메뉴의 독립 작업면으로 추가했다. 7·30·90일 <code>/admin/usage</code> 재조회, DB 원장/영속성, 비용·토큰·계량 커버리지·호출당 단가, 일별 비용, provider/model별 원장, evaluator cache 상세를 표시하고, 기존 <code>GET/PATCH /admin/engine-config</code>로 엔진 방식·연결 주소·기본 모델을 실제 저장한다. 예산은 서버 <code>ADMIN_USAGE_BUDGET_USD</code> 소유임을 그대로 노출한다. 관리자 route guard desktop/mobile 14 passed, 전체 레이아웃 시각 게이트 13 passed(신규 화면 7폭 캡처 포함), session-layout 8 passed.</td></tr>
|
||||
<tr><td>Admin AI operations subpage</td><td><code>npx playwright test e2e/admin.spec.ts --project=chromium-desktop --project=chromium-mobile --workers=1 --grep="shows detailed AI metering"</code> / <code>npx playwright test e2e/layout-visual-gate.spec.ts --project=chromium-single-run --workers=1</code> / <code>public-admin-visual.spec.ts --grep="shows the live Codex and Agy model catalogs"</code></td><td><code>/admin/ai</code>는 7·30·90일 DB 원장, 비용·토큰·토큰 계량 커버리지·호출당 단가, 일별 비용, provider/model breakdown, evaluator cache를 표시하고 엔진 설정을 실제 저장한다. 비용은 <code>SDK 추정</code>·<code>Provider 보고</code>·<code>참조단가</code>·<code>미산정</code>을 구분한다. 토큰 입력은 비캐시·캐시 읽기·캐시 생성 합계이며, 실제 usage가 복구된 행은 토큰과 <code>계량/전체 회</code> 커버리지를, 복구 불가 0/0 행은 <code>미계량</code>을 표시한다. 최신 focused desktop/mobile 2 passed, 7폭 layout 1 passed, 실제 공개 관리자 브라우저 1 passed다.</td></tr>
|
||||
<tr><td>Synthetic health sampler</td><td><code>record_admin_health_sample()</code>, <code>record-admin-health-sample.py</code>, <code>install-health-sampler-task.ps1</code></td><td>Backend focused 31 passed. Python compile/help passed; PowerShell parser + <code>-PrintOnly</code> passed. Local one-shot appended 5 service rows to <code>app.admin_health_event</code>: status ok, engine_mode claude_cli. This remains sample history, not an SLA claim.</td></tr>
|
||||
<tr><td>Health retention/rollup</td><td><code>admin_health_daily_rollup</code>, <code>maintain-admin-health-events.py</code>, <code>/admin/uptime</code></td><td>Backend focused 36 passed. Python compile/help passed. Local dry-run returned <code>rollup_event_count=0</code>, <code>prunable_event_count=0</code>. Uptime summary now combines raw samples with daily rollups when raw rows have been pruned; detailed events stay raw-only.</td></tr>
|
||||
<tr><td>Ticket duplicate linking</td><td><code>support_ticket.fingerprint</code>, <code>parent_ticket_id</code>, <code>/admin/tickets</code></td><td>Backend focused 38 passed. API types, web typecheck, and build passed. Admin tickets E2E 2 passed after restarting local API 8000 on the new code. Duplicate handling is deterministic hint + admin manual parent link/unlink only; no automatic merge, escalation, issue, PR, or thread creation.</td></tr>
|
||||
|
|
@ -1025,6 +1105,7 @@
|
|||
<tr><td>Crisis safety gate UI</td><td><code>PLAYWRIGHT_PORT=5193 npx playwright test e2e/session-mvp.spec.ts --project=chromium-single-run --workers=1</code> / <code>PLAYWRIGHT_PORT=5259 npx playwright test e2e/session-mvp.spec.ts --project=chromium-desktop --workers=1 --grep "voice conversation stop"</code></td><td>mock SSE <code>safety</code> + <code>done.conversation_stopped</code>와 mock voice <code>reply.conversation_stopped</code>에서 안전 자원 109와 입력 disabled가 유지되고, 빈 client reply를 <code>내담자 응답 없음</code>으로 덮지 않으며 live-coach를 호출하지 않는지 검증한다. 음성 위기 경로는 TTS 종료 이벤트 없이도 socket close를 확인한다.</td></tr>
|
||||
<tr><td>Persona/session review UI E2E</td><td><code>PLAYWRIGHT_PORT=5215 npx playwright test e2e/teacher.spec.ts --project=chromium-single-run --workers=1</code></td><td>9 passed mixed suite; real server-row rendering과 route-fixture UI 흐름을 함께 포함한다. 교수자가 pending persona를 승인하고, pending/recent review detail로 진입하며, 별도 student analysis 메뉴·학습자 검색·행 펼침·full learner timeline drilldown을 검증한다.</td></tr>
|
||||
<tr><td>Voice UI synthetic E2E</td><td><code>npx playwright test e2e/voice-success.spec.ts --project=chromium-single-run</code> / <code>PLAYWRIGHT_PORT=5248 npx playwright test e2e/session-mvp.spec.ts --project=chromium-single-run --workers=1 --grep "pending voice transcript"</code></td><td>voice-success 2 passed + pending transcript failure UI 1 passed; direct WS cascade, Session 텍스트 턴의 persisted reply <code>/voice/speech</code>→OpenAI speech→Web Audio 재생, Session mic button path with synthetic browser audio/Web Audio playback, and final transcript followed by persistence failure becoming a visible <code>저장 실패</code> turn instead of a silent successful transcript</td></tr>
|
||||
<tr><td>G7 explicit consent · external proof</td><td><code>pytest app/test_g7_voice_gain_evidence.py app/test_voice_runtime.py ...</code> / <code>pytest scripts/test_g7_external_proof.py scripts/test_g7_runtime_evidence.py scripts/test_g7_topology_evidence.py scripts/test_g7_external_voice_soak.py</code> / <code>voice-success.spec.ts</code></td><td>API/G7 92 passed, evidence runner/checker·release gate 31 passed, voice UI 2/2. 서버 30일·원음 미보존 consent ledger 성공 전 getUserMedia/voice WS 0회, public v4 interim/final latency, worker/Uvicorn·Linux topology high-water와 independent human-held-out gate를 fail-closed로 고정했다. exact SHA <code>a8c27b0a…1a0d</code> NAS 승격과 clean candidate session E2E 24/24도 통과했다. 실제 마이크·Deepgram·human evidence는 미실행이라 G7 BUILD.</td></tr>
|
||||
<tr><td>Voice s2s decision memo</td><td><code>docs/decisions/voice-s2s-poc.md</code></td><td>criteria recorded; keep/drop decision remains owner DECIDE</td></tr>
|
||||
<tr><td>Hanshin data/SSO gate</td><td><code>docs/ops/hanshin-data-governance-gate.md</code></td><td>artifact created; written external evidence still required</td></tr>
|
||||
<tr><td>Hanshin feedback 1차 improvement pack</td><td><code>docs/ops/hanshin-feedback-improvement-plan-2026-07-03.md</code> / <code>docs/redteam/hanshin-feedback-plan-redteam-2026-07-03.md</code> / <code>docs/redteam/hanshin-feedback-plan-counter-redteam-2026-07-03.md</code> / backend+web implementation</td><td>1차 implemented: P1 role mapping + client-only history injection, P2 quality retry/no-save gate + SSE full-response buffer, P3 review display humanization/raw error mapper/fast-deep labels/client feedback label, P4 supervisor scope panel. Verification: backend focused 84 passed, web typecheck and API type check passed, session-review focused 8 passed, layout visual gate 12 passed, session-layout 8 passed. Remaining follow-ups are stored fallback metadata, lower-latency partial stream policy, fast/deep reconciliation, resident namespace/prompt caching, and clinical rubric/CBT wording gates.</td></tr>
|
||||
|
|
@ -1040,6 +1121,10 @@
|
|||
<tr><td>Docker image smoke</td><td><code>docker build -f apps/api/Dockerfile .</code> / <code>docker run ... python -c "import app.main"</code> / <code>docker build -f apps/web/Dockerfile apps/web</code></td><td>API/Web image build and API import smoke pass after packaging cleanup. Web build uses npm lockfile and ignores host <code>node_modules</code>; API image excludes local <code>.env</code> files.</td></tr>
|
||||
<tr><td>Deploy preflight</td><td><code>python scripts\check-deploy-preflight.py --skip-db --env-file infra\.env.example --allow-placeholder-secrets</code> / DB mode with local <code>DATABASE_URL</code></td><td>Passed: exact-pinned API requirements, live coaching <code>data/kb</code> source pack, env template keys, and DB readiness (<code>current_user=vignette</code>). DB mode can additionally check app-role DSN with <code>--require-app-role</code> and now verifies session read-model columns including <code>app.turns</code> voice metadata columns(<code>audio_ref</code>/<code>silence_ms</code>/<code>speech_rate</code>/<code>barge_in</code>/<code>provider_events</code>) plus worksheet review columns.</td></tr>
|
||||
<tr><td>Fresh compose smoke</td><td><code>docker compose -p vignette-packaging-smoke -f infra/docker-compose.yml up -d --build</code> + proxy <code>/api/health</code></td><td>Passed with dummy production-safe env: API healthy, DB healthy, web/proxy up, <code>http://localhost:18080/api/health</code> 200 with <code>db:true</code>, <code>engine:true</code>, <code>engine_mode:"claude_cli"</code>. Smoke volumes/network removed after run.</td></tr>
|
||||
<tr><td>격리 NAS 프리뷰 · 회기 E2E 자동화</td><td><code>http://100.116.83.60:8088</code> / <code>vignette-e2e</code> 매일 04:30 KST / <a href="./ops/nas-preview-deployment-evidence-2026-08-07.md">배포 증거</a> / <a href="./ops/evidence/nas-preview-current-deploy-2026-08-07.json">기계 판독 증거</a></td><td>current SHA <code>6030a677…c611</code>을 전용 Compose 프로젝트·포트·네트워크·named volume에 승격했다. browser E2E 108/108, health 3/3, OpenAPI 126·auth 401·G0~G8, postdeploy SSE→DB review를 통과했고 predeploy dump와 이전 exact images를 보존했다. 자동화는 ACTIVE이며 material milestone+release gate+배포 SHA 변경 때만 프리뷰를 갱신한다. G7 외부 mic/provider/human 증거는 별도다.</td></tr>
|
||||
<tr><td>비-secure origin 회기 리뷰 크래시 수정</td><td><code>apps/web/src/lib/uuid.ts</code> · <code>apps/web/e2e/insecure-context-uuid.spec.ts</code></td><td>격리 NAS 프리뷰(평문 HTTP·비-localhost)에서 회기 리뷰 라우트가 <code>crypto.randomUUID is not a function</code>으로 error boundary에 잡혔다. 이 API는 secure context 전용인데 제품 코드 18곳이 fallback 없이 호출했고 <code>RuptureRepairCard</code>는 렌더 시점 호출이라 화면 전체가 죽었다. 릴리스 게이트는 localhost 후보 스택에서만 돌아 미검출. <code>randomUuid()</code>로 통일하고 fallback도 <code>getRandomValues</code> 우선으로 예측 불가능성을 유지했다. 회귀 <code>6/6</code>(직접 호출 0건 검사 포함), typecheck·build 통과. <b>NAS 프리뷰에는 아직 미배포</b>이며 배포된 SHA <code>6030a677…c611</code>은 여전히 결함 빌드다.</td></tr>
|
||||
<tr><td>공개 워치독 engine readiness 사각지대(해결)</td><td><code>scripts/watch-public-runtime.ps1</code> · <code>scripts/start-public-runtime.ps1</code> · <code>apps/api/engine_gateway/gateway.py</code></td><td>2026-08-07 22:0x KST 소유자 승인으로 9099를 재기동해 복구했다. 재기동 직후 <code>/ready?force=true</code> 200 <code>ok:true·detail:OK</code>, API 8001과 공개 <code>api-vignette.chanpaca.net</code> 모두 <code>status:ok·engine:true·engine_detail:OK</code>다. 원인은 16:06부터 상주한 9099 프로세스 하나였다(CLI·코드·cwd는 정상 — 동일 인자 직접 실행과 <code>EngineSession</code> 재현이 <code>"OK"</code>를 반환). 재발 방지 3건: (1) 워치독·기동 스크립트의 engine 판정을 <code>/health</code>(프로세스 liveness)에서 <code>/ready</code>(실제 생성)로 바꾸고 API 판정에 <code>engine</code>을 추가, shared secret 인스턴스용 토큰 헤더와 503 본문 로깅 포함. (2) 게이트웨이가 자식 <code>claude -p</code>의 stderr를 상시 드레인해(PIPE 미독 시 자식 블록도 차단) 실패 detail에 <code>exit</code>·stderr를 붙인다 — 실증: <code>empty engine response (error: unknown option '--vignette-nonexistent-flag-xyz')</code>. (3) 기동 시 게이트웨이 로그 회전, <code>Stop-UvicornByPort</code>에 <code>Name -like python*</code> 추가(호출자 자기 자신 종료 방지). 검증: <code>pytest engine_gateway</code> 58/58, 워치독 <code>-CheckOnly</code> healthy 5/5, 장애 재현(9299·<code>CLAUDE_BIN</code> 부재)에서 <code>/health</code>는 <code>ok:true</code>로 통과하고 <code>/ready</code>는 503으로 검출되어 워치독이 <code>unhealthy (1/3)</code> + 503 본문을 남겼다. <b>NAS용 9100(06:27 상주)은 실사용 세션 7개가 있어 미접촉</b>이라 게이트웨이 stderr 개선은 다음 재기동 때 반영된다.</td></tr>
|
||||
<tr><td>G8 실제 receipt-bound image rollback</td><td><code>scripts/launch-nas-preview-g8-helpers.py</code> / <code>scripts/serve-nas-preview-rollback-executor.py</code> / <code>scripts/probe-nas-preview-g8-rollback.py</code> / <a href="./ops/nas-preview-g8-rollback-proof-runbook.md">런북</a> / <a href="./ops/evidence/nas-preview-g8-actual-rollback-2026-08-07.json">기계 판독 증거</a></td><td>격리 NAS에서 별도 executor(18149)·control-plane(8018) helper로 실제 image rollback 2회를 실행했다. receipt <code>nas-g8-723eeef2…</code>로 previous <code>79ec…4450</code>/<code>c530…2f28</code>, receipt <code>nas-g8-2738846c…</code>로 current <code>52e0…8b2d</code>/<code>6fdb…f215</code>를 활성화했다. release gate·approval 각각 2회 멱등, <code>audit.ci_lifecycle_event</code> rollback/executed 2건과 receipt id 결속, silent auto-promotion 0, HMAC journal 6-record 체인 검증, health 3/3·OpenAPI 126·auth 401·Web 200, helper 0·listener 0 복귀. helper 격리 계약은 image 상속 label baseline 대비 검증이며 fake-runner 37/37로 고정했다. <code>down</code>/<code>volume rm</code>/<code>prune</code> 미실행, 공개 런타임 미접촉.</td></tr>
|
||||
<tr><td>Actual deployment env</td><td><code>infra/.env</code> owner-secret fill-in</td><td>Remaining external step: deployment target must provide real <code>APP_DB_PASSWORD</code>, OAuth client id/secret, <code>OPENAI_API_KEY</code>, <code>SESSION_SECRET</code>, and production-safe engine/voice flags. Current local stray values such as <code>ENGINE_MODE=claude_cli</code> and prod sample TTS must not be copied.</td></tr>
|
||||
<tr><td>Focused E2E</td><td><code>admin + db-persistence + voice-success</code></td><td>Latest admin spec: 8 passed on chromium desktop, including all-workspace admin navigation, health dashboard, real server-known users, operation tickets, mobile controls, and tablet form containment.</td></tr>
|
||||
<tr><td>Dev dashboard redteam</td><td><code>scripts/test_dev_dashboard_ssot.py</code> / <code>e2e/dev-dashboard.spec.ts</code></td><td>Unit: SSOT checker 4 tests OK + current dashboard check PASS. E2E: local file, <code>127.0.0.1</code>, <code>localhost</code> 외부형 origin, 필터 오순서/빠른 재시도/키보드, details/tabs 접기, hash anchor, 링크·이미지 무결성, desktop/mobile overflow를 검증해 chromium desktop/mobile 10 passed.</td></tr>
|
||||
|
|
@ -1073,7 +1158,7 @@
|
|||
<tr><td>Engine gateway probe (live)</td><td><code>scripts/probe-engine-gateway.py --base-url http://127.0.0.1:9099 --reuse-runs 2</code></td><td>claude_p / opus-4-8; 단일 session_id 2회 reused stream; TTFT 1667–4199ms; cost 누적 0.068→0.123(turns 1→2)</td></tr>
|
||||
<tr><td>Engine gateway RSS (live)</td><td><code>docs/ops/engine-rss-smoke-2026-06-28.md</code></td><td>gateway session 1개, <code>health.sessions=1</code>; gateway PID 51592 RSS 16.2MB; resident <code>claude -p</code> child PID 51688 RSS 363.3MB / Private 628.8MB after one turn; plugin bun children RSS 178.6MB.</td></tr>
|
||||
<tr><td>Postgres RLS/audit smoke (live)</td><td><code>VIGNETTE_RLS_AUDIT_DSN=…vignette_app… check-postgres-rls-audit.py --write-fixtures</code></td><td>5 checks PASS: preflight, learner 세션/턴 격리, teacher cohort 스코프, visible_to evaluator-only 숨김, teacher/admin read audit insert (NOBYPASSRLS app role, fixtures 자동 정리)</td></tr>
|
||||
<tr><td>Turn cost telemetry (live DB)</td><td><code>SELECT … FROM app.turns WHERE actor_kind='client_ai'</code></td><td>13 client_ai turns 적재: llm_provider=claude_cli, model=gateway-default, cost_usd 실값(합 $1.22, 평균 $0.094), visible_to 배열. 토큰은 CLI 게이트웨이 특성상 0, cost는 산출</td></tr>
|
||||
<tr><td>Turn cost/token telemetry (live DB)</td><td><code>SELECT … FROM app.turns WHERE actor_kind='client_ai'</code> / live gateway generate·SSE / <code>scripts/backfill-claude-token-usage.py</code></td><td>Claude CLI의 <code>total_cost_usd</code>는 청구 실비가 아닌 SDK 비용 추정값으로 저장한다. 신규 gateway generate·SSE는 <code>modelUsage</code> 전체 agent tree의 비캐시·cache read/create 입력과 출력을 전달한다. 수정 전 전체 0/0 Claude 442건 중 로컬 JSONL의 정규화 응답 SHA-256·시각이 유일하게 일치한 169건만 실제 입력 5,179,999·출력 46,910 토큰으로 복구했다. 불일치 273건은 비용 역산 없이 <code>token_unmetered_turns</code>로 남겼다.</td></tr>
|
||||
<tr><td>Cosmetic polish re-verify</td><td><code>5 polish agents → typecheck + gate + layout E2E re-run</code></td><td>typecheck OK; current gate 9 passed; layout focused E2E 54 passed; session-layout 8/8; regression 0</td></tr>
|
||||
<tr><td>Dashboard layout alignment</td><td><code>npm run typecheck</code> / <code>npm run build</code> / <code>layout-visual-gate</code> / <code>session-layout</code> / <code>session-review</code> / <code>learner+teacher+admin E2E</code></td><td>current rerun: typecheck/build passed; layout gate 9 passed; session-layout+session-review chromium-desktop 6 passed. 이전 정렬 라운드: session-layout desktop/mobile 8 passed; learner+teacher+admin chromium-desktop 17 passed. 대표 캡처는 <code>docs/archive/ops/layout-research-2026-06-28/final-gate-*.png</code>.</td></tr>
|
||||
<tr><td>Learner session archive</td><td><code>python -B -m pytest app/ -q</code> / <code>python -B -m pytest engine_gateway/ -q</code> / <code>npm run check:api-types</code> / <code>npm run typecheck</code> / <code>npm run build</code> / <code>npx playwright test e2e/learner.spec.ts --project=chromium-desktop --workers=1</code></td><td>Archive 당시 178 backend passed + 11 gateway passed; 최신 gateway contract gate는 27 passed. OpenAPI generated archive DTOs are current; learner history can archive/restore an ended session and keeps archived sessions out of the review queue while preserving records.</td></tr>
|
||||
|
|
@ -1091,7 +1176,7 @@ $env:E2E_PUBLIC_STORAGE_STATE=".\node_modules\.tmp\public-auth.json"
|
|||
powershell -NoProfile -ExecutionPolicy Bypass -File scripts\start-public-runtime.ps1</pre>
|
||||
</div>
|
||||
<div class="source-note"><b>닫은 항목(live 증거):</b> ① engine config 운영값(<code>claude_cli / 9099 / gateway-default</code>, durable, db). ② 상주 엔진풀 probe — 단일 session_id 2회 reused stream, TTFT 1667–4199ms, cost 누적 0.068→0.123. ③ 상주 엔진풀 RSS — gateway session 1개에서 <code>claude -p</code> child RSS 363.3MB, gateway RSS 16.2MB. ④ Postgres RLS/audit smoke 5 checks PASS. ⑤ turn cost telemetry — <code>app.turns</code> client_ai 13행에 provider/model/cost_usd 실적재(합 $1.22). ⑥ 레이아웃 시각 게이트 최신 9/9 + 2026-06-28 적대적 재검수 7/7 accept.</div>
|
||||
<div class="source-note"><b>환경 제약으로 이 세션에서 못 닫는 항목(정직 표기):</b> 공개 Google OAuth 실제 <code>/turn</code>(OAuth start는 2026-06-30 재확인 완료, 실제 계정 storageState 필요) · 실배포 <code>infra/.env</code> owner-secret fill-in(<code>APP_DB_PASSWORD</code>, <code>OAUTH_GOOGLE_CLIENT_ID</code>, <code>OAUTH_GOOGLE_CLIENT_SECRET</code>, <code>OPENAI_API_KEY</code>, <code>SESSION_SECRET</code>) · <code>vnet.18ka.net</code>/<code>api-vnet.18ka.net</code> 공개 런타임(DNS A record 0, Cloudflare 18ka.net zone 권한 불일치, Google OAuth redirect URI 등록 필요) · 한신대 데이터/SSO 거버넌스(owner/기관 written evidence 필요) · 음성 실제 Deepgram interim/final WSS·물리 마이크·공개 WSS·50분 양방향 장시간 실측 · claude_cli↔Messages API 폴백 동일성 · 실제 Windows 재부팅 후 watchdog smoke · Phase 3 파일럿 게이트. owner 결정 7건은 2026-06-30 전건 확정됨. 운영 원칙상 가짜 증거로 DONE 표기하지 않는다. 전체 추적: <code>docs/ops/backlog-2026-06-26.md</code>(B1 구현 후속 · B2 환경제약 · B3 소유자결정 · B4 거버넌스).</div>
|
||||
<div class="source-note"><b>환경 제약으로 이 세션에서 못 닫는 항목(정직 표기):</b> 공개 Google OAuth 실제 <code>/turn</code>(OAuth start는 2026-06-30 재확인 완료, 실제 계정 storageState 필요) · 실배포 <code>infra/.env</code> owner-secret fill-in(<code>APP_DB_PASSWORD</code>, <code>OAUTH_GOOGLE_CLIENT_ID</code>, <code>OAUTH_GOOGLE_CLIENT_SECRET</code>, <code>OPENAI_API_KEY</code>, <code>SESSION_SECRET</code>) · <code>vnet.18ka.net</code>/<code>api-vnet.18ka.net</code> 공개 런타임(DNS A record 0, Cloudflare 18ka.net zone 권한 불일치, Google OAuth redirect URI 등록 필요) · 한신대 데이터/SSO 거버넌스(owner/기관 written evidence 필요) · G7 실제 Deepgram 운영 key·quota live interim/final, 기대 provider/model authenticated public WSS ready, 명시 동의 물리 마이크와 50분 양방향 soak · claude_cli↔Messages API 폴백 동일성 · 실제 Windows 재부팅 후 watchdog smoke · Phase 3 파일럿 게이트. owner 결정 7건은 2026-06-30 전건 확정됨. 운영 원칙상 가짜 증거로 DONE 표기하지 않는다. 전체 추적: <code>docs/ops/backlog-2026-06-26.md</code>(B1 구현 후속 · B2 환경제약 · B3 소유자결정 · B4 거버넌스).</div>
|
||||
</div>
|
||||
<div class="tab-panel" id="panel-decisions" role="tabpanel" aria-labelledby="tab-decisions" hidden>
|
||||
<div class="detail-head"><div><h3>윤찬 결정 상세 (전건 확정)</h3><p>람다가 단독으로 정하면 월권인 항목이었다. 2026-06-30 owner 결정으로 전건 확정됐고, 각 결정의 구현·실측은 후속 build/GATE로 남는다.</p></div><div class="mini-metrics"><span>결정 0개(전건 확정)</span><span>조건부 GO 사유</span></div></div>
|
||||
|
|
@ -1238,7 +1323,8 @@ powershell -NoProfile -ExecutionPolicy Bypass -File scripts\start-public-runtime
|
|||
{id:"P1",name:"MVP 코어루프",pct:78,cls:""},
|
||||
{id:"P2a",name:"권한·페르소나",pct:74,cls:""},
|
||||
{id:"P2b",name:"음성 캐스케이드",pct:40,cls:""},
|
||||
{id:"P3",name:"파일럿·효과",pct:6,cls:"idle"}
|
||||
{id:"P3",name:"파일럿·효과",pct:6,cls:"idle"},
|
||||
{id:"AOS",name:"성과·동맹 OS",pct:22,cls:"cur"}
|
||||
];
|
||||
var rail=$("phase-rail");
|
||||
if(rail){
|
||||
|
|
|
|||
|
|
@ -15,12 +15,12 @@ Vignette 저장소의 모든 검증 수단(백엔드 단위 테스트, 웹 타
|
|||
|
||||
| 검증 | 작업 디렉터리 | 명령 | DB | API(8000) | 웹(5173) | 엔진GW(9099) | 브라우저 | 현재 통과 |
|
||||
|---|---|---|---|---|---|---|---|---|
|
||||
| 백엔드 단위 테스트 | `apps/api` | `python -m pytest app/ -q` | 불필요 | 불필요 | 불필요 | 불필요 | 불필요 | 2026-07-30 전체 실행 432 passed |
|
||||
| 엔진 게이트웨이 테스트 | `apps/api` | `python -m pytest engine_gateway/ -q` | 불필요 | 불필요 | 불필요 | 불필요 | 불필요 | 2026-07-30 전체 실행 44 passed |
|
||||
| 백엔드 단위 테스트 | `apps/api` | `python -m pytest app/ -q` | 불필요 | 불필요 | 불필요 | 불필요 | 불필요 | 2026-07-31 전체 실행 439 passed |
|
||||
| 엔진 게이트웨이 테스트 | `apps/api` | `python -m pytest engine_gateway/ -q` | 불필요 | 불필요 | 불필요 | 불필요 | 불필요 | 2026-07-31 전체 실행 45 passed |
|
||||
| API 타입 생성 체크 | `apps/web` | `npm run check:api-types` | 불필요 | 불필요 | 불필요 | 불필요 | 불필요 | pass |
|
||||
| 웹 타입체크 | `apps/web` | `npm run typecheck` | 불필요 | 불필요 | 불필요 | 불필요 | 불필요 | pass |
|
||||
| 웹 빌드 | `apps/web` | `npm run build` | 불필요 | 불필요 | 불필요 | 불필요 | 불필요 | pass |
|
||||
| Playwright E2E(전체) | `apps/web` | `npm run e2e` | **필요(+시드)** | **필요** | 자동기동 | 일부만 | **필요** | 215 tests / 20 files |
|
||||
| Playwright E2E(전체) | `apps/web` | `npm run e2e` | **필요(+시드)** | **필요** | 자동기동 | 일부만 | **필요** | 현재 수집 610 tests / 44 files · 현 작업트리 전체 GREEN 미검증 |
|
||||
|
||||
핵심 원칙: **단위 테스트(pytest)와 타입체크/빌드는 외부 서비스 없이 단독 실행된다.**
|
||||
**E2E만 풀스택(DB+API+웹+브라우저)을 요구한다.** 아래 각 절에서 근거와 절차를 설명한다.
|
||||
|
|
@ -42,15 +42,15 @@ Vignette 저장소의 모든 검증 수단(백엔드 단위 테스트, 웹 타
|
|||
|
||||
```sh
|
||||
# apps/api
|
||||
python -m pytest app/ -q # 앱 단위 테스트: 2026-07-30 전체 실행 432 passed
|
||||
python -m pytest engine_gateway/ -q # 게이트웨이 단위 테스트: 2026-07-30 전체 실행 44 passed
|
||||
python -m pytest app/ -q # 앱 단위 테스트: 2026-07-31 전체 실행 439 passed
|
||||
python -m pytest engine_gateway/ -q # 게이트웨이 단위 테스트: 2026-07-31 전체 실행 45 passed
|
||||
```
|
||||
|
||||
수집만 빠르게 확인하려면:
|
||||
|
||||
```sh
|
||||
python -m pytest app/ --collect-only -q # 현재: "432 tests collected" (2026-07-30)
|
||||
python -m pytest engine_gateway/ --collect-only -q # 현재: "44 tests collected" (2026-07-30)
|
||||
python -m pytest app/ --collect-only -q # 현재: "436 tests collected" (2026-07-31)
|
||||
python -m pytest engine_gateway/ --collect-only -q # 현재: "45 tests collected" (2026-07-31)
|
||||
```
|
||||
|
||||
> 참고: 실행 중 `PendingDeprecationWarning: Please use 'import python_multipart'`
|
||||
|
|
@ -271,10 +271,160 @@ VITE_API_BASE=http://127.0.0.1:8000 npm run e2e # 프록시 대신 API
|
|||
|
||||
### 3.6 실측 테스트 개수 (현재)
|
||||
|
||||
`npx playwright test --list` 기준 **총 215 tests / 20 files**.
|
||||
2026-08-07 `npx playwright test --list` 기준 **현재 수집 610 tests / 44 files**다.
|
||||
이 숫자는 수집량이지 통과량이 아니다. 현 작업트리 전체 604개 완주는 아직 증거가 없으며,
|
||||
과거 전체 GREEN 기록과 이번 focused/release gate 결과를 구분해 적는다.
|
||||
|
||||
- **병렬 시나리오**: 166 tests (desktop 83 + mobile 83)
|
||||
- **`@single-run` 직렬 시나리오**: 49 tests (DB 영속화·세션 MVP·음성 성공경로·인증 시각 테마·회기말 평가 저장·교수자 명시 재평가 저장·교수자 턴 재평가 저장·교수자 UI 평가 재시도·교수자 사용자별 분석·source pack sync·이론모드 저장·동의 철회 후 voice 차단·브라우저 stream PII 마스킹·음성 transcript 저장 실패 UI 표면화 등)
|
||||
- 2026-08-06 Outcome & Alliance OS G0 검증: `test_measurement_contract.py` + runtime schema SSOT **11 passed**,
|
||||
기존 세션·평가·메모리·RBAC backend **100 passed**, auth cohort 계약 **39 passed**, Python 생성 계약
|
||||
`--check`와 TypeScript measurement/API 계약·typecheck 통과. `scripts/check-measurement-ledger.sql`을 실제
|
||||
PostgreSQL owner 연결에서 실행해 learner/client/evaluator 가시 행이 각각 1/1/2, 교차 view 누수 0,
|
||||
append-only UPDATE/DELETE guard 2건임을 확인했다. DB-backed `session-persistence`의 학습자 턴→교수자
|
||||
대시보드, 워크시트 저장→수정요청 검수, 종료 deep 평가→durable 교수자 리뷰 focused E2E는 각각
|
||||
**1/1 passed**. 명시 cohort가 없는 새 DB에서도 이 증거가 재현되도록 dev-login E2E 요청만 공통
|
||||
`e2e-hanshin` cohort를 고정하며 생산 OAuth/SAML·관리 사용자 cohort는 우선 보존한다.
|
||||
- 2026-08-06 Outcome & Alliance OS G1 검증: alliance service/calibration/routes/runtime/contract focused
|
||||
backend **40 passed**. `scripts/smoke-alliance-pulse-api.py`는 실제 DB/API/엔진에서 `202 → awaiting_agents →
|
||||
ready`, 공개 전 외부 관점 비노출, terminal 3관점×3축 9개, 동일 cohort 교수자 조회, 교수자 재평정 2회의
|
||||
append-only supersession, 시도별 model-run provenance를 단언한다. `e2e/alliance-pulse.spec.ts`는 데스크톱·
|
||||
모바일 **6/6**, 기존 `session-review.spec.ts`는 같은 두 프로젝트 **12/12**를 통과했다. prompt `1.2.0`
|
||||
합성 gold 비교는 방향 정확도 **9/9**, 최종 실패 **0**, 근거 recall **88.9%**이며 첫 시도 실패 2건의
|
||||
오류·재시도 provenance도 보존했다. 비교 산출물은
|
||||
`ops/alliance-calibration-report-prompt-1.2.0-2026-08-06.md`다.
|
||||
- 2026-08-07 G1 실제 회기 checkpoint 보강: 최신 source API와 dev PostgreSQL에서 첫 발화 전 pre는
|
||||
`degraded/insufficient_transcript`로 fail-closed, 첫 실제 왕복의 durable turn UUID 2개를 쓰는 mid와
|
||||
DB 종료 뒤 post는 `ready`로 통과했다. 같은 응답 안의 `awaiting_agents`/external-event read skew를
|
||||
차단하고 degraded terminal/audit DB 제약을 교정했다. post는 최초 202, 동일 payload 안정 replay,
|
||||
변경 payload 409, RLS/cohort와 교수자 3축 supersession을 재확인했다. focused G1 **43 passed**와 Ruff 통과,
|
||||
기계 판독 결과는 `ops/evidence/alliance-pulse-pre-mid-post-live-2026-08-07.json`이다.
|
||||
- 2026-08-06 Outcome & Alliance OS G2 검증: contract/store/schema와 G3 통합 focused backend **90 passed**,
|
||||
durable review UUID 회귀 **37 passed**. `scripts/smoke-outcome-trajectory-api.py`는 실제 DB/API/엔진에서 한
|
||||
case의 S1~S5 연속성, 15개 독립 축, total score 부재, 동일 제출 measurement ID 멱등성, 변경 payload 409,
|
||||
같은 cohort 교수자 조회, 다른 학습자·교차 cohort 차단, 역할별 관계 메모리, 합성·비임상 고지를 단언했다.
|
||||
`e2e/outcome-trajectory.spec.ts` desktop/mobile은 체크인 포함 **12/12**, G1 Alliance Pulse와 기존 Session
|
||||
Review 회귀는 **15/15**를 통과했다. 상세 metadata는
|
||||
`ops/outcome-trajectory-live-integration-evidence-2026-08-06.md`가 소유한다.
|
||||
- 2026-08-07 G2 악화/false-alert 실 DB 보강: `scripts/smoke-outcome-trajectory-alerts.py`는 합성 교육용
|
||||
두 case의 실제 10회기와 production observation producer가 쓴 30개 `measurement_event`를 evaluator가
|
||||
소비하는지 확인한다. 악화 case는 S3 `off_track`·S4~S5 `deteriorating`, 단발성 noise control은
|
||||
S3 `watch` 뒤 S4~S5 `on_track`이며 alert 승격 0건이다. case별 revision 5개·supersession 4개와 최신
|
||||
observation 30개→measurement 30개 provenance, learner/teacher projection 일치, 합성·비임상 표시를
|
||||
단언하고 결과 JSON에는 source score를 기록하지 않는다. G2 focused **40 passed**, Ruff·`py_compile`·실 DB
|
||||
smoke 통과. 동시 G8 변경 수렴 뒤 전체 API도 **855 passed**를 통과했다.
|
||||
- 2026-08-06 Outcome & Alliance OS G3 검증: 11-case/9-type 합성 benchmark의 rupture type macro-F1
|
||||
**1.0**, repair status accuracy **1.0**, critical miss·judge gaming·memorized phrase false resolution **0**.
|
||||
Scenario Director와 runtime/store/session focused **107 passed**, G0~G8 계약·store·runtime 묶음 **251 passed**.
|
||||
실제 DB/API 회기 smoke는 durable turn UUID 2개, 독립 내부 토큰 write, 동일 payload 멱등 ID, 변경 payload
|
||||
409, learner/cohort RLS, 교수자 append-only correction과 최종 resolved를 확인했다. G1~G3 및 기존 리뷰
|
||||
desktop/mobile 회귀는 **50/50 passed**. 상세는
|
||||
`ops/rupture-repair-live-integration-evidence-2026-08-06.md`가 소유한다.
|
||||
- 2026-08-07 G3 자동 runtime 보강: `scripts/smoke-rupture-runtime-auto.py`는 외부 client/evaluator만
|
||||
결정론 seam으로 교체하고 실제 `sessions.submit_turn`·공용 `finalize_completed_turn` hook을 호출한다.
|
||||
rupture 저장 endpoint를 호출하지 않은 채 로컬 PostgreSQL에 missed/partial/resolved 3 episode,
|
||||
observation **10개**, reconciliation **3개**, structured learner evaluation **5개**, 세션 밖 evidence 참조
|
||||
**0건**, 세션별 fast/deep model-run **2개**를 단언하며 learner/teacher read projection도 검증한다.
|
||||
focused G3 runtime/store/core/scenario는 **70 passed**다. 재현 JSON은
|
||||
`ops/evidence/rupture-runtime-auto-live-2026-08-07.json`이다. 이 실 DB 실행은 텍스트 learner-turn
|
||||
transport를 증명하며 SSE·음성 개별 transport live smoke까지 증명했다고 확장하지 않는다.
|
||||
- 2026-08-06 Outcome & Alliance OS G4 검증: deliberate-practice core/store/API focused **54 passed**,
|
||||
5개 연습 모드와 약점·망각 우선 선택, 익숙한 성공/자기 성공 주장만으로 mastery를 열지 않는 전이
|
||||
게이트를 고정했다. 실제 DB/API 회기 smoke는 durable turn UUID 2개를 prescription/attempt evidence로
|
||||
사용하고 내부 토큰 처방 write, 동일 제출 안정 ID, 변경 payload 409, learner/cohort RLS, 교수자 append-only
|
||||
correction, 총점·XP 없음, `clinical_claim_allowed=false`를 확인했다. G4 desktop/mobile은 **8/8**,
|
||||
G1~G4와 기존 회기 리뷰 통합 회귀는 **58/58 passed**. 상세는
|
||||
`ops/deliberate-practice-live-integration-evidence-2026-08-06.md`가 소유한다.
|
||||
- 2026-08-06 Outcome & Alliance OS G5 검증: calibration-transfer core/store/API focused **41 passed**,
|
||||
4-case benchmark expectation accuracy **1.0**, 잠금 뒤 오염 거부 **1**, 반복 보정 MAE **0.667→0.267**,
|
||||
unseen varied transfer 4/4, 암기 문구 false verification **0**, 합성 subgroup drift gap **0.667**을 확인했다.
|
||||
실제 DB/API smoke는 durable turn prediction revision→불가역 lock→독립 runtime observation reveal,
|
||||
동일 제출 안정 ID·변경 payload 409, post-lock revision 422, learner/supervisor projection과 교차 역할/cohort
|
||||
차단을 검증했다. G5 learner/teacher desktop/mobile **4/4**, G1~G5+기존 리뷰 **62/62**, typecheck·build
|
||||
통과. 상세는 `ops/calibration-transfer-live-integration-evidence-2026-08-06.md`가 소유한다.
|
||||
- 2026-08-07 G4/G5 production caller 실 DB 검증: `scripts/smoke-session-learning-producer.py`는 실제 종료
|
||||
회기·durable turn UUID 2개·ready session evaluation을 만들고 `session_learning_producer`만으로 G4/G5
|
||||
파생 원장을 생성한다. 잠금 전 G4 submission/card/prescription/snapshot/decision 각 1개와 G5 observation
|
||||
0개, prediction lock callback 뒤 failed 독립 observation·turn/session provenance model-run 각 1개를
|
||||
확인했다. 재실행은 G4 deterministic submission/card replay이며 G5·model-run 중복 0이다. episode/attempt,
|
||||
mastery/pass, calibration assessment, transfer suite/trial/assessment는 모두 0이다. learner/teacher projection
|
||||
일치와 `clinical_claim_allowed=false`도 단언한다. focused **100 passed**, 전체 API **855 passed**, Ruff·
|
||||
`py_compile`·실 PostgreSQL smoke 통과. 상세는
|
||||
`ops/outcome-os-g4-g5-production-caller-evidence-2026-08-07.md`가 소유한다.
|
||||
- 2026-08-07 학생 자기주도 전체 루프 UI 검증: `self-directed-learning-loop.spec.ts`는 실제 `src` 화면에서
|
||||
학습 홈 추천→목표 선택→텍스트/SSE 회기→종료·리뷰→G4 처방 CTA→새 회기 사전 설정·시작을 하나의
|
||||
상태 흐름으로 묶고 desktop/mobile **2/2**를 통과했다. 미소유 `/api/**`는 전부 fixture 404이고
|
||||
`auth/me`·세션·리뷰·practice read model도 route fixture이므로 실제 로그인/API/DB 증거가 아니다.
|
||||
390×844·320×568 활성 회기를 포함한 9장 시각 QA, 내부 criterion/counterevidence/UUID 비노출,
|
||||
홈 CTA 44px·4.5:1 대비와 source API/DB 증거의 분리 기준은
|
||||
`ops/outcome-os-self-directed-learning-loop-ui-evidence-2026-08-07.md`가 소유한다.
|
||||
- 2026-08-06 Outcome & Alliance OS G6 검증: supervision/research core/store/API/runtime-schema focused
|
||||
**28 passed**, 6-signal attention queue 순서 정확, item별 drilldown 1/상한 3, metadata-only calibration
|
||||
dataset raw transcript row 0, version drift `drift_flagged`, Phase 3 네 도메인 manifest를 고정했다. 실제
|
||||
DB/API smoke는 두 개의 durable practice attempt UUID를 원장 pointer로 사용해 내부 토큰, 동일 제출 안정 ID,
|
||||
변경 payload 409, supervisor/research AI-view 분리, teacher/learner/cross-cohort RLS, teacher audit를 검증했다.
|
||||
role-safe research read model은 model/prompt/instrument version, subgroup metrics, artifact provenance를 hydrate한다.
|
||||
G6 desktop/mobile **6/6**, typecheck·build 통과. 상세는
|
||||
`ops/supervision-research-live-integration-evidence-2026-08-06.md`가 소유한다.
|
||||
- 2026-08-07 G6 scheduled producer 보강: G0~G5 원장 기반 파생과 repo-approved synthetic version evaluator
|
||||
focused **37 passed**. 실제 dev DB에서 6 cohort cycle 실패 0, measurement anchor 12개, baseline/candidate
|
||||
batch 2·drift report 1·subgroup 2, replay 중복 0, evaluator/prompt/instrument provenance hydrate를 확인했다.
|
||||
원문 transcript와 임상 주장은 0이다. 상세는 `ops/outcome-os-g6-ledger-producer-evidence-2026-08-07.md`와
|
||||
`ops/outcome-os-g6-version-comparison-producer-evidence-2026-08-07.md`가 소유한다.
|
||||
- 2026-08-06→07 Outcome & Alliance OS G7 검증: 동의→단일 오디오 시계→독립 text/voice/fusion→철회·
|
||||
tombstone의 실제 HTTP/DB 경계와 역할 안전 UI를 통과했다. 공개 배포 뒤 G7 API route, TLS 1.3 WSS
|
||||
handshake와 비인증 1008 차단을 확인했다. Deepgram streaming adapter/interim/final/word timestamp,
|
||||
HMAC word pseudonym, 1초 동의 재검사, 음성 recovery UX와 `ready` provider/model 계약은 code/internal 완료다.
|
||||
public preflight는 마이크 없이 authenticated ready/ping과 기대 STT/TTS provider/model 네 값만 검사한다.
|
||||
실제 Deepgram key·quota live, 명시 동의 물리 마이크, authenticated public ready, 실행된 50분 soak와 운영 TTS
|
||||
상업 이용권 증거는 외부 게이트이며 synthetic/preflight 증거로 대체하지 않는다.
|
||||
- 2026-08-06→07 Outcome & Alliance OS G8 검증: source→독립 red-team→benchmark→human gate,
|
||||
baseline/threshold/provenance/rollback 4종 증거, lifecycle과 incident DAG를 실제 API/DB에서 통과했다.
|
||||
clean release backend **362 passed**, 회기·Outcome desktop/mobile **126 passed / 2 skipped**, layout visual
|
||||
gate **15/15**, typecheck·API contract·build를 통과한 뒤 API 8001과 Cloudflare Pages production에 배포했다.
|
||||
공개 OpenAPI **119 paths**에서 G1~G8 route, prod/db/engine health, auth 401, Google OAuth 302+state cookie,
|
||||
custom-domain 새 asset과 브라우저 로그인 렌더를 확인했다. 상세는
|
||||
`ops/outcome-os-public-deployment-evidence-2026-08-07.md`가 소유한다.
|
||||
- 2026-08-07 Outcome & Alliance OS 배포 프리플라이트: Compose profile과 별도로 `direct-runtime` profile을
|
||||
추가했다. `scripts/provision-outcome-os-runtime-secrets.py`는 G3~G8 토큰 6개만 원자적으로 생성·회전하고
|
||||
값을 출력하지 않는다. 운영 `apps/api/.env`에서 필수키와 `DATABASE_URL`, `current_user=vignette` non-owner
|
||||
app-role 검사를 통과했다. 상세는 `ops/outcome-os-deploy-preflight-evidence-2026-08-07.md`가 소유한다.
|
||||
- 2026-08-07 G7 로컬 `/voice/ws` short soak: loopback Uvicorn의 실제 FastAPI WebSocket transport에서
|
||||
8.015초·83/83 연결, 성공 발화 79, 정상/인증 거부/provider degraded close, 같은 socket 철회와 재연결
|
||||
차단, 4KiB 실행 한정 overflow 뒤 복구를 확인했다. 관련 42 tests와 API/gateway 전체 **805 passed**.
|
||||
외부 provider·실DB·물리 마이크·public WSS는 사용하지 않았고 50분 옵션은 제공만 했으므로 B2는 유지한다.
|
||||
상세는 `ops/outcome-os-g7-wss-soak-evidence-2026-08-07.md`가 소유한다.
|
||||
- 2026-08-07 G7 후속 hardening: voice service/WS/G7 store·API focused **73 passed**, public runner **12 passed**.
|
||||
`scripts/soak-public-voice-websocket.py --preflight-only`는 auth cookie·소유 회기와 아래 네 기대값을 필수로 받고
|
||||
WSS ready/ping까지만 수행한다. 마이크 장치 열거·캡처가 없으므로 물리 음성 증거로 판정하지 않는다.
|
||||
|
||||
```powershell
|
||||
C:\Users\encep\AppData\Local\Programs\Python\Python311\python.exe -X utf8 `
|
||||
scripts\soak-public-voice-websocket.py --preflight-only `
|
||||
--expected-stt-provider deepgram --expected-stt-model nova-3 `
|
||||
--expected-tts-provider <승인된-provider> --expected-tts-model <승인된-model>
|
||||
```
|
||||
|
||||
50분 full soak는 같은 기대값에 `--duration-seconds 3000`, `--microphone-device <명시-장치>`,
|
||||
`--confirm-physical-capture`를 추가하고 사용자가 실행 직전에 물리 캡처에 명시 동의한 경우에만 실행한다.
|
||||
- 2026-08-07 G7 external-proof readiness: Session 첫 음성 사용은 서버 30일·원음 미보존 consent ledger가
|
||||
성공하기 전 `getUserMedia`와 voice WebSocket을 시작하지 않는다. controlled provider와 synthetic stream을
|
||||
사용한 `voice-success.spec.ts` single-run **2/2**에서 원장 지연 중 둘 다 0회, 성공 뒤 각각 정확히 1회,
|
||||
지연 권한 뒤 pause 시 track stop·WS 0을 확인했다. API voice/G7 focused **92 passed**, external evidence
|
||||
runner/checker와 release gate contract **31 passed**, typecheck·API type
|
||||
contract·Ruff·py_compile을 통과했다. `soak-public-voice-websocket.py` v4, `/admin/voice-runtime`,
|
||||
`capture-g7-runtime-evidence.py`, `capture-g7-topology-evidence.py`, independent human-held-out evaluator를
|
||||
`check-g7-external-proof.py`가 같은 public host·겹치는 50분 시간창으로 묶는다. 실제 마이크·운영 Deepgram·
|
||||
실제 참가자 evidence는 없으므로 G7은 BUILD다. 상세는
|
||||
`ops/outcome-os-g7-external-proof-readiness-2026-08-07.md`가 소유한다.
|
||||
- 2026-08-07 G7/G8 시각 QA: 데스크톱·Pixel 5에서 focused **16/16**, typecheck·build·cosmetic-filter를
|
||||
통과했다. G8 승인 차단 이유를 `title` 의존에서 상시 문구·`aria-describedby`·semantic form/Enter 제출로
|
||||
바꾸고, G7 이벤트 최소 24×24px와 시간축 키보드 포커스를 수치 회귀로 고정했다. 상세는
|
||||
`ops/outcome-os-g7-g8-visual-qa-2026-08-07.md`가 소유한다.
|
||||
- 2026-08-07 G0~G8 release-only assembly: dirty **257파일**을 related 235/mixed 4/excluded 18로 전건
|
||||
분류했다. `api.gen.ts`는 clean HEAD + related API 소스에서 공식 재생성하고 Windows checkout에서도 선언
|
||||
해시를 다시 확인했다. 최초 공개 승격 patch는 229 files, SHA-256 `d9ac3c85...34d41`로 두 번 동일하게
|
||||
조립돼 clean HEAD/canonical index 적용 검사를 통과했다. 최종 SSOT 반영 뒤 다시 결정적 조립·검사한다.
|
||||
- 2026-07-15 전체 검증: `npm run e2e:parallel` **166/166 passed**(2.5분), `npm run e2e:single-run` **49/49 passed**(23.2분). 합계 **215/215 passed**. AI 튜터 DB 영속화는 정상 provider 응답이 2분을 넘을 수 있어 해당 테스트만 같은 실엔진 묶음의 장시간 기준인 240초를 사용한다.
|
||||
- `e2e/voice-success.spec.ts`는 직접 `/voice/ws` 캐스케이드, Session 텍스트 턴의
|
||||
`POST /voice/speech`, Session 마이크 UI를 함께 검증한다. 브라우저 `<audio>.play()`가 차단된 조건에서도
|
||||
|
|
@ -322,6 +472,9 @@ VITE_API_BASE=http://127.0.0.1:8000 npm run e2e # 프록시 대신 API
|
|||
- 2026-07-01 focused 검증: `npx playwright test e2e/admin.spec.ts --project=chromium-desktop --project=chromium-mobile --workers=1 --grep "approved admin without onboarding"` **2 passed**. 승인된 `role=admin` 사용자의 세션에 `admin_access=false`, `onboarding_completed_at=null`이 남아도 `/admin`이 `/onboarding`으로 우회하지 않고 관리 콘솔의 `/admin/*` API를 호출하는지 fixture로 고정한다.
|
||||
- 2026-07-15 보태니컬 글래스 UI·SSOT 검증: `npm run check:design-ssot` + `npm run typecheck` + `npm run build` 통과, `npx playwright test e2e/layout-visual-gate.spec.ts --project=chromium-single-run --reporter=line` 시각 게이트 **15/15**, `npx playwright test e2e/auth-visual.spec.ts --project=chromium-single-run --reporter=line` **1/1**, `npx playwright test e2e/session-layout.spec.ts e2e/learner.spec.ts e2e/session-review.spec.ts --project=chromium-desktop --project=chromium-mobile` **46/46**, 로그인→온보딩 focused desktop/mobile **2/2**. 공통 AppShell GNB, Theme store, Surface variant의 소유권과 전 폭 대시보드/리뷰 탭, 로그인·온보딩 라이트/다크 390/1280px, 고해상도 보태니컬 자산을 함께 고정한다. 라이트 테마도 패널당 복수 굴절 그라데이션, backdrop blur, 헤어라인, 그림자를 computed style로 단언하며 모바일 셸이 보태니컬 배경을 제거하지 않는지 검사한다. 관리자 사용자 표는 semantic table, 정렬 헤더, 1440px 최소 폭과 표 전용 가로 스크롤을 desktop/mobile에서 검증한다.
|
||||
- 2026-07-31 활성 세션 보태니컬 워크스페이스 검증: `full-sweep-session.spec.ts`의 1536×1024 계약이 좌 326px·우 357px 레일, 1408px 상단/본문, 1468px 하단 제어바, 좌우/스테이지 보태니컬 WebP 연결과 라이트 테마를 실측한다. 세션 전수 desktop/mobile **32 passed / 2 skipped**, `session-layout` **8/8**, 7폭 `layout-visual-gate` **15/15**, `check:design-ssot`·typecheck·build를 통과했다. 1366×768 이하는 스테이지보다 자막이 작아지지 않게 별도 압축 계약을 적용한다.
|
||||
- 2026-07-31 관리자 Provider·모델별 비용 원장 검증: `python -X utf8 -m pytest -p no:cacheprovider app/test_llm_pricing.py app/test_admin_ops.py app/test_usage_report.py engine_gateway/test_provider_registry.py engine_gateway/test_gateway_model.py -q` **66 passed**. Claude CLI SDK 비용 추정값과 Agy/Gemini·Codex·Claude API 공식 참조단가 추정을 분리하고, 기존 DB의 0달러 Agy 행을 조회 시 재산정하며, 단가 미등록 모델은 `미산정`으로 남기는 계약을 고정했다. `npm run generate:api-types`·`check:api-types`·typecheck·build를 통과했고, 관리자 AI desktop/mobile focused E2E **2 passed**와 7폭 focused layout visual gate **1 passed**에서 Gemini 원장 `$0.06`·`참조단가` 표시와 레이아웃 containment를 확인했다. 공개 API 프로세스를 재시작하고 Cloudflare Pages production `11b3e11f`에 배포했다. 인증된 공개 `/admin/usage?window_days=30`은 `source=database`, `durable=true`, `gemini-3.6-flash-high` 5호출·입력 35,703·출력 1,129·참조단가 `$0.062022`를 반환했고, 검증용 인증 세션은 즉시 삭제했다. 커스텀 도메인의 `AdminAi-BfxMUlzj.js`와 `AdminAi-XHkda_d4.css`는 올바른 JavaScript/CSS MIME으로 200을 반환한다.
|
||||
- 2026-07-31 Claude CLI 토큰 원장 focused 검증: result `modelUsage`의 전체 agent tree를 합산하고 최상위 `usage` 폴백, cache read/create 입력 포함, generate·SSE done 전파를 회귀화했다. 전체 backend **436 passed**, gateway **45 passed**, API type generation/check·typecheck·build, 관리자 AI desktop/mobile **2 passed**. 신규 live Opus generate는 입력 **31,918**·출력 **4**·비용 추정 `$0.124862`, Haiku SSE done은 입력 **30,450**·출력 **159**·비용 추정 `$0.061685`를 반환했다. 운영 DB 30일 원장의 과거 Claude 행은 역산하지 않고 `tokens=미계량`, `token_unmetered_turns=194`, `cost_basis=provider_estimate`로 분리됨을 임시 인증 세션으로 확인하고 세션을 즉시 폐기했다.
|
||||
- 2026-07-31 Claude CLI 과거 토큰 백필 검증: 로컬 Claude JSONL **3,908개**의 assistant usage record **3,848개**를 읽되 본문을 출력하지 않고, 정규화 응답 SHA-256과 DB 생성 시각 창(-30초~+180초)이 모두 일치하며 후보가 정확히 1개인 턴만 복구했다. 운영 DB의 과거 0/0 Claude 턴 **442건** 중 **169건**을 실제 입력 **5,179,999**·출력 **46,910** 토큰으로 갱신했고, 불일치 **273건**은 계속 `미계량`, 모호한 후보는 **0건**이었다. apply는 `--expected-matches 169` 가드와 행별 `UPDATE 1` 확인을 통과했고, 적용 후 dry-run 재실행에서 추가 exact match **0건**을 확인했다. 전체 backend **439 passed**, gateway **45 passed**이며, 공개 관리자 브라우저 **1 passed**에서 Claude 행의 실제 토큰과 부분 계량 `125/183회` 표시를 확인하고 임시 세션을 폐기했다.
|
||||
|
||||
레이아웃·시각 회귀 게이트(핵심 합격선):
|
||||
|
||||
|
|
|
|||
|
|
@ -11,6 +11,11 @@
|
|||
> **B0. 원천문서 갭** — 한신대 산학협력 원천문서 갭(critical/high/medium)은 SSOT 대시보드 "원천문서 갭 분석"
|
||||
> 섹션과 상세 근거 `docs/ops/source-docs-gap-analysis-2026-06-26.md`에서 추적한다. C1~C3/H·M 구조는 코드로
|
||||
> 선제 구축했고, 임상 문안·평가기준·골든셋 콘텐츠는 임상팀(구훈정·어유경) 외부 정의로 받는다.
|
||||
>
|
||||
> **Outcome & Alliance OS** — 2026-08-06 정식 전략 실행 트랙으로 승격했다. G0 Measurement Truth, G1
|
||||
> 현재 소스·실행 증거 재감사에서는 G0~G6이 internal 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은 2026-08-07 격리 NAS에서 실제 receipt-bound image rollback 2회(`nas-g8-723eeef2…`/`nas-g8-2738846c…`)를 executed lifecycle receipt로 실행해 DONE이다. 과거 clean release/NAS의 route·asset·auth·browser 증거는 배포 존재 이력으로 보존하지만 현재 소스의 완료 증거로 재사용하지 않는다. G7 Multimodal Alliance는 내부 구현 DONE과 외부 proof GATE를 분리하며 Deepgram key·quota live, expected provider/model authenticated public WSS, 명시 동의 물리 마이크·50분 soak, 독립 라벨 voice-gain benchmark와 운영 topology resource high-water를 추적한다. 운영 TTS는 OpenAI API `gpt-4o-mini-tts`로 고정하고 공식 앱 통합·출력 소유 근거와 AI 생성 음성 고지를 반영했다. G0~G8과
|
||||
> AOS-001~012는 `docs/TODO.md` I절에서 전건 추적하고, 상태는 SSOT 대시보드의 9개 계획 카드가 소유한다.
|
||||
> 이 얇은 백로그에는 그중 외부·환경 증거가 필요한 항목만 기존 B2/B4/Phase 3 게이트와 합쳐 유지한다.
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -20,7 +25,7 @@
|
|||
스레드 생성, 처리 결과 audit trail 확장은 아직 설계/승인 필요. 담당 그룹 자동 배정·우선순위 escalation·raw/rollup
|
||||
보존기간 같은 운영 정책은 B3에서 이미 결정됨(수동 승인·미도입 고정). 자동 수정은 운영자 승인 전까지 실행하지 않는다.
|
||||
|
||||
> B1 완료 항목(셸 구분선, 세션 종료 UX/다크테마, 아바타 SVG 리그 복귀와 래스터 비활성화, SEO/공유 카드, 레이아웃 정렬, 권한 위임,
|
||||
> B1 완료 항목(학생 리뷰 1~5 척도 320px 반응형 재배치, 셸 구분선, 세션 종료 UX/다크테마, 아바타 SVG 리그 복귀와 래스터 비활성화, SEO/공유 카드, 레이아웃 정렬, 권한 위임,
|
||||
> 메일링, 아카이브 API, TTS voice map, 빈상태 레이아웃, 평가 실패 복구 UX, SSE 저장, live-coach 표면화,
|
||||
> 음성 메타, 위기 게이트 UI, 운영 티켓/헬스 샘플러 등)의 상세·검증 로그는 아카이브 full-history 참조.
|
||||
|
||||
|
|
@ -28,20 +33,46 @@
|
|||
|
||||
## B2. 환경 제약 — 이 워크스테이션에서 증거 생산 불가
|
||||
|
||||
- [ ] **공개 DB 계정·회기 복구 안정화** — 2026-08-07 18:26 KST owner 승인으로 recovered named volume을
|
||||
`vignette-dev-db:55432`에 전환했다. public 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를
|
||||
모두 보존하며, 실제 사용자의 소유 회기 확인과 자동 백업 운영화 전에는 제거하지 않는다.
|
||||
상세: `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`.
|
||||
- [ ] **실배포 `infra/.env` owner-secret fill-in + real preflight** — 필수 소유자 비밀값: `APP_DB_PASSWORD`,
|
||||
`OAUTH_GOOGLE_CLIENT_ID`, `OAUTH_GOOGLE_CLIENT_SECRET`, `OPENAI_API_KEY`, `SESSION_SECRET`, production-safe
|
||||
engine/voice flags. 로컬 stray 값(`ENGINE_MODE=claude_cli`, prod sample TTS flag 등)은 운영값으로 복사 금지.
|
||||
실제 secret 주입 후 `scripts/check-deploy-preflight.py` DB 포함 모드 + 배포지 health를 별도 증거로 닫는다.
|
||||
- [ ] **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를 다시 닫는다.
|
||||
- [ ] **공개 Google OAuth 실제 `/turn` proof** — 로그인 가능한 계정으로 `storageState` 캡처 후 `E2E_PUBLIC_AUTH=1`
|
||||
+ `chromium-public-auth` 1회 통과 필요. (소유자 지시로 보류 중.) 실행 명령은 대시보드 "다음 실행 명령" 참조.
|
||||
- [ ] **음성 캐스케이드 live** — 로컬 API 실제 OpenAI STT/TTS와 P1 Higgs 실제 문장 WAV 합성 경로는 live smoke 통과.
|
||||
Higgs는 무참조 synthetic seed·dev-only로 유지한다. 남은 범위: 실제 Deepgram interim/final WSS, 물리 마이크,
|
||||
공개 WSS, 50분 양방향 장시간 실측과 상업 라이선스가 명확한 운영 TTS provider 통합.
|
||||
- [ ] **음성 캐스케이드 live** — 공개 G7 API route와 TLS 1.3 WSS handshake, 비인증 1008 차단, 로컬
|
||||
synthetic short soak는 통과했다. Deepgram streaming adapter/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, 30명/50회기/150축 독립 blind
|
||||
human-held-out voice-gain evaluator와 이를 같은 public host·동시 50분 시간창으로 묶는 canonical checker까지
|
||||
code/internal 준비를 마쳤다.
|
||||
운영 TTS는 OpenAI API `gpt-4o-mini-tts`와 공식 Services Agreement 근거, 회기 전·중 AI 생성 음성 고지로
|
||||
닫았다. 남은 범위: 실제 Deepgram 운영 key·quota의 live interim/final, 기대값과 일치하는 authenticated public
|
||||
ready, 실행 직전 명시 동의 물리 마이크와 50분 양방향 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을 만들기 전에는 닫지 않는다.
|
||||
격리 NAS 프리뷰 `http://100.116.83.60:8088`은 전용 Compose 프로젝트·포트·네트워크·볼륨에 배포했고, 실제 브라우저 회기와
|
||||
review API 저장 축어록 2턴을 확인했다. 기존 프로젝트 중단·재생성 명령은 실행하지 않았다. 매일 04:30 KST `Vignette 회기 E2E 정기 검증`
|
||||
(automation id `vignette-e2e`)은 ACTIVE이며 material milestone+release-only patch 결정성·manifest·clean-index·NAS preflight+배포 SHA 변경 때만 프리뷰를 갱신한다. current exact SHA `6030a677af7e87cbfabc422b553d108d53414fd3c446548734a13b036d35c611`은 API/Web 새 이미지, 전체 browser E2E 108/108, OpenAPI 126, auth 401, G0~G8, postdeploy browser SSE→DB review와 health 3/3을 통과했다. 실제 receipt-bound rollback은 같은 프리뷰에서 별도 helper로 두 번 실행해 종료했다([런북](./nas-preview-g8-rollback-proof-runbook.md), [기계 판독 증거](./evidence/nas-preview-g8-actual-rollback-2026-08-07.json)). 첫 예약 실행 이력은 대기 상태다. 증거: [배포 증거](./nas-preview-deployment-evidence-2026-08-07.md),
|
||||
[브라우저 증거](./evidence/nas-preview-live-turn-2026-08-07.png). 물리 마이크 캡처 미승인과 Deepgram 운영 키 부재는 해소되지 않았으므로 G7은 BUILD로 유지한다.
|
||||
- [ ] **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를 통과했다.
|
||||
- [ ] **재부팅 후 watchdog smoke** — `watch-public-runtime.ps1` + Scheduled Task가 재부팅 후 엔진/API/터널을 복구하고
|
||||
public `/turn`이 통과하는지 실측. 재부팅 불가로 미실행(parser/check-only 경로는 확인). DNS 개통 후
|
||||
|
|
|
|||
238
docs/ops/evidence/nas-preview-g8-actual-rollback-2026-08-07.json
Normal file
238
docs/ops/evidence/nas-preview-g8-actual-rollback-2026-08-07.json
Normal file
|
|
@ -0,0 +1,238 @@
|
|||
{
|
||||
"schema_version": "vignette.nas-preview-g8-actual-rollback.v1",
|
||||
"recorded_at_kst": "2026-08-07T21:15:00+09:00",
|
||||
"run_id": "20260807g8a",
|
||||
"scope": "isolated NAS preview only; public runtime untouched",
|
||||
"target": {
|
||||
"compose_project": "vignette-preview-20260807",
|
||||
"remote_root": "/volume1/docker/vignette-preview-20260807",
|
||||
"preview_url": "http://100.116.83.60:8088",
|
||||
"health_url": "http://127.0.0.1:8088/api/health",
|
||||
"db_ip": "172.22.0.2",
|
||||
"db_role": "vignette_app",
|
||||
"db_name": "vignette_preview"
|
||||
},
|
||||
"gate1_preconditions": {
|
||||
"current_api_image": "sha256:52e0e816c47b1152368113b186d5cdb971123bc0961122541a7becf430b88b2d",
|
||||
"current_web_image": "sha256:6fdbb6460a7617540fa3ceff7eba96a2b9d83678318f0e27f99ac4bd7d93f215",
|
||||
"previous_api_image": "sha256:79ec731f6bcdb79850507f5b711930a710e2d8e82276e3ea61dc8e4a60e04450",
|
||||
"previous_web_image": "sha256:c530213f4f8b23f228b2999df0c3653173cfa6939b6abb476f17df7488462f28",
|
||||
"manifest_sha256": "eeb8edc060e920195e083399814b9594c4d88e3d534daa39facd6c895f9e8ef2",
|
||||
"rollback_old_plan_sha256": "1ba0af237b83ace5572b49d699b2fd81bc6b46752cb86317c4d013f8a8f6aa6f",
|
||||
"restore_current_plan_sha256": "9ee8ecca9f712bce96592e9b88f30b24fc2c71c220022efcba95a85316cd3309",
|
||||
"executor_script_sha256": "07ea4ff348b5b96959c9411123c0c8519f22cc972a7c78ecb5f5e40ae88093d7",
|
||||
"probe_script_sha256": "74d4b80b031106c620f36dffc7d7a99667f1eda23333dbdb9e7e75e3ec5bab68",
|
||||
"preproof_dump_sha256": "92ed4736c1b21081e7ce1dc16aa0d026456d7683fd550077136d3de736c1570f",
|
||||
"preproof_dump_bytes": 960970,
|
||||
"listeners_18149_8018": 0,
|
||||
"helper_containers": 0,
|
||||
"health": "ok/db=true/engine=true"
|
||||
},
|
||||
"gate6_helper_isolation": {
|
||||
"contract": "membership, not compose-label absence",
|
||||
"rationale": "the audited current API image carries com.docker.compose.* image labels, so a zero-key rule can never hold while the audited runtime image is reused",
|
||||
"implementation": "scripts/launch-nas-preview-g8-helpers.py",
|
||||
"tests": "scripts/test_launch_nas_preview_g8_helpers.py 37/37 PASS",
|
||||
"image_baseline_compose_labels": {
|
||||
"com.docker.compose.project": "vignette-preview-20260807",
|
||||
"com.docker.compose.service": "api",
|
||||
"com.docker.compose.version": "2.20.1"
|
||||
},
|
||||
"inherited_compose_labels": {
|
||||
"com.docker.compose.version": "2.20.1"
|
||||
},
|
||||
"isolated_project": "vignette-g8-helper-20260807g8a",
|
||||
"target_project_members_during_proof": [
|
||||
"vignette-preview-20260807-api-1",
|
||||
"vignette-preview-20260807-db-1",
|
||||
"vignette-preview-20260807-proxy-1",
|
||||
"vignette-preview-20260807-web-1"
|
||||
],
|
||||
"helpers": [
|
||||
{
|
||||
"role": "executor",
|
||||
"container_name": "g8-rollback-executor-20260807g8a",
|
||||
"container_id": "5f5f3295f57fe9e8ccb61b719d31d0cdfca54730510ab811d95fcc9163b2785c",
|
||||
"isolated_service": "g8-rollback-executor",
|
||||
"hardening": "network=host, readonly rootfs, cap-drop ALL, no-new-privileges, user 1028:100, group-add 101, pids-limit 256, restart=no",
|
||||
"bind": "127.0.0.1:18149",
|
||||
"unauth_healthz_status": 403,
|
||||
"token_healthz_status": 200,
|
||||
"unauth_rollback_post_status": 403
|
||||
},
|
||||
{
|
||||
"role": "control-plane",
|
||||
"container_name": "g8-rollback-control-plane-20260807g8a",
|
||||
"container_id": "ba545c633dd2074b0a31889019b6d10fbe60776de8881b161bfb084f835025df",
|
||||
"isolated_service": "g8-rollback-control-plane",
|
||||
"bind": "127.0.0.1:8018",
|
||||
"health_status": 200,
|
||||
"unauth_auth_me_status": 401,
|
||||
"producer_enabled": false,
|
||||
"distinct_lifecycle_from_preview_api": true
|
||||
},
|
||||
{
|
||||
"role": "probe",
|
||||
"plan": "rollback-old",
|
||||
"container_name": "g8-rollback-probe-20260807g8a",
|
||||
"container_id": "15c385c86db05461a61001ecdf38d693f31e4cdd1d8f0e5e2c7f62933f2fa56e",
|
||||
"isolated_service": "g8-rollback-probe",
|
||||
"docker_socket_mounted": false,
|
||||
"exit_code": 0
|
||||
},
|
||||
{
|
||||
"role": "probe",
|
||||
"plan": "restore-current",
|
||||
"container_name": "g8-rollback-probe-20260807g8a",
|
||||
"container_id": "af079155c9b217f6c271bda03ebfe49ac99a5d8febc769d9cb088ada42ad289f",
|
||||
"isolated_service": "g8-rollback-probe",
|
||||
"docker_socket_mounted": false,
|
||||
"exit_code": 0
|
||||
}
|
||||
]
|
||||
},
|
||||
"planned_step_deviation": {
|
||||
"planned": "Windows SSH tunnel 127.0.0.1:18018 to NAS 127.0.0.1:8018, probe executed from Windows",
|
||||
"observed": "NAS sshd refused direct-tcpip with 'administratively prohibited: open failed'",
|
||||
"action": "probe executed from an isolated NAS-side helper container against NAS loopback 127.0.0.1:8018",
|
||||
"contract_preserved": "control-plane origin http://127.0.0.1:8018 differs from preview origin http://127.0.0.1:8088; the probe still enforces main_preview_self_rollback_forbidden",
|
||||
"nas_sshd_config_changed": false
|
||||
},
|
||||
"executions": [
|
||||
{
|
||||
"plan": "rollback-old",
|
||||
"probe_id": "nas-g8-probe-51c203e09baf9957a571e915",
|
||||
"plan_sha256": "3cdbe07193ae17f96ead11f930db7bb085648de2b75aa19b595733650389bf27",
|
||||
"executor_receipt_id": "nas-g8-723eeef22eab05e63e3fafb0",
|
||||
"artifact_id": "vignette-preview-release-a8c27b0aadcb9dac",
|
||||
"artifact_sha256": "a8c27b0aadcb9dacea86c4be5e62e19277b9cc2678ebca51aa02998d2c2d1a0d",
|
||||
"lifecycle_status": "executed",
|
||||
"release_gate_idempotent_replay": true,
|
||||
"approval_idempotent_replay": true,
|
||||
"binding_verified": true,
|
||||
"control_plane_separation_verified": true,
|
||||
"started_at_utc": "2026-08-07T12:04:31+00:00",
|
||||
"executed_at_utc": "2026-08-07T12:05:11+00:00",
|
||||
"images_before": {
|
||||
"api": "sha256:52e0e816c47b1152368113b186d5cdb971123bc0961122541a7becf430b88b2d",
|
||||
"web": "sha256:6fdbb6460a7617540fa3ceff7eba96a2b9d83678318f0e27f99ac4bd7d93f215"
|
||||
},
|
||||
"images_after": {
|
||||
"api": "sha256:79ec731f6bcdb79850507f5b711930a710e2d8e82276e3ea61dc8e4a60e04450",
|
||||
"web": "sha256:c530213f4f8b23f228b2999df0c3653173cfa6939b6abb476f17df7488462f28"
|
||||
},
|
||||
"health_checks_after": 3,
|
||||
"health_ok_after": 3,
|
||||
"result_file_sha256": "54e156fee3fb2b37fe92e7fba845cc02db1887d29e5c3cfd2ca0045942ddf95e"
|
||||
},
|
||||
{
|
||||
"plan": "restore-current",
|
||||
"probe_id": "nas-g8-probe-3fc9052ae1f4577a316db010",
|
||||
"plan_sha256": "050d2c72d176bf5e6426de837e0c6a98a85c08789411159d7681b4c4e5c07ca5",
|
||||
"executor_receipt_id": "nas-g8-2738846cf2cf4fbe8ce0fc26",
|
||||
"artifact_id": "vignette-preview-release-6030a677af7e87cb",
|
||||
"artifact_sha256": "6030a677af7e87cbfabc422b553d108d53414fd3c446548734a13b036d35c611",
|
||||
"lifecycle_status": "executed",
|
||||
"release_gate_idempotent_replay": true,
|
||||
"approval_idempotent_replay": true,
|
||||
"binding_verified": true,
|
||||
"control_plane_separation_verified": true,
|
||||
"started_at_utc": "2026-08-07T12:06:06+00:00",
|
||||
"executed_at_utc": "2026-08-07T12:06:42+00:00",
|
||||
"images_before": {
|
||||
"api": "sha256:79ec731f6bcdb79850507f5b711930a710e2d8e82276e3ea61dc8e4a60e04450",
|
||||
"web": "sha256:c530213f4f8b23f228b2999df0c3653173cfa6939b6abb476f17df7488462f28"
|
||||
},
|
||||
"images_after": {
|
||||
"api": "sha256:52e0e816c47b1152368113b186d5cdb971123bc0961122541a7becf430b88b2d",
|
||||
"web": "sha256:6fdbb6460a7617540fa3ceff7eba96a2b9d83678318f0e27f99ac4bd7d93f215"
|
||||
},
|
||||
"health_checks_after": 3,
|
||||
"health_ok_after": 3,
|
||||
"result_file_sha256": "c2f535aa3bfb459ddd1395e21b8ebbea0c3ac6252bbb5cd4217c845cab443825"
|
||||
}
|
||||
],
|
||||
"journal": {
|
||||
"path": "/volume1/docker/vignette-preview-20260807/.rollback-executor/journal.jsonl",
|
||||
"schema_version": "vignette.nas-preview-rollback-journal.v1",
|
||||
"records": 6,
|
||||
"events": ["started", "prepared", "executed", "started", "prepared", "executed"],
|
||||
"hash_chain_verified": true,
|
||||
"sha256": "a5594feb848d33e8ebfe52e55dc2802ba03d3161690b71724ec50fb779470690",
|
||||
"integrity_key_id": "6d2f7a5169406e2a",
|
||||
"record_hashes": [
|
||||
"3a694e813d98811ef6132e159e65f6d980ff16f6495696b165079672734187f4",
|
||||
"6748e27bbae655386ef4f73c8bea18cae30d6b9ccc0385a73e9b8df59acb4fab",
|
||||
"7142488d0935b755af33f21675e938f5dda93bb1d268a99feb46bbf4f53ccea4",
|
||||
"dba76366e9e544f8edfc3a5c532484d0de2b9d4d0b8f98b13e335c980401cd12",
|
||||
"cd585370680b0882c8ac775e6cc870c70b005b5c146c17f16a283902fff39878",
|
||||
"34ca44ba32dd9b03b630fc43301addb14bc1a711bbb5551a7081f721d417ac5c"
|
||||
]
|
||||
},
|
||||
"db_lifecycle_aggregate": {
|
||||
"measured_as": "table owner role, RLS bypassed for counting only",
|
||||
"before": {
|
||||
"app_user": 11,
|
||||
"sessions": 10,
|
||||
"turns": 14,
|
||||
"auth_session": 12,
|
||||
"g8_lifecycle_tables_nonzero": 0
|
||||
},
|
||||
"after": {
|
||||
"app_user": 12,
|
||||
"sessions": 10,
|
||||
"turns": 14,
|
||||
"auth_session": 14,
|
||||
"app.ci_release_gate": 2,
|
||||
"app.ci_gate_artifact": 8,
|
||||
"app.ci_ingestion_submission": 4,
|
||||
"audit.ci_human_approval_event": 2,
|
||||
"audit.ci_lifecycle_event": 2,
|
||||
"other_ci_tables": 0
|
||||
},
|
||||
"lifecycle_breakdown": {"rollback/executed": 2},
|
||||
"approval_breakdown": {"authorize_rollback/release_gate": 2},
|
||||
"release_gate_state": {"pending_human_approval/qualified": 2},
|
||||
"silent_auto_promotion": 0,
|
||||
"executor_receipt_ids_bound_in_db": [
|
||||
"nas-g8-2738846cf2cf4fbe8ce0fc26",
|
||||
"nas-g8-723eeef22eab05e63e3fafb0"
|
||||
]
|
||||
},
|
||||
"post_state": {
|
||||
"helper_containers": 0,
|
||||
"listeners_18149_8018_18018": 0,
|
||||
"target_project_members": 4,
|
||||
"api_image": "sha256:52e0e816c47b1152368113b186d5cdb971123bc0961122541a7becf430b88b2d",
|
||||
"web_image": "sha256:6fdbb6460a7617540fa3ceff7eba96a2b9d83678318f0e27f99ac4bd7d93f215",
|
||||
"health_checks": 3,
|
||||
"health_ok": 3,
|
||||
"openapi_paths": 126,
|
||||
"auth_me_status": 401,
|
||||
"web_root_status": 200,
|
||||
"override_temp_files": 0,
|
||||
"helper_env_files_remaining": 0,
|
||||
"compose_down_executed": false,
|
||||
"volume_rm_executed": false,
|
||||
"prune_executed": false,
|
||||
"public_runtime_mutated": false,
|
||||
"public_runtime_note": "the public stack (vignette.chanpaca.net, api origin 127.0.0.1:8001, container vignette-dev-db, volume vignette_recovered_prod_20260807) was only read, never restarted, reconfigured or written to"
|
||||
},
|
||||
"post_rollback_runtime_verification": {
|
||||
"test": "session-persistence: persists the browser SSE stream into DB-backed review",
|
||||
"project": "chromium-single-run",
|
||||
"target": "http://100.116.83.60:8088",
|
||||
"result": "passed",
|
||||
"duration_seconds": 35.0,
|
||||
"physical_microphone_used": false,
|
||||
"route_mocks": 0,
|
||||
"note": "run after restore-current, so the restored runtime is proven to serve a real browser session end to end"
|
||||
},
|
||||
"safety_scan": {
|
||||
"secrets": 0,
|
||||
"emails": 0,
|
||||
"cookies": 0,
|
||||
"non_deterministic_uuids": 0,
|
||||
"note": "every UUID in the preserved probe results is a uuid5 value derived from the repo-fixed probe namespace and the plan suffix; no account, session or personal identifier is recorded"
|
||||
}
|
||||
}
|
||||
166
docs/ops/nas-preview-deployment-evidence-2026-08-07.md
Normal file
166
docs/ops/nas-preview-deployment-evidence-2026-08-07.md
Normal file
|
|
@ -0,0 +1,166 @@
|
|||
# NAS 격리 프리뷰 배포 증거 — 2026-08-07
|
||||
|
||||
## 결과
|
||||
|
||||
- Tailnet 프리뷰: `http://100.116.83.60:8088`
|
||||
- Compose 프로젝트: `vignette-preview-20260807`
|
||||
- 원격 루트: `/volume1/docker/vignette-preview-20260807`
|
||||
- 실제 health: `status=ok`, `db=true`, `engine=true`, `engine_mode=claude_cli`
|
||||
- 배포 SHA: `6030a677af7e87cbfabc422b553d108d53414fd3c446548734a13b036d35c611`
|
||||
- 이전 배포 SHA: `a8c27b0aadcb9dacea86c4be5e62e19277b9cc2678ebca51aa02998d2c2d1a0d`
|
||||
- API 이미지: `sha256:52e0e816c47b1152368113b186d5cdb971123bc0961122541a7becf430b88b2d`
|
||||
- Web 이미지: `sha256:6fdbb6460a7617540fa3ceff7eba96a2b9d83678318f0e27f99ac4bd7d93f215`
|
||||
- OpenAPI: 126 paths, G0~G8 Outcome/Alliance·Practice·Continuous Improvement 경로 포함
|
||||
- 비인증 `/api/auth/me`: 401
|
||||
- DB 계약: `vignette_app` 역할과 최종 `app.ci_regression_dag_node` 스키마 접근 gate 통과
|
||||
|
||||
기존 NAS 서비스와 포트·네트워크·볼륨을 공유하지 않는 별도 프리뷰다. 배포 작업은 이 전용 Compose
|
||||
프로젝트만 대상으로 했으며 기존 프로젝트를 대상으로 한 중단·재생성 명령은 실행하지 않았다. 이 기록은
|
||||
격리 구성과 실행 범위를 증명하지만 기존 서비스의 배포 전후 uptime 비교 증거로 확대 해석하지 않는다.
|
||||
|
||||
## 실제 브라우저 증거
|
||||
|
||||
- 학습자 경로: dev-login → 온보딩 → DB 페르소나 선택 → 새 회기 → Alliance pre 기준 잠금 → 세션 진입
|
||||
- Playwright: `1 passed (13.1s)`
|
||||
- 실제 엔진 1턴: 텍스트 발화 → SSE 내담자 응답 → 프로토콜 종료 → 후속 발화 가능 → DB-backed review
|
||||
- Playwright: `1 passed (24.4s)`, 테스트 본문 `23.4s`
|
||||
- 증거 세션: `c96922ab-0334-4864-ae48-df41a24aae14`
|
||||
- review: 학습자 1턴 + 내담자 1턴, 총 2턴
|
||||
- 2026-08-07 08:29 KST 실제 NAS review API 재조회: HTTP 200, 같은 session id, 저장 축어록 2턴
|
||||
- 물리 마이크는 사용하지 않았다. 자동 TTS 재생만 제품 동작으로 확인했다.
|
||||
- 최종 release-agent 브라우저 증거: 실제 NAS에서
|
||||
`session-persistence: browser SSE stream -> DB-backed review` 통과,
|
||||
stdout SHA-256 `b45111ce9f47d30e4c848362813940f3efd5464660b5046159b88befd9a59840`.
|
||||
|
||||
시각 증거:
|
||||
|
||||
- [학습 홈](./evidence/nas-preview-learner-home-2026-08-07.png)
|
||||
- [Alliance pre 완료 후 세션](./evidence/nas-preview-learner-session-2026-08-07.png)
|
||||
- [실제 엔진 1턴과 후속 입력 가능 상태](./evidence/nas-preview-live-turn-2026-08-07.png)
|
||||
- [기계 판독 배포 증거](./evidence/nas-preview-deployment-2026-08-07.json)
|
||||
|
||||
## 실배포에서 닫은 재발 방지 gate
|
||||
|
||||
1. Synology BuildKit이 거부하던 루트 `.dockerignore` 비ASCII 활성 패턴을 제거하고 ASCII 회귀 테스트를 추가했다.
|
||||
2. Compose 2.20이 JSON 기본값을 `{}}`로 조립하던 `FRONTEND_ORIGIN_MAP`을 필수 환경값으로 바꿨다.
|
||||
3. DB health가 초기 SQL 실행 중 너무 일찍 통과하지 않도록 app role과 최종 G8 스키마 접근을 요구한다.
|
||||
4. API health는 HTTP 200이 아니라 `status=ok`, `db=true`, `engine=true`를 모두 요구한다.
|
||||
5. 새 DB 전체 초기화에 5분 `start_period`를 둔다.
|
||||
6. 프리뷰 dev-login은 `hs.ac.kr`, `twentyoz.kr` 명시 allowlist만 허용한다.
|
||||
7. 브라우저 SSE는 전송 계층 EOF가 아니라 애플리케이션 `done` 이벤트에서 reader를 종료한다.
|
||||
8. Windows clean candidate의 생성 API 계약과 Linux `*.sh`를 LF로 정규화해 `bash\r` DB init 실패를 차단한다.
|
||||
9. 승격·롤백은 active snapshot의 canonical env 경로를 공유하고, 업로드 전에 존재를 검증한다.
|
||||
10. API/Web 교체 직후 502/503/504·연결 대기·초기 semantic health만 180초 안에서 재확인하고,
|
||||
OpenAPI/auth 계약 위반은 즉시 실패한다. 최종 승격은 readiness 3회째 통과했다.
|
||||
|
||||
## 최신 G7 hardening release-agent 승격
|
||||
|
||||
- material milestone과 release-only patch를 두 번 동일하게 생성하고 clean HEAD·canonical index 적용을 확인했다.
|
||||
- 후보 API 전체, Web API 계약·typecheck·build, 격리 Compose와 회기 E2E를 통과한 뒤에만 NAS를 갱신했다.
|
||||
- 첫 실제 교체는 즉시 502를 정상 실패로 판단해 이전 이미지로 자동 롤백했고, 이미지 ID·semantic health·
|
||||
OpenAPI 122·auth 401을 다시 확인했다. 이후 readiness window를 추가한 최종 실행은 3회째 health를 통과했다.
|
||||
- 첫 실행은 기존 DB-backed mic fixture가 새 동의 모달을 거치지 않아 session E2E 23/24에서 fail-closed로
|
||||
중단했고 NAS는 변경하지 않았다. 실제 사용자 순서대로 마이크→서버 동의 원장→synthetic capture로 고친 뒤
|
||||
focused 1/1과 clean candidate session E2E 24/24를 통과했다.
|
||||
- 최종 런타임은 OpenAPI 124, auth 401, G0~G8 계약과 실제 브라우저 SSE→DB review를 통과한 뒤
|
||||
active-state를 위 exact SHA와 새 API/Web 이미지로 원자 커밋했다.
|
||||
- 전체 후보 API 테스트, 최종 session E2E `24/24`·`505.578s`, postdeploy browser review
|
||||
`25.328s`가 통과했다. 물리 마이크는 사용하지 않았다.
|
||||
|
||||
## 현재 G0~G8 통합 승격
|
||||
|
||||
- current source를 분류한 release-only patch는 세 번 동일 SHA로 생성됐고 manifest checker와 canonical
|
||||
clean-index 적용을 통과했다. 모바일 learner 4열 navigation을 소유한 `components/shell/shell.css`도
|
||||
runtime-critical related whole-file로 고정해 패키징 누락을 회귀 테스트로 막았다.
|
||||
- 후보 전체 browser E2E `108/108`은 `634.578s`에 통과했다. 실제 NAS 교체 뒤 일시적 502는 bounded
|
||||
readiness 두 번째 시도에서 회복됐고, 이후 독립 health `3/3`, OpenAPI 126 paths, auth 401,
|
||||
G0~G8 필수 route와 Web/CSS/JS 200을 확인했다.
|
||||
- postdeploy 실제 브라우저 SSE→DB-backed review는 `32.437s`에 통과했다. preview DB named volume은
|
||||
그대로이며 browser smoke 뒤 users 11·sessions 10·turns 14·auth sessions 12다. 배포 전 custom dump
|
||||
SHA는 `47c8156e…958d`이고 삭제·volume 교체·rollback은 없었다.
|
||||
- 기계 판독 요약: [current 통합 배포 증거](./evidence/nas-preview-current-deploy-2026-08-07.json).
|
||||
- 이 승격은 current 소스의 실제 런타임 증거지만 G8 receipt-bound image rollback 자체는 아니다. 그 증명은
|
||||
아래 절에서 별도 loopback executor와 독립 control-plane API로 닫았다.
|
||||
|
||||
## G8 실제 receipt-bound image rollback (2026-08-07 21:04~21:12 KST)
|
||||
|
||||
실행 절차와 Gate6 계약 정정은 [G8 rollback 증명 런북](./nas-preview-g8-rollback-proof-runbook.md),
|
||||
기계 판독 증거는 [`evidence/nas-preview-g8-actual-rollback-2026-08-07.json`](./evidence/nas-preview-g8-actual-rollback-2026-08-07.json)이다.
|
||||
|
||||
### helper 격리 (Gate6 정정)
|
||||
|
||||
감사 대상 current API 이미지 `sha256:52e0e816…8b2d` 자체가 `com.docker.compose.project=vignette-preview-20260807`,
|
||||
`service=api`, `version=2.20.1` image label을 갖는다. 따라서 helper의 compose label 개수를 0으로 요구하는
|
||||
이전 계약은 감사되지 않은 다른 이미지를 쓰지 않는 한 성립하지 않는다. 계약을 **key 부재가 아니라 소속**으로
|
||||
정정해 `scripts/launch-nas-preview-g8-helpers.py`에 구현하고 `scripts/test_launch_nas_preview_g8_helpers.py`
|
||||
`37/37`로 고정했다.
|
||||
|
||||
- image 상속 label을 baseline으로 읽고 container의 모든 `com.docker.compose.*`가 baseline과 같거나 선언된
|
||||
격리 override인지 검증한다. 실제 결과: 상속 `version=2.20.1`, override `project=vignette-g8-helper-20260807g8a`,
|
||||
`service=g8-rollback-executor` / `g8-rollback-control-plane` / `g8-rollback-probe`.
|
||||
- 최종 project는 target이 아니고, 최종 service는 `api`/`web`/`db`/`proxy`가 아니며,
|
||||
`docker ps --filter label=com.docker.compose.project=vignette-preview-20260807` 멤버는 프리뷰 4개뿐이었다.
|
||||
- `docker run` argv에 target project/service label을 주입하면 fake-runner 테스트가 먼저 실패한다.
|
||||
|
||||
| helper | container | ID |
|
||||
|---|---|---|
|
||||
| executor `127.0.0.1:18149` | `g8-rollback-executor-20260807g8a` | `5f5f3295f57f…785c` |
|
||||
| control-plane `127.0.0.1:8018` | `g8-rollback-control-plane-20260807g8a` | `ba545c633dd2…25df` |
|
||||
| probe (rollback-old) | `g8-rollback-probe-20260807g8a` | `15c385c86db0…a56e` |
|
||||
| probe (restore-current) | `g8-rollback-probe-20260807g8a` | `af079155c9b2…289f` |
|
||||
|
||||
executor는 host network·read-only rootfs·`cap-drop ALL`·`no-new-privileges`·uid1028/gid100·supplemental
|
||||
group101·`pids-limit 256`·`restart no`로 실행했고, unauth `/healthz` 403 · token `/healthz` 200 ·
|
||||
unauth `POST /rollback` 403을 확인했다. control-plane은 producer disabled, preview DB `172.22.0.2`의
|
||||
`vignette_app` role, executor endpoint `http://127.0.0.1:18149/rollback`, unauth `/auth/me` 401이다.
|
||||
|
||||
### 계획 대비 이탈 — SSH 터널
|
||||
|
||||
계획했던 Windows SSH 터널 `127.0.0.1:18018 → NAS 127.0.0.1:8018`은 NAS sshd가 direct-tcpip를
|
||||
`administratively prohibited: open failed`로 거부해 사용할 수 없었다. **sshd 설정은 바꾸지 않았고**,
|
||||
같은 격리 계약의 NAS-side probe 컨테이너에서 loopback으로 실행했다. control-plane origin
|
||||
`http://127.0.0.1:8018`과 preview origin `http://127.0.0.1:8088`이 다르므로 probe의
|
||||
`main_preview_self_rollback_forbidden` 계약은 그대로 강제된다.
|
||||
|
||||
### 두 번의 executed receipt
|
||||
|
||||
| plan | receipt | artifact | 활성화된 API/Web 이미지 |
|
||||
|---|---|---|---|
|
||||
| `rollback-old` | `nas-g8-723eeef22eab05e63e3fafb0` | `vignette-preview-release-a8c27b0aadcb9dac` | `79ec…4450` / `c530…2f28` |
|
||||
| `restore-current` | `nas-g8-2738846cf2cf4fbe8ce0fc26` | `vignette-preview-release-6030a677af7e87cb` | `52e0…8b2d` / `6fdb…f215` |
|
||||
|
||||
두 실행 모두 release gate 2회 멱등, approval 2회 멱등, lifecycle `executed`, artifact/approval/receipt
|
||||
binding 검증, control-plane 분리 검증, 실행 뒤 health `3/3`을 통과했다. 최종 상태는 OpenAPI 126 paths,
|
||||
`/api/auth/me` 401, Web root 200이다.
|
||||
|
||||
### 원장·무결성
|
||||
|
||||
- HMAC journal 6 records — `started`/`prepared`/`executed` × 2. `previous_hash` 체인 전수 검증 PASS.
|
||||
SHA256 `a5594feb848d33e8ebfe52e55dc2802ba03d3161690b71724ec50fb779470690`.
|
||||
- DB(owner role 집계): `app.ci_release_gate` 2, `app.ci_gate_artifact` 8, `app.ci_ingestion_submission` 4,
|
||||
`audit.ci_human_approval_event` 2(전부 `authorize_rollback`/`release_gate`),
|
||||
`audit.ci_lifecycle_event` 2(전부 `rollback`/`executed`), 나머지 `ci_*` 0.
|
||||
두 executor receipt id가 DB lifecycle에 그대로 결속돼 있다. release gate는 둘 다
|
||||
`pending_human_approval`·qualified로 남아 silent auto-promotion은 0이다.
|
||||
- core 집계 변화: app_user 11→12, auth_session 12→14(probe dev-login 2회), sessions 10·turns 14 불변.
|
||||
- probe 결과 파일 SHA256 — `rollback-old` `54e156fe…f95e`, `restore-current` `c2f535aa…3825`.
|
||||
- manifest `eeb8edc0…f8ef2`, plan `1ba0af23…aa6f` / `9ee8ecca…3309`, executor script `07ea4ff3…93d7`,
|
||||
probe script `74d4b80b…ab68`는 실행 전후 동일하다.
|
||||
|
||||
### 정리와 보존
|
||||
|
||||
helper 3개 제거, listener 18149/8018/18018 = 0, override 임시 파일 0, helper env 3개는 `shred -u`로 파기했다.
|
||||
journal·manifest·plan·evidence는 보존한다. `docker compose down`, `docker volume rm`, `docker system prune`은
|
||||
실행하지 않았고 공개 런타임(`vignette.chanpaca.net` / `vignette-dev-db`)은 건드리지 않았다.
|
||||
증거 안전 스캔은 이메일 0·비밀 힌트 0·plan 밖 UUID 0이다.
|
||||
|
||||
## 운영 및 남은 경계
|
||||
|
||||
- 자동화 `vignette-e2e` — **Vignette 회기 E2E 정기 검증**, 매일 04:30 KST, 실패 시만 알림, ACTIVE 구성 완료.
|
||||
material milestone이고 release-only patch 2회 결정성·manifest checker·canonical clean-index·NAS preflight가
|
||||
통과하며 현재 배포 SHA와 다를 때만 같은 격리 프리뷰를 갱신한다.
|
||||
이 증거 시점에는 첫 스케줄 실행 이력을 첨부하지 않았으며 실행 결과는 첫 실행 뒤 별도 기록한다.
|
||||
- G8은 내부 구현·격리 NAS runtime promotion·**receipt-bound 실제 image rollback**까지 모두 **DONE**이다.
|
||||
exact 배포 SHA와 두 receipt는 위에 고정했다.
|
||||
- G7은 계속 **BUILD**다. 이 프리뷰는 물리 마이크 실증이나 Deepgram 운영 키 증거를 대신하지 않는다.
|
||||
- 접근은 Tailnet 내부 HTTP 프리뷰다. 외부 공개 배포 완료 증거로 사용하지 않는다.
|
||||
136
docs/ops/nas-preview-g8-rollback-proof-runbook.md
Normal file
136
docs/ops/nas-preview-g8-rollback-proof-runbook.md
Normal file
|
|
@ -0,0 +1,136 @@
|
|||
# NAS 프리뷰 G8 실제 rollback 증명 런북 (2026-08-07)
|
||||
|
||||
> 대상: 격리 NAS 프리뷰 `vignette-preview-20260807` (`http://100.116.83.60:8088`).
|
||||
> 이 런북은 **공개 서비스(`vignette.chanpaca.net`)를 건드리지 않는다.**
|
||||
> 상태 SSOT는 [`docs/dev_dashboard.html`](../dev_dashboard.html)이다.
|
||||
|
||||
## 0. 무엇을 증명하는가
|
||||
|
||||
G8 "지속 개선"의 마지막 열린 게이트는 *합성 control plane*이 아니라 **실제 NAS 런타임에서
|
||||
receipt가 묶인 rollback 실행**이다. 증명 단위는 두 번이다.
|
||||
|
||||
1. `rollback-old` — 현재 릴리스(`6030a677…`)에서 이전 릴리스(`a8c27b0a…`) 이미지로 되돌린다.
|
||||
2. `restore-current` — 다시 현재 릴리스 이미지로 복원한다.
|
||||
|
||||
두 번 모두 durable `executed` lifecycle receipt, artifact/approval binding, health 재확인까지
|
||||
통과해야 한다. health만 보고 성공이라 부르지 않는다.
|
||||
|
||||
## 1. Gate6 helper 격리 계약 (2026-08-07 정정)
|
||||
|
||||
### 정정 이전의 잘못된 계약
|
||||
|
||||
이전 계획은 helper container의 `com.docker.compose.*` label 개수가 **0개**여야 한다고 요구했다.
|
||||
이 계약은 성립할 수 없다. 감사 대상 런타임 이미지 자체가 image label을 갖고 있기 때문이다.
|
||||
|
||||
```text
|
||||
sha256:52e0e816…8b2d → com.docker.compose.project=vignette-preview-20260807
|
||||
com.docker.compose.service=api
|
||||
com.docker.compose.version=2.20.1
|
||||
```
|
||||
|
||||
이 이미지로 만든 모든 container는 위 label을 **상속**한다. 따라서 "0개" 요구를 지키려면
|
||||
감사되지 않은 다른 이미지를 써야 하고, 그건 오히려 증명을 약하게 만든다.
|
||||
|
||||
### 실제로 강제하는 계약 — key 부재가 아니라 소속(membership)
|
||||
|
||||
`scripts/launch-nas-preview-g8-helpers.py`가 아래를 코드로 강제한다.
|
||||
|
||||
| # | 계약 | 구현 |
|
||||
|---|---|---|
|
||||
| 1 | helper는 target Compose project에 속하지 않는다 | `verify_container_isolation` + `docker ps --filter label=com.docker.compose.project=<target>` 멤버십 조회 |
|
||||
| 2 | image 상속 label을 baseline으로 읽고, container의 모든 `com.docker.compose.*`는 baseline과 같거나 **선언된 isolated override**여야 한다 | `docker image inspect` baseline ↔ `docker inspect` container 비교, drift는 `helper_compose_label_drift`로 fail-closed |
|
||||
| 3 | 최종 `com.docker.compose.project` ≠ `vignette-preview-20260807` | isolated override `vignette-g8-helper-<run-id>` |
|
||||
| 4 | 최종 `com.docker.compose.service` ∉ {`api`,`web`,`db`,`proxy`} | isolated override `g8-rollback-executor` / `g8-rollback-control-plane` |
|
||||
| 5 | `docker run` argv에 target project/service label을 넣지 않는다 | `assert_argv_isolation` + fake-runner 테스트 |
|
||||
| 6 | exact container name·ID와 `docker inspect` 증거를 남긴다 | `verify` 산출 evidence(`vignette.nas-preview-g8-helper-isolation.v1`) |
|
||||
|
||||
테스트: `scripts/test_launch_nas_preview_g8_helpers.py` (31/31). 실제 실행 없이 argv 계약이
|
||||
고정되므로, 누가 나중에 target label을 주입하면 테스트가 먼저 깨진다.
|
||||
|
||||
### helper 하드닝 (argv에 고정)
|
||||
|
||||
`--network host`(loopback bind 전용), `--user 1028:100`, `--group-add 101`(docker socket),
|
||||
`--read-only`, `--cap-drop ALL`, `--security-opt no-new-privileges`, `--pids-limit 256`,
|
||||
`--restart no`, target root RO 마운트 + `.rollback-executor` RW 중첩, Docker binary/Compose
|
||||
plugin RO, socket 마운트. `--publish`는 쓰지 않는다(host network).
|
||||
|
||||
## 2. 사전 상태 (읽기 전용으로 매번 재확인)
|
||||
|
||||
```powershell
|
||||
$ssh = @('-i','C:\Users\encep\.ssh\id_ed25519','-o','IdentitiesOnly=yes','-o','BatchMode=yes','YunChan@100.116.83.60')
|
||||
```
|
||||
|
||||
확인 항목:
|
||||
|
||||
- current images: API `sha256:52e0e816…8b2d`, Web `sha256:6fdbb646…f215`
|
||||
- previous images: API `sha256:79ec731f…0450`, Web `sha256:c530213f…62f28`
|
||||
- preview health `http://127.0.0.1:8088/api/health` → `status=ok`, `db=true`, `engine=true`
|
||||
- listeners 18149 / 8018 = 0, helper containers = 0
|
||||
- `.rollback-executor` 해시 4종(manifest / 두 plan / executor·probe 스크립트)
|
||||
- pre-proof DB dump 존재와 SHA256
|
||||
|
||||
## 3. 실행 순서
|
||||
|
||||
1. 읽기 전용 재검증(§2).
|
||||
2. executor env(`executor-<run-id>.env`, mode 0600) 생성 — 무작위 token과 stable journal key.
|
||||
값은 출력하지 않는다.
|
||||
3. control-plane env(`control-plane-<run-id>.env`, mode 0600) 생성 — preview `.env`에서
|
||||
비밀값을 **NAS 안에서만** 복사하고 아래를 덮어쓴다.
|
||||
- `DATABASE_URL` → `vignette_app@172.22.0.2:5432/vignette_preview`
|
||||
- producer/drift trigger 전부 `false`
|
||||
- `VIGNETTE_CONTINUOUS_IMPROVEMENT_ROLLBACK_EXECUTOR_ENABLED=true`
|
||||
- `…_ENDPOINT=http://127.0.0.1:18149/rollback`
|
||||
- `…_TOKEN` = executor token
|
||||
- `AUTH_DEV_LOGIN_ENABLED=true`, `AUTH_ADMIN_EMAILS`에 probe 계정만
|
||||
4. `launch-nas-preview-g8-helpers.py start --role executor` → isolation evidence 확인,
|
||||
unauth `/healthz` 403 / token-auth 200 확인.
|
||||
5. `launch-nas-preview-g8-helpers.py start --role control-plane` → `/api/health` 확인.
|
||||
6. Windows SSH 터널 `127.0.0.1:18018 → NAS 127.0.0.1:8018` (exact PID 기록).
|
||||
7. `probe-nas-preview-g8-rollback.py execute --plan rollback-old.plan.json` →
|
||||
release gate 2회 멱등, approval 2회 멱등, lifecycle `executed`, receipt binding,
|
||||
NAS previous images 활성화 + health 3회.
|
||||
8. 같은 executor/control-plane으로 `restore-current.plan.json` 실행 → current images 복원,
|
||||
health 3/3, OpenAPI 126, auth 401, Web 200.
|
||||
9. 증거 저장 → UUID/email/secret regex scan 0.
|
||||
10. cleanup 순서: tunnel → control-plane → executor. listeners 0, helper 0 확인.
|
||||
11. token/control env 삭제, journal/manifest/evidence 보존.
|
||||
|
||||
## 4. 금지 사항
|
||||
|
||||
- `docker compose down`, `docker volume rm`, `docker system prune` 금지.
|
||||
- current images와 health를 확인하기 전에 helper를 종료하거나 DONE으로 기록하지 않는다.
|
||||
- 비밀값·이메일·UUID·raw response를 로그·증거·채팅에 출력하지 않는다.
|
||||
- 공개 복구 DB(`vignette-dev-db`)와 그 volume/dump/container는 건드리지 않는다.
|
||||
|
||||
## 5. 실패 시 복구
|
||||
|
||||
executor는 실행 직전 관측한 exact image ID로 되돌린다. 그래도 어긋나면 pinned current images
|
||||
(`52e0e816…8b2d` / `6fdbb646…f215`)로 `compose up -d --no-build --no-deps --pull never
|
||||
--force-recreate api web`를 수행하고 health 3회를 다시 통과시킨다. DB는 pre-proof dump
|
||||
(`vignette-preview-g8-preproof-20260807T110610Z.dump`, SHA256 `92ed4736…1570f`)로 복원 가능하다.
|
||||
|
||||
## 6. 실행 결과 (2026-08-07 21:04~21:12 KST, run-id `20260807g8a`)
|
||||
|
||||
| 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`, binding·control-plane 분리 검증 PASS
|
||||
- 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 파기
|
||||
- 롤백/복원 뒤 실제 브라우저 SSE→DB review 재실행 PASS (35.0s)
|
||||
|
||||
### 3단계 이탈 기록 — SSH 터널 사용 불가
|
||||
|
||||
계획된 Windows SSH 터널(`18018 → 8018`)은 NAS sshd가 direct-tcpip를
|
||||
`administratively prohibited: open failed`로 거부했다. **sshd 설정은 바꾸지 않았고**, 같은 격리 계약의
|
||||
NAS-side probe helper 컨테이너에서 loopback으로 실행했다. control-plane(`127.0.0.1:8018`)과
|
||||
preview(`127.0.0.1:8088`) origin이 다르므로 probe의 `main_preview_self_rollback_forbidden` 계약은
|
||||
그대로 강제된다. 다음 실행자도 NAS에서 포트 포워딩을 기대하지 말 것.
|
||||
|
||||
상세 hash와 container ID는
|
||||
[`nas-preview-deployment-evidence-2026-08-07.md`](./nas-preview-deployment-evidence-2026-08-07.md)의 G8 절과
|
||||
[`evidence/nas-preview-g8-actual-rollback-2026-08-07.json`](./evidence/nas-preview-g8-actual-rollback-2026-08-07.json)에 있다.
|
||||
Loading…
Add table
Add a link
Reference in a new issue