정식 승격 상태판과 증거를 동기화
This commit is contained in:
parent
34aff65cb0
commit
be08c0b573
16 changed files with 1160 additions and 63 deletions
|
|
@ -9,6 +9,7 @@
|
|||
|---|---|
|
||||
| **[`dev_dashboard.html`](./dev_dashboard.html)** | **SSOT(단일 진실 공급원)** — 상태·검증 증거·결정·로드맵의 권위 기준 |
|
||||
| **[`TODO.md`](./TODO.md)** | **할 일(안된 것들) 통합** — 흩어진 열린 작업을 주제별로 모은 인덱스 |
|
||||
| **[`ops/handoff-goal-production-2026-08-29.md`](./ops/handoff-goal-production-2026-08-29.md)** | **최신 정식 배포 핸드오프** — 워크북 9/2, public API 장애, 아바타 decode 감사, fail-closed 후보와 승인·E2E 게이트 |
|
||||
| [`../AGENTS.md`](../AGENTS.md) | 에이전트 작업 지침 원본(OS 선파악·증거 정직성·SSOT 동기화·이미지/아바타 파이프라인) |
|
||||
|
||||
## 🗺 원천문서·갭 로드맵
|
||||
|
|
@ -71,7 +72,7 @@
|
|||
| [`guides/architecture.md`](./guides/architecture.md) | 시스템 아키텍처(엔진/오케스트레이터/저항/마스킹/음성/평가/데이터) |
|
||||
| [`guides/testing.md`](./guides/testing.md) | 테스트·검증(pytest, typecheck, Playwright E2E 게이트) |
|
||||
| [`DEPLOYMENT.md`](./DEPLOYMENT.md) | docker compose 배포·엔진 어댑터·환경변수·외부공개 운영 |
|
||||
| [`HANDOFF.md`](./HANDOFF.md) | 최신 세션 인수인계 — 현재 상태·남은 외부 게이트·다음 우선순위 |
|
||||
| [`HANDOFF.md`](./HANDOFF.md) | 일반 세션 인수인계 진입점 — 최신 정식 배포 상태는 위 2026-08-29 핸드오프 우선 |
|
||||
|
||||
## ⚙️ 운영 런북·게이트 트래커 (`ops/`)
|
||||
|
||||
|
|
@ -81,6 +82,12 @@
|
|||
| [`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/evidence/google-account-data-recovery-2026-08-29.json`](./ops/evidence/google-account-data-recovery-2026-08-29.json) | Google identity alias 기반 관리자 데이터 복구의 백업·가드 트랜잭션·운영 DB·공개 런타임·Pages 승격 증거(실제 Gmail callback 미완료) |
|
||||
| [`ops/evidence/google-account-live-browser-proof-2026-08-29.json`](./ops/evidence/google-account-live-browser-proof-2026-08-29.json) | 기존 실서비스 인증 세션의 `yunchan8804@gmail.com`·관리자 권한·온보딩 비전환·복구 데이터 가시성 및 현행 단일 Google CTA 검증 증거 |
|
||||
| [`ops/evidence/workbook-runtime-sync-2026-08-29.json`](./ops/evidence/workbook-runtime-sync-2026-08-29.json) | 개선관리 완료본의 현행 API·Pages 증거 동기화, 전 시트 수식·시각 검증과 완료 8·검토 3 유지 기록 |
|
||||
| [`ops/evidence/workbook-req001-completion-sync-2026-08-29.json`](./ops/evidence/workbook-req001-completion-sync-2026-08-29.json) | REQ-001 완료 승격 뒤 개선관리 완료본 6시트·11요구·38수식·수식 오류 0·완료 9/검토 2와 C-001 `pending-valid` 재생성 증거 |
|
||||
| [`ops/evidence/avatar-decode-audit-2026-08-29.json`](./ops/evidence/avatar-decode-audit-2026-08-29.json) | 아바타 source union 93개·52,973 bytes·inventory SHA와 실제 decode 3 정상/90 실패, 현 DB 참조 2 정상/6 실패를 비식별 집계로 고정한 증거 |
|
||||
| [`ops/evidence/avatar-image-resilience-recovery-2026-08-29.json`](./ops/evidence/avatar-image-resilience-recovery-2026-08-29.json) | 공개 아바타 404 원인·시점 복구·인증 브라우저 DOM과 공통 이미지 fallback 후보의 privacy-safe 증거 |
|
||||
| [`ops/evidence/req008-reboot-preflight-2026-08-29.json`](./ops/evidence/req008-reboot-preflight-2026-08-29.json) | REQ-008 실제 재부팅 전 read-only task/runtime 사전점검 스냅샷 — 현재 public outage와 실제 reboot smoke가 이를 대체·완료하지 않음 |
|
||||
| [`ops/evidence/professor-summary-gap-deploy-2026-08-29.json`](./ops/evidence/professor-summary-gap-deploy-2026-08-29.json) | 교수자 검토 큐·요약 카드 12px 분리의 RED→GREEN·7폭/전체 시각 게이트·clean Pages 배포·실서비스 DOM 증거 |
|
||||
| [`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·엔진 모드 전환·검증 (초안) |
|
||||
|
|
|
|||
38
docs/TODO.md
38
docs/TODO.md
|
|
@ -15,13 +15,16 @@
|
|||
> 이 표는 워크북 ID와 현행 구현을 잇는 얇은 추적표다. 내부 기술 DONE의 focused 증거는
|
||||
> `guides/testing.md`, 상세 계약은 SSOT `dev_dashboard.html`이 소유한다. C-001은 기술 사전검증과 외부
|
||||
> 임상 승인을 분리한다. 검토 패킷과 fail-closed 판정기는 준비됐지만 외부 증거가 없으므로 계속 열린 항목이다.
|
||||
> 완료본은 `outputs/01a04217-f73b-7303-b597-401fa7f5d290/Vignette_개선관리_완료.xlsx`, SHA-256
|
||||
> `c832547f30ae0664e54b302c8e9f62cc157f31022ad158d17803d8cac988d8bd`다. 집계는 **완료 9·검토 2·총 11**이며
|
||||
> 검토는 C-001 적격 외부 임상 검수와 REQ-008 실제 Windows 재부팅뿐이다.
|
||||
|
||||
| ID | 상태 | 현행 경계 |
|
||||
|---|---|---|
|
||||
| C-001 | **OPEN · 외부 GATE** | v2 P1 합성 사례 6건·상태별 canonical 판정기와 109·수단 상세 차단·런타임/carry-over/output `ideation_stage <= 3`·4개 공식 source 기술 사전검증은 완료. 사례별 5필드 30개, 청소년 답변 6개, 검토자·소속·자격·검토일·결정 근거·서면 증거/해시·검토 버전/사례 해시를 포함한 승인 10필드가 있어야 닫는다. 현재 `review_complete=false`; F절/B4에서 추적한다. |
|
||||
| C-002 | **DONE · 내부 기술** | 첫 회기 라포 반영·한 초점 열린 질문 체크리스트, 근거 turn 연결, 이후 회기 not-applicable |
|
||||
| C-003 | **DONE · 내부 기술** | 종료 회기 4건 미만 insufficient, 이후 최다 페르소나 비중 0.75 이상 훈련 집중 주의; 임상·공정성 판정 아님 |
|
||||
| REQ-001 | **READY · 실제 Gmail callback GATE** | provider가 이메일을 검증한 모든 Google 계정은 도메인·사전등록 없이 learner·approved로 즉시 로그인하며 Google 계정 선택기(`prompt=select_account`)를 유지하고 `hd`를 보내지 않는다. Pages production `ef48c0ae…` 승격 뒤 public custom domain은 단일 `Google 계정으로 로그인` CTA 1개·구 선택 버튼 0·dev login 0·console warning/error 0이고 공개 OAuth 시작 E2E 1/1을 통과했다. 실제 Gmail callback과 복구 계정의 관리자 진입·기존 21회기/64턴/평가 6건 확인 전에는 개선관리 상태를 `검토`로 유지한다. |
|
||||
| REQ-001 | **DONE · 운영 수락** | provider가 검증한 모든 Google 계정은 도메인·사전등록 없이 learner·approved로 로그인한다. Pages `0c60261e…`의 단일 Google CTA·공개 Playwright 2건, 기존 Gmail 인증 세션의 로그인 이메일·관리자 진입·온보딩 비전환·복구 데이터 가시성을 확인했고 소유자가 수락했다. 현재 public API 장애는 별도 운영 복구 게이트이며 이미 닫힌 수락 증거를 지우지는 않는다. |
|
||||
| REQ-002 | **DONE · 내부 기술** | admin protocol draft→active→retired, 라이선스 C/D 외부 LLM 금지, evaluator-only RAG·rollback; migration 17 owner-run/readiness-only |
|
||||
| REQ-003 | **DONE · 내부 기술** | 페르소나 주호소가 JSON 키 순서나 surface 상태보다 우선 |
|
||||
| REQ-004 | **DONE · 내부 기술** | learner 현재 설정 AND 세션 snapshot. OFF는 AI 파생 API 403/UI 무요청·혼합 결과 redaction, 사용자 입력·privacy 보존; teacher/admin 유지 |
|
||||
|
|
@ -30,27 +33,40 @@
|
|||
| REQ-007 | **DONE · 내부 기술** | 회기 종료를 평가보다 먼저 영속화해 옛 평가 실패 뒤에도 새 회기 생성·턴 진행 가능 |
|
||||
| REQ-008 | **READY · 실제 재부팅 GATE** | 원본 응답 생성 실패는 기본 엔진+live-client readiness, 구조화 done 없는 SSE EOF의 `client_stream_incomplete` 복원과 공개 P20 실제 응답으로 닫았다. 후속 재부팅 복구는 전역 mutex·단계별 상한·프로세스 트리 종료·전체 다운 즉시복구와 PowerShell 5.1 timed `WaitForExit`의 process handle 선확보를 적용했다. 실제 PS5.1 계약 32 passed, detached-clean `44b7835c…`·tree `06133249…`, 5174 자동복구·HTTP 200, watchdog result 0/failcount 0까지 확인했지만 실제 Windows 재부팅 smoke 전에는 개선관리 상태를 `검토`로 유지한다. PC 비종속 상시 호스트 이전도 별도 운영 게이트다. |
|
||||
|
||||
현재 공개 API/engine/STT/TTS/cloudflared와 Pages production
|
||||
`ef48c0ae-374e-41fc-b6fd-03f69b26e946`의 서비스 소스는 detached-clean `dba9b75a…`·tree `14cd4607…`다. 재부팅 복구용
|
||||
로그온·watchdog 작업도 같은 root·commit·tree·script SHA로 재등록했고,
|
||||
60분 상한·`IgnoreNew`·복구 mutex·전체 다운 즉시복구·PowerShell 5.1 build 종료코드 판정을 고정했다.
|
||||
watchdog의 명시 복구 실행 뒤 웹 5174 HTTP 200, `LastTaskResult=0`, failcount 0과 local/public
|
||||
`status=ok·db=true·engine=true`를 확인했다.
|
||||
새 학습자 라포 헤더와 단일 Google 로그인 번들은 custom domain에 공개됐다. 실제 Gmail callback과 Windows 재부팅 smoke는 별도 게이트다.
|
||||
2026-08-29 최신 read-only 상태에서 정적 public Web과 Pages `0c60261e…`(source `5bf89ff…`)는 유지되지만,
|
||||
local API 8001은 연결 거부, public API는 530이며 Docker daemon이 없어 DB/API는 **OUTAGE**다. task는 old
|
||||
detached `dba9b75a…` runtime을 계속 가리킨다. 과거 `status=ok·db=true·engine=true`와 watchdog result 0은 이전 성공
|
||||
증거이지 현재 GREEN이 아니다. avatar fail-closed 코드와 upload receipt는 아직 후보이고 배포하지 않았다.
|
||||
|
||||
---
|
||||
|
||||
## A. 배포·공개 런타임 게이트 [환경]
|
||||
> 출처: `ops/backlog-2026-06-26.md` B2 · 대시보드 운영 탭 · `ops/tailscale-vnet-runtime-2026-06-27.md` · `ops/public-runtime-watchdog.md` · `HANDOFF.md`
|
||||
> 출처: `ops/backlog-2026-06-26.md` B2 · 대시보드 운영 탭 · `ops/tailscale-vnet-runtime-2026-06-27.md` · `ops/public-runtime-watchdog.md` · `ops/handoff-goal-production-2026-08-29.md`
|
||||
|
||||
- [ ] **public API/DB 복구와 avatar fail-closed 정식 승격** — 허용 source union은 93 objects·52,973 bytes,
|
||||
inventory SHA-256 `9d703126f78d4fc8330408835d76a7d680276240dc578d6fc9ca420c2f25e6aa`다. 실제 decode는 정상 3·실패 90,
|
||||
현 DB 참조는 정상 2·실패 6·missing 0이다. 후보는 손상 bytes/DB reference를 private forensic으로 보존하고
|
||||
public static 응답을 404/fallback으로 막으며 decode-valid 객체만 immutable public cache에 제공한다. 신규 UUID 업로드도
|
||||
URL 기록·현재 참조·GET/HEAD에서 다시 full decode하고, 업로드 뒤 손상은 current invalid/health fallback에 포함한다. 전체 API/Web/
|
||||
PowerShell 회귀, 실제 브라우저 fallback, exact target/total/digest receipt, 후보 SHA와 사용자 배포 승인 전에는 Docker·
|
||||
API/tunnel·task·DB avatar URL·stable upload root를 바꾸지 않는다.
|
||||
현재 Docker engine은 꺼져 있고 public watchdog이 활성이라 daemon 시작 즉시 기존 runtime 복구가 개입할 수 있다. 풀스택 E2E는
|
||||
watchdog 일시 중지·비활성화, 고유 DB/container/volume과 56432/58000/55173 포트, exact cleanup, Docker·watchdog 원상복구를
|
||||
명시 승인받은 뒤 `layout-visual-gate` 15/15와 `session-layout` 8/8을 실행한다.
|
||||
- [ ] **내장 브라우저 G8 사람 게이트 E2E** — 명시적 승인·보류(`keep_quarantine`)·반려(`reject`)와 사유 입력,
|
||||
증거 미충족 승인 차단은 typecheck와 route-fixture desktop/mobile **10/10**을 통과했다. 빠른 3.5초/느린 8초 응답에서도
|
||||
watchdog overlay 0을 확인했다. Codex 내장 브라우저의 격리 local stateful fixture에서도 8.5초 뒤 overlay 0·heading 정상,
|
||||
content `keep_quarantine` 뒤 effect 0, 증거 없는 release 승인 disabled→`reject` 뒤 effect 0, 증거 4종 promote 승인 뒤
|
||||
lifecycle effect 정확히 1을 공동 확인했다. <b>로컬 사람 게이트는 GREEN</b>이지만 실DB append-only 결정과 public proof는
|
||||
아직 없으므로 current 운영 G8 전체를 GREEN으로 올리지 않는다.
|
||||
- [ ] **공개 DB 계정·회기 복구 안정화** — 2026-08-07 18:26 KST owner 승인으로 recovered named volume을
|
||||
공개 `vignette-dev-db:55432`에 전환했다. cutover 기준 users 84/sessions 30/turns 705,
|
||||
Google 계정 16/소유 회기 30, orphan 0, health·engine·OAuth·watchdog가 정상이다. 전환 직전 container와
|
||||
dump, 원본 volume을 보존한다. 남은 것은 사용자가 Google 재로그인한 뒤 본인 과거 회기가 보이는지
|
||||
확인하는 일과 자동 백업 운영화다. 기존 인증 세션은 복사하지 않아 재로그인이 필요하다.
|
||||
2026-08-09 Docker Desktop 중단과 18:02:55 KST Docker Desktop UI의 active DB 직접 stop으로 `db=false`가
|
||||
재발했으나 동일 recovered container/volume만 재기동했고, 현재 owner read-only 집계는 users 356/sessions 193/
|
||||
turns 726, Google users 16/Google-owned sessions 31이며 public db/engine true다. watchdog·로그온 boot는 detached
|
||||
재발했으나 동일 recovered container/volume만 재기동했고, 당시 owner read-only 집계는 users 356/sessions 193/
|
||||
turns 726, Google users 16/Google-owned sessions 31이며 public db/engine true였다. watchdog·로그온 boot는 detached
|
||||
clean release의 Git commit/tree와 watchdog|boot/start SHA가 없으면 mutation 전에 실패하도록 코드로 고정했다.
|
||||
공개 API·cloudflared는 detached-clean `a73bcd24…`·tree `b02b3a1b…`로 fresh 승격했고 receipt
|
||||
`47388d58…9b08`가 passed다. local/public health는 `status=ok·db=true·engine=true`, 공개 OpenAPI 126,
|
||||
|
|
|
|||
|
|
@ -291,7 +291,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-08-28 KST</span>
|
||||
<span class="stamp">2026-08-29 KST</span>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
|
|
@ -300,11 +300,13 @@
|
|||
<section class="pulse">
|
||||
<div class="pulse-main">
|
||||
<span class="eyebrow">project pulse</span>
|
||||
<h1>지금 위치: <em>G0~G6 · G8 증거 DONE</em>. <em>G7 external proof · C-001 · 재부팅 실증</em>이 남았다.</h1>
|
||||
<p class="pulse-state"><b>현재:</b> 공개 앱·API·DB와 current NAS 실회기는 GREEN이고 재부팅 복구 task 계약도 보강했다. <b>다음:</b> 새 Pages 후보 명시 승인 → 실제 Windows 재부팅 자동복구 → G7 mic·human proof.</p>
|
||||
<h1>지금 위치: <em>공개 정적 Web은 유지</em>, <em>Public API·DB는 OUTAGE</em>. 이미지 fail-closed 후보·E2E와 C-001·REQ-008 사람 게이트가 남았다.</h1>
|
||||
<p class="pulse-state"><b>현재:</b> 배포된 API/task 기준선은 <code>dba9b75a…</code>, Pages는 <code>0c60261e…</code>(source <code>5bf89ff…</code>)지만 Docker daemon 부재로 local 8001은 연결 거부, public API는 530이다. 아바타 decode fail-closed 변경은 후보 코드일 뿐 미배포라 운영 GREEN이 아니다. G8 차단·보류·반려·승인의 격리 local 내장 브라우저 proof는 GREEN이다. <b>다음:</b> 전체 통합 GREEN과 G8 실DB/public proof를 확인한 뒤 push/Pages/runtime/task 전환 승인을 받고, 실제 재부팅은 별도 승인으로 실행한다.</p>
|
||||
<details class="pulse-details">
|
||||
<summary>운영 복구·watchdog·배포 상세 보기</summary>
|
||||
<p class="pulse-state"><b>2026-08-28 재부팅 복구 hardening:</b> 로그온·5분 watchdog을 detached-clean <code>99779a6a…fbc8</code>·tree <code>57da86aa…6b91</code>에 재등록했다. 두 task는 <code>ExecutionTimeLimit=PT1H</code>·<code>MultipleInstances=IgnoreNew</code>이고 scheduler 이중 재시도는 제거했다. launcher는 exclusive mutex, API 180초·voice 90초·web build 600초 상한, timeout 시 parent+child process-tree 종료를 적용했고 watchdog은 cloudflared 누락 또는 API/web/voice-sidecar 전체 다운을 debounce 없이 즉시 복구한다. focused 회귀 <code>50 passed</code>, watchdog 18:10:57 KST·boot 18:14:27 KST 수동 실행 결과 0·failcount 0을 확인했다. 이것은 로그인 뒤 복구 증거이며 실제 Windows 재부팅 smoke와 PC 비종속 상시 호스트 이전은 여전히 별도 gate다.</p>
|
||||
<p class="pulse-state"><b>2026-08-29 최신 read-only 상태:</b> <code>VignettePublicRuntime</code>과 watchdog은 여전히 old <code>dba9b75a…</code> root를 가리키며, Docker Desktop Linux engine pipe가 없어 DB/API 복구에 실패한다. 정적 로그인 Web 200은 동적 API 정상 증거가 아니다. 운영 변경 승인 전에는 Docker 시작·API/tunnel 재시작·task 재등록·DB/avatar URL 수정·stable upload root cutover를 하지 않는다.</p>
|
||||
<p class="pulse-state"><b>2026-08-29 아바타 무결성:</b> 허용 source union은 <code>93 objects · 52,973 bytes · SHA-256 9d703126…e6aa</code>다. 실제 decode는 정상 3·실패 90, 현 DB 참조는 정상 2·실패 6·missing 0이다. 후보는 손상 bytes와 참조를 private forensic으로 보존하되 public serve는 404/fallback으로 막고, decode-valid 객체만 immutable public cache에 싣는다. 이 후보는 전체 API/Web/E2E·내장 브라우저·운영 배포 검증 전까지 GREEN이 아니다.</p>
|
||||
<p class="pulse-state"><b>2026-08-29 재부팅 복구 hardening의 이전 성공 증거:</b> Windows PowerShell 5.1의 timed <code>WaitForExit()</code>가 process handle 선확보 없이는 성공한 web build의 <code>ExitCode</code>를 null로 남겨 실패로 오판하는 경계를 수정했다. 실제 PS5.1 계약 <code>32 passed</code>, detached-clean <code>44b7835c…</code>·tree <code>06133249…</code>에 로그온·5분 watchdog을 같은 핀으로 재등록했다. 당시 watchdog 3회차 복구로 5174가 자동 기동해 HTTP 200을 반환했고 후속 실행은 <code>LastTaskResult=0</code>·failcount 0, local/public health <code>status=ok·db=true·engine=true</code>였다. 기존 <code>ExecutionTimeLimit=PT1H</code>·<code>MultipleInstances=IgnoreNew</code>·exclusive mutex·bounded wait·process-tree 종료·hard-down 즉시복구 계약도 유지한다. 이것은 로그인 뒤 과거 복구 증거이며 현재 장애나 실제 Windows 재부팅 smoke를 대신하지 않는다.</p>
|
||||
<p class="pulse-state"><b>2026-08-27 Docker stale socket 인시던트·복구:</b> 공개 로그인 화면은 유지됐지만 API가 <code>db=false</code> degraded 뒤 502가 됐다. 원인은 Docker Desktop 4.82.0 백엔드가 이전 비정상 종료에서 남은 0바이트 AF_UNIX reparse socket <code>Docker\run\dockerInference</code>와 <code>docker-secrets-engine\engine.sock</code>을 ERROR 1920으로 제거하지 못해 startup crash-loop한 결함이다. Docker 전체 종료 후 두 부모 IPC 디렉터리를 <code>run.stale-20260827-232006</code>·<code>docker-secrets-engine.stale-20260827-232006</code>으로 이동해 복구 가능하게 보존했고, container·image·volume 생성/삭제/교체 없이 기존 <code>vignette-dev-db</code>와 <code>vignette_recovered_prod_20260807</code>만 시작했다. API는 detached-clean <code>bf5f7352…0092</code>·tree <code>c39f917b…c88</code>에서 routine API-only 복구했고 public health 3/3 <code>status=ok·db=true·engine=true</code>를 확인했다. 로그온·5분 watchdog도 같은 root/commit/tree와 exact boot/watchdog/start SHA에 재등록했으며 watchdog <code>LastTaskResult=0</code>·failcount <code>0</code>이다. 실제 공개 로그인 브라우저는 console error/warning 0이다. authenticated public <code>/turn</code>은 2026-08-28 폐루프를 통과했고 실제 Windows 재부팅만 별도 gate다.</p>
|
||||
<p class="pulse-state"><b>2026-08-18 엔진 readiness 장애·수정:</b> 06:30~10:05 KST 공개 API가 <code>engine=false</code>로 약 3.5시간 degraded였다. 원인은 (1) <code>reasoning_effort=high</code> 콜드 프로브가 20초 타임아웃을 넘기면 실패가 성공과 같은 TTL(운영 1800초)로 캐시되는 게이트웨이 결함, (2) 공개 엔진 <code>claude -p</code>가 소유자 Claude 구독을 개발 에이전트 작업과 공유해 사용량 한도 소진 시 생성이 실패하는 운영 경계다. 워치독은 기본 <code>/ready</code>의 캐시 200만 보고 엔진을 healthy로 오판해 API만 재시작하는 루프에 빠졌다. 수정: 실패 전용 <code>ENGINE_READY_FAILURE_TTL_SECONDS</code>(기본 30초) 분리 + 프로브 타임아웃 20→45초, 회귀 4건 신설(게이트웨이 62/62·backend 986). detached-clean <code>4771b97c…c880</code>·tree <code>286723de…c1fa</code>로 API/게이트웨이/워치독·부트 task를 재승격·재등록했고 명시 실행 <code>LastTaskResult=0</code>, 공개 health ok·OpenAPI 126·local voice exact를 재확인했다. 1차 승격은 새 worktree에 <code>apps/api/.env</code>(gitignored 비밀값)가 없어 fail-closed로 실패했다 — 승격 절차에 env 이관 단계가 필요함을 기록한다(약 4분 다운타임).</p>
|
||||
<p class="pulse-state"><b>2026-08-12 인시던트(NAS relay 복구 대기):</b> 공개 API는 health ok·db/engine true·OpenAPI 126·auth 401과 local voice를 유지한다. NAS 프리뷰는 앞선 3/3 <code>degraded·db=true·engine=false</code>에서 15:24 KST local health 3회 타임아웃으로 악화됐고 정적 로그인 화면만 계속 보인다. NAS API의 고정 <code>ENGINE_URL=http://192.168.0.223:9100</code> 대상 listener가 사라졌으며 공개 엔진은 별도 loopback <code>127.0.0.1:9099</code>에서 정상이다. G8 clean-head candidate 112/112·실제 origin 112/112와 rollback receipt는 완료 증거로 보존하지만, 현 NAS runtime은 relay를 source/secret/task에 결속해 복구하고 health 3/3·auth·OpenAPI·assets·실제 회기를 다시 통과할 때까지 DEGRADED다. 자동 재시작·포트 노출·배포는 실행하지 않았다. G7은 별도로 명시 동의 mic·동시 high-water·human evidence가 남는다.</p>
|
||||
|
|
@ -318,9 +320,13 @@
|
|||
<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>126 paths · G1~G8</b></span>
|
||||
<span class="mchip crit">Public API <b>OUTAGE · 530/local refused</b></span>
|
||||
<span class="mchip">이전 OpenAPI 증거 · Public API <b>126 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 crit">Avatar decode <b>3 valid · 90 invalid</b></span>
|
||||
<span class="mchip">Workbook <b>완료 9 · 검토 2</b></span>
|
||||
<span class="mchip good">Human gate local <b>10/10 · IAB GREEN</b></span>
|
||||
<span class="mchip">Human gate <b>실DB/public pending</b></span>
|
||||
<span class="mchip">Decision <b>P0 조건부</b></span>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -344,7 +350,7 @@
|
|||
<div class="ocol">
|
||||
<div class="ocol-h">🚧 GO를 막는 블로커 <span class="ct" id="ct-block">0</span></div>
|
||||
<div class="ostack" data-owner-col="block">
|
||||
<article class="ocard" data-owner="1"><div class="otop"><span class="otag block">블로커</span><span class="oblocks">Web production 승격</span></div><h3>Cloudflare Pages production 배포 승인</h3><p class="ask">필요: 준비된 <code>99779a6a…fbc8</code> Web 후보를 Cloudflare Pages production에 업로드한다는 명시 승인.</p><p class="why">왜 당신인가: 저장소 코드와 번들을 외부 배포 서비스로 전송하는 운영 변경이다.</p><span class="opt">승인 문구: Cloudflare Pages production 배포 승인</span></article>
|
||||
<article class="ocard" data-owner="1"><div class="otop"><span class="otag block">블로커</span><span class="oblocks">Web/API/runtime 승격</span></div><h3>통합 후보 정식 배포 승인</h3><p class="ask">필요: 전체 API/Web/PowerShell 회귀와 내장 브라우저 E2E가 GREEN이고 exact commit SHA가 고정된 뒤, push·Cloudflare Pages·public API/tunnel·두 task·upload root cutover 범위를 한 번 더 확인한다.</p><p class="why">왜 당신인가: 외부 배포와 이 PC 운영 런타임·예약 작업을 함께 바꾸는 운영 변경이다. 현재 public API 장애가 있어도 승인 전 mutation은 금지한다.</p><span class="opt">승인 전제: 후보 SHA·다운타임·보존 범위·rollback 영수증 제시</span></article>
|
||||
<article class="ocard" data-owner="1"><div class="otop"><span class="otag block">게이트</span><span class="oblocks">Phase 3 파일럿 · 데이터</span></div><h3>한신대 데이터/SSO 거버넌스</h3><p class="ask">필요: SSO claim 스키마, 추가 축어록 수급, 미성년 원본 활용동의, 개인정보 처리방침에 대한 한신대/데이터 steward의 written evidence.</p><p class="why">왜 당신인가: 기관 공문·협의 영역. 로컬 문서 골격은 준비됨(<code>hanshin-data-governance-gate.md</code>).</p></article>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -365,7 +371,7 @@
|
|||
<div class="ocol">
|
||||
<div class="ocol-h">🤝 외부·임상팀 조율 <span class="ct" id="ct-ext">0</span></div>
|
||||
<div class="ostack" data-owner-col="ext">
|
||||
<article class="ocard" data-owner="1"><div class="otop"><span class="otag ext">조율</span><span class="oblocks">위기게이트·평가·이론</span></div><h3>임상팀 콘텐츠 (구훈정·어유경)</h3><p class="ask">필요: C1 루브릭 항목·척도·가중치·anchor 예시·컷오프·검수 기준의 임상팀 확정본, CBT 이론별 단계 프롬프트 체인, 위기 스크립트·생명유지서약 문안, H2 평가 골든셋. 코드는 C1 schema/loader/validation scaffold까지만 준비됨.</p><p class="why">왜 당신인가: doc3 R&R상 콘텐츠는 임상팀 산출물. 코드는 '편집 가능 구조'만 선제 구축 → 조율·수급 필요.</p></article>
|
||||
<article class="ocard" data-owner="1"><div class="otop"><span class="otag ext">조율</span><span class="oblocks">C-001·위기게이트·평가·이론</span></div><h3>임상팀 콘텐츠·적격 외부 검수 (구훈정·어유경)</h3><p class="ask">필요: C1 루브릭·CBT 체인·위기 문안·H2 골든셋의 임상팀 확정본과, C-001 실제 검수자의 이름·소속·자격·검토 시각, 사례별 판정·근거·수정 요구·재검토·서명 증거를 package/version/hash에 결속한다. 외부 입력 46칸은 현재 의도적으로 공란이다.</p><p class="why">코드·내부 브라우저·운영 배포로 대신할 수 없다. 검수자 증거 없이 <code>review_complete=true</code>를 만들거나 워크북을 완료로 조작하지 않는다.</p></article>
|
||||
<article class="ocard" data-owner="1"><div class="otop"><span class="otag ext">거버넌스</span><span class="oblocks">계약·일정</span></div><h3>스택 정합성·저작권 등재 (L1)</h3><p class="ask">방향은 Node.js 교체 가능성 우선으로 정했다. 남은 일은 FastAPI 유지 사유, 계약 우선 전환 계획, 저작권 등재 문서 반영을 거버넌스 증거로 남기는 것이다.</p><p class="why">권고: <code>docs/decisions/backend-node-transition.md</code> 기준으로 신청서/등재 문서의 스택 설명을 정합화한다.</p></article>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -694,11 +700,15 @@
|
|||
<p class="dg-note">211차 복구·재검증(2026-08-18 15:05 자동화): 엔진 구독 한도 리셋 직후 NAS 엔진 릴레이 실제 1회 시작이 4종 SHA pin 그대로 <b>passed</b>(영수증 receipt-actual-20260818.json, PID 65328, ready 실생 통과). NAS→릴레이 TCP가 막혀 있던 것은 Python311 인바운드 허용 규칙이 Public 프로필뿐인데 이더넷이 Private이라서였고, LAN 서브넷 한정 9100 허용 규칙 추가(소유자 UAC 승인) 뒤 NAS origin health <b>3/3 연속 status=ok·db·engine true</b>, auth 401, OpenAPI 126을 실측 — 2026-08-12 이후 처음이다. current GREEN 표기는 actual-origin 실제 회기 turn→review smoke가 남아 보류. 같은 자동화의 직렬 게이트 재실행은 <b>31 passed/14 failed/12 did-not-run</b>로 실패: 파서 수정 후 첫 실행된 breakpoint 실측 스윕이 settings/learner 협폭 결함 91건을 보고했고(아침 실행에선 연쇄 중단으로 미실행), session-mvp·persistence·voice 실패군은 아침 동일 웹 코드 통과에 근거해 스택·데이터 드리프트가 의심된다 — 원인 조사로 이관, 가짜 증거 금지로 게이트 pending 유지. 증거는 <code>docs/ops/evidence/nas-relay-recovery-2026-08-18.json</code>·<code>usecase-tdd-2026-08-18.json</code>이다.</p>
|
||||
<p class="dg-note">212차 핫픽스(2026-08-18 오후): 공개 <code>POST /sessions/{id}/end</code> 503(소유자 실사용 보고)의 원인은 회기 종료의 pinned_fact upsert가 <b>learner 역할 연결</b>로 실행된 것이었다. <code>app.pinned_fact</code> RLS는 learner에 INSERT(소유 케이스)만 허용하고 SELECT/UPDATE는 AI 컨텍스트·admin/instructor 전용이라 <code>SELECT … FOR UPDATE + INSERT … ON CONFLICT DO UPDATE</code> 조합이 충돌 후보 가시성에서 <code>InsufficientPrivilegeError</code>로 실패하고 fallback 차단이 503으로 노출됐다. 공개 DB 롤백 재현으로 단순 INSERT는 통과·ON CONFLICT 조합은 거부·<code>ai_view=evaluator</code> 컨텍스트는 전체 upsert 통과를 실증했고(<code>bf5f7352</code>), pinned_fact 저장만 같은 learner uid의 AI evaluator 뷰 연결로 분리했다. 검증: 관련 62 passed·app <b>924 passed</b>, 공개 8001 승격 후 health ok. 엔진이 건강해 deep 평가가 처음 팩트를 반환한 순간 발화한 잠복 결함으로, 아침 degraded 구간엔 팩트가 비어 이 경로가 실행되지 않았다.</p>
|
||||
<p class="dg-note">213차 복구·전환(2026-08-18 심야): 대청소 사건(node_modules·구 런타임 워크트리·Temp 소실)과 Docker Desktop 사망·whisper 9882 WinNAT 포트 예약·DHCP LAN IP 회수(223→224)가 연쇄 발생했다. 릴레이를 현행 루트 <code>bf5f7352</code>에 같은 4종 핀으로 재기동(r3 영수증)하고 <code>192.168.0.224:9100</code>으로 바인딩(방화벽 규칙 재발행), NAS는 실행 컨테이너 env 전량을 <code>.env(0600)</code>로 재구성해 ENGINE_URL 224 적용 후 api만 재생성했다. 클로드 예산 한도(Reached maximum budget $0.5)가 재발해 NAS ENGINE_MODE를 <code>agy_cli</code>(gemini)로 전환 — 공개는 소유자가 이미 관리자 UI로 agy 전환 완료. 결과: NAS origin health <b>3/3 status=ok·db·engine true</b>, auth 401, OpenAPI 126, <b>actual-origin 실회기 turn→review smoke 통과</b>(agy 턴 2개→종료 200→리뷰 200) — 2026-08-12 이후 처음으로 201차 current GREEN 조건을 모두 충족했다. 회기 종료 503 핫픽스(<code>bf5f7352</code>)도 소유자 실세션 종료 성공으로 운영 검증. 권고: 공유기 DHCP 예약(호스트 MAC), Docker VM 클럭 동기화(호스트 대비 ~4일 선행 — DB 타임스탬프 오염), NAS 로컬 agy 이전. 증거는 <code>docs/ops/evidence/nas-relay-recovery-2026-08-18.json</code>이다.</p>
|
||||
<p class="dg-note">214차 개선관리 워크북 동기화(2026-08-27): <b>C-002·C-003과 REQ-001~008은 내부 기술 구현 DONE</b>이다. 첫 회기 체크리스트와 종료 회기 기반 훈련 노출 지표를 추가했고, 외부 연구참여자 사전등록은 create에서 <code>pending</code> 고정 뒤 별도 PATCH 승인으로 분리했다. 프로토콜 레지스트리는 <code>draft→active→retired</code>, 라이선스 C/D의 외부 LLM 금지, evaluator-only RAG 색인·rollback을 강제하며 migration 17은 owner-run이고 startup은 readiness-only라 runtime DDL을 하지 않는다. 주호소는 surface보다 우선하고, 새 회기는 <code>persona_id/version</code>을 고정한다. 학습자 AI 피드백은 <b>현재 계정 설정 AND 세션 snapshot</b>일 때만 노출하며 OFF에서는 순수 파생 API 403·기존 share 404·혼합 응답 AI 필드 redaction·UI 파생 endpoint별 요청 0회를 적용한다. 축어록·저장 워크시트·outcome/calibration 입력·multimodal 동의/철회/삭제/raw audio/privacy 원장은 보존하고 alliance는 self-scores-only로 보여주며, privacy 삭제 조작면은 44px 이상·가로 overflow 0을 유지한다. teacher/admin 감독 뷰는 유지한다. 옛 회기 평가 실패는 degraded 리뷰로 남아도 새 회기를 막지 않는다. 헬스는 기본 엔진과 live-client를 함께 확인하고 구조화 <code>done</code> 없는 EOF는 <code>client_stream_incomplete</code>다. 상담자/내담자 이름은 prompt와 구조화 결과에서 <code>[COUNSELOR]</code>/<code>[CLIENT]</code>로 역할 마스킹한다. <b>C-001은 내부 안전 사전검증만 완료</b>했으며 임상 검토자 이름·소속 기관·검토일·서면 결정이 없어 외부 GATE로 계속 연다.</p>
|
||||
<p class="dg-note">214차 개선관리 워크북 동기화(2026-08-27, 2026-08-29 최종 재생성): <b>C-002·C-003과 REQ-001~007은 완료</b>다. 기존 Gmail 인증 세션의 로그인 이메일·관리자 권한·온보딩 비전환·복구 데이터 가시성을 소유자가 수락해 REQ-001을 완료로 승격했다. REQ-008은 실제 내담자 응답과 복구 task 계약은 통과했지만 실제 Windows 재부팅 smoke가 남아 검토다. 완료본은 6시트·11요구·38수식·수식 오류 0, <b>완료 9·검토 2</b>다. <b>C-001은 내부 안전 사전검증만 완료</b>했으며 적격 외부 검수자의 46개 입력과 서명 증거가 없어 <code>pending-valid</code> 외부 GATE로 유지한다. 외부 검수 입력을 만들거나 완료로 조작하지 않는다.</p>
|
||||
<p class="dg-note">215차 적용(2026-08-28): 관리자 Provider·모델별 원장에서 <code>gemini-3.7-flash-high</code> 토큰은 있는데 비용이 <code>미산정</code>이던 원인을 최신 모델 누락과 단일 고정 rate card로 좁혀 수정했다. Gemini 3.6/3.7 Flash는 출시일과 2026-08-13~12-31 프로모션, 2027 표준 단가를 호출 시작일 기준으로 선택한다. 기존 0달러 행은 동일 계량 시그니처별로 묶어 요청 단위로 다시 가격화해 200K 입력 경계를 보존하고, 캐시 입력 수가 유실된 과거행은 확정값 대신 <code>≤$0.103489 · 참조 상한</code>, 확정 산정/미산정 혼합은 <code>$…+ · 일부 산정</code>, 상한/미산정 혼합은 <code>미산정 · 일부 상한</code>으로 표시한다. 전체·일별·예산에 비용 확실성을 전파해 불확실한 평균·비중을 숨기고, migration 18의 client turn 기간 인덱스는 <code>CREATE INDEX CONCURRENTLY</code>로 트랜잭션 밖에서 적용해 턴 쓰기를 막지 않는다. 저장된 과거 양수 비용에는 현재 단가 라벨을 덧씌우지 않는다. 화면의 테스트 데이터는 정확한 <code>(e2e, fake-client, input=1, output=1, cost=0)</code> 조합만 원장·예산에서 제외하고 DB 행은 삭제하지 않았다. <b>검증</b>: focused <b>105 passed/1 skipped</b>, app+gateway 전체 <b>1102 passed/1 skipped</b>, 새 빈 PostgreSQL 온라인 마이그레이션+통합 <b>1 passed</b>, release agent <b>31 passed</b>, API type generation/check·typecheck·build, 관리자 desktop/mobile <b>6 passed</b>. 격리 DB는 자동 삭제했고 공개 배포는 하지 않았다.</p>
|
||||
<p class="dg-note">216차 REQ-001 로그인 단순화(2026-08-29): 실제로 같은 <code>startOAuth</code>를 호출하던 학교용·다른 계정용 중복 버튼을 단일 <b>Google 계정으로 로그인</b> CTA로 통합했다. 보조문구는 <b>모든 Google 계정</b>이며 Google authorize의 <code>prompt=select_account</code>는 남겨 공유 기기에서도 계정을 고를 수 있게 하고 특정 Workspace 도메인을 강제하는 <code>hd</code>는 보내지 않는다. Google이 검증한 이메일은 도메인·사전등록 제약 없이 즉시 승인되는 기존 서버 계약을 유지한다. <b>검증</b>: auth API <b>41 passed</b>, 직접 영향 desktop/mobile <b>32 passed</b>, 인증 시각·경계폭 <b>2 passed</b>, 7폭 전체 레이아웃 <b>15 passed</b>, 세션 무회귀 <b>8 passed</b>, typecheck·lint·build 통과. 이 단일 CTA 번들은 아직 공개 미배포라 현재 공개 Pages의 기존 2버튼 DOM을 새 증거로 대체하지 않는다.</p>
|
||||
<p class="dg-note">217차 REQ-008 복구 판정 교정(2026-08-29): Windows PowerShell 5.1에서 web build process handle을 먼저 열지 않고 timed <code>WaitForExit()</code>를 호출하면 정상 종료 뒤에도 <code>ExitCode=$null</code>로 남는 런타임 경계를 제거했다. handle 선확보→bounded wait→refresh→null/nonzero guard 순서를 실제 PS5.1 계약 <b>32 passed</b>로 고정하고, detached-clean <code>44b7835c…</code>·tree <code>06133249…</code>에 boot/watchdog을 같은 핀으로 재등록했다. watchdog 복구로 web 5174 HTTP 200, 후속 <code>LastTaskResult=0</code>·failcount 0, local/public health <code>status=ok·db=true·engine=true</code>를 확인했다. 개선관리 완료본 SHA <code>0a64d1bc…1177</code>와 C-001 기술 증거 원장의 14개 artifact SHA도 14/14 일치시켰다. 실제 Windows 재부팅 smoke와 PC 비종속 상시 호스트 이전은 별도 gate다.</p>
|
||||
<p class="dg-note">218차 Google 계정 데이터 복구·API 승격(2026-08-29): Gmail 로그인이 새 learner로 갈라져 정식 관리자 데이터가 사라져 보인 원인은 Google subject별 <code>app_user</code>가 둘로 생성된 것이었다. 이메일 자동병합 대신 owner-managed <code>auth_identity_alias</code>를 추가해 canonical 관리자에 Google subject 2개를 명시적으로 연결하고, 세션에는 실제 로그인 이메일을 별도 기록한다. 원본 백업 SHA <code>c4725500…f2e9</code>를 <code>pg_restore --list</code>로 확인한 뒤 복제 DB에서 복구 트랜잭션을 먼저 통과시켰다. Gmail 중복 계정의 상담 데이터는 0건이지만 설정 1행이 있어 첫 시도는 가드가 전부 rollback했고, 전체 FK 스캔 뒤 겹치는 설정값이 동일함을 확인해 source-only 알림 키만 canonical에 합치고 원본 설정 행은 보존했다. 활성 DB 결과는 canonical <b>21회기·64턴·평가 6건</b>, 별칭 2·옛 Gmail 세션 0·감사로그 1·고아 0이며 사용자 1175·회기 469·평가 65를 유지한다. API+gateway <b>1111 passed/1 skipped</b>, release agent <b>31 passed</b>. detached-clean <code>dba9b75a…</code>·tree <code>14cd4607…</code>에서 API/cloudflared fresh provenance를 발행하고 public health·DB·engine·voice·OpenAPI 129·auth 401을 확인했으며 boot/watchdog을 같은 핀으로 재등록해 <code>LastTaskResult=0</code>·failcount 0이다. 브라우저의 옛 Gmail 세션은 reload 후 <code>/login</code>으로 철회됐고, 실제 Gmail callback은 사용자 계정 선택 후 최종 확인한다.</p>
|
||||
<p class="dg-note">219차 단일 Google 로그인 Pages 승격(2026-08-29): detached-clean <code>dba9b75a…</code>에서 typecheck·lint·cosmetic safety와 2회 byte-identical production build를 통과하고 최근 production 5개의 lazy asset 115개를 보존해 714파일을 Pages production <code>ef48c0ae-374e-41fc-b6fd-03f69b26e946</code>로 승격했다. custom domain은 새 entry <code>index-B1rSWL_W.js</code> SHA <code>f8411694…b5bf</code>, login chunk <code>Login-DNNQ624T.js</code> SHA <code>f010f764…9c1d</code>가 clean build와 일치하며, 단일 <b>Google 계정으로 로그인</b> CTA 1개·구 계정 선택 버튼 0·dev login 0·console warning/error 0이다. 격리 Playwright의 공개 OAuth 시작 계약도 <b>1 passed</b>이고 이전 entry는 JavaScript MIME 200으로 보존된다. 배포별 preview origin은 같은 정적 자산을 받지만 서버 연결 진단을 표시하므로 runtime GREEN으로 기록하지 않는다. <b>REQ-001은 실제 Gmail callback과 복구 계정의 관리자·21회기·64턴·평가 6건 확인 전까지 검토</b>다.</p>
|
||||
<p class="dg-note">220차 개선관리 완료본 현행 증거 동기화(2026-08-29): <code>Vignette_개선관리_완료.xlsx</code>의 상태·수식·서식은 유지하고 <code>개발·기능 이슈!M5:M12</code>와 <code>임상·교육 이슈!M6:M7</code>의 오래된 API/Pages 배포 근거만 현재 <code>dba9b75a…</code>·tree <code>14cd4607…</code>·Pages <code>ef48c0ae…</code>로 갱신했다. artifact-tool import→values-only edit→export→reimport 뒤 6개 전 시트와 수정 집중 4개 렌더를 직접 확인했고 수식 오류 0, 완료 8·검토 3·총 11, 임상 checker <code>pending_external_review</code>·<code>review_complete=false</code>를 유지한다. 새 완료본 SHA는 <code>aab6ef52…e26a</code>다. 기존 C-001 사전검증 원장은 당시 14개 해시의 역사 증거로 보존하고, 현행 원장에서 기술 산출물 13/13 불변과 새 워크북 해시를 결합해 현재 패키지 14/14 연속성을 다시 확인했다. 실제 Gmail callback·Windows 재부팅 smoke·외부 임상 승인 없이 상태를 완료로 올리지 않았다. 기계 판독 증거는 <code>docs/ops/evidence/workbook-runtime-sync-2026-08-29.json</code>이다.</p>
|
||||
<p class="dg-note">221차 교수자 콘솔 표면 간격 교정·공개 승격(2026-08-29): <code>/teach</code>의 검토 큐와 교수자 요약이 독립 Surface인데 부모 <code>.pf-signal-strip</code>의 <code>gap:0</code> 때문에 테두리·그림자가 맞붙던 원인을 <code>--sp-3</code>=12px로 교정했다. 수정 전 0px를 다시 주입해 새 회귀 검사가 RED가 되는 것을 확인하고 복원 뒤 focused <b>1 passed</b>, 390~1440 7폭 교수자 gate <b>1 passed</b>, 전체 layout visual <b>15 passed</b>, session 무회귀 <b>8 passed</b>, typecheck·design SSOT·lint·build를 통과했다. tracked-clean <code>5bf89ff4…</code>·tree <code>29f76aeb…</code>에서 599파일 build 2회 byte-identical, 이전 자산 172개 보존 후 Pages production <code>0c60261e-cb37-482d-ba42-d91586194c48</code>로 승격했다. 실서비스 1425×1272 DOM은 computed/실측 간격 12px·overflow 0·console warning/error 0이고 custom/preview의 <code>Professor-BfR874KA.css</code> SHA <code>63dbe2c7…7fff</code>가 clean build와 일치한다. 증거는 <code>docs/ops/evidence/professor-summary-gap-deploy-2026-08-29.json</code>이다.</p>
|
||||
<p class="dg-note">222차 REQ-001 완료·개선관리 재생성(2026-08-29): 기존 실서비스 인증 세션은 <code>yunchan8804@gmail.com</code>으로 설정·관리자 콘솔에 접근하고 온보딩으로 되돌아가지 않으며, 학습 이력 20건·리뷰 필요 6건을 표시했다. 소유자가 복구 데이터 가시성을 수락했고 최신 Pages <code>0c60261e…</code>의 단일 Google CTA 1개·구 선택 버튼 0·dev login 0·무제한 도메인·공개 Playwright 2 passed를 함께 확인해 REQ-001을 완료로 승격했다. 개선관리 완료본은 artifact-tool 구조 추출로 <b>6시트·11개 고유 요구·38수식·오류 0·완료 9/검토 2</b>를 확인했다. C-001은 패키지 8/8·기술 사례 6/6·canonical checker 정상의 <code>pending-valid</code>이며 외부 입력 46칸은 공란으로 보존했다. 완료본 SHA는 <code>c832547f…d8bd</code>, sidecar SHA는 <code>8c9618f0…a610</code>이고 REQ-008 실제 Windows 재부팅 smoke와 C-001 외부 임상 승인만 검토로 남는다.</p>
|
||||
<div class="dg-principles" aria-label="디자인 생성 가드레일">
|
||||
<div><b>래스터만 사용</b><span>이미지 생성 도구 산출물은 PNG 기반 시안이다. SVG·벡터·와이어프레임·로고 시트로 해석하지 않는다.</span></div>
|
||||
<div><b>기능 우선</b><span>메인 라우트의 실제 액션과 정보 구조를 먼저 반영한다. 장식은 기능을 가리지 않는 수준에서만 쓴다.</span></div>
|
||||
|
|
@ -1109,7 +1119,7 @@
|
|||
<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>2026-08-28 현재 수집은 <b>1138 tests / 62 files</b>다. 이 숫자는 수집량이며 현 작업트리 전체 GREEN과 동일하지 않다. G8 clean-head release gate는 candidate 112/112와 실제 NAS-origin 112/112를 통과했다. 이전 단일 120/120과 2026-07-15의 fixture desktop/mobile 166/166 + DB/engine/provider 직렬 49/49 = 215/215는 범위가 다른 역사 기준선으로 보존한다.</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-29 현재 수집은 <b>1158 tests / 64 files</b>다. 이 숫자는 수집량이며 현 작업트리 전체 GREEN과 동일하지 않다. G8 clean-head release gate는 candidate 112/112와 실제 NAS-origin 112/112를 통과했다. 이전 단일 120/120과 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>
|
||||
|
|
@ -1119,11 +1129,11 @@
|
|||
<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>개선관리 C-002·C-003 / REQ-001~008</td><td><code>pytest app/ -q</code> / <code>pytest engine_gateway/ -q</code> / 항목별 focused pytest·DB/browser E2E / <code>npm run check:api-types</code> / <code>npm run build</code></td><td><b>내부 기술 DONE.</b> 2026-08-28 최종 API 1002 passed, gateway 68 passed, Ruff·API type check·typecheck·lint·production build PASS. focused: C-002 3, C-003 11, REQ-001 auth 41 + callback access-log redaction 6 + OAuth UI desktop/mobile 4 + route-mock desktop/mobile 2 + 실제 관리자 DB/browser lifecycle 1, REQ-002·005 26(경고 1) + protocol 실제 DB lifecycle 1 + persona 실제 DB/browser lifecycle 1, REQ-003 34, REQ-004 직접 정책 85·관련 route/read-model 163·desktop/mobile 2, REQ-006 24, REQ-007 관련 163·live/session 50·DB E2E 1, REQ-008 health 1 passed. Google OIDC는 provider-verified 이메일을 도메인·사전등록 없이 learner·approved로 허용하고 suspended는 보존한다. 실제 관리자 E2E는 승인 전 403/대기→승인 후 동일 세션 200→피드백 OFF 영속 재조회→비활성화·세션 0을, persona E2E는 작성→승인→catalog→주호소→exact ID/version pin→controlled SSE→DB 2턴과 exact cleanup/설정 원복을 확인했다. 최종 레이아웃 재실행은 시각 게이트 15/15·포커스 106/106·세션 8/8, 실패·skip 0이며 격리 포트와 프로세스를 모두 종료했다. 전체 Playwright 현 작업트리 GREEN 수치로 환산하지 않는다.</td></tr>
|
||||
<tr><td>개선관리 C-001 외부 임상 승인</td><td><code>scripts/check-clinical-crisis-review.py</code> / <code>run-clinical-crisis-technical-observations.py</code> / <code>data/clinical/p1-crisis-review-cases.json</code> / <code>docs/ops/clinical-crisis-protocol-review-2026-08-27.md</code></td><td><b>내부 기술 READY · 외부 GATE 미완료.</b> v2 P1 합성 사례 6건과 상태별 canonical fail-closed 판정기를 유지한다. 생성·스트림 실제 경로 6/6, 런타임 패키지 8개 HEAD·작업트리 SHA 일치, focused <b>90 passed</b>·Ruff PASS를 확인했다. 개선관리 완료본은 6번째 <code>C-001 외부검토</code> 시트에서 사례별 5필드 30개·청소년 답변 6개·검토자 자격 포함 승인 10필드·서명 증거를 받고 canonical checker와 패키지 해시가 모두 맞을 때만 <code>완료</code>로 전파한다. 현재 gate는 <code>pending-valid</code>지만 외부 결정 0건·<code>review_complete=false</code>라 10 완료/1 검토/총 11을 유지한다.</td></tr>
|
||||
<tr><td>개선관리 C-002·C-003 / REQ-001~008</td><td><code>pytest app/ -q</code> / <code>pytest engine_gateway/ -q</code> / 항목별 focused pytest·DB/browser E2E / <code>npm run check:api-types</code> / <code>npm run build</code></td><td><b>완료 9 · 검토 2.</b> C-002·C-003과 REQ-001~007은 요구·기술 증거가 완료됐다. 2026-08-29 API <b>1074 passed/1 skipped</b>, gateway <b>68 passed</b>, typecheck와 G8 focused desktop/mobile 10/10이 PASS다. 격리 local Codex 내장 브라우저는 8.5초 overlay 0·heading 정상, content 보류와 증거 없는 release 반려 effect 0, 증거 4종 promote 승인 lifecycle effect 1을 확인했다. 이는 local stateful fixture GREEN이며 실DB/public proof가 아니다. REQ-001은 기존 Gmail 인증 세션의 관리자 권한·온보딩 비전환·복구 이력 가시성을 확인하고 소유자가 수락했다. REQ-008은 공개 P20 실제 응답과 복구 계약을 확인했지만 실제 Windows 재부팅 smoke가 없어 검토다. C-001은 별도 외부 GATE다. 전체 Playwright 현 작업트리 GREEN 수치로 환산하지 않는다.</td></tr>
|
||||
<tr><td>개선관리 C-001 외부 임상 승인</td><td><code>scripts/check-clinical-crisis-review.py</code> / <code>run-clinical-crisis-technical-observations.py</code> / <code>data/clinical/p1-crisis-review-cases.json</code> / <code>docs/ops/clinical-crisis-protocol-review-2026-08-27.md</code></td><td><b>내부 기술 READY · 외부 GATE 미완료.</b> v2 P1 합성 사례 6건과 상태별 canonical fail-closed 판정기를 유지한다. 생성·스트림 실제 경로 6/6, 런타임 패키지 8개 HEAD·작업트리 SHA 일치, focused <b>90 passed</b>·Ruff PASS를 확인했다. 현행 개선관리 완료본 SHA는 <code>c832547f…d8bd</code>이며, 이전 C-001 기술 사전검증 원장의 <code>0a64d1bc…1177</code>과 8/3 동기화본 <code>aab6ef52…e26a</code>은 역사 스냅샷으로 보존한다. 후속 원장은 6시트·11요구·38수식·오류 0과 현재 package match를 기록한다. 6번째 <code>C-001 외부검토</code> 시트에서 사례별 5필드 30개·청소년 답변 6개·검토자 자격 포함 승인 10필드·서명 증거를 받고 canonical checker와 패키지 해시가 모두 맞을 때만 <code>완료</code>로 전파한다. 현재 gate는 <code>pending-valid</code>지만 외부 입력 46칸은 공란이고 <code>review_complete=false</code>다. REQ-001은 완료됐고 REQ-008 재부팅 실증과 C-001 외부 승인만 남아 9 완료/2 검토/총 11을 유지한다.</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-08-28 전체 실행 1002 passed.</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>Backend pytest baseline</td><td><code>pytest -q app</code></td><td>2026-08-29 전체 실행 1074 passed / 1 skipped.</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_admin_usage_postgres.py app/test_usage_report.py engine_gateway/test_provider_registry.py engine_gateway/test_gateway_model.py -q</code> / 격리 PostgreSQL / API type generation/check / web typecheck/build / admin focused E2E</td><td>2026-08-28 focused 105 passed/1 skipped, app+gateway 전체 1102 passed/1 skipped, 새 빈 PostgreSQL 온라인 마이그레이션+통합 1 passed, release agent 31 passed, desktop/mobile E2E 6 passed. Claude CLI SDK 비용 추정값과 호출 시점 Agy/Gemini·Codex·Claude API 공식 참조단가를 분리한다. Gemini 3.6/3.7 유효기간, 요청별 200K 구간, 과거 캐시 미보존 행의 <code>reference_upper_bound</code>, 확정 산정/미산정 <code>partial</code>, 상한/미산정 <code>partial_upper_bound</code>, 전체·일별·예산 certainty, 정확한 <code>(e2e, fake-client, input=1, output=1, cost=0)</code> 원장 제외와 리포트 경고를 고정했다. 동일 계량 시그니처 SQL 집계와 migration 18의 concurrent 기간 인덱스로 90일 원장의 raw-row fetch/sort를 제거하고 배포 중 turn 쓰기 잠금을 피한다. 과거 0/0 Claude 442건은 로컬 JSONL의 실제 usage와 유일 일치한 169건만 백필했고 273건은 <code>token_unmetered_turns</code>로 남겼다. 이번 변경은 로컬 검증만 했고 공개 런타임은 미배포다.</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>
|
||||
|
|
@ -1152,7 +1162,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="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>Admin AI operations subpage</td><td><code>npx playwright test e2e/admin.spec.ts e2e/full-sweep-admin-ai.spec.ts --project=chromium-desktop --project=chromium-mobile --workers=1 --grep="shows detailed AI metering|shows partial AI cost|does not present a partial upper bound"</code> / <code>app/test_admin_usage_postgres.py</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>·<code>일부 산정</code>·<code>일부 상한</code>·<code>미산정</code>을 구분한다. 과거 캐시 미보존 보정값은 <code>≤$…</code>, 확정 산정/미산정 합계는 <code>$…+</code>, 상한/미산정 혼합은 <code>미산정</code>으로 표시한다. 전체·일별·예산의 불확실한 평균·비중은 숨기고 예산은 <code>indeterminate</code>를 표시한다. 2026-08-28 새 빈 PostgreSQL 마이그레이션+통합 1 passed와 desktop/mobile 6 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>
|
||||
|
|
@ -1206,10 +1216,10 @@
|
|||
<tr><td>Public OAuth start</td><td><code>/auth/config</code> + <code>/auth/login?provider=google</code></td><td>2026-08-28 public auth config 200, Google configured true, <code>allowed_email_domains=[]</code>, redirect URI <code>https://api-vignette.chanpaca.net/auth/callback</code>, dev-login disabled. 로그인 DOM은 모든 Google 계정을 명시하고 실제 Google 선택기에 <code>yunchan@twentyoz.kr</code>와 <code>yunchan8804@gmail.com</code>이 함께 노출됐다. 기존 Google 계정 선택→callback→<code>/admin</code> 성공을 확인했다. 신규 Gmail 선택은 Vignette 계정 생성이므로 사용자 행동시점 확인 뒤 별도 실증한다.</td></tr>
|
||||
<tr><td>Persona auth boundary</td><td><code>GET /personas</code></td><td>2026-06-30 복구 후 public unauth <code>/personas</code>는 401 <code>not authenticated</code>를 반환한다.</td></tr>
|
||||
<tr><td>Public login</td><td><code>auth.spec.ts --grep public login</code></td><td>1 passed</td></tr>
|
||||
<tr><td>Public runtime scripts · source pin</td><td><code>start/watch/boot-public-runtime*.ps1</code> · <code>install-public-runtime-task.ps1</code> · <code>register-boot-task.ps1</code></td><td>2026-08-28 detached-clean <code>99779a6a…fbc8</code>·tree <code>57da86aa…6b91</code>에 로그온·watchdog 두 task를 재등록했다. action working directory/root/commit/tree/SHA pin, <code>PT1H</code>, <code>IgnoreNew</code>, scheduler retry 0은 PASS다. focused 회귀 50/50 뒤 watchdog 18:10:57 KST·boot 18:14:27 KST 수동 실행은 모두 <code>LastTaskResult=0</code>, failcount 0이었다. 실제 Windows 재부팅 자동복구 smoke는 별도 운영 gate다.</td></tr>
|
||||
<tr><td>Public API health</td><td><code>http://127.0.0.1:8001/health</code> / <code>https://api-vignette.chanpaca.net/health</code></td><td>2026-08-28 17:11:47 KST local/public 모두 <code>status=ok·environment=prod·db=true·engine=true</code>. voice는 local/public 모두 <code>local_whisper small</code>·<code>melotts-korean</code>·WS queue 4, web은 local/public 200과 동일 asset <code>index-DJPgoeJy.js</code>/<code>index-CXOy3QyV.css</code>를 반환했다. DB는 백업 <code>vignette-dev-db-vignette-20260828-065331Z.dump</code>를 검증하고 recovered named volume을 보존한 채 migration 17을 적용했다.</td></tr>
|
||||
<tr><td>Public runtime current snapshot</td><td><code>health + provenance + Scheduled Tasks + Pages</code></td><td>현재 동작 중인 API/engine/web/STT/TTS/cloudflared와 Cloudflare Pages production <code>1f1ddf18-9d5c-4d83-95a7-8cd71245e646</code>은 clean source <code>2a396361…89c</code> 계열이다. 다음 장애/로그온 복구 task만 <code>99779a6a…fbc8</code>에 pin했다. 새 학습자 헤더를 포함한 dist는 714파일·assets 164개로 빌드하고 최근 production 5세대를 보존했지만, Pages upload는 명시 승인이 없어 실행하지 않았다.</td></tr>
|
||||
<tr><td>Public/local/Tailnet login recovery</td><td><code>https://vignette.chanpaca.net/login</code> / <code>https://api-vignette.chanpaca.net/health</code> / <code>https://alpaca-home.taile93291.ts.net/login</code></td><td>2026-08-28 public web 200, public API health 200, auth config 200, Google login start와 callback 성공, P20 실제 회기·응답·종료·평가 폐루프 성공. Earlier Tailnet checks remain recorded separately; vnet DNS A records are still 0.</td></tr>
|
||||
<tr><td>Public runtime scripts · source pin</td><td><code>start/watch/boot-public-runtime*.ps1</code> · <code>install-public-runtime-task.ps1</code> · <code>register-boot-task.ps1</code></td><td>2026-08-29 최신 read-only 확인에서 두 task는 old detached <code>dba9b75a…</code> runtime을 계속 가리킨다. Docker daemon 부재로 runtime task는 실패했고 watchdog도 DB/API를 복구하지 못한다. 새 total-size/decode receipt 후보는 미배포이며, 후보 통합 GREEN·사용자 승인 전 task 재등록이나 runtime 재시작을 하지 않는다. 실제 Windows 재부팅 자동복구 smoke도 별도 운영 gate다.</td></tr>
|
||||
<tr><td>Public API health</td><td><code>http://127.0.0.1:8001/health</code> / <code>https://api-vignette.chanpaca.net/health</code></td><td><b>2026-08-29 최신 상태는 OUTAGE다.</b> local 8001은 연결 거부, public API는 530이며 Docker Desktop Linux engine pipe가 없다. 정적 public Web 200은 API·DB GREEN을 뜻하지 않는다. 2026-08-28의 <code>status=ok·db=true·engine=true</code>와 voice exact 값은 과거 성공 증거로만 보존한다.</td></tr>
|
||||
<tr><td>Public runtime current snapshot</td><td><code>health + provenance + Scheduled Tasks + Pages</code></td><td>배포된 동적 런타임/task 기준선은 clean commit <code>dba9b75a…</code>·tree <code>14cd4607…</code>, Cloudflare Pages production은 <code>0c60261e-cb37-482d-ba42-d91586194c48</code>(source <code>5bf89ff4…</code>)다. 현재 API/DB는 장애이고 fail-closed avatar 후보와 새 upload receipt는 아직 이 기준선에 승격되지 않았다. 후보와 deployed baseline을 분리하며, 승인 전 Docker/API/tunnel/task/DB/upload-root mutation을 하지 않는다.</td></tr>
|
||||
<tr><td>Public/local/Tailnet login recovery</td><td><code>https://vignette.chanpaca.net/login</code> / <code>https://api-vignette.chanpaca.net/health</code> / <code>https://alpaca-home.taile93291.ts.net/login</code></td><td>2026-08-28 Google callback→관리자·P20 실제 회기 폐루프는 유효한 과거 증거다. 2026-08-29에는 public API 530으로 신규 callback·관리자·G8 실DB E2E를 재검증할 수 없으므로 현재 운영 GREEN으로 재사용하지 않는다. public Web은 정적 로그인 화면만 200이다.</td></tr>
|
||||
<tr><td>Local 5175 login</td><td><code>PLAYWRIGHT_BASE_URL=http://127.0.0.1:5175 auth.spec.ts</code></td><td>desktop/mobile passed</td></tr>
|
||||
<tr><td>Learner/readiness E2E</td><td><code>learner.spec.ts + readiness.spec.ts desktop/mobile</code></td><td>14 passed</td></tr>
|
||||
<tr><td>Learner growth header</td><td><code>uc-learner-home-dashboard.spec.ts --grep "성장 지표 라포 헤더"</code></td><td>2026-08-28 chromium desktop/mobile 2 passed. 라포 문구 부모를 inset surface에서 plain div로 바꾸고 computed background transparent·border/radius/padding 0을 고정했다. typecheck·design SSOT·cosmetic filter safety도 통과했으며 Pages에는 아직 미배포다.</td></tr>
|
||||
|
|
@ -1249,7 +1259,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> 새 학습자 헤더 Cloudflare Pages production upload 명시 승인 · 신규 Gmail 선택→Vignette 계정 생성은 사용자 행동시점 확인 필요 · C-001 사례별 판정·근거·수정 요구·재검토·검토일 30개, 청소년 답변 6개, 임상 검토자 이름·소속·자격·결정 근거를 포함한 승인 10필드와 서명 증거/해시·검토본 고정 · <code>vnet.18ka.net</code>/<code>api-vnet.18ka.net</code> DNS·OAuth redirect 등록 · 한신대 데이터/SSO 거버넌스 · G7 명시 동의 물리 마이크 3,120초와 공통 3,000초 운영 high-water, 독립 human voice-gain pack, canonical checker exit 0 · claude_cli↔Messages API 폴백 동일성 · 실제 Windows 재부팅 후 watchdog smoke · Phase 3 파일럿 게이트. C-001 내부 검토 패킷·canonical 판정기·상한 수리와 <code>pending-valid</code> 검증, current source 공개 배포, generic Google callback, P20 실제 턴·종료·평가, local/public voice exact ready, authenticated WSS 무마이크 rehearsal은 완료했다. 운영 원칙상 가짜 증거로 DONE 표기하지 않는다.</div>
|
||||
<div class="source-note"><b>환경·외부 승인 때문에 아직 못 닫는 항목(정직 표기):</b> public API/DB 장애 복구와 avatar fail-closed 후보의 전체 회귀·배포·실브라우저 fallback · G8 관리자 사람 게이트의 승인형 실DB append-only 증거와 public proof(local stateful in-app browser는 GREEN) · C-001 사례별 판정·근거·수정 요구·재검토·검토일 30개, 청소년 답변 6개, 임상 검토자 이름·소속·자격·결정 근거를 포함한 승인 10필드와 서명 증거/해시·검토본 고정 · <code>vnet.18ka.net</code>/<code>api-vnet.18ka.net</code> DNS·OAuth redirect 등록 · 한신대 데이터/SSO 거버넌스 · G7 명시 동의 물리 마이크 3,120초와 공통 3,000초 운영 high-water, 독립 human voice-gain pack, canonical checker exit 0 · claude_cli↔Messages API 폴백 동일성 · 실제 Windows 재부팅 후 watchdog smoke · Phase 3 파일럿 게이트. REQ-001은 Gmail 인증 세션·관리자·복구 데이터 수락으로 완료했다. C-001은 <code>pending-valid</code> 기술 사전검증뿐이며 외부 검수 입력을 조작하지 않는다. 운영 원칙상 가짜 증거로 DONE 표기하지 않는다.</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>
|
||||
|
|
|
|||
|
|
@ -237,11 +237,25 @@ RBAC×AIView로 차단된다. 이 모듈은 평가 신호만 산출한다.
|
|||
cache key·prompt·completion 없이 enabled/entries/hits/misses/stores/evictions/requests/hit_rate와
|
||||
일별 `daily_cost` bucket(day, turns, tokens, cost)을 관리자 관측값으로 반환한다. 비용은
|
||||
공급자가 반환한 저장값을 우선하되 Claude CLI는 SDK 추정치(`provider_estimate`)로 명시하고,
|
||||
저장 비용이 없는 Agy/Gemini·Codex·Claude API는 `app.services.llm_pricing`의 버전 고정 공식 참조단가(`reference_rate`)로
|
||||
입력·캐시 입력·출력 토큰을 환산한다. 기존 DB에 비용 0으로 저장된 행도 조회 시 같은 단가로
|
||||
보정하며, 단가가 없는 모델은 0달러로 위장하지 않고 `unavailable`로 표시한다.
|
||||
응답은 `recorded_cost_usd`, `estimated_cost_usd`, provider/model별 `cost_basis`,
|
||||
`rate_label`, `rate_source_url`을 함께 반환하고 예산 상태는 두 비용을 합친 유효 비용을 사용한다.
|
||||
신규 Agy/Gemini·Codex·Claude API 호출은 `app.services.llm_pricing`의 호출 시작일 기준 공식
|
||||
참조단가(`reference_rate`)로 입력·캐시 입력·출력 토큰을 환산한다. Gemini 3.6/3.7 Flash는
|
||||
유효기간이 있는 단가 구간을 사용하고, 기존 DB의 비용 0 행은 일별 합산 전에 요청별로 다시
|
||||
가격화해 200K 같은 요청 단위 경계를 보존한다. 과거 행은 캐시 입력 수가 따로 남아 있지 않아
|
||||
전체 입력 단가로 계산한 보수적 상한(`reference_upper_bound`, UI `참조 상한`)으로 표시한다.
|
||||
호출별 확정 산정액과 미산정액이 섞이면 `partial`과 `$…+`로 표시한다. 상한 추정과 미산정이
|
||||
함께 있으면 방향을 단정할 수 없으므로 `partial_upper_bound`와 `미산정`으로 표시하며, 단가가
|
||||
전혀 없는 모델도 0달러로 위장하지 않고 `unavailable`로 표시한다. 양수로 저장된 과거 참조
|
||||
비용은 현재 단가 라벨을 다시 붙이지 않고 `호출 시점에 저장된 참조단가 추정값`으로만 설명한다.
|
||||
응답은 전체·일별·provider/model·예산에 `cost_basis`를 전파하고 `recorded_cost_usd`,
|
||||
`estimated_cost_usd`, `rate_label`, `rate_source_url`을 함께 반환한다. 부분 산정·상한 상태에서는
|
||||
호출당 비용·1천 토큰당 비용·비용 비중을 확정값처럼 계산하지 않으며 예산도 `indeterminate`를
|
||||
사용할 수 있다. DB 조회는 동일한 day/provider/model/token/cost 시그니처만 묶어 요청별 단가
|
||||
경계를 보존하고, migration 18의 client-turn `created_at` partial index로 기간 조회를 제한한다.
|
||||
이 인덱스는 release agent가 `CREATE INDEX CONCURRENTLY`로 트랜잭션 밖에서 적용해 턴 쓰기를
|
||||
막지 않으며, 다른 schema migration은 기존 단일 트랜잭션 적용을 유지한다.
|
||||
제어된 음성 E2E가 남긴 정확한 `(e2e, fake-client, input=1, output=1, cost=0)` 조합은 비용 원장·예산에서만 제외하며,
|
||||
다른 `e2e` provider/model 조합이나 원본 DB 행을 포괄 삭제하지 않는다. 음성 성공 E2E의
|
||||
전용 disposable PostgreSQL 격리는 후속 항목이며, 공유 DB를 직접 삭제하는 cleanup은 사용하지 않는다.
|
||||
`EVALUATOR_SEMANTIC_CACHE_ENABLED`, `EVALUATOR_SEMANTIC_CACHE_TTL_SECONDS`,
|
||||
`EVALUATOR_SEMANTIC_CACHE_MAX_ENTRIES`로 제한하며, 원문 prompt/completion은 캐시에 저장하지 않는다.
|
||||
cache hit은 `engine.generate()`와 metadata-only `audit.llm_call_log` 기록을 건너뛰고,
|
||||
|
|
@ -1053,10 +1067,29 @@ DB 레벨 이중강제(`04_audit_eval_rls.sql` §5, `app/db.py` `acquire()`):
|
|||
렌더링하지 않고, 빈 상태 카드 대신 가입 직후 사용자 정보를 입력하는 단순 폼만 보여준다.
|
||||
이 화면은 이메일을 다시 받지 않고 닉네임, 자기소개,
|
||||
선택 아바타 이미지, 이름, 소속, 학과, 학년/직위, 연락처, 주소/수령지와 서비스 이용약관·개인정보
|
||||
처리방침 초안 동의를 저장한다. 아바타 파일은 `POST /users/me/avatar`가 MIME/시그니처/3MB 제한을
|
||||
확인한 뒤 `USER_UPLOAD_DIR/profile-avatars`에 저장하고 URL만 `app_user.avatar_url`에 보관한다.
|
||||
처리방침 초안 동의를 저장한다. 아바타 파일은 `POST /users/me/avatar`가 MIME/시그니처/3MB 제한에 더해
|
||||
Pillow full decode와 확장자-format 일치를 확인한 뒤 `USER_UPLOAD_DIR/profile-avatars`에 저장하고 URL만
|
||||
`app_user.avatar_url`에 보관한다. 시그니처만 닮고 실제 decode가 실패하는 payload는 저장 전에 거부한다.
|
||||
학습자 `POST /sessions`와 dev 음성 persona 시작은 온보딩 완료 후에만 허용하며, 온보딩 저장 시
|
||||
learner `consent_at`도 함께 세팅한다.
|
||||
- public avatar storage는 `USER_UPLOAD_DIR/profile-avatars`만 `/uploads/profile-avatars`에 mount한다. 같은
|
||||
`USER_UPLOAD_DIR`의 `multimodal-audio`는 private adapter 전용이며 static mount와 public migration에서 제외한다.
|
||||
public runtime은 source/upload root 밖 private migration receipt(SHA/root binding)와 현재 DB의 모든 managed avatar
|
||||
참조 파일(regular/no-reparse/readable)을 기동마다 검증한다. 초기 8개 migration snapshot과 현재 set은 동일할 필요가
|
||||
없지만, non-empty initialization 뒤 current set이 0이 되려면 별도 reset receipt가 필요하다.
|
||||
- upload manifest v3는 object count뿐 아니라 전체 byte 합계·inventory SHA-256·객체별 `decode_valid`와 preserved/current
|
||||
decode 정상/실패 count를 결속한다. immutable public cache에는 decode-valid bytes만 올리고, preserved이지만 decode-invalid인
|
||||
legacy payload는 forensic 보존하되 static handler가 404로 차단해 프론트 fallback을 사용하게 한다. 경로 존재·PNG signature만으로
|
||||
정상 이미지를 선언하지 않는다. manifest 이후 생성된 UUID형 업로드도 DB URL 기록·현재 참조 검증·GET/HEAD마다 용량 제한과
|
||||
Pillow full decode·확장자-format 일치를 다시 확인하고, 검증한 동일 bytes를 응답해 검사 뒤 재오픈 경쟁을 막는다. 이후 손상된
|
||||
신규 파일은 current invalid count와 health fallback에 반영되고 public 404가 된다. 2026-08-29 감사 기준은 union 93 objects·
|
||||
52,973 bytes·`9d703126…e6aa`, decode 정상 3/
|
||||
실패 90, 현 DB 참조 정상 2/실패 6/missing 0이다. 이 계약은 후보 코드이며 public API 장애 복구·전체 E2E·정식 승격 전에는
|
||||
deployed runtime의 동작으로 간주하지 않는다.
|
||||
- 프로필 업로드는 새 random target을 create-new 방식으로 완전히 flush한 뒤 publish하고 기존 DB 참조 파일을 선삭제하지
|
||||
않는다. upload 뒤 PATCH가 실패해도 기존 URL의 파일은 남으며, unreferenced object 정리는 별도 manifest-aware GC가
|
||||
소유한다. fresh storage cutover는 API write-lease freeze가 active/valid/drained임을 증명하고 tunnel-first로 외부 write를
|
||||
닫은 뒤 진행한다.
|
||||
- 관리자/교수자 권한은 온보딩 화면에서 신청받지 않는다. 서버는 `AUTH_ADMIN_EMAILS` /
|
||||
`AUTH_TEACHER_EMAILS` allowlist와 관리자 사용자 관리 경로로만 역할을 부여한다.
|
||||
- 로컬/Tailnet dev는 dev-login을 사용한다. 공개 `OAUTH_REDIRECT_URI`가 로컬/Tailnet 세션이 아니라 public API
|
||||
|
|
|
|||
|
|
@ -91,13 +91,29 @@ prod API 8001, web preview 5174, engine gateway 9099, cloudflared tunnel을 검
|
|||
worktree를 만들고, 그 root 내부 registrar에 `-StableSourceRoot`를 명시해 두 task를 승격한다.
|
||||
|
||||
$releaseRoot = 'D:\workspace\vignette-public-runtime-<commit>'
|
||||
$userUploadDir = Join-Path $env:LOCALAPPDATA 'Vignette\public-runtime\uploads'
|
||||
$uploadStateDir = Join-Path $env:LOCALAPPDATA 'Vignette\public-runtime\private-state'
|
||||
$uploadManifestSha = '<initializer가 출력한 64자리 sha256>'
|
||||
$uploadManifestPath = Join-Path $uploadStateDir "public-avatar-upload-$uploadManifestSha.json"
|
||||
$uploadFreezePath = Join-Path $uploadStateDir 'avatar-cutover.freeze.json'
|
||||
$bootRegistrar = Join-Path $releaseRoot 'scripts\register-boot-task.ps1'
|
||||
& powershell.exe -NoProfile -ExecutionPolicy Bypass -File $bootRegistrar -StableSourceRoot $releaseRoot
|
||||
& powershell.exe -NoProfile -ExecutionPolicy Bypass -File $bootRegistrar `
|
||||
-StableSourceRoot $releaseRoot -UserUploadDir $userUploadDir `
|
||||
-UserUploadManifestPath $uploadManifestPath `
|
||||
-ExpectedUserUploadManifestSha256 $uploadManifestSha `
|
||||
-UserUploadWriteFreezePath $uploadFreezePath
|
||||
$watchdogInstaller = Join-Path $releaseRoot 'scripts\install-public-runtime-task.ps1'
|
||||
& powershell.exe -NoProfile -ExecutionPolicy Bypass -File $watchdogInstaller -StableSourceRoot $releaseRoot -IntervalMinutes 5
|
||||
& powershell.exe -NoProfile -ExecutionPolicy Bypass -File $watchdogInstaller `
|
||||
-StableSourceRoot $releaseRoot -UserUploadDir $userUploadDir `
|
||||
-UserUploadManifestPath $uploadManifestPath `
|
||||
-ExpectedUserUploadManifestSha256 $uploadManifestSha `
|
||||
-UserUploadWriteFreezePath $uploadFreezePath -IntervalMinutes 5
|
||||
|
||||
- `VignettePublicRuntime`은 로그온 Docker/DB 복구, `VignettePublicRuntimeWatchdog`은 로그온+5분 반복 health/recovery다.
|
||||
- 두 task action은 release root와 Git commit/tree, boot 또는 watchdog SHA-256, start script SHA-256을 고정한다.
|
||||
- 두 task action은 release root와 Git commit/tree, boot 또는 watchdog SHA-256, start script SHA-256,
|
||||
source 밖의 절대 `USER_UPLOAD_DIR`, private migration manifest SHA, source/upload root 밖의 freeze path를 고정한다.
|
||||
consumer는 빈 upload root를 만들지 않으며 watchdog `-CheckOnly`는 정확한 listener PID의 cwd/env와 현재 DB가 참조하는
|
||||
모든 profile avatar의 존재·regular/no-reparse/readability를 다시 증명한다.
|
||||
등록 뒤 action marker와 working directory를 읽어 검증하기 전에는 `RunNow`를 호출하지 않는다.
|
||||
- secret은 task 인자에 넣지 않는다. API secret은 stable release의 `apps/api/.env`, cloudflared/Claude CLI credential은
|
||||
사용자 profile에 둔다. `.env` 내용을 console이나 evidence에 출력하지 않는다.
|
||||
|
|
@ -368,8 +384,13 @@ C:\Users\encep\AppData\Local\Programs\Python\Python311\python.exe scripts\mainta
|
|||
- 신규 사용자 또는 온보딩 미완료 사용자는 로그인 직후 `/onboarding`에서 닉네임, 자기소개,
|
||||
선택 아바타 이미지, 이름, 소속, 학과, 학년/직위, 전화번호, 주소/수령지와 약관·개인정보
|
||||
동의를 저장해야 역할 홈으로 이동한다. 학습자 회기 시작은 온보딩 완료와 동의가 모두 있어야 한다.
|
||||
- 프로필 아바타 업로드는 API 작업 디렉터리 기준 `USER_UPLOAD_DIR`(기본 `uploads`) 아래
|
||||
`profile-avatars/`에 저장되고, `/uploads/profile-avatars/...` URL로 서빙된다.
|
||||
- 프로필 아바타 업로드는 `USER_UPLOAD_DIR` 아래 `profile-avatars/`에 저장되고,
|
||||
`/uploads/profile-avatars/...` URL로 서빙된다. 로컬 dev에서 변수를 생략하면 API 작업 디렉터리 기준
|
||||
`uploads`를 쓰지만, Windows public runtime은 release root 밖의 절대
|
||||
`%LOCALAPPDATA%\Vignette\public-runtime\uploads`를 task와 API process environment에 명시한다. API는 전체 root가
|
||||
아니라 `profile-avatars`만 static mount하므로 private `multimodal-audio`가 `/uploads`로 노출되지 않는다. public
|
||||
runtime의 manifest/freeze는 upload root 밖 private state directory에 두며, 정확한 초기화·cutover 순서는
|
||||
`docs/ops/public-runtime-watchdog.md`를 따른다.
|
||||
|
||||
> 주의(이메일 도메인): 이 제한은 dev-login·SAML 조직 정책에만 적용된다. dev-login은
|
||||
> 이메일 도메인이 `AUTH_ALLOWED_EMAIL_DOMAINS`에 있어야 한다. 예: `learner@hs.ac.kr`.
|
||||
|
|
|
|||
|
|
@ -72,7 +72,7 @@
|
|||
| **M2** | 다회기 종단 케이스 아크·교차회기 사례개념화 11차 구동 | `(persona_id, learner_id)` 안정 `case_profile` upsert, 원자적 `session_no`, voice/REST/SSE recall cache 주입을 연결했다. 세션 종료 시 마스킹 축어록 기반 fallback `session_summary.digest`, `case_profile.case_digest`, `rapport_trajectory`, `alliance_level`을 갱신하고, 다음 회기 seed recall은 `case_digest`·직전 `session_summary`·client-visible non-contradicted `pinned_fact`를 함께 조립한다. 3차에서는 마스킹된 client-visible 발화에서 `[NAME]`/`[ORG]` identity와 명시적 상담 약속만 보수적으로 `pinned_fact`에 upsert한다. 4차에서는 pinned fact 삽입 또는 값 변경 시 `pinned_fact_history`에 append-only 이력을 남긴다. 5차에서는 명시적 상담 약속 철회/부정만 기존 non-locked `agreement:counseling` fact를 `contradicted`로 격리하고 history reason `contradiction`을 남긴다. 6차에서는 세션 종료 저장 성공 뒤 마스킹된 client-visible 내담자 발화만 `app.turn_embedding`에 BGE-M3 dense/sparse로 idempotent 색인한다. 7차에서는 REST submit/SSE stream의 턴 컨텍스트 조립 경계를 `_prepare_turn_context()`로 묶고, DB seed recall과 pinned fact가 다음 턴 EngineMessage L2/L4에 raw 이름 마스킹 상태로 주입되는 route-level 회귀를 추가했다. 8차에서는 `SessionDigestInput`/`SessionDigestResult`/`SessionSummaryWrite`로 종료 digest 입력·fallback 결과·DB write 인자 경계를 명시해 LLM worker 후보가 raw text, evaluator-only turn, 평가 payload, CCD, deterministic carry를 압축 prompt에 우회 주입하지 못하게 했다. 9차에서는 `DigestQualityAssessment`/`SessionDigestWorkerOutcome`로 local quality harness를 추가해 빈/짧은 digest, raw forbidden substring, 내부 평가·CCD·상태 marker, 잘못된 `S{session_no}:` prefix를 fallback 유지 대상으로 판정한다. 10차에서는 `session_digest_worker.py`가 `CompressionJob`을 Node-compatible `GenerateRequest`/`EngineMessage`로 변환하고, 주입형 engine/audit 호출 뒤 quality gate 통과 결과만 `session_summary.digest/compressed_by/token_count`와 `case_profile.case_digest`에 적용하는 one-shot 경계를 소유한다. 11차에서는 `scripts/run-session-digest-worker.py` dry-run/apply runner와 `SESSION_DIGEST_WORKER_ENABLED=false` 기본값의 세션 종료 background scheduler 골격을 붙였다. scheduler는 DB load/apply 때만 connection을 잡고 engine 호출은 transaction 밖에서 수행하며, `compressed_by IS NULL` loader/apply CAS로 재실행 race를 막는다. DB loader는 persisted fallback summary와 client-visible `text_masked` transcript만 재구성하며 raw `text`, evaluator-only turn, CCD, end_state는 prompt에 넣지 않는다. `digest_pending`은 CompressionJob 생성 여부를 알리는 비동기 압축 필요 신호로 유지한다. 같은 값 재확인은 history를 늘리지 않고, `locked` fact는 건드리지 않는다. 관계갈등·위기·임상 추론은 자동 pinning/모순 처리에서 제외한다. | 관계·임상 fact 승격 기준, 실 provider 장시간 운영, 임상 골든셋 품질평가, 재압축, 접수면접→다회기 연속성·자기개념 진화 실증. |
|
||||
| **M3** | SSO claim 매핑·식별자 안정성 1차 완료·운영 IdP 감사 미연결 | Google/SAML/dev-login이 `AUTH_EMAIL_COHORT_MAP`·`AUTH_DOMAIN_COHORT_MAP` 및 SAML cohort claim을 `cohort_ids`로 전달하고, `app_user.external_id`는 provider subject(`google:`/`saml:`/`dev:`) 기반으로 저장한다. 운영 SAML 서명검증, 기관 claim schema/test tenant, deprovisioning audit은 아직 없다. | 한신 IdP 확정 후 SAML 서명검증, claim→role/cohort/institution_user_id 매핑 표 실연동, role변경/삭제 audit, deprovisioning evidence. |
|
||||
| **X1** | 재귀학습·데이터셋 export 파이프라인 2차 구현 | `scripts/export-recursive-dataset.py`와 `app.services.dataset_export`로 masked-text JSONL dry-run, PII scan, kappa/ICC 계산, approved export 게이트를 구현했다. exporter는 consent가 남아 있고 client-visible인 `text_masked` 턴만 고르며, supervisor comment raw text를 JSONL에 넣지 않는다. `scripts/check-phase3-artifacts.py`는 approved와 dry-run dataset JSONL의 required keys, row count, privacy, PII shape를 검사해 `{}` 한 줄 같은 false-positive를 막는다. 기본은 `technical_dry_run`이며 실제 승인 export·골든셋 승격은 데이터 steward/legal review와 IAA 통과가 필요하다. | 파일럿 evidence에서 reviewer disposition, steward/legal 승인, gold annotation 라운드 적재, withdrawal/consent roster 대조, `min_completed_sessions` 정책 반영 후 `approved_for_recursive_learning_seed` 승격 검증. |
|
||||
| **X2** | AI API 비용 관측·예산 경고·평가 저비용 라우팅·evaluator cache 관측·일별 비용 추이·모델별 비용 검증 리포트 3차 완료 | 턴별 provider/model/tokens/cost 저장 경로와 `GET /admin/usage`, 관리자 비용 대시보드를 연결했다. `ADMIN_USAGE_BUDGET_USD` 기준 예산 상태(ok/warn/exceeded)도 응답/UI에 표시한다. `EVALUATOR_FAST_MODEL`/`EVALUATOR_DEEP_MODEL` 설정 시 fast/deep 평가 호출만 해당 모델 override로 gateway에 전달하고, 비워두면 기존 gateway default 라우팅을 유지한다. fast/deep evaluator structured 결과는 canonical request SHA-256 기반 인메모리 semantic cache로 재사용하며, 원문 prompt·completion은 저장하지 않고 성공 파싱 결과만 TTL/entry 제한 안에서 캐시한다. `/admin/usage`와 관리자 비용 카드가 cache enabled/entries/hits/misses/stores/evictions/requests/hit_rate와 일별 `daily_cost` 추이를 노출한다. `app.services.usage_report`와 `scripts/report-ai-usage.py`는 같은 usage JSON에서 provider/model별 cost share, token share, cost/turn, cost/1k tokens, metered coverage, budget/cache warning을 산출한다. 3차에서는 Claude CLI SDK 비용 추정값을 우선하고, 비용이 없는 Agy/Gemini·Codex·Claude API는 버전 고정 공식 참조단가로 입력·캐시 입력·출력 토큰을 환산한다. Claude 토큰은 `modelUsage` 전체 agent tree와 cache read/create 입력을 합산한다. 과거 미수집 442건은 비용 역산 없이 로컬 Claude JSONL의 정규화 응답 SHA-256·생성 시각이 유일하게 일치한 169건만 실제 usage로 백필했고, 불일치 273건은 `미계량`으로 남겼다. 기존 DB의 0달러 행도 조회 시 보정하고, 단가 미등록 모델은 `미산정`으로 명시한다. DB 미가용 dev는 runtime store fallback, prod는 fail-closed다. | 자동 차단·한도 enforcement 정책, 공식 단가 변경 시 rate card 갱신·회귀. |
|
||||
| **X2** | AI API 비용 관측·예산 경고·평가 저비용 라우팅·evaluator cache 관측·일별 비용 추이·모델별 비용 검증 리포트 4차 완료 | 턴별 provider/model/tokens/cost 저장 경로와 `GET /admin/usage`, 관리자 비용 대시보드를 연결했다. `ADMIN_USAGE_BUDGET_USD` 기준 예산 상태(ok/warn/exceeded/indeterminate)도 응답/UI에 표시한다. `EVALUATOR_FAST_MODEL`/`EVALUATOR_DEEP_MODEL` 설정 시 fast/deep 평가 호출만 해당 모델 override로 gateway에 전달하고, 비워두면 기존 gateway default 라우팅을 유지한다. fast/deep evaluator structured 결과는 canonical request SHA-256 기반 인메모리 semantic cache로 재사용하며, 원문 prompt·completion은 저장하지 않고 성공 파싱 결과만 TTL/entry 제한 안에서 캐시한다. `/admin/usage`와 관리자 비용 카드가 cache enabled/entries/hits/misses/stores/evictions/requests/hit_rate와 일별 `daily_cost` 추이를 노출한다. `app.services.usage_report`와 `scripts/report-ai-usage.py`는 같은 usage JSON에서 provider/model별 token share와 계량 coverage를 산출하며, cost share/cost-per-unit은 비용 확실성이 보장될 때만 산출한다. Claude CLI SDK 추정값을 우선하고 Agy/Gemini·Codex·Claude API는 호출 시작일 기준 공식 참조단가를 쓴다. 4차에서는 Gemini 3.6/3.7 Flash의 출시일·2026-08-13 프로모션·2027-01-01 표준 단가를 유효기간으로 분리하고, 과거 0달러 행을 동일 계량 시그니처별로 묶어 요청별 가격화해 입력 200K 단가 경계를 보존한다. 캐시 토큰이 별도 보존되지 않은 과거 보정액은 `reference_upper_bound`, 확정 산정/미산정 혼합은 `partial`, 상한/미산정 혼합은 `partial_upper_bound`, 완전 미산정은 `unavailable`로 구분한다. 전체·일별·예산에도 같은 `cost_basis`를 전파한다. 정확한 `(e2e, fake-client, input=1, output=1, cost=0)` 테스트 조합만 보고서에서 제외한다. migration 18은 client turn 기간 조회용 partial index를 `CREATE INDEX CONCURRENTLY`로 트랜잭션 밖에서 추가한다. Claude 토큰은 `modelUsage` 전체 agent tree와 cache read/create 입력을 합산한다. 과거 미수집 442건은 비용 역산 없이 로컬 Claude JSONL의 정규화 응답 SHA-256·생성 시각이 유일하게 일치한 169건만 실제 usage로 백필했고, 불일치 273건은 `미계량`으로 남겼다. DB 미가용 dev는 runtime store fallback, prod는 fail-closed다. | 자동 차단·한도 enforcement 정책, 공식 단가 변경 자동 감시, `priced_at`/`rate_id`/캐시 입력 토큰의 영속 provenance, 음성 E2E 전용 disposable DB 격리. |
|
||||
| **L1** | 기술스택 신청서-구현 불일치 및 단기일정 산출물 압박 | doc4 신청서 스택(Spring Boot 3/Node.js·TimescaleDB) vs 실제 FastAPI/Python 불일치, 20주 단기일정·9월 저작권 등재 압박. 소유자 결정으로 장기 교체 대상은 Node.js 우선, 현재 FastAPI 전면 재작성은 보류했다. 내부 전환 증거로 engine gateway 공유 계약, `EngineClient.stream_packets()` decode 경계, schema-backed golden fixture(`engine_gateway_contract.v1.json`/`engine_gateway_schema.v1.json`), Python import 없는 `scripts/check-engine-gateway-contract.mjs` Node.js conformance runner, `gateway-default` default-routing sentinel 정규화, `structured_payload_from_response()` 기반 structured/legacy JSON response parser, 브라우저-facing 세션 read-model 분리(`app/session_read_model.py`), 페르소나 DTO/mapper 분리(`app/persona_read_model.py`), 페르소나 draft generation 계약(`app/persona_generation_contract.py`), session evaluation write packet(`SessionEvaluationWrite`), stage 라벨/phase-key 정규화 SSOT(`app/stage_contract.py`)까지 고정했다. | 신청서/저작권 등재 문서에 FastAPI 유지 사유와 계약 우선 Node 전환 계획을 반영하는 외부 거버넌스 증거. 내부 후보였던 H3 항목형 목록 저작 UI와 프롬프트 미리보기 de-JSON은 10차에서 완료. |
|
||||
|
||||
> X2 근거: doc3 회의록이 'AI API 비용'을 운영 리스크로 명시.
|
||||
|
|
|
|||
|
|
@ -15,12 +15,12 @@ Vignette 저장소의 모든 검증 수단(백엔드 단위 테스트, 웹 타
|
|||
|
||||
| 검증 | 작업 디렉터리 | 명령 | DB | API(8000) | 웹(5173) | 엔진GW(9099) | 브라우저 | 현재 통과 |
|
||||
|---|---|---|---|---|---|---|---|---|
|
||||
| 백엔드 단위 테스트 | `apps/api` | `python -m pytest app/ -q` | 불필요 | 불필요 | 불필요 | 불필요 | 불필요 | 2026-08-28 전체 실행 1002 passed |
|
||||
| 백엔드 단위 테스트 | `apps/api` | `python -m pytest app/ -q` | 불필요 | 불필요 | 불필요 | 불필요 | 불필요 | 2026-08-29 전체 실행 1074 passed / 1 skipped |
|
||||
| 엔진 게이트웨이 테스트 | `apps/api` | `python -m pytest engine_gateway/ -q` | 불필요 | 불필요 | 불필요 | 불필요 | 불필요 | 2026-08-28 전체 실행 68 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` | **필요(+시드)** | **필요** | 자동기동 | 일부만 | **필요** | 현재 수집 1138 tests / 62 files · 현 작업트리 전체 GREEN 미검증 |
|
||||
| Playwright E2E(전체) | `apps/web` | `npm run e2e` | **필요(+시드)** | **필요** | 자동기동 | 일부만 | **필요** | 현재 수집 1158 tests / 64 files · 현 작업트리 전체 GREEN 미검증 |
|
||||
|
||||
핵심 원칙: **단위 테스트(pytest)와 타입체크/빌드는 외부 서비스 없이 단독 실행된다.**
|
||||
**E2E만 풀스택(DB+API+웹+브라우저)을 요구한다.** 아래 각 절에서 근거와 절차를 설명한다.
|
||||
|
|
@ -42,7 +42,7 @@ Vignette 저장소의 모든 검증 수단(백엔드 단위 테스트, 웹 타
|
|||
|
||||
```sh
|
||||
# apps/api
|
||||
python -m pytest app/ -q # 앱 단위 테스트: 2026-08-28 전체 실행 1002 passed
|
||||
python -m pytest app/ -q # 앱 단위 테스트: 2026-08-29 전체 실행 1074 passed / 1 skipped
|
||||
python -m pytest engine_gateway/ -q # 게이트웨이 단위 테스트: 2026-08-28 전체 실행 68 passed
|
||||
```
|
||||
|
||||
|
|
@ -80,12 +80,12 @@ python -m pytest engine_gateway/ --collect-only -q
|
|||
|
||||
#### 개선관리 워크북 focused 증거 (2026-08-27~28)
|
||||
|
||||
아래는 전체 1002/68과 별도로 해당 계약을 좁혀 실행한 확정 증거다. 같은 묶음의 테스트가 여러 요구사항
|
||||
아래는 전체 API 1074 passed/1 skipped·gateway 68 passed와 별도로 해당 계약을 좁혀 실행한 확정 증거다. 같은 묶음의 테스트가 여러 요구사항
|
||||
경계를 함께 검증할 수 있으므로 숫자를 요구사항별 전체 합계로 더하지 않는다.
|
||||
|
||||
| 항목 | focused 명령/파일 | 확인 결과 |
|
||||
|---|---|---|
|
||||
| C-001 기술 사전검증 | `scripts/check-clinical-crisis-review.py`, `run-clinical-crisis-technical-observations.py`, checker/client reply/source pack/state machine/session focused pytest | 90 passed + Ruff PASS(Starlette 제3자 경고 1건) — P1 생성·스트림 실제 경로 6/6, v2 사례 6건과 런타임 패키지 8개가 HEAD·작업트리 SHA에 일치한다. DB direct load·baseline·carry-over·observed 값을 `1..3`으로 정규화하고 과상한 출력은 재생성하며, 수련생 실제 위기는 엔진 전 중단·109, 내담자 수단 상세는 차단한다. 6시트 개선관리 완료본은 수식 오류 0, 10 완료/1 검토/총 11, canonical gate `pending-valid`다. 사례 30필드·청소년 6답변·자격 포함 승인 10필드·서명 증거가 비어 있어 `review_complete=false`; 외부 임상 승인을 대체하지 않음 |
|
||||
| C-001 기술 사전검증 | `scripts/check-clinical-crisis-review.py`, `run-clinical-crisis-technical-observations.py`, checker/client reply/source pack/state machine/session focused pytest | 90 passed + Ruff PASS(Starlette 제3자 경고 1건) — P1 생성·스트림 실제 경로 6/6, v2 사례 6건과 런타임 패키지 8개가 HEAD·작업트리 SHA에 일치한다. DB direct load·baseline·carry-over·observed 값을 `1..3`으로 정규화하고 과상한 출력은 재생성하며, 수련생 실제 위기는 엔진 전 중단·109, 내담자 수단 상세는 차단한다. 6시트 개선관리 완료본은 수식 38개·수식 오류 0, 9 완료/2 검토/총 11, canonical gate `pending-valid`다. REQ-001은 기존 Gmail 인증 세션·관리자 권한·복구 데이터 가시성 수락으로 완료했고, REQ-008 실제 Windows 재부팅 smoke와 C-001 사례 30필드·청소년 6답변·자격 포함 승인 10필드·서명 증거만 남아 있으며 외부 임상 승인을 대체하지 않음 |
|
||||
| C-002 | `app/test_first_session_checklist.py` | 3 passed — 첫 회기 라포 반영·한 초점 열린 질문, evidence turn, 이후 회기 not-applicable |
|
||||
| C-003 | `app/test_learner_dashboard.py` | 11 passed — 종료 회기 4건 전에는 insufficient, 이후 dominant share 0.75 경계 |
|
||||
| REQ-001 | `app/test_auth_providers.py`, `app/test_access_logging.py`, OAuth UI focused E2E, `e2e/admin.spec.ts`/`e2e/uc-admin-console.spec.ts` | auth 41 + access-log redaction 6 pytest, OAuth UI desktop/mobile 4, route-mock browser 2, 실제 DB/browser 1 passed — 모든 provider-verified Google 이메일을 도메인·사전등록 없이 learner·approved로 허용하고 suspended는 보존. callback query는 Uvicorn access log에서 제거. 관리자 사전등록 create는 pending 고정이며 승인 전 `/personas` 403·`/learn→/pending`, 별도 PATCH 승인 뒤 같은 세션 `/personas` 200, `learner_feedback_enabled=false` 영속 재조회, 비활성화·활성 세션 0 |
|
||||
|
|
@ -99,10 +99,12 @@ python -m pytest engine_gateway/ --collect-only -q
|
|||
| REQ-008 | `app/test_engine_health_contract.py`와 session stream 회귀 | health contract 1 passed; incomplete EOF는 live/session 50 묶음에 포함 |
|
||||
| 학습자 성장 패널 헤더 | `uc-learner-home-dashboard.spec.ts --grep "성장 지표 라포 헤더"` + `check:design-ssot` + `check:cosmetic-filter-safety` + typecheck | chromium desktop/mobile 2 passed — 선택된 라포 문구의 부모는 plain `div`, 배경 transparent·border/radius/padding 0이며 주변 panel 구조는 유지 |
|
||||
| 교수자 검토·요약 카드 간격 | `full-sweep-professor.spec.ts --grep "renders six KPI cards"` + `layout-visual-gate.spec.ts` + `session-layout.spec.ts` | 기존 0px에서 회귀 게이트 RED를 확인하고 `--sp-3`=12px로 교정했다. focused 1 passed, 7폭 교수자 gate 1 passed, 전체 layout visual 15 passed, session 무회귀 8 passed. Pages `0c60261e…` 승격 뒤 실제 `/teach`는 computed/실측 12px, overflow 0, console warning/error 0이다. |
|
||||
| REQ-001 공개 운영 | `https://vignette.chanpaca.net/login` + Google account chooser/callback | `allowed_email_domains=[]`이며 기존 일반 Google 계정 callback→`/admin` 성공으로 가입 범위는 확인했다. Pages production `ef48c0ae…` 승격 뒤 custom domain은 단일 `Google 계정으로 로그인` CTA 1개·구 계정 선택 버튼 0·dev login 0·console warning/error 0이고 격리 Playwright OAuth 시작 검증 **1 passed**다. 실제 Gmail callback과 복구 계정의 `/admin`·기존 데이터 확인 전에는 검토를 유지한다. |
|
||||
| Google 계정 데이터 복구 | migration 19, alias/auth focused, 전체 API·gateway, 백업 복제 DB, 활성 DB·공개 API | Gmail의 빈 중복 사용자와 canonical 관리자 계정을 이메일 자동병합 없이 명시적 Google subject 별칭 2개로 연결했다. 정식 계정 21회기·64턴·평가 6건과 Gmail 원본 설정 1행을 보존하고 source-only 알림 키를 canonical 설정에 무손실 병합했다. Gmail 옛 세션 1개 철회, 중복 계정 suspended, 감사로그 1건, 사용자 1175·회기 469·평가 65와 고아 0을 확인했다. API+gateway **1111 passed/1 skipped**, release agent **31 passed**, 백업 복제 DB 트랜잭션·cleanup 및 런타임 DB 역할 alias SELECT를 통과했다. 실제 Gmail OAuth callback은 사용자 계정 선택 후 최종 확인한다. |
|
||||
| REQ-001 공개 운영 | `https://vignette.chanpaca.net/login`, `/settings`, `/learn/history`, `/admin/continuous-improvement` | 2026-08-28 증거에서 `allowed_email_domains=[]`, Pages `0c60261e…`의 단일 Google CTA·공개 Playwright 2 passed, 기존 인증 세션의 Gmail 로그인 이메일·관리자 콘솔·온보딩 비전환·복구 이력 20건/리뷰 필요 6건을 확인했고 소유자가 데이터 가시성을 수락해 REQ-001을 완료했다. 2026-08-29 public API 530은 별도 현재 장애이며, 이 과거 수락 증거를 current health GREEN으로 재사용하지 않는다. |
|
||||
| Google 계정 데이터 복구 | migration 19, alias/auth focused, 전체 API·gateway, 백업 복제 DB, 활성 DB·공개 API·기존 인증 브라우저 세션 | Gmail의 빈 중복 사용자와 canonical 관리자 계정을 이메일 자동병합 없이 명시적 Google subject 별칭 2개로 연결했다. 정식 계정 21회기·64턴·평가 6건과 Gmail 원본 설정 1행을 보존하고 source-only 알림 키를 canonical 설정에 무손실 병합했다. Gmail 옛 세션 1개 철회, 중복 계정 suspended, 감사로그 1건, 사용자 1175·회기 469·평가 65와 고아 0을 확인했다. API+gateway **1111 passed/1 skipped**, release agent **31 passed**, 백업 복제 DB 트랜잭션·cleanup 및 런타임 DB 역할 alias SELECT를 통과했다. 후속 기존 인증 브라우저 세션에서 Gmail 로그인 이메일·관리자 권한·복구 데이터 가시성을 확인했다. |
|
||||
| REQ-005·007·008 공개 운영 | P20 회기 `03dddadd-adf3-4922-acbf-31002470da52` | 생성→자기점검 원장 잠금→실제 학습자/AI 내담자 2턴→종료→AI 평가 완료→P20 1회 기록 영속→다음 회기 버튼 재노출 |
|
||||
| 운영 배포·복구 | live API clean `dba9b75a…`·tree `14cd4607…`, Pages `ef48c0ae-374e-41fc-b6fd-03f69b26e946`; boot/watchdog 동일 pin | fresh API+cloudflared provenance receipt SHA `186529be…f7a4`, 실제 API cwd가 새 detached root이며 local/public prod·db·engine·voice, OpenAPI 129, auth 401이 정상이다. boot/watchdog 실제 실행은 `LastTaskResult=0`·failcount 0이고 custom domain Pages entry SHA는 clean build와 일치한다. 배포별 preview origin은 정적 자산만 일치하고 연결 진단을 표시하므로 runtime GREEN이 아니며, 실제 Windows 재부팅 smoke는 미실행이다. |
|
||||
| 아바타 decode/fallback 후보 | `app/test_upload_storage_contract.py`, `scripts/test_initialize_public_runtime_upload_root.py`, `initialize-public-runtime-upload-root.py probe` | source union 실측은 93 objects·52,973 bytes·inventory SHA-256 `9d703126…e6aa`, decode 정상 3/실패 90이며 현 DB 참조는 정상 2/실패 6/missing 0이다. 후보는 manifest v3 decode 상태·합계·digest를 fail-closed로 결속하고 invalid static 응답을 404/fallback으로 보낸다. focused 단위/probe 증거와 전체 API/Web/PowerShell/E2E·정식 배포 증거를 분리한다. |
|
||||
| G8 사람 게이트 현재 상태 | `e2e/continuous-improvement-admin.spec.ts`, `e2e/continuous-improvement-live.spec.ts`, Codex 내장 브라우저 | typecheck와 route-fixture desktop/mobile **10/10** 통과, 3.5초/8초 watchdog overlay 0이다. 격리 local stateful in-app browser에서도 8.5초 뒤 overlay 0·heading 정상, content `keep_quarantine` 뒤 effect 0, 증거 없는 release 승인 disabled→`reject` 뒤 effect 0, 증거 4종 promote 승인 뒤 lifecycle effect 정확히 1을 확인했다. 이 local proof는 GREEN이지만 실DB append-only 결정과 public proof는 아직 남아 있다. |
|
||||
| 운영 배포·복구 | deployed API/task baseline `dba9b75a…`·tree `14cd4607…`, Pages `0c60261e-cb37-482d-ba42-d91586194c48` | 2026-08-29 최신 read-only 상태는 local 8001 연결 거부·public API 530·Docker daemon 부재다. 정적 public Web 200과 2026-08-28 green receipt는 현재 API/DB GREEN 증거가 아니다. avatar 후보는 미배포이며, 전체 통합 GREEN·후보 SHA·사용자 승인 전에는 runtime/task/DB/upload-root를 바꾸지 않는다. 실제 Windows 재부팅 smoke도 미실행이다. |
|
||||
|
||||
로컬 Playwright는 기존 포트의 서버를 기본 재사용하지 않는다. 공개 preview나 다른 checkout의 stale bundle을
|
||||
현 작업트리 증거로 오인하지 않도록 충돌 시 fail-closed하며, 동일 dev server를 의도적으로 공유할 때만
|
||||
|
|
@ -307,7 +309,7 @@ VITE_API_BASE=http://127.0.0.1:8000 npm run e2e # 프록시 대신 API
|
|||
|
||||
### 3.6 실측 테스트 개수 (현재)
|
||||
|
||||
2026-08-28 `npx playwright test --list` 기준 **현재 수집 1138 tests / 62 files**다
|
||||
2026-08-29 `npx playwright test --list` 기준 **현재 수집 1158 tests / 64 files**다
|
||||
(유스케이스 16테마 `uc-*.spec.ts` 239 시나리오 포함).
|
||||
이 숫자는 수집량이지 통과량이 아니다. 현 작업트리 전체 1109개 완주는 아직 증거가 없으며,
|
||||
과거 전체 GREEN 기록과 이번 focused/release gate 결과를 구분해 적는다.
|
||||
|
|
@ -553,7 +555,9 @@ hourly heartbeat는 최신 GREEN이 6시간 이상 오래됐거나 material mile
|
|||
- 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-08-28 Gemini 3.7 비용 미산정 회귀 검증: screenshot과 같은 `gemini-3.7-flash-high` 21회·입력 115,950·출력 4,407의 기존 0달러 원장을 2026 프로모션 기준 `≤$0.103489`(`reference_upper_bound`)로 보정한다. Gemini 3.6/3.7 출시·프로모션·2027 경계, 요청별 200K 단가 구간, 일부 미산정 `$…+`, 상한+미산정 `partial_upper_bound`의 `미산정`, 저장된 과거 비용의 라벨 불변, 정확한 `(e2e, fake-client, input=1, output=1, cost=0)` 제외를 회귀화했다. 전체·일별·예산에 비용 확실성을 전파하고 불확실한 평균·비중은 숨긴다. 동일 계량 시그니처 SQL 집계와 migration 18의 client-turn 기간 인덱스를 고정했으며, release agent는 이 파일만 `CREATE INDEX CONCURRENTLY`로 트랜잭션 밖에서 적용한다. focused **105 passed / 1 skipped**, `app engine_gateway` 전체 **1102 passed / 1 skipped**, 새 빈 PostgreSQL에서 온라인 마이그레이션+통합 **1 passed**, release agent **31 passed**, API type generation/check·typecheck·build, 관리자 AI desktop/mobile **6 passed**를 확인했다. 통합 컨테이너와 테스트 DB는 `--rm`으로 정리했으며, 공개 배포·운영 DB 데이터 삭제는 하지 않았다.
|
||||
- 2026-08-29 Google 계정 데이터 복구 검증: migration 19가 owner-managed `auth_identity_alias`와 세션의 별도 `login_email`을 추가한다. Gmail 중복 계정은 학습 데이터 0건이지만 기본 설정 1행이 있어 최초 가드가 rollback했고, 전체 app_user FK를 전수 스캔한 뒤 canonical 설정과 겹치는 값이 동일함을 확인했다. 개정 트랜잭션은 source-only `account_approval` 알림 키를 canonical 설정에 합치고 원본 설정 행은 보존한다. 백업 복제 DB에서 alias 2·세션 21·턴 64·평가 6·활성 source session 0·고아 0으로 통과 후 복제 DB를 삭제했고, 활성 DB도 사용자 1175·회기 469·평가 65·고아 0을 유지했다. 공개 API fresh provenance는 commit `dba9b75a…`·tree `14cd4607…`, health DB/engine true, voice exact, OpenAPI 129, auth 401이며 boot/watchdog 실제 실행 결과는 0/0이다. Pages production `ef48c0ae…`의 custom domain은 clean build entry·login chunk SHA와 일치하고 단일 Google CTA E2E 1건을 통과했다. 실제 Gmail callback은 브라우저 계정 선택을 기다린다.
|
||||
- 2026-08-29 REQ-001 완료·개선관리 재생성 검증: 기존 실서비스 인증 세션의 `/settings`에서 `yunchan8804@gmail.com`, 관리자 메뉴와 `/admin/continuous-improvement` 접근, 온보딩 비전환을 확인했고 `/learn/history`는 회기 20건·리뷰 필요 6건을 표시했다. 소유자가 데이터 가시성을 수락했으며, 최신 Pages `0c60261e…`의 단일 Google CTA·무제한 도메인 계약·공개 Playwright 2 passed와 결합해 REQ-001을 완료로 승격했다. 완료본은 artifact-tool import→export→reimport로 6시트·11개 고유 요구·38수식·수식 오류 0·완료 9/검토 2를 확인했다. C-001은 패키지 8/8·기술 사례 6/6·canonical checker 정상의 `pending-valid`지만 외부 입력 46칸을 공란으로 보존했고, REQ-008은 실제 Windows 재부팅 smoke 전까지 검토다. 완료본 SHA는 `c832547f…d8bd`, sidecar SHA는 `8c9618f0…a610`이다.
|
||||
- 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회` 표시를 확인하고 임시 세션을 폐기했다.
|
||||
|
||||
|
|
|
|||
|
|
@ -12,13 +12,19 @@
|
|||
> 섹션과 상세 근거 `docs/ops/source-docs-gap-analysis-2026-06-26.md`에서 추적한다. C1~C3/H·M 구조는 코드로
|
||||
> 선제 구축했고, 임상 문안·평가기준·골든셋 콘텐츠는 임상팀(구훈정·어유경) 외부 정의로 받는다.
|
||||
>
|
||||
> **개선관리 워크북(2026-08-29)** — C-002·C-003과 REQ-002~007은 내부 기술·요구 실증 DONE이다. REQ-001은
|
||||
> single Google CTA를 Pages production `ef48c0ae…`에 승격했지만 실제 Gmail callback, REQ-008은 recovery task
|
||||
> `dba9b75a…`·tree `14cd4607…`와 5174 자동복구까지 완료했지만 실제 Windows 재부팅 smoke가 남아 둘 다 `검토`다. C-001은
|
||||
> **개선관리 워크북(2026-08-29)** — C-002·C-003과 REQ-001~007은 완료다. REQ-001은 기존 Gmail 인증 세션의
|
||||
> 로그인 이메일·관리자 권한·온보딩 비전환·복구 데이터 가시성을 소유자가 수락해 완료로 승격했다. REQ-008은 recovery task
|
||||
> 계약과 5174 자동복구까지 완료했지만 실제 Windows 재부팅 smoke가 남아 `검토`다. C-001은
|
||||
> v2 합성 사례 6건·상태별 canonical 판정기와 자동
|
||||
> 안전 게이트의 기술 사전검증까지 완료됐다. 사례별 5필드 30개·청소년 답변 6개·검토자 이름/소속/자격을 포함한
|
||||
> 승인 10필드와 서명 증거가 없어 B4 외부 GATE로 유지하며, `review_complete=true` 전에는 완료로 닫지 않는다.
|
||||
> 현재 개선관리 집계는 완료 8·검토 3·총 11이다.
|
||||
> 현재 개선관리 집계는 **완료 9·검토 2·총 11**이다. C-001 외부 입력 46칸은 조작하지 않는다.
|
||||
>
|
||||
> **2026-08-29 최신 운영 상태** — 정적 public Web은 200이지만 local API 8001은 연결 거부, public API는 530이고
|
||||
> Docker daemon 부재로 DB/API는 OUTAGE다. 배포된 동적 runtime/task 기준선은 old `dba9b75a…`, Pages는
|
||||
> `0c60261e…`(source `5bf89ff…`)다. avatar union은 93 objects·52,973 bytes·SHA-256 `9d703126…e6aa`, decode
|
||||
> 정상 3/실패 90, 현 DB 참조 정상 2/실패 6/missing 0이다. fail-closed 후보와 deployed baseline을 분리하며,
|
||||
> 전체 회귀·아바타 실브라우저 fallback·G8 실DB/public 증거·사용자 배포 승인 전에는 운영 전체를 GREEN으로 올리지 않는다.
|
||||
>
|
||||
> **Outcome & Alliance OS** — 2026-08-06 정식 전략 실행 트랙으로 승격했다. G0 Measurement Truth, G1
|
||||
> 현재 소스·실행 증거 재감사에서는 G0~G6과 G8이 DONE이다. G1 승격 prompt 1.2+read-skew/JSON 복구는 24/24 ready·방향 9/9·오류 0을 재확인했고, G0 census 29/29·위반 0, G4/G5 실제 API/DB/브라우저 폐루프, G6 safety metadata-only 최우선 runtime을 disposable clone에서 확인했다. G8은 실제 receipt-bound image rollback 2회(`nas-g8-723eeef2…`/`nas-g8-2738846c…`)에 더해 source HEAD `61a41d1f…6af`·tree `87dec55d…3b77`·archive `4d15d055…119d4d`의 candidate 112/112와 실제 NAS 평문 origin 112/112를 통과했다. 과거 `6030a677…c611`의 UUID 24건 실패와 후속 SHA 결함 rollback은 이력으로 보존하며 현재 완료 증거로 재사용하지 않는다. G7 Multimodal Alliance는 내부 구현 DONE과 외부 proof GATE를 분리한다. detached-clean public `a73bcd24…`·OpenAPI 126·`local_whisper`/`melotts` ready·authenticated WSS 무마이크 rehearsal까지 완료했고, 명시 동의 물리 마이크 3,120초·독립 라벨 voice-gain benchmark·동시 topology high-water를 추적한다. 외부 Deepgram/OpenAI adapter는 fallback으로 보존한다. G0~G8과
|
||||
|
|
@ -40,6 +46,17 @@
|
|||
|
||||
## B2. 환경 제약 — 이 워크스테이션에서 증거 생산 불가
|
||||
|
||||
- [ ] **public API/DB·아바타 정식 승격** — Docker/API 장애를 복구하고, 손상 아바타는 private forensic으로 보존하되
|
||||
public 404/fallback, decode-valid 객체만 immutable cache에 제공하는 후보를 전체 API/Web/PowerShell·내장 브라우저 E2E로
|
||||
검증한다. exact 93 objects·52,973 bytes·`9d703126…e6aa` receipt와 후보 commit SHA를 고정한 뒤 push/Pages/runtime/task/
|
||||
upload-root 범위를 명시 승인받는다. 신규 UUID 업로드의 URL 기록·현재 참조·GET/HEAD도 full decode하며 사후 손상은 current
|
||||
invalid/health fallback과 404로 닫는 후보까지 API 1074/1 skip을 통과했다. Docker engine OFF + 활성 public watchdog 때문에
|
||||
풀스택은 watchdog 일시 비활성화와 고유 56432/58000/55173 격리 자원·exact cleanup을 승인받아야 한다. DB avatar URL 삭제·
|
||||
파일 초기화·task/runtime 재시작을 선행하지 않는다.
|
||||
- [ ] **G8 사람 게이트 실DB/public 실증** — 승인·보류(`keep_quarantine`)·반려(`reject`)와 사유 입력, 증거 미충족 승인
|
||||
차단은 typecheck와 route-fixture desktop/mobile 10/10을 통과했다. Codex 내장 브라우저의 격리 local stateful fixture도
|
||||
8.5초 overlay 0·heading 정상, content 보류와 증거 없는 release 반려 effect 0, 증거 4종 promote 승인 lifecycle effect 1로
|
||||
GREEN이다. 남은 것은 사용자 행동 시점 확인 뒤 실행할 실DB append-only 결정과 public proof다. local fixture는 이를 대신하지 않는다.
|
||||
- [ ] **공개 DB 계정·회기 복구 안정화** — 2026-08-07 18:26 KST owner 승인으로 recovered named volume을
|
||||
`vignette-dev-db:55432`에 전환했다. cutover owner 집계 users 84, sessions 30, turns 705, Google 계정 16,
|
||||
Google 소유 회기 30, orphan 0이며 health·engine·OAuth·watchdog가 정상이다. 기존 인증 세션은 복사하지
|
||||
|
|
|
|||
73
docs/ops/evidence/avatar-decode-audit-2026-08-29.json
Normal file
73
docs/ops/evidence/avatar-decode-audit-2026-08-29.json
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
{
|
||||
"schema_version": "vignette.avatar-decode-audit.v1",
|
||||
"generated_at": "2026-08-29T21:18:49+09:00",
|
||||
"status": "deployment_blocked",
|
||||
"method": {
|
||||
"runtime": "Python 3.11",
|
||||
"decoder": "Pillow 12.2.0 Image.verify",
|
||||
"extension_contract": [
|
||||
"png",
|
||||
"jpg",
|
||||
"jpeg",
|
||||
"webp"
|
||||
],
|
||||
"privacy": {
|
||||
"raw_filenames_recorded": false,
|
||||
"raw_paths_recorded": false,
|
||||
"user_ids_recorded": false,
|
||||
"emails_recorded": false,
|
||||
"avatar_urls_recorded": false
|
||||
}
|
||||
},
|
||||
"source_union": {
|
||||
"source_root_count": 3,
|
||||
"object_count": 93,
|
||||
"inventory_sha256": "9d703126f78d4fc8330408835d76a7d680276240dc578d6fc9ca420c2f25e6aa",
|
||||
"total_size_bytes": 52973,
|
||||
"maximum_object_size_bytes": 28208,
|
||||
"same_name_content_conflicts": 0,
|
||||
"missing_database_references": 0,
|
||||
"decode_valid_count": 3,
|
||||
"decode_invalid_count": 90,
|
||||
"extension_mismatch_count": 0,
|
||||
"valid_formats": {
|
||||
"JPEG": 2,
|
||||
"PNG": 1
|
||||
},
|
||||
"valid_dimension_counts": {
|
||||
"1x1": 1,
|
||||
"225x225": 1,
|
||||
"512x512": 1
|
||||
}
|
||||
},
|
||||
"connected_database": {
|
||||
"database_target_sha256": "81fe4a2844b7340f21396931fa18580e24358f857a08cc60540ddf8a4f8789b5",
|
||||
"reference_count": 8,
|
||||
"unique_object_count": 8,
|
||||
"reference_set_sha256": "70926cf36ceb2375dd6c471bc59a38138460d8e4895ad1f2bddbcf1a49210d2b",
|
||||
"active_private_audio_count": 0,
|
||||
"found_object_count": 8,
|
||||
"missing_object_count": 0,
|
||||
"decode_valid_count": 2,
|
||||
"decode_invalid_count": 6,
|
||||
"valid_formats": {
|
||||
"JPEG": 2
|
||||
},
|
||||
"valid_dimension_counts": {
|
||||
"225x225": 1,
|
||||
"512x512": 1
|
||||
},
|
||||
"invalid_signature_counts": {
|
||||
"PNG": 6
|
||||
},
|
||||
"invalid_size_counts": {
|
||||
"70": 6
|
||||
},
|
||||
"invalid_unique_content_count": 1,
|
||||
"all_invalid_objects_have_identical_content": true
|
||||
},
|
||||
"release_gate": {
|
||||
"allowed": false,
|
||||
"reason": "Six of eight database-referenced avatar objects have a PNG signature but fail full image decoding. Preserve the bytes and database references until the owner chooses a recovery policy; do not claim the broken-image incident is resolved or deploy the current storage candidate."
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,134 @@
|
|||
{
|
||||
"schema": "vignette.avatar-image-resilience-recovery.v1",
|
||||
"evidence_id": "AVATAR-IMAGE-RESILIENCE-RECOVERY-2026-08-29",
|
||||
"captured_at": "2026-08-29T18:00:42+09:00",
|
||||
"privacy": {
|
||||
"raw_user_uuid_recorded": false,
|
||||
"raw_filename_recorded": false,
|
||||
"email_recorded": false,
|
||||
"resource_identity": "SHA-256 식별자만 기록"
|
||||
},
|
||||
"incident": {
|
||||
"root_cause": {
|
||||
"runtime_mode": "Python uvicorn 직접 실행 프로세스",
|
||||
"process_working_directory": "<release-root>/apps/api",
|
||||
"user_upload_dir_environment": "미설정",
|
||||
"effective_user_upload_dir": "cwd 기준 상대경로 uploads",
|
||||
"effective_storage_location": "<release-root>/apps/api/uploads",
|
||||
"docker_named_upload_volume_used_by_public_api": false,
|
||||
"failure_mechanism": "DB 경로는 승격 뒤에도 남았지만 업로드 파일은 이전 detached release root에 머물렀다. 새로 승격된 Python 직접 실행 런타임이 같은 상대 업로드 경로를 서로 다른 작업 디렉터리에서 해석해 파일을 찾지 못했다."
|
||||
},
|
||||
"initial_public_failure": {
|
||||
"phase": "복구 전",
|
||||
"http_status": 404,
|
||||
"response_content_type": "application/json",
|
||||
"browser_img_complete": true,
|
||||
"browser_img_natural_width": 0,
|
||||
"browser_img_natural_height": 0
|
||||
},
|
||||
"problem_object": {
|
||||
"path_sha256": "484dcee08b007741872ac48b49421fd45bf5a60b273e7a64c5b9a66dac85138a",
|
||||
"bytes": 28208,
|
||||
"content_sha256": "c5d06061bfa635d65f8285df242c5c9820dadcffcd785ed3f9b8619be24212c7"
|
||||
}
|
||||
},
|
||||
"recovery": {
|
||||
"source_release_commit_prefix": "bf5f7352cee0",
|
||||
"current_release_commit_prefix": "dba9b75a3887",
|
||||
"objects": [
|
||||
{
|
||||
"object_key": "recovered_object_1",
|
||||
"bytes": 18395,
|
||||
"content_sha256": "854403aed367db2d459e482a5e980fd22a846738b6034068cd7b4e80bbb7f34d"
|
||||
},
|
||||
{
|
||||
"object_key": "problem_object",
|
||||
"bytes": 28208,
|
||||
"content_sha256": "c5d06061bfa635d65f8285df242c5c9820dadcffcd785ed3f9b8619be24212c7"
|
||||
}
|
||||
],
|
||||
"object_count": 2,
|
||||
"total_bytes": 46603,
|
||||
"source_and_current_content_hashes_match": true,
|
||||
"current_release_storage_path_kind": "release root 내부의 일반 디렉터리",
|
||||
"current_release_git_dirty_paths": 0,
|
||||
"final_public_probe": {
|
||||
"http_status": 200,
|
||||
"content_type": "image/jpeg",
|
||||
"bytes": 28208,
|
||||
"content_sha256": "c5d06061bfa635d65f8285df242c5c9820dadcffcd785ed3f9b8619be24212c7",
|
||||
"matches_recovered_problem_object": true
|
||||
},
|
||||
"authenticated_browser_dom": {
|
||||
"route": "/admin/users",
|
||||
"image_complete": true,
|
||||
"natural_width": 512,
|
||||
"natural_height": 512,
|
||||
"computed_display": "block",
|
||||
"visible": true,
|
||||
"raw_source_recorded": false
|
||||
}
|
||||
},
|
||||
"ui_resilience": {
|
||||
"files": [
|
||||
"apps/web/src/components/avatar/ResilientImage.tsx",
|
||||
"apps/web/src/components/shell/Topbar.tsx",
|
||||
"apps/web/src/pages/Settings.tsx",
|
||||
"apps/web/src/pages/Onboarding.tsx",
|
||||
"apps/web/src/lib/auth.tsx",
|
||||
"apps/web/e2e/image-resilience.spec.ts",
|
||||
"apps/web/e2e/uc-settings-consent.spec.ts"
|
||||
],
|
||||
"covered_states": [
|
||||
"유효한 외부 HTTPS 이미지",
|
||||
"API 상대경로 이미지",
|
||||
"빈 소스",
|
||||
"허용하지 않는 비웹 scheme",
|
||||
"HTTP 404",
|
||||
"디코딩 또는 로드 실패",
|
||||
"지연 응답 중 0x0 비노출 로딩 상태",
|
||||
"업로드 뒤 소스 교체",
|
||||
"접근 가능한 이니셜 fallback"
|
||||
],
|
||||
"verification": {
|
||||
"typecheck": {
|
||||
"command": "npm run typecheck",
|
||||
"result": "passed"
|
||||
},
|
||||
"production_build": {
|
||||
"command": "npm run build",
|
||||
"result": "passed",
|
||||
"vite_modules_transformed": 148,
|
||||
"deterministic_runs": 2,
|
||||
"dist_file_count": 601,
|
||||
"dist_manifest_sha256": "35c48fbf844efd0514282b76937bbef3b1f173992313c7d377363d71f0806d89"
|
||||
},
|
||||
"design_ssot": {
|
||||
"command": "npm run check:design-ssot",
|
||||
"result": "passed"
|
||||
},
|
||||
"focused_e2e": {
|
||||
"command": "npx playwright test e2e/image-resilience.spec.ts e2e/uc-settings-consent.spec.ts --project=chromium-desktop --project=chromium-mobile",
|
||||
"result": "40/40 passed"
|
||||
},
|
||||
"layout_and_tabs_e2e": "17/17 passed",
|
||||
"session_layout_e2e": "8/8 passed",
|
||||
"scoped_diff_check": "passed"
|
||||
},
|
||||
"release_candidate": {
|
||||
"base_commit": "5bf89ff4ee971940882695d27f153e1460df295e",
|
||||
"commit": "35a62fda90d3ba56707acfeb3fb94090d3949f8e",
|
||||
"tree": "d8634141289b6a0007954671ece5fb7dba9c2881",
|
||||
"author": "Yun Chan",
|
||||
"clean": true,
|
||||
"protected_api_or_generated_paths_changed": 0,
|
||||
"remote_push_and_pages_deploy": "pending_explicit_external_write_approval"
|
||||
}
|
||||
},
|
||||
"stable_upload_contract": {
|
||||
"status": "pending",
|
||||
"current_recovery_scope": "현재 release root에 대한 시점 복구",
|
||||
"remaining_risk": "USER_UPLOAD_DIR가 cwd 상대경로인 동안 이후 승격이나 다른 release root 재시작에서 업로드가 다시 고립될 수 있다.",
|
||||
"required_follow_up": "사용자 업로드를 release root 밖의 절대 영속 상태 디렉터리로 옮기고, 모든 부팅·watchdog 진입점에서 USER_UPLOAD_DIR를 주입·검증한 뒤 inventory, 콘텐츠 해시 검증, 중복 제거, rollback, 공개 재시작 증거를 포함해 마이그레이션한다."
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,63 @@
|
|||
{
|
||||
"schema_version": 1,
|
||||
"status": "verified",
|
||||
"captured_at_utc": "2026-08-29T08:09:03.6788264Z",
|
||||
"scope": "REQ-001 live Google account, administrator entitlement and restored-data acceptance",
|
||||
"current_session": {
|
||||
"frontend_origin": "https://vignette.chanpaca.net",
|
||||
"settings_route": "/settings",
|
||||
"login_email": "yunchan8804@gmail.com",
|
||||
"display_name": "윤찬",
|
||||
"admin_navigation_present": true,
|
||||
"admin_continuous_improvement_route_accessible": true,
|
||||
"onboarding_redirect_absent": true,
|
||||
"observation_mode": "read-only existing authenticated browser session"
|
||||
},
|
||||
"restored_data": {
|
||||
"learner_history_route": "/learn/history",
|
||||
"visible_history_count": 20,
|
||||
"visible_review_required_count": 6,
|
||||
"canonical_database_counts_from_recovery_ledger": {
|
||||
"sessions": 21,
|
||||
"turns": 64,
|
||||
"evaluations": 6
|
||||
},
|
||||
"recovery_ledger": "docs/ops/evidence/google-account-data-recovery-2026-08-29.json",
|
||||
"owner_confirmed_data_visibility": true
|
||||
},
|
||||
"public_login": {
|
||||
"production_deployment_id": "0c60261e-cb37-482d-ba42-d91586194c48",
|
||||
"source_commit": "5bf89ff4ee971940882695d27f153e1460df295e",
|
||||
"source_tree": "29f76aeb37d911a6947f96020719826f606f9c58",
|
||||
"single_google_cta_count": 1,
|
||||
"legacy_google_selector_count": 0,
|
||||
"dev_login_count": 0,
|
||||
"cta_visible": true,
|
||||
"cta_enabled": true,
|
||||
"cta_size_px": {
|
||||
"width": 364,
|
||||
"height": 60
|
||||
},
|
||||
"horizontal_overflow_px": 0,
|
||||
"auth_config_status": 200,
|
||||
"google_enabled": true,
|
||||
"allowed_email_domains": [],
|
||||
"dev_login_enabled": false,
|
||||
"health_status": 200,
|
||||
"health_environment": "prod",
|
||||
"health_db": true,
|
||||
"health_engine": true,
|
||||
"public_auth_playwright": "1 passed",
|
||||
"login_policy_playwright": "1 passed"
|
||||
},
|
||||
"acceptance": {
|
||||
"requirement": "REQ-001",
|
||||
"status": "complete",
|
||||
"basis": "The existing live session identifies yunchan8804@gmail.com, exposes the administrator console without onboarding, shows restored history, and the owner confirmed that the recovered data is visible. The current production login surface independently keeps the unrestricted single Google CTA contract."
|
||||
},
|
||||
"safety": {
|
||||
"agent_started_new_oauth": false,
|
||||
"agent_selected_google_account": false,
|
||||
"agent_changed_account_or_data": false
|
||||
}
|
||||
}
|
||||
82
docs/ops/evidence/req008-reboot-preflight-2026-08-29.json
Normal file
82
docs/ops/evidence/req008-reboot-preflight-2026-08-29.json
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
{
|
||||
"evidence_id": "REQ008-REBOOT-PREFLIGHT-2026-08-29",
|
||||
"captured_at": "2026-08-29T17:40:03+09:00",
|
||||
"mode": "read-only-preflight",
|
||||
"status": "go_pending_explicit_reboot_approval",
|
||||
"mutations_performed": [],
|
||||
"release_pin": {
|
||||
"root": "D:\\workspace\\vignette-public-runtime-dba9b75a3887",
|
||||
"commit": "dba9b75a388783b11d821257e2f91fbcc30bbe1a",
|
||||
"tree": "14cd4607b07f93e06cef8c912694ae1da8517b8c",
|
||||
"detached_head": true,
|
||||
"dirty_paths": 0,
|
||||
"boot_script_sha256": "04dd9144a3943822d0f794e2973f59299edc7595bc2d85919275a07fa5225a54",
|
||||
"watchdog_script_sha256": "904b4751ecf1b1db5e41dc7b85ad2f88e7c6a55a3349db6bfa8988ad9babfc56",
|
||||
"start_script_sha256": "67d2a41ac4a366888b324e40e1c77b75e045505b4bd888c72c3a476a2c1f5c05"
|
||||
},
|
||||
"scheduled_tasks": {
|
||||
"account": "encep",
|
||||
"logon_type": "Interactive",
|
||||
"run_level": "Limited",
|
||||
"boot_task": {
|
||||
"state": "Ready",
|
||||
"last_result": 0,
|
||||
"current_pin_run_proven": false,
|
||||
"reason": "마지막 실행은 2026-08-28 18:14이고 현재 release root는 2026-08-29 15:36에 생성되어 실제 재부팅 검증이 남아 있다."
|
||||
},
|
||||
"watchdog_task": {
|
||||
"state": "Ready",
|
||||
"last_run": "2026-08-29T17:33:54+09:00",
|
||||
"last_result": 0,
|
||||
"failcount": 0
|
||||
}
|
||||
},
|
||||
"current_probes": {
|
||||
"listeners": [55432, 8001, 5174, 9099, 9882, 9883],
|
||||
"components_healthy": [
|
||||
"db",
|
||||
"api",
|
||||
"engine",
|
||||
"web-preview",
|
||||
"voice-api",
|
||||
"voice-sidecars",
|
||||
"cloudflared",
|
||||
"public-api"
|
||||
],
|
||||
"local_api_http": 200,
|
||||
"public_api_http": 200,
|
||||
"public_login_http": 200,
|
||||
"public_unauthenticated_me_http": 401,
|
||||
"environment": "prod",
|
||||
"db": true,
|
||||
"engine": true,
|
||||
"voice": {
|
||||
"stt_provider": "local_whisper",
|
||||
"stt_model": "small",
|
||||
"tts_provider": "melotts",
|
||||
"tts_model": "melotts-korean"
|
||||
},
|
||||
"google_auth_enabled": true,
|
||||
"allowed_domains": [],
|
||||
"dev_login_enabled": false
|
||||
},
|
||||
"windows_reboot_state": {
|
||||
"cbs_reboot_required": false,
|
||||
"windows_update_reboot_required": false,
|
||||
"pending_file_rename_operation_strings": 74,
|
||||
"interpretation": "대기 중인 rename 또는 delete 쌍이 다음 재부팅에 적용될 수 있다는 뜻이며 장애나 파일 74개의 증거는 아니다."
|
||||
},
|
||||
"remaining_gate": {
|
||||
"requires_user_confirmation": true,
|
||||
"confirmation_scope": "저장하지 않은 작업이 없고 지금 Windows를 재부팅해도 되는지",
|
||||
"required_login": "encep",
|
||||
"pass_conditions": [
|
||||
"이번 부팅 이후 boot와 watchdog task가 각각 result 0",
|
||||
"새 boot-public-runtime.log에 boot OK",
|
||||
"watchdog failcount 0",
|
||||
"현재 release pin의 CheckOnly가 8개 구성요소 모두 healthy",
|
||||
"local 및 public API, engine, web가 모두 200",
|
||||
"prod, db=true, engine=true와 voice provider/model 계약 유지"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,93 @@
|
|||
{
|
||||
"schema_version": 1,
|
||||
"status": "review_gates_remain",
|
||||
"captured_at_utc": "2026-08-29T08:24:45.2582699Z",
|
||||
"git_head": "ac9b7026881139780938f4c4f2b89a235b0a0c08",
|
||||
"workbook": {
|
||||
"path": "outputs/01a04217-f73b-7303-b597-401fa7f5d290/Vignette_개선관리_완료.xlsx",
|
||||
"previous_sha256": "362afd218aa9dd67c22cccd5125b8efb18cfaff06945af1ce550e868a3fce39f",
|
||||
"sha256": "c832547f30ae0664e54b302c8e9f62cc157f31022ad158d17803d8cac988d8bd",
|
||||
"bytes": 2751365,
|
||||
"sheet_count": 6,
|
||||
"requirement_count": 11,
|
||||
"formula_count": 38,
|
||||
"formula_error_count": 0,
|
||||
"summary": {
|
||||
"complete": 9,
|
||||
"review": 2,
|
||||
"hold": 0,
|
||||
"total": 11
|
||||
}
|
||||
},
|
||||
"sidecar": {
|
||||
"path": "outputs/01a04217-f73b-7303-b597-401fa7f5d290/Vignette_개선관리_완료.xlsx.inspect.ndjson",
|
||||
"sha256": "8c9618f0931219449af9ed127faf886c53f31823eea613671184204de44ba610",
|
||||
"bytes": 313115,
|
||||
"kind_counts": {
|
||||
"workbook": 1,
|
||||
"sheet": 6,
|
||||
"table": 6,
|
||||
"region": 9,
|
||||
"formula": 38,
|
||||
"computedStyle": 436,
|
||||
"drawing": 4,
|
||||
"conditionalFormatting": 8
|
||||
}
|
||||
},
|
||||
"status_changes": {
|
||||
"REQ-001": {
|
||||
"from": "검토",
|
||||
"to": "완료",
|
||||
"evidence": "docs/ops/evidence/google-account-live-browser-proof-2026-08-29.json",
|
||||
"basis": "The existing authenticated production session identifies yunchan8804@gmail.com, preserves administrator access without onboarding, exposes restored history, and the owner accepted the recovered data visibility. The current production login surface independently keeps one unrestricted Google CTA."
|
||||
}
|
||||
},
|
||||
"c001": {
|
||||
"improvement_status": "검토",
|
||||
"technical_status": "verified",
|
||||
"clinical_status": "pending_external_review",
|
||||
"machine_gate": "pending-valid",
|
||||
"package_match": "PACKAGE_MATCH",
|
||||
"canonical_checker": {
|
||||
"ok": true,
|
||||
"review_complete": false,
|
||||
"external_review_replaced": false
|
||||
},
|
||||
"external_input_cells_preserved_empty": 46,
|
||||
"external_review_fabricated": false
|
||||
},
|
||||
"builder_hardening": {
|
||||
"canonical_checker_transport": "The checker result is passed explicitly to the workbook builder because nested child-process creation is denied in the managed runtime. Missing or malformed input remains fail-closed.",
|
||||
"named_gate_predicates": true,
|
||||
"failed_predicates_rendered_when_present": true,
|
||||
"post_export_assertions": [
|
||||
"C-001 machine gate",
|
||||
"C-001 package match",
|
||||
"C-001 clinical status",
|
||||
"C-001 final review status",
|
||||
"46 external input cells remain empty",
|
||||
"38 formulas",
|
||||
"summary 9 complete, 2 review, 0 hold, 11 total",
|
||||
"REQ-001 complete"
|
||||
]
|
||||
},
|
||||
"verification": {
|
||||
"artifact_tool_import_export_reimport": "passed",
|
||||
"structured_extraction": "6 sheets, 11 unique requirements, 38 formulas",
|
||||
"formula_error_scan": "0 matches",
|
||||
"all_sheet_visual_pass": 6,
|
||||
"focused_c001_visual_pass": 4,
|
||||
"render_count": 10,
|
||||
"render_set_byte_identical_to_visually_checked_candidate": true,
|
||||
"stale_deployment_ids_found": 0,
|
||||
"public_pages_deployment_id": "0c60261e-cb37-482d-ba42-d91586194c48",
|
||||
"public_pages_source": "5bf89ff4ee971940882695d27f153e1460df295e",
|
||||
"public_api_commit": "dba9b75a388783b11d821257e2f91fbcc30bbe1a",
|
||||
"public_api_tree": "14cd4607b07f93e06cef8c912694ae1da8517b8c"
|
||||
},
|
||||
"remaining_gates": {
|
||||
"C-001": "A real qualified external clinical reviewer must complete the six case verdict blocks, six youth-specific answers, ten approval fields and signed evidence. This evidence cannot be fabricated or self-approved.",
|
||||
"REQ-008": "An actual Windows restart and post-boot API, engine, tunnel and browser recovery smoke are still required."
|
||||
},
|
||||
"historical_evidence_mutated": false
|
||||
}
|
||||
59
docs/ops/evidence/workbook-runtime-sync-2026-08-29.json
Normal file
59
docs/ops/evidence/workbook-runtime-sync-2026-08-29.json
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
{
|
||||
"schema_version": 1,
|
||||
"status": "review_gates_remain",
|
||||
"captured_at_utc": "2026-08-29T07:19:40.531Z",
|
||||
"git_head": "ffadc8bd49ca41ade5b84590fe7effc53c7b6a0b",
|
||||
"workbook": {
|
||||
"path": "outputs/01a04217-f73b-7303-b597-401fa7f5d290/Vignette_개선관리_완료.xlsx",
|
||||
"previous_sha256": "0a64d1bcdfd0c1727ebd4279b58b3077227ecba2e30d68009cf9ba8d310b1177",
|
||||
"sha256": "aab6ef52c388e3112be4c88f6fb21b9bceb9ae0604e099549fbd2b7209b4e26a",
|
||||
"bytes": 2751636,
|
||||
"sheet_count": 6,
|
||||
"formula_error_count": 0,
|
||||
"summary": {
|
||||
"complete": 8,
|
||||
"review": 3,
|
||||
"total": 11
|
||||
}
|
||||
},
|
||||
"edit_scope": {
|
||||
"values_only": true,
|
||||
"formats_preserved": true,
|
||||
"formulas_changed": false,
|
||||
"statuses_changed": false,
|
||||
"ranges": [
|
||||
"개발·기능 이슈!M5:M12",
|
||||
"임상·교육 이슈!M6:M7"
|
||||
],
|
||||
"purpose": "Replace stale deployment references with the current API dba9b75a and Pages ef48c0ae evidence while preserving unresolved review gates"
|
||||
},
|
||||
"verification": {
|
||||
"artifact_tool_import_export_reimport": "passed",
|
||||
"all_sheet_visual_pass": 6,
|
||||
"post_edit_focused_visual_pass": 4,
|
||||
"clinical_checker": "passed; pending_external_review; review_complete=false",
|
||||
"public_pages_deployment_id": "ef48c0ae-374e-41fc-b6fd-03f69b26e946",
|
||||
"public_auth_e2e": "1 passed",
|
||||
"public_api_commit": "dba9b75a388783b11d821257e2f91fbcc30bbe1a",
|
||||
"public_api_tree": "14cd4607b07f93e06cef8c912694ae1da8517b8c"
|
||||
},
|
||||
"c001_artifact_continuity": {
|
||||
"historical_preflight_path": "docs/ops/evidence/c-001-clinical-technical-preflight-2026-08-28.json",
|
||||
"historical_preflight_artifact_count": 14,
|
||||
"historical_preflight_current_match_count": 13,
|
||||
"technical_artifact_match_count": 13,
|
||||
"technical_artifact_total": 13,
|
||||
"historical_workbook_sha256": "0a64d1bcdfd0c1727ebd4279b58b3077227ecba2e30d68009cf9ba8d310b1177",
|
||||
"current_workbook_sha256": "aab6ef52c388e3112be4c88f6fb21b9bceb9ae0604e099549fbd2b7209b4e26a",
|
||||
"current_package_match_count": 14,
|
||||
"current_package_artifact_count": 14,
|
||||
"continuity_rule": "13 unchanged technical artifacts from the immutable historical preflight plus the current workbook hash recorded by this evidence",
|
||||
"historical_record_mutated": false,
|
||||
"external_review_state_changed": false
|
||||
},
|
||||
"remaining_gates": {
|
||||
"C-001": "Real external clinical reviewer verdicts, reviewer identity and qualifications, youth-specific answers, approval decision and signed evidence are absent",
|
||||
"REQ-001": "Live yunchan8804@gmail.com OAuth callback and restored admin/data browser proof are absent",
|
||||
"REQ-008": "Actual Windows reboot and post-boot API/engine/tunnel/browser recovery proof are absent"
|
||||
}
|
||||
}
|
||||
350
docs/ops/handoff-goal-production-2026-08-29.md
Normal file
350
docs/ops/handoff-goal-production-2026-08-29.md
Normal file
|
|
@ -0,0 +1,350 @@
|
|||
# Vignette 전체 개선 목표 · 정식 배포 핸드오프
|
||||
|
||||
작성 시각: 2026-08-29 21:18 KST
|
||||
작업 루트: `D:\workspace\vignette`
|
||||
Goal ID: `01a04217-f73b-7303-b597-401fa7f5d290`
|
||||
Goal: `엑셀 파일의 모든 내용을 마친다`
|
||||
|
||||
## 0. 2026-08-30 재개 후 현행 상태
|
||||
|
||||
이 절이 아래 2026-08-29 종료 스냅샷보다 우선한다. 현재 이어받기 Goal ID는
|
||||
`01a04dd0-93ef-7d02-a9cb-40682fd0988a`다.
|
||||
|
||||
- `preserved_total_size_bytes=52,973`과 decode 3/90, 현재 DB 참조 decode 2/6을 initializer→manifest v3→
|
||||
bootstrap/cutover/task-recovery/final receipt→API health까지 결속했다.
|
||||
- manifest 이후 생성된 UUID형 아바타도 URL 기록·현재 DB 참조·GET/HEAD에서 3MB 제한, Pillow full decode,
|
||||
확장자-format 일치를 다시 검사한다. 검증한 동일 bytes를 응답해 검사 뒤 재오픈 경쟁을 없앴고, 사후 손상은
|
||||
current invalid/health fallback과 public 404로 닫는다. 원본 bytes와 DB URL은 삭제하지 않았다.
|
||||
- API 전체 `1074 passed / 1 skipped`, gateway `68 passed`, runtime/bootstrap 통합 `154 tests OK`, web typecheck/build,
|
||||
이미지+사람 게이트 route E2E가 통과했다. SSOT checker와 scoped diff check도 통과했다.
|
||||
- Codex 내장 브라우저의 격리 local stateful fixture에서 8.5초 뒤 overlay 0·heading 정상, content
|
||||
`keep_quarantine` effect 0, 증거 없는 release 승인 disabled→`reject` effect 0, 증거 4종 promote 승인
|
||||
lifecycle effect 정확히 1을 확인했다. 이는 실DB/public proof를 대신하지 않는다.
|
||||
- clean 통합 브랜치는 `YunChan/goal-production-20260830`이며 UI 공통화 두 커밋 위에 runtime·usage·G8·auth·
|
||||
관리자 UI·온보딩 계정 전환을 좁은 커밋으로 결합했다. push·Pages 배포·public runtime/task 변경은 아직 없다.
|
||||
- 풀스택 `layout-visual-gate` 15/15와 `session-layout` 8/8은 Docker Desktop이 꺼져 있고 활성 public watchdog이
|
||||
daemon 기동 즉시 기존 runtime 복구를 시도할 수 있어 승인 대기다. 승인 시 watchdog을 일시 중지·비활성화하고
|
||||
고유 DB/container/volume과 56432/58000/55173만 사용한 뒤 exact cleanup, Docker 종료, watchdog 원상복구를 수행한다.
|
||||
- 최종 외부 게이트는 여전히 C001 적격 외부 임상 검수와 REQ-008 실제 Windows 재부팅 smoke다. 둘 다 추정 증거로
|
||||
닫지 않는다.
|
||||
|
||||
## 1. 이전 세션 종료 결정과 당시 결론
|
||||
|
||||
사용자가 세션 장기화를 이유로 상세 핸드오프 후 익일 재개를 지시했다. 21:15 KST부터 모든 에이전트의 새 편집을 중단했고, 로컬 stage·commit·push·Cloudflare Pages 배포·라이브 DB 변경·API/tunnel 재시작·예약 작업 변경·PC 재부팅은 수행하지 않았다.
|
||||
|
||||
당시 소스는 **배포 가능 GREEN이 아니었다**. 특히 종료 직전 실제 이미지 디코딩 감사를 추가로 수행한 결과, 연결된 운영 DB가 참조하는 아바타 8개 중 6개가 깨진 동일 PNG payload라는 사실을 확인했다. 파일 존재와 해시만 보존하면 사용자가 신고한 깨진 이미지가 그대로 남았다. 이 결함은 위 2026-08-30 후보에서 public 404/fallback으로 닫았지만 아직 정식 배포 전이다.
|
||||
|
||||
Goal은 완료 처리하지 않았다. 기술 구현·정식 배포·실제 재부팅 증명과 별개로 C001 적격 외부 임상 검수도 여전히 인간 게이트다.
|
||||
|
||||
## 2. 완료된 엑셀 산출물
|
||||
|
||||
- 산출물: `D:\workspace\vignette\outputs\01a04217-f73b-7303-b597-401fa7f5d290\Vignette_개선관리_완료.xlsx`
|
||||
- SHA256: `c832547f30ae0664e54b302c8e9f62cc157f31022ad158d17803d8cac988d8bd`
|
||||
- 크기: 2,751,365 bytes
|
||||
- 검증: 6 sheets, 11 requirements, 38 formulas, formula error 0, inspection files 18
|
||||
- 상태 집계: 완료 9, 검토 2, 보류 0
|
||||
- 남은 두 검토 항목:
|
||||
- C001: 적격 외부 임상 검수 입력과 서명 증빙
|
||||
- REQ-008: 실제 PC 재부팅 뒤 예약 작업 기반 자동복구와 공개 smoke
|
||||
- C001 셀 상태: `G23=pending-valid`, `G24=PACKAGE_MATCH`, `B24=pending_external_review`, `J3=검토`; 외부 검수 입력 46칸은 의도적으로 비워 두었다.
|
||||
- 임상 검수 내용을 추정하거나 가짜로 작성하면 안 된다.
|
||||
|
||||
## 3. UI 작업 상태
|
||||
|
||||
깨끗한 UI 후보 워크트리는 아래와 같다.
|
||||
|
||||
- 경로: `D:\workspace\vignette-ui-image-release-20260829`
|
||||
- 브랜치: `YunChan/ui-image-resilience-release-20260829`
|
||||
- HEAD: `a73b9efff77e3c575e32976bbe4f1ed404e103f0`
|
||||
- 관련 커밋:
|
||||
- `35a62fda` 탭 구조와 이미지 복구를 공통화
|
||||
- `a73b9eff` 분석 탭과 축어록 계층을 정돈
|
||||
- 워크트리 상태: clean
|
||||
- 로컬 미리보기: `http://127.0.0.1:5188`
|
||||
|
||||
반영된 브라우저 코멘트:
|
||||
|
||||
- 학습 대시보드의 불필요한 안쪽 컨테이너 스타일 정리
|
||||
- 교수자 요약 카드 상단 간격 분리
|
||||
- 관리자 사용자 탭의 의미 없는 외곽 컨테이너 제거 및 공통 탭 컴포넌트화
|
||||
- Topbar 프로필 이미지 실패 시 깨진 이미지 아이콘 대신 안전한 fallback 표시
|
||||
- 학습자 상세 분석의 4개 탭이 한 줄을 유지하도록 수정
|
||||
- 회기 축어록 내담자 발화의 불필요한 테두리 제거
|
||||
- Google 로그인 단일 진입 UX 정리
|
||||
|
||||
이 UI는 로컬 내장 브라우저에서 시각 확인했지만 production에는 배포하지 않았다. 당시 내장 브라우저에는 로컬 분석/축어록 탭과 production 관리자 탭이 열려 있었다. 익일에는 탭 존재를 가정하지 말고 새로 열어 확인한다.
|
||||
|
||||
## 4. 운영 데이터와 업로드 보존 감사
|
||||
|
||||
### 4.1 정확한 소스 경계
|
||||
|
||||
아래 세 root의 `profile-avatars`만 source allowlist로 사용했다.
|
||||
|
||||
1. `D:\workspace\vignette\apps\api\uploads`
|
||||
2. `D:\workspace\vignette-public-runtime-bf5f7352\apps\api\uploads`
|
||||
3. `D:\workspace\vignette-public-runtime-dba9b75a3887\apps\api\uploads`
|
||||
|
||||
결과:
|
||||
|
||||
- union object count: 93
|
||||
- union inventory SHA256: `9d703126f78d4fc8330408835d76a7d680276240dc578d6fc9ca420c2f25e6aa`
|
||||
- union total bytes: 52,973
|
||||
- maximum object bytes: 28,208
|
||||
- same-name content conflict: 0
|
||||
- invalid/nested/reparse entry: 0
|
||||
- strict server-generated UUID-token filename shape: 93/93
|
||||
- DB references found in union: 8/8
|
||||
|
||||
연결 DB의 개인정보 없는 결속값:
|
||||
|
||||
- database target SHA256: `81fe4a2844b7340f21396931fa18580e24358f857a08cc60540ddf8a4f8789b5`
|
||||
- reference count: 8
|
||||
- unique referenced objects: 8
|
||||
- reference-set SHA256: `70926cf36ceb2375dd6c471bc59a38138460d8e4895ad1f2bddbcf1a49210d2b`
|
||||
- active private multimodal audio: 0
|
||||
|
||||
### 4.2 종료 직전 발견한 손상 이미지
|
||||
|
||||
Pillow 12.2.0의 실제 decode/verify와 확장자-format 일치를 파일명·경로·사용자 ID·이메일·URL을 출력하지 않고 검사했다.
|
||||
|
||||
- 전체 93개: 정상 decode 3, 실패 90
|
||||
- 정상 3개: JPEG 2개, PNG 1개; 크기 225×225, 512×512, 1×1
|
||||
- 운영 DB 참조 8개: 정상 2, 실패 6, missing 0
|
||||
- 정상 참조 2개: JPEG, 225×225 및 512×512
|
||||
- 실패 참조 6개: 모두 70 bytes, PNG signature는 있으나 full decode 실패
|
||||
- 실패 6개는 동일한 content 한 종류다.
|
||||
|
||||
근거 파일: `docs/ops/evidence/avatar-decode-audit-2026-08-29.json`
|
||||
|
||||
이 결과의 의미:
|
||||
|
||||
- “93개를 덮어쓰기 없이 복사했다”만으로는 깨진 이미지 문제가 해결되지 않는다.
|
||||
- 6개 손상 payload와 해당 DB reference를 승인 없이 삭제·초기화하면 안 된다.
|
||||
- 원본을 찾을 수 있으면 복구하고, 찾을 수 없으면 손상 bytes는 private forensic 보존하되 public static 응답은 404/fallback으로 보내는 정책이 권장된다.
|
||||
- 현재 UI 후보의 `ResilientImage`가 시각적 fallback은 제공하지만, backend가 손상 파일을 정상 이미지처럼 공개하는 문제와 데이터 복구 정책은 별도로 닫아야 한다.
|
||||
- 익일 첫 결정 게이트는 다음 둘 중 하나다.
|
||||
1. 권장: 손상 bytes와 DB reference를 보존하고, manifest에 decode 상태를 결속해 손상 객체는 public serve하지 않으며 UI fallback을 사용한다. 이후 원본 복구 또는 소유자 승인 기반 정리를 별도 수행한다.
|
||||
2. 엄격: 6개 원본을 복구할 때까지 API cutover 자체를 fail-closed로 막는다.
|
||||
|
||||
## 5. 업로드·DB·재부팅 복구 코드 상태
|
||||
|
||||
2026-08-29 dirty master에서 시작한 다음 안전 계약은 현재 clean 통합 브랜치에 좁은 커밋으로 결합돼 있다.
|
||||
|
||||
- exact 3-root union + caller-pinned count/inventory digest
|
||||
- source copy 전후 재스캔과 create-only copy
|
||||
- DB reference 8/8 보존 확인
|
||||
- manifest v3의 privacy-safe path/content hash, total bytes, decode 상태와 DB target binding
|
||||
- 새 API가 자기 pool의 repeatable-read snapshot으로 DB target과 현재 avatar refs를 DDL 전에 검증
|
||||
- 모든 신규 physical DB connection이 target digest를 재검증
|
||||
- production Uvicorn `--workers 1` 고정
|
||||
- upload/PATCH/onboarding write lease와 freeze drain
|
||||
- unrelated profile PATCH가 stale avatar URL을 되살리지 못하도록 수정
|
||||
- 신규 업로드는 UUID(user id)+random token 이름, create-only hard-link publish
|
||||
- static 공개 범위는 decode-valid manifest-preserved path 또는 full decode를 재통과한 strict runtime-generated filename으로 제한
|
||||
- preserved bytes immutable memory cache와 신규 업로드 single-read response로 per-request 전체 hash DoS와 disk reopen TOCTOU 제거
|
||||
- boot/watchdog task를 새 정의로 disabled 설치 → exact action 계약 확인 → 둘을 함께 enable
|
||||
- 두 번째 task 설치/enable 실패 시 두 task 모두 disabled로 보상
|
||||
- exact root task path `\` 결속
|
||||
|
||||
재개 후 위 미완료 연결은 해소했다. initializer manifest/result와 bootstrap의 cutover/task-recovery/final passed receipt가
|
||||
모두 `preserved_total_size_bytes=52,973`과 decode proof를 교차 검증한다. 남은 것은 승인된 격리 풀스택 E2E와 정식
|
||||
배포·public browser proof이지 manifest 생산자/소비자 계약 불일치가 아니다.
|
||||
|
||||
## 6. 마지막 검증 결과
|
||||
|
||||
2026-08-30 재개 후 보고:
|
||||
|
||||
- 전체 API: `1074 passed / 1 skipped`
|
||||
- gateway: `68 passed`
|
||||
- runtime/bootstrap 결합: `154 tests OK`
|
||||
- API runtime focused: `31 passed`
|
||||
- web typecheck/build: PASS
|
||||
- 이미지 복구+사람 게이트 focused browser E2E: PASS
|
||||
- Codex 내장 브라우저 local stateful 사람 게이트: overlay 0, 보류/반려 effect 0, 승인 effect 1
|
||||
- SSOT checker와 scoped `git diff --check`: PASS
|
||||
|
||||
주의:
|
||||
|
||||
- full API·web build/typecheck·route/internal-browser proof는 현재 후보 기준이다.
|
||||
- 실제 Postgres를 쓰는 `layout-visual-gate` 15/15와 `session-layout` 8/8, production browser proof는 아직 없다.
|
||||
- Python 3.12/3.14의 `tempfile.TemporaryDirectory`가 현재 sandbox ACL과 충돌해 생성 직후 접근 거부를 냈다. 동일 테스트는 Python 3.11에서 정상 통과했다. 익일 테스트는 `py -3.11` 또는 `C:\Users\encep\AppData\Local\Programs\Python\Python311\python.exe`를 사용한다.
|
||||
- `D:\workspace\vignette\tmp` 아래 접근 거부 임시 디렉터리들은 테스트 환경 잔재다. 광범위 재귀 삭제하지 말고, 필요 시 exact path와 ACL을 확인한 뒤 별도로 정리한다.
|
||||
|
||||
## 7. 이전 세션 Git·워크트리 기준선
|
||||
|
||||
2026-08-29 21:15 KST 기준:
|
||||
|
||||
- shared checkout: `D:\workspace\vignette`
|
||||
- branch: `master`
|
||||
- HEAD: `ac9b7026881139780938f4c4f2b89a235b0a0c08`
|
||||
- HEAD tree: `b07cb4dd6b9b33b650b59b24fc1bc4b8bf2b48f8`
|
||||
- `origin/master`보다 20 commits ahead
|
||||
- shared checkout은 사용자 작업과 이번 작업이 섞인 큰 dirty tree다. `git status --untracked-files=all`은 접근 거부 tmp를 포함해 547 entries를 셌다.
|
||||
- `git add .`, `git commit -a`, whole-tree copy는 금지한다.
|
||||
|
||||
관련 worktree:
|
||||
|
||||
- UI clean candidate: `D:\workspace\vignette-ui-image-release-20260829`, `a73b9eff`
|
||||
- old runtime-storage candidate: `D:\workspace\vignette-runtime-storage-release-20260829`, `dba9b75a`, dirty; 현행 source of truth로 사용하지 않는다.
|
||||
- current public runtime: `D:\workspace\vignette-public-runtime-dba9b75a3887`, detached `dba9b75a`
|
||||
- current scheduled tasks `VignettePublicRuntime`, `VignettePublicRuntimeWatchdog`는 마지막 확인 시 enabled/Ready이며 여전히 old `dba9b75a` runtime을 가리킨다.
|
||||
- current Pages production은 deployment `0c60261e`, source `5bf89ff`였다. UI 후보는 아직 미배포다.
|
||||
|
||||
## 8. 현재 변경 파일 경계
|
||||
|
||||
API 소유 범위:
|
||||
|
||||
- `apps/api/app/config.py`
|
||||
- `apps/api/app/db.py`
|
||||
- `apps/api/app/main.py`
|
||||
- `apps/api/app/routes/users.py`
|
||||
- `apps/api/app/upload_runtime.py`
|
||||
- `apps/api/app/upload_storage.py`
|
||||
- `apps/api/app/test_upload_storage_contract.py`
|
||||
- `apps/api/app/test_engine_health_contract.py`
|
||||
- `scripts/validate-public-runtime-upload-manifest.py`
|
||||
|
||||
runtime/bootstrap 핵심 범위:
|
||||
|
||||
- `scripts/initialize-public-runtime-upload-root.py`
|
||||
- `scripts/initialize-public-runtime-upload-root.ps1`
|
||||
- `scripts/bootstrap-legacy-public-runtime-upload-root.ps1`
|
||||
- `scripts/validate-public-runtime-offline-quiescence.py`
|
||||
- `scripts/probe-public-runtime-database-identity.py`
|
||||
- `scripts/probe-public-runtime-upload-root.py`
|
||||
- `scripts/public_runtime_database_identity.py`
|
||||
- `scripts/public-runtime-upload-root.ps1`
|
||||
- `scripts/public-runtime-task-maintenance.ps1`
|
||||
- `scripts/public-runtime-task-definition-cutover.ps1`
|
||||
- `scripts/start-public-runtime.ps1`
|
||||
- `scripts/boot-public-runtime.ps1`
|
||||
- `scripts/watch-public-runtime.ps1`
|
||||
- `scripts/install-public-runtime-task.ps1`
|
||||
- `scripts/register-boot-task.ps1`
|
||||
- 관련 focused tests 10개
|
||||
- `docs/ops/public-runtime-watchdog.md`
|
||||
- 관련 architecture/local-development/testing 가이드와 `docs/dev_dashboard.html`
|
||||
|
||||
마지막 직접 수정된 task tests:
|
||||
|
||||
- `scripts/test_public_runtime_upload_root.py`
|
||||
- `scripts/test_public_runtime_task_definition_cutover.py`
|
||||
|
||||
파일 전체를 자동 stage하지 말고 각 diff에 선행 사용자 변경이 섞였는지 다시 확인한다.
|
||||
|
||||
## 9. 익일 재개 순서
|
||||
|
||||
### 9.1 현재 truth 재확인
|
||||
|
||||
1. Windows/PowerShell 판, 현재 경로, Git HEAD/worktree/status를 다시 확인한다.
|
||||
2. 이 문서와 `docs/dev_dashboard.html`, `docs/ops/backlog-2026-06-26.md`, `docs/ops/public-runtime-watchdog.md`를 읽는다.
|
||||
3. production/API/task state는 문서만 믿지 말고 read-only로 다시 확인한다.
|
||||
4. 세 source root union을 다시 계산해 `93 / 9d7031... / 52,973 bytes`, DB `8 refs / 70926c... / private audio 0`과 일치하는지 확인한다.
|
||||
5. 아바타 decode audit도 재실행해 `DB refs valid 2 / invalid 6 / missing 0`이 유지되는지 확인한다.
|
||||
|
||||
### 9.2 코드 blocker 해소
|
||||
|
||||
1. `PreservedInventory.total_size_bytes`를 initializer privacy-safe result, manifest, bootstrap cutover receipt, task-recovery receipt, final passed receipt까지 끝까지 결속한다.
|
||||
2. exact expected total `52,973`을 CLI 인자와 tests에서 pin한다. count 93만으로 same-count substitution을 허용하지 않는다.
|
||||
3. 6개 손상 DB-ref에 대한 정책을 소유자와 결정한다. 어떤 경우에도 원본 bytes/DB reference를 승인 없이 삭제하지 않는다.
|
||||
4. 권장 정책을 택하면 decode-valid preserved object만 immutable public cache로 제공하고, invalid object는 private forensic 보존 + public 404/fallback 처리하며 privacy-safe 손상 count를 health/receipt에 기록한다.
|
||||
5. 새 stable upload root가 비어 있거나 exact expected set임을 cutover 전후에 증명한다. UUID형 pre-existing extra를 무조건 허용하지 않는다.
|
||||
6. 문서·SSOT·얇은 backlog를 실제 계약과 일치시킨다.
|
||||
|
||||
### 9.3 통합 테스트
|
||||
|
||||
Python 3.11로 최소 아래를 한 번에 다시 실행한다.
|
||||
|
||||
```powershell
|
||||
py -3.11 -B -X utf8 -m unittest `
|
||||
apps.api.app.test_upload_storage_contract `
|
||||
apps.api.app.test_engine_health_contract `
|
||||
scripts.test_initialize_public_runtime_upload_root `
|
||||
scripts.test_legacy_public_runtime_upload_bootstrap `
|
||||
scripts.test_public_runtime_environment_handoff `
|
||||
scripts.test_public_runtime_listener_pid_probe `
|
||||
scripts.test_public_runtime_task_definition_cutover `
|
||||
scripts.test_public_runtime_task_maintenance `
|
||||
scripts.test_public_runtime_upload_release_safety `
|
||||
scripts.test_public_runtime_upload_root `
|
||||
scripts.test_public_runtime_watchdog_provenance `
|
||||
scripts.test_start_public_runtime_contract -v
|
||||
```
|
||||
|
||||
추가 검증:
|
||||
|
||||
- Windows PowerShell 5.1 AST parse for all changed `.ps1`
|
||||
- Python compile for all new/changed `.py`
|
||||
- scoped `git diff --check`
|
||||
- API의 전체 관련 test suite
|
||||
- 깨끗한 통합 후보에서 `apps/web`의 `npm run typecheck`, `npm run build`
|
||||
- `e2e/layout-visual-gate.spec.ts` 15/15
|
||||
- `e2e/session-layout.spec.ts` 8/8
|
||||
- `e2e/image-resilience.spec.ts`
|
||||
- `e2e/tabs-behavior.spec.ts`
|
||||
- Google auth/onboarding/admin/profile avatar 실제 브라우저 E2E
|
||||
|
||||
### 9.4 좁은 커밋과 clean candidate
|
||||
|
||||
1. shared dirty master에서 이번 runtime 파일만 line-by-line 검토해 좁게 stage한다.
|
||||
2. author는 `Yun Chan <yunchan@twentyoz.kr>`, 한글의 짧은 커밋 메시지를 사용한다.
|
||||
3. `git add .` 금지.
|
||||
4. master `ac9b7026` 이후 runtime commit을 만들고, 새 clean release worktree/branch를 만든다.
|
||||
5. UI 커밋 `35a62fda`, `a73b9eff`를 순서대로 cherry-pick한다.
|
||||
6. clean candidate SHA/tree, clean status, 테스트 결과를 고정한다.
|
||||
|
||||
### 9.5 사용자 승인 후에만 정식 전환
|
||||
|
||||
후보가 GREEN일 때 사용자에게 아래 범위를 정확히 제시하고 승인받는다.
|
||||
|
||||
> 후보 커밋 `<sha>`를 원격에 push하고 Cloudflare Pages production과 이 PC의 public API/tunnel·두 예약 작업을 새 detached runtime으로 전환해도 돼? 기존 API/tunnel은 약 1분 재시작되고, 검증된 upload inventory는 덮어쓰기 없이 새 영구 root에 보존돼.
|
||||
|
||||
승인 전 금지:
|
||||
|
||||
- `git push`
|
||||
- Cloudflare Pages production deploy
|
||||
- public API/cloudflared stop/restart
|
||||
- scheduled task reinstall/retarget/enable 변경
|
||||
- DB avatar URL 수정
|
||||
- stable upload root 생성/복사
|
||||
|
||||
승인 후에도 Pages 자산 보존은 stale default script를 그대로 쓰지 않는다. 최소 다음 실제 production 세대의 immutable asset graph를 explicit origins로 보존한다.
|
||||
|
||||
- `https://0c60261e.vignette-b1q.pages.dev`
|
||||
- `https://ef48c0ae.vignette-b1q.pages.dev`
|
||||
- `https://1f1ddf18.vignette-b1q.pages.dev`
|
||||
|
||||
배포 뒤에는 HTTP 200만 보지 않는다. custom domain의 신규 index/asset hash·MIME·신규 UI marker·구버전 marker 부재, API health manifest/DB/freeze proof, Google 로그인, super account role/onboarding, 데이터/아바타 fallback을 Codex 내장 브라우저로 보여준다.
|
||||
|
||||
### 9.6 실제 재부팅 게이트
|
||||
|
||||
production 전환과 browser smoke가 끝난 뒤에만 아래 문구로 명시 승인받는다.
|
||||
|
||||
> 지금 이 PC를 재부팅해도 돼. 저장하지 않은 작업은 없고, encep 계정으로 로그인한 뒤 REQ-008 자동복구 smoke까지 계속 진행해.
|
||||
|
||||
재부팅 뒤에는 먼저 task를 수동 실행하지 않는다. 로그인 후 자동으로 API/tunnel/tasks가 복구되는지 관찰하고, 공개 health·Google 로그인·데이터·이미지·새 runtime SHA/task action을 증명한다.
|
||||
|
||||
## 10. C001 외부 임상 검수 게이트
|
||||
|
||||
C001은 코드·UI·운영 배포로 대신할 수 없다. 적격 검수자의 실제 입력, 자격/역할, 검토 시각, 대상 버전/패키지 결속, 승인 또는 수정 요청을 받아 workbook의 지정 셀에 반영해야 한다. 검수자가 없으면 최종 Goal은 `기술 완료 / 외부 검수 대기`로 정확히 남긴다.
|
||||
|
||||
## 11. 절대 하지 말 것
|
||||
|
||||
- 손상 아바타 6개의 DB URL이나 파일을 승인 없이 삭제·초기화하지 않는다.
|
||||
- 90개 decode-invalid legacy payload를 정상 이미지로 간주하지 않는다.
|
||||
- 한 개 source root만 복사해 8개 DB ref를 복구했다고 주장하지 않는다.
|
||||
- old runtime root 2개만 보고 데이터가 온전하다고 판단하지 않는다.
|
||||
- general `/uploads` directory를 static mount하지 않는다.
|
||||
- production Uvicorn worker를 2개 이상 띄우지 않는다.
|
||||
- reset receipt 없이 initial nonzero refs → current zero를 정상으로 받아들이지 않는다.
|
||||
- 예약 작업을 새 정의로 교체한 뒤 검증 전에 enable하지 않는다.
|
||||
- 실제 재부팅 전 task를 수동 실행해 자동복구 증거를 오염시키지 않는다.
|
||||
- 외부 임상 검수 내용을 만들어내지 않는다.
|
||||
- dirty tree에서 전체 stage/commit/copy하지 않는다.
|
||||
- Python 3.12/3.14 tempfile ACL 오류를 제품 테스트 실패와 혼동해 같은 방식으로 반복하지 않는다.
|
||||
|
||||
## 12. 재개 프롬프트
|
||||
|
||||
다음 세션에서 아래처럼 시작하면 된다.
|
||||
|
||||
> `docs/ops/handoff-goal-production-2026-08-29.md`를 먼저 읽고, live/Git/DB/avatar decode truth를 read-only로 재검증해. 손상 DB-ref 6개의 보존·fallback 정책과 `preserved_total_size_bytes=52973` end-to-end 결속부터 마무리하고, 전체 통합 GREEN 전에는 stage/push/deploy/runtime/task/DB를 건드리지 마. clean candidate가 준비되면 SHA와 승인 범위를 먼저 보여줘.
|
||||
|
|
@ -24,6 +24,20 @@ watchdog은 이 증거를 health probe와 failcount 기록보다 먼저 다시
|
|||
API secret은 release root의 apps/api/.env에 두되 task 인자에는 넣지 않는다. 이 파일과 web node_modules,
|
||||
runtime log는 Git ignore 대상이다. Cloudflared와 Claude CLI credential은 현재 Windows 사용자 profile에 둔다.
|
||||
|
||||
사용자 업로드는 release root와 분리한 영속 절대 경로만 사용한다. 권장 기본값은
|
||||
`%LOCALAPPDATA%\Vignette\public-runtime\uploads`다. 이 경로는 같은 Windows 호스트의 release 교체와 재부팅에는
|
||||
유지되지만 호스트 장애를 견디는 외부 백업은 아니다. consumer(start/boot/watchdog/registrar)는 빈 경로를 만들지
|
||||
않는다. 별도 initializer가 현재 DB의 정확한 `/uploads/profile-avatars/` 참조를 copy-only·no-overwrite·SHA-256으로
|
||||
검증해 만든 뒤에만 두 task action에 `-UserUploadDir`로 고정한다. 상대 경로, Git root와 겹치거나 이를 포함하는 경로, 기존
|
||||
symlink/junction/reparse point를 통과하는 경로, 디렉터리가 아니거나 쓸 수 없는 경로는 프로세스 변경 전에
|
||||
fail-closed한다. watchdog `-CheckOnly`는 실행 중 API 프로세스의 `USER_UPLOAD_DIR`까지 비교하므로, health가
|
||||
정상이더라도 값이 없거나 다른 release-local 경로면 실패한다.
|
||||
|
||||
공개 static mount는 `USER_UPLOAD_DIR/profile-avatars` 하나뿐이다. 같은 legacy root의 `multimodal-audio`는 private
|
||||
storage이며 공개 migration 대상도 static 서빙 대상도 아니다. 보존 중인 private audio DB 참조가 하나라도 있으면
|
||||
initializer는 별도 private migration 없이는 중단한다. migration manifest와 write-freeze sentinel은 공개 upload root와
|
||||
Git root 밖의 절대 private state directory에만 둔다.
|
||||
|
||||
## Stable Release 준비
|
||||
|
||||
아래 작업은 승인된 clean commit이 생긴 뒤 단일 public mutation owner가 수행한다. 기존 release root를
|
||||
|
|
@ -34,6 +48,7 @@ runtime log는 Git ignore 대상이다. Cloudflared와 Claude CLI credential은
|
|||
$commit = (& git.exe -C $repoRoot rev-parse --verify HEAD).Trim()
|
||||
if ($LASTEXITCODE -ne 0) { throw 'HEAD 조회 실패' }
|
||||
$releaseRoot = "D:\workspace\vignette-public-runtime-$($commit.Substring(0, 12))"
|
||||
$userUploadDir = Join-Path $env:LOCALAPPDATA 'Vignette\public-runtime\uploads'
|
||||
if (Test-Path -LiteralPath $releaseRoot) { throw "release root already exists: $releaseRoot" }
|
||||
|
||||
& git.exe -C $repoRoot worktree add --detach $releaseRoot $commit
|
||||
|
|
@ -54,6 +69,48 @@ runtime log는 Git ignore 대상이다. Cloudflared와 Claude CLI credential은
|
|||
apps/api/.env의 내용을 console이나 evidence에 출력하지 않는다. 새 root에 node_modules와 .env를 준비한 뒤에도
|
||||
위 Git status 결과는 빈 값이어야 한다.
|
||||
|
||||
## 공개 아바타 저장소 초기화와 fresh cutover
|
||||
|
||||
이 단계가 task 설치보다 먼저다. 기존 API도 `upload_write_freeze` health 계약을 지원해야 한다. initializer는 freeze를
|
||||
`CreateNew`로 게시하고 기존 API의 write lease가 0이 될 때까지 기다린 뒤, caller가 명시한 3개 source root의 flat
|
||||
`profile-avatars` regular file 전체 union을 보존한다. 현재 승인 기준은 preserved 93개와 DB 참조 8개이며, 같은 URL을
|
||||
여러 행이 참조하면 파일은 한 번 복사하고 reference count는 보존한다. 호출자는 사전 계산한 preserved object count와
|
||||
privacy-safe path/content/size inventory SHA256을 함께 고정해야 한다. worker는 copy 전후 재스캔과 manifest v2 proof까지
|
||||
그 pin을 재검증한다. 원본은 삭제·이동하지 않으며 대상 충돌, source 간 hash 충돌, 누락 1건, 경로 인코딩/중첩,
|
||||
active private audio가 있으면 중단한다.
|
||||
|
||||
$userUploadDir = Join-Path $env:LOCALAPPDATA 'Vignette\public-runtime\uploads'
|
||||
$uploadStateDir = Join-Path $env:LOCALAPPDATA 'Vignette\public-runtime\private-state'
|
||||
$uploadFreezePath = Join-Path $uploadStateDir 'avatar-cutover.freeze.json'
|
||||
$legacyUploadRoots = @(
|
||||
'D:\exact-approved-upload-root-1'
|
||||
'D:\exact-approved-upload-root-2'
|
||||
'D:\exact-approved-upload-root-3'
|
||||
)
|
||||
$expectedPreservedInventorySha256 = '<approved lowercase SHA256>'
|
||||
$initializer = Join-Path $releaseRoot 'scripts\initialize-public-runtime-upload-root.ps1'
|
||||
$initJson = & powershell.exe -NoProfile -ExecutionPolicy Bypass -File $initializer `
|
||||
-StableSourceRoot $releaseRoot `
|
||||
-UserUploadDir $userUploadDir `
|
||||
-ManifestStateDir $uploadStateDir `
|
||||
-UserUploadWriteFreezePath $uploadFreezePath `
|
||||
-ExpectedReferenceCount 8 `
|
||||
-ExpectedPreservedObjectCount 93 `
|
||||
-ExpectedPreservedInventorySha256 $expectedPreservedInventorySha256 `
|
||||
-SourceUploadDir $legacyUploadRoots
|
||||
if ($LASTEXITCODE -ne 0) { throw 'avatar storage initialization failed' }
|
||||
$init = $initJson | ConvertFrom-Json
|
||||
$uploadManifestSha = [string]$init.manifest_sha256
|
||||
$uploadManifestPath = Join-Path $uploadStateDir "public-avatar-upload-$uploadManifestSha.json"
|
||||
if ((Get-FileHash -LiteralPath $uploadManifestPath -Algorithm SHA256).Hash.ToLowerInvariant() -ne $uploadManifestSha) {
|
||||
throw 'private migration manifest hash mismatch'
|
||||
}
|
||||
|
||||
initializer 성공 시 freeze는 의도적으로 남는다. 이어지는 `-RequireFreshPublicProvenance` cutover는 old API가
|
||||
active+valid+drained freeze를 증명한 뒤 tunnel을 먼저 닫고 API를 교체한다. frozen 새 API와 새 tunnel의 local/public GET,
|
||||
listener PID/cwd/env, receipt를 검증한 뒤에만 소유 token과 일치하는 sentinel을 지우고 쓰기를 재개한다. 쓰기 재개 전 실패는
|
||||
prior API/tunnel과 write availability를 복원한다. 쓰기 재개 뒤에는 old upload root로 자동 rollback하지 않는다.
|
||||
|
||||
## Task 설치 또는 승격
|
||||
|
||||
두 registrar 자체도 동일 stable release root에서 실행해야 한다. 다른 worktree의 registrar로 target만
|
||||
|
|
@ -61,12 +118,20 @@ apps/api/.env의 내용을 console이나 evidence에 출력하지 않는다. 새
|
|||
|
||||
$bootRegistrar = Join-Path $releaseRoot 'scripts\register-boot-task.ps1'
|
||||
& powershell.exe -NoProfile -ExecutionPolicy Bypass -File $bootRegistrar `
|
||||
-StableSourceRoot $releaseRoot
|
||||
-StableSourceRoot $releaseRoot `
|
||||
-UserUploadDir $userUploadDir `
|
||||
-UserUploadManifestPath $uploadManifestPath `
|
||||
-ExpectedUserUploadManifestSha256 $uploadManifestSha `
|
||||
-UserUploadWriteFreezePath $uploadFreezePath
|
||||
if ($LASTEXITCODE -ne 0) { throw 'boot task 등록 실패' }
|
||||
|
||||
$watchdogInstaller = Join-Path $releaseRoot 'scripts\install-public-runtime-task.ps1'
|
||||
& powershell.exe -NoProfile -ExecutionPolicy Bypass -File $watchdogInstaller `
|
||||
-StableSourceRoot $releaseRoot `
|
||||
-UserUploadDir $userUploadDir `
|
||||
-UserUploadManifestPath $uploadManifestPath `
|
||||
-ExpectedUserUploadManifestSha256 $uploadManifestSha `
|
||||
-UserUploadWriteFreezePath $uploadFreezePath `
|
||||
-IntervalMinutes 5
|
||||
if ($LASTEXITCODE -ne 0) { throw 'watchdog task 등록 실패' }
|
||||
|
||||
|
|
@ -88,14 +153,22 @@ RunNow 전에 action을 읽어 두 task가 같은 release root와 commit을 가
|
|||
'-ExpectedSourceCommit',
|
||||
'-ExpectedSourceTree',
|
||||
'-ExpectedBootScriptSha256',
|
||||
'-ExpectedStartScriptSha256'
|
||||
'-ExpectedStartScriptSha256',
|
||||
'-UserUploadDir',
|
||||
'-UserUploadManifestPath',
|
||||
'-ExpectedUserUploadManifestSha256',
|
||||
'-UserUploadWriteFreezePath'
|
||||
)
|
||||
VignettePublicRuntimeWatchdog = @(
|
||||
'-StableSourceRoot',
|
||||
'-ExpectedSourceCommit',
|
||||
'-ExpectedSourceTree',
|
||||
'-ExpectedWatchdogSha256',
|
||||
'-ExpectedStartScriptSha256'
|
||||
'-ExpectedStartScriptSha256',
|
||||
'-UserUploadDir',
|
||||
'-UserUploadManifestPath',
|
||||
'-ExpectedUserUploadManifestSha256',
|
||||
'-UserUploadWriteFreezePath'
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -111,6 +184,9 @@ RunNow 전에 action을 읽어 두 task가 같은 release root와 commit을 가
|
|||
if ($action.Arguments.IndexOf($commit, [StringComparison]::OrdinalIgnoreCase) -lt 0) {
|
||||
throw "$taskName commit pin 누락"
|
||||
}
|
||||
if ($action.Arguments.IndexOf($userUploadDir, [StringComparison]::OrdinalIgnoreCase) -lt 0) {
|
||||
throw "$taskName user upload root pin 누락"
|
||||
}
|
||||
foreach ($marker in $requirements[$taskName]) {
|
||||
if ($action.Arguments.IndexOf($marker, [StringComparison]::Ordinal) -lt 0) {
|
||||
throw "$taskName action pin 누락: $marker"
|
||||
|
|
@ -127,6 +203,21 @@ LastTaskResult=0과 stable release root의 public-runtime-watchdog.failcount=0
|
|||
새 source 배포가 완료된 것은 아니다. 공개 API process cwd, Git commit, OpenAPI, auth, voice provider/model,
|
||||
실제 session smoke까지 별도 배포 gate에서 확인한다.
|
||||
|
||||
### PowerShell 5.1 web build 종료코드 경계
|
||||
|
||||
Windows PowerShell 5.1의 `Start-Process -PassThru`가 반환한 `System.Diagnostics.Process`는 process handle을
|
||||
열기 전에 timed `WaitForExit(milliseconds)`를 호출하면 성공한 자식 프로세스도 `ExitCode=$null`로 남을 수 있다.
|
||||
`start-public-runtime.ps1`은 web build 직후 `$null = $build.Handle`로 handle을 먼저 확보하고, bounded wait 뒤
|
||||
`Refresh()`·null guard·nonzero guard 순서로 판정한다. null을 0으로 간주하거나 build를 무조건 재시도하지 않는다.
|
||||
|
||||
계약 검증은 실제 Windows PowerShell 5.1에서 성공 프로세스의 종료코드를 읽는 probe를 포함한다.
|
||||
|
||||
py -3.11 -B -X utf8 -m pytest -p no:cacheprovider scripts\test_start_public_runtime_contract.py -q
|
||||
|
||||
2026-08-29 기준 32 passed이며, detached-clean `44b7835c…`·tree `06133249…`에 boot/watchdog을 같은 핀으로
|
||||
재등록한 뒤 5174 자동복구·HTTP 200, `LastTaskResult=0`, failcount 0을 확인했다. 실제 Windows 재부팅 smoke는
|
||||
별도 운영 gate다.
|
||||
|
||||
## 숨김 수동 Trigger
|
||||
|
||||
watch-public-runtime-hidden.vbs는 source script를 직접 실행하지 않는다. 등록된 watchdog task action이
|
||||
|
|
@ -144,6 +235,39 @@ install-public-runtime-task.ps1은 액션을 `wscript.exe "<root>\scripts\watch-
|
|||
번쩍이고, 5분 주기 watchdog에서는 그것이 곧 "5분마다 화면에 뜨는 콘솔 창"이 된다(2026-08-08/09/12 세 번 재발).
|
||||
런처는 pin 인자를 해석하지 않고 그대로 전달만 하며, provenance 검증은 watch-public-runtime.ps1이 수행한다.
|
||||
|
||||
## Docker Desktop ERROR 1920 stale AF_UNIX socket 복구
|
||||
|
||||
Docker Desktop 백엔드 로그 또는 `%LOCALAPPDATA%\Docker\backend.error.json`에 아래 경로의 `remove ...
|
||||
The file cannot be accessed by the system`(ERROR 1920)이 보이면 `com.docker.service`나 PostgreSQL volume 문제가
|
||||
아니다. 비정상 종료 뒤 남은 0바이트 AF_UNIX reparse socket 때문에 백엔드가 startup crash-loop한 것이다.
|
||||
|
||||
- `%LOCALAPPDATA%\Docker\run\dockerInference`
|
||||
- `%LOCALAPPDATA%\docker-secrets-engine\engine.sock`
|
||||
|
||||
Docker upstream의 [desktop-feedback #531](https://github.com/docker/desktop-feedback/issues/531)과
|
||||
[#536](https://github.com/docker/desktop-feedback/issues/536)에 같은 결함과 workaround가 기록돼 있다. 개별 socket은
|
||||
`Remove-Item`, `fsutil`, 파일 rename으로도 ERROR 1920이 날 수 있으므로 **삭제·factory reset·WSL unregister를 하지
|
||||
않는다.** Docker Desktop과 Docker CLI만 완전히 종료한 뒤 두 부모 디렉터리를 복구 가능한 timestamp 백업명으로
|
||||
옮긴다.
|
||||
|
||||
$stamp = Get-Date -Format 'yyyyMMdd-HHmmss'
|
||||
$run = 'C:\Users\encep\AppData\Local\Docker\run'
|
||||
$secrets = 'C:\Users\encep\AppData\Local\docker-secrets-engine'
|
||||
if ((Resolve-Path -LiteralPath $run).Path -ne $run) { throw 'run 경로 불일치' }
|
||||
if ((Resolve-Path -LiteralPath $secrets).Path -ne $secrets) { throw 'secrets 경로 불일치' }
|
||||
$runBackup = Join-Path (Split-Path -Parent $run) ("run.stale-$stamp")
|
||||
$secretsBackup = Join-Path (Split-Path -Parent $secrets) ("docker-secrets-engine.stale-$stamp")
|
||||
if (Test-Path -LiteralPath $runBackup) { throw 'run 백업명 충돌' }
|
||||
if (Test-Path -LiteralPath $secretsBackup) { throw 'secrets 백업명 충돌' }
|
||||
Move-Item -LiteralPath $run -Destination $runBackup
|
||||
Move-Item -LiteralPath $secrets -Destination $secretsBackup
|
||||
|
||||
Docker Desktop 일반 사용자 재기동 뒤 `docker version`의 Linux server 응답을 확인한다. DB는
|
||||
`docker inspect vignette-dev-db`로 exact container와 recovered named volume이 존재함을 먼저 확인한 경우에만
|
||||
`docker start vignette-dev-db`를 실행한다. 새 container/volume 생성, 기존 volume 교체, `compose down -v`는 금지다.
|
||||
DB healthy와 55432 listener가 닫힌 뒤에만 아래 stable-root API-only 복구로 이어간다. WSL2 Linux engine에서
|
||||
`AlwaysRunService=false`이면 `com.docker.service`가 stopped인 사실만으로 장애 원인이나 복구 완료를 판정하지 않는다.
|
||||
|
||||
## Manual Source Recovery
|
||||
|
||||
운영 code를 강제로 교체해야 할 때도 shared worktree의 start-public-runtime.ps1을 실행하지 않는다.
|
||||
|
|
@ -152,6 +276,10 @@ install-public-runtime-task.ps1은 액션을 `wscript.exe "<root>\scripts\watch-
|
|||
$startScript = Join-Path $releaseRoot 'scripts\start-public-runtime.ps1'
|
||||
& powershell.exe -NoProfile -ExecutionPolicy Bypass -File $startScript `
|
||||
-Workspace $releaseRoot `
|
||||
-UserUploadDir $userUploadDir `
|
||||
-UserUploadManifestPath $uploadManifestPath `
|
||||
-ExpectedUserUploadManifestSha256 $uploadManifestSha `
|
||||
-UserUploadWriteFreezePath $uploadFreezePath `
|
||||
-ForceApiRestart `
|
||||
-SkipEngineRestart `
|
||||
-SkipWebRestart `
|
||||
|
|
@ -178,6 +306,10 @@ commit/tree와 Python/cloudflared/config SHA를 read-only로 고정하고, confi
|
|||
|
||||
& powershell.exe -NoProfile -ExecutionPolicy Bypass -File $startScript `
|
||||
-Workspace $releaseRoot `
|
||||
-UserUploadDir $userUploadDir `
|
||||
-UserUploadManifestPath $uploadManifestPath `
|
||||
-ExpectedUserUploadManifestSha256 $uploadManifestSha `
|
||||
-UserUploadWriteFreezePath $uploadFreezePath `
|
||||
-ForceApiRestart `
|
||||
-SkipEngineRestart `
|
||||
-SkipWebRestart `
|
||||
|
|
@ -190,8 +322,10 @@ commit/tree와 Python/cloudflared/config SHA를 read-only로 고정하고, confi
|
|||
-RuntimeProvenancePath $receipt
|
||||
if ($LASTEXITCODE -ne 0) { throw 'fresh public provenance 승격 실패' }
|
||||
|
||||
receipt에는 raw command line·config contents를 넣지 않고 PID/start/executable·command SHA/실제 cwd와 topology 입력만
|
||||
남긴다. 이 receipt의 PID와 pin을 `run-g7-external-proof-window.py --topology-mode windows-host`에 그대로 전달하고,
|
||||
receipt에는 raw command line·config contents를 넣지 않고 PID/start/executable·command SHA/실제 cwd,
|
||||
secret이 아닌 resolved `user_upload_root`, migration manifest SHA, 초기/current reference count와 privacy-safe digest,
|
||||
write-freeze path hash, topology 입력만 남긴다. 이 receipt의 PID와 pin을
|
||||
`run-g7-external-proof-window.py --topology-mode windows-host`에 그대로 전달하고,
|
||||
공개 health·auth·OpenAPI·local provider ready를 확인하기 전에는 task action을 새 root로 재등록하지 않는다.
|
||||
|
||||
## Read-only CheckOnly
|
||||
|
|
@ -211,6 +345,7 @@ watchdog script를 직접 CheckOnly로 실행할 때도 task와 같은 pin을
|
|||
-ExpectedSourceTree $tree `
|
||||
-ExpectedWatchdogSha256 $watchSha `
|
||||
-ExpectedStartScriptSha256 $startSha `
|
||||
-UserUploadDir $userUploadDir `
|
||||
-CheckOnly
|
||||
|
||||
추가 public host는 DNS와 routing이 실제로 열린 뒤 installer의
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue