@rulemetric/local 0.18.1 → 0.19.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 +579 -527
- package/dist/supabase/migrations/00241_canonical_experiments.sql +147 -0
- package/dist/supabase/migrations/00242_experiment_results_and_applications.sql +86 -0
- package/dist/supabase/migrations/00243_experiment_receipt_integrity.sql +61 -0
- package/dist/supabase/migrations/00244_experiment_history_completion.sql +32 -0
- package/dist/supabase/migrations/00245_experiment_fixture_guards.sql +20 -0
- package/dist/supabase/migrations/00246_experiment_target_retention.sql +5 -0
- package/dist/supabase/migrations/00247_historical_experiment_receipts.sql +89 -0
- package/dist/supabase/migrations/00248_bounded_study_repairs.sql +5 -0
- package/dist/supabase/migrations/00249_archive_unsupported_study_protocols.sql +17 -0
- package/dist/web/assets/{docs-BCYTAB4p.js → docs-DnOWHYi8.js} +23 -31
- package/dist/web/assets/{index-7QvMyVSq.js → index-B8q1tlwD.js} +55 -57
- package/dist/web/assets/index-CQ6mUBU7.css +1 -0
- package/dist/web/index.html +3 -3
- package/package.json +2 -2
- package/dist/web/assets/index-C0Y0tfIf.css +0 -1
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
-- Replace experiment identities; preserve original IDs and immutable evidence.
|
|
2
|
+
-- This migration must run with experiment workers quiesced for the cutover.
|
|
3
|
+
BEGIN;
|
|
4
|
+
-- Old workers cannot write compatibility projections after cutover. Refuse
|
|
5
|
+
-- deployment while any experimental worker holds a lease; never kill its calls.
|
|
6
|
+
DO $$ BEGIN
|
|
7
|
+
IF EXISTS(SELECT 1 FROM eval_jobs WHERE task_kind IN ('process_eval','process_suggestion_eval','process_suggestion_discovery') AND status IN ('claimed','running'))
|
|
8
|
+
OR EXISTS(SELECT 1 FROM launch_jobs WHERE status IN ('claimed','running') AND (config ? 'scopedConfirmation' OR payload->'config' ? 'scopedConfirmation'))
|
|
9
|
+
OR EXISTS(SELECT 1 FROM agent_jobs WHERE task_kind IN ('harbor_ab','memory_ab','cron_instruction_evolution','cron_instruction_training','cron_eval_autorun') AND status IN ('claimed','running')) THEN
|
|
10
|
+
RAISE EXCEPTION 'Drain experiment workers before canonical experiment migration';
|
|
11
|
+
END IF;
|
|
12
|
+
END $$;
|
|
13
|
+
ALTER TABLE performance_studies RENAME TO experiments;
|
|
14
|
+
DROP TRIGGER performance_study_immutable ON experiments;
|
|
15
|
+
ALTER TABLE experiments RENAME COLUMN result TO analysis;
|
|
16
|
+
ALTER TABLE experiments ALTER COLUMN eval_target_id DROP NOT NULL;
|
|
17
|
+
ALTER TABLE experiments ALTER COLUMN project_path DROP NOT NULL;
|
|
18
|
+
ALTER TABLE experiments DROP CONSTRAINT performance_study_version;
|
|
19
|
+
ALTER TABLE experiments DROP CONSTRAINT performance_study_finalization_pair;
|
|
20
|
+
ALTER TABLE experiments ADD COLUMN executor text NOT NULL DEFAULT 'fixture'
|
|
21
|
+
CHECK (executor IN ('fixture','benchmark','replay','repository','historical'));
|
|
22
|
+
ALTER TABLE experiments ADD COLUMN name text NOT NULL DEFAULT 'Experiment';
|
|
23
|
+
ALTER TABLE experiments ADD COLUMN status text NOT NULL DEFAULT 'ready'
|
|
24
|
+
CHECK (status IN ('ready','queued','running','completed','failed','cancelled','stopped'));
|
|
25
|
+
ALTER TABLE experiments ADD COLUMN result jsonb;
|
|
26
|
+
ALTER TABLE experiments ADD COLUMN parent_experiment_id uuid REFERENCES experiments(id) ON DELETE RESTRICT;
|
|
27
|
+
ALTER TABLE experiments ADD COLUMN delivery_policy_id uuid;
|
|
28
|
+
ALTER TABLE experiments ADD COLUMN salt text;
|
|
29
|
+
ALTER TABLE experiments ADD COLUMN analysis_decision text;
|
|
30
|
+
ALTER TABLE experiments ADD COLUMN legacy_evidence jsonb;
|
|
31
|
+
DROP INDEX performance_studies_protocol_unique;
|
|
32
|
+
CREATE UNIQUE INDEX experiments_fixture_protocol_unique ON experiments(user_id,eval_target_id,protocol_hash) WHERE executor='fixture';
|
|
33
|
+
CREATE INDEX experiments_user_created ON experiments(user_id,created_at DESC,id);
|
|
34
|
+
CREATE INDEX experiments_parent ON experiments(parent_experiment_id) WHERE parent_experiment_id IS NOT NULL;
|
|
35
|
+
CREATE UNIQUE INDEX experiments_delivery_policy ON experiments(delivery_policy_id) WHERE delivery_policy_id IS NOT NULL;
|
|
36
|
+
UPDATE experiments s SET name=coalesce(s.protocol->>'question',t.name),
|
|
37
|
+
status=CASE WHEN s.finalized_at IS NOT NULL THEN 'completed' ELSE 'ready' END,
|
|
38
|
+
legacy_evidence=jsonb_build_object('source','study','id',s.id,'decision',s.analysis->>'decision','runs',coalesce((SELECT jsonb_agg(to_jsonb(r)) FROM eval_runs r WHERE r.outputs->'study'->>'studyId'=s.id::text),'[]'::jsonb))
|
|
39
|
+
FROM eval_targets t WHERE t.id=s.eval_target_id;
|
|
40
|
+
|
|
41
|
+
-- The adapter projections below are READ ONLY; new code writes experiments.
|
|
42
|
+
ALTER TABLE scoped_live_confirmations RENAME TO historical_scoped_live_confirmations;
|
|
43
|
+
INSERT INTO experiments(id,user_id,executor,name,project_path,protocol,protocol_hash,
|
|
44
|
+
status,analysis,analysis_decision,created_at,finalized_at,parent_experiment_id,delivery_policy_id,salt,legacy_evidence)
|
|
45
|
+
SELECT c.id,c.user_id,'repository',i.name,c.protocol->>'projectPath',c.protocol,c.protocol_hash,
|
|
46
|
+
c.status,c.decision_detail,c.decision,c.started_at,c.ended_at,c.source_study_id,c.experiment_id,c.salt,
|
|
47
|
+
jsonb_build_object('source','live','id',c.id,'decision',c.decision,'assignments',coalesce((SELECT jsonb_agg(to_jsonb(a)) FROM scoped_live_confirmation_assignments a WHERE a.confirmation_id=c.id),'[]'::jsonb))
|
|
48
|
+
FROM historical_scoped_live_confirmations c JOIN instruction_experiments x ON x.id=c.experiment_id
|
|
49
|
+
JOIN instructions i ON i.id=x.instruction_id;
|
|
50
|
+
ALTER TABLE scoped_live_confirmation_assignments DROP CONSTRAINT scoped_live_confirmation_assignments_confirmation_id_fkey;
|
|
51
|
+
ALTER TABLE scoped_live_confirmation_assignments ADD FOREIGN KEY(confirmation_id) REFERENCES experiments(id) ON DELETE CASCADE;
|
|
52
|
+
ALTER TABLE recommendation_applications DROP CONSTRAINT recommendation_applications_scoped_confirmation_id_fkey;
|
|
53
|
+
ALTER TABLE recommendation_applications ADD FOREIGN KEY(scoped_confirmation_id) REFERENCES experiments(id) ON DELETE RESTRICT;
|
|
54
|
+
|
|
55
|
+
CREATE VIEW performance_studies AS SELECT id,user_id,eval_target_id,project_path,protocol,protocol_hash,
|
|
56
|
+
analysis AS result,finalized_at,created_at FROM experiments WHERE executor='fixture';
|
|
57
|
+
CREATE VIEW scoped_live_confirmations AS SELECT id,user_id,delivery_policy_id AS experiment_id,
|
|
58
|
+
parent_experiment_id AS source_study_id,protocol,protocol_hash,salt,status,
|
|
59
|
+
analysis_decision AS decision,analysis AS decision_detail,created_at AS started_at,finalized_at AS ended_at
|
|
60
|
+
FROM experiments WHERE executor='repository';
|
|
61
|
+
CREATE FUNCTION reject_retired_experiment_write() RETURNS trigger LANGUAGE plpgsql AS $$
|
|
62
|
+
BEGIN RAISE EXCEPTION 'Retired experiment write path: use the canonical experiment API'; END $$;
|
|
63
|
+
CREATE TRIGGER retired_study_write INSTEAD OF INSERT OR UPDATE OR DELETE ON performance_studies
|
|
64
|
+
FOR EACH ROW EXECUTE FUNCTION reject_retired_experiment_write();
|
|
65
|
+
CREATE TRIGGER retired_confirmation_write INSTEAD OF INSERT OR UPDATE OR DELETE ON scoped_live_confirmations
|
|
66
|
+
FOR EACH ROW EXECUTE FUNCTION reject_retired_experiment_write();
|
|
67
|
+
|
|
68
|
+
ALTER TABLE eval_jobs ADD COLUMN experiment_id uuid REFERENCES experiments(id) ON DELETE RESTRICT;
|
|
69
|
+
ALTER TABLE agent_jobs ADD COLUMN experiment_id uuid REFERENCES experiments(id) ON DELETE RESTRICT;
|
|
70
|
+
CREATE INDEX eval_jobs_experiment ON eval_jobs(experiment_id) WHERE experiment_id IS NOT NULL;
|
|
71
|
+
CREATE INDEX agent_jobs_experiment ON agent_jobs(experiment_id) WHERE experiment_id IS NOT NULL;
|
|
72
|
+
UPDATE eval_jobs j SET experiment_id=s.id FROM experiments s
|
|
73
|
+
WHERE s.executor='fixture' AND s.user_id=j.user_id AND
|
|
74
|
+
s.id::text=coalesce(j.config->>'studyId',j.payload->'study'->>'studyId',j.payload->'study'->>'id');
|
|
75
|
+
|
|
76
|
+
-- Historical records enter the common store once, without recomputing claims.
|
|
77
|
+
INSERT INTO experiments(id,user_id,executor,name,project_path,protocol,protocol_hash,status,analysis,created_at,finalized_at,legacy_evidence)
|
|
78
|
+
SELECT j.id,j.user_id,CASE WHEN j.task_kind='harbor_ab' THEN 'benchmark' ELSE 'historical' END,
|
|
79
|
+
coalesce(j.payload->>'task',t.name,'Historical experiment'),coalesce(j.payload->>'projectPath',t.project_path),
|
|
80
|
+
jsonb_build_object('version',1,'execution',j.task_kind,'configuration',j.payload),encode(sha256(j.payload::text::bytea),'hex'),
|
|
81
|
+
CASE WHEN j.status='pending' THEN 'queued' WHEN j.status='claimed' THEN 'running' WHEN j.status IN ('completed','failed','running') THEN j.status ELSE 'cancelled' END,
|
|
82
|
+
j.result,j.created_at,j.completed_at,jsonb_build_object('source',CASE WHEN j.task_kind='harbor_ab' THEN 'harbor' WHEN j.task_kind='cron_eval_autorun' THEN 'eval' ELSE 'evolution' END,'id',j.id,'record',to_jsonb(j))
|
|
83
|
+
FROM agent_jobs j LEFT JOIN eval_targets t ON t.id::text=j.payload->>'evalTargetId'
|
|
84
|
+
WHERE j.task_kind IN ('harbor_ab','cron_instruction_evolution','cron_instruction_training','cron_eval_autorun');
|
|
85
|
+
UPDATE agent_jobs j SET experiment_id=e.id FROM experiments e WHERE e.id=j.id;
|
|
86
|
+
INSERT INTO experiments(id,user_id,executor,name,project_path,protocol,protocol_hash,status,analysis,created_at,finalized_at,legacy_evidence)
|
|
87
|
+
SELECT j.id,j.user_id,'historical',t.name,t.project_path,jsonb_build_object('version',1,'configuration',j.config),encode(sha256(j.config::text::bytea),'hex'),
|
|
88
|
+
CASE WHEN j.status='pending' THEN 'queued' WHEN j.status='claimed' THEN 'running' WHEN j.status IN ('completed','failed','running') THEN j.status ELSE 'cancelled' END,
|
|
89
|
+
j.result,j.created_at,j.completed_at,jsonb_build_object('source','eval','id',j.id,'record',to_jsonb(j))
|
|
90
|
+
FROM eval_jobs j JOIN eval_targets t ON t.id=j.eval_target_id WHERE j.experiment_id IS NULL
|
|
91
|
+
AND coalesce(j.task_kind,'process_eval') IN ('process_eval','process_suggestion_eval','process_suggestion_discovery');
|
|
92
|
+
UPDATE eval_jobs j SET experiment_id=e.id FROM experiments e WHERE e.id=j.id;
|
|
93
|
+
INSERT INTO experiments(id,user_id,executor,name,protocol,protocol_hash,status,analysis,created_at,finalized_at,legacy_evidence)
|
|
94
|
+
SELECT m.id,m.user_id,'historical','Memory: '||left(m.task_prompt,120),jsonb_build_object('version',1,'prompt',m.task_prompt),encode(sha256(m.task_prompt::bytea),'hex'),
|
|
95
|
+
CASE WHEN m.status='pending' THEN 'queued' ELSE m.status END,m.result,m.created_at,m.completed_at,
|
|
96
|
+
jsonb_build_object('source','memory','id',m.id,'record',to_jsonb(m)) FROM memory_experiments m;
|
|
97
|
+
INSERT INTO experiments(id,user_id,executor,name,project_path,protocol,protocol_hash,status,analysis,created_at,finalized_at,legacy_evidence)
|
|
98
|
+
SELECT c.id,c.user_id,'historical',a.name||' / '||b.name,a.project_path,jsonb_build_object('version',1,'targetA',c.target_a_id,'targetB',c.target_b_id),
|
|
99
|
+
encode(sha256(c.id::text::bytea),'hex'),CASE WHEN c.status='completed' THEN 'completed' ELSE 'failed' END,c.overall,c.created_at,c.created_at,
|
|
100
|
+
jsonb_build_object('source','comparison','id',c.id,'record',to_jsonb(c))
|
|
101
|
+
FROM eval_comparisons c JOIN eval_targets a ON a.id=c.target_a_id JOIN eval_targets b ON b.id=c.target_b_id;
|
|
102
|
+
INSERT INTO experiments(id,user_id,executor,name,project_path,protocol,protocol_hash,status,created_at,legacy_evidence)
|
|
103
|
+
SELECT x.id,x.user_id,'historical',i.name,x.project_path,jsonb_build_object('version',1),encode(sha256(x.id::text::bytea),'hex'),
|
|
104
|
+
CASE WHEN x.status='running' THEN 'cancelled' ELSE 'stopped' END,x.created_at,jsonb_build_object('source','live','id',x.id,'record',to_jsonb(x))
|
|
105
|
+
FROM instruction_experiments x JOIN instructions i ON i.id=x.instruction_id
|
|
106
|
+
WHERE NOT EXISTS(SELECT 1 FROM experiments e WHERE e.delivery_policy_id=x.id);
|
|
107
|
+
|
|
108
|
+
CREATE TABLE experiment_observations (
|
|
109
|
+
id uuid PRIMARY KEY DEFAULT gen_random_uuid(), experiment_id uuid NOT NULL REFERENCES experiments(id) ON DELETE CASCADE,
|
|
110
|
+
task_id text NOT NULL, arm text NOT NULL CHECK(arm IN ('baseline','treatment')), repetition integer NOT NULL CHECK(repetition>0),
|
|
111
|
+
metrics jsonb NOT NULL, eligibility text NOT NULL CHECK(eligibility IN ('eligible','excluded')),
|
|
112
|
+
exclusion text, evidence jsonb NOT NULL, created_at timestamptz NOT NULL DEFAULT now(),
|
|
113
|
+
CONSTRAINT experiment_observation_once UNIQUE(experiment_id,task_id,arm,repetition)
|
|
114
|
+
);
|
|
115
|
+
ALTER TABLE experiment_observations ENABLE ROW LEVEL SECURITY;
|
|
116
|
+
CREATE POLICY experiment_observations_owner ON experiment_observations FOR SELECT USING
|
|
117
|
+
(EXISTS(SELECT 1 FROM experiments e WHERE e.id=experiment_id AND e.user_id=auth.uid()));
|
|
118
|
+
CREATE FUNCTION preserve_experiment_observation() RETURNS trigger LANGUAGE plpgsql AS $$
|
|
119
|
+
BEGIN RAISE EXCEPTION 'Experiment observations are immutable'; END $$;
|
|
120
|
+
CREATE TRIGGER experiment_observation_immutable BEFORE UPDATE ON experiment_observations
|
|
121
|
+
FOR EACH ROW EXECUTE FUNCTION preserve_experiment_observation();
|
|
122
|
+
|
|
123
|
+
-- Archive-only special-purpose experiment producers. Historical reads survive.
|
|
124
|
+
CREATE TRIGGER retired_memory_experiment_write BEFORE INSERT OR UPDATE ON memory_experiments
|
|
125
|
+
FOR EACH ROW EXECUTE FUNCTION reject_retired_experiment_write();
|
|
126
|
+
CREATE TRIGGER retired_blind_comparison_write BEFORE INSERT OR UPDATE ON eval_comparisons
|
|
127
|
+
FOR EACH ROW EXECUTE FUNCTION reject_retired_experiment_write();
|
|
128
|
+
CREATE TRIGGER retired_confirmation_archive_write BEFORE INSERT OR UPDATE ON historical_scoped_live_confirmations
|
|
129
|
+
FOR EACH ROW EXECUTE FUNCTION reject_retired_experiment_write();
|
|
130
|
+
|
|
131
|
+
-- Canonical identity/protocol cannot move, even while execution status changes.
|
|
132
|
+
CREATE FUNCTION preserve_experiment() RETURNS trigger LANGUAGE plpgsql AS $$
|
|
133
|
+
BEGIN
|
|
134
|
+
IF (to_jsonb(NEW)-ARRAY['status','analysis','analysis_decision','result','finalized_at']) IS DISTINCT FROM
|
|
135
|
+
(to_jsonb(OLD)-ARRAY['status','analysis','analysis_decision','result','finalized_at']) THEN
|
|
136
|
+
RAISE EXCEPTION 'Experiment identity and protocol are immutable';
|
|
137
|
+
END IF;
|
|
138
|
+
IF OLD.result IS NOT NULL AND NEW.result IS DISTINCT FROM OLD.result THEN
|
|
139
|
+
RAISE EXCEPTION 'Final experiment result is immutable';
|
|
140
|
+
END IF;
|
|
141
|
+
IF OLD.finalized_at IS NOT NULL AND (NEW.analysis IS DISTINCT FROM OLD.analysis OR NEW.analysis_decision IS DISTINCT FROM OLD.analysis_decision) THEN
|
|
142
|
+
RAISE EXCEPTION 'Final analysis is immutable';
|
|
143
|
+
END IF;
|
|
144
|
+
RETURN NEW;
|
|
145
|
+
END $$;
|
|
146
|
+
CREATE TRIGGER experiment_immutable BEFORE UPDATE ON experiments FOR EACH ROW EXECUTE FUNCTION preserve_experiment();
|
|
147
|
+
COMMIT;
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
BEGIN;
|
|
2
|
+
-- Preserve the original decision verbatim in analysis/legacy_evidence. The
|
|
3
|
+
-- historical normalization is a one-time import, not another live evaluator.
|
|
4
|
+
UPDATE experiments SET result=jsonb_build_object(
|
|
5
|
+
'version',1,'primaryMetric',CASE WHEN analysis IS NULL THEN NULL ELSE (CASE WHEN executor IN ('fixture','repository') THEN 'correctness' WHEN executor='benchmark' THEN CASE WHEN analysis->>'axis'='cost' OR analysis->>'decision'='cost_signal' OR protocol->'configuration'->>'axis'='cost' THEN 'costUsd' ELSE 'correctness' END ELSE 'recorded' END) END,
|
|
6
|
+
'metrics',CASE WHEN analysis IS NULL THEN '[]'::jsonb ELSE jsonb_build_array(jsonb_build_object(
|
|
7
|
+
'key',(CASE WHEN executor IN ('fixture','repository') THEN 'correctness' WHEN executor='benchmark' THEN CASE WHEN analysis->>'axis'='cost' OR analysis->>'decision'='cost_signal' OR protocol->'configuration'->>'axis'='cost' THEN 'costUsd' ELSE 'correctness' END ELSE 'recorded' END),'label',CASE (CASE WHEN executor IN ('fixture','repository') THEN 'correctness' WHEN executor='benchmark' THEN CASE WHEN analysis->>'axis'='cost' OR analysis->>'decision'='cost_signal' OR protocol->'configuration'->>'axis'='cost' THEN 'costUsd' ELSE 'correctness' END ELSE 'recorded' END) WHEN 'costUsd' THEN 'Cost' WHEN 'correctness' THEN 'Correctness' ELSE 'Historical finding' END,
|
|
8
|
+
'unit',CASE (CASE WHEN executor IN ('fixture','repository') THEN 'correctness' WHEN executor='benchmark' THEN CASE WHEN analysis->>'axis'='cost' OR analysis->>'decision'='cost_signal' OR protocol->'configuration'->>'axis'='cost' THEN 'costUsd' ELSE 'correctness' END ELSE 'recorded' END) WHEN 'costUsd' THEN 'USD' WHEN 'correctness' THEN 'pass fraction' ELSE 'original measure' END,
|
|
9
|
+
'direction',CASE WHEN (CASE WHEN executor IN ('fixture','repository') THEN 'correctness' WHEN executor='benchmark' THEN CASE WHEN analysis->>'axis'='cost' OR analysis->>'decision'='cost_signal' OR protocol->'configuration'->>'axis'='cost' THEN 'costUsd' ELSE 'correctness' END ELSE 'recorded' END)='costUsd' THEN 'lower' ELSE 'higher' END,
|
|
10
|
+
'baseline',NULL,'treatment',NULL,'basis','historical','interval',NULL,
|
|
11
|
+
'finding',CASE
|
|
12
|
+
WHEN status NOT IN ('completed','stopped') THEN 'unassessed'
|
|
13
|
+
WHEN executor='benchmark' AND (analysis->>'decisionSemanticsVersion' IS DISTINCT FROM '2' OR analysis->'provenance'->>'comparable' IS DISTINCT FROM 'true') THEN 'unassessed'
|
|
14
|
+
WHEN analysis->>'decision'='cost_signal' AND analysis->'cost'->>'rewardRegressed'='true' THEN 'inconclusive'
|
|
15
|
+
WHEN analysis->>'decision'='cost_signal' AND analysis->'cost'->>'decision'='cheaper' THEN 'improved'
|
|
16
|
+
WHEN analysis->>'decision'='cost_signal' AND analysis->'cost'->>'decision'='costlier' THEN 'worsened'
|
|
17
|
+
WHEN coalesce(analysis->>'decision',analysis_decision) IN ('improvement','improved','holds','now_discriminates','promote') THEN 'improved'
|
|
18
|
+
WHEN coalesce(analysis->>'decision',analysis_decision) IN ('harmful','reward_harm') THEN 'worsened'
|
|
19
|
+
WHEN analysis->>'decision'='no_useful_effect' THEN 'no_useful_difference'
|
|
20
|
+
WHEN legacy_evidence->>'source'='memory' AND analysis->'verdict'->>'sufficient'='true'
|
|
21
|
+
AND analysis->'verdict'->>'significant'='true' AND analysis->'verdict'->>'confounded'='false'
|
|
22
|
+
THEN CASE WHEN (analysis->'verdict'->>'delta')::numeric>0 THEN 'improved' ELSE 'worsened' END
|
|
23
|
+
ELSE 'inconclusive' END,
|
|
24
|
+
'explanation',coalesce(analysis->>'explanation',analysis->>'reason','Historical decision retained; inspect original evidence.'))) END,
|
|
25
|
+
'validity','limited','explanation','Imported historical evidence; original protocol and decision are preserved.',
|
|
26
|
+
'observed',coalesce((analysis->>'observedCalls')::integer,0),'planned',NULL,'applicationAllowed',false)
|
|
27
|
+
WHERE status IN ('completed','failed','stopped','cancelled') AND result IS NULL;
|
|
28
|
+
|
|
29
|
+
-- Executions have transport jobs; their status is not an effect verdict.
|
|
30
|
+
CREATE FUNCTION sync_experiment_execution_status() RETURNS trigger LANGUAGE plpgsql AS $$
|
|
31
|
+
BEGIN
|
|
32
|
+
IF NEW.experiment_id IS NOT NULL THEN
|
|
33
|
+
UPDATE experiments SET status=CASE NEW.status WHEN 'pending' THEN 'queued' WHEN 'claimed' THEN 'running'
|
|
34
|
+
WHEN 'paused' THEN 'running' WHEN 'cancelled' THEN 'cancelled' ELSE NEW.status END
|
|
35
|
+
WHERE id=NEW.experiment_id AND user_id=NEW.user_id AND result IS NULL;
|
|
36
|
+
END IF;
|
|
37
|
+
RETURN NEW;
|
|
38
|
+
END $$;
|
|
39
|
+
CREATE TRIGGER eval_experiment_status AFTER INSERT OR UPDATE OF status,experiment_id ON eval_jobs
|
|
40
|
+
FOR EACH ROW EXECUTE FUNCTION sync_experiment_execution_status();
|
|
41
|
+
CREATE TRIGGER agent_experiment_status AFTER INSERT OR UPDATE OF status,experiment_id ON agent_jobs
|
|
42
|
+
FOR EACH ROW EXECUTE FUNCTION sync_experiment_execution_status();
|
|
43
|
+
|
|
44
|
+
-- File workflow state is an actuator implementation detail. Actual applications
|
|
45
|
+
-- from every actuator have one ledger and cannot contaminate each other's gates.
|
|
46
|
+
CREATE TABLE experiment_applications (
|
|
47
|
+
id uuid PRIMARY KEY, user_id uuid NOT NULL REFERENCES profiles(id) ON DELETE CASCADE,
|
|
48
|
+
experiment_id uuid REFERENCES experiments(id) ON DELETE RESTRICT,
|
|
49
|
+
action text NOT NULL CHECK(action IN ('file_change','instruction_delivery','instruction_revision')),
|
|
50
|
+
status text NOT NULL CHECK(status IN ('not_applied','applied','reverted')),
|
|
51
|
+
target text NOT NULL, applied_at timestamptz, evidence jsonb NOT NULL,
|
|
52
|
+
created_at timestamptz NOT NULL DEFAULT now()
|
|
53
|
+
);
|
|
54
|
+
CREATE INDEX experiment_applications_experiment ON experiment_applications(experiment_id,created_at,id);
|
|
55
|
+
ALTER TABLE experiment_applications ENABLE ROW LEVEL SECURITY;
|
|
56
|
+
CREATE POLICY experiment_applications_owner ON experiment_applications FOR SELECT USING(auth.uid()=user_id);
|
|
57
|
+
CREATE FUNCTION record_file_application() RETURNS trigger LANGUAGE plpgsql AS $$
|
|
58
|
+
BEGIN
|
|
59
|
+
INSERT INTO experiment_applications(id,user_id,experiment_id,action,status,target,applied_at,evidence,created_at)
|
|
60
|
+
VALUES(NEW.id,NEW.user_id,coalesce(NEW.scoped_confirmation_id,NEW.source_study_id),'file_change',
|
|
61
|
+
CASE WHEN NEW.reverted_at IS NOT NULL THEN 'reverted' WHEN NEW.applied_at IS NOT NULL THEN 'applied' ELSE 'not_applied' END,
|
|
62
|
+
NEW.target_path,NEW.applied_at,to_jsonb(NEW),NEW.created_at)
|
|
63
|
+
ON CONFLICT(id) DO UPDATE SET status=EXCLUDED.status,applied_at=EXCLUDED.applied_at,evidence=EXCLUDED.evidence;
|
|
64
|
+
RETURN NEW;
|
|
65
|
+
END $$;
|
|
66
|
+
CREATE TRIGGER file_application_receipt AFTER INSERT OR UPDATE ON recommendation_applications
|
|
67
|
+
FOR EACH ROW EXECUTE FUNCTION record_file_application();
|
|
68
|
+
INSERT INTO experiment_applications(id,user_id,experiment_id,action,status,target,applied_at,evidence,created_at)
|
|
69
|
+
SELECT a.id,a.user_id,coalesce(a.scoped_confirmation_id,a.source_study_id),'file_change',
|
|
70
|
+
CASE WHEN a.reverted_at IS NOT NULL THEN 'reverted' WHEN a.applied_at IS NOT NULL THEN 'applied' ELSE 'not_applied' END,
|
|
71
|
+
a.target_path,a.applied_at,to_jsonb(a),a.created_at FROM recommendation_applications a;
|
|
72
|
+
|
|
73
|
+
-- Retire the competing automatic revision runner. Manual restoration survives.
|
|
74
|
+
CREATE OR REPLACE FUNCTION public.enqueue_instruction_evolution() RETURNS integer
|
|
75
|
+
LANGUAGE sql SECURITY DEFINER SET search_path=public AS $$ SELECT 0 $$;
|
|
76
|
+
CREATE FUNCTION reject_retired_experiment_job() RETURNS trigger LANGUAGE plpgsql AS $$
|
|
77
|
+
BEGIN
|
|
78
|
+
IF NEW.task_kind='memory_ab' OR (NEW.task_kind='cron_instruction_evolution' AND NEW.payload->'restoration' IS NULL) THEN
|
|
79
|
+
RAISE EXCEPTION 'Retired experiment producer: create a canonical experiment';
|
|
80
|
+
END IF;
|
|
81
|
+
RETURN NEW;
|
|
82
|
+
END $$;
|
|
83
|
+
CREATE TRIGGER retired_experiment_job BEFORE INSERT ON agent_jobs FOR EACH ROW EXECUTE FUNCTION reject_retired_experiment_job();
|
|
84
|
+
UPDATE agent_jobs SET status='failed',error='Superseded by canonical experiments; no additional calls authorized',completed_at=now(),claimed_by=NULL,lease_expires_at=NULL
|
|
85
|
+
WHERE status='pending' AND (task_kind='memory_ab' OR (task_kind='cron_instruction_evolution' AND payload->'restoration' IS NULL));
|
|
86
|
+
COMMIT;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
BEGIN;
|
|
2
|
+
-- Compatibility projections must preserve the owner policies on their base tables.
|
|
3
|
+
ALTER VIEW performance_studies SET (security_invoker=true);
|
|
4
|
+
ALTER VIEW scoped_live_confirmations SET (security_invoker=true);
|
|
5
|
+
|
|
6
|
+
-- Serialize completion with receipt admission. Identical retries are safe even
|
|
7
|
+
-- after finalization, but a conflicting retry never disappears behind DO NOTHING.
|
|
8
|
+
CREATE FUNCTION admit_experiment_observation() RETURNS trigger LANGUAGE plpgsql AS $$
|
|
9
|
+
DECLARE e experiments%ROWTYPE; prior experiment_observations%ROWTYPE;
|
|
10
|
+
BEGIN
|
|
11
|
+
SELECT * INTO e FROM experiments WHERE id=NEW.experiment_id FOR UPDATE;
|
|
12
|
+
IF NOT FOUND THEN RAISE EXCEPTION 'Experiment not found'; END IF;
|
|
13
|
+
SELECT * INTO prior FROM experiment_observations WHERE experiment_id=NEW.experiment_id
|
|
14
|
+
AND task_id=NEW.task_id AND arm=NEW.arm AND repetition=NEW.repetition;
|
|
15
|
+
IF FOUND THEN
|
|
16
|
+
IF (to_jsonb(prior)-ARRAY['id','created_at']) IS DISTINCT FROM (to_jsonb(NEW)-ARRAY['id','created_at']) THEN
|
|
17
|
+
RAISE EXCEPTION 'Conflicting experiment observation';
|
|
18
|
+
END IF;
|
|
19
|
+
RETURN NULL;
|
|
20
|
+
END IF;
|
|
21
|
+
IF e.result IS NOT NULL THEN RAISE EXCEPTION 'Experiment is finalized'; END IF;
|
|
22
|
+
RETURN NEW;
|
|
23
|
+
END $$;
|
|
24
|
+
CREATE TRIGGER experiment_observation_admission BEFORE INSERT ON experiment_observations
|
|
25
|
+
FOR EACH ROW EXECUTE FUNCTION admit_experiment_observation();
|
|
26
|
+
|
|
27
|
+
-- Trial and follow-up delivery both carry their phase in the receipt.
|
|
28
|
+
CREATE FUNCTION record_experiment_delivery() RETURNS trigger LANGUAGE plpgsql AS $$
|
|
29
|
+
BEGIN
|
|
30
|
+
IF NEW.delivered_at IS NOT NULL AND OLD.delivered_at IS NULL THEN
|
|
31
|
+
INSERT INTO experiment_applications(id,user_id,experiment_id,action,status,target,applied_at,evidence)
|
|
32
|
+
VALUES(NEW.id,NEW.user_id,NEW.confirmation_id,'instruction_delivery','applied','assignment:'||NEW.id,NEW.delivered_at,
|
|
33
|
+
jsonb_build_object('phase',NEW.phase,'arm',NEW.arm,'deliveryHash',NEW.delivery_hash,'sessionId',NEW.external_session_id))
|
|
34
|
+
ON CONFLICT(id) DO NOTHING;
|
|
35
|
+
END IF;
|
|
36
|
+
RETURN NEW;
|
|
37
|
+
END $$;
|
|
38
|
+
CREATE TRIGGER experiment_delivery_receipt AFTER UPDATE OF delivered_at ON scoped_live_confirmation_assignments
|
|
39
|
+
FOR EACH ROW EXECUTE FUNCTION record_experiment_delivery();
|
|
40
|
+
INSERT INTO experiment_applications(id,user_id,experiment_id,action,status,target,applied_at,evidence)
|
|
41
|
+
SELECT a.id,a.user_id,a.confirmation_id,'instruction_delivery','applied','assignment:'||a.id,a.delivered_at,
|
|
42
|
+
jsonb_build_object('phase',a.phase,'arm',a.arm,'deliveryHash',a.delivery_hash,'sessionId',a.external_session_id,'historical',true)
|
|
43
|
+
FROM scoped_live_confirmation_assignments a WHERE a.delivered_at IS NOT NULL;
|
|
44
|
+
|
|
45
|
+
-- A recorded promotion decision can be a dry run. Only actual rewrites enter
|
|
46
|
+
-- this ledger. Their complete inverse bodies remain in the immutable receipt.
|
|
47
|
+
CREATE FUNCTION record_instruction_revision_application() RETURNS trigger LANGUAGE plpgsql AS $$
|
|
48
|
+
BEGIN
|
|
49
|
+
IF NEW.applied THEN
|
|
50
|
+
INSERT INTO experiment_applications(id,user_id,experiment_id,action,status,target,applied_at,evidence)
|
|
51
|
+
VALUES(NEW.id,NEW.user_id,(SELECT e.id FROM experiments e WHERE e.user_id=NEW.user_id AND e.analysis->>'promotionId'=NEW.id::text LIMIT 1),'instruction_revision','applied','instruction:'||coalesce(NEW.instruction_id,NEW.eval_target_id),coalesce((NEW.gates->>'appliedAt')::timestamptz,NEW.created_at),to_jsonb(NEW))
|
|
52
|
+
ON CONFLICT(id) DO NOTHING;
|
|
53
|
+
END IF;
|
|
54
|
+
RETURN NEW;
|
|
55
|
+
END $$;
|
|
56
|
+
CREATE TRIGGER instruction_revision_application AFTER INSERT OR UPDATE OF applied ON instruction_promotions
|
|
57
|
+
FOR EACH ROW EXECUTE FUNCTION record_instruction_revision_application();
|
|
58
|
+
INSERT INTO experiment_applications(id,user_id,experiment_id,action,status,target,applied_at,evidence)
|
|
59
|
+
SELECT p.id,p.user_id,(SELECT e.id FROM experiments e WHERE e.user_id=p.user_id AND e.analysis->>'promotionId'=p.id::text LIMIT 1),'instruction_revision','applied','instruction:'||coalesce(p.instruction_id,p.eval_target_id),coalesce((p.gates->>'appliedAt')::timestamptz,p.created_at),to_jsonb(p)
|
|
60
|
+
FROM instruction_promotions p WHERE p.applied;
|
|
61
|
+
COMMIT;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
BEGIN;
|
|
2
|
+
-- Runs written before queue identities existed remain separately inspectable.
|
|
3
|
+
-- Do not import receipts already represented by a study or queue experiment.
|
|
4
|
+
INSERT INTO experiments(id,user_id,executor,name,eval_target_id,project_path,protocol,protocol_hash,status,analysis,result,created_at,finalized_at,legacy_evidence)
|
|
5
|
+
SELECT r.id,t.user_id,'historical',t.name||' / '||e.name,t.id,t.project_path,
|
|
6
|
+
jsonb_build_object('version',1,'configuration',r.configuration,'targetVersion',r.eval_target_version),
|
|
7
|
+
encode(sha256(r.id::text::bytea),'hex'),CASE WHEN r.status='completed' THEN 'completed' ELSE 'failed' END,
|
|
8
|
+
jsonb_build_object('grading',r.grading,'outputs',r.outputs,'timing',r.timing),
|
|
9
|
+
jsonb_build_object('version',1,'primaryMetric',NULL,'metrics','[]'::jsonb,'validity','unassessed',
|
|
10
|
+
'explanation','Historical execution receipt; no inferential comparison was recorded.',
|
|
11
|
+
'observed',1,'planned',NULL,'applicationAllowed',false),coalesce(r.created_at,now()),coalesce(r.created_at,now()),
|
|
12
|
+
jsonb_build_object('source','eval-run','id',r.id,'record',to_jsonb(r))
|
|
13
|
+
FROM eval_runs r JOIN eval_targets t ON t.id=r.eval_target_id JOIN evals e ON e.id=r.eval_id
|
|
14
|
+
WHERE NOT EXISTS(SELECT 1 FROM eval_jobs j WHERE j.id=r.job_id AND j.experiment_id IS NOT NULL)
|
|
15
|
+
AND NOT EXISTS(SELECT 1 FROM experiments x WHERE x.id::text=r.outputs->'study'->>'studyId');
|
|
16
|
+
|
|
17
|
+
-- Retired producers cannot leave perpetual running entries. Their original
|
|
18
|
+
-- state and results are already preserved verbatim in legacy_evidence.
|
|
19
|
+
UPDATE experiments SET status='cancelled',finalized_at=coalesce(finalized_at,now()),result=jsonb_build_object(
|
|
20
|
+
'version',1,'primaryMetric',NULL,'metrics','[]'::jsonb,'validity','unassessed',
|
|
21
|
+
'explanation','Historical producer retired before completion. No new calls were made.',
|
|
22
|
+
'observed',0,'planned',NULL,'applicationAllowed',false)
|
|
23
|
+
WHERE executor='historical' AND status IN ('ready','queued','running') AND result IS NULL;
|
|
24
|
+
|
|
25
|
+
-- Ordinary session randomization is retired. Repository delivery policies are
|
|
26
|
+
-- stopped too: their canonical experiment owns per-task enrollment and delivery.
|
|
27
|
+
ALTER TABLE instruction_experiments DROP CONSTRAINT instruction_experiments_stopped_reason_check;
|
|
28
|
+
ALTER TABLE instruction_experiments ADD CONSTRAINT instruction_experiments_stopped_reason_check CHECK (stopped_reason IN (
|
|
29
|
+
'graduated','trial_neutral','trial_inconclusive','content_changed','inactive_project','invalid_template','delivery_refused','scoped_live_confirmation','canonical_experiments'));
|
|
30
|
+
UPDATE instruction_experiments SET status='stopped',stopped_reason='canonical_experiments'
|
|
31
|
+
WHERE status='running';
|
|
32
|
+
COMMIT;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
BEGIN;
|
|
2
|
+
ALTER TABLE experiments ADD CONSTRAINT experiment_fixture_version CHECK(executor<>'fixture' OR (protocol ? 'version' AND protocol->>'version'='1'));
|
|
3
|
+
ALTER TABLE experiments ADD CONSTRAINT experiment_fixture_finalization CHECK(executor<>'fixture' OR ((analysis IS NULL)=(finalized_at IS NULL)));
|
|
4
|
+
CREATE FUNCTION admit_experiment() RETURNS trigger LANGUAGE plpgsql AS $$
|
|
5
|
+
BEGIN
|
|
6
|
+
IF NEW.executor<>'historical' AND (NEW.result IS NOT NULL OR NEW.analysis IS NOT NULL OR NEW.finalized_at IS NOT NULL) THEN
|
|
7
|
+
RAISE EXCEPTION 'Experiments must be registered before finalization';
|
|
8
|
+
END IF;
|
|
9
|
+
RETURN NEW;
|
|
10
|
+
END $$;
|
|
11
|
+
CREATE TRIGGER experiment_admission BEFORE INSERT ON experiments FOR EACH ROW EXECUTE FUNCTION admit_experiment();
|
|
12
|
+
CREATE FUNCTION preserve_experiment_final_state() RETURNS trigger LANGUAGE plpgsql AS $$
|
|
13
|
+
BEGIN
|
|
14
|
+
IF OLD.result IS NOT NULL AND (NEW.status IS DISTINCT FROM OLD.status OR NEW.finalized_at IS DISTINCT FROM OLD.finalized_at) THEN
|
|
15
|
+
RAISE EXCEPTION 'Final experiment execution state is immutable';
|
|
16
|
+
END IF;
|
|
17
|
+
RETURN NEW;
|
|
18
|
+
END $$;
|
|
19
|
+
CREATE TRIGGER experiment_final_state BEFORE UPDATE ON experiments FOR EACH ROW EXECUTE FUNCTION preserve_experiment_final_state();
|
|
20
|
+
COMMIT;
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
BEGIN;
|
|
2
|
+
-- Materialize existing raw receipts, without recomputing historical findings or
|
|
3
|
+
-- granting eligibility to old labels. Only migration imports may bypass finalization.
|
|
4
|
+
ALTER TABLE experiment_observations DISABLE TRIGGER experiment_observation_admission;
|
|
5
|
+
|
|
6
|
+
WITH receipts AS (
|
|
7
|
+
SELECT e.id AS experiment_id,'eval-run:'||r.id AS identity,
|
|
8
|
+
CASE WHEN r.configuration IN ('without_target','baseline','with_prior_version') THEN 'baseline' ELSE 'treatment' END AS arm,
|
|
9
|
+
to_jsonb(r) AS record, 1 AS priority
|
|
10
|
+
FROM eval_runs r JOIN eval_targets t ON t.id=r.eval_target_id
|
|
11
|
+
LEFT JOIN eval_jobs j ON j.id=r.job_id AND j.user_id=t.user_id
|
|
12
|
+
JOIN experiments e ON e.user_id=t.user_id AND e.legacy_evidence IS NOT NULL
|
|
13
|
+
AND e.id::text=coalesce(r.outputs->'study'->>'studyId',j.experiment_id::text,r.id::text)
|
|
14
|
+
UNION ALL
|
|
15
|
+
-- Snapshot receipts survive even when their original execution rows were pruned.
|
|
16
|
+
SELECT e.id,'eval-run:'||(r->>'id'),
|
|
17
|
+
CASE WHEN r->>'configuration' IN ('without_target','baseline','with_prior_version') THEN 'baseline' ELSE 'treatment' END,r,0
|
|
18
|
+
FROM experiments e CROSS JOIN LATERAL jsonb_array_elements(
|
|
19
|
+
CASE WHEN jsonb_typeof(e.legacy_evidence->'runs')='array' THEN e.legacy_evidence->'runs' ELSE '[]'::jsonb END) r
|
|
20
|
+
WHERE r->>'id' IS NOT NULL
|
|
21
|
+
UNION ALL
|
|
22
|
+
SELECT e.id,'eval-run:'||(e.legacy_evidence->'record'->>'id'),
|
|
23
|
+
CASE WHEN e.legacy_evidence->'record'->>'configuration' IN ('without_target','baseline','with_prior_version') THEN 'baseline' ELSE 'treatment' END,
|
|
24
|
+
e.legacy_evidence->'record',0
|
|
25
|
+
FROM experiments e WHERE e.legacy_evidence->>'source'='eval-run' AND e.legacy_evidence->'record'->>'id' IS NOT NULL
|
|
26
|
+
UNION ALL
|
|
27
|
+
SELECT e.id,'assignment:'||(a->>'id'),CASE WHEN a->>'arm'='treated' THEN 'treatment' ELSE 'baseline' END,a,0
|
|
28
|
+
FROM experiments e CROSS JOIN LATERAL jsonb_array_elements(
|
|
29
|
+
CASE WHEN jsonb_typeof(e.legacy_evidence->'assignments')='array' THEN e.legacy_evidence->'assignments' ELSE '[]'::jsonb END) a
|
|
30
|
+
WHERE a->>'id' IS NOT NULL
|
|
31
|
+
UNION ALL
|
|
32
|
+
SELECT e.id,'assignment:'||a.id,CASE WHEN a.arm='treated' THEN 'treatment' ELSE 'baseline' END,to_jsonb(a),1
|
|
33
|
+
FROM experiments e JOIN scoped_live_confirmation_assignments a ON a.confirmation_id=e.id AND a.user_id=e.user_id
|
|
34
|
+
WHERE e.legacy_evidence IS NOT NULL
|
|
35
|
+
UNION ALL
|
|
36
|
+
SELECT e.id,'session-assignment:'||a.id,CASE WHEN a.arm='treated' THEN 'treatment' ELSE 'baseline' END,to_jsonb(a),1
|
|
37
|
+
FROM experiments e JOIN instruction_experiment_arms a ON a.experiment_id=e.id AND a.user_id=e.user_id
|
|
38
|
+
WHERE e.legacy_evidence->>'source'='live' AND e.executor='historical'
|
|
39
|
+
UNION ALL
|
|
40
|
+
SELECT e.id,'worker-observation:'||o.ordinality,CASE WHEN o.value->>'arm'='baseline' THEN 'baseline' ELSE 'treatment' END,o.value,0
|
|
41
|
+
FROM experiments e CROSS JOIN LATERAL jsonb_array_elements(
|
|
42
|
+
CASE WHEN jsonb_typeof(e.legacy_evidence->'record'->'result'->'observations')='array'
|
|
43
|
+
THEN e.legacy_evidence->'record'->'result'->'observations' ELSE '[]'::jsonb END) WITH ORDINALITY o
|
|
44
|
+
UNION ALL
|
|
45
|
+
SELECT e.id,'memory-pair:'||p.ordinality,arm.value,p.value,0
|
|
46
|
+
FROM experiments e CROSS JOIN LATERAL jsonb_array_elements(
|
|
47
|
+
CASE WHEN jsonb_typeof(e.legacy_evidence->'record'->'result'->'pairs')='array'
|
|
48
|
+
THEN e.legacy_evidence->'record'->'result'->'pairs' ELSE '[]'::jsonb END) WITH ORDINALITY p
|
|
49
|
+
CROSS JOIN (VALUES ('baseline'),('treatment')) arm(value)
|
|
50
|
+
WHERE e.legacy_evidence->>'source'='memory'
|
|
51
|
+
), deduplicated AS (
|
|
52
|
+
SELECT DISTINCT ON (experiment_id,identity,arm) * FROM receipts
|
|
53
|
+
-- Prefer the frozen snapshot if the retained execution row later changed.
|
|
54
|
+
ORDER BY experiment_id,identity,arm,priority
|
|
55
|
+
)
|
|
56
|
+
INSERT INTO experiment_observations(experiment_id,task_id,arm,repetition,metrics,eligibility,exclusion,evidence)
|
|
57
|
+
SELECT experiment_id,'historical:'||identity,arm,1,'{}'::jsonb,'excluded',
|
|
58
|
+
'Historical raw receipt; eligibility was not re-adjudicated during migration.',
|
|
59
|
+
jsonb_build_object('historical',true,'sourceIdentity',identity,'record',record)
|
|
60
|
+
FROM deduplicated
|
|
61
|
+
ON CONFLICT(experiment_id,task_id,arm,repetition) DO NOTHING;
|
|
62
|
+
|
|
63
|
+
ALTER TABLE experiment_observations ENABLE TRIGGER experiment_observation_admission;
|
|
64
|
+
-- Restoration is an inverse action tied to the original receipt, not another
|
|
65
|
+
-- experimental revision. Preserve the original evidence and do not guess links.
|
|
66
|
+
CREATE OR REPLACE FUNCTION record_instruction_revision_application() RETURNS trigger LANGUAGE plpgsql AS $$
|
|
67
|
+
DECLARE experiment_id uuid;
|
|
68
|
+
BEGIN
|
|
69
|
+
IF NEW.applied THEN
|
|
70
|
+
SELECT e.id INTO experiment_id FROM experiments e
|
|
71
|
+
WHERE e.user_id=NEW.user_id AND e.analysis->>'promotionId'=NEW.id::text LIMIT 1;
|
|
72
|
+
IF experiment_id IS NULL AND NEW.decision='revert' THEN
|
|
73
|
+
SELECT a.experiment_id INTO experiment_id FROM experiment_applications a
|
|
74
|
+
WHERE a.user_id=NEW.user_id AND a.action='instruction_revision' AND a.evidence->>'decision'='promote'
|
|
75
|
+
AND a.id::text=NEW.gates->>'restoredPromotionId';
|
|
76
|
+
END IF;
|
|
77
|
+
INSERT INTO experiment_applications(id,user_id,experiment_id,action,status,target,applied_at,evidence)
|
|
78
|
+
VALUES(NEW.id,NEW.user_id,experiment_id,'instruction_revision',CASE WHEN NEW.decision='revert' THEN 'reverted' ELSE 'applied' END,
|
|
79
|
+
'instruction:'||coalesce(NEW.instruction_id,NEW.eval_target_id),coalesce((NEW.gates->>'appliedAt')::timestamptz,NEW.created_at),to_jsonb(NEW))
|
|
80
|
+
ON CONFLICT(id) DO NOTHING;
|
|
81
|
+
END IF;
|
|
82
|
+
RETURN NEW;
|
|
83
|
+
END $$;
|
|
84
|
+
UPDATE experiment_applications a SET status='reverted',experiment_id=coalesce(a.experiment_id,(
|
|
85
|
+
SELECT prior.experiment_id FROM experiment_applications prior
|
|
86
|
+
WHERE prior.user_id=a.user_id AND prior.action='instruction_revision' AND prior.evidence->>'decision'='promote'
|
|
87
|
+
AND prior.id::text=a.evidence->'gates'->>'restoredPromotionId'))
|
|
88
|
+
WHERE a.action='instruction_revision' AND a.status='applied' AND a.evidence->>'decision'='revert';
|
|
89
|
+
COMMIT;
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
-- A repair is another paid actor call within the same task observation.
|
|
2
|
+
-- Existing protocols retain exactly one attempt. Old workers are fenced at claim.
|
|
3
|
+
ALTER TABLE public.performance_study_calls ADD COLUMN attempt integer NOT NULL DEFAULT 1 CHECK (attempt BETWEEN 1 AND 2);
|
|
4
|
+
ALTER TABLE public.performance_study_calls DROP CONSTRAINT performance_study_calls_study_id_task_id_arm_repetition_key;
|
|
5
|
+
ALTER TABLE public.performance_study_calls ADD CONSTRAINT performance_study_calls_attempt_unique UNIQUE(study_id,task_id,arm,repetition,attempt);
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
BEGIN;
|
|
2
|
+
-- Some pre-cutover deployments contain version-2 studies despite the repository's
|
|
3
|
+
-- version-1 registration contract. They remain inspectable history, never a new
|
|
4
|
+
-- executable fixture protocol. Preserve every original byte and finding.
|
|
5
|
+
-- During a 00245 refusal on this history, apply this repair before retrying 00245.
|
|
6
|
+
ALTER TABLE experiments DISABLE TRIGGER experiment_immutable;
|
|
7
|
+
UPDATE experiments SET executor='historical'
|
|
8
|
+
WHERE executor='fixture' AND legacy_evidence->>'source'='study'
|
|
9
|
+
AND (NOT (protocol ? 'version') OR protocol->>'version' IS DISTINCT FROM '1');
|
|
10
|
+
ALTER TABLE experiments ENABLE TRIGGER experiment_immutable;
|
|
11
|
+
|
|
12
|
+
CREATE OR REPLACE VIEW performance_studies AS
|
|
13
|
+
SELECT id,user_id,eval_target_id,project_path,protocol,protocol_hash,
|
|
14
|
+
analysis AS result,finalized_at,created_at FROM experiments
|
|
15
|
+
WHERE executor='fixture' OR (executor='historical' AND legacy_evidence->>'source'='study');
|
|
16
|
+
ALTER VIEW performance_studies SET (security_invoker=true);
|
|
17
|
+
COMMIT;
|