현재 작업 전체 반영

This commit is contained in:
Yun Chan 2026-06-27 16:08:41 +09:00
parent 5560638e54
commit c0dddab594
85 changed files with 11322 additions and 539 deletions

View file

@ -222,8 +222,10 @@ RBAC×AIView로 차단된다. 이 모듈은 평가 신호만 산출한다.
회기 라이프사이클 메모리(4계층 매핑: ① working / ② episodic / ③ summary / ④ semantic).
- 회기 시작: `build_recall_context(...) -> RecallContext` — 큰그림(case_digest) → 직전 요약 →
episodic 단편 순으로 회상 조립. **회상 요약엔 CCD·정답·평가가 절대 들어가지 않는다**(내담자 뷰, M6).
- 회기 시작: `(persona_id, learner_id)` 안정 `case_profile`을 확보한 뒤
`build_recall_context(...) -> RecallContext`를 조립한다. 현재 동기 seed는 직전
`session_summary` 기반이고, episodic 단편/KB 단서는 백그라운드 warm cache로 붙는다.
**회상 요약엔 CCD·정답·평가가 절대 들어가지 않는다**(내담자 뷰, M6).
DB/RAG가 없으면 인자 None → 빈 RecallContext(첫 회기/in-proc 폴백).
- 회기 종료: `make_carry_over(state, ...) -> CarryOver` — (A) `end_state = state.snapshot()`
무손실 코드 복사(P4) (B) `rapport_delta` (C) 서사 digest는 `CompressionJob`으로 큐잉(LLM 비동기,
@ -257,8 +259,9 @@ server: ready → state(listening) → state(thinking) → transcript → reply
핵심 엔드포인트:
- `POST /sessions``get_catalog_persona(code)`로 승인 카드 조회 → `memory.build_recall_context()`
`state_machine.init_state(...)``session_persistence.create_session(...)`. DB가 없으면
- `POST /sessions``get_catalog_persona(code)`로 승인 카드 조회 → `case_profile` upsert →
직전 `session_summary` seed recall → `state_machine.init_state(...)`
`session_persistence.create_session(...)`. DB가 없으면
`runtime_fallback_allowed()` 확인 후 `store.create(...)`로 in-proc 생성.
- `POST /sessions/{id}/turn` — 동기 경로. `prepare_turn``run_turn_generate`
학습자 발화 turn(speaker=`counselor`, 원문 `text` + `text_masked`) + 내담자 응답 turn(speaker=`client`)
@ -414,7 +417,7 @@ DB는 PostgreSQL 16 + pgvector(단일 SoR). 초기화 SQL은 `infra/db/init/`에
evaluator 컨텍스트로 fast-loop 평가 rows를 적재한다. 리뷰 경로만 evaluator 컨텍스트로 hydrate한다.
- 종단: `app.learner_profile`(dim_ewma/dim_slope/persistent_gaps — 회기 거듭하며 나아지는지).
- 감사(append-only): `audit.persona_drift_log`(임베딩 일관성/CCD 누출), `audit.audit_log`(인간 열람 추적),
`audit.llm_call_log`(비용·inference_geo).
`audit.llm_call_log`(비용·inference_geo·latency metadata-only; prompt/completion 본문 미저장).
### 5.3 ds 스키마 — 재귀학습 파이프라인 (`infra/db/init/04_audit_eval_rls.sql` §4)
@ -438,7 +441,28 @@ DB 레벨 이중강제(`04_audit_eval_rls.sql` §5, `app/db.py` `acquire()`):
---
## 6. degraded / 폴백 정책
## 6. 인증/OAuth 경계
- 브라우저는 BFF API에만 로그인한다. Google OAuth는 Authorization Code + PKCE를 사용하고, 완료 후
서버가 opaque HttpOnly 쿠키(`__Host-vignette_sid`)를 발급한다. id/access token은 브라우저에 저장하지 않는다.
- OAuth `state`는 서버 메모리 `_oauth_states`에 호환용으로 보관하지만, 동시에 HMAC 서명 토큰 자체로
`next`, 발급시각, nonce를 검증하고 PKCE verifier를 재계산한다. 시작 응답은 같은 state를 HttpOnly
`__Host-vignette_oauth_state` 쿠키로도 내려주며, callback은 URL state와 쿠키 state가 일치할 때만 복구한다.
그래서 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`),
identity claim 실패를 구분하고 실패 reason code를 화면에 함께 표시한다.
- 역할은 `AUTH_TEACHER_EMAILS`/`AUTH_ADMIN_EMAILS` email allowlist로 1차 판정한다. 코호트는
`AUTH_EMAIL_COHORT_MAP``AUTH_DOMAIN_COHORT_MAP` 설정, SAML fixture의 `cohort` claim을 합쳐
`cohort_ids`로 세션에 저장한다. 관리 사용자 `app_user.external_id`는 provider subject 기반
(`google:`/`saml:`/`dev:`)으로 저장해 email 변경 리스크를 줄인다.
- 로컬/Tailnet dev는 dev-login을 사용한다. 공개 `OAUTH_REDIRECT_URI`가 로컬/Tailnet 세션이 아니라 public API
세션으로 돌아가는 혼선을 막기 위해 dev-origin의 Google 직접 시작은 `local_oauth_unavailable`로 차단한다.
---
## 7. degraded / 폴백 정책
| 미가용 대상 | 증상 | 동작 |
|---|---|---|
@ -452,7 +476,7 @@ DB 레벨 이중강제(`04_audit_eval_rls.sql` §5, `app/db.py` `acquire()`):
---
## 7. 로컬 실행 (Windows 11 + PowerShell 기준)
## 8. 로컬 실행 (Windows 11 + PowerShell 기준)
> 한글/공백 경로 주의. `apps/api/.env`는 pydantic-settings가 자동 로드한다.
@ -510,7 +534,7 @@ npm run e2e # Playwright (web+api+DB 스택 필요)
---
## 8. 안전 불변식 요약(설계 보증)
## 9. 안전 불변식 요약(설계 보증)
- 마스킹되지 않은 원문은 외부 LLM 경로로 절대 나가지 않는다(입력 가드레일 하드 게이트, F-03).
- 내담자 AI는 CCD·진단 차원·정답 라벨·상태 수치를 *말로 설명하지 않는다*(L0 + 출력 가드레일 이중방어, R4).