개선관리 요구사항과 Google 로그인을 완료
This commit is contained in:
parent
cc0a15b7c6
commit
2a39636163
112 changed files with 10166 additions and 527 deletions
40
docs/TODO.md
40
docs/TODO.md
|
|
@ -10,6 +10,28 @@
|
|||
|
||||
---
|
||||
|
||||
## 개선관리 워크북 동기화 (2026-08-27)
|
||||
|
||||
> 이 표는 워크북 ID와 현행 구현을 잇는 얇은 추적표다. 내부 기술 DONE의 focused 증거는
|
||||
> `guides/testing.md`, 상세 계약은 SSOT `dev_dashboard.html`이 소유한다. C-001은 기술 사전검증과 외부
|
||||
> 임상 승인을 분리하며, 외부 증거가 없으므로 계속 열린 항목이다.
|
||||
|
||||
| ID | 상태 | 현행 경계 |
|
||||
|---|---|---|
|
||||
| C-001 | **OPEN · 외부 GATE** | 109·수단 상세 차단·ideation 상한·source pack 기술 사전검증은 완료. 임상 검토자 이름·소속 기관·검토일·서면 결정 전부가 있어야 닫는다. F절/B4에서 추적한다. |
|
||||
| C-002 | **DONE · 내부 기술** | 첫 회기 라포 반영·한 초점 열린 질문 체크리스트, 근거 turn 연결, 이후 회기 not-applicable |
|
||||
| C-003 | **DONE · 내부 기술** | 종료 회기 4건 미만 insufficient, 이후 최다 페르소나 비중 0.75 이상 훈련 집중 주의; 임상·공정성 판정 아님 |
|
||||
| REQ-001 | **DONE · 내부 기술** | provider가 이메일을 검증한 모든 Google 계정은 도메인·사전등록 없이 learner·approved로 즉시 로그인. 관리자 exact-email 연구참여자 사전등록 create는 pending 고정, 승인은 별도 PATCH |
|
||||
| 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 유지 |
|
||||
| REQ-005 | **DONE · 내부 기술** | 새 회기는 승인 페르소나 `persona_id/version` 고정, 시작/상세 계약 노출, durable create 실패 503 |
|
||||
| REQ-006 | **DONE · 내부 기술** | prompt와 구조화 결과의 상담자/내담자 identity를 `[COUNSELOR]`/`[CLIENT]`로 역할 마스킹 |
|
||||
| REQ-007 | **DONE · 내부 기술** | 회기 종료를 평가보다 먼저 영속화해 옛 평가 실패 뒤에도 새 회기 생성·턴 진행 가능 |
|
||||
| REQ-008 | **DONE · 내부 기술** | 기본 엔진+live-client readiness, 구조화 done 없는 SSE EOF는 `client_stream_incomplete`로 실패 |
|
||||
|
||||
---
|
||||
|
||||
## A. 배포·공개 런타임 게이트 [환경]
|
||||
> 출처: `ops/backlog-2026-06-26.md` B2 · 대시보드 운영 탭 · `ops/tailscale-vnet-runtime-2026-06-27.md` · `ops/public-runtime-watchdog.md` · `HANDOFF.md`
|
||||
|
||||
|
|
@ -98,10 +120,14 @@
|
|||
NAS `.env(0600)` 재구성 + api만 재생성), 클로드 예산 한도 재발으로 NAS `ENGINE_MODE=agy_cli` 전환. 잔여 권고:
|
||||
공유기 DHCP 예약(호스트 MAC D8-BB-C1-A2-3C-5D), Docker VM 클럭 동기화(호스트 대비 ~4일 선행),
|
||||
NAS 로컬 agy 이전(릴레이·노트북 의존 제거). 증거: `docs/ops/evidence/nas-relay-recovery-2026-08-18.json`.
|
||||
- [ ] **stable-source task 재등록 + 재부팅 후 watchdog smoke** — 사용자 승인 clean commit의 detached release root에
|
||||
watchdog·로그온 boot task를 함께 재등록하고 action의 commit/tree/script SHA pin, 5분 watchdog
|
||||
`LastTaskResult=0`, 로그온 boot, 실제 Windows 재부팅 후 엔진/API/터널 자동 복구 + public `/turn`을 실측한다.
|
||||
DNS 개통 후 `api-vnet.18ka.net`을 `-AdditionalPublicHealthUrls`로 명시 추가.
|
||||
- [ ] **stable-source task 재등록 + 재부팅 후 watchdog smoke** — 2026-08-27 stale AF_UNIX socket으로
|
||||
Docker Desktop 백엔드와 공개 DB가 중단된 사건을 기존 container·volume 삭제/교체 없이 복구했다. 부모 IPC 폴더는
|
||||
timestamp `.stale` 이름으로 보존했고, 기존 `vignette-dev-db`와 `vignette_recovered_prod_20260807`만 재기동했다.
|
||||
detached-clean `bf5f7352…0092`·tree `c39f917b…c88`에 watchdog·로그온 boot task를 재등록해
|
||||
action root/commit/tree/script SHA pin, 5분 watchdog `LastTaskResult=0`·failcount `0`, local/public health
|
||||
`status=ok·db=true·engine=true` 3/3을 확인했다. 남은 것은 실제 Windows 재부팅 후 엔진/API/터널 자동 복구와
|
||||
authenticated public `/turn` 실측이다. DNS 개통 후 `api-vnet.18ka.net`을
|
||||
`-AdditionalPublicHealthUrls`로 명시 추가한다.
|
||||
- [x] **노트북 로컬 음성 스택 STT 구현** — 2026-08-08 소유자 결정으로 음성 양쪽을 노트북 상주
|
||||
로컬 모델로 쓴다(STT faster-whisper, TTS는 아래 항목대로 MeloTTS). 이전에는 코드에 로컬 STT 경로가 아예 없었고
|
||||
(`voice_stt_provider`가 `openai|deepgram` 둘뿐), interim/final 스트리밍 구현도 Deepgram 전용이었다.
|
||||
|
|
@ -262,7 +288,11 @@
|
|||
|
||||
- [ ] C1 사례개념화 **확정 루브릭 콘텐츠** + AI 추출/채점 calibration (현재 scaffold_only).
|
||||
- [ ] CBT 체인·이론부합 루브릭.
|
||||
- [ ] 위기개입 프로토콜 임상 문안.
|
||||
- [ ] **위기개입 프로토콜 임상 승인** — 기술 사전검토는 완료했다. P1 위기 분류, 실제 수련생 위기 시
|
||||
엔진 전 차단·109 연결, 자살 수단 상세 차단, `ideation_stage <= 3`, 공식 source pack 계약을
|
||||
`data/clinical/crisis-protocol-validation.json`과
|
||||
`docs/ops/clinical-crisis-protocol-review-2026-08-27.md`에 고정했다. 남은 것은 임상 검토자 이름·소속 기관·검토일·
|
||||
서면 결정 네 값의 외부 승인이다. 이 증거 전에는 `clinical_status=approved`나 개선관리 시트 `완료`로 바꾸지 않는다.
|
||||
- [ ] 평가 골든셋 콘텐츠.
|
||||
|
||||
## G. 운영 후속 [구현] (비차단)
|
||||
|
|
|
|||
|
|
@ -300,10 +300,11 @@
|
|||
<section class="pulse">
|
||||
<div class="pulse-main">
|
||||
<span class="eyebrow">project pulse</span>
|
||||
<h1>지금 위치: <em>G0~G6 · G8 증거 DONE</em>. <em>NAS 엔진 복구 · G7 external proof</em>가 남았다.</h1>
|
||||
<p class="pulse-state"><b>현재:</b> 공개 앱과 current 주기 실회기는 GREEN, NAS만 health timeout이다. <b>다음:</b> 승인 relay → NAS 재검증 → G7 mic·human proof.</p>
|
||||
<h1>지금 위치: <em>G0~G6 · G8 증거 DONE</em>. <em>G7 external proof · 재부팅 실증</em>이 남았다.</h1>
|
||||
<p class="pulse-state"><b>현재:</b> 공개 앱·API·DB와 current NAS 실회기는 GREEN이다. <b>다음:</b> 실제 Windows 재부팅 자동복구 → authenticated public 회기 → G7 mic·human proof.</p>
|
||||
<details class="pulse-details">
|
||||
<summary>운영 복구·watchdog·배포 상세 보기</summary>
|
||||
<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이다. 실제 Windows 재부팅과 authenticated public <code>/turn</code>은 아직 별도 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>
|
||||
<p class="pulse-state">2026-08-07 공개 DB는 recovered named volume으로 전환했고 원본 container·dump·volume을 보존했다. 2026-08-12에는 휴면 Gradle daemon의 <code>8001</code> 점유·cloudflared/voice sidecar 중단과 PowerShell 5.1 native stderr 조기 종료 결함을 복구했다. DB mutation 전에 fresh custom dump <code>9cbda31e…0ce5</code>(7,532,625 bytes·TOC 1,492)를 고정했다. 공개 API·cloudflared는 detached-clean <code>a73bcd24…</code>·tree <code>b02b3a1b…</code>로 fresh 교체했고 public health 3/3·db/engine true·OpenAPI 126·local voice exact를 통과했다. 로그온·5분 watchdog 두 task도 같은 root/commit/tree/script SHA에 pin해 명시 실행 결과 0을 확인했다. 공개 runtime receipt는 <code>47388d58…9b08</code>, 무마이크 rehearsal evidence는 voice <code>61af2e98…</code>·runtime <code>c6e8670e…</code>·topology <code>89f1cb86…</code>이며 UUID/email literal 0이다. G7은 배포가 아니라 실제 mic·동시 high-water·human evidence만 남은 external GATE다.</p>
|
||||
|
|
@ -692,6 +693,7 @@
|
|||
<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>
|
||||
<div class="dg-principles" aria-label="디자인 생성 가드레일">
|
||||
<div><b>래스터만 사용</b><span>이미지 생성 도구 산출물은 PNG 기반 시안이다. SVG·벡터·와이어프레임·로고 시트로 해석하지 않는다.</span></div>
|
||||
<div><b>기능 우선</b><span>메인 라우트의 실제 액션과 정보 구조를 먼저 반영한다. 장식은 기능을 가리지 않는 수준에서만 쓴다.</span></div>
|
||||
|
|
@ -874,8 +876,8 @@
|
|||
<div class="tg-head"><h3>코드품질 · 로컬 개발 스택</h3><span class="tg-note">리팩토링 연구 + 1-커맨드 dev 스택 (2026-06-26~27). code-quality-research-2026-06-26.md · local-development.md</span></div>
|
||||
<div class="scards">
|
||||
<article class="scard" data-status="doing" data-cat="인프라·스택·RAG" data-owner="0">
|
||||
<button class="scard-head" aria-expanded="false"><span class="chip c-doing">DB RECOVERY · PUBLIC LIVE</span><span class="scard-mid"><span class="scard-title">공개 계정·회기 복구와 무손실 전환</span><span class="scard-sum">복구 named volume을 공개 55432에 전환했다. 데이터·auth·public health는 정상이고 task 자동복구는 stable-source 재등록을 기다린다.</span></span><span class="caret" aria-hidden="true"></span></button>
|
||||
<div class="scard-body"><div class="kv k-good"><b>공개 전환</b><p>2026-08-07 18:26 KST owner 승인으로 <code>vignette_recovered_prod_20260807</code>을 <code>vignette-dev-db:55432</code>에 연결했다. cutover 집계 users 84·sessions 30·turns 705, Google 계정 16·소유 회기 30, orphan 0이다. 2026-08-09 Docker Desktop 중단 후 동일 컨테이너만 재기동했고, fresh dump <code>f1fd569c…5f64</code>를 보존한 뒤 누락 migration 14만 단일 트랜잭션으로 적용했다. 현재 local/public health는 prod·DB true·engine true·claude_cli, OpenAPI 126, Google OAuth 302다. API/cloudflared와 watchdog·로그온 task는 detached-clean <code>a73bcd24…</code>·tree <code>b02b3a1b…</code>와 exact script SHA에 pin했고 두 task 명시 실행 결과 0·Ready를 확인했다. 실제 Windows 재부팅 smoke만 남아 있다.</p></div><div class="kv"><b>보존·rollback</b><p>전환 직전 DB는 <code>vignette-dev-db-pre-recovery-20260807-182642</code>로 중지 보존했고 exact dump SHA는 <code>6b84d5c8…8af1f</code>다. 원본 volume, post-merge dump <code>d24eaa75…2e32</code>, 최종 recovered dump <code>d7bcc396…68ed</code>, 2026-08-09 fresh dump <code>f1fd569c…5f64</code>, 자동 rollback을 실증한 실패 후보 2개도 삭제하지 않았다.</p></div><div class="kv k-warn"><b>사용자 확인</b><p>보안상 기존 <code>auth_session</code>은 복사하지 않아 모두 Google 재로그인이 필요하다. 계정·회기 원장은 복구돼 있다. 실제 사용자가 재로그인한 뒤 과거 소유 회기 목록이 보이는지 확인할 때까지 이 카드는 안정화 중으로 유지한다. 상세: <code>ops/public-db-recovery-rehearsal-2026-08-07.md</code>.</p></div></div>
|
||||
<button class="scard-head" aria-expanded="false"><span class="chip c-doing">DB RECOVERY · PUBLIC LIVE</span><span class="scard-mid"><span class="scard-title">공개 계정·회기 복구와 무손실 전환</span><span class="scard-sum">복구 named volume을 공개 55432에 유지한다. 데이터·auth·public health와 stable-source task 자동복구는 정상이며 실제 재부팅 실증만 남았다.</span></span><span class="caret" aria-hidden="true"></span></button>
|
||||
<div class="scard-body"><div class="kv k-good"><b>공개 전환</b><p>2026-08-07 18:26 KST owner 승인으로 <code>vignette_recovered_prod_20260807</code>을 <code>vignette-dev-db:55432</code>에 연결했다. cutover 집계 users 84·sessions 30·turns 705, Google 계정 16·소유 회기 30, orphan 0이다. 2026-08-27 Docker Desktop stale AF_UNIX socket 사건에서도 container·volume을 교체하지 않고 동일 컨테이너만 재기동했다. 현재 local/public health는 prod·DB true·engine true·claude_cli다. API와 watchdog·로그온 task는 detached-clean <code>bf5f7352…0092</code>·tree <code>c39f917b…c88</code>와 exact script SHA에 pin했고 watchdog 명시 실행 결과 0·Ready·failcount 0을 확인했다. 실제 Windows 재부팅 smoke만 남아 있다.</p></div><div class="kv"><b>보존·rollback</b><p>전환 직전 DB는 <code>vignette-dev-db-pre-recovery-20260807-182642</code>로 중지 보존했고 exact dump SHA는 <code>6b84d5c8…8af1f</code>다. 원본 volume, post-merge dump <code>d24eaa75…2e32</code>, 최종 recovered dump <code>d7bcc396…68ed</code>, 2026-08-09 fresh dump <code>f1fd569c…5f64</code>, 자동 rollback을 실증한 실패 후보 2개도 삭제하지 않았다. 2026-08-27 문제의 IPC 부모 폴더 두 개도 timestamp <code>.stale</code> 이름으로 보존했다.</p></div><div class="kv k-warn"><b>사용자 확인</b><p>보안상 기존 <code>auth_session</code>은 복사하지 않아 모두 Google 재로그인이 필요하다. 계정·회기 원장은 복구돼 있다. 실제 사용자가 재로그인한 뒤 과거 소유 회기 목록이 보이는지 확인할 때까지 이 카드는 안정화 중으로 유지한다. 상세: <code>ops/public-db-recovery-rehearsal-2026-08-07.md</code>.</p></div></div>
|
||||
</article>
|
||||
<article class="scard" data-status="done" data-cat="인프라·스택·RAG" data-owner="0">
|
||||
<button class="scard-head" aria-expanded="false"><span class="chip c-done">DEV STACK · DB 보존 패치</span><span class="scard-mid"><span class="scard-title">로컬 개발 스택 1-커맨드 기동 — <code>scripts/dev-up.ps1</code></span><span class="scard-sum">정지 DB는 start 우선, 신규 PGDATA는 고정 named volume, 일반 dev-up의 DB 삭제 경로는 0.</span></span><span class="caret" aria-hidden="true"></span></button>
|
||||
|
|
@ -1102,7 +1104,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-18 현재 수집은 <b>1111 tests / 61 files</b>다(유스케이스 16테마 <code>uc-*.spec.ts</code> 239 시나리오 추가). 이 숫자는 수집량이며 현 작업트리 전체 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-27 현재 수집은 <b>1136 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>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>
|
||||
|
|
@ -1112,8 +1114,10 @@
|
|||
<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>data/clinical/crisis-protocol-validation.json</code> / <code>docs/ops/clinical-crisis-protocol-review-2026-08-27.md</code></td><td><b>외부 GATE · 미완료.</b> 109 연결, 자살 수단 상세 차단, <code>ideation_stage <= 3</code>, 공식 source pack과 자동 안전 게이트는 기술 사전검증 완료다. 그러나 임상 검토자 이름·소속 기관·검토일·서면 결정 네 값이 모두 없으므로 <code>clinical_status=approved</code>나 개선관리 시트 완료로 바꾸지 않는다.</td></tr>
|
||||
<tr><td>SEO/GEO share cards</td><td><code>pytest app/test_session_share.py app/test_session_turn_persistence.py -q</code> / <code>npm run generate:api-types</code> / <code>npm run typecheck</code></td><td>21 passed; session share creates hashed-token public unfurl payload without raw transcript, revoked token returns 404, OpenAPI generated share DTOs, review screen share button typechecks. Static <code>robots.txt</code>/<code>sitemap.xml</code>/<code>llms.txt</code> added.</td></tr>
|
||||
<tr><td>Backend pytest baseline</td><td><code>pytest -q app</code></td><td>2026-07-31 전체 실행 439 passed. 외부 Starlette 의존성의 <code>python_multipart</code> PendingDeprecationWarning 1건만 존재한다.</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>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>
|
||||
|
|
@ -1190,16 +1194,16 @@
|
|||
<tr><td>Actual deployment env</td><td><code>infra/.env</code> owner-secret fill-in</td><td>Remaining external step: deployment target must provide real <code>APP_DB_PASSWORD</code>, OAuth client id/secret, <code>OPENAI_API_KEY</code>, <code>SESSION_SECRET</code>, and production-safe engine/voice flags. Current local stray values such as <code>ENGINE_MODE=claude_cli</code> and prod sample TTS must not be copied.</td></tr>
|
||||
<tr><td>Focused E2E</td><td><code>admin + db-persistence + voice-success</code></td><td>Latest admin spec: 8 passed on chromium desktop, including all-workspace admin navigation, health dashboard, real server-known users, operation tickets, mobile controls, and tablet form containment.</td></tr>
|
||||
<tr><td>Dev dashboard redteam</td><td><code>scripts/test_dev_dashboard_ssot.py</code> / <code>e2e/dev-dashboard.spec.ts</code></td><td>Unit: SSOT checker 4 tests OK + current dashboard check PASS. E2E: local file, <code>127.0.0.1</code>, <code>localhost</code> 외부형 origin, 필터 오순서/빠른 재시도/키보드, details/tabs 접기, hash anchor, 링크·이미지 무결성, desktop/mobile overflow를 검증해 chromium desktop/mobile 10 passed.</td></tr>
|
||||
<tr><td>Admin manage-users</td><td><code>admin.spec.ts --grep manage real server-known users</code></td><td>desktop/mobile 2 passed</td></tr>
|
||||
<tr><td>Admin manage-users</td><td><code>admin.spec.ts --grep manage real server-known users</code></td><td>route-mock desktop/mobile 2 passed에 더해 2026-08-27 실제 DB/browser 1 passed. pending 계정의 `/personas` 403·`/learn→/pending`, 승인 뒤 동일 세션 200, AI 피드백 OFF PATCH·GET 영속 재조회, 계정 비활성화·active session 0과 격리 포트 종료를 확인했다.</td></tr>
|
||||
<tr><td>Full E2E baseline</td><td><code>PLAYWRIGHT_PORT=5174 npm run e2e</code></td><td>2026-06-27/28 기준선 113 passed</td></tr>
|
||||
<tr><td>Public auth discovery</td><td><code>E2E_PUBLIC_AUTH=1 npx playwright test --list --project=chromium-public-auth</code></td><td>2 tests listed</td></tr>
|
||||
<tr><td>Public pre-auth readiness</td><td><code>public-auth-turn.spec.ts --grep production-safe</code></td><td>production-safe smoke 1 passed. 인증된 public <code>/turn</code> proof는 아직 미완료이며 owner storageState가 필요하다.</td></tr>
|
||||
<tr><td>Public OAuth start</td><td><code>/auth/config</code> + <code>/auth/login?provider=google</code></td><td>2026-06-30 복구 후 auth config 200, Google configured true, redirect URI <code>https://api-vignette.chanpaca.net/auth/callback</code>, dev-login disabled. Google login start는 GET 302와 <code>__Host-vignette_oauth_state</code> HttpOnly/Secure cookie를 반환한다. Authenticated public <code>/turn</code> proof는 owner storageState가 필요하다.</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>detached-clean <code>a73bcd24…</code>·tree <code>b02b3a1b…</code>와 exact script SHA에 로그온·watchdog 두 task를 재등록했다. 두 task를 명시 실행해 <code>LastTaskResult=0</code>·Ready를 확인했고 working directory도 같은 stable root다. 실제 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>local/public 모두 <code>status=ok</code>, <code>db=true</code>, <code>engine=true</code>다. 공개 OpenAPI 126과 <code>/admin/voice-runtime</code>, local Whisper/MeloTTS exact provider/model, queue 4를 확인했다. DB container는 recovered named volume과 <code>unless-stopped</code>를 유지한다.</td></tr>
|
||||
<tr><td>Public runtime current snapshot</td><td><code>health + OpenAPI + voice + provenance + Scheduled Tasks</code></td><td>clean source <code>a73bcd24…</code>·tree <code>b02b3a1b…</code>, receipt <code>47388d58…9b08</code> passed. API/cloudflared는 같은 release root에서 실행되고 두 task result 0이다. 무마이크 rehearsal은 voice/runtime/topology를 모두 통과했으며 public health는 실행 뒤에도 ok·db/engine true다.</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-27 detached-clean <code>bf5f7352…0092</code>·tree <code>c39f917b…c88</code>와 exact boot <code>4f368b2d…5197</code>·watchdog <code>6cd3c869…8242</code>·start <code>c8c837fd…6cf1</code> SHA에 로그온·watchdog 두 task를 재등록했다. action working directory/root/commit/tree/SHA pin은 PASS, watchdog은 <code>LastTaskResult=0</code>·Ready·failcount 0이다. 부트 task는 현재 healthy surface를 불필요하게 건드리지 않도록 RunNow하지 않았고, 실제 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-27 복구 후 local은 <code>status=ok·db=true·engine=true</code>, public은 같은 응답을 연속 3/3 반환했다. 실제 공개 로그인 DOM·스크린샷도 정상이며 console error/warning은 0이다. DB container는 기존 recovered named volume을 그대로 유지한다.</td></tr>
|
||||
<tr><td>Public runtime current snapshot</td><td><code>health + provenance + Scheduled Tasks</code></td><td>clean source <code>bf5f7352…0092</code>·tree <code>c39f917b…c88</code>에서 API-only 복구했고 web·engine·cloudflared는 살아 있던 프로세스를 유지했다. 기존 G7 fresh receipt와 무마이크 rehearsal은 역사 증거로 보존하며, 이번 routine 복구를 새 배포·G7 승격으로 오표기하지 않는다.</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-06-30 public web login HEAD 200, public API health 200, auth config 200, Google login start 302. Earlier Tailnet checks remain recorded separately; vnet DNS A records are still 0.</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>
|
||||
|
|
|
|||
|
|
@ -82,7 +82,9 @@ vignette/
|
|||
|
||||
`lifespan`(startup/shutdown)에서:
|
||||
|
||||
1. `init_pool()` → DB 풀 생성, `ensure_runtime_tables()` / `ensure_review_tables()` 보장.
|
||||
1. `init_pool()` → DB 풀 생성, 런타임 계약 readiness 확인. 개선관리 계약은
|
||||
`infra/db/init/17_improvement_workbook_contracts.sql`을 owner가 선적용해야 하며,
|
||||
`protocol_registry.ensure_protocol_tables()`는 `SELECT`로 준비 상태만 확인하고 애플리케이션 역할로 DDL을 실행하지 않는다.
|
||||
2. `settings.auto_seed_personas` 면 `materialize_seed_personas()`로 시스템 페르소나 P1~P3과 저장소 `data/personas/P4~P7.json`을 `app.persona_card`에 누락분만 물리화한다. 기존 DB 저작본/보관본은 덮어쓰거나 되살리지 않는다. 운영자는 `scripts/materialize-persona-seeds.py` dry-run으로 같은 seed/version manifest를 확인하고, 명시적 `--apply`에서만 DB pool을 초기화해 이 materializer를 호출할 수 있다.
|
||||
3. `engine_client.startup()` / `voice_service.startup()`로 httpx 클라이언트 준비.
|
||||
4. **DB 초기화 실패 시** `environment == "dev"`이면 예외를 삼키고 경고만 남긴 채 degraded 기동한다
|
||||
|
|
@ -90,8 +92,9 @@ vignette/
|
|||
|
||||
등록 라우터(`app/routes/`): `auth, admin, personas, sessions, teacher, users, eval, voice, kb`.
|
||||
|
||||
`GET /health`는 liveness + DB readiness(`db.healthcheck()`) + 엔진 게이트웨이 readiness
|
||||
(`engine_client.health_detail()`)를 합쳐 `{"status": "ok|degraded", db, engine, engine_mode, ...}`를 반환한다.
|
||||
`GET /health`는 liveness + DB readiness(`db.healthcheck()`) + 기본 엔진 조합과 전용 live-client 공급자의
|
||||
readiness(`engine_client.health_detail()`)를 합쳐 `{"status": "ok|degraded", db, engine, engine_mode, ...}`를 반환한다.
|
||||
기본 evaluator 엔진이 준비됐어도 live-client 공급자가 준비되지 않으면 `status=degraded`, `engine=false`다.
|
||||
DB readiness는 auth/admin 테이블뿐 아니라 세션 read-model 핵심 테이블·컬럼
|
||||
(`app.sessions`, `app.turns` 음성 메타 컬럼, `app.session_review_status` worksheet 컬럼)을 함께 확인한다.
|
||||
|
||||
|
|
@ -200,6 +203,9 @@ LLM 아님. 순수함수 + 작은 dataclass `SessionState`.
|
|||
1. **입력 PII 마스킹** `mask_pii(text) -> MaskResult`: Presidio가 설치돼 있으면 우선 사용,
|
||||
미설치면 정규식 폴백(`_PII_PATTERNS`: 주민번호/휴대폰/전화/이메일/장문 숫자열). 마스킹본만
|
||||
저장·외부 LLM 전송에 쓴다(하드 게이트, F-03). Presidio는 지연 로드 캐시(`_try_load_presidio`).
|
||||
`mask_role_identities()`는 현재 회기의 상담자/학습자 이름을 `[COUNSELOR]`, 페르소나/내담자 이름을
|
||||
`[CLIENT]`, 그 밖의 이름을 `[NAME]`으로 정규화한다. evaluator·live-coach 입력 전과 구조화 결과 반환 전
|
||||
모두 적용하며, 선택적 identity 필드는 `getattr(..., None)`으로 안전하게 처리한다.
|
||||
2. **위기 분류** `classify_crisis(text, speaker_is_persona_context=True) -> CrisisResult`:
|
||||
가상내담자의 자살사고 *연기*는 시뮬레이션 정상(`PERSONA_PLAY`, escalate=False). 그러나
|
||||
1인칭 실제 단서(`_FIRST_PERSON_NOW`)가 강하면 **수련생 본인의 실제 위기**(`LEARNER_REAL`,
|
||||
|
|
@ -373,7 +379,10 @@ session lifecycle을 유지하며, future Node read API는 이 read-model contra
|
|||
|
||||
- `POST /sessions` — `get_catalog_persona(code)`로 승인 카드 조회 → `case_profile` upsert →
|
||||
case digest/직전 summary/pinned fact seed recall → `state_machine.init_state(...)` →
|
||||
`session_persistence.create_session(...)`. 프론트 세션 시작 전 화면은 `persona.theory_target`
|
||||
`session_persistence.create_session(...)`. 생성 시 승인 페르소나의 `persona_id`와 `persona_version`을
|
||||
세션에 고정하고 시작/상세 응답에도 두 값을 반환한다. 이후 더 높은 버전이 승인돼도 기존 회기는 고정된
|
||||
역사 버전을 해석한다. DB 영속 생성 실패는 안전하지 않은 성공으로 흡수하지 않고 안정적인
|
||||
`503 session_persistence_unavailable`로 반환한다. 프론트 세션 시작 전 화면은 `persona.theory_target`
|
||||
기본값을 쓰되 학습자가 `humanistic`/`cbt`/`integrative` 중 하나를 명시 선택해 기존
|
||||
`theory_mode` 계약으로 보낸다. DB가 없으면
|
||||
`runtime_fallback_allowed()` 확인 후 `store.create(...)`로 in-proc 생성.
|
||||
|
|
@ -390,7 +399,9 @@ session lifecycle을 유지하며, future Node read API는 이 read-model contra
|
|||
순차 append → 상태 갱신. `EngineError`는 503으로 변환.
|
||||
- `POST /sessions/{id}/stream` — SSE 경로(`run_turn_stream`). token/done/ping/error를 흘리고,
|
||||
done 시점에 학습자 발화 + 누적 내담자 응답 + 상태를 먼저 영속화하고 즉시 완료한다. fast-loop 평가는
|
||||
응답 경로 밖에서 같은 learner turn에 사후 저장한다. `sse_heartbeat_seconds`마다 ping(Cloudflare 타임아웃 회피).
|
||||
응답 경로 밖에서 같은 learner turn에 사후 저장한다. provider 연결이 깨끗한 EOF로 닫혀도 canonical
|
||||
gateway `done`이 없으면 `client_stream_incomplete` 오류로 끝내며 가짜 성공 턴을 저장하지 않는다.
|
||||
`sse_heartbeat_seconds`마다 ping(Cloudflare 타임아웃 회피).
|
||||
- `POST /sessions/{id}/live-coach` — 방금 완료된 상담자 발화를 워크북 요약/RAG/evaluator fast-loop 신호와 대조해
|
||||
라이브 코칭 카드 1개를 반환하고 `app.live_coach_events`에 `use/-1`로 저장한다. 저장 payload는 마스킹
|
||||
excerpt와 코칭 구조화 JSON이며, 잔여 기회가 없으면 409로 차단한다.
|
||||
|
|
@ -401,7 +412,9 @@ session lifecycle을 유지하며, future Node read API는 이 read-model contra
|
|||
`kb.document/kb.chunk`로 색인한다. active hash가 같으면 skip하고, 다르면 최신 document version+1로 색인한다.
|
||||
임베딩 모델이 없으면 BM25-only degraded 색인으로 진행한다.
|
||||
- `POST /sessions/{id}/end` — `memory.make_carry_over(...)` → 세션 종료 + carry 준비 →
|
||||
`_schedule_session_evaluation(sess)`로 deep-loop 평가를 비동기 태스크로 발사.
|
||||
`_schedule_session_evaluation(sess)`로 deep-loop 평가를 비동기 태스크로 발사. 세션 종료 영속화가 평가보다
|
||||
먼저 확정되므로 기존 회기의 deep 평가가 실패해 `error`로 남아도 그 리뷰는 degraded 상태로 읽히고,
|
||||
같은 학습자는 새 회기를 생성해 턴을 이어갈 수 있다.
|
||||
- 진행도 파생(P2, 2026-07-14): `session_read_model.build_session_progress(state, prev_rapport_credit,
|
||||
goal_stages)`가 상태머신 수치를 학습자-안전 %로 파생한다 — 단계별 누적 게이지(현재 단계는
|
||||
`rapport_credit / STAGE_ADVANCE_RAPPORT` 기준, 지나온 단계 100, 전이 대기 99 캡), 라포 누적
|
||||
|
|
@ -413,7 +426,9 @@ session lifecycle을 유지하며, future Node read API는 이 read-model contra
|
|||
- `GET /sessions/dashboard` — 학습자 본인 세션만 `include_turn_evaluation=true`로 집계해
|
||||
`overview/growth/persona_progress/achievements/recent_feedback`를 반환한다. `overview.archived_sessions`는
|
||||
학습자별 보관 상태(`app.session_archive_state`) 기준 카운트이며, 보관된 종료 회기는 리뷰 대기 행동 큐에서
|
||||
제외한다. 성취는 공식 등급/수료가 아니라 실제 연습 milestone만 표시한다.
|
||||
제외한다. `growth.training_exposure`는 종료 회기만 집계하고 4회 미만이면 `insufficient`, 4회 이상에서
|
||||
최다 페르소나 비중이 0.75 이상이면 `훈련 집중 주의`, 그 밖에는 `balanced`로 표시한다. 투명한 노출 비중이지
|
||||
공정성·임상 진단이 아니다. 성취는 공식 등급/수료가 아니라 실제 연습 milestone만 표시한다.
|
||||
- `GET /sessions/{id}/review` — 저장된 축어록 + 평가 AI 산출물을
|
||||
`session_read_model.build_session_review(...)`가 학습자-안전 리뷰로 구성한다. 리뷰 조회 시 발화별 fast-loop 평가는
|
||||
`app.feedback_scores`/라벨 조인 테이블에서 `TurnRecord.evaluation` 형태로 hydrate한다.
|
||||
|
|
@ -423,10 +438,24 @@ session lifecycle을 유지하며, future Node read API는 이 read-model contra
|
|||
`ReviewNote.body`는 제한 markdown(`code`, `strong`, blockquote, list)으로 렌더링하고,
|
||||
`effective_openness` 같은 내부 수치는 `effective openness(유효 개방도)`로 학술 용어화한다.
|
||||
상담자 질문/발화 근거는 `ReviewNote.quote`로 분리해 리뷰 카드 안에서 인용 블록으로 표시한다.
|
||||
첫 회기에는 `first-session-rapport-open-question.v1` 체크리스트가 라포 반영과 한 초점 열린 질문을
|
||||
결정론적으로 관찰하고 근거 turn을 연결한다. 관찰되지 않으면 `not_observed`, 2회기 이후면
|
||||
`not_applicable`이며 임상 점수가 아닌 교육 체크리스트다.
|
||||
학습자의 AI 피드백 노출은 현재 계정 설정과 세션 시작 시점 snapshot이 모두 켜진 경우에만 허용한다.
|
||||
여러 회기를 합치는 학습자 집계도 같은 AND를 각 원천에 적용한다. `calibration/learners/me`는 과거 OFF
|
||||
원천 또는 실행 회기가 하나라도 섞이면 learner-input-only로 fail-closed해 AI·교수자 파생값을 제거하고,
|
||||
`practice/learners/me`는 처방·에피소드·최신 역량 그래프의 원천 회기 중 하나라도 OFF이면 403을 반환한다.
|
||||
일반 practice attempt 제출도 현재 계정과 처방 원천 회기 snapshot이 모두 ON이어야 한다. 그 밖에 OFF인
|
||||
순수 AI 파생 엔드포인트는 403이고 혼합 응답은 사용자가 입력한 calibration/alliance/multimodal 필드만
|
||||
보존한 채 AI 결과·근거·측정을 제거한다. 축어록, 저장된 학습자 워크시트 원문, alliance 자기보고,
|
||||
outcome 관찰 입력, calibration 예측·수정·잠금·실행 입력, multimodal 동의·철회·삭제·raw audio·privacy
|
||||
ledger, privacy export/delete는 유지한다. teacher/admin 감독 뷰에는 이 learner gate를 적용하지 않는다.
|
||||
- `POST /sessions/{id}/share` — 학습자가 종료된 본인 회기 리뷰를 URL로 공유하기 위해 공개 토큰을 생성한다.
|
||||
서버는 `session_read_model.session_share_payload(...)`로 preview를 정규화한 뒤 `app.session_share_link`에
|
||||
토큰 해시와 sanitized preview payload만 저장하고, 원문 축어록·학습자
|
||||
식별자는 payload에 넣지 않는다. 토큰은 생성 응답에서만 반환되며 새 생성은 기존 토큰을 교체한다.
|
||||
학습자 AI 피드백이 현재 계정 또는 세션 snapshot에서 OFF이면 새 공유를 거부하고, ON일 때 이미 만든
|
||||
토큰도 public load 시 같은 두 정책을 다시 확인해 404로 닫는다.
|
||||
- `DELETE /sessions/{id}/share` — 해당 회기의 공개 공유 토큰을 폐기한다.
|
||||
- `POST /sessions/{id}/archive` / `POST /sessions/{id}/restore` — 학습자 본인의 종료 회기를 보관/복원한다.
|
||||
진행 중 회기는 409로 거부한다. 보관은 `app.session_archive_state`만 upsert/delete하는 보기 상태이며,
|
||||
|
|
@ -513,7 +542,8 @@ GET {ENGINE_URL}/ready|/health — readiness/liveness
|
|||
`scripts/check-engine-gateway-contract.mjs`는 같은 artifact를 Node.js에서 Python import 없이 검증하므로
|
||||
미래 Node.js gateway의 최소 conformance gate로 쓴다.
|
||||
`/v1/stream` 종료는 provider pass-through `data: [DONE]`가 아니라 `event: done` + JSON telemetry
|
||||
payload로 변환해야 한다.
|
||||
payload로 변환해야 한다. provider의 `[DONE]`은 호환 입력일 뿐 canonical 완료가 아니며, 구조화 `done`
|
||||
없이 EOF가 오면 앱은 `client_stream_incomplete`로 실패 처리한다.
|
||||
브라우저로 재방출되는 `/sessions/{id}/stream` SSE는 별도 앱 계약이며, engine gateway SSE의
|
||||
JSON token payload와 섞지 않는다.
|
||||
|
||||
|
|
@ -549,6 +579,8 @@ HTTP error mapping을 유지한다.
|
|||
- `get_approved_persona(code)` / `list_approved_personas()` — 승인된 최신 버전 조회(AI 컨텍스트).
|
||||
- `get_catalog_persona(code)` — 승인 카드 조회, 실패 시 `settings.allow_seed_persona_fallback`이면
|
||||
`seed_fallback_persona`(degraded=True)로 폴백.
|
||||
- `persona_read_model._presenting_summary()` — 카드 JSON의 키 순서와 무관하게 `complaint`/`주호소` 계열을
|
||||
우선해 제시문제를 구성한다. `surface` 같은 표면 상태를 주호소로 오인하지 않는다.
|
||||
- 페르소나 워크스페이스: `/teach/personas`는 teacher/admin 전용 운영·저작 작업면이다. 상단 horizontal tab이
|
||||
`대시보드`(공개/활성 페르소나·학습 인원·세션·평가/라포·검수 요약), `카탈로그`(공개 규칙·구성·버전),
|
||||
`페르소나`(검색/필터 가능한 전체 table + 내부 `검수 현황` 탭)를 나눈다. 행을 누르면 소개·학습 현황·설정
|
||||
|
|
@ -574,6 +606,21 @@ HTTP error mapping을 유지한다.
|
|||
- 교수 검수: `list_persona_review_queue(role)` / `update_persona_review_status(action=approve|reject)`
|
||||
— 승인/반려 시 `audit.audit_log`에 감사 기록.
|
||||
|
||||
### 2.13 개선관리 프로토콜 레지스트리 — `app/services/protocol_registry.py`
|
||||
|
||||
관리자 전용 `/admin/protocols`가 프로토콜 목록, draft 생성, 활성화, 폐기를 제공한다. 상태는
|
||||
`draft → active → retired` 단방향이며 retired 항목은 다시 활성화하지 않는다.
|
||||
|
||||
- 생성 내용은 서버에서 정규화하고 SHA-256을 계산한다. 라이선스 분류 A/B/C/D와
|
||||
`external_llm_ok`를 함께 저장하며 C/D는 요청·서비스·DB 제약 모두에서 외부 LLM 사용을 거부한다.
|
||||
- 활성화는 한 트랜잭션에서 `kb.source`를 등록하고 RAG `kb.document/kb.chunk` 색인을 완료한 뒤에만
|
||||
`active`로 바꾼다. 색인 실패는 전체 rollback하며 동시 활성화는 직렬화돼 하나만 성공한다.
|
||||
- chunk는 `visible_to=['evaluator']`, `sensitivity=2`이고 라이선스·외부 LLM 허용 메타데이터를 보존한다.
|
||||
외부 LLM에 보낼 수 없는 근거는 retrieval 후에도 prompt assembly에서 제외한다.
|
||||
- 폐기는 연결 문서를 비활성화한 뒤 terminal `retired`로 전이하고, 중간 실패는 rollback한다.
|
||||
- `ensure_protocol_tables()`는 migration 17 계약을 조회할 뿐 DDL을 만들지 않는다. 누락 시 적용해야 할
|
||||
migration 파일을 명시해 fail-closed한다.
|
||||
|
||||
---
|
||||
|
||||
## 3. 엔진 게이트웨이 — `apps/api/engine_gateway/gateway.py`
|
||||
|
|
@ -713,8 +760,8 @@ React 19 + Vite. 라우팅은 `apps/web/src/App.tsx`(react-router-dom).
|
|||
|
||||
## 5. 데이터베이스 스키마 개요
|
||||
|
||||
DB는 PostgreSQL 16 + pgvector(단일 SoR). 초기화 SQL은 `infra/db/init/`에 순서대로 적용된다
|
||||
(`01_extensions → 02_schema → 03_kb → 04_audit_eval_rls → 05_runtime_auth → 06_session_evaluation → 07_measurement_foundation`).
|
||||
DB는 PostgreSQL 16 + pgvector(단일 SoR). 초기화 SQL은 `infra/db/init/`에 번호순으로 적용되며,
|
||||
개선관리 계약까지 필요한 현행 끝점은 `17_improvement_workbook_contracts.sql`이다.
|
||||
스키마는 **4분할**: `app` / `kb` / `audit` / `ds`.
|
||||
|
||||
### 5.1 app 스키마 (`infra/db/init/02_schema.sql`)
|
||||
|
|
@ -865,6 +912,14 @@ DB는 PostgreSQL 16 + pgvector(단일 SoR). 초기화 SQL은 `infra/db/init/`에
|
|||
fact만 허용하고, 삽입/값 변경 history와 명시적 상담 약속 철회 contradiction까지 남긴다.
|
||||
관계·임상 fact 승격과 광범위 자동 모순 판정은 후속이다.
|
||||
|
||||
### 5.1.1 개선관리 migration 17
|
||||
|
||||
`infra/db/init/17_improvement_workbook_contracts.sql`은 `app.app_user`와 `app.sessions`의
|
||||
`learner_feedback_enabled`, 프로토콜 레지스트리와 라이선스/외부 LLM 제약, lifecycle timestamp/index,
|
||||
관리자 전용 `kb.chunk` write RLS를 멱등하게 추가한다. 새 DB volume은 번호순 init으로 적용되고,
|
||||
기존 DB는 owner가 단일 트랜잭션으로 실행한다. 애플리케이션 역할 startup은 readiness만 확인하며 runtime
|
||||
DDL로 빠진 계약을 보충하지 않는다. 누락되면 migration 17 적용을 요구하며 fail-closed한다.
|
||||
|
||||
### 5.2 audit 스키마 + app 평가/종단 (`infra/db/init/04_audit_eval_rls.sql`)
|
||||
|
||||
- 평가: `app.feedback_scores`(발화별 점수·rationale, `visible_to='{evaluator}'`, loop fast/deep),
|
||||
|
|
@ -916,7 +971,8 @@ DB 레벨 이중강제(`04_audit_eval_rls.sql` §5, `app/db.py` `acquire()`):
|
|||
그래서 public API가 로그인 시작과 콜백 사이에 재시작돼도 callback은 `invalid_state`로 실패하지 않고
|
||||
Google token exchange 단계까지 가며, 쿠키 없는 외부 callback 주입은 차단된다.
|
||||
- OAuth callback 실패는 authorization code, token, raw email을 남기지 않고 reason/status/도메인 수준 정보만
|
||||
서버 로그에 남긴다. 프론트는 `token_exchange_failed`, `invalid_state`, provider error(`access_denied`/`provider_error`),
|
||||
서버 로그에 남긴다. Uvicorn access logger에는 별도 필터를 설치해 `/auth/callback`과 후행 슬래시 변형의
|
||||
query 전체를 제거하되 다른 요청의 query는 보존한다. 프론트는 `token_exchange_failed`, `invalid_state`, provider error(`access_denied`/`provider_error`),
|
||||
identity claim 실패를 구분하고 실패 reason code를 화면에 함께 표시한다.
|
||||
- 역할은 `AUTH_TEACHER_EMAILS`/`AUTH_ADMIN_EMAILS` email allowlist로 1차 판정한다.
|
||||
실제 `admin` 역할 사용자는 관리자 콘솔, 교수자 공간, 학습자 공간에 모두 접근할 수 있다.
|
||||
|
|
@ -927,6 +983,10 @@ DB 레벨 이중강제(`04_audit_eval_rls.sql` §5, `app/db.py` `acquire()`):
|
|||
`AUTH_EMAIL_COHORT_MAP`과 `AUTH_DOMAIN_COHORT_MAP` 설정, SAML fixture의 `cohort` claim을 합쳐
|
||||
`cohort_ids`로 세션에 저장한다. 관리 사용자 `app_user.external_id`는 provider subject 기반
|
||||
(`google:`/`saml:`/`dev:`)으로 저장해 email 변경 리스크를 줄인다.
|
||||
- 관리자 외부 연구참여자 사전등록 `POST /admin/users`는 요청값과 무관하게 항상
|
||||
`account_status=pending`으로 정확한 이메일 계정을 만든다. 승인·정지는 별도
|
||||
`PATCH /admin/users/{user_id}`에서만 수행해 create와 승인 권한 효과를 분리한다. 이는 공개 무제한 가입
|
||||
엔드포인트가 아니며 관리자 콘솔의 사전등록 탭과 승인 큐가 같은 경계를 따른다.
|
||||
- 프론트의 최초 진입 경로(`initialPathForUser`)는 pending이면 `/pending`, 온보딩 미완료 일반 사용자는
|
||||
`/onboarding`, 관리자 콘솔 접근권이 있는 사용자는 기본 역할이 learner/teacher여도 `/admin`을 우선한다.
|
||||
역할 전환용 `roleHomePath`는 그대로 역할별 홈(`/learn`, `/teach`, `/admin`)만 소유한다.
|
||||
|
|
@ -969,11 +1029,13 @@ DB 레벨 이중강제(`04_audit_eval_rls.sql` §5, `app/db.py` `acquire()`):
|
|||
- OAuth/SAML callback은 저장된 `next`가 일반 진입 경로(`/`, `/learn`, `/teach`, `/login`, `/onboarding`)이고
|
||||
로그인 사용자가 관리자 콘솔 접근권을 가지면 `/admin`으로 정규화한다. 단, `/learn/session/...` 같은 깊은
|
||||
링크는 사용자가 의도적으로 연 URL일 수 있으므로 보존한다.
|
||||
- `AUTH_ALLOWED_EMAIL_DOMAINS`는 기본 도메인 게이트다. 단, 슈퍼 관리자/관리자가 `/admin/users`에
|
||||
미리 만든 정확한 이메일은 도메인 밖이어도 Google/SAML/dev-login의 이메일 검증을 통과한다.
|
||||
이 예외는 도메인 전체를 열지 않고, provider 로그인 시 기존 `email:<주소>` 관리 row를
|
||||
`google:`/`saml:`/`dev:` external_id로 이어받아 역할·코호트·승인 상태를 보존한다.
|
||||
- 신규 Google/SAML 사용자는 기본적으로 `account_status=pending`으로 생성된다
|
||||
- Google OIDC는 ID token의 issuer·audience·서명 검증 경로와 `email_verified`를 통과한 모든 이메일을
|
||||
도메인·사전등록 없이 허용한다. 신규 사용자는 learner·`approved`로 만들고, 기존 pending row를 Google
|
||||
external id로 연결할 때도 approved로 승격한다. 기존 inactive/suspended 계정은 이 경로로 우회하지 못한다.
|
||||
- `AUTH_ALLOWED_EMAIL_DOMAINS`는 dev-login·SAML 조직 정책용 게이트다. 슈퍼 관리자/관리자가
|
||||
`/admin/users`에 미리 만든 정확한 이메일은 이 비-Google 경계에서 도메인 밖 예외로 허용하고,
|
||||
`email:<주소>` 관리 row를 provider external id로 이어받아 역할·코호트·승인 상태를 보존한다.
|
||||
- SAML 등 비-Google 신규 사용자는 기본적으로 `account_status=pending`으로 생성된다
|
||||
(`AUTH_NEW_USER_DEFAULT_STATUS`). `/auth/me`만 pending 상태 확인용으로 열어두고, 그 외 REST/WS 기능
|
||||
경로는 `account_pending` 또는 인증 실패로 막는다. `AUTH_APPROVED_EMAILS`, 관리자 생성 사용자,
|
||||
`AUTH_SUPER_ADMIN_EMAILS`, 로컬 dev-login(`external_id=dev:*`)은 자동 approved다.
|
||||
|
|
@ -1086,4 +1148,9 @@ RAG 임베딩/리랭커 의존성은 기본 슬림 이미지에 넣지 않고 `I
|
|||
- 평가/정답/평가 전용 데이터는 RBAC×AIView(레이어1 `visible_to`) + RLS(레이어2)로 학습자 직접 조회에서 차단된다.
|
||||
단, 서버 리뷰 응답은 소유 세션 확인 후 evaluator 컨텍스트로 필요한 평가 rows만 hydrate해 학습자-안전 형태로 가공한다.
|
||||
- 평가/로깅 훅 실패는 비치명적으로 흡수되어 상담 루프를 멈추지 않는다.
|
||||
- 회기 종료 영속화는 deep 평가보다 먼저 확정한다. 오래된 회기의 평가 실패는 error/degraded 리뷰로 남지만
|
||||
새 회기 생성과 턴 저장을 막지 않는다.
|
||||
- 학습자 AI 파생 피드백은 현재 계정 설정과 세션 snapshot의 논리 AND다. 현재 OFF가 과거 ON보다 우선하고,
|
||||
순수 파생 API는 403, 기존 공개 share token은 404다. 사용자 원문/입력·privacy 조작은 보존하며
|
||||
teacher/admin 감독 뷰는 유지한다.
|
||||
```
|
||||
|
|
|
|||
|
|
@ -177,10 +177,10 @@ npm install
|
|||
| `ENGINE_GATEWAY_SHARED_SECRET` | 빈 값 | 선택 인증. NAS/원격 preview에서는 API와 gateway에 동일한 32자 이상 비-placeholder 값을 설정. 빈 값은 기존 로컬 9099 호환 |
|
||||
| `VIGNETTE_LIVE_CLIENT_PROVIDER` | `claude_cli` | 실시간 내담자 AI 전용 lane. 관리자에서 선택한 evaluator/review 공급자와 분리해 회기별 Claude 상주 세션을 재사용 |
|
||||
| `AUTH_DEV_LOGIN_ENABLED` | `true` | dev-login 엔드포인트 활성화 |
|
||||
| `AUTH_ALLOWED_EMAIL_DOMAINS` | `["hs.ac.kr","twentyoz.kr"]` | 기본 로그인 허용 이메일 도메인(dev-login 포함 검증). `/admin/users`에 미리 등록된 정확한 이메일은 도메인 밖이어도 예외로 로그인 가능 |
|
||||
| `AUTH_ALLOWED_EMAIL_DOMAINS` | `["hs.ac.kr","twentyoz.kr"]` | dev-login·SAML 조직 정책용 도메인 목록. Google OIDC는 이 목록을 적용하지 않고 provider-verified 이메일을 모두 허용 |
|
||||
| `AUTH_SUPER_ADMIN_EMAILS` | `["yunchan@twentyoz.kr","hoonjungkoo@hs.ac.kr"]` | 학습자·교수자·관리자 공간 접근과 승인 상태를 부여할 슈퍼 관리자 이메일 |
|
||||
| `AUTH_APPROVED_EMAILS` | `[]` | 신규 외부 로그인 시 pending 없이 바로 승인할 이메일 allowlist |
|
||||
| `AUTH_NEW_USER_DEFAULT_STATUS` | `pending` | Google/SAML 신규 사용자의 기본 승인 상태. `dev:` 로그인은 로컬/E2E 편의를 위해 자동 승인 |
|
||||
| `AUTH_APPROVED_EMAILS` | `[]` | SAML/dev 등 비-Google 신규 로그인에서 pending 없이 바로 승인할 이메일 allowlist |
|
||||
| `AUTH_NEW_USER_DEFAULT_STATUS` | `pending` | SAML 등 비-Google 신규 사용자의 기본 승인 상태. Google은 항상 approved, `dev:` 로그인은 로컬/E2E 편의를 위해 자동 승인 |
|
||||
| `AUTH_EMAIL_COHORT_MAP` | `{}` | 특정 이메일을 cohort id로 매핑한다. 값은 comma-separated 문자열도 허용 |
|
||||
| `AUTH_DOMAIN_COHORT_MAP` | `{}` | 이메일/Google hosted domain을 cohort id로 매핑한다. Google/SAML/dev-login 세션 `cohort_ids`에 반영 |
|
||||
| `VIGNETTE_MELOTTS_TTS_URL` | `http://127.0.0.1:9883` | 로컬 MeloTTS 한국어 사이드카. `scripts/start-melotts.ps1`로 띄운다 |
|
||||
|
|
@ -356,20 +356,25 @@ C:\Users\encep\AppData\Local\Programs\Python\Python311\python.exe scripts\mainta
|
|||
- `role` 기본값은 `learner`.
|
||||
- 성공 시 `__Host-vignette_sid` 쿠키(및 dev 전용 `vignette_sid` 쿠키)를 세팅한다.
|
||||
- dev-login 사용자는 로컬/E2E 흐름 유지를 위해 `account_status=approved`로 생성된다.
|
||||
- Google/SAML 신규 사용자는 기본적으로 `account_status=pending`이며, 승인 전에는 `/pending` 화면만 볼 수 있다.
|
||||
- Google OIDC 신규 사용자는 provider가 이메일을 검증하면 도메인·사전등록 없이 learner·`approved`로 생성된다.
|
||||
기존 pending Google 계정도 로그인 시 approved로 승격하지만 suspended 계정은 그대로 차단한다.
|
||||
- SAML 등 비-Google 신규 사용자는 기본적으로 `account_status=pending`이며, 승인 전에는 `/pending` 화면만 볼 수 있다.
|
||||
관리자 콘솔 `/admin/users`의 가입 승인 탭에서 `approved`로 바꾸면 역할 홈에 접근한다.
|
||||
DB가 연결되어 있으면 pending 생성 시 `app.notification_event`/`app.notification_delivery`에 가입 승인
|
||||
메일 큐가 생긴다. `NOTIFICATION_EMAIL_PROVIDER=disabled`인 로컬 기본값에서는 실제 메일은 발송하지 않는다.
|
||||
- 관리자 콘솔의 외부 연구참여자 사전등록은 `POST /admin/users`에서 항상 `pending`으로만 생성한다.
|
||||
생성 요청으로 즉시 승인할 수 없고, 관리자가 승인 큐에서 별도 `PATCH /admin/users/{user_id}`를 보내야
|
||||
`approved`가 된다. exact-email 사전등록이지 공개 무제한 회원가입이 아니다.
|
||||
- 신규 사용자 또는 온보딩 미완료 사용자는 로그인 직후 `/onboarding`에서 닉네임, 자기소개,
|
||||
선택 아바타 이미지, 이름, 소속, 학과, 학년/직위, 전화번호, 주소/수령지와 약관·개인정보
|
||||
동의를 저장해야 역할 홈으로 이동한다. 학습자 회기 시작은 온보딩 완료와 동의가 모두 있어야 한다.
|
||||
- 프로필 아바타 업로드는 API 작업 디렉터리 기준 `USER_UPLOAD_DIR`(기본 `uploads`) 아래
|
||||
`profile-avatars/`에 저장되고, `/uploads/profile-avatars/...` URL로 서빙된다.
|
||||
|
||||
> 주의(이메일 도메인): dev-login도 기본적으로 `validate_google_identity_domain`을 거치므로
|
||||
> 주의(이메일 도메인): 이 제한은 dev-login·SAML 조직 정책에만 적용된다. dev-login은
|
||||
> 이메일 도메인이 `AUTH_ALLOWED_EMAIL_DOMAINS`에 있어야 한다. 예: `learner@hs.ac.kr`.
|
||||
> 단, 관리자가 `/admin/users`에 미리 등록한 정확한 이메일은 도메인 밖이어도 로그인할 수 있다.
|
||||
> 미등록 외부 도메인은 계속 `403 email domain is not allowed`.
|
||||
> Google OIDC는 provider-verified 이메일이면 도메인과 사전등록 여부를 묻지 않는다.
|
||||
|
||||
### 3.1 메일 알림 큐 확인/처리
|
||||
|
||||
|
|
@ -470,6 +475,24 @@ DB 없이 CLI shape만 확인하려면:
|
|||
py -3.11 scripts\sync-persona-sources.py --help
|
||||
```
|
||||
|
||||
### 3.6 개선관리 migration 17과 프로토콜 레지스트리
|
||||
|
||||
새 Postgres volume은 `infra/db/init/`의 번호순 init으로 migration 17까지 적용한다. 이미 존재하는 DB는
|
||||
애플리케이션 역할이 startup에서 테이블을 만들지 않으므로 owner DSN으로 한 번 적용해야 한다.
|
||||
|
||||
```powershell
|
||||
cd D:\workspace\vignette
|
||||
psql.exe "$env:VIGNETTE_OWNER_DATABASE_URL" -v ON_ERROR_STOP=1 --single-transaction `
|
||||
-f infra\db\init\17_improvement_workbook_contracts.sql
|
||||
```
|
||||
|
||||
`protocol_registry.ensure_protocol_tables()`는 readiness `SELECT`만 실행한다. 계약이 없으면 migration 17을
|
||||
적용하라는 오류로 fail-closed하며 app-role DDL fallback은 없다. 적용 후 admin dev-login으로
|
||||
`GET /admin/protocols`, `POST /admin/protocols`, `POST /admin/protocols/{id}/activate`,
|
||||
`POST /admin/protocols/{id}/retire`를 사용할 수 있다. lifecycle은 `draft → active → retired`이고,
|
||||
활성화는 라이선스·`external_llm_ok` 검증과 evaluator-only RAG 색인이 한 트랜잭션에서 성공해야 끝난다.
|
||||
라이선스 C/D는 외부 LLM 사용을 허용할 수 없다.
|
||||
|
||||
---
|
||||
|
||||
## 4. 웹(프런트엔드) 실행
|
||||
|
|
@ -540,9 +563,11 @@ Invoke-RestMethod 'http://127.0.0.1:9099/ready?provider=codex_cli&model=gpt-5.6-
|
|||
Invoke-RestMethod 'http://127.0.0.1:9099/v1/capabilities?provider=codex_cli&force=true' -Headers $headers
|
||||
```
|
||||
|
||||
API의 `/health`는 현재 DB 설정의 provider/model/reasoning_effort를 게이트웨이 `/ready`에 전달하고
|
||||
(구형 게이트웨이는 `/health`로 폴백) `engine` 필드를 채운다. 따라서 프로세스만 떠 있고 선택한 CLI/API가
|
||||
미인증이거나 모델 조합을 실행할 수 없으면 API `/health`의 `engine: false`가 된다.
|
||||
API의 `/health`는 현재 DB 설정의 provider/model/reasoning_effort와
|
||||
`VIGNETTE_LIVE_CLIENT_PROVIDER` 전용 내담자 lane을 각각 게이트웨이 `/ready`로 확인한다
|
||||
(구형 게이트웨이는 `/health`로 폴백). 기본 evaluator 조합이 준비됐더라도 live-client 공급자가 준비되지
|
||||
않으면 `status=degraded`, `engine=false`다. 따라서 프로세스만 떠 있고 선택한 CLI/API가 미인증이거나
|
||||
어느 필수 모델 조합도 실행할 수 없으면 준비 완료로 보지 않는다.
|
||||
|
||||
### 5.3 프로빙 스크립트 (선택)
|
||||
|
||||
|
|
@ -610,8 +635,8 @@ RAG 임베딩/리랭커 의존성은 기본 이미지에 설치하지 않는다.
|
|||
```powershell
|
||||
# 백엔드 (apps/api)
|
||||
cd apps\api
|
||||
python -m pytest app/ -q # 백엔드 기준선 432 pass
|
||||
python -m pytest engine_gateway\ -q # 현재 44 pass
|
||||
python -m pytest app/ -q # 2026-08-28 전체 실행 1002 passed
|
||||
python -m pytest engine_gateway\ -q # 2026-08-28 전체 실행 68 passed
|
||||
|
||||
# 웹 (apps/web)
|
||||
cd apps\web
|
||||
|
|
@ -636,7 +661,15 @@ npm run e2e # Playwright — web+api+DB 스택 필요
|
|||
아니면 폴백하지 않고 기동이 실패한다.
|
||||
|
||||
- **`/health`의 `engine: false` / 턴 생성 실패**: 게이트웨이(9099)가 안 떠 있거나 `claude` CLI가
|
||||
인증 안 됨. 게이트웨이 `GET /ready`의 `detail`을 보고 원인을 확인한다. 게이트웨이를 먼저 실행하라.
|
||||
인증 안 됐거나 기본 evaluator/live-client 중 하나가 준비되지 않음. 게이트웨이 `GET /ready`의 `detail`과
|
||||
`VIGNETTE_LIVE_CLIENT_PROVIDER`를 함께 확인한다. 게이트웨이를 먼저 실행하라.
|
||||
|
||||
- **SSE가 token 뒤 조용히 끝나고 턴이 저장되지 않음**: gateway의 구조화 `done` 없이 EOF가 온 경우다.
|
||||
서버는 `client_stream_incomplete`로 fail-closed하며 provider `[DONE]`만으로 성공 처리하지 않는다.
|
||||
게이트웨이 로그와 `/ready`를 확인하고 재시도하라.
|
||||
|
||||
- **프로토콜 레지스트리 readiness 실패 / migration 17 요구**: app-role로 DDL을 시도하지 않는다.
|
||||
기존 DB에 owner DSN으로 `17_improvement_workbook_contracts.sql`을 적용한 뒤 API를 다시 기동한다.
|
||||
|
||||
- **dev-login이 404 (`dev login is disabled`)**: `ENVIRONMENT=dev` + `AUTH_DEV_LOGIN_ENABLED=true`인지,
|
||||
요청이 로컬 Origin/Host인지 확인. `apps/api`에서 uvicorn을 실행해 `.env`가 로드됐는지도 확인.
|
||||
|
|
|
|||
|
|
@ -15,12 +15,12 @@ Vignette 저장소의 모든 검증 수단(백엔드 단위 테스트, 웹 타
|
|||
|
||||
| 검증 | 작업 디렉터리 | 명령 | DB | API(8000) | 웹(5173) | 엔진GW(9099) | 브라우저 | 현재 통과 |
|
||||
|---|---|---|---|---|---|---|---|---|
|
||||
| 백엔드 단위 테스트 | `apps/api` | `python -m pytest app/ -q` | 불필요 | 불필요 | 불필요 | 불필요 | 불필요 | 2026-07-31 전체 실행 439 passed |
|
||||
| 엔진 게이트웨이 테스트 | `apps/api` | `python -m pytest engine_gateway/ -q` | 불필요 | 불필요 | 불필요 | 불필요 | 불필요 | 2026-07-31 전체 실행 45 passed |
|
||||
| 백엔드 단위 테스트 | `apps/api` | `python -m pytest app/ -q` | 불필요 | 불필요 | 불필요 | 불필요 | 불필요 | 2026-08-28 전체 실행 1002 passed |
|
||||
| 엔진 게이트웨이 테스트 | `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` | **필요(+시드)** | **필요** | 자동기동 | 일부만 | **필요** | 현재 수집 1111 tests / 61 files · 현 작업트리 전체 GREEN 미검증 |
|
||||
| Playwright E2E(전체) | `apps/web` | `npm run e2e` | **필요(+시드)** | **필요** | 자동기동 | 일부만 | **필요** | 현재 수집 1136 tests / 62 files · 현 작업트리 전체 GREEN 미검증 |
|
||||
|
||||
핵심 원칙: **단위 테스트(pytest)와 타입체크/빌드는 외부 서비스 없이 단독 실행된다.**
|
||||
**E2E만 풀스택(DB+API+웹+브라우저)을 요구한다.** 아래 각 절에서 근거와 절차를 설명한다.
|
||||
|
|
@ -42,17 +42,20 @@ Vignette 저장소의 모든 검증 수단(백엔드 단위 테스트, 웹 타
|
|||
|
||||
```sh
|
||||
# apps/api
|
||||
python -m pytest app/ -q # 앱 단위 테스트: 2026-07-31 전체 실행 439 passed
|
||||
python -m pytest engine_gateway/ -q # 게이트웨이 단위 테스트: 2026-07-31 전체 실행 45 passed
|
||||
python -m pytest app/ -q # 앱 단위 테스트: 2026-08-28 전체 실행 1002 passed
|
||||
python -m pytest engine_gateway/ -q # 게이트웨이 단위 테스트: 2026-08-28 전체 실행 68 passed
|
||||
```
|
||||
|
||||
수집만 빠르게 확인하려면:
|
||||
|
||||
```sh
|
||||
python -m pytest app/ --collect-only -q # 현재: "436 tests collected" (2026-07-31)
|
||||
python -m pytest engine_gateway/ --collect-only -q # 현재: "45 tests collected" (2026-07-31)
|
||||
python -m pytest app/ --collect-only -q
|
||||
python -m pytest engine_gateway/ --collect-only -q
|
||||
```
|
||||
|
||||
수집량은 위 명령의 현재 출력으로 확인한다. `--collect-only` 숫자는 통과 수가 아니므로 실행 결과와 섞어
|
||||
기록하지 않는다.
|
||||
|
||||
> 참고: 실행 중 `PendingDeprecationWarning: Please use 'import python_multipart'`
|
||||
> 경고가 보일 수 있으나 무해하며 통과 결과에 영향을 주지 않는다.
|
||||
|
||||
|
|
@ -75,6 +78,32 @@ python -m pytest engine_gateway/ --collect-only -q # 현재: "45 tests collected
|
|||
| `app/test_session_digest_worker.py` | M2 session digest worker 요청 계약, accepted-only 적용, raw text 차단, 재실행 방지 |
|
||||
| `scripts/check-dev-dashboard-ssot.py --json` | `docs/dev_dashboard.html` 상태 카운트·M2 검증 수치·DONE/GATE stale 문구 guard |
|
||||
|
||||
#### 개선관리 워크북 focused 증거 (2026-08-27~28)
|
||||
|
||||
아래는 전체 1002/68과 별도로 해당 계약을 좁혀 실행한 확정 증거다. 같은 묶음의 테스트가 여러 요구사항
|
||||
경계를 함께 검증할 수 있으므로 숫자를 요구사항별 전체 합계로 더하지 않는다.
|
||||
|
||||
| 항목 | focused 명령/파일 | 확인 결과 |
|
||||
|---|---|---|
|
||||
| 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 |
|
||||
| REQ-002·005 | `app/test_protocol_registry.py app/test_persona_session_contract.py` | 26 passed(경고 1); persona pin·ideation runtime clamp 계약 단독 4 passed(경고 1) |
|
||||
| REQ-002 실제 DB | `e2e/persona-db-lifecycle.spec.ts`의 protocol lifecycle | 1 passed(10.1s) — draft 생성→RAG 활성화→폐기와 DB 정리 |
|
||||
| REQ-003·005 실제 DB | `e2e/persona-db-lifecycle.spec.ts`의 persona lifecycle | 1 passed(14.4s), mock/skip 0 — 교수자 작성→검수 승인→catalog v1, complaint 우선 주호소·surface 미노출, 학습자 prestart, exact persona ID/version session pin, controlled 실제 SSE, UI exact reply, DB `2|counselor,client`; 종료 후 사용자·페르소나·세션·설정 marker 0과 엔진 설정 exact 원복 |
|
||||
| REQ-003 | `app/test_persona_review.py` | 34 passed — complaint 우선 read model과 surface 오인 방지 포함 |
|
||||
| REQ-004 | `app/test_feedback_policy.py` 포함 직접 정책 subset, 관련 route/read-model, `e2e/session-review.spec.ts` desktop·mobile | 85 + 163 pytest, browser 2 passed — learner OFF 직접 API 403, 과거 OFF source가 섞인 calibration 집계의 learner-input-only redaction, deliberate-practice 원천 snapshot OFF 집계·제출 403, alliance self-scores-only, 파생 endpoint별 요청 0회, 입력·privacy 보존, 삭제 조작면 44px·overflow 0 |
|
||||
| REQ-006 | `app/test_pii_masking_eval.py app/test_live_coach_sources.py app/test_client_reply_quality.py` | 24 passed — raw prompt와 구조화 결과의 counselor/client role masking |
|
||||
| REQ-007 | session persistence/evaluation 관련 API 9 files, live/session 묶음, 실제 DB persistence E2E | 163 + 50 pytest, DB E2E 1 passed — 실패한 옛 평가 뒤 새 회기 가능 |
|
||||
| REQ-008 | `app/test_engine_health_contract.py`와 session stream 회귀 | health contract 1 passed; incomplete EOF는 live/session 50 묶음에 포함 |
|
||||
|
||||
로컬 Playwright는 기존 포트의 서버를 기본 재사용하지 않는다. 공개 preview나 다른 checkout의 stale bundle을
|
||||
현 작업트리 증거로 오인하지 않도록 충돌 시 fail-closed하며, 동일 dev server를 의도적으로 공유할 때만
|
||||
`PLAYWRIGHT_REUSE_EXISTING_SERVER=1`을 명시한다. Windows/npm 11에서는 Vite host/port를 등호형 인자로 전달한다.
|
||||
|
||||
REQ-005의 `persona_id/persona_version` API 계약과 영속 pin은 focused 테스트와 실제 DB 브라우저 lifecycle에서
|
||||
모두 확인했다. 통합 실행은 승인 catalog의 exact ID/version, controlled 실제 SSE, UI 응답, DB 2턴과 cleanup을 함께 고정한다.
|
||||
|
||||
### 1.4 `engine_gateway/` 테스트
|
||||
|
||||
| 파일 | 검증 영역 |
|
||||
|
|
@ -271,7 +300,7 @@ VITE_API_BASE=http://127.0.0.1:8000 npm run e2e # 프록시 대신 API
|
|||
|
||||
### 3.6 실측 테스트 개수 (현재)
|
||||
|
||||
2026-08-18 `npx playwright test --list` 기준 **현재 수집 1111 tests / 61 files**다
|
||||
2026-08-27 `npx playwright test --list` 기준 **현재 수집 1136 tests / 62 files**다
|
||||
(유스케이스 16테마 `uc-*.spec.ts` 239 시나리오 포함).
|
||||
이 숫자는 수집량이지 통과량이 아니다. 현 작업트리 전체 1109개 완주는 아직 증거가 없으며,
|
||||
과거 전체 GREEN 기록과 이번 focused/release gate 결과를 구분해 적는다.
|
||||
|
|
@ -527,7 +556,7 @@ hourly heartbeat는 최신 GREEN이 6시간 이상 오래됐거나 material mile
|
|||
| 세션 레이아웃 | `e2e/session-layout.spec.ts` | 4 테스트 × (desktop+mobile) | **8 / 8** |
|
||||
| 시각 레이아웃 게이트 | `e2e/layout-visual-gate.spec.ts` | `@single-run`, 15개 화면 계약 × 7개 폭 검사 + 다크 테마 assertion + 학습 대시보드 라이트 자산 연결 | **15 / 15** |
|
||||
| 인증 테마 게이트 | `e2e/auth-visual.spec.ts` | 로그인·온보딩 × light/dark × 390/1280px, 100vw/100dvh 및 overflow 검사 | **1 / 1** |
|
||||
| 레이아웃 포커스(재설계 화면) | `session-layout`·`session-review`·`admin`·`learner`·`settings`·`teacher`, `@single-run` 제외 | desktop+mobile 병렬 | **54** |
|
||||
| 레이아웃 포커스(재설계 화면) | `session-layout`·`session-review`·`admin`·`learner`·`settings`·`teacher`, `@single-run` 제외 | desktop+mobile, 격리 API/controlled gateway | **106 / 106** |
|
||||
|
||||
> `layout-visual-gate`는 7개 폭(390/720/861/900/1024/1280/1440)에서 15개 핵심 화면 계약(페르소나 운영·작성 단계 포함)의 가로
|
||||
> 오버플로·잘린 컨트롤·다크 테마 적용을 검사하고 전체 페이지 스크린샷을
|
||||
|
|
@ -536,6 +565,9 @@ hourly heartbeat는 최신 GREEN이 6시간 이상 오래됐거나 material mile
|
|||
> `session-layout`은 회기 전/활성 화면이 뷰포트를 벗어나지 않는지, 우측 패널이 코어 영역을
|
||||
> 침범하지 않는지, 시작 후 실제 `session_id` URL에서 새로고침해도 활성 회기 상세가 유지되는지,
|
||||
> 스트림 실패 시 미저장 전사가 남지 않는지를 검증한다.
|
||||
> 2026-08-28 최종 재실행은 `layout-visual-gate` 15/15, 위 레이아웃 포커스 106/106,
|
||||
> `session-layout` 8/8을 실패·skip 0으로 통과했다. 모바일 관리자 topbar와 세션 입력/44px 제어,
|
||||
> P12 ideation DB 범위 clamp를 실제 회귀로 고정했으며, 격리 포트와 프로세스는 모두 종료했다.
|
||||
|
||||
### 3.7 공개 인증 스모크(선택)
|
||||
|
||||
|
|
|
|||
|
|
@ -12,6 +12,10 @@
|
|||
> 섹션과 상세 근거 `docs/ops/source-docs-gap-analysis-2026-06-26.md`에서 추적한다. C1~C3/H·M 구조는 코드로
|
||||
> 선제 구축했고, 임상 문안·평가기준·골든셋 콘텐츠는 임상팀(구훈정·어유경) 외부 정의로 받는다.
|
||||
>
|
||||
> **개선관리 워크북(2026-08-27)** — C-002·C-003과 REQ-001~008은 내부 기술 구현 DONE이므로 열린
|
||||
> 백로그에 중복하지 않는다. C-001은 자동 안전 게이트의 기술 사전검증만 완료됐고 임상 검토자 이름·소속 기관·
|
||||
> 검토일·서면 결정이 없어 B4 외부 GATE로 유지한다. 네 값이 모두 기록되기 전에는 완료로 닫지 않는다.
|
||||
>
|
||||
> **Outcome & Alliance OS** — 2026-08-06 정식 전략 실행 트랙으로 승격했다. G0 Measurement Truth, G1
|
||||
> 현재 소스·실행 증거 재감사에서는 G0~G6과 G8이 DONE이다. G1 승격 prompt 1.2+read-skew/JSON 복구는 24/24 ready·방향 9/9·오류 0을 재확인했고, G0 census 29/29·위반 0, G4/G5 실제 API/DB/브라우저 폐루프, G6 safety metadata-only 최우선 runtime을 disposable clone에서 확인했다. G8은 실제 receipt-bound image rollback 2회(`nas-g8-723eeef2…`/`nas-g8-2738846c…`)에 더해 source HEAD `61a41d1f…6af`·tree `87dec55d…3b77`·archive `4d15d055…119d4d`의 candidate 112/112와 실제 NAS 평문 origin 112/112를 통과했다. 과거 `6030a677…c611`의 UUID 24건 실패와 후속 SHA 결함 rollback은 이력으로 보존하며 현재 완료 증거로 재사용하지 않는다. G7 Multimodal Alliance는 내부 구현 DONE과 외부 proof GATE를 분리한다. detached-clean public `a73bcd24…`·OpenAPI 126·`local_whisper`/`melotts` ready·authenticated WSS 무마이크 rehearsal까지 완료했고, 명시 동의 물리 마이크 3,120초·독립 라벨 voice-gain benchmark·동시 topology high-water를 추적한다. 외부 Deepgram/OpenAI adapter는 fallback으로 보존한다. G0~G8과
|
||||
> AOS-001~012는 `docs/TODO.md` I절에서 전건 추적하고, 상태는 SSOT 대시보드의 9개 계획 카드가 소유한다.
|
||||
|
|
@ -107,10 +111,15 @@
|
|||
PASS했다. 실제 9100 시작·NAS origin 재검증은 소유자 승인 대기다. 명시 동의 물리 마이크와 독립 human voice-gain 증거도 아직 없으므로
|
||||
G7은 external GATE로 유지한다.
|
||||
- [ ] **claude_cli ↔ Anthropic API live 동일성** — provider 라우팅·Anthropic `/v1/models` 탐색·지원 추론 강도·관리자 fail-closed 저장 경로는 구현 완료. 남은 범위는 연구팀/기관 `ANTHROPIC_API_KEY`를 게이트웨이 호스트에 주입한 live 응답·계량·오류 표면화 비교다. Claude CLI·Codex CLI(Terra/Medium)·Agy CLI(Gemini 3.6 Flash/High)는 로컬 live probe를 통과했다.
|
||||
- [ ] **stable-source 재부팅 후 watchdog smoke** — watchdog·로그온 boot는 detached-clean `a73bcd24…` stable
|
||||
root와 exact commit/tree/script SHA에 재등록했고 명시 실행 결과 0·Ready를 확인했다. 남은 것은 실제 Windows
|
||||
재부팅 후 엔진/API/터널 복구와 public `/turn` 실측이다. DNS 개통 후
|
||||
`api-vnet.18ka.net`은 `-AdditionalPublicHealthUrls`로 명시 추가. 상세: `docs/ops/public-runtime-watchdog.md`.
|
||||
- [ ] **stable-source 재부팅 후 watchdog smoke** — 2026-08-27 Docker Desktop 4.82.0이 stale
|
||||
`dockerInference`/`engine.sock` AF_UNIX reparse socket(ERROR 1920)에서 crash-loop한 사건은 두 부모 IPC
|
||||
디렉터리를 `.stale` 백업명으로 이동한 뒤 정상 복구했다. container·image·volume은 생성/삭제/교체하지 않았고,
|
||||
기존 `vignette-dev-db`와 recovered volume만 재기동했다. watchdog·로그온 boot는 detached-clean
|
||||
`bf5f7352…0092`·tree `c39f917b…c88`와 exact commit/tree/script SHA에 재등록했으며, watchdog
|
||||
`LastTaskResult=0`·failcount `0`, public health 3/3 `status=ok·db=true·engine=true`, 실제 로그인 브라우저
|
||||
console error/warning 0을 확인했다. 남은 것은 실제 Windows 재부팅 후 엔진/API/터널 복구와 authenticated
|
||||
public `/turn` 실측이다. DNS 개통 후 `api-vnet.18ka.net`은 `-AdditionalPublicHealthUrls`로 명시 추가한다.
|
||||
상세: `docs/ops/public-runtime-watchdog.md`.
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -146,6 +155,10 @@
|
|||
|
||||
## B4. 외부 거버넌스 — 한신대/데이터 steward 서면 증거
|
||||
|
||||
- [ ] **C-001 자살사고 케이스 위기 반응 프로토콜 임상 승인** — SAMHSA SAFE-T, NIMH Youth Outpatient
|
||||
BSSA, 보건복지부 109를 반영한 기술 사전검토와 자동 안전 게이트는 완료했다. 임상 검토자 이름·소속 기관·검토일·
|
||||
서면 결정이 모두 기록되기 전에는 완료로 닫지 않는다. 검토 원장:
|
||||
`docs/ops/clinical-crisis-protocol-review-2026-08-27.md`.
|
||||
- [ ] **한신대 데이터/SSO 거버넌스 게이트** — SSO claim mapping, 추가 축어록 수급, 미성년 원본 활용동의, 개인정보
|
||||
처리방침. 로컬 골격 `docs/ops/hanshin-data-governance-gate.md` 준비됨, 외부 서면 증거 필요. (소유자 보류 중.)
|
||||
- [ ] **L1 스택 정합성·저작권 등재 문서 반영** — 백엔드는 Node.js 교체 가능성 우선으로 결정됐지만, 신청서/저작권
|
||||
|
|
|
|||
50
docs/ops/clinical-crisis-protocol-review-2026-08-27.md
Normal file
50
docs/ops/clinical-crisis-protocol-review-2026-08-27.md
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
# C-001 자살사고 케이스 위기 반응 프로토콜 검토 원장
|
||||
|
||||
> 상태: **기술 사전검토 통과 · 외부 임상 검토 대기**
|
||||
> 프로토콜 버전: `p1-suicide-ideation-response@2026-08-27.1`
|
||||
> 기계 판독 원본: `data/clinical/crisis-protocol-validation.json`
|
||||
|
||||
## 완료된 기술 게이트
|
||||
|
||||
- 상담자의 직접적인 자살사고 질문을 수련생 본인의 실제 위기로 오인하지 않는다.
|
||||
- 수련생 본인의 현재적 1인칭 위기 신호는 AI 내담자 엔진 호출 전에 중단하고 109 안전자원을 반환한다.
|
||||
- AI 내담자의 정서적 자살사고 신호는 훈련 맥락에서 허용하지만 수단·방법 상세는 재생성 대상으로 차단한다.
|
||||
- AI 내담자 `ideation_stage`는 3을 넘지 못한다.
|
||||
- 라이브 코칭 source pack은 현재 사고, 계획·의도·수단 접근, 과거 행동, 보호요인, 안전계획, 109·119 연결을 공식 출처와 함께 제공한다.
|
||||
|
||||
검증 명령:
|
||||
|
||||
```powershell
|
||||
cd apps/api
|
||||
py -3.11 -X utf8 -m pytest -p no:cacheprovider app/test_client_reply_quality.py app/test_live_coach_sources.py -q
|
||||
```
|
||||
|
||||
## 임상 검토 기준
|
||||
|
||||
임상 검토자는 P1 다회기 골든 시나리오에서 아래를 확인한다.
|
||||
|
||||
1. 자살사고가 없는 대화에서 AI 내담자가 위기 신호를 임의로 만들지 않는가.
|
||||
2. 안전하고 직접적인 질문을 받았을 때 사고의 최근성·빈도·강도를 과장 없이 점진적으로 표현하는가.
|
||||
3. 계획·의도·수단 접근·과거 행동 질문에 필요한 정보만 답하고 구체적 방법을 교육하지 않는가.
|
||||
4. 상담자의 판단적·회피적·유도적 반응과 공감적·직접적 위험 탐색을 임상적으로 타당하게 구분하는가.
|
||||
5. 현재 위험에서는 회기 목표보다 안전 확보, 혼자 두지 않기, 109·119·응급실·현장 보호체계 연결을 우선하는가.
|
||||
6. 안전서약만으로 위험이 낮아졌다고 판정하지 않고 협력적 안전계획과 추후 확인을 요구하는가.
|
||||
|
||||
## 공식 근거
|
||||
|
||||
- SAMHSA, SAFE-T: 위험요인·보호요인·자살사고 탐색·위험수준별 개입·기록/추후관리.
|
||||
- NIMH, Youth Outpatient BSSA: 현재 사고, 계획과 수단 접근, 과거 행동, 보호요인, 긴급 평가, 협력적 안전계획.
|
||||
- 대한민국 보건복지부: 24시간 자살예방상담전화 109.
|
||||
|
||||
링크와 적용 범위는 기계 판독 원본의 `sources`에 고정한다.
|
||||
|
||||
## 승인 기록
|
||||
|
||||
아래 네 값이 모두 채워지고 서면 검토 증거가 연결되기 전에는 `clinical_status=approved`나 워크북 `완료`로 바꾸지 않는다.
|
||||
|
||||
| 항목 | 값 |
|
||||
|---|---|
|
||||
| 임상 검토자 | 미지정 |
|
||||
| 소속 | 미지정 |
|
||||
| 검토일 | 미지정 |
|
||||
| 결정 및 서면 증거 | 미지정 |
|
||||
Loading…
Add table
Add a link
Reference in a new issue