G0~G8 성과·동맹 측정 OS 작업 일괄 고정
8월 7일까지 워킹트리에만 남아 있던 미커밋 작업을 커밋한다. 여러 사본 폴더(worktree·clone)에 흩어져 있던 중간 스냅샷을 정리하기 전에 원본을 git 이력으로 고정하는 것이 목적이다. - contracts/routes/services: measurement, outcome_trajectory, rupture_repair, deliberate_practice, calibration_transfer, supervision_research, multimodal_alliance, continuous_improvement 계열 신규 모듈과 테스트 - infra/db/init: 07~16 마이그레이션(측정 기반~calibration transfer 실행) - apps/web: 세션 리뷰 카드·관리 화면·E2E 스펙 추가 - docs/ops: G0~G8 라이브 통합·배포·롤백 증거 문서와 evidence JSON/PNG - scripts: smoke·ledger·릴리스 에이전트·NAS 프리뷰 운영 스크립트 engine.public 로그 .bak과 apps/web/test-results 산출물은 커밋에서 제외했다.
This commit is contained in:
parent
93dd8f82d7
commit
16e791e044
390 changed files with 243188 additions and 499 deletions
|
|
@ -57,10 +57,10 @@ vignette/
|
|||
│ apps/api app.main:app (uvicorn :8000) │
|
||||
│ routes/ → services/ → engine_client │
|
||||
└───┬───────────────┬───────────────┬───────┘
|
||||
│ HTTP(ENGINE_URL)│ asyncpg 풀 │ httpx(OpenAI)
|
||||
│ HTTP(ENGINE_URL)│ asyncpg 풀 │ HTTPS/WSS voice providers
|
||||
▼ ▼ ▼
|
||||
engine_gateway Postgres16 OpenAI
|
||||
(:9099 등 host) + pgvector STT/TTS
|
||||
engine_gateway Postgres16 Deepgram/OpenAI/Higgs
|
||||
(:9099 등 host) + pgvector streaming STT/TTS
|
||||
claude -p 상주풀 (app/audit/kb/ds) (voice 캐스케이드)
|
||||
│
|
||||
▼ subprocess
|
||||
|
|
@ -72,6 +72,7 @@ vignette/
|
|||
`fetch` 스트림으로 직접 파싱한다(`openSessionStream`).
|
||||
- 백엔드는 엔진 게이트웨이를 `ENGINE_URL`로 HTTP 호출만 한다(`app/engine_client.py`).
|
||||
게이트웨이가 provider 라우팅·캐싱·상주 프로세스 풀을 흡수한다.
|
||||
`ENGINE_GATEWAY_SHARED_SECRET`이 설정되면 모든 호출에 `X-Vignette-Engine-Token`을 붙인다.
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -228,7 +229,13 @@ RBAC×AIView로 차단된다. 이 모듈은 평가 신호만 산출한다.
|
|||
- evaluator structured 결과는 `evaluate_turn()`/`evaluate_session()` 경계에서 canonical
|
||||
`GenerateRequest` SHA-256 키의 인메모리 semantic cache로 재사용할 수 있다. `/admin/usage`는
|
||||
cache key·prompt·completion 없이 enabled/entries/hits/misses/stores/evictions/requests/hit_rate와
|
||||
일별 `daily_cost` bucket(day, turns, tokens, cost)을 관리자 관측값으로 반환한다.
|
||||
일별 `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`을 함께 반환하고 예산 상태는 두 비용을 합친 유효 비용을 사용한다.
|
||||
`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` 기록을 건너뛰고,
|
||||
|
|
@ -305,7 +312,12 @@ RBAC×AIView로 차단된다. 이 모듈은 평가 신호만 산출한다.
|
|||
|
||||
STT/TTS 어댑터(순수 변환 + voice preset 매핑). 상담 로직은 라우트가 조립한다.
|
||||
|
||||
- STT: `/audio/transcriptions` (gpt-4o-transcribe → 404 시 whisper-1 폴백), 언어 힌트 `ko`.
|
||||
- STT provider는 `VIGNETTE_VOICE_STT_PROVIDER=openai|deepgram`이다. OpenAI 경로는 batch
|
||||
`/audio/transcriptions`(gpt-4o-transcribe → 404 시 whisper-1 폴백), 언어 힌트 `ko`를 사용한다.
|
||||
Deepgram 경로는 `/v1/listen` WebSocket 한 세션을 발화 중 유지하며 `interim_results`, `Results`,
|
||||
`speech_final`, word start/end와 allowlisted provider event를 순차 반영한다. 기본 model은 `nova-3`, 언어는
|
||||
`ko`이며 endpointing/utterance end/keepalive/finalize timeout/MIP opt-out을 env로 조정한다.
|
||||
Deepgram 선택 상태에서 key가 없고 OpenAI key가 있으면 `openai-batch-fallback`으로 명시 전환한다.
|
||||
- TTS: `/audio/speech` (gpt-4o-mini-tts → tts-1 폴백). 음성 선택 우선순위는 명시 query preset →
|
||||
`app.persona_voice_map`의 OpenAI row(`base_params.preset/openai_voice/rate/instructions`) →
|
||||
페르소나 code 기본 preset(`PRESET_TO_OPENAI_VOICE`, P1=coral / P2=ash / P3=shimmer)이다.
|
||||
|
|
@ -322,7 +334,9 @@ STT/TTS 어댑터(순수 변환 + voice preset 매핑). 상담 로직은 라우
|
|||
- 텍스트 턴의 AI 내담자 응답은 인증된 `POST /voice/speech`가 `session_id`/`turn_seq`로 소유 회기를 다시
|
||||
로드하고, 이미 저장된 client-visible 내담자 응답만 선택 provider의 오디오로 합성한다. 브라우저가 임의
|
||||
문장을 보내는 TTS 프록시가 아니며, 마이크 WebSocket과 같은 voice map/TTS 어댑터를 공유한다.
|
||||
- 키 없으면 명확히 degraded(`is_available()=False`, `VoiceUnavailable`). 라우트가 503/WS close로 변환.
|
||||
- 사용 가능한 STT/TTS credential이 없으면 명확히 degraded(`VoiceUnavailable`)하고 라우트가 503/WS close로
|
||||
변환한다. fallback은 `ready.stt_provider/model`에 실제 provider/model을 노출하므로 expected-provider
|
||||
public preflight를 통과할 수 없다.
|
||||
|
||||
`/voice/ws` WebSocket 캐스케이드(`routes/voice.py`):
|
||||
```
|
||||
|
|
@ -332,6 +346,10 @@ server: ready → state(listening) → state(thinking) → transcript → reply
|
|||
```
|
||||
- 인증: REST와 동일한 서버측 세션 쿠키를 WebSocket 쿠키에서 복원(`_principal_from_websocket`),
|
||||
learner만 허용.
|
||||
- `ready`는 실제 `stt_provider`, `stt_model`, `tts_provider`, `tts_model`과 batch fallback 가능 여부를 반환한다.
|
||||
public preflight는 마이크 없이 이 네 값을 배포 기대값과 정확히 비교한다.
|
||||
- Deepgram streaming 중에는 동의 원장을 1초마다 재검사한다. 철회·미동의가 확인되면 streaming session을
|
||||
abort하고 추가 오디오 provider 전송과 후속 파생 저장을 fail-closed한다.
|
||||
- 턴 실행은 동일하게 `orchestrator.prepare_turn` → `run_turn_generate`를 거치고, 응답 생성 *후에만*
|
||||
발화를 영속화한다(실패한 AI 턴이 학습자 단독 축어록을 남기지 않도록).
|
||||
- `audio_end.provider_events`와 STT provider 이벤트는 allowlist·size limit 후
|
||||
|
|
@ -341,6 +359,8 @@ server: ready → state(listening) → state(thinking) → transcript → reply
|
|||
provider/source/raw type/text/transcript는 응답하지 않고, 공개 공유 카드는 축어록과 provider raw를 싣지 않는다.
|
||||
- 텍스트 입력 경로도 턴 저장 완료 뒤 `/voice/speech`를 호출해 AI 내담자 음성을 재생한다. 새 텍스트 발화를
|
||||
보내면 이전 합성/재생 요청을 무효화해 겹쳐 재생하지 않는다.
|
||||
- 네트워크/TTS가 끊기면 Session은 작성 중 텍스트를 보존하고 텍스트 계속하기와 키보드 가능한 음성 재연결을
|
||||
제공한다. 이 recovery는 실패한 provider를 성공으로 위장하지 않는다.
|
||||
|
||||
### 2.9 세션 라우트 — `app/routes/sessions.py` (실제 데이터 흐름)
|
||||
|
||||
|
|
@ -562,6 +582,12 @@ HTTP error mapping을 유지한다.
|
|||
Codex CLI, Agy CLI의 모델 탐색·실행 차이를 흡수한다. Claude CLI는 회기당 1 `EngineSession` 프로세스를
|
||||
상주시켜 페르소나 system 프롬프트와 prompt caching을 유지한다. 나머지 공급자는 격리된 stateless 호출로 실행한다.
|
||||
|
||||
선택 보안 경계인 `ENGINE_GATEWAY_SHARED_SECRET`을 설정한 인스턴스는 순수 liveness인 `/health`만
|
||||
무인증으로 열고, 실제 생성을 수행하는 `/ready`와 capability·session·generate·stream을 포함한 나머지 모든 HTTP 경로에서
|
||||
`X-Vignette-Engine-Token`을 constant-time 비교한다. 값은 32자 이상 비-placeholder여야 하며 API와
|
||||
gateway 양쪽에 동일하게 주입한다. 미설정 인스턴스는 기존 localhost 9099 개발 호환을 유지한다.
|
||||
인증은 미들웨어 경계에 있어 secret이나 인증 헤더 값이 OpenAPI·애플리케이션 로그에 포함되지 않는다.
|
||||
|
||||
실행:
|
||||
```
|
||||
cd apps/api && uvicorn engine_gateway.gateway:app --host 0.0.0.0 --port 9099
|
||||
|
|
@ -576,7 +602,7 @@ cd apps/api && uvicorn engine_gateway.gateway:app --host 0.0.0.0 --port 9099
|
|||
→ `--system-prompt`로 주입, 마지막 user를 stdin content로. structured_schema는
|
||||
`_inject_schema()`로 system에 JSON 준수 지시 주입(claude -p는 response_format 미지원이라 차선).
|
||||
- `POST /v1/stream` — `turn_stream()`이 assistant 텍스트 델타를 즉시 yield → SSE
|
||||
`event: token` / `event: done`(provider/model/cost 메타) / `event: error` 프레이밍.
|
||||
`event: token` / `event: done`(provider/model/token/cost 메타) / `event: error` 프레이밍.
|
||||
- `session_id`가 살아있으면 풀 재사용, 아니면 1회성(ephemeral) 세션 생성 후 close(`_resolve_session`).
|
||||
- **공급자 capability** `GET /v1/capabilities` — Codex app-server `model/list`, `agy models`, Anthropic
|
||||
`GET /v1/models` 또는 Claude CLI 공식 alias를 공통 `EngineModelOption`으로 정규화한다. 60초 캐시와
|
||||
|
|
@ -588,6 +614,16 @@ provider 라우팅 모드는 `ENGINE_MODE`(`app/config.py`) 또는 DB의 관리
|
|||
실행 가능 공급자는 `claude_cli`, `claude_api`, `codex_cli`, `agy_cli`다. Codex 기본은
|
||||
`gpt-5.6-terra` / Medium, Agy 기본은 `gemini-3.6-flash-high` / High다. `openai`와 `solar`는
|
||||
기존 계약값을 보존하지만 실행 어댑터가 없어 capability에서 unavailable로 fail-closed한다.
|
||||
각 어댑터는 사용량을 입력·캐시 입력·출력 토큰으로 정규화한다. Claude CLI는 result의
|
||||
`modelUsage`/`model_usage`를 우선해 전체 agent tree를 합산하며, 원장의 입력 토큰은
|
||||
비캐시 입력 + cache read + cache creation 합계다. 모델별 사용량이 없을 때만 최상위
|
||||
`usage`로 폴백한다. `total_cost_usd`는 Claude SDK의 호출별 클라이언트 추정값이지 청구서
|
||||
실비가 아니므로 `provider_estimate`로 표시한다. Agy/Gemini·Codex·Claude API는
|
||||
`app.services.llm_pricing.estimate_reference_cost()`로 공식 참조단가를 적용한다. 과거
|
||||
토큰 미수집 행은 비용에서 역산하지 않는다. `scripts/backfill-claude-token-usage.py`는 로컬
|
||||
Claude JSONL의 실제 assistant usage와 DB 응답을 정규화 본문 SHA-256·생성 시각 창으로 대조하고,
|
||||
후보가 정확히 하나인 행만 `--expected-matches` 가드 아래 복구한다. 일치하지 않거나 모호한 행은
|
||||
`token_unmetered_turns`로 남긴다.
|
||||
|
||||
`AdminEngineConfigResponse`는 provider·URL·model에 `reasoning_effort`를 더해 DB에 저장한다. PATCH는
|
||||
제안된 게이트웨이에서 capability를 강제 재조회한 뒤 실제 목록에 없는 모델·추론 강도, 미인증 공급자,
|
||||
|
|
@ -678,7 +714,7 @@ 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`).
|
||||
(`01_extensions → 02_schema → 03_kb → 04_audit_eval_rls → 05_runtime_auth → 06_session_evaluation → 07_measurement_foundation`).
|
||||
스키마는 **4분할**: `app` / `kb` / `audit` / `ds`.
|
||||
|
||||
### 5.1 app 스키마 (`infra/db/init/02_schema.sql`)
|
||||
|
|
@ -714,6 +750,99 @@ DB는 PostgreSQL 16 + pgvector(단일 SoR). 초기화 SQL은 `infra/db/init/`에
|
|||
이벤트와 수신자별 delivery로 분리해 저장한다. `idempotency_key`가 중복 메일을 막고, delivery는
|
||||
`queued/sending/sent/failed/skipped` 상태와 시도 횟수, provider message id, 마지막 오류만 저장한다.
|
||||
메일 본문 HTML이나 회기 축어록은 DB에 복제하지 않는다. RLS는 관리자 전체 처리만 허용한다.
|
||||
- **Outcome & Alliance 측정 원장** `app.measurement_instrument` / `app.measurement_event` — 척도·훈련지표를
|
||||
`(instrument_id, instrument_version)`으로 고정하고 측정값을 append-only event로 남긴다. 정정은 UPDATE가
|
||||
아니라 `supersedes_id`를 가진 새 event이며, `source_kind`와 `perspective`의 호환성, scale, 상태,
|
||||
`visible_to[]`, 모델 provenance를 DB CHECK/RLS로 강제한다. 기존 `rapport_credit`와 `alliance_level`은
|
||||
Working Alliance가 아닌 `simulation_progress` 신호로만 적응한다.
|
||||
- **Alliance Pulse 실행 원장** `app.alliance_pulse` / `app.self_assessment` /
|
||||
`audit.alliance_pulse_status_event` — 수련생의 goal/task/bond 자기평가를 먼저 잠근 뒤 가상내담자와 독립
|
||||
관찰자를 서로 다른 엔진 세션으로 실행한다. `awaiting_agents` 동안은 자기보고만 읽을 수 있고 모든 agent
|
||||
실행·측정 event 저장이 끝난 뒤 `ready`로 단방향 전이하면서 reveal한다. 실패는 무점수 `error/degraded`로
|
||||
남기며 정상 점수로 보정하지 않는다. 교수자 평정은 `human_rated/supervisor_human` 새 event 3개를 추가하고
|
||||
재평정은 직전 event를 `supersedes_id`로 연결한다. prompt/schema/input evidence hash와 시도별 실패 이력은
|
||||
`audit.model_run`에 보존한다.
|
||||
- **종단 성과 궤적** `ds.synthetic_outcome_arc` / `app.outcome_trajectory_revision` /
|
||||
`app.outcome_trajectory_observation` — 같은 case의 1~5회기에서 고통 부담·일상 기능·학습 참여를 독립 축으로
|
||||
읽고, 기대분포는 `synthetic_educational`·`clinical_claim_allowed=false`로 고정한다. 누락·오류는 무점수로
|
||||
보존하고 revision은 source fingerprint와 supersession을 가진 append-only snapshot이다. 학습자 체크인은
|
||||
submission ID와 축별 measurement ID로 멱등하며, 관계 기억은 `app.relationship_memory_event`와
|
||||
역할별 `app.relationship_memory_projection`으로 분리한다. safety signal은 outcome 상태에 합산하지 않는다.
|
||||
- **파열·수선 원장** `app.rupture_episode` / `app.rupture_observation_event` /
|
||||
`app.rupture_reconciliation_revision` / `app.rupture_safety_reference` — G3의 onset→recognized/repair_attempted→
|
||||
missed/partial/resolved 전이를 episode별 sequence로 append한다. fast warning과 deep reconciliation은 별도
|
||||
revision으로 대조하고 사람 정정은 supersession event로 남긴다. 내부 evaluator write API는
|
||||
`VIGNETTE_RUPTURE_INTERNAL_TOKEN`이 없거나 32자 미만이면 503으로 닫히며, 잘못된 토큰은 evaluator DB
|
||||
context를 열기 전에 거부한다. safety는 resolution에 영향을 주는 점수 없이 동일 회기 FK만 보존한다.
|
||||
`rupture_scenario_director`는 case/session/turn seed에서 4~6턴 간격으로 9종을 순환하되 client prompt에는
|
||||
조건부 행동 cue만 주입한다. taxonomy·scenario UUID·selector provenance는 engine metadata에만 남고,
|
||||
safety escalation이나 내부 marker 누출 가능성이 있으면 생성·SSE를 fail-closed한다. 텍스트·SSE·음성은
|
||||
모두 durable 상담자/내담자 UUID pair와 structured fast evaluation이 저장된 뒤 같은 detector를 실행한다.
|
||||
- **의도적 수련 원장** `app.practice_coaching_card` / `app.practice_prescription` /
|
||||
`app.practice_attempt_episode` / `app.practice_attempt` / `app.practice_competency_snapshot` /
|
||||
`app.practice_curriculum_decision` / `app.practice_teacher_correction` — G4 coaching card를 관찰 가능한
|
||||
원자 행동과 5종 실행 prescription으로 변환하고, 약점·망각 위험·선행 역량으로 다음 과제를 선택한다.
|
||||
익숙한 장면의 반복 성공과 학습자 자기 성공 주장은 mastery 근거가 아니며, `unseen_transfer` 성공 전에는
|
||||
`mastery_allowed=false`를 강제한다. 내부 처방 write는 `VIGNETTE_PRACTICE_INTERNAL_TOKEN`을 DB acquire 전에
|
||||
검증하고, 학습자 시도와 교수자 교정은 역할·cohort RLS와 append-only supersession으로 분리한다.
|
||||
- **자기보정·전이 원장** `app.calibration_prediction_revision` / `app.calibration_prediction_lock` /
|
||||
`app.calibration_performance_observation` / `app.calibration_assessment` /
|
||||
`app.calibration_meta_prescription` / `app.calibration_transfer_suite` / `app.calibration_transfer_trial` /
|
||||
`app.calibration_subgroup_drift` — 외부평가 reveal 전 학습자 성공확률·확신도 revision을 보존하고, 명시적
|
||||
lock 뒤 revision을 trigger로 거부한다. runtime/evaluator 수행 관측은 독립 perspective로 저장하며 역량별
|
||||
error·bounded interval·과신/과소신과 baseline→recent 변화를 계산한다. transfer는 context·관계스타일·난이도·
|
||||
표현의 coverage와 phrase/scenario family novelty를 요구하고 암기 문구를 차단한다. 합성 subgroup drift는
|
||||
실제 인구집단 주장이 아니며 표본 부족을 별도 상태로 둔다. 내부 관측 write는
|
||||
`VIGNETTE_CALIBRATION_TRANSFER_INTERNAL_TOKEN`을 DB acquire 전에 검증하고 역할·cohort RLS를 유지한다.
|
||||
- **감독·연구 OS 원장** `app.supervision_attention_snapshot` / `app.supervision_attention_item` /
|
||||
`app.supervision_evidence_pointer` / `app.supervision_teacher_ai_disagreement` /
|
||||
`app.supervision_calibration_dataset_row` / `audit.supervision_teacher_event` /
|
||||
`app.supervision_curriculum_gap_snapshot` / `app.supervision_evaluation_batch` /
|
||||
`app.supervision_drift_report` / `app.supervision_phase3_manifest` — G6 위험·정체·미해결 관계 사건을
|
||||
총점 대신 원장 UUID pointer 최대 3개로 queue화하고, 교수자 정정은 raw transcript 없는 disagreement/dataset/audit
|
||||
append로 보존한다. baseline/candidate model·prompt·instrument와 subgroup metric, Phase 3 네 도메인 artifact
|
||||
hash/provenance를 재현 read model로 제공한다. supervisor/research AI view는 물리 분리하며 내부 write는
|
||||
`VIGNETTE_SUPERVISION_RESEARCH_INTERNAL_TOKEN`을 DB acquire 전에 검증하고 사람 조회는 teacher/admin·cohort
|
||||
RLS로 제한한다.
|
||||
- **멀티모달 동맹 원장** `app.multimodal_consent_snapshot` / `app.multimodal_audio_asset` /
|
||||
`app.multimodal_audio_timeline` / `app.multimodal_word_timestamp` / `app.multimodal_voice_event` /
|
||||
`app.multimodal_axis_measurement` / `app.multimodal_fusion_decision` /
|
||||
`app.multimodal_deletion_request` / `audit.multimodal_deletion_tombstone` — G7 음성 입력을 STT word와
|
||||
silence/overlap/interruption/prosody가 공유하는 단일 밀리초 시계로 정렬한다. text와 voice 축 측정은 별도
|
||||
provenance로 저장하고 synthetic benchmark에서 유의한 추가 이득이 있을 때만 fusion한다. 비언어 이벤트는
|
||||
관찰 가능한 interaction signal만 허용하며 감정·진단 추론을 계약에서 거부한다. streaming STT word 원문은
|
||||
저장하지 않고 deployment `SESSION_SECRET`으로 `session_id:submission_id:casefold(word)`를 HMAC-SHA256한
|
||||
pseudonym과 시간만 저장한다. learner 동의가 없거나 철회되면 최초 처리 전과 열린 stream 중 1초 간격으로
|
||||
voice ingestion을 fail-closed하고, raw audio는 learner/admin만 보며 teacher는 metadata-only read model을 사용한다.
|
||||
삭제 tombstone 뒤에는 raw audio와 derived voice feature를 read model에서도 제거한다. 내부 write는
|
||||
`VIGNETTE_MULTIMODAL_ALLIANCE_INTERNAL_TOKEN`을 DB acquire 전에 검증한다.
|
||||
- **G7 voice runtime·external evidence boundary** — Session의 첫 음성 입력은 처리 설명과 명시 동의 뒤
|
||||
`app.multimodal_consent_snapshot`에 원음 미보존·전사/파생 특징 30일 동의를 먼저 append한다. write 실패 전에는
|
||||
브라우저 `getUserMedia`와 `/voice/ws`를 시작하지 않으며, 회기 전환·unmount·pause는 capture generation을
|
||||
무효화하고 뒤늦은 MediaStream track을 종료한다. 관리자 전용 `GET /admin/voice-runtime`은 single-worker
|
||||
RSS/peak RSS·CPU·thread/FD, WebSocket/provider session, route audio buffer, streaming event queue,
|
||||
overflow/fallback/error high-water만 반환한다. session ID·축어록·원음·provider payload는 포함하지 않는다.
|
||||
production Docker는 Uvicorn worker 1개와 `--ws-max-queue 4`를 사용한다. 외부 종료는 public WSS/physical mic,
|
||||
process-local runtime, exact Linux topology, independent human-held-out voice-gain 네 artifact가 같은 public host와
|
||||
겹치는 50분 시간창을 가져야 한다. Cloudflare 내부 큐를 직접 측정했다고 주장하지 않고 public runner의
|
||||
TLS/CF-Ray/latency/gap/disconnect를 edge 경계로 쓴다. canonical validator는
|
||||
`scripts/check-g7-external-proof.py`이며 synthetic/preflight/short probe는 종료 증거가 아니다.
|
||||
- **지속 개선 원장** `app.ci_agentic_job` / `app.ci_content_pipeline` / `app.ci_content_qualification` /
|
||||
`app.ci_model_change_gate` / `app.ci_release_gate` / `app.ci_gate_artifact` /
|
||||
`audit.ci_human_approval_event` / `audit.ci_lifecycle_event` / `app.ci_operational_incident` /
|
||||
`app.ci_regression_dag_node` — G8 source pack에서 생성된
|
||||
사례·균열·연습·benchmark 초안을 독립 red-team과 품질 gate로 통과시킨 뒤, baseline·threshold·provenance·
|
||||
rollback 네 종류 증거와 관리자 사유가 모두 있을 때만 append-only 승인 effect를 기록한다. 자동 publish 버튼은
|
||||
없고 pending human approval을 권한 경계로 유지한다. 모델·릴리스 monitor와 rollback, 운영 incident→재현 테스트→
|
||||
backlog DAG를 같은 read model에 연결하되 raw transcript·PII·임상 주장·합산 총점을 노출하지 않는다. 내부 write는
|
||||
`VIGNETTE_CONTINUOUS_IMPROVEMENT_INTERNAL_TOKEN`을 DB acquire 전에 검증한다. opt-in scheduled producer는
|
||||
repo-approved synthetic source를 immutable job으로 등록하고 lease/재시도 상태를 durable하게 소유한다.
|
||||
source usage·고정 classification·본문 hash·PII를 model 호출 전에 다시 검사하고, model/structured 실패는
|
||||
`retry_wait`, 안전 gate 실패는 pipeline 미저장 `rejected`, 전 단계 통과만 `pending_human_approval`로 남긴다.
|
||||
API lifespan은 schema contract가 준비됐을 때만 producer를 시작하며 producer는 사람 승인·catalog 승격을
|
||||
수행할 권한이 없다. 별도 opt-in drift trigger는 G6 합성 drift 원장의 canonical 전체·subgroup 임계값과
|
||||
근거 행을 다시 대조한 뒤 metadata-only incident·4-node DAG·`scheduled_incident` job을 같은 research
|
||||
트랜잭션에서 멱등 생성하며, 자동 승인이나 catalog 쓰기 경로를 호출하지 않는다.
|
||||
- **운영 콘솔** `app.admin_health_event` / `app.admin_health_daily_rollup` — 관리자 `/admin/health`
|
||||
조회 시점 또는 `scripts/record-admin-health-sample.py` synthetic sampler 실행 시점의 서비스별 원시
|
||||
헬스 샘플은 `app.admin_health_event`에 남긴다. `scripts/maintain-admin-health-events.py`는 명시
|
||||
|
|
@ -746,6 +875,9 @@ DB는 PostgreSQL 16 + pgvector(단일 SoR). 초기화 SQL은 `infra/db/init/`에
|
|||
- 종단: `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·latency metadata-only; prompt/completion 본문 미저장).
|
||||
- 측정 실행 감사(append-only): `audit.model_run`은 provider/model뿐 아니라 prompt bundle id/version/hash,
|
||||
structured schema version, input evidence hash를 남긴다. `model_inferred`·`agent_reported` 측정은 이 실행을
|
||||
참조하지 않으면 저장할 수 없다.
|
||||
|
||||
### 5.3 ds 스키마 — 재귀학습 파이프라인 (`infra/db/init/04_audit_eval_rls.sql` §4)
|
||||
|
||||
|
|
@ -753,11 +885,16 @@ AI자동(1R) → 인간검수(2R) → IAA 게이트(κ≥0.6, ICC≥0.75) →
|
|||
`ds.dataset` → `ds.dataset_item` → `ds.annotation_round`(ai_auto/human_review) → `ds.annotation`
|
||||
→ `ds.export_manifest`(iaa_kappa/iaa_icc/jsonl_path).
|
||||
|
||||
G0 측정 benchmark는 운영 회기와 분리된 `ds.benchmark_case` / `ds.benchmark_observation`에 둔다. 첫 pack은
|
||||
goal/task mismatch, empathic miss, withdrawal, confrontation, successful/failed repair,
|
||||
warm-but-directionless의 8개 버전 고정 장면이며 기대 방향과 근거 turn index, 금지 주장을 함께 저장한다.
|
||||
|
||||
### 5.4 정보비대칭 2-레이어 + RLS
|
||||
|
||||
DB 레벨 이중강제(`04_audit_eval_rls.sql` §5, `app/db.py` `acquire()`):
|
||||
|
||||
- **레이어1 (AI 정보비대칭)**: `app.ai_context='1'` + `app.current_ai_view`(client/counselor/evaluator)
|
||||
- **레이어1 (AI 정보비대칭)**: `app.ai_context='1'` + `app.current_ai_view`(client/counselor/evaluator;
|
||||
measurement 원장은 supervisor/research까지 확장)
|
||||
+ `app.current_sens_max` → `turns`/`kb.chunk`/`pinned_fact`의 `visible_to[]`/`sensitivity` WHERE 강제.
|
||||
AIView별 sensitivity 상한 기본값: client=1, counselor=0, evaluator=2(`db._default_sensitivity_max`).
|
||||
- **레이어2 (인간 RBAC×cohort)**: `app.current_role` + `app.current_uid` + `app.current_cohort`로
|
||||
|
|
@ -865,7 +1002,8 @@ DB 레벨 이중강제(`04_audit_eval_rls.sql` §5, `app/db.py` `acquire()`):
|
|||
|---|---|---|
|
||||
| DB(Postgres) | `/health` `db:false`, status `degraded` | `app/main.py` lifespan이 dev에서 예외 흡수, `store.py` in-proc로 1턴 동작 |
|
||||
| 엔진 게이트웨이 | `/health` `engine:false` | 턴 생성 실패(503/SSE error). UI/로그인/페르소나/세션생성(in-memory)은 동작 |
|
||||
| OpenAI 키 미설정 | `/voice/health` `degraded` | 음성 비활성, WS는 degraded 통지 후 정상 close |
|
||||
| 선택 STT credential 미설정 | `/voice/health` `degraded` 또는 `ready`의 batch fallback 메타 | Deepgram 선택+key 없음은 OpenAI key가 있을 때만 `openai-batch-fallback`; usable credential이 없으면 WS degraded close |
|
||||
| 운영 TTS provider/model 불일치 | health/ready expected exact match 실패 | 운영은 OpenAI API `gpt-4o-mini-tts`와 AI 생성 음성 고지를 유지하고, Higgs·sample provider는 dev-only로 차단 |
|
||||
| Presidio 미설치 | — | 정규식 PII 마스킹 폴백 |
|
||||
| 승인 페르소나 조회 실패 | persona `degraded=true` | `ALLOW_SEED_PERSONA_FALLBACK=true`면 시드 카드로 폴백 |
|
||||
|
||||
|
|
|
|||
|
|
@ -33,13 +33,27 @@ powershell -NoProfile -ExecutionPolicy Bypass -File scripts\dev-up.ps1 -UseHiggs
|
|||
```
|
||||
|
||||
- 진입점 **http://localhost:5173** → 로그인 페이지에서 **dev-login**(아무 `@hs.ac.kr`, role learner/teacher/admin).
|
||||
- Docker가 있으면 기본적으로 `127.0.0.1:55432` DB 컨테이너를 사용한다. 새 컨테이너 생성 시 `POSTGRES_USER=vignette_owner`, API용 앱 role은 `DATABASE_URL` 사용자로 분리해 RLS 검증 기반을 보존한다. Docker가 없거나 `-NoDb`를 쓰면 in-memory degraded로 뜬다.
|
||||
- Docker가 있으면 기본적으로 `127.0.0.1:55432` DB 컨테이너를 사용한다. 정지된 기존
|
||||
`vignette-dev-db`는 제거·재생성하지 않고 그대로 시작하며, 새 컨테이너만 고정 named volume
|
||||
`vignette-dev-db-pgdata`를 사용한다. 시작이나 readiness가 실패하면 container/volume을 보존한 채
|
||||
fail closed한다. 새 컨테이너 생성 시 `POSTGRES_USER=vignette_owner`, API용 앱 role은
|
||||
`DATABASE_URL` 사용자로 분리해 RLS 검증 기반을 보존한다. Docker가 없거나 `-NoDb`를 쓰면
|
||||
in-memory degraded로 뜬다.
|
||||
- 로그는 `.devlogs/`(gitignore). 코드 수정 후에는 dev-up을 다시 실행해 재기동(reload 미사용).
|
||||
- 옵션: `-NoGateway`(기존 gateway 보존), `-NoWeb`(기존 web 보존, API만 재기동), `-NoDb`(DB 컨테이너 보장 건너뜀),
|
||||
`-UseHiggsVoice`(설치된 `higgs-audio-v3-tts-4b`를 127.0.0.1:9881에 상주시켜 P1 TTS로 연결).
|
||||
- `-NoGateway`/`-NoWeb`를 쓰면 해당 컴포넌트의 stale 정리도 건너뛰고, API 정리는 지정한 `-ApiPort` listener만 대상으로 한다. public `8001`, Tailnet `8010`, local `8000`을 나눠 띄운 상태에서 API-only 재기동할 때 다른 포트를 건드리지 않는다.
|
||||
- `dev-down.ps1`은 기본적으로 DB 컨테이너를 보존한다. 컨테이너도 멈추려면 `-Db`를 명시한다.
|
||||
|
||||
DB나 배포 경로를 바꾸기 전에는 custom-format dump와 SHA-256 manifest를 먼저 만든다. 스크립트는
|
||||
DB 내용을 stdout에 흘리지 않고 container 내부 임시 파일을 `pg_restore --list`로 검증한 뒤에만
|
||||
로컬 백업을 원자 게시한다. 기존 container나 volume을 제거하는 경로는 없다.
|
||||
|
||||
```powershell
|
||||
powershell -NoProfile -ExecutionPolicy Bypass -File scripts\backup-vignette-db.ps1
|
||||
# 기본 위치: D:\workspace\vignette-backups\*.dump + 같은 이름의 .json manifest
|
||||
```
|
||||
|
||||
> 스크립트는 uvicorn이 설치된 python을 자동 해석한다(시스템에 복수 python 공존 시 'python' 별칭이
|
||||
> uvicorn 없는 인터프리터를 가리킬 수 있음 — 이 함정 때문에 명시 해석함).
|
||||
|
||||
|
|
@ -137,6 +151,7 @@ npm install
|
|||
| `DATABASE_URL` | `postgresql://...@127.0.0.1:55432/vignette` | 미연결 시 degraded 폴백(dev 한정) |
|
||||
| `ENGINE_URL` | `http://127.0.0.1:9099` | 엔진 게이트웨이 베이스 URL |
|
||||
| `ENGINE_MODE` | `claude_cli` | `claude_cli` / `claude_api` / `codex_cli` / `agy_cli` 공급자 라우팅. 실제 운영 변경은 관리자 드롭다운이 DB에 저장 |
|
||||
| `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`에 미리 등록된 정확한 이메일은 도메인 밖이어도 예외로 로그인 가능 |
|
||||
|
|
@ -145,8 +160,14 @@ npm install
|
|||
| `AUTH_NEW_USER_DEFAULT_STATUS` | `pending` | Google/SAML 신규 사용자의 기본 승인 상태. `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_VOICE_POC_SAMPLE_TTS` | `false` | P1 무참조 샘플 음성을 `/voice/ws` TTS에 연결하는 개발 전용 플래그. 마이크/STT는 `OPENAI_API_KEY` 필요, 프로덕션 금지 |
|
||||
| `VIGNETTE_VOICE_TTS_PROVIDER` | `openai` 또는 `higgs` | TTS 공급자. `higgs`는 dev + P1에서만 허용하며 다른 환경은 설정 검증에서 차단 |
|
||||
| `VIGNETTE_VOICE_STT_PROVIDER` | `openai` 또는 `deepgram` | STT 공급자. `deepgram`은 streaming interim/final 경로, `openai`는 batch 경로 |
|
||||
| `DEEPGRAM_API_KEY` | 비밀값 | Deepgram streaming credential. key 없이 `deepgram`을 고르면 `OPENAI_API_KEY`가 있을 때만 `openai-batch-fallback`; live Deepgram 증거가 아님 |
|
||||
| `DEEPGRAM_STT_URL` / `DEEPGRAM_STT_MODEL` / `DEEPGRAM_STT_LANGUAGE` | `wss://api.deepgram.com/v1/listen` / `nova-3` / `ko` | Deepgram endpoint와 provider/model metadata 계약 |
|
||||
| `DEEPGRAM_ENDPOINTING_MS` / `DEEPGRAM_UTTERANCE_END_MS` | `300` / `1200` | streaming EOT 경계. utterance end 최솟값은 1000ms |
|
||||
| `DEEPGRAM_KEEPALIVE_SECONDS` / `DEEPGRAM_FINALIZE_TIMEOUT_SECONDS` | `4` / `15` | streaming keepalive와 final 대기 상한 |
|
||||
| `DEEPGRAM_MIP_OPT_OUT` | `true` | Deepgram model improvement program opt-out query 기본값 |
|
||||
| `VIGNETTE_VOICE_POC_SAMPLE_TTS` | `false` | P1 무참조 샘플 음성을 `/voice/ws` TTS에 연결하는 개발 전용 플래그. 마이크/STT는 선택한 STT provider credential 필요, 프로덕션 금지 |
|
||||
| `VIGNETTE_VOICE_TTS_PROVIDER` | `openai` 또는 `higgs` | TTS 공급자. `higgs`는 dev + P1에서만 허용하며 다른 환경은 설정 검증에서 차단. 동작 자체는 운영 상업 이용권 증거를 대신하지 않음 |
|
||||
| `VIGNETTE_HIGGS_TTS_URL` | `http://127.0.0.1:9881` | 로컬 Higgs 상주 서버. 저장소의 무참조 synthetic seed만 화자 참조로 사용 |
|
||||
| `EVALUATOR_SEMANTIC_CACHE_ENABLED` | `true` | fast/deep evaluator structured 결과 인메모리 캐시 활성화. 원문 prompt/completion은 저장하지 않음 |
|
||||
| `EVALUATOR_SEMANTIC_CACHE_TTL_SECONDS` | `900` | evaluator cache TTL(초). 0 이하면 비활성 |
|
||||
|
|
@ -209,7 +230,40 @@ py -3.11 scripts\run-session-digest-worker.py --session-id <session_uuid> --appl
|
|||
- scheduler도 DB load/apply 구간만 connection을 잡고, engine 호출은 DB transaction 밖에서 수행한다.
|
||||
- 장시간 provider 운영, 임상 골든셋 품질평가, 재압축 정책은 별도 gate다.
|
||||
|
||||
### 2.5 DB 없이 degraded 기동 (정상 동작)
|
||||
### 2.5 G8 scheduled agentic producer
|
||||
|
||||
G8 scheduler는 한 job에 generator·독립 reviewer·variant judge 모델 호출이 여러 번 발생하므로 기본값이
|
||||
`false`다. `infra/db/init/14_continuous_improvement.sql`의 runtime contract가 준비된 경우에만 API lifespan이
|
||||
producer를 시작한다. 활성화하면 저장소의 승인된 비식별 synthetic source pack을 immutable
|
||||
`app.ci_agentic_job`으로 멱등 등록하고, lease·`FOR UPDATE SKIP LOCKED`로 제한된 batch를 처리한다.
|
||||
|
||||
```powershell
|
||||
$env:VIGNETTE_CONTINUOUS_IMPROVEMENT_PRODUCER_ENABLED = "true"
|
||||
$env:VIGNETTE_CONTINUOUS_IMPROVEMENT_PRODUCER_INTERVAL_SECONDS = "3600"
|
||||
$env:VIGNETTE_CONTINUOUS_IMPROVEMENT_PRODUCER_STARTUP_DELAY_SECONDS = "30"
|
||||
$env:VIGNETTE_CONTINUOUS_IMPROVEMENT_PRODUCER_RETRY_DELAY_SECONDS = "300"
|
||||
$env:VIGNETTE_CONTINUOUS_IMPROVEMENT_PRODUCER_LEASE_TIMEOUT_SECONDS = "1800"
|
||||
$env:VIGNETTE_CONTINUOUS_IMPROVEMENT_PRODUCER_ENGINE_TIMEOUT_SECONDS = "300"
|
||||
$env:VIGNETTE_CONTINUOUS_IMPROVEMENT_PRODUCER_BATCH_SIZE = "1"
|
||||
$env:VIGNETTE_CONTINUOUS_IMPROVEMENT_DRIFT_TRIGGER_ENABLED = "true"
|
||||
```
|
||||
|
||||
- source usage가 `approved`가 아니거나 classification·본문 SHA-256·PII 검사가 실패하면 engine을 호출하지 않는다.
|
||||
- engine/structured-output 실패는 candidate를 저장하지 않고 `retry_wait`로 남긴다. 한 job 실패는 다음 job을 막지 않는다.
|
||||
- 안전 gate 실패는 `rejected`, 전 gate 통과 결과는 `completed` job과 `pending_human_approval` candidate로만 저장한다.
|
||||
- producer는 사람 승인 event나 catalog entry를 만들지 않는다. 동일 submission recovery는 model call 0으로 재생한다.
|
||||
- drift trigger는 producer와 별도 opt-in이다. 켜면 G6 합성 drift 원장의 canonical 임계값·subgroup 근거를
|
||||
재검증해 metadata-only incident·4-node DAG·`scheduled_incident` job만 원자적으로 enqueue한다. 두 설정 중
|
||||
하나라도 false면 운영 drift 자동 기동은 없다.
|
||||
|
||||
실제 configured engine과 dev PostgreSQL을 일회 검증할 때는 로그인 없이 아래 runner를 사용한다.
|
||||
|
||||
```powershell
|
||||
py -3.11 -X utf8 scripts\smoke-continuous-improvement-agentic-producer.py `
|
||||
--out docs\ops\evidence\continuous-improvement-agentic-producer-live-2026-08-07.json
|
||||
```
|
||||
|
||||
### 2.6 DB 없이 degraded 기동 (정상 동작)
|
||||
|
||||
DB 연결이 안 되어도 dev에서는 그대로 기동한다. `main.py` lifespan이 풀 초기화 예외를 잡고
|
||||
`store` 인메모리 폴백으로 degraded 기동하며, 다음 경고를 남긴다.
|
||||
|
|
@ -434,6 +488,9 @@ python -m uvicorn engine_gateway.gateway:app --host 127.0.0.1 --port 9099
|
|||
```
|
||||
|
||||
관련 환경변수(`gateway.py`):
|
||||
- `ENGINE_GATEWAY_SHARED_SECRET` — 선택 shared secret. 설정하면 `/health`를 제외한 모든 경로가
|
||||
`X-Vignette-Engine-Token`을 constant-time으로 검증한다. API의 `EngineClient`에도 같은 값을 설정한다.
|
||||
값 자체는 로그나 OpenAPI 스키마에 노출하지 않는다. 빈 값은 기존 로컬 9099 호출을 그대로 허용한다.
|
||||
- `CLAUDE_BIN` — claude 실행 파일 경로(기본 `claude`). PATH에 없으면 절대경로 지정.
|
||||
- `CODEX_BIN`, `AGY_BIN` — 각 CLI 경로. Windows Codex는 npm shim 아래 native exe를 자동 탐색한다.
|
||||
- `ANTHROPIC_API_KEY`, `ANTHROPIC_API_BASE` — Anthropic 모델 목록·Messages API.
|
||||
|
|
@ -451,8 +508,9 @@ python -m uvicorn engine_gateway.gateway:app --host 127.0.0.1 --port 9099
|
|||
|
||||
```powershell
|
||||
Invoke-RestMethod http://127.0.0.1:9099/health
|
||||
Invoke-RestMethod 'http://127.0.0.1:9099/v1/capabilities?provider=codex_cli&force=true'
|
||||
Invoke-RestMethod 'http://127.0.0.1:9099/ready?provider=codex_cli&model=gpt-5.6-terra&reasoning_effort=medium'
|
||||
$headers = @{ 'X-Vignette-Engine-Token' = $env:ENGINE_GATEWAY_SHARED_SECRET }
|
||||
Invoke-RestMethod 'http://127.0.0.1:9099/ready?provider=codex_cli&model=gpt-5.6-terra&reasoning_effort=medium' -Headers $headers
|
||||
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`에 전달하고
|
||||
|
|
@ -496,6 +554,18 @@ python scripts\check-deploy-preflight.py --env-file infra\.env
|
|||
python scripts\check-deploy-preflight.py --env-file infra\.env --database-url $env:DATABASE_URL --require-app-role
|
||||
```
|
||||
|
||||
현재 Windows public runtime처럼 `apps/api/.env`의 `DATABASE_URL`로 직접 Uvicorn을 띄우는 경로는 Compose
|
||||
패키징 비밀번호를 요구하지 않는 전용 profile을 쓴다. G3~G8 내부 토큰 값은 명령행이나 로그에 출력하지 않는다.
|
||||
|
||||
```powershell
|
||||
python scripts\provision-outcome-os-runtime-secrets.py --env-file apps\api\.env
|
||||
python scripts\check-deploy-preflight.py --env-file apps\api\.env --deployment-profile direct-runtime --require-app-role
|
||||
```
|
||||
|
||||
운영·스테이징 env는 G3~G8 내부 ingestion 경계별로 서로 다른 32자 이상 토큰 6개를 가져야 한다.
|
||||
프리플라이트는 누락·짧은 값·예시 값·재사용 값을 배포 전에 거부하며, Compose도 같은 키를 필수로 전달한다.
|
||||
DB 검사를 켜면 G0~G8 migration별 대표 원장 테이블도 전부 확인하므로 하나라도 빠진 스키마에는 배포하지 않는다.
|
||||
|
||||
엔진 게이트웨이(9099)는 컴포즈 밖 호스트에서 돌리고, api 컨테이너는
|
||||
`ENGINE_URL=http://host.docker.internal:9099`로 호출한다(compose 기본값).
|
||||
RAG 임베딩/리랭커 의존성은 기본 이미지에 설치하지 않는다. 모델까지 포함한 API 이미지를 만들 때만
|
||||
|
|
|
|||
|
|
@ -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 관측·일별 비용 추이·모델별 비용 검증 리포트 2차 완료 | 턴별 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을 산출한다. DB 미가용 dev는 runtime store fallback, prod는 fail-closed다. | 자동 차단·한도 enforcement 정책. |
|
||||
| **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 갱신·회귀. |
|
||||
| **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 비용'을 운영 리스크로 명시.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue