227 lines
16 KiB
PL/PgSQL
227 lines
16 KiB
PL/PgSQL
-- Fresh-only enrollment of empty diagnostic tables. No historical backfill,
|
|
-- source-generation claim, public registration or automatic cleanup is granted.
|
|
BEGIN;
|
|
LOCK TABLE server_probe_executions,server_probe_execution_stages,server_probe_execution_quarantines,mcp_diagnostic_cancellations IN ACCESS EXCLUSIVE MODE;
|
|
DO $$ BEGIN
|
|
IF EXISTS(SELECT 1 FROM server_probe_executions)
|
|
OR EXISTS(SELECT 1 FROM server_probe_execution_stages)
|
|
OR EXISTS(SELECT 1 FROM server_probe_execution_quarantines)
|
|
OR EXISTS(SELECT 1 FROM mcp_diagnostic_cancellations)
|
|
THEN RAISE EXCEPTION 'nonempty legacy diagnostic history prevents fresh storage enrollment'; END IF;
|
|
IF EXISTS(SELECT 1 FROM pg_catalog.pg_class WHERE relname IN
|
|
('server_probe_storage_usage','server_probe_storage_admissions','server_probe_execution_payloads'))
|
|
THEN RAISE EXCEPTION 'unmarked diagnostic storage relation prevents fresh storage enrollment'; END IF;
|
|
IF NOT EXISTS(SELECT 1 FROM mcp_settings WHERE id=1 AND requests_retention_days BETWEEN 1 AND 365)
|
|
THEN RAISE EXCEPTION 'authoritative probe retention setting unavailable'; END IF;
|
|
END $$;
|
|
|
|
CREATE TABLE server_probe_storage_usage (
|
|
scope text PRIMARY KEY CHECK(scope='global' OR (scope LIKE 'owner:%' AND length(scope) BETWEEN 7 AND 206)),
|
|
admissions bigint NOT NULL DEFAULT 0 CHECK(admissions>=0),
|
|
reserved_files bigint NOT NULL DEFAULT 0 CHECK(reserved_files>=0 AND reserved_files<=admissions),
|
|
reserved_bytes bigint NOT NULL DEFAULT 0 CHECK(reserved_bytes>=0),
|
|
CHECK(reserved_bytes=admissions*32768+reserved_files*262144)
|
|
);
|
|
CREATE TABLE server_probe_storage_admissions (
|
|
receipt_id uuid PRIMARY KEY,
|
|
owner_id text NOT NULL CHECK(length(owner_id) BETWEEN 1 AND 200),
|
|
identity_kind text NOT NULL CHECK(identity_kind IN ('mcp','rest')), execution_id uuid NOT NULL,
|
|
binding_proof text NOT NULL CHECK(binding_proof ~ '^[a-f0-9]{64}$'),
|
|
authority_expires_at timestamptz NOT NULL,
|
|
claim_token uuid, claim_attempted_at timestamptz,
|
|
purged_at timestamptz, cancelled_at timestamptz, created_at timestamptz NOT NULL DEFAULT clock_timestamp(),
|
|
retention_days integer NOT NULL CHECK(retention_days BETWEEN 1 AND 365),
|
|
safety_ms integer NOT NULL DEFAULT 60000 CHECK(safety_ms BETWEEN 60000 AND 86400000),
|
|
gc_not_before timestamptz NOT NULL DEFAULT 'infinity',
|
|
CHECK(gc_not_before>=authority_expires_at+make_interval(days=>retention_days)+safety_ms*interval '1 millisecond'),
|
|
UNIQUE(identity_kind,execution_id),
|
|
CHECK(claim_token IS NULL OR (claim_attempted_at IS NULL AND purged_at IS NULL AND cancelled_at IS NULL)),
|
|
CHECK(cancelled_at IS NULL OR (claim_token IS NULL AND claim_attempted_at IS NOT NULL))
|
|
);
|
|
CREATE INDEX server_probe_storage_admissions_owner_age ON server_probe_storage_admissions(owner_id,authority_expires_at,receipt_id);
|
|
CREATE TABLE server_probe_execution_payloads (
|
|
receipt_id uuid PRIMARY KEY REFERENCES server_probe_executions(receipt_id),
|
|
receipt_ciphertext text NOT NULL CHECK(octet_length(receipt_ciphertext) BETWEEN 1 AND 262144),
|
|
receipt_proof text NOT NULL CHECK(receipt_proof ~ '^[a-f0-9]{64}$')
|
|
);
|
|
INSERT INTO server_probe_storage_usage(scope,admissions,reserved_files,reserved_bytes) VALUES('global',0,0,0);
|
|
|
|
-- PostgreSQL drops table CHECKs dependent on this column together with it.
|
|
-- The locked empty-table guard precedes this column removal; no data is copied.
|
|
ALTER TABLE server_probe_executions DROP COLUMN receipt_ciphertext;
|
|
ALTER TABLE server_probe_executions ADD CONSTRAINT server_probe_storage_terminal_state CHECK(
|
|
(state='claimed' AND receipt_proof IS NULL AND finished_at IS NULL AND attempted_stages IS NULL AND completed_stages IS NULL)
|
|
OR(state<>'claimed' AND receipt_proof IS NOT NULL AND finished_at IS NOT NULL AND attempted_stages IS NOT NULL AND completed_stages IS NOT NULL));
|
|
|
|
-- Predicate only: it performs no DELETE. Every guarded exception below must
|
|
-- independently satisfy the persisted lifetime, actual current retention and
|
|
-- original terminal operation. The GUC alone never grants cleanup authority.
|
|
CREATE FUNCTION server_probe_storage_gc_allowed(id uuid) RETURNS boolean LANGUAGE plpgsql SET TimeZone='UTC' AS $$
|
|
DECLARE a server_probe_storage_admissions%ROWTYPE; activity timestamptz; current_retention integer; terminal_at timestamptz;
|
|
BEGIN
|
|
IF current_setting('mm.probe_storage_gc',true) IS DISTINCT FROM 'on' THEN RETURN false; END IF;
|
|
PERFORM pg_advisory_xact_lock(hashtextextended('server-probe-storage-v2',0));
|
|
SELECT * INTO a FROM server_probe_storage_admissions WHERE receipt_id=id FOR UPDATE;
|
|
IF NOT FOUND OR a.gc_not_before>=clock_timestamp() OR a.authority_expires_at>=clock_timestamp() THEN RETURN false; END IF;
|
|
SELECT settings.requests_retention_days INTO current_retention FROM mcp_settings settings WHERE settings.id=1;
|
|
IF current_retention IS NULL OR current_retention NOT BETWEEN 1 AND 365 THEN RETURN false; END IF;
|
|
terminal_at:=a.created_at;
|
|
IF a.identity_kind='mcp' THEN
|
|
SELECT GREATEST(o.finished_at,p.expires_at,COALESCE(cancellation.created_at,o.finished_at)) INTO terminal_at
|
|
FROM mcp_operations o JOIN mcp_plans p ON p.id=o.plan_id AND p.connection_id=o.connection_id
|
|
JOIN mcp_connections owner_connection ON owner_connection.id=o.connection_id
|
|
LEFT JOIN mcp_diagnostic_cancellations cancellation ON cancellation.operation_id=o.id
|
|
WHERE o.id=a.execution_id AND owner_connection.owner_id=a.owner_id AND p.operation_id='servers.probes.run'
|
|
AND o.state IN ('applied','failed','unknown','rejected','partial','cancelled') AND o.finished_at IS NOT NULL
|
|
AND (cancellation.operation_id IS NULL OR(cancellation.connection_id=o.connection_id AND cancellation.actor_id=a.owner_id))
|
|
FOR UPDATE OF o,p,owner_connection;
|
|
IF NOT FOUND OR terminal_at IS NULL THEN RETURN false; END IF;
|
|
END IF;
|
|
SELECT GREATEST(a.created_at,a.authority_expires_at,terminal_at,COALESCE(a.cancelled_at,a.created_at),
|
|
COALESCE((SELECT finished_at FROM server_probe_executions WHERE receipt_id=id),a.created_at),
|
|
COALESCE((SELECT created_at FROM server_probe_execution_quarantines WHERE receipt_id=id),a.created_at),
|
|
COALESCE((SELECT max(GREATEST(created_at,COALESCE(received_at,created_at))) FROM server_probe_execution_stages WHERE receipt_id=id),a.created_at)) INTO activity;
|
|
RETURN activity+make_interval(days=>GREATEST(a.retention_days,current_retention))+a.safety_ms*interval '1 millisecond'<clock_timestamp();
|
|
END $$;
|
|
|
|
CREATE FUNCTION server_probe_storage_admission_guard() RETURNS trigger LANGUAGE plpgsql AS $$
|
|
DECLARE current_retention integer; activity timestamptz;
|
|
BEGIN
|
|
PERFORM pg_advisory_xact_lock(hashtextextended('server-probe-storage-v2',0));
|
|
IF TG_OP='DELETE' THEN
|
|
IF server_probe_storage_gc_allowed(OLD.receipt_id) THEN RETURN OLD; END IF;
|
|
RAISE EXCEPTION 'probe admission lifetime has not elapsed';
|
|
END IF;
|
|
SELECT requests_retention_days INTO current_retention FROM mcp_settings WHERE id=1;
|
|
IF current_retention IS NULL OR current_retention NOT BETWEEN 1 AND 365 THEN RAISE EXCEPTION 'probe retention unavailable'; END IF;
|
|
IF TG_OP='INSERT' THEN
|
|
IF NEW.purged_at IS NOT NULL OR (NEW.cancelled_at IS NULL AND
|
|
(NEW.authority_expires_at<=clock_timestamp() OR NEW.claim_token IS NULL OR NEW.claim_attempted_at IS NOT NULL))
|
|
OR (NEW.cancelled_at IS NOT NULL AND (NEW.claim_token IS NOT NULL OR NEW.claim_attempted_at IS NULL))
|
|
THEN RAISE EXCEPTION 'invalid fresh probe admission'; END IF;
|
|
IF NEW.cancelled_at IS NOT NULL THEN NEW.cancelled_at:=clock_timestamp(); NEW.claim_attempted_at:=NEW.cancelled_at; END IF;
|
|
NEW.retention_days:=current_retention; RETURN NEW;
|
|
END IF;
|
|
IF (to_jsonb(OLD)-ARRAY['claim_token','claim_attempted_at','purged_at','cancelled_at']) IS DISTINCT FROM
|
|
(to_jsonb(NEW)-ARRAY['claim_token','claim_attempted_at','purged_at','cancelled_at'])
|
|
THEN RAISE EXCEPTION 'immutable probe admission identity'; END IF;
|
|
IF OLD.cancelled_at IS DISTINCT FROM NEW.cancelled_at THEN
|
|
IF OLD.cancelled_at IS NULL AND NEW.cancelled_at IS NOT NULL AND NEW.claim_token IS NULL
|
|
AND NEW.purged_at IS NOT DISTINCT FROM OLD.purged_at AND NEW.claim_attempted_at IS NOT NULL
|
|
AND (OLD.claim_attempted_at IS NULL OR NEW.claim_attempted_at IS NOT DISTINCT FROM OLD.claim_attempted_at)
|
|
THEN NEW.cancelled_at:=clock_timestamp(); IF OLD.claim_attempted_at IS NULL THEN NEW.claim_attempted_at:=NEW.cancelled_at; END IF; RETURN NEW; END IF;
|
|
RAISE EXCEPTION 'probe cancellation is irreversible';
|
|
END IF;
|
|
IF OLD.purged_at IS NULL AND NEW.purged_at IS NULL AND OLD.claim_token IS NOT NULL AND NEW.claim_token IS NULL
|
|
AND OLD.cancelled_at IS NULL AND OLD.claim_attempted_at IS NULL AND NEW.claim_attempted_at IS NOT NULL AND OLD.authority_expires_at>clock_timestamp()
|
|
THEN RETURN NEW; END IF;
|
|
IF OLD.purged_at IS NULL AND NEW.purged_at IS NOT NULL AND NEW.claim_token IS NULL
|
|
AND NEW.claim_attempted_at IS NOT DISTINCT FROM OLD.claim_attempted_at THEN
|
|
SELECT GREATEST(OLD.authority_expires_at,COALESCE(e.finished_at,OLD.authority_expires_at),COALESCE(q.created_at,OLD.authority_expires_at)) INTO activity
|
|
FROM (SELECT 1) x LEFT JOIN server_probe_executions e ON e.receipt_id=OLD.receipt_id
|
|
LEFT JOIN server_probe_execution_quarantines q ON q.receipt_id=OLD.receipt_id;
|
|
IF activity<clock_timestamp()-make_interval(days=>GREATEST(OLD.retention_days,current_retention)) THEN RETURN NEW; END IF;
|
|
END IF;
|
|
RAISE EXCEPTION 'probe admission transition refused';
|
|
END $$;
|
|
CREATE TRIGGER server_probe_storage_admission_guard BEFORE INSERT OR UPDATE OR DELETE ON server_probe_storage_admissions
|
|
FOR EACH ROW EXECUTE FUNCTION server_probe_storage_admission_guard();
|
|
|
|
CREATE FUNCTION server_probe_storage_anchor_guard() RETURNS trigger LANGUAGE plpgsql AS $$
|
|
DECLARE a server_probe_storage_admissions%ROWTYPE;
|
|
BEGIN
|
|
PERFORM pg_advisory_xact_lock(hashtextextended('server-probe-storage-v2',0));
|
|
SELECT * INTO a FROM server_probe_storage_admissions WHERE receipt_id=NEW.receipt_id FOR UPDATE;
|
|
IF NOT FOUND OR a.owner_id<>NEW.actor_id OR a.identity_kind<>NEW.identity_kind OR a.execution_id<>NEW.execution_id
|
|
OR a.binding_proof<>NEW.binding_proof OR a.authority_expires_at<>NEW.authority_expires_at
|
|
OR a.purged_at IS NOT NULL OR a.cancelled_at IS NOT NULL OR a.claim_token IS NOT NULL OR a.claim_attempted_at IS NULL OR a.authority_expires_at<=clock_timestamp()
|
|
THEN RAISE EXCEPTION 'probe anchor has no live consumed admission'; END IF;
|
|
RETURN NEW;
|
|
END $$;
|
|
CREATE TRIGGER server_probe_storage_anchor_guard BEFORE INSERT OR UPDATE ON server_probe_executions
|
|
FOR EACH ROW EXECUTE FUNCTION server_probe_storage_anchor_guard();
|
|
|
|
CREATE OR REPLACE FUNCTION server_probe_execution_immutable() RETURNS trigger LANGUAGE plpgsql AS $$ BEGIN
|
|
IF TG_OP='DELETE' THEN
|
|
IF server_probe_storage_gc_allowed(OLD.receipt_id) THEN RETURN OLD; END IF;
|
|
RAISE EXCEPTION 'probe execution lifetime has not elapsed';
|
|
END IF;
|
|
IF OLD.state<>'claimed' OR (to_jsonb(OLD)-ARRAY['state','attempted_stages','completed_stages','receipt_proof','finished_at']) IS DISTINCT FROM
|
|
(to_jsonb(NEW)-ARRAY['state','attempted_stages','completed_stages','receipt_proof','finished_at']) OR NEW.state='claimed'
|
|
THEN RAISE EXCEPTION 'probe execution identity or terminal receipt is immutable'; END IF;
|
|
IF EXISTS(SELECT 1 FROM server_probe_execution_quarantines WHERE receipt_id=OLD.receipt_id)
|
|
THEN RAISE EXCEPTION 'probe execution quarantined'; END IF;
|
|
RETURN NEW; END $$;
|
|
|
|
CREATE FUNCTION server_probe_storage_stage_guard() RETURNS trigger LANGUAGE plpgsql AS $$ BEGIN
|
|
PERFORM pg_advisory_xact_lock(hashtextextended('server-probe-storage-v2',0));
|
|
PERFORM receipt_id FROM server_probe_storage_admissions WHERE receipt_id=NEW.receipt_id AND purged_at IS NULL AND cancelled_at IS NULL
|
|
AND claim_token IS NULL AND claim_attempted_at IS NOT NULL AND authority_expires_at>clock_timestamp() FOR UPDATE;
|
|
IF NOT FOUND THEN RAISE EXCEPTION 'probe stage has no live admission'; END IF;
|
|
RETURN NEW; END $$;
|
|
CREATE TRIGGER server_probe_storage_stage_guard BEFORE INSERT OR UPDATE ON server_probe_execution_stages
|
|
FOR EACH ROW EXECUTE FUNCTION server_probe_storage_stage_guard();
|
|
|
|
CREATE FUNCTION server_probe_storage_quarantine_guard() RETURNS trigger LANGUAGE plpgsql AS $$ BEGIN
|
|
PERFORM pg_advisory_xact_lock(hashtextextended('server-probe-storage-v2',0));
|
|
PERFORM receipt_id FROM server_probe_storage_admissions WHERE receipt_id=NEW.receipt_id AND identity_kind=NEW.identity_kind
|
|
AND execution_id=NEW.execution_id AND binding_proof=NEW.binding_proof FOR UPDATE;
|
|
IF NOT FOUND THEN RAISE EXCEPTION 'probe quarantine has no committed matching admission'; END IF;
|
|
RETURN NEW; END $$;
|
|
CREATE TRIGGER server_probe_storage_quarantine_guard BEFORE INSERT ON server_probe_execution_quarantines
|
|
FOR EACH ROW EXECUTE FUNCTION server_probe_storage_quarantine_guard();
|
|
|
|
CREATE FUNCTION server_probe_storage_payload_guard() RETURNS trigger LANGUAGE plpgsql AS $$
|
|
DECLARE a server_probe_storage_admissions%ROWTYPE; e server_probe_executions%ROWTYPE;
|
|
BEGIN
|
|
PERFORM pg_advisory_xact_lock(hashtextextended('server-probe-storage-v2',0));
|
|
IF TG_OP='UPDATE' THEN RAISE EXCEPTION 'probe payload is immutable'; END IF;
|
|
IF TG_OP='DELETE' THEN
|
|
PERFORM receipt_id FROM server_probe_storage_admissions WHERE receipt_id=OLD.receipt_id AND purged_at IS NOT NULL FOR UPDATE;
|
|
IF NOT FOUND AND NOT server_probe_storage_gc_allowed(OLD.receipt_id) THEN RAISE EXCEPTION 'probe payload is not purged'; END IF;
|
|
RETURN OLD;
|
|
END IF;
|
|
SELECT * INTO a FROM server_probe_storage_admissions WHERE receipt_id=NEW.receipt_id FOR UPDATE;
|
|
IF NOT FOUND OR a.purged_at IS NOT NULL OR a.cancelled_at IS NOT NULL OR a.claim_token IS NOT NULL OR a.claim_attempted_at IS NULL OR a.authority_expires_at<=clock_timestamp()
|
|
THEN RAISE EXCEPTION 'probe payload has no live admission'; END IF;
|
|
SELECT * INTO e FROM server_probe_executions WHERE receipt_id=NEW.receipt_id FOR UPDATE;
|
|
IF NOT FOUND OR e.state='claimed' OR e.receipt_proof<>NEW.receipt_proof OR e.actor_id<>a.owner_id OR e.binding_proof<>a.binding_proof
|
|
OR e.identity_kind<>a.identity_kind OR e.execution_id<>a.execution_id OR EXISTS(SELECT 1 FROM server_probe_execution_quarantines q
|
|
WHERE q.receipt_id=NEW.receipt_id OR(q.identity_kind=a.identity_kind AND q.execution_id=a.execution_id))
|
|
THEN RAISE EXCEPTION 'probe payload terminal identity mismatch or quarantine'; END IF;
|
|
RETURN NEW;
|
|
END $$;
|
|
CREATE TRIGGER server_probe_storage_payload_guard BEFORE INSERT OR UPDATE OR DELETE ON server_probe_execution_payloads
|
|
FOR EACH ROW EXECUTE FUNCTION server_probe_storage_payload_guard();
|
|
-- Preserve every original INSERT/UPDATE rule; only the already-validated
|
|
-- lifetime predicate can admit DELETE; no cleanup is scheduled by migration.
|
|
CREATE OR REPLACE FUNCTION server_probe_stage_immutable() RETURNS trigger LANGUAGE plpgsql AS $$ BEGIN
|
|
IF TG_OP='DELETE' THEN
|
|
IF server_probe_storage_gc_allowed(OLD.receipt_id) THEN RETURN OLD; END IF;
|
|
RAISE EXCEPTION 'probe stage lifetime has not elapsed';
|
|
END IF;
|
|
PERFORM receipt_id FROM server_probe_executions WHERE receipt_id=NEW.receipt_id AND state='claimed' FOR UPDATE;
|
|
IF NOT FOUND OR EXISTS(SELECT 1 FROM server_probe_execution_quarantines WHERE receipt_id=NEW.receipt_id) THEN RAISE EXCEPTION 'probe attempt is not executable'; END IF;
|
|
IF TG_OP='INSERT' AND (NEW.stage_index<>(SELECT COALESCE(max(stage_index),0)+1 FROM server_probe_execution_stages WHERE receipt_id=NEW.receipt_id)
|
|
OR EXISTS(SELECT 1 FROM server_probe_execution_stages WHERE receipt_id=NEW.receipt_id AND state<>'received'))
|
|
THEN RAISE EXCEPTION 'probe stages must be serial and contiguous'; END IF;
|
|
IF TG_OP='UPDATE' AND (OLD.state<>'intent' OR NEW.state<>'received' OR (to_jsonb(OLD)-ARRAY['state','outcome','received_at']) IS DISTINCT FROM (to_jsonb(NEW)-ARRAY['state','outcome','received_at']))
|
|
THEN RAISE EXCEPTION 'probe stage terminal state is immutable'; END IF;
|
|
RETURN NEW; END $$;
|
|
CREATE OR REPLACE FUNCTION server_probe_quarantine_immutable() RETURNS trigger LANGUAGE plpgsql AS $$ BEGIN
|
|
IF TG_OP='DELETE' AND server_probe_storage_gc_allowed(OLD.receipt_id) THEN RETURN OLD; END IF;
|
|
RAISE EXCEPTION 'probe quarantine remains immutable'; END $$;
|
|
CREATE OR REPLACE FUNCTION mcp_diagnostic_cancellation_immutable() RETURNS trigger LANGUAGE plpgsql AS $$
|
|
DECLARE original_receipt uuid;
|
|
BEGIN
|
|
IF TG_OP='DELETE' THEN
|
|
SELECT a.receipt_id INTO original_receipt FROM server_probe_storage_admissions a
|
|
JOIN mcp_operations o ON o.id=a.execution_id AND o.connection_id=OLD.connection_id
|
|
WHERE a.identity_kind='mcp' AND a.execution_id=OLD.operation_id AND a.owner_id=OLD.actor_id;
|
|
IF FOUND AND server_probe_storage_gc_allowed(original_receipt) THEN RETURN OLD; END IF;
|
|
END IF;
|
|
RAISE EXCEPTION 'diagnostic cancellation lifetime has not elapsed';
|
|
END $$;
|
|
INSERT INTO schema_migrations(id) VALUES ('0037_server_probe_storage_v2') ON CONFLICT (id) DO NOTHING;
|
|
COMMIT;
|