정식 승격 상태판과 증거를 동기화

This commit is contained in:
Yun Chan 2026-08-30 00:06:45 +09:00
parent 34aff65cb0
commit be08c0b573
16 changed files with 1160 additions and 63 deletions

View file

@ -237,11 +237,25 @@ RBAC×AIView로 차단된다. 이 모듈은 평가 신호만 산출한다.
cache key·prompt·completion 없이 enabled/entries/hits/misses/stores/evictions/requests/hit_rate와
일별 `daily_cost` bucket(day, turns, tokens, cost)을 관리자 관측값으로 반환한다. 비용은
공급자가 반환한 저장값을 우선하되 Claude CLI는 SDK 추정치(`provider_estimate`)로 명시하고,
저장 비용이 없는 Agy/Gemini·Codex·Claude API는 `app.services.llm_pricing`의 버전 고정 공식 참조단가(`reference_rate`)로
입력·캐시 입력·출력 토큰을 환산한다. 기존 DB에 비용 0으로 저장된 행도 조회 시 같은 단가로
보정하며, 단가가 없는 모델은 0달러로 위장하지 않고 `unavailable`로 표시한다.
응답은 `recorded_cost_usd`, `estimated_cost_usd`, provider/model별 `cost_basis`,
`rate_label`, `rate_source_url`을 함께 반환하고 예산 상태는 두 비용을 합친 유효 비용을 사용한다.
신규 Agy/Gemini·Codex·Claude API 호출은 `app.services.llm_pricing`의 호출 시작일 기준 공식
참조단가(`reference_rate`)로 입력·캐시 입력·출력 토큰을 환산한다. Gemini 3.6/3.7 Flash는
유효기간이 있는 단가 구간을 사용하고, 기존 DB의 비용 0 행은 일별 합산 전에 요청별로 다시
가격화해 200K 같은 요청 단위 경계를 보존한다. 과거 행은 캐시 입력 수가 따로 남아 있지 않아
전체 입력 단가로 계산한 보수적 상한(`reference_upper_bound`, UI `참조 상한`)으로 표시한다.
호출별 확정 산정액과 미산정액이 섞이면 `partial``$…+`로 표시한다. 상한 추정과 미산정이
함께 있으면 방향을 단정할 수 없으므로 `partial_upper_bound``미산정`으로 표시하며, 단가가
전혀 없는 모델도 0달러로 위장하지 않고 `unavailable`로 표시한다. 양수로 저장된 과거 참조
비용은 현재 단가 라벨을 다시 붙이지 않고 `호출 시점에 저장된 참조단가 추정값`으로만 설명한다.
응답은 전체·일별·provider/model·예산에 `cost_basis`를 전파하고 `recorded_cost_usd`,
`estimated_cost_usd`, `rate_label`, `rate_source_url`을 함께 반환한다. 부분 산정·상한 상태에서는
호출당 비용·1천 토큰당 비용·비용 비중을 확정값처럼 계산하지 않으며 예산도 `indeterminate`
사용할 수 있다. DB 조회는 동일한 day/provider/model/token/cost 시그니처만 묶어 요청별 단가
경계를 보존하고, migration 18의 client-turn `created_at` partial index로 기간 조회를 제한한다.
이 인덱스는 release agent가 `CREATE INDEX CONCURRENTLY`로 트랜잭션 밖에서 적용해 턴 쓰기를
막지 않으며, 다른 schema migration은 기존 단일 트랜잭션 적용을 유지한다.
제어된 음성 E2E가 남긴 정확한 `(e2e, fake-client, input=1, output=1, cost=0)` 조합은 비용 원장·예산에서만 제외하며,
다른 `e2e` provider/model 조합이나 원본 DB 행을 포괄 삭제하지 않는다. 음성 성공 E2E의
전용 disposable PostgreSQL 격리는 후속 항목이며, 공유 DB를 직접 삭제하는 cleanup은 사용하지 않는다.
`EVALUATOR_SEMANTIC_CACHE_ENABLED`, `EVALUATOR_SEMANTIC_CACHE_TTL_SECONDS`,
`EVALUATOR_SEMANTIC_CACHE_MAX_ENTRIES`로 제한하며, 원문 prompt/completion은 캐시에 저장하지 않는다.
cache hit은 `engine.generate()`와 metadata-only `audit.llm_call_log` 기록을 건너뛰고,
@ -1053,10 +1067,29 @@ DB 레벨 이중강제(`04_audit_eval_rls.sql` §5, `app/db.py` `acquire()`):
렌더링하지 않고, 빈 상태 카드 대신 가입 직후 사용자 정보를 입력하는 단순 폼만 보여준다.
이 화면은 이메일을 다시 받지 않고 닉네임, 자기소개,
선택 아바타 이미지, 이름, 소속, 학과, 학년/직위, 연락처, 주소/수령지와 서비스 이용약관·개인정보
처리방침 초안 동의를 저장한다. 아바타 파일은 `POST /users/me/avatar`가 MIME/시그니처/3MB 제한을
확인한 뒤 `USER_UPLOAD_DIR/profile-avatars`에 저장하고 URL만 `app_user.avatar_url`에 보관한다.
처리방침 초안 동의를 저장한다. 아바타 파일은 `POST /users/me/avatar`가 MIME/시그니처/3MB 제한에 더해
Pillow full decode와 확장자-format 일치를 확인한 뒤 `USER_UPLOAD_DIR/profile-avatars`에 저장하고 URL만
`app_user.avatar_url`에 보관한다. 시그니처만 닮고 실제 decode가 실패하는 payload는 저장 전에 거부한다.
학습자 `POST /sessions`와 dev 음성 persona 시작은 온보딩 완료 후에만 허용하며, 온보딩 저장 시
learner `consent_at`도 함께 세팅한다.
- public avatar storage는 `USER_UPLOAD_DIR/profile-avatars``/uploads/profile-avatars`에 mount한다. 같은
`USER_UPLOAD_DIR``multimodal-audio`는 private adapter 전용이며 static mount와 public migration에서 제외한다.
public runtime은 source/upload root 밖 private migration receipt(SHA/root binding)와 현재 DB의 모든 managed avatar
참조 파일(regular/no-reparse/readable)을 기동마다 검증한다. 초기 8개 migration snapshot과 현재 set은 동일할 필요가
없지만, non-empty initialization 뒤 current set이 0이 되려면 별도 reset receipt가 필요하다.
- upload manifest v3는 object count뿐 아니라 전체 byte 합계·inventory SHA-256·객체별 `decode_valid`와 preserved/current
decode 정상/실패 count를 결속한다. immutable public cache에는 decode-valid bytes만 올리고, preserved이지만 decode-invalid인
legacy payload는 forensic 보존하되 static handler가 404로 차단해 프론트 fallback을 사용하게 한다. 경로 존재·PNG signature만으로
정상 이미지를 선언하지 않는다. manifest 이후 생성된 UUID형 업로드도 DB URL 기록·현재 참조 검증·GET/HEAD마다 용량 제한과
Pillow full decode·확장자-format 일치를 다시 확인하고, 검증한 동일 bytes를 응답해 검사 뒤 재오픈 경쟁을 막는다. 이후 손상된
신규 파일은 current invalid count와 health fallback에 반영되고 public 404가 된다. 2026-08-29 감사 기준은 union 93 objects·
52,973 bytes·`9d703126…e6aa`, decode 정상 3/
실패 90, 현 DB 참조 정상 2/실패 6/missing 0이다. 이 계약은 후보 코드이며 public API 장애 복구·전체 E2E·정식 승격 전에는
deployed runtime의 동작으로 간주하지 않는다.
- 프로필 업로드는 새 random target을 create-new 방식으로 완전히 flush한 뒤 publish하고 기존 DB 참조 파일을 선삭제하지
않는다. upload 뒤 PATCH가 실패해도 기존 URL의 파일은 남으며, unreferenced object 정리는 별도 manifest-aware GC가
소유한다. fresh storage cutover는 API write-lease freeze가 active/valid/drained임을 증명하고 tunnel-first로 외부 write를
닫은 뒤 진행한다.
- 관리자/교수자 권한은 온보딩 화면에서 신청받지 않는다. 서버는 `AUTH_ADMIN_EMAILS` /
`AUTH_TEACHER_EMAILS` allowlist와 관리자 사용자 관리 경로로만 역할을 부여한다.
- 로컬/Tailnet dev는 dev-login을 사용한다. 공개 `OAUTH_REDIRECT_URI`가 로컬/Tailnet 세션이 아니라 public API