\set ON_ERROR_STOP on \if :{?app_role} \else \set app_role vignette_app \endif -- Outcome & Alliance OS G0 live DB proof. -- Owner/superuser connection에서 실행하며 모든 fixture는 마지막에 rollback한다. BEGIN; INSERT INTO app.app_user ( user_id, external_id, email, display_name, role, cohort, consent_at ) VALUES ( 'a0000000-0000-4000-8000-000000000001', 'dev:measurement-ledger-smoke', 'measurement-ledger-smoke@example.invalid', '측정 원장 스모크', 'learner', 'measurement-smoke', now() ) ON CONFLICT (user_id) DO NOTHING; INSERT INTO app.sessions ( id, learner_id, session_no, theory_mode ) VALUES ( 'a0000000-0000-4000-8000-000000000002', 'a0000000-0000-4000-8000-000000000001', 1, 'integrative' ) ON CONFLICT (id) 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 ( 'a0000000-0000-4000-8000-000000000010', 'a0000000-0000-4000-8000-000000000002', 'evaluator', 'contract-smoke', 'deterministic', 'measurement-smoke-evaluator', '1.0.0', repeat('a', 64), 'measurement-event.v1', repeat('b', 64), 'ready' ), ( 'a0000000-0000-4000-8000-000000000011', 'a0000000-0000-4000-8000-000000000002', 'client', 'contract-smoke', 'deterministic', 'measurement-smoke-client', '1.0.0', repeat('c', 64), 'measurement-event.v1', repeat('d', 64), 'ready' ); INSERT INTO app.measurement_event ( measurement_id, session_id, construct, dimension, perspective, source_kind, instrument_id, instrument_version, value, scale_min, scale_max, model_run_id, visible_to ) VALUES ( 'a0000000-0000-4000-8000-000000000020', 'a0000000-0000-4000-8000-000000000002', 'working_alliance', 'learner_goal', 'learner_self_report', 'learner_reported', 'phase3-prepost', 'design-1', 0.4, 0, 1, NULL, ARRAY['counselor','evaluator']::text[] ), ( 'a0000000-0000-4000-8000-000000000021', 'a0000000-0000-4000-8000-000000000002', 'working_alliance', 'observer_goal', 'independent_observer', 'model_inferred', 'vignette-fast-evaluator', 'legacy-1', 0.3, 0, 1, 'a0000000-0000-4000-8000-000000000010', ARRAY['evaluator']::text[] ), ( 'a0000000-0000-4000-8000-000000000022', 'a0000000-0000-4000-8000-000000000002', 'working_alliance', 'client_goal', 'client_agent_report', 'agent_reported', 'vignette-fast-evaluator', 'legacy-1', 0.2, 0, 1, 'a0000000-0000-4000-8000-000000000011', ARRAY['client']::text[] ); SET LOCAL ROLE :"app_role"; SELECT set_config('app.current_uid', 'a0000000-0000-4000-8000-000000000001', true); SELECT set_config('app.current_role', 'learner', true); SELECT set_config('app.ai_context', '0', true); SELECT set_config('app.current_ai_view', '', true); DO $$ DECLARE actual integer; BEGIN SELECT count(*) INTO actual FROM app.measurement_event WHERE session_id = 'a0000000-0000-4000-8000-000000000002'; IF actual <> 1 THEN RAISE EXCEPTION 'learner RLS leak: expected 1 counselor-visible event, got %', actual; END IF; IF EXISTS ( SELECT 1 FROM app.measurement_event WHERE measurement_id IN ( 'a0000000-0000-4000-8000-000000000021', 'a0000000-0000-4000-8000-000000000022' ) ) THEN RAISE EXCEPTION 'learner RLS exposed evaluator/client-agent measurement'; END IF; END; $$; SELECT set_config('app.ai_context', '1', true); SELECT set_config('app.current_ai_view', 'client', true); DO $$ DECLARE actual integer; BEGIN SELECT count(*) INTO actual FROM app.measurement_event WHERE session_id = 'a0000000-0000-4000-8000-000000000002'; IF actual <> 1 THEN RAISE EXCEPTION 'client AI view leak: expected 1 client event, got %', actual; END IF; IF EXISTS ( SELECT 1 FROM app.measurement_event WHERE measurement_id IN ( 'a0000000-0000-4000-8000-000000000020', 'a0000000-0000-4000-8000-000000000021' ) ) THEN RAISE EXCEPTION 'client AI view exposed counselor/evaluator measurement'; END IF; END; $$; SELECT set_config('app.current_ai_view', 'evaluator', true); DO $$ DECLARE actual integer; BEGIN SELECT count(*) INTO actual FROM app.measurement_event WHERE session_id = 'a0000000-0000-4000-8000-000000000002'; IF actual <> 2 THEN RAISE EXCEPTION 'evaluator AI view mismatch: expected 2 events, got %', actual; END IF; IF EXISTS ( SELECT 1 FROM app.measurement_event WHERE measurement_id = 'a0000000-0000-4000-8000-000000000022' ) THEN RAISE EXCEPTION 'evaluator AI view exposed client-agent-only measurement'; END IF; END; $$; SELECT set_config('app.ai_context', '0', true); SELECT set_config('app.current_ai_view', '', true); RESET ROLE; DO $$ BEGIN BEGIN UPDATE app.measurement_event SET value = 0.9 WHERE measurement_id = 'a0000000-0000-4000-8000-000000000020'; RAISE EXCEPTION 'measurement_event UPDATE unexpectedly succeeded'; EXCEPTION WHEN SQLSTATE '55000' THEN NULL; END; BEGIN DELETE FROM audit.model_run WHERE model_run_id = 'a0000000-0000-4000-8000-000000000010'; RAISE EXCEPTION 'model_run DELETE unexpectedly succeeded'; EXCEPTION WHEN SQLSTATE '55000' THEN NULL; END; END; $$; SELECT json_build_object( 'ok', true, 'learner_visible', 1, 'client_visible', 1, 'evaluator_visible', 2, 'cross_view_leaks', 0, 'append_only_guards', 2 ) AS measurement_ledger_smoke; ROLLBACK;