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:
Yun Chan 2026-08-08 01:30:53 +09:00
parent 93dd8f82d7
commit 16e791e044
390 changed files with 243188 additions and 499 deletions

View file

@ -0,0 +1,360 @@
\set ON_ERROR_STOP on
\echo 'G3 rupture/repair ledger live smoke: begin'
BEGIN;
INSERT INTO app.app_user (user_id, external_id, email, display_name, role, cohort)
VALUES
('91000000-0000-0000-0000-000000000001','smoke:g3:learner:a','g3-a@example.invalid','G3 A','learner','g3-a'),
('91000000-0000-0000-0000-000000000002','smoke:g3:learner:b','g3-b@example.invalid','G3 B','learner','g3-b'),
('91000000-0000-0000-0000-000000000003','smoke:g3:teacher','g3-t@example.invalid','G3 T','instructor','g3-a'),
('91000000-0000-0000-0000-000000000004','smoke:g3:admin','g3-admin@example.invalid','G3 Admin','admin',NULL);
INSERT INTO app.case_profile (case_id, persona_id, learner_id)
VALUES
('81000000-0000-0000-0000-000000000001','71000000-0000-0000-0000-000000000001','91000000-0000-0000-0000-000000000001'),
('81000000-0000-0000-0000-000000000002','71000000-0000-0000-0000-000000000002','91000000-0000-0000-0000-000000000002');
INSERT INTO app.sessions (id, case_id, learner_id, session_no, ended_at)
VALUES
('61000000-0000-0000-0000-000000000001','81000000-0000-0000-0000-000000000001','91000000-0000-0000-0000-000000000001',1,now()),
('61000000-0000-0000-0000-000000000002','81000000-0000-0000-0000-000000000002','91000000-0000-0000-0000-000000000002',1,now());
INSERT INTO app.turns (id, session_id, seq, speaker, text, text_masked)
VALUES
('51000000-0000-0000-0000-000000000001','61000000-0000-0000-0000-000000000001',1,'client','[smoke] 불편함','[smoke] 불편함'),
('51000000-0000-0000-0000-000000000002','61000000-0000-0000-0000-000000000001',2,'counselor','[smoke] 다시 확인','[smoke] 다시 확인'),
('51000000-0000-0000-0000-000000000003','61000000-0000-0000-0000-000000000002',1,'client','[smoke] other','[smoke] other');
INSERT INTO audit.model_run (
model_run_id, session_id, agent_role, provider, model,
prompt_bundle_id, prompt_bundle_version, prompt_bundle_hash,
structured_schema_version, input_evidence_hash, status
) VALUES
(
'41000000-0000-0000-0000-000000000001','61000000-0000-0000-0000-000000000001',
'evaluator','smoke','smoke-fast','rupture-observer','1.0.0',repeat('a',64),
'vignette.rupture-repair-assessment.v1',repeat('b',64),'ready'
),
(
'41000000-0000-0000-0000-000000000002','61000000-0000-0000-0000-000000000001',
'evaluator','smoke','smoke-deep','rupture-observer','1.0.0',repeat('c',64),
'vignette.rupture-repair-assessment.v1',repeat('d',64),'ready'
);
INSERT INTO app.safety_events (
id, session_id, turn_id, trigger_type, ko_risk_level, escalated, detail
) OVERRIDING SYSTEM VALUE VALUES
(980000001,'61000000-0000-0000-0000-000000000001',
'51000000-0000-0000-0000-000000000001','smoke_reference',2,true,
'{"smoke":true}'::jsonb),
(980000002,'61000000-0000-0000-0000-000000000002',
'51000000-0000-0000-0000-000000000003','smoke_other_session',1,false,
'{"smoke":true}'::jsonb);
SET LOCAL ROLE vignette;
SELECT set_config('app.ai_context','1',true);
SELECT set_config('app.current_ai_view','evaluator',true);
SELECT set_config('app.current_role','',true);
SELECT set_config('app.current_uid','',true);
SELECT set_config('app.current_cohort','',true);
INSERT INTO app.rupture_episode (
episode_id, session_id, case_id, learner_id, episode_key, created_by_role
) VALUES (
'31000000-0000-0000-0000-000000000001','61000000-0000-0000-0000-000000000001',
'81000000-0000-0000-0000-000000000001','91000000-0000-0000-0000-000000000001',
'smoke-g3-episode','agent'
);
INSERT INTO app.rupture_observation_event (
observation_id, episode_id, session_id, idempotency_key, content_hash,
event_kind, from_state, to_state, rupture_type, source_kind, perspective,
ai_view, confidence, uncertainty, evidence_turn_ids, model_run_id, created_by_role
) VALUES
(
'21000000-0000-0000-0000-000000000001','31000000-0000-0000-0000-000000000001',
'61000000-0000-0000-0000-000000000001','11000000-0000-0000-0000-000000000001',repeat('1',64),
'rupture.detected',NULL,'onset','withdrawal','model_inferred','independent_observer',
'evaluator',0.80,0.20,ARRAY['51000000-0000-0000-0000-000000000001']::uuid[],
'41000000-0000-0000-0000-000000000001','agent'
),
(
'21000000-0000-0000-0000-000000000002','31000000-0000-0000-0000-000000000001',
'61000000-0000-0000-0000-000000000001','11000000-0000-0000-0000-000000000002',repeat('2',64),
'rupture.recognized','onset','recognized','withdrawal','model_inferred','independent_observer',
'evaluator',0.90,0.10,ARRAY['51000000-0000-0000-0000-000000000002']::uuid[],
'41000000-0000-0000-0000-000000000002','agent'
),
(
'21000000-0000-0000-0000-000000000003','31000000-0000-0000-0000-000000000001',
'61000000-0000-0000-0000-000000000001','11000000-0000-0000-0000-000000000003',repeat('3',64),
'repair.attempted','recognized','repair_attempted','withdrawal','model_inferred','independent_observer',
'evaluator',0.90,0.10,ARRAY['51000000-0000-0000-0000-000000000002']::uuid[],
'41000000-0000-0000-0000-000000000002','agent'
),
(
'21000000-0000-0000-0000-000000000004','31000000-0000-0000-0000-000000000001',
'61000000-0000-0000-0000-000000000001','11000000-0000-0000-0000-000000000004',repeat('4',64),
'repair.resolved','repair_attempted','resolved','withdrawal','model_inferred','independent_observer',
'evaluator',0.94,0.06,ARRAY['51000000-0000-0000-0000-000000000001','51000000-0000-0000-0000-000000000002']::uuid[],
'41000000-0000-0000-0000-000000000002','agent'
);
-- Same idempotency key + same hash is a no-op.
INSERT INTO app.rupture_observation_event (
observation_id, episode_id, session_id, idempotency_key, content_hash,
event_kind, from_state, to_state, rupture_type, source_kind, perspective,
ai_view, confidence, uncertainty, evidence_turn_ids, model_run_id, created_by_role
) VALUES (
'21000000-0000-0000-0000-000000000011','31000000-0000-0000-0000-000000000001',
'61000000-0000-0000-0000-000000000001','11000000-0000-0000-0000-000000000001',repeat('1',64),
'rupture.detected',NULL,'onset','withdrawal','model_inferred','independent_observer',
'evaluator',0.80,0.20,ARRAY['51000000-0000-0000-0000-000000000001']::uuid[],
'41000000-0000-0000-0000-000000000001','agent'
) ON CONFLICT DO NOTHING;
DO $$
BEGIN
BEGIN
INSERT INTO app.rupture_observation_event (
episode_id, session_id, idempotency_key, content_hash,
event_kind, from_state, to_state, rupture_type, source_kind, perspective,
ai_view, confidence, uncertainty, evidence_turn_ids, model_run_id, created_by_role
) VALUES (
'31000000-0000-0000-0000-000000000001','61000000-0000-0000-0000-000000000001',
'11000000-0000-0000-0000-000000000001',repeat('9',64),
'rupture.detected',NULL,'onset','withdrawal','model_inferred','independent_observer',
'evaluator',0.80,0.20,ARRAY['51000000-0000-0000-0000-000000000001']::uuid[],
'41000000-0000-0000-0000-000000000001','agent'
);
RAISE EXCEPTION 'mismatched observation idempotency key was accepted';
EXCEPTION WHEN unique_violation THEN NULL;
END;
BEGIN
INSERT INTO app.rupture_observation_event (
episode_id, session_id, idempotency_key, content_hash,
event_kind, from_state, to_state, rupture_type, source_kind, perspective,
ai_view, confidence, uncertainty, evidence_turn_ids, model_run_id, created_by_role
) VALUES (
'31000000-0000-0000-0000-000000000001','61000000-0000-0000-0000-000000000001',
'11000000-0000-0000-0000-000000000012',repeat('a',64),
'repair.attempted','resolved','repair_attempted','withdrawal','model_inferred','independent_observer',
'evaluator',0.80,0.20,ARRAY['51000000-0000-0000-0000-000000000002']::uuid[],
'41000000-0000-0000-0000-000000000002','agent'
);
RAISE EXCEPTION 'resolved rupture was allowed to move backward';
EXCEPTION WHEN check_violation THEN NULL;
END;
BEGIN
INSERT INTO app.rupture_observation_event (
episode_id, session_id, idempotency_key, content_hash,
event_kind, from_state, to_state, rupture_type, source_kind, perspective,
ai_view, confidence, uncertainty, evidence_turn_ids, model_run_id, created_by_role
) VALUES (
'31000000-0000-0000-0000-000000000001','61000000-0000-0000-0000-000000000001',
'11000000-0000-0000-0000-000000000013',repeat('b',64),
'repair.attempted','resolved','repair_attempted','withdrawal','model_inferred','independent_observer',
'evaluator',0.80,0.20,ARRAY['51000000-0000-0000-0000-000000000003']::uuid[],
'41000000-0000-0000-0000-000000000002','agent'
);
RAISE EXCEPTION 'cross-session evidence turn was accepted';
EXCEPTION WHEN check_violation THEN NULL;
END;
END;
$$;
INSERT INTO app.rupture_safety_reference (
episode_id, session_id, safety_event_id
) VALUES (
'31000000-0000-0000-0000-000000000001',
'61000000-0000-0000-0000-000000000001',980000001
);
DO $$
BEGIN
BEGIN
INSERT INTO app.rupture_safety_reference (
episode_id, session_id, safety_event_id
) VALUES (
'31000000-0000-0000-0000-000000000001',
'61000000-0000-0000-0000-000000000001',980000002
);
RAISE EXCEPTION 'cross-session safety reference was accepted';
EXCEPTION WHEN check_violation THEN NULL;
END;
END;
$$;
INSERT INTO app.rupture_reconciliation_revision (
revision_id, episode_id, session_id, revision_no, idempotency_key, content_hash,
fast_warning_observation_id, deep_observation_id, fast_warning_id,
provisional_status, deep_status, disposition, uncertainty, evidence_turn_ids,
model_run_id
) VALUES (
'20000000-0000-0000-0000-000000000001','31000000-0000-0000-0000-000000000001',
'61000000-0000-0000-0000-000000000001',1,
'10000000-0000-0000-0000-000000000001',repeat('5',64),
'21000000-0000-0000-0000-000000000001','21000000-0000-0000-0000-000000000004',
'smoke-fast-warning','missed','resolved','superseded_resolved',0.06,
ARRAY['51000000-0000-0000-0000-000000000001','51000000-0000-0000-0000-000000000002']::uuid[],
'41000000-0000-0000-0000-000000000002'
);
INSERT INTO app.rupture_reconciliation_revision (
revision_id, episode_id, session_id, revision_no, idempotency_key, content_hash,
fast_warning_observation_id, deep_observation_id, fast_warning_id,
provisional_status, deep_status, disposition, uncertainty, evidence_turn_ids,
model_run_id
) VALUES (
'20000000-0000-0000-0000-000000000011','31000000-0000-0000-0000-000000000001',
'61000000-0000-0000-0000-000000000001',1,
'10000000-0000-0000-0000-000000000001',repeat('5',64),
'21000000-0000-0000-0000-000000000001','21000000-0000-0000-0000-000000000004',
'smoke-fast-warning','missed','resolved','superseded_resolved',0.06,
ARRAY['51000000-0000-0000-0000-000000000001']::uuid[],
'41000000-0000-0000-0000-000000000002'
) ON CONFLICT DO NOTHING;
DO $$
BEGIN
BEGIN
INSERT INTO app.rupture_reconciliation_revision (
episode_id, session_id, revision_no, idempotency_key, content_hash,
fast_warning_observation_id, deep_observation_id, fast_warning_id,
provisional_status, deep_status, disposition, uncertainty, model_run_id
) VALUES (
'31000000-0000-0000-0000-000000000001','61000000-0000-0000-0000-000000000001',1,
'10000000-0000-0000-0000-000000000001',repeat('6',64),
'21000000-0000-0000-0000-000000000001','21000000-0000-0000-0000-000000000004',
'smoke-fast-warning','missed','resolved','superseded_resolved',0.06,
'41000000-0000-0000-0000-000000000002'
);
RAISE EXCEPTION 'mismatched reconciliation idempotency key was accepted';
EXCEPTION WHEN unique_violation THEN NULL;
END;
BEGIN
INSERT INTO app.rupture_reconciliation_revision (
episode_id, session_id, revision_no, idempotency_key, content_hash,
supersedes_revision_id, fast_warning_observation_id, deep_observation_id,
fast_warning_id, provisional_status, deep_status, disposition, uncertainty,
model_run_id
) VALUES (
'31000000-0000-0000-0000-000000000001','61000000-0000-0000-0000-000000000001',2,
'10000000-0000-0000-0000-000000000002',repeat('7',64),
'20000000-0000-0000-0000-000000000001',
'21000000-0000-0000-0000-000000000001','21000000-0000-0000-0000-000000000004',
'smoke-fast-warning','partial','partial','confirmed',0.10,
'41000000-0000-0000-0000-000000000002'
);
RAISE EXCEPTION 'resolved reconciliation was allowed to move backward';
EXCEPTION WHEN check_violation THEN NULL;
END;
END;
$$;
-- Human RLS: owner learner, other learner, instructor cohort, wrong cohort, admin.
SELECT set_config('app.ai_context','',true);
SELECT set_config('app.current_ai_view','',true);
SELECT set_config('app.current_role','learner',true);
SELECT set_config('app.current_uid','91000000-0000-0000-0000-000000000001',true);
DO $$ BEGIN
IF (SELECT count(*) FROM app.rupture_episode) <> 1 THEN
RAISE EXCEPTION 'owner learner RLS failed';
END IF;
END $$;
SELECT set_config('app.current_uid','91000000-0000-0000-0000-000000000002',true);
DO $$ BEGIN
IF (SELECT count(*) FROM app.rupture_episode) <> 0 THEN
RAISE EXCEPTION 'cross-learner RLS leak';
END IF;
END $$;
SELECT set_config('app.current_role','instructor',true);
SELECT set_config('app.current_uid','91000000-0000-0000-0000-000000000003',true);
SELECT set_config('app.current_cohort','g3-a',true);
DO $$ BEGIN
IF (SELECT count(*) FROM app.rupture_episode) <> 1 THEN
RAISE EXCEPTION 'instructor cohort RLS failed';
END IF;
END $$;
INSERT INTO app.rupture_observation_event (
observation_id, episode_id, session_id, idempotency_key, content_hash,
event_kind, from_state, to_state, rupture_type, source_kind, perspective,
ai_view, uncertainty, evidence_turn_ids, supersedes_observation_id,
correction_reason, created_by_uid, created_by_role
) VALUES (
'21000000-0000-0000-0000-000000000005','31000000-0000-0000-0000-000000000001',
'61000000-0000-0000-0000-000000000001','11000000-0000-0000-0000-000000000005',repeat('8',64),
'human.corrected','resolved','resolved','withdrawal','human_rated','supervisor_human',
'supervisor',0.04,ARRAY['51000000-0000-0000-0000-000000000002']::uuid[],
'21000000-0000-0000-0000-000000000004','교수자 근거 확인',
'91000000-0000-0000-0000-000000000003','instructor'
);
SELECT set_config('app.current_cohort','g3-b',true);
DO $$ BEGIN
IF (SELECT count(*) FROM app.rupture_episode) <> 0 THEN
RAISE EXCEPTION 'cross-cohort instructor RLS leak';
END IF;
END $$;
SELECT set_config('app.current_role','admin',true);
SELECT set_config('app.current_uid','91000000-0000-0000-0000-000000000004',true);
SELECT set_config('app.current_cohort','',true);
DO $$ BEGIN
IF (SELECT count(*) FROM app.rupture_episode) <> 1 THEN
RAISE EXCEPTION 'admin RLS failed';
END IF;
END $$;
-- Append-only mutation guard.
-- Reset to the table owner so the row reaches the mutation trigger instead of
-- being filtered to zero rows by the intentionally absent UPDATE/DELETE policy.
RESET ROLE;
DO $$
BEGIN
BEGIN
UPDATE app.rupture_observation_event
SET uncertainty = 0.5
WHERE observation_id = '21000000-0000-0000-0000-000000000004';
RAISE EXCEPTION 'append-only observation update was accepted';
EXCEPTION WHEN SQLSTATE '55000' THEN NULL;
END;
BEGIN
DELETE FROM app.rupture_reconciliation_revision
WHERE revision_id = '20000000-0000-0000-0000-000000000001';
RAISE EXCEPTION 'append-only reconciliation delete was accepted';
EXCEPTION WHEN SQLSTATE '55000' THEN NULL;
END;
END;
$$;
ROLLBACK;
DO $$
BEGIN
IF EXISTS (
SELECT 1 FROM app.rupture_episode WHERE episode_key = 'smoke-g3-episode'
) OR EXISTS (
SELECT 1 FROM app.app_user WHERE external_id LIKE 'smoke:g3:%'
) THEN
RAISE EXCEPTION 'G3 smoke rollback left residue';
END IF;
END;
$$;
SELECT
1 AS schema_idempotent,
1 AS rls_pass,
1 AS append_only_pass,
1 AS reconciliation_pass,
1 AS idempotency_pass,
0 AS residue_count;
\echo 'G3 rupture/repair ledger live smoke: PASS'