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
404
scripts/smoke-deliberate-practice-ledger.sql
Normal file
404
scripts/smoke-deliberate-practice-ledger.sql
Normal file
|
|
@ -0,0 +1,404 @@
|
|||
\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
|
||||
('41000000-0000-0000-0000-000000000001', 'g4-smoke-learner', 'learner', 'g4-smoke', 'approved', now()),
|
||||
('41000000-0000-0000-0000-000000000002', 'g4-smoke-teacher', 'instructor', 'g4-smoke', 'approved', now()),
|
||||
('41000000-0000-0000-0000-000000000003', 'g4-smoke-other', 'learner', 'g4-other', 'approved', now());
|
||||
|
||||
INSERT INTO app.sessions (id, learner_id, session_no)
|
||||
VALUES ('42000000-0000-0000-0000-000000000001', '41000000-0000-0000-0000-000000000001', 1);
|
||||
INSERT INTO app.turns (id, session_id, seq, speaker, text_masked, actor_kind)
|
||||
VALUES (
|
||||
'43000000-0000-0000-0000-000000000001',
|
||||
'42000000-0000-0000-0000-000000000001',
|
||||
1,
|
||||
'client',
|
||||
'[G4 smoke masked scene]',
|
||||
'client_ai'
|
||||
);
|
||||
|
||||
SET LOCAL ROLE :"app_role";
|
||||
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);
|
||||
SELECT set_config('app.current_uid', '41000000-0000-0000-0000-000000000002', true);
|
||||
|
||||
INSERT INTO app.practice_prescription_submission (
|
||||
submission_id, session_id, learner_id, content_hash, created_by_role
|
||||
) VALUES (
|
||||
'44000000-0000-0000-0000-000000000001',
|
||||
'42000000-0000-0000-0000-000000000001',
|
||||
'41000000-0000-0000-0000-000000000001',
|
||||
repeat('a', 64),
|
||||
'agent'
|
||||
);
|
||||
INSERT INTO app.practice_coaching_card (
|
||||
coaching_card_record_id, submission_id, session_id, learner_id,
|
||||
card_key, scene_id, coach_claim, card_payload, evidence_turn_ids,
|
||||
source_refs, uncertainty, counterevidence
|
||||
) VALUES (
|
||||
'45000000-0000-0000-0000-000000000001',
|
||||
'44000000-0000-0000-0000-000000000001',
|
||||
'42000000-0000-0000-0000-000000000001',
|
||||
'41000000-0000-0000-0000-000000000001',
|
||||
'oas-g4-card-smoke',
|
||||
'scene-smoke',
|
||||
'내담자 반응을 확인하는 재연습 장면을 실행한다.',
|
||||
'{"card_id":"oas-g4-card-smoke"}',
|
||||
ARRAY['43000000-0000-0000-0000-000000000001']::UUID[],
|
||||
ARRAY['source-pack:g4-smoke:v1'],
|
||||
0.2,
|
||||
ARRAY[]::TEXT[]
|
||||
);
|
||||
INSERT INTO app.practice_prescription (
|
||||
prescription_record_id, prescription_key, submission_id,
|
||||
coaching_card_record_id, session_id, learner_id, competency_id,
|
||||
criterion_id, observable_behavior, activity_mode, scenario_variant_id,
|
||||
scenario_novelty, difficulty_level, prescription_payload,
|
||||
evidence_turn_ids, uncertainty, counterevidence
|
||||
) VALUES (
|
||||
'46000000-0000-0000-0000-000000000001',
|
||||
'oas-g4-practice-smoke',
|
||||
'44000000-0000-0000-0000-000000000001',
|
||||
'45000000-0000-0000-0000-000000000001',
|
||||
'42000000-0000-0000-0000-000000000001',
|
||||
'41000000-0000-0000-0000-000000000001',
|
||||
'competency.empathy.smoke',
|
||||
'criterion.reflect-and-check',
|
||||
'정서를 반영한 뒤 내담자에게 맞는지 확인한다.',
|
||||
'replay',
|
||||
'variant-smoke-familiar',
|
||||
'familiar',
|
||||
1,
|
||||
'{
|
||||
"schema_version":"vignette.practice-prescription.v1",
|
||||
"event_name":"practice.prescribed",
|
||||
"prescription_id":"oas-g4-practice-smoke",
|
||||
"coaching_card_id":"oas-g4-card-smoke",
|
||||
"scene_id":"scene-smoke",
|
||||
"competency_id":"competency.empathy.smoke",
|
||||
"criterion_id":"criterion.reflect-and-check",
|
||||
"observable_behavior":"정서를 반영한 뒤 내담자에게 맞는지 확인한다.",
|
||||
"activity":{
|
||||
"mode":"replay",
|
||||
"launch_intent":"practice.replay.launch",
|
||||
"scenario_variant_id":"variant-smoke-familiar",
|
||||
"scenario_novelty":"familiar",
|
||||
"difficulty_level":1,
|
||||
"pause_at_evidence_ref":"43000000-0000-0000-0000-000000000001"
|
||||
},
|
||||
"can_launch":true,
|
||||
"evidence_refs":[{
|
||||
"ref_id":"43000000-0000-0000-0000-000000000001",
|
||||
"scene_id":"scene-smoke","turn_index":1,"actor":"client","kind":"scene_context"
|
||||
}],
|
||||
"source_refs":["source-pack:g4-smoke:v1"],
|
||||
"uncertainty":0.2,
|
||||
"counterevidence":[]
|
||||
}'::JSONB,
|
||||
ARRAY['43000000-0000-0000-0000-000000000001']::UUID[],
|
||||
0.2,
|
||||
ARRAY[]::TEXT[]
|
||||
);
|
||||
INSERT INTO app.competency_graph_snapshot (
|
||||
snapshot_id, learner_id, session_id, snapshot_no, content_hash,
|
||||
source_prescription_submission_id, graph_payload, evidence_turn_ids,
|
||||
created_by_role
|
||||
) VALUES (
|
||||
'47000000-0000-0000-0000-000000000001',
|
||||
'41000000-0000-0000-0000-000000000001',
|
||||
'42000000-0000-0000-0000-000000000001',
|
||||
1,
|
||||
repeat('b', 64),
|
||||
'44000000-0000-0000-0000-000000000001',
|
||||
'{
|
||||
"schema_version":"vignette.competency-graph.v1",
|
||||
"definitions":[{
|
||||
"competency_id":"competency.empathy.smoke",
|
||||
"label_ko":"스모크 공감",
|
||||
"description":"스모크 검증을 위한 합성 공감 역량 정의다.",
|
||||
"prerequisite_ids":[]
|
||||
}],
|
||||
"states":[{
|
||||
"competency_id":"competency.empathy.smoke",
|
||||
"band":"unassessed","forgetting_risk":0.7,"uncertainty":1.0,
|
||||
"attempt_count":0,"familiar_demonstrations":0,
|
||||
"unseen_transfer_demonstrations":0,"highest_familiar_difficulty":0,
|
||||
"evidence_refs":[],"counterevidence":[]
|
||||
}]
|
||||
}'::JSONB,
|
||||
ARRAY['43000000-0000-0000-0000-000000000001']::UUID[],
|
||||
'agent'
|
||||
);
|
||||
INSERT INTO app.practice_curriculum_decision_event (
|
||||
decision_id, source_snapshot_id, selected_prescription_record_id,
|
||||
learner_id, session_id, content_hash, decision_payload, created_by_role
|
||||
) VALUES (
|
||||
'48000000-0000-0000-0000-000000000001',
|
||||
'47000000-0000-0000-0000-000000000001',
|
||||
'46000000-0000-0000-0000-000000000001',
|
||||
'41000000-0000-0000-0000-000000000001',
|
||||
'42000000-0000-0000-0000-000000000001',
|
||||
repeat('c', 64),
|
||||
'{
|
||||
"schema_version":"vignette.curriculum-decision.v1",
|
||||
"selected_prescription_id":"oas-g4-practice-smoke",
|
||||
"competency_id":"competency.empathy.smoke",
|
||||
"competency_band":"unassessed","forgetting_risk":0.7,
|
||||
"mode":"replay",
|
||||
"selection_basis":["weakest_available_band:unassessed","forgetting_risk:0.700"],
|
||||
"deferred_prescription_ids":[],"blocked_prescription_reasons":[]
|
||||
}'::JSONB,
|
||||
'agent'
|
||||
);
|
||||
|
||||
-- A client-model database context must not inherit the human admin GUC or see
|
||||
-- evaluator-owned prescription/curriculum state.
|
||||
SELECT set_config('app.current_ai_view', 'client', true);
|
||||
DO $$ BEGIN
|
||||
IF (
|
||||
(SELECT count(*) FROM app.practice_prescription_submission)
|
||||
+ (SELECT count(*) FROM app.practice_coaching_card)
|
||||
+ (SELECT count(*) FROM app.practice_prescription)
|
||||
+ (SELECT count(*) FROM app.competency_graph_snapshot)
|
||||
+ (SELECT count(*) FROM app.practice_curriculum_decision_event)
|
||||
) <> 0 THEN
|
||||
RAISE EXCEPTION 'client AI view leaked evaluator practice data';
|
||||
END IF;
|
||||
END $$;
|
||||
SELECT set_config('app.current_ai_view', 'evaluator', true);
|
||||
|
||||
INSERT INTO app.practice_episode_submission (
|
||||
episode_submission_id, episode_key, prescription_record_id,
|
||||
session_id, learner_id, content_hash, assessment_payload, progress,
|
||||
mastery_allowed, mastery_blockers, uncertainty, evidence_turn_ids,
|
||||
counterevidence, created_by_role
|
||||
) VALUES (
|
||||
'49000000-0000-0000-0000-000000000001',
|
||||
'oas-g4-episode-smoke-valid',
|
||||
'46000000-0000-0000-0000-000000000001',
|
||||
'42000000-0000-0000-0000-000000000001',
|
||||
'41000000-0000-0000-0000-000000000001',
|
||||
repeat('d', 64),
|
||||
'{
|
||||
"progress":"mastered","mastery_allowed":true,"competency_id":"competency.empathy.smoke",
|
||||
"attempts":[
|
||||
{"outcome":"needs_retry","scenario_novelty":"familiar","scenario_variant_id":"variant-smoke-familiar","utterance_template_id":"template-baseline"},
|
||||
{"outcome":"passed","scenario_novelty":"familiar","scenario_variant_id":"variant-smoke-familiar","utterance_template_id":"template-familiar"},
|
||||
{"outcome":"passed","scenario_novelty":"unseen_transfer","scenario_variant_id":"variant-smoke-unseen","utterance_template_id":"template-adapted"}
|
||||
]
|
||||
}'::JSONB,
|
||||
'mastered',
|
||||
TRUE,
|
||||
ARRAY[]::TEXT[],
|
||||
0.2,
|
||||
ARRAY['43000000-0000-0000-0000-000000000001']::UUID[],
|
||||
ARRAY[]::TEXT[],
|
||||
'learner'
|
||||
);
|
||||
INSERT INTO app.practice_attempt_evidence (
|
||||
attempt_record_id, attempt_key, episode_submission_id, session_id, learner_id,
|
||||
sequence_no, scenario_variant_id, scenario_novelty, difficulty_level,
|
||||
criterion_status, client_response, outcome, utterance_template_id,
|
||||
uncertainty, evidence_turn_ids, counterevidence, attempt_payload
|
||||
) VALUES
|
||||
(
|
||||
'4a000000-0000-0000-0000-000000000001','oas-g4-attempt-smoke-1',
|
||||
'49000000-0000-0000-0000-000000000001','42000000-0000-0000-0000-000000000001',
|
||||
'41000000-0000-0000-0000-000000000001',1,'variant-smoke-familiar','familiar',1,
|
||||
'not_observed','rejecting','needs_retry','template-baseline',0.2,
|
||||
ARRAY['43000000-0000-0000-0000-000000000001']::UUID[],ARRAY['baseline_miss'],
|
||||
'{"kind":"baseline"}'::JSONB
|
||||
),
|
||||
(
|
||||
'4a000000-0000-0000-0000-000000000002','oas-g4-attempt-smoke-2',
|
||||
'49000000-0000-0000-0000-000000000001','42000000-0000-0000-0000-000000000001',
|
||||
'41000000-0000-0000-0000-000000000001',2,'variant-smoke-familiar','familiar',1,
|
||||
'observed','engaged','passed','template-familiar',0.2,
|
||||
ARRAY['43000000-0000-0000-0000-000000000001']::UUID[],ARRAY[]::TEXT[],
|
||||
'{"kind":"familiar-pass"}'::JSONB
|
||||
),
|
||||
(
|
||||
'4a000000-0000-0000-0000-000000000003','oas-g4-attempt-smoke-3',
|
||||
'49000000-0000-0000-0000-000000000001','42000000-0000-0000-0000-000000000001',
|
||||
'41000000-0000-0000-0000-000000000001',3,'variant-smoke-unseen','unseen_transfer',4,
|
||||
'observed','explicit_alignment','passed','template-adapted',0.2,
|
||||
ARRAY['43000000-0000-0000-0000-000000000001']::UUID[],ARRAY[]::TEXT[],
|
||||
'{"kind":"unseen-pass"}'::JSONB
|
||||
);
|
||||
|
||||
INSERT INTO app.competency_graph_snapshot (
|
||||
snapshot_id, learner_id, session_id, snapshot_no, content_hash,
|
||||
supersedes_snapshot_id, source_episode_submission_id, graph_payload,
|
||||
evidence_turn_ids, created_by_role
|
||||
) VALUES (
|
||||
'4b000000-0000-0000-0000-000000000001',
|
||||
'41000000-0000-0000-0000-000000000001',
|
||||
'42000000-0000-0000-0000-000000000001',
|
||||
2,
|
||||
repeat('e', 64),
|
||||
'47000000-0000-0000-0000-000000000001',
|
||||
'49000000-0000-0000-0000-000000000001',
|
||||
'{
|
||||
"schema_version":"vignette.competency-graph.v1",
|
||||
"definitions":[{
|
||||
"competency_id":"competency.empathy.smoke","label_ko":"스모크 공감",
|
||||
"description":"스모크 검증을 위한 합성 공감 역량 정의다.","prerequisite_ids":[]
|
||||
}],
|
||||
"states":[{
|
||||
"competency_id":"competency.empathy.smoke","band":"transfer_verified",
|
||||
"forgetting_risk":0.45,"uncertainty":0.2,"attempt_count":3,
|
||||
"familiar_demonstrations":1,"unseen_transfer_demonstrations":1,
|
||||
"highest_familiar_difficulty":1,"evidence_refs":[],"counterevidence":[]
|
||||
}]
|
||||
}'::JSONB,
|
||||
ARRAY['43000000-0000-0000-0000-000000000001']::UUID[],
|
||||
'learner'
|
||||
);
|
||||
INSERT INTO app.practice_curriculum_decision_event (
|
||||
decision_id, source_snapshot_id, selected_prescription_record_id,
|
||||
learner_id, session_id, content_hash, decision_payload, created_by_role
|
||||
) VALUES (
|
||||
'4b000000-0000-0000-0000-000000000002',
|
||||
'4b000000-0000-0000-0000-000000000001',
|
||||
'46000000-0000-0000-0000-000000000001',
|
||||
'41000000-0000-0000-0000-000000000001',
|
||||
'42000000-0000-0000-0000-000000000001',
|
||||
repeat('f', 64),
|
||||
'{"selected_prescription_id":"oas-g4-practice-smoke"}'::JSONB,
|
||||
'learner'
|
||||
);
|
||||
|
||||
-- Learner-self, teacher cohort and unrelated-user 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', '41000000-0000-0000-0000-000000000001', true);
|
||||
DO $$ BEGIN
|
||||
IF (SELECT count(*) FROM app.practice_prescription) <> 1 THEN
|
||||
RAISE EXCEPTION 'learner self RLS smoke failed';
|
||||
END IF;
|
||||
END $$;
|
||||
SELECT set_config('app.current_uid', '41000000-0000-0000-0000-000000000003', true);
|
||||
DO $$ BEGIN
|
||||
IF (SELECT count(*) FROM app.practice_prescription) <> 0 THEN
|
||||
RAISE EXCEPTION 'unrelated learner RLS leaked practice data';
|
||||
END IF;
|
||||
END $$;
|
||||
SELECT set_config('app.current_role', 'instructor', true);
|
||||
SELECT set_config('app.current_uid', '41000000-0000-0000-0000-000000000002', true);
|
||||
SELECT set_config('app.current_cohort', 'g4-smoke', true);
|
||||
DO $$ BEGIN
|
||||
IF (SELECT count(*) FROM app.practice_prescription) <> 1 THEN
|
||||
RAISE EXCEPTION 'teacher cohort RLS smoke failed';
|
||||
END IF;
|
||||
END $$;
|
||||
SELECT set_config('app.current_cohort', 'g4-other', true);
|
||||
DO $$ BEGIN
|
||||
IF (SELECT count(*) FROM app.practice_prescription) <> 0 THEN
|
||||
RAISE EXCEPTION 'teacher cross-cohort RLS leaked practice data';
|
||||
END IF;
|
||||
END $$;
|
||||
|
||||
-- Teacher correction remains append-only and cannot mutate the original attempt.
|
||||
SELECT set_config('app.current_cohort', 'g4-smoke', true);
|
||||
INSERT INTO app.practice_teacher_correction (
|
||||
correction_id, submission_id, content_hash, attempt_record_id,
|
||||
episode_submission_id, session_id, learner_id, correction_no,
|
||||
corrected_outcome, correction_reason, evidence_turn_ids, counterevidence,
|
||||
created_by_uid, created_by_role
|
||||
) VALUES (
|
||||
'4c000000-0000-0000-0000-000000000001',
|
||||
'4c000000-0000-0000-0000-000000000002',
|
||||
repeat('1', 64),
|
||||
'4a000000-0000-0000-0000-000000000001',
|
||||
'49000000-0000-0000-0000-000000000001',
|
||||
'42000000-0000-0000-0000-000000000001',
|
||||
'41000000-0000-0000-0000-000000000001',
|
||||
1,
|
||||
'needs_retry',
|
||||
'내담자 후속 반응을 근거로 재시도가 필요하다고 확인했다.',
|
||||
ARRAY['43000000-0000-0000-0000-000000000001']::UUID[],
|
||||
ARRAY['client_response_not_engaged'],
|
||||
'41000000-0000-0000-0000-000000000002',
|
||||
'instructor'
|
||||
);
|
||||
|
||||
RESET ROLE;
|
||||
|
||||
-- Same submission id with changed content is rejected.
|
||||
DO $$ BEGIN
|
||||
BEGIN
|
||||
INSERT INTO app.practice_prescription_submission (
|
||||
submission_id, session_id, learner_id, content_hash, created_by_role
|
||||
) VALUES (
|
||||
'44000000-0000-0000-0000-000000000001',
|
||||
'42000000-0000-0000-0000-000000000001',
|
||||
'41000000-0000-0000-0000-000000000001', repeat('9', 64), 'agent'
|
||||
);
|
||||
RAISE EXCEPTION 'changed idempotent submission was accepted';
|
||||
EXCEPTION WHEN unique_violation THEN NULL;
|
||||
END;
|
||||
END $$;
|
||||
|
||||
-- Memorized phrase cannot be promoted to mastery.
|
||||
DO $$ BEGIN
|
||||
BEGIN
|
||||
INSERT INTO app.practice_episode_submission (
|
||||
episode_submission_id, episode_key, prescription_record_id,
|
||||
session_id, learner_id, content_hash, assessment_payload, progress,
|
||||
mastery_allowed, mastery_blockers, uncertainty, evidence_turn_ids,
|
||||
counterevidence, created_by_role
|
||||
) VALUES (
|
||||
'49000000-0000-0000-0000-000000000009',
|
||||
'oas-g4-episode-smoke-hack',
|
||||
'46000000-0000-0000-0000-000000000001',
|
||||
'42000000-0000-0000-0000-000000000001',
|
||||
'41000000-0000-0000-0000-000000000001',
|
||||
repeat('8', 64),
|
||||
'{
|
||||
"progress":"mastered","mastery_allowed":true,"competency_id":"competency.empathy.smoke",
|
||||
"attempts":[
|
||||
{"outcome":"passed","scenario_novelty":"familiar","scenario_variant_id":"v1","utterance_template_id":"memorized"},
|
||||
{"outcome":"passed","scenario_novelty":"unseen_transfer","scenario_variant_id":"v2","utterance_template_id":"memorized"}
|
||||
]
|
||||
}'::JSONB,
|
||||
'mastered', TRUE, ARRAY[]::TEXT[], 0.2,
|
||||
ARRAY['43000000-0000-0000-0000-000000000001']::UUID[], ARRAY[]::TEXT[], 'learner'
|
||||
);
|
||||
RAISE EXCEPTION 'memorized phrase mastery was accepted';
|
||||
EXCEPTION WHEN check_violation THEN NULL;
|
||||
END;
|
||||
END $$;
|
||||
|
||||
DO $$ BEGIN
|
||||
BEGIN
|
||||
UPDATE app.practice_attempt_evidence
|
||||
SET outcome = 'passed'
|
||||
WHERE attempt_record_id = '4a000000-0000-0000-0000-000000000001';
|
||||
RAISE EXCEPTION 'append-only practice attempt update was accepted';
|
||||
EXCEPTION WHEN object_not_in_prerequisite_state THEN NULL;
|
||||
END;
|
||||
END $$;
|
||||
|
||||
ROLLBACK;
|
||||
|
||||
SELECT 'g4_smoke_rollback_residue' AS check_name,
|
||||
(
|
||||
(SELECT count(*) FROM app.practice_prescription_submission
|
||||
WHERE submission_id = '44000000-0000-0000-0000-000000000001')
|
||||
+ (SELECT count(*) FROM app.practice_episode_submission
|
||||
WHERE episode_submission_id = '49000000-0000-0000-0000-000000000001')
|
||||
+ (SELECT count(*) FROM app.practice_teacher_correction
|
||||
WHERE correction_id = '4c000000-0000-0000-0000-000000000001')
|
||||
+ (SELECT count(*) FROM app.app_user
|
||||
WHERE user_id = '41000000-0000-0000-0000-000000000001')
|
||||
) AS residue_count;
|
||||
Loading…
Add table
Add a link
Reference in a new issue