런타임 계약과 학습자 흐름 보강

This commit is contained in:
Yun Chan 2026-06-29 08:12:14 +09:00
parent f456b8997a
commit 206018b088
56 changed files with 4306 additions and 1008 deletions

View file

@ -1,6 +1,7 @@
# 리팩토링 + RAG 활성화 패치 (워크플로우 산출, 2026-06-26)
> 적용 순서·리스크는 아래 'Sequence' 참고. 일부 anchor는 이후 voice.py 수정(speakable_text)으로 stale일 수 있어 적용 전 현재 코드와 대조 필수.
> 현재 상태(2026-06-29): 이 문서는 적용 전 충돌 분석 기록이다. `TurnMemory`는 현재 `persona.py`에 정의되어 `TurnContext.memory`, `prepare_turn(memory=...)`, `build_turn_messages(memory=...)`, REST/SSE/voice/reevaluate 경로에 반영됐다. `ManagedUserUpsertInput`은 auth managed-user create/reactivate 입력 경계로 반영됐고, `ManagedUserMemoryInput`/`ManagedUserPatch`와 역할을 분리한다. `digest_pending`과 LLM digest worker 실행/품질평가/재압축은 여전히 별도 GATE다.
## 적용 순서/리스크 (Sequence)
@ -15,7 +16,7 @@
| 코드 | 패치 | 성격 | risk |
|---|---|---|---|
| **P1** | 데드코드 3종 제거 (LogHook / gateway·client tier / 서버 RMS 힌트) | 순수 삭제 | medium |
| **P2** | 파라미터 객체화 3종 (`OpennessParams` / `TurnMemory` / `ManagedUserInput`) | 시그니처 리팩토링 | medium |
| **P2** | 파라미터 객체화 3종 (`OpennessParams` / `TurnMemory` / `ManagedUserUpsertInput`) | 시그니처 리팩토링 | medium |
| **P3** | `turn_runtime.py` 추출 (REST/WS 턴 처리 공용화) | 코드 이동·래퍼 | medium |
| **P4** | `routes/sessions.py` RAG 배선 (회상·KB 행동단서) | 기능 추가 | medium |
@ -111,7 +112,7 @@ python -c "from app.services import persona, orchestrator, state_machine; from a
python -m pytest app/test_state_machine_resistance.py app/test_orchestrator_masking.py app/test_session_turn_persistence.py app/test_rbac_idor.py -q
python -m pytest app -q && python -m pytest engine_gateway -q
```
- 중점: 순환참조 부재(`persona→state_machine` 단방향, `orchestrator→persona(TurnMemory)`, `eval→orchestrator(TurnMemory)`), PII 마스킹 불변(`memory=TurnMemory`로 받아도 recall/pinned/recent 마스킹 유지), 저항 곡선 불변, `ctx.memory.recent_turns`가 evaluator-only 비공개 발화 제외, auth/admin 경로(`ManagedUserInput`) 회귀.
- 중점: 순환참조 부재(`persona→state_machine` 단방향, `orchestrator→persona(TurnMemory)`, `eval→orchestrator(TurnMemory)`), PII 마스킹 불변(`memory=TurnMemory`로 받아도 recall/pinned/recent 마스킹 유지), 저항 곡선 불변, `ctx.memory.recent_turns`가 evaluator-only 비공개 발화 제외, auth/admin 경로(`ManagedUserUpsertInput`) 회귀.
- 잔존 참조 점검: `ctx.recall_summary`/`ctx.pinned_facts`/`ctx.recent_turns`/`ctx.kb_behavior_cues` 가 evaluator.py·eval.py·테스트에 남아있지 않은지(AttributeError 차단).
### P3 후
@ -156,15 +157,15 @@ python -m pytest app -q && python -m pytest engine_gateway -q
### 파라미터 객체화 3종 리팩토링 (모두 읽기전용 분석 → 적용 가능한 정확 패치 스펙):
(1) state_machine.init_state(base_resistance/unlock_rate/decay_floor/ideation_baseline) → frozen dataclass OpennessParams 단일 인자 + PersonaCard.openness_params() 팩토리.
(2) orchestrator.prepare_turn / persona.build_turn_messages 의 recall_summary/pinned_facts/recent_turns/kb_behavior_cues → 공유 TurnMemory dataclass (persona.py 정의, orchestrator 재노출). TurnContext 도 memory:TurnMemory 단일 필드로.
(3) auth_sessions.upsert_managed_user / _memory_upsert_managed_user 의 7키워드(email/display_name/role/cohort_ids/user_id/affiliation/reactivate) → pydantic ManagedUserInput. (risk=medium)
(3) auth_sessions.upsert_managed_user 의 create/reactivate 키워드 묶음(email/display_name/role/admin_access/cohort_ids/user_id/external_id/affiliation/account_status/reactivate) → dataclass ManagedUserUpsertInput. `_memory_upsert_managed_user`는 DB→메모리 fallback/store 동기화용 `ManagedUserMemoryInput`, update 계열은 `ManagedUserPatch`로 분리 유지. (risk=medium)
- 근거: 세 리팩토링 모두 "동작 보존 + 인자 묶음"으로 부수효과가 없다. (1) OpennessParams 는 init_state 의 4개 키워드를 frozen dataclass 로 그대로 옮기며 계산식(compute_effective_openness/carry-over 로직)은 한 글자도 바꾸지 않았다. PersonaCard.openness_params() 는 기존 base_resistance()/unlock_rate()/decay_floor()/ideation_baseline() 접근자를 그대로 호출하는 얇은 팩토리라 값이 동일하다. persona→state_machine 은 단방향 import(state_machine 은 어떤 앱 모듈도 import 하지 않음)라 순환참조가 없다. (2) TurnMemory 는 prepare_turn 입력 4종 + TurnContext 저장 + build_turn_messages 입력을 하나로 통일해 "마스킹 1회 → 동일 객체 공유" 흐름을 명확히 한다. 마스킹은 prepare_turn 본문에서 그대로 수행되므로 PII 불변식이 유지된다. TurnContext 필드 축소로 ctx.recent_turns 를 읽던 evaluator.py·eval.py·test_rbac_idor.py 3곳을 ctx.memory.recent_turns 로 동시 갱신해 누락이 없다. (3) ManagedUserInput(pydantic) 는 upsert/_memory_upsert 가 공유하던 동일한 7키워드 중복을 제거하고, fallback 경로는 `_memory_upsert_managed_user(inp)` 로 단순화돼 email 재정규화가 함수 내부에서 멱등 처리된다. 모든 호출부(create_session, admin.create_user, 내부 3곳)를 ManagedUserInput 생성으로 교체했고 외부 엔드포인트/공개 함수(create_session) 시그니처는 불변이라 라우트 회귀가 없다. 가치: 4파라미터 init_state 5호출부, 4파라미터 prepare_turn 7호출부, 7키워드 upsert 5호출부의 시그니처 표류 위험을 타입 객체 하나로 수렴시켜 향후 파라미터 추가 시 호출부 일괄 누락 버그를 구조적으로 차단한다. medium 위험인 이유는 프로덕션 라우트(sessions/voice/admin)·평가기·인증 경로를 동시에 건드리지만, 모두 기계적 치환이고 단위테스트 77개가 경로를 커버한다.
- 근거: 세 리팩토링 모두 "동작 보존 + 인자 묶음"으로 부수효과가 없다. (1) OpennessParams 는 init_state 의 4개 키워드를 frozen dataclass 로 그대로 옮기며 계산식(compute_effective_openness/carry-over 로직)은 한 글자도 바꾸지 않았다. PersonaCard.openness_params() 는 기존 base_resistance()/unlock_rate()/decay_floor()/ideation_baseline() 접근자를 그대로 호출하는 얇은 팩토리라 값이 동일하다. persona→state_machine 은 단방향 import(state_machine 은 어떤 앱 모듈도 import 하지 않음)라 순환참조가 없다. (2) TurnMemory 는 prepare_turn 입력 4종 + TurnContext 저장 + build_turn_messages 입력을 하나로 통일해 "마스킹 1회 → 동일 객체 공유" 흐름을 명확히 한다. 마스킹은 prepare_turn 본문에서 그대로 수행되므로 PII 불변식이 유지된다. TurnContext 필드 축소로 ctx.recent_turns 를 읽던 evaluator.py·eval.py·test_rbac_idor.py 3곳을 ctx.memory.recent_turns 로 동시 갱신해 누락이 없다. (3) ManagedUserUpsertInput(dataclass) 은 upsert_managed_user 의 create/reactivate 입력 묶음을 제거한다. DB→메모리 fallback/store 동기화는 `ManagedUserMemoryInput`, `/users/me`·온보딩·관리자 수정은 `ManagedUserPatch`가 계속 소유하므로 경계가 섞이지 않는다. 모든 호출부(create_session, admin.create_user, 직접 auth 회귀 호출)를 ManagedUserUpsertInput 생성으로 교체했고 외부 엔드포인트/공개 함수(create_session) 시그니처는 불변이라 라우트 회귀가 없다. 가치: 4파라미터 init_state 5호출부, 4파라미터 prepare_turn 7호출부, upsert keyword bag 3호출부의 시그니처 표류 위험을 타입 객체 하나로 수렴시켜 향후 파라미터 추가 시 호출부 일괄 누락 버그를 구조적으로 차단한다. medium 위험인 이유는 프로덕션 라우트(sessions/voice/admin)·평가기·인증 경로를 동시에 건드리지만, 모두 기계적 치환이고 focused tests가 경로를 커버한다.
- 검증: 작업 디렉터리 D:\workspace\vignette\apps\api 기준.
1) 임포트/순환참조 sanity: `python -c "from app.services import persona, orchestrator, state_machine; from app import auth_sessions; from app.routes import admin; from app.routes import eval as _e"` (persona→state_machine 단방향 import, orchestrator→persona(TurnMemory), eval→orchestrator(TurnMemory) 가 깨지지 않는지 확인).
2) 핵심 회귀 4종: `python -m pytest app/test_state_machine_resistance.py app/test_orchestrator_masking.py app/test_session_turn_persistence.py app/test_rbac_idor.py -q` — 특히 (a) test_orchestrator_masking 의 PII 마스킹(prepare_turn 이 memory=TurnMemory 로 받아도 recall/pinned/recent 가 여전히 마스킹되는지), (b) test_state_machine_resistance 의 공감/조언 곡선 불변, (c) test_rbac_idor 의 ctx.memory.recent_turns 가 evaluator-only 비공개 발화를 제외하는지.
3) 전체 백엔드 단위테스트 77개: `python -m pytest app -q` (auth/admin/login 경로 — create_session→upsert_managed_user(ManagedUserInput), admin create_user 엔드포인트 포함 회귀 확인).
3) auth/admin/login focused: `python -m pytest app/test_auth_providers.py app/test_admin_ops.py app/test_rbac_idor.py app/test_runtime_policy.py -q` (create_session→upsert_managed_user(ManagedUserUpsertInput), admin create_user 엔드포인트 포함 회귀 확인).
4) 게이트웨이 영향 없음 확인: `python -m pytest engine_gateway -q` (7개).
모두 green 이어야 하며, 시그니처 변경분(init_state 단일 인자, prepare_turn/build_turn_messages memory 인자, upsert/_memory_upsert 의 ManagedUserInput)이 호출부와 정합하는지 import 에러/AttributeError(ctx.recall_summary 등 잔존 참조) 부재로 검증.
모두 green 이어야 하며, 시그니처 변경분(init_state 단일 인자, prepare_turn/build_turn_messages memory 인자, upsert_managed_user 의 ManagedUserUpsertInput)이 호출부와 정합하는지 import 에러/AttributeError(ctx.recall_summary 등 잔존 참조) 부재로 검증.
- affected_callers: ['init_state — apps/api/app/session_persistence.py:101', 'init_state — apps/api/app/routes/sessions.py:712', 'init_state — apps/api/app/routes/voice.py:498', 'init_state — apps/api/app/test_orchestrator_masking.py:32', 'init_state — apps/api/app/test_state_machine_resistance.py:29', 'prepare_turn — apps/api/app/routes/sessions.py:903', 'prepare_turn — apps/api/app/routes/sessions.py:979', 'prepare_turn — apps/api/app/routes/voice.py:293', 'prepare_turn — apps/api/app/test_orchestrator_masking.py:41', 'prepare_turn — apps/api/app/test_orchestrator_masking.py:132', 'prepare_turn — apps/api/app/test_session_turn_persistence.py:178', 'prepare_turn — apps/api/app/test_session_turn_persistence.py:213', 'build_turn_messages — apps/api/app/services/orchestrator.py:146 (유일 호출부)', 'TurnContext.memory(생성) — apps/api/app/services/orchestrator.py:115', 'TurnContext.memory(생성) — apps/api/app/routes/eval.py:176 (+ import 171)', 'TurnContext.memory(읽기 ctx.recent_turns) — apps/api/app/services/evaluator.py:381', 'TurnContext.memory(읽기 ctx.recent_turns) — apps/api/app/test_rbac_idor.py:285', 'upsert_managed_user — apps/api/app/auth_sessions.py:675 (create_session)', 'upsert_managed_user — apps/api/app/routes/admin.py:493 (+ import 12-18)', '_memory_upsert_managed_user — apps/api/app/auth_sessions.py:452 (upsert DB 성공)', '_memory_upsert_managed_user — apps/api/app/auth_sessions.py:466 (upsert fallback)', '_memory_upsert_managed_user — apps/api/app/auth_sessions.py:548 (update_managed_user)', 'PersonaCard.openness_params 신규 — apps/api/app/services/persona.py (P1/P2/P3 시드는 변경 불필요)']
- edits: 44건