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
360
scripts/smoke-rupture-repair-ledger.sql
Normal file
360
scripts/smoke-rupture-repair-ledger.sql
Normal 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'
|
||||
Loading…
Add table
Add a link
Reference in a new issue