vignette/docs/decisions/outcome-alliance-measurement-ledger.md
Yun Chan 16e791e044 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 산출물은 커밋에서 제외했다.
2026-08-08 01:30:53 +09:00

74 lines
4.7 KiB
Markdown

# ADR — Outcome & Alliance 측정 원장과 provenance 경계
- 상태: Accepted
- 결정일: 2026-08-06
- 범위: Outcome & Alliance OS G0 / AOS-002
- 소유 코드: `apps/api/app/contracts/measurement.py`, `infra/db/init/07_measurement_foundation.sql`
## 맥락
Vignette에는 이미 `rapport_credit`, `case_profile.alliance_level`, fast/deep LLM 평가, Phase 3 KPI가 있다.
그러나 이 값들은 생성 주체와 관점이 서로 다르다. 특히 상태머신 라포 값은 시뮬레이션 진행 상태이고,
LLM 평가는 모델 추론이며, 학습자 자기보고와 교수자 평정은 또 다른 증거층이다. 이를 하나의 `alliance score`
합치면 UI는 그럴듯해도 측정 의미와 재현 가능성이 사라진다.
## 결정
1. 모든 새 측정은 append-only `app.measurement_event`로 기록한다. 정정은 UPDATE가 아니라
`supersedes_id`를 가진 새 이벤트로만 표현한다.
2. 척도와 훈련지표는 버전 고정 `app.measurement_instrument`에 등록한다.
3. 모델 또는 에이전트가 만든 측정은 반드시 append-only `audit.model_run`을 참조한다. 실행에는 provider,
model, prompt bundle id/version/hash, structured schema version, 입력 근거 hash를 남긴다.
4. source와 perspective를 다음 1:1 경계로 제한한다.
| source | perspective |
|---|---|
| `simulated_state` | `client_simulation` |
| `model_inferred` | `independent_observer` |
| `agent_reported` | `client_agent_report` |
| `learner_reported` | `learner_self_report` |
| `human_rated` | `supervisor_human` |
| `observed_runtime` | `runtime_observation` |
5. 서로 다른 source/perspective 층은 평균·총점으로 합치지 않는다. 제품에서는 관점별 series를 나란히
보여주고 차이 자체를 학습 근거로 쓴다.
6. `rapport_credit`와 기존 `alliance_level`은 이름과 무관하게 `simulation_progress`로만 이관한다.
Working Alliance 임상 측정으로 승격하지 않는다.
7. `visible_to[]`와 RLS가 client/counselor/evaluator/supervisor/research 정보 경계를 물리적으로 강제한다.
8. benchmark는 `ds` schema에 격리하고 운영 회기 행과 결합하지 않는다.
## 선택하지 않은 대안
- 기존 `alliance_level` 컬럼 이름을 그대로 공식 동맹 점수로 사용: 출처와 관점이 없고 임상적 과대주장을 만든다.
- 현재 행을 UPDATE하는 mutable score table: 모델 교체·교수자 재평정·오류 정정 이력을 재현할 수 없다.
- 모든 관점을 단일 0~100 점수로 정규화: 숫자 비교는 쉬워지지만 불일치가 숨고 자기보정 학습이 불가능해진다.
- 모델명만 기록: prompt/schema/input drift를 분리할 수 없어 재현 계약으로 부족하다.
## 전진 복구와 롤백
이 마이그레이션은 기존 컬럼을 삭제하거나 덮어쓰지 않는 additive 변경이다.
- 애플리케이션 기능 롤백: 새 measurement read/write 경로의 feature flag를 끄고 기존 세션·리뷰 read model로
즉시 복귀한다. 이미 쌓인 원장 행은 감사 증거로 보존한다.
- 데이터 정정: 잘못된 측정 행은 삭제·수정하지 않고 `status='rejected'` 또는 정정 값을 가진 새 이벤트로
supersede한다.
- schema 전진 복구: 계약 불일치는 다음 버전의 instrument/schema와 새 이벤트로 해결한다. 같은
instrument version 또는 benchmark version의 의미를 바꾸지 않는다.
- 물리 schema 제거: 운영 데이터가 한 건이라도 있으면 기본 롤백 절차로 허용하지 않는다. 완전 미사용이
DB 쿼리로 증명된 개발 환경에서만 역순으로 `ds.benchmark_observation`, `ds.benchmark_case`,
`app.measurement_event`, `audit.model_run`, `app.measurement_instrument`를 제거할 수 있다.
## 검증 계약
- Python Pydantic 계약, 생성 JSON Schema, TypeScript 계약, PostgreSQL CHECK enum이 일치해야 한다.
- benchmark 8개 장면은 고정 ID·버전과 근거 turn index를 가져야 한다.
- 혼합 provenance 집계, source/perspective 불일치, 모델 실행 provenance 없는 모델 측정은 실패해야 한다.
- measurement/model_run UPDATE·DELETE는 trigger로 실패하고, RLS는 AI view 및 사용자 역할별 가시성을
차단해야 한다.
- 기존 DB-backed 세션·종료·리뷰 E2E가 무회귀여야 G0를 닫는다.
## 결과
이 결정은 새 기능의 속도를 조금 늦추는 대신, G1 이후 goal/task/bond, rupture/repair, 장기 성과 궤적이
무엇을 누구의 관점에서 어떻게 측정했는지 잃지 않게 한다. 숫자가 같아도 출처가 다르면 다른 증거라는 원칙이
제품 UI, API, DB, benchmark에 동일하게 유지된다.