vignette/scripts/smoke-calibration-transfer-ledger.sql
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

421 lines
19 KiB
PL/PgSQL

\set ON_ERROR_STOP on
\if :{?app_role}
\else
\set app_role vignette
\endif
BEGIN;
INSERT INTO app.app_user (
user_id, external_id, role, cohort, account_status, consent_at
) VALUES
('51000000-0000-0000-0000-000000000001', 'g5-smoke-learner', 'learner', 'g5-smoke', 'approved', now()),
('51000000-0000-0000-0000-000000000002', 'g5-smoke-teacher', 'instructor', 'g5-smoke', 'approved', now()),
('51000000-0000-0000-0000-000000000003', 'g5-smoke-other', 'learner', 'g5-other', 'approved', now());
INSERT INTO app.sessions (id, learner_id, session_no)
VALUES ('52000000-0000-0000-0000-000000000001', '51000000-0000-0000-0000-000000000001', 1);
INSERT INTO app.turns (id, session_id, seq, speaker, text_masked, actor_kind)
VALUES
('53000000-0000-0000-0000-000000000001', '52000000-0000-0000-0000-000000000001', 1, 'counselor', '[G5 masked evidence 1]', 'human_learner'),
('53000000-0000-0000-0000-000000000002', '52000000-0000-0000-0000-000000000001', 2, 'client', '[G5 masked evidence 2]', 'client_ai');
INSERT INTO app.measurement_instrument (
instrument_id, instrument_version, name_ko, instrument_kind,
construct, validation_basis, scoring_schema, metadata
) VALUES
(
'vignette-g5-calibration', '1.0.0', 'G5 역량별 자기보정',
'training_metric', 'self_calibration',
'잠긴 자기예측과 독립 관찰의 교육용 역량별 대조.',
'{"per_competency":true}'::JSONB,
'{"clinical_claim_allowed":false}'::JSONB
),
(
'vignette-g5-transfer', '1.0.0', 'G5 미지 사례 전이',
'training_metric', 'transfer',
'교육용 합성 미지 사례의 역량별 전이 관찰.',
'{"per_competency":true,"scenario_novelty":"unseen_transfer"}'::JSONB,
'{"clinical_claim_allowed":false,"data_classification":"synthetic_educational"}'::JSONB
)
ON CONFLICT (instrument_id, instrument_version) DO NOTHING;
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 (
'54000000-0000-0000-0000-000000000001',
'52000000-0000-0000-0000-000000000001',
'evaluator', 'smoke', 'g5-deterministic', 'g5-smoke', '1.0.0',
repeat('a', 64), 'vignette.calibration-transfer.v1', repeat('b', 64), 'ready'
);
SET LOCAL ROLE :"app_role";
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', '51000000-0000-0000-0000-000000000001', true);
SELECT set_config('app.current_cohort', 'g5-smoke', true);
INSERT INTO app.calibration_prediction_history (
history_id, session_id, learner_id, competency_id,
practice_block_id, scenario_variant_id, phrase_family_id, created_by_role
) VALUES (
'55000000-0000-0000-0000-000000000001',
'52000000-0000-0000-0000-000000000001',
'51000000-0000-0000-0000-000000000001',
'competency.empathic-check', 'oas-g5-block-smoke',
'variant-smoke-familiar', 'phrase-smoke-familiar', 'learner'
);
INSERT INTO app.calibration_prediction_revision (
prediction_revision_id, submission_id, content_hash, history_id,
session_id, learner_id, revision_no, predicted_success_probability,
confidence, recorded_sequence, revision_reason, instrument_id,
instrument_version, evidence_turn_ids, created_by_role
) VALUES (
'56000000-0000-0000-0000-000000000001',
'56100000-0000-0000-0000-000000000001', repeat('1', 64),
'55000000-0000-0000-0000-000000000001',
'52000000-0000-0000-0000-000000000001',
'51000000-0000-0000-0000-000000000001',
1, 0.70, 0.60, 1, '첫 자기예측을 기록했다.',
'vignette-g5-calibration', '1.0.0',
ARRAY['53000000-0000-0000-0000-000000000001']::UUID[], 'learner'
);
INSERT INTO app.calibration_prediction_revision (
prediction_revision_id, submission_id, content_hash, history_id,
session_id, learner_id, revision_no, supersedes_prediction_revision_id,
predicted_success_probability, confidence, recorded_sequence,
revision_reason, instrument_id, instrument_version, evidence_turn_ids,
created_by_role
) VALUES (
'56000000-0000-0000-0000-000000000002',
'56100000-0000-0000-0000-000000000002', repeat('2', 64),
'55000000-0000-0000-0000-000000000001',
'52000000-0000-0000-0000-000000000001',
'51000000-0000-0000-0000-000000000001',
2, '56000000-0000-0000-0000-000000000001',
0.55, 0.70, 2, '반대 근거를 보고 공개 전에 예측을 낮췄다.',
'vignette-g5-calibration', '1.0.0',
ARRAY['53000000-0000-0000-0000-000000000001']::UUID[], 'learner'
);
INSERT INTO app.calibration_prediction_lock (
lock_id, submission_id, content_hash, history_id,
prediction_revision_id, session_id, learner_id,
locked_sequence, created_by_role
) VALUES (
'57000000-0000-0000-0000-000000000001',
'57100000-0000-0000-0000-000000000001', repeat('3', 64),
'55000000-0000-0000-0000-000000000001',
'56000000-0000-0000-0000-000000000002',
'52000000-0000-0000-0000-000000000001',
'51000000-0000-0000-0000-000000000001', 2, 'learner'
);
DO $$ BEGIN
BEGIN
INSERT INTO app.calibration_prediction_revision (
prediction_revision_id, submission_id, content_hash, history_id,
session_id, learner_id, revision_no, supersedes_prediction_revision_id,
predicted_success_probability, confidence, recorded_sequence,
revision_reason, instrument_id, instrument_version, created_by_role
) VALUES (
'56000000-0000-0000-0000-000000000003',
'56100000-0000-0000-0000-000000000003', repeat('4', 64),
'55000000-0000-0000-0000-000000000001',
'52000000-0000-0000-0000-000000000001',
'51000000-0000-0000-0000-000000000001',
3, '56000000-0000-0000-0000-000000000002',
0.90, 0.90, 3, '공개 후 오염 시도',
'vignette-g5-calibration', '1.0.0', 'learner'
);
RAISE EXCEPTION 'post-lock prediction revision was accepted';
EXCEPTION WHEN object_not_in_prerequisite_state THEN NULL;
END;
END $$;
SELECT set_config('app.ai_context', '1', true);
SELECT set_config('app.current_ai_view', 'evaluator', true);
SELECT set_config('app.current_role', 'admin', true);
INSERT INTO app.calibration_performance_observation (
observation_id, submission_id, content_hash, history_id,
prediction_lock_id, session_id, learner_id, competency_id,
practice_block_id, scenario_variant_id, phrase_family_id,
status, source_kind, perspective, model_run_id,
instrument_id, instrument_version, uncertainty,
evidence_turn_ids, counterevidence, revealed_sequence
) VALUES (
'58000000-0000-0000-0000-000000000001',
'58100000-0000-0000-0000-000000000001', repeat('5', 64),
'55000000-0000-0000-0000-000000000001',
'57000000-0000-0000-0000-000000000001',
'52000000-0000-0000-0000-000000000001',
'51000000-0000-0000-0000-000000000001',
'competency.empathic-check', 'oas-g5-block-smoke',
'variant-smoke-familiar', 'phrase-smoke-familiar',
'passed', 'model_inferred', 'independent_observer',
'54000000-0000-0000-0000-000000000001',
'vignette-g5-calibration', '1.0.0', 0.20,
ARRAY['53000000-0000-0000-0000-000000000002']::UUID[],
ARRAY[]::TEXT[], 3
);
INSERT INTO app.calibration_assessment_snapshot (
assessment_snapshot_id, submission_id, content_hash, session_id,
learner_id, competency_id, snapshot_no, source_observation_ids,
assessment_payload, model_run_id, instrument_id, instrument_version,
evidence_turn_ids
) VALUES (
'59000000-0000-0000-0000-000000000001',
'59100000-0000-0000-0000-000000000001', repeat('6', 64),
'52000000-0000-0000-0000-000000000001',
'51000000-0000-0000-0000-000000000001',
'competency.empathic-check', 1,
ARRAY['58000000-0000-0000-0000-000000000001']::UUID[],
'{"competency_id":"competency.empathic-check","pair_count":1,"bias":"insufficient_evidence","improvement":"insufficient_evidence","pairs":[],"excluded_block_ids":[],"counterevidence":["below-minimum"]}'::JSONB,
'54000000-0000-0000-0000-000000000001',
'vignette-g5-calibration', '1.0.0',
ARRAY['53000000-0000-0000-0000-000000000002']::UUID[]
);
INSERT INTO app.calibration_metacognitive_prescription (
prescription_id, assessment_snapshot_id, session_id, learner_id,
competency_id, prescription_payload, model_run_id,
instrument_id, instrument_version, evidence_turn_ids
) VALUES (
'5a000000-0000-0000-0000-000000000001',
'59000000-0000-0000-0000-000000000001',
'52000000-0000-0000-0000-000000000001',
'51000000-0000-0000-0000-000000000001',
'competency.empathic-check',
'{"competency_id":"competency.empathic-check","bias":"insufficient_evidence","practice_mode":"collect_more_evidence","instruction_ko":"같은 역량의 새 장면을 세 번 수행하고 외부평가 전에 예측을 잠근다.","completion_evidence":["three_locked_predictions"]}'::JSONB,
'54000000-0000-0000-0000-000000000001',
'vignette-g5-calibration', '1.0.0',
ARRAY['53000000-0000-0000-0000-000000000002']::UUID[]
);
INSERT INTO app.calibration_transfer_suite (
transfer_suite_record_id, submission_id, content_hash, suite_key,
session_id, learner_id, training_phrase_family_ids,
model_run_id, instrument_id, instrument_version
) VALUES (
'5b000000-0000-0000-0000-000000000001',
'5b100000-0000-0000-0000-000000000001', repeat('7', 64),
'oas-g5-suite-smoke', '52000000-0000-0000-0000-000000000001',
'51000000-0000-0000-0000-000000000001',
ARRAY['phrase-smoke-familiar'],
'54000000-0000-0000-0000-000000000001',
'vignette-g5-transfer', '1.0.0'
);
INSERT INTO app.calibration_transfer_trial (
transfer_trial_record_id, transfer_suite_record_id, session_id,
learner_id, trial_key, competency_id, scenario_variant_id,
context_variant, relationship_style, difficulty_level,
expression_variant, synthetic_subgroup, scenario_family_id,
phrase_family_id, status, uncertainty, evidence_turn_ids,
counterevidence, model_run_id, instrument_id, instrument_version
) VALUES
(
'5c000000-0000-0000-0000-000000000001',
'5b000000-0000-0000-0000-000000000001',
'52000000-0000-0000-0000-000000000001',
'51000000-0000-0000-0000-000000000001',
'oas-g5-transfer-smoke-a', 'competency.empathic-check',
'variant-smoke-unseen-a', 'school', 'withdrawn', 3,
'indirect', 'synthetic-a', 'family-unseen-a', 'phrase-adapted-a',
'passed', 0.20,
ARRAY['53000000-0000-0000-0000-000000000001']::UUID[], ARRAY[]::TEXT[],
'54000000-0000-0000-0000-000000000001',
'vignette-g5-transfer', '1.0.0'
),
(
'5c000000-0000-0000-0000-000000000002',
'5b000000-0000-0000-0000-000000000001',
'52000000-0000-0000-0000-000000000001',
'51000000-0000-0000-0000-000000000001',
'oas-g5-transfer-smoke-b', 'competency.empathic-check',
'variant-smoke-unseen-b', 'home', 'ambivalent', 4,
'metaphoric', 'synthetic-b', 'family-unseen-b', 'phrase-adapted-b',
'failed', 0.30,
ARRAY['53000000-0000-0000-0000-000000000002']::UUID[],
ARRAY['client_response_not_engaged'],
'54000000-0000-0000-0000-000000000001',
'vignette-g5-transfer', '1.0.0'
);
INSERT INTO app.calibration_transfer_assessment (
transfer_assessment_id, transfer_suite_record_id, session_id,
learner_id, competency_id, source_trial_ids, assessment_payload,
evidence_turn_ids, model_run_id, instrument_id, instrument_version
) VALUES (
'5d000000-0000-0000-0000-000000000001',
'5b000000-0000-0000-0000-000000000001',
'52000000-0000-0000-0000-000000000001',
'51000000-0000-0000-0000-000000000001',
'competency.empathic-check',
ARRAY['5c000000-0000-0000-0000-000000000001','5c000000-0000-0000-0000-000000000002']::UUID[],
'{"competency_id":"competency.empathic-check","trial_count":2,"observed_trial_count":2,"success_rate":0.5,"coverage":{"contexts":2},"eligible":false,"transfer_verified":false,"blockers":["below-minimum"],"evidence_refs":[],"counterevidence":["client_response_not_engaged"]}'::JSONB,
ARRAY['53000000-0000-0000-0000-000000000001','53000000-0000-0000-0000-000000000002']::UUID[],
'54000000-0000-0000-0000-000000000001',
'vignette-g5-transfer', '1.0.0'
);
INSERT INTO app.calibration_subgroup_drift_report (
drift_report_id, transfer_suite_record_id, session_id, learner_id,
competency_id, source_trial_ids, report_payload, model_run_id,
instrument_id, instrument_version
) VALUES (
'5e000000-0000-0000-0000-000000000001',
'5b000000-0000-0000-0000-000000000001',
'52000000-0000-0000-0000-000000000001',
'51000000-0000-0000-0000-000000000001',
'competency.empathic-check',
ARRAY['5c000000-0000-0000-0000-000000000001','5c000000-0000-0000-0000-000000000002']::UUID[],
'{"competency_id":"competency.empathic-check","status":"insufficient_evidence","compared_subgroups":[],"subgroup_results":[],"threshold":0.2,"notice_ko":"교육용 합성 subgroup 관측이 부족해 드리프트를 판정하지 않는다."}'::JSONB,
'54000000-0000-0000-0000-000000000001',
'vignette-g5-transfer', '1.0.0'
);
-- Learner-self, unrelated learner, teacher cohort and cross-cohort isolation.
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', '51000000-0000-0000-0000-000000000001', true);
DO $$ BEGIN
IF (SELECT count(*) FROM app.calibration_prediction_history) <> 1 THEN
RAISE EXCEPTION 'learner self G5 RLS smoke failed';
END IF;
END $$;
SELECT set_config('app.current_uid', '51000000-0000-0000-0000-000000000003', true);
DO $$ BEGIN
IF (SELECT count(*) FROM app.calibration_prediction_history) <> 0 THEN
RAISE EXCEPTION 'unrelated learner G5 RLS leaked data';
END IF;
END $$;
SELECT set_config('app.current_role', 'instructor', true);
SELECT set_config('app.current_uid', '51000000-0000-0000-0000-000000000002', true);
SELECT set_config('app.current_cohort', 'g5-smoke', true);
DO $$ BEGIN
IF (SELECT count(*) FROM app.calibration_transfer_suite) <> 1 THEN
RAISE EXCEPTION 'teacher cohort G5 RLS smoke failed';
END IF;
END $$;
SELECT set_config('app.current_cohort', 'g5-other', true);
DO $$ BEGIN
IF (SELECT count(*) FROM app.calibration_transfer_suite) <> 0 THEN
RAISE EXCEPTION 'teacher cross-cohort G5 RLS leaked data';
END IF;
END $$;
SELECT set_config('app.current_cohort', 'g5-smoke', true);
INSERT INTO app.calibration_teacher_review_event (
review_id, submission_id, content_hash, target_kind, target_id,
session_id, learner_id, review_no, disposition, correction_payload,
review_reason, evidence_turn_ids, counterevidence,
created_by_uid, created_by_role
) VALUES (
'5f000000-0000-0000-0000-000000000001',
'5f100000-0000-0000-0000-000000000001', repeat('8', 64),
'calibration_assessment', '59000000-0000-0000-0000-000000000001',
'52000000-0000-0000-0000-000000000001',
'51000000-0000-0000-0000-000000000001',
1, 'confirmed', '{}'::JSONB, '역량별 근거와 불확실성을 확인했다.',
ARRAY['53000000-0000-0000-0000-000000000002']::UUID[], ARRAY[]::TEXT[],
'51000000-0000-0000-0000-000000000002', 'instructor'
);
DO $$ BEGIN
BEGIN
INSERT INTO app.calibration_teacher_review_event (
review_id, submission_id, content_hash, target_kind, target_id,
session_id, learner_id, review_no, supersedes_review_id,
disposition, correction_payload, review_reason,
created_by_uid, created_by_role
) VALUES (
'5f000000-0000-0000-0000-000000000002',
'5f100000-0000-0000-0000-000000000002', repeat('9', 64),
'calibration_assessment', '59000000-0000-0000-0000-000000000001',
'52000000-0000-0000-0000-000000000001',
'51000000-0000-0000-0000-000000000001',
2, '5f000000-0000-0000-0000-000000000001',
'corrected', '{"raw_transcript":"forbidden"}'::JSONB,
'금지된 본문 복제 시도',
'51000000-0000-0000-0000-000000000002', 'instructor'
);
RAISE EXCEPTION 'raw transcript payload was accepted';
EXCEPTION WHEN check_violation THEN NULL;
END;
BEGIN
INSERT INTO app.calibration_teacher_review_event (
review_id, submission_id, content_hash, target_kind, target_id,
session_id, learner_id, review_no, supersedes_review_id,
disposition, correction_payload, review_reason,
created_by_uid, created_by_role
) VALUES (
'5f000000-0000-0000-0000-000000000003',
'5f100000-0000-0000-0000-000000000003', repeat('a', 64),
'calibration_assessment', '59000000-0000-0000-0000-000000000001',
'52000000-0000-0000-0000-000000000001',
'51000000-0000-0000-0000-000000000001',
2, '5f000000-0000-0000-0000-000000000001',
'corrected', '{"total_score":0.9}'::JSONB,
'금지된 합산 점수 시도',
'51000000-0000-0000-0000-000000000002', 'instructor'
);
RAISE EXCEPTION 'aggregate score payload was accepted';
EXCEPTION WHEN check_violation THEN NULL;
END;
END $$;
RESET ROLE;
DO $$ BEGIN
BEGIN
UPDATE app.calibration_prediction_revision
SET predicted_success_probability = 1.0
WHERE prediction_revision_id = '56000000-0000-0000-0000-000000000001';
RAISE EXCEPTION 'append-only calibration revision update was accepted';
EXCEPTION WHEN object_not_in_prerequisite_state THEN NULL;
END;
BEGIN
INSERT INTO app.calibration_performance_observation (
observation_id, submission_id, content_hash, history_id,
prediction_lock_id, session_id, learner_id, competency_id,
practice_block_id, scenario_variant_id, phrase_family_id,
status, source_kind, perspective, model_run_id,
instrument_id, instrument_version, uncertainty,
evidence_turn_ids, revealed_sequence
) VALUES (
'58000000-0000-0000-0000-000000000009',
'58100000-0000-0000-0000-000000000001', repeat('f', 64),
'55000000-0000-0000-0000-000000000001',
'57000000-0000-0000-0000-000000000001',
'52000000-0000-0000-0000-000000000001',
'51000000-0000-0000-0000-000000000001',
'competency.empathic-check', 'oas-g5-block-smoke',
'variant-smoke-familiar', 'phrase-smoke-familiar',
'passed', 'model_inferred', 'independent_observer',
'54000000-0000-0000-0000-000000000001',
'vignette-g5-calibration', '1.0.0', 0.2,
ARRAY['53000000-0000-0000-0000-000000000002']::UUID[], 4
);
RAISE EXCEPTION 'changed idempotent observation was accepted';
EXCEPTION WHEN unique_violation THEN NULL;
END;
END $$;
ROLLBACK;
SELECT 'g5_calibration_transfer_rollback_residue' AS check_name,
(
(SELECT count(*) FROM app.calibration_prediction_history
WHERE history_id = '55000000-0000-0000-0000-000000000001')
+ (SELECT count(*) FROM app.calibration_performance_observation
WHERE observation_id = '58000000-0000-0000-0000-000000000001')
+ (SELECT count(*) FROM app.calibration_transfer_suite
WHERE transfer_suite_record_id = '5b000000-0000-0000-0000-000000000001')
+ (SELECT count(*) FROM app.calibration_teacher_review_event
WHERE review_id = '5f000000-0000-0000-0000-000000000001')
+ (SELECT count(*) FROM app.app_user
WHERE user_id = '51000000-0000-0000-0000-000000000001')
) AS residue_count;