@rulemetric/local 0.15.0 → 0.15.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/meta.json +2 -2
- package/dist/server.mjs +344 -339
- package/dist/supabase/migrations/00231_scoped_live_confirmations.sql +47 -0
- package/dist/supabase/migrations/00232_scoped_live_confirmation_disposition.sql +13 -0
- package/dist/supabase/migrations/00233_performance_study_integrity.sql +33 -0
- package/dist/supabase/migrations/00234_scoped_live_confirmation_rls.sql +20 -0
- package/dist/supabase/migrations/00235_scoped_live_observed_environment.sql +4 -0
- package/dist/supabase/migrations/00236_scoped_live_followup_receipts.sql +3 -0
- package/dist/supabase/migrations/00237_scoped_live_frozen_evidence.sql +27 -0
- package/dist/web/assets/{index-CixmvJJZ.js → index-DDP0GLLt.js} +46 -46
- package/dist/web/index.html +1 -1
- package/package.json +2 -2
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
-- P4/P5: pre-treatment organic task eligibility and the auditable link from a
|
|
2
|
+
-- supported frozen study to a later application. Legacy instruction arms are
|
|
3
|
+
-- intentionally not backfilled: they lack the task/configuration contract.
|
|
4
|
+
create table public.scoped_live_confirmations (
|
|
5
|
+
id uuid primary key default gen_random_uuid(),
|
|
6
|
+
user_id uuid not null references public.profiles(id) on delete cascade,
|
|
7
|
+
experiment_id uuid not null references public.instruction_experiments(id) on delete cascade,
|
|
8
|
+
source_study_id uuid not null references public.performance_studies(id) on delete restrict,
|
|
9
|
+
protocol jsonb not null,
|
|
10
|
+
protocol_hash text not null,
|
|
11
|
+
salt text not null,
|
|
12
|
+
status text not null default 'running' check (status in ('running','stopped')),
|
|
13
|
+
decision text check (decision is null or decision in ('improved','harmful','inconclusive')),
|
|
14
|
+
decision_detail jsonb,
|
|
15
|
+
started_at timestamptz not null default now(),
|
|
16
|
+
ended_at timestamptz,
|
|
17
|
+
unique (experiment_id)
|
|
18
|
+
);
|
|
19
|
+
create index scoped_live_confirmations_user_status on public.scoped_live_confirmations(user_id,status);
|
|
20
|
+
|
|
21
|
+
create table public.scoped_live_confirmation_assignments (
|
|
22
|
+
id uuid primary key default gen_random_uuid(),
|
|
23
|
+
confirmation_id uuid not null references public.scoped_live_confirmations(id) on delete cascade,
|
|
24
|
+
user_id uuid not null references public.profiles(id) on delete cascade,
|
|
25
|
+
task_id text not null,
|
|
26
|
+
external_session_id text not null,
|
|
27
|
+
arm text not null check (arm in ('treated','control')),
|
|
28
|
+
model text not null,
|
|
29
|
+
configuration_hash text not null,
|
|
30
|
+
assigned_at timestamptz not null default now(),
|
|
31
|
+
delivery_hash text,
|
|
32
|
+
delivered_at timestamptz,
|
|
33
|
+
outcome text check (outcome is null or outcome in ('completed','failed','unknown')),
|
|
34
|
+
outcome_detail jsonb,
|
|
35
|
+
outcome_at timestamptz,
|
|
36
|
+
unique (confirmation_id,task_id),
|
|
37
|
+
unique (confirmation_id,external_session_id)
|
|
38
|
+
);
|
|
39
|
+
create index scoped_live_confirmation_assignments_confirmation_arm
|
|
40
|
+
on public.scoped_live_confirmation_assignments(confirmation_id,arm);
|
|
41
|
+
|
|
42
|
+
alter table public.recommendation_applications
|
|
43
|
+
add column source_study_id uuid references public.performance_studies(id) on delete restrict,
|
|
44
|
+
add column scoped_confirmation_id uuid references public.scoped_live_confirmations(id) on delete restrict,
|
|
45
|
+
add column scoped_confirmation_assignment_id uuid references public.scoped_live_confirmation_assignments(id) on delete restrict;
|
|
46
|
+
create index idx_recommendation_applications_scoped_confirmation
|
|
47
|
+
on public.recommendation_applications(scoped_confirmation_id, scoped_confirmation_assignment_id);
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
-- A legacy project-wide experiment is closed when a task-level P4 protocol
|
|
2
|
+
-- takes over. Its old arms remain historical evidence; this reason prevents
|
|
3
|
+
-- the ordinary renderer from continuing to inject alongside scoped assignment.
|
|
4
|
+
alter table public.instruction_experiments
|
|
5
|
+
drop constraint instruction_experiments_stopped_reason_check;
|
|
6
|
+
|
|
7
|
+
alter table public.instruction_experiments
|
|
8
|
+
add constraint instruction_experiments_stopped_reason_check
|
|
9
|
+
check (stopped_reason in (
|
|
10
|
+
'graduated', 'trial_neutral', 'trial_inconclusive', 'content_changed',
|
|
11
|
+
'inactive_project', 'invalid_template', 'delivery_refused',
|
|
12
|
+
'scoped_live_confirmation'
|
|
13
|
+
));
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
-- Close insert-time gaps left by 00230. A CHECK whose expression evaluates to
|
|
2
|
+
-- NULL accepts the row, so the version key must be required explicitly.
|
|
3
|
+
alter table public.performance_studies
|
|
4
|
+
drop constraint performance_study_version;
|
|
5
|
+
alter table public.performance_studies
|
|
6
|
+
add constraint performance_study_version
|
|
7
|
+
check (protocol ? 'version' and protocol->>'version' = '1');
|
|
8
|
+
alter table public.performance_studies
|
|
9
|
+
add constraint performance_study_finalization_pair
|
|
10
|
+
check ((result is null) = (finalized_at is null));
|
|
11
|
+
|
|
12
|
+
create or replace function public.preserve_study_protocol() returns trigger
|
|
13
|
+
language plpgsql security definer set search_path = public, pg_temp as $$
|
|
14
|
+
begin
|
|
15
|
+
if tg_op = 'INSERT' then
|
|
16
|
+
if new.result is not null or new.finalized_at is not null then
|
|
17
|
+
raise exception 'Studies must be frozen before a result can be finalized';
|
|
18
|
+
end if;
|
|
19
|
+
return new;
|
|
20
|
+
end if;
|
|
21
|
+
if (to_jsonb(new)-'result'-'finalized_at') is distinct from (to_jsonb(old)-'result'-'finalized_at') or old.result is not null then
|
|
22
|
+
raise exception 'Frozen study protocols and final results are immutable; create a new revision';
|
|
23
|
+
end if;
|
|
24
|
+
if new.result is null or new.finalized_at is null then
|
|
25
|
+
raise exception 'Final result and timestamp are required together';
|
|
26
|
+
end if;
|
|
27
|
+
return new;
|
|
28
|
+
end $$;
|
|
29
|
+
|
|
30
|
+
drop trigger performance_study_immutable on public.performance_studies;
|
|
31
|
+
create trigger performance_study_immutable
|
|
32
|
+
before insert or update on public.performance_studies
|
|
33
|
+
for each row execute function public.preserve_study_protocol();
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
-- Scoped study rows contain experiment arms and outcomes. Browser clients may
|
|
2
|
+
-- read their own evidence; all writes remain behind the authenticated API.
|
|
3
|
+
alter table public.scoped_live_confirmations enable row level security;
|
|
4
|
+
alter table public.scoped_live_confirmation_assignments enable row level security;
|
|
5
|
+
|
|
6
|
+
create policy scoped_live_confirmations_select_own
|
|
7
|
+
on public.scoped_live_confirmations for select
|
|
8
|
+
using (auth.uid() = user_id);
|
|
9
|
+
|
|
10
|
+
create policy scoped_live_confirmation_assignments_select_own
|
|
11
|
+
on public.scoped_live_confirmation_assignments for select
|
|
12
|
+
using (auth.uid() = user_id);
|
|
13
|
+
|
|
14
|
+
alter table public.scoped_live_confirmation_assignments
|
|
15
|
+
add constraint scoped_live_delivery_pair
|
|
16
|
+
check ((delivery_hash is null) = (delivered_at is null)),
|
|
17
|
+
add constraint scoped_live_outcome_pair
|
|
18
|
+
check ((outcome is null) = (outcome_at is null)),
|
|
19
|
+
add constraint scoped_live_outcome_requires_delivery
|
|
20
|
+
check (outcome_at is null or delivered_at is not null);
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
-- API-only writers still need immutable protocol and once-only evidence guards.
|
|
2
|
+
create or replace function public.guard_scoped_live_confirmation() returns trigger
|
|
3
|
+
language plpgsql set search_path = public as $$
|
|
4
|
+
begin
|
|
5
|
+
if row(new.id,new.user_id,new.experiment_id,new.source_study_id,new.protocol,new.protocol_hash,new.salt,new.started_at)
|
|
6
|
+
is distinct from row(old.id,old.user_id,old.experiment_id,old.source_study_id,old.protocol,old.protocol_hash,old.salt,old.started_at)
|
|
7
|
+
or old.status = 'stopped' then
|
|
8
|
+
raise exception 'Scoped confirmation protocol and final decision are immutable';
|
|
9
|
+
end if;
|
|
10
|
+
return new;
|
|
11
|
+
end $$;
|
|
12
|
+
create trigger scoped_live_confirmation_frozen before update on public.scoped_live_confirmations
|
|
13
|
+
for each row execute function public.guard_scoped_live_confirmation();
|
|
14
|
+
|
|
15
|
+
create or replace function public.guard_scoped_live_assignment() returns trigger
|
|
16
|
+
language plpgsql set search_path = public as $$
|
|
17
|
+
begin
|
|
18
|
+
if row(new.id,new.confirmation_id,new.user_id,new.task_id,new.external_session_id,new.arm,new.phase,new.model,new.configuration_hash,new.enrollment_evidence,new.assigned_at)
|
|
19
|
+
is distinct from row(old.id,old.confirmation_id,old.user_id,old.task_id,old.external_session_id,old.arm,old.phase,old.model,old.configuration_hash,old.enrollment_evidence,old.assigned_at)
|
|
20
|
+
or (old.delivered_at is not null and row(new.delivered_at,new.delivery_hash) is distinct from row(old.delivered_at,old.delivery_hash))
|
|
21
|
+
or (old.outcome_at is not null and row(new.outcome_at,new.outcome,new.outcome_detail) is distinct from row(old.outcome_at,old.outcome,old.outcome_detail)) then
|
|
22
|
+
raise exception 'Scoped assignment identity, delivery and outcome evidence are immutable';
|
|
23
|
+
end if;
|
|
24
|
+
return new;
|
|
25
|
+
end $$;
|
|
26
|
+
create trigger scoped_live_assignment_frozen before update on public.scoped_live_confirmation_assignments
|
|
27
|
+
for each row execute function public.guard_scoped_live_assignment();
|